概述
本文将详细介绍如何使用 Three.js 中的着色器来加工和定制材质效果。我们将学习如何通过修改材质的着色器代码,实现动态变形、纹理处理和复杂视觉效果,从而创建出独特且富有表现力的三维场景。
准备工作
首先,我们需要引入必要的 Three.js 库和相关工具:
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import gsap from "gsap";
import * as dat from "dat.gui";
import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
场景初始化
首先,我们需要创建一个基本的 Three.js 场景:
// 初始化场景
const scene = new THREE.Scene();
// 创建透视相机
const camera = new THREE.PerspectiveCamera(
75,
window.innerHeight / window.innerHeight,
1,
50
);
// 设置相机位置
camera.position.set(0, 0, 10);
scene.add(camera);
// 加入辅助轴,帮助我们查看3维坐标轴
const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);
环境纹理设置
为了创建更真实的光照效果,我们添加环境纹理:
// 加载纹理
const textureLoader = new THREE.TextureLoader();
// 添加环境纹理
const cubeTextureLoader = new THREE.CubeTextureLoader();
const envMapTexture = cubeTextureLoader.load([
"textures/environmentMaps/0/px.jpg",
"textures/environmentMaps/0/nx.jpg",
"textures/environmentMaps/0/py.jpg",
"textures/environmentMaps/0/ny.jpg",
"textures/environmentMaps/0/pz.jpg",
"textures/environmentMaps/0/nz.jpg",
]);
// 设置方向光
const directionLight = new THREE.DirectionalLight("#ffffff", 1);
directionLight.castShadow = true;
directionLight.position.set(0, 0, 200);
scene.add(directionLight);
// 应用环境纹理
scene.environment = envMapTexture;
scene.background = envMapTexture;
模型加载与材质处理
加载模型并应用基本材质:
// 加载模型纹理
const modelTexture = textureLoader.load("./models/LeePerrySmith/color.jpg");
// 加载模型的法向纹理
const normalTexture = textureLoader.load("./models/LeePerrySmith/normal.jpg");
const material = new THREE.MeshStandardMaterial({
map: modelTexture,
normalMap: normalTexture,
});
着色器修改的关键技术
方法一:使用 onBeforeCompile 修改现有材质
这是最常用的方法,可以在 Three.js 自动生成的着色器代码基础上进行修改:
const customUniforms = {
uTime: {
value: 0,
},
};
material.onBeforeCompile = (shader) => {
console.log(shader.vertexShader);
console.log(shader.fragmentShader);
// 传递时间变量到着色器
shader.uniforms.uTime = customUniforms.uTime;
// 在顶点着色器的 common 部分插入自定义函数和变量
shader.vertexShader = shader.vertexShader.replace(
"#include <common>",
`
#include <common>
mat2 rotate2d(float _angle){
return mat2(cos(_angle),-sin(_angle),
sin(_angle),cos(_angle));
}
uniform float uTime;
`
);
// 在顶点变换前修改法线
shader.vertexShader = shader.vertexShader.replace(
"#include <beginnormal_vertex>",
`
#include <beginnormal_vertex>
float angle = sin(position.y+uTime) *0.5;
mat2 rotateMatrix = rotate2d(angle);
objectNormal.xz = rotateMatrix * objectNormal.xz;
`
);
// 在顶点变换前修改顶点位置
shader.vertexShader = shader.vertexShader.replace(
"#include <begin_vertex>",
`
#include <begin_vertex>
// float angle = transformed.y*0.5;
// mat2 rotateMatrix = rotate2d(angle);
transformed.xz = rotateMatrix * transformed.xz;
`
);
};
方法二:为深度材质添加相同效果
对于阴影等特殊情况,我们还需要为深度材质添加相同的效果:
const depthMaterial = new THREE.MeshDepthMaterial({
depthPacking: THREE.RGBADepthPacking,
});
depthMaterial.onBeforeCompile = (shader) => {
shader.uniforms.uTime = customUniforms.uTime;
shader.vertexShader = shader.vertexShader.replace(
"#include <common>",
`
#include <common>
mat2 rotate2d(float _angle){
return mat2(cos(_angle),-sin(_angle),
sin(_angle),cos(_angle));
}
uniform float uTime;
`
);
shader.vertexShader = shader.vertexShader.replace(
"#include <begin_vertex>",
`
#include <begin_vertex>
float angle = sin(position.y+uTime) *0.5;
mat2 rotateMatrix = rotate2d(angle);
transformed.xz = rotateMatrix * transformed.xz;
`
);
console.log("depthMaterial", shader.vertexShader);
};
模型加载与材质应用
加载 3D 模型并应用自定义材质:
// 模型加载
const gltfLoader = new GLTFLoader();
gltfLoader.load("./models/LeePerrySmith/LeePerrySmith.glb", (gltf) => {
const mesh = gltf.scene.children[0];
console.log(mesh);
mesh.material = material;
mesh.castShadow = true;
// 设定自定义的深度材质
mesh.customDepthMaterial = depthMaterial;
scene.add(mesh);
});
// 添加地面
const plane = new THREE.Mesh(
new THREE.PlaneBufferGeometry(20, 20),
new THREE.MeshStandardMaterial()
);
plane.position.set(0, 0, -6);
plane.receiveShadow = true;
scene.add(plane);
渲染器和控制器设置
设置渲染器和控制器:
// 初始化渲染器
const renderer = new THREE.WebGLRenderer();
// 设置渲染尺寸大小
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
// 监听屏幕大小改变的变化,设置渲染的尺寸
window.addEventListener("resize", () => {
// 更新摄像头
camera.aspect = window.innerWidth / window.innerHeight;
// 更新摄像机的投影矩阵
camera.updateProjectionMatrix();
// 更新渲染器
renderer.setSize(window.innerWidth, window.innerHeight);
// 设置渲染器的像素比例
renderer.setPixelRatio(window.devicePixelRatio);
});
// 将渲染器添加到body
document.body.appendChild(renderer.domElement);
// 初始化控制器
const controls = new OrbitControls(camera, renderer.domElement);
// 设置控制器阻尼
controls.enableDamping = true;
动画循环
在动画循环中更新时间变量,以驱动着色器中的动画效果:
const clock = new THREE.Clock();
function animate(t) {
controls.update();
const time = clock.getElapsedTime();
customUniforms.uTime.value = time;
requestAnimationFrame(animate);
// 使用渲染器渲染相机看这个场景的内容渲染出来
renderer.render(scene, camera);
}
animate();
自定义着色器材质示例
除了修改现有材质,我们也可以创建完全自定义的着色器材质:
let basicMaterial = new THREE.MeshBasicMaterial({
color: "#00ff00",
side: THREE.DoubleSide,
});
const basicUniforms = {
uTime: {
value: 0
}
};
basicMaterial.onBeforeCompile = (shader, renderer) => {
console.log(shader);
console.log(shader.vertexShader);
console.log(shader.fragmentShader);
// 添加时间变量到着色器
shader.uniforms.uTime = basicUniforms.uTime;
// 在顶点着色器的 common 部分添加时间变量
shader.vertexShader = shader.vertexShader.replace(
'#include <common>',
`
#include <common>
uniform float uTime;
`
);
// 在顶点变换部分添加动态位移
shader.vertexShader = shader.vertexShader.replace(
'#include <begin_vertex>',
`
#include <begin_vertex>
transformed.x += sin(uTime)* 2.0;
transformed.z += cos(uTime)* 2.0;
`
);
};
GLSL 着色器代码详解
基本着色器 (basic/vertex.glsl)
uniform vec3 uColor;
uniform float uFrequency;
uniform float uScale;
uniform float uTime;
varying float vElevation;
varying vec2 vUv;
precision highp float;
void main(){
vec4 modelPosition = modelMatrix * vec4( position, 1.0 );
modelPosition.z += sin((modelPosition.x+uTime) * uFrequency)*uScale ;
modelPosition.z += cos((modelPosition.y+uTime) * uFrequency)*uScale ;
vElevation = modelPosition.z;
gl_Position = projectionMatrix * viewMatrix * modelPosition;
vUv = uv;
}
基础片元着色器 (basic/fragment.glsl)
uniform vec3 uColor;
varying float vElevation;
precision highp float;
varying vec2 vUv;
uniform sampler2D uTexture;
void main(){
float alpha = (vElevation+0.1)+0.8;
vec4 textureColor = texture2D(uTexture,vUv);
textureColor.rgb*=alpha;
gl_FragColor = textureColor;
}
深度着色器技术
深度着色器通常用于阴影计算和特殊视觉效果,其中包含了丰富的 GLSL 技术:
- 随机函数:用于生成程序化噪声
- 旋转函数:用于变换 UV 坐标
- 噪声函数:用于创建自然效果
- Perlin 噪声:用于高质量的程序化纹理
总结
通过这个项目,我们学习了如何使用 Three.js 的着色器加工材质技术:
- 使用
onBeforeCompile回调修改现有材质的着色器代码 - 向着色器传递自定义 uniform 变量
- 在着色器的关键位置插入自定义代码
- 为深度材质应用相同的修改以保证一致性
- 利用 GLSL 编写高效的顶点和片元着色器
这种方法让我们能够在不完全重写材质的情况下,对 Three.js 的内置材质进行深度定制,创造出独特的视觉效果。掌握这项技术是实现高级视觉效果的关键技能,可以用于创建动态地形、流体效果、变形动画等各种复杂场景。