Home Page Twitter Facebook Feed RSS
NotJustCode
Apri

Toon Shading 440 Visite) DirectX 11

Detto anche Toon Mapping, è una tecnica di rendering per simulare il disegno dei cartoni animati, caratterizzato dall'utilizzo di ombre nette e di contorni.

Il Toon Shading shading è un algoritmo che si compone di due fasi

  1. Il cell shading vero e proprio, ossia il rendering della scena con ombre nette
  2. L'outline rendering, ossia il rendering dei contorni

Il secondo passaggio può essere saltato se non si vogliono usare i contorni (un esempio è Legend Of Zelda the Wind Waker che usa il Cell Shading senza contorni).

Il primo passaggio è un normale rendering multi pass (ossia in cui gli oggetti vengono renderizzati sul target ad uno ad uno). Per il toon mapping si possono utilizzare tutte le forme di illuminazione che vogliamo. Cambia però quello che mandiamo a video. Nel phong ad esempio otteniamo il valore di Diffuse con il DotProduct tra direzione della luce e normale, un valore che varia tra -1 e +1. Portandolo come valore tra 0 ed 1 (scartando quindi i valori negativi), possiamo usare il valore come coordinata per una texture come questa sotto. Più alto è il valore di diffuse e più a destra prenderemo il colore. Come potete subito capire il colore varierà in base al colore della texture.

http://www.notjustcode.it/public/CellShading_DC67/band_thumb1.jpg

Questa è un ingrandimento, basta anche una piccola texture 8x1. Il numero di bande e la loro gradazione è da scegliere in base alle proprie preferenze. Stesso sistema potete usare per lo specular (io ho utilizzato una texture con nero e bianco per lo specular).

Il secondo step richiede invece un post processing tramite un filtro di tipo Sobel.

I filtri sono degli algoritmi che permettono di ottenere delle trasformazioni dell'immagine. Il filtro sobel è un algoritmo che permette di tracciare linee nere dove c'è una forte differenza di colori.

Utilizzando 2 texture come render target mandiamo nella prima il rendering del cell shading, nel secondo invece mandiamo le normali in RGB e la distanza dalla telecamera in alpha. Un punto dove tracciare una linea nera è dove un corpo ha degli spigoli ed uno spigolo è proprio un punto in cui le normali cambiano direzione rapidamente. L'altro punto è invece quello in cui c'è differenza di profondità e per quello si usa la distanza (il contorno comparirà dove c'è una forte differenza di distanza). Con il filtro sobel applicato alla media delle normali e alla profondità otterremo i contorni corretti. Questo è l'algoritmo Sobel

float d=1.0f/1024.0f;
float4 aa=depthTarget.Sample(textureSampler2,float2(input.tex.x-d,input.tex.y-d));
float4 bb=depthTarget.Sample(textureSampler2,float2(input.tex.x,input.tex.y-d));
float4 cc=depthTarget.Sample(textureSampler2,float2(input.tex.x+d,input.tex.y-d));
float4 dd=depthTarget.Sample(textureSampler2,float2(input.tex.x-d,input.tex.y));
float4 ee=depthTarget.Sample(textureSampler2,float2(input.tex.x+d,input.tex.y));
float4 ff=depthTarget.Sample(textureSampler2,float2(input.tex.x-d,input.tex.y+d));
float4 gg=depthTarget.Sample(textureSampler2,float2(input.tex.x,input.tex.y+d));
float4 hh=depthTarget.Sample(textureSampler2,float2(input.tex.x+d,input.tex.y+d));
float4 delX;
float4 delY;
delX = ((cc + (2 * ee) + hh) - (aa + (2 * dd) + ff));
delY = ((ff + (2 * gg) + hh) - (aa + (2 * bb) + cc));
float4 var = abs(delX) + abs(delY);

Questo è l'algoritmo di sobel usato. Si legge la texture negli 8 punti intorno a lui, quindi si usa la formula per calcolare la differenza ed in var si ottiene un valore che indica quanto varia. Usando ad esempio

float C= var.x<0.4F;

La variabile C varrà 0 dove c'è una differenza minore del valore 0.4 scelto, 1 altrimenti (quindi bianco e nero). Nel nostro caso useremo la media di XYZ per i contorni basati su normale, W per quelli basati sulla distanza. Variando il parametro di confronto potremo correggere il contorno.

Ultime note sul demo, il post processing. Per ottenerlo ho renderizzato un quadrato di spigoli -1,-1,0 e 1,1,0. Se mandato in output dal vertex shader senza trasformarlo per una matrice coprirà perfettamente lo schermo. Su di esso useremo le texture uscite dal primo rendering su texture.

Si può migliorare ulteriormente l'algoritmo applicando un filtro blur al contorno in modo da renderlo più morbido.

 

Vi rimando al tutorial numero 15 della serie (Github o in fondo dal primo tutorial)