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
- Mapa d'Ombres (Shadow Map): Una textura que emmagatzema la informació de profunditat des de la perspectiva de la font de llum.
- 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.
- 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
- 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);
- 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);
- 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);
- 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:
- Configura el framebuffer i la textura de profunditat.
- Renderitza l'escena des de la perspectiva de la font de llum per generar el mapa d'ombres.
- 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.
- 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
iSHADOW_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.
Curs de Programació OpenGL
Mòdul 1: Introducció a OpenGL
- Què és OpenGL?
- Configurar el Teu Entorn de Desenvolupament
- Crear el Teu Primer Programa OpenGL
- Entendre el Pipeline d'OpenGL
Mòdul 2: Renderització Bàsica
- Dibuixar Formes Bàsiques
- Entendre les Coordenades i les Transformacions
- Coloració i Ombrejat
- Ús de Buffers
Mòdul 3: Tècniques de Renderització Intermèdies
- Textures i Mapeig de Textures
- Il·luminació i Materials
- Barreja i Transparència
- Prova de Profunditat i Prova de Plantilla
Mòdul 4: Tècniques de Renderització Avançades
Mòdul 5: Optimització del Rendiment
- Optimitzar el Codi OpenGL
- Ús d'Objectes de Matriu de Vèrtexs (VAOs)
- Gestió Eficient de la Memòria
- Perfilat i Depuració