Theme

alex/beta-minecraft-rust

Minecraft Beta 1.7.3 Rewrite

BRANCH main
main
SNAPSHOT Draw terrain GPU-driven from packed per-section buffers with GPU occlusion culling, add a render distance slider (2-128) with a quad/disk shape, cut chunk bookkeeping at high render distances, and fix a light underflow at y=0
DOWNLOAD
Download file

root / crates / client / shaders / terrain.slang

// Chunk sections, culled and drawn on the GPU.
//
// Culling runs in two phases: sections visible last frame draw first, then the bounding boxes
// of all sections in the frustum are rasterized against that depth, and the newly visible ones
// draw second. Draws skip the facing groups that point away from the camera.
//
// Vertices are pulled per face: a face references a deduplicated shape (corner positions
// relative to its block, shade and texture coordinates), light and tint. A fixed-function look:
// per-vertex light averaged from four brightness samples (smooth lighting), flat face shading,
// alpha test and linear radial fog.

// See `SectionRecord` in terrain.rs.
struct Section
{
    int3 position;
    uint visible;      // a fragment of the box passed the depth test this frame
    uint wasVisible;
    uint extents[3];   // signed bytes: minimum and maximum block bounds, then facing planes
    uint* shapes;      // 12 words: per corner x | y << 16 (wrapping), z | shade << 16, uv
    uint4* lights;     // per corner, four samples of sky light | block light << 4
    uint* tints;       // RGBA8
    uint2* faces;      // x | y << 4 | z << 8 | shape << 12, light | tint << 18
    uint counts[15];   // faces per group: solid and cutout per facing, then translucent
    uint total;        // 0 for a free slot
};

struct DrawCommand
{
    uint indexCount;
    uint instanceCount;
    uint firstIndex;
    int vertexOffset;
    uint firstInstance;
};

static const uint FACINGS = 7;
static const uint TRANSLUCENT = 14;
/// Boxes reach this far past the geometry, so they are not coplanar with it.
static const float MARGIN = 1.0 / 16.0;

int extent(Section* section, uint i)
{
    return int(section.extents[i / 4] << (24 - 8 * (i % 4))) >> 24;
}

/// The section's minimum corner relative to the eye.
float3 sectionOrigin(Section* section, int4 eyeBlock, float4 eyeFraction)
{
    return float3(section.position * 16 - eyeBlock.xyz) - eyeFraction.xyz;
}

void sectionBox(Section* section, float3 origin, out float3 lo, out float3 hi)
{
    lo = origin + float3(extent(section, 0), extent(section, 1), extent(section, 2)) - MARGIN;
    hi = origin + float3(extent(section, 3), extent(section, 4), extent(section, 5)) + MARGIN;
}

// ---- Drawing ---------------------------------------------------

struct DrawPush
{
    float4x4 viewProjection;
    float4 fogColor;
    float4 camera;
    float4 eyeFraction;
    int4 eyeBlock;
    Section* sections;
    float fogStart;
    float fogEnd;
    uint skyDarkness;
    uint texture;
    float opacity;
    uint padding;
    float brightness[16];
};

[[vk::binding(0, 0)]] Texture2D textures[];
[[vk::binding(1, 0)]] SamplerState samplers[];

static const float SHADE[4] = { 1.0, 0.8, 0.6, 0.5 };

struct Varyings
{
    float4 position : SV_Position;
    float2 uv : TEXCOORD0;
    float4 color : COLOR0;
    float distance : TEXCOORD1;
};

