commit eb3897fc514820f47d2f70cc21b617572270cb71
parent 39c5ff0b24c0f512c4ab7a65225501c8c37759f4
Author: Christian Ermann <christianermann@gmail.com>
Date: Sun, 7 Jul 2024 16:19:25 -0400
Add normal maps
Diffstat:
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);