Unity Terrain是什么呢?它在窗口中看起来像一个Mesh,有很多的网格。
但是它并没有Mesh Filter和Mesh Renderer组件,取而代之的是Terrain组件。
Terrain tools
that affect height, such asRaise or Lower Terrain
andSet Height
, use agrayscale texture
called aheightmap
. Unity represents the height of each point on the Terrain as a value in a rectangular array. It represents this array using a grayscale heightmap. Heightmaps are built into the Terrain, and the values stored in a heightmap define the height of each point or vertex on the Terrain.
根据官方文档的以上描述,我们可以知道,Unity内部是用一个二维数组(rectangular array)来存储Terrain每个点的高度的。而每个点的高度又是从一张灰度图(heightmap)里采样的。
这张灰度图可以在Terrain组件的Texture Resolutions选项卡里Import或者Export,选择导出后我得到了个名为“terrain.raw”的文件。使用PhotoShop打开,可以看到该图片的属性如下:宽度和高度都为513像素,和Terrain组件中的“Heightmap Resolution”的值一致。通道数量为2,深度为8位。
对比Untiy窗口的Terrain渲染图,我们可以观察到黑色代表高度为0,白色代表高度为最大值(由Mesh Resolution中的Terrain Height定义),灰色则代表(0~maxHeight)中的一个值。
(1)heightmap的坐标系
以左下角为原点,向右向上为正。如下图,白色正方体所在的位置为世界坐标系的原点。选中的Terrain在世界坐标系中,坐标和原点重合,position=(0,0,0)。可以看到,虽然移动工具在Terrain的中心,但是Terrain的左下角和position重合。
public class TestTerrain : MonoBehaviour
{
Terrain terrain;
// Start is called before the first frame update
void Start()
{
terrain = GetComponent<Terrain>();
int resolution = terrain.terrainData.heightmapResolution;
//GetHeights函数:获取从(xbase,ybase)开始的resolution的点的height。
//即hts存储了heightmap上所有点的高度值。
float[,] hts = terrain.terrainData.GetHeights(0, 0, resolution, resolution);
for(int i = 0; i < resolution / 5; i++)
{
for(int j = 0; j < resolution / 5; j++)
{
hts[i,j] = 0;
}
}
terrain.terrainData.SetHeights(0, 0, hts);
}
}
效果如图:
修改前 | 修改后 |
---|---|
需要注意的是,即使是在运行时修改了Terrain的height,效果也是永久的,因为这里修改的实际上heightmap,图像已经改变。
答案就是使用Raycast
!我们可以从heightmap上的每一个点发射一条射线,如果这条射线和road模型相交了,就可以得到交点处的坐标值。我们就可以将heightmap上的该点的高度设置为交点的y值了。
public class TestTerrain : MonoBehaviour
{
Terrain terrain;
// Start is called before the first frame update
void Start()
{
//【Terrain Data】
terrain = GetComponent<Terrain>();
int resolution = terrain.terrainData.heightmapResolution;
Debug.Log("resolution:" + resolution);
float sizeX = terrain.terrainData.size.x;
float sizeY = terrain.terrainData.size.y;
float sizeZ = terrain.terrainData.size.z;
Debug.Log("size x:" + sizeX+" size y:"+sizeY + " size z:" + sizeZ);
float posX = terrain.transform.position.x;
float posY = terrain.transform.position.y;
float posZ = terrain.transform.position.z;
//【Process】
//获取从(xbase,ybase)开始的resolution的点的height。即hts存储了heightmap上所有点的高度值。
float[,] hts = terrain.terrainData.GetHeights(0, 0, resolution, resolution);
RaycastHit hit;
for (int y = 0; y < resolution; y++)
{
for (int x = 0; x < resolution; x++) {
Vector3 origin = new Vector3(
(float)x / (float)(resolution - 1) * sizeX + posX,
posY+terrain.terrainData.GetHeight(x,y),
(float)y/(float)(resolution-1)*sizeZ+posZ
);
if(Physics.Raycast(new Vector3(origin.x,origin.y-sizeY,origin.z), Vector3.up, out hit, sizeY * 2)){
hts[y, x] = (hit.point.y - posY) / sizeY;
}
}
}
terrain.terrainData.SetHeights(0, 0, hts);
}
效果如图:
研究ing……