虛幻4渲染編程(Shader篇)【第七卷:虛幻4中的ComputeShader】
來自專欄虛幻4渲染編程5 人贊了文章
Computeshader非常勝任大量的計算,下面我們就在虛幻4中使用ComputeShader。
先上效果圖吧
這是一張純由ComputeShader計算出來的一張圖片,代碼來自Shadertoy。
我下面的代碼是基於我前面章節所寫的代碼的,想要知道從零是如何開始的,請從第二卷開始看。
下面我們就用虛幻4的computeshader來生成它
首先我們要聲明一個ComputeShader
class FMyComputeShader : public FGlobalShader { DECLARE_SHADER_TYPE(FMyComputeShader, Global); public: FMyComputeShader(){} FMyComputeShader(const ShaderMetaType::CompiledShaderInitializerType& Initializer) :FGlobalShader(Initializer) { OutputSurface.Bind(Initializer.ParameterMap, TEXT("OutputSurface")); } static bool ShouldCache(EShaderPlatform Platform) { return IsFeatureLevelSupported(Platform, ERHIFeatureLevel::SM5); } static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5); //return true; } static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Platform, FShaderCompilerEnvironment& OutEnvironment) { FGlobalShader::ModifyCompilationEnvironment(Platform, OutEnvironment); OutEnvironment.CompilerFlags.Add(CFLAG_StandardOptimization); } virtual bool Serialize(FArchive& Ar) override { bool bShaderHasOutdatedParams = FGlobalShader::Serialize(Ar); Ar << OutputSurface; return bShaderHasOutdatedParams; } void SetSurfaces(FRHICommandList& RHICmdList, FUnorderedAccessViewRHIRef OutputSurfaceUAV) { FComputeShaderRHIParamRef ComputeShaderRHI = GetComputeShader(); if (OutputSurface.IsBound()) RHICmdList.SetUAVParameter(ComputeShaderRHI, OutputSurface.GetBaseIndex(), OutputSurfaceUAV); } void UnbindBuffers(FRHICommandList& RHICmdList) { FComputeShaderRHIParamRef ComputeShaderRHI = GetComputeShader(); if (OutputSurface.IsBound()) RHICmdList.SetUAVParameter(ComputeShaderRHI, OutputSurface.GetBaseIndex(), FUnorderedAccessViewRHIRef()); } private: FShaderResourceParameter OutputSurface; }; IMPLEMENT_SHADER_TYPE(, FMyComputeShader, TEXT("/Plugin/ShadertestPlugin/Private/MyShader.usf"), TEXT("MainCS"), SF_Compute)
它繼承自GlobalShader。
這個宏會幫我們加入一些shader相關的通用代碼。
下面我們聲明兩個版本的構造函數。
第二個版本的構造函數會把我們的私有成員變數OutputSurface與HLSL中的OutputSurface進行綁定。
然後就是shader相關的三個函數。
最後是shader的綁定聲明和實例創建。
現在我們需要完成Computeshader的HLSL部分的代碼。代碼如下:
#include "/Engine/Private/Common.ush" Texture2D MyTexture; SamplerState MyTextureSampler; float4 SimpleColor; void MainVS( in float4 InPosition : ATTRIBUTE0, in float2 InUV : ATTRIBUTE1, out float2 OutUV : TEXCOORD0, out float4 OutPosition : SV_POSITION ) { // screenspace position from vb OutPosition = InPosition; OutUV = InUV; } void MainPS( in float2 UV : TEXCOORD0, out float4 OutColor : SV_Target0 ) { OutColor = float4(MyTexture.Sample(MyTextureSampler, UV.xy).rgb, 1.0f); switch (FMyUniform.ColorIndex) { case 0 : OutColor *= FMyUniform.ColorOne; break; case 1: OutColor *= FMyUniform.ColorTwo; break; case 2: OutColor *= FMyUniform.ColorThree; break; case 3: OutColor *= FMyUniform.ColorFour; break; } } RWTexture2D<uint> OutputSurface; [numthreads(32, 32, 1)] void MainCS(uint3 ThreadId : SV_DispatchThreadID) { //Set up some variables we are going to need float sizeX, sizeY; OutputSurface.GetDimensions(sizeX, sizeY); float2 iResolution = float2(sizeX, sizeY); float2 uv = (ThreadId.xy / iResolution.xy) - 0.5; float iGlobalTime = 1.0f; //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); //Since there are limitations on operations that can be done on certain formats when using compute shaders //I elected to go with the most flexible one (UINT 32bit) and do my packing manually to simulate an R8G8B8A8_UINT format. //There might be better ways to do this uint r = outputColor.r * 255.0; uint g = ((uint) (outputColor.g * 255.0)) << 8; uint b = ((uint) (outputColor.b * 255.0)) << 16; uint a = ((uint) (outputColor.a * 255.0)) << 24; OutputSurface[ThreadId.xy] = r | g | b | a; }
完成了HLSL部分的代碼之後,我們需要執行它,然後把執行後渲染出的圖導出成bmp
static void UseComputeShader_RenderThread( FRHICommandListImmediate& RHICmdList, FTextureRenderTargetResource* OutputRenderTargetResource, ERHIFeatureLevel::Type FeatureLevel ) { check(IsInRenderingThread()); TShaderMapRef<FMyComputeShader> ComputeShader(GetGlobalShaderMap(FeatureLevel)); RHICmdList.SetComputeShader(ComputeShader->GetComputeShader()); //ComputeShader->SetSurfaces(RHICmdList,) int32 SizeX = OutputRenderTargetResource->GetSizeX(); int32 SizeY = OutputRenderTargetResource->GetSizeY(); FRHIResourceCreateInfo CreateInfo; FTexture2DRHIRef Texture = RHICreateTexture2D(SizeX, SizeY, PF_R32_UINT, 1, 1, TexCreate_ShaderResource | TexCreate_UAV, CreateInfo); //OutputRenderTargetResource->UA FUnorderedAccessViewRHIRef TextureUAV = RHICreateUnorderedAccessView(Texture); ComputeShader->SetSurfaces(RHICmdList, TextureUAV); DispatchComputeShader(RHICmdList, *ComputeShader, SizeX/32, SizeY/32, 1); //FLinearColor Color = FLinearColor::White; //FMyShaderStructData data; //data.ColorOne = FLinearColor::White; //data.ColorTwo = FLinearColor::White; //data.Colorthree = FLinearColor::White; //data.ColorFour = FLinearColor::White; //DrawTestShaderRenderTarget_RenderThread(RHICmdList, OutputRenderTargetResource, FeatureLevel, Color, Texture, data); ComputeShader->UnbindBuffers(RHICmdList); //OutputRenderTargetResource->TextureRHI = Texture; //create a bitmap TArray<FColor> Bitmap; //To access our resource we do a custom read using lockrect uint32 LolStride = 0; char* TextureDataPtr = (char*)RHICmdList.LockTexture2D(Texture, 0, EResourceLockMode::RLM_ReadOnly, LolStride, false); for (uint32 Row = 0; Row < Texture->GetSizeY(); ++Row) { uint32* PixelPtr = (uint32*)TextureDataPtr; //Since we are using our custom UINT format, we need to unpack it here to access the actual colors for (uint32 Col = 0; Col < Texture->GetSizeX(); ++Col) { uint32 EncodedPixel = *PixelPtr; uint8 r = (EncodedPixel & 0x000000FF); uint8 g = (EncodedPixel & 0x0000FF00) >> 8; uint8 b = (EncodedPixel & 0x00FF0000) >> 16; uint8 a = (EncodedPixel & 0xFF000000) >> 24; Bitmap.Add(FColor(r, g, b, a)); PixelPtr++; } // move to next row: TextureDataPtr += LolStride; } RHICmdList.UnlockTexture2D(Texture, 0, false); // if the format and texture type is supported if (Bitmap.Num()) { // Create screenshot folder if not already present. IFileManager::Get().MakeDirectory(*FPaths::ScreenShotDir(), true); const FString ScreenFileName(FPaths::ScreenShotDir() / TEXT("VisualizeTexture")); uint32 ExtendXWithMSAA = Bitmap.Num() / Texture->GetSizeY(); // Save the contents of the array to a bitmap file. (24bit only so alpha channel is dropped) FFileHelper::CreateBitmap(*ScreenFileName, ExtendXWithMSAA, Texture->GetSizeY(), Bitmap.GetData()); UE_LOG(LogConsoleResponse, Display, TEXT("Content was saved to "%s""), *FPaths::ScreenShotDir()); } else { UE_LOG(LogConsoleResponse, Error, TEXT("Failed to save BMP, format or texture type is not supported")); } }
下面我們在我們的shader中使用computeshader計算出來的值
我們把第11個像素中的值取出來
然後輸出到了我們之前的testshader中
我們得到了一個深墨綠色的球。
至此我們從簡單地從computeshader的計算結果中拿到了值,並且把這個結果加以利用。
然後我們就能在項目目錄SavedScreenshotsWindows下找到我們Computeshader的計算結果了。前面說了這麼多都沒做一個東西出來,下一卷我們就利用這1~7卷的知識做一個效果。
推薦閱讀: