我的专栏目录:
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 OceanRenderer
OceanRender* 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 PhillipsNoiseRHITexture
if (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 pass
bool bPassSuccessInit;
//mark we can only excute initpss function once
bool 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 here
PhlipSurface.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 here
Ar << PhlipSurface << PhiipNoiseTexture;
return bShaderHasOutdatedParameters;
}
void BeginUseComputeShaderPhlip(FRHICommandList& RHICmdList, FUnorderedAccessViewRHIRef SurfaceTextureUAV, FShaderResourceViewRHIRef PhilipNoiseTextureSRV)
{
FComputeShaderRHIParamRef Compute