GLES3和WebGL2中GPU上成千上万个唯一粒子的交互算法

逻辑算法的描述,以及以技术演示游戏的形式对工作示例进行分析


此演示的WebGL2版本https://danilw.itch.io/flat-maze-web有关其他链接,请参阅本文。



本文分为两部分,第一部分涉及逻辑,第二部分涉及游戏中的应用程序, 第一部分


  • 主要特点
  • 链接和简要说明。
  • 逻辑算法。
  • 逻辑的局限性。 错误/功能和角度错误。
  • 访问索引数据。

游戏演示的进一步说明, 第二部分


  • 此逻辑的二手功能。 并快速渲染一百万像素的粒子。
  • 实现,对代码进行一些注释,在两个方向描述冲突。 并与玩家互动。
  • 使用opengameart链接到使用的图形,以及用于阴影的着色器。 和文章链接到cyberleninka.ru

第一部分


1.主要特点


这个想法是实时地在彼此之间碰撞成千上万个粒子,其中每个粒子都有唯一的标识符ID


当对每个粒子进行索引时, 可以控制任何粒子的任何参数 ,例如质量,其健康(hp)或损坏,加速,减速度,遇到的对象以及对事件的反应取决于粒子的类型/索引,每个粒子还具有唯一的计时器,依此类推。


GLSL上的所有逻辑可以完全移植到任何游戏引擎和任何具有GLES3支持的操作系统。


粒子的最大数量等于帧缓冲区的大小(fbo,所有像素)。


舒适的粒子数量 (当有空间可以相互作用时)为(Resolution.x*Resolution.y/2)/2x每个第二像素和y每个第二像素,这就是逻辑描述如此表示的原因。


本文的第一部分显示了最小的逻辑;第二部分,以游戏为例,显示了具有大量交互条件的逻辑。


2.链接和简短描述


我对此逻辑进行了三个演示:


1.在 GLSLfragment -shader上 ,在shadertoy https://www.shadertoy.com/view/tstSz7上 ,全部逻辑请参见BufferC代码。 此代码还允许您在片段着色器上的任意位置显示数十万个带有UV的粒子,而无需使用实例粒子。



2.将逻辑移植到实例粒子 (由Godot用作引擎)



链接Web版本 exe(win)项目为particles_2D_self_collision


简短说明: 这是对实例粒子的不好的演示 ,因为我在整个地图可见的地方做出了最大的增加,总是处理640x360粒子(230k),这一事实很多。 参见下面的游戏说明,我做对了,没有多余的颗粒。 (视频中存在粒子索引错误,已在代码中修复)


3.游戏,有关游戏的描述,请参见下文。 链接Web版本exe(win)


3.逻辑算法


简要地:


逻辑类似于落沙,每个像素保留位置的分数值(在其像素内移动)和当前加速度。


逻辑检查半径1的像素,它们的下一个位置要转到该像素(由于此限制,请参见下面的限制) ,还检查半径2的像素是否排斥(碰撞)。


通过将逻辑转换为int-float并减小给定位置pospos速度的大小,可以保存唯一索引。


数据以这种方式存储:( 由于存在此错误,请参见限制)


 pixel.rgba r=[0xfffff-posx, 0xf-data] g=[0xfffff-posy, 0xf-data] b=[0xffff-velx, 0xff-data] a=[0xffff-vely, 0xff-data] 


在代码中 ,BufC的行号https://www.shadertoy.com/view/tstSz7、115个过渡检查,139个冲突检查。


这些是获取相邻值的简单循环。 而且条件是,如果位置等于当前像素的位置,则将数据移动到该像素(由于此限制) ,并且vel的值根据相邻像素(如果有)而变化。


这就是所有粒子逻辑。


如果粒子比1像素更近, 最好将粒子彼此隔开1个像素的距离,然后会有排斥力,例如游戏中带有迷宫的地图,粒子由于它们之间1像素的距离而静止不动。


接下来是渲染(rendering), 在片段着色器的情况下,以1的半径获取像素以显示相交区域。 对于实例粒子,在从线性视图转换为二维数组的地址INSTANCE_ID处获取像素。


