minecraft: instanced rendering grouped by cube configuration

This commit is contained in:
Zack Buhman 2026-03-06 23:40:52 -06:00
parent 86890e2a77
commit 094847caca
23 changed files with 242 additions and 118 deletions

View File

@ -2,7 +2,7 @@
CC=$(PREFIX)gcc
CXX=$(PREFIX)g++
OPT = -Og -march=x86-64-v3
OPT = -O0 -march=x86-64-v3
CSTD = -std=gnu23
CXXSTD = -std=gnu++23

10
include/data.inc Normal file
View File

@ -0,0 +1,10 @@
short index_buffer_configuration_offsets[] = {
0, 0, 6, 12, 24, 30, 42, 54,
72, 78, 90, 102, 120, 132, 150, 168,
192, 198, 210, 222, 240, 252, 270, 288,
312, 324, 342, 360, 384, 402, 426, 450,
480, 486, 498, 510, 528, 540, 558, 576,
600, 612, 630, 648, 672, 690, 714, 738,
768, 780, 798, 816, 840, 858, 882, 906,
936, 954, 978, 1002, 1032, 1056, 1086, 1116,
};

BIN
minecraft/configuration.idx Normal file

Binary file not shown.

View File

@ -2,6 +2,7 @@ import sys
import struct
from pprint import pprint
from itertools import chain
from collections import defaultdict
import mcregion
import vec3
@ -16,15 +17,6 @@ def wrap_n(nc, chunk_c):
chunk_c = chunk_c + 1
return nc, chunk_c
normals = [
(-1.0, 0.0, 0.0),
(0.0, -1.0, 0.0),
(0.0, 0.0, -1.0),
(0.0, 0.0, 1.0),
(0.0, 1.0, 0.0),
(1.0, 0.0, 0.0),
]
def block_neighbors(level_table, chunk_x, chunk_z, block_index):
block_id = level_table[(chunk_x, chunk_z)].blocks[block_index]
if block_id == 0:
@ -51,7 +43,7 @@ def block_neighbors(level_table, chunk_x, chunk_z, block_index):
center_position = vec3.add((x, y, z), (chunk_x * 16, 0, chunk_z * 16))
def find_non_neighbors():
for i, normal in enumerate(normals):
for i, normal in enumerate(vertex_buffer.normals):
neighbor = vec3.add(normal, (x, y, z))
if not neighbor_exists(*neighbor):
yield i
@ -87,17 +79,38 @@ def build_block_configuration_table():
indices = []
for j in range(6):
if ((i >> j) & 1) != 0:
indices.extend(vertex_buffer.faces_by_normal[normals[j]])
indices.extend(vertex_buffer.faces_by_normal[vertex_buffer.normals[j]])
yield indices
def build_block_instances(f, blocks):
def build_block_instances(blocks):
by_configuration = defaultdict(list)
for position, block_id, normal_indices in blocks:
block_configuration = normal_indices_as_block_configuration(normal_indices)
configuration = normal_indices_as_block_configuration(normal_indices)
#print(position, block_id, block_configuration)
f.write(struct.pack("<hhhBB",
position[0], position[1], position[2],
block_id,
block_configuration))
by_configuration[configuration].append((position, block_id))
offset = 0
configuration_instance_count_offset = []
with open(f"{data_path}.instance.vtx", "wb") as f:
for configuration in range(64):
if configuration not in by_configuration:
configuration_instance_count_offset.append((0, 0))
continue
blocks = by_configuration[configuration]
configuration_instance_count_offset.append((len(blocks), offset))
for position, block_id in blocks:
packed = struct.pack("<hhhBB",
position[0], position[1], position[2],
block_id,
0)
f.write(packed)
offset += len(packed)
with open(f"{data_path}.instance.cfg", "wb") as f:
for instance_count, offset in configuration_instance_count_offset:
print(instance_count, offset)
f.write(struct.pack("<ii", instance_count, offset))
def main(mcr_path, data_path):
with open(mcr_path, "rb") as f:
@ -111,9 +124,7 @@ def main(mcr_path, data_path):
level_table = build_level_table(mem, locations)
blocks = devoxelize_region(level_table)
with open(data_path + ".vtx", "wb") as f:
build_block_instances(f, blocks)
build_block_instances(blocks)
#pprint(list(build_block_configuration_table()))

