游戏开发基础(八)

 

第八章
模板缓存是一个用于获得某种特效的离屏缓存
模板缓存的分辨率与后台缓存和深度缓存的分辨率完全相同,所有模板缓存中的像素与后台缓存和深度缓存中的像素是一一对应的
模板缓存的功能与模板类似,允许我们动态地,有针对性地决定是否将某个像素写入后台缓存中
在Direct3D初始化时,首先必须查询当前设备是否支持模板缓存,如果支持,还必须启用,为了启用模板缓存,必须将绘制状态D3DRS_STENCILENABLE设为true,如要禁用模板缓存,则该状态应设为false
Device->SetRenderState(D3DRS_STENCILENABLE,true);
Device->SetRenderState(D3DRS_STENCILENABLE,false);
DirextX9.0中增加了双面模板特性,该功能可通过削减绘制阴影体所需要的绘制路径,从而提升阴影体的绘制速度
IDirect3DDevice9::Clear方法将模板缓存清空为一个默认值,该方法也可对后台缓存的深度缓存进行清空操作
Device->Clear(0,0,D3DCLEAR_TAEGET|D3DCLEAR_ZBUFFER|D3DCLEAR_STENCIL,0xff000000,1.0f,0);
注意第三个参数增加了D3DCLEAR_STENCIL表明我们要对模板缓存,目标缓存(后台缓存)和深度缓存进行清空操作,第6个参数用于指定要将模板缓存清为何值,这里,我们设为0;

依据nVIDIA报告,在现在硬件中使用模板缓存可被认为是"没有计算开销的"运算,前提是你已经在使用深度缓存

模板缓存可与深度缓存一同创建,为深度缓存指定格式时,我们可以同时指定模板缓存格式,实际上,模板缓存和深度缓存共享同一个离屏的表面缓存,而每个像素的内存段被划分为若干部分,分别与某种特定缓存相对应,考虑3种深度/模板缓存格式:
#D3DFMT_D24S8  含义是,已创建了一个32位深度/模板缓存,其中每个像素的24位指定给深度缓存,8位指定给模板缓存
#D3DFMT_D24X4S4 含义是,已创建了一个32位深度/模板缓存,其中每个像素的24位指定给深度缓存,4位指定给模板缓存,其余4位不用
#D3DFMT_D15S1 含义是,已创建了一个16位深度/模板缓存,其中每个像素的15位指定给深度缓存,1位指定给模板缓存
注意,一些格式没有为模板缓存分配任何空间,例如,D3DFMT_D32格式仅创建一个32位深度缓存
可用模板缓存来阻止对后台缓存中某些特定区域进行绘制,判断是否将某个像素写入后台缓存的决策过程称为模板测试
假定模板已处于启用状态,则每个像素都需要进行模板测试,模板测试需要如下两个操作数:
#左操作数(LHS = ref & mask)该值由应用程序定义的模板参考值(ref)和模板掩码(mask)通过按位与运算得到
#右操作数(RHS = value & mask)该值由当前进行测试的像素的模板缓存中的数值(value)与模板掩码(mask)通过按位与运算得到
模板测试的下一步是依据comparison operation 所指定的比较规则对LHS和RHS进行比较,上述表达式的运算结果为布尔类型(true或false),如果测试为true,将该像素写入后台缓存,如果测试为false,将阻止写入后台缓存,当然当一个像素不被写入后台缓存时,也不会被写入深度缓存。
我们可以对模板参考值,模板掩码,以及对比较函数进行设定

模板参考值(stencil reference value)ref 的默认值为0 ,但我们可用D3DRS_STENCILREF绘制状态改变该值。

例:将模板参考值设为1
 Device->SetRenderState(D3DRS_STENCILREF,0x01);

模板掩码(mask)用于屏蔽(隐藏)ref 和value变量中的某些位,其默认值为0xffffffff,表示不屏蔽任何位,我们可借助绘制状态D3DRS_STENCILMASK来修改该掩码值,
例:屏蔽高6位;
 Devie->SetRenderState(D3DRS_STENCILMASK,0x0000ffff);
模板值是当前待测试像素在模板缓存中的对应值,
例:对第i行,第j列的像素进行测试,则value就是模板缓存中第i行,第j列的值,我们不能显式第单独设置模板值,但可以对模板缓存进行清空操作,可以用模板的绘制状态控制将要写入模板缓存的内容

可以通过绘制状态D3DRS_STENCILFUNC来设置比较运算函数,该比较运算函数可取自枚举类型D3DCMPFUNC
typedef enum D3DCMPFUNC
{
D3DCMP_NEVER = 1,//比较函数总是返回false
D3DCMP_LESS = 2 ,//若LHS < RHS则模板测试成功
D3DCMP_EQUAL = 3,//若LHS = RHS则模板测试成功
D3DCMP_LESSEQUAL = 4,//若LHS <= RHS则模板测试成功
D3DCMP_GREATER = 5,//若LHS > RHS则模板测试成功
D3DCMP_NOTEQUAL = 6,//若(LHS = RHS) == false 则模板测试成功
D3DCMP_GREATEREQUAL = 7,//若LHS >= RHS则模板测试成功
D3DCMP_ALWAYS = 8,//比较函数总是返回true
D3DCMP_FORCE_DWORD = 0x7fffffff,
}D3DCMPFUNC,*LPD3DCMPFUNC;

