nightshade 0.13.1

A cross-platform data-oriented game engine.
Documentation
struct Uniform {
    proj: mat4x4<f32>,
    proj_inv: mat4x4<f32>,
    view: mat4x4<f32>,
    cam_pos: vec4<f32>,
    time: f32,
    _pad0: f32,
    _pad1: f32,
    _pad2: f32,
};

@group(0) @binding(0)
var<uniform> u: Uniform;

struct VertexOutput {
    @builtin(position) position: vec4<f32>,
    @location(0) world_dir: vec3<f32>,
};

@vertex
fn vs_sky(@builtin(vertex_index) vertex_index: u32) -> VertexOutput {
    let tmp1 = i32(vertex_index) / 2;
    let tmp2 = i32(vertex_index) & 1;
    let pos = vec4<f32>(
        f32(tmp1) * 4.0 - 1.0,
        f32(tmp2) * 4.0 - 1.0,
        0.0,
        1.0
    );
    let inv_model_view = transpose(mat3x3<f32>(u.view[0].xyz, u.view[1].xyz, u.view[2].xyz));
    let unprojected = u.proj_inv * pos;
    var result: VertexOutput;
    result.world_dir = inv_model_view * unprojected.xyz;
    result.position = pos;
    return result;
}

fn hash(p: vec2<f32>) -> f32 {
    let k = vec2<f32>(0.3183099, 0.3678794);
    let x = p * k + k.yx;
    return fract(16.0 * k.x * fract(x.x * x.y * (x.x + x.y)));
}

fn hash2(p: vec2<f32>) -> vec2<f32> {
    let k = vec2<f32>(0.3183099, 0.3678794);
    let q = p * k + k.yx;
    return fract(16.0 * k * fract(q.x * q.y * (q.x + q.y))) * 2.0 - 1.0;
}

fn star_layer(uv: vec2<f32>, scale: f32, seed: f32) -> f32 {
    let grid_uv = uv * scale;
    let grid_id = floor(grid_uv);
    let grid_fract = fract(grid_uv) - 0.5;

    let rand = hash(grid_id + seed);

    if rand > 0.97 {
        let offset = hash2(grid_id + seed + 100.0) * 0.4;
        let dist = length(grid_fract - offset);

        let star_size = 0.02 + hash(grid_id + seed + 200.0) * 0.03;
        let brightness = smoothstep(star_size, 0.0, dist);

        let twinkle_speed = 1.0 + hash(grid_id + seed + 300.0) * 3.0;
        let twinkle_phase = hash(grid_id + seed + 400.0) * 6.283;
        let twinkle = 0.7 + 0.3 * sin(u.time * twinkle_speed + twinkle_phase);

        return brightness * twinkle;
    }

    return 0.0;
}

fn dir_to_uv(dir: vec3<f32>) -> vec2<f32> {
    let phi = atan2(dir.z, dir.x);
    let theta = asin(clamp(dir.y, -1.0, 1.0));
    return vec2<f32>(phi / 6.283185 + 0.5, theta / 3.141593 + 0.5);
}

@fragment
fn fs_sky(in: VertexOutput) -> @location(0) vec4<f32> {
    let dir = normalize(in.world_dir);
    let uv = dir_to_uv(dir);

    var stars = 0.0;
    stars += star_layer(uv, 50.0, 0.0);
    stars += star_layer(uv, 100.0, 10.0);
    stars += star_layer(uv, 200.0, 20.0) * 0.7;
    stars += star_layer(uv, 400.0, 30.0) * 0.4;

    let star_hue = hash(floor(uv * 100.0));
    var star_color = vec3<f32>(1.0);
    if star_hue < 0.3 {
        star_color = vec3<f32>(0.8, 0.85, 1.0);
    } else if star_hue > 0.7 {
        star_color = vec3<f32>(1.0, 0.95, 0.85);
    }

    let final_color = star_color * stars;

    return vec4<f32>(final_color, 1.0);
}