Visualización 3D usando OpenGL

Las matrices son ampliamente utilizadas en la visualización de gráficos 3D, OpenGL las utiliza para aplicar transformaciones (escalado, traslación, rotación) a los objetos 3D, manipular la vista de proyección, entre otras cosas, para empezar veremos cómo mostrar un objeto tridimensional en pantalla (el clásico cubo).

Transformación de proyección


Define lo que estará visible en pantalla, OpenGL soporte dos tipos de proyección, perspectiva y ortográfica, usaremos la primera ya que nos proporciona un resultado realista, parecido al mundo real, la proyección ortográfica es usada comúnmente en programas CAD.

Para la creación de la matriz de proyección en perspectiva utilizaremos la función GLM perspective(fovy, aspect, zNear, zFar), fovy define el ángulo de visión, aspect la relación (ancho/alto) de la pantalla, zNear y zFar son las distancias de los planos de recorte.

opengl perpective transform
Ha esto se le llama pirámide de visualización, todo lo que se encuentre dentro del área marcada será lo que podremos visualizar en pantalla, en la figura inferior vemos como el cambio de ángulo de visión afecta la pirámide.

opengl fovy

Transformación de visualización


Esta matriz indica la ubicación de la cámara, a donde mira la misma y su orientación, la creamos mediante la función GLM lookAt(eye, center, up), donde eye es el punto donde se ubica la cámara, center es donde mira la cámara y up define el vector vertical.

opengl lookat
En este punto hemos definido la cámara, donde se ubica, hacia donde mira y el rango de visualización de la misma, todo lo hemos hecho utilizando matrices, la librería GLM no proporciona mat4, una matriz de 4x4 y las funciones para la creación y manipulación de estas matrices.

glm::mat4 Projection = glm::perspective(45.0f, 4.0f / 3.0f, 0.1f, 100.0f);
glm::mat4 View = glm::lookAt(glm::vec3(4, 3, -3), glm::vec3(0, 0, 0), glm::vec3(0, 1, 0));

Dibujar Cubo Usando Índices


Partiendo del tutorial anterior donde dibujamos un triángulo 2D haremos las siguientes modificaciones para agregar los 8 vértices necesarios para generar un cubo, agregamos también los índices necesarios para dibujar el cubo, recordemos que un índice especifica como se unen los vértices para formar una determinada figura.

// vetices para generar un cubo 3D
static const float vertex[] =
{
	 1.0f, -1.0f, -1.0f, 1.0f,
	 1.0f, -1.0f,  1.0f, 1.0f,
	-1.0f, -1.0f,  1.0f, 1.0f,
	-1.0f, -1.0f, -1.0f, 1.0f,
	 1.0f,  1.0f, -1.0f, 1.0f,
	 1.0f,  1.0f,  1.0f, 1.0f,
	-1.0f,  1.0f,  1.0f, 1.0f,
	-1.0f,  1.0f, -1.0f, 1.0f
};

// componetes RGBA para los colores de cada vertice
static const float color[] = { /.../ };

// indices usados para unir los vertices que componen el cubo
static const GLushort indices[] =
{
	0, 1, 2,
	7, 6, 5,
	4, 5, 1,
	5, 6, 2,
	6, 7, 3,
	0, 3, 7,
	3, 0, 2,
	4, 7, 5,
	0, 4, 1,
	1, 5, 2,
	2, 6, 3,
	4, 0, 7
};

// generar, almacenar el buffer de indices 
glGenBuffers(1, &index_buffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_buffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

El primer triangulo será dibujado usando los vértices indicados por los índices (0, 1, 2), el segundo usando los 3 vértices indicados por los siguientes índices (7, 6, 5), seguimos de este modo hasta dibujar los 12 triángulos que requiere un cubo.

Al igual que los vértices, los índices también deben ser almacenados en un buffer, el procedimiento es el mismo, puedes ir el tutorial anterior para más detalles, solo cambia el tipo a GL_ELEMENT_ARRAY_BUFFER.

Uso de uniform en shader


En el tutorial anterior vimos como pasar datos de vértices a los shaders, ahora requerimos enviar las matrices al shader para que el mismo aplique las correspondientes transformaciones, antes usamos un tipo de variable en los shader llamada attribute que usamos para manipular los buffer de color y vertices, ahora usaremos uniform a diferencia del attribute este retiene su valor en cada ejecución del shader.

Modificamos el vertex shader para recibir la matriz correspondiente a las transformaciones y lo multiplicamos por cada vértice en el buffer.

layout (location = 0) in vec4 vPosition;
layout (location = 1) in vec4 vColor;

uniform mat4 MVP;

out vec4 color;

void main(void)
{
     gl_Position =  MVP * vPosition;
     color = vColor; 
}

Para enviar la matriz al shader usaremos glUniformMatrix4fv(MatrixID, 1, GL_FALSE, &MVP[0][0]).

Podemos usar la multiplicación de matrices para crear una matriz total, esta contiene la matriz de proyección, la matriz de visualización y la matriz de modelo, pudimos pasarlas individualmente al shader y luego multiplicarlas, pero, la primera forma es más eficiente.

void onrender(double time) override {
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glEnable(GL_CULL_FACE);

	shader_simple.use();

	glm::mat4 Model = glm::rotate(glm::mat4(1.0f), (float)time, glm::vec3(0.0f, 1.0f, 0.0f));
	glm::mat4 Projection = glm::perspective(45.0f, aspect_ratio, 0.1f, 100.0f);
	glm::mat4 View = glm::lookAt(glm::vec3(4, 3, -3), glm::vec3(0, 0, 0), glm::vec3(0, 1, 0));

	glm::mat4 MVP = Projection * View * Model;
	
	// Enviar las tranformaciones al shader
	// MatrixID es el ID del uniform MVP obtenida por glGetUniformLocation(program, "MVP");
	glUniformMatrix4fv(MatrixID, 1, GL_FALSE, &MVP[0][0]);

	// Dibujar cubo usando los indices almacenados en el buffer,
	// 1 triangulo = 3 indices, 1 cara = 2 triangulos, 1 cubo = 6 caras.
	// 3 * 2 * 6 = 36 indices a dibujar 
	glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_SHORT, 0);
}

La matriz de modelo en esta caso llamada: Model, es la que contiene las transformaciones del modelo como: rotación, traslación y escalado, en este ejemplo aplicamos una rotación para hacer girar el cubo sobre el eje Y, más adelante veremos en detalle las transformaciones.

cubo en opengl
GitHub: Visualización 3D OpenGL

Comentarios

Temas relacionados

Entradas populares de este blog

tkinter Grid

Conectar SQL Server con Java

Controles y Contenedores JavaFX 8 - I

tkinter Canvas