LIBGDX Animacions3D

De MediaWiki
Ir a la navegación Ir a la búsqueda

UNIDADE 4: Animacións en 3D

Introdución

Neste apartado imos ver como podemos animar (mover, trasladar e rotar) os obxectos 3D.

Como ven pensades, imos necesitar unha matriz :)

O que imos facer será animar un cubo.

Preparación:

  • Copiar a seguinte textura ó cartafol assets do proxecto Android:


Crate.png
  • Imos crear un obxecto Mesh onde imos definir os vértices que conforman os cubos de acordo co seguinte esquema:
LIBGDX UD4 Animacion 1.jpg

Para facelo imos a utilizar os conceptos aprendidos cos triángulos, pero facendo algunha modificación. Cando fixemos o proxecto dos triángulos definimos dous grupos de vértices, un para cada triángulo. Pero tamén podemos definir os dous triángulos nun mesmo grupo de vértices.

triangulos = new Mesh(true,6,6,new VertexAttribute(Usage.Position,3,"a_position"),new VertexAttribute(Usage.ColorPacked,4,"a_color"));
triangulos.setVertices(new float[] {-0.5f,-0.5f,-3f,1f,0,0,1f, 0.5f,-0.5f,-3f,1f,0,0,1f, 0f,0.5f,-3f,1f,0,0,1f, 0f,-0.5f,-5f,1f,0,0,1f, 1f,-0.5f,-5f,1f,0,0,1f, 0.5f,0.5f,-5f,1f,0,0,1f});
triangulos.setIndices(new short[]{0,1,2,3,4,5});
.....
triangulos.render(GL10.GL_TRIANGLES,0,3);
triangulos.render(GL10.GL_TRIANGLES,3,3);


Está marcado en negrilla os cambios. Agora os vértices están xuntos. Cando debuxamos (render) informamos que imos debuxar un triángulo que ten a información dos vértices no array de vértices. Un dos triángulos empeza na posición 0 do array e ten tres vértices, mentres que o triángulo2 empeza na posición 3 e ten tres vértices. A orde de debuxo dos vértices ven indicado polo array de índices (triángulo1 => 0,1,2 ; triángulo2 => 3,4,5).

Nota: Poderíamos aproveitar os triángulos que comparten lados e aforrarnos puntos no array de vértices.

LIBGDX UD4 Animacion 2.jpg



Volvendo ó Cubo, vemos que cada lado do cubo vai necesitar 4 vértices, por 6 caras = 24 vértices. Por outra banda cada triángulo necesita 3 índices. Se cada cara ten dous triángulos isto da:

3 índices por triángulo x 2 triángulos por cara x 6 caras = 36 índices.

Crear unha clase de nome Shapes. Dita clase o que vai definir será un Cubo cos seus vértices e índices así como as coordenadas da textura.

Código da clase Shapes
Obxectivo: Xera un obxecto Mesh coa forma dun cubo en 3D.

 1 import com.badlogic.gdx.graphics.Mesh;
 2 import com.badlogic.gdx.graphics.VertexAttribute;
 3 
 4 public class Shapes {
 5 	public static Mesh genCube() {
 6 		Mesh mesh = new Mesh(true, 24, 36, VertexAttribute.Position(),VertexAttribute.TexCoords(0));
 7 
 8 		float[] vertices = { -0.5f, -0.5f, 0.5f, 0, 1, 0.5f, -0.5f, 0.5f, 1, 1,
 9 				0.5f, 0.5f, 0.5f, 1, 0, -0.5f, 0.5f, 0.5f, 0, 0, 0.5f, -0.5f,
10 				0.5f, 0, 1, 0.5f, -0.5f, -0.5f, 1, 1, 0.5f, 0.5f, -0.5f, 1, 0,
11 				0.5f, 0.5f, 0.5f, 0, 0, 0.5f, -0.5f, -0.5f, 0, 1, -0.5f, -0.5f,
12 				-0.5f, 1, 1, -0.5f, 0.5f, -0.5f, 1, 0, 0.5f, 0.5f, -0.5f, 0, 0,
13 				-0.5f, -0.5f, -0.5f, 0, 1, -0.5f, -0.5f, 0.5f, 1, 1, -0.5f,
14 				0.5f, 0.5f, 1, 0, -0.5f, 0.5f, -0.5f, 0, 0, -0.5f, 0.5f, 0.5f,
15 				0, 1, 0.5f, 0.5f, 0.5f, 1, 1, 0.5f, 0.5f, -0.5f, 1, 0, -0.5f,
16 				0.5f, -0.5f, 0, 0, -0.5f, -0.5f, 0.5f, 0, 1, 0.5f, -0.5f, 0.5f,
17 				1, 1, 0.5f, -0.5f, -0.5f, 1, 0, -0.5f, -0.5f, -0.5f, 0, 0 };
18 		short[] indices = { 0, 1, 3, 1, 2, 3, 4, 5, 7, 5, 6, 7, 8, 9, 11, 9,
19 				10, 11, 12, 13, 15, 13, 14, 15, 16, 17, 19, 17, 18, 19, 20, 21,
20 				23, 21, 22, 23, };
21 
22 		mesh.setVertices(vertices);
23 		mesh.setIndices(indices);
24 		return mesh;
25 	}
26 }


Como vemos non mandamos información de cor, só posición e textura polo que temos que modificar os arquivos de vertex.vert e fragment.frag.

  • Arquivo vertex.vert:
1 attribute vec3 a_position;
2 attribute vec2 a_texCoord0;
3 varying vec2 v_textCoord;
4 uniform mat4 u_worldView;
5 void main() 
6 {
7   gl_Position =  u_worldView *vec4(a_position,1);
8   v_textCoord = a_texCoord0;
9 }
  • Arquivo fragment.frag:
 1 #ifdef GL_ES 
 2   precision mediump float;
 3 #endif  
 4 varying vec2 v_textCoord;
 5 uniform sampler2D u_texture;
 6 void main() 
 7 {
 8   vec4 texColor = texture2D(u_texture, v_textCoord);
 9   gl_FragColor = texColor;
10   
11 }


  • Lembrar que o programa Vertex.vert e Fragment.frag xa os explicamos neste punto.


  • Crear unha clase de nome UD4_5_Animacion3D, que derive da clase Game e sexa chamada pola clase principal das diferentes versións (desktop, android,...).


Código da clase UD4_5_Animacion3D
Obxectivo: Visualiza un cubo en 3D cunha cámara en perspectiva.

 1 import com.badlogic.gdx.Game;
 2 import com.badlogic.gdx.Gdx;
 3 import com.badlogic.gdx.files.FileHandle;
 4 import com.badlogic.gdx.graphics.GL20;
 5 import com.badlogic.gdx.graphics.Mesh;
 6 import com.badlogic.gdx.graphics.PerspectiveCamera;
 7 import com.badlogic.gdx.graphics.Texture;
 8 import com.badlogic.gdx.graphics.glutils.ShaderProgram;
 9 
10 /**
11  * Funcionamento do ShaderProgram
12  * @author ANGEL
13  */
14 
15 public class UD4_5_Animacion3D extends Game {
16 
17 	private Mesh cubo; 
18 	private ShaderProgram shaderProgram;
19 
20 	private Texture textura;
21 	private PerspectiveCamera camara3d;
22 	
23 	@Override
24 	public void create() {
25 		// TODO Auto-generated method stub
26 
27 		shaderProgram = new ShaderProgram(Gdx.files.internal("vertex.vert"), Gdx.files.internal("fragment.frag"));
28 		if (shaderProgram.isCompiled() == false) {
29 			Gdx.app.log("ShaderError", shaderProgram.getLog());
30 			System.exit(0);
31 		}
32 	
33 		cubo = Shapes.genCube();
34 	
35 		FileHandle imageFileHandle = Gdx.files.internal("crate.png");
36 		textura = new Texture(imageFileHandle);
37 		
38 		camara3d = new PerspectiveCamera();
39 	}
40 	
41 	
42 	@Override
43 	public void render() {
44 
45 		Gdx.gl20.glClearColor(0f, 0f, 0f, 1f);
46 		Gdx.gl20.glClear(GL20.GL_COLOR_BUFFER_BIT|GL20.GL_DEPTH_BUFFER_BIT);
47 
48 		Gdx.gl20.glEnable(GL20.GL_DEPTH_TEST);
49 		
50 		
51 		shaderProgram.begin();
52 		shaderProgram.setUniformMatrix("u_worldView", camara3d.combined);
53 		
54 		textura.bind(0);		
55 		shaderProgram.setUniformi("u_texture", 0);
56 		
57 		cubo.render(shaderProgram, GL20.GL_TRIANGLES);
58 
59 		shaderProgram.end();
60 		
61 		Gdx.gl20.glDisable(GL20.GL_DEPTH_TEST);
62 
63 	}
64 	
65 	@Override
66 	public void resize (int width,int height){
67 		// Definimos os parámetros da cámara
68 		float aspectRatio = (float) width / (float) height; 
69 		camara3d.viewportWidth=aspectRatio*1f;
70 		camara3d.viewportHeight=1f;
71 		camara3d.far=1000f;
72 		camara3d.near=0.1f;
73 		camara3d.lookAt(0,0,0);
74 		camara3d.position.set(0f,0f,5f);
75 		camara3d.update();
76 	}
77 	
78 	@Override
79 	public void dispose(){
80 		shaderProgram.dispose();
81 		cubo.dispose();
82 
83 	}
84 
85 }


