Questa tecnica permette il rendering di una scena in modo che somigli alla classica ombreggiatura cartoon caratterizzata dal disegno dei personaggi con bordo a china e riempimento con poche bande di colore in luogo della sfumatura continua.
Per creare questo effetto sarà necessario estendere il codice per il rilevamento dei bordi: occorrerà aggiungere al G-Buffer un nuovo render-target dove si terrà traccia del colore per pixel e occorrerà integrare nel Pixel Shader presentato precedentemente un metodo per ombreggiare la superficie delle geometrie.
1 // Samplers dei render-targets
2 sampler2D normalSampler;
3 sampler2D depthSampler;
4 sampler2D colorSampler;
5
6 // Inverso della dimensione del viewport
7 uniform float2 invViewSize;
8
9 // Soglie per determinare la presenza di un bordo
10 uniform float depthTrash;
11 uniform float normalTrash;
12
13 // Colore e intensità bordo
14 uniform float4 borderColor;
15 uniform float borderIntensity;
16
17 // Direzione della luce
18 uniform float4 lightDir;
19
20 // Colori ambientale e diffuso della superficie
21 uniform float4 ambientColor;
22 uniform float4 diffuseColor;
23
24 // Posizioni relative al texel corrente da cui prelevare i dati
25 const float2 tap[] =
26 {
27 float2(-1, -1),
28 float2( 0, -1),
29 float2( 1, -1),
30 float2(-1, 1),
31 float2( 0, 1),
32 float2( 1, 1),
33 float2(-1, 0),
34 float2( 1, 0)
35 };
36
37 float4 ps_main(float2 TexCoords : TEXCOORD0) : COLOR0
38 {
39 // Recupero le informazioni dai render-targets per il pixel corrente
40 float depth = tex2D(depthSampler, TexCoords).r;
41 float3 normal = tex2D(normalSampler, TexCoords) * 2.0 - 1.0;
42 float4 color = tex2D(colorSampler, TexCoords);
43
44 // Calcolo intensità diffusa
45 float diff = saturate(dot(normal, -lightDir));
46
47 // Creazione di 5 livelli di intensità invece della
48 // gradazione continua da 0.0 a 1.0
49 diff = ((int)(diff * 5)) * 0.2;
50
51 color = ambientColor + color * diff;
52
53 // Calcolo l'intensità del bordo
54 float4 border = 0.0;
55 for (int i = 0; i < 8; i++)
56 {
57 // Calcolo le coordinate dei texel adiacenti
58 float2 uv = TexCoords + tap[i] * invViewSize;
59 // Recupero le info dai render-targets
60 float d = tex2D(depthSampler, uv).r;
61 float3 n = tex2D(normalSampler, uv) * 2.0 - 1.0;
62
63 // Un bordo è identificabile quando la distanza o l'angolo
64 // fra le normali di due pixel adiacenti supera una certa soglia
65 border += (abs(d - depth) > depthTrash) +
66 (dot(normal, n) < normalTrash);
67 }
68
69 // Media dell'intensità è moltiplicazione per intensità specificata
70 border /= 8;
71 border *= borderIntensity;
72
73 // Interpolazione tra colore diffuso e colore del bordo
74 // secondo l'intensità del bordo.
75 return lerp(color, borderColor, border);
76 }
Come visibile dal codice (che qui sfrutta l'illuminazione di una sorgente direzionale) il calcolo dell'ombreggiatura diffusa è esattamente lo stesso applicato nella sezione dedicata all'implementazione dei materiali; quello che genera l'effetto cartoon è racchiuso interamente nella linea di codice 49.
Grazie al casting, trasformazione di un tipo in un'altro, è possibile troncare la parte decimale di un numero floating-point producendo una parziale perdita di precisione che trasforma una gradazione costante tra 0.0 e 1.0 (nero e bianco) in un determinato numero di steps. Nello specifico: moltiplicando il valore per n, troncando la parte decimale con un casting verso il tipo int e dividendo di nuovo per n si otterranno n steps; nel nostro caso corrispondenti ad n bande di intensità (nel codice n = 5).
Per estensione alla tecnica precedente è possibile applicare delle texture agli oggetti avendo ovviamente cura di rispettare lo stile di visualizzazione (non ha senso utilizzare textures fotorealistiche nel contesto di un'ombreggiatura cartoon).


Il risvolto interessante di questo approccio al cartoon rendering si rivela nella possibilità di utilizzare techinche di illuminazione e tipi di sorgenti luminose convenzionali per poi modificare l'apparenza della scena nel post-processo di ombreggiatura cartoon; tutto ciò rende virtualmente possibile il passaggio tra i due metodi di visualizzazione in real-time, magari per esigenze di design e di particolari condizioni in cui il giocatore si trova.
