在3D图形编程中,向最终用户清晰地展示交互对象是一项挑战性任务。例如,在CAD软件中,如何简洁地显示用户在三维场景中选择的对象?在实时策略游戏中,如何展示用户控制的单位?为了解决这个问题,一种方法是对感兴趣的模型进行轮廓勾勒或发光处理。
在深入探讨发光效果的实现之前,有必要简要概述一下模板缓冲区。具体来说,它是什么?它的作用是什么?
模板缓冲区是一个按像素级别的位掩码。换句话说,它是一个通用的深度缓冲区!为了详细说明,考虑一个过度简化的渲染器中的深度缓冲区逻辑。当需要渲染一个三角形时,首先将三角形的顶点推送到渲染管线中。然后,管线将三角形转换到屏幕空间,将连续的三角形数据光栅化为离散的像素,并为这些像素计算着色方程。最后,渲染器执行深度测试。它将新像素的深度与深度缓冲区中已有像素的深度进行比较。如果新像素的深度较小,则用新像素替换旧像素。
模板测试的工作原理类似。然而,可以定义模板比较操作以及渲染器写入模板缓冲区的信息。
步骤1:将模型渲染到帧缓冲区、深度缓冲区和模板缓冲区中。
步骤2:在模板缓冲区中遮罩像素的同时,将模型的更大尺寸渲染到帧缓冲区和深度缓冲区中。(在这个渲染步骤中,可能需要更改着色方程。例如,如果想要一个红色发光效果,着色方程需要生成红色像素。)
由于这个项目基于DirectX10,将需要Windows Vista或更高版本。此外,使用的是DirectX,2010年2月SDK。需要安装2010年2月或更高版本的可再发行版本。最后,虽然不是必需的,但建议拥有一个兼容DirectX 10的GPU。
首先,所有示例要求也适用于源代码。此外,需要安装整个DirectX SDK,2010年2月版本或更高版本。安装SDK后,需要在IDE中包含DirectX和DXUT头文件,并链接它们各自的库。
注意:微软不分发DXUT库。SDK文件夹内将包含一个Visual Studio项目,其中包含DXUT头文件。构建此项目以创建库。
首先,不要忘记分配一个模板缓冲区。知道这听起来微不足道;然而,DirectX 10将深度缓冲区和模板缓冲区合并为一个对象,这使得它容易被忽视。以下是初始化缓冲区的一种方法。
C++
// 初始化一个适合发光效果的深度模板缓冲区。
D3D10_TEXTURE2D_DESC descDepth;
descDepth.Width = pBufferSurfaceDesc->
Width;
descDepth.Height = pBufferSurfaceDesc->
Height;
descDepth.MipLevels =
1
;
descDepth.ArraySize =
1
;
// “S8”为模板保留32位中的8位。
descDepth.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
descDepth.SampleDesc.Count =
1
;
descDepth.SampleDesc.Quality =
0
;
descDepth.Usage = D3D10_USAGE_DEFAULT;
descDepth.BindFlags = D3D10_BIND_DEPTH_STENCIL;
descDepth.CPUAccessFlags =
0
;
descDepth.MiscFlags =
0
;
V_RETURN( pd3dDevice->
CreateTexture2D( &descDepth, NULL, &g_pDepthStencil ) );
// 为深度模板缓冲区设置视图。
D3D10_DEPTH_STENCIL_VIEW_DESC descDSV;
ZeroMemory( &descDSV,
sizeof
( D3D10_DEPTH_STENCIL_VIEW_DESC ) );
descDSV.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
descDSV.ViewDimension = D3D10_DSV_DIMENSION_TEXTURE2D;
descDSV.Texture2D.MipSlice =
0
;
V_RETURN( pd3dDevice->
CreateDepthStencilView
( g_pDepthStencil, &descDSV, &g_pDSView ) );
接下来,DirectX10通过深度模板状态对象控制对这些缓冲区的写入。确保为每个渲染步骤设置几个这样的对象。
顺便说一句,DirectX实现了双面模板。模板缓冲区阴影效果最充分利用了这个特性。因此,对于发光效果,只需忽略“背面”像素操作。但是,确保它们不会干扰并从“正面”操作中调整值。
C++
// 创建与文章第一步对应的深度模板状态。
D3D10_DEPTH_STENCIL_DESC dsDesc;
dsDesc.DepthEnable =
true
;
dsDesc.DepthWriteMask = D3D10_DEPTH_WRITE_MASK_ALL;
dsDesc.DepthFunc = D3D10_COMPARISON_LESS;
// 模板测试参数
dsDesc.StencilEnable =
true
;
dsDesc.StencilReadMask =
0xFF
;
dsDesc.StencilWriteMask =
0xFF
;
// 如果像素是正面的,模板操作如下。
// 在失败时保留原始值。
dsDesc.FrontFace.StencilFailOp = D3D10_STENCIL_OP_KEEP;
dsDesc.FrontFace.StencilDepthFailOp = D3D10_STENCIL_OP_KEEP;
// 在通过时写入模板。
dsDesc.FrontFace.StencilPassOp = D3D10_STENCIL_OP_INCR_SAT;
dsDesc.FrontFace.StencilFunc = D3D10_COMPARISON_ALWAYS;
// 如果像素是背面的,模板操作如下。
// 由于不关心背面像素,总是保留原始值。
dsDesc.BackFace.StencilFailOp = D3D10_STENCIL_OP_KEEP;
dsDesc.BackFace.StencilDepthFailOp = D3D10_STENCIL_OP_KEEP;
dsDesc.BackFace.StencilPassOp = D3D10_STENCIL_OP_KEEP;
dsDesc.BackFace.StencilFunc = D3D10_COMPARISON_NEVER;
// 创建深度模板状态。
V_RETURN( pd3dDevice->
CreateDepthStencilState(&dsDesc, &g_pDSState) );
// 创建与文章第二步对应的深度模板状态。
dsDesc.DepthEnable =
true
;
dsDesc.DepthWriteMask = D3D10_DEPTH_WRITE_MASK_ALL;
dsDesc.DepthFunc = D3D10_COMPARISON_LESS;
dsDesc.StencilEnable =
true
;
dsDesc.StencilReadMask =
0xFF
;
dsDesc.StencilWriteMask =
0xFF
;
// 写入的内容无关紧要,因为在此步骤之后不使用这些值。
// 换句话说,只是使用这些值来遮罩像素。
dsDesc.FrontFace.StencilFailOp = D3D10_STENCIL_OP_KEEP;
dsDesc.FrontFace.StencilDepthFailOp = D3D10_STENCIL_OP_KEEP;
dsDesc.FrontFace.StencilPassOp = D3D10_STENCIL_OP_KEEP;
// 如果通过的参数等于缓冲区中的值,则模板测试通过。
dsDesc.FrontFace.StencilFunc = D3D10_COMPARISON_EQUAL;
// 同样,不关心背面像素。
dsDesc.BackFace.StencilFailOp = D3D10_STENCIL_OP_KEEP;
dsDesc.BackFace.StencilDepthFailOp = D3D10_STENCIL_OP_KEEP;
dsDesc.BackFace.StencilPassOp = D3D10_STENCIL_OP_KEEP;
dsDesc.BackFace.StencilFunc = D3D10_COMPARISON_NEVER;
// 创建深度模板状态。
V_RETURN( pd3dDevice->
CreateDepthStencilState(&dsDesc, &g_pDSStateOutline) );
最后,将所有内容整合在一起,开始渲染一些发光效果。
C++
// 设置与文章第一步对应的模板状态。
pd3dDevice->
OMSetDepthStencilState( g_pDSState,
0
);
// 此技术渲染第一步。
g_pOutlineTechnique->
GetPassByIndex(
0
)->
Apply(
0
);
pd3dDevice->
DrawIndexed(
36
,
0
,
0
);
// 设置与文章第二步对应的模板状态。
pd3dDevice->
OMSetDepthStencilState( g_pDSStateOutline,
0
);
// 此技术渲染第二步。
// 请记住,此步骤需要进行一些额外的缩放,并输出正确的颜色像素以实现发光效果。
g_pOutlineTechnique->
GetPassByIndex(
1
)->
Apply(
0
);
pd3dDevice->
DrawIndexed(
36
,
0
,
0
);
虽然这种实现对于立方体或球体来说效果很好,但让考虑一下环面。当缩放时,内环的像素将落在原始未缩放的环面内。这将产生一个有趣的效果,但并不是完全的发光效果。
此外,任何不均匀缩放的网格都会有类似的视觉伪影。