initial commit-moved from vulkan_guide

This commit is contained in:
2025-10-10 22:53:54 +09:00
commit 8853429937
2484 changed files with 973414 additions and 0 deletions

83
src/scene/camera.cpp Normal file
View File

@@ -0,0 +1,83 @@
#include "camera.h"
#include <glm/gtx/transform.hpp>
#include <glm/gtx/quaternion.hpp>
#include <SDL2/SDL.h>
#include <algorithm>
#include <cmath>
void Camera::update()
{
glm::mat4 cameraRotation = getRotationMatrix();
position += glm::vec3(cameraRotation * glm::vec4(velocity * moveSpeed, 0.f));
}
void Camera::processSDLEvent(SDL_Event& e)
{
if (e.type == SDL_KEYDOWN) {
// Camera uses -Z forward convention (right-handed)
if (e.key.keysym.sym == SDLK_w) { velocity.z = -1; }
if (e.key.keysym.sym == SDLK_s) { velocity.z = 1; }
if (e.key.keysym.sym == SDLK_a) { velocity.x = -1; }
if (e.key.keysym.sym == SDLK_d) { velocity.x = 1; }
}
if (e.type == SDL_KEYUP) {
if (e.key.keysym.sym == SDLK_w) { velocity.z = 0; }
if (e.key.keysym.sym == SDLK_s) { velocity.z = 0; }
if (e.key.keysym.sym == SDLK_a) { velocity.x = 0; }
if (e.key.keysym.sym == SDLK_d) { velocity.x = 0; }
}
if (e.type == SDL_MOUSEBUTTONDOWN && e.button.button == SDL_BUTTON_RIGHT) {
rmbDown = true;
SDL_SetRelativeMouseMode(SDL_TRUE);
}
if (e.type == SDL_MOUSEBUTTONUP && e.button.button == SDL_BUTTON_RIGHT) {
rmbDown = false;
SDL_SetRelativeMouseMode(SDL_FALSE);
}
if (e.type == SDL_MOUSEMOTION && rmbDown) {
// Mouse right (xrel > 0) turns view right with -Z-forward
yaw += (float)e.motion.xrel * lookSensitivity; // axis = +Y
// Mouse up (yrel < 0) looks up with -Z-forward
pitch -= (float)e.motion.yrel * lookSensitivity;
}
if (e.type == SDL_MOUSEWHEEL) {
// Ctrl modifies FOV, otherwise adjust move speed
const bool ctrl = (SDL_GetModState() & KMOD_CTRL) != 0;
const int steps = e.wheel.y; // positive = wheel up
if (ctrl) {
// Wheel up -> zoom in (smaller FOV)
fovDegrees -= steps * 2.0f;
fovDegrees = std::clamp(fovDegrees, 30.0f, 110.0f);
} else {
// Exponential scale for pleasant feel
float factor = std::pow(1.15f, (float)steps);
moveSpeed = std::clamp(moveSpeed * factor, 0.001f, 5.0f);
}
}
}
glm::mat4 Camera::getViewMatrix()
{
// to create a correct model view, we need to move the world in opposite
// direction to the camera
// so we will create the camera model matrix and invert
glm::mat4 cameraTranslation = glm::translate(glm::mat4(1.f), position);
glm::mat4 cameraRotation = getRotationMatrix();
return glm::inverse(cameraTranslation * cameraRotation);
}
glm::mat4 Camera::getRotationMatrix()
{
// fairly typical FPS style camera. we join the pitch and yaw rotations into
// the final rotation matrix
glm::quat pitchRotation = glm::angleAxis(pitch, glm::vec3 { 1.f, 0.f, 0.f });
// Yaw around +Y keeps mouse-right -> turn-right with -Z-forward
glm::quat yawRotation = glm::angleAxis(yaw, glm::vec3 { 0.f, 1.f, 0.f });
return glm::toMat4(yawRotation) * glm::toMat4(pitchRotation);
}

31
src/scene/camera.h Normal file
View File

@@ -0,0 +1,31 @@
#pragma once
#include <core/vk_types.h>
#include <SDL_events.h>
#include "glm/vec3.hpp"
class Camera {
public:
glm::vec3 velocity;
glm::vec3 position;
// vertical rotation
float pitch { 0.f };
// horizontal rotation
float yaw { 0.f };
// Movement/look tuning
float moveSpeed { 0.03f };
float lookSensitivity { 0.0020f };
bool rmbDown { false };
// Field of view in degrees for projection
float fovDegrees { 70.f };
glm::mat4 getViewMatrix();
glm::mat4 getRotationMatrix();
void processSDLEvent(SDL_Event& e);
void update();
};

