2. 相关知识点

2.2 阴影实现原理

2.2.1 准备阴影贴图

``````var SHADOW_VSHADER_SOURCE =
'attribute vec4 a_Position;\\n' +
'uniform mat4 u_MvpMatrix;\\n' + //声明uniform变量u_MvpMatrix，存放以光源位置为视点位置的模型视图投影矩阵
'void main() {\\n' +
'  gl_Position = u_MvpMatrix * a_Position;\\n' +
'}\\n';

//绘制阴影的片元着色器
'#ifdef GL_ES\\n' +
'precision mediump float;\\n' +
'#endif\\n' +
'void main() {\\n' +
'  gl_FragColor = vec4(gl_FragCoord.z, 0.0, 0.0, 0.0);\\n' + //将片元深度值z写入RGBA颜色的R分量
'}\\n';
``````

2.2.2 阴影映射

``````var VSHADER_SOURCE =
'attribute vec4 a_Position;\\n' + //声明attribute变量a_Position，用来存放顶点位置信息
'attribute vec4 a_Color;\\n' + //声明attribute变量a_Color，用来存放顶点颜色
'uniform mat4 u_MvpMatrix;\\n' + //声明uniform变量u_MvpMatrix，用来存放模型视图投影组合矩阵
'uniform mat4 u_MvpMatrixFromLight;\\n' +
'varying vec4 v_PositionFromLight;\\n' +
'varying vec4 v_Color;\\n' +
'void main() {\\n' +
'  gl_Position = u_MvpMatrix * a_Position;\\n' +
'  v_PositionFromLight = u_MvpMatrixFromLight * a_Position;\\n' +
'  v_Color = a_Color;\\n' +
'}\\n';

'#ifdef GL_ES\\n' +
'precision mediump float;\\n' +
'#endif\\n' +
'varying vec4 v_PositionFromLight;\\n' +
'varying vec4 v_Color;\\n' +
'void main() {\\n' +
'  vec3 shadowCoord = (v_PositionFromLight.xyz/v_PositionFromLight.w)/2.0 + 0.5;\\n' + //转换为光源坐标系，坐标区间[-1.0,1.0]转换为[0.0,1.0]
'  float depth = rgbaDepth.r;\\n' + //从R分量中获取深度值
'  float visibility = (shadowCoord.z > depth + 0.005) ? 0.7 : 1.0;\\n' + //判断片元在平面上是否为阴影
'  gl_FragColor = vec4(v_Color.rgb * visibility, v_Color.a);\\n' +
'}\\n';
``````

2.2.3 马赫带消除

• 纹理图像的RGBA的每个分量都是8位，假设存储的值是0.1234567，实际上是31个1/256,即0.12109375
• 计算的当前片元深度值是float类型，是16为，假设存储的值也是0.1234567，实际上是8090个1/65535,即0.12344360

3. demo代码

``````<!DOCTYPE html>
<html lang="en">

<meta charset="UTF-8">
<title></title>

<body>
<!--通过canvas标签创建一个800px*800px大小的画布-->
<canvas id="webgl" width="800" height="800"></canvas>
<script type="text/javascript" src="./lib/cuon-matrix.js"></script>
<script>
'attribute vec4 a_Position;\\n' + //声明attribute变量a_Position，用来存放顶点位置信息
'attribute vec4 a_Color;\\n' + //声明attribute变量a_Color，用来存放顶点颜色
'uniform mat4 u_MvpMatrix;\\n' + //声明uniform变量u_MvpMatrix，用来存放模型视图投影组合矩阵
'uniform mat4 u_MvpMatrixFromLight;\\n' +
'varying vec4 v_PositionFromLight;\\n' +
'varying vec4 v_Color;\\n' +
'void main() {\\n' +
'  gl_Position = u_MvpMatrix * a_Position;\\n' +
'  v_PositionFromLight = u_MvpMatrixFromLight * a_Position;\\n' +
'  v_Color = a_Color;\\n' +
'}\\n';

'#ifdef GL_ES\\n' +
'precision mediump float;\\n' +
'#endif\\n' +
'varying vec4 v_PositionFromLight;\\n' +
'varying vec4 v_Color;\\n' +
'void main() {\\n' +
'  vec3 shadowCoord = (v_PositionFromLight.xyz/v_PositionFromLight.w)/2.0 + 0.5;\\n' +
//转换为光源坐标系，坐标区间[-1.0,1.0]转换为[0.0,1.0]
'  float depth = rgbaDepth.r;\\n' + //从R分量中获取深度值
'  float visibility = (shadowCoord.z > depth + 0.005) ? 0.7 : 1.0;\\n' + //判断片元在平面上是否为阴影
'  gl_FragColor = vec4(v_Color.rgb * visibility, v_Color.a);\\n' +
'}\\n';

