GDC2016【彩虹六号-围攻 】的物理破坏艺术

发表于2016-06-03
评论0 1.67w浏览

翻译 TraceYang 校对 Qiankanglai 


 


The Art of Destructionin Rainbow Six: Siege

Julien L’Heureux

Technical Lead /Physics Programmer

Ubisoft Montreal

 

 


       进行演讲的是Ubisoft MontrealJulien L'Heureux

 

前言

       首先JulienL'Heureux解释了什么是程序化的破坏(Procedural Destruction),最大的不同是,程序化方式的物体对象是在运行时改变生成破碎状态的,有不同的输入和输出,可以得到独一无二的结果。而与之对立的预生成破坏的方式,它的输出非常的固定,破坏的切面通常是相同的,就像一个表面不管是被车撞,还是被棒球棒击打或者子弹击穿,随出来的结果都是一样的。

 


       程序化破碎可以更丰富的破碎输入和输出结果

       UBI的程序化破碎的研究开发是从2012年开始的,当时实现的是彩虹六号的程序化玻璃破碎功能。


 

    接下来演示的是程序化玻璃破坏实现原型的视频,和当时比已经不同,有了很大的变化。 L'Heureux他们动态的创建了正确表现效果的几何体,然后通过贴花来改变法线,大概是4年多以前完成的。这是项目的一个转折点,大家觉得这个效果非常赞,所以我们对其他材质也做了相同处理,发现我们能在游戏里做真实的破坏效果。



 

Heureux他们准备了一些美术资源,在2013年完成了概念技术Demo,非常的Cool,是以蒙特利尔工作室为原型制作的。


 

    接下来演示的是概念技术的演示,可以说内部花了很长一段时间制作的破坏系统的标准,有很多员工参与,可以说在三年前就是非常好的效果了。下面视频里有子弹和场景的交互。

可以打碎墙面,窗户,地板,天花板,并引发大量的爆炸。就如所看到的一样,是非常帅气的效果。但那时还无法在60FPS的情况下线上运行,又没有工具,所以花了大量的工作来实现这些特性。这样可以从一个非常清晰的起点开始构思。可以射击来破坏物体,让这些惊人的效果更加正确。 




 

    到了2015年,彩虹六号发售并获得了成功,而它的概念是至少是三年前的, L'Heureux他们经过了很多的步骤才让它可以工作起来的。这样这也就是他接下来所主要介绍的内容。



演讲概述


 

    首先是RealBlast在产品中的作用,然后是第二部分彩虹六号中破坏的制作,在第三部分你可以从中了解到所关心的破坏解决方案,以及它是如何与游戏交互的,还有就是各种破坏的生成。最后会讨论游戏中大量生成破坏方面的性能。



RealBlast 预览


 

   REALBLAST是一个全部由程序员组成的很小的团队,而且已经成立了5年以上了。属于是L’Heureux所来自的技术团队的一部分。Alexander Ouimet是他非常敬仰的一名同事,发表了很多的文章,L'Heureux也是跟他一起工作的。


 

    UBISOFT的技术小组在内部是一个独立的部门,进行研发,通用技术的研究,并分享给不同的产品。由精通破坏,导航网格,UI,网络,动画等各不同领域的专家组成。每个人都有专业知识,通常被委托在不同的产品上,以作出更大更好的游戏。


 

   REALBLAST提供了完整的破坏解决方案,包括在集成在引擎内的破坏功能,同时也提供了一些碎片工具,可以用上生成美术内容。还有就是破坏属性编辑器,可以设置要使用哪类的物理,使用多大的物理属性,以及一个外部的调试器可以追踪所有的数据。或许你会觉得调试复杂的几何体算法是一件很复杂的事情,特别是当看不到具体信息的调适,特别让人少脑筋,但L'Heureux这里并没有展开这个话题


 

    合作方面,L'Heureux前面也提到,他们是被多个委托到多个产品的。使用REALBLAST的第一个产品,是【刺客信条:黑旗】,用来表现船体的破损。而真正作为核心特性使用的是在本作彩虹六号中。