600
src/scene/vk_loader.cpp Normal file
View File

@@ -0,0 +1,600 @@
#include "stb_image.h"
#include <iostream>
#include "vk_loader.h"
#include "core/vk_engine.h"
#include "render/vk_materials.h"
#include "core/vk_initializers.h"
#include "core/vk_types.h"
#include <glm/gtx/quaternion.hpp>
#include <fastgltf/glm_element_traits.hpp>
#include <fastgltf/parser.hpp>
#include <fastgltf/tools.hpp>
#include <fastgltf/util.hpp>
#include <optional>
//> loadimg
std::optional<AllocatedImage> load_image(VulkanEngine *engine, fastgltf::Asset &asset, fastgltf::Image &image, bool srgb)
{
AllocatedImage newImage{};
int width, height, nrChannels;
std::visit(
fastgltf::visitor{
[](auto &arg) {
},
[&](fastgltf::sources::URI &filePath) {
assert(filePath.fileByteOffset == 0); // We don't support offsets with stbi.
assert(filePath.uri.isLocalPath()); // We're only capable of loading
// local files.
const std::string path(filePath.uri.path().begin(),
filePath.uri.path().end()); // Thanks C++.
unsigned char *data = stbi_load(path.c_str(), &width, &height, &nrChannels, 4);
if (data)
{
VkExtent3D imagesize;
imagesize.width = width;
imagesize.height = height;
imagesize.depth = 1;
VkFormat fmt = srgb ? VK_FORMAT_R8G8B8A8_SRGB : VK_FORMAT_R8G8B8A8_UNORM;
newImage = engine->_resourceManager->create_image(
data, imagesize, fmt, VK_IMAGE_USAGE_SAMPLED_BIT, false);
stbi_image_free(data);
}
},
[&](fastgltf::sources::Vector &vector) {
unsigned char *data = stbi_load_from_memory(vector.bytes.data(), static_cast<int>(vector.bytes.size()),
&width, &height, &nrChannels, 4);
if (data)
{
VkExtent3D imagesize;
imagesize.width = width;
imagesize.height = height;
imagesize.depth = 1;
VkFormat fmt = srgb ? VK_FORMAT_R8G8B8A8_SRGB : VK_FORMAT_R8G8B8A8_UNORM;
newImage = engine->_resourceManager->create_image(
data, imagesize, fmt, VK_IMAGE_USAGE_SAMPLED_BIT, false);
stbi_image_free(data);
}
},
[&](fastgltf::sources::BufferView &view) {
auto &bufferView = asset.bufferViews[view.bufferViewIndex];
auto &buffer = asset.buffers[bufferView.bufferIndex];
std::visit(fastgltf::visitor{
// We only care about VectorWithMime here, because we
// specify LoadExternalBuffers, meaning all buffers
// are already loaded into a vector.
[](auto &arg) {
},
[&](fastgltf::sources::Vector &vector) {
unsigned char *data = stbi_load_from_memory(
vector.bytes.data() + bufferView.byteOffset,
static_cast<int>(bufferView.byteLength),
&width, &height, &nrChannels, 4);
if (data)
{
VkExtent3D imagesize;
imagesize.width = width;
imagesize.height = height;
imagesize.depth = 1;
VkFormat fmt = srgb ? VK_FORMAT_R8G8B8A8_SRGB : VK_FORMAT_R8G8B8A8_UNORM;
newImage = engine->_resourceManager->create_image(
data, imagesize, fmt, VK_IMAGE_USAGE_SAMPLED_BIT, false);
stbi_image_free(data);
}
}
},
buffer.data);
},
},
image.data);
// if any of the attempts to load the data failed, we havent written the image
// so handle is null
if (newImage.image == VK_NULL_HANDLE)
{
return {};
}
else
{
return newImage;
}
}
//< loadimg
//> filters
VkFilter extract_filter(fastgltf::Filter filter)
{
switch (filter)
{
// nearest samplers
case fastgltf::Filter::Nearest:
case fastgltf::Filter::NearestMipMapNearest:
case fastgltf::Filter::NearestMipMapLinear:
return VK_FILTER_NEAREST;
// linear samplers
case fastgltf::Filter::Linear:
case fastgltf::Filter::LinearMipMapNearest:
case fastgltf::Filter::LinearMipMapLinear:
default:
return VK_FILTER_LINEAR;
}
}
VkSamplerMipmapMode extract_mipmap_mode(fastgltf::Filter filter)
{
switch (filter)
{
case fastgltf::Filter::NearestMipMapNearest:
case fastgltf::Filter::LinearMipMapNearest:
return VK_SAMPLER_MIPMAP_MODE_NEAREST;
case fastgltf::Filter::NearestMipMapLinear:
case fastgltf::Filter::LinearMipMapLinear:
default:
return VK_SAMPLER_MIPMAP_MODE_LINEAR;
}
}
//< filters
std::optional<std::shared_ptr<LoadedGLTF> > loadGltf(VulkanEngine *engine, std::string_view filePath)
{
//> load_1
fmt::print("Loading GLTF: {}", filePath);
std::shared_ptr<LoadedGLTF> scene = std::make_shared<LoadedGLTF>();
scene->creator = engine;
LoadedGLTF &file = *scene.get();
fastgltf::Parser parser{};
constexpr auto gltfOptions = fastgltf::Options::DontRequireValidAssetMember | fastgltf::Options::AllowDouble |
fastgltf::Options::LoadGLBBuffers | fastgltf::Options::LoadExternalBuffers;
// fastgltf::Options::LoadExternalImages;
fastgltf::GltfDataBuffer data;
data.loadFromFile(filePath);
fastgltf::Asset gltf;
std::filesystem::path path = filePath;
auto type = fastgltf::determineGltfFileType(&data);
if (type == fastgltf::GltfType::glTF)
{
auto load = parser.loadGLTF(&data, path.parent_path(), gltfOptions);
if (load)
{
gltf = std::move(load.get());
}
else
{
std::cerr << "Failed to load glTF: " << fastgltf::to_underlying(load.error()) << std::endl;
return {};
}
}
else if (type == fastgltf::GltfType::GLB)
{
auto load = parser.loadBinaryGLTF(&data, path.parent_path(), gltfOptions);
if (load)
{
gltf = std::move(load.get());
}
else
{
std::cerr << "Failed to load glTF: " << fastgltf::to_underlying(load.error()) << std::endl;
return {};
}
}
else
{
std::cerr << "Failed to determine glTF container" << std::endl;
return {};
}
//< load_1
//> load_2
// we can stimate the descriptors we will need accurately
std::vector<DescriptorAllocatorGrowable::PoolSizeRatio> sizes = {
{VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 3},
{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 3},
{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1}
};
file.descriptorPool.init(engine->_deviceManager->device(), gltf.materials.size(), sizes);
//< load_2
//> load_samplers
// load samplers
for (fastgltf::Sampler &sampler: gltf.samplers)
{
VkSamplerCreateInfo sampl = {.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, .pNext = nullptr};
sampl.maxLod = VK_LOD_CLAMP_NONE;
sampl.minLod = 0.0f;
sampl.magFilter = extract_filter(sampler.magFilter.value_or(fastgltf::Filter::Nearest));
sampl.minFilter = extract_filter(sampler.minFilter.value_or(fastgltf::Filter::Nearest));
sampl.mipmapMode = extract_mipmap_mode(sampler.minFilter.value_or(fastgltf::Filter::Nearest));
// Address modes: default to glTF Repeat
auto toAddress = [](fastgltf::Wrap w) -> VkSamplerAddressMode {
switch (w) {
case fastgltf::Wrap::ClampToEdge: return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
case fastgltf::Wrap::MirroredRepeat: return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT;
case fastgltf::Wrap::Repeat:
default: return VK_SAMPLER_ADDRESS_MODE_REPEAT;
}
};
// fastgltf::Sampler::wrapS/wrapT are non-optional and already default to Repeat
sampl.addressModeU = toAddress(sampler.wrapS);
sampl.addressModeV = toAddress(sampler.wrapT);
sampl.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
sampl.unnormalizedCoordinates = VK_FALSE;
VkSampler newSampler;
vkCreateSampler(engine->_deviceManager->device(), &sampl, nullptr, &newSampler);
file.samplers.push_back(newSampler);
}
//< load_samplers
//> load_arrays
// temporal arrays for all the objects to use while creating the GLTF data
std::vector<std::shared_ptr<MeshAsset> > meshes;
std::vector<std::shared_ptr<Node> > nodes;
std::vector<AllocatedImage> images;
std::vector<std::shared_ptr<GLTFMaterial> > materials;
//< load_arrays
// load all textures
for (fastgltf::Image &image: gltf.images)
{
// Default-load GLTF images as linear; baseColor is reloaded as sRGB when bound
std::optional<AllocatedImage> img = load_image(engine, gltf, image, false);
if (img.has_value())
{
images.push_back(*img);
file.images[image.name.c_str()] = *img;
}
else
{
// we failed to load, so lets give the slot a default white texture to not
// completely break loading
images.push_back(engine->_errorCheckerboardImage);
std::cout << "gltf failed to load texture " << image.name << std::endl;
}
}
//> load_buffer
// create buffer to hold the material data
file.materialDataBuffer = engine->_resourceManager->create_buffer(
sizeof(GLTFMetallic_Roughness::MaterialConstants) * gltf.materials.size(),
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VMA_MEMORY_USAGE_CPU_TO_GPU);
int data_index = 0;
GLTFMetallic_Roughness::MaterialConstants *sceneMaterialConstants = (GLTFMetallic_Roughness::MaterialConstants *)
file.materialDataBuffer.info.pMappedData;
//< load_buffer
//
//> load_material
for (fastgltf::Material &mat: gltf.materials)
{
std::shared_ptr<GLTFMaterial> newMat = std::make_shared<GLTFMaterial>();
materials.push_back(newMat);
file.materials[mat.name.c_str()] = newMat;
GLTFMetallic_Roughness::MaterialConstants constants;
constants.colorFactors.x = mat.pbrData.baseColorFactor[0];
constants.colorFactors.y = mat.pbrData.baseColorFactor[1];
constants.colorFactors.z = mat.pbrData.baseColorFactor[2];
constants.colorFactors.w = mat.pbrData.baseColorFactor[3];
constants.metal_rough_factors.x = mat.pbrData.metallicFactor;
constants.metal_rough_factors.y = mat.pbrData.roughnessFactor;
// write material parameters to buffer
sceneMaterialConstants[data_index] = constants;
MaterialPass passType = MaterialPass::MainColor;
if (mat.alphaMode == fastgltf::AlphaMode::Blend)
{
passType = MaterialPass::Transparent;
}
GLTFMetallic_Roughness::MaterialResources materialResources;
// default the material textures
materialResources.colorImage = engine->_whiteImage;
materialResources.colorSampler = engine->_samplerManager->defaultLinear();
materialResources.metalRoughImage = engine->_whiteImage;
materialResources.metalRoughSampler = engine->_samplerManager->defaultLinear();
// set the uniform buffer for the material data
materialResources.dataBuffer = file.materialDataBuffer.buffer;
materialResources.dataBufferOffset = data_index * sizeof(GLTFMetallic_Roughness::MaterialConstants);
// grab textures from gltf file
if (mat.pbrData.baseColorTexture.has_value())
{
const auto &tex = gltf.textures[mat.pbrData.baseColorTexture.value().textureIndex];
size_t imgIndex = tex.imageIndex.value();
// Sampler is optional in glTF; fall back to default if missing
bool hasSampler = tex.samplerIndex.has_value();
size_t sampler = hasSampler ? tex.samplerIndex.value() : SIZE_MAX;
// Reload albedo as sRGB, independent of the global image cache
if (imgIndex < gltf.images.size())
{
auto albedoImg = load_image(engine, gltf, gltf.images[imgIndex], true);
if (albedoImg.has_value())
{
materialResources.colorImage = *albedoImg;
// Track for cleanup using a unique key
std::string key = std::string("albedo_") + mat.name.c_str() + "_" + std::to_string(imgIndex);
file.images[key] = *albedoImg;
}
else
{
materialResources.colorImage = images[imgIndex];
}
}
else
{
materialResources.colorImage = engine->_errorCheckerboardImage;
}
materialResources.colorSampler = hasSampler ? file.samplers[sampler]
: engine->_samplerManager->defaultLinear();
}
// Metallic-Roughness texture
if (mat.pbrData.metallicRoughnessTexture.has_value())
{
const auto &tex = gltf.textures[mat.pbrData.metallicRoughnessTexture.value().textureIndex];
size_t imgIndex = tex.imageIndex.value();
bool hasSampler = tex.samplerIndex.has_value();
size_t sampler = hasSampler ? tex.samplerIndex.value() : SIZE_MAX;
if (imgIndex < images.size())
{
materialResources.metalRoughImage = images[imgIndex];
materialResources.metalRoughSampler = hasSampler ? file.samplers[sampler]
: engine->_samplerManager->defaultLinear();
}
}
// build material
newMat->data = engine->metalRoughMaterial.write_material(engine->_deviceManager->device(), passType, materialResources,
file.descriptorPool);
data_index++;
}
//< load_material
// Flush material constants buffer so GPU sees updated data on non-coherent memory
if (!gltf.materials.empty())
{
VkDeviceSize totalSize = sizeof(GLTFMetallic_Roughness::MaterialConstants) * gltf.materials.size();
vmaFlushAllocation(engine->_deviceManager->allocator(), file.materialDataBuffer.allocation, 0, totalSize);
}
// use the same vectors for all meshes so that the memory doesnt reallocate as
// often
std::vector<uint32_t> indices;
std::vector<Vertex> vertices;
for (fastgltf::Mesh &mesh: gltf.meshes)
{
std::shared_ptr<MeshAsset> newmesh = std::make_shared<MeshAsset>();
meshes.push_back(newmesh);
file.meshes[mesh.name.c_str()] = newmesh;
newmesh->name = mesh.name;
// clear the mesh arrays each mesh, we dont want to merge them by error
indices.clear();
vertices.clear();
for (auto &&p: mesh.primitives)
{
GeoSurface newSurface;
newSurface.startIndex = (uint32_t) indices.size();
newSurface.count = (uint32_t) gltf.accessors[p.indicesAccessor.value()].count;
size_t initial_vtx = vertices.size();
// load indexes
{
fastgltf::Accessor &indexaccessor = gltf.accessors[p.indicesAccessor.value()];
indices.reserve(indices.size() + indexaccessor.count);
fastgltf::iterateAccessor<std::uint32_t>(gltf, indexaccessor,
[&](std::uint32_t idx) {
indices.push_back(idx + initial_vtx);
});
}
// load vertex positions
{
fastgltf::Accessor &posAccessor = gltf.accessors[p.findAttribute("POSITION")->second];
vertices.resize(vertices.size() + posAccessor.count);
fastgltf::iterateAccessorWithIndex<glm::vec3>(gltf, posAccessor,
[&](glm::vec3 v, size_t index) {
Vertex newvtx;
newvtx.position = v;
newvtx.normal = {1, 0, 0};
newvtx.color = glm::vec4{1.f};
newvtx.uv_x = 0;
newvtx.uv_y = 0;
vertices[initial_vtx + index] = newvtx;
});
}
// load vertex normals
auto normals = p.findAttribute("NORMAL");
if (normals != p.attributes.end())
{
fastgltf::iterateAccessorWithIndex<glm::vec3>(gltf, gltf.accessors[(*normals).second],
[&](glm::vec3 v, size_t index) {
vertices[initial_vtx + index].normal = v;
});
}
// load UVs
auto uv = p.findAttribute("TEXCOORD_0");
if (uv != p.attributes.end())
{
fastgltf::iterateAccessorWithIndex<glm::vec2>(gltf, gltf.accessors[(*uv).second],
[&](glm::vec2 v, size_t index) {
vertices[initial_vtx + index].uv_x = v.x;
vertices[initial_vtx + index].uv_y = v.y;
});
}
// load vertex colors
auto colors = p.findAttribute("COLOR_0");
if (colors != p.attributes.end())
{
fastgltf::iterateAccessorWithIndex<glm::vec4>(gltf, gltf.accessors[(*colors).second],
[&](glm::vec4 v, size_t index) {
vertices[initial_vtx + index].color = v;
});
}
if (p.materialIndex.has_value())
{
newSurface.material = materials[p.materialIndex.value()];
}
else
{
newSurface.material = materials[0];
}
glm::vec3 minpos = vertices[initial_vtx].position;
glm::vec3 maxpos = vertices[initial_vtx].position;
for (int i = initial_vtx; i < vertices.size(); i++)
{
minpos = glm::min(minpos, vertices[i].position);
maxpos = glm::max(maxpos, vertices[i].position);
}
newSurface.bounds.origin = (maxpos + minpos) / 2.f;
newSurface.bounds.extents = (maxpos - minpos) / 2.f;
newSurface.bounds.sphereRadius = glm::length(newSurface.bounds.extents);
newmesh->surfaces.push_back(newSurface);
}
newmesh->meshBuffers = engine->_resourceManager->uploadMesh(indices, vertices);
}
//> load_nodes
// load all nodes and their meshes
for (fastgltf::Node &node: gltf.nodes)
{
std::shared_ptr<Node> newNode;
// find if the node has a mesh, and if it does hook it to the mesh pointer and allocate it with the meshnode class
if (node.meshIndex.has_value())
{
newNode = std::make_shared<MeshNode>();
static_cast<MeshNode *>(newNode.get())->mesh = meshes[*node.meshIndex];
}
else
{
newNode = std::make_shared<Node>();
}
nodes.push_back(newNode);
file.nodes[node.name.c_str()];
std::visit(fastgltf::visitor{
[&](fastgltf::Node::TransformMatrix matrix) {
memcpy(&newNode->localTransform, matrix.data(), sizeof(matrix));
},
[&](fastgltf::Node::TRS transform) {
glm::vec3 tl(transform.translation[0], transform.translation[1],
transform.translation[2]);
glm::quat rot(transform.rotation[3], transform.rotation[0], transform.rotation[1],
transform.rotation[2]);
glm::vec3 sc(transform.scale[0], transform.scale[1], transform.scale[2]);
glm::mat4 tm = glm::translate(glm::mat4(1.f), tl);
glm::mat4 rm = glm::toMat4(rot);
glm::mat4 sm = glm::scale(glm::mat4(1.f), sc);
newNode->localTransform = tm * rm * sm;
}
},
node.transform);
}
//< load_nodes
//> load_graph
// run loop again to setup transform hierarchy
for (int i = 0; i < gltf.nodes.size(); i++)
{
fastgltf::Node &node = gltf.nodes[i];
std::shared_ptr<Node> &sceneNode = nodes[i];
for (auto &c: node.children)
{
sceneNode->children.push_back(nodes[c]);
nodes[c]->parent = sceneNode;
}
}
// find the top nodes, with no parents
for (auto &node: nodes)
{
if (node->parent.lock() == nullptr)
{
file.topNodes.push_back(node);
node->refreshTransform(glm::mat4{1.f});
}
}
return scene;
//< load_graph
}
void LoadedGLTF::Draw(const glm::mat4 &topMatrix, DrawContext &ctx)
{
// create renderables from the scenenodes
for (auto &n: topNodes)
{
n->Draw(topMatrix, ctx);
}
}
void LoadedGLTF::clearAll()
{
VkDevice dv = creator->_deviceManager->device();
for (auto &[k, v]: meshes)
{
creator->_resourceManager->destroy_buffer(v->meshBuffers.indexBuffer);
creator->_resourceManager->destroy_buffer(v->meshBuffers.vertexBuffer);
}
for (auto &[k, v]: images)
{
if (v.image == creator->_errorCheckerboardImage.image)
{
// dont destroy the default images
continue;
}
creator->_resourceManager->destroy_image(v);
}
for (auto &sampler: samplers)
{
vkDestroySampler(dv, sampler, nullptr);
}
auto materialBuffer = materialDataBuffer;
auto samplersToDestroy = samplers;
descriptorPool.destroy_pools(dv);
creator->_resourceManager->destroy_buffer(materialBuffer);
}

72
src/scene/vk_loader.h Normal file
View File

@@ -0,0 +1,72 @@
// vulkan_engine.h : Include file for standard system include files,
// or project specific include files.
#pragma once
#include <core/vk_types.h>
#include "core/vk_descriptors.h"
#include <unordered_map>
#include <filesystem>
class VulkanEngine;
struct Bounds
{
glm::vec3 origin;
float sphereRadius;
glm::vec3 extents;
};
struct GLTFMaterial
{
MaterialInstance data;
};
struct GeoSurface
{
uint32_t startIndex;
uint32_t count;
Bounds bounds;
std::shared_ptr<GLTFMaterial> material;
};
struct MeshAsset
{
std::string name;
std::vector<GeoSurface> surfaces;
GPUMeshBuffers meshBuffers;
};
struct LoadedGLTF : public IRenderable
{
// storage for all the data on a given gltf file
std::unordered_map<std::string, std::shared_ptr<MeshAsset> > meshes;
std::unordered_map<std::string, std::shared_ptr<Node> > nodes;
std::unordered_map<std::string, AllocatedImage> images;
std::unordered_map<std::string, std::shared_ptr<GLTFMaterial> > materials;
// nodes that dont have a parent, for iterating through the file in tree order
std::vector<std::shared_ptr<Node> > topNodes;
std::vector<VkSampler> samplers;
DescriptorAllocatorGrowable descriptorPool;
AllocatedBuffer materialDataBuffer;
VulkanEngine *creator;
~LoadedGLTF() { clearAll(); };
void clearMeshes(){ clearAll(); };
virtual void Draw(const glm::mat4 &topMatrix, DrawContext &ctx);
private:
void clearAll();
};
std::optional<std::shared_ptr<LoadedGLTF> > loadGltf(VulkanEngine *engine, std::string_view filePath);

187
src/scene/vk_scene.cpp Normal file
View File

@@ -0,0 +1,187 @@
#include "vk_scene.h"
#include <utility>
#include "vk_swapchain.h"
#include "core/engine_context.h"
#include "glm/gtx/transform.hpp"
#include <glm/gtc/matrix_transform.hpp>
#include "glm/gtx/norm.inl"
void SceneManager::init(EngineContext *context)
{
_context = context;
mainCamera.velocity = glm::vec3(0.f);
mainCamera.position = glm::vec3(30.f, -00.f, 85.f);
mainCamera.pitch = 0;
mainCamera.yaw = 0;
sceneData.ambientColor = glm::vec4(0.1f, 0.1f, 0.1f, 1.0f);
sceneData.sunlightDirection = glm::vec4(-1.0f, -1.0f, -1.0f, 1.0f);
sceneData.sunlightColor = glm::vec4(1.0f, 1.0f, 1.0f, 3.0f);
}
void SceneManager::update_scene()
{
auto start = std::chrono::system_clock::now();
mainDrawContext.OpaqueSurfaces.clear();
mainDrawContext.TransparentSurfaces.clear();
mainCamera.update();
if (loadedScenes.find("structure") != loadedScenes.end())
{
loadedScenes["structure"]->Draw(glm::mat4{1.f}, mainDrawContext);
}
// dynamic GLTF instances
for (const auto &kv: dynamicGLTFInstances)
{
const GLTFInstance &inst = kv.second;
if (inst.scene)
{
inst.scene->Draw(inst.transform, mainDrawContext);
}
}
// Default primitives are added as dynamic instances by the engine.
// dynamic mesh instances
for (const auto &kv: dynamicMeshInstances)
{
const MeshInstance &inst = kv.second;
if (!inst.mesh || inst.mesh->surfaces.empty()) continue;
for (const auto &surf: inst.mesh->surfaces)
{
RenderObject obj{};
obj.indexCount = surf.count;
obj.firstIndex = surf.startIndex;
obj.indexBuffer = inst.mesh->meshBuffers.indexBuffer.buffer;
obj.vertexBuffer = inst.mesh->meshBuffers.vertexBuffer.buffer;
obj.vertexBufferAddress = inst.mesh->meshBuffers.vertexBufferAddress;
obj.material = &surf.material->data;
obj.bounds = surf.bounds;
obj.transform = inst.transform;
if (obj.material->passType == MaterialPass::Transparent)
{
mainDrawContext.TransparentSurfaces.push_back(obj);
}
else
{
mainDrawContext.OpaqueSurfaces.push_back(obj);
}
}
}
glm::mat4 view = mainCamera.getViewMatrix();
// Use reversed infinite-Z projection (right-handed, -Z forward) to avoid far-plane clipping
// on very large scenes. Vulkan clip space is 0..1 (GLM_FORCE_DEPTH_ZERO_TO_ONE) and requires Y flip.
auto makeReversedInfinitePerspective = [](float fovyRadians, float aspect, float zNear) {
// Column-major matrix; indices are [column][row]
float f = 1.0f / tanf(fovyRadians * 0.5f);
glm::mat4 m(0.0f);
m[0][0] = f / aspect;
m[1][1] = f;
m[2][2] = 0.0f;
m[2][3] = -1.0f; // w = -z_eye (right-handed)
m[3][2] = zNear; // maps near -> 1, far -> 0 (reversed-Z)
return m;
};
const float fov = glm::radians(70.f);
const float aspect = (float) _context->getSwapchain()->windowExtent().width /
(float) _context->getSwapchain()->windowExtent().height;
const float nearPlane = 0.1f;
glm::mat4 projection = makeReversedInfinitePerspective(fov, aspect, nearPlane);
// Vulkan NDC has inverted Y.
projection[1][1] *= -1.0f;
sceneData.view = view;
sceneData.proj = projection;
sceneData.viewproj = projection * view;
// Build a simple directional light view-projection (reversed-Z orthographic)
// Centered around the camera for now (non-cascaded, non-stabilized)
{
const glm::vec3 camPos = glm::vec3(glm::inverse(view)[3]);
glm::vec3 L = glm::normalize(-glm::vec3(sceneData.sunlightDirection));
if (glm::length(L) < 1e-5f) L = glm::vec3(0.0f, -1.0f, 0.0f);
const glm::vec3 worldUp(0.0f, 1.0f, 0.0f);
glm::vec3 right = glm::normalize(glm::cross(worldUp, L));
glm::vec3 up = glm::normalize(glm::cross(L, right));
if (glm::length2(right) < 1e-6f)
{
right = glm::vec3(1, 0, 0);
up = glm::normalize(glm::cross(L, right));
}
const float orthoRange = 40.0f; // XY half-extent
const float nearDist = 0.1f;
const float farDist = 200.0f;
const glm::vec3 lightPos = camPos - L * 100.0f;
glm::mat4 viewLight = glm::lookAtRH(lightPos, camPos, up);
// Standard RH ZO ortho with near<far, then explicitly flip Z to reversed-Z
glm::mat4 projLight = glm::orthoRH_ZO(-orthoRange, orthoRange, -orthoRange, orthoRange,
nearDist, farDist);
sceneData.lightViewProj = projLight * viewLight;
}
auto end = std::chrono::system_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
stats.scene_update_time = elapsed.count() / 1000.f;
}
void SceneManager::loadScene(const std::string &name, std::shared_ptr<LoadedGLTF> scene)
{
loadedScenes[name] = std::move(scene);
}
std::shared_ptr<LoadedGLTF> SceneManager::getScene(const std::string &name)
{
auto it = loadedScenes.find(name);
return (it != loadedScenes.end()) ? it->second : nullptr;
}
void SceneManager::cleanup()
{
loadedScenes.clear();
loadedNodes.clear();
}
void SceneManager::addMeshInstance(const std::string &name, std::shared_ptr<MeshAsset> mesh, const glm::mat4 &transform)
{
if (!mesh) return;
dynamicMeshInstances[name] = MeshInstance{std::move(mesh), transform};
}
bool SceneManager::removeMeshInstance(const std::string &name)
{
return dynamicMeshInstances.erase(name) > 0;
}
void SceneManager::clearMeshInstances()
{
dynamicMeshInstances.clear();
}
void SceneManager::addGLTFInstance(const std::string &name, std::shared_ptr<LoadedGLTF> scene,
const glm::mat4 &transform)
{
if (!scene) return;
dynamicGLTFInstances[name] = GLTFInstance{std::move(scene), transform};
}
bool SceneManager::removeGLTFInstance(const std::string &name)
{
return dynamicGLTFInstances.erase(name) > 0;
}
void SceneManager::clearGLTFInstances()
{
dynamicGLTFInstances.clear();
}

87
src/scene/vk_scene.h Normal file
View File

@@ -0,0 +1,87 @@
#pragma once
#include <core/vk_types.h>
#include <scene/camera.h>
#include <unordered_map>
#include <memory>
#include "scene/vk_loader.h"
class EngineContext;
struct RenderObject
{
uint32_t indexCount;
uint32_t firstIndex;
VkBuffer indexBuffer;
VkBuffer vertexBuffer; // for RG buffer tracking (device-address path still used in shader)
MaterialInstance *material;
Bounds bounds;
glm::mat4 transform;
VkDeviceAddress vertexBufferAddress;
};
struct DrawContext
{
std::vector<RenderObject> OpaqueSurfaces;
std::vector<RenderObject> TransparentSurfaces;
};
class SceneManager
{
public:
void init(EngineContext *context);
void cleanup();
void update_scene();
Camera &getMainCamera() { return mainCamera; }
const GPUSceneData &getSceneData() const { return sceneData; }
DrawContext &getMainDrawContext() { return mainDrawContext; }
void loadScene(const std::string &name, std::shared_ptr<LoadedGLTF> scene);
std::shared_ptr<LoadedGLTF> getScene(const std::string &name);
// Dynamic renderables API
struct MeshInstance
{
std::shared_ptr<MeshAsset> mesh;
glm::mat4 transform{1.f};
};
void addMeshInstance(const std::string &name, std::shared_ptr<MeshAsset> mesh,
const glm::mat4 &transform = glm::mat4(1.f));
bool removeMeshInstance(const std::string &name);
void clearMeshInstances();
// GLTF instances (runtime-spawned scenes with transforms)
struct GLTFInstance
{
std::shared_ptr<LoadedGLTF> scene;
glm::mat4 transform{1.f};
};
void addGLTFInstance(const std::string &name, std::shared_ptr<LoadedGLTF> scene,
const glm::mat4 &transform = glm::mat4(1.f));
bool removeGLTFInstance(const std::string &name);
void clearGLTFInstances();
struct SceneStats
{
float scene_update_time = 0.f;
} stats;
private:
EngineContext *_context = nullptr;
Camera mainCamera = {};
GPUSceneData sceneData = {};
DrawContext mainDrawContext;
std::unordered_map<std::string, std::shared_ptr<LoadedGLTF> > loadedScenes;
std::unordered_map<std::string, std::shared_ptr<Node> > loadedNodes;
std::unordered_map<std::string, MeshInstance> dynamicMeshInstances;
std::unordered_map<std::string, GLTFInstance> dynamicGLTFInstances;
};