test 3D Bifurcation Diagram [uts-001C]
test 3D Bifurcation Diagram [uts-001C]
2025-06-06 UPDATE: Known to be broken for now. Errors: ``` Uncaught (in promise) JsValue(OperationError: Failed to execute 'requestDevice' on 'GPUAdapter': The limit "maxInterStageShaderComponents" with a non-undefined value is not recognized. OperationError: Failed to execute 'requestDevice' on 'GPUAdapter': The limit "maxInterStageShaderComponents" with a non-undefined value is not recognized. ```
Adapted from ⧉:
#storage atomic_storage array<atomic<i32>> const MaxSamples = 256.0; const FOV = 0.6; const PI = 3.14159265; const TWO_PI = 6.28318530718; const STEP = 0.01; const LARGENUM = 1e10; const ATOMIC_SCALE = 1024.0; struct Camera { pos: float3, cam: float3x3, fov: float, size: float2 } struct Ray { ro: float3, rd: float3, } var<private> camera : Camera; var<private> state : uint4; fn pcg4d(a: uint4) -> uint4 { var v = a * 1664525u + 1013904223u; v.x += v.y*v.w; v.y += v.z*v.x; v.z += v.x*v.y; v.w += v.y*v.z; v = v ^ ( v >> uint4(16u) ); v.x += v.y*v.w; v.y += v.z*v.x; v.z += v.x*v.y; v.w += v.y*v.z; return v; } fn rand4() -> float4 { state = pcg4d(state); return float4(state)/float(0xffffffffu); } fn nrand4(sigma: float, mean: float4) -> float4 { let Z = rand4(); return mean + sigma * sqrt(-2.0 * log(Z.xxyy)) * float4(cos(TWO_PI * Z.z),sin(TWO_PI * Z.z),cos(TWO_PI * Z.w),sin(TWO_PI * Z.w)); } fn udir(rng: float2) -> float3 { let r = float2(2.*PI*rng.x, acos(2.*rng.y - 1.0)); let c = cos(r); let s = sin(r); return float3(c.x*s.y, s.x*s.y, c.y); } fn disk(rng: float2) -> float2 { return float2(sin(TWO_PI*rng.x), cos(TWO_PI*rng.x))*sqrt(rng.y); } fn Rotate(t: float) -> float2x2 { return float2x2( cos(t), sin(t), - sin(t), cos(t), ); } fn RotXY(x: float3, t: float) -> float3 { return float3(Rotate(t)*x.xy, x.z); } fn GetCameraMatrix(ang: float2) -> float3x3 { let x_dir = float3(cos(ang.x)*sin(ang.y), cos(ang.y), sin(ang.x)*sin(ang.y)); let y_dir = normalize(cross(x_dir, float3(0.0,1.0,0.0))); let z_dir = normalize(cross(y_dir, x_dir)); return float3x3(-x_dir, y_dir, z_dir); } fn SetCamera() { let screen_size = int2(textureDimensions(screen)); let screen_size_f = float2(screen_size); let ang = float2(mouse.pos.xy)*float2(-TWO_PI, PI)/screen_size_f + float2(0.4, 0.4); camera.fov = FOV; camera.cam = GetCameraMatrix(ang); camera.pos = - (camera.cam*float3(8.0*custom.Radius+0.5,0.0,0.0)); camera.size = screen_size_f; } //project to clip space fn Project(cam: Camera, p: float3) -> float3 { let td = distance(cam.pos, p); let dir = (p - cam.pos)/td; let screen = dir*cam.cam; return float3(screen.yz*cam.size.y/(cam.fov*screen.x) + 0.5*cam.size,screen.x*td); } const max_iterations = 256; const color_thresholds = float4(255.0, 130.0, 80.0, 255.0); fn AdditiveBlend(color: float3, depth: float, index: int) { let scaledColor = int3(floor(ATOMIC_SCALE*color/(depth*depth + 0.2) + rand4().xyz)); if(scaledColor.x>0) { atomicAdd(&atomic_storage[index*4+0], scaledColor.x); } if(scaledColor.y>0) { atomicAdd(&atomic_storage[index*4+1], scaledColor.y); } if(scaledColor.z>0) { atomicAdd(&atomic_storage[index*4+2], scaledColor.z); } } fn RasterizePoint(pos: float3, color: float3) { let screen_size = int2(camera.size); let projectedPos = Project(camera, pos); let screenCoord = int2(projectedPos.xy+0.5*rand4().xy); //outside of our view if(screenCoord.x < 0 || screenCoord.x >= screen_size.x || screenCoord.y < 0 || screenCoord.y >= screen_size.y || projectedPos.z < 0.0) { return; } let idx = screenCoord.x + screen_size.x * screenCoord.y; AdditiveBlend(color, projectedPos.z, idx); } fn saturate(x: f32) -> f32 { return min(1.0, max(0.0, x)); } fn saturate_vec3(x: vec3<f32>) -> vec3<f32> { return min(vec3<f32>(1.0, 1.0, 1.0), max(vec3<f32>(0.0, 0.0, 0.0), x)); } fn bump3y(x: vec3<f32>, yoffset: vec3<f32>) -> vec3<f32> { var y: vec3<f32> = vec3<f32>(1.0, 1.0, 1.0) - x * x; y = saturate_vec3(y - yoffset); return y; } fn spectral_zucconi(w: f32) -> vec3<f32> { let x: f32 = saturate((w - 400.0) / 300.0); let cs: vec3<f32> = vec3<f32>(3.54541723, 2.86670055, 2.29421995); let xs: vec3<f32> = vec3<f32>(0.69548916, 0.49416934, 0.28269708); let ys: vec3<f32> = vec3<f32>(0.02320775, 0.15936245, 0.53520021); return bump3y(cs * (x - xs), ys); } fn hue(v: float) -> float3 { return .6 + .6 * cos(6.3 * v + float3(0.,23.,21.)); } fn bifurcation(iters: i32) { var p = rand4().xyz * float3(4.0, 4.0, 5.0) - float3(2.0, 2.0, -0.25); let s = rand4().x; let alpha = custom.Alpha; let beta = custom.Beta + custom.BetaAnim*(0.5 * sin(time.elapsed) + 0.5) + custom.BetaS*s; for (var j: i32 = 0; j <= iters; j = j + 1) { let p0 = p; p.x = p.z - p0.y*(beta * p0.x + (1.0 - beta) * p0.y); p.y = p0.x + p0.y * p0.y * alpha; if(j < iters - int(custom.Samples*MaxSamples + 1.0)) {continue;} var color = spectral_zucconi(350 + 350.0*s); color = pow(color, vec3(1.0)); RasterizePoint(float3(1,1,2.5)*(p - float3(0.5, 0.5, 1.5)), 32.0*color/(custom.Samples*MaxSamples + 1.0)); } } @compute @workgroup_size(16, 16) fn Clear(@builtin(global_invocation_id) id: uint3) { let screen_size = int2(textureDimensions(screen)); let idx0 = int(id.x) + int(screen_size.x * int(id.y)); atomicStore(&atomic_storage[idx0*4+0], 0); atomicStore(&atomic_storage[idx0*4+1], 0); atomicStore(&atomic_storage[idx0*4+2], 0); atomicStore(&atomic_storage[idx0*4+3], 0); } @compute @workgroup_size(16, 16) fn Rasterize(@builtin(global_invocation_id) id: uint3) { SetCamera(); //RNG state state = uint4(id.x, id.y, id.z, uint(custom.NoiseAnimation)*time.frame); bifurcation(int(MaxSamples*2.0)); } fn Sample(pos: int2) -> float3 { let screen_size = int2(textureDimensions(screen)); let idx = pos.x + screen_size.x * pos.y; var color: float3; let x = float(atomicLoad(&atomic_storage[idx*4+0])); let y = float(atomicLoad(&atomic_storage[idx*4+1])); let z = float(atomicLoad(&atomic_storage[idx*4+2])); color = float3(x,y,z)/ATOMIC_SCALE; return abs(color); } @compute @workgroup_size(16, 16) fn FinalPass(@builtin(global_invocation_id) id: uint3) { let screen_size = uint2(textureDimensions(screen)); // Prevent overdraw for workgroups on the edge of the viewport if (id.x >= screen_size.x || id.y >= screen_size.y) { return; } // Pixel coordinates (centre of pixel, origin at bottom left) let fragCoord = float2(float(id.x) + .5, float(id.y) + .5); let oldColor = textureLoad(pass_in, int2(id.xy), 0, 0); var color = float4(Sample(int2(id.xy)), 1.0); if(mouse.click != 1) { color += oldColor * custom.Accumulation; } let exposed = 1.0 - exp(-5.0*custom.Exposure*color.xyz/color.w); // Output to buffer textureStore(pass_out, int2(id.xy), 0, color); textureStore(screen, int2(id.xy), float4(exposed, 1.)); }