模版缓存3

来源:互联网 发布:linux移动文件夹命令 编辑:程序博客网 时间:2024/06/07 20:39

7.1 模板技术

7.1.1 模板的概念

模板缓冲(Stencil Buffer)用来控制是否一个像素一个像素地向渲染目标表面绘制场景。在模板缓冲最基本的一级上,它能够使程序掩盖住被渲染图像的一部分,使它不能显 示。

模板缓冲区是一个记录每个像素信息的附加的缓冲区, 很像一个z缓冲区。实际上,该缓冲区就驻留在z缓冲区的某些位中。常见的模板/z缓冲区格式为15位的z和1位的模板,或24位的z和8位的模板,如图 7-1如示。在描绘多边形时,可以针对每个像素,对模板的内容进行简单的数学操作。例如,可以增加或减少模板缓冲区,或在模板值没能通过一项简单的比较测 试时,拒绝绘制像素。可以将帧缓冲区的一个区域标出,然后只对标出(或未标出)的区域进行描绘。上述操作对于此类效果十分有用,各种体积效果就是很好的例 子,比如阴影量。

图7-1 模板的存储格式

模板缓冲的工作原理也类似于深度缓冲。模板缓冲发挥 作用的时机是在所有的3D模型已投影到了目标缓冲区后。这样做的原因是模板会对每一个将要绘制的像素进行模板测试,模板测试使用模板参考值、模板掩码、模 板比较函数,以及当前像素对应的模板缓冲区的模板值作为参数。在测试后,会根据测试结果对模板缓冲中的值做出适当的处理。最后在渲染出最终的效果时,系统 会根据模板缓冲的值做出适当的渲染效果。可以看出,模板缓冲技术的本质和深度缓冲比较类似,都是提供一个标准进行比较,从而决定是否显示某个像素。其工作 过程可参见图7-2。

Direct3D在模板缓冲上执行一个基于 pixel-by-pixel的测试。对于目标表面上的每一个像素,它使用模板缓冲中相应的值——模板参考值(stencil reference value)和模板掩模值(stencil mask value)来执行测试。如果测试通过,Direct3D就会执行一个动作。测试使用下面的步骤来进行:

(1)将模板参考值与模板掩模进行逐位AND运算;

(2)将当前像素的模板缓冲置于模板掩模进行逐位 AND运算;

(3)用比较函数比较前两步得到的结果。

如果写成伪代码形式,步骤如下:

(StencilRef & StencilMask) CompFunc

(StencilBufferValue & StencilMask)

上式中,StencilBufferValue是当前像素的模板缓冲的内容;伪代码使用&符号来表示逐位AND操 作;StencilMask表示模板掩模的值;StencilRef表示模板参考值;CompFunc是比较函数。

图7-2 模板的工作过程

如果模板测试通过了,那么当前像素就会被写到目标表 面;如果没有通过,则会将当前像素忽略掉。默认的比较行为是无论每个逐位操作得到什么结果,都执行写像素操作(D3DCMP_ALWAYS标志)。可以改 变D3DRENDERSTATE_STENCILFUNC渲染状态来改变这一行为,通过传递D3DCMPFUNC枚举类型的一个成员来声明所需的比较函 数。

7.1.2 模板的程序实现

1.清空模板缓冲

先清空模板缓冲,使得整个模板缓冲的内容为一个指定值,例如:

Device->Clear(0, 0,

        D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER | D3DCLEAR_STENCIL,

        0xff000000, 1.0f, 0 );

D3DCLEAR_STENCIL标记说明在清空时要把模板绶冲区也清空,而用什么值来清空则是由最后一个参数来说明的, 一般是设为0。

2.启用模板缓冲

在默认情况下,D3D中的模板缓冲是关闭的,这是因为模板缓冲技术是一个逐像素比较的操作,需要占用一定的时间,而这个功 能并不是每时每刻都需要使用的。要想使用这个功能,需要如下面这样操作来打开使用开关。

Device->SetRenderState(D3DRS_STENCILENABLE, TRUE);

3.在模板缓冲中标记需要绘制的区域

这里要涉及模板测试,然后更新模板缓冲入口 (value)。模板测试工作是由系统来做的,但它的测试标准是由程序来指定的。

模板测试:

( ref & mask ) ComparisonOperation ( value & mask )

模板测试是针对每个像素的,不过先假定模板是开启的(enabled),而且它获得两个操作数:

(1)左边的操作数(LHS = ref & mask),表明应用程序定义的模板参考值(ref)和应用程序定义的掩码值(mask)的与操作;

(2)右边的操作数(RHS = value & mask)表明模板缓冲中将要测试的特定像素(value)和应用程序定义的掩码值(mask)的与操作。

