更新于 

阴影

阴影贴图

实现阴影的方法有很多,本节介绍的方法叫做 阴影贴图
阴影贴图(shadow map) 也称 深度贴图(depth map)

如何实现阴影

阴影贴图的原理

使用两对着色器以实现阴影,
设第一对着色器为S1,第二对着色器为S2,
光源为O,物体上存在一点P1,P1在阴影上的位置时P2

  • S1计算出OP1
  • 使用一张纹理图像将S1的计算结果传入S2中
    • 这张纹理图像就是 阴影贴图(shadow map)
阴影映射的实际步骤
  1. 即将视点移到光源位置处
    • 将每个像素最前面的z值写入到阴影贴图中
      • 如p1的z值
  2. 将视点移回原来的位置
    • 计算出每个片元在光源坐标系下的坐标,与阴影贴图中记录的z值比较
      • 如p2的z值与p1的z值
    • 如果前者大于后者,就说明当前片元处在阴影之中,用较深颜色绘制
      • p2.z > p1.z ,因此p2处于阴影之中

Shadow

Shadow Code
Shadow运行效果
Shadow运行效果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// 阴影vertex shader
let SHADOW_VSHADER_SOURCE = `
attribute vec4 a_Position;
uniform mat4 u_MvpMatrix;
void main(){
gl_Position = u_MvpMatrix * a_Position;
}
`

// 阴影fragment shader
let SHADOW_FSHADER_SOURCE = `
#ifdef GL_ES
precision mediump float;
#endif
void main(){
// write the z-value in r
gl_FragColor = vec4(gl_FragCoord.z, 0.0, 0.0, 0.0);
}
`

// vertex shader
let VSHADER_SOURCE = `
attribute vec4 a_Position;
attribute vec4 a_Color;
uniform mat4 u_MvpMatrix;
uniform mat4 u_MvpMatrixFromLight; // 光源坐标系
varying vec4 v_PositionFromLight; // 光源坐标系下的物体坐标
varying vec4 v_Color;
void main(){
gl_Position = u_MvpMatrix * a_Position;
v_PositionFromLight = u_MvpMatrixFromLight * a_Position;
v_Color = a_Color;
}
`

// fragment shader
let FSHADER_SOURCE = `
#ifdef GL_ES
precision mediump float;
#endif
uniform sampler2D u_ShadowMap;
varying vec4 v_PositionFromLight;
varying vec4 v_Color;
void main(){
vec3 shadowCoord = (v_PositionFromLight.xyz/v_PositionFromLight.w)/2.0 + 0.5;
vec4 rgbaDepth = texture2D(u_ShadowMap, shadowCoord.xy);
float depth = rgbaDepth.r;
float visibility = (shadowCoord.z > depth + 0.005) ? 0.7 : 1.0;
gl_FragColor = vec4(v_Color.rgb * visibility, v_Color.a);
}
`
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
// 帧缓冲区渲染尺寸
let OFFSCREEN_WIDTH = 2048
let OFFSCREEN_HEIGHT = 2048

// 光照点
let LIGHT_X = 0
let LIGHT_Y = 7
let LIGHT_Z = 2

