如何更好地在 Cocos Creator 中使用 3D 资产?一文帮你排雷,常见问题全解答
之前的文章与大家介绍了一下在 Cocos Creator 中零代码构建场景的全过程。有朋友反馈自己在搭建场景时遇到了一些问题,对 3D 资产中的一些术语和概念也不是非常熟悉。今天给大家带来了这篇“排坑篇”,我们将一起看看在 Cocos Creator 中使用 3D 资产可能遇到的各种迷思,希望能够帮助大家解决使用时各种已经遇到的和没有遇到的问题。
对游戏开发有所了解的同学都知道,3D 美术资产都是由美术的同学在 DCC 中制作的。因此,过度到引擎阶段之后,我们首先要做的是 3D 资产的正确导出和导入,这包括了 3D 模型在 DCC 和引擎之间传递所采用的格式,以及资产在引擎中使用所必要的数据。在模型构建的过程中,美术的同学会将 3D 网格的相关数据处理好,但是如果我们不了解哪些数据是重要的以及它们为什么重要,即便我们拿到了一个所有细节都为我们准备妥当的模型资产,导入引擎的过程中也难免撞见各种各样的问题。
锚点
所有的 3D 模型都是以单独的网格物体为单位的,而每个单独的网格物体都有自己的锚点(Pivot Point,或称为 Anchor Point)。所谓锚点,指的是网格物体所有的移动、旋转和缩放的轴心点。
我们如何知道模型的锚点在哪里呢?一个最简单的识别方法:当你使用移动、旋转和缩放工具时,工具在 3D 视窗中的操作手柄在模型上出现的位置,就是模型锚点所在的位置。通常,模型在完成之后,美术会把锚点放在模型的中心位置,或者对于模型来说最合理的位置上。比如一个放置在地面上的雕塑,它的锚点通常会放在底部的中心位置。毕竟对于雕塑来说,所有的移动、旋转和尺寸比例变化,通常都是相对于地面而言的。
移动模型 / 移动锚点
锚点放置好之后,美术通常还会把模型移动到世界原点( X, Y 和 Z 轴在世界空间坐标中取值都是 0 )的位置,这就是为什么绝大多数 3D 模型打开之后都会端正地出现在 DCC 视窗的正中心,这并不是 DCC 打开模型后会非常智能地帮你完成什么人性化操作,而是美术在制作模型时依照行业的惯例为后续工作流程的同学准备好的。引擎在渲染 3D 模型的过程中,第一步就是将 3D 模型的顶点位置数据转换到世界空间。将顶点位置全部归一到相对于世界原点的位置,会省去之后不少的麻烦。当然,从另一个角度来说:模型并不是只有端端正正位于世界原点的情况下才允许导出和导入,模型在世界空间坐标中任何位置的情况下都可以导出其数据。如果你导出了模型之后,导入到引擎之后在场景中却找不到模型,首先需要确定在模型导出的时候,是否将模型放置到了世界原点的位置。
我们提到过:模型的锚点应该放置在模型的中心位置或者模型最合理的位置。那么如果锚点没有放置在这些位置上,模型就不能导出了吗?答案当然是否定的。如果锚点没有放置在模型合理的位置,比如说距离模型几米开外,那么我们在导入引擎后,对模型的所有的移动、旋转和缩放(这三者通常合称为“变换”(Transform))操作,都会以这个几米开外的位置以轴心进行,不仅如此,即便你不使用引擎提供的工具,而用脚本以程序的方式修改模型的移动、旋转和缩放属性,这个问题也会依然存在。不仅是 Cocos Creator,所有的引擎都是如此。
模型锚点的位置决定模型变换的轴心
那么,我们只需要无脑把所有模型的锚点都放置在它们的中心位置,问题不就解决了吗?答案也是否定的。有些时候,我们会刻意把锚点放在模型特定的位置,这将帮助我们实现动画效果。举个典型的例子:我们在游戏场景中经常见到可以打开的门,这些门有 3D 的门板模型,并且嵌入在一个门框的模型当中。试想一下:如果门板的锚点在门板的中心位置,那么无论它如何旋转都只能卡在门框的中心位置,这扇“门”是根本打不开的。反之,如果我们把锚点移动到门框的一边,让门板以门框的边缘为轴心,那么一个简单的旋转动画就能实现门的打开效果。
层级
在代码中我们接触过层级和继承的关系,在 3D 模型中,不同的网格物体也可以建立层级关系,与代码的继承不同的是:除了变换参数,3D 模型中的子层级并不会继承父层级的任何其他参数。举个例子:将父层级模型的旋转参数设为 90 度,之后将子层级模型的相同轴向旋转参数也设为 45 度,那么子层级最终呈现的角度其实是 135 度:因为子层级在继承了父层级 90 度旋转的情况下,自己又旋转了 45 度。之后,为父层级的模型赋予一个材质,子层级的材质并不会发成改变:因为父层级的材质并不会继承给子层级,它们仍然使用各自原本的材质参数。
简单地说:当我们变换父层级的时候,子层级会像“粘”在父层级上一样,与父层级一同移动、旋转和缩放。反之:单独变换子层级的时候,父层级的模型却不会有任何变化。这种层级关系的规律,也是骨骼系统实现的基本原理。
虽然如此,这并不意味着当我们使用一个带有层级关系的模型时,必须为它绑定骨骼。还是门板和门框的例子:把门板的锚点移动到合理的位置后,我们可以把门板放到门框的子层级上,这样我们只需要移动门框,门板就会跟着移动,同时,门板在旋转打开时,门框并不会有变化。进一步延申,我们可以再加入一个墙的模型,将把门框放在墙的子层级上,这样我们只需要变换墙的层级,就可以得到面向各种方向的带门的墙,并且墙上的门还可以打开。再进一步延申,我们可以把墙放到整个房子的模型的子层级下,这样我们在场景中任意的位置摆放整个房子,房子中的墙都会朝向正确的方向,而且墙上所有的门都可以正确打开。
再举个例子:我们有一个电风扇的模型,我们希望风扇的扇叶可以旋转,并且风扇头会像日常生活中常见的那样左右摆动。首先我们可以把电风扇模型的扇叶部分分离出来,将其锚点放在扇叶和风扇头衔接的位置;再将风扇头可以转动的部分也分离出来,将其放在与风扇剩余部分衔接的位置。扇叶在自己转动的同时,也会随着风扇头左右摆动,因此将扇叶放在风扇头的子层级上,风扇头放在剩余的风扇模型的子层级上。为扇叶的旋转参数加入关键帧,使它产生旋转动画,同时风扇头和风扇剩余的部分不会受到影响;再为风扇头的旋转参数加入关键帧,让风扇头能够左右摆动,扇叶会在自己旋转的同时,被风扇头带着左右摆动,同时风扇剩余的部分仍然不会有任何变化。
这种利用父子层级的基本关系得到变换效果的方法,就被称为正向运动学(Forward Kinematics),你会发现,在使用正向动力学的方式制作动画的过程中,我们不需要骨骼,只需要简单的锚点和层级关系就能够实现。
UV
UV 是 3D 模型包含的最重要的数据之一,因为它直接决定了模型是否能够正确投射贴图。没有正确 UV 数据的模型,不能在其表面正确投射任何的贴图,自然也不能正确使用任何材质和 shader 效果。美术的同学会在构建模型的过程中,相应地对其 UV 进行制作。
UV 是一组 2D 的坐标数据,它可以直观理解为:将 3D 模型的表面切割成若干独立的块,将每个块展平在 2D 平面上,将所有的块放置在同一张贴图的空间中,这样得到的 2D 数据就是模型的 UV。因为每个独立的 UV 块看上去就像地图上的小岛,因此这些 UV 块也被称为 UV island。因此,处理 UV 的关键要素,除了如何切割,就是如何在一张贴图的 2D 空间中摆放切割后独立的块。关于如何操作,程序的同学大概率不需要关心。但在使用方面,UV 的摆放会引申出两个问题:一是独立的 UV 块是否会有相互重叠的情况;二是 UV 块究竟该摆放在哪里。
当两个 UV 块重贴在一起,意味着在它们相应的模型上投射贴图的相同部分,在一些情况下这是理想的:毕竟同一个模型上出现相同的材质和纹理并不突兀,而且两个 UV 块空用空间,意味着剩下的 UV 块有更多的空间使用,每个 UV 块能够投射更多的贴图像素,从而提高使材质的精度。UV 的重叠并不一定是有意的,在另一些用途方面就会出现问题,比如渲染静态贴图。静态光照贴图是将光照的效果渲染为贴图供场景使用,而一个模型上不同的地方出现一摸一样的明暗效果几乎不可能出现,因此在烘培静态光照贴图时,UV 的重叠是不允许的。
至于 UV 的摆放,绝大多数 shader 只会从 UV 的 2D 空间 X 和 Y 轴中的 [0, 1] 范围内采样,因此 UV 块通常会被摆放在 X 和 Y 轴都小于 1 的正方形方格中,这个方格又称之为 UDIM。除了从坐标系原点出发的 (1, 1) 方格之外,在 X 轴上继续延申还有(2, 1)、(3, 1)、(4, 1) 等等无限多个方格,同理,在 Y 轴上延申也有(1, 2)、(1, 3)、(1, 4) 等无限多个方格,因此为了明确表示,我们把最靠近坐标系原点的 (1, 1) 的方格,称之为 UDIM1001。
虽然 UDIM1001 是大多数情况下 UV 摆放的空间,但由于 shader 可以通过缩放 UV 数值的方式改变贴图投射的大小,所以如果使用的贴图是四方连续的,即贴图的上下左右四个边都可以无缝衔接,那么摆放 UV 时则没有要求:无论摆放在哪里,UV 块的大小几何,都能够实现正确的效果。
在 Cocos Creator 中,我们需要为模型准备两套 UV,其中第一套 UV 用于材质和贴图,如上文所述,可以依照贴图的绘制和材质的需求进行灵活的处理,无论是否重叠还是是否超出 UDIM1001,只要相应的贴图合理则都是允许的。而第二套 UV 仅用于 Cocos Creator 中烘培静态光照贴图,正如我们在上面提到过的,UV 块的重叠时不允许的,而且必须摆放在 UDIM1001 内。
导出
模型在 DCC 中准备好之后,下一步就是以一个中转文件的格式,从 DCC 中导出以便导入到 Cocos Creator 中。3D 模型的格式五花八门,比如游戏引擎常用的 FBX 和 OBJ,来自远古 DOS 时代的 3DS,工业光魔的 alembic、OpenGL 本家 Khronos 的 Collada 等等。在 Cocos Creator 中,我们只能导入 FBX 格式的模型。需要注意的是,FBX 不是只能导出模型,它也可以导出摄像机、灯光、材质、贴图、动画等。在模型阶段,我们只需要导出模型即可。
确定了格式,我们还需要注意模型的坐标系系统。计算机图形学中使用两套系统:左手坐标系和右手坐标系,分别对应图形 API 中最常见的 DirectX 和 OpenGL。Cocos Creator 使用右手坐标系,其标志是竖直朝上的轴向为 Y 轴,在导出过程中,我们需要以 Y 轴朝上的设定导出。如果你的模型导入引擎之后,在旋转参数为 0 的情况下仍然“倒”向了一边(旋转 90 度),那么大概率是导出时的坐标系的选择并不正确。
顶点法线
除了锚点和 UV,另一个可能需要我们注意的是模型的顶点法线数据。顶点法线即模型的每一个顶点垂直于模型表面的矢量数据,它用于表达模型表面的朝向,因此会直接影响模型表面的光影渐变关系。如果你的模型看上去“不干净”,光影的渐变比较奇怪,能看到几何形状的痕迹,那么通常是顶点法线的问题。
虽然顶点法线是模型的重要数据之一,但在 DCC 中我们通常不会直接处理顶点法线。DCC 通常选择把顶点法线封装在另一套数据中的方法来调整顶点法线数据。比如在 3ds max 中,顶点法线是通过“光滑组”功能进行定义,在 Maya 中是通过“软硬边”功能定义。虽然这些功能字面上与顶点法线没有关系,但它们实际上都是在对模型的顶点法线数据进行修改。这也是为什么在 3ds max 和 Maya 中相互传导模型,模型的效果完全一样,“光滑组”和“软硬边”的数据却不能相互继承。因为“光滑组”和“软硬边”的最终结果都是顶点法线数据,顶点法线数据是可以通过模型文件在不同平台之间传导的,但“光滑组”和“软硬边”却分别是 3ds max 和 Maya 专有的数据和功能。
有些时候,你可能会遇到一些看上去“一片空白”的模型:它们在 DCC 中只会渲染出外轮廓的边缘,模型本身的结构则是一整块毫无凹凸变化的纯色块,即便赋予了新的材质,使用不同的渲染器也依然如此。我们前面提到了顶点法线主要影响模型的光影渐变关系,因此利用它也可以制作出完全没有光影渐变一片平色的效果。如此处理的模型,它的结构、UV 等其他模型数据都是没有问题的,唯一不同的是它的所有顶点法线矢量都被平均为同一个方向。如此处理的模型非常适合使用了 unlit 材质和手绘贴图的模型中,它完全抹去了模型因为自身结构产生的光影关系,杜绝了任何因为模型面数太少或布线不合理导致的光影渐变。那么,如果我们要为这样的模型恢复正常的顶点法线呢?绝大多数 DCC 都提供了重置顶点法线的功能,只需要在导出模型前调用相应的功能即可。
模型的类别
模型的构建有不同的方法,我们在游戏中常见的通常是多边形(Polyonal)模型。这类模型结构稳定,数据清晰,执行效率高。除此之外你可能也了解过使用数学运算构建的 NURBS 模型,使用光线追踪(Raymarching)形成的 SDF 模型等等。除此之外,我们还会经常接触到高模和低模的区别。简而言之,高模就是顶点和多边形数量较高的模型,一个模型的顶点和多边形可以多大几十甚至上百万。顶点和多边形的数量决定了模型能够表现如何精度的细节,因此高模可以呈现出非常丰富和真实的几何结构,而且这些结构都是通过真实存在的顶点实现的,在光影算法处理下每个细节都可以形成真实的光影效果。高模对运行时硬件的渲染能力要求较高,在没有强力硬件支持的情况下运行的帧数会非常低。
类似的,低模即顶点和多边形数量较少的模型,它可以只有几百甚至几十个顶点和多边形结构。因为它的结构简单,因此不可避免地会出现棱角分明的形态。相应的,它的运行效率非常高,即便是较老的硬件也能应对自如。不过,既然低模本身结构简单,因此不能表现丰富的几何细节。如果单纯用低模,通常只能表现较简单或者“偏卡通”的物体和结构。
为了解决这个问题,我们通常会依照低模的形态,再制作一个与之类似的高模,我们可以在高模上添加各种表面细节,再将高模的顶点法线数据信息存贮为一张贴图,通过 shader 赋予到低模上。这一技术让过去受限于运行性能而必须使用低模的游戏渲染焕然一新,虽然使用了法线贴图的低模的模型精度实际上并没有增加,但法线贴图能够在视觉上让低模也能够呈现丰富的细节,也因此拉开了我们常听到的“次世代”的序幕。
无论是高模、低模、还是“一片空白”的顶点法线模型,它们都能够通过 FBX 导入到 Cocos Creator 中,服务于各种各样的场景和项目。至于使用哪种类型的模型,就要看你的项目需求了。
最后需要说明的是,高模和低模虽然本质上只有顶点和多边形数量上的区别,但是并不能简单通过从高模上删减顶点和多边形的方式获得低模。模型的关键数据不仅仅是结构和形态,我们已经花了很多时长介绍顶点法线和 UV 的作用以及它们的重要性。删除顶点,对于模型来说是一个非常危险的做法,几乎一定会导致顶点法线和 UV 数据的混乱。除非你非常清楚自己在干什么,否则不要贸然尝试。
导入
导出模型之后,我们就可以和 DCC 道别了,下一步是将模型导入 Cocos Creator 中。操作很简单,把 FBX 文件从它的文件夹拖拽到 Cocos Creator 的资源管理器窗口中即可。
导入成功后,FBX 文件会以一个资源的形式出现在资源管理器中,点选它,可以在右侧的属性检查器中查看模型的相关属性。这里的属性都与我们导入的 FBX 直接相关,因此绝大多数情况下,我们不需要对它们有过多关注。
我们在前文提到过:除了模型,FBX 也可以导出材质和贴图。材质使用的贴图会封装在 FBX 当中,无法作为单独的资产访问。将材质和贴图通过 FBX 一同导出并不会简化你的工作流,反而可能会使简单的流程变得更麻烦,但如果这种情况无法避免,比如模型是客户提供的,那么可以通过资源管理器中模型资产下的子层级中找到 FBX 中封装的贴图。
导入模型后,模型只是成为资源加入了我们的项目目录当中,要使用模型,我们还需要把模型资产从资源管理器中拖拽到场景编辑器中,这样会将模型加入到当前的场景当中。
你可能会问:“为什么模型不能在我导入 Cocos Creator 的时候自动出现在场景中?”因为在一个项目里,你可能有不止一个场景,同一个模型可以用不止一次。无论是场景、模型、材质、贴图、还是脚本和代码,它们都是服务于整个项目的独立模块。它们之间可能依据我们的需要相互参照了数据来填充自身的数据,但从来没有神秘的代码在我们看不见的后台把它们绑定为天然的“一对一”对应关系。
模型加入场景之后,可以在左上角的层级管理器查看模型在场景中的层级关系。同理,场景中所有的节点(或者通俗地说:场景中所有存在的东西,无论它是干什么的)都会列在层级管理器中。因此,即便是在场景编辑器中看不到的节点,只要它是存在于场景中的,也可以在层级管理器中点选。同理,我们在准备模型的过程中为模型分配的层级关系,也会在层级管理器中以模型层级的子层级出现。
现在模型已经存在于当前场景中了,我们现在需要的是将它移动到场景中我们需要的位置。使用场景编辑器左上角的移动、旋转和缩放工具,我们可以自由决定模型在场景中的位置、朝向和拉伸。需要注意的是:场景中的模型是我们导入 FBX 的一个实例,因此无论我们如何折腾场景中的模型,都不会对导入的 FBX 产生任何影响。模型只是服务于当前场景的一个模块,我们对它所有的修改,无论是移动它的位置、修改它的材质、为它赋予不同的脚本,都是为了当前场景的需要,有其他需要时,我们可以从资源管理器中再拖拽一个新的实例,新的实例就像我们刚从 DCC 中导出时的状态一样,不会因为我们对之前实例的修改而改变。
所谓“3D”,首当其冲的数据当然是在三维空间中所处的位置。场景中的每一个节点都有它的位置、旋转和缩放参数,即便是一个没有任何功能的空节点也不例外。所以,当我们对模型使用移动、渲染和缩放工具时,实际上是对模型节点的位置、旋转和缩放参数进行修改。既然如此,在这些参数上手动输入相应的数值,完全可以得到相同的效果。
摄像机
除了模型,我们也可以为场景添加新的节点,在层级管理器的左上角点击加号按钮,可以看到可以添加到场景中不同节点的列表。这些节点都是有空间坐标参数的,也就是说,它们都有位置、旋转和缩放的参数,我们可以在场景中像模型一样使用移动、旋转和缩放工具摆放它们。
除此之外,引擎中的其他功能模块,比如粒子、Billboard、碰撞体等等,虽然它们与场景也有密切的联系,但是它们不能直接创建自己的 3D 节点,我们需要先创建一个新的空节点,然后将相应的功能模块以组件的方式添加到空节点上。之后,我们就可以通过移动空节点,来决定相应组件在 3D 空间中的摆放和位置。空节点还可以作为模型的父层级使用,我们可以多选若干个场景中的模型实例,将他们都放在同一个空节点的子层级上。这样相当于我们把这几个模型合并到了一个组当中,我们只需要移动父层级的空节点,就也可以对这几个模型当作有一个模型,共同进行变换的操作。
在自带空间坐标参数的 3D 节点当中,其中很重要的一个节点就是摄像机。摄像机的作用是指代用户在运行场景时所看到的视角,我们可以通过调整摄像机参数调整用户看到的镜像,或者为摄像机添加控制脚本,使用户能够自由控制自己在场景中的位置和观察角度。Cocos Creator 中的摄像机自带了模拟现实中摄像机功能的属性参数,如 FOV、光圈、快门、ISO 等等。除此之外,我们还可以选择摄像机的投影(Projection)模式。虽然叫“投影”,但实际上它与阴影没有任何关系,它的主要功能是修改摄像机所看到的物体的顶点坐标数据,从而形成美术上的透视效果(即“近大远小”)。通过它的取值,我们可以得到没有透视关系的正交视角,正交视角在游戏中也是颇为常见的。
透视视图 / 正交视图
Cocos Creator 场景中已经默认创建了一个摄像机节点。除此之外,我们还可以在层级管理器中点击左上角的加号按钮,选择 Camera,在场景中创建一个新摄像机。
我们也可以对摄像机进行移动、旋转和缩放的操作。在移动摄像机时,我们可以在场景编辑器右下角的预览窗口中看到从当前摄像机视角看到的景象,这可以帮助我们找到最理想的摄像机角度。
决定摄像机角度之后,我们可以点击场景编辑器上方中央的播放按钮,打开浏览器预览我们的场景。
材质
虽然我们在之前导出模型的时候,已经相应地导出了材质,但是可以看到,在 Cocos Creator 中导入的 FBX 资产的实例并没有材质。即便是在导出 FBX 时打包了材质所用的贴图,也是同样的结果。这是由于模型在 DCC 中使用的是 DCC 自有的着色器,而在 Cocos Creator 中渲染需要使用 Cocos Creator 的着色器。由于市面上流行的 DCC 非常之多,不同的的 DCC 都采用不同的着色器,不同的 DCC 搭配不同的渲染器又会引申出更多的着色器,因此想要完美地在引擎中还原 DCC 的渲染效果是非常困难的。
我们可以选中 FBX 资产,在属性检查器的 Material 标签下勾选 Dump Material。引擎会根据 FBX 中已经打包的数据和贴图(如果有)自动分配一个相应的材质。然而你会发现:即便如此,渲染的效果依然与我们在 DCC 中所见的不同。诚然:材质所使用的贴图在材质的表现中起到了关键的作用。但是仅仅是把贴图放在该放的地方,材质参数对贴图中不同数据的处理关系仍然是无法复制的。这也是为什么我们需要反复强调:模型、材质、贴图,这些都是帮助我们实现效果的模块,他们之间没有任何必然的、天然的、神秘莫测的联系。因此,我们不能机械地把一个模块搬到另一个模块上,寄希望于得到完全一样的效果。
模型的材质在哪里可以查看呢?我们可以在场景中选取模型,在右侧的属性检查器中可以找到 Materials 参数,点击箭头按钮,就能看到模型使用的所有材质列表。为了给模型赋予材质,我们首先要创建一个新的材质资产。在资源管理器中点击加号按钮,选择 Create / Material,就可以创建一个新的材质文件。
正如场景中的模型是导入的 FBX 的实例,材质也其使用的着色器的一个实例。通过材质,我们可以为着色器中所写的各种参数进行赋值,贴图就是值的一个表现方式,将贴图投射到模型的 UV 上之后,相当于我们告诉着色器渲染时为模型的每一个像素点应该赋予怎样的值。这个值可以是颜色,也可以是一个简单的数字,着色器中不同的算法会依照这些数字,计算各自的光影输出效果。因此,贴图只是一个赋值的方式,它本身并没有神秘之处。
材质设置完成之后,点击右上角的勾号按钮,保存对材质文件的修改。然后,我们只需要把刚保存的材质文件拖拽到模型的 Materials 参数中,覆盖其中已有的文件即可。
模型的处理已经差不多了,我们可以先保存一下我们的进度。在顶部菜单栏选择 File / Save Scene 或 Save as 可以保存当前的场景。保存好的场景也会作为一个资产出现在资源管理器窗口当中。双击场景即可打开它。
环境
在前文中,我们已经处理好了模型从 DCC 到引擎的转换。下一步我们需要关注的是场景在引擎内部应该如何呈现。模型只是为了我们的场景服务的,模型处理好了,我们的工作才进行了一半。
了解 3D 工作流的同学都知道:计算机所理解的 3D 空间只是笛卡尔坐标系中的一堆坐标数据,在这个空间中没有天空、没有大地,没有任何物体,甚至没有光。要想让这个空间丰富起来,必须把相应的东西全部做出来。虽然 DCC 可以帮助美术把世间万物都建模出来,但每个场景都要从日月星辰开始建模还是太费时费力了。所以,我们会使用一张从现实生活中采集的 HDRI 贴图,把我们的 3D 场景包裹起来。就像绘画中的背景一样,HDRI 贴图将为我们呈现这个世界中必须存在但于我们场景的关联不大的部分。这样我们就可以把注意力集中到表现我们的主体上。
那么,场景中是不是必须要 HDRI 贴图呢?答案当然是否定的。再次重申:所有的模块都是为了我们的场景服务的,我们当然可以选择弃用一部分模块,代价就是会导致一部分内容和功能的缺失,但如果缺失的这部分恰好是与我们所期望的效果不相关的,那么弃用当然是无伤大雅的。
我们的场景中虽然有模型了,但周围的环境还是一片空白。我们首先可以给场景一个大的背景。在这里我们需要一张 HDRI 贴图。在 Cocos Creator 中,我们只能使用 HDR 格式的 HDRI 贴图,如果你的贴图是 EXR 格式的,只需要用 Photoshop 快速转存一下,就可以正确导入了。
和模型一样,我们只需要把 HDR 文件拖拽到资源管理器窗口中,就可以导入 HDRI 贴图了。
要在场景中使用我们刚导入的 HDRI 贴图,我们只需要在层级管理器中选择根层级,然后在属性检查器的 Skybox 标签下,将 HDRI 贴图拖拽到 ENV Map 参数上,确保 Enabled 是勾选的。除此之外,我们还需要再层级管理器中选择场景的摄像机,再 Clear Flags 参数的下拉菜单中,选择 Skybox。否则 HDRI 贴图只会在编辑器中显示,在运行时是不会显示的。
与之前相比,我们的场景已经有大概的感觉了。在 Ambient 标签下我们可以调整 HDRI 贴图的曝光度,在这里我们也可以调节天空漫反射和地面反射的颜色,这两个颜色根据使用的 HDRI 贴图已经为我们自动生成了,但如果我们觉得模型和背景的融合还是不够理想,也可以自己设置颜色。
灯光
我们前面提到过:3D 空间中没有创建的东西就是不存在的,光也是如此。如果没有创建光源,3D 空间中的一切物体都是不可见的。无论模型多么复杂,着色器多么高级,我们能看到的只有一片漆黑。在 Cocos Creator 中,添加了 HDRI 贴图后会启用天空漫反射和地面反射,因此我们的场景中不会出现一片漆黑的情况。然而,为了真正意义上把我们的场景看清楚,我们还需要添加光源。
Cocos Creator 在场景中默认添加了一个方向光。和其他节点相同,我们也可以对它的位置、旋转和缩放进行操作,并且在属性查看器中修改它的属性参数。
在 Cocos Creator 中,灯光分为 3 种类型:方向光、球形灯和聚光灯。
关于这 3 种常用的光源,我们已经反复介绍过了。那么与室外场景相比,室内场景的光源是否有不同呢?
方向光:方向光的作用方式类似于现实中的太阳光,但这并不意味着方向光只能用作太阳光,或者在阴天下雨的场景中方向光就不需要出场了。任何有一定方向指向的,需要遍布整个场景的光源,都可以使用方向光。另外,鉴于方向光能够无差别照明整个场景,方向光也可以用作为整个场景的光照“打底”。比如夜间的室内场景,无论室内是否有其他的光源,都可以用一个方向光,赋予较低的强度和偏冷、偏暗的颜色,为整个场景奠基夜晚天光的基调。
球形灯:球形灯没有方向的指向,并且只能照亮一定范围。因此非常适合需要照亮特定区域,或者需要形成大的明暗渐变的需求。无论是室内还是室外,球形灯都是适用的。但因为没有特定的方向,球形灯非常不适合产生投影。即便是大致的光照效果已经确立了,也可以有的放矢地再加入几个球形光,目的是给已有的光照添加更多的光照强度差异,或者“渐变”的差异。
聚光灯:聚光灯主要适用于需要形成投影,或者在有明确的照亮范围的情况下。聚光灯的效果更集中在被照亮的部分,在光源的部分容易有所欠缺,因此可以再添加一个球形灯到聚光灯光源的位置,两者相互配合,能够得到更自然的效果。
阴影
阴影与光照通常是伴随存在的,毕竟没有阴影,我们很难感受到光照的存在。阴影可以伴随光照,在每一帧渲染时即时计算得来,或者将阴影形成的明暗关系烘培在一张贴图上,以贴图的形式叠加在材质上。前者得到的阴影每一帧更新,可以伴随着动态的光源形成动态的阴影,但会消耗额外的渲染资源。后者的阴影已经烘培为贴图,无法每帧更新,但可以在几乎不牺牲画面效果的前提下,明显提升运行时的渲染效率。在 Cocos Creator 中,我们可以为光源开启动态阴影,或者适用引擎内置烘培器烘培静态阴影贴图。
在场景中开启动态阴影,需要对环境、模型和光源三者进行分别设置:
首先是模型,在层级管理器中选择模型网格所在的层级,在 Dynamic Shadow Settings 标签下,将 Shadow Casting Mode 设为 on。
其次是光源,选取光源,在 Dynamic Shadow Settings 标签下勾选 Shadow Enabled。
最后是环境,在层级管理器中选择场景的根层级,在 Shadows 标签下勾选 Enabled。
Cocos Creator 中有 2 种动态阴影类型,可在 Shadows 标签的 Type 参数中选择。
Planar 是一种简单的阴影类型,它只能投射在正对 X, Y 和 Z 轴的方向上。如果接受阴影的物体凹凸不平,或者呈一定角度的旋转的话,则阴影会穿模而过。与之相关的设置包括决定阴影颜色的 Shadow Color 和决定投射平面方向的 Normal 参数。
Shadow Map 是一种更多细节的阴影类型,它可以投射到任何表面的模型上。获得理想的阴影效果需要多个参数的共同配合:
首先,选择场景根层级,在 Shadows 标签下调整 Shadow Map Size,确保有足够的像素数量绘制阴影。如果在场景中看不到阴影,或者阴影呈若干矩形状,那么这是首先需要调整的参数。
其次,选择光源,在 Dynamic Shadow Settings 标签下把 Shadow Pcf 选取为 Soft 2X 或 Soft 4X,Shadow Map 默认会形成锯齿状的边缘,调整该设置可以开启相关模糊算法,柔滑过于锋利的阴影边缘。
再次,仍然选择光源,在 Dynamic Shadow Settings 标签下调整 Shadow Distance 的数值,以得到满意的阴影细节,由于有 Shadow Pcf 边缘模糊算法的作用,阴影不需要过于丰富的细节,达成较为满意的效果之后,可以相应地降低 Shadow Map Size 的数值以节省性能。
最后,调整 Shadow Saturation 的数值,这相当于阴影的半透明度。
烘培
静态光照贴图将当前场景的光照和阴影,依据所有模型的第二套UV,烘培到若干张贴图上,再将贴图叠加到材质上,通过材质的颜色输出来呈现光影效果。烘培静态光照贴图之后,场景中的光影关系将不再受光源的影响。光影关系将完全由烘培的贴图呈现。材质和环境的参数属性仍然能够照常访问和修改,但如果修改光源的参数,场景中的光照将不再符合静态光照贴图所烘培的结果,因此需要重新烘培静态光照贴图。
在 Cocos Creator 中,烘培静态光照需要对环境、模型、光源和材质四者进行分别设置:
首先是模型,在层级管理器中选择模型网格所在的层级,在 Lightmap Settings 标签下,勾选 Bakeable, Cast Shadow 和 Receive Shadow。
Bakeable 的作用是允许该物体加入烘培的队列,如果不予设置,该模型将继续使用动态光照。
Cast Shadow 是模型是否投射阴影的开关。
Receive Shadow 是模型是否允许其他模型的投影投射到自己身上的开关。
Lightmap Size 决定了该模型烘培出的贴图大小,关于该参数的数值,我们会在后面加以展开。
其次是光源,选取任意光源,在 Static Settings 标签下勾选 Bakeable 和 Cast Shadow。
然后是环境,在层级管理器中选择场景的根层级,在 Shadows 标签下勾选 Enabled,这一项操作与动态阴影是相同的,当然,如果不希望场景中产生阴影,可以跳过这个设置。
最后是材质,在资源管理器中选取在场景中使用的材质,点选 Use Second UV。
设置完成之后,在编辑器顶部的菜单栏选择 Project / Lightmap,打开烘培器窗口。
这里的绝大多数参数都不需要我们修改。我们需要注意的是:
MSAA:这将影响烘培结果的精度,同时会明显增加烘培所需要的计算时间,提高该数值可以解决烘培模糊、有脏污等质量问题。
Resolution:顾名思义,这决定了烘培出的贴图的分辨率。
AO Color:默认的颜色太浅,烘培的结果可能不够明显。初次烘培的时候可以直接选择纯黑色,随后根据调整的需求选择不同的颜色。
设置完成后,点击 Lightmap Generate,在弹出的对话框中选择烘培的输出路径(默认项目目录下的 assets 文件夹即可),开始烘培。
烘培完成后,我们可以在烘培器的 Baked 标签下,查看烘培出的静态光照贴图预览。
我们可以看到,静态光照贴图是根据模型的第二套 UV,以不同的大小分别烘培,再集成在同一张贴图中的,那么,每个模型在一张静态光照贴图中所占的比例是什么决定的呢?
打开烘培的静态光照贴图,它的大小为 1024 × 1024。对比我们在烘培器中 Resolution 的数值 1024,我们会发现:烘培器会依照模型的 Lightmap Size 数值大小,将烘培出来的静态光照绘制到一张分辨率为烘培器 Resolution 数值的贴图上。如果模型的 Lightmap Size 与烘培器的 Resolution 正好相等,那么烘培器会为这个模型单独烘培一张贴图;如果模型的 Lightmap Size 比烘培器的 Resolution 小,那么该模型烘培出的静态光照会只占到一张贴图的一部分,剩下的部分会预留给其他模型的静态光照贴图。
由此,我们可以总结出:模型的 Lightmap Size 参数,一定要比烘培器的 Resolution 参数小,并且最好是 POT 数值(64,128,256,512等),这样能在保证烘培效果的同时,最大程度利用每张静态光照贴图的空间。
烘培了静态光照之后,光照的明暗关系会以贴图的形式,叠加到每个材质上。即便我们修改材质,也不会影响已经烘培好的静态光照贴图。只有当我们修改了光源的颜色、强度、方向等参数时,才需要重新烘培静态光照。
当我们不需要静态光照,需要返回到未烘培前动态光照的状态下时,可以回到烘培器面板,在 Baked 标签下点击 Lightmap Clear。
当我们已经烘培了静态光照,但是发现场景过亮或是过暗时,需要重新调节光源的参数,光源修改后则需要重新烘培光照。如此来回调整光源参数,再重复烘培,会消耗很多时间。我们最终看到的场景的明暗程度,不只由光源决定。摄像机同样会影响场景的整体亮度。
当我们对现有光源的明暗关系大体满意,只需要整体强弱的微调的时候,可以选择摄像机,调整摄像机的光圈、快门和 ISO 参数,就可以在不需要重复烘培静态光照的情况下调节场景的亮度。
反射
现在我们场景的基本元素都已经就位了。我们仍需要为场景开启最后一个开关。
目前我们的模型虽然已经有了材质,但是材质的表现仍有一些欠缺。尤其是金属这类材质,在我们的场景中表现仍然比较奇怪。
回到场景的根层级,在 Skybox 标签下 ENV Lighting Type 的下拉菜单中,选择 Diffuse Map with Reflection。
这个设置可以将场景的环境光照影响添加到所有的材质中,让模型和场景更加自然地融合。
除此之外,点击 Reflection Convolution 参数的 Bake 按钮,生成一张反射卷积图,将为所有的材质添加反射效果。反射的强度可以通过材质本身的 Roughness 参数调节。
需要注意的是,这两项功能都基于我们为场景加入的 HDRI 贴图,如果没有为 ENV Map 参数赋予贴图,或者贴图本身过于简单缺乏细节,则得不到相应的效果。
反射效果由场景的 HDRI 贴图决定
关于 3D 场景搭建的各种坑就先介绍的这里,相信大家在搭建自己的场景时会有一个更加明晰和顺畅的体验。期待看到大家的作品。
此外,Cocos 官方布道团队正在整理一份“Cocos Creator 3D 资产工作流入门指南”,带大家更全面、深入地了解引擎的 3D 资产使用方法,高效构建 3D 资产。该材料预计很快就将和大家见面,敬请期待!