Il rilevamento dei bordi di una scena o di un solo oggetto 3D è un metodo molto utilizzato nel design dell'interfaccia con l'utente per segnalare una geometria selezionata o attiva; quando applicato all'intera scena può essere utile per realizzare un rendering non fotorealistico che somigli ad un disegno fatto manualmente tramite diversi strumenti come: pennini, carboncini, grafite, ecc...
I bordi possono essere rilevati secondo due metodi complementari: la differenza di distanza dall'osservatore e l'angolo formato da due vettori normali. Questi metodi richiedono il test di un pixel con quelli adiacenti rendendo necessaria l'implementazione di un G-Buffer che contenga la distanza dalla telecamera e la direzione delle normali per pixel.

Come è possibile notare dall'immagine precedente i due metodi non sono in grado di rilevare correttamente tutti i bordi, per questo verranno utilizzati simultaneamente. Quello seguente è il codice del Pixel Shader che per ogni pixel del viewport (e quindi per ogni texel del G-Buffer) determina la presenza di un bordo:
1 // Samplers dei render-targets
2 sampler2D normalSampler;
3 sampler2D depthSampler;
4
5 // Inverso della dimensione del viewport
6 uniform float2 invViewSize;
7
8 // Soglie per determinare la presenza di un bordo
9 uniform float depthTrash;
10 uniform float normalTrash;
11
12 // Colore e intensità bordo
13 uniform float4 borderColor;
14 uniform float borderIntensity;
15
16 // Posizioni relative al texel corrente da cui prelevare i dati
17 const float2 tap[] =
18 {
19 float2(-1, -1),
20 float2( 0, -1),
21 float2( 1, -1),
22 float2(-1, 1),
23 float2( 0, 1),
24 float2( 1, 1),
25 float2(-1, 0),
26 float2( 1, 0)
27 };
28
29 float4 ps_main(float2 TexCoords : TEXCOORD0) : COLOR0
30 {
31 // Recupero le informazioni dai render-targets per il pixel corrente
32 float depth = tex2D(depthSampler, TexCoords).r;
33 float3 normal = tex2D(normalSampler, TexCoords) * 2.0 - 1.0;
34
35 // Calcolo l'intensità del bordo
36 float4 border = 0.0;
37 for (int i = 0; i < 8; i++)
38 {
39 // Calcolo le coordinate dei texel adiacenti
40 float2 uv = TexCoords + tap[i] * invViewSize;
41 // Recupero le info dai render-targets
42 float d = tex2D(depthSampler, uv).r;
43 float3 n = tex2D(normalSampler, uv) * 2.0 - 1.0;
44
45 // Un bordo è identificabile quando la distanza o l'angolo
46 // fra le normali di due pixel adiacenti supera una certa soglia
47 border += (abs(d - depth) > depthTrash) +
48 (dot(normal, n) < normalTrash);
49 }
50
51 // Media dell'intensità è moltiplicazione per intensità specificata
52 border /= 8;
53 border *= borderIntensity;
54
55 // Interpolazione tra bianco e colore del bordo
56 // secondo l'intensità del bordo.
57 return lerp(1, borderColor, border);
58 }
Nel codice troviamo applicati esattamente i principi di funzionamento descritti in precedenza: viene recuperato il valore della distanza e la direzione della normale per il pixel corrente; nel ciclo for vengono testati gli otto pixel adiacenti secondo i due metodi tenendo traccia dei risultati.
Tralascando gran parte del codice, perchè commentato e intuitivo, è interessante fermarsi su due punti chiave: il calcolo delle coordinate di texture dei texels adiacenti ed il test per determinare la presenza di un bordo.
È noto che nelle GPU l'indirizzamento dei texel avviene tramite coordinate di texture normalizzate (aventi un range 0.0 ... 1.0) qualsiasi sia la dimensione della texture; per questo è necessario passare al Pixel Shader una variabile di tipo float2 che rappresenti l'inverso della dimensione del viewport (che in questo caso corrisponde con l'inverso della dimensione della texture) che moltiplicata per gli offset specificati nell'array tap[] permette di calcolare le coordinate dei texels adiacenti. Nella sezione dedicata all'applicazione degli effetti in post-processo vedremo in realtà che sara necessario scrivere del codice aggiuntivo nel Vertex Shader per permettere il perfetto allineamento tra pixels dello schermo e texels, requisito di vitale importanza per la corretta visualizzazione dell'immagine.
Nel calcolo dell'intensità del bordo viene sfruttata la capacità della GPU di trasformare un risultato booleano in floating-point (1.0 = vero 0.0 = falso) e la conseguente possibilità di fondere i due metodi esposti precedentemente. Le due variabili defionibili dall'utente depthTrash e normalTrash rappresentano rispettivamente la distanza massima e il coseno dell'angolo minimo oltre i quali viene rilevato un bordo.
La variabile border traccia l'intensità del bordo passante per il pixel corrente con un range che va da 0.0 (bordo assente) a 1.0 rendendo possibile il disegno di un bordo con anti-aliasing.

È interessante notare che, quando non ci si basa sul fotorealismo, uno stesso effetto può essere implementato in modi differenti e, viceversa, modificando semplicemente i parametri di una stessa implementazione, è possibile ottenere altri effetti anche significativamente differenti.
