如果理解了HeightMap,对一块地形某一块区域的地势更改将会是一件很容易的事,但由于需要实现跨多块地图,四块地图之间的修改就会比较麻烦。从这一篇开始的几篇文章,会逐步完善一个地形编辑工具类TerrainUtility及其他相关扩展。代码我已经上传到了我的Github上,需要的话可以直接去下载https://github.com/xdedzl/RunTimeTerrainEditor,里面有一个TerrainModilfyDemo的场景,我做了一个简单的UI用来测试,工程版本目前使用的是2019.2,但2018.3之后的版本应该都没问题,但Unity貌似不支持从2019回滚到2018,需要新建工程后将资源复制过去。注意编译环境需要时.net4.x,用3.5会报错。

一、关键步骤

1.获取地图高度数据

public float[,] GetHeights(int xBase, int yBase, int width, int height);

这是TerrainData的获取高度数据的函数,传入起始索引和对应的宽高,返回一个高度数据

2.修改高度数据

根据需要修改返回的二维数组,在这里可以根据自己的算法给地形以不同的形状,后面会讲到通过自定义笔刷绘制任意形状

3.重新设置地形高度

public void SetHeights(int xBase, int yBase, float[,] heights);

这是TerrainData的设置地形高度的函数,传入起始索引和包含高度数据的二维数组设置高度

在这里要注意的一点是设置和获取的高度数据的二维数组第一维对应Terrain的z轴,第二维对应的是x轴,不要想当然弄反了

二、获取相邻的地图块

由于涉及到多地图块之间的编辑,寻找相邻地图块就必不可少了,Terrain本身提供了这种查询,下面是Terrain类里的四个属性

public Terrain leftNeighbor { get; }
public Terrain rightNeighbor { get; }
public Terrain topNeighbor { get; }
public Terrain bottomNeighbor { get; }

注意,这是Unity2018中才有的,在Unity2017中我只看到了SetNieghbors的方法,Unity2018的Terrain系统相比2017强大了很多,可以在编辑器中跨地形编辑,可以直接创建地形的邻居,2018.3为地形系统GPU实例渲染路径,官方宣称能减少50%的CPU消耗。为了方便编辑工具在2017中的使用,我给Terrain类写了个扩展方法,用发射射线的方法获取邻居。

这里我建立一个用于写拓展方法的类ExternFun,后面还会其他的拓展方法会也会写在这个类里,利用预处理机制将Unity2018和其他版本区分,是Unity2018直接返回对应邻居,不是的话则利用射线寻找,并且虽然这里有上下左右四个邻居,但实际上只用到了Top和Right两个。

using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
using UnityEngine.UI;
public static class ExtenFun
{#region Terrain/// <summary>/// 右边的地形块/// </summary>/// <param name="terrain"></param>/// <returns></returns>public static Terrain Right(this Terrain terrain){
#if UNITY_2018return terrain.rightNeighbor;
#elseVector3 rayStart = terrain.GetPosition() + new Vector3(terrain.terrainData.size.x * 1.5f, 1000, terrain.terrainData.size.z * 0.5f);RaycastHit hitInfo;Physics.Raycast(rayStart, Vector3.down, out hitInfo, float.MaxValue, LayerMask.GetMask("Terrain"));return hitInfo.collider?.GetComponent<Terrain>();
#endif}/// <summary>/// 上边的地形块/// </summary>/// <param name="terrain"></param>/// <returns></returns>public static Terrain Up(this Terrain terrain){
#if UNITY_2018return terrain.topNeighbor;
#elseVector3 rayStart = terrain.GetPosition() + new Vector3(terrain.terrainData.size.x * 0.5f, 1000, terrain.terrainData.size.z * 1.5f);RaycastHit hitInfo;Physics.Raycast(rayStart, Vector3.down, out hitInfo, float.MaxValue, LayerMask.GetMask("Terrain"));return hitInfo.collider?.GetComponent<Terrain>();
#endif}/// <summary>/// 左边的地形块/// </summary>/// <param name="terrain"></param>/// <returns></returns>public static Terrain Left(this Terrain terrain){
#if UNITY_2018return terrain.leftNeighbor;
#elseVector3 rayStart = terrain.GetPosition() + new Vector3(-terrain.terrainData.size.x * 0.5f, 1000, terrain.terrainData.size.z * 0.5f);RaycastHit hitInfo;Physics.Raycast(rayStart, Vector3.down, out hitInfo, float.MaxValue, LayerMask.GetMask("Terrain"));return hitInfo.collider?.GetComponent<Terrain>();
#endif}/// <summary>/// 下边的地形块/// </summary>/// <param name="terrain"></param>/// <returns></returns>public static Terrain Down(this Terrain terrain){
#if UNITY_2018return terrain.bottomNeighbor;
#elseVector3 rayStart = terrain.GetPosition() + new Vector3(terrain.terrainData.size.x * 0.5f, 1000, -terrain.terrainData.size.z * 0.5f);RaycastHit hitInfo;Physics.Raycast(rayStart, Vector3.down, out hitInfo, float.MaxValue, LayerMask.GetMask("Terrain"));return hitInfo.collider?.GetComponent<Terrain>();
#endif}#endregion
}

