遠近法の正確なテクスチャ マッピング。Z距離の計算が間違っている可能性があります
-
20-09-2019 - |
質問
ソフトウェア ラスタライザーを作成しているのですが、ちょっとした問題に遭遇しました。遠近法を正しいテクスチャ マッピングが機能しないように思えます。
私のアルゴリズムは、まずプロットする座標をソートすることです。 y
. 。これにより、最高点、最低点、および中心点が返されます。次に、デルタを使用して走査線を横切っていきます。
// ordering by y is put here
order[0] = &a_Triangle.p[v_order[0]];
order[1] = &a_Triangle.p[v_order[1]];
order[2] = &a_Triangle.p[v_order[2]];
float height1, height2, height3;
height1 = (float)((int)(order[2]->y + 1) - (int)(order[0]->y));
height2 = (float)((int)(order[1]->y + 1) - (int)(order[0]->y));
height3 = (float)((int)(order[2]->y + 1) - (int)(order[1]->y));
// x
float x_start, x_end;
float x[3];
float x_delta[3];
x_delta[0] = (order[2]->x - order[0]->x) / height1;
x_delta[1] = (order[1]->x - order[0]->x) / height2;
x_delta[2] = (order[2]->x - order[1]->x) / height3;
x[0] = order[0]->x;
x[1] = order[0]->x;
x[2] = order[1]->x;
そして、からレンダリングします order[0]->y
に order[2]->y
, を増加させます。 x_start
そして x_end
デルタによって。上部をレンダリングするとき、デルタは次のようになります。 x_delta[0]
そして x_delta[1]
. 。下部をレンダリングするとき、デルタは次のようになります。 x_delta[0]
そして x_delta[2]
. 。次に、スキャンライン上の x_start と x_end の間を線形補間します。UV 座標も同様に補間され、開始点と終了点から開始して y 順に並べられ、各ステップにデルタが適用されます。
これは、遠近法の正しい UV マッピングを実行しようとする場合を除いて、正常に機能します。基本的なアルゴリズムは次のとおりです UV/z
そして 1/z
各頂点に対して、それらの間を補間します。各ピクセルの UV 座標は次のようになります。 UV_current * z_current
. 。ただし、結果は次のとおりです。
反転した部分は、デルタがどこで反転されるかを示します。ご覧のとおり、2 つの三角形はどちらも地平線の異なる点に向かって進んでいるように見えます。
空間内の点における Z を計算するために使用するものは次のとおりです。
float GetZToPoint(Vec3 a_Point)
{
Vec3 projected = m_Rotation * (a_Point - m_Position);
// #define FOV_ANGLE 60.f
// static const float FOCAL_LENGTH = 1 / tanf(_RadToDeg(FOV_ANGLE) / 2);
// static const float DEPTH = HALFHEIGHT * FOCAL_LENGTH;
float zcamera = DEPTH / projected.z;
return zcamera;
}
そうですか、それは Z バッファーの問題でしょうか?
解決
ZBufferはそれとは関係ありません。
ZBuffer は、三角形が重なっていて、それらが正しく描画されていることを確認したい場合にのみ役立ちます (例:Z で正しく順序付けされています)。ZBuffer は、三角形のピクセルごとに、以前に配置されたピクセルがカメラに近いかどうかを判断し、そうであれば、三角形のピクセルを描画しません。
重ならない 2 つの三角形を描画しているため、これが問題になることはありません。
一度(携帯電話用に)固定小数点でソフトウェア ラスタライザーを作成したことがありますが、ラップトップにはソースがありません。それで、今夜、私がどのようにやったかを確認させてください。本質的に、あなたが持っているものは悪くありません!このようなことは非常に小さなエラーによって引き起こされる可能性があります
これをデバッグする際の一般的なヒントは、いくつかのテスト用三角形 (左側の傾斜、右側の傾斜、90 度の角度など) を用意し、デバッガーでそれをステップ実行し、ロジックがケースをどのように処理するかを確認することです。
編集:
私のラスタライザーの擬似コード (U、V、Z のみが考慮されます...)グーローも実行したい場合は、U、V、Z に対して行っていることと同様に、R G と B に対してもすべてを実行する必要があります。
三角形は 2 つの部分に分解できるという考え方です。上部と下部です。上部は y[0] から y[1] まで、下部は y[1] から y[2] までです。どちらのセットでも、補間に使用するステップ変数を計算する必要があります。以下の例は、上部の作成方法を示しています。必要であれば底部分もお付けします。
以下の「疑似コード」フラグメントの下部に必要な補間オフセットはすでに計算されていることに注意してください。
- まず coord(x,y,z,u,v) を coord[0].y < coord[1].y < coord[2].y の順序で並べます。
- 次に、2 つの座標セットが同一であるかどうかを確認します (x と y のみを確認します)。だったら描くなよ
- 例外:三角形の上端は平らですか?そうであれば、最初の傾きは無限大になります
- 例外2:三角形の底は平らですか (はい、三角形にも平らな底がある可能性があります ;^) ) その場合、最後の傾きも無限になります
- 2つの傾き(左側と右側)を計算します
leftDeltaX = (x[1] - x[0]) / (y[1]-y[0]) および rightDeltaX = (x[2] - x[0]) / (y[2]-y[0]) ) - 三角形の 2 番目の部分は、以下に応じて計算されます。三角形の左側が実際に左側にある場合 (または交換が必要な場合)
コードの断片:
if (leftDeltaX < rightDeltaX)
{
leftDeltaX2 = (x[2]-x[1]) / (y[2]-y[1])
rightDeltaX2 = rightDeltaX
leftDeltaU = (u[1]-u[0]) / (y[1]-y[0]) //for texture mapping
leftDeltaU2 = (u[2]-u[1]) / (y[2]-y[1])
leftDeltaV = (v[1]-v[0]) / (y[1]-y[0]) //for texture mapping
leftDeltaV2 = (v[2]-v[1]) / (y[2]-y[1])
leftDeltaZ = (z[1]-z[0]) / (y[1]-y[0]) //for texture mapping
leftDeltaZ2 = (z[2]-z[1]) / (y[2]-y[1])
}
else
{
swap(leftDeltaX, rightDeltaX);
leftDeltaX2 = leftDeltaX;
rightDeltaX2 = (x[2]-x[1]) / (y[2]-y[1])
leftDeltaU = (u[2]-u[0]) / (y[2]-y[0]) //for texture mapping
leftDeltaU2 = leftDeltaU
leftDeltaV = (v[2]-v[0]) / (y[2]-y[0]) //for texture mapping
leftDeltaV2 = leftDeltaV
leftDeltaZ = (z[2]-z[0]) / (y[2]-y[0]) //for texture mapping
leftDeltaZ2 = leftDeltaZ
}
- currentLeftX と currentRightX を両方とも x[0] に設定します
- currentLeftU を leftDeltaU に、currentLeftV を leftDeltaV に、currentLeftZ を leftDeltaZ に設定します
- 最初の Y 範囲の開始点と終了点を計算します。startY = ceil(y[0]);endY = ceil(y[1])
- 私の固定点アルゴリズムの場合、サブピクセルの精度のyの分数部分(これもフロートに必要だと思います)の場合、これは、ラインとテクスチャを作るために必要でした。ディスプレイの解像度)
- x が y[1] のどこにあるべきかを計算します。Halfwayx =(x [2] -x [0]) *(y [1] -y [0]) /(y [2] -y [0]) + x [0]およびuとvとzで同じ:途中U = (u[2]-u[0]) * (y[1]-y[0]) / (y[2]-y[0]) + u[0]
- そして、halfwayX を使用して、U、V、z のステッパーを計算します。if(halfwayX - x[1] == 0){slopeU=0,slopeV=0,slopeZ=0 } else {slopeU = (halfwayU - U[1]) / (halfwayX - x[1])} //( v と z も同様)
- Y の上部をクリッピングします (三角形の上部が画面外 (またはクリッピング長方形から外れた) 場合に備えて、どこから描画を開始するかを計算します)。
- y=startYの場合;y < 終了 Y;y ++){
- Y は画面の下を超えていますか?レンダリングをやめてください!
- 最初の水平線Leftcurx = ceil(startx)のcalc startxとendx;leftCurY = ceil(エンディ);
- 描画する線を画面の左側の水平境界線(またはクリッピング領域)にクリップします。
- 宛先バッファーへのポインターを準備します(毎回配列インデックスを介してそれを行うと遅すぎる) buf = destbuf + (yピッチ) + startX;(24ビットまたは32ビットのレンダリングを行っている場合に署名されていないINT)また、ここでZbufferポインターを準備します(これを使用している場合)
- for(x=startX;x < 終了 X;x ++){
- 次に、パースペクティブ テクスチャ マッピングを行います (バイリネア補間を使用せず、次のようにします)。
コードの断片:
float tv = startV / startZ
float tu = startU / startZ;
tv %= texturePitch; //make sure the texture coordinates stay on the texture if they are too wide/high
tu %= texturePitch; //I'm assuming square textures here. With fixed point you could have used &=
unsigned int *textPtr = textureBuf+tu + (tv*texturePitch); //in case of fixedpoints one could have shifted the tv. Now we have to multiply everytime.
int destColTm = *(textPtr); //this is the color (if we only use texture mapping) we'll be needing for the pixel
- ダミーライン
- ダミーライン
- ダミーライン
- オプション:この座標で以前にプロットされたピクセルが私たちのものより高いか低いかどうかを zbuffer で確認します。
- ピクセルをプロットする
- 開始Z += 傾斜Z;開始U+=傾斜U;開始V + = スロープV;//すべてのインターポレータを更新します
- x ループの終わり
- leftCurX+= leftDeltaX;rightCurX += rightDeltaX;leftCurU+= rightDeltaU;leftCurV += rightDeltaV;leftCurZ += rightDeltaZ;//Y 補間器を更新します
- ダミーライン
} y ループの終わり
//これで最初の部分は終わりです。これで三角形の半分が描画されました。上から真ん中のY座標まで。// 基本的にはまったく同じことを行いますが、今度は三角形の下半分に対して (他の補間器のセットを使用して) 行います。
「ダミー行」については申し訳ありません。これらはマークダウン コードを同期させるために必要でした。(すべてが意図したとおりに表示されるようになるまでに時間がかかりました)
これがあなたが直面している問題の解決に役立つかどうか教えてください。
他のヒント
私はあなたの質問を助けることができることを知りませんが、私は一度読んでいたソフトウェアレンダリングで最高の本の一つは、オンラインで入手可能です<のhref = "http://www.gamedev.net/マイケル・アブラッシュによってを参照/記事/ article1698.asp」のrel = "nofollowをnoreferrer">グラフィックスプログラミングブラックブックます。
1/z
を補間している場合は、、あなたはUV/z
、ないz
によって1/z
を乗算する必要があります。あなたがこれを持っていると仮定します:
UV = UV_current * z_current
とz_current
は1/z
を補間され、あなたはそれを変更する必要があります:
UV = UV_current / z_current
そして、あなたはz_current
のようなものにone_over_z_current
の名前を変更したい場合があります。