彩虹六号:围攻中的破坏系统


 

    游戏中的任务非常的简单,下图中攻击者一方是想要进入解救人质,也就是意味着要进行破坏才能进入。而另外一方的防守方,是要阻挡入侵者的进入,(funnel这个没听懂)。


 

    在游戏中,是把破坏作为玩法上的一种有利条件,你可以通过破坏来制作玩家可以走过的道路,制造地形优势杀人或者穿过墙壁。而防守方可以通过隐藏钥匙,加固墙壁。这给予了玩家更多的玩法,而且每个策略都有很大的不同。


 

    玩家在游戏中的体验时,可破坏的环境制作的相当的好,例如活板门,可以控制特定的楼层。路障不会阻挡进入,但是主要是会遮挡视线,Line of Sight 是指就是破坏之后玩家还是走不过不去,玩家只能在视觉上透过。


 

    使用破坏系统虽然听起来很容易,但在3年前并没有那么简单。现在是这需要付出很大的代价,因为要为玩家表现连贯的物理破坏环境,其中包括模型的破坏准备阶段,要比原来的模型准备更多的细节,因为以前很多地方是看不到的,但是摧毁之后能看到的区域。最终需要创建以前从没见过的大量的破坏表现。这么大量的工作实现起来是非常困难的。所以需要一种合适的方法使项目在物理上是可行的。


 

    首先,需要美术方面的培训,因为通常有些地方需要物理,有些地方不需要,要修改模型的风格。而最大的改变是游戏设计师,要用虚拟语言来控制破碎,同时也要用新的变量来完成一些新的表现。最后的性能方面,需要更多的物理,更多动态对象要渲染,而且运行时生成破碎也不是无偿的,


 

    现在既然决定要做破坏系统,那么接下来的问题就是,这个破坏系统实际与其他游戏中的系统如何交互,L'Heureux他们用了非常简单的方式实现。


 

    用游戏里的物理输入来驱动破坏,可以把破坏系统想象成是一个黑盒,通过它输出信息并反馈回到游戏里。输出的信息是一个结构,包含了相关系统工作所需要的信息。通过这些信息可以了解到是什么被破坏,使用了什么样的武器,哪个部位等各种细节。你可以从中获取信息提供声音,特效,AI等等。


 

    例如像AINavigation,可以动态导航链接, AI可以打碎墙壁或钻洞,而不是被卡住等待门的打开等等。


 

    有了足够的信息,可以把它用在任何的事物上,比如AI的能见度上,不需要做那种在各个方向上投射视线的检测的无用功,也不用把AI制作的太智能。同样也在声音的传播中也有使用,这个是在彩虹六号里非常重要的游戏特性。当你破坏一堵墙壁时,也会改变环境的声音。玩家非常依赖这方面提供的信息来进行游戏。


 

    通常各种玩法需要不同的行为,譬如有些东西你希望一定被破坏而不是有概率、或者是程序化破坏(你需要输入参数)。需要强调的是我们做的是游戏,而不是tech demo



RealBlast 技术

接下来讨论更多关于RealBlast的技术,虽然是在高层次上的,但在破坏的技术上,有很大的不同和相同点。但总的来说,预先定义模型非常简单


 

    其实,基本的概念是很简单的。把物体用稍微不同的部件做成,或者是分解一个物体对象。也要符合常识,譬如不能做一棵玻璃树。所以的都是用不同的部件来组成,要更加致力于物理材质上的调整,因为每个物件都是由不同部件组成,很容易破碎,所以制作时要更有物理的感觉。这个其实就是一个层次化的东西,反正就是建好东西之后一层层碎开来。


 

    当一些物体破碎,你要把它分成多个部件,而开始的第一步,实际是要维持它们的状态,因为要考虑我们接下来处理所引发的渲染和物理的性能问题。


 

    在获取了一些破坏模型(Destruction Model)后,可以模拟物体对象的状态,要做的工作非常的简单,树中不同的碎片代表图表中不同的节点,图表中的每一条边是碎片的物理关联。在完成了这个步骤后,就获得一个正确的图表,如果你很好的理解了这个图的含义,你就可以用网络流算法max flow/min cut来获得一些有趣的性质。   


 

    通过图表可以来维护物体对象,它的关联被打破后,意味着物体不能在被碰触了。相互之间不再产生联系。


 

    可以把程序化破坏集成到某些模型里,基于拓扑结构,叶节点可以标记为可程序化破坏的。外观和碰撞可以改变,也可以移动一个碎片并用新的来替换。这样对那些有完整标记的模型来说,效果很好


 

