虚幻4渲染编程(环境模拟篇)【第八卷:海洋模拟-中-在UE中实现FFT海洋】
我的专栏目录:
YivanLee:专题概述及目录
简介:
先上一下效果。因为是GPU的FFT海洋模拟,所以效率非常高。我这里只做了波形的部分,shading放到下一卷介绍
上一篇推导了海洋模拟的基础理论,实现了Gerstner Wave。这一卷在Unreal中实现FFT海洋。在UnrealEngine4中实现的难度我觉得至少比在OpenGL或者DX中实现难10倍,比在Unity中实现难20倍(手动狗头)。
再来梳理下FFT海洋实现步骤。
(1)构造一个高斯分布,这一步可以再CPU中完成也可以再GPU中完成,反正它只算一遍。
(2)构造HZero
(3)构造Twiddle。这一步也是既可以在GPU中完成,或者可以在CPU中完成。它也只需要构造一次。它可以在所有过程之前构造。
(4)构造FrequencyXYZ
(5)完成了这些之后对FrequencyXYZ进行IFFT蝶形变换
我给个GIF可以看到蝶形变换的整个过程。下面是把XZY三个频谱图从频率反变换到空间域的过程如下。
(6)用上面变换得到的高度图构造法线,这一步就很简单了。
【1】OceanRenderer的设计
首先我们需要对海洋渲染器这个类进行一下设计,因为我发现如果不事先设计好渲染程序,到后期根本没法写下去了,因为整个过程涉及到了大量的渲染资源,Shader,状态,Uniform Buffer,乒乓缓冲蝶形变换等。
OceanRenderer会有OneceInit的逻辑负责初始化一些只需要画一次的成员。HZeroPass负责构造HZero,FrequencyPass负责构造XYZ频谱。TwiddlePass负责IFFT的旋转因子。IFFTButterFlyPass负责IFFT运算。CopyDataPass负责把CS计算出来的结果拷贝到RT上供渲染管线使用。
【2】UOceanComponent
首先我们需要一个ActorComponent来负责把引擎的上层数据,RenderTarget传到OceanRenderer中
#pragma once#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "Runtime/Engine/Classes/Components/ActorComponent.h"
#include "Engine/Classes/Engine/TextureRenderTarget2D.h"
#include "OceanComponent.generated.h"class OceanRender;UCLASS(hidecategories = (Object, LOD, Physics, Collision), editinlinenew, meta = (BlueprintSpawnableComponent), ClassGroup = Rendering, DisplayName = "OceanComponent")
class SDHOCEAN_API UOceanComponent : public UActorComponent
{GENERATED_BODY()public:UOceanComponent(const FObjectInitializer& ObjectInitializer);//~ Begin UActorComponent Interface.virtual void OnRegister() override;virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override;UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "OceanComponent")UTextureRenderTarget2D* PhillipsRenderTarget2D;UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "OceanComponent")UTextureRenderTarget2D* OmegaRenderTarget2D;UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "OceanComponent")UTextureRenderTarget2D* DisplacementRenderTarget2D_Y;UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "OceanComponent")UTextureRenderTarget2D* DisplacementRenderTarget2D_X;UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "OceanComponent")UTextureRenderTarget2D* DisplacementRenderTarget2D_Z;UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "OceanComponent")UTexture2D* OmegaTexture2D;UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "OceanComponent")UTexture2D* PhillipsNoiseTexture2D;//void InitGaussianNoise();FRHITexture* GetRHITextureFromRT(UTextureRenderTarget2D* RenderTarget);void RenderOcean(float DeltaTime);float TimeValue;//The OceanRendererOceanRender* OcenRenderer;UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "OceanComponent")bool bStopCalculateOcean;private:bool bHaveInitTwiddle;};
非常简单的类,然后定义我们的OceanRenderer
class OceanRender
{
public:OceanRender():bDrawCPUTwiddleTexture(true){OceanHZeroPass = new OceanRenderHZeroPass;FrequencyPass = new FrequencySpectrumPass;OceanTwiddlePass = new OceanRenderTwiddlePass;ButterFlyPassY = new OceanRenderButterFlyPass;ButterFlyPassX = new OceanRenderButterFlyPass;ButterFlyPassZ = new OceanRenderButterFlyPass;CopyButterFlyPassY = new CopyDataPass;CopyButterFlyPassX = new CopyDataPass;CopyButterFlyPassZ = new CopyDataPass;}~OceanRender() {delete OceanTwiddlePass;delete FrequencyPass;delete OceanHZeroPass;delete ButterFlyPassY;delete ButterFlyPassX;delete ButterFlyPassZ;delete CopyButterFlyPassY;delete CopyButterFlyPassX;delete CopyButterFlyPassZ;}FRHITexture* GetRHITextureFromRT(UTextureRenderTarget2D* RenderTarget){FTextureReferenceRHIRef OutputRenderTargetTextureRHI = RenderTarget->TextureReference.TextureReferenceRHI;checkf(OutputRenderTargetTextureRHI != nullptr, TEXT("Can't get render target %d texture"));FRHITexture* RenderTargetTextureRef = OutputRenderTargetTextureRHI->GetTextureReference()->GetReferencedTexture();return RenderTargetTextureRef;}OceanRenderHZeroPass* OceanHZeroPass;FrequencySpectrumPass * FrequencyPass;OceanRenderTwiddlePass* OceanTwiddlePass;OceanRenderButterFlyPass* ButterFlyPassY;OceanRenderButterFlyPass* ButterFlyPassX;OceanRenderButterFlyPass* ButterFlyPassZ;CopyDataPass* CopyButterFlyPassY;CopyDataPass* CopyButterFlyPassX;CopyDataPass* CopyButterFlyPassZ;void DrawTwiddleIndiceTexture_CPU(UTexture2D* RenderTexture2D);bool bDrawCPUTwiddleTexture;
};
它管理了各个pass以及各个pass的调用。
【3】RenderPass的设计与实现
我的一个pass的设计如下:
用一个pass类来管理Shader.usf,ShaderClass和渲染需要的各种资源。
HZeroPass
下面实现我们OceanRenderer需要的第一个Pass:HZeroPass
OceanHZeroPass.h
#pragma once#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "RHI/Public/RHIResources.h"
#include "RHI/Public/RHICommandList.h"
#include "Classes/Engine/World.h"struct OceanRenderHZeroPassSetupData
{int32 OutputSizeX;int32 OutputSizeY;EPixelFormat OutputUAVFormat;ERHIFeatureLevel::Type FeatureLevel;UTexture2D* PhillipsNoiseTexture2D;float WorldTimeSeconds;
};//Pass data that The render resource using for each pass
class OceanRenderHZeroPass
{
public:OceanRenderHZeroPass();~OceanRenderHZeroPass() {//We can't release PhillipsNoiseRHITextureif (PhillipsNoiseTextureSRV->IsValid())PhillipsNoiseTextureSRV->Release();if (OutputSurfaceTexture->IsValid())OutputSurfaceTexture->Release();if (OutputSurfaceTextureSRV->IsValid())OutputSurfaceTextureSRV->Release();if (OutputSurfaceTextureUAV->IsValid())OutputSurfaceTextureUAV->Release();}void InitPass(const OceanRenderHZeroPassSetupData& SetupData);void Draw(const OceanRenderHZeroPassSetupData& SetupData, FRHITexture* DebugRenderTargetRHITexture = nullptr);FTexture2DRHIRef OutputSurfaceTexture;FUnorderedAccessViewRHIRef OutputSurfaceTextureUAV;FShaderResourceViewRHIRef OutputSurfaceTextureSRV;FTexture2DRHIRef PhillipsNoiseRHITexture;FShaderResourceViewRHIRef PhillipsNoiseTextureSRV;private://Flag tell us weather we can use this passbool bPassSuccessInit;//mark we can only excute initpss function oncebool bShouldInitPass;
};
OceanHZeroPass.cpp
#include "SDHOcean/Private/OceanPass/OceanHZeroPass.h"#include "Runtime/RHI/Public/RHIResources.h"#include "RenderCore/Public/GlobalShader.h"
#include "RenderCore/Public/ShaderParameterUtils.h"
#include "RenderCore/Public/ShaderParameterMacros.h"#include "Classes/Engine/World.h"
#include "Public/GlobalShader.h"
#include "Public/PipelineStateCache.h"
#include "Public/RHIStaticStates.h"
#include "Public/SceneUtils.h"
#include "Public/SceneInterface.h"
#include "Public/ShaderParameterUtils.h"
#include "Public/Logging/MessageLog.h"
#include "Public/Internationalization/Internationalization.h"
#include "Public/StaticBoundShaderState.h"
#include "RHI/Public/RHICommandList.h"
#include "RHI/Public/RHIResources.h"
#include "Engine/Classes/Kismet/KismetRenderingLibrary.h"
#include "Runtime/Engine/Classes/Kismet/GameplayStatics.h"#include "Math/UnrealMathUtility.h"BEGIN_GLOBAL_SHADER_PARAMETER_STRUCT(FOceanBasicUniformBufferData, )
SHADER_PARAMETER(FVector4, A_V_windDependency_T)
SHADER_PARAMETER(FVector4, W_unused_unused)
SHADER_PARAMETER(FVector4, width_height_Lx_Lz)
SHADER_PARAMETER(float, TotalTimeElapsedSeconds)
END_GLOBAL_SHADER_PARAMETER_STRUCT()
IMPLEMENT_GLOBAL_SHADER_PARAMETER_STRUCT(FOceanBasicUniformBufferData, "OceanBasicUniformBuffer");class FOceeanComputeShader_Phlip : public FGlobalShader
{DECLARE_SHADER_TYPE(FOceeanComputeShader_Phlip, Global)
public:FOceeanComputeShader_Phlip() {}FOceeanComputeShader_Phlip(const ShaderMetaType::CompiledShaderInitializerType& Initializer): FGlobalShader(Initializer){//TODO Bind pramerter herePhlipSurface.Bind(Initializer.ParameterMap, TEXT("PhlipSurface"));PhiipNoiseTexture.Bind(Initializer.ParameterMap, TEXT("PhiipNoiseTexture"));}static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters){return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5);}static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment){FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);}virtual bool Serialize(FArchive& Ar) override{bool bShaderHasOutdatedParameters = FGlobalShader::Serialize(Ar);//Serrilize something hereAr << PhlipSurface << PhiipNoiseTexture;return bShaderHasOutdatedParameters;}void BeginUseComputeShaderPhlip(FRHICommandList& RHICmdList, FUnorderedAccessViewRHIRef SurfaceTextureUAV, FShaderResourceViewRHIRef PhilipNoiseTextureSRV){FComputeShaderRHIParamRef ComputeShaderRHI = GetComputeShader();if (PhlipSurface.IsBound())RHICmdList.SetUAVParameter(ComputeShaderRHI, PhlipSurface.GetBaseIndex(), SurfaceTextureUAV);if (PhiipNoiseTexture.IsBound())RHICmdList.SetShaderResourceViewParameter(ComputeShaderRHI, PhiipNoiseTexture.GetBaseIndex(), PhilipNoiseTextureSRV);}void EndUseComputeShaderPhlip(FRHICommandList& RHICmdList){FComputeShaderRHIParamRef ComputeShaderRHI = GetComputeShader();if (PhlipSurface.IsBound())RHICmdList.SetUAVParameter(ComputeShaderRHI, PhlipSurface.GetBaseIndex(), FUnorderedAccessViewRHIRef());}void SetOceanUniformBuffer(FRHICommandList& RHICmdList, const FOceanBasicUniformBufferData& OceanStructData){SetUniformBufferParameterImmediate(RHICmdList, GetComputeShader(), GetUniformBufferParameter<FOceanBasicUniformBufferData>(), OceanStructData);}private:FShaderResourceParameter PhlipSurface;FShaderResourceParameter PhiipNoiseTexture;
};
IMPLEMENT_SHADER_TYPE(, FOceeanComputeShader_Phlip, TEXT("/Plugin/SDHOcean/HZeroGenerator.usf"), TEXT("ComputeHZero"), SF_Compute);OceanRenderHZeroPass::OceanRenderHZeroPass()
{bPassSuccessInit = false;bShouldInitPass = true;
}void OceanRenderHZeroPass::InitPass(const OceanRenderHZeroPassSetupData& SetupData)
{if (bShouldInitPass){FRHIResourceCreateInfo CreateInfo;OutputSurfaceTexture = RHICreateTexture2D(SetupData.OutputSizeX, SetupData.OutputSizeY, SetupData.OutputUAVFormat, 1, 1, TexCreate_ShaderResource | TexCreate_UAV, CreateInfo);OutputSurfaceTextureUAV = RHICreateUnorderedAccessView(OutputSurfaceTexture);OutputSurfaceTextureSRV = RHICreateShaderResourceView(OutputSurfaceTexture, 0);if (SetupData.PhillipsNoiseTexture2D != nullptr){PhillipsNoiseRHITexture = (FRHITexture2D*)(SetupData.PhillipsNoiseTexture2D->TextureReference.TextureReferenceRHI->GetReferencedTexture());PhillipsNoiseTextureSRV = RHICreateShaderResourceView(PhillipsNoiseRHITexture, 0);}if (OutputSurfaceTexture && OutputSurfaceTextureUAV && OutputSurfaceTextureSRV && PhillipsNoiseRHITexture && PhillipsNoiseTextureSRV)bPassSuccessInit = true;}bShouldInitPass = false;
}void OceanRenderHZeroPass::Draw(const OceanRenderHZeroPassSetupData& SetupData, FRHITexture* DebugRenderTargetRHITexture /*= null*/)
{if (bPassSuccessInit == false) return;ENQUEUE_RENDER_COMMAND(OceanRenderHZeroTextureCommand)([SetupData, this, DebugRenderTargetRHITexture](FRHICommandListImmediate& RHICmdList){check(IsInRenderingThread());TShaderMapRef<FOceeanComputeShader_Phlip> OceanComputeShader(GetGlobalShaderMap(SetupData.FeatureLevel));RHICmdList.SetComputeShader(OceanComputeShader->GetComputeShader());OceanComputeShader->BeginUseComputeShaderPhlip(RHICmdList, OutputSurfaceTextureUAV, PhillipsNoiseTextureSRV);//Update uniform bufferFOceanBasicUniformBufferData OceanBasicUniformData;OceanBasicUniformData.A_V_windDependency_T = FVector4(30, 150, 0.25, 0);OceanBasicUniformData.W_unused_unused = FVector4(0.5, 0.5, 0.0f, 0.0f);OceanBasicUniformData.width_height_Lx_Lz = FVector4(SetupData.OutputSizeX, SetupData.OutputSizeY, 2000.0f, 2000.0f);OceanBasicUniformData.TotalTimeElapsedSeconds = SetupData.WorldTimeSeconds;OceanComputeShader->SetOceanUniformBuffer(RHICmdList, OceanBasicUniformData);DispatchComputeShader(RHICmdList, *OceanComputeShader, SetupData.OutputSizeX / 32, SetupData.OutputSizeY / 32, 1);OceanComputeShader->EndUseComputeShaderPhlip(RHICmdList);if (DebugRenderTargetRHITexture != nullptr)RHICmdList.CopyToResolveTarget(OutputSurfaceTexture, DebugRenderTargetRHITexture, FResolveParams());});
}
HZeroPass.usf
#include "/Engine/Private/Common.ush"#define PI 3.1415926536f
#define TWOPI (2.f*PI)#define GRAVITY 981.0f //gravitational acceleration (cm/s^2)
#define HALF_SQRT_2 0.7071068fRWTexture2D<float4> PhlipSurface;
Texture2D<float4> PhiipNoiseTexture;float2 Get_K(in float2 pos)
{float2 k;float4 width_height_Lx_Lz = OceanBasicUniformBuffer.width_height_Lx_Lz;k.x = (pos.x * TWOPI / width_height_Lx_Lz.z);k.y = (pos.y * TWOPI / width_height_Lx_Lz.w);return k;
}float GetPhillipsSpectrum(in float2 k)
{float windDependency = OceanBasicUniformBuffer.A_V_windDependency_T.z;float A = OceanBasicUniformBuffer.A_V_windDependency_T.x;float V = OceanBasicUniformBuffer.A_V_windDependency_T.y;float2 W = OceanBasicUniformBuffer.W_unused_unused.xy;float L = (V * V) / GRAVITY;float l = L * 0.001f;float ksqr = dot(k, k);float result = 0.0f;if (ksqr > 0.0f) //avoid division by 0{float2 Wn = normalize(W); //normalize wind direction float2 kn = normalize(k);float kdotw = dot(kn, Wn);float k4 = ksqr * ksqr;float kL2 = ksqr * L * L;float exp_term = A * exp(-1.0f / kL2);float P_k = (exp_term / k4) * (kdotw * kdotw); //resulting Phillips spectrum//introduce wind dependencyif (kdotw < 0.0f){P_k *= windDependency;}//finally suppress waves smaller than a small length (l<<L)result = P_k * exp(-ksqr * l * l);}return result;
}[numthreads(32, 32, 1)]
void ComputeHZero(uint3 ThreadId : SV_DispatchThreadID)
{float2 pos = ThreadId.xy; //screen space position float2 K = Get_K(pos);float4 gaussRand = PhiipNoiseTexture.Load(int3(ThreadId.xy, 0));float phil = sqrt(GetPhillipsSpectrum(K));float phil_m = sqrt(GetPhillipsSpectrum(-K));//set the Phillips spectrum to variables with a more meaningful namefloat P_k = phil;float P_km = phil_m;//H0(k)float2 H0k = P_k * gaussRand.xy * HALF_SQRT_2;float2 H0mK = P_km * gaussRand.zw * HALF_SQRT_2;PhlipSurface[pos] = float4(H0k, H0mK);
}
在Render函数中调用HZerPass可以得到如下结果
//Compute HZero dataOceanRenderHZeroPassSetupData HZeroPassData;HZeroPassData.FeatureLevel = GetWorld()->FeatureLevel;HZeroPassData.OutputSizeX = 512;HZeroPassData.OutputSizeY = 512;HZeroPassData.OutputUAVFormat = PF_FloatRGBA;HZeroPassData.PhillipsNoiseTexture2D = PhillipsNoiseTexture2D;HZeroPassData.WorldTimeSeconds = TimeValue;OcenRenderer->OceanHZeroPass->InitPass(HZeroPassData);OcenRenderer->OceanHZeroPass->Draw(HZeroPassData);
FrequencyPass
Frequency.h
Frequencypass需要根据HZeroPass的计算结果,计算出XYZ三个轴向的Frequency。
#pragma once#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "RHI/Public/RHIResources.h"
#include "RHI/Public/RHICommandList.h"
#include "Classes/Engine/World.h"struct FrequencySpectrumPassSetupData
{int32 OutputSizeX;int32 OutputSizeY;EPixelFormat OutputUAVFormat;ERHIFeatureLevel::Type FeatureLevel;FShaderResourceViewRHIRef HZeroTextureSRV;float WorldTimeSeconds;
};//Pass data that The render resource using for each pass
class FrequencySpectrumPass
{
public:FrequencySpectrumPass();~FrequencySpectrumPass(){//We can't release PhillipsNoiseRHITextureif (SurfaceTextureX->IsValid())SurfaceTextureX->Release();if (SurfaceTextureX_UAV->IsValid())SurfaceTextureX_UAV->Release();if (SurfaceTextureX_SRV->IsValid())SurfaceTextureX_SRV->Release();if (SurfaceTextureY->IsValid())SurfaceTextureY->Release();if (SurfaceTextureY_UAV->IsValid())SurfaceTextureY_UAV->Release();if (SurfaceTextureY_SRV->IsValid())SurfaceTextureY_SRV->Release();if (SurfaceTextureZ->IsValid())SurfaceTextureZ->Release();if (SurfaceTextureZ_UAV->IsValid())SurfaceTextureZ_UAV->Release();if (SurfaceTextureZ_SRV->IsValid())SurfaceTextureZ_SRV->Release();}void InitPass(const FrequencySpectrumPassSetupData& SetupData);void Draw(const FrequencySpectrumPassSetupData& SetupData, FTextureRHIParamRef DebugRenderTarget = nullptr);FTexture2DRHIRef SurfaceTextureX;FUnorderedAccessViewRHIRef SurfaceTextureX_UAV;FShaderResourceViewRHIRef SurfaceTextureX_SRV;FTexture2DRHIRef SurfaceTextureY;FUnorderedAccessViewRHIRef SurfaceTextureY_UAV;FShaderResourceViewRHIRef SurfaceTextureY_SRV;FTexture2DRHIRef SurfaceTextureZ;FUnorderedAccessViewRHIRef SurfaceTextureZ_UAV;FShaderResourceViewRHIRef SurfaceTextureZ_SRV;FShaderResourceViewRHIRef HZeroTextureSRV;private://Flag tell us weather we can use this passbool bPassSuccessInit;//mark we can only excute initpss function oncebool bShouldInitPass;
};
Frequency.cpp
#include "SDHOcean/Private/OceanPass/FrequencySpectrum.h"#include "Runtime/RHI/Public/RHIResources.h"#include "RenderCore/Public/GlobalShader.h"
#include "RenderCore/Public/ShaderParameterUtils.h"
#include "RenderCore/Public/ShaderParameterMacros.h"#include "Classes/Engine/World.h"
#include "Public/GlobalShader.h"
#include "Public/PipelineStateCache.h"
#include "Public/RHIStaticStates.h"
#include "Public/SceneUtils.h"
#include "Public/SceneInterface.h"
#include "Public/ShaderParameterUtils.h"
#include "Public/Logging/MessageLog.h"
#include "Public/Internationalization/Internationalization.h"
#include "Public/StaticBoundShaderState.h"
#include "RHI/Public/RHICommandList.h"
#include "RHI/Public/RHIResources.h"
#include "Engine/Classes/Kismet/KismetRenderingLibrary.h"
#include "Runtime/Engine/Classes/Kismet/GameplayStatics.h"#include "Math/UnrealMathUtility.h"BEGIN_GLOBAL_SHADER_PARAMETER_STRUCT(FOceanFrequencyDrawData, )
SHADER_PARAMETER(float, OceanTime)
SHADER_PARAMETER(float, WaveAmplitude)
SHADER_PARAMETER(float, WindDependency)
SHADER_PARAMETER(float, WindSpeed)
SHADER_PARAMETER(FVector, WindDir)
END_GLOBAL_SHADER_PARAMETER_STRUCT()
IMPLEMENT_GLOBAL_SHADER_PARAMETER_STRUCT(FOceanFrequencyDrawData, "FOceanFrequencyDrawData");class FOceeanComputeShader_FrequencySpectrum : public FGlobalShader
{DECLARE_SHADER_TYPE(FOceeanComputeShader_FrequencySpectrum, Global)
public:FOceeanComputeShader_FrequencySpectrum() {}FOceeanComputeShader_FrequencySpectrum(const ShaderMetaType::CompiledShaderInitializerType& Initializer): FGlobalShader(Initializer){//TODO Bind pramerter hereSurfaceTextureX.Bind(Initializer.ParameterMap, TEXT("SurfaceTextureX"));SurfaceTextureY.Bind(Initializer.ParameterMap, TEXT("SurfaceTextureY"));SurfaceTextureZ.Bind(Initializer.ParameterMap, TEXT("SurfaceTextureZ"));HZeroTextureSRV.Bind(Initializer.ParameterMap, TEXT("HZeroTextureSRV"));}static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters){return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5);}static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment){FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);}virtual bool Serialize(FArchive& Ar) override{bool bShaderHasOutdatedParameters = FGlobalShader::Serialize(Ar);//Serrilize something hereAr << SurfaceTextureX << SurfaceTextureY << SurfaceTextureZ << HZeroTextureSRV;return bShaderHasOutdatedParameters;}void BeginUseComputeShader(FRHICommandList& RHICmdList, FUnorderedAccessViewRHIRef SurfaceTextureUAVX, FUnorderedAccessViewRHIRef SurfaceTextureUAVY, FUnorderedAccessViewRHIRef SurfaceTextureUAVZ, FShaderResourceViewRHIRef HZeroSurfaceTextureSRV){FComputeShaderRHIParamRef ComputeShaderRHI = GetComputeShader();if (SurfaceTextureX.IsBound())RHICmdList.SetUAVParameter(ComputeShaderRHI, SurfaceTextureX.GetBaseIndex(), SurfaceTextureUAVX);if (SurfaceTextureY.IsBound())RHICmdList.SetUAVParameter(ComputeShaderRHI, SurfaceTextureY.GetBaseIndex(), SurfaceTextureUAVY);if (SurfaceTextureZ.IsBound())RHICmdList.SetUAVParameter(ComputeShaderRHI, SurfaceTextureZ.GetBaseIndex(), SurfaceTextureUAVZ);if (HZeroTextureSRV.IsBound())RHICmdList.SetShaderResourceViewParameter(ComputeShaderRHI, HZeroTextureSRV.GetBaseIndex(), HZeroSurfaceTextureSRV);}void EndUseComputeShader(FRHICommandList& RHICmdList){FComputeShaderRHIParamRef ComputeShaderRHI = GetComputeShader();if (SurfaceTextureX.IsBound())RHICmdList.SetUAVParameter(ComputeShaderRHI, SurfaceTextureX.GetBaseIndex(), FUnorderedAccessViewRHIRef());if (SurfaceTextureY.IsBound())RHICmdList.SetUAVParameter(ComputeShaderRHI, SurfaceTextureY.GetBaseIndex(), FUnorderedAccessViewRHIRef());if (SurfaceTextureZ.IsBound())RHICmdList.SetUAVParameter(ComputeShaderRHI, SurfaceTextureZ.GetBaseIndex(), FUnorderedAccessViewRHIRef());if (HZeroTextureSRV.IsBound())RHICmdList.SetShaderResourceViewParameter(ComputeShaderRHI, HZeroTextureSRV.GetBaseIndex(), FShaderResourceViewRHIRef());}void SetOceanUniformBuffer(FRHICommandList& RHICmdList, const FOceanFrequencyDrawData& OceanStructData){SetUniformBufferParameterImmediate(RHICmdList, GetComputeShader(), GetUniformBufferParameter<FOceanFrequencyDrawData>(), OceanStructData);}private:FShaderResourceParameter SurfaceTextureX;FShaderResourceParameter SurfaceTextureY;FShaderResourceParameter SurfaceTextureZ;FShaderResourceParameter HZeroTextureSRV;
};
IMPLEMENT_SHADER_TYPE(, FOceeanComputeShader_FrequencySpectrum, TEXT("/Plugin/SDHOcean/FrequencySpectrum.usf"), TEXT("ComputeFrequency"), SF_Compute);FrequencySpectrumPass::FrequencySpectrumPass()
{bPassSuccessInit = false;bShouldInitPass = true;
}void FrequencySpectrumPass::InitPass(const FrequencySpectrumPassSetupData& SetupData)
{if (bShouldInitPass){FRHIResourceCreateInfo CreateInfo;SurfaceTextureX = RHICreateTexture2D(SetupData.OutputSizeX, SetupData.OutputSizeY, SetupData.OutputUAVFormat, 1, 1, TexCreate_ShaderResource | TexCreate_UAV, CreateInfo);SurfaceTextureX_UAV = RHICreateUnorderedAccessView(SurfaceTextureX);SurfaceTextureX_SRV = RHICreateShaderResourceView(SurfaceTextureX, 0);SurfaceTextureY = RHICreateTexture2D(SetupData.OutputSizeX, SetupData.OutputSizeY, SetupData.OutputUAVFormat, 1, 1, TexCreate_ShaderResource | TexCreate_UAV, CreateInfo);SurfaceTextureY_UAV = RHICreateUnorderedAccessView(SurfaceTextureY);SurfaceTextureY_SRV = RHICreateShaderResourceView(SurfaceTextureY, 0);SurfaceTextureZ = RHICreateTexture2D(SetupData.OutputSizeX, SetupData.OutputSizeY, SetupData.OutputUAVFormat, 1, 1, TexCreate_ShaderResource | TexCreate_UAV, CreateInfo);SurfaceTextureZ_UAV = RHICreateUnorderedAccessView(SurfaceTextureZ);SurfaceTextureZ_SRV = RHICreateShaderResourceView(SurfaceTextureZ, 0);HZeroTextureSRV = SetupData.HZeroTextureSRV;if (SurfaceTextureX && SurfaceTextureX_UAV && SurfaceTextureX_SRV &&SurfaceTextureY &&SurfaceTextureY_UAV &&SurfaceTextureY_SRV &&SurfaceTextureZ &&SurfaceTextureZ_UAV &&SurfaceTextureZ_SRV &&HZeroTextureSRV)bPassSuccessInit = true;}bShouldInitPass = false;
}void FrequencySpectrumPass::Draw(const FrequencySpectrumPassSetupData& SetupData, FTextureRHIParamRef DebugRenderTarget /* = nullptr */)
{if (bPassSuccessInit == false) return;ENQUEUE_RENDER_COMMAND(FrequencySpectrumCommand)([SetupData, this, DebugRenderTarget](FRHICommandListImmediate& RHICmdList){check(IsInRenderingThread());TShaderMapRef<FOceeanComputeShader_FrequencySpectrum> OceanComputeShader(GetGlobalShaderMap(SetupData.FeatureLevel));RHICmdList.SetComputeShader(OceanComputeShader->GetComputeShader());OceanComputeShader->BeginUseComputeShader(RHICmdList, SurfaceTextureX_UAV, SurfaceTextureY_UAV, SurfaceTextureZ_UAV, HZeroTextureSRV);//Update uniform bufferFOceanFrequencyDrawData OceanBasicUniformData;OceanBasicUniformData.OceanTime = SetupData.WorldTimeSeconds * 0.1;OceanBasicUniformData.WindSpeed = 150.0f;OceanBasicUniformData.WaveAmplitude = 30.0f;OceanBasicUniformData.WindDependency = 0.25f;OceanBasicUniformData.WindDir = FVector(0.5, 0.5, 0);OceanComputeShader->SetOceanUniformBuffer(RHICmdList, OceanBasicUniformData);DispatchComputeShader(RHICmdList, *OceanComputeShader, SetupData.OutputSizeX / 32, SetupData.OutputSizeY / 32, 1);OceanComputeShader->EndUseComputeShader(RHICmdList);if (DebugRenderTarget != nullptr)RHICmdList.CopyToResolveTarget(SurfaceTextureY, DebugRenderTarget, FResolveParams());});
}
Frequency.usf
#include "/Engine/Private/Common.ush"#define PI 3.1415926536f
#define TWOPI (2.f*PI)#define GRAVITY 981.0f //gravitational acceleration (cm/s^2)
#define HALF_SQRT_2 0.7071068fTexture2D<float4> HZeroTextureSRV;RWTexture2D<float2> SurfaceTextureX;
RWTexture2D<float2> SurfaceTextureY;
RWTexture2D<float2> SurfaceTextureZ;float2 Get_K(in float2 pos)
{float2 k;const float4 width_height_Lx_Lz = float4(512, 512, 2000, 2000);k.x = (pos.x * TWOPI / width_height_Lx_Lz.z);k.y = (pos.y * TWOPI / width_height_Lx_Lz.w);return k;
}[numthreads(32, 32, 1)]
void ComputeFrequency(uint3 ThreadId : SV_DispatchThreadID)
{float2 pos = ThreadId.xy; //screen space position float4 h0k_h0mk = HZeroTextureSRV.Load(int3(pos, 0));float2 K = Get_K(pos); //get the wave vector //start calculating H(k,t) first then Dx and Dzconst float gTime = FOceanFrequencyDrawData.OceanTime;const float T = 0;const float2 H0k = h0k_h0mk.xy;const float2 h0_mk = h0k_h0mk.zw;//angular frequency is following the dispersion relation:// out_omega^2 = g*kfloat omega = sqrt(GRAVITY * length(K));float sin_v, cos_v;sincos(omega * gTime, sin_v, cos_v);//Dy vertical displacementfloat2 HKt;HKt.x = (H0k.x + h0_mk.x) * cos_v - (H0k.y + h0_mk.y) * sin_v;HKt.y = (H0k.x - h0_mk.x) * sin_v + (H0k.y - h0_mk.y) * cos_v;float Ksqr = dot(K, K);float rsqr_k = 0;if (Ksqr > 1e-12f){rsqr_k = 1 / sqrt(Ksqr);}float2 Knorm = K * rsqr_k;//Dx,Dz horizontal displacements in frequency domainfloat2 DxKt = float2(HKt.y * Knorm.x, -HKt.x * Knorm.x);float2 DzKt = float2(HKt.y * Knorm.y, -HKt.x * Knorm.y);//output frequency spectrum for dx,dy,dz to UAVsSurfaceTextureX[pos] = DxKt;SurfaceTextureY[pos] = HKt;SurfaceTextureZ[pos] = DzKt;
}
在OceanRender函数中调用之后可以得到如下结果。注意RenderTexture的格式
TwiddlePass
Twiddlepass负责生成IFFT过程蝶形变换需要用到的旋转因子。IFFT变换的时候直接去查因子就能快速算出结果。
TwiddlePass.h
#pragma once#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "RHI/Public/RHIResources.h"
#include "RHI/Public/RHICommandList.h"
#include "Classes/Engine/World.h"struct OceanRenderTwiddlePassSetupData
{int32 OutputSizeX;int32 OutputSizeY;EPixelFormat OutputUAVFormat;ERHIFeatureLevel::Type FeatureLevel;
};//Pass data that The render resource using for each pass
class OceanRenderTwiddlePass
{
public:OceanRenderTwiddlePass();~OceanRenderTwiddlePass(){if (OutputSurfaceTexture->IsValid())OutputSurfaceTexture->Release();if (OutputSurfaceTextureSRV->IsValid())OutputSurfaceTextureSRV->Release();if (OutputSurfaceTextureUAV->IsValid())OutputSurfaceTextureUAV->Release();}void InitPass(const OceanRenderTwiddlePassSetupData& SetupData);void Draw(const OceanRenderTwiddlePassSetupData& SetupData, FRHITexture* DebugRenderTargetRHITexture = nullptr);FTexture2DRHIRef OutputSurfaceTexture;FUnorderedAccessViewRHIRef OutputSurfaceTextureUAV;FShaderResourceViewRHIRef OutputSurfaceTextureSRV;private://Flag tell us weather we can use this passbool bPassSuccessInit;//mark we can only excute initpss function oncebool bShouldInitPass;
};
TwiddlePass.cpp
#include "SDHOcean/Private/OceanPass/OceanTwiddlePass.h"#include "RenderCore/Public/GlobalShader.h"
#include "RenderCore/Public/ShaderParameterUtils.h"
#include "RenderCore/Public/ShaderParameterMacros.h"#include "Classes/Engine/World.h"
#include "Public/GlobalShader.h"
#include "Public/PipelineStateCache.h"
#include "Public/RHIStaticStates.h"
#include "Public/SceneUtils.h"
#include "Public/SceneInterface.h"
#include "Public/ShaderParameterUtils.h"
#include "Public/Logging/MessageLog.h"
#include "Public/Internationalization/Internationalization.h"
#include "Public/StaticBoundShaderState.h"
#include "RHI/Public/RHICommandList.h"
#include "RHI/Public/RHIResources.h"
#include "Engine/Classes/Kismet/KismetRenderingLibrary.h"
#include "Runtime/Engine/Classes/Kismet/GameplayStatics.h"#include "Math/UnrealMathUtility.h"class FOceeanComputeShader_TwiddleIndice : public FGlobalShader
{DECLARE_SHADER_TYPE(FOceeanComputeShader_TwiddleIndice, Global)
public:FOceeanComputeShader_TwiddleIndice() {}FOceeanComputeShader_TwiddleIndice(const ShaderMetaType::CompiledShaderInitializerType& Initializer): FGlobalShader(Initializer){//TODO Bind pramerter hereTwiddleIndiceSurface.Bind(Initializer.ParameterMap, TEXT("TwiddleIndices"));}static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters){return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5);}static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment){FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);}virtual bool Serialize(FArchive& Ar) override{bool bShaderHasOutdatedParameters = FGlobalShader::Serialize(Ar);//Serrilize something hereAr << TwiddleIndiceSurface;return bShaderHasOutdatedParameters;}void BeginUseComputeShaderTwiddle(FRHICommandList& RHICmdList, FUnorderedAccessViewRHIRef SurfaceTextureUAV){FComputeShaderRHIParamRef ComputeShaderRHI = GetComputeShader();if (TwiddleIndiceSurface.IsBound())RHICmdList.SetUAVParameter(ComputeShaderRHI, TwiddleIndiceSurface.GetBaseIndex(), SurfaceTextureUAV);}void EndUseComputeShaderTwiddle(FRHICommandList& RHICmdList){FComputeShaderRHIParamRef ComputeShaderRHI = GetComputeShader();if (TwiddleIndiceSurface.IsBound())RHICmdList.SetUAVParameter(ComputeShaderRHI, TwiddleIndiceSurface.GetBaseIndex(), FUnorderedAccessViewRHIRef());}private:FShaderResourceParameter TwiddleIndiceSurface;
};
IMPLEMENT_SHADER_TYPE(, FOceeanComputeShader_TwiddleIndice, TEXT("/Plugin/SDHOcean/TwiddleIndex.usf"), TEXT("OceanTwiddleIndiceMainCSY"), SF_Compute);OceanRenderTwiddlePass::OceanRenderTwiddlePass()
{bPassSuccessInit = false;bShouldInitPass = true;
}void OceanRenderTwiddlePass::Draw(const OceanRenderTwiddlePassSetupData& SetupData, FRHITexture* DebugRenderTargetRHITexture /* = nullptr */)
{ENQUEUE_RENDER_COMMAND(OceanRenderTwiddleIndiceTextureCommand)([SetupData, this, DebugRenderTargetRHITexture](FRHICommandListImmediate& RHICmdList){check(IsInRenderingThread());TShaderMapRef<FOceeanComputeShader_TwiddleIndice> OceanComputeShader(GetGlobalShaderMap(SetupData.FeatureLevel));RHICmdList.SetComputeShader(OceanComputeShader->GetComputeShader());OceanComputeShader->BeginUseComputeShaderTwiddle(RHICmdList, OutputSurfaceTextureUAV);DispatchComputeShader(RHICmdList, *OceanComputeShader, SetupData.OutputSizeX / 32, SetupData.OutputSizeY / 32, 1);OceanComputeShader->EndUseComputeShaderTwiddle(RHICmdList);FResolveParams ResolveParm;//ResolveParm.Rect = FResolveRect(0, 0, TextureSize, TextureSize);//ResolveParm.DestRect = FResolveRect(0, 0, TextureSize, TextureSize);RHICmdList.CopyToResolveTarget(OutputSurfaceTexture, DebugRenderTargetRHITexture, ResolveParm);});
}void OceanRenderTwiddlePass::InitPass(const OceanRenderTwiddlePassSetupData& SetupData)
{if (bShouldInitPass){FRHIResourceCreateInfo CreateInfo;OutputSurfaceTexture = RHICreateTexture2D(SetupData.OutputSizeX, SetupData.OutputSizeY, SetupData.OutputUAVFormat, 1, 1, TexCreate_ShaderResource | TexCreate_UAV, CreateInfo);OutputSurfaceTextureUAV = RHICreateUnorderedAccessView(OutputSurfaceTexture);OutputSurfaceTextureSRV = RHICreateShaderResourceView(OutputSurfaceTexture, 0);if (OutputSurfaceTexture && OutputSurfaceTextureUAV && OutputSurfaceTextureSRV)bPassSuccessInit = true;}bShouldInitPass = false;
}
TwiddlePass.usf
#include "/Engine/Private/Common.ush"#define COHERENCY_GRANULARITY 32#define M_PI 3.1415926535897932384626433832795f
#define PI 3.1415926535897932384626433832795f
#define M_2PI (2.0f*M_PI)//Number of samples is fixed for this release
static const int N = 512;//number of stages
static const int log2_N = (int) (log(N) / log(2));RWTexture2D<float4> TwiddleIndices;struct Complex
{float Re;float Im;
};Complex Add(Complex c1, Complex c2)
{Complex c;c.Re = c1.Re + c2.Re;c.Im = c1.Im + c2.Im;return c;
}Complex Sub(Complex c1, Complex c2)
{Complex c;c.Re = c1.Re - c2.Re;c.Im = c1.Im - c2.Im;return c;
}Complex Mul(Complex c1, Complex c2)
{Complex c;c.Re = c1.Re * c2.Re;c.Im = c1.Im * c2.Im;return c;
}Complex Conj(Complex c)
{c.Im = -c.Im;return c;
}[numthreads(1, 32, 1)]
void OceanTwiddleIndiceMainCSY(uint3 ThreadId : SV_DispatchThreadID)
{// Set up some variables we are going to need float sizeX, sizeY;TwiddleIndices.GetDimensions(sizeX, sizeY);float k = ThreadId.y * sizeY / pow(2.0, ThreadId.x + 1) % sizeY;// Twiddle factorComplex Twiddle;Twiddle.Re = cos(2 * PI * k / sizeY);Twiddle.Im = sin(2 * PI * k / sizeY);// Strideint ButterflySpan = pow(2, ThreadId.x);// Judege ButterflyWing is TopWing or BottomWingint ButterflyWing;if (ThreadId.y % pow(2, ThreadId.x + 1) < pow(2, ThreadId.x))ButterflyWing = 1;elseButterflyWing = 0;// First stage, bit reserved indicesif (ThreadId.x == 0){// Invert Orderint InitIndices[512];int Levels = log2(sizeY);int i;for (i = 0; i < Levels; i++)InitIndices[i] = 0;for (i = 0; i < Levels; i++){int Stride = 1 << i;int Add = 1 << (Levels - 1 - i);for (int j = 0; j < sizeY; j++){if ((j / Stride) % 2 != 0)InitIndices[j] += Add;}}if (ButterflyWing == 1)TwiddleIndices[ThreadId.xy] = float4(Twiddle.Re, Twiddle.Im, InitIndices[ThreadId.y], InitIndices[ThreadId.y + 1]);elseTwiddleIndices[ThreadId.xy] = float4(Twiddle.Re, Twiddle.Im, InitIndices[ThreadId.y - 1], InitIndices[ThreadId.y]);}// Log2N stageelse{if (ButterflyWing == 1)TwiddleIndices[ThreadId.xy] = float4(Twiddle.Re, Twiddle.Im, ThreadId.y, ThreadId.y + ButterflySpan);elseTwiddleIndices[ThreadId.xy] = float4(Twiddle.Re, Twiddle.Im, ThreadId.y - ButterflySpan, ThreadId.y);}//TwiddleIndices[ThreadId.xy] = float4(1, 0, 0, 1);}
需要强调的是TwiddlePass只能执行一次。
IFFTButterFlyPass
前面的Pass准备好Twiddle和FrequencyX,FrequencyY,FrequencyZ以后就可以开始IFFT蝶形变换了,这一步是把Frequency中的频率变到空间域中。
SetupData会把Twiddle,和FrequencyXYZ中的一张传进来,然后IFFTPass自己还会有一张UAV,和一个空的FInaleTextureSRV。
FinalTextureSRV是用来拿到乒乓蝶形变换后的那张最终输出的UAV对应的Texture的SRV的。
在Draw函数中我们需要做一个乒乓缓冲来迭代每次结果。Draw的时候我们需要在水平方向和竖直方向进行FFT变换,每次变换根据TwiddleTexture的Stage进行查询。Stage使用一个UniformBuffer传到shader里,从而不让shader采到无用像素
IFFTButterFlyPass.h
#pragma once#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "RHI/Public/RHIResources.h"
#include "RHI/Public/RHICommandList.h"
#include "Classes/Engine/World.h"struct OceanRenderButterFlyPassSetupData
{int32 OutputSizeX;int32 OutputSizeY;EPixelFormat OutputUAVFormat;ERHIFeatureLevel::Type FeatureLevel;FShaderResourceViewRHIRef TwiddleTextureSRV;//Using Frequency data to init it at first frameFTexture2DRHIRef FrequencySpectrumTexture;FUnorderedAccessViewRHIRef FrequencySpectrumTextureUAV;FShaderResourceViewRHIRef FrequencySpectrumTextureSRV;
};//Pass data that The render resource using for each pass
class OceanRenderButterFlyPass
{
public:OceanRenderButterFlyPass();~OceanRenderButterFlyPass(){//ButterFlyTexture_A and ButterFlyUAV_A and ButterFlySRV_A is created by other pass, we can't release itif (ButterFlyTexture_B->IsValid())ButterFlyTexture_B->Release();if (ButterFlyUAV_B->IsValid())ButterFlyUAV_B->Release();if (ButterFlySRV_B->IsValid())ButterFlySRV_B->Release();}void InitPass(const OceanRenderButterFlyPassSetupData& SetupData);void Draw(const OceanRenderButterFlyPassSetupData& SetupData, FRHITexture* DebugRenderTargetRHITexture = nullptr);FTexture2DRHIRef ButterFlyTexture_A;FTexture2DRHIRef ButterFlyTexture_B;FUnorderedAccessViewRHIRef ButterFlyUAV_A;FUnorderedAccessViewRHIRef ButterFlyUAV_B;FShaderResourceViewRHIRef ButterFlySRV_A;FShaderResourceViewRHIRef ButterFlySRV_B;FShaderResourceViewRHIRef TwiddleTextureSRV;FShaderResourceViewRHIRef FinalTextureSRV;private://Flag tell us weather we can use this passbool bPassSuccessInit;//mark we can only excute initpss function oncebool bShouldInitPass;
};
IFFTOceanPass.cpp
#include "SDHOcean/Private/OceanPass/OceanFFTButterFlyPass.h"#include "RenderCore/Public/GlobalShader.h"
#include "RenderCore/Public/ShaderParameterUtils.h"
#include "RenderCore/Public/ShaderParameterMacros.h"#include "Classes/Engine/World.h"
#include "Public/GlobalShader.h"
#include "Public/PipelineStateCache.h"
#include "Public/RHIStaticStates.h"
#include "Public/SceneUtils.h"
#include "Public/SceneInterface.h"
#include "Public/ShaderParameterUtils.h"
#include "Public/Logging/MessageLog.h"
#include "Public/Internationalization/Internationalization.h"
#include "Public/StaticBoundShaderState.h"
#include "RHI/Public/RHICommandList.h"
#include "RHI/Public/RHIResources.h"
#include "Engine/Classes/Kismet/KismetRenderingLibrary.h"
#include "Runtime/Engine/Classes/Kismet/GameplayStatics.h"#include "Math/UnrealMathUtility.h"BEGIN_GLOBAL_SHADER_PARAMETER_STRUCT(FCSIFFTData, )
SHADER_PARAMETER(uint32, Stage)
SHADER_PARAMETER(uint32, Dir)
END_GLOBAL_SHADER_PARAMETER_STRUCT()
IMPLEMENT_GLOBAL_SHADER_PARAMETER_STRUCT(FCSIFFTData, "IFFTData");static uint32 GetStage(const uint32 N) {if (N == 0)return -1;return StaticCast<uint32>((log(StaticCast<float>(N)) / log(2.0f)));
}class FOceeanComputeShader_IFFT : public FGlobalShader
{DECLARE_SHADER_TYPE(FOceeanComputeShader_IFFT, Global)
public:FOceeanComputeShader_IFFT() {}FOceeanComputeShader_IFFT(const ShaderMetaType::CompiledShaderInitializerType& Initializer): FGlobalShader(Initializer){//TODO Bind pramerter hereInputSurface.Bind(Initializer.ParameterMap, TEXT("PreviousSurface"));OutputSurface.Bind(Initializer.ParameterMap, TEXT("OutputSurface"));TwiddleTexture.Bind(Initializer.ParameterMap, TEXT("TwiddleTexture"));}static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters){return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5);}static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment){FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);}virtual bool Serialize(FArchive& Ar) override{bool bShaderHasOutdatedParameters = FGlobalShader::Serialize(Ar);//Serrilize something hereAr << InputSurface << OutputSurface << TwiddleTexture;return bShaderHasOutdatedParameters;}void BeginUseComputeShaderIFFT(FRHICommandList& RHICmdList, FUnorderedAccessViewRHIRef OutputTextureUAV, FShaderResourceViewRHIRef InputTextureSRV, FShaderResourceViewRHIRef TwiddleTextureSRV){FComputeShaderRHIParamRef ComputeShaderRHI = GetComputeShader();if (OutputSurface.IsBound())RHICmdList.SetUAVParameter(ComputeShaderRHI, OutputSurface.GetBaseIndex(), OutputTextureUAV);if (InputSurface.IsBound())RHICmdList.SetShaderResourceViewParameter(ComputeShaderRHI, InputSurface.GetBaseIndex(), InputTextureSRV);if (TwiddleTexture.IsBound())RHICmdList.SetShaderResourceViewParameter(ComputeShaderRHI, TwiddleTexture.GetBaseIndex(), TwiddleTextureSRV);}void EndUseComputeShaderIFFT(FRHICommandList& RHICmdList){FComputeShaderRHIParamRef ComputeShaderRHI = GetComputeShader();if (OutputSurface.IsBound())RHICmdList.SetUAVParameter(ComputeShaderRHI, OutputSurface.GetBaseIndex(), FUnorderedAccessViewRHIRef());if (InputSurface.IsBound())RHICmdList.SetShaderResourceViewParameter(ComputeShaderRHI, OutputSurface.GetBaseIndex(), FShaderResourceViewRHIRef());if (TwiddleTexture.IsBound())RHICmdList.SetShaderResourceViewParameter(ComputeShaderRHI, TwiddleTexture.GetBaseIndex(), FShaderResourceViewRHIRef());}void SetOceanUniformBuffer(FRHICommandList& RHICmdList, const FCSIFFTData& IFFTData){SetUniformBufferParameterImmediate(RHICmdList, GetComputeShader(), GetUniformBufferParameter<FCSIFFTData>(), IFFTData);}private:FShaderResourceParameter InputSurface;FShaderResourceParameter OutputSurface;FShaderResourceParameter TwiddleTexture;
};
IMPLEMENT_SHADER_TYPE(, FOceeanComputeShader_IFFT, TEXT("/Plugin/SDHOcean/OceanIFFTCompute.usf"), TEXT("ComputeButterflyCS"), SF_Compute);OceanRenderButterFlyPass::OceanRenderButterFlyPass()
{bPassSuccessInit = false;bShouldInitPass = true;
}void OceanRenderButterFlyPass::Draw(const OceanRenderButterFlyPassSetupData& SetupData, FRHITexture* DebugRenderTargetRHITexture /* = nullptr */)
{ENQUEUE_RENDER_COMMAND(FOceanIFFTCommand)([SetupData, this, DebugRenderTargetRHITexture](FRHICommandListImmediate& RHICmdList){check(IsInRenderingThread());TShaderMapRef<FOceeanComputeShader_IFFT> ComputeShader(GetGlobalShaderMap(SetupData.FeatureLevel));RHICmdList.SetComputeShader(ComputeShader->GetComputeShader());FShaderResourceViewRHIRef SRV[2];SRV[0] = ButterFlySRV_A;SRV[1] = ButterFlySRV_B;FUnorderedAccessViewRHIRef UAV[2];UAV[0] = ButterFlyUAV_A;UAV[1] = ButterFlyUAV_B;const uint32 StageNumber = GetStage(SetupData.OutputSizeX);const uint32 GroupThreadX = SetupData.OutputSizeX / 32;const uint32 GroupThreadY = SetupData.OutputSizeY / 32;FCSIFFTData IFFTData;uint32 FrameIndex = 0;for (uint32 Dir = 0; Dir < 2; ++Dir){IFFTData.Dir = Dir;for (uint32 StageIndex = 0; StageIndex < StageNumber; ++StageIndex){IFFTData.Stage = StageIndex;const uint32 InputBufferIndex = FrameIndex % 2;const uint32 OutputBufferIndex = (FrameIndex + 1) % 2;ComputeShader->BeginUseComputeShaderIFFT(RHICmdList, UAV[OutputBufferIndex], SRV[InputBufferIndex], TwiddleTextureSRV);ComputeShader->SetOceanUniformBuffer(RHICmdList, IFFTData);DispatchComputeShader(RHICmdList, *ComputeShader, GroupThreadX, GroupThreadY, 1);ComputeShader->EndUseComputeShaderIFFT(RHICmdList);FrameIndex++;}}FinalTextureSRV = SRV[(FrameIndex % 2)];});
}void OceanRenderButterFlyPass::InitPass(const OceanRenderButterFlyPassSetupData& SetupData)
{if (bShouldInitPass){ButterFlyTexture_A = SetupData.FrequencySpectrumTexture;ButterFlyUAV_A = SetupData.FrequencySpectrumTextureUAV;ButterFlySRV_A = SetupData.FrequencySpectrumTextureSRV;TwiddleTextureSRV = SetupData.TwiddleTextureSRV;FRHIResourceCreateInfo CreateInfo;ButterFlyTexture_B = RHICreateTexture2D(SetupData.OutputSizeX, SetupData.OutputSizeY, SetupData.OutputUAVFormat, 1, 1, TexCreate_ShaderResource | TexCreate_UAV, CreateInfo);ButterFlyUAV_B = RHICreateUnorderedAccessView(ButterFlyTexture_B);ButterFlySRV_B = RHICreateShaderResourceView(ButterFlyTexture_B, 0);if (ButterFlySRV_A && ButterFlyTexture_A && ButterFlyUAV_A && ButterFlySRV_B && ButterFlyTexture_B && ButterFlyUAV_B && TwiddleTextureSRV)bPassSuccessInit = true;}bShouldInitPass = false;
}
OceanIFFTCompute.usf
#include "/Engine/Private/Common.ush"float2 mult(in float2 c0, in float2 c1)
{float2 c;c.x = c0.x * c1.x - c0.y * c1.y;c.y = c0.x * c1.y + c0.y * c1.x;return c;
}float2 add(in float2 c0, in float2 c1)
{float2 c;c.x = c0.x + c1.x;c.y = c0.y + c1.y;return c;
}//Texture should be replaced with not typed buffer (i.e. structured buffer etc.) because they are more efficent
Texture2D<float4> TwiddleTexture; //Twiddle factors/indices texture
Texture2D<float2> PreviousSurface; //previous pass data
RWTexture2D<float4> OutputSurface; //output surface //RWStructuredBuffer<float4> g_DstData : register(u0);
//RWTexture2D<float4> g_DstDataTexture : register(u1);void HorizontalButterfly(in uint2 AbsoluteThreadId)
{int stage = IFFTData.Stage;float4 data = TwiddleTexture.Load(int3(stage, AbsoluteThreadId.x, 0)).xyzw;float2 p_ = PreviousSurface.Load(int3(data.z, AbsoluteThreadId.y, 0)).xy;float2 q_ = PreviousSurface.Load(int3(data.w, AbsoluteThreadId.y, 0)).xy;float2 w_ = data.xy;//Butterfly operationfloat2 H = add(p_, mult(w_, q_));OutputSurface[AbsoluteThreadId] = float4(H, 0, 0);
}void VerticalButterfly(in uint2 AbsoluteThreadId)
{int stage = IFFTData.Stage;float4 data = TwiddleTexture.Load(int3(stage, AbsoluteThreadId.y, 0)).xyzw;float2 p_ = PreviousSurface.Load(int3(AbsoluteThreadId.x, data.z, 0)).xy;float2 q_ = PreviousSurface.Load(int3(AbsoluteThreadId.x, data.w, 0)).xy;float2 w_ = data.xy;//Butterfly operationfloat2 H = add(p_, mult(w_, q_));OutputSurface[AbsoluteThreadId] = float4(H, 0, 0);
}//Butterfly Kernel
[numthreads(32, 32, 1)]
void ComputeButterflyCS(uint3 GlobalThreadIndex : SV_DispatchThreadID)
{//OutputSurface0[GlobalThreadIndex.xy] = InputTexture.Load(int3(GlobalThreadIndex.xy,0));[branch]if (IFFTData.Dir == 0){HorizontalButterfly(GlobalThreadIndex.xy);}else{VerticalButterfly(GlobalThreadIndex.xy);}
}
我们需要在Render函数中分别对XYZ执行三次Draw
CopyDataPass
这个pass就非常简单了,就是把ComputeShader计算出来的SRV画到RT上
CopyDataPass.h
#pragma once#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "RHI/Public/RHIResources.h"
#include "RHI/Public/RHICommandList.h"
#include "Classes/Engine/World.h"struct CopyPassSetupData
{int32 OutputSizeX;int32 OutputSizeY;ERHIFeatureLevel::Type FeatureLevel;FShaderResourceViewRHIRef ResorceToCopy;
};//Simple draw SRV to rendertarget
class CopyDataPass
{
public:CopyDataPass(){}~CopyDataPass(){}void Draw(CopyPassSetupData& SetupData, UTextureRenderTarget2D* RenderTarget);
};
CopyPass.cpp
这里因为需要采贴图所以需要自己声明一个顶点格式
下面是一个平铺屏幕空间的VB
BV对应的IB,并且要在全局实例化它们。最后就可以开始制作简单的PS了
最后设置好渲染管线的各个状态然后调用Draw即可
shader也非常简单直接把SRV用load函数draw到RT上就可以了
最后在海洋渲染主函数中调用XYZ三次拷贝
最后直接把XYZ的置换贴图丢到材质里就可以进入渲染阶段了
https://www.zhihu.com/video/1117846976418463744
调整菲利普频谱的生成函数就可以动态实时调整海面的起伏
https://www.zhihu.com/video/1117849279871537152
FFT海洋比起其它几种制作海洋的方式优点就在于波形丰富,可以实时方便调整,适用于动态天气的海洋。如果想要制作浮力等效果,FFT的方法也能提供足够的模拟计算资源。
至此我们完成了FFT海洋的波形部分,可以看到整个过程十分艰辛,难度真的是在其它环境里实现的好几十倍。有时候误打误撞得到了一些奇怪的效果,比如翻滚的云海
下一节我将介绍海面的Shading部分。
Enjoy it.
虚幻4渲染编程(环境模拟篇)【第八卷:海洋模拟-中-在UE中实现FFT海洋】相关推荐
- unity scence灯光不显示_Unity渲染编程(灯光篇)【第二卷:MobileVolumetricLight】
MY BLOG DIRECTORY: todo... INTRODUCTION: 如果需要一个方案来渲染城镇或空旷的马路上巨量的灯光.图形程序拿到这个需求直接开始搞F+或者延迟光照,但是对于移动端的巨 ...
- 虚幻4渲染编程(环境模拟篇)【第三卷:体积云天空模拟(3)---高层云】
我的专栏目录: 小IVan:专题概述及目录 目前业内流行有两种体积云模拟的方式,模型+特殊shader法,RayMarching法.我前两篇文章已经对它们都做了介绍.当然还有些比较非主流的,比如粒子云 ...
- 虚幻4渲染编程(环境模拟篇)【第五卷:可交互物理植被模拟 - 上】
我的专栏目录: 小IVan:专题概述及目录 开篇综述 这一卷将会开始研究可交互植被环境的模拟.我把可交互植被环境模拟这个大的课题拆解为几个部分.我挑选了几个森林模拟至关重要的几个要素并且实现它们. [ ...
- 虚幻4渲染编程(图元汇编篇)【第五卷:游戏中的动力学模拟】
我的专栏目录 小IVan:专题概述及目录 还是先上效果吧 目前(2018年)在游戏中,通常使用韦尔莱积分做动力学模拟.使用韦尔莱积分可以模拟大部分物体的运动.布料,绳子,弹簧,软体,棍子都可以模拟.但 ...
- 渲染到ui_虚幻4渲染编程(UI篇)【第二卷:程序化UI特效-[1]】
MY BLOG DIRECTORY: 小IVan:专题概述及目录zhuanlan.zhihu.com INTRODUCTION: 当遇见某些特殊需求,比如对游戏效果有很多变化的要求,这时使用静态的贴 ...
- 虚幻4皮肤材质_虚幻4渲染编程(材质编辑器篇)【第六卷:各向异性材质amp;玻璃材质】...
My blog directory: YivanLee:专题概述及目录zhuanlan.zhihu.com Introduction: 各向异性材质 玻璃材质 材质编辑器篇的很多效果都非常简单,可以 ...
- 虚幻4渲染编程(材质编辑器篇)【第三卷:正式准备开始材质开发】
My blog directory: YivanLee:专题概述及目录 Introduction: 前面两章我们已经完成了对工具的研究,下面我们久正式开始启程啦!后面的内容可能就比较美术了. 还是老规 ...
- 虚幻4渲染编程(光线追踪篇)【第一卷:光线追踪篇开篇综述】
MY BLOG DIRECTORY: 小IVan:专题概述及目录zhuanlan.zhihu.com INTRODUCTION: 什么都不说了先上个效果: 光线追踪云 电子游戏的光线追踪时代即将到来 ...
- 虚幻4渲染编程(材质编辑器篇)【第五卷:布料,丝绸纱皮革棉】
My blog directory: 小IVan:专题概述及目录 Introduction: 现在的游戏对质感要求越来越高(我估计是硬件越来越好,可编程管线越来越来越完善).游戏的画面已经越来越接近影 ...
- 虚幻4渲染编程(灯光篇)【第二卷:体积光】
我的专栏目录: 小IVan:专题概述及目录 体积光在游戏里被越来越多地用到,对烘托场景气氛,提高游戏的逼格有比较重要的作用.这篇就来由浅入深研究一下这个东西.从容易的做法到高端做法依次递进. 首先先来 ...
最新文章
- Docker学习(8)——构建镜像(Dockerfile、commit)
- NFV节省企业宽带成本—Vecloud微云
- CNN之性能指标:卷积神经网络中常用的性能指标(IOU/AP/mAP、混淆矩阵)简介、使用方法之详细攻略
- 这个网站绝了,收录近600条Linux系统命令
- AD18-画PCB步骤-总结
- Java对数组的操作(二)——集合与数组的切换
- 论文浅尝 | 基于事理图谱的脚本事件预测
- 报名 | 全国知识图谱与语义计算大会(CCKS 2019)评测任务发布
- assign深拷贝_Object.assign 深拷贝?浅拷贝?
- ofo已还清蚂蚁金服欠款?回应:消息不实 但没有放弃
- 为什么索引不支持模糊查询_百度站长平台查询的关键词排名,为什么与实际不符合?...
- delphi中griddata控件写入float数值_年中巨献!明道云发布多项重磅功能
- Android添加拍照功能,Android自定义相机,添加水印
- 长虹声纹识别技术推动家电产业向高阶形态发展
- 微信小程序云开发教程-小程序在云开发下的运作模式
- c语言中期报告程序,课题中期报告
- office2010每次打开总是出现配置进度
- zblog php 二级菜单,zblog博客系统二级(下拉)导航菜单设置教程
- Echarts柱状图上加图标
- 101. Domino 10 就要来了