4.逻辑限制。 错误/功能和角度错误


  1. 代码中的像素大小 BALL_SIZE必须在计算限制内,大于sqrt(2)/2且小于1 。 越接近1,像素内部(像素本身)的行进空间越小,则空间越大。 需要这样的大小,以使像素不会彼此落在一起,当您有小物体时,可以将其设置为小于1,从而产生小于1像素(计算得出)的物体幻觉。
  2. 速度不能超过1像素,否则像素将消失。 但是您可以使每帧的速度超过1 ,如果您制作多个帧缓冲区(fbo /视口)并一次处理多个逻辑步骤,则帧速度将增加等于附加fbo数量的次数。 这是我在水果演示中所做的,并使用了指向shadertoy(bufC复制到bufD)的链接。
  3. 压力限制 (例如重力或其他力法线贴图)。 如果几个相邻像素占据此位置(请参见上图),则仅保存一个像素,其余像素消失。 在有关阴影的演示中很容易注意到这一点,将鼠标设置为Force,将Common中MOUSE_F的值更改为10 ,然后将粒子定向到屏幕的一角,它们将彼此消失。 或与Common中的重力值maxG相同。
  4. 角度错误。 为了使此逻辑在GPU(实例化)-粒子中运行,最好(更便宜,更快)在instance-shader中计算位置以及所有其他粒子参数以进行显示。 但是Angle 不允许为着色器使用多个fbo纹理,因此必须将部分逻辑的计算转移到Vertex-shader ,以便从实例着色器转移索引号。 这是我在两个使用GPU粒子的演示中所做的。
  5. 两个演示中都有一个严重的错误 (游戏除外),如果位置值不是1/0xfffff倍数,则会丢失位置值。错误测试在此处https://www.shadertoy.com/view/WdtSWS
    更准确地说,这不是错误,为简单起见,应该如此,作为此算法的一部分,我称其为错误。

修复错误:
请勿将位置值转换为int-float ,因此,将0xffff 0xff ,可用于数据的8位,但是将保留数据的0xffff值,这对于很多事情来说已经足够了。
我只是在游戏演示中做到了这一点 ,我只将0xffff用于存储粒子类型,动画计时器,运行状况以及仍然有可用空间的数据。


5.访问索引数据


instanced-particle有其自己的INSTANCE_ID ,它使用粒子逻辑(bufC,例如着色器)从帧缓冲区的纹理中提取一个像素,如果在那里解包该粒子的粒子(请参阅数据存储),则通过该ID读取包含粒子数据的纹理(bufB) ,例如着色器上的示例)。


在shadertoy示例中,bufB仅存储每个粒子的颜色,但是很明显可以存在任何数据,如质量,加速度,减速度前面提到的,以及任何逻辑动作(例如,完成后可以将任何粒子移动到任何位置(传送))代码中的相应逻辑动作),您还可以从键盘控制任何粒子或组的移动...


我的意思是,您可以对每个粒子执行任何操作,就好像它们是处理器数组中的普通粒子一样,从GPU粒子的双向访问可以更改其状态,但是从CPU可以按索引更改粒子状态(使用逻辑操作和纹理)数据缓冲区)。


第二部分


1.此逻辑的使用功能。 快速渲染一百万个像素的粒子


粒子的帧缓冲区(fbo /视口)的大小为1280x720,部件位于1之后,即23万个活动粒子(迷宫中的活动元素)。
屏幕上始终不超过12,000个GPU实例化的粒子。


逻辑用途:


  • 播放器逻辑与粒子逻辑是分开的,并且仅从粒子缓冲区读取数据。
  • 与物体碰撞时,播放器会放慢速度。
  • 怪物类型的物品会对玩家造成伤害。
  • 玩家进行了2次攻击,一次击退周围的一切,第二次产生粒子,如火球 (图片如下)
  • 火球类型有其自身的质量,双向跟踪与其他粒子的碰撞效果。
  • 在与火球的碰撞中摧毁了其他粒子,例如演员僵尸 (一种类型的演员是无敌的)
  • 一次碰撞后火球熄灭
  • 物理级别-玩家排斥树木和正方形,其他粒子不相互作用,没有加速度作用于火球
  • 动画计时器对于每个粒子都是唯一的

相比于水果演示程序(有开销), 在此游戏中,GPU实例化粒子的数量仅为1.2万。


看起来像这样:



它们的数量取决于地图的当前缩放( zoom ),并且增加被限制为某个值,因此仅考虑屏幕上可见的那些。
屏幕随播放器移动,计算移动的逻辑有些复杂,而且非常情况,我怀疑她是否会在另一个项目中找到应用。


2.实现,对代码进行一些注释。


所有游戏代码都在GPU上。


/shaders/scene2/particle_logic2.shader文件中,随着顶点功能的增加, 用于计算屏幕中粒子移动的逻辑是粒子着色器文件(顶点和片段),不是实例化着色器,实例化着色器不执行任何操作,仅由于以下原因传递其索引:上述错误。


粒子按类型和粒子相互作用的整个逻辑在一个文件中,这是一个着色器的文件着色器文件着色器/scene2/particles_fbo_logic.shader


 // 1-2 ghost // 3-zombi // 4-18 blocks // +20 is on fire // 40 is bullet(right) 41 left 42 top 43 down 

数据存储像素[pos.x, pos.y, [0xffff-vel.x, 0xff-data1],[0xffff-vel.y, 0xff-data2]]
data1是类型,data2是HP或计时器。


