El mapeig d'ombres és una tècnica de renderització que permet afegir ombres realistes a les escenes 3D. Aquesta tècnica és àmpliament utilitzada en jocs i aplicacions gràfiques per millorar la percepció de profunditat i realisme. En aquest tema, aprendrem com implementar el mapeig d'ombres utilitzant OpenGL.

Conceptes Clau

  1. Mapa d'Ombres (Shadow Map): Una textura que emmagatzema la informació de profunditat des de la perspectiva de la font de llum.
  2. Passada de Profunditat (Depth Pass): La primera passada on es renderitza la profunditat de l'escena des de la perspectiva de la font de llum.
  3. Passada de Renderització (Render Pass): La segona passada on es renderitza l'escena des de la perspectiva de la càmera, utilitzant el mapa d'ombres per determinar si un fragment està a l'ombra.

Passos per Implementar el Mapeig d'Ombres

  1. Crear el Framebuffer i la Textura de Profunditat

Primer, necessitem crear un framebuffer i una textura de profunditat per emmagatzemar el mapa d'ombres.

GLuint depthMapFBO;
glGenFramebuffers(1, &depthMapFBO);

// Crear la textura de profunditat
GLuint depthMap;
glGenTextures(1, &depthMap);
glBindTexture(GL_TEXTURE_2D, depthMap);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, SHADOW_WIDTH, SHADOW_HEIGHT, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
float borderColor[] = {1.0, 1.0, 1.0, 1.0};
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);

// Adjuntar la textura de profunditat al framebuffer
glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthMap, 0);
glDrawBuffer(GL_NONE);
glReadBuffer(GL_NONE);
glBindFramebuffer(GL_FRAMEBUFFER, 0);

  1. Passada de Profunditat

Renderitzem l'escena des de la perspectiva de la font de llum per generar el mapa d'ombres.

glm::mat4 lightProjection, lightView;
glm::mat4 lightSpaceMatrix;
float near_plane = 1.0f, far_plane = 7.5f;
lightProjection = glm::ortho(-10.0f, 10.0f, -10.0f, 10.0f, near_plane, far_plane);
lightView = glm::lookAt(lightPos, glm::vec3(0.0f), glm::vec3(0.0, 1.0, 0.0));
lightSpaceMatrix = lightProjection * lightView;

// Configurar el shader de profunditat
depthShader.use();
depthShader.setMat4("lightSpaceMatrix", lightSpaceMatrix);

// Renderitzar l'escena a la textura de profunditat
glViewport(0, 0, SHADOW_WIDTH, SHADOW_HEIGHT);
glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO);
glClear(GL_DEPTH_BUFFER_BIT);
renderScene(depthShader);
glBindFramebuffer(GL_FRAMEBUFFER, 0);

  1. Passada de Renderització

Renderitzem l'escena des de la perspectiva de la càmera, utilitzant el mapa d'ombres per determinar si un fragment està a l'ombra.

// Configurar el shader de l'escena
sceneShader.use();
sceneShader.setMat4("lightSpaceMatrix", lightSpaceMatrix);
sceneShader.setVec3("lightPos", lightPos);
sceneShader.setVec3("viewPos", camera.Position);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, depthMap);
sceneShader.setInt("shadowMap", 1);

// Renderitzar l'escena
glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
renderScene(sceneShader);

  1. Shader de Fragment

El shader de fragment utilitza el mapa d'ombres per determinar si un fragment està a l'ombra.

#version 330 core
out vec4 FragColor;

in vec3 FragPos;
in vec3 Normal;
in vec4 FragPosLightSpace;

uniform sampler2D shadowMap;
uniform vec3 lightPos;
uniform vec3 viewPos;

float ShadowCalculation(vec4 fragPosLightSpace)
{
    // Convertir de clip space a NDC
    vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
    projCoords = projCoords * 0.5 + 0.5;
    
    // Obtenir la profunditat del mapa d'ombres
    float closestDepth = texture(shadowMap, projCoords.xy).r; 
    float currentDepth = projCoords.z;
    
    // Verificar si el fragment està a l'ombra
    float shadow = currentDepth > closestDepth  ? 1.0 : 0.0;
    
    return shadow;
}

void main()
{
    vec3 color = vec3(1.0); // Color de l'objecte
    vec3 normal = normalize(Normal);
    vec3 lightColor = vec3(1.0);
    vec3 lightDir = normalize(lightPos - FragPos);
    float diff = max(dot(lightDir, normal), 0.0);
    vec3 diffuse = diff * lightColor;
    
    // Calcular l'ombra
    float shadow = ShadowCalculation(FragPosLightSpace);
    vec3 lighting = (1.0 - shadow) * diffuse * color;
    
    FragColor = vec4(lighting, 1.0);
}

Exercici Pràctic

Implementa el mapeig d'ombres en una escena senzilla amb una font de llum direccional i un objecte que projecti una ombra sobre un pla.

Passos:

  1. Configura el framebuffer i la textura de profunditat.
  2. Renderitza l'escena des de la perspectiva de la font de llum per generar el mapa d'ombres.
  3. Renderitza l'escena des de la perspectiva de la càmera, utilitzant el mapa d'ombres per determinar si un fragment està a l'ombra.
  4. Implementa el shader de fragment per calcular les ombres.

Solució:

Segueix els passos descrits anteriorment per implementar el mapeig d'ombres. Assegura't de configurar correctament els shaders i els buffers.

Errors Comuns i Consells

  • Resolució del Mapa d'Ombres: Una resolució baixa pot causar artefactes d'ombres. Ajusta SHADOW_WIDTH i SHADOW_HEIGHT segons sigui necessari.
  • Bias: Afegir un petit bias a la profunditat pot ajudar a evitar artefactes d'auto-ombra.
  • Projecció de la Llum: Assegura't que la projecció de la llum cobreixi tota l'escena per evitar ombres tallades.

Conclusió

El mapeig d'ombres és una tècnica poderosa per afegir realisme a les escenes 3D. Amb una comprensió clara dels passos i la implementació correcta, pots crear ombres realistes que millorin significativament la qualitat visual de les teves aplicacions gràfiques. En el següent tema, explorarem altres efectes especials que poden complementar el mapeig d'ombres per crear escenes encara més immersives.

© Copyright 2024. Tots els drets reservats