How to udpate an array of values for the shadermaterial in three.js?

I’m using Three.js to render dynamic shadows. In my GLSL shader, I have a uniform vec3 array parameter and a uniform float array parameter. During the rendering process, I want to update these arrays. My question is how to update them in my animate function?

My code looks like:

 <script id="fragmentShader" type="x-shader/x-fragment">
        uniform vec3 pos[4];
        uniform float size[4];
        uniform float tt;

        void main() {
            vec3 color = vec3(0.0)
            for(int i=0; i<4; i++)
            {
             color += myfunction(pos[i], size[i]); // the final color value is within [0,1]
            }
            gl_FragColor=vec4(color * tt,1.0);
        }
    </script>
    <script>
        var mypos = new Array();
        var mysize = new Array();
        var renderer, camera, scene, mesh, material;

        init();
        animate();
 
        function init(){         
    
        // initiate renderer, scene, camera, mesh
        ...
        //

        mypos[0] = new THREE.Vector3(1,0,0);
        mypos[1] = new THREE.Vector3(0,1,0);
        mypos[2] = new THREE.Vector3(0,0,1);
        mypos[3] = new THREE.Vector3(1,1,0);

        mysize[0] = 0.1;
        mysize[1] = 0.2;        
        mysize[2] = 0.3;
        mysize[3] = 0.4;

       material = new THREE.ShaderMaterial({
          uniforms: {
            pos: { value: [mypos[0], mypos[1], mypos[2], mypos[3]] },
            size: { value: [mysize[0], mysize[1], mysize[2], mysize[3]] },
            tt : {value: 0.3}
          },
          vertexShader: document.getElementById('vertexShader').textContent,
          fragmentShader: document.getElementById('fragmentShader').textContent
        });
        } 
       
        ...
        }

        function animate()
        { 
          for(var i=0; i<4)
          {
            mypos[i] = new vec3(mypos[i].x + 0.1*i, mypos[i].y + 0.2*i, mypos[i].z + 0.3*i);
            mysize[i] = mysize[i]+ 0.5* i;
          }
          tt = tt + 0.01;
          
          // To change tt 
          material.uniforms.tt.value = tt;
          
          //How to update mypos[4] and mysize[4] for material
          ??
        }
         
    </script>

To update the tt value in shader, I can use material.uniforms.tt.value = tt;. When I update the values of the two arrays, mypos[4] and mysize[4], I tried to use the following code:

material.uniforms.pos.value = [mypos[0], mypos[1], mypos[2], mypos[3]];
material.uniforms.size.value = [mysize[0], mysize[1], mysize[2], mysize[3]];

It gives me the error that Uncaught TypeError: firstElem.toArray is not a function .

So, I want to know how to update the values of mypos[4] and mysize[4] for the material. Thanks a lot!!!

You can check the values of material.uniforms.pos and material.uniforms.size. If they are THREE.Vector4, then:

material.uniforms.pos.set( mypos[0], mypos[1], mypos[2], mypos[3] );
material.uniforms.size.set( mysize[0], mysize[1], mysize[2], mysize[3] );

(post deleted by author)

I modified the code to be
uniform vec4 pos[4]; uniform vec4 size[4];
and
mypos[0] = new THREE.Vector4(1,0,0,1); mysize[0] = new THREE.Vector4(1,0,0,1); for the shader and the animate() respectively.

However, when I use uniforms.pos.set( mypos[0], mypos[1], mypos[2], mypos[3] ); material.uniforms.size.set( mysize[0], mysize[1], mysize[2], mysize[3] );,
it gives the error that material.uniforms.pos.set is not a function

I tried an array of four Vector3 elements with the following syntax:

Fragment shader (GLSL):

uniform vec3 pos[4];

Shader uniforms definition (JS):

var myPos = [ new THREE.Vector3(1,0,0),
              new THREE.Vector3(0,1,0), 
              new THREE.Vector3(0,0,1), 
              new THREE.Vector3(1,1,1)
            ];

