418 lines
18 KiB
C++
418 lines
18 KiB
C++
#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,
|
||
meshRenderer.RenderingLayerMask,
|
||
meshRenderer.MaxDrawDistance,
|
||
meshRenderer.StaticForCulling,
|
||
meshRenderer.CastShadows,
|
||
meshRenderer.ReceiveShadows,
|
||
{},
|
||
meshRenderer.BuiltinMesh == MetaCoreBuiltinMeshType::Cube ? 12U :
|
||
(meshRenderer.BuiltinMesh == MetaCoreBuiltinMeshType::Plane ? 2U : 0U),
|
||
0U
|
||
});
|
||
auto& syncRenderable = snapshot.Renderables.back();
|
||
const glm::vec3 position = glm::vec3(worldMatrix[3]);
|
||
const glm::vec3 extents{
|
||
std::max(0.001F, glm::length(glm::vec3(worldMatrix[0])) * 0.5F),
|
||
std::max(0.001F, glm::length(glm::vec3(worldMatrix[1])) * 0.5F),
|
||
std::max(0.001F, glm::length(glm::vec3(worldMatrix[2])) * 0.5F)};
|
||
syncRenderable.WorldBounds = {position - extents, position + extents};
|
||
}
|
||
|
||
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;
|
||
syncCamera.PostProcessProfileGuid = camera.PostProcessProfileGuid;
|
||
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.Type,
|
||
light.Color,
|
||
light.Intensity,
|
||
worldMatrix,
|
||
light.Enabled,
|
||
light.UseColorTemperature,
|
||
light.ColorTemperatureKelvin,
|
||
light.Range,
|
||
light.InnerConeDegrees,
|
||
light.OuterConeDegrees,
|
||
light.FalloffExponent,
|
||
light.CastShadows,
|
||
light.ShadowBias,
|
||
light.ShadowNormalBias,
|
||
light.ShadowPriority,
|
||
light.LightingMask
|
||
});
|
||
}
|
||
|
||
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(gameObject.HasComponent<MetaCoreReflectionProbeComponent>())snapshot.ReflectionProbes.push_back({objectId,gameObject.GetComponent<MetaCoreReflectionProbeComponent>(),worldMatrix});
|
||
if(gameObject.HasComponent<MetaCoreLodGroupComponent>())snapshot.LodGroups.push_back({objectId,gameObject.GetComponent<MetaCoreLodGroupComponent>(),worldMatrix});
|
||
if(gameObject.HasComponent<MetaCoreParticleEmitterComponent>())snapshot.ParticleEmitters.push_back({objectId,gameObject.GetComponent<MetaCoreParticleEmitterComponent>(),worldMatrix});
|
||
if(gameObject.HasComponent<MetaCoreLineRendererComponent>())snapshot.LineRenderers.push_back({objectId,gameObject.GetComponent<MetaCoreLineRendererComponent>(),worldMatrix});
|
||
if(gameObject.HasComponent<MetaCoreTrailRendererComponent>())snapshot.TrailRenderers.push_back({objectId,gameObject.GetComponent<MetaCoreTrailRendererComponent>(),worldMatrix});
|
||
if(gameObject.HasComponent<MetaCoreDecalProjectorComponent>())snapshot.DecalProjectors.push_back({objectId,gameObject.GetComponent<MetaCoreDecalProjectorComponent>(),worldMatrix});
|
||
if(gameObject.HasComponent<MetaCoreTerrainComponent>())snapshot.Terrains.push_back({objectId,gameObject.GetComponent<MetaCoreTerrainComponent>(),worldMatrix});
|
||
}
|
||
|
||
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
|