Search code examples
javascriptthree.jsglslshadergradient

How do I create a gradient effect across the mesh like in my example?


I have the following code on JSFiddle: https://jsfiddle.net/gentleman_goat66/o5wn3bpf/215/

enter image description here

What I want is the look on the red/green box but with the borders of the purple box. I created one with BoxGeometry (the purple one) and the other (the red/green) manually created with vertices and fed to BufferGeometry.

I want the pink box on this JSFiddle to keep it's borders but have the red/green gradient effect.

I created a custom shader which adds a nice black border around the geometry by taking advantage of the edges of UVs, but I can't figure out how to get it to create the red to yellow to green gradient effect that is possible with the MeshStandardMaterial

You can see I was able to sort of create a gradient by using the vUv.y but the top face does not align when I do this and not sure how to get it to go to another color?

How can I modify the UVs and Shadercode to create this effect with my current custom shader?

Put another way, refer to this different JSFiddle: (https://jsfiddle.net/gentleman_goat66/o5wn3bpf/216/)

I want the pink box on this JSFiddle to keep it's borders but have the red/green gradient effect.

enter image description here

enter image description here

HTML

<script type="importmap">
{
"imports": 
  {
    "three": "https://unpkg.com/[email protected]/build/three.module.js",
    "OrbitControls": "https://unpkg.com/[email protected]/examples/jsm/controls/OrbitControls.js"
  }
}
</script>
<script id="vertexShader" type="x-shader/x-vertex">

            #include <common>
      #include <uv_pars_vertex>
      #include <displacementmap_pars_vertex>
      #include <color_pars_vertex>
      #include <fog_pars_vertex>
      #include <morphtarget_pars_vertex>
      #include <skinning_pars_vertex>
      #include <shadowmap_pars_vertex>
      #include <specularmap_pars_fragment>
      #include <logdepthbuf_pars_vertex>
      #include <clipping_planes_pars_vertex>
      
            varying vec2 vUv;
      
            void main() {
            
                #include <beginnormal_vertex>
                #include <morphnormal_vertex>
                #include <begin_vertex>
                #include <morphtarget_vertex>
                
        vUv = uv;
        
                //gl_Position = vec4( transformed, 1.0 );
        gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);

            }

</script>
<script id="fragmentShader" type="x-shader/x-fragment">
            varying vec2 vUv;
        uniform float thickness;
      
      float edgeFactor(vec2 p){
        vec2 grid = abs(fract(p - 0.5) - 0.5) / fwidth(p) / thickness;
        return min(grid.x, grid.y);
        }
      
            void main() {

                float a = edgeFactor(vUv);
      
        vec3 c = mix(vec3(0), vec3(0.1), a);
        if(a >= 1.0){
          //float changex = vUv.x;
          //float changey = vUv.y;
          //if(vUv.x > 0.5){
            //changex = vUv.x - 0.5;
          //}
          //if(vUv.y > 0.5){
            //changey = vUv.y - 0.5;
          //}
            c = vec3(1.0,vUv.y,1.0);
        }
      
        gl_FragColor = vec4(c, 1.0);

            }

</script>
<div id="container">
</div>

JS

import * as THREE from 'three';
import { OrbitControls } from 'OrbitControls';

class Heatmap {
  constructor(){
    console.log("Heatmap - constructor()");
    
    //ThreeJS Variables
    this.camera = null;
    this.scene = null;
    this.renderer = new THREE.WebGLRenderer({antialias: true});
    this.orbital_controls = null;
    this.ambientLight = null;
    this.heatmap = new THREE.Object3D(); //This will hold all the meshes that make up the heatmap
    
    //Animation Related Variables
    this.clock = new THREE.Clock();
    this.delta = 0;
    this.fps = 60; //60 fps
        this.interval = 1/this.fps;
        this.seconds = 2; //seconds for animation
        this.timeSoFar = 0;
        this.targetTime = 3;
    
    //Setup
    this.scene = new THREE.Scene();
    this.setUpCamera();
    this.setUpRenderer();
    this.setUpLighting();
    this.setUpOrbitalControls();
    
    //Add optional GridHelper for Debug
    this.scene.add(new THREE.GridHelper());
    
    //TODO: Create the heatmap here
    this.createDataRectangle();
    
    //Add the finished heatmap to the scene
    this.scene.add(this.heatmap);
    this.render();
    this.animate();
  }
  setUpRenderer(){
    console.log("Heatmap - setUpRenderer()");
    let width = $('#container').width();
    let height = $('#container').height();
    this.renderer.setPixelRatio(window.devicePixelRatio);
    this.renderer.setSize(width,height);
    this.renderer.setClearColor(0x404040);
 
    $("#container").html(this.renderer.domElement);
    
    window.addEventListener("resize", event => {
      this.camera.aspect = innerWidth / innerHeight;
      this.camera.updateProjectionMatrix();
      this.renderer.setSize(innerWidth, innerHeight);
    });
  }
  setUpCamera(){
    console.log("Heatmap - setUpCamera()");
    this.camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.01, 100);
    this.camera.position.z = 4;
  }
  setUpLighting(){
    console.log("Heatmap - setUpLighting()");
    this.ambientLight = new THREE.AmbientLight(0xffffff, 1);
    this.scene.add(this.ambientLight);
  }
  setUpOrbitalControls(){
    console.log("Heatmap - setUpOrbitalControls()");
    this.orbital_controls = new OrbitControls( this.camera, this.renderer.domElement );
    this.orbital_controls.target.set(0, 0, 0);
    this.orbital_controls.enableDamping = true;
    this.orbital_controls.dampingFactor = 0.025;
    this.orbital_controls.maxPolarAngle = Math.PI/2.0;
    this.orbital_controls.minDistance = 2.0;
    this.orbital_controls.maxDistance = 80.0;
    this.orbital_controls.update();
    
    this.orbital_controls.addEventListener( 'change', function(){
      if (this.target.y < 0){
          this.target.y = 0;
          /* camera.position.y = -1; */
      } else if (this.target.y > 1){
          this.target.y = 1;
          /* camera.position.y = 1; */
      }
    });
  }
  createDataRectangle(){ //TODO: Make this create at a position specified
    console.log("Heatmap - createDataRectangle()");
    const geometry = new THREE.BufferGeometry();
    const vertices = [
      -2, 0, 1,//0, floor, bottom left
      -1, 0, 1,//1, floor, bottom right
      -1, 0, -1,//2, floor, top right
      -2, 0, -1,//3 floor, top left
      -2, 3, 1,//4 roof, bottom left
      -1, 3, 1,//5 roof, bottom right
      -1, 3, -1,//6 roof, top right
      -2, 3, -1//7 roof, top left
    ];
    const indices = [
      0, 1, 2,//floor, first triangle
      2, 3, 0,//floor, second triangle
      4, 5, 6,//roof, first triangle
      6, 7, 4,//roof, second triangle
      3, 0, 4,//west wall, first triangle
      3, 4, 7,//west wall, second triangle
      0, 1, 5,//south wall, first triangle
      0, 5, 4,//south wall, first triangle
      1, 2, 6,//east wall, first triangle
      1, 6, 5,//east wall, second triangle
      2, 3, 7,//north wall, first triangle
      2, 7, 6,//north wall, second triangle
    ];
    const colors = [
      2,0,0, // bottom left
      0,2,0, // bottom right
      0,2,0, // top right
      2,0,0,  // top left
      2,0,0, // bottom left
      0,2,0, // bottom right
      0,2,0, // top right
      2,0,0  // top left
    ];
    var quad_uvs = [
        0, 1,
      0, 0,
      1, 0,
      1, 1,
      0, 0,
      1, 0,
      1, 1,
      0, 1
    ];
    geometry.setIndex(indices);
    geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
    geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
    geometry.setAttribute('uv', new THREE.Float32BufferAttribute( quad_uvs, 2 ) );
    geometry.computeVertexNormals();
    console.log(geometry);
    
    const material = new THREE.MeshStandardMaterial({side:THREE.FrontSide,vertexColors:true,color: 0xFFFFFF });
    var shader_material = new THREE.ShaderMaterial( {
      vertexShader: document.getElementById( 'vertexShader' ).textContent,
      fragmentShader: document.getElementById( 'fragmentShader' ).textContent,
      uniforms: {
        thickness: {
          value: 0.5
        }
      }
    });
    const sample_rectangle = new THREE.Mesh(geometry, material);
    this.heatmap.add(sample_rectangle);
    
    //Demo showing correct effect
    const geometryBox = new THREE.BoxGeometry();
    console.log(geometryBox);
    
    const sample_box = new THREE.Mesh(geometryBox, shader_material);
    sample_box.geometry.computeVertexNormals();
    sample_box.scale.y = 3;
    this.heatmap.add(sample_box);
  }
  animate(){
  
    requestAnimationFrame(() => {
      this.animate()
    });

    this.orbital_controls.update();//Required for Damping on OrbitControls

    this.delta += this.clock.getDelta();

    if (this.delta  > this.interval) {
      //this.timeSoFar = this.timeSoFar + (1 * morphDirection);

      this.render();
      this.delta = this.delta % this.interval;
    }
  }
  render(){
    this.renderer.render(this.scene, this.camera);
  }
}

