這是上學期的一次課程作業,難度不高但是也一並記錄下來,偷懶地拿課程報告改改發上來。
課程要求:使用sketchUp建模,在Unity3D中實現場景漫游和場景互動。
知識點:建模、官方第一人稱控制器、網格碰撞器、剛體、觸發器、射線(觸發)碰撞器。
實驗題目
基於SketchUp和Unity 3D的虛擬場景漫游和場景互動(增強現實)
實驗內容
實驗要求
實驗要求是實現虛擬場景漫游和增強顯示效果。
模型實現
我們組的項目模型采用SketchUp的小作業房屋設計模型修改而來,最終通過兩張房屋設計圖分別設計了兩套住房模型:圖略。
增強現實效果實現
將模型導入Unity3d中后,進一步細化貼圖和場景,導入官方package以加入第一人稱角色控制器,對模型加入碰撞器,實現了場景漫游並結合第一人稱角色控制器的camera加入射線碰撞器,實現注視特定目標可以顯示出目標信息(左上角第一個顯示欄,如“這是老韓家吃飯的地方”),對於特殊目標設定對應的Text 3D效果,實現類似增強現實的場景效果(如餐桌上方浮現的“Let’s Eat!”),加入觸發器可以顯示出當前所在位置(左上角第一個顯示欄,如“welcome to 老韓家的客廳”)。
我們設立了多處地點觸發顯示和多處特定目標信息顯示,對於餐桌、房屋、電視機和床加入了增強現實的Text 3D效果,具體實現將在設計過程中展示。
具體流程(重點)
創建SketchUp模型
生成fbx文件
將SketchUp中的模型導入Unity 3D中需要使用SketchUp 2016 pro並生成fbx文件及其貼圖文件和文件夾,在實際操作中我們還注意到由於房頂等模型材質具有雙面不同貼圖的情況,結合網上博客了解到生成fbx文件時需要如下設置:
其中選項部分需要如下設置:
創建Unity場景
在Unity 3D中創建項目,生成Assert文件夾,創建場景在Assert文件夾下:
Terrian、Light和Skybox制作
在場景中加入第一個3D模型——Terrian,其中可以加入樹木、草、地形和地面貼圖,我們采用綠色草地的貼圖效果
而Light我們使用,並在房屋中設置了
,優點是可以使得漫游時照亮房屋,方便觀察操作,缺點是不符合實際日照情況。
Skybox我們先后使用了Standard Assert自帶的Skybox和Assert Store中的Skybox,效果均不理想,最后放棄加入Skybox,使用默認場景。
fbx移入場景中並貼圖
將之前導出的fbx文件及其貼圖文件夾移動到Assert文件夾中,由於Unity自身原因,並不能自動將貼圖文件和模型完美結合,需要自行貼圖,即在Albedo中拖入貼圖文件夾下的對應圖片,由於圖片命名和模型命名不完全相同,有一些模型結果並不理想。(此過程及其繁瑣)
加入第一人稱角色控制器
如果要自行實現角色控制並漫游,難度過高,我們選擇導入官方package即Standard Assert:
導入后效果如下:
而角色控制器分為:
我們選擇了第一人稱角色控制器,一方面是因為第三人稱角色控制器為了實現攝像頭跟蹤要加入smooth follow而且並不理想,另一方面我們實現的增強現實效果,即注視物體顯示信息和Text 3D效果通過第一人稱角色控制器來實現射線碰撞器更為方面,需要注意的是,為了通過某些狹窄的通道,可能需要更改角色的XYZ大小,也應該同步更改offset步長,否則會報錯。
模型加入碰撞器屬性
碰撞器是極為重要的一環,因為為了實現增強現實效果(注視現實物體信息和Text 3D模型),所有物體都要選擇是否作為碰撞體,在Unity中設置如下:
因為大多數模型,包括房屋家具等都是復雜的多面體模型,所以需要使用Mesh Colider(網格碰撞器),在網格碰撞器的具體實現中我們注意到了以下幾個關鍵點:
- 如果一個父物體加入了Mesh Colider組件,那它的所有子物體都必須加入Mesh Colider組件,擁有子物體的物體模型有很多:
- Mesh Colider組件中有Convex(相互碰撞)的選項,大多數物體我們需要勾選,但是在具體實現中,我們將房屋的Convex選擇不勾選,並將門的門框(僅僅是門框,不是整個門)不作為Mesh Colider碰撞體,可以使得角色方便進入,但是仍然會和門碰撞,不會穿過門板。
- 調試時候出現了部分模型Convex不通過的問題,原因是對應的Mesh超過了255個面,我們將這個模型(一盆花)不勾選Convex,即可。
- 大多數碰撞體結果如下:
加入trigger觸發器物體並隱藏
我們另一個技術點是實現顯示身處位置,即“welcome to 老韓家的客廳”,具體實現需要先設置觸發器,我們在多個地點設置了cube模型的觸發器,包括客廳、廁所、廚房、卧室、房屋外:
觸發器的效果是隱藏物體:
並設置成可穿過的物體:
一一對應模型信息並加入Text 3D的文字模型
為了實現增強現實效果,即注視物體顯示物體信息,我們將很多模型信息記錄下來,Tag是模型的名稱記錄,方便腳本中對應實現:
為了實現Text 3D的模型效果,我們加入了Text 3D模型:
命名為對應的text+對應的模型tag,並設置為隱藏,只有觀測到對應模型時才會顯示:
編寫RayTest2.cs腳本,實現射線碰撞器
做好了碰撞器和Text 3D模型,我們編寫了對應的腳本:
在Start()函數中,進行字典的初始化,即將碰撞物體及其信息一一對應:
在Update()函數中,在每一幀進行以camera為射線起點,以camera方向為射線方向,長度為100的射線碰撞,並返回碰撞到的物體信息:
在OnGUI()函數中,輸出射線碰撞到的物體對應的字典信息:
同時檢測是否有對應的Text 3D模型,有則將其顯示:
也會記錄前一次的碰撞物體,如果不同,則說明視線轉移了,之前的Text 3D繼續隱藏:
編寫TriggerTest2.cs腳本,實現觸發器
比較簡單,見附代碼。
運行調試
完成之后可以在Unity 3D中運行查看,可以生成exe文件運行,在Console中可以顯示Debug信息,通過調試的一點人生の經驗:
- 在室內向房頂看去,房頂顯示為透明,是因為fbx導出時沒有選擇雙面材質。
- Terrain加入草的元素時沒有效果,是因為草的Height需要 > 0。
- 第一人稱角色控制器不能移動,是因為模型大小更改以后,要同時更改offset步長。
- 顯示模型Convex錯誤,是因為過於復雜的物體如一盆花的Mesh網格超過了255個,取消掉其Convex選項即可。
- 物體添加了Mesh Conlider之后仍然會直接穿過,原因是其子物體也要全部加上Mesh Conlider組件。
- 注意整個場景中只能包含一個監視器,一般將后加入的監視器去掉,即去掉第一人稱角色控制器中的MainCamera。
參考
http://stephen830.iteye.com/blog/ Unity學習筆記
http://www.cnblogs.com/infly123/p/3920393.html 碰撞器和觸發器的區別
http://www.ceeger.com/Components/ Unity聖典
http://www.cnblogs.com/slysky/p/4290803.html 碰撞器與觸發器[Unity]
代碼
RayTest2.cs
using UnityEngine; using System.Collections; using System; using System.Collections.Generic; using System.Text; public class RayTest2 : MonoBehaviour { public string cur = ""; public string fro = ""; public Dictionary<string, string> dic = new Dictionary<string, string>(); // Use this for initialization void Start () { dic.Add("Mesh390","老張家的房子"); dic.Add("Mesh230", "老張家的書桌"); dic.Add("Mesh298", "老張家的書桌"); dic.Add("Mesh220", "老張家的電腦桌!"); dic.Add("Mesh249", "老張家的沙發"); dic.Add("Mesh253", "老張家的沙發"); dic.Add("Mesh250", "老張家的沙發"); dic.Add("Mesh252", "老張家的沙發"); dic.Add("Mesh389", "老張家的電視機"); dic.Add("Mesh239", "老張家的隔斷"); dic.Add("Mesh231", "老張家的一盆花"); dic.Add("Mesh232", "老張家的一盆花"); dic.Add("Mesh290", "老張家的一盆花"); dic.Add("Mesh291", "老張家的一盆花"); dic.Add("Mesh13", "老張家的大床"); dic.Add("Mesh21", "老張家的大床"); dic.Add("Mesh29", "老張家的大床"); dic.Add("Mesh246", "老張家吃飯的地方"); dic.Add("Mesh198", "老張家的衣櫃"); dic.Add("Mesh205", "老張家的衣櫃"); dic.Add("Mesh305", "老張家的衣櫃"); dic.Add("Mesh292", "老張家的馬桶!"); dic.Add("Mesh293", "老張家的馬桶!"); dic.Add("Mesh265", "老張家的廚房一套"); dic.Add("Mesh266", "老張家的廚房一套"); dic.Add("Mesh267", "老張家的廚房一套"); dic.Add("Mesh1812","老韓家的房子"); dic.Add("Mesh31", "老韓家吃飯的地方"); dic.Add("Mesh1327", "老韓家的大桌子"); dic.Add("Mesh121", "老韓家的衣櫃"); dic.Add("Mesh126", "老韓家的衣櫃"); dic.Add("Mesh131", "老韓家的衣櫃"); dic.Add("Mesh136", "老韓家的衣櫃"); dic.Add("Mesh141", "老韓家的衣櫃"); dic.Add("Mesh103", "老韓家的沙發"); dic.Add("Mesh116", "老韓家的茶幾"); dic.Add("Mesh1315", "老韓家的洗衣機"); dic.Add("Mesh1322", "老韓家的廚房一套"); dic.Add("Mesh40", "老韓家的環繞音響"); dic.Add("Mesh64", "老韓家的環繞音響"); } // Update is called once per frame void Update () { Ray ray=new Ray(Camera.main.transform.position,Camera.main.transform.forward*100); Debug.Log(ray); RaycastHit hitcur; if(Physics.Raycast(ray,out hitcur,100)) { cur = hitcur.collider.gameObject.name.ToString(); Debug.Log(cur); } } void OnGUI() { try { GUI.Box(new Rect(0, 0, 200, 30), "這是" + dic[cur]); } catch (Exception e) { GUI.Box(new Rect(0, 0, 200, 30), ""); } if (fro == cur) { } else { try { GameObject obj2 = GameObject.Find("text" + fro); obj2.GetComponent<Renderer>().enabled = false; } catch (Exception e) { }; } try { GameObject obj = GameObject.Find("text" + cur); obj.GetComponent<Renderer>().enabled = true; fro = cur; }catch (Exception e) { }; } }
TriggerTest2.cs
using UnityEngine; using System.Collections; using System.Collections.Generic; using System.Collections; using System; using System.Collections.Generic; using System.Text; public class TriggerTest2 : MonoBehaviour { public string cur_room = "outside1"; public Dictionary<string, string> dic = new Dictionary<string, string>(); // Use this for initialization void Start () { dic.Add("outside1","北京地下室群居房區"); dic.Add("house1room11","老張的客廳"); dic.Add("house1room12","老張的客廳"); dic.Add("house1room13","老張的客廳"); dic.Add("house1room21","老張的卧室A"); dic.Add("house1room31","老張的廁所"); dic.Add("house1room41","老張的廚房"); dic.Add("house1room51","老張的卧室B"); dic.Add("house1room61","老張的卧室C"); dic.Add("outside2","北京地下室群居房區"); dic.Add("house2room11","老韓的客廳"); dic.Add("house2room12","老韓的客廳"); dic.Add("house2room21","老韓的卧室A"); dic.Add("house2room31","老韓的卧室B"); dic.Add("house2room41","老韓的卧室C"); dic.Add("house2room51","老韓的廚房"); dic.Add("house2room61","老韓的廁所"); } // Update is called once per frame void Update () { //Debug.Log(cur_room); } void OnGUI() { try { GUI.Box(new Rect(0, 40, 200, 30), "welcome to " + dic[cur_room]); } catch (Exception e) { }; } void OnTriggerEnter(Collider collider) { //進入觸發器執行的代碼 cur_room = collider.gameObject.name.ToString(); } }