一、模板缓冲
与模板缓冲相关的操作有两种——比较操作和更新操作。
1. 比较操作
Stencil Test 比较的是Reference和Stencil Buffer中的值,公式如下:
(Stencil Ref &mask ) op (Stencil Buffer &mask ) // 左右顺序不可颠倒
相应的DX和OpenGL中的命令为:
glStencil(cmp_fun, ref, mask) 和 device->SetRenderState(D3DRS_STENCILFUNC, cmp_fun)
其中的cmp_fun就是上式中的“op”,实际为 “〉”、“〈”、“〉=”、“〈=”等比较运算。所谓的Stencil Test就是进行op运算。op运算的结果是与Stencil Test的状态相对应的(Stencil Test只有pass、fail两种状态),其对应关系如下:
op的运算结果 Stencil Test
true pass
false fail
当op被设为always(DX:D3DCMP_ALWAYS | OPENGL:GL_ALWAYS)运算时,无论Stencil Ref和Stencil Buffer的当前值如何,Stencil Test永远为pass。
2. 更新操作
Stencil Test有pass/fail两种结果,Z-Buffer Test也有pass/fail两种结果。Stencil Buffer中的值的更新,需要同时指明是在何种状态(实际上是由Stencil Test和Z-Buffer Test运算结果两两组合而成的一共四种状态)下进行的何种更新。更新操作命令格式如下:
DX: device->SetRenderState(state, op)
OpenGL: glstencilOp(op1, op2, op3)
OpenGL DX(state值)
Stencil Test pass 参数op3 D3DRS_STENCILPASS
Stencil Test fail 参数op1 D3DRS_STENCILFAIL
Z Buffer Test fail 参数op2 D3DRS_STENCILZFAIL
OpenGL(op1/op2/op3的值) DX(op的值)
GL_KEEP D3DSTENCILOP_KEEP
GL_ZERO D3DSTENCILOP_ZERO
GL_REPLACE D3DSTENCILOP_REPLACE //用reference值替换模板缓冲中相应位置的值
GL_INCR D3DSTENCILOP_INCRSAT
GL_DECR D3DSTENCILOP_DECRSAT
GL_INCR_WRAP D3DSTENCILOP_INCR
GL_DECR_WRAP D3DSTENCILOP_DECR
要注意加以区分的两个概念——“stencil test通过后对颜色缓冲值的更新”和“stencil test通过后对模板缓冲值的更新”。
3. Stencil Reference
Stencil Reference主要有两个作用:
一是,在比较操作中用作进行比较的一个值;二是,在更新操作中用来替换模板缓冲中的相应数据(GL_REPLACE/D3DSTENCILOP_REPLACE)。
4. Z-Buffer Test 与 Stencil Test 的先后顺序
在OpenGL中Depth Test(Z-Buffer Test)是在Stencil Test之后,参见。
而在DirectX中Z-Buffer Test 是在Stencil Test之前,参见。 // 这里是DX9的资料,DX11后渲染管线有较大变化,不知道这一顺序是否有变。
二、阴影体(Shadow Volume)
通过阴影体来描绘物体投影的技术,在很多地方都有相关的介绍,但多不够直观。将阴影体(Shadow Volume)与Stencil Test结合来形成物体阴影的技术是其中最常见的一种方式,现结合下图作简单介绍。
假设,有一个对象OB在光源L作用下,在平面P上形成自身的阴影W,阴影在屏幕窗口S上对应的区域为W'(如上图)。此时光源L与OB相交的边沿光照方向的延长线,和OB的背光面一起将形成一个棱台VL。Shadow Volume的基本思想是这样的:先生成一个VL的实体并将其投影到S上,但并不真的把它画出来(也就是说,在将VL投影到S上时,并不真正地更新相应的颜色缓冲),而只是借此操作来设置S上的W'区域所对应的模板缓冲的值,同时使W'所对应的模板缓冲中的值与其它区域所对应的模板缓冲中的值区别开来。这样就可以在接下来的过程中,借助模板缓冲将S上的W'区域涂黑,形成最终的阴影。
通过VL的投影过程来形成W'区域与其它区域的Stenclil Buffer值的差别,是基于这样一种思想:假设有一条视线V1与VL正面(面对观察者或摄像机)相交于点b,然后又与VL背面(背对观察者或摄像机)相交于点c,那么说明V1所经过的区域没有其它的物体,所以VL就不会在此处有阴影投射,也就是说V1与S的交点a上的颜色,将由场景中的其它对象来决定;假设有另一条视线V2与VL正面相交于点e,在与VL背面相交之前先与平面P相交于点f,这就说明点f必处于OB的阴影中,那么V2与S的交点d上的颜色必然要带上阴影色。以下的技术手段是对这一原理的实现。
先将VL的正面(面向观察者或摄像机)在S上作一次投影(不更新颜色缓冲值),同时使S上与其相应的区域(在上图中没有画出)所对应的模板缓冲值加一;然后,再将VL的背面(背向观察者或摄像机)在S上再作一次投影(不更新颜色缓冲值),同时使S上与其相应的区域(在上图中没有画出)所对应的模板缓冲值减一。当然,在此过程中场景里所有其它对象也要渲染到S上。
在渲染平面P时,Z-Buffer的Test和Write功能正常开启,在对VL的正、背面进行投影时,Z-Buffer的Test功能开启,而Write功能关闭。这样就使得在VL正面投影到S上时,不会更新Z-Buffer中的值,而平面P在渲染时会更新Z-Buffer的值。在V2所经过的路径上,由于平面P处于构成VL背面的诸三角形之前,因此,在Z-Buffer Test阶段,构成VL背面的诸三角形在光栅化阶段将被丢弃,所以,VL背面投影操作所附带的对相应区域的模板缓冲值的减一操作也就自动取消了,最终使得f点所对应的模板缓冲值,在加一操作(进行VL正面投影时进行)后就一直不变了;而在V1所经过的路径上,由于没有其它的物体的阻隔,对模板缓冲区的加一、减一操作都会按预期进行。这样一来,W'区域与其它区域相应的模板缓冲值就被区别开来了。
(注意,DX与OpenGL虽然在Stencil Test和Z-Buffer Test的先后顺序上有所不同,但两者对Stencil Buffer值的设置都是在Z-Buffer Test和Stencil Test都完成之后才进行的。)