{"id":478,"date":"2026-05-26T23:16:47","date_gmt":"2026-05-26T15:16:47","guid":{"rendered":"http:\/\/chenglixue.top\/?p=478"},"modified":"2026-05-26T23:21:38","modified_gmt":"2026-05-26T15:21:38","slug":"478","status":"publish","type":"post","link":"http:\/\/chenglixue.top\/?p=478","title":{"rendered":"UE5 Culling\u5206\u6790"},"content":{"rendered":"<p><div class=\"has-toc have-toc\"><\/div><\/p>\n<h1>UE5 \u5305\u542b\u54ea\u4e9bCulling<\/h1>\n<p>UE5\u7684culling\u540c\u65f6\u5305\u542bCPU\u7aef\u3001GPU\u7aefculling<\/p>\n<p>CPU\u7aef\u4f20\u7edf\u975e Nanite \u7269\u4f53\u7684\u6536\u96c6\u548c\u5254\u9664\u6838\u5fc3\u6536\u5f55\u5728<code>SceneVisibility.cpp<\/code>\uff0c\u6709\u5982\u4e0bCulling\uff1a<\/p>\n<ul>\n<li>Distance Culling<\/li>\n<li>Frustum Culling<\/li>\n<li>Pre Computed Visibility<\/li>\n<li>Occlusion Culling<\/li>\n<\/ul>\n<p>GPU\u7aefNanite\u7ba1\u7ebf\u5254\u9664\u90e8\u5206\u6536\u5f55\u5728<code>NaniteCullRaster.cpp<\/code>\uff0c\u6709\u5982\u4e0bCulling\uff1a<\/p>\n<ul>\n<li>Nanite Culling<\/li>\n<li>HIZ Culling<\/li>\n<\/ul>\n<h1>Culling\u524d\u7684\u51c6\u5907<\/h1>\n<h2>\u4e3b\u8981\u5de5\u4f5c<\/h2>\n<p>\u5728Culling\u6b63\u5f0f\u5f00\u59cb\u524d\uff0cSceneRenderer\u4f1a\u5148\u6267\u884cInitViews()\uff0c\u4e3b\u8981\u4e3aCulling\u505a\u4ee5\u4e0b\u5de5\u4f5c\uff1a<\/p>\n<ul>\n<li>View &amp; Frustum Setup\n<ul>\n<li>\u8ba1\u7b97\u5fc5\u8981\u77e9\u9635<\/li>\n<li>\u63d0\u53d6\u89c6\u9525\u4f53\u5e73\u9762<\/li>\n<li>\u5199\u5165 View Constant Buffer<\/li>\n<\/ul>\n<\/li>\n<li>Primitive \u89c6\u56fe\u76f8\u5173\u6027\u8bc4\u4f30\n<ul>\n<li>Flags \u8fc7\u6ee4<\/li>\n<li><strong>\u586b\u5145 <code>FPrimitiveViewRelevance<\/code>\uff1a<\/strong> \u904d\u5386\u573a\u666f\u4e2d\u7684 <code>FPrimitiveSceneProxy<\/code>\uff0c\u5feb\u901f\u8bc4\u4f30\u5176\u6750\u8d28\u5c5e\u6027\u3002\u5982\uff1a\u8be5\u7269\u4f53\u662f\u5426\u5305\u542b\u4e0d\u900f\u660e\u6750\u8d28\u3001\u662f\u5426\u6709\u534a\u900f\u660e\u6750\u8d28\u3001\u662f\u5426\u6295\u5c04\u9634\u5f71\u3001\u662f\u5426\u9700\u8981\u5199\u5165 Custom Depth\u3001\u662f\u5426\u662f Editor-Only \u7684\u8f85\u52a9\u7ebf\u3002\u8fd9\u4e3a\u540e\u7eed\u628a\u5b83\u4eec\u5206\u53d1\u5230\u4e0d\u540c\u7684Render Buckets\u505a\u597d\u4e86\u51c6\u5907<\/li>\n<\/ul>\n<\/li>\n<li>\u52a8\u6001\u6570\u636e\u6536\u96c6\u4e0e\u5305\u56f4\u76d2\u66f4\u65b0<\/li>\n<li>\u89e3\u51b3\u65f6\u5e8f\u4f9d\u8d56\n<ul>\n<li>\u51c6\u5907HZB\uff1a\u63d0\u53d6\u4e0a\u4e00\u5e27\u672b\u5c3e\u7531GPU\u8ba1\u7b97\u7684HIZ<\/li>\n<li>\u56de\u8bfb\u786c\u4ef6\u67e5\u8be2\uff1a\u5982\u679c\u7ba1\u7ebf\u914d\u7f6e\u4e3a\u4f7f\u7528\u4f20\u7edf\u7684Hardware Occlusion Queries\uff0c\u8fd9\u91cc\u4f1a\u56de\u6536\u524d\u51e0\u5e27 GPU \u4f20\u56de\u7684\u53ef\u89c1\u6027\u50cf\u7d20\u8ba1\u6570<\/li>\n<\/ul>\n<\/li>\n<li>\u5e76\u53d1\u642d\u5efa\n<ul>\n<li>\u9884\u5206\u914d\u5185\u5b58\u6808\uff1a\u5728 Culling \u8fc7\u7a0b\u4e2d\u4f1a\u4ea7\u751f\u6d77\u91cf\u7684\u4e34\u65f6\u6570\u7ec4<\/li>\n<li>\u5207\u5206\u4efb\u52a1\uff1a\u6839\u636e\u573a\u666f\u516b\u53c9\u6811\u7684\u8282\u70b9\u6570\u91cf\uff0c\u5c06\u5254\u9664\u4efb\u52a1\u5207\u5206\u6210\u591a\u4e2a\u5c0f\u5757\uff0c\u4e22\u7ed9 Task Graph\uff0c\u5524\u9192\u591a\u6838 CPU \u7684 Worker \u7ebf\u7a0b\uff0c\u51c6\u5907\u8fdb\u884c\u5e76\u53d1\u8ba1\u7b97<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>\u603b\u4f53\u6765\u8bf4\uff0cInitViews\uff08\uff09\u89e3\u51b3\u4e86\u201c<strong>\u7528\u4ec0\u4e48\u89c6\u89d2\u770b\u3001\u6392\u9664\u54ea\u4e9b\u4e0d\u770b\u7684\u3001\u8ba1\u7b97\u52a8\u6001\u7269\u4f53\u5728\u5f53\u524d\u5e27\u7684\u6570\u636e\u3001\u62ff\u51fa\u4e0a\u4e00\u5e27\u7684\u906e\u6321\u53c2\u8003\u3001\u5206\u53d1\u591a\u7ebf\u7a0b\u5185\u5b58\u4e0e\u4efb\u52a1<\/strong>\u201d<\/p>\n<p>FDeferredShadingSceneRenderer\u4e0d\u50cfFMobileSceneRenderer\u90a3\u6837\uff0c\u5b83\u5c06InitViews()\u62c6\u5206\u4e3aBeginInitViews()\u4e0eEndInitViews()\u3002\u8fd9\u662f\u56e0\u4e3a\u79fb\u52a8\u7aef\u7684\u7ba1\u7ebf\u76f8\u5bf9\u7b80\u5355\uff0c\u5f02\u6b65\u8ba1\u7b97\u5e26\u6765\u7684\u6536\u76ca\u53ef\u80fd\u76d6\u4e0d\u4f4f\u540c\u6b65\u5f00\u9500\uff1b\u800cPC\u7aef\u6027\u80fd\u591f\u5f3a\uff0c\u9700\u8981\u538b\u69a8\u591a\u7ebf\u7a0b\u3001CPU\u4e0eGPU\u5f02\u6b65\u3002<strong>BeginInitViews\u6d3e\u53d1\u4efb\u52a1\u4e0d\u7b49\u5f85\u7ed3\u679c\uff0cEndInitViews\u540c\u6b65\u7ed3\u679c\u751f\u6210Draw Call<\/strong><\/p>\n<h2>\u5177\u4f53\u5206\u6790<\/h2>\n<p>\u4f46\u6d3e\u53d1\u57fa\u7840 Culling \u4efb\u52a1\u7684\u4ee3\u7801\u4e0d\u5728BeginInitViews\u2014\u2014<strong>render\u4e2d\uff0cBeginInitViews\u4e4b\u524d<\/strong><\/p>\n<h3>Render()\u6d3e\u53d1 Culling \u4efb\u52a1<\/h3>\n<ul>\n<li><strong>\u51c6\u5907<\/strong>Frustum Cull &amp; \u56fe\u5143\u76f8\u5173\u6027\u8ba1\u7b97(\u51b3\u5b9a\u7269\u4f53\u662f\u753b\u5728 Base Pass \u8fd8\u662f\u534a\u900f\u660e Pass \u7b49)\n<pre><code class=\"line-numbers\">FInitViewTaskDatas InitViewTaskDatas = OnRenderBegin(GraphBuilder, SceneUpdateInputs);\n<\/code><\/pre>\n<ul>\n<li>\u51c6\u5907\u573a\u666fRT Task<\/li>\n<\/ul>\n<pre><code class=\"line-numbers\">if (SceneUpdateInputs)\n{\n    PrepareSceneTexturesConfigTask = UE::Tasks::Launch(UE_SOURCE_LOCATION, [SceneUpdateInputs]\n    {\n        TRACE_CPUPROFILER_EVENT_SCOPE(PrepareViewRects);\n        FTaskTagScope TagScope(ETaskTag::EParallelRenderingThread);\n\n        for (FSceneRenderer* Renderer : SceneUpdateInputs-&gt;Renderers)\n        {\n            Renderer-&gt;PrepareViewRectsForRendering();\n\n            InitializeSceneTexturesConfig(Renderer-&gt;ViewFamily.SceneTexturesConfig, Renderer-&gt;ViewFamily);\n            const FSceneTexturesConfig&amp; SceneTexturesConfig = Renderer-&gt;GetActiveSceneTexturesConfig();\n\n            \/\/ Custom render passes have their own view family structure, so they can have separate EngineShowFlags, so the SceneTexturesConfig\n            \/\/ needs to be copied.  The FSceneTextures structure itself is pointer shared, and doesn't need to be copied.\n            for (FCustomRenderPassInfo&amp; CustomRenderPass : Renderer-&gt;CustomRenderPassInfos)\n            {\n                CustomRenderPass.ViewFamily.SceneTexturesConfig = Renderer-&gt;ViewFamily.SceneTexturesConfig;\n\n                \/\/ Custom Render Passes don't support MSAA.  If MSAA is enabled, the first Custom Render Pass will allocate a separate non-MSAA\n                \/\/ FSceneTextures, initialized using this config (see logic in the FSceneRenderer constructor that fills in CustomRenderPassInfos).\n                CustomRenderPass.ViewFamily.SceneTexturesConfig.NumSamples = 1;\n                CustomRenderPass.ViewFamily.SceneTexturesConfig.EditorPrimitiveNumSamples = 1;\n            }\n        }\n\n    }, UE::Tasks::ETaskPriority::Normal, bIsMobilePlatform ? UE::Tasks::EExtendedTaskPriority::Inline : UE::Tasks::EExtendedTaskPriority::None);\n}\n<\/code><\/pre>\n<p>\u8ba1\u7b97\u51c6\u5907ViewRect\u3001SceneTexturesConfig\u3001CustomRenderPass<br \/>\n\u8fd9\u91cc\u7684<strong>CustomRenderPass\u5e76\u4e0d\u662f\u63d2\u4ef6\u5f0f\u81ea\u5b9a\u4e49\u7684render pass\uff0c\u800c\u662f\u7528\u4e0d\u540c\u7684\u6e32\u67d3\u8bbe\u7f6e\u518d\u6e32\u67d3\u4e00\u6b21\uff0c\u5c06\u573a\u666f\u201c\u79bb\u5c4f\u6e32\u67d3\u201d\u5230\u4e00\u5f20\u8d34\u56fe\u4e0a\uff0c\u4f9b\u540e\u7eed\u91c7\u6837<\/strong><\/p>\n<ul>\n<li>\u5b9a\u4e49\u573a\u666f\u66f4\u65b0\u5b8c\u6bd5\u540e\u7684\u56de\u8c03<\/li>\n<\/ul>\n<pre><code class=\"line-numbers\">SceneUpdateParameters.Callbacks.PostStaticMeshUpdate = [&amp;] (const UE::Tasks::FTask&amp; StaticMeshUpdateTask)\n{\n    PrepareSceneTexturesConfigTask.Wait();\n\n    if (!ViewFamily.ViewExtensions.IsEmpty())\n    {\n        RDG_CSV_STAT_EXCLUSIVE_SCOPE(GraphBuilder, PreRender);\n        SCOPE_CYCLE_COUNTER(STAT_FDeferredShadingSceneRenderer_ViewExtensionPreRenderView);\n\n        for (auto&amp; ViewExtension : ViewFamily.ViewExtensions)\n        {\n            ViewExtension-&gt;PreRenderViewFamily_RenderThread(GraphBuilder, ViewFamily);\n\n            for (FViewInfo* View : AllViews)\n            {\n                ViewExtension-&gt;PreRenderView_RenderThread(GraphBuilder, *View);\n            }\n        }\n    }\n\n    if (SceneUpdateInputs)\n    {\n        for (FSceneRenderer* Renderer : SceneUpdateInputs-&gt;Renderers)\n        {\n            const FSceneTexturesConfig&amp; SceneTexturesConfig = Renderer-&gt;GetActiveSceneTexturesConfig();\n            Renderer-&gt;PrepareViewStateForVisibility(SceneTexturesConfig);\n        }\n    }\n\n    if (ViewFamily.EngineShowFlags.LensDistortion &amp;&amp; FPaniniProjectionConfig::IsEnabledByCVars())\n    {\n        const FPaniniProjectionConfig PaniniProjection = FPaniniProjectionConfig::ReadCVars();\n\n        for (FViewInfo&amp; View : Views)\n        {\n            if (View.ViewMatrices.IsPerspectiveProjection())\n            {\n                View.LensDistortionLUT = PaniniProjection.GenerateLUTPasses(GraphBuilder, View);\n            }\n        }\n    }\n\n    \/\/ Run Groom LOD selection prior to visibility for selecting appropriate LOD &amp; geometry type\n    if (IsGroomEnabled())\n    {\n        if (Views.Num() &gt; 0 &amp;&amp; !ViewFamily.EngineShowFlags.HitProxies)\n        {\n            FHairStrandsBookmarkParameters Parameters;\n            CreateHairStrandsBookmarkParameters(Scene, Views, AllViews, Parameters, false\/*bComputeVisibleInstances*\/);\n            if (Parameters.HasInstances())\n            {\n                \/\/ 1. Select appropriate LOD &amp; geometry type\n                RunHairStrandsBookmark(GraphBuilder, EHairStrandsBookmark::ProcessLODSelection, Parameters);\n            }\n        }\n    }\n\n    \/\/ Lighting is skipped when running ERendererOutput::DepthPrepassOnly\n    if (GetRendererOutput() == ERendererOutput::FinalSceneColor)\n    {\n        LightFunctionAtlas::OnRenderBegin(LightFunctionAtlas, *Scene, Views, ViewFamily);\n    }\n\n    VisibilityTaskData = LaunchVisibilityTasks(GraphBuilder.RHICmdList, *this, StaticMeshUpdateTask);\n\n    if (GraphBuilder.IsParallelSetupEnabled())\n    {\n        GPUSceneUpdateTaskPrerequisites.AddPrerequisites(VisibilityTaskData-&gt;GetComputeRelevanceTask());\n    }\n    GPUSceneUpdateTaskPrerequisites.Trigger();\n};\n<\/code><\/pre>\n<ul>\n<li>\u7b49\u5f85SceneTexturesConfig Task\u5b8c\u6210<\/p>\n<\/li>\n<li>\n<p>\u5982\u679c\u9700\u8981\u6dfb\u52a0\u81ea\u5b9a\u4e49Pass\uff0c\u4e14Pass\u9700\u8981\u6dfb\u52a0\u4fee\u6539View\u6570\u636e<\/p>\n<pre><code class=\"line-numbers\">if (!ViewFamily.ViewExtensions.IsEmpty())\n  {\n      RDG_CSV_STAT_EXCLUSIVE_SCOPE(GraphBuilder, PreRender);\n      SCOPE_CYCLE_COUNTER(STAT_FDeferredShadingSceneRenderer_ViewExtensionPreRenderView);\n\n      for (auto&amp; ViewExtension : ViewFamily.ViewExtensions)\n      {\n          ViewExtension-&gt;PreRenderViewFamily_RenderThread(GraphBuilder, ViewFamily);\n\n          for (FViewInfo* View : AllViews)\n          {\n              ViewExtension-&gt;PreRenderView_RenderThread(GraphBuilder, *View);\n          }\n      }\n  }\n<\/code><\/pre>\n<p>\u4e3a\u63d2\u4ef6\u5f0f\u81ea\u5b9a\u4e49\u7684render pass\uff0c\u6dfb\u52a0\u4fee\u6539View\u6570\u636e<\/p>\n<\/li>\n<li>\n<p>\u66f4\u65b0ViewState<\/p>\n<pre><code class=\"line-numbers\">if (SceneUpdateInputs)\n{\n  for (FSceneRenderer* Renderer : SceneUpdateInputs-&gt;Renderers)\n  {\n      const FSceneTexturesConfig&amp; SceneTexturesConfig = Renderer-&gt;GetActiveSceneTexturesConfig();\n      Renderer-&gt;PrepareViewStateForVisibility(SceneTexturesConfig);\n  }\n}\n<\/code><\/pre>\n<p>FSceneViewState\u8bb0\u5f55\u7684\u662f\u4e0a\u4e00\u5e27\u6570\u636e\uff1bFViewInfo\u8bb0\u5f55\u7684\u662f\u5f53\u524d\u5e27\u7684\u6570\u636e\u3002\u4e3b\u8981\u670d\u52a1HIZ<\/p>\n<\/li>\n<li>\n<p>\u751f\u6210LUT\uff0c\u7528\u4e8e\u540e\u7eed\u540e\u5904\u7406\u4fee\u590d\u955c\u5934\u7578\u53d8<\/p>\n<pre><code class=\"line-numbers\">if (ViewFamily.EngineShowFlags.LensDistortion &amp;&amp; FPaniniProjectionConfig::IsEnabledByCVars())\n{\n  const FPaniniProjectionConfig PaniniProjection = FPaniniProjectionConfig::ReadCVars();\n\n  for (FViewInfo&amp; View : Views)\n  {\n      if (View.ViewMatrices.IsPerspectiveProjection())\n      {\n          View.LensDistortionLUT = PaniniProjection.GenerateLUTPasses(GraphBuilder, View);\n      }\n  }\n}\n<\/code><\/pre>\n<p>\u7528\u4e8ePanini \u6295\u5f71\u4fee\u590d\u7ebf\u6027\u900f\u89c6\u6295\u5f71\u3002FOV \u8d8a\u5927\uff0c\u5c4f\u5e55\u8fb9\u7f18\u7684\u7269\u4f53\u4f1a\u88ab\u62c9\u4f38\u5f97\u975e\u5e38\u5bbd\u3001\u751a\u81f3\u53d8\u5f62<\/p>\n<\/li>\n<li>\n<p>\u4e3aGroom\u6bdb\u53d1\u8ba1\u7b97LOD<\/p>\n<pre><code class=\"line-numbers\">\/\/ Run Groom LOD selection prior to visibility for selecting appropriate LOD &amp; geometry type\nif (IsGroomEnabled())\n{\n  if (Views.Num() &gt; 0 &amp;&amp; !ViewFamily.EngineShowFlags.HitProxies)\n  {\n      FHairStrandsBookmarkParameters Parameters;\n      CreateHairStrandsBookmarkParameters(Scene, Views, AllViews, Parameters, false\/*bComputeVisibleInstances*\/);\n      if (Parameters.HasInstances())\n      {\n          \/\/ 1. Select appropriate LOD &amp; geometry type\n          RunHairStrandsBookmark(GraphBuilder, EHairStrandsBookmark::ProcessLODSelection, Parameters);\n      }\n  }\n}\n<\/code><\/pre>\n<\/li>\n<li>\u5224\u65ad\u5f53\u524drender\u662f\u5426\u8f93\u51fascene color\uff0c\u4e0d\u8f93\u51fa\u5219\u8df3\u8fc7\uff0c\u5426\u5219\u8ba1\u7b97LightFunction\n<pre><code class=\"line-numbers\">if (GetRendererOutput() == ERendererOutput::FinalSceneColor)\n{\n  LightFunctionAtlas::OnRenderBegin(LightFunctionAtlas, *Scene, Views, ViewFamily);\n}\n<\/code><\/pre>\n<p>\u904d\u5386\u6240\u6709\u5e26\u6709LightFunction\u7684\u706f\u5149\uff0c\u5c06\u4ed6\u4eec\u7684LightFunction\u5199\u5728\u4e00\u5f20Atlas<\/p>\n<\/li>\n<li>\n<p>\u628aVisibility Culling\u4e22\u8fdb\u591a\u7ebf\u7a0b<\/p>\n<pre><code class=\"line-numbers\">VisibilityTaskData = LaunchVisibilityTasks(GraphBuilder.RHICmdList, *this, StaticMeshUpdateTask);\n<\/code><\/pre>\n<ul>\n<li>\u9884\u8ba1\u7b97\u53ef\u89c1\u6027\u5254\u9664<\/li>\n<\/ul>\n<pre><code class=\"line-numbers\">if (ViewPacket.ViewState)\n{\n    SCOPE_CYCLE_COUNTER(STAT_DecompressPrecomputedOcclusion);\n    ViewPacket.View.PrecomputedVisibilityData = ViewPacket.ViewState-&gt;ResolvePrecomputedVisibilityData(ViewPacket.View, &amp;Scene);\n\n    if (ViewPacket.View.PrecomputedVisibilityData)\n    {\n        SceneRenderer.bUsedPrecomputedVisibility = true;\n    }\n}\n<\/code><\/pre>\n<ul>\n<li>\u7b49\u5f85\u5fc5\u8981\u4efb\u52a1\u5b8c\u6210<\/li>\n<\/ul>\n<pre><code class=\"line-numbers\">\/\/ Each relevance task should have this as a prerequisite, but in case there aren't any tasks we make it explicit.\nTasks.ComputeRelevance.AddPrerequisites(Scene.GetCacheMeshDrawCommandsTask());\n\n\/\/ Wait on the GPU skin update task prior to GDME.\nTasks.DynamicMeshElementsPrerequisites.AddPrerequisites(Scene.GetGPUSkinUpdateTask());\n<\/code><\/pre>\n<ul>\n<li>\u56fe\u5143\u5206\u7c7b\u3001GPU Skin<\/p>\n<\/li>\n<li>\n<p>\u5149\u6e90\u7684\u89c6\u9525\u4f53\u5254\u9664<\/p>\n<\/li>\n<\/ul>\n<pre><code class=\"line-numbers\">Tasks.LightVisibility.AddPrerequisites(UE::Tasks::Launch(UE_SOURCE_LOCATION, [this]\n{\n    FTaskTagScope Scope(ETaskTag::EParallelRenderingThread);\n    SceneRenderer.ComputeLightVisibility();\n\n}, TaskConfig.TaskPriority));\n<\/code><\/pre>\n<ul>\n<li>\u4e0d\u7b49Culling\u5b8c\u5168\u7ed3\u675f\u540e\uff0c\u624d\u904d\u5386\u8ba1\u7b97DynamicMeshElement\u3002\u800c\u662fCulling\u5b8c\u4e00\u90e8\u5206\u540e\uff0c\u76f4\u63a5\u904d\u5386\u8fd9\u4e9b\u5254\u9664\u51fa<strong>\u4e00\u5c0f\u6279<\/strong>\u53ef\u89c1\u7684\u52a8\u6001\u7269\u4f53\uff0c\u5e76\u8ba1\u7b97DynamicMeshElement<\/li>\n<\/ul>\n<pre><code class=\"line-numbers\">if (TaskConfig.Schedule == EVisibilityTaskSchedule::Parallel)\n{\n    if (ViewPackets.Num() == 1)\n    {\n        \/\/ When using a single view, dynamic mesh elements are pushed into a pipe that is executed on the render thread which allows for some overlap with compute relevance work.\n        DynamicMeshElements.CommandPipe = Allocator.Create&lt;TCommandPipe&lt;FDynamicPrimitiveIndexList&gt;&gt;(TEXT(\"GatherDynamicMeshElements\"));\n\n        DynamicMeshElements.CommandPipe-&gt;SetCommandFunction([this](FDynamicPrimitiveIndexList&amp;&amp; DynamicPrimitiveIndexList)\n        {\n            GatherDynamicMeshElements(MoveTemp(DynamicPrimitiveIndexList));\n        });\n\n        DynamicMeshElements.CommandPipe-&gt;SetPrerequisiteTask(Tasks.DynamicMeshElementsPrerequisites);\n\n        Tasks.DynamicMeshElementsPipe = FGraphEvent::CreateGraphEvent();\n\n        DynamicMeshElements.CommandPipe-&gt;SetEmptyFunction([this]\n        {\n            Tasks.DynamicMeshElementsPipe-&gt;DispatchSubsequents();\n            Tasks.DynamicMeshElements.Trigger();\n        });\n\n        \/\/ Take a reference that is released when the relevance pipe has completed. We only need to take one since there can only be one view.\n        DynamicMeshElements.CommandPipe-&gt;AddNumCommands(1);\n\n        \/\/ We don't need the primitive view masks when in parallel mode with a single view.\n        bAllocatePrimitiveViewMasks = false;\n    }\n}\n<\/code><\/pre>\n<ul>\n<li>Frustum Cull &amp; Distance Cull&amp; \u786c\u4ef6\u906e\u6321\u67e5\u8be2<\/li>\n<\/ul>\n<pre><code class=\"line-numbers\">SceneRenderer.WaitOcclusionTests(RHICmdList);\n\n\/\/ Parallel occlusion culling is not supported on mobile\ncheck(!Views.IsEmpty())\ncheckf(!Views[0]-&gt;bIsMobileMultiViewEnabled, TEXT(\"This culling path was not tested with MMV\"));\n\nfor (FVisibilityViewPacket&amp; ViewPacket : ViewPackets)\n{\n    if (ViewPacket.OcclusionCull.ContextIfParallel)\n    {\n        ViewPacket.OcclusionCull.ContextIfParallel-&gt;Map(RHICmdList);\n    }\n\n    Tasks.BeginInitVisibility.AddPrerequisites(UE::Tasks::Launch(UE_SOURCE_LOCATION, [&amp;ViewPacket]\n    {\n        FTaskTagScope Scope(ETaskTag::EParallelRenderingThread);\n        ViewPacket.BeginInitVisibility();\n\n    }, BeginInitVisibilityPrerequisites, TaskConfig.TaskPriority));\n}\n\n\/\/ Static relevance is finalized for ALL views after each view completes static mesh filtering tasks.\nTasks.FinalizeRelevance = UE::Tasks::Launch(UE_SOURCE_LOCATION, [this]\n{\n    TRACE_CPUPROFILER_EVENT_SCOPE(FSceneRenderer_FinalizeStaticRelevance);\n\n    for (FVisibilityViewPacket&amp; ViewPacket : ViewPackets)\n    {\n        ViewPacket.Relevance.Context-&gt;Finalize();\n    }\n\n}, Tasks.ComputeRelevance, TaskConfig.TaskPriority);\n<\/code><\/pre>\n<ul>\n<li>\n<p>\u56de\u8bfb\u3002\u4e0a\u4e00\u5e27\u5f15\u64ce\u5411 GPU \u63d0\u4ea4\u4e86\u4e00\u6279\u4e0d\u53ef\u89c1\u7269\u4f53\u7684\u5305\u56f4\u76d2<\/p>\n<\/li>\n<li>\n<p>\u7ed9\u6bcf\u4e2a\u76f8\u673a\u6d3e\u53d1\u521d\u59cb\u5316\u4efb\u52a1<\/p>\n<ul>\n<li>\u516b\u53c9\u6811\u8282\u70b9\u5254\u9664<\/li>\n<\/ul>\n<pre><code class=\"line-numbers\">if (bShouldVisibilityCull &amp;&amp; Flags.bUseVisibilityOctree)\n{\n    VisibleNodes = TaskData.Allocator.Create&lt;FSceneBitArray&gt;();\n    const FConvexVolume&amp; ViewCullingFrustum = View.GetCullingFrustum();\n    CullOctree(Scene, View, Flags, *VisibleNodes, ViewCullingFrustum);\n}\n<\/code><\/pre>\n<ul>\n<li>FrustumCull &amp; Distance Cull<\/li>\n<\/ul>\n<pre><code class=\"line-numbers\">\/\/ Frustum culling tasks have to run serially if custom culling is not thread-safe.\nconst UE::Tasks::EExtendedTaskPriority ExtendedTaskPriority = GetExtendedTaskPriority(bCullingIsThreadsafe);\n\n\/\/ Assign the number of expected commands first so the pipe can determine when the last task has completed.\nOcclusionCull.CommandPipe.AddNumCommands(TaskConfig.FrustumCull.NumTasks);\n\nfor (uint32 TaskIndex = 0; TaskIndex &lt; TaskConfig.FrustumCull.NumTasks; ++TaskIndex)\n{\n    Tasks.FrustumCull.AddPrerequisites(\n        UE::Tasks::Launch(UE_SOURCE_LOCATION, [this, Flags, MaxDrawDistanceScale, HLODState, VisibleNodes, TaskIndex]() mutable\n    {\n        TRACE_CPUPROFILER_EVENT_SCOPE(SceneVisibility_FrustumCull);\n        FTaskTagScope TaskTagScope(ETaskTag::EParallelRenderingThread);\n        int32 NumCulledPrimitives = FrustumCull(Scene, View, Flags, MaxDrawDistanceScale, HLODState, VisibleNodes, TaskConfig, TaskIndex);\n\n        FPrimitiveRange PrimitiveRange;\n        PrimitiveRange.StartIndex = TaskConfig.FrustumCull.NumPrimitivesPerTask * (TaskIndex);\n        PrimitiveRange.EndIndex   = TaskConfig.FrustumCull.NumPrimitivesPerTask + PrimitiveRange.StartIndex;\n        PrimitiveRange.EndIndex   = FMath::Min(PrimitiveRange.EndIndex, int32(TaskConfig.NumTestedPrimitives));\n\n        \/\/ Skip rendering of dynamic objects without static lighting for static reflection captures.\n        if (View.bStaticSceneOnly)\n        {\n            for (FSceneSetBitIterator BitIt(View.PrimitiveVisibilityMap, PrimitiveRange.StartIndex); BitIt.GetIndex() &lt; PrimitiveRange.EndIndex; ++BitIt)\n            {\n                if (!Scene.PrimitiveSceneProxies[BitIt.GetIndex()]-&gt;HasStaticLighting())\n                {\n                    View.PrimitiveVisibilityMap.AccessCorrespondingBit(BitIt) = false;\n                    NumCulledPrimitives++;\n                }\n            }\n        }\n\n        \/\/ Skip rendering of small objects when in wireframe mode for performance since wireframe doesn't enable occlusion culling.\n        if (View.Family-&gt;EngineShowFlags.Wireframe)\n        {\n            const float ScreenSizeScale = FMath::Max(View.ViewMatrices.GetProjectionMatrix().M[0][0] * View.ViewRect.Width(), View.ViewMatrices.GetProjectionMatrix().M[1][1] * View.ViewRect.Height());\n\n            for (FSceneSetBitIterator BitIt(View.PrimitiveVisibilityMap, PrimitiveRange.StartIndex); BitIt.GetIndex() &lt; PrimitiveRange.EndIndex; ++BitIt)\n            {\n                if (ScreenSizeScale * Scene.PrimitiveBounds[BitIt.GetIndex()].BoxSphereBounds.SphereRadius &lt;= GWireframeCullThreshold)\n                {\n                    View.PrimitiveVisibilityMap.AccessCorrespondingBit(BitIt) = false;\n                    NumCulledPrimitives++;\n                }\n            }\n        }\n\n        const uint32 NumVisiblePrimitives = PrimitiveRange.EndIndex - PrimitiveRange.StartIndex - NumCulledPrimitives;\n\n        if (NumVisiblePrimitives == 0)\n        {\n            OcclusionCull.CommandPipe.ReleaseNumCommands(1);\n        }\n        else\n        {\n            OcclusionCull.CommandPipe.EnqueueCommand(PrimitiveRange);\n        }\n\n        TaskConfig.FrustumCull.NumCulledPrimitives.fetch_add(NumCulledPrimitives, std::memory_order_relaxed);\n\n    }, bHasAlwaysVisible ? Tasks.AlwaysVisible : PrerequisiteTask, TaskConfig.TaskPriority, ExtendedTaskPriority));\n}\n\nOcclusionCull.CommandPipe.ReleaseNumCommands(1);\n<\/code><\/pre>\n<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<h3>BeginInitViews()<\/h3>\n<p><strong>\u628a\u6781\u5ea6\u8017\u65f6\u7684\u7269\u7406\u3001\u9aa8\u9abc\u3001\u7c92\u5b50\u3001TLAS \u6784\u5efa\u4efb\u52a1\u7edf\u7edf\u6254\u7ed9 GPU \u5f02\u6b65\u8ba1\u7b97\u5f15\u64ce\u6216 CPU \u7ebf\u7a0b\u6c60<\/strong><\/p>\n<ul>\n<li>\u8ba1\u7b97FX\u4f4d\u7f6e\uff1a\u4e00\u822cViewUniformBuffer\u5728Culling\u540e\u624d\u7ed1\u5b9a\uff0c\u4f46Niagara Compute Shader \u9700\u8981\u5c3d\u65e9\u62ff\u5230\u8fd9\u4e9b\u77e9\u9635\u53bb\u7b97\u7c92\u5b50\u7684\u8fd0\u52a8\n<pre><code class=\"line-numbers\">if (FXSystem &amp;&amp; FXSystem-&gt;RequiresEarlyViewUniformBuffer() &amp;&amp; Views.IsValidIndex(0) &amp;&amp; bRendererOutputFinalSceneColor)\n    {\n        \/\/ during ISR, instanced view RHI resources need to be initialized first.\n        if (FViewInfo* InstancedView = const_cast&lt;FViewInfo*&gt;(Views[0].GetInstancedView()))\n        {\n            InstancedView-&gt;InitRHIResources();\n        }\n        Views[0].InitRHIResources();\n        FXSystem-&gt;PostInitViews(GraphBuilder, GetSceneViews(), !ViewFamily.EngineShowFlags.HitProxies);\n    }\n<\/code><\/pre>\n<\/li>\n<li>\u6536\u96c6\u52a8\u6001\u7f51\u683c\u4f53\uff1a\u9aa8\u9abc\u52a8\u753b\u3001\u52a8\u6001\u751f\u6210\u7684 Mesh\n<pre><code class=\"line-numbers\">TaskDatas.VisibilityTaskData-&gt;StartGatherDynamicMeshElements();\n<\/code><\/pre>\n<\/li>\n<li>\u4e3aTLAS\u6536\u96c6\u5f53\u524d\u5e27\u6240\u6709\u9700\u8981\u66f4\u65b0\u7684Dynamic Instances\u7684\u4fe1\u606f\uff0c\u4e3a\u540e\u7eedTLAS\u505a\u51c6\u5907\n<pre><code class=\"language-glsl line-numbers\">if (TaskDatas.RayTracingGatherInstances != nullptr)\n{                                                     RayTracing::BeginGatherDynamicRayTracingInstances(*TaskDatas.RayTracingGatherInstances);\n}\n<\/code><\/pre>\n<\/li>\n<li>\u6536\u96c6\u8d34\u82b1\n<pre><code class=\"line-numbers\">if (!ViewFamily.EngineShowFlags.HitProxies)\n{\n  TaskDatas.Decals = FDecalVisibilityTaskData::Launch(GraphBuilder, *Scene, Views);\n}\n<\/code><\/pre>\n<\/li>\n<li>\u8ba1\u7b97\u9634\u5f71\u6570\u636e\n<pre><code class=\"line-numbers\">if (bRendererOutputFinalSceneColor)\n{\n  BeginInitDynamicShadows(GraphBuilder, TaskDatas, InstanceCullingManager);\n}\n<\/code><\/pre>\n<p>\u8ba1\u7b97CSM Splits\u3001Light Space Matrix\u3001Shadow Frustum Culling\u3001\u5206\u914d Shadow Map Atlas\u3001\u6784\u5efa\u4e0a\u4e0b\u6587<\/p>\n<\/li>\n<li>\n<p>\u51c6\u5907\u5168\u5c40\u73af\u5883\u5149\u4e0eView<\/p>\n<pre><code class=\"line-numbers\">\/\/ \u51c6\u5907\u5929\u7a7a\u5149\u7167\u8f90\u7167\u5ea6\nUpdateSkyIrradianceGpuBuffer(GraphBuilder, ViewFamily.EngineShowFlags, Scene-&gt;SkyLight, Scene-&gt;SkyIrradianceEnvironmentMap);\n\n\/\/ \u51c6\u5907\u5929\u7a7a\u5927\u6c14\u8d44\u6e90\nif (ShouldRenderSkyAtmosphere(Scene, ViewFamily.EngineShowFlags))\n{\n  InitSkyAtmosphereForViews(RHICmdList, GraphBuilder);\n}\n\n\/\/ \u5faa\u73af\u904d\u5386\u6240\u6709 View\nfor (int32 ViewIndex = Views.Num() - 1; ViewIndex &gt;= 0; --ViewIndex)\n{\n  FViewInfo&amp; View = Views[ViewIndex];\n  View.UpdatePreExposure(); \/\/ \u66f4\u65b0\u81ea\u52a8\u66dd\u5149\u53c2\u6570\n\n  UpdateHairResources(GraphBuilder, View);\/\/ \u51c6\u5907\u6bdb\u53d1\u6e32\u67d3\u8d44\u6e90\n  View.InitRHIResources();  \/\/ \u6b63\u5f0f\u5206\u914d View \u7684\u5404\u79cd\u5e95\u5c42\u5e38\u91cf\u7f13\u51b2\n}\n\nfor (FCustomRenderPassInfo&amp; PassInfo : CustomRenderPassInfos)\n{\n  for (FViewInfo&amp; View : PassInfo.Views)\n  {\n      View.InitRHIResources();\n  }\n}\n<\/code><\/pre>\n<\/li>\n<li>\u518d\u6b21\u8ba1\u7b97\u9634\u5f71\n<p>\u56e0\u4e3aProcessRenderThreadTasks()\u4e4b\u540e\u624d\u77e5\u9053\u52a8\u6001\u6570\u636e\uff0c\u518d\u6b21\u8ba1\u7b97\u5269\u4e0b\u7684\u52a8\u6001\u9634\u5f71<\/p>\n<pre><code class=\"line-numbers\">if (bRendererOutputFinalSceneColor)\n{\n  BeginInitDynamicShadows(GraphBuilder, TaskDatas, InstanceCullingManager);\n}\n<\/code><\/pre>\n<\/li>\n<\/ul>\n<h3>EndInitViews()<\/h3>\n<p><strong>\u62ff\u5230\u6240\u6709\u7684 Culling \u7ed3\u679c\uff0c\u6253\u5305\u6210\u6700\u7ec8\u7684 Draw Call \u961f\u5217<\/strong><\/p>\n<ul>\n<li>\u7b49\u5f85\u7ebf\u7a0bComputeViewVisibility\uff08\uff09\u4efb\u52a1\u5168\u90e8\u7ed3\u675f\n<pre><code class=\"line-numbers\">TaskDatas.VisibilityTaskData-&gt;Finish();\n<\/code><\/pre>\n<\/li>\n<li>\u51c6\u5907shadow cast\u6570\u636e\n<pre><code class=\"line-numbers\">BeginShadowGatherDynamicMeshElements(TaskDatas.DynamicShadows);\n<\/code><\/pre>\n<p>\u51c6\u5907draw data\uff0c\u5982\u9876\u70b9\/\u7d22\u5f15\u6570\u636e\u3001\u6750\u8d28\u4fe1\u606f\u3001\u53d8\u6362\u77e9\u9635\u3001LOD<\/p>\n<\/li>\n<li>\n<p>\u5904\u7406GI<\/p>\n<pre><code class=\"line-numbers\">\/\/ \u5982\u679c\u6ca1\u6709\u5f00\u591a\u7ebf\u7a0b\u4efb\u52a1\nif (ViewFamily.EngineShowFlags.HitProxies == 0\n    &amp;&amp; Scene-&gt;PrecomputedLightVolumes.Num() &gt; 0\n    &amp;&amp; !(GILCUpdatePrimTaskEnabled &amp;&amp; FPlatformProcess::SupportsMultithreading()))\n{\n  check(!TaskDatas.ILCUpdatePrim);\n  \/\/ \u66f4\u65b0 ILC\n  Scene-&gt;IndirectLightingCache.UpdateCache(Scene, *this, true);\n}\n\n\/\/ \u5982\u679c\u4e4b\u524d\u6d3e\u53d1\u4e86 ILC \u7684\u5f02\u6b65\u4efb\u52a1\uff0c\u5728\u8fd9\u91cc\u7b49\u5f85\u5b83\u5b8c\u6210\u5e76 Finalize\nif (TaskDatas.ILCUpdatePrim)\n{\n  Scene-&gt;IndirectLightingCache.FinalizeCacheUpdates(Scene, *this, *TaskDatas.ILCUpdatePrim);\n}\n\n\/\/ \u5c06\u66f4\u65b0\u540e\u7684\u6570\u636e\u4e0a\u4f20\u5230 GPU Buffer      UpdatePrimitiveIndirectLightingCacheBuffers(GraphBuilder.RHICmdList);\n<\/code><\/pre>\n<p>ILC \u4e3b\u8981\u7528\u4e8e\u5c06\u70d8\u7119\u597d\u7684\u9759\u6001\u5149\u7167\uff08Lightmass \u4f53\u7d20\uff09\u63d2\u503c\u5e94\u7528\u5230<strong>\u52a8\u6001\u7269\u4f53<\/strong>\u4e0a<\/p>\n<\/li>\n<li>\n<p>\u5904\u7406\u534a\u900f\u660e\u4e0e\u53cd\u5c04\u6355\u6349<\/p>\n<pre><code class=\"line-numbers\">SeparateTranslucencyDimensions = UpdateSeparateTranslucencyDimensions(*this);\n\nSetupSceneReflectionCaptureBuffer(RHICmdList);\n<\/code><\/pre>\n<ul>\n<li>\u8ba1\u7b97\u5e76\u66f4\u65b0\u5206\u79bb\u7684\u534a\u900f\u660e\u6e32\u67d3\u76ee\u6807\u7684\u5c3a\u5bf8\uff1a\u73b0\u4ee3\u5f15\u64ce\u901a\u5e38\u4f1a\u5c06\u534a\u900f\u660e\u7269\u4f53\u653e\u5728\u4e00\u4e2a\u5355\u72ec\u7684 Pass \u4e2d\u751a\u81f3\u964d\u4f4e\u5206\u8fa8\u7387\u6e32\u67d3\u4ee5\u8282\u7701\u5e26\u5bbd<\/li>\n<li>Reflection Capture: \u5c06 Reflection Capture Actor \u7684\u6570\u636e\u6253\u5305\u585e\u5165 GPU Buffer<\/li>\n<\/ul>\n<\/li>\n<li>\u524d\u5411\u6e32\u67d3\u7684\u7279\u6b8a\u9634\u5f71\u5904\u7406\n<pre><code class=\"line-numbers\">if (IsForwardShadingEnabled(ShaderPlatform))\n{\n  \/\/ Dynamic shadows are synced earlier when forward shading is enabled.\n  FinishInitDynamicShadows(GraphBuilder, TaskDatas.DynamicShadows, InstanceCullingManager);\n}\n<\/code><\/pre>\n<p>\u5982\u679c\u5f00\u542f\u4e86Forward Shading\uff0c\u52a8\u6001\u9634\u5f71\u7684\u521d\u59cb\u5316\u5fc5\u987b\u5728\u8fd9\u91cc\u76f4\u63a5Finish<\/p>\n<\/li>\n<\/ul>\n<h1>Culling\u89e3\u6790<\/h1>\n<h2>Pre Computed Visibility<\/h2>\n<ul>\n<li>\n<p>\u4ec0\u4e48\u662fPre Computed Visibility<\/p>\n<p>\u4e00\u79cd<strong>\u79bb\u7ebf\u7684Occlusion Culling<\/strong>\uff0c\u5b83\u5c06\u6e38\u620f\u4e16\u754c\u5212\u5206\u4e3a\u4e00\u4e2a\u4e2a3d\u5355\u5143\u683c\uff0c\u9884\u5148\u8ba1\u7b97\u5e76\u8bb0\u5f55\u4e0b<strong>\u4ece\u6bcf\u4e2a\u7f51\u683c\u53ef\u80fd\u770b\u5230\u54ea\u4e9b\u9759\u6001\u7269\u4f53<\/strong>\u3002\u540e\u7eed\u4e0d\u9700\u8981\u5b9e\u65f6\u8ba1\u7b97\u906e\u6321\uff0c\u53ea\u8981\u68c0\u67e5\u5355\u5143\u683c\uff0c\u5c31\u80fd\u77e5\u9053\u54ea\u4e9b\u7269\u4f53\u662f\u88ab\u906e\u6321\u7684<\/p>\n<\/li>\n<li>\n<p>\u4e3a\u4ec0\u4e48\u9700\u8981Pre Computed Visibility<\/p>\n<p>\u5b9e\u65f6\u52a8\u6001\u906e\u6321\u5254\u9664\u6bcf\u4e00\u5e27\u5411GPU\u53d1\u9001\u67e5\u8be2\u6307\u4ee4\uff0c\u5982\u679c\u573a\u666f\u4e2d\u7269\u4f53\u6781\u591a\uff0c\u8fd9\u4e2a\u67e5\u8be2\u8fc7\u7a0b\u672c\u8eab\u5c31\u4f1a\u6210\u4e3a\u6027\u80fd\u74f6\u9888<\/p>\n<p>\u5bf9\u4e8e\u573a\u666f\u4e2d\u9759\u6001\u7269\u4f53\uff0c\u5b8c\u5168\u53ef\u4ee5\u9884\u5236\uff0c\u4e0d\u5fc5\u8fd0\u884c\u65f6\u518d\u67e5\u8be2<\/p>\n<\/li>\n<li>\n<p>\u5927\u81f4\u7b97\u6cd5<\/p>\n<ul>\n<li>\u627e\u51fa<strong>\u7a7a\u65f7\u533a\u57df<\/strong><\/p>\n<\/li>\n<li>\n<p>\u5bf9\u7a7a\u65f7\u533a\u57df\u5212\u5206\u76d2\u5b50\u2014\u2014 <strong>Cell<\/strong><\/p>\n<\/li>\n<li>\n<p>\u5728<strong>\u6bcf\u4e2a Cell\u5185\u90e8<\/strong>\uff0c\u751f\u6210camra<\/p>\n<\/li>\n<li>\n<p>\u6bcf\u4e2acamera360\u00b0\u53d1\u5c04\u5c04\u7ebf\uff0c\u649e\u51fb <strong>Static Mesh<\/strong>\u3002\u5982\u679c\u5c04\u7ebf\u88abStatic Mesh A\u6321\u4f4f\uff0c\u5bfc\u81f4\u6839\u672c\u770b\u4e0d\u89c1Static Mesh B\uff0c\u90a3\u4e48\u5728\u8fd9\u4e2a Cell \u7684bit mask\u8868\u91cc\uff0cStatic Mesh B \u5c31\u88ab\u8bb0\u4e3a <code>0<\/code>\uff08\u8868\u793a\u6839\u672c\u770b\u4e0d\u5230\uff09<\/p>\n<\/li>\n<\/ul>\n<\/li>\n<li>\n<p>UE\u5982\u4f55\u5b9e\u73b0<\/p>\n<pre><code class=\"line-numbers\">const uint8* FSceneViewState::ResolvePrecomputedVisibilityData(FViewInfo&amp; View, const FScene* InScene)\n{\nconst uint8* PrecomputedVisibilityData = NULL;\nif (InScene-&gt;PrecomputedVisibilityHandler &amp;&amp; GAllowPrecomputedVisibility &amp;&amp; View.Family-&gt;EngineShowFlags.PrecomputedVisibility)\n{\n    const FPrecomputedVisibilityHandler&amp; Handler = *InScene-&gt;PrecomputedVisibilityHandler;\n    FViewElementPDI VisibilityCellsPDI(&amp;View, nullptr, nullptr);\n\n    \/\/ Draw visibility cell bounds for debugging if enabled\n    if ((GShowPrecomputedVisibilityCells || View.Family-&gt;EngineShowFlags.PrecomputedVisibilityCells) &amp;&amp; !GShowRelevantPrecomputedVisibilityCells)\n    {\n        for (int32 BucketIndex = 0; BucketIndex &lt; Handler.PrecomputedVisibilityCellBuckets.Num(); BucketIndex++)\n        {\n            for (int32 CellIndex = 0; CellIndex &lt; Handler.PrecomputedVisibilityCellBuckets[BucketIndex].Cells.Num(); CellIndex++)\n            {\n                const FPrecomputedVisibilityCell&amp; CurrentCell = Handler.PrecomputedVisibilityCellBuckets[BucketIndex].Cells[CellIndex];\n                \/\/ Construct the cell's bounds\n                const FBox CellBounds(CurrentCell.Min, CurrentCell.Min + FVector(Handler.PrecomputedVisibilityCellSizeXY, Handler.PrecomputedVisibilityCellSizeXY, Handler.PrecomputedVisibilityCellSizeZ));\n                if (View.GetCullingFrustum().IntersectBox(CellBounds.GetCenter(), CellBounds.GetExtent()))\n                {\n                    DrawWireBox(&amp;VisibilityCellsPDI, CellBounds, FColor(50, 50, 255), SDPG_World);\n                }\n            }\n        }\n    }\n\n    \/\/Determine view origin\n    FVector ViewOrigin = View.CullingOrigin;\n#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)\n    if (const FViewMatrices* FrozenViewMatrices = GetFrozenViewMatrices())\n    {\n        \/\/ Use the frozen view for culling so we can test that it's working\n        ViewOrigin = FrozenViewMatrices-&gt;GetViewOrigin();\n    }\n#endif\n\n    \/\/ Calculate the bucket that ViewOrigin falls into\n    \/\/ Cells are hashed into buckets to reduce search time\n    const float FloatOffsetX = (ViewOrigin.X - Handler.PrecomputedVisibilityCellBucketOriginXY.X) \/ Handler.PrecomputedVisibilityCellSizeXY;\n    \/\/ FMath::TruncToInt rounds toward 0, we want to always round down\n    const int32 BucketIndexX = FMath::Abs((FMath::TruncToInt(FloatOffsetX) - (FloatOffsetX &lt; 0.0f ? 1 : 0)) \/ Handler.PrecomputedVisibilityCellBucketSizeXY % Handler.PrecomputedVisibilityNumCellBuckets);\n    const float FloatOffsetY = (ViewOrigin.Y -Handler.PrecomputedVisibilityCellBucketOriginXY.Y) \/ Handler.PrecomputedVisibilityCellSizeXY;\n    const int32 BucketIndexY = FMath::Abs((FMath::TruncToInt(FloatOffsetY) - (FloatOffsetY &lt; 0.0f ? 1 : 0)) \/ Handler.PrecomputedVisibilityCellBucketSizeXY % Handler.PrecomputedVisibilityNumCellBuckets);\n    const int32 PrecomputedVisibilityBucketIndex = BucketIndexY * Handler.PrecomputedVisibilityCellBucketSizeXY + BucketIndexX;\n\n    check(PrecomputedVisibilityBucketIndex &lt; Handler.PrecomputedVisibilityCellBuckets.Num());\n    const FPrecomputedVisibilityBucket&amp; CurrentBucket = Handler.PrecomputedVisibilityCellBuckets[PrecomputedVisibilityBucketIndex];\n    for (int32 CellIndex = 0; CellIndex &lt; CurrentBucket.Cells.Num(); CellIndex++)\n    {\n        const FPrecomputedVisibilityCell&amp; CurrentCell = CurrentBucket.Cells[CellIndex];\n        \/\/ Construct the cell's bounds\n        const FBox CellBounds(CurrentCell.Min, CurrentCell.Min + FVector(Handler.PrecomputedVisibilityCellSizeXY, Handler.PrecomputedVisibilityCellSizeXY, Handler.PrecomputedVisibilityCellSizeZ));\n        \/\/ Check if ViewOrigin is inside the current cell\n        if (CellBounds.IsInside(ViewOrigin))\n        {\n            \/\/ Reuse a cached decompressed chunk if possible\n            if (CachedVisibilityChunk\n                &amp;&amp; CachedVisibilityHandlerId == InScene-&gt;PrecomputedVisibilityHandler-&gt;GetId()\n                &amp;&amp; CachedVisibilityBucketIndex == PrecomputedVisibilityBucketIndex\n                &amp;&amp; CachedVisibilityChunkIndex == CurrentCell.ChunkIndex)\n            {\n                checkSlow(CachedVisibilityChunk-&gt;Num() &gt;= CurrentCell.DataOffset + CurrentBucket.CellDataSize);\n                PrecomputedVisibilityData = &amp;(*CachedVisibilityChunk)[CurrentCell.DataOffset];\n            }\n            else\n            {\n                const FCompressedVisibilityChunk&amp; CompressedChunk = Handler.PrecomputedVisibilityCellBuckets[PrecomputedVisibilityBucketIndex].CellDataChunks[CurrentCell.ChunkIndex];\n                CachedVisibilityBucketIndex = PrecomputedVisibilityBucketIndex;\n                CachedVisibilityChunkIndex = CurrentCell.ChunkIndex;\n                CachedVisibilityHandlerId = InScene-&gt;PrecomputedVisibilityHandler-&gt;GetId();\n\n                if (CompressedChunk.bCompressed)\n                {\n                    \/\/ Decompress the needed visibility data chunk\n                    DecompressedVisibilityChunk.Reset();\n                    DecompressedVisibilityChunk.AddUninitialized(CompressedChunk.UncompressedSize);\n                    verify(FCompression::UncompressMemory(\n                        NAME_Zlib, \n                        DecompressedVisibilityChunk.GetData(),\n                        CompressedChunk.UncompressedSize,\n                        CompressedChunk.Data.GetData(),\n                        CompressedChunk.Data.Num()));\n                    CachedVisibilityChunk = &amp;DecompressedVisibilityChunk;\n                }\n                else\n                {\n                    CachedVisibilityChunk = &amp;CompressedChunk.Data;\n                }\n\n                checkSlow(CachedVisibilityChunk-&gt;Num() &gt;= CurrentCell.DataOffset + CurrentBucket.CellDataSize);\n                \/\/ Return a pointer to the cell containing ViewOrigin's decompressed visibility data\n                PrecomputedVisibilityData = &amp;(*CachedVisibilityChunk)[CurrentCell.DataOffset];\n            }\n        }\n    }\n}\nreturn PrecomputedVisibilityData;\n}\n<\/code><\/pre>\n<p>\u5982\u679c\u573a\u666f\u975e\u5e38\u5927\uff0c\u70d8\u7119\u51fa\u7684\u5355\u5143\u683c\u53ef\u80fd\u4f1a\u975e\u5e38\u591a\u3002\u5982\u679c\u6bcf\u4e00\u5e27\u90fd\u904d\u5386\u4e00\u4e2a\u51e0\u5341\u4e07\u957f\u5ea6\u7684\u6570\u7ec4\u6765\u5224\u65ad <code>CellBounds.IsInside(ViewOrigin)<\/code>\uff0cCPU \u4f1a\u6210\u4e3a\u74f6\u9888\u3002UE\u91c7\u7528\u7684\u65b9\u6848\u662f<strong>2d\u54c8\u5e0c\u5206\u6876<\/strong>\uff0c\u5229\u7528camera\u4e16\u754c\u5750\u6807\uff0c\u901a\u8fc7\u7b80\u5355\u7684\u9664\u6cd5\u548c\u53d6\u6a21\u8fd0\u7b97\uff0c\u4ee5<span class=\"katex math inline\">O(1)<\/span>\u65f6\u95f4\u590d\u6742\u5ea6\u5b9a\u4f4dcamera\u6240\u5728\u7684Bucket<\/p>\n<pre><code class=\"line-numbers\">\/\/ Calculate the bucket that ViewOrigin falls into\n\/\/ Cells are hashed into buckets to reduce search times\nconst float FloatOffsetX = (ViewOrigin.X - Handler.PrecomputedVisibilityCellBucketOriginXY.X) \/ Handler.PrecomputedVisibilityCellSizeXY;\n\/\/ FMath::TruncToInt rounds toward 0, we want to always round down\nconst int32 BucketIndexX = FMath::Abs((FMath::TruncToInt(FloatOffsetX) - (FloatOffsetX &lt; 0.0f ? 1 : 0)) \/ Handler.PrecomputedVisibilityCellBucketSizeXY % Handler.PrecomputedVisibilityNumCellBuckets);\nconst float FloatOffsetY = (ViewOrigin.Y -Handler.PrecomputedVisibilityCellBucketOriginXY.Y) \/ Handler.PrecomputedVisibilityCellSizeXY;\nconst int32 BucketIndexY = FMath::Abs((FMath::TruncToInt(FloatOffsetY) - (FloatOffsetY &lt; 0.0f ? 1 : 0)) \/ Handler.PrecomputedVisibilityCellBucketSizeXY % Handler.PrecomputedVisibilityNumCellBuckets);\nconst int32 PrecomputedVisibilityBucketIndex = BucketIndexY * Handler.PrecomputedVisibilityCellBucketSizeXY + BucketIndexX;\n<\/code><\/pre>\n<p>\u968f\u540e\u904d\u5386Bucket\u91cc\u7684cells\uff0c\u5e76\u8ba1\u7b97camera\u662f\u5426\u5728\u5f53\u524dcells\u4e2d,\u5728\u7684\u8bdd\u52a0\u8f7dbit mask\u8868<br \/>\n\u4f46\u5e76\u4e0d\u4f1a\u5168\u90e8\u52a0\u8f7d\uff0c\u800c\u662f\u4f1a\u5212\u5206\u4e00\u4e2a\u533a\u57df\uff08Chunk\uff09\uff0cBucket\u4f1a\u7ef4\u62a4\u6bcf\u4e2aChunk\u7684\u6570\u636e\uff0c\u5f53camera\u8fdb\u5165\u4e00\u4e2aChunk\u4f1a\u52a0\u8f7d\u5f53\u524dChunk\u7684\u6570\u636e\uff0c\u540e\u7eed\u79bb\u5f00\u8fd9\u4e2aChunk\u624d\u4f1a\u52a0\u8f7d\u65b0\u7684chunk\u6570\u636e\uff0c\u907f\u514d\u5185\u5b58\u7206\u70b8<\/p>\n<pre><code class=\"line-numbers\">if (CellBounds.IsInside(ViewOrigin))\n{\n\/\/ Reuse a cached decompressed chunk if possible\n\/\/ \u7f13\u5b58\u547d\u4e2d\u68c0\u6d4b\uff1a\u5982\u679c\u6444\u50cf\u673a\u8fd8\u5728\u4e0a\u4e00\u4e2a Chunk \u91cc\uff0c\u76f4\u63a5\u8fd4\u56de\u5185\u5b58\u6307\u9488\n  if (CachedVisibilityChunk\n      &amp;&amp; CachedVisibilityHandlerId == InScene-&gt;PrecomputedVisibilityHandler-&gt;GetId()\n      &amp;&amp; CachedVisibilityBucketIndex == PrecomputedVisibilityBucketIndex\n      &amp;&amp; CachedVisibilityChunkIndex == CurrentCell.ChunkIndex)\n  {\n      checkSlow(CachedVisibilityChunk-&gt;Num() &gt;= CurrentCell.DataOffset + CurrentBucket.CellDataSize);\n      PrecomputedVisibilityData = &amp;(*CachedVisibilityChunk)[CurrentCell.DataOffset];\n  }\n  else\n  {\n      const FCompressedVisibilityChunk&amp; CompressedChunk = Handler.PrecomputedVisibilityCellBuckets[PrecomputedVisibilityBucketIndex].CellDataChunks[CurrentCell.ChunkIndex];\n      CachedVisibilityBucketIndex = PrecomputedVisibilityBucketIndex;\n      CachedVisibilityChunkIndex = CurrentCell.ChunkIndex;\n      CachedVisibilityHandlerId = InScene-&gt;PrecomputedVisibilityHandler-&gt;GetId();\n\n    \/\/ \u7f13\u5b58\u672a\u547d\u4e2d\uff1a\u8de8\u8fc7\u4e86\u8fb9\u754c\uff0c\u9700\u8981\u89e3\u538b\u65b0\u7684 Chunk\n      if (CompressedChunk.bCompressed)\n      {\n          \/\/ Decompress the needed visibility data chunk\n          DecompressedVisibilityChunk.Reset();\n          DecompressedVisibilityChunk.AddUninitialized(CompressedChunk.UncompressedSize);\n          verify(FCompression::UncompressMemory(\n              NAME_Zlib, \n              DecompressedVisibilityChunk.GetData(),\n              CompressedChunk.UncompressedSize,\n              CompressedChunk.Data.GetData(),\n              CompressedChunk.Data.Num()));\n          CachedVisibilityChunk = &amp;DecompressedVisibilityChunk;\n      }\n      else\n      {\n          CachedVisibilityChunk = &amp;CompressedChunk.Data;\n      }\n\n      checkSlow(CachedVisibilityChunk-&gt;Num() &gt;= CurrentCell.DataOffset + CurrentBucket.CellDataSize);\n      \/\/ Return a pointer to the cell containing ViewOrigin's decompressed visibility data\n      PrecomputedVisibilityData = &amp;(*CachedVisibilityChunk)[CurrentCell.DataOffset];\n  }\n}\n<\/code><\/pre>\n<\/li>\n<\/ul>\n<h2>\u786c\u4ef6\u906e\u6321\u67e5\u8be2<\/h2>\n<p>\u786c\u4ef6\u906e\u6321\u5e76\u4e0d\u5728\u8fd9\u4e2apass\u6267\u884c\uff0c\u8fd9\u91cc\u53ea\u4f1a\u62ff\u53d6\u4e0a\u4e00\u5e27\u7684\u786c\u4ef6\u906e\u6321\u7ed3\u679c<\/p>\n<pre><code class=\"line-numbers\">SceneRenderer.WaitOcclusionTests(RHICmdList);\nif (ViewPacket.OcclusionCull.ContextIfParallel)\n{\n    ViewPacket.OcclusionCull.ContextIfParallel-&gt;Map(RHICmdList);\n}\n<\/code><\/pre>\n<h2>Frustum Cull &amp; Distance Cull<\/h2>\n<h3>\u516b\u53c9\u6811<\/h3>\n<pre><code class=\"line-numbers\">static void CullOctree(const FScene&amp; Scene, FViewInfo&amp; View, const FFrustumCullingFlags&amp; Flags, FSceneBitArray&amp; OutVisibleNodes, const FConvexVolume&amp; ViewCullingFrustum)\n{\n    TRACE_CPUPROFILER_EVENT_SCOPE(SceneVisibility_CullOctree);\n\n    \/\/ Two bits per octree node, 1st bit is Inside Frustum, 2nd bit is Outside Frustum\n    OutVisibleNodes.Init(false, Scene.PrimitiveOctree.GetNumNodes() * 2);\n\n    Scene.PrimitiveOctree.FindNodesWithPredicate(\n       [&amp;View, &amp;OutVisibleNodes, &amp;Flags, &amp;ViewCullingFrustum](FScenePrimitiveOctree::FNodeIndex ParentNodeIndex, FScenePrimitiveOctree::FNodeIndex NodeIndex, const FBoxCenterAndExtent&amp; NodeBounds)\n       {\n          \/\/ If the parent node is completely contained there is no need to test containment\n          if (ParentNodeIndex != INDEX_NONE &amp;&amp; !OutVisibleNodes[(ParentNodeIndex * 2) + 1])\n          {\n             OutVisibleNodes[NodeIndex * 2] = true;\n             OutVisibleNodes[NodeIndex * 2 + 1] = false;\n             return true;\n          }\n\n          const FPlane* PermutedPlanePtr = ViewCullingFrustum.PermutedPlanes.GetData();\n          bool bIntersects = false;\n\n          if (Flags.bUseFastIntersect)\n          {\n             bIntersects = IntersectBox8Plane(NodeBounds.Center, NodeBounds.Extent, PermutedPlanePtr);\n          }\n          else\n          {\n             bIntersects = ViewCullingFrustum.IntersectBox(NodeBounds.Center, NodeBounds.Extent);\n          }\n\n          if (bIntersects)\n          {\n             OutVisibleNodes[NodeIndex * 2] = true;\n             OutVisibleNodes[NodeIndex * 2 + 1] = ViewCullingFrustum.GetBoxIntersectionOutcode(NodeBounds.Center, NodeBounds.Extent).GetOutside();\n          }\n\n          return bIntersects;\n       },\n       [](FScenePrimitiveOctree::FNodeIndex \/*ParentNodeIndex*\/, FScenePrimitiveOctree::FNodeIndex \/*NodeIndex*\/, const FBoxCenterAndExtent&amp; \/*NodeBounds*\/)\n       {\n\n       });\n}\n<\/code><\/pre>\n<ul>\n<li>Two-Bit\u8bbe\u8ba1\n<p>\u4e3a\u4e86\u673a\u5236\u7684\u538b\u7f29\u5185\u5b58\u548c\u63d0\u9ad8\u7f13\u5b58\u547d\u4e2d\u7387\uff0cUE\u4e3a\u6bcf\u4e2a\u8282\u70b9\u5206\u914d2bit\uff0c\u4e00\u4e2a\u8868\u793a\u662f\u5426\u6709\u4efb\u4e00\u90e8\u5206\u5728\u89c6\u9525\u4f53\u5185\u90e8(Inside)\uff0c\u53e6\u4e00\u4e2a\u8868\u793a\u662f\u5426\u662f\u5426\u6709\u4efb\u4e00\u90e8\u5206\u5728\u89c6\u9525\u4f53\u5916\u90e8\uff08Outside\uff09<\/p>\n<ul>\n<li>Inside = false, Outside = false\uff1a\u5168\u5728\u5916\u9762<\/li>\n<li>Inside = true, Outside = true\uff1a\u8de8\u8d8a\u8fb9\u754c<\/li>\n<li>Inside = true, Outside = false\uff1a\u5b8c\u5168\u5305\u542b<\/li>\n<\/ul>\n<pre><code class=\"line-numbers\">\/\/ Two bits per octree node, 1st bit is Inside Frustum, 2nd bit is Outside Frustum\nOutVisibleNodes.Init(false, Scene.PrimitiveOctree.GetNumNodes() * 2);\n<\/code><\/pre>\n<\/li>\n<li>\u904d\u5386\u516b\u53c9\u6811\n<ul>\n<li>\u5224\u65ad\u5f53\u524dnode\u662f\u5426\u5b8c\u5168\u5728\u89c6\u9525\u4f53\u5185\u90e8\uff0c\u662f\u5219\u4e0d\u7ee7\u7eed\u5411\u4e0b\u904d\u5386<\/li>\n<\/ul>\n<pre><code class=\"line-numbers\">\/\/ If the parent node is completely contained there is no need to test containment\nif (ParentNodeIndex != INDEX_NONE &amp;&amp; !OutVisibleNodes[(ParentNodeIndex * 2) + 1])\n{\n    OutVisibleNodes[NodeIndex * 2] = true;\n    OutVisibleNodes[NodeIndex * 2 + 1] = false;\n    return true;\n}\n<\/code><\/pre>\n<ul>\n<li>\u5224\u65ad\u5f53\u524dnode\u662f\u5426\u4e0e\u89c6\u9525\u4f53\u76f8\u4ea4bIntersects<\/li>\n<\/ul>\n<pre><code class=\"line-numbers\">if (Flags.bUseFastIntersect)\n{\n    bIntersects = IntersectBox8Plane(NodeBounds.Center, NodeBounds.Extent, PermutedPlanePtr);\n}\nelse\n{\n    bIntersects = ViewCullingFrustum.IntersectBox(NodeBounds.Center, NodeBounds.Extent);\n}\n<\/code><\/pre>\n<ul>\n<li>\u8bbe\u7f6eInside\u3001Outside<\/li>\n<\/ul>\n<pre><code class=\"line-numbers\">if (bIntersects)\n{\n    OutVisibleNodes[NodeIndex * 2] = true;\n    OutVisibleNodes[NodeIndex * 2 + 1] = ViewCullingFrustum.GetBoxIntersectionOutcode(NodeBounds.Center, NodeBounds.Extent).GetOutside();\n}\n<\/code><\/pre>\n<p>Inside(NodeIndex * 2)\uff1a\u5982\u679c\u4e0e\u89c6\u9525\u4f53\u76f8\u4ea4(bIntersects = true)\uff0cInside\u4e00\u5b9a\u4e3atrue<\/p>\n<p>Outside(NodeIndex * 2 + 1)\uff1aGetBoxIntersectionOutcode\uff08\uff09\u5224\u65adbox\u662f\u5426\u6709\u90e8\u5206\u5728\u89c6\u9525\u4f53\u5916\uff0c\u662f\u5219true<\/p>\n<\/li>\n<li>\n<p>Frustum Cull<\/p>\n<ul>\n<li>\u5bf9\u4e8eAlways Visible\u7684\u7269\u4f53(\u5982skybox)\uff0c\u4e0d\u505a\u4efb\u4f55\u5254\u9664\uff0c\u76f4\u63a5\u66f4\u65b0<\/li>\n<\/ul>\n<pre><code class=\"line-numbers\">\/\/ Always Visible\nconst bool bHasAlwaysVisible = TaskConfig.NumVisiblePrimitives &gt; 0;\nif (bHasAlwaysVisible)\n{\n    const float CurrentWorldTime = View.Family-&gt;Time.GetWorldTimeSeconds();\n    for (uint32 TaskIndex = 0; TaskIndex &lt; TaskConfig.AlwaysVisible.NumTasks; ++TaskIndex)\n    {\n        Tasks.AlwaysVisible.AddPrerequisites(\n            UE::Tasks::Launch(UE_SOURCE_LOCATION, [this, Flags, TaskIndex, CurrentWorldTime]() mutable\n        {\n            TRACE_CPUPROFILER_EVENT_SCOPE(SceneVisibility_AlwaysVisible);\n            SCOPE_CYCLE_COUNTER(STAT_UpdateAlwaysVisible);\n\n            FTaskTagScope TaskTagScope(ETaskTag::EParallelRenderingThread);\n            UpdateAlwaysVisible(Scene, View, Flags, TaskConfig, TaskIndex, CurrentWorldTime);\n\n        }, PrerequisiteTask, TaskConfig.TaskPriority, UE::Tasks::EExtendedTaskPriority::None));\n    }\n}\n<\/code><\/pre>\n<ul>\n<li>Frustum Cull<\/p>\n<\/li>\n<li>\n<p>UE\u5c06Frustum Cull\u62c6\u5206\u6210\u591a\u4e2a\u5b50\u4efb\u52a1<\/p>\n<pre><code class=\"line-numbers\">for (uint32 TaskIndex = 0; TaskIndex &lt; TaskConfig.FrustumCull.NumTasks; ++TaskIndex)\n{\n  Tasks.FrustumCull.AddPrerequisites(\n      UE::Tasks::Launch(UE_SOURCE_LOCATION, [this, Flags, MaxDrawDistanceScale, HLODState, VisibleNodes, TaskIndex]() mutable\n  {\n      TRACE_CPUPROFILER_EVENT_SCOPE(SceneVisibility_FrustumCull);\n      FTaskTagScope TaskTagScope(ETaskTag::EParallelRenderingThread);\n      int32 NumCulledPrimitives = FrustumCull(Scene, View, Flags, MaxDrawDistanceScale, HLODState, VisibleNodes, TaskConfig, TaskIndex);\n\n      FPrimitiveRange PrimitiveRange;\n      PrimitiveRange.StartIndex = TaskConfig.FrustumCull.NumPrimitivesPerTask * (TaskIndex);\n      PrimitiveRange.EndIndex   = TaskConfig.FrustumCull.NumPrimitivesPerTask + PrimitiveRange.StartIndex;\n      PrimitiveRange.EndIndex   = FMath::Min(PrimitiveRange.EndIndex, int32(TaskConfig.NumTestedPrimitives));\n\n      \/\/ Skip rendering of dynamic objects without static lighting for static reflection captures.\n      if (View.bStaticSceneOnly)\n      {\n          for (FSceneSetBitIterator BitIt(View.PrimitiveVisibilityMap, PrimitiveRange.StartIndex); BitIt.GetIndex() &lt; PrimitiveRange.EndIndex; ++BitIt)\n          {\n              if (!Scene.PrimitiveSceneProxies[BitIt.GetIndex()]-&gt;HasStaticLighting())\n              {\n                  View.PrimitiveVisibilityMap.AccessCorrespondingBit(BitIt) = false;\n                  NumCulledPrimitives++;\n              }\n          }\n      }\n\n      \/\/ Skip rendering of small objects when in wireframe mode for performance since wireframe doesn't enable occlusion culling.\n      if (View.Family-&gt;EngineShowFlags.Wireframe)\n      {\n          const float ScreenSizeScale = FMath::Max(View.ViewMatrices.GetProjectionMatrix().M[0][0] * View.ViewRect.Width(), View.ViewMatrices.GetProjectionMatrix().M[1][1] * View.ViewRect.Height());\n\n          for (FSceneSetBitIterator BitIt(View.PrimitiveVisibilityMap, PrimitiveRange.StartIndex); BitIt.GetIndex() &lt; PrimitiveRange.EndIndex; ++BitIt)\n          {\n              if (ScreenSizeScale * Scene.PrimitiveBounds[BitIt.GetIndex()].BoxSphereBounds.SphereRadius &lt;= GWireframeCullThreshold)\n              {\n                  View.PrimitiveVisibilityMap.AccessCorrespondingBit(BitIt) = false;\n                  NumCulledPrimitives++;\n              }\n          }\n      }\n\n      const uint32 NumVisiblePrimitives = PrimitiveRange.EndIndex - PrimitiveRange.StartIndex - NumCulledPrimitives;\n\n      if (NumVisiblePrimitives == 0)\n      {\n          OcclusionCull.CommandPipe.ReleaseNumCommands(1);\n      }\n      else\n      {\n          OcclusionCull.CommandPipe.EnqueueCommand(PrimitiveRange);\n      }\n\n      TaskConfig.FrustumCull.NumCulledPrimitives.fetch_add(NumCulledPrimitives, std::memory_order_relaxed);\n\n  }, bHasAlwaysVisible ? Tasks.AlwaysVisible : PrerequisiteTask, TaskConfig.TaskPriority, ExtendedTaskPriority));\n}\n\n<\/code><\/pre>\n<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<h3>FrustumCull<\/h3>\n<ul>\n<li>UE\u4e0d\u662f\u9010\u4e2a\u904d\u5386Primitives\uff0c\u800c\u662f\u53cc\u5faa\u73af\n<ul>\n<li>\u7b2c\u4e00\u4e2a\u5faa\u73af<strong>VisWords<\/strong>(\u4e00\u4e2abit mask\uff0c0\u4e3a\u4e0d\u53ef\u89c1\uff0c1\u4e3a\u53ef\u89c1)\uff1aUE\u5e76\u4e0d\u662f\u5faa\u73afbitmask\uff0c\u800c\u662f\u5c06\u5176\u8f6c\u6210uint32\uff08\u53c8\u56e0\u4e3ac++\u4e2d\uff0c1 \u4e2a uint32 \u88ab\u79f0\u4e3a 1 \u4e2a \"Word\"\uff09<\/li>\n<\/ul>\n<pre><code class=\"line-numbers\">uint32* RESTRICT VisWords = View.PrimitiveVisibilityMap.GetData();\nfor (int32 WordIndex = TaskWordOffset; WordIndex &lt; TaskWordOffset + int32(TaskConfig.FrustumCull.NumWordsPerTask) &amp;&amp; WordIndex * NumBitsPerDWORD &lt; BitArrayNumInner; WordIndex++)\n{\n    if (!Flags.bShouldVisibilityCull)\n    {\n        VisBits = VisWords[WordIndex];\n    }\n}\n\n<\/code><\/pre>\n<p>\u7528\u4e8e\u5224\u65ad\uff0c\u662f\u5426\u9700\u8981cull\uff0c\u5982\u679c\u4e0d\u9700\u8981\uff0c\u76f4\u63a5\u62ff\u5230\u4e4b\u524d\u8ba1\u7b97\u7684\u53ef\u89c1\u6027<\/p>\n<ul>\n<li>\u7b2c\u4e8c\u4e2a\u5faa\u73afBitSubIndex\uff1a\u901a\u8fc7\u4f4d\u79fb\u64cd\u4f5c <code>Mask &lt;&lt;= 1<\/code>\uff0c\u9010\u4e2a\u68c0\u67e5\u8fd9 32 \u4e2a\u56fe\u5143<\/li>\n<\/ul>\n<pre><code class=\"line-numbers\">for (int32 BitSubIndex = 0; BitSubIndex &lt; NumBitsPerDWORD &amp;&amp; WordIndex * NumBitsPerDWORD + BitSubIndex &lt; BitArrayNumInner; BitSubIndex++, Mask &lt;&lt;= 1)\n{\n    \/\/...\n}\n<\/code><\/pre>\n<ul>\n<li>\u5982\u679c\u5f00\u542f\u4e86\u5254\u9664\uff08\u770b\u5148\u524dpass\u6709\u6ca1\u6709\u8ba1\u7b97\u8fc7\u906e\u6321\uff09\uff0c\u9ed8\u8ba4\u8bbe\u4e3a <code>true<\/code>\uff1b\u5982\u679c\u6ca1\u5f00\u542f\u5254\u9664\uff0c\u90a3\u5c31\u901a\u8fc7 <code>(VisBits &amp; Mask) == Mask<\/code> \u8bfb\u53d6\u4e0a\u4e00\u4e2apass\u4fdd\u5b58\u5728 <code>VisBits<\/code> \u91cc\u7684\u72b6\u6001\n<pre><code class=\"line-numbers\">int32 Index = WordIndex * NumBitsPerDWORD + BitSubIndex;\nbool bPrimitiveIsHidden = IsPrimitiveHidden(Scene, View, Index, Flags);\nbool bIsVisible = Flags.bShouldVisibilityCull ? true : (VisBits &amp; Mask) == Mask;\n\nbIsVisible = bIsVisible &amp;&amp; !bPrimitiveIsHidden;\n<\/code><\/pre>\n<\/li>\n<li>\u516b\u53c9\u6811\u67e5\u8be2\n<p>\u67e5\u8be2\u5f53\u524d\u7269\u4f53\u6240\u5728\u516b\u53c9\u6811\u7684\u8282\u70b9\u4e0e\u89c6\u9525\u4f53\u76f8\u4ea4\u3001\u8fd8\u662f\u88ab\u5305\u542b\u3001\u8fd8\u662f\u5728\u5916\u9762<\/p>\n<pre><code class=\"line-numbers\">if (Flags.bUseVisibilityOctree)\n{\n  \/\/ If the parent octree node was completely contained by the frustum, there is no need do an additional frustum test on the primitive bounds\n  \/\/ If the parent octree node is partially in the frustum, perform an additional test on the primitive bounds\n  uint32 OctreeNodeIndex = Scene.PrimitiveOctreeIndex[Index];\n\n  bIsVisible = (*VisibleNodes)[OctreeNodeIndex * 2];\n  bPartiallyOutside = (*VisibleNodes)[OctreeNodeIndex * 2 + 1];\n}\n<\/code><\/pre>\n<\/li>\n<li>\u5254\u9664\n<pre><code class=\"line-numbers\">if (bIsVisible)\n{\n  int32 VisibilityId = INDEX_NONE;\n\n  if (Flags.bUseCustomCulling &amp;&amp;\n      ((Scene.PrimitiveOcclusionFlags[Index] &amp; CustomVisibilityFlags) == CustomVisibilityFlags))\n  {\n      VisibilityId = Scene.PrimitiveSceneProxies[Index]-&gt;GetVisibilityId();\n  }\n\n  bIsVisible = !bPartiallyOutside || IsPrimitiveVisible(View, PermutedPlanePtr, ViewCullingFrustum, Bounds, VisibilityId, Flags);\n}\n<\/code><\/pre>\n<ul>\n<li>VisibilityId\uff1a\u9884\u8ba1\u7b97\u53ef\u89c6\u6027\u88ab\u5b8c\u5168\u906e\u6321\u7684id<\/p>\n<\/li>\n<li>\n<p>\u5224\u65ad\u5f53\u524d\u7269\u4f53\u662f\u5426\u5b8c\u5168\u5728\u89c6\u9525\u4f53\u5185\u90e8\uff0c\u662f\u5219bIsVisible = true\uff1b<\/p>\n<\/li>\n<\/ul>\n<p>\u5426\u5219\u6267\u884cIsPrimitiveVisible()\uff0c\u67e5\u8be2\u9884\u8ba1\u7b97\u53ef\u89c6\u6027\uff0c\u662f\u5426\u5b8c\u5168\u770b\u4e0d\u89c1\uff0c\u82e5\u662f\u5219\u5254\u9664<\/p>\n<p>\u5426\u5219\u5224\u65ad\u7269\u4f53\u662f\u5426\u5728\u89c6\u9525\u4f53\u516d\u4e2a\u9762\u5185\uff0c\u82e5\u90fd\u4e0d\u5728\uff0c\u5254\u9664<\/p>\n<pre><code class=\"line-numbers\">\/\/ Returns true if the frustum and bounds intersect\ninline bool IsPrimitiveVisible(FViewInfo&amp; View, const FPlane* PermutedPlanePtr, const FConvexVolume&amp; ViewCullingFrustum, const FPrimitiveBounds&amp; Bounds, int32 VisibilityId, FFrustumCullingFlags Flags)\n{\n    \/\/ The custom culling and sphere culling are additional tests, meaning that if they pass, the\n    \/\/ remaining culling tests will still be performed.  If any of the tests fail, then the primitive\n    \/\/ is culled, and the remaining tests do not need be performed\n\n    if (Flags.bUseCustomCulling &amp;&amp; !View.CustomVisibilityQuery-&gt;IsVisible(VisibilityId, FBoxSphereBounds(Bounds.BoxSphereBounds.Origin, Bounds.BoxSphereBounds.BoxExtent, Bounds.BoxSphereBounds.SphereRadius)))\n    {\n        return false;\n    }\n\n    if (Flags.bUseSphereTestFirst &amp;&amp; !ViewCullingFrustum.IntersectSphere(Bounds.BoxSphereBounds.Origin, Bounds.BoxSphereBounds.SphereRadius))\n    {\n        return false;\n    }\n\n    if (Flags.bUseFastIntersect)\n    {\n        return IntersectBox8Plane(Bounds.BoxSphereBounds.Origin, Bounds.BoxSphereBounds.BoxExtent, PermutedPlanePtr);\n    }\n    else\n    {\n        return ViewCullingFrustum.IntersectBox(Bounds.BoxSphereBounds.Origin, Bounds.BoxSphereBounds.BoxExtent);\n    }\n}\n<\/code><\/pre>\n<ul>\n<li>Distance cull<\/p>\n<\/li>\n<li>\n<p>\u8ba1\u7b97\u6700\u5927\u6e32\u67d3\u8ddd\u79bb<\/p>\n<pre><code class=\"line-numbers\">float MaxDrawDistance = Bounds.MaxCullDistance * MaxDrawDistanceScale;\n                    float MinDrawDistanceSq = FMath::Square(Bounds.MinDrawDistance * MaxDrawDistanceScale);\n<\/code><\/pre>\n<p>MaxDrawDistanceScale\uff1a\u6e38\u620f\u4e2d\u89c6\u91ce\u8ddd\u79bb\u8c03\u6574<\/p>\n<\/li>\n<li>\n<p>\u8ba1\u7b97\u4e2d\u5fc3\u70b9\u8ddd\u79bb\u6216\u8fb9\u7f18\u8ddd\u79bb<\/p>\n<pre><code class=\"line-numbers\">if (GDistanceCullToSphereEdge)\n{\n  ComputeDistances(Bounds, ViewOriginForDistanceCulling, ClosestDistSquared, FurthestDistSquared);\n}\nelse\n{\n  ClosestDistSquared = FurthestDistSquared = FVector::DistSquared(Bounds.BoxSphereBounds.Origin, ViewOriginForDistanceCulling);\n}\n<\/code><\/pre>\n<ul>\n<li>\u4e2d\u5fc3\u70b9\u6d4b\u8ddd\uff08\u9ed8\u8ba4\uff09\uff1a\u8ba1\u7b97camera\u5230\u7269\u4f53\u5305\u56f4\u76d2<strong>\u4e2d\u5fc3\u70b9<\/strong>\u7684\u8ddd\u79bb<br \/>\n\u5982\u679c\u662f\u957f\u57ce\u3001\u6cb3\u6d41\u8fd9\u79cd<strong>\u6781\u5176\u5de8\u5927<\/strong>\u7684\u5355\u4e00\u6a21\u578b\uff0c\u5b83\u7684\u4e2d\u5fc3\u70b9\u53ef\u80fd\u5728\u51e0\u516c\u91cc\u5916\uff0c\u5bfc\u81f4\u73a9\u5bb6\u660e\u660e\u5c31\u7ad9\u5728\u6a21\u578b\u8fb9\u7f18\uff0c\u6a21\u578b\u5374\u56e0\u4e3a\u201c\u4e2d\u5fc3\u70b9\u592a\u8fdc\u201d\u800c\u7a81\u7136\u6d88\u5931<\/li>\n<li>\u8fb9\u7f18\u6d4b\u8ddd\uff1a\u8ba1\u7b97camera\u5230\u5305\u56f4\u7403<strong>\u8fb9\u7f18<\/strong>\u7684\u6700\u8fd1\/\u6700\u8fdc\u8ddd\u79bb<\/li>\n<\/ul>\n<\/li>\n<li>Fade \u7f13\u51b2\n<pre><code class=\"line-numbers\">if (bHasMaxDrawDistance)\n{\n  float MaxFadeDistanceSquared = FMath::Square(MaxDrawDistance + FadeRadius);\n  float MinFadeDistanceSquared = FMath::Square(MaxDrawDistance - FadeRadius);\n  if ((ClosestDistSquared &lt; MaxFadeDistanceSquared &amp;&amp; ClosestDistSquared &gt; MinFadeDistanceSquared)\n      &amp;&amp; Scene.PrimitiveSceneProxies[Index]-&gt;IsUsingDistanceCullFade())  \/\/ Proxy call is intentionally behind the fade check to prevent an expensive memory read\n  {\n      FadingBits |= Mask;\n  }\n}\n<\/code><\/pre>\n<p>\u5f53\u7269\u4f53\u56e0\u4e3a\u8ddd\u79bb\u592a\u8fdc\u800c\u88ab\u5254\u9664\u65f6\uff0c\u76f4\u63a5\u6d88\u5931\u4f1a\u5f88\u786c\u3002UE\u5728\u8fd9\u91cc\u8ba1\u7b97 <code>FadeRadius<\/code> \u7f13\u51b2\u5e26<\/p>\n<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<h2>HIZ Culling<\/h2>\n<ul>\n<li>\n<p>\u4e3a\u4ec0\u4e48\u9700\u8981HIZ Culling<\/p>\n<p>\u4f20\u7edf\u7684\u786c\u4ef6\u906e\u6321\u5254\u9664\u4f1a\u67091\u5e27\u5ef6\u8fdf\uff0c\u4e3a\u4e86\u89e3\u51b3\u8fd9\u4e00\u5e27\u5ef6\u8fdf\uff0cHIZ Culling\u7531\u6b64\u8bde\u751f<\/p>\n<\/li>\n<li>\n<p>\u4ec0\u4e48\u662fHIZ Culling<\/p>\n<p>\u4e00\u79cdGPU Driven\u7684\u906e\u6321\u5254\u9664\uff0c\u4e0d\u518d\u7531\u786c\u4ef6\u8ba1\u7b97\uff0c\u800c\u662f\u7a0b\u5e8f\u5458\u624b\u52a8\u5b9e\u73b0<\/p>\n<\/li>\n<li>\n<p>\u7b97\u6cd5<\/p>\n<p>\u7531\u4e8eHIZ Culling\u662fGPU Culling\uff0c\u56e0\u6b64\u9700\u8981\u7b49\u5230CPU Culling\u5b8c\u5168\u63a5\u53d7\u540e\uff0cBase Pass\u5f00\u59cb\u524d\uff0c\u624d\u8fdb\u884c\uff0c\u4ee5\u6b64\u51cf\u5c11Base Pass Draw Call\u3002\u56e0\u6b64HIZ Culling\u6267\u884c\u65f6\u662f\u4e0d\u77e5\u9053\u5f53\u524d\u5e27\u7684\u6df1\u5ea6\u56fe\uff0c\u9700\u8981\u4f7f\u7528\u4e0a\u4e00\u5e27\u7684\u6df1\u5ea6\u56fe<\/p>\n<ul>\n<li>\u7b2c 1 \u904d\u5254\u9664\uff1aCompute Shader \u8bfb\u53d6<strong>\u4e0a\u4e00\u5e27<\/strong>\u7684 HZB \u6df1\u5ea6\u56fe\uff0c\u53bb\u6d4b\u8bd5\u5f53\u524d\u5e27\u7684\u5305\u56f4\u76d2<\/li>\n<\/ul>\n<p><em>\u5224\u5b9a\u53ef\u89c1\u7684\u7269\u4f53<\/em> -> \u76f4\u63a5\u5199\u5165 <code>IndirectArgsBuffer<\/code>\uff0c\u7acb\u523b\u8c03\u7528 <code>ExecuteIndirect<\/code> \u753b\u51fa\u6765<\/p>\n<p><em>\u5224\u5b9a\u88ab\u6321\u4f4f\u7684\u7269\u4f53<\/em> -> \u5b58\u5165 <code>OccludedList<\/code>\uff08\u88ab\u906e\u6321\u6e05\u5355\uff09\u7684\u7f13\u51b2\u533a\u91cc\u6682\u5b58<\/p>\n<ul>\n<li>\u7528\u7b2c\u4e00\u4e2apass\u5f97\u5230\u7684\u53ef\u89c1\u7269\u4f53\u6784\u5efa\u5f53\u524d\u5e27\u7684\u6df1\u5ea6mipmap\u56fe<\/p>\n<\/li>\n<li>\n<p>\u7b2c 2 \u904d\u5254\u9664\uff1a\u5f15\u64ce\u6d3e\u53d1\u7b2c\u4e8c\u4e2a Compute Shader\uff0c\u628a\u521a\u624d\u6682\u5b58\u5728 <code>OccludedList<\/code> \u91cc\u7684\u201c\u88ab\u6321\u4f4f\u7684\u7269\u4f53\u201d\u62ff\u51fa\u6765\uff0c\u7528<strong>\u5f53\u524d\u5e27\u7684 HZB<\/strong> \u518d\u6d4b\u4e00\u904d\uff01 \u5982\u679c\u53d1\u73b0\u6709\u7269\u4f53\u9732\u51fa\u6765\uff0c\u7acb\u523b\u628a\u5b83\u5199\u5165\u7b2c\u4e8c\u4e2a Draw Call \u961f\u5217\u753b\u51fa\u6765<\/p>\n<\/li>\n<\/ul>\n<p>\u4e5f\u8bb8\u4f60\u4f1a\u7591\u60d1\u4e3a\u4ec0\u4e48\u80fd\u8fd9\u4e48\u8bbe\u8ba1\uff1f\u5c24\u5176\u662f<strong>\u4e3a\u4ec0\u4e48\u53ef\u4ee5\u7528\u4e0a\u4e00\u5e27\u7684\u6df1\u5ea6\u56fe\u4e0e\u8fd9\u4e00\u9635\u7684BOX\u5206\u8fa8\u662f\u5426\u53ef\u89c1\uff0c\u5f97\u5230\u7684\u53ef\u89c1\u961f\u5217\u4e3a\u4ec0\u4e48\u53ef\u4ee5\u7528\u4e8e\u751f\u6210\u6df1\u5ea6\u56fe<\/strong>\u3002\u7b14\u8005\u4e5f\u5341\u5206\u7591\u60d1\uff0c\u4f46\u6df1\u601d\u540e\u60f3\u901a\u4e86\uff0c\u4e00\u4e2a\u7269\u4f53\u906e\u6321\u60c5\u51b5\u65e0\u975e\u4e09\u79cd\uff1a<\/p>\n<ul>\n<li>\u8fd9\u4e00\u5e27\u7684\u906e\u6321\u60c5\u51b5\u4e0e\u4e0a\u4e00\u5e27\u4e00\u81f4\uff1a\u8fd9\u79cd\u90fd\u4e0d\u9700\u8981\u989d\u5916\u5904\u7406<\/li>\n<li>\u4e0a\u4e00\u5e27\u88ab\u906e\u6321\uff0c\u8fd9\u4e00\u5e27\u6ca1\u88ab\u906e\u6321(\u8bef\u6740)\uff1a\u8fd9\u79cd\u60c5\u51b5\u4f1a\u88ab\u5217\u5165\u88ab\u906e\u6321\u6e05\u5355\uff0c\u4f46HZB\u7531<strong>\u5f53\u524d\u5e27\u7684\u53ef\u89c1\u7269\u4f53\u6784\u5efa<\/strong>\uff0c\u5728HZB\u4e2d\u5b83\u7684\u6df1\u5ea6\u66f4\u6df1\uff0c\u800c\u5728\u88ab\u906e\u6321\u6e05\u5355\u4e2d\u5b83\u7684\u6df1\u5ea6\u66f4\u6d45\uff0c\u56e0\u6b64\u4f1a\u7ea0\u6b63\u56de\u6765\u2014\u2014\u5373Pass3<\/li>\n<li>\u4e0a\u4e00\u5e27\u6ca1\u88ab\u906e\u6321\uff0c\u8fd9\u4e00\u5e27\u88ab\u906e\u6321\uff08\u6f0f\u7f51\u4e4b\u9c7c\uff09\uff1a\u8fd9\u79cd\u60c5\u51b5\u4f1a\u88ab\u5217\u5165\u53ef\u89c1\u540d\u5355\uff0c\u4f46\u53ef\u89c1\u540d\u5355\u4f1a\u5728\u540e\u7eedbase pass\u7684early-z\u518d\u6b21\u5224\u65ad\u906e\u6321\uff0c\u8fd9\u91cc\u6839\u636ez\u4f1a\u53d1\u73b0\u7269\u4f53\u662f\u88ab\u906e\u6321\u7684<\/li>\n<\/ul>\n<\/li>\n<li>UE\u5b9e\u73b0\n<p>UE\u4e2d\u7b49CPU Culling\u591a\u7ebf\u7a0b\u5b8c\u6210\u5e76\u6c47\u96c6\u540e\uff0c\u624d\u6267\u884cHIZ Culling<\/p>\n<pre><code class=\"line-numbers\">\/\/ Must happen after visibility state &amp; scene UB has been updated.\nInstanceCullingManager.BeginDeferredCulling(GraphBuilder);\n\nDeferredContext = FInstanceCullingContext::CreateDeferredContext(GraphBuilder, GPUScene, *this);\n<\/code><\/pre>\n<ul>\n<li>HIZ Culling Compute Shader<\/p>\n<\/li>\n<li>\n<p>InstanceCullBuildInstanceId\uff1a<\/p>\n<ul>\n<li>UE\u5e76\u6ca1\u6709\u8ba9\u7ebf\u7a0b ID \u76f4\u63a5\u7b49\u4e8e\u7269\u4f53 ID\uff0c\u5373\u6d3e\u53d1 <code>N \/ 64<\/code> \u4e2a Compute Shader \u7ebf\u7a0b\u7ec4\uff0c\u800c\u662f\u7528\u5230\u4e86\u590d\u6742\u7684Batch<br \/>\n\u8fd9\u662f\u56e0\u4e3abuffer\u91cc\u6240\u6709id\u5e76\u4e0d\u662f\u540c\u4e00\u79cd\u7269\u4f53\uff0c\u8fd9\u91cc\u9762\u5f88\u6709\u53ef\u80fd\u5305\u542b\u4e0d\u540c\u79cd\u7c7b\u7684\u7269\u4f53\uff08\u77f3\u5934\uff0c\u6811\u7b49\uff09\uff0c\u5982\u679c\u4e0dbatch\uff0c\u5c31\u9700\u8981\u5728GPU\u4e2d\u5224\u65ad\u5f53\u524d\u7ebf\u7a0b\u662f\u5c5e\u4e8e\u54ea\u79cd\u7269\u4f53\uff0c\u4f1a\u5bfc\u81f4\u4e25\u91cd\u7684\u5206\u652f\u53d1\u6563<\/li>\n<\/ul>\n<pre><code class=\"line-numbers\">uint DispatchGroupId = GetUnWrappedDispatchGroupId(GroupId);\n\n#if ENABLE_BATCH_MODE\n    \/\/ Load Instance culling context batch info, indirection per group\n    FContextBatchInfo BatchInfo = LoadBatchInfo(DispatchGroupId);\n#else \/\/ !ENABLE_BATCH_MODE\n    \/\/ Single Instance culling context batch in the call, set up batch from the kernel parameters\n    FContextBatchInfo BatchInfo = (FContextBatchInfo)0;\n    BatchInfo.NumViewIds = NumViewIds;\n    BatchInfo.DynamicInstanceIdOffset = DynamicInstanceIdOffset;\n    BatchInfo.DynamicInstanceIdMax = DynamicInstanceIdMax;\n    \/\/ Note: for the unbatched case, the permutation will control HZB test, so we set to true\n    BatchInfo.bAllowOcclusionCulling = true;\n#endif \/\/ ENABLE_BATCH_MODE\n\nFInstanceCullingSetup InstanceCullingSetup = LoadInstanceCullingSetup(GroupId, GroupThreadIndex, BatchInfo.DynamicInstanceIdOffset, BatchInfo.DynamicInstanceIdMax, GetItemDataOffset(BatchInfo, CurrentBatchProcessingMode));\n\n<\/code><\/pre>\n<ul>\n<li>\u89e3\u5305<\/li>\n<\/ul>\n<p>\u77e5\u9053\u81ea\u5df1\u8981\u5904\u7406\u54ea\u4e2a <code>InstanceId<\/code> \u540e\uff0c\u7ebf\u7a0b\u9700\u8981\u53bb\u5168\u5c40\u663e\u5b58\u91cc\u83b7\u53d6\u8fd9\u4e2a\u7269\u4f53\u7684\u4fe1\u606f<\/p>\n<pre><code class=\"line-numbers\">\/\/ Extract the draw command payload\nconst FInstanceCullingPayload Payload = LoadInstanceCullingPayload(WorkSetup.Item.Payload, BatchInfo);\n\n\/\/ Load auxiliary per-instanced-draw command info\nconst FDrawCommandDesc DrawCommandDesc = UnpackDrawCommandDesc(DrawCommandDescs[Payload.IndirectArgIndex]);\n\nconst FInstanceSceneData InstanceData = GetInstanceSceneData(InstanceId);\nconst FPrimitiveSceneData PrimitiveData = GetPrimitiveData(InstanceData.PrimitiveId);\n<\/code><\/pre>\n<ul>\n<li>Culling<\/li>\n<\/ul>\n<pre><code class=\"line-numbers\">bool bVisible = IsInstanceVisible(PrimitiveData, InstanceData, InstanceId, BatchInfo.ViewIdsOffset + ViewIdIndex, BatchInfo.bAllowOcclusionCulling, DrawCommandDesc, CullingFlags);\n<\/code><\/pre>\n<ul>\n<li>\u5254\u9664\u65e0\u6548\u3001\u7a7a\u7684\u5305\u56f4\u76d2\n<p>\u5982\u679c\u5b9e\u4f8b\u6570\u636e\u672c\u8eab\u88ab\u6807\u8bb0\u4e3a\u65e0\u6548\uff0c\u5305\u56f4\u76d2\u6ca1\u6709\u4f53\u79ef\uff0c\u90fd\u5e94\u8be5\u5254\u9664<\/p>\n<pre><code class=\"line-numbers\">if (!InstanceData.ValidInstance)\n{\n  return false;\n}\n\nif (dot(InstanceData.LocalBoundsExtent, InstanceData.LocalBoundsExtent) &lt;= 0.0f)\n{\n  return true;\n}\n<\/code><\/pre>\n<\/li>\n<li>\u7981\u7528WPO\n<p>\u7531\u4e8eWPO\u5728GPU\u4e0a\u8ba1\u7b97\u7279\u522b\u6602\u8d35\uff0c\u56e0\u6b64\u8fd9\u91cc\u9700\u8981\u770b<strong>\u5b9e\u4f8b\u7684\u8ddd\u79bb\u8d85\u51fa\u8bbe\u5b9a\u7684 WPO \u7981\u7528\u8ddd\u79bb\u6ca1\uff0c\u4e14\u6750\u8d28\u6ca1\u6709\u5f3a\u5236\u5f00\u542f WPO<\/strong>\uff0c\u662f\u5219\u7981\u7528WPO<\/p>\n<pre><code class=\"line-numbers\">if ((PrimitiveData.Flags &amp; PRIMITIVE_SCENE_DATA_FLAG_WPO_DISABLE_DISTANCE) == 0)\n{\n  return true;\n}\n<\/code><\/pre>\n<\/li>\n<li>\u5c4f\u5e55\u5c3a\u5bf8\u5254\u9664\n<p>\u57fa\u4e8e <code>MinScreenSize<\/code> \u548c <code>MaxScreenSize<\/code> \u68c0\u67e5\u7269\u4f53\u5728\u5c4f\u5e55\u4e0a\u7684\u6295\u5f71\u5360\u6bd4\u3002\u5982\u679c\u592a\u5c0f\uff0c\u8bf4\u660e\u4e0d\u503c\u5f97\u6e32\u67d3\uff0c\u5254\u9664<\/p>\n<pre><code class=\"line-numbers\">Cull.ScreenSize(DrawCommandDesc.MinScreenSize, DrawCommandDesc.MaxScreenSize);\n\nbool ScreenSize(float MinScreenSize, float MaxScreenSize)\n{\n  BRANCH\n  if (bIsVisible &amp;&amp; (MinScreenSize != MaxScreenSize || bMinScreenRadiusCull))\n  {\n      \/\/ Needs to match C++ logic in ComputeLODForMeshes() and ComputeBoundsScreenRadiusSquared() so that culling matches the submitted range of Lods.\n      \/\/ Differences to that code which shouldn't affect the result are:\n      \/\/ * ScreenMultiple doesn't include the factor of 0.5f, and so it doesn't need applying to the ScreenSize.\n      \/\/ * ScreenMultiple has the inverse LODDistanceScale baked in, and so it doesn't need applying to the ScreenSize.\n      float3 CenterTranslatedWorld = mul(float4(LocalBoxCenter, 1.0f), LocalToTranslatedWorld).xyz;\n      float InstanceDrawDistSq = length2(CenterTranslatedWorld - NaniteView.CullingViewOriginTranslatedWorld);\n      const float RadiusSq = length2( LocalBoxExtent * NonUniformScale.xyz );\n\n#if NANITE_CULLING_ENABLE_MIN_RADIUS_CULL\n      if (bMinScreenRadiusCull)\n      {\n          \/\/ implements: ScreenRadius &lt; MinRadius, where ScreenRadius = Radius \/ (LodDistanceFactor * Distance)\n          if (RadiusSq &lt; NaniteView.CullingViewMinRadiusTestFactorSq * InstanceDrawDistSq)\n          {\n              bIsVisible = false;\n              return bIsVisible;\n          }\n      }\n      \/\/ Only perform this test if enabled\n      if (MinScreenSize != MaxScreenSize)\n#endif\n      {\n          float ScreenSizeSq = NaniteView.CullingViewScreenMultipleSq * RadiusSq \/ max(InstanceDrawDistSq, 1.0f);\n          float MinScreenSizeSq = MinScreenSize * MinScreenSize;\n          float MaxScreenSizeSq = MaxScreenSize * MaxScreenSize;\n          bIsVisible = ScreenSizeSq &gt;= MinScreenSizeSq &amp;&amp; (MaxScreenSize == 0 || ScreenSizeSq &lt; MaxScreenSizeSq);\n      }\n  }\n\n  return bIsVisible;\n}\n<\/code><\/pre>\n<\/li>\n<li>\u5168\u5c40\u88c1\u526a\u5e73\u9762\n<p>\u5bf9\u4e8e\u67d0\u4e9b\u5e73\u9762\uff08\u5982\u53cd\u5c04\uff09\u6839\u672c\u4e0d\u4f1a\u6e32\u67d3base pass\u7684\uff0c\u8fd9\u79cd\u5e94\u8be5\u76f4\u63a5\u5254\u9664<\/p>\n<pre><code class=\"line-numbers\">Cull.GlobalClipPlane();\n\nvoid GlobalClipPlane()\n{\n#if USE_GLOBAL_CLIP_PLANE\n  BRANCH\n  if( bIsVisible )\n  {\n      \/\/ Prevent the result being \"intersecting\" when the global plane is invalid (effectively disabled). This prevents clusters that\n      \/\/ should rasterize in SW from being sent down the HW path\n      if (bSkipCullGlobalClipPlane || all(NaniteView.TranslatedGlobalClipPlane.xyz == (float3)0.0f))\n      {\n          return;\n      }\n\n      \/\/ Get the global clipping plane in local space (multiply by inverse transpose)\n      const float4 PlaneLocal = mul(LocalToTranslatedWorld, NaniteView.TranslatedGlobalClipPlane);\n\n      \/\/ AABB\/Plane intersection test\n      const float3 ScaledExtents = LocalBoxExtent * NonUniformScale.xyz;\n      const float ExtentAlongPlaneN = dot(abs(ScaledExtents * PlaneLocal.xyz), (float3)1.0f);\n      const float CenterDist = dot(PlaneLocal, float4(LocalBoxCenter, 1.0f));\n\n      if (CenterDist &lt; -ExtentAlongPlaneN)\n      {\n          bIsVisible = false;\n      }\n      else if (CenterDist &lt; ExtentAlongPlaneN)\n      {\n          bNeedsClipping = true;\n      }\n  }\n#endif\n}\n<\/code><\/pre>\n<\/li>\n<li>Frustum Culling\n<pre><code class=\"line-numbers\">BRANCH\nif( Cull.bIsVisible )\n{\n  Cull.Frustum();\n}\n<\/code><\/pre>\n<pre><code class=\"line-numbers\">FFrustumCullData Frustum()\n{\n  \/\/ Frustum test against current frame\n  FFrustumCullData FrustumCull = BoxCullFrustum( LocalBoxCenter, LocalBoxExtent, LocalToTranslatedWorld, NaniteView.TranslatedWorldToClip, NaniteView.ViewToClip, bIsOrtho, bNearClip, bSkipCullFrustum );\n  bIsVisible        = bIsVisible &amp;&amp; FrustumCull.bIsVisible;\n  bNeedsClipping    = bNeedsClipping || FrustumCull.bCrossesNearPlane || FrustumCull.bCrossesFarPlane;\n\n#if VIRTUAL_TEXTURE_TARGET\n  if (bIsVisible &amp;&amp; !bIsStaticGeometry)\n  {\n      bIsVisible = FrustumCull.RectMax.z &gt; DynamicDepthCullRange.x &amp;&amp; FrustumCull.RectMin.z &lt; DynamicDepthCullRange.y;\n  }\n#endif\n  return FrustumCull;\n}\n<\/code><\/pre>\n<pre><code class=\"line-numbers\">\/\/ Splitting the transform in two generates much better code on DXC when WorldToClip is scalar.\nFFrustumCullData BoxCullFrustum( float3 Center, float3 Extent, float4x4 LocalToWorld, float4x4 WorldToClip, float4x4 ViewToClip, bool bIsOrtho, bool bNearClip, bool bSkipFrustumCull )\n{\n\/\/ NOTE: We assume here that if near clipping is disabled the projection is orthographic, as disabling near clipping is\n\/\/ a feature for directional light shadows, and disabling near clipping for a perspective projection doesn't make much sense.\n\/\/ Checking both also serves to help out DCE when either is a compile-time constant.\ncheckSlow(bIsOrtho || bNearClip);\n\nif (bIsOrtho || !bNearClip)\n{\n    return BoxCullFrustumOrtho( Center, Extent, LocalToWorld, WorldToClip, bNearClip, bSkipFrustumCull );\n}\nelse\n{\n    return BoxCullFrustumPerspective( Center, Extent, LocalToWorld, WorldToClip, ViewToClip, bSkipFrustumCull );\n}\n}\n<\/code><\/pre>\n<pre><code class=\"line-numbers\">FFrustumCullData BoxCullFrustumPerspective(float3 Center, float3 Extent, float4x4 LocalToWorld, float4x4 WorldToClip, float4x4 ViewToClip, bool bSkipFrustumCull)\n{\n  FFrustumCullData Cull;\n\n  float4  DX = (2.0f * Extent.x) * mul(LocalToWorld[0], WorldToClip);\n  float4  DY = (2.0f * Extent.y) * mul(LocalToWorld[1], WorldToClip);\n\nfloat   MinW = +INFINITE_FLOAT;\nfloat   MaxW = -INFINITE_FLOAT;\n  float4  PlanesMin = 1.0f;\n\nCull.RectMin = float3(+1, +1, +1);\nCull.RectMax = float3(-1, -1, -1);\n\n\/\/ To discourage the compiler from overlapping the entire calculation, which uses an excessive number of VGPRs, the evaluation is split into 4 isolated passes with two corners per pass.\n\/\/ There seems to be no additional benefit from evaluating just one corner per pass and it prevents the use of fast min3\/max3 intrinsics.\n\n  #define EVAL_POINTS(PC0, PC1) \\\n      MinW            = min3(MinW, PC0.w, PC1.w); \\\n      MaxW            = max3(MaxW, PC0.w, PC1.w); \\\n      PlanesMin       = min3(PlanesMin, float4(PC0.xy, -PC0.xy) - PC0.w, float4(PC1.xy, -PC1.xy) - PC1.w); \\\n      float2 PS0      = PC0.xy \/ PC0.w; \\\n      float2 PS1      = PC1.xy \/ PC1.w; \\\n      Cull.RectMin.xy = min3(Cull.RectMin.xy, PS0, PS1); \\\n      Cull.RectMax.xy = max3(Cull.RectMax.xy, PS0, PS1);\n\n  float4 PC000, PC100;\nPLATFORM_SPECIFIC_ISOLATE\n  {\n      float4 DZ = (2.0f * Extent.z) * mul(LocalToWorld[2], WorldToClip);\n      PC000 = mul(mul(float4(Center - Extent, 1.0), LocalToWorld), WorldToClip);\n      PC100 = PC000 + DZ;\n      EVAL_POINTS(PC000, PC100);\n  }\n\n  float4 PC001, PC101;\nPLATFORM_SPECIFIC_ISOLATE\n  {\n      PC001 = PC000 + DX;\n      PC101 = PC100 + DX;\n      EVAL_POINTS(PC001, PC101);\n  }\n\n  float4 PC011, PC111;\nPLATFORM_SPECIFIC_ISOLATE\n  {\n      PC011 = PC001 + DY;\n      PC111 = PC101 + DY;\n      EVAL_POINTS(PC011, PC111);\n  }\n\n  float4 PC010, PC110;\nPLATFORM_SPECIFIC_ISOLATE\n  {\n      PC010 = PC011 - DX;\n      PC110 = PC111 - DX;\n      EVAL_POINTS(PC010, PC110);\n  }\n\n  #undef EVAL_POINTS\n\nfloat MinZ = MaxW * ViewToClip[2][2] + ViewToClip[3][2];\nfloat MaxZ = MinW * ViewToClip[2][2] + ViewToClip[3][2];\n\n\/\/ Near is z=1\nbool bInFrontNearPlane = MinW &lt;= MaxZ;\nbool bBehindNearPlane  = MaxW &gt;  MinZ;\n\n\/\/ Far is z=0\nbool bInFrontFarPlane = 0 &lt;  MaxZ;\nbool bBehindFarPlane  = 0 &gt;= MinZ;\n\nCull.bCrossesNearPlane  = bInFrontNearPlane;\nCull.bCrossesFarPlane   = bBehindFarPlane;\nCull.bIsVisible         = bBehindNearPlane &amp;&amp; bInFrontFarPlane;\n\nif (MinW &lt;= 0.0f &amp;&amp; MaxW &gt; 0.0f)\n{\n    Cull.RectMin = float3(-1, -1, -1);\n    Cull.RectMax = float3(+1, +1, +1);\n}\nelse\n{\n    Cull.RectMin.z = MinZ \/ MaxW;\n    Cull.RectMax.z = MaxZ \/ MinW;\n}\n\nCull.bFrustumSideCulled = false;\nif (!bSkipFrustumCull)\n{\n    const bool bFrustumCull = any(PlanesMin &gt; 0.0f);\n    Cull.bFrustumSideCulled = Cull.bIsVisible &amp;&amp; bFrustumCull;\n    Cull.bIsVisible = Cull.bIsVisible &amp;&amp; !bFrustumCull;\n}\n\n  return Cull;\n}\n\n<\/code><\/pre>\n<\/li>\n<li>\u786c\u4ef6\u4e0e HZB \u906e\u6321\u5254\u9664\n<p>\u5982\u679c\u7269\u4f53\u5728\u955c\u5934\u5185\uff0c\u4e14\u5f00\u542f\u4e86 <code>#if OCCLUSION_CULL_INSTANCES<\/code>\uff0c\u4ee3\u7801\u4f1a\u68c0\u67e5\u8be5\u7269\u4f53\u662f\u5426\u88ab\u524d\u65b9\u5176\u4ed6\u66f4\u5927\u7684\u7269\u4f53\u6321\u4f4f<\/p>\n<ul>\n<li>HZB Test\uff1a\u5373\u4e0a\u8ff0\u63d0\u5230\u7684\u7b2c\u4e8c\u4e2apass\u2014\u2014\u5c4f\u5e55\u77e9\u5f62\u4e0e HZB \u6df1\u5ea6\u56fe\u5bf9\u6bd4\uff0c\u5224\u65ad\u7269\u4f53\u662f\u5426\u88ab\u906e\u6321<\/li>\n<li>\u786c\u4ef6\u906e\u6321\u67e5\u8be2\u63a9\u7801\uff1a\u68c0\u67e5\u786c\u4ef6\u7ea7\u522b\u7684\u906e\u6321\u67e5\u8be2\u7f13\u51b2\u533a\uff0c\u786e\u4fdd\u8be5\u5b9e\u4f8b\u786e\u5b9e\u9700\u8981\u88ab\u6e32\u67d3<\/li>\n<\/ul>\n<pre><code class=\"line-numbers\">BRANCH\nif (Cull.bIsVisible &amp;&amp; bAllowOcclusionCulling)\n{\n  const bool bPrevIsOrtho = IsOrthoProjection(NaniteView.PrevViewToClip);\n  FFrustumCullData PrevCull = BoxCullFrustum(LocalBoundsCenter, LocalBoundsExtent, DynamicData.PrevLocalToTranslatedWorld, NaniteView.PrevTranslatedWorldToClip, NaniteView.PrevViewToClip, bPrevIsOrtho, Cull.bNearClip, true);\n  BRANCH\n  if (PrevCull.bIsVisible &amp;&amp; !PrevCull.bCrossesNearPlane)\n  {\n      FScreenRect PrevRect = GetScreenRect( NaniteView.HZBTestViewRect, PrevCull, 4 );\n      \/\/ Avoid cases where instance might self-occlude the HZB test due to minor precision differences\n      PrevRect.Depth = RoundUpF16(PrevRect.Depth);\n      Cull.bIsVisible = IsVisibleHZB( PrevRect, true );\n  }\n\n  BRANCH\n  if (NaniteView.InstanceOcclusionQueryMask &amp;&amp; Cull.bIsVisible)\n  {\n      if ((InstanceOcclusionQueryBuffer[InstanceId] &amp; NaniteView.InstanceOcclusionQueryMask) == 0)\n      {\n          Cull.bIsVisible = false;\n      }\n  }\n}\n<\/code><\/pre>\n<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<h1>\u603b\u7ed3<\/h1>\n<p>UE5\u7684Culling\u975e\u5e38\u5e9e\u5927\uff0c\u8bbe\u8ba1\u7684\u4e5f\u975e\u5e38\u4f18\u96c5\uff0c\u5927\u81f4\u5982\u4e0b\uff1a<\/p>\n<ol>\n<li>\u5b8f\u89c2\u8c03\u5ea6\uff1a<code>InitViews<\/code>\n<p>\u5728\u6b63\u5f0f\u6e32\u67d3\u4efb\u4f55\u50cf\u7d20\u4e4b\u524d\uff0c<code>InitViews<\/code> \u627f\u62c5\u6574\u4e2a\u7ba1\u7ebf\u7684\u51c6\u5907\u5de5\u4f5c<\/p>\n<\/li>\n<\/ol>\n<ul>\n<li><strong>\u89c6\u89d2\u4e0e\u77e9\u9635\u51c6\u5907\uff1a<\/strong> \u63d0\u53d6\u89c6\u9525\u4f53\u5e73\u9762\uff0c\u66f4\u65b0\u5e38\u91cf\u7f13\u51b2\u533a<\/li>\n<li><strong>\u56fe\u5143\u76f8\u5173\u6027\u8bc4\u4f30\uff08Relevance\uff09\uff1a<\/strong> \u5224\u65ad\u573a\u666f\u7269\u4f53\u6750\u8d28\u5c5e\u6027\uff08\u662f\u5426\u900f\u660e\u3001\u662f\u5426\u6295\u5c04\u9634\u5f71\u7b49\uff09<\/li>\n<li><strong>\u5e76\u53d1\u6267\u884c\uff1a<\/strong> \u5c06\u5254\u9664\u4efb\u52a1\u3001\u9aa8\u9abc\u52a8\u753b\u66f4\u65b0\u3001\u7c92\u5b50\u8ba1\u7b97\u7b49\u6781\u5176\u8017\u65f6\u7684\u64cd\u4f5c\uff0c\u6253\u5305\u6210\u4e00\u4e2a\u4e2a Task\uff0c\u6254\u7ed9\u7ebf\u7a0b\u6c60\u5f02\u6b65\u6267\u884c<\/li>\n<\/ul>\n<ol start=\"2\">\n<li>CPU \u7aef\u5254\u9664\uff1a\u7c97\u7c92\u5ea6\u7684\u5b8f\u89c2\u8fc7\u6ee4<\/li>\n<\/ol>\n<ul>\n<li>\u9884\u8ba1\u7b97\u53ef\u89c1\u6027\uff1a\u9488\u5bf9\u9759\u6001\u573a\u666f\u3002\u5c06\u5730\u56fe\u5212\u5206\u4e3a 3DCell\u5e76\u9884\u5148\u70d8\u7119\u53ef\u89c1\u6027\u3002\u4e3a\u4e86\u89e3\u51b3\u6d77\u91cf\u6570\u636e\u7684\u5bfb\u5740\u95ee\u9898\uff0cUE \u4f7f\u7528 <strong>2D \u54c8\u5e0c\u5206\u6876<\/strong> \u548c<strong>\u52a8\u6001\u6570\u636e\u5757\uff08Chunk\uff09<\/strong>\u52a0\u8f7d\uff0c\u5c06\u67e5\u8be2\u65f6\u95f4\u538b\u7f29\u81f3 <span class=\"katex math inline\">O(1)<\/span> \u7ea7\u522b<\/li>\n<li>\u516b\u53c9\u6811\u4e0e\u89c6\u9525\u4f53\u5254\u9664\uff1a\u573a\u666f\u7684\u516b\u53c9\u6811\u4f7f\u7528\u6781\u5ea6\u538b\u7f29\u7684 <strong>Two-Bit \u6807\u8bb0<\/strong>\uff08\u5b8c\u5168\u5305\u542b\/\u90e8\u5206\u76f8\u4ea4\uff09\u5feb\u901f\u5224\u65ad\u7269\u4f53\u662f\u5426\u5728\u955c\u5934\u89c6\u91ce\u5185<\/li>\n<li>\u8ddd\u79bb\u5254\u9664\uff1a\u6839\u636e\u7269\u4f53\u5305\u56f4\u76d2\u7684\u4e2d\u5fc3\u70b9\u6216\u8fb9\u7f18\u8ddd\u79bb\u5254\u9664\uff0c\u5e76\u5f15\u5165 <code>FadeRadius<\/code> \u7f13\u51b2\u5e26\uff0c\u9632\u6b62\u7269\u4f53\u7a81\u7136\u6d88\u5931\u5e26\u6765\u7684\u89c6\u89c9\u7a81\u53d8<\/li>\n<\/ul>\n<ol start=\"3\">\n<li>GPU \u7aef\u5254\u9664\uff1a\u7ec6\u7c92\u5ea6\u7684\u5fae\u89c2\u8fc7\u6ee4<\/li>\n<\/ol>\n<ul>\n<li>Two-Pass HZB Culling\uff1a\u89e3\u51b3\u786c\u4ef6\u906e\u6321\u67e5\u8be2\u7684\u201c\u4e00\u5e27\u5ef6\u8fdf\u201d<\/li>\n<li>\u989d\u5916\u5254\u9664\uff1a\u5c4f\u5e55\u5c3a\u5bf8\u5254\u9664\uff08\u592a\u5c0f\u7684\u4e0d\u753b\uff09\u3001\u5168\u5c40\u88c1\u526a\u5e73\u9762\u5254\u9664\uff0c\u9488\u5bf9 WPO\u6750\u8d28\u505a\u8ddd\u79bb\u5254\u9664<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>UE5 \u5305\u542b\u54ea\u4e9bCulling UE5\u7684culling\u540c\u65f6\u5305\u542bCPU\u7aef\u3001GPU\u7aefculling CPU\u7aef\u4f20\u7edf\u975e Nanite \u7269\u4f53 &#8230;<\/p>","protected":false},"author":1,"featured_media":448,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"_links":{"self":[{"href":"http:\/\/chenglixue.top\/index.php?rest_route=\/wp\/v2\/posts\/478"}],"collection":[{"href":"http:\/\/chenglixue.top\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/chenglixue.top\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/chenglixue.top\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/chenglixue.top\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=478"}],"version-history":[{"count":2,"href":"http:\/\/chenglixue.top\/index.php?rest_route=\/wp\/v2\/posts\/478\/revisions"}],"predecessor-version":[{"id":480,"href":"http:\/\/chenglixue.top\/index.php?rest_route=\/wp\/v2\/posts\/478\/revisions\/480"}],"wp:featuredmedia":[{"embeddable":true,"href":"http:\/\/chenglixue.top\/index.php?rest_route=\/wp\/v2\/media\/448"}],"wp:attachment":[{"href":"http:\/\/chenglixue.top\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=478"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/chenglixue.top\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=478"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/chenglixue.top\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=478"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}