'attribute vec4 a_Position;\\n' +
'uniform mat4 u_MvpMatrix;\\n' + //声明uniform变量u_MvpMatrix，存放以光源位置为视点位置的模型视图投影矩阵
'void main() {\\n' +
'  gl_Position = u_MvpMatrix * a_Position;\\n' +
'}\\n';

//绘制阴影的片元着色器
'#ifdef GL_ES\\n' +
'precision mediump float;\\n' +
'#endif\\n' +
'void main() {\\n' +
'  gl_FragColor = vec4(gl_FragCoord.z, 0.0, 0.0, 0.0);\\n' + //将片元深度值z写入RGBA颜色的R分量
'}\\n';

var OFFSCREEN_WIDTH = 2048
var OFFSCREEN_HEIGHT = 2048

//光源坐标
var LIGHT_X = 0,
LIGHT_Y = 7,
LIGHT_Z = 2;

//创建程序对象
//创建顶点着色器对象
//创建片元着色器对象

return null
}

//创建程序对象program
var program = gl.createProgram()
if (!gl.createProgram()) {
return null
}
//分配顶点着色器和片元着色器到program
//链接program

//检查程序对象是否连接成功
var error = gl.getProgramInfoLog(program)
console.log('程序对象连接失败: ' + error)
gl.deleteProgram(program)
return null
}

gl.program = program
//返回程序program对象
return program
}

// 创建顶点着色器对象
if (shader == null) {
console.log('创建着色器失败')
return null
}

// 引入着色器源代码

// 编译着色器

// 检查顶是否编译成功
if (!compiled) {
console.log('编译着色器失败: ' + error)
return null
}

}

function init() {
//通过getElementById()方法获取canvas画布
var canvas = document.getElementById('webgl')

//通过方法getContext()获取WebGL上下文
var gl = canvas.getContext('webgl')

//获取绘制阴影贴图相关变量的存储地址
if (shadowProgram.a_Position < 0 || !shadowProgram.u_MvpMatrix) {
console.log('获取attribute变量或uniform变量存储地址失败');
return;
}

//获取绘制阴影以外物体相关变量的存储地址
normalProgram.a_Position = gl.getAttribLocation(normalProgram, 'a_Position');
normalProgram.a_Color = gl.getAttribLocation(normalProgram, 'a_Color');
normalProgram.u_MvpMatrix = gl.getUniformLocation(normalProgram, 'u_MvpMatrix');
normalProgram.u_MvpMatrixFromLight = gl.getUniformLocation(normalProgram, 'u_MvpMatrixFromLight');
if (normalProgram.a_Position < 0 || normalProgram.a_Color < 0 || !normalProgram.u_MvpMatrix ||
!normalProgram.u_MvpMatrixFromLight || !normalProgram.u_ShadowMap) {
console.log('获取attribute变量或uniform变量存储地址失败');
return;
}

//初始化三角形顶点信息
var triangle = initVertexBuffersForTriangle(gl)
//初始化平面顶点信息
var plane = initVertexBuffersForPlane(gl)
if (!triangle || !plane) {
console.log('初始化顶点信息失败')
return
}

//初始化帧缓冲区对象(FBO)
var fbo = initFramebufferObject(gl)
if (!fbo) {
console.log('初始化帧缓冲区对象失败')
return
}

//激活0号纹理单元并绑定到纹理对象
gl.activeTexture(gl.TEXTURE0)
gl.bindTexture(gl.TEXTURE_2D, fbo.texture) //将帧缓冲区的颜色关联对象关联的纹理对象绑定到纹理单元

// 设置canvas的背景色
gl.clearColor(0, 0, 0, 1)
//开启隐藏面消除
gl.enable(gl.DEPTH_TEST)

//创建、设置视图投影矩阵
var viewProjMatrix = new Matrix4()
viewProjMatrix.setPerspective(45, 1, 1, 100)
viewProjMatrix.lookAt(0.0, 7.0, 9.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);

var viewProjMatrixFromLight = new Matrix4() //为阴影贴图创建视图投影矩阵
viewProjMatrixFromLight.setPerspective(70.0, OFFSCREEN_WIDTH / OFFSCREEN_HEIGHT, 1.0, 100.0)
viewProjMatrixFromLight.lookAt(LIGHT_X, LIGHT_Y, LIGHT_Z, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0)

var mvpMatrixFromLight_t = new Matrix4(); //三角形的模型视图投影矩阵
var mvpMatrixFromLight_p = new Matrix4(); //底面的模型视图投影矩阵
var currentAngle = 0.0
var tick = function () {
currentAngle = getCurrentAngle(currentAngle) //获取当前要旋转的角度

gl.bindFramebuffer(gl.FRAMEBUFFER, fbo) //绑定帧缓冲区对象，后续绘制在绑定帧缓冲区中进行
gl.viewport(0, 0, OFFSCREEN_WIDTH, OFFSCREEN_HEIGHT) //设置在帧缓冲区绘制时窗口大小
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) //清空帧缓冲区

//启用绘制阴影的着色器程序对象

//准备阴影纹理，即将三角形和平面片元写入帧缓冲区的深度关联对象
drawTriangle(gl, shadowProgram, triangle, currentAngle, viewProjMatrixFromLight); //以光源位置为视点计算投影
mvpMatrixFromLight_t.set(g_mvpMatrix); //恢复绘制三角形的模型视图投影矩阵
drawPlane(gl, shadowProgram, plane, viewProjMatrixFromLight);
mvpMatrixFromLight_p.set(g_mvpMatrix); //恢复绘制平面的模型视图投影矩阵

gl.bindFramebuffer(gl.FRAMEBUFFER, null) //帧缓冲区对象解绑，解绑后绘制在默认的颜色缓冲区中进行
gl.viewport(0, 0, canvas.width, canvas.height) //设置在默认的颜色缓冲区绘制时窗口大小
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) //清空颜色和深度缓冲区

