声明:部分原理与代码取自链接:https://blog.csdn.net/xdedzl/article/details/85268674

首先了解一下,Unity的地形系统Terrain的地形相关数据是存放在TerrainData里的

而关于地形的高低起伏数据,是存储在TerrainData中的HeightMap中。

通常在新建地形的时候,我们就已经确定了HeightMap的分辨率

高度图的分辨率即精度,精度越高地形可变粒度就越高,高低起伏就越平滑。

而我们要修改地形,也是通过修改这个HeightMap来实现的。

弹坑说明

根据上图可以看出,想要创造一个弹坑,首先确定的是爆炸中心(centerPosition)和弹坑半径(radius),通过这两个条件,可以确定我们到底要获取高度图的哪一块进行编辑。

TerrainData本身提供了GetHeights方法:

// 摘要://     Get an array of heightmap samples.//// 参数://   xBase://     First x index of heightmap samples to retrieve.////   yBase://     First y index of heightmap samples to retrieve.////   width://     Number of samples to retrieve along the heightmap's x axis.////   height://     Number of samples to retrieve along the heightmap's y axis.public float[,] GetHeights(int xBase, int yBase, int width, int height);

可以了解到参数是需要高度图起点的x,y值和获取区域的长宽

所以爆炸中心的位置(centerPosition)是无法直接做参数使用的,我们需要先算出左下起点(LeftBottomPosition)位置

Vector3 LeftBottomPosition = centerPosition - new Vector3(radius,0, radius);

接着计算出起点(LeftBottomPosition)与地形(item)的相对位置

//起点相对地形坐标位置Vector3 tLocalPos = LeftBottomPosition - item.transform.position;

再使用高度图的精度和地图的长宽进行计算,将起点位置转化为高度图坐标位置(dPos)

TerrainData td = item.terrainData;Vector2 dPos = new Vector2(tLocalPos.x/td.size.x*td.heightmapResolution,tLocalPos.z/td.size.z*td.heightmapResolution);

最后,将弹坑直径转换成高度图长度,所有的参数就得到了,通过GetHeights方法得到需要修改的原高度信息float[,]

得到了原始数据接下来考虑的就是如何修改它,相关的方法有各种各样,我使用的是类似unity地形自带的笔刷效果,通过一张α通道的图片来控制各个位置与初始位置的偏离

因为需要实现的效果如下图

所以做了一张笔刷图如下

图片边缘半透明,对应原地形高度不变

往中间渐变成不透明,对应地形逐渐凸起

继续往中间渐变成完全透明,对应地形完全凹陷

假设图片所有α通道的区间是0~1,而边缘部分α值是0.8,我们直接定义,0.8就是地形原高度,图片所有α通道和0.8做差值计算,得到的差值大于0则凸起,小于0则凹陷,这样就可以只一次修改地形数据,同时体现凸起和凹陷两种效果

实现效果如下

无贴图无光源效果

有贴图有光源效果

多种弹坑效果

注意:         1.在修改地形的一瞬间帧率会大幅下降,所以避免高频率操作。

2.如果在修改高度的同时修改地面贴图,比如焦土, 可使得弹坑更加真实

贴上弹坑生成测试代码,在爆炸中心点上方放置物体并挂载脚本即可测试。

