Kyle Paulsen
Kyle Paulsen

Reputation: 1006

How do you update a uniform In Three.js?

How do you update a shader uniform in three.js?

Let's say the uniform is defined as

new THREE.ShaderMaterial({
  uniforms: {
    yourUniform: { value: 'Something' }
  }
});

Upvotes: 15

Views: 19973

Answers (5)

Joel Teply
Joel Teply

Reputation: 3296

For those not using THREE.ShaderMaterial

If for example you are modifying a material beyond just THREE.ShaderMaterial, say THREE.MeshStandardMaterial, via edits to its fragment shader, you will need to do things somewhat differently.

In order to modify this shader's uniforms, WITHOUT requiring recompilation, you will need to store a reference to that shader within the onBeforeCompile callback, then access those uniforms via that stored reference's uniforms.

Typescript class:

First plug into THREE.Material's onBeforeCompile hook transferring the values stored somewhere (in my case a uniforms dictionary created in the constructor) into the shader's uniforms. This is important because shader compilation takes place prior to first usage. Store a reference to the shader in your class for access. Do any of your other work to the fragment shader using these uniforms as you see fit.

private _uniforms: { [uniform: string]: ShaderUniform } = {};
private _shader?:THREE.Shader;

this._material.onBeforeCompile = (shader) => {
    let prepend = "";
    //transfer any changes occurring prior to compilation, 
    //and also prepend these shaders to the fragment shader 
    Object.entries(this._uniforms).forEach(([key, info]) => {
       prepend += `uniform ${info.type} ${key};\n`
       shader.uniforms[key] = {value: info.value};
    });
    //prepend these shaders, along with any other work to the 
    //fragment shader via basic string substitutions 
    shader.fragmentShader = prepend + shader.fragmentShader;
    //store a reference to the shader
    this._shader = shader;
}

Get or set the property from the stored uniforms if not compiled yet, otherwise use the shader's own uniform value, which only exists after compilation.

public getUniform(name:string) : any {
    return this._shader ? this._shader.uniforms[name].value : this._uniforms[name].value;
}

public setUniform(name:string, value:any) {
   if (this._shader) {
       return this._shader.uniforms[name].value = value
   } else {
       this._uniforms[name].value = value;
   }
}

Upvotes: 0

Jacksonkr
Jacksonkr

Reputation: 32207

This seemed like a good way to do it for my use case:

const mat = new THREE.ShaderMaterial({
    uniforms: {
        customVec3Uniform: { value: new THREE.Vector3(1,1,1) }
    },
    vertexShader: document.getElementById('vertexShader').textContent,
    fragmentShader: document.getElementById('fragmentShader').textContent,
});

As for updating during runtime (citation):

mat.uniforms.customVec3Uniform.value = new THREE.Vector3(2,2,2);
// mat.needsUpdate = true; // my example works without 'needsUpdate'

Upvotes: 1

Hitesh Sahu
Hitesh Sahu

Reputation: 45042

There are 2 Ways:

  • You can update uniform in Shader Material itself

    OR

  • you can use mesh.material to access the ShaderMaterialand then update the uniform

    Example of both cases:

       var  delta = 0
       var customUniforms = {
        delta: { value: 0 },
        u_time: { value: Date.now() }
       };
    
        // shader material with custom Uniform
        shaderMaterial = new THREE.ShaderMaterial({
          uniforms: customUniforms,
          vertexShader: document.getElementById("vertexShader2").textContent,
          fragmentShader: document.getElementById("fragmentShader2").textContent
        });
    
        // test Mesh object for shader
        var geometry = new THREE.BoxBufferGeometry(10, 10, 10, 10, 10, 10);
        shaderMesh = new THREE.Mesh(geometry, shaderMaterial);
        this.scene.add(shaderMesh);
    

    In animate loop

      animate = () => {
        delta += 0.1; 
    
              // Update uniform in Shader Material
              shaderMaterial.uniforms.delta.value = 0.5 + Math.sin(delta) * 0.0005;
    
              // Update uniform from Mesh itself
         shaderMesh.material.uniforms.u_time.value = delta;
    
    }
    

