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:
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,
+ };
+ }
+};