Formalize final color policy resolution
This commit is contained in:
374
docs/plan/Renderer下一阶段_FinalColorPipeline正式化计划_2026-04-06.md
Normal file
374
docs/plan/Renderer下一阶段_FinalColorPipeline正式化计划_2026-04-06.md
Normal file
@@ -0,0 +1,374 @@
|
||||
# Renderer 下一阶段: FinalColorPipeline 正式化计划
|
||||
|
||||
日期: `2026-04-06`
|
||||
|
||||
## 1. 阶段定位
|
||||
|
||||
上一阶段已经完成:
|
||||
|
||||
- `CameraPostProcess` 描述层正式化
|
||||
- `SceneRenderer` 从相机描述层自动构建 post-process request
|
||||
- manual path 与 formal scene path 都已测试闭环
|
||||
|
||||
下一阶段不再讨论“要不要继续往引擎里内置几个后处理特效”。
|
||||
|
||||
下一阶段真正要做的是:
|
||||
|
||||
- 把 `Final Color Pipeline` 作为 renderer 主链的正式组成部分建立起来
|
||||
- 并且从架构上明确对齐 Unity 风格的 SRP 演进方向
|
||||
|
||||
也就是把当前链路从:
|
||||
|
||||
`SceneColor -> CameraPostProcessStack -> Surface`
|
||||
|
||||
推进到:
|
||||
|
||||
`SceneColor -> CameraPostProcessStack -> FinalColorPipeline -> Output Surface`
|
||||
|
||||
其中:
|
||||
|
||||
- `CameraPostProcessStack` 是相机上的可扩展全屏效果链
|
||||
- `FinalColorPipeline` 是 renderer 内置的最终出图规则
|
||||
|
||||
二者不是同一个概念。
|
||||
|
||||
## 2. 与 Unity/SRP 对齐的核心结论
|
||||
|
||||
如果目标是后续在这套 rendering 之上做 Unity 风格的 `SRP`,那这一步必须遵守下面这条主线:
|
||||
|
||||
### 2.1 FinalColor 不能只挂在 Camera 上
|
||||
|
||||
Unity 风格的渲染架构里,最终出图规则不应该被设计成“Camera 私有特效配置”。
|
||||
|
||||
更合理的来源解析链应该是:
|
||||
|
||||
`Pipeline/Renderer defaults -> Camera override -> future Volume override -> resolved per-camera final color policy`
|
||||
|
||||
也就是说:
|
||||
|
||||
- 默认值来自 pipeline/renderer 级配置
|
||||
- 相机只做少量 override
|
||||
- 以后 volume 再覆盖部分结果
|
||||
- renderer 最终对每个 camera 解析出一份正式的 final color policy
|
||||
|
||||
### 2.2 FinalColor 属于 renderer 主链,不属于任意 post-process stack
|
||||
|
||||
下面这些应被视为 renderer 正式内置能力:
|
||||
|
||||
- linear -> sRGB output transfer
|
||||
- exposure
|
||||
- tone mapping
|
||||
- final color transform
|
||||
|
||||
而不是被混成“任意全屏效果链”里的普通 pass。
|
||||
|
||||
### 2.3 必须提前预留 SRP pass injection 点
|
||||
|
||||
如果以后要做 Unity 风格 SRP / RendererFeature / 自定义 pass:
|
||||
|
||||
- 现在就不能把 final color policy 写成一堆散落在 `SceneRenderer` 里的硬编码分支
|
||||
- 必须明确 runtime 主链阶段
|
||||
- 必须给 future renderer feature / custom fullscreen pass 预留稳定插入点
|
||||
|
||||
## 3. 当前问题
|
||||
|
||||
当前 renderer 虽然已经具备:
|
||||
|
||||
- forward runtime scene renderer
|
||||
- shadow / skybox / multi-light
|
||||
- camera post-process 描述层
|
||||
- formal render request / final output stage 框架
|
||||
|
||||
但距离 SRP-ready 还差下面这些正式收口:
|
||||
|
||||
1. `FinalOutput` 还没有真正变成正式的 final color 执行层
|
||||
2. `FinalColorPolicy` 还没有独立于 `CameraPostProcessStack`
|
||||
3. pipeline 默认值、camera override、future volume override 还没有分层
|
||||
4. 最终出图的颜色空间 contract 还没有制度化
|
||||
5. 还没有给 future `RendererFeature / Pass injection` 明确主链挂点
|
||||
|
||||
## 4. 本阶段目标
|
||||
|
||||
本阶段完成后,应达到:
|
||||
|
||||
1. 引入正式的 `FinalColorSettings / FinalColorPolicy`
|
||||
2. 形成明确的数据来源链:
|
||||
- pipeline/renderer defaults
|
||||
- camera override
|
||||
- reserved future volume override
|
||||
3. `SceneRenderer` 负责解析每个 camera 的最终 final color policy
|
||||
4. `CameraRenderer` 的 `FinalOutput` stage 正式承担最终出图职责
|
||||
5. `CameraPostProcessStack` 与 `FinalColorPipeline` 完全分层
|
||||
6. 为 future `RendererFeature / Pass injection` 预留清晰的阶段和 contract
|
||||
7. 三后端对 final color path 有稳定测试覆盖
|
||||
|
||||
## 5. 非目标
|
||||
|
||||
本阶段明确不做:
|
||||
|
||||
- bloom
|
||||
- SSAO
|
||||
- DOF
|
||||
- motion blur
|
||||
- 完整 LUT color grading 套件
|
||||
- volume 系统完整落地
|
||||
- renderer feature 全量系统
|
||||
- render graph
|
||||
- editor-only 特效
|
||||
|
||||
本阶段的重点是:
|
||||
|
||||
- 把最终出图主链做正式
|
||||
|
||||
而不是:
|
||||
|
||||
- 堆更多视觉效果
|
||||
|
||||
## 6. 设计原则
|
||||
|
||||
### 6.1 三层来源解析
|
||||
|
||||
第一层: `Pipeline / Renderer defaults`
|
||||
|
||||
这里放全局默认 final color 行为,例如:
|
||||
|
||||
- output transfer mode
|
||||
- default exposure mode/value
|
||||
- default tone mapping mode
|
||||
- 是否启用 final color stage
|
||||
|
||||
第二层: `Camera override`
|
||||
|
||||
相机只做少量覆盖,例如:
|
||||
|
||||
- 是否允许 post-process/final color
|
||||
- override exposure
|
||||
- override final color transform
|
||||
- 指定 renderer / pipeline variant
|
||||
|
||||
第三层: `Future Volume override`
|
||||
|
||||
当前阶段只预留 contract,不完整落地,但必须明确将来会从这里覆写:
|
||||
|
||||
- exposure
|
||||
- tone mapping
|
||||
- color adjustments
|
||||
|
||||
最终由 renderer 解析出:
|
||||
|
||||
- `ResolvedFinalColorPolicy`
|
||||
|
||||
### 6.2 PostProcess 与 FinalColor 必须分层
|
||||
|
||||
约定如下:
|
||||
|
||||
#### `CameraPostProcessStack`
|
||||
|
||||
职责:
|
||||
|
||||
- 相机上的可扩展 fullscreen effect chain
|
||||
- 偏向“特效层”与“自定义效果层”
|
||||
|
||||
例子:
|
||||
|
||||
- color scale
|
||||
- 以后可能的 edge effect / custom fullscreen effect
|
||||
|
||||
#### `FinalColorPipeline`
|
||||
|
||||
职责:
|
||||
|
||||
- renderer 正式最终出图
|
||||
- 负责把线性 scene color 变成最终显示输出
|
||||
|
||||
例子:
|
||||
|
||||
- output transfer
|
||||
- exposure
|
||||
- tone mapping
|
||||
- final color transform
|
||||
|
||||
### 6.3 FinalOutput 是正式主链阶段
|
||||
|
||||
这一阶段完成后,需要把 `FinalOutput` 明确成正式 runtime stage,而不是“名义上存在、实际上只留空接口”的阶段。
|
||||
|
||||
主链应明确为:
|
||||
|
||||
`MainScene -> CameraPostProcess -> FinalColor -> Output`
|
||||
|
||||
### 6.4 先做 contract,再做更多效果
|
||||
|
||||
第一版不追求效果数量,先把下面这些做对:
|
||||
|
||||
- 数据模型
|
||||
- 来源解析
|
||||
- 执行阶段
|
||||
- 三后端一致性
|
||||
|
||||
## 7. 核心方案
|
||||
|
||||
### 7.1 引入 FinalColorSettings / ResolvedFinalColorPolicy
|
||||
|
||||
建议引入两层数据:
|
||||
|
||||
#### 配置层
|
||||
|
||||
- `FinalColorSettings`
|
||||
|
||||
来源:
|
||||
|
||||
- pipeline defaults
|
||||
- camera override
|
||||
- future volume override
|
||||
|
||||
#### 执行层
|
||||
|
||||
- `ResolvedFinalColorPolicy`
|
||||
|
||||
用途:
|
||||
|
||||
- renderer 在本帧、对这个 camera 最终真正执行的 final color 规则
|
||||
|
||||
### 7.2 第一版字段建议
|
||||
|
||||
第一版保持克制,建议只引入最小必要字段:
|
||||
|
||||
- output transfer mode
|
||||
- disabled
|
||||
- linear to sRGB
|
||||
- exposure mode
|
||||
- disabled
|
||||
- fixed exposure
|
||||
- exposure value
|
||||
- tone mapping mode
|
||||
- disabled
|
||||
- reserved future enum
|
||||
- final color scale
|
||||
|
||||
注意:
|
||||
|
||||
- tone mapping mode 可以先留枚举和数据入口
|
||||
- 第一阶段不要求完整 tone mapping 实装
|
||||
|
||||
### 7.3 将 FinalOutput 接入正式工厂化执行
|
||||
|
||||
当前已有 post-process 的描述层和工厂翻译思路。
|
||||
|
||||
这一阶段要把同样的正式化思路用于 `FinalOutput`:
|
||||
|
||||
- `SceneRenderer` 负责根据 resolved final color policy 构建 final output request
|
||||
- `CameraRenderer` 负责执行 final output stage
|
||||
- final output 相关 pass 不再靠零散 if 分支拼装
|
||||
|
||||
### 7.4 预留 future RendererFeature / Pass injection 点
|
||||
|
||||
这一步必须把下面这些插入点概念固定下来:
|
||||
|
||||
- before main scene
|
||||
- after main scene
|
||||
- after camera post-process
|
||||
- before final output
|
||||
- after final output / before editor composite
|
||||
|
||||
当前阶段不要求把完整 feature 系统实现完,但必须在 plan 和 runtime stage 语义上先定好位置。
|
||||
|
||||
## 8. 执行阶段
|
||||
|
||||
### Phase A: SRP-ready FinalColor contract
|
||||
|
||||
目标:
|
||||
|
||||
- 定义 `FinalColorSettings`
|
||||
- 定义 `ResolvedFinalColorPolicy`
|
||||
- 明确三层来源解析
|
||||
|
||||
完成标准:
|
||||
|
||||
- pipeline defaults / camera override / future volume override 的职责写清楚
|
||||
- 不破坏现有 `CameraPostProcess` 主链
|
||||
|
||||
### Phase B: FinalOutput 正式执行层
|
||||
|
||||
目标:
|
||||
|
||||
- 让 `FinalOutput` 成为正式执行阶段
|
||||
|
||||
完成标准:
|
||||
|
||||
- `SceneRenderer` 能自动附加 final output request
|
||||
- `CameraRenderer` 走正式 final-output chain
|
||||
- 不再依赖零散条件分支
|
||||
|
||||
### Phase C: 最小 builtin final color 闭环
|
||||
|
||||
目标:
|
||||
|
||||
- 用最小 final color policy 验证 contract
|
||||
|
||||
建议首选:
|
||||
|
||||
- `LinearToSRGB / OutputTransfer`
|
||||
|
||||
可选再加:
|
||||
|
||||
- fixed exposure
|
||||
- final color scale
|
||||
|
||||
完成标准:
|
||||
|
||||
- 三后端输出一致
|
||||
- 与已有 `camera_post_process_scene` / `post_process_scene` 不冲突
|
||||
|
||||
### Phase D: 测试与文档收口
|
||||
|
||||
目标:
|
||||
|
||||
- 补足 unit / integration coverage
|
||||
- 把这一步与 future SRP 承接关系写回文档
|
||||
|
||||
完成标准:
|
||||
|
||||
- 计划可归档到 `docs/used`
|
||||
|
||||
## 9. 测试策略
|
||||
|
||||
至少覆盖:
|
||||
|
||||
- `components_tests`
|
||||
- `rendering_unit_tests`
|
||||
- 一条新的 `final_color_scene` 集成测试
|
||||
- `camera_post_process_scene`
|
||||
- `post_process_scene`
|
||||
- 必要时 `skybox_scene`
|
||||
|
||||
新的 `final_color_scene` 必须验证:
|
||||
|
||||
- `FinalOutput` stage 真的参与了最终出图
|
||||
- 输出 transfer / exposure 等策略真的生效
|
||||
- D3D12 / OpenGL / Vulkan 三后端一致
|
||||
|
||||
## 10. 收口判定
|
||||
|
||||
满足下面条件时,本阶段可视为完成:
|
||||
|
||||
1. `FinalColorSettings / ResolvedFinalColorPolicy` 已正式建模
|
||||
2. `FinalOutput` 已成为正式 runtime stage
|
||||
3. `CameraPostProcessStack` 与 `FinalColorPipeline` 已彻底分层
|
||||
4. pipeline defaults / camera override / future volume override 的职责边界已写死
|
||||
5. future renderer feature / pass injection 点已在架构上预留
|
||||
6. 至少一条正式 final color path 完成三后端闭环
|
||||
7. 现有 rendering 主线测试不回退
|
||||
|
||||
## 11. 本阶段之后再做什么
|
||||
|
||||
等这一步完成后,后续 SRP 方向的推进顺序才合理:
|
||||
|
||||
1. 扩更多 builtin final color policy
|
||||
2. 再做 renderer feature / custom fullscreen pass
|
||||
3. 再做 volume 系统正式接入
|
||||
4. 再做 C# 层对 pipeline/camera/feature 的承接
|
||||
|
||||
也就是说:
|
||||
|
||||
如果你的目标是最终做 Unity 风格 SRP,
|
||||
那这一阶段就是把 renderer 从“能出图”推进到“能作为 SRP 底座”的关键收口阶段。
|
||||
@@ -0,0 +1,127 @@
|
||||
# Renderer 下一阶段: Camera PostProcess 描述层正式化计划
|
||||
|
||||
日期: `2026-04-06`
|
||||
|
||||
## 1. 阶段目标
|
||||
|
||||
在已经完成 runtime `SceneColor -> PostProcess -> FinalOutput` 主链收口的基础上,
|
||||
把相机上的后处理配置从临时的 `ColorScale` 专用字段,提升为正式的描述层。
|
||||
|
||||
这一阶段只解决一件事:
|
||||
|
||||
- `CameraComponent` 持有正式的 post-process pass 描述
|
||||
- `SceneRenderer` 通过工厂把描述翻译成 runtime pass sequence
|
||||
- 旧的 `ColorScale` 接口继续兼容,旧场景序列化继续可读
|
||||
|
||||
## 2. 已完成内容
|
||||
|
||||
### 2.1 相机后处理描述层
|
||||
|
||||
已新增:
|
||||
|
||||
- `CameraPostProcessPassType`
|
||||
- `CameraPostProcessPassDesc`
|
||||
- `CameraPostProcessStack`
|
||||
|
||||
当前首个正式 builtin effect 仍然是:
|
||||
|
||||
- `ColorScale`
|
||||
|
||||
但它已经不再通过 `CameraComponent -> vector<Vector4> -> SceneRenderer if/for` 这种硬编码链路传递。
|
||||
|
||||
### 2.2 CameraComponent 正式化
|
||||
|
||||
已完成:
|
||||
|
||||
- `CameraComponent` 内部存储迁移到 `CameraPostProcessStack`
|
||||
- 新增通用接口:
|
||||
- `GetPostProcessPasses`
|
||||
- `SetPostProcessPasses`
|
||||
- `AddPostProcessPass`
|
||||
- `ClearPostProcessPasses`
|
||||
- 旧接口继续保留:
|
||||
- `SetColorScalePostProcessEnabled`
|
||||
- `SetColorScalePostProcessScale`
|
||||
- `SetColorScalePostProcessPasses`
|
||||
- `GetColorScalePostProcessPasses`
|
||||
|
||||
兼容规则:
|
||||
|
||||
- 旧接口继续映射到 `ColorScale` 类型的描述
|
||||
- 新格式序列化写出正式 `postProcessPassN*` 字段
|
||||
- 反序列化同时兼容旧的 `colorScalePostProcess*` 字段
|
||||
|
||||
### 2.3 SceneRenderer 工厂翻译层
|
||||
|
||||
已完成:
|
||||
|
||||
- 从 `SceneRenderer` 中移除 `ColorScale` 专用硬编码构造
|
||||
- 新增 `BuildCameraPostProcessPassSequence(...)`
|
||||
- `SceneRenderer` 现在只关心:
|
||||
- 读取相机的 `CameraPostProcessStack`
|
||||
- 为 post-process 分配 source surface
|
||||
- 调用工厂生成 `RenderPassSequence`
|
||||
|
||||
这意味着后续增加新的 builtin camera post-process 时,
|
||||
修改点已经收缩到描述层和工厂,而不是继续把分支堆进 `SceneRenderer`。
|
||||
|
||||
## 3. 验证结果
|
||||
|
||||
### 3.1 构建
|
||||
|
||||
已通过:
|
||||
|
||||
- `cmake --build --preset debug --target components_tests rendering_unit_tests rendering_integration_camera_post_process_scene rendering_integration_post_process_scene -- /m:1`
|
||||
|
||||
### 3.2 单元测试
|
||||
|
||||
已通过:
|
||||
|
||||
- `components_tests --gtest_filter=CameraComponent_Test.*`
|
||||
- `rendering_unit_tests --gtest_filter=SceneRenderer_Test.*:CameraRenderer_Test.*`
|
||||
|
||||
覆盖点包括:
|
||||
|
||||
- 新描述层 round-trip
|
||||
- 旧 `ColorScale` 字段反序列化兼容
|
||||
- `SceneRenderer` 从 camera post-process stack 构建正式 request
|
||||
|
||||
### 3.3 集成测试
|
||||
|
||||
已通过:
|
||||
|
||||
- `rendering_integration_camera_post_process_scene`
|
||||
- D3D12
|
||||
- OpenGL
|
||||
- Vulkan
|
||||
- `rendering_integration_post_process_scene`
|
||||
- D3D12
|
||||
- OpenGL
|
||||
- Vulkan
|
||||
|
||||
说明:
|
||||
|
||||
- `camera_post_process_scene` 验证 formal scene path
|
||||
- `post_process_scene` 保留为 manual / low-level multi-pass 覆盖
|
||||
|
||||
## 4. 当前结论
|
||||
|
||||
这一阶段已经完成。
|
||||
|
||||
当前 renderer 在 camera post-process 这一层,已经从“单个效果的临时接线”升级为“正式描述 + 工厂翻译”的结构。
|
||||
|
||||
但现阶段仍然有两个边界:
|
||||
|
||||
- 描述层虽然正式化了,但当前 builtin effect 仍只有 `ColorScale`
|
||||
- editor / asset / material 侧还没有把 camera post-process 做成完整的资产化与配置面板体系
|
||||
|
||||
## 5. 下一步建议
|
||||
|
||||
下一步不要回头继续往 `SceneRenderer` 里塞特判,而应沿着下面的方向推进:
|
||||
|
||||
1. 继续扩展 `CameraPostProcessPassDesc`
|
||||
- 例如 tone mapping / exposure / gamma / final color transform
|
||||
2. 让新的 builtin camera effect 继续只经过“描述层 + 工厂”
|
||||
3. 等 shader/material 主线更稳定后,再决定 camera post-process 的资产化形式和 editor 配置入口
|
||||
|
||||
本阶段的核心收口标准已经满足,可以提交归档。
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <XCEngine/Core/Math/Rect.h>
|
||||
#include <XCEngine/Core/Math/Vector4.h>
|
||||
#include <XCEngine/Rendering/Planning/CameraPostProcessDesc.h>
|
||||
#include <XCEngine/Rendering/Planning/FinalColorSettings.h>
|
||||
#include <XCEngine/Resources/Material/Material.h>
|
||||
|
||||
#include <string>
|
||||
@@ -92,6 +93,10 @@ public:
|
||||
const Math::Color& GetSkyboxBottomColor() const { return m_skyboxBottomColor; }
|
||||
void SetSkyboxBottomColor(const Math::Color& value) { m_skyboxBottomColor = value; }
|
||||
|
||||
const Rendering::FinalColorOverrideSettings& GetFinalColorOverrides() const { return m_finalColorOverrides; }
|
||||
void SetFinalColorOverrides(const Rendering::FinalColorOverrideSettings& value) { m_finalColorOverrides = value; }
|
||||
void ClearFinalColorOverrides() { m_finalColorOverrides = {}; }
|
||||
|
||||
const Rendering::CameraPostProcessStack& GetPostProcessPasses() const { return m_postProcessPasses; }
|
||||
void SetPostProcessPasses(const Rendering::CameraPostProcessStack& values);
|
||||
void AddPostProcessPass(const Rendering::CameraPostProcessPassDesc& value);
|
||||
@@ -131,6 +136,7 @@ private:
|
||||
Math::Color m_skyboxTopColor = Math::Color(0.18f, 0.36f, 0.74f, 1.0f);
|
||||
Math::Color m_skyboxHorizonColor = Math::Color(0.78f, 0.84f, 0.92f, 1.0f);
|
||||
Math::Color m_skyboxBottomColor = Math::Color(0.92f, 0.93f, 0.95f, 1.0f);
|
||||
Rendering::FinalColorOverrideSettings m_finalColorOverrides = {};
|
||||
Math::Vector4 m_colorScalePostProcessDefaultScale = Math::Vector4::One();
|
||||
Rendering::CameraPostProcessStack m_postProcessPasses;
|
||||
};
|
||||
|
||||
@@ -46,6 +46,8 @@ public:
|
||||
|
||||
private:
|
||||
void PrepareOwnedCameraPostProcessState(size_t requestCount);
|
||||
void ResolveCameraFinalColorPolicies(
|
||||
std::vector<CameraRenderRequest>& requests) const;
|
||||
void AttachCameraPostProcessRequests(
|
||||
const RenderContext& context,
|
||||
std::vector<CameraRenderRequest>& requests);
|
||||
|
||||
@@ -304,6 +304,7 @@ private:
|
||||
class BuiltinForwardPipelineAsset final : public RenderPipelineAsset {
|
||||
public:
|
||||
std::unique_ptr<RenderPipeline> CreatePipeline() const override;
|
||||
FinalColorSettings GetDefaultFinalColorSettings() const override { return {}; }
|
||||
};
|
||||
|
||||
} // namespace Pipelines
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Rendering/FrameData/RenderCameraData.h>
|
||||
#include <XCEngine/Rendering/Planning/FinalColorSettings.h>
|
||||
#include <XCEngine/Rendering/RenderContext.h>
|
||||
#include <XCEngine/Rendering/RenderPass.h>
|
||||
#include <XCEngine/Rendering/RenderSurface.h>
|
||||
@@ -184,6 +185,7 @@ struct CameraRenderRequest {
|
||||
DirectionalShadowRenderPlan directionalShadow;
|
||||
PostProcessRenderRequest postProcess;
|
||||
FinalOutputRenderRequest finalOutput;
|
||||
ResolvedFinalColorPolicy finalColorPolicy = {};
|
||||
ObjectIdRenderRequest objectId;
|
||||
float cameraDepth = 0.0f;
|
||||
uint8_t cameraStackOrder = 0;
|
||||
|
||||
122
engine/include/XCEngine/Rendering/Planning/FinalColorSettings.h
Normal file
122
engine/include/XCEngine/Rendering/Planning/FinalColorSettings.h
Normal file
@@ -0,0 +1,122 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Core/Math/Vector4.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Rendering {
|
||||
|
||||
enum class FinalColorOutputTransferMode : uint8_t {
|
||||
Disabled = 0,
|
||||
LinearToSRGB
|
||||
};
|
||||
|
||||
enum class FinalColorExposureMode : uint8_t {
|
||||
Disabled = 0,
|
||||
Fixed
|
||||
};
|
||||
|
||||
enum class FinalColorToneMappingMode : uint8_t {
|
||||
Disabled = 0,
|
||||
Neutral,
|
||||
ACES
|
||||
};
|
||||
|
||||
struct FinalColorSettings {
|
||||
FinalColorOutputTransferMode outputTransferMode = FinalColorOutputTransferMode::Disabled;
|
||||
FinalColorExposureMode exposureMode = FinalColorExposureMode::Disabled;
|
||||
float exposureValue = 1.0f;
|
||||
FinalColorToneMappingMode toneMappingMode = FinalColorToneMappingMode::Disabled;
|
||||
Math::Vector4 finalColorScale = Math::Vector4::One();
|
||||
|
||||
bool RequiresProcessing() const {
|
||||
return outputTransferMode != FinalColorOutputTransferMode::Disabled ||
|
||||
exposureMode != FinalColorExposureMode::Disabled ||
|
||||
toneMappingMode != FinalColorToneMappingMode::Disabled ||
|
||||
finalColorScale != Math::Vector4::One();
|
||||
}
|
||||
};
|
||||
|
||||
struct FinalColorOverrideSettings {
|
||||
bool overrideOutputTransferMode = false;
|
||||
FinalColorOutputTransferMode outputTransferMode = FinalColorOutputTransferMode::Disabled;
|
||||
|
||||
bool overrideExposureMode = false;
|
||||
FinalColorExposureMode exposureMode = FinalColorExposureMode::Disabled;
|
||||
|
||||
bool overrideExposureValue = false;
|
||||
float exposureValue = 1.0f;
|
||||
|
||||
bool overrideToneMappingMode = false;
|
||||
FinalColorToneMappingMode toneMappingMode = FinalColorToneMappingMode::Disabled;
|
||||
|
||||
bool overrideFinalColorScale = false;
|
||||
Math::Vector4 finalColorScale = Math::Vector4::One();
|
||||
|
||||
bool HasOverrides() const {
|
||||
return overrideOutputTransferMode ||
|
||||
overrideExposureMode ||
|
||||
overrideExposureValue ||
|
||||
overrideToneMappingMode ||
|
||||
overrideFinalColorScale;
|
||||
}
|
||||
};
|
||||
|
||||
struct ResolvedFinalColorPolicy : FinalColorSettings {
|
||||
bool hasPipelineDefaults = false;
|
||||
bool hasCameraOverrides = false;
|
||||
bool hasVolumeOverrides = false;
|
||||
};
|
||||
|
||||
inline void ApplyFinalColorOverrides(
|
||||
const FinalColorOverrideSettings& overrides,
|
||||
FinalColorSettings& settings) {
|
||||
if (overrides.overrideOutputTransferMode) {
|
||||
settings.outputTransferMode = overrides.outputTransferMode;
|
||||
}
|
||||
|
||||
if (overrides.overrideExposureMode) {
|
||||
settings.exposureMode = overrides.exposureMode;
|
||||
}
|
||||
|
||||
if (overrides.overrideExposureValue) {
|
||||
settings.exposureValue = overrides.exposureValue;
|
||||
}
|
||||
|
||||
if (overrides.overrideToneMappingMode) {
|
||||
settings.toneMappingMode = overrides.toneMappingMode;
|
||||
}
|
||||
|
||||
if (overrides.overrideFinalColorScale) {
|
||||
settings.finalColorScale = overrides.finalColorScale;
|
||||
}
|
||||
}
|
||||
|
||||
inline ResolvedFinalColorPolicy ResolveFinalColorPolicy(
|
||||
const FinalColorSettings& pipelineDefaults,
|
||||
const FinalColorOverrideSettings* cameraOverrides = nullptr,
|
||||
const FinalColorOverrideSettings* volumeOverrides = nullptr) {
|
||||
ResolvedFinalColorPolicy resolved = {};
|
||||
resolved.outputTransferMode = pipelineDefaults.outputTransferMode;
|
||||
resolved.exposureMode = pipelineDefaults.exposureMode;
|
||||
resolved.exposureValue = pipelineDefaults.exposureValue;
|
||||
resolved.toneMappingMode = pipelineDefaults.toneMappingMode;
|
||||
resolved.finalColorScale = pipelineDefaults.finalColorScale;
|
||||
resolved.hasPipelineDefaults = true;
|
||||
|
||||
if (cameraOverrides != nullptr && cameraOverrides->HasOverrides()) {
|
||||
ApplyFinalColorOverrides(*cameraOverrides, resolved);
|
||||
resolved.hasCameraOverrides = true;
|
||||
}
|
||||
|
||||
if (volumeOverrides != nullptr && volumeOverrides->HasOverrides()) {
|
||||
ApplyFinalColorOverrides(*volumeOverrides, resolved);
|
||||
resolved.hasVolumeOverrides = true;
|
||||
}
|
||||
|
||||
return resolved;
|
||||
}
|
||||
|
||||
} // namespace Rendering
|
||||
} // namespace XCEngine
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Rendering/Planning/FinalColorSettings.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace XCEngine {
|
||||
@@ -12,6 +14,7 @@ public:
|
||||
virtual ~RenderPipelineAsset() = default;
|
||||
|
||||
virtual std::unique_ptr<RenderPipeline> CreatePipeline() const = 0;
|
||||
virtual FinalColorSettings GetDefaultFinalColorSettings() const { return {}; }
|
||||
};
|
||||
|
||||
} // namespace Rendering
|
||||
|
||||
@@ -276,6 +276,16 @@ void CameraComponent::Serialize(std::ostream& os) const {
|
||||
os << "skyboxTopColor=" << m_skyboxTopColor.r << "," << m_skyboxTopColor.g << "," << m_skyboxTopColor.b << "," << m_skyboxTopColor.a << ";";
|
||||
os << "skyboxHorizonColor=" << m_skyboxHorizonColor.r << "," << m_skyboxHorizonColor.g << "," << m_skyboxHorizonColor.b << "," << m_skyboxHorizonColor.a << ";";
|
||||
os << "skyboxBottomColor=" << m_skyboxBottomColor.r << "," << m_skyboxBottomColor.g << "," << m_skyboxBottomColor.b << "," << m_skyboxBottomColor.a << ";";
|
||||
os << "finalColorOverrideOutputTransferEnabled=" << (m_finalColorOverrides.overrideOutputTransferMode ? 1 : 0) << ";";
|
||||
os << "finalColorOverrideOutputTransferMode=" << static_cast<int>(m_finalColorOverrides.outputTransferMode) << ";";
|
||||
os << "finalColorOverrideExposureModeEnabled=" << (m_finalColorOverrides.overrideExposureMode ? 1 : 0) << ";";
|
||||
os << "finalColorOverrideExposureMode=" << static_cast<int>(m_finalColorOverrides.exposureMode) << ";";
|
||||
os << "finalColorOverrideExposureValueEnabled=" << (m_finalColorOverrides.overrideExposureValue ? 1 : 0) << ";";
|
||||
os << "finalColorOverrideExposureValue=" << m_finalColorOverrides.exposureValue << ";";
|
||||
os << "finalColorOverrideToneMappingModeEnabled=" << (m_finalColorOverrides.overrideToneMappingMode ? 1 : 0) << ";";
|
||||
os << "finalColorOverrideToneMappingMode=" << static_cast<int>(m_finalColorOverrides.toneMappingMode) << ";";
|
||||
os << "finalColorOverrideScaleEnabled=" << (m_finalColorOverrides.overrideFinalColorScale ? 1 : 0) << ";";
|
||||
os << "finalColorOverrideScale=" << EncodeVector4(m_finalColorOverrides.finalColorScale) << ";";
|
||||
os << "postProcessPassCount=" << m_postProcessPasses.size() << ";";
|
||||
for (size_t index = 0; index < m_postProcessPasses.size(); ++index) {
|
||||
const Rendering::CameraPostProcessPassDesc& pass = m_postProcessPasses[index];
|
||||
@@ -295,6 +305,7 @@ void CameraComponent::Deserialize(std::istream& is) {
|
||||
m_skyboxMaterial.Reset();
|
||||
m_skyboxMaterialPath.clear();
|
||||
m_skyboxMaterialRef.Reset();
|
||||
m_finalColorOverrides = {};
|
||||
m_colorScalePostProcessDefaultScale = Math::Vector4::One();
|
||||
m_postProcessPasses.clear();
|
||||
|
||||
@@ -367,6 +378,29 @@ void CameraComponent::Deserialize(std::istream& is) {
|
||||
std::replace(value.begin(), value.end(), ',', ' ');
|
||||
std::istringstream ss(value);
|
||||
ss >> m_skyboxBottomColor.r >> m_skyboxBottomColor.g >> m_skyboxBottomColor.b >> m_skyboxBottomColor.a;
|
||||
} else if (key == "finalColorOverrideOutputTransferEnabled") {
|
||||
m_finalColorOverrides.overrideOutputTransferMode = (std::stoi(value) != 0);
|
||||
} else if (key == "finalColorOverrideOutputTransferMode") {
|
||||
m_finalColorOverrides.outputTransferMode =
|
||||
static_cast<Rendering::FinalColorOutputTransferMode>(std::stoi(value));
|
||||
} else if (key == "finalColorOverrideExposureModeEnabled") {
|
||||
m_finalColorOverrides.overrideExposureMode = (std::stoi(value) != 0);
|
||||
} else if (key == "finalColorOverrideExposureMode") {
|
||||
m_finalColorOverrides.exposureMode =
|
||||
static_cast<Rendering::FinalColorExposureMode>(std::stoi(value));
|
||||
} else if (key == "finalColorOverrideExposureValueEnabled") {
|
||||
m_finalColorOverrides.overrideExposureValue = (std::stoi(value) != 0);
|
||||
} else if (key == "finalColorOverrideExposureValue") {
|
||||
m_finalColorOverrides.exposureValue = std::stof(value);
|
||||
} else if (key == "finalColorOverrideToneMappingModeEnabled") {
|
||||
m_finalColorOverrides.overrideToneMappingMode = (std::stoi(value) != 0);
|
||||
} else if (key == "finalColorOverrideToneMappingMode") {
|
||||
m_finalColorOverrides.toneMappingMode =
|
||||
static_cast<Rendering::FinalColorToneMappingMode>(std::stoi(value));
|
||||
} else if (key == "finalColorOverrideScaleEnabled") {
|
||||
m_finalColorOverrides.overrideFinalColorScale = (std::stoi(value) != 0);
|
||||
} else if (key == "finalColorOverrideScale") {
|
||||
TryParseVector4(value, m_finalColorOverrides.finalColorScale);
|
||||
} else if (key == "postProcessPassCount") {
|
||||
postProcessPassCount = static_cast<size_t>(std::stoul(value));
|
||||
deserializedPostProcessPasses.clear();
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "Rendering/Caches/FullscreenPassSurfaceCache.h"
|
||||
#include "Rendering/Planning/CameraPostProcessPassFactory.h"
|
||||
#include "Rendering/Planning/SceneRenderRequestUtils.h"
|
||||
#include "Rendering/RenderPipelineAsset.h"
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Rendering {
|
||||
@@ -35,6 +36,7 @@ std::vector<CameraRenderRequest> SceneRenderer::BuildRenderRequests(
|
||||
const RenderSurface& surface) {
|
||||
std::vector<CameraRenderRequest> requests =
|
||||
m_requestPlanner.BuildRequests(scene, overrideCamera, context, surface);
|
||||
ResolveCameraFinalColorPolicies(requests);
|
||||
AttachCameraPostProcessRequests(context, requests);
|
||||
return requests;
|
||||
}
|
||||
@@ -91,6 +93,24 @@ void SceneRenderer::PrepareOwnedCameraPostProcessState(size_t requestCount) {
|
||||
}
|
||||
}
|
||||
|
||||
void SceneRenderer::ResolveCameraFinalColorPolicies(
|
||||
std::vector<CameraRenderRequest>& requests) const {
|
||||
const RenderPipelineAsset* pipelineAsset = GetPipelineAsset();
|
||||
const FinalColorSettings pipelineDefaults =
|
||||
pipelineAsset != nullptr ? pipelineAsset->GetDefaultFinalColorSettings() : FinalColorSettings();
|
||||
|
||||
for (CameraRenderRequest& request : requests) {
|
||||
if (request.camera == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
request.finalColorPolicy = ResolveFinalColorPolicy(
|
||||
pipelineDefaults,
|
||||
&request.camera->GetFinalColorOverrides(),
|
||||
nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void SceneRenderer::AttachCameraPostProcessRequests(
|
||||
const RenderContext& context,
|
||||
std::vector<CameraRenderRequest>& requests) {
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
#include <XCEngine/Components/CameraComponent.h>
|
||||
#include <XCEngine/Components/LightComponent.h>
|
||||
#include <XCEngine/Resources/BuiltinResources.h>
|
||||
|
||||
#include <sstream>
|
||||
|
||||
using namespace XCEngine::Components;
|
||||
@@ -33,6 +31,7 @@ TEST(CameraComponent_Test, DefaultValues) {
|
||||
EXPECT_FLOAT_EQ(camera.GetSkyboxHorizonColor().g, 0.84f);
|
||||
EXPECT_FLOAT_EQ(camera.GetSkyboxBottomColor().b, 0.95f);
|
||||
EXPECT_TRUE(camera.GetPostProcessPasses().empty());
|
||||
EXPECT_FALSE(camera.GetFinalColorOverrides().HasOverrides());
|
||||
EXPECT_FALSE(camera.IsColorScalePostProcessEnabled());
|
||||
EXPECT_FLOAT_EQ(camera.GetColorScalePostProcessScale().x, 1.0f);
|
||||
EXPECT_FLOAT_EQ(camera.GetColorScalePostProcessScale().y, 1.0f);
|
||||
@@ -72,10 +71,20 @@ TEST(CameraComponent_Test, SerializeRoundTripPreservesViewportAndClearState) {
|
||||
source.SetCullingMask(0x0000000Fu);
|
||||
source.SetViewportRect(XCEngine::Math::Rect(0.25f, 0.125f, 0.5f, 0.625f));
|
||||
source.SetSkyboxEnabled(true);
|
||||
source.SetSkyboxMaterialPath(XCEngine::Resources::GetBuiltinDefaultPrimitiveMaterialPath().CStr());
|
||||
source.SetSkyboxTopColor(XCEngine::Math::Color(0.12f, 0.21f, 0.64f, 1.0f));
|
||||
source.SetSkyboxHorizonColor(XCEngine::Math::Color(0.71f, 0.76f, 0.88f, 1.0f));
|
||||
source.SetSkyboxBottomColor(XCEngine::Math::Color(0.92f, 0.82f, 0.58f, 1.0f));
|
||||
XCEngine::Rendering::FinalColorOverrideSettings finalColorOverrides = {};
|
||||
finalColorOverrides.overrideOutputTransferMode = true;
|
||||
finalColorOverrides.outputTransferMode =
|
||||
XCEngine::Rendering::FinalColorOutputTransferMode::LinearToSRGB;
|
||||
finalColorOverrides.overrideExposureMode = true;
|
||||
finalColorOverrides.exposureMode = XCEngine::Rendering::FinalColorExposureMode::Fixed;
|
||||
finalColorOverrides.overrideExposureValue = true;
|
||||
finalColorOverrides.exposureValue = 1.35f;
|
||||
finalColorOverrides.overrideFinalColorScale = true;
|
||||
finalColorOverrides.finalColorScale = XCEngine::Math::Vector4(1.0f, 0.9f, 0.8f, 1.0f);
|
||||
source.SetFinalColorOverrides(finalColorOverrides);
|
||||
source.SetPostProcessPasses({
|
||||
XCEngine::Rendering::CameraPostProcessPassDesc::MakeColorScale(
|
||||
XCEngine::Math::Vector4(0.55f, 0.8f, 1.2f, 1.0f)),
|
||||
@@ -97,10 +106,22 @@ TEST(CameraComponent_Test, SerializeRoundTripPreservesViewportAndClearState) {
|
||||
EXPECT_FLOAT_EQ(target.GetViewportRect().width, 0.5f);
|
||||
EXPECT_FLOAT_EQ(target.GetViewportRect().height, 0.625f);
|
||||
EXPECT_TRUE(target.IsSkyboxEnabled());
|
||||
EXPECT_EQ(target.GetSkyboxMaterialPath(), XCEngine::Resources::GetBuiltinDefaultPrimitiveMaterialPath().CStr());
|
||||
EXPECT_TRUE(target.GetSkyboxMaterialPath().empty());
|
||||
EXPECT_FLOAT_EQ(target.GetSkyboxTopColor().b, 0.64f);
|
||||
EXPECT_FLOAT_EQ(target.GetSkyboxHorizonColor().g, 0.76f);
|
||||
EXPECT_FLOAT_EQ(target.GetSkyboxBottomColor().r, 0.92f);
|
||||
EXPECT_TRUE(target.GetFinalColorOverrides().overrideOutputTransferMode);
|
||||
EXPECT_EQ(
|
||||
target.GetFinalColorOverrides().outputTransferMode,
|
||||
XCEngine::Rendering::FinalColorOutputTransferMode::LinearToSRGB);
|
||||
EXPECT_TRUE(target.GetFinalColorOverrides().overrideExposureMode);
|
||||
EXPECT_EQ(
|
||||
target.GetFinalColorOverrides().exposureMode,
|
||||
XCEngine::Rendering::FinalColorExposureMode::Fixed);
|
||||
EXPECT_TRUE(target.GetFinalColorOverrides().overrideExposureValue);
|
||||
EXPECT_FLOAT_EQ(target.GetFinalColorOverrides().exposureValue, 1.35f);
|
||||
EXPECT_TRUE(target.GetFinalColorOverrides().overrideFinalColorScale);
|
||||
EXPECT_FLOAT_EQ(target.GetFinalColorOverrides().finalColorScale.y, 0.9f);
|
||||
ASSERT_EQ(target.GetPostProcessPasses().size(), 2u);
|
||||
EXPECT_EQ(
|
||||
target.GetPostProcessPasses()[0].type,
|
||||
|
||||
@@ -279,6 +279,7 @@ private:
|
||||
struct MockPipelineAssetState {
|
||||
int createCalls = 0;
|
||||
std::shared_ptr<MockPipelineState> lastCreatedPipelineState;
|
||||
FinalColorSettings defaultFinalColorSettings = {};
|
||||
};
|
||||
|
||||
class MockPipeline final : public RenderPipeline {
|
||||
@@ -344,6 +345,10 @@ public:
|
||||
return std::make_unique<MockPipeline>(m_state->lastCreatedPipelineState);
|
||||
}
|
||||
|
||||
FinalColorSettings GetDefaultFinalColorSettings() const override {
|
||||
return m_state->defaultFinalColorSettings;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<MockPipelineAssetState> m_state;
|
||||
};
|
||||
@@ -1838,6 +1843,50 @@ TEST(SceneRenderer_Test, BuildsCameraColorScalePostProcessRequestFromCameraPassS
|
||||
delete backBufferColorView;
|
||||
}
|
||||
|
||||
TEST(SceneRenderer_Test, ResolvesFinalColorPolicyFromPipelineDefaultsAndCameraOverrides) {
|
||||
Scene scene("SceneRendererFinalColorPolicyScene");
|
||||
|
||||
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
||||
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
||||
camera->SetPrimary(true);
|
||||
camera->SetDepth(2.0f);
|
||||
|
||||
FinalColorOverrideSettings cameraOverrides = {};
|
||||
cameraOverrides.overrideExposureValue = true;
|
||||
cameraOverrides.exposureValue = 1.8f;
|
||||
cameraOverrides.overrideFinalColorScale = true;
|
||||
cameraOverrides.finalColorScale = XCEngine::Math::Vector4(0.95f, 0.9f, 0.85f, 1.0f);
|
||||
camera->SetFinalColorOverrides(cameraOverrides);
|
||||
|
||||
auto assetState = std::make_shared<MockPipelineAssetState>();
|
||||
assetState->defaultFinalColorSettings.outputTransferMode =
|
||||
FinalColorOutputTransferMode::LinearToSRGB;
|
||||
assetState->defaultFinalColorSettings.exposureMode =
|
||||
FinalColorExposureMode::Fixed;
|
||||
assetState->defaultFinalColorSettings.exposureValue = 1.25f;
|
||||
assetState->defaultFinalColorSettings.finalColorScale =
|
||||
XCEngine::Math::Vector4(1.0f, 0.95f, 0.9f, 1.0f);
|
||||
|
||||
SceneRenderer renderer(std::make_shared<MockPipelineAsset>(assetState));
|
||||
const std::vector<CameraRenderRequest> requests =
|
||||
renderer.BuildRenderRequests(scene, nullptr, CreateValidContext(), RenderSurface(640, 360));
|
||||
|
||||
ASSERT_EQ(requests.size(), 1u);
|
||||
const CameraRenderRequest& request = requests[0];
|
||||
EXPECT_TRUE(request.finalColorPolicy.hasPipelineDefaults);
|
||||
EXPECT_TRUE(request.finalColorPolicy.hasCameraOverrides);
|
||||
EXPECT_FALSE(request.finalColorPolicy.hasVolumeOverrides);
|
||||
EXPECT_EQ(
|
||||
request.finalColorPolicy.outputTransferMode,
|
||||
FinalColorOutputTransferMode::LinearToSRGB);
|
||||
EXPECT_EQ(
|
||||
request.finalColorPolicy.exposureMode,
|
||||
FinalColorExposureMode::Fixed);
|
||||
EXPECT_FLOAT_EQ(request.finalColorPolicy.exposureValue, 1.8f);
|
||||
EXPECT_FLOAT_EQ(request.finalColorPolicy.finalColorScale.x, 0.95f);
|
||||
EXPECT_FALSE(request.finalOutput.IsRequested());
|
||||
}
|
||||
|
||||
TEST(CameraRenderer_Test, UsesResolvedRenderAreaForCameraViewportDimensions) {
|
||||
Scene scene("CameraRendererViewportRectScene");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user