#include "Rendering/Extraction/RenderSceneUtility.h" #include "Components/CameraComponent.h" #include "Components/GaussianSplatRendererComponent.h" #include "Components/GameObject.h" #include "Components/LightComponent.h" #include "Components/MeshFilterComponent.h" #include "Components/MeshRendererComponent.h" #include "Components/TransformComponent.h" #include "Components/VolumeRendererComponent.h" #include "Rendering/Materials/RenderMaterialResolve.h" #include "Scene/Scene.h" #include namespace XCEngine { namespace Rendering { namespace { RenderObjectId BuildRenderObjectIdOrInvalid(const Components::GameObject& gameObject) { RenderObjectId renderObjectId = kInvalidRenderObjectId; TryConvertRuntimeObjectIdToRenderObjectId(gameObject.GetID(), renderObjectId); return renderObjectId; } } // namespace RenderCameraData BuildRenderCameraData( const Components::CameraComponent& camera, uint32_t viewportWidth, uint32_t viewportHeight) { RenderCameraData cameraData = {}; cameraData.viewportWidth = viewportWidth; cameraData.viewportHeight = viewportHeight; cameraData.worldPosition = camera.transform().GetPosition(); cameraData.worldRight = camera.transform().GetRight(); cameraData.worldUp = camera.transform().GetUp(); cameraData.worldForward = camera.transform().GetForward(); cameraData.clearColor = camera.GetClearColor(); cameraData.perspectiveProjection = camera.GetProjectionType() == Components::CameraProjectionType::Perspective; cameraData.verticalFovRadians = camera.GetFieldOfView() * Math::DEG_TO_RAD; cameraData.orthographicSize = camera.GetOrthographicSize(); cameraData.nearClipPlane = camera.GetNearClipPlane(); cameraData.farClipPlane = camera.GetFarClipPlane(); const Math::Matrix4x4 view = camera.transform().GetWorldToLocalMatrix(); const float aspect = viewportHeight > 0 ? static_cast(viewportWidth) / static_cast(viewportHeight) : 1.0f; cameraData.aspectRatio = aspect; Math::Matrix4x4 projection = Math::Matrix4x4::Identity(); if (camera.GetProjectionType() == Components::CameraProjectionType::Perspective) { projection = Math::Matrix4x4::Perspective( camera.GetFieldOfView() * Math::DEG_TO_RAD, aspect, camera.GetNearClipPlane(), camera.GetFarClipPlane()); } else { const float orthoSize = camera.GetOrthographicSize(); projection = Math::Matrix4x4::Orthographic( -orthoSize * aspect, orthoSize * aspect, -orthoSize, orthoSize, camera.GetNearClipPlane(), camera.GetFarClipPlane()); } cameraData.view = view.Transpose(); cameraData.projection = projection.Transpose(); cameraData.viewProjection = (projection * view).Transpose(); return cameraData; } void AppendRenderItemsForGameObject( Components::GameObject& gameObject, const Math::Vector3& cameraPosition, std::vector& outVisibleItems) { if (!gameObject.IsActiveInHierarchy()) { return; } auto* meshFilter = gameObject.GetComponent(); auto* meshRenderer = gameObject.GetComponent(); if (meshFilter == nullptr || meshRenderer == nullptr || !meshFilter->IsEnabled() || !meshRenderer->IsEnabled()) { return; } Resources::Mesh* mesh = meshFilter->GetMesh(); if (mesh == nullptr || !mesh->IsValid()) { return; } const Math::Matrix4x4 localToWorld = gameObject.GetTransform()->GetLocalToWorldMatrix(); const Math::Vector3 worldPosition = localToWorld.GetTranslation(); const float cameraDistanceSq = (worldPosition - cameraPosition).SqrMagnitude(); const RenderObjectId renderObjectId = BuildRenderObjectIdOrInvalid(gameObject); const Containers::Array& sections = mesh->GetSections(); if (!sections.Empty()) { for (size_t sectionIndex = 0; sectionIndex < sections.Size(); ++sectionIndex) { const Resources::MeshSection& section = sections[sectionIndex]; VisibleRenderItem visibleItem = {}; visibleItem.gameObject = &gameObject; visibleItem.meshFilter = meshFilter; visibleItem.meshRenderer = meshRenderer; visibleItem.mesh = mesh; visibleItem.materialIndex = section.materialID; visibleItem.renderObjectId = renderObjectId; visibleItem.sectionIndex = static_cast(sectionIndex); visibleItem.hasSection = true; visibleItem.material = ResolveMaterial(meshRenderer, mesh, section.materialID); visibleItem.renderQueue = ResolveMaterialRenderQueue(visibleItem.material); visibleItem.cameraDistanceSq = cameraDistanceSq; visibleItem.localToWorld = localToWorld; outVisibleItems.push_back(visibleItem); } return; } VisibleRenderItem visibleItem = {}; visibleItem.gameObject = &gameObject; visibleItem.meshFilter = meshFilter; visibleItem.meshRenderer = meshRenderer; visibleItem.mesh = mesh; visibleItem.renderObjectId = renderObjectId; visibleItem.materialIndex = 0; visibleItem.sectionIndex = 0; visibleItem.hasSection = false; visibleItem.material = ResolveMaterial(meshRenderer, mesh, 0); visibleItem.renderQueue = ResolveMaterialRenderQueue(visibleItem.material); visibleItem.cameraDistanceSq = cameraDistanceSq; visibleItem.localToWorld = localToWorld; outVisibleItems.push_back(visibleItem); } void AppendVisibleVolumesForGameObject( Components::GameObject& gameObject, const Math::Vector3& cameraPosition, std::vector& outVisibleVolumes) { if (!gameObject.IsActiveInHierarchy()) { return; } auto* volumeRenderer = gameObject.GetComponent(); if (volumeRenderer == nullptr || !volumeRenderer->IsEnabled()) { return; } Resources::VolumeField* volumeField = volumeRenderer->GetVolumeField(); Resources::Material* material = volumeRenderer->GetMaterial(); if (volumeField == nullptr || !volumeField->IsValid() || material == nullptr) { return; } const Math::Matrix4x4 localToWorld = gameObject.GetTransform()->GetLocalToWorldMatrix(); const Math::Vector3 worldPosition = localToWorld.GetTranslation(); VisibleVolumeItem visibleVolume = {}; visibleVolume.gameObject = &gameObject; visibleVolume.volumeRenderer = volumeRenderer; visibleVolume.volumeField = volumeField; visibleVolume.material = material; visibleVolume.renderQueue = ResolveMaterialRenderQueue(material); visibleVolume.cameraDistanceSq = (worldPosition - cameraPosition).SqrMagnitude(); visibleVolume.localToWorld = localToWorld; outVisibleVolumes.push_back(visibleVolume); } void AppendVisibleGaussianSplatsForGameObject( Components::GameObject& gameObject, const Math::Vector3& cameraPosition, std::vector& outVisibleGaussianSplats) { if (!gameObject.IsActiveInHierarchy()) { return; } auto* gaussianSplatRenderer = gameObject.GetComponent(); if (gaussianSplatRenderer == nullptr || !gaussianSplatRenderer->IsEnabled()) { return; } Resources::GaussianSplat* gaussianSplat = gaussianSplatRenderer->GetGaussianSplat(); Resources::Material* material = gaussianSplatRenderer->GetMaterial(); if (gaussianSplat == nullptr || !gaussianSplat->IsValid() || material == nullptr) { return; } const Math::Matrix4x4 localToWorld = gameObject.GetTransform()->GetLocalToWorldMatrix(); const Math::Vector3 worldPosition = localToWorld.GetTranslation(); VisibleGaussianSplatItem visibleGaussianSplat = {}; visibleGaussianSplat.gameObject = &gameObject; visibleGaussianSplat.gaussianSplatRenderer = gaussianSplatRenderer; visibleGaussianSplat.gaussianSplat = gaussianSplat; visibleGaussianSplat.material = material; visibleGaussianSplat.renderQueue = ResolveMaterialRenderQueue(material); visibleGaussianSplat.cameraDistanceSq = (worldPosition - cameraPosition).SqrMagnitude(); visibleGaussianSplat.localToWorld = localToWorld; outVisibleGaussianSplats.push_back(visibleGaussianSplat); } bool CompareVisibleRenderItemsStable( const VisibleRenderItem& lhs, const VisibleRenderItem& rhs) { if (lhs.renderQueue != rhs.renderQueue) { return lhs.renderQueue < rhs.renderQueue; } const bool isTransparentQueue = IsTransparentRenderQueue(lhs.renderQueue); if (lhs.cameraDistanceSq != rhs.cameraDistanceSq) { return isTransparentQueue ? lhs.cameraDistanceSq > rhs.cameraDistanceSq : lhs.cameraDistanceSq < rhs.cameraDistanceSq; } const Core::uint64 lhsObjectId = lhs.gameObject != nullptr ? lhs.gameObject->GetID() : 0u; const Core::uint64 rhsObjectId = rhs.gameObject != nullptr ? rhs.gameObject->GetID() : 0u; if (lhsObjectId != rhsObjectId) { return lhsObjectId < rhsObjectId; } if (lhs.hasSection != rhs.hasSection) { return lhs.hasSection && !rhs.hasSection; } if (lhs.sectionIndex != rhs.sectionIndex) { return lhs.sectionIndex < rhs.sectionIndex; } if (lhs.materialIndex != rhs.materialIndex) { return lhs.materialIndex < rhs.materialIndex; } return false; } bool CompareVisibleGaussianSplatsStable( const VisibleGaussianSplatItem& lhs, const VisibleGaussianSplatItem& rhs) { if (lhs.renderQueue != rhs.renderQueue) { return lhs.renderQueue < rhs.renderQueue; } const bool isTransparentQueue = IsTransparentRenderQueue(lhs.renderQueue); if (lhs.cameraDistanceSq != rhs.cameraDistanceSq) { return isTransparentQueue ? lhs.cameraDistanceSq > rhs.cameraDistanceSq : lhs.cameraDistanceSq < rhs.cameraDistanceSq; } const Core::uint64 lhsObjectId = lhs.gameObject != nullptr ? lhs.gameObject->GetID() : 0u; const Core::uint64 rhsObjectId = rhs.gameObject != nullptr ? rhs.gameObject->GetID() : 0u; if (lhsObjectId != rhsObjectId) { return lhsObjectId < rhsObjectId; } return false; } bool CompareVisibleVolumesStable( const VisibleVolumeItem& lhs, const VisibleVolumeItem& rhs) { if (lhs.renderQueue != rhs.renderQueue) { return lhs.renderQueue < rhs.renderQueue; } const bool isTransparentQueue = IsTransparentRenderQueue(lhs.renderQueue); if (lhs.cameraDistanceSq != rhs.cameraDistanceSq) { return isTransparentQueue ? lhs.cameraDistanceSq > rhs.cameraDistanceSq : lhs.cameraDistanceSq < rhs.cameraDistanceSq; } const Core::uint64 lhsObjectId = lhs.gameObject != nullptr ? lhs.gameObject->GetID() : 0u; const Core::uint64 rhsObjectId = rhs.gameObject != nullptr ? rhs.gameObject->GetID() : 0u; if (lhsObjectId != rhsObjectId) { return lhsObjectId < rhsObjectId; } return false; } std::vector CollectRenderItemsForEntityIds( const Components::Scene& scene, const std::vector& entityIds, const Math::Vector3& cameraPosition) { std::vector visibleItems; std::unordered_set visitedEntityIds; visitedEntityIds.reserve(entityIds.size()); for (uint64_t entityId : entityIds) { if (entityId == 0 || !visitedEntityIds.insert(entityId).second) { continue; } Components::GameObject* gameObject = scene.FindByID(entityId); if (gameObject == nullptr) { continue; } AppendRenderItemsForGameObject(*gameObject, cameraPosition, visibleItems); } return visibleItems; } bool IsUsableLight(const Components::LightComponent* light) { return light != nullptr && light->IsEnabled() && light->GetGameObject() != nullptr && light->GetGameObject()->IsActiveInHierarchy(); } bool IsLightVisibleForCullingMask( const Components::LightComponent& light, uint32_t cullingMask) { if (!IsUsableLight(&light)) { return false; } const Components::GameObject* gameObject = light.GetGameObject(); const uint32_t gameObjectLayerMask = 1u << gameObject->GetLayer(); return (cullingMask & gameObjectLayerMask) != 0u; } Math::Vector3 BuildRenderLightDirection( const Components::LightComponent& light) { Math::Vector3 direction = light.transform().GetForward() * -1.0f; if (direction.SqrMagnitude() <= Math::EPSILON) { return Math::Vector3::Back(); } return direction.Normalized(); } Components::LightComponent* FindMainDirectionalLight( const Components::Scene& scene, uint32_t cullingMask) { const std::vector lights = scene.FindObjectsOfType(); Components::LightComponent* mainDirectionalLight = nullptr; for (Components::LightComponent* light : lights) { if (light == nullptr || !IsLightVisibleForCullingMask(*light, cullingMask) || light->GetLightType() != Components::LightType::Directional) { continue; } if (mainDirectionalLight == nullptr || light->GetIntensity() > mainDirectionalLight->GetIntensity()) { mainDirectionalLight = light; } } return mainDirectionalLight; } } // namespace Rendering } // namespace XCEngine