So far in our efforts to create a lava ball we have a blobby ball. But the colours used when rendering use the default fragment shader for MeshStandardNodeMaterial. Time to update the fragment shader.

It’s not identical to the original but it did help my understanding of TSL. Click the JS button to see the code changes. Notice we load a texture.

const tex = new THREE.TextureLoader()
    .setPath("https://s3-us-west-2.amazonaws.com/s.cdpn.io/2666677/")
    .load("explosion.png");

The texture is a strip of colours. The fragment shader simply picks a texel by setting a uv value for sampling. The u component is always 0, but the v can be anywhere up and down the texture.

Notice the options object used by the gui now has range, offset, amplitude and pivot values. These will be used to tweak the uv value, via four uniforms uvRange, uvOffset, uvAmplitude and uvPivot.

Which brings us to the fragFunc code. Again we use the Perlin noise function mx_noise_float. This can take three parameters. A vec3, here we use the varying vPosition, this will be the interpolated positionLocal value. We multiply it, add time multiplied by 2. Then multiply again by 2. The second parameter is the amplitude, the first parameter is multiplied by this. We use the uniform uvAmplitude so we can adjust this value with the gui. The third parameter is the pivot, this is added to the scaled value after the multiplication by uvAmplitude. Again we use a uniform, uvPivot, so we can tweak it using the gui.

You may wonder why the toVar method. This ensures that the compiled code on the GPU uses a variable. Otherwise inline code is added each time. Without using toVar()

k = pow(abs(mul(a,10)),5);
v = vec2(sin(k),cos(k));

Is the same as.

k = pow(abs(mul(a,10)),5);
v = vec2(sin(pow(abs(mul(a,10)),5)),cos(pow(abs(mul(a,10)),5)));

The value of k is calculated but ignored and the code that created the value k is substituted. Resulting in the value of k being calculated three times.

Pavel Boytchev explains this in an excellent Q&A, which provides a guide to what he learnt as he taught himself TSL.

const fragFunc = Fn(() => {
    const r = mx_noise_float(
      vPosition.mul(2).add(
        time.mul(2)
      ).mul(2),
      uvAmplitude,
      uvPivot
    ).toVar();
    const uv0 = vec2(0,
      vNoise.mul(uvRange).add(
        uvOffset.add(r)
    )).toVar();
    return texture(tex, uv0);
  });

We assign the fragFunc to the fragmentNode. This differs from the colorNode which simply sets the diffuse colour. The fragmentNode replaces the fragment shader.

In the next lessons we will create a flock of birds.