function main() {
let canvas = document.getElementById('webgl')
let gl = getWebGLContext(canvas)
if (!gl) {
console.log('failed to get the rendering context for webgl')
return;
}
// initialize shaders for generating a shadow map
let shadowProgram = createProgram(gl, SHADOW_VSHADER_SOURCE, SHADOW_FSHADER_SOURCE)
shadowProgram.a_Position = gl.getAttribLocation(shadowProgram, 'a_Position')
shadowProgram.u_MvpMatrix = gl.getUniformLocation(shadowProgram, 'u_MvpMatrix')
if (shadowProgram.a_Position < 0 || !shadowProgram.u_MvpMatrix) {
console.log('failed to get the storage location of attribute or uniform variable from shadowProgram')
return
}

// initialize shaders for regular drawing
let normalProgram = createProgram(gl, VSHADER_SOURCE, FSHADER_SOURCE);
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')
normalProgram.u_ShadowMap = gl.getUniformLocation(normalProgram, 'u_ShadowMap')
if (normalProgram.a_Position < 0 || normalProgram.a_Color < 0 ||
!normalProgram.u_MvpMatrix || !normalProgram.u_MvpMatrixFromLight || !normalProgram.u_ShadowMap
) {
console.log('failed to get the storage location of attribute or uniform varibale fron normalProgram')
return
}

// set the vertex information
let triangle = initVertexBuffersForTriangle(gl)
let plane = initVertexBuffersForPlane(gl)
if (!triangle || !plane) {
console.log('failed to set the vertex information')
return
}

// initialize framebuffer object (FBO)
let fbo = initFramebufferObject(gl)
if (!fbo) {
console.log('failed to initialize frame buffer object')
return
}
gl.activeTexture(gl.TEXTURE0) // set a texture obejct to the texture unit
gl.bindTexture(gl.TEXTURE_2D, fbo.texture)

// set the clear color and enable the depth tes
gl.clearColor(0, 0, 0, 1)
gl.enable(gl.DEPTH_TEST)

// prepare a view projection matrix for generating a shadow map
let viewProjMatrixFromLight = new Matrix4()
viewProjMatrixFromLight.setPerspective(
70.0, OFFSCREEN_WIDTH/OFFSCREEN_HEIGHT, 1.0, 100.0
).lookAt(
LIGHT_X, LIGHT_Y, LIGHT_Z,
0, 0, 0,
0, 1, 0
)

// prepare a view projection matrix for regular drawing
let viewProjMatrix = new Matrix4()
viewProjMatrix.setPerspective(
45, canvas.width/canvas.clientHeight, 1.0, 100.0
).lookAt(
0.0, 7.0, 9.0,
0.0, 0.0, 0.0,
0.0, 1.0, 0.0
)

let currentAngle = 0.0
// a model view projection matrix from light source for triangle
let mvpMatrixFromLight_t = new Matrix4()
// a model view projection matrix from light source for plant
let mvpMatrixFromLight_p = new Matrix4()
let tick = function () {
currentAngle = animate(currentAngle)

/** 步骤1 使用阴影着色器计算出阴影贴图 **/
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo) // change the drawing destination to FBO
gl.viewport(0, 0, OFFSCREEN_WIDTH, OFFSCREEN_HEIGHT) // set view port for FBO
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) // clear FBO

gl.useProgram(shadowProgram) // set shaders for generating a shadow map
// draw the triangle and the plane for generating a shadow map
drawTriangle(gl, shadowProgram, triangle, currentAngle, viewProjMatrixFromLight)
mvpMatrixFromLight_t.set(g_mvpMatrix)
drawPlane(gl, shadowProgram, plane, viewProjMatrixFromLight)
mvpMatrixFromLight_p.set(g_mvpMatrix)


/** 步骤2 使用绘图着色器按照阴影映射原理绘图 **/
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.uniform1i(normalProgram.u_ShadowMap, 0) // Pass 0 because gl.TEXTURE0 is enabled
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)

window.requestAnimationFrame(tick)
}
tick()
}
let ANGLE_STEP = 40
let last = Date.now()
function animate(angle) {
let now = Date.now()
let elapsed = now - last
last = now
return (angle + elapsed * ANGLE_STEP / 1000) % 360

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
let g_modelMatrix = new Matrix4()
let 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
).multiply(
g_modelMatrix
)
gl.uniformMatrix4fv(program.u_MvpMatrix, false, g_mvpMatrix.elements)
gl.drawElements(gl.TRIANGLES, o.numIndices, gl.UNSIGNED_BYTE, 0)

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
// 写入attribute数据
function initAttributeVariable(gl, a_attribute, buffer) {
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
gl.vertexAttribPointer(a_attribute, buffer.num, buffer.type, false, 0, 0)
gl.enableVertexAttribArray(a_attribute)
}

