将unity地形转换为mesh 您所在的位置:网站首页 unity地形导出插件 将unity地形转换为mesh

将unity地形转换为mesh

2024-07-11 23:06| 来源: 网络整理| 查看: 265

之前这个博客: https://blog.csdn.net/wodownload2/article/details/90263882 主要是关于如何把场景中的物件,进行空间划分,并且根据主角的位置,动态的加载周边的物件,对于大地形的处理,则没有涉及到。

经过搜索,主要收集了如下具有参考价值的网址: 重点参考三个网址: http://www.cnblogs.com/jietian331/p/5831062.html 本文重点讲述 https://blog.csdn.net/zr339361504/article/details/53352800 这个文章涉及法线的计算 https://gitee.com/langresser_king/terrain_proj 码云有代码,分割地形的方法同上面一个网址

https://catlikecoding.com/unity/tutorials/procedural-grid/ 一个很好的关于unity学习的网址,后面会重点研究和这个博客 http://www.52vr.com/article-1173-1.html 无限大地形生成 http://c.biancheng.net/view/2739.html https://www.bilibili.com/video/av4380122?from=search&seid=13314329861953277930 视频教程高度图生成地形 http://darrellbircsak.com/2017/01/27/split-unity-terrain-script/ 分割地形的blog https://kostiantyn-dvornik.blogspot.com/2013/12/unity-split-terrain-script.html http://indago.homenko.pl/wp-content/uploads/2016/08/World-Streamer-Manual.pdf https://unity3d.com/how-to/big-games-on-low-end-mobile https://www.gamasutra.com/blogs/ChristophEnder/20170222/292179/Open_World_on_Mobile_with_Unity.php https://mattgadient.com/2014/09/28/unity3d-a-free-script-to-convert-a-splatmap-to-a-png/ https://github.com/tangrams/unity-terrain-example https://docs.unity3d.com/ScriptReference/TerrainData.GetAlphamaps.html https://www.jianshu.com/p/264e9665f6b1 https://www.bbsmax.com/A/amd0624LJg/ https://connect.unity.com/p/mte-mesh-terrain-editor-mo-xing-di-xing-bian-ji-qi https://www.bilibili.com/video/av10191087/ http://www.manew.com/thread-20801-1-1.html mesh terrain editor插件下载 https://www.bilibili.com/video/av10191087/?p=1 bili教程 http://new-play.tudou.com/v/XMjQ1MTY5MTY4MA==.html 土豆mte教程 http://www.vr2.tv/develop/unity-chajian-kaifa-jieshao.html 十款unity必备插件 https://gameinstitute.qq.com/community/detail/126192 lam地编工具 https://github.com/wachel/TerrainToLodMesh github 将terrain转换mesh https://github.com/jinsek/MightyTerrainMesh 四叉树加载mesh github源码 https://zhuanlan.zhihu.com/p/64809281 上面的知乎博客 https://zhuanlan.zhihu.com/p/53355843 知乎高度图生成terrain https://www.cnblogs.com/AZ-ZK/p/4219981.html 感觉是个大牛 http://gulu-dev.com/ 感觉是个大牛 http://oldking.wang/ 隔壁老王的博客

关于插件: t4m 过时 mte 只有dll terrain to mesh 只有dll

上面的这几个也曾视图去分析是否源码,然后进行分析,定制自己的unity terrain转mesh的需求,但是未果,所以还是重点参考了两篇文章: http://www.cnblogs.com/jietian331/p/5831062.html 本文重点讲述 https://gitee.com/langresser_king/terrain_proj 码云有代码,分割地形的方法同上面一个网址

ok,下面来重点分析下其实现的过程。

1、首先是把unity的地形转为mesh

要明白为啥这么做? 引用https://zhuanlan.zhihu.com/p/64809281的一段文字: Unity的Terrain一直被移动开发团队诟病其可用性,使用原生的Terrain的移动项目大部分都是平地,仅使用了贴图混合的部分。对于有高低起伏的Terrain采用转为mesh的方式使用,早期常用的插件如T4M和Terrain2Mesh,在Unity2018对terrain做了改动以后貌似都不再更新了。T4M更倾向于一个方便Artist修改模型的工具,不过一个模型反反复复地在多个工具中来回实在是很痛苦。Terrain2Mesh使用的人不多,本身比较简单,仅能导出固定规则网格和材质,限制也比较多。

