Direct3D テンプレート キャッシュ

テンプレート キャッシュは、特定の特殊効果を得るために使用されるオフスクリーン キャッシュであり、テンプレート キャッシュの解像度は、バックグラウンド キャッシュおよび深度キャッシュの解像度とまったく同じであるため、ピクセルも 1 対 1 に対応します。テンプレート キャッシュを使用すると、動的でターゲットを絞ったものにすることができ、ピクセルがバック キャッシュに書き込まれるかどうかが決まります。

たとえば、ミラー効果を実装する場合、ミラーが配置されている平面に特定のオブジェクトのイメージを描画するだけで済みますが、そのオブジェクトのイメージを、対応するサブエリアにのみ表示したい場合は、ミラーの場合、テンプレート キャッシュを使用して、オブジェクトのイメージを防ぐことができます。非ミラー領域に描画する場合、a ではミラーと壁の両方のイメージが描画され、b では非ミラー領域の描画が行われます。阻止される。

テンプレートキャッシュの使用

テンプレート キャッシュを使用するには、現在のデバイスが Direct3D の初期化中にテンプレート キャッシュをサポートしているかどうかをクエリする必要があります。サポートしている場合は、それを有効にする必要があります。

Device->SetRenderState(D3DRS_STENCILENABLE, true);
//do stencil work
Device->SetRenderState(D3DRS_STENCILENABLE, false);

IDirect3DDevice9::Clear メソッドを使用して、テンプレート キャッシュをデフォルト値にクリアできます。このメソッドは、バックグラウンド キャッシュと深度キャッシュもクリアできます。

Device->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER | D3DCLEAR_STENCIL, 0x00000000, 1.0f, 0);

マーク D3DClEAR_DTENCIL が 3 番目のパラメーターに追加され、テンプレート キャッシュ、ターゲット キャッシュ (バックグラウンド キャッシュ)、および深度キャッシュをクリアすることを示します。6 番目のパラメーターは、テンプレート キャッシュをクリアする値を指定するために使用されます。

テンプレートキャッシュフォーマットクエリ

テンプレート キャッシュは深度キャッシュと一緒に作成できます。深度キャッシュの形式を指定するときに、テンプレート キャッシュの形式も指定できます。テンプレート キャッシュと深度キャッシュは同じオフスクリーン サーフェス キャッシュを共有し、各ピクセルのメモリ セグメントは、特定のキャッシュに対応するいくつかの部分に分割されます。次に例を示します。

D3DFMT_D24S8 : 32 ビットの深度/ステンシル バッファーを作成します。各ピクセルの 24 ビットが深度バッファーに割り当てられ、8 ビットがステンシル キャッシュに割り当てられます。 D3DFMT_D24X4S4: 32 ビットの深度/ステンシル バッファーを作成し
ます。各ピクセルの 15 ビットが深度キャッシュに割り当てられ、各ピクセル
15 ビットが深度バッファに割り当てられます。 、1 ビットがステンシル キャッシュに割り当てられます。一部の
形式ではステンシル キャッシュにスペースが割り当てられません。たとえば、D3DFMT_D32 形式では 32 ビットの深度バッファのみが作成されます。

テンプレートテスト

ピクセルをバック キャッシュに書き込むかどうかを決定する意思決定プロセスはステンシル テストと呼ばれ、ステンシルが有効であると仮定して各ピクセルはステンシル テストされる必要があります。

(ref & マスク) ComparisonOperation (値 & マスク)
左オペランド LHS: アプリケーションによって定義されたテンプレート参照値 ref とテンプレート マスク マスクをビット単位の AND 演算で取得します 右
オペランド RHS: 現在取得されているピクセルのテンプレート キャッシュテスト済み value の数値とテンプレート マスクのマスクのビット単位の AND 演算が行われます。
演算結果が true の場合、ピクセルはバック キャッシュに書き込まれます。false の場合、ピクセルはバック キャッシュに書き込まれません。ピクセルがバック キャッシュに書き込まれない場合、深度キャッシュにも書き込まれません。

テンプレートテストコントロール

テンプレート参照値 テンプレート
参照値 ref のデフォルト値は 0 です。値を変更するには、 D3DRS_STENCILREF描画状態を使用できます。整数のビット配置が一目瞭然で、ビットごとの論理演算が容易になる 16 進数を使用する傾向があります。 。

Device->SetRenderState(D3DRS_STENCILREF, 0x1);

テンプレート マスク
テンプレート マスクは、ref 変数と value 変数の特定のビットをマスクするために使用されます。デフォルト値は0xffffffffです。テーブルはビットをマスクせず、描画状態D3DRS_STENCILMASKを使用して変更できます。

//屏蔽了高16位
Device->SetRenderState(D3DRS_STENCILMASK, 0x0000ffff);

テンプレート値
この値は、テンプレート キャッシュ内でテストされる現在のピクセルに対応する値です。テンプレート値を個別に明示的に設定することはできませんが、テンプレート キャッシュをクリアでき、テンプレートの描画状態を使用して制御することもできますテンプレート キャッシュに書き込まれるコンテンツ。

比較演算 比較
演算子関数は、状態D3DRS_STENCILFUNCを描画することで設定できます。パラメータは、 D3DCMPFUNC型を使用して列挙できます。
D3DCMP_NEVER: テンプレート テストは常に失敗します。つまり、比較関数は常に false を返します。
D3DCMP_LESS: LHS<RHS、その後
D3DCMP_EQUAL: LHS=RHS の場合、テンプレート テストは成功します D3DCMP_LESSEQUAL: LHS <
=RHS
D3DCMP_GREATER: LHS>RHS
D3DCMP_NOTEQUAL: LHS!=RHS
D3DCMP_GREATEREQUAL: LHS>=RHS
D3DCMP_ALWAYS: テンプレート テストは常に成功し、返されます真実

テンプレートキャッシュの更新

特定のピクセルをバック キャッシュに書き込むかどうかを決定するだけでなく、次の 3 つの考えられるシナリオに基づいてテンプレート キャッシュ内の値を更新する方法を定義することもできます。

i 番目の行と j 番目の列のピクセルテンプレート テストが失敗しました。描画状態D3DRS_STENCILFAILを使用して、次のようにテンプレート キャッシュ内の同じ位置にある項目の更新メソッドを定義できます。

Device->SetRenderState(D3DRS_STENCILFAIL, StencilOperation);

i 番目の行と j 番目の列のピクセル深度テストが失敗しました。描画状態D3DRS_STENCILZFAILを使用して、次のようにテンプレート キャッシュ内の同じ位置にある項目の更新メソッドを定義できます。

Device->SetRenderState(D3DRS_STENCILZFAIL, StencilOperation);

i 行 j 列のピクセル深度テストとテンプレート テストが両方とも成功し、テンプレート キャッシュ内の同じ位置にあるアイテムの更新メソッドは、描画状態D3DRS_STENCILPASSを使用して次のように定義できます。

Device->SetRenderState(D3DRS_STENCILPASS, StencilOperation);

StencilOperation は、次の定義済み定数を取ることができます
D3DSTENCILOP_KEEP: テンプレート キャッシュ内の値を更新しません (現在の値を保持します)
D3DSTENCILOP_ZERO: テンプレート キャッシュ内の値を 0 に設定します
D3DSTENCILOP_REPLACE: テンプレート キャッシュ内の対応する値をテンプレート参照値に置き換えます
D3DSTENCILOP_INCRSAT: テンプレート キャッシュの対応する値を増やす 最大値を超える場合は最大値を採用 D3DSTENCILOP_DECRSAT: テンプレート キャッシュの対応する値を減らす 最小値未満の場合は最小値を
採用
D3DSTENCILOP_INVERT : テンプレート キャッシュの対応する値をビット反転します
D3DSTENCILOP_INCR: テンプレート キャッシュの対応する値を増加します 最大値を超える場合は 0 となります
D3DSTENCILOP_DECR: テンプレート キャッシュの対応する値をビット反転します0未満の場合は最大値を取る

テンプレート書き込みマスク

テンプレートの描画状態に加えて、書き込みマスクを設定することもできます。この値は、テンプレート キャッシュに書き込む任意の値の特定のビットをマスクできます。描画状態 D3DRS_STENCILWRITEMASK を使用して、書き込みマスクの値を設定できます。デフォルト値は0xffffffffです

