Load and convert skybox to cubemap

This commit is contained in:
2025-12-07 01:23:15 +01:00
parent 9baf42e838
commit df00e3052f
4 changed files with 382 additions and 2 deletions

View File

@@ -2,8 +2,8 @@
layout(set = 0, binding = 0) uniform sampler _EquirectangularSampler;
layout(set = 1, binding = 0) uniform texture2D _EquirectangularTexture;
layout(set = 2, binding = 1, rgba16f) uniform writeonly restrict imageCube _CubemapImage;
layout(set = 0, binding = 1) uniform texture2D _EquirectangularTexture;
layout(set = 0, binding = 2, rgba16f) uniform writeonly restrict imageCube _CubemapImage;
const float INV_PI = 0.31830987;
const float HALF_INV_PI = 0.15915494;

View File

@@ -16,6 +16,7 @@ const Materials = @import("assets/Materials.zig");
const Matrix4x4 = math.Matrix4x4;
const Player = @import("Player.zig");
const Quaternion = math.Quaternion;
const Skybox = @import("engine/Skybox.zig");
const StagingBuffer = @import("engine/StagingBuffer.zig");
const Swapchain = @import("engine/Swapchain.zig");
const Textures = @import("assets/Textures.zig");
@@ -48,6 +49,7 @@ blocks: Blocks,
materials: Materials,
textures: Textures,
chunks: std.AutoHashMapUnmanaged([3]i16, Chunk),
skybox: Skybox,
player: Player,
@@ -616,6 +618,9 @@ pub fn init(allocator: std.mem.Allocator, engine: *Engine, swapchain: *Swapchain
.elements = directional_lights_data,
});
var skybox = try Skybox.load("skybox.hdr", engine, 512, allocator);
errdefer skybox.deinit(engine);
return .{
.allocator = allocator,
.engine = engine,
@@ -642,6 +647,7 @@ pub fn init(allocator: std.mem.Allocator, engine: *Engine, swapchain: *Swapchain
.materials = materials,
.textures = textures,
.chunks = chunks,
.skybox = skybox,
.player = .init(player_position, 0, 0),
};
@@ -658,6 +664,7 @@ pub fn deinit(self: *Game) void {
chunk.deinit(self.engine, self.descriptor_pool);
}
self.chunks.deinit(self.allocator);
self.skybox.deinit(self.engine);
self.global_uniforms.deinit(self.engine);
self.global_uniforms_staging_buffer.deinit(self.engine);

372
src/engine/Skybox.zig Normal file
View File

@@ -0,0 +1,372 @@
const Skybox = @This();
const std = @import("std");
const shaders = @import("../shaders.zig");
const stbi = @import("zstbi");
const vk = @import("vulkan");
const CommandBuffer = @import("CommandBuffer.zig");
const Engine = @import("Engine.zig");
image: vk.Image,
image_view: vk.ImageView,
device_memory: vk.DeviceMemory,
pub fn load(filename: []const u8, engine: *Engine, cube_size: u32, temp_allocator: std.mem.Allocator) !Skybox {
std.log.debug("Loading skybox \"{s}\"...", .{filename});
// --- LOAD IMAGE FROM MEMORY ----------------------------------------------
const cwd = std.fs.cwd();
var dir = try cwd.openDir("assets", .{});
defer dir.close();
const file_buf = try dir.readFileAlloc(temp_allocator, filename, std.math.maxInt(usize));
defer temp_allocator.free(file_buf);
var img = try stbi.Image.loadFromMemory(file_buf, 4);
defer img.deinit();
std.debug.assert(img.num_components == 4);
// --- CREATE EQUIRECTANGULAR IMAGE AND COPY INTO IT -----------------------
const equirect_image = try engine.createImage(.{
.image_type = .@"2d",
.format = .r16g16b16a16_sfloat,
.extent = .{
.width = img.width,
.height = img.height,
.depth = 1,
},
.mip_levels = 1,
.array_layers = 1,
.samples = .{ .@"1_bit" = true },
.tiling = .linear,
.usage = .{
.transfer_dst_bit = true,
.sampled_bit = true,
},
.queue_family_indices = &.{
engine.compute_queue.allocation.family,
},
.initial_layout = .preinitialized,
});
defer engine.destroyImage(equirect_image);
const equirect_memory_requirements = engine.getImageMemoryRequirements(equirect_image);
const equirect_device_memory = try engine.allocate(equirect_memory_requirements, .{
.host_visible_bit = true,
.host_coherent_bit = true,
});
defer engine.freeMemory(equirect_device_memory);
try engine.bindImageMemory(equirect_image, equirect_device_memory, 0);
const equirect_image_view = try engine.createImageView(.{
.image = equirect_image,
.view_type = .@"2d",
.format = .r16g16b16a16_sfloat,
.subresource_range = .{
.aspect_mask = .{ .color_bit = true },
.base_mip_level = 0,
.level_count = 1,
.base_array_layer = 0,
.layer_count = 1,
},
});
defer engine.destroyImageView(equirect_image_view);
const data = try engine.mapMemory(equirect_device_memory, 0, equirect_memory_requirements.size, .{});
@memcpy(data, img.data);
engine.unmapMemory(equirect_device_memory);
// --- CREATE CUBEMAP IMAGE ------------------------------------------------
const cubemap_image = try engine.createImage(.{
.flags = .{ .cube_compatible_bit = true },
.image_type = .@"2d",
.format = .r16g16b16a16_sfloat,
.extent = .{
.width = cube_size,
.height = cube_size,
.depth = 1,
},
.mip_levels = 1,
.array_layers = 6,
.samples = .{ .@"1_bit" = true },
.tiling = .optimal,
.usage = .{
.sampled_bit = true,
.storage_bit = true,
},
.queue_family_indices = &.{
engine.graphics_queue.allocation.family,
engine.compute_queue.allocation.family,
},
.initial_layout = .undefined,
});
errdefer engine.destroyImage(cubemap_image);
const cubemap_memory_requirements = engine.getImageMemoryRequirements(cubemap_image);
const cubemap_device_memory = try engine.allocate(cubemap_memory_requirements, .{ .device_local_bit = true });
errdefer engine.freeMemory(cubemap_device_memory);
try engine.bindImageMemory(cubemap_image, cubemap_device_memory, 0);
const cubemap_image_view = try engine.createImageView(.{
.image = cubemap_image,
.view_type = .cube,
.format = .r16g16b16a16_sfloat,
.subresource_range = .{
.aspect_mask = .{ .color_bit = true },
.base_mip_level = 0,
.level_count = 1,
.base_array_layer = 0,
.layer_count = 6,
},
});
errdefer engine.destroyImageView(cubemap_image_view);
// --- PIPELINE AND DESCRIPTORS --------------------------------------------
const sampler = try engine.createSampler(.{
.mag_filter = .linear,
.min_filter = .linear,
.mipmap_mode = .linear,
.address_mode_u = .repeat,
.address_mode_v = .repeat,
.address_mode_w = .repeat,
.mip_lod_bias = 0,
.anisotropy_enable = .false,
.max_anisotropy = 0,
.compare_enable = .false,
.compare_op = .always,
.min_lod = 0,
.max_lod = vk.LOD_CLAMP_NONE,
.border_color = .float_transparent_black,
.unnormalized_coordinates = .false,
});
defer engine.destroySampler(sampler);
const descriptor_set_layout = try engine.createDescriptorSetLayout(.{
.bindings = &.{
.{
.binding = 0,
.descriptor_type = .sampler,
.descriptor_count = 1,
.stage_flags = .{ .compute_bit = true },
.immutable_samplers = &.{sampler},
},
.{
.binding = 1,
.descriptor_type = .sampled_image,
.descriptor_count = 1,
.stage_flags = .{ .compute_bit = true },
},
.{
.binding = 2,
.descriptor_type = .storage_image,
.descriptor_count = 1,
.stage_flags = .{ .compute_bit = true },
},
},
});
defer engine.destroyDescriptorSetLayout(descriptor_set_layout);
const pipeline_layout = try engine.createPipelineLayout(.{
.set_layouts = &.{descriptor_set_layout},
});
defer engine.destroyPipelineLayout(pipeline_layout);
const compute_shader = try engine.createShaderModule(.{ .code = &shaders.equirect_to_cube_comp_spv });
defer engine.destroyShaderModule(compute_shader);
var pipeline: vk.Pipeline = undefined;
_ = try engine.device.createComputePipelines(.null_handle, 1, &.{
.{
.stage = .{
.stage = .{ .compute_bit = true },
.module = compute_shader,
.p_name = "main",
},
.layout = pipeline_layout,
.base_pipeline_handle = .null_handle,
.base_pipeline_index = -1,
},
}, &engine.vk_allocator.interface, @ptrCast(&pipeline));
defer engine.destroyPipeline(pipeline);
const descriptor_pool = try engine.createDescriptorPool(.{
.max_sets = 1,
.pool_sizes = &.{
.{
.type = .sampler,
.descriptor_count = 1,
},
.{
.type = .sampled_image,
.descriptor_count = 1,
},
.{
.type = .storage_image,
.descriptor_count = 1,
},
},
});
defer engine.destroyDescriptorPool(descriptor_pool);
const descriptor_set = try engine.allocateDescriptorSet(.{
.descriptor_pool = descriptor_pool,
.set_layout = descriptor_set_layout,
});
try engine.updateDescriptorSets(.{
.writes = &.{
.{
.dst_set = descriptor_set,
.dst_binding = 1,
.dst_array_element = 0,
.descriptor_type = .sampled_image,
.descriptor_infos = .{
.image = &.{
.{
.sampler = .null_handle,
.image_view = equirect_image_view,
.image_layout = .shader_read_only_optimal,
},
},
},
},
.{
.dst_set = descriptor_set,
.dst_binding = 2,
.dst_array_element = 0,
.descriptor_type = .storage_image,
.descriptor_infos = .{
.image = &.{
.{
.sampler = .null_handle,
.image_view = cubemap_image_view,
.image_layout = .general,
},
},
},
},
},
});
// --- COMMAND BUFFERS -----------------------------------------------------
// 0. Synchronization primitives
const semaphore = try engine.createSemaphore();
defer engine.destroySemaphore(semaphore);
const fence = try engine.createFence(.{});
defer engine.destroyFence(fence);
// 1. Run compute shader
var compute_command_buffer = try CommandBuffer.init(engine, .compute, .transient);
defer compute_command_buffer.deinit(engine);
try compute_command_buffer.beginCommandBuffer();
compute_command_buffer.pipelineBarrier(.{
.src_stage_mask = .{ .top_of_pipe_bit = true },
.dst_stage_mask = .{ .compute_shader_bit = true },
.image_memory_barriers = &.{
.{
.src_access_mask = .{},
.dst_access_mask = .{ .shader_read_bit = true },
.old_layout = .preinitialized,
.new_layout = .shader_read_only_optimal,
.src_queue_family_index = vk.QUEUE_FAMILY_IGNORED,
.dst_queue_family_index = vk.QUEUE_FAMILY_IGNORED,
.image = equirect_image,
.subresource_range = .{
.aspect_mask = .{ .color_bit = true },
.base_mip_level = 0,
.level_count = 1,
.base_array_layer = 0,
.layer_count = 1,
},
},
.{
.src_access_mask = .{},
.dst_access_mask = .{ .shader_write_bit = true },
.old_layout = .undefined,
.new_layout = .general,
.src_queue_family_index = vk.QUEUE_FAMILY_IGNORED,
.dst_queue_family_index = vk.QUEUE_FAMILY_IGNORED,
.image = cubemap_image,
.subresource_range = .{
.aspect_mask = .{ .color_bit = true },
.base_mip_level = 0,
.level_count = 1,
.base_array_layer = 0,
.layer_count = 6,
},
},
},
});
compute_command_buffer.bindPipeline(.compute, pipeline);
compute_command_buffer.bindDescriptorSet(.compute, pipeline_layout, 0, descriptor_set, null);
compute_command_buffer.proxy.dispatch(@divExact(cube_size, 8), @divExact(cube_size, 8), 6);
try compute_command_buffer.endCommandBuffer();
try compute_command_buffer.submit(engine, .{
.signal_semaphores = &.{semaphore},
});
// 2. Transition cubemap
var transition_command_buffer = try CommandBuffer.init(engine, .graphics, .transient);
defer transition_command_buffer.deinit(engine);
try transition_command_buffer.beginCommandBuffer();
transition_command_buffer.pipelineBarrier(.{
.src_stage_mask = .{ .compute_shader_bit = true },
.dst_stage_mask = .{ .top_of_pipe_bit = true },
.image_memory_barriers = &.{
.{
.src_access_mask = .{ .shader_write_bit = true },
.dst_access_mask = .{ .shader_read_bit = true },
.old_layout = .general,
.new_layout = .shader_read_only_optimal,
.src_queue_family_index = vk.QUEUE_FAMILY_IGNORED,
.dst_queue_family_index = vk.QUEUE_FAMILY_IGNORED,
.image = cubemap_image,
.subresource_range = .{
.aspect_mask = .{ .color_bit = true },
.base_mip_level = 0,
.level_count = 1,
.base_array_layer = 0,
.layer_count = 6,
},
},
},
});
try transition_command_buffer.endCommandBuffer();
try transition_command_buffer.submit(engine, .{
.wait_semaphores = &.{.{ .semaphore = semaphore }},
.fence = fence,
});
// 3. Synchronize
try engine.waitForFence(fence);
return .{
.image = cubemap_image,
.image_view = cubemap_image_view,
.device_memory = cubemap_device_memory,
};
}
pub fn deinit(self: *Skybox, engine: *Engine) void {
engine.destroyImageView(self.image_view);
engine.freeMemory(self.device_memory);
engine.destroyImage(self.image);
}

View File

@@ -102,5 +102,6 @@ pub const ObjectUniforms = extern struct {
}
};
pub const equirect_to_cube_comp_spv align(4) = @embedFile("shaders/equirect_to_cube_comp.spv").*;
pub const main_vert_spv align(4) = @embedFile("shaders/main_vert.spv").*;
pub const main_frag_spv align(4) = @embedFile("shaders/main_frag.spv").*;