本文的大部分代码参考@YivanLee的文章,特此说明
有关ComputeShader的原理解释可以参考:https://zhuanlan.zhihu.com/p/36697128
这从此文可以得知ComputeShader是并行渲染的,所以需要设定UnbindBuffers。他与别的Shader通过RenderTarget交换数据。
声明ComputeShader
ComputeShader大部分代码与别的Shader相同。需要注意的是:自定义的SetParameters函数与自定义的UnbindBuffers函数。
我github上的代码在这里设定Struct并没有特殊意义,完全可以在PixelShader中添加,所以在这里为了不误导读者我将这段去掉了。
大致代码如下:
class FSimpleComputeShader : public FGlobalShader
{
DECLARE_SHADER_TYPE(FSimpleComputeShader, Global);
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5);
}
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
OutEnvironment.CompilerFlags.Add(CFLAG_StandardOptimization);
}
public:
FSimpleComputeShader() {}
FSimpleComputeShader(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
: FGlobalShader(Initializer)
{
OutTexture.Bind(Initializer.ParameterMap, TEXT("OutTexture"));
}
virtual bool Serialize(FArchive& Ar) override
{
bool bShaderHasOutdatedParameters = FGlobalShader::Serialize(Ar);
Ar << OutTexture;
return bShaderHasOutdatedParameters;
}
//这里使用了FUnorderedAccessViewRHIParamRef(UAV)类型的形参,用于设定RenderTarget(UAC)。
void SetParameters(FRHICommandList& RHICmdList, FUnorderedAccessViewRHIParamRef InOutUAV)
{
FComputeShaderRHIParamRef ComputeShaderRHI = GetComputeShader();
if (OutTexture.IsBound())
RHICmdList.SetUAVParameter(ComputeShaderRHI, OutTexture.GetBaseIndex(), InOutUAV);
}
//将UAV设置为空
void UnbindBuffers(FRHICommandList& RHICmdList)
{
FComputeShaderRHIParamRef ComputeShaderRHI = GetComputeShader();
if (OutTexture.IsBound())
RHICmdList.SetUAVParameter(ComputeShaderRHI, OutTexture.GetBaseIndex(), FUnorderedAccessViewRHIParamRef());
}
protected:
//用于进行数据交换的RenderTarget(UAC)变量
FShaderResourceParameter OutTexture;
};
IMPLEMENT_SHADER_TYPE(, FSimpleComputeShader, TEXT("/Plugin/BRPlugins/Private/SimpleComputeShader.usf"), TEXT("MainCS"), SF_Compute);
在渲染函数中设置并且使用ComputeShader
我们需要重新定义一个ComputeShader所用的渲染函数,并且通过PixelShader将计算结果渲染出来:
static void UseComputeShader_RenderThread(
FRHICommandListImmediate& RHICmdList,
FTextureRenderTargetResource* OutputRenderTargetResource,
FSimpleUniformStruct UniformStruct,
ERHIFeatureLevel::Type FeatureLevel
)
{
check(IsInRenderingThread());
//为RHICmdList设置ComputeShader
TShaderMapRef<FSimpleComputeShader> ComputeShader(GetGlobalShaderMap(FeatureLevel));
RHICmdList.SetComputeShader(ComputeShader->GetComputeShader());
int32 SizeX = OutputRenderTargetResource->GetSizeX();
int32 SizeY = OutputRenderTargetResource->GetSizeY();
FRHIResourceCreateInfo CreateInfo;
//设置RenderTarget格式与属性,并以此创建UAC
FTexture2DRHIRef Texture = RHICreateTexture2D(SizeX, SizeY, PF_A32B32G32R32F, 1, 1, TexCreate_ShaderResource | TexCreate_UAV, CreateInfo);
FUnorderedAccessViewRHIRef TextureUAV = RHICreateUnorderedAccessView(Texture);
//通过自定义函数设置ComputeShader变量=》分发计算任务进行并行运行=》计算完成后解除绑定
ComputeShader->SetParameters(RHICmdList, TextureUAV);
DispatchComputeShader(RHICmdList, *ComputeShader, SizeX / 32, SizeY / 32, 1);
ComputeShader->UnbindBuffers(RHICmdList);
//通过PixelShader的渲染函数将计算结果渲染出来
DrawTestShaderRenderTarget_RenderThread(RHICmdList, OutputRenderTargetResource, FeatureLevel, FLinearColor(), Texture, UniformStruct);
}
编写usf
这里就直接贴代码了:
#include "/Engine/Public/Platform.ush"
#include "/Engine/Private/Common.ush"
RWTexture2D<float4> OutTexture;
[numthreads(32, 32, 1)]
void MainCS(uint3 ThreadId : SV_DispatchThreadID)
{
//Set up some variables we are going to need
float sizeX, sizeY;
OutTexture.GetDimensions(sizeX, sizeY);
float2 iResolution = float2(sizeX, sizeY);
float2 uv = (ThreadId.xy / iResolution.xy) - 0.5;
float iGlobalTime = SimpleUniformStruct.Color1.r;
//This shader code is from www.shadertoy.com, converted to HLSL by me. If you have not checked out shadertoy yet, you REALLY should!!
float t = iGlobalTime * 0.1 + ((0.25 + 0.05 * sin(iGlobalTime * 0.1)) / (length(uv.xy) + 0.07)) * 2.2;
float si = sin(t);
float co = cos(t);
float2x2 ma = { co, si, -si, co };
float v1, v2, v3;
v1 = v2 = v3 = 0.0;
float s = 0.0;
for (int i = 0; i < 90; i++)
{
float3 p = s * float3(uv, 0.0);
p.xy = mul(p.xy, ma);
p += float3(0.22, 0.3, s - 1.5 - sin(iGlobalTime * 0.13) * 0.1);
for (int i = 0; i < 8; i++)
p = abs(p) / dot(p, p) - 0.659;
v1 += dot(p, p) * 0.0015 * (1.8 + sin(length(uv.xy * 13.0) + 0.5 - iGlobalTime * 0.2));
v2 += dot(p, p) * 0.0013 * (1.5 + sin(length(uv.xy * 14.5) + 1.2 - iGlobalTime * 0.3));
v3 += length(p.xy * 10.0) * 0.0003;
s += 0.035;
}
float len = length(uv);
v1 *= lerp(0.7, 0.0, len);
v2 *= lerp(0.5, 0.0, len);
v3 *= lerp(0.9, 0.0, len);
float3 col = float3(v3 * (1.5 + sin(iGlobalTime * 0.2) * 0.4), (v1 + v3) * 0.3, v2)
+ lerp(0.2, 0.0, len) * 0.85
+ lerp(0.0, 0.6, v3) * 0.3;
float3 powered = pow(abs(col), float3(1.2, 1.2, 1.2));
float3 minimized = min(powered, 1.0);
float4 outputColor = float4(minimized, 1.0);
OutTexture[ThreadId.xy] = outputColor;
}
PS.https://zhuanlan.zhihu.com/p/36697128 该文中通过RHI取得计算结果的方法好像失效了,所以采用了https://zhuanlan.zhihu.com/p/36697483 的方法。