Stenciling技术实现镜子效果
本人花时间整理的渲染镜子的技术,有需要的朋友可以参照一下。
出处。http://blog.csdn.net/kenden23
简介:模板缓冲(The stencil buffer)(或者翻译为蒙蔽层缓冲吧)属于是一个后台缓冲,不过它跟back buffer不一样,它主要用来完成一些特效。比如:镜子效果,阴影效果等。
它的缓冲大小是和一般的后台缓冲(back buffer)和深度缓冲(depth buffer)一样的。
模板缓冲的工作原来就是用来阻挡后台缓冲的特定区域来完成特效的。
如果渲染的时候开启了深度缓冲,那么模板缓冲的计算时间基本上为零,速度很快。因为模板缓冲和深度缓冲是同时生成的,他们共同用一个32bits的缓冲。例如下面的四种:depth/stencil缓冲:
D3DFMT_D24S8 深度缓冲24 bits每像素 模板缓冲8bits每像素。
D3DFMT_D24X4S4
D3DFMT_D15S1
D3DFMT_D32 a 32-bit depth buffer only
thestencil test模板缓冲测试
决定什么像素可以显示在屏幕上是有模板缓冲测试决定的,例如运用下面逻辑:,
IF ref & mask (comparation operation) value& mask= = true THEN accept pixel
ELSE reject pixel
ref 和mask的值都是程序员定义的,value是像素的某值,通过comparation operation(也是程序员定义的函数)之后,比较如果为真就显示,不为真就不显示。
例如Direct3D9中就用下面函数来修改Stencil Reference Value(ref)为1.
gd3dDevice->SetRenderState(D3DRS_STENCILREF, 0x1);
默认的mask的值为0xffffffff, 没有蒙蔽任何位像素
下面代码修改为蒙蔽搞16位:
gd3dDevice->SetRenderState(D3DRS_STENCILENABLE, true);gd3dDevice->SetRenderState(D3DRS_STENCILFUNC, D3DCMP_ALWAYS);//镜子区域的渲染都总是通过stencil测试,所以为always,当然镜子的区域可能会随着镜头的转变而转变的。gd3dDevice->SetRenderState(D3DRS_STENCILREF, 0x1); //设置ref为1gd3dDevice->SetRenderState(D3DRS_STENCILMASK, 0xffffffff);gd3dDevice->SetRenderState(D3DRS_STENCILWRITEMASK, 0xffffffff);gd3dDevice->SetRenderState(D3DRS_STENCILZFAIL, D3DSTENCILOP_KEEP);gd3dDevice->SetRenderState(D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP);gd3dDevice->SetRenderState(D3DRS_STENCILPASS, D3DSTENCILOP_REPLACE);//用上面的ref值1来代替stencil的当前值注意这里是重点,要做出镜子效果就必须理解好如何渲染镜子和为什么这样渲染镜子的。
这里只是把镜子渲染到模板缓冲中去,并没有更新后台缓冲,因为镜子并不需要显示在屏幕上,只是我们设置的一堵映射平面。
// Disable writes to the depth and back buffersgd3dDevice->SetRenderState(D3DRS_ZWRITEENABLE, false);gd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, true);gd3dDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ZERO);gd3dDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE);// Draw mirror to stencil only.drawMirror();// Re-enable depth writesHR(gd3dDevice->SetRenderState( D3DRS_ZWRITEENABLE, true));3.设置映射渲染需要的状态:
// Only draw reflected teapot to the pixels where the mirror was drawn.gd3dDevice->SetRenderState(D3DRS_STENCILFUNC, D3DCMP_EQUAL);gd3dDevice->SetRenderState(D3DRS_STENCILPASS, D3DSTENCILOP_KEEP);
这时候如果stencil测试通过对的话,就保持需要画的物体的像素。测试函数为D3DCMP_EQUAL,而镜子的stencil值为1,测试的值也为1,mask的值为0Xffffffff,所以镜子区域的像素都为通过状态,就是说这个状态下,物体就能在镜子映射出来。
4.下面就是正式画镜子物体的步骤了,以画一个茶壶为例:// Build reflection transformation.D3DXMATRIX R;D3DXPLANE plane(0.0f, 0.0f, 1.0f, 0.0f); // xy-planeD3DXMatrixReflect(&R, &plane);// Save the original teapot world matrix.D3DXMATRIX oldTeapotWorld = mTeapotWorld;// Add reflection transform.mTeapotWorld = mTeapotWorld * R;// Reflect light vector also.D3DXVECTOR3 oldLightVecW = mLightVecW;D3DXVec3TransformNormal(&mLightVecW, &mLightVecW, &R);mFX->SetValue(mhLightVecW, &mLightVecW, sizeof(D3DXVECTOR3));
这里是坐标变换的知识,利用原有的茶壶位置和反射平面的位置计算出需要映射茶壶的位置,然后渲染之。
如果我们现在马上渲染茶壶的话,那反射镜子里面不会显示茶壶的。为什么?因为镜子的深度离镜头更加近,所以把茶壶挡住了,这时候需要关闭深度测试。
// Disable depth buffer and render the reflected teapot. We also// disable alpha blending since we are done with it.gd3dDevice->SetRenderState(D3DRS_ZENABLE, false);gd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, false);gd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_CW);drawTeapot();
用D3DCULL_CW,意思就是顺时针为正面,directx默认是逆时针为正面的, 因为在镜子里面是反射世界,所以正面和反面都调转过来了。
6.还有就是还原为默认渲染状态的代码了:// Restore original teapot world matrix and light vector.mTeapotWorld = oldTeapotWorld;mLightVecW = oldLightVecW;// Restore render states.gd3dDevice->SetRenderState(D3DRS_ZENABLE, true);gd3dDevice->SetRenderState( D3DRS_STENCILENABLE, false);gd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);
总结:
这里的渲染知识要一定基础,包括:
1. 坐标转换
2.几何知识
3.渲染过程一定了解
DirectX感觉还是挺方便的了,如果要更好的理解也许学习一下最基本的一些图形学知识会更加好吧。比如一些图形学最低层的算法,如何操作像素,一些画直线的算法和众多插值算法(这是很博大精深的算法),这些东西很低层,不过对于理解高层的应用还是非常有用的。