add minecraft region rendering demo

This commit is contained in:
Zack Buhman 2026-03-03 20:54:49 +00:00
parent 761a0073ef
commit 8bc64d418a
10 changed files with 267 additions and 8 deletions

View File

@ -21,7 +21,7 @@ local pixel_ssao = love.filesystem.newFileData("pixel_ssao.glsl")
local shader_ssao = love.graphics.newShader(pixel_ssao, vertex_screen)
local pixel_clear = love.filesystem.newFileData("pixel_clear.glsl")
local shader_clear = love.graphics.newShader(pixel_clear, vertex_screen)
shader_clear = love.graphics.newShader(pixel_clear, vertex_screen)
local pixel_blur = love.filesystem.newFileData("pixel_blur.glsl")
local shader_blur = love.graphics.newShader(pixel_blur, vertex_screen)

View File

@ -1,6 +1,3 @@
local rotation
local texture
local ffi = require 'ffi'
local _math = require '_math'
local mat4 = _math.mat4
@ -21,6 +18,10 @@ local terminus_font
local update_global_parameters = require "update_global_parameters"
local minecraft = require "minecraft"
local joysticks
local scenes = {
sci_fi_ship = {
descriptor = scene_sci_fi_ship.descriptor,
@ -89,17 +90,23 @@ function init()
load_screen_shader()
terminus_font = font.load_font(font.fonts.ter_10x18)
----------------------------------------------------------------------
-- minecraft
----------------------------------------------------------------------
minecraft.init()
--
joysticks = love.joystick.getJoysticks()
end
local rotation = 0.0
local t = 0.0
local update = function(dt)
collada_scene_animate.update(t, node_state)
t = t + 0.016 * 0.1
rotation = rotation + 0.01
end
local draw = function()
@ -138,6 +145,14 @@ local keypressed = function(key, scancode, isrepeat)
update_global_parameters.update_current_ix(function(v) return v - 1 end)
elseif key == "down" then
update_global_parameters.update_current_ix(function(v) return v + 1 end)
-- elseif key == "j" then
-- minecraft.viewpos.x = minecraft.viewpos.x - 5
-- elseif key == "l" then
-- minecraft.viewpos.x = minecraft.viewpos.x + 5
-- elseif key == "i" then
-- minecraft.viewpos.z = minecraft.viewpos.z - 5
-- elseif key == "k" then
-- minecraft.viewpos.z = minecraft.viewpos.z + 5
else
print(key)
end
@ -165,8 +180,24 @@ function love.run()
local dt = love.timer.step()
update(dt)
draw()
for _, joystick in ipairs(joysticks) do
local lx = joystick:getGamepadAxis("leftx")
local ly = joystick:getGamepadAxis("lefty")
local ry = joystick:getGamepadAxis("righty")
minecraft.viewpos.x = minecraft.viewpos.x + 2.5 * lx
minecraft.viewpos.y = minecraft.viewpos.y + -2.5 * ry
minecraft.viewpos.z = minecraft.viewpos.z + -2.5 * ly
end
--draw()
local width, height = love.graphics.getDimensions()
local aspect_ratio = width / height
local perspective_projection = mat4.perspective_fov_rh(scalar.convert_to_radians(45 * 0.75),
aspect_ratio,
1,
0.1)
minecraft.draw(perspective_projection)
love.graphics.present()
love.timer.sleep(0.001)
end

106
minecraft.lua Normal file
View File

@ -0,0 +1,106 @@
local ffi = require "ffi"
local _math = require '_math'
local mat4 = _math.mat4
local vec3 = _math.vec3
local vec4 = _math.vec4
local cube_indices = {
0, 1, 2,
3, 4, 5,
6, 7, 8,
9, 10, 11,
12, 13, 14,
15, 16, 17,
0, 18, 1,
3, 19, 4,
6, 20, 7,
9, 21, 10,
12, 22, 13,
15, 23, 16
}
local cube_indices_count = #cube_indices
local cube_index_buffer
local blocks
local shader_minecraft
local region_buffers = {}
local load_blocks_buffer = function(name)
local data = love.filesystem.newFileData(name)
local format = {
{ format = "uint8vec4" },
}
local buffer = love.graphics.newBuffer(format, data, { shaderstorage = true, usage = "static" })
return buffer
end
local load_region_buffer = function(path)
local data = love.filesystem.newFileData(path)
local format = {
{ format = "uint8vec4" },
{ format = "uint32" },
}
local buffer = love.graphics.newBuffer(format, data, { shaderstorage = true, usage = "static" })
return buffer
end
local load_cube_index_buffer = function()
local data = love.data.newByteData(cube_indices_count * 4)
local ptr = ffi.cast("uint32_t*", data:getFFIPointer())
for i, index in ipairs(cube_indices) do
ptr[i - 1] = index
end
local buffer = love.graphics.newBuffer("uint32", data, { index = true, usage = "static" })
return buffer
end
local load_minecraft_shader = function()
local pixel_data = love.filesystem.newFileData("pixel_minecraft.glsl")
local vertex_data = love.filesystem.newFileData("vertex_minecraft.glsl")
local shader = love.graphics.newShader(pixel_data, vertex_data)
return shader
end
local init = function()
table.insert(region_buffers, load_region_buffer("minecraft/region.0.0.data"))
table.insert(region_buffers, load_region_buffer("minecraft/region.-1.0.data"))
table.insert(region_buffers, load_region_buffer("minecraft/region.0.-1.data"))
table.insert(region_buffers, load_region_buffer("minecraft/region.-1.-1.data"))
cube_index_buffer = load_cube_index_buffer()
shader_minecraft = load_minecraft_shader()
end
local viewpos = {
x = -4 * 31,
y = 0,
z = -4 * 31,
}
local draw = function(projection)
local view = mat4.look_at_rh(vec3(-50 + viewpos.x, -50 + viewpos.z, 100 + 50 + viewpos.y),
vec3(50 + viewpos.x, 50 + viewpos.z, 50 + viewpos.y),
vec3(0, 0, 1))
love.graphics.setCanvas()
love.graphics.setDepthMode("always", true)
love.graphics.setShader(shader_clear)
love.graphics.drawFromShader(screen_index_buffer, 3 * 2, 1, 1)
love.graphics.setDepthMode("greater", true)
love.graphics.setShader(shader_minecraft)
local view_projection = view * projection
for _, region_buffer in ipairs(region_buffers) do
local instance_count = math.floor(region_buffer:getSize() / (4 * 2))
shader_minecraft:send("transform", "column", view_projection:data())
shader_minecraft:send("BlocksLayout", region_buffer)
love.graphics.drawFromShader(cube_index_buffer, cube_indices_count, instance_count, 1)
end
end
return {
viewpos = viewpos,
init = init,
draw = draw,
}

BIN
minecraft/region.-1.-1.data Normal file

Binary file not shown.

BIN
minecraft/region.-1.0.data Normal file

Binary file not shown.

BIN
minecraft/region.0.-1.data Normal file

Binary file not shown.

BIN
minecraft/region.0.0.data Normal file

Binary file not shown.

22
pixel_minecraft.glsl Normal file
View File

@ -0,0 +1,22 @@
#pragma language glsl4
in vec3 PixelNormal;
in vec3 PixelColor;
in float PixelBlock;
out vec4 out_color;
void pixelmain()
{
if (PixelBlock == 0) {
discard;
return;
}
vec3 light_direction = normalize(vec3(-1, -0.5, 0.5));
float diffuse_intensity = max(dot(PixelNormal, light_direction), 0.0);
vec3 color = PixelColor * diffuse_intensity;
out_color = vec4(color, 1.0);
}

View File

@ -0,0 +1,19 @@
Reason #1: Can't bind vertex attributes per draw call
----------------------------------------------------------------------
While this can be mostly worked around with shader storage buffers and
the Love2D "drawFromShader" API, the format of shader storage buffers
must be defined using GLSL types.
GLSL has no sized integer or sized float type, which makes it
impossible to use, for example, vectors of uint8. uint8 is useful for
representing things like Minecraft Block IDs.
A workaround for the lack of sized scalars in shader storage buffers
is to use bit shifts on GLSL's uint(32) type, but this is fragile as
it makes the shader dependent on host endianness.
However, this is extremely awkward if a vector of signed 8-bit
integers is desired (as in signed Minecraft chunk x/z coordinates). A
workaround for this is to perform sign extension manually via GLSL
arithmetic.

81
vertex_minecraft.glsl Normal file
View File

@ -0,0 +1,81 @@
#pragma language glsl4
struct vertex_t {
vec3 position;
vec3 normal;
};
vertex_t vertices[] = vertex_t[](vertex_t(vec3(-1.0, 1.0, -1.0), vec3(0.0, 1.0, 0.0)),
vertex_t(vec3(1.0, 1.0, 1.0), vec3(0.0, 1.0, 0.0)),
vertex_t(vec3(1.0, 1.0, -1.0), vec3(0.0, 1.0, 0.0)),
vertex_t(vec3(1.0, 1.0, 1.0), vec3(0.0, 0.0, 1.0)),
vertex_t(vec3(-1.0, -1.0, 1.0), vec3(0.0, 0.0, 1.0)),
vertex_t(vec3(1.0, -1.0, 1.0), vec3(0.0, 0.0, 1.0)),
vertex_t(vec3(-1.0, 1.0, 1.0), vec3(-1.0, 0.0, 0.0)),
vertex_t(vec3(-1.0, -1.0, -1.0), vec3(-1.0, 0.0, 0.0)),
vertex_t(vec3(-1.0, -1.0, 1.0), vec3(-1.0, 0.0, 0.0)),
vertex_t(vec3(1.0, -1.0, -1.0), vec3(0.0, -1.0, 0.0)),
vertex_t(vec3(-1.0, -1.0, 1.0), vec3(0.0, -1.0, 0.0)),
vertex_t(vec3(-1.0, -1.0, -1.0), vec3(0.0, -1.0, 0.0)),
vertex_t(vec3(1.0, 1.0, -1.0), vec3(1.0, 0.0, 0.0)),
vertex_t(vec3(1.0, -1.0, 1.0), vec3(1.0, 0.0, 0.0)),
vertex_t(vec3(1.0, -1.0, -1.0), vec3(1.0, 0.0, 0.0)),
vertex_t(vec3(-1.0, 1.0, -1.0), vec3(0.0, 0.0, -1.0)),
vertex_t(vec3(1.0, -1.0, -1.0), vec3(0.0, 0.0, -1.0)),
vertex_t(vec3(-1.0, -1.0, -1.0), vec3(0.0, 0.0, -1.0)),
vertex_t(vec3(-1.0, 1.0, 1.0), vec3(0.0, 1.0, 0.0)),
vertex_t(vec3(-1.0, 1.0, 1.0), vec3(0.0, 0.0, 1.0)),
vertex_t(vec3(-1.0, 1.0, -1.0), vec3(-1.0, 0.0, 0.0)),
vertex_t(vec3(1.0, -1.0, 1.0), vec3(0.0, -1.0, 0.0)),
vertex_t(vec3(1.0, 1.0, 1.0), vec3(1.0, 0.0, 0.0)),
vertex_t(vec3(1.0, 1.0, -1.0), vec3(0.0, 0.0, -1.0)));
uniform mat4 transform;
out vec3 PixelNormal;
out vec3 PixelColor;
out float PixelBlock;
struct block_t {
uint x_z_blockid;
uint block_index;
};
layout (std430) readonly buffer BlocksLayout
{
block_t Blocks[];
};
vec3 palette(float t) {
vec3 a = vec3(0.5, 0.5, 0.5);
vec3 b = vec3(0.5, 0.5, 0.5);
vec3 c = vec3(1.0, 1.0, 1.0);
vec3 d = vec3(0.263, 0.416, 0.557);
return a + b * cos(6.28318 * (c * t + d));
}
int sign_extend(uint x)
{
return int(((x + 0x80) & 0xff) - 0x80);
}
void vertexmain()
{
vertex_t vertex = vertices[gl_VertexID];
block_t block = Blocks[int(gl_InstanceID)];
float x = float(block.block_index / (128 * 16));
float y = float(block.block_index % 128);
float z = float(int(block.block_index / 128) % 16);
float chunk_x = float(sign_extend((block.x_z_blockid >> 0) & 0xff));
float chunk_z = float(sign_extend((block.x_z_blockid >> 8) & 0xff));
int block_id = int((block.x_z_blockid >> 16) & 0xff);
PixelNormal = vertex.normal;
PixelColor = palette(float(block_id) * 0.01);
PixelBlock = block_id;
vec3 position = ((vertex.position * 0.4) + vec3(x, z, y) + vec3(chunk_x * 16.0, chunk_z * 16.0, 0));
love_Position = transform * vec4(position, 1.0);
}