从零向 UE 5.3 渲染管线插入一个 Mesh Pass

发布时间:2026/9/24 17:36:42

从零向 UE 5.3 渲染管线插入一个 Mesh Pass 从零向 UE 5.3 渲染管线插入一个 Mesh Pass前言这次实验会在 Base Pass 之后、光照之前把选中的 Mesh 再画一遍。预期效果很简单在UStaticMeshComponent的 Rendering 高级选项中勾选bRenderCustomMeshPass运行时模型会呈现红色。上一篇的 Screen Pass 处理的是二维纹理Mesh Pass 要走的路长得多先确定哪些 Primitive 与当前 View、当前 Pass 相关取得它们的FMeshBatch选择 Shader 和渲染状态最后才轮到 RDG 安排实际绘制。总览从 UPrimitiveComponent 到 FMeshDrawCommand先约定本文的术语Primitive指渲染场景中的基本对象游戏侧对应UPrimitiveComponentProxy专指FPrimitiveSceneProxyMesh Batch指FMeshBatch描述的一批几何Draw Command指FMeshDrawCommand。Actor 可以挂载UStaticMeshComponent、USkeletalMeshComponent等组件它们都继承自UPrimitiveComponent。Game Thread 管理组件的变换、材质和渲染选项但有组件不等于一定参与渲染组件得先满足加入渲染场景的条件CreateSceneProxy()也可能返回空指针。FScene::BatchAddPrimitives在 Game Thread 调用CreateSceneProxy()创建 Proxy并创建与之关联的FPrimitiveSceneInfo。Proxy 保存渲染所需的数据提供 Mesh 和 View RelevanceFPrimitiveSceneInfo是 Renderer 内部的场景记录保存StaticMeshes、缓存信息、场景索引和八叉树 ID 等。两者随后交给 Render Thread 维护的FScene。创建完成之后Game Thread 不会再去改已注册的 Proxy组件的变化通过渲染命令传递或者靠重建 Proxy 生效。一帧可能包含多个 View例如主视口、分屏视口或场景捕获。FSceneRenderer根据FSceneViewFamily组织渲染PC 延迟路径用的是FDeferredShadingSceneRenderer。渲染侧针对每个 View 做可见性处理综合视锥、距离、遮挡和 LOD 等条件决定需要哪些 PrimitiveProxy 的GetViewRelevance()返回FPrimitiveViewRelevance说明这个 Primitive 对当前 View 有哪些渲染用途。可见性处理由 Render Thread 发起也可能被拆成并行任务分发出去——第五步会回到这里。几何则以FMeshBatch的形式提供给网格绘制流程。一个FMeshBatch关联材质和 Vertex Factory包含一个或多个FMeshBatchElementVertex Factory 决定 Shader 如何读取顶点数据。静态提交路径通常在 Proxy 加入场景时通过DrawStaticElements保存FMeshBatch动态提交路径则按需通过GetDynamicMeshElements收集它。这里的「静态动态」说的是 Mesh Batch 的提交路径既不等同于组件类名也不等同于物体是否移动。Mesh Pass 规定这些 Mesh 怎样画。例如 Depth Pass 主要处理深度Base Pass 处理基础材质绘制。每个 Pass 的FMeshPassProcessor接收FMeshBatch选择 Shader 和渲染状态再构建FMeshDrawCommand有的 Pass 可以复用缓存命令有的则按帧构建。FMeshDrawCommand描述 Shader 绑定、管线状态和绘制参数本身不发起 GPU 绘制渲染器还要在对应阶段绑定目标并把命令记录到FRHICommandList。可参考下面的流程图理解上述过程本次实验要做的事情就是让UPrimitiveComponent声明自己要参与自定义 Mesh Pass然后在可见性阶段把它的FMeshBatch送给新的FMeshPassProcessor再把得到的FMeshDrawCommand安排到一帧中的合适位置绘制。第一步给 UPrimitiveComponent 添加开关在Engine/Source/Runtime/Engine/Classes/Components/PrimitiveComponent.h的 Rendering 属性附近新增UPROPERTY(EditAnywhere,AdvancedDisplay,BlueprintReadOnly,CategoryRendering,meta(ToolTipWhen true, will rendering in custom Mesh Pass))uint8 bRenderCustomMeshPass:1;第二步将开关复制到 FPrimitiveSceneProxy在Engine/Source/Runtime/Engine/Public/PrimitiveSceneProxy.h增加inlineboolShouldRenderInCustomMeshPass()const{returnbRenderCustomMeshPass;}// private:uint8 bRenderCustomMeshPass:1;然后在Engine/Source/Runtime/Engine/Private/PrimitiveSceneProxy.cpp的构造函数初始化列表中从组件复制这个值,bRenderCustomMeshPass(InComponent-bRenderCustomMeshPass)第三步在 FPrimitiveViewRelevance 中声明 Pass 相关性Proxy 上有了开关还不够。可见性处理还得知道这个 Primitive 对当前 View是不是与自定义 Pass 相关。为此在Engine/Source/Runtime/Engine/Public/PrimitiveViewRelevance.h添加/** The primitive should render to the custom Mesh Pass. */uint32 bRenderCustomMeshPass:1;再分别修改Engine/Source/Runtime/Engine/Private/StaticMeshRender.cpp的FStaticMeshSceneProxy::GetViewRelevance以及Engine/Source/Runtime/Engine/Private/SkeletalMesh.cpp的FSkeletalMeshSceneProxy::GetViewRelevanceResult.bRenderCustomMeshPassShouldRenderInCustomMeshPass();FPrimitiveViewRelevance不是组件上的一份永久「待绘制名单」而是针对当前 View 的一组判断这个 Primitive 是否有绘制相关性、走静态还是动态路径、是否包含半透明内容以及是否与特定 Pass 相关。新增的 bitbRenderCustomMeshPass把组件开关带进可见性流程它只表示「有资格进入自定义 Pass」不会跳过视锥、距离或遮挡判断也不保证最终一定产生 Draw Call。第四步Mesh Pass 枚举在Engine/Source/Runtime/Renderer/Public/MeshPassProcessor.h的EMeshPass::Type中加入MeshDecal,MustardCustomMeshPass,同一文件的GetMeshPassName补上对应名称并把断言里的 Pass 总数加一caseEMeshPass::MustardCustomMeshPass:returnTEXT(MustardCustomMeshPass);那个断言是static_assert(EMeshPass::Num 30)WITH_EDITOR下写成30 4这里两处都要改成31和31 4。另外Engine/Source/Runtime/Engine/Public/PSOPrecache.h里的MaxPSOCollectorCount也要由34改为35。PSO Collector 的 JumpTable 是以 Pass 序号为下标的定长数组FRegisterPSOCollectorCreateFunction会把(uint32)EMeshPass::X当作 Index 写进去Pass 多了一个表容量就得跟着涨一格。第五步收集 Pass 的 MeshBatch这一步有两个很容易混在一起的问题FMeshBatch是不是每帧都要重新收集FMeshDrawCommand是不是每帧都要重新构建。两者互相独立得分开看。在Engine/Source/Runtime/Renderer/Private/SceneVisibility.cppFRelevancePacket::ComputeRelevance会对候选 Primitive 调用 Proxy 的GetViewRelevance()拿到FPrimitiveViewRelevance。其中bStaticRelevance和bDynamicRelevance指示该 Proxy 在当前 View 走哪种 Mesh 提交路径bRenderCustomMeshPass则是我们新增的 Pass 相关性。一个 Primitive 可以同时具有静态与动态相关性所以别把ComputeRelevance当成只处理静态 Mesh 的函数。静态路径不会在这里生成FMeshBatch。Proxy 加入场景时DrawStaticElements已经把它提交并保存在FPrimitiveSceneInfo::StaticMeshes里了ComputeRelevance只是遍历这些FStaticMeshBatch结合当前 View 的 LOD 和其他条件挑出需要绘制的 Batch。为我们的 Pass 增加入口如下// ComputeRelevanceif(ViewRelevance.bRenderCustomMeshPass){DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex,PrimitiveSceneInfo,StaticMeshRelevance,StaticMesh,Scene,bCanCache,EMeshPass::MustardCustomMeshPass);}这里的StaticMesh是一个FStaticMeshBatchStaticMeshRelevance是与之对应的FStaticMeshBatchRelevance名字里的 Static 不特指UStaticMeshComponent更不代表 Draw Command 一定已经缓存。AddCommandsForMesh有两种结果满足缓存条件时把已经存在的FMeshDrawCommand选进当前 View 的可见命令列表不满足时只把这个 Batch 放进DynamicBuildRequests[Pass]留到本帧的 Pass 准备阶段再去构建命令。本文的 Pass 在第七步只注册了EMeshPassFlags::MainView没有EMeshPassFlags::CachedMeshCommands。所以哪怕bCanCache为真上面的调用也会走「登记构建请求」这一支。静态FMeshBatch可以复用不等于本 Pass 的FMeshDrawCommand可以复用。动态路径分两段。ComputeRelevance发现bDynamicRelevance后先把 Primitive 记进动态收集列表稍后GatherDynamicMeshElementsForPrimitive调用 Proxy 的GetDynamicMeshElements()为当前 View 收集本帧的FMeshBatch。有了这些 BatchFVisibilityTaskData::SetupMeshPasses才会逐个调用ComputeDynamicMeshRelevance依据先前的FPrimitiveViewRelevance设置FMeshPassMask。这里加的是// ComputeDynamicMeshRelevanceif(ViewRelevance.bRenderCustomMeshPass){PassMask.Set(EMeshPass::MustardCustomMeshPass);View.NumVisibleDynamicMeshElements[EMeshPass::MustardCustomMeshPass]NumElements;}PassMask.Set标记的是当前这一个动态FMeshBatch应该进入自定义 PassNumVisibleDynamicMeshElements的累加则供后面 Pass 准备阶段分配容量。注意这里没有调用AddCommandsForMesh动态 Batch 已经在View.DynamicMeshElements里了后续只要能按 Pass Mask 把它们挑出来直接交给本 Pass 的FMeshPassProcessor::AddMeshBatch就行。两条路径在FSceneRenderer::SetupMeshPass之后汇合Pass 准备阶段既读静态路径的构建请求也读动态路径的 Batch 和 Pass Mask然后调用FCustomMeshPassMeshProcessor::AddMeshBatch。到这一步才真正生成FMeshDrawCommand。拿本次实验里的不透明UStaticMeshComponent来说正常 View 下它一般是bStaticRelevance走左边的静态FMeshBatch路径但由于本 Pass 没启用命令缓存FMeshDrawCommand仍然在每帧的AddMeshBatch里生成。调试视图之类的特殊条件可能让同一个组件改走动态提交路径。决定走哪条路的是 Proxy 针对当前 View 返回的 Relevance既不是组件的 Mobility也不是bRenderCustomMeshPass这个开关。第六步Shader新建Engine/Shaders/Private/Mustard/CustomMeshPass.usf。与上一篇只写 Pixel Shader 不同这次要重新提交网格顶点Vertex Shader 必不可少。文件的核心部分如下#include ../Common.ush #include /Engine/Generated/Material.ush #include /Engine/Generated/VertexFactory.ush #if VERTEXSHADER void MainVS( FVertexFactoryInput Input, out float4 OutPosition : SV_POSITION #if INSTANCED_STEREO , out uint ViewportIndex : SV_ViewPortArrayIndex #endif ) { #if INSTANCED_STEREO ViewportIndex GetEyeIndexFromVF(Input); #endif ResolvedView ResolveViewFromVF(Input); FVertexFactoryIntermediates VFIntermediate GetVertexFactoryIntermediates(Input); float4 WorldPosition VertexFactoryGetWorldPosition(Input, VFIntermediate); float4 RasterizedWorldPosition VertexFactoryGetRasterizedWorldPosition(Input, VFIntermediate, WorldPosition); OutPosition mul(RasterizedWorldPosition, ResolvedView.TranslatedWorldToClip); } #endif #if PIXELSHADER void MainPS( in INPUT_POSITION_QUALIFIERS float4 SvPosition : SV_Position OPTIONAL_IsFrontFace OPTIONAL_OutDepthConservative , out float4 OutColor : SV_Target0) { OutColor float4(1.0, 0.0, 0.0, 1.0); } #endifFVertexFactoryInput不是某个固定的网格格式它由当前 Vertex Factory 的生成代码提供。VS 先通过 Vertex Factory 得到世界位置再变换到当前 View 的裁剪空间RasterizedWorldPosition用的是 Vertex Factory 为真实栅格化准备的位置。INSTANCED_STEREO分支负责给立体视图提供 Viewport Index。PS 暂时不读材质颜色只输出一个常量红色。SV_Target0表示第一个颜色目标后文会把它绑到 SceneColor。和全屏后处理不同这个 Pass 只会改那些被重绘 Mesh 覆盖、并且通过深度测试的像素。接着在Engine/Source/Runtime/Renderer/Private/Mustard/CustomMeshPassRendering.h声明两个 Shader 类型classFCustomMeshPassVS:publicFMeshMaterialShader{public:DECLARE_SHADER_TYPE(FCustomMeshPassVS,MeshMaterial);staticboolShouldCompilePermutation(constFMeshMaterialShaderPermutationParametersParameters){returnIsFeatureLevelSupported(Parameters.Platform,ERHIFeatureLevel::SM5)FMeshMaterialShader::ShouldCompilePermutation(Parameters);}FCustomMeshPassVS()default;FCustomMeshPassVS(constShaderMetaType::CompiledShaderInitializerTypeInitializer):FMeshMaterialShader(Initializer){}};classFCustomMeshPassPS:publicFMeshMaterialShader{public:DECLARE_SHADER_TYPE(FCustomMeshPassPS,MeshMaterial);staticboolShouldCompilePermutation(constFMeshMaterialShaderPermutationParametersParameters){returnFCustomMeshPassVS::ShouldCompilePermutation(Parameters);}FCustomMeshPassPS()default;FCustomMeshPassPS(constShaderMetaType::CompiledShaderInitializerTypeInitializer):FMeshMaterialShader(Initializer){}};并在同名.cpp里关联 Shader 路径与入口函数IMPLEMENT_MATERIAL_SHADER_TYPE(,FCustomMeshPassVS,TEXT(/Engine/Private/Mustard/CustomMeshPass.usf),TEXT(MainVS),SF_Vertex);IMPLEMENT_MATERIAL_SHADER_TYPE(,FCustomMeshPassPS,TEXT(/Engine/Private/Mustard/CustomMeshPass.usf),TEXT(MainPS),SF_Pixel);FGlobalShader适合上一篇那种不依附 Mesh 材质的全屏工作FMeshMaterialShader则进入「材质 × Vertex Factory」的 Shader 排列体系。即使 PS 目前只写常量色VS 仍然需要正确的顶点输入FCustomMeshPassMeshProcessor也得从材质和 Vertex Factory 里找到可用的 Shader。ShouldCompilePermutation决定编译哪些变体不负责每帧筛选可见 Primitive。第七步FMeshPassProcessor仍在CustomMeshPassRendering.h中声明FCustomMeshPassMeshProcessor。它是本 Pass 的命令生成器接收FMeshBatch选择 Shader 和渲染状态再调用基类逻辑构建FMeshDrawCommand。classFCustomMeshPassMeshProcessor:publicFMeshPassProcessor{public:FCustomMeshPassMeshProcessor(constFScene*Scene,ERHIFeatureLevel::Type InFeatureLevel,constFSceneView*InViewIfDynamicMeshCommand,FMeshPassDrawListContext*InDrawListContext);virtualvoidAddMeshBatch(constFMeshBatchRESTRICT MeshBatch,uint64 BatchElementMask,constFPrimitiveSceneProxy*RESTRICT PrimitiveSceneProxy,int32 StaticMeshId-1)overridefinal;protected:FMeshPassProcessorRenderState PassDrawRenderState;};FMeshPassDrawListContext负责接收构建好的命令InViewIfDynamicMeshCommand只在需要按 View 构建命令时才有值不保证始终存在。关键实现位于同名.cpp。先在构造函数里指定 Pass 身份和固定状态FCustomMeshPassMeshProcessor::FCustomMeshPassMeshProcessor(constFScene*Scene,ERHIFeatureLevel::Type InFeatureLevel,constFSceneView*InViewIfDynamicMeshCommand,FMeshPassDrawListContext*InDrawListContext):FMeshPassProcessor(EMeshPass::MustardCustomMeshPass,Scene,InFeatureLevel,InViewIfDynamicMeshCommand,InDrawListContext){PassDrawRenderState.SetBlendState(TStaticBlendState::GetRHI());PassDrawRenderState.SetDepthStencilState(TStaticDepthStencilStatefalse,CF_Equal::GetRHI());}false表示不写深度CF_Equal表示只保留与现有 SceneDepth 相等的片元。接着在AddMeshBatch里从FMeshBatch提供的材质代理和 Vertex Factory 选择 ShaderconstFMaterialRenderProxy*MaterialRenderProxyMeshBatch.MaterialRenderProxy;constFMaterialMaterialMaterialRenderProxy-GetMaterialWithFallback(FeatureLevel,MaterialRenderProxy);constFVertexFactory*VertexFactoryMeshBatch.VertexFactory;TMeshProcessorShadersFCustomMeshPassVS,FCustomMeshPassPSPassShaders;PassShaders.VertexShaderMaterial.GetShaderFCustomMeshPassVS(VertexFactory-GetType());PassShaders.PixelShaderMaterial.GetShaderFCustomMeshPassPS(VertexFactory-GetType());GetMaterialWithFallback取的是当前 Feature Level 可用的材质及其代理必要时会退到 fallback 材质VertexFactory-GetType()决定去查哪一组网格输入对应的 Shader。接下来算 Fill Mode、Cull Mode 和 Sort Key初始化FMeshMaterialShaderElementData最后进入 UE 的通用命令构建入口constFMeshDrawingPolicyOverrideSettings OverrideSettingsComputeMeshOverrideSettings(MeshBatch);constERasterizerFillMode MeshFillModeComputeMeshFillMode(Material,OverrideSettings);constERasterizerCullMode MeshCullModeComputeMeshCullMode(Material,OverrideSettings);FMeshMaterialShaderElementData ShaderElementData;ShaderElementData.InitializeMeshMaterialData(ViewIfDynamicMeshCommand,PrimitiveSceneProxy,MeshBatch,StaticMeshId,false);FMeshDrawCommandSortKey SortKeyCalculateMeshStaticSortKey(PassShaders.VertexShader,PassShaders.PixelShader);BuildMeshDrawCommands(MeshBatch,BatchElementMask,PrimitiveSceneProxy,*MaterialRenderProxy,Material,PassDrawRenderState,PassShaders,MeshFillMode,MeshCullMode,SortKey,EMeshPassFeatures::Default,ShaderElementData);BatchElementMask指定当前FMeshBatch里需要处理哪些FMeshBatchElementFill Mode、Cull Mode 保留材质和 Mesh 的栅格化约束ShaderElementData提供 Shader 绑定所需的 per-Mesh 数据BuildMeshDrawCommands把 Shader、资源绑定、管线状态和绘制参数组织成FMeshDrawCommand。它还不会调用图形 API排序、实例化和实际提交都在后面的流程里。当前的AddMeshBatch没有显式过滤半透明材质所以本文最后用不透明 Mesh 做测试。最后注册工厂让 UE 知道这个EMeshPass该创建哪个 ProcessorstaticFMeshPassProcessor*CreateCustomMeshPassMeshProcessor(ERHIFeatureLevel::Type InFeatureLevel,constFScene*Scene,constFSceneView*InViewIfDynamicMeshCommand,FMeshPassDrawListContext*InDrawListContext){constERHIFeatureLevel::Type FeatureLevelInViewIfDynamicMeshCommand?InViewIfDynamicMeshCommand-GetFeatureLevel():InFeatureLevel;returnnewFCustomMeshPassMeshProcessor(Scene,FeatureLevel,InViewIfDynamicMeshCommand,InDrawListContext);}FRegisterPassProcessorCreateFunctionRegisterCustomMeshPass(CreateCustomMeshPassMeshProcessor,EShadingPath::Deferred,EMeshPass::MustardCustomMeshPass,EMeshPassFlags::MainView);这相当于把「Deferred 路径 MustardCustomMeshPass」映射到处理器工厂。MainView表示它供主视图的 Mesh Pass 使用——对照FSceneRenderer::SetupMeshPass就能看到它正是按这个 flag 决定要不要给某个 Pass 创建 Processor 并启动准备流程的。当前没有注册EMeshPassFlags::CachedMeshCommands所以即使静态路径保存了FMeshBatch也不代表本 Pass 的FMeshDrawCommand会被缓存。可见性处理完成后FSceneRenderer::SetupMeshPass创建 ProcessorFParallelMeshDrawCommandPass::DispatchPassSetup启动 Pass 准备再由GenerateDynamicMeshDrawCommands对静态路径的构建请求和动态路径的 Mesh 调用AddMeshBatch。到这里UE 已经能为相关 Mesh 准备好本 Pass 的FMeshDrawCommand。第八步把 Pass 接进 RDG在CustomMeshPassRendering.cpp增加 Raster Pass 的参数结构BEGIN_SHADER_PARAMETER_STRUCT(FCustomMeshPassParameters,)SHADER_PARAMETER_STRUCT_INCLUDE(FViewShaderParameters,View)SHADER_PARAMETER_STRUCT_INCLUDE(FInstanceCullingDrawParams,InstanceCullingDrawParams)RENDER_TARGET_BINDING_SLOTS()END_SHADER_PARAMETER_STRUCT()View提供当前视图的 Shader 参数InstanceCullingDrawParams给 Mesh Draw Command Pass 的实例剔除和调度使用RENDER_TARGET_BINDING_SLOTS则让 RDG 知道颜色与深度附件。上一篇为了避免同一张纹理既当 SRV 又当 RTV额外插了一次拷贝这次不需要直接把模型画进当前 SceneColor。在Engine/Source/Runtime/Renderer/Private/DeferredShadingRenderer.h给FDeferredShadingSceneRenderer声明成员函数voidRenderCustomMeshPass(FRDGBuilderGraphBuilder,FRDGTextureRef SceneColorTexture,FRDGTextureRef SceneDepthTexture);定义仍写在CustomMeshPassRendering.cpp核心代码如下voidFDeferredShadingSceneRenderer::RenderCustomMeshPass(FRDGBuilderGraphBuilder,FRDGTextureRef SceneColorTexture,FRDGTextureRef SceneDepthTexture){for(int32 ViewIndex0;ViewIndexViews.Num();ViewIndex){FViewInfoViewViews[ViewIndex];FParallelMeshDrawCommandPassParallelMeshPassView.ParallelMeshDrawCommandPasses[EMeshPass::MustardCustomMeshPass];if(!View.ShouldRenderView()||!ParallelMeshPass.HasAnyDraw()){continue;}FCustomMeshPassParameters*PassParametersGraphBuilder.AllocParametersFCustomMeshPassParameters();PassParameters-ViewView.GetShaderParameters();PassParameters-RenderTargets[0]FRenderTargetBinding(SceneColorTexture,ERenderTargetLoadAction::ELoad);PassParameters-RenderTargets.DepthStencilFDepthStencilBinding(SceneDepthTexture,ERenderTargetLoadAction::ELoad,ERenderTargetLoadAction::ELoad,FExclusiveDepthStencil::DepthRead_StencilRead);ParallelMeshPass.BuildRenderingCommands(GraphBuilder,Scene-GPUScene,PassParameters-InstanceCullingDrawParams);GraphBuilder.AddPass(RDG_EVENT_NAME(CustomMeshPass),PassParameters,ERDGPassFlags::Raster,[this,View,ParallelMeshPass,PassParameters](FRHICommandListRHICmdList){SetStereoViewport(RHICmdList,View);ParallelMeshPass.DispatchDraw(nullptr,RHICmdList,PassParameters-InstanceCullingDrawParams);});}}这里有三个关键点。FParallelMeshDrawCommandPass是按 View 保存当前 Pass 的命令的所以要逐 View 检查HasAnyDraw()。颜色附件用ELoad保留已有的 SceneColor深度附件以DepthRead_StencilRead绑定正好对应前面 Processor 里「不写深度、只做 Equal 测试」的设置。BuildRenderingCommands准备 GPU Scene 和 Instance Culling 参数DispatchDraw则在 RDG Raster Pass 的执行回调里向FRHICommandList记录绘制。RDG 负责 Pass 依赖、资源生命周期和状态转换GraphBuilder.AddPass只是把工作加进帧图不表示 GPU 此时已经画完了。最后到Engine/Source/Runtime/Renderer/Private/DeferredShadingRenderer.cpp的FDeferredShadingSceneRenderer::Render。调用位于RenderBasePass及相关深度处理之后、AddSetupExposureIlluminancePass之前RenderCustomMeshPass(GraphBuilder,SceneTextures.Color.Target,SceneTextures.Depth.Target);// 随后仍由 UE 处理曝光准备、光照和后面的阶段。FRDGTextureRef ExposureIlluminanceSetupAddSetupExposureIlluminancePass(GraphBuilder,Views,SceneTextures);放在这里有两个前提Processor 用的是CF_Equal调用时必须有可比较的 SceneDepthBase Pass 跑完之后 SceneColor 也已经就绪这次重绘才有地方可写。不过还有一个坑。Deferred Base Pass 把材质信息写进了 GBuffer后续光照读的是 GBuffer我们的 Pass 只覆盖 SceneColor并没有同步改 GBuffer。所以屏幕上那块红色仍会受到后续光照、曝光与 Tonemapping 的影响最终像素不一定等于 PS 写出的(1.0, 0.0, 0.0)。这次实验只是演示自定义 Mesh Pass 的接入流程如果需要稳定的输出颜色插入时机和合成方式都得另做设计。参考剖析虚幻渲染体系03- 渲染机制 - 0向往0 - 博客园Mesh Drawing PipelineRender Dependency GraphFrameGraph|设计基于DX12实现 - 知乎
延伸阅读

