How do you update a uniform In Three.js? - javascript

I had to re-write my question because I was asking the wrong thing before.
I meant to ask, how do you update a uniform in three.js?

This seems to work:
yourMesh.material.uniforms.yourUniform.value = whatever;

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
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>

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

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'

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

Related

WebGL: Set opacity when using texture2D()

I have a simple WebGL scene created by Three.js for which I'm using a custom shader. The scene contains an image and I'd like to set the opacity of the image, but manipulating the .a attribute of gl_FragColor is not doing the trick.
Does anyone know how to set the opacity of an image in WebGL? I'd be grateful for any ideas others can offer on this question!
<html>
<head>
<style>
html, body { width: 100%; height: 100%; background: #000; }
body { margin: 0; overflow: hidden; }
canvas { width: 100%; height: 100%; }
</style>
</head>
<body>
<script src='https://cdnjs.cloudflare.com/ajax/libs/three.js/92/three.min.js'></script>
<script src='https://threejs.org/examples/js/controls/TrackballControls.js'></script>
<script type='x-shader/x-vertex' id='vertex-shader'>
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
uniform vec3 cameraPosition;
attribute vec2 uv;
attribute vec3 position;
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
</script>
<script type='x-shader/x-fragment' id='fragment-shader'>
precision highp float; // set float precision (optional)
uniform sampler2D texture; // identify the texture as a uniform argument
varying vec2 vUv; // identify the uv values as a varying attribute
void main() {
vec4 color = texture2D(texture, vUv);
gl_FragColor = vec4(color[0], color[1], color[2], 0.0);
}
</script>
<script>
function getScene() {
var scene = new THREE.Scene();
scene.background = new THREE.Color(0xffffff);
return scene;
}
function getCamera() {
var aspectRatio = window.innerWidth / window.innerHeight;
var camera = new THREE.PerspectiveCamera(75, aspectRatio, 0.1, 1000);
camera.position.set(0, 1, 10);
return camera;
}
function getRenderer() {
// Create the canvas with a renderer
var renderer = new THREE.WebGLRenderer({antialias: true});
// Add support for retina displays
renderer.setPixelRatio(window.devicePixelRatio);
// Specify the size of the canvas
renderer.setSize(window.innerWidth, window.innerHeight);
// Add the canvas to the DOM
document.body.appendChild(renderer.domElement);
return renderer;
}
function getControls(camera, renderer) {
var controls = new THREE.TrackballControls(camera, renderer.domElement);
controls.zoomSpeed = 0.4;
controls.panSpeed = 0.4;
return controls;
}
function loadImage() {
var geometry = new THREE.BufferGeometry();
/*
Now we need to push some vertices into that geometry to identify the coordinates the geometry should cover
*/
// Identify the image size
var imageSize = {width: 10, height: 7.5};
// Identify the x, y, z coords where the image should be placed
var coords = {x: -5, y: -3.75, z: 0};
// Add one vertex for each corner of the image, using the
// following order: lower left, lower right, upper right, upper left
var vertices = new Float32Array([
coords.x, coords.y, coords.z, // bottom left
coords.x+imageSize.width, coords.y, coords.z, // bottom right
coords.x+imageSize.width, coords.y+imageSize.height, coords.z, // upper right
coords.x, coords.y+imageSize.height, coords.z, // upper left
])
// set the uvs for this box; these identify the following corners:
// lower-left, lower-right, upper-right, upper-left
var uvs = new Float32Array([
0.0, 0.0,
1.0, 0.0,
1.0, 1.0,
0.0, 1.0,
])
// indices = sequence of index positions in `vertices` to use as vertices
// we make two triangles but only use 4 distinct vertices in the object
// the second argument to THREE.BufferAttribute is the number of elements
// in the first argument per vertex
geometry.setIndex([0,1,2, 2,3,0])
geometry.addAttribute('position', new THREE.BufferAttribute( vertices, 3 ));
geometry.addAttribute('uv', new THREE.BufferAttribute( uvs, 2) )
// Create a texture loader so we can load our image file
var loader = new THREE.TextureLoader();
// specify the url to the texture
var url = 'https://s3.amazonaws.com/duhaime/blog/tsne-webgl/assets/cat.jpg';
// specify custom uniforms and attributes for shaders
// Uniform types: https://github.com/mrdoob/three.js/wiki/Uniforms-types
var material = new THREE.RawShaderMaterial({
uniforms: {
texture: {
type: 't',
value: loader.load(url)
},
transparent: true,
},
vertexShader: document.getElementById('vertex-shader').textContent,
fragmentShader: document.getElementById('fragment-shader').textContent
});
// Combine our image geometry and material into a mesh
var mesh = new THREE.Mesh(geometry, material);
// Set the position of the image mesh in the x,y,z dimensions
mesh.position.set(0,0,0)
// Add the image to the scene
scene.add(mesh);
}
/**
* Render!
**/
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
controls.update();
};
var scene = getScene();
var camera = getCamera();
var renderer = getRenderer();
var controls = getControls(camera, renderer);
loadImage();
render();
</script>
</body>
</html>
You need to set
material.transparent = true;
Doing so will force normal alpha-blending to be set automatically by the renderer.
three.js r.95

