En aquest tema, aprendrem a crear un visualitzador de models 3D utilitzant OpenGL. Aquest projecte ens permetrà aplicar molts dels conceptes apresos en els mòduls anteriors, com ara la càrrega de models, la gestió de textures, la il·luminació i les transformacions.

Objectius del Tema

  1. Càrrega de Models 3D: Aprendre a carregar models 3D des de fitxers.
  2. Renderització de Models: Renderitzar models 3D utilitzant OpenGL.
  3. Gestió de Càmeres: Implementar una càmera que permeti navegar pel model.
  4. Il·luminació i Materials: Aplicar tècniques d'il·luminació i materials per millorar l'aparença del model.
  5. Interacció de l'Usuari: Permetre la interacció de l'usuari per rotar, escalar i moure el model.

  1. Càrrega de Models 3D

1.1. Llibreria Assimp

Per carregar models 3D, utilitzarem la llibreria Assimp (Open Asset Import Library). Aquesta llibreria suporta molts formats de fitxers 3D com OBJ, FBX, COLLADA, entre d'altres.

Instal·lació d'Assimp

# En sistemes basats en Debian/Ubuntu
sudo apt-get install libassimp-dev

# En sistemes basats en Arch
sudo pacman -S assimp

1.2. Càrrega de Models amb Assimp

A continuació, es mostra un exemple de com carregar un model 3D utilitzant Assimp.

#include <assimp/Importer.hpp>
#include <assimp/scene.h>
#include <assimp/postprocess.h>
#include <iostream>

void loadModel(const std::string& path) {
    Assimp::Importer importer;
    const aiScene* scene = importer.ReadFile(path, aiProcess_Triangulate | aiProcess_FlipUVs);

    if (!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode) {
        std::cerr << "ERROR::ASSIMP::" << importer.GetErrorString() << std::endl;
        return;
    }

    // Processar el node arrel
    processNode(scene->mRootNode, scene);
}

void processNode(aiNode* node, const aiScene* scene) {
    // Processar tots els meshes (malles) del node actual
    for (unsigned int i = 0; i < node->mNumMeshes; i++) {
        aiMesh* mesh = scene->mMeshes[node->mMeshes[i]];
        processMesh(mesh, scene);
    }

    // Processar els nodes fills
    for (unsigned int i = 0; i < node->mNumChildren; i++) {
        processNode(node->mChildren[i], scene);
    }
}

void processMesh(aiMesh* mesh, const aiScene* scene) {
    // Aquí es processa la malla (vertices, textures, normals, etc.)
    // Exemple: carregar vertices
    std::vector<float> vertices;
    for (unsigned int i = 0; i < mesh->mNumVertices; i++) {
        vertices.push_back(mesh->mVertices[i].x);
        vertices.push_back(mesh->mVertices[i].y);
        vertices.push_back(mesh->mVertices[i].z);
    }
    // Continuar amb la càrrega de textures, normals, etc.
}

  1. Renderització de Models

2.1. Configuració de Buffers

Per renderitzar el model, necessitem configurar els buffers de vèrtexs (VBO) i els objectes de matriu de vèrtexs (VAO).

unsigned int VBO, VAO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);

glBindVertexArray(VAO);

glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(float), &vertices[0], GL_STATIC_DRAW);

// Posicions de vèrtexs
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);

2.2. Shader Program

Necessitem un shader program per renderitzar el model. A continuació es mostra un exemple de shaders bàsics per a vèrtexs i fragments.

Vertex Shader (vertex_shader.glsl)

#version 330 core
layout (location = 0) in vec3 aPos;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main() {
    gl_Position = projection * view * model * vec4(aPos, 1.0);
}

Fragment Shader (fragment_shader.glsl)

#version 330 core
out vec4 FragColor;

void main() {
    FragColor = vec4(1.0, 0.5, 0.2, 1.0);
}

2.3. Render Loop

Finalment, necessitem un bucle de renderització per dibuixar el model.

while (!glfwWindowShouldClose(window)) {
    // Processar entrades
    processInput(window);

    // Renderitzar
    glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // Activar shader program
    shader.use();

    // Configurar matrius de transformació
    glm::mat4 model = glm::mat4(1.0f);
    glm::mat4 view = camera.GetViewMatrix();
    glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
    shader.setMat4("model", model);
    shader.setMat4("view", view);
    shader.setMat4("projection", projection);

    // Renderitzar model
    glBindVertexArray(VAO);
    glDrawArrays(GL_TRIANGLES, 0, vertices.size() / 3);
    glBindVertexArray(0);

    // Intercanviar buffers
    glfwSwapBuffers(window);
    glfwPollEvents();
}

  1. Gestió de Càmeres

3.1. Implementació de la Càmera

Implementarem una càmera que permeti a l'usuari navegar pel model utilitzant el teclat i el ratolí.

class Camera {
public:
    glm::vec3 Position;
    glm::vec3 Front;
    glm::vec3 Up;
    glm::vec3 Right;
    glm::vec3 WorldUp;

    float Yaw;
    float Pitch;

    float MovementSpeed;
    float MouseSensitivity;
    float Zoom;

