NinjaLive插件解析
NinjaLive插件解析

NinjaLive插件解析

NinjaLive逻辑概括

image.png

NinjaLive背后的运行逻辑总结为:

  1. 使用一个interactionVolume来检测相交的物体。
  2. 发射一条射线(射线起点为摄像机,终点为相交物体,确定射线方向),射线与模拟渲染的平面相交,得到相交点处uv即可在交点画出物体。
  3. 将投影相交的物体写入位置和速度等信息传入buffer使用。
  4. 根据配置的参数计算流体与速度buffer。
  5. 得到原始流体数据驱动水、植物、烟、云等材质自行使用。

检测获得需要交互的物体

NinjaLiveActor蓝图主要为管理和维护需要交互的物体。

每个NinjaLiveActor下都会有一个InteractionVolume组件和ActivationVolume组件,在NinjaLiveActor的蓝图tick中(固定每秒80帧),以固定的频率(0.1s)去检测是否有actor进入ActivationVolume,如果有将会去调用Replay进入初始化与交互重叠actors检测。

image.png

交互重叠actors检测主要是使用 GetOverlappingComponents来得到与InteractionVolume组件重叠的组件,遍历所有交互范围内的组件进行筛选与管理。

image.png

将筛选得到需要交互的Components传入NinjaLiveComponent的OverlappingComponents中进行遍历交互。

画出交互物体形状

image.png

NinjaLiveComponent蓝图为主要逻辑蓝图,包括优化、初始化、材质管理、运算、debug等很多逻辑,这里主要只讲相对重要部分逻辑。

在NinjaLiveActor蓝图中已经将需要交互的物体传入NinjaLiveComponent的OverlappingComponents数组中了,NinjaLiveComponent蓝图将会在每帧tick中遍历OverlappingComponents中的每个需要交互的Component(通常为StaticMeshComponent)。

假设当前遍历的Component为A。首先需要得到当前遍历Component A的世界空间坐标,在根据A的缩放与ComponentBounds得到A的绘制大小。

我们需要从摄像机到A的坐标方向绘制一条直线,这条直线如果击中TraceMesh面片便可以得到击中点的UV,在这个UV位置上绘制上A大小的球(大小由bound和缩放决定,也有更精准的Capture方式)。

补充说明

直线起始点为摄像机位置,终点为A的位置+(A位置-摄像机位置)*Overshoot

TranceMesh为每个NinjaLiveActor下自带的组件,碰撞通道为FluidTrace,有固定和面相相机两种模式。

使用UE自带的LineTraceByChannel函数发射线来检测与TranceMesh的碰撞点,优化了指针对场景中的NinjaLiveActor,且碰撞通道为碰撞通道为FluidTrace的Mesh做射线检测,这个方法特别像成像原理,这样做出来就能在2D的片上模拟出3D的感觉。

image.png

image.png

传入交互物体参数

通过射线碰撞到绘制面片得到对应的UV,将UV位置参数和NinjaLiveActor上带的流体密度等信息传入材质。

image.png

最后还得传入一个速度参数来决定拖尾的长度,速度我们可以通过GetPhysicsLinearVelocity函数获得再将component A的速度从世界空间转到TraceMesh空间(贴图空间或者uv空间)。

image.png

绘制拖尾RT

形状拖尾RT为流体模拟的输入RT,是最基础的输入RT,查看此RT可以将NinjaLiveComponent的RenderTargetsMap打开可视化查看。

RT_Painter总共输出三通道信息,RG为物体拖尾Velocity(Noise处理过),B为物体形状拖尾。

image.png

拖尾RT的渲染方法比较巧妙,和大家平时使用的双RT迭代不同,Ninja的拖尾RT只使用了一次DrawCall和一张RT。

比较巧妙的做法是借助了RT绘制能接受半透明材质的透明的绘制。

image.png

原理是将材质结果写入RT时候,会根据Opacity进行混合。我们将渲染到的RT称作ResRT(也就是上面的RT_Painter),材质渲染的结果EmissiveColor称作OutRT,材质渲染的Opacity称作OutOpacity,就能得到如下混合公式,上一帧的结果会跟0做混合就形成了拖尾。
ResRT = Lerp(ResRT,OutRT,OutOpacity)