归结为一句话就是性能,unity提供的terrain性能较差。再加上如果是特大地形就必须分开加载,综上,需要将terrain转换为mesh,然后进行分块处理。

下面就是代码的部分,首先是进行terrain转mesh:

[MenuItem("Terrain/Convert terrain to mesh")] static void Init() { //1.所选择的物体是否为空,在Hierarchy视图选中一个物体即可。 if (Selection.objects.Length Debug.Log("terrainObj == null"); return; } //2.获取地形选中物体身上的组件Terrain var terrain = terrainObj.GetComponent(); if (terrain == null) { Debug.Log("terrain == null"); return; } //3.判断是否有地形数据 var terrainData = terrain.terrainData; if (terrainData == null) { Debug.Log("terrainData == null"); return; }

下面看看unity给我们提供的地形的样子: 在这里插入图片描述

int vertexCountScale = 4; int w = terrainData.heightmapWidth; //高度图的宽度 int h = terrainData.heightmapHeight;//高度图的高度 Vector3 size = terrainData.size; //地形的长宽高

这里很多人会对接下来的代码产生怀疑,我也不例外,但是经过逐行的理解,还是参悟了其中的道理。 首先是vertexCountScale的意义,其实就是距离多远采样一个点,也就是采样的精度。 比如地形原先是10001000大小: 那么如果按照距离4进行采样,最后得出的就是250250个点。 在这里插入图片描述

高度图的宽和高,以及地形的长宽高,在配置中可以看出来: 在这里插入图片描述

这里提下Heightmap Resolution为啥是513: 在这里插入图片描述

float[,,] alphaMapData = terrainData.GetAlphamaps(0, 0, terrainData.alphamapWidth, terrainData.alphamapHeight);

这个函数的意思是,返回从(0,0)开始,到(terrainData.alphamapWidth, terrainData.alphamapHeight)宽度和高度的三维数组。 float[,]第一维和第二维构成了对应(x,y)坐标;第三维是对应SplatAlpha贴图的哪个通道,而float[,]的值,是对应通道的值。 比如我们自己创建的地形使用到两个贴图,那么只有一个splatalpha,unity使用RGBA四个通道值,对应四个贴图的混合度。如下图所示你可能看得更明白一点: 在这里插入图片描述

这里使用两个贴图,所以根绝RGBA四个通道,只使用了RG通道。

在这里插入图片描述

第一个贴图对应的全是红色;第二个贴图对应的全是绿色。 如果有人问,如果是大于4个贴图怎么办,是的,会形成多个splatalpha贴图,这里只考虑最多四个贴图的情况。

我们可以使用如下的代码测试:

float aaaa = alphaMapData[0, 0, 0]; //第一个贴图的alpha值 float bbbb = alphaMapData[0, 0, 1]; //第二个贴图的alpha值 Vector3 meshScale = new Vector3(size.x / (w - 1f) * vertexCountScale, 1, size.z / (h - 1f) * vertexCountScale);

有人可能不太明白这个代码的意思,下面我就来具体的阐述一下: 如果要看懂的话,还需仔细阅下面的代码。

首先要确定点的x和y,然后确定z。那么到底要生成多少个点呢? 我们是根据高度图来生成mesh网格的。高度图的意思是对应(x,y)处的高度是多少。

有人就问了,难道地形是10001000,高度图不是10001000吗?可以是也可以不是,他们没有必然相等的要求,高度图可以低分辨率,比如上面的513,其实是512分辨率。

我的理解是地形是10001000,但是并不是所有的点都能对应一个高度图上的一个点,可以是多个点对应高度图上的同一个点。 下面我们就要理解下地形的长宽的意义了: 在这里插入图片描述 我们把地形设置为111的大小,那么它和111的Quad是什么大小关系: 在这里插入图片描述 可以看到其实11*1的地形就是一个Quad大小。

下面我们就来确定下,到底要生成多少个点,以谁为标准生成点。 由于我们要确定点的高度,也就是y轴坐标,所以我们必须以高度图为标准生成点。那么我们就要在高度图上每4个点采样一个点,然后得到对应的height,组成(x,y,z)一个点。

那么有人会问了,如果地形是10241024,而高度图是512512,那么以高度图为标准,也就是生成了512/4=128 128128个点,也就是128128的地形,那么这个缩小了呀,本来是1024*1024大小的地形,这样就缩小了。

所以要有一个缩放的关系: 所以要求出本来地形的宽度和高度图宽度的几倍,然后再乘以每隔多少采样一个点,这样求出一个缩放系数,最后求出的点再乘以和这个系数,就恢复到了原来地形的尺寸了。 比如上面:地形10241024 高度图是:512512 所以1024/512=2 再者,每个4个单位取高度图,所以2*4=8 这样求出坐标之后,再乘以8即可。

代码如下:

int vertexCountScale = 4; int w = terrainData.heightmapWidth; //高度图的宽度 int h = terrainData.heightmapHeight;//高度图的高度 Vector3 size = terrainData.size; //地形的长宽高 Vector3 meshScale = new Vector3(size.x / (w - 1f) * vertexCountScale, 1, size.z / (h - 1f) * vertexCountScale); w = (w - 1) / vertexCountScale + 1; h = (h - 1) / vertexCountScale + 1; Vector3[] vertices = new Vector3[w * h]; //最后生成w*h这么多个点 for (int i = 0; i int index = j * w + i; float z = terrainData.GetHeight(i * vertexCountScale, j * vertexCountScale); //采样高度图 vertices[index] = Vector3.Scale(new Vector3(i, z, j), meshScale); //缩放会原来的地形尺寸 } }

同理我们uv怎么计算呢? 如果要搞清楚uv是怎么计算的,就需要明白贴图的size以及offset是什么意思? 在这里插入图片描述 可以看到这里的size,其实并不是我们通常意义的tile,它的意思就是size,就是定义了这个贴图的尺寸。那么地图被平铺多少个呢?使用地形的size.x/这里贴图的size的x即可。 而且每个贴图的wrapmode都是设置为repeat的方式: 在这里插入图片描述

Vector2 uvScale = new Vector2(1f / (w - 1f), 1f / (h - 1f)) * vertexCountScale * (size.x /terrainData.splatPrototypes[0].tileSize.x);

new Vector2(1f / (w - 1f), 1f / (h - 1f))我们可以理解其实就是高度图的1/(w-1)以及1/(h-1)。 乘以vertexCountScale则是间隔多少的倍数。 而size.x/terrainData.splatPrototypes[0].tileSize.x,则是上面设置贴图里的size,也就是用地形的x除以这里的贴图的x,得到了水平平铺的倍数。

举例: 地形是88大小 高度图44大小 贴图是22大小 那么则要用贴图平铺44个贴图。

在这里插入图片描述

所以完整的代码是:

Vector3 meshScale = new Vector3(size.x / (w - 1f) * vertexCountScale, 1, size.z / (h - 1f) * vertexCountScale); Vector2 uvScale = new Vector2(1f / (w - 1f), 1f / (h - 1f)) * vertexCountScale * (size.x / terrainData.splatPrototypes[0].tileSize.x); // [dev] 此处有问题,若每个图片大小不一,则出问题。日后改善 w = (w - 1) / vertexCountScale + 1; h = (h - 1) / vertexCountScale + 1; Vector3[] vertices = new Vector3[w * h]; Vector2[] uvs = new Vector2[w * h]; Vector4[] alphasWeight = new Vector4[w * h]; for (int i = 0; i int index = j * w + i; float z = terrainData.GetHeight(i * vertexCountScale, j * vertexCountScale); Vector3 dddd = Vector3.Scale(new Vector3(i, z, j), meshScale); vertices[index] = Vector3.Scale(new Vector3(i, z, j), meshScale); uvs[index] = Vector2.Scale(new Vector2(i, j), uvScale); // alpha map int i2 = (int)(i * terrainData.alphamapWidth / (w - 1f)); int j2 = (int)(j * terrainData.alphamapHeight / (h - 1f)); i2 = Mathf.Min(terrainData.alphamapWidth - 1, i2); j2 = Mathf.Min(terrainData.alphamapHeight - 1, j2); var alpha0 = alphaMapData[j2, i2, 0]; var alpha1 = alphaMapData[j2, i2, 1]; //var alpha2 = alphaMapData[j2, i2, 2]; //var alpha3 = alphaMapData[j2, i2, 3]; alphasWeight[index] = new Vector4(alpha0, alpha1, 0, 0); } }

这里没有考虑到,每个贴图的size如果不一样的情况,所以为了简化纹理,可以将四个贴图的size设置为一样。

下面就是组织三角形网格了:

/* * 三角形 * b c * ******* * * * * * * * * * ******* * a d */ int[] triangles = new int[(w - 1) * (h - 1) * 6]; int triangleIndex = 0; for (int i = 0; i int a = j * w + i; int b = (j + 1) * w + i; int c = (j + 1) * w + i + 1; int d = j * w + i + 1; triangles[triangleIndex++] = a; triangles[triangleIndex++] = b; triangles[triangleIndex++] = c; triangles[triangleIndex++] = a; triangles[triangleIndex++] = c; triangles[triangleIndex++] = d; } }

