» it.Tutorial Effects - Pixels Adiacenti
This site relies heavily on Javascript. You should enable it if you want the full experience. Learn more.

it.Tutorial Effects - Pixels Adiacenti

English | Mandarin | Japanese

The original english version of this page is newer and may contain information this translation does not have! Click here to view the english version.

INDICE: it.Of Effects and Shaders
Precedente: Le Coordinate della Texture
Prossimo: Textures Multiple


Alcuni filtri per l'elaborazione dell'immagine partono dall'idea che per decidere quale sia il colore di un pixel si debbano prendere in considerazione i pixels adiacenti (in alto, in basso, a destra, a sinistra e possibilmente anche in diagonale) .
Il problema principale per applicare questi filtri con un pixelshader sta nell'accedere ai pixels adiacenti partendo dalle coordinate di un pixel avendo la texture come sistema di riferimento.
Dalla funzione pixelshader non si può semplicemente accedere al pixel a sinistra: per prima cosa si deve capire quali siano le sue coordinate usando la texture come sistema di riferimento, per poterlo campionare correttamente.

Partendo dalla dimensione originale di una texture (Altezza/Larghezza, WIDTH/HEIGHT) si può calcolare la distanza tra la coordinata di un pixel e quella di un pixel adiacente in orizzontale nell'unità di misura basata sulla texture. Posto, ad esempio, che coordinata 1 = Larghezza (WIDTH) possiamo dire che la distanza tra un dato pixel ed un pixel adiacente sia 1 / WIDTH (pensate a più textures con dimensioni diverse). La dimensione di un pixel in relazione alla texture può essere indicata così:

  float2 pixelSize = 1 / float2(WIDTH/HEIGHT)

Usando pixelSize.xy si può accedere quindi ai pixel adiacenti aggiungendo/sottraendo questo valore alle coordinate del pixel.

Individuare i Bordi

Un esempio potrebbe essere il filtro per l'individuazione dei bordi in cui per ogni pixel prendiamo i suoi adiacenti a destra e sinistra, calcoliamo quanto differiscano tra di loro e controlliamo se il valore assoluto del risultato sia maggiore di un certo limite. Se lo è, il pixel viene colorato di bianco, altrimenti di nero.

float2 PixelSize;
float Threshold = 0.2;
float4 PS(vs2ps In): COLOR
{
    //l'offset delle coordinata delle texture con la coordinata verticale impostata a 0
    float2 off = float2(PixelSize.x, 0);
 
    //campiona i pixels adiacenti a sinistra e destra
    float4 left = tex2D(Samp, In.TexCd - off);
    float4 right = tex2D(Samp, In.TexCd + off);
 
    if (abs(ConvertToGray(left).x - ConvertToGray(right).x) > Threshold)
      return 1;
    else
      return float4(0, 0, 0, 1);
}

Ci sono due cose da segnalare nel codice qui sopra:

  • La variabile off è solo una variabile temporanea che definiamo per essere usata nelle righe 39 e 40 che viene prima sottratta per trovare il pixel adiacente a sinistra e poi aggiunta per trovare quello a destra.
  • L'uso dello swizzle .x nella riga 42: la funzione ConvertToGray restituisce un float4, ma dato che per i calcoli successivi abbiamo bisogno di un singolo valore luminosità, in questo punto accediamo solo al primo dei quattro componenti del float4 per comparare la luminosità di due pixels adiacenti.

Per migliorare l'individuazione dei bordi è necessario eseguire l'operazione anche in verticale. Così ora campioniamo anche i pixel in alto ed in basso:

//'offset delle coordinata delle texture con la coordinata orizzontale impostata a 0
off = float2(0, PixelSize.y);
//campiona i pixels adiancenti in alto ed in basso
float4 upper = tex2D(Samp, In.TexCd - off);
float4 lower = tex2D(Samp, In.TexCd + off);

a questo punto dobbiamo tenere in considerazione questi pixel assieme ai pixel a destra ed a sinistra prima di calcolare il colore finale

if (abs(ConvertToGray(left) - ConvertToGray(right)).x > Threshold
||
abs(ConvertToGray(upper) - ConvertToGray(lower)).x > Threshold)
    ...

dove ||, nel linguaggio hlsl, è la booleana OR, vale a dire che coloriamo il pixel di bianco se la differenza tra i pixels a destre e sinistra supera una certa soglia OR questo succede tra i pixels in alto ed in basso.

