You need to enable JavaScript to run this app. 如何基于一维float数组存储的高度图构建3D网格
优惠活动
大模型
产品
解决方案
定价
更多

如何基于一维float数组存储的高度图构建3D网格

一维高度图构建3D网格核心逻辑拆解

核心本质:二维网格的扁平化存储

  • 二维数组存储高度图时,底层内存本来就是连续按行排布的,比如255x255的二维数组map[y][x],实际存储顺序就是第0行所有值、第1行所有值……依次拼接。
  • 一维数组存储只是把这个连续的内存空间直接暴露给使用者,所有计算都是围绕“一维索引 <-> 二维坐标”的互相映射展开,没有额外的数学黑魔法。

标注代码段逐行拆解

1. 一维索引转二维坐标逻辑

int x = i % mapSize;
int y = i / mapSize;
int meshMapIndex = y * mapSize + x;
  • 高度图采用行优先存储规则:第0行的所有高度值对应一维索引0 ~ mapSize-1,第1行对应mapSize ~ 2*mapSize-1,以此类推。
  • 反向计算坐标时:
    • i % mapSize取余数,得到当前点在所在行的列号x(每满mapSize个元素就换一行,余数就是列偏移量)
    • i / mapSize是整数除法,得到当前点所在的行号y(累计有多少个完整mapSize长度的行)
  • 这行meshMapIndex = y * mapSize + x其实是把刚才算出的坐标反过来推回一维索引,计算结果和循环变量i完全相等,写出来只是为了让逻辑更直白,避免后续索引计算出错。

2. 三维顶点位置计算

Vector2 percent = new Vector2 (x / (mapSize - 1f), y / (mapSize - 1f));
Vector3 pos = new Vector3 (percent.x * 2 - 1, 0, percent.y * 2 - 1) * scale;
pos += Vector3.up * map[meshMapIndex] * elevationScale;
  • 先把x、y的网格坐标归一化:除以mapSize-1是把坐标范围从[0, mapSize-1]映射到[0,1]的比例值,不管mapSize取值多少,最边缘的点比例永远是0或1。
  • 再把比例值映射到三维空间:percent * 2 - 1是把0~1的范围转成[-1,1],也就是让网格中心对齐坐标原点,乘以scale是把整个网格缩放到需要的世界大小。
  • 最后叠加高度值:直接取一维数组里对应索引的高度,乘以elevationScale调整地形的起伏夸张程度,沿Unity的Y轴(向上方向)抬升顶点,就得到了最终的3D顶点位置。

三角形索引生成逻辑

很多人卡在这里本质是没搞懂“单个四边形拆成两个三角形”的顶点偏移规律:

  • 每个非边缘的顶点(x,y),都会和它三个邻居构成一个正方形小四边形:
    • 当前顶点:meshMapIndex
    • 右侧邻居(x+1,y):索引是meshMapIndex + 1(同行往右移一列)
    • 下侧邻居(x,y+1):索引是meshMapIndex + mapSize(下一行同一列,整行长度为mapSize,所以偏移mapSize)
    • 右下邻居(x+1,y+1):索引是meshMapIndex + mapSize + 1
  • 这个四边形需要拆成两个逆时针顺序的三角形,索引顺序特意选成逆时针,是为了让三角形的法线方向朝上,符合Unity的面剔除规则,不会出现地形从上方看是透明的情况。
  • 代码里直接计算三角起始位置t,是为了不用循环累加计数:每个四边形占6个三角形索引,前面总共有y*(mapSize-1)+x个完整的四边形,直接乘6就能定位到当前四边形对应的三角起始位置,效率更高。

和常规二维数组实现的区别

没有本质区别:之前看到的二维数组代码里的map[y][x],本质就是编译器帮你自动做了y*mapSize + x的计算,换成一维数组只是把这个计算手动写出来而已,逻辑完全对等。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.26 13:06:03