Niagara_Advanced内容示例 3.1 Color Copy by Cell

在这里插入图片描述

粒子效果

存在类似于框架一样的东西,将空间分为了8块。在大约1s的时间间隔下,粒子会随机出现在着八个格子内,并且在有粒子的每个格子中,又存在一个较大的Sprite粒子和多个较小的Sprite粒子,他们共享着相同的颜色。

Niagara蓝图部分

本系列开始介绍并着重研究一个新的结构——Neighbor Grid 3D,这是一种基于位置的哈希查找结构。

哈希表是什么?简单来讲就是通过键与哈希函数来为对应的数据分配存储位置。哈希表的优势是什么?快速查找。Neighbor Grid 3D即是希望起到这样的作用。它的使用中随时伴随着各种各样的“哈希函数”来进行空间分配和查找。

最后还需要注意一点,即蓝图中透露现在Neighbor Grid 3D仅限于GPU粒子。

回到蓝图中,其中有三个发射器,Grid_Visualizer是负责将Grid 3D显示出来,Grid_Write和Grid_Read是分别负责对Neighbor Grid 3D进行写入和读取的发射器。此外,因为Neighbor Grid 3D需要被两个多个发射器访问,这里将其定义成系统变量(位于System Update里,意味着每一帧都会发生创建和销毁操作,两者中间又发生着访问、渲染等显示相关的操作,由Emitter决定)。

在这里插入图片描述

效果实现分析

我们分别来看三个粒子发射器

Grid_Visualizer——显示Grid 3D

这个发射器和Neighbor Grid 3D的使用关系并不大,更多的是为了将Grid的边界显示出来,作以辅助理解之用。我们这里简要介绍其中的模块。

首先定义了两个Emitter级的参数——GridSize和GridResolution,分别用来控制绘制的Grid的大小(150x150x150,即边长为150的立方体)和分辨率(2x2x2,即总共8个格子)。

在这里插入图片描述

Emitter Update部分,使用的是Spawn Burst Instantaneous,其中的数目是自定义的动态输入模块GridVisualizerCount。

在这里插入图片描述

GridVisualizerCount中的运算,换句话说其实就是6x9,6个面,每个面上9个粒子。

在这里插入图片描述

Particle Spawn阶段的Grid Visualizer Location模块写入了每个粒子的位置Position和RibbonID两个属性,用以定义条带Ribbon渲染器中渲染的顺序(即哪个粒子连接哪个粒子)。

在这里插入图片描述

Grid_Write——写入Neighbor Grid 3D

这个发射器是负责生成立方体内的那些大粒子,并将一些信息写入了Grid 3D中,其中写入信息是在Simulation Stage里完成的。我们重点看这个自定义模块的内容。

在这里插入图片描述

模块主要是读粒子的位置,并将Execution Index数据写入到Grid中。具体来说,主要看其中的两端HLSL代码片段。

在这里插入图片描述

其中World BBox Size是之前定义好的。

在这里插入图片描述

左侧较小的HLSL代码片段中,输入Scale是一个Vector类型,输入的值是Neighbor Grid 3D大小的倒数;输出OutMatrix是一个Matrix类型。

OutMatrix[0][0] = Scale.x;
OutMatrix[1][1] = Scale.y;
OutMatrix[2][2] = Scale.z;
OutMatrix[3][3] = 1.0f;

OutMatrix[1][0] = 0.0f;
OutMatrix[2][0] = 0.0f;
OutMatrix[3][0] = .5f;

OutMatrix[0][1] = 0.0f;
OutMatrix[2][1] = 0.0f;
OutMatrix[3][1] =.5f;

OutMatrix[0][2] = 0.0f;
OutMatrix[1][2] = 0.0f;
OutMatrix[3][2] = .5f;

OutMatrix[0][3] = 0.0f;
OutMatrix[1][3] = 0.0f;
OutMatrix[2][3] = .5f;

其实就是用Scale构建了一个矩阵
O u t M a t r i x = ( 1 / 150 0 0 0 0 1 / 150 0 0 0 0 1 / 150 0.5 0.5 0.5 0.5 1 ) \mathbf{OutMatrix} = \begin{pmatrix} 1/150 & 0 & 0 & 0 \\ 0 & 1/150 & 0 & 0\\ 0 & 0 & 1/150 & 0.5\\ 0.5 & 0.5 & 0.5 & 1\\ \end{pmatrix}\\ OutMatrix=1/150000.501/15000.5001/1500.5000.51
较大的HLSL代码片段中,输入有NeighborGrid,即我们读取的Neighbor Grid 3D,Position是粒子的Position,SimulationToUnit是上面的OutMatrix矩阵,ExecIndex是当前粒子的索引。具体解读见代码注释

AddedToGrid = false;

#if GPU_SIMULATION

//求出粒子的位置转化为的在Grid 3D中的位置(单位位置,相当于世界空间转局部空间,再将局部坐标做一个归一化)
// Derive the Neighbor Grid Index from the world position
float3 UnitPos;
NeighborGrid.SimulationToUnit(Position, SimulationToUnit, UnitPos);

//将粒子的单位位置转化为所在格子的索引
int3 Index;
NeighborGrid.UnitToIndex(UnitPos, Index.x,Index.y,Index.z);

// Verify that the derived index is valid.
int3 NumCells;
NeighborGrid.GetNumCells(NumCells.x, NumCells.y, NumCells.z);

//确保求出的索引不超过前面设定格子总数(在三个维度上)
if (Index.x >= 0 && Index.x < NumCells.x && 
    Index.y >= 0 && Index.y < NumCells.y && 
	Index.z >= 0 && Index.z < NumCells.z)
{
    //将三维的索引转化为1维索引(目的都是为了用索引确定唯一的一个格子)
    int LinearIndex;
    NeighborGrid.IndexToLinear(Index.x, Index.y, Index.z, LinearIndex);
	
    //当前该索引下的格子所拥有的属性之一Neighbor Count自增1表示,格子自生所能容纳的信息数+1(Neighbor即是信息的容器,在本例中,可以简单得理解为大粒子,即一个格子中有几个大粒子,就存了几个Neighbor)。
    // Increment the neighbor count for this cell. This records the number of overlaps
    // and can return a higher count than the MaxNeighborsPerCell
    int PreviousNeighborCount;
    NeighborGrid.SetParticleNeighborCount(LinearIndex, 1, PreviousNeighborCount);
	
    //每个格子所能存储的最多信息数,是我们自己设定好的
    int MaxNeighborsPerCell;
    NeighborGrid.MaxNeighborsPerCell(MaxNeighborsPerCell);
	
    //当我们增加的Neighbor Count超过了所能容纳的最大值时,就不再继续往里塞东西了。
    // Limit the number of neighbors added to each cell
    if (PreviousNeighborCount < MaxNeighborsPerCell)
    {
        AddedToGrid = true;
		
        //此时,我们可以通过Index找到格子,但是怎么找到某一个存进去的Neighbor,所以需要给这个新人Neighbor一个Index索引(一维的)
        int NeighborGridLinear;
        NeighborGrid.NeighborGridIndexToLinear(Index.x, Index.y, Index.z, PreviousNeighborCount, NeighborGridLinear);
		
        //本例中,是将ExecIndex信息,存入了Neighbor
        int IGNORE;
        NeighborGrid.SetParticleNeighbor(NeighborGridLinear, ExecIndex, IGNORE);
    }		
}
#endif    

迭代的结果就是,Grid_Write这帧的这些粒子被分别写入了不同的格子当中(其中如果有某个格子满了,就不再往那个格子里塞了)。

是不是看得有点懵。我们模拟一下,现在要弄清楚靠前面的左上角的格子存了一个什么信息,怎么查?首先搞清楚这个格子的索引,假如是1,那么要继续在这个索引是1的格子内部看看它存了多少信息,打开一看Neighbor Count是2,是2个Neighbor信息,再仔细看看索引是?的Neighbor里放的是什么,哇,原来是个ExecIndex,是某个粒子的执行索引啊。那么我们拿到这个执行索引可以干嘛呢?这就是后面Grid_Read里完成的工作了。

Grid_Read

对Neighbor Grid 3D的读取还伴随着粒子属性的读取(Particle Attribute Reader),最终通过Sumulation Stage : Query Grid来完成整合。其中主要是两个自定模块——Find Closest Neighbor和Copy Color。

在这里插入图片描述

Find Closest Neighbor做的事情就是找到距离当前粒子最近的那个格子所存储的Neighbor中的那个ExecIndex。

在这里插入图片描述

NeighborIndex = -1;

#if GPU_SIMULATION

bool Valid;

//找到粒子所在的格子的索引
// Derive the Neighbor Grid Index from the world position
float3 UnitPos;
NeighborGrid.SimulationToUnit(Position, SimulationToUnit, UnitPos);

int3 Index;
NeighborGrid.UnitToIndex(UnitPos, Index.x,Index.y,Index.z);

// Initialize the closest distance to a really large number
float neighbordist =  3.4e+38;

// loop over all neighbors in this cell
int MaxNeighborsPerCell;
NeighborGrid.MaxNeighborsPerCell(MaxNeighborsPerCell);

//遍历找到的格子中所有的Neighbor
for (int i = 0; i < MaxNeighborsPerCell; ++i)
{
    // Find the ExecIndex for the current neighbor particle
    
    //从0到最大,挨个翻开来看里面是不是存了东西
    int NeighborLinearIndex;
    NeighborGrid.NeighborGridIndexToLinear(Index.x, Index.y, Index.z, i, NeighborLinearIndex);
	
    //把里面村的东西拿出来赋予到当前变量上
    int CurrNeighborIdx;
    NeighborGrid.GetParticleNeighbor(NeighborLinearIndex, CurrNeighborIdx);

    // Only proceed if the returned index is valid. This is most often triggered
    // by there being fewer neighbors in the cell than the MaxNeighborsPerCell limit.
    if (CurrNeighborIdx != -1)
    {
        // Use the Attribute Reader to query the position of the neighbor particle
        float3 NeighborPos;
        //通过Attribute Reader以及拿到的ExecIndex来查询里面粒子的位置,写入到上面的变量里
        AttributeReader.GetVectorByIndex<Attribute="Position">(CurrNeighborIdx, Valid, NeighborPos);

        // Compare the distance found maintaining the closest
        const float3 delta = Position - NeighborPos;
        const float dist = length(delta);
		//跌打查找距离当前粒子最近的那个存入的Neighbor粒子
        if( dist < neighbordist )
        {
            neighbordist = dist;
            NeighborIndex = CurrNeighborIdx;
        }
    }  
}    

#endif

这个模块输出一个NeighborIndex,在上面的代码中,已经用其进行了一次位置查找,后面的Copy Color其实是一样的,是做了一次颜色查找,过程更为简单。

在这里插入图片描述

总结

本例已经是涉及到一些高级的数据结构,和代码语言,单单看表层代码已经很难去理解代码片段的功能了。所以此次的话借助了源码,即通过联系代码中的上下文(包括代码和注释),找到函数的用途和功能,从而帮助理解。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Claude的羽毛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值