Sfocatura

Anche una sfocatura può essere realizzata in questa maniera. Migliore deve essere il risultato, maggiore deve essere la quantità di pixels adiacenti presi in considerazione. Dato che il numero dei campioni per ogni pixel di una texture è limitato a 16, una buona sfocatura si ottiene eseguendola in due passaggi. Prima orizzontalmente, poi si prende il risultato e si ripete la sfocatura in verticale. Cominciamo con la sfocatura orizzontale.

L'idea è quella di campionare una serie di pixels adiacenti, sommare i loro colori, mentre si valuta il peso della loro influenza in rapporto alla loro distanza dal pixel scelto. Il pixel "target" ha quindi il maggior peso, mentre i 2 pixel all'estrema destra e all'estrema sinistra hanno un peso minore. Il risultato deve quindi essere riportato nell'intervallo 0...1 facendo la media di tutti i valori sommati.

float2 PixelSize;
float4 PSHorizontalBlur(vs2ps In): COLOR
{
    float4 sum = 0;
    int weightSum = 0;
    //i pesi dei pixels adiacenti
    int weights[15] = {1, 2, 3, 4, 5, 6, 7, 8, 7, 6, 5, 4, 3, 2, 1};
    //prendiamo 15 campioni
    for (int i = 0; i < 15; i++)
    {
        //7 a sinistra, il "target", 7 a destra
        float2 cord = float2(In.TexCd.x + PixelSize.x * (i-7), In.TexCd.y);
        //i campioni vengono pesati in base alla relazione con il pixel "target"
        sum += tex2D(Samp, cord) * weights[i];
        //mentre il loop prosegue, sommiamo i vari pesi
        weightSum += weights[i];
    }
    sum /= weightSum;
    return float4(sum.rgb, 1);
}

Per effettuare quest'operazione in verticale, dovremmo duplicare la funzione PS(), chiamarla PSVerticalBlur() e cambiare il codice così che calcoli la sfocatura in verticale:

float4 PSVerticalBlur(vs2ps In): COLOR
{
    ...
        //7 sopra, il "target" e 7 sotto
        float2 cord = float2(In.TexCd.x, In.TexCd.y + PixelSize.y * (i-7));
    ...
}

Ora che esistono due funzioni pixelshader sono necessarie anche due tecniche:

technique THorizontalBlur
{
    pass P0
    {
        PixelShader  = compile ps_2_0 PSHorizontalBlur();
    }
}
 
technique TVerticalBlur
{
    pass P0
    {
        PixelShader  = compile ps_2_0 PSVerticalBlur();
    }
}

In questo modo si possono usare le due tecniche alternativamente, ma come detto prima, per un risultato migliore si dovrebbe applicare il risultato della sfocatura orizzontale su quella verticale. Ci saranno altri tutorial, dopo il prossimo capitolo, sugli effetti con più passaggi (MultiPass Effects).

Queste sono le basi per l'elaborazione delle immagini basata su pixels adiacenti, ma c'è tutta una serie di filtri simili comunemente chiamati filtri di convoluzione/kernel. Con quello che dovresti aver imparato finora, puoi essere in grado di proseguire in questa direzione da solo/a. Vedi qui e qua per dettagli ed esempi.


Prossimo: Textures Multiple
Precedente: Le Coordinate della Texture
TOC: it.Of Effects and Shaders

anonymous user login

Shoutbox

~14d ago

~17d ago

joreg: The Winter Season of vvvv workshops is now over but all recordings are still available for purchase: https://thenodeinstitute.org/ws23-vvvv-intermediates/

~23d ago

schlonzo: Love the new drag and drop functionality for links in latest previews!

~1mth ago

joreg: Workshop on 29 02: Create Sequencers and Precise Clock Based Tools. Signup here: https://thenodeinstitute.org/courses/ws23-vvvv-08-create-sequencers-and-precise-clock-based-tools-in-vvvv-gamma/

~1mth ago

joreg: Workshop on 22 02: Unlocking Shader Artistry: A Journey through ‘The Book of Shaders’ with FUSE. Signup here: https://thenodeinstitute.org/courses/ws23-vvvv-12-book-of-shaders/

~2mth ago

joreg: Talk and Workshop on February 15 & 16 in Frankfurt: https://visualprogramming.net/blog/vvvv-at-node-code-frankfurt/

~2mth ago

woei: @Joanie_AntiVJ: think so, looks doable