更多相关文章

2026/9/24 17:36:42

`del`的核心功能是解除名称(name)与对象(object)之间的绑定关系

在Python编程中,del是一个常被初学者忽视但功能强大的语句。它不仅可以删除变量、列表元素、字典键值对,还能用于删除对象的属性甚至整个对象。深入理解del的工作机制,对于掌握Python内存管理、编写高效且安全的代码具有重要意义。 本报告将从…

2026/9/24 17:36:42

AI时代程序员效率跃迁:从返工少到快人一步

AI时代程序员效率提升完全指南:从小妙招到工具链本文整合了AI辅助开发技巧、程序员实用小妙招、高效工作方法和工具链推荐。核心观点只有一个:效率不是敲得快,而是返工少。适合所有想提升日常开发效率的程序员。一、先说个反直觉的事&#xf…

2026/9/24 18:46:47

Debian控制结构实战:用if/for/while/case打造高效运维脚本

聊 Debian 的 control structures(控制结构),很多人第一反应是语法:if 后面要加 then,for 循环里变量怎么取值,case 的右括号要不要双写。可我在 Debian 上写脚本写了这么多年,发现真正的门槛从…

2026/9/24 18:46:47

智能问答系统文件存储选型与MinIO集成实战指南

最近在做一个智能问答系统,文档上传和文件存储这条链路折腾了我好几周。最开始图省事,所有PDF、Word、图片直接落在本机磁盘,配个Nginx别名路径就完事。结果一上线问题全来了:预览打不开、文件下载到一半断线、多台机器部署后文件…