Se probades a executar o código aparecerá un cubo na pantalla.

Nota: Loxicamente os modelos 3D non os temos que definir desta forma, se non que usaremos programas de deseño en 3D ou modelos xa feitos.

Animando o cubo: translación, rotación e escalado


Clase utilizada: Matrix4.

Información na wiki: https://github.com/libgdx/libgdx/wiki/Vectors,-matrices,-quaternions



En OPEN GL imos poder facer tres tipos de operacións:

  • TRASLACIÓN: Lembrar que por defecto todos os obxectos se debuxan nas coordenadas (0,0,0). No noso mundo os obxectos (figuras Mesh) estarán nunha posición no espazo 3D. Será necesario trasladalos dende a posición (0,0,0) á posición no noso mundo.
  • ROTACIÓN: Podemos rotar a figura Mesh en calquera dos eixes (x,y,z). Lembrar que para saber como se rota a figura en cada un deles temos que imaxinar que a nosa cabeza está atravesado por unha lanza en cada un dos eixes. Así:
Eixe X: Moveríamos arriba-abaixo.
Eixe Y: Moveríamos esquerda-dereita.
Eixe Z; Moveríamos de lado, levando a cabeza ós ombreiros.
  • ESCALADO: O da escala o entenderedes mellor cando vexades o vídeo de como facer unha figura xeométrica 3D nun programa de deseño. Nestes programas a visualización 3D está dividida en cadrados. Cada cadrado ven ser unha unidade, de tal forma que se achegamos a cámara ata o obxecto, por defecto , o ancho e alto da cámara será dunha unidade. O que nos interesa é facer os obxectos de forma proporcional uns con outros. Así, se teño un modelo dun coche e está ocupando 4 unidades, se agora engado un deseño dun barco, non pode ser que o barco ocupe 4 unidades. Proporcionalmente ten que haber unha relación entre un modelo e outro.


LIBGDX UD4 Animacion 3.jpg
Exemplo dunha esfera ocupando 2x2x2 unidades.


Como ven vos estaredes imaxinando imos necesitar gardar unha matriz, unha matriz de modelado. Esta matriz é un vector 4x4.



LIBGDX UD4 Animacion 4.jpg


Exemplo de definición de matriz:

1 public Matrix4 matriz;


A matriz que aparece na imaxe se denomina matriz de identidade e ven a ser o número 1 na multiplicación, é dicir, que non fai nada.

Para cargar a matriz de identidade temos que chamar ó método idt():

1 public Matrix4 matriz;
2 ..........
3 
4 matriz.idt();

Agora será necesario aplicar os 3 tipos de operacións:

  • Traslación:
Método translate:
Exemplo: matriz.translate(posicion);
Sendo 'posicion' un Vector3 que indica cara onde se ten que trasladar. Este método está sobrecargado e temos varias opcións.
É importante comprender que sempre debemos de partir da matriz identidade e aplicar a nova posición que queremos chegar. Non o podemos facer a partires da última posición gardada.

Exemplo de código:

1 public Matrix4 matriz;
2 ..........
3 matriz.idt();
4 matriz.translate(10,1,0);
  • Rotación:
Método rotate:
Este método está sobrecargado e temos varias posibilidades.

Un exemplo:

1 public Matrix4 matriz;
2 ..........
3 matriz.idt();
4 matriz.translate(10,1,0);
5 matriz.rotate(1,0,0,90);

Neste caso estaremos rotando no eixe X a figura 90º. Neste caso o método rotate leva catro parámetros:

  • param1: Indica se o eixe X debe rotarse. Se ten valor 1 o rota e se ten 0 non fai nada nese eixe.
  • param2: Indica se o eixe Y debe rotarse. Se ten valor 1 o rota e se ten 0 non fai nada nese eixe.
  • param3: Indica se o eixe Z debe rotarse. Se ten valor 1 o rota e se ten 0 non fai nada nese eixe.
  • param4: Indica o número de grados da rotación.

Aviso: É importante primeiro facer a traslación e despois a rotación, xa que se o facemos ó revés, o punto está rotado cando aplicamos a traslación e por tanto se moverá a outro lugar diferente o que queremos. Se o fixeramos cunha esfera, teríamos o efecto dun movemento de traslación arredor do sol.


  • Escala:
Método scale:

Podemos modificar a escala en cada un dos eixes (X-Y-Z).

Por exemplo:

1 public Matrix4 matriz;
2 ..........
3 matriz.idt();
4 matriz.translate(10,1,0);
5 matriz.rotate(1,0,0,90);
6 matriz.scale(2,1,1);

Neste exemplo indicamos que o tamaño X da figura debe ser o dobre que o tamaño Y e Z.


Moi ben, chegou a hora de mover ese cubo :).

Preparación: Sen programar totalmente seguindo o MVC imos definir o noso mundo cun array de cubos. A clase Cubo terá a información necesaria, que será parecida a que tiñamos cando fixemos o xogo en 2D, pero tendo en conta as particularidades do 3D.

Código da clase Cubo
Obxectivo: Definimos a clase Cubo para engadir cubos ó noso mundo.

 1 import com.badlogic.gdx.math.Matrix4;
 2 import com.badlogic.gdx.math.Vector3;
 3 
 4 public class Cubo {
 5 	
 6 	public Matrix4 matriz;
 7 	public Vector3 posicion;
 8 	public float escala;
 9 	public Vector3 velocidade;
10 	private float rotar;
11 	private Vector3 temp;
12 	
13 	
14 	public Cubo(Vector3 pos, float escala,Vector3 velocidade){
15 		matriz = new Matrix4();
16 		posicion = pos;
17 		this.escala=escala;
18 		this.velocidade = velocidade;
19 		
20 		temp = new Vector3();
21 		
22 	}
23 	public void update(float delta){
24 		
25 		temp.set(velocidade);
26 		posicion.add(temp.scl(delta));
27 		
28 		matriz.idt();
29 		matriz.translate(posicion);
30 	}
31 
32 }

Comentemos o código:

  • Liñas 6-11: O que necesitamos gardar da figura.
Liña 6: A matriz de modelado que vai gardar os datos necesarios a aplicar en cada punto do Mesh.
Liña 7: Como vemos temos unha posición (Vector3). Aplicaremos ó método translate sobre a matriz.
Liña 8: A escala (que vai ser aplicada ós tres eixes) e que tamén pode non ser necesaria, dependendo do xogo.
Liña 9: A velocidade. Tamén pode non ser necesaria (se non se move). Neste caso poderíamos ter diferentes velocidades en cada un dos eixes. Se a velocidade é a mesma en calquera dos eixes no que se mova podería ser un float en vez dun Vector3.
Liña 10: O ángulo de rotación, pero se no noso xogo non o necesitamos podemos eliminar esta propiedade.
Liña 11: Vector temporal para non ten que estar creando novos no método update. Lembrar que os métodos cpy, scl,...aplicados a un vector3 modifican o vector orixinal.
  • Liñas 14-22: Gardamos nas propiedades os valores enviados no constructor.
  • Liña 26: Modificamos o vector posición en función de delta igual que fixemos no xogo 2D.
  • Liñas 28-29: Modificamos a matriz de modelado cos datos da nova posición. Fixarse como temos que partir da matriz identidade (liña 28).