using System.Collections.Generic;
using UnityEngine;public class TCrasher : MonoBehaviour
{/// <summary>/// 单位高度/// </summary>static float deltaHeight = 0;private static Dictionary<string, float[,]> brushDic = new Dictionary<string, float[,]>();void Awake(){InitBrushs();}/// <summary>/// 读取图片α通道值,透明度越高,α越接近0,地形变化越大/// </summary>private static void InitBrushs(){Texture2D[] textures = Resources.LoadAll<Texture2D>("Textures/Brushs");for (int i = 0, length = textures.Length; i < length; i++){Debug.Log(textures[i].name);string[] args = textures[i].name.Split('_');//图片命名为1_0.8,拆分得到0.8(根据图片不同自行调整)float tempHeight = 1;if(args.Length > 1){float.TryParse(args[1],out tempHeight);//取得0.8f作为地平线高度}Color[] colors = textures[i].GetPixels();float[,] alphas = new float[textures[i].height, textures[i].width];for (int j = 0, length0 = textures[i].height, index = 0; j < length0; j++){for (int k = 0, length1 = textures[i].width; k < length1; k++){alphas[j, k] = tempHeight - colors[index].a;//重新定义水平高度0.8,便于一次性实现地形的凸起和凹陷index++;}}brushDic[args[0]] =alphas;}}/// <summary>/// 取得地形t的高度单位/// </summary>/// <param name="t"></param>static void SetDelteH(Terrain t){deltaHeight = 1 / Terrain.activeTerrain.terrainData.size.y;}void Update(){if (Input.GetKeyUp(KeyCode.A)){//for (int i = 0; i < 10; i++){TCrash(transform.position, 5,-2f);}}}/// <summary>/// 爆炸生成弹坑/// </summary>/// <param name="centerPosition">爆炸中心点</param>/// <param name="radius">杀伤半径</param>/// <param name="depth">深度系数</param>static void TCrash(Vector3 centerPosition,float radius = 1f,float depth = -1f){Vector3 LeftBottomPosition = centerPosition - new Vector3(radius,0, radius);float range = radius *2+0.5f;//因高度图的分辨率不同,增加0.5f调整弹坑大小int rangex, rangez;Ray ray= new  Ray(LeftBottomPosition, Vector3.down);RaycastHit[] hits = Physics.RaycastAll(ray, 100);List<Terrain> ts = new List<Terrain>();if (hits.Length > 0){foreach (var item in hits){Terrain temp = item.transform.GetComponent<Terrain>();if (temp != null){ts.Add(temp);}}}foreach (var item in ts){TerrainData td = item.terrainData;//起点地形坐标位置Vector3 tLocalPos = LeftBottomPosition - item.transform.position;//高度图坐标系位置Vector2 dPos = new Vector2(tLocalPos.x/td.size.x*td.heightmapResolution,tLocalPos.z/td.size.z*td.heightmapResolution);SetDelteH(item);rangex = (int)(range * td.heightmapResolution/ td.size.x);rangez = (int)(range * td.heightmapResolution / td.size.z);float addHeight = depth * deltaHeight;float[,] oldData = GetHeightMap(item,(int)dPos.x, (int)dPos.y, rangex, rangez);//await Task.Run(async () =>{float[,] deltaMap = BilinearInterp(brushDic["1"], rangex, rangez);//float[,] deltaMap = await Utility.ZoomBilinearInterpAsync(brushDic[brushIndex], 2 * mapRadius, 2 * mapRadius);for (int i = 0; i < rangex; i++){for (int j = 0; j < rangez; j++){oldData[i, j] += deltaMap[i, j] * addHeight * 1f;}}}//);SetHeightMap(item, (int)dPos.x, (int)dPos.y, oldData);}}/// <summary>/// 获取涉及地形的高度图数据/// </summary>/// <param name="terrain"></param>/// <param name="xBase"></param>/// <param name="yBase"></param>/// <param name="width"></param>/// <param name="height"></param>/// <returns></returns>public static float[,] GetHeightMap(Terrain terrain, int xBase = 0, int yBase = 0, int width = 0, int height = 0){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.rightNeighbor?.terrainData.GetHeights(0, yBase, differX, height);if (right != null)ret = ret.ConcatRight(right);}else if (differX <= 0 && differY > 0)  // 上边溢出{ret = terrain.terrainData.GetHeights(xBase, yBase, width, height - differY);float[,] up = terrain.topNeighbor?.terrainData.GetHeights(xBase, 0, width, differY);if (up != null)ret = ret.ConcatUp(up);}else // 上右均溢出{ret = terrain.terrainData.GetHeights(xBase, yBase, width - differX, height - differY);float[,] right = terrain.rightNeighbor?.terrainData.GetHeights(0, yBase, differX, height - differY);float[,] up = terrain.topNeighbor?.terrainData.GetHeights(xBase, 0, width - differX, differY);float[,] upRight = terrain.rightNeighbor?.topNeighbor?.terrainData.GetHeights(0, 0, differX, differY);if (right != null)ret = ret.ConcatRight(right);if (upRight != null)ret =ret.ConcatUp(up.ConcatRight(upRight));}return ret;}/// <summary>/// 重新设置高度图/// </summary>/// <param name="terrain"></param>/// <param name="xBase"></param>/// <param name="yBase"></param>/// <param name="heights"></param>public static void SetHeightMap(Terrain terrain, int xBase, int yBase, float[,] heights){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) // 无溢出{//ThreadPool.QueueUserWorkItem((a) => { terrainData.SetHeights(xBase, yBase, heights); }//);}else if (differX > 0 && differY <= 0) // 右溢出{terrainData.SetHeights(xBase, yBase, heights.GetPart(0, 0, length_0, length_1 - differX + 1));  // 最后的 +1是为了和右边的地图拼接terrain.rightNeighbor?.terrainData.SetHeights(0, yBase, heights.GetPart(0, length_1 - differX, length_0, differX));}else if (differX <= 0 && differY > 0) // 上溢出{terrainData.SetHeights(xBase, yBase, heights.GetPart(0, 0, length_0 - differY + 1, length_1));  // 最后的 +1是为了和上边的地图拼接terrain.topNeighbor?.terrainData.SetHeights(xBase, 0, heights.GetPart(length_0 - differY, 0, differY, length_1));}else  // 右上均溢出{terrainData.SetHeights(xBase, yBase, heights.GetPart(0, 0, length_0 - differY + 1, length_1 - differX + 1));  // 最后的 +1是为了和上边及右边的地图拼接terrain.rightNeighbor?.terrainData.SetHeights(0, yBase, heights.GetPart(0, length_1 - differX, length_0 - differY + 1, differX));terrain.topNeighbor?.terrainData.SetHeights(xBase, 0, heights.GetPart(length_0 - differY, 0, differY, length_1 - differX + 1));terrain.topNeighbor?.rightNeighbor?.terrainData.SetHeights(0, 0, heights.GetPart(length_0 - differY, length_1 - differX, differY, differX));}}public static float[,] BilinearInterp(float[,] array, int length_0, int length_1){float[,] _out = new float[length_0, length_1];int original_0 = array.GetLength(0);int original_1 = array.GetLength(1);float ReScale_0 = original_0 / ((float)length_0);  // 倍数的倒数float ReScale_1 = original_1 / ((float)length_1);float index_0;float index_1;int inde_0;int inde_1;float s_leftUp;float s_rightUp;float s_rightDown;float s_leftDown;//await Task.Run(async () =>{for (int i = 0; i < length_0; i++){//await Task.Run(() =>{for (int j = 0; j < length_1; j++){index_0 = i * ReScale_0;index_1 = j * ReScale_1;inde_0 = Mathf.FloorToInt(index_0);inde_1 = Mathf.FloorToInt(index_1);s_leftUp = (index_0 - inde_0) * (index_1 - inde_1);s_rightUp = (inde_0 + 1 - index_0) * (index_1 - inde_1);s_rightDown = (inde_0 + 1 - index_0) * (inde_1 + 1 - index_1);s_leftDown = (index_0 - inde_0) * (inde_1 + 1 - index_1);_out[i, j] = array[inde_0, inde_1] * s_rightDown +array[inde_0 + 1, inde_1] * s_leftDown +array[inde_0 + 1, inde_1 + 1] * s_leftUp + array[inde_0, inde_1 + 1] * s_rightUp;}}//);}}//);return _out;}}public static class TerrainExtend
{public static T[,] ConcatRight<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[,] ConcatUp<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;}
}

