版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Spring_24/article/details/78225148
一、概要
模版缓存是一个用于获得某种特效的离屏(off-screen)缓存。模版缓存的分辨率与后台缓存和深度缓存的分辨率完全相同,所以模版缓存中的像素与后台缓存和深度缓存中的像素是一一对应的。模版缓存允许我们动态地、有针对性地决定是否绘制某个像素,创建诸如图形合成、贴花、消融、淡入淡出、轮廓显示、侧影、滑入以及阴影等特殊效果。
模版测试的工作步骤如下:
1、 将模版参考值同模版掩码进行按位与运算。
2、将当前像素的模版缓存中的数值同模版掩码进行按位与运算。
3、利用模版比较函数,比较上述两步产生的结果。
二、模版缓存的格式
模版缓存可与深度缓存一同创建。为深度缓存指定格式时,我们可以同时指定模版缓存的格式。实际上,模版缓存和深度缓存共享同一个离屏的表面缓存,而每个像素的内存段被划分为若干部分,分别与某种特定缓存相对应。
例如:
●D3DFMT_D24S8 该格式的含义是,创建一个32位的深度/模版缓存,其中每个像素的24位指定给深度缓存,8位指定给模版缓存。
●D3DFMT_D24X4S4 该格式的含义是,创建一个32位的深度/模版缓存,其中每个像素的24位指定给深度缓存,4位指定给模版缓存,其余4位不使用。
●D3DFMT_D15S1 该格式的含义是,创建一个16位的深度/模版缓存,其中每个像素的15位指定给深度缓存,1位指定给模版缓存。
一些格式没有为模版缓存分配任何空间,例如,D3DFMT_D32仅创建一个32位的深度缓存。
三、模版测试
判定是否将某个像素写入后台缓存的决策过程称为模版测试,其表达式如下:
(ref & mask)ComparisonOperation (value & mask)
假定模版已处于启用状态,则每个像素都需要进行模版测试。模版测试需要如下两个操作数。
●左操作数(LHS = ref & mask),该值由应用程序定义的模版参考值(ref)和模版掩码(mask)通过按位与运算得到。
●右操作数(RHS = value & mask),该值由当前进行测试的像素的模版缓存中的数值(value)和模版掩码(mask)通过按位与运算得到。
接下来依据ComparisonOperation所指定的比较规则对 LHS 和 RHS 进行比较。比较结果为布尔类型。如果测试结果为 true ,便将该像素写入后台缓存。如果测试结果为 false ,则阻止该像素被写入后台缓存。当然,当一个像素不被写入后台缓存时,也不会被写入深度缓存。
四、模版测试的控制
1、模版参考值
模版参考值(stencil reference value)ref的默认值为 0 ,我们可以使用D3DRS_STENCILREF绘制状态改变该值。例如:
Device->SetRenderState(D3DRS_STENCILREF,0x1);
2、模版掩码
模版掩码(mask)用于屏蔽(隐藏)ref 和 value 变量中的某些位。其默认值为 0xffffffff ,表示不屏蔽任何位。我们可以使用绘制状态D3DRS_STENCILMASK来修改该值。例如:
Device->SetRenderState(D3DRS_STENCILMASK,0xffffffff);
3、模版值
模版值是当前待测试像素在模版缓存中的对应值。我们不能显式地单独设置模版值,但是可以对模版缓存进行清空操作。此外,我们还可以用模版的绘制状态控制将要写入模版缓存的内容。
4、比较运算
我们可以通过绘制状态D3DRS_STENCILFUNC来设置比较运算函数。该比较运算函数可取自如下枚举类型D3DCMPFUNC。
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, /* force 32-bit size 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, /* force 32-bit size enum */
} D3DCMPFUNC;
●D3DCMP_NEVER 模版测试总是失败,即比较函数总是返回 false 。
●D3DCMP_LESS 若 LHS < RHS ,则模版测试成功。
●D3DCMP_EQUAL 若 LHS = RHS ,则模版测试成功。
●D3DCMP_LESSEQUAL 若 LHS <= RHS ,则模版测试成功。
●D3DCMP_GREATER 若 LHS > RHS ,则模版测试成功。
●D3DCMP_NOTEQUAL 若 LHS ≠ RHS ,则模版测试成功。
●D3DCMP_GREATEREQUAL 若 LHS >= RHS ,则模版测试成功。
●D3DCMP_ALWAYS 模版测试总是成功,即比较函数总是返回 true 。
五、模版缓存的更新
除了决定一个具体像素是否应该被写入后台缓存,我们还可以基于以下 3 种可能的情形定义模版缓存中的值如何进行更新。
●第 i 行、第 j 列的像素模版测试失败。这种情况下,我们可借助绘制状态D3DRS_STENCILFAIL将模版缓存中处于同样位置的项的更新方式定义如下:
Device->SetRenderState(D3DRS_STENCILFAIL,StencilOperation);
●第 i 行、第 j 列的像素深度测试失败。这种情况下,我们可借助绘制状态D3DRS_STENCILZFAIL将模版缓存中处于同样位置的项的更新方式定义如下:
Device->SetRenderState(D3DRS_STENCILZFAIL,StencilOperation);
●第 i 行、第 j 列的像素深度测试和模版测试均成功。这种情况下,我们可借助绘制状态D3DRS_STENCILPASS将模版缓存中处于同样位置的项的更新方式定义如下:
Device->SetRenderState(D3DRS_STENCILPASS,StencilOperation);
其中StencilOperation可取以下预定义的常量:
●D3DSTENCILOP_KEEP 不更新模版缓存中的值,即保留当前值。
●D3DSTENCILOP_ZERO 将模版缓存中的值设为 0 。
●D3DSTENCILOP_REPLACE 用模版参考值替代模版缓存中的对应值。
●D3DSTENCILOP_INCRSAT 增加模版缓存中的对应数值,如果超过最大值,取最大值。
●D3DSTENCILOP_ZERO 将模版缓存中的值设为 0 。
●D3DSTENCILOP_REPLACE 用模版参考值替代模版缓存中的对应值。
●D3DSTENCILOP_INCRSAT 增加模版缓存中的对应数值,如果超过最大值,取最大值。
●D3DSTENCILOP_DECRSAT 减小模版缓存中的对应数值,如果小于最小值,取最小值。
●D3DSTENCILOP_INVERT 模版缓存中的值按位取反。
●D3DSTENCILOP_INCR 增加模版缓存中的对应数值,如果超过最大值,取 0 。
●D3DSTENCILOP_DECR 减小模版缓存中的对应数值,如果小于 0,取最大值 。
●D3DSTENCILOP_INVERT 模版缓存中的值按位取反。
●D3DSTENCILOP_INCR 增加模版缓存中的对应数值,如果超过最大值,取 0 。
●D3DSTENCILOP_DECR 减小模版缓存中的对应数值,如果小于 0,取最大值 。
六、模版写掩码
通过设置写掩码,我们可以屏蔽将写入模版缓存的任何值的某些位。可以用绘制状态D3DRS_STENCILWRITEMASK来设定写掩码的值,其默认值为 0xffffff。
Device->SetRenderState(D3DRS_STENCILWRITEMASK,0x0000ffff);
七、示例代码
下面的代码绘制一个矩形,矩形的左下角为红色,右上角为绿色,然后在矩形的正中心绘制一个茶壶。通过应用模版缓存,仅绘制茶壶位于矩形右上角的部分。
#include <d3d9.h>
#pragma warning( disable : 4996 ) // disable deprecated warning
#include <strsafe.h>
#pragma warning( default : 4996 )
#include <d3dx9math.h>
LPDIRECT3D9 g_pD3D = NULL;
LPDIRECT3DDEVICE9 g_pd3dDevice = NULL;
LPDIRECT3DVERTEXBUFFER9 g_pVB = NULL;
ID3DXMesh* Teapot = 0;
struct CUSTOMVERTEX
{
FLOAT x, y, z;
DWORD color;
};
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE)
HRESULT InitD3D( HWND hWnd )
{
if( NULL == ( g_pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) )
return E_FAIL;
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory( &d3dpp, sizeof( d3dpp ) );
d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8;
d3dpp.BackBufferCount = 1;
d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE;
d3dpp.MultiSampleQuality = 0;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.Windowed = true;
d3dpp.EnableAutoDepthStencil = true;
d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8;
d3dpp.Flags = 0;
if( FAILED( g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING,
&d3dpp, &g_pd3dDevice ) ) )
{
return E_FAIL;
}
return S_OK;
}
VOID SetupMatrices()
{
D3DXVECTOR3 vEyePt(0.0f, 0.0f, -20);
D3DXVECTOR3 vLookatPt(0.0f, 0.0f, 0.0f);
D3DXVECTOR3 vUpVec(0.0f, 1.0f, 0.0f);
D3DXMATRIXA16 matView;
D3DXMatrixLookAtLH(&matView, &vEyePt, &vLookatPt, &vUpVec);
g_pd3dDevice->SetTransform(D3DTS_VIEW, &matView);
//D3DXMatrixPerspectiveFovLH()函数中的最远、最近距离为相对于视点的距离(即vEyePt中的距离)
D3DXMATRIXA16 matProj;
D3DXMatrixPerspectiveFovLH(&matProj, D3DX_PI / 2, 1.0f, 1.0f, 200.0f);
g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &matProj);
}
HRESULT InitVB()
{
D3DXCreateTeapot(g_pd3dDevice, &Teapot, 0);
CUSTOMVERTEX vertices[6] = {
-10, -10, 0, D3DCOLOR_XRGB(255, 0, 0),
-10, 10, 0, D3DCOLOR_XRGB(255, 0, 0),
10, -10, 0, D3DCOLOR_XRGB(255, 0, 0),
-10, 10, 0, D3DCOLOR_XRGB(0, 255, 0),
10, 10, 0, D3DCOLOR_XRGB(0, 255, 0),
10, -10, 0, D3DCOLOR_XRGB(0, 255, 0)
};
if( FAILED( g_pd3dDevice->CreateVertexBuffer( sizeof( vertices ),
0, D3DFVF_CUSTOMVERTEX,
D3DPOOL_DEFAULT, &g_pVB, NULL ) ) )
{
return E_FAIL;
}
VOID* pVertices;
if( FAILED( g_pVB->Lock( 0, sizeof( vertices ), ( void** )&pVertices, 0 ) ) )
return E_FAIL;
memcpy( pVertices, vertices, sizeof( vertices ) );
g_pVB->Unlock();
SetupMatrices();
return S_OK;
}
VOID Cleanup()
{
if( g_pVB != NULL )
g_pVB->Release();
if(Teapot != NULL)
Teapot->Release();
if( g_pd3dDevice != NULL )
g_pd3dDevice->Release();
if( g_pD3D != NULL )
g_pD3D->Release();
}
void RenderRectangle()
{
g_pd3dDevice->SetStreamSource( 0, g_pVB, 0, sizeof( CUSTOMVERTEX ) );
g_pd3dDevice->SetFVF( D3DFVF_CUSTOMVERTEX );
g_pd3dDevice->SetRenderState( D3DRS_LIGHTING, FALSE );
g_pd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
g_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 2);
}
void RenderTeapot()
{
/************************************
/将模版缓存用于标记右上三角形区域/
***********************************/
g_pd3dDevice->SetRenderState(D3DRS_STENCILENABLE, true);
g_pd3dDevice->SetRenderState(D3DRS_STENCILFUNC, D3DCMP_ALWAYS);
g_pd3dDevice->SetRenderState(D3DRS_STENCILREF, 0x1);
g_pd3dDevice->SetRenderState(D3DRS_STENCILMASK, 0xffffffff);
g_pd3dDevice->SetRenderState(D3DRS_STENCILWRITEMASK, 0xffffffff);
g_pd3dDevice->SetRenderState(D3DRS_STENCILZFAIL, D3DSTENCILOP_KEEP);
g_pd3dDevice->SetRenderState(D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP);
g_pd3dDevice->SetRenderState(D3DRS_STENCILPASS, D3DSTENCILOP_REPLACE);
//禁止写入深度缓存和后台缓存
//通过将源目标因子和目标融合因子分别设置为D3DBLEND_ZERO和D3DBLEND_ONE来阻止对后台缓存的更新
g_pd3dDevice->SetRenderState(D3DRS_ZWRITEENABLE, false);
g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, true);
g_pd3dDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ZERO);
g_pd3dDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE);
//将右上三角形画入模版缓存,这样模版缓存中对应于右上三角形的区域被标记为1
g_pd3dDevice->SetStreamSource(0, g_pVB, 0, sizeof(CUSTOMVERTEX));
g_pd3dDevice->SetFVF( D3DFVF_CUSTOMVERTEX );
g_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLELIST, 3, 1);
// re-enable depth writes
g_pd3dDevice->SetRenderState( D3DRS_ZWRITEENABLE, true );
//将茶壶只画入右上三角形
g_pd3dDevice->SetRenderState(D3DRS_STENCILFUNC, D3DCMP_EQUAL);
g_pd3dDevice->SetRenderState(D3DRS_STENCILPASS, D3DSTENCILOP_KEEP);
g_pd3dDevice->Clear(0, 0, D3DCLEAR_ZBUFFER, 0, 1.0f, 0);
g_pd3dDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ONE);
g_pd3dDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ZERO);
//画茶壶
D3DXMATRIX W;
D3DXMatrixTranslation(&W, 0, 0, -15);
g_pd3dDevice->SetTransform(D3DTS_WORLD, &W);
Teapot->DrawSubset(0);
D3DXMATRIX I;
D3DXMatrixIdentity(&I);
g_pd3dDevice->SetTransform(D3DTS_WORLD, &I);
// Restore render states.
g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, false);
g_pd3dDevice->SetRenderState( D3DRS_STENCILENABLE, false);
}
VOID Render()
{
g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER | D3DCLEAR_STENCIL, D3DCOLOR_XRGB(255, 255, 255), 1.0f, 0);
if( SUCCEEDED( g_pd3dDevice->BeginScene() ) )
{
RenderRectangle();
RenderTeapot();
g_pd3dDevice->EndScene();
}
// Present the backbuffer contents to the display
g_pd3dDevice->Present( NULL, NULL, NULL, NULL );
}
LRESULT WINAPI MsgProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
switch( msg )
{
case WM_DESTROY:
Cleanup();
PostQuitMessage( 0 );
return 0;
}
return DefWindowProc( hWnd, msg, wParam, lParam );
}
INT WINAPI wWinMain( HINSTANCE hInst, HINSTANCE, LPWSTR, INT )
{
// Register the window class
WNDCLASSEX wc =
{
sizeof( WNDCLASSEX ), CS_CLASSDC, MsgProc, 0L, 0L,
GetModuleHandle( NULL ), NULL, NULL, NULL, NULL,
"D3D Tutorial", NULL
};
RegisterClassEx( &wc );
// Create the application's window
HWND hWnd = CreateWindow( "D3D Tutorial", "D3D Tutorial: Stencil",
WS_OVERLAPPEDWINDOW, 100, 100, 600, 600,
NULL, NULL, wc.hInstance, NULL );
if( SUCCEEDED( InitD3D( hWnd ) ) )
{
// Create the vertex buffer and index buffer
if( SUCCEEDED( InitVB() ) )
{
// Show the window
ShowWindow( hWnd, SW_SHOWDEFAULT );
UpdateWindow( hWnd );
// Enter the message loop
MSG msg;
ZeroMemory( &msg, sizeof( msg ) );
while( msg.message != WM_QUIT )
{
if( PeekMessage( &msg, NULL, 0U, 0U, PM_REMOVE ) )
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}
else
Render();
}
}
}
UnregisterClass( "D3D Tutorial", wc.hInstance );
return 0;
}
运行效果如下: