cuneus 0.5.0

A WGPU-based shader development tool
Documentation
// Enes Altun, 3 Sep 2025 
// This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.

struct TimeUniform {
    time: f32,
    delta: f32,
    frame: u32,
    _padding: u32,
};
@group(0) @binding(0) var<uniform> time_data: TimeUniform;

struct SystemParams {
    a: f32, 
    b: f32,
    c: f32,
    dof_amount: f32,         
    dof_focal_dist: f32,     
    brightness: f32,         
    color1_r: f32,           
    color1_g: f32,           
    color1_b: f32,           
    color2_r: f32,           
    color2_g: f32,           
    color2_b: f32,           
    zoom: f32,
}
@group(1) @binding(0) var output: texture_storage_2d<rgba16float, write>;
@group(1) @binding(1) var<uniform> custom: SystemParams;

@group(2) @binding(0) var<storage, read_write> atomic_buffer: array<atomic<u32>>;

alias v4 = vec4<f32>;
alias v3 = vec3<f32>;
alias v2 = vec2<f32>;
alias m2 = mat2x2<f32>;
alias m3 = mat3x3<f32>;
alias m4 = mat4x4<f32>;

var<workgroup> texCoords: array<array<vec2<f32>, 16>, 16>;
const pi = 3.14159265359;
const tau = 6.28318530718;
var<private> R: v2;
var<private> U: v2;
var<private> seed: u32;
//hash and some interesting rots from: https://compute.toys/view/90; wrighter
fn rot(a: f32)-> m2{ return m2(cos(a), -sin(a),sin(a), cos(a));}
fn rotX(a: f32) -> m3{
    let r = rot(a); return m3(1.,0.,0.,0.,r[0][0],r[0][1],0.,r[1][0],r[1][1]);
}
fn rotY(a: f32) -> m3{
    let r = rot(a); return m3(r[0][0],0.,r[0][1],0.,1.,0.,r[1][0],0.,r[1][1]);
}
fn rotZ(a: f32) -> m3{
    let r = rot(a); return m3(r[0][0],r[0][1],0.,r[1][0],r[1][1],0.,0.,0.,1.);
}

fn hash_u(_a: u32) -> u32{ var a = _a; a ^= a >> 16;a *= 0x7feb352du;a ^= a >> 15;a *= 0x846ca68bu;a ^= a >> 16;return a; }
fn hash_f() -> f32{ var s = hash_u(seed); seed = s;return ( f32( s ) / f32( 0xffffffffu ) ); }
fn hash_v2() -> v2{ return v2(hash_f(), hash_f()); }
fn hash_v3() -> v3{ return v3(hash_f(), hash_f(), hash_f()); }

fn sample_disk() -> v2{
    let r = hash_v2();
    return v2(sin(r.x*tau),cos(r.x*tau))*sqrt(r.y);
}

const COL_CNT = 4;
var<private> kCols = array<v3, COL_CNT>( 
     vec3(1.0, 1.0, 1.0), vec3(0.2, 0.9, 1.0),
     vec3(1.0, 1.0, 1.0) * 1.5, vec3(1.0, 0.3, 0.7)
);

fn mix_cols(_idx: f32)->v3{
    let idx = _idx%1.;
    var cols_idx = i32(idx*f32(COL_CNT));
    var fract_idx = fract(idx*f32(COL_CNT));
    fract_idx = smoothstep(0.,1.,fract_idx);
    return mix( kCols[cols_idx], kCols[(cols_idx + 1)%COL_CNT], fract_idx );
}

fn projParticle(_p: v3) -> v3{
    var p = _p;
    
    p += sin(v3(1.8, 2.9, 1.4) + time_data.time*0.5)*0.08;
    
    p.z += 1.4;
    p /= p.z*0.315*custom.zoom;
    p.z = _p.z;
    p.x /= R.x/R.y;
    return p;
}

