We got a skybox

This commit is contained in:
2026-01-21 06:05:35 +01:00
parent b9878f2a06
commit a5b26f3bdd
27 changed files with 843 additions and 42 deletions

View File

@@ -25,8 +25,10 @@ set(SRC_FILES
"src/Graphics/Resources/GPUImage.cpp"
"src/Graphics/Resources/NBuffer.cpp"
"src/Graphics/Resources/Cubemap.cpp"
"src/Graphics/Pipelines/MeshPipeline.cpp"
"src/Graphics/Pipelines/SkyboxPipeline.cpp"
"src/Input/InputManager.cpp"

View File

@@ -0,0 +1,38 @@
#version 450
#extension GL_GOOGLE_include_directive : require
#include "bindless.glsl"
layout(location = 0) in vec3 localPos;
// Input equirectangular HDR texture
layout(location = 0) out vec4 outColor;
const float PI = 3.14159265359;
layout(push_constant) uniform PushConstants {
mat4 view;
mat4 proj;
uint skyboxId;
} pcs;
vec2 sampleSphericalMap(vec3 v) {
// Convert direction to spherical coordinates
vec2 uv = vec2(atan(v.z, v.x), asin(v.y));
uv /= vec2(2.0 * PI, PI);
uv += 0.5;
return uv;
}
void main() {
// Normalize direction vector
vec3 dir = normalize(localPos);
// Sample from equirectangular texture
vec2 uv = sampleSphericalMap(dir);
vec4 color = sampleTexture2DNearest(pcs.skyboxId, uv);
outColor = color;
}

View File

@@ -0,0 +1,42 @@
#version 450
// Hardcoded cube vertices (36 vertices for 12 triangles)
const vec3 positions[36] = vec3[](
// Front face
vec3(-1.0, -1.0, 1.0), vec3( 1.0, -1.0, 1.0), vec3( 1.0, 1.0, 1.0),
vec3( 1.0, 1.0, 1.0), vec3(-1.0, 1.0, 1.0), vec3(-1.0, -1.0, 1.0),
// Back face
vec3(-1.0, -1.0, -1.0), vec3(-1.0, 1.0, -1.0), vec3( 1.0, 1.0, -1.0),
vec3( 1.0, 1.0, -1.0), vec3( 1.0, -1.0, -1.0), vec3(-1.0, -1.0, -1.0),
// Top face
vec3(-1.0, 1.0, -1.0), vec3(-1.0, 1.0, 1.0), vec3( 1.0, 1.0, 1.0),
vec3( 1.0, 1.0, 1.0), vec3( 1.0, 1.0, -1.0), vec3(-1.0, 1.0, -1.0),
// Bottom face
vec3(-1.0, -1.0, -1.0), vec3( 1.0, -1.0, -1.0), vec3( 1.0, -1.0, 1.0),
vec3( 1.0, -1.0, 1.0), vec3(-1.0, -1.0, 1.0), vec3(-1.0, -1.0, -1.0),
// Right face
vec3( 1.0, -1.0, -1.0), vec3( 1.0, 1.0, -1.0), vec3( 1.0, 1.0, 1.0),
vec3( 1.0, 1.0, 1.0), vec3( 1.0, -1.0, 1.0), vec3( 1.0, -1.0, -1.0),
// Left face
vec3(-1.0, -1.0, -1.0), vec3(-1.0, -1.0, 1.0), vec3(-1.0, 1.0, 1.0),
vec3(-1.0, 1.0, 1.0), vec3(-1.0, 1.0, -1.0), vec3(-1.0, -1.0, -1.0)
);
// Hardcoded UVs aren't needed for cube map rendering
layout(location = 0) out vec3 localPos;
// Push constants for view and projection matrices
layout(push_constant) uniform PushConstants {
mat4 view;
mat4 proj;
uint skyboxId;
} pc;
void main() {
// Get hardcoded vertex position
vec3 pos = positions[gl_VertexIndex];
localPos = pos; // Pass to fragment shader
// Apply view and projection matrices
gl_Position = pc.proj * pc.view * vec4(pos, 1.0);
}

View File

@@ -0,0 +1,10 @@
#version 460
layout (location = 0) out vec2 outUV;
void main()
{
outUV = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
// gl_Position = vec4(outUV * 2.0f + -1.0f, 0.0f, 1.0f);
gl_Position = vec4(outUV * 2.0 - 1.0, 1.0, 1.0);
}

View File

@@ -18,7 +18,7 @@ void main()
{
MaterialData material = pcs.sceneData.materials.data[pcs.materialID];
vec4 diffuse = sampleTexture2DLinear(material.diffuseTex, inUV);
vec4 diffuse = sampleTexture2DLinear(material.diffuseTex, inUV) * material.baseColor;
outFragColor = diffuse;

View File

@@ -0,0 +1,35 @@
#version 450
#extension GL_GOOGLE_include_directive : require
#extension GL_EXT_nonuniform_qualifier : enable
#include "bindless.glsl"
layout(location = 0) in vec2 uv; // from fullscreen triangle: 0..2 range
layout(location = 0) out vec4 outColor;
layout(push_constant) uniform SkyboxPC {
mat4 invViewProj; // inverse(Proj * View) (your current setup)
vec4 cameraPos; // xyz = camera world position
uint skyboxTextureId; // index into textureCubes[]
} pcs;
void main()
{
// Fullscreen-triangle trick gives uv in [0..2]. Convert to [0..1].
vec2 uv01 = uv * 0.5;
// Build an NDC point on the far plane.
// Vulkan NDC is x,y in [-1..1], z in [0..1]. Using z=1 means "far".
vec4 ndc = vec4(uv01 * 2.0 - 1.0, 1.0, 1.0);
// Unproject to world space
vec4 world = pcs.invViewProj * ndc;
vec3 worldPos = world.xyz / world.w;
// Direction from camera through this pixel
vec3 dir = normalize(worldPos - pcs.cameraPos.xyz);
// Sample cubemap directly
outColor = sampleTextureCubeLinear(pcs.skyboxTextureId, dir);
// outColor = sampleTextureCubeNearest(pcs.skyboxTextureId, dir);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -21,6 +21,7 @@ public:
void Randomize(uint32_t seed);
void Update() override;
void Start() override;
// optional setters
void SetRadius(float r) { m_Radius = r; }
@@ -37,12 +38,18 @@ private:
float m_OrbitSpeed = 1.0f; // rad/sec
float m_OrbitAngle = 0.0f; // current angle
float m_OrbitPhase = 0.0f; // starting offset
float m_GrowPhase = 0.0f;
glm::vec3 m_U{1,0,0}; // orbit basis axis 1
glm::vec3 m_V{0,0,1}; // orbit basis axis 2
glm::vec3 m_BaseScale{1.0f};
float m_GrowSpeed = 1.0f; // rad/sec
// self spin
glm::vec3 m_SpinAxis{0,1,0};
float m_SpinSpeed = 2.0f; // rad/sec
MaterialID m_MaterialID{0};
};
#endif //ORBITANDSPIN_H

View File

@@ -46,6 +46,7 @@ public:
VkCommandBuffer beginFrame();
[[nodiscard]] bool needsSwapchainRecreate() const { return swapchain.isDirty(); }
VulkanImmediateExecutor& GetImmediateExecuter();
struct EndFrameProps {
@@ -126,6 +127,19 @@ private:
ImageCache imageCache;
static uint32_t BytesPerTexel(VkFormat fmt) {
switch (fmt) {
case VK_FORMAT_R8_UNORM: return 1;
case VK_FORMAT_R8G8B8A8_UNORM: return 4;
case VK_FORMAT_B8G8R8A8_SRGB: return 4;
case VK_FORMAT_R16G16B16A16_SFLOAT: return 8;
case VK_FORMAT_R32G32B32A32_SFLOAT: return 16;
case VK_FORMAT_R8G8B8A8_SRGB: return 4;
default:
throw std::runtime_error("BytesPerTexel: unsupported format");
}
}
};

View File

@@ -39,6 +39,16 @@ public:
void setErrorImageId(ImageID id) { errorImageId = id; }
static uint32_t BytesPerTexel(VkFormat fmt) {
switch (fmt) {
case VK_FORMAT_R32G32B32A32_SFLOAT: return 16;
case VK_FORMAT_R16G16B16A16_SFLOAT: return 8;
case VK_FORMAT_R8G8B8A8_UNORM: return 4;
// add formats you use
default: throw std::runtime_error("BytesPerTexel: unsupported format");
}
}
private:
std::vector<GPUImage> images;
GfxDevice& gfxDevice;

View File

@@ -26,6 +26,10 @@ public:
const GPUBuffer& getMaterialDataBuffer() const { return materialDataBuffer; }
VkDeviceAddress getMaterialDataBufferAddress() const { return materialDataBuffer.address; }
Material& getMaterialMutable(MaterialID id);
void updateMaterialGPU(GfxDevice& gfxDevice, MaterialID id);
private:
std::vector<Material> materials;

View File

@@ -0,0 +1,43 @@
#ifndef SKYBOXPIPELINE_H
#define SKYBOXPIPELINE_H
#include <memory>
#include <vulkan/vulkan.h>
#include <glm/mat4x4.hpp>
#include <glm/vec4.hpp>
#include <destrum/Graphics/ids.h>
#include <destrum/Graphics/Pipeline.h>
#include <destrum/Graphics/Camera.h>
class SkyboxPipeline final {
public:
SkyboxPipeline();
~SkyboxPipeline();
void init(
GfxDevice& gfxDevice,
VkFormat drawImageFormat,
VkFormat depthImageFormat
);
void cleanup(VkDevice device);
void draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera& camera);
void setSkyboxImage(const ImageID skyboxId);
private:
VkPipelineLayout pipelineLayout;
std::unique_ptr<Pipeline> pipeline;
ImageID skyboxTextureId{NULL_IMAGE_ID};
struct SkyboxPushConstants {
glm::mat4 invViewProj;
glm::vec4 cameraPos;
std::uint32_t skyboxTextureId;
};
};
#endif //SKYBOXPIPELINE_H

View File

@@ -13,6 +13,8 @@
#include <memory>
#include "Pipelines/SkyboxPipeline.h"
class GameRenderer {
public:
struct SceneData {
@@ -39,6 +41,12 @@ public:
createDrawImage(gfxDevice, newSize, false);
}
Material& getMaterialMutable(MaterialID id);
void updateMaterialGPU(MaterialID id);
void setSkyboxTexture(ImageID skyboxImageId);
private:
void createDrawImage(GfxDevice& gfxDevice, const glm::ivec2& drawImageSize, bool firstCreate);
@@ -79,6 +87,7 @@ private:
std::unique_ptr<MeshPipeline> meshPipeline;
std::unique_ptr<SkyboxPipeline> skyboxPipeline;
};
#endif //RENDERER_H

View File

@@ -0,0 +1,66 @@
#ifndef CUBEMAP_H
#define CUBEMAP_H
#include <array>
#include <filesystem>
#include <glm/glm.hpp>
#include <glm/ext/matrix_transform.hpp>
#include <vulkan/vulkan.h>
#include <destrum/Graphics/ids.h>
#include "destrum/Graphics/Pipeline.h"
class CubeMap {
public:
explicit CubeMap();
~CubeMap();
void LoadCubeMap(const std::filesystem::path &directoryPath);
void RenderToCubemap(ImageID inputImage, VkImage outputImage, std::array<VkImageView, 6> faceViews, uint32_t size);
void InitCubemapPipeline(const std::string& vertPath, const std::string& fragPath);
void CreateCubeMap();
ImageID GetCubeMapImageID();
private:
const std::array<glm::mat4, 6> viewMatrices = {
// POSITIVE_X
glm::lookAt(glm::vec3(0.0f), glm::vec3(1.0f, 0.0f, 0.0f), glm::vec3(0.0f, -1.0f, 0.0f)),
// NEGATIVE_X
glm::lookAt(glm::vec3(0.0f), glm::vec3(-1.0f, 0.0f, 0.0f), glm::vec3(0.0f, -1.0f, 0.0f)),
// POSITIVE_Y
glm::lookAt(glm::vec3(0.0f), glm::vec3(0.0f, 1.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)),
// NEGATIVE_Y
glm::lookAt(glm::vec3(0.0f), glm::vec3(0.0f, -1.0f, 0.0f), glm::vec3(0.0f, 0.0f, -1.0f)),
// POSITIVE_Z
glm::lookAt(glm::vec3(0.0f), glm::vec3(0.0f, 0.0f, 1.0f), glm::vec3(0.0f, -1.0f, 0.0f)),
// NEGATIVE_Z
glm::lookAt(glm::vec3(0.0f), glm::vec3(0.0f, 0.0f, -1.0f), glm::vec3(0.0f, -1.0f, 0.0f))
};
struct alignas(16) PC {
glm::mat4 viewMtx; // 64
glm::mat4 projMtx; // 64
std::uint32_t inputImageId; // 4
};
glm::mat4 m_projection{};
ImageID m_hdrImage{};
uint32_t m_cubeMapSize = 1024; // Default size for cube map
VkImageView m_skyboxView;
VkPipelineLayout m_cubemapPipelineLayout = VK_NULL_HANDLE;
std::unique_ptr<Pipeline> m_cubemapPipeline;
std::string m_cubemapVert;
std::string m_cubemapFrag;
ImageID m_cubemapImageID;
};
#endif //CUBEMAP_H

View File

@@ -0,0 +1,40 @@
#ifndef GAMESTATE_H
#define GAMESTATE_H
#include <cassert>
#include <destrum/Singleton.h>
class GfxDevice;
class GameRenderer;
class GameState final: public Singleton<GameState> {
public:
friend class Singleton<GameState>;
void SetGfxDevice(GfxDevice* device) { m_gfxDevice = device; }
GfxDevice& Gfx() {
assert(m_gfxDevice && "GfxDevice not registered yet!");
return *m_gfxDevice;
}
const GfxDevice& Gfx() const {
assert(m_gfxDevice && "GfxDevice not registered yet!");
return *m_gfxDevice;
}
void SetRenderer(GameRenderer* renderer) { m_renderer = renderer; }
GameRenderer& Renderer() {
assert(m_renderer && "Renderer not registered yet!");
return *m_renderer;
}
const GameRenderer& Renderer() const {
assert(m_renderer && "Renderer not registered yet!");
return *m_renderer;
}
private:
GfxDevice* m_gfxDevice{nullptr};
GameRenderer* m_renderer{nullptr};
};
#endif //GAMESTATE_H

View File

@@ -4,7 +4,10 @@
#include <random>
#include <cmath>
#include "destrum/ObjectModel/GameObject.h"
#include "destrum/ObjectModel/Transform.h"
#include "destrum/Components/MeshRendererComponent.h"
#include "destrum/Util/GameState.h"
static glm::vec3 RandomUnitVector(std::mt19937& rng)
{
@@ -48,11 +51,6 @@ void OrbitAndSpin::BuildOrbitBasis()
void OrbitAndSpin::Update()
{
// If your engine provides dt via a global/time service, use that instead.
// Since your Spinner takes dt indirectly, I'm assuming Component::Update()
// is called once per frame and you can access dt somewhere globally.
//
// If you CAN pass dt into Update, change signature to Update(float dt).
float dt = 1.0f / 60.0f;
// orbit
@@ -60,10 +58,37 @@ void OrbitAndSpin::Update()
float a = m_OrbitAngle + m_OrbitPhase;
glm::vec3 offset = (m_U * std::cos(a) + m_V * std::sin(a)) * m_Radius;
// IMPORTANT: if SetWorldPosition resets TRS in your engine, this will wipe scale unless you set it again after.
GetTransform().SetWorldPosition(m_Center + offset);
// self spin (local rotation)
// spin
glm::quat dq = glm::angleAxis(m_SpinSpeed * dt, glm::normalize(m_SpinAxis));
auto current = GetTransform().GetLocalRotation(); // adapt to your API
auto current = GetTransform().GetLocalRotation();
GetTransform().SetLocalRotation(glm::normalize(dq * current));
// grow (always positive)
m_GrowPhase += m_GrowSpeed * dt;
float t = 0.5f * (std::sin(m_GrowPhase) + 1.0f); // 0..1
float s = 1.50 + t * 0.70f; // 0.05..0.15 (pick what you want)
GetTransform().SetLocalScale(glm::vec3(std::sin(m_GrowPhase)));
// material color
// auto& mat = GameState::GetInstance().Renderer().getMaterialMutable(m_MaterialID);
// mat.baseColor = glm::vec3(
// 0.5f + 0.5f * std::sin(m_OrbitAngle * 2.0f),
// 0.5f + 0.5f * std::sin(m_OrbitAngle * 3.0f + 2.0f),
// 0.5f + 0.5f * std::sin(m_OrbitAngle * 4.0f + 4.0f)
// );
// GameState::GetInstance().Renderer().updateMaterialGPU(m_MaterialID);
}
void OrbitAndSpin::Start() {
auto meshComp = this->GetGameObject()->GetComponent<MeshRendererComponent>();
m_MaterialID = meshComp->GetMaterialID();
m_BaseScale = GetTransform().GetLocalScale(); // <-- important
}

View File

@@ -70,8 +70,8 @@ void Camera::Update(float deltaTime) {
if (input.IsKeyDown(SDL_SCANCODE_S)) move -= m_forward;
if (input.IsKeyDown(SDL_SCANCODE_D)) move += m_right;
if (input.IsKeyDown(SDL_SCANCODE_A)) move -= m_right;
if (input.IsKeyDown(SDL_SCANCODE_Q)) move += glm::vec3(0, 1, 0); // Absolute Up
if (input.IsKeyDown(SDL_SCANCODE_E)) move -= glm::vec3(0, 1, 0); // Absolute Down
if (input.IsKeyDown(SDL_SCANCODE_E)) move += glm::vec3(0, 1, 0); // Absolute Up
if (input.IsKeyDown(SDL_SCANCODE_Q)) move -= glm::vec3(0, 1, 0); // Absolute Down
if (glm::length2(move) > 0.0f) {
m_position += glm::normalize(move) * (moveSpeed * deltaTime);

View File

@@ -15,6 +15,7 @@
#include <destrum/Graphics/Init.h>
#include "destrum/Graphics/imageLoader.h"
#include "destrum/Util/GameState.h"
#include "spdlog/spdlog.h"
GfxDevice::GfxDevice(): imageCache(*this) {
@@ -141,7 +142,7 @@ void GfxDevice::init(SDL_Window* window, const std::string& appName, bool vSync)
// "white texture",
// &pixel);
// }
GameState::GetInstance().SetGfxDevice(this);
}
void GfxDevice::recreateSwapchain(int width, int height) {
@@ -164,6 +165,10 @@ VkCommandBuffer GfxDevice::beginFrame() {
return cmd;
}
VulkanImmediateExecutor& GfxDevice::GetImmediateExecuter() {
return executor;
}
void GfxDevice::endFrame(VkCommandBuffer cmd, const GPUImage& drawImage, const EndFrameProps& props) {
// get swapchain image
const auto [swapchainImage, swapchainImageIndex] = swapchain.acquireNextImage(getCurrentFrameIndex());
@@ -426,7 +431,7 @@ GPUImage GfxDevice::createImageRaw(
if (createInfo.isCubemap) {
assert(createInfo.numLayers % 6 == 0);
assert(!createInfo.mipMap);
// assert(!createInfo.mipMap);
assert((createInfo.flags & VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT) != 0);
}
@@ -503,16 +508,14 @@ GPUImage GfxDevice::createImageRaw(
void GfxDevice::uploadImageData(const GPUImage& image, void* pixelData, std::uint32_t layer) const
{
int numChannels = 4;
if (image.format == VK_FORMAT_R8_UNORM) {
// FIXME: support more types
numChannels = 1;
}
const auto dataSize =
image.extent.depth * image.extent.width * image.extent.height * numChannels;
VkDeviceSize dataSize =
VkDeviceSize(image.extent.depth) *
image.extent.width *
image.extent.height *
BytesPerTexel(image.format);
const auto uploadBuffer = createBuffer(dataSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
memcpy(uploadBuffer.info.pMappedData, pixelData, dataSize);
auto uploadBuffer = createBuffer(dataSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
memcpy(uploadBuffer.info.pMappedData, pixelData, size_t(dataSize));
executor.immediateSubmit([&](VkCommandBuffer cmd) {
assert(

View File

@@ -75,3 +75,33 @@ MaterialID MaterialCache::getPlaceholderMaterialId() const
assert(placeholderMaterialId != NULL_MATERIAL_ID && "MaterialCache::init not called");
return placeholderMaterialId;
}
Material& MaterialCache::getMaterialMutable(MaterialID id) {
assert(id < materials.size());
return materials.at(id);
}
void MaterialCache::updateMaterialGPU(GfxDevice& gfxDevice, MaterialID id)
{
assert(id < materials.size());
assert(materialDataBuffer.info.pMappedData && "materialDataBuffer must be mapped");
const auto getTextureOrElse = [](ImageID imageId, ImageID placeholder) {
return imageId != NULL_IMAGE_ID ? imageId : placeholder;
};
Material& material = materials[id];
MaterialData* data = reinterpret_cast<MaterialData*>(materialDataBuffer.info.pMappedData);
const ImageID whiteTextureID = gfxDevice.getWhiteTextureID();
data[id] = MaterialData{
.baseColor = glm::vec4(material.baseColor, 1.0f),
.metalRoughnessEmissive = glm::vec4(material.metallicFactor, material.roughnessFactor, material.emissiveFactor, 0.f),
.diffuseTex = getTextureOrElse(material.diffuseTexture, whiteTextureID),
.normalTex = whiteTextureID, // if you have this field
.metallicRoughnessTex = whiteTextureID,
.emissiveTex = whiteTextureID,
};
}

View File

@@ -1,7 +1,7 @@
#include <destrum/Graphics/Pipelines/MeshPipeline.h>
#include <destrum/FS/AssetFS.h>
MeshPipeline::MeshPipeline() {
MeshPipeline::MeshPipeline(): m_pipelineLayout{nullptr} {
}
MeshPipeline::~MeshPipeline() {

View File

@@ -0,0 +1,90 @@
#include <destrum/Graphics/Pipelines/SkyboxPipeline.h>
#include <destrum/FS/AssetFS.h>
#include "spdlog/spdlog.h"
SkyboxPipeline::SkyboxPipeline(): pipelineLayout{nullptr} {
}
SkyboxPipeline::~SkyboxPipeline() {
}
void SkyboxPipeline::init(GfxDevice& gfxDevice, VkFormat drawImageFormat, VkFormat depthImageFormat) {
const auto vertexShader = AssetFS::GetInstance().GetCookedPathForFile("engine://shaders/fullscreen_triangle.vert");
const auto fragShader = AssetFS::GetInstance().GetCookedPathForFile("engine://shaders/skybox.frag");
constexpr auto bufferRange = VkPushConstantRange{
.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT,
.offset = 0,
.size = sizeof(SkyboxPushConstants),
};
constexpr auto pushConstantRanges = std::array{bufferRange};
const auto layouts = std::array{gfxDevice.getBindlessDescSetLayout()};
VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pipelineLayoutInfo.setLayoutCount = static_cast<uint32_t>(layouts.size());
pipelineLayoutInfo.pSetLayouts = layouts.data();
pipelineLayoutInfo.pushConstantRangeCount = static_cast<uint32_t>(pushConstantRanges.size());
pipelineLayoutInfo.pPushConstantRanges = pushConstantRanges.data();
if (vkCreatePipelineLayout(gfxDevice.getDevice().device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) {
throw std::runtime_error("Could not make pipleine layout");
}
PipelineConfigInfo pipelineConfig{};
Pipeline::DefaultPipelineConfigInfo(pipelineConfig);
pipelineConfig.name = "skybox pipeline";
pipelineConfig.pipelineLayout = pipelineLayout;
pipelineConfig.vertexAttributeDescriptions = {};
pipelineConfig.vertexBindingDescriptions = {};
pipelineConfig.colorAttachments = { drawImageFormat };
pipelineConfig.depthAttachment = depthImageFormat;
pipelineConfig.rasterizationInfo.cullMode = VK_CULL_MODE_NONE;
pipelineConfig.depthStencilInfo.depthTestEnable = VK_TRUE;
pipelineConfig.depthStencilInfo.depthWriteEnable = VK_FALSE;
pipelineConfig.depthStencilInfo.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL;
pipelineConfig.rasterizationInfo.cullMode = VK_CULL_MODE_NONE;
pipeline = std::make_unique<Pipeline>(
gfxDevice,
vertexShader.string(),
fragShader.string(),
pipelineConfig
);
}
void SkyboxPipeline::cleanup(VkDevice device) {
pipeline.reset();
vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
}
void SkyboxPipeline::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera& camera) {
if (skyboxTextureId == NULL_IMAGE_ID) {
return;
}
pipeline->bind(cmd);
gfxDevice.bindBindlessDescSet(cmd, pipelineLayout);
const auto pcs = SkyboxPushConstants{
.invViewProj = glm::inverse(camera.GetViewProjectionMatrix()),
.cameraPos = glm::vec4{camera.GetPosition(), 1.f},
.skyboxTextureId = static_cast<std::uint32_t>(skyboxTextureId),
};
vkCmdPushConstants(cmd, pipelineLayout, VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(SkyboxPushConstants), &pcs);
vkCmdDraw(cmd, 3, 1, 0, 0);
}
void SkyboxPipeline::setSkyboxImage(const ImageID skyboxId) {
skyboxTextureId = skyboxId;
}

View File

@@ -2,6 +2,7 @@
#include <destrum/Graphics/Util.h>
#include "destrum/Util/GameState.h"
#include "spdlog/spdlog.h"
GameRenderer::GameRenderer(MeshCache& meshCache, MaterialCache& matCache): meshCache{meshCache}, materialCache{matCache} {
@@ -19,6 +20,11 @@ void GameRenderer::init(GfxDevice& gfxDevice, const glm::ivec2& drawImageSize) {
meshPipeline = std::make_unique<MeshPipeline>();
meshPipeline->init(gfxDevice, drawImageFormat, depthImageFormat);
skyboxPipeline = std::make_unique<SkyboxPipeline>();
skyboxPipeline->init(gfxDevice, drawImageFormat, depthImageFormat);
GameState::GetInstance().SetRenderer(this);
}
void GameRenderer::beginDrawing(GfxDevice& gfxDevice) {
@@ -80,6 +86,9 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera&
meshDrawCommands,
sortedMeshDrawCommands);
skyboxPipeline->draw(cmd, gfxDevice, camera);
vkCmdEndRendering(cmd);
// vkutil::cmdEndLabel(cmd);
@@ -106,6 +115,21 @@ const GPUImage& GameRenderer::getDrawImage(const GfxDevice& gfx_device) const {
return gfx_device.getImage(drawImageId);
}
Material& GameRenderer::getMaterialMutable(MaterialID id) {
assert(id != NULL_MATERIAL_ID);
return materialCache.getMaterialMutable(id);
}
void GameRenderer::updateMaterialGPU(MaterialID id) {
assert(id != NULL_MATERIAL_ID);
materialCache.updateMaterialGPU(GameState::GetInstance().Gfx(), id);
}
void GameRenderer::setSkyboxTexture(ImageID skyboxImageId) {
spdlog::debug("Set skybox texture to image id {}", skyboxImageId);
skyboxPipeline->setSkyboxImage(skyboxImageId);
}
void GameRenderer::createDrawImage(GfxDevice& gfxDevice,
const glm::ivec2& drawImageSize,
bool firstCreate)

View File

@@ -0,0 +1,286 @@
#include <destrum/Graphics/Resources/Cubemap.h>
#include "destrum/FS/AssetFS.h"
#include "destrum/Graphics/GfxDevice.h"
#include "destrum/Graphics/Pipeline.h"
#include "destrum/Util/GameState.h"
#include <destrum/Graphics/Util.h>
#include "glm/ext/matrix_clip_space.hpp"
#include "spdlog/spdlog.h"
CubeMap::CubeMap() {
m_projection = glm::perspective(glm::radians(90.0f), 1.0f, 0.1f, 10.0f);;
}
CubeMap::~CubeMap() {
auto& gfx = GameState::GetInstance().Gfx();
VkDevice device = gfx.getDevice();
if (m_skyboxView) {
vkDestroyImageView(device, m_skyboxView, nullptr);
m_skyboxView = VK_NULL_HANDLE;
}
if (m_cubemapPipelineLayout) {
vkDestroyPipelineLayout(device, m_cubemapPipelineLayout, nullptr);
m_cubemapPipelineLayout = VK_NULL_HANDLE;
}
}
void CubeMap::LoadCubeMap(const std::filesystem::path& directoryPath) {
m_hdrImage = GameState::GetInstance().Gfx().loadImageFromFile(directoryPath, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, false);
}
void CubeMap::RenderToCubemap(ImageID inputImage,
VkImage outputImage,
std::array<VkImageView, 6> faceViews,
uint32_t size)
{
// Ensure pipeline exists
if (!m_cubemapPipeline || m_cubemapPipelineLayout == VK_NULL_HANDLE) {
throw std::runtime_error("Cubemap pipeline not initialized. Call InitCubemapPipeline first.");
}
auto& gfx = GameState::GetInstance().Gfx();
gfx.GetImmediateExecuter().immediateSubmit([&](VkCommandBuffer cmd) {
VkImageMemoryBarrier barrier{ VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER };
barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
barrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.image = outputImage;
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
barrier.subresourceRange.baseMipLevel = 0;
barrier.subresourceRange.levelCount = 1;
barrier.subresourceRange.baseArrayLayer = 0;
barrier.subresourceRange.layerCount = 6;
barrier.srcAccessMask = 0;
barrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
vkCmdPipelineBarrier(
cmd,
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
0,
0, nullptr,
0, nullptr,
1, &barrier
);
VkViewport viewport{};
viewport.x = 0.0f;
viewport.y = 0.0f;
viewport.width = static_cast<float>(size);
viewport.height = static_cast<float>(size);
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;
VkRect2D scissor{};
scissor.offset = {0, 0};
scissor.extent = {size, size};
for (uint32_t face = 0; face < 6; ++face) {
PC pc{};
pc.viewMtx = viewMatrices[face];
pc.projMtx = m_projection;
pc.inputImageId = m_hdrImage;
VkRenderingAttachmentInfoKHR colorAttachment{ VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO_KHR };
colorAttachment.imageView = faceViews[face];
colorAttachment.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
colorAttachment.clearValue.color = {0.f, 0.f, 0.f, 1.f};
VkRenderingInfoKHR renderingInfo{ VK_STRUCTURE_TYPE_RENDERING_INFO_KHR };
renderingInfo.renderArea.offset = {0, 0};
renderingInfo.renderArea.extent = {size, size};
renderingInfo.layerCount = 1;
renderingInfo.colorAttachmentCount = 1;
renderingInfo.pColorAttachments = &colorAttachment;
vkCmdBeginRendering(cmd, &renderingInfo);
vkCmdSetViewport(cmd, 0, 1, &viewport);
vkCmdSetScissor(cmd, 0, 1, &scissor);
m_cubemapPipeline->bind(cmd);
gfx.bindBindlessDescSet(cmd, m_cubemapPipelineLayout);
vkCmdPushConstants(
cmd,
m_cubemapPipelineLayout,
VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
0,
sizeof(PC),
&pc
);
vkCmdDraw(cmd, 36, 1, 0, 0);
vkCmdEndRendering(cmd);
}
// Transition to shader read
barrier.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
barrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
vkCmdPipelineBarrier(
cmd,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
0,
0, nullptr,
0, nullptr,
1, &barrier
);
});
}
void CubeMap::CreateCubeMap() {
const uint32_t mipLevels = static_cast<uint32_t>(std::floor(std::log2(m_cubeMapSize))) + 1;
VkImageCreateInfo imageInfo{};
imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
imageInfo.imageType = VK_IMAGE_TYPE_2D;
imageInfo.extent.height = m_cubeMapSize;
imageInfo.extent.width = m_cubeMapSize;
imageInfo.extent.depth = 1;
imageInfo.mipLevels = mipLevels;
imageInfo.arrayLayers = 6; // 6 faces for cubemap
imageInfo.format = VK_FORMAT_R32G32B32A32_SFLOAT;
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
imageInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
imageInfo.flags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT; // Create a cubemap
VmaAllocationCreateInfo allocInfo{};
allocInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
auto& device = GameState::GetInstance().Gfx();
GPUImage cubeMapID = device.createImageRaw({
.format = VK_FORMAT_R32G32B32A32_SFLOAT,
.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT,
.flags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT,
.extent =
VkExtent3D{
.width = m_cubeMapSize,
.height = m_cubeMapSize,
.depth = 1,
},
.numLayers = 6,
.mipMap = true,
.isCubemap = true
});
// if (vmaCreateImage(device.getAllocator(), &imageInfo, &allocInfo, &cubeMapID.image, &cubeMapID.allocation, nullptr) != VK_SUCCESS) {
// throw std::runtime_error("Failed to create image with VMA!");
// }
std::array<VkImageView, 6> faceViews{};
for (uint32_t face = 0; face < 6; ++face) {
VkImageViewCreateInfo viewInfo{};
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
viewInfo.image = cubeMapID.image;
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
viewInfo.format = VK_FORMAT_R32G32B32A32_SFLOAT;
viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
viewInfo.subresourceRange.baseMipLevel = 0;
viewInfo.subresourceRange.levelCount = 1;
viewInfo.subresourceRange.baseArrayLayer = face;
viewInfo.subresourceRange.layerCount = 1;
if (vkCreateImageView(device.getDevice(), &viewInfo, nullptr, &faceViews[face]) != VK_SUCCESS) {
throw std::runtime_error("Failed to create image view!");
}
}
VkImageViewCreateInfo viewInfo{};
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
viewInfo.image = cubeMapID.image;
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_CUBE;
viewInfo.format = VK_FORMAT_R32G32B32A32_SFLOAT;
viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
viewInfo.subresourceRange.baseMipLevel = 0;
viewInfo.subresourceRange.levelCount = 1;
viewInfo.subresourceRange.baseArrayLayer = 0;
viewInfo.subresourceRange.layerCount = 6;
if (vkCreateImageView(device.getDevice(), &viewInfo, nullptr, &m_skyboxView) != VK_SUCCESS) {
throw std::runtime_error("Failed to create image view!");
}
const auto vertPath = AssetFS::GetInstance().GetCookedPathForFile("engine://shaders/cubemap.vert");
const auto fragPath = AssetFS::GetInstance().GetCookedPathForFile("engine://shaders/cubemap.frag");
spdlog::info("hdriImage id = {}", m_hdrImage);
RenderToCubemap(m_hdrImage, cubeMapID.image, faceViews, m_cubeMapSize);
m_cubemapImageID = GameState::GetInstance().Gfx().addImageToCache(cubeMapID);
for (auto v : faceViews) {
vkDestroyImageView(device.getDevice(), v, nullptr);
}
}
ImageID CubeMap::GetCubeMapImageID() {
return m_cubemapImageID;
}
void CubeMap::InitCubemapPipeline(const std::string& vertPath, const std::string& fragPath)
{
auto& gfx = GameState::GetInstance().Gfx();
VkDevice device = gfx.getDevice();
if (m_cubemapPipeline) return; // already created
// Save paths if you want
m_cubemapVert = vertPath;
m_cubemapFrag = fragPath;
VkPushConstantRange pushConstantRange{};
pushConstantRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
pushConstantRange.offset = 0;
pushConstantRange.size = sizeof(PC);
const auto layouts = std::array{ gfx.getBindlessDescSetLayout() };
VkPipelineLayoutCreateInfo pipelineLayoutInfo{ VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO };
pipelineLayoutInfo.setLayoutCount = static_cast<uint32_t>(layouts.size());
pipelineLayoutInfo.pSetLayouts = layouts.data();
pipelineLayoutInfo.pushConstantRangeCount = 1;
pipelineLayoutInfo.pPushConstantRanges = &pushConstantRange;
if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &m_cubemapPipelineLayout) != VK_SUCCESS) {
throw std::runtime_error("Failed to create cubemap pipeline layout!");
}
PipelineConfigInfo pipelineConfig{};
Pipeline::DefaultPipelineConfigInfo(pipelineConfig);
pipelineConfig.vertexAttributeDescriptions = {};
pipelineConfig.vertexBindingDescriptions = {};
pipelineConfig.pipelineLayout = m_cubemapPipelineLayout;
pipelineConfig.colorAttachments = { VK_FORMAT_R32G32B32A32_SFLOAT }; // must match cubemap image view format
pipelineConfig.depthAttachment = VK_FORMAT_UNDEFINED;
pipelineConfig.depthStencilInfo.depthTestEnable = VK_FALSE;
pipelineConfig.depthStencilInfo.depthWriteEnable = VK_FALSE;
pipelineConfig.rasterizationInfo.cullMode = VK_CULL_MODE_NONE;
m_cubemapPipeline = std::make_unique<Pipeline>(
gfx,
vertPath,
fragPath,
pipelineConfig
);
}