通过深度测试呈现场景Render the scene with depth testing

通过向顶点(或几何图形)着色器和像素着色器中添加深度测试来创建阴影效果。Create a shadow effect by adding depth testing to your vertex (or geometry) shader and your pixel shader. 操作实例:使用 Direct3D 11 中的深度缓冲区实现阴影卷的第 3 部分。Part 3 of Walkthrough: Implement shadow volumes using depth buffers in Direct3D 11.

包括用于光锥的转换Include transformation for light frustum

顶点着色器需要计算每个顶点的转换光线空间位置。Your vertex shader needs to compute the transformed light space position for each vertex. 使用常量缓冲区提供光线空间模型、视图以及投影矩阵。Provide the light space model, view, and projection matrices using a constant buffer. 也可以使用该常量缓冲区为光线计算提供光线位置和法线。You can also use this constant buffer to provide the light position and normal for lighting calculations. 在深度测试期间将使用光线空间中的转换位置。The transformed position in light space will be used during the depth test.

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

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

    // Transform the vertex position into projected space from the POV of the light.
    float4 lightSpacePos = mul(modelPos, lView);
    lightSpacePos = mul(lightSpacePos, lProjection);
    output.lightSpacePos = lightSpacePos;

    // Light ray
    float3 lRay = lPos.xyz - modelPos.xyz;
    output.lRay = lRay;
    
    // Camera ray
    output.view = eyePos.xyz - modelPos.xyz;

    // Transform the vertex normal into world space.
    float4 norm = float4(input.norm, 1.0f);
    norm = mul(norm, model);
    output.norm = norm.xyz;
    
    // Pass through the color and texture coordinates without modification.
    output.color = input.color;
    output.tex = input.tex;

    return output;
}

接下来,像素着色器将使用顶点着色器提供的差值光线空间位置来测试像素是否处于阴影中。Next, the pixel shader will use the interpolated light space position provided by the vertex shader to test whether the pixel is in shadow.

测试位置是否位于光锥之内Test whether the position is in the light frustum

首先,通过将 X 和 Y 坐标规范化来检查像素是否位于视锥之内。First, check that the pixel is in the view frustum of the light by normalizing the X and Y coordinates. 如果两者都在范围 [ 0、1范围内,则 ] 像素可能位于阴影中。If they are both within the range [0, 1] then it's possible for the pixel to be in shadow. 否则,你可以跳过深度测试。Otherwise you can skip the depth test. 通过调用 Saturate 并将结果与原始值进行比较来快速对着色器进行此项测试。A shader can test for this quickly by calling Saturate and comparing the result against the original value.

// Compute texture coordinates for the current point's location on the shadow map.
float2 shadowTexCoords;
shadowTexCoords.x = 0.5f + (input.lightSpacePos.x / input.lightSpacePos.w * 0.5f);
shadowTexCoords.y = 0.5f - (input.lightSpacePos.y / input.lightSpacePos.w * 0.5f);
float pixelDepth = input.lightSpacePos.z / input.lightSpacePos.w;

float lighting = 1;

// Check if the pixel texture coordinate is in the view frustum of the 
// light before doing any shadow work.
if ((saturate(shadowTexCoords.x) == shadowTexCoords.x) &&
    (saturate(shadowTexCoords.y) == shadowTexCoords.y) &&
    (pixelDepth > 0))
{

针对阴影映射进行深度测试Depth test against the shadow map

使用示例比较函数(SampleCmpSampleCmpLevelZero)来针对深度映射测试像素在光线空间中的深度。Use a sample comparison function (either SampleCmp or SampleCmpLevelZero) to test the pixel's depth in light space against the depth map. 计算标准化光线空间深度值(即 z / w),并将该值传递给比较函数。Compute the normalized light space depth value, which is z / w, and pass the value to the comparison function. 由于我们使用 LessOrEqual 示例比较测试,则比较测试通过时固有函数会返回零;这表示像素位于阴影中。Since we use a LessOrEqual comparison test for the sampler, the intrinsic function returns zero when the comparison test passes; this indicates that the pixel is in shadow.

// Use an offset value to mitigate shadow artifacts due to imprecise 
// floating-point values (shadow acne).
//
// This is an approximation of epsilon * tan(acos(saturate(NdotL))):
float margin = acos(saturate(NdotL));
#ifdef LINEAR
// The offset can be slightly smaller with smoother shadow edges.
float epsilon = 0.0005 / margin;
#else
float epsilon = 0.001 / margin;
#endif
// Clamp epsilon to a fixed range so it doesn't go overboard.
epsilon = clamp(epsilon, 0, 0.1);

// Use the SampleCmpLevelZero Texture2D method (or SampleCmp) to sample from 
// the shadow map, just as you would with Direct3D feature level 10_0 and
// higher.  Feature level 9_1 only supports LessOrEqual, which returns 0 if
// the pixel is in the shadow.
lighting = float(shadowMap.SampleCmpLevelZero(
    shadowSampler,
    shadowTexCoords,
    pixelDepth + epsilon
    )
    );

计算阴影内外的照明Compute lighting in or out of shadow

如果像素不在阴影内,则像素阴影应计算直接照明并将其添加到像素值中。If the pixel is not in shadow, the pixel shader should compute direct lighting and add it to the pixel value.

return float4(input.color * (ambient + DplusS(N, L, NdotL, input.view)), 1.f);
float3 DplusS(float3 N, float3 L, float NdotL, float3 view)
{
    const float3 Kdiffuse = float3(.5f, .5f, .4f);
    const float3 Kspecular = float3(.2f, .2f, .3f);
    const float exponent = 3.f;

    // Compute the diffuse coefficient.
    float diffuseConst = saturate(NdotL);

    // Compute the diffuse lighting value.
    float3 diffuse = Kdiffuse * diffuseConst;

    // Compute the specular highlight.
    float3 R = reflect(-L, N);
    float3 V = normalize(view);
    float3 RdotV = dot(R, V);
    float3 specular = Kspecular * pow(saturate(RdotV), exponent);

    return (diffuse + specular);
}

否则,像素阴影将使用环境照明计算像素值。Otherwise, the pixel shader should compute the pixel value using ambient lighting.

return float4(input.color * ambient, 1.f);

在本演练的下一部分中,了解如何在硬件范围内支持阴影映射In the next part of this walkthrough, learn how to Support shadow maps on a range of hardware.