三、跨地图

假设我们有一个由四块Terrain组成的地图,那么跨地图的编辑大致有四种情况

按照上面所说的三个步骤,我们只在第一步和第三步中对跨地图的事情做处理,第二部只负责管理修改区域的形状和高度,任务区分开来就好做了。当然了,除了上面四种情况,还有修改区域在整个地图边缘的情况也需要我们在代码中做处理。这样一来,高度编辑的整个流程就清晰了。由上图可知,在处理跨地形的问题时我们需要将多个地形的HeightMap进行融合,也需要将一个二维数组拆分成多个HeightMap,所以,这里再写几个拆分和融合的拓展函数加入到ExternFun类里面。

    #region Collectionpublic static T[,] Concat0<T>(this T[,] array_0, T[,] array_1){if (array_0.GetLength(0) != array_1.GetLength(0)){Debug.LogError("两个数组第一维不一致");return null;}T[,] ret = new T[array_0.GetLength(0), array_0.GetLength(1) + array_1.GetLength(1)];for (int i = 0; i < array_0.GetLength(0); i++){for (int j = 0; j < array_0.GetLength(1); j++){ret[i, j] = array_0[i, j];}}for (int i = 0; i < array_1.GetLength(0); i++){for (int j = 0; j < array_1.GetLength(1); j++){ret[i, j + array_0.GetLength(1)] = array_1[i, j];}}return ret;}public static T[,] Concat1<T>(this T[,] array_0, T[,] array_1){if (array_0.GetLength(1) != array_1.GetLength(1)){Debug.LogError("两个数组第二维不一致");return null;}T[,] ret = new T[array_0.GetLength(0) + array_1.GetLength(0), array_0.GetLength(1)];for (int i = 0; i < array_0.GetLength(0); i++){for (int j = 0; j < array_0.GetLength(1); j++){ret[i, j] = array_0[i, j];}}for (int i = 0; i < array_1.GetLength(0); i++){for (int j = 0; j < array_1.GetLength(1); j++){ret[i + array_0.GetLength(0), j] = array_1[i, j];}}return ret;}public static T[,] GetPart<T>(this T[,] array, int base_0, int base_1, int length_0, int length_1){if (base_0 + length_0 > array.GetLength(0) || base_1 + length_1 > array.GetLength(1)){Debug.Log(base_0 + length_0 + ":" + array.GetLength(0));Debug.Log(base_1 + length_1 + ":" + array.GetLength(1));Debug.LogError("索引超出范围");return null;}T[,] ret = new T[length_0, length_1];for (int i = 0; i < length_0; i++){for (int j = 0; j < length_1; j++){ret[i, j] = array[i + base_0, j + base_1];}}return ret;}#endregion

四、射线及高斯模糊工具

射线是项目开发中比较常用的功能,这里建立一个工具类Utility用来发射射线,然后再加上一个之前写过的高斯模糊方法,高斯模糊用来对地形做平滑处理,相关类容可参考高斯模糊。高斯模糊的方法我在这里用到了异步。

