In questo articolo analizzeremo due argomenti molto importanti del webgl: gli Shader e le prime due Funzioni di Inizializzazione.

Queste ultime vengono eseguite solo una volta allo start del progetto e hanno lo scopo di creare,caricare e inizializzare valori, strutture e file che verranno utilizzati durante l’esecuzione dell’applicazione. Elenchiamole brevemente:

-InitGL()
-InitShader()
-InitBuffer()

Ora spiegheremo gli obiettivi di ogni funzione,  traslasciando momentaneamente alcuni elementi che al momento risulterebbero troppo “pesanti da digerire”.

Function:InitGL()

Descriviamo ora questa funzione. Essa ha svariati compiti:
1.Controllare che esista un canvas con un contesto su cui lavorare
2.Ricavare le dimensioni del rettangolo di visualizzazione e salvarsi tale informazioni
3.Inizializzare la scena prima del disegno settando alcune proprietà.

Ora postiamo il codice della funzione InitGL che useremo nell’esempio così da poter fare un confronto:

var gl;
function initGL(canvas){
try {
gl = canvas.getContext(“experimental-webgl”);
gl.viewportWidth = canvas.width;
gl.viewportHeight = canvas.height;
} catch(e) {
}
if (!gl) {
alert(“Impossibile Inizializzare WebGL”);
}
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clearDepth(1.0);
gl.enable(gl.DEPT H_TEST);
gl.depthFunc(gl.LEQUAL);
}

La variabile gl viene definita fuori dalla funzione in modo tale che sia visibile in ogni parte del progetto ( quindi in ogni funzione), quindi è una variabile globale. In essa noi salveremo il Contesto, cioè l’orchestratore che ci permetterà di disegnare. La funzione GetContext ha proprio lo scopo di farsi ritornare il contesto, prendendo come argomento una stringa particolare, “experimental-webgl”: esso è il valore di default del contesto, quindi per il momento lasciamolo così come è. Se il sistema riesce a recuperare tale informazioni( in caso contrario, stampa un messaggio di errore sullo schermo tramite alert e si blocca), si salva in due attributi le dimensioni di altezza e larghezza del rettangolo, che verranno poi utilizzati per successivi scopi.

Terminato ciò, notiamo quelle quattro funzioni apparentemente complesse: essa settano alcune proprietà base che ora analizzeremo brevemente:

Nome Funzione
gl.clearColor(0.0, 0.0, 0.0, 1.0) Il colore base della scena quando non c’è nulla. I primi tre valori rappresentano la codifica RGB del colore nero, il quarto numero la luminosità. Tale configurazione si riferisce al nero opaco
gl.clearDepth(1.0) Pulisci la scena ( diventa tutto nero)
gl.enable(gl.DEPT H_TEST) Abilita il Buffer Depth (al momento è un concetto piuttosto complesso, per il momento tralasciamo, diciamo che è un buffer dove le schede grafiche salvano la prodonfità ( componenet z) dei pixel)
gl.depthFunc(gl.LEQUAL) Se ci sono oggetti a distanza ravvicinata che coprirebbero oggetti lontani, questi ultimi non vengono oscurati.

Prima di parlare della funzione InitShader(), sarà meglio prima introdurre cosa sono gli Shader.

Gli Shaders
Essi sono importanti elementi che riguardando la grafica in generale; solitamente sono porzioni di codice che vengono “appiccicati” a determinati oggetti e hanno la capacità di modificare alcuni parametri degli elementi a cui si riferiscono:per esempio se il sole colpisce una sfera, la superficie di tale elemento rifletterà il fascio luminoso a seconda delle caratteristiche del pallone: qui entra in gioco gli shaders, che alterano colore,movimento,illuminazione od altro ancora a seconda della situazione. Il vantaggio di tali elementi è che operano sulla scheda video, per cui tolgono lavoro alla Cpu.
A seconda dell’uso, esistono diversi tipi di shader, brevemente:

1.Vertex Shader:riguarda ciascun vertice della scena e permette di cambiare per ognuno, colore,illuminazione,…
2.Geometry Shader: aggiunge nuovi vertici alla scena, inserendo nuovi dettagli alla scena.
3.Pixel Shader:lavora direttamente sui pixel dell’immagine (già rasterizzata, cioè convertita da descrizione vettoriale ad immagine composta da pixel). Utile per inserire texture.
Capito cosa sono,iniziamo ad analizzare l’initShader()

