Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Texture

Imparare ad applicare una texture sugli oggetti, per realizzare un videogame per Android in Java con OpenGL ES.
Imparare ad applicare una texture sugli oggetti, per realizzare un videogame per Android in Java con OpenGL ES.
Link copiato negli appunti

Per completare la trattazione sui triangoli, e capire come la
nostra mesh viene implementata dalla classe GameObjectMesh,
vediamo come aggiungere al buffer le coordinate di una texture,
associandole ad ogni vertice.

In OpenGL ES, una texture è un'immagine
con un suo sistema di riferimento, noto come UV o ST,
il cui nome deriva dalle etichette attribuite agli assi di questo
sistema di riferimento (u o s per l'asse orizzontale,
v o t per quello verticale). L'origine del sistema
di riferimento delle immagini di texture è in basso a sinistra,
con valori che variano tra 0 e 1. Una coppia di questi valori
identifica quello che viene chiamato texel,
che possiamo pensare come un quadratino di un'immagine molto piccolo
(di area inferiore a quella di un pixel).

Per applicare una texture su una mesh costituita
da 2 triangoli (a formare un rettangolo), dobbiamo associare a
ciascun vertice del buffer le coordinate UV dei 4 punti estremi della texture.

Figura 7. Texture Mapping (click per ingrandire)
Texture Mapping