Device->SetRenderState(D3DRS_STENCILWRITEMASK, 0x0000ffff);

ミラーエフェクトルーチン

プログラムでミラー効果を実現するには、2 つの問題を解決する必要があります
: 1. 平面オブジェクトを画像化する方法を理解する 
; 2. 特定の表面領域をミラー面として「マーク」する必要があります。

D3DX ライブラリは、任意の平面を作成するためのミラー変換マトリックスを提供します。

D3DXMATRIX* D3DXMatrixReflect(
	D3DXMATRIX *pOut,		
	CONST D3DXPLANE *pPlane
);

他の 3 種類のミラー変換行列。これらは 3 つの標準座標平面 (yz 平面、xz 平面、xy 平面) に対するミラー変換です。

R_{yz}=\begin{bmatrix} -1 & 0 & 0 &0 \\ 0&1 & 0 &0 \\ 0& 0& 1 &0 \\ 0& 0 &0 &1 \end{bmatrix}  R_{xz}=\begin{bmatrix} 1 & 0 & 0 &0 \\ 0&-1 & 0 &0 \\ 0& 0& 1 &0 \\ 0& 0 &0 &1 \end{bmatrix}  R_{xy}=\begin{bmatrix} 1 & 0 & 0 &0 \\ 0&1 & 0 &0 \\ 0& 0& -1 &0 \\ 0& 0 &0 &1 \end{bmatrix}

yz 平面を基準とした点の画像を見つけるには、xz 平面の y コンポーネントを反転し、xy 平面の z コンポーネントを反転するのと同様に、点の x コンポーネントを反転するだけです。

効果実感

ミラー効果を実装する場合、オブジェクトはミラーの前にある場合にのみ画像化されますが、オブジェクトがミラーの前にあるかどうかを判断するための空間テストは実行したくありません。この問題を単純化するには、オブジェクトがどこにあるかに関係なく、そのイメージングを計算して描画し、テンプレート バッファー領域を使用して一部の領域の描画を防ぎます。

1. 通常どおりシーン全体 (床、壁、鏡、ティーポット) を描画しますが、最初にティーポットのイメージを描画しません 2. テンプレート キャッシュを
0 にクリアします
3. ミラーを構成するプリミティブをテンプレートに描画しますキャッシュを作成し、テンプレート テストを常に true に設定し、テストに合格した場合、テンプレート キャッシュ値が 1 に置き換えられるように指定します。そのため、ミラーの外側の領域のピクセル値は 0 になります。 4. ティーポットのイメージを描画します
。バックグラウンド キャッシュとテンプレート キャッシュ。テンプレート テストに合格すると、ティーポットの画像は次のようになります。画像はバックグラウンド キャッシュにのみ描画されます。テンプレート テストの成功条件は、テンプレート キャッシュの値が 1 であるため、ティーポットのみが描画されます。ミラー領域に引き寄せられます。

