So we can distribute the fluid dynamic calculations across the GPU threads, we use a double buffering approach for the density and velocity arrays.
We implement the diffusion, advect and curl mechanisms.
• Diffusion - Process by which molecules in a fluid move from regions of higher concentration to regions of lower concentration, resulting in a more uniform distribution. This movement occurs due to random thermal motion, leading to the gradual mixing of substances within the fluid. Acts as a mechanism for the redistribution of mass, energy, and momentum, playing a crucial role in phenomena such as mass transport, heat conduction, and the dissipation of vortices within fluid systems.
• Advect - The advect step is a computational process where the fluid properties, such as velocity, temperature, or concentration of a substance, are transported through the flow field according to the local velocity field. This step involves calculating the movement of fluid parcels over small time intervals, typically using numerical methods like finite difference or finite volume schemes. The advect step is essential for accurately simulating the advection of quantities within the fluid domain, capturing phenomena such as fluid mixing, dispersion, and transport of properties like heat or chemical species.
• Curl - The curl is a mathematical operation that describes the rotation or vorticity of a vector field. It quantifies the tendency of the field to circulate around a point or axis, indicating the local spinning motion within the field. The curl of a vector field is a vector quantity obtained by taking the cross product of the gradient operator with the vector field itself. It's particularly useful for understanding and analyzing the presence of vortices, eddies, and rotational flow patterns within a fluid, providing insights into the dynamics of fluid motion and turbulence.
Complete Code
let div = document.createElement('div'); document.body.appendChild( div ); div.style['font-size'] = '20pt'; function log( s ) { console.log( s ); let args = [...arguments].join(' '); div.innerHTML += args + '<br><br>'; }
log('WebGPU Compute Example');
if (!navigator.gpu) { log("WebGPU is not supported (or is it disabled? flags/settings)"); return; }
fn diffuseDensity( factor:f32, x:u32, y:u32 ) { let center = density0[index(x, y)]; let left = density0[index(max(x, 1u) - 1u, y)]; let right = density0[index(min(x + 1u, size - 1u), y)]; let up = density0[index(x, max(y, 1u) - 1u)]; let down = density0[index(x, min(y + 1u, size - 1u))]; density1[index(x,y)] = ( center + factor * (left + right + up + down - 4.0 * center) ); }
fn diffuseVelocity( factor:f32, x:u32, y:u32 ) { let center = velocity0[index(x, y)]; let left = velocity0[index(max(x, 1u) - 1u, y)]; let right = velocity0[index(min(x + 1u, size - 1u), y)]; let up = velocity0[index(x, max(y, 1u) - 1u)]; let down = velocity0[index(x, min(y + 1u, size - 1u))]; velocity1[index(x,y)] = ( center + factor * (left + right + up + down - 4.0 * center) ); }
@compute @workgroup_size(8, 8) fn diffuse_step(@builtin(global_invocation_id) id: vec3<u32>) { let x = id.x; let y = id.y; if (x >= size || y >= size) { return; }
diffuseDensity( diffusion, x, y );
diffuseVelocity( vdiff, x, y ); }
@compute @workgroup_size(8, 8) fn advect_step(@builtin(global_invocation_id) id: vec3<u32>) { let x = id.x; let y = id.y; if (x >= size || y >= size) { return; }
// Simple advection let uv = velocity0[index(x, y)]; let pos = vec2<f32>(f32(x), f32(y)) - dt * uv; let x0 = clamp(floor(pos.x), 0.0, f32(size - 1)); let x1 = clamp(ceil(pos.x), 0.0, f32(size - 1)); let y0 = clamp(floor(pos.y), 0.0, f32(size - 1)); let y1 = clamp(ceil(pos.y), 0.0, f32(size - 1)); let s1 = pos.x - x0; let s0 = 1.0 - s1; let t1 = pos.y - y0; let t0 = 1.0 - t1; let advectedDensity = s0 * (t0 * density0[index(u32(x0), u32(y0))] + t1 * density0[index(u32(x0), u32(y1))]) + s1 * (t0 * density0[index(u32(x1), u32(y0))] + t1 * density0[index(u32(x1), u32(y1))]); density1[index(x, y)] = advectedDensity; }
@compute @workgroup_size(8, 8) fn curl_step(@builtin(global_invocation_id) id: vec3<u32>) { let x = id.x; let y = id.y; if (x >= size || y >= size) { return; }
@compute @workgroup_size(8, 8) fn display(@builtin(global_invocation_id) globalId: vec3<u32>) { let x = globalId.x; let y = globalId.y; if (x >= imgWidth || y >= imgHeight) { return; }
let d = density0[index(x, y)]; var finalColor = vec4<f32>(d, d, d, 1.0); textureStore(myTexture1, vec2<i32>(globalId.xy), finalColor); }
`;
let shaderModule = device.createShaderModule({code:computeShader}); // Pipeline setup
var pipelines = {}; function addPipeline( pipelineName ) { let p = device.createComputePipeline({ layout : device.createPipelineLayout({bindGroupLayouts: [bindGroupLayout]}), compute: { module: shaderModule, entryPoint: pipelineName } }); pipelines[ pipelineName ] = p;
• Try other text and test patterns for the initial density
• Try different initial 'velocity' patterns
• Visualize the velocities information
• Use other colors for the density visualization (instead of black and white - use a color gradient such as blue to red)
• Add in other fluid characteristics (e.g., boyancy)