UE4_WindField 戰神風力場的實現 (1)_框架

這一部分首先實現一下 WindField風力場 的整體框架。

主要分爲幾個部分

1.ADynamicWindFieldActor 和 UDynamicWindFieldComponent,遊戲場景實體以及對應主要承載功能的組件

2.FDynamicWindFieldSimulatorInterface 和 FDynamicWindFieldSimulator,渲染線程中的註冊以及操作對象,用於實際渲染時處理信息,遊戲線程中的消息都需要通過RenderCommandList發送到渲染線程中通過Simulator來處理

3.FWindFieldResource 繼承於 FRenderResource,用於儲存計算過程中數據,比如需要用到的存儲信息的體紋理, CS需要用到的紋理的UAV

4.多種GlobalShader,實際流體模擬計算過程中的Shader,比如計算平流的FAdvectVelocityCS,計算散度的FComputeVelocityDivergenceCS等等,還會有一些PS用於之後繪製Debug信息

一步步來

 

目錄

1.創建遊戲線程實體

2.創建渲染線程關聯

3.創建RenderResource

4.Fluid Simulation

(1)ApplyExternalWindSource

(2)AdvectVelocity

(3)ComputeVelocityDivergence

(4)ComputePressure

(5)ProjectVelocity


 

1.創建遊戲線程實體

首先需要的是一個場景中的Actor實體,以及對應的主要承載功能的Component,這裏命名爲

ADynamicWindFieldActor 

UCLASS(ClassGroup=Wind, showcategories=(Rendering, "Utilities|Transformation"))
class ENGINE_API ADynamicWindFieldActor : public AInfo
{
	UPROPERTY(Category = WindField, VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true"))
	class UDynamicWindFieldComponent* Component;
};

Actor裏目前只需要簡單的聲明和對Component的引用,之後有需求可以再加

UDynamicWindFieldComponent

Component承載的功能有點多,一點點來,開始的話,主要有幾個需求

第一個是希望編輯器下不需要運行也能看到風力場的效果,所以這裏把一些Resource初始化操作放到Component的Active和Deactive裏,並把 bAutoActivate 設爲true,這樣不需要Play就也可以看到一些效果

第二個是希望編輯器下修改了Component的屬性,希望能立刻作用到渲染線程中,因此需要重載PostEditChangeProperty方法,當有屬性修改時,發送Command到渲染線程中,修改對應屬性

第三個是之後運行時Component也會需要更新一些數據,比如遊戲的TickTime,之後每幀邏輯線程會決定是否注入力到風場中,這些信息也需要更新到渲染線程中

所以初始Component設計如下

class ENGINE_API UDynamicWindFieldComponent : public USceneComponent
{
	virtual void Activate(bool bReset) override;//資源初始化
	virtual void Deactivate() override;//資源銷燬
	virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override;//實時更新數據
	virtual void BeginDestroy() override;//資源銷燬
#if WITH_EDITOR
	virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;//編輯器下更新數據
#endif // WITH_EDITOR
}

2.創建渲染線程關聯

創建了遊戲線程中的實體,接下來需要創建關聯的在渲染線程處理數據的對象。

首先創建一個渲染線程接口,

FDynamicWindFieldSimulatorInterface

主要需要用到兩個方法,一個在Renderer一開始的時候計算模擬風力場,這樣之後BasePass等階段,草樹等Actor可以直接使用這幀更新後的數據,另一個可以放在Renderer的最後,用於取出數據繪製Debug信息,方便調試,這裏我把聲明放在了SceneManagement.h裏

class FDynamicWindFieldSimulatorInterface
{
public:
	virtual void PreRender(FRHICommandListImmediate& RHICmdList) = 0;
	virtual void PostRender(FRHICommandListImmediate& RHICmdList, const FViewInfo& View) = 0;
};

之後,將其綁定到Scene中,首先在FSceneInterface中加入綁定和解綁方法

