Soprattutto nelle applicazioni real-time, l'utilizzo di materiali doppia faccia è utile nell'approssimazioni di un oggetto con spessore molto limitato (in genere meno di un centimetro) come: fogli di carta e di plastica, pannelli in metallo, ecc...
La soluzione presentata fa leva sulla semantica VFACE disponibile dagli Shader Model 3.0 in poi che, se applicata ad un parametro di tipo float in ingresso ad un Pixel Shader, indica l'orientamento del triangolo di cui il pixel processato fa parte. Questo parametro può prendere due valori: 1 e -1 rispettivamente quando il triangolo è front-face rispetto al punto di vista e back-face rispetto al punto di vista.
Grazie a questa informazione supplementare sarà possibile renderizzare in un solo passaggio (quindi con l'invio della geometria alla GPU una sola volta) due differenti materiali: uno per l'interno ed uno per l'esterno della geometria.
Questo approccio prevede però il dimezzarsi del numero di samplers per materiale; nonstante ciò sarà possibile realizzare un dignitoso per-pixel normal mapping e specificare valori indipendenti per le componenti diffusa e speculare.
Vertex Shader:
1 uniform float4x4 World;
2 uniform float4x4 WorldView;
3 uniform float4x4 Proj;
4
5 // Posizione della telecamera in World-Space
6 uniform float4 eyePos;
7 // Posizione della sorgente luminosa omnidirezionale
8 uniform float3 lightPos;
9
10 // Struttura per gli elementi in uscita dal Vertex Shader
11 struct VS_OUT
12 {
13 float4 oPos : POSITION;
14 float2 oTexCoords : TEXCOORD0;
15 float3 oLightVec : TEXCOORD1;
16 float3 oViewVec : TEXCOORD2;
17 };
18
19 // Dichiarazione della funzione del Vertex Shader con elementi del vertice
20 // inseriti direttamente nella lista dei parametri.
21 VS_OUT mainVS(
22 in float3 Pos : POSITION,
23 in float3 Normal : NORMAL,
24 in float3 Tangent : TANGENT,
25 in float3 Binormal : BINORMAL,
26 in float2 TexCoords : TEXCOORD0)
27 {
28 // Dichiarazione della struttura per i dati in uscita
29 VS_OUT Out;
30
31 // Trasformazione del vertice da object-space a world-space
32 float4 p = mul(float4(Pos, 1.0), World);
33
34 // Passaggio della posizione trasformata in screen space...
35 Out.oPos = mul(mul(p, WorldView), Proj);
36 // ...e delle coordinate di textures al Pixel Shader
37 Out.oTexCoords = TexCoords * float2(1, .5);
38
39 // Calcolo della matrice di rotazione per la trasformazione da
40 // world-space a tangent-space
41 float3x3 objToTangentSpace;
42 objToTangentSpace[0] = mul(float4(Tangent, 0.0), World);
43 objToTangentSpace[1] = mul(float4(Binormal, 0.0), World);
44 objToTangentSpace[2] = mul(float4(Normal, 0.0), World);
45
46 // Calcolo dei vettori che vanno al vertice alla sorgente luminosa e
47 // dal vertice alla posizione della telecamera.
48 // Trasformazione dei vettori in tangent-space.
49 float3 LightVec = lightPos - p;
50 Out.oLightVec = mul(objToTangentSpace, LightVec);
51 Out.oViewVec = mul(objToTangentSpace, (eyePos - p));
52
53 return Out;
54 }
Pixel Shader:
1 // Colore ambientale
2 uniform float4 ambientColor;
3
4 // Textures del materiale front-face
5 sampler2D frontDiffuseSampler;
6 sampler2D frontNormalSampler;
7
8 // Proprietà del materiale front-face
9 uniform float4 frontDiffuseColor;
10 uniform float4 frontSpecularColor;
11 uniform float frontSpecularExponent;
12
13 // Textures del materiale back-face
14 sampler2D backDiffuseSampler;
15 sampler2D backNormalSampler;
16
17 // Proprietà del materiale back-face
18 uniform float4 backDiffuseColor;
19 uniform float4 backSpecularColor;
20 uniform float backSpecularExponent;
21
22 // Dichiarazione del Pixel Shader per materiali doppia faccia
23 float4 litTexturedPS(
24 float Side : VFACE,
25 float2 TexCoord : TEXCOORD0,
26 float3 LightVec : TEXCOORD1,
27 float3 ViewVec : TEXCOORD2) : COLOR0
28 {
29 // Normalizzazione dei vettori provenienti dal Vertex Shader
30 LightVec = normalize(LightVec);
31 ViewVec = normalize(ViewVec);
32
33 float4 ambient, diffuse, specular;
34 float3 normal;
35 float exponent;
36
37 // Recupero di tutti i texel delle varie componenti del materiale e
38 // modulazione con i rispettivi colori secondo la superficie visibile
39 if (Side > 0.0)
40 {
41 // front-face
42 ambient = tex2D(frontDiffuseSampler, TexCoord) * ambientColor;
43 diffuse = tex2D(frontDiffuseSampler, TexCoord) * frontDiffuseColor;
44 specular = frontSpecularColor;
45 exponent = frontSpecularExponent;
46
47 normal = tex2D(frontNormalSampler, TexCoord).xyz * 2.0 - 1.0;
48
49 }
50 else
51 {
52 // back-face
53 ambient = tex2D(backDiffuseSampler, TexCoord) * ambientColor;
54 diffuse = tex2D(backDiffuseSampler, TexCoord) * backDiffuseColor;
55 normal = tex2D(backNormalSampler, TexCoord).xyz * 2.0 - 1.0;
56 specular = backSpecularColor;
57 exponent = backSpecularExponent;
58
59 // Per le parti back-face è necessario invertire il verso della normale
60 // in modo da eseguire correttamente l'illuminazione
61 normal = -normal;
62 }
63
64 // Calcolo della componente di illuminazione diffusa
65 float diff = saturate(dot(normal, LightVec));
66 // Calcolo della componente dell'hot-spot speculare
67 float spec = pow(saturate(dot(reflect(-ViewVec, normal), LightVec)), exponent);
68
69 // Calcolo del colore definitivo come somma delle componenti
70 return (ambient + diffuse * diff + specular * spec);
71 }
Come visibile dal codice il cuore dell'implementazione si trova nel Pixel Shader dove è presente una gestione particolare della legge di lambert, invertendo la normale per le parti back-face, dovuta alla necessità di illuminare correttamente anche l'interno dell'oggetto. Secondo la variabile Side sono inoltre selezionate le caratteristiche del materiale corretto.

Quello visibile in figura è un toro geometrico annodato tagliato dal piano di clipping della telecamera.