webGL光照

webGL光照

光照模型

为了使得渲染出来的场景更加真实,我们在3D场景中引入了光照的概念。当然,光照的实现只是在着色器中模拟出来的,不是有真的光源和光线,最终输出的还是像素的rgb数值。为了达到真实的光照效果,我们需要实现三种类型的光照,分别是环境光照、漫反射光照和镜面反射光照,这个光照模型叫做冯氏反射模型。下面这张图形象的说明了什么是冯氏反射模型。

虽然没有真实的光源,但是我们依然需要在程序中模拟光源。光源分为两种,平行光和点光源。顾名思义,平行光射入物体的所有的顶点的角度都是一样的,而点光源射入物体的角度则取决于物体与点光源的相对位置,下面两张图展示了平行光与点光源的不同。

无论是平行光还是点光源,照射在物体上都会产生漫反射和镜面反射的效果,至于漫反射和镜面反射效果的强弱则由物体本身的材质决定。
那么怎么计算光照的效果呢?从感官上来说,我们知道如果光线直直地照射到物体上会把物体照的很亮,而如果光线与物体成一定的角度,则物体的亮度会下降。就像在下图中展示的一样,光线A与顶点的法线的夹角为0,顶点完全反射了射入的光线。光线B与顶点完全平行,顶点对光线B没有任何反射。光线C与顶点成一定的角度,我们可以把光线C在入射点分成两部分,其中C1与法线的夹角为0,C2与法线成90度角,也就是说只有C1被完全反射了,C2没有对顶点起到照亮的作用。

按照这个思路,我们可以通过计算光线与顶点的夹角的cos值来计算光照的效果。这种计算方式虽然简单,但是实际效果还是不错的。

带光照的demo

下面这个demo演示添加了光照效果的茶壶,茶壶通过加载茶壶模型(带有茶壶顶点坐标,纹理坐标,法线向量等信息的json文件),添加了环境光和一个点光源,可以通过开启和关闭光照来查看光照的效果。

启用光照

shader代码

shader代码的实现可以用两种方式,一种是在顶点着色器上实现光照效果,叫做逐顶点光照;另一种是在片段着色器上实现光照效果,叫逐片段光照。逐顶点光照只计算顶点缓冲中每个顶点的光照,顶点之间的像素点的光照则通过线性插值来计算。而逐片段光照则会计算每个像素点的光照,其中计算光照所用到的像素坐标和法线则通过顶点的数据线性插值得到。

相对于逐顶点光照,逐片段光照会更加真实。考虑下面这个例子,B点的光线与顶点法线夹角接近0度,B点的亮度应该比A点和C点都要亮。但是使用逐顶点光照的话,B点的光照效果是A点和C点的平均值,这显然是不对的。

当然逐顶点光照也不是一定错误,如果场景中只有平行光的话,逐顶点光照是没错的。所以在场景中没有点光源或者物体上的光照效果真实性要求不高的情况下,会使用逐顶点光照,这样能够节省一些GPU资源(不需要为每个像素都计算和存储法线变量)。

逐顶点光照shader

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

<script id="per-vertex-lighting-vs" type="x-shader/x-vertex">
attribute vec3 aVertexPosition;
attribute vec3 aVertexNormal;
attribute vec2 aTextureCoord;

uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;
uniform mat4 uNMatrix;//uMVMatrix的逆转置矩阵,用于法线变换

uniform vec3 uAmbientColor;
uniform float uMaterialShininess;//材质光泽度

uniform vec3 uPointLightingLocation;
uniform vec3 uPointLightingColor;

uniform bool uUseLighting;

varying vec2 vTextureCoord;
varying vec3 vLightWeighting;

void main(void) {
vec4 mvPosition = uMVMatrix * vec4(aVertexPosition, 1.0);
gl_Position = uPMatrix * mvPosition;
vTextureCoord = aTextureCoord;

if (!uUseLighting) {
vLightWeighting = vec3(1.0, 1.0, 1.0);
}
else {
//计算点光源的光线方向
vec3 lightDirection = normalize(uPointLightingLocation - mvPosition.xyz);
//计算顶点法线方向,因为顶点做了模型变换,所以法线也需要做变换
vec3 transformedNormal = (uNMatrix * vec4(aVertexNormal,1.0)).xyz;
//只考虑夹角在0到90度之间的光线
float diffuseLightWeighting = max(dot(transformedNormal, lightDirection), 0.0);
//相机的位置始终在原点,所以观察方向就是片段的反方向
vec3 eyeDirection = normalize(-mvPosition.xyz);
//使用reflect函数计算镜面反射的反射光的方向
vec3 reflectionDirection = reflect(-lightDirection, transformedNormal);
//镜面反射效果通过公式(Rm · V)^a计算,Rm是前面计算的reflectionDirection,V是观察方向eyeDirection,a是材质的光泽度
float specularLightWeighting = = pow(max(dot(reflectionDirection, eyeDirection), 0.0), uMaterialShininess);
//合并不同光照的效果
vLightWeighting = uAmbientColor + uPointLightingColor * specularLightWeighting + uPointLightingColor * diffuseLightWeighting;

}
}
</script>

<script id="per-vertex-lighting-fs" type="x-shader/x-fragment">

precision mediump float;


varying vec2 vTextureCoord;
varying vec3 vLightWeighting;

uniform sampler2D uSampler;

void main(void) {
vec4 fragmentColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));
//将光照效果乘上纹理颜色得到最终的rgb数值
gl_FragColor = vec4(fragmentColor.rgb * vLightWeighting, fragmentColor.a);
}
</script>

逐片段光照shader

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


<script id="per-fragment-lighting-vs" type="x-shader/x-vertex">
attribute vec3 aVertexPosition;
attribute vec3 aVertexNormal;
attribute vec2 aTextureCoord;

uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;
uniform mat4 uNMatrix;

varying vec2 vTextureCoord;
varying vec3 vTransformedNormal;
varying vec4 vPosition;


void main(void) {
vPosition = uMVMatrix * vec4(aVertexPosition, 1.0);
gl_Position = uPMatrix * vPosition;
vTextureCoord = aTextureCoord;
vTransformedNormal = (uNMatrix * vec4(aVertexNormal, 1.0)).xyz;
}
</script>

<script id="per-fragment-lighting-fs" type="x-shader/x-fragment">

precision mediump float;


varying vec2 vTextureCoord;
varying vec3 vTransformedNormal;//顶点着色器传递的片段法向量
varying vec4 vPosition;//顶点着色器传递的片段坐标

uniform bool uUseLighting;

uniform vec3 uAmbientColor;
uniform float uMaterialShininess;//材质光泽度

uniform vec3 uPointLightingLocation;
uniform vec3 uPointLightingColor;

uniform sampler2D uSampler;


void main(void) {
vec3 lightWeighting;
if (!uUseLighting)
{
lightWeighting = vec3(1.0, 1.0, 1.0);
}
else
{
vec3 lightDirection = normalize(uPointLightingLocation - vPosition.xyz);
//线性插值的法向量需要先归一化
vec3 normal = normalize(vTransformedNormal);
//与逐顶点光照类似
vec3 eyeDirection = normalize(-vPosition.xyz);
vec3 reflectionDirection = reflect(-lightDirection, normal);
float specularLightWeighting = = pow(max(dot(reflectionDirection, eyeDirection), 0.0), uMaterialShininess);
float diffuseLightWeighting = max(dot(normal, lightDirection), 0.0);
lightWeighting = uAmbientColor + uPointLightingColor * specularLightWeighting + uPointLightingColor * diffuseLightWeighting;
}

vec4 fragmentColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));
gl_FragColor = vec4(fragmentColor.rgb * lightWeighting, fragmentColor.a);
}
</script>