Home > Guide e tutorial per Android > Sviluppare un gioco per Android – Lezione 12: visualizzare elementi grafici con OpenGL ES

Sviluppare un gioco per Android – Lezione 12: visualizzare elementi grafici con OpenGL ES

programmazione-android12Come visualizzare elementi grafici con OpenGL ES? Nella scorsa lezione abbiamo visto come impostare il nostro progetto Android per utilizzare OpenGL ES. Oggi, continuiamo sullo stesso passo, continuando lo sviluppo del codice che abbiamo visto la volta scorsa, ma prima di scoprire come visualizzare elementi grafici, è necessario avere chiari alcuni concetti basilari di programmazione 3D. Come alcuni di voi già sapranno, il modello 3D fa uso di un sistema di coordinate con 3 dimensioni: x, y e z. Ogni oggetto, anche un’auto ad esempio, viene creato grazie alle primitive e, parlando di primitive OpenGL ES, intendiamo i triangoli.

Ogni triangolo ha una faccia frontale e una opposta (face e backface), inoltre esso è definito da tre punti nello spazio: i vertici (vertices, vertex al singolare). Nell’immagine di seguito, potete vedere i due vertici A e B.

Vertices

L’immagine qui sopra è essenziale per capire la differenza tra 2D e 3D. Un vertice, infatti, è definito grazie alle sue 3 coordinate (come B), mentre nel caso di A abbiamo impostato la coordinata z pari a 0. Se tutti i nostri elementi avessero questa caratteristica, allora inizieremmo a parlare di programmazione 2D.

Il triangolo è un oggetto di tipo primitivo, il più semplice che OpenGL riconosce ed è in grado di rappresentare. Naturalmente ce ne sono altri, ma noi ci limiteremo alle basi. Ogni oggetto può essere composto da triangoli. Ritorniamo, ora, alle facce del triangolo in questione.

Si tratta di uno dei concetti essenziali di programmazione 3D, infatti ogni oggetto avrà una faccia rivolta verso di voi, e una nascosta. Quella nascosta non sarà disegnata da OpenGL proprio perché non ce n’è bisogno (backface culling), ma come fa OpenGL a determinare tutto questo? Molto semplice: se l’ordine dei vertici è antiorario, abbiamo quella che nel gergo, come soprascritto, viene chiamata face, altrimenti si ha a che fare con una backface. Questo meccanismo è di default, ma può essere modificato. Ad ogni modo, l’immagine qui di seguito vi chiarirà le idee (il triangolo rosso non verrà disegnato).

Faces

Visualizzare elementi grafici: creare e disegnare un triangolo

A questo punto, abbiamo concluso con la teoria e ciò che ci resta da fare riguarda la creazione e il disegno del triangolo.

Come abbiamo già detto, il triangolo è definito da tre vertici e le rispettive coordinate non vengono misurate in pixel. Useremo il tipo float per rappresentare i valori delle coordinate, le quali saranno l’una relativa all’altra. Ad esempio, se due lati sono lunghi rispettivamente 1.0f e 0.5f, implichiamo che la lunghezza del secondo è l’esatta metà di quella del primo.

Per quanto riguarda la grandezza del triangolo, essa dipenderà dalla viewport che utilizzeremo. Immaginate quest’ultima come una fotocamera che, nel caso di grafica 2D, è ortogonale allo schermo. Se la fotocamera è vicina, il triangolo apparirà grande, ma più si allontana più esso rimpicciolirà. A questo punto, creiamo la classe Triangle.java:

package com.example.lezioneopengl_androidblog;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

import javax.microedition.khronos.opengles.GL10;

public class Triangle {

	private FloatBuffer vertexBuffer;	// buffer contenente i vertici

	private float vertices[] = {
			-0.5f, -0.5f,  0.0f,		// V1 - primo vertice (x,y,z)
			 0.5f, -0.5f,  0.0f,		// V2 - secondo vertice
			 0.0f,  0.5f,  0.0f			// V3 - terzo vertice
	};

	public Triangle() {
		// il tipo float ha 4 bytes quindi allochiamo per ogni coordinata 4 bytes
		ByteBuffer vertexByteBuffer = ByteBuffer.allocateDirect(vertices.length * 4);
		vertexByteBuffer.order(ByteOrder.nativeOrder());

		// allochiamo la memoria dal byte buffer
		vertexBuffer = vertexByteBuffer.asFloatBuffer();

		// rempiamo vertexBuffer con i vertici
		vertexBuffer.put(vertices);

		// settiamo la posizione del puntatore all'inizio del buffer
		vertexBuffer.position(0);

	}

