这节教程可以作为我的那节 “”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 buffer
DSDESC.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缓存值
当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 buffer
DSDESC.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();
最后放出效果运行图:
下面是我的源代码链接: