Friday, December 18, 2009

...si inizia!


A differenza di altre tecnologie per fare 3d su web, WebGL non richiede l'istallazione di plugin. 
Per poterne vedere le funzionalita' bisogna avere (per ora) delle particolari versioni dei browser utilizzati... ed una scheda grafica con accelerazione 3d.
In parole povere cosa vuol dire questo: 
  • Se si possiede Internet Explorer bisogna rassegnarsi: per ora non c'e' supporto
  • Se si usa Firefox, WebGL funziona nelle release sperimentali (le nightly, o meglio o compilati giornalieri che presentano le ultime features implementate ma poco testate) ed esistono le versioni per Windows, Mac OS X, e Linux.
  • Se si usa Safari, WebGL funziona solo sui Mac.
  • Se si usa Chrome, come il sottoscritto, si puo' trovare il supporto per WebGL nel canale dello sviluppo, dove si possono scaricare le versioni sperimentali.


Non staro' qui a spiegare o descrivere come far funzionare i vari browser... le istruzioni le trovate nei siti del browser specifico! 
Non lo faccio un po' per pigrizia (non ho voglia di tradurre e capire i vari settaggi da fare) e poi anche perche' mi voglio concentrare sull' aspetto piu' divertente: lo sviluppo! Ma vi rendete conto che prima c'era bisogno di un ambiente di compilazione ed ora viene tutto eseguito in tempo reale?!? E' vero, per cose complesse, per ora, il rischio e' quello di perdere il controllo, pero' questo, per me, e' un vincolo che verra' risolto presto.



In giro troverete anche molte demo fatte con le quali potrete sperimentare gia' alcune funzionalita' di webgl... ma senza che ve le abbia spiegate io, quindi iniziamo subito con il primo esempio, non molto accattivante ma introduce bene le funzionalita' proposte da questa tecnologia. Daro' per scontato che si abbiano gia' alcuni rudimenti sulla programmazione web, sulla scrittura di codice html e come funziona javascript (penso che non tocchero' l'argomento css...), sulla matematica usata(matrici, vettori, etc...).



<body onload="webGLStart()">
<canvas id="lesson01-canvas" style="border: none;" width="500" height="500">
</canvas>
</body>

Questo e' tutto l'html che, per ora, bisogna scrivere: tutto il resto e' javascript, o meglio webgl!

Ora vediamo com'e' implementata la funzione javascript webGLStart():


function webGLStart() { var canvas = document.getElementById("lesson01-canvas"); initGL(canvas); initShaders() gl.clearColor(0.0, 0.0, 0.0, 1.0); gl.clearDepth(1.0) gl.enable(gl.DEPTH_TEST); gl.depthFunc(gl.LEQUAL); initBuffers(); setInterval(drawScene, 15); }


In ordine la funzione esegue:
  • ottengo il riferimento all'oggetto canvas del mio documento html: questo e' il mio pannello dove "disegnero'" con le webgl
  • inizializzo l'ambiente webgl nel pannello
  • inizializzo gli shaders 
  • dico che quando "pulisco" il pannello lo voglio nero
  • pulisco tutto quello che visualizzo
  • abilitiamo il depth_test e la funzione da usare
  • inizializziamo qualche buffer, che conterrano informazioni sulle primitive che disegneremo
  • impostiamo l'intervallo della chiamata alla funzione drawscene ogni 15 millisecondi


Come funzionano initGL ed initShaders lo vedremo piu' avanti e ci permetteranno di capire meglio come "lavora" la pagina. Adesso soffermiamoci dulle funzioni initBuffers e drawscene.


var triangleVertexPositionBuffer; var squareVertexPositionBuffer;


Prima di vedere la funzione dichiariamo due variabili che funzionano da buffer (in realta' webgl non usa delle variabili globali separate per ogni progetto, ma viene adottata questo approccio per semplificare le cose).


function initBuffers() { triangleVertexPositionBuffer = gl.createBuffer();


Creiamo il buffer che conterra' la posizione dei vertici, che non sono altro che punti in uno spazio 3d che ci permetteranno di disegnare la nostra scena. Il buffer per ora occuopa veramente poco spazio nella nostra scheda grafica: se mettiamo la posizione dei vertici nel nostro codice di inizializzazione e, successivamente, ridisegnamo e' come se dicessimo a webgl "disegna le cose che ti ho detto prima", e cio' ci permette di rendere il nostro codice veramente efficente(soprattutto se i vertici da pochi diventano decine o centinaia di migliaia)!


gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer); 


Qui specifichiamo che ogni operazione successiva che usera' il buffer operera' su quello specificato.


var vertices = [ 0.0, 1.0, 0.0, -1.0, -1.0, 0.0, 1.0, -1.0, 0.0 ];


Definiamo la posizione dei nostri vertici tramite una lista javascript. 


gl.bufferData(gl.ARRAY_BUFFER, new WebGLFloatArray(vertices), gl.STATIC_DRAW);


Ora creiamo un oggetto WebGLFloatArray basato sulla nostra lista javascript e diciamo a webgl di usarla per riempire il buffer corrente, ovvero il  triangleVertexPositionBuffer


triangleVertexPositionBuffer.itemSize = 3; triangleVertexPositionBuffer.numItems = 3;

Ora settiamo due proprieta' del buffer, che ci serviranno di seguito per dire che i 9 elementi presenti nel buffer rappresentano 3 separate posizioni dei vertici(numItems),  ognuno dei quali e' definito da tre numeri (itemSize).
Ora che abbiamoi definito il buffer per il triangolo, facciamo lo stesso per il quadrato.

squareVertexPositionBuffer = gl.createBuffer(); 
gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer); 
vertices = [ 1.0, 1.0, 0.0, -1.0, 1.0, 0.0, 1.0, -1.0, 0.0, -1.0, -1.0, 0.0 ]; gl.bufferData(gl.ARRAY_BUFFER, new WebGLFloatArray(vertices), gl.STATIC_DRAW); squareVertexPositionBuffer.itemSize = 3; squareVertexPositionBuffer.numItems = 4; }


