Before we move into 3D. We want to make sure that when we’re calculating the uv value to sample a cell from our 3D texture that the uv is correct. Try clicking and dragging the mouse up and down the CodePen below. The number at the top right is the cell selected from the 3D texture we saw in the previous installment.

We add some event listeners to set the uniform, slice, based on the mouse y position. I’m assuming you’re comfortable with JavaScript. If not I do have a FREE Udemy course that might help.

let pointerDown = false;

function setSlice( x ){
  slice.value = ~~( ((window.innerHeight - x) / window.innerHeight) * slices ); 
  const elm = document.getElementById('slice');
  elm.innerHTML = slice.value; 
}

window.addEventListener( 'pointerdown', () => { 
  pointerDown = true;
  setSlice( event.pageY );
} );
window.addEventListener( 'pointerup', () => { pointerDown = false; } );
window.addEventListener( 'pointermove', ( event ) => { 
  if ( pointerDown) setSlice( event.pageY );
} );

Via the pointer event listeners we have a slice uniform that takes a value between 0 and 127. The column where we’ll find the cell to use is the remainder of slice divided by cellsX and the row is slice divided by cellsX. That means the bottom left corner of the cell to use, in uv terms, is ( col/cellsX, row/cellsY ). uv for any texture is 0, 0 at the bottom-left and 1, 1 at the top-right.

In the image below the highlighted cell is row 4, column 3. Remember 0,0 is bottom-left. So for the blue dot, uv origin is (3/16, 4/8) = ( 0.1875, 0.5). And the slice index is 4 * cellsX + 3 = 4 * 16 + 3 = 67.

We want to use the content of the highlighted cell when rendering the Plane mesh. The Plane mesh has uv values of 0,0 at the bottom left and 1,1 at the top right. Our fragment shader needs to take these values and convert them into values we can use to sample a texel from the highlighted cell if the slice value is 67. To convert from the Plane uv space to the storageTexture we divided the uv value by cellsX, cellsY and add the calculated origin.

Suppose the uv value is 0.5, 0.5 and slice is 67. We want sample the texel in the middle of the highlighted cell. That is 3.5/16, 4.5/8 = 0.21875, 0.5625. If we take the uv value, 0.5, 0.5 and divide it by cellsX, cellsY, we get 0.5/16, 0.5/8 = 0.03125, 0.0625. We saw that our code gives an origin value of 0.1875, 0.5. If we add the divided uv value and the calculated origin we get 0.1875 + 0.03125, 0.5 + 0.0625 = 0.21875, 0.5625 as desired. Now we can sample the texel as we saw in an earlier instalment.

const fragTSL = Fn(({ storageTexture }) => {
    const slices = cellsX * cellsY;

    const col = slice.modInt(cellsX).toVar();
    const row = uint(slice.div(cellsX)).toVar();
    const origin = vec2(float(col).div(cellsX), float(row).div(cellsY));

    const uv1 = vec2(uv()).div(vec2(cellsX, cellsY)).toVar();
    uv1.addAssign(origin);

    const texel = texture(storageTexture, uv1, 0).toVar();
    const perlinWorley = texel.x.toVar();
    const worley = texel.yzw.toVar();
    
    // worley fbms with different frequencies
    const wfbm = worley.x
      .mul(0.625)
      .add(worley.y.mul(0.125))
      .add(worley.z.mul(0.25))
      .toVar();

    // cloud shape modeled after the GPU Pro 7 chapter
    const cloud = remap(perlinWorley, wfbm.sub(1), 1, 0, 1).toVar();
    cloud.assign(saturate(remap(cloud, 0.85, 1, 0, 1))); // fake cloud coverage

    return vec4(1, 1, 1, cloud);
});

We’re ready to do the raymarching. Which we’ll do in the final part of this clouds section. Coming soon.

If you find these articles useful, perhaps you can buy me a coffee by clicking the button below. It might encourage me to write more.

Signup to my mailing list.