65

私は現在、有名なユタ州のティーポットを描くためにベジエ曲線とサーフェスを使用しています。16 のコントロール ポイントのベジエ パッチを使用して、ティーポットを描画し、結果のティーポットを回転させる機能を提供する「ワールドからカメラへ」機能を使用して表示することができ、現在は正投影を使用しています。

その結果、「平らな」ティーポットが得られました。これは、正射投影の目的が平行線を維持することであると予想されます。

ただし、透視投影を使用してティーポットに奥行きを持たせたいと考えています。私の質問は、「ワールドからカメラへ」関数から返された 3D xyz 頂点をどのように取得し、これを 2D 座標に変換するのかということです。z=0 で投影面を使用し、ユーザーがキーボードの矢印キーを使用して焦点距離と画像サイズを決定できるようにしたいと考えています。

私はこれをJavaでプログラミングしており、すべての入力イベントハンドラーをセットアップしており、基本的な行列の乗算を処理する行列クラスも作成しています。ウィキペディアやその他のリソースをしばらく読んでいますが、この変換をどのように実行するかについてはよくわかりません。

4

10 に答える 10

106

この質問は少し古いようですが、検索でこの質問を見つけた人のために、とにかく答えを出すことにしました。
現在、2D/3D 変換を表す標準的な方法は、同次座標を使用することです。[x,y,w]は 2D の場合、[x,y,z,w]は 3D の場合です。3D と平行移動に 3 つの軸があるため、その情報は 4x4 変換マトリックスに完全に適合します。この説明では、列優先の行列表記を使用します。特に明記しない限り、すべての行列は 4x4 です。
3D ポイントからラスタライズされたポイント、ライン、またはポリゴンへの段階は次のようになります。

  1. 逆カメラ行列を使用して 3D ポイントを変換し、その後に必要な変換を行います。表面法線がある場合は、それらも変換しますが、法線を変換したくないため、w をゼロに設定します。法線を変換するマトリックスは等方性でなければなりません。スケーリングとせん断により、法線が変形します。
  2. ポイントをクリップ スペース マトリックスで変換します。この行列は、x と y を視野と縦横比でスケーリングし、z をニア クリッピング プレーンとファー クリッピング プレーンでスケーリングし、「古い」z を w に差し込みます。変換後、x、y、z を w で割る必要があります。これをパースペクティブ ディバイドと呼びます。
  3. 頂点がクリップ スペースにあるので、クリッピングを実行して、ビューポートの境界外のピクセルをレンダリングしないようにします。Sutherland-Hodgeman クリッピングは、最も広く使用されているクリッピング アルゴリズムです。
  4. x と y を w と半幅と半高に関して変換します。x 座標と y 座標がビューポート座標になりました。w は破棄されますが、1/w と z は通常保存されます。これは、1/w がポリゴン サーフェス全体で遠近法を修正する補間を行うために必要であり、z が z バッファーに格納され、深度テストに使用されるためです。

z が位置のコンポーネントとして使用されなくなったため、この段階が実際の投影です。

アルゴリズム:

視野の計算

これにより、視野が計算されます。tan がラジアンを取るか度を取るかは関係ありませんが、角度は一致する必要があります。角度が 180 度に近づくと、結果が無限大になることに注意してください。これほど広い焦点を持つことは不可能なので、これは特異点です。数値的な安定性が必要な場合は、角度を 179 度以下に保ちます。

fov = 1.0 / tan(angle/2.0)

また、1.0 / tan(45) = 1 であることに注意してください。他の誰かが、z で割ることを提案しました。ここでの結果は明らかです。90 度の FOV と 1:1 のアスペクト比が得られます。このように同次座標を使用すると、他にもいくつかの利点があります。たとえば、特殊なケースとして扱わずに、ニア プレーンとファー プレーンに対してクリッピングを実行できます。

クリップ行列の計算

これは、クリップ マトリックスのレイアウトです。アスペクト比は幅/高さです。したがって、x コンポーネントの FOV は、y の FOV に基づいてスケーリングされます。far および near は、near および far クリッピング プレーンの距離である係数です。

