接着上篇文章的创建一个AR脸部特效安卓程序,这次来创建一个自己的可交互AR程序。同样使用之前的项目,在Assets的Scene文件夹下使用Ctrl+N新建一个Scene(或者File→New Scene),选择AR场景,点击Create创建:
这里由于也是对人脸进行识别,因此也需要使用移动设备的前置摄像头
菜单栏选择File → Build Settings,将新建的场景加入到打包列表中,隐藏或删除之前的场景:
zip下载位置在此,点击即可下载。
下载完成解压后,即可将InteractiveFaceFilter.unitypackage导入工程的Assets文件夹下。
导入方法:
在zip文件解压的文件夹位置,找到其中的unitypackage,将其拖拽至Unity的Assets文件夹上,接着会跳出一个导入资源的窗口,点击右下角的Import导入。
这一步中,我们将使用AR Face Manager来告知AR Session Origin,当检测到面部时,脸上将会显示什么特效。
将Hierarchy中的Cube物体删除,它只是用于测试的。
选中AR Session Origin物体,在其Inspector窗口中搜索并添加AR Face Manager组件,该组件用于检测人脸:
这一步中,为所有可选的眼镜创建一个预制体,称为“GlassesGroup”,用于用户选择眼镜。
为了使得眼镜能正好出现在眼睛的位置,我们需要将GlassesGroup做成AR Face预制体。
在Inspector面板中为GlassesGroup添加AR Face组件,用于GlassesGroup追踪人脸的移动:
将Hierarchy中的GlassesGroup拖拽至Assets → _InteractiveFaceFilter → Prefabs中做成预制体:
将该制作好的预制体拖拽至AR Session Origin物体AR Face Manager组件的Face Prefab中:
首先确保Game视图是适配手机的16:9分辨率,接着回到Scene窗口中,在Hierarchy窗口中选择GlassesGroup物体,将它的位置Z轴改为0.4,以便它可以显示在Game窗口的中心:
同样的,为了让这些眼镜出现在移动设备的视野中心,需要将GlassesGroup预制体的PositionZ轴也修改为0.4:
如果在Unity中测试,那么使用的是Hierarchy中的GlassesGroup预制体;而如果要在安卓移动设备上测试,那么需要隐藏Hierarchy中的GlassesGroup预制体。
项目中的眼镜模型是以一般人脸为基准设定的大小,我们可以修改眼镜的大小比例或者加以旋转,使得它们变得更加有趣。
在诸如Sketchfab等网站上有很多眼镜的模型,可以下载后导入项目中。在模型选择上,有以下3个大体的考量:
制作自己的眼镜模型可以考虑以下几点:
该项目使用UGUI系统,即所有的UI都使用Canvas组件来管理,它们都是Canvas的子物体。
现在眼镜切换按钮都是以圆圈的形式表示,为了让用户能更直观地分辨哪个按钮切换到哪一副眼镜,需要将圆圈替换为特定的眼镜图标。
Unity中使用sprite sheet来存放一个图集,它可以被切片,以便图像能单独在Unity中使用:
为了使眼镜更加美观,这里需要定义不同的材质(颜色)按钮:
我们还可以做如下的一些尝试:
制作自己的眼镜图标: 如果使用的是自己制作的眼镜模型,那么可以通过修改PSD文件UI_Icon_Glasses_Template来实现制作自己的眼镜图标
我们可以使用Visual Scripting为项目创建逻辑功能,这是一种可视化逻辑的方式。
如下左图和右图是一样的逻辑表达:
Visual Scripting还为非编码人员提供了对整个Unity Scripting应用编程接口(API)的访问。这个API包含了可以在Unity脚本中操作的类、事件、方法(行为)和属性(设置)的全套定义。
可视化脚本是存储于图(graph)中的,它可以以组件的形式应用于任意的GameObject上。
1.在Hierarchy中Create Empty创建一个空物体,确保其坐标在(0,0,0)位置,将其命名为“Visual Scripts”。
2.在“Visual Scripts”的Inspector窗口中添加“Script Machine”组件。
“Script Machine”组件是用于运行可视化脚本的工具。
在“Script Machine”组件中点击Edit Graph以打开可视化脚本工作区。如果是第一次打开,会跳出一个对话框,选择Change now即可。
且一个名为VisualScripting SceneVariables的新GameObject将在Hierarchy中自动生成。
节点是用来构建图中逻辑的组件,有几种不同类型的节点。每当创建一个新图时,它将加载On Start和On Update节点。
Script Graph窗口右侧是Graph Editor,在这里建立图形逻辑;窗口左侧是Graph Inspector,它将为我们展示选择对象的详细信息;在图形检查器窗口的下面是Blackboard,在这里创建变量。
Graph Editor中的操作:
选中On Start节点,左侧的Inspector窗口中列出了该节点有一个称作“Trigger”的Output(输出)。流程(Flow)的inputs(和outputs)决定了图的逻辑流程。
这里我们需要实现的功能不需要使用到On Start和On Update节点。因此,选中它们后右键选择Delete删除或者直接按Delete键删除。
右键创建一个On Button Click节点或者点击Events > GUI > On Button Click创建:
与之前的On Start和On Update节点不同,On Button Click有一个Inputs部分。
有两种类型的输入:控制输入和数据输入。
On Button Click节点只在按钮被按下时执行事件,因此它需要知道它应该监视哪个按钮。
Graph Inspector窗口中,On Button Click节点的输入正在寻找一个GameObject目标,这个GameObject就是On Button Click节点将监视的输入按钮。
默认情况下,On Button Click节点有一个标有This的参数,这个参数是目标输入将被连接的地方。当没有连接输入节点时,该节点有一个保护措施:它将在它所分配的游戏对象上寻找目标输入。
在这种情况下,如果把参数留空,On Button Click节点就会在我们创建的Script Machine所附的Visual Scripts GameObject上寻找一个Button组件。如果脚本图被附在AR_Canvas的一个按钮上,那么这就没问题了;
但是如果Graph在不同的GameObjects上,那么就需要使用变量来控制。
Variables是不同种类数据的容器。这些数据可以是数字、GameObjects、声音,或者其他任何东西。变量可以在graph本身中生成,也可以作为应用程序中其他对象的引用。在本例中,我们将创建一个变量,它将引用用户界面上的一个按钮。
创建一个变量。变量是在Blackboard中创建的,Blackboard顶端有如下的一些标签:
这些标签是我们可以创建的不同类别变量。因为我们需要创建一个引用场景中GameObject的变量,因此我们将创建一个Object变量。
首先,选择Object标签,输入 “glasses1Button” 后点击“+”号或按“Enter”添加变量;接着,将该变量的类型(Type)设置为Button、其Value参数选择Hierarchy中的Glasses1Button;最后,将Blackboard中创建的 “glasses1Button” 变量拖拽至Graph中,变成Get Variable节点。
Glasses1Button的Get Variable节点上也有This参数,这和On Button Click一样,是在Get Variable的返回值没有填写情况下的一个后备选项。但由于这个节点被定义为glasses1Button,所以不需要有返回值。
连接Get Variable节点与On Button Click节点,这样就实现了检测Glasses1Button按钮被按下这个事件了。
可以观察到,On Button Click节点上的This值消失了,这是因为它现在接受到了glasses1Button这个变量。
这样连接之后,点击Play运行项目就会发现在点击第一个眼镜图标时,On Button Click节点在闪烁,这说明它接收到了按钮的点击。
而要在点击按钮后在Console窗口中输出一条信息,那么可以在Graph中添加 Debug Log (Message) 节点以及String Literal节点用来输出一条文本信息。
其中,Debug Log (Message) 节点的作用是想Unity的控制台输出一条信息。该节点有一个Flow和Object输入,Object输入的内容是转换为字符串表示的字符串或对象,这样方便显示。因此需要创建一个String Literal(字符串)节点。
将On Button Click节点右侧的Flow输出连接到Debug Log节点左侧的Flow输入,接着将写有“Button 1 pressed!”的String Literal节点的输出连接到Debug Log节点左侧的Message输入。
运行可以发现控制台在检测到按钮按下后输出了“Button 1 pressed!”
另外,可以尝试使用Get Name、String、Concat节点进行获取GameObject名并将字符串拼接输出:
为了达到点击按钮出现眼镜模型的效果,需要先将眼镜模型的Mesh Renderer组件禁用,这个组件用于管理一个GameObject的可见性。
在Unity中,有几种在脚本中快速定位到指定GameObject的方法。其中一种方法是为GameObject分配一个独有的tag,即标签。这里为三幅眼镜分别添加一个特有的标签以更好地识别与分辨它们。
在Hierarchy窗口中,选择GlassesGroup物体,在其Inspector窗口中选择Overrides右侧的下拉菜单并点击Apply All以将标签设置保存到预制体中。
更新script graph,为了通过tag识别游戏物体,需要使用到Find With Tag节点。
可以看到该节点返回场景中一个active的GameObject,由于之前我们只是禁用了眼镜模型的mesh renderers,它们仍是处于active状态,因此是可以使用Find With Tag找到的。该节点需要一个数据(Flow)输入,也就是要查找的标签的名称。
使用Mesh Renderer:Set Enabled节点来实现用户点击按钮显示眼镜模型功能
该节点详细信息如下:
可以观察到需要两个数据来使用这个节点:
连接Find with Tag节点的数据(Flow)输出和Set Enabled节点的输入,连接Find with Tag节点的result输出和Set Enabled节点的输入,并将Bool值打勾。这表示当点击到按钮时,特定标签的眼镜模型会显示。
点击Play测试时就会发现点击第一个按钮,按钮上显示的眼镜也会出现在场景中。
复制两份制作好的script graph,并增加两个Object变量,拖动剩余两个按钮到其Value上,将Get Variable节点上相应的值修改。接着修改Find with Tag节点的Tag值为之前设置的眼镜特定tag。
点击Play测试时会发现点击按钮后眼镜依次出现在场景中,但是它们是叠加在一块的,这是因为还没有添加使它们隐藏的功能。
接着可以打包成apk到安卓设备上运行测试,注意需要将Hierarchy中的GlassesGroup禁用。
由于上一小节中,眼镜模型叠加在一块出现,因此需要使用Mesh Renderer:Set Enabled节点来禁用眼镜模型的可见性。
复制script graph中三个按钮的Find With Tag节点和Set Enabled节点,并将Set Enabled节点上的勾去掉。
修改script graph,使得用户点击任一按钮时,所有的眼镜模型都不显示。由于visual script运行的很快,因此顺序运行的隐藏眼镜在我们看起来是同时隐藏的。
接下来需要将上述相连的节点放到如下图中箭头所指的流程中,因为要在显示对应眼镜模型前将所有眼镜模型都隐藏。
但是可见,上图红框中的序列比较长,因此需要将该序列转换为一个自定义事件,这样我们就可以将他作为一个单一的节点以便调用该事件。
新建一个Custom Event节点,命名为“DisableRenderers”,并将其右侧输出流连接到上述序列的左侧输入流。
可以从graph Inspector面板中观察到,Custom Event节点的输入流可以是GameObject或String。
这里使用一个字符串“DisableRenderers”来命名该自定义事件,之后将使用该名称来调用该事件。
使用Trigger Custom Event节点来调用上述自定义事件。新建一个Trigger Custom Event节点,并复制两个。使用鼠标右键点击节点的输出流或输入流以取消连接,并将该节点插入OnButtonClick节点后。
我们可以把Trigger Custom Event节点当作一个链接(link),当这个节点被调用时,它将启动它所关联的自定义事件中的节点序列。
点击Play在编辑器中运行测试可以发现,点击对应的按钮,就会出现相应的模型,且不是叠加出现。
接着可以打包成apk到安卓设备上运行测试,注意需要将Hierarchy中的GlassesGroup禁用。
在使用script graph为GameObject添加逻辑功能时,最好是一个graph实现一个功能,这样graph就不会杂乱无章了。
在Hierarchy面板中,选中Visual Scripts物体,并在其Inspector面板中点击Add Component为其再添加一个“Script Machine”组件。接着,点击New,将该Visual script命名为“Material Changer”,并在其Title栏中填写“Material Changer”,在其Summary栏中填写“Changes the material assigned to all of the glasses when the material buttons are selected.”表示该可视化脚本用于实现点击按钮,变换眼镜模型的材质。
同样的,在这个功能中,我们不需要使用到On Start节点以及On Update节点,因此选中它们后按delete键删除。首先,新建一个On Button Click节点,并在blackboard中为它设置一个Object变量用于获取变化材质按钮。
在Assets下的_InteractiveFaceFilter文件夹中创建一个新文件夹,并命名为 “Materials”。新建对应按钮颜色的三种颜色材质,分别命名为“Material1”、“Material2”和“Material3”。
在blackboard中,创建三个Object类型的变量用来引用三种材质,它们的类型是Material,将它们分别命名为“Material1”、“Material2”和“Material3”。
要改变眼镜模型的颜色,需要获取到眼镜模型,再对其材质进行修改。因此,需要用到Find With Tag节点使用特定tag获取对应眼镜模型。
Set Material节点有两个输入:
其中Target使用从Find With Tag节点获取到的眼镜模型,Value使用对应材质的变量:
同样的方式,可以将另外两个颜色材质的赋予功能添加到script graph中。但是这里其实用到了重复的节点序列,因此下一节中使用带参数的自定义事件进一步将另外两个材质的功能添加进来。
首先,需要将所有的“Find With Tag”节点以及“Set Material”节点分离到Custom Event中,这样在每个材质修改按钮按下时就可以通过调用事件调用它们了。
参数可以被传递到事件或函数中,以改变事件的结果或函数的输出。由于我们需要对眼镜模型应用三种不同的材质,这取决于是哪个按钮被点击了。为Custom Event节点添加一个数据输出,称为参数,将不同的数据传递给那些Set Material节点。
调用定义的ChangeMaterial事件需要使用Trigger Custom Event节点,同样的,它也需要一个参数。
这时点击Play测试,也是同样的将第一种材质赋予三副眼镜。
接着,就可以禁用Hierarchy中的GlassesGroup物体,打包到安卓设备上测试了。可以观察到,眼镜可以追人脸的移动而移动。
在上一节的测试中,可以发现用户的头一旦离开了屏幕,再返回查看时,之前的眼镜不会再重新出现在脸上了。这是因为我们没有保存用户的选择,用户的脸离开屏幕后,AR Face Manager会将GlassesGroup组件删除;而当用户的脸再次回到屏幕前,AR Face Manager会重新实例化一个GlassesGroup预制体,它只有默认的设置配置,也没有将之前用户选择的材质应用到眼镜模型上。
首先,需要确保Hierarchy中的GlassesGroup物体是启用状态,接着编辑MeshChanger可视化脚本,在blackboard中创建一个名为“currentGlassesTag”的String类型变量(Object)用来存储用户当前选择的眼镜tag,并在graph中通过搜索“Set currentGlassesTag”找到“currentGlassesTag”变量的Set Variable节点。
由于currentGlassesTag的值需要根据用户选择的按钮进行变化,因此我们需要创建一个名为“SaveTag”的custom event,使它可以被三个按钮调用,它有一个参数用于输出获取到的tag。再添加一个获取物体Tag的节点,用于获取当前模型的tag。
使用Trigger Custom Even节点调用获取到tag的SaveTag事件,将其参数数量也修改为1,并作如下连接:
在之前的步骤中,我们获取了当前用户选择的是哪副眼镜的引用。因此,这一步中我们将在新的GroupGlasses预制体被实例化时,将用户选择的眼镜应用到GroupGlasses上。
这里,在Script Machine上再创建一个名为“FilterUpdater”的Graph,并在其Title栏目中填写“Filter Updater”,在其“Summary”栏目中填写“Reloads the last worn pair of glasses and selected material after a user’s face is lost and rediscovered by AR Foundation”,表示在用户的脸移开摄像头后重新加载之前选定的眼镜和材质。
先删除其中的On Start节点,保留On Update节点,因为我们需要实时检测用户的脸是否在摄像机的检测范围内。当应用程序运行时,On Update节点将持续地运行。FilterUpdater图的功能是通过currentGlassesTag变量获取当前用户选择的眼镜模型并将其Mesh Renderer启用。
可以通过这样的方式在编辑器中测试以上脚本的功能:
同样的方式,选择编辑MaterialChanger图,在blackboard中创建一个名为“currentMaterial”的Material类型变量(Object)用来存储用户当前选择的眼镜材质,并在graph中通过搜索“Set currentMaterial”找到“currentMaterial”变量的Set Variable节点。
由于之前已有ChangeMaterial的Cutom Event用来传递用户选择的材质,直接使用Set Variable节点获取即可:
最后,回到FilterUpdater图中,使用Mesh Renderer Set Material节点实时更新将用户已选择的材质赋予给眼镜模型:
如果这时候点击Play进行测试,会发现当选择了一副眼镜,眼镜上的材质缺失了,这是因为开始时用户并没有选择眼镜的材质,导致currentMaterial变量的值为空。因此需要为currentMaterial变量设置一个初始值。
在blackboard中找到currentMaterial变量,将其Value值设置为Lambert1,即默认为灰色的材质。
重新点击Play测试后,会发现一切正常显示了。接着,可以禁用Hierarchy中的GlassesGroup预制体,打包到安卓设备上进行测试。