将阴影映射呈现到深度缓冲区Render the shadow map to the depth buffer

从光线的角度呈现,以创建一个表示阴影卷的二维深度映射。Render from the point of view of the light to create a two-dimensional depth map representing the shadow volume. 深度映射会掩盖将在阴影中呈现的空间。The depth map masks the space that will be rendered in shadow. 操作实例:使用 Direct3D 11 中的深度缓冲区实现阴影卷的第 2 部分。Part 2 of Walkthrough: Implement shadow volumes using depth buffers in Direct3D 11.

清除深度缓冲区Clear the depth buffer

呈现到深度缓冲区之前,始终清除深度缓冲区。Always clear the depth buffer before rendering to it.

context->ClearRenderTargetView(m_deviceResources->GetBackBufferRenderTargetView(), DirectX::Colors::CornflowerBlue);
context->ClearDepthStencilView(m_shadowDepthView.Get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);

将阴影映射呈现到深度缓冲区Render the shadow map to the depth buffer

对于阴影呈现通道,指定深度缓冲区,但不指定呈现目标。For the shadow rendering pass, specify a depth buffer but do not specify a render target.

指定光线视区、顶点着色器,并设置光线空间常量缓冲区。Specify the light viewport, a vertex shader, and set the light space constant buffers. 为该通道使用正面剔除以优化放置在阴影缓冲区中的深度值。Use front face culling for this pass to optimize the depth values placed in the shadow buffer.

请注意,在大多数设备上,你可以为像素着色器指定 nullptr(或者完全跳过指定像素着色器)。Note that on most devices, you can specify nullptr for the pixel shader (or skip specifying a pixel shader entirely). 但一些驱动程序可能会在像素着色器集为 null 的 Direct3D 设备上调用 draw 时引发异常。But some drivers may throw an exception when you call draw on the Direct3D device with a null pixel shader set. 为了避免发生此异常,你可以为阴影呈现通道设置最低像素着色器。To avoid this exception, you can set a minimal pixel shader for the shadow rendering pass. 扔掉该着色器的输出;它可以在每个像素上调用 discardThe output of this shader is thrown away; it can call discard on every pixel.

呈现可以投影的对象,但不要打扰没有投影的呈现几何图形(就像房间中的地板,或者为了优化而从阴影通道中删除的对象)。Render the objects that can cast shadows, but don't bother rendering geometry that can't cast a shadow (like a floor in a room, or objects removed from the shadow pass for optimization reasons).

void ShadowSceneRenderer::RenderShadowMap()
{
    auto context = m_deviceResources->GetD3DDeviceContext();

    // Render all the objects in the scene that can cast shadows onto themselves or onto other objects.

    // Only bind the ID3D11DepthStencilView for output.
    context->OMSetRenderTargets(
        0,
        nullptr,
        m_shadowDepthView.Get()
        );

    // Note that starting with the second frame, the previous call will display
    // warnings in VS debug output about forcing an unbind of the pixel shader
    // resource. This warning can be safely ignored when using shadow buffers
    // as demonstrated in this sample.

    // Set rendering state.
    context->RSSetState(m_shadowRenderState.Get());
    context->RSSetViewports(1, &m_shadowViewport);

    // Each vertex is one instance of the VertexPositionTexNormColor struct.
    UINT stride = sizeof(VertexPositionTexNormColor);
    UINT offset = 0;
    context->IASetVertexBuffers(
        0,
        1,
        m_vertexBuffer.GetAddressOf(),
        &stride,
        &offset
        );

    context->IASetIndexBuffer(
        m_indexBuffer.Get(),
        DXGI_FORMAT_R16_UINT, // Each index is one 16-bit unsigned integer (short).
        0
        );

    context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
    context->IASetInputLayout(m_inputLayout.Get());

    // Attach our vertex shader.
    context->VSSetShader(
        m_simpleVertexShader.Get(),
        nullptr,
        0
        );

    // Send the constant buffers to the Graphics device.
    context->VSSetConstantBuffers(
        0,
        1,
        m_lightViewProjectionBuffer.GetAddressOf()
        );

    context->VSSetConstantBuffers(
        1,
        1,
        m_rotatedModelBuffer.GetAddressOf()
        );

    // In some configurations, it's possible to avoid setting a pixel shader
    // (or set PS to nullptr). Not all drivers are tolerant of this, so to be
    // safe set a minimal shader here.
    //
    // Direct3D will discard output from this shader because the render target
    // view is unbound.
    context->PSSetShader(
        m_textureShader.Get(),
        nullptr,
        0
        );

    // Draw the objects.
    context->DrawIndexed(
        m_indexCountCube,
        0,
        0
        );
}

优化视锥: 确保你的实现计算一个严密的视锥,以便在深度缓冲区之外获得最大精度。Optimize the view frustum: Make sure your implementation computes a tight view frustum so that you get the most precision out of your depth buffer. 有关阴影技术的更多提示,请参阅改进阴影深度映射的常用技术See Common Techniques to Improve Shadow Depth Maps for more tips on shadow technique.

阴影通道的顶点着色器Vertex shader for shadow pass

使用简化版的顶点着色器在光线空间中仅呈现顶点位置。Use a simplified version of your vertex shader to render just the vertex position in light space. 不要包含任何照明法线、二次转换等。Don't include any lighting normals, secondary transformations, and so on.

PixelShaderInput main(VertexShaderInput input)
{
    PixelShaderInput output;
    float4 pos = float4(input.pos, 1.0f);

    // Transform the vertex position into projected space.
    pos = mul(pos, model);
    pos = mul(pos, view);
    pos = mul(pos, projection);
    output.pos = pos;

    return output;
}

在本演练的下一部分中,了解如何通过使用深度测试进行呈现来添加阴影。In the next part of this walkthrough, learn how to add shadows by rendering with depth testing.