流体模拟数据流程:

  1. (N帧)用户自定义数据与贴图(密度图与速度图)与输入数据(碰撞交互RT_Painter)得到初始的 密度图与速度图
  2. (N帧)速度和密度经过(M_Advection)平流处理输出平流扩散贴图(RT_Advection),平流:根据速度方向平移N个单位,乘以0.99的阻尼,为当前单位的速度与密度
  3. (N帧)平流处理过的速度(Vector)还会计算散度(float)(M_Divergence),散度计算:当前点为 X Y, ((TexSample(X+N单位) – TexSample(X-N单位)) + (TexSample(Y+N单位) – TexSample(Y-N单位))) / 2 ,也就是记得当前点周围的速度变化。
  4. (N帧)通过压力解算器计算流体压力,Step1(MI_Pressure_Solver2_Step1)计算X轴压力,Step2(MI_Pressure_Solver2_Step2)中计算Y轴压力,得到最终的压力RT_PressureDivergence
  5. (N+1帧)平流过后的密度将会再与用户自定义数据与贴图(密度图与速度图)与输入数据(碰撞交互RT_Painter)得到新的密度图(M_CompositeAndGradient输出的RT_Composite B通道)。
    平流过后的速度将会被压力解算得到的压力数据修正再与用户自定义数据与贴图(密度图与速度图)与输入数据(碰撞交互RT_Painter)得到新的速度图(M_CompositeAndGradient输出的RT_Composite RG通道)

第5步结束跳转到第2步循环

Divergence散度计算补充说明

M_Divergence计算散度输出到RT_PressureDivergence传入MI_PressureCycle1,因为RT_PressureDivergence本身R通道上带有上一帧的压力解算信息所以当前M_Divergence的BlendMode改为Additive,散度只输出G通道(flot3(0,divergencs,0))。

Pressure压力计算详细

压力解算器分两种PressureSolver1与PressureSolver2,通常2K分辨率以上使用PressureSolver1性能比较好(较多的RT写入RT操作),2K以下使用PressureSolver2性能比较好(较少的RT写入操作),这里只讲PressureSolver2。
通常PressureSolver2每帧总共有两步Step1(MI_Pressure_Solver2_Step1)与Step2(MI_Pressure_Solver2_Step2)

(N帧)RT_PressureDivergence = (N-1)Pressure(R通道)+(N)Divergence(M_Divergence output to G通道)
(N帧)Step1: RT_PressureDivergence → Separable MultiKernel Diffusion (SMD) → RT_PressureDivergenceTemp

(N帧)RT_PressureDivergenceTemp= H-averaged pressure(R通道)+H-averaged divergence(G通道)
(N帧)Step2: RT_PressureDivergenceTemp→ Separable MultiKernel Diffusion (SMD) – averaged divergence → RT_PressureDivergence
(N帧)RT_PressureDivergence =HV-averaged pressure(R通道)+Empty(G通道)

graph LR
    A[M_Divergence<br>Material] -->|输出散度到G通道| B[RT_PressureDivergence<br>]
    B --> C[MI_Pressure_Solver2_Step1<br>Material]
    C --> D[RT_PressureDivergenceTemp<br>RT]
    D --> E[MI_Pressure_Solver2_Step2<br>Material]
    E --> B

Separable MultiKernel Diffusion (SMD) :

//LICENSING
//Separable MultiKernel Diffusion (SMD) module for FluidNinja LIVE 2021
//License: Creative Commons Attribution-ShareAlike 4.0 International
//Module version: 1.0

//PERFORMANCE
//Writing RenderTargets is a performance bottleneck in UE4
//SMD solver changes the balance in a tradeoff situation to improve overall performance
//More instructions per pixel --- less RenderTarget read-write ops

//METHOD
//Axiom: divergence peak values behave like sources that spread out, building smooth pressure field 
//Classic solvers spread divergence by averaging local texel neighbours in many repeated steps
//SMD uses precalc values to weight global texel neighbours in two passes (horizontal, vertical)
//Both solvers cheat by employing feedback: using previously calculated values as initial guess to create the pressure field for a given frame
//SMD uses Gaussian weights for smoothing the feedback component and custom, experimentally derived values to spread raw divergence
//The distribution of custom weights is somewhat similar to "stacking Gauss kernels of different sizes"


float GaussKernels[6][16] = { { 0.00117, 0.0045, 0.01394, 0.035, 0.07186, 0.12, 0.1631, 0.1807, 0.1631, 0.12, 0.07186, 0.035, 0.01394, 0.0045, 0.00117, 0.0 },
			 { 0.0, 0.00222, 0.00876, 0.027, 0.0648, 0.121, 0.176, 0.199, 0.176, 0.121, 0.0648, 0.027, 0.00876, 0.00222, 0.0, 0.0 },
			 { 0.0, 0.0, 0.00276, 0.01377, 0.0478, 0.1169, 0.19945, 0.23832, 0.19945, 0.1169, 0.0478, 0.01377, 0.00276, 0.0, 0.0, 0.0 },
			 { 0.0, 0.0, 0.0, 0.0034, 0.0202, 0.09134, 0.22958, 0.31094, 0.22958, 0.09134, 0.0202, 0.0034, 0.0, 0.0, 0.0, 0.0 },
			 { 0.0, 0.0, 0.0, 0.0, 0.0106, 0.075, 0.2387, 0.351, 0.2387, 0.075, 0.0106, 0.0, 0.0, 0.0, 0.0, 0.0 },
			 { 0.0, 0.0, 0.0, 0.0, 0.0, 0.06136, 0.24477, 0.38774, 0.24477, 0.06136, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }};
			 