Vertex Colors are changing to white

I'm working with THREE.js points and sometimes I need them to have different per point color. Sometimes, I'm also modifying their alpha value so I had to write my own shader programs.
In JavaScript I have the following code:
let materials;
if (pointCloudData.colors !== undefined) {
geometry.colors = pointCloudData.colors.map(hexColor => new THREE.Color(hexColor));
// If the point cloud has color for each point...
materials = new THREE.ShaderMaterial({
vertexColors: THREE.VertexColors,
vertexShader: document.getElementById('vertexshader').textContent,
fragmentShader: document.getElementById('fragmentshader').textContent,
transparent: true,
});
} else {
// Set color for the whole cloud
materials = new THREE.ShaderMaterial({
uniforms: {
unicolor: { value: pointCloudData.color },
},
vertexShader: document.getElementById('vertexshader').textContent,
fragmentShader: document.getElementById('fragmentshader').textContent,
transparent: true,
});
}
const pointCloud = new THREE.Points(geometry, materials);
I am basically setting the mesh color to a uniform value unless I have defined per point colors - then I set vertexColors to the geometry. I also checked the values being stored in the geometry.colors and they are correct RGB values in range [0,1].
My Vertex Shader code:
attribute float size;
attribute float alpha;
varying float vAlpha;
varying vec3 vColor;
void main() {
vAlpha = alpha;
#ifdef USE_COLOR
vColor = color;
#endif
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
gl_PointSize = size * ( 300.0 / -mvPosition.z );
gl_Position = projectionMatrix * mvPosition;
}
And my Fragment shader code:
uniform vec3 unicolor;
varying vec3 vColor;
varying float vAlpha;
void main() {
#ifdef USE_COLOR
gl_FragColor = vec4(vColor, vAlpha);
#else
gl_FragColor = vec4(unicolor, vAlpha);
#endif
}
Again, I am checking if the vertexColor is set and then passing it to the Fragment Shader which then sets the per point.
For some reason, the vertices are all white when setting the color per point (screenshot: The white pixels should be green/red). I'm far from advanced user in WebGL and any help would be appreciated. Am I doing something wrong that I'm not aware of?
You are creating a custom ShaderMaterial and using this pattern in your fragment shader:
#ifdef USE_COLOR
vColor = color;
#endif
Consequently, you need to specify the material.defines like so:
var defines = {};
defines[ "USE_COLOR" ] = "";
// points material
var shaderMaterial = new THREE.ShaderMaterial( {
defines: defines,
uniforms: uniforms,
vertexShader: document.getElementById( 'vertexshader' ).textContent,
fragmentShader: document.getElementById( 'fragmentshader' ).textContent,
transparent: true
} );
You do not need to set vertexColors: THREE.VertexColors. That is just a flag used by built-in materials to alert the renderer to set the defines for you.
three.js r.85
OK, I think I figured it out since it's working now.
I had to set the colors as geometry attributes:
const colors = new Float32Array(n * 3);
for (let i = 0; i < n; i += 1) {
new THREE.Color(pointCloudData.colors[i]).toArray(colors, i * 3);
}
geometry.addAttribute('colors', new THREE.BufferAttribute(colors, 1));
I also used the suggestion provided by WestLangley and removed the vertexColors: THREE.VertexColors, part from the Material definition and set the define as well:
materials = new THREE.ShaderMaterial({
defines: {
USE_COLOR: '',
},
vertexShader: document.getElementById('vertexshader').textContent,
fragmentShader: document.getElementById('fragmentshader').textContent,
transparent: true,
});
Then in my Vertex shader I added:
attributes vec3 colors;
to get the colors passed from the JavaScript. The rest is the same, I just passed the colors to the fragment shader using the same code as in the posted question above.

