為頂點(或幾何)著色器和像素著色器新增深度測試,以創建陰影效果。 逐步解說的第 3 部分:在 Direct3D 11中利用深度緩衝區實作陰影體積。
包含光錐體的轉換
您的頂點著色器必須計算每個頂點的轉換光線空間位置。 使用常數緩衝區提供光源空間模型、視圖和投影矩陣。 您也可以使用此常數緩衝區來提供光線位置和正常光源計算。 在深度測試期間,將會使用光線空間中的轉換位置。
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;
}
接下來,圖元著色器會使用頂點著色器所提供的插補光線空間位置來測試圖元是否在陰影中。
測試位置是否位於光錐體中
首先,檢查像素是否位於光源的可視錐體中,方法是將 X 和 Y 座標正規化。 如果兩者都在 [0, 1] 範圍內,則圖元有可能處於陰影中。 否則,您可以略過深度測試。 著色器可以藉由呼叫 飽和,並將結果與原始值進行比較,以快速測試此狀況。
// 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))
{
針對陰影圖的深度測試
使用範例比較函式(SampleCmp 或 SampleCmpLevelZero),針對深度地圖測試圖元在光線空間中的深度。 計算標準化光空間深度值,也就是 z / w,並將值傳遞至比較函式。 由於我們使用取樣器的 LessOrEqual 比較測試,因此當比較測試通過時,內部函數會傳回零;這表示圖元位於陰影中。
// 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
)
);
計算陰影中或陰影外光源
如果圖元不在陰影中,圖元著色器應該計算直接光源,並將它新增至圖元值。
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);
}
否則,圖元著色器應該使用環境光源來計算圖元值。
return float4(input.color * ambient, 1.f);
在本逐步解說的下一個部分中,瞭解如何 支援各種硬體上的陰影對應。