Salve a tutti! Salutiamo il nuovo anno pubblicando il nuovo ciclo di Articoli, incentrati sull’uso dei colori, delle basi dell’animazione e sulle texture.Buona lettura.

ESEMPIO DUE: DISEGNARE UN POLIGONO COLORATO

Dopo aver terminato il nostro primo esempio, potremmo rimanere un pò  delusi circa la banalità della figura, ci vorrebbe un pò più di COLORE! ( perdonate la freddura ma ogni tanto bisogna smorzare i toni :) ). Forse qualcuno avrà notato che  quando definiamo il fragment shader,invochiamo la suddetta chiamata:

gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);

Cosa significano quei 4 numeri? Praticamente servono a codificare  il colore bianco usando il cosiddetto modello RGBA. In pratica, quei valori da 0.0(assenza) a 1.0( massimo), indicano l’intensità di questi quattro componenti ,necessari a formare un colore desiderato. I primi tre corrispondono ai tre colori primari, il quarto indica invece la trasparenza.

Valore Significato
R(Red) Intensità del colore Rosso primario
G(Green) Intensità del colore Verde primario
B(Blue) Intensità del colore Blu primario
A(Alpha Channel) Livello di Opacità (0.0) o Trasparenza (1.0) del colore
Questo disegno rappresenta il cosiddetto spazio dei colori: le tre componenti definisco un punto di tale cubo a cui corrisponde un colore. La giusta mescolanza permette di creare il colore che più si avvicina ai nostri desideri. spazio dei colori

Partendo da queste conoscenze, se per esempio volessimo disegnare un triangolo verde trasparente, dovremmo usare la quaterna 0.0,1.0,0.0,1.0.

Tornando alla nostra funzione, se sostituiamo il colore che viene assegnato a livello di shader con quello sopra riportato ( ovvero gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0); ) e aprendo di nuovo la pagina, noteremo che il triangolo effetivamente ha un colore diverso:

triangolo verde

L’obiettivo è stato raggiunto, ma se volessimo un triangolo con più colori? Per rispondere a questo quesito, è meglio riassumere brevemente il ruolo degli shader nel WEBGL:

-nelle funzioni javascript ( drawScene) noi riempiamo/elaboriamo variabili globali ed uniformi

-il tutto viene passato al Vertex Shader:esso calcola PER OGNI VERTICE alcuni valori tra cui la posizione, che viene salvata in alcuni oggetti chiamati varying variables ( nel nostro esempio gl_Position che rappresenta la posizione).

-completato questo, inizia la fase in cui dobbiamo “proiettare” la nostra scena sul nostro schermo per cui dobbiamo convertire il nostro mondo 3D in uno 2D, disegnato dentro il nostro canvas. Il sistema poi chiamerà il fragment shader PER OGNI PIXEL dell’immagine 2D da rappresentare. Questa differenza tra pixel è vertice è assai importante: poichè non è certo detto che ad ogni pixel dell’immagine corrisponde un vertice ( basti pensare al nostro triangolo, se lo facciamo piuttosto grande 640 pixel  di larghezza per 320 pixel di altezza, abbiamo un pò più di 200000 chiamate al fragment ma solo tre vertici !), si instaura un meccanismo denominato interpolazione lineare : in parole povere il colore che assumerà un pixel sarà influenzato proporzionalmente  dalla distanza  di esso dai vertici del poligono il cui colore è fissato.Maggiore sarà la vicinanza di esso ad un vertice e più grande sarà il suo contributo.

Ecco spiegato perchè cambiando quel valore nel fragment shader , il triangolo diventa verde: imponiamo a tale shader che quando dovrà essere renderizzata l’immagine, tutti i pixel dentro il triangolo dovranno necessariamente essere di quel colore prefissato! Capito ciò, dobbiamo trovare un modo più corretto per assegnare il colore alla nostra figura; il trucchetto che useremo è piuttosto semplice ma efficace: oltre alla posizione, assegneremo ad ogni vertice un colore, esso verrà poi salvato dal vertex shader in una variabile; infine essa verrà passata al fragmente shader che l’applicherà al pixel. A scriverla pare complicata, ma non è così!

Questo approccio necessariamente ci obbliga a fare delle piccole variazioni al nostro codice; ora le elencheremo segnando con un colore diverso le aggiunte e le variazioni con il codice del primo esempio:

1. Creiamo e inizializziamo un buffer per il colore:

var triangleVertexPositionBuffer;
var triangleVertexColorBuffer;

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 Float32Array(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 Float32Array(colors), gl.STATIC_DRAW);

}

Il codice si commenta da solo: come per le posizioni, anche per il colore ho bisogno di un buffer per salvarmi i dati, perciò creo il  buffer, inserisco dei valori credibili, riempio il buffer e setto i soliti attributi. Per mostrare meglio l’effetto dell’interpolazione lineare, decidiamo che ogni vertice possieda un colore ben distinto ( Rosso, Verde e Blu), e vedremo che i pixel tra di essi avranno sfumature di colore decisamente variegate.

2. creazione di un nuovo attributo e gestione del colore negli shader

<script id=”shader-vs” type=”x-shader/x-vertex”>
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;
}

</script>
<script id=”shader-fs” type=”x-shader/x-fragment”>
#ifdef GL_ES
precision highp float;

varying vec4 vColor;

#endif
void main(void) {
// gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);

gl_FragColor=vColor;

}
</script>

3. Modifica all’ initShader.

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”);
}
gl.useProgram(shaderProgram);
shaderProgram.vertexPositionAttribute =gl.getAttribLocation(shaderProgram,
“aVertexPosition”);

vertexColorAttribute=gl.getAttribLocation(shaderProgram,”aVertexColor”);
gl.enableVertexAttribArray(vertexColorAttribute);

gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);
shaderProgram.pMatrixUniform = gl.getUniformLocation(shaderProgram, “uPMatrix”);
shaderProgram.mvMatrixUniform = gl.getUniformLocation(shaderProgram, “uMVMatrix”);
}

Praticamente Abilito un nuovo attributo, che deve essere preso dallo shader vertex chiamato aVertextColor e che viene usato per il passaggio dei dati tra shaders.

4. Modifica al 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, triangleVertexPositionBuffer);
gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute,
triangleVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);

gl.bindBuffer(gl.ARRAY_BUFFER,triangleVertexColorBuffer);
gl.vertexAttribPointer(vertexColorAttribute,4,gl.FLOAT,false,0,0);

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

Terminata la copiatura, facciamo partire la pagina HTML,dovremmo avere un output del genere:

Esempio02

L’esempio è direttamente visionabile su questo link e scaricabile dal solito repository con il nome Esempio02.

Proposta di Esercizio:colori animati
– cambiare il codice javascript in modo tale che ciclicamente i colori dei tre vertici cambiano ogni tot secondi. La soluzione è consultabile presso il solito repository con il nome Esercizio01.

Grazie per l’attenzione,

Andrea