$(document).ready(function(){
  console.log("DOM ready!");
  new Heatmap();
});

CSS

body,html{
  margin: 0;
  width: 100%;
  height: 100%;
}
#container {
  width: 100%;
  height: 100%;
  background-color: black;
  margin: 0;
}

UPDATE:

Here is an example of how I want the color with boxGeometry as well:

https://jsfiddle.net/gentleman_goat66/o5wn3bpf/249/

I just need that to work with the shader now.


Solution

  • I solved the issue, the key is that the color vector information has to be passed in correctly and the shader has to get the attribute color and work with it accordingly.

    Refer to my JSFiddle here: https://jsfiddle.net/gentleman_goat66/o5wn3bpf/249/

    <script type="importmap">
    {
    "imports": 
      {
        "three": "https://unpkg.com/[email protected]/build/three.module.js",
        "OrbitControls": "https://unpkg.com/[email protected]/examples/jsm/controls/OrbitControls.js"
      }
    }
    </script>
    <script id="vertexShader" type="x-shader/x-vertex">
    
                #include <common>
          #include <uv_pars_vertex>
          #include <displacementmap_pars_vertex>
          #include <color_pars_vertex>
          #include <fog_pars_vertex>
          #include <morphtarget_pars_vertex>
          #include <skinning_pars_vertex>
          #include <shadowmap_pars_vertex>
          #include <specularmap_pars_fragment>
          #include <logdepthbuf_pars_vertex>
          #include <clipping_planes_pars_vertex>
          
                varying vec2 vUv;
          attribute vec3 color;
          varying vec3 vColor;
          
                void main() {
                
                    #include <beginnormal_vertex>
                    #include <morphnormal_vertex>
                    #include <begin_vertex>
                    #include <morphtarget_vertex>
                    
            vUv = uv;
            vColor = color;
            
                    //gl_Position = vec4( transformed, 1.0 );
            gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);
    
                }
    
    </script>
    <script id="fragmentShader" type="x-shader/x-fragment">
                varying vec2 vUv;
          varying vec3 vColor;
          
            uniform float thickness;
          
          float edgeFactor(vec2 p){
            vec2 grid = abs(fract(p - 0.5) - 0.5) / fwidth(p) / thickness;
            return min(grid.x, grid.y);
            }
          
                void main() {
    
                    float a = edgeFactor(vUv);
          
            vec3 c = mix(vec3(0), vec3(0.1), a);
            if(a >= 1.0){
                c = vec3(vColor.r,vColor.g,vColor.b);
            }
          
            gl_FragColor = vec4(c, 1.0);
    
                }
    
    </script>
    <div id="container">
    </div>