void RenderMirror()
{	
	Device->SetRenderState(D3DRS_STENCILENABLE, true);					//启用模板缓存
	Device->SetRenderState(D3DRS_STENCILFUNC, D3DCMP_ALWAYS);			//设置模板测试总为成功
	Device->SetRenderState(D3DRS_STENCILREF, 0x1);						//设置模板参考值
	Device->SetRenderState(D3DRS_STENCILMASK, 0xffffffff);				//设置模板掩码
	Device->SetRenderState(D3DRS_STENCILWRITEMASK, 0xffffffff);			//设置写模板掩码
	Device->SetRenderState(D3DRS_STENCILZFAIL, D3DSTENCILOP_KEEP);		//深度测试失败则模板缓存保持原状
	Device->SetRenderState(D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP);		//模板测试失败则模板缓存保持原状
	Device->SetRenderState(D3DRS_STENCILPASS, D3DSTENCILOP_REPLACE);	//通过模板/深度测试则模板缓存替换为模板参考值

	//绘制镜面到模板缓存
	Device->SetRenderState(D3DRS_ZWRITEENABLE, false);					//将绘制状态D3DRS_ZWRITEENABLE设为false阻止对深度缓存中进行写操作
	Device->SetRenderState(D3DRS_ALPHABLENDENABLE, true);				//开启融合运算
	Device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ZERO);				//设置源融合因子
	Device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE);				//设置目标融合因子
	
	Device->SetStreamSource(0, Mirror, 0, sizeof(Vertex));
	Device->SetFVF(Vertex::FVF);
	Device->SetMaterial(&MirrorMtrl);									//设置材质
	Device->SetTexture(0, MirrorTex);									//设置纹理
	D3DXMATRIX I;
	D3DXMatrixIdentity(&I);
	Device->SetTransform(D3DTS_WORLD, &I);
	Device->DrawPrimitive(D3DPT_TRIANGLELIST, 18, 2);

	Device->SetRenderState(D3DRS_ZWRITEENABLE, true);					//取消阻止对深度缓存的写操作

	//绘制茶壶映像
	Device->SetRenderState(D3DRS_STENCILFUNC, D3DCMP_EQUAL);			//设置比较运算符函数
	Device->SetRenderState(D3DRS_STENCILPASS, D3DSTENCILOP_KEEP);		//设置测试成功后保留模板缓存中的值
			
	//为物体的映像进行定位的镜像变换矩阵
	D3DXMATRIX W, T, R;
	D3DXPLANE plane(0.0f, 0.0f, 1.0f, 0.0f);							//xy plane
	D3DXMatrixReflect(&R, &plane);										//创建平面变换矩阵
	D3DXMatrixTransformation(&T, TeapotPosition, x, TeapotPosition.y, TeapotPosition.z);		//平移到尚未进行镜像变换的茶壶所在位置
	W = T * R;

	//如果现在就进行绘制,映像不会显示出来,因为镜子中的茶壶映像的深度大于镜面的深度(经过镜像变换矩阵后,镜壶位置在镜面的另一端)
	//所以构成镜面的图元就遮挡了镜壶映像,为了解决这个问题,需要将深度缓存清空
	Device->Clear(0, 0, D3DCLEAR_ZBUFFER, 0, 1.0f, 0);
	//如果仅对深度缓存进行清空,茶壶的映像会被绘制到镜面之前,这样看起来也是有问题,还需将茶壶映像与镜面进行融合操作,这样茶壶的视觉效果就会处于镜"中"
	//源像素来自茶壶映像,目标像素来自镜面
	Device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_DESTCOLOR);			//设置源融合因子
	Device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ZERO);				//设置目标融合因子

	Device->SetTransform(D3DTS_WORLD, &W);
	Device->SetMaterial(&TeapotMtrl);
	Device->SetTexture(0, 0);
	Device->SetRenderState(D3DRS_CULLMODE, D3DCULL_CW);					//修改背面消隐模式,改为只对顺时针绕序的三角形单元消隐
	Teapot->DrawSubset(0);
	
	Device->SetRenderState(D3DRS_ALPHABLENDENABLE, false);				//关闭融合
	Device->SetRenderState(D3DRS_STENCILENABLE, false);				
	Device->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);
}

ティーポットイメージとミラーフュージョン式

FinalPixel=sourcePixel\bigotimes destPixel+destPixel\bigotimes (0,0,0,0)=sourcePixel\bigotimes destPixel

バックサイドブランキングモードを変更する理由

オブジェクトが鏡に映されると、その表側と裏側が入れ替わりますが、巻き順は変わりません。そのため、「新しい」表側の巻き順により、Direct3D はそれらを裏側と誤認し、「新しい」 「裏側の巻き順を表側と間違えていました。これを修正するために、ブランキングモードを修正しました。」

平行光シャドウ、点光シャドウ、シャドウマトリックス

頂点 p を通過する平行光線:r(t)=p+tL   頂点 p を通過する点光線:r(t)=p+t(pL)

点光源と平行光源では L の意味が異なり、点光源では点光源の空間的位置を定義するために L が使用され、平行光源では平行光の方向を定義するために L が使用されます。この図から、平行光によって生じる影は、本質的には物体を特定の方向に沿った平面に平行投影したものであることがわかりますが、点光源によって生じる影は、物体を特定の方向に沿った平面に透視投影したものであることがわかります。光源を視点として使用します。