定义模板缓存中的值如何进行更新:
#第i行,第j列的像素模板测试失败,这种情况下,我们可借助绘制状态D3DRS_STENCILFAIL将模板缓存中处于同样位置的项的更新方式定义如下:Device->SetRenderState(D3DRS_STENCILFAIL,StencilOperation);
#第i行,第j列的像素深度测试失败,可借助绘制状态D3DRS_STENCILZFAIL将模板缓存中处于同样位置的项的更新方式:
Device->SetRenderState(D3DRS_STENCILFAIL,StencilOperation);
#第i行,第j列的像素深度测试和模板测试均成功,可借助绘制状态D3DRS_STENCILPASS将模板缓存中处于同样位置的项的更新方式定义如下:
Device->SetRenderState(D3DRS_STENCILPASS,StencilOperation);
StencilOperation可取一下预定义常量:
D3DSTENCILOP_KEEP 不更新模板缓存中的值(保留当前值)
D3DSTENCILOP_ZERO 将模板缓存中的值设为0
D3DSTENCILOP_REPLACE 用模板参考值替代模板缓存中的对应值
D3DSTENCILOP_INCRSAT 增加模板缓存中的对应数值,如果超过最大值,取最大值
D3DSTENCILOP_DECRSAT 减小模板缓存中的对应数值,如果小于最小值,取最小值
D3DSTENCILOP_INVERT  增加模板缓存中的对应数值,如果超过最大值,则取0
D3DSTENCILOP_DECR    减小模板缓存中的对应数值,如果如果小于0 ,则取最大值

设置写掩码(write mask),该值可屏蔽我们将写入模板缓存的任何值的某些位,可用绘制状态D3DRS_STENCILWRITEMASK来设定写掩码的值,其默认值为0xffffffff。
例:对高16位屏蔽:
 Device->SetRenderState(D3DRS_STENCILWRITEMASK,0x0000ffff);

实现镜面效果,需解决两个问题:首先,了解任意平面物体如何成像,其次,将某一表面区域"标记"为镜面,然后,就可用只绘制处于镜面区域中那部分物体的映像了,
如何成像,借助向量几何很容易解决,"标记"镜面借助模板缓存来解决
D3DX库提供了下列函数用于创建相对于任意平面的镜像变换矩阵
D3DXMATRIX* D3DXMatrixReflect(
D3DXMATRIX* pOut,
CONST D3DXPLANE * pPlane,
)

镜面效果实现步骤:
(1)像往常那样绘制整个场景(地板,墙壁和茶壶)但先不绘制茶壶的映像,注意,本步中尚不需修改模板缓存
(2)将模板缓存清0
(3)将构成镜面的图元仅绘制到模板缓存中,将模板测试设置为总成功,并指定如果测试通过,模板缓存值被替换为1,因为我们仅绘制镜面,
(4)现在我们将茶壶的映像绘制到后台缓存和模板缓存中,但要注意,如果通过了模板测试,我们将茶壶的映像仅绘制到后台缓存中,这次我们设置为如果模板缓存值为1,模板测试就一定成功,按照这种方式,茶壶仅被绘制到那个在对应的模板缓存中值为1的区域中,由于在模板缓存中,只有对应于镜面区域的部分的模板值为1,所以茶壶的映像将只绘制到镜面所在的子区域中。

例:
void RenderMirror()
{
 Device->SetRenderState(D3DRS_STENCILENABLE,true);
 Device->SetRenderState(D3DRS_STENCILFUNC,D3DCMP_ALWAYS);//模板比较运算函数设为D3DCMP_ALWAYS,测试总会成功
 Device->SetRenderState(D3DRS_STENCILREF,0x1);
 Device->SetRenderState(D3DRS_STENCILMASK,0xffffffff);
 Device->SetRenderState(D3DRS_STENCILWRITEMASK,0xffffffff);
 //如果深度测试失败,我们模板操作指定为D3DSTENCILOP_KEEP,意味不对模板缓存中的值进行更新,即保留其当前值,我这样做,是因为深度测试失败就意味着当前测试中的像素被遮挡,而我们也不希望将物体映像到某些部分绘制到被遮挡的像素上
 Device->SetRenderState(D3DRS_STENCILZFAIL,D3DSTENCILOP_KEEP);//
 //如果模板测试失败,我们仍将模板操作指定为 D3DSTENCILOP_KEEP
 Device->SetRenderState(D3DRS_STENCILFAIL,D3DSTENCILOP_KEEP);
 //如果深度测试和模板测试均通过,我们将模板缓存的操作方式设定为D3DSTENCILOP_REPLACE,意味用模板参考值(0x1)替代模板缓存中的对应值
 Device->SetRenderState(D3DRS_STENCILPASS,D3DSTENCILOP_REPLACE);
}

