Home Page Home Page Articoli Texture Blending (Parte I)

Texture Blending (Parte I)

Come gestire gli effetti di blending tra texture. Multitexturing e Multipassing. Effect Files
Autore: Stefano Cristiano Livello:
Questi due termini insieme indicano quel procedimento attraverso il quale diversi “livelli” (layer in inglese) vengono fusi insieme (blending) per creare i più disparati effetti. Se avete mai usato un programma di grafica quale ad esempio Paint Shop Pro oppure PhotoShop sapete già di cosa sto parlando. In questi programmi ogni layer viene fuso con quelli che si trovano sotto di esso. Non esiste un unico modo di fondere due layer, ma una miriade infinita di tali modalità, che si possono ottenere combinando tra loro alcune operazioni basilari. In pratica nel texture blending si prendono due livelli, che possiamo tranquillamente immaginare come due texture, e li si combina attraverso un’operazione. Il risultato di questo “blending” (leggasi di questo “stage”) può essere usato per texturizzare un poligono, oppure può diventare uno dei due layer di base per un'altra operazione di blending. Come potete bene vedere, possiamo eseguire delle vere e proprie “catene” di blending, fondendo a due due a due i layer di una gerarchia.

Se usando ID3DXSprite vi siete divertiti a giocare con il parametro alpha di trasparenza del colore da passare a ID3DXSprite::Draw(…), non lo sapevate ancora ma stavate abilitando il texture blending. Il concetto stesso di blending è affine a quello di trasparenza, anzi potremmo dire che la trasparenza e la traslucenza sono dei “casi particolari” di blending. In generale quando vogliamo ottenere un certo effetto, prima ancora di mettere mani al nostro caro compilatore e dare comandi a DirectX, dobbiamo prendere carta e penna e scrivere la nostra cara equazione di blending. Ecco un esempio.



Questo è sicuramente il blending più semplice che esiste, il blending “additivo”. Si prendono i pixel a due a dure dalle texture in input e si somma algebricamente il loro valore. Quest’operazione viene fatta componente per componente, per il canale del rosso, per quello del verde e quello del blu. Se tale valore eccede il valore massimo ammissibile per un pixel (il bianco praticamente, dove tutte le componenti r, g, e b assumono il loro valore massimo) si effettua un “clamp” al bianco, cioè si evita che possa eccedere tale valore. In PseudoCodice (tralasciando problematiche di stretching se le due texture non hanno la stessa dimensione...):


for(int x =0; x < xSize; x++)
for(int y=0; y < ySize; y++)
{
out(x,y).r = Tex1(x,y).r+Tex2(x,y).r ;
out(x,y).g = Tex1(x,y).g+Tex2(x,y).g ;
out(x,y).b = Tex1(x,y).b+Tex2(x,y).b ;
out(x,y).a = Tex1(x,y).a+Tex2(x,y).a ;

if(out(x,y).r>1)
out(x,y).r = 1;

if(out(x,y).g>1)
out(x,y).g = 1;

if(out(x,y).b>1)
out(x,y).b = 1;

if(out(x,y).a>1)
out(x,y).a = 1;
}


Un altro blending potrebbe essere questo:



che si chiama (indovinate indovinate) blending “sottrattivo”, e fa l’esatto opposto del precedente. Una modalità di blending molto comune è anche la seguente:



detto blending di “modulazione”. Ogni pixel di una texture viene moltiplicato per quello dell’altro stage (quando dico questo intendo sempre componente per componente). Con questa operazione per esempio si può usare una texture per creare delle zone d’ombra. Il blending modulativi (in alcune sue varianti) è quello usato da praticamente tutti i motori 3D che usano la tecnica del lightmapping (illustrata nel capitolo sul 3D e già accennata quando si parlava degli utilizzi del texture packer). Tra questi ci sono tutti i motori della serie Quake1/2/3.

Queste tre operazioni effettuano blending solamente sui COLORI dei singoli pixel, ignorando completamente il valore dell’eventuale canale alpha. Inizia a delinearsi già una distinzione tra operazioni sul COLORE (colorop) e operazioni sui canali alpha (alphaop).

Potremmo utilizzare il canale alpha per modulare il colore dei nostri pixel. In questo modo possiamo riprodurre all’interno dei nostri programmi degli effetti di traslucenza con controllo “sul singolo pixel”. ID3DXSprite fa già questo automaticamente per i nostri sprite: provate a salvare le vostre immagini con degli effetti di trasparenza in un formato che preserva le informazioni sul canale alpha (per esempio TGA oppure PNG) e ve ne accorgerete. ID3DXSprite fa una cosa del genere:



Come potete ben vedere se il pixel in questione contiene zero nel canale alpha, il secondo termine di questa addizione sarà uguale a zero e quindi il pixel finale sara uguale al pixel già a schermo, ovverosia sarò “trasparente”. Se al posto di zero ci fosse 1 nel canale alpha del nostro pixel, le cose si invertirebbero, il primo membro scomparirebbe e il pixel finale sarebbe uguale al pixel letto così com’è dalla texture. Per tutti i valori intermedi tra zero ed uno, viene effettuata una media pesata dei due pixel in questione come potete ben vedere, ed il risultato è che il pixel sarà più o meno trasparente.

Multi Passing
La teoria del texture blending è sostanzialmente quella illustrata nel paragrafo precedente. Cercheremo ora di addentrarci meglio nell’attuale implementazione hardware di questa tecnologia. In verità con l’introduzione delle DirectX8 è stato creato un linguaggio simil-Assembler appositamente per specificare questo tipo di operazioni. Gli script creati in questo linguaggio sono chiamati Pixel Shader, e sono supportati in hardware dalle schede GeForce 3 e Radeon 8500 in su. Esistono anche diverse versioni di questo linguaggio, che a quanto pare verrà notevolmente potenziato con l’introduzione delle DirectX 9 e delle schede di nuova generazione. Ora anche NVidia se n’è uscita con i suoi CG Shaders, compatibili con DirectX e OpenGL, ma bisognerà tastarne la vera utilità.

Come viene effettuato il blending? Beh, la cosa più semplice da pensare è che viene letto il colore del pixel presente nel backbuffer, viene letto il pixel dalla texture, si effettua l’operazione e si rimette il nuovo pixel nel backbuffer, rendendolo pronto per un’altra eventuale operazione di blending. Il procedimento appena descritto si chiama Multi Passing. Il termine deriva dal fatto che un poligono viene disegnato disegnato più volte, in più “passate” per ottenere l’effetto voluto.

Voglio ricordare a tutti però che le letture e scritture nel backbuffer non sono “gratis” e che più pixel scriviamo in una scena, meno grande sarà il conto degli FPS nel nostro gioco. Se iniziamo a fare troppi “pass” il nostro engine potrebbe diventare “fillrate limited”, cioè limitato dal numero di pixel per secondo che il nostro acceleratore è in grado di gestire.

Multi Texturing
Per ovviare a questo problema, tutti i moderni acceleratori sono stati dotati di più di una “Texture Unit” e possono effettuare tali operazioni senza passare attraverso la scrittura nel backbuffer. In pratica le texture vengono messe in queste Texture Unit, si indica la tipologia e la sequenza delle operazioni da eseguire e verrà scritto nel backbuffer solamente il pixel risultante di questa “catena” di blending. Questa tecnica si chiama Multi Texturing, ed offre indubbi vantaggi di performance rispetto al Multi Passing.

Le cose ora però si complicano: innanzitutto ogni scheda ha un numero di texture unit differente, e non tutte le schede possono effettuare tutte le operazioni che ci possono venire in mente. Ci sono schede addirittura che supportano alcune operazioni in determinate texture unit ma non in altre. Potete ben capire come sia possibilissimo il nostro effetto possa funzionare sulla nostra scheda ma non su quella del nostro amico. In questi casi chiederemo aiuto agli “effect files”, come vedremo più avanti.

Immaginiamo che il nostro effetto “consumi” 4 layer diversi. Immaginiamo anche che la nostra scheda abbia solamente 2 texture unit. Che facciamo, rinunciamo a tutto? Certo che no, utilizziamo prima queste unit per i primi 2 “stage”, poi usando il multipassing, scriviamo i layer rimanenti, che “non entrano” nelle texture unit a nostra disposizione. L’unico problema è che NON sempre è possibile fare le stesse cose in multitexturing e multipassing. In generale facendo più “pass” si può quasi sempre ottenere l’effetto voluto ma non sempre è vero il contrario. Ci sono alcune equazioni di blending che diventano un casino se le volete fare in multitexturing. Ovviamente sta proprio lì la bravura del programmatore: scrivere degli effetti che sfruttano tutte le texture unit quando presenti e usano il multipassing solo lo stretto necessario.

Effect File
In teoria per impostare tutti i render state necessari per una catena di multipassing o multitexturing dovremmo smanettare con i vari IDirect3DDevice8::SetRenderState e IDirect3DDevice8::SetTexureStageState() e trilioni di flag. Per il bene di chi sta leggendo ho preferito da subito introdurre il concetto di “Effect File”, e quello associato di Shader.

Allora lo “Shader” non ha nulla a che fare per ora con i Pixel o Vertex Shader di cui avete tanto sentito parlare. In questo libro uno “shader” è semplicemente un insieme di flag di rendering che ci permettono di renderizzare il nostro poligono con l’effetto che desideriamo, ad esempio in trasparenza oppure fondendo diverse texture tra di loro, animando le coordinate texture ecc. Un effect files in due parole permette di impostare tutti i rendestate che volete in un file di testo a parte, staccato dal codice della vostra applicazione.

Senza perdere troppo tempo vi butto giù barbaramente un esempio:


Texture DiffuseTexture;
Texture BlendedTexture;

//Esempio di Texture Blending attraverso Multi Texturing
Technique MultiTexturing
{
Pass P0
{

AlphaBlendEnable = false;
PixelShader = NULL;

//STAGE 1 (primo layer)
Texture[0] = ;
ColorArg1[0] = Texture;
ColorOp[0] = SelectArg1;
AlphaOp[0] = Disable;

//STAGE 2 (secondo layer)
Texture[1] = ;
ColorArg1[1] = Texture;
ColorOp[1] = Add;
ColorArg2[1] = Current;
AlphaOp[1] = Disable;

//Disabilitiamo il prossimo stage
ColorOp[2] = Disable;
AlphaOp[2] = Disable;

}
}


Niente di più semplice: dichiariamo due variabili di tipo “Texture” come faremmo in C++ con IDirect3DTexture8, dichiariamo una “Tecnica” (vedremo cosa significa) e tra una parentesi graffa aperta e una chiusa inseriamo tutti i “pass” che ci interessano. In questo caso poiché il “pass” è solo uno, NON stiamo usando il MultiPassing (che si ha ovviamente quando i “pass” sono in numero strettamente maggiore di uno). Più precisamente in questo pass stiamo utilizzando due Texture Unit (che possiamo presupporre presenti sul 99% degli acceleratori in giro) per effettuare un semplicissimo blending additivo tra le due texture dichiarate in alto. Le prime due righe disabilitano l’alpha blending e i pixel shader (vedremo dopo).

Subito dopo riempiamo la prima Texture Unit (TU) impostando la nostra texture e dicendo a DirectX di prenderla e copiarla così com’è (Arg1 = TEXTURE, operazione = SELECTARG1). Ogni stage, come vediamo, contiene almeno due argomenti ed un’operazione da eseguire tra di essi. Dato che questa è un’operazione dove non serve il canale alpha lo disabilitiamo (AlphaOP[0]=Disable). In generale, quando vogliamo “bloccare” una catena di multitexturing, ci basta bloccare uno stage (ponendo ColorOp[NUMERO_STAGE]=Disable) e verranno disabilitati anche i successivi. Come ben vedete, disabiltiamo il terzo stage (ColorOp[2]=…;AlphaOp[2]=…) poiché stiamo utilizzando solo i primi due. Fate SEMPRE questa cosa, altrimenti potrebbero essere usate impostazioni provenienti da precedenti setup della pipeline (per un diverso shader per esempio) e l’effetto non sarà quello che vi aspettavate.

Ora prendiamo un’altra texture e la mettiamo nella seconda TU. Programmiamo questa TU per eseguire un’addizione (ADD) tra il risultato dello stage precedente (CURRENT) e la texture in questione (TEXTURE), esattamente il blending additivo di cui si parlava poc’anzi. Vi andrebbe di vedere come andrebbe fatta questa cosa in multi passing? Certo che vi va!


Technique MultiPassing
{
Pass P0
{
AlphaBlendEnable = false;
PixelShader = NULL;
Texture[0] = ;
ColorOp[0] = SelectArg1;
ColorArg1[0] = Texture;
ColorOp[1] = Disable;
}

Pass P1
{
AlphaBlendEnable = true;
SrcBlend = one;
DestBlend = one;
Texture[0] = ;

ColorOp[0] = SelectArg1;
ColorArg1[0] = Texture;
ColorOp[1] = Disable;
}
}


Allora, il primo pass è uguale a prima, ma utilizziamo solo la prima texture unit (disabilitiamo la seconda con ColorOp[1]=Disable). Prima del secondo pass invece ABILITIAMO l’alpha blending (importantissimo!) è impostiamo i fattori di blending (SrcBlend e DestBlend). Nella Texture Unit diciamo di prendere la texture così com’è e “fonderla” con l’alpha blending così com’è. Probabilmente tutto questo avrà creato un po’ di confusione a tutti, cerchiamo di seguire la cosa passo per passo:

Disabilitiamo l’alphablending: il contenuto del nostro pass (P0) viene scritto così com’è nel backbuffer. Prendiamo la texture DiffuseTexture e “l’appiccichiamo” sul poligono in questione. Successivament abilitiamo l’alpha blending. Quando abilitiamo l’alpha blending viene eseguita un’operazione del genere su tutti i pixel scritti:

PixelNelBackBuffer= PixelNelBackBuffer*DestFactor OPERAZIONE PixelNelloStage0*SrcFactor. Avendo impostato fattori di blending entrambi a uno questa equazione si riduce a:

PixelNelBackBuffer=PixelNelBackBuffer + PixelNelloStage0

Il “+” è l’operazione di default, ma volendo potremmo cambiarla con un “-“ o qualcos’altro (sempre stando attenti che sia supportata questa operazione dall’acceleratore in questione…).

Il concetto di “Tecnica” è molto importante negli Effect File. In pratica potremmo creare un effetto “SuperMegaStraFigo” che richiede 18 layers per essere renderizzato. Ci piacerebbe far girare questo effetto sia sulla nuovissima Geforce 944214 che su quelle più vecchie, così creiamo diverse “tecniche” cercando di sfruttare al massimo il numero di TU disponibili. Se scriviamo in ordine le tecniche a partire da quelle per le schede più potenti, scalando via via a quelle che hanno bisogno di molti pass, a livello di codice potremmo scorrerle e scegliere la più performante. Il codice per leggere dal disco un Effect File e cercare la prima tecnica valida è veramente banale:


ID3DXEffect* effect;
D3DXCreateEffectFromFile(GetDevice(),"data\\basic_effect.fx",&effect,0);
D3DXTECHNIQUE_DESC technique;
hr = effect->FindNextValidTechnique(NULL, &technique);
CHECKHR(hr);
effect->SetTechnique(technique.Index);


Per usarlo durante il rendering invece :


effect->SetTexture(_T("DiffuseTexture"), texture);
effect->SetTexture(_T("BlendedTexture"), texture_sopra);

UINT nPasses;
effect->Begin(&nPasses,0);
for(UINT i = 0; i < nPasses; i++)
{
effect->Pass(i);
lpDev->DrawPrimitive(...);
}
effect->End();


Come vedete a livello di codice siamo assolutamente “ignari” di quanto stia accadendo nel nostro effect file: l’unica cosa che dobbiamo sapere è quali parametri in input gli servono (in questo caso solo le texture,ma come vedremo possiamo passargli di tutto, anche matrici, colori ed altro). In questo modo abbiamo “decentralizzato” le proprietà di rendering: ci basterà mettere mano all’effect file (che è un file di testo, ricordiamolo) e non dovremo ricompilare nulla dopo averlo modificato. Ah, attenzione, almeno in questa versione di DirectX (8.1) le stringhe che indicano i parametri da passare all’effetto con SetXXXXX sono CASE SENSITIVE, quindi state attenti a maiuscole/minuscole.

L’ultimo passo è quello chi chiamare ID3DXEffect::Begin(), che ritornerà in nPasses ovviamente il numero di pass da eseguire. Impostiamo i pass uno alla volta con ID3DXEffect::Pass(…) e disegnamo i nostri poligoni. Alla fine di tutto attraverso ID3DXEffect::End(), vengono ripristinati tutti i renderstate modificati. In realtà potremmo ulteriormente ottimizzare quest’operazione passando il flag D3DXFX_DONOTSAVESTATE, ed in tale caso è meglio specificare nell’effect file TUTTI, ma proprio TUTTI i flags che servono alla corretta riuscita del nostro effetto, dato che non possiamo fare affidamento sui valori di “default”, come per esempio abbiamo fatto per l’operazione di “+” durante l’alpha blending. Volevo dire un’ultima cosa: se per esempio avete bisogno di disegnare gruppi di poligoni con lo STESSO effect, applicando però texture diverse o comunque cambiando solo alcuni parametri, potete farlo dopo aver impostato il pass corrente, senza bisogno di fare una nuova Begin/End che, come potete immaginare, diventa un collo di bottiglia se eseguita troppo spesso.Una cosa del genere:


effect->SetTexture(_T("DiffuseTexture"), texture);
effect->SetTexture(_T("BlendedTexture"), texture_sopra);