[fov * aspectRatio][        0        ][        0              ][        0       ]
[        0        ][       fov       ][        0              ][        0       ]
[        0        ][        0        ][(far+near)/(far-near)  ][        1       ]
[        0        ][        0        ][(2*near*far)/(near-far)][        0       ]

スクリーン投影

クリッピング後、これが画面座標を取得するための最終的な変換です。

new_x = (x * Width ) / (2.0 * w) + halfWidth;
new_y = (y * Height) / (2.0 * w) + halfHeight;

C++ での簡単な実装例

#include <vector>
#include <cmath>
#include <stdexcept>
#include <algorithm>

struct Vector
{
    Vector() : x(0),y(0),z(0),w(1){}
    Vector(float a, float b, float c) : x(a),y(b),z(c),w(1){}

    /* Assume proper operator overloads here, with vectors and scalars */
    float Length() const
    {
        return std::sqrt(x*x + y*y + z*z);
    }
    
    Vector Unit() const
    {
        const float epsilon = 1e-6;
        float mag = Length();
        if(mag < epsilon){
            std::out_of_range e("");
            throw e;
        }
        return *this / mag;
    }
};

inline float Dot(const Vector& v1, const Vector& v2)
{
    return v1.x*v2.x + v1.y*v2.y + v1.z*v2.z;
}

class Matrix
{
    public:
    Matrix() : data(16)
    {
        Identity();
    }
    void Identity()
    {
        std::fill(data.begin(), data.end(), float(0));
        data[0] = data[5] = data[10] = data[15] = 1.0f;
    }
    float& operator[](size_t index)
    {
        if(index >= 16){
            std::out_of_range e("");
            throw e;
        }
        return data[index];
    }
    Matrix operator*(const Matrix& m) const
    {
        Matrix dst;
        int col;
        for(int y=0; y<4; ++y){
            col = y*4;
            for(int x=0; x<4; ++x){
                for(int i=0; i<4; ++i){
                    dst[x+col] += m[i+col]*data[x+i*4];
                }
            }
        }
        return dst;
    }
    Matrix& operator*=(const Matrix& m)
    {
        *this = (*this) * m;
        return *this;
    }

    /* The interesting stuff */
    void SetupClipMatrix(float fov, float aspectRatio, float near, float far)
    {
        Identity();
        float f = 1.0f / std::tan(fov * 0.5f);
        data[0] = f*aspectRatio;
        data[5] = f;
        data[10] = (far+near) / (far-near);
        data[11] = 1.0f; /* this 'plugs' the old z into w */
        data[14] = (2.0f*near*far) / (near-far);
        data[15] = 0.0f;
    }

    std::vector<float> data;
};

inline Vector operator*(const Vector& v, const Matrix& m)
{
    Vector dst;
    dst.x = v.x*m[0] + v.y*m[4] + v.z*m[8 ] + v.w*m[12];
    dst.y = v.x*m[1] + v.y*m[5] + v.z*m[9 ] + v.w*m[13];
    dst.z = v.x*m[2] + v.y*m[6] + v.z*m[10] + v.w*m[14];
    dst.w = v.x*m[3] + v.y*m[7] + v.z*m[11] + v.w*m[15];
    return dst;
}

typedef std::vector<Vector> VecArr;
VecArr ProjectAndClip(int width, int height, float near, float far, const VecArr& vertex)
{
    float halfWidth = (float)width * 0.5f;
    float halfHeight = (float)height * 0.5f;
    float aspect = (float)width / (float)height;
    Vector v;
    Matrix clipMatrix;
    VecArr dst;
    clipMatrix.SetupClipMatrix(60.0f * (M_PI / 180.0f), aspect, near, far);
    /*  Here, after the perspective divide, you perform Sutherland-Hodgeman clipping 
        by checking if the x, y and z components are inside the range of [-w, w].
        One checks each vector component seperately against each plane. Per-vertex
        data like colours, normals and texture coordinates need to be linearly
        interpolated for clipped edges to reflect the change. If the edge (v0,v1)
        is tested against the positive x plane, and v1 is outside, the interpolant
        becomes: (v1.x - w) / (v1.x - v0.x)
        I skip this stage all together to be brief.
    */
    for(VecArr::iterator i=vertex.begin(); i!=vertex.end(); ++i){
        v = (*i) * clipMatrix;
        v /= v.w; /* Don't get confused here. I assume the divide leaves v.w alone.*/
        dst.push_back(v);
    }

    /* TODO: Clipping here */

    for(VecArr::iterator i=dst.begin(); i!=dst.end(); ++i){
        i->x = (i->x * (float)width) / (2.0f * i->w) + halfWidth;
        i->y = (i->y * (float)height) / (2.0f * i->w) + halfHeight;
    }
    return dst;
}