计时器在每个粒子上运行 ,计时器的最大值是255,我不需要那么多,我只使用1-16最大值( 0xf ),而0xf保持未使用状态,例如您可以存储实际的HP值,而对我来说不使用。 (即,是的, 我使用0xff作为计时器 ,但实际上我的动画少于16帧,而0xf足够了,但我不需要其他数据)
实际上0xff用于燃烧树木的计时器,它们在255帧后变成僵尸。 计时器逻辑部分位于粒子帧缓冲区着色器的type_hp_logic中(上面的链接)。


火球在第一次击中时熄灭,并且被击中的对象也执行其动作的双向碰撞操作的示例


文件着色器/场景2 / particle_fbo_logic.shader第438行:


 if (((real_index == 40) || (real_index == 41) || (real_index == 42) || (real_index == 43)) && (type_hp.y > 22)) { int h_id = get_id(fragCoord + vec2(float(x), float(y))); ivec2 htype_hp = unpack_type_hp(h_id); int hreal_index = htype_hp.x; if ((hreal_index != 40) && (hreal_index != 41) && (hreal_index != 42) && (hreal_index != 43)) type_hp.y = 22; } else { if (!need_upd) { int h_id = get_id(fragCoord + vec2(float(x), float(y))); ivec2 htype_hp = unpack_type_hp(h_id); int hreal_index = htype_hp.x; if (((hreal_index == 40) || (hreal_index == 41) || (hreal_index == 42) || (hreal_index == 43)) && (htype_hp.y > 22)) { need_upd = true; } } } 

real_index是一种类型,上面列出了一些类型,40-43是火球
另外type_hp.y > 22是计时器的值,如果大于22,则说明火球什么都没有遇到。
h_id = get_id(...获取遇到的粒子的类型和HP(计时器)的值
hreal_index != 40...被忽略的类型(其他火球
type_hp.y = 22计时器设置为22,这表明该火球与一个对象碰撞。
else { if (!need_upd)变量need_upd检查是否存在重复的冲突,因为该函数处于循环中,所以我们遇到了一个火球
h_id = get_id(...如果尚未发生冲突,我们将使用类型和计时器的对象。
hreal_index == 40...htype_hp.y > 22碰撞对象是火球 ,它不会熄灭。
need_upd = true标志,因为它遇到了火球 ,所以必须更新类型。


再行481
if((need_upd)&&(real_index<24)){ real_index <24的类型小于24,则存在不燃烧的僵尸和鬼树,然后在这种情况下,我们将根据当前类型更新类型。


因此,几乎可以完成对象的任何交互。


与玩家互动:


文件着色器/场景2 / logic.shader第143行函数player_collision


此逻辑读取4x4像素半径内播放器周围的像素,获取每个像素的位置,并将其与播放器位置进行比较,如果找到一个元素,则接下来进行类型检查,如果这是怪物,则我们从播放器中获得HP。


这个工作有点不准确,我不想修复它 ,可以使此功能更准确。


攻击过程中,粒子会从玩家身上推开并产生排斥效果:


帧缓冲区(视口)用于编写当前动作的法线,粒子( particles_fbo_logic.shader )在其位置获取(从正常)纹理并将该值应用于其速度和位置。 该逻辑的整个代码实际上只是几行,文件force_collision.shader


单击鼠标左键, 火球 壳会飞起来;它们的外观不是很自然 ,它们没有纠正并以这种形式离开。


您可以为生成的粒子创建一个正常的区域(形状),并且相对于播放器出现偏移(此操作未完成)。
或者,您可以将火球作为玩家作为一个单独的对象,然后将法线绘制到缓冲区中以将粒子推离火球 ,即类似于玩家...
谁需要认为自己会自己解决这个问题。


3.使用opengameart和阴影着色器链接到使用的图形


我获得了指向cyberleninka.ru上一篇文章的链接。
在其中所用算法的描述中,也许比本文中有更详细和正确的描述


阴影着色器的工作非常简单,基于此着色器https://www.shadertoy.com/view/XsK3RR (我有经过修改的代码)
着色器构建1D径向光照贴图



地板着色代码着色器/ scene2 / mainImage.shader中的着色和着色


从网站https://opengameart.org 链接到所使用的图形,游戏中的所有图形
火球 https://opengameart.org/content/animated-traps-and-obstacles
角色https://opengameart.org/content/legend-of-faune
树木和树木https://opengameart.org/content/lolly-set-01
(以及opengameart的更多图片)


菜单中的图形由2D_GI着色器获得,该工具用于创建此类菜单:



谁读到最后-做得好:)
如果您有任何疑问,请询问,我可以根据要求补充说明。

Source: https://habr.com/ru/post/zh-CN474676/


All Articles