一、原理
实现阴影的基本思想是:太阳看不见阴影。如果在光源处放置以为观察者,其视线方向与光线一致,那么观察者也看不到阴影。他看到的每一处都在光的照射下,而那些背后的,他没有看到的物体则处在阴影中。这里,我们需要用到光源与物体之间的距离(实际上也就是物体在光源坐标系下的深度z值)来决定物体是否可见。如图所示,同一条光线上有两个点P1和P2,由于P2的z值大于P1,所以P2在阴影中。
我们需要使用两对着色器以实现阴影:
[1]一对着色器用来计算光源到物体的距离,
[2]另一对着色器根据[1]中计算出的距离绘制场景。
使用一张纹理图像把[1]的结果传入[2]中,这张纹理图像就被称为阴影贴图(shadow map),而通过阴影贴图实现阴影的方法就被称为阴影映射(shadow mapping)。阴影映射的过程包括以下两步:
-
将视点移动到光源的位置处,并运行[1]中的着色器。这时,那些“将要被绘出”的片元都是被光照射到的,即落在这个像素上的各个片元中最前面的。我们并不实际地绘制出片元的颜色,而是将片元的z值写入到阴影贴图中。 -
将视点移回原来的位置,运行[2]中的着色器绘制场景。此时,我们计算出每个片元在光源坐标系(即[1]中的视点坐标系)下的坐标,并与阴影贴图中记录的z值比较,如果前者大于后者,就说明当前片元处在阴影之中,用较深暗的颜色绘制。
二、效果
三、代码
var v_shader =/*glsl*/`
attribute vec4 a_Position;
attribute vec2 a_Uv;
attribute vec4 a_Color;
uniform mat4 u_ModelMatrix;
uniform mat4 u_ViewMatrix;
uniform mat4 u_ProjectMatrix;
uniform mat4 u_LightViewMatrix;
varying vec2 v_Uv;
varying vec4 v_LightPosition;
varying vec4 v_Color;
void main(){
gl_Position=u_ProjectMatrix*u_ViewMatrix*u_ModelMatrix*a_Position;
//计算光源坐标系下位置
v_LightPosition=u_ProjectMatrix*u_LightViewMatrix*u_ModelMatrix*a_Position;
v_Uv=a_Uv;
v_Color=a_Color;
}
`
var f_shader =/*glsl*/`
#ifdef GL_ES
precision highp float;
#endif
varying vec2 v_Uv;
varying vec4 v_LightPosition;
uniform sampler2D u_Texture;
varying vec4 v_Color;
float unPackDepth(const in vec4 rgbaDepth){
const vec4 bitShift=vec4(1.0,1.0/256.0,1.0/(256.0*256.0),1.0/(256.0*256.0*256.0));
float depth=dot(rgbaDepth,bitShift);
return depth;
}
void main(){
//计算光源坐标系下ndc坐标
vec3 shadowCoord=(v_LightPosition.xyz/v_LightPosition.w)/2.0+0.5;
//从阴影贴图中读取原来深度
vec4 rgbaDepth=texture2D(u_Texture,shadowCoord.xy);
//获取深度
float depth=unPackDepth(rgbaDepth);
//比较深度
float visibility=(shadowCoord.z>depth+0.0015)?0.7:1.0;
gl_FragColor=vec4(vec3(1.0)*visibility,v_Color.a);
}
`
var fbo_v_shader =/*glsl*/`
attribute vec4 a_Position;
attribute vec4 a_Color;
uniform mat4 u_ModelMatrix;
uniform mat4 u_ViewMatrix;
uniform mat4 u_ProjectMatrix;
varying vec4 v_Color;
void main(){
gl_Position=u_ProjectMatrix*u_ViewMatrix*u_ModelMatrix*a_Position;
v_Color=a_Color;
}
`
var fbo_f_shader =/*glsl*/`
#ifdef GL_ES
precision mediump float;
#endif
varying vec4 v_Color;
void main(){
const vec4 bitShift=vec4(1.0,256.0,256.0*256.0,256.0*256.0*256.0);
const vec4 bitMask=vec4(1.0/256.0,1.0/256.0,1.0/256.0,0.0);
vec4 rgbaDepth=fract(gl_FragCoord.z*bitShift);
rgbaDepth-=rgbaDepth.gbaa*bitMask;
gl_FragColor=rgbaDepth;
}
`
//声明js需要的相关变量
var canvas = document.getElementById("canvas");
var gl = getWebGLContext(canvas);
var fbo_program, normal_program
async function main() {
if (!gl) {
console.log("你的浏览器不支持WebGL");
return;
}
fbo_program = createProgram(gl, fbo_v_shader, fbo_f_shader)
if (!fbo_program) {
console.warn("创建FBO程序失败!");
return;
}
normal_program = createProgram(gl, v_shader, f_shader)
if (!normal_program) {
console.warn("创建正常程序失败!");
return;
}
//设置透视投影矩阵
var projMatrix = new Matrix4();
projMatrix.setPerspective(30, canvas.width / canvas.height, 0.1, 100);
//设置视角矩阵的相关信息(视点,视线,上方向)
var viewMatrix = new Matrix4();
viewMatrix.setLookAt(0, 4, 6, 0, 0, 0, 0, 1, 0)
//开启隐藏面清除
gl.enable(gl.DEPTH_TEST);
// gl.enable(gl.CULL_FACE)
gl.useProgram(fbo_program)
gl.program = fbo_program
//获取内置变量的信息
getVariableLocation();
const fbo_box = createCube(gl);
const fbo_plane = createPlane(gl)
gl.useProgram(normal_program)
gl.program = normal_program
//获取内置变量的信息
getVariableLocation();
const box = createCube(gl);
const plane = createPlane(gl)
const offset_width = 1024, offset_height = 1024
//创建FBO
var fboTexture = createFBOTexture(gl, offset_width, offset_height)
//初始化
const fbo = createFrameBuffer(gl, fboTexture, offset_width, offset_height)
//获取光源mvp矩阵
const u_LightViewMatrix = gl.getUniformLocation(gl.program, 'u_LightViewMatrix')
document.addEventListener("keydown", (e) => {
if (e.key === "ArrowUp") {
lightPosition_y += 0.01
} else if (e.key === "ArrowDown") {
lightPosition_y -= 0.01
} else if (e.key === "ArrowLeft") {
lightPosition_x -= 0.1
} else if (e.key === "ArrowRight") {
lightPosition_x += 0.1
}
});
// await draw(projMatrix,viewMatrix)
//设置底色
//根据时间绘制
var tick = async function () {
await draw(projMatrix, viewMatrix, box, plane, fbo, fbo_box, fbo_plane, u_LightViewMatrix)
//重复请求
requestAnimationFrame(tick)
}
tick()
}
let lightViewMatrix = new Matrix4()
var lightPosition_x = 1.01, lightPosition_y = 5.2, lightPosition_z = 0.0
async function draw(projMatrix, viewMatrix, box, plane, fbo, fbo_box, fbo_plane, u_LightViewMatrix) {
//清空颜色和深度缓冲区
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.viewport(0, 0, canvas.width, canvas.height);
//设置视点在光源处
lightViewMatrix.setLookAt(lightPosition_x, lightPosition_y, lightPosition_z, 0, 0, 0, 0, 1, 0)
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo)
gl.useProgram(fbo_program)
gl.program = fbo_program
await drawBox(projMatrix, lightViewMatrix, fbo_box)
await drawPlane(projMatrix, lightViewMatrix, fbo_plane)
gl.bindFramebuffer(gl.FRAMEBUFFER, null)
gl.useProgram(normal_program)
gl.program = normal_program
gl.activeTexture(gl.TEXTURE0);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 0);
gl.bindTexture(gl.TEXTURE_2D, fbo.texture);
gl.uniform1i(normal_program.texture, 0);
//给光源view矩阵赋值
gl.uniformMatrix4fv(u_LightViewMatrix, false, lightViewMatrix.elements)
await drawBox(projMatrix, viewMatrix, box)
await drawPlane(projMatrix, viewMatrix, plane)
}
async function drawBox(projMatrix, viewMatrix, box) {
const program = gl.program;
//设置模型矩阵的相关信息
var modelMatrix = new Matrix4();
modelMatrix.setScale(0.3, 0.3, 0.3)
modelMatrix.translate(0, 1.0, 0)
gl.uniformMatrix4fv(program.modelMatrix, false, modelMatrix.elements);
gl.uniformMatrix4fv(program.viewMatrix, false, viewMatrix.elements);
gl.uniformMatrix4fv(program.projectMatrix, false, projMatrix.elements);
const arrayBuffers = box.arrayBuffers
arrayBuffers.forEach(arrayBuffer => {
if (arrayBuffer) {
writeAttributeVariable(gl, arrayBuffer.attribute, arrayBuffer)
}
});
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, box.indexBuffer);
//绘制图形
gl.drawElements(gl.TRIANGLES, box.length, gl.UNSIGNED_BYTE, 0);
}
async function drawPlane(projMatrix, viewMatrix, plane) {
const program = gl.program;
//设置模型矩阵的相关信息
var modelMatrix = new Matrix4();
modelMatrix.setScale(1, 1, 1)
modelMatrix.translate(0, -0.5, 0)
gl.uniformMatrix4fv(program.modelMatrix, false, modelMatrix.elements);
gl.uniformMatrix4fv(program.viewMatrix, false, viewMatrix.elements);
gl.uniformMatrix4fv(program.projectMatrix, false, projMatrix.elements);
const arrayBuffers = plane.arrayBuffers
arrayBuffers.forEach(arrayBuffer => {
if (arrayBuffer) {
writeAttributeVariable(gl, arrayBuffer.attribute, arrayBuffer)
}
});
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, plane.indexBuffer);
//绘制图形
gl.drawElements(gl.TRIANGLES, plane.length, gl.UNSIGNED_BYTE, 0);
}