commit 3f164f4e03c5760ada8c0169014b5aeec9b23e09
parent d0f22beff84f999692e3194da21e16634ea73a4a
Author: Christian Ermann <christianermann@gmail.com>
Date: Fri, 17 May 2024 17:10:28 -0400
Add cube map, finish skybox render pipeline
Diffstat:
11 files changed, 156 insertions(+), 4 deletions(-)
diff --git a/build.zig b/build.zig
@@ -52,6 +52,13 @@ pub fn build(b: *std.Build) void {
try gpu.link(gpu_dep.builder, exe, &exe.root_module, .{});
}
+ if (b.lazyDependency("zigimg", .{
+ .target = target,
+ .optimize = optimize,
+ })) |img_dep| {
+ exe.root_module.addImport("zigimg", img_dep.module("zigimg"));
+ }
+
// This declares intent for the executable to be installed into the
// standard location when the user invokes the "install" step (the default
// step when running `zig build`).
diff --git a/build.zig.zon b/build.zig.zon
@@ -25,6 +25,11 @@
.hash = "1220fe2e555ca66741539bc0f97769b2513c5e609c968d27eb8997f577a1d195f048",
.lazy = true,
},
+ .zigimg = .{
+ .url = "https://github.com/zigimg/zigimg/archive/637974e2d31dcdbc33f1e9cc8ffb2e46abd2e215.tar.gz",
+ .hash = "122012026c3a65ff1d4acba3b3fe80785f7cee9c6b4cdaff7ed0fbf23b0a6c803989",
+ .lazy = true,
+ },
},
.paths = .{
// This makes *all* files, recursively, included in this package. It is generally
diff --git a/cube/cube-neg-x.png b/cube/cube-neg-x.png
Binary files differ.
diff --git a/cube/cube-neg-y.png b/cube/cube-neg-y.png
Binary files differ.
diff --git a/cube/cube-neg-z.png b/cube/cube-neg-z.png
Binary files differ.
diff --git a/cube/cube-pos-x.png b/cube/cube-pos-x.png
Binary files differ.
diff --git a/cube/cube-pos-y.png b/cube/cube-pos-y.png
Binary files differ.
diff --git a/cube/cube-pos-z.png b/cube/cube-pos-z.png
Binary files differ.
diff --git a/src/cubemap.zig b/src/cubemap.zig
@@ -0,0 +1,113 @@
+const std = @import("std");
+const gpu = @import("mach_gpu");
+const Image = @import("zigimg").Image;
+const App = @import("main.zig").App;
+
+view: *gpu.TextureView,
+sampler: *gpu.Sampler,
+
+const CubeMap = @This();
+
+const CubeMapPaths = struct {
+ pos_x: []const u8,
+ neg_x: []const u8,
+ pos_y: []const u8,
+ neg_y: []const u8,
+ pos_z: []const u8,
+ neg_z: []const u8,
+};
+
+pub fn init(paths: CubeMapPaths, allocator: std.mem.Allocator, app: *App) !CubeMap {
+ var images: [6]Image = undefined;
+ images[0] = try Image.fromFilePath(allocator, paths.pos_x);
+ defer images[0].deinit();
+ images[1] = try Image.fromFilePath(allocator, paths.neg_x);
+ defer images[1].deinit();
+ images[2] = try Image.fromFilePath(allocator, paths.pos_y);
+ defer images[2].deinit();
+ images[3] = try Image.fromFilePath(allocator, paths.neg_y);
+ defer images[3].deinit();
+ images[4] = try Image.fromFilePath(allocator, paths.pos_z);
+ defer images[4].deinit();
+ images[5] = try Image.fromFilePath(allocator, paths.neg_z);
+ defer images[5].deinit();
+
+ const img_size = gpu.Extent3D{
+ .width = @as(u32, @intCast(images[0].width)),
+ .height = @as(u32, @intCast(images[1].height)),
+ };
+
+ const tex_size = gpu.Extent3D{
+ .width = @as(u32, @intCast(images[0].width)),
+ .height = @as(u32, @intCast(images[1].height)),
+ .depth_or_array_layers = 6,
+ };
+
+ const texture = app.device.createTexture(&.{
+ .size = tex_size,
+ .format = .rgba8_unorm,
+ .dimension = .dimension_2d,
+ .usage = .{
+ .texture_binding = true,
+ .copy_dst = true,
+ .render_attachment = false,
+ },
+ });
+
+ const data_layout = gpu.Texture.DataLayout{
+ .bytes_per_row = @as(u32, @intCast(images[0].width * 4)),
+ .rows_per_image = @as(u32, @intCast(images[0].height)),
+ };
+
+ const encoder = app.device.createCommandEncoder(null);
+
+ var staging_buf: [6]*gpu.Buffer = undefined;
+ for (0..6) |i| {
+ staging_buf[i] = app.device.createBuffer(&.{
+ .usage = .{ .copy_src = true, .map_write = true },
+ .size = @as(u64, @intCast(images[0].width)) * @as(u64, @intCast(images[0].height)) * @sizeOf(u32),
+ .mapped_at_creation = .true,
+ });
+ switch (images[i].pixels) {
+ .rgba32 => |pixels| {
+ const staging_map = staging_buf[i].getMappedRange(u32, 0, @as(u64, @intCast(images[0].width)) * @as(u64, @intCast(images[0].height)));
+ @memcpy(staging_map.?, @as([]u32, @ptrCast(@alignCast(pixels))));
+ staging_buf[i].unmap();
+ },
+ else => @panic("unsupported image color format"),
+ }
+
+ const copy_buf = gpu.ImageCopyBuffer{
+ .layout = data_layout,
+ .buffer = staging_buf[i],
+ };
+ const copy_tex = gpu.ImageCopyTexture{
+ .texture = texture,
+ .origin = gpu.Origin3D{ .x = 0, .y = 0, .z = @as(u32, @intCast(i)) },
+ };
+
+ encoder.copyBufferToTexture(©_buf, ©_tex, &img_size);
+ staging_buf[i].release();
+ }
+
+ var command = encoder.finish(null);
+ encoder.release();
+
+ app.queue.submit(&[_]*gpu.CommandBuffer{command});
+ command.release();
+
+ const view = texture.createView(
+ &gpu.TextureView.Descriptor{ .dimension = .dimension_cube },
+ );
+ texture.release();
+
+ const sampler = app.device.createSampler(&.{
+ .mag_filter = .linear,
+ .min_filter = .linear,
+ });
+
+ return .{
+ .view = view,
+ .sampler = sampler,
+ };
+}
diff --git a/src/main.zig b/src/main.zig
@@ -6,6 +6,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 CubeMap = @import("cubemap.zig");
const Camera = @import("camera.zig");
const input = @import("input.zig");
@@ -535,6 +536,16 @@ pub fn main() !void {
try app.init(allocator);
defer app.deinit();
+ const images = .{
+ .pos_x = "cube/cube-pos-x.png",
+ .neg_x = "cube/cube-neg-x.png",
+ .pos_y = "cube/cube-pos-y.png",
+ .neg_y = "cube/cube-neg-y.png",
+ .pos_z = "cube/cube-pos-z.png",
+ .neg_z = "cube/cube-neg-z.png",
+ };
+ const cube_map = try CubeMap.init(images, allocator, &app);
+
var camera = Camera{};
camera.logParams();
@@ -562,7 +573,7 @@ pub fn main() !void {
});
var rp = try MeshRenderPipeline.init(&app, &mesh_buffer, allocator);
- var skybox = SkyBoxRenderPipeline.init(&app);
+ var skybox = SkyBoxRenderPipeline.init(&app, cube_map);
const pipelines = [_]RenderPipeline{ rp.pipeline(), skybox.pipeline() };
diff --git a/src/render_pipeline.zig b/src/render_pipeline.zig
@@ -6,6 +6,7 @@ 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");
pub const RenderPipeline = struct {
ptr: *anyopaque,
@@ -290,7 +291,7 @@ pub const SkyBoxRenderPipeline = struct {
inv_proj: mat4,
};
- pub fn init(app: *App) SkyBoxRenderPipeline {
+ pub fn init(app: *App, cube_map: CubeMap) SkyBoxRenderPipeline {
const vs =
\\ struct VertexOutput {
\\ @builtin(position) clip_position: vec4<f32>,
@@ -325,6 +326,9 @@ pub const SkyBoxRenderPipeline = struct {
\\ inv_proj: mat4x4<f32>,
\\ };
\\
+ \\ @group(0) @binding(1) var cubemap_sampler: sampler;
+ \\ @group(0) @binding(2) var cubemap_texture: texture_cube<f32>;
+ \\
\\ struct VertexOutput {
\\ @builtin(position) clip_position: vec4<f32>,
\\ @location(0) position: vec4<f32>,
@@ -333,8 +337,10 @@ pub const SkyBoxRenderPipeline = struct {
\\ @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);
+ \\ let dir = normalize(pos_world.xyz / pos_world.w) * vec3<f32>(-1, 1, 1);
+ \\ let sample = textureSample(cubemap_texture, cubemap_sampler, dir);
+ \\ //return vec4<f32>((dir + 1) * 0.5, 1.0);
+ \\ return sample;
\\ }
;
const fs_module = app.device.createShaderModuleWGSL("skybox fragment shader", fs);
@@ -363,6 +369,8 @@ pub const SkyBoxRenderPipeline = struct {
.layout = bind_group_layout,
.entries = &.{
gpu.BindGroup.Entry.buffer(0, uniform_buffer, 0, @sizeOf(SkyBoxRenderPipeline.UniformData)),
+ gpu.BindGroup.Entry.sampler(1, cube_map.sampler),
+ gpu.BindGroup.Entry.textureView(2, cube_map.view),
},
});
const bind_group = app.device.createBindGroup(&bind_group_descriptor);
@@ -401,6 +409,14 @@ pub const SkyBoxRenderPipeline = struct {
.vertex = false,
.fragment = true,
}, .uniform, false, 0),
+ gpu.BindGroupLayout.Entry.sampler(1, .{
+ .vertex = false,
+ .fragment = true,
+ }, .filtering),
+ gpu.BindGroupLayout.Entry.texture(2, .{
+ .vertex = false,
+ .fragment = true,
+ }, .float, .dimension_cube, false),
},
});
return device.createBindGroupLayout(&descriptor);