In this lesson we’re going to move the vertices over time. The CodePen below is what we’re creating. Try adjusting the strength, scale and speed controls.

  • strength – controls the blend between the displaced vertex and the original location of the modelled vertex.
  • scale – controls the scale of the noise used for the displacment.
  • speed – controls the rate of change of the displacement.

Now press the JS button.

Notice first the list of tsl imports. Then slide down to the tsl function.

const vNoise = varying(float());
const vPosition = varying(vec3());

Notice vNoise and vPosition are declared as varyings. If you’re new to shaders then a varying will be unfamiliar. Shaders divide the job of rendering a surface into two tasks. First they move the vertices from modelled space to homogeneous clip space. For a vertex to be within the screen window each components xyz location once transformed to homogeneous clip space needs to be in the range -1 to 1. This stage is called the vertex shader.

Having transformed the vertex position the second task is to set the colour for a pixel. This stage is the fragment shader. You can pass data between the vertex and the fragment shader, this type of data in GLSL 1.0 is called a varying, because the value in the fragment shader is an interpolated version of the values of the three vertices of a triangle based on the pixel (fragment) within the triangle. TSL supports varyings if a variable is an instance of a varying.

Recall that we’re trying to reproduce a lava ball, (see the previous lesson). This used a custom GLSL function, turbulence. We need a way of reproducing this. Here’s a remix of the ThreeJS TSL Transpiler . It converts GLSL into TSL. This is a useful and quick way to get the TSL code. pnoise in the GLSL version is a Perlin noise function. The ThreeJS library comes with just such a function mx_noise_float that is an import for the CodePen example above.

Notice the line that includes void main(). This is the original code for the GLSL vertex shader. My attempt at reproducing this is the posFunc Fn function.

const posFunc = Fn(() => {
  vNoise.assign( 
    turbulenceFunc( 
      normalLocal.mul( 0.5 ).add( time )
    )
  );
  ...

First we assign the vNoise varying, using the turbulence function. It takes a modified normalLocal as the input.

...
const b = mx_noise_float(
    positionLocal.mul( noiseScale )
      .add( 
        vec3(2).mul( 
          time.mul( noiseSpeed ) 
        )
      )
    );
    
  const displacement = vNoise.add(b);

  vPosition.assign(positionLocal);
  ...

Than we create the b value, again using mx_noise_float. This takes a modified positionLocal as the input. Now we’re able to create the displacement value. We assign the vPosition value to be positionLocal. This will be used in the next lesson.

...
const pos = positionLocal.add(
    normalLocal.mul(displacement)
  ).toVar();

  vNoise.assign(vNoise.mul(noiseStrength));

  return mix(positionLocal, pos, noiseStrength);
})

pos is set to positionLocal plus normalLocal multiplied by the calculated displacement value. Finally we adjust the value of vNoise scaling it by the noiseStrength uniform and return a blend of the newly calculated position and the original position based again on the noiseStrength uniform.

Now we have the posFunc function we assign this as material.positionNode.

In the next lesson we’ll update the colours used for the rendered object.