initial commit-moved from vulkan_guide
This commit is contained in:
83
src/scene/camera.cpp
Normal file
83
src/scene/camera.cpp
Normal 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
31
src/scene/camera.h
Normal 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
600
src/scene/vk_loader.cpp
Normal 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
72
src/scene/vk_loader.h
Normal 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
187
src/scene/vk_scene.cpp
Normal 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
87
src/scene/vk_scene.h
Normal 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;
|
||||
};
|
||||
Reference in New Issue
Block a user