render-zig

A 3D rendering engine written in Zig
git clone git://git.christianermann.dev/render-zig
Log | Files | Refs

commit eb3897fc514820f47d2f70cc21b617572270cb71
parent 39c5ff0b24c0f512c4ab7a65225501c8c37759f4
Author: Christian Ermann <christianermann@gmail.com>
Date:   Sun,  7 Jul 2024 16:19:25 -0400

Add normal maps

Diffstat:
Acottage/cottage_normal-fixed.png | 0
Msrc/load_obj.zig | 72+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Msrc/main.zig | 11++++++++++-
Msrc/material.zig | 5+++--
Msrc/render_pipeline.zig | 62++++++++++++++++++++++++++++++++++++++++++++++++++------------
5 files changed, 134 insertions(+), 16 deletions(-)

diff --git a/cottage/cottage_normal-fixed.png b/cottage/cottage_normal-fixed.png Binary files differ. diff --git a/src/load_obj.zig b/src/load_obj.zig @@ -188,16 +188,86 @@ 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_data = MeshVertexData{ .positions = pslice, .normals = nslice, .tex_coords = tslice, .indices = islice, + .tangents = try allocator.alloc(f32x3, pslice.len), + .bitangents = try allocator.alloc(f32x3, pslice.len), }; + + try tangentsAndBitangents(&mesh_data, allocator); + return options.mesh_buffer.initMesh(&.{ .scale = options.scale, .offset = options.offset, .data = &mesh_data, }); } + +fn tangentsAndBitangents( + mesh_data: *const MeshVertexData, + allocator: std.mem.Allocator, +) !void { + const positions = mesh_data.positions; + const tex_coords = mesh_data.tex_coords; + const indices = mesh_data.indices; + const tangents = mesh_data.tangents; + const bitangents = mesh_data.bitangents; + + @memset(std.mem.asBytes(tangents), 0); + @memset(std.mem.asBytes(bitangents), 0); + if (tex_coords.len <= 0) { + return; + } + + var references = try allocator.alloc(u32, positions.len); + defer allocator.free(references); + @memset(references, 0); + + var i: u32 = 0; + while (i < indices.len) : (i += 3) { + const idx_0 = indices[i + 0]; + const idx_1 = indices[i + 1]; + const idx_2 = indices[i + 2]; + + const p0: f32x3 = @as([4]f32, positions[idx_0])[0..3].*; + const p1: f32x3 = @as([4]f32, positions[idx_1])[0..3].*; + const p2: f32x3 = @as([4]f32, positions[idx_2])[0..3].*; + const delta_p1 = p1 - p0; + const delta_p2 = p2 - p0; + + const t0 = tex_coords[idx_0]; + const t1 = tex_coords[idx_1]; + const t2 = tex_coords[idx_2]; + const delta_t1 = t1 - t0; + const delta_t2 = t2 - t0; + + const r = 1.0 / (delta_t1[0] * delta_t2[1] - delta_t1[1] * delta_t2[0]); + + const tangent_a = delta_p1 * @as(f32x3, @splat(delta_t2[1])); + const tangent_b = delta_p2 * @as(f32x3, @splat(delta_t1[1])); + const tangent = (tangent_a - tangent_b) * @as(f32x3, @splat(r)); + tangents[idx_0] += tangent; + tangents[idx_1] += tangent; + tangents[idx_2] += tangent; + + const bitangent_a = delta_p2 * @as(f32x3, @splat(delta_t1[0])); + const bitangent_b = delta_p1 * @as(f32x3, @splat(delta_t2[0])); + const bitangent = (bitangent_a - bitangent_b) * @as(f32x3, @splat(-r)); + bitangents[idx_0] += bitangent; + bitangents[idx_1] += bitangent; + bitangents[idx_2] += bitangent; + + references[idx_0] += 1; + references[idx_1] += 1; + references[idx_2] += 1; + } + + for (references, 0..references.len) |ref_count, idx| { + const scale = @as(f32x3, @splat(1 / @as(f32, @floatFromInt(ref_count)))); + tangents[idx] *= scale; + bitangents[idx] *= scale; + } +} diff --git a/src/main.zig b/src/main.zig @@ -409,6 +409,8 @@ pub const MeshVertexData = struct { positions: []f32x4, normals: []f32x3, tex_coords: []f32x3, + tangents: []f32x3, + bitangents: []f32x3, indices: []u32, }; @@ -425,6 +427,8 @@ pub const MeshBuffer = struct { positions: Buffer, normals: Buffer, tex_coords: Buffer, + tangents: Buffer, + bitangents: Buffer, indices: Buffer, instances: Buffer, @@ -443,6 +447,8 @@ pub const MeshBuffer = struct { .positions = Buffer.init_vtx(n_vertices, device), .normals = Buffer.init_vtx(n_vertices, device), .tex_coords = Buffer.init_vtx(n_vertices, device), + .tangents = Buffer.init_vtx(n_vertices, device), + .bitangents = Buffer.init_vtx(n_vertices, device), .indices = Buffer.init_idx(n_indices, device), .instances = Buffer.init_instance(options.n_meshes, device), .indirect = Buffer.init_indirect(options.n_meshes, device), @@ -459,6 +465,8 @@ pub const MeshBuffer = struct { const offset_p = try self.positions.allocWrite(mesh_data.positions, self.queue); const offset_n = try self.normals.allocWrite(mesh_data.normals, self.queue); const offset_t = try self.tex_coords.allocWrite(mesh_data.tex_coords, self.queue); + _ = try self.tangents.allocWrite(mesh_data.tangents, self.queue); + _ = try self.bitangents.allocWrite(mesh_data.bitangents, self.queue); std.log.info("pnt offsets: {}, {}, {} bytes", .{ offset_p, offset_n, offset_t }); const offset_i = try self.indices.allocWrite(mesh_data.indices, self.queue); @@ -507,7 +515,7 @@ pub const Mesh = struct { .{ 0, 0, self.scale, 0 }, .{ self.offset[0], self.offset[1], self.offset[2], 1 }, }, - .material = .{ .albedo = 0, .normal = 0 }, + .material = .{ .albedo = 0, .normal = 1 }, }; } }; @@ -572,6 +580,7 @@ pub fn main() !void { _ = try Material.init( &textures, "cottage/cottage_diffuse.png", + "cottage/cottage_normal-fixed.png", app.queue, ); diff --git a/src/material.zig b/src/material.zig @@ -10,12 +10,13 @@ const Self = @This(); pub fn init( textures: *Textures, albedo_path: []const u8, + normal_path: []const u8, queue: *gpu.Queue, ) !Self { const albedo = try textures.loadFile(albedo_path, queue); - + const normal = try textures.loadFile(normal_path, queue); return .{ .albedo = albedo, - .normal = 0, + .normal = normal, }; } diff --git a/src/render_pipeline.zig b/src/render_pipeline.zig @@ -107,13 +107,17 @@ pub const MeshRenderPipeline = struct { \\ @location(0) position: vec4<f32>, \\ @location(1) normal: vec3<f32>, \\ @location(2) tex_coord: vec3<f32>, + \\ @location(3) tangent: vec3<f32>, + \\ @location(4) bitangent: vec3<f32>, \\ }; \\ \\ struct VertexOutput { \\ @builtin(position) clip_position: vec4<f32>, - \\ @location(0) normal: vec3<f32>, - \\ @location(1) tex_coord: vec3<f32>, - \\ @location(2) @interpolate(flat) instance_id: u32, + \\ @location(0) tex_coord: vec3<f32>, + \\ @location(1) t: vec3<f32>, + \\ @location(2) b: vec3<f32>, + \\ @location(3) n: vec3<f32>, + \\ @location(4) @interpolate(flat) instance_id: u32, \\ }; \\ \\ @group(0) @binding(0) @@ -140,8 +144,21 @@ pub const MeshRenderPipeline = struct { \\ ) -> VertexOutput { \\ let model_matrix = instances[idx].model_matrix; \\ let camera_matrix = uniform_data.proj_matrix * uniform_data.view_matrix; - \\ let matrix = camera_matrix * model_matrix; - \\ return VertexOutput(matrix * in.position, in.normal, in.tex_coord, idx); + \\ + \\ let t = normalize((model_matrix * vec4(in.tangent, 0.0)).xyz); + \\ let b = normalize((model_matrix * vec4(in.bitangent, 0.0)).xyz); + \\ let n = normalize((model_matrix * vec4(in.normal, 0.0)).xyz); + \\ + \\ let world_position = model_matrix * in.position; + \\ + \\ return VertexOutput( + \\ camera_matrix * world_position, + \\ in.tex_coord, + \\ t, + \\ b, + \\ n, + \\ idx, + \\ ); \\ } ; const vs_module = app.device.createShaderModuleWGSL("default vertex shader", vs); @@ -157,9 +174,11 @@ pub const MeshRenderPipeline = struct { const fs = \\ struct VertexOutput { \\ @builtin(position) position: vec4<f32>, - \\ @location(0) normal: vec3<f32>, - \\ @location(1) tex_coord: vec3<f32>, - \\ @location(2) @interpolate(flat) instance_id: u32, + \\ @location(0) tex_coord: vec3<f32>, + \\ @location(1) t: vec3<f32>, + \\ @location(2) b: vec3<f32>, + \\ @location(3) n: vec3<f32>, + \\ @location(4) @interpolate(flat) instance_id: u32, \\ }; \\ \\ @group(0) @binding(0) @@ -179,8 +198,11 @@ pub const MeshRenderPipeline = struct { \\ @fragment fn main(in: VertexOutput) -> @location(0) vec4<f32> { \\ let material = instances[in.instance_id].material; \\ let uv = vec2<f32>(in.tex_coord.x, 1 - in.tex_coord.y); - \\ let sample = textureSample(material_texture, material_sampler, uv, material.albedo); - \\ return sample; + \\ let albedo = textureSample(material_texture, material_sampler, uv, material.albedo); + \\ + \\ let tangent_matrix = mat3x3<f32>(in.t, in.b, in.n); + \\ let normal = textureSample(material_texture, material_sampler, uv, material.normal); + \\ return vec4<f32>((tangent_matrix * normal.rgb + 1.0) * 0.5, 1.0); \\ } ; const fs_module = app.device.createShaderModuleWGSL("default fragment shader", fs); @@ -249,7 +271,7 @@ pub const MeshRenderPipeline = struct { }; } - fn bufferLayouts() [3]gpu.VertexBufferLayout { + fn bufferLayouts() [5]gpu.VertexBufferLayout { const positions = gpu.VertexBufferLayout.init(.{ .array_stride = @sizeOf(f32x4), .step_mode = .vertex, @@ -271,7 +293,21 @@ pub const MeshRenderPipeline = struct { .{ .format = .float32x3, .shader_location = 2, .offset = 0 }, }, }); - return .{ positions, normals, tex_coords }; + const tangents = gpu.VertexBufferLayout.init(.{ + .array_stride = @sizeOf(f32x4), + .step_mode = .vertex, + .attributes = &.{ + .{ .format = .float32x3, .shader_location = 3, .offset = 0 }, + }, + }); + const bitangents = gpu.VertexBufferLayout.init(.{ + .array_stride = @sizeOf(f32x4), + .step_mode = .vertex, + .attributes = &.{ + .{ .format = .float32x3, .shader_location = 4, .offset = 0 }, + }, + }); + return .{ positions, normals, tex_coords, tangents, bitangents }; } fn bindGroupLayout(device: *gpu.Device) *gpu.BindGroupLayout { @@ -305,6 +341,8 @@ pub const MeshRenderPipeline = struct { pass.setVertexBuffer(0, self.mesh_buffer.positions.data, 0, self.mesh_buffer.positions.size); pass.setVertexBuffer(1, self.mesh_buffer.normals.data, 0, self.mesh_buffer.normals.size); pass.setVertexBuffer(2, self.mesh_buffer.tex_coords.data, 0, self.mesh_buffer.tex_coords.size); + pass.setVertexBuffer(3, self.mesh_buffer.tangents.data, 0, self.mesh_buffer.tangents.size); + pass.setVertexBuffer(4, self.mesh_buffer.bitangents.data, 0, self.mesh_buffer.bitangents.size); pass.setBindGroup(0, self.bind_group, null); pass.setIndexBuffer(self.mesh_buffer.indices.data, .uint32, 0, self.mesh_buffer.indices.size);