//启用绘制三角形和平面的着色器程序对象
gl.useProgram(normalProgram);

//绘制三角形
gl.uniformMatrix4fv(normalProgram.u_MvpMatrixFromLight, false, mvpMatrixFromLight_t.elements);
drawTriangle(gl, normalProgram, triangle, currentAngle, viewProjMatrix);
//绘制平面及平面上三角形的阴影
gl.uniformMatrix4fv(normalProgram.u_MvpMatrixFromLight, false, mvpMatrixFromLight_p.elements);
drawPlane(gl, normalProgram, plane, viewProjMatrix)

requestAnimationFrame(tick)
}

tick() // 调用tick

}

var g_LastTime = Date.now() // 上次绘制的时间
var ANGLE_SET = 30.0 // 旋转速度（度/秒）
function getCurrentAngle(angle) {
var now = Date.now()
var elapsed = now - g_LastTime //上次调用与当前时间差
g_LastTime = now
var newAngle = angle + (ANGLE_SET * elapsed) / 1000
return newAngle %= 360
}

//初始化帧缓冲区（FBO）
function initFramebufferObject(gl) {
var framebuffer, texture, depthBuffer

//处理错误
var error = function () {
if (framebuffer) gl.deleteFramebuffer(framebuffer)
if (texture) gl.deleteTexture(texture)
if (depthBuffer) gl.deleteRenderbuffer(depthBuffer)
return null
}

//创建帧缓冲区
framebuffer = gl.createFramebuffer()
if (!framebuffer) {
console.log('创建帧缓冲区对象失败')
return error()
}

//创建纹理对象并设置参数
texture = gl.createTexture() //创建纹理对象
if (!texture) {
console.log('创建纹理对象失败')
return error()
}
gl.bindTexture(gl.TEXTURE_2D, texture) //绑定纹理对象
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, OFFSCREEN_WIDTH, OFFSCREEN_HEIGHT, 0, gl.RGBA, gl.UNSIGNED_BYTE,
null) //纹理图像分配给纹理对象
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) //配置纹理对象参数
framebuffer.texture = texture //将纹理对象关联到帧缓冲区的颜色关联对象

//创建渲染缓冲区对象并设置参数
depthBuffer = gl.createRenderbuffer() //创建渲染缓冲区对象
if (!depthBuffer) {
console.log('创建渲染缓冲区对象失败')
return error()
}
gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer) //绑定渲染缓冲区
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, OFFSCREEN_WIDTH, OFFSCREEN_HEIGHT) //设置渲染缓冲区尺寸

//将纹理对象和渲染缓冲区对象关联到帧缓冲区对象
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer) //绑定帧缓冲区对象
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0)
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthBuffer)

//检查帧缓冲区的配置状态
var e = gl.checkFramebufferStatus(gl.FRAMEBUFFER)
if (gl.FRAMEBUFFER_COMPLETE !== e) { //gl.FRAMEBUFFER_COMPLETE 表示帧缓冲区对象已正确配置
console.log('帧缓冲区配置不正确: ' + e.toString())
return error()
}

//解绑帧缓冲区对象、纹理对象、渲染缓冲区对象
gl.bindFramebuffer(gl.FRAMEBUFFER, null)
gl.bindTexture(gl.TEXTURE_2D, null)
gl.bindRenderbuffer(gl.RENDERBUFFER, null)

return framebuffer
}

//模型矩阵、模型视图投影矩阵
var g_modelMatrix = new Matrix4()
var g_mvpMatrix = new Matrix4()

