feat: improve fps controller / add fog and ambient light to the scene

This commit is contained in:
2026-10-16 09:29:59 +02:00
parent 21621001c2
commit 4c9b77c948
105 changed files with 609 additions and 110 deletions
+166 -33
View File
@@ -7,10 +7,12 @@
#include <destrum/Graphics/Managers/LineRenderingManager.h>
#include <destrum/Graphics/Managers/QuadRenderingManager.h>
#include "glm/gtc/constants.hpp"
#include "glm/gtc/quaternion.hpp"
#include "glm/gtx/transform.hpp"
#include "spdlog/spdlog.h"
#include <destrum/Components/Physics/Rigidbody.h>
#include <destrum/Components/Physics/BoxCollider.h>
#include <destrum/Components/Physics/CapsuleCollider.h>
#include <destrum/Scene/Scene.h>
#include <destrum/Serialization/SceneSerializer.h>
@@ -53,6 +55,55 @@ namespace {
const std::filesystem::path path{value};
return path.is_absolute() ? path : exeDir / path;
}
// Box centered on the origin. UVs are in meters, so tiling textures keep the same size on every box
CPUMesh MakeBoxMesh(const glm::vec3& size)
{
struct Face {
glm::vec3 normal;
glm::vec3 tangent;
};
constexpr std::array<Face, 6> faces{{
{{1.0f, 0.0f, 0.0f}, {0.0f, 0.0f, -1.0f}},
{{-1.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 1.0f}},
{{0.0f, 1.0f, 0.0f}, {1.0f, 0.0f, 0.0f}},
{{0.0f, -1.0f, 0.0f}, {1.0f, 0.0f, 0.0f}},
{{0.0f, 0.0f, 1.0f}, {1.0f, 0.0f, 0.0f}},
{{0.0f, 0.0f, -1.0f}, {-1.0f, 0.0f, 0.0f}},
}};
constexpr std::array<glm::vec2, 4> corners{{
{-1.0f, -1.0f}, {1.0f, -1.0f}, {1.0f, 1.0f}, {-1.0f, 1.0f},
}};
CPUMesh mesh{};
mesh.name = "Box";
mesh.minPos = -size * 0.5f;
mesh.maxPos = size * 0.5f;
for (const Face& face : faces) {
const glm::vec3 bitangent = glm::cross(face.normal, face.tangent);
const float width = glm::dot(glm::abs(face.tangent), size);
const float height = glm::dot(glm::abs(bitangent), size);
const auto first = static_cast<std::uint32_t>(mesh.vertices.size());
for (const glm::vec2& corner : corners) {
// v runs against the bitangent so images are upright on the side faces (w = -1 to match)
mesh.vertices.push_back(CPUMesh::Vertex{
.position = (face.normal + face.tangent * corner.x + bitangent * corner.y) * size * 0.5f,
.uv_x = (corner.x + 1.0f) * 0.5f * width,
.normal = face.normal,
.uv_y = (1.0f - corner.y) * 0.5f * height,
.tangent = glm::vec4{face.tangent, -1.0f},
});
}
for (const std::uint32_t index : {0u, 1u, 2u, 0u, 2u, 3u}) {
mesh.indices.push_back(first + index);
}
}
return mesh;
}
}
LightKeeper::LightKeeper() : App()
@@ -88,12 +139,25 @@ void LightKeeper::customInit()
camera.SetRotation(glm::radians(glm::vec2(90.f, 0.f)));
auto& scene = SceneManager::GetInstance().CreateScene("Main");
scene.GetEnvironment().fogDensity = 0.01f;
// Directional lights shine down their local -Z: tilt it 50 degrees down, then turn it around the up axis
const auto sunObj = scene.CreateGameObject("Sun");
sunObj->GetTransform().SetWorldPosition(glm::vec3(0.f, 10.f, 0.f));
sunObj->GetTransform().SetWorldRotation(
glm::angleAxis(glm::radians(35.f), glm::vec3{0.f, 1.f, 0.f}) *
glm::angleAxis(glm::radians(-50.f), glm::vec3{1.f, 0.f, 0.f}));
auto* sun = sunObj->AddComponent<LightComponent>(LightType::Directional);
sun->SetColor(glm::vec3{1.0f, 0.95f, 0.85f});
sun->SetIntensity(3.0f);
sun->SetCastShadows(true);
// scene.Add(testCube);
// const auto skyboxID = AssetFS::GetInstance().GetFullPath("engine://textures/skybox.jpg");
// const auto skyboxID = AssetFS::GetInstance().GetFullPath("engine://textures/mars.jpg");
const auto skyboxID = AssetFS::GetInstance().GetCookedPathForFile("game://starmap_2020_4k.exr");
// const auto skyboxID = AssetFS::GetInstance().GetCookedPathForFile("game://starmap_2020_4k.exr");
const auto skyboxID = AssetFS::GetInstance().GetCookedPathForFile("engine://DJI_0001_stitch.jpg");
//
// const auto skyboxID = AssetFS::GetInstance().GetFullPath("engine://textures/test-skybox.png");
//
@@ -136,11 +200,71 @@ void LightKeeper::customInit()
planeMeshComp->SetMaterialID(planeMaterialID);
planeObj->GetTransform().SetWorldPosition(glm::vec3(0.f, -1.0f, 0.f));
planeObj->GetTransform().SetWorldScale(glm::vec3(10.f, 1.f, 10.f));
planeObj->AddComponent<BoxCollider>(glm::vec3{10.0f, 0.5f, 10.0f});
auto* floorRb = planeObj->AddComponent<Rigidbody>();
floorRb->SetType(RigidbodyType::Static);
scene.GetPhysics().RegisterGameObject(*planeObj);
// Kenney prototype textures, texture_01 is a 1 x 1 meter grid
const auto addPrototypeMaterial = [&](const std::string& color, float roughness)
{
Material material = Material::SimpleColor(glm::vec3{1.0f}, "Prototype" + color);
material.roughnessFactor = roughness;
material.textureFilteringMode = TextureFilteringMode::Anisotropic;
material.diffuseTexture = resources.loadImageFromFile(
gfxDevice, AssetFS::GetInstance().GetFullPath("engine://kenny/" + color + "/texture_01.png"));
return resources.materials().addMaterial(std::move(material));
};
const auto createBox = [&](const std::string& name, const glm::vec3& position, const glm::vec3& size,
MaterialID materialID, RigidbodyType type)
{
const auto box = scene.CreateGameObject(name);
box->GetTransform().SetWorldPosition(position);
const auto boxMeshComp = box->AddComponent<MeshRendererComponent>();
boxMeshComp->SetMeshID(resources.meshes().addMesh(gfxDevice, MakeBoxMesh(size)));
boxMeshComp->SetMaterialID(materialID);
box->AddComponent<BoxCollider>(size * 0.5f);
auto* boxRb = box->AddComponent<Rigidbody>();
boxRb->SetType(type);
boxRb->SetMass(10.0f);
scene.GetPhysics().RegisterGameObject(*box);
return box;
};
// The plane mesh is flat at y = 0, so the floor collider sits right below it
const auto groundObj = scene.CreateGameObject("GroundCollider");
groundObj->GetTransform().SetWorldPosition(glm::vec3(0.f, -1.5f, 0.f));
groundObj->AddComponent<BoxCollider>(glm::vec3{10.0f, 0.5f, 10.0f});
groundObj->AddComponent<Rigidbody>()->SetType(RigidbodyType::Static);
scene.GetPhysics().RegisterGameObject(*groundObj);
// Parkour course, every platform is reachable with a jump from the previous one
const MaterialID platformMaterial = addPrototypeMaterial("Orange", 0.6f);
const MaterialID goalMaterial = addPrototypeMaterial("Green", 0.6f);
const MaterialID wallMaterial = addPrototypeMaterial("Dark", 0.9f);
const MaterialID crateMaterial = addPrototypeMaterial("Purple", 0.35f);
const auto createPlatform = [&](const std::string& name, const glm::vec3& center, float top, const glm::vec3& size,
MaterialID materialID)
{
const float height = top - (-1.0f);
createBox(name, glm::vec3{center.x, top - height * 0.5f, center.z}, glm::vec3{size.x, height, size.z},
materialID, RigidbodyType::Static);
};
createPlatform("Parkour_Step", {0.0f, 0.0f, 4.0f}, -0.5f, {2.0f, 0.0f, 2.0f}, platformMaterial);
createPlatform("Parkour_Block", {0.0f, 0.0f, 7.0f}, 0.4f, {2.0f, 0.0f, 2.0f}, platformMaterial);
createPlatform("Parkour_Pillar1", {3.5f, 0.0f, 8.5f}, 1.2f, {1.5f, 0.0f, 1.5f}, platformMaterial);
createPlatform("Parkour_Pillar2", {6.5f, 0.0f, 7.5f}, 2.0f, {1.2f, 0.0f, 1.2f}, platformMaterial);
createBox("Parkour_Beam", {6.5f, 1.85f, 4.0f}, {0.6f, 0.3f, 5.0f}, platformMaterial, RigidbodyType::Static);
createPlatform("Parkour_Goal", {6.5f, 0.0f, -0.5f}, 2.8f, {2.5f, 0.0f, 2.5f}, goalMaterial);
createBox("Wall", {-5.0f, 0.5f, 0.0f}, {0.5f, 3.0f, 8.0f}, wallMaterial, RigidbodyType::Static);
createBox("LowWall", {-2.5f, -0.6f, -4.0f}, {4.0f, 0.8f, 0.4f}, wallMaterial, RigidbodyType::Static);
for (int i = 0; i < 4; ++i)
{
createBox("Crate" + std::to_string(i),
{-2.5f, -0.6f + 0.81f * static_cast<float>(i), 2.0f},
glm::vec3{0.8f}, crateMaterial, RigidbodyType::Dynamic);
}
const auto helmetObj = scene.CreateGameObject("FlightHelmet");
helmetObj->GetTransform().SetWorldPosition(glm::vec3(2.f, -1.f, 0.f));
@@ -521,12 +645,15 @@ void LightKeeper::customInit()
// orbit->SetPivotPosition(glm::vec3{0.0f, -0.25f, 0.0f});
// }
// auto playerObj = scene.CreateGameObject("Player");
// playerObj->GetTransform().SetWorldPosition(glm::vec3(0.0f, 2.0f, -5.0f));
// auto* fps = playerObj->AddComponent<FirstPersonController>();
// m_Player = playerObj;
// m_FPSController = fps;
// scene.GetPhysics().RegisterGameObject(*playerObj);
auto playerObj = scene.CreateGameObject("Player");
playerObj->GetTransform().SetWorldPosition(glm::vec3(0.0f, 0.0f, -6.0f));
playerObj->AddComponent<CapsuleCollider>(0.4f, 1.8f);
playerObj->AddComponent<Rigidbody>();
auto* fps = playerObj->AddComponent<FirstPersonController>();
fps->SetYaw(glm::half_pi<float>());
m_Player = playerObj;
m_FPSController = fps;
scene.GetPhysics().RegisterGameObject(*playerObj);
const auto texPath = AssetFS::GetInstance().GetFullPath("engine://textures/kobe.png");
texID = resources.loadImageFromFile(gfxDevice, texPath);
@@ -540,16 +667,7 @@ void LightKeeper::customUpdate(float dt)
{
// LightKeeper owns a camera that hides App::camera; update this instance
// after SDL events have been consumed.
if (m_FPSController != nullptr && m_FPSController->IsMouseCaptured())
{
auto& playerTransform = m_Player->GetTransform();
const glm::vec3 playerPos = playerTransform.GetWorldPosition();
camera.SetRotation(m_FPSController->GetYaw(), m_FPSController->GetPitch());
camera.m_position = playerPos + glm::vec3(0.0f, m_FPSController->GetEyeHeight(), 0.0f);
camera.CalculateViewMatrix();
camera.CalculateProjectionMatrix();
}
else if (getSceneCamera() == nullptr)
if (!isPlayerViewActive() && getSceneCamera() == nullptr)
{
camera.Update(dt);
}
@@ -591,6 +709,11 @@ void LightKeeper::customUpdate(float dt)
}
bool LightKeeper::isPlayerViewActive() const
{
return m_FPSController != nullptr && m_FPSController->IsMouseCaptured();
}
CameraComponent* LightKeeper::getSceneCamera() const
{
const auto& activeScenes = SceneManager::GetInstance().GetActiveScenes();
@@ -809,19 +932,32 @@ void LightKeeper::customDraw()
glm::vec2{100.0f, 100.0f}, glm::vec2{200.0f, 200.0f}, 0.0f,
glm::vec4{1.0f}, texID);
CameraComponent* sceneCamera = getSceneCamera();
// The player camera is placed here, after the fixed steps, so it can be interpolated between them
if (isPlayerViewActive())
{
camera.SetRotation(m_FPSController->GetYaw(), m_FPSController->GetPitch());
camera.m_position = m_FPSController->GetEyePosition();
camera.CalculateViewMatrix();
camera.CalculateProjectionMatrix();
}
CameraComponent* sceneCamera = isPlayerViewActive() ? nullptr : getSceneCamera();
Camera& viewCamera = sceneCamera != nullptr ? sceneCamera->GetCamera(camera.GetAspectRatio()) : camera;
const SceneEnvironment& environment = SceneManager::GetInstance().GetCurrentScene().GetEnvironment();
const GameRenderer::SceneData sceneData{
.camera = viewCamera,
.ambientColor = environment.ambientColor,
.ambientIntensity = environment.ambientIntensity,
.fogColor = environment.fogColor,
.fogDensity = environment.fogDensity,
.skyboxRotation = environment.skyboxRotation,
};
const RenderContext ctx{
.renderer = renderer,
.camera = viewCamera,
.sceneData = {
.camera = viewCamera,
.ambientColor = glm::vec3(0.1f),
.ambientIntensity = 0.5f,
.fogColor = glm::vec3(0.5f),
.fogDensity = 0.01f
}
.sceneData = sceneData,
};
SceneManager::GetInstance().Render(ctx);
@@ -829,10 +965,7 @@ void LightKeeper::customDraw()
const auto& drawImage = renderer.getDrawImage();
renderer.draw(
cmd, gfxDevice, viewCamera, GameRenderer::SceneData{
viewCamera, glm::vec3(0.1f), 0.5f, glm::vec3(0.5f), 0.01f
});
renderer.draw(cmd, gfxDevice, viewCamera, sceneData);
gfxDevice.endFrame(
cmd, drawImage, {