Antes de tudo, quero dizer que li várias postagens sobre mapeamento de sombra usando mapas de profundidade e cubemaps e entendo como eles funcionam e também tenho experiência de trabalho com eles usando o OpenGL, mas tenho um problema ao implementar Técnica omnidirecional de mapeamento de sombras usando uma fonte de luz de ponto único no meu mecanismo de gráficos 3D chamado "EZ3". Meu mecanismo usa o WebGL como uma API gráfica 3D e JavaScript como linguagem de programação, isto é para a tese de meu bacharelado em Ciência da Computação.
Basicamente, é assim que eu implementei meu algoritmo de mapeamento de sombra, mas vou focar apenas no caso das luzes pontuais porque, com elas, posso arquivar o mapeamento de sombra omnidirecional.
Primeiro, eu ativo o abate frontal assim:
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, eu crio um programa de profundidade para registrar valores de profundidade para cada face do mapa do cubo, este é o meu código de programa de profundidade no GLSL 1.0:
Vertex Shader:
precision highp float;
attribute vec3 position;
uniform mat4 uModelView;
uniform mat4 uProjection;
void main() {
gl_Position = uProjection * uModelView * vec4(position, 1.0);
}
Shader do fragmento:
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);
}
Terceiro, este é o corpo da minha função JavaScript que "arquiva" o mapeamento de sombra omnidirecional
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 ...
}
}
Quarto, é assim que eu computo o Mapeamento de Sombra Omnidirecional usando meu mapa de cubos de profundidade no meu principal Vertex Shader & Fragment Shader:
Vertex Shader:
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);
}
Shader do fragmento:
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 é o resultado que estou obtendo, minha cena tem um avião, um cubo e uma esfera. Além disso, a esfera vermelha brilhante é a fonte de luz pontual:
Como você pode ver, parece que o mapa de cubos do framebuffer com profundidade de luz pontual não está fazendo uma boa interpolação entre as faces deles.
Até agora, não tenho idéia de como resolver isso.
fonte
Respostas:
SOLUÇÃO
Depois de alguns dias, percebi que estava computando minha matriz de projeção usando um ângulo de FOV em graus e deveria estar em radianos . Fiz a conversão e agora tudo funciona muito bem. A interpolação entre as faces do mapa de cubos do meu framebuffer de profundidade agora é perfeita. Por esse motivo, é importante lidar com o ângulo de cada função trigonométrica em radianos.
Além disso, percebi que você pode calcular sua matriz de visualizações, como eu disse na pergunta e desta maneira:
Essa abordagem significa que seu ponto de vista é colocado no centro do pointlight e você apenas renderiza em cada direção do seu mapa cúbico, mas quais são essas direções? bem, essas instruções são calculadas adicionando cada alvo que eu tenho no bloco de comutação (de acordo com a face de cada mapa do cubo) com a posição do seu foco .
Além disso, não é necessário inverter a coordenada Y da matriz de projeção . Nesse caso, é bom enviar a matriz de projeção em perspectiva do pointlight para o seu shader GLSL sem escalá-lo em (1, -1, 1) porque estou trabalhando com texturas que não têm uma Coordenada Y invertida , acho que você deve inverter a Coordenada Y da matriz de projeção do spotlight apenas se estiver trabalhando com a Coordenada Y de uma textura invertida , para obter um efeito de mapeamento de sombra omnidirecional correto.
Finalmente, deixarei aqui a versão final do meu algoritmo Omnidirectional Shadow Mapping no lado da CPU / GPU. No lado da CPU, explicarei todas as etapas que você deve executar para calcular um mapa de sombras correto para a face de cada cubo. Por outro lado, no lado da GPU, explicarei a função de mapeamento de vértice / fragmento de som e omnidirecional do meu programa de profundidade no meu sombreador de fragmento principal, para ajudar alguém que possa estar aprendendo essa técnica ou para solucionar dúvidas futuras sobre esse algoritmo :
CPU
Na função renderMeshDepth, tenho:
GPU
Programa de profundidade Vertex Shader:
Shader de fragmento do programa de profundidade:
Função Omnidirectional Shadow Mapping no meu principal shader fragment:
Aqui você tem uma renderização final do algoritmo
Divirta-se codificando belos gráficos, boa sorte :)
CZ
fonte