您的位置:首页 > 其它

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的结构。

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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