Function:initShader()

Descriviamo ora questa funzione. Essa ha svariati compiti:
1.Farsi ritornare gli Shader descritti dentro la pagina
2.Crea Programma e ci attacca gli shader
3.Abilito Programmia e setto alcune Matrici

var shaderProgram;
function initShaders() {
var fragmentShader = getShader(gl, “shader-fs”);
var vertexShader = getShader(gl, “shader-vs”);
shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
alert(“Non posso inizializzare gli shader. Devi avere un browser ce abiliti webgl tipo Chromium”);
}
gl.useProgram(shaderProgram);
shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram,”aVertexPosition”);
gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);
shaderProgram.pMatrixUniform = gl.getUniformLocation(shaderProgram, “uPMatrix”);
shaderProgram.mvMatrixUniform = gl.getUniformLocation(shaderProgram, “uMVMatrix”);
}

Le chiamate getShader() servono per ricavare gli shader dal nostro progetto, più precisamente dall’HTML : infatti successivamente creeremo gli shader e gli inseriremo direttamente nella pagina ( potremmo anche crearli direttamente nella funzione ma così è più veloce) .
Al momento lo shader “shader-fs”, ccolora di bianco ogni pixel per il quale viene chiamato, mentre “shader-vs” decide dove posizionare il vertice;Tali valori vengono poi inseriti nel gl. Caricati , creiamo un programma, un contenitore di shader che trasformano i dati grezzi in elementi grafici. Questo sarà poi attacco al contesto 3D e compilato nella scheda video. Ad esso attaccheremo gli shader
da gestire.Successivamente controllo che l’inizializzazione sia stata
fatta in modo corretto..Ora recupero dal codice dello shader il riferimento ad un attributo, cioè un parametro input passato al vertex shader. Questo attributo si chiama vertexPositionAttribute e conterrà la posizione del vertice corrente.
Infine inizializziamo due variabile uniformi, dette uPMatrix e uMVMatrix, che vengono salvate nel program.Queste due matrici vengono usate dallo shader-vs per calcolare in fase di disegno la posizione del vertice tenendo conto della posizione dell’oggetto e del punto di vista, tale valore verrà poi salvato in aVertexPosition.

Function: Getshader

function getShader(gl, id) {
var shaderScript = document.getElementById(id);
if (!shaderScript) {
return null;
}
var str = “”;
var k = shaderScript.firstChild;
while (k) {
if (k.nodeType == 3) {
str += k.textContent;
}
k = k.nextSibling;
}
var shader;
if (shaderScript.type == “x-shader/x-fragment”) {
shader = gl.createShader(gl.FRAGMENT_SHADER);
} else if (shaderScript.type == “x-shader/x-vertex”) {
shader = gl.createShader(gl.VERTEX_SHADER);
} else {
return null;
}
gl.shaderSource(shader, str);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
alert(gl.getShaderInfoLog(shader));
return null;
}
return shader;
}

La funzione preleva dall’HTML gli shader utlizzando la classica getElementById , a seconda del tipo ( se vertex o shader),crea lo shader appropriato e dopo lo compila: se tutto va correttamente,ritorna lo shader. In caso di errore, stampa un messaggio tramite la funzione alert.


Shader in Pagina

Fino ad ora abbiamo detto che gli shader sono scritti direttamente nell ‘HTML per questioni di velocità, ma al momento non li abbiamo ancora ancorati alla pagina, come si fa?

Semplice, per vedere il nostro esempio,dovremmo incollare questo codice nella pagina HTML:

<script id=”shader-fs” type=”x-shader/x-fragment”>
#ifdef GL_ES
precision highp float;
#endif
void main(void) {
gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
}
></script>

<script id=”shader-vs” type=”x-shader/x-vertex”>
attribute vec3 aVertexPosition;
uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;
void main(void) {
gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
}
</script>

Ricordiamo che lo shader-fs si occupa del colore, shader-vs della posizione dei vertici:i valori dell’attributo id di ogni script rappresenta la stringa chiave con cui recuperiamo lo shader tramite la funzione getshader.

Al momento abbiamo messo molta carne sul fuoco, per cui propongo di interrompere ora l’articolo: la prossima parte descriverà le ultime due funzioni che useremo e completeremo il nostro primo esempio.
Il materiale è disponibile nella sezione documenti del lab.pralevis.com.

Grazie per l’attenzione, alla prossima.

Andrea