我来详细介绍一下 Three.js 中的着色器材质(ShaderMaterial)。
着色器材质允许你使用自定义的 GLSL 代码来编写顶点和片元着色器,实现高度自定义的渲染效果。
javascript
// 创建基础的着色器材质
const material = new THREE.ShaderMaterial({
uniforms: {
time: { value: 0 }
},
vertexShader: `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
varying vec2 vUv;
uniform float time;
void main() {
gl_FragColor = vec4(vUv, sin(time), 1.0);
}
`
});
从 JavaScript 传递到着色器的变量。
javascript
uniforms: {
time: { value: 0 },
texture: { value: textureLoader.load('image.jpg') },
color: { value: new THREE.Color(0xff0000) },
resolution: { value: new THREE.Vector2(window.innerWidth, window.innerHeight) }
}
每个顶点特有的数据。
javascript
// 在顶点着色器中
attribute float size;
attribute vec3 customColor;
// 在 JavaScript 中设置
geometry.setAttribute('size', new THREE.Float32BufferAttribute(sizes, 1));
从顶点着色器传递到片元着色器的插值变量。
glsl
// 顶点着色器
varying vec2 vUv;
varying vec3 vNormal;
void main() {
vUv = uv;
vNormal = normal;
}
// 片元着色器
varying vec2 vUv;
varying vec3 vNormal;
javascript
// 自动传入的内置 uniforms
uniforms: {
modelMatrix: { value: mesh.matrixWorld },
viewMatrix: { value: camera.matrixWorldInverse },
projectionMatrix: { value: camera.projectionMatrix },
modelViewMatrix: { value: mesh.modelViewMatrix },
normalMatrix: { value: mesh.normalMatrix },
cameraPosition: { value: camera.position }
}
javascript
const vertexShader = `
uniform float time;
varying vec2 vUv;
void main() {
vUv = uv;
// 创建波动效果
float wave = sin(position.x * 5.0 + time) * 0.1;
vec3 pos = position;
pos.z = wave;
gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
}
`;
const fragmentShader = `
uniform float time;
uniform vec3 color;
varying vec2 vUv;
void main() {
// 创建动态颜色
vec3 finalColor = color * (0.5 + 0.5 * sin(time + vUv.x * 10.0));
// 添加一些图案
float pattern = sin(vUv.x * 20.0 + time) * sin(vUv.y * 20.0);
gl_FragColor = vec4(finalColor + pattern * 0.2, 1.0);
}
`;
const material = new THREE.ShaderMaterial({
uniforms: {
time: { value: 0 },
color: { value: new THREE.Color(0x00ffff) }
},
vertexShader: vertexShader,
fragmentShader: fragmentShader,
side: THREE.DoubleSide
});
// 动画循环中更新 uniform
function animate() {
material.uniforms.time.value += 0.01;
}
javascript
// 噪声函数(通常在片元着色器中)
const noiseShader = `
// 简单的伪随机函数
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;
}
`;
javascript
const shader = THREE.ShaderLib.standard;
const material = new THREE.ShaderMaterial({
uniforms: THREE.UniformsUtils.clone(shader.uniforms),
vertexShader: shader.vertexShader,
fragmentShader: shader.fragmentShader
});
javascript
const sharedUniforms = {
time: { value: 0 },
resolution: { value: new THREE.Vector2() }
};
const material1 = new THREE.ShaderMaterial({
uniforms: THREE.UniformsUtils.merge([sharedUniforms, { color: { value: new THREE.Color(0xff0000) } }]),
// ...
});
const material2 = new THREE.ShaderMaterial({
uniforms: THREE.UniformsUtils.merge([sharedUniforms, { color: { value: new THREE.Color(0x00ff00) } }]),
// ...
});
javascript
// 显示编译错误
material.onCompile = (shader) => {
console.log('Shader compiled:', shader);
};
// 检查编译状态
console.log(material.program);
glsl
// 使用颜色可视化调试
gl_FragColor = vec4(vUv, 0.0, 1.0); // 检查UV
gl_FragColor = vec4(vNormal * 0.5 + 0.5, 1.0); // 检查法线
javascript
// 引入着色器库
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';
import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js';
import { GlitchPass } from 'three/examples/jsm/postprocessing/GlitchPass.js';
// 常用库:
// - three/examples/jsm/shaders/* - Three.js内置着色器
// - https://github.com/mrdoob/three.js/tree/dev/examples/jsm/shaders
// - https://thebookofshaders.com/ - 学习GLSL
最小化 uniform 更新:只在必要时更新 uniforms
使用 defines:编译时常量使用 defines 而不是 uniforms
避免分支:尽量在着色器中避免 if 语句
纹理优化:使用适当大小的纹理,考虑使用纹理图集
精度选择:根据需求选择 highp/mediump/lowp
javascript
const material = new THREE.ShaderMaterial({
defines: {
USE_TEXTURE: true,
MAX_STEPS: 100
},
precision: 'mediump', // 或 'highp'、'lowp'
// ...
});
着色器材质是 Three.js 中最强大的功能之一,虽然学习曲线较陡,但能实现几乎所有你能想象的视觉效果。建议从简单的示例开始,逐步学习 GLSL 语法和 GPU 编程概念。