欢迎光临散文网 会员登陆 & 注册

在Niagara中基于LBM优化流体性能(下)

2023-05-01 22:25 作者:尘星_Star  | 我要投稿

        悲催,本章开头就要纠正上篇文章的小错误。上篇文章提到梯度算子,这个说法并不准确。应该叫做哈密顿算子或者nabla算子。至于梯度、散度和旋度是另一种概念。由此可见,本篇文章也仅作为参考,因为很多都是我极其主观的理解。尤其是接下来涉及官方流体的示例,欢迎大家一起交流。

        打开官方示例(需要在项目中启用流体插件才能看到),一条一条往下看。看到最后,单独来看每一条感觉好像懂了。但连起来还是不懂,于是又看一遍还是不懂。然后闲着没事,数了下左边的变量数目。最多的Emitter命名空间就有200多个!这么多变量来回捯饬,弄得我头昏脑胀。没办法,只能用其他方法来帮助自己理解。

        这个思维导图其实只是我理清关系用的,在后面并没有多大的用,没必要去细看。在之前的研究中,虽然没有弄明白整体的思路。但能感觉到重心是几个官方定义的Grid3D Collection。

        这样就能够大致了解每个Grid的功能了。SimGrid主要涉及到模拟部分;TransientGrid与边界有关;PressureGrid与求解压力梯度有关;LightingGrid与光照有关;ScalarGrid主要用来传递参数;Temporary与计算旋度与散度有关;RasterGrid与旁边的另一个发射器有关。

        我们还需要在开始研究发射器之前再回顾一些基本知识。我觉得自己描述的不够准确,请上GPT为我们讲解。Grid3D Collection是一种数据结构,用于存储在三维网格中的粒子数据。它可以看作是一个三维的数组,每个元素(或单元格)都可以存储一个或多个粒子。Grid3D Collection可以用于各种效果,例如体积渲染、流体模拟等。错了不要怪我,甩锅给AI。在这里,一定要和之前的粒子渲染区分开来,由于之前我一直把自己的思维拘束在粒子上。导致写代码时的核心逻辑出现巨大偏差。对于粒子渲染,计算针对的是每一个粒子;而在流体渲染中,计算针对的是模拟的一个个小方格或者小方块。尽管有从其他发射器获取粒子数据,但这是不同的。

         Simulation Stages这里可以看看知乎大佬Feilon的相关文章,我就简单提下。普通情况下,我们需要执行两个操作A和B。对于粒子或者Grid Collection来说,A,B它们有的先被计算,有的后被计算完成。但是在流体中,我们需要所有对象A执行完了,B需要用到A中得到数据,才能去执行B操作,这个时候就需要用到Simulation Stages了。

        官方设置了这么多的Grid3D Collection,主要的目的有两个。一是模拟和渲染的网格分辨率是不同的;二是数据分组可以避免不必要的复制操作带来的性能损耗。

        每一个模块我只是简单提一下它有啥子用,不然深入讲的话,我整个五一就没有了,而且有些模块我自己也懒得深入研究,等要用到的时候,再去研究。



        Grid 3D LBM CONTROLS SPAWN

        Grid 3D LBM SCALABILITY SPAWN

        这两个模块和官方是一致的,只是稍微调整了下。主要的目的是将模拟需要调整的参数集成在一起。这样调整参数,就不用到处找了。

        然后就是创建SimGrid、TransientGrid、DistributionGrid、LightingGrid、ScalarGrid这几个Grid3D Collection,其中DistributionGrid是储存分布函数用的。

        Grid 3D Set Resolution

        接下来就是设置各个Grid3D Collection的分辨率了,这里注意一下分辨率官方是如何设置的。ScalarGrid是用户手动设置的,LightingGrid、SimGrid,在继承ScalarGrid的分辨率的同时还可以用参数手动调节自身的分辨率。

        Grid 3D Modify Grid Scale

        在这里进行的就是空间的离散化了,数学表达式的dx就是来自这里。但这里还多了几个额外的dx_render等,这也是和上面提到的渲染和模拟的分辨率是不同的有关。有了更多的可操作性。

        

        LBM Create Grid Attribute Indices

        这里就是创建属性的地方,比如在一个网格中,我存了一些信息,速度,颜色。怎么才能让程序知道那个数据指的是速度,那个数据指的是颜色。这个时候就是通过属性索引来让程序分辨了。在这里官方的命名具有迷惑性,在其他网格创建的索引最后以SimGrid作为前缀命名。当然,这只是看待问题的角度不同导致的,并无对错之分。之前提到过,我们采用D3Q7模型,所以需要7个索引来存储分布函数。对应的属性数据类型是1个Float,2个Vector。其他的没有太多变化。

        往下看也是创建属性,并且规定重力加速度g的数值。

        

     我们把算好的数据需要传递到Render Target Volume里进行渲染,注意这里需要设置正确的浮点数精度。

   Grid 3D Init RT

   这个就是初始化上面RT的大小,这里是根据ScalarGrid来进行设置的。

    下面一系列操作和上面一样,只不过操作的对象不一样罢了,也没有新玩意出现,考虑文章篇幅,就不赘述了。

    

        最后两个一个是设置Particle Attribute Reader,这个就是指向旁边发射器的。我们每帧需要从哪里获得源信息嘛。举个例子,旁边的发射器就是设置燃料如何运动,至于燃料产生的烟雾,燃烧的火焰如何运动就是这个发射器做的事。

        还有一个是Debug用的,不要小看Debug了!!!如此多的模块。做的时候,一个字母打错了,一个逻辑没想明白。系统就不会正常运行,这个时候没有做好Debug,想要解决问题,难度极其极其之大。虽然为了篇幅原因,我自己也偷懒不会过多介绍这方面内容,但这一部分说是整个发射器最重要的部分也不为过。大家可以仔细研究学习。

        

        LBM GAS CONTROLS UPDATE

        Grid 3D GAS SCALABILITY UPDATE

        这个我改来改去还是和官方一样的,忽略命名不同。主要就是调节流体的美术效果用的。

        Sourcing Comp Modes

     这里的CompModes是与下面Sourcing的Simulation Stages搭配使用,就是处理速度,温度与密度的值。要三种方法可选,直接相加,取最大值,和一个给定阈值取其中之一,不同方式会有不同的美术表现。

        接下来就是时间的离散化了,dt。官方有两个可选,一个就是自己设置;一个用系统的DeltaTime。我就没搞那么复杂,直接用系统的,然后乘以一个变量方便控制。

        Grid 3D Create Unit to World Transform

        进行坐标转换,涉及到常用的世界空间,本地空间;以及对应Grid3D的坐标空间。这些都是方便数学计算用的。然后赋值给变量,后面可以直接使用。

        Grid 3D Set Bounds

        设置模拟空间的边界。

        Grid 3D Rigid Mesh Find Colliders

        根据User传递来的模型,进行碰撞体的查找。防止流体拖进场景就产生交互,官方设置了Tag,只有名为collider的Tag才会产生交互。

   

     

         这个Position是与Mesh Render里的Position Binding联系起来的,官方直接设置的GridCenterPosition。但是疑似由于官方引入Position类型用于区分Vector导致我怎么也无法将GridCenterPosition直接绑定到Position上,只好这样解决了。

         

 

        接下来就是一系列初始化Grid3D Collection。这里首先注意子命名空间的用法,就是指明变量向那个Grid3D Collection写入数据,这里写自定义的时候,要注意写法首字母大写,不要有任何其他的符号,包括空格。我在这里也卡很长一段时间。最后发现自己多打了一个空格才拖不进去,内心有些小波动。

        迭代源是数据接口,和之前提到计算对象是Grid3D中的每一个格子而不是粒子联系起来了。当涉及数据写入的时候,一定要确定写入的对象和数据接口是一致的,像上面的DistributionGrid就不能换成其他Grid。我测试了一下,涉及数据读取倒没有非常大限制只要有数据就行。执行的行为,只要涉及初始化的选择在模拟重置时执行(On Simulation Reset)就可以了。后面其他的模拟阶段选择Not On Simulation Reset后面我就不再重复提及了。

        Grid 3D Gas Particle Scatter Source

        这里就是从旁边的发射器获得数据,并且将数据传到RasterGrid里了,这个里面有些复杂,可以多花点时间看看如何传递数据的。

          

