Eseguire il rendering della scena con test di profondità

Creare un effetto di ombreggiatura aggiungendo test di profondità allo shader di vertici (o di geometria) e allo shader di pixel. Parte 3 della Procedura dettagliata: implementare volumi di ombre tramite buffer di profondità in Direct3D 11.

Includere la trasformazione per il frustum di luce

Lo shader di vertici deve calcolare la posizione dello spazio di luce trasformato per ciascun vertice. Fornire il modello dello spazio di luce, la visualizzazione e le matrici di proiezione utilizzando un buffer costante. È anche possibile utilizzare questo buffer costante per fornire la posizione della luce e la normale per i calcoli di illuminazione. La posizione trasformata nello spazio di luce verrà utilizzata durante il test di profondità.

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;
}

Successivamente, lo shader di pixel utilizzerà la posizione dello spazio di luce interpolata fornita dallo shader di vertici per verificare se il pixel è in ombreggiatura.

Verificare se la posizione si trovi o meno nel frustum di luce

Prima di tutto, controllare che il pixel si trovi nel frustum di visualizzazione della luce normalizzando le coordinate X e Y. Se si trovano entrambe all'interno dell'intervallo [0, 1] allore è possibile che il pixel sia in ombra. In caso contrario, è possibile ignorare il test di profondità. Uno shader può verificarlo rapidamente chiamando Saturate e confrontando il risultato rispetto al valore originale.

// 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))
{

Test di profondità sulla mappa delle ombre

Utilizzare una funzione di confronto di esempio (SampleCmp o SampleCmpLevelZero) per testare la profondità del pixel nello spazio di luce rispetto alla mappa di profondità. Calcolare il valore di profondità dello spazio di luce normalizzato, ovvero z / w, e passare il valore alla funzione di confronto. Poiché si utilizza un test di confronto LessOrEqual per il campionatore, la funzione intrinseca restituisce zero quando il test di confronto viene superato; ciò indica che il pixel è in ombra.

// 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
    )
    );

Calcolo dell'illuminazione all'interno o all'esterno dell'ombreggiatura

Se il pixel non è ombreggiato, lo shader di pixel deve calcolare l'illuminazione diretta e aggiungerla al valore del pixel.

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);
}

In caso contrario, lo shader di pixel deve calcolare il valore del pixel utilizzando l'illuminazione ambientale.

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

Nella parte successiva di questa procedura dettagliata si apprenderà come Supportare mappe delle ombre in una gamma di hardware.