UE5 SF_VertexShader 如何使用

效果就是把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有点像

  1. 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传过来的

相关推荐
电子云与长程纠缠4 小时前
UE5 Lyra PocketWorld进行3D内容UI预览 - 上
开发语言·学习·3d·ue5·游戏引擎
电子云与长程纠缠6 小时前
UE5 Lyra PocketWorld进行3D内容UI预览 - 下
开发语言·学习·游戏·ui·ue5
远离UE41 天前
UE5 GPU内部异步
ue5
1204157137 肖哥1 天前
UE5 PCG PCGBlueprintElement简介
ue5
dong1326971 天前
UE5FPS游戏开发教程(四)
ue5
dong1326972 天前
UE5FPS游戏开发教程(三)
ue5
日月云棠2 天前
UE5源码分析之Editor——AnimationBlueprintLibrary模块全面分析
ue5
远离UE42 天前
UE5 ECS 原理
ue5
mengzhi啊4 天前
UE5 把剑绑定到骨骼-给要播放的动画及动画蒙太奇添加武器
ue5