旋度计算

        接下来就是和官方示例有不同的地方了,官方在这里初始化TemporaryGrid然后计算了旋度。由于使用LBM,所以我们不需要,直接跳过!

        

        我们直接开始模拟速度的预处理,Pre Sim Velocity。

        Grid 3D Resample Float

        从ScalarGrid获得密度,温度数据,给到临时类型变量。这种类型的变量正如名字一样,转瞬即逝,每一帧都可以不一样。

        Grid 3D Compute Boundary

        这个计算边界的,里面好复杂。我大概看了下,最后输出的Boundary。0代表流体单元,1代表固体单元,2代表空单元。

        下面那个是与固体速度有关的,跳过跳过,浪费性能。

        接下来一大堆节点都是计算外力的影响,比较有意思的是计算RadialForce,这个力的方向是格子与中心(默认0,0,0)的连线方向是一致的。这一部分就解决了流体方程关于外力的影响,后面计算的时候,就不要想着,唉,我重力还没算了。因为都在这个部分解决了。最后算好的速度通过变量写入到SimGrid中。

        

 

       首先获得速度,而后将值给到临时变量里,这里如果开启Debug,值就会变为0。然后采样RasterGrid所有属性,全部拿来吧你。在赋值给变量的时候,有不同的操作。

        在Grid3D中,数据的写入和读取是比较特别的,读取的是初始值,或者理解为上一时刻,上一帧的值。经过一系列的计算,我们写入的是修改后的值,我们不能在同一个Simulation Stages里读取我们修改后的值。这是我比较主观的理解,如有理解偏差,希望大家指出来。

        所以可以看到速度,密度和温度是采用之前提到过的ADD模式叠加来的,而Alpha和RGB采用其它方式处理的。

        Grid 3D Gas Init 没用到,跳过。

        然后从TransientGrid获得Boundary信息,最后写入ScalarGrid的值都要和Boundary进行一次判断,如果是0即流体单元才传值,否则就是0。

        这里是对速度进行平流,一般来说,对于流体格子。我们考虑格子里面的流体要到哪里去,但这样在数学上会出现问题。所以我们考虑当前的流体从哪里来,即逆向思考。如果不好理解的话,可以简化问题考虑最简单的情况然后用数值计算帮助理解。后面的Grid 3D Set Value说实话这一块我也不太清楚为什么这样做,官方写的注释也只是提到为解算做准备。希望大佬能指出来。好在并不影响其他东西,这个先暂时搁置一下。

    

