commit 9bbfcb6ed996d596cd3e321fb01441585115dbdf
parent 54f8213cf6fe2f6650294d54ab5697dcc3bf31b1
Author: Christian Ermann <christianermann@gmail.com>
Date: Sun, 21 Jul 2024 14:57:18 -0400
Add bounding box rendering
Diffstat:
5 files changed, 348 insertions(+), 3 deletions(-)
diff --git a/src/line_render_pipeline.zig b/src/line_render_pipeline.zig
@@ -0,0 +1,145 @@
+const std = @import("std");
+const gpu = @import("mach_gpu");
+
+const App = @import("main.zig").App;
+const MeshBuffer = @import("main.zig").MeshBuffer;
+const UniformData = @import("main.zig").UniformData;
+const f32x4 = @import("main.zig").f32x4;
+const mat4 = @import("main.zig").mat4;
+const CubeMap = @import("cubemap.zig");
+const Textures = @import("textures.zig");
+const RenderPipeline = @import("render_pipeline.zig").RenderPipeline;
+
+const Self = @This();
+
+gpu_pipeline: *gpu.RenderPipeline,
+mesh_buffer: *const MeshBuffer,
+bind_group: *gpu.BindGroup,
+uniform_buffer: *gpu.Buffer,
+
+pub fn init(
+ app: *App,
+ mesh_buffer: *const MeshBuffer,
+ comptime shader_path: []const u8,
+) !Self {
+ const src = @embedFile(shader_path);
+
+ const module = app.device.createShaderModuleWGSL("line shader", src);
+ defer module.release();
+
+ const layouts = Self.bufferLayouts();
+ const vertex = gpu.VertexState.init(.{
+ .module = module,
+ .entry_point = "vertex",
+ .buffers = &layouts,
+ });
+
+ const blend: gpu.BlendState = .{};
+ const color_target = gpu.ColorTargetState{
+ .format = app.swap_chain.format,
+ .blend = &blend,
+ .write_mask = gpu.ColorWriteMaskFlags.all,
+ };
+ const fragment = gpu.FragmentState.init(.{
+ .module = module,
+ .entry_point = "fragment",
+ .targets = &.{color_target},
+ });
+
+ const buffer_descriptor = gpu.Buffer.Descriptor{
+ .size = @sizeOf(UniformData),
+ .usage = .{ .uniform = true, .copy_dst = true },
+ .mapped_at_creation = .false,
+ };
+ const uniform_buffer = app.device.createBuffer(&buffer_descriptor);
+
+ const bind_group_layout = Self.bindGroupLayout(app.device);
+ const bind_group_descriptor = gpu.BindGroup.Descriptor.init(.{
+ .layout = bind_group_layout,
+ .entries = &.{
+ gpu.BindGroup.Entry.buffer(0, mesh_buffer.instances.data, 0, mesh_buffer.instances.size),
+ gpu.BindGroup.Entry.buffer(1, uniform_buffer, 0, @sizeOf(UniformData)),
+ },
+ });
+ const bind_group = app.device.createBindGroup(&bind_group_descriptor);
+
+ const pipeline_layout_descriptor = gpu.PipelineLayout.Descriptor.init(.{
+ .bind_group_layouts = &.{bind_group_layout},
+ });
+ const pipeline_layout = app.device.createPipelineLayout(&pipeline_layout_descriptor);
+ defer pipeline_layout.release();
+
+ const pipeline_descriptor = gpu.RenderPipeline.Descriptor{
+ .label = "line render pipeline",
+ .fragment = &fragment,
+ .layout = pipeline_layout,
+ .depth_stencil = &.{
+ .format = .depth24_plus,
+ .depth_write_enabled = .true,
+ .depth_compare = .less,
+ },
+ .vertex = vertex,
+ .multisample = .{},
+ .primitive = .{
+ .topology = .line_list,
+ .front_face = .ccw,
+ .cull_mode = .back,
+ },
+ };
+
+ return .{
+ .gpu_pipeline = app.device.createRenderPipeline(&pipeline_descriptor),
+ .mesh_buffer = mesh_buffer,
+ .bind_group = bind_group,
+ .uniform_buffer = uniform_buffer,
+ };
+}
+
+fn bufferLayouts() [1]gpu.VertexBufferLayout {
+ const positions = gpu.VertexBufferLayout.init(.{
+ .array_stride = @sizeOf(f32x4),
+ .step_mode = .vertex,
+ .attributes = &.{
+ .{ .format = .float32x4, .shader_location = 0, .offset = 0 },
+ },
+ });
+ return .{positions};
+}
+
+fn bindGroupLayout(device: *gpu.Device) *gpu.BindGroupLayout {
+ const descriptor = gpu.BindGroupLayout.Descriptor.init(.{
+ .entries = &.{
+ gpu.BindGroupLayout.Entry.buffer(0, .{
+ .vertex = true,
+ .fragment = true,
+ }, .read_only_storage, false, 0),
+ gpu.BindGroupLayout.Entry.buffer(1, .{
+ .vertex = true,
+ .fragment = false,
+ }, .uniform, false, 0),
+ },
+ });
+ return device.createBindGroupLayout(&descriptor);
+}
+
+pub fn frame(ptr: *anyopaque, pass: *gpu.RenderPassEncoder) void {
+ const self: *Self = @ptrCast(@alignCast(ptr));
+ pass.setPipeline(self.gpu_pipeline);
+
+ pass.setVertexBuffer(0, self.mesh_buffer.positions.data, 0, self.mesh_buffer.positions.size);
+ pass.setIndexBuffer(self.mesh_buffer.indices.data, .uint32, 0, self.mesh_buffer.indices.size);
+ pass.setBindGroup(0, self.bind_group, null);
+
+ const num_indirect = self.mesh_buffer.lines_indirect.count();
+ for (0..num_indirect) |idx| {
+ const offset = idx * self.mesh_buffer.lines_indirect.attrib_size;
+ pass.drawIndexedIndirect(self.mesh_buffer.lines_indirect.data, offset);
+ }
+}
+
+pub fn pipeline(self: *Self) RenderPipeline {
+ return .{
+ .ptr = self,
+ .frameFn = frame,
+ };
+}
diff --git a/src/load_obj.zig b/src/load_obj.zig
@@ -183,7 +183,7 @@ pub fn loadFile(options: LoadFileOptions) !Mesh {
const nslice = try normals_indexed.toOwnedSlice();
const tslice = try tex_coords_indexed.toOwnedSlice();
const islice = try indices.toOwnedSlice();
- const mesh = Mesh{
+ var mesh = Mesh{
.positions = pslice,
.normals = nslice,
.tex_coords = tslice,
@@ -191,7 +191,7 @@ pub fn loadFile(options: LoadFileOptions) !Mesh {
.tangents = try allocator.alloc(f32x3, pslice.len),
.bitangents = try allocator.alloc(f32x3, pslice.len),
};
-
+ mesh.bbox = mesh.axisAlignedBoundingBox();
try tangentsAndBitangents(&mesh, allocator);
return mesh;
diff --git a/src/main.zig b/src/main.zig
@@ -7,6 +7,7 @@ const RenderPass = @import("render_pass.zig").RenderPass;
const ForwardScreenPass = @import("render_pass.zig").ForwardScreenPass;
const RenderPipeline = @import("render_pipeline.zig").RenderPipeline;
const MeshRenderPipeline = @import("mesh_render_pipeline.zig");
+const LineRenderPipeline = @import("line_render_pipeline.zig");
const SkyBoxRenderPipeline = @import("render_pipeline.zig").SkyBoxRenderPipeline;
const CubeMap = @import("cubemap.zig");
const Textures = @import("textures.zig");
@@ -399,6 +400,9 @@ pub const MeshBuffer = struct {
meshes: std.ArrayList(RenderMesh),
+ lines: std.ArrayList(RenderLines),
+ lines_indirect: Buffer([5]u32),
+
pub fn init(options: MeshBufferOptions) MeshBuffer {
const device = options.device;
const n_vertices = options.n_vertices;
@@ -455,10 +459,47 @@ pub const MeshBuffer = struct {
.{ .indirect = true, .copy_dst = true },
false,
),
+ .lines_indirect = Buffer([5]u32).init(
+ options.n_meshes,
+ device,
+ .{ .indirect = true, .copy_dst = true },
+ false,
+ ),
.meshes = std.ArrayList(RenderMesh).init(options.allocator),
+ .lines = std.ArrayList(RenderLines).init(options.allocator),
};
}
+ pub fn initLines(
+ self: *MeshBuffer,
+ positions: []const f32x4,
+ indices: []const u32,
+ ) !*RenderLines {
+ const num_vertices = positions.len;
+ const num_indices = indices.len;
+
+ const offset_p = try self.positions.allocWrite(positions, self.queue);
+ const offset_i = try self.indices.allocWrite(indices, self.queue);
+
+ const instance_id = self.indirect.count();
+ const indirect_args = [_][5]u32{.{
+ @intCast(num_indices),
+ 1,
+ offset_i,
+ offset_p,
+ instance_id,
+ }};
+ _ = try self.lines_indirect.allocWrite(&indirect_args, self.queue);
+
+ const lines = RenderLines{
+ .num_vertices = num_vertices,
+ .num_indices = num_indices,
+ };
+
+ try self.lines.append(lines);
+ return &self.lines.items[0];
+ }
+
pub fn initMesh(
self: *MeshBuffer,
options: *const MeshOptions,
@@ -538,6 +579,36 @@ pub const MeshBuffer = struct {
}
};
+pub const RenderLines = struct {
+ num_vertices: usize,
+ num_indices: usize,
+ scale: f32 = 1.0,
+ offset: f32x3 = .{ 0.0, 0.0, 0.0 },
+ dirty: bool = false,
+
+ pub fn instanceData(self: *const RenderLines) InstanceData {
+ return InstanceData{
+ .model_matrix = .{
+ .{ self.scale, 0, 0, 0 },
+ .{ 0, self.scale, 0, 0 },
+ .{ 0, 0, self.scale, 0 },
+ .{ self.offset[0], self.offset[1], self.offset[2], 1 },
+ },
+ .material = undefined,
+ };
+ }
+
+ pub fn setScale(self: *RenderMesh, scale: f32) void {
+ self.scale = scale;
+ self.dirty = true;
+ }
+
+ pub fn setOffset(self: *RenderMesh, offset: f32x3) void {
+ self.offset = offset;
+ self.dirty = true;
+ }
+};
+
pub const RenderMesh = struct {
num_vertices: usize,
num_indices: usize,
@@ -645,7 +716,13 @@ pub fn main() !void {
.offset = .{ 0, 0, 0 },
}, allocator);
+ const corners = mesh.bboxVertices();
+ const indices = Mesh.bboxIndices();
+
+ _ = try mesh_buffer.initLines(&corners, &indices);
+
var rp = try MeshRenderPipeline.init(&app, &mesh_buffer, &textures, "shaders/mesh_clusters.wgsl");
+ var lrp = try LineRenderPipeline.init(&app, &mesh_buffer, "shaders/lines.wgsl");
const material = try Material.init(
&textures,
@@ -663,7 +740,7 @@ pub fn main() !void {
var fp = ForwardScreenPass.init(.{
.depth = app.depth_texture_view,
- .pipelines = &.{ rp.pipeline(), skybox.pipeline() },
+ .pipelines = &.{ rp.pipeline(), lrp.pipeline(), skybox.pipeline() },
});
const passes = [_]RenderPass{fp.renderPass()};
@@ -689,6 +766,7 @@ pub fn main() !void {
camera.proj(&uniform_data.proj_matrix);
uniform_data.pw_camera = camera.position;
app.queue.writeBuffer(rp.uniform_buffer, 0, &[1]UniformData{uniform_data});
+ app.queue.writeBuffer(lrp.uniform_buffer, 0, &[1]UniformData{uniform_data});
camera.invView(&skybox_uniform.inv_view);
camera.invProj(&skybox_uniform.inv_proj);
diff --git a/src/mesh.zig b/src/mesh.zig
@@ -3,6 +3,8 @@ const std = @import("std");
const f32x3 = @Vector(3, f32);
const f32x4 = @Vector(4, f32);
+const Self = @This();
+
positions: []f32x4,
normals: []f32x3,
tex_coords: []f32x3,
@@ -10,9 +12,72 @@ tangents: []f32x3,
bitangents: []f32x3,
indices: []u32,
descriptors: ?[]MeshletDescriptor = null,
+bbox: [6]f32 = undefined,
pub const MeshletDescriptor = struct {
offset: u32,
num_vertices: u32,
num_triangles: u32,
};
+
+pub fn axisAlignedBoundingBox(self: *const Self) [6]f32 {
+ var min_x: f32 = self.positions[0][0];
+ var max_x: f32 = self.positions[0][0];
+ var min_y: f32 = self.positions[0][1];
+ var max_y: f32 = self.positions[0][1];
+ var min_z: f32 = self.positions[0][2];
+ var max_z: f32 = self.positions[0][2];
+ for (self.positions) |p| {
+ const x = p[0];
+ const y = p[1];
+ const z = p[2];
+ if (x < min_x) {
+ min_x = x;
+ }
+ if (y < min_y) {
+ min_y = y;
+ }
+ if (z < min_z) {
+ min_z = z;
+ }
+ if (x > max_x) {
+ max_x = x;
+ }
+ if (y > max_y) {
+ max_y = y;
+ }
+ if (z > max_z) {
+ max_z = z;
+ }
+ }
+ std.log.info("bbox min: {} {} {}", .{ min_x, min_y, min_z });
+ std.log.info("bbox max: {} {} {}", .{ max_x, max_y, max_z });
+ return .{ min_x, min_y, min_z, max_x, max_y, max_z };
+}
+
+pub fn bboxVertices(self: *const Self) [8]f32x4 {
+ const min_x = self.bbox[0];
+ const min_y = self.bbox[1];
+ const min_z = self.bbox[2];
+ const max_x = self.bbox[3];
+ const max_y = self.bbox[4];
+ const max_z = self.bbox[5];
+ return .{
+ .{ min_x, min_y, min_z, 1 },
+ .{ max_x, min_y, min_z, 1 },
+ .{ max_x, max_y, min_z, 1 },
+ .{ min_x, max_y, min_z, 1 },
+ .{ min_x, min_y, max_z, 1 },
+ .{ max_x, min_y, max_z, 1 },
+ .{ max_x, max_y, max_z, 1 },
+ .{ min_x, max_y, max_z, 1 },
+ };
+}
+
+pub fn bboxIndices() [24]u32 {
+ return .{
+ 0, 1, 1, 2, 2, 3, 3, 0,
+ 4, 5, 5, 6, 6, 7, 7, 4,
+ 0, 4, 1, 5, 2, 6, 3, 7,
+ };
+}
diff --git a/src/shaders/lines.wgsl b/src/shaders/lines.wgsl
@@ -0,0 +1,57 @@
+
+struct VertexInput {
+ @location(0) position: vec4<f32>,
+};
+
+struct VertexOutput {
+ @builtin(position) clip_position: vec4<f32>,
+ @location(0) @interpolate(flat) instance_id: u32,
+};
+
+@group(0) @binding(0)
+var<storage, read> instances: array<Instance>;
+struct Instance {
+ model_matrix: mat4x4<f32>,
+ material: Material,
+};
+struct Material {
+ albedo: u32,
+ normal: u32,
+ roughness: u32,
+ metalness: u32,
+};
+
+@group(0) @binding(1)
+var<uniform> uniform_data: UniformData;
+struct UniformData {
+ pw_camera: vec3<f32>,
+ view_matrix: mat4x4<f32>,
+ proj_matrix: mat4x4<f32>,
+};
+
+@vertex fn vertex(
+ in: VertexInput,
+ @builtin(instance_index) idx: u32,
+) -> VertexOutput {
+ let model_matrix = instances[0].model_matrix;
+ let camera_matrix = uniform_data.proj_matrix * uniform_data.view_matrix;
+ let pc_vertex = camera_matrix * model_matrix * in.position;
+ return VertexOutput(pc_vertex, idx);
+}
+
+@group(0) @binding(2) var material_sampler: sampler;
+@group(0) @binding(3) var material_texture: texture_2d_array<f32>;
+
+@fragment fn fragment(in: VertexOutput) -> @location(0) vec4<f32> {
+ let colors = array<vec3<f32>, 6>(
+ vec3<f32>(1, 1, 0),
+ vec3<f32>(0, 1, 1),
+ vec3<f32>(1, 0, 1),
+ vec3<f32>(1, 0, 0),
+ vec3<f32>(0, 1, 0),
+ vec3<f32>(0, 0, 1),
+ );
+ let color = colors[in.instance_id % 6];
+
+ return vec4<f32>(0.0, 0.0, 0.0, 1.0);
+}