// 填入plane绘制所需缓冲区数据
function initVertexBuffersForPlane(gl) {
let 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
])
let colors = new Float32Array([
1.0, 1.0, 0.7,
1.0, 0.7, 1.0,
0.7, 1.0, 1.0,
0.7, 0.7, 0.7
])
let indices = new Uint8Array([
0, 1, 2,
0, 2, 3
])
let o = new Object()
o.vertexBuffer = initArrayBufferForLaterUse(gl, vertices, 3, gl.FLOAT)
o.colorBuffer = initArrayBufferForLaterUse(gl, colors, 3, gl.FLOAT)
o.indexBuffer = initElementArrayBufferForLaterUse(gl, indices, gl.UNSIGNED_BYTE)
if (!o.vertexBuffer || !o.colorBuffer || !o.indexBuffer) {
return null
}
o.numIndices = indices.length

gl.bindBuffer(gl.ARRAY_BUFFER, null)
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null)
return o
}

// 填入triangle绘制所需缓冲区数据
function initVertexBuffersForTriangle(gl) {
let vertices = new Float32Array([
-0.8, 3.5, 0.0,
0.8, 3.5, 0.0,
0.0, 3.5, 1.8
])
let colors = new Float32Array([
1.0, 0.0, 0.0,
0.0, 1.0, 0.0,
0.0, 0.0, 1.0
])
let indices = new Uint8Array([
0, 1, 2
])
let o = new Object()
o.vertexBuffer = initArrayBufferForLaterUse(gl, vertices, 3, gl.FLOAT)
o.colorBuffer = initArrayBufferForLaterUse(gl, colors, 3, gl.FLOAT)
o.indexBuffer = initElementArrayBufferForLaterUse(gl, indices, gl.UNSIGNED_BYTE)
if (!o.vertexBuffer || !o.colorBuffer || !o.indexBuffer) {
return null
}
o.numIndices = indices.length

gl.bindBuffer(gl.ARRAY_BUFFER, null)
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null)
return o
}

function initArrayBufferForLaterUse(gl, data, num, type) {
let buffer = gl.createBuffer()
if (!buffer) {
console.log('failed to create the buffer object')
return null
}
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)
buffer.num = num
buffer.type = type
return buffer
}

function initElementArrayBufferForLaterUse(gl, data, type) {
let buffer = gl.createBuffer()
if (!buffer) {
console.log('failed to create the buffer object')
return null
}
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer)
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, data, gl.STATIC_DRAW)
buffer.type = type
return buffer
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
// 创建帧缓冲区对象
function initFramebufferObject(gl) {

let framebuffer, texture, depthBuffer

// error handling function
let 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('failed to create frame buffer object')
return error()
}

// 创建纹理缓冲区对象并设置尺寸
texture = gl.createTexture()
if (!texture) {
console.log('failed to create texture object')
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
)

// 创建渲染缓冲区对象并设置尺寸
depthBuffer = gl.createRenderbuffer()
if (!depthBuffer) {
console.log('failed to create depthbuffer object')
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
)

// 检查帧缓冲区状态
let e = gl.checkFramebufferStatus(gl.FRAMEBUFFER)
if (gl.FRAMEBUFFER_COMPLETE !== e) {
console.log('Frame buffer object is incomplete: ' + e.toString())
return error()
}

// 保留所需的数据
framebuffer.texture = texture

// 解绑定
gl.bindFramebuffer(gl.FRAMEBUFFER, null)
gl.bindTexture(gl.TEXTURE_2D, null)
gl.bindRenderbuffer(gl.RENDERBUFFER, null)

return framebuffer

}

步骤分解

步骤1:获取阴影贴图

  • 绘制目标: 帧缓冲区对象
  • 绘制视点: 光源
  • 绘制着色器:
    • 顶点着色器:负责将顶点坐标切换到光源坐标系
    • 片元着色器:将片元的z值写入纹理贴图
