这是上学期的一次课程作业,难度不高但是也一并记录下来,偷懒地拿课程报告改改发上来。
课程要求:使用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*);
Debug.Log(ray);
RaycastHit hitcur;
if(Physics.Raycast(ray,out hitcur,))
{
cur = hitcur.collider.gameObject.name.ToString();
Debug.Log(cur);
}
}
void OnGUI()
{
try
{
GUI.Box(new Rect(, , , ), "这是" + dic[cur]);
}
catch (Exception e)
{
GUI.Box(new Rect(, , , ), "");
} 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(, , , ), "welcome to " + dic[cur_room]);
}
catch (Exception e) { };
}
void OnTriggerEnter(Collider collider)
{ //进入触发器执行的代码
cur_room = collider.gameObject.name.ToString();
}
}