本人花时间整理的渲染镜子的技术,有需要的朋友可以参照一下。
出处。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 (comparationoperation)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_STENCILMASK, 0x0000ffff);
Stencil Value 就是当前测试的模板缓冲的像素值,我们可以用模板渲染状态(stencil render states)来控制什么可以写在模板缓冲里
Comparison Operation包括如下:
typedef enum _D3DCMPFUNC {
D3DCMP_NEVER = 1,
D3DCMP_LESS = 2,
D3DCMP_EQUAL = 3,
D3DCMP_LESSEQUAL = 4,
D3DCMP_GREATER = 5,
D3DCMP_NOTEQUAL = 6,
D3DCMP_GREATEREQUAL = 7,
D3DCMP_ALWAYS = 8,
D3DCMP_FORCE_DWORD = 0x7fffffff
} D3DCMPFUNC;
最后到正题了:如何做镜子效果?
要编程实现镜子效果需要首先解决两个问题:
1. 要知道如何镜子是如何映射出物体的,我们就利用发射原理来正确地画出镜子里面的物体。
2. 要蒙蔽不是镜子的区域,让物体只在镜子里显示。
第一个问题就是用几何学解决,第二个问题是用我们上面讲到的模板技术。
第一个问题如果基础不好的,赶紧去恶补平面几何知识。道理就是:知道了原物体的向量,然后知道平面的公式,利用这两个已知量来计算映射物体的位置,就可以画出映射物体了,DirectX可以调用函数如下:
D3DXMATRIX *D3DXMatrixReflect( D3DXMATRIX *pout, // The resulting reflection matrix. CONST D3DXPLANE *pPlane // The plane to reflect about.);
其实我们一直都有在渲染镜子里面的物体的,不过是加了蒙蔽层效果,所以只会在适当角度的时候,你才能看到镜子里面的物体罢了。
渲染步骤如下:
1. 正常地渲染整个场面,但是暂时不渲染镜子里面的物体。
2. 把镜子位置渲染出来(或类似镜子可以映射物体的物体),设置模板测试为通过,通过的话设置镜子区域的像素为1. 其他非镜子区域的像素都为0.
3. 然后把需要映射的物体渲染到后台缓冲和模板缓冲中去。正如前面所介绍的这些像素通过模板测试的才可以显示,就是才可以渲染到后台缓冲中去。从逻辑上解析就是做了与运算,需要渲染的像素与像素为1的与运算之后保持原来像素,所以正常渲染;如果与像素为0的与运算之后就会为0,所以没有渲染。
以下是部分示例代码:
1. 首先设置好初始的渲染状态:
gd3dDevice->SetRenderState(D3DRS_STENCILENABLE, true);
gd3dDevice->SetRenderState(D3DRS_STENCILFUNC, D3DCMP_ALWAYS);//镜子区域的渲染都总是通过stencil测试,所以为always,当然镜子的区域可能会随着镜头的转变而转变的。
gd3dDevice->SetRenderState(D3DRS_STENCILREF, 0x1); //设置ref为1
gd3dDevice->SetRenderState(D3DRS_STENCILMASK, 0xffffffff);
gd3dDevice->SetRenderState(D3DRS_STENCILWRITEMASK, 0xffffffff);
gd3dDevice->SetRenderState(D3DRS_STENCILZFAIL, D3DSTENCILOP_KEEP);//stencil测试失败,则保持stencil buffer原有的像素,这里为0
gd3dDevice->SetRenderState(D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP);
gd3dDevice->SetRenderState(D3DRS_STENCILPASS, D3DSTENCILOP_REPLACE);//用上面的ref值1来代替stencil的当前值
2. 渲染镜子:
注意这里是重点,要做出镜子效果就必须理解好如何渲染镜子和为什么这样渲染镜子的。
这里只是把镜子渲染到模板缓冲中去,并没有更新后台缓冲,因为镜子并不需要显示在屏幕上,只是我们设置的一堵映射平面。
注意:画镜子的时候会自动同时更新back buffer和depthbuffer的,自动更新的算法应该是用或运算,把模板缓冲的像素和back buffer的像素做或运算。
这时候利用blending技术,把将要写入的物体像素全部设置为零,就是全透明了,那么就不会更新back buffer和depth buffer了。
// Disable writes to the depth and back buffers
gd3dDevice->SetRenderState(D3DRS_ZWRITEENABLE, false);//关闭depth buffer写入
gd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, true);//开启blending
gd3dDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ZERO);//设置源像素因子(就是将要写入的像素)为0,那么与像素值与之后,结果为零。
gd3dDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE);//目标(原来在back buffer的)像素因子为1, 与目标像素与之后,结果为目标像素值
// Draw mirror to stencil only.
drawMirror();
// Re-enable depth writes
HR(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-plane
D3DXMatrixReflect(&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));
这里是坐标变换的知识,利用原有的茶壶位置和反射平面的位置计算出需要映射茶壶的位置,然后渲染之。
5. 最后一步了
如果我们现在马上渲染茶壶的话,那反射镜子里面不会显示茶壶的。为什么?因为镜子离镜头更加近,所以把茶壶挡住了,这时候需要关闭深度测试。
注意:关闭深度测试的时候,画物体的先后顺序会很重要,画后的物体会遮蔽画先的物体,就是相当于把先画在back buffer的像素复写了。所以这里一定要画完镜子,然后再画镜子里面的物体,否则,镜子就挡住了里面的物体了。
// 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感觉还是挺方便的了,如果要更好的理解也许学习一下最基本的一些图形学知识会更加好吧。比如一些图形学最低层的算法,如何操作像素,一些画直线的算法和众多插值算法(这是很博大精深的算法),这些东西很低层,不过对于理解高层的应用还是非常有用的。