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

实现效果:

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控制台中输出顶点着色器源码如下:

源码分析: 

#include <common>           // 包含着色器公共模块(包含常用的数学工具函数以及一些常量定义什么的)
#include <uv_pars_vertex>   // 包含处理uv所需要的一些定义
#include <uv2_pars_vertex>  // 包含处理uv2所需要的一些定义
#include <displacementmap_pars_vertex> // 包含置换贴图displacementmap所需要的定义
#include <color_pars_vertex>            // 包含顶点颜色所需要的定义
#include <fog_pars_vertex>              // 包含雾化效果所需要的定义
#include <morphtarget_pars_vertex>      // 包含变形动画所需要的定义
#include <skinning_pars_vertex>         // 包含蒙皮动画所需要的定义
#include <shadowmap_pars_vertex>        // 包含阴影计算所需要的定义
#include <logdepthbuf_pars_vertex>      // 包含深度处理的一些定义
#include <clipping_planes_pars_vertex>  // 包含裁剪平面所需要的一些定义
void main() {
    #include <uv_vertex>            // uv 数据处理
    #include <uv2_vertex>           // uv2 数据处理
    #include <color_vertex>         // 颜色 数据处理
    #include <beginnormal_vertex>   // 开始法线处理
    #include <morphnormal_vertex>   // 变形动画法线处理
    #include <skinbase_vertex>      // 骨骼蒙皮基本运算
    #include <skinnormal_vertex>    // 骨骼蒙皮法线运算
    #include <defaultnormal_vertex> // 默认法线处理
#ifndef FLAT_SHADED // Normal computed with derivatives when FLAT_SHADED
    vNormal = normalize( transformedNormal );
#endif
    #include <begin_vertex>         // 开始顶点位置处理
    #include <morphtarget_vertex>   // 变形动画位置处理
    #include <skinning_vertex>          // 蒙皮顶点处理
    #include <displacementmap_vertex>   // 置换贴图运用顶点处理
    #include <project_vertex>           // 投影顶点运算
    #include <logdepthbuf_vertex>       // logDepth深度运算
    #include <clipping_planes_vertex>   // 裁剪平面运算
    vViewPosition = - mvPosition.xyz;
    #include <worldpos_vertex>      // 世界坐标运算
    #include <shadowmap_vertex>     // 阴影所需要的一些运算
    #include <fog_vertex>           // 雾化所需要的运算


}

因此,我们在使用着色器实现对材质的更改时,可以通过替换shader中的参数进行实现。

例如上述物体随时间运动的案例中,便是通过replace方法实现,其语法是:stringObj.replace(rgExp, replaceText) 其中stringObj是字符串(string),reExp可以是正则表达式对象(RegExp)也可以是字符串(string),replaceText是替代查找到的字符串。

上述案例中通过替换

  #include <common>

为 

    #include <common>
    uniform float uTime;

实现时间参数uTime的设置,并通过对  #include <begin_vertex>替换为

    #include <begin_vertex>
    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 控制台中输出片元着色器源如下:

源码分析:

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 <common>           //  包含着色器公共模块(包含常用的数学工具函数以及一些常量定义什么的)
#include <packing>          // 数据编码解码功能函数
#include <dithering_pars_fragment>  // 抖动处理的定义
#include <color_pars_fragment>      // 颜色处理的定义
#include <uv_pars_fragment>         // uv相关处理的定义
#include <uv2_pars_fragment>        // uv2相关处理的定义
#include <map_pars_fragment>        // map贴图相关处理的定义
#include <alphamap_pars_fragment>   // alphamap贴图的处理定义
#include <aomap_pars_fragment>      // aomap贴图的处理定义
#include <lightmap_pars_fragment>   // lighmap贴图处理定义
#include <emissivemap_pars_fragment>    // emissivemap贴图处理的定义
#include <envmap_pars_fragment> // envmap贴图处理的定义
#include <fog_pars_fragment>    // 雾化需要的定义
#include <bsdfs>                    // brdf相关的功能函数
#include <cube_uv_reflection_fragment>  // cubemap反射相关
#include <lights_pars_begin>        // 灯光相关定义
#include <lights_pars_maps>         // 灯光贴图相关
#include <lights_physical_pars_fragment> // 灯光相关物理运算
#include <shadowmap_pars_fragment>  // shadowmap影子相关运算定义
#include <bumpmap_pars_fragment>        // bumpmap相关运算的定义
#include <normalmap_pars_fragment>      // normalmap相关运算的定义
#include <roughnessmap_pars_fragment>       // roughnessmap相关运算的定义
#include <metalnessmap_pars_fragment>       // metalnessmap相关运算的定义
#include <logdepthbuf_pars_fragment>        // logdepth相关运算的定义
#include <clipping_planes_pars_fragment>        // clipplane裁剪平面相关的定义
void main() {
    #include <clipping_planes_fragment> // 裁剪平面裁剪
    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 <logdepthbuf_fragment> // logdepth运算
    #include <map_fragment>         // map通道颜色采样
    #include <color_fragment>       // color参与计算
    #include <alphamap_fragment>    // alphamap通道颜色采样
    #include <alphatest_fragment>   // alpha测试
    #include <roughnessmap_fragment>    // 粗糙贴图采样
    #include <metalnessmap_fragment>    // 金属性贴图采样
    #include <normal_fragment_begin>    // 法线贴图基本运算
    #include <normal_fragment_maps>     // 法线通过法线贴图运算
    #include <emissivemap_fragment>     // 自发光贴图采样
    // accumulation
    #include <lights_physical_fragment> // 物理光照基础运算
    #include <lights_fragment_begin> // 计算各种灯光入射光和反射光信息
    #include <lights_fragment_maps> // 从环境光和光照贴图获取辐射
    #include <lights_fragment_end>  // 根据辐射光取得反射信息
    // modulation
    #include <aomap_fragment>   // 根据AO贴图调整反射光照强度
    // 反射光直接漫反射+间接漫反射+直接高光+间接高光+自发光 = 输出光照颜色
    vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance;
    gl_FragColor = vec4( outgoingLight, diffuseColor.a );
    #include <tonemapping_fragment>// tonemap进行曝光
    #include <encodings_fragment> // 颜色编码
    #include <fog_fragment>             // 雾化颜色运算
    #include <premultiplied_alpha_fragment> // 颜色预乘alpha
    #include <dithering_fragment>   // 颜色随机抖动
}

 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 <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;
    float angle = 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.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;
    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();

实现效果:

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

HM-hhxx!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值