	public void draw(GL10 gl) {
		gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
		// settiamo il colore di background
//		gl.glClearColor(0.0f, 0.0f, 0.0f, 0.5f);

		// per mostrare il colore
//		gl.glClear(GL10.GL_COLOR_BUFFER_BIT);

		// settiamo il colore per il triangolo
		gl.glColor4f(0.0f, 1.0f, 0.0f, 0.5f);

		// puntiamo i nostri vertici
		gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer);

		// Disegniamo i vertici come TRIANGLE_STRIP
		gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, vertices.length / 3);

		gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
	}
}

Al rigo 11 dichiariamo il FloatBuffer che otterrà i vertici del nostro triangolo. Questi ultimi sono contenuti nell’array vertices[]. Potete capire meglio come verrà rappresentato il triangolo dall’immagine qui di seguito:

Triangle

Nel costruttore, inizializziamo il triangolo, riempiendo il FloatBuffer con i vertici e impostando la posizione del puntatore all’inizio. Ora diamo un’occhiata al metodo draw().

Poiché noi memorizziamo le coordinate dei vertici in un FloatBuffer dobbiamo abilitare OpenGL affinché esso possa leggerle e capire che si trovano lì. Al rigo 37 facciamo proprio questo. Al rigo 45, invece, impostiamo un colore per il triangolo che sarà visualizzato.

Con glVertexPointer informiamo OpenGL  che deve utilizzare vertexBuffer per estrarre i vertici e gli passiamo, come parametro iniziale, il numero di coordinate che saranno utilizzate per ogni vertice. Il secondo parametro, invece, fa capire ad OpenGL che tipo di dati sono contenuti nel buffer, mentre il terzo altro non è che l’offset nell’array utilizzato per i vertici.

Infine, utilizziamo glDrawArrays per disegnare la nostra entità come un triangolo, a partire dal primo elemento trovato nel buffer. Diamo, un’occhiata, prima di concludere, ai cambiamenti apportati al nostro renderer:

public class GlRenderer implements Renderer {

private Triangle triangle;	// il triangolo da disegnare

	public GlRenderer() {
		this.triangle = new Triangle();
	}

	@Override
	public void onDrawFrame(GL10 gl) {
		gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);

		// resetta la Modelview Matrix
		gl.glLoadIdentity();

		// Disegno
		gl.glTranslatef(0.0f, 0.0f, -5.0f);		// muove di 5 unità nello schermo

//		gl.glScalef(0.5f, 0.5f, 0.5f);			// scala il triangolo del 50% 
								// nel caso sia troppo grande
		triangle.draw(gl);				// disegna il triangolo

	}

	@Override
	public void onSurfaceChanged(GL10 gl, int width, int height) {
		if(height == 0) { 			//controlliamo la divisione per 0
			height = 1; 						
		}

		gl.glViewport(0, 0, width, height); 	//resettiamo la Viewport corrente
		gl.glMatrixMode(GL10.GL_PROJECTION); 	//Selezioniamo la Projection Matrix
		gl.glLoadIdentity(); 			//Resettiamo la Projection Matrix

		//Calcoliamo l'Aspect Ratio dello schermo
		GLU.gluPerspective(gl, 45.0f, (float)width / (float)height, 0.1f, 100.0f);

		gl.glMatrixMode(GL10.GL_MODELVIEW); 	//Selezioniamo la Modelview Matrix
		gl.glLoadIdentity(); 			//Resettiamo la Modelview Matrix

	}

	@Override
	public void onSurfaceCreated(GL10 gl, EGLConfig config) {
		// TODO Auto-generated method stub

	}

}

Concentriamoci su OnDrawFrame(). Dovete sapere che OpenGL funziona con variabili di stato e, come potete vedere, ogni metodo cambia il contesto interno. Ogni volta che un frame viene disegnato, il buffer ottenuto viene eliminato, la matrice ModelView viene ricaricata (tralasciatela al momento) e la fotocamera viene allontanata di 5 unità (non vengono utilizzati i pixel ma le unità). Infine viene chiamato il metodo draw() del triangolo.

In onSurfaceChanged() invece, in primo luogo si imposta la viewport con la larghezza e l’altezza attuale (in modo che funzioni con lo stato GL_PROJECTION), poi transitiamo allo stato GL_MODELVIEW in modo da poter lavorare con i nostri modelli (il triangolo nel nostro caso). Vedremo tutto questo più specificatamente nella prossima lezione.

Avviando l’applicazione a questo punto, dovreste ritrovarvi davanti il seguente risultato:

Screenshot from 2014-03-17 13:06:17