https://codesandbox.io/s/autumn-http-e9wk5

enter image description here

Complete Example

<body>
    <div id="container"></div>
    <script src="js/three.min.js"></script>
    <script id="vertexShader" type="x-shader/x-vertex">
        void main() {
            gl_Position = vec4( position, 1.0 );
        }
    </script>
    <script id="fragmentShader" type="x-shader/x-fragment">
        uniform vec2 u_resolution;
        uniform float u_time;

        void main() {
            vec2 st = gl_FragCoord.xy/u_resolution.xy;
            gl_FragColor=vec4(st.x,st.y,0.0,1.0);
        }
    </script>
    <script>
        var container;
        var camera, scene, renderer;
        var uniforms;

        init();
        animate();

        function init() {
            container = document.getElementById( 'container' );

            camera = new THREE.Camera();
            camera.position.z = 1;

            scene = new THREE.Scene();

            var geometry = new THREE.PlaneBufferGeometry( 2, 2 );

            uniforms = {
                u_time: { type: "f", value: 1.0 },
                u_resolution: { type: "v2", value: new THREE.Vector2() },
                u_mouse: { type: "v2", value: new THREE.Vector2() }
            };

            var material = new THREE.ShaderMaterial( {
                uniforms: uniforms,
                vertexShader: document.getElementById( 'vertexShader' ).textContent,
                fragmentShader: document.getElementById( 'fragmentShader' ).textContent
            } );

            var mesh = new THREE.Mesh( geometry, material );
            scene.add( mesh );

            renderer = new THREE.WebGLRenderer();
            renderer.setPixelRatio( window.devicePixelRatio );

            container.appendChild( renderer.domElement );

            onWindowResize();
            window.addEventListener( 'resize', onWindowResize, false );

            document.onmousemove = function(e){
              uniforms.u_mouse.value.x = e.pageX
              uniforms.u_mouse.value.y = e.pageY
            }
        }

        function onWindowResize( event ) {
            renderer.setSize( window.innerWidth, window.innerHeight );
            uniforms.u_resolution.value.x = renderer.domElement.width;
            uniforms.u_resolution.value.y = renderer.domElement.height;
        }

        function animate() {
            requestAnimationFrame( animate );
            render();
        }

        function render() {
            uniforms.u_time.value += 0.05;
            renderer.render( scene, camera );
        }
    </script>
</body>

Upvotes: 8

Careen
Careen

Reputation: 567

Little example updating the shader uniform.

/* Vertex Shader */
<script type="x-shader/x-fragment" id="myShader">
    uniform float myuniform;
    uniform sampler2D myTexture;
    varying vec2 vUV;
    varying vec2 ver;

    ver = uv *vec2( myuniform,  myuniform); // this will be updated on mouse move        

    gl_Position = projectionMatrix *modelViewMatrix * vec4(position,1.0);
</script>

/* Set uniform */
var myUniform;
var myTexture = new THREE.ImageUtils.loadTexture( './data/textures/theTexture.jpg'  );
    _uniforms = {
    myUniform:  { type: "f", value: myUniform },        
    myTexture:  { type: "t", value: myTexture },
};

customMaterial = new THREE.ShaderMaterial( 
{
    uniforms: _uniforms,
    vertexShader: document.getElementById( 'myShader' ).textContent,
    fragmentShader: document.getElementById( 'fragmentShader' ).textContent,
    wireframe: false,
    side: THREE.FrontSide 

}   );

document.addEventListener( 'mousemove', onDocumentMouseMove, false );

function onDocumentMouseMove(ev) {
   uniforms.myUniform.value += 0.01; // Updates this new value
   uniforms.myUniform.needsUpdate = true;
}

...

Upvotes: 1

Kyle Paulsen
Kyle Paulsen

Reputation: 1006

This seems to work:

yourMesh.material.uniforms.yourUniform.value = whatever;

Upvotes: 12

Related Questions