TubeGeometry material gradient based on vertex index

I have this awesome code from @prisoner849

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

Which results in this:

I’m clueless with shaders, is there any way I can make it go from red to blue from start to finish? Rather than based on the y position.
(if you follow the line currently you can see it goes red, purple, blue, purple, blue… whereas I want red, purple, blue).

I’d want something like

vUv.y = index / totalVertices

Or am I talking nonsense and this isn’t how shaders work at all?

1 Like

Not nonsense. But “index” might need to be

vUv.y =  float(gl_VertexID) / totalVertices;

and somehow you have to get “totalVertices” into the shader, either by hardcoding a number, or passing it in as a uniform.

edit: whoops gl_VertexID is in GLES 3 only.

but I think it might be available by default now since threejs is webGL2 by default. Sorry it’s a bit confusing tbh.

If you use TubeGeometry, then

vUv.y = (position.y - bboxMin.y) / (bboxMax.y - bboxMin.y);

should be enough to turn it into:

vUv = uv;

:thinking:

I think OP is wanting a gradient along the line… not from top to bottom in Y.

This is what vUv.y vUv.x will do, if you use TubeGeometry :thinking:

Snippet:

let g = new THREE.TubeGeometry(new THREE.CatmullRomCurve3([
	[-1, -1, 0],
  [0, 0, 0],
  [1, 1, 1],
  [-1, 2, 0],
  [-1, -2, -1]
].map(p => {return new THREE.Vector3(...p)})), 100, 0.1, 6);
let m = new THREE.ShaderMaterial({
	uniforms: {
    colorStart: {value: new THREE.Color(0xff0000)},
    colorEnd: {value: new THREE.Color(0x0000ff)}
  },
  vertexShader: `
  	varying vec2 vUv;
  	void main(){
    	vUv = uv;
    	gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.);
    }
  `,
  fragmentShader: `
    uniform vec3 colorStart;
    uniform vec3 colorEnd;
  	varying vec2 vUv;
    
    void main(){
      vec3 col = mix(colorStart, colorEnd, vUv.x); // the using of uv.x is enough
    	gl_FragColor = vec4(col, 1);
    }
  `
})
1 Like

Ahhhh yessss this is the way. @LeeMc

Thank you both @manthrax and @prisoner849
Shaders are an alien language to us mortals, continue the great work :slight_smile:

2 Likes