Motor OpenGL
Cargando...
Buscando...
Nada coincide
2.Pasos de Renderizado

En esta página se explican los pasos a seguir para el renderizado de un triangulo.

2.1 Estructura de las Etapas

Las etapas marcadas en negrita son donde se pueden inyectar shaders para modificar el render.

  1. Vertex Input
  2. Vertex Shader
  3. Shape Assembly
  4. Geometry Shader
  5. Rasterization
  6. Fragment Shader
  7. Test and Blending

La GPU para renderizar necesita si o si un Vertex Shader y un Fragment Shader, el Geometry Shader es opcional.

2.2 Vertex Input

De momento se pasa un array de vértices. Cada vértice tiene 3 coordenadas x, y, z.

float vertices[] = {
-0.5f, -0.5f, 0.0f, // Inferior izq
0.5f, -0.5f, 0.0f, // Inferior der
0.0f, 0.5f, 0.0f // Punta
};

Para renderizar, las coordenadas tienen que estar en el sistema NDC, que son puntos entre -1.0 y 1.0, todo lo que esté fuera no se ve.

VBO

La memoria se controla mediante el Vertex Buffer Objects (VBO) que guarda una gran cantidad de vértices en la GPU.

Asique hay que crear un objecto de openGL

unsigned int VBO; // Se puede usar tambien GLuint
glGenBuffers(1, &VBO);

VBO se guarda el id de donde se almacena en memoria de la GPU es buffer

Hay que especificar el tipo de buffer, en este caso es GL_ARRAY_BUFFER

glBindBuffer(GL_ARRAY_BUFFER, VBO);

Ahora hay que introducir la información al buffer

glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

Los parámetros son:

  1. GL_ARRAY_BUFFER: el tipo de objeto (target).
  2. sizeof(vertices): el tamaño de los datos (lo que ocupa el vector de vertices).
  3. vertices: la información.
  4. GL_STATIC_DRAW: esto indica a la GPU como manejar la información
    1. GL_STREAM_DRAW: se setea 1 vez y se usa poco por la GPU
    2. GL_STATIC_DRAW: se setea 1 vez y se usa mucho
    3. GL_DYNAMIC_DRAW: la información cambia mucho y se usa mucho

2.3 Vertex Shader

Este indica a la GPU que hacer con los vértices que se pasan del paso anterior (que hacer con los vértices del VBO). Aquí un pequeño ejemplo que no los modifica solo los transmite al siguiente paso.

#version 330 core
layout (location = 0) in vec3 aPos;
void main() {
gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}

Copilar un shader

Primero hay que pasarlo a un const char*

const char* vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0";

Y luego creamos un objeto de tipo GL_VERTEX_SHADER

// Creamos el objeto de shader
GLuint vertexShader;
vertexShader = glCreateShader(GL_VERTEX_SHADER); // Se crea con el tipo especifico
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL); // Se linkea con el source code
glCompileShader(vertexShader); // Se compila

El número 1 especifica cuantas strings le pasamos, en nuestro caso es solo vertexShaderSource.

Así se puede comprobar si esta bien compilado y si no lo esta se muestran los errores en consola

int success;
char infoLog[512];
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" <<
infoLog << std::endl;
}

2.4 Fragment Shader

Este shader decide cual es el color final de cada vértice. (En verdad queda el paso de opacidad y bending, pero el color opaco final se decide aquí)

Aquí un pequeño ejemplo que solo pone el color a un tono de naranja

#version 330 core
out vec4 FragColor;
void main() {
FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
}

Y para crearlo se sigue el mismo proceso

const char* fragShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
"FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\0";
GLuint fragmentShader;
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragShaderSource, NULL);
glCompileShader(fragmentShader);

Shader Program

Ahora hay que linkear todos los shaders a un programa.

GLuint shaderProgram;
shaderProgram = glCreateProgram(); // Se crea el programa
glAttachShader(shaderProgram, vertexShader); // Se añaden los shaders al programa
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram); // Se linkea el programa
// Comprobar si hay errores de linker
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if (!success) {
glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
}
// Hay que borrar los shaders ya que ya se han compilado y linkeado
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);

Para usar el shader simplemente se llama a esta función

glUseProgram(shaderProgram);

2.5 Link Vertex Attributes

Una vez creado el programa hay que decirle como interpretar la información.

Vértice Componente X Componente Y Componente Z
VERTEX 1 Bytes 0 - 4 Bytes 4 - 8 Bytes 8 - 12
VERTEX 2 Bytes 12 - 16 Bytes 16 - 20 Bytes 20 - 24
VERTEX 3 Bytes 24 - 28 Bytes 28 - 32 Bytes 32 - 36

Configuración del Atributo (POSITION):

  • Stride (Salto): 12 bytes
  • Offset (Desplazamiento inicial): 0 bytes

Detalles del formato de datos

  • Los datos de posición se almacenan como valores de punto flotante (float) de 32 bits (4 bytes).
  • Cada posición está compuesta por 3 de esos valores (X, Y, Z).
  • No hay espacio (ni otros valores) entre cada conjunto de 3 valores. Los valores están empaquetados de forma compacta (tightly packed) en el arreglo.
  • El primer valor de los datos se encuentra exactamente al principio del búfer.
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); glEnableVertexAttribArray(0);

Parámetros:

  1. Indica cual atributo vamos a modificar, en el shader se especificó que sería location = 0
  2. El tamaño del vertex attribute, es decir 3 valores (x, y, z)
  3. Que tipo de data se almacena, (GL_FLOAT, 4bytes)
  4. Si queremos la data normalizada o no
  5. Aqui se indica el stride, que distancia hay entre vértices consecutivos (12 = 3 * 4Bytes)
  6. El offset de la data de posición (está al principio asique 0)

2.6 VAO

Para hacer el proceso de obtener los atributos de los vértices, lo mejor es utilizar un array

Flujo del VAO

Así se genera un VAO

GLuint VAO;
glGenVertexArrays(1, &VAO);
// 1. bind Vertex Array Object
glBindVertexArray(VAO);
// 2. copy our vertices array in a buffer for OpenGL to use
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 3. copy our index array in a element buffer for OpenGL to use
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices,
GL_STATIC_DRAW);
// 4. then set our vertex attributes pointers
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float),
(void*)0);
glEnableVertexAttribArray(0);

2.7 EBO

El EBO sirve para evitar repetición de vértices y va por índices, es decir:

float vertices[] = {
-0.5f, -0.5f, 0.0f, // Abajo izq
0.5f, -0.5f, 0.0f, // Abajo der
0.5f, 0.5f, 0.0f, // Arriba der
-0.5f, 0.5f, 0.0f, // Arriba izq
};
GLuint indices[] = {
0, 1, 3, // Primer triángulo
1, 2, 3, // Segundo triángulo
};

Luego se crea el buffer del EBO

GLuint EBO;
glGenBuffers(1, &EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

Luego al crear el VAO se añade el EBO

// 3. copy our index array in a element buffer for OpenGL to use
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

Y para renderizar seria así

glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);