9.3. Colorizzazione costante

5
Il tuo voto: Nessuno Media: 5 (1 vote)

Il processo di colorizzazione ha la caratteristica di alterare la tonalità, la saturazione o l'intensità dei colori dell'immagine; può essere effettuato seguendo metodi differenti con risultati altrettanto diversi.

Il metodo più semplice è quello di integrare nel Pixel Shader di desaturazione la modulazione dell'intensità calcolata con un colore specificato dall'utente: questo processo genera un'immagine monocromatica con tonalità basata sul colore impostato (esempio fotografia invecchiata tendente al seppia):

  1 // Sampler dell'immagine sorgente
  2 sampler2D sourceSampler;
  3 
  4 // Inverso della dimensione del viewport
  5 uniform float2 invViewSize;
  6 
  7 // Gestione intensità componenti in modo dipendente
  8 uniform float4 hue;
  9 
 10 // Costanti per l'influenza delle singole componenti
 11 const float4 luminanceConstants = float4(0.2125, 0.7154, 0.0721, 0.0000);
 12 
 13 float4 ps_main(float2 TexCoords : TEXCOORD0) : COLOR0
 14 {
 15     // Recupero le informazioni dai render-targets per il pixel corrente
 16     float4 color = tex2D(sourceSampler, TexCoords);
 17 
 18     // Calcolo intensità corrispondente
 19     float intensity = dot(color, luminanceConstants);
 20 
 21     return hue * intensity;
 22 }

Visualizzazione dell'effetto:

Un secondo metodo può essere quello di specificare delle costanti differenti per ognuna delle tre componenti di colore in modo da far risaltare maggiormente alcune tinte rispetto ad altre; questo tipo di elaborazione è spesso utilizzato per dare risalto a scene particolari secondo la loro caratteristica (interni, esterni, ecc...) o secondo il loro significato riguardo la trama o il gameplay.

  1 // Sampler dell'immagine sorgente
  2 sampler2D sourceSampler;
  3 
  4 // Inverso della dimensione del viewport
  5 uniform float2 invViewSize;
  6 
  7 // Gestione intensità componenti in modo dipendente
  8 uniform float3 rgbMultiplier;
  9 
 10 float4 ps_main(float2 TexCoords : TEXCOORD0) : COLOR0
 11 {
 12     // Recupero le informazioni dai render-targets per il pixel corrente
 13     float4 color = tex2D(sourceSampler, TexCoords);
 14 
 15     // Moltiplico con le costanti
 16     color.rgb *= rgbMultiplier;
 17 
 18     return color;
 19 }

Visualizzazione dell'effetto:

Un terzo metodo, il più complesso tra quelli presentati in questo capitolo, prevede l'elaborazione cromatica del colore tramite conversioni da e verso spazi colore differenti (in genere i più utilizzati sono RGB e HSV (Hue Saturation Value - Tonalità Saturazione Valore). Tramite questi processi si possono elaborare in modo indipendente: la luminosità, la saturazione del colore, la distorsione cromatica dell'immagine; di questi l'ultima possibilità è sicuramente la più interessante per la capacità di creare effetti particolari legati ad esempio allo stato del giocatore senza richiedere l'uso di una segnalazione nell'interfaccia con l'utente tramite grafica specifica.