可将绘制状态D3DRS_ZWRITEENABLE设为false阻止对深度缓存中进行写操作
可借助融合操作并将源融合因子和目标融合因子分别设为D3DBLEND_ZERO和D3DBLEND_ONE来阻止对后台缓存的更新,将这些融合因子代入融合方程,可推知后台缓存没有发生任何变化
FinalPixel = sourePixel * (0,0,0,0) + DestPixel * (1,1,1,1) = (0,0,0,0) + DestPixel = DestPixel

Device->SetRenderState(D3DRS_SRCBLEND,D3DBLEND_ZERO);
Device->SetRenderState(D3DRS_DESTBLEND,D3DBLEND_ONE);

对绘制状态进行设置
Device->SetRenderState(D3DRS_STENCILFUNC,D3DCMP_EQUAL);
Device->SetRenderState(D3DRS_STENCILPASS,D3DSTENCILOP_KEEP);
现在我们就设置了一个新的比较操作,然后我们进行模板测试:
(ref & mask) == (value & mask)
(0x1 & 0xffffffff) == (value & 0xffffffff)
(0x1) == (value & 0xffffffff)
这表明仅当value=0x1时模板测试才会成功,由于value仅在模板缓存中对应于该镜面的区域中才为0x01,所有仅当我们要在那些区域中进行绘制时,模板测试才会成功,这样上面的茶壶映像只会被绘制到镜面中,而不会被绘制到其他表面上
注意,将绘制状态 D3DRS_STENCILPASS设为了D3DSTENCILOP_KEEP,
意味如果测试成功,则保留模板缓存中的值,所以,该模板缓存(模板操作方式为D3DSTENCILOP_KEEP)中的值没有发生变化。只用模板缓存来标记那些对应于镜面区域的像素

为了不遮挡构成镜面的图元,我们将深度缓存清空:
Device->Clear(0,0,D3DCLEAR_ZBUFFEER,0,1.0f,0);
然后我们还要进行融合操作,融合公式:
 FinalPixal = sourcePixel*destPixel + destPixel*(0,0,0,0) = sourcePixel*destPixel
Device->SetRenderState(D3DRS_SRCBLEND,D3DBLEND_DESTCOLOR);
Device->SetRenderState(D3DRS_DESTLEND,D3DBLEND_ZERO);

当物体在镜面中成像时,其正面和背面将会相互调换,但绕序并未发生变化,所以,"新"正面的绕序将使Direct3D误认为它们是背面,类似的"新"背面的绕序会使Direct3D误认为它们是正面,所以,徐改变背面消隐模式

将融合操作和模板操作禁用,并恢复先前的消隐模式
Device->SetRenderState(D3DRS_ALPHABLENDENABLE,false)
Device->SetRenderState(D3DRS_STENCILENABLE,false)
Device->SetRenderState(D3DRS_CULLIMODE,D3DCULL_CCW);

当给定投影平面和描述平行光光源(w = 0时)或点光源(w = 1 )的向量时,可以D3DX库的函数创建阴影矩阵:
D3DXMATRIX * D3DXMatrixShadow(
         D3DXMATRIX* pOut,
         CONST D3DXVECTOR4* pLight,
         CONST D3DXPLANE* pPlane);

借助模板缓存,将模板测试设置为只接受第一次得到绘制的那些像素,即在向后台缓存绘制阴影的像素时,对相应的模板缓存值进行标记,然后,如果企图将一个像素写入已经被绘制(模板缓存中已被标记的位置)的区域时,模板测试就会失败,这样就防治了重叠像素写入,并由此避免了二次融合的发生。

将模板比较函数设为D3DCMP_EQUAL,将D3DRS_STENCILREF绘制状态设为0x0,这就指定了如果模板缓存中对应值为0,阴影就被绘制到后台缓存中,由于模板缓存已被清零(0x0),当我们首次将阴影的像素写入时,模板测试结果为真,但由于已将绘制状态D3DRS_STENCILPASS设为D3DSTENCILOP_INCR,所以如果试图对一个已被写入的像素进行写操作,模板测试就会失败,当某像素被首次写入时,该像素的模板值就会增加为0x1,所以当试图再次对该像素进行写操作时,模板测试就会失败,这样就避免了对一个像素多次写入,从而避免了二次融合的发生

模板缓存和深度缓存共享同一个表面存储区,所以二者是同时创建的,我们可用D3DFORMAT类型指定深度/模板表面的类型

发布了79 篇原创文章 · 获赞 17 · 访问量 23万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章