D3DXMATRIX* D3DXMatrixShadow(
	D3DXMATRIX *pOut, 
	CONST D3DXVECTOR4 *pLight,
	CONST D3DXPLANE *pPlane
);

テンプレート キャッシュを使用して二次融合を防止する

影を表現するためにオブジェクトを特定の平面に「平坦化」する場合、2 つ以上のオブジェクトが重なる可能性があります。フュージョン操作を使用してこれらの影を描画すると、それらの重なった領域が乗算されます。サブフュージョンでは、より暗く見えます。 。

この問題は、テンプレート キャッシュを利用することで解決できます。テンプレート テストは、初めて描画されるピクセルのみを受け入れるように設定されています。つまり、影を描画するためのピクセルが背景にキャッシュされている場合、対応するピクセルをマークします。テンプレート キャッシュ値 ピクセルが書き込まれる場合 テンプレート キャッシュ内の位置領域がマークされている場合、テンプレート テストは失敗するため、重複するピクセルの書き込みが防止され、二次融合が回避されます。

void RenderShadow()
{
	Device->SetRenderState(D3DRS_STENCILENABLE, true);
	Device->SetRenderState(D3DRS_STENCILFUNC, D3DCMP_EQUAL);
	Device->SetRenderState(D3DRS_STENCILREF, 0x0);
	Device->SetRenderState(D3DRS_STENCILMASK, 0xffffffff);
	Device->SetRenderState(D3DRS_STENCILWRITEMASK, 0xffffffff);
	Device->SetRenderState(D3DRS_STENCILZFAIL, D3DSTENCILOP_KEEP);
	Device->SetRenderState(D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP);
	Device->SetRenderState(D3DRS_STENCILPASS, D3DSTENCILOP_INCR);

	//计算阴影变换并将阴影平移到场景中恰当的位置
	D3DXVECTOR4 lightDirection(0.707f, -0.707f, 0.707f, 0.0f);
	D3DXPLANE groundPlane(0.0f, -1.0f, 0.0f, 0.0f);
	D3DXMATRIX S;
	D3DXMatrixShadow(&S, &lightDirection, &groundPlane);
	D3DXMATRIX T;
	D3DXMatrixTranslation(&T, TeapotPosition.x, TeapotPosition.y, TeapotPosition.z);
	D3DXMATRIX W = T * S;
	Device->SetTransform(D3DTS_WORLD, &W);

	//融合运算相关设置
	Device->SetRenderState(D3DRS_ALPHABLENDENABLE, true);
	Device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
	Device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);

	D3DMATERIAL9 mtrl = d3d::InitMtrl(d3d::BLACK, d3d::BLACK, d3d::BLACK, d3d::BLACK, 0.0f);
	mtrl.Diffuse.a = 0.5f;

	//禁用深度缓存的目的是防止出现深度冲突,当两个不同的表面在深度缓存中的深度值相同时会出现这种现象
	//由于谁前谁后深度缓存无从知晓,显示时就会出现闪烁现象,由于阴影和底板共面,俩者之间深度冲突极有可能出现
	//因此解决方案是先绘制底板,然后禁用深度测试,最后再绘制阴影,就保证了阴影被绘制在地板上。
	Device->SetRenderState(D3DRS_ZENABLE, false);

	Device->SetMaterial(&mtrl);
	Device->SetTexture(0, 0);
	Teapot->DrawSubset(0);

	Device->SetRenderState(D3DRS_ZENABLE, true);
	Device->SetRenderState(D3DRS_ALPHABLENDENABLE, false);
	Device->SetRenderState(D3DRS_STENCILENABLE, false);
}

深度の競合を防ぐもう 1 つの方法は、Direct3D の深度オフセット メカニズムを使用することです。

テンプレート キャッシュでは、他の種類のプログラムも実装できます。

1. シャドウ ボリューム
2. ディゾルションとフェード
3. 奥行きの複雑さの視覚化
4. 輪郭とシルエットの効果
5. 幾何学的エンティティの構築
6. 共平面性によって引き起こされる奥行きの矛盾の修正

おすすめ

転載: blog.csdn.net/SwordArcher/article/details/133143317