我的专栏目录:

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海洋】相关推荐

  1. unity scence灯光不显示_Unity渲染编程(灯光篇)【第二卷:MobileVolumetricLight】

    MY BLOG DIRECTORY: todo... INTRODUCTION: 如果需要一个方案来渲染城镇或空旷的马路上巨量的灯光.图形程序拿到这个需求直接开始搞F+或者延迟光照,但是对于移动端的巨 ...

  2. 虚幻4渲染编程(环境模拟篇)【第三卷:体积云天空模拟(3)---高层云】

    我的专栏目录: 小IVan:专题概述及目录 目前业内流行有两种体积云模拟的方式,模型+特殊shader法,RayMarching法.我前两篇文章已经对它们都做了介绍.当然还有些比较非主流的,比如粒子云 ...

  3. 虚幻4渲染编程(环境模拟篇)【第五卷:可交互物理植被模拟 - 上】

    我的专栏目录: 小IVan:专题概述及目录 开篇综述 这一卷将会开始研究可交互植被环境的模拟.我把可交互植被环境模拟这个大的课题拆解为几个部分.我挑选了几个森林模拟至关重要的几个要素并且实现它们. [ ...

  4. 虚幻4渲染编程(图元汇编篇)【第五卷:游戏中的动力学模拟】

    我的专栏目录 小IVan:专题概述及目录 还是先上效果吧 目前(2018年)在游戏中,通常使用韦尔莱积分做动力学模拟.使用韦尔莱积分可以模拟大部分物体的运动.布料,绳子,弹簧,软体,棍子都可以模拟.但 ...

  5. 渲染到ui_虚幻4渲染编程(UI篇)【第二卷:程序化UI特效-[1]】

    MY BLOG DIRECTORY: 小IVan:专题概述及目录​zhuanlan.zhihu.com INTRODUCTION: 当遇见某些特殊需求,比如对游戏效果有很多变化的要求,这时使用静态的贴 ...

  6. 虚幻4皮肤材质_虚幻4渲染编程(材质编辑器篇)【第六卷:各向异性材质amp;玻璃材质】...

    My blog directory: YivanLee:专题概述及目录​zhuanlan.zhihu.com Introduction: 各向异性材质 玻璃材质 材质编辑器篇的很多效果都非常简单,可以 ...

  7. 虚幻4渲染编程(材质编辑器篇)【第三卷:正式准备开始材质开发】

    My blog directory: YivanLee:专题概述及目录 Introduction: 前面两章我们已经完成了对工具的研究,下面我们久正式开始启程啦!后面的内容可能就比较美术了. 还是老规 ...

  8. 虚幻4渲染编程(光线追踪篇)【第一卷:光线追踪篇开篇综述】

    MY BLOG DIRECTORY: 小IVan:专题概述及目录​zhuanlan.zhihu.com INTRODUCTION: 什么都不说了先上个效果: 光线追踪云 电子游戏的光线追踪时代即将到来 ...

  9. 虚幻4渲染编程(材质编辑器篇)【第五卷:布料,丝绸纱皮革棉】

    My blog directory: 小IVan:专题概述及目录 Introduction: 现在的游戏对质感要求越来越高(我估计是硬件越来越好,可编程管线越来越来越完善).游戏的画面已经越来越接近影 ...

  10. 虚幻4渲染编程(灯光篇)【第二卷:体积光】

    我的专栏目录: 小IVan:专题概述及目录 体积光在游戏里被越来越多地用到,对烘托场景气氛,提高游戏的逼格有比较重要的作用.这篇就来由浅入深研究一下这个东西.从容易的做法到高端做法依次递进. 首先先来 ...

最新文章

  1. Docker学习(8)——构建镜像(Dockerfile、commit)
  2. NFV节省企业宽带成本—Vecloud微云
  3. CNN之性能指标:卷积神经网络中常用的性能指标(IOU/AP/mAP、混淆矩阵)简介、使用方法之详细攻略
  4. 这个网站绝了,收录近600条Linux系统命令
  5. AD18-画PCB步骤-总结
  6. Java对数组的操作(二)——集合与数组的切换
  7. 论文浅尝 | 基于事理图谱的脚本事件预测
  8. 报名 | 全国知识图谱与语义计算大会(CCKS 2019)评测任务发布
  9. assign深拷贝_Object.assign 深拷贝?浅拷贝?
  10. ofo已还清蚂蚁金服欠款?回应:消息不实 但没有放弃
  11. 为什么索引不支持模糊查询_百度站长平台查询的关键词排名,为什么与实际不符合?...
  12. delphi中griddata控件写入float数值_年中巨献!明道云发布多项重磅功能
  13. Android添加拍照功能,Android自定义相机,添加水印
  14. 长虹声纹识别技术推动家电产业向高阶形态发展
  15. 微信小程序云开发教程-小程序在云开发下的运作模式
  16. c语言中期报告程序,课题中期报告
  17. office2010每次打开总是出现配置进度
  18. zblog php 二级菜单,zblog博客系统二级(下拉)导航菜单设置教程
  19. Echarts柱状图上加图标
  20. 101. Domino 10 就要来了

热门文章

  1. IntelliJ IDEA 自动导入包的问题
  2. 龙狼三国神龙守护者自动通关脚本
  3. U盘的文件系统为FAT32才可以同时在苹果电脑和windows电脑中正常使用
  4. c语言数组初始化的区别
  5. Windows WSUS更新服务
  6. folly::AtomicHashMap实现分析
  7. wav格式的音乐怎么转换?
  8. 叮咚谁呀我是送信的邮递员呀_听说游戏内容
  9. 年级计算机试题五年级,五年级计算机操作测试题.pdf
  10. Python利用matplot绘图