diff --git a/apps/typegpu-docs/src/examples/simulation/slime-mold-3d/index.ts b/apps/typegpu-docs/src/examples/simulation/slime-mold-3d/index.ts index 2a6e2cc00..2bd367117 100644 --- a/apps/typegpu-docs/src/examples/simulation/slime-mold-3d/index.ts +++ b/apps/typegpu-docs/src/examples/simulation/slime-mold-3d/index.ts @@ -6,9 +6,7 @@ import { randf } from '@typegpu/noise'; import * as m from 'wgpu-matrix'; const root = await tgpu.init({ - device: { - optionalFeatures: ['float32-filterable'], - }, + device: { optionalFeatures: ['float32-filterable'] }, }); const canFilter = root.enabledFeatures.has('float32-filterable'); const device = root.device; @@ -32,8 +30,8 @@ const CAMERA_FOV_DEGREES = 60; const CAMERA_DISTANCE_MULTIPLIER = 1.5; const CAMERA_INITIAL_ANGLE = Math.PI / 4; -const RAYMARCH_STEPS = 128; -const DENSITY_MULTIPLIER = 0.05; +const RAYMARCH_STEPS = 48; +const DENSITY_MULTIPLIER = 0.1; const RANDOM_DIRECTION_WEIGHT = 0.3; const CENTER_BIAS_WEIGHT = 0.7; @@ -105,15 +103,19 @@ const Params = d.struct({ evaporationRate: d.f32, }); -const agentsData = root.createMutable(d.arrayOf(Agent, NUM_AGENTS)); +const agentsDataBuffers = [0, 1].map(() => + root.createBuffer(d.arrayOf(Agent, NUM_AGENTS)).$usage('storage') +); +const mutableAgentsDataBuffers = agentsDataBuffers.map((b) => b.as('mutable')); root['~unstable'].createGuardedComputePipeline((x) => { 'use gpu'; randf.seed(x / NUM_AGENTS); const pos = randf.inUnitSphere().mul(resolution.x / 4).add(resolution.div(2)); const center = resolution.div(2); const dir = std.normalize(center.sub(pos)); - agentsData.$[x] = Agent({ position: pos, direction: dir }); + mutableAgentsDataBuffers[0].$[x] = Agent({ position: pos, direction: dir }); + mutableAgentsDataBuffers[1].$[x] = Agent({ position: pos, direction: dir }); }).dispatchThreads(NUM_AGENTS); const params = root.createUniform(Params, { @@ -136,8 +138,16 @@ const textures = [0, 1].map(() => ); const computeLayout = tgpu.bindGroupLayout({ + oldAgents: { storage: d.arrayOf(Agent), access: 'readonly' }, oldState: { storageTexture: d.textureStorage3d('r32float', 'read-only') }, + newAgents: { storage: d.arrayOf(Agent), access: 'mutable' }, + newState: { storageTexture: d.textureStorage3d('r32float', 'write-only') }, +}); + +const blurLayout = tgpu.bindGroupLayout({ + oldState: { texture: d.texture3d() }, newState: { storageTexture: d.textureStorage3d('r32float', 'write-only') }, + sampler: { sampler: 'filtering' }, }); const renderLayout = tgpu.bindGroupLayout({ @@ -223,24 +233,18 @@ const updateAgents = tgpu['~unstable'].computeFn({ const dims = std.textureDimensions(computeLayout.$.oldState); const dimsf = d.vec3f(dims); - const agent = agentsData.$[gid.x]; - const random = randf.sample(); + const agent = computeLayout.$.oldAgents[gid.x]; let direction = std.normalize(agent.direction); const senseResult = sense3D(agent.position, direction); - - if (senseResult.totalWeight > 0.01) { - const targetDir = std.normalize(senseResult.weightedDir); - direction = std.normalize( - direction.add(targetDir.mul(params.$.turnSpeed * params.$.deltaTime)), - ); - } else { - const perp = getPerpendicular(direction); - const randomOffset = perp.mul( - (random * 2 - 1) * params.$.turnSpeed * params.$.deltaTime, - ); - direction = std.normalize(direction.add(randomOffset)); - } + const targetDirection = std.select( + randf.onHemisphere(direction), + std.normalize(senseResult.weightedDir), + senseResult.totalWeight > 0.01, + ); + direction = std.normalize(direction.add( + targetDirection.mul(params.$.turnSpeed * params.$.deltaTime), + )); const newPos = agent.position.add( direction.mul(params.$.moveSpeed * params.$.deltaTime), @@ -292,7 +296,7 @@ const updateAgents = tgpu['~unstable'].computeFn({ ); } - agentsData.$[gid.x] = Agent({ + computeLayout.$.newAgents[gid.x] = Agent({ position: newPos, direction, }); @@ -306,52 +310,47 @@ const updateAgents = tgpu['~unstable'].computeFn({ ); }); +const sampler = root['~unstable'].createSampler({ + magFilter: canFilter ? 'linear' : 'nearest', + minFilter: canFilter ? 'linear' : 'nearest', +}); + +const getSummand = tgpu.fn([d.vec3f, d.vec3f], d.f32)((uv, offset) => + std.textureSampleLevel( + blurLayout.$.oldState, + blurLayout.$.sampler, + uv.add(offset), + 0, + ).x +); + const blur = tgpu['~unstable'].computeFn({ in: { gid: d.builtin.globalInvocationId }, workgroupSize: BLUR_WORKGROUP_SIZE, })(({ gid }) => { - const dims = std.textureDimensions(computeLayout.$.oldState); + const dims = d.vec3u(std.textureDimensions(blurLayout.$.oldState)); if (gid.x >= dims.x || gid.y >= dims.y || gid.z >= dims.z) return; + const uv = d.vec3f(gid).add(0.5).div(d.vec3f(dims)); + let sum = d.f32(); - let count = d.f32(); - - for (let offsetZ = -1; offsetZ <= 1; offsetZ++) { - for (let offsetY = -1; offsetY <= 1; offsetY++) { - for (let offsetX = -1; offsetX <= 1; offsetX++) { - const samplePos = d.vec3i(gid.xyz).add( - d.vec3i(offsetX, offsetY, offsetZ), - ); - const dimsi = d.vec3i(dims); - - if ( - samplePos.x >= 0 && samplePos.x < dimsi.x && - samplePos.y >= 0 && samplePos.y < dimsi.y && - samplePos.z >= 0 && samplePos.z < dimsi.z - ) { - const value = - std.textureLoad(computeLayout.$.oldState, d.vec3u(samplePos)).x; - sum = sum + value; - count = count + 1; - } - } - } - } - const blurred = sum / count; + sum += getSummand(uv, d.vec3f(-1, 0, 0).div(d.vec3f(dims))); + sum += getSummand(uv, d.vec3f(1, 0, 0).div(d.vec3f(dims))); + sum += getSummand(uv, d.vec3f(0, -1, 0).div(d.vec3f(dims))); + sum += getSummand(uv, d.vec3f(0, 1, 0).div(d.vec3f(dims))); + sum += getSummand(uv, d.vec3f(0, 0, -1).div(d.vec3f(dims))); + sum += getSummand(uv, d.vec3f(0, 0, 1).div(d.vec3f(dims))); + + const blurred = sum / 6.0; const newValue = std.saturate(blurred - params.$.evaporationRate); std.textureStore( - computeLayout.$.newState, + blurLayout.$.newState, gid.xyz, d.vec4f(newValue, 0, 0, 1), ); }); -const sampler = root['~unstable'].createSampler({ - magFilter: canFilter ? 'linear' : 'nearest', - minFilter: canFilter ? 'linear' : 'nearest', -}); - // Ray-box intersection const rayBoxIntersection = ( rayOrigin: d.v3f, @@ -375,6 +374,7 @@ const fragmentShader = tgpu['~unstable'].fragmentFn({ in: { uv: d.vec2f }, out: d.vec4f, })(({ uv }) => { + randf.seed2(uv); const ndc = d.vec2f(uv.x * 2 - 1, 1 - uv.y * 2); const ndcNear = d.vec4f(ndc, -1, 1); const ndcFar = d.vec4f(ndc, 1, 1); @@ -393,11 +393,23 @@ const fragmentShader = tgpu['~unstable'].fragmentFn({ return d.vec4f(); } - // March params - const tStart = std.max(isect.tNear, 0); + const jitter = randf.sample() * 20; + const tStart = std.max(isect.tNear + jitter, jitter); const tEnd = isect.tFar; - const numSteps = RAYMARCH_STEPS; - const stepSize = (tEnd - tStart) / numSteps; + + const intersectionLength = tEnd - tStart; + const baseStepsPerUnit = d.f32(0.3); + const minSteps = d.i32(8); + const maxSteps = d.i32(RAYMARCH_STEPS); + + const adaptiveSteps = std.clamp( + d.i32(intersectionLength * baseStepsPerUnit), + minSteps, + maxSteps, + ); + + const numSteps = adaptiveSteps; + const stepSize = intersectionLength / d.f32(numSteps); const thresholdLo = d.f32(0.06); const thresholdHi = d.f32(0.25); @@ -411,11 +423,8 @@ const fragmentShader = tgpu['~unstable'].fragmentFn({ const TMin = d.f32(1e-3); - for (let i = 0; i < numSteps; i++) { - if (transmittance <= TMin) { - break; - } - + let i = d.i32(0); + while (i < numSteps && transmittance > TMin) { const t = tStart + (d.f32(i) + 0.5) * stepSize; const pos = rayOrigin.add(rayDir.mul(t)); const texCoord = pos.div(resolution); @@ -433,6 +442,8 @@ const fragmentShader = tgpu['~unstable'].fragmentFn({ accum = accum.add(contrib.mul(transmittance)); transmittance = transmittance * (1 - alphaSrc); + + i += 1; } const alpha = 1 - transmittance; @@ -454,8 +465,18 @@ const blurPipeline = root['~unstable'] const bindGroups = [0, 1].map((i) => root.createBindGroup(computeLayout, { + oldAgents: agentsDataBuffers[i], + oldState: textures[i], + newAgents: agentsDataBuffers[1 - i], + newState: textures[1 - i], + }) +); + +const blurBindGroups = [0, 1].map((i) => + root.createBindGroup(blurLayout, { oldState: textures[i], newState: textures[1 - i], + sampler: sampler, }) ); @@ -476,7 +497,7 @@ function frame() { params.writePartial({ deltaTime }); blurPipeline - .with(bindGroups[currentTexture]) + .with(blurBindGroups[currentTexture]) .dispatchWorkgroups( Math.ceil(resolution.x / BLUR_WORKGROUP_SIZE[0]), Math.ceil(resolution.y / BLUR_WORKGROUP_SIZE[1]), diff --git a/packages/typegpu/tests/examples/individual/slime-mold-3d.test.ts b/packages/typegpu/tests/examples/individual/slime-mold-3d.test.ts index b01863174..a0a948017 100644 --- a/packages/typegpu/tests/examples/individual/slime-mold-3d.test.ts +++ b/packages/typegpu/tests/examples/individual/slime-mold-3d.test.ts @@ -59,30 +59,39 @@ describe('slime mold 3d example', () => { direction: vec3f, } - @group(0) @binding(1) var agentsData_10: array; + @group(0) @binding(1) var item_10: array; + + @group(0) @binding(2) var item_12: array; fn wrappedCallback_2(x: u32, _arg_1: u32, _arg_2: u32) { randSeed_3((f32(x) / 8e+5f)); var pos = ((randInUnitSphere_6() * 64.) + vec3f(128)); var center = vec3f(128); var dir = normalize((center - pos)); - agentsData_10[x] = Agent_11(pos, dir); + item_10[x] = Agent_11(pos, dir); + item_12[x] = Agent_11(pos, dir); } - struct mainCompute_Input_12 { + struct mainCompute_Input_13 { @builtin(global_invocation_id) id: vec3u, } - @compute @workgroup_size(256, 1, 1) fn mainCompute_0(in: mainCompute_Input_12) { + @compute @workgroup_size(256, 1, 1) fn mainCompute_0(in: mainCompute_Input_13) { if (any(in.id >= sizeUniform_1)) { return; } wrappedCallback_2(in.id.x, in.id.y, in.id.z); } - @group(1) @binding(0) var oldState_1: texture_storage_3d; + @group(1) @binding(0) var oldState_1: texture_3d; + + @group(1) @binding(2) var sampler_3: sampler; + + fn getSummand_2(uv: vec3f, offset: vec3f) -> f32 { + return textureSampleLevel(oldState_1, sampler_3, (uv + offset), 0).x; + } - struct Params_3 { + struct Params_5 { deltaTime: f32, moveSpeed: f32, sensorAngle: f32, @@ -91,37 +100,30 @@ describe('slime mold 3d example', () => { evaporationRate: f32, } - @group(0) @binding(0) var params_2: Params_3; + @group(0) @binding(0) var params_4: Params_5; - @group(1) @binding(1) var newState_4: texture_storage_3d; + @group(1) @binding(1) var newState_6: texture_storage_3d; - struct blur_Input_5 { + struct blur_Input_7 { @builtin(global_invocation_id) gid: vec3u, } - @compute @workgroup_size(4, 4, 4) fn blur_0(_arg_0: blur_Input_5) { - var dims = textureDimensions(oldState_1); + @compute @workgroup_size(4, 4, 4) fn blur_0(_arg_0: blur_Input_7) { + var dims = vec3u(textureDimensions(oldState_1)); if ((((_arg_0.gid.x >= dims.x) || (_arg_0.gid.y >= dims.y)) || (_arg_0.gid.z >= dims.z))) { return; } + var uv = ((vec3f(_arg_0.gid) + 0.5) / vec3f(dims)); var sum = 0f; - var count = 0f; - for (var offsetZ = -1; (offsetZ <= 1i); offsetZ++) { - for (var offsetY = -1; (offsetY <= 1i); offsetY++) { - for (var offsetX = -1; (offsetX <= 1i); offsetX++) { - var samplePos = (vec3i(_arg_0.gid.xyz) + vec3i(offsetX, offsetY, offsetZ)); - var dimsi = vec3i(dims); - if (((((((samplePos.x >= 0i) && (samplePos.x < dimsi.x)) && (samplePos.y >= 0i)) && (samplePos.y < dimsi.y)) && (samplePos.z >= 0i)) && (samplePos.z < dimsi.z))) { - var value = textureLoad(oldState_1, vec3u(samplePos)).x; - sum = (sum + value); - count = (count + 1f); - } - } - } - } - var blurred = (sum / count); - var newValue = saturate((blurred - params_2.evaporationRate)); - textureStore(newState_4, _arg_0.gid.xyz, vec4f(newValue, 0f, 0f, 1f)); + sum += getSummand_2(uv, (vec3f(-1, 0f, 0f) / vec3f(dims))); + sum += getSummand_2(uv, (vec3f(1, 0, 0) / vec3f(dims))); + sum += getSummand_2(uv, (vec3f(0f, -1, 0f) / vec3f(dims))); + sum += getSummand_2(uv, (vec3f(0, 1, 0) / vec3f(dims))); + sum += getSummand_2(uv, (vec3f(0f, 0f, -1) / vec3f(dims))); + sum += getSummand_2(uv, (vec3f(0, 0, 1) / vec3f(dims))); + var blurred = (sum / 6f); + var newValue = saturate((blurred - params_4.evaporationRate)); + textureStore(newState_6, _arg_0.gid.xyz, vec4f(newValue, 0f, 0f, 1f)); } var seed_3: vec2f; @@ -134,28 +136,16 @@ describe('slime mold 3d example', () => { seed_2(seed); } - @group(1) @binding(0) var oldState_4: texture_storage_3d; + @group(1) @binding(1) var oldState_4: texture_storage_3d; struct Agent_6 { position: vec3f, direction: vec3f, } - @group(0) @binding(0) var agentsData_5: array; + @group(1) @binding(0) var oldAgents_5: array; - fn item_8() -> f32 { - var a = dot(seed_3, vec2f(23.140779495239258, 232.6168975830078)); - var b = dot(seed_3, vec2f(54.47856521606445, 345.8415222167969)); - seed_3.x = fract((cos(a) * 136.8168f)); - seed_3.y = fract((cos(b) * 534.7645f)); - return seed_3.y; - } - - fn randFloat01_7() -> f32 { - return item_8(); - } - - fn getPerpendicular_10(dir: vec3f) -> vec3f { + fn getPerpendicular_8(dir: vec3f) -> vec3f { var axis = vec3f(1, 0, 0); var absX = abs(dir.x); var absY = abs(dir.y); @@ -171,7 +161,7 @@ describe('slime mold 3d example', () => { return normalize(cross(dir, axis)); } - struct Params_12 { + struct Params_10 { deltaTime: f32, moveSpeed: f32, sensorAngle: f32, @@ -180,84 +170,101 @@ describe('slime mold 3d example', () => { evaporationRate: f32, } - @group(0) @binding(1) var params_11: Params_12; + @group(0) @binding(0) var params_9: Params_10; - struct SenseResult_13 { + struct SenseResult_11 { weightedDir: vec3f, totalWeight: f32, } - fn sense3D_9(pos: vec3f, direction: vec3f) -> SenseResult_13 { + fn sense3D_7(pos: vec3f, direction: vec3f) -> SenseResult_11 { var dims = textureDimensions(oldState_4); var dimsf = vec3f(dims); var weightedDir = vec3f(); var totalWeight = 0f; - var perp1 = getPerpendicular_10(direction); + var perp1 = getPerpendicular_8(direction); var perp2 = cross(direction, perp1); var numSamples = 8; for (var i = 0; (i < numSamples); i++) { var theta = (((f32(i) / f32(numSamples)) * 2f) * 3.141592653589793f); var coneOffset = ((perp1 * cos(theta)) + (perp2 * sin(theta))); - var sensorDir = normalize((direction + (coneOffset * sin(params_11.sensorAngle)))); - var sensorPos = (pos + (sensorDir * params_11.sensorDistance)); + var sensorDir = normalize((direction + (coneOffset * sin(params_9.sensorAngle)))); + var sensorPos = (pos + (sensorDir * params_9.sensorDistance)); var sensorPosInt = vec3u(clamp(sensorPos, vec3f(), (dimsf - vec3f(1)))); var weight = textureLoad(oldState_4, sensorPosInt).x; weightedDir = (weightedDir + (sensorDir * weight)); totalWeight = (totalWeight + weight); } - return SenseResult_13(weightedDir, totalWeight); + return SenseResult_11(weightedDir, totalWeight); + } + + fn item_14() -> f32 { + var a = dot(seed_3, vec2f(23.140779495239258, 232.6168975830078)); + var b = dot(seed_3, vec2f(54.47856521606445, 345.8415222167969)); + seed_3.x = fract((cos(a) * 136.8168f)); + seed_3.y = fract((cos(b) * 534.7645f)); + return seed_3.y; + } + + fn randOnUnitSphere_13() -> vec3f { + var z = ((2f * item_14()) - 1f); + var oneMinusZSq = sqrt((1f - (z * z))); + var theta = (6.283185307179586f * item_14()); + var x = (cos(theta) * oneMinusZSq); + var y = (sin(theta) * oneMinusZSq); + return vec3f(x, y, z); } - fn randUniformExclusive_17() -> f32 { - return ((item_8() * 0.9999998f) + 1e-7f); + fn randOnUnitHemisphere_12(normal: vec3f) -> vec3f { + var value = randOnUnitSphere_13(); + var alignment = dot(normal, value); + return (sign(alignment) * value); } - fn randNormal_16(mu: f32, sigma: f32) -> f32 { - var theta = (6.283185307179586f * randUniformExclusive_17()); - var R = sqrt((-2 * log(randUniformExclusive_17()))); + fn randUniformExclusive_18() -> f32 { + return ((item_14() * 0.9999998f) + 1e-7f); + } + + fn randNormal_17(mu: f32, sigma: f32) -> f32 { + var theta = (6.283185307179586f * randUniformExclusive_18()); + var R = sqrt((-2 * log(randUniformExclusive_18()))); return (((R * sin(theta)) * sigma) + mu); } - fn randInUnitSphere_15() -> vec3f { - var u = item_8(); - var v = vec3f(randNormal_16(0f, 1f), randNormal_16(0f, 1f), randNormal_16(0f, 1f)); + fn randInUnitSphere_16() -> vec3f { + var u = item_14(); + var v = vec3f(randNormal_17(0f, 1f), randNormal_17(0f, 1f), randNormal_17(0f, 1f)); var vNorm = normalize(v); return (vNorm * pow(u, 0.33f)); } - fn randInUnitHemisphere_14(normal: vec3f) -> vec3f { - var value = randInUnitSphere_15(); + fn randInUnitHemisphere_15(normal: vec3f) -> vec3f { + var value = randInUnitSphere_16(); var alignment = dot(normal, value); return (sign(alignment) * value); } - @group(1) @binding(1) var newState_18: texture_storage_3d; + @group(1) @binding(2) var newAgents_19: array; + + @group(1) @binding(3) var newState_20: texture_storage_3d; - struct updateAgents_Input_19 { + struct updateAgents_Input_21 { @builtin(global_invocation_id) gid: vec3u, } - @compute @workgroup_size(64) fn updateAgents_0(_arg_0: updateAgents_Input_19) { + @compute @workgroup_size(64) fn updateAgents_0(_arg_0: updateAgents_Input_21) { if ((_arg_0.gid.x >= 800000u)) { return; } randSeed_1(((f32(_arg_0.gid.x) / 8e+5f) + 0.1f)); var dims = textureDimensions(oldState_4); var dimsf = vec3f(dims); - var agent = agentsData_5[_arg_0.gid.x]; - var random = randFloat01_7(); + var agent = oldAgents_5[_arg_0.gid.x]; var direction = normalize(agent.direction); - var senseResult = sense3D_9(agent.position, direction); - if ((senseResult.totalWeight > 0.01f)) { - var targetDir = normalize(senseResult.weightedDir); - direction = normalize((direction + (targetDir * (params_11.turnSpeed * params_11.deltaTime)))); - } - else { - var perp = getPerpendicular_10(direction); - var randomOffset = (perp * ((((random * 2f) - 1f) * params_11.turnSpeed) * params_11.deltaTime)); - direction = normalize((direction + randomOffset)); - } - var newPos = (agent.position + (direction * (params_11.moveSpeed * params_11.deltaTime))); + var senseResult = sense3D_7(agent.position, direction); + var targetDirection = select(randOnUnitHemisphere_12(direction), normalize(senseResult.weightedDir), (senseResult.totalWeight > 0.01f)); + direction = normalize((direction + (targetDirection * (params_9.turnSpeed * params_9.deltaTime)))); + var newPos = (agent.position + (direction * (params_9.moveSpeed * params_9.deltaTime))); var center = (dimsf / 2); if (((newPos.x < 0f) || (newPos.x >= dimsf.x))) { newPos.x = clamp(newPos.x, 0f, (dimsf.x - 1f)); @@ -265,7 +272,7 @@ describe('slime mold 3d example', () => { if ((newPos.x > 1f)) { normal = vec3f(-1, 0f, 0f); } - var randomDir = randInUnitHemisphere_14(normal); + var randomDir = randInUnitHemisphere_15(normal); var toCenter = normalize((center - newPos)); direction = normalize(((randomDir * 0.3) + (toCenter * 0.7))); } @@ -275,7 +282,7 @@ describe('slime mold 3d example', () => { if ((newPos.y > 1f)) { normal = vec3f(0f, -1, 0f); } - var randomDir = randInUnitHemisphere_14(normal); + var randomDir = randInUnitHemisphere_15(normal); var toCenter = normalize((center - newPos)); direction = normalize(((randomDir * 0.3) + (toCenter * 0.7))); } @@ -285,14 +292,14 @@ describe('slime mold 3d example', () => { if ((newPos.z > 1f)) { normal = vec3f(0f, 0f, -1); } - var randomDir = randInUnitHemisphere_14(normal); + var randomDir = randInUnitHemisphere_15(normal); var toCenter = normalize((center - newPos)); direction = normalize(((randomDir * 0.3) + (toCenter * 0.7))); } - agentsData_5[_arg_0.gid.x] = Agent_6(newPos, direction); + newAgents_19[_arg_0.gid.x] = Agent_6(newPos, direction); var oldState = textureLoad(oldState_4, vec3u(newPos)).x; var newState = (oldState + 1f); - textureStore(newState_18, vec3u(newPos), vec4f(newState, 0f, 0f, 1f)); + textureStore(newState_20, vec3u(newPos), vec4f(newState, 0f, 0f, 1f)); } struct fullScreenTriangle_Input_1 { @@ -311,21 +318,31 @@ describe('slime mold 3d example', () => { return fullScreenTriangle_Output_2(vec4f(pos[in.vertexIndex], 0, 1), uv[in.vertexIndex]); } - struct Camera_5 { + var seed_6: vec2f; + + fn seed2_5(value: vec2f) { + seed_6 = value; + } + + fn randSeed2_4(seed: vec2f) { + seed2_5(seed); + } + + struct Camera_8 { viewProj: mat4x4f, invViewProj: mat4x4f, position: vec3f, } - @group(0) @binding(0) var cameraData_4: Camera_5; + @group(0) @binding(0) var cameraData_7: Camera_8; - struct RayBoxResult_7 { + struct RayBoxResult_10 { tNear: f32, tFar: f32, hit: bool, } - fn rayBoxIntersection_6(rayOrigin: vec3f, rayDir: vec3f, boxMin: vec3f, boxMax: vec3f) -> RayBoxResult_7 { + fn rayBoxIntersection_9(rayOrigin: vec3f, rayDir: vec3f, boxMin: vec3f, boxMax: vec3f) -> RayBoxResult_10 { var invDir = (vec3f(1) / rayDir); var t0 = ((boxMin - rayOrigin) * invDir); var t1 = ((boxMax - rayOrigin) * invDir); @@ -334,58 +351,76 @@ describe('slime mold 3d example', () => { var tNear = max(max(tmin.x, tmin.y), tmin.z); var tFar = min(min(tmax.x, tmax.y), tmax.z); var hit = ((tFar >= tNear) && (tFar >= 0f)); - return RayBoxResult_7(tNear, tFar, hit); + return RayBoxResult_10(tNear, tFar, hit); } - @group(1) @binding(0) var state_8: texture_3d; + fn item_12() -> f32 { + var a = dot(seed_6, vec2f(23.140779495239258, 232.6168975830078)); + var b = dot(seed_6, vec2f(54.47856521606445, 345.8415222167969)); + seed_6.x = fract((cos(a) * 136.8168f)); + seed_6.y = fract((cos(b) * 534.7645f)); + return seed_6.y; + } + + fn randFloat01_11() -> f32 { + return item_12(); + } - @group(0) @binding(1) var sampler_9: sampler; + @group(1) @binding(0) var state_13: texture_3d; - struct fragmentShader_Input_10 { + @group(0) @binding(1) var sampler_14: sampler; + + struct fragmentShader_Input_15 { @location(0) uv: vec2f, } - @fragment fn fragmentShader_3(_arg_0: fragmentShader_Input_10) -> @location(0) vec4f { + @fragment fn fragmentShader_3(_arg_0: fragmentShader_Input_15) -> @location(0) vec4f { + randSeed2_4(_arg_0.uv); var ndc = vec2f(((_arg_0.uv.x * 2f) - 1f), (1f - (_arg_0.uv.y * 2f))); var ndcNear = vec4f(ndc, -1, 1f); var ndcFar = vec4f(ndc, 1f, 1f); - var worldNear = (cameraData_4.invViewProj * ndcNear); - var worldFar = (cameraData_4.invViewProj * ndcFar); + var worldNear = (cameraData_7.invViewProj * ndcNear); + var worldFar = (cameraData_7.invViewProj * ndcFar); var rayOrigin = (worldNear.xyz / worldNear.w); var rayEnd = (worldFar.xyz / worldFar.w); var rayDir = normalize((rayEnd - rayOrigin)); var boxMin = vec3f(); var boxMax = vec3f(256); - var isect = rayBoxIntersection_6(rayOrigin, rayDir, boxMin, boxMax); + var isect = rayBoxIntersection_9(rayOrigin, rayDir, boxMin, boxMax); if (!isect.hit) { return vec4f(); } - var tStart = max(isect.tNear, 0f); + var jitter = (randFloat01_11() * 20f); + var tStart = max((isect.tNear + jitter), jitter); var tEnd = isect.tFar; - var numSteps = 128; - var stepSize = ((tEnd - tStart) / f32(numSteps)); + var intersectionLength = (tEnd - tStart); + var baseStepsPerUnit = 0.30000001192092896f; + var minSteps = 8i; + var maxSteps = 48i; + var adaptiveSteps = clamp(i32((intersectionLength * baseStepsPerUnit)), minSteps, maxSteps); + var numSteps = adaptiveSteps; + var stepSize = (intersectionLength / f32(numSteps)); var thresholdLo = 0.05999999865889549f; var thresholdHi = 0.25f; var gamma = 1.399999976158142f; - var sigmaT = 0.05000000074505806f; + var sigmaT = 0.10000000149011612f; var albedo = vec3f(0.5699999928474426, 0.4399999976158142, 0.9599999785423279); var transmittance = 1f; var accum = vec3f(); var TMin = 0.0010000000474974513f; - for (var i = 0; (i < numSteps); i++) { - if ((transmittance <= TMin)) { - break; - } + var i = 0i; + while (((i < numSteps) && (transmittance > TMin))) { var t = (tStart + ((f32(i) + 0.5f) * stepSize)); var pos = (rayOrigin + (rayDir * t)); var texCoord = (pos / vec3f(256)); - var sampleValue = textureSampleLevel(state_8, sampler_9, texCoord, 0).x; + var sampleValue = textureSampleLevel(state_13, sampler_14, texCoord, 0).x; var d0 = smoothstep(thresholdLo, thresholdHi, sampleValue); var density = pow(d0, gamma); var alphaSrc = (1f - exp(((-sigmaT * density) * stepSize))); var contrib = (albedo * alphaSrc); accum = (accum + (contrib * transmittance)); transmittance = (transmittance * (1f - alphaSrc)); + i += 1i; } var alpha = (1f - transmittance); return vec4f(accum, alpha);