javascriptthree.jsvertex-shadergeometry-shader

Apply color gradient to material on mesh - three.js


I have an STL file loaded into my scene with a single colour applied to a phong material

I'd like a way of applying two colours to this mesh's material with a gradient effect applied on the Z axis a like the example below.Gradient Vase]1

I have a feeling I may have to introduce shaders but I've not gotten this far with three.js.


Solution

  • Simple gradient shader, based on uvs:

    var scene = new THREE.Scene();
    var camera = new THREE.PerspectiveCamera(60, 1, 1, 1000);
    camera.position.set(13, 25, 38);
    camera.lookAt(scene.position);
    var renderer = new THREE.WebGLRenderer({
      antialias: true
    });
    var canvas = renderer.domElement
    document.body.appendChild(canvas);
    
    var controls = new THREE.OrbitControls(camera, renderer.domElement);
    
    var geometry = new THREE.CylinderBufferGeometry(2, 5, 20, 32, 1, true);
    var material = new THREE.ShaderMaterial({
      uniforms: {
        color1: {
          value: new THREE.Color("red")
        },
        color2: {
          value: new THREE.Color("purple")
        }
      },
      vertexShader: `
        varying vec2 vUv;
    
        void main() {
          vUv = uv;
          gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);
        }
      `,
      fragmentShader: `
        uniform vec3 color1;
        uniform vec3 color2;
      
        varying vec2 vUv;
        
        void main() {
          
          gl_FragColor = vec4(mix(color1, color2, vUv.y), 1.0);
        }
      `,
      wireframe: true
    });
    var mesh = new THREE.Mesh(geometry, material);
    scene.add(mesh);
    
    
    
    render();
    
    function resize(renderer) {
      const canvas = renderer.domElement;
      const width = canvas.clientWidth;
      const height = canvas.clientHeight;
      const needResize = canvas.width !== width || canvas.height !== height;
      if (needResize) {
        renderer.setSize(width, height, false);
      }
      return needResize;
    }
    
    function render() {
      if (resize(renderer)) {
        camera.aspect = canvas.clientWidth / canvas.clientHeight;
        camera.updateProjectionMatrix();
      }
      renderer.render(scene, camera);
      requestAnimationFrame(render);
    }
    html,
    body {
      height: 100%;
      margin: 0;
      overflow: hidden;
    }
    
    canvas {
      width: 100%;
      height: 100%;
      display;
      block;
    }
    <script src="https://cdn.jsdelivr.net/npm/three@0.115.0/build/three.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/three@0.115.0/examples/js/controls/OrbitControls.js"></script>

    Simple gradient shader, based on coordinates:

    var scene = new THREE.Scene();
    var camera = new THREE.PerspectiveCamera(60, 1, 1, 1000);
    camera.position.set(13, 25, 38);
    camera.lookAt(scene.position);
    var renderer = new THREE.WebGLRenderer({
      antialias: true
    });
    var canvas = renderer.domElement
    document.body.appendChild(canvas);
    
    var controls = new THREE.OrbitControls(camera, renderer.domElement);
    
    var geometry = new THREE.CylinderBufferGeometry(2, 5, 20, 16, 4, true);
    geometry.computeBoundingBox();
    var material = new THREE.ShaderMaterial({
      uniforms: {
        color1: {
          value: new THREE.Color("red")
        },
        color2: {
          value: new THREE.Color("purple")
        },
        bboxMin: {
          value: geometry.boundingBox.min
        },
        bboxMax: {
          value: geometry.boundingBox.max
        }
      },
      vertexShader: `
        uniform vec3 bboxMin;
        uniform vec3 bboxMax;
      
        varying vec2 vUv;
    
        void main() {
          vUv.y = (position.y - bboxMin.y) / (bboxMax.y - bboxMin.y);
          gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);
        }
      `,
      fragmentShader: `
        uniform vec3 color1;
        uniform vec3 color2;
      
        varying vec2 vUv;
        
        void main() {
          
          gl_FragColor = vec4(mix(color1, color2, vUv.y), 1.0);
        }
      `,
      wireframe: true
    });
    var mesh = new THREE.Mesh(geometry, material);
    scene.add(mesh);
    
    
    
    render();
    
    function resize(renderer) {
      const canvas = renderer.domElement;
      const width = canvas.clientWidth;
      const height = canvas.clientHeight;
      const needResize = canvas.width !== width || canvas.height !== height;
      if (needResize) {
        renderer.setSize(width, height, false);
      }
      return needResize;
    }
    
    function render() {
      if (resize(renderer)) {
        camera.aspect = canvas.clientWidth / canvas.clientHeight;
        camera.updateProjectionMatrix();
      }
      renderer.render(scene, camera);
      requestAnimationFrame(render);
    }
    html,
    body {
      height: 100%;
      margin: 0;
      overflow: hidden;
    }
    
    canvas {
      width: 100%;
      height: 100%;
      display: block;
    }
    <script src="https://cdn.jsdelivr.net/npm/three@0.115.0/build/three.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/three@0.115.0/examples/js/controls/OrbitControls.js"></script>

    Gradient with vertex colours:

    var scene = new THREE.Scene();
    var camera = new THREE.PerspectiveCamera(60, 1, 1, 1000);
    camera.position.set(0, 0, 10);
    var renderer = new THREE.WebGLRenderer({
      antialias: true
    });
    var canvas = renderer.domElement
    document.body.appendChild(canvas);
    
    
    var geom = new THREE.TorusKnotGeometry(2.5, .5, 100, 16);
    
    var rev = true;
    
    var cols = [{
      stop: 0,
      color: new THREE.Color(0xf7b000)
    }, {
      stop: .25,
      color: new THREE.Color(0xdd0080)
    }, {
      stop: .5,
      color: new THREE.Color(0x622b85)
    }, {
      stop: .75,
      color: new THREE.Color(0x007dae)
    }, {
      stop: 1,
      color: new THREE.Color(0x77c8db)
    }];
    
    setGradient(geom, cols, 'z', rev);
    
    function setGradient(geometry, colors, axis, reverse) {
    
      geometry.computeBoundingBox();
    
      var bbox = geometry.boundingBox;
      var size = new THREE.Vector3().subVectors(bbox.max, bbox.min);
    
      var vertexIndices = ['a', 'b', 'c'];
      var face, vertex, normalized = new THREE.Vector3(),
        normalizedAxis = 0;
    
      for (var c = 0; c < colors.length - 1; c++) {
    
        var colorDiff = colors[c + 1].stop - colors[c].stop;
    
        for (var i = 0; i < geometry.faces.length; i++) {
          face = geometry.faces[i];
          for (var v = 0; v < 3; v++) {
            vertex = geometry.vertices[face[vertexIndices[v]]];
            normalizedAxis = normalized.subVectors(vertex, bbox.min).divide(size)[axis];
            if (reverse) {
              normalizedAxis = 1 - normalizedAxis;
            }
            if (normalizedAxis >= colors[c].stop && normalizedAxis <= colors[c + 1].stop) {
              var localNormalizedAxis = (normalizedAxis - colors[c].stop) / colorDiff;
              face.vertexColors[v] = colors[c].color.clone().lerp(colors[c + 1].color, localNormalizedAxis);
            }
          }
        }
      }
    }
    
    var mat = new THREE.MeshBasicMaterial({
      vertexColors: THREE.VertexColors,
      wireframe: true
    });
    var obj = new THREE.Mesh(geom, mat);
    scene.add(obj);
    
    
    
    render();
    
    function resize(renderer) {
      const canvas = renderer.domElement;
      const width = canvas.clientWidth;
      const height = canvas.clientHeight;
      const needResize = canvas.width !== width || canvas.height !== height;
      if (needResize) {
        renderer.setSize(width, height, false);
      }
      return needResize;
    }
    
    function render() {
      if (resize(renderer)) {
        camera.aspect = canvas.clientWidth / canvas.clientHeight;
        camera.updateProjectionMatrix();
      }
      renderer.render(scene, camera);
      obj.rotation.y += .01;
      requestAnimationFrame(render);
    }
    html,
    body {
      height: 100%;
      margin: 0;
      overflow: hidden;
    }
    
    canvas {
      width: 100%;
      height: 100%;
      display;
      block;
    }
    <script src="https://cdn.jsdelivr.net/npm/three@0.115.0/build/three.min.js"></script>

    Actually, it's up to you which approach to use: shaders, vertex colours, textures etc.