unity 为什么一定要缓存unity 删除gameobjectt应用

&img src=&/50/v2-eb24dbad282d6f1fda047_b.jpg& data-rawwidth=&850& data-rawheight=&512& class=&origin_image zh-lightbox-thumb& width=&850& data-original=&/50/v2-eb24dbad282d6f1fda047_r.jpg&&&h2&0x00 前言&/h2&&p&在很长一段时间里,Unity项目的开发者的优化指南上基本都会有一条关于使用GetComponent方法获取组件的条目(例如14年我的这篇博客&a href=&/?target=http%3A///84323/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&《深入浅出聊Unity3D项目优化:从Draw Calls到GC》&i class=&icon-external&&&/i&&/a&)。有时候还会发展为连一些Unity内部对象的属性访问器都要小心使用的注意事项,记得曾经有一段时间我们的项目组也会严格要求把例如transform、gameobject之类的属性访问器进行缓存使用。这其中的确有一些措施是有道理的,但很多朋友却也是知其然而不知其所以然,朦胧之间似乎有一个印象,进而成为习惯。那么本文就来聊聊Unity优化这个题目中偶尔会被误解的内容吧。&/p&&br&&br&&h2&0x01 来自官方的建议&/h2&&p&本文主要是关于Unity脚本优化的,而脚本和引擎打交道的一个常见情景便是使用GetComponent之类的方法, 接触过Unity的朋友大都知道要将GetComponent的结果进行缓存使用。不过很多人的理由是:&/p&&blockquote&&p&使用GetComponent会造成GC,从而影响效率。&/p&&/blockquote&&p&所以从Unity官方的手册来寻找关于GetCompnent的线索是最好的途径。的确,2011年的3.5.3版本的官方手册就已经建议减少使用GetCompnent方法来获取组件了,同时建议我们使用变量缓存获取的组件。&/p&&blockquote&&p&Reduce GetComponent Calls&br& Using GetComponent or built-in component accessors can have a noticeable overhead. You can avoid this by getting a reference to the component once and assigning it to a variable (sometimes referred to as &caching& the reference).&/p&&/blockquote&&p&但是,我们可以发现手册上只说了频繁的调用GetComponent会导致CPU的开销增加,但是并没有提到GC的问题。所以,为了验证GetComponent到底会导致哪些性能上的问题,我们可以做几个小测试。&/p&&br&&br&&h2&0x02 和GC无关的性能优化&/h2&&p&众所周知,GetComponent有三个重载版本,分别是:&/p&&ul&&li&GetComponent()&/li&&li&GetComponent(typeof(T))&/li&&li&GetComponent(string)&/li&&/ul&&p&所以,测试的第一步就是先确定一个效率最高的重载版本,之后再去检查它们各自引起的堆内存分配。&/p&&h4&“效率之王”&/h4&&p&为此,我们在&strong&5.X版本的Unity&/strong&中准备一个空白的场景并实现一个简单的计时器,之后就可以开始测试了。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&using S
using System.D
/// &summary&
/// 简易的计时类
/// &/summary&
public class YiWatch : IDisposable
#region 字段
private string testN
private int testC
#endregion
#region 构造函数
public YiWatch(string name, int count)
this.testName =
this.testCount = count & 0 ? count : 1;
this.watch = Stopwatch.StartNew();
#endregion
#region 方法
public void Dispose()
this.watch.Stop();
float totalTime = this.watch.ElapsedM
UnityEngine.Debug.Log(string.Format(&测试名称:{0}
总耗时:{1}
单次耗时:{2}
测试数量:{3}&,
this.testName, totalTime, totalTime / this.testCount, this.testCount));
#endregion
&/code&&/pre&&/div&&p&自定义的组件TestComp,以及我们的测试代码,每一个方法会被调用1000000次以便于观察测试结果:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&
int testCount = 1000000;//定义测试的次数
using (new YiWatch(&GetComponent&&&, testCount))
for(int i = 0; i & testC i++)
GetComponent&TestComp&();
using (new YiWatch(&GetComponent(typeof(T))&, testCount))
for(int i = 0; i & testC i++)
GetComponent(typeof(TestComp));
using (new YiWatch(&GetComponent(string)&, testCount))
for(int i = 0; i & testC i++)
GetComponent(&TestComp&);
&/code&&/pre&&/div&&p&运行的结果如图(单位ms):&img src=&/v2-f52a9bed8debc3598cc4_b.png& data-rawheight=&253& data-rawwidth=&1232& class=&origin_image zh-lightbox-thumb& width=&1232& data-original=&/v2-f52a9bed8debc3598cc4_r.png&&&br&我们可以发现在Unity 5.x版本中,泛型版本的GetComponent&&的性能最好,而GetComponent(string)的性能最差。&/p&&p&做成柱状图可能更加直观:&/p&&img src=&/v2-58a69a15cebcaf66c8abb_b.png& data-rawheight=&1107& data-rawwidth=&1141& class=&origin_image zh-lightbox-thumb& width=&1141& data-original=&/v2-58a69a15cebcaf66c8abb_r.png&&&p&接下来,我们来测试一下我们感兴趣的堆内存分配吧。为了更好的观察,我们把测试代码放在Update中执行。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&void Update()
for(int i = 0; i & testC i++)
GetComponent&TestComp&();
&/code&&/pre&&/div&&p&同样每帧执行1000000次的GetComponent方法。打开profiler来观察一下堆内存分配吧:&/p&&p&&img src=&/v2-44a9c9a5f37a2cd3f02eb_b.png& data-rawheight=&1092& data-rawwidth=&1987& class=&origin_image zh-lightbox-thumb& width=&1987& data-original=&/v2-44a9c9a5f37a2cd3f02eb_r.png&&&br&我们可以发现,虽然&strong&频繁调用GetComponent时会造成CPU的开销很大,但是堆内存分配却是0B&/strong&。&/p&&p&但是,我和朋友聊天偶尔聊到这个话题时,朋友说有时候会发现每次调用GetComponent时,在profiler中都会增加0.5kb的堆内存分配。不知道各位读者是否有遇到过这个问题,那么是不是说GetComponent方法有时的确会造成GC呢?&/p&&p&答案是否定的。&/p&&p&这是因为朋友是在&strong&Editor中运行&/strong&,并且&strong&GetComponent返回Null&/strong&的情况下,才会出现堆内存分配的问题。&br& 我们还可以继续我们的测试,这次把TestComp组件从场景中去除,同时把测试次数改为100000。我们在Editor运行测试,可以看到结果如下:&br&&img src=&/v2-0a9c5451dfb0de2aaae0bd49428caea3_b.png& data-rawheight=&684& data-rawwidth=&1341& class=&origin_image zh-lightbox-thumb& width=&1341& data-original=&/v2-0a9c5451dfb0de2aaae0bd49428caea3_r.png&&&br&10000次调用GetComponent方法,并且返回为Null时,观察Editor的Profiler,可以发现每一帧都分配了5.6MB的堆内存。&/p&&p&那么如果在移动平台上调用GetComponent方法,并且返回为Null时,是否会造成堆内存分配呢?&/p&&p&这次我们让这个测试跑在一个小米4的手机上,连接profiler观察堆内存分配,结果如图:&/p&&img src=&/v2-e55e9fbddd11ac86a86ea803e038be4a_b.png& data-rawheight=&703& data-rawwidth=&1363& class=&origin_image zh-lightbox-thumb& width=&1363& data-original=&/v2-e55e9fbddd11ac86a86ea803e038be4a_r.png&&&p&可以发现,在手机上并不会产生堆内存的分配。&/p&&h4&Null Check造成的困惑&/h4&&p&那么这是为什么呢?其实这种情况只会发生在运行在Editor的情况下,因为Editor会做更多的检测来保证正常运行。而这些堆内存的分配也是这种检测的结果,它会在找不到对应组件时在内部生成警告的字符串,从而造成了堆内存的分配。&/p&&blockquote&&p&We do this in the editor only. This is why when you call GetComponent() to query for a component that doesn’t exist, that you see a C# memory allocation happening, because we are generating this custom warning string inside the newly allocated fake null object.&/p&&/blockquote&&p&所以各位不必担心使用GetComponent会造成额外的堆内存分配了。同时也可以发现只要不频繁的调用GetComponent方法,CPU的开销还是可以接受的。但是频繁的调用GetComponent会造成显著的CPU的开销的情况下,各位还是对组件进行缓存的好。&/p&&h4&属性访问器的性能&/h4&&p&既然聊了GetComponent方法的性能,接下来我们可以继续来聊聊和GetComponent功能有些类似的,Unity脚本系统中的一些属性访问器的性能。&br& 我们最常见的属性访问器大概算是transform和gameObject了吧,当然,如果使用过4.x版本的朋友应该还会知道rigidbody、camera、renderer等等。但是到了5.x时代,除了gameObject和transform之外的属性访问器都已经被弃用了,相反,5.x中会使用 GetComponent&&来获取它们:&/p&&img src=&/v2-6fab960a524b7_b.png& data-rawheight=&721& data-rawwidth=&1642& class=&origin_image zh-lightbox-thumb& width=&1642& data-original=&/v2-6fab960a524b7_r.png&&&p&所以从4.x升级到5.x之后,这些访问器就无法使用了,所以升级引擎时各位可以关注一下自己的代码中是否有类似的问题。&/p&&p&好了,我们接着在测试中加入使用访问器获取Transform组件的效率:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&
using (new YiWatch(&transform&, testCount))
for(int i = 0; i & testC i++)
transformTest = this.
&/code&&/pre&&/div&&p&运行1000000次,结果如下(单位ms)&/p&&img src=&/v2-a47eeea900cb1a2d757273_b.png& data-rawheight=&144& data-rawwidth=&944& class=&origin_image zh-lightbox-thumb& width=&944& data-original=&/v2-a47eeea900cb1a2d757273_r.png&&&p&单次的耗时是0.000026ms,性能要远好于调用GetComponent&&方法,所以是否缓存类似gameObject或者transform这样的属性访问器似乎对性能的优化帮助不大。当然写代码和个人的习惯关系很大,如果各位早已习惯缓存这些属性访问器自然也是不错的选择。&/p&&br&&br&&h2&0x03 总结&/h2&&p&通过以上测试,我们可以发现:&/p&&ul&&li&频繁的调用GetComponent方法会造成CPU的开销,但是对GC几乎没有影响。&/li&&li&Profiler不要用来分析Editor中运行的项目,由于一些引擎内部的检查会导致结果出现较大偏差。&/li&&li&5.X版本中GetComponent&&的性能最好。&/li&&li&使用属性访问器来访问一些内建的属性例如transform的性能已经可以让人接受了,并不一定非要缓存这些属性。&/li&&li&5.X版本删掉了很多属性访问器,基本上只保留了gameObject和transform。&/li&&/ul&&p&最后需要说明的是,上述的测试发生在5.X版本的Unity中。如果使用4.x版本可能会有些许不同,例如在4.X版本中,GetComponent(typeof)的性能可能要好于GetComponent&&,而且能够直接使用的属性访问器也更多,各位可以自己进行测试。&/p&&br&&br&&p&-华丽的分割线-&/p&&br&&p&欢迎大家关注我的公众号慕容的游戏编程:chenjd01&/p&&img src=&/v2-ce71b8c86ed04d665d784dcb98094d43_b.png& data-rawheight=&400& data-rawwidth=&400& class=&content_image& width=&400&&&br&&p&最后打个广告,欢迎支持我的书&a href=&/?target=https%3A///.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&《Unity 3D脚本编程》&i class=&icon-external&&&/i&&/a&~&/p&&img src=&/v2-5dd4b158fa2e980fcc840ba859d10e10_b.jpg& data-rawheight=&398& data-rawwidth=&296& class=&content_image& width=&296&&
0x00 前言在很长一段时间里,Unity项目的开发者的优化指南上基本都会有一条关于使用GetComponent方法获取组件的条目(例如14年我的这篇博客)。有时候还会发展为连一些Unity内部对象的属性访问器都要小心使…
&img src=&/50/v2-4bf0641bf6aaef207b6ae1c90a44404f_b.png& data-rawwidth=&799& data-rawheight=&499& class=&origin_image zh-lightbox-thumb& width=&799& data-original=&/50/v2-4bf0641bf6aaef207b6ae1c90a44404f_r.png&&&p&这是侑虎科技第208篇原创文章,作者陈霈霖,欢迎转发分享,未经作者授权请勿转载。如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ群)&/p&&p&同时,作者也是U Sparkle活动参与者哦,UWA欢迎更多开发朋友加入 &a href=&/?target=https%3A///%23activity-us& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&U Sparkle开发者计划&i class=&icon-external&&&/i&&/a&,这个舞台有你更精彩!&/p&&h2&&strong&一、变味的易用性&/strong&&/h2&&p&Unity是一款主打易用性的游戏引擎。它支持开发者可以低门槛、快速容易地使用Unity开发游戏,所以Unity在最初以类似JavaScript、类似Python的脚本语言作为主要开发语言。目前,Unity几乎已经成为手游开发的首选方案了,其自身的功能和各种围绕它的技术生态日趋完善,C#语言也当仁不让地成为首选开发语言。&/p&&p&但是,她的骨子里依旧还是那个标榜易用性的游戏引擎,由于她“易用”这个特点,开发团队如果在开发之初按照Unity标准的易用方式来制作游戏,到了后期就免不了出现各种各样的坑,需要花费大量的时间去重新重构和代码维护。最典型的情况就是:&/p&&blockquote&&p&&b&一个开头快速功能迭代开发的游戏,直到中后期才萌生热更新的需求,而在Unity里做资源热更(AssetBundle)和代码热更(Lua)是一个不小的工作,需要耗费相当多的精力。 怎么办?加班或延期呗。&/b&&/p&&/blockquote&&p&回想使用Unity这5年的搬砖经历,Unity里最令我沮丧的功能是它的AssetBundle——不论是打包还是加载,都要花费开发人员大量的时间成本去研究和应用。你以为它可以像Resource.Load那样轻松加载资源?不,它还要手工码代码打包;你以为打包完了就能直接加载调用了?不,它还要在加载时注意处理依赖关系。&/p&&h2&&strong&二、从Unity的资源方式说起&/strong&&/h2&&p&如前面所说,Unity是一款主打易用性的游戏引擎。它的资源打包方式有两种。&/p&&p&&em&&u&Unity资源方式:&/u&&/em&&br&&img src=&/50/v2-756a406cdd6dad85cb686df1_b.png& data-rawwidth=&348& data-rawheight=&272& class=&content_image& width=&348&&&/p&&p&一种是使用Resource模式,这种模式更像是端游时代的资源打包方式,把所有的游戏资源打包成一整块文件,然后通过索引文件去记录索引,各个资源文件散落在不同文件里的不同索引位置。比如说,暴雪公司的魔兽世界、魔兽争霸的mpq文件就是这样的一个思路。&/p&&p&这种方式完全体现了Unity的易用性,比如一个图片,不管它是PNG、TGA还是PSD,只要丢到Unity里,都会被统一转化成Unity的Texture格式,简单、傻瓜,非常适合小游戏的开发。但是它也有缺点,就是每一次发布最终编译包的时候,都会重新对资源进行一次打包,速度非常慢,这也反映出它的致命问题: &strong&由于资源全部堆砌在一块了,要替换其中的资源就变得困难——难以进行资源热更新。&/strong&&/p&&p&另一个种模式,AssetBundle模式。相比较之下,这个模式看上去就像后来迭代版本的时候加出来的一个功能——基于原有Resource模式的不足,提供一个对资源控制更自由的方式。&/p&&p&在Resource模式中,开发者几乎不用操心他们资源管理的技术细节。直接使用编辑器进行资源编辑,用完以后直接打包最终程序就可以了。而Asset Bundle模式则需要自己进行资源的打包加载管理。&/p&&p&&u&&em&Unity AssetBundle打包方式进化&/em&&/u&&br&&/p&&img src=&/50/v2-3fa9b9834373ebbe122e6ccc82f2c137_b.png& data-rawwidth=&544& data-rawheight=&292& class=&origin_image zh-lightbox-thumb& width=&544& data-original=&/50/v2-3fa9b9834373ebbe122e6ccc82f2c137_r.png&&&p&在Unity 5.x之前的版本——3.x和4.x中,AssetBundle是一个非常难用的功能。你不仅要操心资源的管理规范,还要写大量的代码控制它们的打包,更要命的是,除了打包速度慢,还有数之不尽的坑。相信不少开发团队都在AssetBundle上花费过不少精力和时间。&/p&&p&我经历过了4个不同的中大型游戏规模的Asset Bundle打包,躺过其中相当多的坑,逐渐开始掌握它的特征。回头仔细一想,其实很多坑完全是没有必要的,但是前提是在设计之初就给予高度重视,提炼统一的方案,这样才不会导致后期失控的状况。&/p&&p&在Unity 5.x里,官方推出了一个全新的打包方案,对于程序员而言,可以仅用一行代码打包所有的AssetBundle。尽管它里面还是有一些坑,可是却大大减轻了开发团队的工作。打包方式变简单了,大家可以集中精力研究怎么更好地去把这些AssetBundle加载起来了。&/p&&br&&h2&&strong&三、更好的方式去加载AssetBundle&/strong&&/h2&&p&AssetBundle加载资源的API非常简单,核心其实只是两类函数,一类同步,一类异步。&br&&img src=&/50/v2-8df4d3f01a66ddf3444d94_b.png& data-rawwidth=&622& data-rawheight=&111& class=&origin_image zh-lightbox-thumb& width=&622& data-original=&/50/v2-8df4d3f01a66ddf3444d94_r.png&&相信每一个游戏开发团队都在官方的这些AssetBundle加载API基础上,封装出自己的加载管理类,这几乎是必须的。封装的方式千奇百怪,怎么样去封装会比较好?&/p&&p&接下来我所讲述的是一种模仿面向对象的AssetBundle加载管理类封装方式,实现方便加载的同时,又可以更容易地进行实时调试。&strong&这种基于面向对象的方式来设计的AssetBundle加载管理器,我们给他一个名字叫ResourceModule,方便下文讲述。它的主要目的是为了让开发者在方便加载资源的同时,提供方便的实时调试功能,并且你会在这个过程中了解到资源文件的热更新策略。&/strong&&/p&&p&&strong&下面将会分成五个部分来介绍ResourceModule:加载、调试、异步、垃圾回收、路由。&/strong&&/p&&p&&strong&3.1 加载器 —— 基于追踪对象&/strong&&br&&img src=&/50/v2-f6d856b464ddf_b.png& data-rawwidth=&622& data-rawheight=&91& class=&origin_image zh-lightbox-thumb& width=&622& data-original=&/50/v2-f6d856b464ddf_r.png&&&/p&&p&在Unity的标准AssetBundle加载接口中,同步加载返回了行为结果,异步加载则返回了行为追踪对象。具体来说,同步加载直接就返回了资源的AssetBundle;异步加载则返回了异步加载的追踪对象AssetBundleCreateRequest。追踪对象,用于之后进行资源异步加载情况跟踪,被协程判断是否已经异步加载完毕,若完成了可从追踪对象里获取加载资源。&/p&&p&由于同步和异步的加载API不一样,在项目实际应用时,往往没有统一的加载接口。要避免这种情况,可以统一加载行为,返回追踪对象。这也是ResourceModule加载方式的核心。&/p&&p&&strong&3.1.1
函数式接口&/strong&&br&在ResourceModule里,提供了两个最简化的加载AssetBundle API,看上去就跟Resources.Load一样简单。&br&&img src=&/50/v2-ceab6bc60510dc7faf4b77d_b.png& data-rawwidth=&622& data-rawheight=&242& class=&origin_image zh-lightbox-thumb& width=&622& data-original=&/50/v2-ceab6bc60510dc7faf4b77d_r.png&&这里的函数式接口跟官方不一样的地方:ResourceModule的函数式接口的返回值,将始终是一个AbstractResourceLoader对象,也就是“追踪对象”——对于异步加载,使用追踪对象,可以判断异步加载的进度并获取加载后的资源;对于同步加载,使用追踪对象立即获取资源;它也提供错误处理信息;并且后续所讲及的实时调试,也是基于这个追踪对象。&/p&&p&两个接口的返回值类型是一样的。&br&&img src=&/50/v2-f08a37ef106fbf139a27e6_b.png& data-rawwidth=&622& data-rawheight=&113& class=&origin_image zh-lightbox-thumb& width=&622& data-original=&/50/v2-f08a37ef106fbf139a27e6_r.png&&&/p&&p&看上去,AssetBundle的加载接口很简单。但本质来说,这只是接下来Loader对象式加载的一个使用简化。&/p&&p&&strong&3.1.2 Loader对象&/strong&&br&ResourceModule.LoadBundle的本质,是使用AssetFileLoader进行加载行为,并把自己作为追踪对象返回。AssetFileLoader本身是对UnityEngine.Object进行处理,它自身可以通过配置,修改成使用Resources.Load模式或AssetBundle模式。&/p&&img src=&/50/v2-22fafacea2b07d95dec48a83d5b3d56f_b.png& data-rawwidth=&576& data-rawheight=&313& class=&origin_image zh-lightbox-thumb& width=&576& data-original=&/50/v2-22fafacea2b07d95dec48a83d5b3d56f_r.png&&当AssetFileLoader配置成AssetBundle加载模式,它就会调用AssetBundleLoader进行AssetBundle加载行为,而AssetBundle本身则使用HotBytesLoader进行AssetBundle文件字节码进行加载。&p&&img src=&/50/v2-8fb981dd22fca853bd12_b.png& data-rawwidth=&780& data-rawheight=&564& class=&origin_image zh-lightbox-thumb& width=&780& data-original=&/50/v2-8fb981dd22fca853bd12_r.png&&HotBytesLoader是一个热更新桥接器——根据资源“相对路径”和“热更新资源目录”,当热更新资源目录存在对应路径的文件时,使用热更新目录的资源。&/p&&p&所以说,一次加载行为会有4个Loader产生,它们之间形成链式关系。即AssetFileLoader -& AssetBundleLoader -& HotBytesLoader -& WWWLoader。&/p&&p&如前所说,ResourceModule函数式加载其实Loader对象式加载的一个简化。每一次加载行为都会对应一个Loader对象。那么基于AssetFileLoader,由于它是一个单独的解耦对象,我们还可以针对它一些特定需求的功能扩展:&/p&&p&&em&&u&Loaders&/u&&/em&&br&&/p&&img src=&/50/v2-f2c7e3220eaf9f8c27b40bcf9d263c5d_b.png& data-rawwidth=&438& data-rawheight=&542& class=&origin_image zh-lightbox-thumb& width=&438& data-original=&/50/v2-f2c7e3220eaf9f8c27b40bcf9d263c5d_r.png&&&p&在不同类型的资源加载中,不同的行为被划分成不同的Loader对象,来给资源加载代码赋予更好的维护性和可读性。同时,由于链式关系的存在,指定的AssetBundle文件永远只会被加载一次——从而避免一些项目中常见的AssetBundle文件被重复加载问题。&/p&&p&每一个Loader对象,都有一个静态.Load函数,这是一个工厂函数,每一个Loader对象通过自身的Load静态函数生成Loader,来确保引用计数、状态的正确。&/p&&p&&strong&3.2 对象式调试&/strong&&/p&&p&&em&&u&Profiler中的AssetBundle&/u&&/em&&br&&/p&&img src=&/50/v2-b7a0e22af7ed_b.png& data-rawwidth=&371& data-rawheight=&104& class=&content_image& width=&371&&&p&Unity的Profiler可以方便地提供各种Unity运行时资源的调试功能。它采用快照的方式,捕捉当前运行时状态。只要你对Profiler足够熟悉,大部分运行性能问题都能从中发现。&/p&&p&对于AssetBundle加载,Profiler是不能实时获取动态的,即使发现了AssetBundle残留,也难以发现具体是哪部分代码残留了。而这类调试的事情,我们可以通过游戏里的统一加载接口来更易发现。&/p&&p&由于加载所使用的每一次行为都对应着一个Loader追踪对象,所以我们要对资源加载行为进行实时调试,简单来说就是对这些追踪对象进行监视。这里用了一个偷懒的方式:Unity引擎编辑器本身就是基于游戏对象的。&/p&&p&那好吧,我们把加载对象以游戏对象的方式显示在编辑器上,每创建一个Loader,就紧跟着一个GameObject,达到可视化实时调试的目的。&/p&&p&&em&&u&调试追踪对象和加载后的资源&/u&&/em&&br&&/p&&img src=&/50/v2-9b6a08814bbb_b.png& data-rawwidth=&380& data-rawheight=&197& class=&content_image& width=&380&&&p&从上图可知,每一个Loader追踪对象(加载行为)都被一个静态的全局列表包存起来,因此可以很方便地在Unity编辑器上显示它们的具体数量,我们把这些称为“调试对象”,点击后右边还能显示其引用计数和资源路径。&br&&/p&&p&&strong&3.3
异步风格&/strong&&br&Unity的协程是一个非常好用的单线程异步编程方式,让普通开发者在没有线程编程的基础下,也可以方便地进行异步编程。&/p&&p&另一种常见的单线程异步编程方式是回调Callback风格,是非阻塞IO语言NodeJS的主要异步方式。&/p&&p&无论是协程还是或者回调,它们都有一个共同的特点,都可以做到是基于单线程进行了异步编程。关于异步编程这个话题可以引申出很长的篇幅,这里就不多介绍了。Unity开发中,两者各有优点。协程可以让看起来同步的代码实现了异步,但在Unity中它有一个蹩脚的地方是需要另写一个IEnumerator ()函数; 而Callback风格则由于C#中强大的匿名函数语法,使得让异步代码写起来更加的方便。&/p&&p&ResourceModule中两种异步风格并存,可以根据喜好使用。&/p&&p&&strong&3.3.1 协程式&/strong&&br&&img src=&/50/v2-10dc0ea406cfb96bbd528f88b896154d_b.png& data-rawwidth=&621& data-rawheight=&266& class=&origin_image zh-lightbox-thumb& width=&621& data-original=&/50/v2-10dc0ea406cfb96bbd528f88b896154d_r.png&&&/p&&p&这种协程可谓在Unity中最为常见、舒服的异步方式了。使用起来跟Unity原生的WWW差不多。&br&&/p&&p&&strong&3.3.2 回调式&/strong&&/p&&p&&img src=&/50/v2-ce80c541ebee8be38523a_b.png& data-rawwidth=&621& data-rawheight=&100& class=&origin_image zh-lightbox-thumb& width=&621& data-original=&/50/v2-ce80c541ebee8be38523a_r.png&&相比而言,匿名函数回调的异步风格,可以写更好的代码,并且调用代码更紧密连接。&/p&&p&&strong&3.4 垃圾回收——基于引用计数&/strong&&br&我们都知道Java/C#语言的核心是面向对象,他们之所以那么的强大还有一个杀手锏,就是完全自动垃圾回收机制。因其基于对象的设计,所有对象的生命期都是可以被监视和管理的。做过iOS开发的同学也知道,Objective-C语言的内存管理使用引用计数的方式来实现的。&/p&&p&由于ResourceModule的加载行为都是基于对象,多个Loader对象有互相引用的关系,ResourceModule模仿了Objective-C引用计数的方式来实现AssetBundle对象的管理。&/p&&p&&em&&u&调试对象的信息&/u&&/em&&br&&/p&&img src=&/50/v2-10a0d2da36eadea198fb_b.png& data-rawwidth=&826& data-rawheight=&161& class=&origin_image zh-lightbox-thumb& width=&826& data-original=&/50/v2-10a0d2da36eadea198fb_r.png&&&p&点开调试对象的GameObject,就能看到调试对象的引用计数信息和加载所耗费的时间。&br&&/p&&p&&strong&3.4.1 资源的释放&/strong&&br&要对加载Loader追踪对象进行引用计数递减。&br&&img src=&/50/v2-a689ea9ea25ae9915832ea_b.png& data-rawwidth=&621& data-rawheight=&33& class=&origin_image zh-lightbox-thumb& width=&621& data-original=&/50/v2-a689ea9ea25ae9915832ea_r.png&&当一个Loader的引用计数为0时,它就会进入到释放队列,待几秒后释放。&/p&&blockquote&&p&&b&为什么不像java那样能全自动的判断对象是否无用自动释放?&/b&&br&ResourceModule的加载器需要手工释放引用计数。因为没法捕捉GameObject对象删除事件,Unity并没有提供这样的事件出来监视游戏对象的删除事件,所以无法捕捉到什么时候去把这一个对象的引用递减,所以只能手动去进行引用计数的管理。&/p&&/blockquote&&br&&p&&strong&3.4.2 延迟清理&/strong&&/p&&img src=&/50/v2-5b31dabefa50a_b.png& data-rawwidth=&542& data-rawheight=&306& class=&origin_image zh-lightbox-thumb& width=&542& data-original=&/50/v2-5b31dabefa50a_r.png&&&p&当一个加载对象被引用计数减为0的时候,他不会被立刻释放。因为存在这样一种场景:当引用变成0的同一时间,同样的资源又被创建一份新的,引用计数立刻变回1。所以如果说当他引用计数为0时候,立刻就被清理了,同时又被创建,这里就会造成了重复的对这份内存资源创建和释放。&/p&&br&&p&&strong&3.5 路由——管理资源加载的路径&/strong&&/p&&p&Unity是一个跨平台的游戏引擎,每一个平台都会有它特殊的处理资源的路径方式,在Unity中一般我们常见的是StreamingAssets和PersistentDataPath两种路径。可是这里面也隐含有不少的坑,比如说在windows平台里面,路径URL,斜杠必须得3个///。安卓平台下,StreamingAssets目录是不能同步读取的(APK内目录),但是包括iOS在内的其他所有平台都是可以通过同步File.ReadAllBytes读取的。&/p&&p&不仅如此,由于AssetBundle的打包是平台定向性的:打出Android的AssetBundle,不能在iOS下使用;反之亦然。因此,AssetBundleLoader加载器在实际运行时,需要一个路由管理器来告诉它什么样的平台,使用哪里的AssetBundle目录。&/p&&p&&em&&u&ResourceModule路由&/u&&/em&&br&&/p&&img src=&/50/v2-a4d832835dbd07d0ff15d2_b.png& data-rawwidth=&568& data-rawheight=&258& class=&origin_image zh-lightbox-thumb& width=&568& data-original=&/50/v2-a4d832835dbd07d0ff15d2_r.png&&&p&所以在ResourceModule中,路由管理器做了很多路径的识别工作来统筹各种不同平台下的资源路径,让AssetBundle模块开箱即用。&br&&/p&&h2&&strong&四、热更新&/strong&&/h2&&p&我们使用AssetBundle,无非最想解决的就是一个需求——热更新。&/p&&p&资源路由管理器除了平台差异化路径处理,另外的核心功能就是热更新路径处理了——即此前所说的,优先判断PersistentDataPath路径是否存在指定的热更文件。&/p&&h2&&strong&五、后记&/strong&&/h2&&p&&img src=&/50/v2-dd9ddf663fced5b1ff4ce_b.png& data-rawwidth=&978& data-rawheight=&774& class=&origin_image zh-lightbox-thumb& width=&978& data-original=&/50/v2-dd9ddf663fced5b1ff4ce_r.png&&以上我们分别从加载、调试、异步、垃圾回收、路由5个方面,介绍了这种基于面向对象的思想,来进行AssetBundle加载的ResourceModule管理器。它的代码开源放在Github ResourceModule 。它是Unity开发框架KSFramework的核心部分。对于很多使用者来说,ResourceModule就像一个黑箱子,虽然一直能用,但是一直不好理解它的内部构思,所以就有了本文。它的本质是将行为进行对象化。概括来说就是把加载行为以对象的方式保存起来。&/p&&p&ResourceModule管理器开源代码:&br&&a href=&/?target=https%3A///mr-kelly/KEngine/tree/master/KEngine.UnityProject/Assets/KEngine/CoreModules/ResourceModule& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&mr-kelly/KEngine&i class=&icon-external&&&/i&&/a&&/p&&p&关于作者:陈霈霖,金山西山居游戏软件工程师,KSFramework开发框架的开发者,关注Unity游戏开发领域。博客:&a href=&/?target=http%3A///u/674f1a626944& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&公的Kelly - 简书&i class=&icon-external&&&/i&&/a&。&/p&
这是侑虎科技第208篇原创文章,作者陈霈霖,欢迎转发分享,未经作者授权请勿转载。如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ群)同时,作者也是U Sparkle活动参与者哦,UWA欢迎更多开发朋友加入 ,这个舞…
接触Unity一年多,参与过两款上线游戏的开发(iOS/Android)。根据个人感受回答一下。细节中有错误的地方欢迎指正。&br&&br&个人认为Unity相比于其他引擎易用性较好的原因主要有:&br&&br&&b&基于组件(Component)的关卡设计更符合直觉&/b&&br&Unity通过将系统或用户的脚本抽象为可重用的组件(Component)并附着在游戏对象上来控制游戏的行为。相较于传统的基于脚本的开发方式,关卡设计师可以更灵活、更快速地搭建界面和关卡,有一种“搭积木”的感觉。虽然这种设计牺牲了一部分扩展性(例如难以实现嵌套Prefab),但对初学者来讲是非常友好的。&br&&br&虚幻引擎4.7版本的更新也效仿Unity,向组件化的方向靠拢,使关卡结构更容易理解和维护。&br&&br&&b&使用Mono作为脚本运行的平台&/b&&br&C#/Mono相比C++和其他脚本语言,有更好的稳定性和抽象能力,有容易使用的.NET框架和易于移植的各类开源库,相对完善的语言服务(如GC和反射)也使开发复杂逻辑容易许多。虽然降低了门槛的同时也让低质量的代码更容易产生,但不管对于初学者还是老鸟来说,我觉得都是利大于弊的。&br&&br&&b&引擎本身的功能相对简单 + 丰富的Asset Store插件&/b&&br&和虚幻等超级引擎相比,Unity提供的功能算是非常基础的,组件数量和各组件可配置的内容都不多,所以在学习的时候更容易产生比较直观的感受,不至于迷失在细节当中。另一方面,Asset Store模式的成功造就了大量功能强大的第三方插件,填补了Unity开发中的各种空白,进一步降低了开发门槛。&br&&br&而难于精通方面,我觉得主要原因在于:&br&&br&&b&对综合能力要求高&/b&&br&首先,不仅对Unity,对任何游戏引擎或者对游戏开发本身来说,想要精通都是很难的一件事。因为游戏客户端开发本身是一项综合性非常强的活动,整合的技术非常多,例如:&br&&ul&&li&建模&/li&&li&关卡制作&/li&&li&脚本逻辑&/li&&li&网络通信&/li&&li&平台特性集成&/li&&li&动画制作&/li&&li&特效制作&/li&&li&工作流集成&/li&&li&调试和优化&/li&&/ul&而对于将这些技术粘合在一起的Unity工程师来说,虽然不需要精通方方面面,但将团队成员的工作高效率高质量地结合在一起,也是非常考验其能力的,只有长期地,全方面地参与整个开发过程并了解团队成员的工作方式,才能逐渐成长为一名优秀的Unity工程师。&br&&br&举例来说,Unity工程师需要:&br&&ul&&li&与设计和美术团队沟通,评估设计对于游戏性能的影响,实现原型,进行各种性能测试。&/li&&li&与服务器工程师沟通,确定互相之间的接口和协议内容的细节。&/li&&li&Unity本身没有一个Gameplay Framework,场景管理、游戏数据管理等相对底层的框架都需要Unity工程师来搭建,如何减少团队中其他成员的错误实践,是主程序的责任,也很考验其架构能力和对Unity Runtime的理解。&/li&&li&一些特效需要自己编写Shader才能实现。&/li&&li&动画师和美术团队产生的资产,根据项目的需要常常要自己写Pipeline来导入和进行优化处理。一部分还需要以合适的方式打包,供客户端增量下载,需要对Unity的资源管线有较好的理解才可以。而且看似简单的决定之中,常常蕴含了很多性能上的考量。&/li&&li&根据关卡设计师的需要,制作编辑器扩展工具,提高其工作效率。这方面的文档稀少,同时也需要对Unity独特的序列化机制有比较深的理解才行。&/li&&li&想利用iOS和Android以及各种平台特有的功能时,需要针对特定的平台编写一些Native插件,如本地Push通知,自定义系统键盘,系统弹窗等等。要懂一些iOS开发和Android开发的知识才能驾驭。&/li&&/ul&有些做Unity的工程师可能只是搭建关卡,写一些控制脚本,而优秀的Unity工程师的价值往往在于其能够承担更多的团队职责。所以说想要精通这些,真的需要付出很多的时间和努力才行。&br&&br&&b&难在细节&/b&&br&&br&&br&任何事想精通,细节都是非常重要的,比如:&br&&ul&&li&内存管理&/li&&ul&&li&避免和排查脚本中的内存泄漏。例如没有清空的委托和静态闭包造成的引用。&/li&&li&优化GC,了解Mono和.NET GC算法的差别。比如Unity采用的是较老版本的Boehm GC,不分世代,GC分页为1KB,内存碎片无法合并,单线程,缺少LOH,托管堆一旦扩大就很难向系统返还内存。如果按照.NET的思路优化GC,有时候并没有理想的效果。而Unity最近引入的IL2CPP运行时,采用了新版本的Boehm GC,算法又有所改变,优化策略也应适当调整。&/li&&li&了解Unity的内存模型,哪些资源分配在Native堆上,哪些分配在Mono的托管堆上。Native上不同种类的资源分别用哪些方法能够释放干净。Native堆上的引用计数是如何工作的。如何减轻Unity自动释放场景时的压力。AssetBundle的内存结构是怎么样的,各个部分如何不依赖GC而精准地释放。&/li&&li&暂时隐藏起来的图片或物体如何暂时性地释放,在显示时又如何重新加载回内存。&/li&&li&了解哪些API和操作会分配内存,什么时候使用值类型更好或更不好。这都需要对C#有很深入的理解。&/li&&/ul&&li&网络和下载&/li&&ul&&li&AssetBundle如何打包,什么样的图片用什么粒度打包效率最高。&/li&&li&如果使用Json反序列化数据,怎么才能避免内存碎片降低整个App的性能。&/li&&li&不断更新的素材需要增量下载,Unity内置的下载方式瓶颈在哪,怎么自己实现一个比Unity内置API更高效的下载机制和更精准的缓存控制机制。&/li&&/ul&&li&脚本执行&/li&&ul&&li&能够将较复杂的操作(如反序列化数据,较重的IO操作)放在后台线程执行,再调度回主线程更新游戏界面,以避免UI卡顿。Unity的线程优先级又是怎样的?&/li&&li&能否理解Unity Coroutine的迭代器本质,怎样对Coroutine中的异常进行处理,如何使Coroutine具有返回值,Coroutine启动时将分配多少内存,为什么复杂的Coroutine会使用更多的内存,如何将多个Coroutine合并为一个从而消除内存分配。&/li&&li&在与Native插件(如iOS插件)交互时,如何让C#与Objective C或Java共享内存,从而减少大型数据封送造成的CPU负担。&/li&&li&iOS平台上AOT异常的根本原因是什么,有哪几种。值类型和泛型的组合更容易引发AOT异常的原因是什么,如何绕过。怎么安全的使用Linq,使用C#标准事件为什么会触发AOT异常,怎样避免。Mono AOT的trampoline又是什么,哪种风格的代码更容易耗尽trampoline并引发AOT异常。&/li&&li&Unity的C#编译器有哪些弱点,哪些代码通过Visual Studio编译成DLL再放到Unity中可以提高执行效率和内存使用效率。&/li&&li&场景加载缓慢等Unity内部表现出来的问题如何从自身脚本下手进行优化。或者说不同性能问题在自己代码中的Critical Path都是哪些部分。&/li&&/ul&&li&渲染&/li&&ul&&li&Draw Call是什么。不同AssetBundle中的小图片如何Batch到一个Draw Call中。&/li&&li&移动平台常用的Tile-based GPU有哪些弱点,如何避免。&/li&&li&Retina等高清屏幕上制作2D游戏时,如何动态为图片生成最小的Mesh网格以节约fill rate。&/li&&/ul&&li&团队协作&/li&&ul&&li&自己编写Gameplay框架的情况下,如何控制队友代码中的内存泄漏。&/li&&li&使用版本管理系统时会产生哪些难以解决的冲突,如何建立开发规则。自己开发的框架或工具是否能有效避免队友间发生冲突。&/li&&li&如何实现资源管线的自动化。&/li&&li&如何将各种奇葩动画编辑器的输出转换成Unity标准的动画资源。&/li&&li&制作编辑器扩展时,是否能正确序列化复杂的数据结构。能否让自己的工具和脚本也实现所见即所得,让队友更快的搭建场景。&/li&&li&是否会使用Gizmo和Handle来扩展场景编辑器。&/li&&li&当不得不修改MonoBehaviour的定义时,怎样让已经上线的老版本中的数据正确地反序列化到新版本。&/li&&li&要有能力编写模块划分明确,依赖关系合理清晰的可重用代码和组件,作为公司的资产加速新项目的开发。&/li&&/ul&&/ul&这些都是Unity开发中会不断面对的问题,如果不能从始至终中控制住这些细节,积累起来往往会使团队效率底下,难以产出高质量的应用。&br&&br&所以我觉得Unity难以精通之处就在于对细节知识的把握,以及在整合团队价值的过程中如何做到扬长避短。Unity开发团队中常常不是所有人都会这个引擎有很深刻的认识,大家术业有专攻,有人搭场景,有人做后端,对自己不熟悉的领域,难免有错误的认知和实践。我们尚可以通过时间和努力精通细节,然而到头来真正缺少的,其实是团队成员间的信任所带来的沟通成本的降低。&br&&br&【更新】&br&回答一些朋友的疑问。&br&&br&&b&关于资料来源&/b&&br&细节问题很难有系统的资料来源,而像AssetBundle Dynamic Batch这种几乎找不到答案的问题只能自己慢慢摸索。在此列举几个最主要的知识来源。&br&&ol&&li&官方手册。例如搜索Unity Optimization可以找到官方在GPU,CPU和Mobile方面的几篇优化手册。官方资料常常包含最核心也最容易被忽略的原则,深入理解往往会有新的收获。&/li&&li&官方博客。博客上不时会有一些技术类文章,如关于IL2CPP和序列化机制的知识几乎只能看那几篇博客。&a href=&///?target=http%3A///category/technology/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Technology&i class=&icon-external&&&/i&&/a&&/li&&li&Unite的视频和Slide。对某些领域有比较深入的探讨,特别在内存管理,AssetBundle和代码组织方面。YouTube和SlideShare上搜Unite或Unity能找到。值得注意的是Unite的分会场,如日本和韩国,有时会有一些更加深入的分析。如 &a href=&///?target=http%3A//www.slideshare.net/williamyang3910/unitekorea2013-memory-profiling-in-unity& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&slideshare.net 的页面&i class=&icon-external&&&/i&&/a&&/li&&li&Unity的Mono fork:&a href=&///?target=https%3A///Unity-Technologies/mono& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Unity-Technologies/mono · GitHub&i class=&icon-external&&&/i&&/a& ,关于GC和AOT方面是第一手的资料。比如gc的配置,Enumerable类的实现如何导致Linq容易触发AOT异常,以及泛型CompareExchange中存在的JIT Hack导致C#事件和一些线程同步操作触发AOT异常等等。另外GC方面也可以参考&a href=&///?target=https%3A///ivmai/bdwgc/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&ivmai/bdwgc · GitHub&i class=&icon-external&&&/i&&/a& ,有详细的机制解释。&/li&&li&UI的源代码:&a href=&///?target=https%3A//bitbucket.org/Unity-Technologies/ui& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Unity-Technologies / UI&i class=&icon-external&&&/i&&/a& ,修改优化后可以直接集成到游戏中,很方便。&/li&&li&随Unity安装的PDB调试文件。Unity的安装目录下其实是有Editor和Player当中所有C++源码的PDB文件的,而且居然都是private PDB。需要探查Unity内部数据结构和过程实现的时候,通过WinDbg配合这些PDB文件调试Unity进程可以获得很多最底层的信息。当然,如果公司买了Unity的源码就不必这样麻烦了。&/li&&/ol&&br&&b&关于优化策略&/b&&br&不只是Unity,优化程序最重要的原则就是&b&先测量&/b&。而且在没有丰富经验和自信的情况下,不要自己写测量代码,而要依赖Unity自己Profiler和Profiler API。这里只说一些Unity特有的内容。&br&&br&使用Profiler时,切忌猜测。一定要弄清各种数据的精确涵义,如Self %,Self ms,GC Alloc等,如果弄不清楚,优化常常是南辕北辙。另外诊断CPU Spike时,一定要打开Deep Profile,否则只能看到误导性很强的表面数据。找到真正的Hot Line才能着手优化改善性能。&br&&br&当Hot Line在自己的代码中时,可以尝试将CPU密集的操作分派到后台线程,然后在需要与Unity API交互时调度回来。粒度较好的操作可以尝试用Coroutine分派到其他帧分别执行。大量GC Alloc造成的Spike需要重新设计内存分配策略,小对象(目前版本是小于1KB)较多的时候也可以尝试预先扩大托管堆(如分配很多小于1KB的缓冲区,然后再释放),这样可以加速后续内存分配。因为Boehm GC的堆扩展策略是时间线性而非空间线性的,所以每次扩大后的容量都是翻倍的,需要注意。&br&&br&而当Hot Line在Unity API中时,常常是自己的错误实践造成的,需要重新审视设计。一方面要减少昂贵API的调用次数,一方面要降低Unity内部处理数据的规模。例如场景加载缓慢时,可能需要简化场景本身,然后在场景启动后再手动、增量地加载场景中的其他内容。&br&&br&另外要了解一些底层知识。例如App启动时性能较差的原因,在非AOT平台上可能是因为大量的JIT编译造成的,而在AOT平台上则可能是因为初始化代码过于复杂导致CPU缓存命中率很低,和操作系统频繁地Page Fault。这也是为什么启动代码一定要精简,并且要尽量实现批处理。&br&&br&GPU方面,如果没有复杂的特效,瓶颈常常在Draw Call和Fill Rate上。Draw Call需要Batch,能共享的材质一定要共享。Fill Rate的问题通常在高分辨率的2D游戏中比较明显,Profiler中的Transparency渲染占比很大时就应该着手优化。GPU优化策略上Unity相比其他引擎并没有很多特异的方面,准确测量的基础上通常能找到比较通用的解决方法。
接触Unity一年多,参与过两款上线游戏的开发(iOS/Android)。根据个人感受回答一下。细节中有错误的地方欢迎指正。 个人认为Unity相比于其他引擎易用性较好的原因主要有: 基于组件(Component)的关卡设计更符合直觉 Unity通过将系统或用户的脚本抽象为可…
&img src=&/50/v2-f1a929eccc550f1f842b46_b.jpg& data-rawwidth=&1085& data-rawheight=&583& class=&origin_image zh-lightbox-thumb& width=&1085& data-original=&/50/v2-f1a929eccc550f1f842b46_r.jpg&&&p&原文链接:&a href=&/?target=http%3A///archives/Study_Haar.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&基于物理的渲染-基于Haar小波基的实时全局光照明 - Blog&i class=&icon-external&&&/i&&/a&&/p&&p&上一期惟学无际我们给大家介绍了一篇 &a href=&/?target=http%3A///archives/Study_PRT.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&基于球面调和基函数(Spherical Hamonices,简称 SH )的动态全局光照明算法&i class=&icon-external&&&/i&&/a&。它的思路是:将光线在物体间复杂的传输过程看作一个传输函数,预先模拟计算好并用 SH 基函数来近似表示。同时,在实时光照计算时,将入射的环境光用 SH 基函数近似,这样光照渲染结果可以用两者的 SH 基函数的点积进行计算。该算法通过预计算模拟,使得实时计算的复杂度大大降低,从而能够支持实时动态光源的全局光照明渲染。但是,该算法有一个缺陷,即只能获得低频的渲染效果。因此,基于 SH 的 PRT 只能渲染类似于 AO(Ambient Occlusion)的软阴影,无法渲染有明显轮廓边界的真实阴影。&/p&&p&今天介绍的这篇文章是 Ren Ng 等人在2003年发表于 Siggraph 会议上的经典论文 《All-Frequency Shadows Using Non-linear Wavelet LightingApproximation》,目前该篇文章的引用次数已经超过400次。在这篇论文中,作者提出了一个采用非线性小波变换来代替线性 SH 变换的 PRT 算法。实验结果证明,相较于前人的 SH 函数,本文作者在使用相同数量的小波函数时,能够更好地捕捉到全局光照明的高频信息。因此,该算法能够渲染出更加真实的全局光照明效果,比如具有明显边缘的实时阴影。&/p&&p&&img src=&/v2-de1cc4a8ca03f87e08b7e_b.png& data-rawwidth=&639& data-rawheight=&626& class=&origin_image zh-lightbox-thumb& width=&639& data-original=&/v2-de1cc4a8ca03f87e08b7e_r.png&&首先我们来看看这篇论文的效果图,如上图所示。其中,左图是基于 SH 的 PRT 渲染结果,右边是基于小波的 PRT 渲染结果。两幅图采用了相同数量的基函数项:左图采用100个 SH 基函数,右图采用100个小波基函数。中间的六个方形纹理则是环境光贴图。比较左右两图的阴影渲染效果则可以发现,右图的阴影边缘较为清晰,而左图的比较模糊。因此,在使用相同数量基函数的情况下,小波变换比 SH 变换能够捕捉到更高频的光照信息,渲染结果更加贴近真实情况。接下来,我们将详细介绍这篇论文算法的主要内容。&/p&&h2&&strong&一、算法与实现&/strong&&/h2&&p&所有的渲染问题,其实都是在解决渲染方程的计算问题。这篇论文也不例外,因此我们首先来介绍渲染方程。考虑物体表面某一点 x,其法线方向为 n,则该点从 ωo 方向反射出的光 P( x, ωo ) 可表示为:&/p&&img src=&/v2-9ad01baac1_b.png& data-rawwidth=&599& data-rawheight=&51& class=&origin_image zh-lightbox-thumb& width=&599& data-original=&/v2-9ad01baac1_r.png&&&p&其中,L 为入射光强,V 为可见性函数,fr 为BRDF函数,n 为法线方向,ω 为入射光方向。&/p&&p&论文算法可以应用于两种情况:1)视点变化,材质限定为Diffuse;2)视点不变,材质任意。接下来我们分别介绍这两种情况下的解决方案。&/p&&p&&strong&1.1 视点可变且材质限定为Diffuse&/strong&&br&当材质限定为Diffuse时,传输函数可以表示成为:&/p&&img src=&/v2-b45aa3b66f8ce69a5d4d0f709bcd385c_b.png& data-rawwidth=&600& data-rawheight=&34& class=&origin_image zh-lightbox-thumb& width=&600& data-original=&/v2-b45aa3b66f8ce69a5d4d0f709bcd385c_r.png&&&p&&strong&1.2 视点不变且材质任意&/strong&&br&当材质为任意时,传输函数可表示成为:&/p&&img src=&/v2-859a9c2dfbbb_b.png& data-rawwidth=&600& data-rawheight=&30& class=&origin_image zh-lightbox-thumb& width=&600& data-original=&/v2-859a9c2dfbbb_r.png&&&p&其中,由于视点固定,所以 ωo 只跟 x 相关。&/p&&p&渲染方程中的积分式可以看成入射光强 L 与其他项关于入射光方向 ω 的卷积。离散化后就变成对应项相乘之后求和。于是,对于每一点 xi ,其光照计算可表示成为:&/p&&img src=&/v2-40d306b7b3bbfb_b.png& data-rawwidth=&600& data-rawheight=&68& class=&origin_image zh-lightbox-thumb& width=&600& data-original=&/v2-40d306b7b3bbfb_r.png&&&p&如果用矩阵以及向量形式表示,则可简化为:P= T · L 。其示意图如下图所示。&br&&/p&&img src=&/v2-f69af15a1369d52bfda552_b.png& data-rawwidth=&748& data-rawheight=&204& class=&origin_image zh-lightbox-thumb& width=&748& data-original=&/v2-f69af15a1369d52bfda552_r.png&&&p&&strong&1.3 预计算&/strong&&br&在预计算阶段,论文作者采用了两种方法。第一种方法是每次对传输矩阵 T 的一列进行模拟计算,也就是对入射光向量 L 的每一个方向 ω 做计算,计算方式是光线跟踪。因此,T 中每一列向量表示的是环境光贴图每一个像素照射场景的结果。该方法比较简单,并且支持各种全局光照效果,但是它只支持固定视点的情况。&/p&&p&第二种方法是每次对传输矩阵T 的一行进行模拟计算。这种方法只计算直接光照的部分。因此,传输矩阵的每一行表示的是物体表面某一点 x 的可见性立方体贴图外加BRDF和Cosine项的权重,如下图所示。&br&&br&&img src=&/v2-dafed5c0afd_b.png& data-rawwidth=&416& data-rawheight=&209& class=&content_image& width=&416&&&img src=&/v2-b48f185f3ce3cb872bb9c8c_b.png& data-rawwidth=&412& data-rawheight=&207& class=&content_image& width=&412&&&img src=&/v2-a855e8feecbd84ab2d995e_b.png& data-rawwidth=&412& data-rawheight=&209& class=&content_image& width=&412&&&/p&&p&上方图片为场景中某些顶点的预计算结果。其中,第一张图是场景底部面片某一点的预计算结果,第二张图是植物下方某点的预计算结果,第三张图是植物叶子上某点的预计算结果。该方式可以用光栅化图形硬件来实现。论文作者对每一个点 x ,首先渲染一个高分辨率的半立方体可见性贴图。然后计算BRDF和Cosine项的权重值,并且进行降采样到环境贴图一样的分辨率。&/p&&p&&strong&1.4 实时渲染&/strong&&br&在实时渲染时,论文作者首先将环境光贴图通过2D Haar小波变换计算出相应的系数向量 L 。然后,选用了一部分入射光进行计算。其主要目的是通过删减能量比较小的入射光来减少计算量。论文作者采用了三种过滤方法。第一种是直接用Haar小波系数的大小进行过滤,第二种是用传输矩阵的对应列计算出一个权重,第三种是用光源的面积大小来过滤。然后,计算 L 与 T 的乘积得到最后的渲染结果。整个计算过程是在 CPU 端进行,因为小波变换后的系数向量和矩阵是非常大的稀疏向量和矩阵,而当时的 GPU 无法支持通用的稀疏矩阵乘法运算。&/p&&h2&&strong&二、效果对比&/strong&&/h2&&p&论文作者分别用 SH 基函数以及小波基函数对环境光贴图进行模拟,并且比较了两者在采用不同数量基函数时的结果与原始环境贴图差别,如下图所示。&/p&&img src=&/v2-1ceffcaeadb_b.png& data-rawwidth=&757& data-rawheight=&460& class=&origin_image zh-lightbox-thumb& width=&757& data-original=&/v2-1ceffcaeadb_r.png&&&p&其中,左右两幅图是分别对两张不同的环境光贴图的模拟结果。在左右两图中,左边一列是分别用100、4096以及10000个SH基函数项对环境光贴图的模拟结果,右边一列上两幅图是分别用100和4096个小波基函数项对环境光贴图的模拟结果,第三幅是原始环境光贴图。对比 SH 和小波的模拟结果可以看到,对于比较简单的环境光贴图,小波基函数只需要100项即可捕获高频信息,而 SH 需要4096项才能达到相似的结果。对于复杂的环境光贴图,小波基函数只需4096项即可还原出原始贴图,而 SH 即使采用10000项依然无法很好地还原原始贴图。&/p&&p&小波变换之所以能够用非常少的项就能捕捉全频光照信息的原因是,经过小波变换后,大部分项的系数变成了零或接近于零,如下图所示。&/p&&p&&img src=&/v2-28a0a7de9bc26e7f707f525e19b2bbf1_b.png& data-rawwidth=&412& data-rawheight=&343& class=&content_image& width=&412&&经过实验发现,小波变换之后非零项所占的比例只有0.1%~1%。小波变换相当于对全频信息进行了压缩。因此,采用相同数量项时,小波变换能捕获的光照信息要比 SH 更多。但是,小波变换也有它的局限性。它并不具备 SH 的旋转不变性,因此在传输函数中法线方向 n 被看作位置变量 x 的函数。这意味着基于小波变换的 PRT 无法实时渲染物体经过旋转后的结果。而SH则没有此限制。这也是基于小波变换的 PRT 在随后的研究中发展受限的主要原因之一。&/p&&p&论文作者对比了采用不同数量项的 SH 基函数和小波基函数对环境光贴图近似结果与原始图的 L2 误差,如下图所示。&/p&&p&&img src=&/v2-4ea0d98dd12f8cd944b4_b.png& data-rawwidth=&673& data-rawheight=&602& class=&origin_image zh-lightbox-thumb& width=&673& data-original=&/v2-4ea0d98dd12f8cd944b4_r.png&&可以看到,随着项数的增加,SH 和小波的近似误差都在减少。但是小波收敛的更快,到1000 时已经比 SH 在10000项时的误差还要小了。&/p&&h2&&strong&三、更多渲染结果&/strong&&/h2&&p&论文作者渲染了采用更多的场景进行实验,下表统计了不同场景中对环境光贴图进行采样的分辨率、稀疏矩阵中非零项的比例以及内存占用量。&br&&/p&&img src=&/v2-bb57b344a6c_b.png& data-rawwidth=&755& data-rawheight=&228& class=&origin_image zh-lightbox-thumb& width=&755& data-original=&/v2-bb57b344a6c_r.png&&&p&更多的渲染结果如下图所示,上排是 SH 渲染结果,下排是 Haar 小波渲染结果。从左到右采用的基函数项逐渐增加。可以见到 Haar 小波在采用相同数量基函数项的情况下,比 SH 渲染出的阴影结果具有更多的高频细节。&br&&img src=&/v2-b_b.png& data-rawwidth=&1312& data-rawheight=&481& class=&origin_image zh-lightbox-thumb& width=&1312& data-original=&/v2-b_r.png&&&img src=&/v2-1fe2c48fb7faf9fa7d94_b.png& data-rawwidth=&1318& data-rawheight=&485& class=&origin_image zh-lightbox-thumb& width=&1318& data-original=&/v2-1fe2c48fb7faf9fa7d94_r.png&&&/p&&h2&&strong&四、总结&/strong&&/h2&&p&本篇论文提出了使用Haar小波函数来作为PRT的基函数,从而实时渲染出能够保留全频信息的全局光照明效果。与球面调和基函数相比,Haar小波基函数同样具有标准正交性,同时,它的高压缩比特性可以让其以非常少的参数就可以保留住渲染效果的高频信息,进而达到全频光照明的渲染效果。虽然Haar小波由于自身特点限制在后续的全局光照明渲染中并没有得到广泛的应用,但其在推进全局光照明的实时渲染研究领域,有着不可磨灭的贡献。&/p&&h2&&strong&五、论文信息&/strong&&/h2&&p&&strong&论文参考视频:&/strong&&/p&&p&视频网址:&a class=&video-box& href=&/?target=https%3A///x/page/n0384trr1va.html& target=&_blank& data-video-id=&& data-video-playable=&& data-name=&基于物理的渲染-基于小波的实时全局光照明 - 腾讯视频& data-poster=&/qqvideo_ori/0/n0384trr1va_228_128/0& data-lens-id=&&&
&img class=&thumbnail& src=&/qqvideo_ori/0/n0384trr1va_228_128/0&&&span class=&content&&
&span class=&title&&基于物理的渲染-基于小波的实时全局光照明 - 腾讯视频&span class=&z-ico-extern-gray&&&/span&&span class=&z-ico-extern-blue&&&/span&&/span&
&span class=&url&&&span class=&z-ico-video&&&/span&/x/page/n0384trr1va.html&/span&
&/p&&p&&strong&作者介绍:&/strong&&br&Ren Ng,目前就职于UC Berkeley&br&个人主页:&a href=&/?target=https%3A//www2.eecs.berkeley.edu/Faculty/Homepages/yirenng.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&EECS at UC Berkeley&i class=&icon-external&&&/i&&/a&&/p&&p&Ravi Ramamoorthi,著名计算机图形学学者,目前为UCSD教授。计算机图形学界大牛,发表论文数量可谓著作等身(Publications 好几页翻不完)&br&个人主页:&a href=&/?target=https%3A//cseweb.ucsd.edu/%7Eravir/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&RAVI RAMAMOORTHI'S HOME PAGE&i class=&icon-external&&&/i&&/a&&/p&&p&Pat Hanrahan,著名计算机图形学学者,目前为斯坦福大学教授。计算机图形学界大牛,曾经在Pixar工作,为RenderMan Shading Language开发者之一&br&个人主页:&a href=&/?target=https%3A//graphics.stanford.edu/%7Ehanrahan/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Pat Hanrahan&i class=&icon-external&&&/i&&/a&&/p&&p&&strong&下载连接:&/strong&&br&&a href=&/?target=https%3A//graphics.stanford.edu/papers/allfreq/allfreq.pdf& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://&/span&&span class=&visible&&graphics.stanford.edu/p&/span&&span class=&invisible&&apers/allfreq/allfreq.pdf&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&&/p&&p&这是侑虎科技第213篇原创文章,欢迎转发分享,未经作者授权请勿转载。如果您也有任何独到的见解或者全新的发现也欢迎联系我们,一起探讨。(QQ群)&/p&&h2&&strong&关于惟学无际&/strong&&/h2&&p&为学无际是UWA在2017年推出的全新研究型专栏,我们将为大家推荐极具实际价值的学术论文,并梳理其中的研究背景、实现原理和执行方法等。内容专注于游戏、VR和AR相关的计算机图形学领域。正所谓问渠哪得清如许,为有源头活水来 ,希望大家在研发的过程中不仅知其然,还能知其所以然。&/p&
原文链接:上一期惟学无际我们给大家介绍了一篇 。它的思路是:将光线在物体间复杂的传输过程看作一个传输函数,预先模拟计…
我挑一些有趣的算法,希望尽量提及相关算法在游戏中的应用。&br&&br&&b&光栅化&/b&&br&&ul&&li&&a href=&///?target=http%3A//en.wikipedia.org/wiki/Bresenham%2527s_line_algorithm& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Bresenham's line algorithm&i class=&icon-external&&&/i&&/a& [1]:经典的绘画直线算法,后来还可以稍作修改用于绘画圆弧[2],都不用三角函数或除数,只需用整数加法、减法和乘法。 &/li&&/ul&&img src=&/b7cb85217babb238ac6c_b.jpg& data-rawwidth=&592& data-rawheight=&401& class=&origin_image zh-lightbox-thumb& width=&592& data-original=&/b7cb85217babb238ac6c_r.jpg&&&br&&ul&&li&Perspective-Correct Texture Mapping [3]:透视正确的光栅化纹理贴图算法是1980才出现的。第一代Quake引擎引入后,才开始支持不垂直的墙、不水平的地面天花。&/li&&/ul&&img src=&/24681fdb28cfc1e4342f_b.jpg& data-rawwidth=&1000& data-rawheight=&352& class=&origin_image zh-lightbox-thumb& width=&1000& data-original=&/24681fdb28cfc1e4342f_r.jpg&&(图片来自维基百科)&br&&br&&ul&&li&Polygon Rasterization with Edge Function [4]:Bresenham算法如果用来画多边形,两个多边形的共边会被重绘。后来发明了使用简单的edge function去解决这个问题,而且适合并行的硬件实现。现在的GPU都是使用这个算法。&/li&&/ul&&img src=&/bc6a762cbbfeb_b.jpg& data-rawwidth=&492& data-rawheight=&368& class=&origin_image zh-lightbox-thumb& width=&492& data-original=&/bc6a762cbbfeb_r.jpg&&&br&&br&&b&全局光照&/b&&br&&ul&&li&Precomputed Radiance Transfer (PRT) with Spherical Harmonics(SH)[5]:储存静态环境对于各个方向光源的漫反射数据,可以实现动态低频光源的全局光照效果。这种表示方式非常神奇。Halo 3也使用到这种技术[6]。&/li&&/ul&&img src=&/4225d10efdd85cb81c6ef6fe855b14a3_b.jpg& data-rawwidth=&694& data-rawheight=&295& class=&origin_image zh-lightbox-thumb& width=&694& data-original=&/4225d10efdd85cb81c6ef6fe855b14a3_r.jpg&&&br&&ul&&li&Screen-space Ambient Occlusion (SSAO)[7]:Crytek提出的首个屏幕空间环境光遮蔽算法,之后引来大量的研究及改进算法。也有用类似的概念去做近距离的反射,如SSDO[8]。&/li&&/ul&&img src=&/1faa1a94f23aa9b4b1eb_b.jpg& data-rawwidth=&798& data-rawheight=&316& class=&origin_image zh-lightbox-thumb& width=&798& data-original=&/1faa1a94f23aa9b4b1eb_r.jpg&&&br&&ul&&li&Light Propagation Volume (LPV)[9]:Crytek提出的首个动态全局光照算法,不需要预计算。但要在体积数据中计算传播,性能较慢,所以之后再优化成 Cascaded LPV [10]。&br&&/li&&/ul&&img src=&/cda20e301ccbaa6d3b823b22_b.jpg& data-rawwidth=&1198& data-rawheight=&377& class=&origin_image zh-lightbox-thumb& width=&1198& data-original=&/cda20e301ccbaa6d3b823b22_r.jpg&&&br&&ul&&li&Voxel Cone Tracing [11]:也是不需要预计算的动态全局光照算法。把场景动态生成层阶式的体素数据(像mipmap那样的pre-filtering),从光源视角计算直接光照,然后逐像素追踪这组数据获取非直接光照。结果比LPV精确,也可以做到光泽反射(glossy reflection)。&/li&&/ul&&img src=&/a5bc6edfaf47c2be14ba70_b.jpg& data-rawwidth=&1086& data-rawheight=&333& class=&origin_image zh-lightbox-thumb& width=&1086& data-original=&/a5bc6edfaf47c2be14ba70_r.jpg&&&br&&b&阴影&/b&&br&&ul&&li&Shadow Volume [12]:阴影体积是1977年发表的阴影技术,在屏幕空间光栅化阴影体积,可准确判断每个屏幕像素是否在阴影之内。可以处理平行光源和点光源的阴影。1991年[13]讲述如何用stencil buffer来实现此算法,适合在图形加速硬件(当时还没有所谓GPU)上使用。但很多人发现,如果摄像机在阴影体积内,就会出错。在年有多人发现一种解决方法,需要把John Carmack在2000年的电邮[14]中提及这个想法,后来成为2004年《毁灭战士3(Doom 3)》引擎的重要特徵,因他把这项技术发扬光大,即使他非首个发明人,此项技术通常被称为Carmack's Reverse。&/li&&/ul&&img src=&/c0aefec2d7cab_b.jpg& data-rawwidth=&461& data-rawheight=&346& class=&origin_image zh-lightbox-thumb& width=&461& data-original=&/c0aefec2d7cab_r.jpg&&&br&&br&&ul&&li&Parallel Split Shadow Map (PSSM) [15][16] / Cascaded Shadow Map(CSM)[17]:虽然Shadow Volume很吸引,但它需要大量的内存频宽,而且通常不能实现软阴影。后来大部分游戏改为使用Shadow Map(阴影贴图),这更适合GPU,并且可以通过多次采样(Percentage Closer Filtering, PCF)来实现软阴影。然而,阴影贴图也有许多问题,例如远近景物都采用同一张纹理,就会令到近景的精度不足,出现锯齿。2006年香港中文大学的博士生Fan Zhang等人发表了一种 PSSM 算法 [15],为不同距离的场景渲染多张阴影贴图,在采样的时候按距离决定使用那一张。这个方法的变种CSM,在切割上和PSSM有点差异,被广泛使用于现时大部分游戏引擎中。&/li&&/ul&&img src=&/dba24fba68e2ff8eb5a5ab819c50a1a5_b.jpg& data-rawwidth=&511& data-rawheight=&327& class=&origin_image zh-lightbox-thumb& width=&511& data-original=&/dba24fba68e2ff8eb5a5ab819c50a1a5_r.jpg&&&br&&ul&&li&Variance Shadow Map(VSM)[18]:之前谈到用PCF做软阴影,它的坏处就是要做多次采样。那么可否把阴影贴图直接模糊化来实现软阴影?答案是否定的。但是在2006年有学者发表了VSM,它是一种用统计方式来逼近软阴影的效果。 &a href=&/question//answer/& class=&internal&&如何推导方差阴影贴图(variance shadow map, VSM) ? - Milo Yip 的回答&/a&&/li&&/ul&&img src=&/6bb2fb0efc1d_b.jpg& data-rawwidth=&856& data-rawheight=&300& class=&origin_image zh-lightbox-thumb& width=&856& data-original=&/6bb2fb0efc1d_r.jpg&&&br&&b&场景管理&/b&&br&&ul&&li&&a href=&///?target=http%3A//en.wikipedia.org/wiki/Binary_space_partitioning& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Binary Space Partitioning&i class=&icon-external&&&/i&&/a& (BSP)&br&&/li&&li&&a href=&///?target=http%3A//en.wikipedia.org/wiki/Portal_rendering& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Portal rendering&i class=&icon-external&&&/i&&/a&&br&&/li&&li&&a href=&///?target=http%3A//en.wikipedia.org/wiki/Quadtree& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Quadtree&i class=&icon-external&&&/i&&/a&、&a href=&///?target=http%3A//en.wikipedia.org/wiki/Octree& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Octree&i class=&icon-external&&&/i&&/a&:&a href=&/question//answer/?group_id=696704& class=&internal&&游戏场景管理的八叉树算法是怎样的? - Milo Yip 的回答&/a&&/li&&li&Potential Visibility Set (PVS)&/li&&li&Occlusion Culling by Software Rasterization&/li&&/ul&&br&&b&动画/物理&/b&&br&&ul&&li&Particle System&/li&&li&Smoothed Particle Hydrodynamics(SPH)&/li&&li&Curl Noise&/li&&li&Dual Quaternion Skinning&/li&&/ul&&br&&b&碰撞测试&/b&&br&&ul&&li&&a href=&///?target=http%3A//en.wikipedia.org/wiki/Hyperplane_separation_theorem& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Hyperplane separation theorem&i class=&icon-external&&&/i&&/a& (或称separating axis theorem/SAT):凸形状相交测试的基本原理。在&a href=&/question//answer/& class=&internal&&怎样判断平面上一个矩形和一个圆形是否有重叠? - Milo Yip 的回答&/a&中,其实背后也是使用了SAT。&br&&/li&&li&&a href=&///?target=http%3A//en.wikipedia.org/wiki/Gilbert%25E2%Johnson%25E2%Keerthi_distance_algorithm& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Gilbert-Johnson-Keerthi distance algorithm&i class=&icon-external&&&/i&&/a& (GJK距离算法):计算两个凸形状的距离(可用于相交测试)&br&&/li&&li&&a href=&///?target=http%3A//en.wikipedia.org/wiki/Sweep_and_prune& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Sweep and prune&i class=&icon-external&&&/i&&/a&:用于broad phase碰撞检测,找出物体AABB是否相交。对于时空上连续的物体运动,算法最坏O(n^2)、最好O(n)。&br&&/li&&/ul&&br&&b&人工智能&/b&&br&&ul&&li&&a href=&///?target=http%3A//en.wikipedia.org/wiki/Minimax& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Minimax&i class=&icon-external&&&/i&&/a&&/li&&li&&a href=&///?target=http%3A//en.wikipedia.org/wiki/Alpha%25E2%beta_pruning& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Alpha-Beta Pruning&i class=&icon-external&&&/i&&/a&&br&&/li&&li&A* path finding&/li&&li&&a href=&///?target=http%3A//en.wikipedia.org/wiki/Dijkstra%2527s_algorithm& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Dijkstra's algorithm&i class=&icon-external&&&/i&&/a&&br&&/li&&li&&a href=&///?target=http%3A//en.wikipedia.org/wiki/Finite-state_machine& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Finite-state machine&i class=&icon-external&&&/i&&/a&&br&&/li&&li&Behavior Tree&/li&&/ul&&br&(中午吃饭时间写不完,后补)&br&&br&&b&参考&/b&&br&&br&[1] Bresenham, Jack E. &Algorithm for computer control of a digital plotter.& &i&IBM Systems journal&/i& 4.1 (1965): 25-30. &a href=&///?target=http%3A//www.cse.iitb.ac.in/%7Eparagc/teaching/2011/cs475/papers/bresenham_line.pdf& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://www.&/span&&span class=&visible&&cse.iitb.ac.in/~paragc/&/span&&span class=&invisible&&teaching/2011/cs475/papers/bresenham_line.pdf&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&&br&[2] Bresenham, Jack. &A linear algorithm for incremental digital display of circular arcs.& &i&Communications of the ACM&/i& 20.2 (1977): 100-106. &a href=&///?target=http%3A//www.cse.iitb.ac.in/%7Eparagc/teaching/2014/cs475/papers/bresenham_circle.pdf& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://www.&/span&&span class=&visible&&cse.iitb.ac.in/~paragc/&/span&&span class=&invisible&&teaching/2014/cs475/papers/bresenham_circle.pdf&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&&br&[3] Catmull, Ed, and Alvy Ray Smith. &3-D transformations of images in scanline order.& &i&ACM SIGGRAPH Computer Graphics&/i&. Vol. 14. No. 3. ACM, 1980. &a href=&///?target=http%3A///Papers/CG/2pass80.pdf& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://&/span&&span class=&visible&&/Papers/CG/2&/span&&span class=&invisible&&pass80.pdf&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&&br&[4] Pineda, Juan. &A parallel algorithm for polygon rasterization.& &i&ACM SIGGRAPH Computer Graphics&/i&. Vol. 22. No. 4. ACM, 1988. &a href=&///?target=http%3A//people.csail.mit.edu/ericchan/bib/pdf/p17-pineda.pdf& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://&/span&&span class=&visible&&people.csail.mit.edu/er&/span&&span class=&invisible&&icchan/bib/pdf/p17-pineda.pdf&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&&br&[5] Sloan, Peter-Pike, Jan Kautz, and John Snyder. &Precomputed radiance transfer for real-time rendering in dynamic, low-frequency lighting environments.& &i&ACM Transactions on Graphics (TOG)&/i&. Vol. 21. No. 3. ACM, 2002. &a href=&///?target=http%3A//www1.cs.columbia.edu/%7Eravir/6998/papers/p527-sloan.pdf& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://&/span&&span class=&visible&&www1.cs.columbia.edu/~r&/span&&span class=&invisible&&avir/6998/papers/p527-sloan.pdf&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&&br&[6] Chen, Hao, and Xinguo Liu. &Lighting and material of Halo 3.& &i&ACM SIGGRAPH 2008 Games&/i&. ACM, 2008. &a href=&///?target=http%3A///wordpress/media/08-Chen-Lighting_and_Material_of_Halo3.pdf& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://&/span&&span class=&visible&&/wordp&/span&&span class=&invisible&&ress/media/08-Chen-Lighting_and_Material_of_Halo3.pdf&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&&br&[7] Mittring, Martin. &Finding next gen: Cryengine 2.& &i&ACM SIGGRAPH 2007 courses&/i&. ACM, 2007. &a href=&///?target=http%3A///wordpress/media/2012/10/Chapter8-Mittring-Finding_NextGen_CryEngine2.pdf& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://&/span&&span class=&visible&&/wordp&/span&&span class=&invisible&&ress/media/2012/10/Chapter8-Mittring-Finding_NextGen_CryEngine2.pdf&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&&br&[8] Ritschel, Tobias, Thorsten Grosch, and Hans-Peter Seidel. &Approximating dynamic global illumination in image space.& &i&Proceedings of the 2009 symposium on Interactive 3D graphics and games&/i&. ACM, 2009. &a href=&///?target=https%3A//people.mpi-inf.mpg.de/%7Eritschel/Papers/SSDO.pdf& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://&/span&&span class=&visible&&people.mpi-inf.mpg.de/~&/span&&span class=&invisible&&ritschel/Papers/SSDO.pdf&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&&br&[9] Kaplanyan, Anton. &Light propagation volumes in cryengine 3.& &i&ACM SIGGRAPH Courses&/i& 7 (2009): 2. &a href=&///?target=http%3A///download/Light_Propagation_Volumes.pdf& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://www.&/span&&span class=&visible&&/download/Lig&/span&&span class=&invisible&&ht_Propagation_Volumes.pdf&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&&br&[10] Kaplanyan, Anton, and Carsten Dachsbacher. &Cascaded light propagation volumes for real-time indirect illumination.& &i&Proceedings of the 2010 ACM SIGGRAPH symposium on Interactive 3D Graphics and Games&/i&. ACM, 2010. &a href=&///?target=http%3A//www.vis.uni-stuttgart.de/%7Edachsbcn/download/lpv.pdf& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://www.&/span&&span class=&visible&&vis.uni-stuttgart.de/~d&/span&&span class=&invisible&&achsbcn/download/lpv.pdf&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&&br&[11] Crassin, Cyril, et al. &Interactive indirect illumination using voxel cone tracing.&&i&Computer Graphics Forum&/i&. Vol. 30. No. 7. Blackwell Publishing Ltd, 2011. &a href=&///?target=https%3A///sites/default/files/publications/GIVoxels-pg2011-authors.pdf& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://&/span&&span class=&visible&&/sit&/span&&span class=&invisible&&es/default/files/publications/GIVoxels-pg2011-authors.pdf&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&&br&[12] Crow, Franklin C. &Shadow algorithms for computer graphics.& &i&ACM SIGGRAPH Computer Graphics&/i&. Vol. 11. No. 2. ACM, 1977. &a href=&///?target=http%3A//excelsior.biosci.ohio-state.edu/%7Ecarlson/history/PDFs/crow-shadows.pdf& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://&/span&&span class=&visible&&excelsior.biosci.ohio-state.edu&/span&&span class=&invisible&&/~carlson/history/PDFs/crow-shadows.pdf&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&&br&[13] Heidmann, Tim. &Real shadows, real time.& &i&Iris Universe&/i& 18 (1991): 28-31.&br&[14] Carmack, John, &e-mail to Mark Kilgard on Shadow Volume&, 23 May 2000. &a href=&///?target=http%3A//web.archive.org/web/35/http%3A///attach/6832& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://&/span&&span class=&visible&&web.archive.org/web/200&/span&&span class=&invisible&&//attach/6832&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&&br&[15] Zhang, Fan, et al. &Parallel-split shadow maps for large-scale virtual environments.& &i&Proceedings of the 2006 ACM international conference on Virtual reality continuum and its applications&/i&. ACM, 2006.&br&[16] Zhang, Fan, Hanqiu Sun, and Oskari Nyman. &Parallel-split shadow maps on programmable gpus.& &i&GPU Gems&/i& 3 (2007): 203-237. &a href=&///?target=http%3A//http./GPUGems3/gpugems3_ch10.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&GPU Gems 3 - Chapter 10. Parallel-Split Shadow Maps on Programmable GPUs&i class=&icon-external&&&/i&&/a&&br&[17] Dimitrov, Rouslan. &Cascaded shadow maps.& &i&Developer Documentation, NVIDIA Corp&/i& (2007). &a href=&///?target=http%3A//www.cse.chalmers.se/edu/year/2011/course/TDA361/Advanced%2520Computer%2520Graphics/cascaded_shadow_maps.pdf& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://www.&/span&&span class=&visible&&cse.chalmers.se/edu/yea&/span&&span class=&invisible&&r/2011/course/TDA361/Advanced%20Computer%20Graphics/cascaded_shadow_maps.pdf&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&&br&[18] Donnelly, William, and Andrew Lauritzen. &Variance shadow maps.&&i&Proceedings of the 2006 symposium on Interactive 3D graphics and games&/i&. ACM, 2006. &a href=&///?target=http%3A//www.punkuser.net/vsm/vsm_paper.pdf& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://www.&/span&&span class=&visible&&punkuser.net/vsm/vsm_pa&/span&&span class=&invisible&&per.pdf&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&
我挑一些有趣的算法,希望尽量提及相关算法在游戏中的应用。 光栅化
[1]:经典的绘画直线算法,后来还可以稍作修改用于绘画圆弧[2],都不用三角函数或除数,只需用整数加法、减法和乘法。 Perspective-Correct Texture Mapping …
书给多了你也就迷惑了.&br&我给一个基本建议, 不要用c/cpp/java这些语言来写. 用python/ruby/racket随你愿&br&&br&个人觉得那几本书特别是编译器的书很有可能严重打击你的士气, 因为它们都有严重的毛病&br&就像这样&br&&img src=&/0e190e9a301a41bdc49cbcfad9d20629_b.jpg& data-rawwidth=&600& data-rawheight=&1254& class=&origin_image zh-lightbox-thumb& width=&600& data-original=&/0e190e9a301a41bdc49cbcfad9d20629_r.jpg&&(汉化了一下...)&br&&br&&br&所以可以走easy mode, 比如&br&编译器你可以...&br&&ol&&li&先实现lisp语法的解释器(以后你可以拿这个金手指找别人约架...)&br&&/li&&li&把它编译成一个栈式虚拟机代码&/li&&li&转换到汇编或者直接用虚拟机跑就随意了&/li&&/ol&&br&图形学你可以...&br&放弃, 最后再弄&br&&br&操作系统你可以...&br&&ol&&li&先&操作系统概念/现代操作系统/FreeBSD操作系统设计与实现&随便看一本, 理解概念就行&/li&&li&再去github上抓一个小操作系统来玩, 也就1万行代码不到, 就ok了&br&&/li&&/ol&&br&然后你别忘了...&br&计 算 机 网 络, 一个完整的tcp/ip协议栈不过3-5万行代码, 如果你做完了上面的事情, 写个协议栈也就是3-5个月的光景&br&如果你们学校用锐捷/3com认证网络&br&如果你们学校限制客户端数量&br&如果你们学校限制客户端操作系统&br&如果你们学校限制上网时长&br&如果....&br&那你就有救了, 一网在手, 天下你有. (但是的但是千万别专门当写网络程序的程序员, 非 常 无 聊(当然说不定你也许会喜欢什么的...))&br&&br&&br&(在

我要回帖

更多关于 unity 获取gameobject 的文章

 

随机推荐