Máis adiante faremos a rotación e escalado. Polo de agora o deixamos así...


Agora chega o momento de definir o mundo:

Código da clase Mundo
Obxectivo: Engadir dous cubos ó mundo.

 1 import java.util.ArrayList;
 2 
 3 import com.badlogic.gdx.math.Vector3;
 4 
 5 public class Mundo {
 6 	public ArrayList<Cubo>cubos;
 7 	
 8 	public Mundo(){
 9 		cubos = new ArrayList<Cubo>();
10 		
11 		cubos.add(new Cubo(new Vector3(0,0,-3f),1f,new Vector3(1,0,0)));
12 
13 		cubos.add(new Cubo(new Vector3(4f,0,-3f),5f,new Vector3(-1,0,0)));
14 
15 	}
16 
17 }

Esta clase non ten nada que comentar. Creamos dous cubos.


Agora chega o momento de pasarlle esta matriz ó Shader Program para que teña en conta onde están situados os puntos. Para iso temos que multiplicar a matriz combinada da cámara coa matriz de modelado do obxecto, pero tendo coidado de ter gardada a matriz combinada xa que a operación de multiplicación modifica a matriz.

Código da clase UD4_5_Animacion3D
Obxectivo: Movemos os cubos.

 1 	@Override
 2 	public void render() {
 3 
 4 		Gdx.gl20.glClearColor(0f, 0f, 0f, 1f);
 5 		Gdx.gl20.glClear(GL20.GL_COLOR_BUFFER_BIT|GL20.GL_DEPTH_BUFFER_BIT);
 6 
 7 		Gdx.gl20.glEnable(GL20.GL_DEPTH_TEST);
 8 		
 9 		
10 		shaderProgram.begin();
11 		
12 		textura.bind(0);		
13 		shaderProgram.setUniformi("u_texture", 0);
14 		
15 		for (Cubo cub : mundo.cubos){
16 			cub.update(Gdx.graphics.getDeltaTime());
17 			shaderProgram.setUniformMatrix("u_worldView", camara3d.combined.cpy().mul(cub.matriz));
18 			cubo.render(shaderProgram, GL20.GL_TRIANGLES);
19 			
20 			if (Math.abs(cub.posicion.x)>=10){
21 				cub.velocidade.x *=-1;
22 			}
23 			
24 		}
25 
26 		shaderProgram.end();
27 		
28 		Gdx.gl20.glDisable(GL20.GL_DEPTH_TEST);
29 
30 	}

Liña 17: Por cada cubo do mundo (estamos a percorrer o array) multiplicamos unha copia da matriz combinada da cámara, polo matriz de modelado do obxecto. Chamamos ó método cpy() para facer unha copia. O ideal é ter unha matriz temporal xa instanciada no constructor e asinarlle o valor da matriz combinada antes de multiplicar.

Liña 20-22: Código posto para que os cubos cambien de dirección.


Imos modificar ó método update da clase Cubo para que rote e escale os cubos de acordo ós datos enviados no constructor.

 1 	public void update(float delta){
 2 		
 3 		temp.set(velocidade);
 4 		posicion.add(temp.scl(delta));
 5 		
 6 		rotar+=60f*delta;
 7 
 8 		matriz.idt();
 9 		matriz.translate(posicion);
10 		matriz.scl(escala);
11 		matriz.rotate(1, 1, 1, rotar);
12 	}
  • Liña 6: Gardamos o ángulo de rotación.
  • Liña 10: Escalamos a matriz en todos os eixes.
  • Liñá 11: Rotamos a matriz en todos os eixes.

Dará como resultado isto:


LIBGDX UD4 Animacion 5.jpg



Como vemos un dos cubos é moi grande pola escala que enviamos no constructor.


NOTA: Lembrar que cando fagamos unha rotación, primeiro debemos de aplicar a escala á matriz.




-- Ángel D. Fernández González -- (2015).