// ==========================================
// 描述:
// 作者: HAK
// 时间: 2018-10-24 16:26:10
// 版本: V 1.0
// ==========================================
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Threading.Tasks;
using UnityEngine;/// <summary>
/// 使用工具类
/// </summary>
public static class Utility
{/// <summary>/// 发射射线并返回RaycastInfo/// </summary>public static RaycastHit SendRay(int layer = -1){RaycastHit hitInfo;if (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out hitInfo, float.MaxValue, layer)){return hitInfo;}else{return default(RaycastHit);}}public static RaycastHit SendRayDown(Vector3 start,int layer = -1){RaycastHit hitInfo;start.y += 10000;if (Physics.Raycast(start,Vector3.down, out hitInfo, float.MaxValue, layer)){return hitInfo;}else{return default(RaycastHit);}}/// <summary>/// 对二维数组做高斯模糊/// </summary>/// <param name="array">要处理的数组</param>/// <param name="dev"></param>/// <param name="r">高斯核扩展半径</param>/// <param name="isCircle">改变形状是否是圆</param>public async static Task GaussianBlur(float[,] array, float dev, int r = 1,bool isCircle = true){// 构造半径为1的高斯核int length = r * 2 + 1;float[,] gaussianCore = new float[length, length];float k = 1 / (2 * Mathf.PI * dev * dev);for (int i = 0; i < length; i++){for (int j = 0; j < length; j++){float pow = -((j - r) * (j - r) + (i - r) * (i - r)) / (2 * dev * dev);gaussianCore[i, j] = k * Mathf.Pow(2.71828f, pow);}}// 使权值和为1float sum = 0;for (int i = 0; i < length; i++){for (int j = 0; j < length; j++){sum += gaussianCore[i, j];}}for (int i = 0; i < length; i++){for (int j = 0; j < length; j++){gaussianCore[i, j] /= sum;}}// 对二维数组进行高斯模糊处理int circleR = array.GetLength(0) / 2;await Task.Run(async() =>{for (int i = r, length_0 = array.GetLength(0) - r; i < length_0; i++){await Task.Run(() =>{for (int j = r, length_1 = array.GetLength(1) - r; j < length_1; j++){if (isCircle && (i - circleR) * (i - circleR) + (j - circleR) * (j - circleR) > (circleR - r) * (circleR - r))continue;// 用高斯核处理一个值float value = 0;for (int u = 0; u < length; u++){for (int v = 0; v < length; v++){if ((i + u - r) >= array.GetLength(0) || (i + u - r) < 0 || (j + v - r) >= array.GetLength(1) || (j + v - r) < 0)Debug.LogError("滴嘟滴嘟的报错");elsevalue += gaussianCore[u, v] * array[i + u - r, j + v - r];}}array[i, j] = value;}});}});}
}

Utility和ExternFun两个会在后面继续添加一些函数以方便TerrainUtility的调用,现在正式开始TerrainUtility类的开发

五、TerrainUtility之高度编辑

UnityEgine的命名空间下有一个Vector2Int类,可以用来存储高度图索引

直接上代码,主要的两个方法是GetHeightMap和SetHeightMap,在这两步之间更改获取到的HeightMap就可以了,ChangeHeight方法提供的是一个圆形的高度修改,后面会讲到怎么使用自定义笔刷修改地形。

