着色器编程
着色器(Shader)是运行在 GPU 上的程序,可实现高度自定义的视觉效果。Three.js 提供了 ShaderMaterial 和 RawShaderMaterial 两种材质用于自定义着色器开发。
系统架构
code
┌─────────────────────────────────────────────────────────────┐
│ 着色器渲染管线 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ JavaScript │ │ 顶点着色器 │ │ 片元着色器 │ │
│ │ 应用层 │───▶│ Vertex │───▶│ Fragment │ │
│ │ │ │ Shader │ │ Shader │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Uniforms │ │ Attributes │ │ Varyings │ │
│ │ 全局变量 │ │ 顶点属性 │ │ 插值变量 │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
数据流向:
JavaScript → Uniforms/Attributes → 顶点着色器 → Varyings → 片元着色器 → 像素输出核心组件说明
| 组件 | 说明 | 更新频率 | 使用场景 |
|---|---|---|---|
| Attributes | 顶点级别数据 | 静态/偶尔更新 | 位置、法线、UV、顶点颜色 |
| Uniforms | 全局共享数据 | 每帧更新 | 时间、矩阵、颜色、纹理 |
| Varyings | 着色器间传递 | 自动插值 | 传递计算结果到片元着色器 |
概述
着色器编程是 Three.js 高级开发的核心技能。顶点着色器(Vertex Shader)处理几何变换,片元着色器(Fragment Shader)处理像素颜色,通过组合两者可以实现各种复杂的视觉效果。
着色器类型
| 类型 | 执行位置 | 主要职责 | 执行频率 |
|---|---|---|---|
| 顶点着色器 | GPU 顶点处理单元 | 坐标变换、顶点动画 | 每顶点一次 |
| 片元着色器 | GPU 片元处理单元 | 颜色计算、纹理采样 | 每像素一次 |
| 几何着色器 | GPU 几何处理单元 | 图元生成(WebGL 2) | 每图元一次 |
GLSL 基础
GLSL(OpenGL Shading Language)是着色器的编程语言。
数据类型
glsl
// ==================== 基本类型 ====================
float f = 1.0; // 浮点数(必须带小数点)
int i = 1; // 整数
bool b = true; // 布尔值
// ==================== 向量类型 ====================
vec2 v2 = vec2(1.0, 1.0); // 二维向量
vec3 v3 = vec3(1.0, 1.0, 1.0); // 三维向量(位置、颜色)
vec4 v4 = vec4(1.0, 1.0, 1.0, 1.0); // 四维向量(齐次坐标、RGBA)
// 特定向量类型
ivec2 iv2 = ivec2(1, 2); // 整数向量
bvec2 bv2 = bvec2(true, false); // 布尔向量
// ==================== 矩阵类型 ====================
mat2 m2 = mat2(1.0, 0.0, 0.0, 1.0); // 2x2 矩阵
mat3 m3 = mat3(1.0); // 3x3 单位矩阵
mat4 m4 = mat4(1.0); // 4x4 单位矩阵
// ==================== 访问向量分量 ====================
float x = v3.x; // 单分量访问
float y = v3.y;
float z = v3.z;
// 多种访问方式(等效)
float r = v4.r; // 颜色语义 (rgba)
float s = v2.s; // 纹理语义 (stpq)
// Swizzling(重组)
vec3 rgb = v4.rgb; // 提取 RGB
vec2 xy = v3.xy; // 提取 XY
vec3 yzx = v3.yzx; // 重排顺序
vec4 rgba = v4.abgr; // 反转顺序
// ==================== 采样器类型 ====================
sampler2D tex; // 2D 纹理采样器
samplerCube cube; // 立方体贴图采样器
sampler3D vol; // 3D 纹理采样器(WebGL 2)内置函数
glsl
// ==================== 数学函数 ====================
abs(x) // 绝对值
sign(x) // 符号函数
floor(x) // 向下取整
ceil(x) // 向上取整
round(x) // 四舍五入
fract(x) // 小数部分
mod(x, y) // 取模
min(x, y) // 最小值
max(x, y) // 最大值
clamp(x, minVal, maxVal) // 限制范围
mix(x, y, a) // 线性插值 (x * (1-a) + y * a)
step(edge, x) // 阶跃函数(x < edge ? 0 : 1)
smoothstep(edge0, edge1, x) // 平滑阶跃
// ==================== 三角函数 ====================
sin(x), cos(x), tan(x) // 三角函数
asin(x), acos(x), atan(y, x) // 反三角函数
radians(degrees) // 角度转弧度
degrees(radians) // 弧度转角度
// ==================== 指数函数 ====================
pow(x, y) // 幂函数
exp(x) // e^x
log(x) // 自然对数
exp2(x) // 2^x
log2(x) // 以2为底的对数
sqrt(x) // 平方根
inversesqrt(x) // 平方根倒数
// ==================== 向量函数 ====================
length(x) // 向量长度
distance(x, y) // 两点距离
dot(x, y) // 点积
cross(x, y) // 叉积(仅 vec3)
normalize(x) // 归一化
faceforward(N, I, Nref) // 正面朝向
reflect(I, N) // 反射向量
refract(I, N, eta) // 折射向量
// ==================== 纹理函数 ====================
texture2D(sampler, coord) // 2D 纹理采样
textureCube(sampler, coord) // 立方体贴图采样
texture2DLod(sampler, coord, lod) // 指定 LOD 采样(WebGL 2)
texture2DProj(sampler, coord) // 投影纹理采样
// ==================== 导数函数(WebGL 2)====================
dFdx(p) // X 方向导数
dFdy(p) // Y 方向导数
fwidth(p) // abs(dFdx(p)) + abs(dFdy(p))精度修饰符
glsl
// 精度声明(通常在着色器顶部)
precision highp float; // 高精度(32位)
precision mediump float; // 中精度(16位)
precision lowp float; // 低精度(8位)
// 单变量精度
highp vec3 position;
mediump vec2 uv;
lowp vec4 color; // 适合颜色
// 精度选择建议
// highp: 位置、深度、复杂计算
// mediump: UV 坐标、法线
// lowp: 颜色、简单布尔值ShaderMaterial 基础
基本结构
javascript
import * as THREE from 'three';
// 创建着色器材质
const material = new THREE.ShaderMaterial({
// ==================== Uniform 变量 ====================
uniforms: {
time: { value: 0 },
color: { value: new THREE.Color(0xff0000) },
texture: { value: null }
},
// ==================== 顶点着色器 ====================
vertexShader: `
// 声明传递给片元着色器的变量
varying vec2 vUv;
varying vec3 vNormal;
varying vec3 vPosition;
void main() {
// 传递 UV 坐标
vUv = uv;
vNormal = normalize(normalMatrix * normal);
// 计算世界坐标位置
vec4 worldPosition = modelMatrix * vec4(position, 1.0);
vPosition = worldPosition.xyz;
// 计算裁剪空间坐标(必需)
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
// ==================== 片元着色器 ====================
fragmentShader: `
// 接收来自顶点着色器的变量
uniform float time;
uniform vec3 color;
varying vec2 vUv;
varying vec3 vNormal;
varying vec3 vPosition;
void main() {
// 计算动态颜色
float intensity = sin(time + vUv.x * 10.0) * 0.5 + 0.5;
// 输出最终颜色(必需)
gl_FragColor = vec4(color * intensity, 1.0);
}
`,
// ==================== 材质选项 ====================
transparent: true, // 启用透明
side: THREE.DoubleSide, // 双面渲染
depthWrite: true, // 写入深度缓冲
blending: THREE.NormalBlending // 混合模式
});
// 创建网格
const geometry = new THREE.PlaneGeometry(2, 2);
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
// 更新 uniform
function animate() {
requestAnimationFrame(animate);
// 更新时间 uniform
material.uniforms.time.value = performance.now() * 0.001;
renderer.render(scene, camera);
}Three.js 内置属性
ShaderMaterial 会自动注入以下属性和 uniform:
顶点着色器内置属性(Attributes)
glsl
// 顶点属性
attribute vec3 position; // 顶点位置(局部坐标)
attribute vec3 normal; // 顶点法线
attribute vec2 uv; // 第一组 UV 坐标
attribute vec2 uv2; // 第二组 UV 坐标
attribute vec3 color; // 顶点颜色
attribute vec4 skinWeight; // 骨骼权重
attribute vec4 skinIndex; // 骨骼索引内置矩阵(Uniforms)
glsl
// 变换矩阵
uniform mat4 modelMatrix; // 模型矩阵(局部→世界)
uniform mat4 viewMatrix; // 视图矩阵(世界→相机)
uniform mat4 projectionMatrix; // 投影矩阵(相机→裁剪)
uniform mat4 modelViewMatrix; // 模型视图矩阵(局部→相机)
uniform mat3 normalMatrix; // 法线矩阵(用于正确变换法线)
uniform mat4 viewMatrixInverse; // 视图矩阵逆矩阵其他内置 Uniform
glsl
// 相机
uniform vec3 cameraPosition; // 相机世界坐标
uniform float cameraNear; // 近裁剪面
uniform float cameraFar; // 远裁剪面
// 时间(需启用)
uniform float time; // 动画时间
// 雾效
uniform vec3 fogColor; // 雾颜色
uniform float fogNear; // 雾近距
uniform float fogFar; // 雾远距
uniform float fogDensity; // 雾密度
// 光照(需启用)
uniform vec3 ambientLightColor; // 环境光颜色
uniform vec3 directionalLightColor[MAX_LIGHTS];
uniform vec3 directionalLightDirection[MAX_LIGHTS];RawShaderMaterial
RawShaderMaterial 不会自动注入任何内置属性,需要完全手动管理:
javascript
const rawMaterial = new THREE.RawShaderMaterial({
uniforms: {
// 必须手动定义所有 uniform
projectionMatrix: { value: camera.projectionMatrix },
modelViewMatrix: { value: new THREE.Matrix4() },
time: { value: 0 }
},
vertexShader: `
// 必须手动声明所有 attribute
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);
}
`,
fragmentShader: `
precision mediump float;
varying vec2 vUv;
uniform float time;
void main() {
gl_FragColor = vec4(vUv, 0.5 + 0.5 * sin(time), 1.0);
}
`
});使用场景对比:
| 特性 | ShaderMaterial | RawShaderMaterial |
|---|---|---|
| 自动注入属性 | ✅ 是 | ❌ 否 |
| 代码简洁性 | ✅ 高 | ⚠️ 低 |
| 完全控制 | ⚠️ 部分 | ✅ 完全 |
| 兼容性 | ✅ 自动处理 | ⚠️ 需手动处理 |
| 适用场景 | 大多数情况 | 需要精细控制时 |
常用着色器效果
渐变效果
javascript
const gradientMaterial = new THREE.ShaderMaterial({
uniforms: {
color1: { value: new THREE.Color(0xff0000) },
color2: { value: new THREE.Color(0x0000ff) },
direction: { value: new THREE.Vector2(0, 1) } // 渐变方向
},
vertexShader: `
varying vec2 vUv;
varying vec3 vPosition;
void main() {
vUv = uv;
vPosition = position;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
uniform vec3 color1;
uniform vec3 color2;
uniform vec2 direction;
varying vec2 vUv;
varying vec3 vPosition;
void main() {
// 基于方向的渐变
float mixFactor = dot(vUv - 0.5, direction) + 0.5;
mixFactor = clamp(mixFactor, 0.0, 1.0);
vec3 color = mix(color1, color2, mixFactor);
gl_FragColor = vec4(color, 1.0);
}
`
});噪声效果
javascript
const noiseMaterial = new THREE.ShaderMaterial({
uniforms: {
time: { value: 0 },
scale: { value: 5.0 },
speed: { value: 1.0 }
},
vertexShader: `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
uniform float time;
uniform float scale;
uniform float speed;
varying vec2 vUv;
// ==================== 随机函数 ====================
float random(vec2 st) {
return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453123);
}
// ==================== 值噪声 ====================
float noise(vec2 st) {
vec2 i = floor(st);
vec2 f = fract(st);
// 四个角的随机值
float a = random(i);
float b = random(i + vec2(1.0, 0.0));
float c = random(i + vec2(0.0, 1.0));
float d = random(i + vec2(1.0, 1.0));
// 平滑插值
vec2 u = f * f * (3.0 - 2.0 * f);
return mix(a, b, u.x) + (c - a) * u.y * (1.0 - u.x) + (d - b) * u.x * u.y;
}
// ==================== 分形布朗运动(FBM)====================
float fbm(vec2 st) {
float value = 0.0;
float amplitude = 0.5;
float frequency = 1.0;
for (int i = 0; i < 6; i++) {
value += amplitude * noise(st * frequency);
frequency *= 2.0;
amplitude *= 0.5;
}
return value;
}
void main() {
vec2 st = vUv * scale;
float t = time * speed;
// 动态噪声
float n = fbm(st + t);
// 添加时间扭曲
n += 0.5 * fbm(st * 2.0 + vec2(t * 0.5, t * 0.3));
gl_FragColor = vec4(vec3(n), 1.0);
}
`
});波浪效果
javascript
const waveMaterial = new THREE.ShaderMaterial({
uniforms: {
time: { value: 0 },
amplitude: { value: 0.5 },
frequency: { value: 2.0 },
speed: { value: 1.0 }
},
vertexShader: `
uniform float time;
uniform float amplitude;
uniform float frequency;
uniform float speed;
varying vec2 vUv;
varying float vElevation;
varying vec3 vNormal;
void main() {
vUv = uv;
vec3 pos = position;
// 多层波浪叠加
float wave1 = sin(pos.x * frequency + time * speed) * amplitude;
float wave2 = sin(pos.y * frequency * 0.8 + time * speed * 1.2) * amplitude * 0.5;
float wave3 = cos((pos.x + pos.y) * frequency * 0.5 + time * speed * 0.7) * amplitude * 0.3;
float elevation = wave1 + wave2 + wave3;
pos.z += elevation;
vElevation = elevation;
// 计算法线(近似)
float dx = cos(pos.x * frequency + time * speed) * amplitude * frequency;
float dy = cos(pos.y * frequency * 0.8 + time * speed * 1.2) * amplitude * frequency * 0.4;
vNormal = normalize(vec3(-dx, -dy, 1.0));
gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
}
`,
fragmentShader: `
varying vec2 vUv;
varying float vElevation;
varying vec3 vNormal;
void main() {
// 基于高度的颜色映射
float intensity = (vElevation + 1.0) * 0.5;
// 海洋颜色
vec3 deepColor = vec3(0.0, 0.1, 0.3);
vec3 shallowColor = vec3(0.0, 0.5, 0.7);
vec3 foamColor = vec3(0.9, 0.95, 1.0);
vec3 color;
if (intensity < 0.4) {
color = mix(deepColor, shallowColor, intensity / 0.4);
} else {
color = mix(shallowColor, foamColor, (intensity - 0.4) / 0.6);
}
// 简单光照
vec3 lightDir = normalize(vec3(1.0, 1.0, 1.0));
float diff = max(dot(vNormal, lightDir), 0.0);
color *= 0.5 + 0.5 * diff;
gl_FragColor = vec4(color, 1.0);
}
`,
side: THREE.DoubleSide
});发光效果(Fresnel)
javascript
const glowMaterial = new THREE.ShaderMaterial({
uniforms: {
time: { value: 0 },
glowColor: { value: new THREE.Color(0x00ffff) },
intensity: { value: 1.0 },
power: { value: 3.0 }
},
vertexShader: `
varying vec3 vNormal;
varying vec3 vViewPosition;
void main() {
vNormal = normalize(normalMatrix * normal);
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
vViewPosition = -mvPosition.xyz;
gl_Position = projectionMatrix * mvPosition;
}
`,
fragmentShader: `
uniform vec3 glowColor;
uniform float intensity;
uniform float time;
uniform float power;
varying vec3 vNormal;
varying vec3 vViewPosition;
void main() {
// 计算视线方向
vec3 viewDir = normalize(vViewPosition);
// Fresnel 效果
float fresnel = pow(1.0 - abs(dot(viewDir, vNormal)), power);
// 脉冲效果
float pulse = sin(time * 2.0) * 0.3 + 0.7;
vec3 color = glowColor * fresnel * intensity * pulse;
gl_FragColor = vec4(color, fresnel * 0.8);
}
`,
transparent: true,
side: THREE.BackSide, // 背面渲染实现外发光
depthWrite: false,
blending: THREE.AdditiveBlending
});全息效果
javascript
const hologramMaterial = new THREE.ShaderMaterial({
uniforms: {
time: { value: 0 },
color: { value: new THREE.Color(0x00ffcc) },
scanlineSpeed: { value: 2.0 }
},
vertexShader: `
varying vec2 vUv;
varying vec3 vNormal;
varying vec3 vPosition;
void main() {
vUv = uv;
vNormal = normalize(normalMatrix * normal);
vPosition = position;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
uniform float time;
uniform vec3 color;
uniform float scanlineSpeed;
varying vec2 vUv;
varying vec3 vNormal;
varying vec3 vPosition;
void main() {
// 扫描线效果
float scanline = sin(vUv.y * 100.0 + time * scanlineSpeed * 10.0) * 0.1;
// Fresnel 边缘发光
vec3 viewDir = normalize(cameraPosition - vPosition);
float fresnel = pow(1.0 - abs(dot(viewDir, vNormal)), 2.0);
// 闪烁效果
float flicker = sin(time * 20.0) * 0.05 + 0.95;
// 组合效果
float alpha = (0.5 + scanline + fresnel * 0.5) * flicker;
gl_FragColor = vec4(color, alpha);
}
`,
transparent: true,
side: THREE.DoubleSide,
depthWrite: false,
blending: THREE.AdditiveBlending
});使用纹理
纹理采样
javascript
const textureMaterial = new THREE.ShaderMaterial({
uniforms: {
map: { value: textureLoader.load('texture.jpg') },
normalMap: { value: textureLoader.load('normal.jpg') },
time: { value: 0 },
distortionStrength: { value: 0.05 }
},
vertexShader: `
varying vec2 vUv;
varying vec3 vNormal;
varying vec3 vPosition;
void main() {
vUv = uv;
vNormal = normalize(normalMatrix * normal);
vPosition = (modelMatrix * vec4(position, 1.0)).xyz;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
uniform sampler2D map;
uniform sampler2D normalMap;
uniform float time;
uniform float distortionStrength;
varying vec2 vUv;
varying vec3 vNormal;
varying vec3 vPosition;
void main() {
// UV 扭曲效果
vec2 uv = vUv;
uv.x += sin(uv.y * 10.0 + time) * distortionStrength;
uv.y += cos(uv.x * 10.0 + time) * distortionStrength;
// 采样基础纹理
vec4 texColor = texture2D(map, uv);
// 法线贴图
vec3 normal = texture2D(normalMap, uv).rgb;
normal = normalize(normal * 2.0 - 1.0);
// 简单光照
vec3 lightDir = normalize(vec3(1.0, 1.0, 1.0));
float diff = max(dot(normal, lightDir), 0.0);
vec3 color = texColor.rgb * (0.3 + 0.7 * diff);
gl_FragColor = vec4(color, texColor.a);
}
`
});多纹理混合
javascript
const blendMaterial = new THREE.ShaderMaterial({
uniforms: {
texture1: { value: textureLoader.load('grass.jpg') },
texture2: { value: textureLoader.load('rock.jpg') },
blendMap: { value: textureLoader.load('blend.png') }
},
vertexShader: `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
uniform sampler2D texture1;
uniform sampler2D texture2;
uniform sampler2D blendMap;
varying vec2 vUv;
void main() {
// 采样混合贴图
float blend = texture2D(blendMap, vUv).r;
// 采样两个纹理
vec4 color1 = texture2D(texture1, vUv);
vec4 color2 = texture2D(texture2, vUv);
// 混合
gl_FragColor = mix(color1, color2, blend);
}
`
});光照着色器
Blinn-Phong 光照模型
javascript
const litMaterial = new THREE.ShaderMaterial({
uniforms: {
lightPosition: { value: new THREE.Vector3(5, 5, 5) },
lightColor: { value: new THREE.Color(0xffffff) },
ambientColor: { value: new THREE.Color(0x404040) },
diffuseColor: { value: new THREE.Color(0x00ff00) },
specularColor: { value: new THREE.Color(0xffffff) },
shininess: { value: 30.0 }
},
vertexShader: `
varying vec3 vNormal;
varying vec3 vPosition;
varying vec2 vUv;
void main() {
vNormal = normalize(normalMatrix * normal);
vPosition = (modelViewMatrix * vec4(position, 1.0)).xyz;
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
uniform vec3 lightPosition;
uniform vec3 lightColor;
uniform vec3 ambientColor;
uniform vec3 diffuseColor;
uniform vec3 specularColor;
uniform float shininess;
varying vec3 vNormal;
varying vec3 vPosition;
varying vec2 vUv;
void main() {
// 归一化向量
vec3 normal = normalize(vNormal);
vec3 lightDir = normalize(lightPosition - vPosition);
vec3 viewDir = normalize(-vPosition);
vec3 halfDir = normalize(lightDir + viewDir); // Blinn-Phong
// 环境光
vec3 ambient = ambientColor * diffuseColor;
// 漫反射
float diff = max(dot(normal, lightDir), 0.0);
vec3 diffuse = diff * lightColor * diffuseColor;
// 镜面反射(Blinn-Phong)
float spec = pow(max(dot(normal, halfDir), 0.0), shininess);
vec3 specular = spec * lightColor * specularColor;
// 最终颜色
vec3 color = ambient + diffuse + specular;
gl_FragColor = vec4(color, 1.0);
}
`
});PBR 光照模型
javascript
const pbrMaterial = new THREE.ShaderMaterial({
uniforms: {
albedo: { value: new THREE.Color(0.8, 0.2, 0.2) },
metallic: { value: 0.5 },
roughness: { value: 0.5 },
lightPosition: { value: new THREE.Vector3(5, 5, 5) },
lightColor: { value: new THREE.Color(1, 1, 1) },
cameraPosition: { value: new THREE.Vector3() }
},
vertexShader: `
varying vec3 vNormal;
varying vec3 vWorldPosition;
void main() {
vNormal = normalize(normalMatrix * normal);
vWorldPosition = (modelMatrix * vec4(position, 1.0)).xyz;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
uniform vec3 albedo;
uniform float metallic;
uniform float roughness;
uniform vec3 lightPosition;
uniform vec3 lightColor;
uniform vec3 cameraPosition;
varying vec3 vNormal;
varying vec3 vWorldPosition;
const float PI = 3.14159265359;
// 正态分布函数(GGX/Trowbridge-Reitz)
float distributionGGX(vec3 N, vec3 H, float roughness) {
float a = roughness * roughness;
float a2 = a * a;
float NdotH = max(dot(N, H), 0.0);
float NdotH2 = NdotH * NdotH;
float nom = a2;
float denom = (NdotH2 * (a2 - 1.0) + 1.0);
denom = PI * denom * denom;
return nom / denom;
}
// 几何遮蔽函数
float geometrySchlickGGX(float NdotV, float roughness) {
float r = (roughness + 1.0);
float k = (r * r) / 8.0;
float nom = NdotV;
float denom = NdotV * (1.0 - k) + k;
return nom / denom;
}
float geometrySmith(vec3 N, vec3 V, vec3 L, float roughness) {
float NdotV = max(dot(N, V), 0.0);
float NdotL = max(dot(N, L), 0.0);
float ggx2 = geometrySchlickGGX(NdotV, roughness);
float ggx1 = geometrySchlickGGX(NdotL, roughness);
return ggx1 * ggx2;
}
// Fresnel 方程
vec3 fresnelSchlick(float cosTheta, vec3 F0) {
return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
}
void main() {
vec3 N = normalize(vNormal);
vec3 V = normalize(cameraPosition - vWorldPosition);
vec3 L = normalize(lightPosition - vWorldPosition);
vec3 H = normalize(V + L);
float distance = length(lightPosition - vWorldPosition);
float attenuation = 1.0 / (distance * distance);
vec3 radiance = lightColor * attenuation;
// 基础反射率
vec3 F0 = vec3(0.04);
F0 = mix(F0, albedo, metallic);
// Cook-Torrance BRDF
float NDF = distributionGGX(N, H, roughness);
float G = geometrySmith(N, V, L, roughness);
vec3 F = fresnelSchlick(max(dot(H, V), 0.0), F0);
vec3 numerator = NDF * G * F;
float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0) + 0.0001;
vec3 specular = numerator / denominator;
// 能量守恒
vec3 kS = F;
vec3 kD = vec3(1.0) - kS;
kD *= 1.0 - metallic;
// 最终颜色
float NdotL = max(dot(N, L), 0.0);
vec3 Lo = (kD * albedo / PI + specular) * radiance * NdotL;
vec3 ambient = vec3(0.03) * albedo;
vec3 color = ambient + Lo;
// HDR tonemapping
color = color / (color + vec3(1.0));
// Gamma 校正
color = pow(color, vec3(1.0 / 2.2));
gl_FragColor = vec4(color, 1.0);
}
`
});高级技术
顶点位移
javascript
const displacementMaterial = new THREE.ShaderMaterial({
uniforms: {
heightMap: { value: textureLoader.load('height.jpg') },
displacementScale: { value: 2.0 },
time: { value: 0 }
},
vertexShader: `
uniform sampler2D heightMap;
uniform float displacementScale;
uniform float time;
varying vec2 vUv;
varying float vHeight;
varying vec3 vNormal;
void main() {
vUv = uv;
// 从高度图读取位移值
float height = texture2D(heightMap, uv).r;
vHeight = height;
// 应用位移
vec3 pos = position;
pos += normal * height * displacementScale;
// 动态波浪
pos.y += sin(pos.x * 2.0 + time) * 0.1;
pos.y += cos(pos.z * 2.0 + time) * 0.1;
// 计算新的法线(近似)
float delta = 0.01;
float hL = texture2D(heightMap, uv - vec2(delta, 0.0)).r;
float hR = texture2D(heightMap, uv + vec2(delta, 0.0)).r;
float hD = texture2D(heightMap, uv - vec2(0.0, delta)).r;
float hU = texture2D(heightMap, uv + vec2(0.0, delta)).r;
vNormal = normalize(vec3(hL - hR, 2.0 * delta, hD - hU));
gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
}
`,
fragmentShader: `
varying vec2 vUv;
varying float vHeight;
varying vec3 vNormal;
void main() {
// 基于高度的颜色映射
vec3 lowColor = vec3(0.1, 0.5, 0.1); // 绿色(低地)
vec3 midColor = vec3(0.5, 0.4, 0.2); // 棕色(山地)
vec3 highColor = vec3(1.0, 1.0, 1.0); // 白色(雪顶)
vec3 color;
if (vHeight < 0.5) {
color = mix(lowColor, midColor, vHeight * 2.0);
} else {
color = mix(midColor, highColor, (vHeight - 0.5) * 2.0);
}
// 光照
vec3 lightDir = normalize(vec3(1.0, 1.0, 1.0));
float diff = max(dot(vNormal, lightDir), 0.0);
color *= 0.5 + 0.5 * diff;
gl_FragColor = vec4(color, 1.0);
}
`
});菲涅尔效果
javascript
const fresnelMaterial = new THREE.ShaderMaterial({
uniforms: {
fresnelColor: { value: new THREE.Color(0x00ffff) },
bias: { value: 0.1 },
scale: { value: 1.0 },
power: { value: 2.0 }
},
vertexShader: `
varying vec3 vNormal;
varying vec3 vViewPosition;
void main() {
vNormal = normalize(normalMatrix * normal);
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
vViewPosition = -mvPosition.xyz;
gl_Position = projectionMatrix * mvPosition;
}
`,
fragmentShader: `
uniform vec3 fresnelColor;
uniform float bias;
uniform float scale;
uniform float power;
varying vec3 vNormal;
varying vec3 vViewPosition;
void main() {
vec3 viewDir = normalize(vViewPosition);
float fresnel = bias + scale * pow(1.0 - abs(dot(viewDir, vNormal)), power);
gl_FragColor = vec4(fresnelColor * fresnel, 1.0);
}
`
});API 参考
ShaderMaterial 属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
uniforms | Object | {} | Uniform 变量对象 |
vertexShader | String | '' | 顶点着色器代码 |
fragmentShader | String | '' | 片元着色器代码 |
transparent | Boolean | false | 是否透明 |
side | Integer | FrontSide | 渲染面(FrontSide/BackSide/DoubleSide) |
depthWrite | Boolean | true | 是否写入深度缓冲 |
depthTest | Boolean | true | 是否进行深度测试 |
blending | Integer | NormalBlending | 混合模式 |
wireframe | Boolean | false | 是否线框模式 |
lights | Boolean | false | 是否包含光照计算 |
fog | Boolean | true | 是否受雾影响 |
Uniform 类型映射
| JavaScript 类型 | GLSL 类型 | 说明 |
|---|---|---|
Number | float | 浮点数 |
THREE.Vector2 | vec2 | 二维向量 |
THREE.Vector3 | vec3 | 三维向量 |
THREE.Vector4 | vec4 | 四维向量 |
THREE.Color | vec3 | 颜色(RGB) |
THREE.Matrix3 | mat3 | 3x3 矩阵 |
THREE.Matrix4 | mat4 | 4x4 矩阵 |
THREE.Texture | sampler2D | 2D 纹理 |
THREE.CubeTexture | samplerCube | 立方体贴图 |
Array | array | 数组类型 |
常用方法
javascript
// 设置 uniform 值
material.uniforms.time.value = 1.0;
// 动态更新着色器
material.vertexShader = newVertexShader;
material.fragmentShader = newFragmentShader;
material.needsUpdate = true; // 触发重新编译
// 克隆材质
const clonedMaterial = material.clone();
// 释放资源
material.dispose();配置参数详解
透明度配置
javascript
const material = new THREE.ShaderMaterial({
transparent: true,
// Alpha 测试(透明度阈值)
alphaTest: 0.5, // Alpha < 0.5 的像素被丢弃
// 深度写入
depthWrite: false, // 透明物体通常禁用深度写入
// 混合模式
blending: THREE.AdditiveBlending, // 叠加混合
// 其他混合模式:
// THREE.NormalBlending - 正常混合
// THREE.SubtractiveBlending - 减法混合
// THREE.MultiplyBlending - 乘法混合
// THREE.CustomBlending - 自定义混合
});渲染面配置
javascript
// 正面渲染(默认)
material.side = THREE.FrontSide;
// 背面渲染(用于内表面)
material.side = THREE.BackSide;
// 双面渲染
material.side = THREE.DoubleSide;深度测试配置
javascript
// 完全禁用深度测试(始终渲染)
material.depthTest = false;
// 禁用深度写入(用于透明物体)
material.depthWrite = false;调试与错误处理
错误检查
javascript
// 方法一:使用 renderer 的错误回调
const material = new THREE.ShaderMaterial({
vertexShader: `...`,
fragmentShader: `...`,
uniforms: { ... },
onBeforeCompile: (shader) => {
console.log('着色器编译前:', shader);
}
});
// 方法二:手动检查编译状态
function checkShaderErrors(material, renderer) {
const gl = renderer.getContext();
const program = renderer.properties.get(material).currentProgram;
if (program) {
const vertexShader = program.vertexShader;
const fragmentShader = program.fragmentShader;
// 检查顶点着色器
if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
const error = gl.getShaderInfoLog(vertexShader);
console.error('顶点着色器编译错误:', error);
console.error('着色器代码:', material.vertexShader);
}
// 检查片元着色器
if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
const error = gl.getShaderInfoLog(fragmentShader);
console.error('片元着色器编译错误:', error);
console.error('着色器代码:', material.fragmentShader);
}
// 检查链接状态
if (!gl.getProgramParameter(program.program, gl.LINK_STATUS)) {
const error = gl.getProgramInfoLog(program.program);
console.error('程序链接错误:', error);
}
}
}
// 首次渲染后检查
renderer.render(scene, camera);
checkShaderErrors(material, renderer);性能监控
javascript
// 使用 WebGL 扩展进行性能监控
const gl = renderer.getContext();
// WebGL 1 扩展
const timerExt = gl.getExtension('EXT_disjoint_timer_query');
// WebGL 2 内置支持
if (gl instanceof WebGL2RenderingContext) {
console.log('支持 GPU 时间查询');
}
// 使用 Stats.js 进行帧率监控
import Stats from 'stats.js';
const stats = new Stats();
document.body.appendChild(stats.dom);
function animate() {
stats.begin();
renderer.render(scene, camera);
stats.end();
requestAnimationFrame(animate);
}调试技巧
javascript
// 1. 使用纯色输出验证着色器是否工作
fragmentShader: `
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // 纯红色
}
`
// 2. 输出变量值进行调试
fragmentShader: `
varying vec2 vUv;
varying vec3 vNormal;
void main() {
// 输出 UV 坐标
gl_FragColor = vec4(vUv, 0.0, 1.0);
// 或输出法线
// gl_FragColor = vec4(vNormal * 0.5 + 0.5, 1.0);
}
`
// 3. 使用 console.log 在 JavaScript 端验证数据
console.log('Uniform 值:', material.uniforms.time.value);
console.log('几何体属性:', geometry.attributes.position.array);性能优化
优化策略
- 减少条件分支
glsl
// 不推荐:使用 if-else
if (condition) {
color = color1;
} else {
color = color2;
}
// 推荐:使用 mix 函数
color = mix(color2, color1, float(condition));- 在顶点着色器计算
glsl
// 不推荐:在片元着色器计算
// (每个像素执行一次)
varying vec3 vNormal;
void main() {
vec3 normal = normalize(vNormal); // 每像素归一化
}
// 推荐:在顶点着色器计算
// (每个顶点执行一次)
varying vec3 vNormal;
void main() {
vNormal = normalize(normalMatrix * normal); // 每顶点归一化
}- 使用内置函数
glsl
// 不推荐:手动实现
float length = sqrt(x * x + y * y + z * z);
// 推荐:使用内置函数
float length = length(vec3(x, y, z));- 避免重复计算
glsl
// 不推荐:重复计算
float value = sin(time) * 0.5;
float result1 = value + 0.5;
float result2 = value * 2.0;
// 推荐:复用计算结果
float sineValue = sin(time);
float value = sineValue * 0.5;
float result1 = value + 0.5;
float result2 = value * 2.0;- 精度优化
glsl
// 在着色器顶部设置默认精度
precision highp float; // 需要高精度
precision mediump int; // 整数可用中精度
// 对特定变量使用低精度
lowp vec4 color; // 颜色值不需要高精度性能分析工具
javascript
// 1. 使用 Chrome DevTools 的 WebGL Inspector
// 2. 使用 Spector.js
import * as SPECTOR from 'spectorjs';
const spector = new SPECTOR.Spector();
spector.spyCanvas(canvas);
spector.onCapture.add((capture) => {
console.log('捕获的帧:', capture);
});
// 3. 手动测量渲染时间
console.time('render');
renderer.render(scene, camera);
console.timeEnd('render');最佳实践
代码组织
javascript
// 推荐:将着色器代码分离到常量
const VERTEX_SHADER = `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`;
const FRAGMENT_SHADER = `
uniform float time;
varying vec2 vUv;
void main() {
gl_FragColor = vec4(vUv, sin(time) * 0.5 + 0.5, 1.0);
}
`;
const material = new THREE.ShaderMaterial({
vertexShader: VERTEX_SHADER,
fragmentShader: FRAGMENT_SHADER,
uniforms: {
time: { value: 0 }
}
});Uniform 管理
javascript
// 推荐:使用函数更新 uniform
function updateUniforms(time, deltaTime) {
material.uniforms.time.value = time;
material.uniforms.deltaTime.value = deltaTime;
}
// 推荐:使用对象组织 uniform
const uniforms = {
time: { value: 0 },
color: { value: new THREE.Color(0xff0000) },
texture: { value: null }
};
const material = new THREE.ShaderMaterial({ uniforms });
// 更新
uniforms.time.value = elapsedTime;
uniforms.texture.value = loadedTexture;复用着色器代码
javascript
// 使用 chunk 复用代码
const commonChunk = `
float random(vec2 st) {
return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453123);
}
float noise(vec2 st) {
vec2 i = floor(st);
vec2 f = fract(st);
float a = random(i);
float b = random(i + vec2(1.0, 0.0));
float c = random(i + vec2(0.0, 1.0));
float d = random(i + vec2(1.0, 1.0));
vec2 u = f * f * (3.0 - 2.0 * f);
return mix(a, b, u.x) + (c - a) * u.y * (1.0 - u.x) + (d - b) * u.x * u.y;
}
`;
const fragmentShader = `
${commonChunk}
void main() {
float n = noise(vUv * 10.0);
gl_FragColor = vec4(vec3(n), 1.0);
}
`;常见问题
Q1: 着色器编译错误如何排查?
A: 按以下步骤排查:
javascript
// 1. 检查语法错误
// 确保每个语句以分号结尾
// 确保所有变量都已声明
// 2. 检查变量类型匹配
// float 不能直接赋值 int
float f = 1.0; // 正确
float f = 1; // 错误
// 3. 检查内置变量名冲突
// 避免使用 color、normal 等作为 uniform 名
// 4. 使用浏览器控制台查看错误
// WebGL 错误会输出到控制台Q2: 着色器显示全黑或全白?
A: 可能原因及解决方案:
javascript
// 1. 未正确计算 gl_Position
void main() {
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
// 2. 输出颜色超出范围
// 颜色值应在 [0, 1] 范围内
gl_FragColor = vec4(color, 1.0); // color 应为 vec3,值在 0-1
// 3. 法线未归一化
vNormal = normalize(normalMatrix * normal);
// 4. 深度问题
// 检查相机近远裁剪面设置
camera.near = 0.1;
camera.far = 1000;Q3: 如何在着色器中使用纹理?
A:
javascript
// 1. 声明 uniform 采样器
uniform sampler2D map;
// 2. 使用 texture2D 采样
vec4 color = texture2D(map, vUv);
// 3. 确保 UV 坐标正确传递
varying vec2 vUv; // 片元着色器中声明
vUv = uv; // 顶点着色器中传递Q4: 透明度渲染不正确?
A:
javascript
// 1. 启用透明
transparent: true
// 2. 设置正确的混合模式
blending: THREE.NormalBlending
// 3. 禁用深度写入(对于透明物体)
depthWrite: false
// 4. 在片元着色器中正确设置 alpha
gl_FragColor = vec4(color, alpha); // alpha 应在 [0, 1] 范围Q5: 性能太差怎么办?
A: 参考优化策略:
javascript
// 1. 减少片元着色器复杂度
// 将计算移到顶点着色器
// 2. 使用 LOD(细节层次)
const lod = new THREE.LOD();
lod.addLevel(highDetailMesh, 0); // 近距离
lod.addLevel(mediumDetailMesh, 20); // 中距离
lod.addLevel(lowDetailMesh, 50); // 远距离
// 3. 降低渲染分辨率
renderer.setPixelRatio(0.5);
// 4. 减少后处理通道数量
// 5. 使用 instancing 减少绘制调用
const instancedMesh = new THREE.InstancedMesh(geometry, material, count);Q6: 如何实现自定义光照?
A: 参考光照着色器章节,或使用 Three.js 的 onBeforeCompile:
javascript
const material = new THREE.MeshStandardMaterial({
color: 0xffffff,
onBeforeCompile: (shader) => {
// 修改着色器代码
shader.vertexShader = shader.vertexShader.replace(
'#include <common>',
`
#include <common>
varying vec3 vWorldPosition;
`
);
shader.fragmentShader = shader.fragmentShader.replace(
'#include <dithering_fragment>',
`
#include <dithering_fragment>
// 自定义后处理
gl_FragColor.rgb *= 1.2;
`
);
}
});Q7: RawShaderMaterial 和 ShaderMaterial 有什么区别?
A:
| 特性 | ShaderMaterial | RawShaderMaterial |
|---|---|---|
| 自动注入 attributes | ✅ | ❌ |
| 自动注入 uniforms | ✅ | ❌ |
| 自动添加精度声明 | ✅ | ❌ |
| 适用场景 | 快速开发 | 精细控制 |
javascript
// ShaderMaterial - 自动注入
// position, normal, uv, projectionMatrix 等自动可用
// RawShaderMaterial - 需要手动声明
// 必须手动声明所有变量和精度Q8: 如何调试着色器?
A:
javascript
// 1. 输出中间值
fragmentShader: `
varying vec2 vUv;
void main() {
// 输出 UV 坐标可视化
gl_FragColor = vec4(vUv, 0.0, 1.0);
}
`
// 2. 分步验证
// 先确保基础颜色正确,再逐步添加复杂计算
// 3. 使用 ShaderToy 测试
// https://www.shadertoy.com/
// 4. 使用浏览器开发工具
// 检查 WebGL 错误信息