2026/9/24 18:46:47

CentOS 7上用Docker部署Redis与PostgreSQL完整指南

最近在测试环境要搭一套缓存加关系型数据库的组合,顺手把整个流程从零到一完整走了一遍。今天就以 CentOS 7 为底,把 Docker 装好,再用 Docker 把 Redis 和 PostgreSQL 跑起来。整个过程其实就是一条命令链,但中间值得注意的坑不少…

2026/9/24 18:46:47

Java Web原生实战:Servlet+JDBC手写学生管理系统

简介:本资源是一份面向Java Web初学者与课程设计实践者的完整学生信息管理系统项目,涵盖登录、学生增删改查、管理员密码修改等核心功能模块,适用于高校Java程序设计、Web开发或数据库应用类课程实训。压缩包共78个文件,包含16个J…

2026/9/24 18:46:47

few-shot-gaze复现全程指南:从数据预处理到MAML元学习视线估计

简介:面向计算机视觉与人机交互方向的毕业设计,整份源码包用于复现并优化 Seonwook Park 的 few-shot-gaze 项目,核心任务基于 MPIIFaceGaze 与 GazeCapture 两个公开数据集,解决少样本条件下的视线估计问题。压缩包内共有 93 个文…

2026/9/24 18:41:47

运维转网络安全:6-12个月落地转型路线图

