[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介绍学习