Reputation: 1006
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
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
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
Reputation: 45042
There are 2 Ways:
You can update uniform
in Shader Material
itself
OR
you can use mesh.material
to access the ShaderMaterial
and 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
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
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
Reputation: 1006
This seems to work:
yourMesh.material.uniforms.yourUniform.value = whatever;
Upvotes: 12