[shader("vertex")]
Varyings vertexMain(uniform DrawPush push, uint vertexIndex : SV_VulkanVertexID, uint slot : SV_VulkanInstanceID)
{
    Section* section = push.sections + slot;
    uint2 face = section.faces[vertexIndex >> 2];
    uint corner = vertexIndex & 3;
    uint* shape = section.shapes + (face.x >> 12) * 12 + corner * 3;
    uint3 block = (face.x >> uint3(0, 4, 8)) & 15;
    uint3 fixed = (uint3(shape[0] & 0xFFFF, shape[0] >> 16, shape[1] & 0xFFFF) + (block << 11)) & 0xFFFF;
    float3 position = float3(fixed) / 2048.0 - 8.0 + sectionOrigin(section, push.eyeBlock, push.eyeFraction);
    uint shade = shape[1] >> 16;
    uint light = section.lights[face.y & 0x3FFFF][corner];
    uint tint = section.tints[face.y >> 18];
    uint uv = shape[2];

    float brightness = 0.0;
    for (uint i = 0; i < 4; i++)
    {
        uint sample = light >> (8 * i);
        int sky = int(sample & 15);
        int blockLight = int((sample >> 4) & 15);
        brightness += push.brightness[clamp(max(sky - int(push.skyDarkness), blockLight), 0, 15)];
    }
    float lit = brightness / 4.0 * SHADE[shade & 3];
    // Shade 4 glows: halfway towards full brightness
    if ((shade & 4) != 0)
        lit = (lit + 1.0) * 0.5;
    float3 color = float3(tint & 0xFF, (tint >> 8) & 0xFF, (tint >> 16) & 0xFF) / 255.0;

    Varyings o;
    o.position = mul(push.viewProjection, float4(position, 1.0));
    o.uv = float2(uv & 0xFFFF, uv >> 16) / 65536.0;
    o.color = float4(color * lit, 1.0);
    o.distance = length(position - push.camera.xyz);
    return o;
}

float4 fogged(DrawPush push, Varyings i, float4 color)
{
    // A negative start selects GL_EXP fog with the negated start as density
    float fog = push.fogStart < 0.0
        ? exp(push.fogStart * i.distance)
        : saturate((push.fogEnd - i.distance) / (push.fogEnd - push.fogStart));
    return float4(lerp(push.fogColor.rgb, color.rgb, fog), color.a * push.opacity);
}

[shader("fragment")]
float4 fragmentMain(uniform DrawPush push, Varyings i) : SV_Target
{
    float4 color = textures[push.texture].Sample(samplers[0], i.uv) * i.color;
    if (color.a <= 0.1)
        discard;
    return fogged(push, i, color);
}

/// For faces whose texels all pass the alpha test: without `discard`, depth is tested early.
[shader("fragment")]
float4 solidFragment(uniform DrawPush push, Varyings i) : SV_Target
{
    return fogged(push, i, textures[push.texture].Sample(samplers[0], i.uv) * i.color);
}

// ---- Box test --------------------------------------------------

struct BoxPush
{
    float4x4 viewProjection;
    float4 eyeFraction;
    int4 eyeBlock;
    Section* sections;
    uint* tested;
};

/// Corners of the twelve box triangles, bits 1, 2 and 4 picking the maximum x, y and z.
static const uint BOX[36] = {
    0, 2, 6, 0, 6, 4, 1, 5, 7, 1, 7, 3,
    0, 4, 5, 0, 5, 1, 2, 3, 7, 2, 7, 6,
    0, 1, 3, 0, 3, 2, 4, 6, 7, 4, 7, 5,
};

struct BoxVaryings
{
    float4 position : SV_Position;
    nointerpolation uint slot : SLOT;
};

[shader("vertex")]
BoxVaryings boxVertex(uniform BoxPush push, uint vertexIndex : SV_VulkanVertexID)
{
    uint slot = push.tested[vertexIndex / 36];
    Section* section = push.sections + slot;
    float3 lo, hi;
    sectionBox(section, sectionOrigin(section, push.eyeBlock, push.eyeFraction), lo, hi);
    uint corner = BOX[vertexIndex % 36];
    float3 position = select((corner & uint3(1, 2, 4)) != 0, hi, lo);

    BoxVaryings o;
    o.position = mul(push.viewProjection, float4(position, 1.0));
    o.slot = slot;
    return o;
}

[shader("fragment")]
[earlydepthstencil]
void boxFragment(uniform BoxPush push, BoxVaryings i)
{
    push.sections[i.slot].visible = 1;
}

// ---- Culling ---------------------------------------------------