接下来讨论的是物体表面的程序化破坏。


 

    这个功能一开始是为了玻璃的游戏表现,限制在平面一类的物体上,(墙壁啥的。。)。最大优点是可以在任意地方切割。可以帮助美术师很方便的设置模型,或使用2D样板来生成新的模型。总的来说,这种方法很稳健,快速,实现起来也不是很难,相当的简单。


 

    那么实际上是如何做的呢?首先第一步是,是要一个像平面一样的2D物体对象,ProjectionBasis


 

    接下来,从游戏中获取输入,与材质的参数输入一起来破碎的Cutter。生成2Dpattern,然后映射回3D


 

    然后把新的2D多边形与做为Cutter多边形相交,可以用一些经典算法,其实和3D没啥关系,例如使用Weiler Athertion 这种非常简单的概念,可以生成相当复杂的多边形。然后处理uv,顶点,物理属性等等。


 

    三角形化(Triangulation)的解决方案,譬如Delaunay,是最经典的。最后使用的是Ear Clipping的方法,虽然不是最优的算法,但更加健壮,也不适用于所有的情况,但可以成功的处理墙壁上简单的小的洞孔。


 

    在完成三角形化后,变回到3D空间是一件非常简单的事情,要使用Projection Basis2D表面Extrude3D的模型    


 

    然后讨论的是Cutter,有很多种不同的Cutter,但是总的来说这些就是一个大的家族里的,通过定义边界来定义形状,一些通过定义内部的分块,,有些用来定义内部的破碎(结合了前面两种)

 

    还有就是制作一些Texture Cutter来使用,是非常漂亮的概念,美术师或者画家可以,精确的在UV空间画他要的效果,或者是复用连续的表面,并平铺到surface上。一旦从游戏中获取到了输入,就可以查找到破坏的UV空间的位置。这些很有意思而且注重细节,但艺术家不会手动去做这个



 

那么,下面获得了类似下图的结果。(程序员的美术水准?)没听懂,确实是缺少了一些东西


 

    这就需要改善视觉外观。在场景里射击的话,通常是使用贴花(Decal)来制作。 L'Heureux他们要找到更好的解决方案,也就是使用Decoration来制作真正完整的几何体。贴花(Decal)虽然也像是物体表面(Surface)一样,但也只是是2D的。Decoration可以用来表现物体对象的内部透明,可以设置在物体的顶端和背后。所以说,Decoration是非常灵活的,可以实际的管理和处理物体对象的生命时间,即便是被切割成了多个部分仍然可以维持,管理起来非常简单。

    但缺点是要有更多的消耗,比如CPU,渲染和内存。而好处是美术可以像普通的离线方式那样的来工作。美术还是习惯用贴花,所以会麻烦一些。


 

这里有两种基本类型的Decoration,其中一种类似贴花,是贴在物体表面的,可以沿着表面进行切割。


 

    还有就是Feature-BoundDecoration,是在顶点和边上,所以可以放置在孔洞里或是它的边缘位置。一旦AttachFeature去掉就把它们移除掉,算是一种实现从平面挤出效果的好的方法。也就是平面里的破碎用Cutter(类似贴花),然后外面用FeatureBound,最后再组合起来。


 

最后处理结果就是下图这样,虽然还是程序员的艺术,但看起和所期待的一样。维持了它的真实感觉。


 

下图就是游戏中的实践结果。   


 

    然后是破坏系统的性能。我们需要成功的在PVP模式以60FPS来运行,即使没有破碎系统这个也挺有挑战的,特别是破碎系统还会作用在别的子系统里。


 

    破坏系统会影响到其他的系统,例如AIAI导航,声音传播等,这需要更多的努力来达到这个目标。渲染部分的话,只有很少一部分用于静态光照,更多的用于动态的阴影。还有少量的occluder,因为墙壁可能在任何时候被打破,你可以透过墙壁看到更多的物体,因为需要看到更多的物体,因为墙会在任意时刻被破碎掉。因为这个主题有些复杂,如果有兴趣的话,可以看下后面Jalal的在GDC2016的分享“RenderingRainbow Six: Siege”


 

       如果你破坏了一些物体的话,他们的物理形状就会变成凹状,虽然也可以选择是视觉上判断发生碰撞地方,但这么做的话,消耗会变得非常高,这并不是我们所希望的。


 

    那么解决的方法是,可以去掉一些小的洞,它们没有什么用处,因为你不能够从这些洞里走过去。可以减少孔洞的细分(Tesselation),但需要注意表面原始的形状,另外因为知道正反面,可以制作一些2DConvex的集合,这样可以很快速的完成。


 

    接下来是碎片(Debris),就像你们在游戏里看到破坏产生的碎片一样,这些并没有程序化生成,因为我们觉得这是一个可以避免掉的性能热点。如果要较好的品质的话,就要在物理正确的位置来放置,获得更好的表现,可以通过实例化和积极的回收,对我们来说非常有吸引力。

 

    其他的一些技巧有,动态碎片之间并不会相互碰撞,可以爆破一些复杂的物体,偷偷的去掉一些碎片。也可以一直使用Box,运气好的话也可以用Havok FX

