[Unity 学习] - 进阶篇 - Mesh基础系列1:生成网格

Posted 星恋晨

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[Unity 学习] - 进阶篇 - Mesh基础系列1:生成网格相关的知识,希望对你有一定的参考价值。

[Unity 学习] - 进阶篇 - Mesh基础系列1:生成网格

本文并非原创,只是本人的学习记录,原文是由放牛的星星老师翻译Catlike系列教程
链接: https://mp.weixin.qq.com/s/jIKG2rpNkgVQx2BIWjmYvg

文章目录

1 渲染物体

Unity是基于mesh去做渲染的,也就是说你想在Unity里看见东西的话,就必须要使用mesh。
Mesh是什么呢?从概念上讲,mesh是图形硬件用来绘制复杂事物的的框架。它至少包含一个顶点集合(这些顶点是三维空间中的一些坐标,)以及连接这些点的一组三角形(最基本的2D形状)。这些三角形集合在一起就构成任何mesh所代表的表面形状。


当我们需要展示一个可见的物体时,物体上面一定需要两个components(mesh filter和mesh renderer)

2 创建顶点网格

创建一个C#脚本Grid

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Grid : MonoBehaviour

    public int xSize, ySize;

我们现在是做一个mesh,所以需要mesh filter和mesh rendere组件,这里我们可以使用RequireComponent属性,以便Unity自动为我们添加

// RequireCommponent在我们添加这个组件时,会将属性里的其他组件一起添加
[RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))]
public class Grid : MonoBehaviour

创建空的gameobject,添加gird组件,自欧东添加其他两个组件,将gird大小设置为10和5

我们需要一个三维的矢量阵列来存储点,顶点数量取决于gird的大小,由于相邻的四边形共享相同的顶点,所以一个2X4的矩阵,定义3X5顶点即可。

   private Vector3[] vertices;
   ...
   private void Generate()
    
       vertices = new Vector3[(xSize + 1) * (ySize + 1)];
  

我们可以通过定义OnDrawGizeom方法生成一些在sencen场景中显示但是在Game场景中不显示的小球。(这里星星老师少翻译了一部分,这里我们进行一个补充)

什么是Gizmos?

Gizmos是可以在编辑器中使用的视觉提示。默认情况下,它们在场景视图中可见,而在游戏视图中不可见,但您可以通过它们的工具栏进行调整。Gizmos工具类允许您绘制图标、线条和其他一些东西。
Gizmo可以在OnDrawGizmo方法中绘制,该方法由Unity编辑器自动调用。另一种方法是OnDrawGizmosSelected,它仅对选定对象调用。
(百度翻译得已经很清楚了,所以我就直接粘贴了)
这里是代码部分

private void OnDrawGizmos () 

		if (vertices == null)
		 
			return;
		
		Gizmos.color = Color.black;
		for (int i = 0; i < vertices.Length; i++)
		 
			Gizmos.DrawSphere(vertices[i], 0.1f);
		

放置gizmos的数组可以在Generate中进行定义

	private void Generate () 
	
		vertices = new Vector3[(xSize + 1) * (ySize + 1)];
		for (int i = 0, y = 0; y <= ySize; y++) 
		
			for (int x = 0; x <= xSize; x++, i++)
			 
				vertices[i] = new Vector3(x, y);
			
		
	

但是我们不明白这样的点放置的位置是否有问题,所以我们可以使用协程的方式将他们放置依次输出出来

private void Awake () 

		StartCoroutine(Generate());


private IEnumerator Generate () 

	WaitForSeconds wait = new WaitForSeconds(0.05f);
	vertices = new Vector3[(xSize + 1) * (ySize + 1)];
	for (int i = 0, y = 0; y <= ySize; y++) 
	
		for (int x = 0; x <= xSize; x++, i++) 
		
			vertices[i] = new Vector3(x, y);
			yield return wait;
		
	

3 创建Mesh

我们已经制动顶点的位置以及顺序是正确的,我们可以处理实际的mesh。除了在我们自己的组件中保存对mesh的引用外,还比较将他分改mesh Filter。当我们处理好顶点后,将其交给给网格处理

	private Mesh mesh;
	private IEnumerator Generate () 
	
		
		WaitForSeconds wait = new WaitForSeconds(0.05f);
		GetComponent<MeshFilter>().mesh = mesh = new Mesh();
		mesh.name = "Procedural Grid"; // 初始化mesh
		vertices = new Vector3[(xSize + 1) * (ySize + 1)];
		…
		mesh.vertices = vertices; // 交给网格
	

生成一个三角形面片,triangls是三角形三个顶点分别是那几个点,如果是0,1,2那么就是一条线,vertices记录了所有的顶点,只需要根据index就可以自动找到顶点

		private IEnumerator Generate ()
		
		…
		int[] triangles = new int[3];
		triangles[0] = 0;
		triangles[1] = 1;
		triangles[2] = xSize + 1;
		mesh.triangles = triangles;
		

