UE5 Mesh Shader 学习笔记
目录Engine/Plugins/Developer/MeshShaderPersistentMesh/MeshShaderPersistentMesh.uplugin{ FileVersion: 3, Version: 1, VersionName: 1.0, FriendlyName: Mesh Shader Persistent Mesh, Category: Rendering, EnabledByDefault: false, CanContainContent: false, Modules: [ { Name: MeshShaderPersistentMesh, Type: Runtime, LoadingPhase: PostConfigInit } ] }Source/MeshShaderPersistentMesh/MeshShaderPersistentMesh.Build.csusing System.IO; using UnrealBuildTool; public class MeshShaderPersistentMesh : ModuleRules { public MeshShaderPersistentMesh(ReadOnlyTargetRules Target) : base(Target) { PrivateDependencyModuleNames.AddRange(new[] { Core, CoreUObject, Engine, Projects, RHI, RenderCore, Renderer }); PrivateIncludePaths.Add( Path.Combine(EngineDirectory, Source/Runtime/Renderer/Internal) ); } }Source/MeshShaderPersistentMesh/Private/MeshShaderPersistentMeshModule.h#pragma once #include Modules/ModuleManager.h class FMeshShaderPersistentMeshViewExtension; class FRealMeshGpuResource; class FMeshShaderPersistentMeshModule : public IModuleInterface { public: virtual void StartupModule() override; virtual void ShutdownModule() override; private: TSharedPtrFRealMeshGpuResource, ESPMode::ThreadSafe MeshResource; TSharedPtrFMeshShaderPersistentMeshViewExtension, ESPMode::ThreadSafe ViewExtension; };Source/MeshShaderPersistentMesh/Private/MeshShaderPersistentMeshModule.cpp#include MeshShaderPersistentMeshModule.h #include Interfaces/IPluginManager.h #include MeshShaderPersistentMeshViewExtension.h #include Misc/Paths.h #include RealMeshGpuResource.h #include RenderingThread.h #include SceneViewExtension.h #include ShaderCore.h void FMeshShaderPersistentMeshModule::StartupModule() { const FString PluginShaderDir FPaths::Combine(IPluginManager::Get().FindPlugin(TEXT(MeshShaderPersistentMesh))-GetBaseDir(), TEXT(Shaders)); AddShaderSourceDirectoryMapping(TEXT(/Plugin/MeshShaderPersistentMesh), PluginShaderDir); MeshResource CreateCubeMeshGpuResource(); BeginInitResource(MeshResource.Get()); ViewExtension FSceneViewExtensions::NewExtensionFMeshShaderPersistentMeshViewExtension(MeshResource.ToSharedRef()); } void FMeshShaderPersistentMeshModule::ShutdownModule() { ViewExtension.Reset(); if (MeshResource.IsValid()) { BeginReleaseResource(MeshResource.Get()); FlushRenderingCommands(); MeshResource.Reset(); } } IMPLEMENT_MODULE(FMeshShaderPersistentMeshModule, MeshShaderPersistentMesh)Source/MeshShaderPersistentMesh/Private/RealMeshGpuResource.h#pragma once #include RenderResource.h #include RHIResources.h struct FRealMeshVertex { FVector3f Position; FVector3f Normal; FVector2f UV; }; class FRealMeshGpuResource : public FRenderResource { public: void SetMeshData(TArrayFRealMeshVertex InVertices, TArrayuint32 InIndices); virtual void InitRHI(FRHICommandListBase RHICmdList) override; virtual void ReleaseRHI() override; bool IsReady() const; uint32 GetVertexCount() const { return VertexCount; } uint32 GetTriangleCount() const { return TriangleCount; } FShaderResourceViewRHIRef GetVertexBufferSRV() const { return VertexBufferSRV; } FShaderResourceViewRHIRef GetIndexBufferSRV() const { return IndexBufferSRV; } private: TArrayFRealMeshVertex CPUVertices; TArrayuint32 CPUIndices; FBufferRHIRef VertexBufferRHI; FBufferRHIRef IndexBufferRHI; FShaderResourceViewRHIRef VertexBufferSRV; FShaderResourceViewRHIRef IndexBufferSRV; uint32 VertexCount 0; uint32 TriangleCount 0; }; TSharedRefFRealMeshGpuResource, ESPMode::ThreadSafe CreateCubeMeshGpuResource();Source/MeshShaderPersistentMesh/Private/RealMeshGpuResource.cpp#include RealMeshGpuResource.h #include RHICommandList.h #include RHIResourceUtils.h void FRealMeshGpuResource::SetMeshData(TArrayFRealMeshVertex InVertices, TArrayuint32 InIndices) { CPUVertices MoveTemp(InVertices); CPUIndices MoveTemp(InIndices); } void FRealMeshGpuResource::InitRHI(FRHICommandListBase RHICmdList) { VertexCount CPUVertices.Num(); TriangleCount CPUIndices.Num() / 3; const EBufferUsageFlags VertexUsage EBufferUsageFlags::Static | EBufferUsageFlags::ShaderResource | EBufferUsageFlags::StructuredBuffer; const EBufferUsageFlags IndexUsage EBufferUsageFlags::Static | EBufferUsageFlags::ShaderResource | EBufferUsageFlags::StructuredBuffer; VertexBufferRHI UE::RHIResourceUtils::CreateBufferFromArray( RHICmdList, TEXT(PersistentRealMesh.VertexBuffer), VertexUsage, sizeof(FRealMeshVertex), CPUVertices.GetData(), CPUVertices.Num() * sizeof(FRealMeshVertex)); IndexBufferRHI UE::RHIResourceUtils::CreateBufferFromArray( RHICmdList, TEXT(PersistentRealMesh.IndexBuffer), IndexUsage, sizeof(uint32), CPUIndices.GetData(), CPUIndices.Num() * sizeof(uint32)); VertexBufferSRV RHICmdList.CreateShaderResourceView( VertexBufferRHI, FRHIViewDesc::CreateBufferSRV() .SetType(FRHIViewDesc::EBufferType::Structured) .SetStride(sizeof(FRealMeshVertex))); IndexBufferSRV RHICmdList.CreateShaderResourceView( IndexBufferRHI, FRHIViewDesc::CreateBufferSRV() .SetType(FRHIViewDesc::EBufferType::Structured) .SetStride(sizeof(uint32))); } void FRealMeshGpuResource::ReleaseRHI() { VertexBufferSRV.SafeRelease(); IndexBufferSRV.SafeRelease(); VertexBufferRHI.SafeRelease(); IndexBufferRHI.SafeRelease(); } bool FRealMeshGpuResource::IsReady() const { return VertexBufferSRV.IsValid() IndexBufferSRV.IsValid() VertexCount 0 TriangleCount 0; } TSharedRefFRealMeshGpuResource, ESPMode::ThreadSafe CreateCubeMeshGpuResource() { TArrayFRealMeshVertex Vertices; TArrayuint32 Indices; auto AddFace [Vertices, Indices]( const FVector3f A, const FVector3f B, const FVector3f C, const FVector3f D, const FVector3f Normal) { const uint32 BaseIndex Vertices.Num(); Vertices.Add({ A, Normal, FVector2f(0.0f, 0.0f) }); Vertices.Add({ B, Normal, FVector2f(1.0f, 0.0f) }); Vertices.Add({ C, Normal, FVector2f(1.0f, 1.0f) }); Vertices.Add({ D, Normal, FVector2f(0.0f, 1.0f) }); Indices.Add(BaseIndex 0); Indices.Add(BaseIndex 1); Indices.Add(BaseIndex 2); Indices.Add(BaseIndex 0); Indices.Add(BaseIndex 2); Indices.Add(BaseIndex 3); }; const float S 50.0f; const FVector3f LBN(-S, -S, -S); const FVector3f RBN(S, -S, -S); const FVector3f RTN(S, S, -S); const FVector3f LTN(-S, S, -S); const FVector3f LBF(-S, -S, S); const FVector3f RBF(S, -S, S); const FVector3f RTF(S, S, S); const FVector3f LTF(-S, S, S); AddFace(LBN, RBN, RTN, LTN, FVector3f(0, 0, -1)); AddFace(LBF, LTF, RTF, RBF, FVector3f(0, 0, 1)); AddFace(LBN, LBF, RBF, RBN, FVector3f(0, -1, 0)); AddFace(RBN, RBF, RTF, RTN, FVector3f(1, 0, 0)); AddFace(RTN, RTF, LTF, LTN, FVector3f(0, 1, 0)); AddFace(LTN, LTF, LBF, LBN, FVector3f(-1, 0, 0)); TSharedRefFRealMeshGpuResource, ESPMode::ThreadSafe Resource MakeSharedFRealMeshGpuResource, ESPMode::ThreadSafe(); Resource-SetMeshData(MoveTemp(Vertices), MoveTemp(Indices)); return Resource; }Source/MeshShaderPersistentMesh/Private/MeshShaderPersistentMeshViewExtension.h#pragma once #include SceneViewExtension.h class FRealMeshGpuResource; class FMeshShaderPersistentMeshViewExtension : public FSceneViewExtensionBase { public: FMeshShaderPersistentMeshViewExtension( const FAutoRegister AutoRegister, TSharedRefFRealMeshGpuResource, ESPMode::ThreadSafe InMeshResource); virtual bool IsActiveThisFrame_Internal(const FSceneViewExtensionContext Context) const override; virtual void PrePostProcessPass_RenderThread( FRDGBuilder GraphBuilder, const FSceneView InView, const FPostProcessingInputs Inputs) override; private: TSharedRefFRealMeshGpuResource, ESPMode::ThreadSafe MeshResource; };Source/MeshShaderPersistentMesh/Private/MeshShaderPersistentMeshViewExtension.cpp#include MeshShaderPersistentMeshViewExtension.h #include DataDrivenShaderPlatformInfo.h #include GlobalShader.h #include PipelineStateCache.h #include PostProcess/PostProcessInputs.h #include RealMeshGpuResource.h #include RenderGraphBuilder.h #include RHIStaticStates.h #include ShaderParameterStruct.h class FPersistentRealMeshMS : public FGlobalShader { DECLARE_GLOBAL_SHADER(FPersistentRealMeshMS); SHADER_USE_PARAMETER_STRUCT(FPersistentRealMeshMS, FGlobalShader); BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_SRV(StructuredBufferFRealMeshVertex, VertexBuffer) SHADER_PARAMETER_SRV(StructuredBufferuint, IndexBuffer) SHADER_PARAMETER(FMatrix44f, LocalToClip) SHADER_PARAMETER(FVector3f, Tint) SHADER_PARAMETER(uint32, VertexCount) SHADER_PARAMETER(uint32, TriangleCount) END_SHADER_PARAMETER_STRUCT() static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters Parameters) { return FDataDrivenShaderPlatformInfo::GetSupportsMeshShadersTier0(Parameters.Platform); } static void ModifyCompilationEnvironment( const FGlobalShaderPermutationParameters Parameters, FShaderCompilerEnvironment OutEnvironment) { FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment); OutEnvironment.CompilerFlags.Add(CFLAG_HLSL2021); } }; class FPersistentRealMeshPS : public FGlobalShader { DECLARE_GLOBAL_SHADER(FPersistentRealMeshPS); SHADER_USE_PARAMETER_STRUCT(FPersistentRealMeshPS, FGlobalShader); BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) END_SHADER_PARAMETER_STRUCT() }; IMPLEMENT_GLOBAL_SHADER( FPersistentRealMeshMS, /Plugin/MeshShaderPersistentMesh/Private/MeshShaderPersistentMesh.usf, MainMS, SF_Mesh); IMPLEMENT_GLOBAL_SHADER( FPersistentRealMeshPS, /Plugin/MeshShaderPersistentMesh/Private/MeshShaderPersistentMesh.usf, MainPS, SF_Pixel); BEGIN_SHADER_PARAMETER_STRUCT(FPersistentRealMeshPassParameters, ) SHADER_PARAMETER_STRUCT_INCLUDE(FPersistentRealMeshMS::FParameters, MS) SHADER_PARAMETER_STRUCT_INCLUDE(FPersistentRealMeshPS::FParameters, PS) RENDER_TARGET_BINDING_SLOTS() END_SHADER_PARAMETER_STRUCT() FMeshShaderPersistentMeshViewExtension::FMeshShaderPersistentMeshViewExtension( const FAutoRegister AutoRegister, TSharedRefFRealMeshGpuResource, ESPMode::ThreadSafe InMeshResource) : FSceneViewExtensionBase(AutoRegister) , MeshResource(InMeshResource) { } bool FMeshShaderPersistentMeshViewExtension::IsActiveThisFrame_Internal(const FSceneViewExtensionContext Context) const { return GRHISupportsMeshShadersTier0; } void FMeshShaderPersistentMeshViewExtension::PrePostProcessPass_RenderThread( FRDGBuilder GraphBuilder, const FSceneView InView, const FPostProcessingInputs Inputs) { if (!GRHISupportsMeshShadersTier0 || !MeshResource-IsReady() || Inputs.ViewFamilyTexture nullptr) { return; } FPersistentRealMeshPassParameters* PassParameters GraphBuilder.AllocParametersFPersistentRealMeshPassParameters(); PassParameters-MS.VertexBuffer MeshResource-GetVertexBufferSRV(); PassParameters-MS.IndexBuffer MeshResource-GetIndexBufferSRV(); PassParameters-MS.VertexCount MeshResource-GetVertexCount(); PassParameters-MS.TriangleCount MeshResource-GetTriangleCount(); PassParameters-MS.Tint FVector3f(0.1f, 0.7f, 1.0f); const FVector ViewOrigin InView.ViewMatrices.GetViewOrigin(); const FVector ViewForward InView.GetViewDirection(); const FVector MeshWorldCenter ViewOrigin ViewForward * 300.0; const FMatrix LocalToWorld FTranslationMatrix(MeshWorldCenter); const FMatrix LocalToClip LocalToWorld * InView.ViewMatrices.GetViewProjectionMatrix(); PassParameters-MS.LocalToClip FMatrix44f(LocalToClip); PassParameters-RenderTargets[0] FRenderTargetBinding(Inputs.ViewFamilyTexture, ERenderTargetLoadAction::ELoad); const FGlobalShaderMap* ShaderMap GetGlobalShaderMap(InView.GetFeatureLevel()); TShaderMapRefFPersistentRealMeshMS MeshShader(ShaderMap); TShaderMapRefFPersistentRealMeshPS PixelShader(ShaderMap); const FIntRect PassViewRect InView.UnscaledViewRect; GraphBuilder.AddPass( RDG_EVENT_NAME(MeshShaderPersistentMesh), PassParameters, ERDGPassFlags::Raster | ERDGPassFlags::NeverCull, [PassParameters, MeshShader, PixelShader, PassViewRect]( FRDGAsyncTask, FRHICommandList RHICmdList) { FGraphicsPipelineStateInitializer GraphicsPSOInit; RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit); RHICmdList.SetViewport( PassViewRect.Min.X, PassViewRect.Min.Y, 0.0f, PassViewRect.Max.X, PassViewRect.Max.Y, 1.0f); GraphicsPSOInit.PrimitiveType PT_TriangleList; GraphicsPSOInit.RasterizerState TStaticRasterizerStateFM_Solid, CM_None::GetRHI(); GraphicsPSOInit.DepthStencilState TStaticDepthStencilStatefalse, CF_Always::GetRHI(); GraphicsPSOInit.BlendState TStaticBlendStateCW_RGBA, BO_Add, BF_SourceAlpha, BF_InverseSourceAlpha::GetRHI(); GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI nullptr; GraphicsPSOInit.BoundShaderState.SetMeshShader(MeshShader.GetMeshShader()); GraphicsPSOInit.BoundShaderState.PixelShaderRHI PixelShader.GetPixelShader(); SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit, 0); SetShaderParameters( RHICmdList, MeshShader, MeshShader.GetMeshShader(), PassParameters-MS); SetShaderParameters( RHICmdList, PixelShader, PixelShader.GetPixelShader(), PassParameters-PS); RHICmdList.DispatchMeshShader(1, 1, 1); }); }Shaders/Private/MeshShaderPersistentMesh.usf#include /Engine/Public/Platform.ush struct FRealMeshVertex { float3 Position; float3 Normal; float2 UV; }; StructuredBufferFRealMeshVertex VertexBuffer; StructuredBufferuint IndexBuffer; float4x4 LocalToClip; float3 Tint; uint VertexCount; uint TriangleCount; struct FMeshOut { float4 Position : SV_Position; float3 Normal : TEXCOORD0; float2 UV : TEXCOORD1; float3 Color : COLOR0; }; MESH_SHADER_TRIANGLE_ATTRIBUTES(32) void MainMS( uint GroupIndex : SV_GroupIndex, MESH_SHADER_VERTEX_EXPORT(FMeshOut, 64), MESH_SHADER_TRIANGLE_EXPORT(64)) { SetMeshOutputCounts(VertexCount, TriangleCount); if (GroupIndex VertexCount) { FRealMeshVertex V VertexBuffer[GroupIndex]; FMeshOut OutV; OutV.Position mul(float4(V.Position, 1.0f), LocalToClip); OutV.Normal V.Normal; OutV.UV V.UV; OutV.Color Tint * (0.35f 0.65f * abs(V.Normal.z)); MESH_SHADER_WRITE_VERTEX(GroupIndex, OutV); } if (GroupIndex TriangleCount) { uint BaseIndex GroupIndex * 3; uint3 Tri uint3( IndexBuffer[BaseIndex 0], IndexBuffer[BaseIndex 1], IndexBuffer[BaseIndex 2]); MESH_SHADER_WRITE_TRIANGLE(GroupIndex, Tri); } } float4 MainPS(FMeshOut Input) : SV_Target0 { float3 N normalize(Input.Normal); float3 L normalize(float3(0.4f, -0.3f, 0.8f)); float Lighting saturate(dot(N, L)) * 0.7f 0.3f; return float4(Input.Color * Lighting, 0.85f); }从RealMeshGpuResource.h开始看FBufferRHIRef 是“数据本体” FShaderResourceViewRHIRef 是“shader 怎么读这份数据”RealMeshGpuResource.cppconst EBufferUsageFlags VertexUsage EBufferUsageFlags::Static | EBufferUsageFlags::ShaderResource;定义 vertex buffer 的用途。Static表示这个 buffer 创建后基本不频繁修改适合静态网格。ShaderResource表示这个 buffer 会被 shader 读取因为 mesh shader 里要这样读StructuredBufferFRealMeshVertex VertexBuffer;所以必须带ShaderResource。VertexBufferRHI UE::RHIResourceUtils::CreateBufferFromArray( RHICmdList, TEXT(PersistentRealMesh.VertexBuffer), VertexUsage, sizeof(FRealMeshVertex), CPUVertices.GetData(), CPUVertices.Num() * sizeof(FRealMeshVertex));开始创建 GPU vertex buffer。返回值存在VertexBufferRHI它代表 GPU 上那块真实 buffer。VertexBufferSRV RHICmdList.CreateShaderResourceView( VertexBufferRHI, FRHIViewDesc::CreateBufferSRV() .SetType(FRHIViewDesc::EBufferType::Structured) .SetStride(sizeof(FRealMeshVertex)));给 vertex buffer 创建 SRV。SRV 全称是 Shader Resource View。auto AddFace [Vertices, Indices]( const FVector3f A, const FVector3f B, const FVector3f C, const FVector3f D, const FVector3f Normal) { const uint32 BaseIndex Vertices.Num(); Vertices.Add({ A, Normal, FVector2f(0.0f, 0.0f) }); Vertices.Add({ B, Normal, FVector2f(1.0f, 0.0f) }); Vertices.Add({ C, Normal, FVector2f(1.0f, 1.0f) }); Vertices.Add({ D, Normal, FVector2f(0.0f, 1.0f) }); Indices.Add(BaseIndex 0); Indices.Add(BaseIndex 1); Indices.Add(BaseIndex 2); Indices.Add(BaseIndex 0); Indices.Add(BaseIndex 2); Indices.Add(BaseIndex 3); };UE5 这套 RHI 约定里通常把屏幕空间顺时针 winding 当作正面。也就是说一个三角形如果顶点顺序是0 - 1 - 2在屏幕上看起来是顺时针那么它就是 front face。例如一个面A ---- B | | | | D ---- C如果你按这个顺序加顶点A 0 B 1 C 2 D 3然后索引写0, 1, 2 0, 2, 3就是三角形1A - B - C 三角形2A - C - Dconst FVector3f LBN(-S, -S, -S); const FVector3f RBN(S, -S, -S); const FVector3f RTN(S, S, -S); const FVector3f LTN(-S, S, -S); const FVector3f LBF(-S, -S, S); const FVector3f RBF(S, -S, S); const FVector3f RTF(S, S, S); const FVector3f LTF(-S, S, S); AddFace(LBN, RBN, RTN, LTN, FVector3f(0, 0, -1)); AddFace(LBF, LTF, RTF, RBF, FVector3f(0, 0, 1)); AddFace(LBN, LBF, RBF, RBN, FVector3f(0, -1, 0)); AddFace(RBN, RBF, RTF, RTN, FVector3f(1, 0, 0)); AddFace(RTN, RTF, LTF, LTN, FVector3f(0, 1, 0)); AddFace(LTN, LTF, LBF, LBN, FVector3f(-1, 0, 0));同一个顶点为什么要重复添加进去因为这里的“同一个空间位置”不一定是“同一个渲染顶点”。你这个 cube 有 8 个角点没错LBN, RBN, RTN, LTN, LBF, RBF, RTF, LTF但渲染里的一个 vertex 通常不是只有 Position它还包含Position Normal UV对于立方体来说同一个角点会被 3 个面共享但这 3 个面的法线不同。比如RBN这个位置RBN(S, -S, -S)它同时属于底面 Normal (0, 0, -1) 前/后某个面 Normal (0, -1, 0) 右面 Normal (1, 0, 0)如果你只存一个RBN顶点它只能有一个 Normal{ RBN, 某一个Normal, UV }那 shader 做光照时这个角点的三个面都会用同一个法线立方体边缘会被“平滑”掉看起来像圆角盒子而不是硬边立方体。virtual void PrePostProcessPass_RenderThread( FRDGBuilder GraphBuilder, const FSceneView InView, const FPostProcessingInputs Inputs) override;所以它会在后处理流程开始前在RenderThread上被调用。virtual void SubscribeToPostProcessingPass( EPostProcessingPass PassId, const FSceneView View, FPostProcessingPassDelegateArray InOutPassCallbacks, bool bIsPassEnabled) override;意思是UE 在组织后处理链路时会问你的 extension你想不想订阅某个具体的后处理 pass比如Tonemap、MotionBlur、FXAA、VisualizeDepthOfField等附近的阶段。你不是在这个函数里直接画东西而是在这里把一个 callback 注册进去const FVector MeshWorldCenter ViewOrigin ViewForward * 300.0; const FMatrix LocalToWorld FTranslationMatrix(MeshWorldCenter); const FMatrix LocalToClip LocalToWorld * InView.ViewMatrices.GetViewProjectionMatrix(); PassParameters-MS.LocalToClip FMatrix44f(LocalToClip);这几行是在给 Mesh Shader 准备一个矩阵把你的 mesh 顶点从模型局部空间变换到裁剪空间 Clip Space这样 GPU 才知道它应该画在屏幕哪里。裁剪空间 Clip Space 是透视除法之前的空间NDC 是透视除法之后的空间。透视除法就是 GPU 把裁剪空间坐标里的(x, y, z, w)变成NDC 坐标(x / w, y / w, z / w)这个动作就叫Perspective Divide 透视除法为什么要除以w因为透视投影要表达一个现象越远的东西看起来越小。在透视投影矩阵里GPU 会把“距离相机有多远”编码进w里。通常物体越远w越大。所以当你做x_ndc x_clip / w_clip y_ndc y_clip / w_clip远处的点因为w更大除完以后x/y会更接近屏幕中心。于是看起来就变小了。const FMatrix LocalToWorld FTranslationMatrix(MeshWorldCenter);上面代码是MVP的Mconst FMatrix LocalToClip LocalToWorld * InView.ViewMatrices.GetViewProjectionMatrix();上面代码是MVP的VPModel LocalToWorld ViewProj InView.ViewMatrices.GetViewProjectionMatrix() MVP LocalToClipMeshShaderPersistentMesh.usfMESH_SHADER_TRIANGLE_ATTRIBUTES(32) void MainMS( uint GroupIndex : SV_GroupIndex, MESH_SHADER_VERTEX_EXPORT( FMeshOut, 64), MESH_SHADER_TRIANGLE_EXPORT(64))这几个东西是在声明这个 Mesh Shader 的一个 thread group 最多能导出多少顶点、多少三角形MESH_SHADER_TRIANGLE_ATTRIBUTES(32)里的32在 UE 宏里其实是线程组线程数不是三角形属性数量。它展开类似[numthreads(32, 1, 1)] [outputtopology(triangle)]Mesh Shader 真实用法里通常是一个 thread group 处理一个 meshlet。但你现在这个 demo 里确实还没真正体现 meshlet 的价值。因为我们现在画的是一个很小的 cube24 个顶点 12 个三角形它太小了小到一个 mesh shader group 就能整块吃掉。所以这个阶段更像一个 group 处理整个 mesh而不是一个 group 处理一个 meshletif (GroupIndex VertexCount) { FRealMeshVertex V VertexBuffer[GroupIndex]; FMeshOut OutV; OutV.Position mul(float4(V.Position, 1.0f), LocalToClip); OutV.Normal V.Normal; OutV.UV V.UV; OutV.Color Tint * (0.35f 0.65f * abs(V.Normal.z)); MESH_SHADER_WRITE_VERTEX(GroupIndex, OutV); } if (GroupIndex TriangleCount) { uint BaseIndex GroupIndex * 3; uint3 Tri uint3( IndexBuffer[BaseIndex 0], IndexBuffer[BaseIndex 1], IndexBuffer[BaseIndex 2]); MESH_SHADER_WRITE_TRIANGLE(GroupIndex, Tri); }void MainMS( out vertices FXSJMeshVertex OutVertices[3], ------------------------------------------------------- MESH_SHADER_WRITE_VERTEX(GroupIndex, OutV);MESH_SHADER_WRITE_VERTEX(GroupIndex, OutV)本质上就是在写out vertices输出数组里的第GroupIndex个元素。也就是它和下面这个概念等价out vertices FXSJMeshVertex OutVertices[3]然后在函数里写OutVertices[GroupIndex] OutV;MESH_SHADER_VERTEX_EXPORT( FMeshOut, 64),MESH_SHADER_WRITE_VERTEX(GroupIndex, OutV);这两个是相互对应的有了MESH_SHADER_VERTEX_EXPORT才能用MESH_SHADER_WRITE_VERTEX去写入然后导出数据Mesh Shader 输出 vertices triangles | v GPU 固定功能管线 | | 裁剪 / 透视除法 / 视口变换 | 三角形装配 / 背面剔除 | 光栅化 rasterization | 插值 interpolation v Pixel Shader

相关新闻