如何使用每个片元的z值制作一张阴影贴图?
光源视角下计算阴影贴图
光源视角下计算阴影贴图
1
gl_FragColor = vec4(gl_FragCoord.z. 0.0, 0.0, 0.0);

实际上存入阴影贴图的值也就是每个片元的 gl_FragCoord.z 的值

gl_FragCoord

gl_FragCoordvec4 类型的

  • gl_FragCoord.xy:片元在屏幕上的坐标
  • gl_FragCoord.z: 片元的深度值
gl_FragCoord是如何被计算出来的?

答: gl_FragCoord是由 gl_Position归一化 得到的。
[-1.0, 1.0] 归一化到 [0.0, 1.0]

  • gl_FragCoord.z = 0.0
    • 表示片元在 近裁剪面
  • gl_FragCoord.z = 1.0
    • 表示片元在 远裁剪面

归一化公式

1
gl_FragCoord.xyz = (gl_Position.xyz/gl_Position.w)/2.0+0.5

步骤2:使用阴影贴图

  • 绘制目标: 颜色缓冲区
  • 绘制视点: 原视点
计算出来的阴影贴图要怎么使用呢?

每一个片元都有两个值:

  • 光照坐标系下的z值(v_PositionFromLight.z)
  • 该片元对应阴影贴图中存储的z值(shadowMap)

需要将这两个值拿来比较,那么问题就来了:

问题1:怎么获取片元在光照坐标系下的z值呢?

答: 只需要

  • a_Position 顶点坐标
  • u_MvpMatrixFromLight 光源模型视图投影矩阵

计算光源MVP矩阵下的顶点坐标即可:

1
v_PositionFromLight = u_MvpMatrixFromLight * a_Position;
问题2:怎么获取片元对应shadowMap中存储的对应值呢?

需要两步:

  1. 根据上一步计算出来的v_PositionFromLight转化为纹素坐标
  2. 根据纹素坐标从阴影贴图中抽取对应纹素
步骤1:将光照坐标系下的顶点坐标转化为纹素坐标

同样需要对顶点坐标进行 纹素归一化处理
这是由于顶点坐标和纹素坐标的区间不同:

  • 顶点坐标:[-1.0, 1.0]
  • 纹素坐标: [0.0, 1.0]

归一化处理与gl_Position到gl_FragCoord归一化的过程相似

1
2
s = (v_PositionFromLight.x/v_PositionFromLight.w)/2.0+0.5;
t = (v_PositionFromLight.y/v_PositionFromLight.w)/2.0+0.5;

将归一化得到的纹素坐标放到shadowCoord中去

1
vec3 shadowCoord = (v_PositionFromLight.xyz/v_PositionFromLight.w)/2.0+0.5;

shadowCoord包含了很多信息:

  • shadowCoord.xy: 该片元对应的shadowMap坐标
  • shadowCoord.z:当前片元在光源坐标系中的归一化z值
步骤2:抽取阴影贴图中的纹素

既然计算出shadowCoord,剩下的工作就比较简单了,
可以通过 shadowCoord.xyshadowMap 中抽取纹素,
这里别忘了 内插过程

depth里最终放着阴影贴图中对应的z值

1
2
vec4 rgbaDepth = texture2D(u_ShadowMap, shadowCoord.xy);
float depth = rgbaDepth.r;

问题2 中,我们获得了用于比较的两个值:

  • shadowCoord.z 光照坐标系下被归一化的片元的z值
  • depth 同一片元对应的阴影贴图中的值

接下来只需要将这两个值拿来比较就可以了:

1
2
float visibility = (shadowCoord.z > depth +0.005)?0.7:1.0;
gl_FragColor = vec4(v_Color.rgb * visibility, v_Color.a);
马赫带(Mach band)

为什么在比较z值和阴影贴图值的时候要多给阴影贴图加上 0.005?
如果把这0.005拿掉会怎么样?