@compute @workgroup_size(256, 1,1)
fn Splat(@builtin(global_invocation_id) id: vec3<u32>) {
    let Ru = vec2<u32>(textureDimensions(output));
    R = v2(Ru); U = v2(id.xy);
    
    seed = hash_u(id.x + hash_u(Ru.x*id.y*200u)*20u + hash_u(id.z)*250u);
    seed = hash_u(seed);

    let iters = 90;
    
    var t = time_data.time*0.6 - hash_f()*0.4;
    var env = t + sin(t*0.4);

    var p = (hash_v3() - 0.5)*1.2;

    var charge: f32;
    if hash_f() < 0.5 {
        charge = 12.0;
    } else {
        charge = -1.0;
    }
    let focusDist = (custom.dof_focal_dist*2. - 1.)*2.;
    let dofFac = 1./v2(R.x/R.y,1.)*custom.dof_amount;
    
    for(var i = 0; i < iters; i++){
        let r = hash_f();
        if(r < 0.4){
            let field_strength = 1.4 + custom.a*1.2;
            p = p/(dot(p,p)*field_strength*charge + 0.2);
            if(hash_f() < 0.1){
                charge = -charge;
                p += v3(sin(env), cos(env*1.2), sin(env*0.8))*1.1;
            }
        } 
        else if(r < 0.6){
            let gradient_factor = 0.9 + custom.b*1.1;
            p = p/(dot(p,p)*gradient_factor + 0.25); 
            
            let field_dir = normalize(p + v3(0.001, 0.001, 0.001));
            p += field_dir * charge * .15;
        }
        else {
            p += v3(custom.c*charge, 1.2, 0.1*charge);
            
            if(length(p) > 1.2){
                p *= 1.1;
                charge = -charge;
            } else if(length(p) < 1.3){
                p *= 1.8;
            }
            if(hash_f() < 0.1){
                p += (hash_v3() - 0.1)*0.01*abs(charge);
            }
        }
        
        var q = projParticle(p);
        var k = q.xy;

        k += sample_disk()*abs(q.z - focusDist)*0.03*dofFac;
        
        let uv = k.xy/2. + 0.5;
        let cc = vec2<u32>(uv.xy*R.xy);
        let idx = cc.x + Ru.x * cc.y;
        if ( 
            uv.x > 0. && uv.x < 1. 
            && uv.y > 0. && uv.y < 1. 
            && idx < u32(Ru.x*Ru.y)
            ){     
            let field_intensity = abs(charge) + length(p) * 0.5;
            if (charge > 0.0) {
                atomicAdd(&atomic_buffer[idx], u32(field_intensity * 200.0));
            } else {
                atomicAdd(&atomic_buffer[idx + Ru.x*Ru.y], u32(field_intensity * 200.0));
            }
        }
    }
}

@compute @workgroup_size(16, 16, 1)
fn main_image(@builtin(global_invocation_id) id: vec3<u32>) {
    let res = vec2<u32>(textureDimensions(output));
    if (id.x >= res.x || id.y >= res.y) { return; }

    R = v2(res);
    let hist_id = id.x + u32(R.x) * id.y;

    var col1 = f32(atomicLoad(&atomic_buffer[hist_id])) * v3(custom.color1_r, custom.color1_g, custom.color1_b);
    var col2 = f32(atomicLoad(&atomic_buffer[hist_id + res.x*res.y])) * v3(custom.color2_r, custom.color2_g, custom.color2_b);
    var col = (col1 + col2);
    
    let sc = 25000.0;
    col = log(col*custom.brightness*10000.0 + 1.0)/ log(sc);
    col = smoothstep(v3(0.),v3(1.),col);

    col = pow(col, v3(3./0.1));
    
    textureStore(output, vec2<i32>(id.xy), v4(col, 1.));
    
    // Clear
    atomicStore(&atomic_buffer[hist_id], 0u);
    atomicStore(&atomic_buffer[hist_id + res.x*res.y], 0u);
}