using UnityEngine;
using System.Collections.Generic;
using System;
using System.Reflection;
using System.Linq;
using System.Threading.Tasks;
/**
* Terrain的HeightMap坐标原点在左下角
*   z
*   ↑
*   0 → x
*/
/// <summary>
/// Terrain工具
/// terrainData.GetHeights和SetHeights的参数都是 值域为[0,1]的比例值
/// </summary>
public static class TerrainUtility
{/// <summary>/// 用于修改高度的单位高度/// </summary>private static float deltaHeight;/// <summary>/// 地形大小/// </summary>private static Vector3 terrainSize;/// <summary>/// 高度图分辨率/// </summary>private static int heightMapRes;/// <summary>/// 静态构造函数/// </summary>static TerrainUtility(){deltaHeight = 1 / Terrain.activeTerrain.terrainData.size.y;terrainSize = Terrain.activeTerrain.terrainData.size;heightMapRes = Terrain.activeTerrain.terrainData.heightmapResolution;}#region 高度图相关/// <summary>/// 返回Terrain上某一点的HeightMap索引。/// </summary>/// <param name="terrain">Terrain</param>/// <param name="point">Terrain上的某点</param>/// <returns>该点在HeightMap中的位置索引</returns>private static Vector2Int GetHeightmapIndex(Terrain terrain, Vector3 point){TerrainData tData = terrain.terrainData;float width = tData.size.x;float length = tData.size.z;// 根据相对位置计算索引int x = (int)((point.x - terrain.GetPosition().x) / width * tData.heightmapWidth);int z = (int)((point.z - terrain.GetPosition().z) / length * tData.heightmapHeight);return new Vector2Int(x, z);}/// <summary>/// 返回地图Index对应的世界坐标系位置/// </summary>/// <param name="terrain"></param>/// <param name="x"></param>/// <param name="z"></param>/// <returns></returns>public static Vector3 GetIndexWorldPoint(Terrain terrain, int x, int z){TerrainData data = terrain.terrainData;float _x = data.size.x / (data.heightmapWidth - 1) * x;float _z = data.size.z / (data.heightmapHeight - 1) * z;float _y = GetPointHeight(terrain, new Vector3(_x, 0, _z));return new Vector3(_x, _y, _z) + terrain.GetPosition();}/// <summary>/// 返回GameObject在Terrain上的相对(于Terrain的)位置。/// </summary>/// <param name="terrain">Terrain</param>/// <param name="go">GameObject</param>/// <returns>相对位置</returns>public static Vector3 GetRelativePosition(Terrain terrain, GameObject go){return go.transform.position - terrain.GetPosition();}/// <summary>/// 返回Terrain上指定点在世界坐标系下的高度。/// </summary>/// <param name="terrain">Terrain</param>/// <param name="point">Terrain上的某点</param>/// <param name="vertex">true: 获取最近顶点高度  false: 获取实际高度</param>/// <returns>点在世界坐标系下的高度</returns>public static float GetPointHeight(Terrain terrain, Vector3 point, bool vertex = false){// 对于水平面上的点来说,vertex参数没有影响if (vertex){// GetHeight得到的是离点最近的顶点的高度Vector2Int index = GetHeightmapIndex(terrain, point);return terrain.terrainData.GetHeight(index.x, index.y);}else{// SampleHeight得到的是点在斜面上的实际高度return terrain.SampleHeight(point);}}/// <summary>/// 返回Terrain的HeightMap的一部分/// 场景中有多块地图时不要直接调用terrainData.getheights/// 这个方法会解决跨多块地形的问题/// </summary>/// <param name="terrain">Terrain</param>/// <param name="xBase">检索HeightMap时的X索引起点</param>/// <param name="yBase">检索HeightMap时的Y索引起点</param>/// <param name="width">在X轴上的检索长度</param>/// <param name="height">在Y轴上的检索长度</param>/// <returns></returns>public static float[,] GetHeightMap(Terrain terrain, int xBase = 0, int yBase = 0, int width = 0, int height = 0){// 如果后四个均为默认参数,则直接返回当前地形的整个高度图if (xBase + yBase + width + height == 0){width = terrain.terrainData.heightmapWidth;height = terrain.terrainData.heightmapHeight;return terrain.terrainData.GetHeights(xBase, yBase, width, height);}TerrainData terrainData = terrain.terrainData;int differX = xBase + width - (terrainData.heightmapResolution - 1);   // 右溢出量级int differY = yBase + height - (terrainData.heightmapResolution - 1);  // 上溢出量级float[,] ret;if (differX <= 0 && differY <= 0)  // 无溢出{ret = terrain.terrainData.GetHeights(xBase, yBase, width, height);}else if (differX > 0 && differY <= 0) // 右边溢出{ret = terrain.terrainData.GetHeights(xBase, yBase, width - differX, height);float[,] right = terrain.Right()?.terrainData.GetHeights(0, yBase, differX, height);if (right != null)ret = ret.Concat0(right);}else if (differX <= 0 && differY > 0)  // 上边溢出{ret = terrain.terrainData.GetHeights(xBase, yBase, width, height - differY);float[,] up = terrain.Top()?.terrainData.GetHeights(xBase, 0, width, differY);if (up != null)ret = ret.Concat1(up);}else // 上右均溢出{ret = terrain.terrainData.GetHeights(xBase, yBase, width - differX, height - differY);float[,] right = terrain.Right()?.terrainData.GetHeights(0, yBase, differX, height - differY);float[,] up = terrain.Top()?.terrainData.GetHeights(xBase, 0, width - differX, differY);float[,] upRight = terrain.Right()?.Top()?.terrainData.GetHeights(0, 0, differX, differY);if (right != null)ret = ret.Concat0(right);if (upRight != null)ret = ret.Concat1(up.Concat0(upRight));}return ret;}/// <summary>/// 初始化地形高度图编辑所需要的参数/// 后四个参数需要在调用前定义/// </summary>/// <param name="center">目标中心</param>/// <param name="radius">半径</param>/// <param name="mapIndex">起始修改点在高度图上的索引</param>/// <param name="heightMap">要修改的高度二维数组</param>/// <param name="mapRadius">修改半径对应的索引半径</param>/// <param name="limit">限制高度</param>/// <returns></returns>private static Terrain InitHMArg(Vector3 center, float radius, ref Vector2Int mapIndex, ref float[,] heightMap, ref int mapRadius, ref int mapRadiusZ, ref float limit){Vector3 leftDown = new Vector3(center.x - radius, 0, center.z - radius);// 左下方TerrainTerrain terrain = Utility.SendRayDown(leftDown, LayerMask.GetMask("Terrain")).collider?.GetComponent<Terrain>();// 左下至少有一个方向没有Terrainif (terrain != null){// 获取相关参数mapRadius = (int)(terrain.terrainData.heightmapResolution / terrain.terrainData.size.x * radius);mapRadiusZ = (int)(terrain.terrainData.heightmapResolution / terrain.terrainData.size.z * radius);mapRadius = mapRadius < 1 ? 1 : mapRadius;mapRadiusZ = mapRadiusZ < 1 ? 1 : mapRadiusZ;mapIndex = GetHeightmapIndex(terrain, leftDown);heightMap = GetHeightMap(terrain, mapIndex.x, mapIndex.y, 2 * mapRadius, 2 * mapRadiusZ);//limit = heightMap[mapRadius, mapRadius];}return terrain;}/// <summary>/// 改变地形高度/// </summary>/// <param name="center"></param>/// <param name="radius"></param>/// <param name="opacity"></param>/// <param name="amass"></param>public static void ChangeHeight(Vector3 center, float radius, float opacity, bool isRise = true, bool amass = true){int mapRadius = 0;int mapRadiusZ = 0;Vector2Int mapIndex = default(Vector2Int);float[,] heightMap = null;float limit = 0;Terrain terrain = InitHMArg(center, radius, ref mapIndex, ref heightMap, ref mapRadius, ref mapRadiusZ, ref limit);if (terrain == null) return;if (!isRise) opacity = -opacity;// 修改高度图for (int i = 0, length_0 = heightMap.GetLength(0); i < length_0; i++){for (int j = 0, length_1 = heightMap.GetLength(1); j < length_1; j++){// 限制范围为一个圆float rPow = (i - mapRadiusZ) * (i - mapRadiusZ) + (j - mapRadius) * (j - mapRadius);if (rPow > mapRadius * mapRadiusZ)continue;float differ = 1 - rPow / (mapRadius * mapRadiusZ);if (amass){heightMap[i, j] += differ * deltaHeight * opacity;}else if (isRise){heightMap[i, j] = heightMap[i, j] >= limit ? heightMap[i, j] : heightMap[i, j] + differ * deltaHeight * opacity;}else{heightMap[i, j] = heightMap[i, j] <= limit ? heightMap[i, j] : heightMap[i, j] + differ * deltaHeight * opacity;}}}// 重新设置高度图SetHeightMap(terrain, heightMap, mapIndex.x, mapIndex.y);}/// <summary>/// 平滑地形/// </summary>/// <param name="center"></param>/// <param name="radius"></param>/// <param name="dev"></param>/// <param name="level"></param>public async static void Smooth(Vector3 center, float radius, float dev, int level = 1){center.x -= terrainSize.x / (heightMapRes - 1) * level;center.z -= terrainSize.z / (heightMapRes - 1) * level;radius += terrainSize.x / (heightMapRes - 1) * level;int mapRadius = 0;int mapRadiusZ = 0;Vector2Int mapIndex = default(Vector2Int);float[,] heightMap = null;float limit = 0;Terrain terrain = InitHMArg(center, radius, ref mapIndex, ref heightMap, ref mapRadius, ref mapRadiusZ, ref limit);if (terrain == null) return;await Utility.GaussianBlur(heightMap, dev, level);SetHeightMap(terrain, heightMap, mapIndex.x, mapIndex.y);}/// <summary>/// 设置Terrain的HeightMap/// 有不只一块地形的场景不要直接调用terrainData.SetHeights/// 这个方法会解决跨多块地形的问题/// </summary>/// <param name="terrain">Terrain</param>/// <param name="heights">HeightMap</param>/// <param name="xBase">X起点</param>/// <param name="yBase">Y起点</param>public static void SetHeightMap(Terrain terrain, float[,] heights, int xBase = 0, int yBase = 0){TerrainData terrainData = terrain.terrainData;int length_1 = heights.GetLength(1);int length_0 = heights.GetLength(0);int differX = xBase + length_1 - (terrainData.heightmapResolution - 1);int differY = yBase + length_0 - (terrainData.heightmapResolution - 1);if (differX <= 0 && differY <= 0) // 无溢出{terrain.terrainData.SetHeights(xBase, yBase, heights);}else if (differX > 0 && differY <= 0) // 右溢出{terrain.terrainData.SetHeights(xBase, yBase, heights.GetPart(0, 0, length_0, length_1 - differX + 1));  // 最后的 +1是为了和右边的地图拼接terrain.Right()?.terrainData.SetHeights(0, yBase, heights.GetPart(0, length_1 - differX, length_0, differX));}else if (differX <= 0 && differY > 0) // 上溢出{terrain.terrainData.SetHeights(xBase, yBase, heights.GetPart(0, 0, length_0 - differY + 1, length_1));  // 最后的 +1是为了和上边的地图拼接terrain.Top()?.terrainData.SetHeights(xBase, 0, heights.GetPart(length_0 - differY, 0, differY, length_1));}else  // 右上均溢出{terrain.terrainData.SetHeights(xBase, yBase, heights.GetPart(0, 0, length_0 - differY + 1, length_1 - differX + 1));  // 最后的 +1是为了和上边及右边的地图拼接terrain.Right()?.terrainData.SetHeights(0, yBase, heights.GetPart(0, length_1 - differX, length_0 - differY + 1, differX));terrain.Top()?.terrainData.SetHeights(xBase, 0, heights.GetPart(length_0 - differY, 0, differY, length_1 - differX + 1));terrain.Top()?.Right().terrainData.SetHeights(0, 0, heights.GetPart(length_0 - differY, length_1 - differX, differY, differX));}}#endregion
}

