diff --git a/CHANGELOG.md b/CHANGELOG.md index 90e3d6f6c0..ba621ad3e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Binary Serializer and Deserializer (#1306, **@RiscadoA**). - Type Client and Type Server (#1302, **@RiscadoA**). - Deadzone for input axis (#844, **@kuukitenshi**) +- Generic Camera component to hold projection matrix (#1331, **@mkuritsu**) ### Fixed diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index c17dca0871..0015497893 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -129,6 +129,7 @@ set(CUBOS_ENGINE_SOURCE "src/render/camera/plugin.cpp" "src/render/camera/perspective_camera.cpp" "src/render/camera/draws_to.cpp" + "src/render/camera/camera.cpp" "src/render/voxels/plugin.cpp" "src/render/voxels/load.cpp" "src/render/voxels/grid.cpp" diff --git a/engine/include/cubos/engine/render/camera/camera.hpp b/engine/include/cubos/engine/render/camera/camera.hpp new file mode 100644 index 0000000000..f6c643e195 --- /dev/null +++ b/engine/include/cubos/engine/render/camera/camera.hpp @@ -0,0 +1,28 @@ +/// @file +/// @brief Component @ref cubos::engine::Camera +/// @ingroup render-camera-plugin + +#pragma once + +#include + +#include + +#include + +namespace cubos::engine +{ + /// @brief Generic component to hold the projection matrix of a specific camera (either perspective or orthogonal). + /// @note Added automatically once a specific camera is added (e.g @ref cubos::engine::PerspectiveCamera). + /// @ingroup render-camera-plugin + struct CUBOS_ENGINE_API Camera + { + CUBOS_REFLECT; + + /// @brief Whether the camera is drawing to a target. + bool active{true}; + + /// @brief Projection matrix of the camera. + glm::mat4 projection{}; + }; +} // namespace cubos::engine diff --git a/engine/include/cubos/engine/render/camera/perspective_camera.hpp b/engine/include/cubos/engine/render/camera/perspective_camera.hpp index 9ba3fcbd65..e25701a9bf 100644 --- a/engine/include/cubos/engine/render/camera/perspective_camera.hpp +++ b/engine/include/cubos/engine/render/camera/perspective_camera.hpp @@ -17,9 +17,6 @@ namespace cubos::engine { CUBOS_REFLECT; - /// @brief Whether the camera is drawing to a target. - bool active = true; - /// @brief Vertical field of view in degrees. float fovY = 60.0F; diff --git a/engine/src/gizmos/plugin.cpp b/engine/src/gizmos/plugin.cpp index 99964665a0..80b0c2262b 100644 --- a/engine/src/gizmos/plugin.cpp +++ b/engine/src/gizmos/plugin.cpp @@ -6,8 +6,8 @@ #include #include +#include #include -#include #include #include #include @@ -106,7 +106,7 @@ void cubos::engine::gizmosPlugin(Cubos& cubos) .tagged(drawToRenderPickerTag) .call([](Gizmos& gizmos, GizmosRenderer& gizmosRenderer, const DeltaTime& deltaTime, Query targets, - Query cameras) { + Query cameras) { auto& rd = *gizmosRenderer.renderDevice; auto orthoVP = glm::translate(glm::mat4(1.0F), glm::vec3(-1.0F, -1.0F, 0.0F)) * glm::ortho(-0.5F, 0.5F, -0.5F, 0.5F); @@ -156,11 +156,7 @@ void cubos::engine::gizmosPlugin(Cubos& cubos) static_cast(drawsTo.viewportSize.y * static_cast(target.size.y))); // Prepare camera projection (TODO: fetch this matrix from a Camera component). - auto worldVP = glm::perspective(glm::radians(camera.fovY), - (drawsTo.viewportSize.x * static_cast(target.size.x)) / - (drawsTo.viewportSize.y * static_cast(target.size.y)), - camera.zNear, camera.zFar) * - glm::inverse(localToWorld.mat); + auto worldVP = camera.projection * glm::inverse(localToWorld.mat); // Draw world-space gizmos. rd.setDepthStencilState(gizmosRenderer.doDepthCheckStencilState); @@ -209,11 +205,7 @@ void cubos::engine::gizmosPlugin(Cubos& cubos) static_cast(drawsTo.viewportSize.y * static_cast(target.size.y))); // Prepare camera projection (TODO: fetch this matrix from a Camera component). - auto worldVP = glm::perspective(glm::radians(camera.fovY), - (drawsTo.viewportSize.x * static_cast(target.size.x)) / - (drawsTo.viewportSize.y * static_cast(target.size.y)), - camera.zNear, camera.zFar) * - glm::inverse(localToWorld.mat); + auto worldVP = camera.projection * glm::inverse(localToWorld.mat); // Draw world-space gizmos. rd.setDepthStencilState(gizmosRenderer.doDepthCheckStencilState); diff --git a/engine/src/render/camera/camera.cpp b/engine/src/render/camera/camera.cpp new file mode 100644 index 0000000000..8ac158e688 --- /dev/null +++ b/engine/src/render/camera/camera.cpp @@ -0,0 +1,13 @@ +#include +#include +#include + +#include + +CUBOS_REFLECT_IMPL(cubos::engine::Camera) +{ + return core::ecs::TypeBuilder("cubos::engine::Camera") + .withField("active", &Camera::active) + .withField("projection", &Camera::projection) + .build(); +} diff --git a/engine/src/render/camera/draws_to.cpp b/engine/src/render/camera/draws_to.cpp index 5c55097cfb..59e2b5b8d8 100644 --- a/engine/src/render/camera/draws_to.cpp +++ b/engine/src/render/camera/draws_to.cpp @@ -6,6 +6,7 @@ CUBOS_REFLECT_IMPL(cubos::engine::DrawsTo) { return core::ecs::TypeBuilder("cubos::engine::DrawsTo") + .tree() .withField("viewportOffset", &DrawsTo::viewportOffset) .withField("viewportSize", &DrawsTo::viewportSize) .build(); diff --git a/engine/src/render/camera/perspective_camera.cpp b/engine/src/render/camera/perspective_camera.cpp index 4dc2e06026..0b4d8a8824 100644 --- a/engine/src/render/camera/perspective_camera.cpp +++ b/engine/src/render/camera/perspective_camera.cpp @@ -6,7 +6,6 @@ CUBOS_REFLECT_IMPL(cubos::engine::PerspectiveCamera) { return core::ecs::TypeBuilder("cubos::engine::PerspectiveCamera") - .withField("active", &PerspectiveCamera::active) .withField("fovY", &PerspectiveCamera::fovY) .withField("zNear", &PerspectiveCamera::zNear) .withField("zFar", &PerspectiveCamera::zFar) diff --git a/engine/src/render/camera/plugin.cpp b/engine/src/render/camera/plugin.cpp index c3d6429ce7..1aa4998c06 100644 --- a/engine/src/render/camera/plugin.cpp +++ b/engine/src/render/camera/plugin.cpp @@ -1,10 +1,44 @@ +#include + +#include #include #include #include +#include +#include + +using namespace cubos::engine; void cubos::engine::cameraPlugin(Cubos& cubos) { + cubos.depends(renderTargetPlugin); + cubos.component(); + cubos.component(); cubos.relation(); + + cubos.observer("add Camera on add PerspectiveCamera") + .onAdd() + .without() + .call([](Commands cmds, Query query) { + for (auto [ent] : query) + { + cmds.add(ent, Camera{}); + } + }); + + cubos.system("update Camera projection by PerspectiveCamera") + .call([](Query query) { + for (auto [camera, perspective, drawsTo, target] : query) + { + float aspect = (static_cast(target.size.x) * drawsTo.viewportSize.x) / + (static_cast(target.size.y) * drawsTo.viewportSize.y); + if (aspect > 0) + { + camera.projection = + glm::perspective(glm::radians(perspective.fovY), aspect, perspective.zNear, perspective.zFar); + } + } + }); } diff --git a/engine/src/render/deferred_shading/plugin.cpp b/engine/src/render/deferred_shading/plugin.cpp index 91d59906e6..74b32d2959 100644 --- a/engine/src/render/deferred_shading/plugin.cpp +++ b/engine/src/render/deferred_shading/plugin.cpp @@ -2,8 +2,8 @@ #include #include +#include #include -#include #include #include #include @@ -177,13 +177,13 @@ void cubos::engine::deferredShadingPlugin(Cubos& cubos) Query pointLights, Query> spotLights, Query targets, - Query perspectiveCameras) { + Query cameras) { auto& rd = window->renderDevice(); for (auto [targetEnt, hdr, gBuffer, ssao, deferredShading] : targets) { // Find the cameras that draw to the GBuffer. - for (auto [localToWorld, camera, drawsTo] : perspectiveCameras.pin(1, targetEnt)) + for (auto [localToWorld, camera, drawsTo] : cameras.pin(1, targetEnt)) { if (!camera.active) { @@ -213,11 +213,7 @@ void cubos::engine::deferredShadingPlugin(Cubos& cubos) // Fill the PerScene constant buffer. PerScene perScene{}; perScene.inverseView = localToWorld.mat; - perScene.inverseProjection = - glm::inverse(glm::perspective(glm::radians(camera.fovY), - (float(gBuffer.size.x) * drawsTo.viewportSize.x) / - (float(gBuffer.size.y) * drawsTo.viewportSize.y), - camera.zNear, camera.zFar)); + perScene.inverseProjection = glm::inverse(camera.projection); perScene.ambientLight = glm::vec4(environment.ambient, 1.0F); perScene.skyGradient[0] = glm::vec4(environment.skyGradient[0], 1.0F); diff --git a/engine/src/render/g_buffer_rasterizer/plugin.cpp b/engine/src/render/g_buffer_rasterizer/plugin.cpp index d2f8718d8b..3cac723e6b 100644 --- a/engine/src/render/g_buffer_rasterizer/plugin.cpp +++ b/engine/src/render/g_buffer_rasterizer/plugin.cpp @@ -2,8 +2,8 @@ #include #include +#include #include -#include #include #include #include @@ -142,10 +142,10 @@ void cubos::engine::gBufferRasterizerPlugin(Cubos& cubos) cubos.system("rasterize to GBuffer") .tagged(rasterizeToGBufferTag) - .with() + .with() .related() .call([](State& state, const Window& window, const RenderMeshPool& pool, const RenderPalette& palette, - Assets& assets, Query perspectiveCameras, + Assets& assets, Query cameras, Query targets, Query meshes) { auto& rd = window->renderDevice(); @@ -233,7 +233,7 @@ void cubos::engine::gBufferRasterizerPlugin(Cubos& cubos) } // Find the active cameras for this target. - for (auto [cameraLocalToWorld, camera, drawsTo] : perspectiveCameras.pin(1, ent)) + for (auto [cameraLocalToWorld, camera, drawsTo] : cameras.pin(1, ent)) { // Skip inactive cameras. if (!camera.active) @@ -243,10 +243,7 @@ void cubos::engine::gBufferRasterizerPlugin(Cubos& cubos) // Send the PerScene data to the GPU. auto view = glm::inverse(cameraLocalToWorld.mat); - auto proj = glm::perspective(glm::radians(camera.fovY), - (float(gBuffer.size.x) * drawsTo.viewportSize.x) / - (float(gBuffer.size.y) * drawsTo.viewportSize.y), - camera.zNear, camera.zFar); + auto proj = camera.projection; PerScene perScene{.viewProj = proj * view}; state.perSceneCB->fill(&perScene, sizeof(perScene)); diff --git a/engine/src/render/split_screen/plugin.cpp b/engine/src/render/split_screen/plugin.cpp index a8189161ae..bb3c028af8 100644 --- a/engine/src/render/split_screen/plugin.cpp +++ b/engine/src/render/split_screen/plugin.cpp @@ -1,7 +1,7 @@ #include +#include #include -#include #include #include #include @@ -62,48 +62,46 @@ void cubos::engine::splitScreenPlugin(Cubos& cubos) cubos.system("split screen for each DrawsTo relation") .tagged(splitScreenTag) - .call( - [](Query targets, - Query perspectiveCameras) { - for (auto [targetEnt, target] : targets) + .call([](Query targets, + Query cameras) { + for (auto [targetEnt, target] : targets) + { + int cameraCount = 0; + + // Find the cameras that draw to the target. + for (auto [localToWorld, camera, drawsTo, splitScreen] : cameras.pin(1, targetEnt)) { - int cameraCount = 0; + // Ignore unused argument warnings + (void)splitScreen; - // Find the cameras that draw to the target. - for (auto [localToWorld, camera, drawsTo, splitScreen] : perspectiveCameras.pin(1, targetEnt)) + if (!camera.active) { - // Ignore unused argument warnings - (void)splitScreen; + continue; + } - if (!camera.active) - { - continue; - } + cameraCount += 1; + } - cameraCount += 1; - } + std::vector positions(static_cast(cameraCount)); + std::vector sizes(static_cast(cameraCount)); - std::vector positions(static_cast(cameraCount)); - std::vector sizes(static_cast(cameraCount)); + setViewportCameras({0, 0}, target.size, cameraCount, positions.begin(), sizes.begin()); - setViewportCameras({0, 0}, target.size, cameraCount, positions.begin(), sizes.begin()); + unsigned long i = 0; + for (auto [localToWorld, camera, drawsTo, splitScreen] : cameras.pin(1, targetEnt)) + { + // Ignore unused argument warnings + (void)splitScreen; - unsigned long i = 0; - for (auto [localToWorld, camera, drawsTo, splitScreen] : perspectiveCameras.pin(1, targetEnt)) + if (!camera.active) { - // Ignore unused argument warnings - (void)splitScreen; - - if (!camera.active) - { - continue; - } - - drawsTo.viewportOffset = - static_cast(positions[i]) / static_cast(target.size); - drawsTo.viewportSize = static_cast(sizes[i]) / static_cast(target.size); - i++; + continue; } + + drawsTo.viewportOffset = static_cast(positions[i]) / static_cast(target.size); + drawsTo.viewportSize = static_cast(sizes[i]) / static_cast(target.size); + i++; } - }); + } + }); } diff --git a/engine/src/render/ssao/plugin.cpp b/engine/src/render/ssao/plugin.cpp index cc00d49000..a105ba0021 100644 --- a/engine/src/render/ssao/plugin.cpp +++ b/engine/src/render/ssao/plugin.cpp @@ -5,8 +5,8 @@ #include #include +#include #include -#include #include #include #include @@ -146,7 +146,7 @@ void cubos::engine::ssaoPlugin(Cubos& cubos) cubos.system("apply SSAO to the GBuffer and output to the SSAO texture") .tagged(drawToSSAOTag) .call([](State& state, const Window& window, Query targets, - Query perspectiveCameras) { + Query cameras) { auto& rd = window->renderDevice(); for (auto [targetEnt, gBuffer, ssao] : targets) @@ -188,7 +188,7 @@ void cubos::engine::ssaoPlugin(Cubos& cubos) } // Find the cameras that draw to the SSAO target. - for (auto [localToWorld, camera, drawsTo] : perspectiveCameras.pin(1, targetEnt)) + for (auto [localToWorld, camera, drawsTo] : cameras.pin(1, targetEnt)) { if (!camera.active) { @@ -225,10 +225,7 @@ void cubos::engine::ssaoPlugin(Cubos& cubos) // Fill the PerScene constant buffer. PerScene perScene{}; perScene.view = glm::inverse(localToWorld.mat); - perScene.projection = glm::perspective(glm::radians(camera.fovY), - (float(gBuffer.size.x) * drawsTo.viewportSize.x) / - (float(gBuffer.size.y) * drawsTo.viewportSize.y), - camera.zNear, camera.zFar); + perScene.projection = camera.projection; for (std::size_t i = 0; i < state.kernel.size(); ++i) { perScene.samples[i] = glm::vec4(state.kernel[i], 0.0F); diff --git a/tools/tesseratos/src/tesseratos/debug_camera/plugin.cpp b/tools/tesseratos/src/tesseratos/debug_camera/plugin.cpp index 1a1f95151f..ed85780e82 100644 --- a/tools/tesseratos/src/tesseratos/debug_camera/plugin.cpp +++ b/tools/tesseratos/src/tesseratos/debug_camera/plugin.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -20,6 +21,7 @@ using namespace cubos::core::io; using cubos::core::ecs::EntityHash; +using cubos::engine::Camera; using cubos::engine::Commands; using cubos::engine::Cubos; using cubos::engine::DrawsTo; @@ -63,9 +65,8 @@ void tesseratos::debugCameraPlugin(Cubos& cubos) cubos.startupSystem("create Debug Camera").call([](Commands commands, State& debugCamera) { debugCamera.entity = commands.create() .named("debug-camera") - .add(PerspectiveCamera{ - .active = false, - }) + .add(Camera{.active = false}) + .add(PerspectiveCamera{}) .add(Position{{}}) .add(FreeCameraController{ .enabled = false, @@ -103,8 +104,7 @@ void tesseratos::debugCameraPlugin(Cubos& cubos) cubos.system("update Debug Camera") .tagged(cubos::engine::imguiTag) .onlyIf([](Toolbox& toolbox) { return toolbox.isOpen("Debug Camera"); }) - .call([](State& state, Commands cmds, - Query cameras, + .call([](State& state, Commands cmds, Query cameras, Query targets) { ImGui::Begin("Debug Camera"); diff --git a/tools/tesseratos/src/tesseratos/transform_gizmo/plugin.cpp b/tools/tesseratos/src/tesseratos/transform_gizmo/plugin.cpp index 89923dfe56..9320365596 100644 --- a/tools/tesseratos/src/tesseratos/transform_gizmo/plugin.cpp +++ b/tools/tesseratos/src/tesseratos/transform_gizmo/plugin.cpp @@ -8,7 +8,7 @@ #include #include -#include +#include #include #include #include @@ -24,11 +24,11 @@ using cubos::core::ecs::World; using cubos::core::io::Window; using cubos::core::memory::Opt; +using cubos::engine::Camera; using cubos::engine::Cubos; using cubos::engine::Gizmos; using cubos::engine::Input; using cubos::engine::LocalToWorld; -using cubos::engine::PerspectiveCamera; using cubos::engine::Position; using cubos::engine::Rotation; using cubos::engine::Scale; @@ -111,7 +111,7 @@ static void checkRotation(Rotation& rotation, glm::vec3 globalPosition, glm::vec rotation.quat = glm::angleAxis(angle, axisVector) * rotation.quat; } -static void drawPositionGizmo(Query positionQuery, const PerspectiveCamera& camera, +static void drawPositionGizmo(Query positionQuery, const Camera& camera, const LocalToWorld& cameraLtw, Gizmos& gizmos, const Input& input, const Window& window, Entity selectedEntity, bool useLocalAxis, double distance) { @@ -123,9 +123,7 @@ static void drawPositionGizmo(Query positionQuery, con pv = glm::inverse(pv); - pv = glm::perspective(glm::radians(camera.fovY), (float)window->size().x / (float)window->size().y, camera.zNear, - camera.zFar) * - pv; + pv = camera.projection * pv; glm::vec3 xColor = {1, 0, 0}; glm::vec3 yColor = {0, 1, 0}; @@ -229,7 +227,7 @@ static void drawPositionGizmo(Query positionQuery, con 0.7F); } -static void drawRotationGizmo(Query positionQuery, const PerspectiveCamera& camera, +static void drawRotationGizmo(Query positionQuery, const Camera& camera, const LocalToWorld& cameraLtw, Gizmos& gizmos, const Input& input, const Window& window, Entity selectedEntity, bool useLocalAxis, double distance) { @@ -241,9 +239,7 @@ static void drawRotationGizmo(Query positionQuery, con pv = glm::inverse(pv); - pv = glm::perspective(glm::radians(camera.fovY), (float)window->size().x / (float)window->size().y, camera.zNear, - camera.zFar) * - pv; + pv = camera.projection * pv; glm::vec3 xColor = {1, 0, 0}; glm::vec3 yColor = {0, 1, 0}; @@ -306,7 +302,7 @@ void tesseratos::transformGizmoPlugin(Cubos& cubos) .call([](const EntitySelector& entitySelector, const Window& window, const Input& input, Settings& settings, Gizmos& gizmos, Query positionQuery, Query rotationQuery, - Query cameraQuery) { + Query cameraQuery) { if (entitySelector.selection.isNull()) { return; @@ -333,4 +329,4 @@ void tesseratos::transformGizmoPlugin(Cubos& cubos) drawRotationGizmo(rotationQuery, camera, cameraLtw, gizmos, input, window, entitySelector.selection, useLocal, distance); }); -} \ No newline at end of file +}