Come spesso si fa, anche per questo gioco utilizzeremo un'unica grande
immagine Atlas (quella vista all'inizio della lezione 2), contente
varie immagini che saranno opportunamente estratte per essere usate come texture.
Ma come facciamo a ricavare le coordinate di texture
da applicare ad un rettangolo? Il meccanismo è molto semplice.

Aprendo l'immagine Atlas con un qualsiasi editor, si individua
l'area di texture che ci interessa. Quindi se ne valutano la larghezza
(width) e l'altezza (height), nonchè
le coordinate x ed y del punto in
alto a sinistra dell'area di interesse. Tutte queste dimensioni,
ovviamente, vanno misurate in pixel.

Figura 8. Identificazione delle coordinate di texture
Identificazione delle coordinate di texture

Ottenute le dimensioni appena descritte, le seguenti formule
consentono di calcolare le coordinate UV da passare ai vertici:

  • u1 = x / textureWidth
  • v1 = y / textureHeight
  • u2 = u1 + width / textureWidth
  • v2 = v1 + height / textureHeight

Qui, textureWidth e textureHeight rappresentano
rispettivamente la larghezza e l'altezza dell'intero Atlas, e sono
anch'esse espresse in pixel. u1 e v1
rappresentano le coordinate del punto in alto a sinistra dell'area
di texture che stiamo considerando, mentre u2 e v2
sono quelle del punto in basso a destra.

Queste formule sono implementate dalla classe GameImage,
che rappresenta un'immagine contenuta all'interno della nostra texture.

public GameImage(float x, float y, float width, float height, int textureWidth, int textureHeight) {
				this.width=width;
				this.height=height;
				this.textureWidth=textureWidth;
				this.textureHeight=textureHeight;
				this.x=x;
				this.y=y;
				computeUvCoord();
			}
			public void computeUvCoord() {
				u1 = x / textureWidth;
				v1 = y / textureHeight;
				u2 = u1 + width / textureWidth;
				v2 = v1 + height / textureHeight;
			}

La classe GameObjectMesh usa invece il seguente costruttore:

public GameObjectMesh(float x, float y, int width, int height) {
				this.x=x;
				this.y=y;
				this.width=width;
				this.height=height;
				setIndices();
				setVertices();
			}

Nel costruttore precedente vengono specificate le coordinate
del centro del rettangolo e le sue dimensioni. Internamente,
la classe si occupa di costruire, nella posizione specificata,
una mesh di due triangoli a formare un rettangolo che viene
costruito con 4 vertici, sfruttando il riuso attraverso indicizzazione dei vertici:

private FloatBuffer verticesBuffer;
			private short[] triangles = {0, 1, 2, 2, 3, 0};
			private ShortBuffer trianglesBuffer;

L'array triangles specifica il numero associato
a ciascun vertice. Così, 0, 1 e 2 indicano il primo, il secondo ed
il terzo vertice tra quelli inclusi nel buffer, costituendo un
triangolo; gli indici 2, 3 e 0 rappresentano invece l'altro triangolo.
Con triangles e trianglesBuffer rappresentiamo
quindi i due triangoli della mesh:

private void setIndices() {
				ByteBuffer byteBuffer = ByteBuffer.allocateDirect(triangles.length * 2);
				byteBuffer.order(ByteOrder.nativeOrder());
				ShortBuffer shortBuffer = byteBuffer.asShortBuffer();
				shortBuffer.put(triangles);
				shortBuffer.flip();
				trianglesBuffer=shortBuffer;
			}

Con verticesBuffer, invece, rappresentiamo i vertici
di questi triangoli:

private void setVertices() {
				ByteBuffer byteBuffer = ByteBuffer.allocateDirect(4*VERTEX_SIZE);
				byteBuffer.order(ByteOrder.nativeOrder());
				verticesBuffer = byteBuffer.asFloatBuffer();
				verticesBuffer.put(new float[]{	x-width/2, y+height/2, 0, 0,
												x+width/2, y+height/2, 0, 0,
												x+width/2, y-height/2, 0, 0,
												x-width/2, y-height/2, 0, 0});
				verticesBuffer.flip();
			}

Notiamo come i valori 0, 0 impostano temporaneamente
le coordinate texture associate ai vertici sull'origine del sistema
di riferimento. Sarà il metodo mapTextureUV() di GameObjectMesh
ad occuparsi di aggiungere la texture, a partire da un'istanza
della classe GameImage:

public void mapTextureUV(GameImage gameImage) {
				float uTopLeft = gameImage.getU1();
				float vTopLeft = gameImage.getV1();
				float uBottomRight = gameImage.getU2();
				float vBottomRight = gameImage.getV2();
				verticesBuffer.position(2);
				verticesBuffer.put(uTopLeft);
				verticesBuffer.position(3);
				verticesBuffer.put(vTopLeft);
				verticesBuffer.position(6);
				verticesBuffer.put(uBottomRight);
				verticesBuffer.position(7);
				verticesBuffer.put(vTopLeft);
				verticesBuffer.position(10);
				verticesBuffer.put(uBottomRight);
				verticesBuffer.position(11);
				verticesBuffer.put(vBottomRight);
				verticesBuffer.position(14);
				verticesBuffer.put(uTopLeft);
				verticesBuffer.position(15);
				verticesBuffer.put(vBottomRight);
			}

Si può notare come, spostando la posizione corrente dell'indice
tramite il metodo position, le coordinate di texture
vengono impostante nelle posizioni opportune del FloatBuffer,
facendo sì che la texture ricopra l'intera mesh.

Le classi che ci permettono di gestire una texture sono GameAtlas
e GameBackgroundAtlas. La prima contiene solo immagini
di oggetti, la seconda solo immagini di sfondo.
Queste classi dispongono rispettivamente dei metodi loadAtlas()
e loadBackground(), che ci permettono di caricare
i file che intendiamo usare. Oltre ad effettuare il binding della texture,
queste classi si occupano anche di applicare opportuni filtri
che migliorano la qualità delle immagini nel gioco. In GamebackgroundAtlas
troviamo il metodo per caricare uno sfondo:

public static GameImage getBackgroundImage(int imageX, int imageY, int imageWidth, int imageHeight) {
				GameImage backgroundImage = new GameImage(imageX, imageY, imageWidth, imageHeight, width, height);
				return backgroundImage;
			}

La classe GameAtlas ha invece i metodi seguenti:

public static GameImage getGameImage(int imageX, int imageY, int imageWidth, int imageHeight) {
				GameImage spriteImage = new GameImage(imageX, imageY, imageWidth, imageHeight, width, height);
				return spriteImage;
			}
			public static GameImage[] getGameAnimation(int initialFrameX, int initialFrameY, int frameWidth, int frameHeight, int[] rowFrames) {
				// ...
			}

Il secondo dei metodi precedenti consente di recuperare un'animazione.
Dobbiamo specificare la coordinata del vertice in alto a
sinistra del primo frame, l'ampiezza e altezza comune a tutti i frame,
ed infine un array che specifica il numero di frame per riga. Se, ad esempio,
l'animazione della prima riga è composta da 3 frame, e quella della
seconda riga da 4 frame, l'array sarà composto dagli elementi {3, 4}.

L'array di oggetti GameImage viene gestito a sua
volta dalla classe GameImageAnimation, che rappresenta una sequenza di frame
estratti dalla texture, che compongono l'animazione.
Il metodo getFrame() di GameImageAnimation
processa la sequenza e restituisce, di volta in volta, il frame da renderizzare.

Il disegno di un oggetto GameObject avviene invece grazie ai
seguenti metodi della classe:

public void draw(GL10 gl) {
				ShortBuffer triangles = mesh.getTrianglesBuffer();
				FloatBuffer buffer = mesh.getVerticesBuffer();
				buffer.position(0);
				gl.glVertexPointer(2, GL10.GL_FLOAT, (2 + 2) * 4, buffer);
				buffer.position(2);
				gl.glTexCoordPointer(2, GL10.GL_FLOAT, (2 + 2) * 4, buffer);
				gl.glDrawElements(GL10.GL_TRIANGLES, 6, GL10.GL_UNSIGNED_SHORT,	triangles);
			}
			public void drawAnimation(GL10 gl, Mode mode, float time) {
				this.mesh.mapTextureUV(gameImageAnimation.getFrame(time, mode));
				draw(gl);
			}

Essi disegnano rispettivamente un'immagine ed un'animazione.
Nel primo caso, vediamo come dalla mesh vengano recuperati i buffer di triangoli e vertici,
mentre i puntatori di vertici e texture sono posti nelle posizioni
da cui essi cominciano all'interno del buffer. Un vertice di un buffer
inizia sempre dall'indice 0, mentre un valore di texture sempre dall'indice 2
(dobbiamo infatti saltare 0 e 1, che denotano x ed y del primo vertice).
Il metodo drawAnimation(), invece, si occupa di
mappare la texture del frame corrente sui vertici, e successivamente invoca
il metodo draw(), che disegna tale frame.
Questi metodi vengono invocati all'interno del metodo present()
di una classe GameScreen, in quanto metodi di rendering.


Ti consigliamo anche