	virtual void AddDynamicWindFieldSimulator(class FDynamicWindFieldSimulatorInterface* DynamicWindFieldSimulator) = 0;
	virtual void RemoveDynamicWindFieldSimulator(class FDynamicWindFieldSimulatorInterface* DynamicWindFieldSimulator) = 0;

在繼承於FSceneInterface 的 FScene 和 FNULLSceneInterface 中都聲明對應方法(FNULLSceneInterface不聲明會編譯不過),在FScene中另外一個FDynamicWindFieldSimulatorInterface 的數組 DynamicWindFieldSimulators用於保存, 最後加入實現

void FScene::AddDynamicWindFieldSimulator(class FDynamicWindFieldSimulatorInterface* DynamicWindFieldSimulator)
{
	FScene* Scene = this;

	ENQUEUE_RENDER_COMMAND(AddDynamicWindFieldSimulatorCommand)(
		[Scene, DynamicWindFieldSimulator](FRHICommandList& RHICmdList)
	{
		Scene->DynamicWindFieldSimulators.AddUnique(DynamicWindFieldSimulator);
	});
}

void FScene::RemoveDynamicWindFieldSimulator(class FDynamicWindFieldSimulatorInterface* DynamicWindFieldSimulator)
{
	FScene* Scene = this;

	ENQUEUE_RENDER_COMMAND(RemoveDynamicWindFieldSimulatorCommand)(
		[Scene, DynamicWindFieldSimulator](FRHICommandList& RHICmdList)
	{
		Scene->DynamicWindFieldSimulators.Remove(DynamicWindFieldSimulator);

		delete DynamicWindFieldSimulator;
	});
}

然後就是調用了,在FDeferredShadingSceneRenderer::Render 里加入這兩個過程中

void FDeferredShadingSceneRenderer::Render(FRHICommandListImmediate& RHICmdList)
{
	//開始
	for (auto Simulator : Scene->DynamicWindFieldSimulators)
	{
		Simulator->PreRender(RHICmdList);
	}

    //...原來的邏輯

    //結束
    for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ++ViewIndex)
	{
		for (auto Simulator : Scene->DynamicWindFieldSimulators)
		{
			Simulator->PostRender(RHICmdList, Views[ViewIndex]);
		}
	}
}

這裏結束時,爲了繪製Debug信息到屏幕,把View信息也傳了進去

最後,就是聲明自己的Simulator了,這裏先添加基本的接口,之後根據功能逐步豐富內容。

class FDynamicWindFieldSimulator : public FDynamicWindFieldSimulatorInterface
{
public:
	FDynamicWindFieldSimulator();//默認聲明
    //接口聲明
	virtual void PreRender(FRHICommandListImmediate& RHICmdList) override;
	virtual void PostRender(FRHICommandListImmediate& RHICmdList, const FViewInfo& View) override;

public:
	UDynamicWindFieldComponent* Owner;//存儲場景Comp指針
    //用於通過Comp初始化以及更新自身狀態,當然運行時更新都需要遊戲線程發送Command到渲染線程中
	void Initialize(UDynamicWindFieldComponent* InOwner, ERHIFeatureLevel::Type InFeatureLevel = ERHIFeatureLevel::Num);

private:
	ERHIFeatureLevel::Type GetFeatureLevel() const { return FeatureLevel; }
	ERHIFeatureLevel::Type FeatureLevel;//用於RHICommandList盤算是否支持
};

3.創建RenderResource

現在邏輯線程和渲染線程用於處理數據的對象都有了,也把處理過程加入到了實際渲染Renderer的步驟裏,這一步就需要創建用於保存以及被處理的數據了,也就是創建RenderResource。

由於風力場是一個場,需要的是三維信息,因此需要使用3Dtexture來存儲,另外由於計算過程主要使用ComputeShader,因此需要創建Texture對應的UAV,最後,由於計算過程中,既可能需要存儲Vector,又可能需要存儲float,因此需要同時支持這兩種,這裏使用模板來實現

