alex/beta-minecraft-rust
Minecraft Beta 1.7.3 Rewrite
BRANCH 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
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;
}
}