计算散度

            在求解之前,官方又计算了一次散度。我们不用,直接跳过。

        而后官方就是进行了压力求解,而且还算了6遍,LBM在这里也只是算一遍。简单提一下官方的求解吧。一般来说,求解压力我们可以想象数值不断地向正确值逼近,每算一次值会向正确值逼近一点。每次前进的距离是有限的,我们当然希望它逼近的速度快一些,所以就有了SOR方法,引入权重,这个权重会随着每次计算还不断改变。权重的初始值的选取也不能太离谱,否则会离准确值越来越远,导致不能收敛。在此基础上,还用了红黑高斯-塞德尔迭代解法进一步加快速度。这个方法我还没详细研究,就不多提了。

        至此,就可以感受到在计算上有多大的提升了。


        

        首先从SimGrid获得速度与密度,其实密度到后面没有用到。之后会详细解释的。

        终于到LBM了,再回顾一下重要式子。

        平衡分布函数:

        f_i%5E%7Beq%7D%20%3D%20w_i%20%5Crho%20%5Cleft%5B%201%20%2B%20%5Cfrac%7Bu_i%20%5Ccdot%20v%7D%7Bc_s%5E2%7D%20%2B%20%5Cfrac%7B(u_i%20%5Ccdot%20v)%5E2%7D%7B2c_s%5E4%7D%20-%20%5Cfrac%7Bu_i%20%5Ccdot%20u_i%7D%7B2c_s%5E2%7D%20%5Cright%5D

        碰撞后的分布函数:

        f_i%5E%7BCol%7D%20%3D%20f_i%5E%7Bin%7D-k(f_i%5E%7Bin%7D-f_i%5E%7Beq%7D)

        与宏观的联系:

        %5Crho%20%3D%5Csum%5Cnolimits_%7Bi%7Df_%7Bi%7D%20

        %5Cupsilon%20%3D%5Csum%5Cnolimits_%7Bi%7Df_%7Bi%7D%20u_%7Bi%7D%20%2F%5Crho%20

        

        我把代码尽量写好注释。

        

         其它的和官方的没什么不同。

        这里还需要注意权重和平衡函数的计算,权重如何取值。涉及到公式的推导,我试着推了一遍,可始终有个2没有消掉,只能拿出结论来用。这个部分可以看书。上面的代码是按照格子速度与声速的平方比为4算的。如果按照3来算,把数据带进去我看了下好像肉眼看不出来有啥子区别。平衡函数这里我直接把密度变为了常数1,一开始这里本是Density,但是运行起来总是一开始有流体,后面就消失了。当时这里卡了很长时间,后来想想,density变为常数1。举个例子,一个墨水瓶掉进湖里,里面墨水经过一段时间肉眼就看不到了,常数1意味着违背了物质守恒,这个墨水瓶源源不断的冒出墨水,无穷无尽,所以始终看得到墨水。

        

  

         得到碰撞后的分布函数就可以得到我们想要的流体的宏观速度了。

     

  

         其他的和官方一样了,只需要把得到的速度(OutVelocity)和官方的压力梯度替换就行了。

        Dissipate Vector(Float)

        控制变量更快的消散(趋向于0)

        后面我没有进行任何修改,只是替换了自己改的模块的变量。后面就是对Scalar中的温度和密度进行平流,还有光照的相关处理,最后把结果给到RT上。

        

  

        材质这一块就是和官方一样的,官方将材质里的变量和Niagara中绑定到一起。然后材质中比较重要的是一个宏,MARCH_SINGLE_RAY_EMISSION,该宏可能是一个体积光线追踪的方法,这里我就没有往下研究了,用到了再去深入吧。

        最后提一下如何调节美术表现吧,这里可以了解蜡烛燃烧的过程。首先最上面白烟或者黑烟的是没有燃烧充分的燃料,这个颜色可以通过线性颜色重采样。中间的黄色部分是黑体辐射,这个也可以用线性颜色重采样。最下面的蓝色部分是电子跃迁产生的,好像不用考虑。    

       

        


    

        

        


        

        

        



在Niagara中基于LBM优化流体性能(下)的评论 (共 条)

分享到微博请遵守国家法律