游戏编程:二叉树实验中Verlet树叶子节点的GPU碰撞算法

最近在FISH当中,我所做的工作是为整个游戏世界构建基于真实物理模拟的树。FISH中的树,利用Verlet物理算法来进行模拟,主要形态为二叉树形式。
 

FISH中的二叉树实验


对于一棵树来说,他所拥有的数据类型主要有2种:节点和约束。其中节点主要表示树形结构的每一个分叉点;而约束主要表示,点与点之间的关系。目前的约束种类如下:

边约束 - 也叫做弹簧约束。顾名思义,让两个节点保持固定的距离。也就是树形结构中的枝干。

角度约束 - 让3个节点之间保持固定的夹角。

这里我们暂且先不讨论树形结构本身的数据结构,生成算法,以及Verlet积分如何修改节点位置。这篇文章主要回顾一下,在GPU上,如何对处理叶子节点的碰撞问题。

为什么是GPU

FISH是一个非常注重环境体验的游戏,利用Verlet物理,可以实现树的柔体运动。而将树的物理运动模拟算法,以及叶子节点的碰撞算法放到GPU上,可以实现大规模的树群效果,这种overwhelming的感觉,使我们一直做追求的。目前,CPU上的物理模拟和检测已经实现,算法验证完成,效果展示如下。
 

物体与树的叶子节点进行碰撞

物体与树的叶子节点进行碰撞


Verlet物理本身因为其性质,可以比较方便的移植到GPU上。然而,手游账号购买对于叶子节点的GPU计算,却有诸多限制。

首先,Unity本身对于Coompute Shader的支持,仅限特定的DX11平台。因此,我们必须使用vert-frag管线来实现GPGPU计算。

再次,物理碰撞检测算法一般包含Broad Phase和Narrow Phase 两部分。其中Broad Phase 主要用于构建场景中碰撞盒的BVH (Bounding Volume Hirerarchy)。

Bounding Circle


通过构建BVH,来配对碰撞盒。而在Narrow Phase中,对已经配对的碰撞盒进行二次的检测,用以确定最终的碰撞结果。对于这两个过程,CPU实现起来相对灵活和容易。而放到vert-frag管线上,则会遇到数据结构和算法上的诸多限制。因此在实际的问题处理中,我们通过简化和默认一些设定,来达到快速方便的进行碰撞检测。

已有的基本条件

对于树形结构来说,叶子节点全部参与碰撞。每一个叶子节点我们默认具有同样的半径r。每一个叶子节点拥有自己在2D空间中的位置向量(px,py)。叶子节点与节点之间不发生碰撞,碰撞仅仅发生在叶子节点与外来碰撞盒之间。外来碰撞盒一般是例如,石头,鱼,生物体类似这些物体。目前支持外来碰撞盒的种类为圆形,计划加入多边形的支持。因此,目前的问题可以表述为,对于具有相同半径的n个叶子节点,以及具有不同半径的m个参与碰撞的外来碰撞盒,检测并判断哪些叶子节点和外来碰撞盒之间是相互叠加(也就是发生碰撞)的。假设任意一对叶子节点和外来碰撞盒的发生叠加,并且叠加距离为d,那么把2者向各自相反的距离,推开d/2的距离。

而初始的数据则是,把n个节点的数量取到n的下一个power of 2。例如,当前叶子节点数量有7个,那么我们暂且把7取到8,也就是2的3次方。这是因为,GPU对于材质的要求,尽量是2的次方,这样才会性能比较好。

接着,构建一个拥有8个像素点的RenderTexture,形式为Tex1D,格式为RGFloat。这是问题初始的数据,我们需要经过一些列的Vertex 和 Fragment Shader 来对这个RenderTexture进行操作,并且把外来碰撞盒的信息作为参数传入Shader中来配合计算,直到输出我们想要的结果。

最初的想法——通过多次Blit生成BVH

最初,我是想通过多次的DownSample blit,来生成多个Text1D,每个对应一个层级的BVH(这个想法虽然可行,但是后面被我否定了)。具体做法如下:

Broad Phase 生成BVH

首先,生成BVH。如果不太了解BVH建议先熟悉一下概念,这里我们主要使用二叉树的Bounding Circle 来生成BVH。对于原先存储叶子节点信息的Tex1D,它具有n个像素点。我们为每两个相邻的点,生成一个Bounding Circle,先把它DownSample & Blit到一个n/2像素点的Tex1D上,每个像素采样前一个层级的Tex1D的两个点的位置,半径是已知的全局变量。这时候可以计算出2个叶子节点的Bounding Circle了,然后存储到这个像素里,用RGBFloat格式,RG存位置,B存半径。这时候我们有n/2个Bounding Circle 的信息了。

然后,对于下一层级,重复上面的过程,这个时候我们应该有多一个Tex1D了,然后有n/4个Bounding Circle了。

最后重复这个过程直到最后我们生成一个只有4个像素的Tex1D,作为整个BVH的起始。

至此,我们应该有了一系列的Tex1D,每个存对应层级的BVH。

共计 2*n-8 次采样,n-4次输出。

Narrow Phase 单个物体 Traverse BVH

首先,NP的碰撞处理,针对每个外来碰撞盒单独进行。先把外来碰撞盒的位置和半径传入Shader。

然后,从从最底部的层级开始。这一层的Tex1D只有4个像素。每一个像素检测该像素所含的Bounding Circle与外来碰撞盒之间的碰撞关系,如果发生碰撞,那么这个像素,假设为a,则做标记。接着把这个层级的Tex1D,UpSample到上一层级的8个像素中。同时,碰撞的标记,也会被UpSample到上一层级。假设为b,c像素点。

来到上一层及,若是该像素点,有来自于下一层及的标记的话,则进一步进行检测,其他的则放弃输出,或者输出标记的放弃颜色。b,c像素点,在这一层因为有来自a像素点的标记,所以,进一步对b和c进行检测,假设,c点发生碰撞,而b点没有。则在再次UpSample的时候,输出c点对应的2个像素点进行标记。

重复这一过程,直到输出到了所有的叶子节点。这时,我们已经对所有的叶子节点进行了标记,每个叶子节点,都知道了自己是否于碰撞体发生了碰撞,此时,计算碰撞,并重新输出新的位置,就得到了碰撞的结果。

共计 n-4次采样,2*n-8次输出。

因此对于BP和NP,共计3*n-12次采样,3*n-12次输出。<span style="color:gray">(n>=4)</span>

虽然算法实现了。但是感觉仅仅是为了实现算法而实现算法,没有考虑GPU的特性。GPU其实是一种SIMD的硬件。因此,或许可以考虑直接把叶子节点和外来碰撞盒进行判断。这里大概写了下叶子节点与外来碰撞体之间的碰撞检测。对于n个节点,采样n次,输出n次。反而更高效。
 

  1. //This is a example frag shader
  2. uniform float leafRadius; //the radius of the leaf collider
  3. uniform float3 colliderTarget;//x,y = positon,z = radius
  4. fixed4 frag(v2f_img i) : SV_Target {
  5.     float2 pos = tex2D(LeafPositonTex,i.uv0); //sample the texture to get the positon of leaf
  6.     float2 distVector = pos - colliderTarget.xy;
  7.     float dist2 = distVector.x * distVector.x + distVector.y * distVector.y;
  8.     float dist = sqrt(dist2);//get the real distance betwee the two
  9.     //get how much two circle are colliding into each other,
  10.     //if pDepth > 0,then colliding happens,
  11.     float pDepth = leafRadius + colliderTarget.z - dist;
  12.     bool isColliding = pDepth > 0;
  13.     //by doing so, we do not need to use if-else branching
  14.     distVector *= (pDepth/dist * isColliding);
  15.     return pos+distVector;
  16. }
复制代码


接下来我会在其他的篇幅里,探讨其他的verlet树的实现问题。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值