性能

接下来是破坏系统的性能

 

    前面也提到过,我们需要60FPS,这就要求破坏的生成不能降低帧率,我们需要让每个在线游戏的玩家在游戏里看到的都是相同的。

 

    刚提到过要关注破坏的性能预算,因为某些原因,会产生像前述提到那样的巨大风险。管理起来很简单,但是程序破坏比较麻烦,所以1堵墙给了6ms的时间预算, 用了2Procedural LayerGPU上使用25MB内存,内存一共使用了350M

 

    破坏部分的内存使用方面,在制作过程中并不是很大的问题,尽可能的精益求精降低占用,最后没有费太大的力气。

 

   L'Heureux他们做了非常多的相关工作,性能是破坏系统中最大的问题,它是完全数据驱动的,一旦美术师确定设计后,就会有非常高的复杂度和很酷外观,而且谁也不知道最后是啥效果。另外一点就是非常的准时(Punctual),所以他们使用了一系列的技术来让破坏系统可以工作。

 

    首先L'Heureux 提到的是多线程(Multithreading),多线程在物体对象的层面上是毫无价值的,但因为每个破坏的处理是独立的, 所以一个物体的不同独立部分可以分别来模拟,每个部分都可以独立的模拟的来工作。那么可以确定的是,同样物体对象的程序化破坏是可以用多线程来实现的。但这样还是不够的,后面可以加入一些算法,但工作量太大了,现阶段还没有实现。

 

    L'Heureux实现的方式是异步(Asynchronicity)的方式来实现破坏。这样以一种风险可控的方式来制作破坏表现。就算要几百ms,也不会对渲染、游戏体验产生很大影响,让项目中的大量开发人员的血压降低一些。但问题是这样会在实际游戏中造成破坏在视觉识别上的延迟,这样效果就不太好了。

 

    那么它是如何工作的呢?大家都知道,Engine Tick是每帧都进行的,获得输入后就开始工作了,开始一个新的Job,通过各种的异步工作,为引擎节省了大量的开销,可以和引擎的开发人员说这样我就不会拖慢你了。可能你会觉得,这样在Job完成前就不需要更新了,完成后在把结果返回给引擎,也就做完了。如果破碎处理的太慢的话恐怕就无效了。

 

    那么实际能做的就是预生成破坏处理(Pre Destruction),一旦你知道破坏要进行了,也可以通过动画的结束(下图这种按爆破按钮的动画)来得知Time Frame,也就是当玩家按下这个动画的一开始播放,就开始计算破坏。这样,可以提前进行破坏处理,等玩家动画结束后在把结果提交。类似的,更早的从GamePlay获取输入,然后执行各种同步,这时候你开始计算破碎,当GamePlay需要结果的时候,这时再把结果返回给引擎。这只是类似处理的例子,实际处理要加入更多的细节。需要强制其他的系统处理一些工作,对其他系统也这么干,但他们并没有这么做。

 

    接下来是时间片(Time Slicing),如果破坏同步每帧都同步调用的话,那么引擎就不能友好的处理了。有很严格的方式来提交数据,那么最终可能会影响帧率,所以可以把工作分割为多个部分,包装时间片的话,就需要更多的时间进行计算,虽然需要更多调度上的开销,但非常的有帮助。

 

    实际在BasicLevel上分为多个步骤,在Lower Level上更加的复杂,所以没有什么非常显著的解决方案。或许未来会在游戏里更多的使用调度来处理其他系统。把调度做的更加友好。而现在能做的就是状态变量(State variables ),它非常有用,通过这个就可以了解实际运行到哪里,在做什么了,并减少了allocation

 

    下图就是时间片实际使用的一个非常简单的例子。实际运行的并不是这样,但使用宏可以很简单的关闭,这些变量可以设为可见或不可见状态,可以检查剩余时间来继续的工作这点是一个特殊的设计,这样可以很好的运作了。

 

    多线程对调试(Debug)来说非常的困难,非常痛苦。但可以通过宏很方便的关闭,所以也是可以这样做的。最后,如果你需要调试他们的话,(可以用单线程),这样就不需要承担上下文切换(Context Switch)的消耗了。通常来说,这样可以解决所有的问题,对游戏运作来说是很好的方法。这样就不用在考虑这些风险,当多线程出问题的时候,在单线程DEBUG下一般能够复现,所以总的来说还是很有用的。

 

    接下来,是优化部分,优化通常是必须要面对的,可以更快的运行,或算法可以更快。所有游戏都期望这样。优化还是可以去做的,就是有些难度。

 

    首先是通过测量性能,可以找到主要的瓶颈点,并进行优化。这里有很多非常好的经验,比如Telemetry,以及通过TesterGame sessionAutomatic tests捕获信息来进行测量。实现起来非常的简单。如果有兴趣可以看下GDC2016M. De Pascale的分享:Unified Telemetry, Building an Infrastructure for Big Data in GamesDevelopment 

 

    还有就是要限制出现恶化的情况。听起来是逻辑上的,但需要向美术师交代,可能无法用相同的语言去讲解这些,因为美术和程序思考方式不同,所以你得考虑怎么去培训。同样,可以关闭一些不再需要的特性,例如(cupboard?),在爆破后,一些被物理推开部件不需要再次做出反应(再次破坏)。最后,也可以创建一些这样的例子, 增加特性来帮助限制复杂度。

 

    举个例子,L'Heureux他们实现了一种破碎,当玩家很密集的射击了很多的弹孔后,变成了奶酪一样(下图左),可以把它变成一个大的窟窿(下图右),这样看起来的表现就更好,而且设计师和玩家也喜欢,在性能方面也更节省。

 

    最后是限制运行时的分配(Limit Runtime Allocation),因为比较懒,L'Heureux他们在项目中修正的晚了,没有非常的关注这些。运行时的分配也会有消耗,申请内存的时候需要锁定多线程,但有时也没有其他选择必须要分配,因为需要永久的保持数据。但也可以找到一些比较好的策略,比如从hybird heapstack arrays里读取,尽可能减小那些你始终需要的数据量,也会使用数据结构来运作,这是一个非常大的Buffer, 另外还有对象池(Pools of Objects)是给那些不会存活太长时间的,只有几帧生命周期的对象使用,也可以创建一个新的对象来使用。当然另外还有一些内置的算法。

 

   Benchmark方面,PC速度快,子弹弹孔消耗也很低,第1个参数是程序化破坏的时间,第2个参数是不可见物体的更新和物理更新。因为可能有关系,在爆炸上大量修改了物理属性。爆破方面,因为改变了大量的事物,PC上(后面没听懂)。

