使用深度測試轉譯場景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 中的深度緩衝區實作陰影體的第三部分。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.