如何在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
相关产品推荐
相关产品推荐