uniforms = {
	...
	{ pos: { value: myPos } }
};

Modification of values (JS):

uniforms.pos.value[0].set( 0,0,0,1 ); // first vector
uniforms.pos.value[1].set( 2,1,0,1 ); // second vector
... // and so on

PS. When earlier I suggested material.uniforms.pos.set(...) I did not realize that you have an array of vectors. My bad. So, instead of this, use material.uniforms.pos.value[i].set(...) where i is the index in the array.

1 Like

Thanks for your reply. I modified my code as your suggestion, and it works now! Now my code looks like:

<script id="fragmentShader" type="x-shader/x-fragment">
        uniform vec3 pos[4];
        uniform float size[4];
        uniform float tt;

        void main() {
            vec3 color = vec3(0.0)
            for(int i=0; i<4; i++)
            {
             color += myfunction(pos[i], size[i]); // the final color value is within [0,1]
            }
            gl_FragColor=vec4(color * tt,1.0);
        }
    </script>
    <script>
        var mypos = new Array();
        var mysize = new Array();
        var renderer, camera, scene, mesh, material;

        init();
        animate();
 
        function init(){         
    
        // initiate renderer, scene, camera, mesh
        ...
        //

        mypos = [ new THREE.Vector3(1,0,0),
                  new THREE.Vector3(0,1,0), 
                  new THREE.Vector3(0,0,1), 
                  new THREE.Vector3(1,1,0) ];

        mysize =  [ 0.1, 0.2, 0.3, 0.4];

       material = new THREE.ShaderMaterial({
          uniforms: {
            pos: { value: mypos },
            size: { value: mysize },
            tt : {value: 0.3}
          },
          vertexShader: document.getElementById('vertexShader').textContent,
          fragmentShader: document.getElementById('fragmentShader').textContent
        });
        } 
       
        ...
        }

        function animate()
        { 
          for(var i=0; i<4)
          {
            mypos[i] = new vec3(mypos[i].x + 0.1*i, mypos[i].y + 0.2*i, mypos[i].z + 0.3*i);
            mysize[i] = mysize[i]+ 0.5* i;
          }
          tt = tt + 0.01;
          
          // To update tt 
          material.uniforms.tt.value = tt;
          
          //To update mypos[4] and mysize[4]
          for(var i=0; i<4; i++)
          {
             material.uniforms.pos.value[i].set(mypos[i].x, mypos[i].y, mypos[i].z, 1);
             material.uniforms.size.value[i] = myszie[i];
           }
        }
         
    </script>

For the array of vectors, uniform vec3 pos[4], I can use material.uniforms.pos.value[i].set(...) but for the array of float uniform float size[4];, If I use material.uniforms.size.value[i].set(...), it gives the error that material.uniforms.size.value[i].set is not a function. While I use material.uniforms.size.value[i] = myszie[i]; it works fine.

By the way, I also want to know why I need four values (0,0,0, 1) to update the value of pos[i], which is a vec3 parameter.
If I use uniforms.pos.value[0].set( 0,0,0,1 ); the value of pos[0] in the shader should be ve3(0, 0, 0), am I correct? Then, what is the role of 1 in ....set( 0,0,0,1 );? Is it for memory alignment?

  1. material.uniforms.pos.value is an array of Vector3, so after you get individual vector, you change its value with .set. At the same time, material.uniforms.size.value is an array of numbers, so you can change individual elements directly.
  2. You do not need a 4th parameter when setting the value of Vector3. Before answering you, I had to test my answer and I used Vector4 for my test. When I wrote the answer to you, I forgot to remove it. If you use uniforms.pos.value[0].set(0,0,0) it should work fine.

I change my code to uniforms.pos[0].set(mypos[0].x,mypos[0].y,mypos[0].z), and it still works! Thank you so much!

1 Like