From 12285321d21cfe7a690587a8aea9d6c804dbdecd Mon Sep 17 00:00:00 2001 From: duck Date: Wed, 14 May 2025 22:13:15 +0500 Subject: [PATCH] Perspective projection --- data/shaders/basic.frag | 11 ++-- data/shaders/basic.vert | 17 ++++-- src/graphics.zig | 103 ++++++++++++++++++++----------------- src/graphics/camera.zig | 24 +++++++++ src/graphics/presets.zig | 24 +++++++++ src/graphics/transform.zig | 53 +++++++++++++++++++ 6 files changed, 179 insertions(+), 53 deletions(-) create mode 100644 src/graphics/camera.zig create mode 100644 src/graphics/presets.zig create mode 100644 src/graphics/transform.zig diff --git a/data/shaders/basic.frag b/data/shaders/basic.frag index 673fe32..c67c5d6 100644 --- a/data/shaders/basic.frag +++ b/data/shaders/basic.frag @@ -1,9 +1,14 @@ #version 450 -layout(location = 0) in vec3 fragCoord; -layout(location = 1) in float vertexIndex; +layout(location = 0) in float vertexIndex; +layout(location = 1) in float depth; layout(location = 0) out vec4 fragColor; void main() { - fragColor = vec4(fragCoord.z, vertexIndex * 0.1, 0.0, 1.0); + fragColor = vec4( + depth, + 0.0, + 0.0, + 1.0 + ); } diff --git a/data/shaders/basic.vert b/data/shaders/basic.vert index 712453c..a79ca11 100644 --- a/data/shaders/basic.vert +++ b/data/shaders/basic.vert @@ -1,11 +1,20 @@ #version 450 layout(location = 0) in vec3 inCoord; -layout(location = 0) out vec3 outCoord; -layout(location = 1) out float vertexIndex; +layout(location = 0) out float vertexIndex; +layout(location = 1) out float depth; + +layout(set = 1, binding = 0) uniform Camera{ + mat4 transform; +} camera; +layout(set = 1, binding = 1) uniform Object{ + mat4 transform; +} object; + void main() { - outCoord = inCoord * 0.5 + 0.5; vertexIndex = gl_VertexIndex; - gl_Position = vec4(inCoord, 1.0); + vec4 outPos = vec4(inCoord, 1.0) * object.transform * camera.transform; + depth = outPos.z / outPos.w; + gl_Position = outPos; } diff --git a/src/graphics.zig b/src/graphics.zig index 7484d05..bbe865f 100644 --- a/src/graphics.zig +++ b/src/graphics.zig @@ -1,5 +1,8 @@ const std = @import("std"); const sdl = @import("sdl"); +const presets = @import("graphics/presets.zig"); +const Transform = @import("graphics/transform.zig"); +const Camera = @import("graphics/camera.zig"); const GameError = @import("game.zig").GameError; window: *sdl.Window, @@ -18,6 +21,23 @@ pipeline: *sdl.GPUGraphicsPipeline, to_resize: ?struct { u32, u32 } = null, +const MESH_BYTES = MESH.len * 4; +const MESH_VERTS = @divExact(MESH.len, 3); +const MESH = [_]f32{ + -1, 0, 4, + 0, 1, 4, + 1, -1, 6, + 1, 0, 4, + 0, -1, 4, + -1, 1, 6, + -1, 1, 6, + 1, 1, 6, + -1, -1, 6, + 1, -1, 6, + -1, -1, 6, + 1, 1, 6, +}; + const Self = @This(); pub fn create() GameError!Self { // Init @@ -28,7 +48,7 @@ pub fn create() GameError!Self { var window: ?*sdl.Window = null; if (!sdl.CreateWindowAndRenderer( - "Spacefarer", + "", 1600, 900, sdl.WINDOW_VULKAN | sdl.WINDOW_RESIZABLE, @@ -55,26 +75,35 @@ pub fn create() GameError!Self { const shader_vert = try loadShader( device, "data/shaders/basic.vert", - sdl.GPU_SHADERSTAGE_VERTEX, + .{ + .entrypoint = "main", + .format = sdl.GPU_SHADERFORMAT_SPIRV, + .stage = sdl.GPU_SHADERSTAGE_VERTEX, + .num_uniform_buffers = 2, + }, ); errdefer sdl.ReleaseGPUShader(device, shader_vert); const shader_frag = try loadShader( device, "data/shaders/basic.frag", - sdl.GPU_SHADERSTAGE_FRAGMENT, + .{ + .entrypoint = "main", + .format = sdl.GPU_SHADERFORMAT_SPIRV, + .stage = sdl.GPU_SHADERSTAGE_FRAGMENT, + }, ); errdefer sdl.ReleaseGPUShader(device, shader_frag); const vertex_buffer = sdl.CreateGPUBuffer(device, &.{ .usage = sdl.GPU_BUFFERUSAGE_VERTEX, - // 6 Vertices * 3 Coordinates * 4 Bytes - .size = 6 * 3 * 4, + // Vertices * 3 Coordinates * 4 Bytes + .size = MESH_BYTES, }) orelse return GameError.SdlError; errdefer sdl.ReleaseGPUBuffer(device, vertex_buffer); const transfer_buffer = sdl.CreateGPUTransferBuffer(device, &.{ - .size = 6 * 3 * 4, + .size = MESH_BYTES, .usage = sdl.GPU_TRANSFERBUFFERUSAGE_UPLOAD, }) orelse return GameError.SdlError; defer sdl.ReleaseGPUTransferBuffer(device, transfer_buffer); @@ -82,16 +111,7 @@ pub fn create() GameError!Self { { // Filling up transfer buffer const mapped_buffer: [*c]f32 = @alignCast(@ptrCast(sdl.MapGPUTransferBuffer(device, transfer_buffer, false) orelse return GameError.SdlError)); defer sdl.UnmapGPUTransferBuffer(device, transfer_buffer); - std.mem.copyForwards(f32, mapped_buffer[0 .. 6 * 3], &[6 * 3]f32{ - // Triangle 1 - -1.0, 0.0, 0.0, - 0.0, 1.0, 0.0, - 1.0, -1.0, 1.0, - // Triangle 2 - 1.0, 0.0, 0.0, - 0.0, -1.0, 0.0, - -1.0, 1.0, 1.0, - }); + std.mem.copyForwards(f32, mapped_buffer[0..MESH.len], &MESH); } { // Copying data over from transfer buffer to vertex buffer @@ -99,7 +119,7 @@ pub fn create() GameError!Self { const copy_pass = sdl.BeginGPUCopyPass(command_buffer) orelse return GameError.SdlError; sdl.UploadToGPUBuffer(copy_pass, &.{ .transfer_buffer = transfer_buffer }, &.{ - .size = 6 * 3 * 4, + .size = MESH_BYTES, .buffer = vertex_buffer, }, false); @@ -141,33 +161,16 @@ pub fn create() GameError!Self { .num_vertex_attributes = 1, }, .primitive_type = sdl.GPU_PRIMITIVETYPE_TRIANGLELIST, - .rasterizer_state = .{ - .cull_mode = sdl.GPU_CULLMODE_BACK, - .fill_mode = sdl.GPU_FILLMODE_FILL, - .front_face = sdl.GPU_FRONTFACE_CLOCKWISE, - }, + .rasterizer_state = presets.RASTERIZER_CULL, .multisample_state = .{ .sample_count = sdl.GPU_SAMPLECOUNT_4, }, - .depth_stencil_state = .{ - .compare_op = sdl.GPU_COMPAREOP_LESS, - .enable_depth_test = true, - .enable_depth_write = true, - }, + .depth_stencil_state = presets.DEPTH_ENABLED, .target_info = .{ .depth_stencil_format = sdl.GPU_TEXTUREFORMAT_D16_UNORM, .color_target_descriptions = &sdl.GPUColorTargetDescription{ .format = target_format, - .blend_state = .{ - .enable_blend = true, - .alpha_blend_op = sdl.GPU_BLENDOP_ADD, - .color_blend_op = sdl.GPU_BLENDOP_ADD, - .color_write_mask = sdl.GPU_COLORCOMPONENT_R | sdl.GPU_COLORCOMPONENT_G | sdl.GPU_COLORCOMPONENT_B | sdl.GPU_COLORCOMPONENT_A, - .src_alpha_blendfactor = sdl.GPU_BLENDFACTOR_ONE, - .src_color_blendfactor = sdl.GPU_BLENDFACTOR_SRC_ALPHA, - .dst_alpha_blendfactor = sdl.GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, - .dst_color_blendfactor = sdl.GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, - }, + .blend_state = presets.BLEND_NORMAL, }, .num_color_targets = 1, .has_depth_stencil_target = true, @@ -245,7 +248,18 @@ pub fn drawDebug(self: *Self) GameError!void { sdl.BindGPUGraphicsPipeline(render_pass, self.pipeline); sdl.BindGPUVertexBuffers(render_pass, 0, &.{ .offset = 0, .buffer = self.vertex_buffer }, 1); - sdl.DrawGPUPrimitives(render_pass, 6, 1, 0, 0); + const transform = Transform{}; + const camera = Camera{ + .transform = Transform{ + .position = .{ 0.0, 0.0, -1.0 }, + }, + .near = 1.0, + .far = 1024.0, + .lens = .{ 0.25 * 16.0 / 9.0, 0.25 }, + }; + sdl.PushGPUVertexUniformData(self.command_buffer, 0, &camera.matrix(), 16 * 4); + sdl.PushGPUVertexUniformData(self.command_buffer, 1, &transform.matrix(), 16 * 4); + sdl.DrawGPUPrimitives(render_pass, MESH_VERTS, 1, 0, 0); sdl.EndGPURenderPass(render_pass); } @@ -255,20 +269,17 @@ pub fn endDraw(self: *Self) GameError!void { if (!sdl.SubmitGPUCommandBuffer(self.command_buffer)) return GameError.SdlError; } -fn loadShader(device: *sdl.GPUDevice, path: []const u8, stage: c_uint) GameError!*sdl.GPUShader { +fn loadShader(device: *sdl.GPUDevice, path: []const u8, info: sdl.GPUShaderCreateInfo) GameError!*sdl.GPUShader { const file = std.fs.cwd().openFile(path, .{}) catch return GameError.OSError; defer file.close(); const code = file.readToEndAllocOptions(std.heap.c_allocator, 1024 * 1024 * 1024, null, @alignOf(u8), 0) catch return GameError.OSError; defer std.heap.c_allocator.free(code); - return sdl.CreateGPUShader(device, &.{ - .code = code, - .code_size = code.len, - .entrypoint = "main", - .format = sdl.GPU_SHADERFORMAT_SPIRV, - .stage = stage, - }) orelse return GameError.SdlError; + var updated_info = info; + updated_info.code = code; + updated_info.code_size = code.len; + return sdl.CreateGPUShader(device, &updated_info) orelse return GameError.SdlError; } fn createDepthTexture(device: *sdl.GPUDevice, width: u32, height: u32) GameError!*sdl.GPUTexture { diff --git a/src/graphics/camera.zig b/src/graphics/camera.zig new file mode 100644 index 0000000..7ca22ed --- /dev/null +++ b/src/graphics/camera.zig @@ -0,0 +1,24 @@ +const std = @import("std"); +const sdl = @import("sdl"); +const Transform = @import("transform.zig"); +const Camera = @This(); + +transform: Transform, +lens: @Vector(2, f32), +near: f32, +far: f32, + +pub fn matrix(camera: Camera) @Vector(16, f32) { + const xx = 1.0 / camera.lens[0]; + const yy = 1.0 / camera.lens[1]; + const fnmod = 1.0 / (camera.far - camera.near); + const zz = camera.far * fnmod; + const wz = -camera.near * camera.far * fnmod; + const projection = @Vector(16, f32){ + xx, 0, 0, 0, + 0, yy, 0, 0, + 0, 0, zz, wz, + 0, 0, 1, 0, + }; + return Transform.multiplyMatrix(projection, camera.transform.inverse()); +} diff --git a/src/graphics/presets.zig b/src/graphics/presets.zig new file mode 100644 index 0000000..b2ebb9c --- /dev/null +++ b/src/graphics/presets.zig @@ -0,0 +1,24 @@ +const sdl = @import("sdl"); + +pub const BLEND_NORMAL = sdl.GPUColorTargetBlendState{ + .enable_blend = true, + .alpha_blend_op = sdl.GPU_BLENDOP_ADD, + .color_blend_op = sdl.GPU_BLENDOP_ADD, + .color_write_mask = sdl.GPU_COLORCOMPONENT_R | sdl.GPU_COLORCOMPONENT_G | sdl.GPU_COLORCOMPONENT_B | sdl.GPU_COLORCOMPONENT_A, + .src_alpha_blendfactor = sdl.GPU_BLENDFACTOR_ONE, + .src_color_blendfactor = sdl.GPU_BLENDFACTOR_SRC_ALPHA, + .dst_alpha_blendfactor = sdl.GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, + .dst_color_blendfactor = sdl.GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, +}; + +pub const DEPTH_ENABLED = sdl.GPUDepthStencilState{ + .compare_op = sdl.GPU_COMPAREOP_LESS, + .enable_depth_test = true, + .enable_depth_write = true, +}; + +pub const RASTERIZER_CULL = sdl.GPURasterizerState{ + .cull_mode = sdl.GPU_CULLMODE_BACK, + .fill_mode = sdl.GPU_FILLMODE_FILL, + .front_face = sdl.GPU_FRONTFACE_CLOCKWISE, +}; diff --git a/src/graphics/transform.zig b/src/graphics/transform.zig new file mode 100644 index 0000000..64157aa --- /dev/null +++ b/src/graphics/transform.zig @@ -0,0 +1,53 @@ +const std = @import("std"); +const Transform = @This(); + +// TODO: Rotation + +position: @Vector(3, f32) = @splat(0.0), +rotation: @Vector(3, f32) = @splat(0.0), +scale: @Vector(3, f32) = @splat(1.0), + +pub fn matrix(transform: Transform) @Vector(16, f32) { + return .{ + 1.0, 0.0, 0.0, transform.position[0], + 0.0, 1.0, 0.0, transform.position[1], + 0.0, 0.0, 1.0, transform.position[2], + 0.0, 0.0, 0.0, 1.0, + }; +} + +pub fn inverse(transform: Transform) @Vector(16, f32) { + // TODO: Could we just translate, rotate and scale back instead of relying on matrix math? + return invertMatrix(transform.matrix()); +} + +fn invertMatrix(a: @Vector(16, f32)) @Vector(16, f32) { + const MOD: f32 = 1.0 / 16.0; + const ID = @Vector(16, f32){ + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1, + }; + var p = ID - @as(@Vector(16, f32), @splat(MOD)) * a; + var output = ID + p; + inline for (0..8) |_| { + p = multiplyMatrix(p, p); + output = multiplyMatrix(output, ID + p); + } + return output * @as(@Vector(16, f32), @splat(MOD)); +} + +pub fn multiplyMatrix(a: @Vector(16, f32), b: @Vector(16, f32)) @Vector(16, f32) { + var output: @Vector(16, f32) = [1]f32{0.0} ** 16; + + @setFloatMode(.optimized); + for (0..4) |row| { + for (0..4) |col| { + for (0..4) |i| { + output[row * 4 + col] += a[row * 4 + i] * b[i * 4 + col]; + } + } + } + return output; +}