1. 参考
  2. 参数
  3. 数据类型
  4. 数据转换
  5. 内建变量
  6. precision(精度)
  7. uniform(统一值)
  8. varying
  9. 函数
    1. dot 计算两向量的点积
    2. normalize 归一化
  10. Vertex shaders(顶点着色器)
  11. Fragment shaders(片元着色器)
  12. Three.js内置参数
  13. 法线(normal)
  14. 调试工具
  15. 附录
    1. 名词解释
  16. 示例

WebGL 着色器

着色器是一段跑在GPU中的程序,使用GLSL语言编写(OpenGL Shading Language)
着色器又分为两种,顶点着色器和片元着色器
其中顶点点着色器负责计算每个顶点的位置/颜色等信息,片元着色器则用来计算这些顶点间每个片元(像素)的颜色

参考

The Book of Shaders
WebGLProgram threejs.org
翻译:非常详细易懂的法线贴图
lesson-webgl-shader-threejs
Three.js Beginners GLSL Tutorial

参数

varying 从顶点着色器传递变量到片元着色器
uniform 由js代码传递给顶点/片元着色器的变量,只读
attribute 由js代码传递给顶点着色器,只读

数据类型

  • int 整数
  • float 浮点数
  • bool 布尔值(true|false|0|1)
  • vec2,vec3,vec4 2,3,4维向量(浮点数)
  • ivec2,ivec3,ivec4 2,3,4维向量(整数)
  • mat2,mat3,mat4 2X2,3x3,4x4矩阵(浮点数)
  • void 无返回值的函数
1
2
3
4
5
6
void main(){
int eNum = 1;
float eFNum = 1.0;
bool eBool = true;
vec3 eV3 = vec2(1.0, 1.0, 1.0);
}

GLSL不会自动转换数据类型,如下代码会报错

1
2
3
4
5
int eNum = 1.0;
// or
int eNum = 1;
float eFNum = eNum;
// global variable initializers should be constant expressions (uniforms and globals are allowed in global initializers for legacy compatibility)

数据转换

1
2
3
4
5
6
7
8
9
10
11
12
13

vec3 v = vec3(1.0,2.0,3.0);
// 浮点数向量转整数向量
ivec3 iv = ivec3(v);
// 整数向量转浮点数向量
vec3 j = vec3(ivec3);
// 转4维向量
vec4 v4 = vec4(v, 1.0);
// 转2维向量
vec2 v2 = vec2(v);
// or
vec2 v2 = v.xy;

内建变量

gl_FragColor
gl_FragCoord

precision(精度)

precision mediump float;
precision lowp float;
precision highp float;

uniform(统一值)

uniform是连通CPU和GPU的桥梁

1
uniform vec3 u_color;

uniform只读,不可修改
如下错误示范

1
2
3
4
5
6
uniform vec3 u_color;
void main(){
//l-value required (can't modify a uniform "color")
u_color = vec3(1.0,1.0,1.0);
}

varying

函数

dot 计算两向量的点积

1
2
3
vec3 a = vec3(1, 1, 0);
vec3 b = vec3(-1, 0, 0);
float d = dot(a, b);

normalize 归一化

1
vec3 vertex = normalize(vec3(2,3,4));

Vertex shaders(顶点着色器)

顶点着色器的作用是把3D数据转换为屏幕上的2D形状,即计算3维空间在2维屏幕上投影的坐标.

可以把顶点着色器放在html中,然后操作Dom来获取相关代码.

1
2
3
4
5
6
7
8
9
10
11
// 顶点着色器
<script id="vs" type="x-shader/x-vertex">
// 最常用的顶点计算方式
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
// or
// gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4( position, 1.0 );
</script>
// 片段着色器
<script id="fs" type="x-shader/x-vertex">
gl_FragColor = vec4(1.0)
</script>
1
2
3
4
// 获取顶点着色器
var vs = document.getElementById('vs').textContent;
// 获取片段着色器
var fs = document.getElementById('fs').textContent;

Fragment shaders(片元着色器)

例1

1
2
3
4
5
// 片元着色器
uniform vec3 color;
void main(){
gl_FragColor = vec4(color, 1.0);
}

Three.js内置参数

由Geometry和BufferGeometry提供的顶点属性

  • attribute vec3 position 顶点在物体中的坐标位置
  • attribute vec3 normal 法向量
  • attribute vec2 uv uv坐标,可用uv.x和uv.y分别获取顶点的横纵坐标
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var planeGeo = new THREE.PlaneBufferGeometry(1,1);
console.log(planeGeo.attributes.normal.array);
// Float32Array [ 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, … ]
planeGeo.attributes.normal.array.length;
// 12