template<bool IsVector>
class FWindFieldResource : public FRenderResource
{
public:
	/** The volume texture containing the wind field. */
	FTexture3DRHIRef WindFieldTextureRHI;
	int32 SizeX;
	int32 SizeY;
	int32 SizeZ;

	/** Unordered access view in to the volume texture. */
	FUnorderedAccessViewRHIRef WindFieldTextureUAV;

	FWindFieldResource()
	{
		SizeX = WindFieldResourceSizeX;
		SizeY = WindFieldResourceSizeY;
		SizeZ = WindFieldResourceSizeZ;
	}

	virtual void InitRHI() override
	{
		if (GSupportsTexture3D)
		{
			check(SizeX > 0);
			check(SizeY > 0);
			check(SizeZ > 0);

			uint32 TexCreateFlags = 0;
			if (GetFeatureLevel() >= ERHIFeatureLevel::SM5)
			{
				TexCreateFlags = TexCreate_ShaderResource | TexCreate_UAV;
			}

			FRHIResourceCreateInfo CreateInfo;

			WindFieldTextureRHI = RHICreateTexture3D(
				SizeX, SizeY, SizeZ,
				IsVector ? PF_FloatRGBA : PF_R32_FLOAT,
				/*NumMips=*/ 1,
				TexCreateFlags,
				CreateInfo);

			if (GetFeatureLevel() >= ERHIFeatureLevel::SM5)
			{
				WindFieldTextureUAV = RHICreateUnorderedAccessView(WindFieldTextureRHI);
			}
		}
	}

	virtual void ReleaseRHI() override
	{
		WindFieldTextureRHI.SafeRelease();
		WindFieldTextureUAV.SafeRelease();
	}
};

Resource聲明好了,就可以在Component中構造出來使用了,在Componnet中加入這些Resource聲明,具體每個怎麼使用,會在下一部分中解釋。

	FDynamicWindFieldSimulator WindFieldSimulator;

	class FWindFieldResource<true>  VelocityFieldResource[2];
	class FWindFieldResource<true>  TempVectorFieldResource[2];
	class FWindFieldResource<false> DivergenceFieldResource;
	class FWindFieldResource<false> PressureFieldResource[2];

4.Fluid Simulation

現在對象和數據都有了,終於到了最重要的流體模擬的部分了,這部分就是構建整個模擬的計算過程了。

計算過程都在渲染線程,因此實現都在Simulator裏,主要分爲以下幾步

(1).注入力到風力場中,開始這裏寫的比較簡單,只是往一個位置按照一定時間間隔注入,之後會逐步完善這裏,加入更多的風力類型

(2).Advect平流,使用 Semi-Lagrangian 計算力傳播後的Velocity

(3).Divergence,計算當前Velocity的散度

(4).Pressure Iterate,根據散度做Jacobi的壓力迭代

(5).Project Velocity,使用最終的Pressure做速度投影,計算最後的Velocity

也就是如下過程

void FDynamicWindFieldSimulator::PreRender(FRHICommandListImmediate& RHICmdList)
{
    CacheTime += DeltaTime;
	while (CacheTime > AddForceInterval)
	{
		ApplyExternalWindSource(RHICmdList, FVector(10, 30, 0));
		CacheTime -= AddForceInterval;
	}

	float MinTimestep = 0.016f;
	int32 SubstepIndex = 0;
	float SubstepDeltaTime = GFixedSimulationTimestep > 0.f ? GFixedSimulationTimestep : DeltaTime;

	while (SubstepDeltaTime > 0.f && SubstepIndex < MaxTimesteps)
	{
		SubstepIndex++;

		AdvectVelocity(RHICmdList, SubstepIndex == MaxTimesteps ? SubstepDeltaTime : FMath::Min(SubstepDeltaTime, MinTimestep));

		ComputeVelocityDivergence(RHICmdList);

		ComputePressure(RHICmdList);

		ProjectVelocity(RHICmdList);

		SubstepDeltaTime -= MinTimestep;
	}
}

繼續一步步來

(1)ApplyExternalWindSource

這一步很簡單,就是往紋理中的某個位置疊加一個速度值上去就可以

首先是CS,shader寫在一個新加的 DynamicWindField.usf 裏面

[numthreads(THREADS_X, THREADS_Y, THREADS_Z)]
void ApplyExternalWindSource(
	uint3 DispatchThreadId : SV_DispatchThreadID,
	uint3 GroupId : SV_GroupID,
	uint3 GroupThreadId : SV_GroupThreadID,
	uint ThreadId : SV_GroupIndex)
{
	OutVelocityFieldTexture[DispatchThreadId + WindSourceOffset] += float4(WindVelocity, 0.f);
}

可以看到,就一行代碼,就是根據WindSourceOffset 偏移一個位置,然後把WindVelocity 加上去就可以了

由於是第一個,這裏寫一下在C++聲明的和這個shader的關聯,之後其他Shader大體都類似,可能只寫一下比較重要的部分

class FApplyExternalWindSourceCS : public FGlobalShader//繼承GlobalShader
{
	DECLARE_SHADER_TYPE(FApplyExternalWindSourceCS, Global);//和下面Implament對應的聲明類型
public:

	static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
	{
		return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5);//SM驗證
	}
    //初始化Shader中用到的宏
	static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
	{
		FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
		OutEnvironment.SetDefine(TEXT("THREADS_X"), 2);
		OutEnvironment.SetDefine(TEXT("THREADS_Y"), 8);
		OutEnvironment.SetDefine(TEXT("THREADS_Z"), 1);
		OutEnvironment.SetDefine(TEXT("USE_2D_SPACE"), USE_2D_SPACE_MACRO);
	}

	FApplyExternalWindSourceCS(){}
    //Bind shader中的參數到聲明的變量上
	explicit FApplyExternalWindSourceCS(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
		: FGlobalShader(Initializer)
	{
		OutVelocityFieldTexture.Bind(Initializer.ParameterMap, TEXT("OutVelocityFieldTexture"));
		WindVelocityParameter.Bind(Initializer.ParameterMap, TEXT("WindVelocity"));
		WindSourceOffsetParameter.Bind(Initializer.ParameterMap, TEXT("WindSourceOffset"));
	}
    //序列化參數
	virtual bool Serialize(FArchive& Ar) override
	{
		bool bShaderHasOutdatedParameters = FGlobalShader::Serialize(Ar);
		Ar << OutVelocityFieldTexture;
		Ar << WindVelocityParameter;
		Ar << WindSourceOffsetParameter;
		return bShaderHasOutdatedParameters;
	}
    //初始化Shader中參數
	void SetParameters(
		FRHICommandList& RHICmdList,
		const FVector& WindVelocity,
		const FVector& WindSourceOffset)
	{
		FComputeShaderRHIParamRef ComputeShaderRHI = GetComputeShader();
		SetShaderValue(RHICmdList, ComputeShaderRHI, WindVelocityParameter, WindVelocity);
		SetShaderValue(RHICmdList, ComputeShaderRHI, WindSourceOffsetParameter, WindSourceOffset);
	}
	//設置CS的輸出到UAV
	void SetOutput(FRHICommandList& RHICmdList, FUnorderedAccessViewRHIParamRef VelocityFieldUAV)
	{
		FComputeShaderRHIParamRef ComputeShaderRHI = GetComputeShader();
		if (OutVelocityFieldTexture.IsBound())
		{
			RHICmdList.SetUAVParameter(ComputeShaderRHI, OutVelocityFieldTexture.GetBaseIndex(), VelocityFieldUAV);
		}
	}
    //取消UAV的設置
	void UnbindBuffers(FRHICommandList& RHICmdList)
	{
		FComputeShaderRHIParamRef ComputeShaderRHI = GetComputeShader();
		if (OutVelocityFieldTexture.IsBound())
		{
			RHICmdList.SetUAVParameter(ComputeShaderRHI, OutVelocityFieldTexture.GetBaseIndex(), FUnorderedAccessViewRHIParamRef());
		}
	}

private:
    //Shader中參數對應的變量聲明
	FShaderResourceParameter OutVelocityFieldTexture;//輸出的Velocity Texture

	FShaderParameter WindVelocityParameter;//風力注入的速度大小
	FShaderParameter WindSourceOffsetParameter;//風力注入的位置偏移
};
IMPLEMENT_SHADER_TYPE(, FApplyExternalWindSourceCS, TEXT("/Engine/Private/DynamicWindField.usf"), TEXT("ApplyExternalWindSource"), SF_Compute);

然後就是實際的Simulator的調用了,同樣第一次寫全一點,之後可能會省略一些重複的信息。

void FDynamicWindFieldSimulator::ApplyExternalWindSource(FRHICommandListImmediate& RHICmdList, FVector WindSourceOffset, float VelocityScaling)
{
    //加事件,用於截幀工具查看
	SCOPED_DRAW_EVENT(RHICmdList, ApplyExternalWindSource);
    //查找用到的CS文件
	TShaderMapRef<FApplyExternalWindSourceCS> ApplyExternalWindSourceCS(GetGlobalShaderMap(GetFeatureLevel()));
    //設置cs
	RHICmdList.SetComputeShader(ApplyExternalWindSourceCS->GetComputeShader());
    //設置cs輸出的UAV
	ApplyExternalWindSourceCS->SetOutput(RHICmdList, Owner->VelocityFieldResource[0].WindFieldTextureUAV);
    //設置cs參數
	ApplyExternalWindSourceCS->SetParameters(
		RHICmdList,
		ForceVelocity * VelocityScaling,
		WindSourceOffset);
    //調用CS
	DispatchComputeShader(
		RHICmdList,
		*ApplyExternalWindSourceCS,
		1,
		1,
		1);
    //清理輸出的UAV參數
	ApplyExternalWindSourceCS->UnbindBuffers(RHICmdList);
}

輸入就是當前要注入的風力大小以及位置

輸出到VelocityFieldResource的第一張上面,之後也會用這張傳入下一個平流階段。

(2)AdvectVelocity

這一步就要計算平流了,使用注入力之後的Velocity Texture,計算平流之後的Velocity Texture,一個讀,一個寫,之前在Comp裏面聲明瞭兩個Velocity Texture,也是爲了這裏能做 Ping-Pong Buffer。

比較簡單的計算方式就是根據傳入的Velocity以及這幀的時間布,每一個紋素計算一個之前所在的位置,然後再該位置採樣,再輸出到第二張Velocity Texture。之後也會有一些更復雜的計算方法,這裏也用比較簡單的,複雜的之後可以補充一下。

float3 GetAdvectedPosTexCoords(uint3 TexCoords)
{
	float3 Position = TexCoords;

	float3 Velocity = PreviousVelocityFieldTexture.Load(int4(TexCoords, 0)).xyz;

	Position -= Timestep * Forward * Velocity;

    Position += float3(0.5, 0.5, 0.5);

	return float3(Position.x / TextureResolution.x, Position.y / TextureResolution.y, (Position.z + 0.5) / TextureResolution.z);
}
[numthreads(THREADS_X, THREADS_Y, THREADS_Z)]
void AdvectVelocity(
	uint3 DispatchThreadId	: SV_DispatchThreadID,
	uint3 GroupId			: SV_GroupID,
	uint3 GroupThreadId		: SV_GroupThreadID,
	uint ThreadId			: SV_GroupIndex)
{
	float3 PreviousLocation = GetAdvectedPosTexCoords(DispatchThreadId);
	
	float4 NewVelocity = PreviousVelocityFieldTexture.SampleLevel(LinearTextureSampler, PreviousLocation, 0);

	OutVelocityFieldTexture[DispatchThreadId] = NewVelocity * Modulate;
}

