目录:
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.cs
using 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: TSharedPtr<FRealMeshGpuResource, ESPMode::ThreadSafe> MeshResource; TSharedPtr<FMeshShaderPersistentMeshViewExtension, 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::NewExtension<FMeshShaderPersistentMeshViewExtension>(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(TArray<FRealMeshVertex>&& InVertices, TArray<uint32>&& 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: TArray<FRealMeshVertex> CPUVertices; TArray<uint32> CPUIndices; FBufferRHIRef VertexBufferRHI; FBufferRHIRef IndexBufferRHI; FShaderResourceViewRHIRef VertexBufferSRV; FShaderResourceViewRHIRef IndexBufferSRV; uint32 VertexCount = 0; uint32 TriangleCount = 0; }; TSharedRef<FRealMeshGpuResource, ESPMode::ThreadSafe> CreateCubeMeshGpuResource();Source/MeshShaderPersistentMesh/Private/RealMeshGpuResource.cpp
#include "RealMeshGpuResource.h" #include "RHICommandList.h" #include "RHIResourceUtils.h" void FRealMeshGpuResource::SetMeshData(TArray<FRealMeshVertex>&& InVertices, TArray<uint32>&& 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; } TSharedRef<FRealMeshGpuResource, ESPMode::ThreadSafe> CreateCubeMeshGpuResource() { TArray<FRealMeshVertex> Vertices; TArray<uint32> 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)); TSharedRef<FRealMeshGpuResource, ESPMode::ThreadSafe> Resource = MakeShared<FRealMeshGpuResource, 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, TSharedRef<FRealMeshGpuResource, 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: TSharedRef<FRealMeshGpuResource, 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(StructuredBuffer<FRealMeshVertex>, VertexBuffer) SHADER_PARAMETER_SRV(StructuredBuffer<uint>, 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, TSharedRef<FRealMeshGpuResource, 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.AllocParameters<FPersistentRealMeshPassParameters>(); 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()); TShaderMapRef<FPersistentRealMeshMS> MeshShader(ShaderMap); TShaderMapRef<FPersistentRealMeshPS> 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 = TStaticRasterizerState<FM_Solid, CM_None>::GetRHI(); GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI(); GraphicsPSOInit.BlendState = TStaticBlendState<CW_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; }; StructuredBuffer<FRealMeshVertex> VertexBuffer; StructuredBuffer<uint> 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.cpp:
const EBufferUsageFlags VertexUsage = EBufferUsageFlags::Static | EBufferUsageFlags::ShaderResource;定义 vertex buffer 的用途。
Static表示:
这个 buffer 创建后基本不频繁修改适合静态网格。
ShaderResource表示:
这个 buffer 会被 shader 读取因为 mesh shader 里要这样读:
StructuredBuffer<FRealMeshVertex> 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就是:
三角形1:A -> B -> C 三角形2:A -> 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的M
const FMatrix LocalToClip = LocalToWorld * InView.ViewMatrices.GetViewProjectionMatrix();上面代码是MVP的V+P
Model = LocalToWorld View+Proj = InView.ViewMatrices.GetViewProjectionMatrix() MVP = LocalToClipMeshShaderPersistentMesh.usf
MESH_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 的价值。因为我们现在画的是一个很小的 cube:
24 个顶点 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