Ora vediamo la drawscene():


function drawScene() { gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);


Creiamo il canvas e prepariamolo per disegnarci qualcosa.


perspective(45, 1.0, 0.1, 100.0);

Qui settiamo la prospettiva con la quale visualizzare la scena. Di default webgl disegna gli oggetti che stanno in profondita' della stessa grandezza di quelli che stanno in primo piano, ovvero usa una vista ortogonale. Per ottenere un effetto diverso, ovvero oggetti sullo sfondo piu' piccoli rispetto a quelli in primo piano, dobbiamo specificare i settaggi della prospettiva usata. In questo caso stiamo dicendo che il field of view, ovvero l'asse verticale, e' di 45 gradi; il ratio(la proporzione) fra altezza e larghezza del nostro canvas e' 1:1, che non vogliamo vedere oggetti che distano piu' di 0.1 unita' dalla nostra viewport e con vogliamo vedere oggetti che distano piu' di 100 unita' (nb: a cosa corrisponde una unita'? cos'e' la viewport?con che orientamento ruoto?)


loadIdentity();


Ora specifichiamo il centro della scena. Con le OpenGL, quando si disegna una scena lo si fa specificando, per ogni oggetto la posizione e rotazione attuale, es."trasla di 20 unita' in avanti, ruota di 32 gradi e disegna un robot”. Questo e' molto utile, perche' si puo' incapsulare "disegna il robot" in una funzione.
Le informazioni sulla posizione e sulla rotazione sono gestite con una matrice, chiamata model-view matrix, e la funzione loadIdentity la setta con una mtrice identita' , in modo da essere pronti per moltiplicare traslazioni e rotazioni su essa.(ripeto, do per scontato anche la conoscenza di queste nozioni, me se ci sono domande...)


mvTranslate([-1.5, 0.0, -7.0]);


Una volta posizionati al centro del nostro spazio 3d, con la chiamata loadidentity, ci muoviamo 1.5 unita' a sinistra(ci muoviamo in senso negativo rispetto all'asse x, il primo valore specificato), e ci muoviamo di sette unita' in senso negativo rispetto all'asse z(il terzo valore specificato). Per tornare alla matematica, questa non fa altro che applicare una matrice di traslazione alla matrice identita'.


gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer); 
gl.vertexAttribPointer(vertexPositionAttribute, triangleVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);


Usiamo gl.bindBuffer per specificare il buffer da usare e chiamiamo il codice che deve operare su esso. 
Qui selezioniamo un triangleVertexPositionBuffer, che dice a webgl che i suoi valori devono essere usati come posizioni dei vertici, usando itemSize per specificare che ogni vertice e' definito da tre numeri(x,y,z).


setMatrixUniforms();


Tutte le matrici non sono proprieta' di WebGL, o meglio, non sono create inizialmente con questa tecnologia. Quando, per esempio, si esegue mvTranslate , lo si fa nell'ambiente JavaScript.  setMatrixUniforms trasforma il tutto nell'ambiente webgl.
Una volta fatto, webgl ha un array di numeri da usare come posizioni di vertici, e conosce la nostra matrice di trasformazione.   


gl.drawArrays(gl.TRIANGLES, 0, triangleVertexPositionBuffer.numItems);


Ecco la chiamata principale che disegnera' la nostra primitiva! Il primo parametro dice che disegnero' dei triangoli, in questo caso uno, il terzo dice di usare il buffer creato prima, il secondo che deve partire dallo 0imo elemento per finire con il numItems-imo elemento(del buffer).
Ora facciamo il tutto per il quadrato.


mvTranslate([3.0, 0.0, 0.0])


Ricordiamoci che ci stiamo muovendo rispetto all'ultima matrice di trasformazione(quela che ci faceva spostare -1.5,0,-7!!!)


gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer); 
gl.vertexAttribPointer(vertexPositionAttribute, squareVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
setMatrixUniforms();
gl.drawArrays(gl.TRIANGLE_STRIP, 0, squareVertexPositionBuffer.numItems);


TRIANGLE_STRIP? Ma non era un quadrato? Beh, un quadrato e' in realta' fatto da due triangoli, no? In casi piu' complicati, come l'approssimazione di superfici, e' meglio usare come primitiva il triangolo, piuttosto che il quadrato,


}


Fine di drawscene!!!


Prossimamente aggiungero' file di esempio, mettero' link a risorse esterne.
Mi sono reso conto che ho dato per scontato davvero molte cose, dunque se avete domande... non esitate a farmele!





No comments:

Post a Comment