官术网_书友最值得收藏!

1.4 SDF的碰撞檢測與碰撞響應

前面提到φ(x)≤0表示點x在障礙物內,那么碰撞檢測只需要得到點的φ值,然后與碰撞半徑r比較即可,φ(x)≤r表示角色與障礙物發生了碰撞。由于柵格地圖的SDF數據是離散存儲的,但角色移動是連續的,不能把角色在一個柵格內任意位置的φ值等同于柵格頂點,否則會在柵格邊界產生巨變。因此,在移動是連續的情況下,無法直接查表獲取角色所在位置的φ值,如圖1.4所示圓的圓心位置,需要根據周邊柵格頂點的φ值采樣獲取。

圖1.4 如何計算圓心的φ

因為距離本身是線性的,可以采用雙線性過濾(Bilinear Filtering)采樣角色位置的φ值,根據角色所處柵格的四個頂點線性插值可得到場景任意點的φ值,如圖1.5所示。

圖1.5 雙線性過濾示意圖

由此完成SDF的碰撞檢測,只需要查表和乘法計算,時間復雜度為O(1)。以下為插值獲得場景任意點的SD值的代碼。

    // 計算位置pos的SD值
    // 每個柵格的實際尺寸為grid,橫向柵格數量為width
    public float Sample(Vector2 pos) {
        pos = pos / grid;
        int fx = Mathf.FloorToInt(pos.x);
        int fy = Mathf.FloorToInt(pos.y);
        float rx = pos.x - fx;
        float ry = pos.y - fy;
        int i = fy * width + fx;
        return
          (sdf[i        ] * (1- rx) + sdf[i         + 1] * rx) * (1- ry) +
          (sdf[i + width] * (1- rx) + sdf[i + width + 1] * rx) * ry;
    }

當前幾乎所有的MOBA手游在搖桿移動過程中,碰到障礙物之后均是繞著障礙物滑行的,而不是直接停止,因為停止的體驗實在很糟糕。那么SDF在發生碰撞后如何處理繞障礙物滑行的問題呢?

如圖1.6所示,v表示搖桿方向(角色原始移動方向),當與障礙物發生碰撞后需要沿著v′方向滑行,v′和v的關系是

圖1.6 滑行

上式中,n為碰撞法線,如何獲取呢?

SDF為純量場,純量場中某一點上的梯度(Gradient)指向純量場增長最快的方向,因此可以利用SDF的梯度作為碰撞法線:

同時,φ(x)幾乎隨處可導,可以使用有限差分法(Finite Difference)求出x處的梯度:

從而得到碰撞法線n,求出在滑行方向實現碰撞后繞障礙物滑行。以下為求梯度方向的代碼。

    // 求位置pos的梯度方向
    public Vector2 Gradient(Vector2 pos) {
        float delta = 1f;
        return 0.5f * new Vector2(
            Sample(new Vector2(pos.x + delta, pos.y)) -
            Sample(new Vector2(pos.x - delta, pos.y)),
            Sample(new Vector2(pos.x, pos.y + delta)) -
            Sample(new Vector2(pos.x, pos.y - delta)));
    }

至此,得到當角色按搖桿方向移動時的實際移動方向代碼。

    // 獲取在移動過程中使用SDF得到的最佳位置
    public Vector2 GetVaildPositionBySDF(Vector2 pos, Vector2 dir, float speed) {
        Vector2 newPos = pos + dir * speed;
        float SD = Sample(newPos);

        // 不可行走
        if (SD < playerRadius) {
            Vector2 gradient = Gradient(newPos);
            Vector2 adjustDir = dir - gradient * Vector2.Dot(gradient, dir);
            newPos = pos + adjustDir.normalized * speed;

            // 多次迭代
            for (int i = 0; i < 3; i++) {
                SD = Sample(newPos);
                if (SD >= playerRadius) break;
                newPos += Gradient(newPos) * (playerRadius - SD);
            }

            // 避免往返
            if (Vector2.Dot(newPos - pos, dir) < 0)
                newPos = pos;
        }
        return newPos;
    }
主站蜘蛛池模板: 股票| 藁城市| 苍山县| 密云县| 灵山县| 镇远县| 汕头市| 凤台县| 巴林左旗| 海原县| 和顺县| 洱源县| 石家庄市| 吉林省| 阿瓦提县| 华亭县| 方城县| 科技| 定兴县| 平阳县| 当涂县| 广安市| 灌南县| 高邑县| 马龙县| 秦皇岛市| 永和县| 原平市| 如皋市| 房产| 内黄县| 苍梧县| 新密市| 新野县| 磐安县| 太康县| 昌黎县| 泸州市| 巴彦县| 鸡泽县| 东乌珠穆沁旗|