You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Unity主线程外高效实现Raycast?求性能优化方案

优化Mesh顶视高度图生成性能的方案

问题背景

需要为静态Mesh生成顶视高度图,最初采用Unity内置Physics.Raycast会阻塞主线程,移至子线程又受Unity物理机制限制;自行实现的射线检测逻辑功能正常,但因遍历所有采样点并逐个检查全部三角形,运行速度极慢。


优化方案

1. 空间划分:大幅减少每个采样点的三角形检查量

当前代码无空间剔除逻辑,每个采样点都遍历所有三角形,这是核心性能瓶颈。通过预划分空间将三角形分组,采样时仅检查目标点所在区域的三角形:

  • 网格空间划分:将Mesh的x-z范围拆分为若干单元格,把每个三角形存入其覆盖的单元格;采样时仅检查当前点所在单元格及相邻单元格内的三角形。
  • AABB树:为所有三角形构建层次包围盒树,通过树结构快速剔除不可能与采样点相交的三角形。

网格划分示例代码:

// 预处理:构建三角形空间网格
struct TriangleData {
    public Vector3 p0, p1, p2;
    public float nx, nz, d; // 预计算平面系数
    public float minX, maxX, minZ, maxZ; // x-z包围盒
}

List<TriangleData>[,] spatialGrid;
int gridXCount = 32; // 根据Mesh范围调整
int gridZCount = 32;

// 初始化网格
spatialGrid = new List<TriangleData>[gridXCount, gridZCount];
for(int x = 0; x < gridXCount; x++)
    for(int z = 0; z < gridZCount; z++)
        spatialGrid[x, z] = new List<TriangleData>();

// 填充网格
foreach(var tri in preprocessedTriangles) {
    int startX = WorldToGrid(tri.minX, gridXCount, meshMinX, meshMaxX);
    int endX = WorldToGrid(tri.maxX, gridXCount, meshMinX, meshMaxX);
    int startZ = WorldToGrid(tri.minZ, gridZCount, meshMinZ, meshMaxZ);
    int endZ = WorldToGrid(tri.maxZ, gridZCount, meshMinZ, meshMaxZ);

    for(int x = startX; x <= endX; x++)
        for(int z = startZ; z <= endZ; z++)
            spatialGrid[x, z].Add(tri);
}

// 采样逻辑:仅检查目标点所在区域的三角形
for(int i = 0; i < xArray.Length; i++){
    for(int j = 0; j < zArray.Length; j++){
        float y = float.PositiveInfinity;
        bool isHit = false;
        int gridX = WorldToGrid(xArray[i], gridXCount, meshMinX, meshMaxX);
        int gridZ = WorldToGrid(zArray[j], gridZCount, meshMinZ, meshMaxZ);

        // 检查当前单元格及相邻单元格(避免边界漏检)
        for(int dx = -1; dx <= 1; dx++){
            for(int dz = -1; dz <= 1; dz++){
                int cx = gridX + dx;
                int cz = gridZ + dz;
                if(cx < 0 || cx >= gridXCount || cz < 0 || cz >= gridZCount) continue;
                foreach(var tri in spatialGrid[cx, cz]){
                    // 先判断采样点是否在三角形x-z包围盒内
                    if(xArray[i] < tri.minX || xArray[i] > tri.maxX || zArray[j] < tri.minZ || zArray[j] > tri.maxZ)
                        continue;
                    // 原有三角形包含判断及高度计算
                    ...
                }
            }
        }
        if(isHit) topView[i, j] = -y;
    }
}

// 辅助函数:世界坐标转网格坐标
int WorldToGrid(float worldPos, int gridCount, float minWorld, float maxWorld){
    float norm = (worldPos - minWorld) / (maxWorld - minWorld);
    return Mathf.Clamp(Mathf.FloorToInt(norm * gridCount), 0, gridCount - 1);
}

2. 预计算与过滤:减少重复运算与无效检查

  • 预计算三角形参数:提前计算每个三角形的平面方程系数(y = nx*x + nz*z + d)、x-z包围盒,避免采样时重复计算Vector3.Dot和除法。
  • 加强三角形过滤:除了过滤法向量y分量接近0的三角形,还可直接排除法向量y分量为负的三角形(顶视射线向下,这类三角形的交点高度必然低于上方三角形)。
  • 替换精确浮点判断:将s != 0、d == 0改为基于epsilon的判断(如s < -1e-6 || s > 1e-6),减少精度问题与分支预测开销。

3. 并行处理:利用多CPU核心加速

由于每个采样点的计算完全独立,可使用Parallel.For并行遍历采样点的x或z维度:

float[,] topView = new float[xArray.Length, zArray.Length];
Parallel.For(0, xArray.Length, i => {
    for(int j = 0; j < zArray.Length; j++){
        // 采样逻辑(使用预处理后的空间网格与三角形数据)
        ...
        if(isHit) topView[i, j] = -y;
    }
});

注:需确保所有预处理数据为只读,避免线程安全问题。

4. 替换算法:光栅化思路反转遍历逻辑

不再遍历每个采样点检查三角形,而是遍历每个三角形,更新其在x-z平面覆盖的所有采样点:

  • 将三角形投影到高度图网格,计算其覆盖的单元格范围,对每个单元格内的采样点计算高度并更新最大值。
  • 此方法在三角形数量远少于采样点数量时,效率提升尤为明显。

内容的提问来源于stack exchange,提问作者Anton

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 21:21:11