将unity地形转换为mesh

之前这个博客:
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 <= 0)
        {
            Debug.Log("Selection.objects.Length <= 0");
            return;
        }

        var terrainObj = Selection.objects[0] as GameObject;
        if (terrainObj == null)
        {
            Debug.Log("terrainObj == null");
            return;
        }

		//2.获取地形选中物体身上的组件Terrain 
        var terrain = terrainObj.GetComponent<Terrain>();
        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进行采样,最后得出的就是250
250个点。
在这里插入图片描述

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

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

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

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

所以要有一个缩放的关系:
所以要求出本来地形的宽度和高度图宽度的几倍,然后再乘以每隔多少采样一个点,这样求出一个缩放系数,最后求出的点再乘以和这个系数,就恢复到了原来地形的尺寸了。
比如上面:地形10241024
高度图是:512
512
所以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 < w; i++)
 {
            for (int j = 0; j < h; j++)
            {
 				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大小
高度图4
4大小
贴图是22大小
那么则要用贴图平铺4
4个贴图。

在这里插入图片描述

所以完整的代码是:

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 < w; i++)
        {
            for (int j = 0; j < h; j++)
            {
                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 < w - 1; i++)
        {
            for (int j = 0; j < h - 1; j++)
            {
                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<MeshRenderer>();
        Material mat = mr.sharedMaterial;
        if (!mat)
            mat = new Material(Shader.Find("Custom/Environment/TerrainSimple"));

        for (int i = 0; i < terrainData.splatPrototypes.Length; i++)
        {
            var sp = terrainData.splatPrototypes[i];
            mat.SetTexture("_Texture" + i, sp.texture);
        }

        t.parent = terrainObj.transform.parent;
        t.position = terrainObj.transform.position;
        t.gameObject.layer = terrainObj.layer;
        t.GetComponent<MeshFilter>().sharedMesh = mesh;
        t.GetComponent<MeshCollider>().sharedMesh = mesh;
        mr.sharedMaterial = mat;

        t.gameObject.SetActive(true);
        terrainObj.SetActive(false);

        Debug.Log("Convert terrain to mesh finished!");

这样我们就根据terrain高度图,将terrain转换成mesh网格了。

下面就是写shader,用于采样贴图了:

Shader "Custom/Environment/TerrainSimple"
{
    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,并制定策略进行加载。

  • 8
    点赞
  • 51
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: UnityTerrain是一个非常有用的工具,可以用于快速创建景观和环境。然而,在某些情况下,将Terrain转换为网格(mesh)可能是更好的选择,因为网格具有更高的控制力和更大的灵活性。 要将UnityTerrain转换为网格,可以使用Unity中的一些脚本或第三方插件。在转换Terrain时,需要考虑一些因素,如地形的分辨率、细节、大小和顶点数量等。这些因素决定了生成的网格的复杂性和质量。 转换Terrain后,可以使用网格的优点来扩展实现场景的能力,如创建更多的自定义对象、添加水、树木和建筑等。此外,如果需要大规模的场景或复杂的元素,网格还可以优化游戏性能。 但是,将Terrain转换为网格也需要考虑一些限制和注意事项。例如,过于复杂的网格可能导致游戏的性能下降,而在使用网格时需要考虑多个碰撞器、UV贴图和纹理等。 总之,将UnityTerrain转换为网格可以为游戏开发者提供更多的自定义方式和更高的灵活性,但需要注意一些限制和注意事项。 ### 回答2: Unity是一款游戏引擎,它被广泛用于游戏开发中。UnityTerrain地形)是一个强大的工具,可以用来构建大量的自然景观。然而,它在物理效果和优化方面有一些限制。所以,有时需要将Terrain转换Mesh(网格)来实现更高精度的效果,同时提高游戏的性能。 Unity Terrain转换Mesh有两种方法。第一种是使用Unity的API,从Terrain中获取数据,并将其转换Mesh。这个过程涉及到一些数学计算和算法优化,需要一定的编程技能。 第二种方法是使用第三方工具。这些工具可以直接将Unity Terrain转换Mesh,省去了一些复杂的步骤。目前有一些比较流行的工具,例如"Terrain to Mesh 2023",它可以快速、高效地将Terrain转换成高质量的Mesh。使用这种工具可以节省开发时间,同时获得更好的游戏效果。 总之,Unity Terrain转换Mesh可以使游戏更加逼真、优化,提高用户的体验,是游戏开发的重要环节之一。 ### 回答3: Unity Terrain to Mesh2023这个问题可能涉及到Unity引擎的一个未来的更新或新功能,但由于无法确定具体的背景和含义,所以我只能就此进行猜测和探讨。 Unity TerrainMesh都是Unity引擎中用于场景渲染的重要组件。Unity Terrain是一种用于快速创建和编辑复杂地形的工具,可以在不需要复杂建模技能的情况下制作出逼真的地形效果。而Mesh则是一种用于渲染三维模型的基本几何形状,它可以由多边形(或三角形)组成,通过调整点的位置、法线和纹理等属性来表现出复杂的形态和细节。 在这个问题中,Unity Terrain to Mesh2023可能意味着一种将Unity Terrain转换Mesh的新功能或技术。这可能会使得开发者能够更加灵活地创建和编辑场景,通过将地形转换Mesh,就可以对它进行更加精细的编辑和优化,从而达到更好的渲染效果。 此外,Unity Terrain to Mesh2023还可能涉及到未来版本的Unity引擎的改进和升级。随着Unity引擎的不断发展和完善,我们可以期待更多的创新和功能!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值