Windows 8 Directx 开发学习笔记(十三)利用模板实现木箱镜像
2013-01-08 08:28
911 查看
假设墙上有一面镜子,镜子前面有个木箱。如果观察角度合适,整个木箱镜像都会在镜子里,计算起来还比较简单;而变换个角度,木箱的镜像可能只有一部分在镜子里,这时单纯依靠计算来实现就很麻烦。DirectX提供了模板技术以方便地完成这个任务。我印象中用到模板就是喷漆的时候。将设计好的图案在一块板上刻出来,然后把这块板扣在要喷涂的地方,不管三七二十一直接喷漆,最后把板拿下来,图案就喷好了。DirectX中的模板也一样,启用模板后,后续的绘制就被限制在模板镂空(即模板测试通过)的地方。不过DirectX中的模板更加灵活复杂。
使用模板时只有两个结果,通过和不通过。测试方法如下:
StencilRef & StencilReadMask⊴ Value & StencilReadMask
其中StencilRef是模板参考值,StencilReadMask是模板掩码,Value是当前像素点的模板值,&是按位与运算,而⊴代表比较函数。与混合一样,模板也是通过组合StencilReadMask和⊴的不同枚举值来实现各种效果。在DirectX中使用模板的细节内容见DirectX 10 游戏编程入门和MSDN,这里只关注镜像效果的实现。
镜像效果可以仿照喷漆来做,给墙壁盖上一块只能露出镜子的模板,画完反射模型之后再把模板拿掉。大致可以分为四步:
1、创建墙壁、地板、木箱
2、创建镜子模版
3、绘制木箱和地板的反射
4、混合镜子,木箱和地板的纹理
基于第十篇的纹理木箱来实现镜像效果。其中HLSL代码只修改顶点着色器即可,方便纹理坐标变换,即调整ConstantBuffer的结构。
并在main方法中进行纹理坐标变换。
然后可以直接进入C++代码部分。首先依然是修改CubeRenderer.h中的ModelViewProjectionConstantBuffer定义,使其与顶点着色器中的一致。接着添加新的私有成员和方法。
前面几个成员的功能在后面用到时详细介绍。而后面四个模型的成员和方法只是名字不同,功能一致,写的很啰嗦,有待修改。定义好上面的成员和方法就转到实现部分。首先在CreateDeviceResources方法中载入四种纹理并初始化各个状态
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方法中的详细绘制流程。
以上代码与开始时介绍的流程一致。注意在绘制镜像时还更改了光栅化状态。因为绘制镜像时,模型索引的环绕顺序和平面法线都不翻转,造成镜像的法线方向错误,所以需要更改光栅化状态。代码中的RenderXXXModel方法也很简单,就是把Render方法中负责渲染的代码抽出来,仅以绘制正方体的方法为例:
还要注意m_reflectConstantBufferData。它代表镜子里的空间,是现实空间的镜像,在Update方法中更新。本例中镜子是在XY平面上,所以用XMMatrixReflect方法计算XY平面的对称变换矩阵,并增加平移变换,使模型能够转移到镜子内的对应位置。
程序运行效果如下图:
本篇文章虽然实现了镜像效果,但是代码中有很多重复的地方,像渲染模型的功能就可以只用一个方法,通过输入参数进行不同模型的渲染,时间紧没有整理,会在以后更新。
本篇文章的源代码:Direct3DApp_CubeMirror
原文地址:/article/1354520.html
-------------------------------------------------------------------------------------------------------------------
修改后的源代码:http://download.csdn.net/detail/raymondcode/4975386
使用模板时只有两个结果,通过和不通过。测试方法如下:
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
原文地址:/article/1354520.html
-------------------------------------------------------------------------------------------------------------------
修改后的源代码:http://download.csdn.net/detail/raymondcode/4975386
相关文章推荐
- Windows 8 Directx 开发学习笔记(十二)利用混合实现浮在水面的木箱
- Windows 8 Directx 开发学习笔记(十)纹理贴图实现旋转的木箱
- Windows 8 Directx 开发学习笔记(七)水波纹的实现
- Windows 8 Directx 开发学习笔记(十四)使用几何着色器实现三角形细分
- Windows 8 DirectX 开发学习笔记(十五)使用Billboard实现树木贴图
- Windows 8 Directx 开发学习笔记(九)材质定义及混合光照效果实现
- Windows 8 Directx 开发学习笔记(五)山峰河谷模型的简单实现
- Windows 8 Directx 开发学习笔记(十一)地形纹理贴图
- Windows 8 Directx 开发学习笔记(三)摄像机设置及控制正方体旋转
- (ios开发学习笔记四)利用toolbar实现多窗体跳转
- Windows 8 Directx 开发学习笔记(四)示例程序小结
- Windows 8 Directx 开发学习笔记(二)建立模型及初始化设备
- ((ios开发学习笔记 十三))实现九宫格效果(附带源码)
- Windows 8 Directx 开发学习笔记(八)要有光
- Windows 8 Directx开发学习笔记(一)应用基本框架
- Windows 8 DirectX 开发学习笔记(十六)使用Terragen生成自然环境贴图
- Windows 8 Directx 开发学习笔记(六)添加水模型
- 前端开发学习之——利用模板实现涉及url问题时的bug分析及解决(chrome源码)
- android 开发零起步学习笔记(十三):Android 实现多页界面左右滑动切换效果
- boost.asio 学习笔记05 asio的windows实现