由于三角形面片有正反面,三个点顺时针排列时,才是正面,所以当我们面对z轴的反面时是看不到三角形的,我们可以将,第二点和第三个点反过来就可以看到了triangles[1] = xSize + 1;triangles[2] = 1;

接下来我们将第二个三角形面片画出
triangles[3] = 1;
triangles[4] = xSize + 1;
triangles[5] = xSize + 2;
这里有个问题,我们不能用这样一个一个点的方式将三角形画出,所以用循环吧

 // 三角形的那一边可见是由顶点顺序的时钟方向决定的
        // 由于这些三角形共享两个顶点,所以我们可以将其简化为四行代码,只显式地提到每个顶点索引一次。
        int[] triangles = new int[xSize * ySize* 6];
        // 用循环遍历的方式将所有三角形面片都画出来
        // x和vi都是计数器,也可以用一个
        // ti是每个正方形面片的左下角的计数点
        // triangles中的标量是指每个顶点,保存的值是具体哪个顶点
        for(int ti = 0, vi = 0, y = 0; y < ySize; ++y, ++vi)
        
            for(int x = 0;x < xSize; ++x, ti += 6, ++vi)
            
                triangles[ti] = vi;
                triangles[ti + 3] = triangles[ti + 2] = vi + 1;
                triangles[ti + 4] = triangles[ti + 1] = vi + xSize + 1;
                triangles[ti + 5] = vi + xSize + 2;
                yield return wait;
            
        
        mesh.triangles = triangles;
        yield return wait;

当我们可以合理的将三角形面片生成出来时,就可以将协程干掉了,直接将整个面片画出来

private void Awake () 
		Generate();
	

	private void Generate () 
		GetComponent<MeshFilter>().mesh = mesh = new Mesh();
		mesh.name = "Procedural Grid";

		vertices = new Vector3[(xSize + 1) * (ySize + 1)];
		for (int i = 0, y = 0; y <= ySize; y++) 
			for (int x = 0; x <= xSize; x++, i++) 
				vertices[i] = new Vector3(x, y);
			
		
		mesh.vertices = vertices;

		int[] triangles = new int[xSize * ySize * 6];
		for (int ti = 0, vi = 0, y = 0; y < ySize; y++, vi++) 
			for (int x = 0; x < xSize; x++, ti += 6, vi++) 
				triangles[ti] = vi;
				triangles[ti + 3] = triangles[ti + 2] = vi + 1;
				triangles[ti + 4] = triangles[ti + 1] = vi + xSize + 1;
				triangles[ti + 5] = vi + xSize + 2;
			
		
		mesh.triangles = triangles;
	

4 生成附加顶点数据

我们的需要一个我们自己想要的法线,现在我们所有的三角形的法线都是一样的,但我们不喜欢,我们可以通过提供法线来达到一些“作弊”行为。在现实中,顶点是没有法线的,但三角形有。但是,通过在顶点上附加自定义法线并在它们之间进行三角插值,就可以假装我们有一个平滑的曲面而不是一堆平坦的三角形。这种错觉是能够欺骗普通人的感官的。.
法线是每个顶点单独定义的,所以我们必须填充另外一个向量数组。或者,我们可以要求网格根据其三角形来确定法线本身。这次我们偷下懒。
mesh.RecalculateNormals();//从三角形和顶点重新计算网格的法线。

法线是怎么计算的?

Mesh.RecalculateNormals 计算每个顶点的法线是通过计算哪些三角形与该顶点相连,先确定这些平面三角形的法线,对它们进行平均,最后对结果进行归一化处理。

将纹理适配网格,纹理UV坐标是0-1,所以用顶点位置除以网格尺寸即可,注意这里我们一定要使用浮点才可以

Vector2[] uv = new Vector2[vertices.Length];
for(int i = 0, y = 0; y <= ySize; y++)

    for(int x=0;x<=xSize;++x,++i)
    
        vertices[i] = new Vector3(x, y);
        uv[i] = new Vector2((float)x / xSize, (float)y / ySize);
        tangents[i] = tangent;
    

 mesh.uv = uv;

还有一种简单的方式就是直接使用法线纹理,这个也是我们比较常用的一种方式
我们需要在网格中添加切线向量来正确的定位它们,表面法线在空间中,是垂直于三角形面片的,但游戏中当然不是这样的,我们的法线方向是由两个切线方向的叉乘所得,所以如果我们希望得到正确的法线,就需要将切线加载到三维空间中。Unity 的着色器执行此计算方式要求我们使用-1.所以我们可以得到一个切线(1,0,0,-1)

    private void Generate()
     
       ...
        Vector4[] tangents = new Vector4[vertices.Length];
        Vector4 tangent = new Vector4(1f, 0f, 0f, -1f);
        for(int i = 0, y = 0; y <= ySize; y++)
        
            for(int x=0;x<=xSize;++x,++i)
            
                ...
                tangents[i] = tangent;
            
        
        ...
     


虽然我们看到的这个mesh是凹凸不平的,但事实上,他只是通过数据将一个平面上的法线向量进行了改写。

Unity零基础到进阶 ☀️| 一篇文章 学会在Unity中访问 URL 连接网页 和 下载图片文件

📢前言

  • 最近在开发应用,有需求是通过Unity连接外部网页 🙈
  • 在网上查了查思路,所以就来写篇博客学一下Unity怎么访问URL🙉
  • 那就来简单学习并介绍一下吧~🙊

🎄在Unity中使用URL连接Web网页

在网上看到有好几种方式可以访问URL,那就来简单介绍一下吧~
先来看一下效果图
在这里插入图片描述

第一种:使用链接直接连接

将此连接方法写在需要连接的时候即可,比如下面写在Button点击事件里,点击就可以访问百度。下同

    public void ConnectUrl()
    {
        Application.OpenURL("http://www.baidu.com");
    }

2.通过WWW方式访问URL

在网上看到可以通过WWW的方式访问URL,试了一下可以的,但是VS中提示已经过时了,还是来演示一下吧
在这里插入图片描述

//WWW
        var www = new     WWW("file:///F:/anyun/zayFileWork/UnitySDK/WebView/Assets/Vuplex/WebView/Documentation/index.html");
        Application.OpenURL(www.url);

3.通过UnityWebRequest 方式访问URL

//新版UnityWebRequest
    public void ConnectUrl1()
    {
        UnityWebRequest unityWebRequest = new UnityWebRequest("https://www.csdn.net/");
        Application.OpenURL(unityWebRequest.url);
    }

4.通过本地HTML访问URL
注意此处的html是我放在本地去加载的

//使用绝对路径
    public void ConnectUrl1()
    {
        UnityWebRequest unityWebRequest = new UnityWebRequest("file:///F:/a/b/c/d/Assets/Vuplex/WebView/Documentation/index.html");
        Application.OpenURL(unityWebRequest.url);
    }
//使用相对路径
    public void ConnectUrl2()
    {
        UnityWebRequest unityWebRequest = new UnityWebRequest(Application.dataPath + "/Resources/index.html");
        Application.OpenURL(unityWebRequest.url);
    }

(1)./是当前目录
(2). ./是父级目录
(3)/是根目录
在这里插入图片描述


🔔利用URL下载文件和图片

先来看一下演示图
我这里是将下载的图片直接复制给了场景中的Image
在这里插入图片描述

通过URL 下载文件

废话不多说,直接上代码了,将URL换一下就可以直接用
Tips:这里的存储的本地路径一定要加上为 下载的文件—命名+后缀
我就是没有命名加后缀,只写了个路径,一直报错也是很郁闷啊!

    public IEnumerator DownFile()
    {
        //下载路径
        string url = "file://F:/a/b/private/Tex/%E5%9C%86%E8%84%B80.jpg";
        //存储的本地路径
        string localurl = Application.dataPath + "/Resources/bb.gif";
        UnityWebRequest WebRequest = new UnityWebRequest(url);
        DownloadHandlerFile Download = new DownloadHandlerFile(localurl);
        WebRequest.downloadHandler = Download;
        yield return WebRequest.SendWebRequest();
        //等待资源下载完成
        while (!WebRequest.isDone)
        {
            yield return null;
        }
        if (string.IsNullOrEmpty(WebRequest.error))
        {
            //文件下载成功
            Debug.Log("下载成功");
        }
        else
        {
            //文件下载失败
              Debug.Log("下载失败");
        }
    }

下载图片
亦是同理,直接上代码,替换URL可直接用
下面还写了一个GetSpriteByTexture方法,可以将下载的图片直接转换格式赋值给我们场景中的Image使用,

    public IEnumerator DownTexture()
    {
        /*
        读取的包外路径
        当为安卓环境时需添加前缀  file://
        路径需要包含文件的后缀名
        */
        string url = "file://F:/a/b/private/Tex/%E5%9C%86%E8%84%B80.jpg";
        UnityWebRequest WebRequest = new UnityWebRequest(url);
        DownloadHandlerTexture Download = new DownloadHandlerTexture(true);
        WebRequest.downloadHandler = Download;
        yield return WebRequest.SendWebRequest();
        //等待资源下载完成
        while (!WebRequest.isDone)
        {
            yield return null;
        }
        if (string.IsNullOrEmpty(WebRequest.error))
        {
            //文件下载成功
            //读取的图片
            Texture2D rexture = Download.texture;
            
            texture2D.sprite = GetSpriteByTexture(rexture);
            Debug.Log("图片下载成功");
        }
        else
        {
            //文件下载失败
            Debug.Log("图片下载失败");
        }
    }
        //将texture转成image的Sprite
    Sprite GetSpriteByTexture(Texture2D tex)
    {
        Sprite _sprite = Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height), new Vector2(0.5f, 0.5f));
        return _sprite;
    }

读取文件一样

    public IEnumerator GetFile()
    {
        /*
        读取的包外路径
        当为安卓环境时需添加前缀  file://
        路径需要包含文件的后缀名
        */
        string url = Application.dataPath + "/Resources/bb.gif";
        UnityWebRequest WebRequest = new UnityWebRequest(url);
        DownloadHandlerBuffer Download = new DownloadHandlerBuffer();
        WebRequest.downloadHandler = Download;
        yield return WebRequest.SendWebRequest();
        //等待资源下载完成
        while (!WebRequest.isDone)
        {
            yield return null;
        }
        if (string.IsNullOrEmpty(WebRequest.error))
        {
            //文件读取成功
            //读取的数据
            var data = Download.data;
              Debug.Log("成功");
        }
        else
        {
            //文件读取失败
              Debug.Log("失败");

        }
    }

Post

    public IEnumerator Post_Demo()
    {
        //Post请求的地址
        string url = "www.csdn.net";
        //Post请求的参数
        WWWForm form = new WWWForm();
        form.AddField("key1", "value1");
        form.AddField("key2", "value2");
        UnityWebRequest webRequest = UnityWebRequest.Post(url, form);
        //发送请求
        yield return webRequest.SendWebRequest();
        if (string.IsNullOrEmpty(webRequest.error))
        {
            //Post的请求成功
            //Post请求的返回参数
            var data = webRequest.downloadHandler.text;
            Debug.Log(data);
            Postttt.text = "成功";
        }
        else
        {
            //Post的请求失败
            Postttt.text = "失败";
        }
    }

Get

    public IEnumerator Get_Demo()
    {
        //Get请求的地址
        string url = "www.baidu.com";

        UnityWebRequest webRequest = UnityWebRequest.Get(url);
        //发送请求
        yield return webRequest.SendWebRequest();
        //等待请求完成
        while (!webRequest.isDone)
        {
            yield return null;
        }
        if (string.IsNullOrEmpty(webRequest.error))
        {
            //Get的请求成功
            //Get请求的返回参数
            var data = webRequest.downloadHandler.text;
            Debug.Log(data);
            Getttt.text = "成功";
        }
        else
        {
            //Get的请求失败
            Getttt.text = "失败";
        }
    }

🎁Unity中的编码解码

既然说到了访问读取文件,那就顺带提一下编码解码

URL中有一些符号是不能被解析的,所以我们需要进行编码比如:=
这个等号一般是有特殊意义的,编码后变成这个样子,就没有问题:%3d

在Unity中System.Web.HttpUtility.UrlDecode不能使用,所以我们一般用
编码

UnityWebRequest.EscapeURL(string url);
UnityWebRequest.EscapeURL(string url,Encoding e);

解码

UnityWebRequest.UnEscapeURL(string url);
UnityWebRequest.UnEscapeURL(string url,Encoding e);

实例

        string ccc = UnityWebRequest.UnEscapeURL("%e4%b8%ad%e5%9b%bd%e4%b8%96%e7%95%8c%e7%ac%ac%e4%b8%80%ef%bc%81", System.Text.Encoding.GetEncoding("utf-8"));//url 编码 转中文
        Debug.Log(ccc);
        
         string aaa = UnityWebRequest.EscapeURL("中国世界第一!");//中文转url编码
        Debug.Log(aaa);

在这里插入图片描述


💬总结

一直以为使用Unity访问URL很难,原来学习了才知道原来这么简单😘

不知道你学废了没呢?🥰

在这里插入图片描述

以上是关于[Unity 学习] - 进阶篇 - Mesh基础系列1:生成网格的主要内容,如果未能解决你的问题,请参考以下文章

游戏开发进阶Unity网格探险之旅(Mesh | 动态合批 | 骨骼动画 | 蒙皮 )

游戏开发进阶Unity网格探险之旅(Mesh | 动态合批 | 骨骼动画 | 蒙皮 )

Unity零基础到进阶 | Unity中Scriptable Object介绍学习

Unity零基础到进阶 | Unity中Scriptable Object介绍学习

Unity零基础到进阶 ☀️| 一篇文章 学会在Unity中访问 URL 连接网页 和 下载图片文件

Unity Mesh Mesh画球