    Camera(glm::vec3 position, glm::vec3 up, float yaw, float pitch) : Front(glm::vec3(0.0f, 0.0f, -1.0f)), MovementSpeed(2.5f), MouseSensitivity(0.1f), Zoom(45.0f) {
        Position = position;
        WorldUp = up;
        Yaw = yaw;
        Pitch = pitch;
        updateCameraVectors();
    }

    glm::mat4 GetViewMatrix() {
        return glm::lookAt(Position, Position + Front, Up);
    }

    void ProcessKeyboard(Camera_Movement direction, float deltaTime) {
        float velocity = MovementSpeed * deltaTime;
        if (direction == FORWARD)
            Position += Front * velocity;
        if (direction == BACKWARD)
            Position -= Front * velocity;
        if (direction == LEFT)
            Position -= Right * velocity;
        if (direction == RIGHT)
            Position += Right * velocity;
    }

    void ProcessMouseMovement(float xoffset, float yoffset, GLboolean constrainPitch = true) {
        xoffset *= MouseSensitivity;
        yoffset *= MouseSensitivity;

        Yaw += xoffset;
        Pitch += yoffset;

        if (constrainPitch) {
            if (Pitch > 89.0f)
                Pitch = 89.0f;
            if (Pitch < -89.0f)
                Pitch = -89.0f;
        }

        updateCameraVectors();
    }

    void ProcessMouseScroll(float yoffset) {
        if (Zoom >= 1.0f && Zoom <= 45.0f)
            Zoom -= yoffset;
        if (Zoom <= 1.0f)
            Zoom = 1.0f;
        if (Zoom >= 45.0f)
            Zoom = 45.0f;
    }

private:
    void updateCameraVectors() {
        glm::vec3 front;
        front.x = cos(glm::radians(Yaw)) * cos(glm::radians(Pitch));
        front.y = sin(glm::radians(Pitch));
        front.z = sin(glm::radians(Yaw)) * cos(glm::radians(Pitch));
        Front = glm::normalize(front);
        Right = glm::normalize(glm::cross(Front, WorldUp));
        Up = glm::normalize(glm::cross(Right, Front));
    }
};

  1. Il·luminació i Materials

4.1. Configuració de la Il·luminació

Afegirem il·luminació bàsica al nostre shader per millorar l'aparença del model.

Fragment Shader amb Il·luminació (fragment_shader.glsl)

#version 330 core
out vec4 FragColor;

in vec3 Normal;
in vec3 FragPos;

uniform vec3 lightPos;
uniform vec3 viewPos;
uniform vec3 lightColor;
uniform vec3 objectColor;

void main() {
    // Ambient
    float ambientStrength = 0.1;
    vec3 ambient = ambientStrength * lightColor;

    // Difús
    vec3 norm = normalize(Normal);
    vec3 lightDir = normalize(lightPos - FragPos);
    float diff = max(dot(norm, lightDir), 0.0);
    vec3 diffuse = diff * lightColor;

    // Especular
    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);
}

4.2. Aplicació de Materials

Podem definir materials per als nostres models per controlar com interactuen amb la llum.

struct Material {
    glm::vec3 ambient;
    glm::vec3 diffuse;
    glm::vec3 specular;
    float shininess;
};

Material material = {
    glm::vec3(1.0f, 0.5f, 0.31f),
    glm::vec3(1.0f, 0.5f, 0.31f),
    glm::vec3(0.5f, 0.5f, 0.5f),
    32.0f
};

shader.setVec3("material.ambient", material.ambient);
shader.setVec3("material.diffuse", material.diffuse);
shader.setVec3("material.specular", material.specular);
shader.setFloat("material.shininess", material.shininess);

  1. Interacció de l'Usuari

5.1. Control de la Càmera

Permetrem a l'usuari controlar la càmera utilitzant el teclat i el ratolí.

void processInput(GLFWwindow *window) {
    if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
        camera.ProcessKeyboard(FORWARD, deltaTime);
    if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
        camera.ProcessKeyboard(BACKWARD, deltaTime);
    if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
        camera.ProcessKeyboard(LEFT, deltaTime);
    if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
        camera.ProcessKeyboard(RIGHT, deltaTime);
}

void mouse_callback(GLFWwindow* window, double xpos, double ypos) {
    if (firstMouse) {
        lastX = xpos;
        lastY = ypos;
        firstMouse = false;
    }

    float xoffset = xpos - lastX;
    float yoffset = lastY - ypos;
    lastX = xpos;
    lastY = ypos;

    camera.ProcessMouseMovement(xoffset, yoffset);
}

void scroll_callback(GLFWwindow* window, double xoffset, double yoffset) {
    camera.ProcessMouseScroll(yoffset);
}

Conclusió

En aquest tema, hem après a crear un visualitzador de models 3D utilitzant OpenGL. Hem cobert la càrrega de models 3D amb Assimp, la renderització de models, la gestió de càmeres, la il·luminació i materials, i la interacció de l'usuari. Aquest projecte ens permet aplicar molts dels conceptes apresos en els mòduls anteriors i ens prepara per a projectes més complexos en el futur.

© Copyright 2024. Tots els drets reservats