En esta sección, aprenderemos cómo aplicar colores y sombras a los objetos que renderizamos en OpenGL. Estos conceptos son fundamentales para crear gráficos visualmente atractivos y realistas.
Conceptos Clave
- Coloreado: Aplicar colores a los vértices de los objetos.
- Sombreado: Técnica para simular la forma en que la luz interactúa con las superficies de los objetos.
Coloreado en OpenGL
Coloreado de Vértices
En OpenGL, podemos asignar colores a los vértices de un objeto. Estos colores se interpolan a lo largo de las superficies del objeto durante el proceso de rasterización.
Ejemplo Práctico: Coloreado de un Triángulo
Vamos a colorear un triángulo con tres colores diferentes en cada vértice.
Código de Ejemplo
// Vertex Shader (vertex_shader.glsl)
#version 330 core
layout(location = 0) in vec3 aPos;
layout(location = 1) in vec3 aColor;
out vec3 ourColor;
void main()
{
gl_Position = vec4(aPos, 1.0);
ourColor = aColor;
}
// Fragment Shader (fragment_shader.glsl)
#version 330 core
in vec3 ourColor;
out vec4 FragColor;
void main()
{
FragColor = vec4(ourColor, 1.0);
}
// Main Program (main.cpp)
#include <glad/glad.h>
#include <GLFW/glfw3.h>
// Function prototypes
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow *window);
// Settings
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;
int main()
{
// Initialize GLFW
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// Create a windowed mode window and its OpenGL context
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
if (window == NULL)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
// Load all OpenGL function pointers
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
// Build and compile our shader program
// Vertex shader
unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
// Check for shader compile errors
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;
}
// Fragment shader
unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
// Check for shader compile errors
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
}
// Link shaders
unsigned int shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
// Check for linking errors
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if (!success) {
glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
}
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
// Set up vertex data (and buffer(s)) and configure vertex attributes
float vertices[] = {
// positions // colors
0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom right
-0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, // bottom left
0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f // top
};
unsigned int VBO, VAO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// position attribute
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// color attribute
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
// Render loop
while (!glfwWindowShouldClose(window))
{
// Input
processInput(window);
// Render
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// Draw our first triangle
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
// Swap buffers and poll IO events
glfwSwapBuffers(window);
glfwPollEvents();
}
// Deallocate all resources once they've outlived their purpose
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
// Terminate GLFW
glfwTerminate();
return 0;
}
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
glViewport(0, 0, width, height);
}
void processInput(GLFWwindow *window)
{
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
}Explicación del Código
-
Shaders:
- Vertex Shader: Define la posición de los vértices y pasa el color a los fragment shaders.
- Fragment Shader: Recibe el color interpolado y lo asigna al fragmento.
-
Configuración de Vértices:
- Definimos un array de vértices que incluye posiciones y colores.
- Configuramos los atributos de vértices para posiciones y colores.
-
Render Loop:
- Limpiamos el buffer de color.
- Usamos el programa de shaders y dibujamos el triángulo.
Sombreado en OpenGL
Tipos de Sombreado
- Sombreado Plano (Flat Shading): Cada polígono tiene un solo color.
- Sombreado de Gouraud: Los colores se interpolan a lo largo de los vértices.
- Sombreado de Phong: Los colores se interpolan a lo largo de los píxeles.
Ejemplo Práctico: Sombreado de Phong
Vamos a implementar el sombreado de Phong para un objeto simple.
Código de Ejemplo
// Vertex Shader (vertex_shader.glsl)
#version 330 core
layout(location = 0) in vec3 aPos;
layout(location = 1) in vec3 aNormal;
out vec3 FragPos;
out vec3 Normal;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
FragPos = vec3(model * vec4(aPos, 1.0));
Normal = mat3(transpose(inverse(model))) * aNormal;
gl_Position = projection * view * vec4(FragPos, 1.0);
}
// Fragment Shader (fragment_shader.glsl)
#version 330 core
out vec4 FragColor;
in vec3 FragPos;
in vec3 Normal;
uniform vec3 lightPos;
uniform vec3 viewPos;
uniform vec3 lightColor;
uniform vec3 objectColor;
void main()
{
// Ambient
float ambientStrength = 0.1;
vec3 ambient = ambientStrength * lightColor;
// Diffuse
vec3 norm = normalize(Normal);
vec3 lightDir = normalize(lightPos - FragPos);
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = diff * lightColor;
// Specular
float specularStrength = 0.5;
vec3 viewDir = normalize(viewPos - FragPos);
vec3 reflectDir = reflect(-lightDir, norm);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
vec3 specular = specularStrength * spec * lightColor;
vec3 result = (ambient + diffuse + specular) * objectColor;
FragColor = vec4(result, 1.0);
}Explicación del Código
- Vertex Shader:
- Calcula la posición del fragmento y la normal transformada.
- Fragment Shader:
- Calcula los componentes ambiental, difuso y especular de la iluminación.
- Combina estos componentes para obtener el color final del fragmento.
Ejercicios Prácticos
Ejercicio 1: Coloreado de un Cuadrado
Objetivo: Colorear un cuadrado con diferentes colores en cada vértice.
Instrucciones:
- Modifica el array de vértices para definir un cuadrado.
- Asigna colores diferentes a cada vértice.
- Renderiza el cuadrado.
Ejercicio 2: Implementar Sombreado de Gouraud
Objetivo: Implementar el sombreado de Gouraud en un objeto.
Instrucciones:
- Modifica los shaders para calcular la iluminación en el vertex shader.
- Interpola los colores calculados en el fragment shader.
Soluciones
Solución al Ejercicio 1
// Vertex data for a square
float vertices[] = {
// positions // colors
0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, // top right
0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, // bottom right
-0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom left
-0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 0.0f // top left
};
unsigned int indices[] = { // note that we start from 0!
0, 1, 3, // first triangle
1, 2, 3 // second triangle
};
// Setup code remains the same, just use the new vertex data and indicesSolución al Ejercicio 2
// Vertex Shader (vertex_shader.glsl)
#version 330 core
layout(location = 0) in vec3 aPos;
layout(location = 1) in vec3 aNormal;
out vec3 vertexColor;
uniform vec3 lightPos;
uniform vec3 viewPos;
uniform vec3 lightColor;
uniform vec3 objectColor;
void main()
{
// Ambient
float ambientStrength = 0.1;
vec3 ambient = ambientStrength * lightColor;
// Diffuse
vec3 norm = normalize(aNormal);
vec3 lightDir = normalize(lightPos - aPos);
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = diff * lightColor;
// Specular
float specularStrength = 0.5;
vec3 viewDir = normalize(viewPos - aPos);
vec3 reflectDir = reflect(-lightDir, norm);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
vec3 specular = specularStrength * spec * lightColor;
vec3 result = (ambient + diffuse + specular) * objectColor;
vertexColor = result;
gl_Position = vec4(aPos, 1.0);
}
// Fragment Shader (fragment_shader.glsl)
#version 330 core
in vec3 vertexColor;
out vec4 FragColor;
void main()
{
FragColor = vec4(vertexColor, 1.0);
}Conclusión
En esta sección, hemos aprendido cómo aplicar colores y sombras a los objetos en OpenGL. Hemos cubierto los conceptos básicos de coloreado de vértices y sombreado, y hemos implementado ejemplos prácticos para ilustrar estos conceptos. Con estos conocimientos, estás preparado para crear gráficos más complejos y visualmente atractivos en OpenGL. En la próxima sección, exploraremos cómo usar buffers para optimizar el renderizado.
Curso de Programación OpenGL
Módulo 1: Introducción a OpenGL
- ¿Qué es OpenGL?
- Configuración de tu Entorno de Desarrollo
- Creando tu Primer Programa OpenGL
- Entendiendo el Pipeline de OpenGL
Módulo 2: Renderizado Básico
- Dibujando Formas Básicas
- Entendiendo Coordenadas y Transformaciones
- Coloreado y Sombreado
- Usando Buffers
Módulo 3: Técnicas de Renderizado Intermedio
- Texturas y Mapeo de Texturas
- Iluminación y Materiales
- Mezcla y Transparencia
- Pruebas de Profundidad y Plantilla
Módulo 4: Técnicas de Renderizado Avanzado
Módulo 5: Optimización del Rendimiento
- Optimizando Código OpenGL
- Usando Objetos de Array de Vértices (VAOs)
- Gestión Eficiente de Memoria
- Perfilado y Depuración