Unity 动态编辑Terrain地形(二)地势相关推荐

  1. Unity动态编辑Terrain地形(四)植被编辑

    **** 完整代码我已经上传到了我的Github上,需要的话可以直接去下载https://github.com/xdedzl/RunTimeTerrainEditor,里面有一个TerrainModi ...

  2. Unity中动态修改Terrain地形

    以下类实现了在Unity中动态的修改Terrain的功能,可以在运行时升高.降低以及平滑地形高度.在Unity的Play Mode修改地形后退出Play Mode仍然会保留修改:当游戏打包成独立的可执 ...

  3. 《UnityAPI.Terrain地形》(Yanlz+Unity+SteamVR+云技术+5G+AI+VR云游戏+Unity+Terrain+AddTreeInstance+立钻哥哥++OK++)

    <UnityAPI.Terrain地形> 版本 作者 参与者 完成日期 备注 UnityAPI_Terrain_V01_1.0 严立钻 2020.09.24 #<UnityAPI.T ...

  4. 【蓝鸥Unity开发基础三】课时3 Terrain地形系统【未完】

    [蓝鸥Unity开发基础三]课时3  Terrain地形系统[未完] 推荐视频讲师博客:http://11165165.blog.51cto.com/ 一.Terrain地形系统 Terrain地形系 ...

  5. Unity面试题加强版之二《unity编辑器基础》

    Unity面试题加强版之二Unity编辑器基础 unity超全面试题,掌握轻轻松松拿Offer,码住学习 40.请描述游戏动画有几种,以及其原理. 主要有关节动画.单一网格模型动画(关键帧动画).骨骼 ...

  6. unity创建草地_Unity3D_(地形)创建基本场景

    第一人称漫游场景 地形漫游系统: (自己绘制的GIF文件超过20MB放不上博客园.截取了几张图片)按键盘上的"上下左右"可以控制第一人称的漫游视角 资源包和项目源文件:传送门 自己 ...

  7. 如何用通过Unity 3D生成真实地形

    如何用通过Unity 3D生成真实地形 发布时间:2018-06-27 版权: 相关教程: 1.建筑物轮廓三维立体图生成 其他三维制作视频教程:Bigemap视频教程-支持200多种格式互转kml\s ...

  8. unity动态修改标准材质自发光(Emission)

    目录 一.目的 1.想知道:unity动态修改标准材质自发光(Emission) 二.参考 1Unity利用材质自发光实现物体闪烁 三.操作:一:完成:变换材质自发光的数值 1.运行效果:材质变换了 ...

  9. Unity学习日志_Unity地形系统简介

    Unity学习日志_Unity地形系统简介 地形创建: Terrain中的组件: Transform Terrain 四大功能: paint Terrain,可以选择下面几种具体的模式: Create ...

