Loading... 第六节 地形Mesh网格生成器实现 ---  上一篇:<div class="preview"> <div class="post-inser post box-shadow-wrap-normal"> <a href="https://omo.moe/archives/389/" target="_blank" class="post_inser_a no-external-link"> <div class="inner-image bg" style="background-image: url(https://omo.moe/usr/uploads/2018/12/647748680.jpg);background-size: cover;"></div> <div class="inner-content" > <p class="inser-title">【Unity 3D进阶教程分享】EndlessTerrain无限地形搭建 第五节:对生成的灰度图进行上色填充</p> <div class="inster-summary text-muted"> 第五节 对生成的灰度图进行上色填充上一篇:我们根据不同灰度进行上色填充,为后面的三维地形构建打好基础进入mapGe... </div> </div> </a> <!-- .inner-content #####--> </div> <!-- .post-inser ####--> </div> 上一节,我们已经生成了一张图片显示彩色分区域图和黑白高度图,这一节,我们将生成一个plane,来构造地形的mesh网格基础,通过对不同区域mesh网格顶点坐标设置不同的height值,来形成真实地形基本构造 我们在第一节的时候提到过x,y轴尺寸和顶点数,三角面以及矩形方块计算的数学式子关系,这里再温习一下: 再次查阅我们的【总顶点数构图】  从左下角原点开始,顺时针方向,第一个三角形是 (0,5,1),第二个为(1,5,6),接下来是(1,6,2),(2,6,7)依次类推 顶点数 `vertices=width*height`,而三角形顶点总数则为 `(width-1)*(height-1)*2*3`//方块数=》三角形个数=》顶点总数。 这也是记录我们三角形顶点总数数组所需长度Length的式子。 温习了这些数学关系之后,我们新建MeshGenerator脚本 这个也不需要其他脚本进行修改,改成static类,不需要在unity世界直接展示,去掉:MonoBehaviour继承 ``````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````` public static void GenerateTerrainMesh(float[,] heightMap){ int width =heightMap.GetLength (0);////获取数组第一维元素长度值,赋值给width int height = heightMap.GetLength(1);////获取数组第二维元素长度值,赋值给height for (int y=0; y<height; y++){ for (int x =0; x<width; x++){ } } } ``````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````` 为了方便记录我们的网格数据,在当前类最外面,后面新建一个网格数据类,方便后面的数据保存调用: ``` public class MeshData { public Vector3[] vertices; public int[] triangles; int triangleIndex;//构建AddTriangle方法时,在此申明一个int变量作为三角面索引值 public MeshData (int meshWidth, int meshHeight) { //网格数据存储顶点和三角面信息 vertices = new Vector3[meshWidth * meshHeight]; triangles = new int[(meshWidth - 1) * (meshHeight - 1) * 6]; } public void AddTriangle(int a, int b, int c) { //新建一个添加三角面的方法,a,b,c代替,简化后面的表达式。 triangles[triangleIndex] = a; triangles[triangleIndex + 1] = b; triangles[triangleIndex + 2] = c; triangleIndex += 3; } } ``` 完成了MeshData类的构建,我们可以继续完善GenerateTerrainMesh方法了: ``` public static void GenerateTerrainMesh(float[,] heightMap){ int width =heightMap.GetLength (0); int height = heightMap.GetLength(1); //在这继续输入: MeshData meshData =new MeshData (width,height); int vertexIndex=0; //再将遍历循环内加入 meshData.vertices[vertextIndex]=new Vector3(x,heightMap[x,y],y);//将高度图信息存储在(x,z)平面上 vertexIndex ++; ``` 接下来,我们如果想让mesh网格顶点居中显示,如下图效果的话  需要对居中计算公式做一下推导: 假设有一排三个像素点,我们想要中间的点居中显示,则中间点的x轴坐标值设为0,那么左边的点x轴的值就是-1,右边则为1.那么计算最左边的像素点的坐标值公式可以是:首先整个x轴总像素点数width-1得到对应轴的长度,然后对半分/2,就获得左右两半对应的长度值,对应就是最右侧的点的坐标值:`x=(width-1)/2`, 居中计算公式验证: 我们可以验证一下,总共9个点的话,9-1拿掉最中间的5号,再除以2得到左右两侧平分为4,最右侧坐标4,这是符合我们预期的。 当然,如果总像素点是偶数的时候,比如8,计算得到最右侧坐标值3.5,也是ok的。 我们在 ```````````````````````````````````````````````````````````` public static void GenerateTerrainMesh(float[,] heightMap){ ```````````````````````````````````````````````````````````` 方法中继续完善我们的mesh网格居中计算,需要用到两个新变量定义最右侧的x坐标值以及最上端Z轴坐标值 ``````````````````````````````````````````````````` float maxX=(width-1)/2f; float maxZ=(height-1)/2f; ``````````````````````````````````````````````````` 接下来修正我们的meshData,vertices[vertexIndex]存储位置,使其居中: ````````````````````````````````````````````````````````````````` meshData.vertices[vertextIndex]=new Vector3(x,heightMap[x,y],y); ````````````````````````````````````````````````````````````````` 改为: `````````````````````````````````````````````````````````````````````````` meshData.vertices[vertexIndex]=new Vector3(x-maxX,heightMap[x,y],y-maxZ); `````````````````````````````````````````````````````````````````````````` 接下来我们查看演示:  用0号点管理第一个方块中2个三角形,1号管理对应两个,最右侧坐标点留空不计算,最后y=height时上方边界也留空不进行计算。 我们再思考下,顶点index索引我们是设为i开始遍历,  我们将index和之前的三角形构建公式结合起来,我们比方说第一排中间任意一个顶点索引值i,右侧类推就是i+1,i+2,那么一排遍历完毕,往上第二排同列位置就应该是i+width,往右侧类推就是i+width+1,i+width+2; 那么我们第一个三角形记录下来就是(i,i+width,i+1),第二个就是(i+1,i+width,i+width+1),以此类推,等于是再次温习下之前提到的三角形数组记录:(i+1,i+width+1,i+2),(i+2,i+width+1,i+width+2) 分析完毕,我们在vertexIndex++;之前,构建我们的三角形网格: `````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````` if (x < width -1&& y < height -1) {//最右侧像素点和上方边界不进行绘制 meshData.AddTriangle(vertexIndex, vertexIndex + width, vertexIndex + 1); meshData.AddTriangle(vertexIndex + 1, vertexIndex + width, vertexIndex + width + 1); meshData.AddTriangle(vertexIndex, vertexIndex + width, vertexIndex + 1); } vertexIndex++; `````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````` 这样,我们网格绘制基础方块就完成了 接下来我们思考下UV地图绘制,有了UV贴图,就等于告诉unity我们的二维图片该以何种顺序和方向布局到我们的三维物体表面,这样我们就可以将材质贴到我们的mesh网格上了,这里用到的是plane,所以uv构建是比较简单的,以后接触三维模型制作的时候,uv就需要专门的软件进行自动或者手动引导设置一块块uv布局了,unity就难以胜任复杂模型快速方便地标记uv贴图了。 好了,接下来,我们去MeshData类里面新申明一个uv 变量: `````````````````````` public Vector2[] uvs; `````````````````````` MeshData构造里面新增: ```````````````````````````````````````` uvs =new Vector2[meshWidth*meshHeight]; ```````````````````````````````````````` 然后回到GenerateTerrainMesh方法里面meshData.vertices顶点绘制语句后,补充uv绘制: ``````````````````````````````````````````````````````````````````````` meshData.uvs[vertexIndex]=new Vector2(x/(float)width,y/(float)height); ``````````````````````````````````````````````````````````````````````` 平面的三维模型uv贴图绘制还是非常简单的,这样就ok了 接下来,我们构建一个mesh绘制方法,对我们刚才已经完毕的meshData进行加工,绘制出Mesh网格 在MeshData类里面新增一个公开方法: `````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````` public Mesh CreateMesh(){ Mesh mesh = new Mesh(); mesh.vertices=vertices; mesh.triangles=triangles; mesh.uv=uvs; mesh.RecalculateNormals();//重新计算法线,使得所有三角面方向正常显示,从而顺利计算光线反射。 return mesh;//最后传出绘制好顶点,三角面,uv贴图和正确法线分部的mesh信息。 `````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````` 然后回到我们的静态方法GenerateTerrainMesh,我们现在已经构建完毕MeshData类,获得MeshData数据了,那么此方法就可以传出MeshData数据了,将无传出的void改为MeshData,并在此方法最后补上 ````````````````` return meshData; ````````````````` 有人会问为什么我们不直接传递出来mesh信息,而是传递整个meshData类干嘛 这是因为后面我们创建多个mesh地图块调用多线程计算的时候,不能直接在每个线程里面直接创建或者调用mesh信息,只能将整个类数据传回unity主线程,然后在主线程里进行unity组件类型的调用,所以传出整个meshData类,方便后期主线程调用获取内部的mesh信息。 目前MeshGenerator里面基本就是这样了,我们回到MapGenerator脚本里面新增一个DrawMode:Mesh ``````````````````````````````````````````````````````````````````````````````` public enum DrawMode{NoiseMap,ColourMap,Mesh};//新增Draw Mesh枚举内容; ``````````````````````````````````````````````````````````````````````````````` 接下来在GenerateMap()方法中新增MapDisplay渲染,如果drawMode不是NoiseMap,ColourMap的话,加入判断: ``````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````` else if (drawMode==DrawMode.Mesh){ display.DrawMesh(MeshGenerator.GenerateTerrainMesh(noiseMap),TextureGenerator.TextureFromColourMap(colourMap,mapWidth,mapHeight));//DrawMesh方法我们还没有构建,先写在这里好了。 ``````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````` 我们去MapDisplay脚本,把DrawMesh方法构建出来 申明两个变量: ```````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````` public MeshFilter meshFilter;//引导mesh信息传递 public MeshRenderer meshRenderer;//mesh信息导入后进行渲染 public void DrawMesh(MeshData meshData , Texture2D texture){ //开始构建绘制mesh方法 meshFilter.sharedMesh=meshData.CreateMesh(); meshRenderer.sharedMaterial.mainTexture=texture;//由于我们可能会在脚本之外会调用修改网格贴图材质,所以这里都采用共用网格和材质类型 ```````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````` 回到unity,我们新建一个空物体,命名为Mesh,物体新增两个组件:meshFilter和meshRenderer Assets里面,Materials文件夹下新建一个材质球,命名Mash Mat; 材质球smooth度调成0,让边缘清晰,点Mesh物体,将Mesh Renderer组件下的材质栏打开,拖放我们新建的材质球到这里,然后点击Hierarchy面板的Map Generator物体,挂载的Map Display脚本,我们新建的Mesh Filter和Mesh Renderer字段,都挂载上我们的Mesh物体,这样我们就可以在Inspector面板选择脚本里面的DrawMode模式为Mesh绘制,应该Scene场景就可以看见我们绘制出来的网格了 之后我们对这个mesh网格进行高度拉升就可以实现基本的三维地形绘制了,现在我们先把Mesh的Scale改成10,10,10好匹配我们的地图 新加一个光源,让效果更舒适,这里目前有一个小问题,我们绘制出来的mesh网格贴图和单独的彩色材质图是180度倒置的,造成此问题的原因目前我的能力无法找到,我们可以通过将MeshGenerator脚本里面 `````````````````````````````````````````````````````````````````````````` meshData.vertices[vertexIndex]=new Vector3(x-maxX,heightMap[x,y],y-maxZ); `````````````````````````````````````````````````````````````````````````` 顶点坐标进行x,y轴翻转,可以得到正确的结果: `````````````````````````````````````````````````````````````````````````` meshData.vertices[vertexIndex]=new Vector3(maxX-x,heightMap[x,y],maxZ-y); `````````````````````````````````````````````````````````````````````````` 希望有大佬指点下原因,非常感谢(☆ω☆)。 好了,下一节我们再讲解如何拉升mesh网格高度,目前来说,我们基本的mesh绘制已经完成,当然对于无限生成的大地形目标来说,目前这个受到unity自身65000个顶点单地形生成限制,如果我们在Inspector面板Map Generator脚本上修改Map Width和Map Height 超过256,则会出现一些鬼畜的问题 这在后面的分享中我们会通过分割mesh地图块来实现无限地图生成。这节到此结束。 下一篇:<div class="preview"> <div class="post-inser post box-shadow-wrap-normal"> <a href="https://omo.moe/archives/400/" target="_blank" class="post_inser_a no-external-link"> <div class="inner-image bg" style="background-image: url(https://omo.moe/usr/uploads/2018/12/877050884.jpg);background-size: cover;"></div> <div class="inner-content" > <p class="inser-title">【Unity 3D进阶教程分享】EndlessTerrain无限地形搭建 第七节:LOD多细节层次渲染功能实现</p> <div class="inster-summary text-muted"> 第七节 LOD多细节层次渲染功能实现上一篇:我们继续上节内容,如何将平面plane mesh网格拉高形成立体网格,... </div> </div> </a> <!-- .inner-content #####--> </div> <!-- .post-inser ####--> </div> [1]: https://omo.moe/usr/uploads/2018/12/2556684610.jpg [2]: https://omo.moe/usr/uploads/2018/12/942430886.png [3]: https://omo.moe/usr/uploads/2018/12/4059390309.png [4]: https://omo.moe/usr/uploads/2018/12/1106315071.png [5]: https://omo.moe/usr/uploads/2018/12/1810005139.png Last modification:August 12th, 2020 at 06:13 pm © 允许规范转载 Support If you think my article is useful to you, please feel free to appreciate ×Close Appreciate the author Sweeping payments Pay by AliPay Pay by WeChat