refactor: transform world 矩阵改由帧循环统一重算,删除手动 Compose

- Scene::UpdateTransforms():渲染前全量 Compose(UE transform-update
  阶段模式),Application::Run 在 FlushDestroyQueue 后调用
- 用户代码永远不需要手动 Compose——Set*/直接写字段均安全;
  Compose 降级为引擎内部调用
- 选择全量而非 dirty 标记:字段直接写(t.rotation.y = x)无法可靠
  追踪脏,无条件重算零脚枪;万级实体内成本可忽略,更大规模再引入
  dirty+封装
- main.cpp/Camera 所有手动 Compose 调用点删除
This commit is contained in:
2026-09-23 15:26:58 +08:00
parent 1bc44e261b
commit 57ab8901a9
6 changed files with 35 additions and 25 deletions
+5 -9
View File
@@ -58,9 +58,8 @@ protected:
const auto ground = GetAssets().CreatePlane(15.0f, 140, 140, 145); const auto ground = GetAssets().CreatePlane(15.0f, 140, 140, 145);
m_Ground = scene.CreateEntity("Ground"); m_Ground = scene.CreateEntity("Ground");
m_Ground.GetComponent<TransformComponent>() // 出生即有,直接取 m_Ground.GetComponent<TransformComponent>()
.SetPosition(glm::vec3(0.0f, -0.5f, 0.0f)) .SetPosition(glm::vec3(0.0f, -0.5f, 0.0f)); // dirty → 帧循环统一 Compose
.Compose();
m_Ground.AddComponent<MeshRendererComponent>(MeshRendererComponent{ground}); m_Ground.AddComponent<MeshRendererComponent>(MeshRendererComponent{ground});
// 相机实体(primary = 当前渲染视角;位姿存实体 Transform) // 相机实体(primary = 当前渲染视角;位姿存实体 Transform)
@@ -68,15 +67,13 @@ protected:
auto& camera = m_Camera.AddComponent<CameraComponent>(); auto& camera = m_Camera.AddComponent<CameraComponent>();
camera.primary = true; camera.primary = true;
m_Camera.GetComponent<TransformComponent>() m_Camera.GetComponent<TransformComponent>()
.SetPosition(glm::vec3(0.0f, 0.0f, 7.0f)) .SetPosition(glm::vec3(0.0f, 0.0f, 7.0f));
.Compose();
// 神庙实体 × 1( FBX 单位较大,缩放到场景尺度) // 神庙实体 × 1( FBX 单位较大,缩放到场景尺度)
{ {
auto e = scene.CreateEntity("SunTemple"); auto e = scene.CreateEntity("SunTemple");
e.GetComponent<TransformComponent>() e.GetComponent<TransformComponent>()
.SetScale(glm::vec3(0.05f)) .SetScale(glm::vec3(0.05f));
.Compose();
e.AddComponent<MeshRendererComponent>(MeshRendererComponent{temple}); e.AddComponent<MeshRendererComponent>(MeshRendererComponent{temple});
} }
@@ -116,8 +113,7 @@ protected:
{ {
auto& t = m_Spinners[i].GetComponent<TransformComponent>(); auto& t = m_Spinners[i].GetComponent<TransformComponent>();
t.rotation.y = m_Time * 60.0f + static_cast<f32>(i) * 40.0f; t.rotation.y = m_Time * 60.0f + static_cast<f32>(i) * 40.0f;
t.scale = glm::vec3(0.5f); t.scale = glm::vec3(0.5f); // dirty → 帧循环统一 Compose
t.Compose();
} }
} }
+6 -3
View File
@@ -34,8 +34,10 @@ namespace Engine::ECS {
std::string name; std::string name;
}; };
// 世界变换:可编辑的 SRT 分量 + 派生的世界矩阵。 // 世界变换:可编辑的 SRT 分量 + 派生的 world 矩阵。
// 编辑分量后调用 Compose() 重算 world;渲染热路径只读 world。 // 分量可直接读写(含直接改字段);world 由帧循环的
// Scene::UpdateTransforms() 全量重算(UE transform-update 阶段
// 模式),调用方永远不需要手动 Compose。渲染热路径只读 world。
// 层级(父子变换)留给后续的层级同步系统。 // 层级(父子变换)留给后续的层级同步系统。
struct TransformComponent { struct TransformComponent {
glm::vec3 position{0.0f}; glm::vec3 position{0.0f};
@@ -47,7 +49,8 @@ namespace Engine::ECS {
TransformComponent& SetRotation(const glm::vec3& r) { rotation = r; return *this; } TransformComponent& SetRotation(const glm::vec3& r) { rotation = r; return *this; }
TransformComponent& SetScale(const glm::vec3& s) { scale = s; return *this; } TransformComponent& SetScale(const glm::vec3& s) { scale = s; return *this; }
// 分量 → world 矩阵(T * R * S) // SRT 分量 → world 矩阵(T * R * S)。
// 由 UpdateTransforms 每帧全量调用;勿在用户代码手动调用
TransformComponent& Compose() TransformComponent& Compose()
{ {
const glm::mat4 t = glm::translate(glm::mat4(1.0f), position); const glm::mat4 t = glm::translate(glm::mat4(1.0f), position);
+5
View File
@@ -51,6 +51,11 @@ namespace Engine::ECS {
EntityHandle CreateEntity(std::string name = {}); EntityHandle CreateEntity(std::string name = {});
void DestroyEntity(Entity e); // 延迟:帧尾统一销毁 void DestroyEntity(Entity e); // 延迟:帧尾统一销毁
void FlushDestroyQueue(); // 每帧末尾由 Application 调用 void FlushDestroyQueue(); // 每帧末尾由 Application 调用
// 帧循环统一重算脏 transform 的 world 矩阵(UE transform-update
// 阶段模式)——由 Application 在 OnUpdate 之后、渲染之前调用,
// 用户代码永远不需要手动 Compose
void UpdateTransforms();
bool IsAlive(Entity e) const { return m_Registry.valid(e); } bool IsAlive(Entity e) const { return m_Registry.valid(e); }
// 按稳定 UUID 查实体(序列化/跨引用场景);找不到返回 entt::null // 按稳定 UUID 查实体(序列化/跨引用场景);找不到返回 entt::null
+1
View File
@@ -96,6 +96,7 @@ namespace Engine {
// 延迟销毁在渲染前统一 flush,保证渲染遍历时池结构不变 // 延迟销毁在渲染前统一 flush,保证渲染遍历时池结构不变
m_Scene->FlushDestroyQueue(); m_Scene->FlushDestroyQueue();
m_Scene->UpdateTransforms(); // UE transform-update 阶段:渲染前保证 world 新鲜
const auto uiStart = std::chrono::steady_clock::now(); const auto uiStart = std::chrono::steady_clock::now();
+10
View File
@@ -53,6 +53,16 @@ namespace Engine::ECS {
m_PendingDestroy.clear(); m_PendingDestroy.clear();
} }
void Scene::UpdateTransforms()
{
// 全量重算:分量允许直接写字段(无法可靠 dirty 追踪),
// 无条件 Compose 保证任何写法下 world 都新鲜。
// 实测万级实体内成本可忽略;更大规模再引入 dirty+封装优化
m_Registry.view<TransformComponent>().each([](TransformComponent& t) {
t.Compose();
});
}
CameraComponent* Scene::GetPrimaryCamera(TransformComponent** outTransform) CameraComponent* Scene::GetPrimaryCamera(TransformComponent** outTransform)
{ {
CameraComponent* found = nullptr; CameraComponent* found = nullptr;
+8 -13
View File
@@ -14,9 +14,8 @@ namespace Engine {
void Camera::Update(Window& window, ECS::TransformComponent& t, f32 deltaTime) void Camera::Update(Window& window, ECS::TransformComponent& t, f32 deltaTime)
{ {
bool dirty = false; // 鼠标旋转:直接写实体旋转(yaw → rotation.y,pitch → rotation.x)。
// Set*/成员写后由帧循环的 UpdateTransforms 统一 Compose
// 鼠标旋转:直接写实体旋转(yaw → rotation.y,pitch → rotation.x)
if (m_MouseLookEnabled) if (m_MouseLookEnabled)
{ {
f32 mouseDX = 0, mouseDY = 0; f32 mouseDX = 0, mouseDY = 0;
@@ -27,7 +26,6 @@ namespace Engine {
t.rotation.y += mouseDX * m_MouseSensitivity; t.rotation.y += mouseDX * m_MouseSensitivity;
t.rotation.x = glm::clamp( t.rotation.x = glm::clamp(
t.rotation.x - mouseDY * m_MouseSensitivity, -89.0f, 89.0f); t.rotation.x - mouseDY * m_MouseSensitivity, -89.0f, 89.0f);
dirty = true;
} }
} }
@@ -42,15 +40,12 @@ namespace Engine {
const glm::vec3 right = r * glm::vec3(1, 0, 0); const glm::vec3 right = r * glm::vec3(1, 0, 0);
f32 speed = m_MoveSpeed * deltaTime; f32 speed = m_MoveSpeed * deltaTime;
if (window.IsKeyPressed(GLFW_KEY_W)) { t.position += forward * speed; dirty = true; } if (window.IsKeyPressed(GLFW_KEY_W)) { t.position += forward * speed; }
if (window.IsKeyPressed(GLFW_KEY_S)) { t.position -= forward * speed; dirty = true; } if (window.IsKeyPressed(GLFW_KEY_S)) { t.position -= forward * speed; }
if (window.IsKeyPressed(GLFW_KEY_A)) { t.position -= right * speed; dirty = true; } if (window.IsKeyPressed(GLFW_KEY_A)) { t.position -= right * speed; }
if (window.IsKeyPressed(GLFW_KEY_D)) { t.position += right * speed; dirty = true; } if (window.IsKeyPressed(GLFW_KEY_D)) { t.position += right * speed; }
if (window.IsKeyPressed(GLFW_KEY_SPACE)) { t.position.y += speed; dirty = true; } if (window.IsKeyPressed(GLFW_KEY_SPACE)) { t.position.y += speed; }
if (window.IsKeyPressed(GLFW_KEY_LEFT_SHIFT)) { t.position.y -= speed; dirty = true; } if (window.IsKeyPressed(GLFW_KEY_LEFT_SHIFT)) { t.position.y -= speed; }
// 世界矩阵供 GetView(inverse)与渲染消费
if (dirty) t.Compose();
} }
glm::mat4 Camera::GetView(const ECS::TransformComponent& t) const glm::mat4 Camera::GetView(const ECS::TransformComponent& t) const