主要計算過程就是上面說的,比較難理解的是在GetAdvectedPosTexCoords裏有一步,Position += float3(0.5, 0.5, 0.5);這一步主要是爲了解決下面做除法float精度丟失的問題,由於精度丟失,導致不管速度怎樣,採樣uv都會變小,新的Location的位置總是靠原來位置的左上,這會導致新的Velocity總會朝右下偏移,每執行一次Advect,都會偏離一個像素,這在實際遊戲中就會是非常大的誤差,風力很快就會偏出紋理去了,所以爲了補償誤差丟失,這裏每個軸都加0.5作爲補償。

之後就是調用的地方了

	AdvectVelocityCS->SetOutput(RHICmdList, Owner->VelocityFieldResource[1].WindFieldTextureUAV);

	AdvectVelocityCS->SetParameters(
		RHICmdList,
		&(Owner->VelocityFieldResource[0]),
		Timestep,
		1.0,
		AdvectModulate);

	DispatchComputeShader(
		RHICmdList,
		*AdvectVelocityCS,
		WindFieldResourceSizeX / THREADS_PER_AXIS,
		WindFieldResourceSizeY / THREADS_PER_AXIS,
		WindFieldResourceSizeZ / THREADS_PER_AXIS);

輸入是第一張注入力的Velocity Texture

輸出是第二張Velocity Texture

(3)ComputeVelocityDivergence

計算散度,採樣自己鄰接的六個紋素的Velocity,左右兩個做x軸梯度差值,前後兩個做y軸梯度差值,上下兩個做z軸梯度差值。

[numthreads(THREADS_X, THREADS_Y, THREADS_Z)]
void ComputeVelocityDivergence(
	uint3 DispatchThreadId : SV_DispatchThreadID,
	uint3 GroupId : SV_GroupID,
	uint3 GroupThreadId : SV_GroupThreadID,
	uint ThreadId : SV_GroupIndex)
{
	float4 FieldL = CurrentVelocityFieldTexture.Load(LEFTCELL);
	float4 FieldR = CurrentVelocityFieldTexture.Load(RIGHTCELL);
	float4 FieldB = CurrentVelocityFieldTexture.Load(BOTTOMCELL);
	float4 FieldT = CurrentVelocityFieldTexture.Load(TOPCELL);
	float4 FieldD = CurrentVelocityFieldTexture.Load(DOWNCELL);
	float4 FieldU = CurrentVelocityFieldTexture.Load(UPCELL);

	OutVelocityDivergenceTexture[DispatchThreadId] = 0.5 * ((FieldR.x - FieldL.x) + (FieldT.y - FieldB.y) + (FieldU.z - FieldD.z));
}

直接 (左-右)*0.5,是因爲 =((左-中)-(中-右))*0.5,中間那個直接抵消了,所以直接左-右。

調用的地方

	ComputeVelocityDivergenceCS->SetOutput(RHICmdList, Owner->DivergenceFieldResource.WindFieldTextureUAV);

	ComputeVelocityDivergenceCS->SetParameters(
		RHICmdList,
		&Owner->VelocityFieldResource[1]);

輸入是第二張Velocity Texture,也就是上一步的輸出

輸出是散度紋理,這是一張float類型的紋理

(4)ComputePressure

迭代使用Jacobi計算壓力,採樣周圍鄰接紋素的散度,減去自己的散度值,之後做平均

