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
+125 -13
View File
@@ -1,5 +1,6 @@
import { Camera } from "../components/camera.ts";
import { mat4 } from "gl-matrix";
import type { RenderLayer } from "$/common/everything_registry.ts";
export let device: GPUDevice;
export let canvas: HTMLCanvasElement;
@@ -20,6 +21,10 @@ let canvas_format: GPUTextureFormat;
let pipeline_2d: GPURenderPipeline;
let pipeline_3d: GPURenderPipeline;
let terrain_pipelines: Record<RenderLayer, GPURenderPipeline>;
// 0 1 2 0 2 3 for every quad, shared by all solid and cutout chunk meshes
let quad_index_buffer: GPUBuffer | undefined;
let quad_index_capacity = 0;
let uniform_layout: GPUBindGroupLayout;
let texture_layout: GPUBindGroupLayout;
let sampler: GPUSampler;
@@ -90,9 +95,30 @@ fn vs_main(
return out;
}
// blended draws. fully clear texels don't write depth, or they would hide what's drawn behind them later
@fragment
fn fs_main(in: VertexOut) -> @location(0) vec4<f32> {
return textureSample(texture0, sampler0, in.tex_coord) * in.color;
let color = textureSample(texture0, sampler0, in.tex_coord) * in.color;
if (color.a < 0.01) {
discard;
}
return color;
}
// no discard, so the gpu can reject hidden fragments before running the shader
@fragment
fn fs_solid(in: VertexOut) -> @location(0) vec4<f32> {
return vec4<f32>((textureSample(texture0, sampler0, in.tex_coord) * in.color).rgb, 1.0);
}
// alpha tested instead of blended, so it doesn't need sorting
@fragment
fn fs_cutout(in: VertexOut) -> @location(0) vec4<f32> {
let color = textureSample(texture0, sampler0, in.tex_coord) * in.color;
if (color.a < 0.1) {
discard;
}
return vec4<f32>(color.rgb, 1.0);
}
`;
@@ -251,8 +277,33 @@ export function flush_batch() {
vert_index = 0;
}
export function flush_buffer(buffer: GPUBuffer, draw_count: number) {
draw(buffer, 0, draw_count);
// draws a chunk mesh made of quads (4 vertices each). without an index buffer the quads are drawn in order
export function draw_terrain(
layer: RenderLayer,
vertex_buffer: GPUBuffer,
quad_count: number,
index_buffer?: GPUBuffer,
) {
if (!current_texture || quad_count === 0) {
return;
}
if (!index_buffer) {
ensure_quad_indices(quad_count);
index_buffer = quad_index_buffer!;
}
const render_pass = ensure_pass();
const pipeline = terrain_pipelines[layer];
if (current_pipeline !== pipeline) {
render_pass.setPipeline(pipeline);
current_pipeline = pipeline;
}
render_pass.setBindGroup(0, uniform_bind_group, [uniform_slot * UNIFORM_SLOT_SIZE]);
render_pass.setBindGroup(1, get_texture_bind_group(current_texture));
render_pass.setVertexBuffer(0, vertex_buffer);
render_pass.setIndexBuffer(index_buffer, "uint32");
render_pass.drawIndexed(quad_count * 6);
}
export function create_vertex_buffer(vertices: Float32Array): GPUBuffer {
@@ -264,7 +315,16 @@ export function create_vertex_buffer(vertices: Float32Array): GPUBuffer {
return buffer;
}
export function destroy_vertex_buffer(buffer: GPUBuffer) {
export function create_index_buffer(indices: Uint32Array): GPUBuffer {
const buffer = device.createBuffer({
size: Math.max(4, indices.byteLength),
usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST,
});
device.queue.writeBuffer(buffer, 0, indices);
return buffer;
}
export function destroy_buffer(buffer: GPUBuffer) {
// it might be used by a draw thats not submitted yet
if (encoder) {
pending_destroy.push(buffer);
@@ -375,6 +435,28 @@ export function push_quad_vertices(
// internal
function ensure_quad_indices(quad_count: number) {
if (quad_count <= quad_index_capacity) {
return;
}
if (quad_index_buffer) {
destroy_buffer(quad_index_buffer);
}
quad_index_capacity = Math.max(quad_count, quad_index_capacity * 2, 16384);
const indices = new Uint32Array(quad_index_capacity * 6);
for (let q = 0; q < quad_index_capacity; q++) {
const v = q * 4;
const i = q * 6;
indices[i] = v;
indices[i + 1] = v + 1;
indices[i + 2] = v + 2;
indices[i + 3] = v;
indices[i + 4] = v + 2;
indices[i + 5] = v + 3;
}
quad_index_buffer = create_index_buffer(indices);
}
function draw(buffer: GPUBuffer, offset: number, vertex_count: number) {
if (!current_texture || vertex_count === 0) {
return;
@@ -554,21 +636,51 @@ function create_pipelines() {
depthStencil: { format: DEPTH_FORMAT, depthWriteEnabled: false, depthCompare: "always" },
});
const primitive_3d: GPUPrimitiveState = { topology: "triangle-list", cullMode: "back", frontFace: "ccw" };
const depth_3d: GPUDepthStencilState = {
format: DEPTH_FORMAT,
depthWriteEnabled: true,
depthCompare: "less-equal",
// same as the old polygonOffset(1, 1)
depthBias: 1,
depthBiasSlopeScale: 1,
};
pipeline_3d = device.createRenderPipeline({
layout,
vertex,
fragment,
multisample,
primitive: { topology: "triangle-list", cullMode: "back", frontFace: "ccw" },
depthStencil: {
format: DEPTH_FORMAT,
depthWriteEnabled: true,
depthCompare: "less-equal",
// same as the old polygonOffset(1, 1)
depthBias: 1,
depthBiasSlopeScale: 1,
},
primitive: primitive_3d,
depthStencil: depth_3d,
});
// like sodium and vanilla: solid and cutout don't blend, translucent blends and still writes depth
// since it's drawn sorted back to front
const unblended = (entryPoint: string): GPUFragmentState => ({
module,
entryPoint,
targets: [{ format: canvas_format }],
});
terrain_pipelines = {
solid: device.createRenderPipeline({
layout,
vertex,
fragment: unblended("fs_solid"),
multisample,
primitive: primitive_3d,
depthStencil: depth_3d,
}),
cutout: device.createRenderPipeline({
layout,
vertex,
fragment: unblended("fs_cutout"),
multisample,
primitive: primitive_3d,
depthStencil: depth_3d,
}),
translucent: pipeline_3d,
};
}
function create_stream_buffer(size: number) {