Fix transparent blocks

This commit is contained in:
2026-09-25 15:01:03 -03:00
parent dfabe40e7a
commit f8d406dcf0
13 changed files with 645 additions and 192 deletions
+103 -21
View File
@@ -1,14 +1,15 @@
import { Component } from "$/common/ecs/mod.ts";
import { block_value, chunk_key, default_block_value } from "$/common/utils.ts";
import { AIR_ID, BlockChange } from "$/common/protocol.ts";
import { BlockRegistry, EverythingRegistry } from "$/common/everything_registry.ts";
import { BlockRegistry, EverythingRegistry, RENDER_LAYERS, RenderLayer } from "$/common/everything_registry.ts";
import { AIR, CHUNK_AREA, CHUNK_HEIGHT, CHUNK_SIZE, Faces, ID_MASK, VOID } from "../../common/constants.ts";
import { AssetManager } from "../assets.ts";
import { ClientWorld } from "../client_world.ts";
import { ChunkWorkerPool } from "../chunk_workers.ts";
import { worldgen_mods } from "../mods.ts";
import type { FromChunkWorker } from "../workers/chunk_messages.ts";
import { create_vertex_buffer, destroy_vertex_buffer, Texture } from "../renderer/mod.ts";
import { create_index_buffer, create_vertex_buffer, destroy_buffer, Texture } from "../renderer/mod.ts";
import { crosses_planes } from "../workers/translucent_sort.ts";
import { Camera } from "./camera.ts";
export interface Block {
@@ -28,12 +29,32 @@ export interface Chunk {
dirty: boolean;
// bumped on every mesh request so late results from older requests get ignored
mesh_version: number;
opaque_vertex_buffer?: GPUBuffer;
opaque_vertex_count?: number;
transparent_vertex_buffer?: GPUBuffer;
transparent_vertex_count?: number;
meshes: Partial<Record<RenderLayer, ChunkMesh>>;
// only for translucent meshes that have to be sorted again as the camera moves
translucent_sort?: TranslucentSort;
}
export interface ChunkMesh {
vertex_buffer: GPUBuffer;
quad_count: number;
// the translucent layer's quads sorted back to front, the others are drawn in order
index_buffer?: GPUBuffer;
}
interface TranslucentSort {
// the mesh_version of the mesh these are for
mesh_version: number;
centers: Float32Array;
planes: [Float32Array, Float32Array, Float32Array];
// where the camera was for the last sort that was requested
camera: number[];
// sorts come back out of order, older ones than what's shown get skipped
requested: number;
applied: number;
}
const FLOATS_PER_QUAD = 4 * 9;
export { chunk_key };
const NEIGHBOR_OFFSETS = [[-1, 0], [1, 0], [0, -1], [0, 1]] as const;
@@ -92,6 +113,7 @@ export class Dimension extends Component {
dirty: true,
generated: false,
mesh_version: 0,
meshes: {},
};
this.chunks.set(chunk_key(x, z), chunk);
return chunk;
@@ -294,7 +316,7 @@ export class Dimension extends Component {
}
// sends every dirty chunk that can be meshed to the workers
request_meshes() {
request_meshes(camera: Camera) {
for (const chunk of this.chunks.values()) {
if (!chunk.dirty || !this.can_mesh(chunk)) {
continue;
@@ -308,15 +330,41 @@ export class Dimension extends Component {
chunk_z: chunk.z,
version: chunk.mesh_version,
padded_chunk,
camera: [camera.x, camera.y, camera.z],
}, [padded_chunk.buffer]);
}
}
// like sodium, a chunk's translucent quads only change order when the camera crosses one of the
// planes they lie on, so that's the only time they get sorted again
update_translucent_sorting(camera: Camera) {
const position = [camera.x, camera.y, camera.z];
for (const chunk of this.chunks.values()) {
const sort = chunk.translucent_sort;
if (!sort || !crosses_planes(sort.planes, sort.camera, position)) {
continue;
}
sort.camera = position;
sort.requested += 1;
this.workers.post({
type: "sort",
chunk_x: chunk.x,
chunk_z: chunk.z,
version: sort.mesh_version,
sort_version: sort.requested,
centers: sort.centers,
camera: position,
});
}
}
#on_worker_message(message: FromChunkWorker) {
if (message.type === "generated") {
this.#on_generated(message.chunk_x, message.chunk_z, message.blocks, message.spills);
} else {
} else if (message.type === "meshed") {
this.#on_meshed(message);
} else {
this.#on_sorted(message);
}
}
@@ -370,13 +418,47 @@ export class Dimension extends Component {
this.delete_chunk_mesh(chunk);
chunk.opaque_vertex_buffer = create_vertex_buffer(message.opaque_vertices.subarray(0, message.opaque_count));
chunk.opaque_vertex_count = message.opaque_count / 9;
for (const layer of RENDER_LAYERS) {
const { vertices, quad_count } = message[layer];
if (quad_count === 0) {
continue;
}
chunk.meshes[layer] = {
vertex_buffer: create_vertex_buffer(vertices.subarray(0, quad_count * FLOATS_PER_QUAD)),
quad_count,
};
}
chunk.transparent_vertex_buffer = create_vertex_buffer(
message.transparent_vertices.subarray(0, message.transparent_count),
);
chunk.transparent_vertex_count = message.transparent_count / 9;
const translucent = message.translucent;
if (translucent.quad_count === 0) {
return;
}
chunk.meshes.translucent!.index_buffer = create_index_buffer(translucent.indices);
if (translucent.sort_type === "dynamic") {
chunk.translucent_sort = {
mesh_version: message.version,
centers: translucent.centers,
planes: translucent.planes,
camera: message.camera,
requested: 0,
applied: 0,
};
}
}
#on_sorted(message: Extract<FromChunkWorker, { type: "sorted" }>) {
const chunk = this.get_chunk(message.chunk_x, message.chunk_z);
const sort = chunk?.translucent_sort;
const mesh = chunk?.meshes.translucent;
// remeshed since, or a newer sort already came back
if (!sort || !mesh || sort.mesh_version !== message.version || message.sort_version <= sort.applied) {
return;
}
sort.applied = message.sort_version;
if (mesh.index_buffer) {
destroy_buffer(mesh.index_buffer);
}
mesh.index_buffer = create_index_buffer(message.indices);
}
// a neighbor's leaves, only fill air so the result doesn't depend on which chunk loaded first.
@@ -398,14 +480,14 @@ export class Dimension extends Component {
}
delete_chunk_mesh(chunk: Chunk) {
if (chunk.opaque_vertex_buffer) {
destroy_vertex_buffer(chunk.opaque_vertex_buffer);
chunk.opaque_vertex_buffer = undefined;
}
if (chunk.transparent_vertex_buffer) {
destroy_vertex_buffer(chunk.transparent_vertex_buffer);
chunk.transparent_vertex_buffer = undefined;
for (const mesh of Object.values(chunk.meshes)) {
destroy_buffer(mesh.vertex_buffer);
if (mesh.index_buffer) {
destroy_buffer(mesh.index_buffer);
}
}
chunk.meshes = {};
chunk.translucent_sort = undefined;
}
get_looked_block(