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

相关推荐
远离UE41 天前
UE5 高性能 实时 玻璃破碎系统
ue5
远离UE42 天前
UE5 多线程 与 异步 并行
ue5
日月云棠5 天前
UE5源码分析之Editor——ConfigEditor模块全面分析
ue5
日月云棠5 天前
UE5源码分析之Editor——CommonMenuExtensions插件全面分析
ue5
日月云棠5 天前
UE5源码分析之Editor——ClothingSystemEditorInterface模块全面分析
ue5
日月云棠5 天前
UE5源码分析之Editor——ClothPainter插件全面分析
ue5
[苦行僧]6 天前
ue5 异步 Task
ue5
曼巴UE59 天前
Unlua UE5.3 调试
ue5·lua·unlua
远离UE410 天前
UE5 母材质 编写笔记
笔记·ue5·材质
远离UE410 天前
UE5 HLSLTranslator 与 ShaderCompiler
ue5