DirectX9上でのレンダリングでよく耳にするのが、フルスクリーンのフィルター系のシェーダーを走らせる際にUVがずれるという話。
この現象はUV座標を画面左上方向に半ピクセルずらすことで解決することが多いです。 実際そのように記述されているプログラムをよく見かけます。でも実際に起こっていることはちょっと違います。
正規化デバイス座標系を考える
DirectXでは正規化デバイス座標系で次の範囲にプリミティブが入れば描画の対象になります。
X:-1.0~1.0 Y:-1.0~1.0 Z: 0.0~1.0
頂点シェーダーなどから出力されたプリミティブの同次座標を正規化したXYZが、この範囲ならば描画されるはずです。 つまりXYが-1.0~1.0の範囲がフルスクリーンに対応した座標となるわけです。 ためしにサンプルプログラムをつくり、XY:-1.0~1.0を覆うようにプリミティブを出力すると、フルスクリーンにプリミティブを描画することが出来ます。 ところがDirectX9ではこの時点で、すでにこちらの想定からズレが生じています。
実はこのプリミティブは、スクリーン上で右下方向にに半ピクセルずれた位置に描画されています。 ただしDirectXのrasterization ruleでは、ピクセル中央をプリミティブの境界が通る場合、右下側のプリミティブが描画されることになっています。 このルールでスクリーンのピクセル全体へのプリミティブの描画というのは保たれます。ただしプリミティブの位置はずれているので、補完された頂点アトリビュートの値は想定している値からズレてしまいます。
これがよく言う”DX9ではUVがズレる”につながる訳です。
プリミティブの位置がずれているので、マルチサンプルのレンダーターゲットに描画すると、この現象ははっきりと観測することが出来ます。
レンダーターゲットの解像度に応じてプリミティブの位置がずれているのが良くわかります。
これを修正するのは簡単で、プリミティブの頂点の位置を半ピクセル分左上にオフセットすることで解決できます。
当然このプリミティブの位置のズレは、フルスクリーンのフィルターを掛けているときに限った話ではありません。他の描画でも起こっています。
下記のスクリーンショットはDX11とDX9で同じジオメトリを描画したものです。見比べると半ピクセルずれているのがわかります。
このズレを修正するためには、描画時に使用するプロジェクションマトリクスに下記のような、DX9にのみ起きるプリミティブのシフトを相殺するマトリクスを適用することで、描画位置をDX11と同じ位置にすることができます。
シフト用マトリクスの一例
D3DXMATRIXA16 mShift; D3DXMatrixIdentity(&mShift); mShift._41 = -1.0f / 1280.0f; //X方向のシフト mShift._42 = 1.0f / 720.0f; //Y方向のシフト mWorldViewProjection = mWorld * mView * mProj * mShift;
このスクリーンショットとDX11のレンダリングを見比べると、テクスチャのミップマップの自動生成による差異がありますが、プリミティブの位置はDX11と一致するようになったのがわかると思います。