Unity 基于Terrain系统 真实弹坑的实现相关推荐

  1. Unity 基于eventTriggers的3D场景交互系统

    Unity 基于eventTriggers的3D场景交互系统 Unity里的3D项目有时候需要大量的交互,而且是无UI的交互. 这时候可以像UI系统里,使用eventTrigger去制作交互系统.但是 ...

  2. 【无标Unity基于ShaderLab实现光照系统(着色器代码实现小结)

    文章来源: 学习通http://www.bdgxy.com/ 普学网http://www.boxinghulanban.cn/ 智学网http://www.jaxp.net/ 表格制作excel教程h ...

  3. 基于Linux系统的边界网关协议的设计与实现

    基于Linux系统的边界网关协议的设计与实现 3.6 BGP和RMer系统间通信 RMer系统和BGP系统之间采用的是UNIX本地的服务器客户端模式进行通信,它们创建的socket的地址格式为AF_U ...

  4. Unity简单商城系统,用SQLite数据库保存/加载数据

    Unity简单商城系统案例 流程 最后效果展示 1. 创建项目并导入SQLite需要的dll文件 2. 创建数据库表(玩家表和商店表) 3. Singleton 单例脚本 4. 封装SQLite数据库 ...

  5. 案例学习——Unity基于体绘制的大气散射shader

    本案例学习资料来源 Volumetric Atmospheric Scattering 案例学习--Unity基于体绘制的大气散射shader 0 效果展示 1 引入 1.0 介绍 1.1 单次散射( ...

  6. Unity Mecanim动画系统 之 IK(Inverse Kinematics即反向动力学)的相关说明和简单使用

    Unity Mecanim动画系统 之 IK(Inverse Kinematics即反向动力学)的相关说明和简单使用 目录 Unity Mecanim动画系统 之 IK(Inverse Kinemat ...

  7. 电商客户消费预测模型-基于数千万真实在线零售数据__企业调研_论文科研_毕业设计

    之前发过 <谁主沉浮?银行,消金,互联网公司的精准营销_智慧营销完全解读>介绍了智慧营销/精准营销目的是降低运营成本.但精准营销可以带来很多额外收益,例如提高销售利润,提高客户忠诚度,降低 ...

  8. 使用基于分形系统的离线签名方法

    使用基于分形系统的离线签名方法 主要参考文献: (基于分形理论的离线签名鉴别系统 叶秀芬,裴志,王杰) 完全使用C语言编写,使用opencv库 使用二值化,归一化,进行图像预处理后,使得图像可以比较. ...

  9. android 手写字体识别,一种基于Android系统的手写数学公式识别及生成MathML的方法...

    专利名称:一种基于Android系统的手写数学公式识别及生成MathML的方法 技术领域: 本发明属于模式识别技术领域,涉及数学公式中字符间的空间结构分析,具体涉及一种基于Android系统的手写数学 ...

最新文章

  1. STL中map/vector的删除元素操作
  2. gcc,cc,g++,CC的区别
  3. java技术面试之面试题大全
  4. python中 是什么运算_“是”运算符在Python中做了什么?
  5. 觅知blibli专业版弹幕播放器开源无加密JSON解析版-后台功能一键管理-开源版22-8-24
  6. mysql 函数 人民币大写_sql 数字转人民币大写函数(两种方法)
  7. CAD三维图形转化成二维图形的过程具体的步骤
  8. java比较炫的登录界面_教你写一个炫酷的Material Design 风格的登录和注册页面
  9. 在img的图片上添加文字
  10. 【GEE】批量下载全球降水量GPM数据 (NASA)
  11. ValidFrom验证控件
  12. 亚马逊国际站获取全部商品分类
  13. 用HMM(隐马)图解三国杀的于吉“质疑”
  14. 请求服务无响应Dispatcher has no subscribers
  15. python入门教学视频材料整理-免费
  16. android解析xml-豆瓣电影API的xml解析实例
  17. 某 dewu api 价格接口更新
  18. javascript 日期日历控件
  19. 医院信息系统建设技术参数及资质要求招标公告
  20. excel一列求和_这么多超实用的excel技巧,花费6个小时整理出来的

热门文章

  1. requirejs:杏仁的优化(almond)
  2. coolshell陈皓建议
  3. 关于用户头像上传的配置
  4. C++控制台白底黑字
  5. When Shift Operation Meets Vision Transformer: An Extremely Simple Alternative to Attention Mechanis
  6. 2022年Landsat8/9 Collection2数据 ENVI5.3打开(暴力打开,亲测有效)
  7. 论文致谢应该写多少字
  8. 深圳七夕相亲交友活动 | “码“上七夕--高品质程序员交友派对来啦~免费进场,甜蜜成双
  9. CentOS OpenStack Pike tacker 之 mistral 安装实录
  10. 二维数组a的数组名a,a[0],a,a[0]的联系,和指针的关系