1417232lld2xaa7i626dfl.png
  GameRes游资网授权发布 文/paraself

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

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

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

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

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

  为什么是GPU

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

none.gif  物体与树的叶子节点进行碰撞
none.gif 物体与树的叶子节点进行碰撞
  Verlet物理本身因为其性质,可以比较方便的移植到GPU上。然而,对于叶子节点的GPU计算,却有诸多限制。

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

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

none.gif 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.  
  3. uniform float leafRadius; //the radius of the leaf collider
  4. uniform float3 colliderTarget;//x,y = positon,z = radius
  5.  
  6. fixed4 frag(v2f_img i) : SV_Target {
  7.  
  8. float2 pos = tex2D(LeafPositonTex,i.uv0); //sample the texture to get the positon of leaf
  9. float2 distVector = pos - colliderTarget.xy;
  10. float dist2 = distVector.x * distVector.x + distVector.y * distVector.y;
  11. float dist = sqrt(dist2);//get the real distance betwee the two
  12. //get how much two circle are colliding into each other,
  13. //if pDepth > 0,then colliding happens,
  14. float pDepth = leafRadius + colliderTarget.z - dist;
  15. bool isColliding = pDepth > 0;
  16. //by doing so, we do not need to use if-else branching
  17. distVector *= (pDepth/dist * isColliding);
  18. return pos+distVector;
  19. }
复制代码
  接下来我会在其他的篇幅里,探讨其他的verlet树的实现问题。
锐亚教育

锐亚教育,游戏开发论坛|游戏制作人|游戏策划|游戏开发|独立游戏|游戏产业|游戏研发|游戏运营| unity|unity3d|unity3d官网|unity3d 教程|金融帝国3|8k8k8k|mcafee8.5i|游戏蛮牛|蛮牛 unity|蛮牛