Unity游戏编程研究院FISH鱼 | 开发日志

Verlet树叶子节点的GPU碰撞算法

2015-08-28  本文已影响1942人  paraself

Verlet树叶子节点的GPU碰撞算法

Collision Detection for the Leaf Particles of Verlet Tree

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

FISH中的二叉树实验

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

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

Narrow Phase 单个物体 Traverse BVH

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

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

//This is a example frag shader

uniform float leafRadius; //the radius of the leaf collider
uniform float3 colliderTarget;//x,y = positon,z = radius

fixed4 frag(v2f_img i) : SV_Target {

    float2 pos = tex2D(LeafPositonTex,i.uv0); //sample the texture to get the positon of leaf
    float2 distVector = pos - colliderTarget.xy;
    float dist2 = distVector.x * distVector.x + distVector.y * distVector.y;
    float dist = sqrt(dist2);//get the real distance betwee the two
    //get how much two circle are colliding into each other,
    //if pDepth > 0,then colliding happens,
    float pDepth = leafRadius + colliderTarget.z - dist;
    bool isColliding = pDepth > 0;
    //by doing so, we do not need to use if-else branching
    distVector *= (pDepth/dist * isColliding);
    return pos+distVector;
}

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

上一篇 下一篇

猜你喜欢

热点阅读