最新文章

  1. python PILLOW
  2. js中计时器setTimeout、setInterval、requestAnimationFrame区别
  3. UI组件之AdapterView及其子类(四)Gallery画廊控件使用
  4. 基于visual graph开发实时线损管理系统
  5. 电脑手写输入法_QQ拼音输入法除了能打字,竟然还有 N 多妙用!
  6. python下载的库包放_python下载的库包存放路径
  7. cs231n作业一 knn
  8. php如何给注册页面加验证码,网站注册页面如何添加验证码注册登录
  9. 平衡二叉树的插入与删除
  10. 格力董明珠还想再赌五年 雷军:可以试一下
  11. 开源配置管理中心apollo使用方法
  12. LeetCode #780 - Reaching Points
  13. 自整理---Mysql高级笔记
  14. 用ansible自动化搭建web、sql服务器、lvs调度器
  15. localtime()
  16. 红帽linux安装docker,在CentOS7.6、红帽7.6系统中安装Docker:只需3条命令
  17. QQ那些厚黑又细腻的人性社交设计
  18. Acrelcloud-6000安全用电管理平台是一套电气火灾预警和预防管理系统
  19. 「实验记录」MIT 6.S081 Lab7 multithreading
  20. 微型计算机键盘上的tab键是指标定位键,微型计算机键盘上的Tab键是指标定位键。...

热门文章

  1. 如何锻炼自己的意志和毅力
  2. Ubuntu 升级到 20.04.2 后启动系统电脑黑屏,左上角光标闪烁
  3. 不能忘却的记忆——水源垃圾史
  4. matlab 数值积分 奇点,一类含奇点函数的数值积分方法
  5. GetDlgItem所获取CWnd*的时效性
  6. 比尔总动员日常任务攻略三
  7. 再也不怕用错可视化图表,一文教你合理使用图表组件
  8. Southern Blot amp; Northern Blot
  9. 【平衡小车设计】1.平衡小车设计概述
  10. python变量加点_Python 二次元速成 level 0 变量X语句X计算