模板测试将通过指定的比较运算来比较LHS和 RHS,这整个表达式的最终结果是一个布尔值(true或false)。如果测试结果为true,将把像素写入后台缓冲,如果是false,就阻止像素的 写入。当然,如果像素没有被写入后台缓冲,那么它也不会被写入深度缓冲。

模板参考值(Stencil Reference Value)ref默认为0,但是可以通过D3DRS_ STENCILREF渲染状态来改变它。

例如,以下的代码设置模板参考值为1:

Device->SetRenderState ( D3DRS_STENCILREF,0x1 );

模板掩码值mask用来遮盖ref和value变量的位。默认的mask为0xffffffff,它不会掩盖任何位。可以 通过设置D3DRS_STENCILMASK渲染状态来改变mask。以下例子表明掩盖高16位:

Device->SetRenderState(D3DRS_STENCILMASK,0x0000ffff);

测试函数ComparisonOperation可以采用的方案应该是一个枚举值:

typedef enum _D3DCMPFUNC {

    D3DCMP_NEVER = 1,       //模板测试永远不能成功

    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模板测试成功

    D3DCMP_GREATEREQUAL = 7,    //如果LHS >= RHS模板测试成功

    D3DCMP_ALWAYS = 8,          //模板测试永远成功

    D3DCMP_FORCE_DWORD = 0x7fffffff

} D3DCMPFUNC;

4.更新模板缓冲

除了决定是否写入或阻止一个特定像素被写入后台缓 冲,还能通过三种方法来定义模板缓冲入口将如何被更新。

1)[i][j]处像素模板缓冲测试失败

可以设置D3DRS_STENCILFAIL渲染状态来定义如何更新模板缓冲中的[i][j]来响应这种情况:

Device->SetRenderState(D3DRS_STENCILFAIL,StencilOperation);

2)[i][j]处像素深度缓冲测试失败

可以设置D3DRS_STENCILZFAIL渲染状态来定义如何更新[i][j]入口以响应这种情况:

Device->SetRenderState(D3DRS_STENCILZFAIL,StencilOperation);

3)[i][j]处像素深度缓冲和模板缓冲测试均成功

可以设置D3DRS_STENCILPASS渲染状态来定义如何更新[i][j]入口以响应这种情况:

Device->SetRenderState( D3DRS_STENCILPASS,StencilOperation);

其中StencilOperation可以是如表7-1所示的预定义常数之一。

表7-1 模板操作的类型

    

D3DSTENCILOP_KEEP

指定不改变模板缓冲

D3DSTENCILOP_ZERO

指定设置模板缓冲为0

D3DSTENCILOP_REPLACE

指定用模板引用值来代替模板缓冲

D3DSTENCILOP_INCRSAT

指定增加模板缓冲入口如 果增加后的值大于最大值,就把它限定为那个最大值

D3DSTENCILOP_DECRSAT

指定减少模板缓冲入口如 果减少后的值小于0,就把它限定为0

D3DSTENCILOP_INVERT

指定取模板缓冲入口的逆

D3DSTENCILOP_INCR

指定增加模板缓冲入口如 果增加后的值大于最大值,把它限定为0

D3DSTENCILOP_DECR

指定减少模板缓冲入口如 果减少后的值小于0,就把它限定为无穷大(maximum allowed value.)

7.1.3 模板的简单应用

1.模板技术的应用场合

模板技术由于其独特的性质,在一定条件下使用起来可 以达到一些意想不到的好处。它可以发挥作用的地方如下。

1)渐隐、淡入淡出与Swipe

渐隐技术就是将一幅图像逐渐地用另一幅图像替换掉。 可以使用Direct3D的多纹理融合技术来达到这样的效果,但是一般还是选用模板缓冲来完成。这样,就能在使用模板缓冲进行渐隐处理时,仍然可以使用多 纹理融合来进行其他效果的处理。

当程序执行渐隐操作时,要渲染两个不同的图像。这 时,使用模板缓冲来控制将哪幅图像的像素绘制到渲染目标表面。可以定义一系列模板掩模,并将它们拷贝到模板缓冲中。另一方面,也可以为第一帧定义一个基本 的模板掩模,然后随着帧的变化,再对基本掩模进行适当的改变。

在渐隐操作开始时,设置的模板函数和模板掩模应该使 开始图像的大部分像素能够通过测试,而终止图像的大部分像素则不应该通过测试。对于连续的帧,模板掩模要不断变化,这样才能使能够通过测试的开始图像的像 素不断减少,而随着帧的变化,越来越多的终止图像的像素将能够通过测试。通过这样的处理,就可以使用任意的图像来实现渐隐效果了。

淡入淡出实际上是渐隐技术的一种特殊情况。淡入技术 就是由一幅黑色或白色的图像逐渐变换为一幅场景中的图像。淡出则刚好相反,它是由一个场景图像逐渐变换为黑色或白色。

Direct3D程序还可以使用另一种类似的技术 ——swipe。例如,如果执行了一个从右至左的swipe操作,最终的图像就会逐渐从右至左地滑出到开始图像的上面。与执行渐隐操作时一样,需要定义一 些列的模板掩模,并将这些掩模加载到连续变化的帧的模板缓冲中;或者,可以对开始模板掩模进行连续的调制,再将调制后的掩模值应用到图像上。用这些模板掩 模来控制开始图像和终止图像的像素的填写,从而实现预想的效果。

一个swipe操作要比一个渐隐操作更加复杂,因为 它要按照与swipe相反的顺序来读取像素。也就是说,当一个swipe操作从右到左执行时,那么程序就必须在终止图像中从左到右地读取像素。

2)贴纸

Direct3D程序用贴纸技术将一个特殊图元图像 中的像素绘制到渲染目标表面。可以对具有共面多边形的图元使用这一技术,这样可以保证它们能够被正确地渲染。

比如要把一些轮胎痕迹及黄色警戒线渲染到一条公路 上。这些痕迹会直接位于公路的表面上,痕迹的z值与公路的z值完全相同。这样,深度缓冲就不能清楚地将它们两者分离开来,位于后面的图元上的一些像素就会 被渲染到前面的图元上。最终的图像就会在帧与帧之间产生微弱的闪光。这种效果称为“z fighting”或“flimmering”。

要解决这一问题,就要使用一个模板来将贴纸出现地方 的后面图元的像素掩盖掉。然后,关闭z-buffering,将前面图元的图像渲染到目标表面上被掩盖掉的区域上。

也可以使用多纹理融合来解决这一问题,但是,这样做 就会限制其他使用多纹理融合才能实现的效果的数量。而使用模板缓冲就可以节省多纹理融合的能力,从而能够实现更多的特殊效果。

3)合成

可以使用模板缓冲来将2D或3D图像合成到一个3D 场景中。用模板缓冲中的一个掩模来封闭渲染目标表面上的某个区域,然后就可以将2D信息,例如文本或位图,填写到被封闭的区域。另一方面,程序还可以在渲 染目标表面上被模板掩模掉的区域绘制额外的3D图元,甚至可以绘制一个完整的场景。

游戏中经常将多个3D场景合成在一起。例如,驾驶类 游戏中通常要显示一个后视镜,镜中显示了驾驶员背后的3D场景,这时,就可以用合成技术来将后视的三维场景合成到后视镜中。如图7-3所示。

图7-3 用模板实现合成的效果

4)轮廓与剪影

模板缓冲还能够用来实现一些抽象的效果,如轮廓与剪 影。

如果将一个模板掩模应用到一个与图元有相同形状但是 尺寸小一些的图像上,那么最终的图像就会得到这个图元的轮廓。然后,可以在模板掩模的区域填充一个具有固定颜色的图像,从而得到一种类似于浮雕的效果。

如果模板掩模的大小和形状与图元的大小和形状完全一 样,那么最后的图像就会在图元存在的地方出现一个“洞”。可以将这个“洞“填充上黑色,这样就得到了这个图元的剪影效果。

2.实现模板深度

下面通过一个具体的例子来说明如何在程序中使用模板 实现一些特殊的效果。在这个例子中,通过模板缓冲来记录一个模型的层次深度,并用不同的颜色来表示不同的深度,效果如图7-4所示。

图7-4 用模板显示模型重叠关系

【例7-1】 用模板显示模型重叠关系:

VOID Render()

{

    // 清空后备缓冲和深度缓冲

    g_pd3dDevice->Clear( 0, NULL,

        D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER,

                            D3DCOLOR_XRGB(0,0,0), 1.0f, 0 );

    g_pd3dDevice->BeginScene();

    SetStatesForRecordingDepthComplexity();

    SetupMatrices();

    g_pMesh.Render(g_pd3dDevice);

    ShowDepthComplexity();

    g_pd3dDevice->EndScene();

    g_pd3dDevice->Present( NULL, NULL, NULL, NULL );

}

在模板深度效果的这个例子中,按通常的程序框架建立一个程序并在其中导入一个直升机的模型,通过对时间的响应,直升机会在 划定的区域做盘旋飞行。这个例子的核心程序部分是在Render()函数中完成的,下面说明一下函数Render()。

先清空所有的缓冲区,包括目标缓冲区、深度缓冲区和 模板缓冲区,然后开始绘图。通过调用函数SetStatesForRecordingDepthComplexity()开始做模板深度的前期准备工作。 在SetupMatrices()中完成对时间的响应,并把时间的变化转化为直升机飞行位置的变化。接着调用直升机模型的演示函数。最后通过 ShowDepthComplexity()函数与前面的模板设置相对应,完成模板的所有设置。

将这段代码中最重要的两个函数做详细的介绍如下。

