1. ホーム
  2. java

[解決済み] 3次元の点を2次元の透視投影に変換する方法は?

2022-03-17 07:08:59

質問

現在、ベジェ曲線と曲面を使って、有名なユタのティーポットを描く作業をしています。16制御点のベジェパッチを使って、ティーポットを描き、「world to camera」機能を使って表示することができました。

これは、正射投影の目的が平行線を維持することであるため、予想されたことでした。

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

私はこれをjavaでプログラミングしており、すべての入力イベントハンドラを設定し、基本的な行列の乗算を処理する行列クラスも書きました。また、基本的な行列の乗算を処理する行列クラスも書きました。しばらくの間、wikipediaや他のリソースを読みましたが、この変換をどのように実行するのかがよくわかりません。

どのように解決するのですか?

この質問は少し古いようですが、検索してこの質問を見つけた人のために、とりあえず答えを出しておくことにしました。
現在、2D/3D変換を表現する標準的な方法としては 同次座標 . [x,y,w] は2Dの場合、そして [x,y,z,w] です。 は3D用です。3Dには移動だけでなく3軸があるので、その情報は4x4の変換行列にぴったりと収まります。この説明では、列挙型の行列表記を使うことにします。特に断りのない限り、すべての行列は4x4です。
3次元の点から、ラスタライズされた点、線、ポリゴンへのステージは次のようになります。

  1. 3D点をカメラ逆行列で変換し、その後に必要な変換を行います。サーフェス法線がある場合は、法線を平行移動させたくないので、w を 0 に設定して、それらも同様に変換します。法線を変換する行列は、以下のものでなければなりません。 等方性 スケーリングとシアーは法線を変形させます。
  2. クリップスペース行列で点を変換します。この行列は、x と y を視野とアスペクト比でスケーリングし、z を近傍と遠方のクリッピング平面でスケーリングし、「古い」 z を w に差し込みます。 パースペクティブディバイド .
  3. 頂点がクリップ空間にあるので、ビューポート境界外のピクセルをレンダリングしないように、クリッピングを実行します。 サザーランド・ホッジマンクリッピング は、最も広く使用されているクリッピングアルゴリズムです。
  4. xとyをwと半値幅と半値高さに関して変換します。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のアスペクト比が得られます。このように均質な座標を使用すると、他にもいくつかの利点があります。例えば、近景と遠景に対して、特別なケースとして扱うことなくクリッピングを行うことができます。

クリップマトリックスの計算

クリップマトリックスのレイアウトです。 アスペクト比 は、Width/Heightです。つまり、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の仕様書は、関連する数学について本当に良いリファレンスです。 のDevMasterフォーラムに参加しています。 http://www.devmaster.net/ ソフトウェアラスタライザーに関連する記事もたくさんあります。