Salve a tutti! In questo articolo cercheremo di disegnare il nostro prima oggetto interamente 3D ( un insieme di poligoni).

QUARTO ESEMPIO: CUBO COLORATO
Finalmente abbiamo le basi delle basi per affrontare la nostra prima applicazione 3D. Questo introdurrà un aumento della complessità del codice e l’inevitabile necessità di utilizzare tecniche efficienti per il disegno, visto che il numero di vertici aumenterà inesorabilmente.

Per disegnare, un cubo semplice monocolore al momento non richiederebbe molte modifiche: basterebbe passare alla sottofunzione drawArrays un array con gli otto punti necessari :

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

Per disegnare, tale funzione fa un ciclo e aggiunge un vertice alla volta tracciando  nella scena i triangoli ( per disegnare oggetti di forma varia, un buon metodo è suddividere il tutto in tanti triangolini che singolarmente son facili da disegnare) che collegano il vertice corrente dai precedenti.
Tale metodo funziona, ma,in caso di facce multicolore intrinsecamente porta ad una complicanza: poichè ogni vertice appartiene a tre facce diverse del nostro dato, egli dovrebbe possedere come attributo tre colori possibilmente diversi. Questi ed altri problemi ci costringono a valutare un ‘altra strada.

Potremmo decidere di ottenere un disegno del nostro cubo tracciando singolarmente ogni sua faccia. Certo, ogni vertice verrà ridisegnanto più volte (6 quadrati da 4 vertici= 24 vertici), ma questo approccio non richiede una gestione particolare dell’uso dei colori (in questo caso un vertice -> un colore) e non richiede l’introduzione di modifiche significative. E’ una tecnica valida, ma non ottimizza le prestazioni, questo perchè chiamare la funzione DrawArray è un ‘operazione onerosa e già solo per un cubo la dovremmo chiamare 6 volte: immaginiamo di usare tale metodologia per un mondo completamente 3D!!

Il nostro obiettivo è quindi trovare un metodo per disegnare tutti questi triangoli con una sola chiamata per il disegno: fortunamente arriva in nostro aiuto la funzione gl.drawElements(). A questa funzione però dovremmo passare esplicitamente tutti i vertici di tutti i triangoli che deve disegnare. Poichè un quadrato è possibile scomporlo in 2 triangoli e un cubo ha 6 facce, dovremmo definire 12 triangoli e perciò 36 vertici !
Facendo alcune considerazioni riusciremo a ridurre la complessità: prendiamo un quadrato come esempio. Esso può essere diviso in due triangoli rettangoli come si vede in figura. Per disegnare questo cubo quindi dovremmo definire 6 vertici ma 2 di questi sono ripetuti ( il che ci va bene).

Contando che abbiamo sei facce da disegnare, dovremmo definire 36 punti ma alla fine ne definiremo 24 perchè molti sono repliche.
Una soluzione semplice quanto efficace è realizzare un ARRAY DEGLI INDICI:ogni elemento di questo array di 36 punti indicherà una posizione dentro un secondo array che conterrà i nostri 24 vertici.

Riassumendo per disegnare il cubo dovremmo:
1.definire un array di posizioni e uno dei colori, entrambi da 24 elementi.

2.definire un array di indici, le cui posizioni sono pari al numero di vertici di tutti i triangoli che disegneremo , cioè 12*3=36. Poichè sono indici, i suoi valori indicheranno dell posizioni dentro un nostro array composto da 24 , indi i valori spazieranno da 0 a 23.

3.lanciare drawElement dandoli come argomento l’array di indici.

Passiamo ora a vedere un pò di codice.

Le modifiche riguarderanno solo initBuffer e drawScene :

1.InitBuffer

