For these examples, I'm really only interested in changing the pixel colors, so I'll just worry about the fragment shader.
The Three.js (v.56) code for the a simple fragment shader that just displays a texture would be:
<script id="fragmentShader" type="x-shader/x-vertex">
uniform sampler2D texture1;
varying vec2 vUv;
void main()
{
vec4 baseColor = texture2D( texture1, vUv );
gl_FragColor = baseColor;
}
</script>
(note that this code is contained within its own script tags.)
Then to create the material in the initialization of the Three.js code:
var lavaTexture = new THREE.ImageUtils.loadTexture( 'images/lava.jpg' );
lavaTexture.wrapS = lavaTexture.wrapT = THREE.RepeatWrapping;
var customUniforms = { texture1: { type: "t", value: lavaTexture } };
// create custom material from the shader code above within specially labeled script tags
var customMaterial = new THREE.ShaderMaterial(
{
uniforms: customUniforms,
vertexShader: document.getElementById( 'vertexShader' ).textContent,
fragmentShader: document.getElementById( 'fragmentShader' ).textContent
} );
What we would like to create a more realistic effect is to:
- add a bit of "random noise" to the texture coordinates vector (vUv) to cause distortion
- change the values of the random noise so that the distortions in the image change fluidly
- (optional) add support for transparency (custom alpha values)
To accomplish this, we can add some additional parameters to the shader, namely:
- a second texture (noiseTexture) to generate "noise values"; for instance, we can add the red/green values at a given pixel to the x and y coordinates of vUv, causing an offset
- a float (noiseScale) to scale the effects of the "noise values"
- a float (time) to pass a "time" value to the shader to continuously shift the texture used to generate noise values
- a float (baseSpeed) to scale the effects of the time to either speed up or slow down rate of distortions in the base texture
- a float (alpha) to set the transparency amount
The new version of the fragment shader code looks like this:
<script id="fragmentShader" type="x-shader/x-vertex">
uniform sampler2D baseTexture;
uniform float baseSpeed;
uniform sampler2D noiseTexture;
uniform float noiseScale;
uniform float alpha;
uniform float time;
varying vec2 vUv;
void main()
{
vec2 uvTimeShift = vUv + vec2( -0.7, 1.5 ) * time * baseSpeed;
vec4 noise = texture2D( noiseTexture, uvTimeShift );
vec2 uvNoisyTimeShift = vUv + noiseScale * vec2( noise.r, noise.g );
vec4 baseColor = texture2D( baseTexture, uvNoisyTimeShift );
baseColor.a = alpha;
gl_FragColor = baseColor;
}
</script>
The new code to create this shader in Three.js:
// initialize a global clock to keep track of time
this.clock = new THREE.Clock();
var noiseTexture = new THREE.ImageUtils.loadTexture( 'images/noise.jpg' );
noiseTexture.wrapS = noiseTexture.wrapT = THREE.RepeatWrapping;
var lavaTexture = new THREE.ImageUtils.loadTexture( 'images/lava.jpg' );
lavaTexture.wrapS = lavaTexture.wrapT = THREE.RepeatWrapping;
// create global uniforms object so its accessible in update method
this.customUniforms = {
baseTexture: { type: "t", value: lavaTexture },
baseSpeed: { type: "f", value: 0.05 },
noiseTexture: { type: "t", value: noiseTexture },
noiseScale: { type: "f", value: 0.5337 },
alpha: { type: "f", value: 1.0 },
time: { type: "f", value: 1.0 }
}
// create custom material from the shader code above within specially labeled script tags
var customMaterial = new THREE.ShaderMaterial(
{
uniforms: customUniforms,
vertexShader: document.getElementById( 'vertexShader' ).textContent,
fragmentShader: document.getElementById( 'fragmentShader' ).textContent
} );
and in the update or render method, don't forget to update the time using something like:
var delta = clock.getDelta(); customUniforms.time.value += delta;
That's about it -- for a live example, check out the demo in my GitHub collection, which uses this shader to create an animated lava-style texture and animated water-style texture.
Happy coding!

