Three.js-着色器加工材质及材质着色器详解

在Three中,我们可以使用着色器对材质进行加工,例如在对物体材质进行设置时,我们可以通过对顶点着色器的更改,从而实现物体的运动或变化。使用着色器加工材质,主要依赖于Material材质基类中的onBeforeCompile方法进行实现。

目录

1. onBeforeCompile

.onBeforeCompile ( shader : Shader, renderer : WebGLRenderer )

2. Three材质中shader源码分析

2.1 shader.vertexShader

2.2 shader.fragmentShader

 3. 使用着色器修改材质实现gltf模型旋转


1. onBeforeCompile

.onBeforeCompile ( shader : Shader, renderer : WebGLRenderer ) : undefined

在编译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;
    `
  )
}

实现效果:

Three.js-着色器加工材质及材质着色器详解_第1张图片

2. Three材质中shader源码分析

可参考如下文章:


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

2.1 shader.vertexShader

详见:

ThreeJS 物理材质shader源码分析(顶点着色器) - zzatp - 博客园ThreeJS 物理材质shader源码分析(顶点着色器) Threejs将shader代码分为ShaderLib和ShaderChunk两部分,ShaderLib通过组合ShaderChunk的代码https://www.cnblogs.com/zzatp/p/9253482.html控制台中输出顶点着色器源码如下:

Three.js-着色器加工材质及材质着色器详解_第2张图片

源码分析: 

#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;

实现该物体的运动 。 

2.2 shader.fragmentShader

详见:ThreeJS 物理材质shader源码分析(像素着色器) - zzatp - 博客园像素着色器(meshphysical_frag.glsl) #define PHYSICAL uniform vec3 diffuse; // 漫反射颜色 uniform vec3 emissive;https://www.cnblogs.com/zzatp/p/9274074.html 控制台中输出片元着色器源如下:

Three.js-着色器加工材质及材质着色器详解_第3张图片

源码分析:

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    // 颜色随机抖动
}

 3. 使用着色器修改材质实现gltf模型旋转

 实现原理与上相同,通过替换着色器中的变量及参数等,实现着色器对材质的修改。

示例代码:

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();

实现效果:

你可能感兴趣的:(着色器,Three,着色器,three.js,3d,Three.js)