在Three中,我们可以使用着色器对材质进行加工,例如在对物体材质进行设置时,我们可以通过对顶点着色器的更改,从而实现物体的运动或变化。使用着色器加工材质,主要依赖于Material材质基类中的onBeforeCompile方法进行实现。
目录
1. onBeforeCompile
.onBeforeCompile ( shader : Shader, renderer : WebGLRenderer )
2. Three材质中shader源码分析
2.1 shader.vertexShader
2.2 shader.fragmentShader
3. 使用着色器修改材质实现gltf模型旋转
在编译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方法中设置的控制台输出中,我们可以看到其内部的着色器源码如下:
详见:
ThreeJS 物理材质shader源码分析(顶点着色器) - zzatp - 博客园ThreeJS 物理材质shader源码分析(顶点着色器) Threejs将shader代码分为ShaderLib和ShaderChunk两部分,ShaderLib通过组合ShaderChunk的代码https://www.cnblogs.com/zzatp/p/9253482.html控制台中输出顶点着色器源码如下:
源码分析:
#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;
实现该物体的运动 。
详见:ThreeJS 物理材质shader源码分析(像素着色器) - zzatp - 博客园像素着色器(meshphysical_frag.glsl) #define PHYSICAL uniform vec3 diffuse; // 漫反射颜色 uniform vec3 emissive;https://www.cnblogs.com/zzatp/p/9274074.html 控制台中输出片元着色器源如下:
源码分析:
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();
实现效果: