LIBGDX Camara3D
UNIDADE 4: A cámara en 3D.
Sumario
OrthographicCamera
Clase: http://libgdx.badlogicgames.com/nightlies/docs/api/com/badlogic/gdx/graphics/OrthographicCamera.html
Información na wiki: https://github.com/libgdx/libgdx/wiki/Orthographic-camera
Agora mesmo estamos a ver unha Proxección Ortográfica. Neste tipo de proxección, non existe unha perspectiva dos obxectos que se visualizan na pantalla.
Dita cámara por defecto visualiza o seguinte espazo:
Sendo a coordenada Z a que sairía da pantalla, indo dende +1 (mirando cara nós, saíndo da pantalla) ata -1 (cara dentro da pantalla).
Podemos comprobar como se posicionamos o noso triángulo na coordenada Z = -1.1f xa non se visualiza (comprobádeo).
Do anterior podemos concluír que a cámara visualiza un volume:
Fixarse onde está a cámara e o resultado da proxección. Ó ter unha cámara ortográfica (en 2D) todo o que visualiza a cámara (estea diante ou detrás) se vai plasmar sobre un plano, levando os punto de forma paralela.
Para determinar o tamaño do volume do que se ve, se ten que indicar o plano NEAR (preto) e o plano FAR (afastado).
O plano NEAR se determina por catro coordenadas (left, top, right, bottom) e despois indicamos dúas distancias (near / far). O tamaño do plano far é igual o tamaño do plano near.
Fixarse como neste tipo de perspectiva podemos ter un near que quede por detrás da cámara (como é o exemplo anterior). O que fai dita perspectiva será coller todo o que hai dentro deste volume e visualizalo de forma 'plana'.
Nota: A todo o volume que se visualiza e o que se coñece coma View Frustrum.
Neste caso se visualiza o mesmo que no caso anterior xa que segue 'collendo' os dous obxectos. Imos crear dous triángulos (de cores vermello e verde) sen texturas e imos colocalos nas coordenadas Z -3 e -5 respectivamente, situando o triángulo verde na coordenada X dende a posición 0 ata a posición 1f e deixando o triángulo vermello na posición do exercicio anterior (-0,5 a +0,5 en X).
Preparación do exercicio:
Crear uhna nova clase de nome UD4_2_Camara2D que derive da clase Game e cambiade os diferentes proxectos para que executen dita clase.
Código da clase UD4_2_Camara2D
Obxectivo: Visualizar dous triángulos.
1 import com.badlogic.gdx.Game;
2 import com.badlogic.gdx.Gdx;
3 import com.badlogic.gdx.graphics.GL20;
4 import com.badlogic.gdx.graphics.Mesh;
5 import com.badlogic.gdx.graphics.OrthographicCamera;
6 import com.badlogic.gdx.graphics.PerspectiveCamera;
7 import com.badlogic.gdx.graphics.VertexAttribute;
8 import com.badlogic.gdx.graphics.glutils.ShaderProgram;
9
10 /**
11 * Traballos coa clase Mesh para definir un triángulo en 3D
12 * @author ANGEL
13 */
14
15 public class UD4_2_Camara2D extends Game {
16
17 private Mesh trianguloRed,trianguloGreen;
18 private ShaderProgram shaderProgram;
19
20 private PerspectiveCamera camara3d;
21 private OrthographicCamera camara2d;
22
23
24
25 @Override
26 public void create() {
27 // TODO Auto-generated method stub
28
29 // create shader program
30 String vertexShader = "attribute vec4 "+ ShaderProgram.POSITION_ATTRIBUTE +";\n"
31 + "attribute vec4 " + ShaderProgram.COLOR_ATTRIBUTE +"; \n"
32 + "uniform mat4 u_worldView; \n"
33 + "varying vec4 v_color; \n"
34 + "void main() \n"
35 + "{ \n"
36 + " gl_Position = u_worldView *" + ShaderProgram.POSITION_ATTRIBUTE +";\n"
37 + " v_color = " + ShaderProgram.COLOR_ATTRIBUTE +";\n"
38 +"} \n";
39 String fragmentShader = "#ifdef GL_ES \n"
40 + "precision mediump float; \n"
41 + "#endif \n"
42 + "varying vec4 v_color; \n"
43 + "void main() \n"
44 + "{ \n"
45 + " gl_FragColor = v_color; \n"
46 + "} \n";
47
48 // Creamos o ShaderProgram en base ós programas definidos anteriormente
49 shaderProgram = new ShaderProgram(vertexShader, fragmentShader);
50 if (shaderProgram.isCompiled() == false) {
51 Gdx.app.log("ShaderError", shaderProgram.getLog());
52 System.exit(0);
53 }
54
55 trianguloRed = new Mesh(true, 3, 3, VertexAttribute.Position(), VertexAttribute.ColorUnpacked());
56 trianguloRed.setVertices(new float[] {
57 -0.5f, -0.5f, -3f, 1f,0f,0f,1f,
58 0.5f, -0.5f, -3f, 1f,0f,0f,1f,
59 0f, 0.5f, -3f, 1f,0f,0f,1f
60 });
61 trianguloRed.setIndices(new short[] {0, 1, 2});
62
63 trianguloGreen = new Mesh(true, 3, 3, VertexAttribute.Position(), VertexAttribute.ColorUnpacked());
64 trianguloGreen.setVertices(new float[] { // É o verde, se ve detrás
65 0f, -0.5f, -5f, 0f,1f,0f,1f,
66 1.0f, -0.5f, -5f, 0f,1f,0f,1f,
67 0.5f, 0.5f, -5f, 0f,1f,0f,1f });
68 trianguloGreen.setIndices(new short[] {0, 1, 2});
69
70 }
71
72
73
74
75 @Override
76 public void dispose(){
77 shaderProgram.dispose();
78 trianguloRed.dispose();
79 trianguloGreen.dispose();
80
81 }
82
83 }
Agora chega o momento de indicarlle a cámara cal é o seu volume de visualización.
Isto o podemos facer cun obxecto cámara ou directamente no método render.
Pero antes disto temos que falar doutro concepto que existe en OPEN GL ES que son as matrices.
Todas as modificacións que se fan sobre os obxectos que se van visualizar (cambio de perspectiva, movemento da cámara, cambiar o tamaño do que se visualiza, rotar ou trasladar,...) se fai mediante unha serie de operacións matemáticas con matrices. Ditas matrices sofren modificacións e se aplican a cada un dos vértices que conforman a figura xeométrica que queremos debuxar.
Os tres tipos de matrices que existen son: matriz de proxección (GL_PROJECTION), matriz de modelado (GL_MODELVIEW) e matriz de textura (GL_TEXTURE). No caso de querer cambiar o tamaño de visualización o temos que facer sobre a matriz de proxección, mentres que o debuxado (render) dos obxectos o temos que facer na matriz de modelado.
Imos definir unha cámara ortográfica:
1 private OrthographicCamera camara2d;
2
3 ......
4 camara2d = new OrthographicCamera(2f,2f);
5 camara2d.near=0.1f;
6 camara2d.far=100f;
7 camara2d.update();
Como vemos estamos a definir o seu ViewFrustum o área de visualización. A cámara vai ver 100 unidades de distancia cara a adiante (plano near e far) e o seu plano near (igual en tamaño ó plano far) terá un tamaño de 2x2 unidades.
IMPORTANTE: É necesario chamar ó método update cando se fagan modificacións sobre a cámara.
Cando chamamos ó método update estamos a actualizar a matriz de proxección da cámara.
Podedes acceder a dita matriz escribindo: camara2d.projection
Ó mesmo tempo, a cámara se posiciona nun lugar por defecto. Este lugar é a metade do viewportwidth e viewportheight da definición. Así, no noso caso, a cámara estaría situada nas coordenadas:
- X = 2f / 2 = 1f;
- Y = 2f /2 = 1f;
- Z = 0;
Cando movemos a cámara estamos a modificar a súa matriz de modelado. Ó chamar o método update esta matriz se actualiza. Podedes acceder a dita matriz escribindo: camara2d.view
Na cámara temos unha matriz que é a combinación das dúas matrices anteriores, e que se denomina combined. É esta matriz a que temos que aplicar a cada un dos puntos dos obxectos do noso mundo para que se rendericen adecuadamente.
Na versión OPEN GL ES 2.0 temos que pasarlle dita matriz como parámetro ó Shader Program para que a aplique a cada punto.
Seguindo o noso exemplo:
1 import com.badlogic.gdx.Game;
2 import com.badlogic.gdx.Gdx;
3 import com.badlogic.gdx.graphics.GL20;
4 import com.badlogic.gdx.graphics.Mesh;
5 import com.badlogic.gdx.graphics.OrthographicCamera;
6 import com.badlogic.gdx.graphics.VertexAttribute;
7 import com.badlogic.gdx.graphics.glutils.ShaderProgram;
8
9 /**
10 * Uso da cámara orthographic e perspective
11 * @author ANGEL
12 */
13
14 public class UD4_2_Camara2D extends Game {
15
16 private Mesh trianguloRed,trianguloGreen;
17 private ShaderProgram shaderProgram;
18
19 private OrthographicCamera camara2d;
20
21 @Override
22 public void create() {
23 // TODO Auto-generated method stub
24
25 // create shader program
26 String vertexShader = "attribute vec4 "+ ShaderProgram.POSITION_ATTRIBUTE +";\n"
27 + "attribute vec4 " + ShaderProgram.COLOR_ATTRIBUTE +"; \n"
28 + "uniform mat4 u_worldView; \n"
29 + "varying vec4 v_color; \n"
30 + "void main() \n"
31 + "{ \n"
32 + " gl_Position = u_worldView *" + ShaderProgram.POSITION_ATTRIBUTE +";\n"
33 + " v_color = " + ShaderProgram.COLOR_ATTRIBUTE +";\n"
34 +"} \n";
35 String fragmentShader = "#ifdef GL_ES \n"
36 + "precision mediump float; \n"
37 + "#endif \n"
38 + "varying vec4 v_color; \n"
39 + "void main() \n"
40 + "{ \n"
41 + " gl_FragColor = v_color; \n"
42 + "} \n";
43
44 // Creamos o ShaderProgram en base ós programas definidos anteriormente
45 shaderProgram = new ShaderProgram(vertexShader, fragmentShader);
46 if (shaderProgram.isCompiled() == false) {
47 Gdx.app.log("ShaderError", shaderProgram.getLog());
48 System.exit(0);
49 }
50
51 trianguloRed = new Mesh(true, 3, 3, VertexAttribute.Position(), VertexAttribute.ColorUnpacked());
52 trianguloRed.setVertices(new float[] {
53 -0.5f, -0.5f, -3f, 1f,0f,0f,1f,
54 0.5f, -0.5f, -3f, 1f,0f,0f,1f,
55 0f, 0.5f, -3f, 1f,0f,0f,1f
56 });
57 trianguloRed.setIndices(new short[] {0, 1, 2});
58
59 trianguloGreen = new Mesh(true, 3, 3, VertexAttribute.Position(), VertexAttribute.ColorUnpacked());
60 trianguloGreen.setVertices(new float[] { // É o verde, se ve detrás
61 0f, -0.5f, -5f, 0f,1f,0f,1f,
62 1.0f, -0.5f, -5f, 0f,1f,0f,1f,
63 0.5f, 0.5f, -5f, 0f,1f,0f,1f });
64 trianguloGreen.setIndices(new short[] {0, 1, 2});
65
66 // Definimos os parámetros da cámara
67 camara2d = new OrthographicCamera(2f,2f);
68 camara2d.near=0.1f;
69 camara2d.far=100f;
70 camara2d.update();
71 }
72
73
74
75
76 @Override
77 public void render() {
78
79 Gdx.gl20.glClearColor(0f, 0f, 0f, 1f);
80 Gdx.gl20.glClear(GL20.GL_COLOR_BUFFER_BIT);
81
82
83 shaderProgram.begin();
84 shaderProgram.setUniformMatrix("u_worldView", camara2d.combined);
85 trianguloRed.render(shaderProgram, GL20.GL_TRIANGLES,0,3);
86 trianguloGreen.render(shaderProgram, GL20.GL_TRIANGLES,0,3);
87
88 shaderProgram.end();
89
90 }
91
92 @Override
93 public void resize (int width,int height){
94 }
95
96 @Override
97 public void dispose(){
98 shaderProgram.dispose();
99 trianguloRed.dispose();
100 trianguloGreen.dispose();
101
102 }
103
104 }
Analicemos o código:
- Liñas 19 e 67-71: Definimos a cámara ortográfica, o seu viewport e os planos far e near.
- Liña 84: Pasamos ó Shader Program a matriz combinada (Model - View) da cámara para que a aplique a cada punto do obxecto Mesh.
- Liñas 85-86: Debuxamos os dous triángulos.
Se executamos este código dará como resultado isto:
TAREFA 4.2 A FACER: Esta parte está asociada á realización dunha tarefa.
Se queremos podemos modificar a posición da cámara, polo que os triángulos parecen que se moven, pero será a cámara quen o faga.
Se posicionamos a cámara nun principio e non se move, teríamos que facelo no constructor, ou onde teñamos o código que define os planos far e near da cámara.
No noso caso imos facelo no render, xa que imos facer que se mova a cámara ata que os triángulos deixen de estar dentro do viewfrustrum da cámara.
Código da clase UD4_2_Camara2D
Obxectivo: Mover a cámara.
1 private float tempo=0;
2 .............
3
4 @Override
5 public void render() {
6
7 Gdx.gl20.glClearColor(0f, 0f, 0f, 1f);
8 Gdx.gl20.glClear(GL20.GL_COLOR_BUFFER_BIT);
9
10 tempo += Gdx.graphics.getDeltaTime();
11 camara2d.position.sub(0,0,tempo*0.2f);
12 camara2d.update();
13
14 shaderProgram.begin();
15 shaderProgram.setUniformMatrix("u_worldView", camara2d.combined);
16
17 trianguloRed.render(shaderProgram, GL20.GL_TRIANGLES,0,3);
18 trianguloGreen.render(shaderProgram, GL20.GL_TRIANGLES,0,3);
19
20 shaderProgram.end();
21
22 }
Se executades o código veredes que ó cabo dun rato os triángulos desaparecen.
Sabedes por que non van afastándose pouco a pouco da cámara ?
Porque estamos utilizando unha cámara 2D e nesta cámara non hai perspectiva. Os obxectos se 'aplastan' contra a parede indepedentemente da distancia.
Algoritmo Z-Buffer
Información na wiki: http://es.wikipedia.org/wiki/Z-Buffer
Se vos fixades no exercicio anterior o triángulo verde (coordenada z=-5) está máis afastado que o triángulo vermello (coordenada z=-3). Sen embargo se debuxa o verde por enriba.
Isto é debido a que agora mesmo OPEN GL non ten en conta a coordenada Z para determinar que obxecto se debuxa antes ou despois.
Para solucionalo en OPELGL temos dúas opcións:
- Algoritmo das caras de atrás: consiste en ocultar as caras que non se debuxarían porque formarían parte da parte traseira do obxecto:
- Algoritmo do Z-Buffer: consiste en ter gardada nunha matriz a posición Z de cada punto de tal forma que cando se debuxa cada punto se comproba o Z do novo punto e se verifica que non exista outro punto no mesmo sitio cun Z máis grande (iría diante).
Nos imos utilizar dita técnica. Para isto temos que:
- Dicirlle que cando borre a pantalla tamén borre os datos do Z-buffer:
1 public void render(){ 2 Gdx.gl20.glClear(GL20.GL_COLOR_BUFFER_BIT|GL20.GL_DEPTH_BUFFER_BIT); 3 ..............
- Antes de debuxar debemos habilitar dito modo:
1 Gdx.gl20.glEnable(GL20.GL_DEPTH_TEST);
- Debuxamos e deshabilitamos o modo:
1 shaderProgram.begin(); 2 shaderProgram.setUniformMatrix("u_worldView", camara2d.combined); 3 4 trianguloRed.render(shaderProgram, GL20.GL_TRIANGLES,0,3); 5 trianguloGreen.render(shaderProgram, GL20.GL_TRIANGLES,0,3); 6 7 shaderProgram.end(); 8 Gdx.gl20.glDisable(GL20.GL_DEPTH_TEST);
Relación de aspecto
Sobre este punto xa falamos no desenvolvemento dos xogo 2D.
Fixarse que pasa se alongamos a pantalla (sería equivalente a executar a nosa aplicación nun dispositivo móbil cunha resolución maior en ancho):
Como vemos os triángulos se alongan.
Isto é debido a que o ViewPort que definimos sempre ten 2f de ancho, polo que se aumenta a resolución os triángulos se teñen que adaptar á nova medida.
Para evitalo, unha posible solución sería sobreescribir o método resize para que aumente o tamaño do width do viewport en función do ancho da pantalla (relación de aspecto):
Nota: quitamos todo o código do constructor da clase referido á cámara.
public void create() {
......
camara2d = new OrthographicCamera(2f,2f);
camara2d.near=0.1f;
camara2d.far=100f;
camara2d.update();
No método resize:
1 @Override
2 public void resize (int width,int height){
3 // Definimos os parámetros da cámara
4
5 float aspectRatio = (float) width / (float) height;
6 camara2d = new OrthographicCamera();
7 camara2d.viewportWidth = 2f*aspectRatio;
8 camara2d.viewportHeight = 2f;
9 camara2d.near=0.1f;
10 camara2d.far=10f;
11 camara2d.update();
12
13
14 }
O resultado agora é este:
O que estamos a facer é ampliar o width da cámara para que manteña a proporción de aspecto có height.
Dependendo do xogo esta pode ser unha opción ou non, xa que os usuarios que tiveran una resolución maior poderían ver máis partes do xogo (ven máis ancho).
Veremos máis adiante que na cámara 3D, non importa o tamaño do width-height, no senso que o máximo que imos ter cando o obxecto se achegue á cámara será un valor baixo, de 1f x 1f ou 1fx2f. Só imos establecer a relación de aspecto.
Transparencia
A transparencia ven indicado polo valor alfa nos datos que enviamos de cor en cada un dos vértices.
Nota: Lembrade que un valor 1 é opaco e un valor 0 é transparente.
Para que funcione a transparencia temos que facer o seguinte:
- Todos os obxectos con transparencias teñen que estar renderizados dende o máis distante á cámara ata o máis preto, nesa orde.
- Todos os obxectos opacos teñen que estar renderizados primeiro e non importa a orde.
No noso caso temos que renderizar primeiro a triángulo verde (é o máis distante), despois o vermello.
Ademais temos que activar o Blending (transparencia) en Open GL dunha forma moi parecido a como activamos o Z-Buffer:
1 Gdx.gl20.glEnable(GL20.GL_BLEND); //ACTIVAMOS
2 Gdx.gl20.glBlendFunc(GL20.GL_SRC_ALPHA , GL20.GL_ONE_MINUS_SRC_ALPHA) ;
3
4 Gdx.gl20.glDisable(GL20.GL_BLEND) ; // DESACTIVAMOS
Modificamos o exercicio UD4_2_Camara2D para engadir a transparencia.
Fixarse como seguindo as normas primeiro imos debuxar o triángulo verde (que ten alfa = 1) e despois o vermello que ten as transparencias (alfa=0.5f)
Código da clase UD4_2_Camara2D
Obxectivo: Engadir transparencias ó triángulo vermello.
1 import com.badlogic.gdx.Game;
2 import com.badlogic.gdx.Gdx;
3 import com.badlogic.gdx.graphics.GL20;
4 import com.badlogic.gdx.graphics.Mesh;
5 import com.badlogic.gdx.graphics.OrthographicCamera;
6 import com.badlogic.gdx.graphics.VertexAttribute;
7 import com.badlogic.gdx.graphics.glutils.ShaderProgram;
8
9 /**
10 * Uso da cámara orthographic e perspective
11 * @author ANGEL
12 */
13
14 public class UD4_2_Camara2D extends Game {
15
16 private Mesh trianguloRed,trianguloGreen;
17 private ShaderProgram shaderProgram;
18
19 private OrthographicCamera camara2d;
20
21 @Override
22 public void create() {
23 // TODO Auto-generated method stub
24
25 // create shader program
26 String vertexShader = "attribute vec4 "+ ShaderProgram.POSITION_ATTRIBUTE +";\n"
27 + "attribute vec4 " + ShaderProgram.COLOR_ATTRIBUTE +"; \n"
28 + "uniform mat4 u_worldView; \n"
29 + "varying vec4 v_color; \n"
30 + "void main() \n"
31 + "{ \n"
32 + " gl_Position = u_worldView *" + ShaderProgram.POSITION_ATTRIBUTE +";\n"
33 + " v_color = " + ShaderProgram.COLOR_ATTRIBUTE +";\n"
34 +"} \n";
35 String fragmentShader = "#ifdef GL_ES \n"
36 + "precision mediump float; \n"
37 + "#endif \n"
38 + "varying vec4 v_color; \n"
39 + "void main() \n"
40 + "{ \n"
41 + " gl_FragColor = v_color; \n"
42 + "} \n";
43
44 // Creamos o ShaderProgram en base ós programas definidos anteriormente
45 shaderProgram = new ShaderProgram(vertexShader, fragmentShader);
46 if (shaderProgram.isCompiled() == false) {
47 Gdx.app.log("ShaderError", shaderProgram.getLog());
48 System.exit(0);
49 }
50
51 trianguloRed = new Mesh(true, 3, 3, VertexAttribute.Position(), VertexAttribute.ColorUnpacked());
52 trianguloRed.setVertices(new float[] {
53 -0.5f, -0.5f, -3f, 1f,0f,0f,0.5f,
54 0.5f, -0.5f, -3f, 1f,0f,0f,0.5f,
55 0f, 0.5f, -3f, 1f,0f,0f,0.5f
56 });
57 trianguloRed.setIndices(new short[] {0, 1, 2});
58
59 trianguloGreen = new Mesh(true, 3, 3, VertexAttribute.Position(), VertexAttribute.ColorUnpacked());
60 trianguloGreen.setVertices(new float[] { // É o verde, se ve detrás
61 0f, -0.5f, -5f, 0f,1f,0f,1f,
62 1.0f, -0.5f, -5f, 0f,1f,0f,1f,
63 0.5f, 0.5f, -5f, 0f,1f,0f,1f });
64 trianguloGreen.setIndices(new short[] {0, 1, 2});
65
66
67 }
68
69
70 @Override
71 public void render() {
72
73 Gdx.gl20.glClearColor(0f, 0f, 0f, 1f);
74 Gdx.gl20.glClear(GL20.GL_COLOR_BUFFER_BIT|GL20.GL_DEPTH_BUFFER_BIT);
75
76
77 Gdx.gl20.glEnable(GL20.GL_DEPTH_TEST);
78 Gdx.gl20.glEnable(GL20.GL_BLEND);
79 Gdx.gl20.glBlendFunc(GL20.GL_SRC_ALPHA , GL20.GL_ONE_MINUS_SRC_ALPHA) ;
80
81
82 shaderProgram.begin();
83 shaderProgram.setUniformMatrix("u_worldView", camara2d.combined);
84
85 trianguloGreen.render(shaderProgram, GL20.GL_TRIANGLES,0,3);
86 trianguloRed.render(shaderProgram, GL20.GL_TRIANGLES,0,3);
87
88 shaderProgram.end();
89 Gdx.gl20.glDisable(GL20.GL_BLEND) ;
90
91 Gdx.gl20.glDisable(GL20.GL_DEPTH_TEST);
92
93 }
94
95 @Override
96 public void resize (int width,int height){
97 // Definimos os parámetros da cámara
98
99 float aspectRatio = (float) width / (float) height;
100 camara2d = new OrthographicCamera();
101 camara2d.viewportWidth = 2f*aspectRatio;
102 camara2d.viewportHeight = 2f;
103 camara2d.near=0.1f;
104 camara2d.far=10f;
105 camara2d.update();
106
107
108 }
109
110 @Override
111 public void dispose(){
112 shaderProgram.dispose();
113 trianguloRed.dispose();
114 trianguloGreen.dispose();
115
116 }
117
118 }
Comentemos o código:
- Liña 52: Cambiamos o valor alfa da cor en cada vértice do triángulo vermello a 0.5f.
- Liñas 78-79: Habilitamos as transparencias.
- Liñas 78-79: Renderizamos primeiro os opacos e despois os transparentes.
- Liña 89: Deshabilitamos as transparencias.
O resultado:
PerspectiveCamera
Clase: PerspectiveCamera.
Información: http://www.badlogicgames.com/wordpress/?p=1550
Ata o de agora estamos utilizando unha visión ortogonal. Agora imos utilizar unha visión en perspectiva polo que os obxectos distantes veranse máis pequenos que os pretos.
Os conceptos son os mesmos aprendidos para a cámara orthogonal. Temos un ViewFrustrum que ven a ser o volume que se pode visualizar, definido polo plano near e far. A diferenza é que agora cando vexamos a imaxe en pantalla teremos unha 'perspectiva' (por exemplo, os obxectos máis pretos veranse máis grandes).
Fixarse como agora os raios non van paralelos, se non que parten dun ollo (a cámara).
Agora o plano far e plano near non teñen o mesmo tamaño, xa que este último vaise agrandando a medida que nos afastamos da cámara.
Preparación do exercicio:
Crear unha nova clase de nome UD4_3_Camara3D que derive da clase Game e cambiade os diferentes proxectos para que executen dita clase.
Código da clase UD4_3_Camara3D
Obxectivo: Visualizar dous triángulos utilizando unha cámara en perspectiva.
1 import com.badlogic.gdx.Game;
2 import com.badlogic.gdx.Gdx;
3 import com.badlogic.gdx.graphics.GL20;
4 import com.badlogic.gdx.graphics.Mesh;
5 import com.badlogic.gdx.graphics.OrthographicCamera;
6 import com.badlogic.gdx.graphics.PerspectiveCamera;
7 import com.badlogic.gdx.graphics.VertexAttribute;
8 import com.badlogic.gdx.graphics.glutils.ShaderProgram;
9 import com.badlogic.gdx.math.Matrix4;
10
11 /**
12 * Uso da cámara orthographic e perspective
13 * @author ANGEL
14 */
15
16 public class UD4_3_Camara3D extends Game {
17
18 private Mesh trianguloRed,trianguloGreen;
19 private ShaderProgram shaderProgram;
20
21 private PerspectiveCamera camara3d;
22
23 @Override
24 public void create() {
25 // TODO Auto-generated method stub
26
27 // create shader program
28 String vertexShader = "attribute vec4 "+ ShaderProgram.POSITION_ATTRIBUTE +";\n"
29 + "attribute vec4 " + ShaderProgram.COLOR_ATTRIBUTE +"; \n"
30 + "uniform mat4 u_worldView; \n"
31 + "varying vec4 v_color; \n"
32 + "void main() \n"
33 + "{ \n"
34 + " gl_Position = u_worldView *" + ShaderProgram.POSITION_ATTRIBUTE +";\n"
35 + " v_color = " + ShaderProgram.COLOR_ATTRIBUTE +";\n"
36 +"} \n";
37 String fragmentShader = "#ifdef GL_ES \n"
38 + "precision mediump float; \n"
39 + "#endif \n"
40 + "varying vec4 v_color; \n"
41 + "void main() \n"
42 + "{ \n"
43 + " gl_FragColor = v_color; \n"
44 + "} \n";
45
46 // Creamos o ShaderProgram en base ós programas definidos anteriormente
47 shaderProgram = new ShaderProgram(vertexShader, fragmentShader);
48 if (shaderProgram.isCompiled() == false) {
49 Gdx.app.log("ShaderError", shaderProgram.getLog());
50 System.exit(0);
51 }
52
53 trianguloRed = new Mesh(true, 3, 3, VertexAttribute.Position(), VertexAttribute.ColorUnpacked());
54 trianguloRed.setVertices(new float[] {
55 -0.5f, -0.5f, -3f, 1f,0f,0f,1f,
56 0.5f, -0.5f, -3f, 1f,0f,0f,1f,
57 0f, 0.5f, -3f, 1f,0f,0f,1f
58 });
59 trianguloRed.setIndices(new short[] {0, 1, 2});
60
61 trianguloGreen = new Mesh(true, 3, 3, VertexAttribute.Position(), VertexAttribute.ColorUnpacked());
62 trianguloGreen.setVertices(new float[] { // É o verde, se ve detrás
63 0f, -0.5f, -5f, 0f,1f,0f,1f,
64 1.0f, -0.5f, -5f, 0f,1f,0f,1f,
65 0.5f, 0.5f, -5f, 0f,1f,0f,1f });
66 trianguloGreen.setIndices(new short[] {0, 1, 2});
67
68 camara3d = new PerspectiveCamera();
69
70 }
71
72 @Override
73 public void render() {
74
75 Gdx.gl20.glClearColor(0f, 0f, 0f, 1f);
76 Gdx.gl20.glClear(GL20.GL_COLOR_BUFFER_BIT|GL20.GL_DEPTH_BUFFER_BIT);
77
78 Gdx.gl20.glEnable(GL20.GL_DEPTH_TEST);
79 shaderProgram.begin();
80 shaderProgram.setUniformMatrix("u_worldView", camara3d.combined);
81
82 trianguloRed.render(shaderProgram, GL20.GL_TRIANGLES,0,3);
83 trianguloGreen.render(shaderProgram, GL20.GL_TRIANGLES,0,3);
84
85 shaderProgram.end();
86 Gdx.gl20.glDisable(GL20.GL_DEPTH_TEST);
87
88 }
89
90 @Override
91 public void resize (int width,int height){
92 // Definimos os parámetros da cámara
93 float aspectRatio = (float) width / (float) height;
94 camara3d.viewportWidth = 2f*aspectRatio;
95 camara3d.viewportHeight = 2f;
96
97 camara3d.far=100f;
98 camara3d.near=0.1f;
99 camara3d.position.set(0,0,2f);
100 camara3d.update();
101 }
102
103 @Override
104 public void dispose(){
105 shaderProgram.dispose();
106 trianguloRed.dispose();
107 trianguloGreen.dispose();
108
109 }
110
111 }
Analicemos o código:
- Liña 21: Definimos o obxecto camara3d.
- Liña 68: Instanciamos a cámara 3D. O facemos aquí para optimizar o código, xa que poderíamos levalo ó método resize, pero dito método se chama dúas veces ó iniciar ó xogo e se hai un cambio na resolución volve a chamarse. Como neste momento non sabemos a resolución usamos o constructor sen parámetros.
- Liña 80: Pasamos á matriz combinada ó Shader Program.
- Liñas 93-100: Definimos o plano far, near e o viewportwidth - viewportheight. Tamén posicionamos a cámara para que mire cara ós triángulos.
O resultado:
Como vemos os triángulos se visualizan con perspectiva....
Un dos atributos que podemos modificar na cámara 3D é o que se coñece como Field of view (FOV).
Este dato o podemos dar directamente no constructor da forma:
- camara3d = new PerspectiveCamera(fov,viewportWidth,viewportHeight)
Ou accedendo á propiedade da forma:
- camara3d.fieldOfView=valor
Por defecto establece un ángulo alfa de 67 grados.
O ángulo alfa se coñece coma Field Of View (FOV) e representa o ángulo de visión da cámara sobre a proxección dos obxectos. Queda máis claro no seguinte gráfico:
Como podemos observar, o que se visualizaría na pantalla (screen window) variará en función deste ángulo, podendo producir un certo efecto de 'zoom' dependendo do ángulo utilizado.
MOI IMPORTANTE: Na cámara en perspectiva ten que cumplirse que 0 < near < far.
NOTA: O viewport width/height só serve para establecer a relación de aspecto. Lembrar que
estamos traballando en 3D e polo tanto non limitamos o ancho e alto do que vemos.
Por exemplo, se temos un cubo situado na posición (0,0,0) de tamaño 1f. O tamaño da pantalla onde se vai visualizar ten unha relación de aspecto de 1 (por exemplo 100x100 ou 200x200,...).
- cfg.width = 400;
- cfg.height = 400;
Situamos a cámara na posición (0f,0f,1.2f). Definimos a cámara có viewport width e height a 1:
- camara = new PerspectiveCamera(67, 1f , 1f);
Teríamos este resultado:
Se agora cambiamos o viewport width e poñemos dous:
O cubo se deforma xa que coa cámara estamos a dicirlle que pode debuxar, para cubrir todo o ancho da pantalla, un obxecto con dúas unidades de ancho (2f) e o cubo ten unha polo que para adaptalo a ese ancho 'o deforma'. Se queremos que o cubo sempre se vexa ben teremos que adaptar o ancho da pantalla en función da relación de aspecto (ancho e alto da resolución de pantalla).
- float aspectRatio = (float) width / (float) height;
- camara = new PerspectiveCamera(67, 1f*aspectRatio , 1f);
Se o deixamos así o ancho se adapta e o cubo sempre vaise debuxar ben:
Normalmente ponse:
- camara = new PerspectiveCamera(67, width*aspectRatio ,height);
Aínda que o efecto é o mesmo (lembrar que non estamos definindo o tamaño do que se ve, como no caso da cámara ortográfica, se non a relación de aspecto). ESTAMOS EN 3D.
-- Ángel D. Fernández González -- (2015).