按照本人的使用顺序,Unity是我用过的第五个引擎了,前面几个分别是Irrlicht,Ogre,Cocos2dx和NeoX。Irrlicht和Ogre基本是同时代的产物,但是功能和使用普及率Ogre都要比Irrlicht高出很多。Ogre1.8版本在设计模式和工具链上面都在当时拥有很强的竞争力。但基于C++作为开发语言和大量使用的设计模式、庞大的代码量对于需要修改引擎做定制开发的模式,以及手动编写和调试shader语言来讲,Ogre的门槛算是挺高了。加上漫长的版本更新,逐渐使这个引擎淡出了游戏开发者的视野。Cocos2dx则正好赶上了移动互联网的浪潮,在当时unity尚不完善的情况下,基于opengles的跨平台UI库,几乎成了轻量级2d游戏开发的首选。cocos2dx给大部分基于C++开发网络游戏的程序员提供了可热更新的脚本开发机制,以及方便的ui动画和多种图片格式支持,但对于3d游戏和pc端的支持则十分欠缺,基本局限于一个UI库,虽然后来加入了3d功能。Neox引擎作为游戏大厂的自研引擎,在多线程渲染,跨平台,物理引擎,骨骼动画,材质系统,摄像机控制,工具链上面,相对于前面几款引擎都有非常大的优势,毕竟要支持猪场每年几十款游戏的开发,其开发效率非常之高。但作为一款自研引擎,细节不方便多说,也没有具体的使用意义(买不到)。作为本专题的主角Unity引擎来说,unity是一款入门成本最低,精通成本建立在懂渲染的基础上属于中等的位置。它自带的工具比较齐全,资源市场插件丰富,跨平台性能强,编辑器功能强大,但是相较于虚幻,它没有源代码,调试成本非常非常非常高,自带材质效果不佳。
以上是本人总结的几款引擎特点。作为目前公司核心项目的主程,我们深度利用了Unity引擎的多种功能和渲染特性,在没有源码和文档的情况下,通过积累的引擎开发经验,解决了一个个Unity隐藏的深坑,本专题对于希望了解Unity引擎内部机理以及使用引擎高级特性完成酷炫效果的同学很有学习意义。也欢迎行家里手一起交流。本专题需要一定的unity基础,掌握基本的Frame Debugger使用技巧,基本的shader原理,AssetBundle打包机制等。
你所不知道的Unity功能和背后的原理(一)
预加载着色器(Preloaded Shaders)用途和使用方法
预加载着色器是Unity项目设置(Project Settings)->图形选项(Graphics)里面的一个选项。这个功能有什么用呢?其实它对于渲染的正确性和内存优化都具有重要的非常意义!首先看一下美术制作的场景图
可以看到在房顶缝隙处存在的阴影(AO),对于真实感的烘托还是很有好处的。假如我们要让房顶贴图可更换,并且实现更复杂的光照模型,我们一般会把美术团队用标准材质球(Standard)制作的房顶材质替换成效果更好的那个材质球,这个过程通常是把材质球打包成assetbundle然后加载到内存,在进行替换。好的,替换后发现房顶变成了下图
咦?房顶缝隙的阴影去哪里了?不急,使用FrameDebugger分别查看一下两种情况下的渲染状态
对比两种渲染状态,我们发现,材质球的Keywords存在差异,有阴影的渲染打开了LIGHTMAP_ON相关的关键词。关于关键词的说明,大家可以阅读文档,简单来说就是关键词用来打开shader里面的宏开关,可以动态增加或者关闭shader的部分语句,对于标准材质球的Standard着色器,这是一个含有几千个变体的着色器,你可以只打开点光源的光照模型,那么它只处理点光源的光照,依次类推。知道这一点以后,我们就能分析出,缺少阴影正是因为没有打开LIGHTMAP_ON这个光照图选项引起的。场景制作同学制作了带ao的光照图,但是我们在更换材质球后,并没有打开这个关键词。但是即便我们手动调用EnableKeyword依然没有LIGHTMAP_ON关键词,为什么呢?因为我们打包材质球的时候,并没有制定LIGHTMAP_ON选项,所以AssetBundle包里的shader实际是没有包含光照图的变体!如果想包含进去,一个简单粗暴的方法是下面这样
这个操作实际是永远包含Standard着色器的所有变体,貌似问题解决了,但是在移动端频繁崩溃,一看内存增加了一两百兆。我们并不会用到Standard着色器的所有变体,那么如何只包含我们需要的变体呢?答案就是预加载着色器!怎么使用呢?首先我们随便打开一个场景,改变Preloaded Shader的size为1,点击save to asset,它会把当前场景用到的变体的关键词保存到一个变体文件,我们选择这个文件,然后双击编辑它
在我们使用的那个着色器下面点击加号,增加关键词
点击dirlightmap_combined
再点击lightmap_on,最后在下面选择需要的关键词组合,点击Add Selected variants,
最后逐个添加所需的全部关键词的变体,然后使用这个配置文件作为预加载着色器的配置,重新生成材质球的AssetBundle文件,放到项目里。当我们再想更换屋顶材质球时,AO已经能正常显示了。
同时也不会因为把这个Standard着色器全部包含进去,导致内存过大而经常崩溃了:) 是不是很棒!
最后细心的你也会发现,我们打包出来的材质球,在不打开关键词和打开关键词的时候,文件体积也是有变化的。包含AO的材质球,体积更大,是因为它包含更多的GPU指令
这一点进一步验证了着色器变体对材质球的真实影响,即使你在代码里显式调用了EnableKeyword,但是AssetBundle里根本没打包进响应的着色器变体,也是不起任何作用的。
总结:预加载着色器适用于需要精准控制着色模型的渲染环境,对于控制文件体积,优化内存大小和提高渲染效率具有重要意义!