Omni-directional light shadow mapping with cubemaps in WebGL
- by Winged
First of all I must say, that I have read a lot of posts describing an usage of cubemaps, but I'm still confused about how to use them.
My goal is to achieve a simple omni-directional (point) light type shading in my WebGL application.
I know that there is a lot more techniques (like using Two-Hemispheres or Camera Space Shadow Mapping) which are way more efficient, but for an educational purpose cubemaps are my primary goal.
Till now, I have adapted a simple shadow mapping which works with spotlights (with one exception: I don't know how to cut off the glitchy part beyond the reach of a single shadow map texture): glitchy shadow mapping<<<
So for now, this is how I understand the usage of cubemaps in shadow mapping:
Setup a framebuffer (in case of cubemaps - 6 framebuffers; 6 instead of 1 because every usage of framebufferTexture2D slows down an execution which is nicely described here <<<) and a texture cubemap. Also in WebGL depth components are not well supported, so I need to render it to RGBA first.
this.texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.texture);
gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
for (var face = 0; face < 6; face++)
gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + face, 0, gl.RGBA, this.size, this.size, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
gl.bindTexture(gl.TEXTURE_CUBE_MAP, null);
this.framebuffer = [];
for (face = 0; face < 6; face++) {
this.framebuffer[face] = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer[face]);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_CUBE_MAP_POSITIVE_X + face, this.texture, 0);
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, this.depthbuffer);
var e = gl.checkFramebufferStatus(gl.FRAMEBUFFER); // Check for errors
if (e !== gl.FRAMEBUFFER_COMPLETE) throw "Cubemap framebuffer object is incomplete: " + e.toString();
}
Setup the light and the camera (I'm not sure if should I store all of 6 view matrices and send them to shaders later, or is there a way to do it with just one view matrix).
Render the scene 6 times from the light's position, each time in another direction (X, -X, Y, -Y, Z, -Z)
for (var face = 0; face < 6; face++) {
gl.bindFramebuffer(gl.FRAMEBUFFER, shadow.buffer.framebuffer[face]);
gl.viewport(0, 0, shadow.buffer.size, shadow.buffer.size);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
camera.lookAt( light.position.add( cubeMapDirections[face] ) );
scene.draw(shadow.program);
}
In a second pass, calculate the projection a a current vertex using light's projection and view matrix. Now I don't know If should I calculate 6 of them, because of 6 faces of a cubemap. ScaleMatrix pushes the projected vertex into the 0.0 - 1.0 region.
vDepthPosition = ScaleMatrix * uPMatrixFromLight * uVMatrixFromLight * vWorldVertex;
In a fragment shader calculate the distance between the current vertex and the light position and check if it's deeper then the depth information read from earlier rendered shadow map. I know how to do it with a 2D Texture, but I have no idea how should I use cubemap texture here. I have read that texture lookups into cubemaps are performed by a normal vector instead of a UV coordinate. What vector should I use? Just a normalized vector pointing to the current vertex? For now, my code for this part looks like this (not working yet):
float shadow = 1.0;
vec3 depth = vDepthPosition.xyz / vDepthPosition.w;
depth.z = length(vWorldVertex.xyz - uLightPosition) * linearDepthConstant;
float shadowDepth = unpack(textureCube(uDepthMapSampler, vWorldVertex.xyz));
if (depth.z > shadowDepth)
shadow = 0.5;
Could you give me some hints or examples (preferably in WebGL code) how I should build it?