1
float visibility = (shadowCoord.z > depth)?0.7:1.0;
马赫带
马赫带
为什么会出现马赫带?

答: 由于用于比较的两个值存在 精度偏差

  • shadowCoord.z
    • 类型:RGBA分量
    • 位数:8位
    • 精度:1/256
  • depth
    • 类型:float
    • 位数:16位
    • 精度:1/65536
如何消除马赫带

答: 给较大精度的值加上一个偏移量。

注意: 偏移量应当略大于精度

如:N > M ?
N: 8位 精度1/256
M:16位 精度1/65536
因为M精度值较大,所以应该加上一个略大于1/256的值(0.005)
∴ N > M + 0.005

阴影绘制步骤

Step1 program

准备绘制的着色器程序

Step2 buffer

准备绘制图形的顶点数据

Step3 frameBuffer

创建帧缓冲区对象

Step4 bindTexture

将0号纹理绑定到帧缓冲区的纹理对象上

Step5 count shadowMap

切换光源视角,计算出阴影贴图

Step6 draw by shadowMap

切换屏幕视角,使用阴影贴图开始绘图

提高精度

Shadow.js存在哪些问题?

Shadow.js存在精度缺陷问题:

近距离光源

1
2
3
let LIGHT_X = 0
let LIGHT_Y= 7
let LIGHT_Z= 2
近距离光源
近距离光源

远距离光源

1
2
3
let LIGHT_X = 0
let LIGHT_Y= 40
let LIGHT_Z= 2
远距离光源
远距离光源
存在精度缺陷的原因是什么?

随着光源与照射物间距的变大,
gl_FragCoord.z的值也会随之变大,
最终8位的R分量无法存储下gl_FragCoord.z,导致精度缺陷

Shadow_highp

光源位置:
(0, 40, 4)

8位精度

Shadow.js绘制效果
Shadow.js绘制效果

提高精度

Shadow_highp.js绘制效果
Shadow_highp.js绘制效果
1
2
3
4
5
6
7
8
9
10
11
12
let SHADOW_FSHADER_SOURCE = `
#ifdef GL_ES
precision mediump float;
#endif
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;
}
`;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let FSHADER_SOURCE = `
#ifdef GL_ES
precision mediump float;
#endif
varying vec4 v_Color;
varying vec4 v_PositionFromLight;
uniform sampler2D u_ShadowMap;

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(){
vec3 shadowCoord = (v_PositionFromLight.xyz/v_PositionFromLight.w)/2.0 + 0.5;
vec4 rgbaDepth = texture2D(u_ShadowMap, shadowCoord.xy);
float depth = unpackDepth(rgbaDepth);
float visibility = (shadowCoord.z > depth + 0.0015)?0.7:1.0;
gl_FragColor = vec4(v_Color.rgb * visibility, v_Color.a);
}
`;
提高精度后ShadowMap里都存的什么?

假设有这样一个片元坐标z值:
gl_FragCoord.z = 2.135793057816414332378301
如果只使用 gl_FragColor.r 来存储它的话
实际存储的结果是
gl_FragCoord.z * 2^(-8) * 2^(-8) = 2.1328125

既然gl_FragColor.r放不下精度这么大的值,
那就把gl_FragColor的 rgba 四个8位值全用上

R: (2^-8, 2^0)
G: (2^-16, 2^-8)
B: (2^-24, 2^-16)
A: (0, 2^-24)

一个位数提取的问题

如何在不破坏一个二进制数的其他位的情况下,将其中需要的位数提取出来?
如:有一个二进制数 0b0001_0001_0000_1000
我要如何将它的高8位和低8位分别提取出来?

fract函数(模糊)

作用: 舍弃参数的整数部分,返回小数部分。

消除马赫带偏移量
1
float visibility = ( shadowCoord.z > depth + 0.0015 )?0.7:1.0;

之所以选0.0015作为偏移量是因为:

z精度已经提高到了float
在mediump精度下,精度为2^-10
2^-10 = 0.000976563