struct CullPush
{
    float4 planes[5];
    float4 eyeFraction;
    float4 camera;        // relative to the eye
    int4 eyeBlock;
    Section* sections;
    uint* order;
    uint* counters;       // solid and cutout draws, sections drawn, -, the box test's draw
    uint* tested;
    DrawCommand* commands; // solid, then cutout at `capacity`
    uint* translucent;
    DrawCommand* translucentCommands;
    uint count;
    uint capacity;
    uint mode;
    uint padding;
};

bool inFrustum(CullPush push, float3 lo, float3 hi)
{
    for (uint i = 0; i < 5; i++)
    {
        float4 plane = push.planes[i];
        if (dot(plane.xyz, select(plane.xyz >= 0.0, hi, lo)) + plane.w < 0.0)
            return false;
    }
    return true;
}

/// Bit per facing group whose faces may point at a camera this far into the section.
uint visibleFacings(Section* section, float3 camera)
{
    float plane[6];
    for (uint i = 0; i < 6; i++)
        plane[i] = float(extent(section, 6 + i));
    return uint(camera.y < plane[0])
        | uint(camera.y > plane[1]) << 1
        | uint(camera.z < plane[2]) << 2
        | uint(camera.z > plane[3]) << 3
        | uint(camera.x < plane[4]) << 4
        | uint(camera.x > plane[5]) << 5
        | 1u << 6;
}

DrawCommand draw(uint faces, uint first, uint slot)
{
    DrawCommand command = { faces * 6, 1, 0, int(first * 4), slot };
    return command;
}

/// Adds draws for the solid and cutout groups that face the camera, merging adjacent ones.
void emit(CullPush push, Section* section, uint slot, float3 camera)
{
    uint facings = visibleFacings(section, camera);
    uint first = 0;
    for (uint layer = 0; layer < 2; layer++)
    {
        DrawCommand commands[4];
        uint count = 0;
        uint start = first;
        uint run = 0;
        for (uint facing = 0; facing < FACINGS; facing++)
        {
            uint faces = section.counts[layer * FACINGS + facing];
            if ((facings >> facing & 1) != 0 || faces == 0)
            {
                run += faces;
            }
            else
            {
                if (run != 0)
                    commands[count++] = draw(run, start, slot);
                run = 0;
                start = first + faces;
            }
            first += faces;
        }
        if (run != 0)
            commands[count++] = draw(run, start, slot);
        if (count == 0)
            continue;
        uint base;
        InterlockedAdd(push.counters[layer], count, base);
        for (uint i = 0; i < count && base + i < push.capacity; i++)
            push.commands[layer * push.capacity + base + i] = commands[i];
    }
    InterlockedAdd(push.counters[2], 1);
}

[shader("compute")]
[numthreads(64, 1, 1)]
void cullMain(uniform CullPush push, uint3 id : SV_DispatchThreadID)
{
    uint index = id.x;
    if (index >= push.count)
        return;
    if (push.mode == 2)
    {
        uint slot = push.translucent[index];
        Section* section = push.sections + slot;
        uint first = section.total - section.counts[TRANSLUCENT];
        DrawCommand none = {};
        push.translucentCommands[index] = section.wasVisible != 0
            ? draw(section.counts[TRANSLUCENT], first, slot)
            : none;
        return;
    }

    uint slot = push.order[index];
    Section* section = push.sections + slot;
    if (section.total == 0)
        return;
    float3 origin = sectionOrigin(section, push.eyeBlock, push.eyeFraction);
    float3 lo, hi;
    sectionBox(section, origin, lo, hi);
    bool visible = inFrustum(push, lo, hi);
    float3 camera = push.camera.xyz;
    if (push.mode == 0)
    {
        if (!visible)
            return;
        // Boxes this near may reach behind the near plane: count them visible untested
        if (all(camera > lo - 1.0) && all(camera < hi + 1.0))
        {
            section.visible = 1;
        }
        else
        {
            uint vertex;
            InterlockedAdd(push.counters[4], 36, vertex);
            push.tested[vertex / 36] = slot;
        }
        if (section.wasVisible != 0)
            emit(push, section, slot, camera - origin);
    }
    else
    {
        visible = visible && section.visible != 0;
        if (visible && section.wasVisible == 0)
            emit(push, section, slot, camera - origin);
        section.wasVisible = uint(visible);
        section.visible = 0;
    }
}