介绍一个基于有向距离场(SDF)的地图碰撞系统

因为处理不可通过时用的是一个栅格最后有128128个栅格数据,但是检测距离用的顶点,实则有129129个顶点,所以有个1的差值,到底是差在那通过画图就能看出来

https://baike.baidu.com/item/双线性插值/11055945?fr=aladdin

上面链接有公式,取当前栅格的四个顶点进行双线性插值

我优化了你的高度图的部分
将黑白图改为了颜色图,每个像素的颜色值就是距离

这样就减少了

优化:
因为计算量高达(N+1)(N+1)N*N次,可能会消耗大量时间。经试验,一张网格尺寸为128*128的地图,在纯h5环境、以及安卓的微信小游戏环境下,计算速度尚能接受,但是在iOS的微信小游戏环境下,计算时间高达50s,这显然是不能接受的。
所以,推荐使用事先处理好数据,然后导出json文件的方式,游戏运行时直接读取现成的json文件即可。
这就是以内存空间换取速度的思想,也是SDF系统的核心思想。

这一部分的耗时

然后学渣表示, 你的代码里面还有一个:gridLen这个参数没太理解是用来干嘛的

感谢大佬回复

mark…

啊,这是个不错的思路:+1:可以深入挖掘一下:stuck_out_tongue_winking_eye:
顺带gridLen指代一个单位格子在场景中的实际长度

这个地方为什么先乘个0.5再标准化?

这里为什么选择迭代3次?希望大神指教~~~

应该是arr.push(r<128&&g<128&&b<128);

啊这,如果要归一化的话就不需要再乘0.5了,感谢指正。
不过我仔细看了一下,发现这一块的代码是有问题的,并不需要做归一化。可以看一下这个新的工程:
sdfDemo.zip (996.1 KB)

代码更新

calGradient(pos:Vec3):Vec3{
    var delta=1;
    var dis0 = this.calPointDis(new Vec3(pos.x+delta,0,pos.z));
    var dis1 = this.calPointDis(new Vec3(pos.x-delta,0,pos.z));
    var dis2 = this.calPointDis(new Vec3(pos.x,0,pos.z+delta));
    var dis3 = this.calPointDis(new Vec3(pos.x,0,pos.z-delta));
    var result = new Vec3(dis0-dis1,0,dis2-dis3).multiplyScalar(0.5);
    //console.log(StringUtils.format("dis0=%s,dis1=%s,dis2=%s,dis3=%s,result=%s",dis0,dis1,dis2,dis3,result));
    return result;
}

这样写才符合求导公式

因为delta=1,所以最后可以直接*0.5
加上归一化是错的。可以看到去除归一化后,角色贴墙移动比原先稳定了许多。

==================================

第二个问题,这段我直接copy了《腾讯游戏开发精粹》的相关代码:

我的理解是,角色触碰墙壁进行位置修正后,可能会遇到修正后点的SD依然小于自身体积的情况(例如90度的转角),这时会试图沿着法线向量“退回”到安全区域。但是如前面的公式所示,法线计算的结果并不精准,有可能计算出来的结果依然不处于安全区域,这时候需要进行多次回退计算。计算的次数越多,目标点落在安全区域的概率就越大。
当然,每帧总计4轮的计算确实是一笔挺大的开销,我试过把迭代次数设为1,似乎也没有出什么问题。不过安全起见最后我还是保留了最多3次的循环验证。

1赞

多谢指正。因为最开始的一版存储的是通行数据,发布的时候这块用的是老代码:joy:

试了一下,delta=1,去掉归一化,虽然贴墙不会那么抖,但是帧数低的时候在墙角会出现穿墙的情况。30帧及以下。不知道是不是我改了其它参数的缘故

感谢分享,mark mark

我用2d改写了下,发现在90度夹角会穿墙,大佬们能提供点建议吗

质量贴,mark

腾讯游戏开发精粹 第一章讲的就是这个吧

mark~~~~~~~~~~~~~~~~~~~~~~~~~~

给力,学习了!

最近刚好读了一些字体sdf渲染相关文章,计算有向距离的算法可以参考这篇文章,时间复杂度可以从O(N^2)优化到O(N)。
https://zhuanlan.zhihu.com/p/65421383

1赞