diff --git a/.gitignore b/.gitignore index 9309668d..a38f0748 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ /active_project /test/assets/assets.db /test/products/ +_codeql_detected_source_root diff --git a/runtime/render/adaptor/include/render/adaptor/components/HLODComponent.h b/runtime/render/adaptor/include/render/adaptor/components/HLODComponent.h new file mode 100644 index 00000000..258ba286 --- /dev/null +++ b/runtime/render/adaptor/include/render/adaptor/components/HLODComponent.h @@ -0,0 +1,40 @@ +// +// Created by Copilot on 2026/2/16. +// + +#pragma once + +#include +#include +#include + +namespace sky { + + class HLODComponent : public ComponentBase { + public: + HLODComponent() = default; + ~HLODComponent() override; + + COMPONENT_RUNTIME_INFO(HLODComponent) + + static void Reflect(SerializationContext *context); + + void Tick(float time) override; + + void SaveJson(JsonOutputArchive &ar) const override; + void LoadJson(JsonInputArchive &ar) override; + + void SetHLODTree(const HLODTreePtr &tree); + HLODRenderer *GetRenderer() const { return renderer; } + + void OnAttachToWorld() override; + void OnDetachFromWorld() override; + private: + void ShutDown(); + void BuildRenderer(); + + HLODTreePtr hlodTree; + HLODRenderer *renderer = nullptr; + }; + +} // namespace sky diff --git a/runtime/render/adaptor/src/RenderModule.cpp b/runtime/render/adaptor/src/RenderModule.cpp index d7b6ae33..d18bd81a 100644 --- a/runtime/render/adaptor/src/RenderModule.cpp +++ b/runtime/render/adaptor/src/RenderModule.cpp @@ -18,12 +18,14 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include @@ -43,6 +45,7 @@ namespace sky { CameraComponent::Reflect(context); SkeletonMeshComponent::Reflect(context); SkyBoxComponent::Reflect(context); + HLODComponent::Reflect(context); static std::string GROUP = "Render"; ComponentFactory::Get()->RegisterComponent(GROUP); @@ -50,6 +53,7 @@ namespace sky { ComponentFactory::Get()->RegisterComponent(GROUP); ComponentFactory::Get()->RegisterComponent(GROUP); ComponentFactory::Get()->RegisterComponent(GROUP); + ComponentFactory::Get()->RegisterComponent(GROUP); } void RenderModule::ProcessArgs(const StartArguments &args) @@ -121,6 +125,7 @@ namespace sky { void RenderModule::InitFeatures() // NOLINT { MeshFeature::Get()->Init(); + HLODFeature::Get()->Init(); ImGuiFeature::Get()->Init(); TextFeature::Get()->Init(); LightFeature::Get()->Init(); @@ -149,6 +154,7 @@ namespace sky { RenderTechniqueLibrary::Destroy(); MeshFeature::Destroy(); + HLODFeature::Destroy(); ImGuiFeature::Destroy(); TextFeature::Destroy(); diff --git a/runtime/render/adaptor/src/components/HLODComponent.cpp b/runtime/render/adaptor/src/components/HLODComponent.cpp new file mode 100644 index 00000000..094083cc --- /dev/null +++ b/runtime/render/adaptor/src/components/HLODComponent.cpp @@ -0,0 +1,94 @@ +// +// Created by Copilot on 2026/2/16. +// + +#include + +#include +#include +#include + +#include +#include + +namespace sky { + + HLODComponent::~HLODComponent() + { + ShutDown(); + } + + void HLODComponent::Reflect(SerializationContext *context) + { + context->Register("HLODComponent"); + } + + void HLODComponent::SaveJson(JsonOutputArchive &ar) const + { + ar.StartObject(); + ar.EndObject(); + } + + void HLODComponent::LoadJson(JsonInputArchive &ar) + { + } + + void HLODComponent::SetHLODTree(const HLODTreePtr &tree) + { + hlodTree = tree; + if (renderer != nullptr) { + renderer->SetHLODTree(hlodTree); + } + } + + void HLODComponent::BuildRenderer() + { + auto *fp = GetFeatureProcessor(actor); + + if (renderer != nullptr) { + fp->RemoveHLODRenderer(renderer); + } + + renderer = fp->CreateHLODRenderer(); + if (hlodTree) { + renderer->SetHLODTree(hlodTree); + } + } + + void HLODComponent::ShutDown() + { + if (renderer != nullptr) { + GetFeatureProcessor(actor)->RemoveHLODRenderer(renderer); + renderer = nullptr; + } + } + + void HLODComponent::Tick(float time) + { + if (renderer == nullptr) { + BuildRenderer(); + } + + if (renderer != nullptr) { + auto *ts = actor->GetComponent(); + renderer->UpdateTransform(ts->GetWorldMatrix()); + + auto *renderScene = GetRenderSceneFromActor(actor); + auto *sceneView = renderScene->GetSceneView(Name("MainCamera")); + if (sceneView != nullptr) { + const auto &col = sceneView->GetWorld()[3]; + renderer->UpdateLODSelection(Vector3{col.x, col.y, col.z}); + } + } + } + + void HLODComponent::OnAttachToWorld() + { + } + + void HLODComponent::OnDetachFromWorld() + { + ShutDown(); + } + +} // namespace sky diff --git a/runtime/render/core/include/render/hlod/HLODFeature.h b/runtime/render/core/include/render/hlod/HLODFeature.h new file mode 100644 index 00000000..52cf41f3 --- /dev/null +++ b/runtime/render/core/include/render/hlod/HLODFeature.h @@ -0,0 +1,19 @@ +// +// Created by Copilot on 2026/2/16. +// + +#pragma once + +#include + +namespace sky { + + class HLODFeature : public Singleton { + public: + HLODFeature() = default; + ~HLODFeature() override = default; + + void Init(); + }; + +} // namespace sky diff --git a/runtime/render/core/include/render/hlod/HLODFeatureProcessor.h b/runtime/render/core/include/render/hlod/HLODFeatureProcessor.h new file mode 100644 index 00000000..d307e843 --- /dev/null +++ b/runtime/render/core/include/render/hlod/HLODFeatureProcessor.h @@ -0,0 +1,29 @@ +// +// Created by Copilot on 2026/2/16. +// + +#pragma once + +#include +#include +#include +#include + +namespace sky { + + class HLODFeatureProcessor : public IFeatureProcessor { + public: + explicit HLODFeatureProcessor(RenderScene *scene) : IFeatureProcessor(scene) {} + ~HLODFeatureProcessor() override = default; + + void Tick(float time) override; + void Render(rdg::RenderGraph &rdg) override; + + HLODRenderer *CreateHLODRenderer(); + void RemoveHLODRenderer(HLODRenderer *renderer); + + private: + std::list> renderers; + }; + +} // namespace sky diff --git a/runtime/render/core/include/render/hlod/HLODNode.h b/runtime/render/core/include/render/hlod/HLODNode.h new file mode 100644 index 00000000..ea331d3f --- /dev/null +++ b/runtime/render/core/include/render/hlod/HLODNode.h @@ -0,0 +1,28 @@ +// +// Created by Copilot on 2026/2/16. +// + +#pragma once + +#include +#include +#include +#include +#include + +namespace sky { + + struct HLODNode { + uint32_t lodLevel = 0; + float switchInDistance = 0.f; + float switchOutDistance = 0.f; + + AABB worldBound; + + RDMeshPtr mesh; + + uint32_t parentIndex = ~0U; + std::vector childIndices; + }; + +} // namespace sky diff --git a/runtime/render/core/include/render/hlod/HLODRenderer.h b/runtime/render/core/include/render/hlod/HLODRenderer.h new file mode 100644 index 00000000..490f1a9b --- /dev/null +++ b/runtime/render/core/include/render/hlod/HLODRenderer.h @@ -0,0 +1,41 @@ +// +// Created by Copilot on 2026/2/16. +// + +#pragma once + +#include +#include +#include +#include + +namespace sky { + class RenderScene; + + class HLODRenderer { + public: + HLODRenderer() = default; + ~HLODRenderer(); + + void AttachScene(RenderScene *scn); + void SetHLODTree(const HLODTreePtr &tree); + + void UpdateTransform(const Matrix4 &matrix); + void UpdateLODSelection(const Vector3 &cameraPos); + + void Tick(); + + private: + void RebuildPrimitives(const std::vector &visibleNodes); + void ClearPrimitives(); + + RenderScene *scene = nullptr; + + HLODTreePtr hlodTree; + Matrix4 worldMatrix; + + std::vector activeNodes; + std::vector> primitives; + }; + +} // namespace sky diff --git a/runtime/render/core/include/render/hlod/HLODTree.h b/runtime/render/core/include/render/hlod/HLODTree.h new file mode 100644 index 00000000..3d7649f0 --- /dev/null +++ b/runtime/render/core/include/render/hlod/HLODTree.h @@ -0,0 +1,32 @@ +// +// Created by Copilot on 2026/2/16. +// + +#pragma once + +#include +#include +#include + +namespace sky { + + class HLODTree : public RefObject { + public: + HLODTree() = default; + ~HLODTree() override = default; + + uint32_t AddNode(const HLODNode &node); + const HLODNode &GetNode(uint32_t index) const; + uint32_t GetNodeCount() const { return static_cast(nodes.size()); } + + void SelectLODs(const Vector3 &cameraPos, std::vector &visibleNodes) const; + + private: + void SelectLODRecursive(uint32_t nodeIndex, const Vector3 &cameraPos, std::vector &visibleNodes) const; + float ComputeDistanceSq(const AABB &bound, const Vector3 &point) const; + + std::vector nodes; + }; + using HLODTreePtr = CounterPtr; + +} // namespace sky diff --git a/runtime/render/core/src/hlod/HLODFeature.cpp b/runtime/render/core/src/hlod/HLODFeature.cpp new file mode 100644 index 00000000..493ab814 --- /dev/null +++ b/runtime/render/core/src/hlod/HLODFeature.cpp @@ -0,0 +1,16 @@ +// +// Created by Copilot on 2026/2/16. +// + +#include +#include +#include + +namespace sky { + + void HLODFeature::Init() + { + Renderer::Get()->RegisterRenderFeature(); + } + +} // namespace sky diff --git a/runtime/render/core/src/hlod/HLODFeatureProcessor.cpp b/runtime/render/core/src/hlod/HLODFeatureProcessor.cpp new file mode 100644 index 00000000..ad316621 --- /dev/null +++ b/runtime/render/core/src/hlod/HLODFeatureProcessor.cpp @@ -0,0 +1,34 @@ +// +// Created by Copilot on 2026/2/16. +// + +#include + +namespace sky { + + void HLODFeatureProcessor::Tick(float time) + { + for (auto &renderer : renderers) { + renderer->Tick(); + } + } + + void HLODFeatureProcessor::Render(rdg::RenderGraph &rdg) + { + } + + HLODRenderer *HLODFeatureProcessor::CreateHLODRenderer() + { + auto *renderer = new HLODRenderer(); + renderer->AttachScene(scene); + return renderers.emplace_back(renderer).get(); + } + + void HLODFeatureProcessor::RemoveHLODRenderer(HLODRenderer *renderer) + { + renderers.remove_if([renderer](const auto &val) { + return renderer == val.get(); + }); + } + +} // namespace sky diff --git a/runtime/render/core/src/hlod/HLODRenderer.cpp b/runtime/render/core/src/hlod/HLODRenderer.cpp new file mode 100644 index 00000000..f4dbbfdf --- /dev/null +++ b/runtime/render/core/src/hlod/HLODRenderer.cpp @@ -0,0 +1,99 @@ +// +// Created by Copilot on 2026/2/16. +// + +#include +#include +#include + +namespace sky { + + HLODRenderer::~HLODRenderer() + { + ClearPrimitives(); + } + + void HLODRenderer::AttachScene(RenderScene *scn) + { + scene = scn; + } + + void HLODRenderer::SetHLODTree(const HLODTreePtr &tree) + { + ClearPrimitives(); + hlodTree = tree; + activeNodes.clear(); + } + + void HLODRenderer::UpdateTransform(const Matrix4 &matrix) + { + worldMatrix = matrix; + } + + void HLODRenderer::UpdateLODSelection(const Vector3 &cameraPos) + { + if (!hlodTree) { + return; + } + + std::vector visibleNodes; + hlodTree->SelectLODs(cameraPos, visibleNodes); + + std::sort(visibleNodes.begin(), visibleNodes.end()); + + if (visibleNodes != activeNodes) { + RebuildPrimitives(visibleNodes); + activeNodes = std::move(visibleNodes); + } + } + + void HLODRenderer::Tick() + { + for (auto &prim : primitives) { + prim->worldBound = AABB::Transform(prim->localBound, worldMatrix); + } + } + + void HLODRenderer::ClearPrimitives() + { + if (scene != nullptr) { + for (auto &prim : primitives) { + scene->RemovePrimitive(prim.get()); + } + } + primitives.clear(); + } + + void HLODRenderer::RebuildPrimitives(const std::vector &visibleNodes) + { + ClearPrimitives(); + + if (!hlodTree || !scene) { + return; + } + + for (uint32_t nodeIdx : visibleNodes) { + const auto &node = hlodTree->GetNode(nodeIdx); + if (!node.mesh) { + continue; + } + + const auto &subMeshes = node.mesh->GetSubMeshes(); + const auto &geometry = node.mesh->GetGeometry(); + + for (const auto &subMesh : subMeshes) { + auto prim = std::make_unique(); + prim->geometry = geometry; + prim->localBound = subMesh.aabb; + prim->worldBound = AABB::Transform(subMesh.aabb, worldMatrix); + prim->material = subMesh.material; + + prim->args.emplace_back(rhi::CmdDrawIndexed{subMesh.indexCount, 1, subMesh.firstIndex, static_cast(subMesh.firstVertex), 0}); + + scene->AddPrimitive(prim.get()); + primitives.emplace_back(std::move(prim)); + } + } + } + +} // namespace sky diff --git a/runtime/render/core/src/hlod/HLODTree.cpp b/runtime/render/core/src/hlod/HLODTree.cpp new file mode 100644 index 00000000..6ca2bdc6 --- /dev/null +++ b/runtime/render/core/src/hlod/HLODTree.cpp @@ -0,0 +1,71 @@ +// +// Created by Copilot on 2026/2/16. +// + +#include +#include +#include + +namespace sky { + + uint32_t HLODTree::AddNode(const HLODNode &node) + { + uint32_t index = static_cast(nodes.size()); + nodes.emplace_back(node); + return index; + } + + const HLODNode &HLODTree::GetNode(uint32_t index) const + { + return nodes[index]; + } + + void HLODTree::SelectLODs(const Vector3 &cameraPos, std::vector &visibleNodes) const + { + if (nodes.empty()) { + return; + } + + for (uint32_t i = 0; i < static_cast(nodes.size()); ++i) { + if (nodes[i].parentIndex == ~0U) { + SelectLODRecursive(i, cameraPos, visibleNodes); + } + } + } + + void HLODTree::SelectLODRecursive(uint32_t nodeIndex, const Vector3 &cameraPos, std::vector &visibleNodes) const + { + const auto &node = nodes[nodeIndex]; + float distSq = ComputeDistanceSq(node.worldBound, cameraPos); + + float switchOutSq = node.switchOutDistance * node.switchOutDistance; + if (switchOutSq > 0.f && distSq >= switchOutSq) { + return; + } + + float switchInSq = node.switchInDistance * node.switchInDistance; + bool useChildren = !node.childIndices.empty() && distSq < switchInSq; + + if (useChildren) { + for (uint32_t childIdx : node.childIndices) { + SelectLODRecursive(childIdx, cameraPos, visibleNodes); + } + } else if (node.mesh) { + visibleNodes.emplace_back(nodeIndex); + } + } + + float HLODTree::ComputeDistanceSq(const AABB &bound, const Vector3 &point) const + { + Vector3 center = { + (bound.min.x + bound.max.x) * 0.5f, + (bound.min.y + bound.max.y) * 0.5f, + (bound.min.z + bound.max.z) * 0.5f + }; + float dx = point.x - center.x; + float dy = point.y - center.y; + float dz = point.z - center.z; + return dx * dx + dy * dy + dz * dz; + } + +} // namespace sky