Skip to main content

TRACE_WGSL

Constant TRACE_WGSL 

Source
pub const TRACE_WGSL: &str = "struct Frame {\n    size: vec4u,\n    counts: vec4u,\n    origin: vec4f,\n    forward: vec4f,\n    right: vec4f,\n    up: vec4f,\n    sun_dir: vec4f,\n    sun_color: vec4f,\n    lens: vec4f,\n    window: vec4u,\n    columns: vec4u,\n}\noverride TOTAL_BOUNCES: u32 = 16u;\noverride DIFFUSE_BOUNCES: u32 = 4u;\noverride GLOSSY_BOUNCES: u32 = 8u;\noverride TRANSMISSION_BOUNCES: u32 = 12u;\noverride BLOCK_STRIDE: u32 = 1u;\noverride TRANSMISSIVE_SHADOWS: bool = false;\noverride CLAMP_INDIRECT: f32 = 0.0;\noverride FILTER_GLOSSY: f32 = 0.0;\noverride SOBOL: bool = false;\noverride SOBOL_LENGTH: u32 = 256u;\noverride STEAM_SPREAD_BASE: f32 = 0.7;\noverride STEAM_SPREAD_GROWTH: f32 = 4.0;\noverride STEAM_DRIFT_X_QUAD: f32 = -0.9;\noverride STEAM_DRIFT_X_LIN: f32 = -0.12;\noverride STEAM_DRIFT_Z_QUAD: f32 = 0.0;\noverride STEAM_DRIFT_Z_LIN: f32 = 0.02;\noverride STEAM_SWAY_X_WAVE: f32 = 17.0;\noverride STEAM_SWAY_X_RATE: f32 = 1.3;\noverride STEAM_SWAY_X_AMP: f32 = 0.012;\noverride STEAM_SWAY_X_BASE: f32 = 0.4;\noverride STEAM_SWAY_X_GROWTH: f32 = 6.0;\noverride STEAM_SWAY_Z_WAVE: f32 = 13.0;\noverride STEAM_SWAY_Z_RATE: f32 = 0.9;\noverride STEAM_SWAY_Z_AMP: f32 = 0.01;\noverride STEAM_SWAY_Z_BASE: f32 = 0.4;\noverride STEAM_SWAY_Z_GROWTH: f32 = 5.0;\noverride STEAM_FADE_IN: f32 = 0.02;\noverride STEAM_FADE_OUT_START: f32 = 0.07;\noverride STEAM_FADE_OUT_END: f32 = 0.2;\noverride STEAM_GRAIN_H: f32 = 70.0;\noverride STEAM_GRAIN_V: f32 = 42.0;\noverride STEAM_LIFT_RATE: f32 = 0.05;\noverride STEAM_WARP_AMP: f32 = 2.2;\noverride STEAM_WARP_RATE: f32 = 0.2;\noverride STEAM_CHURN: f32 = 0.7;\noverride STEAM_THRESHOLD_LOW: f32 = 0.42;\noverride STEAM_THRESHOLD_HIGH: f32 = 0.9;\nstruct Tri { a: vec4f, b: vec4f, c: vec4f, na: vec4f, nb: vec4f, nc: vec4f }\nstruct Node { lo: vec4f, hi: vec4f, first: u32, count: u32, pad: vec2u }\nstruct Shape { a: vec4f, b: vec4f, c: vec4f, d: vec4f }\nstruct Material {\n    base_roughness: vec4f,\n    metal_spec_coat: vec4f,\n    sheen_trans_ior_disp: vec4f,\n    thick_sub_film: vec4f,\n    tint_absorption: vec4f,\n    emission_film: vec4f,\n    normal_maps: vec4f,\n    maps_content: vec4f,\n    age_a: vec4f,\n    age_b: vec4f,\n    layer0: vec4f,\n    layer1: vec4f,\n    layer2: vec4f,\n    layer3: vec4f,\n    tail: vec4f,\n    params: array<vec4f, 16>,\n}\nstruct Hit { t: f32, n: vec3f, geometric: vec3f, material: u32, uv: vec2f, tangent: vec3f, bitangent: vec3f, surface: u32, shape: u32 }\nstruct ContentInfo { offset: u32, width: u32, height: u32, kind: u32 }\nstruct InstanceSurface { clip0: vec4f, clip1: vec4f, transform: vec4f, crop: vec4f }\nstruct ContentLayer { layer: vec4f, extra: vec4f }\nstruct LightSample { dir: vec3f, pdf: f32, channel: u32, weight: vec3f, lobe: u32 }\nstruct Accum { color: vec4f, albedo: vec4f, normal: vec4f }\nstruct DetailResult { shade: Shaded, normal: vec3f }\nstruct Candidate { wi: vec3f, scale: vec3f, pdf: f32, roughness: f32, direction: vec3f, limit: f32, shape: u32 }\n@group(0) @binding(0) var<uniform> frame: Frame;\n@group(0) @binding(1) var<storage, read> triangles: array<Tri>;\n@group(0) @binding(2) var<storage, read> nodes: array<Node>;\n@group(0) @binding(3) var<storage, read> shapes: array<Shape>;\n@group(0) @binding(4) var<storage, read> materials: array<Material>;\n@group(0) @binding(5) var<storage, read> triangle_uv_v: array<vec4f>;\n@group(0) @binding(6) var<storage, read> content_texels: array<vec4f>;\n@group(0) @binding(7) var<storage, read_write> accum: array<Accum>;\n@group(0) @binding(8) var output: texture_storage_2d<rgba32float, write>;\n@group(0) @binding(9) var albedo: texture_storage_2d<rgba32float, write>;\n@group(0) @binding(10) var normal: texture_storage_2d<rgba32float, write>;\n@group(0) @binding(11) var<storage, read> content_info: array<ContentInfo>;\n@group(0) @binding(12) var<storage, read> instance_surfaces: array<InstanceSurface>;\n@group(0) @binding(13) var<storage, read> content_layers: array<ContentLayer>;\n\nfn effects() -> Material {\n    return materials[arrayLength(&materials)-1u];\n}\n\nfn trace_noise_start() {}\nfn pcg(v: u32) -> u32 {\n    let s = v * 747796405u + 2891336453u;\n    let w = ((s >> ((s >> 28u) + 4u)) ^ s) * 277803737u;\n    return (w >> 22u) ^ w;\n}\nfn hash3_xor(p: vec3i) -> f32 {\n    let h = u32(p.x) * 73856093u ^ u32(p.y) * 19349663u ^ u32(p.z) * 83492791u;\n    return f32(pcg(h) >> 8u) / 16777216.0;\n}\nfn hash3_nested(p: vec3i) -> f32 {\n    let h = pcg(u32(p.x) * 73856093u ^ pcg(u32(p.y) * 19349663u ^ pcg(u32(p.z) * 83492791u)));\n    return f32(h >> 8u) / 16777216.0;\n}\nfn value_noise3(p: vec3f, nested: bool) -> f32 {\n    let i=vec3i(floor(p));\n    let f=fract(p);\n    let u=f*f*(3.0-2.0*f);\n    var a0: f32;\n    var a1: f32;\n    var a2: f32;\n    var a3: f32;\n    var b0: f32;\n    var b1: f32;\n    var b2: f32;\n    var b3: f32;\n    if (nested) {\n        a0=hash3_nested(i);\n        a1=hash3_nested(i+vec3i(1,0,0));\n        a2=hash3_nested(i+vec3i(0,1,0));\n        a3=hash3_nested(i+vec3i(1,1,0));\n        b0=hash3_nested(i+vec3i(0,0,1));\n        b1=hash3_nested(i+vec3i(1,0,1));\n        b2=hash3_nested(i+vec3i(0,1,1));\n        b3=hash3_nested(i+vec3i(1,1,1));\n    } else {\n        a0=hash3_xor(i);\n        a1=hash3_xor(i+vec3i(1,0,0));\n        a2=hash3_xor(i+vec3i(0,1,0));\n        a3=hash3_xor(i+vec3i(1,1,0));\n        b0=hash3_xor(i+vec3i(0,0,1));\n        b1=hash3_xor(i+vec3i(1,0,1));\n        b2=hash3_xor(i+vec3i(0,1,1));\n        b3=hash3_xor(i+vec3i(1,1,1));\n    }\n    let a=mix(mix(a0,a1,u.x),mix(a2,a3,u.x),u.y);\n    let b=mix(mix(b0,b1,u.x),mix(b2,b3,u.x),u.y);\n    return mix(a,b,u.z);\n}\nfn trace_noise_end() {}\n\nfn random(state: ptr<function,u32>) -> f32 {\n    *state = pcg(*state);\n    return f32(*state >> 8u) / 16777216.0;\n}\nvar<private> rng: vec3u;\nfn hash_iqnt2d(x: u32, y: u32) -> u32 {\n    let qx=1103515245u*((x>>1u)^y);\n    let qy=1103515245u*((y>>1u)^x);\n    return 1103515245u*(qx^(qy>>3u));\n}\nfn trace_sobol_start() {}\nfn sobol(dimension: u32) -> vec4f {\n    return vec4f(0.0);\n}\nfn trace_sobol_end() {}\nfn rnd(state: ptr<function,u32>, value: f32) -> f32 {\n    if (SOBOL) { return value; }\n    return random(state);\n}\nfn light_dim(k: u32) -> u32 {\n    if (k==0u) { return 1u; }\n    if (k<13u) { return 3u+k; }\n    return k<<16u;\n}\nfn box_hit(node: Node, o: vec3f, d: vec3f, limit: f32) -> bool {\n    var near = 0.0;\n    var far = limit;\n    for (var axis=0u; axis<3u; axis++) {\n        if (abs(d[axis]) < 1e-12) {\n            if (o[axis] < node.lo[axis] || o[axis] > node.hi[axis]) { return false; }\n        } else {\n            let a = (node.lo[axis]-o[axis])/d[axis];\n            let b = (node.hi[axis]-o[axis])/d[axis];\n            near = max(near,min(a,b));\n            far = min(far,max(a,b));\n            if (near > far) { return false; }\n        }\n    }\n    return true;\n}\nconst ONE_SIDED: u32 = 8388608u;\nconst LAYER_MASK: u32 = 262143u;\nfn hit_layer(i: u32, material: u32) -> ContentLayer {\n    let layer=(u32(triangles[i].b.w)>>5u) & LAYER_MASK;\n    if (layer>0u) { return content_layers[layer-1u]; }\n    return content_layers[material];\n}\nfn cut_out(i: u32, u: f32, v: f32) -> bool {\n    let tri = triangles[i];\n    if ((u32(tri.b.w)&4u)==0u) { return false; }\n    let slot=i32(round(hit_layer(i,u32(tri.a.w)).layer.x));\n    if (slot<0) { return true; }\n    let uv_v=triangle_uv_v[i];\n    let uv=vec2f(tri.na.w*(1.0-u-v)+tri.nb.w*u+tri.nc.w*v,uv_v.x*(1.0-u-v)+uv_v.y*u+uv_v.z*v);\n    let surface=instance_surfaces[i];\n    let at=content_uv(uv,surface.transform);\n    if (!content_inside(at,surface.crop)) { return true; }\n    let cutoff=uv_v.w;\n    if (cutoff==0.5) { return content_at(slot,at).a<0.5; }\n    return content_at(slot,at).a<cutoff;\n}\nfn tri_hit(i: u32, o: vec3f, d: vec3f, shadow_ray: bool, hit: ptr<function,Hit>) {\n    let tri = triangles[i];\n    let flags = u32(tri.b.w);\n    if ((flags&1u)!=0u && !shadow_ray) { return; }\n    if ((flags&2u)!=0u && shadow_ray) { return; }\n    let e1 = tri.b.xyz-tri.a.xyz;\n    let e2 = tri.c.xyz-tri.a.xyz;\n    let p = cross(d,e2);\n    let det = dot(e1,p);\n    if (abs(det)<1e-9) { return; }\n    if (!shadow_ray && (flags & ONE_SIDED) != 0u && det < 0.0) { return; }\n    let inv = 1.0/det;\n    let s = o-tri.a.xyz;\n    let u = dot(s,p)*inv;\n    if (u<0.0 || u>1.0) { return; }\n    let q = cross(s,e1);\n    let v = dot(d,q)*inv;\n    if (v<0.0 || u+v>1.0) { return; }\n    let t = dot(e2,q)*inv;\n    if (t>0.0001 && t<(*hit).t) {\n        let surface=instance_surfaces[i];\n        let point=o+d*t;\n        if ((any(surface.clip0.xyz != vec3f(0.0)) && dot(surface.clip0.xyz,point)>surface.clip0.w)\n            || (any(surface.clip1.xyz != vec3f(0.0)) && dot(surface.clip1.xyz,point)>surface.clip1.w)) { return; }\n        if (cut_out(i,u,v)) { return; }\n        (*hit).t=t;\n        (*hit).surface=i;\n        let geometric=normalize(cross(e1,e2));\n        (*hit).geometric=geometric;\n        let shaded=tri.na.xyz*(1.0-u-v)+tri.nb.xyz*u+tri.nc.xyz*v;\n        (*hit).n=select(geometric,normalize(shaded),dot(shaded,shaded)>1e-12);\n        (*hit).material=u32(tri.a.w);\n        let uv_v=triangle_uv_v[i];\n        (*hit).uv=vec2f(tri.na.w*(1.0-u-v)+tri.nb.w*u+tri.nc.w*v,\n            uv_v.x*(1.0-u-v)+uv_v.y*u+uv_v.z*v);\n        let du1=tri.nb.w-tri.na.w;\n        let du2=tri.nc.w-tri.na.w;\n        let dv1=uv_v.y-uv_v.x;\n        let dv2=uv_v.z-uv_v.x;\n        let uv_det=du1*dv2-du2*dv1;\n        var tangent=normalize(e1);\n        if (abs(uv_det)>1e-10) { tangent=normalize((e1*dv2-e2*dv1)/uv_det); }\n        (*hit).tangent=tangent;\n        (*hit).bitangent=normalize(cross(geometric,tangent))*select(-1.0,1.0,uv_det>=0.0);\n    }\n}\nfn ellipsoid(p: vec3f, radius: vec3f) -> f32 {\n    let k0 = length(p/radius);\n    let k1 = length(p/(radius*radius));\n    return k0*(k0-1.0)/max(k1,1e-8);\n}\nfn cone(p: vec3f, a: vec3f, b: vec3f, r1: f32, r2: f32) -> f32 {\n    let ba=b-a;\n    let length_axis=length(ba);\n    if (length_axis<=abs(r1-r2)) {\n        return select(length(p-b)-r2,length(p-a)-r1,r1>=r2);\n    }\n    let l2=dot(ba,ba);\n    let rr=r1-r2;\n    let a2=l2-rr*rr;\n    let pa=p-a;\n    let y=dot(pa,ba);\n    let z=y-l2;\n    let xv=pa*l2-ba*y;\n    let x2=dot(xv,xv);\n    let y2=y*y*l2;\n    let z2=z*z*l2;\n    let k=sign(rr)*rr*rr*x2;\n    if (sign(z)*a2*z2>k) { return sqrt(x2+z2)/l2-r2; }\n    if (sign(y)*a2*y2<k) { return sqrt(x2+y2)/l2-r1; }\n    return sqrt(max(x2*a2/l2,0.0))/l2+y*rr/l2-r1;\n}\nfn base_distance(i: u32, p: vec3f) -> f32 {\n    let s=shapes[i];\n    let kind=u32(s.a.w);\n    if (kind==1u) {\n        let q=abs(p-s.a.xyz)-(s.b.xyz-vec3f(s.b.w));\n        return length(max(q,vec3f(0.0)))+min(max(q.x,max(q.y,q.z)),0.0)-s.b.w;\n    }\n    if (kind==2u) { return cone(p,s.a.xyz,s.b.xyz,s.c.x,s.c.y); }\n    if (kind==3u) { return ellipsoid(p-s.a.xyz,s.b.xyz); }\n    if (kind==4u) {\n        let ba=s.b.xyz-s.a.xyz;\n        let h=clamp(dot(p-s.a.xyz,ba)/max(dot(ba,ba),1e-9),0.0,1.0);\n        return length(p-s.a.xyz-ba*h)-s.c.x;\n    }\n    return 1e30;\n}\nfn shape_distance(i: u32, p: vec3f) -> f32 {\n    let s=shapes[i];\n    if (u32(s.a.w)==5u) {\n        let a=base_distance(u32(s.b.x),p);\n        let b=base_distance(u32(s.b.y),p);\n        let radius=max(s.b.z,1e-6);\n        let h=clamp(0.5+0.5*(b-a)/radius,0.0,1.0);\n        return mix(b,a,h)-radius*h*(1.0-h);\n    }\n    return base_distance(i,p);\n}\nfn roots(a: f32, b: f32, c: f32) -> vec2f {\n    if (abs(a)<1e-10) {\n        if (abs(b)<1e-10) { return vec2f(1e30); }\n        return vec2f(-c/(2.0*b),1e30);\n    }\n    let disc=b*b-a*c;\n    if (disc<0.0) { return vec2f(1e30); }\n    let root=sqrt(disc);\n    return vec2f((-b-root)/a,(-b+root)/a);\n}\nfn consider(i: u32, o: vec3f, d: vec3f, t: f32, best: ptr<function,f32>) {\n    if (t>0.0001 && t<*best && abs(shape_distance(i,o+d*t))<0.002) { *best=t; }\n}\nfn primitive_hit(i: u32, o: vec3f, d: vec3f, limit: f32) -> f32 {\n    let shape=shapes[i];\n    let kind=u32(shape.a.w);\n    var best=limit;\n    if (kind==3u) {\n        let origin=(o-shape.a.xyz)/shape.b.xyz;\n        let direction=d/shape.b.xyz;\n        let candidates=roots(dot(direction,direction),dot(origin,direction),dot(origin,origin)-1.0);\n        consider(i,o,d,candidates.x,&best);\n        consider(i,o,d,candidates.y,&best);\n    }\n    if (kind==1u) {\n        let core=max(shape.b.xyz-vec3f(shape.b.w),vec3f(0.0));\n        let radius=shape.b.w;\n        for (var axis=0u;axis<3u;axis++) {\n            if (abs(d[axis])>1e-9) {\n                for (var side=0u;side<2u;side++) {\n                    let sign_side=select(-1.0,1.0,side==1u);\n                    consider(i,o,d,(shape.a[axis]+sign_side*(core[axis]+radius)-o[axis])/d[axis],&best);\n                }\n            }\n            let j=(axis+1u)%3u;\n            let k=(axis+2u)%3u;\n            for (var sj=0u;sj<2u;sj++) {\n                for (var sk=0u;sk<2u;sk++) {\n                    let js=select(-1.0,1.0,sj==1u);\n                    let ks=select(-1.0,1.0,sk==1u);\n                    let oj=o[j]-shape.a[j]-js*core[j];\n                    let ok=o[k]-shape.a[k]-ks*core[k];\n                    let candidates=roots(d[j]*d[j]+d[k]*d[k],oj*d[j]+ok*d[k],oj*oj+ok*ok-radius*radius);\n                    for (var root=0u;root<2u;root++) {\n                        let t=candidates[root];\n                        if (abs(o[axis]+d[axis]*t-shape.a[axis])<=core[axis]+0.002) { consider(i,o,d,t,&best); }\n                    }\n                }\n            }\n        }\n        for (var sx=0u;sx<2u;sx++) {\n            for (var sy=0u;sy<2u;sy++) {\n                for (var sz=0u;sz<2u;sz++) {\n                    let corner=shape.a.xyz+vec3f(select(-1.0,1.0,sx==1u),select(-1.0,1.0,sy==1u),select(-1.0,1.0,sz==1u))*core;\n                    let v=o-corner;\n                    let candidates=roots(dot(d,d),dot(v,d),dot(v,v)-radius*radius);\n                    consider(i,o,d,candidates.x,&best);\n                    consider(i,o,d,candidates.y,&best);\n                }\n            }\n        }\n    }\n    if (kind==4u) {\n        let ba=shape.b.xyz-shape.a.xyz;\n        let oa=o-shape.a.xyz;\n        let baba=dot(ba,ba);\n        let bard=dot(ba,d);\n        let baoa=dot(ba,oa);\n        let rdoa=dot(d,oa);\n        let oaoa=dot(oa,oa);\n        let radius=shape.c.x;\n        let candidates=roots(baba*dot(d,d)-bard*bard,baba*rdoa-baoa*bard,baba*oaoa-baoa*baoa-radius*radius*baba);\n        for (var root=0u;root<2u;root++) {\n            let t=candidates[root];\n            let y=baoa+t*bard;\n            if (y>=0.0 && y<=baba) { consider(i,o,d,t,&best); }\n        }\n        for (var end=0u;end<2u;end++) {\n            let center=select(shape.a.xyz,shape.b.xyz,end==1u);\n            let v=o-center;\n            let candidates=roots(dot(d,d),dot(v,d),dot(v,v)-radius*radius);\n            consider(i,o,d,candidates.x,&best);\n            consider(i,o,d,candidates.y,&best);\n        }\n    }\n    if (kind==2u) {\n        let ba=shape.b.xyz-shape.a.xyz;\n        let len=length(ba);\n        if (len>1e-8) {\n            let axis=ba/len;\n            let slope=(shape.c.x-shape.c.y)/len;\n            if (abs(slope)<1.0) {\n                let side=sqrt(1.0-slope*slope);\n                let oa=o-shape.a.xyz;\n                let y0=dot(oa,axis);\n                let yd=dot(d,axis);\n                let radial0=oa-axis*y0;\n                let radiald=d-axis*yd;\n                let e=shape.c.x-slope*y0;\n                let candidates=roots(side*side*dot(radiald,radiald)-slope*slope*yd*yd,side*side*dot(radial0,radiald)+e*slope*yd,side*side*dot(radial0,radial0)-e*e);\n                for (var root=0u;root<2u;root++) {\n                    let t=candidates[root];\n                    let y=y0+t*yd;\n                    if (y>=slope*shape.c.x-0.002 && y<=len+slope*shape.c.y+0.002) { consider(i,o,d,t,&best); }\n                }\n            }\n        }\n        for (var end=0u;end<2u;end++) {\n            let center=select(shape.a.xyz,shape.b.xyz,end==1u);\n            let radius=select(shape.c.x,shape.c.y,end==1u);\n            let v=o-center;\n            let candidates=roots(dot(d,d),dot(v,d),dot(v,v)-radius*radius);\n            consider(i,o,d,candidates.x,&best);\n            consider(i,o,d,candidates.y,&best);\n        }\n    }\n    return best;\n}\nfn union_hit(i: u32, o: vec3f, d: vec3f, limit: f32) -> f32 {\n    var t=0.0;\n    var last=shape_distance(i,o);\n    for (var step=0u;step<384u;step++) {\n        let previous=t;\n        t+=max(abs(last),0.00005);\n        if (t>=limit) { return limit; }\n        let distance=shape_distance(i,o+d*t);\n        if (abs(distance)<0.0001) { return t; }\n        if ((last>0.0 && distance<0.0) || (last<0.0 && distance>0.0)) {\n            var lo=previous;\n            var hi=t;\n            for (var refine=0u;refine<24u;refine++) {\n                let mid=(lo+hi)*0.5;\n                let value=shape_distance(i,o+d*mid);\n                if (sign(value)==sign(last)) { lo=mid; } else { hi=mid; }\n            }\n            return (lo+hi)*0.5;\n        }\n        last=distance;\n    }\n    return limit;\n}\nfn scene(o: vec3f, d: vec3f, limit: f32, shadow_ray: bool) -> Hit {\n    var hit=Hit(limit,vec3f(0.0),vec3f(0.0),0u,vec2f(0.0),vec3f(1.0,0.0,0.0),vec3f(0.0,1.0,0.0),0u,0u);\n    if (frame.counts.x>0u) {\n        var stack: array<u32,64>;\n        var top=1u;\n        stack[0]=0u;\n        while (top>0u) {\n            top-=1u;\n            let index=stack[top];\n            let node=nodes[index];\n            if (!box_hit(node,o,d,hit.t)) { continue; }\n            if (node.count==0u) {\n                if (top+2u<=64u) {\n                    stack[top]=node.first;\n                    stack[top+1u]=index+1u;\n                    top+=2u;\n                }\n            } else {\n                for (var j=0u;j<node.count;j++) { tri_hit(node.first+j,o,d,shadow_ray,&hit); }\n            }\n        }\n    }\n    for (var i=0u;i<frame.counts.y;i++) {\n        if (shapes[i].d.x > 0.5) { continue; }\n        var t=hit.t;\n        if (u32(shapes[i].a.w)==5u) { t=union_hit(i,o,d,hit.t); } else { t=primitive_hit(i,o,d,hit.t); }\n        if (t<hit.t && t>0.0001) {\n            let p=o+d*t;\n            let e=0.0002;\n            let n=normalize(vec3f(\n                shape_distance(i,p+vec3f(e,0.0,0.0))-shape_distance(i,p-vec3f(e,0.0,0.0)),\n                shape_distance(i,p+vec3f(0.0,e,0.0))-shape_distance(i,p-vec3f(0.0,e,0.0)),\n                shape_distance(i,p+vec3f(0.0,0.0,e))-shape_distance(i,p-vec3f(0.0,0.0,e))));\n            hit=Hit(t,n,n,u32(shapes[i].c.w),vec2f(0.0),vec3f(1.0,0.0,0.0),vec3f(0.0,1.0,0.0),0u,i+1u);\n        }\n    }\n    return hit;\n}\nfn tri_blocks(i: u32, o: vec3f, d: vec3f, limit: f32) -> bool {\n    let tri = triangles[i];\n    if ((u32(tri.b.w)&2u)!=0u) { return false; }\n    let e1 = tri.b.xyz-tri.a.xyz;\n    let e2 = tri.c.xyz-tri.a.xyz;\n    let p = cross(d,e2);\n    let det = dot(e1,p);\n    if (abs(det)<1e-9) { return false; }\n    let inv = 1.0/det;\n    let s = o-tri.a.xyz;\n    let u = dot(s,p)*inv;\n    if (u<0.0 || u>1.0) { return false; }\n    let q = cross(s,e1);\n    let v = dot(d,q)*inv;\n    if (v<0.0 || u+v>1.0) { return false; }\n    let t = dot(e2,q)*inv;\n    if (t<=0.0001 || t>=limit) { return false; }\n    let surface=instance_surfaces[i];\n    let point=o+d*t;\n    if ((any(surface.clip0.xyz != vec3f(0.0)) && dot(surface.clip0.xyz,point)>surface.clip0.w)\n        || (any(surface.clip1.xyz != vec3f(0.0)) && dot(surface.clip1.xyz,point)>surface.clip1.w)) { return false; }\n    return !cut_out(i,u,v);\n}\nfn occluded(o: vec3f, d: vec3f, limit: f32) -> bool {\n    if (frame.counts.x>0u) {\n        var stack: array<u32,64>;\n        var top=1u;\n        stack[0]=0u;\n        while (top>0u) {\n            top-=1u;\n            let index=stack[top];\n            let node=nodes[index];\n            if (!box_hit(node,o,d,limit)) { continue; }\n            if (node.count==0u) {\n                if (top+2u<=64u) {\n                    stack[top]=node.first;\n                    stack[top+1u]=index+1u;\n                    top+=2u;\n                }\n            } else {\n                for (var j=0u;j<node.count;j++) {\n                    if (tri_blocks(node.first+j,o,d,limit)) { return true; }\n                }\n            }\n        }\n    }\n    for (var i=0u;i<frame.counts.y;i++) {\n        if (shapes[i].d.x > 0.5) { continue; }\n        var t=limit;\n        if (u32(shapes[i].a.w)==5u) { t=union_hit(i,o,d,limit); } else { t=primitive_hit(i,o,d,limit); }\n        if (t<limit && t>0.0001) { return true; }\n    }\n    return false;\n}\nfn cast_tint(material: u32) -> vec3f {\n    let transmission=materials[material].sheen_trans_ior_disp.y;\n    if (transmission<=0.0) { return vec3f(0.0); }\n    var passed=materials[material].base_roughness.xyz;\n    let thickness=materials[material].thick_sub_film.x;\n    let absorption=materials[material].tint_absorption.w;\n    if (thickness>0.0 && absorption>0.0) { passed=beer(thickness,passed,absorption); }\n    return clamp(passed*transmission,vec3f(0.0),vec3f(1.0));\n}\nfn tri_passes(i: u32, o: vec3f, d: vec3f, limit: f32) -> vec3f {\n    if (!tri_blocks(i,o,d,limit)) { return vec3f(1.0); }\n    return sqrt(cast_tint(u32(triangles[i].a.w)));\n}\nfn shadow_passed(o: vec3f, d: vec3f, limit: f32) -> vec3f {\n    var passed=vec3f(1.0);\n    if (frame.counts.x>0u) {\n        var stack: array<u32,64>;\n        var top=1u;\n        stack[0]=0u;\n        while (top>0u) {\n            top-=1u;\n            let index=stack[top];\n            let node=nodes[index];\n            if (!box_hit(node,o,d,limit)) { continue; }\n            if (node.count==0u) {\n                if (top+2u<=64u) {\n                    stack[top]=node.first;\n                    stack[top+1u]=index+1u;\n                    top+=2u;\n                }\n            } else {\n                for (var j=0u;j<node.count;j++) {\n                    passed*=tri_passes(node.first+j,o,d,limit);\n                    if (all(passed<=vec3f(0.0))) { return vec3f(0.0); }\n                }\n            }\n        }\n    }\n    for (var i=0u;i<frame.counts.y;i++) {\n        if (shapes[i].d.x > 0.5) { continue; }\n        var t=limit;\n        if (u32(shapes[i].a.w)==5u) { t=union_hit(i,o,d,limit); } else { t=primitive_hit(i,o,d,limit); }\n        if (t<limit && t>0.0001) { passed*=cast_tint(u32(shapes[i].c.w)); }\n    }\n    return passed;\n}\nfn offset_axis(position: f32, normal: f32) -> f32 {\n    if (abs(position) < 1.0/32.0) { return position + normal/65536.0; }\n    let bits=bitcast<i32>(position);\n    let shift=i32(normal*256.0);\n    return bitcast<f32>(bits + select(shift, -shift, position < 0.0));\n}\nfn offset_ray(p: vec3f, n: vec3f) -> vec3f {\n    return vec3f(offset_axis(p.x,n.x), offset_axis(p.y,n.y), offset_axis(p.z,n.z));\n}\nfn spawn_point(p: vec3f, dir: vec3f, origin: vec3f, t: f32) -> vec3f {\n    let err=4e-7*(max(abs(origin.x),max(abs(origin.y),abs(origin.z)))+t);\n    return offset_ray(p, dir)+dir*err;\n}\nfn basis(n: vec3f) -> mat3x3f {\n    let t=normalize(cross(select(vec3f(0.0,1.0,0.0),vec3f(1.0,0.0,0.0),abs(n.y)>0.9),n));\n    return mat3x3f(t,cross(n,t),n);\n}\nconst ROUGHNESS_FLOOR: f32 = 0.05;\nfn surface(material: Material) -> Shaded {\n    return Shaded(material.base_roughness.xyz,material.base_roughness.w,\n        material.metal_spec_coat.x,material.metal_spec_coat.y,material.metal_spec_coat.z,material.metal_spec_coat.w,\n        material.sheen_trans_ior_disp.x,material.sheen_trans_ior_disp.y,material.sheen_trans_ior_disp.z,material.sheen_trans_ior_disp.w,\n        material.thick_sub_film.x,material.thick_sub_film.y,material.tint_absorption.xyz,material.tint_absorption.w,\n        material.thick_sub_film.z,material.thick_sub_film.w,material.emission_film.w,material.emission_film.xyz,\n        material.age_b.w,vec3f(0.0,0.0,1.0));\n}\nfn detail_wobble(p: vec3f, scale: f32) -> vec3f {\n    let q=p*scale;\n    let e=0.35;\n    return vec3f(value_noise3(q+vec3f(e,0.0,0.0),false)-value_noise3(q-vec3f(e,0.0,0.0),false),\n        value_noise3(q+vec3f(0.0,e,0.0),false)-value_noise3(q-vec3f(0.0,e,0.0),false),\n        value_noise3(q+vec3f(0.0,0.0,e),false)-value_noise3(q-vec3f(0.0,0.0,e),false))/(2.0*e);\n}\nfn detail_perturb(n: vec3f, g: vec3f) -> vec3f {\n    return normalize(n-(g-n*dot(g,n)));\n}\nfn detail_layer(value: Shaded, normal_in: vec3f, layer: vec4f, pa: vec4f, pb: vec4f, pc: vec4f, pd: vec4f, position: vec3f) -> DetailResult {\n    var shade=value;\n    var base=value.base;\n    var roughness=value.roughness;\n    var coat_roughness=value.clearcoat_roughness;\n    var n=normal_in;\n    let kind=u32(round(layer.x));\n    let amp=layer.z;\n    let seed=bitcast<u32>(layer.w);\n    let p=position+vec3f(f32(seed)*0.001,f32(seed)*0.001,0.0);\n    if (kind==1u) {\n        let fibre=value_noise3(p*vec3f(pa.x,pa.y,pa.x),false)*0.6+value_noise3(p*pa.z,false)*0.4;\n        let mottle=value_noise3(p*pa.w,false);\n        base*=1.0+((fibre-0.5)*pb.z+(mottle-0.5)*pb.w)*amp;\n        n=detail_perturb(n,detail_wobble(p,pb.x)*(pc.x*amp)+detail_wobble(p,pb.y)*(pc.y*amp));\n    } else if (kind==2u) {\n        n=detail_perturb(n,detail_wobble(p,pa.x)*(pa.z*amp)+detail_wobble(p,pa.y)*(pa.w*amp));\n    } else if (kind==3u && pa.x>0.0) {\n        let plank=floor(p.z*pd.x);\n        let tone=hash3_xor(vec3i(i32(plank),7,3));\n        let across=fract(p.z*pd.x);\n        let seam=smoothstep(0.0,0.006,across)+smoothstep(0.0,0.006,1.0-across)-1.0;\n        let warp=value_noise3(vec3f(p.x*1.4,plank*3.7,p.z*6.0),false)*0.9+value_noise3(vec3f(p.x*6.0,plank,p.z*20.0),false)*0.25;\n        let rings=fract(fma(fma(warp,pa.z,p.z),pa.y,value_noise3(vec3f(p.x*0.8,plank,0.0),false)*pc.y));\n        let figure=smoothstep(0.55,0.95,rings)*0.55+value_noise3(vec3f(p.x*pa.w,p.z*pb.x,plank),false)*0.25;\n        let factor=(0.78+tone*pb.y)*(1.08-figure*pb.z)*(pb.w+clamp(seam,0.0,1.0)*pd.y);\n        base*=mix(1.0,factor,clamp(amp,0.0,1.0));\n        roughness=mix(roughness,sqrt(min(roughness*roughness*(0.85+figure*pc.x),1.0)),clamp(amp,0.0,1.0));\n    } else if (kind==4u) {\n        let broad=value_noise3(p*pa.x,false)*0.6+value_noise3(p*pa.y,false)*0.4;\n        let fine=value_noise3(p*pa.z,false)*0.6+value_noise3(p*pa.w,false)*0.4;\n        base*=mix(1.0,1.0+(broad-0.5)*pb.z+(fine-0.5)*pb.w,clamp(amp,0.0,1.0));\n        n=detail_perturb(n,detail_wobble(p,pb.x)*(pc.x*amp)+detail_wobble(p,pb.y)*(pc.y*amp));\n    } else if (kind==5u) {\n        let grime=value_noise3(p*pa.x,false)*0.6+value_noise3(p*pa.y,false)*0.4;\n        let wear=smoothstep(0.35,0.75,grime);\n        base*=mix(vec3f(1.0),mix(vec3f(pa.z,pa.w,pb.x),vec3f(pb.y,pb.z,pb.w),wear),clamp(amp,0.0,1.0));\n        roughness=mix(roughness,sqrt(min(roughness*roughness*mix(pc.x,pc.y,wear),1.0)),clamp(amp,0.0,1.0));\n    } else if (kind==15u) {\n        base*=mix(1.0,1.0+(value_noise3(p*layer.y,false)-0.5)*2.0,clamp(amp,0.0,1.0));\n    } else if (kind==17u) {\n        let line=1.0-smoothstep(0.0,0.045,abs(value_noise3(vec3f(p.x*pd.x+f32(seed),p.y*pa.x,p.z*pd.x),false)-0.5));\n        let smudge=smoothstep(0.52,0.78,value_noise3(p*pa.y,false)*0.65+value_noise3(p*pa.z,false)*0.35);\n        let cell=vec3i(floor(p*pa.w));\n        let speck=select(0.0,1.0,hash3_xor(vec3i(cell.x,cell.y,cell.z^i32(seed)))>0.9985);\n        let breakup=clamp(smudge*0.55+line*0.35+speck,0.0,1.0)*min(amp,1.0);\n        if (value.clearcoat>0.0) {\n            coat_roughness=sqrt(mix(coat_roughness*coat_roughness,min(coat_roughness*coat_roughness*4.0+0.02,0.35),breakup));\n        } else {\n            roughness=sqrt(mix(roughness*roughness,min(roughness*roughness*4.0+0.02,0.35),breakup));\n        }\n        base=mix(base,base*0.9+vec3f(0.06),speck*min(amp,1.0));\n    } else if (kind==18u) {\n        n=detail_perturb(n,detail_wobble(p,pa.x)*amp+detail_wobble(p,pa.y)*(amp*pa.w)+detail_wobble(p,pa.z)*(amp*pb.x));\n    }\n    shade.base=base;\n    shade.roughness=roughness;\n    shade.clearcoat_roughness=coat_roughness;\n    return DetailResult(shade,n);\n}\nfn leaf_hash(p: vec2f) -> f32 {\n    let q=fract(p*vec2f(123.34,456.21));\n    return fract((q.x+45.32)*(q.y+45.32)*34.23+q.x*13.7);\n}\nfn leaf_noise(p: vec2f) -> f32 {\n    let i=floor(p);\n    let f=fract(p);\n    let u=f*f*(3.0-2.0*f);\n    return mix(mix(leaf_hash(i),leaf_hash(i+vec2f(1.0,0.0)),u.x),\n        mix(leaf_hash(i+vec2f(0.0,1.0)),leaf_hash(i+vec2f(1.0,1.0)),u.x),u.y);\n}\nfn leaf_fbm(p: vec2f) -> f32 {\n    return leaf_noise(p)*0.55+leaf_noise(p*2.13+3.1)*0.28+leaf_noise(p*4.37+7.7)*0.17;\n}\nfn canopy(p: vec3f) -> f32 {\n    let sun=normalize(frame.sun_dir.xyz);\n    let e1=normalize(cross(sun,vec3f(0.0,1.0,0.0)));\n    let e2=cross(e1,sun);\n    let q=vec2f(dot(p-effects().metal_spec_coat.xyz,e1),dot(p-effects().metal_spec_coat.xyz,e2));\n    let t=effects().base_roughness.x;\n    let gust=sin(t*0.21)*0.6+sin(t*0.37+1.3)*0.4;\n    let sway=vec2f(sin(t*0.9+q.y*9.0)*0.005+gust*0.011,cos(t*0.7+q.x*7.0)*0.004+gust*0.004);\n    let shifted=q+sway;\n    let branch=smoothstep(-0.55,0.15,shifted.x*0.8+shifted.y*0.6);\n    let clusters=smoothstep(0.3,0.62,leaf_fbm(shifted*3.4+vec2f(5.0,2.0)));\n    let leaves=smoothstep(0.36,0.56,leaf_fbm(shifted*17.0+vec2f(sin(t*1.3)*0.08,cos(t*1.1)*0.08)));\n    return mix(1.0,1.0-leaves*clusters*branch*0.88,effects().base_roughness.y);\n}\nfn steam_fbm(p: vec3f) -> f32 {\n    var sum=0.0;\n    var amp=0.55;\n    var q=p;\n    for (var k=0;k<4;k++) {\n        sum+=value_noise3(q,false)*amp;\n        q=q*2.07+vec3f(1.7,9.2,3.1);\n        amp*=0.5;\n    }\n    return sum;\n}\nfn steam_density(p: vec3f) -> f32 {\n    let t=effects().base_roughness.w;\n    let source=effects().sheen_trans_ior_disp.xyz;\n    let rise=p.y-source.y;\n    if (rise<0.0 || rise>STEAM_FADE_OUT_END) { return 0.0; }\n    let drift=vec3f(STEAM_DRIFT_X_QUAD*rise*rise+STEAM_DRIFT_X_LIN*rise,0.0,STEAM_DRIFT_Z_QUAD*rise*rise+STEAM_DRIFT_Z_LIN*rise);\n    let sway=vec3f(sin(rise*STEAM_SWAY_X_WAVE-t*STEAM_SWAY_X_RATE)*STEAM_SWAY_X_AMP*(STEAM_SWAY_X_BASE+rise*STEAM_SWAY_X_GROWTH),0.0,\n        cos(rise*STEAM_SWAY_Z_WAVE-t*STEAM_SWAY_Z_RATE)*STEAM_SWAY_Z_AMP*(STEAM_SWAY_Z_BASE+rise*STEAM_SWAY_Z_GROWTH));\n    let axis=source+vec3f(0.0,rise,0.0)+drift+sway;\n    let width=effects().sheen_trans_ior_disp.w*(STEAM_SPREAD_BASE+rise*STEAM_SPREAD_GROWTH);\n    let r=length((p-axis).xz);\n    let core=exp(-r*r/(width*width));\n    let fade=smoothstep(0.0,STEAM_FADE_IN,rise)*(1.0-smoothstep(STEAM_FADE_OUT_START,STEAM_FADE_OUT_END,rise));\n    if (core*fade<0.002) { return 0.0; }\n    let q=vec3f(p.x*STEAM_GRAIN_H,(p.y-t*STEAM_LIFT_RATE)*STEAM_GRAIN_V,p.z*STEAM_GRAIN_H);\n    let warp=vec3f(steam_fbm(q*0.5+vec3f(t*STEAM_WARP_RATE,0.0,0.0)),0.0,\n        steam_fbm(q*0.5+vec3f(3.1,0.0,t*STEAM_WARP_RATE)))*STEAM_WARP_AMP;\n    let turbulence=smoothstep(STEAM_THRESHOLD_LOW,STEAM_THRESHOLD_HIGH,steam_fbm(q+warp+vec3f(0.0,-t*STEAM_CHURN,0.0)));\n    return core*turbulence*fade*effects().thick_sub_film.w;\n}\nfn steam_composite(origin: vec3f, direction: vec3f, surface_depth: f32, color: vec3f) -> vec3f {\n    if (effects().thick_sub_film.w<=0.0) { return color; }\n    let inv=1.0/select(direction,sign(direction)*vec3f(1e-8),abs(direction)<vec3f(1e-8));\n    let t0=(effects().thick_sub_film.xyz-origin)*inv;\n    let t1=(effects().tint_absorption.xyz-origin)*inv;\n    let enter=max(max(min(t0.x,t1.x),min(t0.y,t1.y)),max(min(t0.z,t1.z),0.0));\n    let leave=min(min(max(t0.x,t1.x),max(t0.y,t1.y)),min(max(t0.z,t1.z),surface_depth));\n    if (enter>=leave) { return color; }\n    let dt=(leave-enter)/20.0;\n    var transmittance=1.0;\n    var scattered=vec3f(0.0);\n    let sun=normalize(frame.sun_dir.xyz);\n    let g=effects().metal_spec_coat.w;\n    let phase=(1.0-g*g)/pow(1.0+g*g-2.0*g*dot(direction,sun),1.5);\n    for (var k=0u;k<20u;k++) {\n        let p=origin+direction*(enter+(f32(k)+0.5)*dt);\n        let density=steam_density(p);\n        if (density>1e-4) {\n            let extinction=exp(-density*90.0*dt);\n            let open=select(1.0,0.0,scene(spawn_point(p,sun,p,0.0),sun,1e20,true).t<1e19);\n            let lit=frame.sun_color.xyz*frame.sun_color.w*open*phase;\n            scattered+=transmittance*(lit+vec3f(effects().tint_absorption.w))*(1.0-extinction)*0.85;\n            transmittance*=extinction;\n        }\n    }\n    return scattered+transmittance*color;\n}\nfn texels_at(offset: u32, width: u32, height: u32, uv: vec2f) -> vec4f {\n    let size=vec2f(f32(width),f32(height));\n    let p=clamp(uv*size-vec2f(0.5),vec2f(0.0),size-vec2f(1.0));\n    let lo=vec2u(floor(p));\n    let hi=min(lo+vec2u(1u),vec2u(width-1u,height-1u));\n    let f=fract(p);\n    let a=content_texels[offset+lo.y*width+lo.x];\n    let b=content_texels[offset+lo.y*width+hi.x];\n    let c=content_texels[offset+hi.y*width+lo.x];\n    let d=content_texels[offset+hi.y*width+hi.x];\n    return mix(mix(a,b,f.x),mix(c,d,f.x),f.y);\n}\nfn atlas_texel(offset: u32, width: u32, x: u32, y: u32) -> vec3f {\n    let at=y*width+x;\n    let packed=u32(content_texels[offset+at/4u][at&3u]);\n    return vec3f(f32(packed&255u),f32((packed>>8u)&255u),f32(packed>>16u))/255.0;\n}\nfn atlas_at(offset: u32, width: u32, height: u32, uv: vec2f) -> vec3f {\n    let p=uv*vec2f(f32(width),f32(height))-vec2f(0.5);\n    let start=floor(p);\n    let f=p-start;\n    let lo=vec2u(clamp(start,vec2f(0.0),vec2f(f32(width-1u),f32(height-1u))));\n    let hi=vec2u(clamp(start+vec2f(1.0),vec2f(0.0),vec2f(f32(width-1u),f32(height-1u))));\n    let a=atlas_texel(offset,width,lo.x,lo.y);\n    let b=atlas_texel(offset,width,hi.x,lo.y);\n    let c=atlas_texel(offset,width,lo.x,hi.y);\n    let d=atlas_texel(offset,width,hi.x,hi.y);\n    return mix(mix(a,b,f.x),mix(c,d,f.x),f.y);\n}\nfn text_at(offset: u32, uv: vec2f) -> vec4f {\n    let grid=content_texels[offset];\n    let shape=vec4u(content_texels[offset+1u]);\n    let atlas=vec4u(content_texels[offset+2u]);\n    let picture=vec4u(content_texels[offset+3u]);\n    var color=vec4f(0.0);\n    if (picture.x>0u) { color=texels_at(offset+picture.z,picture.x,picture.y,uv); }\n    let cell=floor((uv-grid.xy)*grid.zw);\n    if (cell.x<0.0 || cell.y<0.0 || cell.x>=f32(shape.x) || cell.y>=f32(shape.y)) { return color; }\n    let index=u32(cell.y)*shape.x+u32(cell.x);\n    let pair=content_texels[offset+4u+index/2u];\n    let entry=vec2u(select(pair.xy,pair.zw,(index&1u)==1u));\n    let size=vec2f(f32(atlas.x),f32(atlas.y));\n    let half=vec2f(0.5)/size;\n    for (var k=0u;k<entry.y;k++) {\n        let at=entry.x+k;\n        let glyph=offset+shape.z+u32(content_texels[offset+shape.w+at/4u][at&3u])*5u;\n        let m=content_texels[glyph];\n        let t=content_texels[glyph+1u];\n        let clip=content_texels[glyph+2u];\n        let box=content_texels[glyph+3u];\n        let ink=content_texels[glyph+4u];\n        let f=vec2f(m.x*uv.x+m.y*uv.y+t.x,m.z*uv.x+m.w*uv.y+t.y);\n        if (any(f<max(clip.xy,vec2f(0.0))) || any(f>min(clip.zw,vec2f(1.0)))) { continue; }\n        let low=min(box.xy+half,box.zw);\n        let high=max(box.zw-half,low);\n        let sampled=atlas_at(offset+atlas.z,atlas.x,atlas.y,clamp(box.xy+(box.zw-box.xy)*f,low,high));\n        var coverage=sampled.x;\n        if (atlas.w==3u) {\n            let median=max(min(sampled.x,sampled.y),min(max(sampled.x,sampled.y),sampled.z));\n            coverage=select(0.0,1.0,median>=0.5);\n        }\n        let opacity=coverage*ink.a;\n        color=vec4f(ink.rgb*opacity+color.rgb*(1.0-opacity),opacity+color.a*(1.0-opacity));\n    }\n    return color;\n}\nfn content_at(slot: i32, uv: vec2f) -> vec4f {\n    if (slot < 0) { return vec4f(0.0); }\n    let info=content_info[u32(slot)];\n    if (info.width==0u || info.height==0u) { return vec4f(0.0); }\n    if (info.kind==1u) { return text_at(info.offset,uv); }\n    return texels_at(info.offset,info.width,info.height,uv);\n}\nconst CF_WEIGHTS = array<vec3f, 81>(\n    vec3f(5.039987e-05, 0.0, 0.00035778867),\n    vec3f(6.775547e-05, 0.0, 0.0005768482),\n    vec3f(0.00013374595, 0.0, 0.001105031),\n    vec3f(0.000244155, -8.0429134e-05, 0.001990094),\n    vec3f(0.0004620315, -0.00031065758, 0.0037327078),\n    vec3f(0.00075178174, -0.00079914776, 0.0060585286),\n    vec3f(0.0013901147, -0.0016784245, 0.01140122),\n    vec3f(0.0024506692, -0.0030452313, 0.020409657),\n    vec3f(0.0041841697, -0.00489408, 0.035484146),\n    vec3f(0.006491386, -0.0070713423, 0.05709876),\n    vec3f(0.008225769, -0.009268977, 0.07611217),\n    vec3f(0.00893323, -0.0110506825, 0.08910742),\n    vec3f(0.008651618, -0.011844616, 0.09585401),\n    vec3f(0.0075308583, -0.0111710355, 0.09771433),\n    vec3f(0.00574333, -0.009713182, 0.0970225),\n    vec3f(0.0034815131, -0.0075853216, 0.09534322),\n    vec3f(0.00069844024, -0.0048573813, 0.091060266),\n    vec3f(-0.0036886954, -0.0017018986, 0.083120264),\n    vec3f(-0.0063273897, 0.0017186198, 0.06969027),\n    vec3f(-0.008724144, 0.005763426, 0.05593676),\n    vec3f(-0.011347991, 0.009961927, 0.04306675),\n    vec3f(-0.014388451, 0.014161423, 0.031928584),\n    vec3f(-0.017747117, 0.018677758, 0.023228362),\n    vec3f(-0.021200482, 0.023959707, 0.016556388),\n    vec3f(-0.024560854, 0.030223139, 0.011426527),\n    vec3f(-0.027776832, 0.03802389, 0.00728438),\n    vec3f(-0.030845057, 0.046441782, 0.0033540253),\n    vec3f(-0.03346058, 0.055137716, 0.0),\n    vec3f(-0.03508027, 0.06285677, -0.0025753065),\n    vec3f(-0.035013195, 0.068301246, -0.004734569),\n    vec3f(-0.032630157, 0.071965545, -0.006312869),\n    vec3f(-0.027983842, 0.07395785, -0.00737929),\n    vec3f(-0.02177047, 0.07446333, -0.008106243),\n    vec3f(-0.0140442625, 0.07356939, -0.008540686),\n    vec3f(-0.0049150633, 0.07139566, -0.008724192),\n    vec3f(0.0046487506, 0.06808836, -0.008696091),\n    vec3f(0.0153741175, 0.063680954, -0.008493348),\n    vec3f(0.026958833, 0.058147695, -0.008150433),\n    vec3f(0.03911687, 0.051680956, -0.007698217),\n    vec3f(0.05145111, 0.04444711, -0.0071432022),\n    vec3f(0.063475594, 0.036714625, -0.006503886),\n    vec3f(0.07455033, 0.028763412, -0.005807879),\n    vec3f(0.08411886, 0.020992132, -0.0050818375),\n    vec3f(0.091669224, 0.0137893725, -0.00435086),\n    vec3f(0.09618923, 0.007610871, -0.0036377995),\n    vec3f(0.097940184, 0.002461353, -0.0029763517),\n    vec3f(0.09633994, -0.0015017615, -0.0023928043),\n    vec3f(0.0919427, -0.004451467, -0.0018944021),\n    vec3f(0.08495084, -0.0062283436, -0.0014817494),\n    vec3f(0.07555293, -0.0070042815, -0.0011499332),\n    vec3f(0.06515909, -0.006994329, -0.00089005305),\n    vec3f(0.05535661, -0.0064279693, -0.00069090264),\n    vec3f(0.046014983, -0.005523557, -0.0005405573),\n    vec3f(0.037232462, -0.004469, -0.00042767118),\n    vec3f(0.029350668, -0.0034102271, -0.0003423706),\n    vec3f(0.022698376, -0.0024474342, -0.00027671963),\n    vec3f(0.017146055, -0.001637843, -0.00022480598),\n    vec3f(0.012616245, -0.0010029421, -0.00018254654),\n    vec3f(0.00910757, -0.0005379737, -0.00014732922),\n    vec3f(0.0066325837, -0.00022167167, -0.00011759913),\n    vec3f(0.004884865, -2.4797864e-05, -9.247229e-05),\n    vec3f(0.0034370946, 0.0, -7.142631e-05),\n    vec3f(0.0023721277, 0.0, -5.40864e-05),\n    vec3f(0.0016515272, 0.0, -4.010171e-05),\n    vec3f(0.0011923393, 0.0, -2.9092696e-05),\n    vec3f(0.00084797293, 0.0, -2.064557e-05),\n    vec3f(0.000605779, 0.0, -1.4331459e-05),\n    vec3f(0.0004273203, 0.0, -9.733269e-06),\n    vec3f(0.00030592916, 0.0, -6.469539e-06),\n    vec3f(0.00021032631, 0.0, -4.210301e-06),\n    vec3f(0.00014668911, 0.0, -2.683982e-06),\n    vec3f(0.0001021725, 0.0, -1.6768181e-06),\n    vec3f(7.628618e-05, 0.0, -1.027186e-06),\n    vec3f(5.108625e-05, 0.0, -6.1728224e-07),\n    vec3f(3.186762e-05, 0.0, -3.6408065e-07),\n    vec3f(1.9218627e-05, 0.0, -2.1085688e-07),\n    vec3f(1.9218627e-05, 0.0, -1.1996055e-07),\n    vec3f(1.2648995e-05, 0.0, 2.5954927e-07),\n    vec3f(1.2648995e-05, 0.0, 2.5954927e-07),\n    vec3f(1.2648995e-05, 0.0, 2.5954927e-07),\n    vec3f(-5.4965028e-08, 0.0, -1.0592136e-08),\n);\nfn cf_fresnel(cos_i: f32, na: f32, nb: f32) -> vec3f {\n    let sin2=(na/nb)*(na/nb)*(1.0-cos_i*cos_i);\n    if (sin2>=1.0) { return vec3f(1.0,1.0,0.0); }\n    let cos_t=sqrt(1.0-sin2);\n    let rs=(na*cos_i-nb*cos_t)/(na*cos_i+nb*cos_t);\n    let rp=(nb*cos_i-na*cos_t)/(nb*cos_i+na*cos_t);\n    return vec3f(rs,rp,cos_t);\n}\nfn cf_film(cosine: f32, thickness: f32, n1: f32, nf: f32, n3: f32) -> vec3f {\n    let first=cf_fresnel(clamp(cosine,0.0,1.0),n1,nf);\n    if (first.z<=0.0) { return vec3f(1.0); }\n    let second=cf_fresnel(first.z,nf,n3);\n    if (second.z<=0.0) { return vec3f(1.0); }\n    let r12=first.xy;\n    let r23=second.xy;\n    let a=r12*r12+r23*r23;\n    let b=2.0*r12*r23;\n    let e=1.0+r12*r12*r23*r23;\n    let path=4.0*PI*nf*thickness*first.z;\n    var weights=CF_WEIGHTS;\n    var total=vec3f(0.0);\n    for (var k=0u;k<81u;k++) {\n        let c=cos(path/(380.0+5.0*f32(k)));\n        let reflect=0.5*((a.x+b.x*c)/(e.x+b.x*c)+(a.y+b.y*c)/(e.y+b.y*c));\n        total+=weights[k]*reflect;\n    }\n    return clamp(total,vec3f(0.0),vec3f(1.0));\n}\nfn film_active(s: Shaded) -> bool {\n    return s.thin_film>0.0 && s.thin_film_amount>0.0;\n}\nfn cf_surface(cosine: f32, thickness: f32, film_ior: f32, ior: f32, entering: bool) -> vec3f {\n    let nf=max(film_ior,1.01);\n    if (entering) { return cf_film(cosine,thickness,1.0,nf,ior); }\n    return cf_film(cosine,thickness,1.0,nf/ior,1.0/ior);\n}\nfn transmissive_fresnel(s: Shaded, cosine: f32, ratio: f32, entering: bool, ior: f32) -> vec3f {\n    let plain=vec3f(fresnel_dielectric(cosine,ratio));\n    if (!film_active(s)) { return plain; }\n    let filmed=cf_surface(cosine,s.thin_film,s.thin_film_ior,ior,entering);\n    return mix(plain,filmed,clamp(s.thin_film_amount,0.0,1.0));\n}\nconst IOR_SPLIT: f32 = 1e-4;\nfn channel_mask(channel: u32) -> vec3f {\n    return vec3f(select(0.0,1.0,channel==0u),select(0.0,1.0,channel==1u),select(0.0,1.0,channel==2u));\n}\nfn channel_ior(s: Shaded, channel: u32) -> f32 {\n    if (channel>2u) { return max(s.ior,1.01); }\n    return max(dispersed_ior(s.ior,s.dispersion)[channel],1.01);\n}\nfn glossy_share(s: Shaded) -> f32 {\n    return 1.0-clamp(s.transmission,0.0,1.0)*(1.0-clamp(s.metalness,0.0,1.0));\n}\nfn sample_bsdf(s: Shaded, wo: vec3f, entering: bool, carried: u32, state: ptr<function,u32>, u: vec4f) -> LightSample {\n    let transmission=clamp(s.transmission,0.0,1.0)*(1.0-clamp(s.metalness,0.0,1.0));\n    if (rnd(state,u.w)<transmission) {\n        let iors=dispersed_ior(s.ior,s.dispersion);\n        var channel=carried;\n        var weight=vec3f(1.0);\n        if (channel>2u && max(abs(iors.x-iors.y),abs(iors.z-iors.y))>IOR_SPLIT) {\n            channel=min(u32(rnd(state,u.z)*3.0),2u);\n            weight=channel_mask(channel)*3.0;\n        }\n        let ior=channel_ior(s,channel);\n        let ratio=select(1.0/ior,ior,entering);\n        let live=select(channel_mask(channel),vec3f(1.0),channel>2u);\n        let fresnel=transmissive_fresnel(s,max(wo.z,0.0),ratio,entering,ior)*live;\n        let chance=dot(fresnel,vec3f(1.0))/dot(live,vec3f(1.0));\n        let incident=-wo;\n        let mirrored=reflect(incident,vec3f(0.0,0.0,1.0));\n        if (rnd(state,u.x)<chance) {\n            return LightSample(mirrored,-1.0,channel,weight*fresnel/chance,1u);\n        }\n        let refracted=refract_dir(incident,vec3f(0.0,0.0,1.0),1.0/ratio);\n        if (refracted.w>0.0) { return LightSample(normalize(refracted.xyz),-2.0,channel,weight*(live-fresnel)/(1.0-chance),2u); }\n        return LightSample(mirrored,-1.0,channel,weight*live,1u);\n    }\n    let coat_weight=clamp(s.clearcoat*(0.12+0.88*(0.04+0.96*pow(1.0-max(wo.z,0.0),5.0))),0.0,0.9);\n    let spec_weight=(1.0-coat_weight)*clamp(s.metalness+s.specular,0.0,0.9);\n    let pick=rnd(state,u.z);\n    if (pick<coat_weight+spec_weight) {\n        let coat=pick<coat_weight;\n        let alpha=alpha_rough(select(s.roughness,s.clearcoat_roughness,coat));\n        let u1=rnd(state,u.x);\n        let u2=rnd(state,u.y);\n        let phi=2.0*PI*u1;\n        let cos_theta=sqrt((1.0-u2)/(1.0+(alpha*alpha-1.0)*u2));\n        let h=vec3f(cos(phi)*sqrt(max(0.0,1.0-cos_theta*cos_theta)),sin(phi)*sqrt(max(0.0,1.0-cos_theta*cos_theta)),cos_theta);\n        let wi=reflect(-wo,h);\n        return LightSample(wi,bsdf_pdf(s,wo,wi),3u,vec3f(1.0),1u);\n    }\n    let u1=rnd(state,u.x);\n    let u2=rnd(state,u.y);\n    let phi=2.0*PI*u1;\n    let r=sqrt(u2);\n    let wi=vec3f(cos(phi)*r,sin(phi)*r,sqrt(1.0-u2));\n    return LightSample(wi,bsdf_pdf(s,wo,wi),3u,vec3f(1.0),0u);\n}\nfn bsdf_pdf(s: Shaded, wo: vec3f, wi: vec3f) -> f32 {\n    if (wi.z<=0.0) { return 0.0; }\n    let trans=clamp(s.transmission,0.0,1.0)*(1.0-clamp(s.metalness,0.0,1.0));\n    let coat=clamp(s.clearcoat*(0.12+0.88*(0.04+0.96*pow(1.0-max(wo.z,0.0),5.0))),0.0,0.9);\n    let spec=(1.0-coat)*clamp(s.metalness+s.specular,0.0,0.9);\n    let h=normalize(wo+wi);\n    let a=alpha_rough(s.roughness);\n    let ca=alpha_rough(s.clearcoat_roughness);\n    let denom=max(4.0*abs(dot(wi,h)),1e-8);\n    let base_pdf=ggx_d(max(h.z,0.0),a*a)*max(h.z,0.0)/denom;\n    let coat_pdf=ggx_d(max(h.z,0.0),ca*ca)*max(h.z,0.0)/denom;\n    return (1.0-trans)*(coat*coat_pdf+spec*base_pdf+(1.0-coat-spec)*wi.z/PI);\n}\nfn trace_lights_start() {}\nfn emitter_weight(hit: Hit, p: vec3f, previous_point: vec3f, previous_pdf: f32) -> f32 {\n    return 1.0;\n}\nfn light_hits(o: vec3f, d: vec3f, limit: f32, previous_point: vec3f, previous_pdf: f32) -> vec3f {\n    return vec3f(0.0);\n}\nfn light_slots() -> u32 {\n    return 0u;\n}\nfn sampled_emitter(hit: Hit) -> bool {\n    return false;\n}\nfn direct_light(slot: u32, transform: mat3x3f, p: vec3f, origin: vec3f, geometric: vec3f, state: ptr<function,u32>, u: vec4f) -> Candidate {\n    return Candidate(vec3f(0.0,0.0,1.0),vec3f(0.0),0.0,0.0,vec3f(0.0,0.0,1.0),-1.0,0u);\n}\nfn trace_lights_end() {}\nfn catcher_visible(origin: vec3f, direction: vec3f, limit: f32) -> f32 {\n    if (TRANSMISSIVE_SHADOWS) { return dot(shadow_passed(origin,direction,limit),vec3f(0.2126,0.7152,0.0722)); }\n    return select(1.0,0.0,occluded(origin,direction,limit));\n}\nfn catcher_shadow(p: vec3f, n: vec3f, o: vec3f, t: f32, state: ptr<function,u32>) -> f32 {\n    let transform=basis(n);\n    let origin=spawn_point(p,n,o,t);\n    var open=0.0;\n    var seen=0.0;\n    for (var k=0u;k<2u+light_slots();k++) {\n        let u=sobol(light_dim(k));\n        var direction=vec3f(0.0,0.0,1.0);\n        var strength=0.0;\n        var limit=1e19;\n        var shape=0u;\n        if (k==0u) {\n            if (frame.sun_color.w<=0.0) { continue; }\n            direction=normalize(frame.sun_dir.xyz);\n            if (frame.sun_dir.w<1.0) {\n                let height=1.0-rnd(state,u.x)*(1.0-frame.sun_dir.w);\n                let radius=sqrt(max(0.0,1.0-height*height));\n                let phi=2.0*PI*rnd(state,u.y);\n                direction=normalize(basis(direction)*vec3f(radius*cos(phi),radius*sin(phi),height));\n            }\n            strength=dot(frame.sun_color.xyz,vec3f(0.2126,0.7152,0.0722))*frame.sun_color.w*max(dot(direction,n),0.0);\n        } else if (k==1u) {\n            if (sky_cdf.width==0u) { continue; }\n            for (var j=0u;j<4u;j++) {\n                let light=sky_sample(vec2f(random(state),random(state)));\n                if (!(light.pdf>0.0)) { continue; }\n                let part=dot(sky_surface_radiance(light.direction),vec3f(0.2126,0.7152,0.0722))*max(dot(light.direction,n),0.0)/light.pdf*0.25;\n                if (!(part>0.0)) { continue; }\n                open+=part;\n                seen+=part*catcher_visible(origin,light.direction,1e19);\n            }\n            continue;\n        } else {\n            let picked=direct_light(k-2u,transform,p,origin,n,state,u);\n            if (picked.limit<0.0) { continue; }\n            direction=picked.direction;\n            limit=picked.limit;\n            shape=picked.shape;\n            strength=dot(picked.scale,vec3f(0.2126,0.7152,0.0722))*max(picked.wi.z,0.0)/select(1.0,picked.pdf,picked.pdf>0.0);\n        }\n        if (!(strength>0.0)) { continue; }\n        if (shape>0u) {\n            let i=shape-1u;\n            var hit_t=1e20;\n            if (u32(shapes[i].a.w)==5u) { hit_t=union_hit(i,origin,direction,1e20); } else { hit_t=primitive_hit(i,origin,direction,1e20); }\n            if (hit_t<=0.0001 || hit_t>=1e19) { continue; }\n            limit=hit_t*0.9999-0.0002;\n        }\n        open+=strength;\n        if (limit>0.0) { seen+=strength*catcher_visible(origin,direction,limit); } else { seen+=strength; }\n    }\n    if (open<=0.0) { return 0.0; }\n    return clamp(1.0-seen/open,0.0,1.0);\n}\nconst INDIRECT_LUMA: vec3f = vec3f(0.2126,0.7152,0.0722);\nfn indirect_capped(value: vec3f, deep: bool) -> vec3f {\n    if (!(CLAMP_INDIRECT>0.0) || !deep) { return value; }\n    let luma=dot(value,INDIRECT_LUMA);\n    if (!(luma>CLAMP_INDIRECT)) { return value; }\n    return value*(CLAMP_INDIRECT/luma);\n}\nfn radiance(o0: vec3f, d0: vec3f, state: ptr<function,u32>, first_albedo: ptr<function,vec4f>, first_normal: ptr<function,vec4f>) -> vec3f {\n    var o=o0;\n    var d=d0;\n    var throughput=vec3f(1.0);\n    var result=vec3f(0.0);\n    var previous_pdf=0.0;\n    var carried=3u;\n    var lobes=vec3u(0u);\n    var last=false;\n    var lit_vertex=false;\n    var through=false;\n    var diffused=false;\n    for (var bounce=0u;bounce<=TOTAL_BOUNCES+1u;bounce++) {\n        if (SOBOL) { rng.z=16u*(bounce+1u); }\n        let hit=scene(o,d,1e20,false);\n        let seen=TRANSMISSIVE_SHADOWS && through;\n        if (!seen) { result+=indirect_capped(throughput*light_hits(o,d,hit.t,spawn_point(o,-d,o,0.0),previous_pdf),bounce>1u); }\n        if (hit.t>=1e19) {\n            if (bounce==0u && frame.forward.w>0.0) { break; }\n            if (seen && sky_cdf.width>0u) { break; }\n            let pdf=sky_pdf(d);\n            let weight=select(1.0,previous_pdf/(previous_pdf+pdf),bounce>0u && previous_pdf>0.0);\n            let environment=select(sky_radiance(d),sky_surface_radiance(d),bounce>0u);\n            result+=indirect_capped(throughput*environment*weight,bounce>1u);\n            break;\n        }\n        let p=o+d*hit.t;\n        var n=select(hit.n,-hit.n,dot(hit.n,d)>0.0);\n        if (bounce==0u && frame.forward.w>1.5 && hit.material==u32(frame.forward.w)-2u) {\n            *first_albedo=vec4f(0.0,0.0,0.0,catcher_shadow(p,n,o,hit.t,state));\n            *first_normal=vec4f(n,1.0);\n            break;\n        }\n        let m=materials[hit.material];\n        var s=surface(m);\n        let layers=array<vec4f,4>(m.layer0,m.layer1,m.layer2,m.layer3);\n        for (var i=0u;i<4u;i++) {\n            if (layers[i].x>0.0 && layers[i].z!=0.0) {\n                let detailed=detail_layer(s,n,layers[i],m.params[i*4u],m.params[i*4u+1u],m.params[i*4u+2u],m.params[i*4u+3u],p);\n                s=detailed.shade;\n                n=detailed.normal;\n            }\n        }\n        let geometric=select(hit.geometric,-hit.geometric,dot(hit.geometric,d)>0.0);\n        if (dot(n,geometric)<0.05) { n=normalize(n+geometric*0.1); }\n        var layer=content_layers[hit.material];\n        var shown=true;\n        if (hit.shape==0u) {\n            layer=hit_layer(hit.surface,hit.material);\n            let face=(u32(triangles[hit.surface].b.w)>>3u)&3u;\n            let front=dot(hit.geometric,d)<0.0;\n            shown=face==0u || (face==1u && front) || (face==2u && !front);\n        }\n        let slot=i32(round(layer.layer.x));\n        if (slot>=0 && shown) {\n            let surface_data=instance_surfaces[hit.surface];\n            let at=content_uv(hit.uv,surface_data.transform);\n            if (content_inside(at,surface_data.crop)) {\n                let texel=content_at(slot,at);\n                let blend=u32(round(layer.layer.y));\n                s=content_composite(s,blend,texel,layer.layer.z,layer.extra.x);\n                if (layer.layer.w!=0.0) {\n                    let info=content_info[u32(slot)];\n                    if (info.width>0u && info.height>0u) {\n                        let tangent=normalize(hit.tangent-n*dot(hit.tangent,n));\n                        let bitangent=normalize(hit.bitangent-n*dot(hit.bitangent,n));\n                        let step=vec2f(1.5/f32(info.width),1.5/f32(info.height));\n                        let gx=content_at(slot,at+vec2f(step.x,0.0)).a-content_at(slot,at-vec2f(step.x,0.0)).a;\n                        let gy=content_at(slot,at+vec2f(0.0,step.y)).a-content_at(slot,at-vec2f(0.0,step.y)).a;\n                        n=content_emboss(n,tangent,bitangent,vec2f(gx,gy),layer.layer.w);\n                        let geometric=select(hit.geometric,-hit.geometric,dot(hit.geometric,d)>0.0);\n                        if (dot(n,geometric)<0.05) { n=normalize(n+geometric*0.1); }\n                    }\n                }\n            }\n        }\n        s.roughness=max(s.roughness,ROUGHNESS_FLOOR);\n        s.clearcoat_roughness=max(s.clearcoat_roughness,ROUGHNESS_FLOOR);\n        if (FILTER_GLOSSY>0.0 && diffused) {\n            s.roughness=max(s.roughness,FILTER_GLOSSY);\n            s.clearcoat_roughness=max(s.clearcoat_roughness,FILTER_GLOSSY);\n        }\n        if (bounce==0u) {\n            *first_albedo=vec4f(s.base,1.0);\n            *first_normal=vec4f(n,1.0);\n        }\n        s.emission*=emitter_weight(hit,p,spawn_point(o,-d,o,0.0),previous_pdf);\n        if (seen && sampled_emitter(hit)) { s.emission=vec3f(0.0); }\n        result+=indirect_capped(throughput*s.emission,bounce>1u);\n        if (last) { break; }\n        let transform=basis(n);\n        let wo=transpose(transform)*(-d);\n        let origin=spawn_point(p,n,o,hit.t);\n        let entering=dot(hit.n,d)<0.0;\n        for (var k=0u;k<2u+light_slots();k++) {\n            let u=sobol(light_dim(k));\n            var local=vec3f(0.0,0.0,1.0);\n            var direction=vec3f(0.0,0.0,1.0);\n            var strength=vec3f(0.0);\n            var limit=1e19;\n            var shape=0u;\n            var pdf=0.0;\n            var lit=s;\n            if (k==0u) {\n                if (frame.sun_color.w<=0.0) { continue; }\n                direction=normalize(frame.sun_dir.xyz);\n                if (frame.sun_dir.w<1.0) {\n                    let height=1.0-rnd(state,u.x)*(1.0-frame.sun_dir.w);\n                    let radius=sqrt(max(0.0,1.0-height*height));\n                    let phi=2.0*PI*rnd(state,u.y);\n                    let orient=basis(direction);\n                    direction=normalize(orient*vec3f(radius*cos(phi),radius*sin(phi),height));\n                }\n                local=transpose(transform)*direction;\n                if (local.z<=0.0) { continue; }\n                var shade=1.0;\n                if (bounce==0u && effects().base_roughness.y>0.0) { shade=canopy(p); }\n                strength=frame.sun_color.xyz*(frame.sun_color.w*shade);\n            } else if (k==1u) {\n                if (sky_cdf.width==0u) { continue; }\n                let light=sky_sample(vec2f(rnd(state,u.x),rnd(state,u.y)));\n                direction=light.direction;\n                local=transpose(transform)*direction;\n                if (local.z<=0.0) { continue; }\n                strength=sky_surface_radiance(direction);\n                pdf=light.pdf;\n                if (!(pdf>0.0)) { continue; }\n            } else {\n                let picked=direct_light(k-2u,transform,p,origin,geometric,state,u);\n                if (picked.limit<0.0) { continue; }\n                local=picked.wi;\n                direction=picked.direction;\n                strength=picked.scale;\n                pdf=picked.pdf;\n                limit=picked.limit;\n                shape=picked.shape;\n                lit.roughness=max(s.roughness,picked.roughness);\n            }\n            if (pdf>0.0) {\n                let bsdf=bsdf_pdf(s,wo,local);\n                if (k==1u) {\n                    strength/=pdf+bsdf;\n                } else {\n                    let ratio=bsdf/pdf;\n                    strength/=pdf*(1.0+ratio*ratio);\n                }\n            }\n            if (shape>0u) {\n                let i=shape-1u;\n                var t=1e20;\n                if (u32(shapes[i].a.w)==5u) { t=union_hit(i,origin,direction,1e20); } else { t=primitive_hit(i,origin,direction,1e20); }\n                if (t<=0.0001 || t>=1e19) { continue; }\n                limit=t*0.9999-0.0002;\n            }\n            if (limit>0.0) {\n                if (TRANSMISSIVE_SHADOWS) {\n                    var unshadowed=throughput*eval(lit,wo,local)*select(1.0,glossy_share(s),pdf>0.0)*local.z*strength;\n                    let reach=throughput*strength;\n                    let keep=clamp(16.0*PI*max(unshadowed.r,max(unshadowed.g,unshadowed.b))/max(max(reach.r,max(reach.g,reach.b)),1e-30),0.0,1.0);\n                    if (keep<1.0) {\n                        if (rnd(state,u.w)>=keep) { continue; }\n                        unshadowed/=keep;\n                    }\n                    result+=indirect_capped(unshadowed*shadow_passed(origin,direction,limit),bounce>0u);\n                    continue;\n                } else if (occluded(origin,direction,limit)) { continue; }\n            }\n            result+=indirect_capped(throughput*eval(lit,wo,local)*select(1.0,glossy_share(s),pdf>0.0)*local.z*strength,bounce>0u);\n        }\n        let sample=sample_bsdf(s,wo,entering,carried,state,sobol(3u));\n        let wi=sample.dir;\n        diffused=diffused || sample.lobe==0u;\n        lobes+=vec3u(select(0u,1u,sample.lobe==0u),select(0u,1u,sample.lobe==1u),select(0u,1u,sample.lobe==2u));\n        last=bounce>=TOTAL_BOUNCES || any(lobes>vec3u(DIFFUSE_BOUNCES,GLOSSY_BOUNCES,TRANSMISSION_BOUNCES));\n        through=sample.pdf< -1.5 && lit_vertex;\n        lit_vertex=sample.pdf>0.0 || through;\n        if (sample.pdf<0.0) {\n            throughput*=sample.weight;\n            carried=sample.channel;\n            if (sample.pdf< -1.5) {\n                throughput*=sqrt(clamp(s.base,vec3f(0.0),vec3f(1.0)))*beer(s.thickness,s.subsurface_tint,s.absorption);\n            }\n            d=normalize(transform*wi);\n            previous_pdf=0.0;\n        } else {\n            if (wi.z<=0.0 || sample.pdf<=0.0) { break; }\n            throughput*=eval(s,wo,wi)*glossy_share(s)*wi.z/sample.pdf;\n            d=normalize(transform*wi);\n            previous_pdf=sample.pdf;\n        }\n        o=spawn_point(p,d,o,hit.t);\n        if (bounce>=3u) {\n            let keep=clamp(max(throughput.r,max(throughput.g,throughput.b)),0.05,0.95);\n            if (rnd(state,sobol(0u).x)>keep) { break; }\n            throughput/=keep;\n        }\n    }\n    return result;\n}\n@compute @workgroup_size(8,8)\nfn main(@builtin(workgroup_id) group: vec3u, @builtin(local_invocation_id) local: vec3u) {\n    let lane=local.y*8u+local.x;\n    let first_lane=(frame.counts.w>>16u)&0x7fu;\n    let end_lane=frame.counts.w>>23u;\n    let blocks=(frame.size.y+7u)/8u;\n    let block=((frame.counts.w&0xffffu)+group.y)*BLOCK_STRIDE%blocks;\n    let gid=vec3u((group.x+frame.columns.x)*8u+local.x,block*8u+local.y,0u);\n    if (lane<first_lane || lane>=end_lane || gid.x>=frame.size.x || gid.y>=frame.size.y) { return; }\n    let index=gid.y*frame.size.x+gid.x;\n    let pixel=gid.xy+frame.window.zw;\n    let stream=pixel.y*frame.window.x+pixel.x;\n    var sum=Accum(vec4f(0.0),vec4f(0.0),vec4f(0.0));\n    if (frame.size.z>0u) { sum=accum[index]; }\n    if (sum.color.w<0.0) { return; }\n    for (var sample=0u;sample<frame.size.w;sample++) {\n        var first_albedo=vec4f(0.0);\n        var first_normal=vec4f(0.0);\n        var state=pcg(stream*1973u+pcg(frame.counts.z*9277u+(frame.size.z+sample)*7919u+26699u));\n        if (SOBOL) { rng=vec3u(hash_iqnt2d(pixel.x,pixel.y)^frame.counts.z,frame.size.z+sample,0u); }\n        let pixel_filter=sobol(0u);\n        let jitter=vec2f(rnd(&state,pixel_filter.x),rnd(&state,pixel_filter.y));\n        let ndc=(vec2f(pixel)+jitter)/vec2f(frame.window.xy)*2.0-1.0;\n        let kind=u32(round(frame.origin.w));\n        var direction=normalize(frame.forward.xyz+frame.right.xyz*(ndc.x+frame.lens.x)+frame.up.xyz*(-ndc.y+frame.lens.y));\n        var origin=frame.origin.xyz;\n        if (kind==1u) {\n            direction=normalize(frame.forward.xyz);\n            origin+=normalize(frame.right.xyz)*((ndc.x+frame.lens.x)*frame.right.w)+normalize(frame.up.xyz)*((-ndc.y+frame.lens.y)*frame.up.w);\n        } else if (kind==2u) {\n            let phi=(ndc.x*0.5)*2.0*PI;\n            let theta=(ndc.y*0.5+0.5)*PI;\n            let local=vec3f(sin(theta)*sin(phi),cos(theta),-sin(theta)*cos(phi));\n            direction=normalize(normalize(frame.right.xyz)*local.x+normalize(frame.up.xyz)*local.y-normalize(frame.forward.xyz)*local.z);\n        } else if (frame.lens.z>0.0) {\n            let focus=origin+direction*(frame.lens.w/dot(direction,normalize(frame.forward.xyz)));\n            let aperture=sobol(1u);\n            let radius=sqrt(rnd(&state,aperture.x))*frame.lens.z;\n            let phi=2.0*PI*rnd(&state,aperture.y);\n            origin+=normalize(frame.right.xyz)*(radius*cos(phi))+normalize(frame.up.xyz)*(radius*sin(phi));\n            direction=normalize(focus-origin);\n        }\n        let traced=radiance(origin,direction,&state,&first_albedo,&first_normal);\n        var value=traced;\n        if (effects().thick_sub_film.w>0.0) {\n            let depth=scene(origin,direction,1e20,false).t;\n            value=steam_composite(origin,direction,depth,traced);\n        }\n        sum.color=vec4f(sum.color.xyz+value,sum.color.w);\n        sum.albedo+=first_albedo;\n        sum.normal+=first_normal;\n    }\n    sum.color.w=f32(frame.size.z+frame.size.w);\n    accum[index]=sum;\n    let count=max(sum.color.w,1.0);\n    let n=sum.normal.xyz/count;\n    textureStore(output,vec2i(gid.xy),vec4f(sum.color.xyz/count,select(1.0,sum.albedo.w/count,frame.forward.w>0.0)));\n    textureStore(albedo,vec2i(gid.xy),sum.albedo/count);\n    textureStore(normal,vec2i(gid.xy),vec4f(select(vec3f(0.0),normalize(n),length(n)>1e-6),sum.normal.w/count));\n}\n";