不解释了,很容易。

下面是用网格显示出来地形,唯一要注意的是,这里将四个贴图的alpha权重赋值给了mesh的tangent,实际上不是切线,只是作为后面的shader的中采样计算贴图混合方式的中间存储器。

同样我们还注意到,这里只有顶点信息、uv信息、三角形网格、alpha权重、但是没有法线信息,这个在这个博客中讲到: https://blog.csdn.net/zr339361504/article/details/53352800 生成地形网格,我们需要保存顶点数据、瓦片贴图权重、瓦片uv坐标、光照贴图uv坐标、三角面、以及。由于与地形的光照部分直接使用烘培贴图,所以法线可以不用记录。

我们也可以计算出每个顶点的法线:

在这里插入图片描述 这个地方存疑点?到底是怎么计算法线,还需要研究下。

Mesh mesh = new Mesh(); mesh.vertices = vertices; mesh.uv = uvs; mesh.triangles = triangles; mesh.tangents = alphasWeight; // 将地形纹理的比重写入到切线中 string transName = "[dev]MeshFromTerrainData"; var t = terrainObj.transform.parent.Find(transName); if (t == null) { GameObject go = new GameObject(transName, typeof(MeshFilter), typeof(MeshRenderer), typeof(MeshCollider)); t = go.transform; } // 地形渲染 MeshRenderer mr = t.GetComponent(); Material mat = mr.sharedMaterial; if (!mat) mat = new Material(Shader.Find("Custom/Environment/TerrainSimple")); for (int i = 0; i Properties { _Texture0 ("Texture 1", 2D) = "white" {} _Texture1 ("Texture 2", 2D) = "white" {} _Texture2 ("Texture 3", 2D) = "white" {} _Texture3 ("Texture 4", 2D) = "white" {} } SubShader { Tags { "RenderType" = "Opaque" } LOD 200 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag sampler2D _Texture0; sampler2D _Texture1; sampler2D _Texture2; sampler2D _Texture3; struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; float4 tangent : TANGENT; }; struct v2f { float4 pos : SV_POSITION; float2 uv : TEXCOORD0; float4 weight : TEXCOORD1; }; v2f vert(appdata v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); o.weight = v.tangent; o.uv = v.uv; return o; } fixed4 frag(v2f i) : SV_TARGET { fixed4 t0 = tex2D(_Texture0, i.uv); fixed4 t1 = tex2D(_Texture1, i.uv); fixed4 t2 = tex2D(_Texture2, i.uv); fixed4 t3 = tex2D(_Texture3, i.uv); fixed4 tex = t0 * i.weight.x + t1 * i.weight.y + t2 * i.weight.z + t3 * i.weight.w; return tex; } ENDCG } } Fallback "Diffuse" } Custom/Environment/TerrainSimple

这里是不支持光照计算的,也不支持光照贴图,所以完整的还需要参考: http://www.cnblogs.com/jietian331/p/5831062.html https://blog.csdn.net/zr339361504/article/details/53352800 但是http://www.cnblogs.com/jietian331/p/5831062.html 没有讲到法线的计算方式。 而https://blog.csdn.net/zr339361504/article/details/53352800讲到法线的计算方式,以及光照贴图的uv2计算。 两者的正确与否还是值得商榷的,但是这两篇也是唯一能够讲述怎么转terrain为mesh的博客了,如果有人知道其他的资料,麻烦留言给我,谢谢。

以上就是关于如果将unity自带的terrain转换为mesh的全部介绍了,下面我们将学习如果将mesh进行分割为小的mesh,并制定策略进行加载。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有