render-zig

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

commit 1494036a2c88de72e059e729880321db83593b55
parent 1392f56b6a10e64db00d73e6b24a12f76ed8d57d
Author: Christian Ermann <christianermann@gmail.com>
Date:   Fri, 12 Jul 2024 15:40:54 -0400

Split MeshRenderPipeline into separate file and embed shaders from file

Diffstat:
Msrc/main.zig | 4++--
Asrc/mesh_render_pipeline.zig | 187+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/render_pipeline.zig | 390-------------------------------------------------------------------------------
Asrc/shaders/mesh_pbr.wgsl | 192+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 381 insertions(+), 392 deletions(-)

diff --git a/src/main.zig b/src/main.zig @@ -6,7 +6,7 @@ const builtin = @import("builtin"); const RenderPass = @import("render_pass.zig").RenderPass; const ForwardScreenPass = @import("render_pass.zig").ForwardScreenPass; const RenderPipeline = @import("render_pipeline.zig").RenderPipeline; -const MeshRenderPipeline = @import("render_pipeline.zig").MeshRenderPipeline; +const MeshRenderPipeline = @import("mesh_render_pipeline.zig"); const SkyBoxRenderPipeline = @import("render_pipeline.zig").SkyBoxRenderPipeline; const CubeMap = @import("cubemap.zig"); const Textures = @import("textures.zig"); @@ -613,7 +613,7 @@ pub fn main() !void { //.offset = .{ 0, -0.5, 0 }, }); - var rp = try MeshRenderPipeline.init(&app, &mesh_buffer, &textures); + var rp = try MeshRenderPipeline.init(&app, &mesh_buffer, &textures, "shaders/mesh_pbr.wgsl"); const material = try Material.init( &textures, diff --git a/src/mesh_render_pipeline.zig b/src/mesh_render_pipeline.zig @@ -0,0 +1,187 @@ +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 MeshRenderPipeline = @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, + textures: *const Textures, + comptime shader_path: []const u8, +) !MeshRenderPipeline { + const src = @embedFile(shader_path); + + const module = app.device.createShaderModuleWGSL("mesh shader", src); + defer module.release(); + + const layouts = MeshRenderPipeline.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 = MeshRenderPipeline.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)), + gpu.BindGroup.Entry.sampler(2, textures.sampler), + gpu.BindGroup.Entry.textureView(3, textures.view), + }, + }); + 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 = "mesh render pipeline", + .fragment = &fragment, + .layout = pipeline_layout, + .depth_stencil = &.{ + .format = .depth24_plus, + .depth_write_enabled = .true, + .depth_compare = .less, + }, + .vertex = vertex, + .multisample = .{}, + .primitive = .{ + .topology = .triangle_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() [5]gpu.VertexBufferLayout { + const positions = gpu.VertexBufferLayout.init(.{ + .array_stride = @sizeOf(f32x4), + .step_mode = .vertex, + .attributes = &.{ + .{ .format = .float32x4, .shader_location = 0, .offset = 0 }, + }, + }); + const normals = gpu.VertexBufferLayout.init(.{ + .array_stride = @sizeOf(f32x4), + .step_mode = .vertex, + .attributes = &.{ + .{ .format = .float32x3, .shader_location = 1, .offset = 0 }, + }, + }); + const tex_coords = gpu.VertexBufferLayout.init(.{ + .array_stride = @sizeOf(f32x4), + .step_mode = .vertex, + .attributes = &.{ + .{ .format = .float32x3, .shader_location = 2, .offset = 0 }, + }, + }); + 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 { + 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), + gpu.BindGroupLayout.Entry.sampler(2, .{ + .vertex = false, + .fragment = true, + }, .filtering), + gpu.BindGroupLayout.Entry.texture(3, .{ + .vertex = false, + .fragment = true, + }, .float, .dimension_2d_array, false), + }, + }); + return device.createBindGroupLayout(&descriptor); +} + +pub fn frame(ptr: *anyopaque, pass: *gpu.RenderPassEncoder) void { + const self: *MeshRenderPipeline = @ptrCast(@alignCast(ptr)); + pass.setPipeline(self.gpu_pipeline); + + 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); + + for (0..self.mesh_buffer.meshes.items.len) |mesh_idx| { + const offset = mesh_idx * 5 * @sizeOf(u32); + pass.drawIndexedIndirect(self.mesh_buffer.indirect.data, offset); + } +} + +pub fn pipeline(self: *MeshRenderPipeline) RenderPipeline { + return .{ + .ptr = self, + .frameFn = frame, + }; +} diff --git a/src/render_pipeline.zig b/src/render_pipeline.zig @@ -91,396 +91,6 @@ pub const TriangleRenderPipeline = struct { } }; -pub const MeshRenderPipeline = struct { - 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, - textures: *const Textures, - ) !MeshRenderPipeline { - const vs = - \\ struct VertexInput { - \\ @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) 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) - \\ var<storage, read> instances: array<Instance>; - \\ struct Instance { - \\ model_matrix: mat4x4<f32>, - \\ material: Material, - \\ }; - \\ struct Material { - \\ albedo: u32, - \\ normal: 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 main( - \\ in: VertexInput, - \\ @builtin(instance_index) idx: u32, - \\ ) -> VertexOutput { - \\ let model_matrix = instances[idx].model_matrix; - \\ let camera_matrix = uniform_data.proj_matrix * uniform_data.view_matrix; - \\ - \\ 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, - \\ tangent_position, - \\ tangent_light_position, - \\ pt_camera, - \\ idx, - \\ ); - \\ } - ; - const vs_module = app.device.createShaderModuleWGSL("default vertex shader", vs); - defer vs_module.release(); - - const layouts = MeshRenderPipeline.bufferLayouts(); - const vertex = gpu.VertexState.init(.{ - .module = vs_module, - .entry_point = "main", - .buffers = &layouts, - }); - - const fs = - \\ struct VertexOutput { - \\ @builtin(position) position: vec4<f32>, - \\ @location(0) tex_coord: 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, - \\ }; - \\ - \\ @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(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); - \\ 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)); - \\ - \\ return vec4<f32>(c, 1.0); - \\ } - ; - const fs_module = app.device.createShaderModuleWGSL("default fragment shader", fs); - defer fs_module.release(); - - 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 = fs_module, - .entry_point = "main", - .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 = MeshRenderPipeline.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)), - gpu.BindGroup.Entry.sampler(2, textures.sampler), - gpu.BindGroup.Entry.textureView(3, textures.view), - }, - }); - 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 = "default render pipeline", - .fragment = &fragment, - .layout = pipeline_layout, - .depth_stencil = &.{ - .format = .depth24_plus, - .depth_write_enabled = .true, - .depth_compare = .less, - }, - .vertex = vertex, - .multisample = .{}, - .primitive = .{ - .topology = .triangle_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() [5]gpu.VertexBufferLayout { - const positions = gpu.VertexBufferLayout.init(.{ - .array_stride = @sizeOf(f32x4), - .step_mode = .vertex, - .attributes = &.{ - .{ .format = .float32x4, .shader_location = 0, .offset = 0 }, - }, - }); - const normals = gpu.VertexBufferLayout.init(.{ - .array_stride = @sizeOf(f32x4), - .step_mode = .vertex, - .attributes = &.{ - .{ .format = .float32x3, .shader_location = 1, .offset = 0 }, - }, - }); - const tex_coords = gpu.VertexBufferLayout.init(.{ - .array_stride = @sizeOf(f32x4), - .step_mode = .vertex, - .attributes = &.{ - .{ .format = .float32x3, .shader_location = 2, .offset = 0 }, - }, - }); - 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 { - 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), - gpu.BindGroupLayout.Entry.sampler(2, .{ - .vertex = false, - .fragment = true, - }, .filtering), - gpu.BindGroupLayout.Entry.texture(3, .{ - .vertex = false, - .fragment = true, - }, .float, .dimension_2d_array, false), - }, - }); - return device.createBindGroupLayout(&descriptor); - } - - pub fn frame(ptr: *anyopaque, pass: *gpu.RenderPassEncoder) void { - const self: *MeshRenderPipeline = @ptrCast(@alignCast(ptr)); - pass.setPipeline(self.gpu_pipeline); - - 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); - - for (0..self.mesh_buffer.meshes.items.len) |mesh_idx| { - const offset = mesh_idx * 5 * @sizeOf(u32); - pass.drawIndexedIndirect(self.mesh_buffer.indirect.data, offset); - } - } - - pub fn pipeline(self: *MeshRenderPipeline) RenderPipeline { - return .{ - .ptr = self, - .frameFn = frame, - }; - } -}; - pub const SkyBoxRenderPipeline = struct { gpu_pipeline: *gpu.RenderPipeline, uniform_buffer: *gpu.Buffer, diff --git a/src/shaders/mesh_pbr.wgsl b/src/shaders/mesh_pbr.wgsl @@ -0,0 +1,192 @@ +struct VertexInput { + @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) tex_coord: 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, +}; + +@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[idx].model_matrix; + let camera_matrix = uniform_data.proj_matrix * uniform_data.view_matrix; + + 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, + tangent_position, + tangent_light_position, + pt_camera, + idx, + ); +} + + +@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 fragment(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); + 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)); + + return vec4<f32>(c, 1.0); +}