render-zig

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

commit bbf61fdd93fb8e5a352d58fd7577bc86a52caa54
parent 4fdfd4f1ecc51530bfe22e21e92c7890baf7a143
Author: Christian Ermann <christianermann@gmail.com>
Date:   Wed, 10 Jul 2024 19:20:44 -0400

Add PBR mesh shader

Diffstat:
Msrc/main.zig | 13+++++++++----
Msrc/material.zig | 8++++++++
Msrc/render_pipeline.zig | 141+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
3 files changed, 148 insertions(+), 14 deletions(-)

diff --git a/src/main.zig b/src/main.zig @@ -292,6 +292,7 @@ const InstanceData = struct { }; pub const UniformData = struct { + pw_camera: f32x3, view_matrix: mat4, proj_matrix: mat4, }; @@ -515,7 +516,7 @@ pub const Mesh = struct { .{ 0, 0, self.scale, 0 }, .{ self.offset[0], self.offset[1], self.offset[2], 1 }, }, - .material = .{ .albedo = 0, .normal = 1 }, + .material = .{ .albedo = 0, .normal = 1, .roughness = 2, .metalness = 3 }, }; } }; @@ -563,7 +564,7 @@ pub fn main() !void { _ = try load_obj.loadFile(.{ .allocator = allocator, .mesh_buffer = &mesh_buffer, - .path = "cottage/cottage-fixed.obj", + .path = "Cottage_Clean/cottage.obj", .scale = 1, .offset = .{ 0, -5, 15 }, }); @@ -579,8 +580,10 @@ pub fn main() !void { _ = try Material.init( &textures, - "cottage/cottage_diffuse.png", - "cottage/cottage_normal-fixed.png", + "Cottage_Clean/Cottage_Clean_Base_Color_fixed.png", + "Cottage_Clean/Cottage_Clean_Normal_fixed.png", + "Cottage_Clean/Cottage_Clean_Roughness_fixed.png", + "Cottage_Clean/Cottage_Clean_Metallic_fixed.png", app.queue, ); @@ -596,6 +599,7 @@ pub fn main() !void { try app.inputs.append(camera.input.userInput()); var uniform_data = UniformData{ + .pw_camera = undefined, .view_matrix = undefined, .proj_matrix = undefined, }; @@ -611,6 +615,7 @@ pub fn main() !void { camera.update(0.01); camera.view(&uniform_data.view_matrix); camera.proj(&uniform_data.proj_matrix); + uniform_data.pw_camera = camera.position; app.queue.writeBuffer(rp.uniform_buffer, 0, &[1]UniformData{uniform_data}); camera.invView(&skybox_uniform.inv_view); diff --git a/src/material.zig b/src/material.zig @@ -4,6 +4,8 @@ const Textures = @import("textures.zig"); albedo: u32, normal: u32, +roughness: u32, +metalness: u32, const Self = @This(); @@ -11,12 +13,18 @@ pub fn init( textures: *Textures, albedo_path: []const u8, normal_path: []const u8, + roughness_path: []const u8, + metalness_path: []const u8, queue: *gpu.Queue, ) !Self { const albedo = try textures.loadFile(albedo_path, queue); const normal = try textures.loadFile(normal_path, queue); + const roughness = try textures.loadFile(roughness_path, queue); + const metalness = try textures.loadFile(metalness_path, queue); return .{ .albedo = albedo, .normal = normal, + .roughness = roughness, + .metalness = metalness, }; } diff --git a/src/render_pipeline.zig b/src/render_pipeline.zig @@ -134,6 +134,7 @@ pub const MeshRenderPipeline = struct { \\ @group(0) @binding(1) \\ var<uniform> uniform_data: UniformData; \\ struct UniformData { + \\ pw_camera: vec3<f32>, \\ view_matrix: mat4x4<f32>, \\ proj_matrix: mat4x4<f32>, \\ }; @@ -148,15 +149,22 @@ pub const MeshRenderPipeline = struct { \\ 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 tangent_matrix = transpose(mat3x3<f32>(t, b, n)); \\ \\ let world_position = model_matrix * in.position; + \\ let tangent_position = tangent_matrix * world_position.xyz; + \\ + \\ let light_position = vec3<f32>(20, 80, 20); + \\ let tangent_light_position = tangent_matrix * light_position; + \\ + \\ let pt_camera = tangent_matrix * uniform_data.pw_camera; \\ \\ return VertexOutput( \\ camera_matrix * world_position, \\ in.tex_coord, - \\ t, - \\ b, - \\ n, + \\ tangent_position, + \\ tangent_light_position, + \\ pt_camera, \\ idx, \\ ); \\ } @@ -175,9 +183,9 @@ pub const MeshRenderPipeline = struct { \\ struct VertexOutput { \\ @builtin(position) position: vec4<f32>, \\ @location(0) tex_coord: vec3<f32>, - \\ @location(1) t: vec3<f32>, - \\ @location(2) b: vec3<f32>, - \\ @location(3) n: vec3<f32>, + \\ @location(1) pt_fragment: vec3<f32>, + \\ @location(2) pt_light: vec3<f32>, + \\ @location(3) pt_camera: vec3<f32>, \\ @location(4) @interpolate(flat) instance_id: u32, \\ }; \\ @@ -190,19 +198,132 @@ pub const MeshRenderPipeline = struct { \\ struct Material { \\ albedo: u32, \\ normal: u32, + \\ roughness: u32, + \\ metalness: u32, \\ }; \\ \\ @group(0) @binding(2) var material_sampler: sampler; \\ @group(0) @binding(3) var material_texture: texture_2d_array<f32>; \\ + \\ fn diffuse_lambert(albedo: vec3<f32>) -> vec3<f32> { + \\ return albedo * 0.31830988618; + \\ } + \\ + \\ fn distribution_trowbridge_reitz_ggx( + \\ n_dot_h: f32, + \\ alpha: f32, + \\ ) -> f32 { + \\ let alpha_squared = alpha * alpha; + \\ let n_dot_h_squared = n_dot_h * n_dot_h; + \\ let denom = n_dot_h_squared * (alpha_squared - 1) + 1; + \\ let denom_squared = denom * denom; + \\ return alpha_squared / (3.14159265359 * denom_squared); + \\ } + \\ + \\ fn geometry_schlick_ggx( + \\ n_dot_x: f32, + \\ alpha: f32, + \\ ) -> f32 { + \\ let k = 0.5 * alpha; + \\ let denom = n_dot_x * (1.0 - k) + k; + \\ return n_dot_x / denom; + \\ } + \\ + \\ fn geometry_smith( + \\ n_dot_l: f32, + \\ n_dot_v: f32, + \\ alpha: f32, + \\ ) -> f32 { + \\ let g1 = geometry_schlick_ggx(n_dot_l, alpha); + \\ let g2 = geometry_schlick_ggx(n_dot_v, alpha); + \\ return g1 * g2; + \\ } + \\ + \\ fn fresnel_schlick( + \\ f0: vec3<f32>, + \\ v_dot_h: f32, + \\ ) -> vec3<f32> { + \\ return f0 + (1.0 - f0) * pow(1.0 - v_dot_h, 5.0); + \\ } + \\ + \\ fn cook_torrance( + \\ n_dot_v: f32, + \\ n_dot_l: f32, + \\ n_dot_h: f32, + \\ roughness: f32, + \\ ) -> f32 { + \\ let alpha = roughness * roughness; + \\ let distribution = distribution_trowbridge_reitz_ggx( + \\ n_dot_h, + \\ alpha, + \\ ); + \\ let geometry = geometry_smith( + \\ n_dot_l, + \\ n_dot_v, + \\ alpha, + \\ ); + \\ let denom = 4.0 * n_dot_v * n_dot_l + 0.000001; + \\ return (distribution * geometry) / denom; + \\ } + \\ + \\ fn lighting( + \\ normal: vec3<f32>, + \\ p_fragment: vec3<f32>, + \\ p_camera: vec3<f32>, + \\ p_light: vec3<f32>, + \\ c_light: vec3<f32>, + \\ albedo: vec3<f32>, + \\ roughness: f32, + \\ metalness: f32, + \\ ) -> vec3<f32> { + \\ let v = normalize(p_camera - p_fragment); + \\ let l = normalize(p_light - p_fragment); + \\ let h = normalize(v + l); + \\ + \\ let n_dot_v = max(dot(normal, v), 0.0); + \\ let n_dot_l = max(dot(normal, l), 0.0); + \\ let n_dot_h = max(dot(normal, h), 0.0); + \\ let v_dot_h = max(dot(v, h), 0.0); + \\ + \\ let f0 = mix(vec3<f32>(0.04), albedo, metalness); + \\ let k_s = fresnel_schlick(f0, v_dot_h); + \\ let k_d = 1.0 - k_s; + \\ + \\ let diffuse = k_d * diffuse_lambert(albedo); + \\ let specular = k_s * cook_torrance(n_dot_v, n_dot_l, n_dot_h, roughness); + \\ let brdf = (diffuse + specular) * n_dot_l; + \\ return brdf * c_light; + \\ } + \\ \\ @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 albedo = textureSample(material_texture, material_sampler, uv, material.albedo); + \\ var albedo = textureSample(material_texture, material_sampler, uv, material.albedo).rgb; + \\ albedo = pow(albedo, vec3<f32>(2.2)); + \\ var normal = textureSample(material_texture, material_sampler, uv, material.normal).rgb; + \\ normal = normalize(normal * 2.0 - 1.0); + \\ var roughness = textureSample(material_texture, material_sampler, uv, material.roughness).r; + \\ var metalness = textureSample(material_texture, material_sampler, uv, material.metalness).r; + \\ + \\ let light_color = normalize(vec3<f32>(23.47, 21.31, 20.79)); + \\ var c = lighting( + \\ normal, + \\ in.pt_fragment, + \\ in.pt_camera, + \\ in.pt_light, + \\ light_color, + \\ albedo.rgb, + \\ roughness, + \\ metalness, + \\ ); + \\ + \\ let ambient = vec3<f32>(0.03) * albedo; + \\ c += ambient; + \\ + \\ c = c / (c + 1.0); + \\ c = pow(c, vec3<f32>(1.0/2.2)); \\ - \\ 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); + \\ return vec4<f32>(c, 1.0); \\ } ; const fs_module = app.device.createShaderModuleWGSL("default fragment shader", fs);