1

I'm trying to use a code I found here to create a gradient shader on a cube, based on coordinates. But the position in my vertex shader doesn't seem to vary. It goes from 0 to 1 without any steps in between:

enter image description here

What am I doing wrong? https://codesandbox.io/s/modest-silence-xzg1c?file=/src/App.js

Here is my fragment and vertex shader:

    const vertexShader = `
      varying vec2 vUv; 

      void main() {
        vUv.y = position.y;
        gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
      }
    `

    const fragmentShader = `
      uniform vec3 colors[2]; 

      varying vec2 vUv;

      void main() {
        gl_FragColor = vec4(mix(colors[0], colors[1], vUv.y), 1.0);
      }
    `

    const uniforms = {
      colors: {
        type: 'v3v',
        value: [new Color('#ff0000'), new Color('#0000ff')]
      }
    }
Zamadhi Eturu
  • 131
  • 2
  • 10
  • I'm pretty sure this is what you need: https://stackoverflow.com/questions/52614371/apply-color-gradient-to-material-on-mesh-three-js – alon Dec 10 '21 at 12:51
  • Yes but I have the same code and it doesn’t produce a gradient but only two colors. That’s why I asked the question – Zamadhi Eturu Dec 10 '21 at 13:08
  • This is a very confusing line: `vUv.y = position.y;`. Seeing `vUv` in the fragment shader I expect, that it uses uv coords :) – prisoner849 Dec 10 '21 at 15:10

1 Answers1

4

This is how you can interpolate colors, using Y-coord of vertices:

body{
  overflow: hidden;
  margin: 0;
}
<script type="module">
import * as THREE from "https://cdn.skypack.dev/three@0.135.0";
import {OrbitControls} from "https://cdn.skypack.dev/three@0.135.0/examples/jsm/controls/OrbitControls";

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

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

let g = new THREE.BoxBufferGeometry(200, 200, 200, 10, 10, 10);
let m = new THREE.ShaderMaterial({
  uniforms: {
    colors: { 
      value: [new THREE.Color('#ff0000'), new THREE.Color('#0000ff')]
    }
  },
  vertexShader: `
    varying float h; 

    void main() {
      h = position.y;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
  `,
  fragmentShader: `
    uniform vec3 colors[2]; 

    varying float h;

    void main() {
      float f = (h + 100.) / 200.;  // linear interpolation
                                // but you can also use 'smoothstep'
      f = clamp(f, 0., 1.);
      gl_FragColor = vec4(mix(colors[0], colors[1], f), 1.0);
    }
  `
})
let o = new THREE.Mesh(g, m);
o.scale.setScalar(5);
scene.add(o);

renderer.setAnimationLoop(() => {
  renderer.render(scene, camera);
});
</script>
prisoner849
  • 16,894
  • 4
  • 34
  • 68
  • It gives the same result as I already have. Just half of the cube is blue and the other half is red. – Zamadhi Eturu Dec 10 '21 at 17:33
  • I just added a codesandbox https://codesandbox.io/s/modest-silence-xzg1c?file=/src/App.js – Zamadhi Eturu Dec 10 '21 at 18:02
  • @ZamadhiEturu I've changed the snippet. Blindly copy-paste the code for interpolation in range from -1 to 1 on the box with sizes of 200, what result do you expect to happen, except it visually will be like a hard line between colours? – prisoner849 Dec 10 '21 at 18:33
  • 1
    I'm not a three.js expert (just saw this in [tag:shader]) but `uniform vec3 colors[2]; uniform float width; varying float h; void main() { float f = h/width + 0.5; f = clamp(f, 0., 1.); gl_FragColor = vec4(mix(colors[0], colors[1], f), 1.0); }` and `const uniforms = { colors: {type: "v3v",value: [new Color("#ff0000"), new Color("#0000ff")]}, width: { type: "float", value: 200.0}` works for me – Ruzihm Dec 10 '21 at 18:42
  • @Ruzihm very good :) – prisoner849 Dec 10 '21 at 18:45