"在像素与三角面之间,有一块布,名曰 Fragment;
在曲面起伏之中,有一道咒语,名曰 Vertex;
若你掌握了它们的语法,你就能让宇宙颤抖。"
------《图形术·着色卷》
🎨 第一章:开场------什么是动态材质?
在 Three.js 的魔法世界中,材质(Material) 是物体的灵魂外衣。
而我们说的"动态材质",指的是:
- 可随时间、鼠标、交互改变的外观
- 利用 GLSL(OpenGL Shading Language)在 GPU 上实时计算
- 创造出 流光、波纹、描边、扭曲等超自然视觉
说白了就是让你的立方体会"呼吸",让地面泛起水波,让鼠标召唤星辉。
🔮 第二章:召唤 ShaderMaterial
我们使用 THREE.ShaderMaterial
这个法术卷轴,来定义你自己的顶点与像素着色器。
php
const material = new THREE.ShaderMaterial({
vertexShader: `...`, // 顶点着色器
fragmentShader: `...`, // 片元着色器
uniforms: { // 可供 JS 控制的变量
uTime: { value: 0 },
uMouse: { value: new THREE.Vector2() }
}
});
这就像炼金术的配方------你可以往里面加时间(time)、鼠标(mouse)、贴图(texture)、甚至灵魂(noise)。
💦 第三章:水波术------让地面随鼠标泛起涟漪
"在风的指引下,湖面生出层层涟漪,那是鼠标在引导世界的变化。"
✨ 顶点着色器(vertexShader)
ini
uniform float uTime;
uniform vec2 uMouse;
varying vec2 vUv;
void main() {
vUv = uv;
vec3 pos = position;
float dist = distance(uv, uMouse);
float ripple = sin(20.0 * dist - uTime * 4.0) * 0.1 / (dist + 0.1);
pos.z += ripple;
gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
}
uv
:二维纹理坐标uMouse
:鼠标坐标(范围 0~1)distance
:计算每个点离鼠标有多远ripple
:水波函数,带着时间流动的 sin 函数
✨ 片元着色器(fragmentShader)
ini
varying vec2 vUv;
void main() {
vec3 color = vec3(vUv, 0.5);
gl_FragColor = vec4(color, 1.0);
}
- 渐变背景,先别让画面太复杂。
🖱️ 鼠标控制传入
ini
const mouse = new THREE.Vector2();
window.addEventListener('mousemove', (e) => {
mouse.x = e.clientX / window.innerWidth;
mouse.y = 1.0 - e.clientY / window.innerHeight; // Y轴倒置
material.uniforms.uMouse.value.copy(mouse);
});
"你的指尖,点燃了水波的灵魂。"
⏱️ 时间更新
ini
function animate(time) {
material.uniforms.uTime.value = time * 0.001;
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
🌈 第四章:流光术与描边术
✨ Fragment Shader 魔改:炫光边缘
scss
varying vec2 vUv;
uniform float uTime;
void main() {
float glow = abs(sin(uTime + vUv.x * 10.0)) * 0.8;
float edge = smoothstep(0.48, 0.5, abs(vUv.x - 0.5));
vec3 color = mix(vec3(0.1, 0.1, 0.1), vec3(0.0, 1.0, 1.0), glow * edge);
gl_FragColor = vec4(color, 1.0);
}
glow
:闪闪发亮的流光edge
:距离中心的边缘检测mix
:混合背景和高光
🕳️ 第五章:顶点拉扯术------交互式形变
"当你靠近它,它在逃离你;当你远离它,它在追随你。" ------ 变形元老会·鼠标流派
scss
float deform = exp(-10.0 * dist) * sin(uTime * 3.0) * 0.2;
pos.z += deform;
让每个顶点按距离鼠标的远近微微鼓起,结合时间摆动,如同肌肉收缩。
💥 第六章:完整 JS 初始化代码
ini
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 100);
camera.position.z = 2;
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const geometry = new THREE.PlaneGeometry(2, 2, 128, 128);
const uniforms = {
uTime: { value: 0 },
uMouse: { value: new THREE.Vector2() }
};
const material = new THREE.ShaderMaterial({
uniforms,
vertexShader: `...`, // 👈 填入前面写好的顶点着色器
fragmentShader: `...`, // 👈 填入片元着色器
side: THREE.DoubleSide
});
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
window.addEventListener('mousemove', (e) => {
uniforms.uMouse.value.set(
e.clientX / window.innerWidth,
1.0 - e.clientY / window.innerHeight
);
});
function animate(time) {
uniforms.uTime.value = time * 0.001;
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
animate();
🔚 第七章:术法背后的秘密
sin + distance
:构建自然的震动波exp(-d)
:制造渐隐衰减(如鼓面振动)mix()
:平滑过渡视觉uniform
:将 JS 世界与 GPU 着色世界连接起来
📚 延伸术卷推荐
- 流动文字/边框光晕 :使用
gl_FragCoord.xy
+ 时间实现屏幕空间动画 - 反射高光动画:传入法线贴图与视角方向实现动态高光
- 步进条纹风格 :用
floor(sin(...))
实现动态描边与扁平风格
🧙♂️ 尾声:愿你成为像素之主
"在每一帧 GPU 的低语中,隐藏着魔法的节奏。
拿起你的
ShaderMaterial
,你不是在编码,你在咏唱咒语。"------ 着色者之歌·序章