var planeGeo = new THREE.PlaneBufferGeometry(10,10);
// position
console.log(planeGeo.attributes.position.array);
// Float32Array [ -5, 5, 0, 5, 5, 0, -5, -5, 0, 5, … ]
// normal
console.log(planeGeo.attributes.normal.array);
// Float32Array [ 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, … ]
// uv
console.log(planeGeo.attributes.uv.array);
// Float32Array [ 0, 1, 1, 1, 0, 0, 1, 0 ]

内置的uniform

  • uniform mat3 normalMatrix; 法向矩阵,modelViewMatrix的反转
  • uniform mat4 model modelMatrix; 模型空间矩阵
  • uniform mat4 viewMatrix; 视图空间矩阵
  • uniform mat4 modelViewMatrix; 模型空间矩阵和视图空间矩阵的组合(viewMatrix * modelMatrix)
  • uniform mat4 projectionMatrix; 投影矩阵,用于转换3D坐标到2D平面
  • uniform vec3 cameraPosition; 镜头在world space中的位置

也可用uniforms传入自定义的uniform

1
2
3
4
5
6
7
8
9
10
11
12
shaderMaterial = new THREE.ShaderMaterial({
uniforms: {
color: {
type: 'v3', // 数据类型,3维向量
value: new THREE.Color('#60371b'), // 数据值
},
light: {
type: 'v3',
value: this.spotlight.position,
},
},
});

自己用uniforms属性塞进来的uniform需要声明才能使用
而内置的uniform不需要这样做,Three.js已经在顶点着色器里声明了.再声明会报错的,例如:

1
2
3
4
<script id="vs" type="x-shader/x-vertex">
uniform mat4 projectionMatrix;
// 'projectionMatrix' : redefinition
</script>

但这些内置的uniform并没有在片元着色器里声明(viewMatrix|cameraPosition除外)
要用的话需要自己声明一次,或者用varying从顶点着色器传过来

1
2
3
4
<script id="fs" type="x-shader/x-vertex">
// 这个在顶点着色器里会报错,而片元着色器不会.
uniform mat3 normalMatrix;
</script>

法线(normal)

法线是垂直于平面内某点的一条直线(曲面则为该点的切平面)
通常用一个向量来表示,称这个向量叫法向量
法线 维基百科
法向量通常位于-1,1之间

1
2
3
4
5
6
// 无论Three.js正方形的大小如何变化,法向量距原点依然在[-1,1]区间内
var planeGeo = new THREE.BoxBufferGeometry(10,10,10);
planeGeo.attributes.normal.array
// Float32Array(72) [1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0...
var planeGeo = new THREE.BoxBufferGeometry(100,100,10);
// Float32Array(72) [1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0...

可通过归一化将法向量映射到[0,1]间

1
vec3 vNormal = normalize(normalMatrix * normal);

在WebGL中,法向量用于表示模型顶点的朝向.常被用于计算光照/判断模型某一面的朝向

调试工具

firefox自带着色器调试工具,开启方式:

1
Toolbox option > 勾选: Shader Editor

附录

名词解释

转换顶点坐标到视图空间

1
2
3
4
5
uniform mat4 modelViewMatrix;
void main(){
vec3 vertex = vec3(1.0,1.0,1.0);
vec4 vertexCameraSpace = modelViewMatrix * vec4(vertex,1.0);
}
1
2
3
4
5
// 注意,如果是光源的话不需要计算modelMatrix
void main() {
vec3 light = vec3(1.0,1.0,1.0);
vec4 lightCameraSpace = viewMatrix * vec4(vertex,1.0);
}
  • 点积(又称为点乘/数量积/内积)
    一个向量再另一个向量上的投影
    结果为负时,两个向量方向相反
    结果为0时,两向量互相垂直

需要注意的是,少个分号(;)也会导致着色器报错

示例

计算光照

1
2
3
4
5
6
//顶点着色器
varying vec3 vNormal;
void main(){
vNormal = normal;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
1
2
3
4
5
6
7
8
//片元着色器
varying vec3 vNormal;
void main() {
vec3 color = vec3(1, 1, 0);
vec3 light = normalize(vec3(0, 10, 10));
float dProd = max(0.0,dot(vNormal, light));
gl_FragColor = vec4(color.r * dProd, color.g * dProd, color.b * dProd, 1.0);
}