Thursday, December 31, 2009

Buon 2010

http://code.google.com/p/webgl-it/downloads/list

Ho aggiunto un nuovo pacchetto che contiene anche la seconda lezione che aggiunge un po' di colore alla scena.

Pensavo nell'anno nuovo di sperimentare qualcosa a piu' alto livello, come le c3dl http://www.c3dl.org/ e di utilizzare un ambiente di sviluppo un po' piu' "professionale" dei semplici file html e js, come Google App Engine, e magari provare anche a fare qualche RIA con le nuove GWT(traduzione simultanea: provare a fare qualche applicazione web con gli strumenti usati da Google...).
Ormai sul web hanno migrato quasi tutte le applicazioni desktop, un'applicazione 3d manca: magari sarebbe bello iniziare con la migrazione webgl di Teddy di Takeo Igarashi( http://www-ui.is.s.u-tokyo.ac.jp/~takeo/teddy/teddy.htm ).
Vabbe'... per ora un augurio di Buon 2010!

Sunday, December 27, 2009

Consigli vari...

Lo standard WebGL e' supportato nel progetto Chromium ed e' disponibile per i sistemi operativi Linux, Mac OS X, and Windows. Ecco alcuni indirizzi:

* Linux 32-bit: http://build.chromium.org/buildbot/continuous/linux/LATEST/
* Linux 64-bit: http://build.chromium.org/buildbot/snapshots/chromium-rel-linux-64/
* Mac OS X: http://build.chromium.org/buildbot/continuous/mac/LATEST/
* Windows: http://build.chromium.org/buildbot/continuous/win/LATEST/

Chromium deve essere eeguito da CLI per abilitare le webgl.

* Linux: ./chrome --no-sandbox --enable-webgl
* Mac OS X: Chromium.app/Contents/MacOS/Chromium --no-sandbox --enable-webgl
* Windows: chrome.exe --no-sandbox --enable-webgl


Ecco invece il link al codice delle lezioni:
http://code.google.com/p/webgl-it/downloads/list

Ecco il link al wiki ufficiale
https://www.khronos.org/webgl/wiki/Main_Page

Tuesday, December 22, 2009

Lezione 2: aggiungiamo un po' di colore...

Nella seconda lezione vedremo come aggiungere un po' di colore alla nostra scena 3d; le uniche cose che cambieranno saranno gli shaders, l' initBuffers e la funzione drawScene. Il funzionamento della pipeline di rendering di webgl lo si puo' vedere nella seconda lezione sul sito originale. Qui mi addentrero' subito nel codice, spiegando qua' e la' come funziona la logica di visualizzazione.

the vertex shader.
ecco come cambia:
attribute vec3 aVertexPosition;
attribute vec4 aVertexColor;
uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;
varying vec4 vColor;
void main(void) {
gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
vColor = aVertexColor;
}

Abbiamo due attribute, VertexPosition and aVertexColor, due uniforms non variabili called uMVMatrix anduPMatrix, e un output rappresentato dalla variabile vColor.
Nella parte centrale dello shader calcoleremo la gl_Position (che e' implicitamente definita come una variabile per ogni vertex shader) esattamente come abbiamo fatto nella lezione precedente.


Una volta eseguito questo per ogni vertice, viene eseguita un'interpolazione per generare il fragments, i quali vengono passati allo fragmaents shader:

varying vec4 vColor;
void main(void) {
gl_FragColor = vColor;
}

Qui prendiamo la variabile in input vColor , che contieneil colotr elaborato dall'interpolazione lineare e lo ritorna subito come colore per questo fragment.

Ci sono altre due modifiche. La prima nell' initShaders , dove ora otteniamo i riferimenti di due attributi invece di uno:

var shaderProgram;
var vertexPositionAttribute;
var vertexColorAttribute;
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("Could not initialise shaders");
}
gl.useProgram(shaderProgram);
vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
gl.enableVertexAttribArray(vertexPositionAttribute);
vertexColorAttribute = gl.getAttribLocation(shaderProgram, "aVertexColor");
gl.enableVertexAttribArray(vertexColorAttribute);
}

Nella lezione precedente avevamo solo l'attributo per la posizione...

I cambiamenti nell' initBuffers consistono nel definire due variabili che conterranno il color buffer per il triangolo e per il quadrato:

var triangleVertexPositionBuffer;
var triangleVertexColorBuffer;
var squareVertexPositionBuffer;
var squareVertexColorBuffer;

Dopo avere creato il buffer che contiene la posizione del triangolo, specifichiamo anche il colore dei vertici:

function initBuffers() {
triangleVertexPositionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer);
var vertices = [ 0.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);
triangleVertexPositionBuffer.itemSize = 3;
triangleVertexPositionBuffer.numItems = 3;
triangleVertexColorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexColorBuffer);
var colors = [ 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, ];
gl.bufferData(gl.ARRAY_BUFFER, new WebGLFloatArray(colors), gl.STATIC_DRAW);
triangleVertexColorBuffer.itemSize = 4; triangleVertexColorBuffer.numItems = 3;

E' evidente la principale differenza fra i buffer posizione e colore: mentre la posizione dei vertici e' definita da tre numeri(le coordinate x,y,z), il loro colore e' definito da quattro elementi(il colore rosso, il verde, il blue ed il canale alpha per la trasparenza).
E' ovvio che questa variazione influenza anche l' itemSize ...

Successivamente facciamo lo stesso per il quadrato ma questa volta useremo lo stesso colore per ogni vertice e per far questo useremo un ciclo:

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;
squareVertexColorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexColorBuffer);
colors = [];
for (var i=0; i < 4; i++) {
colors = colors.concat([0.5, 0.5, 1.0, 1.0]);
}
gl.bufferData(gl.ARRAY_BUFFER, new WebGLFloatArray(colors), gl.STATIC_DRAW);
squareVertexColorBuffer.itemSize = 4;
squareVertexColorBuffer.numItems = 4;

Ora modifichiamo drawScene per usare le nuove informazioni sui colori:

function drawScene() {
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
perspective(45, 1.0, 0.1, 100.0);
loadIdentity();
mvTranslate([-1.5, 0.0, -7.0]);
gl.bindBuffer(gl.ARRAY_BUFFER,triangleVertexPositionBuffer);
gl.vertexAttribPointer(vertexPositionAttribute,triangleVertexPositionBuffer.itemSize,gl.FLOAT,false,0,0);
gl.bindBuffer(gl.ARRAY_BUFFER,triangleVertexColorBuffer);
gl.vertexAttribPointer(vertexColorAttribute,triangleVertexColorBuffer.itemSize,gl.FLOAT,false,0,0); setMatrixUniforms();
gl.drawArrays(gl.TRIANGLES,0,triangleVertexPositionBuffer.numItems);
mvTranslate([3.0, 0.0, 0.0]);
gl.bindBuffer(gl.ARRAY_BUFFER,squareVertexPositionBuffer);
gl.vertexAttribPointer(vertexPositionAttribute,squareVertexPositionBuffer.itemSize,gl.FLOAT,false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER,squareVertexColorBuffer);
gl.vertexAttribPointer(vertexColorAttribute,squareVertexColorBuffer.itemSize,gl.FLOAT,false,0,0);
setMatrixUniforms();
gl.drawArrays(gl.TRIANGLE_STRIP, 0, squareVertexPositionBuffer.numItems);
}

...thats all folks!
Ora abbiamo il colore nella nostra scena!

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!