From 2512e3ade63ac0ebe4c33110c15eef0daf2f133f Mon Sep 17 00:00:00 2001 From: Rowland <975945824@qq.com> Date: Wed, 22 Jul 2026 16:08:48 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B8=B2=E6=9F=93=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Apps/MetaCorePlayer/main.cpp | 14 + CMakeLists.txt | 102 ++++- ...-24e7-45c4-a51a-4a11327fd92c.mcpostprocess | Bin 0 -> 625 bytes ...c4-a51a-4a11327fd92c.mcpostprocess.mcasset | Bin 0 -> 1519 bytes ...5c4-a51a-4a11327fd92c.mcpostprocess.mcmeta | 16 + ...-f061-4ab2-be9f-9acdc35f2dfc.mcpostprocess | Bin 0 -> 625 bytes ...b2-be9f-9acdc35f2dfc.mcpostprocess.mcasset | Bin 0 -> 1519 bytes ...ab2-be9f-9acdc35f2dfc.mcpostprocess.mcmeta | 16 + ...-49e6-42e3-a602-a903da59f99f.mcenvironment | Bin 0 -> 406 bytes ...e3-a602-a903da59f99f.mcenvironment.mcasset | Bin 0 -> 1519 bytes ...2e3-a602-a903da59f99f.mcenvironment.mcmeta | 16 + ...-2ac4-44df-9a6f-df6518e33054.mcpostprocess | Bin 0 -> 625 bytes ...df-9a6f-df6518e33054.mcpostprocess.mcasset | Bin 0 -> 1519 bytes ...4df-9a6f-df6518e33054.mcpostprocess.mcmeta | 16 + ...-2d1b-40aa-a0ef-66683b9351db.mcenvironment | Bin 0 -> 406 bytes ...aa-a0ef-66683b9351db.mcenvironment.mcasset | Bin 0 -> 1519 bytes ...0aa-a0ef-66683b9351db.mcenvironment.mcmeta | 16 + ...-b32a-452f-846a-5891bef8e406.mcenvironment | Bin 0 -> 406 bytes ...2f-846a-5891bef8e406.mcenvironment.mcasset | Bin 0 -> 1519 bytes ...52f-846a-5891bef8e406.mcenvironment.mcmeta | 16 + .../Private/MetaCoreBuildPipeline.cpp | 95 +++- .../MetaCoreBuiltinCoreServicesModule.cpp | 63 ++- .../Private/MetaCoreBuiltinEditorModule.cpp | 91 +++- .../Private/Materials/Templates/decal.mat | 2 + .../Private/Materials/Templates/line.mat | 2 + .../Private/Materials/Templates/particle.mat | 2 + .../Private/Materials/Templates/pbr.mat | 2 + .../Private/Materials/Templates/terrain.mat | 2 + .../Materials/Templates/transparent.mat | 2 + .../Private/Materials/Templates/unlit.mat | 2 + .../Private/Materials/render_debug.mat | 21 + .../MetaCoreEditorViewportRenderer.cpp | 16 +- .../Private/MetaCoreFilamentSceneBridge.cpp | 420 +++++++++++++++++- .../Private/MetaCoreSceneRenderSync.cpp | 18 +- .../MetaCoreEditorViewportRenderer.h | 3 + .../MetaCoreFilamentSceneBridge.h | 4 + .../MetaCoreRender/MetaCoreSceneRenderSync.h | 8 + .../Private/MetaCoreRendering.cpp | 218 +++++++++ .../MetaCoreRendering/MetaCoreRendering.h | 200 +++++++++ .../Public/MetaCoreScene/MetaCoreComponents.h | 10 + ...etacore-mature-engine-gap-and-work-plan.md | 52 ++- tests/MetaCoreRenderBenchmark.cpp | 47 ++ tests/MetaCoreRenderTests.cpp | 83 +++- tests/MetaCoreSmokeTests.cpp | 47 ++ tools/bake_reflection_probe.sh | 65 +++ tools/build_filament_host_tools.sh | 61 +++ 46 files changed, 1696 insertions(+), 52 deletions(-) create mode 100644 SandboxProject/Assets/Rendering/0041a59d-24e7-45c4-a51a-4a11327fd92c.mcpostprocess create mode 100644 SandboxProject/Assets/Rendering/0041a59d-24e7-45c4-a51a-4a11327fd92c.mcpostprocess.mcasset create mode 100644 SandboxProject/Assets/Rendering/0041a59d-24e7-45c4-a51a-4a11327fd92c.mcpostprocess.mcmeta create mode 100644 SandboxProject/Assets/Rendering/01231740-f061-4ab2-be9f-9acdc35f2dfc.mcpostprocess create mode 100644 SandboxProject/Assets/Rendering/01231740-f061-4ab2-be9f-9acdc35f2dfc.mcpostprocess.mcasset create mode 100644 SandboxProject/Assets/Rendering/01231740-f061-4ab2-be9f-9acdc35f2dfc.mcpostprocess.mcmeta create mode 100644 SandboxProject/Assets/Rendering/67c50c20-49e6-42e3-a602-a903da59f99f.mcenvironment create mode 100644 SandboxProject/Assets/Rendering/67c50c20-49e6-42e3-a602-a903da59f99f.mcenvironment.mcasset create mode 100644 SandboxProject/Assets/Rendering/67c50c20-49e6-42e3-a602-a903da59f99f.mcenvironment.mcmeta create mode 100644 SandboxProject/Assets/Rendering/b3db5266-2ac4-44df-9a6f-df6518e33054.mcpostprocess create mode 100644 SandboxProject/Assets/Rendering/b3db5266-2ac4-44df-9a6f-df6518e33054.mcpostprocess.mcasset create mode 100644 SandboxProject/Assets/Rendering/b3db5266-2ac4-44df-9a6f-df6518e33054.mcpostprocess.mcmeta create mode 100644 SandboxProject/Assets/Rendering/cfa65625-2d1b-40aa-a0ef-66683b9351db.mcenvironment create mode 100644 SandboxProject/Assets/Rendering/cfa65625-2d1b-40aa-a0ef-66683b9351db.mcenvironment.mcasset create mode 100644 SandboxProject/Assets/Rendering/cfa65625-2d1b-40aa-a0ef-66683b9351db.mcenvironment.mcmeta create mode 100644 SandboxProject/Assets/Rendering/f207fd8c-b32a-452f-846a-5891bef8e406.mcenvironment create mode 100644 SandboxProject/Assets/Rendering/f207fd8c-b32a-452f-846a-5891bef8e406.mcenvironment.mcasset create mode 100644 SandboxProject/Assets/Rendering/f207fd8c-b32a-452f-846a-5891bef8e406.mcenvironment.mcmeta create mode 100644 Source/MetaCoreRender/Private/Materials/Templates/decal.mat create mode 100644 Source/MetaCoreRender/Private/Materials/Templates/line.mat create mode 100644 Source/MetaCoreRender/Private/Materials/Templates/particle.mat create mode 100644 Source/MetaCoreRender/Private/Materials/Templates/pbr.mat create mode 100644 Source/MetaCoreRender/Private/Materials/Templates/terrain.mat create mode 100644 Source/MetaCoreRender/Private/Materials/Templates/transparent.mat create mode 100644 Source/MetaCoreRender/Private/Materials/Templates/unlit.mat create mode 100644 Source/MetaCoreRender/Private/Materials/render_debug.mat create mode 100644 tests/MetaCoreRenderBenchmark.cpp create mode 100755 tools/bake_reflection_probe.sh create mode 100755 tools/build_filament_host_tools.sh diff --git a/Apps/MetaCorePlayer/main.cpp b/Apps/MetaCorePlayer/main.cpp index c4a8fa8..0fadd24 100644 --- a/Apps/MetaCorePlayer/main.cpp +++ b/Apps/MetaCorePlayer/main.cpp @@ -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::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="</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" "$/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 diff --git a/SandboxProject/Assets/Rendering/0041a59d-24e7-45c4-a51a-4a11327fd92c.mcpostprocess b/SandboxProject/Assets/Rendering/0041a59d-24e7-45c4-a51a-4a11327fd92c.mcpostprocess new file mode 100644 index 0000000000000000000000000000000000000000..adbbd64cfa7bc2fb9c6e778a9723ac1249757e1a GIT binary patch literal 625 zcmZuvJxc>Y5Z!>{w;0q$5K?HTaPhRTkjq97QHTUD+K=9l99(vfojr|}{t?Ug8^p@O zGFDdB7XA(I;%>M=7KYj3y?JkE*b-x`oW)EQ`ME|#krY$^6)R>eDm|cMD)n zm^O9&K_ji0g7N2?1`^V&#>wwU%*X_VLtWnESWq6 z1FcZBb;ovp4hBcdMMA5ikUBCBEh8Jpv-(`~i#6Q53NAV(f5k(LL@#IYQz0gVsU41aT8Gi3$-sfBp1dHyRaK}@JB`KdT@4-+B(lWWgNa|9Pf6s966F*PEY^FmvSpVkf!-BK)Q YA|IEr+HZ`XXrGan~zW@Ax5>l`%Yil$YM^le$6f)*)?r&!mMs?Y&JtO~xB3ki?;mgd;nz+1ha4}VgL_6(G_}d4Dm4YaLn%wPb9m&O9@}dM zHo7U7nLwY1NG2Ms8twzjAZgq?w*8Nv>N^H|nc@?Rm`&+=pEGsPoz{8VGyH-SF}zAC zw*UH4r7PuQ$Fe$qn7pcG^;|lBi!<~gO=H$3ADu+EuO*y!8Vqg|4E0-&$GWLw7$||P zts?3k4hEHtyr2~clo3xtQs{W{_n3;kBnFww*Ew`NUjhWsNfVURuJMWq2tZ{5RPM*P UC;RS_p=X>a0a8)t!gJC61=xjfIRF3v literal 0 HcmV?d00001 diff --git a/SandboxProject/Assets/Rendering/01231740-f061-4ab2-be9f-9acdc35f2dfc.mcpostprocess.mcasset b/SandboxProject/Assets/Rendering/01231740-f061-4ab2-be9f-9acdc35f2dfc.mcpostprocess.mcasset new file mode 100644 index 0000000000000000000000000000000000000000..729d53cc6d054b3643a23cf34c1ecb66380973ef GIT binary patch literal 1519 zcmbtUy>1gh5WWyV;Wr3H>39Q<*iHxv9d>@OkYg*3DCpLEbIyTp_q4l~a*@*U4m3Og z5-lz81QgKGfoMtvuYv`(j#ni4jCoWce> z(GmGTi>M3I`BJv0%kSX?13&5U?96&HfaCf9P9==hgR52?L8V$-tln9wgmGoL8ZL=m zE$qQc9Igl%$;I1oEsEu>RA$<`%xLLb=LSi(z=?)7P*f5`_pC3)RV{}pC`X)8^WdGE zh%0+U{_jp}_a6HIVwc<{ga7Upw-nBfX2fCqbTId_`ugVE%cP(3*7OCq%N&A^T8ndg ztoyz&=dY6+o^kxfB%cTqzKACv4QFwJP5^y4(eRZo;KIpfSP22@A5od*Ri ZXwW%Ii(S~l1~ZB3FO)BjlSal(!9R{H_f!A? literal 0 HcmV?d00001 diff --git a/SandboxProject/Assets/Rendering/01231740-f061-4ab2-be9f-9acdc35f2dfc.mcpostprocess.mcmeta b/SandboxProject/Assets/Rendering/01231740-f061-4ab2-be9f-9acdc35f2dfc.mcpostprocess.mcmeta new file mode 100644 index 0000000..de539e3 --- /dev/null +++ b/SandboxProject/Assets/Rendering/01231740-f061-4ab2-be9f-9acdc35f2dfc.mcpostprocess.mcmeta @@ -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 +} \ No newline at end of file diff --git a/SandboxProject/Assets/Rendering/67c50c20-49e6-42e3-a602-a903da59f99f.mcenvironment b/SandboxProject/Assets/Rendering/67c50c20-49e6-42e3-a602-a903da59f99f.mcenvironment new file mode 100644 index 0000000000000000000000000000000000000000..1313eaec4473da3fd6763fedad1e5c5398f76234 GIT binary patch literal 406 zcmZQ%U|`?^Vs;<~fw0t~;>`R!76^}l5lC}F*^b44|0f{9U91sCG$qQu#XICcWR}iHrEVDQ>DJK==Xb@oJhtLc``6Y=ZKySOG zrWd8A7Q@^Mk`;q0@ytufEJ{r-@ySfjDDliINzE(HEUAR6{37lSGYsM$OeHt2xxqbL XQIKC;T9oRXpIZPl2y8dpD4>M^@&`!W literal 0 HcmV?d00001 diff --git a/SandboxProject/Assets/Rendering/67c50c20-49e6-42e3-a602-a903da59f99f.mcenvironment.mcasset b/SandboxProject/Assets/Rendering/67c50c20-49e6-42e3-a602-a903da59f99f.mcenvironment.mcasset new file mode 100644 index 0000000000000000000000000000000000000000..5d0a7e30b492444163487e3d94b67dc9933460c5 GIT binary patch literal 1519 zcmbtUOKTKC5T5wLcSJC)4g=}O2+>X^bhzG z{0*X4Pr;)X5xl4n5xj^8yLPHVBFmD|hNgSw`>LwzITHlIdB2bQjjJX4sXSKPgSB>} zb&l>U+GN5#0%KFHm@wnPGeqbJ`9$<#Js$$YM?G|!;ANGhLMgD8(Fww}Vz&#!ufay}J4O`kS+)pYqo91-Qi=g7#XAbGxtm zJ~QX9lN+9K{Kh072ot`DCm;_Zfx+g1Lm4p5jAtfQiUVc{dW8)DuCnT5uD^bexy1NI=Xl}D4J}rQju;S|a zn?c@n#mCS6^T}6Z{7z#$`mMCR;vMwy*l`YBZV{VuOVblFmpd16=kmN--Ev@apolY# zA;suLEVSwH5M%~v^X05knZYVJaBz?rno1;cjyL>V$b-Q=FW6t87b(Q}LCtJyHUFfd z?S$5#*XdS;7+(AF+axgrr4rel=?LLpUr&kpO$nV8W4r4~Y`Z)GBcoAvP0x3Kk49HJ zMMAr$k$aTJo?-31ejrT}qzQ1re<|PdV?%1#9PK-d;A|&MY%?khRM{kI%W0|Xp<;Ogc%2(MM9VHCu{&4c?JX@chDJxSENXolwb^@6NJ}Bi3m}^!+z7t znz;pY0l`-b&v)k*qMc_~Kfe9({qcRW(o-YN;T|)ar;vGb=jV&P+1=kHxdsw6vr3~Y z#Pg>7I$a#WLzcn@>kpxcKRn@>+b8k66UPQDi4VQ6Zjp1n$_ z%krUOq6gf%QZ`4+@2@EazTxA^nbl+n2lM|GOW0SBUzOqr`c=`dR~n75!gwvL)kGZL zW{o%$aihL;3#!!{_1g86XUf=2EACq73QD%YvPfwtDhb40<4SQ^@lguWB4^w@{!UFq zl|3f^cc*pwTP}dUOYWe-e|L*o%Fm9LpfG;;a_QyL>uYc3P(S9a`R!76^}l5lC}F*^b4R@E(=x0l2i}I6=$DwADg#v`0;OH^$})@c^Kw)3N)(dQ63tA_j7&j} z0s+sY9Pg5ffW(pv4u}Apc_Gzc*ALuiJe{F1~HptoI8 z(~DA5i(zgB$%;Xhc;=;K7NsVa_++MMlz8Tqq~;Z8mQ=!3UU74W83u6=rV<8ycesZu X3i69fi&CBQa|?h5f$fGH1+)+VkiAD# literal 0 HcmV?d00001 diff --git a/SandboxProject/Assets/Rendering/cfa65625-2d1b-40aa-a0ef-66683b9351db.mcenvironment.mcasset b/SandboxProject/Assets/Rendering/cfa65625-2d1b-40aa-a0ef-66683b9351db.mcenvironment.mcasset new file mode 100644 index 0000000000000000000000000000000000000000..df53edaa5efb7dca4a28b8bc670a8ae749cab225 GIT binary patch literal 1519 zcmbtU&1(}u6yN%V-<2xf`X9KqNzI~O)NIolBqk)qgSXk;mvn7*Cd^Ev>&cV&pGXh= z8;ao7i-H$V7QBcc2wprnFZ*6;C4{aH3^TjG-+OP~=bYzxm)$k{49l+J{G0@|cXGGWev7l_ai@s=3CMm_?DPdey4!3~+DLP{`((J8{~<23O}0Abwx zvU+a8Tp{pjxACFzDt!5R|K6K-?-#mkrKiR^hKIszo+aj|%FflT(fuzhxegMvuu79F z%tKSS&J~CESY)svq9N4j2NF)WeTKf<{k{Rq;zRV+Eq1P#z5vDs1#}RpW;RmVmQen2 zhH^H-X=rShRQ`E}@_CBNJbADRBajKmI4|+~ZxoYhq2bprkb!V)tjZ`gC2agWh7ES2 zBl4jVNe^P{O4%GQzrSY~_(hLrXV%go9M1m_Dq*Z1T-D<6;=TxKLAB;rlS<^@S{A|= z%h30OAh^8}-C3zslIUg@gM6N9B{PtAktADSMW{3sl?2HH<4SQ=#bYE9XVN@)=O*IH zo{;~$)7sH97eMTiyJYa+-Qt$Q+0jfmjQiiO?N;_~yuC>JDQ`_zfZNO==%BSYw}-m# zD|7xjx#1bdZ%p!$FyV`M0`edd7;GLmlmXpLu9JS8rnu^<6gp>|`Ne?h2)p^Hpm`yk XleCznE;g7>`R!76^}l5lC}F*^b481tJYj>G=$DwADg#v`0;OH^$})@c^Kw)3N)*zJ49wF~ERsQv z0s+sY9Pg5ffW(pv4u}Apc_Gzc*ALuiJe{F1~HptoI8 z(~DA5i(zgB$%;Xhc;=;K7NsVa_++MMlz8Tqq~;Z8mQ=!3DloXi41>4_Q;83wJKVz+ X1^LCLMXApDxdlLjz;?rp0$K+A zgG+SZR5}xO7qm&0WWtOCFA$+4q65)`oqPZcpLWoBf;VKG3Ms)DMkfew4b#{s0fce$ z%Nn@_vp`^P6wDqx2_8RNe*gN*tAz(_rK^TIg1f@(%@MQm`RVW7@cB!Y+yV(&Sf$Ao z=AkKE=ZeF-FEZE>VILaw0|}?xK1<*2Br#xFypO)R#m;rpN5ELGfDR+o$_7f?63X9A zQO-s<4I879%0Em|K2K4ZClA(P05S#{?f z5qV#UxC@bWrEE@?-(OP<{G`XTGn;82j^_V6l`vKhu4-|3Np literal 0 HcmV?d00001 diff --git a/SandboxProject/Assets/Rendering/f207fd8c-b32a-452f-846a-5891bef8e406.mcenvironment.mcmeta b/SandboxProject/Assets/Rendering/f207fd8c-b32a-452f-846a-5891bef8e406.mcenvironment.mcmeta new file mode 100644 index 0000000..f9df691 --- /dev/null +++ b/SandboxProject/Assets/Rendering/f207fd8c-b32a-452f-846a-5891bef8e406.mcenvironment.mcmeta @@ -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 +} \ No newline at end of file diff --git a/Source/MetaCoreDelivery/Private/MetaCoreBuildPipeline.cpp b/Source/MetaCoreDelivery/Private/MetaCoreBuildPipeline.cpp index 57d4a19..e7808cb 100644 --- a/Source/MetaCoreDelivery/Private/MetaCoreBuildPipeline.cpp +++ b/Source/MetaCoreDelivery/Private/MetaCoreBuildPipeline.cpp @@ -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 MetaCoreCollectCookablePhysicsMeshes( const std::filesystem::path& projectRoot, const MetaCoreTypeRegistry& registry, @@ -118,6 +121,79 @@ std::optional MetaCoreValidateScenePhysicsForCook( return std::nullopt; } +std::optional 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 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 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 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 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 environmentProfileGuids; std::unordered_set 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" diff --git a/Source/MetaCoreEditor/Private/MetaCoreBuiltinCoreServicesModule.cpp b/Source/MetaCoreEditor/Private/MetaCoreBuiltinCoreServicesModule.cpp index 2a95911..80cb326 100644 --- a/Source/MetaCoreEditor/Private/MetaCoreBuiltinCoreServicesModule.cpp +++ b/Source/MetaCoreEditor/Private/MetaCoreBuiltinCoreServicesModule.cpp @@ -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(); + const auto reflectionRegistry = editorContext.GetModuleRegistry().ResolveService(); + if (assetDatabase != nullptr && assetDatabase->HasProject() && reflectionRegistry != nullptr) { + struct ProfileChoice { MetaCoreAssetGuid Guid{}; std::string Name{}; }; + std::vector 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() ? &object.GetComponent().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() ? &object.GetComponent().PostProcessProfileGuid : nullptr; }, profile.Guid); + } + } + ImGui::EndCombo(); + } + ImGui::TextDisabled("优先级:相机 > 场景 > 引擎默认"); + } } void MetaCoreDrawLightComponentInspector(MetaCoreEditorContext& editorContext, MetaCoreGameObject& gameObject) { @@ -3685,7 +3724,29 @@ void MetaCoreDrawCharacterControllerComponentInspector(MetaCoreEditorContext& ed template void MetaCoreDrawRenderingComponentInspector(MetaCoreEditorContext& editorContext, MetaCoreGameObject& gameObject) { if(!gameObject.HasComponent())return;auto& value=gameObject.GetComponent();bool changed=ImGui::Checkbox("启用",&value.Enabled); - if constexpr(std::is_same_v){int shape=static_cast(value.Shape);if(ImGui::Combo("形状",&shape,"Box\0Sphere\0")){value.Shape=static_cast(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){ + int shape=static_cast(value.Shape);if(ImGui::Combo("形状",&shape,"Box\0Sphere\0")){value.Shape=static_cast(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 sourcePath{};MetaCoreCopyStringToBuffer(value.SourceEnvironmentPath,sourcePath.data(),sourcePath.size()); + if(ImGui::InputText("源环境(项目相对路径)",sourcePath.data(),sourcePath.size())){value.SourceEnvironmentPath=sourcePath.data();changed=true;} + const std::array resolutions{16U,32U,64U,128U,256U,512U,1024U,2048U}; + int resolutionIndex=0;for(std::size_t index=0;index(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(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(); + 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){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(i));changed|=ImGui::DragFloat("屏幕占比",&value.Levels[i].ScreenHeight,0.005F,0.0F,1.0F);ImGui::PopID();}} else if constexpr(std::is_same_v){changed|=ImGui::DragFloat("发射率",&value.EmissionRate,0.5F,0.0F);int maximum=static_cast(value.MaxParticles);if(ImGui::DragInt("最大粒子数",&maximum,10,1,1000000)){value.MaxParticles=static_cast(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){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());} diff --git a/Source/MetaCoreEditor/Private/MetaCoreBuiltinEditorModule.cpp b/Source/MetaCoreEditor/Private/MetaCoreBuiltinEditorModule.cpp index e54cee9..1aa8ee9 100644 --- a/Source/MetaCoreEditor/Private/MetaCoreBuiltinEditorModule.cpp +++ b/Source/MetaCoreEditor/Private/MetaCoreBuiltinEditorModule.cpp @@ -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> environmentProfiles; + std::vector> 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(value.ExposureMode);if(ImGui::Combo("曝光模式",&exposure,"手动\0自动\0")){value.ExposureMode=static_cast(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(value.ToneMapping);if(ImGui::Combo("Tone Mapping",&tone,"ACES\0Filmic\0Linear\0")){value.ToneMapping=static_cast(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(value.AntiAliasing);if(ImGui::Combo("Profile AA",&aa,"None\0FXAA\0TAA\0")){value.AntiAliasing=static_cast(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(); + 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(Settings_.Quality); + if (ImGui::Combo("质量档", &quality, "Low\0Medium\0High\0Ultra\0")) { Settings_.Quality = static_cast(quality); Dirty_ = true; } + if (ImGui::CollapsingHeader("阴影", ImGuiTreeNodeFlags_DefaultOpen)) { + int cascades = static_cast(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(i+1U)/static_cast(Settings_.DirectionalCascadeCount); Dirty_=true; } + Dirty_ |= ImGui::DragFloat("最大阴影距离", &Settings_.DirectionalShadowDistance, 1.0F, 1.0F, 10000.0F); + for (std::size_t index=0; index(index)); Dirty_ |= ImGui::SliderFloat("级联分割", &Settings_.DirectionalCascadeSplits[index], 0.001F, 1.0F); ImGui::PopID(); } + int filter = static_cast(Settings_.ShadowFilter); if (ImGui::Combo("阴影过滤", &filter, "Hard/PCF\0DPCF\0PCSS\0")) { Settings_.ShadowFilter=static_cast(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<(Settings_.MaxShadowCastingPointSpotLights);if(ImGui::SliderInt("Point/Spot 阴影预算",&localBudget,0,64)){Settings_.MaxShadowCastingPointSpotLights=static_cast(localBudget);Dirty_=true;} + } + if (ImGui::CollapsingHeader("抗锯齿与可见性", ImGuiTreeNodeFlags_DefaultOpen)) { + int aa=static_cast(Settings_.AntiAliasing);if(ImGui::Combo("抗锯齿",&aa,"None\0FXAA\0TAA\0")){Settings_.AntiAliasing=static_cast(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<(Settings_.OcclusionGraceFrames);if(ImGui::SliderInt("遮挡宽限帧",&grace,0,8)){Settings_.OcclusionGraceFrames=static_cast(grace);Dirty_=true;} + } + if (ImGui::CollapsingHeader("表现组件预算", ImGuiTreeNodeFlags_DefaultOpen)) { + int particles=static_cast(Settings_.GlobalParticleBudget);if(ImGui::DragInt("全局粒子数",&particles,1000,1,1000000)){Settings_.GlobalParticleBudget=static_cast(particles);Dirty_=true;} + int linePoints=static_cast(Settings_.DynamicLinePointBudget);if(ImGui::DragInt("动态线点数",&linePoints,1024,1,4000000)){Settings_.DynamicLinePointBudget=static_cast(linePoints);Dirty_=true;} + } + if (ImGui::CollapsingHeader("调试视图", ImGuiTreeNodeFlags_DefaultOpen)) { + int debugView=static_cast(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(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(statistics.VisibleTriangleCount), static_cast(statistics.EstimatedVideoMemoryBytes)/(1024.0*1024.0)); + ImGui::TextDisabled("三角形与显存需资产元数据后才会显示非零值。"); + } + if (ImGui::Button("保存")) { + std::vector 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()); moduleRegistry.RegisterPanelProvider(std::make_unique()); moduleRegistry.RegisterPanelProvider(std::make_unique()); + moduleRegistry.RegisterPanelProvider(std::make_unique()); moduleRegistry.RegisterPanelProvider(std::make_unique()); moduleRegistry.RegisterService(std::make_shared()); // The central Scene / Game viewport is not a dockable provider, but it keeps diff --git a/Source/MetaCoreRender/Private/Materials/Templates/decal.mat b/Source/MetaCoreRender/Private/Materials/Templates/decal.mat new file mode 100644 index 0000000..30c2a33 --- /dev/null +++ b/Source/MetaCoreRender/Private/Materials/Templates/decal.mat @@ -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); } } diff --git a/Source/MetaCoreRender/Private/Materials/Templates/line.mat b/Source/MetaCoreRender/Private/Materials/Templates/line.mat new file mode 100644 index 0000000..c98d172 --- /dev/null +++ b/Source/MetaCoreRender/Private/Materials/Templates/line.mat @@ -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; } } diff --git a/Source/MetaCoreRender/Private/Materials/Templates/particle.mat b/Source/MetaCoreRender/Private/Materials/Templates/particle.mat new file mode 100644 index 0000000..faaff39 --- /dev/null +++ b/Source/MetaCoreRender/Private/Materials/Templates/particle.mat @@ -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; } } diff --git a/Source/MetaCoreRender/Private/Materials/Templates/pbr.mat b/Source/MetaCoreRender/Private/Materials/Templates/pbr.mat new file mode 100644 index 0000000..0e4486b --- /dev/null +++ b/Source/MetaCoreRender/Private/Materials/Templates/pbr.mat @@ -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; } } diff --git a/Source/MetaCoreRender/Private/Materials/Templates/terrain.mat b/Source/MetaCoreRender/Private/Materials/Templates/terrain.mat new file mode 100644 index 0000000..d775237 --- /dev/null +++ b/Source/MetaCoreRender/Private/Materials/Templates/terrain.mat @@ -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; } } diff --git a/Source/MetaCoreRender/Private/Materials/Templates/transparent.mat b/Source/MetaCoreRender/Private/Materials/Templates/transparent.mat new file mode 100644 index 0000000..3eef7fd --- /dev/null +++ b/Source/MetaCoreRender/Private/Materials/Templates/transparent.mat @@ -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); } } diff --git a/Source/MetaCoreRender/Private/Materials/Templates/unlit.mat b/Source/MetaCoreRender/Private/Materials/Templates/unlit.mat new file mode 100644 index 0000000..08cecf1 --- /dev/null +++ b/Source/MetaCoreRender/Private/Materials/Templates/unlit.mat @@ -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; } } diff --git a/Source/MetaCoreRender/Private/Materials/render_debug.mat b/Source/MetaCoreRender/Private/Materials/render_debug.mat new file mode 100644 index 0000000..907fa44 --- /dev/null +++ b/Source/MetaCoreRender/Private/Materials/render_debug.mat @@ -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; + } +} diff --git a/Source/MetaCoreRender/Private/MetaCoreEditorViewportRenderer.cpp b/Source/MetaCoreRender/Private/MetaCoreEditorViewportRenderer.cpp index 76cc4ff..53a274e 100644 --- a/Source/MetaCoreRender/Private/MetaCoreEditorViewportRenderer.cpp +++ b/Source/MetaCoreRender/Private/MetaCoreEditorViewportRenderer.cpp @@ -115,7 +115,9 @@ void MetaCoreEditorViewportRenderer::RenderSnapshotToViewport( RenderDevice_->SetSceneViewportRect(ViewportRect_); FilamentSceneBridge_.Resize(static_cast(ViewportRect_.Width), static_cast(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); } diff --git a/Source/MetaCoreRender/Private/MetaCoreFilamentSceneBridge.cpp b/Source/MetaCoreRender/Private/MetaCoreFilamentSceneBridge.cpp index c398d76..b55818a 100644 --- a/Source/MetaCoreRender/Private/MetaCoreFilamentSceneBridge.cpp +++ b/Source/MetaCoreRender/Private/MetaCoreFilamentSceneBridge.cpp @@ -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 @@ -61,6 +62,7 @@ #include #include #include +#include #include #include #include @@ -77,6 +79,7 @@ #include #include #include +#include // 引入平台和 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 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::hash{}(path.generic_string()))); + combine(static_cast(std::filesystem::file_size(path, error))); + error.clear(); + combine(static_cast(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(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(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(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(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(viewport.width) / static_cast(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 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 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 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 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 softwareDepth(static_cast(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::clamp(bounds.x,0.0F,1.0F)*hzbWidth)),maxX=std::min(hzbWidth-1U,static_cast(std::clamp(bounds.z,0.0F,1.0F)*hzbWidth));const std::uint32_t minY=std::min(hzbHeight-1U,static_cast(std::clamp(bounds.y,0.0F,1.0F)*hzbHeight)),maxY=std::min(hzbHeight-1U,static_cast(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(y)*hzbWidth+x]=std::min(softwareDepth[static_cast(y)*hzbWidth+x],farthestDepth);} + (void)PreviousHzb_.Build(hzbWidth,hzbHeight,softwareDepth);LastFrameVisibleObjects_=std::move(currentVisible);PreviousVisibilityCameraPosition_=CurrentSceneView_.CameraPosition;PreviousVisibilityCameraDirection_=cameraDirection;HasPreviousVisibilityCamera_=true; + std::vector instanceCandidates; + instanceCandidates.reserve(snapshot.Renderables.size()); + for (const auto& renderable : snapshot.Renderables) { + std::uint64_t stateHash = static_cast(renderable.MeshSource) | (static_cast(renderable.BuiltinMesh) << 8U) | (static_cast(renderable.AlphaMode) << 16U) | (static_cast(renderable.DoubleSided) << 24U); + std::uint64_t parameterHash = std::hash{}(renderable.Metallic) ^ (std::hash{}(renderable.Roughness) << 1U) ^ (std::hash{}(renderable.BaseColor.x) << 2U) ^ (std::hash{}(renderable.BaseColor.y) << 3U) ^ (std::hash{}(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(instanceBatches.size()); + RenderingStatistics_.DrawCallCount = static_cast(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 activeHostRootIds; for (const auto& renderable : snapshot.Renderables) { @@ -2385,7 +2557,9 @@ public: } } + ApplyRenderDebugView(snapshot, renderableMap); ApplyAnimationStates(snapshot); + RenderingStatistics_.CpuFrameMilliseconds = std::chrono::duration(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(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 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 bytes((std::istreambuf_iterator(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.Primitivedestroy(value.Debug);} + DebugMaterialOverrides_.clear(); + } + + void ApplyRenderDebugView(const MetaCoreSceneRenderSyncSnapshot& snapshot, + const std::unordered_map& renderables) { + RestoreDebugMaterials();if(ActiveDebugView_==MetaCoreRenderDebugView::Lit||Engine_==nullptr)return;const filament::Material* material=EnsureRenderDebugMaterial();if(material==nullptr)return; + std::vector 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 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::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;primitivecreateInstance();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(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(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(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 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 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 values(pixels,pixels+static_cast(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(width),static_cast(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(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 active;std::uint32_t particleRemaining=RenderSettings_.GlobalParticleBudget; + std::vector 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->ObjectIdObjectId;}); + 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(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 points;const auto count=std::min(value.Component.Points.size(),lineRemaining);points.reserve(count);for(std::size_t index=0;index(count);UpdateProductionRenderable(key,MetaCoreBuildLineRibbon(points,value.Component.Width,{value.Component.Color,1},{value.Component.Color,1},cameraForward,static_cast(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 points;for(const auto& point:trail.GetPoints())points.push_back(point.Position);const auto allowed=std::min(lineRemaining,static_cast(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 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 candidatePaths{ + const std::array 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 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 EnvironmentIblKtxBytes_{}; @@ -3743,6 +4093,15 @@ private: MetaCoreSceneRenderSync RenderSync_{}; MetaCoreSceneRenderSyncSnapshot LastSyncSnapshot_{}; MetaCoreSceneView CurrentSceneView_{}; + std::unordered_map LodSelections_{}; + MetaCoreStaticVisibilityBvh VisibilityBvh_{}; + MetaCoreConservativeHzb PreviousHzb_{}; + MetaCoreOcclusionHistory OcclusionHistory_{}; + std::unordered_set 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 EditorGridMaterialInstances_{}; + filament::Material* ProductionMaterial_ = nullptr; + filament::Material* RenderDebugMaterial_ = nullptr; + std::vector DebugMaterialOverrides_{}; + MetaCoreRenderDebugView ActiveDebugView_ = MetaCoreRenderDebugView::Lit; + std::unordered_map ProductionRenderables_{}; + std::unordered_map ParticleSimulations_{}; + std::unordered_map ParticleSimulationSeeds_{}; + std::unordered_map 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); } diff --git a/Source/MetaCoreRender/Private/MetaCoreSceneRenderSync.cpp b/Source/MetaCoreRender/Private/MetaCoreSceneRenderSync.cpp index 7b3fb48..129b905 100644 --- a/Source/MetaCoreRender/Private/MetaCoreSceneRenderSync.cpp +++ b/Source/MetaCoreRender/Private/MetaCoreSceneRenderSync.cpp @@ -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()) { diff --git a/Source/MetaCoreRender/Public/MetaCoreRender/MetaCoreEditorViewportRenderer.h b/Source/MetaCoreRender/Public/MetaCoreRender/MetaCoreEditorViewportRenderer.h index 2a86263..6be0cb4 100644 --- a/Source/MetaCoreRender/Public/MetaCoreRender/MetaCoreEditorViewportRenderer.h +++ b/Source/MetaCoreRender/Public/MetaCoreRender/MetaCoreEditorViewportRenderer.h @@ -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; diff --git a/Source/MetaCoreRender/Public/MetaCoreRender/MetaCoreFilamentSceneBridge.h b/Source/MetaCoreRender/Public/MetaCoreRender/MetaCoreFilamentSceneBridge.h index b7c1074..c59806e 100644 --- a/Source/MetaCoreRender/Public/MetaCoreRender/MetaCoreFilamentSceneBridge.h +++ b/Source/MetaCoreRender/Public/MetaCoreRender/MetaCoreFilamentSceneBridge.h @@ -1,6 +1,7 @@ #pragma once #include "MetaCoreFoundation/MetaCoreId.h" +#include "MetaCoreRendering/MetaCoreRendering.h" #include #include @@ -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; diff --git a/Source/MetaCoreRender/Public/MetaCoreRender/MetaCoreSceneRenderSync.h b/Source/MetaCoreRender/Public/MetaCoreRender/MetaCoreSceneRenderSync.h index b838720..c33a60e 100644 --- a/Source/MetaCoreRender/Public/MetaCoreRender/MetaCoreSceneRenderSync.h +++ b/Source/MetaCoreRender/Public/MetaCoreRender/MetaCoreSceneRenderSync.h @@ -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 { diff --git a/Source/MetaCoreRendering/Private/MetaCoreRendering.cpp b/Source/MetaCoreRendering/Private/MetaCoreRendering.cpp index 516ce9d..e29579f 100644 --- a/Source/MetaCoreRendering/Private/MetaCoreRendering.cpp +++ b/Source/MetaCoreRendering/Private/MetaCoreRendering.cpp @@ -1,12 +1,20 @@ #include "MetaCoreRendering/MetaCoreRendering.h" #include "MetaCoreFoundation/MetaCoreArchive.h" +#include "MetaCoreFoundation/MetaCoreHash.h" #include #include #include +#include +#include +#include +#include #include +#include +#include + namespace MetaCore { namespace { template @@ -101,6 +109,34 @@ std::vector MetaCoreAllocateShadowBudget(const std::vector MetaCoreSelectReflectionProbes(const std::vector& candidates,const glm::vec3& worldPosition){ + struct Match{const MetaCoreReflectionProbeCandidate* Candidate=nullptr;float Weight=0.0F;float Distance=0.0F;};std::vector 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.DistanceObjectIdObjectId;}); + 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(matches.size());std::vector 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 MetaCoreBuildInstanceBatches(const std::vector& candidates,bool instancingEnabled){ + struct Key{MetaCoreAssetGuid Mesh{};std::vector 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{}(key.Parameters));combine(std::hash{}(key.State));combine(std::hash{}(key.Object));return hash;}}; + std::unordered_map indices;indices.reserve(candidates.size());std::vector 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(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(EmissionAccumulator_);EmissionAccumulator_-=static_cast(requested);if(!BurstEmitted_){requested+=component.BurstCount;BurstEmitted_=true;}}const std::uint32_t capacity=component.MaxParticles>Particles_.size()?component.MaxParticles-static_cast(Particles_.size()):0U;const std::uint32_t allocated=MetaCoreAllocateParticleBudget(requested,capacity,globalRemaining);for(std::uint32_t index=0;index0.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* 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 migrated;std::unordered_set 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&¶meter.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& items, std::uint32_t first, std::uint32_t count) { + MetaCoreRenderBounds result{glm::vec3(std::numeric_limits::max()), glm::vec3(std::numeric_limits::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 items) { + Items_ = std::move(items); + Nodes_.clear(); + if (!Items_.empty()) (void)BuildNode(0U, static_cast(Items_.size())); +} + +std::int32_t MetaCoreStaticVisibilityBvh::BuildNode(std::uint32_t first, std::uint32_t count) { + const std::int32_t nodeIndex = static_cast(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(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(nodeIndex)].Left = left; + Nodes_[static_cast(nodeIndex)].Right = right; + return nodeIndex; +} + +std::vector MetaCoreStaticVisibilityBvh::Query( + const MetaCoreViewFrustum& frustum, const glm::vec3& cameraPosition, + std::uint32_t cameraMask, MetaCoreVisibilityStatistics* statistics) const { + std::vector visible; + if (Nodes_.empty()) return visible; + std::vector stack{0}; + while (!stack.empty()) { + const Node& node = Nodes_[static_cast(stack.back())]; + stack.pop_back(); + if (!MetaCoreIntersectsFrustum(frustum, node.Bounds)) { + if (statistics) for (std::uint32_t index=0; index= 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 raw{r3+r0, r3-r0, r3+r1, r3-r1, r3+r2, r3-r2}; + MetaCoreViewFrustum result; + for (std::size_t index=0; index linearDepth) { + Reset(); + if (width==0U || height==0U || linearDepth.size()!=static_cast(width)*height) return false; + Mips_.push_back({width,height,std::vector(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(static_cast(width)*height,0.0F)}; + for(std::uint32_t y=0;y(sy)*source.Width+sx]); + } + target.Depth[static_cast(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(Mips_[0].Width); + const float pixelHeight=(bounds.w-bounds.y)*static_cast(Mips_[0].Height); + const std::uint32_t mip=std::min(static_cast(Mips_.size()-1U), + static_cast(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(bounds.x*level.Width)); + const std::uint32_t maxX=std::min(level.Width-1U,static_cast(bounds.z*level.Width)); + const std::uint32_t minY=std::min(level.Height-1U,static_cast(bounds.y*level.Height)); + const std::uint32_t maxY=std::min(level.Height-1U,static_cast(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(y)*level.Width+x]); + return nearestLinearDepth<=farthest+0.001F; +} + +void MetaCoreConservativeHzb::Reset(){Mips_.clear();} + +MetaCoreDynamicGeometry MetaCoreBuildParticleBillboards( + std::span 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(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 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(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(index)/static_cast(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 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(width)*height)return geometry;sampleStep=std::max(1U,sampleStep); + std::vector xs,ys;for(std::uint32_t x=0;x(x)/static_cast(width-1U),v=static_cast(y)/static_cast(height-1U),h=static_cast(heights[static_cast(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(xs.size())+x,b=a+1U,c=a+static_cast(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(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 "< 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(value);return index MetaCoreParseRenderDebugView(std::string_view value){for(int index=0;index<=static_cast(MetaCoreRenderDebugView::ReflectionProbe);++index){const auto mode=static_cast(index);if(MetaCoreRenderDebugViewName(mode)==value)return mode;}return std::nullopt;} + } // namespace MetaCore diff --git a/Source/MetaCoreRendering/Public/MetaCoreRendering/MetaCoreRendering.h b/Source/MetaCoreRendering/Public/MetaCoreRendering/MetaCoreRendering.h index 4da4340..0b588bb 100644 --- a/Source/MetaCoreRendering/Public/MetaCoreRendering/MetaCoreRendering.h +++ b/Source/MetaCoreRendering/Public/MetaCoreRendering/MetaCoreRendering.h @@ -5,12 +5,18 @@ #include "MetaCoreFoundation/MetaCoreReflection.h" #include +#include +#include +#include +#include #include #include #include +#include #include #include +#include #include 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 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 items); + [[nodiscard]] std::vector 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 Items_{}; + std::vector 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 linearDepth); + [[nodiscard]] bool IsVisible(const glm::vec4& normalizedScreenBounds, float nearestLinearDepth) const; + [[nodiscard]] std::uint32_t GetMipCount() const { return static_cast(Mips_.size()); } + void Reset(); +private: + struct Mip { std::uint32_t Width=0U, Height=0U; std::vector Depth{}; }; + std::vector Mips_{}; +}; + +struct MetaCoreDynamicVertex { glm::vec3 Position{}; glm::vec4 Color{1.0F}; glm::vec2 Uv{}; }; +struct MetaCoreDynamicGeometry { std::vector Vertices{}; std::vector Indices{}; }; +struct MetaCoreParticleState; +[[nodiscard]] MetaCoreDynamicGeometry MetaCoreBuildParticleBillboards( + std::span particles, const glm::vec3& cameraRight, const glm::vec3& cameraUp, + const glm::mat4& emitterWorldMatrix); +[[nodiscard]] MetaCoreDynamicGeometry MetaCoreBuildLineRibbon( + std::span points, float width, const glm::vec4& startColor, const glm::vec4& endColor, + const glm::vec3& cameraForward, std::uint32_t pointBudget); +[[nodiscard]] MetaCoreDynamicGeometry MetaCoreBuildTerrainGeometry( + std::span 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& GetPoints() const { return Points_; } + void Reset() { Points_.clear(); } +private: + std::vector 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 MetaCoreReadReflectionProbeDerivedHeader( + const std::filesystem::path& path); + +struct MetaCoreInstanceCandidate { + MetaCoreId ObjectId = 0; + MetaCoreAssetGuid MeshAssetGuid{}; + std::vector MaterialAssetGuids{}; + std::uint64_t ParameterHash = 0U; + std::uint64_t RenderStateHash = 0U; + bool Visible = true; +}; + +struct MetaCoreInstanceBatch { + MetaCoreAssetGuid MeshAssetGuid{}; + std::vector MaterialAssetGuids{}; + std::uint64_t ParameterHash = 0U; + std::uint64_t RenderStateHash = 0U; + std::vector 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 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& GetParticles() const { return Particles_; } + [[nodiscard]] std::uint32_t GetDroppedParticleCount() const { return DroppedParticleCount_; } +private: + [[nodiscard]] float NextRandom(); + void Spawn(const MetaCoreParticleEmitterComponent& component); + std::vector 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 MetaCoreAllocateShadowBudget(const std::vector& candidates, const MetaCoreRenderSettingsDocument& settings); [[nodiscard]] std::uint32_t MetaCoreAllocateParticleBudget(std::uint32_t requested, std::uint32_t emitterMaximum, std::uint32_t globalRemaining); +[[nodiscard]] std::vector MetaCoreSelectReflectionProbes( + const std::vector& candidates, const glm::vec3& worldPosition); +[[nodiscard]] std::vector MetaCoreBuildInstanceBatches( + const std::vector& 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 MetaCoreParseRenderDebugView(std::string_view value); } // namespace MetaCore diff --git a/Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreComponents.h b/Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreComponents.h index fb18157..8308ac5 100644 --- a/Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreComponents.h +++ b/Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreComponents.h @@ -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() diff --git a/docs/designs/metacore-mature-engine-gap-and-work-plan.md b/docs/designs/metacore-mature-engine-gap-and-work-plan.md index 9e94704..56492e7 100644 --- a/docs/designs/metacore-mature-engine-gap-and-work-plan.md +++ b/docs/designs/metacore-mature-engine-gap-and-work-plan.md @@ -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.1–R7.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 构建会自动 stage,Cook 生成包含模板版本、源码 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 新增源环境、16–2048 烘焙尺寸、派生 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/`。 +- Render Bridge 已接入静态 BVH,依次执行 Layer/距离/视锥、LOD、实例批次和保守遮挡;遮挡使用上一帧 64×36 深度层级、相机突变/动态/新出现对象宽限。统计现在包含提交/可见、各剔除原因、LOD、实例批次、Draw Call、可见三角形、显存估算及 CPU/GPU 时间。实例批次键改为无字符串分配的 GUID Hash,100k CPU 基准 p95 从 26.00 ms 降至 2.66 ms。 +- Filament 执行后端新增可复用动态 Vertex/Index Buffer 池:CPU 粒子转 Billboard,Line/Trail 转相机朝向 Ribbon,Decal 首版提交 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 和 Terrain;Smoke 回归会强制执行 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 `,正式 Release Player 确定性拒绝该参数并输出诊断。 +- Editor 新增“渲染设置”面板,可编辑 `Runtime/Rendering.mcruntime` 的质量、级联阴影、Atlas/本地光阴影预算、AA/MSAA、实例化、LOD、遮挡宽限、粒子及动态线预算。Scene 面板可创建/选择并直接编辑 Environment/Post-process Profile;Camera 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 Tools;CMake 会优先探测这组工具,也允许通过 `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 和表现组件在对象 Inspector;Reflection Probe 的源环境、尺寸及烘焙按钮在该组件 Inspector。 - 当前没有生成 Release/Development 交付包;本次“build”产物是上述 CMake 构建目录中的开发验证二进制。问题 7 只有在剩余执行后端、GPU 回归和工业场景性能门禁完成后才能标记关闭。 ### 8. 动画系统 diff --git a/tests/MetaCoreRenderBenchmark.cpp b/tests/MetaCoreRenderBenchmark.cpp new file mode 100644 index 0000000..49152e7 --- /dev/null +++ b/tests/MetaCoreRenderBenchmark.cpp @@ -0,0 +1,47 @@ +#include "MetaCoreRendering/MetaCoreRendering.h" + +#include +#include +#include +#include + +int main() { + constexpr std::size_t kInstanceCount = 100000U; + constexpr std::size_t kFrameCount = 120U; + std::vector items; + items.reserve(kInstanceCount); + for (std::size_t index = 0; index < kInstanceCount; ++index) { + const float x = static_cast(index % 500U) * 0.015F - 3.75F; + const float y = static_cast((index / 500U) % 200U) * 0.015F - 1.5F; + const float z = static_cast(index % 13U) * 0.002F; + const glm::vec3 center{x, y, z}; + items.push_back({static_cast(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 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 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(end - begin).count()); + } + std::sort(frameTimes.begin(), frameTimes.end()); + const double p95 = frameTimes[static_cast(0.95 * static_cast(frameTimes.size() - 1U))]; + const double buildMs = std::chrono::duration(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; +} diff --git a/tests/MetaCoreRenderTests.cpp b/tests/MetaCoreRenderTests.cpp index ecf6203..703576c 100644 --- a/tests/MetaCoreRenderTests.cpp +++ b/tests/MetaCoreRenderTests.cpp @@ -8,6 +8,8 @@ #include #include +#include + 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 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 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 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 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().Priority = 7; + auto& reflectionProbe=object.AddComponent();reflectionProbe.Priority=7;reflectionProbe.SourceEnvironmentPath="Assets/Environments/factory.hdr";reflectionProbe.BakeResolution=512U; object.AddComponent().Levels.push_back({0.5F, {}, {}}); object.AddComponent().MaxParticles = 321U; object.AddComponent().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(), "reflection probe snapshot"); + Expect(restoredObject.GetComponent().BakeResolution==512U&&restoredObject.GetComponent().SourceEnvironmentPath=="Assets/Environments/factory.hdr","reflection probe bake settings snapshot"); Expect(restoredObject.HasComponent() && restoredObject.HasComponent(), "lod and particles snapshot"); Expect(restoredObject.HasComponent() && restoredObject.HasComponent(), "line and trail snapshot"); Expect(restoredObject.HasComponent() && restoredObject.HasComponent(), "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; } diff --git a/tests/MetaCoreSmokeTests.cpp b/tests/MetaCoreSmokeTests.cpp index fc821a6..73e0319 100644 --- a/tests/MetaCoreSmokeTests.cpp +++ b/tests/MetaCoreSmokeTests.cpp @@ -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(); diff --git a/tools/bake_reflection_probe.sh b/tools/bake_reflection_probe.sh new file mode 100755 index 0000000..d831173 --- /dev/null +++ b/tools/bake_reflection_probe.sh @@ -0,0 +1,65 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ $# -lt 3 || $# -gt 4 ]]; then + echo "Usage: $0 [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}" diff --git a/tools/build_filament_host_tools.sh b/tools/build_filament_host_tools.sh new file mode 100755 index 0000000..cb6e77b --- /dev/null +++ b/tools/build_filament_host_tools.sh @@ -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}"