

效果就是把VS变成了一个UV的样子
目录结构:
D:\UEProject\AITest
└─ Plugins
└─ AITestGlobalShader
├─ AITestGlobalShader.uplugin
├─ Source
│ └─ AITestGlobalShader
│ ├─ AITestGlobalShader.Build.cs
│ ├─ Public
│ │ └─ AITestShaderBlueprintLibrary.h
│ └─ Private
│ ├─ AITestGlobalShaderModule.cpp
│ └─ AITestShaderBlueprintLibrary.cpp
└─ Shaders
└─ Private
└─ AITestFullscreen.usf
AITestGlobalShader.uplugin
ruby
{
"FileVersion": 3,
"Version": 1,
"VersionName": "1.0",
"FriendlyName": "AI Test Global Shader",
"Category": "Rendering",
"CanContainContent": false,
"Modules": [
{
"Name": "AITestGlobalShader",
"Type": "Runtime",
"LoadingPhase": "PostConfigInit"
}
]
}
Source/AITestGlobalShader/AITestGlobalShader.Build.cs
ruby
using UnrealBuildTool;
public class AITestGlobalShader : ModuleRules
{
public AITestGlobalShader(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(new[]
{
"Core",
"CoreUObject",
"Engine"
});
PrivateDependencyModuleNames.AddRange(new[]
{
"Projects",
"RenderCore",
"RHI",
"Renderer"
});
}
}
Source/AITestGlobalShader/Private/AITestGlobalShaderModule.cpp
ruby
#include "Interfaces/IPluginManager.h"
#include "Misc/Paths.h"
#include "Modules/ModuleManager.h"
#include "ShaderCore.h"
class FAITestGlobalShaderModule : public IModuleInterface
{
public:
virtual void StartupModule() override
{
TSharedPtr<IPlugin> Plugin = IPluginManager::Get().FindPlugin(TEXT("AITestGlobalShader"));
if (Plugin.IsValid())
{
AddShaderSourceDirectoryMapping(
TEXT("/Plugin/AITestGlobalShader"),
FPaths::Combine(Plugin->GetBaseDir(), TEXT("Shaders"))
);
}
}
virtual void ShutdownModule() override
{
}
};
IMPLEMENT_MODULE(FAITestGlobalShaderModule, AITestGlobalShader)
Source/AITestGlobalShader/Public/AITestShaderBlueprintLibrary.h
ruby
#pragma once
#include "Kismet/BlueprintFunctionLibrary.h"
#include "AITestShaderBlueprintLibrary.generated.h"
class UTextureRenderTarget2D;
UCLASS()
class AITESTGLOBALSHADER_API UAITestShaderBlueprintLibrary : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, Category = "AITest|Shader")
static void DrawFullscreenTriangleToRenderTarget(UTextureRenderTarget2D* RenderTarget);
};
Source/AITestGlobalShader/Private/AITestShaderBlueprintLibrary.cpp
ruby
#include "AITestShaderBlueprintLibrary.h"
#include "CommonRenderResources.h"
#include "Engine/TextureRenderTarget2D.h"
#include "GlobalShader.h"
#include "PipelineStateCache.h"
#include "RHIStaticStates.h"
#include "RenderResource.h"
#include "ShaderParameterStruct.h"
#include "TextureResource.h"
class FAITestFullscreenVS : public FGlobalShader
{
DECLARE_GLOBAL_SHADER(FAITestFullscreenVS);
SHADER_USE_PARAMETER_STRUCT(FAITestFullscreenVS, FGlobalShader);
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
END_SHADER_PARAMETER_STRUCT()
public:
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
return true;
}
};
class FAITestFullscreenPS : public FGlobalShader
{
DECLARE_GLOBAL_SHADER(FAITestFullscreenPS);
SHADER_USE_PARAMETER_STRUCT(FAITestFullscreenPS, FGlobalShader);
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
END_SHADER_PARAMETER_STRUCT()
public:
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
return true;
}
};
IMPLEMENT_GLOBAL_SHADER(
FAITestFullscreenVS,
"/Plugin/AITestGlobalShader/Private/AITestFullScreen.usf",
"MainVS",
SF_Vertex
);
IMPLEMENT_GLOBAL_SHADER(
FAITestFullscreenPS,
"/Plugin/AITestGlobalShader/Private/AITestFullScreen.usf",
"MainPS",
SF_Pixel
);
void UAITestShaderBlueprintLibrary::DrawFullscreenTriangleToRenderTarget(UTextureRenderTarget2D* RenderTarget)
{
if (!RenderTarget)
{
return;
}
FTextureRenderTargetResource* RenderTargetResource = RenderTarget->GameThread_GetRenderTargetResource();
const FIntPoint TargetSize(RenderTarget->SizeX, RenderTarget->SizeY);
ENQUEUE_RENDER_COMMAND(AITestDrawFullscreenTriangle)(
[RenderTargetResource, TargetSize](FRHICommandListImmediate& RHICmdList)
{
FRHITexture* TargetTexture = RenderTargetResource->GetRenderTargetTexture();
if (!TargetTexture)
{
return;
}
FRHIRenderPassInfo RenderPassInfo(TargetTexture, ERenderTargetActions::Clear_Store);
RHICmdList.BeginRenderPass(RenderPassInfo, TEXT("AITestDrawFullscreenTriangle"));
{
RHICmdList.SetViewport(
0.0f,
0.0f,
0.0f,
static_cast<float>(TargetSize.X),
static_cast<float>(TargetSize.Y),
1.0f
);
FGlobalShaderMap* ShaderMap = GetGlobalShaderMap(GMaxRHIFeatureLevel);
TShaderMapRef<FAITestFullscreenVS> VertexShader(ShaderMap);
TShaderMapRef<FAITestFullscreenPS> PixelShader(ShaderMap);
FGraphicsPipelineStateInitializer GraphicsPSOInit;
RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
GraphicsPSOInit.BlendState = TStaticBlendState<>::GetRHI();
GraphicsPSOInit.RasterizerState = TStaticRasterizerState<>::GetRHI();
GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
GraphicsPSOInit.PrimitiveType = PT_TriangleList;
GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI =
GEmptyVertexDeclaration.VertexDeclarationRHI;
GraphicsPSOInit.BoundShaderState.VertexShaderRHI =
VertexShader.GetVertexShader();
GraphicsPSOInit.BoundShaderState.PixelShaderRHI =
PixelShader.GetPixelShader();
SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit, 0);
RHICmdList.DrawPrimitive(0, 1, 1);
}
RHICmdList.EndRenderPass();
}
);
}
FRHIRenderPassInfo RenderPassInfo(TargetTexture, ERenderTargetActions::Clear_Store);
是清理了然后写的意思
几个常见组合可以这样理解:
Clear_Store:先清空,再保存结果
Load_Store:保留旧内容,再保存结果
DontLoad_Store:不关心旧内容,但保存结果
Clear_DontStore:先清空,但结束不保存,比较少见
开 RenderPass
FRHIRenderPassInfo RenderPassInfo(TargetTexture, ERenderTargetActions::Clear_Store);
RHICmdList.BeginRenderPass(RenderPassInfo, TEXT("AITestDrawFullscreenTriangle"));
意思是:
RenderPassInfo 这两句只是在说:
我要往哪个 RenderTarget 写
开始时怎么处理旧内容
结束时要不要保存结果
所以你能在 RT 里看到最终颜色。
Shaders/Private/AITestFullscreen.usf
ruby
#include "/Engine/Public/Platform.ush"
struct FVSOutput
{
float4 Position : SV_POSITION;
float2 UV : TEXCOORD0;
};
FVSOutput MainVS(uint VertexID : SV_VertexID)
{
float2 Positions[3] =
{
float2(-1.0, -1.0),
float2(-1.0, 3.0),
float2( 3.0, -1.0)
};
FVSOutput Output;
Output.Position = float4(Positions[VertexID], 0.0, 1.0);
Output.UV = float2(
Positions[VertexID].x * 0.5 + 0.5,
-Positions[VertexID].y * 0.5 + 0.5
);
return Output;
}
float4 MainPS(FVSOutput Input) : SV_Target0
{
return float4(Input.UV, 0.2, 1.0);
}
Vertex Shader 不负责往 RenderTarget 写像素。
Vertex Shader 只输出"顶点数据"。
真正写 RT 的是 Pixel Shader 的 SV_Target0 输出。
然后在 AITest.uproject 里启用插件,或者进编辑器插件面板启用:
"Plugins": [
{
"Name": "AITestGlobalShader",
"Enabled": true
}
]
用法是:创建一个 Texture Render Target 2D 资产,然后在蓝图里调用:
DrawFullscreenTriangleToRenderTarget(RenderTarget)
加入Vertex Buffer:

AITestGlobalShader.uplugin
{
"FileVersion": 3,
"Version": 1,
"VersionName": "1.0",
"FriendlyName": "AI Test Global Shader",
"Category": "Rendering",
"CanContainContent": false,
"Modules": [
{
"Name": "AITestGlobalShader",
"Type": "Runtime",
"LoadingPhase": "PostConfigInit"
}
]
}
AITestGlobalShader.Build.cs
using UnrealBuildTool;
public class AITestGlobalShader : ModuleRules
{
public AITestGlobalShader(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(new[]
{
"Core",
"CoreUObject",
"Engine"
});
PrivateDependencyModuleNames.AddRange(new[]
{
"Projects",
"RenderCore",
"RHI",
"Renderer"
});
}
}
AITestGlobalShaderModule.cpp
#include "Interfaces/IPluginManager.h"
#include "Misc/Paths.h"
#include "Modules/ModuleManager.h"
#include "ShaderCore.h"
class FAITestGlobalShaderModule : public IModuleInterface
{
public:
virtual void StartupModule() override
{
TSharedPtr<IPlugin> Plugin = IPluginManager::Get().FindPlugin(TEXT("AITestGlobalShader"));
if (Plugin.IsValid())
{
AddShaderSourceDirectoryMapping(
TEXT("/Plugin/AITestGlobalShader"),
FPaths::Combine(Plugin->GetBaseDir(), TEXT("Shaders"))
);
}
}
virtual void ShutdownModule() override
{
}
};
IMPLEMENT_MODULE(FAITestGlobalShaderModule, AITestGlobalShader)
AITestShaderBlueprintLibrary.h
#pragma once
#include "Kismet/BlueprintFunctionLibrary.h"
#include "AITestShaderBlueprintLibrary.generated.h"
class UTextureRenderTarget2D;
UCLASS()
class AITESTGLOBALSHADER_API UAITestShaderBlueprintLibrary : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, Category = "AITest|Shader")
static void DrawFullscreenTriangleToRenderTarget(UTextureRenderTarget2D* RenderTarget);
UFUNCTION(BlueprintCallable, Category = "AITest|Shader")
static void DrawVertexBufferTriangleToRenderTarget(UTextureRenderTarget2D* RenderTarget);
};
AITestShaderBlueprintLibrary.cpp
#include "AITestShaderBlueprintLibrary.h"
#include "CommonRenderResources.h"
#include "Engine/TextureRenderTarget2D.h"
#include "GlobalShader.h"
#include "PipelineStateCache.h"
#include "RHIResourceUtils.h"
#include "RHIStaticStates.h"
#include "RenderResource.h"
#include "ShaderParameterStruct.h"
#include "TextureResource.h"
namespace
{
struct FAITestTriangleVertex
{
FVector2f Position;
FVector2f UV;
};
class FAITestTriangleVertexBuffer : public FVertexBuffer
{
public:
virtual void InitRHI(FRHICommandListBase& RHICmdList) override
{
static const FAITestTriangleVertex Vertices[] =
{
{ FVector2f(-0.8f, -0.8f), FVector2f(0.0f, 1.0f) },
{ FVector2f( 0.0f, 0.8f), FVector2f(0.5f, 0.0f) },
{ FVector2f( 0.8f, -0.8f), FVector2f(1.0f, 1.0f) }
};
VertexBufferRHI = UE::RHIResourceUtils::CreateVertexBufferFromArray(
RHICmdList,
TEXT("FAITestTriangleVertexBuffer"),
EBufferUsageFlags::Static,
MakeConstArrayView(Vertices)
);
}
};
class FAITestTriangleVertexDeclaration : public FRenderResource
{
public:
FVertexDeclarationRHIRef VertexDeclarationRHI;
virtual void InitRHI(FRHICommandListBase& RHICmdList) override
{
FVertexDeclarationElementList Elements;
const uint16 Stride = sizeof(FAITestTriangleVertex);
Elements.Add(FVertexElement(
0,
STRUCT_OFFSET(FAITestTriangleVertex, Position),
VET_Float2,
0,
Stride
));
Elements.Add(FVertexElement(
0,
STRUCT_OFFSET(FAITestTriangleVertex, UV),
VET_Float2,
1,
Stride
));
VertexDeclarationRHI = PipelineStateCache::GetOrCreateVertexDeclaration(Elements);
}
virtual void ReleaseRHI() override
{
VertexDeclarationRHI.SafeRelease();
}
};
TGlobalResource<FAITestTriangleVertexBuffer, FRenderResource::EInitPhase::Pre> GAITestTriangleVertexBuffer;
TGlobalResource<FAITestTriangleVertexDeclaration, FRenderResource::EInitPhase::Pre> GAITestTriangleVertexDeclaration;
}
class FAITestFullscreenVS : public FGlobalShader
{
DECLARE_GLOBAL_SHADER(FAITestFullscreenVS);
SHADER_USE_PARAMETER_STRUCT(FAITestFullscreenVS, FGlobalShader);
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
END_SHADER_PARAMETER_STRUCT()
public:
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
return true;
}
};
class FAITestFullscreenPS : public FGlobalShader
{
DECLARE_GLOBAL_SHADER(FAITestFullscreenPS);
SHADER_USE_PARAMETER_STRUCT(FAITestFullscreenPS, FGlobalShader);
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
END_SHADER_PARAMETER_STRUCT()
public:
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
return true;
}
};
class FAITestVertexBufferVS : public FGlobalShader
{
DECLARE_GLOBAL_SHADER(FAITestVertexBufferVS);
SHADER_USE_PARAMETER_STRUCT(FAITestVertexBufferVS, FGlobalShader);
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
END_SHADER_PARAMETER_STRUCT()
public:
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
return true;
}
};
class FAITestVertexBufferPS : public FGlobalShader
{
DECLARE_GLOBAL_SHADER(FAITestVertexBufferPS);
SHADER_USE_PARAMETER_STRUCT(FAITestVertexBufferPS, FGlobalShader);
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
END_SHADER_PARAMETER_STRUCT()
public:
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
return true;
}
};
IMPLEMENT_GLOBAL_SHADER(
FAITestFullscreenVS,
"/Plugin/AITestGlobalShader/Private/AITestFullScreen.usf",
"MainVS",
SF_Vertex
);
IMPLEMENT_GLOBAL_SHADER(
FAITestFullscreenPS,
"/Plugin/AITestGlobalShader/Private/AITestFullScreen.usf",
"MainPS",
SF_Pixel
);
IMPLEMENT_GLOBAL_SHADER(
FAITestVertexBufferVS,
"/Plugin/AITestGlobalShader/Private/AITestFullScreen.usf",
"MainVertexBufferVS",
SF_Vertex
);
IMPLEMENT_GLOBAL_SHADER(
FAITestVertexBufferPS,
"/Plugin/AITestGlobalShader/Private/AITestFullScreen.usf",
"MainVertexBufferPS",
SF_Pixel
);
void UAITestShaderBlueprintLibrary::DrawFullscreenTriangleToRenderTarget(UTextureRenderTarget2D* RenderTarget)
{
if (!RenderTarget)
{
return;
}
FTextureRenderTargetResource* RenderTargetResource = RenderTarget->GameThread_GetRenderTargetResource();
const FIntPoint TargetSize(RenderTarget->SizeX, RenderTarget->SizeY);
ENQUEUE_RENDER_COMMAND(AITestDrawFullscreenTriangle)(
[RenderTargetResource, TargetSize](FRHICommandListImmediate& RHICmdList)
{
FRHITexture* TargetTexture = RenderTargetResource->GetRenderTargetTexture();
if (!TargetTexture)
{
return;
}
FRHIRenderPassInfo RenderPassInfo(TargetTexture, ERenderTargetActions::Clear_Store);
RHICmdList.BeginRenderPass(RenderPassInfo, TEXT("AITestDrawFullscreenTriangle"));
{
RHICmdList.SetViewport(
0.0f,
0.0f,
0.0f,
static_cast<float>(TargetSize.X),
static_cast<float>(TargetSize.Y),
1.0f
);
FGlobalShaderMap* ShaderMap = GetGlobalShaderMap(GMaxRHIFeatureLevel);
TShaderMapRef<FAITestFullscreenVS> VertexShader(ShaderMap);
TShaderMapRef<FAITestFullscreenPS> PixelShader(ShaderMap);
FGraphicsPipelineStateInitializer GraphicsPSOInit;
RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
GraphicsPSOInit.BlendState = TStaticBlendState<>::GetRHI();
GraphicsPSOInit.RasterizerState = TStaticRasterizerState<>::GetRHI();
GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
GraphicsPSOInit.PrimitiveType = PT_TriangleList;
GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI =
GEmptyVertexDeclaration.VertexDeclarationRHI;
GraphicsPSOInit.BoundShaderState.VertexShaderRHI =
VertexShader.GetVertexShader();
GraphicsPSOInit.BoundShaderState.PixelShaderRHI =
PixelShader.GetPixelShader();
SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit, 0);
RHICmdList.DrawPrimitive(0, 1, 1);
}
RHICmdList.EndRenderPass();
}
);
}
void UAITestShaderBlueprintLibrary::DrawVertexBufferTriangleToRenderTarget(UTextureRenderTarget2D* RenderTarget)
{
if (!RenderTarget)
{
return;
}
FTextureRenderTargetResource* RenderTargetResource = RenderTarget->GameThread_GetRenderTargetResource();
const FIntPoint TargetSize(RenderTarget->SizeX, RenderTarget->SizeY);
ENQUEUE_RENDER_COMMAND(AITestDrawVertexBufferTriangle)(
[RenderTargetResource, TargetSize](FRHICommandListImmediate& RHICmdList)
{
FRHITexture* TargetTexture = RenderTargetResource->GetRenderTargetTexture();
if (!TargetTexture)
{
return;
}
FRHIRenderPassInfo RenderPassInfo(TargetTexture, ERenderTargetActions::Clear_Store);
RHICmdList.BeginRenderPass(RenderPassInfo, TEXT("AITestDrawVertexBufferTriangle"));
{
RHICmdList.SetViewport(
0.0f,
0.0f,
0.0f,
static_cast<float>(TargetSize.X),
static_cast<float>(TargetSize.Y),
1.0f
);
FGlobalShaderMap* ShaderMap = GetGlobalShaderMap(GMaxRHIFeatureLevel);
TShaderMapRef<FAITestVertexBufferVS> VertexShader(ShaderMap);
TShaderMapRef<FAITestVertexBufferPS> PixelShader(ShaderMap);
FGraphicsPipelineStateInitializer GraphicsPSOInit;
RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
GraphicsPSOInit.BlendState = TStaticBlendState<>::GetRHI();
GraphicsPSOInit.RasterizerState = TStaticRasterizerState<>::GetRHI();
GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
GraphicsPSOInit.PrimitiveType = PT_TriangleList;
GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI =
GAITestTriangleVertexDeclaration.VertexDeclarationRHI;
GraphicsPSOInit.BoundShaderState.VertexShaderRHI =
VertexShader.GetVertexShader();
GraphicsPSOInit.BoundShaderState.PixelShaderRHI =
PixelShader.GetPixelShader();
SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit, 0);
RHICmdList.SetStreamSource(0, GAITestTriangleVertexBuffer.VertexBufferRHI, 0);
RHICmdList.DrawPrimitive(0, 1, 1);
}
RHICmdList.EndRenderPass();
}
);
}
顶点结构
struct FAITestTriangleVertex
{
FVector2f Position;
FVector2f UV;
};
ruby
class FAITestTriangleVertexBuffer : public FVertexBuffer
{
public:
virtual void InitRHI(FRHICommandListBase& RHICmdList) override
{
static const FAITestTriangleVertex Vertices[] =
{
{ FVector2f(-0.8f, -0.8f), FVector2f(0.0f, 1.0f) },
{ FVector2f( 0.0f, 0.8f), FVector2f(0.5f, 0.0f) },
{ FVector2f( 0.8f, -0.8f), FVector2f(1.0f, 1.0f) }
};
VertexBufferRHI = UE::RHIResourceUtils::CreateVertexBufferFromArray(
RHICmdList,
TEXT("FAITestTriangleVertexBuffer"),
EBufferUsageFlags::Static,
MakeConstArrayView(Vertices)
);
}
};
首先FVertexBuffer是什么?
FVertexBuffer 是 Unreal 渲染层里对 GPU 顶点缓冲资源 的 C++ 封装。
简单说:
FVertexBuffer代表一块 GPU 内存,这块内存专门用来存顶点数据。
比如我们新增的顶点结构:
struct FAITestTriangleVertex
{
FVector2f Position;
FVector2f UV;
};
三个顶点:
{ FVector2f(-0.8f, -0.8f), FVector2f(0.0f, 1.0f) },
{ FVector2f( 0.0f, 0.8f), FVector2f(0.5f, 0.0f) },
{ FVector2f( 0.8f, -0.8f), FVector2f(1.0f, 1.0f) }
这些数据一开始只是 CPU 内存里的数组。
FVertexBuffer 的作用就是把这批数据变成 GPU 能读的顶点缓冲:
class FAITestTriangleVertexBuffer : public FVertexBuffer
{
public:
virtual void InitRHI(FRHICommandListBase& RHICmdList) override
{
// 创建 GPU Vertex Buffer
}
};
真正创建 GPU 资源的是:
VertexBufferRHI = UE::RHIResourceUtils::CreateVertexBuffer
CreateVertexBufferFromArray(...) 创建一个 RHI 顶点缓冲资源,并把初始化数据交给 RHI。至于它什么时候真正拷到 GPU 可访问内存,由 RHI/渲染命令执行时机决定。
ruby
class FAITestTriangleVertexDeclaration : public FRenderResource
{
public:
FVertexDeclarationRHIRef VertexDeclarationRHI;
virtual void InitRHI(FRHICommandListBase& RHICmdList) override
{
FVertexDeclarationElementList Elements;
const uint16 Stride = sizeof(FAITestTriangleVertex);
Elements.Add(FVertexElement(
0,
STRUCT_OFFSET(FAITestTriangleVertex, Position),
VET_Float2,
0,
Stride
));
Elements.Add(FVertexElement(
0,
STRUCT_OFFSET(FAITestTriangleVertex, UV),
VET_Float2,
1,
Stride
));
VertexDeclarationRHI = PipelineStateCache::GetOrCreateVertexDeclaration(Elements);
}
virtual void ReleaseRHI() override
{
VertexDeclarationRHI.SafeRelease();
}
};
VertexDeclaration
class FAITestTriangleVertexDeclaration : public FRenderResource
它告诉 GPU:VertexBuffer 里的字节怎么解释。
意思是:
从 stream 0 读取
偏移量 = Position 在结构体里的偏移
类型 = float2
绑定到 ATTRIBUTE0
每个顶点大小 = Stride
和之前写vulkan有点像
- VkDescriptorSetLayout --- 布局定义
它描述了一个描述符集里包含哪些类型的资源以及它们的绑定点(binding)、个数、可访问的着色器阶段。
例如,对应你的着色器代码:
glsl
ruby
layout(binding = 0) uniform UniformBufferObject { ... } UBO;
你需要创建一个 VkDescriptorSetLayout 来告诉 Vulkan:"在绑定点 0 处有一个 uniform buffer"。
cpp
ruby
VkDescriptorSetLayoutBinding uboLayoutBinding{};
uboLayoutBinding.binding = 0;
uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
uboLayoutBinding.descriptorCount = 1;
uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; // 用于顶点着色器
VkDescriptorSetLayoutCreateInfo layoutInfo{};
layoutInfo.bindingCount = 1;
layoutInfo.pBindings = &uboLayoutBinding;
vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &descriptorSetLayout);
可以把 DescriptorSetLayout 想象成一个 "资源槽位的蓝图",它不包含实际数据,只定义格式。
全局资源
TGlobalResource<FAITestTriangleVertexBuffer, FRenderResource::EInitPhase::Pre> GAITestTriangleVertexBuffer;
TGlobalResource<FAITestTriangleVertexDeclaration, FRenderResource::EInitPhase::Pre> GAITestTriangleVertexDeclaration;
这让 UE 在渲染资源初始化阶段创建这两个资源。
你后面绘制时就能直接用:
GAITestTriangleVertexBuffer.VertexBufferRHI
GAITestTriangleVertexDeclaration.VertexDeclarationRHI
那如何赋值的呢?
它会在渲染资源初始化阶段调用:
FAITestTriangleVertexDeclaration::InitRHI(...)
也就是这段:
virtual void InitRHI(FRHICommandListBase& RHICmdList) override
{
FVertexDeclarationElementList Elements;
const uint16 Stride = sizeof(FAITestTriangleVertex);
Elements.Add(FVertexElement(
0,
STRUCT_OFFSET(FAITestTriangleVertex, Position),
VET_Float2,
0,
Stride
));
Elements.Add(FVertexElement(
0,
STRUCT_OFFSET(FAITestTriangleVertex, UV),
VET_Float2,
1,
Stride
));
VertexDeclarationRHI = PipelineStateCache::GetOrCreateVertexDeclaration(Elements);
}
真正赋值就在最后这一句:
VertexDeclarationRHI = PipelineStateCache::GetOrCreateVertexDeclaration(Elements);
所以初始化链路是:
定义全局资源:
TGlobalResource<FAITestTriangleVertexDeclaration> GAITestTriangleVertexDeclaration;
UE 初始化渲染资源:
调用 GAITestTriangleVertexDeclaration.InitRHI(...)
InitRHI 里创建 VertexDeclaration:
PipelineStateCache::GetOrCreateVertexDeclaration(Elements)
结果写入:
GAITestTriangleVertexDeclaration.VertexDeclarationRHI
RHICmdList.SetStreamSource(0, GAITestTriangleVertexBuffer.VertexBufferRHI, 0);
这句是真正绑定 VertexBuffer。
意思是:
stream 0 使用 GAITestTriangleVertexBuffer
从 offset 0 开始读
AITestFullScreen.usf
#include "/Engine/Public/Platform.ush"
struct FVSOutput
{
float4 Position : SV_POSITION;
float2 UV : TEXCOORD0;
};
FVSOutput MainVS(uint VertexID : SV_VertexID)
{
float2 Positions[3] =
{
float2(-1.0, -1.0),
float2(-1.0, 3.0),
float2( 3.0, -1.0)
};
FVSOutput Output;
Output.Position = float4(Positions[VertexID], 0.0, 1.0);
Output.UV = float2(
Positions[VertexID].x * 0.5 + 0.5,
-Positions[VertexID].y * 0.5 + 0.5
);
return Output;
}
struct FVertexBufferVSInput
{
float2 Position : ATTRIBUTE0;
float2 UV : ATTRIBUTE1;
};
FVSOutput MainVertexBufferVS(FVertexBufferVSInput Input)
{
FVSOutput Output;
Output.Position = float4(Input.Position, 0.0, 1.0);
Output.UV = Input.UV;
return Output;
}
float4 MainPS(FVSOutput Input) : SV_Target0
{
return float4(Input.UV, 0.2, 1.0);
}
float4 MainVertexBufferPS(FVSOutput Input) : SV_Target0
{
return float4(Input.UV, 0.8, 1.0);
}
相当于之前是在.usf直接声明的position 和 uv,现在的vs是从cpu传过来的