這一部分首先實現一下 WindField風力場 的整體框架。
主要分爲幾個部分
1.ADynamicWindFieldActor 和 UDynamicWindFieldComponent,遊戲場景實體以及對應主要承載功能的組件
2.FDynamicWindFieldSimulatorInterface 和 FDynamicWindFieldSimulator,渲染線程中的註冊以及操作對象,用於實際渲染時處理信息,遊戲線程中的消息都需要通過RenderCommandList發送到渲染線程中通過Simulator來處理
3.FWindFieldResource 繼承於 FRenderResource,用於儲存計算過程中數據,比如需要用到的存儲信息的體紋理, CS需要用到的紋理的UAV
4.多種GlobalShader,實際流體模擬計算過程中的Shader,比如計算平流的FAdvectVelocityCS,計算散度的FComputeVelocityDivergenceCS等等,還會有一些PS用於之後繪製Debug信息
一步步來
目錄
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和換算,以及最終在遊戲中風力場的應用等等,還有很多工作需要做,任重而道遠,需要之後一步步來完成。
那這裏先這樣~