var cubover; //vertici cubo
var cubocolor; //colori vertici cubo
var cuboindex; //indici vertici cubo
function initBuffers() {
cubocolor= gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, cubocolor);
var cubecolors = [
1.0, 0.0, 0.0, 1.0,      1.0, 0.0, 0.0, 1.0,     1.0, 0.0, 0.0, 1.0,      1.0, 0.0, 0.0, 1.0,
0.0, 1.0, 0.0, 1.0,      0.0, 1.0, 0.0, 1.0,     0.0, 1.0, 0.0, 1.0,      0.0, 1.0, 0.0, 1.0,
0.0, 0.0, 1.0, 1.0,      0.0, 1.0, 1.0, 1.0,     0.0, 0.0, 1.0, 1.0,      0.0, 0.0, 1.0, 1.0,
1.0, 1.0, 0.0, 1.0,      1.0, 1.0, 0.0, 1.0,     1.0, 1.0, 0.0, 1.0,      1.0, 1.0, 0.0, 1.0,
1.0, 1.0, 1.0, 1.0,      1.0, 1.0, 1.0, 1.0,     1.0, 1.0, 1.0, 1.0,      1.0, 1.0, 1.0, 1.0,
0.5, 0.5, 0.5, 1.0,      0.5, 0.5, 0.5, 1.0,     0.5, 0.5, 0.5, 1.0,      0.5, 0.5, 0.5, 1.0,
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(cubecolors), gl.STATIC_DRAW);
cubocolor.itemSize = 4;
cubocolor.numItems = 24;
cubover= gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, cubover);
var cuboverarr= [
// Faccia davanti
-1.0, -1.0,  1.0,
1.0, -1.0,  1.0,
1.0,  1.0,  1.0,
-1.0,  1.0,  1.0,
// Faccia dietro
-1.0, -1.0, -1.0,
-1.0,  1.0, -1.0,
1.0,  1.0, -1.0,
1.0, -1.0, -1.0,
// Faccia superiore
-1.0,  1.0, -1.0,
-1.0,  1.0,  1.0,
1.0,  1.0,  1.0,
1.0,  1.0, -1.0,
// Faccia inferiore
-1.0, -1.0, -1.0,
1.0, -1.0, -1.0,
1.0, -1.0,  1.0,
-1.0, -1.0,  1.0,
// Faccia a destra
1.0, -1.0, -1.0,
1.0,  1.0, -1.0,
1.0,  1.0,  1.0,
1.0, -1.0,  1.0,
// Faccia a sinistra
-1.0, -1.0, -1.0,
-1.0, -1.0,  1.0,
-1.0,  1.0,  1.0,
-1.0,  1.0, -1.0,
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(cuboverarr), gl.STATIC_DRAW);
cubover.itemSize = 3;
cubover.numItems = 24;
cuboindex= gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cuboindex);
var indexes= [
0, 1, 2,      0, 2, 3,    // Faccia davanti
4, 5, 6,      4, 6, 7,    // Faccia dietro
8, 9, 10,     8, 10, 11,  // Faccia superiore
12, 13, 14,   12, 14, 15, // Faccia inferiore
16, 17, 18,   16, 18, 19, // Faccia a destra
20, 21, 22,   20, 22, 23  // Faccia a sinistra

];
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indexes), gl.STATIC_DRAW);
cuboindex.itemSize = 1;
cuboindex.numItems = 36;
}

Nell’initBuffer non faccio altro che definire 3 nuovi buffer e inizializzarli con valori precisi.

Attenzione
L’ordine con cui definisco vertici, colori ed indici è importante: la posizione del vertice nell’array cuboover indica anche la posizione del suo colore nell’array cubocolor ( il colore del 4a vertice è ovviamente la quarta quadrupla di cubocolor). Anche l’ordine degli indici di cuboindex non è casuale; ogni tripletta indica un triangolo ed ogni riga ( 2 triplette) corrisponde ad una faccia quadrata disegnata. Se ad esempio cambiassimo l’ordine dei colori in cubocolor, avremmo un cubo “pasticciato”, mentre se scrivessimo a caso gli indici dentro cuboindex , difficilmente il poligono risultante sarebbe un cubo .

Attenzione2:
Nel disegno, per pratica comune, ho messo l’asse Z verticalmente: in realtà nella nostra scena lo Z è perpendicolare allo schermo ( per quello nel codice c’è scritto che i primi 4 vertici disegnano la faccia sopra ma nel disegno sopra sembra la faccia superiore).

2.drawScene

function drawScene() {
gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
perspective(45, gl.viewportWidth / gl.viewportHeight, 0.1, 100.0);
loadIdentity();
mvTranslate([-1.5, 0.0, -7.0]);

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

gl.bindBuffer(gl.ARRAY_BUFFER, cubocolor);
gl.vertexAttribPointer(shaderProgram.vertexColorAttribute, cubocolor.itemSize, gl.FLOAT, false, 0, 0);

gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cuboindex);

setMatrixUniforms();

gl.drawElements(gl.TRIANGLES, cuboindex.numItems, gl.UNSIGNED_SHORT, 0);

}

Come potete vedere il codice è molto semplice e questo dovrebbe essere il vostro output:

Lesson04a

Il nostro cubo è piuttosto statico, un noto metodo per poterlo ammirare in tutto il suo 3d è quello di farlo ruotare con la funzione mvRotate, magari attorno ad un asse obliquo così scorgeremo ogni sua faccia:

Codice Modificato


var cubeangle=0;
function increaseangle()
{
cubeangle+=4.0;
}

function drawScene() {
gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
perspective(45, gl.viewportWidth / gl.viewportHeight, 0.1, 100.0);
loadIdentity();
mvTranslate([0.0, 0.0, -5.0]);
mvRotate(cubeangle,[0.5,1,0]);

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

gl.bindBuffer(gl.ARRAY_BUFFER, cubocolor);
gl.vertexAttribPointer(shaderProgram.vertexColorAttribute, cubocolor.itemSize, gl.FLOAT, false, 0, 0);

gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cuboindex);
setMatrixUniforms();
gl.drawElements(gl.TRIANGLES, cuboindex.numItems, gl.UNSIGNED_SHORT, 0);
}

function tick()
{
increaseangle();
drawScene();
}

..

Ecco il risultato della nostra modifica

Lesson04b

Tutti gli script e gli elementi usati in questo articolo sarenno disponibili sul solito repository Lab .

Grazie per l’attenzione,

Andrea