En primer lugar, quiero decir que he leído muchas publicaciones sobre mapeo de sombras usando mapas de profundidad y mapas de cubos y entiendo cómo funcionan y también, tengo experiencia laboral con ellos usando OpenGL, pero tengo un problema al implementar Técnica de mapeo de sombras omnidireccional que utiliza una fuente de luz de un solo punto en mi motor de gráficos 3D llamado "EZ3". Mi motor utiliza WebGL como API de gráficos 3D y JavaScript como lenguaje de programación, esto es para mi tesis de licenciatura en informática.
Básicamente, así es como he implementado mi algoritmo de mapeo de sombras, pero solo me enfocaré en el caso de las luces de punto porque con ellas puedo archivar el mapeo de sombras omnidireccional.
Primero, activo el sacrificio frontal como este:
if (this.state.faceCulling !== Material.FRONT) {
if (this.state.faceCulling === Material.NONE)
gl.enable(gl.CULL_FACE);
gl.cullFace(gl.FRONT);
this.state.faceCulling = Material.FRONT;
}
Segundo, creo un programa de profundidad para registrar valores de profundidad para cada cara de mapa de cubos, este es mi código de programa de profundidad en GLSL 1.0:
Sombreador de vértices:
precision highp float;
attribute vec3 position;
uniform mat4 uModelView;
uniform mat4 uProjection;
void main() {
gl_Position = uProjection * uModelView * vec4(position, 1.0);
}
Fragmento Shader:
precision highp float;
vec4 packDepth(const in float depth) {
const vec4 bitShift = vec4(256.0 * 256.0 * 256.0, 256.0 * 256.0, 256.0, 1.0);
const vec4 bitMask = vec4(0.0, 1.0 / 256.0, 1.0 / 256.0, 1.0 / 256.0);
vec4 res = mod(depth * bitShift * vec4(255), vec4(256)) / vec4(255);
res -= res.xxyz * bitMask;
return res;
}
void main() {
gl_FragData[0] = packDepth(gl_FragCoord.z);
}
Tercero, este es el cuerpo de mi función de JavaScript que "archiva" el mapeo omnidireccional de sombras
program.bind(gl);
for (i = 0; i < lights.length; i++) {
light = lights[i];
// Updates pointlight's projection matrix
light.updateProjection();
// Binds point light's depth framebuffer
light.depthFramebuffer.bind(gl);
// Updates point light's framebuffer in order to create it
// or if it's resolution changes, it'll be created again.
light.depthFramebuffer.update(gl);
// Sets viewport dimensions with depth framebuffer's dimensions
this.viewport(new Vector2(), light.depthFramebuffer.size);
if (light instanceof PointLight) {
up = new Vector3();
view = new Matrix4();
origin = new Vector3();
target = new Vector3();
for (j = 0; j < 6; j++) {
// Check in which cubemap's face we are ...
switch (j) {
case Cubemap.POSITIVE_X:
target.set(1, 0, 0);
up.set(0, -1, 0);
break;
case Cubemap.NEGATIVE_X:
target.set(-1, 0, 0);
up.set(0, -1, 0);
break;
case Cubemap.POSITIVE_Y:
target.set(0, 1, 0);
up.set(0, 0, 1);
break;
case Cubemap.NEGATIVE_Y:
target.set(0, -1, 0);
up.set(0, 0, -1);
break;
case Cubemap.POSITIVE_Z:
target.set(0, 0, 1);
up.set(0, -1, 0);
break;
case Cubemap.NEGATIVE_Z:
target.set(0, 0, -1);
up.set(0, -1, 0);
break;
}
// Creates a view matrix using target and up vectors according to each face of pointlight's
// cubemap. Furthermore, I translate it in minus light position in order to place
// the point light in the world's origin and render each cubemap's face at this
// point of view
view.lookAt(origin, target, up);
view.mul(new EZ3.Matrix4().translate(light.position.clone().negate()));
// Flips the Y-coordinate of each cubemap face
// scaling the projection matrix by (1, -1, 1).
// This is a perspective projection matrix which has:
// 90 degress of FOV.
// 1.0 of aspect ratio.
// Near clipping plane at 0.01.
// Far clipping plane at 2000.0.
projection = light.projection.clone();
projection.scale(new EZ3.Vector3(1, -1, 1));
// Attaches a cubemap face to current framebuffer in order to record depth values for the face with this line
// gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_CUBE_MAP_POSITIVE_X + j, id, 0);
light.depthFramebuffer.texture.attach(gl, j);
// Clears current framebuffer's color with these lines:
// gl.clearColor(1.0,1.0,1.0,1.0);
// gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
this.clear(color);
// Renders shadow caster meshes using the depth program
for (k = 0; k < shadowCasters.length; k++)
this._renderShadowCaster(shadowCasters[k], program, view, projection);
}
} else {
// Directional light & Spotlight case ...
}
}
Cuarto, así es como calculo el mapeo de sombras omnidireccional usando mi mapa de cubos de profundidad en mi sombreador de vértices y sombreador de fragmentos principal:
Sombreador de vértices:
precision highp float;
attribute vec3 position;
uniform mat4 uModel;
uniform mat4 uModelView;
uniform mat4 uProjection;
varying vec3 vPosition;
void main() {
vPosition = vec3(uModel * vec4(position, 1.0));
gl_Position = uProjection * uModelView * vec4(position, 1.0);
}
Fragmento Shader:
float unpackDepth(in vec4 color) {
return dot(color, vec4(1.0 / (256.0 * 256.0 * 256.0), 1.0 / (256.0 * 256.0), 1.0 / 256.0, 1.0 ));
}
float pointShadow(const in PointLight light, const in samplerCube shadowSampler) {
vec3 direction = vPosition - light.position;
float vertexDepth = clamp(length(direction), 0.0, 1.0);
float shadowMapDepth = unpackDepth(textureCube(shadowSampler, direction));
return (vertexDepth > shadowMapDepth) ? light.shadowDarkness : 1.0;
}
Finalmente, este es el resultado que estoy obteniendo, mi escena tiene un plano, un cubo y una esfera. Además, la esfera roja brillante es la fuente de luz puntual:
Como puede ver, parece que el mapa de cubos de framebuffer de profundidad de luz de punto no está haciendo una buena interpolación entre sus caras.
Hasta ahora, no tengo idea de cómo resolver esto.
fuente
Respuestas:
SOLUCIÓN
Después de un par de días, me di cuenta de que estaba calculando mi matriz de proyección usando un ángulo FOV en grados y debería estar en radianes . Hice la conversión y ahora todo funciona muy bien. La interpolación entre caras del mapa de cubos de mi framebuffer de profundidad ahora es perfecta. Por esta razón, es importante manejar el ángulo de cada función trigonométrica en radianes.
Además, me di cuenta de que puede calcular su matriz de vista ya sea como dije en la pregunta y de esta manera:
Este enfoque significa que su punto de vista se coloca en el centro de la luz puntual y usted simplemente renderiza en cada dirección de su mapa de cubos, pero ¿cuáles son estas direcciones? bueno, estas direcciones se calculan agregando cada objetivo que tengo en el bloque de interruptores (de acuerdo con la cara de cada mapa de cubos) con la posición de su foco .
Además, no es necesario voltear la coordenada Y de la matriz de proyección , en este caso, está bien enviar la matriz de proyección de perspectiva de Pointlight a su sombreador GLSL sin escalarlo en (1, -1, 1) porque estoy trabajando con texturas que no tienen una coordenada Y invertida , creo que debería voltear la coordenada Y de la matriz de proyección de su punto de luz solo si está trabajando con una coordenada Y de una textura invertida , esto para tener un efecto de mapeo de sombras omnidireccional correcto.
Finalmente, dejaré aquí la versión final de mi algoritmo de mapeo de sombras omnidireccional en el lado de la CPU / GPU. En el lado de la CPU, explicaré cada paso que tiene que hacer para calcular un mapa de sombras correcto para la cara de cada mapa de cubos. Por otro lado, en el lado de la GPU, explicaré el sombreador de vértices / fragmentos de mi programa de profundidad y la función de mapeo de sombras omnidireccional en mi sombreador de fragmentos principal, esto para ayudar a alguien que podría estar aprendiendo esta técnica o resolver futuras dudas sobre este algoritmo :
UPC
En la función renderMeshDepth tengo:
GPU
Programa de profundidad del sombreador de vértices:
Shader de fragmento de programa de profundidad:
Función de mapeo de sombras omnidireccional en mi sombreador de fragmentos principal:
Aquí tienes un render final del algoritmo
Diviértete codificando hermosos gráficos, buena suerte :)
CZ
fuente