ShaderMaterial not working correctly

What i want is a gradient effect for my particles like in this image

this is my code

const shaderMaterial = new THREE.ShaderMaterial({
      uniforms: {
          time: { value: 0 }
      },
      vertexShader: `
          uniform float time;
          varying vec3 vPosition;
          
          void main() {
              vPosition = position;
              vPosition.x += sin(time + vPosition.z * 4.0) / 4.0;
              vPosition.y += cos(time + vPosition.z * 4.0) / 4.0;
              gl_Position = projectionMatrix * modelViewMatrix * vec4(vPosition, 1.0);
          }
      `,
      fragmentShader: `
          varying vec3 vPosition;
          
          void main() {
              gl_FragColor = vec4(vPosition * 2.0, 1.0);
          }
      `
  });




    const sphereGeometry = new THREE.SphereGeometry(0.008, 16, 16);
    const particles = new THREE.InstancedMesh(
      sphereGeometry,
      new THREE.MeshStandardMaterial({color:0xfff00}),
      8000
    );
    particles.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
    const matrix = new THREE.Matrix4();

    const group = new THREE.Group();
    scene.add(group);

    let sampler;
    let model;
    loader.load('/earth.glb', (gltf) => {
      model = gltf.scene;
      gltf.scene.traverse((obj) => {
        if (obj.isMesh) {
          obj.scale.set(0.4, 0.4, 0.4);
          sampler = new MeshSurfaceSampler(obj).build();

          for (let i = 0; i < 30000; i++) {
            const sample = new THREE.Vector3();
            sampler.sample(sample);
            matrix.makeTranslation(sample.x, sample.y, sample.z);
            particles.setMatrixAt(i, matrix);
          }
          group.add(particles)
          group.scale.set(0.36, 0.36, 0.36);
        }
      });
    });

what i get now is

when i use shaderMaterial instead of meshStandardMaterial it not working.
if you see this video you can understand the issue video link

In the shader material, you don’t take instance matrices in count.

I still don’t understand, why you want so badly to use instancing of a sphere instead of points. :thinking:
An attempt with points:

1 Like

Instance looks good. But I have no knowledge in glsl for custom shader for gradient. :neutral_face:

I would draw a gradient on a canvas, pass that canvas in CanvasTexture, pass the texture in a uniform, then process it in shaders.

1 Like

can i see the code of this . please

I recommend making vPosition modifications for color gradients in the fragment shader, because it is responsible for the color on the screen. As its name suggests, the vertex shader takes care of the vertices. If you normalize the vPosition in the fragment shader and use the amount you will get a color gradient when your sphere is at the origin. The amount of the normalized positions is then simply the RGB value.

const shaderMaterial = new THREE.ShaderMaterial({
      uniforms: {
          time: { value: 0 }
      },
      vertexShader: `
          varying vec3 vPosition;
          
          void main() {
              vPosition = position;
              gl_Position = projectionMatrix * modelViewMatrix * vec4(vPosition, 1.0);
          }
      `,
      fragmentShader: `
      
      	  #define PI 3.141592653
      
          varying vec3 vPosition;
          uniform float time;

          
          vec3 color = normalize(vPosition);
           
          color.r *= sin(time);
          color.g *= sin(time + PI/3.0);
          color.b *= sin(time + PI*2.0/3.0);
          
          void main() {
              gl_FragColor = vec4(abs(color), 1.0);
          }
      `
  });

P.S. Or as prisoner849 recommends incorporating a color gradient texture. Both ways are possible.

1 Like

This is how you get a texture in the shader. I’ll leave it up to you what you do with it inside the shader.

var textureLoader = new THREE.TextureLoader();
var texture = textureLoader.load('./your_texture_path/customTexture.png'); //or jpg


const shaderMaterial = new THREE.ShaderMaterial({
      uniforms: {
          time: { value: 0 },
          gradientTex: { value: texture }
      },
      vertexShader: `
          varying vec3 vPosition;
          varying vec2 vUv;
          
          void main() {
          	  vUv = uv;
              vPosition = position;
              gl_Position = projectionMatrix * modelViewMatrix * vec4(vPosition, 1.0);
          }
      `,
      fragmentShader: `
      
      	  #define PI 3.141592653
      
          varying vec3 vPosition;
          varying vec2 vUv;
          uniform float time;
          uniform sampler2D gradientTex;
  
  
  		  vec4 textureColor = texture2D(gradientTex, vUv); //rgba
        
          
          vec3 color = normalize(vPosition);
           
          color.r *= sin(time);
          color.g *= sin(time + PI/3.0);
          color.b *= sin(time + PI*2.0/3.0);
          
          void main() {
              gl_FragColor = vec4(abs(color), 1.0);
          }
      `
  });

I use Points here.

First of all, I would recommend to fix the positioning of instances, as you stand for InstanceMesh. So far you don’t get instanceMatrix in count in your vertex shader.

2 Likes

but its not like https://colorver.se/

the gradient of points are not fixed . its changing according to the rotation.
Is there any other way than shader or with shader

i tried this but getting some errors
Program Info Log: Fragment shader is not compiled.
ERROR: 0:42: ‘=’ : global variable initializers must be constant expressions
ERROR: 0:45: ‘=’ : global variable initializers must be constant expressions
ERROR: 0:47: ‘color’ : syntax error

37: varying vec2 vUv;
38: uniform float time;
39: uniform sampler2D gradientTex;
40:
41:

42: vec4 textureColor = texture2D(gradientTex, vUv); //rgba
43:
44:
45: vec3 color = abs(normalize(vPosition));
46:
47: color.r *= sin(time);
48: color.g *= sin(time + PI/3.0);

It doesn’t have to be like this for me, so I made it my way.

Back to your project.
As you want the gradient to follow rotation of the sphere, set color of each instance with .setColorAt() method. :thinking: Thus, you don’t have to do it in shaders.

const color = new THREE.Color();

    const sphereGeometry = new THREE.SphereGeometry(0.008, 16, 16);
    const particles = new THREE.InstancedMesh(
      sphereGeometry,
      new THREE.MeshStandardMaterial(),
      8000
    );
    particles.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
    const matrix = new THREE.Matrix4();

    const group = new THREE.Group();
    scene.add(group);

    let sampler;
    let model;
    loader.load('/earth.glb', (gltf) => {
      model = gltf.scene;
      gltf.scene.traverse((obj) => {
        if (obj.isMesh) {
          obj.scale.set(0.4, 0.4, 0.4);
          sampler = new MeshSurfaceSampler(obj).build();
          for (let i = 0; i < 30000; i++) {
            const sample = new THREE.Vector3();
            sampler.sample(sample);
            matrix.makeTranslation(sample.x, sample.y, sample.z);
            particles.setMatrixAt(i, matrix);

            const gradientPosition = (sample.x - obj.position.x + obj.scale.x / 2) / obj.scale.x;
            if (gradientPosition < 0.5) {
              color.lerpColors(new THREE.Color(0x006dff), new THREE.Color(0xfc0001), gradientPosition * 2);
            } else {
              color.lerpColors(new THREE.Color(0xfc0001), new THREE.Color(0xf2e300), (gradientPosition - 0.5) * 2);
            }
    
            particles.setColorAt(i, color);
          }
          
          group.add(particles)
          group.scale.set(0.36, 0.36, 0.36);
        }
      });
    });

when i do like this i get a gradient effect but not exactly like in the website

I would calculate gradientPosition, using globe’s radius. :thinking: