游戲開發(fā)進(jìn)階Unity網(wǎng)格(Mesh\動(dòng)態(tài)合批\骨骼動(dòng)畫\蒙皮)
一、前言
嗨,大家好,我是新發(fā)。
有同學(xué)私信我讓我寫一篇Unity
網(wǎng)格相關(guān)的教程,
那我就帶大家來一次Unity
的網(wǎng)格探險(xiǎn)之旅吧~
二、Hello Mesh
我背著旅行背包走在Unity
的場(chǎng)景中,突然眼前出現(xiàn)了一棵樹,
我走近一看,這棵樹身上掛著MeshFilter
和MeshRenderer
組件,根據(jù)Unity
探險(xiǎn)手冊(cè)記載,這個(gè)MeshFilter
是網(wǎng)格過濾器,它會(huì)引用一個(gè)網(wǎng)格資源,我順騰摸瓜,找到了對(duì)應(yīng)的網(wǎng)格,
實(shí)在太美了,我久久佇立,這就是網(wǎng)格啊!
正當(dāng)我欣賞著網(wǎng)格三角形時(shí),突然世界暗了下來,眼前出現(xiàn)了一團(tuán)火,
我又拿出了Unity
探險(xiǎn)手冊(cè),啊,這一定就是粒子系統(tǒng)了!它可以動(dòng)態(tài)生成網(wǎng)格。
天外傳來一陣打字聲,場(chǎng)景中出現(xiàn)了一行看起來像文字的網(wǎng)格,作為一個(gè)具有多年Hello World
經(jīng)驗(yàn)的程序員,我看出了第一個(gè)單詞應(yīng)該是Hello
,第二個(gè)單詞…我知道了,
是Hello Mesh
!
(此處為震撼人心的入場(chǎng)音樂)
三、萌新初識(shí)Mesh
1、引擎內(nèi)置的Mesh
網(wǎng)格的英文名是Mesh
,Unity
萌新最先接觸的網(wǎng)格應(yīng)該就是引擎內(nèi)置的Cube
(正方體)、Capsule
(膠囊體)、Cylinder
(圓柱體)、Plane
(平面)、Sphere
(球體)、Quad
(四邊形),如下
事實(shí)上,我們?cè)?code>Unity場(chǎng)景中,所有能被渲染出來的物體都會(huì)帶有網(wǎng)格,比如3D
模型、粒子特效、UI
、文字等等。
2、Mesh是什么
從概念上講,網(wǎng)格是圖形硬件用來繪制復(fù)雜內(nèi)容的構(gòu)造。它至少包含一組定義3D
空間中點(diǎn)的頂點(diǎn),以及一組連接這些點(diǎn)的三角形,實(shí)際上還包含法線、頂點(diǎn)顏色紋理坐標(biāo)等信息,這些三角形構(gòu)成了網(wǎng)格所代表的任何表面。
我們可以看下Unity
的Mesh
類,Mesh
的屬性和方法很多,我這里列舉幾個(gè)比較常用的,如下
// 頂點(diǎn)坐標(biāo)數(shù)組 public Vector3[] vertices { get; set; } // 法線向量數(shù)組 public Vector3[] normals { get; set; } // 頂點(diǎn)顏色數(shù)組 public Color[] colors { get; set; } // 三角形序列數(shù)組,每三個(gè)數(shù)字為一組 public int[] triangles { get; set; } // uv坐標(biāo)數(shù)組 public Vector2[] uv { get; set; } // 重新計(jì)算法線,在修改完頂點(diǎn)后,通常會(huì)更新法線來反映新的變化,注意,法線是根據(jù)共享的頂點(diǎn)計(jì)算出來的。 public void RecalculateNormals(); // 從法線和紋理坐標(biāo)重新計(jì)算網(wǎng)格的切線。修改網(wǎng)格的頂點(diǎn)和法線之后,如果網(wǎng)格使用引用法線貼圖的著色器進(jìn)行渲染,則切線需要更新。 public void RecalculateTangents(); // 重新計(jì)算從網(wǎng)格包圍體的頂點(diǎn), 在修改頂點(diǎn)后需要這個(gè)函數(shù)以確保包圍體是正確的,賦值三角形將自動(dòng)重新計(jì)算這個(gè)包圍體。 public void RecalculateBounds();
畫個(gè)圖,方便大家有個(gè)直觀印象,
三、Mesh的創(chuàng)建方式
1、第三方建模軟件
建模本質(zhì)上就是建網(wǎng)格,我們可以事先通過第三方建模軟件來創(chuàng)建模型網(wǎng)格,
常見的建模軟件比如
3DS MAX
官網(wǎng):https://www.autodesk.com/products/3ds-max/overview
MAYA
官網(wǎng):https://www.autodesk.com/products/maya/overview
blender
官網(wǎng):https://www.blender.org/
2、Unity建模插件:ProBuilder
Unity
官方提供了一個(gè)可以用來創(chuàng)建和自定義幾何體的工具ProBuilder
,我們可以在Unity
的Package Manager
中下載到這個(gè)插件,
使用ProBuilder
我們可以直接在Unity
中創(chuàng)建或編輯簡單的幾何體,不用通過第三方建模軟件,提升了效率,方便快速搭建場(chǎng)景原型,
3、程序動(dòng)態(tài)生成網(wǎng)格
網(wǎng)格也可以是程序動(dòng)態(tài)生成的,比如粒子系統(tǒng)的網(wǎng)格就是動(dòng)態(tài)生成的,
又比如文字,也是程序動(dòng)態(tài)生成網(wǎng)格,
文章后面我還會(huì)手把手教你如何使用純代碼來構(gòu)建網(wǎng)格,這里先不急著寫代碼,我們繼續(xù)探尋網(wǎng)格的秘密先~
四、Unity中如何顯示網(wǎng)格
在Unity
中,我們要顯示一個(gè)網(wǎng)格,需要用到兩個(gè)組件:MeshFilter
和MeshRenderer
。
注:你也可以直接使用SkinnedMeshRenderer
組件,與MeshFilter
和MeshRenderer
的區(qū)別我下文會(huì)講。
1、MeshFilter:網(wǎng)格過濾器
MeshFilter
是網(wǎng)格過濾器,我們需要通過它設(shè)置引用的網(wǎng)格資源,比如這里引用的是一個(gè)Cube
(正方體)網(wǎng)格。
我們可以看下MeshFilter.cs
的源碼,
[RequireComponent(typeof(Transform))] [NativeHeader("Runtime/Graphics/Mesh/MeshFilter.h")] public sealed partial class MeshFilter : Component { [RequiredByNativeCode] // MeshFilter is used in the VR Splash screen. private void DontStripMeshFilter() {} extern public Mesh sharedMesh { get; set; } extern public Mesh mesh {[NativeName("GetInstantiatedMeshFromScript")] get; [NativeName("SetInstantiatedMesh")] set; } }
MeshFilter
只有兩個(gè)屬性:mesh
和sharedMesh
,
我們查看Unity
的官方手冊(cè),看看mesh
與sharedMesh
的區(qū)別:https://docs.unity3d.com/ScriptReference/MeshFilter.html
我來解讀一下,mesh
訪問的是一個(gè)Mesh
資源的實(shí)例(副本),這意味著我們修改這個(gè)mesh
并不會(huì)修改到原始資源本身,改的只是Mesh
的實(shí)例(副本)。
而sharedMesh
是原始資源的引用,如果修改了sharedMesh
,比如修改頂點(diǎn)坐標(biāo),那么原始資源也會(huì)被修改。
畫成圖大概是這樣子:
這里我順手寫個(gè)隨機(jī)修改Mesh
頂點(diǎn)坐標(biāo)的腳本,如下,將下面這個(gè)RandoMeshmVertices
腳本掛到MeshFilter
組件所在的物體上即可,
// RandoMeshmVertices.cs // 隨機(jī)修改Mesh頂點(diǎn)坐標(biāo) using UnityEngine; public class RandoMeshmVertices: MonoBehaviour { // Mesh的實(shí)例 MeshFilter meshFilter; // 頂點(diǎn)的原始坐標(biāo) Vector3[] originalVertices; void Start() { meshFilter = GetComponent<MeshFilter>(); originalVertices = meshFilter.mesh.vertices; } void Update() { // 隨機(jī)修改頂點(diǎn)坐標(biāo) Vector3[] vertices = meshFilter.mesh.vertices; for (int i = 0, len = originalVertices.Length; i < len; ++i) { var v = originalVertices[i]; vertices[i] = v + Random.Range(-0.1F, 0.1F) * Vector3.one; } meshFilter.mesh.vertices = vertices; meshFilter.mesh.RecalculateNormals(); } }
運(yùn)行效果如下,網(wǎng)格頂點(diǎn)坐標(biāo)發(fā)生了隨機(jī)偏移,
關(guān)于mesh
屬性的訪問需要特別注意一下,我們先看看Unity
官方手冊(cè)的說明,https://docs.unity3d.com/ScriptReference/MeshFilter-mesh.html
翻譯一下就是,如果一個(gè)Mesh
資源已經(jīng)被分配給MeshFilter
的mesh
屬性,那么當(dāng)我們?cè)诖a中第一次訪問mesh
屬性時(shí)才正真創(chuàng)建了Mesh
的實(shí)例;再次訪問mesh
屬性時(shí)則直接返回這個(gè)實(shí)例,并且一旦mesh
屬性被訪問,則與原始共享網(wǎng)格的鏈接會(huì)丟失,此時(shí)sharedMesh
變成mesh
的別名,如果我們想避免這種自動(dòng)生成Mesh
實(shí)例,可以使用sharedMesh
代替。
寫成偽代碼的話大致是這樣子:
public class MeshFilter ... { ... private Mesh _mesh; public Mesh mesh { get { if (_mesh == null) { _mesh = new Mesh(); Copy(sharedMeh, _mesh); } return _mesh; } } ... }
還有,如果我們?cè)L問了mesh
屬性而導(dǎo)致自動(dòng)創(chuàng)建了Mesh
實(shí)例,則需要在代碼中主動(dòng)調(diào)用Resources.UnloadUnusedAssets
來銷毀沒有引用的Mesh
實(shí)例,建議是在場(chǎng)景切換時(shí)調(diào)用Resources.UnloadUnusedAssets
。
2、MeshRenderer:網(wǎng)格渲染器
MeshRenderer
,顧名思義,網(wǎng)格渲染器。我們依舊先來看看官方手冊(cè)的介紹:
https://docs.unity3d.com/Manual/class-MeshRenderer.html
翻譯過來就是MeshRenderer
會(huì)從MeshFilter
那里拿到網(wǎng)格數(shù)據(jù)并在所在物體的位置處將其渲染出來。
如果沒有MeshRenderer
,我們就看不見網(wǎng)格了,如下
另外,我們還需要在MeshRenderer
的Materials
中指定一個(gè)材質(zhì)球,這樣才能正常顯示,否則模型表面就是紫色的。
3、SkinnedMeshRenderer:蒙皮網(wǎng)格渲染器
SkinnedMeshRenderer
是蒙皮網(wǎng)格渲染器,可能有小伙伴就會(huì)問了,上面使用MeshFilter
和MeshRenderer
已經(jīng)可以顯示模型網(wǎng)格了,為什么又弄了一個(gè)SkinnedMeshRenderer
呢?
看下Unity
官方手冊(cè)的介紹:https://docs.unity3d.com/Manual/class-SkinnedMeshRenderer.html
可以看到SkinnedMeshRenderer
其實(shí)是針對(duì)帶 骨骼動(dòng)畫 的模型的渲染的。
3.1 骨骼動(dòng)畫
為什么需要做骨骼動(dòng)畫呢?
就好比我們?nèi)艘粯?,我們的骨骼?huì)隨著我們肌肉的伸縮而動(dòng),骨骼又可以帶動(dòng)它管轄的身體部位發(fā)生形變和移動(dòng),骨骼還會(huì)影響它所連接的其他骨骼一起發(fā)生聯(lián)動(dòng)。對(duì)應(yīng)到模型動(dòng)作上,想想一個(gè)簡單的舉手動(dòng)作要牽涉到多少網(wǎng)格頂點(diǎn)的移動(dòng),如果沒有骨骼,那動(dòng)畫師要每幀挨個(gè)網(wǎng)格頂點(diǎn)進(jìn)行調(diào)整,即使動(dòng)畫做出來了,這個(gè)動(dòng)畫也不能復(fù)用到其他模型上,因?yàn)椴煌P偷捻旤c(diǎn)信息都不一樣,這么低效的動(dòng)畫制作肯定是不行的,于是,就有了骨骼動(dòng)畫。
骨骼動(dòng)畫的原理
就是將模型分為骨骼(Bone
)和蒙皮(Mesh
)兩個(gè)部分,骨骼可分為多層父子骨骼,每個(gè)骨骼都附加到周圍網(wǎng)格的一些頂點(diǎn)上,在動(dòng)畫關(guān)鍵幀數(shù)據(jù)的驅(qū)動(dòng)下,計(jì)算出各個(gè)父子骨骼的位置,基于骨骼的控制通過頂點(diǎn)混合動(dòng)態(tài)計(jì)算出蒙皮網(wǎng)格的頂點(diǎn)。
動(dòng)畫師可以在MAYA
軟件上給模型綁定骨骼,綁定骨骼不是本文的重點(diǎn),這里就不展講開具體操作了,感興趣的同學(xué)可以自行百科學(xué)習(xí)。
制作好導(dǎo)出為fbx
格式,
將fbx
文件導(dǎo)入到Unity
中,選中它,
在Inspector
視圖中點(diǎn)擊Rig
按鈕,
我們可以看到動(dòng)畫類型Animation Type
有None
、Legacy
、Generic
和Humanoid
四個(gè),
具體選項(xiàng)可以參見Unity
官方手冊(cè):https://docs.unity3d.com/Manual/FBXImporter-Rig.html
我這里演示一下人形骨骼動(dòng)畫,選擇Humanoid
類型,Avatar Definition
選擇Create From This Model
,然后點(diǎn)擊Configure
,
在Inspector
視圖中我們就可以看到對(duì)應(yīng)的骨骼綁定信息了,
如下,綠色的線段就是一根根骨骼,
我們調(diào)整一根骨骼,對(duì)應(yīng)的網(wǎng)格也會(huì)跟著一起動(dòng),如下
這樣做出來的人形動(dòng)畫是可以進(jìn)行復(fù)用了,有請(qǐng)妹子上場(chǎng),
骨骼動(dòng)畫資源的話,我在之前的文章中也介紹過一個(gè)寶藏網(wǎng)站Mixamo
:https://www.mixamo.com/,上面有很多做好的人形骨骼動(dòng)畫,
看,是不是挺好玩的,
我們可以把它的動(dòng)作直接復(fù)用到我們自己的人形模型上,效果如下:
3.2 SkinnedMeshRenderer組件
骨骼動(dòng)畫可以正常播放,要?dú)w功于SkinnedMeshRenderer
組件,制作好骨骼動(dòng)畫的fbx
文件導(dǎo)入Unity
中,Unity
會(huì)自動(dòng)幫我們掛上SkinnedMeshRenderer
組件,
其中幾個(gè)重要的屬性我講一下,
Bounds
:骨骼數(shù)據(jù);
Mesh
:要渲染的網(wǎng)格;
Root Bone
:根骨骼,其他骨骼都是相對(duì)根骨骼移動(dòng)的;
BlendShapes
:一般用于制作表情融合,我之前寫過一篇文章講過BlendShapes
:
Unity通過BlendShape實(shí)現(xiàn)面部表情過渡切換Animation教程
我們?cè)賮砜纯?code>SkinnedMeshRenderer腳本的屬性和方法:
需要講的應(yīng)該就是這個(gè)BakeMesh
方法了,下面我就單獨(dú)拎出來講下BakeMesh
。
3.2 使用BakeMesh進(jìn)行優(yōu)化
假設(shè)現(xiàn)在場(chǎng)景中有100
只皮卡丘,每只皮卡丘的網(wǎng)格、貼圖、動(dòng)作相同,
如果每只皮卡丘身上都掛SkinnedMeshRenderer
,那就是100
個(gè)SkinnedMeshRenderer
在計(jì)算蒙皮,
由于SkinnedMeshRenderer
是根據(jù)骨骼動(dòng)畫動(dòng)態(tài)計(jì)算網(wǎng)格頂點(diǎn)坐標(biāo),這個(gè)運(yùn)算開銷還是不小的,有沒有辦法優(yōu)化呢?
SkinnedMeshRenderer
提供了一個(gè)BakeMesh
方法,可以將一個(gè)蒙皮動(dòng)畫的某個(gè)時(shí)間點(diǎn)上的動(dòng)作,Bake
成一個(gè)不帶蒙皮的Mesh
,我們統(tǒng)一使用這個(gè)Mesh
來顯示其余的皮卡丘,這樣就可以大大減少了SkinnedMeshRenderer
的計(jì)算了,
畫成圖大概是這樣子:
不過,上面這種方案的局限性是每只皮卡丘的動(dòng)畫是相同的,如果突然某一只皮卡丘要播放與其他皮卡丘不同的動(dòng)畫,那就不行了。
另一種Bake
方案可以是這樣:
對(duì)皮卡丘的每個(gè)動(dòng)畫進(jìn)行遍歷采樣,把采樣到的Mesh
存到數(shù)組中,因?yàn)檫@里要Bake
很多網(wǎng)格,比較耗時(shí),建議在加載場(chǎng)景時(shí)時(shí)就完成采樣過程;后面要播放某個(gè)動(dòng)畫時(shí)直接從這個(gè)Mesh
數(shù)組中獲取Mesh
來顯示,此時(shí)直接使用MeshFilter
加MeshRenderer
的方式來顯示網(wǎng)格就好了。
貼個(gè)BakeMesh
的示例腳本:
using UnityEngine; using System.Collections.Generic; /// <summary> /// Bake Mesh 示例 /// </summary> public class BakeMeshTest : MonoBehaviour { [SerializeField] Animation m_animation; [SerializeField] SkinnedMeshRenderer m_skinnedMeshRenderer; [SerializeField] string m_clipToBake = "Idle"; List<Mesh> m_bakedMeshList = new List<Mesh>(); /// <summary> /// 采樣幀數(shù) /// </summary> [SerializeField] int m_numFramesToBake = 30; void Start() { // 獲取要Bake的動(dòng)畫片段 AnimationState clipState = m_animation[m_clipToBake]; if (clipState == null) { Debug.LogError(string.Format("Unable to get clip '{0}'", m_clipToBake), this); return; } // 開始播放動(dòng)畫 m_animation.Play(m_clipToBake, PlayMode.StopAll); // 設(shè)置動(dòng)畫初始時(shí)間戳 clipState.time = 0.0f; // 采樣幀間隔 float deltaTime = clipState.length / (float)(m_numFramesToBake - 1); for (int frameIndex = 0; frameIndex < m_numFramesToBake; ++frameIndex) { string frameName = string.Format("BakedFrame{0}", frameIndex); // 創(chuàng)建Mesh Mesh frameMesh = new Mesh(); frameMesh.name = frameName; // 動(dòng)畫采樣 m_animation.Sample(); // 執(zhí)行BakeMesh m_skinnedMeshRenderer.BakeMesh(frameMesh); m_bakedMeshList.Add(frameMesh); // 設(shè)置動(dòng)畫時(shí)間戳 clipState.time += deltaTime; } // 停止播放動(dòng)畫 m_animation.Stop(); } }
需要提醒的是,這個(gè)方案是利用空間換時(shí)間,如果模型頂點(diǎn)數(shù)據(jù)特別多或動(dòng)畫時(shí)長特別長的時(shí)候,這時(shí)就會(huì)遇到內(nèi)存瓶頸。
五、純代碼動(dòng)態(tài)創(chuàng)建網(wǎng)格
一般情況下,網(wǎng)格是事先制作好的資源,但也有一些特殊的需求需要在代碼中動(dòng)態(tài)創(chuàng)建網(wǎng)格。
比如我之前寫的一篇牙齒碎了的文章:
現(xiàn)在我來教大家如何使用代碼從零創(chuàng)建網(wǎng)格并將網(wǎng)格渲染出來,下文我以創(chuàng)建一個(gè)正方形網(wǎng)格為例進(jìn)行講解。
1、創(chuàng)建Mesh對(duì)象
第一步最簡單,就是直接new
一個(gè)Mesh
,
var mesh = new Mesh();
2、頂點(diǎn)坐標(biāo)
首先分析一下,一個(gè)四邊形有四個(gè)頂點(diǎn),假設(shè)正方形邊長為1
,四個(gè)點(diǎn)的坐標(biāo)如下,
寫成代碼就是這樣:
// 構(gòu)建頂點(diǎn)坐標(biāo) var vertices = new List<Vector3>(); vertices.Add(new Vector3(-0.5f, -0.5f, 0)); vertices.Add(new Vector3(-0.5f, 0.5f, 0)); vertices.Add(new Vector3(0.5f, 0.5f, 0)); vertices.Add(new Vector3(0.5f, -0.5f, 0)); // 將頂點(diǎn)坐標(biāo)設(shè)置給Mesh mesh.SetVertices(vertices);
3、UV坐標(biāo)
UV
坐標(biāo)就是紋理貼圖坐標(biāo),它將紋理上每一個(gè)點(diǎn)精確對(duì)應(yīng)到模型物體的表面上,注意U
和V
的取值范圍是0~1
。
UV
坐標(biāo)系原點(diǎn)在左下角,U
軸是水平軸,V
軸是豎直軸,如下:
對(duì)應(yīng)到我們的上面那個(gè)正方向網(wǎng)格的話,四個(gè)點(diǎn)的UV
坐標(biāo)如下:
寫成代碼就是這樣:
// 構(gòu)建UV坐標(biāo) var uvs = new List<Vector2>(); uvs.Add(new Vector2(0, 0)); uvs.Add(new Vector2(0, 1)); uvs.Add(new Vector2(1, 1)); uvs.Add(new Vector2(1, 0)); // 將UV坐標(biāo)設(shè)置給Mesh mesh.SetUVs(0, uvs);
4、三角形序列
網(wǎng)格需要切分成三角形,我們可以這樣切分,
當(dāng)然也可以這樣切分,
兩種切分方法對(duì)應(yīng)不同的三角形序列,假設(shè) 法線方向 是垂直于屏幕從內(nèi)指向屏幕外的話,第一種切分方式的三角形序列如下:
注:法線的方向就決定了表面正面,如果你的材質(zhì)是單面渲染的話,那么只有從正面看才能看到網(wǎng)格被渲染。
即三角形序列為:{ 0, 1, 2, 0, 2, 3 }
,注意序號(hào)是從0
開始的。
為什么是這樣的順序呢?我教大家一個(gè)技巧,伸出你的左手,豎起大拇指,像這樣子,
大拇指指向法線的方向,那么此時(shí)你的其余四根手指頭環(huán)繞的方向就是三角形的序號(hào)的順序,三個(gè)序號(hào)為一組按順序塞入數(shù)組中即可,即得到的數(shù)組就是:{ 0, 1, 2, 0, 2,3}當(dāng)然,以下數(shù)組最終的效果都是等價(jià)的,只要順序一致即可:
{ 0, 1, 2, 0, 2, 3 },
{ 1, 2, 0, 0, 2, 3 },
{ 0, 2, 3, 1, 2, 0 },
…
我們現(xiàn)在寫成代碼,
// 重新計(jì)算法線,注意,法線是根據(jù)共享的頂點(diǎn)計(jì)算出來的。 mesh.RecalculateNormals(); // 重新計(jì)算包圍體,在修改頂點(diǎn)后需要這個(gè)函數(shù)以確保包圍體是正確的 mesh.RecalculateBounds(); // 從法線和紋理坐標(biāo)重新計(jì)算網(wǎng)格的切線(如果網(wǎng)格使用引用法線貼圖的著色器進(jìn)行渲染,則切線需要更新) // 因?yàn)槲覀冞@里不使用法線貼圖,所以就不調(diào)用它了 // mesh.RecalculateTangents();
5、重新計(jì)算法線和包圍體
當(dāng)我們?cè)O(shè)置或修改了頂點(diǎn)數(shù)據(jù)后,需要調(diào)用Mesh
的Recalculate
方法來重新計(jì)算一些必要的信息,比如重新計(jì)算法線、包圍體,代碼如下
// 重新計(jì)算法線,注意,法線是根據(jù)共享的頂點(diǎn)計(jì)算出來的。 mesh.RecalculateNormals(); // 重新計(jì)算包圍體,在修改頂點(diǎn)后需要這個(gè)函數(shù)以確保包圍體是正確的 mesh.RecalculateBounds(); // 從法線和紋理坐標(biāo)重新計(jì)算網(wǎng)格的切線(如果網(wǎng)格使用引用法線貼圖的著色器進(jìn)行渲染,則切線需要更新) // 因?yàn)槲覀冞@里不使用法線貼圖,所以就不調(diào)用它了 // mesh.RecalculateTangents();
6、完整版代碼
以上代碼封裝成GenQuadMesh.cs
腳本,完整代碼如下:
// 使用代碼生成四邊形網(wǎng)格 using System.Collections.Generic; using UnityEngine; [RequireComponent(typeof(MeshFilter))] [RequireComponent(typeof(MeshRenderer))] public class GenQuadMesh : MonoBehaviour { public MeshFilter mf; private void Start() { mf.mesh = Build(); } public static Mesh Build() { var mesh = new Mesh(); // 構(gòu)建頂點(diǎn)坐標(biāo) var vertices = new List<Vector3>(); vertices.Add(new Vector3(-0.5f, -0.5f, 0)); vertices.Add(new Vector3(-0.5f, 0.5f, 0)); vertices.Add(new Vector3(0.5f, 0.5f, 0)); vertices.Add(new Vector3(0.5f, -0.5f, 0)); // 將頂點(diǎn)坐標(biāo)設(shè)置給Mesh mesh.SetVertices(vertices); // 構(gòu)建UV坐標(biāo) var uvs = new List<Vector2>(); uvs.Add(new Vector2(0, 0)); uvs.Add(new Vector2(0, 1)); uvs.Add(new Vector2(1, 1)); uvs.Add(new Vector2(1, 0)); // 將UV坐標(biāo)設(shè)置給Mesh mesh.SetUVs(0, uvs); // 設(shè)置三角形序列 var triangles = new int[] { 0, 1, 2, 0, 2, 3 }; mesh.SetTriangles(triangles, 0); mesh.RecalculateNormals(); mesh.RecalculateBounds(); return mesh; } }
7、測(cè)試
創(chuàng)建一個(gè)空物體,掛上MeshFilter
和MeshRenderer
組件。
再掛上我們上面寫的GenQuadMesh
腳本,賦值mf
變量為MeshFilter
對(duì)象,如下
運(yùn)行Unity
,看到一個(gè)紫色快,
將Scene
視圖的模式設(shè)置為Wireframe
,如下
現(xiàn)在我們可以看到我們動(dòng)態(tài)創(chuàng)建的網(wǎng)格啦,
上面之所以顯示紫色塊,是因?yàn)槲覀儧]有給MeshFilter
設(shè)置材質(zhì)球,順手做一個(gè)炮姐的材質(zhì)球吧,
給MeshRenderer
設(shè)置材質(zhì)球?qū)ο螅?/p>
重新運(yùn)行Unity
,效果如下,
8、項(xiàng)目源碼
要用代碼動(dòng)態(tài)創(chuàng)建一個(gè)Mesh
,就是new
一個(gè)Mesh
,給它塞入頂點(diǎn)坐標(biāo)、UV
坐標(biāo)和三角形序列即可。再復(fù)雜的網(wǎng)格也可以通過這些步驟創(chuàng)建出來~
下面這些就是使用純代碼創(chuàng)建出來的幾何體網(wǎng)格,感興趣的同學(xué)可以下載項(xiàng)目源碼下來學(xué)習(xí)。
項(xiàng)目源碼:https://codechina.csdn.net/linxinfa/unity-mesh-builder
六、網(wǎng)格相關(guān)的開源項(xiàng)目
我再推薦一些網(wǎng)格相關(guān)的開源項(xiàng)目給大家~
1、2D網(wǎng)格涂鴉
項(xiàng)目地址:https://github.com/mattatz/unity-triangulation2D
2、3D網(wǎng)格涂鴉
項(xiàng)目地址:https://github.com/mattatz/unity-teddy
3、網(wǎng)格體素化
項(xiàng)目地址:https://github.com/Scrawk/Mesh-Voxelization
4、網(wǎng)格平滑算法
項(xiàng)目地址:https://github.com/mattatz/unity-mesh-smoothing
5、網(wǎng)格切割
項(xiàng)目地址:https://github.com/hugoscurti/mesh-cutter
6、網(wǎng)格合并
項(xiàng)目地址:https://github.com/sanukin39/UniMeshCombiner
七、未完的探險(xiǎn)
好了,這次探險(xiǎn)之旅就暫時(shí)到這里吧,還有很多內(nèi)容需要探索,先保持體力,我們下次再見,更多關(guān)于Unity網(wǎng)格(Mesh\動(dòng)態(tài)合批\骨骼動(dòng)畫\蒙皮)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Chrome瀏覽器中清除特定網(wǎng)站的Cookie數(shù)據(jù)三種方法
當(dāng)我們?cè)谑褂秒娔X瀏覽網(wǎng)頁時(shí),服務(wù)器會(huì)生成一個(gè)證書并將其返回給電腦,這個(gè)證書是cookie,也可以稱為瀏覽器緩存,這篇文章主要給大家介紹了關(guān)于Chrome瀏覽器中清除特定網(wǎng)站的Cookie數(shù)據(jù)三種方法,需要的朋友可以參考下2023-10-10代碼著色之SyntaxHighlighter項(xiàng)目(最流行的代碼高亮)
dp.SyntaxHighlighter。它可以在網(wǎng)頁中對(duì)各種程序源代碼語法進(jìn)行加亮顯示。支持當(dāng)前流 行的各種編程語言:C#、CSS、C++、Delphi、Java、JavaScript、PHP、Python、Ruby、SQL、Visual Basic、XML / HTML等2014-04-04代碼中到底應(yīng)不應(yīng)當(dāng)寫注釋?
注釋的確有其用途,但大部分情況下,程序員在濫用注釋。我是反對(duì)夾雜在代碼間的注釋的,我認(rèn)為注釋應(yīng)當(dāng)從代碼中獨(dú)立出來——通常被稱為文檔。2014-10-10基于QGIS的研究區(qū)域遙感影像裁切下載方法(以岳麓區(qū)為例)
本文以湖南省長沙市岳麓區(qū)為例,主要講解如何基于QGIS這款軟件,把岳麓區(qū)范圍內(nèi)的遙感影像下載,并使用QGIS進(jìn)行切片,最后用Leaflet進(jìn)行展示的例子,對(duì)QGIS遙感影像裁切下載相關(guān)知識(shí)感興趣的朋友跟隨小編一起看看吧2024-05-05解決IDEA中編輯HTML格式文件不自動(dòng)縮進(jìn)問題
這篇文章主要介紹了解決IDEA中編輯HTML格式文件不自動(dòng)縮進(jìn)問題,本文內(nèi)容簡短,解決方法給大家提出了,需要的朋友可以參考下2020-01-01