Binary file not shown.

View File

@ -1,3 +1,4 @@
import struct
import vec3
vertex_table = [
@ -36,29 +37,42 @@ faces_by_normal = {
(1.0, 0.0, 0.0): [12, 13, 14, 12, 22, 13]
}
vertex_buffer = {}
normals = [
(-1.0, 0.0, 0.0),
(0.0, -1.0, 0.0),
(0.0, 0.0, -1.0),
(0.0, 0.0, 1.0),
(0.0, 1.0, 0.0),
(1.0, 0.0, 0.0),
]
def add_vertex(vertex):
if vertex in vertex_buffer:
return vertex_buffer[vertex]
else:
index = len(vertex_buffer)
vertex_buffer[vertex] = index
return index
def build_configuration_index_buffers(f):
offset = 0
configuration_offsets = []
for configuration in range(64):
configuration_offsets.append(offset)
for i in range(6):
if (configuration & (1 << i)) == 0:
continue
normal = normals[i]
indices = faces_by_normal[normal]
for index in indices:
f.write(struct.pack("<B", index))
offset += 1
def emit_face(center_position, block_id, triangles):
for index in triangles:
position, normal, texture = vertex_table[index]
position = vec3.add(vec3.mul(position, 0.5), center_position)
vertex = (position, normal, texture, block_id)
new_index = add_vertex(vertex)
yield new_index
for i, offset in enumerate(configuration_offsets):
print(str(offset).rjust(4), end=", ")
if i % 8 == 7:
print()
def linearized_vertex_buffer():
for vertex, i in sorted(vertex_buffer.items(), key=lambda kv: kv[1]):
yield vertex
def build_vertex_buffer(f):
for position, normal, texture in vertex_table:
position = vec3.mul(position, 0.5)
f.write(struct.pack("<eeeeeeee", *position, *normal, *texture))
#with open(data_path + ".vtx", "wb") as f:
# for vertex in linearized_vertex_buffer():
# vertex = [*vertex[0], *vertex[1], *vertex[2], vertex[3]]#, vertex[3]]
# f.write(struct.pack("<fffffffff", *vertex))
if __name__ == "__main__":
with open("configuration.idx", "wb") as f:
build_configuration_index_buffers(f)
with open("per_vertex.vtx", "wb") as f:
build_vertex_buffer(f)

BIN
minecraft/per_vertex.vtx Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,10 +1,10 @@
#version 330 core
in GS_OUT {
in VS_OUT {
vec3 Normal;
vec2 Texture;
flat int BlockID;
} gs_out;
} fs_in;
out vec4 FragColor;
@ -32,13 +32,13 @@ int Textures[256] = int[256](
void main()
{
vec3 light_direction = normalize(vec3(-1, -0.5, 0.5));
float diffuse_intensity = max(dot(normalize(gs_out.Normal), light_direction), 0.0);
float diffuse_intensity = max(dot(normalize(fs_in.Normal), light_direction), 0.0);
int terrain_ix = int(Textures[int(gs_out.BlockID)]);
int terrain_ix = int(Textures[int(fs_in.BlockID)]);
int terrain_x = terrain_ix % 16;
int terrain_y = terrain_ix / 16;
ivec2 coord = ivec2(terrain_x, terrain_y) * 16;
coord += ivec2(gs_out.Texture.xy * 16.0);
coord += ivec2(fs_in.Texture.xy * 16.0);
vec4 texture_color = texelFetch(TerrainSampler, coord, 0);
if (texture_color.w != 1.0) {
@ -46,8 +46,11 @@ void main()
return;
}
if (int(gs_out.BlockID) == 18) // leaves
if (int(fs_in.BlockID) == 18) // leaves
texture_color.xyz *= vec3(0.125, 0.494, 0.027);
if (diffuse_intensity < 0.1)
diffuse_intensity = 0.1;
FragColor = vec4(texture_color.xyz * vec3(diffuse_intensity), 1.0);
}

View File

@ -63,25 +63,22 @@ vec4 transform(vec3 p)
void emit_face(vec3 normal, int face[4])
{
vec3 position = gl_in[0].gl_Position.xyz;
gs_out.Normal = normal.xzy;
PT vtx0 = vertices[face[0]];
gl_Position = transform(position + vtx0.Position * 0.5);
gs_out.Normal = normal.xzy;
gs_out.Texture = vtx0.Texture;
EmitVertex();
PT vtx1 = vertices[face[1]];
gl_Position = transform(position + vtx1.Position * 0.5);
gs_out.Normal = normal.xzy;
gs_out.Texture = vtx1.Texture;
EmitVertex();
PT vtx2 = vertices[face[2]];
gl_Position = transform(position + vtx2.Position * 0.5);
gs_out.Normal = normal.xzy;
gs_out.Texture = vtx2.Texture;
EmitVertex();
PT vtx3 = vertices[face[3]];
gl_Position = transform(position + vtx3.Position * 0.5);
gs_out.Normal = normal.xzy;
gs_out.Texture = vtx3.Texture;
EmitVertex();
EndPrimitive();

View File

@ -1,20 +1,26 @@
#version 330 core
layout (location = 0) in vec3 Position;
layout (location = 1) in float BlockID;
layout (location = 2) in float Configuration;
// per-vertex:
in vec3 Position;
in vec3 Normal;
in vec2 Texture;
// per-instance:
in vec3 BlockPosition;
in float BlockID;
out VS_OUT {
int BlockID;
int Configuration;
vec3 Normal;
vec2 Texture;
flat int BlockID;
} vs_out;
uniform mat4 Transform;
void main()
{
vs_out.Normal = Normal.xzy;
vs_out.Texture = Texture;
vs_out.BlockID = int(BlockID);
vs_out.Configuration = int(Configuration);
gl_Position = vec4(Position.xyz, 1.0);
gl_Position = Transform * vec4((Position + BlockPosition).xzy, 1.0);
}

View File

@ -1,16 +1,22 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "glad/gl.h"
#include "opengl.h"
#include "directxmath/directxmath.h"
#include "test.h"
#include "data.inc"
struct location {
struct {
unsigned int position;
unsigned int normal;
unsigned int texture;
unsigned int block_position;
unsigned int block_id;
unsigned int configuration;
//unsigned int configuration;
} attrib;
struct {
unsigned int transform;
@ -22,27 +28,55 @@ struct location {
static unsigned int test_program;
static struct location location;
static unsigned int vertex_array_objects[4];
static unsigned int vertex_buffers[4];
static unsigned int vertex_count[4];
//static unsigned int index_buffers[4];
//static unsigned int index_count[4];
struct char_tpl {
const char * vtx;
const char * cfg;
};
static const int region_count = 4;
static const char_tpl vertex_paths[region_count] = {
{ "minecraft/region.0.0.instance.vtx", "minecraft/region.0.0.instance.cfg" },
{ "minecraft/region.-1.0.instance.vtx", "minecraft/region.-1.0.instance.cfg" },
{ "minecraft/region.0.-1.instance.vtx", "minecraft/region.0.-1.instance.cfg" },
{ "minecraft/region.-1.-1.instance.vtx", "minecraft/region.-1.-1.instance.cfg" },
};
static unsigned int vertex_array_objects[region_count];
static unsigned int vertex_buffers[region_count];
static unsigned int vertex_count[region_count];
static unsigned int index_buffer;
static unsigned int per_vertex_buffer;
static const int vertex_size = 8;
static const int per_vertex_size = (3 + 3 + 2) * 2;
struct instance_cfg {
struct {
int instance_count;
int offset;
} cfg[64];
};
static instance_cfg instance_cfg[region_count];
void load_program()
{
unsigned int program = compile_from_files("shader/test.vert",
"shader/test.geom",
NULL, //"shader/test.geom",
"shader/test.frag");
location.attrib.position = glGetAttribLocation(program, "Position");
location.attrib.normal = glGetAttribLocation(program, "Normal");
location.attrib.texture = glGetAttribLocation(program, "Texture");
location.attrib.block_position = glGetAttribLocation(program, "BlockPosition");
location.attrib.block_id = glGetAttribLocation(program, "BlockID");
location.attrib.configuration = glGetAttribLocation(program, "Configuration");
printf("attributes:\n position %u\n block_id %u\n configuration %u\n",
printf("attributes:\n position %u\n normal %u\n texture %u\n block_position %u\n block_id %u\n\n",
location.attrib.position,
location.attrib.block_id,
location.attrib.configuration);
location.attrib.normal,
location.attrib.texture,
location.attrib.block_position,
location.attrib.block_id);
location.uniform.transform = glGetUniformLocation(program, "Transform");
location.uniform.terrain_sampler = glGetUniformLocation(program, "TerrainSampler");
@ -53,17 +87,10 @@ void load_program()
test_program = program;
}
const char * vertex_paths[] = {
"minecraft/region.0.0.inst.vtx",
"minecraft/region.0.-1.inst.vtx",
"minecraft/region.-1.0.inst.vtx",
"minecraft/region.-1.-1.inst.vtx",
};
void load_vertex_buffer(int i)
{
int vertex_buffer_data_size;
void * vertex_buffer_data = read_file(vertex_paths[i], &vertex_buffer_data_size);
void * vertex_buffer_data = read_file(vertex_paths[i].vtx, &vertex_buffer_data_size);
glBindBuffer(GL_ARRAY_BUFFER, vertex_buffers[i]);
glBufferData(GL_ARRAY_BUFFER, vertex_buffer_data_size, vertex_buffer_data, GL_STATIC_DRAW);
@ -72,23 +99,65 @@ void load_vertex_buffer(int i)
free(vertex_buffer_data);
}
/*
void load_element_buffer(int i)
void load_per_vertex_buffer()
{
int vertex_buffer_data_size;
void * vertex_buffer_data = read_file("minecraft/per_vertex.vtx", &vertex_buffer_data_size);
glBindBuffer(GL_ARRAY_BUFFER, per_vertex_buffer);
glBufferData(GL_ARRAY_BUFFER, vertex_buffer_data_size, vertex_buffer_data, GL_STATIC_DRAW);
free(vertex_buffer_data);
}
void load_index_buffer()
{
int index_buffer_data_size;
void * index_buffer_data = read_file(index_paths[i], &index_buffer_data_size);
void * index_buffer_data = read_file("minecraft/configuration.idx", &index_buffer_data_size);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_buffers[i]);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_buffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, index_buffer_data_size, index_buffer_data, GL_STATIC_DRAW);
index_count[i] = index_buffer_data_size / 4;
free(index_buffer_data);
}
*/
void load_vertex_attributes()
void load_vertex_attributes(int i)
{
glBindBuffer(GL_ARRAY_BUFFER, per_vertex_buffer);
glVertexAttribPointer(location.attrib.position,
3,
GL_HALF_FLOAT,
GL_FALSE,
per_vertex_size,
(void*)(0)
);
glVertexAttribPointer(location.attrib.normal,
3,
GL_HALF_FLOAT,
GL_FALSE,
per_vertex_size,
(void*)(6)
);
glVertexAttribPointer(location.attrib.texture,
2,
GL_HALF_FLOAT,
GL_FALSE,
per_vertex_size,
(void*)(12)
);
glEnableVertexAttribArray(location.attrib.position);
glEnableVertexAttribArray(location.attrib.normal);
glEnableVertexAttribArray(location.attrib.texture);
glVertexAttribDivisor(location.attrib.position, 0);
glVertexAttribDivisor(location.attrib.normal, 0);
glVertexAttribDivisor(location.attrib.texture, 0);
glBindBuffer(GL_ARRAY_BUFFER, vertex_buffers[i]);
glVertexAttribPointer(location.attrib.block_position,
3,
GL_SHORT,
GL_FALSE,
@ -102,34 +171,43 @@ void load_vertex_attributes()
vertex_size,
(void*)(6)
);
glVertexAttribPointer(location.attrib.configuration,
1,
GL_UNSIGNED_BYTE,
GL_FALSE,
vertex_size,
(void*)(7)
);
glEnableVertexAttribArray(location.attrib.position);
glEnableVertexAttribArray(location.attrib.block_position);
glEnableVertexAttribArray(location.attrib.block_id);
glEnableVertexAttribArray(location.attrib.configuration);
glVertexAttribDivisor(location.attrib.position, 1);
glVertexAttribDivisor(location.attrib.block_position, 1);
glVertexAttribDivisor(location.attrib.block_id, 1);
glVertexAttribDivisor(location.attrib.configuration, 1);
}
void load_instance_cfg(int i)
{
int data_size;
void * data = read_file(vertex_paths[i].cfg, &data_size);
assert(data_size == 512);
memcpy(&instance_cfg[i], data, data_size);
}
void load_buffers()
{
glGenVertexArrays(4, vertex_array_objects);
//glGenBuffers(4, index_buffers);
glGenBuffers(4, vertex_buffers);
// per-vertex buffer
glGenBuffers(1, &per_vertex_buffer);
load_per_vertex_buffer();
for (int i = 0; i < 4; i++) {
// per-instance buffer
glGenVertexArrays(region_count, vertex_array_objects);
glGenBuffers(region_count, vertex_buffers);
for (int i = 0; i < region_count; i++) {
glBindVertexArray(vertex_array_objects[i]);
//load_element_buffer(i);
load_vertex_buffer(i);
load_vertex_attributes();
load_vertex_attributes(i);
load_instance_cfg(i);
}
// index buffer
glGenBuffers(1, &index_buffer);
load_index_buffer();
}
static unsigned int texture;
@ -167,8 +245,6 @@ void load_texture_shader_storage()
void * textures_data = read_file("minecraft/block_id_to_texture_id.data", &textures_data_size);
assert(textures_data != NULL);
printf("%d\n", textures_data_size);
glBufferData(GL_UNIFORM_BUFFER, textures_data_size, textures_data, GL_STATIC_DRAW);
free(textures_data);
@ -205,6 +281,11 @@ void update(float lx, float ly, float ry)
vz += -2.5 * ly;
}
static inline int popcount(int x)
{
return __builtin_popcount(x);
}
void draw()
{
XMVECTOR eye = XMVectorSet(vx + -50.0f, vz + -50.0f, vy + 150.0f, 0.0f);
@ -237,22 +318,24 @@ void draw()
//glBindBuffer(GL_UNIFORM_BUFFER, textures_ubo);
//glBindBufferBase(GL_UNIFORM_BUFFER, 0, textures_ubo);
glEnable(GL_CULL_FACE);
glCullFace(GL_FRONT);
glFrontFace(GL_CCW);
//glEnable(GL_CULL_FACE);
//glCullFace(GL_FRONT);
//glFrontFace(GL_CCW);
for (int i = 0; i < 4; i++) {
glBindVertexArray(vertex_array_objects[i]);
for (int region_index = 0; region_index < region_count; region_index++) {
glBindVertexArray(vertex_array_objects[region_index]);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_buffer);
for (int configuration = 1; configuration < 64; configuration++) {
int element_count = 6 * popcount(configuration);
const void * indices = (void *)((ptrdiff_t)index_buffer_configuration_offsets[configuration]); // index into configuration.idx
//glDrawElements(GL_TRIANGLES, index_count[i], GL_UNSIGNED_INT, 0);
int instance_count = instance_cfg[region_index].cfg[configuration].instance_count;
int base_instance = instance_cfg[region_index].cfg[configuration].offset / vertex_size; // index into region.0.0.instance.vtx
int instance_count = vertex_count[i];
//printf("instance_count %d\n", instance_count);
if (instance_count == 0)
continue;
glPointSize(10.0);
glDrawArraysInstanced(GL_POINTS,
0,
1,
instance_count);
glDrawElementsInstancedBaseInstance(GL_TRIANGLES, element_count, GL_UNSIGNED_BYTE, indices, instance_count, base_instance);
}
}
}