
XSJHelloMeshShaderPass.h
#pragma once
#include "RenderGraphResources.h"
class FRDGBuilder;
class FViewInfo;
void AddXSJHelloMeshShaderPass(
FRDGBuilder& GraphBuilder,
const FViewInfo& View,
FRDGTextureRef SceneColorTexture);
这里按照之前的文章,没什么好讲的,只是声明函数
XSJHelloMeshShaderPass.cpp
cpp
#include "XSJHelloMeshShaderPass.h"
#include "DataDrivenShaderPlatformInfo.h"
#include "GlobalShader.h"
#include "PipelineStateCache.h"
#include "RenderGraphBuilder.h"
#include "RenderGraphUtils.h"
#include "RHIStaticStates.h"
#include "SceneRendering.h"
#include "ShaderParameterUtils.h"
class FXSJHelloMeshShaderMS : public FGlobalShader
{
DECLARE_GLOBAL_SHADER(FXSJHelloMeshShaderMS);
SHADER_USE_PARAMETER_STRUCT(FXSJHelloMeshShaderMS, FGlobalShader);
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER(FVector4f, DebugColor)
END_SHADER_PARAMETER_STRUCT()
public:
static bool ShouldCompilePermutation(
const FGlobalShaderPermutationParameters& Parameters)
{
return RHISupportsMeshShadersTier0(Parameters.Platform);
}
};
IMPLEMENT_GLOBAL_SHADER(
FXSJHelloMeshShaderMS,
"/Engine/Private/XSJ/XSJHelloMeshShader.usf",
"MainMS",
SF_Mesh);
class FXSJHelloMeshShaderPS : public FGlobalShader
{
DECLARE_GLOBAL_SHADER(FXSJHelloMeshShaderPS);
SHADER_USE_PARAMETER_STRUCT(FXSJHelloMeshShaderPS, FGlobalShader);
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER(FVector4f, DebugColor)
RENDER_TARGET_BINDING_SLOTS()
END_SHADER_PARAMETER_STRUCT()
public:
static bool ShouldCompilePermutation(
const FGlobalShaderPermutationParameters& Parameters)
{
return RHISupportsMeshShadersTier0(Parameters.Platform);
}
};
IMPLEMENT_GLOBAL_SHADER(
FXSJHelloMeshShaderPS,
"/Engine/Private/XSJ/XSJHelloMeshShader.usf",
"MainPS",
SF_Pixel);
BEGIN_SHADER_PARAMETER_STRUCT(FXSJHelloMeshShaderPassParameters, )
SHADER_PARAMETER_STRUCT_INCLUDE(
FXSJHelloMeshShaderMS::FParameters,
MS)
SHADER_PARAMETER_STRUCT_INCLUDE(
FXSJHelloMeshShaderPS::FParameters,
PS)
END_SHADER_PARAMETER_STRUCT()
void AddXSJHelloMeshShaderPass(
FRDGBuilder& GraphBuilder,
const FViewInfo& View,
FRDGTextureRef SceneColorTexture)
{
if (!GRHISupportsMeshShadersTier0)
{
return;
}
if (!SceneColorTexture)
{
return;
}
FXSJHelloMeshShaderPassParameters* PassParameters =
GraphBuilder.AllocParameters<
FXSJHelloMeshShaderPassParameters>();
PassParameters->MS.DebugColor =
FVector4f(1.0f, 0.1f, 0.0f, 1.0f);
PassParameters->PS.DebugColor =
FVector4f(1.0f, 1.0f, 1.0f, 0.85f);
PassParameters->PS.RenderTargets[0] =
FRenderTargetBinding(
SceneColorTexture,
ERenderTargetLoadAction::ELoad);
TShaderMapRef<FXSJHelloMeshShaderMS> MeshShader(
View.ShaderMap);
TShaderMapRef<FXSJHelloMeshShaderPS> PixelShader(
View.ShaderMap);
const FIntRect ViewRect = View.ViewRect;
GraphBuilder.AddPass(
RDG_EVENT_NAME("XSJ.HelloMeshShader"),
PassParameters,
ERDGPassFlags::Raster,
[
PassParameters,
MeshShader,
PixelShader,
ViewRect
](FRDGAsyncTask, FRHICommandList& RHICmdList)
{
FGraphicsPipelineStateInitializer GraphicsPSOInit;
RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
GraphicsPSOInit.BlendState =
TStaticBlendState<
CW_RGBA,
BO_Add,
BF_SourceAlpha,
BF_InverseSourceAlpha,
BO_Add,
BF_One,
BF_InverseSourceAlpha>::GetRHI();
GraphicsPSOInit.RasterizerState =
TStaticRasterizerState<
FM_Solid,
CM_None>::GetRHI();
GraphicsPSOInit.DepthStencilState =
TStaticDepthStencilState<
false,
CF_Always>::GetRHI();
GraphicsPSOInit.BoundShaderState.SetMeshShader(
MeshShader.GetMeshShader());
GraphicsPSOInit.BoundShaderState.PixelShaderRHI =
PixelShader.GetPixelShader();
GraphicsPSOInit.PrimitiveType = PT_TriangleList;
SetGraphicsPipelineState(
RHICmdList,
GraphicsPSOInit,
0);
SetShaderParameters(
RHICmdList,
MeshShader,
MeshShader.GetMeshShader(),
PassParameters->MS);
SetShaderParameters(
RHICmdList,
PixelShader,
PixelShader.GetPixelShader(),
PassParameters->PS);
RHICmdList.SetViewport(
ViewRect.Min.X,
ViewRect.Min.Y,
0.0f,
ViewRect.Max.X,
ViewRect.Max.Y,
1.0f);
RHICmdList.DispatchMeshShader(1, 1, 1);
});
}
FXSJHelloMeshShaderMS
这个PS只是定义了一个颜色,并没有其他信息
IMPLEMENT_GLOBAL_SHADER(FMyVS, "...", "MainVS", SF_Vertex);
IMPLEMENT_GLOBAL_SHADER(FMyPS, "...", "MainPS", SF_Pixel);
IMPLEMENT_GLOBAL_SHADER(FMyCS, "...", "MainCS", SF_Compute);
IMPLEMENT_GLOBAL_SHADER(FMyMS, "...", "MainMS", SF_Mesh);
它们 C++ 都可以继承:
public FGlobalShader
这里IMPLEMENT_GLOBAL_SHADER(XXX, SF_Meh)就说明它是MeshShader了
这三个东西:
"MainMS", SF_Mesh
"MainPS", SF_Pixel
同一个 .usf 文件只是源码容器,UE 会把它编译成两个完全不同的 shader bytecode。
你现在是这样:
IMPLEMENT_GLOBAL_SHADER(
FXSJHelloMeshShaderMS,
"/Engine/Private/XSJ/XSJHelloMeshShader.usf",
"MainMS",
SF_Mesh);
这表示:
从 XSJHelloMeshShader.usf 里找 MainMS()
把它编译成 Mesh Shader
然后这个:
IMPLEMENT_GLOBAL_SHADER(
FXSJHelloMeshShaderPS,
"/Engine/Private/XSJ/XSJHelloMeshShader.usf",
"MainPS",
SF_Pixel);
表示:
从同一个 usf 里找 MainPS()
把它编译成 Pixel Shader
数量 通常一进一出 可决定输出多少顶点 三角形数量 由索引和 Draw Call 决定 可决定输出多少三角形 连接关系 由 Index Buffer 决定 Shader 直接写出三角形索引 几何剔除 难以整体剔除 可直接输出 0 个图元 执行模型 各顶点相对独立 线程组协作、共享数据 管线角色 只负责顶点变换 替代 VS 和传统图元组装等阶段
Meshlet 是把一个大网格(Mesh)预先切分得到的"小型三角形簇"。
一个完整 Mesh
├─ Meshlet 0:几十个顶点、几十个三角形
├─ Meshlet 1:几十个顶点、几十个三角形
├─ Meshlet 2:几十个顶点、几十个三角形
└─ ...
每个 Meshlet 通常包含:
-
一小组顶点
-
一组局部三角形索引
-
包围球或包围盒
-
用于背面剔除的法线锥等辅助数据
BEGIN_SHADER_PARAMETER_STRUCT(FXSJHelloMeshShaderPassParameters, )
SHADER_PARAMETER_STRUCT_INCLUDE(
FXSJHelloMeshShaderMS::FParameters,
MS)SHADER_PARAMETER_STRUCT_INCLUDE( FXSJHelloMeshShaderPS::FParameters, PS)END_SHADER_PARAMETER_STRUCT()
等价概念大致是:
struct FXSJHelloMeshShaderPassParameters
{
FXSJHelloMeshShaderMS::FParameters MS;
FXSJHelloMeshShaderPS::FParameters PS;
};
因此可以这样赋值:
auto* PassParameters =
GraphBuilder.AllocParameters<FXSJHelloMeshShaderPassParameters>();
PassParameters->MS.SomeBuffer = MeshBuffer;
PassParameters->MS.SomeValue = 123;
PassParameters->PS.SomeTexture = Texture;
PassParameters->PS.SomeSampler = Sampler;
if (!GRHISupportsMeshShadersTier0)
{
return;
}
拆开看:
GRHISupportsMeshShadersTier0:全局布尔变量,表示当前运行环境是否支持基础 Mesh Shader 功能。
创建图形管线状态
FGraphicsPipelineStateInitializer GraphicsPSOInit;
RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
GraphicsPSOInit 描述这次绘制使用的完整图形管线状态。
ApplyCachedRenderTargets() 把 RDG 已经绑定好的 Render Target 格式等信息写入 PSO。这里对应之前设置的:
PassParameters->PS.RenderTargets[0] = ...
ExecutePassPrologue(RHICmdListPass, Pass);
Pass->Execute(RHICmdListPass); // 这里才执行你的 Lambda
ExecutePassEpilogue(RHICmdListPass, Pass);
位置:RenderGraphBuilder.cpp
其中 Pass->Execute() 就是你传给 GraphBuilder.AddPass() 的 Lambda。
1. RDG 在 Prologue 中启动 Render Pass
因为你指定了:
ERDGPassFlags::Raster
RDG 会在 ExecutePassPrologue() 中执行:
RHICmdList.BeginRenderPass(
Pass->GetParameters().GetRenderPassInfo(),
Pass->GetName());
cpp
GraphicsPSOInit.BlendState =
TStaticBlendState<
CW_RGBA,
BO_Add,
BF_SourceAlpha,
BF_InverseSourceAlpha,
BO_Add,
BF_One,
BF_InverseSourceAlpha>::GetRHI();
GraphicsPSOInit.RasterizerState =
TStaticRasterizerState<
FM_Solid,
CM_None>::GetRHI();
GraphicsPSOInit.DepthStencilState =
TStaticDepthStencilState<
false,
CF_Always>::GetRHI();
这三段分别设置图形管线的:
- 颜色混合方式
- 三角形光栅化方式
- 深度/模板测试方式
最终效果可以概括为:
以实心、双面、忽略深度的方式绘制,并通过 Alpha 与原画面混合。
参数含义:
CW_RGBA 写入 R、G、B、A 四个通道
BO_Add 颜色使用加法混合
BF_SourceAlpha 源颜色乘源 Alpha
BF_InverseSourceAlpha 目标颜色乘 (1 - 源 Alpha)
BO_Add Alpha 使用加法混合
BF_One 源 Alpha 乘 1
BF_InverseSourceAlpha 目标 Alpha 乘 (1 - 源 Alpha)
颜色公式:
最终RGB =
Shader输出RGB × Shader输出Alpha
+ 原RenderTarget RGB × (1 - Shader输出Alpha)
Alpha 公式:
最终Alpha =
Shader输出Alpha
+ 原RenderTarget Alpha × (1 - Shader输出Alpha)
它通过 RenderTargets[0] 的绑定知道,不是通过 .usf 猜出来的。
关键代码是:
PassParameters->PS.RenderTargets[0] =
FRenderTargetBinding(
SceneColorTexture,
ERenderTargetLoadAction::ELoad);
这里明确告诉 RDG:
渲染目标槽位 0 → SceneColorTexture
这里过后执行
cpp
GraphicsPSOInit.BlendState =
TStaticBlendState<
CW_RGBA,
BO_Add,
BF_SourceAlpha,
BF_InverseSourceAlpha,
BO_Add,
BF_One,
BF_InverseSourceAlpha>::GetRHI();
这里面的原RenderTargetRGB就是你绑定的RenderTarget的RGB(这里RenderTarget绑定的是场景颜色图)
RasterizerState:光栅化方式
TStaticRasterizerState<
FM_Solid,
CM_None
>::GetRHI();
FM_Solid:实心填充三角形,而不是只画线框。CM_None:不剔除任何一面。
因此无论三角形朝向相机还是背向相机,都会进行光栅化。
FM_Solid:填满整个三角形
CM_None :正面、背面都绘制
DepthStencilState:深度状态
TStaticDepthStencilState<
false,
CF_Always
>::GetRHI();
false:不写入深度缓冲。CF_Always:深度比较永远通过。- 未指定的模板参数保持默认值,模板测试关闭
这个让我们画的三角形永远显示在屏幕前方
它让三角形在"这个 Pass 执行时"不受场景深度遮挡,因此看起来像叠加在当前画面最前面。
TStaticDepthStencilState<
false, // 不写深度
CF_Always // 无论深度值是多少都通过
>
假设场景中已有一个物体的深度是 0.2,你的三角形深度是 0.8,正常深度测试会认为三角形在后面:
普通深度测试:0.8 被 0.2 挡住 → 不显示
CF_Always: 无条件通过 → 仍然显示
cpp
GraphicsPSOInit.BoundShaderState.SetMeshShader(
MeshShader.GetMeshShader());
GraphicsPSOInit.BoundShaderState.PixelShaderRHI =
PixelShader.GetPixelShader();
这两段代码是在告诉图形管线:
这次绘制的 Mesh Shader 阶段和 Pixel Shader 阶段分别使用哪一份已编译 Shader。
cpp
GraphicsPSOInit.PrimitiveType = PT_TriangleList;
SetGraphicsPipelineState(
RHICmdList,
GraphicsPSOInit,
0);
SetShaderParameters(
RHICmdList,
MeshShader,
MeshShader.GetMeshShader(),
PassParameters->MS);
SetShaderParameters(
RHICmdList,
PixelShader,
PixelShader.GetPixelShader(),
PassParameters->PS);
GraphicsPSOInit.PrimitiveType = PT_TriangleList;
表示这条管线输出的是独立三角形:
三角形0:顶点 0、1、2
三角形1:顶点 3、4、5
Mesh Shader 管线:
DispatchMeshShader()
↓
MS 自己读取/生成顶点
MS 自己输出三角形索引
↓
光栅化
这一步在我们的XSJHelloMeshShader.usf里面有两行代码:
cpp
// 这次输出3个顶点、1个三角形
SetMeshOutputCounts(3, 1);
它在自己将顶点梳理成三角形
[outputtopology("triangle")]
这是 Mesh Shader 入口函数的一个 HLSL 属性,意思是:
这个 Mesh Shader 输出的图元类型是三角形。
它告诉编译器和光栅化器,MainMS 输出的索引每一项都代表一个三角形:
查找并激活 PSO
SetGraphicsPipelineState(
RHICmdList,
GraphicsPSOInit,
0);
GraphicsPSOInit 此时已经包含:
Render Target 格式
混合状态
光栅化状态
深度状态
Mesh Shader
Pixel Shader
图元类型
UE 会根据这些配置从 PSO 缓存中查找管线;不存在时则创建,然后设置到 RHICmdList。
我提前把PSO设置好了,我在gpu里面拿着PSO的数据判断如何执行,速度就很快,如果没有PSO数据,我GPU每次需要什么数据需要CPU再上传给你,你GPU就需要等待,浪费线程利用率,等CPU把数据度过来又继续执行
绑定 Mesh Shader 参数
SetShaderParameters(
RHICmdList,
MeshShader,
MeshShader.GetMeshShader(),
PassParameters->MS);
参数分别表示:
RHICmdList 向哪个命令列表设置
MeshShader UE Shader 对象及其参数元数据
MeshShader.GetMeshShader() 底层 RHI Mesh Shader
PassParameters->MS 要绑定的实际参数值
绑定 Pixel Shader 参数
SetShaderParameters(
RHICmdList,
PixelShader,
PixelShader.GetPixelShader(),
PassParameters->PS);
和上面类似
cpp
PrimitiveType
↓
设置并激活完整 PSO
↓
绑定 MainMS 的参数
↓
绑定 MainPS 的参数
↓
DispatchMeshShader() 真正开始绘制
DispatchMeshShader(1, 1, 1);
这里是表示有多少组工作组:1 * 1 * 1 = 1个
Numthreads1, 1, 1是表示工作组的线程数量
因此:
每组线程数 = 1×1×1 = 1
工作组数量 = 1×1×1 = 1
总线程调用 = 1×1 = 1
XSJHelloMeshShader.usf
cpp
#include "/Engine/Private/Common.ush"
float4 DebugColor;
struct FXSJMeshVertex
{
float4 Position : SV_Position;
float4 Color : COLOR0;
};
[outputtopology("triangle")]
[numthreads(1, 1, 1)]
void MainMS(
out vertices FXSJMeshVertex OutVertices[3],
out indices uint3 OutTriangles[1])
{
SetMeshOutputCounts(3, 1);
OutVertices[0].Position =
float4(-0.55f, -0.45f, 0.5f, 1.0f);
OutVertices[0].Color =
float4(1.0f, 0.0f, 0.0f, 1.0f) * DebugColor;
OutVertices[1].Position =
float4(0.0f, 0.55f, 0.5f, 1.0f);
OutVertices[1].Color =
float4(0.0f, 1.0f, 0.0f, 1.0f) * DebugColor;
OutVertices[2].Position =
float4(0.55f, -0.45f, 0.5f, 1.0f);
OutVertices[2].Color =
float4(0.0f, 0.2f, 1.0f, 1.0f) * DebugColor;
OutTriangles[0] = uint3(0, 1, 2);
}
float4 MainPS(FXSJMeshVertex Input) : SV_Target0
{
return Input.Color * DebugColor;
}
Mesh Shader:生成三角形
[outputtopology("triangle")]
[numthreads(1, 1, 1)]
void MainMS(...)
含义:
-
输出拓扑是三角形。
-
每个工作组只有
1×1×1个线程。 -
C++ 中
DispatchMeshShader(1,1,1)启动一个工作组,因此这里只执行一次MainMS。SetMeshOutputCounts(3, 1);
声明本工作组输出:
3 个顶点
1 个三角形
三个顶点的位置:
(-0.55, -0.45, 0.5, 1) // 左下
( 0.00, 0.55, 0.5, 1) // 上方
( 0.55, -0.45, 0.5, 1) // 右下
每个顶点设置了不同颜色:
顶点0:红色
顶点1:绿色
顶点2:蓝色
但随后会乘以 MS 的 DebugColor:
OutVertices[i].Color = 顶点颜色 * DebugColor;
Pixel Shader:输出颜色
float4 MainPS(FXSJMeshVertex Input) : SV_Target0
{
return Input.Color * DebugColor;
}
Input.Color:光栅化器插值后的颜色。SV_Target0:输出到RenderTargets[0],也就是SceneColorTexture。