写这篇文章之前,我先说个真实的感受。我身边不少做运维的朋友,干了三五年后都会冒出一个念头:“我是不是该转安全?”原因五花八门——有的是觉得运维天花板低,天天处理琐事;有的是看安全岗位薪资高、越老越…

2026/9/23 12:07:00

GAMP 5 基于风险的计算机化系统验证:软件分类与审计追踪实践

简介:《A Risk-Based Approach to Compliant GxP Computerized Systems》即业内熟知的GAMP 5指南,面向制药企业质量与IT合规人员、验证工程师及计算机化系统管理者,用于解决GxP法规环境下系统合规性难以科学落地的问题。文档以风险管理为主线…

2026/9/23 12:06:55

安全托管MSSP实战:从静态防御到人机协同的攻防运营与应急响应

简介:这份PPT围绕互联网业务安全托管服务展开,面向企业安全负责人、IT运维人员及关注MSSP/MSS选型的读者,重点回应传统安全过度依赖人工、碎片化静态防御难以对抗产业化攻击等痛点。资源共1个pptx文件,包体约30.63MB,以…

2026/9/24 0:00:21

基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程

简介:这是一套面向计算机、人工智能、自动化等专业学生与教师的毕业设计级项目资源,围绕YOLOv8实现渔船作业监控系统,可用于毕设、课程设计、大作业或项目立项演示。压缩包共97个文件,约24.21MB,以70个Python源码文件为…

2026/9/24 0:00:21

单细胞注释实战:基于Scanpy的标记基因与参考映射流程解析

简介:一份基于单细胞RNA测序数据的细胞类型注释算法研究Python毕业设计源码,针对计算机相关专业正在做毕设或需要项目实战的学习者,可用于课程设计与期末大作业。项目代码完整、经导师指导评审通过,可直接运行,覆盖数据…

2026/9/24 0:00:21

C#源生成器实战:用增量生成器替代反射,告别AOT崩溃

第一次在项目里被反射卡住,是在一个老旧的WinForms模块里:几十个类依赖PropertyChanged通知,运行时反射读属性、发通知,每次启动慢半拍不说,一上.NET Native/AOT裁剪模式几乎全面崩盘。后来我把这段逻辑全部改成C#源生…

2026/9/22 16:34:32

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/22 20:01:30

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/22 13:25:41

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码