在Three中,我们可以使用着色器对材质进行加工,例如在对物体材质进行设置时,我们可以通过对顶点着色器的更改,从而实现物体的运动或变化。使用着色器加工材质,主要依赖于Material材质基类中的onBeforeCompile方法进行实现。
目录
.onBeforeCompile ( shader : Shader, renderer : WebGLRenderer )
在编译shader程序之前立即执行的可选回调。此函数使用shader源码作为参数。用于修改内置材质。
和其他属性不一样的是,这个回调在.clone(),.copy() 和 .toJSON() 中不支持。
利用该方法实现着色器对材质的更改,本质是通过替换材质底层的着色器设置的参数实现对其的变换。
例如我们通过基础材质设置一个绿色的矩形,并使用着色器使其实现运动,
基础材质设置:
- let basicMaterial = new THREE.MeshBasicMaterial({
- color: "#00ff00",
- side: THREE.DoubleSide,
- });
着色器设置:
- const basicUnifrom = {
- uTime:{
- value:0
- }
- }
- basicMaterial.onBeforeCompile = (shader,renderer)=>{
- console.log(shader);
- console.log(shader.vertexShader)
- console.log(shader.fragmentShader)
-
- // console.log(renderer)
- shader.uniforms.uTime = basicUnifrom.uTime;
- shader.vertexShader = shader.vertexShader.replace(
- '#include
' , - `
- #include
- uniform float uTime;
- `
- )
- shader.vertexShader = shader.vertexShader.replace(
- '#include
' , - `
- #include
- transformed.x += sin(uTime)* 2.0;
- transformed.z += cos(uTime)* 2.0;
- `
- )
- }
实现效果:

可参考如下文章:
在 onBeforeCompile方法中设置的控制台输出中,我们可以看到其内部的着色器源码如下:
详见:

源码分析:
- #include
// 包含着色器公共模块(包含常用的数学工具函数以及一些常量定义什么的) - #include
// 包含处理uv所需要的一些定义 - #include
// 包含处理uv2所需要的一些定义 - #include
// 包含置换贴图displacementmap所需要的定义 - #include
// 包含顶点颜色所需要的定义 - #include
// 包含雾化效果所需要的定义 - #include
// 包含变形动画所需要的定义 - #include
// 包含蒙皮动画所需要的定义 - #include
// 包含阴影计算所需要的定义 - #include
// 包含深度处理的一些定义 - #include
// 包含裁剪平面所需要的一些定义 - void main() {
- #include
// uv 数据处理 - #include
// uv2 数据处理 - #include
// 颜色 数据处理 - #include
// 开始法线处理 - #include
// 变形动画法线处理 - #include
// 骨骼蒙皮基本运算 - #include
// 骨骼蒙皮法线运算 - #include
// 默认法线处理 - #ifndef FLAT_SHADED // Normal computed with derivatives when FLAT_SHADED
- vNormal = normalize( transformedNormal );
- #endif
- #include
// 开始顶点位置处理 - #include
// 变形动画位置处理 - #include
// 蒙皮顶点处理 - #include
// 置换贴图运用顶点处理 - #include
// 投影顶点运算 - #include
// logDepth深度运算 - #include
// 裁剪平面运算 - vViewPosition = - mvPosition.xyz;
- #include
// 世界坐标运算 - #include
// 阴影所需要的一些运算 - #include
// 雾化所需要的运算 -
-
- }
因此,我们在使用着色器实现对材质的更改时,可以通过替换shader中的参数进行实现。
例如上述物体随时间运动的案例中,便是通过replace方法实现,其语法是:stringObj.replace(rgExp, replaceText) 其中stringObj是字符串(string),reExp可以是正则表达式对象(RegExp)也可以是字符串(string),replaceText是替代查找到的字符串。
上述案例中通过替换
#include
为
- #include
- uniform float uTime;
实现时间参数uTime的设置,并通过对 #include
- #include
- transformed.x += sin(uTime)* 2.0;
- transformed.z += cos(uTime)* 2.0;
实现该物体的运动 。

源码分析:
- uniform vec3 diffuse; // 漫反射颜色
- uniform vec3 emissive; // 自发光颜色
- uniform float roughness; // 粗糙度
- uniform float metalness; // 金属性
- uniform float opacity; // 透明度
- #ifndef STANDARD
- uniform float clearCoat; //
- uniform float clearCoatRoughness;
- #endif
- varying vec3 vViewPosition; // 摄像机空间的坐标
- #ifndef FLAT_SHADED
- varying vec3 vNormal; // 摄像机空间的法线
- #endif
- #include
// 包含着色器公共模块(包含常用的数学工具函数以及一些常量定义什么的) - #include
// 数据编码解码功能函数 - #include
// 抖动处理的定义 - #include
// 颜色处理的定义 - #include
// uv相关处理的定义 - #include
// uv2相关处理的定义 - #include
// map贴图相关处理的定义 - #include
// alphamap贴图的处理定义 - #include
// aomap贴图的处理定义 - #include
// lighmap贴图处理定义 - #include
// emissivemap贴图处理的定义 - #include
// envmap贴图处理的定义 - #include
// 雾化需要的定义 - #include
// brdf相关的功能函数 - #include
// cubemap反射相关 - #include
// 灯光相关定义 - #include
// 灯光贴图相关 - #include
// 灯光相关物理运算 - #include
// shadowmap影子相关运算定义 - #include
// bumpmap相关运算的定义 - #include
// normalmap相关运算的定义 - #include
// roughnessmap相关运算的定义 - #include
// metalnessmap相关运算的定义 - #include
// logdepth相关运算的定义 - #include
// clipplane裁剪平面相关的定义 - void main() {
- #include
// 裁剪平面裁剪 - vec4 diffuseColor = vec4( diffuse, opacity );// 合成rgba四通道漫反射颜色
- ReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );
- vec3 totalEmissiveRadiance = emissive;
- #include
// logdepth运算 - #include
// map通道颜色采样 - #include
// color参与计算 - #include
// alphamap通道颜色采样 - #include
// alpha测试 - #include
// 粗糙贴图采样 - #include
// 金属性贴图采样 - #include
// 法线贴图基本运算 - #include
// 法线通过法线贴图运算 - #include
// 自发光贴图采样 - // accumulation
- #include
// 物理光照基础运算 - #include
// 计算各种灯光入射光和反射光信息 - #include
// 从环境光和光照贴图获取辐射 - #include
// 根据辐射光取得反射信息 - // modulation
- #include
// 根据AO贴图调整反射光照强度 - // 反射光直接漫反射+间接漫反射+直接高光+间接高光+自发光 = 输出光照颜色
- vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance;
- gl_FragColor = vec4( outgoingLight, diffuseColor.a );
- #include
// tonemap进行曝光 - #include
// 颜色编码 - #include
// 雾化颜色运算 - #include
// 颜色预乘alpha - #include
// 颜色随机抖动 - }
实现原理与上相同,通过替换着色器中的变量及参数等,实现着色器对材质的修改。
示例代码:
- import * as THREE from "three";
- import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
- import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader"
-
- // console.log(THREE);
- // 初始化场景
- const scene = new THREE.Scene();
- // 创建透视相机
- const camera = new THREE.PerspectiveCamera(
- 75,
- window.innerHeight / window.innerHeight,
- 1,
- 50
- );
- // 设置相机位置
- // object3d具有position,属性是1个3维的向量
- 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
- })
- //
- const customUniforms = {
- uTime : {
- value:0
- }
- }
- material.onBeforeCompile = (shader)=>{
- console.log(shader.vertexShader);
- console.log(shader.fragmentShader);
- // 传递时间
- shader.uniforms.uTime = customUniforms.uTime;
- //添加旋转矩阵算法
- shader.vertexShader = shader.vertexShader.replace(
- '#include
' , - `
- #include
- mat2 rotate2d(float _angle){
- return mat2(cos(_angle),-sin(_angle),
- sin(_angle),cos(_angle));
- }
- uniform float uTime;
- `
- )
-
- shader.vertexShader = shader.vertexShader.replace(
- '#include
' , - `
- #include
- // float angle = sin(position.y+uTime) *0.5;
- float angle = uTime *0.5;
- mat2 rotateMatrix = rotate2d(angle);
-
-
- objectNormal.xz = rotateMatrix * objectNormal.xz;
- `
- )
- shader.vertexShader = shader.vertexShader.replace(
- '#include
' , - `
- #include
- // 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.vertexShader = shader.vertexShader.replace(
- '#include
' , - `
- #include
- mat2 rotate2d(float _angle){
- return mat2(cos(_angle),-sin(_angle),
- sin(_angle),cos(_angle));
- }
- uniform float uTime;
- `
- );
- shader.vertexShader = shader.vertexShader.replace(
- '#include
' , - `
- #include
- // float angle = sin(position.y+uTime) *0.5;
- float angle = uTime *0.5;
- mat2 rotateMatrix = rotate2d(angle);
- transformed.xz = rotateMatrix * transformed.xz;
- `
- )
-
- }
-
- // 模型加载
- const gltfLoader = new GLTFLoader();
- gltfLoader.load('./models/LeePerrySmith/LeePerrySmith.glb',(gltf)=>{
- // console.log(gltf)
- const mesh = gltf.scene.children[0];
- console.log(mesh)
- mesh.material = material;
- mesh.castShadow = true;
- // 设定自定义的深度材质
- mesh.customDepthMaterial = depthMaterial;
- scene.add(mesh);
- })
-
- // 初始化渲染器
- const renderer = new THREE.WebGLRenderer();
- // 设置渲染尺寸大小
- renderer.setSize(window.innerWidth, window.innerHeight);
- renderer.shadowMap.enabled = true;
-
- // 监听屏幕大小改变的变化,设置渲染的尺寸
- window.addEventListener("resize", () => {
- // console.log("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;
- // 设置自动旋转
- // controls.autoRotate = 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();
实现效果:
