虚幻4渲染编程(环境模拟篇)【第八卷:海洋模拟-中-在UE中实现FFT海洋】

本文档介绍了在Unreal Engine 4中使用GPU FFT技术实现海洋模拟的详细步骤,包括高斯分布构造、HZero、Twiddle因子、频率XYZ计算、IFFT蝶形变换以及法线构造。通过一系列RenderPass的实现,展示了从频率域到空间域的转换过程,为动态天气的海洋效果提供基础。
摘要由CSDN通过智能技术生成

我的专栏目录:

YivanLee:专题概述及目录

简介:

先上一下效果。因为是GPU的FFT海洋模拟,所以效率非常高。我这里只做了波形的部分,shading放到下一卷介绍

v2-b3bd6ddc5d782ae6bbb41a09a27fcdf7_b.gif

上一篇推导了海洋模拟的基础理论,实现了Gerstner Wave。这一卷在Unreal中实现FFT海洋。在UnrealEngine4中实现的难度我觉得至少比在OpenGL或者DX中实现难10倍,比在Unity中实现难20倍(手动狗头)。

再来梳理下FFT海洋实现步骤。

(1)构造一个高斯分布,这一步可以再CPU中完成也可以再GPU中完成,反正它只算一遍。

v2-871e9f7883e6b7ed13a5367fcb81ed37_b.jpg

(2)构造HZero

v2-24a0059316abf5773e7f256dc1a78981_b.jpg


(3)构造Twiddle。这一步也是既可以在GPU中完成,或者可以在CPU中完成。它也只需要构造一次。它可以在所有过程之前构造。

v2-664c4eae2f883b19da3f54a2c6848825_b.jpg

(4)构造FrequencyXYZ

v2-2f70792a340e78312ff9290d67962a48_b.jpg

(5)完成了这些之后对FrequencyXYZ进行IFFT蝶形变换

v2-58d0183c94f581a812cf77a27bd9ce5e_b.jpg

我给个GIF可以看到蝶形变换的整个过程。下面是把XZY三个频谱图从频率反变换到空间域的过程如下。

v2-b10ef256f627b8e5fde2a7ede30895c4_b.gif

(6)用上面变换得到的高度图构造法线,这一步就很简单了。


【1】OceanRenderer的设计

首先我们需要对海洋渲染器这个类进行一下设计,因为我发现如果不事先设计好渲染程序,到后期根本没法写下去了,因为整个过程涉及到了大量的渲染资源,Shader,状态,Uniform Buffer,乒乓缓冲蝶形变换等。

v2-309380591435ac413fe2f89fa3e66159_b.jpg

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的设计如下:

v2-fe4e84a8ac0f6bf0a5f237169cb92b7c_b.jpg

用一个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
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值