render-zig

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

commit d0f22beff84f999692e3194da21e16634ea73a4a
parent 50c1e63b87a5821cdbb5720d22f360f4ef3d64ab
Author: Christian Ermann <christianermann@gmail.com>
Date:   Fri, 17 May 2024 15:14:30 -0400

Add skybox render pipeline (partial)

Diffstat:
Msrc/camera.zig | 43+++++++++++++++++++++++++++++++++++++++++++
Msrc/main.zig | 13++++++++++++-
Msrc/render_pipeline.zig | 142+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 197 insertions(+), 1 deletion(-)

diff --git a/src/camera.zig b/src/camera.zig @@ -119,6 +119,18 @@ fn lookAt(eye: f32x3, target: f32x3, up: f32x3, mat: *mat4) void { }; } +fn invLookAt(eye: f32x3, target: f32x3, up: f32x3, mat: *mat4) void { + const f = f32x3_normalize(target - eye); + const s = f32x3_normalize(f32x3_mul(f, up)); + const u = f32x3_normalize(f32x3_mul(s, f)); + mat.* = .{ + .{ s[0], s[1], s[2], 0 }, + .{ u[0], u[1], u[2], 0 }, + .{ -f[0], -f[1], -f[2], 0 }, + .{ f32x3_dot(s, eye), f32x3_dot(u, eye), f32x3_dot(f, eye), 1.0 }, + }; +} + fn perspective(fovy: f32, aspect: f32, near: f32, far: f32, mat: *mat4) void { const tan_half_fov = @tan(std.math.degreesToRadians(f32, fovy * 0.5)); const a = 1.0 / (aspect * tan_half_fov); @@ -133,6 +145,20 @@ fn perspective(fovy: f32, aspect: f32, near: f32, far: f32, mat: *mat4) void { }; } +fn invPerspective(fovy: f32, aspect: f32, near: f32, far: f32, mat: *mat4) void { + const tan_half_fov = @tan(std.math.degreesToRadians(f32, fovy * 0.5)); + const a = aspect * tan_half_fov; + const b = tan_half_fov; + const c = (far + near) / (2 * far * near); + const d = (near - far) / (2 * far * near); + mat.* = .{ + .{ a, 0, 0, 0 }, + .{ 0, b, 0, 0 }, + .{ 0, 0, 0, d }, + .{ 0, 0, -1, c }, + }; +} + pub fn view(self: *const Camera, mat: *mat4) void { const cos_yaw = @cos(std.math.degreesToRadians(f32, self.yaw)); const sin_yaw = @sin(std.math.degreesToRadians(f32, self.yaw)); @@ -146,10 +172,27 @@ pub fn view(self: *const Camera, mat: *mat4) void { lookAt(self.position, target, f32x3{ 0, 1, 0 }, mat); } +pub fn invView(self: *const Camera, mat: *mat4) void { + const cos_yaw = @cos(std.math.degreesToRadians(f32, self.yaw)); + const sin_yaw = @sin(std.math.degreesToRadians(f32, self.yaw)); + const cos_pitch = @cos(std.math.degreesToRadians(f32, self.pitch)); + const sin_pitch = @sin(std.math.degreesToRadians(f32, self.pitch)); + const forward = f32x3_normalize( + f32x3{ cos_yaw * cos_pitch, sin_pitch, sin_yaw * cos_pitch }, + ); + + const target = self.position + forward; + invLookAt(self.position, target, f32x3{ 0, 1, 0 }, mat); +} + pub fn proj(self: *const Camera, mat: *mat4) void { perspective(self.fovy, self.aspect, self.near, self.far, mat); } +pub fn invProj(self: *const Camera, mat: *mat4) void { + invPerspective(self.fovy, self.aspect, self.near, self.far, mat); +} + pub fn update(self: *Camera, _: f32) void { var yaw: f32 = 0; if (self.input.right) { diff --git a/src/main.zig b/src/main.zig @@ -5,6 +5,7 @@ const builtin = @import("builtin"); const RenderPipeline = @import("render_pipeline.zig").RenderPipeline; const MeshRenderPipeline = @import("render_pipeline.zig").MeshRenderPipeline; +const SkyBoxRenderPipeline = @import("render_pipeline.zig").SkyBoxRenderPipeline; const Camera = @import("camera.zig"); const input = @import("input.zig"); @@ -561,8 +562,9 @@ pub fn main() !void { }); var rp = try MeshRenderPipeline.init(&app, &mesh_buffer, allocator); + var skybox = SkyBoxRenderPipeline.init(&app); - const pipelines = [_]RenderPipeline{rp.pipeline()}; + const pipelines = [_]RenderPipeline{ rp.pipeline(), skybox.pipeline() }; try app.inputs.append(camera.input.userInput()); @@ -570,6 +572,11 @@ pub fn main() !void { .view_matrix = undefined, .proj_matrix = undefined, }; + + var skybox_uniform = SkyBoxRenderPipeline.UniformData{ + .inv_view = undefined, + .inv_proj = undefined, + }; while (!app.window.shouldClose()) { glfw.pollEvents(); @@ -578,6 +585,10 @@ pub fn main() !void { camera.proj(&uniform_data.proj_matrix); app.queue.writeBuffer(rp.uniform_buffer, 0, &[1]UniformData{uniform_data}); + camera.invView(&skybox_uniform.inv_view); + camera.invProj(&skybox_uniform.inv_proj); + app.queue.writeBuffer(skybox.uniform_buffer, 0, &[1]SkyBoxRenderPipeline.UniformData{skybox_uniform}); + try app.frame(&pipelines); } } diff --git a/src/render_pipeline.zig b/src/render_pipeline.zig @@ -5,6 +5,7 @@ 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; pub const RenderPipeline = struct { ptr: *anyopaque, @@ -278,3 +279,144 @@ pub const MeshRenderPipeline = struct { }; } }; + +pub const SkyBoxRenderPipeline = struct { + gpu_pipeline: *gpu.RenderPipeline, + uniform_buffer: *gpu.Buffer, + bind_group: *gpu.BindGroup, + + pub const UniformData = struct { + inv_view: mat4, + inv_proj: mat4, + }; + + pub fn init(app: *App) SkyBoxRenderPipeline { + const vs = + \\ struct VertexOutput { + \\ @builtin(position) clip_position: vec4<f32>, + \\ @location(0) position: vec4<f32>, + \\ }; + \\ + \\ @vertex fn main( + \\ @builtin(vertex_index) VertexIndex : u32 + \\ ) -> VertexOutput { + \\ var pos = array<vec2<f32>, 3>( + \\ vec2<f32>(-1, 3), + \\ vec2<f32>(-1, -1), + \\ vec2<f32>( 3, -1), + \\ ); + \\ let vtx_pos = vec4<f32>(pos[VertexIndex], 1, 1); + \\ return VertexOutput(vtx_pos, vtx_pos); + \\ } + ; + const vs_module = app.device.createShaderModuleWGSL("skybox vertex shader", vs); + defer vs_module.release(); + + const vertex = gpu.VertexState{ + .module = vs_module, + .entry_point = "main", + }; + + const fs = + \\ @group(0) @binding(0) + \\ var<uniform> uniform_data: UniformData; + \\ struct UniformData { + \\ inv_view: mat4x4<f32>, + \\ inv_proj: mat4x4<f32>, + \\ }; + \\ + \\ struct VertexOutput { + \\ @builtin(position) clip_position: vec4<f32>, + \\ @location(0) position: vec4<f32>, + \\ }; + \\ + \\ @fragment fn main(in: VertexOutput) -> @location(0) vec4<f32> { + \\ let inv_cam = uniform_data.inv_view * uniform_data.inv_proj; + \\ let pos_world = inv_cam * in.position; + \\ let dir = normalize(pos_world.xyz / pos_world.w); + \\ return vec4<f32>((dir + 1) * 0.5, 1.0); + \\ } + ; + const fs_module = app.device.createShaderModuleWGSL("skybox fragment shader", fs); + defer fs_module.release(); + + const color_target = gpu.ColorTargetState{ + .format = app.swap_chain.format, + .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(SkyBoxRenderPipeline.UniformData), + .usage = .{ .uniform = true, .copy_dst = true }, + .mapped_at_creation = .false, + }; + const uniform_buffer = app.device.createBuffer(&buffer_descriptor); + + const bind_group_layout = SkyBoxRenderPipeline.bindGroupLayout(app.device); + const bind_group_descriptor = gpu.BindGroup.Descriptor.init(.{ + .layout = bind_group_layout, + .entries = &.{ + gpu.BindGroup.Entry.buffer(0, uniform_buffer, 0, @sizeOf(SkyBoxRenderPipeline.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 = "skybox render pipeline", + .fragment = &fragment, + .layout = pipeline_layout, + .depth_stencil = &.{ + .format = .depth24_plus, + .depth_write_enabled = .true, + .depth_compare = .less_equal, + }, + .vertex = vertex, + .multisample = .{}, + .primitive = .{}, + }; + + return .{ + .gpu_pipeline = app.device.createRenderPipeline(&pipeline_descriptor), + .uniform_buffer = uniform_buffer, + .bind_group = bind_group, + }; + } + + fn bindGroupLayout(device: *gpu.Device) *gpu.BindGroupLayout { + const descriptor = gpu.BindGroupLayout.Descriptor.init(.{ + .entries = &.{ + gpu.BindGroupLayout.Entry.buffer(0, .{ + .vertex = false, + .fragment = true, + }, .uniform, false, 0), + }, + }); + return device.createBindGroupLayout(&descriptor); + } + + pub fn frame(ptr: *anyopaque, pass: *gpu.RenderPassEncoder) void { + const self: *SkyBoxRenderPipeline = @ptrCast(@alignCast(ptr)); + pass.setPipeline(self.gpu_pipeline); + pass.setBindGroup(0, self.bind_group, null); + pass.draw(3, 1, 0, 0); + } + + pub fn pipeline(self: *SkyBoxRenderPipeline) RenderPipeline { + return .{ + .ptr = self, + .frameFn = frame, + }; + } +};