A differenza del metodo precedente richiede l'utilizzo dello Shader Model 3.0 dato che la conversione viene effettuata tramite operazioni aritmetiche e blocchi condizionali, oggi trattati dalle GPU in modo molto efficiente. Precedentemente per realizzare questi effetti erano utilizzate delle textures precalcolate che permettevano (tramite il corretto indirizzamento con le coordinate di texture) la conversione istantanea da uno spazio colore all'altro.

  1 // Sampler dell'immagine sorgente
  2 sampler2D sourceSampler;
  3 
  4 // Inverso della dimensione del viewport
  5 uniform float2 invViewSize;
  6 
  7 // Spostamento della tonalità
  8 uniform float hueShift;
  9 // Intensità della saturazione
 10 uniform float saturationStrength;
 11 // Intensità del valore
 12 uniform float valueStrength;
 13 
 14 // Funzione per la conversione dallo spazio colore HSV a quello RGB
 15 float3 HSVToRGB(float3 HSV)
 16 {
 17     // Porta il range della tonalità (HSV.x) da 0.0 ... 1.0 a 0 ... 360
 18     // convertendola in gradi sessagesimali
 19     float h = (HSV.x * 360.0) % 360.0;
 20     float s = HSV.y;
 21     float v = HSV.z;
 22 
 23     // Se la saturazione è zero allora ritorna una sfumatura di grigio
 24     if (s == 0.0) return HSV.zzz;
 25 
 26     // La ruota dei colori è costituita da sei settori.
 27     // Calcola il settore corrente e la sua parte frazionale.
 28     float sectorPos = h / 60.0;
 29     int sectorNumber = (int)(floor(sectorPos));
 30     float fractionalSector = sectorPos - sectorNumber;
 31 
 32     // Calcola i valori dei tre assi dello spazio colore RGB
 33     float p = v * (1.0 - s);
 34     float q = v * (1.0 - (s * fractionalSector));
 35     float t = v * (1.0 - (s * (1.0 - fractionalSector)));
 36 
 37     // Assegna le componenti in base al settore contenente
 38     // l'angolo specificato nella tonalità di colore.
 39     if (sectorNumber == 0)
 40         return float3(v, t, p);
 41     else if (sectorNumber == 1)
 42         return float3(q, v, p);
 43     else if (sectorNumber == 2)
 44         return float3(p, v, t);
 45     else if (sectorNumber == 3)
 46         return float3(p, q, v);
 47     else if (sectorNumber == 4)
 48         return float3(t, p, v);
 49     else if (sectorNumber == 5)
 50         return float3(v, p, q);
 51 
 52     return float3(0.0, 0.0, 0.0);
 53 }
 54 
 55 // Funzione per la conversione dallo spazio colore RGB a quello HSV
 56 float3 RGBToHSV(float3 RGB)
 57 {
 58     float h;
 59     float s;
 60     float v;
 61 
 62     // Calcola la componente minima e massima del colore
 63     float cmin = min(min(RGB.r, RGB.g), RGB.b);
 64     float cmax = max(max(RGB.r, RGB.g), RGB.b);
 65     float delta = cmax - cmin;
 66     v = cmax;
 67 
 68     // Se le componenti di colore sono zero o tutte dello stesso valore
 69     // la saturazione è zero e la tonalità indefinita.
 70     if (cmax == 0.0 || delta == 0.0)
 71     {
 72         s = 0.0;
 73         h = 1.0;
 74     }
 75     else
 76     {
 77         s = delta / cmax;
 78         if (RGB.r == cmax)
 79         {
 80             // Tonalità tra giallo e magenta
 81             h = (RGB.g - RGB.b) / delta;
 82         }
 83         else if (RGB.g == cmax)
 84         {
 85             // Tonalità tra ciano e giallo
 86             h = 2.0 + (RGB.b - RGB.r) / delta;
 87         }
 88         else
 89         {
 90             // Tonalità tra magenta e ciano
 91             h = 4.0 + (RGB.r - RGB.g) / delta;
 92         }
 93     }
 94 
 95     // Ridimensiona h nel range 0.0 ... 1.0 e aggiunge 1.0
 96     // finchè il valore risulta negativo.
 97     h *= 1.0 / 6.0;
 98     while (h < 0.0)
 99         h += 1.0;
100 
101     return float3(h, s, v);
102 }
103 
104 // Funzione principale del Pixel Shader
105 float4 ps_main(float2 TexCoords : TEXCOORD0) : COLOR0
106 {
107     // Recupero le informazioni dai render-targets per il pixel corrente
108     float4 color = tex2D(sourceSampler, TexCoords);
109 
110     // Converto lo spazio colore da RGB a HSV
111     float3 hsv = RGBToHSV(color.rgb);
112 
113     // Modifico i parametri di tonalità, saturazione e valore
114     hsv.x = (hsv.x + hueShift) % 1.0;
115     hsv.y = saturate(hsv.y * saturationStrength);
116     hsv.z = saturate(hsv.z * valueStrength);
117 
118     // Riconverto in RGB per la visualizzazione
119     return float4(HSVToRGB(hsv), 1);
120 }

Come è possibile notare questa è la prima volta che nel codice appaiono più di una funzione, in questo caso le funzioni che verranno richiamate da un metodo devono essere definite prima dei metodo stesso.

Di seguito è visibile un possibile risultato di tale elaborazione:

In questo capitolo più che negli altri dedicati agli effetti in post-processo, analizzare tutte le possibili elaborazioni è praticamente impossibile, invito quindi il lettore a sperimentare liberamente cercando effetti particolari che catturino l'attenzione.