在线模式 Online

 

   Gameplay方面的特性,需要被确定(Deterministic),因为需要所有玩家都看到相同的表现,也就是需要复制(replicated)。或许你会觉得,这只是测量别人机器的工作,再同样在自己机器上工作一次。那么替代的方案是通过网络传递消息事件,要求在输入一定的情况下输出保持一致。

 

    这样不会有什么问题,但是没有实现物理的复制(Physics Replication),因为破坏是异步的,没办法简单的指出什么时候开始,什么时候结束。角色同样也会碰撞动态的物体对象也会产生问题。在彩虹六号围攻里,因为动态物体和碎片都非常的小,在Gameplay中就忽略了。同步这些小玩意儿没有任何意义,所以动态物体对象的破碎也没有同步。

 

那么反过头来看如何DeterministicL'Heureux他们的方法,是在游戏与破坏系统之间建立协议(Contract)。然后,在保证相同顺序的同一输入下,系统能保证结果一致。

 

    那么相同的输入的处理并不简单, 我们需要的是内容相同的输入。其他的时间,当你做数据压缩时,可能并不需要全部的数值。你本地的游戏也需要数据压缩,如果是客户端的话,需要把这些Deterministic发送给主机(Host),主机处理后,再压缩后发送给其他的客户端,所有的客户端要检查数值,所以需要对称的(symmetrical)压缩。这并不意味着要丢失,最开始的压缩会去掉所有信息,最后得到的结果就不一样。

 

   保证相同顺序这个事情比较简单。在彩虹六号中,通过网络层实现了这一点,非常简单。物理模拟要能够支持多个时间挤在同一帧的情况,这样就可以处理更多内容。

 

     接下来关注的是随机性的来程序化破坏,不论发生了什么都必须保证结果一致,如果是需要某种随机性,要向随机数生成器(RNG)传递一些游戏输入的值,基于一些游戏中公共值。在彩虹六号围攻中,完美的复制了输入,基于实际的碰撞位置,是一个很好的输入值。可以在随机数生成器中使用这些数据,有了足够的粒度后,就没有人会发现这其实并不是完全随机的。

    另外随机数生成器是在C++中使用的,使用起来非常的简单,但要关注time slicing的使用。

 

    那么什么是不同状态(Different State),信息可能来源于过去(Past),从客户端发出,比如需要异步处理的信息。也可能获取来自于将来(Future)的信息,是服务器主机有事件的爆发。所以需要可以容忍这些现象,但可以保存一些历史记录,重新的定位。也可以使用一些机制,让信息回滚到需要的位置。

 

    然后是即时反馈(Instant Feedback),当在射击时,需要一种即时的反馈,在彩虹六号中,当你射中一些易碎的东西时会有立刻的反馈,当你处于在线模式时,你发送信息并等待返回,这会成为一个问题,在互联网上游戏时,会有大量的延迟,所以你没法直接就发送再等待返回。打破顺序(Ordering),游戏中的协议就会打破,这样在自我破坏(self-destruction)的特性上就很成问题,例如很多弹孔变成一个大洞的变化,如果顺序发生了改变,那就会以不同的顺序结束,导致变成不同的数值,不同玩家会产生分歧得到不同的值,就完蛋了。

 

       L'Heureux他们的解决方法是,把这个特性分为了两个部分,首先是表面的实际伤害(Damage),使用了较大的数值,要关注的是,有些很小的数值被送到了Host上,

 

    所有的数据被传送到其他的每个客户端,除了玩家自己获得的是特殊的信息,这个就保证了damage被处理的顺序保持一致。

 

    虽然这不是一个完美的解决方案,发起者(Originator)可能会有微小的不同。在实践中,做了非常多的测试,在相同的位置,虽然有一些小的不同点,但并不是问题。    

 

    另外,还有一些其他的解决方案,之前提到的一些点并不足够的好,这里使用了一种更好的称作回滚(Rollback)的的解决方案。客户端上做的一些事情可以进行跟踪,当从host获取消息后,如果没有超出保存记录的栈的范围的话,可以Undo之前的改变,重新适应的消息,再Redo新的消息。这看起来非常的有逻辑性,在延迟不是很高的情况下可以很好的运作。

    堆栈可能会变得非常非常高,那么运行起来就有很大的浪费,就并不是那么好了。所以 L’Heureux他们没这么做,未来也许会尝试下,但目前的解决方案够用了。

未来的发展与总结

 

    未来的开发简单来说是工具,通过工具可以让破坏的制作更加简单和轻松,这样就可以处理大量的工作,有各种不同的输入和输出,美术就可以更加清晰的制作表现,一开始他们只能做平面,现在可以做弯曲的表面了。在彩虹六号里也可以设置曲面。也会开发更好的破坏类型,例如塑形变形(Plastic Deformation),压力分析(Stress analysis),(没听懂)。获得更好的外观效果。

 

    被去掉的部分如果要使用破坏,你需要一个专门的开发团队,来处理各个子系统的交互。会有很多不同的技术问题,(运行时的Gameplay指令什么的,没听懂),需要更多开发人员。

 

研发投入越早越好,因为你不知道有什么坑;而且与之前形成的行为习惯要做斗争

 

最后总结:现在已经到了支持动态场景的地步,而且不准备回到静态了!破坏效果非常帅,让我们坚持创新和变化,咬紧牙关上。。

 

译注:历年GDC经典Talk的听译分享正式开始。今后没有意外的话1~21篇,大概是渲染,动画,物理,人工智能顺序,这次的老哥口语太快,有些也没听清楚,请谅解。也感谢Qiankanglai的大力支持。

 

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引

0个评论