代码已上传至GitHub,这部分对应的项目为NormalDisplacementSsao。运行项目时可以使用数字键1-3切换渲染的shader,另外按下z键可以看到SSAO生成的遮罩贴图。
这篇文章主要结合代码介绍渲染的流程(对应代码中的NDSsaoApp::DrawScene)。
Shadow Map
首先我们要渲染阴影深度图,看ShadowMap的定义
class ShadowMap
{
... ...
ID3D11ShaderResourceView* mDepthMapSRV;
ID3D11DepthStencilView* mDepthMapDSV;
D3D11_VIEWPORT mViewport;
};
因为生成的深度图,既要作为管线的输出,也要作为着色器的输入,所以创建的2D纹理需要同时有ID3D11ShaderResourceView和ID3D11DepthStencilView。另外ShadowMap中定义了自己的Viewport,可以渲染出自己需要的大小的纹理。
纹理创建的过程和一般的模板深度纹理相同,但是要注意BindFlags要同时包D3D11_BIND_DEPTH_STENCIL和D3D11_BIND_SHADER_RESOURCE。
绘制阴影深度图,第一步是要把ShadowMap中的DepthStencilView绑定到管线的输出阶段。
void ShadowMap::BindDsvAndSetNullRenderTarget(ID3D11DeviceContext* dc)
{
dc->RSSetViewports(1, &mViewport);
// Set null render target because we are only going to draw to depth buffer.
// Setting a null render target will disable color writes.
ID3D11RenderTargetView* renderTargets[1] = {0};
dc->OMSetRenderTargets(1, renderTargets, mDepthMapDSV);
dc->ClearDepthStencilView(mDepthMapDSV, D3D11_CLEAR_DEPTH, 1.0f, 0);
}
这里在设置RenderTarget时,使用了空的RenderTargetView,因为我们只需要绘制深度buffer不需要输出颜色。
设置好输出后,我们要向DepthStencilView中绘制深度信息。
前面说过,我们有三种绘制形式,普通绘制,NormalMap绘制和贴图置换绘制。前两种对生成的阴影形状没有影响,因此在这里可以归为一种,贴图置换由于会影响物体的表面,所以在绘制阴影时也要做贴图置换。
void NDSsaoApp::DrawSceneToShadowMap()
{
//Setup Const buffer... ...
if(mDrawOptions != RenderOptionsDisplacementMap)
{
md3dImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
for(size_t i =0;i<mSceneObjects.size();++i)
{
mSceneObjects[i]->Draw(this,BuildShadowMapEffect::Ptr(),BuildShadowMapEffect::Ptr()->BuildShadowMapTech,
&mLightView, &mLightProj);
}
}
else
{
md3dImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_3_CONTROL_POINT_PATCHLIST);
for(size_t i =0;i<mSceneObjects.size();++i)
{
mSceneObjects[i]->Draw(this,BuildShadowMapEffect::Ptr(),BuildShadowMapEffect::Ptr()->TessBuildShadowMapTech,
&mLightView, &mLightProj);
}
}
// FX sets tessellation stages, but it does not disable them. So do that here
// to turn off tessellation.
md3dImmediateContext->HSSetShader(0, 0, 0);
md3dImmediateContext->DSSetShader(0, 0, 0);
md3dImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
mSkullVertexState.Apply(md3dImmediateContext);
mSkull.Draw(this,BuildShadowMapEffect::Ptr(),BuildShadowMapEffect::Ptr()->BuildShadowMapTech, &mLightView, &mLightProj);
}
绘制前要设置许多的变量,这部分请查看源码。我们先看不做贴图置换的情况,此时使用的是BuildShadowMapTech,对应到着色器代码中
RasterizerState Depth
{
DepthBias = 100000;
DepthBiasClamp = 0.0f;
SlopeScaledDepthBias = 1.0f;
};
VertexOut VS(VertexIn vin)
{
VertexOut vout;
vout.PosH = mul(float4(vin.PosL, 1.0f), gWorldViewProj);
vout.Tex = mul(float4(vin.Tex, 0.0f, 1.0f), gTexTransform).xy;
return vout;
}
technique11 BuildShadowMapTech
{
pass P0
{
SetVertexShader( CompileShader( vs_5_0, VS() ) );
SetGeometryShader( NULL );
SetPixelShader( NULL );
SetRasterizerState(Depth);
}
}
我们不需要输出颜色只需要深度信息,因此我们只需要VertexShader。在Vertex Shader的输出中,SV_POSITION是必须的,因为光栅化阶段的裁剪还是会进行的,而且我们需要由它来计算深度信息,而TEXCOORD是不需要的,但是如果输出中只有一个SV_POSITION在编译shader时会报错。
贴图置换时的绘制过程同样也是不需要输出颜色的,因此可以不需要PS。
techni