Tag Archives: texture

SpriteBatch per effetti speciali – parte 4

By Davide Luzzu – Webcast by Giuseppe Maggiore

Il (resto del) codice C#

// Generiamo un array di float che sono le altezze (y) dei punti di

controllo.

var tmp =

Enumerable.Range(0, controlPoints.Width)

// i ∈[0,controlPoints.Width-1]

.Select(i => i / (float)controlPoints.Width)

// x ∈[0,1]

.Select(x => sin(-4.0f * t + 32.0f * pi * sigmoid(x + 0.5f) * x)

* 0.1f + 0.5f)

// y é l’altezza di un punto di controllo. La convertiamo in formato

integrale.

.Select(y => (UInt16)(y * (UInt16.MaxValue – 1)))

.ToArray();

// Salviamo l’array di altezze nella texture.

controlPoints.SetData(tmp);

Per dare la forma al serpente definiamo i punti di controllo sulle x e sulle y. Per fare ciò ci serviremo delle funzioni sin e sigmoid, che abbiamo definito in precedenza. Definiamo un range da 0 a 256, e ne facciamo il rapporto tra 0 e 1 in modo che x ∈ [0,1]. Dato che x è nel range [0,1] possiamo definire la forma del serpente usando il seno. Tale forma varia in base al tempo ( …4.0f * t…) ed ha, ad ogni istante di tempo, una forma curva definita da “…32.0f * pi * sigmoid(x + 0.5f) * x…”; variando tali parametri determineremo un’alterazione a piacimento della forma o del tempo di esecuzione del serpente.

Una delle peculiarità derivanti da questa porzione di codice è il fatto che, ciò che viene disegnato, è ad una dimensione algoritmicamente, ma a due dimensioni visivamente; la x difatti è un seno, e ciò è sufficiente per disegnare il serpente. Ma la x è l’unica variabile da cui dipende effettivamente il controllo dello spazio. Quindi è monodimensionale.

A questo punto la y è libera, e la si utilizza per inserire i dati di illuminazione, quelli che avevamo previsto inserendo SurfaceFormat.Luminance16 nella definizione della texture. Convertiamo la y in intero a 16 bit Unsigned, e trasformiamo la struttura in un array. Quindi la variabile “tmp” è un array di interi a 16 bit Unsigned.

Non resta che inserire i dati dei punti di controllo nella Texture2D.

// Carichiamo la texture di sfondo.

var background = Content.Load(“background”);

/// Disegnamo la texture di sfondo. Questa operazione non dipende in alcun

modo da come poi disegneremo il serpente.

spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate,

SaveStateMode.SaveState);

spriteBatch.Draw(background, new Rectangle(0, 0, 800, 600), Color.White);

spriteBatch.End();

Per rendere l’applicazione più gradevole prevediamo uno sfondo, di nostro gradimento, che disegnamo con lo SpriteBatch. Ci preme ribadire che tale operazione non dipende in alcun modo da come disegneremo il serpente.

/// Disegnamo il serpente con il nostro shader in alpha blending sulla

scena precedente. L’operazione di disegno genera un serpente che passa

per i punti di controllo della texture “controlPoints”.

new Vector3[] { new Vector3(0, 1, 1}

.Select((color, index) => new {color, index})

.ToList().ForEach(snake =>

{

fx.Parameters["SnakeColor"].SetValue(snake.color);

spriteBatch.Begin(SpriteBlendMode.AlphaBlend,

SpriteSortMode.Immediate, SaveStateMode.SaveState);

fx.Begin();

pass(0, p => p.Begin());

spriteBatch.Draw(controlPoints, new Rectangle(0, 100 + 100 *

snake.index, 800, 400), Color.White);

pass(0, p => p.End());

fx.End();

spriteBatch.End();

});

base.Draw(gameTime);

}

Finalmente disegnamo il serpente!

Definiamo innanzittutto il colore con un vettore, un azzurro tenue.

Inseriamo il colore in ogni posizione di un array di Vector3 e lo modifichiamo con la tecnica dello Shader.

Il primo passo è impostare il parametro “SnakeColor” dello Shader con l’azzurro appena definito.

Iniziamo lo SpriteBatch con il metodo “Begin()” e avviamo il rendering dello shader con

fx.Begin();

che provoca, inoltre, la sostituzione automatica dello Shader di default nello SpriteBatch.

A questo punto non resta che aprire il passo zero di rendering con

pass(0, p => p.Begin());

e disegnamo sullo schermo la Texture2D.

Chiudiamo il passo e indichiamo che lo shader ha terminato la sua tecnica con

fx.End();

ricordandoci di chiudere anche le funzioni dello SpriteBatch con la funzione “End()”. Continue reading

Posted in Uncategorized | Tagged , , , , , | Leave a comment

SpriteBatch per effetti speciali – parte 1

By Davide Luzzu – Webcast by Giuseppe Maggiore

Abstract

Uno dei problemi più pressanti, nella creazione di mondi virtuali 3D, è rendere realistica la percezione del mondo. I dettagli stanno alla base di tutto. Più sono curati i dettagli più l’utente si troverà a proprio agio nell’ambiente 3D. Per meglio comprendere vediamo un esempio. Ammettiamo che si debba rappresentare un oggetto radioattivo. Come faremo capire all’utente che esso è radioattivo? Lo coloriamo di verde? Non basterebbe. Aggiungiamo una luce all’oggetto per indicare che esso reagisce in modo “strano” al contatto con l’aria circostante? Può andare, ma si può fare di più. Dovremo, in questo caso, creare una vera e propria fluorescenza dell’oggetto radioattivo.