function drawTriangle(gl, program, triangle, angle, viewProjMatrix) {
//模型矩阵设置旋转角度
g_modelMatrix.setRotate(angle, 0, 1, 0);
draw(gl, program, triangle, viewProjMatrix);
}

function drawPlane(gl, program, plane, viewProjMatrix) {
//模型矩阵设置旋转角度
g_modelMatrix.setRotate(-45, 0, 1, 1);
draw(gl, program, plane, viewProjMatrix);
}

function draw(gl, program, o, viewProjMatrix) {
//为变量分配缓存并开启
initAttributeVariable(gl, program.a_Position, o.vertexBuffer);
if (program.a_Color != undefined) {
initAttributeVariable(gl, program.a_Color, o.colorBuffer);
}

gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, o.indexBuffer);

//计算模型视图投影矩阵并传值
g_mvpMatrix.set(viewProjMatrix);
g_mvpMatrix.multiply(g_modelMatrix);
gl.uniformMatrix4fv(program.u_MvpMatrix, false, g_mvpMatrix.elements);

gl.drawElements(gl.TRIANGLES, o.numIndices, gl.UNSIGNED_BYTE, 0);
}

//初始化绘制平面的信息
function initVertexBuffersForPlane(gl) {
//平面顶点信息
var vertices = new Float32Array([
3.0, -1.7, 2.5, -3.0, -1.7, 2.5, -3.0, -1.7, -2.5, 3.0, -1.7, -2.5 // v0-v1-v2-v3
])

//平面颜色
var colors = new Float32Array([1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0])

//平面索引
var indices = new Uint8Array([0, 1, 2, 0, 2, 3])

var obj = new Object()

//所需顶点、颜色、索引信息写入缓冲区对象
obj.vertexBuffer = initArrayBufferForLaterUse(gl, vertices, 3, gl.FLOAT)
obj.colorBuffer = initArrayBufferForLaterUse(gl, colors, 3, gl.FLOAT)
obj.indexBuffer = initElementArrayBufferForLaterUse(gl, indices, gl.UNSIGNED_BYTE)
if (!obj.vertexBuffer || !obj.colorBuffer || !obj.indexBuffer) {
return null
}

//顶点索引数量
obj.numIndices = indices.length

//缓冲区对象解绑
gl.bindBuffer(gl.ARRAY_BUFFER, null)
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null)

return obj

}

function initVertexBuffersForTriangle(gl) {

//三角形顶点
var vertices = new Float32Array([-0.8, 3.5, 0.0, 0.8, 3.5, 0.0, 0.0, 3.5, 1.8]);
//颜色
var colors = new Float32Array([1.0, 0.5, 0.0, 1.0, 0.5, 0.0, 1.0, 0.0, 0.0]);
//顶点索引数量
var indices = new Uint8Array([0, 1, 2]);

var obj = new Object();

//所需顶点、颜色、索引信息写入缓冲区对象
obj.vertexBuffer = initArrayBufferForLaterUse(gl, vertices, 3, gl.FLOAT);
obj.colorBuffer = initArrayBufferForLaterUse(gl, colors, 3, gl.FLOAT);
obj.indexBuffer = initElementArrayBufferForLaterUse(gl, indices, gl.UNSIGNED_BYTE);
if (!obj.vertexBuffer || !obj.colorBuffer || !obj.indexBuffer) return null;

obj.numIndices = indices.length;

//缓冲区对象解绑
gl.bindBuffer(gl.ARRAY_BUFFER, null);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);

return obj;
}

function initArrayBufferForLaterUse(gl, data, num, type) {
//创建缓冲区对象
var buffer = gl.createBuffer()
if (!buffer) {
console.log('创建缓冲区对象失败')
return null
}

//将数据写入缓冲区对象
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)

//设置num和type后续使用
buffer.num = num
buffer.type = type
return buffer
}

function initElementArrayBufferForLaterUse(gl, data, type) {
//创建缓冲区对象
var buffer = gl.createBuffer()
if (!buffer) {
console.log('创建缓冲区对象失败')
return null
}

//将数据写入缓冲区对象
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer)
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, data, gl.STATIC_DRAW)

//设置type后续使用
buffer.type = type
return buffer
}
//为指定变量分配缓存并开启
function initAttributeVariable(gl, a_attribute, buffer) {
gl.bindBuffer(gl.ARRAY_BUFFER, buffer) //将buffer绑定到缓冲区对象
gl.vertexAttribPointer(a_attribute, buffer.num, buffer.type, false, 0, 0) //缓冲区对象分配给a_attribute指定的地址
gl.enableVertexAttribArray(a_attribute) //开启a_attribute指定的变量
}

init()
</script>
</body>

</html>
``````