Three.js Texture in shaderMaterial

I could not load texture to my shader material, only what I see are just black dot. This is my shader.js
THREE.ShaderLib['cloud'] = {
uniforms: {
texture: { type: "t", value: THREE.ImageUtils.loadTexture("img/cloud.png") }
},
vertexShader: [
"varying vec2 vUv;",
"void main() {",
"vUv = uv;",
"gl_PointSize = 8.0;",
"gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);",
"}",
].join("\n"),
fragmentShader: [
"varying vec2 vUv;",
"uniform sampler2D texture;",
"void main() {",
"gl_FragColor = texture2D(texture, vUv);",
"}",
].join("\n")
};
and this is how I'm trying to load and use it:
var cloudShader = THREE.ShaderLib["cloud"];
var uniforms = THREE.UniformsUtils.clone(cloudShader.uniforms);
var texture = THREE.ImageUtils.loadTexture("img/cloud.png", undefined, function () {
uniforms.texture.value = texture;
texture.needsUpdate = true;
var _material = new THREE.ShaderMaterial({
fragmentShader: cloudShader.fragmentShader,
vertexShader: cloudShader.vertexShader,
uniforms: uniforms
});
_material.uniforms.texture.value.needsUpdate = true;
var _geometry = new THREE.Geometry();
_geometry.vertices.push(new THREE.Vector3(0, 0, 0));
var _mesh = new THREE.Points(_geometry, _material);
scene.add(_mesh);
});
As You can see I'm trying to set update texture value two times, before and after material is created. It's really simpy example but I have no idea why it is not working in the way that I'm using it. There is no errors in debug console.
I'm using THREE.Points class, because I'm using it to generate clouds as a particle groups.
Thanks in advance for any help.
So, after some time I have found a solution for this specific problem. I need to move my uniforms from shader.js directly to place where i'm creating shader material. This is enough to fix problem with my texture, but still I don't know why previous code doesn't work. Working shader material looks like this:
var _material = new THREE.ShaderMaterial({
fragmentShader: cloudShader.fragmentShader,
vertexShader: cloudShader.vertexShader,
uniforms: uniforms: {
texture: { type: "t", value: THREE.ImageUtils.loadTexture("img/cloud.png") }
},
});

How to add shader to THREE.Object3D loaded from OBJMTLLoader

I have a model loaded using THREE.OBJMTLLoader.
var loader = new THREE.OBJMTLLoader();
loader.addEventListener('load', function(event) {
var mesh = event.content;
scene.add(mesh);
});
loader.load('model/machine.obj', 'model/machine.mtl');
I need to apply a vertex and fragment shader to this model. How to do this?
In addition to Gero3's answer, this would ensure that all the meshes in the content will get the right material:
var material = new THREE.ShaderMaterial( {
uniforms: shader.uniforms,
fragmentShader: shader.fragmentShader,
vertexShader: shader.vertexShader
} );
var object = event.content;
object.traverse( function ( child ) {
if ( child instanceof THREE.Mesh ) {
child.material = material;
}
} );
scene.add( object );
You need a shadermaterial for adding a vertex and fragment shader.
var material = new THREE.ShaderMaterial( {
fragmentShader: shader.fragmentShader,
vertexShader: shader.vertexShader,
uniforms: shader.uniforms
} ),

