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

  1. Coloreado: Aplicar colores a los vértices de los objetos.
  2. 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

  1. 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.
  2. 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.
  3. Render Loop:

    • Limpiamos el buffer de color.
    • Usamos el programa de shaders y dibujamos el triángulo.

Sombreado en OpenGL

Tipos de Sombreado

  1. Sombreado Plano (Flat Shading): Cada polígono tiene un solo color.
  2. Sombreado de Gouraud: Los colores se interpolan a lo largo de los vértices.
  3. 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

  1. Vertex Shader:
    • Calcula la posición del fragmento y la normal transformada.
  2. 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:

  1. Modifica el array de vértices para definir un cuadrado.
  2. Asigna colores diferentes a cada vértice.
  3. Renderiza el cuadrado.

Ejercicio 2: Implementar Sombreado de Gouraud

Objetivo: Implementar el sombreado de Gouraud en un objeto.

Instrucciones:

  1. Modifica los shaders para calcular la iluminación en el vertex shader.
  2. 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 indices

Solució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.

© Copyright 2024. Todos los derechos reservados