UINT nPasses;
effect->Begin(&nPasses,0);
for(UINT i = 0; i < nPasses; i++)
{
effect->Pass(i);
for (int j = 0; j < polys_num; j++)
{
effect->SetTexture(_T("DiffuseTexture"), texturePool[j]);
effect->SetTexture(_T("BlendedTexture"), texturePool[j]);
lpDev->DrawPrimitive(...);
}
}
effect->End();


Se avete voglia di fare delle prove in maniera visuale con i settaggi della pipeline di rendering, potreste usare l’applicazione MFCTex che è inclusa nell’SDK, che vi permette di fare quasi tutto quello che riguarda il multitexturing. Per il multipassing non c’è un’analoga applicazione, ma giusto per non lasciarvi a piedi ne ho fatta una io, l’esempio “BasicMultiPassing” vi permette di girare tra le varie operazioni di blending con la tastiera.

Facciamo ora una tabella con le modalità di blending più comuni, e come esse possano essere realizzate in multitexturing:



Il blending modulativo esiste in alcune sue varianti (MODULATE2X e MODULATE4X). Queste varianti fanno esattamente quello che il loro nome suggerisce, eseguono una moltiplicazione e raddoppiano o quadruplicano l’intensità del pixel finale. Il blending modulativo si usa principalmente per il lightmapping e un semplice MODULATE a volte tende a scurire troppo la scena, che deve poi essere schiarita con “trucchetti” di vario tipo (ad esempio con il fattore gamma discusso al capitolo di introduzione). Se abbiamo bisogno di fare queste operazioni in multipassing un’eventuale tabella potrebbe essere questa:

Voto medio articolo: 4.0 Numero Voti: 1
Stefano Cristiano

Stefano Cristiano

3D Coder. Sto lavorando ad un engine chiamato "Muse Engine" di cui potete leggere al mio sito http://pagghiu.gameprog.it Profilo completo

Articoli collegati

Integrare le Direct3D 8.1 con MFC Usando Visual Studio 6.0
In questo articolo mostreremo come creare una semplice applicazione SDI (Single Document Interface), in cui mostrare un oggetto tridimensionale in formato *.X e di farlo ruotare intorno all'asse Y.
Autore: Biagio Iannuzzi | Difficoltà:
Particle System
Brevi cenni su come implementare un particle system in DirectX
Autore: Stefano Cristiano | Difficoltà: | Voto:
Texture Blending (Parte II) Quake 3 Arena Shader System
Questo articolo è simile alla Parte I e ripete molti aspetti però tratta anche un pò degli shaders di Quake 3 Arena.
Autore: Stefano Cristiano | Difficoltà: | Voto:
Sprite
Come creare uno "sprite" e animarlo. Special thanks to Elevator2 per gli sprites di megaman
Autore: Stefano Cristiano | Difficoltà: | Voto:
Texture
Introduzione al concetto di texture, di coordinate texture. Come animare le coordinate texture
Autore: Stefano Cristiano | Difficoltà:
Grafica 2D in DirectX. Vertex Buffers
Introduzione alla grafica 2D. Utilizzo dei vertex buffers
Autore: Stefano Cristiano | Difficoltà: | Voto:
Debbugging di applicazioni DirectX
Consigli e strategie per il Debugging delle applicazioni DirectX.
Autore: Stefano Cristiano | Difficoltà: | Voto:
Framework DXGWrapper
Spiega la filosofia di fondo del framework ideato dal sottoscritto ed usato da tutti i samples degli articoli relativi a DirectX.
Autore: Stefano Cristiano | Difficoltà: | Voto:
Gamma Correction e Colori
Una trattazione breve ma con una base matematica che spiega in parole povere il problema del fattore gamma e i colori in generale
Autore: Stefano Cristiano | Difficoltà: | Voto:
Inizializzazione e schermo
Si parla dell'inizializzazione dello schermo e delle modalità video in DirectX e di come gestire gli eventi "lost device", come scrivere un Game Loop e alcune info sul VSYNC
Autore: Stefano Cristiano | Difficoltà: | Commenti: 1 | Voto:
Introduzione a DirectX
Introduzione al mondo DirectX , requisiti, tools necessari, configurazione dell’ambiente di sviluppo.
Autore: Stefano Cristiano | Difficoltà: | Commenti: 2 | Voto:
Copyright © dotNetHell.it 2002-2017
Running on Windows Server 2008 R2 Standard, SQL Server 2012 & ASP.NET 3.5