Environment settings become scene entities (UE Skybox/SkyLight semantics)

Skybox/SkyLight are now Actor-carried components instead of resident
global settings, matching UE where SkyAtmosphere/SkyLight/fog are
Actors added from Place Actors and configured in Details.

- New SkyboxComponent (active + envPath asset slot) and SkyLightComponent
  (active + ambientColor); first-active entity wins per frame
- SceneRenderer collects env from components into FrameSceneData
  (DrawSkybox/envPath/ambient); Renderer performs delta sync - env
  reloads only when the resolved path changes, neutral fallback when
  empty or unloadable
- App-side env plumbing removed: kEnvPaths table and getEnvIndex/
  setEnvIndex callbacks deleted; StatsPanel keeps ClearColor + debug
  view, env params move to Details
- Serializer: scene-level Settings stores ClearColor only; env
  components serialize per entity; legacy v1 scenes (Environment header
  + Settings.AmbientColor/DrawSkybox) auto-migrate into an Environment
  entity on load for visual parity
- Inspector: Skybox/Sky Light sections with .hdr asset slot dropdown
  and Add Component entries; Outliner "+ Create" popup offers
  Empty/Light/Camera/Environment
This commit is contained in:
2026-10-05 01:02:21 +08:00
parent b7d6f53591
commit 432089747f
14 changed files with 317 additions and 144 deletions
+108
View File
@@ -58,6 +58,39 @@ namespace
return s_Files;
}
// 递归收集 assets/ 下环境贴图(.hdr,Skybox 资产槽用;会话级缓存同上)
const std::vector<std::string>& AssetEnvFiles()
{
static std::vector<std::string> s_Files;
static bool s_Cached = false;
if (!s_Cached)
{
s_Cached = true;
namespace fs = std::filesystem;
if (fs::exists("assets"))
{
for (const auto& entry :
fs::recursive_directory_iterator("assets"))
{
if (!entry.is_regular_file())
{
continue;
}
std::string ext = entry.path().extension().string();
std::transform(ext.begin(), ext.end(), ext.begin(),
[](unsigned char c)
{ return std::tolower(c); });
if (ext == ".hdr")
{
s_Files.push_back(entry.path().generic_string());
}
}
std::sort(s_Files.begin(), s_Files.end());
}
}
return s_Files;
}
// InputText 需要 char 缓冲;提交时写回 std::string
bool InputTextString(const char* label, std::string& str)
{
@@ -198,6 +231,9 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected)
menuItem(static_cast<Engine::ECS::MeshRendererComponent*>(nullptr),
"Mesh Renderer");
menuItem(static_cast<Engine::ECS::LightComponent*>(nullptr), "Light");
menuItem(static_cast<Engine::ECS::SkyboxComponent*>(nullptr), "Skybox");
menuItem(static_cast<Engine::ECS::SkyLightComponent*>(nullptr),
"Sky Light");
menuItem(static_cast<Engine::ECS::CameraComponent*>(nullptr), "Camera");
menuItem(static_cast<Engine::ECS::AnimationComponent*>(nullptr), "Animation");
menuItem(static_cast<Engine::ECS::NameComponent*>(nullptr), "Name");
@@ -417,6 +453,78 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected)
}
}
// ---- Skybox(UE Skybox Actor 语义:环境贴图 = 实体资产槽)----
if (auto* sb = reg.try_get<Engine::ECS::SkyboxComponent>(selected))
{
bool removeSb = false;
if (ComponentHeader("Skybox", &sb->active, true, &removeSb))
{
if (!sb->active)
{
ImGui::BeginDisabled(true);
}
// 环境贴图资产槽(模型槽同款:下拉列 assets/ 下 .hdr,选中
// 即加载;空 = 无环境,只露清屏色)。渲染侧由 Renderer 按
// 帧数据差量同步(路径变化才重载),此处零额外同步
{
const auto& s_EnvFiles = AssetEnvFiles();
std::vector<const char*> opts;
opts.push_back("<none>");
for (const auto& f : s_EnvFiles)
{
opts.push_back(f.c_str());
}
int sel = 0;
for (size_t k = 0; k < s_EnvFiles.size(); ++k)
{
if (s_EnvFiles[k] == sb->envPath)
{
sel = static_cast<int>(k) + 1;
}
}
ImGui::SetNextItemWidth(-1.0f);
if (ImGui::Combo("##env_slot", &sel, opts.data(),
static_cast<int>(opts.size())))
{
sb->envPath = (sel <= 0)
? std::string{}
: s_EnvFiles[static_cast<size_t>(sel) - 1];
}
ImGui::TextDisabled("(empty = clear color only)");
}
if (!sb->active)
{
ImGui::EndDisabled();
}
}
if (removeSb)
{
reg.remove<Engine::ECS::SkyboxComponent>(selected);
}
}
// ---- SkyLight(UE SkyLight 语义:全局环境光)----
if (auto* sl = reg.try_get<Engine::ECS::SkyLightComponent>(selected))
{
bool removeSl = false;
if (ComponentHeader("Sky Light", &sl->active, true, &removeSl))
{
if (!sl->active)
{
ImGui::BeginDisabled(true);
}
ImGui::ColorEdit4("ambient", &sl->ambientColor.x);
if (!sl->active)
{
ImGui::EndDisabled();
}
}
if (removeSl)
{
reg.remove<Engine::ECS::SkyLightComponent>(selected);
}
}
// ---- Camera ----
if (auto* cam = reg.try_get<Engine::ECS::CameraComponent>(selected))
{