float CustomKernels[6][16] = { { 0.0006, 0.003, 0.01, 0.028, 0.064, 0.1348, 0.30636, 0.65711, 0.30636, 0.1348, 0.064, 0.028, 0.01, 0.003, 0.0006, 0.0 },
			 { 0.0, 0.00166, 0.00726, 0.02369, 0.06055, 0.1353, 0.3028, 0.64626, 0.3028, 0.1353, 0.06055, 0.02369, 0.00726, 0.00166, 0.0, 0.0 },			 
			 { 0.0, 0.0, 0.00426, 0.017, 0.052, 0.13326, 0.324532, 0.63592, 0.324532, 0.13326, 0.052, 0.017, 0.00426, 0.0, 0.0, 0.0 },			 
			 { 0.0, 0.0, 0.0, 0.00514, 0.022, 0.09, 0.2956, 0.60248, 0.2956, 0.09, 0.022, 0.00514, 0.0, 0.0, 0.0, 0.0 },			 
			 { 0.0, 0.0, 0.0, 0.0, 0.0053, 0.0528, 0.2503, 0.57293, 0.2503, 0.0528, 0.0053, 0.0, 0.0, 0.0, 0.0, 0.0 },			 
			 { 0.0, 0.0, 0.0, 0.0, 0.0, 0.04567, 0.18454, 0.51596, 0.18454, 0.04567, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }};
		 
int SampleCounts[6] = { 15, 13, 11, 9, 7, 5 };
int KI = KernelIndex;
int KIoffset = GaussKernelIndexOffset;
int SampleCount = SampleCounts[KI];

float2   Direction  = lerp( float2( TexelSize.x, 0 ), float2( 0, TexelSize.y), StepIndex);
float2   OffsetCenterUV = lerp( float2(CenterUV.x - ((TexelSize.x * (SampleCount-1) )/2), CenterUV.y), float2(CenterUV.x, CenterUV.y - (TexelSize.y * (SampleCount-1) )/2), StepIndex);
float2	 WeightedAverage = { 0, 0 };

for ( uint ix = 0; ix <= SampleCount; ix++ )
{
	WeightedAverage += Texture.SampleLevel( TextureSampler, OffsetCenterUV + Direction * ( ix - Direction ), 0 ).rg * float2(GaussKernels[KI+KIoffset][ix+KI], CustomKernels[KI][ix+KI]);	
}

return WeightedAverage;

在MI_Pressure_Solver2_Step1中输入RT_PressureDivergence = (N-1)Pressure(R通道)+(N)Divergence(M_Divergence output to G通道),进行Separable MultiKernel Diffusion(分离多核扩散)解算,在MI_Pressure_Solver2_Step1中只计算X轴方向的N个单位压力通过GaussKernels进行加权,散度通过CustomKernels进行加权,得到当前点的平均权重。

在MI_Pressure_Solver2_Step2中输入RT_PressureDivergenceTemp= H-averaged pressure(R通道)+H-averaged divergence(G通道),进行Separable MultiKernel Diffusion(分离多核扩散)解算,在MI_Pressure_Solver2_Step2中只计算Y轴方向的N个单位压力通过GaussKernels进行加权,散度通过CustomKernels进行加权,得到当前点的平均权重。

CompositeVelocityDensity合成速度与密度详解

M_CompositeAndGradient材质RG通道输出速度,B通道输出密度。

Velocity = (用户自定义速度场贴图 * 速度噪声 +材质速度输入 + 交互速度输入 + 速度方向噪声 + 压力梯度计算处理后的平流扩散速度)* 边缘遮罩

Density = ((自定义密度场贴图 + 材质密度输入 + 交互密度输入 )* 密度噪声 + 平流扩散密度)* 边缘遮罩

交互速度输入和交互密度输入分别为 RT_Painter的RG通道与B通道。

压力梯度计算处理平流扩散速度:

计算压力(RT_PressureDivergence)梯度
XPressureGradient = ((TexSample(X+N单位) – TexSample(X-N单位));
YPressureGradient = ((TexSample(Y+N单位) – TexSample(Y-N单位));

处理平流扩散(RT_Advection)速度修正:

FixAdvectionVelocity.xy = float2(AdvectionVelocity.x – XPressureGradient * 0.5 , AdvectionVelocity.y – YPressureGradient * 0.5 );

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注