D3D11教程二十八之PlannarReflection(基于stencilBuffer的实现)

来源:互联网 发布:轻松网络销售招聘 编辑:程序博客网 时间:2024/05/17 20:21

这节教程可以作为我的那节 “”D3D11教程十九之平面反射(planar reflect)“”的补充,那节教程的反射镜像是利用RenderToTexture技术生成的反射纹理形成的,而这节教程的镜像是基于stencilBuffer和DepthBuffer技术形成的.

首先给出这节教程的结构:





一,介绍stencilBuffer

模板缓存决定了是否可以进行深度缓存,默认情况下StencilTest是关闭的,而DepthTest是默认开启,当StecilTest和DepthTest同时开启时,两种测试都通过时颜色才能写到backBuffer上,这里给出一篇好文章分享:  http://www.cnblogs.com/mikewolf2002/archive/2012/05/15/2500867.html

StencilTest和DepthTest的关系如图所示:




二,怎么用StencilBuffer实现镜像?

(1)先来看看镜像的本质是啥?


假设物体为A,则A关于MirrorPlane对称的物体为B,实际上人眼看到A的镜像其实就是B。

可以这么认为,人眼看到的物体的镜像也就是将原本的物体A关于mirror对称的另一个物体B.在D3D11的绘制中按正常绘制A和其的镜像B的话,绝对会因为镜面的遮挡(ZTest测试不通过,也据说Z缓存剔除)而导致镜像的像素无法打印到backBuffer,那么人眼(相机)就看不到镜像,此时可以通过stencilBuffer来解决


下面说明渲染出镜像的步骤:

第一,我们正常的将地面,墙,立方体渲染到场景中.

如图:





代码如下:


bool  GraphicsClass::RenderNormalScene(){//三个变换矩阵XMMATRIX WorldMatrix, ViewMatrix, ProjMatrix;bool result;/********************第一,进行立方体的渲染****************************///清除缓存开始绘制场景mD3D->BeginScene(0.3f, 0.2f, 0.f, 1.0f);//(更新)获取ViewMatrix(根据CameraClass的mPostion和mRotation来生成的)mCamera->Render();//获取三个变换矩阵(WorldMatrix和ProjMatrix来自mD3D类,ViewMatrix来自CameraClass)WorldMatrix = mD3D->GetWorldMatrix();ProjMatrix = mD3D->GetProjMatrix();ViewMatrix = mCamera->GetViewMatrix();//将立方体的顶点数据和索引数据放入3D渲染流水线mCubeModel->Render(mD3D->GetDeviceContext());//绘制立方体result = mColorShader->Render(mD3D->GetDeviceContext(), mCubeModel->GetIndexCount(), WorldMatrix, ViewMatrix, ProjMatrix, mCubeModel->GetTexture(), mLight->GetDiffuseColor(), mLight->GetLightDirection());if (!result){MessageBox(NULL, L"ColorShader Render failure", NULL, MB_OK);return false;}/********************第二,进行地面的渲染****************************///将世界变换矩阵绕X轴顺时针旋转90度  XMMATRIXROATATION 得从 XYZ正轴看才是顺时针    //重新获取世界变换矩阵WorldMatrix = mD3D->GetWorldMatrix();WorldMatrix = WorldMatrix*XMMatrixTranslation(0, -2.0f, 0);//将地面的顶点数据和索引数据放入3D渲染流水线mFloorModel->Render(mD3D->GetDeviceContext());//绘制地面result = mColorShader->Render(mD3D->GetDeviceContext(), mFloorModel->GetIndexCount(), WorldMatrix, ViewMatrix, ProjMatrix, mFloorModel->GetTexture(), mLight->GetDiffuseColor(), mLight->GetLightDirection());if (!result){MessageBox(NULL, L"ColorShader Render failure", NULL, MB_OK);return false;}/********************第三,进行墙面的渲染****************************///重新获取世界变换矩阵WorldMatrix = mD3D->GetWorldMatrix();WorldMatrix = WorldMatrix *XMMatrixRotationX(-3.14f/2.0f)*XMMatrixTranslation(0, 0.0f, 5.0f); //将墙面的顶点数据和索引数据放入3D渲染流水线mWallModel->Render(mD3D->GetDeviceContext());//绘制墙面result = mColorShader->Render(mD3D->GetDeviceContext(), mWallModel->GetIndexCount(), WorldMatrix, ViewMatrix, ProjMatrix, mWallModel->GetTexture(), mLight->GetDiffuseColor(), mLight->GetLightDirection());if (!result){MessageBox(NULL, L"ColorShader Render failure", NULL, MB_OK);return false;}return true;}




第二,对镜面进行渲染上去(镜面比墙面更靠近照相机)

此时创建相应的ID3D11DepthStencilSate,

        ID3D11DepthStencilState* md3dMarkMirrorDSS; //给镜面部分写入遮掩码的深度(模板)缓存状态ZeroMemory(&DSDESC, sizeof(DSDESC));DSDESC.DepthEnable = true;  //深度测试开启DSDESC.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ZERO;  //深度写无效DSDESC.DepthFunc = D3D11_COMPARISON_LESS; //在墙面之前 DSDESC.StencilEnable = true;   //模板测试开启DSDESC.StencilReadMask = 0xff;DSDESC.StencilWriteMask = 0xff;//前面设定DSDESC.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;  //stencil失败 怎么更新stencil bufferDSDESC.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_KEEP;// stencil通过,但depth失败DSDESC.FrontFace.StencilPassOp = D3D11_STENCIL_OP_REPLACE;  //stencil通过 depth通过DSDESC.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS;   //总是能通过StencilTest//背面设定,在光栅化状态剔除背面时这个设定没用,但是依然要设定,不然无法创建深度(模板)状态DSDESC.BackFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;   DSDESC.BackFace.StencilDepthFailOp = D3D11_STENCIL_OP_KEEP;DSDESC.BackFace.StencilPassOp = D3D11_STENCIL_OP_REPLACE;DSDESC.BackFace.StencilFunc = D3D11_COMPARISON_ALWAYS;HR(md3dDevice->CreateDepthStencilState(&DSDESC, &md3dMarkMirrorDSS));
可以看出:

 1,DSDESC.DepthEnable = true;  DSDESC.StencilEnable = true;
    StencilTest和DepthTest都开启

 2,DSDESC.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ZERO; 
无法写入值到Z缓存中,也就是绘制镜面时的Z缓存值假设为正常绘制墙,底面时,立方体的Z缓存值

 3,DSDESC.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;  
  当stencilTest失败时,stencilBuffer的值不变
   DSDESC.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_KEEP;
   当stencilTest成功,并且depthTest失败时,stencilBuffer的值不变

4,DSDESC.FrontFace.StencilPassOp = D3D11_STENCIL_OP_REPLACE;
   当stencilTest成功,并且depthTest成功时,相应位置的stencilBuffer的值被 StencilRef所代替

由于镜面在墙面之前,所以整个镜面对应的StencilBuffer的值都变为StencilRef
md3dImmediateContext->OMSetDepthStencilState(md3dMarkMirrorDSS,1);
看上面函数的第二个参数1就是StencilRef  

而千万要注意的是StencilBuffer的值和ZBuffer的值在没绘制场景前我们就初始化了
//清除深度缓存和模板缓存,设置每帧初始值md3dImmediateContext->ClearDepthStencilView(md3dDepthStencilView, D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);
像上面这条函数,最后一个参数为StencilBuffer中每个位置的初始值,倒数第二个参数为DepthBuffer每个位置的初始值.
总上所述,我们看到这副场景:



看上面图StencilBuffer的白色部分的值都为0,而黑色部分都为1,不清楚的话再来看一幅图:




用黑色线圈住的部分的stencilBuffer的值未1,而圈外对应的StencilBuffer的值为0




第三,绘制镜像。

此时创建的 ID3D11DepthStencilState* md3dDrawReflectionDSS派上用场了,
ZeroMemory(&DSDESC, sizeof(DSDESC));DSDESC.DepthEnable = true;  //深度测试开启DSDESC.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ZERO;  //深度写DSDESC.DepthFunc = D3D11_COMPARISON_ALWAYS; //总能通过Z缓存测试DSDESC.StencilEnable = true;   //模板测试开启DSDESC.StencilReadMask = 0xff;DSDESC.StencilWriteMask = 0xff;//前面设定DSDESC.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;  //stencil失败 怎么更新stencil bufferDSDESC.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_KEEP;// stencil通过,但depth失败DSDESC.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;  //stencil通过 depth通过DSDESC.FrontFace.StencilFunc = D3D11_COMPARISON_EQUAL;   //总是能通过StencilTest  //背面设定,在光栅化状态剔除背面时这个设定没用,但是依然要设定,不然无法创建深度(模板)状态DSDESC.BackFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;DSDESC.BackFace.StencilDepthFailOp = D3D11_STENCIL_OP_KEEP;DSDESC.BackFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;DSDESC.BackFace.StencilFunc = D3D11_COMPARISON_EQUAL;HR(md3dDevice->CreateDepthStencilState(&DSDESC, &md3dDrawReflectionDSS));



1  DSDESC.DepthEnable = true;   
  DSDESC.StencilEnable = true; 
 StencilTest和DepthTest都开启

2 ,DSDESC.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ZERO; 
 深度写无效的,也就是说此时的ZBuffer的值还是停留在第一步立方体,底面,墙面的深度缓存值而无任何改变

3.DSDESC.DepthFunc = D3D11_COMPARISON_ALWAYS; 
  DepthTest总是能通过的,这以为着只要stencilTest能通过,就能往backBuffer写入颜色值,此时看看StencilTest通过的条件函数
DSDESC.FrontFace.StencilFunc = D3D11_COMPARISON_EQUAL; 

也就是相应位置的StencilBuffer的值等于StemcilRef,就能通过StencilBuffer,StencilBuffer通过Test的位置对应的BackBuffer就能写入立方体镜像的值
看这时StencilRef依然为1,那么StencilBuffer中值为0的位置的颜色值无法绘制进BackBuffer,而1的位置的颜色值顺利绘制进Backbuffer
md3dImmediateContext->OMSetDepthStencilState(md3dDrawReflectionDSS, 1);


接下来求出反射平面(0.0f,0.0f,1.0f,-4.9f)也就是Z=4.9的平面的反射矩阵,并求出立方体的镜像的位置,然后进行绘制

此时又一个问题来了,看图:


看图中的立方体的边和对应的反射体的边的法线方向是相同的,这造成了镜像被BackCulling(背面剔除),这时候我们得改变光栅化状态的绕线方向为逆时针(CounterClockWise),才能绘制出镜像

ID3D11RasterizerState* md3dCounterClockRasterizerState; //逆时针的D3D的光栅化状态rasterDesc.AntialiasedLineEnable = false;rasterDesc.CullMode = D3D11_CULL_BACK; //背面剔除rasterDesc.DepthBias = 0;rasterDesc.DepthBiasClamp = 0.0f;rasterDesc.DepthClipEnable = true; //深度裁剪开启rasterDesc.FillMode = D3D11_FILL_SOLID; //实体渲染rasterDesc.FrontCounterClockwise = true; //逆时针绕线方向rasterDesc.MultisampleEnable = false;rasterDesc.ScissorEnable = false;rasterDesc.SlopeScaledDepthBias = 0.0f;


看我们绘制立方体镜像总的代码:
/*******************第二,绘制立方体反射的镜像的镜像,遮掩码为1的地方才能看到镜像*******************************///重新获取世界变换矩阵WorldMatrix = mD3D->GetWorldMatrix();//设置逆时针的光栅化状态,改变绕线方向mD3D->SetCounterClockRasterizerState();//设置绘制反射镜像的深度状态mD3D->SetDrawReflectionMarkDepthStencilState();//求出反射矩阵XMVECTOR ReflectPlane = XMVectorSet(0.0f, 0.0f, 1.0f, -4.9f);XMMATRIX ReflecMatrix = XMMatrixReflect(ReflectPlane);WorldMatrix = WorldMatrix*ReflecMatrix;//把立方体的数据放进3D渲染流水线mCubeModel->Render(mD3D->GetDeviceContext());//第五,绘制立方体result = mColorShader->Render(mD3D->GetDeviceContext(), mCubeModel->GetIndexCount(), WorldMatrix, ViewMatrix, ProjMatrix, mCubeModel->GetTexture(), mLight->GetDiffuseColor(), mLight->GetLightDirection());if (!result){MessageBox(NULL, L"ColorShader Render failure", NULL, MB_OK);return false;}


第四,再次绘制镜面,让镜面和镜像实现混合的效果。

这一步不难,因此我就不给出代码了,不过要注意的是在第三步我改变了绕线方向,在绘制镜面混合效果之前得恢复光栅化的状态为顺时针绕线方向(ClockWise)和恢复正常的DepthStencilState,这点谨记.

放出第四步的代码:

//重新获取世界变换矩阵WorldMatrix = mD3D->GetWorldMatrix();WorldMatrix = WorldMatrix *XMMatrixRotationX(-XM_PI / 2.0f)*XMMatrixScaling(0.6f, 1.0f, 1.0f)*XMMatrixTranslation(0, 0.0f, 4.9f);//最后一步,光栅化状态,深度缓存状态恢复为默认状态mD3D->SetDefaultDepthStencilState();mD3D->SetDefaultRasterizerState();//设置混合状态,使得镜像能和镜面混合mD3D->TurnOnBaseAlphaBlend();//将镜面的顶点数据和索引数据放入3D渲染流水线mMirrorModel->Render(mD3D->GetDeviceContext());//绘制镜面result = mColorShader->Render(mD3D->GetDeviceContext(), mMirrorModel->GetIndexCount(), WorldMatrix, ViewMatrix, ProjMatrix, mMirrorModel->GetTexture(), mLight->GetDiffuseColor(), mLight->GetLightDirection());if (!result){MessageBox(NULL, L"ColorShader Render failure", NULL, MB_OK);return false;}//关闭混合状态mD3D->TurnOffAlphaBlend();


最后放出效果运行图:





下面是我的源代码链接:
http://download.csdn.net/detail/qq_29523119/9666202


0 0
原创粉丝点击