codecraft 0.1.1

A minimalist 3D game engine built on parts of Bevy (ECS, color) with wgpu and winit: OpenPBR materials, clustered lighting, an immediate-mode UI, audio and gamepad haptics
Documentation
// An infinite ground grid, drawn as one triangle over the whole frame.
//
// Nothing is tessellated: every pixel casts its own ray through the inverse
// view-projection, meets the ground plane, and asks how close it landed to a
// line. That is what keeps the lines a pixel wide however far away they are,
// and what lets the grid run to the horizon without a mesh big enough to get
// there.

struct Grid {
    view_proj: mat4x4<f32>,
    inv_view_proj: mat4x4<f32>,
    // xyz: where the eye is. w: the height of the plane.
    eye: vec4<f32>,
    // x: spacing between lines, y: how many of those to a heavy line,
    // z: where the fade starts, w: where nothing is left.
    params: vec4<f32>,
    line: vec4<f32>,
    major: vec4<f32>,
    x_axis: vec4<f32>,
    z_axis: vec4<f32>,
};

@group(0) @binding(0)
var<uniform> grid: Grid;

struct VertexOutput {
    @builtin(position) clip_position: vec4<f32>,
    @location(0) ndc: vec2<f32>,
};

// One triangle large enough to cover the frame, from the vertex index alone.
// A quad would need a buffer and would seam down its diagonal.
@vertex
fn vs_main(@builtin(vertex_index) index: u32) -> VertexOutput {
    let ndc = vec2<f32>(
        f32(i32(index) % 2) * 4.0 - 1.0,
        f32(i32(index) / 2) * 4.0 - 1.0,
    );
    var out: VertexOutput;
    out.ndc = ndc;
    out.clip_position = vec4<f32>(ndc, 0.0, 1.0);
    return out;
}

// Where a point on the near plane and the same point on the far plane sit in
// the world, for the pixel at `ndc`.
fn unproject(ndc: vec2<f32>, depth: f32) -> vec3<f32> {
    let world = grid.inv_view_proj * vec4<f32>(ndc, depth, 1.0);
    return world.xyz / world.w;
}

// How lit a line is at `p`, given how much of the plane this pixel covers.
//
// `coverage` is the width of a pixel measured in cells: divide the distance
// to the nearest line by it and the answer is in pixels, so a line comes out
// the same weight at any distance or angle. Blender's grid does the same.
fn line_alpha(p: vec2<f32>, coverage: vec2<f32>) -> f32 {
    let distance = abs(fract(p - 0.5) - 0.5) / max(coverage, vec2<f32>(1e-8));
    return 1.0 - min(min(distance.x, distance.y), 1.0);
}

struct FragmentOutput {
    @location(0) color: vec4<f32>,
    // Written so the grid sits in the same depth as everything else drawn
    // this frame: a model in front of it hides it, and one behind does not.
    @builtin(frag_depth) depth: f32,
};

@fragment
fn fs_main(in: VertexOutput) -> FragmentOutput {
    let near = unproject(in.ndc, 0.0);
    let far = unproject(in.ndc, 1.0);
    let ray = far - near;

    // Where this pixel's ray crosses the plane, as a fraction of near..far.
    // Outside that range it either misses (looking up, or away) or lands
    // beyond the far plane, and either way there is nothing to draw.
    let height = grid.eye.w;
    let hit = (height - near.y) / ray.y;
    if (hit < 0.0 || hit > 1.0) {
        discard;
    }
    let world = near + ray * hit;

    let spacing = grid.params.x;
    let cell = world.xz / spacing;
    // Screen-space derivatives of the plane coordinate: how much of the grid
    // one pixel spans. This is the whole anti-aliasing scheme.
    let coverage = fwidth(cell);

    let minor = line_alpha(cell, coverage);
    let heavy = grid.params.y;
    let major = line_alpha(cell / heavy, coverage / heavy);

    // Minor lines are dropped once they crowd together tighter than a pixel,
    // which is where a grid otherwise turns into moire. The heavy lines are
    // still spaced out at that point and carry on.
    let crowding = max(coverage.x, coverage.y);
    let minor_visible = minor * (1.0 - smoothstep(0.4, 1.0, crowding));

    var color = grid.line;
    var alpha = minor_visible * grid.line.a;
    if (major * grid.major.a > alpha) {
        color = grid.major;
        alpha = major * grid.major.a;
    }

    // The two lines through the origin name the axes, the way they do in a
    // modelling program: red across, blue into the screen.
    let axis = abs(world.xz) / max(coverage * spacing, vec2<f32>(1e-8));
    let on_z = 1.0 - min(axis.x, 1.0);
    let on_x = 1.0 - min(axis.y, 1.0);
    if (on_z * grid.z_axis.a > alpha) {
        color = grid.z_axis;
        alpha = on_z * grid.z_axis.a;
    }
    if (on_x * grid.x_axis.a > alpha) {
        color = grid.x_axis;
        alpha = on_x * grid.x_axis.a;
    }

    if (alpha <= 0.001) {
        discard;
    }

    // Fade out with distance so the grid ends in nothing rather than in a
    // hard rim at the far plane.
    let distance = length(world - grid.eye.xyz);
    let fade = 1.0 - smoothstep(grid.params.z, grid.params.w, distance);

    let clip = grid.view_proj * vec4<f32>(world, 1.0);
    var out: FragmentOutput;
    out.color = vec4<f32>(color.rgb, alpha * fade);
    out.depth = clip.z / clip.w;
    return out;
}