three.js skybox assigned to camera

I'm trying to make skybox assigned to player camera.
When camera moves(also skybox moves with it), texture get stretched.
How to get rid of this?
Code:
var textureCube = THREE.ImageUtils.loadTextureCube( urls );
textureCube.format = THREE.RGBFormat;
var shader = THREE.ShaderUtils.lib[ "cube" ];
shader.uniforms[ "tCube" ].value = textureCube;
cubematerial = new THREE.ShaderMaterial({
fragmentShader: shader.fragmentShader,
vertexShader: shader.vertexShader,
uniforms: shader.uniforms,
depthWrite: false,
side: THREE.BackSide
});
skyBox = new THREE.Mesh(new THREE.CubeGeometry(1000,1000,1000), cubematerial);
camera.add(skyBox);
So, after digging into Three.js examples, I found a way how to do this. http://learningthreejs.com/blog/2011/08/15/lets-do-a-sky/ is outdated. A way used in examples is to add skybox into second scene with fixed camera, and render both scenes. Look at webgl_materials_cars.html example.
Also because I use 3rd person camera assigned to character, I must get world rotation from character camera to skybox camera. This can be done on render with:
function render(){
<...>
skyboxCamera.rotation.setEulerFromRotationMatrix( new THREE.Matrix4().extractRotation( camera.matrixWorld ), skyboxCamera.eulerOrder );
renderer.render(skyboxScene, skyboxCamera);
renderer.render(scene, camera);
<...>
}
I know it's a closed question but I want to offer an alternative that does not require an additional scene, for future seekers:
start by reading and following this tutorial: http://learningthreejs.com/blog/2011/08/15/lets-do-a-sky/
now create the following shader (I added it to three.js ShaderLib, but if you don't want to temper with three' source code add it outside):
'skybox': {
uniforms: { "tCube": { type: "t", value: null },
"tFlip": { type: "f", value: -1 } },
vertexShader: [
"varying vec3 vWorldPosition;",
THREE.ShaderChunk[ "logdepthbuf_pars_vertex" ],
"void main() {",
" vec4 worldPosition = modelMatrix * vec4( position, 1.0 );",
" vWorldPosition = worldPosition.xyz;",
" gl_Position = projectionMatrix * modelViewMatrix * vec4( position + cameraPosition, 1.0 );",
THREE.ShaderChunk[ "logdepthbuf_vertex" ],
"}"
].join("\n"),
fragmentShader: [
"uniform samplerCube tCube;",
"uniform float tFlip;",
"varying vec3 vWorldPosition;",
THREE.ShaderChunk[ "logdepthbuf_pars_fragment" ],
"void main() {",
" gl_FragColor = textureCube( tCube, vec3( tFlip * vWorldPosition.x, vWorldPosition.yz ) );",
THREE.ShaderChunk[ "logdepthbuf_fragment" ],
"}"
].join("\n")
},
create your skybox like this:
// urls is a list of textures to use
var cubemap = THREE.ImageUtils.loadTextureCube(urls);
cubemap.format = THREE.RGBFormat;
var shader = THREE.ShaderLib['skybox']; // init the skybox shader we created above
shader.uniforms['tCube'].value = cubemap; // apply textures to shader
// create shader material
var skyBoxMaterial = new THREE.ShaderMaterial( {
fragmentShader: shader.fragmentShader,
vertexShader: shader.vertexShader,
uniforms: shader.uniforms,
depthWrite: false,
side: THREE.BackSide
});
// create skybox mesh
var skybox = new THREE.Mesh(
new THREE.CubeGeometry(1000, 1000, 1000),
skyBoxMaterial
);
// THIS IS IMPORTANT! or the skybox will get culled after you move the camera too far..
skybox.frustumCulled = false;

Categories

Resources