これについてまだ熟考している場合、OpenGL 仕様は、関連する数学の非常に優れたリファレンスです。http://www.devmaster.net/の DevMaster フォーラムにも、ソフトウェア ラスタライザーに関連する優れた記事が多数あります。

于 2009-05-15T02:26:19.290 に答える
23

これはおそらくあなたの質問に答えると思います。ここに私が書いたものがあります:

これは非常に一般的な答えです。カメラが (Xc, Yc, Zc) にあり、投影したい点が P = (X, Y, Z) であるとします。カメラから投影先の 2D 平面までの距離は F です (平面の方程式は Z-Zc=F です)。平面に投影された P の 2D 座標は (X', Y') です。

次に、非常に簡単に:

X' =​​ ((X - Xc) * (F/Z)) + Xc

Y' = ((Y - Yc) * (F/Z)) + Yc

カメラが原点の場合、これは次のように単純化されます。

X' =​​ X * (F/Z)

Y' = Y * (F/Z)

于 2009-04-07T05:33:06.077 に答える
11

遠近補正された座標を取得するには、座標で割るだけzです。

xc = x / z
yc = y / z

上記は、カメラが に(0, 0, 0)あり、平面に投影していると仮定して機能しz = 1ます。それ以外の場合は、カメラに対して座標を変換する必要があります。

3D ベジェ曲線の点を投影しても、投影された点を通る 2D ベジエ曲線を描くのと同じ点が得られない限り、曲線にはいくつかの複雑な問題があります。

于 2009-04-07T05:33:20.553 に答える
1

すべての回答は、タイトルで提起された質問に対処しています。ただし、テキストに暗示されている警告を追加したいと思います。ベジエ パッチはサーフェスを表すために使用されますが、パッチのポイントを変換してパッチを多角形にテッセレーションすることはできません。これによりジオメトリが歪んでしまうからです。ただし、最初に変換されたスクリーン許容値を使用してパッチをポリゴンに分割してからポリゴンを変換するか、ベジエ パッチを有理ベジエ パッチに変換してから、スクリーン空間許容値を使用してそれらを分割することができます。前者の方が簡単ですが、実稼働システムには後者の方が適しています。

あなたはもっと簡単な方法を望んでいると思います。このためには、逆透視変換のヤコビアンのノルムでスクリーン許容値をスケーリングし、それを使用してモデル空間で必要なテッセレーションの量を決定します (フォワード ヤコビアンを計算し、それを逆にしてから、標準を取る)。このノルムは位置に依存することに注意してください。視点によっては、いくつかの場所でこれを評価したい場合があります。また、射影変換は有理であるため、導関数を計算するには商規則を適用する必要があることも覚えておいてください。

于 2016-05-08T02:30:40.470 に答える
0

私はそれが古いトピックであることを知っていますが、あなたのイラストは正しくありません.ソースコードはクリップマトリックスを正しく設定しています.

[fov * aspectRatio][        0        ][        0              ][        0       ]
[        0        ][       fov       ][        0              ][        0       ]
[        0        ][        0        ][(far+near)/(far-near)  ][(2*near*far)/(near-far)]
[        0        ][        0        ][        1              ][        0       ]

あなたのものへのいくつかの追加:

このクリップ マトリックスは、カメラの動きと回転を追加する場合に、静的な 2D 平面に投影している場合にのみ機能します。

viewMatrix = clipMatrix * cameraTranslationMatrix4x4 * cameraRotationMatrix4x4;

これにより、2D 平面を回転させて移動させることができます..-

于 2010-07-22T08:29:46.310 に答える