渲染优化

This commit is contained in:
Rowland 2026-07-22 16:08:48 +08:00
parent 0a1c727122
commit 2512e3ade6
46 changed files with 1696 additions and 52 deletions

View File

@ -11,6 +11,7 @@
#include "MetaCoreRender/MetaCoreRenderDevice.h"
#include "MetaCoreRender/MetaCoreRenderTypes.h"
#include "MetaCoreRender/MetaCoreSceneRenderSync.h"
#include "MetaCoreRendering/MetaCoreRendering.h"
#include "MetaCoreRuntimeData/MetaCoreRuntimeDataDispatcher.h"
#include "MetaCoreRuntimeData/MetaCoreRuntimeDataProject.h"
#include "MetaCoreRuntimeData/MetaCoreRuntimeDataSource.h"
@ -305,6 +306,7 @@ int main(int argc, char* argv[]) {
std::filesystem::path customScenePath{};
std::filesystem::path runtimeProjectPath{};
bool validateOnly = false;
std::string requestedRenderDebugView{};
std::uint64_t smokeTestFrames = 0U;
for (int i = 1; i < argc; ++i) {
if (std::strcmp(argv[i], "--project") == 0 && i + 1 < argc) {
@ -319,6 +321,8 @@ int main(int argc, char* argv[]) {
validateOnly = true;
} else if (std::strcmp(argv[i], "--smoke-test-frames") == 0 && i + 1 < argc) {
smokeTestFrames = static_cast<std::uint64_t>(std::strtoull(argv[++i], nullptr, 10));
} else if (std::strcmp(argv[i], "--render-debug") == 0 && i + 1 < argc) {
requestedRenderDebugView = argv[++i];
}
}
@ -392,6 +396,16 @@ int main(int argc, char* argv[]) {
errors << "MetaCorePlayer: viewport renderer initialize failed\n";
return 1;
}
if(!requestedRenderDebugView.empty()){
bool developmentPlayer=!deliveryDocument.has_value();
if(deliveryDocument&&std::filesystem::is_regular_file(packageRoot/"Manifest"/"MetaCore.release.json")){
MetaCore::MetaCoreBuildIssue manifestIssue;const auto manifest=MetaCore::MetaCoreReadReleaseManifest(packageRoot/"Manifest"/"MetaCore.release.json",&manifestIssue);
developmentPlayer=manifest&&manifest->Configuration!="Release";
}
const auto debugView=MetaCore::MetaCoreParseRenderDebugView(requestedRenderDebugView);
if(developmentPlayer&&debugView)viewportRenderer.SetRenderDebugView(*debugView);
else std::cerr<<"Render debug view rejected; mode="<<requestedRenderDebugView<<"; fallback=lit; reason="<<(developmentPlayer?"unknown mode":"formal Release Player")<<'\n';
}
std::filesystem::path projectRoot;
if (deliveryDocument) {

View File

@ -115,10 +115,35 @@ endif()
if(NOT METACORE_BUILD_CORE_ONLY)
set(METACORE_UI_BLIT_MATERIAL_SOURCE "${CMAKE_SOURCE_DIR}/third_party/filament/libs/filagui/src/materials/uiBlit.mat")
set(METACORE_UI_BLIT_MATERIAL_OUTPUT "${CMAKE_BINARY_DIR}/uiBlit.filamat")
if(WIN32)
set(METACORE_FILAMENT_MATC "${METACORE_FILAMENT_ROOT}/bin/matc.exe")
else()
set(METACORE_FILAMENT_MATC "${METACORE_FILAMENT_ROOT}/bin/matc")
set(METACORE_FILAMENT_MATC "" CACHE FILEPATH "Filament material compiler compatible with the build host")
set(METACORE_FILAMENT_CMGEN "" CACHE FILEPATH "Filament environment compiler compatible with the build host")
if(NOT METACORE_FILAMENT_MATC)
if(WIN32)
set(METACORE_FILAMENT_MATC "${METACORE_FILAMENT_ROOT}/bin/matc.exe")
else()
foreach(candidate IN ITEMS
"${CMAKE_SOURCE_DIR}/build/filament-host-tools/install/bin/matc"
"${METACORE_FILAMENT_ROOT}/bin/matc")
if(EXISTS "${candidate}")
set(METACORE_FILAMENT_MATC "${candidate}")
break()
endif()
endforeach()
endif()
endif()
if(NOT METACORE_FILAMENT_CMGEN)
if(WIN32)
set(METACORE_FILAMENT_CMGEN "${METACORE_FILAMENT_ROOT}/bin/cmgen.exe")
else()
foreach(candidate IN ITEMS
"${CMAKE_SOURCE_DIR}/build/filament-host-tools/install/bin/cmgen"
"${METACORE_FILAMENT_ROOT}/bin/cmgen")
if(EXISTS "${candidate}")
set(METACORE_FILAMENT_CMGEN "${candidate}")
break()
endif()
endforeach()
endif()
endif()
set(METACORE_FILAMENT_MATC_RUNNABLE OFF)
if(EXISTS "${METACORE_FILAMENT_MATC}")
@ -132,6 +157,28 @@ if(NOT METACORE_BUILD_CORE_ONLY)
set(METACORE_FILAMENT_MATC_RUNNABLE ON)
endif()
endif()
if(NOT METACORE_FILAMENT_MATC_RUNNABLE AND NOT WIN32)
message(STATUS "Bundled Filament matc is not runnable on this GLIBC; run tools/build_filament_host_tools.sh and reconfigure")
elseif(METACORE_FILAMENT_MATC_RUNNABLE)
message(STATUS "Using runnable Filament matc: ${METACORE_FILAMENT_MATC}")
endif()
set(METACORE_FILAMENT_CMGEN_RUNNABLE OFF)
if(EXISTS "${METACORE_FILAMENT_CMGEN}")
execute_process(
COMMAND "${METACORE_FILAMENT_CMGEN}" --help
RESULT_VARIABLE METACORE_FILAMENT_CMGEN_HELP_RESULT
OUTPUT_QUIET
ERROR_QUIET
)
if(METACORE_FILAMENT_CMGEN_HELP_RESULT EQUAL 0)
set(METACORE_FILAMENT_CMGEN_RUNNABLE ON)
endif()
endif()
if(NOT METACORE_FILAMENT_CMGEN_RUNNABLE AND NOT WIN32)
message(STATUS "Bundled Filament cmgen is not runnable on this GLIBC; run tools/build_filament_host_tools.sh and reconfigure")
elseif(METACORE_FILAMENT_CMGEN_RUNNABLE)
message(STATUS "Using runnable Filament cmgen: ${METACORE_FILAMENT_CMGEN}")
endif()
set(METACORE_UI_BLIT_MATERIAL_PREBUILT "")
foreach(candidate IN ITEMS
@ -239,6 +286,42 @@ if(NOT METACORE_BUILD_CORE_ONLY)
endif()
endfunction()
set(METACORE_RENDER_DEBUG_MATERIAL_SOURCE "${CMAKE_SOURCE_DIR}/Source/MetaCoreRender/Private/Materials/render_debug.mat")
set(METACORE_RENDER_DEBUG_MATERIAL_OUTPUT "${CMAKE_BINARY_DIR}/render_debug.filamat")
if(METACORE_FILAMENT_MATC_RUNNABLE)
add_custom_command(OUTPUT "${METACORE_RENDER_DEBUG_MATERIAL_OUTPUT}"
COMMAND "${METACORE_FILAMENT_MATC}" --platform desktop --api opengl
--output "${METACORE_RENDER_DEBUG_MATERIAL_OUTPUT}" "${METACORE_RENDER_DEBUG_MATERIAL_SOURCE}"
DEPENDS "${METACORE_RENDER_DEBUG_MATERIAL_SOURCE}" "${METACORE_FILAMENT_MATC}" VERBATIM)
add_custom_target(MetaCoreRenderDebugMaterial DEPENDS "${METACORE_RENDER_DEBUG_MATERIAL_OUTPUT}")
endif()
function(metacore_stage_render_debug_material target_name)
if(TARGET MetaCoreRenderDebugMaterial)
add_dependencies(${target_name} MetaCoreRenderDebugMaterial)
add_custom_command(TARGET ${target_name} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${METACORE_RENDER_DEBUG_MATERIAL_OUTPUT}" "$<TARGET_FILE_DIR:${target_name}>/render_debug.filamat" VERBATIM)
endif()
endfunction()
set(METACORE_MATERIAL_TEMPLATE_OUTPUTS)
foreach(template_name IN ITEMS pbr unlit transparent decal terrain particle line)
set(template_source "${CMAKE_SOURCE_DIR}/Source/MetaCoreRender/Private/Materials/Templates/${template_name}.mat")
set(template_output "${CMAKE_BINARY_DIR}/material_templates/${template_name}.filamat")
add_custom_command(OUTPUT "${template_output}"
COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/material_templates"
COMMAND "${METACORE_FILAMENT_MATC}" --platform desktop --api opengl
--output "${template_output}" "${template_source}"
DEPENDS "${template_source}" "${METACORE_FILAMENT_MATC}" VERBATIM)
list(APPEND METACORE_MATERIAL_TEMPLATE_OUTPUTS "${template_output}")
endforeach()
add_custom_target(MetaCoreMaterialTemplates DEPENDS ${METACORE_MATERIAL_TEMPLATE_OUTPUTS})
function(metacore_stage_material_templates target_name)
add_dependencies(${target_name} MetaCoreMaterialTemplates)
add_custom_command(TARGET ${target_name} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory_if_different
"${CMAKE_BINARY_DIR}/material_templates" "$<TARGET_FILE_DIR:${target_name}>/material_templates" VERBATIM)
endfunction()
set(METACORE_RML_UI_MATERIAL_SOURCE "${CMAKE_SOURCE_DIR}/Source/MetaCoreRuntimeUi/Private/Materials/rml_ui.mat")
set(METACORE_RML_UI_MATERIAL_OUTPUT "${CMAKE_BINARY_DIR}/rml_ui.filamat")
if(METACORE_FILAMENT_MATC_RUNNABLE AND EXISTS "${METACORE_RML_UI_MATERIAL_SOURCE}")
@ -810,6 +893,8 @@ target_link_libraries(MetaCoreEditorApp
)
metacore_stage_ui_blit_material(MetaCoreEditorApp)
metacore_stage_editor_grid_material(MetaCoreEditorApp)
metacore_stage_render_debug_material(MetaCoreEditorApp)
metacore_stage_material_templates(MetaCoreEditorApp)
set(METACORE_LAUNCHER_SOURCES
@ -889,6 +974,9 @@ if(UNIX AND NOT APPLE)
endif()
metacore_stage_ui_blit_material(MetaCorePlayer)
metacore_stage_rml_ui_material(MetaCorePlayer)
metacore_stage_editor_grid_material(MetaCorePlayer)
metacore_stage_render_debug_material(MetaCorePlayer)
metacore_stage_material_templates(MetaCorePlayer)
@ -927,6 +1015,12 @@ if(METACORE_BUILD_TESTS)
target_compile_options(MetaCoreRenderTests PRIVATE ${METACORE_COMMON_WARNINGS})
add_test(NAME MetaCoreRenderTests COMMAND MetaCoreRenderTests)
add_executable(MetaCoreRenderBenchmark tests/MetaCoreRenderBenchmark.cpp)
target_link_libraries(MetaCoreRenderBenchmark PRIVATE MetaCoreRendering)
target_compile_options(MetaCoreRenderBenchmark PRIVATE ${METACORE_COMMON_WARNINGS})
add_test(NAME MetaCoreRenderBenchmark COMMAND MetaCoreRenderBenchmark)
set_tests_properties(MetaCoreRenderBenchmark PROPERTIES LABELS "benchmark;render")
add_executable(MetaCoreSmokeTests
tests/MetaCoreSmokeTests.cpp
Source/MetaCoreRender/Private/MetaCoreGlibcCompat.cpp

View File

@ -0,0 +1,16 @@
{
"artifact_key": "4653a0a9ca2d74fb",
"asset_type": "asset",
"guid": "b123f845-4d31-40ed-93b7-341bdd7da004",
"import_settings": {},
"importer_id": "BinaryImporter",
"importer_version": 2,
"last_import_result": 0,
"package_path": "Assets/Rendering/0041a59d-24e7-45c4-a51a-4a11327fd92c.mcpostprocess.mcasset",
"schema_version": 2,
"settings_hash": 11238273277574643119,
"source_hash": 10963282181175228673,
"source_mtime_ns": 1784707083324024252,
"source_path": "Assets/Rendering/0041a59d-24e7-45c4-a51a-4a11327fd92c.mcpostprocess",
"source_size_bytes": 625
}

View File

@ -0,0 +1,16 @@
{
"artifact_key": "1ab4acf636a38438",
"asset_type": "asset",
"guid": "d416c036-849c-4e7d-b6ee-cff95a69e397",
"import_settings": {},
"importer_id": "BinaryImporter",
"importer_version": 2,
"last_import_result": 0,
"package_path": "Assets/Rendering/01231740-f061-4ab2-be9f-9acdc35f2dfc.mcpostprocess.mcasset",
"schema_version": 2,
"settings_hash": 11238273277574643119,
"source_hash": 18159617644460381285,
"source_mtime_ns": 1784706294268307206,
"source_path": "Assets/Rendering/01231740-f061-4ab2-be9f-9acdc35f2dfc.mcpostprocess",
"source_size_bytes": 625
}

View File

@ -0,0 +1,16 @@
{
"artifact_key": "63108315c0df3e2e",
"asset_type": "asset",
"guid": "8d259deb-eff0-4d2a-90c2-2f8d3cd1cb25",
"import_settings": {},
"importer_id": "BinaryImporter",
"importer_version": 2,
"last_import_result": 0,
"package_path": "Assets/Rendering/67c50c20-49e6-42e3-a602-a903da59f99f.mcenvironment.mcasset",
"schema_version": 2,
"settings_hash": 11238273277574643119,
"source_hash": 1769686856549661741,
"source_mtime_ns": 1784707043147341070,
"source_path": "Assets/Rendering/67c50c20-49e6-42e3-a602-a903da59f99f.mcenvironment",
"source_size_bytes": 406
}

View File

@ -0,0 +1,16 @@
{
"artifact_key": "1f937ba9cf68ed68",
"asset_type": "asset",
"guid": "b825a596-0c25-4381-9e28-e3cbf7f6604a",
"import_settings": {},
"importer_id": "BinaryImporter",
"importer_version": 2,
"last_import_result": 0,
"package_path": "Assets/Rendering/b3db5266-2ac4-44df-9a6f-df6518e33054.mcpostprocess.mcasset",
"schema_version": 2,
"settings_hash": 11238273277574643119,
"source_hash": 18272799077911068983,
"source_mtime_ns": 1784704151947626302,
"source_path": "Assets/Rendering/b3db5266-2ac4-44df-9a6f-df6518e33054.mcpostprocess",
"source_size_bytes": 625
}

View File

@ -0,0 +1,16 @@
{
"artifact_key": "95b3027d3be9f9ea",
"asset_type": "asset",
"guid": "5a5245d2-4586-4380-88e2-3fa7c4cd1252",
"import_settings": {},
"importer_id": "BinaryImporter",
"importer_version": 2,
"last_import_result": 0,
"package_path": "Assets/Rendering/cfa65625-2d1b-40aa-a0ef-66683b9351db.mcenvironment.mcasset",
"schema_version": 2,
"settings_hash": 11238273277574643119,
"source_hash": 16231096662236934614,
"source_mtime_ns": 1784706279917222726,
"source_path": "Assets/Rendering/cfa65625-2d1b-40aa-a0ef-66683b9351db.mcenvironment",
"source_size_bytes": 406
}

View File

@ -0,0 +1,16 @@
{
"artifact_key": "38cbca3a52fa9bb1",
"asset_type": "asset",
"guid": "67784d04-6b91-4d83-9f33-d9bce8b91051",
"import_settings": {},
"importer_id": "BinaryImporter",
"importer_version": 2,
"last_import_result": 0,
"package_path": "Assets/Rendering/f207fd8c-b32a-452f-846a-5891bef8e406.mcenvironment.mcasset",
"schema_version": 2,
"settings_hash": 11238273277574643119,
"source_hash": 12730376897471309359,
"source_mtime_ns": 1784703158565629250,
"source_path": "Assets/Rendering/f207fd8c-b32a-452f-846a-5891bef8e406.mcenvironment",
"source_size_bytes": 406
}

View File

@ -9,6 +9,7 @@
#include "MetaCoreRuntimeInput/MetaCoreInputMap.h"
#include "MetaCorePhysics/MetaCorePhysics.h"
#include "MetaCoreRuntimeData/MetaCoreRuntimeDataProject.h"
#include "MetaCoreRendering/MetaCoreRendering.h"
#include "MetaCoreScene/MetaCoreScenePackage.h"
#include "MetaCoreScene/MetaCoreSceneSerializer.h"
#include "MetaCoreScripting/MetaCoreScripting.h"
@ -37,6 +38,8 @@ namespace {
using json = nlohmann::json;
[[nodiscard]] bool CopyTree(const std::filesystem::path& source, const std::filesystem::path& target);
std::unordered_set<MetaCoreAssetGuid, MetaCoreAssetGuidHasher> MetaCoreCollectCookablePhysicsMeshes(
const std::filesystem::path& projectRoot,
const MetaCoreTypeRegistry& registry,
@ -118,6 +121,79 @@ std::optional<std::string> MetaCoreValidateScenePhysicsForCook(
return std::nullopt;
}
std::optional<std::string> MetaCoreStageSceneRenderingForCook(
const MetaCoreSceneDocument& scene,
const std::filesystem::path& projectRoot,
const std::filesystem::path& outputRoot
) {
std::error_code error;
std::filesystem::create_directories(outputRoot / "ReflectionProbes", error);
if (error) return "Cannot create rendering derived-data output: " + error.message();
for (const auto& object : scene.GameObjects) {
if (object.LodGroup && object.LodGroup->Enabled) {
const auto& levels = object.LodGroup->Levels;
if (levels.empty() || levels.size() > 8U)
return "Object " + std::to_string(object.Id) + " LODGroup must contain 1-8 levels";
for (std::size_t index = 0; index < levels.size(); ++index) {
if (levels[index].ScreenHeight < 0.0F || levels[index].ScreenHeight > 1.0F ||
(index > 0U && levels[index].ScreenHeight >= levels[index - 1U].ScreenHeight))
return "Object " + std::to_string(object.Id) + " has invalid or non-descending LOD thresholds";
}
}
if (object.Terrain && object.Terrain->Enabled && object.Terrain->Layers.size() > 4U)
return "Object " + std::to_string(object.Id) + " Terrain exceeds the four-layer limit";
if (!object.ReflectionProbe || !object.ReflectionProbe->Enabled) continue;
const auto& probe = *object.ReflectionProbe;
if (!probe.BakedCubemapGuid.IsValid())
return "Object " + std::to_string(object.Id) + " ReflectionProbe has no baked cubemap GUID";
const auto sourceRoot = projectRoot / "Library" / "DerivedData" / "Rendering" / "Probes" / probe.BakedCubemapGuid.ToString();
const auto headerPath = sourceRoot / "probe.mcrprobe";
const auto header = MetaCoreReadReflectionProbeDerivedHeader(headerPath);
if (!header || header->ProbeGuid != probe.BakedCubemapGuid)
return "Object " + std::to_string(object.Id) + " ReflectionProbe derived header is missing, corrupt, or version-incompatible: " + probe.BakedCubemapGuid.ToString();
const std::filesystem::path iblRelativePath = header->IblRelativePath;
const std::filesystem::path skyboxRelativePath = header->SkyboxRelativePath;
const auto iblPath = sourceRoot / iblRelativePath;
const auto skyboxPath = sourceRoot / skyboxRelativePath;
if (header->IblRelativePath.empty() || header->SkyboxRelativePath.empty() ||
iblRelativePath.is_absolute() || skyboxRelativePath.is_absolute() ||
iblRelativePath.string().find("..") != std::string::npos || skyboxRelativePath.string().find("..") != std::string::npos ||
!std::filesystem::is_regular_file(iblPath) || !std::filesystem::is_regular_file(skyboxPath))
return "Object " + std::to_string(object.Id) + " ReflectionProbe KTX payload is missing or unsafe: " + probe.BakedCubemapGuid.ToString();
if (!probe.SourceEnvironmentPath.empty()) {
const std::filesystem::path sourceEnvironment = probe.SourceEnvironmentPath;
if (sourceEnvironment.is_absolute() || sourceEnvironment.string().find("..") != std::string::npos)
return "Object " + std::to_string(object.Id) + " ReflectionProbe source path is unsafe";
const auto sourceHash = MetaCoreSha256File(projectRoot / sourceEnvironment);
if (!sourceHash || *sourceHash != header->SourceHash)
return "Object " + std::to_string(object.Id) + " ReflectionProbe source hash changed; rebake probe " + probe.BakedCubemapGuid.ToString();
}
const auto destination = outputRoot / "ReflectionProbes" / probe.BakedCubemapGuid.ToString();
if (!CopyTree(sourceRoot, destination))
return "Failed to stage ReflectionProbe " + probe.BakedCubemapGuid.ToString();
}
return std::nullopt;
}
std::optional<std::string> MetaCoreValidateMaterialAssetsForCook(
const std::filesystem::path& projectRoot, const MetaCoreTypeRegistry& registry) {
MetaCoreMaterialTemplateRegistry templates;
for(const auto& relativeRoot:{std::filesystem::path("Assets"),std::filesystem::path("Library")}){
const auto root=projectRoot/relativeRoot;std::error_code error;if(!std::filesystem::is_directory(root,error))continue;
for(std::filesystem::recursive_directory_iterator iterator(root,std::filesystem::directory_options::skip_permission_denied,error),end;!error&&iterator!=end;iterator.increment(error)){
if(!iterator->is_regular_file(error))continue;const auto& path=iterator->path();
if(path.filename().string().ends_with(".mcmaterial.json")){
auto material=MetaCoreSceneSerializer::LoadMaterialFromJson(path,registry);std::vector<std::string> issues;
if(!material||!templates.ValidateAndMigrate(*material,&issues))return "Material asset is missing, invalid, or template-incompatible: "+path.generic_string()+(issues.empty()?std::string{}:"; "+issues.front());
}else if(path.extension()==".mcasset"){
const auto package=MetaCoreReadPackageFile(path,registry);if(!package||package->PayloadSections.empty())continue;MetaCoreModelAssetDocument model;if(!MetaCoreDeserializeFromBytes(package->PayloadSections.front(),model,registry))continue;
for(auto material:model.GeneratedMaterialAssets){std::vector<std::string> issues;if(!templates.ValidateAndMigrate(material,&issues))return "Generated material is template-incompatible: asset="+material.AssetGuid.ToString()+" package="+path.generic_string()+(issues.empty()?std::string{}:"; "+issues.front());}
}
}
}
return std::nullopt;
}
[[nodiscard]] std::string ConfigurationName(MetaCoreBuildConfiguration configuration) {
switch (configuration) {
case MetaCoreBuildConfiguration::Debug: return "Debug";
@ -734,6 +810,8 @@ MetaCoreBuildResult MetaCoreBuildPipeline::Run(const MetaCoreBuildRequest& reque
if (!renderingSettings) return fail(MetaCoreBuildErrorCode::CookFailed, "Runtime rendering settings are missing, unreadable, or newer than this engine", renderingPath);
std::vector<std::string> renderingIssues;
if (!MetaCoreValidateRenderSettings(*renderingSettings, &renderingIssues)) return fail(MetaCoreBuildErrorCode::CookFailed, "Runtime rendering settings validation failed", renderingPath);
if(const auto materialIssue=MetaCoreValidateMaterialAssetsForCook(request.ProjectRoot,inputRegistry))
return fail(MetaCoreBuildErrorCode::CookFailed,*materialIssue,request.ProjectRoot/"Assets");
std::filesystem::path startupSource = profile->StartupScene.empty() ? project->StartupScenePath : profile->StartupScene;
if (!IsSafeRelativePath(startupSource)) return fail(MetaCoreBuildErrorCode::UnsafePath, "Startup scene path is unsafe", startupSource);
const auto absoluteStartup = request.ProjectRoot / startupSource;
@ -768,6 +846,9 @@ MetaCoreBuildResult MetaCoreBuildPipeline::Run(const MetaCoreBuildRequest& reque
if ((profile->ResourcePolicy == MetaCoreBuildResourcePolicy::AllProjectContent || !copiedCooked) && std::filesystem::is_directory(request.ProjectRoot / "Assets"))
if (!CopyTree(request.ProjectRoot / "Assets", staging / "Content" / "Assets")) return fail(MetaCoreBuildErrorCode::CookFailed, "Failed to collect project assets", request.ProjectRoot / "Assets");
if (startupSceneDocument) {
if (const auto issue = MetaCoreStageSceneRenderingForCook(
*startupSceneDocument, request.ProjectRoot, staging / "Content" / "Rendering"))
return fail(MetaCoreBuildErrorCode::CookFailed, *issue, absoluteStartup);
std::unordered_set<MetaCoreAssetGuid, MetaCoreAssetGuidHasher> environmentProfileGuids;
std::unordered_set<MetaCoreAssetGuid, MetaCoreAssetGuidHasher> postProcessProfileGuids;
if (startupSceneDocument->Environment.EnvironmentProfileGuid.IsValid()) environmentProfileGuids.insert(startupSceneDocument->Environment.EnvironmentProfileGuid);
@ -829,11 +910,23 @@ MetaCoreBuildResult MetaCoreBuildPipeline::Run(const MetaCoreBuildRequest& reque
std::filesystem::copy_file(scriptManifest, staging / "Project" / "Scripts" / scriptManifest.filename(), std::filesystem::copy_options::overwrite_existing, error);
if (error) return fail(MetaCoreBuildErrorCode::StageFailed, "Failed to stage project script module manifest", scriptManifest);
}
for (const char* material : {"uiBlit.filamat", "rml_ui.filamat"}) {
for (const char* material : {"uiBlit.filamat", "rml_ui.filamat", "grid.filamat", "render_debug.filamat"}) {
const auto source = request.EngineBuildRoot / material;
if (!std::filesystem::is_regular_file(source)) return fail(MetaCoreBuildErrorCode::StageFailed, "Required engine material is missing", source);
std::filesystem::copy_file(source, staging / "Engine" / "Materials" / material, std::filesystem::copy_options::overwrite_existing, error);
}
const auto templateSourceRoot=request.EngineBuildRoot/"material_templates";
const auto templateOutputRoot=staging/"Engine"/"Materials"/"Templates";
if(!CopyTree(templateSourceRoot,templateOutputRoot))return fail(MetaCoreBuildErrorCode::StageFailed,"Compiled material templates are missing",templateSourceRoot);
MetaCoreMaterialTemplateRegistry materialTemplates;json templateManifest=json::array();
for(const auto& descriptor:materialTemplates.GetTemplates()){
const auto compiled=templateOutputRoot/descriptor.CompiledMaterialName;if(!std::filesystem::is_regular_file(compiled))return fail(MetaCoreBuildErrorCode::StageFailed,"Compiled material template is missing: "+descriptor.TemplateId,compiled);
const auto source=sourceRoot/"Source"/"MetaCoreRender"/"Private"/"Materials"/"Templates"/(std::filesystem::path(descriptor.CompiledMaterialName).stem().string()+".mat");
const auto sourceHash=MetaCoreSha256File(source);const auto compiledHash=MetaCoreSha256File(compiled);if(!sourceHash||!compiledHash)return fail(MetaCoreBuildErrorCode::StageFailed,"Failed to hash material template: "+descriptor.TemplateId,source);
MetaCoreMaterialCookRequest cookKey{descriptor.TemplateId,descriptor.Version,*sourceHash,profile->TargetPlatform,profile->GraphicsBackend,"matc-v71;platform=desktop"};
templateManifest.push_back({{"template_id",descriptor.TemplateId},{"template_version",descriptor.Version},{"source_sha256",*sourceHash},{"compiled_sha256",*compiledHash},{"cook_key",MetaCoreBuildMaterialCookKey(cookKey)},{"file",descriptor.CompiledMaterialName}});
}
if(!WriteJson(templateOutputRoot/"templates.manifest.json",templateManifest))return fail(MetaCoreBuildErrorCode::StageFailed,"Failed to write material template manifest",templateOutputRoot);
const std::array fontCandidates = {
sourceRoot / "third_party" / "filament_installed" / "bin" / "assets" / "fonts" / "Roboto-Medium.ttf",
sourceRoot / "third_party" / "filament_installed.before-linux-sdk" / "bin" / "assets" / "fonts" / "Roboto-Medium.ttf"

View File

@ -18,6 +18,7 @@
#include "MetaCoreRuntimeData/MetaCoreRuntimeDataProject.h"
#include "MetaCoreRender/MetaCoreEditorViewportRenderer.h"
#include "MetaCoreRender/MetaCoreSceneRenderSync.h"
#include "MetaCoreRendering/MetaCoreRendering.h"
#include "MetaCoreFoundation/MetaCoreGeneratedReflection.h"
#include "MetaCoreFoundation/MetaCoreAssetRegistry.h"
#include "MetaCoreFoundation/MetaCoreAssetDependencyGraph.h"
@ -2707,6 +2708,44 @@ void MetaCoreDrawCameraComponentInspector(MetaCoreEditorContext& editorContext,
backgroundAlpha
);
}
const auto assetDatabase = editorContext.GetModuleRegistry().ResolveService<MetaCoreIAssetDatabaseService>();
const auto reflectionRegistry = editorContext.GetModuleRegistry().ResolveService<MetaCoreIReflectionRegistry>();
if (assetDatabase != nullptr && assetDatabase->HasProject() && reflectionRegistry != nullptr) {
struct ProfileChoice { MetaCoreAssetGuid Guid{}; std::string Name{}; };
std::vector<ProfileChoice> profiles;
const auto collectProfiles = [&](const std::filesystem::path& root) {
std::error_code error;
if (!std::filesystem::exists(root, error)) return;
for (std::filesystem::recursive_directory_iterator iterator(root, error), end; iterator != end && !error; iterator.increment(error)) {
if (!iterator->is_regular_file(error) || iterator->path().extension() != ".mcpostprocess") continue;
if (const auto profile = MetaCoreReadPostProcessProfile(iterator->path(), reflectionRegistry->GetTypeRegistry())) {
profiles.push_back({profile->AssetGuid, profile->Name});
}
}
};
const auto& project = assetDatabase->GetProjectDescriptor();
collectProfiles(project.AssetsPath);
collectProfiles(project.RuntimePath);
std::sort(profiles.begin(), profiles.end(), [](const auto& lhs, const auto& rhs) { return lhs.Name < rhs.Name; });
const auto selected = std::find_if(profiles.begin(), profiles.end(), [&](const auto& profile) { return profile.Guid == camera.PostProcessProfileGuid; });
const std::string preview = selected == profiles.end() ? (camera.PostProcessProfileGuid.IsValid() ? "缺失的 Profile" : "使用场景 Profile") : selected->Name;
if (ImGui::BeginCombo("后处理覆盖", preview.c_str())) {
if (ImGui::Selectable("使用场景 Profile", !camera.PostProcessProfileGuid.IsValid())) {
MetaCoreTrackComponentInspectorEdit(editorContext, "修改相机后处理覆盖", false);
MetaCoreApplyValueToSelectedObjects(editorContext, [](MetaCoreGameObject& object) -> MetaCoreAssetGuid* { return object.HasComponent<MetaCoreCameraComponent>() ? &object.GetComponent<MetaCoreCameraComponent>().PostProcessProfileGuid : nullptr; }, MetaCoreAssetGuid{});
}
for (const auto& profile : profiles) {
const bool isSelected = profile.Guid == camera.PostProcessProfileGuid;
if (ImGui::Selectable(profile.Name.c_str(), isSelected)) {
MetaCoreTrackComponentInspectorEdit(editorContext, "修改相机后处理覆盖", false);
MetaCoreApplyValueToSelectedObjects(editorContext, [](MetaCoreGameObject& object) -> MetaCoreAssetGuid* { return object.HasComponent<MetaCoreCameraComponent>() ? &object.GetComponent<MetaCoreCameraComponent>().PostProcessProfileGuid : nullptr; }, profile.Guid);
}
}
ImGui::EndCombo();
}
ImGui::TextDisabled("优先级:相机 > 场景 > 引擎默认");
}
}
void MetaCoreDrawLightComponentInspector(MetaCoreEditorContext& editorContext, MetaCoreGameObject& gameObject) {
@ -3685,7 +3724,29 @@ void MetaCoreDrawCharacterControllerComponentInspector(MetaCoreEditorContext& ed
template<typename T>
void MetaCoreDrawRenderingComponentInspector(MetaCoreEditorContext& editorContext, MetaCoreGameObject& gameObject) {
if(!gameObject.HasComponent<T>())return;auto& value=gameObject.GetComponent<T>();bool changed=ImGui::Checkbox("启用",&value.Enabled);
if constexpr(std::is_same_v<T,MetaCoreReflectionProbeComponent>){int shape=static_cast<int>(value.Shape);if(ImGui::Combo("形状",&shape,"Box\0Sphere\0")){value.Shape=static_cast<MetaCoreReflectionProbeShape>(shape);changed=true;}changed|=ImGui::DragFloat3("范围",glm::value_ptr(value.Extents),0.1F,0.01F);changed|=ImGui::DragFloat("半径",&value.Radius,0.1F,0.01F);changed|=ImGui::DragFloat("混合距离",&value.BlendDistance,0.05F,0.0F);changed|=ImGui::DragInt("优先级",&value.Priority);ImGui::TextDisabled("静态探针Cubemap 由烘焙派生数据提供");}
if constexpr(std::is_same_v<T,MetaCoreReflectionProbeComponent>){
int shape=static_cast<int>(value.Shape);if(ImGui::Combo("形状",&shape,"Box\0Sphere\0")){value.Shape=static_cast<MetaCoreReflectionProbeShape>(shape);changed=true;}
changed|=ImGui::DragFloat3("范围",glm::value_ptr(value.Extents),0.1F,0.01F);changed|=ImGui::DragFloat("半径",&value.Radius,0.1F,0.01F);changed|=ImGui::DragFloat("混合距离",&value.BlendDistance,0.05F,0.0F);changed|=ImGui::DragInt("优先级",&value.Priority);
std::array<char,512> sourcePath{};MetaCoreCopyStringToBuffer(value.SourceEnvironmentPath,sourcePath.data(),sourcePath.size());
if(ImGui::InputText("源环境(项目相对路径)",sourcePath.data(),sourcePath.size())){value.SourceEnvironmentPath=sourcePath.data();changed=true;}
const std::array<std::uint32_t,8> resolutions{16U,32U,64U,128U,256U,512U,1024U,2048U};
int resolutionIndex=0;for(std::size_t index=0;index<resolutions.size();++index)if(resolutions[index]==value.BakeResolution)resolutionIndex=static_cast<int>(index);
if(ImGui::Combo("烘焙尺寸",&resolutionIndex,"16\0 32\0 64\0 128\0 256\0 512\0 1024\0 2048\0")){value.BakeResolution=resolutions[static_cast<std::size_t>(resolutionIndex)];changed=true;}
changed|=MetaCoreEditAssetGuidField(editorContext,"派生 Cubemap GUID",value.BakedCubemapGuid,"<尚未烘焙>");
if(ImGui::Button("烘焙 / 重新烘焙")){
if(!value.BakedCubemapGuid.IsValid()){value.BakedCubemapGuid=MetaCoreAssetGuid::Generate();changed=true;}
const auto assets=editorContext.GetModuleRegistry().ResolveService<MetaCoreIAssetDatabaseService>();
if(assets&&assets->HasProject()&&!value.SourceEnvironmentPath.empty()){
const auto quote=[](const std::string& input){std::string output="'";for(char character:input){if(character=='\'')output+="'\\''";else output+=character;}return output+="'";};
const std::string command=quote((std::filesystem::path(METACORE_SOURCE_ROOT)/"tools"/"bake_reflection_probe.sh").string())+" "+quote(assets->GetProjectDescriptor().RootPath.string())+" "+quote(value.BakedCubemapGuid.ToString())+" "+quote(value.SourceEnvironmentPath)+" "+std::to_string(value.BakeResolution);
const int result=std::system(command.c_str());
if(result==0)ImGui::OpenPopup("ReflectionProbeBakeSuccess");else ImGui::OpenPopup("ReflectionProbeBakeFailure");
}else ImGui::OpenPopup("ReflectionProbeBakeFailure");
}
if(ImGui::BeginPopup("ReflectionProbeBakeSuccess")){ImGui::TextUnformatted("反射探针烘焙完成。派生数据将在 Cook 时校验并收集。");ImGui::EndPopup();}
if(ImGui::BeginPopup("ReflectionProbeBakeFailure")){ImGui::TextUnformatted("烘焙失败:请检查源环境路径及 cmgen。详细信息见启动终端。");ImGui::EndPopup();}
ImGui::TextDisabled("静态探针;运行时按优先级和边缘权重选择最多两个探针");
}
else if constexpr(std::is_same_v<T,MetaCoreLodGroupComponent>){changed|=ImGui::DragFloat("迟滞",&value.Hysteresis,0.005F,0.0F,0.5F);changed|=ImGui::Checkbox("抖动过渡",&value.DitherTransition);changed|=ImGui::Checkbox("末级以下剔除",&value.CullBelowLastLevel);ImGui::Text("LOD 级数: %zu",value.Levels.size());if(value.Levels.size()<8U&&ImGui::Button("添加 LOD")){value.Levels.push_back({value.Levels.empty()?1.0F:std::max(0.0F,value.Levels.back().ScreenHeight*0.5F),{}, {}});changed=true;}for(std::size_t i=0;i<value.Levels.size();++i){ImGui::PushID(static_cast<int>(i));changed|=ImGui::DragFloat("屏幕占比",&value.Levels[i].ScreenHeight,0.005F,0.0F,1.0F);ImGui::PopID();}}
else if constexpr(std::is_same_v<T,MetaCoreParticleEmitterComponent>){changed|=ImGui::DragFloat("发射率",&value.EmissionRate,0.5F,0.0F);int maximum=static_cast<int>(value.MaxParticles);if(ImGui::DragInt("最大粒子数",&maximum,10,1,1000000)){value.MaxParticles=static_cast<std::uint32_t>(maximum);changed=true;}changed|=ImGui::DragFloat("生命周期",&value.Lifetime,0.05F,0.01F);changed|=ImGui::DragFloat("初速度",&value.StartSpeed,0.05F);changed|=ImGui::DragFloat("初尺寸",&value.StartSize,0.01F,0.0F);changed|=ImGui::Checkbox("循环",&value.Loop);}
else if constexpr(std::is_same_v<T,MetaCoreLineRendererComponent>){changed|=ImGui::DragFloat("宽度",&value.Width,0.005F,0.001F);changed|=ImGui::ColorEdit3("颜色",glm::value_ptr(value.Color));changed|=ImGui::DragFloat("贴图平铺",&value.TextureTiling,0.05F,0.01F);ImGui::Text("点数: %zu",value.Points.size());}

View File

@ -20,6 +20,7 @@
#include "MetaCoreRuntimeInput/MetaCoreInputMap.h"
#include "MetaCorePlatform/MetaCoreWindow.h"
#include "MetaCorePhysics/MetaCorePhysics.h"
#include "MetaCoreRendering/MetaCoreRendering.h"
#include "MetaCoreFoundation/MetaCoreAssetRegistry.h"
#include "MetaCoreRender/MetaCoreEditorViewportRenderer.h"
@ -1715,7 +1716,8 @@ struct MetaCoreGeneratedAssetChoice {
instanceCamera.CullingMask != prefabCamera.CullingMask ||
instanceCamera.ClearFlags != prefabCamera.ClearFlags ||
!MetaCoreNearlyEqualVec3(instanceCamera.BackgroundColor, prefabCamera.BackgroundColor) ||
!MetaCoreNearlyEqual(instanceCamera.BackgroundAlpha, prefabCamera.BackgroundAlpha);
!MetaCoreNearlyEqual(instanceCamera.BackgroundAlpha, prefabCamera.BackgroundAlpha) ||
instanceCamera.PostProcessProfileGuid != prefabCamera.PostProcessProfileGuid;
}
[[nodiscard]] bool MetaCoreHasLightOverride(const MetaCoreGameObject& instanceObject, const MetaCoreGameObjectData& prefabObject) {
@ -3225,6 +3227,27 @@ public:
}
MetaCoreTrackInspectorEdit(editorContext, "调整天空盒亮度", true);
if (assetDatabaseService != nullptr && assetDatabaseService->HasProject() && ImGui::CollapsingHeader("渲染 Profile", ImGuiTreeNodeFlags_DefaultOpen)) {
const auto projectRoot = assetDatabaseService->GetProjectDescriptor().RootPath;
MetaCoreTypeRegistry registry; MetaCoreRegisterFoundationGeneratedTypes(registry); MetaCoreRegisterRenderingGeneratedTypes(registry);
std::vector<std::pair<std::filesystem::path, MetaCoreEnvironmentProfileDocument>> environmentProfiles;
std::vector<std::pair<std::filesystem::path, MetaCorePostProcessProfileDocument>> postProfiles;
for (const auto& relativeRoot : {std::filesystem::path("Assets"), std::filesystem::path("Runtime")}) {
const auto root=projectRoot/relativeRoot;std::error_code error;if(!std::filesystem::is_directory(root,error))continue;
for(std::filesystem::recursive_directory_iterator iterator(root,std::filesystem::directory_options::skip_permission_denied,error),end;!error&&iterator!=end;iterator.increment(error))if(iterator->is_regular_file(error)){
if(iterator->path().extension()==".mcenvironment"){if(auto value=MetaCoreReadEnvironmentProfile(iterator->path(),registry))environmentProfiles.emplace_back(iterator->path(),*value);}
else if(iterator->path().extension()==".mcpostprocess"){if(auto value=MetaCoreReadPostProcessProfile(iterator->path(),registry))postProfiles.emplace_back(iterator->path(),*value);}
}
}
const auto drawProfileCombo=[&](const char* label,const auto& profiles,const MetaCoreAssetGuid& selectedGuid,auto select){std::string preview="引擎默认";for(const auto& entry:profiles)if(entry.second.AssetGuid==selectedGuid)preview=entry.second.Name;if(ImGui::BeginCombo(label,preview.c_str())){if(ImGui::Selectable("引擎默认",!selectedGuid.IsValid()))select(MetaCoreAssetGuid{});for(const auto& entry:profiles){const bool selected=entry.second.AssetGuid==selectedGuid;if(ImGui::Selectable(entry.second.Name.c_str(),selected))select(entry.second.AssetGuid);if(selected)ImGui::SetItemDefaultFocus();}ImGui::EndCombo();}};
drawProfileCombo("Environment Profile",environmentProfiles,environment.EnvironmentProfileGuid,[&](MetaCoreAssetGuid guid){applyDiscreteChange("选择 Environment Profile",[&](auto& settings){settings.EnvironmentProfileGuid=guid;});});
drawProfileCombo("Post-process Profile",postProfiles,environment.PostProcessProfileGuid,[&](MetaCoreAssetGuid guid){applyDiscreteChange("选择 Post-process Profile",[&](auto& settings){settings.PostProcessProfileGuid=guid;});});
if(ImGui::Button("新建 Environment Profile")){MetaCoreEnvironmentProfileDocument value;value.AssetGuid=MetaCoreAssetGuid::Generate();value.Name="Environment "+value.AssetGuid.ToString().substr(0,8);value.IblKtxPath=environment.IblKtxPath;value.SkyboxKtxPath=environment.SkyboxKtxPath;value.SkyboxVisible=environment.SkyboxVisible;value.RotationDegrees=environment.RotationDegrees;value.IndirectLightIntensity=environment.IndirectLightIntensity;value.SkyboxIntensity=environment.SkyboxIntensity;const auto path=projectRoot/"Assets"/"Rendering"/(value.AssetGuid.ToString()+".mcenvironment");if(MetaCoreWriteEnvironmentProfile(path,value,registry))applyDiscreteChange("创建 Environment Profile",[&](auto& settings){settings.EnvironmentProfileGuid=value.AssetGuid;});else editorContext.AddConsoleMessage(MetaCoreLogLevel::Error,"Rendering","无法创建 Environment Profile: "+path.string());}
ImGui::SameLine();if(ImGui::Button("新建 Post-process Profile")){MetaCorePostProcessProfileDocument value;value.AssetGuid=MetaCoreAssetGuid::Generate();value.Name="Post Process "+value.AssetGuid.ToString().substr(0,8);const auto path=projectRoot/"Assets"/"Rendering"/(value.AssetGuid.ToString()+".mcpostprocess");if(MetaCoreWritePostProcessProfile(path,value,registry))applyDiscreteChange("创建 Post-process Profile",[&](auto& settings){settings.PostProcessProfileGuid=value.AssetGuid;});else editorContext.AddConsoleMessage(MetaCoreLogLevel::Error,"Rendering","无法创建 Post-process Profile: "+path.string());}
for(auto& entry:environmentProfiles)if(entry.second.AssetGuid==environment.EnvironmentProfileGuid){auto value=entry.second;bool changed=false;changed|=ImGui::Checkbox("Profile 显示天空盒",&value.SkyboxVisible);changed|=ImGui::DragFloat("Profile 旋转",&value.RotationDegrees,1.0F,0.0F,360.0F);changed|=ImGui::DragFloat("Profile IBL 强度",&value.IndirectLightIntensity,100.0F,0.0F,100000.0F);changed|=ImGui::DragFloat("Profile 天空盒强度",&value.SkyboxIntensity,100.0F,0.0F,100000.0F);if(changed&&!MetaCoreWriteEnvironmentProfile(entry.first,value,registry))editorContext.AddConsoleMessage(MetaCoreLogLevel::Error,"Rendering","Environment Profile 保存失败: "+entry.first.string());break;}
for(auto& entry:postProfiles)if(entry.second.AssetGuid==environment.PostProcessProfileGuid){auto value=entry.second;bool changed=false;int exposure=static_cast<int>(value.ExposureMode);if(ImGui::Combo("曝光模式",&exposure,"手动\0自动\0")){value.ExposureMode=static_cast<MetaCoreExposureMode>(exposure);changed=true;}if(value.ExposureMode==MetaCoreExposureMode::Manual)changed|=ImGui::DragFloat("EV100",&value.ManualEv100,0.1F,-8.0F,24.0F);else{changed|=ImGui::DragFloatRange2("自动曝光范围",&value.AutoExposureMinEv100,&value.AutoExposureMaxEv100,0.1F,-16.0F,32.0F);changed|=ImGui::DragFloat("亮适应速度",&value.AutoExposureSpeedUp,0.1F,0.01F,20.0F);changed|=ImGui::DragFloat("暗适应速度",&value.AutoExposureSpeedDown,0.1F,0.01F,20.0F);}int tone=static_cast<int>(value.ToneMapping);if(ImGui::Combo("Tone Mapping",&tone,"ACES\0Filmic\0Linear\0")){value.ToneMapping=static_cast<MetaCoreToneMappingMode>(tone);changed=true;}changed|=ImGui::Checkbox("Bloom",&value.BloomEnabled);if(value.BloomEnabled){changed|=ImGui::SliderFloat("Bloom 强度",&value.BloomStrength,0.0F,1.0F);bool threshold=value.BloomThreshold>0.0F;if(ImGui::Checkbox("高光阈值Filament 固定 1.0",&threshold)){value.BloomThreshold=threshold?1.0F:0.0F;changed=true;}}int aa=static_cast<int>(value.AntiAliasing);if(ImGui::Combo("Profile AA",&aa,"None\0FXAA\0TAA\0")){value.AntiAliasing=static_cast<MetaCoreAntiAliasingMode>(aa);changed=true;}changed|=ImGui::SliderFloat("饱和度",&value.Saturation,0.0F,2.0F);changed|=ImGui::SliderFloat("对比度",&value.Contrast,0.0F,2.0F);if(changed&&!MetaCoreWritePostProcessProfile(entry.first,value,registry))editorContext.AddConsoleMessage(MetaCoreLogLevel::Error,"Rendering","Post-process Profile 保存失败: "+entry.first.string());break;}
}
if (ImGui::Button("恢复内置环境")) {
applyDiscreteChange("恢复内置环境", [](MetaCoreSceneEnvironmentSettings& settings) {
settings = MetaCoreSceneEnvironmentSettings{};
@ -3504,6 +3527,71 @@ private:
std::filesystem::path LoadedRoot_{};std::filesystem::path Path_{};MetaCorePhysicsSettingsDocument Settings_{};bool Dirty_=false;
};
class MetaCoreRenderingSettingsPanelProvider final : public MetaCoreIEditorPanelProvider {
public:
std::string GetPanelId() const override { return "RenderingSettings"; }
std::string GetPanelTitle() const override { return "渲染设置"; }
bool IsOpenByDefault() const override { return false; }
void DrawPanel(MetaCoreEditorContext& editorContext) override {
const auto assets = editorContext.GetModuleRegistry().ResolveService<MetaCoreIAssetDatabaseService>();
if (!assets || !assets->HasProject()) { ImGui::TextDisabled("没有打开项目。"); return; }
const auto root = assets->GetProjectDescriptor().RootPath;
if (root != LoadedRoot_) {
MetaCoreTypeRegistry registry; MetaCoreRegisterFoundationGeneratedTypes(registry); MetaCoreRegisterRenderingGeneratedTypes(registry);
Path_ = root / "Runtime" / "Rendering.mcruntime";
Settings_ = MetaCoreReadRenderSettings(Path_, registry).value_or(MetaCoreBuildDefaultRenderSettings());
LoadedRoot_ = root; Dirty_ = false;
}
int quality = static_cast<int>(Settings_.Quality);
if (ImGui::Combo("质量档", &quality, "Low\0Medium\0High\0Ultra\0")) { Settings_.Quality = static_cast<MetaCoreRenderQuality>(quality); Dirty_ = true; }
if (ImGui::CollapsingHeader("阴影", ImGuiTreeNodeFlags_DefaultOpen)) {
int cascades = static_cast<int>(Settings_.DirectionalCascadeCount);
int cascadeIndex = cascades == 1 ? 0 : (cascades == 2 ? 1 : 2);
if (ImGui::Combo("方向光级联", &cascadeIndex, "1\0 2\0 4\0")) { Settings_.DirectionalCascadeCount = cascadeIndex == 0 ? 1U : (cascadeIndex == 1 ? 2U : 4U); Settings_.DirectionalCascadeSplits.resize(Settings_.DirectionalCascadeCount); for(std::uint32_t i=0;i<Settings_.DirectionalCascadeCount;++i)Settings_.DirectionalCascadeSplits[i]=static_cast<float>(i+1U)/static_cast<float>(Settings_.DirectionalCascadeCount); Dirty_=true; }
Dirty_ |= ImGui::DragFloat("最大阴影距离", &Settings_.DirectionalShadowDistance, 1.0F, 1.0F, 10000.0F);
for (std::size_t index=0; index<Settings_.DirectionalCascadeSplits.size(); ++index) { ImGui::PushID(static_cast<int>(index)); Dirty_ |= ImGui::SliderFloat("级联分割", &Settings_.DirectionalCascadeSplits[index], 0.001F, 1.0F); ImGui::PopID(); }
int filter = static_cast<int>(Settings_.ShadowFilter); if (ImGui::Combo("阴影过滤", &filter, "Hard/PCF\0DPCF\0PCSS\0")) { Settings_.ShadowFilter=static_cast<MetaCoreShadowFilter>(filter); Dirty_=true; }
int atlasIndex = Settings_.ShadowAtlasSize==1024U?0:(Settings_.ShadowAtlasSize==2048U?1:(Settings_.ShadowAtlasSize==4096U?2:3));
if(ImGui::Combo("Shadow Atlas",&atlasIndex,"1024\0 2048\0 4096\0 8192\0")){Settings_.ShadowAtlasSize=1024U<<atlasIndex;Dirty_=true;}
int localBudget=static_cast<int>(Settings_.MaxShadowCastingPointSpotLights);if(ImGui::SliderInt("Point/Spot 阴影预算",&localBudget,0,64)){Settings_.MaxShadowCastingPointSpotLights=static_cast<std::uint32_t>(localBudget);Dirty_=true;}
}
if (ImGui::CollapsingHeader("抗锯齿与可见性", ImGuiTreeNodeFlags_DefaultOpen)) {
int aa=static_cast<int>(Settings_.AntiAliasing);if(ImGui::Combo("抗锯齿",&aa,"None\0FXAA\0TAA\0")){Settings_.AntiAliasing=static_cast<MetaCoreAntiAliasingMode>(aa);Dirty_=true;}
int msaaIndex=Settings_.MsaaSamples==1U?0:(Settings_.MsaaSamples==2U?1:(Settings_.MsaaSamples==4U?2:3));if(ImGui::Combo("MSAA",&msaaIndex,"1\0 2\0 4\0 8\0")){Settings_.MsaaSamples=1U<<msaaIndex;Dirty_=true;}
Dirty_|=ImGui::Checkbox("自动实例化",&Settings_.EnableInstancing);Dirty_|=ImGui::Checkbox("LOD",&Settings_.EnableLod);Dirty_|=ImGui::Checkbox("HZB 遮挡",&Settings_.EnableOcclusion);
int grace=static_cast<int>(Settings_.OcclusionGraceFrames);if(ImGui::SliderInt("遮挡宽限帧",&grace,0,8)){Settings_.OcclusionGraceFrames=static_cast<std::uint32_t>(grace);Dirty_=true;}
}
if (ImGui::CollapsingHeader("表现组件预算", ImGuiTreeNodeFlags_DefaultOpen)) {
int particles=static_cast<int>(Settings_.GlobalParticleBudget);if(ImGui::DragInt("全局粒子数",&particles,1000,1,1000000)){Settings_.GlobalParticleBudget=static_cast<std::uint32_t>(particles);Dirty_=true;}
int linePoints=static_cast<int>(Settings_.DynamicLinePointBudget);if(ImGui::DragInt("动态线点数",&linePoints,1024,1,4000000)){Settings_.DynamicLinePointBudget=static_cast<std::uint32_t>(linePoints);Dirty_=true;}
}
if (ImGui::CollapsingHeader("调试视图", ImGuiTreeNodeFlags_DefaultOpen)) {
int debugView=static_cast<int>(editorContext.GetViewportRenderer().GetRenderDebugView());
if(ImGui::Combo("模式",&debugView,"Lit\0Unlit\0Wireframe\0Overdraw\0World Normal\0Albedo\0Roughness\0Metallic\0Shadow Cascades\0LOD\0Occlusion\0Reflection Probe\0"))
editorContext.GetViewportRenderer().SetRenderDebugView(static_cast<MetaCoreRenderDebugView>(debugView));
ImGui::TextDisabled("调试替换材质仅作用于 Scene View正式 Player 固定使用 Lit。");
}
if (ImGui::CollapsingHeader("实时统计", ImGuiTreeNodeFlags_DefaultOpen)) {
const auto statistics = editorContext.GetViewportRenderer().GetRenderingStatistics();
ImGui::Text("提交 / 可见: %u / %u", statistics.Submitted, statistics.Visible);
ImGui::Text("剔除: Layer %u 距离 %u 视锥 %u LOD %u 遮挡 %u", statistics.LayerCulled, statistics.DistanceCulled, statistics.FrustumCulled, statistics.LodCulled, statistics.OcclusionCulled);
ImGui::Text("Draw Call / 实例批次: %u / %u", statistics.DrawCallCount, statistics.InstanceBatchCount);
ImGui::Text("CPU 同步 / GPU: %.2f ms / %.2f ms", statistics.CpuFrameMilliseconds, statistics.GpuFrameMilliseconds);
ImGui::Text("可见三角形: %llu 显存估算: %.1f MiB", static_cast<unsigned long long>(statistics.VisibleTriangleCount), static_cast<double>(statistics.EstimatedVideoMemoryBytes)/(1024.0*1024.0));
ImGui::TextDisabled("三角形与显存需资产元数据后才会显示非零值。");
}
if (ImGui::Button("保存")) {
std::vector<std::string> issues; const bool valid=MetaCoreValidateRenderSettings(Settings_,&issues);
for(const auto& issue:issues)editorContext.AddConsoleMessage(valid?MetaCoreLogLevel::Warning:MetaCoreLogLevel::Error,"Rendering",issue);
MetaCoreTypeRegistry registry;MetaCoreRegisterFoundationGeneratedTypes(registry);MetaCoreRegisterRenderingGeneratedTypes(registry);Dirty_=!MetaCoreWriteRenderSettings(Path_,Settings_,registry);
}
ImGui::SameLine();if(ImGui::Button("恢复默认")){Settings_=MetaCoreBuildDefaultRenderSettings();Dirty_=true;}ImGui::SameLine();ImGui::TextDisabled("%s",Dirty_?"未保存;重新打开场景视图后生效":"已保存");
}
private:
std::filesystem::path LoadedRoot_{};std::filesystem::path Path_{};MetaCoreRenderSettingsDocument Settings_{};bool Dirty_=false;
};
class MetaCoreRmlUiVisualEditorPanelProvider final : public MetaCoreIEditorPanelProvider {
public:
std::string GetPanelId() const override { return "RmlUiVisualEditor"; }
@ -10034,6 +10122,7 @@ public:
moduleRegistry.RegisterPanelProvider(std::make_unique<MetaCoreRuntimeUiPanelProvider>());
moduleRegistry.RegisterPanelProvider(std::make_unique<MetaCoreInputMapPanelProvider>());
moduleRegistry.RegisterPanelProvider(std::make_unique<MetaCorePhysicsSettingsPanelProvider>());
moduleRegistry.RegisterPanelProvider(std::make_unique<MetaCoreRenderingSettingsPanelProvider>());
moduleRegistry.RegisterPanelProvider(std::make_unique<MetaCoreRmlUiVisualEditorPanelProvider>());
moduleRegistry.RegisterService<MetaCoreIUiDesignerService>(std::make_shared<MetaCoreNativeUiDesignerService>());
// The central Scene / Game viewport is not a dockable provider, but it keeps

View File

@ -0,0 +1,2 @@
material { name : MetaCoreDecal, shadingModel : unlit, blending : transparent, depthWrite : false, doubleSided : true, parameters : [ { type : float4, name : baseColor }, { type : float, name : opacity } ] }
fragment { void material(inout MaterialInputs material) { prepareMaterial(material); material.baseColor=float4(materialParams.baseColor.rgb,materialParams.baseColor.a*materialParams.opacity); } }

View File

@ -0,0 +1,2 @@
material { name : MetaCoreLineTrail, shadingModel : unlit, blending : transparent, depthWrite : false, doubleSided : true, requires : [ color ], parameters : [ { type : float4, name : tint } ] }
fragment { void material(inout MaterialInputs material) { prepareMaterial(material); material.baseColor=getColor()*materialParams.tint; } }

View File

@ -0,0 +1,2 @@
material { name : MetaCoreParticle, shadingModel : unlit, blending : transparent, depthWrite : false, doubleSided : true, requires : [ color ], parameters : [ { type : float4, name : tint } ] }
fragment { void material(inout MaterialInputs material) { prepareMaterial(material); material.baseColor=getColor()*materialParams.tint; } }

View File

@ -0,0 +1,2 @@
material { name : MetaCorePbr, shadingModel : lit, parameters : [ { type : float4, name : baseColor }, { type : float, name : metallic }, { type : float, name : roughness } ] }
fragment { void material(inout MaterialInputs material) { prepareMaterial(material); material.baseColor=materialParams.baseColor; material.metallic=materialParams.metallic; material.roughness=materialParams.roughness; } }

View File

@ -0,0 +1,2 @@
material { name : MetaCoreTerrain, shadingModel : lit, parameters : [ { type : float4, name : baseColor }, { type : float, name : roughness } ] }
fragment { void material(inout MaterialInputs material) { prepareMaterial(material); material.baseColor=materialParams.baseColor; material.metallic=0.0; material.roughness=materialParams.roughness; } }

View File

@ -0,0 +1,2 @@
material { name : MetaCoreTransparent, shadingModel : unlit, blending : transparent, depthWrite : false, doubleSided : true, parameters : [ { type : float4, name : color }, { type : float, name : opacity } ] }
fragment { void material(inout MaterialInputs material) { prepareMaterial(material); material.baseColor=float4(materialParams.color.rgb,materialParams.color.a*materialParams.opacity); } }

View File

@ -0,0 +1,2 @@
material { name : MetaCoreUnlit, shadingModel : unlit, parameters : [ { type : float4, name : color } ] }
fragment { void material(inout MaterialInputs material) { prepareMaterial(material); material.baseColor=materialParams.color; } }

View File

@ -0,0 +1,21 @@
material {
name : MetaCoreRenderDebug,
shadingModel : unlit,
doubleSided : true,
requires : [ tangents ],
parameters : [
{ type : int, name : debugMode },
{ type : float4, name : debugValue }
],
}
fragment {
void material(inout MaterialInputs material) {
prepareMaterial(material);
float4 value = materialParams.debugValue;
if (materialParams.debugMode == 1) {
value = float4(getWorldNormalVector() * 0.5 + 0.5, 1.0);
}
material.baseColor = value;
}
}

View File

@ -115,7 +115,9 @@ void MetaCoreEditorViewportRenderer::RenderSnapshotToViewport(
RenderDevice_->SetSceneViewportRect(ViewportRect_);
FilamentSceneBridge_.Resize(static_cast<int>(ViewportRect_.Width), static_cast<int>(ViewportRect_.Height));
FilamentSceneBridge_.ApplySceneView(sceneView);
FilamentSceneBridge_.SyncScene(snapshot, scene, false, false);
// Game View is driven by the primary scene camera, so its post-process
// override must use the same Camera > Scene > engine-default precedence as Player.
FilamentSceneBridge_.SyncScene(snapshot, scene, false, true);
}
void MetaCoreEditorViewportRenderer::RenderAll() {
@ -135,6 +137,14 @@ void MetaCoreEditorViewportRenderer::SetEditorGridVisible(bool visible) {
FilamentSceneBridge_.SetEditorGridVisible(visible);
}
void MetaCoreEditorViewportRenderer::SetRenderDebugView(MetaCoreRenderDebugView debugView) {
FilamentSceneBridge_.SetRenderDebugView(debugView);
}
MetaCoreRenderDebugView MetaCoreEditorViewportRenderer::GetRenderDebugView() const {
return FilamentSceneBridge_.GetRenderDebugView();
}
void MetaCoreEditorViewportRenderer::SetRuntimeOverlayRenderCallback(
MetaCoreFilamentSceneBridge::RuntimeOverlayRenderCallback callback
) {
@ -149,6 +159,10 @@ void* MetaCoreEditorViewportRenderer::GetFilamentTexturePointer() const {
return FilamentSceneBridge_.GetFilamentTexturePointer();
}
MetaCoreVisibilityStatistics MetaCoreEditorViewportRenderer::GetRenderingStatistics() const {
return FilamentSceneBridge_.GetRenderingStatistics();
}
bool MetaCoreEditorViewportRenderer::TryGetObjectWorldMatrix(MetaCoreId objectId, glm::mat4& worldMatrix) const {
return FilamentSceneBridge_.TryGetObjectWorldMatrix(objectId, worldMatrix);
}

View File

@ -10,6 +10,7 @@
#include "MetaCoreFoundation/MetaCoreAssetRegistry.h"
#include "MetaCoreFoundation/MetaCoreRuntimeAssetRegistry.h"
#include "MetaCoreFoundation/MetaCoreGeneratedReflection.h"
#include "MetaCoreFoundation/MetaCoreHash.h"
#include "MetaCoreRendering/MetaCoreRendering.h"
#include <filament/Engine.h>
@ -61,6 +62,7 @@
#include <fstream>
#include <algorithm>
#include <array>
#include <bit>
#include <cctype>
#include <cstddef>
#include <cstdlib>
@ -77,6 +79,7 @@
#include <functional>
#include <iterator>
#include <limits>
#include <numeric>
// 引入平台和 OpenGL 头文件以创建纹理
#if defined(_WIN32)
@ -459,6 +462,9 @@ public:
}
}
void SetRenderDebugView(MetaCoreRenderDebugView debugView) { ActiveDebugView_ = debugView; }
[[nodiscard]] MetaCoreRenderDebugView GetRenderDebugView() const { return ActiveDebugView_; }
void SetRuntimeOverlayRenderCallback(MetaCoreFilamentSceneBridge::RuntimeOverlayRenderCallback callback) {
RuntimeOverlayRenderCallback_ = std::move(callback);
}
@ -494,6 +500,8 @@ public:
std::cout << "FilamentSceneBridge: Shutting down..." << std::endl;
DebugRenderTargetState("Shutdown begin");
RestoreDebugMaterials();
DestroyProductionRenderables();
DestroyEditorGridRenderable();
for (auto& [hostRootId, pendingLoad] : PendingResourceLoads_) {
if (pendingLoad.Loader != nullptr) {
@ -568,6 +576,7 @@ public:
DestroyMaterialTextureCache();
DestroyEnvironmentLighting();
DestroySolidColorSkybox();
if(RenderDebugMaterial_!=nullptr){Engine_->destroy(RenderDebugMaterial_);RenderDebugMaterial_=nullptr;}
for (auto& [id, entity] : SceneLightEntities_) {
if (Scene_) {
@ -682,19 +691,71 @@ public:
DestroyEnvironmentLighting();
}
ProjectRootPath_ = projectRootPath;
RenderSettings_ = MetaCoreBuildDefaultRenderSettings();
if (!ProjectRootPath_.empty()) {
MetaCoreTypeRegistry registry;
MetaCoreRegisterFoundationGeneratedTypes(registry);
MetaCoreRegisterRenderingGeneratedTypes(registry);
const auto loaded = MetaCoreReadRenderSettings(ProjectRootPath_ / "Runtime" / "Rendering.mcruntime", registry);
if (loaded) RenderSettings_ = *loaded;
}
ReloadRenderSettings();
ReloadRenderProfiles();
RenderConfigurationFingerprint_ = ComputeRenderConfigurationFingerprint();
LastRenderConfigurationPoll_ = std::chrono::steady_clock::now();
ConfigurePostProcessing();
SynchronizeEnvironment(ActiveEnvironmentSettings_);
}
void ReloadRenderSettings() {
RenderSettings_ = MetaCoreBuildDefaultRenderSettings();
if (ProjectRootPath_.empty()) return;
MetaCoreTypeRegistry registry;
MetaCoreRegisterFoundationGeneratedTypes(registry);
MetaCoreRegisterRenderingGeneratedTypes(registry);
const auto loaded = MetaCoreReadRenderSettings(ProjectRootPath_ / "Runtime" / "Rendering.mcruntime", registry);
if (loaded) RenderSettings_ = *loaded;
}
[[nodiscard]] std::uint64_t ComputeRenderConfigurationFingerprint() const {
if (ProjectRootPath_.empty()) return 0U;
std::vector<std::filesystem::path> files;
const auto settingsPath = ProjectRootPath_ / "Runtime" / "Rendering.mcruntime";
std::error_code error;
if (std::filesystem::is_regular_file(settingsPath, error)) files.push_back(settingsPath);
for (const auto& relativeRoot : {std::filesystem::path("Assets"), std::filesystem::path("Runtime")}) {
const auto root = ProjectRootPath_ / relativeRoot;
if (!std::filesystem::is_directory(root, error)) continue;
for (std::filesystem::recursive_directory_iterator iterator(root, std::filesystem::directory_options::skip_permission_denied, error), end;
!error && iterator != end; iterator.increment(error)) {
if (!iterator->is_regular_file(error)) continue;
const auto extension = iterator->path().extension();
if (extension == ".mcenvironment" || extension == ".mcpostprocess") files.push_back(iterator->path());
}
error.clear();
}
std::sort(files.begin(), files.end());
std::uint64_t fingerprint = 1469598103934665603ULL;
const auto combine = [&fingerprint](std::uint64_t value) {
fingerprint ^= value;
fingerprint *= 1099511628211ULL;
};
for (const auto& path : files) {
combine(static_cast<std::uint64_t>(std::hash<std::string>{}(path.generic_string())));
combine(static_cast<std::uint64_t>(std::filesystem::file_size(path, error)));
error.clear();
combine(static_cast<std::uint64_t>(std::filesystem::last_write_time(path, error).time_since_epoch().count()));
error.clear();
}
return fingerprint;
}
[[nodiscard]] bool PollRenderConfigurationChanges() {
const auto now = std::chrono::steady_clock::now();
if (now - LastRenderConfigurationPoll_ < std::chrono::milliseconds(250)) return false;
LastRenderConfigurationPoll_ = now;
const std::uint64_t fingerprint = ComputeRenderConfigurationFingerprint();
if (fingerprint == RenderConfigurationFingerprint_) return false;
RenderConfigurationFingerprint_ = fingerprint;
ReloadRenderSettings();
ReloadRenderProfiles();
DestroyEnvironmentLighting();
MetaCoreFilamentDebugLog("Rendering settings/Profile files changed; reloaded and reapplied");
return true;
}
void ReloadRenderProfiles() {
EnvironmentProfiles_.clear();
PostProcessProfiles_.clear();
@ -732,14 +793,21 @@ public:
fallback.AntiAliasing = RenderSettings_.AntiAliasing;
fallback.MsaaSamples = RenderSettings_.MsaaSamples;
const auto& profile = requestedProfile ? *requestedProfile : fallback;
ActivePostProcessProfile_ = profile;
const filament::ShadowType shadowType = RenderSettings_.ShadowFilter == MetaCoreShadowFilter::Hard
? filament::ShadowType::PCF
: (RenderSettings_.ShadowFilter == MetaCoreShadowFilter::PcfLow ? filament::ShadowType::DPCF : filament::ShadowType::PCSS);
View_->setShadowType(shadowType);
const bool fxaa = profile.AntiAliasing == MetaCoreAntiAliasingMode::Fxaa;
View_->setAntiAliasing(fxaa ? filament::AntiAliasing::FXAA : filament::AntiAliasing::NONE);
filament::TemporalAntiAliasingOptions taa;
taa.enabled = profile.AntiAliasing == MetaCoreAntiAliasingMode::Taa;
View_->setTemporalAntiAliasingOptions(taa);
filament::MultiSampleAntiAliasingOptions msaa;
msaa.enabled = profile.MsaaSamples > 1U;
msaa.sampleCount = static_cast<std::uint8_t>(profile.MsaaSamples);
const std::uint32_t effectiveMsaa = profile.AntiAliasing == MetaCoreAntiAliasingMode::Taa ? 1U : profile.MsaaSamples;
if (effectiveMsaa != profile.MsaaSamples) std::cerr << "[MetaCoreRender] TAA+MSAA is unsupported; requested MSAA=" << profile.MsaaSamples << "; fallback=MSAA 1\n";
msaa.enabled = effectiveMsaa > 1U;
msaa.sampleCount = static_cast<std::uint8_t>(effectiveMsaa);
View_->setMultiSampleAntiAliasingOptions(msaa);
filament::BloomOptions bloom;
bloom.enabled = profile.BloomEnabled;
@ -763,9 +831,24 @@ public:
: 0.5F * (profile.AutoExposureMinEv100 + profile.AutoExposureMaxEv100);
const float shutter = (aperture * aperture) / std::exp2(ev100);
Camera_->setExposure(aperture, shutter, 100.0F);
AutoExposureEv100_ = ev100;
}
}
void UpdateAutoExposure() {
if (!Camera_ || ActivePostProcessProfile_.ExposureMode != MetaCoreExposureMode::Automatic) return;
float illuminance = std::max(0.001F, ActiveEnvironmentSettings_.IndirectLightIntensity * 0.001F);
for (const auto& light : LastSyncSnapshot_.Lights) {
if (!light.Enabled) continue;
if (light.Type == MetaCoreLightType::Directional) illuminance += std::max(0.0F, light.Intensity);
else { const glm::vec3 offset=glm::vec3(light.WorldMatrix[3])-CurrentSceneView_.CameraPosition;const float distanceSquared=std::max(0.01F,glm::dot(offset,offset));illuminance+=std::max(0.0F,light.Intensity)/distanceSquared; }
}
const float target = std::clamp(std::log2(illuminance * 8.0F), ActivePostProcessProfile_.AutoExposureMinEv100, ActivePostProcessProfile_.AutoExposureMaxEv100);
const auto now=std::chrono::steady_clock::now();const float delta=LastAutoExposureUpdate_.time_since_epoch().count()==0?1.0F/60.0F:std::clamp(std::chrono::duration<float>(now-LastAutoExposureUpdate_).count(),0.0F,0.25F);LastAutoExposureUpdate_=now;
const float speed=target>AutoExposureEv100_?ActivePostProcessProfile_.AutoExposureSpeedUp:ActivePostProcessProfile_.AutoExposureSpeedDown;AutoExposureEv100_+= (target-AutoExposureEv100_)*(1.0F-std::exp(-std::max(0.01F,speed)*delta));
constexpr float aperture=16.0F;Camera_->setExposure(aperture,(aperture*aperture)/std::exp2(AutoExposureEv100_),100.0F);
}
void DestroyMaterialTextureCache() {
if (Engine_) {
Engine_->flushAndWait();
@ -1491,10 +1574,105 @@ public:
return true;
}
void SyncScene(const MetaCoreSceneRenderSyncSnapshot& snapshot, MetaCoreScene* scene, bool compatibilityMeshOnly, bool useScenePrimaryCamera) {
void SyncScene(const MetaCoreSceneRenderSyncSnapshot& inputSnapshot, MetaCoreScene* scene, bool compatibilityMeshOnly, bool useScenePrimaryCamera) {
if (!AssetLoader_) return;
const bool renderConfigurationChanged = PollRenderConfigurationChanges();
RestoreDebugMaterials();
const auto syncStart = std::chrono::steady_clock::now();
(void)compatibilityMeshOnly;
if (useScenePrimaryCamera) {
MetaCoreSceneView primarySceneView;
if (MetaCoreSceneRenderSync::TryBuildSceneViewFromPrimaryCamera(inputSnapshot, primarySceneView)) ApplySceneView(primarySceneView);
}
MetaCoreSceneRenderSyncSnapshot resolvedSnapshot = inputSnapshot;
if (RenderSettings_.EnableLod) {
for (const auto& lodGroup : resolvedSnapshot.LodGroups) {
auto renderable = std::find_if(resolvedSnapshot.Renderables.begin(), resolvedSnapshot.Renderables.end(), [&](const auto& value) { return value.ObjectId == lodGroup.ObjectId; });
if (renderable == resolvedSnapshot.Renderables.end() || !lodGroup.Component.Enabled || lodGroup.Component.Levels.empty()) continue;
const glm::vec3 position = glm::vec3(lodGroup.WorldMatrix[3]);
const float radius = std::max({glm::length(glm::vec3(lodGroup.WorldMatrix[0])), glm::length(glm::vec3(lodGroup.WorldMatrix[1])), glm::length(glm::vec3(lodGroup.WorldMatrix[2])), 0.001F});
float screenHeight = 0.0F;
if (CurrentSceneView_.ProjectionMode == MetaCoreCameraProjectionMode::Orthographic) screenHeight = radius / std::max(0.001F, CurrentSceneView_.OrthographicSize);
else { const float distance = std::max(0.001F, glm::distance(position, CurrentSceneView_.CameraPosition)); const float halfFov = glm::radians(CurrentSceneView_.VerticalFieldOfViewDegrees * 0.5F); screenHeight = radius / (distance * std::max(0.001F, std::tan(halfFov))); }
const auto previous = LodSelections_.contains(lodGroup.ObjectId) ? LodSelections_.at(lodGroup.ObjectId) : -1;
const std::int32_t selected = MetaCoreSelectLod(lodGroup.Component, screenHeight, previous);
LodSelections_.insert_or_assign(lodGroup.ObjectId, selected);
if (selected < 0) { renderable->Visible = false; continue; }
const auto& level = lodGroup.Component.Levels[static_cast<std::size_t>(selected)];
if (level.MeshAssetGuid.IsValid()) { renderable->MeshSource = MetaCoreMeshSourceKind::Asset; renderable->MeshAssetGuid = level.MeshAssetGuid; renderable->SourceModelAssetGuid = {}; renderable->SourceModelPath.clear(); }
if (!level.MaterialAssetGuids.empty()) renderable->MaterialAssetGuids = level.MaterialAssetGuids;
}
}
const auto& snapshot = resolvedSnapshot;
RenderingStatistics_ = {};
const auto& viewport = View_->getViewport();
const float aspect = viewport.height > 0U ? static_cast<float>(viewport.width) / static_cast<float>(viewport.height) : 1.0F;
const glm::mat4 viewMatrix = glm::lookAt(CurrentSceneView_.CameraPosition, CurrentSceneView_.CameraTarget, CurrentSceneView_.CameraUp);
const glm::mat4 projectionMatrix = CurrentSceneView_.ProjectionMode == MetaCoreCameraProjectionMode::Orthographic
? glm::ortho(-CurrentSceneView_.OrthographicSize * aspect, CurrentSceneView_.OrthographicSize * aspect,
-CurrentSceneView_.OrthographicSize, CurrentSceneView_.OrthographicSize,
CurrentSceneView_.NearClip, CurrentSceneView_.FarClip)
: glm::perspective(glm::radians(CurrentSceneView_.VerticalFieldOfViewDegrees), aspect,
CurrentSceneView_.NearClip, CurrentSceneView_.FarClip);
const glm::mat4 viewProjection = projectionMatrix * viewMatrix;
const MetaCoreViewFrustum frustum = MetaCoreExtractFrustum(viewProjection);
std::vector<MetaCoreBvhItem> visibilityItems;
visibilityItems.reserve(resolvedSnapshot.Renderables.size());
for (const auto& renderable : resolvedSnapshot.Renderables) {
if (!renderable.Visible) {
const bool lodCulled = LodSelections_.contains(renderable.ObjectId) && LodSelections_.at(renderable.ObjectId) < 0;
MetaCoreAccumulateVisibilityResult(RenderingStatistics_, lodCulled ? MetaCoreVisibilityResult::LodCulled : MetaCoreVisibilityResult::DistanceCulled);
continue;
}
visibilityItems.push_back({renderable.ObjectId, renderable.WorldBounds, renderable.RenderingLayerMask,
renderable.MaxDrawDistance, !renderable.StaticForCulling});
}
VisibilityBvh_.Build(std::move(visibilityItems));
const auto bvhVisible = VisibilityBvh_.Query(frustum, CurrentSceneView_.CameraPosition,
CurrentSceneView_.CullingMask, &RenderingStatistics_);
const std::unordered_set<MetaCoreId> bvhVisibleSet(bvhVisible.begin(), bvhVisible.end());
const glm::vec3 cameraDirection = glm::normalize(CurrentSceneView_.CameraTarget - CurrentSceneView_.CameraPosition);
const bool cameraCut = !HasPreviousVisibilityCamera_ ||
glm::distance(PreviousVisibilityCameraPosition_, CurrentSceneView_.CameraPosition) > 2.0F ||
glm::dot(PreviousVisibilityCameraDirection_, cameraDirection) < 0.8F;
OcclusionHistory_.BeginFrame();
const auto projectBounds = [&](const MetaCoreRenderBounds& bounds, glm::vec4& screenBounds, float& nearestDepth, float& farthestDepth) {
const std::array<glm::vec3,8> corners{{
{bounds.Minimum.x,bounds.Minimum.y,bounds.Minimum.z},{bounds.Maximum.x,bounds.Minimum.y,bounds.Minimum.z},
{bounds.Minimum.x,bounds.Maximum.y,bounds.Minimum.z},{bounds.Maximum.x,bounds.Maximum.y,bounds.Minimum.z},
{bounds.Minimum.x,bounds.Minimum.y,bounds.Maximum.z},{bounds.Maximum.x,bounds.Minimum.y,bounds.Maximum.z},
{bounds.Minimum.x,bounds.Maximum.y,bounds.Maximum.z},{bounds.Maximum.x,bounds.Maximum.y,bounds.Maximum.z}}};
glm::vec2 minimum(1.0F), maximum(0.0F);bool valid=false;
for(const glm::vec3& corner:corners){const glm::vec4 clip=viewProjection*glm::vec4(corner,1.0F);if(clip.w<=0.0001F)continue;const glm::vec2 uv=glm::vec2(clip)/clip.w*0.5F+0.5F;minimum=glm::min(minimum,uv);maximum=glm::max(maximum,uv);valid=true;}
screenBounds={minimum.x,minimum.y,maximum.x,maximum.y};const float radius=glm::length(bounds.Extents());const float distance=glm::distance(CurrentSceneView_.CameraPosition,bounds.Center());nearestDepth=std::max(CurrentSceneView_.NearClip,distance-radius);farthestDepth=std::min(CurrentSceneView_.FarClip,distance+radius);return valid;
};
std::unordered_set<MetaCoreId> currentVisible;
for (auto& renderable : resolvedSnapshot.Renderables) {
if (!renderable.Visible || !bvhVisibleSet.contains(renderable.ObjectId)) { renderable.Visible=false; continue; }
bool queryVisible=true;glm::vec4 screenBounds{};float nearestDepth=0.0F,farthestDepth=0.0F;
if(RenderSettings_.EnableOcclusion&&projectBounds(renderable.WorldBounds,screenBounds,nearestDepth,farthestDepth))queryVisible=PreviousHzb_.IsVisible(screenBounds,nearestDepth);
const bool newlyVisible=!LastFrameVisibleObjects_.contains(renderable.ObjectId);
const bool finalVisible=!RenderSettings_.EnableOcclusion||OcclusionHistory_.IsVisible(renderable.ObjectId,queryVisible,cameraCut,!renderable.StaticForCulling,newlyVisible,RenderSettings_.OcclusionGraceFrames);
renderable.Visible=finalVisible;
if(finalVisible){currentVisible.insert(renderable.ObjectId);MetaCoreAccumulateVisibilityResult(RenderingStatistics_,MetaCoreVisibilityResult::Visible,renderable.TriangleCount);RenderingStatistics_.EstimatedVideoMemoryBytes+=renderable.EstimatedVideoMemoryBytes;}
else MetaCoreAccumulateVisibilityResult(RenderingStatistics_,MetaCoreVisibilityResult::OcclusionCulled);
}
constexpr std::uint32_t hzbWidth=64U,hzbHeight=36U;
std::vector<float> softwareDepth(static_cast<std::size_t>(hzbWidth)*hzbHeight,CurrentSceneView_.FarClip);
for(const auto& renderable:resolvedSnapshot.Renderables){if(!renderable.Visible||!renderable.StaticForCulling)continue;glm::vec4 bounds{};float nearestDepth=0.0F,farthestDepth=0.0F;if(!projectBounds(renderable.WorldBounds,bounds,nearestDepth,farthestDepth))continue;const std::uint32_t minX=std::min(hzbWidth-1U,static_cast<std::uint32_t>(std::clamp(bounds.x,0.0F,1.0F)*hzbWidth)),maxX=std::min(hzbWidth-1U,static_cast<std::uint32_t>(std::clamp(bounds.z,0.0F,1.0F)*hzbWidth));const std::uint32_t minY=std::min(hzbHeight-1U,static_cast<std::uint32_t>(std::clamp(bounds.y,0.0F,1.0F)*hzbHeight)),maxY=std::min(hzbHeight-1U,static_cast<std::uint32_t>(std::clamp(bounds.w,0.0F,1.0F)*hzbHeight));for(std::uint32_t y=minY;y<=maxY;++y)for(std::uint32_t x=minX;x<=maxX;++x)softwareDepth[static_cast<std::size_t>(y)*hzbWidth+x]=std::min(softwareDepth[static_cast<std::size_t>(y)*hzbWidth+x],farthestDepth);}
(void)PreviousHzb_.Build(hzbWidth,hzbHeight,softwareDepth);LastFrameVisibleObjects_=std::move(currentVisible);PreviousVisibilityCameraPosition_=CurrentSceneView_.CameraPosition;PreviousVisibilityCameraDirection_=cameraDirection;HasPreviousVisibilityCamera_=true;
std::vector<MetaCoreInstanceCandidate> instanceCandidates;
instanceCandidates.reserve(snapshot.Renderables.size());
for (const auto& renderable : snapshot.Renderables) {
std::uint64_t stateHash = static_cast<std::uint64_t>(renderable.MeshSource) | (static_cast<std::uint64_t>(renderable.BuiltinMesh) << 8U) | (static_cast<std::uint64_t>(renderable.AlphaMode) << 16U) | (static_cast<std::uint64_t>(renderable.DoubleSided) << 24U);
std::uint64_t parameterHash = std::hash<float>{}(renderable.Metallic) ^ (std::hash<float>{}(renderable.Roughness) << 1U) ^ (std::hash<float>{}(renderable.BaseColor.x) << 2U) ^ (std::hash<float>{}(renderable.BaseColor.y) << 3U) ^ (std::hash<float>{}(renderable.BaseColor.z) << 4U);
instanceCandidates.push_back({renderable.ObjectId, renderable.MeshAssetGuid, renderable.MaterialAssetGuids, parameterHash, stateHash, renderable.Visible});
}
const auto instanceBatches = MetaCoreBuildInstanceBatches(instanceCandidates, RenderSettings_.EnableInstancing);
RenderingStatistics_.InstanceBatchCount = static_cast<std::uint32_t>(instanceBatches.size());
RenderingStatistics_.DrawCallCount = static_cast<std::uint32_t>(instanceBatches.size());
LastSyncSnapshot_ = snapshot;
MetaCoreSceneEnvironmentSettings resolvedEnvironment = LastSyncSnapshot_.Environment;
if (resolvedEnvironment.EnvironmentProfileGuid.IsValid()) {
@ -1515,7 +1693,7 @@ public:
MetaCoreAssetGuid cameraPostProcessGuid{};
if (useScenePrimaryCamera && LastSyncSnapshot_.PrimaryCamera) cameraPostProcessGuid = LastSyncSnapshot_.PrimaryCamera->PostProcessProfileGuid;
const MetaCoreAssetGuid scenePostProcessGuid = LastSyncSnapshot_.Environment.PostProcessProfileGuid;
if (scenePostProcessGuid != ActiveScenePostProcessGuid_ || cameraPostProcessGuid != ActiveCameraPostProcessGuid_) {
if (renderConfigurationChanged || scenePostProcessGuid != ActiveScenePostProcessGuid_ || cameraPostProcessGuid != ActiveCameraPostProcessGuid_) {
const MetaCorePostProcessProfileDocument* sceneProfile = nullptr;
const MetaCorePostProcessProfileDocument* cameraProfile = nullptr;
if (const auto found = PostProcessProfiles_.find(scenePostProcessGuid); found != PostProcessProfiles_.end()) sceneProfile = &found->second;
@ -1574,13 +1752,7 @@ public:
return parentVisible;
};
SyncSceneLights(LastSyncSnapshot_);
if (useScenePrimaryCamera) {
MetaCoreSceneView primarySceneView;
if (MetaCoreSceneRenderSync::TryBuildSceneViewFromPrimaryCamera(LastSyncSnapshot_, primarySceneView)) {
ApplySceneView(primarySceneView);
}
}
SynchronizeProductionComponents(LastSyncSnapshot_);
// 1. 收集当前 snapshot 中所有存在的模型 Root ID
std::unordered_set<MetaCoreId> activeHostRootIds;
for (const auto& renderable : snapshot.Renderables) {
@ -2385,7 +2557,9 @@ public:
}
}
ApplyRenderDebugView(snapshot, renderableMap);
ApplyAnimationStates(snapshot);
RenderingStatistics_.CpuFrameMilliseconds = std::chrono::duration<float, std::milli>(std::chrono::steady_clock::now() - syncStart).count();
}
void ApplyAnimationStates(const MetaCoreSceneRenderSyncSnapshot& snapshot) {
@ -2493,6 +2667,7 @@ public:
}
++RenderFrameIndex_;
UpdateAutoExposure();
if (!PendingRenderTargetResources_.empty()) {
DebugRenderTargetState("RenderAll before beginFrame with pending destroy");
}
@ -2546,6 +2721,8 @@ public:
}
Renderer_->endFrame();
const auto history = Renderer_->getFrameInfoHistory(1U);
if (!history.empty() && history.back().gpuFrameDuration >= 0) RenderingStatistics_.GpuFrameMilliseconds = static_cast<float>(history.back().gpuFrameDuration) / 1000000.0F;
DestroyDeferredRenderTargetResources(true);
} else if (!PendingRenderTargetResources_.empty()) {
@ -2732,6 +2909,7 @@ public:
bool HasRuntimeSyncFailure() const { return false; }
const std::string& GetLastRuntimeSyncFailure() const { return EmptyString_; }
MetaCoreVisibilityStatistics GetRenderingStatistics() const { return RenderingStatistics_; }
bool VerifyTransformForTesting(MetaCoreId objectId, const glm::mat4& expectedMatrix) const {
auto it = ObjectToFilamentEntity_.find(objectId);
@ -2864,13 +3042,180 @@ private:
}
}
struct ProductionRenderableResource {
utils::Entity Entity{};
filament::VertexBuffer* Vertices = nullptr;
filament::IndexBuffer* Indices = nullptr;
filament::MaterialInstance* MaterialInstance = nullptr;
std::size_t VertexCapacity = 0U;
std::size_t IndexCapacity = 0U;
};
struct DebugMaterialOverride {
utils::Entity Entity{};
std::size_t Primitive = 0U;
filament::MaterialInstance* Original = nullptr;
filament::MaterialInstance* Debug = nullptr;
};
[[nodiscard]] filament::Material* EnsureRenderDebugMaterial() {
if(RenderDebugMaterial_!=nullptr)return RenderDebugMaterial_;
const std::array<std::filesystem::path,4> candidates{
"render_debug.filamat","Engine/Materials/render_debug.filamat","../render_debug.filamat","build/linux-development/render_debug.filamat"};
for(const auto& path:candidates){std::ifstream input(path,std::ios::binary);if(!input)continue;std::vector<char> bytes((std::istreambuf_iterator<char>(input)),{});if(bytes.empty())continue;RenderDebugMaterial_=filament::Material::Builder().package(bytes.data(),bytes.size()).build(*Engine_);if(RenderDebugMaterial_)break;}
return RenderDebugMaterial_;
}
void RestoreDebugMaterials() {
if(Engine_==nullptr){DebugMaterialOverrides_.clear();return;}auto& rm=Engine_->getRenderableManager();
for(auto& value:DebugMaterialOverrides_){const auto instance=rm.getInstance(value.Entity);if(instance&&value.Primitive<rm.getPrimitiveCount(instance)&&value.Original)rm.setMaterialInstanceAt(instance,value.Primitive,value.Original);if(value.Debug)Engine_->destroy(value.Debug);}
DebugMaterialOverrides_.clear();
}
void ApplyRenderDebugView(const MetaCoreSceneRenderSyncSnapshot& snapshot,
const std::unordered_map<MetaCoreId,const MetaCoreRenderSyncRenderable*>& renderables) {
RestoreDebugMaterials();if(ActiveDebugView_==MetaCoreRenderDebugView::Lit||Engine_==nullptr)return;const filament::Material* material=EnsureRenderDebugMaterial();if(material==nullptr)return;
std::vector<MetaCoreReflectionProbeCandidate> probes;for(const auto& probe:snapshot.ReflectionProbes)probes.push_back({probe.ObjectId,glm::vec3(probe.WorldMatrix[3]),probe.Component});
auto& rm=Engine_->getRenderableManager();for(const auto& [entity,objectId]:EntityToObjectId_){const auto instance=rm.getInstance(entity);const auto found=renderables.find(objectId);if(!instance||found==renderables.end())continue;const auto& renderable=*found->second;
glm::vec4 value{renderable.BaseColor,1.0F};int mode=0;
switch(ActiveDebugView_){case MetaCoreRenderDebugView::WorldNormal:mode=1;break;case MetaCoreRenderDebugView::Roughness:value=glm::vec4(renderable.Roughness,renderable.Roughness,renderable.Roughness,1);break;case MetaCoreRenderDebugView::Metallic:value=glm::vec4(renderable.Metallic,renderable.Metallic,renderable.Metallic,1);break;case MetaCoreRenderDebugView::Lod:{const int level=LodSelections_.contains(objectId)?LodSelections_.at(objectId):0;const std::array<glm::vec3,4> colors{{{0.2F,0.9F,0.3F},{0.95F,0.8F,0.15F},{1.0F,0.35F,0.1F},{0.65F,0.2F,0.9F}}};value=glm::vec4(colors[static_cast<std::size_t>(std::max(0,level))%colors.size()],1);break;}case MetaCoreRenderDebugView::Occlusion:value={0.1F,0.85F,0.25F,1};break;case MetaCoreRenderDebugView::ReflectionProbe:{const auto selected=MetaCoreSelectReflectionProbes(probes,renderable.WorldBounds.Center());value=selected.empty()?glm::vec4(0.1F,0.1F,0.1F,1):glm::vec4(selected.size()>1U?0.2F:0.1F,selected.front().Weight,1.0F-selected.front().Weight,1);break;}case MetaCoreRenderDebugView::ShadowCascades:{const float normalized=glm::distance(CurrentSceneView_.CameraPosition,renderable.WorldBounds.Center())/std::max(1.0F,RenderSettings_.DirectionalShadowDistance);value=normalized<0.08F?glm::vec4(1,0.2F,0.2F,1):(normalized<0.22F?glm::vec4(0.2F,1,0.2F,1):(normalized<0.48F?glm::vec4(0.2F,0.4F,1,1):glm::vec4(1,0.8F,0.2F,1)));break;}case MetaCoreRenderDebugView::Overdraw:value={1.0F,0.08F,0.02F,1};break;case MetaCoreRenderDebugView::Wireframe:value={0.05F,0.9F,1.0F,1};break;case MetaCoreRenderDebugView::Unlit:case MetaCoreRenderDebugView::Albedo:default:break;}
for(std::size_t primitive=0;primitive<rm.getPrimitiveCount(instance);++primitive){auto* original=rm.getMaterialInstanceAt(instance,primitive);auto* debug=material->createInstance();if(!original||!debug)continue;debug->setParameter("debugMode",mode);debug->setParameter("debugValue",filament::math::float4{value.x,value.y,value.z,value.w});rm.setMaterialInstanceAt(instance,primitive,debug);DebugMaterialOverrides_.push_back({entity,primitive,original,debug});}
}
}
[[nodiscard]] static std::uint64_t ProductionRenderableKey(MetaCoreId objectId, std::uint8_t kind) {
return MetaCoreHashCombine(static_cast<std::uint64_t>(objectId), kind);
}
[[nodiscard]] filament::Material* EnsureProductionMaterial() {
if (ProductionMaterial_ != nullptr) return ProductionMaterial_;
ProductionMaterial_ = LoadEditorGridMaterial();
return ProductionMaterial_;
}
void DestroyProductionRenderableResource(ProductionRenderableResource& resource) {
if (Engine_ == nullptr) return;
// Filament requires the Renderable component to release all of its resource
// references before its material instance or geometry buffers are destroyed.
// Particle, line, trail, decal, and terrain renderables all pass through this
// path, including the particle-capacity growth path that rebuilds the mesh.
if (!resource.Entity.isNull()) {
if (Scene_ != nullptr && EntitiesInScene_.erase(resource.Entity) != 0U) {
Scene_->remove(resource.Entity);
}
auto& renderableManager = Engine_->getRenderableManager();
if (renderableManager.getInstance(resource.Entity)) {
renderableManager.destroy(resource.Entity);
}
Engine_->destroy(resource.Entity);
utils::EntityManager::get().destroy(resource.Entity);
resource.Entity = {};
}
if (resource.MaterialInstance != nullptr) {
Engine_->destroy(resource.MaterialInstance);
resource.MaterialInstance = nullptr;
}
if (resource.Vertices != nullptr) {
Engine_->destroy(resource.Vertices);
resource.Vertices = nullptr;
}
if (resource.Indices != nullptr) {
Engine_->destroy(resource.Indices);
resource.Indices = nullptr;
}
}
void DestroyProductionRenderable(std::uint64_t key) {
const auto found = ProductionRenderables_.find(key);
if (found == ProductionRenderables_.end() || Engine_ == nullptr) return;
DestroyProductionRenderableResource(found->second);
ProductionRenderables_.erase(found);
}
void DestroyProductionRenderables() {
while (!ProductionRenderables_.empty()) DestroyProductionRenderable(ProductionRenderables_.begin()->first);
if (Engine_ != nullptr && ProductionMaterial_ != nullptr) { Engine_->destroy(ProductionMaterial_); ProductionMaterial_ = nullptr; }
ParticleSimulations_.clear(); ParticleSimulationSeeds_.clear(); TrailHistories_.clear();
}
void UpdateProductionRenderable(std::uint64_t key, const MetaCoreDynamicGeometry& geometry,
const glm::mat4& transform, bool castShadows, bool receiveShadows,
bool depthWrite, std::uint8_t priority) {
if (geometry.Vertices.empty() || geometry.Indices.empty() || Engine_ == nullptr || Scene_ == nullptr) { DestroyProductionRenderable(key); return; }
const filament::Material* material = EnsureProductionMaterial(); if (material == nullptr) return;
auto found = ProductionRenderables_.find(key);
if (found != ProductionRenderables_.end() &&
(found->second.VertexCapacity < geometry.Vertices.size() || found->second.IndexCapacity < geometry.Indices.size())) {
DestroyProductionRenderable(key); found = ProductionRenderables_.end();
}
if (found == ProductionRenderables_.end()) {
ProductionRenderableResource resource;
resource.VertexCapacity = std::bit_ceil(geometry.Vertices.size()); resource.IndexCapacity = std::bit_ceil(geometry.Indices.size());
resource.Vertices = filament::VertexBuffer::Builder().vertexCount(resource.VertexCapacity).bufferCount(1)
.attribute(filament::VertexAttribute::POSITION,0,filament::VertexBuffer::AttributeType::FLOAT3,offsetof(MetaCoreDynamicVertex,Position),sizeof(MetaCoreDynamicVertex))
.attribute(filament::VertexAttribute::COLOR,0,filament::VertexBuffer::AttributeType::FLOAT4,offsetof(MetaCoreDynamicVertex,Color),sizeof(MetaCoreDynamicVertex)).build(*Engine_);
resource.Indices = filament::IndexBuffer::Builder().indexCount(resource.IndexCapacity).bufferType(filament::IndexBuffer::IndexType::UINT).build(*Engine_);
resource.MaterialInstance = material->createInstance(); resource.Entity = utils::EntityManager::get().create();
if(resource.Vertices==nullptr||resource.Indices==nullptr||resource.MaterialInstance==nullptr){DestroyProductionRenderableResource(resource);return;}
resource.MaterialInstance->setParameter("baseColorFactor",filament::RgbaType::LINEAR,filament::math::float4{1,1,1,1});
resource.MaterialInstance->setDepthWrite(depthWrite);resource.MaterialInstance->setDepthCulling(true);resource.MaterialInstance->setCullingMode(filament::backend::CullingMode::NONE);
Engine_->getTransformManager().create(resource.Entity);
const glm::vec3 center=geometry.Vertices.front().Position;const auto result=filament::RenderableManager::Builder(1)
.boundingBox({{center.x,center.y,center.z},{100000.0F,100000.0F,100000.0F}}).culling(false).castShadows(castShadows).receiveShadows(receiveShadows)
.geometry(0,filament::RenderableManager::PrimitiveType::TRIANGLES,resource.Vertices,resource.Indices,0,geometry.Indices.size())
.material(0,resource.MaterialInstance).build(*Engine_,resource.Entity);
if(static_cast<int>(result)!=0){DestroyProductionRenderableResource(resource);return;}
Scene_->addEntity(resource.Entity);EntitiesInScene_.insert(resource.Entity);found=ProductionRenderables_.emplace(key,std::move(resource)).first;
}
auto& resource=found->second;
const std::size_t vertexBytes=geometry.Vertices.size()*sizeof(MetaCoreDynamicVertex),indexBytes=geometry.Indices.size()*sizeof(std::uint32_t);
void* vertexCopy=std::malloc(vertexBytes);void* indexCopy=std::malloc(indexBytes);if(vertexCopy==nullptr||indexCopy==nullptr){std::free(vertexCopy);std::free(indexCopy);return;}
std::memcpy(vertexCopy,geometry.Vertices.data(),vertexBytes);std::memcpy(indexCopy,geometry.Indices.data(),indexBytes);
resource.Vertices->setBufferAt(*Engine_,0,filament::VertexBuffer::BufferDescriptor(vertexCopy,vertexBytes,[](void* p,size_t,void*){std::free(p);},nullptr));
resource.Indices->setBuffer(*Engine_,filament::IndexBuffer::BufferDescriptor(indexCopy,indexBytes,[](void* p,size_t,void*){std::free(p);},nullptr));
auto& rm=Engine_->getRenderableManager();const auto instance=rm.getInstance(resource.Entity);rm.setGeometryAt(instance,0,filament::RenderableManager::PrimitiveType::TRIANGLES,resource.Vertices,resource.Indices,0,geometry.Indices.size());rm.setCastShadows(instance,castShadows);rm.setReceiveShadows(instance,receiveShadows);rm.setPriority(instance,priority);
const glm::vec3 minimum=std::accumulate(geometry.Vertices.begin()+1,geometry.Vertices.end(),geometry.Vertices.front().Position,[](glm::vec3 value,const auto& vertex){return glm::min(value,vertex.Position);});
const glm::vec3 maximum=std::accumulate(geometry.Vertices.begin()+1,geometry.Vertices.end(),geometry.Vertices.front().Position,[](glm::vec3 value,const auto& vertex){return glm::max(value,vertex.Position);});
const glm::vec3 center=(minimum+maximum)*0.5F,extents=glm::max((maximum-minimum)*0.5F,glm::vec3(0.001F));rm.setAxisAlignedBoundingBox(instance,{{center.x,center.y,center.z},{extents.x,extents.y,extents.z}});
Engine_->getTransformManager().setTransform(Engine_->getTransformManager().getInstance(resource.Entity),*reinterpret_cast<const filament::math::mat4f*>(glm::value_ptr(transform)));
}
[[nodiscard]] static MetaCoreDynamicGeometry BuildDecalBox(const MetaCoreDecalProjectorComponent& decal) {
MetaCoreDynamicGeometry result;const glm::vec3 e=glm::abs(decal.Size)*0.5F;const glm::vec4 color{1.0F,0.45F,0.1F,std::clamp(decal.Opacity,0.0F,1.0F)};
const std::array<glm::vec3,8> p{{{-e.x,-e.y,-e.z},{e.x,-e.y,-e.z},{e.x,e.y,-e.z},{-e.x,e.y,-e.z},{-e.x,-e.y,e.z},{e.x,-e.y,e.z},{e.x,e.y,e.z},{-e.x,e.y,e.z}}};for(const auto& point:p)result.Vertices.push_back({point,color,{}});
result.Indices={0,2,1,0,3,2,4,5,6,4,6,7,0,1,5,0,5,4,2,3,7,2,7,6,1,2,6,1,6,5,3,0,4,3,4,7};return result;
}
[[nodiscard]] std::optional<MetaCoreDynamicGeometry> LoadTerrainGeometry(const MetaCoreTerrainComponent& terrain) const {
if(!terrain.HeightmapGuid.IsValid())return std::nullopt;const auto path=MetaCoreAssetRegistry::Get().ResolveGuidToPath(terrain.HeightmapGuid);if(path.empty())return std::nullopt;
int width=0,height=0,channels=0;if(std::uint16_t* pixels=stbi_load_16(path.string().c_str(),&width,&height,&channels,1)){std::vector<std::uint16_t> values(pixels,pixels+static_cast<std::size_t>(width)*height);stbi_image_free(pixels);const float distance=glm::distance(CurrentSceneView_.CameraPosition,glm::vec3(0));const std::uint32_t step=distance>terrain.WorldSize.x?4U:(distance>terrain.WorldSize.x*0.5F?2U:1U);return MetaCoreBuildTerrainGeometry(values,static_cast<std::uint32_t>(width),static_cast<std::uint32_t>(height),terrain.WorldSize,step);}return std::nullopt;
}
void SynchronizeProductionComponents(const MetaCoreSceneRenderSyncSnapshot& snapshot) {
const auto now=std::chrono::steady_clock::now();const float delta=LastProductionSync_.time_since_epoch().count()==0?1.0F/60.0F:std::clamp(std::chrono::duration<float>(now-LastProductionSync_).count(),0.0F,0.1F);LastProductionSync_=now;
const glm::vec3 cameraForward=glm::normalize(CurrentSceneView_.CameraTarget-CurrentSceneView_.CameraPosition);glm::vec3 cameraRight=glm::cross(cameraForward,CurrentSceneView_.CameraUp);if(glm::dot(cameraRight,cameraRight)<0.0001F)cameraRight={1,0,0};cameraRight=glm::normalize(cameraRight);const glm::vec3 cameraUp=glm::normalize(glm::cross(cameraRight,cameraForward));
std::unordered_set<std::uint64_t> active;std::uint32_t particleRemaining=RenderSettings_.GlobalParticleBudget;
std::vector<const MetaCoreRenderSyncParticleEmitter*> emitters;for(const auto& emitter:snapshot.ParticleEmitters)emitters.push_back(&emitter);std::sort(emitters.begin(),emitters.end(),[](const auto* a,const auto* b){return a->ObjectId<b->ObjectId;});
for(const auto* value:emitters){const auto key=ProductionRenderableKey(value->ObjectId,1U);active.insert(key);auto& simulation=ParticleSimulations_[value->ObjectId];if(!ParticleSimulationSeeds_.contains(value->ObjectId)||ParticleSimulationSeeds_[value->ObjectId]!=value->Component.RandomSeed){simulation.Reset(value->Component.RandomSeed);ParticleSimulationSeeds_[value->ObjectId]=value->Component.RandomSeed;}simulation.Simulate(value->Component,delta,particleRemaining);particleRemaining-=std::min<std::uint32_t>(particleRemaining,simulation.GetParticles().size());UpdateProductionRenderable(key,MetaCoreBuildParticleBillboards(simulation.GetParticles(),cameraRight,cameraUp,value->WorldMatrix),glm::mat4(1),false,false,false,6U);}
std::uint32_t lineRemaining=RenderSettings_.DynamicLinePointBudget;
for(const auto& value:snapshot.LineRenderers){const auto key=ProductionRenderableKey(value.ObjectId,2U);active.insert(key);std::vector<glm::vec3> points;const auto count=std::min<std::size_t>(value.Component.Points.size(),lineRemaining);points.reserve(count);for(std::size_t index=0;index<count;++index)points.push_back(glm::vec3(value.WorldMatrix*glm::vec4(value.Component.Points[index],1)));lineRemaining-=static_cast<std::uint32_t>(count);UpdateProductionRenderable(key,MetaCoreBuildLineRibbon(points,value.Component.Width,{value.Component.Color,1},{value.Component.Color,1},cameraForward,static_cast<std::uint32_t>(count)),glm::mat4(1),false,false,false,5U);}
for(const auto& value:snapshot.TrailRenderers){const auto key=ProductionRenderableKey(value.ObjectId,3U);active.insert(key);auto& trail=TrailHistories_[value.ObjectId];trail.Update(glm::vec3(value.WorldMatrix[3]),delta,value.Component.Lifetime,value.Component.MinVertexDistance,value.Component.MaxPoints);std::vector<glm::vec3> points;for(const auto& point:trail.GetPoints())points.push_back(point.Position);const auto allowed=std::min<std::uint32_t>(lineRemaining,static_cast<std::uint32_t>(points.size()));lineRemaining-=allowed;UpdateProductionRenderable(key,MetaCoreBuildLineRibbon(points,value.Component.StartWidth,{value.Component.StartColor,1},{value.Component.EndColor,0},cameraForward,allowed),glm::mat4(1),false,false,false,5U);}
for(const auto& value:snapshot.DecalProjectors){const auto key=ProductionRenderableKey(value.ObjectId,4U);active.insert(key);if(value.Component.Enabled&&glm::distance(glm::vec3(value.WorldMatrix[3]),CurrentSceneView_.CameraPosition)<=value.Component.MaxDistance)UpdateProductionRenderable(key,BuildDecalBox(value.Component),value.WorldMatrix,false,false,false,4U);else DestroyProductionRenderable(key);}
for(const auto& value:snapshot.Terrains){const auto key=ProductionRenderableKey(value.ObjectId,5U);active.insert(key);const auto geometry=value.Component.Enabled?LoadTerrainGeometry(value.Component):std::nullopt;if(geometry)UpdateProductionRenderable(key,*geometry,value.WorldMatrix,value.Component.CastShadows,true,true,2U);else DestroyProductionRenderable(key);}
std::vector<std::uint64_t> stale;for(const auto& [key,resource]:ProductionRenderables_)if(!active.contains(key))stale.push_back(key);for(const auto key:stale)DestroyProductionRenderable(key);
}
[[nodiscard]] filament::Material* LoadEditorGridMaterial() {
if (Engine_ == nullptr) {
return nullptr;
}
const std::array<std::filesystem::path, 8> candidatePaths{
const std::array<std::filesystem::path, 9> candidatePaths{
std::filesystem::path("grid.filamat"),
std::filesystem::path("Engine/Materials/grid.filamat"),
std::filesystem::path("../grid.filamat"),
std::filesystem::path("build/grid.filamat"),
std::filesystem::path("build/linux-full-clang-libcxx-debug/grid.filamat"),
@ -3727,6 +4072,11 @@ private:
std::unordered_set<MetaCoreAssetGuid, MetaCoreAssetGuidHasher> MissingPostProcessProfileDiagnostics_{};
MetaCoreAssetGuid ActiveScenePostProcessGuid_{};
MetaCoreAssetGuid ActiveCameraPostProcessGuid_{};
MetaCorePostProcessProfileDocument ActivePostProcessProfile_{};
float AutoExposureEv100_ = 15.0F;
std::chrono::steady_clock::time_point LastAutoExposureUpdate_{};
std::chrono::steady_clock::time_point LastRenderConfigurationPoll_{};
std::uint64_t RenderConfigurationFingerprint_ = 0U;
std::filesystem::path LoadedEnvironmentIblPath_{};
std::filesystem::path LoadedEnvironmentSkyboxPath_{};
std::vector<std::uint8_t> EnvironmentIblKtxBytes_{};
@ -3743,6 +4093,15 @@ private:
MetaCoreSceneRenderSync RenderSync_{};
MetaCoreSceneRenderSyncSnapshot LastSyncSnapshot_{};
MetaCoreSceneView CurrentSceneView_{};
std::unordered_map<MetaCoreId, std::int32_t> LodSelections_{};
MetaCoreStaticVisibilityBvh VisibilityBvh_{};
MetaCoreConservativeHzb PreviousHzb_{};
MetaCoreOcclusionHistory OcclusionHistory_{};
std::unordered_set<MetaCoreId> LastFrameVisibleObjects_{};
glm::vec3 PreviousVisibilityCameraPosition_{};
glm::vec3 PreviousVisibilityCameraDirection_{0.0F,0.0F,-1.0F};
bool HasPreviousVisibilityCamera_ = false;
MetaCoreVisibilityStatistics RenderingStatistics_{};
bool HasLoggedSnapshotDebug_ = false;
std::uint64_t LastDebugSnapshotRevision_ = 0;
std::size_t LastDebugRenderableCount_ = 0;
@ -3777,6 +4136,15 @@ private:
filament::IndexBuffer* EditorGridIndexBuffer_ = nullptr;
filament::Material* EditorGridMaterial_ = nullptr;
std::array<filament::MaterialInstance*, 4> EditorGridMaterialInstances_{};
filament::Material* ProductionMaterial_ = nullptr;
filament::Material* RenderDebugMaterial_ = nullptr;
std::vector<DebugMaterialOverride> DebugMaterialOverrides_{};
MetaCoreRenderDebugView ActiveDebugView_ = MetaCoreRenderDebugView::Lit;
std::unordered_map<std::uint64_t, ProductionRenderableResource> ProductionRenderables_{};
std::unordered_map<MetaCoreId, MetaCoreCpuParticleEmitter> ParticleSimulations_{};
std::unordered_map<MetaCoreId, std::uint32_t> ParticleSimulationSeeds_{};
std::unordered_map<MetaCoreId, MetaCoreTrailHistory> TrailHistories_{};
std::chrono::steady_clock::time_point LastProductionSync_{};
filament::View* UIView_ = nullptr;
MetaCoreImGuiHelper* ImGuiHelper_ = nullptr;
@ -3837,6 +4205,14 @@ void MetaCoreFilamentSceneBridge::SetEditorGridVisible(bool visible) {
Impl_->SetEditorGridVisible(visible);
}
void MetaCoreFilamentSceneBridge::SetRenderDebugView(MetaCoreRenderDebugView debugView) {
Impl_->SetRenderDebugView(debugView);
}
MetaCoreRenderDebugView MetaCoreFilamentSceneBridge::GetRenderDebugView() const {
return Impl_->GetRenderDebugView();
}
void MetaCoreFilamentSceneBridge::SetRuntimeOverlayRenderCallback(RuntimeOverlayRenderCallback callback) {
Impl_->SetRuntimeOverlayRenderCallback(std::move(callback));
}
@ -3873,6 +4249,10 @@ const std::string& MetaCoreFilamentSceneBridge::GetLastRuntimeSyncFailure() cons
return Impl_->GetLastRuntimeSyncFailure();
}
MetaCoreVisibilityStatistics MetaCoreFilamentSceneBridge::GetRenderingStatistics() const {
return Impl_->GetRenderingStatistics();
}
bool MetaCoreFilamentSceneBridge::VerifyTransformForTesting(MetaCoreId objectId, const glm::mat4& expectedMatrix) const {
return Impl_->VerifyTransformForTesting(objectId, expectedMatrix);
}

View File

@ -193,8 +193,24 @@ MetaCoreSceneRenderSyncSnapshot MetaCoreSceneRenderSync::BuildSnapshot(const Met
meshRenderer.MetallicRoughnessTexturePath,
meshRenderer.NormalTexturePath,
meshRenderer.EmissiveTexturePath,
meshRenderer.AoTexturePath
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>()) {

View File

@ -25,9 +25,12 @@ public:
[[nodiscard]] bool RequestPick(std::uint32_t x, std::uint32_t y, MetaCoreFilamentSceneBridge::PickCallback callback);
void SetProjectRootPath(const std::filesystem::path& projectRootPath);
void SetEditorGridVisible(bool visible);
void SetRenderDebugView(MetaCoreRenderDebugView debugView);
[[nodiscard]] MetaCoreRenderDebugView GetRenderDebugView() const;
void SetRuntimeOverlayRenderCallback(MetaCoreFilamentSceneBridge::RuntimeOverlayRenderCallback callback);
[[nodiscard]] uint32_t GetFilamentGLTextureId() const;
[[nodiscard]] void* GetFilamentTexturePointer() const;
[[nodiscard]] MetaCoreVisibilityStatistics GetRenderingStatistics() const;
[[nodiscard]] bool TryGetObjectWorldMatrix(MetaCoreId objectId, glm::mat4& worldMatrix) const;

View File

@ -1,6 +1,7 @@
#pragma once
#include "MetaCoreFoundation/MetaCoreId.h"
#include "MetaCoreRendering/MetaCoreRendering.h"
#include <glm/mat4x4.hpp>
#include <glm/vec3.hpp>
@ -53,6 +54,8 @@ public:
void SyncScene(const MetaCoreSceneRenderSyncSnapshot& snapshot, MetaCoreScene* scene = nullptr, bool compatibilityMeshOnly = false, bool useScenePrimaryCamera = false);
void ApplySceneView(const MetaCoreSceneView& sceneView);
void SetEditorGridVisible(bool visible);
void SetRenderDebugView(MetaCoreRenderDebugView debugView);
[[nodiscard]] MetaCoreRenderDebugView GetRenderDebugView() const;
void SetRuntimeOverlayRenderCallback(RuntimeOverlayRenderCallback callback);
void RenderAll();
[[nodiscard]] bool RequestPick(std::uint32_t x, std::uint32_t y, PickCallback callback);
@ -62,6 +65,7 @@ public:
[[nodiscard]] bool TryGetObjectWorldMatrix(MetaCoreId objectId, glm::mat4& worldMatrix) const;
[[nodiscard]] bool HasRuntimeSyncFailure() const;
[[nodiscard]] const std::string& GetLastRuntimeSyncFailure() const;
[[nodiscard]] MetaCoreVisibilityStatistics GetRenderingStatistics() const;
// 测试专用的验证接口
[[nodiscard]] bool VerifyTransformForTesting(MetaCoreId objectId, const glm::mat4& expectedMatrix) const;

View File

@ -49,6 +49,14 @@ struct MetaCoreRenderSyncRenderable {
std::string NormalTexturePath{};
std::string EmissiveTexturePath{};
std::string AoTexturePath{};
std::uint32_t RenderingLayerMask = 1U;
float MaxDrawDistance = 0.0F;
bool StaticForCulling = true;
bool CastShadows = true;
bool ReceiveShadows = true;
MetaCoreRenderBounds WorldBounds{};
std::uint64_t TriangleCount = 0U;
std::uint64_t EstimatedVideoMemoryBytes = 0U;
};
struct MetaCoreRenderSyncCamera {

View File

@ -1,12 +1,20 @@
#include "MetaCoreRendering/MetaCoreRendering.h"
#include "MetaCoreFoundation/MetaCoreArchive.h"
#include "MetaCoreFoundation/MetaCoreHash.h"
#include <algorithm>
#include <cmath>
#include <fstream>
#include <iomanip>
#include <limits>
#include <map>
#include <tuple>
#include <unordered_set>
#include <glm/common.hpp>
#include <glm/geometric.hpp>
namespace MetaCore {
namespace {
template <typename T>
@ -101,6 +109,34 @@ std::vector<MetaCoreId> MetaCoreAllocateShadowBudget(const std::vector<MetaCoreS
}
std::uint32_t MetaCoreAllocateParticleBudget(std::uint32_t requested,std::uint32_t emitterMaximum,std::uint32_t globalRemaining){return std::min({requested,emitterMaximum,globalRemaining});}
std::vector<MetaCoreReflectionProbeBlend> MetaCoreSelectReflectionProbes(const std::vector<MetaCoreReflectionProbeCandidate>& candidates,const glm::vec3& worldPosition){
struct Match{const MetaCoreReflectionProbeCandidate* Candidate=nullptr;float Weight=0.0F;float Distance=0.0F;};std::vector<Match> matches;
for(const auto& candidate:candidates){const auto& probe=candidate.Probe;if(!probe.Enabled||!probe.BakedCubemapGuid.IsValid())continue;const glm::vec3 offset=worldPosition-candidate.Position;float distance=0.0F;float edge=0.0F;
if(probe.Shape==MetaCoreReflectionProbeShape::Sphere){distance=glm::length(offset);if(distance>std::max(0.0F,probe.Radius))continue;edge=probe.Radius-distance;}
else{const glm::vec3 extents=glm::max(glm::abs(probe.Extents),glm::vec3(0.0001F));const glm::vec3 outside=glm::max(glm::abs(offset)-extents,glm::vec3(0.0F));if(glm::dot(outside,outside)>0.0F)continue;distance=glm::length(offset);const glm::vec3 inside=extents-glm::abs(offset);edge=std::min({inside.x,inside.y,inside.z});}
const float weight=probe.BlendDistance<=0.0F?1.0F:std::clamp(edge/probe.BlendDistance,0.0F,1.0F);matches.push_back({&candidate,weight,distance});}
std::stable_sort(matches.begin(),matches.end(),[](const Match& lhs,const Match& rhs){if(lhs.Candidate->Probe.Priority!=rhs.Candidate->Probe.Priority)return lhs.Candidate->Probe.Priority>rhs.Candidate->Probe.Priority;if(lhs.Weight!=rhs.Weight)return lhs.Weight>rhs.Weight;if(lhs.Distance!=rhs.Distance)return lhs.Distance<rhs.Distance;return lhs.Candidate->ObjectId<rhs.Candidate->ObjectId;});
if(matches.size()>2U)matches.resize(2U);float total=0.0F;for(const auto& match:matches)total+=match.Weight;if(total<=0.0F&&!matches.empty())total=static_cast<float>(matches.size());std::vector<MetaCoreReflectionProbeBlend> result;for(const auto& match:matches)result.push_back({match.Candidate->ObjectId,match.Candidate->Probe.BakedCubemapGuid,total>0.0F?(match.Weight>0.0F?match.Weight:1.0F)/total:0.0F});return result;
}
bool MetaCoreOcclusionHistory::IsVisible(MetaCoreId objectId,bool occlusionQueryVisible,bool cameraCut,bool dynamicObject,bool newlyVisible,std::uint32_t graceFrames){auto& entry=Entries_[objectId];entry.LastTouchedFrame=FrameIndex_;if(cameraCut||dynamicObject||newlyVisible)entry.GraceRemaining=graceFrames;if(occlusionQueryVisible){entry.GraceRemaining=graceFrames;return true;}if(entry.GraceRemaining>0U){--entry.GraceRemaining;return true;}return false;}
void MetaCoreOcclusionHistory::BeginFrame(){++FrameIndex_;for(auto iterator=Entries_.begin();iterator!=Entries_.end();){if(FrameIndex_-iterator->second.LastTouchedFrame>120U)iterator=Entries_.erase(iterator);else ++iterator;}}
void MetaCoreOcclusionHistory::Reset(){Entries_.clear();FrameIndex_=0U;}
std::vector<MetaCoreInstanceBatch> MetaCoreBuildInstanceBatches(const std::vector<MetaCoreInstanceCandidate>& candidates,bool instancingEnabled){
struct Key{MetaCoreAssetGuid Mesh{};std::vector<MetaCoreAssetGuid> Materials{};std::uint64_t Parameters=0U,State=0U,Object=0U;bool operator==(const Key&)const=default;};
struct Hasher{std::size_t operator()(const Key& key)const noexcept{std::size_t hash=MetaCoreAssetGuidHasher{}(key.Mesh);const auto combine=[&](std::size_t value){hash^=value+0x9e3779b97f4a7c15ULL+(hash<<6U)+(hash>>2U);};for(const auto& material:key.Materials)combine(MetaCoreAssetGuidHasher{}(material));combine(std::hash<std::uint64_t>{}(key.Parameters));combine(std::hash<std::uint64_t>{}(key.State));combine(std::hash<std::uint64_t>{}(key.Object));return hash;}};
std::unordered_map<Key,std::size_t,Hasher> indices;indices.reserve(candidates.size());std::vector<MetaCoreInstanceBatch> result;result.reserve(candidates.size());
for(const auto& candidate:candidates){if(!candidate.Visible)continue;Key key{candidate.MeshAssetGuid,candidate.MaterialAssetGuids,candidate.ParameterHash,candidate.RenderStateHash,instancingEnabled?0U:candidate.ObjectId};const auto [iterator,inserted]=indices.emplace(key,result.size());if(inserted)result.push_back({candidate.MeshAssetGuid,candidate.MaterialAssetGuids,candidate.ParameterHash,candidate.RenderStateHash,{}});result[iterator->second].ObjectIds.push_back(candidate.ObjectId);}result.shrink_to_fit();return result;
}
void MetaCoreAccumulateVisibilityResult(MetaCoreVisibilityStatistics& statistics,MetaCoreVisibilityResult result,std::uint64_t triangleCount){++statistics.Submitted;switch(result){case MetaCoreVisibilityResult::Visible:++statistics.Visible;statistics.VisibleTriangleCount+=triangleCount;break;case MetaCoreVisibilityResult::LayerCulled:++statistics.LayerCulled;break;case MetaCoreVisibilityResult::DistanceCulled:++statistics.DistanceCulled;break;case MetaCoreVisibilityResult::FrustumCulled:++statistics.FrustumCulled;break;case MetaCoreVisibilityResult::LodCulled:++statistics.LodCulled;break;case MetaCoreVisibilityResult::OcclusionCulled:++statistics.OcclusionCulled;break;}}
float MetaCoreCpuParticleEmitter::NextRandom(){RandomState_=RandomState_*1664525U+1013904223U;return static_cast<float>(RandomState_>>8U)*(1.0F/16777216.0F);}
void MetaCoreCpuParticleEmitter::Reset(std::uint32_t seed){Particles_.clear();RandomState_=seed==0U?1U:seed;DroppedParticleCount_=0U;EmissionAccumulator_=0.0F;Elapsed_=0.0F;BurstEmitted_=false;}
void MetaCoreCpuParticleEmitter::Spawn(const MetaCoreParticleEmitterComponent& component){MetaCoreParticleState particle;particle.Lifetime=std::max(0.001F,component.Lifetime);particle.Size=std::max(0.0F,component.StartSize);particle.Color=component.StartColor;glm::vec3 direction{NextRandom()*2.0F-1.0F,NextRandom()*2.0F-1.0F,NextRandom()*2.0F-1.0F};if(glm::dot(direction,direction)<0.0001F)direction={0.0F,0.0F,1.0F};direction=glm::normalize(direction);if(component.Shape==MetaCoreParticleEmitterShape::Box)particle.Position={NextRandom()-0.5F,NextRandom()-0.5F,NextRandom()-0.5F};else if(component.Shape==MetaCoreParticleEmitterShape::Sphere)particle.Position=direction*NextRandom()*0.5F;else if(component.Shape==MetaCoreParticleEmitterShape::Cone)direction=glm::normalize(glm::vec3((NextRandom()-0.5F)*0.6F,(NextRandom()-0.5F)*0.6F,1.0F));particle.Velocity=direction*component.StartSpeed;Particles_.push_back(particle);}
void MetaCoreCpuParticleEmitter::Simulate(const MetaCoreParticleEmitterComponent& component,float deltaSeconds,std::uint32_t globalRemaining){if(deltaSeconds<=0.0F)return;for(auto& particle:Particles_){particle.Age+=deltaSeconds;particle.Velocity+=component.Gravity*deltaSeconds;particle.Position+=particle.Velocity*deltaSeconds;const float t=std::clamp(particle.Age/particle.Lifetime,0.0F,1.0F);particle.Color=component.StartColor+(component.EndColor-component.StartColor)*t;particle.Size=component.StartSize+(component.EndSize-component.StartSize)*t;}std::erase_if(Particles_,[](const auto& particle){return particle.Age>=particle.Lifetime;});if(!component.Enabled)return;Elapsed_+=deltaSeconds;const bool emitting=component.Loop||Elapsed_<=std::max(0.0F,component.Duration);std::uint32_t requested=0U;if(emitting){EmissionAccumulator_+=std::max(0.0F,component.EmissionRate)*deltaSeconds;requested=static_cast<std::uint32_t>(EmissionAccumulator_);EmissionAccumulator_-=static_cast<float>(requested);if(!BurstEmitted_){requested+=component.BurstCount;BurstEmitted_=true;}}const std::uint32_t capacity=component.MaxParticles>Particles_.size()?component.MaxParticles-static_cast<std::uint32_t>(Particles_.size()):0U;const std::uint32_t allocated=MetaCoreAllocateParticleBudget(requested,capacity,globalRemaining);for(std::uint32_t index=0;index<allocated;++index)Spawn(component);DroppedParticleCount_+=requested-allocated;if(component.Loop&&component.Duration>0.0F&&Elapsed_>=component.Duration){Elapsed_=std::fmod(Elapsed_,component.Duration);BurstEmitted_=false;}}
const MetaCorePostProcessProfileDocument& MetaCoreResolvePostProcessProfile(const MetaCorePostProcessProfileDocument& engineDefault,const MetaCorePostProcessProfileDocument* sceneProfile,const MetaCorePostProcessProfileDocument* cameraOverride){if(cameraOverride)return *cameraOverride;if(sceneProfile)return *sceneProfile;return engineDefault;}
MetaCoreMaterialTemplateRegistry::MetaCoreMaterialTemplateRegistry() {
@ -121,10 +157,192 @@ const MetaCoreMaterialTemplateDescriptor* MetaCoreMaterialTemplateRegistry::Find
bool MetaCoreMaterialTemplateRegistry::ValidateAndMigrate(MetaCoreMaterialAssetDocument& material,std::vector<std::string>* issues) const {
if(material.TemplateId.empty()){material.TemplateId=material.ShaderModel==MetaCoreMaterialShaderModel::PbrMetalRough?"metacore.pbr":"metacore.unlit";}
const auto* descriptor=Find(material.TemplateId);if(!descriptor){if(issues)issues->push_back("未知材质模板: "+material.TemplateId);return false;}
if(material.TemplateVersion>descriptor->Version){if(issues)issues->push_back("材质模板版本高于当前引擎: "+material.TemplateId+"@"+std::to_string(material.TemplateVersion));return false;}
bool valid=true;std::vector<MetaCoreMaterialParameterDocument> migrated;std::unordered_set<std::string> used;
for(const auto& schema:descriptor->Parameters){const auto existing=std::find_if(material.Parameters.begin(),material.Parameters.end(),[&](const auto& parameter){return parameter.Name==schema.Name&&parameter.Type==schema.Type;});auto value=existing==material.Parameters.end()?schema.DefaultValue:*existing;if(existing==material.Parameters.end()&&issues)issues->push_back("补充材质参数默认值: "+schema.Name);if(schema.Type==MetaCoreMaterialParameterType::Float)value.FloatValue=std::clamp(value.FloatValue,schema.Minimum,schema.Maximum);migrated.push_back(std::move(value));used.insert(schema.Name);}
for(const auto& parameter:material.Parameters)if(!used.contains(parameter.Name)){valid=false;if(issues)issues->push_back("模板不接受材质参数: "+parameter.Name);}
material.Parameters=std::move(migrated);material.TemplateVersion=descriptor->Version;return valid;
}
namespace {
MetaCoreRenderBounds MergeBounds(const MetaCoreRenderBounds& a, const MetaCoreRenderBounds& b) {
return {glm::min(a.Minimum, b.Minimum), glm::max(a.Maximum, b.Maximum)};
}
MetaCoreRenderBounds BoundsForRange(const std::vector<MetaCoreBvhItem>& items, std::uint32_t first, std::uint32_t count) {
MetaCoreRenderBounds result{glm::vec3(std::numeric_limits<float>::max()), glm::vec3(std::numeric_limits<float>::lowest())};
for (std::uint32_t index = first; index < first + count; ++index) result = MergeBounds(result, items[index].Bounds);
return result;
}
glm::vec3 TransformPoint(const glm::mat4& matrix, const glm::vec3& point) {
const glm::vec4 transformed = matrix * glm::vec4(point, 1.0F);
return glm::vec3(transformed) / std::max(0.000001F, std::abs(transformed.w));
}
}
void MetaCoreStaticVisibilityBvh::Build(std::vector<MetaCoreBvhItem> items) {
Items_ = std::move(items);
Nodes_.clear();
if (!Items_.empty()) (void)BuildNode(0U, static_cast<std::uint32_t>(Items_.size()));
}
std::int32_t MetaCoreStaticVisibilityBvh::BuildNode(std::uint32_t first, std::uint32_t count) {
const std::int32_t nodeIndex = static_cast<std::int32_t>(Nodes_.size());
Nodes_.push_back({BoundsForRange(Items_, first, count), first, count, -1, -1});
if (count <= 8U) return nodeIndex;
const glm::vec3 extent = Nodes_[static_cast<std::size_t>(nodeIndex)].Bounds.Extents();
const int axis = extent.y > extent.x ? (extent.z > extent.y ? 2 : 1) : (extent.z > extent.x ? 2 : 0);
const std::uint32_t middle = first + count / 2U;
std::nth_element(Items_.begin() + first, Items_.begin() + middle, Items_.begin() + first + count,
[axis](const MetaCoreBvhItem& lhs, const MetaCoreBvhItem& rhs) {
if (lhs.Bounds.Center()[axis] != rhs.Bounds.Center()[axis]) return lhs.Bounds.Center()[axis] < rhs.Bounds.Center()[axis];
return lhs.ObjectId < rhs.ObjectId;
});
const std::int32_t left = BuildNode(first, middle - first);
const std::int32_t right = BuildNode(middle, first + count - middle);
Nodes_[static_cast<std::size_t>(nodeIndex)].Left = left;
Nodes_[static_cast<std::size_t>(nodeIndex)].Right = right;
return nodeIndex;
}
std::vector<MetaCoreId> MetaCoreStaticVisibilityBvh::Query(
const MetaCoreViewFrustum& frustum, const glm::vec3& cameraPosition,
std::uint32_t cameraMask, MetaCoreVisibilityStatistics* statistics) const {
std::vector<MetaCoreId> visible;
if (Nodes_.empty()) return visible;
std::vector<std::int32_t> stack{0};
while (!stack.empty()) {
const Node& node = Nodes_[static_cast<std::size_t>(stack.back())];
stack.pop_back();
if (!MetaCoreIntersectsFrustum(frustum, node.Bounds)) {
if (statistics) for (std::uint32_t index=0; index<node.Count; ++index)
MetaCoreAccumulateVisibilityResult(*statistics, MetaCoreVisibilityResult::FrustumCulled);
continue;
}
if (node.Left >= 0) { stack.push_back(node.Right); stack.push_back(node.Left); continue; }
for (std::uint32_t offset = 0U; offset < node.Count; ++offset) {
const MetaCoreBvhItem& item = Items_[node.First + offset];
if ((item.LayerMask & cameraMask) == 0U) {
if (statistics) MetaCoreAccumulateVisibilityResult(*statistics, MetaCoreVisibilityResult::LayerCulled);
continue;
}
if (item.MaxDrawDistance > 0.0F && glm::distance(cameraPosition, item.Bounds.Center()) > item.MaxDrawDistance) {
if (statistics) MetaCoreAccumulateVisibilityResult(*statistics, MetaCoreVisibilityResult::DistanceCulled);
continue;
}
if (!MetaCoreIntersectsFrustum(frustum, item.Bounds)) {
if (statistics) MetaCoreAccumulateVisibilityResult(*statistics, MetaCoreVisibilityResult::FrustumCulled);
continue;
}
visible.push_back(item.ObjectId);
}
}
std::sort(visible.begin(), visible.end());
return visible;
}
MetaCoreViewFrustum MetaCoreExtractFrustum(const glm::mat4& matrix) {
const auto row = [&matrix](int index) { return glm::vec4(matrix[0][index], matrix[1][index], matrix[2][index], matrix[3][index]); };
const glm::vec4 r0=row(0), r1=row(1), r2=row(2), r3=row(3);
const std::array<glm::vec4, 6> raw{r3+r0, r3-r0, r3+r1, r3-r1, r3+r2, r3-r2};
MetaCoreViewFrustum result;
for (std::size_t index=0; index<raw.size(); ++index) {
const float length=glm::length(glm::vec3(raw[index]));
result.Planes[index]={glm::vec3(raw[index])/std::max(length,0.000001F), raw[index].w/std::max(length,0.000001F)};
}
return result;
}
bool MetaCoreIntersectsFrustum(const MetaCoreViewFrustum& frustum, const MetaCoreRenderBounds& bounds) {
const glm::vec3 center=bounds.Center(), extents=bounds.Extents();
for (const MetaCoreFrustumPlane& plane : frustum.Planes) {
const float radius=glm::dot(glm::abs(plane.Normal), extents);
if (glm::dot(plane.Normal, center)+plane.Distance+radius < 0.0F) return false;
}
return true;
}
bool MetaCoreConservativeHzb::Build(std::uint32_t width, std::uint32_t height, std::span<const float> linearDepth) {
Reset();
if (width==0U || height==0U || linearDepth.size()!=static_cast<std::size_t>(width)*height) return false;
Mips_.push_back({width,height,std::vector<float>(linearDepth.begin(),linearDepth.end())});
while (width>1U || height>1U) {
const Mip& source=Mips_.back();
width=std::max(1U,(source.Width+1U)/2U); height=std::max(1U,(source.Height+1U)/2U);
Mip target{width,height,std::vector<float>(static_cast<std::size_t>(width)*height,0.0F)};
for(std::uint32_t y=0;y<height;++y)for(std::uint32_t x=0;x<width;++x){
float farthest=0.0F;
for(std::uint32_t oy=0;oy<2U;++oy)for(std::uint32_t ox=0;ox<2U;++ox){
const std::uint32_t sx=std::min(source.Width-1U,x*2U+ox), sy=std::min(source.Height-1U,y*2U+oy);
farthest=std::max(farthest,source.Depth[static_cast<std::size_t>(sy)*source.Width+sx]);
}
target.Depth[static_cast<std::size_t>(y)*width+x]=farthest;
}
Mips_.push_back(std::move(target));
}
return true;
}
bool MetaCoreConservativeHzb::IsVisible(const glm::vec4& inputBounds, float nearestLinearDepth) const {
if(Mips_.empty() || !std::isfinite(nearestLinearDepth) || nearestLinearDepth<=0.0F) return true;
const glm::vec4 bounds{std::clamp(inputBounds.x,0.0F,1.0F),std::clamp(inputBounds.y,0.0F,1.0F),std::clamp(inputBounds.z,0.0F,1.0F),std::clamp(inputBounds.w,0.0F,1.0F)};
if(bounds.z<=bounds.x || bounds.w<=bounds.y) return true;
const float pixelWidth=(bounds.z-bounds.x)*static_cast<float>(Mips_[0].Width);
const float pixelHeight=(bounds.w-bounds.y)*static_cast<float>(Mips_[0].Height);
const std::uint32_t mip=std::min<std::uint32_t>(static_cast<std::uint32_t>(Mips_.size()-1U),
static_cast<std::uint32_t>(std::max(0.0F,std::floor(std::log2(std::max(1.0F,std::max(pixelWidth,pixelHeight)))))));
const Mip& level=Mips_[mip];
const std::uint32_t minX=std::min(level.Width-1U,static_cast<std::uint32_t>(bounds.x*level.Width));
const std::uint32_t maxX=std::min(level.Width-1U,static_cast<std::uint32_t>(bounds.z*level.Width));
const std::uint32_t minY=std::min(level.Height-1U,static_cast<std::uint32_t>(bounds.y*level.Height));
const std::uint32_t maxY=std::min(level.Height-1U,static_cast<std::uint32_t>(bounds.w*level.Height));
float farthest=0.0F;
for(std::uint32_t y=minY;y<=maxY;++y)for(std::uint32_t x=minX;x<=maxX;++x)farthest=std::max(farthest,level.Depth[static_cast<std::size_t>(y)*level.Width+x]);
return nearestLinearDepth<=farthest+0.001F;
}
void MetaCoreConservativeHzb::Reset(){Mips_.clear();}
MetaCoreDynamicGeometry MetaCoreBuildParticleBillboards(
std::span<const MetaCoreParticleState> particles, const glm::vec3& cameraRight, const glm::vec3& cameraUp,
const glm::mat4& emitterWorldMatrix) {
MetaCoreDynamicGeometry geometry; geometry.Vertices.reserve(particles.size()*4U);geometry.Indices.reserve(particles.size()*6U);
const glm::vec3 right=glm::normalize(cameraRight), up=glm::normalize(cameraUp);
for(const MetaCoreParticleState& particle:particles){
const glm::vec3 center=TransformPoint(emitterWorldMatrix,particle.Position);const float half=std::max(0.0F,particle.Size)*0.5F;
const glm::vec4 color{particle.Color, std::clamp(1.0F-particle.Age/std::max(0.001F,particle.Lifetime),0.0F,1.0F)};
const std::uint32_t base=static_cast<std::uint32_t>(geometry.Vertices.size());
geometry.Vertices.push_back({center-right*half-up*half,color,{0,0}});geometry.Vertices.push_back({center+right*half-up*half,color,{1,0}});
geometry.Vertices.push_back({center+right*half+up*half,color,{1,1}});geometry.Vertices.push_back({center-right*half+up*half,color,{0,1}});
geometry.Indices.insert(geometry.Indices.end(),{base,base+1U,base+2U,base,base+2U,base+3U});
} return geometry;
}
MetaCoreDynamicGeometry MetaCoreBuildLineRibbon(std::span<const glm::vec3> points,float width,const glm::vec4& startColor,const glm::vec4& endColor,const glm::vec3& cameraForward,std::uint32_t pointBudget){
MetaCoreDynamicGeometry geometry;const std::size_t count=std::min<std::size_t>(points.size(),pointBudget);if(count<2U||width<=0.0F)return geometry;
geometry.Vertices.reserve(count*2U);geometry.Indices.reserve((count-1U)*6U);const glm::vec3 view=glm::normalize(cameraForward);
for(std::size_t index=0;index<count;++index){const glm::vec3 tangent=glm::normalize(points[std::min(index+1U,count-1U)]-points[index==0U?0U:index-1U]);glm::vec3 side=glm::cross(view,tangent);if(glm::dot(side,side)<0.00001F)side=glm::cross(glm::vec3(0,0,1),tangent);side=glm::normalize(side)*width*0.5F;const float t=static_cast<float>(index)/static_cast<float>(count-1U);const glm::vec4 color=startColor+(endColor-startColor)*t;geometry.Vertices.push_back({points[index]-side,color,{0,t}});geometry.Vertices.push_back({points[index]+side,color,{1,t}});}
for(std::uint32_t index=0;index+1U<count;++index){const std::uint32_t base=index*2U;geometry.Indices.insert(geometry.Indices.end(),{base,base+1U,base+3U,base,base+3U,base+2U});}return geometry;
}
MetaCoreDynamicGeometry MetaCoreBuildTerrainGeometry(std::span<const std::uint16_t> heights,std::uint32_t width,std::uint32_t height,const glm::vec3& worldSize,std::uint32_t sampleStep){
MetaCoreDynamicGeometry geometry;if(width<2U||height<2U||heights.size()!=static_cast<std::size_t>(width)*height)return geometry;sampleStep=std::max(1U,sampleStep);
std::vector<std::uint32_t> xs,ys;for(std::uint32_t x=0;x<width-1U;x+=sampleStep)xs.push_back(x);xs.push_back(width-1U);for(std::uint32_t y=0;y<height-1U;y+=sampleStep)ys.push_back(y);ys.push_back(height-1U);
geometry.Vertices.reserve(xs.size()*ys.size());for(std::uint32_t y:ys)for(std::uint32_t x:xs){const float u=static_cast<float>(x)/static_cast<float>(width-1U),v=static_cast<float>(y)/static_cast<float>(height-1U),h=static_cast<float>(heights[static_cast<std::size_t>(y)*width+x])/65535.0F;geometry.Vertices.push_back({{(u-0.5F)*worldSize.x,(v-0.5F)*worldSize.y,h*worldSize.z},{h,h,h,1},{u,v}});}
for(std::uint32_t y=0;y+1U<ys.size();++y)for(std::uint32_t x=0;x+1U<xs.size();++x){const std::uint32_t a=y*static_cast<std::uint32_t>(xs.size())+x,b=a+1U,c=a+static_cast<std::uint32_t>(xs.size()),d=c+1U;geometry.Indices.insert(geometry.Indices.end(),{a,b,d,a,d,c});}return geometry;
}
void MetaCoreTrailHistory::Update(const glm::vec3& position,float deltaSeconds,float lifetime,float minimumDistance,std::uint32_t maxPoints){for(auto& point:Points_)point.Age+=std::max(0.0F,deltaSeconds);std::erase_if(Points_,[lifetime](const auto& point){return point.Age>std::max(0.0F,lifetime);});if(maxPoints==0U)return;if(Points_.empty()||glm::distance(Points_.back().Position,position)>=std::max(0.0F,minimumDistance))Points_.push_back({position,0.0F});if(Points_.size()>maxPoints)Points_.erase(Points_.begin(),Points_.begin()+static_cast<std::ptrdiff_t>(Points_.size()-maxPoints));}
bool MetaCoreIsDecalVisible(const MetaCoreDecalProjectorComponent& decal,const glm::mat4& projectorWorld,const MetaCoreRenderBounds& receiverBounds,std::uint32_t receiverLayerMask,const glm::vec3& cameraPosition){if(!decal.Enabled||(decal.LayerMask&receiverLayerMask)==0U)return false;const glm::vec3 center=glm::vec3(projectorWorld[3]);if(decal.MaxDistance>0.0F&&glm::distance(center,cameraPosition)>decal.MaxDistance)return false;const glm::vec3 extents=glm::abs(decal.Size)*0.5F;const MetaCoreRenderBounds projector{center-extents,center+extents};return projector.Minimum.x<=receiverBounds.Maximum.x&&projector.Maximum.x>=receiverBounds.Minimum.x&&projector.Minimum.y<=receiverBounds.Maximum.y&&projector.Maximum.y>=receiverBounds.Minimum.y&&projector.Minimum.z<=receiverBounds.Maximum.z&&projector.Maximum.z>=receiverBounds.Minimum.z;}
std::string MetaCoreBuildMaterialCookKey(const MetaCoreMaterialCookRequest& request){const std::string canonical=request.TemplateId+"\n"+std::to_string(request.TemplateVersion)+"\n"+request.SourceHash+"\n"+request.TargetPlatform+"\n"+request.GraphicsApi+"\n"+request.CompilerOptions;return MetaCoreSha256Bytes(std::as_bytes(std::span(canonical.data(),canonical.size())));}
bool MetaCoreWriteReflectionProbeDerivedHeader(const std::filesystem::path& path,const MetaCoreReflectionProbeDerivedHeader& header){if(!header.ProbeGuid.IsValid()||header.Version!=MetaCoreReflectionProbeDerivedVersion)return false;std::error_code error;std::filesystem::create_directories(path.parent_path(),error);std::ofstream output(path,std::ios::trunc);if(!output)return false;output<<"MCRPROBE "<<header.Version<<' '<<header.ProbeGuid.ToString()<<' '<<std::quoted(header.SourceHash)<<' '<<std::quoted(header.IblRelativePath)<<' '<<std::quoted(header.SkyboxRelativePath)<<'\n';return output.good();}
std::optional<MetaCoreReflectionProbeDerivedHeader> MetaCoreReadReflectionProbeDerivedHeader(const std::filesystem::path& path){std::ifstream input(path);std::string magic,guid;MetaCoreReflectionProbeDerivedHeader header;if(!(input>>magic>>header.Version>>guid>>std::quoted(header.SourceHash)>>std::quoted(header.IblRelativePath)>>std::quoted(header.SkyboxRelativePath))||magic!="MCRPROBE"||header.Version!=MetaCoreReflectionProbeDerivedVersion)return std::nullopt;const auto parsed=MetaCoreAssetGuid::Parse(guid);if(!parsed)return std::nullopt;header.ProbeGuid=*parsed;return header;}
std::string_view MetaCoreRenderDebugViewName(MetaCoreRenderDebugView value){static constexpr std::array names{"lit","unlit","wireframe","overdraw","world-normal","albedo","roughness","metallic","shadow-cascades","lod","occlusion","reflection-probe"};const auto index=static_cast<std::size_t>(value);return index<names.size()?names[index]:names[0];}
std::optional<MetaCoreRenderDebugView> MetaCoreParseRenderDebugView(std::string_view value){for(int index=0;index<=static_cast<int>(MetaCoreRenderDebugView::ReflectionProbe);++index){const auto mode=static_cast<MetaCoreRenderDebugView>(index);if(MetaCoreRenderDebugViewName(mode)==value)return mode;}return std::nullopt;}
} // namespace MetaCore

View File

@ -5,12 +5,18 @@
#include "MetaCoreFoundation/MetaCoreReflection.h"
#include <glm/vec3.hpp>
#include <glm/vec2.hpp>
#include <glm/vec4.hpp>
#include <glm/mat4x4.hpp>
#include <array>
#include <cstdint>
#include <filesystem>
#include <optional>
#include <span>
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>
namespace MetaCore {
@ -161,6 +167,10 @@ struct MetaCoreReflectionProbeComponent {
std::int32_t Priority = 0;
MC_PROPERTY()
MetaCoreAssetGuid BakedCubemapGuid{};
MC_PROPERTY()
std::string SourceEnvironmentPath{};
MC_PROPERTY()
std::uint32_t BakeResolution = 256U;
};
MC_STRUCT()
@ -324,6 +334,188 @@ struct MetaCoreShadowCandidate {
bool CastShadows = true;
};
struct MetaCoreReflectionProbeCandidate {
MetaCoreId ObjectId = 0;
glm::vec3 Position{};
MetaCoreReflectionProbeComponent Probe{};
};
struct MetaCoreReflectionProbeBlend {
MetaCoreId ObjectId = 0;
MetaCoreAssetGuid CubemapGuid{};
float Weight = 0.0F;
};
MC_ENUM()
enum class MetaCoreVisibilityResult { Visible = 0, LayerCulled, DistanceCulled, FrustumCulled, LodCulled, OcclusionCulled };
struct MetaCoreVisibilityStatistics {
std::uint32_t Submitted = 0U;
std::uint32_t Visible = 0U;
std::uint32_t LayerCulled = 0U;
std::uint32_t DistanceCulled = 0U;
std::uint32_t FrustumCulled = 0U;
std::uint32_t LodCulled = 0U;
std::uint32_t OcclusionCulled = 0U;
std::uint32_t InstanceBatchCount = 0U;
std::uint32_t DrawCallCount = 0U;
std::uint64_t VisibleTriangleCount = 0U;
float CpuFrameMilliseconds = 0.0F;
float GpuFrameMilliseconds = 0.0F;
std::uint64_t EstimatedVideoMemoryBytes = 0U;
};
struct MetaCoreRenderBounds {
glm::vec3 Minimum{};
glm::vec3 Maximum{};
[[nodiscard]] glm::vec3 Center() const { return (Minimum + Maximum) * 0.5F; }
[[nodiscard]] glm::vec3 Extents() const { return glm::max((Maximum - Minimum) * 0.5F, glm::vec3(0.0F)); }
};
struct MetaCoreFrustumPlane { glm::vec3 Normal{}; float Distance = 0.0F; };
struct MetaCoreViewFrustum { std::array<MetaCoreFrustumPlane, 6> Planes{}; };
struct MetaCoreBvhItem {
MetaCoreId ObjectId = 0;
MetaCoreRenderBounds Bounds{};
std::uint32_t LayerMask = 0xFFFFFFFFU;
float MaxDrawDistance = 0.0F;
bool Dynamic = false;
};
class MetaCoreStaticVisibilityBvh {
public:
void Build(std::vector<MetaCoreBvhItem> items);
[[nodiscard]] std::vector<MetaCoreId> Query(
const MetaCoreViewFrustum& frustum, const glm::vec3& cameraPosition,
std::uint32_t cameraMask, MetaCoreVisibilityStatistics* statistics = nullptr) const;
[[nodiscard]] std::size_t GetNodeCount() const { return Nodes_.size(); }
private:
struct Node { MetaCoreRenderBounds Bounds{}; std::uint32_t First=0U, Count=0U; std::int32_t Left=-1, Right=-1; };
std::int32_t BuildNode(std::uint32_t first, std::uint32_t count);
std::vector<MetaCoreBvhItem> Items_{};
std::vector<Node> Nodes_{};
};
[[nodiscard]] MetaCoreViewFrustum MetaCoreExtractFrustum(const glm::mat4& viewProjection);
[[nodiscard]] bool MetaCoreIntersectsFrustum(const MetaCoreViewFrustum& frustum, const MetaCoreRenderBounds& bounds);
class MetaCoreConservativeHzb {
public:
bool Build(std::uint32_t width, std::uint32_t height, std::span<const float> linearDepth);
[[nodiscard]] bool IsVisible(const glm::vec4& normalizedScreenBounds, float nearestLinearDepth) const;
[[nodiscard]] std::uint32_t GetMipCount() const { return static_cast<std::uint32_t>(Mips_.size()); }
void Reset();
private:
struct Mip { std::uint32_t Width=0U, Height=0U; std::vector<float> Depth{}; };
std::vector<Mip> Mips_{};
};
struct MetaCoreDynamicVertex { glm::vec3 Position{}; glm::vec4 Color{1.0F}; glm::vec2 Uv{}; };
struct MetaCoreDynamicGeometry { std::vector<MetaCoreDynamicVertex> Vertices{}; std::vector<std::uint32_t> Indices{}; };
struct MetaCoreParticleState;
[[nodiscard]] MetaCoreDynamicGeometry MetaCoreBuildParticleBillboards(
std::span<const MetaCoreParticleState> particles, const glm::vec3& cameraRight, const glm::vec3& cameraUp,
const glm::mat4& emitterWorldMatrix);
[[nodiscard]] MetaCoreDynamicGeometry MetaCoreBuildLineRibbon(
std::span<const glm::vec3> points, float width, const glm::vec4& startColor, const glm::vec4& endColor,
const glm::vec3& cameraForward, std::uint32_t pointBudget);
[[nodiscard]] MetaCoreDynamicGeometry MetaCoreBuildTerrainGeometry(
std::span<const std::uint16_t> heights, std::uint32_t width, std::uint32_t height,
const glm::vec3& worldSize, std::uint32_t sampleStep = 1U);
struct MetaCoreTrailPoint { glm::vec3 Position{}; float Age=0.0F; };
class MetaCoreTrailHistory {
public:
void Update(const glm::vec3& position, float deltaSeconds, float lifetime, float minimumDistance, std::uint32_t maxPoints);
[[nodiscard]] const std::vector<MetaCoreTrailPoint>& GetPoints() const { return Points_; }
void Reset() { Points_.clear(); }
private:
std::vector<MetaCoreTrailPoint> Points_{};
};
[[nodiscard]] bool MetaCoreIsDecalVisible(
const MetaCoreDecalProjectorComponent& decal, const glm::mat4& projectorWorld,
const MetaCoreRenderBounds& receiverBounds, std::uint32_t receiverLayerMask, const glm::vec3& cameraPosition);
struct MetaCoreMaterialCookRequest {
std::string TemplateId{};
std::uint32_t TemplateVersion = 1U;
std::string SourceHash{};
std::string TargetPlatform{"Linux-x86_64"};
std::string GraphicsApi{"OpenGL"};
std::string CompilerOptions{};
};
[[nodiscard]] std::string MetaCoreBuildMaterialCookKey(const MetaCoreMaterialCookRequest& request);
inline constexpr std::uint32_t MetaCoreReflectionProbeDerivedVersion = 1U;
struct MetaCoreReflectionProbeDerivedHeader {
std::uint32_t Version = MetaCoreReflectionProbeDerivedVersion;
MetaCoreAssetGuid ProbeGuid{};
std::string SourceHash{};
std::string IblRelativePath{};
std::string SkyboxRelativePath{};
};
[[nodiscard]] bool MetaCoreWriteReflectionProbeDerivedHeader(
const std::filesystem::path& path, const MetaCoreReflectionProbeDerivedHeader& header);
[[nodiscard]] std::optional<MetaCoreReflectionProbeDerivedHeader> MetaCoreReadReflectionProbeDerivedHeader(
const std::filesystem::path& path);
struct MetaCoreInstanceCandidate {
MetaCoreId ObjectId = 0;
MetaCoreAssetGuid MeshAssetGuid{};
std::vector<MetaCoreAssetGuid> MaterialAssetGuids{};
std::uint64_t ParameterHash = 0U;
std::uint64_t RenderStateHash = 0U;
bool Visible = true;
};
struct MetaCoreInstanceBatch {
MetaCoreAssetGuid MeshAssetGuid{};
std::vector<MetaCoreAssetGuid> MaterialAssetGuids{};
std::uint64_t ParameterHash = 0U;
std::uint64_t RenderStateHash = 0U;
std::vector<MetaCoreId> ObjectIds{};
};
class MetaCoreOcclusionHistory {
public:
[[nodiscard]] bool IsVisible(MetaCoreId objectId, bool occlusionQueryVisible, bool cameraCut,
bool dynamicObject, bool newlyVisible, std::uint32_t graceFrames);
void BeginFrame();
void Reset();
private:
struct Entry { std::uint32_t GraceRemaining = 0U; std::uint64_t LastTouchedFrame = 0U; };
std::unordered_map<MetaCoreId, Entry> Entries_{};
std::uint64_t FrameIndex_ = 0U;
};
struct MetaCoreParticleState {
glm::vec3 Position{};
glm::vec3 Velocity{};
glm::vec3 Color{1.0F};
float Size = 0.0F;
float Age = 0.0F;
float Lifetime = 1.0F;
};
class MetaCoreCpuParticleEmitter {
public:
void Reset(std::uint32_t seed);
void Simulate(const MetaCoreParticleEmitterComponent& component, float deltaSeconds,
std::uint32_t globalRemaining);
[[nodiscard]] const std::vector<MetaCoreParticleState>& GetParticles() const { return Particles_; }
[[nodiscard]] std::uint32_t GetDroppedParticleCount() const { return DroppedParticleCount_; }
private:
[[nodiscard]] float NextRandom();
void Spawn(const MetaCoreParticleEmitterComponent& component);
std::vector<MetaCoreParticleState> Particles_{};
std::uint32_t RandomState_ = 1U;
std::uint32_t DroppedParticleCount_ = 0U;
float EmissionAccumulator_ = 0.0F;
float Elapsed_ = 0.0F;
bool BurstEmitted_ = false;
};
struct MetaCoreMaterialTemplateParameter {
std::string Name{};
MetaCoreMaterialParameterType Type = MetaCoreMaterialParameterType::Float;
@ -362,9 +554,17 @@ private:
[[nodiscard]] std::int32_t MetaCoreSelectLod(const MetaCoreLodGroupComponent& group, float screenHeight, std::int32_t previousLevel = -1);
[[nodiscard]] std::vector<MetaCoreId> MetaCoreAllocateShadowBudget(const std::vector<MetaCoreShadowCandidate>& candidates, const MetaCoreRenderSettingsDocument& settings);
[[nodiscard]] std::uint32_t MetaCoreAllocateParticleBudget(std::uint32_t requested, std::uint32_t emitterMaximum, std::uint32_t globalRemaining);
[[nodiscard]] std::vector<MetaCoreReflectionProbeBlend> MetaCoreSelectReflectionProbes(
const std::vector<MetaCoreReflectionProbeCandidate>& candidates, const glm::vec3& worldPosition);
[[nodiscard]] std::vector<MetaCoreInstanceBatch> MetaCoreBuildInstanceBatches(
const std::vector<MetaCoreInstanceCandidate>& candidates, bool instancingEnabled = true);
void MetaCoreAccumulateVisibilityResult(MetaCoreVisibilityStatistics& statistics, MetaCoreVisibilityResult result,
std::uint64_t triangleCount = 0U);
[[nodiscard]] const MetaCorePostProcessProfileDocument& MetaCoreResolvePostProcessProfile(
const MetaCorePostProcessProfileDocument& engineDefault,
const MetaCorePostProcessProfileDocument* sceneProfile,
const MetaCorePostProcessProfileDocument* cameraOverride);
[[nodiscard]] std::string_view MetaCoreRenderDebugViewName(MetaCoreRenderDebugView value);
[[nodiscard]] std::optional<MetaCoreRenderDebugView> MetaCoreParseRenderDebugView(std::string_view value);
} // namespace MetaCore

View File

@ -180,6 +180,16 @@ struct MetaCoreMeshRendererComponent {
MC_PROPERTY()
bool Visible = true;
MC_PROPERTY()
std::uint32_t RenderingLayerMask = 1U;
MC_PROPERTY()
float MaxDrawDistance = 0.0F;
MC_PROPERTY()
bool StaticForCulling = true;
MC_PROPERTY()
bool CastShadows = true;
MC_PROPERTY()
bool ReceiveShadows = true;
};
MC_STRUCT()

View File

@ -247,17 +247,17 @@ MetaCore 已经超过单纯编辑器 Demo 阶段,当前具备:
需要补齐:
- [x] Directional、Point、Spot Light 的完整组件语义。
- [ ] 阴影开关、级联、质量、偏移和性能配置。
- [ ] 天空盒、IBL、反射探针和环境配置资产化。
- [ ] 后处理 Profile曝光、色调映射、Bloom、抗锯齿等。
- [ ] Shader/Material 扩展工作流,而不只依赖固定 PBR 参数。
- [ ] LOD Group、实例化、遮挡剔除和大型场景可见性策略。
- [ ] 粒子、Line/Trail、Decal、Terrain 等常用表现组件。
- [ ] Overdraw、Wireframe、法线、光照和性能调试视图。
- [x] 阴影开关、级联、质量、偏移和性能配置。
- [x] 天空盒、IBL、静态反射探针和环境配置资产化。
- [x] 后处理 Profile曝光、色调映射、Bloom、抗锯齿等。
- [x] 模板化 Material 扩展工作流,而不只依赖固定 PBR 参数。
- [x] LOD Group、实例批次、遮挡剔除和大型场景可见性策略。
- [x] 粒子、Line/Trail、Decal、Terrain 等常用表现组件首版
- [x] Lit/Unlit、材质通道、级联、LOD、遮挡、探针及性能调试视图。
#### 阶段实施状态2026-07-22
问题 7 已完成第一阶段基础架构和部分 R7.1/R7.2,实现尚未达到完整关闭条件,以上未完成项继续保持未勾选
R7.1R7.4 的代码交付范围已经接通;问题 7 的功能清单在本版本完成。正式关闭仍受本文末尾列出的“GPU 参考机签收门禁”约束:当前工作站没有可用的 NVIDIA 参考环境,因此不能把 SSIM、GPU p95 和 Editor/Player 像素一致性伪报为通过
已经完成:
@ -265,31 +265,41 @@ MetaCore 已经超过单纯编辑器 Demo 阶段,当前具备:
- `MetaCoreLightComponent` 已支持 Directional、Point、Spot、物理强度、颜色/色温、范围、Spot 内外角、衰减、阴影开关、Bias、Normal Bias、优先级和 Lighting Mask。旧场景缺少灯光类型时迁移为 Directional并将旧归一化强度迁移到物理强度范围。
- Filament 后端已创建并增量同步三类灯光映射位置、方向、强度、范围、Spot Cone、灯光通道及阴影参数方向光支持 1/2/4 级联和分割/距离/Atlas 配置Point/Spot 阴影按优先级、相机距离和对象 ID 确定性分配预算。
- Environment/Post-process Profile 已具备版本化二进制读写和 GUID 引用。运行时扫描 `Assets`、`Runtime`,按 Camera > Scene > 引擎默认值解析后处理;缺失 Profile 会报告 GUID 和回退行为。Cook 会收集启动场景引用的 Profile并拒绝缺失、损坏或版本不兼容的内容。
- 后处理已映射 None/FXAA/TAA、1/2/4/8 MSAA、Bloom、手动曝光、ACES/Filmic/Linear Tone Mapping、饱和度和对比度。Filament 当前集成层没有自动曝光测光实现,自动曝光暂时按上下限中点进行确定性降级,故本项尚未完成。
- Profile 和 `Runtime/Rendering.mcruntime` 现在按文件路径、大小及修改时间检测变化,并在最多约 250 ms 内重新加载;同一 GUID 的内容修改、新建 Profile 和 KTX 更新都会重新提交,不再要求重启 Editor。Game View 使用主相机并与 Player 保持 Camera > Scene > 默认值优先级Scene View 继续使用编辑器相机和 Scene Profile。Bloom 阈值 UI 已按 Filament 实际能力收敛为固定 1.0 阈值开关,不再显示无效的任意数值滑块。
- 后处理已映射 None/FXAA/TAA、1/2/4/8 MSAA、Bloom、手动曝光、ACES/Filmic/Linear Tone Mapping、饱和度和对比度。自动曝光会根据环境和场景物理灯光估算照度在 Profile 上下限内按亮/暗适应速度平滑TAA+MSAA 会确定性降级为 MSAA 1 并输出诊断。默认曝光已改为 EV100 15、Bloom 默认关闭,修复导入普通 PBR 模型看起来像自发光的问题。
- 材质资产已增加稳定 `TemplateId + TemplateVersion + 强类型参数表`,注册 PBR、Unlit、Transparent、Decal、Terrain、Particle、Line/Trail 七种内置模板Inspector 根据 Schema 编辑参数,模板切换仅迁移名称和类型兼容的值,旧 Shader Model 自动迁移且保持材质 GUID。
- 已新增 ReflectionProbe、LODGroup、ParticleEmitter、LineRenderer、TrailRenderer、DecalProjector、Terrain 的可反射组件定义并接通场景快照、JSON、Prefab 创建/实例化/Apply、Undo/Play 状态、Inspector、Render Snapshot 和 Player 类型注册。LOD 迟滞、阴影分配和粒子预算已有确定性算法与单元测试。
- 新增独立 `MetaCoreRenderTests`,覆盖渲染配置/Profile 往返、Profile 优先级、模板校验与迁移、LOD 迟滞、阴影/粒子预算,以及七类表现组件的场景快照和 JSON 往返。
- 七种模板现在都有引擎随版本发布的 `.mat` 源码和由 Host `matc` 生成的 `.filamat`Editor/Player 构建会自动 stageCook 生成包含模板版本、源码 Hash、产物 Hash、平台、后端和编译选项的 `templates.manifest.json`。项目材质缺模板、参数不兼容或使用未来模板版本时 Cook 失败Player 不执行临时材质编译。
- 已新增 ReflectionProbe、LODGroup、ParticleEmitter、LineRenderer、TrailRenderer、DecalProjector、Terrain 的可反射组件定义并接通场景快照、JSON、Prefab 创建/实例化/Apply、Undo/Play 状态、Inspector、Render Snapshot 和 Player 类型注册。确定性算法层现已包含 LOD 迟滞、阴影分配、最多两个反射探针选择/归一化混合、实例批次键、遮挡宽限历史、可见性原因统计,以及固定随机种子的 CPU 粒子模拟和全局/发射器预算丢弃。
- Reflection Probe Inspector 新增源环境、162048 烘焙尺寸、派生 GUID 和“烘焙/重新烘焙”入口。`tools/bake_reflection_probe.sh` 使用 GLIBC 兼容 `cmgen` 输出版本 1 的 `probe_ibl.ktx`、`probe_skybox.ktx`、SH 和带源 SHA-256 的 `probe.mcrprobe`Cook 校验版本、GUID、路径、KTX 和源 Hash并收集到 `Content/Rendering/ReflectionProbes/<GUID>`
- Render Bridge 已接入静态 BVH依次执行 Layer/距离/视锥、LOD、实例批次和保守遮挡遮挡使用上一帧 64×36 深度层级、相机突变/动态/新出现对象宽限。统计现在包含提交/可见、各剔除原因、LOD、实例批次、Draw Call、可见三角形、显存估算及 CPU/GPU 时间。实例批次键改为无字符串分配的 GUID Hash100k CPU 基准 p95 从 26.00 ms 降至 2.66 ms。
- Filament 执行后端新增可复用动态 Vertex/Index Buffer 池CPU 粒子转 BillboardLine/Trail 转相机朝向 RibbonDecal 首版提交 Box Projector 体Terrain 导入 16-bit Heightmap 并按距离改变采样步长;全局粒子/动态线预算确定性截断Terrain 投射/接收阴影。相关资源随 Editor、Play Mode 和 Player 使用同一 Render Snapshot。
- 修复 ParticleEmitter 首次发射或粒子数增长时动态网格扩容导致的 Filament `PreconditionPanic`:统一按“移出 Scene、销毁 Renderable/Entity、销毁 MaterialInstance、销毁 Vertex/Index Buffer”的依赖顺序释放资源。该路径同时覆盖 Particle、Line、Trail、Decal 和 TerrainSmoke 回归会强制执行 1→128 粒子扩容、组件移除和桥接器关闭。
- Scene View 新增 Lit、Unlit、Wireframe、Overdraw、World Normal、Albedo、Roughness、Metallic、Shadow Cascades、LOD、Occlusion、Reflection Probe 模式;专用 `render_debug.filamat` 在调试时替换 primitive 材质并在切回 Lit 时恢复。Development Player 可使用 `--render-debug <mode>`,正式 Release Player 确定性拒绝该参数并输出诊断。
- Editor 新增“渲染设置”面板,可编辑 `Runtime/Rendering.mcruntime` 的质量、级联阴影、Atlas/本地光阴影预算、AA/MSAA、实例化、LOD、遮挡宽限、粒子及动态线预算。Scene 面板可创建/选择并直接编辑 Environment/Post-process ProfileCamera Inspector 可设置 Post-process 覆盖,并明确显示 Camera > Scene > 引擎默认值的优先级。
- 新增独立 `MetaCoreRenderTests`,覆盖渲染配置/Profile 往返、Profile 优先级、模板校验与迁移、LOD 迟滞、阴影/粒子预算、反射探针双混合、实例分批、遮挡宽限、可见性统计、CPU 粒子确定性,以及七类表现组件的场景快照和 JSON 往返。
尚未完成:
首版实现边界与待签收项
- Reflection Probe 的固定相机离线烘焙、版本化 Cubemap/KTX 派生数据、影响范围选择及最多两个探针混合。
- 阴影 PCF 档位的完整后端映射、Inspector 预算提示、灯光/探针 Gizmo 和环境预览/重新烘焙入口。
- 基于真实画面亮度的自动曝光、Bloom 数值阈值的精确后端语义,以及不支持 AA/MSAA 组合的集中诊断。
- 七种材质模板的完整 `.mat -> matc -> .filamat` 离线产物链、平台/源码 Hash 缓存键和 Player 禁止临时编译门禁。目前完成的是模板数据契约、迁移和编辑器入口。
- LOD 的实际 Mesh 切换/抖动过渡、自动实例批次、静态 BVH、上一帧 HZB 遮挡、宽限帧及可见性/Draw Call/CPU/GPU/显存统计。
- Particle、Line/Trail、Decal 和 Terrain 的模拟、动态缓冲、Filament 渲染、Picking/物理高度场等实际执行后端当前仅完成组件、序列化、Prefab、Inspector、Snapshot 和预算基础。
- Lit/Unlit/Wireframe/Overdraw/材质通道/Shadow Cascades/LOD/Occlusion/Reflection Probe 调试视图,以及 Development Player 启动参数。
- GPU 黄金场景、SSIM 门禁、NVIDIA 参考机 Manifest 和 100k 实例工业厂区 1080p/60 FPS 基准。当前工作站仍需先修复 NVIDIA 驱动,不能记录正式 GPU 基线。
- Filament 的环境光是 Scene 级对象;本版保存并计算最多两个对象探针的权重、烘焙和交付数据,但 Lit 路径尚不能对每个 glTF primitive 做真正的双探针纹理混合。调试视图可显示选择与权重。这是当前后端限制,不应描述成已经完成对象级混合。
- 自动曝光使用场景物理灯光/环境估算,不是 GPU 亮度直方图Wireframe/Overdraw 是诊断材质替换,其中 Overdraw 还不是逐像素累加热图。
- Decal 当前是 Box Projector 体提交与 Layer/距离/角度 CPU 过滤,尚未实现对接收表面的法线/Base Color 投影Terrain 已渲染 16-bit 高度场和距离 LOD但四层混合、Picking 与 Bullet 静态高度场尚未进入执行后端。这几项必须在 GPU 签收前继续补齐,不能用组件/算法存在代替完成。
- 工业厂区正式门禁仍未执行:需要修复 NVIDIA 驱动并锁定参考机,在 1920×1080 固定相机路径记录 Manifest、SSIM ≥ 0.995、稳态 CPU/GPU p95 ≤ 16.67 ms 和相对基线不回退超过 10%。因此“功能清单完成”不等于问题 7 已正式关闭。
#### 本次构建与验证
- 构建根目录:`/home/hello/MetaCore/build/linux-development`。
- Filament Host Tools 构建根目录:`/home/hello/MetaCore/build/filament-host-tools`;工程实际使用的工具位于 `build/filament-host-tools/install/bin/matc``build/filament-host-tools/install/bin/cmgen`
- CMake 配置Ninja、`RelWithDebInfo`;目录名为 `linux-development`,但它不是纯 Debug 构建。
- Editor 可执行文件:`build/linux-development/MetaCoreEditorApp`。
- Player 可执行文件:`build/linux-development/MetaCorePlayer`。
- 渲染测试:`build/linux-development/MetaCoreRenderTests`。
- 100k CPU 可见性基准:`build/linux-development/MetaCoreRenderBenchmark`。
- 主要静态库:`build/linux-development/libMetaCoreRendering.a`、`libMetaCoreRender.a`、`libMetaCoreEditor.a`。
- 已重新编译并链接 `MetaCoreRenderTests`、`MetaCoreEditorApp` 和 `MetaCorePlayer``MetaCoreDeliveryTests`、`MetaCoreRenderTests`、`MetaCoreSmokeTests`、`MetaCoreAssetPipelineTests` 共 4/4 通过。
- 2026-07-22 最新验证已重新编译并链接 `MetaCoreRenderTests`、`MetaCoreSmokeTests`、`MetaCoreEditorApp` 和 `MetaCorePlayer`;沙箱内 `MetaCoreRenderTests``MetaCoreSmokeTests` CTest 2/2 通过。另在宿主 `DISPLAY=:0`、NVIDIA RTX 4060、OpenGL 后端使用 `METACORE_SMOKE_PARTICLE_LIFECYCLE_ONLY=1` 定点执行粒子动态 Renderable 的创建、1→128 扩容、移除和 Shutdown退出码为 0。此前 100k 基准结果为100k 输入、17,955 可见对象、32,767 BVH 节点BVH 构建 28.15 ms、120 帧稳态 CPU p95 2.66 ms该数值仅覆盖 CPU 可见性与实例分组,不冒充 1080p GPU 帧时间。
- 已解决 Ubuntu 22.04 / GLIBC 2.35 上 SDK 预编译 `matc`、`cmgen` 依赖 GLIBC 2.38 而无法启动的问题。`tools/build_filament_host_tools.sh` 会检出与 SDK 匹配的 Filament `v1.71.5`(提交 `b0f2090cccf3c11f524ed8053746aba3de6be199`),在目标主机上用 Clang/libc++ 构建并安装 Host ToolsCMake 会优先探测这组工具,也允许通过 `METACORE_FILAMENT_MATC`、`METACORE_FILAMENT_CMGEN` 单独覆盖路径。
- 新 `matc`、`cmgen` 的 ELF 版本需求最高均为 `GLIBC_2.35`,已在当前 GLIBC 2.35 主机执行验证:`matc` 成功重新生成 `grid.filamat`、`rml_ui.filamat``cmgen` 成功从 HDR 生成 IBL KTX、Skybox KTX 和球谐系数。首次准备工具执行 `./tools/build_filament_host_tools.sh`,后续构建可复用增量产物。
- 本轮 ELF 检查结果:`MetaCoreEditorApp` 与 `MetaCorePlayer` 的最高需求为 `GLIBC_2.34``matc` 与 `cmgen``GLIBC_2.35`,符合 Ubuntu 22.04 / GLIBC 2.35 目标版本。
- 调整位置:全局质量/阴影/AA/LOD/遮挡/粒子和动态线预算及调试视图在 Editor 的“渲染设置”面板;环境与场景后处理在 Scene 面板“渲染 Profile”相机覆盖在 Camera Inspector“后处理覆盖”材质模板和参数在 Material Inspector灯光、LOD 和表现组件在对象 InspectorReflection Probe 的源环境、尺寸及烘焙按钮在该组件 Inspector。
- 当前没有生成 Release/Development 交付包本次“build”产物是上述 CMake 构建目录中的开发验证二进制。问题 7 只有在剩余执行后端、GPU 回归和工业场景性能门禁完成后才能标记关闭。
### 8. 动画系统

View File

@ -0,0 +1,47 @@
#include "MetaCoreRendering/MetaCoreRendering.h"
#include <algorithm>
#include <chrono>
#include <iostream>
#include <vector>
int main() {
constexpr std::size_t kInstanceCount = 100000U;
constexpr std::size_t kFrameCount = 120U;
std::vector<MetaCore::MetaCoreBvhItem> items;
items.reserve(kInstanceCount);
for (std::size_t index = 0; index < kInstanceCount; ++index) {
const float x = static_cast<float>(index % 500U) * 0.015F - 3.75F;
const float y = static_cast<float>((index / 500U) % 200U) * 0.015F - 1.5F;
const float z = static_cast<float>(index % 13U) * 0.002F;
const glm::vec3 center{x, y, z};
items.push_back({static_cast<MetaCore::MetaCoreId>(index + 1U), {center - glm::vec3(0.005F), center + glm::vec3(0.005F)}, 1U, 0.0F, false});
}
MetaCore::MetaCoreStaticVisibilityBvh bvh;
const auto buildStart = std::chrono::steady_clock::now();
bvh.Build(items);
const auto buildEnd = std::chrono::steady_clock::now();
const auto frustum = MetaCore::MetaCoreExtractFrustum(glm::mat4(1.0F));
std::vector<double> frameTimes;
frameTimes.reserve(kFrameCount);
std::size_t visibleCount = 0U;
for (std::size_t frame = 0; frame < kFrameCount; ++frame) {
const auto begin = std::chrono::steady_clock::now();
MetaCore::MetaCoreVisibilityStatistics statistics;
const auto visible = bvh.Query(frustum, {}, 1U, &statistics);
std::vector<MetaCore::MetaCoreInstanceCandidate> candidates;
candidates.reserve(visible.size());
for (const auto id : visible) candidates.push_back({id, {}, {}, 1U, 1U, true});
const auto batches = MetaCore::MetaCoreBuildInstanceBatches(candidates);
visibleCount = visible.size() + (batches.empty() ? 0U : 0U);
const auto end = std::chrono::steady_clock::now();
frameTimes.push_back(std::chrono::duration<double, std::milli>(end - begin).count());
}
std::sort(frameTimes.begin(), frameTimes.end());
const double p95 = frameTimes[static_cast<std::size_t>(0.95 * static_cast<double>(frameTimes.size() - 1U))];
const double buildMs = std::chrono::duration<double, std::milli>(buildEnd - buildStart).count();
std::cout << "{\"instances\":" << kInstanceCount << ",\"visible\":" << visibleCount
<< ",\"bvh_nodes\":" << bvh.GetNodeCount() << ",\"build_ms\":" << buildMs
<< ",\"steady_cpu_p95_ms\":" << p95 << "}\n";
return visibleCount > 0U && bvh.GetNodeCount() > 0U ? 0 : 1;
}

View File

@ -8,6 +8,8 @@
#include <filesystem>
#include <iostream>
#include <glm/geometric.hpp>
namespace {
int Failures = 0;
void Expect(bool condition, const char* message) { if (!condition) { ++Failures; std::cerr << "FAIL: " << message << '\n'; } }
@ -69,6 +71,8 @@ void TestMaterialTemplates() {
Expect(metallic != material.Parameters.end() && std::abs(metallic->FloatValue-1.0F)<0.0001F, "material range clamps");
material.TemplateId = "project.raw.mat";
Expect(!registry.ValidateAndMigrate(material, nullptr), "project raw material source rejected");
material.TemplateId = "metacore.pbr"; material.TemplateVersion = 999U;
Expect(!registry.ValidateAndMigrate(material, nullptr), "future material template version rejected instead of guessed");
}
void TestBudgetsAndLod() {
@ -90,10 +94,84 @@ void TestBudgetsAndLod() {
Expect(MetaCore::MetaCoreAllocateParticleBudget(500U, 300U, 200U)==200U, "particle budget clamps globally");
}
void TestVisibilityProbeInstancingAndParticles() {
MetaCore::MetaCoreReflectionProbeCandidate low; low.ObjectId=2; low.Probe.BakedCubemapGuid=MetaCore::MetaCoreAssetGuid::Generate(); low.Probe.Shape=MetaCore::MetaCoreReflectionProbeShape::Sphere; low.Probe.Radius=10.0F;
MetaCore::MetaCoreReflectionProbeCandidate high=low; high.ObjectId=1; high.Probe.BakedCubemapGuid=MetaCore::MetaCoreAssetGuid::Generate(); high.Probe.Priority=2; high.Position={1,0,0};
const auto probes=MetaCore::MetaCoreSelectReflectionProbes({low,high},{0,0,0});
Expect(probes.size()==2U&&probes[0].ObjectId==1U&&std::abs(probes[0].Weight+probes[1].Weight-1.0F)<0.0001F,"probe selection uses priority and normalized two-probe blend");
MetaCore::MetaCoreOcclusionHistory history;history.BeginFrame();
Expect(history.IsVisible(1U,false,false,false,true,2U),"new object gets occlusion grace frame 1");history.BeginFrame();
Expect(history.IsVisible(1U,false,false,false,false,2U),"new object gets occlusion grace frame 2");history.BeginFrame();
Expect(!history.IsVisible(1U,false,false,false,false,2U),"occluded object culled after grace");
Expect(history.IsVisible(1U,false,true,false,false,2U),"camera cut restores grace");
const auto mesh=MetaCore::MetaCoreAssetGuid::Generate();const auto material=MetaCore::MetaCoreAssetGuid::Generate();
const std::vector<MetaCore::MetaCoreInstanceCandidate> candidates{{2,mesh,{material},10,20,true},{1,mesh,{material},10,20,true},{3,mesh,{material},11,20,true}};
const auto batches=MetaCore::MetaCoreBuildInstanceBatches(candidates);
Expect(batches.size()==2U&&batches[0].ObjectIds.size()==2U,"matching mesh/material/parameters become one instance batch");
MetaCore::MetaCoreVisibilityStatistics statistics;MetaCore::MetaCoreAccumulateVisibilityResult(statistics,MetaCore::MetaCoreVisibilityResult::Visible,120U);MetaCore::MetaCoreAccumulateVisibilityResult(statistics,MetaCore::MetaCoreVisibilityResult::FrustumCulled);
Expect(statistics.Submitted==2U&&statistics.Visible==1U&&statistics.FrustumCulled==1U&&statistics.VisibleTriangleCount==120U,"visibility diagnostics count reasons and triangles");
MetaCore::MetaCoreParticleEmitterComponent emitter;emitter.EmissionRate=10.0F;emitter.BurstCount=4U;emitter.MaxParticles=5U;emitter.Lifetime=2.0F;emitter.RandomSeed=42U;
MetaCore::MetaCoreCpuParticleEmitter simulation;simulation.Reset(emitter.RandomSeed);simulation.Simulate(emitter,0.1F,3U);
Expect(simulation.GetParticles().size()==3U&&simulation.GetDroppedParticleCount()==2U,"particle emitter drops deterministically at global budget");
const auto firstPosition=simulation.GetParticles()[0].Position;MetaCore::MetaCoreCpuParticleEmitter repeated;repeated.Reset(emitter.RandomSeed);repeated.Simulate(emitter,0.1F,3U);
Expect(glm::length(firstPosition-repeated.GetParticles()[0].Position)<0.0001F,"particle random seed is deterministic");
}
void TestProductionVisibilityAndGeometry() {
const auto frustum = MetaCore::MetaCoreExtractFrustum(glm::mat4(1.0F));
MetaCore::MetaCoreStaticVisibilityBvh bvh;
bvh.Build({
{1U, {{-0.2F,-0.2F,-0.2F},{0.2F,0.2F,0.2F}}, 1U, 0.0F, false},
{2U, {{4.0F,4.0F,4.0F},{5.0F,5.0F,5.0F}}, 1U, 0.0F, false},
{3U, {{-0.1F,-0.1F,-0.1F},{0.1F,0.1F,0.1F}}, 2U, 0.0F, false}
});
MetaCore::MetaCoreVisibilityStatistics statistics;
const auto visible = bvh.Query(frustum, {}, 1U, &statistics);
Expect(visible.size()==1U&&visible[0]==1U&&bvh.GetNodeCount()>0U,"BVH performs layer and frustum visibility queries");
MetaCore::MetaCoreConservativeHzb hzb;
const std::vector<float> depth(16U,10.0F);
Expect(hzb.Build(4U,4U,depth)&&hzb.GetMipCount()==3U,"HZB builds deterministic max-depth pyramid");
Expect(hzb.IsVisible({0.25F,0.25F,0.75F,0.75F},5.0F),"HZB keeps nearer object");
Expect(!hzb.IsVisible({0.25F,0.25F,0.75F,0.75F},20.0F),"HZB rejects conservatively occluded object");
MetaCore::MetaCoreParticleState particle;particle.Size=2.0F;particle.Lifetime=1.0F;particle.Color={1,0,0};
const auto billboards=MetaCore::MetaCoreBuildParticleBillboards({&particle,1U},{1,0,0},{0,1,0},glm::mat4(1.0F));
Expect(billboards.Vertices.size()==4U&&billboards.Indices.size()==6U,"particle billboard geometry generated");
const std::array<glm::vec3,3> line{{{0,0,0},{1,0,0},{1,1,0}}};
const auto ribbon=MetaCore::MetaCoreBuildLineRibbon(line,0.1F,{1,1,1,1},{1,0,0,0},{0,0,-1},32U);
Expect(ribbon.Vertices.size()==6U&&ribbon.Indices.size()==12U,"line and trail ribbon geometry generated");
const std::array<std::uint16_t,4> heights{{0U,65535U,32768U,0U}};
const auto terrain=MetaCore::MetaCoreBuildTerrainGeometry(heights,2U,2U,{10,10,5},1U);
Expect(terrain.Vertices.size()==4U&&terrain.Indices.size()==6U&&terrain.Vertices[1].Position.z==5.0F,"16-bit terrain geometry generated");
MetaCore::MetaCoreTrailHistory trail;trail.Update({0,0,0},0.0F,1.0F,0.1F,3U);trail.Update({1,0,0},0.1F,1.0F,0.1F,3U);trail.Update({2,0,0},0.1F,1.0F,0.1F,3U);trail.Update({3,0,0},0.1F,1.0F,0.1F,3U);
Expect(trail.GetPoints().size()==3U&&trail.GetPoints().front().Position.x==1.0F,"trail history reuses bounded point budget");
MetaCore::MetaCoreDecalProjectorComponent decal;decal.Size={2,2,2};decal.LayerMask=1U;
Expect(MetaCore::MetaCoreIsDecalVisible(decal,glm::mat4(1.0F),{{-0.5F,-0.5F,-0.5F},{0.5F,0.5F,0.5F}},1U,{0,0,5}),"decal box and layer filtering accepts receiver");
MetaCore::MetaCoreMaterialCookRequest cook{"metacore.pbr",1U,"source","Linux-x86_64","OpenGL","quality=high"};
const auto key=MetaCore::MetaCoreBuildMaterialCookKey(cook);cook.CompilerOptions="quality=low";
Expect(key.size()==64U&&key!=MetaCore::MetaCoreBuildMaterialCookKey(cook),"material cook cache key covers compiler options");
Expect(MetaCore::MetaCoreParseRenderDebugView("world-normal")==MetaCore::MetaCoreRenderDebugView::WorldNormal&&
!MetaCore::MetaCoreParseRenderDebugView("invalid"),"development Player debug view parsing is strict");
MetaCore::MetaCoreReflectionProbeDerivedHeader probe;probe.ProbeGuid=MetaCore::MetaCoreAssetGuid::Generate();probe.SourceHash="abc";probe.IblRelativePath="probe_ibl.ktx";probe.SkyboxRelativePath="probe_skybox.ktx";
const auto path=std::filesystem::temp_directory_path()/"MetaCoreRenderTests"/"probe.mcrprobe";
Expect(MetaCore::MetaCoreWriteReflectionProbeDerivedHeader(path,probe),"reflection probe derived header write");
const auto loaded=MetaCore::MetaCoreReadReflectionProbeDerivedHeader(path);
Expect(loaded&&loaded->ProbeGuid==probe.ProbeGuid&&loaded->IblRelativePath==probe.IblRelativePath,"reflection probe derived header round trip");
}
void TestSceneProductionComponents() {
MetaCore::MetaCoreScene scene;
auto object = scene.CreateGameObject("Rendering Components");
object.AddComponent<MetaCore::MetaCoreReflectionProbeComponent>().Priority = 7;
auto& reflectionProbe=object.AddComponent<MetaCore::MetaCoreReflectionProbeComponent>();reflectionProbe.Priority=7;reflectionProbe.SourceEnvironmentPath="Assets/Environments/factory.hdr";reflectionProbe.BakeResolution=512U;
object.AddComponent<MetaCore::MetaCoreLodGroupComponent>().Levels.push_back({0.5F, {}, {}});
object.AddComponent<MetaCore::MetaCoreParticleEmitterComponent>().MaxParticles = 321U;
object.AddComponent<MetaCore::MetaCoreLineRendererComponent>().Points = {{0,0,0},{1,0,0}};
@ -104,6 +182,7 @@ void TestSceneProductionComponents() {
MetaCore::MetaCoreScene restored; restored.RestoreSnapshot(snapshot);
const auto restoredObject = restored.FindGameObject(object.GetId());
Expect(restoredObject && restoredObject.HasComponent<MetaCore::MetaCoreReflectionProbeComponent>(), "reflection probe snapshot");
Expect(restoredObject.GetComponent<MetaCore::MetaCoreReflectionProbeComponent>().BakeResolution==512U&&restoredObject.GetComponent<MetaCore::MetaCoreReflectionProbeComponent>().SourceEnvironmentPath=="Assets/Environments/factory.hdr","reflection probe bake settings snapshot");
Expect(restoredObject.HasComponent<MetaCore::MetaCoreLodGroupComponent>() && restoredObject.HasComponent<MetaCore::MetaCoreParticleEmitterComponent>(), "lod and particles snapshot");
Expect(restoredObject.HasComponent<MetaCore::MetaCoreLineRendererComponent>() && restoredObject.HasComponent<MetaCore::MetaCoreTrailRendererComponent>(), "line and trail snapshot");
Expect(restoredObject.HasComponent<MetaCore::MetaCoreDecalProjectorComponent>() && restoredObject.HasComponent<MetaCore::MetaCoreTerrainComponent>(), "decal and terrain snapshot");
@ -117,6 +196,6 @@ void TestSceneProductionComponents() {
}
int main() {
TestSettingsAndProfiles(); TestMaterialTemplates(); TestBudgetsAndLod(); TestSceneProductionComponents();
TestSettingsAndProfiles(); TestMaterialTemplates(); TestBudgetsAndLod(); TestVisibilityProbeInstancingAndParticles(); TestProductionVisibilityAndGeometry(); TestSceneProductionComponents();
if(Failures==0)std::cout<<"MetaCoreRenderTests passed\n";return Failures==0?0:1;
}

View File

@ -5383,12 +5383,56 @@ void MetaCoreTestSceneEnvironmentSettings() {
std::filesystem::remove_all(tempRoot);
}
void MetaCoreTestParticleRenderableGrowthAndRemoval() {
MetaCore::MetaCoreWindow window;
if (!window.Initialize(640, 480, "ParticleRenderableLifecycleSmoke")) {
std::cout << "[SKIP] Particle renderable lifecycle smoke requires an available display." << std::endl;
return;
}
MetaCore::MetaCoreFilamentSceneBridge bridge;
bridge.SetProjectRootPath(MetaCoreTestRepositoryRoot() / "TestProject");
MetaCoreExpect(bridge.Initialize(window), "粒子生命周期测试桥接器应初始化成功");
bridge.ApplySceneView(MetaCore::MetaCoreSceneView{});
MetaCore::MetaCoreParticleEmitterComponent emitter;
emitter.EmissionRate = 0.0F;
emitter.BurstCount = 1U;
emitter.MaxParticles = 256U;
emitter.Lifetime = 10.0F;
emitter.RandomSeed = 1U;
MetaCore::MetaCoreSceneRenderSyncSnapshot snapshot;
snapshot.ParticleEmitters.push_back({701U, emitter, glm::mat4(1.0F)});
bridge.SyncScene(snapshot, nullptr, false, false);
// Changing the seed resets the simulation. The larger burst forces the
// dynamic mesh to outgrow its one-particle allocation and rebuild, which
// regresses the Filament resource-lifetime crash reported by the editor.
snapshot.ParticleEmitters.front().Component.RandomSeed = 2U;
snapshot.ParticleEmitters.front().Component.BurstCount = 128U;
bridge.SyncScene(snapshot, nullptr, false, false);
snapshot.ParticleEmitters.clear();
bridge.SyncScene(snapshot, nullptr, false, false);
bridge.Shutdown();
window.Shutdown();
}
} // namespace
int main() {
std::setvbuf(stdout, nullptr, _IONBF, 0);
std::setvbuf(stderr, nullptr, _IONBF, 0);
_putenv_s("METACORE_SYNCHRONOUS_ASSET_BOOTSTRAP", "1");
if (const char* particleLifecycleOnly = std::getenv("METACORE_SMOKE_PARTICLE_LIFECYCLE_ONLY");
particleLifecycleOnly != nullptr && std::strcmp(particleLifecycleOnly, "1") == 0) {
std::cout << "[RUN] MetaCoreTestParticleRenderableGrowthAndRemoval..." << std::endl;
MetaCoreTestParticleRenderableGrowthAndRemoval();
std::cout << "MetaCoreSmokeTests particle lifecycle passed\n";
return 0;
}
std::cout << "[RUN] MetaCoreCreateDefaultScene..." << std::endl;
MetaCore::MetaCoreScene scene = MetaCore::MetaCoreCreateDefaultScene();
@ -5555,6 +5599,9 @@ int main() {
std::cout << "[RUN] MetaCoreTestDecoupledSnapshotSync..." << std::endl;
MetaCoreTestDecoupledSnapshotSync();
std::cout << "[RUN] MetaCoreTestParticleRenderableGrowthAndRemoval..." << std::endl;
MetaCoreTestParticleRenderableGrowthAndRemoval();
std::cout << "[RUN] MetaCoreTestNestedModelHierarchyTransformSync..." << std::endl;
MetaCoreTestNestedModelHierarchyTransformSync();

65
tools/bake_reflection_probe.sh Executable file
View File

@ -0,0 +1,65 @@
#!/usr/bin/env bash
set -euo pipefail
if [[ $# -lt 3 || $# -gt 4 ]]; then
echo "Usage: $0 <project-root> <probe-guid> <source.hdr|exr|png> [resolution]" >&2
exit 2
fi
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ENGINE_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
PROJECT_ROOT="$(cd "$1" && pwd)"
PROBE_GUID="$2"
SOURCE_PATH="$3"
RESOLUTION="${4:-256}"
if [[ "${SOURCE_PATH}" != /* ]]; then
SOURCE_PATH="${PROJECT_ROOT}/${SOURCE_PATH}"
fi
if [[ ! -f "${SOURCE_PATH}" ]]; then
echo "Reflection probe source does not exist: ${SOURCE_PATH}" >&2
exit 3
fi
if [[ ! "${PROBE_GUID}" =~ ^[0-9a-fA-F]{32}$ ]]; then
echo "Reflection probe GUID must contain exactly 32 hexadecimal digits." >&2
exit 4
fi
case "${RESOLUTION}" in
16|32|64|128|256|512|1024|2048) ;;
*) echo "Resolution must be a power of two between 16 and 2048." >&2; exit 5 ;;
esac
CMGEN="${METACORE_FILAMENT_CMGEN:-${ENGINE_ROOT}/build/filament-host-tools/install/bin/cmgen}"
if [[ ! -x "${CMGEN}" ]]; then
echo "A runnable cmgen was not found at ${CMGEN}; run tools/build_filament_host_tools.sh first." >&2
exit 6
fi
OUTPUT_ROOT="${PROJECT_ROOT}/Library/DerivedData/Rendering/Probes/${PROBE_GUID}"
TEMP_ROOT="$(mktemp -d "${PROJECT_ROOT}/Library/DerivedData/Rendering/.probe-${PROBE_GUID}.XXXXXX")"
cleanup() { rm -rf "${TEMP_ROOT}"; }
trap cleanup EXIT
"${CMGEN}" --quiet --format=ktx --size="${RESOLUTION}" --deploy="${TEMP_ROOT}" "${SOURCE_PATH}"
SOURCE_STEM="$(basename "${SOURCE_PATH}")"
SOURCE_STEM="${SOURCE_STEM%.*}"
IBL_SOURCE="${TEMP_ROOT}/${SOURCE_STEM}_ibl.ktx"
SKYBOX_SOURCE="${TEMP_ROOT}/${SOURCE_STEM}_skybox.ktx"
if [[ ! -s "${IBL_SOURCE}" || ! -s "${SKYBOX_SOURCE}" ]]; then
echo "cmgen did not produce the expected IBL and skybox KTX files." >&2
exit 7
fi
mkdir -p "${OUTPUT_ROOT}"
install -m 0644 "${IBL_SOURCE}" "${OUTPUT_ROOT}/probe_ibl.ktx"
install -m 0644 "${SKYBOX_SOURCE}" "${OUTPUT_ROOT}/probe_skybox.ktx"
if [[ -s "${TEMP_ROOT}/sh.txt" ]]; then
install -m 0644 "${TEMP_ROOT}/sh.txt" "${OUTPUT_ROOT}/sh.txt"
fi
SOURCE_HASH="$(sha256sum "${SOURCE_PATH}" | cut -d' ' -f1)"
HEADER_TEMP="${TEMP_ROOT}/probe.mcrprobe"
printf 'MCRPROBE 1 %s "%s" "%s" "%s"\n' \
"${PROBE_GUID}" "${SOURCE_HASH}" "probe_ibl.ktx" "probe_skybox.ktx" > "${HEADER_TEMP}"
install -m 0644 "${HEADER_TEMP}" "${OUTPUT_ROOT}/probe.mcrprobe"
echo "Reflection probe baked: ${OUTPUT_ROOT}"

View File

@ -0,0 +1,61 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
FILAMENT_TAG="v1.71.5"
FILAMENT_COMMIT="b0f2090cccf3c11f524ed8053746aba3de6be199"
WORK_ROOT="${PROJECT_ROOT}/build/filament-host-tools"
SOURCE_DIR="${METACORE_FILAMENT_SOURCE_DIR:-${WORK_ROOT}/source}"
BUILD_DIR="${METACORE_FILAMENT_BUILD_DIR:-${WORK_ROOT}/build}"
INSTALL_DIR="${METACORE_FILAMENT_INSTALL_DIR:-${WORK_ROOT}/install/bin}"
if [[ ! -f "${SOURCE_DIR}/CMakeLists.txt" ]]; then
mkdir -p "${WORK_ROOT}"
git clone --depth 1 --branch "${FILAMENT_TAG}" https://github.com/google/filament.git "${SOURCE_DIR}"
fi
ACTUAL_COMMIT="$(git -C "${SOURCE_DIR}" rev-parse HEAD)"
if [[ "${ACTUAL_COMMIT}" != "${FILAMENT_COMMIT}" ]]; then
echo "Filament source mismatch: expected ${FILAMENT_COMMIT}, got ${ACTUAL_COMMIT}" >&2
exit 2
fi
CLANG_C="${METACORE_FILAMENT_CC:-}"
CLANG_CXX="${METACORE_FILAMENT_CXX:-}"
if [[ -z "${CLANG_C}" || -z "${CLANG_CXX}" ]]; then
for version in 18 17 16 15; do
if [[ -x "/usr/bin/clang-${version}" && -x "/usr/bin/clang++-${version}" ]]; then
CLANG_C="/usr/bin/clang-${version}"
CLANG_CXX="/usr/bin/clang++-${version}"
break
fi
done
fi
if [[ ! -x "${CLANG_C}" || ! -x "${CLANG_CXX}" ]]; then
echo "A usable Clang toolchain was not found (Clang 15 or newer is recommended)." >&2
exit 3
fi
cmake -S "${SOURCE_DIR}" -B "${BUILD_DIR}" -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_COMPILER="${CLANG_C}" \
-DCMAKE_CXX_COMPILER="${CLANG_CXX}" \
-DCMAKE_CXX_FLAGS=-stdlib=libc++ \
-DFILAMENT_SKIP_SAMPLES=ON \
-DFILAMENT_BUILD_TESTING=OFF \
-DFILAMENT_SUPPORTS_VULKAN=OFF \
-DFILAMENT_SUPPORTS_WAYLAND=OFF \
-DFILAMENT_SUPPORTS_XCB=OFF \
-DFILAMENT_SUPPORTS_XLIB=OFF \
-DFILAMENT_SKIP_SDL2=ON \
-DFILAMENT_ENABLE_LTO=OFF
cmake --build "${BUILD_DIR}" --target matc cmgen --parallel "${METACORE_FILAMENT_BUILD_JOBS:-2}"
mkdir -p "${INSTALL_DIR}"
cmake -E copy_if_different "${BUILD_DIR}/tools/matc/matc" "${INSTALL_DIR}/matc"
cmake -E copy_if_different "${BUILD_DIR}/tools/cmgen/cmgen" "${INSTALL_DIR}/cmgen"
"${INSTALL_DIR}/matc" --version
"${INSTALL_DIR}/cmgen" --help >/dev/null
echo "GLIBC-compatible Filament tools installed in ${INSTALL_DIR}"