通过使用onBeforeCompile

问题描述

在我的three.js场景中,我有一个加载了THREE.OBJLoader的.obj文件

我想在保留对象MeshStandardMaterial着色器的同时,在z轴上将线性颜色渐变应用于此对象。下面是在对象上应用2色线性渐变的示例。

在保留MeshStandardMaterial类随附的着色器的同时,我需要这样做。我想做到这一点,我需要用一些代码注入MeshStandardMaterial的vertexShader和materialShaders。

question的答案部分实现了这一点。这些答案将创建一种新的着色器材质,而没有我需要的MeshStandardMaterial属性

我尝试使用onBeforeCompile,但不知道将上面提到的question的答案中概述的代码注入哪里。我对着色器只有一个基本的了解。

我的对象的材质由THREE.MeshStandardMaterial类定义。我设置了该类的以下属性:金属性,粗糙度,透明性和不透明性。我用纹理设置了map和envmap属性

解决方法

很难理解着色器块中的内容是什么,但并非不可能。

body{
  overflow: hidden;
  margin: 0;
}
<script type="module">
import * as THREE from "https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.js";
import {OrbitControls} from "https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/controls/OrbitControls.js";

let scene = new THREE.Scene();
let camera = new THREE.PerspectiveCamera(60,innerWidth / innerHeight,1,1000);
camera.position.set(0,10,20);
let renderer = new THREE.WebGLRenderer();
renderer.setSize(innerWidth,innerHeight);
document.body.appendChild(renderer.domElement);

let controls = new OrbitControls(camera,renderer.domElement);

let light = new THREE.DirectionalLight(0xffffff,1);
light.position.setScalar(10);
scene.add(light);
scene.add(new THREE.AmbientLight(0xffffff,1));

let g = new THREE.TorusKnotBufferGeometry(5,128,16);
g.rotateX(-Math.PI * 0.5);
g.computeBoundingBox();

let uniforms = {
    bbMin: {value: g.boundingBox.min},bbMax: {value: g.boundingBox.max},color1: {value: new THREE.Color(0xff0000)},color2: {value: new THREE.Color(0xffff00)}
}
console.log(g);
let m = new THREE.MeshStandardMaterial({
    roughness: 0.25,metalness: 0.75,map: new THREE.TextureLoader().load("https://threejs.org/examples/textures/floors/FloorsCheckerboard_S_Diffuse.jpg",tex => {
    tex.wrapS = THREE.RepeatWrapping;
    tex.wrapT = THREE.RepeatWrapping;
    tex.repeat.set( 16,1 );
  }),onBeforeCompile: shader => {
    shader.uniforms.bbMin = uniforms.bbMin;
    shader.uniforms.bbMax = uniforms.bbMax;
    shader.uniforms.color1 = uniforms.color1;
    shader.uniforms.color2 = uniforms.color2;
    shader.vertexShader = `
        varying vec3 vPos;
      ${shader.vertexShader}
    `.replace(
    `#include <begin_vertex>`,`#include <begin_vertex>
    vPos = transformed;
    `
    );
    shader.fragmentShader = `
        uniform vec3 bbMin;
      uniform vec3 bbMax;
      uniform vec3 color1;
      uniform vec3 color2;
      varying vec3 vPos;
      ${shader.fragmentShader}
    `.replace(
        `vec4 diffuseColor = vec4( diffuse,opacity );`,`
      float f = clamp((vPos.z - bbMin.z) / (bbMax.z - bbMin.z),0.,1.);
      vec3 col = mix(color1,color2,f);
      vec4 diffuseColor = vec4( col,opacity );`
    );
    console.log(shader.vertexShader);
    console.log(shader.fragmentShader);
  }
});

let o = new THREE.Mesh(g,m);
scene.add(o);


renderer.setAnimationLoop(()=>{
    renderer.render(scene,camera);
});
</script>

,

MeshStandardMaterial着色器可编译为数百行代码,因此要在保持环境反射,照明,金属性,粗糙度以及所有这些行为的同时,精确指出更改颜色的位置非常困难。您可能想得太多。如果您只需要在MeshStandardMaterial上渐变,为什么不只用渐变创建纹理,然后将其分配给Material.map属性?如果添加白色的AmbientLight,则应该可以毫无问题地看到渐变。

或者,如果希望渐变不受灯光影响,则可以将纹理分配给材质的.emissive属性。

最后,如果要避免使用纹理,可以为每个顶点分配颜色,然后将Material.vertexColors设置为true。