首先我们系统地梳理一下 Three.js 中的 GLSL 语言基础。
GLSL 是 OpenGL Shading Language 的缩写,它是一种类 C 的高级着色器语言,用于在 GPU 上执行的可编程渲染管线中编写着色器。在 Three.js 中,我们通过 ShaderMaterial 或 RawShaderMaterial 使用 GLSL 编写顶点着色器和片段着色器。
在 Three.js 的渲染管线中,有两个必须的着色器阶段:
顶点着色器 (Vertex Shader): 每个顶点执行一次。负责将顶点位置从模型空间转换到裁剪空间。也可以计算并传递给片段着色器的数据(如 varying)。
片段着色器 (Fragment Shader / Pixel Shader): 每个像素(严格说是每个片段)执行一次。负责计算该像素的最终颜色。
一个最简单的 Three.js 着色器材质结构:
javascript
const material = new THREE.ShaderMaterial({
vertexShader: vertexShaderSource,
fragmentShader: fragmentShaderSource,
uniforms: { /* 传递外部变量 */ }
});
基本类型:
float: 单精度浮点数 (如 3.14)
int: 整数 (如 1)
bool: 布尔值 (如 true)
向量类型: 非常重要!
vec2, vec3, vec4: 浮点向量 (如位置、颜色 rgb、纹理坐标 uv)
ivec2, ivec3, ivec4: 整数向量
bvec2, bvec3, bvec4: 布尔向量
访问方式灵活:xyzw, rgba, stpq
glsl
vec3 color = vec3(1.0, 0.0, 0.0);
float r = color.r;
vec2 uv = color.xy;
矩阵类型:
mat2, mat3, mat4: 主要用于变换。纹理采样器:
sampler2D: 2D 纹理
samplerCube: 立方体贴图
const: 常量,编译时确定。
attribute: (仅在顶点着色器中) 每个顶点特有的数据,如位置、法线、UV。在 Three.js 中通常通过 BufferGeometry 自动传递。
uniform: 全局只读变量,所有顶点和片段共享,一帧内不变。例如:变换矩阵、时间、纹理、颜色等。在 JavaScript 端通过 uniforms 对象设置。
varying: (在旧版本 GLSL 中) 用于从顶点着色器向片段着色器传递插值数据。在现代 OpenGL(WebGL 2.0 / GLSL 300 es)中,被 in 和 out 取代。
in / out: (GLSL 300 es 及以后)
顶点着色器中:in 代替 attribute,out 声明要传递给片段着色器的变量。
片段着色器中:in 接收来自顶点着色器的插值变量,out 声明最终的输出颜色(通常是 vec4 到颜色缓冲区)。
顶点着色器:
gl_Position: 必须赋值!顶点在裁剪空间中的位置(vec4)。
gl_PointSize: 点渲染时的大小。
片段着色器:
gl_FragColor: (旧版本) 输出颜色。
out vec4 fragColor: (GLSL 300 es) 自定义名称的输出颜色。
GLSL 内置了大量优化过的数学函数:
sin, cos, tan, asin, acos, atan
pow, exp, log, sqrt, inversesqrt
abs, floor, ceil, fract (获取小数部分),mod
min, max, clamp (限制范围),mix (线性插值,mix(a, b, t) 相当于 a * (1.0 - t) + b * t)
dot (点积),cross (叉积),length (向量长度),distance (两点距离),normalize (归一化)
texture2D(sampler2D, vec2): (旧版本) 纹理采样。
texture(sampler, uv)。Three.js 默认使用 WebGL 1.0 和 GLSL 1.00(即较旧的语法)。如果需要使用现代语法(in/out),需明确指定:
javascript
const material = new THREE.RawShaderMaterial({
glslVersion: THREE.GLSL3, // 使用 GLSL 300 es
vertexShader: `#version 300 es ...`,
fragmentShader: `#version 300 es ...`,
uniforms: {}
});
关键区别示例:
glsl
// 顶点着色器
attribute vec3 position;
attribute vec2 uv;
uniform mat4 projectionMatrix;
uniform mat4 modelViewMatrix;
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
// 片段着色器
varying vec2 vUv;
uniform sampler2D map;
void main() {
gl_FragColor = texture2D(map, vUv);
}
glsl
#version 300 es
precision highp float;
// 顶点着色器
in vec3 position;
in vec2 uv;
uniform mat4 projectionMatrix;
uniform mat4 modelViewMatrix;
out vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
// 片段着色器
in vec2 vUv;
uniform sampler2D map;
out vec4 fragColor;
void main() {
fragColor = texture(map, vUv);
}
当使用 ShaderMaterial(而非 RawShaderMaterial)时,Three.js 会自动为你提供一些常用的 uniforms 和 attributes,无需手动声明。
projectionMatrix: 摄像机投影矩阵
modelViewMatrix: 模型视图矩阵(模型变换 × 视图变换)
modelMatrix: 模型的世界变换矩阵
viewMatrix: 视图矩阵
normalMatrix: 用于变换法线的矩阵
cameraPosition: 摄像机在世界空间中的位置
position: 顶点位置
normal: 顶点法线
uv: 纹理坐标
color: 顶点颜色
这意味着在 ShaderMaterial 的顶点着色器中,你可以直接使用 uniform mat4 projectionMatrix; 而无需在 JavaScript 中定义它。
javascript
// JavaScript 部分
const vertexShader = `
varying vec2 vUv;
varying vec3 vPosition;
void main() {
vUv = uv;
vPosition = position;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`;
const fragmentShader = `
uniform float time;
uniform vec3 color;
varying vec2 vUv;
varying vec3 vPosition;
void main() {
// 创建一个基于UV和时间的动态效果
float wave = sin(vUv.x * 10.0 + time) * 0.5 + 0.5;
vec3 finalColor = color * wave;
// 添加一个基于位置的渐变
float gradient = vPosition.y * 0.5 + 0.5;
finalColor *= gradient;
gl_FragColor = vec4(finalColor, 1.0);
}
`;
const material = new THREE.ShaderMaterial({
vertexShader: vertexShader,
fragmentShader: fragmentShader,
uniforms: {
time: { value: 0.0 },
color: { value: new THREE.Color(0xff00ff) }
}
});
// 在动画循环中更新 uniform
function animate() {
material.uniforms.time.value = performance.now() / 1000;
}
精度指定: 在片段着色器顶部,通常需要指定浮点数精度 precision mediump float;(或 highp)。在 GLSL 300 es 中,这通常是必须的。
逐步开发: 从最简单的纯色输出开始,逐步添加复杂计算。
使用归一化坐标: 尽量在 [0, 1] 范围内处理颜色和 UV。
向量化运算: GLSL 对向量运算高度优化,尽量使用 vec3 a = b * c; 而非逐分量计算。
重用计算: 将重复的计算结果存储在局部变量中。
官方文档:
WebGL 规范 (查询 GLSL 函数)
在线编辑与学习:
fragColor,并且主函数是 mainImage(out vec4 fragColor, in vec2 fragCoord)。书籍与教程:
《The Book of Shaders》 :极佳的入门渐进式教程。
《WebGL Programming Guide》 有详细的着色器基础。
掌握 GLSL 是释放 Three.js 和 WebGL 真正潜力的关键。从修改简单示例开始,慢慢理解光线、材质和纹理的编写,你会逐渐能够创造出令人惊叹的视觉特效。