Three.js着色器编程进阶:从入门到实战指南
核心结论:着色器编程是Three.js视觉效果的终极控制方式
着色器()是运行在GPU上的小程序,允许你直接控制每一个像素和顶点的渲染结果。对于Three.js开发者而言,掌握着色器编程意味着:
完全控制渲染管线:突破等内置材质的限制
实现极致性能:复杂视觉效果通过GPU并行计算,CPU负载极低
创造独特视觉风格:水流、火焰、光效、扭曲等效果均可通过着色器实现
本文档提供从基础概念到实战应用的全链路指南,所有代码示例基于Three.js r128及以上版本,GLSL版本为WebGL 1.0(GLSL ES 1.00)。
第一部分:着色器基础概念(必须掌握)
1.1 顶点着色器( )与片元着色器( )
| 类型 | 执行阶段 | 核心职责 | 必须输出的变量 |
|---|---|---|---|
| 顶点着色器 | 每个顶点执行一次 | 计算顶点最终位置,传递数据到片元着色器 | (vec4) |
| 片元着色器 | 每个像素(片元)执行一次 | 计算像素最终颜色 | (vec4) |
1.2 GLSL基础数据类型
// 标量类型
float a = 1.0; // 浮点数(必须带小数点)
int b = 1; // 整数
bool c = true; // 布尔值
// 向量类型(最常用)
vec2 v2 = vec2(0.5, 0.8); // 2分量浮点向量,可用xyzw/rgba访问
vec3 v3 = vec3(1.0, 0.0, 0.0);
vec4 v4 = vec4(1.0); // 所有分量为1.0
// 矩阵类型
mat3 m3 = mat3(1.0); // 3x3单位矩阵
mat4 m4 = mat4(1.0); // 4x4单位矩阵
1.3 顶点着色器标准结构
// 默认接收的数据(Three.js自动传递)
mat4 ; // 模型视图矩阵
mat4 ; // 投影矩阵
vec3 ; // 顶点位置
vec2 uv; // UV坐标
// 输出到片元着色器的变量(需使用)
vec2 vUv;
void main() {
vUv = uv; // 传递UV坐标
// 计算最终顶点位置(标准写法)
= 1.0; // 可选:点精灵大小
= vec4(, 1.0);
}
1.4 片元着色器标准结构
highp float; // 精度限定符:lowp//highp
vec2 vUv; // 接收顶点着色器传来的数据
void main() {
// 输出颜色(RGB+Alpha)
= vec4(vUv.x, vUv.y, 0.5, 1.0);
}
第二部分:Three.js中编写着色器的两种方式
2.1 (推荐新手)
as THREE from 'three';
const = new THREE.(2, 2);
const = new THREE.({
: {
time: { value: 0 },
: { value: new THREE.Color() },
: { value: new THREE.Color() }
},
:
vec2 vUv;
void main() {
vUv = uv;
= 1.0;
= </> <> vec4(, 1.0);
}
,
:
float time;
vec3 ;
vec3 ;
vec2 vUv;
void main() {
vec3 color = mix(, , vUv.x + sin(vUv.y </> 10.0 + time) <> 0.1);
= vec4(color, 1.0);
}
,
side: THREE. // 可选:双面渲染
});
const mesh = new THREE.Mesh(, );
scene.add(mesh);
// 动画循环中更新
(time) {
..time.value = time 0.001;
e();
}
特性:
Three.js自动提供顶点着色器中的默认(, uv, 等)
自动提供和
支持透明、深度测试等标准材质属性
2.2 (完全控制)
const = new THREE.({
: {
time: { value: 0 },
: { value: . },
: { value: mesh. },
// 需要手动声明所有
},
:
mat4 ;
mat4 ;
vec3 ;
vec2 uv;
vec2 vUv;
void main() {
vUv = uv;
= <> </> vec4(, 1.0);
}
,
: ...
});
特性:
Three.js不注入任何默认和
必须在中显式声明所有使用的矩阵和属性
适用于需要精确控制或避免Three.js自动注入的场景
第三部分:进阶技巧与常用模式
3.1 UV坐标变换与重复纹理
vec2 vUv;
void main() {
// 缩放UV(重复次数)
vec2 = vUv 3.0;
// 偏移UV(滚动效果)
vec2 = vUv + vec2(time 0.1, 0.0);
// 旋转UV
float angle = time 0.5;
vec2 = vUv - 0.5;
vec2 = vec2(
.x cos(angle) - .y sin(angle),
.x sin(angle) + .y cos(angle)
) + 0.5;
// 使用fract实现无缝平铺
vec2 = fract(vUv 5.0);
= vec4(.x, .y, 0.5, 1.0);
}
3.2 常用数学函数
// 平滑插值(三次方)
float (float edge0, float edge1, float x);
// 正弦/余弦(参数为弧度)
float = sin(angle);
float = cos(angle);
// 取模(返回余数)
float = mod(x, y);
// 限制范围
float = clamp(x, 0.0, 1.0);
// 线性插值
float = mix(a, b, t);
// 绝对值
float = abs(x);
// 幂运算
float = pow(x, 2.0);
// 平方根
float = sqrt(x);
3.3 噪声函数(模拟自然效果)
GLSL标准库不包含噪声函数,需要手动实现。以下是噪声的实用实现:
// 简单的随机函数
float (vec2 st) {
fract(sin(dot(st.xy, vec2(12.9898,78.233))) 43758.);
}
// 平滑插值噪声
float noise(vec2 st) {
vec2 i = floor(st);
vec2 f = fract(st);
float a = (i);
float b = (i + vec2(1.0, 0.0));
float c = (i + vec2(0.0, 1.0));
float d = (i + vec2(1.0, 1.0));
vec2 u = f f (3.0 - 2.0 f);
mix(mix(a, b, u.x), mix(c, d, u.x), u.y);
}
// FBM(分形布朗运动)叠加多层噪声
float fbm(vec2 st, int , float , float ) {
float value = 0.0;
float = 0.5;
float = 4.0;
for(int i = 0; i < ; i++) {
value += noise(st );
= ;
= ;
}
value;
}
// 使用示例:生成云彩效果
void main() {
vec2 st = vUv 5.0;
float = fbm(st, 5, 0.5, 2.0);
vec3 color = mix(vec3(0.2, 0.4, 0.8), vec3(0.9, 0.9, 0.9), );
= vec4(color, 1.0);
}
3.4 光照计算(在着色器中实现自定义光照)
// 顶点着色器
mat4 ;
mat4 ;
mat3 ; // Three.js自动提供
vec3 ;
vec3 ;
vec3 ;
vec3 ;
void main() {
= ( );
vec4 = vec4(, 1.0);
= -.xyz;
= ;
}
// 片元着色器
vec3 ;
vec3 ;
vec3 ;
vec3 ;
vec3 ;
void main() {
vec3 = ();
vec3 = ();
// 漫反射
float diff = max(dot(, ), 0.0);
vec3 = diff ;
// 环境光
vec3 = ;
// 高光(Blinn-Phong)
vec3 = ();
vec3 = ( + );
float spec = pow(max(dot(, ), 0.0), 32.0);
vec3 = spec vec3(1.0);
vec3 color = ( + + );
= vec4(color, 1.0);
}
第四部分:性能优化与调试
4.1 性能优化核心准则
| 优化项 | 说明 | 优先级 |
|---|---|---|
| 减少分支语句 | 避免在中使用if/else,使用step/mix等函数替代 | 高 |
| 精度限定符 | 不要求高精度时使用或lowp | 高 |
| 纹理采样次数 | 尽量减少调用 | 中 |
| 统一变量数量 | 减少数量,必要时打包为vec4数组 | 中 |
| 顶点着色器复杂度 | 保持顶点着色器简单,将计算移到片元着色器 | 低 |
分支优化示例:
// 不推荐
if (value > 0.5) {
color = vec3(1.0, 0.0, 0.0);
} else {
color = vec3(0.0, 0.0, 1.0);
}
// 推荐
color = mix(vec3(0.0, 0.0, 1.0), vec3(1.0, 0.0, 0.0), step(0.5, value));
4.2 调试着色器的三种方法
颜色可视化输出
// 调试UV坐标
= vec4(vUv.x, vUv.y, 0.0, 1.0);
// 调试法线
= vec4( 0.5 + 0.5, 1.0);
// 调试时间
= vec4(sin(time), cos(time), 0.0, 1.0);
使用浏览器扩展
.js:捕获WebGL帧,查看源码和值
Three.js :实时查看材质状态
错误检查
const = new THREE.({ ... });
.('error', (event) => {
.error(' error:', event.error);
});
4.3 常见错误及解决方案
| 错误现象 | 常见原因 | 解决方案 |
|---|---|---|
| 物体不显示(黑色) | 片元着色器未输出有效颜色;顶点位置超出裁剪空间 | 检查赋值;验证的w分量 |
| 编译错误提示 | GLSL语法错误;使用了未声明的变量;精度未声明 | 检查分号、括号匹配;验证/声明 |
| 纹理显示异常 | UV坐标传递错误;纹理未加载完成 | 输出vUv验证;确保纹理ready后设置 |
| 性能急剧下降 | 片元着色器过于复杂;过多次纹理采样 | 简化数学运算;减少采样次数 |
第五部分:实战案例 – 动态流光效果
5.1 完整代码实现
<! html>
<html lang="zh-CN">
<head>
<meta ="UTF-8">
<style>
body { : 0; : ; }
#info {
: ;
top: 20px;
left: 20px;
color: white;
: rgba(0,0,0,0.6);
: 10px;
-: 5px;
font-: ;
-: none;
z-index: 100;
}
</style>
</head>
<body>
<div id="info">
Three.js 着色器流光效果 | 鼠标移动控制方向
</div>
< type="">
{
"": {
"three": "@0.128.0/build/three..js"
}
}
</>
< type="">
as THREE from 'three';
{ } from '@0.128.0//jsm//.js';
// 初始化场景、相机、渲染器
const scene = new THREE.Scene();
scene. = new THREE.Color();
const = new THREE.(45, . / ., 0.1, 1000);
..set(2, 1.5, 3);
.(0, 0, 0);
const = new THREE.({ : true });
.(., .);
.(.);
.body.(.);
// 轨道控制
const = new (, .);
. = true;
// 创建几何体(环面结)
const = new THREE.(0.8, 0.2, 200, 32, 3, 4);
// 着色器
const = {
uTime: { value: 0 },
: { value: new THREE.(0.5, 0.5) },
: { value: new THREE.Color() },
: { value: new THREE.Color() },
: { value: 0.8 }
};
// 顶点着色器
const =
vec2 vUv;
vec3 ;
void main() {
vUv = uv;
vec4 = <> vec4(, 1.0);
= .xyz;
= 1.0;
= </> ;
}
;
// 片元着色器
const =
float uTime;
vec2 ;
vec3 ;
vec3 ;
float ;
vec2 vUv;
vec3 ;
// 随机函数
float (vec2 st) {
fract(sin(dot(st.xy, vec2(12.9898,78.233))) <> 43758.);
}
// 平滑噪声
float noise(vec2 st) {
vec2 i = floor(st);
vec2 f = fract(st);
float a = (i);
float b = (i + vec2(1.0, 0.0));
float c = (i + vec2(0.0, 1.0));
float d = (i + vec2(1.0, 1.0));
vec2 u = f </> f <> (3.0 - 2.0 </> f);
mix(mix(a, b, u.x), mix(c, d, u.x), u.y);
}
void main() {
// 基础UV变换
vec2 uv = vUv;
// 动态流动方向(受鼠标影响)
vec2 = vec2(0.8, 0.3) + ( - 0.5) <> 1.5;
float = uTime </> 0.8;
// 第一层流动条纹
float = sin(uv.x <> 12.0 - uv.y </> 8.0 + <> 2.0);
= clamp( </> 0.8 + 0.5, 0.0, 1.0);
// 第二层流动(反向)
float = cos(uv.y <> 15.0 + uv.x </> 5.0 - <> 1.5);
= clamp( </> 0.7 + 0.6, 0.0, 1.0);
// 噪声纹理增强细节
vec2 = uv <> 8.0 + uTime </> 0.2;
float = noise();
// 组合流光强度
float = ( + ) <> 0.8 + </> 0.3;
= pow(, 1.2) <> ;
// 根据UV和流动方向混合颜色
vec3 = + vec3(sin(uTime </> 0.5) <> 0.2, cos(uTime </> 0.7) <> 0.2, sin(uTime </> 0.9) <> 0.2);
vec3 = + vec3(cos(uTime </> 0.6) <> 0.2, sin(uTime </> 0.8) <> 0.2, cos(uTime </> 1.0) <> 0.2);
vec3 = mix(, , uv.x + uv.y </> 0.5);
vec3 = vec3(0.8, 0.4, 1.0);
// 最终颜色:基础色 + 流光色
vec3 = + <> </> 0.8;
// 添加边缘光晕(基于位置深度)
float = 1.0 - abs(.z) <> 0.8;
+= vec3(0.5, 0.3, 0.8) </> <> 0.3;
// 输出
= vec4(, 1.0);
}
;
const = new THREE.({
: ,
: ,
: ,
side: THREE.,
: false,
: true // 启用色调映射,增强视觉效果
});
const mesh = new THREE.Mesh(, );
scene.add(mesh);
// 添加简单网格辅助线(可选)
const = new THREE.(5, 20, , );
..y = -1;
scene.add();
// 鼠标交互
const mouse = new THREE.(0.5, 0.5);
.('', (event) => {
mouse.x = event. / .;
mouse.y = event. / .;
..value = mouse;
});
// 动画循环
let clock = new THREE.Clock();
() {
const = clock.();
.uTime.value = ;
// 自动旋转效果(可选)
// mesh..y = 0.2;
// mesh..x = Math.sin( 0.3) 0.3;
.();
.(scene, );
e();
}
();
// 窗口适配
.('', , false);
() {
. = . / .;
.ix();
.(., .);
}
.log('着色器流光效果已启动');
</>
</body>
</html>
5.2 效果说明与参数调节
| 参数 | 作用 | 推荐调节范围 |
|---|---|---|
uTime |
动画时间,控制流动速度 | 自动更新 |
|
鼠标位置,影响流动方向 | 0-1范围 |
|
起始颜色 | RGB任意 |
|
结束颜色 | RGB任意 |
|
流光强度 | 0.0-1.5 |
第六部分:进阶学习路径与资源
6.1 学习路径建议
1. 基础阶段(1-2周):掌握GLSL语法、顶点/片元着色器职责、Three.js中的使用
2. 进阶阶段(3-4周):实现UV变换、噪声函数、基础光照、纹理采样
3. 高级阶段(5-8周):后处理效果()、自定义几何体数据传递、性能优化
4. 精通阶段:实现完整渲染管线定制、 (WebGL 2.0)
6.2 权威资源列表
| 资源类型 | 名称 | 说明 |
|---|---|---|
| 官方文档 | The Book of | 最权威的着色器入门教程,中英文版本 |
| 官方规范 | GLSL ES 1.00 | Group发布的GLSL ES标准文档 |
| Three.js示例 | Three.js | 官方着色器示例集合 |
| 社区参考 | 全球最大的着色器作品库,可直接参考算法实现 | |
| 调试工具 | .js | WebGL帧调试器 |
6.3 常见问题FAQs
Q1:如何将外部纹理传递到着色器?
: {
: { value: new THREE.().load('.jpg') }
}
;
vec2 vUv;
void main() {
vec4 = (, vUv);
= ;
}
Q2:顶点着色器和片元着色器中的精度声明有何区别?
顶点着色器通常使用 highp float;
片元着色器中,移动端建议使用 float;以获得更好性能
纹理坐标建议使用 float;
Q3:如何让着色器支持透明?
. = true;
= vec4(color, alpha); // alpha < 1.0
Q4:着色器中的更新频率有限制吗?
每帧更新均有效,但注意性能
大量建议打包为数组或纹理传递
着色器编程是Three.js进阶的核心技能,掌握了着色器即掌握了WebGL渲染的最后一块拼图。本文涵盖了从概念到实战的完整知识体系,所有代码示例均经过验证可直接运行。建议读者按照学习路径逐步实践,利用等平台进行算法实验,逐步构建自己的着色器知识库。如有疑问,可参考官方文档或社区资源获取最新信息。

