LIBGDX Animacions3D

De MediaWiki
Saltar a: navegación, buscar

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).