SetStatesForRecordingDepthComplexity() 函数用于在渲染前对模板操作做相应的设置。

【例7-2】 在渲染前对模板进行设置:

VOID    SetStatesForRecordingDepthComplexity()

{

   // 清空模板缓冲

   Device->Clear( 0L, NULL, D3DCLEAR_STENCIL, 0x0, 1.0f, 0L );

   // 执行模板测试, 并打开模板功能开关

   Device->SetRenderState( D3DRS_STENCILENABLE,   TRUE );

   // 将模板测试函数设为总是成功

   Device->SetRenderState( D3DRS_STENCILFUNC,     D3DCMP_ALWAYS );

   // 设置模板参考值为0

   Device->SetRenderState( D3DRS_STENCILREF,      0 );

   // 模板掩码设为0

   Device->SetRenderState( D3DRS_STENCILMASK,     0x00000000 );

   // 设置可以写到模板缓冲的写入掩码值

   Device->SetRenderState( D3DRS_STENCILWRITEMASK,0xffffffff );

   // 对模板缓冲中的每个像素做增量操作,

   //深度测试失败对模板缓冲中的值做增量操作

   Device->SetRenderState(D3DRS_STENCILZFAIL, D3DSTENCILOP_INCRSAT );

   //模板测试失败,保持模板缓冲中的值

   Device->SetRenderState( D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP );

   // 模板测试成功,对模板缓冲中的值做增量操作

   Device->SetRenderState( D3DRS_STENCILPASS,D3DSTENCILOP_INCRSAT );

}

ShowDepthComplexity()函数用于模型渲染完后对模板所做的操作。

【例7-3】 在渲染后对模板进行设置:

VOID    ShowDepthComplexity()

{

// 在离开缓冲后,打开透明混合

Device->SetRenderState( D3DRS_ZENABLE,          FALSE );

Device->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE );

Device->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_SRCCOLOR );

Device->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_INVSRCCOLOR );

// 设置模板操作的状态,深度测试失败,模板测试成功与失败这几种情况下都不改变

//模板缓冲中的值。左操作数等于右操作数时视为模板测试成功, 模板参考值设为0

Device->SetRenderState( D3DRS_STENCILZFAIL, D3DSTENCILOP_KEEP );

Device->SetRenderState( D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP );

Device->SetRenderState( D3DRS_STENCILPASS, D3DSTENCILOP_KEEP );

Device->SetRenderState( D3DRS_STENCILFUNC, D3DCMP_NOTEQUAL );

Device->SetRenderState( D3DRS_STENCILREF,   0 );

// 设置目标缓冲区为黑色

Device->Clear( 0L, NULL, D3DCLEAR_TARGET, 0x00000000, 1.0f, 0L );

// 设置渲染状态用于视口区

// 这个区域的颜色将通过入口

D3DRS_TEXTUREFACTOR

Device->SetFVF( D3DFVF_XYZRHW );

Device->SetStreamSource( 0, g_pBigSquareVB, 0, sizeof(D3DXVECTOR4) );

Device->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TFACTOR );

Device->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1 );

// 把模板缓冲中第一位有值的区域(即没有重叠的区域)设为红色

Device->SetRenderState( D3DRS_STENCILMASK, 0x01 );

Device->SetRenderState( D3DRS_TEXTUREFACTOR, 0xffFF0000 );

Device->DrawPrimitive( D3DPT_TRIANGLESTRIP, 0, 2 );

//把模板缓冲中第二位有值的区域(即有重叠的区域)设为绿色

Device->SetRenderState( D3DRS_STENCILMASK, 0x02 );

Device->SetRenderState( D3DRS_TEXTUREFACTOR, 0xff00ff00 );

Device->DrawPrimitive( D3DPT_TRIANGLESTRIP, 0, 2 );

//把模板缓冲中第三位有值的区域(即有多层重叠的区域)设为蓝色

Device->SetRenderState( D3DRS_STENCILMASK, 0x04 );

Device->SetRenderState( D3DRS_TEXTUREFACTOR, 0xff0000FF );

Device->DrawPrimitive( D3DPT_TRIANGLESTRIP, 0, 2 );

//在所有的渲染完成后,恢复原来的正常状态

//为第二次的渲染保留环境

Device->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE );

Device->SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE );

Device->SetTextureStageState( 0, D3DTSS_COLOROP,   D3DTOP_MODULATE );

Device->SetRenderState( D3DRS_ZENABLE,          TRUE );

Device->SetRenderState( D3DRS_STENCILENABLE,    FALSE );

Device->SetRenderState( D3DRS_ALPHABLENDENABLE, FALSE );

}

经过这一系列的设置操作,模型就会按不同的模板深度显示出不同的颜色。重要的是要注意模板设置的步骤和次序,在渲染前设置 模板测试的规则,在渲染后根据模板值做相应的操作,以达到想要的效果