Esistono svariati modi per creare una fluorescenza, ma, dal momento che si tratta di un dettaglio, non potrà avere un ruolo predominante nella scena 3D, cioè non può occupare troppe risorse.

Si presenta dunque la necessità di possedere uno strumento che esegua del codice a basso livello, (cioè che ci permetta un grande risparmio di memoria e risorse) chiamato genericamente Shader.

Potremmo dire che uno Shader è quella parte di codice che valuta come gli oggetti visuali devono rispondere alle luci; in altre parole con uno Shader determiniamo la distribuzione dei colori sugli oggetti visuali, in modo tale da ottenere effetti di tridimensionalità, fluorescenza, luminescenza ecc… Per meglio comprendere l’importanza degli Shader proviamo ad immaginare un mondo virtuale in cui venga rappresentata una tavola imbandita con ogni sorta di cibi, insomma un ambiente 3D abbastanza complesso. Se in tale ambiente non avessimo nessuna luce, niente ci vieterebbe di pensare non solo che non c’è tridimensionalità, ma che addirittura si tratta di un mondo virtuale vuoto; questo perché in assenza di luce lo percepiamo completamente nero.

Al contrario se avessimo una luce intensissima lo percepiremo completamente bianco.

La soluzione dunque risiede nel corretto bilanciamento delle luci e delle ombre.

Quello che presentiamo in questo articolo è un Pixel Shader HLSL ( High Level Shading Language ), e ci servirà appunto per il controllo di alcuni effetti di luce, applicati ad una texture.

Uno Shader in HLSL è formato da una technique (tecnica), che a sua volta è formato da vari pass (passi). Nel pass vengono specificate quali funzioni devono essere utilizzate, e a quale tipo di shader si intende fare riferimento (Pixel/Vertex Shader).

Figura 1 Screenshot dell’applicazione.

Struttura di un nuovo progetto

Ogni progetto XNA 3.0 possiede una sotto-struttura logica (cioè dove mettere i dati), chiamata Content, nella quale dovremo copiare il file (*.fx) che contiene il codice dello Shader. La copia può avvenire attraverso una semplice operazione di dragging.

E’ importante inserire i dati nella struttura apposita Content, poiché essa contiene delle References statiche alle impostazioni di libreria, ed esse ci consentiranno, senza alcuno sforzo, il caricamento dei dati (audio, effetti, textures…), utili al nostro progetto.

Figura 1 References statiche contenute nella struttura logica Content.

L’approccio che seguiremo per la definizione del progetto è bottom-up, cioè definiremo prima lo shader, poi come si inserisce nel contesto dell’applicazione, ed infine come si controlla e si visualizza con un esempio.

L’idea è quella di creare un serpente dal colore fluorescente, la cui forma è definita solo in alcuni punti, detti punti di controllo.

Figura 2 Esempio di Punti di Controllo nel serpente. I punti, in questo caso, sono 13 e sono inseriti nel range [0,1].

Tali punti di controllo verranno definiti nel codice. Lo Shader dovrà contenere una funzione generica, che imposti una texture per alcuni punti di controllo definiti altrove. L’intera forma del serpente è derivata dall’interpolazione delle posizioni dei punti di controllo.

Il nostro primo obbiettivo è dunque creare uno Shader e copiarlo nella struttura logica Content.

Info aggiuntive

Per quanto riguarda luci e ombre, è necessario precisare che esistono due tipi di ombre:

ombre sugli oggetti: danno la sensazione di tridimensionalità ( una sfera non si distinguerebbe da una semplice circonferenza se non avesse una ombreggiatura )
ombre riportate: rappresentano l’interazione tra gli oggetti visuali, e vengono chiamate ombre volumetriche.

Per quanto riguarda gli Shader, abbiamo fatto riferimento a due diversi tipi:

Pixel Shader (o Fragment Shader ) valuta l’interazione della luce con ogni singolo pixel che colora l’oggetto visuale, ed ha un costo computazionale particolarmente elevato. Tuttavia tale shader ha un vantaggio intrinseco. Il pixel shader è legato alla risoluzione con cui si sta visualizzando l’applicazione; più la densità di pixel nell’applicazione è elevata più lo shader è preciso e più il costo computazionale aumenta. Al contrario è banale diminuire la risoluzione, e dunque il peso che tale shader possiede nell’esecuzione.

Vertex Shader valuta l’intensità di luce su ogni vertice delle mesh dell’oggetto visuale, per cui il colore risultante sarà dato da:

Media dei colori tra:

Colore della mesh (risultante dall’interpolazione dei colori dei tre vertici)
Colore della luce (diffuse color)

Moltiplicazione del colore medio per l’intensità di luce sulla mesh

Uno Shader HLSL permette di scrivere complessi calcoli grafici che possono essere eseguiti con grande rapidità dalla GPU, poiché sono istruzioni di basso livello.

Queste istruzioni vengono impartite direttamente alla scheda video, pertanto è necessario assicurarsi che essa supporti tali istruzioni.

Uno Shader HLSL, fortunatamente, ha una sintassi C-like style, ed è abbastanza semplice da comprendere; le operazioni che supporta sono le comuni operazioni aritmetiche, più gran parte delle comuni funzioni trigonometriche. Inoltre, per le espressioni di controllo, vale la stessa sintassi del C; per i booleani si usano gli stessi operatori: &&, ||, , ==, !=, =.

La versione dello Shader utilizzata per questo esempio è la 2_0, che permette solitamente una maggiore ottimizzazione del codice nella traduzione in assembly. Continue reading

Posted in Uncategorized | Tagged , , , , , | Leave a comment