[numthreads(THREADS_X, THREADS_Y, THREADS_Z)]
void ComputePressure(
	uint3 DispatchThreadId : SV_DispatchThreadID,
	uint3 GroupId : SV_GroupID,
	uint3 GroupThreadId : SV_GroupThreadID,
	uint ThreadId : SV_GroupIndex)
{
    float bC = DivergenceFieldTexture.Load(int4(DispatchThreadId, 0));

    float pL = PressureFieldTexture.Load(LEFTCELL);
    float pR = PressureFieldTexture.Load(RIGHTCELL);
    float pB = PressureFieldTexture.Load(BOTTOMCELL);
    float pT = PressureFieldTexture.Load(TOPCELL);
    float pD = PressureFieldTexture.Load(DOWNCELL);
    float pU = PressureFieldTexture.Load(UPCELL);

	OutPressureTexture[DispatchThreadId] = (pL + pR + pB + pT + pU + pD - bC) / 6.0;
}

調用的地方

	for (int32 Index = 0; Index < NumPressureIteration; ++Index)
	{
		ComputePressureCS->SetOutput(RHICmdList, Owner->PressureFieldResource[(Index + 1) & 1].WindFieldTextureUAV);

		ComputePressureCS->SetParameters(
			RHICmdList,
			&Owner->DivergenceFieldResource,
			&Owner->PressureFieldResource[Index & 1]);

		DispatchComputeShader(
			RHICmdList,
			*ComputePressureCS,
			WindFieldResourceSizeX / THREADS_PER_AXIS,
			WindFieldResourceSizeY / THREADS_PER_AXIS,
			WindFieldResourceSizeZ / THREADS_PER_AXIS);
	}

根據配置的迭代次數,同樣用Ping-pong buffer,迭代循環執行

輸入輸出根據配置的迭代次數,兩張Pressure Texture都有可能。

(5)ProjectVelocity

好,到最後一步了,最終要使用最後的Pressure,做速度的投影,根據當前紋素左右,前後,上下的壓力梯度,計算速度應該擴散到的地方

[numthreads(THREADS_X, THREADS_Y, THREADS_Z)]
void ProjectVelocity(
	uint3 DispatchThreadId : SV_DispatchThreadID,
	uint3 GroupId : SV_GroupID,
	uint3 GroupThreadId : SV_GroupThreadID,
	uint ThreadId : SV_GroupIndex)
{
	float pL = PressureFieldTexture.Load(LEFTCELL);
	float pR = PressureFieldTexture.Load(RIGHTCELL);
	float pB = PressureFieldTexture.Load(BOTTOMCELL);
	float pT = PressureFieldTexture.Load(TOPCELL);
	float pD = PressureFieldTexture.Load(DOWNCELL);
	float pU = PressureFieldTexture.Load(UPCELL);

	OutVelocityFieldTexture[DispatchThreadId] = float4(CurrentVelocityFieldTexture.Load(int4(DispatchThreadId, 0)).xyz - (0.5 * float3(pR - pL, pT - pB, pU - pD)), 0.f) * Modulate;
}

調用的地方,

	ProjectVelocityCS->SetOutput(RHICmdList, Owner->VelocityFieldResource[0].WindFieldTextureUAV);

	ProjectVelocityCS->SetParameters(
		RHICmdList,
		&Owner->VelocityFieldResource[1],
		&Owner->PressureFieldResource[(NumPressureIteration) & 1],
		ProjectModulate);

輸入是計算上面幾步計算好的第二張速度紋理,以及Pressure Iterate之後的壓力紋理

輸出到第一張速度紋理上

 

整體的初步框架搭建大概結束了~

但從上面可以看到,一個是代碼裏還有蠻多細節沒有解釋到,所以下一部分會解釋一下這一篇中沒有說到的細節,比如速度的衰減,更好的Advect計算方式,簡單的Debug等等,另一個是目前只是實現了初步的框架,還有很多東西沒有完善,比如多種風力的發射器的實現,力的世界空間到紋理空間的Merge和換算,以及最終在遊戲中風力場的應用等等,還有很多工作需要做,任重而道遠,需要之後一步步來完成。

那這裏先這樣~


目錄:UE4_WindField 戰神風力場的實現 (0)_概述

下一篇:UE4_WindField 戰神風力場的實現 (2)_調試初步

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章