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

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

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

Directx11教程二十八之PlannarReflection(基于stencilBuffer的实现)_第1张图片




一,介绍stencilBuffer

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

StencilTest和DepthTest的关系如图所示:
Directx11教程二十八之PlannarReflection(基于stencilBuffer的实现)_第2张图片



二,怎么用StencilBuffer实现镜像?

(1)先来看看镜像的本质是啥?
Directx11教程二十八之PlannarReflection(基于stencilBuffer的实现)_第3张图片

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

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


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

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

如图:

Directx11教程二十八之PlannarReflection(基于stencilBuffer的实现)_第4张图片



代码如下:


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缓存值

 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每个位置的初始值.
总上所述,我们看到这副场景:

Directx11教程二十八之PlannarReflection(基于stencilBuffer的实现)_第5张图片

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

Directx11教程二十八之PlannarReflection(基于stencilBuffer的实现)_第6张图片


用黑色线圈住的部分的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的平面的反射矩阵,并求出立方体的镜像的位置,然后进行绘制

此时又一个问题来了,看图:
Directx11教程二十八之PlannarReflection(基于stencilBuffer的实现)_第7张图片

看图中的立方体的边和对应的反射体的边的法线方向是相同的,这造成了镜像被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();


最后放出效果运行图:

Directx11教程二十八之PlannarReflection(基于stencilBuffer的实现)_第8张图片



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


你可能感兴趣的:(directx11入门)