387 lines
12 KiB
C++
387 lines
12 KiB
C++
#include "vk_scene.h"
|
|
|
|
#include <utility>
|
|
#include <unordered_set>
|
|
#include <chrono>
|
|
|
|
#include "vk_swapchain.h"
|
|
#include "core/engine_context.h"
|
|
#include "core/config.h"
|
|
#include "glm/gtx/transform.hpp"
|
|
#include <glm/gtc/matrix_transform.hpp>
|
|
#include "glm/gtx/norm.inl"
|
|
#include "glm/gtx/compatibility.hpp"
|
|
#include <algorithm>
|
|
#include <limits>
|
|
#include <cmath>
|
|
#include "core/config.h"
|
|
|
|
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(-0.2f, -1.0f, -0.3f, 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();
|
|
mainDrawContext.nextID = 1;
|
|
|
|
mainCamera.update();
|
|
|
|
// Simple per-frame dt (seconds) for animations
|
|
static auto lastFrameTime = std::chrono::steady_clock::now();
|
|
auto now = std::chrono::steady_clock::now();
|
|
float dt = std::chrono::duration<float>(now - lastFrameTime).count();
|
|
lastFrameTime = now;
|
|
if (dt < 0.f)
|
|
{
|
|
dt = 0.f;
|
|
}
|
|
if (dt > 0.1f)
|
|
{
|
|
dt = 0.1f;
|
|
}
|
|
|
|
// Advance glTF animations once per unique LoadedGLTF
|
|
if (dt > 0.f)
|
|
{
|
|
std::unordered_set<LoadedGLTF *> animatedScenes;
|
|
|
|
auto updateSceneAnim = [&](std::shared_ptr<LoadedGLTF> &scene) {
|
|
if (!scene) return;
|
|
LoadedGLTF *ptr = scene.get();
|
|
if (animatedScenes.insert(ptr).second)
|
|
{
|
|
ptr->updateAnimation(dt);
|
|
}
|
|
};
|
|
|
|
for (auto &[name, scene] : loadedScenes)
|
|
{
|
|
updateSceneAnim(scene);
|
|
}
|
|
for (auto &[name, inst] : dynamicGLTFInstances)
|
|
{
|
|
updateSceneAnim(inst.scene);
|
|
}
|
|
}
|
|
|
|
// Draw all loaded GLTF scenes (static world)
|
|
for (auto &[name, scene] : loadedScenes)
|
|
{
|
|
if (scene)
|
|
{
|
|
scene->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;
|
|
uint32_t surfaceIndex = 0;
|
|
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;
|
|
if (inst.boundsTypeOverride.has_value())
|
|
{
|
|
obj.bounds.type = *inst.boundsTypeOverride;
|
|
}
|
|
obj.transform = inst.transform;
|
|
obj.sourceMesh = inst.mesh.get();
|
|
obj.surfaceIndex = surfaceIndex++;
|
|
obj.objectID = mainDrawContext.nextID++;
|
|
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;
|
|
|
|
// Clipmap shadow setup (directional). Each level i covers a square region
|
|
// around the camera in the light's XY plane with radius R_i = R0 * 2^i.
|
|
// The region center is snapped to the light-space texel grid for stability.
|
|
static const float kAheadBlend[kShadowCascadeCount] = {0.2f, 0.5f, 0.75f, 1.0f};
|
|
{
|
|
const glm::mat4 invView = glm::inverse(view);
|
|
const glm::vec3 camPos = glm::vec3(invView[3]);
|
|
const glm::vec3 camFwd = -glm::vec3(invView[2]);
|
|
|
|
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::cross(L, worldUp);
|
|
if (glm::length2(right) < 1e-6f) right = glm::vec3(1, 0, 0);
|
|
right = glm::normalize(right);
|
|
glm::vec3 up = glm::normalize(glm::cross(right, L));
|
|
|
|
auto level_radius = [](int level) {
|
|
return kShadowClipBaseRadius * powf(2.0f, float(level));
|
|
};
|
|
|
|
sceneData.cascadeSplitsView = glm::vec4(
|
|
level_radius(0), level_radius(1), level_radius(2), level_radius(3));
|
|
|
|
for (int ci = 0; ci < kShadowCascadeCount; ++ci)
|
|
{
|
|
const float radius = level_radius(ci);
|
|
const float cover = radius * kShadowCascadeRadiusScale + kShadowCascadeRadiusMargin;
|
|
|
|
const float ahead = radius * 0.5;
|
|
const float fu = glm::dot(camFwd, right);
|
|
const float fv = glm::dot(camFwd, up);
|
|
|
|
const glm::vec3 aheadXY = right * (fu * ahead) + up * (fv * ahead);
|
|
|
|
const float u = glm::dot(camPos + aheadXY, right) + fu * ahead;
|
|
const float v = glm::dot(camPos + aheadXY, up) + fv * ahead;
|
|
|
|
const float texel = (2.0f * cover) / float(kShadowMapResolution);
|
|
const float uSnapped = floorf(u / texel) * texel;
|
|
const float vSnapped = floorf(v / texel) * texel;
|
|
const float du = uSnapped - u;
|
|
const float dv = vSnapped - v;
|
|
|
|
const glm::vec3 center = camPos + aheadXY * kAheadBlend[ci] + right * du + up * dv;
|
|
|
|
const float pullback = glm::max(kShadowClipPullbackMin, cover * kShadowClipPullbackFactor);
|
|
const glm::vec3 eye = center - L * pullback;
|
|
const glm::mat4 V = glm::lookAtRH(eye, center, up);
|
|
|
|
const float zNear = 0.2f;
|
|
const float zFar = pullback + cover * kShadowClipForwardFactor + kShadowClipZPadding;
|
|
|
|
const glm::mat4 P = glm::orthoRH_ZO(-cover, cover, -cover, cover, zNear, zFar);
|
|
const glm::mat4 lightVP = P * V;
|
|
|
|
sceneData.lightViewProjCascades[ci] = lightVP;
|
|
if (ci == 0)
|
|
{
|
|
sceneData.lightViewProj = lightVP;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Publish shadow/RT settings to SceneData
|
|
if (_context)
|
|
{
|
|
const auto &ss = _context->shadowSettings;
|
|
const uint32_t rtEnabled = (ss.mode != 0) ? 1u : 0u;
|
|
sceneData.rtOptions = glm::uvec4(rtEnabled, ss.hybridRayCascadesMask, ss.mode, 0u);
|
|
sceneData.rtParams = glm::vec4(ss.hybridRayNoLThreshold, 0.0f, 0.0f, 0.0f);
|
|
}
|
|
|
|
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)
|
|
{
|
|
if (scene)
|
|
{
|
|
scene->debugName = name;
|
|
}
|
|
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()
|
|
{
|
|
// Explicitly clear dynamic instances first to drop any extra shared_ptrs
|
|
// that could keep GPU resources alive.
|
|
clearMeshInstances();
|
|
clearGLTFInstances();
|
|
|
|
// Drop our references to GLTF scenes. Their destructors call clearAll()
|
|
// exactly once to release GPU resources.
|
|
loadedScenes.clear();
|
|
loadedNodes.clear();
|
|
}
|
|
|
|
void SceneManager::addMeshInstance(const std::string &name, std::shared_ptr<MeshAsset> mesh,
|
|
const glm::mat4 &transform, std::optional<BoundsType> boundsType)
|
|
{
|
|
if (!mesh) return;
|
|
MeshInstance inst{};
|
|
inst.mesh = std::move(mesh);
|
|
inst.transform = transform;
|
|
inst.boundsTypeOverride = boundsType;
|
|
dynamicMeshInstances[name] = std::move(inst);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
bool SceneManager::setGLTFInstanceTransform(const std::string &name, const glm::mat4 &transform)
|
|
{
|
|
auto it = dynamicGLTFInstances.find(name);
|
|
if (it == dynamicGLTFInstances.end()) return false;
|
|
it->second.transform = transform;
|
|
return true;
|
|
}
|
|
|
|
void SceneManager::clearGLTFInstances()
|
|
{
|
|
dynamicGLTFInstances.clear();
|
|
}
|
|
|
|
bool SceneManager::setSceneAnimation(const std::string &sceneName, int animationIndex, bool resetTime)
|
|
{
|
|
auto it = loadedScenes.find(sceneName);
|
|
if (it == loadedScenes.end() || !it->second)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
it->second->setActiveAnimation(animationIndex, resetTime);
|
|
return true;
|
|
}
|
|
|
|
bool SceneManager::setSceneAnimation(const std::string &sceneName, const std::string &animationName, bool resetTime)
|
|
{
|
|
auto it = loadedScenes.find(sceneName);
|
|
if (it == loadedScenes.end() || !it->second)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
it->second->setActiveAnimation(animationName, resetTime);
|
|
return true;
|
|
}
|
|
|
|
bool SceneManager::setSceneAnimationLoop(const std::string &sceneName, bool loop)
|
|
{
|
|
auto it = loadedScenes.find(sceneName);
|
|
if (it == loadedScenes.end() || !it->second)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
it->second->animationLoop = loop;
|
|
return true;
|
|
}
|
|
|
|
bool SceneManager::setGLTFInstanceAnimation(const std::string &instanceName, int animationIndex, bool resetTime)
|
|
{
|
|
auto it = dynamicGLTFInstances.find(instanceName);
|
|
if (it == dynamicGLTFInstances.end() || !it->second.scene)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
it->second.scene->setActiveAnimation(animationIndex, resetTime);
|
|
return true;
|
|
}
|
|
|
|
bool SceneManager::setGLTFInstanceAnimation(const std::string &instanceName, const std::string &animationName, bool resetTime)
|
|
{
|
|
auto it = dynamicGLTFInstances.find(instanceName);
|
|
if (it == dynamicGLTFInstances.end() || !it->second.scene)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
it->second.scene->setActiveAnimation(animationName, resetTime);
|
|
return true;
|
|
}
|
|
|
|
bool SceneManager::setGLTFInstanceAnimationLoop(const std::string &instanceName, bool loop)
|
|
{
|
|
auto it = dynamicGLTFInstances.find(instanceName);
|
|
if (it == dynamicGLTFInstances.end() || !it->second.scene)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
it->second.scene->animationLoop = loop;
|
|
return true;
|
|
}
|