假设墙上有一面镜子,镜子前面有个木箱。如果观察角度合适,整个木箱镜像都会在镜子里,计算起来还比较简单;而变换个角度,木箱的镜像可能只有一部分在镜子里,这时单纯依靠计算来实现就很麻烦。DirectX提供了模板技术以方便地完成这个任务。我印象中用到模板就是喷漆的时候。将设计好的图案在一块板上刻出来,然后把这块板扣在要喷涂的地方,不管三七二十一直接喷漆,最后把板拿下来,图案就喷好了。DirectX中的模板也一样,启用模板后,后续的绘制就被限制在模板镂空(即模板测试通过)的地方。不过DirectX中的模板更加灵活复杂。
使用模板时只有两个结果,通过和不通过。测试方法如下:
StencilRef & StencilReadMask⊴ Value & StencilReadMask
其中StencilRef是模板参考值,StencilReadMask是模板掩码,Value是当前像素点的模板值,&是按位与运算,而⊴代表比较函数。与混合一样,模板也是通过组合StencilReadMask和⊴的不同枚举值来实现各种效果。在DirectX中使用模板的细节内容见DirectX 10 游戏编程入门和MSDN,这里只关注镜像效果的实现。
镜像效果可以仿照喷漆来做,给墙壁盖上一块只能露出镜子的模板,画完反射模型之后再把模板拿掉。大致可以分为四步:
1、创建墙壁、地板、木箱
2、创建镜子模版
3、绘制木箱和地板的反射
4、混合镜子,木箱和地板的纹理
基于第十篇的纹理木箱来实现镜像效果。其中HLSL代码只修改顶点着色器即可,方便纹理坐标变换,即调整ConstantBuffer的结构。
cbuffer ModelViewProjectionConstantBuffer : register(b0)
{
matrix model;
matrix view;
matrix projection;
matrix texTransform;
};
并在main方法中进行纹理坐标变换。
// 纹理坐标变换
output.tex =mul(float4(input.tex, 0.0f,1.0f), texTransform).xy;
然后可以直接进入C++代码部分。首先依然是修改CubeRenderer.h中的ModelViewProjectionConstantBuffer定义,使其与顶点着色器中的一致。接着添加新的私有成员和方法。
Microsoft::WRL::ComPtr<ID3D11BlendState>m_alphaBlendState;
Microsoft::WRL::ComPtr<ID3D11DepthStencilState>m_markDepthStencilState;
Microsoft::WRL::ComPtr<ID3D11DepthStencilState>m_reflectDepthStencilState;
Microsoft::WRL::ComPtr<ID3D11RasterizerState>m_rasterizerState;
ModelViewProjectionConstantBufferm_constantBufferData;
ModelViewProjectionConstantBufferm_reflectConstantBufferData;
// 立方体模型
void CreateCubeModel();
void RenderCubeModel(ModelViewProjectionConstantBuffer*constantBufferData);
Microsoft::WRL::ComPtr<ID3D11Buffer>m_vertexCubeBuffer;
Microsoft::WRL::ComPtr<ID3D11Buffer>m_indexCubeBuffer;
Microsoft::WRL::ComPtr<ID3D11ShaderResourceView> m_woodSRV;
uint32 m_indexCubeCount;
// 墙体模型
void CreateWallModel();
void RenderWallModel();
Microsoft::WRL::ComPtr<ID3D11Buffer>m_vertexWallBuffer;
Microsoft::WRL::ComPtr<ID3D11Buffer>m_indexWallBuffer;
Microsoft::WRL::ComPtr<ID3D11ShaderResourceView> m_wallSRV;
uint32 m_indexWallCount;
// 地板模型
void CreateFloorModel();
void RenderFloorModel(ModelViewProjectionConstantBuffer*constantBufferData);
Microsoft::WRL::ComPtr<ID3D11Buffer>m_vertexFloorBuffer;
Microsoft::WRL::ComPtr<ID3D11Buffer>m_indexFloorBuffer;
Microsoft::WRL::ComPtr<ID3D11ShaderResourceView> m_floorSRV;
uint32 m_indexFloorCount;
// 镜子模型
voidCreateMirrorModel();
voidRenderMirrorModel();
Microsoft::WRL::ComPtr<ID3D11Buffer>m_vertexMirrorBuffer;
Microsoft::WRL::ComPtr<ID3D11Buffer>m_indexMirrorBuffer;
Microsoft::WRL::ComPtr<ID3D11ShaderResourceView> m_mirrorSRV;
uint32 m_indexMirrorCount;
前面几个成员的功能在后面用到时详细介绍。而后面四个模型的成员和方法只是名字不同,功能一致,写的很啰嗦,有待修改。定义好上面的成员和方法就转到实现部分。首先在CreateDeviceResources方法中载入四种纹理并初始化各个状态
DX::ThrowIfFailed(
CreateWICTextureFromFile(
m_d3dDevice.Get(),
m_d3dContext.Get(),
L"wood.jpg",
NULL,
m_woodSRV.GetAddressOf()
)
);
……
D3D11_SAMPLER_DESC samplerDesc;
samplerDesc.Filter= D3D11_FILTER_MIN_MAG_MIP_LINEAR;
samplerDesc.AddressU= D3D11_TEXTURE_ADDRESS_WRAP;
samplerDesc.AddressV= D3D11_TEXTURE_ADDRESS_WRAP;
samplerDesc.AddressW= D3D11_TEXTURE_ADDRESS_WRAP;
samplerDesc.MipLODBias= 0;
samplerDesc.MaxAnisotropy= 1;
samplerDesc.ComparisonFunc= D3D11_COMPARISON_NEVER;
samplerDesc.BorderColor[0 ] = 1.0f;
samplerDesc.BorderColor[1 ] = 1.0f;
samplerDesc.BorderColor[2 ] = 1.0f;
samplerDesc.BorderColor[3 ] = 1.0f;
samplerDesc.MinLOD= -3.402823466e+38F; // -FLT_MAX
samplerDesc.MaxLOD= 3.402823466e+38F; // FLT_MAX
DX::ThrowIfFailed(
m_d3dDevice->CreateSamplerState(
&samplerDesc,
m_samplerState.GetAddressOf()
)
);
// 初始化Alpha混合状态
D3D11_BLEND_DESC alphaBlendDesc ={0};
alphaBlendDesc.RenderTarget[0].BlendEnable= TRUE;
alphaBlendDesc.RenderTarget[0].SrcBlend= D3D11_BLEND_SRC_ALPHA; // Color_Fsrc
alphaBlendDesc.RenderTarget[0].DestBlend= D3D11_BLEND_INV_SRC_ALPHA; // Color_Fdst
alphaBlendDesc.RenderTarget[0].BlendOp= D3D11_BLEND_OP_ADD; // Color_Operation
alphaBlendDesc.RenderTarget[0].SrcBlendAlpha= D3D11_BLEND_ONE; // Alpha_Fsrc
alphaBlendDesc.RenderTarget[0].DestBlendAlpha= D3D11_BLEND_ZERO; // Alpha_Fdst
alphaBlendDesc.RenderTarget[0].BlendOpAlpha= D3D11_BLEND_OP_ADD; // Alpha_Operation
alphaBlendDesc.RenderTarget[0].RenderTargetWriteMask= D3D11_COLOR_WRITE_ENABLE_ALL;
DX::ThrowIfFailed(
m_d3dDevice->CreateBlendState(
&alphaBlendDesc,
&m_alphaBlendState
)
);
// 初始化镜子模版
D3D11_DEPTH_STENCIL_DESC mirrorDesc;
mirrorDesc.DepthEnable = true;
mirrorDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL;
mirrorDesc.DepthFunc = D3D11_COMPARISON_LESS;
mirrorDesc.StencilEnable = true;
mirrorDesc.StencilReadMask = 0xff;
mirrorDesc.StencilWriteMask= 0xff;
mirrorDesc.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
mirrorDesc.FrontFace.StencilDepthFailOp= D3D11_STENCIL_OP_KEEP;
mirrorDesc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_REPLACE;
mirrorDesc.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS;
// 背面参数设置无影响
mirrorDesc.BackFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
mirrorDesc.BackFace.StencilDepthFailOp = D3D11_STENCIL_OP_KEEP;
mirrorDesc.BackFace.StencilPassOp = D3D11_STENCIL_OP_REPLACE;
mirrorDesc.BackFace.StencilFunc = D3D11_COMPARISON_ALWAYS;
DX::ThrowIfFailed(
m_d3dDevice->CreateDepthStencilState(
&mirrorDesc,
&m_markDepthStencilState
)
);
// 初始化绘制反射时的模板
D3D11_DEPTH_STENCIL_DESC drawReflectionDesc;
drawReflectionDesc.DepthEnable = true;
drawReflectionDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ZERO;
drawReflectionDesc.DepthFunc = D3D11_COMPARISON_ALWAYS;
drawReflectionDesc.StencilEnable = true;
drawReflectionDesc.StencilReadMask = 0xff;
drawReflectionDesc.StencilWriteMask= 0xff;
drawReflectionDesc.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
drawReflectionDesc.FrontFace.StencilDepthFailOp= D3D11_STENCIL_OP_KEEP;
drawReflectionDesc.FrontFace.StencilPassOp= D3D11_STENCIL_OP_KEEP;
drawReflectionDesc.FrontFace.StencilFunc = D3D11_COMPARISON_EQUAL;
// 背面参数设置无影响
drawReflectionDesc.BackFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
drawReflectionDesc.BackFace.StencilDepthFailOp= D3D11_STENCIL_OP_KEEP;
drawReflectionDesc.BackFace.StencilPassOp= D3D11_STENCIL_OP_KEEP;
drawReflectionDesc.BackFace.StencilFunc = D3D11_COMPARISON_EQUAL;
DX::ThrowIfFailed(
m_d3dDevice->CreateDepthStencilState(
&drawReflectionDesc,
&m_reflectDepthStencilState
)
);
samplerDesc和alphaBlendDesc在以前的文章中已经介绍过,主要看下mirrorDesc和drawReflectionDesc。结合模板公式和MSDN的介绍,对这两个结构能有基本的了解。
http://msdn.microsoft.com/zh-cn/library/windows/desktop/ff476101.aspx
http://msdn.microsoft.com/zh-cn/library/windows/desktop/ff476219.aspx
D3D11_COMPARISON_LESS
If the source data is lessthan the destination data, the comparison passes.
D3D11_COMPARISON_EQUAL
If the source data is equalto the destination data, the comparison passes.
D3D11_COMPARISON_ALWAYS
Always pass the comparison.
D3D11_STENCIL_OP_KEEP
Keep the existing stencil data.
D3D11_STENCIL_OP_ZERO
Set the stencil data to 0.
mirrorDesc就是一直通过模板测试,因为绘制镜子模板之前还没有任何模板,测试也无意义。但是深度测试是必须的,镜子在墙前面,通过深度测试时就会将已有模板值替换为当前的模板值,实现镜子形状的模板绘制。
drawReflectionDesc则是一直通过深度测试,只进行模板测试。因为要绘制的木箱和地板实际是在镜子后面,所以深度测试无意义。模板测试的通过条件是两个模板值相等。因为镜子覆盖区域的模板值已被修改,其他区域都是默认。所以这次测试时,只有镜子区域的模板值能达成相等的条件,对应位置像素被更新,从而实现限制绘制区域的目的。
在这后面还要调用四个方法来初始化模型,即初始化模型的顶点缓冲区和索引缓冲区。代码很简单,大同小异,不进行介绍,细节可以参照源代码。接着看下在Render方法中的详细绘制流程。
//------------------------------------------------
// 第一步 绘制正常模型,墙壁,地板和原始木箱
//------------------------------------------------
RenderWallModel();
RenderFloorModel(&m_constantBufferData);
RenderCubeModel(&m_constantBufferData);
//------------------------------------------------
// 第二步 绘制镜子模版
//------------------------------------------------
// 设置模板状态
m_d3dContext->OMSetDepthStencilState(m_markDepthStencilState.Get(),1);
RenderMirrorModel();
// 清除设置
m_d3dContext->OMSetDepthStencilState(0,0);
//------------------------------------------------
// 第三步 绘制反射木箱和地板
//------------------------------------------------
// 设置光栅化状态和模板状态
m_d3dContext->RSSetState(m_rasterizerState.Get());
m_d3dContext->OMSetDepthStencilState(m_reflectDepthStencilState.Get(),1);
RenderFloorModel(&m_reflectConstantBufferData);
RenderCubeModel(&m_reflectConstantBufferData);
// 清除设置
m_d3dContext->RSSetState(0);
m_d3dContext->OMSetDepthStencilState(0,0);
//------------------------------------------------
// 第四步 绘制镜子
//------------------------------------------------
m_d3dContext->OMSetBlendState(m_alphaBlendState.Get(),blendFactors, 0xffffffff);
RenderMirrorModel();
m_d3dContext->OMSetBlendState(0,blendFactors, 0xffffffff);
以上代码与开始时介绍的流程一致。注意在绘制镜像时还更改了光栅化状态。因为绘制镜像时,模型索引的环绕顺序和平面法线都不翻转,造成镜像的法线方向错误,所以需要更改光栅化状态。代码中的RenderXXXModel方法也很简单,就是把Render方法中负责渲染的代码抽出来,仅以绘制正方体的方法为例:
void CubeRenderer::RenderCubeModel(ModelViewProjectionConstantBuffer* constantBufferData)
{
UINT stride = sizeof(VertexPositionColor);
UINT offset = 0;
XMMATRIX cubeScale =XMMatrixScaling(1.0f, 1.0f, 0.0f);
XMStoreFloat4x4(&constantBufferData->transform,XMMatrixTranspose(cubeScale));
m_d3dContext->UpdateSubresource(
m_constantBuffer.Get(),
0,
NULL,
constantBufferData,
0,
0
);
m_d3dContext->IASetVertexBuffers(
0,
1,
m_vertexCubeBuffer.GetAddressOf(),
&stride,
&offset
);
m_d3dContext->IASetIndexBuffer(
m_indexCubeBuffer.Get(),
DXGI_FORMAT_R16_UINT,
0
);
// 设置材质
m_d3dContext->PSSetShaderResources(
0,
1,
m_woodSRV.GetAddressOf()
);
// 设置纹理采样
m_d3dContext->PSSetSamplers(
0,
1,
m_samplerState.GetAddressOf()
);
m_d3dContext->DrawIndexed(
m_indexCubeCount,
0,
0
);
}
还要注意m_reflectConstantBufferData。它代表镜子里的空间,是现实空间的镜像,在Update方法中更新。本例中镜子是在XY平面上,所以用XMMatrixReflect方法计算XY平面的对称变换矩阵,并增加平移变换,使模型能够转移到镜子内的对应位置。
// 计算反射空间矩阵
XMVECTOR mirrorPlane =XMVectorSet(0.0f, 0.0f, 1.0f, 0.0f); // xy 平面
XMMATRIX T =XMMatrixTranslation(0.0f, 0.0f, -4.0f);
XMMATRIX R =XMMatrixReflect(mirrorPlane) * T;
XMStoreFloat4x4(&m_reflectConstantBufferData.projection,XMLoadFloat4x4(&m_constantBufferData.projection) );
XMStoreFloat4x4(&m_reflectConstantBufferData.view,XMLoadFloat4x4(&m_constantBufferData.view));
XMStoreFloat4x4(&m_reflectConstantBufferData.model,XMMatrixTranspose(XMMatrixRotationY(0) * R));
程序运行效果如下图:
本篇文章虽然实现了镜像效果,但是代码中有很多重复的地方,像渲染模型的功能就可以只用一个方法,通过输入参数进行不同模型的渲染,时间紧没有整理,会在以后更新。
本篇文章的源代码:Direct3DApp_CubeMirror
原文地址:http://blog.csdn.net/raymondcode/article/details/8479342
-------------------------------------------------------------------------------------------------------------------