MetaCore/Source/MetaCoreRender/Private/MetaCoreSceneRenderSync.cpp
2026-07-13 15:28:35 +08:00

382 lines
16 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "MetaCoreRender/MetaCoreSceneRenderSync.h"
#include "MetaCoreRender/MetaCoreRenderTypes.h"
#include "MetaCoreScene/MetaCoreScene.h"
#include "MetaCoreScene/MetaCoreTransformUtils.h"
#include <glm/ext/matrix_clip_space.hpp>
#include <glm/ext/matrix_transform.hpp>
#include <glm/geometric.hpp>
#include <glm/vec4.hpp>
#include <algorithm>
#include <cmath>
namespace MetaCore {
namespace {
[[nodiscard]] bool MetaCoreIsUsableDirection(const glm::vec3& value) {
return std::isfinite(value.x) &&
std::isfinite(value.y) &&
std::isfinite(value.z) &&
glm::length(value) > 0.0001F;
}
[[nodiscard]] glm::vec3 MetaCoreExtractTranslation(const glm::mat4& matrix) {
return glm::vec3(matrix[3]);
}
[[nodiscard]] std::string MetaCoreNormalizeSyncPath(std::string path) {
std::replace(path.begin(), path.end(), '\\', '/');
return path;
}
[[nodiscard]] float MetaCoreSafeAspectRatio(float width, float height) {
return height > 0.0001F ? std::max(0.0001F, width / height) : 1.0F;
}
[[nodiscard]] glm::mat4 MetaCoreBuildProjectionMatrix(
const MetaCoreSceneView& sceneView,
float aspectRatio
) {
const float nearClip = std::max(0.0001F, sceneView.NearClip);
const float farClip = std::max(nearClip + 0.0001F, sceneView.FarClip);
if (sceneView.ProjectionMode == MetaCoreCameraProjectionMode::Orthographic) {
const float halfHeight = std::max(0.0001F, sceneView.OrthographicSize);
const float halfWidth = halfHeight * aspectRatio;
return glm::ortho(-halfWidth, halfWidth, -halfHeight, halfHeight, nearClip, farClip);
}
const float fovDegrees = std::clamp(sceneView.VerticalFieldOfViewDegrees, 1.0F, 179.0F);
return glm::perspective(glm::radians(fovDegrees), aspectRatio, nearClip, farClip);
}
[[nodiscard]] glm::vec3 MetaCoreSafeForward(const MetaCoreSceneView& sceneView) {
glm::vec3 forward = sceneView.CameraTarget - sceneView.CameraPosition;
if (!MetaCoreIsUsableDirection(forward)) {
forward = glm::vec3(0.0F, 0.0F, -1.0F);
}
return glm::normalize(forward);
}
[[nodiscard]] glm::vec3 MetaCoreSafeUp(const MetaCoreSceneView& sceneView, const glm::vec3& forward) {
glm::vec3 up = sceneView.CameraUp;
if (!MetaCoreIsUsableDirection(up) || glm::length(glm::cross(forward, up)) <= 0.0001F) {
up = glm::vec3(0.0F, 1.0F, 0.0F);
}
if (glm::length(glm::cross(forward, up)) <= 0.0001F) {
up = glm::vec3(0.0F, 0.0F, 1.0F);
}
return glm::normalize(up);
}
} // namespace
MetaCoreSceneRenderSyncSnapshot MetaCoreSceneRenderSync::BuildSnapshot(const MetaCoreScene& scene) const {
MetaCoreSceneRenderSyncSnapshot snapshot;
snapshot.FrameId = ++FrameId_;
snapshot.SceneRevision = scene.GetRevision();
snapshot.Environment = scene.GetEnvironmentSettings();
// 预处理:构建子树模型网格节点计数表,支撑在没有 Tag 时的 Fallback 回溯
std::unordered_map<MetaCoreId, std::unordered_map<std::string, int>> subtreeModelCounts;
for (MetaCoreId objectId : scene.BuildHierarchyPreorder()) {
const MetaCoreGameObject gameObject = scene.FindGameObject(objectId);
if (gameObject && gameObject.HasComponent<MetaCoreMeshRendererComponent>()) {
const auto& mesh = gameObject.GetComponent<MetaCoreMeshRendererComponent>();
if (mesh.ModelNodeIndex >= 0 && !mesh.SourceModelPath.empty()) {
std::string normPath = MetaCoreNormalizeSyncPath(mesh.SourceModelPath);
MetaCoreId currentId = objectId;
while (currentId != 0) {
auto currentObj = scene.FindGameObject(currentId);
if (!currentObj) break;
subtreeModelCounts[currentId][normPath]++;
currentId = currentObj.GetParentId();
}
}
}
}
for (MetaCoreId objectId : scene.BuildHierarchyPreorder()) {
const MetaCoreGameObject gameObject = scene.FindGameObject(objectId);
if (!gameObject) {
continue;
}
glm::mat4 localMatrix{1.0F};
if (gameObject.HasComponent<MetaCoreTransformComponent>()) {
localMatrix = MetaCoreBuildTransformMatrix(gameObject.GetComponent<MetaCoreTransformComponent>());
}
glm::mat4 worldMatrix = localMatrix;
const MetaCoreId parentId = gameObject.GetParentId();
if (parentId != 0) {
const auto parentWorldIt = snapshot.WorldMatrices.find(parentId);
if (parentWorldIt != snapshot.WorldMatrices.end()) {
worldMatrix = parentWorldIt->second * localMatrix;
}
}
snapshot.WorldMatrices[objectId] = worldMatrix;
snapshot.LocalMatrices[objectId] = localMatrix;
if (gameObject.HasComponent<MetaCoreMeshRendererComponent>()) {
const auto& meshRenderer = gameObject.GetComponent<MetaCoreMeshRendererComponent>();
// 黄金算法:溯源确定唯一的模型根加载主体
MetaCoreGameObject hostRoot = gameObject;
if (meshRenderer.ModelNodeIndex >= 0) {
const std::string& currentModelPath = meshRenderer.SourceModelPath;
std::string normCurrentPath = MetaCoreNormalizeSyncPath(currentModelPath);
MetaCoreGameObject current = gameObject;
MetaCoreGameObject foundRoot = {};
// 1. 优先通过运行时 MetaCoreModelRootTag 快速寻找顶级根
while (current) {
if (current.HasComponent<MetaCoreModelRootTag>()) {
auto& tag = current.GetComponent<MetaCoreModelRootTag>();
if (MetaCoreNormalizeSyncPath(tag.SourceModelPath) == normCurrentPath) {
foundRoot = current;
break;
}
}
if (current.GetParentId() == 0) break;
current = scene.FindGameObject(current.GetParentId());
}
if (foundRoot) {
hostRoot = foundRoot;
} else {
// 2. Fallback若无明确 Tag使用转折点判定算法
current = gameObject;
MetaCoreGameObject bestCandidate = gameObject;
int maxCount = subtreeModelCounts[gameObject.GetId()][normCurrentPath];
while (current) {
int count = subtreeModelCounts[current.GetId()][normCurrentPath];
if (count > maxCount) {
maxCount = count;
bestCandidate = current;
}
if (current.GetParentId() == 0) break;
current = scene.FindGameObject(current.GetParentId());
}
hostRoot = bestCandidate;
}
}
snapshot.Renderables.push_back(MetaCoreRenderSyncRenderable{
objectId,
parentId,
gameObject.GetName(),
localMatrix,
worldMatrix,
meshRenderer.Visible,
meshRenderer.MeshSource,
meshRenderer.BuiltinMesh,
meshRenderer.MeshAssetGuid,
meshRenderer.SourceModelAssetGuid,
meshRenderer.SourceModelPath,
meshRenderer.ModelNodeIndex,
hostRoot.GetId(),
hostRoot.GetName(),
hostRoot.HasComponent<MetaCoreModelRootTag>(),
meshRenderer.MaterialAssetGuids,
meshRenderer.BaseColor,
meshRenderer.Metallic,
meshRenderer.Roughness,
meshRenderer.EmissiveColor,
meshRenderer.AlphaMode,
meshRenderer.AlphaCutoff,
meshRenderer.DoubleSided,
meshRenderer.BaseColorTexturePath,
meshRenderer.MetallicRoughnessTexturePath,
meshRenderer.NormalTexturePath,
meshRenderer.EmissiveTexturePath,
meshRenderer.AoTexturePath
});
}
if (gameObject.HasComponent<MetaCoreCameraComponent>()) {
const auto& camera = gameObject.GetComponent<MetaCoreCameraComponent>();
MetaCoreRenderSyncCamera syncCamera;
syncCamera.ObjectId = objectId;
syncCamera.IsPrimary = camera.IsPrimary;
syncCamera.FieldOfViewDegrees = camera.FieldOfViewDegrees;
syncCamera.NearClip = camera.NearClip;
syncCamera.FarClip = camera.FarClip;
syncCamera.WorldMatrix = worldMatrix;
syncCamera.Enabled = camera.Enabled;
syncCamera.ProjectionMode = camera.ProjectionMode;
syncCamera.OrthographicSize = camera.OrthographicSize;
syncCamera.Depth = camera.Depth;
syncCamera.CullingMask = camera.CullingMask;
syncCamera.ClearFlags = camera.ClearFlags;
syncCamera.BackgroundColor = camera.BackgroundColor;
syncCamera.BackgroundAlpha = camera.BackgroundAlpha;
snapshot.Cameras.push_back(syncCamera);
if (syncCamera.IsPrimary && syncCamera.Enabled && !snapshot.PrimaryCamera.has_value()) {
snapshot.PrimaryCamera = syncCamera;
}
}
if (gameObject.HasComponent<MetaCoreLightComponent>()) {
const auto& light = gameObject.GetComponent<MetaCoreLightComponent>();
snapshot.Lights.push_back(MetaCoreRenderSyncLight{
objectId,
light.Color,
light.Intensity,
worldMatrix,
light.Enabled
});
}
if (gameObject.HasComponent<MetaCoreAnimatorComponent>()) {
const auto& animator = gameObject.GetComponent<MetaCoreAnimatorComponent>();
std::string sourceModelPath = animator.SourceModelPath;
if (sourceModelPath.empty() && gameObject.HasComponent<MetaCoreModelRootTag>()) {
sourceModelPath = gameObject.GetComponent<MetaCoreModelRootTag>().SourceModelPath;
}
snapshot.Animations.push_back(MetaCoreRenderSyncAnimationState{
objectId,
animator.SourceModelAssetGuid,
sourceModelPath,
animator.ClipIndex,
animator.RuntimeTimeSeconds,
animator.Enabled,
animator.RuntimePlaying,
animator.Loop,
animator.Speed
});
}
}
if (!snapshot.PrimaryCamera.has_value() && !snapshot.Cameras.empty()) {
const MetaCoreRenderSyncCamera* bestCamera = nullptr;
for (const MetaCoreRenderSyncCamera& camera : snapshot.Cameras) {
if (!camera.Enabled) {
continue;
}
if (bestCamera == nullptr || camera.Depth < bestCamera->Depth) {
bestCamera = &camera;
}
}
if (bestCamera != nullptr) {
snapshot.PrimaryCamera = *bestCamera;
}
}
return snapshot;
}
bool MetaCoreSceneRenderSync::TryBuildSceneViewFromPrimaryCamera(
const MetaCoreSceneRenderSyncSnapshot& snapshot,
MetaCoreSceneView& sceneView
) {
if (!snapshot.PrimaryCamera.has_value()) {
return false;
}
const MetaCoreRenderSyncCamera& camera = *snapshot.PrimaryCamera;
return TryBuildSceneViewFromCamera(camera, sceneView);
}
bool MetaCoreSceneRenderSync::TryBuildSceneViewFromCamera(
const MetaCoreRenderSyncCamera& camera,
MetaCoreSceneView& sceneView
) {
if (!camera.Enabled) {
return false;
}
const glm::vec3 cameraPosition = MetaCoreExtractTranslation(camera.WorldMatrix);
glm::vec3 cameraForward = glm::vec3(camera.WorldMatrix * glm::vec4(0.0F, 0.0F, -1.0F, 0.0F));
glm::vec3 cameraUp = glm::vec3(camera.WorldMatrix * glm::vec4(0.0F, 1.0F, 0.0F, 0.0F));
if (!MetaCoreIsUsableDirection(cameraForward)) {
cameraForward = glm::vec3(0.0F, 0.0F, -1.0F);
}
if (!MetaCoreIsUsableDirection(cameraUp)) {
cameraUp = glm::vec3(0.0F, 1.0F, 0.0F);
}
sceneView.CameraPosition = cameraPosition;
sceneView.CameraTarget = cameraPosition + glm::normalize(cameraForward);
sceneView.CameraUp = glm::normalize(cameraUp);
sceneView.VerticalFieldOfViewDegrees = camera.FieldOfViewDegrees;
sceneView.NearClip = camera.NearClip;
sceneView.FarClip = camera.FarClip;
sceneView.ProjectionMode = camera.ProjectionMode;
sceneView.OrthographicSize = camera.OrthographicSize;
sceneView.ClearFlags = camera.ClearFlags;
sceneView.BackgroundColor = camera.BackgroundColor;
sceneView.BackgroundAlpha = camera.BackgroundAlpha;
sceneView.CullingMask = camera.CullingMask;
sceneView.SourceCameraObjectId = camera.ObjectId;
return true;
}
MetaCoreCameraRay MetaCoreScreenPointToRay(
const MetaCoreSceneView& sceneView,
const MetaCoreViewportRect& viewportRect,
const glm::vec2& screenPosition
) {
const float width = std::max(1.0F, viewportRect.Width);
const float height = std::max(1.0F, viewportRect.Height);
const float localX = std::clamp(screenPosition.x - viewportRect.Left, 0.0F, width);
const float localY = std::clamp(screenPosition.y - viewportRect.Top, 0.0F, height);
const float normalizedX = (localX / width) * 2.0F - 1.0F;
const float normalizedY = 1.0F - (localY / height) * 2.0F;
const glm::vec3 forward = MetaCoreSafeForward(sceneView);
const glm::vec3 up = MetaCoreSafeUp(sceneView, forward);
const glm::vec3 right = glm::normalize(glm::cross(forward, up));
const float aspectRatio = MetaCoreSafeAspectRatio(width, height);
if (sceneView.ProjectionMode == MetaCoreCameraProjectionMode::Orthographic) {
const float halfHeight = std::max(0.0001F, sceneView.OrthographicSize);
const float halfWidth = halfHeight * aspectRatio;
return MetaCoreCameraRay{
sceneView.CameraPosition + right * (normalizedX * halfWidth) + up * (normalizedY * halfHeight),
forward
};
}
const float fovDegrees = std::clamp(sceneView.VerticalFieldOfViewDegrees, 1.0F, 179.0F);
const float tanHalfFov = std::tan(glm::radians(fovDegrees) * 0.5F);
return MetaCoreCameraRay{
sceneView.CameraPosition,
glm::normalize(forward + right * (normalizedX * tanHalfFov * aspectRatio) + up * (normalizedY * tanHalfFov))
};
}
std::optional<glm::vec3> MetaCoreWorldToScreenPoint(
const MetaCoreSceneView& sceneView,
const MetaCoreViewportRect& viewportRect,
const glm::vec3& worldPosition
) {
const float width = std::max(1.0F, viewportRect.Width);
const float height = std::max(1.0F, viewportRect.Height);
const glm::vec3 forward = MetaCoreSafeForward(sceneView);
const glm::vec3 up = MetaCoreSafeUp(sceneView, forward);
const glm::mat4 viewMatrix = glm::lookAt(sceneView.CameraPosition, sceneView.CameraTarget, up);
const glm::mat4 projectionMatrix = MetaCoreBuildProjectionMatrix(sceneView, MetaCoreSafeAspectRatio(width, height));
const glm::vec4 clipPosition = projectionMatrix * viewMatrix * glm::vec4(worldPosition, 1.0F);
if (std::abs(clipPosition.w) <= 0.000001F) {
return std::nullopt;
}
const glm::vec3 ndc = glm::vec3(clipPosition) / clipPosition.w;
if (!std::isfinite(ndc.x) || !std::isfinite(ndc.y) || !std::isfinite(ndc.z)) {
return std::nullopt;
}
return glm::vec3(
viewportRect.Left + (ndc.x + 1.0F) * 0.5F * width,
viewportRect.Top + (1.0F - ndc.y) * 0.5F * height,
ndc.z
);
}
} // namespace MetaCore