GAMES 图形学系列笔记(十一)
19.网络游戏的进阶架构 (Part 2) | GAMES104-现代游戏引擎:从入门到实践 - P1:GAMES104_Lecture19-Part02-3 - GAMES-Webinar - BV1RG411t7TP
那我们接下来开始我们课程的第二部分,就是真正的去讲一个如何做一个mo game,首先的话呢就是说什么叫m game对吧。
m game的话呢其实叫massive multiplayer online gaming,他其实讲的意思就是说我们有很多很多的玩家,然后呢他们在这个多人在一个世界里面去online的连接在一起。
这种游戏其实很多了,大家会想到m是不是都是指的在我们比如说在中国,我们讲mo很多人都觉得是m o r p g对吧,但实际上并不是的,比如现在的无论是这个f p s游戏,其实很多都已经是m了。
就是用户量非常大,那么mo这就是说当玩家online联系在一起啊,这些游戏其实啊很早就已经有了,其实最早的一个这是我们课程组找到的最早的这个networking,连在联网在一起的游戏对吧。
大家一起几个人一起走个迷宫,互相去经济,那么其实呢最早的这个multiplayer这个role playing game的话,其实是文字游戏,哎这个游戏这种叫什么,就是那个叫这个是最早的。
但是我记得我们很早以前玩过一种游戏叫做mut啊,那真是充满乐趣的一个时代啊,大家在那边就是说我进了一个房间,我在桌上捡起了一个碗,然后门的那个房间的左边有个门,房间的那个有个桌子,桌子前面坐了谁。
你是上去跟他讲话,还是决定出门推门而出对吧,哎真的是玩的大学时代,那个时候真的玩的不亦乐乎对吧,ok所以说其实呢这就是online游戏,它这个最早的雏形。
其实online游戏真的是个非常了不起的一种游戏类型,就是猫猫这种游戏就是说它真的让用户能够连接在一起,我们第一次能够在一个cyberal word里面能够感知到彼此,所以随着这么多年的发展的话。
其实现代的m o的话已经变得非常的丰富和完善了,就是说今天我们去进入到一个m游戏的时候,我个人的感觉啊,那其实就是一个小的虚拟世界,我个人还是蛮认可鸡胖讲的那句话,就是说现在很多人讲什么元宇宙什么的。
他就说你们这些讲元宇宙的人,你们还不如打打游戏呢对吧,如果你打打这个我们mo游戏的话,比如说你打个final fantasy对吧,你就知道了,那个那你们你讲的很多东西人家就已经有了呀,对不对。
在那里面就是在梦的世界里面,我们就可以进行星空探索对吧,我们也可以进行这个一起去挖矿,一起去做什么各种很有趣的事情,所以说实际上的话呢,就是现代的m游戏的话已经变得极为复杂和丰富了。
那么其实呢他和一个单独的做一个gameplay,比如刚才在前面我们教大家更多的是说诶,我们一个session就是一个战斗里面,我们怎么去玩呢,就大家怎么去战斗。
但是我真的去构建一个这样的一个网上的这样的一个虚拟世界,能够让几10万上百万的人能在这个世界里面彼此连接,一起进一起下战场对吧,一起在这个主城里互相能看见,而且在这里面的话,我们还可以互相对话。
互相交易,这样的一个游戏世界该怎么去架呢,哎今天这个section的话呢,我就给大家简单的讲一下这个结构,其实呢如果要解决这个问题,其实你会发现一下子多了很多的子系统。
比如说我们就需要对很多的玩家数据进行管理,我们需要user management的这个系统,我们的这些玩家在一起,我们要去匹配对战或者一起下副本对吧,我们需要一个matchmaking系统,对不对。
然后呢我们还有什么呢,我们彼此之间还有能交易对吧,我们有的交易甚至是会牵扯到大家真实世界的货币,那就是非常严肃了,对不对,就大家知道在网游里面的话,如果你的道具被偷了的话。
这个有的时候严重的时候是会直接产生法律责任的,为什么,因为它实际上是有经济属性的,所以说他的确定系统可能也要做得非常的严格,要保护好玩家们的安全,另外一个的话呢,我在这个世界你们还跟别人交好友啊。
我要跟他去聊天啊,对不对,我还可以组织更重要的这个社群啊,这种公告啊,所以说诶我还需要一个社交系统,那最后呢我这么多的数据存在,那我们还有一个data系统存在,这大家会发现没有。
就是说从一个单纯的只是构建一个小小的那个游戏的一个场景,我们在彼此去玩,去打一个session的时候,真正你做一款mmo的时候,实际上这个系统就会变得非常的复杂,它有很多的subsistence构成。
那这个系统会以什么样的结构在一起呢,这边的话呢是我们画的一个非常简单的一个m的architecture,那么在这最上面的话呢是各种各样的玩家对吧,玩家连接到我们这个世界里面。
那首先的话呢有一层叫link层,就是连接层,连接层干什么呢,就是说哎这么多用户他要login到我们的世界里面,他还有个login server对吧,它还有一个叫gateway,待会我给他解释。
就这一层就是我们把用户的所有的链接管理好,然后接下来有很多的服务了,这些服务层的话呢,它就会提供各种各样的服务,比如说账号那个就是你的房间那个大厅服务啊,你的每一局对战的服务啊对吧,你的社交服务啊。
你的这个存储服务啊等等等等等的这些服务,然后最下面一层是什么呢,有真正的数据存储层,有各种各样的db,就数据库那个那个数,以数据库为核心构建的这样的一个数据存储和服务层。
所以你可以看一个就是一个网游的架构啊,大概就是三层,这三层是最核心的,那么首先的话呢从一个最简单的就是呃也不叫巨匠,其实挺复杂的,就是这个这个连接这一层来讲,有两个概念。
可能是大家没有做过网游的人没有概念,第一个就是我们要做一个login server,就是我们那么复杂的这个就是这个游戏的m的服务器,它本身是一个一定要被保护起来的一个这样的一个东西。
所以呢所有的用户不是直接往服务器里面去连的,他都是要先跟我们一个叫login server训练在那里面干嘛呢,诶我们要完成链接,我们要完成握手,大家还记得我们前面讲htb对吧,其实在真实的时候。
我们一般会用http s吧,我们要经过更安全的信道的去加密,我们连到一起,我们要验证你的什么账号密码,这样我才能允许你登录我的游戏,好,我允许玩家你登录的,你假设用的真实的账号密码的时候。
我是不是就允许你直接开始跟我的各个server去token呢,诶不好意思,绝对不行,这个时候你要做一个叫晚关服务服务器的服务,这个叫gateway,为什么要做这个gateway呢。
其实大家一般会讲很多关于gateway的功能对吧,gateway有很多功能,我待会会讲,但是gateway在你从最至少是我个人的一个理解,它最核心的作用是什么呢。
也就是说用户他永远只跟gateway去talk,而gateway才会负责和内部的,比如说character server,角色服务器啊,lobby服务器啊,就是大厅服务器啊,什么邮件服务器去t。
大家想想看怎么去理解它,其实非常简单,对不对,它就是一个防火墙,也就是说因为虽然你有正确的账号和密码,假假设你这个黑客,你是不是会给我发很多很奇怪的数据,尝试去这个破解我的服务器的很多东西呢。
那么gateway它实际上就会去验证你跟我所有的信息的合法性有效性,比如说像游戏服务器,有的时候会遭遇一些比如像ddos攻击对吧,那gateway这边的话其实要有一定拦截的功能。
但是呢ddos更复杂的时候有更复杂的机制,我我的解压缩这些所有的操作其实都在gtav里面做完了,而gtav的话实际上工作任务是很重的,所以说很多时候无论是这个gateway的话,一般都会同时开很多个。
随着你玩家的数量越来越多的话,越开越多,他们你可以理解什么呢,他们就是大厅接待员对吧,他来去接待你们这些所有人,真的就是服务的这个这个怎么就跟那个你们去那个啊,比如说某一个公司这些人是提供服务的。
这些我们叫客服客服团队,他提供了所有的客服好,那么你有了这层link 3的话呢,哎接下来你就进入了游戏大厅,大厅很多时候呢它其实就是一个游戏,一个场景。
其实你可以认为就是大厅实际上就是一个特殊的游戏模式,只是在这个游戏模式里面的话,你不能放技能了,你不能跟别人去那个打架,但其实有的大厅里面,比如说玩家可以在主持人里面彼此去决斗对吧。
那可能这里面还要允许一定的这个就是玩家game play的功能,但是大厅呢很多时候会作为一个缓冲池,就是说让玩家在等待这些,比如说match making的时候,彼此之间能够被管理起来。
就是其实有的时候大厅它不一定真的是一个场景,有的时候就是个虚拟的大厅也是有的对吧,是不是也有一个虚拟的大厅啊,大家在里面玩的也很开心对吧,before,我们真的进入到一一局的这个游戏里面来。
那接下来的话呢就是大家不太熟悉的叫character server,其实在网游里面啊,就是用户的数据其实是非常多的,就是大家可能想象不到,就是说一个你你的一个角色,他的数据量肯定达到几兆甚至几十兆。
因为大家想想看,我如果玩一个游戏,我在这个游戏里面,我发生的所有的事情,我获得的所有的道具,我以前完完成了哪些任务,这些数据大家一开始觉得不会多,但是随着我们的游戏时间增长的时候,这些数据会越来越大。
而且这些这些数据的话呢,它会被各种各样的这个server都会去问,那这个时候的话呢,如果这些数据分散在比如说又是战斗的server里面去,又是交易的server里面去,又是聊天的server里面去。
那是不是这个数据就会708落,谁也管不了了,对不对,所以呢一般在网游里面有一个专门的叫character server,它其实负担特别重,他就会管理所有的玩家的真正的这个属性数据,而且这个数据的话呢。
它就相当于说你无论是发邮件也好,还是这个战斗中,比如说对方那个获得了某个道具交易成功了对吧,获得某个道也会战斗中,诶我我我这个突然受了一个很重的伤,那这个时候哦对这个有的时候不会马上同步与carry。
但是的话比如说我要问一下,我现在是不是装备了什么什么什么一个道具,那这些东西的话呢其实都是character server要完成的这个这个任务,那么接下来就是大名鼎鼎的这个交易系统了,那交易系统的话呢。
这个讲起来就很多了对吧,我们不展开,但是呢其实这里面大家记住有一个重要的属性是什么呢,就是交易系统它是有非常重的,我们叫做啊就是这个金融属性的什么意思,就是说你去写它的时候。
要求保证它的安全性是足够高的,每一笔交易要保证它的绝对的原子性和安全性,全部都要可以roll back,这样的话就是说这样去保证就是在任何的情况下,打个比方。
比如说啊对方的client交易到一半突然断断线了对吧,或者服务器突然发生了什么异常的情况,那么我们做的每一个transaction,它都能够被准确地记录下来,而不会出现我们担心的各种各样的异常的数据。
所以说这个地方的话呢,就是当我们作为一个m游戏的时候,这一点是非常重要的,那接下来也就是大家经常熟悉的这个搜索系统,那基本就是聊天啦,邮件啦,其实其实在网游里面的话,这些系统还是非常的重要。
而且它写起来其实并不简单,因为即使在一个小小的网游里面,我们还有一个叫什么拉黑别人的功能啊对吧,我还跟几个朋朋友一起组队啊对吧,开小群组聊天啊,分很多的频道啊,实际上依靠网游的这个搜索系统也是很复杂。
甚至有的时候比如说邮件有专门的邮件server对吧,诶他这个就分得非常细,为什么呢,因为你如果把它全写到一起的时候,当很多很多人同时在彼此发邮件,彼此聊天的时候,你一个单一的server它可能就会炸掉。
所以说呢哎这个social system也是要分成若干种不同的server,那么接下来还有这个match making,match making,其实对于很多网游来讲是蛮重要的,因为比如说我们做一个啊。
大概比方我们作为一个lgleon这样的游戏对吧,英雄联盟这样的游戏,其实最重要的就是说把不同等级的玩家诶,你全部都要说我要匹配,他要把大家这个这个这个ranking值差不多的玩家。
就是我们讲的这个大家的天梯分差不多的玩家,我们要把它集中到一起,这样的话你玩起来才会觉得这个好像相对公平对吧,那么其实match making的话,实际上在后面做了很多工作,比如说举个例子啊。
就比如说啊,如果我们做一款射击游戏,假设这是一个全球联合匹配的这个联网的这样一个设计游戏的话,其实match max系统是非常复杂,为什么呢,它不仅要考虑大家之间的这个啊。
就是说我们的这个rank值就是大家的那个scale,就是大家的技能是差不多的,我还要考虑你们彼此的延迟,我要尽可能把延迟相近,而且不要那么大的玩家匹配到一起,否则我才能保证大家之间的这个体验是ok的。
所以说其实比如说大家在玩很多moba游戏的时候,很多moba游戏它的match making系统都是最核心的系统,就是要确保所有用户的匹配体验尽可能好,当然这里面有个有一个特殊情况。
就是如果你远程的跟你的队友开黑的话,那他就没办法了对吧,那你们强行要在一起,那我只能配合你们了,对不对,但是呢一般来讲在匹配系统里面,如果你开黑的话,他一般会把开黑的玩家扔到一个单独的池子里面去匹配。
因为你开黑了,我真让你匹配那些开黑的人,否则那些人不公平对吧,你看这里面就是设计一款网游的时候,他考虑的东西其实还是蛮多的,所以那接下来的话呢也是一个大家以前可能不会太注意到的,一个网络游戏的一个架构。
就是数据的存储诶,这个东西其实啊就是大家想想看,如果我们要构建一个online gaming的世界的话,这个世界它是不是一直要存在的对吧,那我我的电脑,我的游戏可以关掉,对不对,但是服务器是不能关的。
而且这里面的数据随时随地都会去产生啊,这这这里面产生大量的数据,谁来存它,谁来保管它,谁来负责去调用它,所以其实在这个就是online game里面的话,数据的存储设计是一个非常核心的设计。
所以今天的这节课上的话呢,我们只能给大家点一下皮毛,比如说一般来讲我们会有三类的这个数据存储的这个服务,第一类的话呢就是这个关系数据库,比如像大名鼎鼎的micro对吧,如果你有钱。
你可以用oracle对吧,但是好像我很少听到网游会用oracle的,那么anyway,就比如说我们举一个mysql这样的一个一个数据库,那我们游戏中很多的核心数据,我们都是用这种关系数据库存储的。
但这里面其实讲起来就比较深了,就是说实际上在你一个网游的这个海量的数据面前的话,我们实际上采用的架构是类似于分布式数据库的架构,就是你同时写的时候,我会写到好多个并行的表里面去。
这样确保就是说诶我写的效率图的效率足够高,而且万一有一个服务器这个挂掉了之后,我的数据不会丢掉或者不会毁坏对吧,还有什么冷热表呀,还有这种各种各样的。
这个就是说我们的这个叫这个这个这个这个为在灾难备份啊,这些东西其实都是这个就是说做游戏的数据存储的时候,很核心要解决的问题,那么其实最早期的网游你们用的最多的都是关系数据库,但是呢随着游戏的发展。
你会发现一个很有意思的事情,就是说你会产生很多呢没有,那么需要严格的就是a你用关系数据库的逻辑,就是诶我一句query的才能获得的数据,为什么呢,因为关系数据库它首先存储的负载还是比较重的。
因为你一个数据存下去,他要把很多的建制建立一个他自己的关系表对吧哈希好,这样的话你用各种carry组合它才能查得到,但是很多时候比如说假设是我游戏里面最重要的log信息对吧。
大家知道一个游戏里面一天能产生的log,可以以这个tb为单位产生它的log,但这里面的话呢我经常又要去对你们的数据进行query,但这个query的话呢又不需要特别的及时。
但是呢我确实对这个数据要进行一定的检索,这个时候呢就是非关系数据库,它访问速度更快,更清亮的这种数据库又提上了用那个我们的这个那个需求,这个清单里面,所以比如像芒果db这种低轻量db诶。
非关系数据库它又起了一个用处,所以说其实在游戏里面很多临时性的game state啊,或者说一些logging的信息呀,特别是game state,我们基本上就用非关系事故直接往里面去当铺。
这样的话它形成一个非常大的一个数据集,但这个数据集的话呢,其实我们不需要那么高效率的,也不需要非常复杂的去查询,但这个数据对我们其实是有用的对吧,那现在再多讲一句,现在有的时候我们会用到。
比如像数据仓库也能够去处理m所产生的海量数据,大家可能没有感觉,就是说如果我们真的做一个m的话,实际上他每时每刻都会产生海量的数据,大家打个补偿,比如说你玩一个网游,如果你发现你的道具丢了。
你打电话给客服诶,客服小姐姐会说啊,你跟我说说你的角色id,你这个是因为什么事情丢了,那他可能过一段时间就真的能把你那个道具给你找回来对吧,那为什么呢,就是因为他要在这里巨量的这个数据里面。
这个log里面这个尤其state的这个这个这个村这个cash里面,把你的这个状态找到验证,你说的事情都是真的,然后把这个道具还给你,实际上这对于一款网游来讲也是非常重要的。
那么最后呢就是这是现代游戏中用的也越来越多了,我们叫做in memory storage,就是说叫内存数据库,这是一个现在比较啊还比较新的概念,其实也不算特别新吧。
就是说我们发现随着游戏的系统变得越来越复杂,比如说一一款这个网游跑起来之后,我们可能有几百个server去从事探跑,那么这些是分布在我的内存中,那实际上呢我会产生很多的游戏的中间数据,这些数据的话呢。
如果我只是road扔在哪,那么我们对它的查询和管理都会非常的复杂,这个时候其实我们需要有一个效率非常高,能够帮我们把这个数据管理起来,但是呢他又不会那么重的存写读写磁盘,因为读写磁盘就慢了嘛,对不对。
那我们只需要在内存中把它保存下来的这个数据,说那个数据管理的工具,所以内存数据库的话呢,现在在就是游戏那个游戏的服务器端架构中也是越来越这个popular,那这件事情的话。
我个人倒是蛮推崇这个内存数据库的,因为它实际上当你去你想想看我们有那么多的server对吧,这个然后这些server中间一些中间数据公共数据怎么存储。
其实用一个内存数据库可能是一个更好的更整洁的一套处理方法,所以说的话呢这个其实就在我们的一个做一个online game的时候,你真的做一个m的时候,你会发现数据存储这一趴其实非常的复杂。
所以说就像我们最早在讲做游戏引擎的时候,我会跟大家讲说,你要先从数据结构开始去思考,其实当我们去架构一个mm的这样的一个网络服务器的架构的时候,我会建议是从数据开始思考,因为你把数据想清楚了。
你上面的各个系统的结构它也就清楚了对,所以说这其实就是一个m的一个大致的框架,那么这个框架呢基本上你是能做一个诶,有点那么意思的网络游戏了,但是呢这个好日子一般都不会长久。
如果你的这个游戏引擎做的非常的好,上面做的游戏也很成功,你接下来会遇到一个什么问题呢,诶你就会有越来越多的玩家加入你们,当你面对了1万多个玩家的时候,你觉得还好,我一台服务器就能扛得住对吧。
当我面对的是,就比如说10万个同时玩家同时在线,假设你很厉害,你做了一个百万玩家同时在线的时候,你会发现你的服务器根本不可能,无论你怎么优化,你都看不出百万很夸张了,比如说就10万玩家在线的时候。
你的服务器可能就扛不住了对吧,那这个时候你该怎么去做你的架构去处理这件事情呢,那其实解决方法就是分布式的系统,也就是说把服务器的各个服务把它变成就是可以用多个进程,同时帮你满足的是一个分布式系统架构。
而且分布式系统里面这些服务我可以加可以减对吧,那这样的一个架构如果做好的话,那恭喜你,你的这个服务器架构呢它就开始有弹性,所以说如果我们要讲大家要做mmo的话。
就是massive multiplayer对吧,我们既然lan我们是massive multiplayer,那你就可能要考虑分布式的架构了,好那分布式的架构呢其实是技术原理上讲还是蛮简单的对吧。
比如说我前面举的cart server对吧,我同时管理在rt里面就1万个玩家的属性的话,那我一个进程就可以了,但是现在我假设同时有10万玩家登录的时候,我管理10万玩家的数据的话。
那我一个进程可能就一个cpu就吃爆了对吧,那我就可能必须要拆成十个服务好,这个时候呢你就要解决很多问题了,比如说同样一份数据不同的就是就分开来的这service访问他的时候,他彼此之间不会冲突。
不会产生这种竞争啊,不会产生死锁,对不对,然后呢,当你网络发过来的消息的时候,有的时候因为这这么多服务,他可能说不稳定嘛对吧,那你这些消息冗余的发过来的时候,你能不能保证这个处理的叫密等性对吧。
那密等性的意思是什么呢,就是说一个消息发了冗余了之后,你也不会出现异常,你能够自动地把那些荣誉的信息给过滤掉,执行你正常的逻辑,而且你的系统一旦分布式的时候,中间总有地方会挂掉。
那你挂掉之后也不会影响你整个的逻辑,其实当你真的做一个mm的分布式系统的时候,那你的服务器各个服务,网络链接出现各种各样的问题,这就是你的日常,所以我们经常说什么什么某某大厂是土豆服务器。
这里面要跟他们去这个小小的替他们解释一下,就是说如果真的要做一个鲁棒的服务器,这件事情其实是很多东西都要考虑的,否则的话你可以把业务逻辑做对,但是呢它的健壮性就是有问题的对吧,还有什么呢。
就是说如果我把这个服务分得特别开的时候,如果有一个服务产生了一个bug之后,如果不去做好必要的防护的话,这个bug可能会在整个这个分布式的系统里面来回的震荡,震荡也就算了,还会什么呢,还会放大。
这个就是本来是一个小型的bug,如果在在第一个服务里面没有被及时的给他,就是检测到,并且把它按掉的话,那这个错误的数据可能会传递到另外一个服务,另外一个服务面假设没处理好,他会把这个错误再放大。
然后再传递出去对吧,r p c互相调来调去,有甚至有可能会出现什么情况呢,就会在这个系统里面无限的震荡,唉这个情况我讲的不是这个编出来的,其实在就是真的实战型的网络游戏团队里面,他们真的遇到这种bug。
就是一个很难被检测到了,低频的一个bug,一旦他开始在系统里震荡的时候,首先你很难发现它,但是它会导致你服务器的性能急剧的下降,然后大家只能把那个rpc dump下来看。
发现有一些rpc很诡异的被吊了无穷很多很多次对吧,这就是一个我们讲就是我自己蛮喜欢一本书,叫复杂系统嘛,就是说其实当我们构建一种分布式系统的时候,你实际上用的整个架构思维方式就是复杂系统的范围了。
那在复杂系统里面确实有这种震荡的这种现象的存在对吧,那么你把服务分得这么开的话,你还要解决什么呢,还要解决他们彼此之间这个形成了各个业务的它的数据的一致性对吧,就是同样一个业务我们处理完之后。
a的服务和b的服务产生的结果必须是一致的对吧,这样的话呢我们才能保证整个业务的这个绝对安全,所以这里也不一展开了,就是说当你把一个简单系统一旦变成分布式系统的时候,实际上就会对你代代码的安全性鲁棒性。
而且他的这个这个要求就会高很多很多,但是它的好处也是显而易见的对吧,那么一旦我们有了这样一个分布式系统的话呢,哎我们就可以把load去balance去,就刚才那个例子。
比如说我的character server对吧,我要同时管理1万个在线的这个玩家,我可以把它分成十个,这样每个人管1万个诶,我觉得能处理好了,对不对,那么这里面就有一个叫负载均衡的概念了。
那为了解决这个负载,为了实现这个负载均衡,实际上有一个很重要的这个这个算法,这个大家可以就是关注叫什么呢,叫一致性哈,希他怎么去解决这个问题呢,我们先讲还是举刚才那个案例,就是假设我有1万玩家诶。
我现在有一个有有一个有一局战斗正在如火如荼地进行,这个战斗里面的话呢,我这个玩家想这个这个获取了一个道具,这个道具的话呢再捡到了,捡到了之后呢,那我就要写到玩家的数据库,对不对,那我按照我们的说法说。
这个玩家的数据应该存在哪,应该存在character server里面对吧,少在服务器端,现在跑的是这样的,好,我讲这个角色的i d叫101对吧,或者叫104,我们叫104吧,好好ok。
我现在有十个这个character server s1 s2 s一直在s 10,那我到底这个是我自己是存在哪的呢,那我这个这个这个我这个game server我就一路的去问。
说这个十个server s1 ,我是不是在你这s一说我不是好s2 问是不是我不是,我一路问下去,你想想看这个效率是不是非常的低对吧,那有没有一种方法能让我快速的知道我是在哪个server上。
那大家说很简单嘛,我就分配嘛对吧,我就我就等等比的分配,但是这里面有一个假设的前提是我知道我现在有多少个character,但实际上的话大家如果对游戏有研究的话,你会发现character id啊。
它是不重用的,它会一直去加的,但是真的在一个网游里面,玩家会不断的这个登录,就是增加新的character,对吧,诶但是有一个玩家logo的离开,有些character又会有些数字又是空的。
所以说你多少到多少之间分配到哪一个character server,这件事情其实是不稳定的,另外一个的话呢就是说我们的这个server的数量,比如说一开始只有2万玩家在线的时候。
我两个server就够了,对不对,这是三位玩家的时候,我就变变成三了,你如果一开始约定好说奇数位都属于server,一偶数位都属于sv没问题,但是我第三个server来的时候,哎你怎么办对吧。
你可能这个事情就不对了,那我第四个第五个第六个呢对吧,而且中间假设我又发现这个就是用户量变少了,我又把一些server要释放出来,那我释放的时候,那我那些角色怎么办呢。
所以这其实啊真的大家真的去写一个分布式系统的时候,你会发现其实类似的问题非常的多,这一类的问题怎么去解决呢,有一个很经典的算法叫一致性哈,希它的核心想法其实非常的简单,就是说无论对于服务器也好。
对于play也好,我的设计对应的哈希算法,注意啊,服务器和那个player,就刚才那个例子里面,我设计两种不同的海峡,比如服务器我用你的这个ip地址端口号作为哈希值,我算一个值。
但是呢我映射到比如说0~0到二的32次方减一,这个范围里面对吧,然后呢我把你的这个player id我也作为一个哈希值,我也算出一个也到零的30 30 33,然后呢我把我的服务器放到这个圆环。
我们把这个东西把这个整个数据啊变成一个环,注意啊,只是环就是说零的下一个元素是什么呢,是二的32次方减一,我行那个花为什么是二的32次方减一呢,我假设用integer作为我的整个一个上限去管理它。
我就是用一个int好,那你会发现啊,在这个环上面,这个服务器有一个分布对吧,我的所有的数据它其实也有个分布,那我定义一个非常简单的规则,比如说像逆时针规则对吧,也就是说我所有的cc的数据。
我就逆时针方向找到我最近的那个server作为我的存储,其实这个怎么找的,因为server的数量不多嘛对吧,每个server的k值我知道,所以我很容易就知道说哦,比如说我知道s1 s2 。
我就知道我现在实际上是在哪个server上,我找那个server那个数据我就能找到server存储数据也遵照这个规则,那我假设有这样的一个哈希算法的话,是不是我就能够很方便的把这个数据分配到这个。
就是整各个服务器上去了,而且的话呢但这个算法其实它有一个前提啊,这是我个人的理解,就是说你的这个哈希算法是要认公式,是要认真设计过的,因为那个阿强如果设计的不好的话,对吧。
或者说player分布的过于集中的话,那你这个负载它其实也是不均衡的,这个里面的话其实是啊,后面还有别的方法可以优化的,但是这个其实是一个基础性的要求,那么好,这个时候如果我删掉了一个server对吧。
我们先讲个简单的case,就是删掉一个server,那其实它非常的简单,就是说诶假设这里面我把s2 退出去了,那在s一和s3 之间的那些就是原来放在s2 上的那些server。
它就顺眼的按照逆时针方向找到他下一个server,那找到的是谁呢,他找到了s3 ,那这样就意味着我把s2 以前的那些character全部扔到s3 就好了,他这个算法就是这么淳朴,同样的道理就是反之。
如果我们增加了一个server的话,是不是就相当于在这个圆环中我又加一个这个这个中间的那个server节点,然后呢这样我就把这个珍珠的话在中间又分开了,其实你想想看。
不就是一个珍珠中间无限分的一个问题嘛是吧,所以它非常的简单,那这个好处是什么呢,就是说它为什么叫一致性哈希呢,就是说只要你的player的这个哈希算法哈希函数和server的哈希函数,只要定下来之后。
那我不需要进行任何的rpc的cory,我就能知道就是你的每一个player到底属于哪个server,这个事情就是他就把一个一个一个复杂问题变得非常简单了,因为大家知道就是在真实的一个服务器架构里面的话。
如果我我让每一次都去问轮询一遍,所有的server说诶我在不在你下面的话,这个成本其实是非常非常高的,就是它的成本,一个是rpc本身的成本,大家想想看r p c怎么怎么发生的,对不对。
我的这个这个指令对吧,先要去serialize,然后呢网络传输那边还有deserialize,然后呢出去之后,而且一般来讲一次cos是两次rpk,我先去问一圈。
过一会儿等他另外一个rpc回来告诉我说哥们儿你到底是在还是不在,所以而且他你真的去问他的时候,那边的话大概率会做一个query,那做一个就是比如说至少是啊log n的一个复杂度去,才知道我在不在你上面。
所以这个其实也是这个就是说一致性哈希的一个很好的地方,那么回到刚才我讲的这个哈希函数呢是很难设计的,特别好,可能会出现就是说诶如果server的数量不够多的时候,就算多了。
所以这里面还有一个简单的算法叫做virtual server node的方法,就是说我会在这个环上,我会再随机哈希出来几个server。
然后呢这些virtual server的话呢又会对这个珍珠进行一个更详细的划分,然后呢我这个vivirtual server,比如说我会映射回原始的server,实际上也能让这个负载相对均衡。
这个就是一个很细节的算法,如果大家真的去用这种一致性哈希的方法去写这个分布式系统的话,大家可以去用一用对吧,但是这个我相信学104的同学,如果你刚刚入学写基础的时候啊。
我不建议大家一上来写这么复杂的系统架构,因为这个架构的话,其实我刚才我一直在讲,就是分布式系统,其实是一个初步想想很简单的一个系统,但是你真的把一个很复杂的逻辑放进去的时候。
你会发现他简直就是一个很简单的功能嘛,写一两个月是很正常的,因为就是写写其实很简单,但是你去真的上线测试,然后各种各样的边界情况处理掉的话,周期是非常非常的漫长的,ok所以我我自己跟大家分享一下。
就是我自己的经验,就是说当我们的引擎写到这一块的时候,我们如果在线上出了一个bug,我每次都说,如果这个bug我能确定的重现它的时候,那这个bug我一定能把它解决掉,但是如果这个bug是什么。
你服务器上线跑了三天之后,偶然的会有一次出现了这个非常恶劣的bug的时候,一般来讲写分布式系统的这个程序员就会非常的痛苦,因为他最恐惧的是说我都抓不住它,真的分布式系统。
所以它的架构对这个大家的这个要求是非常非常高的,那么其实呢如果我们采用了分布式系统的话,还会产生另外一个问题,就是说哎呀有太多太多的服务了对吧,这么多的服务对吧,服务本身还要被管理,为什么呢。
因为这些服务会不断的被创生,刚才我们讲了句,刚才那个例子就是负载均衡的例子里面,你玩家登录多了对吧,我要创建一些什么character server,game server,让你去处理我跟他的业务。
但是呢诶玩家这时候到了半夜了,很多玩家走了,我这服务还得关掉,对不对,而且呢我起了这么多服务,几百个服务,上千个服务,有的服务可能一绕绕到一半,比如说自己产生了逻辑错误,他自己挂掉了,对不对。
你这个别人都不知道,那接下来其他的服务就跟着一起,就是灾难性的雪崩式的,大家就一起崩溃了,对不对,那么其实在这里面,而且呢就是说这么多服务我怎么找到彼此对吧,其实大家如果观察一下。
真的一个啊mo游戏里面的话,各个服务之间的连接啊,真的像一团乱麻一样,我以前特别喜欢用一个词儿叫spaghetti对吧,就是一种一种意大利面的特点,那种意大利面的特点是什么呢。
就是说这个这个真的就搅在一起,你你想吃一根面条是不可能的,你一筷子下去,可能一碗面就直接起来了,那种对吧好,所以这个时候呢其实就产生了一个非常重要的一个需求,就这些服务怎么去管理。
那这里面的话呢就是service management的,实际上就是叫最更专业的说法,叫服务发现,实际上也就应运而生了,这里面无论是阿帕奇的这个zoo keeper对吧。
还是这个etc d e t c d这个名字其实特别有意思,好像e t c是个中国人写的吧,就是那个e t c的话,etc的意思就是好像linux下面的话就是分布式统有这么一个目录对吧。
所以说呢就是啊这个就是有这个目录,所以e cd可能stands for distribute,所以说其实这些工具的话,他就帮你说把各种的服务就是你只要创建好的时候,诶,你通过一个简单的注册流程注册。
因为我我们自己我喜欢用etcd的好,我们就注册到我们etcd里面去了,o好,你有有一段这个它的标识,它的地址,这样的话任何一个其他的服务想找你这个服务的时候,只要用这个标识,他就能找到你对吧。
你可以这个时候呢你去query说这个服务有没有,他会说有,然后你跟他说诶这个服务以物有什么变化,你得告诉我啊,对不对,比如这个服务挂了对吧,这个服务或者有什么异常,你得告诉我,所以你要去卧室。
它e d c d也说可以,没问题,我帮你看着他,只要这哥们儿有什么风水走动,我马上就告诉你对吧,另外一个的话呢,我起了这么多服务,假设有个服务已经挂了对吧,那我etcd演的发现说诶哪个服务内存已经爆了。
他自己就死掉了,我把这个进场给杀了,诶,同时的话呢我会通知所有的观察者,那么如果需要重启这个服务的重启。
其实这件事情的话就是这个service的recovery discovery或者是service的管理的话,实际上当我们在做一个大型分布式系统的时候,是非常非常重要的。
那么对于一个mamoo的引擎来讲的话呢,这也是大家必须要做,我个人觉得就是在未来是大家必须要做的东西,否则的话这个服务器其实挺难嫁的,因为一一款网游的服务器跑起来真的是至少上百个服务吧,要起来。
而且比如举个举个例子吧,比如说房间服务器对吧,大家那个冲进来打一个游戏,开个房间,打个游戏,开个房间,那这个房间就会不停地创生对吧,要加和减,当然有的房间我可以reuse了。
但是这里面的话它的管理实际上是挺复杂的,所以说的话呢服务管理实际上是一个非常重要的一个东西,所以讲到这一块的话呢,基本上我们的一个网游的服务器架构就是这么一个用用了这些工具,但是大家听完这一节啊。
大家千万不要觉得我就能做过网游了,实际上真的去做一款这个mo的时候呢,这里面基本上每一页它都有很多小细节,大家可以在里面去去去思考,实际上大家现在听到这里的话,大家去做一个对战游戏,没问题不大了。
但是你如果想做个m的话,还要补很多,其他的更多的技术细节和知识体系好,那接下来的话呢就是讲一个比较实战的两个问题了,第一个问题就是说带宽要去优化,这个大家会觉得哎我们学了半天做网游。
为什么要学带宽优化呢,因为带宽优化真的非常重要,为什么呢,首先它会和你的这个游戏实战的这个成本息息相关,因为其实网络的带宽实际上对这个就是说游戏的影响特别大,特别大家想象一下,我做一个mm的话。
特别是那种大场景,其实它的数据量是特别大的,就算说ok我不care对吧,我觉得我可以付得起这个网络的钱,但是这里面会产生另外一个问题是什么呢,就是说如果你的数据量不去做优化的。
因为这是网络协议本身保证的,大家还记得我们前面讲了一些网络协议对吧,如果你的数据量过大的时候,它就会产生拥塞,拥塞的话,它就会产生latency对吧,而且呢就是雷特在过大的时候,有的时候甚至有的地方。
比如说有些网关他会主动的帮你把这个网连接给你掐断,因为他要保证,比如说我们进了小区,小区配了一个策略对吧,你这一个人,你的网关需求量太大了,他就把你给掐了,因为什么呢,因为你这样用的话,其他人怎么办。
其他就没得用了对吧,所以说其实对于带宽的优化,其实对于网游的体验来讲是一个非常关键的一个需求,那么怎么去计算这个带宽的,其实讲起来很复杂,实际上也比较简单,就是说第一个玩家的数量对吧。
我玩家到底传输玩家数量有多少,第二个的话呢就是我大概多长时间跟用户做一次数据沟通,那其实对你的数据量是不是也是有影响的,诶那我也问题不大,但如果我每次都更新一大堆数据的话,那显然带宽消耗量已经比较大。
所以其实我们在进行带宽优化的时候呢,我们实际上也就是对这三个点进行优化,那么好,那首先想到的是什么,a就是数据压缩,数据压缩这个事情的话,那就我这边当然大家知道就是有很多ononline的这种。
就是说流逝流数据的压缩对吧,这个呢在有些网游你不会用,但有些网友你们不一定会用,因为还有可能考虑这会让get way的负载太大了,因为gateway本身还要处理很多很多的东西,其实在网游中啊。
最常用的一种数据压缩方法是对这个浮点数转成定点数,也就是说比如说我们表达一个人的位置,大家想到什么是vector 3 x y z对吧,我的坐标对吧,三是什么,三个float。
一个float是四个bite对吧,所以我还要花12个beat能表达一个人的位置,同样的其实vex 3的东西特别多,比如说我的速度,我的velocity对吧,又是个vector 3,对不对。
又是x y z,对不对,whatever都可以,但是大家想想,那我去表达,比如说我的位置的时候,我需要用vector 3,首先在很多游戏里面,比如说我就是个大致基本的2d的一个品牌。
或者说虽然我不是2d的,但是我人都是贴地的好吧,甲这种情况下,那我是不是只要传一个xy就可以了,我可以少一个channel,对不对,好第二件事情就是说如果我这个世界不是特别大。
其实我用一个16位的定点数,是不是就能够把精确到比如说以厘米为单位的这个人的位置呢,其实很多时候其实是可以的,所以呢我们就会想方设法的把一些浮点量变成定变量,比如说我们要表达一个人的朝向。
orientation quarantine的时候,有的时候我们就会用bt 4个beat表达一个人的朝向就可以了对吧,一个或者一个character的朝向,所以这种就是就是那个quantization。
就是说我们把浮点数变成一个定点数的这种方法的话,而且这个实战中的效果是非常非常好的,那么所以呢有的时候为了配合这个方法,我们甚至会对游戏地图进行分区,就是我们每一个区的时候,我们不会把这个区弄得特别大。
这样的话我用定点数存储一下你的位置也好,那实际上就可以表达得非常的准确,所以这件事情的话,大家千万不要小瞧,至少很多时候很容易的就会下降一半以上,那这个其实对网络游戏的这个体验稳定性帮助其实是非常大的。
所以这也是我觉得在压缩带宽,我最想讲的就是,首先要用这种简单的方法进行数据压缩,那么第二种呢,其实哎稍微有点高深了,就是说我们整个世界上有那么多的对象,对不对,那我们是不是要把所有的对象都同步给我呢。
大家会说不需要,为什么呢,因为其实只有一些我关注的对象对吧,比如说我要做开放世界的游戏,方圆这个什么这个几百平方公里,但是他们对我重要吗,不重要,对不对,很多时候我只关心我方圆1百米。
2百米之内的这个情况对吧,那么那个所以的话如果是一个动作,这个这个这个这个近战游戏的话,我可能关注的范围会更小,所以说其实我们只要把相关的物体传递给我。
这件事情我就可以去这个我做我本地的各种game play了,所以呢其实呢这里面就有很多的方法了,比如说我把这个世界分成一个个的这个静态的zoo对吧,把玩家呢放到这个zone里面去。
那这个zone呢打个比方,比如说我们做个开放世界,我们每个市每个区域之间我是用pto连在一起,那put to隔开之后,那我在这个比如说城外发生的事情跟城内就没有关系了对吧。
然后呢我进到一个比如说某一个巨大的building,building进去之后,诶,这样的话我在每一种情况下,我们彼此的这个信息全部都能隔绝掉,这种静态中的方法的话。
就能非常有效地屏蔽掉大量的不必要的这个状态同步好,但是的话呢如果这个世界真的是开放的,我们不希望有photo,就不希望有这种传送门的这种情况发生的时候。
诶有一个很重要的概念叫aa aa in interest就出来了,就是说我们以我自己为中心,就是以每个play为中心,我只需要关注我周围的数据的情况。
唉这个就是大家如果真的有机会加入到一个网游团队的时候,你可能如果你在做这个game server的话,可能你一上来接触到的就是这个m a o i的概念,你在做任何消息的rpc同步啊,做任何的状态同步啊。
结算的时候,你可能首先就要考虑说哎我只和我的a2 区,你们的物体有关系对吧,如果我的一个比如说什么技能啊,我的一个什么开枪动作啊,其实诶都要考虑a2 的这个范围好,那么这个l o l的话呢。
其实讲起来非常简单,最简单的一种方法是什么,就是我给个半径对吧,在我这个半径之内的都是我care的,比如说我们是一个这个这个比如说是一个射击游戏,可能半径3百米,5百米就是我的范围,5百米之外。
我不管了对吧,我的狙只能打5百米,实际上真实的世界狙可以打的很远,但是在游戏里面的话,因为那个空间是被扭曲过的,所以你一般打到5百米之外都已经觉得很远了,那其他的事情我就不管了。
虽然你这个地图可以开放做的无比的大,那最简单的做法就是在整个世界里面进行一个query,那这个query的话啊,如果角色数量不多,没关系,你就暴力运算,但是如果角色数量多的时候,这其实就会有些问题对吧。
那怎么办呢,哎其实大家想到了一个最简单的方法,那个我们的传统艺能就来了,在空间中画格子,是的这个大家千万别笑,在空间的画格子实际上是个非常鲁棒的好用的一个方法,比如说在早期的网游中。
我们最常用的一个尺寸是多少呢,我印象特别深,就是1百米就1百米乘1百米是个格子,然后一个大家如果有幸看一些,就是古典时代的这个网游的这个这个代码的时候,你会发现就是服务器的架构。
很多时候都会以1百米为1百米定义一个zone或者叫region对吧,然后的话呢这样的话我玩家在一个zone里面去站着的时候,诶,我方圆再去扩展两个,我大概这个这个方面你们的这些人我都会去看的到。
所以说的话呢就是说我们每一个角色,我就把它扔到这样的一个nt里面去了,那这样的话呢就是我可以根据这个这个我自己的,比如说我们假设这个人感知的范围是3x3的zone,都是我的a o l唉对吧。
就我当前假设在在这个zone里面,那么我3x3的周围的这个纵都是我关注的,那好那这个时候我这九个字关注了,那好那如果我就形成了一个我的a o l的这个一个列表。
那当有entity进入和离开这个33怎么办呢,哎其实很简单,他给我发一个event就好了,就是说如果有一个人诶进入到这个这个zone的话,那我的event就会被更新,如果我离开的话。
我的我的这个a2 就会被把那个删除掉,反之亦然,就是对于我自己来讲,我进入到一个zone,我我退出一个zone,我也会notify其他人,这一次因为玩每一个玩家进出众都是相对低频的事情,大家仔细想想啊。
听上去好像很复杂对吧,大家仔细想想,大家都知道,其实呢这个频率并不高,因为1百米乘1百米,你在游戏里面自己跑跑,怎么着也得跑个几秒钟对吧,实际上很多时候它只要处理几十个这样的消息可能就可以了。
并不会让大家想象的那么高频,这样大家的每个人的a2 都会相应的得到这样的一个更新好,那么这个时候呢其实就是我们就可以用这种简单的空间画格子的方法。
我们就可以去query到我们的这个就是周围的这个a2 的这个区域,那这个方法呢实际上我们称之为叫一个空间换时间吧,就是说你实际上还要在空间形成一个点状的分布。
有的时候大家会argue说这个格子到底是1百米乘1百米好呢,还是这个更大的好,因为有的时候你会发现就是格子画的太密,那你这个zone就太多了,格子画的太稀疏。
那一个zone里面的character也太多了,你本身的效果也没有达到,所以另外一种方法呢,其实就是诶大名鼎鼎的数据结构里面的方法叫什么呢,十字链表法对吧。
十字链表法其实呢呃说实话作为一个性情很有意思的,对不对,就是说诶我们以这个x轴为方向,假设我们假设空间我们认为是一个2d分布的,这个我们简化一下。
那我沿着x轴把所有的a把所有的那个所有的object去这个排个序,然后呢我再沿着y轴把所有人排个序,那实际上我自己在这个x轴y轴哪个位置,我是不是都知道,然后呢我往两边都去找,那我就可以找到。
就是比如说我说我的这个ai的半径,比如是150米,那我就在x轴上往前走150米,往后走150米对吧,那实际上我就能找到一个圈儿,一群这个一些一些的这个这个nt的id,然后呢我在y轴上做同样的事。
我又能得到另外一圈的id,那么如果你在x轴的那个排序的列表上和y轴排序列表上都满足,都在我的这个这个这个满足的话,就是两个set有两个集合的交集的话,其实这些人是不是就是我的那个aoi。
你们的这个案例挺大家想想十字链表是不是就非常的简单,它就能解决这个问题对吧,那么同样的就是当有任何一个物体在这个世界上去移动的时候呢,实际上呢我就会相应的去notify别人。
我去更新每个人各自的这个ai这件事情也就能完成了,所以呢其实十字链表呢本身啊还是一个非常高效的一个一个算法,那么就是说当当然了,这里面大量的物体都在高速移动的时候,这个14连秒稍微有一个维护的需求。
但其实我觉得你画格子的方法还是用十字链表的方法,都是大家各自的选择,我觉得这里面本身没有什么太大的这个啊,这个这个就是说一定说谁好谁坏,诶我们可以根据你现在所在的位置,我把这个世界我已经预先生好了。
visibility的这个这个set,那这样的话在不在我的p vs里面的东西,我可以不关注,这个其实也是一个更宏观的帮助我们快速的filter out,我们不关注东西的这样一个点。
那么这个对于这种就是说空间分割能力啊,分割感更强的这种游戏来讲会更也更实用一点,所以说其实啊就是a i是一个很宽泛的概念,但是呢在网络游戏做同步的时候,其实如果想降低带宽,是一定要做a i的。
因为而且还有一点,它不仅是降低带宽,其实也节约了,这个是服务器端和客户端的算力,为什么大家想想看啊,你本身要处理要对逻辑状态进行更新,大家想想这个负载是不是很大对吧,而服务器端就更惨,把它传送出去。
所以服务器端那边的传输的压力,其实你有时候你去理解一下网游的服务器端的话,很像什么呢,我自己的感觉像是一个快递公司,你知道吗,然后每一个这个一client像是每一个家庭住址,然后呢那个服务器端就很惨。
然后呢pp的发给每个不同的地址,所以服务器端其实非常的辛苦,所以我们自己玩游戏的时候,我们觉得很简单对吧,我们在一起能跳能看,但实际上真的要大家去做引擎的底层去实现这个功能的时候。
大家会发现它其实还是挺复杂的,很多的细节好,所以呢其实啊即使我们有了这个a i的这样一个东西啊,实际上在一真正的网游设计中的话呢,我们还有一个方法就是我们会降低这个server的这个传递数据的频率。
其实这里我们会用到一个细节,就是说特别是对于那种啊,比如说近战型游戏或者什么游戏的时候,我们会把月在近处的jus,我们的这个object传自动频率会略高一点,比如你身边突然疾驰过的一辆车。
跑过去的一个人,那稍微远一点,比如对方已经在50米开外,比如说三次两次对吧,比如说1百米开外,我每隔一秒,比如1百米到2百米开外,我每隔一秒同步一次,如果对方没有进行过多的急转弯。
因为其实前面讲了那么多的差值和外差的算法,它基本能保证这个东西的平滑的运转,而且那么远的地方你不一定注意到,所以其实在真的这个网络游戏的这个架构里面的话,我们会做这样的一个策略。
就是说根据它以我主体的远近,我们会调整这个信息同步的频率,这样的话才能够让这个带宽最大利用,最大效率的利用起来,所以呢这个就是对带宽的这个优化好,这段讲的稍微的有那么一丢丢的枯燥。
但是同学们要这个不要在乎他枯燥,他真的很实战,如果你真的做做一款网游的话,这个能帮助你真正的节约你的这个运营成本好,接下来讲一个大家会比较感兴趣的问题了对吧,首先的话呢是高能警告,就是说这个反作弊对吧。
还是这个这个这个这个这个非常高危的一个行业啊,就是说大家千万不要把nt去掉了,搞作弊了,作弊你就完蛋了对吧好,那为什么反作弊这件事情非常重要的,首先我们大家都在玩网游对吧。
我想想谁在以前玩单机游戏的时候,我们有没有谁没有用过这种各种作弊神器呢,反正我承认我用过对吧,但是呢当我们去玩网游,网游的时候,一旦作弊的时候,对游戏也好,对玩家也好,都是巨大的伤害的,游戏就不用加了。
对不对,对于玩家来讲,这个我们会有个调研会告诉你说这个这是一个stea的一个调研,他就说70%的玩家如果发现别人在作弊的时候,他就不想玩这个游戏了,确实是这样的,就是这很奇怪。
就是我我自己玩单机游戏的时候,我可以作弊,修改我的血量,修改我的这个金钱,但是我玩网游的时候可以我作弊,但是我受不了别人作弊,所以作为任何一款online gaming的时候。
反作弊是一个非常非常核心的一个系统。
这是必然要面对和解决的问题,但是呢其实呢因为它变成了一个online game了,那么作弊就会变得特别特别的,这个怎么说呢,用一个词儿叫vulnerable,就是你会变得非常的脆弱。
这个黑客会从各种角度来攻击你,比如说它可以直接修改你游戏的带那个那个主体,它把你的内存给你改掉对吧,甚至给你注入一段代码,你怎么办,这个就很麻烦,甚至把你的客户端给你破解掉,这个就很痛苦。
那么他也可以不动你的客户端,他把那个系统的底层d k给你破解掉,比如说最著名的一个叫透明挂对吧,怎么怎么怎么做的,他可以这样。
他把d3 d的就是这个rendering sdk的底层的那函数入口给你重载掉,所以当你一旦扩一些,比如说画一个画面片,画这些东西的时候,它被你全部变成线框了,所以在这个线框的世界里面。
所有的东西就一目了然了,对不对,类似于这样的东西,就是说你根本防不胜防,那这样的话,比如说大名鼎鼎的某某精灵对吧,它就能解决这样的问题,你你你还真的是很难去提第二个,然后呢还有什么呢。
就是说诶我不去动你的这个系统,我也不去注入到你的这个这个你的客户端,但是我干什么呢,劫持这个事情,甚至可以用第三就第三台机器把你的网络通讯给你劫持掉,这样的话我给你发一些假的消息对吧。
那么这个时候其实你也会产生很多的作弊的方法,所以说其实啊作弊他真的是很难很难防的,那这边我就一次跟大家讲一下,就是作弊的各种各样的点,比如说第一种就是我去查内存,其实大家会发现就是说在作弊里面。
我们很多说要抓这个用户的数据嘛,其实最简单的方法就是说我去定位内存中,哪个数据存的是你最敏感的数据,然后我把你这个数据给你改了对吧,因为特别是很多在客户端做校验的这种啊,游戏逻辑的话。
实际上很多作弊器都是在你客户端找到你的数据,把你这个数据改了,所以这个方法呢对于单机游戏比较有用,比如说我们以前玩一些单机游戏的时候,我们会用这种方法去锁定我的血量,找到我的血量内存内存值,那怎么办。
我就会一直改它对吧,那这样我就会在我服务器端产生很多的,那个就是如果啊这个我的数据就一直不变,但这里面怎么办呢,其实很多时候我们可以给客户端诶,我们给客户端加个壳儿,把客户端加密。
然后呢到你这个游戏运行起来的时候,我实时的在内存中把这个客户端给给这个解掉这个壳,然后我这个ex可以运转了,这件事情呢它本身呢应该来讲是非常复杂的,是专门有公司给你的客户端加一个壳儿帮你保护。
特别是你在做刚才我讲的,比如说我做个设计游戏,我这个设计游戏是用的client side detection的话,那事实上我的客户端的保护反防止被别人注入,是我最核心的一个需求。
那么我相信就是很多游戏其实是做了这个房屋的,但是我跟大家讲一个很悲观的现实啊,至少以我所知道啊,几乎所有的客户端加壳的这个这个服务好像据我所知都被别人攻破了,我也不知道那帮人怎么那么强。
但是如果因为你要是有真的客户端加壳的很好的软件的话,我肯定是第一个用的,但是实际上是非常的难,那么这里面呢还有一种方法叫什么呢,其实叫内存混淆,什么意思,就是说呃我可以把我最关键最敏感的数据。
我不希望那个就是那个那个写外挂的人抓到的话,我可以对它内存进行混淆,刚才有同学问我说那个真同步的游戏怎么样防止别人作弊对吧,其实真同尊同步的游戏,防止别人的就是偷窥嘛对吧,你看到很多。
你不要看到这个信息嘛,那其实有一种策略就是内存混淆,就是把我高度敏感的game play数据在内存中进行加密,只有在用的那一瞬间给它给它给它读读掉,然后再写进去的时候呢,我又把它再变掉。
但是呢好像这个东西啊比较难破解,但是好像也有人能破,我也不知道,反正对吧,民间的高手实在太多了,所以的话呢简单来讲的话,就是你要想方设法的把你的这个程序保护起来,那也会把你的这个内存也保护起来。
那么另外一种的话作弊的方法是什么呢,就是说诶我去修改你本地的文件资源,打个比方吧,比如说我做个设计游戏,我帮你把这个贴图资源全部改掉对吧,比如像这个吃鸡游戏,我把你本地贴图都改成发光的这个材质好了呀。
那你里面看到的所有的敌人,所有的目标都非常的一目了然对吧,这个你你穿一身迷彩,你趴在地上对我来讲都是无视了,我就直接把你打掉了对吧,那就相当于直接开了地图透视挂,那这种问题怎么去解吧,解解第一个解决呢。
其实啊一般来讲是用那个本地文件哈希值,就是说我要求你的客户端不停的去算以本地的资源文件的哈希值,然后把这个哈希值传上传到我的服务器,跟我的服务器的进行比较,如果我发现你这个哈希值不对。
我认为你篡改了我本地的数据,那我就把你直接踢下线了,那这个其实是一个啊,基本上现代的online gaming的这个引擎啊都会做的一件事,因为确实篡改本地文件数据是这个非常常见的一种啊外挂的方式。
这个确实是很麻烦,就是啊基本上所有的网游最开始遭到的攻击都是这,比如说刚才讲了,如果采取这种啊客户端的这个这个检测的话,那我就不听他说,我打众实时的消息过来,那服务器那边的话,它怎么检测。
就算服务器检测,服务器可能不堪重负对吧,它会产生很多的问题,我们最核心的方法就是要首先是一定要加密,那这里面就讲到一个就是这个加密的一个细节了,就是大家知道在网络上这个连接啊,加密算法有两种。
一种叫对称加密算法,一种叫非对称加密算法,那么精神加密算法其实比较简单,就是说诶我的客户端和服务器共享一个密钥对吧,然后呢我们之间的通讯就是用这个密钥来,但问题就在于你这个密钥存在哪呢对吧。
如果你的客户端被破解了,别人很容易就知道你的你的密钥是什么,那好了,那你的这个客户端和服务器间的通讯,是不是就变成了这个透明大白于天下了,就是就相当于两个人打电话对吧,你说的所有的话。
别人都能够谈旁听到,那么如果是这个黑客厅听到的话,那你基本上就没有任何的秘密而言了,对不对,所以这里面就有一个很著名的东西,叫做非非对称算法,什么意思呢,就是说呃你在这个就是客户端。
我只给你一个公钥对吧,你破解了就破解了,但是呢我的服务器端是有一个私钥的,它两个对称是不对称的,那么这样的话呢就是你即使把我的客户端破解了,你知道我的公钥是什么,你也不会去知道我聊的内容是什么。
因为你没有私钥,打不开这个数据,对吧嗯好,那这个时候呢我怎么去建立一个网游的这个网络游戏的链接呢,其实刚才大家讲的,我们讲这个在有一个login server对吧,在很多网游里面一般是这样。
就是说我首先建立连接的时候,我会通过比如说像ssr这样的一个非常高度安全的网络链接,他走的就是一个非对称加密,但是非对称加密的问题是什么呢,它的这个速度比较慢,成本比较高,但没有关系。
因为你登录的时候用一次对吧,当我们一个安全的网络链接建立好的时候,诶,我们用这个加密的方法把一个就对称的钥匙传下去,所以接下来我的客户端和服务器之间的通讯呢用的都是一次性的对称,下面的钥匙。
因为你一旦破解了也没有关系,你只会破坏掉我一个客户端,你不会影响到我其他后面所有的链接,而且这个钥匙其实如果有必要的话,我还可以不停的去更换对吧,所以说其实呢在整个网游的这个服务器架构里面的话。
我们的网络这个traffic是一定要加密的,其实很麻烦,所以其实在现代网游里面,很多时候我们都会对这个加密的问题是非常的重视,而且这个我也会建议就是作为一个引擎底层提供的一种服务。
如果你做的是一款网络游戏的引擎的话好,那其实呢还有一种方法就是我们经常讲的就是这个啊软件注入,那么其实这个呢还是也是一个非常常见的这个游戏的作弊的方法,就是它通过一个钩子勾到你的游戏里面去。
然后呢注入到你们的这个引擎代码,那这个方法怎么去怎么去处理呢,其实这里面我们要讲到一些很多仿作弊的软件呢,大家大名鼎鼎的v a c对吧,大家如果上steam的同学玩游戏的同学就知道诶。
很多游戏好像进入了vc对吧,其实这些东西呢都是一些反作弊的这个软件,这些软件它的核心工作原理其实大同小异,其中就有个很重要的功能是什么呢,它会扫描你的这个内存中的这个游戏的签名。
如果他发现你这个游戏被人家注入了一些奇怪的代码,或者内存的这个这个这个这个它的比如说它的check on,或者是说他的哈希值不对的时候,诶他就知道你被人家外挂挂上去了,或者是你被人修改掉了。
那他就会直接认为你作弊了,但是他防作弊的方法有很多种啊,但是最重要的一点就是他会检查你的这个那个你的内存中的这个游戏,是不是没有被人改过,其实另外一个还有很重要的一个东西是什么呢。
诶很多的可疑的外挂程序,比如说有些经常用的一些内存修改器啊,当你开了这样一些可疑的软件的时,候,像vc这些软件它就会检测到它就会报警,他说你这个账号是可能在作弊对吧,当然还有可能是一些常用的外挂。
那常用的外挂的话,我们也会去检测,所以其实它这个东西就是道高一尺,魔高一丈,所以说实际上有人尝试去注入到你的游戏中间去,那像vac,像这个ez a c e a c的话,他就是专门去扫描这些可疑的进程。
可疑的服务,所以其实只要大家做网游反作弊是一个最核心的一个需求,这你们讲个有意思的笑话,就是说呃我们在和某一个国家的这个开发者,我们在合作的时候,我们会发现一个很有意思的问题。
就是说我们中国的游戏开发者会非常的关注做反作弊的问题,但是呢你会发现那个国家的开发者,他们好像就我不点名了,就是他们就是诶觉得好像为什么要写那么多反作弊的代码呢,会把你的这个游戏体验变得非常的差对吧。
然后我们就很郁闷,就跟他解释说为什么这个很重要,但是呢在那个国家里面是这样的,如果你敢在游戏中作弊的话,在那个国家是一个违法行为,所以没有人会作弊,所以他们的游戏就不需要花很多时间去写反作弊的代码。
那么这个就很有意思,所以我们也希望就是说如果有一天玩游戏的作弊就是违法行为的话,那这样的话那个也会让我们反作弊的压力会小很多,但是这个目前来看在全球想落地的可能性还是蛮小的。
因为全世界确实有那么多的国家太多了对吧,每个国家的法律基础都是不不不太一样的好,那么其实呢这些以上讲的这些作弊啊,我觉得我们多多少少还是有些方法能够防范他了,反正就道高一尺魔高一丈嘛。
我们就反正就反复的去追嘛,但是有一种作弊说实话真的是非常难的,就是ai的作弊对吧,现在的人工智能实际上是非常的强了,对不对,假设我用人工智能的技术,我直接图像识别出来敌人在哪里对吧,然后我我去移动。
我就training我的鼠模拟鼠标和键盘的操作,然后我就开始去这个这个训练这个机器人了,比如说那我真的我什么作弊方法都没有用了,对不对,因为对方是真的是很真实的操作。
当然这里面我可以检测说你虚拟的键鼠标键盘操作,我可以检测你,但这个其实已经很难了,那么实话实说呢,当我看了这些ai作弊的这些这些这些视频的时候,我不知道诸位看了什么感觉啊,反正我看了觉得挺爽的。
你不觉得你看这个打的比我们的这个人打的要牛逼多了吗,真的是行云流水啊,这个见面爆头,见面爆头看起来一种莫名其妙的爽感,所以有时候我怀疑说我要以后要不这个游戏就直接交给ai去打了。
我们就在旁边一边吃着泡面,一边看着他枪枪爆头,然后两边的ai的话相爱相杀,他们彼此之间互相k讲,我的ai干掉了你的ai,我觉得也是一种游戏玩法,但是说实话这个其实是真的是很难很难防的一种这个作弊的形式。
而且这件事情呢说实话现在的门槛已经是越来越低了对吧,现在越来越多的比较像优菈的,已经出了v5 v7 ,那你在一个图片中自动的识别别人,甚至你可以精准到知通过skeleton识别出来哪些是关节。
哪些是重要的头部对吧,我可以做到枪枪爆头这件事情其实已经门槛越来越低了,所以说其实呢在下一代的反作弊的这个软件研发里面的话,其实ai作弊实际上是一个啊,我觉得大家重点考虑的一件事情。
确实是特别是对于这种对抗性越来越强的游戏的话,那么我们这也是我们这个行业所要面临的一个问题,说实话到目前为止,我也没有想到这个有什么好的方法,当然了,这里面也有一些方法,比如说用魔法打法魔法。
用ai来识别ai作弊对吧,也有人提出这样的方法,但是我个人觉得这种方法还在一个非常早期的阶段对好,那所以说的话呢其实这里面也有一些很很淳朴的做法,我们发现了一些可疑的行为。
通过大数据统计发现你这个人这个行为有点可疑,跟你传统的这个比如说爆头率啊,你的这个击杀力数据明显出现不对了,玩家给你打分,说你到底是这个作弊了还是没作弊了,但这件事情呢我个人一直觉得可能有一点点难。
因为你并不知道这个降下者是不是相对公平,而且呢呃我反正听说了很多的事例,就是有些高玩玩家,他一通行云流水的操作发挥到非常的了不起,就被其他玩家误判为作弊了,然后就被干掉了。
这个说实话当然你是威慑爸爸无所谓对吧,你就干了就干了呗,但是对于一般的一个游戏或者游戏引擎来讲的话,如果这么做的话,还是有点狠了,所以说我觉得靠人力去判断是一个方法,但可能也未必是一个好的方法。
但是呢诶这个还是我们今天这节课中,唯一一个跟技术没有关的一个方法去反作弊好,那么其实呢就是基于统计数据的方法,就是就是我刚才讲的,其实正常玩家的模型是有一个pattern的,对不对。
我把那些开了挂的用户的信用模型其实可以训练成另外一个pattern,这样的话呢它可能不会纠结于单次的操作,单次怎么样,但是它确实能够从浩如烟海的战局数据中,我能detection你在作弊。
这个确实是这样,但是呢这个假设就是那个写ai作弊器的人比较笨,如果我ai写的真的足够聪明,我就比你正常的操作好那么一丢丢,比如说我这个ai只是给你提供一个叫辅助瞄准的功能对吧,大部分操作还是人在做诶。
我只是这时候想作弊的时候,我开一下辅助瞄准一下,让我发挥稍微那么启动,那么一下那这个你能发现我吗,我觉得他的那个就是那个这个这个这个这个deep。
就是那个就是数据的这个deep learning的这个算法要求还是比较高的,但是呢这个东西这种东西呢我认为就是道高一尺魔高一丈吧,也是作为这个未来技术发展的一个方向,所以呢其实这个啊还有一点就是说。
其实啊所有的其实我个人认为一个比较实用的一个反作弊的方法,是这样,就是说一般来讲做外挂程序都是牟利的,所以呢它就必然会在网上去叫卖,对不对好,那你可以把已知的别人做的外挂的气。
把它的这个特征给识别出来对吧,总是有signature的,那么我就在这个内存中扫描,我只要扫描到已知的外挂的东西,我就可以知道你的作弊了嘛对吧,所以说呢其实这个方法其实对于所有的商业性的外挂。
是一个非常好的方法,所以综上所述啊,就是如果你想做一个大家真的不会弃坑的一个online game的话,那反外挂实际上是一个叫长期战持久战对吧,当然了,你每天要忍受你的用户更对你各种指责。
但是你自己是知道这件事情其实非常的难,所以这也是就是下一代online gaming的话,我觉得必然要面对的问题,当然有同学会说诶我是不是用了这个cloud gaming,这个反作弊就不用解决了。
唉in siri确实是这样,但是啊大家记住我前面讲过了,如果对方用ai作弊的话对吧,你怎么办,人家模拟的是鼠标键盘的操作对吧,人家读取的数据就是你的屏幕信息,我觉得一样,你还是防不了的。
所以说啊这个事情还真的是非常有意思,很要挑战,ok好的,那最后呢我们讲到这一块的话,我们的这个online game的这个架构啊,基本上就讲完了,最后呢我们就荡开一笔,就是这也是课程结束的时候。
我们就放飞一下自我,那个如果我们要构建一个真正的开放世界对吧,那怎么去构建呢,那首先你要做第一件事情,就是你得把这个世界做得足够大吧,足够大,里面能放入足够多的这个character,对不对。
那这个时候如何构建一个scalable的实数据呢,这个其实很复杂,那我们今天就讲一个最简单的一些它的这个知识,就是诶我怎么去把这个世界,这个就是把这个世界做的能够hosting这么多的人,这么大的地方。
其实啊它的方法其实也非常的简单,我们去看整个世界的这个这个构建啊,无非也就三种模型,第一种呢是中间的叫instancing,就是说诶我们把世界分成副本对吧,大家往往有都知道下副本对不对。
其实一个魔术里面我可能有几万人,是更多的人在一个服务器里面,但是的话呢同时下副本就那么十几个人,所以我这个世界里面就是每一个副本里面就有十几个人,就是比较简单的,那么还有一种做法是什么呢。
诶我真的要实现于开放世界,我把世界分块,就是刚才我讲的分成一个个zone对吧,那么好,那我们把这个世界分成无数次zm之后呢,诶我这个人就可以在zm之间一来一去对吧,那么我怎么把这个room做的。
让你感受不到它有边界的存在呢,唉这里面肯定是有方法的,或者说呢我这个世界呢room,你可以理解成是对这个世界的横向的这个空间上的分割对吧,但实际上还有一个方法是什么呢,我们叫做replication。
就是说诶我们这个世界分很多的这个虚拟的层,我们把成千上万的玩家分到不同的层上去,这个跟我们前面讲的那个分布系统里面那个就是load balance是不是很像。
就是说我每一个就是这个这个这个这个这个这个世界的层的话,我的hosting,比如1万个玩家,假设现在10万玩家在世界上跑,我就分十层嘛对吧,那我这个数据其实也能够处理。
所以其实如果大家想构建一个开放世界的话呢,诶你最重要的就是让你的这个game server具有这种stability,那么好具体讲一讲这个怎么去做一个这样的是第一个几种方法呢,就是用zi的方法。
我怎么去构建一个无缝的zone呢,其实很简单,我对这个世界还是分块对吧,那每一个角色,你每一个character你属于哪个zone的话,那我就放在那儿呗,那我一个用上的人,我假设是不会太多。
这里面其实有个细节,就是说啊实际上的话character就是觉玩家在这个世界上的分布,比如说我作为一个很大的开放世界,比如说啊几千平方公里,但是已经很大了,对不对,那我假设有几10万个玩家撒上去的话。
它的分布式会怎么样呢,是不是有问题对吧,很多zone里面可能没有人,很多zone里面比如像主城都已经塞爆了,但是你可能就要炸掉了对吧,那这个送的话,其实它是可以动态的这个这个创建或者添加的哦。
就是划分的,就是一般来讲我们会用那个四叉树在进行不断的划分,但是呢划分到一定小的事情,我们就不划分了,这个里面的细节我们以后再呃,后面会简单讲一讲,但其实这个有很多小细节处理好。
那有这样的一些room之后呢,接下来我们就做一件事,其实你唯一要处理的问题就是这个角色要跨边界了,从a送到b中的这个问题对吧,那怎么办呢,其实首先我们不讲了一个a o i的概念吗。
每一个角色关注的什么呢,不就是我旁边一个半径的区区域吗,那我有了这样一个半径,我在zone之间呢做一个就是border边界好,那么当每一个角色到了这个边界的时候,我就知道说你进了这个边界。
虽然你还你现在还在a中za,但是呢你虽然没到zb,但zb的玩家应该要能看到你了对吧,因为否则的话你会当你从a中到b中的时候,会突然看见你出现了这个感觉,是不是很奇怪对吧。
哎这个时候其实做法其实非常的简单,就是说我在za的那个服务器上,zb的服务器上,我只要你的这个character这个entity,你在zone的那个区啊,border那个区域里面。
我会去做你的一个ghost诶,这里面就引入了ghost的概念了,就是说啊,虽然这个其实就是说你虽然这个时候,你这个比如说我们举一个nt da为例,你这个虽然还在a这个这个nt里面去。
还在a这个zone里面,但是的话呢在zombie里面我会做你一个ghost,这样做zombie的玩家是能看到你的,只是你真正的逻辑,你真正的行为其实还是有原来那个a的那个server去hosting了。
对不对,这件事情其实就完美的解决了,而且因为你我们只处理它在波段里面的情况,所以说是用我们就假设ghost不会太多吧,我们第一个有了这样一个ghost,那么第二件事情是什么呢。
诶当我们去跨越这个zone的时候,实际上他就做一个简单的fleeping,就是比如说这个nt体跨越从za到zb的时候,他越过那个边界线的一瞬间,我本来是在za服务器里面的这个实体,b服务性的ghost。
然后呢我把数据迁移一下,把a里面变成ghost就可以了,但是呢实际上在实际做的时候呢,我们一般会做一个小小的一个缓冲区,什么意思呢,就是有的玩家特别的坏,他就专门当然玩家其实也不知道你的动画在哪对吧。
假设有个玩家特别的坏,他就反复的在你那个边界那边来回来回来回抖动,那你就会就会不停的这两篇的数据考来考去,这种情况还真的是有的,所以真的我们要再做一个就是无缝的这个大世界的时候呢,我们一般会做一个阈值。
就是说诶你得穿过了这个zona的这个边界,比如说过了20米,30米了,诶,我在考虑万一迁过去对吧,那么你如果从那个b又后悔了,往往回跑,你跑到一段距离之后,我才切回来,这样避免这种高频的震荡对吧。
假设有一个老哥不长心,他就老是沿着那个边界线跑的话,那你这个两边服务器忙得要死,数据考来考去考虑考虑去对吧,所以的话呢其实这里面还会有一些相应的处理,但它最核心的思想就是里面大家记住一个核心的思想。
什么就是ghost,我要做个ghost,就把这个哎这样我两边就感觉好像彼此真的存在了,但实际上我们并不在一个服务器里面,所以这就是一个无缝的大世界,用用的方法来解决。
那么另外一种方法就是replication,其实刚才如果大家理解的什么叫ghost的概念的话,其实replication就好理解了,就是说我这些每一个character呢。
我是扔到多个这个世界的镜像里面去处理的,这个世界足够大,但是呢我比如说你可以想象一下,我们把这个宇宙这个把这个这个这个这个一个城市分了很多诗意的层,对吧。
每一个cd的每个每一个玩家都扔到了不同的层来处理,每个层上平均那处理1万个市民,那我但是呢其他的这个九层的市民,他在我那个层上面的这个是个ghost,但是呢这个ghost其实是虚拟的。
有的时候我只是在穿做的ri的时候,我会去取一下它的数据,所以用这种简单的rap思想的话,那我也能够去处理很多很多的用户,但是呢如果我们去真实的去构建一个哎这样的一个大世界的时候呢,那至少我个人会推荐。
就是说应该把两个方法去结合,你既要用空间划分的这个思想,而且这个划分的思想最好是要用这个adaptive的,这个这个这个这个这个这个划分,为什么呢,因为就像我刚才讲的。
就是说真的你做一个上千平方公里的地方的话,大家想想玩家都喜欢在哪聚集,玩家都喜欢在主城里面聚集对吧,野外很多地方大家都是空着的,所以你这个zone到底花多大都是不合适的。
所以呢它一定是就是当用户聚集的时候,它会这个划分会再变得更密的,但是注意这个不能够划分的就是过于的迷,为什么,因为你老是动态的去切割他的话,这个数据会在各个服务器之间来回跑来跑去好。
但是呢你这个z又不能变得太小,因为z变得太小之后呢,你的a i又不能太小对吧,a i一般都是要12百米左右,那你z太小,如果比如说zone太小了之后,你这个数据就是老是会来回的这种搬来搬去。
所以呢到了一定的尺寸之后,假设我在这个方圆100x100的平方米的地方,角色数量已经超过了我单台服务器的hosting能力怎么办,哎我在下面再去点他的replication来去分担他的压力。
所以呢我就说这两个东西的结合,是一个相对完整的这样的一个解决方案,所以同学们如果想做一个开放大世界游戏,你们可以尝试用这个结构对吧,所以的话我觉得就是说我们经常讲。
就是也许这就是我们的online gaming的未来对吧,就是像绿洲这样的一个世界对吧,我已经看到地平线上已经有很多的这样的产品在研发,确实是这真的是啊,也会让我们非常激动的这样的一种游戏的类型。
所以讲到这一part的话呢,也是我非常开心,就是用这这这一页图纸作为我们这个online gaming section architecture的一个结束,心中想的一定是个绿洲这样的世界,ok好的。
今天这个课程的话两个半小时好,接下来的话呢就是也感谢一下我们的课程组的小伙伴们,大家那个非常的辛苦对吧,每节课都是这句话,这个台词再用一遍也不嫌也不嫌麻烦,然后呢接下来就是我们的reference方。
这一节课的话reference也是比较多的,确实是我自己的感受,就是我们这两节课的话,基本上每一节课的内容如果真的在学校里去讲的话,可以讲一整个学期吧,就内容量挺大,所以呢确实很遗憾。
就是在课程上没有办法给同学们讲的很细,很多算法其实有很多的细节才能真的做出来,所以呢我们也尽可能的把这个各个算法的这个reference交给他。
比如像这一页的话是就是replicate character movement的这一部分,有这样的一些比较好的一些论文或者是一些文献,大家可以去读,然后呢比如说像那个like medication。
其实就是我刚讲的like composition呢,还有heatregistration的,基本上在这些文章,你们会有那么这个m网络网络游戏的架构呢,其实资料不是特别多,但是大家可以看一下,有这些东西。
其实今天分享的很多东西都是我们自己的经验之谈给大家,那这个架构呢其实也是就是被行业里面官方验证的这样的一个架构,但是这里面其实还有很多很多的细节,那么接下来呢还有就是说这个带宽怎么去优化。
也有一些gdc的文章跟大家去分享对吧,然后还有就是反作弊,反作弊的文章就比较多了对吧,大家有兴趣的话,可以在你们深入的研究,但是还是那句话,大家一定要学好的,不要学坏的,一定要学antchat。
千万不要去学chat,ok然后所以呢这就是我们今天内容的全部好了,同学们有什么问题,那个我们可以在这里面去交流啊,第一个问题是有同学问我说,如果玩家在服务器端做了微调。
这个命中判定这个服务器能不能判定出来啊,这个答案应该是取决于你是什么什么怎么做的,假设是客户端判断的话,那按照刚才我讲的守望的一个案例的话,他打中那么大的一个盒子都算你打中的话,那确实是判定不出来的。
所以他的假设是客户端是不会被compromise,是不会被人hack进去的,那如果是这个就是说采取的hate registration,在服务器端判定的话。
呃就是说如果你发射的那个射线是真的命中了那个目标,而且服务器端做做那个就是luck composition,他算的结果也是一样的话,那他就只能认为你就是真的击中了对吧,假设你在本地其实没有击中。
或者你调整了,你在本地强行的把你的这个就是那个对方的位置做了一些移动,就是跟你差值算法不一致的话,那服务器那边肯定是认为这个事情是不对的,他会让你判定不出来,所以说啊就是看你微调的性质到底是怎么回事。
就是其实很难讲,因为其实客户端微调你可以微调你枪的角度,你可以微调你的这个这个你的靶子的位置,一般来讲的话,如果是hate registration,你动的是靶子的位置的话。
那么我的那个服务器端的算法的话,会跟我服器上算的不一致,那我可以判断出来,但是如果你微调你枪口的位置,你真的是通过某种那个外挂把你的枪真的对准那个人去打的话,那我服务器端真的是判断不出来的。
啊这个问题是同学问我们说分布式系统在游戏行业应用广泛嘛,呃其实在早期的时候呢,就是网络游戏的服务器架构没有那么复杂,其实我知道最早的有些很厉害的游戏啊,它整个服务在一台服务器上。
就是一台物理机上就跑完了,真的很厉害,而且那个游戏产品非常的厉害,真的是常青树,但是呢随着现代网游的这个复杂度越来越高,现在越来越多的这个服务器的引擎的,就是服务器的架构的话,采取了分布式系统架构。
但是整个行业其实我的理解啊还是在一个过渡阶段,就是说大家越来越多的用这个分布系统的架构去架构,整个服务器引擎的底层,就保证这个就是我们的整个服务的现状性。
所以我觉得这件事情在未来的五到10年就要不了那么久,可能3~5年吧,我觉得就会逐渐的成熟,而且应该我也会成为这个行业的一个标配哇,这个第三个问题太难了,同学问我说无缝大世界的mmo实现的难点有哪些。
首先第一个这个吴桐大师的m我自己还没做出来,所以我不敢说我知道什么难点,因为这件事情其实是蛮挑战的,那么我觉得他第一步的话不能说难点就在我们的课程,最后一把讲的就是你首先要做一个足够大的世界对吧。
能让诚信上的玩家在世界里面走来走去,那么这里面的话呢其实除了你的server host能力,还有一个很复杂的东西是什么呢,就是说哎这些玩家不是只看到彼此的,对不对,他们还有很多的这个消息。
我有很多的行为,比如说我可以开枪打到那个远处的敌人呢,我可以突然把一个墙摧毁掉呀对吧,而且这些人的话会有很多其他的这种transaction这些行为啊,这个时候就像我们讲的这个复杂度的话。
又变成了一个m平方的复杂度,所以这个时候实际上当这个世界如果只是静态的,大家只是在里面走来走去,say hi,跳个舞对吧,做一些简单的战斗,操作比较简单,可以在世界里面去构建战斗,创造所有的事情的话。
那这个它的复杂度其实还是非常非常大的,因为今天我们看很多网游的架构啊,实际上它还是分房间的价格,就是说当用户进入到战斗模式的时候,它很多时候还是会进入到一个个房间,就真的在这个世界上。
你能让就是几万甚至几10万的人在一起,又是战斗,又是移动,又是交易,又是对话,又是做各种行为的话,其实啊他对其他各个server就是不仅仅是这个就是这个世界的hosting server。
比如说甚至连聊天,连这个就是啊,我觉得就是基本上所有的server都得要重新按照分布式的架构,而负载均衡的架构重新加一遍,而且要保证它们叠在一起还能work,所以这件事的话呢啊其实是非常非常挑战的。
这也是为什么举个例子吧,比如说啊大家知道就是像那个最早是epic的tim sweeney,他在游戏引擎界就讲说matters对吧,就是元宇宙,但是呢其实就是team的话。
他自己在他的一个talk中也就讲了,就是说构建manners的这个底层的这个引擎,它其实有很多的要求的,这个这个要求的话,实际上他讲的比如像persistency啊,这个这个整个世界的事物原子性啊。
这个要求的要求非常的高,那么到目前为止的话,我至少在商业引擎里面,我看到大家都在往这个方向去努力,但是还没有人敢说我交付了这样的一个这个技术平台,这也是我认为下一代游戏引擎很值得大家去努力和推动的东西。
所以这里面的话呢很值得我背去跳进去弄,如果同学们有兴趣去做的话,我非常愿意跟大家去探讨,因为确实只作为我个人来讲的话,就是我的终身的职业梦想,真的就是作为一个游戏人的话。
作为一个游戏引擎的这个这个程序员,这真的是没有什么比比,这个让我更兴奋的事情,而且我自己非常清楚说这件事情很难很难,就是可能需要我们再去努力,五到10年才能交付这样一个世界出来,ok好的。
那今天要不就先这样,好的,那就下一节,同学们下一节课的话,我们就休息一周,充满乐趣,充满挑战的这个我们的游戏引擎课。
那同学们。
20.时域调制 (III) | GAMES204-计算成像 - P1 - GAMES-Webinar - BV1T8411V7ne
很好我们时间到,我们正式开始啊,首先欢迎同学们来到games,204计算摄影的课堂啊,计算成像我们已经上到了第20节课了,已经上了,大概覆盖了60%,到70%的一个内容啊。
后面我们还会呃整个我们这个temporal encoding,大概会覆盖到5~6节课,唉,这个temporary encoding我们讲完之后哎,再给大家讲这个special encoding啊。
在我们在空间域的一个调制诶,这个整个的一个呃宏伟蓝图啊,就给大家描绘的差不多了哈,今天是武器,正式进入到我们这个temple encoding的一个第三课,这叫间接光成像,说到这个渐进过程。
像我们把今天的这个topic分为,首先给大家介绍一下,什么是连续波的一个tam flaming,然后再给大家分析一下我们这个interrupt temple,flying的一个成像的一个模型,呃。
我们照明啊有什么样的trick啊,还有怎么样的设计,我们这个多路径的问题是怎么引入的,然后去怎么解决这个多路径问题啊,最后给大家引入一个非常经典的案例啊,叫facer mei啊。
facemi今天我们可能时间不够啊,我们就把这个facing拆成了一半哈,这个我们今天讲墙面前半部分,下节课我们在讲这个facer image的后半部分哈,说到这个连续波的q调整。
因为我们之前上节课讲到了一个呃direct time flight,它是发送一个脉冲波,但从这个名字上来看,我们这个连续波的一个top成像,就是我们打出一个连续调制的一个波形。
哎他比如说我们用一个正弦波诶,或者是用一个呃这种方波来做调制,来最后生成,我们通过这个相位的解调,来生成我们最后想要的一个深度图像。
当然这个continue wave的一个time fighting ming,它是具体是怎么工作的呢,首先啊我们的光源啊,光源我们看到这个地方,我们用我们的光源发出一束连续调制的波啊。
我最开始还是你一个呃一个seniso的,一个就是正弦函数的一个波形打出去,因为正弦函数本身你到频域啊还是正值函数,对不对,这个时候我们哎到频域,它就是一个单单独的一个频率,这个时候还比较好表示哈。
所以说我们整个先用一个正弦波来去讲解,我们用这个正弦波打到了这个场景上,然后再被这个传感器所感知到啊,我们这个receive the light,我们用蓝色的也来表示诶。
这个时候我们加了这么一个计时器哈,我们计时到这个连续波发出去,到我这个连续波收回来,他的这个时间是有多少,诶怎么记这个时间呢,你看我们发出去就这么一个正弦波,正弦波它是这么一个波形,经过一定的时间后。
它这个波形我们可以看到会有一定的下移哈,我们最后我们器件能做器件能做到的事,就是我们想把我们这个象仪啊,face shift,你拿到再结合我们本身这个调制波形的周期啊。
我们就可以知道我这个face shift,在这个周期里占了多了多少比例,哎我们在通过这个周期跟光速的一个啊,光的一个速度啊,最后就可以把这个深度来推算出来哈,那我们是如何去来测量这个相位的一个象移呢。
啊什么我们知道了,我们最后拿到了一个intense图像,我们这个呃发出了一个正弦波,然后收到一个正弦波,这个正弦波本身呢我们收到了正弦波啊,它不再是一个啊直接被打出一只光。
它那个振幅相位都会发生一定的变化,同时呢呃我们还会受到光本身自个儿的影响哈,光本身自个儿的影响,它是嗯有一定的这个偏执的哈,偏执的哎,我们拿到了这个象仪啊。
我把它denote as fi recora record outset呢,呃即为o啊,empty就记为a哈,最后我们这个收到了一个,就在我们进入这个传感器之前的一个,波形的函数啊,我们记为l。
它是一个关于时间t的函数,它是一个offset,加上他的etitude,最后再加上一个正弦波哎,剪掉它的一个相位的移动号,相位相移,总共呢我们可以知道,我们在探测器上拿到这么一个信号。
我们总共有三个未知量,第一个未知量就是它的一个offset,第二个未知量就是我信号的一个复制,two,第三个信号,也就是我们最终的目的,就是想要拿到了一个相位的象仪,这个呢我们器件本身啊是没有办法诶。
我很快的,比如说我们现在调制速度到100兆,我要采样这么多个点,我不可能用采用100个点,那就是一个实际的一个采样率来去采这个波形,怎么办呢,怎么办呢,这个时候如果同学们学过电路的这种情况。
大家特别是学过啊通信的同学们哈,可能会我们可以用相关呀,对不对,这个地方是十分类似于我们的一个呃,就是信号处理的一种信号采样的一种方法,诶,这个叫lock in啊。
这个叫lock in这种方法叫做这个叫叫什么来呃,所向哈,就有一种就是电路的一种采样的一种方法,就是采样的一种方法叫所向放大器啊,我们是用一个跟它同频的一个信号,就是我们一个参考信号。
跟我们接受过来信号做一个相关啊,就是做一个correlation,实际上就是对在一个周期内对它进行积分啊,最后我们通过积分就可以拿到一个啊,跟相位相关的一个强度值哈,我们就可以。
当然这个周期是可以非常多的,我们也可以积累到一个比较强的信号,这样的话我们就可以拿到一个信噪比比较高的,一个correlation的一个值哈,本身我们有三个未知量,好就是e1 e2 e3 。
就是amplitude offset,还有一个fidelay啊,我们通过给他一个参考信号,我们这个地方把它记为,11213,这三个信号呢分别是由一个90度的一个象移。
最后我们通过这样一个相关就可以拿到三个啊,就是三个强度的一个测量值哈,三个强度的测量值,它本身这个频率是一致的,这个时候我们可以通过这三个方程啊,这三个测量值121223来去计算出来。
我们这个face delay到底是多少啊,当然这个offset empty也可以写出来,我们一般嗯可能就懒得解释了,就直接把这个depth来算出来哈,这个时候呢。
我们本身这个continue wave的一个飞行时间法,是可以拿到一个比较高的seed的通道zero啊,也可以实现一个实时的一个捕获啊,但现在业界最高已经可以做到,120帧的一个啊,etf的一个捕获哈。
就是啊我们先比较一下我们的depth accuracy,这是time of flight imaging,我们可以看到啊,结构光呢就是中间这个是结构光的一个方法,我刚才讲到哈。
这个本身这个off是可以做到,1mm左右的一个精度啊,这个stripper line本身呢你要离得近的话,精度其实挺高的,但是你要到10米这个范围大概嗯一般是1cm,厘米厘米级的左右的一个精度哈。
这个当然也跟他的一个baseline有关啊,这个baseline有关系啊,用双目呢,双目其实啊这个也是会随着我们深度的变大,距离的变大,它的一个精度会下降,在10米的时候哦。
可能这个精度会差到一个10cm左右的,一个量级哈,当然这个也会因为有很多位置,因为缺少这个texture的匹配,而从而导致一个非常巨大的错误啊,这是啊一些方法的优劣,这是一个业界比较先进的一些器件哈。
左边这个是我们造了一款呃,比较高端的一个就是tm flat传感器哈,右边这个是微软的connect,就是业界比较常见的一些啊器件哈,这个是可以做到100跟120Hz的,一个彩色同步的点云输出。
kk net可以做到30Hz,让我们来分析一下这个imagine model,然后这个我们这个艾特怎么来去分析,它的一个成像的一个过程,它到底是如何成像的,我们的器件又是怎么样子的呢。
让我们来详细的解答一下,我们这个itt的一个成像模型,首先说到这个itp的成像模型,我们就不得不先提一下,我们这个correlation sensor,他这个sense到底是怎么工作的。
这个这个器件哈叫就叫相关传感器啊,就有时候也叫photonic mixer device,我现在有家啊这个top的传感器啊,非常大的一家top传感器就叫啊p m d哈。
就是就是pta mia divide divide的缩写,我们可以通过电路上对我们的参考信号,跟我们的一个receive的一个信号,来进行自相关哈,但是在电路里面就自己做相关了。
这个时候我们就可以直接测量,我们最后相关的一个强度值,哎这个时候我们也可以对参考信号进行项移啊,同时拿到多个测量信号,这个右边是我们一个像素的,一个就是一个示意图吧,示意图,其实大家看看就好了哈。
这个我后面会给大家详细的讲解一下,这个就pmb这个器件是怎么工作的,这个我从这个德州仪器的一个官网上,找了一个简化的一个pixel模型,他这个是怎么怎么工作呢,就是我们你看中间这个蓝色的部分啊。
是一个呃就是假设一个理想的foto die的,就是一个感光的一个部分啊,我们本身这一个像素有两个node,node和node b上面呢分别有一个电容叫c a跟cb哈,这个是用来存储电荷用的。
这个时候我们的参考信号,就刚才我们讲到一个参考信号,就是这个时候我们用的是方波了哈,就不再是正弦波了,因为正弦波在电路上是比较难实现的,我们就方波来测白测这个方波的相仪,我每次reset之后。
这个ca跟cb就会充满电,然后呢我们参考信号啊,参考信号它会使这个d mix 0跟d mix 1,选择其中一个开关,我们这个参考信号就相当于一个开关来选择,node a接通还是node b接通。
当我们这个左边是高电平啊,左边是高电平,就是note a接通的时候,哎我们这个受到我们接收过来的这个参考光诶,跟这个传感器反应,然后再加上这个本身这个信号,这个电流就会往外流。
往外流到这个信号关闭我们电这个电荷,这个电容b上的一个信号往外流,最后我们就可以读出来这个ca instant b,c b两个电容上的一个呃电荷值哈,就是最后我们的一个电压值,通过这个电压值了。
我们就可以判断这个相位到底是有多少哈,我们来看一下这个是怎么判断的,就是我们现在假设,我们现在理想的一个情况下哈,就是我们的一个理想的pixel,没有一个环境光的干扰。
我们把这个a记为a上的一个node,a上的电压,b呢即为这个node b上的电压,我们有个reference的一个电影,就是参考电压叫几个vr,我们的一个照明光哈,发出去的一个呃,发出去了一个照明光。
它有一个周期,周期是2t我们这个传感器上接触到的光,也就是这个蓝色的一个传感器上接触到的光,哎我们就记为这个第二行reflected light本身呢,它这个t r t就是我们最后要求到了一个啊。
向移的一个就是就t2 t,比上t就是我们最后目标的一个项移的一个值,the mix 0呢也就是我们这边啊这边导通,这个时候我们这比发低电平的时候,这边导通这边导通我们整个电荷假设它是满的。
在t r t的部分,就把这个d mix 0这个地方电压放出去,一部分电荷放出一部分,同理呢,demix一哎在这上面是高的地方啊,这这上面是低的时候,就是fancy line是低的,这个位置也是放电了诶。
我就把这一部分买了电荷放出去,这个按周期按时间周期放出去,诶这么一个比例,最后我们的一个a d c,就跟传统的一个图像传感器一样,我们就分别读出这个node a跟node b上的电压啊。
node a跟node b上的电压,我们就可以通过这两个电压值啊,这两个电压值来直接算出,我们最后的相位到底是多少,这个时候呢,我们假设这个放电率是k啊,我们导通的时候放电率是k。
本身呢我们的一个电压是一个参考电压,vr就是c a c b的一个参考电压,都是vr啊,我们记为vr,然后经过这个t减t r t唉,经过这么一段时间放电啊,有一个比例k哈,我们就可以啊知道这a上的电压。
就把它记为vr减去tr t,减t乘以k b上的电压呢,就是在tt这段时间按一个k的一个速率放电,最后呢我们把这个k消掉哈,把这个k消掉,是可以直接拿到这个t减去r t r t比上啊,t的一个值。
实际上就是我们最后要求到的一个相位哈,然后这个相位呢本身我们就可以啊,再按那个光速传播,就可以直接把这个深度值来算出来,这是我们理想的情况下,我们可以直接哎用一个用一次测量,就可以把这个深度值拿出来哈。
这个是理想的情况下,因为我们本身这个vr这个电压是已经知道的啊,vr这个电压是已经知道的,我们读到了a b两个tap的一个电压的时候,哎就可以直接把这个相位算出来了,这是理想的情况下,但实际上呢。
我们又不得不考虑环境光的一个干扰哈,这个时候前面的三个像ab电压太高,电压还是带的这个受到环境光的影响呢,就是我们的环境光会干扰,会照在我的一个图像传感器上啊,到到我一个图像传感器上,因为环境光引起。
我们会引入一个新的一个叫v a x叫,因为环境光引起了一个电压,同时呢我们要考虑这个offset,有时候那不等于这个v o b,这个set multage会让两边不相等。
这位这个呃又会啊引入一个新的一个未知量啊,这个三七同学问机器没有办法精确控制相位吗,我们是有办法精确控制相位的,我们可以用啊扫向环呀或者被频电路啊啊,再加上一些我们的一个主要是锁相环,pll锁相环唉。
来精确的控制相位,当然这个时候它不是我们工,精确控制相位的问题啊,这个是因为我们本身,我们对这个测量这个高速信号的一个相位,的一个问题,就是我们怎么样去在这个node a node b去选通。
然后精确测量这个电压值,当我们考虑这个offset啊,场景还有环境光的时候,我们又引入了三个未知量啊,最后呢本身我们在node a跟node b测上电压哈,刚才上一上一页,我们知道理想情况下。
我们只有一个we reset这么一个电压,这个在node a的时候,我们考虑到环境光的干扰,再考虑到reset,noise,reset voltage不一样,这个时候我们a上拿到了电压。
就是v a a加上vv加上vr啊,减掉我们放电的一个过程,b上的电压呢,我们要考虑这个在这个node b上的一个reset butage,还有note b上的一个呃环境光啊。
再去啊加上这个vr减去我们这个放电,最后呢我们现在呢是有两个方程,两个方程哈,我们上到a b两个tap的电压,但是呢我们实际上这个时候,我们有四个未知量哈,四个未知量。
就是我们的我们可以把这个写在一起啊,这个把它当成一个整体的未知量,v a b加上v o b第二位质量,当然还有这个放电速率,k还有t r t就是我们最后要求的这么一个值,怎么有四个未知量。
但是我们只有两个方程,这个咋办呢,其实这个思想很简单,我们就简单的可以增加它的一个测量数量,就是比如说呃我们对这个illumination做一个像移,或者对一个啊参考官进行一个像仪啊。
当然最简单的办法就是我们再测一次,我们不在,我们把这整个快门关上啊,快门关上再去曝光相同的时间,来测到一个a关上的一个电压,还有b关上了一个电压,这个时候我们就可以a off就没有再放电了。
b off也是在没有再放电了,我们直接就可以把这个环境框的一个干扰诶,直接去掉哈,还有这三个未知量的干扰,这是最简单的一种做法,这个时候我们就有四个方程,四个未知量啊,我们就是。
a0 减去a of b0 减去b off,然后再一比把这个k消掉,我们就可以拿到t r t跟t的一个比值,实际上就是把我们最后的一个相位,给计算出来了啊,这是我们呃稍微考虑一点实际情况,但实际上呢。
我们面临的一个场景会更加的复杂哈,现在呢对一个实际的一个,就是我们实际最后工厂加工出来一个pixel来说哈,pixel来说,我们两边的还会对这个电容c a c b,它的一个进行一个模拟放大放大。
收了之后再回材料,但是呢两边分别跟一个运算放大器,他们两边的一个增益往往是不一样的,而且这个增益的值也不是很好精确,的确去测量哈,所以说这个时候考虑到这个进error哎,我们又搞进来这两个变量啊。
又又搞进来两个未知量,这个咋办呢,我们可以看到当我们不考虑这个呃,前面一连串的这个就是这个这个这个啊,前面一连串的这些常数的一个电压哈,本身这个a上面放了电,就已经有12344个未知量。
就g a g b k跟t r t这四个未知量,这个时候为这个问题就变得有点复杂哈,这个不好解啊,不好解怎么办呢,这个时候我们就引入了我们top里面,常见的四相位的测量方法,我们就分别测量0度,180度。
90度跟270度这四个相位上的一个呃,coration的一个值哈,这个时候我们a0 减b0 ,a0 减b0 本身啊,我们就涵涵盖了一个相位的一个信息哈,在a在0度的一个地方,它有一个常数值。
前面就是第一串,在180度的时候呢,哎这个常数值也是前面那一串,但是后面我们可以看到它是一个ga加gb的,这么一个对,他把这个两个tap的一个相互影响,给相互消掉了。
我们第三次测量呢就会啊还是拿了一个constant,但是这个是跟sn相关的,sn相关的一个呃,下位在270度的时候呢,诶这个时候我们发现这总共1234,四个方程啊,四个方程,但我们有多少个未知量呢。
cos 1 cos 2 g跟gb对不对,这个时候我们发现我们仅仅只有四个未知量,这个时候而且我们是有四个方程的哈,四个方程,然后呢,我们这个时候就可以把a0 b0 减三个。
a180 度减去个b180 度,记为i,a90 度减b90 度减去一个a,270度减b270 度即为q啊,最后我们的相位就是arc tan的q除以i,这就是我们常见的一个top计算的一个公式啊,公式哈。
这个有时候同学们就是特别是嗯,玩一些这种top传感器的时候,我们会经常会拿到这个饶恕句里面哈,它有两个tap,一个tape a type b啊,这个我们最后啊有时候你也可以设置模式啊。
就输出一个啊type a减tape b的一个值哈,实际上就是我们这个地方的a0 减b0 ,你拿到了一个数值,就是一个a0 减b0 ,就是我们最后adc读出来node ag node b两个值的相减。
最后呢我们就通过诶这四个,这四个差值,然后就可以算出阿根q啊,阿gq就可以最后算出我们的相位哈,这个相位实际上就是意味着深度,当我们把这个iq画成一个在一个单位坐标下,我们把它画出来哈。
这个i的平方加上q的平方,最后就变成了一个我们定义为叫confidence,confidence,当我们考虑到有噪声的情况下,我们发现诶我们在这个competence画一个圈,对不对,在这个圈的附近啊。
这个就是当我们画了一个圈哈,我们可以看到啊,给大家画一下,有圈的时候,大家可以看到吗,好有圈的时候我们就可以,看到,德尔塔data,而且这个噪声越大,噪声越大,噪声越大。
我们这个德尔塔theta也就越大,对不对,当然我们要考虑到这个confidence的时候呢,我们就会发现我们的confidence越大的时候,我们本身这个noise。
noise半径半径就近似出一个confidence,就是约等于theta,对不对,dsa我发现这个confidence越大的时候,哎我们噪声对深度的一个det error的影响就越小,就越小。
ok我们继续,那这节课可能会有点难哈,因为它涉及的一些底层的东西会比较多啊,希望大家同学们啊耐心的就是去消化一下哈,去消化一下,本身呢我们要引入一个叫few factor的一个概念,就是我们的填充率。
实际上就是啊我们有效感光的一个面积,对上我们整个的一个像素的面积,比如说我10微米的像素,但是我中间只有一个呃,3x3的,一个3微米乘3微米的一个小区域感光,这个就是它的一个few factor。
这个要提前给大家打个预防针,本身呢我们器件啊叫responsibility,它有一个概念叫quantum phency,也就是我们,整个生成的电,就是我们光子打到我们这个传感器上,我们生成的一个电荷数。
比上我们达到这个传感器上的一个光子数,这个就叫它的一个呃量子效率,我们记为n lambda,它的一个responsitivity是怎么记得呢,responsivity就是我们现在产生的电流。
跟我们轰击到这个传感器上的一个啊,这个光功率哈,它的比值,最后呢我们就可以把它记为lamba q e,比上hc,然后再乘以一个我们的量子效率啊,这个lambda就是我们的一个来不来。
就是我们这个波长波长,这个是跟波长相关的,我们知道这个波长越短,他这个能量哈,他的这个能量就越强,然后呢q呢q这个小qe呢,就是我们嗯一个啊电荷的一个电荷量啊,就是一个电荷量标准单位的电荷量。
h就是普朗克常量,c就是光速,把前面这一坨呃,前面这一坨我们就简单的记为一个,前,前面这一坨简单的记为一个啊,这个常数就好了哈,实际上我们发现这个responsivity哈。
实际上就是跟这个qe直接正相关的,这个时候我们记为记一个呃电压哈,就是在就是每个pixel他们左右两个node上的电压,实际上呢它是什么呢,我们捕捉到的电荷量比上这个光速哈,实际上这是它的一个电压。
它是跟这个responsivity直接正相关的,也是跟时间啊直接正相关的,最后呢我们发现我们刚才计算的这个confidence,也就是,呃confidence,也就是i方乘q方。
和我们的这个responsibility for factor啊,还有一个积分时间啊,诶都是正相关的哈都是正相关的,也是我们积分时间越长,fire fake越高,我们的一个响应率越高。
我们的confidence也就是越强的,与之对应的,我们这个噪声对我们深度精度的影响,唉也就越小,我们还要说一个概念叫demodulation contrast。
demotivation contract是什么意思呢,就是我们在node a减去node b的值,note a上的电荷减去node b上的电荷,比上他们两个总共捕获的电荷的总量的比值。
叫demodulation contrast,这个demodulation contract越高了,我们发现啊这个dem 0 c ctrl降低的时候,a减b就变低了,confidence就更低了。
实际上大家有时候看到这个这个top传感器的,这个有个飙升的一次参量叫demotivation contrast,也就是这个值我们希望它越高越好,因为越高啊,我就可以拿到更高的一个confidence。
也就是可以拿到一个更高的一个noise,in competence的一个ritual,也就是嗯,也就意味着我们的噪声实际上是更小的,通常呢啊对我们的硅光起点啊,硅光起点啊,差不多,在850nm的时候。
我们的这个demodulation contrast,比如说在100兆哈,举个例子,因为频率越高,这个demolicontrast会丢失100兆,一般是可以做到40%多哈,你要到了949百40nm。
这个在100兆的情况下,一般是可以做到20%多的,30%多啊,这个是呃跟波长也是有关系的哈,本身这个top本身是有什么样的一个,噪声的影响呢,首先我们要考虑他一个reset noise。
也就是我们那个电荷上它会要reset,每次要reset,这个还有reset noise,这个是跟温度相关的一个嗯值哈,叫我们叫nt啊,这个温度相关,这个很明显哈,这就是reset nice,也是一种。
就破松噪声吧,应该是可以这么理解哈,本身呢除了这个之外,我们光子打到这个传感器上,它是有一个腹痛short noise的,也就是它是有一个这么一个散粒噪声,散粒噪声,我们这个depth noise。
也就是深深度上的一个噪声,也就是直接决定了我们啊深度的一个分辨率啊,我们就可以把这个化为等号哈,实际上这个d noise也就是等于d resolution,我们把这个记为nd哈,它是跟什么相关的。
我们就会发现啊,这个光束呃,那个光咱就不说了,这个格式统一的bug好,我给大家标一下啊,这个地方就是,fm cd啊,实际上我们最后发现它跟什么正相关呢,就是,我们的一个,最后的一个得到了一个光强啊。
光强越强哈,符文拿到了一个呃深度的一个分辨率就越高,有趣的是,我们频率越高,fm频率越高,我们这个深度精度也越高,我们的demotivation contrast越高。
我们这个death noise也小,我们这个深度分辨率也越高,所以说我们想要更高的一个幅值,更高的频率,更高的demotivation contrast,这样的话。
我们就可以拿到更加高精度的一个深度分辨率,好来这几个东西挺矛盾的哈,挺矛盾的哈,我们要同时拿到比较高的一个啊强度值啊,频率值啊,可以一个这个demodulation contrast,这个啊挺难的哈。
挺难的,这个时候我们就要考虑到我们这个照明的一,个情况了哈,这个考虑到照明的情况,我们知道我们深度的一个噪声,也就是深度的分辨率啊,它是呃,直接正比于我们这个刚才说到了那三个地方,刚才说到那三个地方。
就是我们强度这个频率,还有一个demodation contrast,是不是,当我们提高这个强度,也就是,那怎么提高我们这个最后的一个信号的强度呢,哎简单一点,我们就是增加一下我们这个进光量。
是不是就可以了,所以我们想要更更大的光圈,也就是更小的f数或者是更强的一个照明功率,来实现我们这个nm的一个提升啊,m的提升,这个地方还给大家强调一下哈,这个是根号下n m那三个啊黄金的一个指标哈。
fm调制频率,还有demodation contrast 3个黄金指标,第一个就是我们提高这个强度哈,这个强度我们知道这常数不管嗯这个距离,比如固定的apture,apture。
我们可以想办法提高这个app,而且我们可以也可以想办法提高qe,对不对,同时大家没办法,我就可以提高这个照明的一个功率,提高这个照明的功率,这个是最直接的哈,qe这个是半导体的。
有时候他器件买回来就不好整了,对不对,但这个aperture跟这个illumination,我们是可以想办法让这个呃光圈更大呀,或者是照明功率更大,来降低我们这个depth noise的一个影响。
但是我们平均的一个照明功率呢,照明功率实际上就是哎,我们这个照明的功率乘一个距离啊,这个啊这个是duty cycle哈,duty小d integration,duty cycle。
还有一个就是frame rate啊,这个就是我们最后的一个平均的一个照明功率,当我们考虑到这个距离,和这个相位之间的关系呢,有时候我们发现我们这个信号会变形哈,它不再是一个呃,进行照明的时候。
我们会发现这个发出来这个光波啊,他不大可能是这个就是sin呀,或者是一个正弦波参考光呢,它也会受到带宽的限制,它也会产生一定变形哈,比如说我们在100兆的情况下哈,100兆的一个情况下。
一个单品是一个理想的一个正弦波,但是我们比如说一些带宽啊,比如说像呃一个方波打出去,我只能保留机屏,就是100兆100兆的一个方波,我们材料频率稍微高一些,他就是一个变形的这么一个波形诶。
500兆一起1g采样率,它都是一个变形的,就是我们最后啊这个受受限于带宽啊,或者一些其他电路上的一些干扰啊,比如说驱动电流诶,它是电磁辐射的一些干扰,或者是电路自激了啊,有些大的电感效应啊。
都会影响我最后波形的形状哈,这个波形的形状我们做了相关之后,我们发现这个这个波形的形状,会直接影响到我们最后测量到来的一个相位值,我们就会发现,最后我们的相位跟我们的一个距离。
它会有一个非线性的一个关系,也就是右边这个图里面,我们线性的一个这样写一个关系,我理想的情况下,我们的一个深度跟距离,它是一个线性的一个关系,但是呢,实际上哎,我们就会拿到一个非常难看的一个波形哈。
非常难看的波形,这是因为我们这个信号走样引起的,当我们把这个走样的信号啊,走样的信号用这个arin q去把它画出来的时候,哎我们就发现啊,这个它不再是一个圆了,它不再是一个圆了。
它是一个像红色标注的这个地方诶,它会随着这个相位的变化,它的一个confidence也在变化,它的confidence也在变化,这个时候我们就会拿到一个周期性的一个误差。
就是相位跟我们的深度有一个周期性的误差啊,有时候我们也叫这个周期性的误差,叫非线性误差,这个是可以通过标准解决的啊,这个其实问题不大,问题不大,诶不好意思,这个解决方法呢。
就是我是通常建立一个这个look up table啊,look up table通了,建立这么一个look up table哎,来就是找一这么一个通过这么一个查找表吗,建立起来这个相位和啊。
我们这个深度之间的一个关系好,怎么样建立这个查找表呢,一般哈我们是可以用一个呃,下位扫描的一种技术哈,下位扫描的一种技术,我们比如说100个采样点,我们去扫描啊,这么一对这个face进行一个延时哈。
把这个100个点建成一个表,另一种方法就是我们用一个呃,可以精确移动的一个物体哈来做一个啊,又把它测到了一个深度值作为相位哎,来直接建立起真实深度跟相位的一个关系啊,这是我们校正这个非线性误差的一种啊。
两种基本的一种常见方法哈,当然说了这么多哈,我们终于到了今天的一个比较头痛的问题啊,我们刚才讲到了,我们整个itt的一个成像原理啊,还有一些基本的误差呀,啊我们这个深度的精度到底跟什么相关哈。
我们这个我们记住哈,啊,跟这个confident,跟这个我们最后的一个呃能量强度啊,跟我们的这个调制频率,跟我们的这个demodulation contrast,都是啊直接相关的一个三个参数啊。
大家记住,当然我们现在就要考虑到这个外在的一些影响,就是叫multipath,这个影响叫多路径,当我们考到这个多路径的时候呢,我们会发现哈我们一个pass啊,一个pass我们拿到了一个值。
哎我就是一个理想的一个呃下位的一个移动啊,对不对,但是本身啊这个光可不听你的哈,我们这个光它要考虑到它的一个global ation,它这个path啊是想往哪走。
就是根据他那个b r b r d f方程,去跟着往哪走,最后我们在我们传感器上拿到的一个信号呢,往往是很多路径反射回来信号的叠加,这个信号都会被你的这个就是photonic mister device。
可看到这个就会引起相位的误差,所以说呢这个是外在跟sin相关的,跟我们场景直接相关的一种误差啊,这个是怎么玩呢,我们先仿真一下哈,我们这个嗯用这个康奈尔box,康奈尔box来先做个简单的仿真哈。
我们把这个camera放到这个box的一个正前方,正前方,我把这个可能要box本身的一个尺寸,都设为3米哈,然后把这个sensor距离设为5米,理想的情况下,我们测到了一个深度值啊。
它就是一个3米乘3米的一个boss,诶但是好家伙,我们考虑到这个多路径的测量呢,比如说这个五次的一个global elimination,这个时候我们后面多次反射,它路径本身比较长嘛,我们在相互叠加。
我们就会得到一个相位的延时,最后我们这个计算到了一个ship,就会有一个很大的一个误差,本身我们只考虑多路径,不考虑scattering的时候呢,这个误差往往是比我们实际测量,实际的一个真实值会大一些。
哎最后我们实验哈也会跟这个相似,最后我们发现我们最后计算到了这个ship,比我们实际这个ship大一米左右啊,大一米左右,侧面呢也大了0。6米,这个就是非常大的一个误差哈,这是etf啊本身的一个痛点。
当然这种啊multipass本身,在这种director ilumination,就是我们这种就直接嗯就是direct time flat,这种情况也会有,但是direct flies呢本身哈。
他那个他那个那个,我们可以直接提取前面那个相位就好了啊,我们可以很快把这个time his gram测翻,把直接提取出整个相位就可以比较好的算出来,哎我们这个相位真实的位置,但是对这个i top自己哈。
没办法,我最后就拿到了三个强度值,我要通过这个强度值结算相位啊,对不对,这个时候比较难比较难,怎么办呢,那我发现诶这个multipinference,就考虑到整个场景,我们怎么解决呢。
我会发现这个相位叠在一起,根本就没有办法去搞定这个事啊,但是啊聪明的人类呢就会发现这个还真有,为什么我们说这个这个time of flight image这一系列呢,是这个人类智慧的一个巅峰哈。
他真的有非常多的一个技巧,可能你看了就会觉得这些技巧真的非常惊艳,非常惊艳,我们可以看到这个sensor radiance哈,我们是怎么样去就分开,这个不一样的一些成分呢。
不一样的成分呢我们有这个直接照过来的光,也有啊,这么多次反射间接照过来的光,我们去啊,怎么样去把这个啊intersection去把它分离开了,对不对,我们会有不一样的一个呃,face delay啊。
这个是其实挺讨厌的哈,挺讨厌的,它呢还会有不一样的一个emptitude的叠加,不一样的emptitude叠加,哎我们最后发现,我们最后传感就是光源发出去的,这个一个一个正弦一个信号啊。
mit snl soul,经过这个场景,我们这个最后拿在传感器上,拿到了一个正弦波呢,理想情况下就只有这么一个向移哈,然后有一个相位跟outset衰减,但是呢。
我们考虑到这个multipath就会变得非常复杂,所以说啊这是gpa跟rnana哈,就我们rnas是我们这个整个啊,计算摄影里面的鼻祖之一哈,鼻祖之一,那时候跟啊上课大于金玉老师之前说过啊。
于老师就觉得这个这个这个这个,水男也非常厉害啊,这是整个计算摄影的鼻祖啊,哎这个我们干脆下节课就邀请一下,水南亚组里的一些啊老师来给大家讲一讲啊,这个是大亚组里的一些工作,好了哈,我们下下下周安排哈。
下周安排,这个时候我就有一种方法叫faceming啊,facemail,我们这个菲美景来如何描述,我们这个呃这个弦波发射出去,到接收的一个过程呢,那么左边还是一个正弦波啊,有他这个offset。
还有mg 9的a他的目前的相位是five哈,我们把这个公式写出来,就是i等于a cos我们一个t啊,这是motivation frequency,然后有一个延时费,我们用facer image来做表示。
我们就可以用一个欧拉方程啊,来直接表示这个啊幅度啊,相位啊,来好表示一下哈,唉这个时候我们就可以用一个fer啊,也就是一个欧拉方程来去描述这个下面编号,最后我们发现我们发射出去的一个正弦波。
它有这样一个相位,这样一个振幅,经过这个这个这个场景的一个反射,我们这个振幅啊衰减了下位啊,经过一定的一个啊延时哈,就打到这个位置,哎呀我们最后拿到了这么一个vector啊,这么一个vector啊。
就是像图里面这种形状,本身呢我们就可以用一个我们这个场景啊,就可以用一个light transport coeffici,这一个负负函数啊,哎来表示这整个这个它的一个变化,实际上就是啊。
一个相位和相位的移动跟幅度的衰减哈,哎我们最后就会拿到这么一个嗯,就是一个receive的一个呃下位跟赋值吧,但我们要考虑到这个多路径反射的时候呢,多路径反射哎。
我就有很多个这么一个light transport,qualification的一个叠加哈,一个叠加,最后我拿了一个叠加法的信号,它是什么样子呢,本身啊我就发现这个这个非线性啊,这是多次反射。
它每个信号都是可以直接线性叠加的,也就是我们这个输入输出哎,它是线性的好,但是线性的其实这个其实挺好的,唉我们这边最后就可以用,我们这个最后我们在图像传感器上收到了值。
就是那个light transport matrix,一个这个array哈,哎最后再乘上我们这个发射取势一个方程啊,最后通过我们考虑到这个,propagation的一个问题啊。
我们发现这个一个发射器的光自由传播,诶我们可以看到啊,这个cpu所有的这个这个散播啊,只是发生了一个像移,这个facer呢也就是这个vector转了一下哈,这个角度转了一下哈。
这就facer就是稍微转了一下,当我们考虑到这个场景的一个反射呢,我们这个啊,这个正弦波跟这个facer又是怎么变化的呢,诶考虑到一次反射到这个位置哈,这个反射我们只考虑它的反射率哈。
仅仅只是幅度有了个轻微的衰减啊,幅度有个轻微的衰减,诶这个时候我们发现诶,这个整个过程描述的非常清晰可观,哎我们回过来的时候,就考虑到一些呃这个光的衰减啊,或者是一些什么。
我们这个幅度跟相位都发生了变化,相位是因为岩石哎,幅度是因为啊,随着距离的一个或者是一个介质的,引起的一个衰减,最后我们才发现我们的facer啊,这个这个vector,它的这个幅度和相位都发生了变化。
当我考虑到这个多次路径,比如说这是一个scattering的一个medium,它会一次反射,二次反射,三次反射,诶,最后就会产生这么一个facer一个叠加哈,这么这么样一个facer的叠加,对不对。
这是我们最后拿到了一个啊result的一个值,最后我们要看一看这个下位的一个变化,跟这个modulation frequency有什么关系呢,就跟我们调制的频率有什么关系呢,你考虑到我们一个固定频率。
我们移动了一个啊d的一个距离,我们发现我们的相位啊,产生了一个f的一个变化,这是我们我们考虑到这个modulation frequency是omega,诶,我们不断增加这个频率。
我会发现这个相位是不是诶变大了呢,就是说我在有一个固定的距离,我十兆的时候我变了1度,我100兆的时候是不是就变了10度呢,是不是哎,这个时候我们就可以增加频率,提高频,通过提高频率来放大我们的相位。
这是一个线性变化的一个过程啊,我们这个呃相位啊,实际上就是我们一个d除以c哈,我们一个d除以c,我们发现频率越高,唉相位变化的越大,对不对,然后我们最后发现诶,你不断提高频率,第二次反射诶。
多路径我们整个路径就可以啊,但是我们这基本上很近的邻居哈,很近的邻居,它会产生一个嗯大致相等的一个eu,然后呢我们在这一一定的范围内拿到了一个啊,拿到一个face的一个像仪呢。
就会产生这么一个fser啊,这么一个扇形的一个fser,在整个扇形区域内都是我,因为嗯就都是有可能的一个深度值啊,这就是这个multipath带来了一个误差哈,multipath带来的误差咋办呢。
提高频率,提高频率,我们把这个角放大,这个我们差不多平均的位置哈,就是这个啊result的一个位置哈,就大概我们的一个啊深度值,那它周围这个黄色区域都是啊,与之对应的一个误差的一个范围,在嗯都有。
在这整个范围内的值都是可以得到的哈,这有可能会得到的,让我们看一下,但是我们提高频率,不断提高频率哎,再提高频率,整个误差在360度1个范围内,最后变成了啥呢,变成了一个offset,变成一个常数值。
这个时候我们就发现,当我们把频率提高到一定程度的时候,给你提到一定程度的时候,多路径带来的误差消失了,这个频率很高的频率,使这个多路径的一个误差消失哈,这是一个非常神奇的现象,来给大家再看一遍哈。
再看一遍哈,提高频率提高频率,所有的误差再分布到360度的一个地方,它就会最后就变变成了一个平均过的一个offset,最后你测量了一个峰值在哪,它就是这么一个值啊,然后呢我们就发现。
我们知道了这个增加这个调整频率也可以啊,到一定程度之后,可以把这个多路径的效应减为零哈,这个是非常呃,这个让人家啊兴奋的一个这个这个这个发现,哎我们最后发现这个int reflection。
就是我们这个多路径反射诶,仅仅只是变成了一个offset啊,这个offset,大家可以看到这个offset这个变化,也就是我们频率速度高的时候,这个多路径不再影响我们的相位了。
只是影响了一个offset,因为我们在这个360度的一个范围内,平均了,来我们要考虑这个啊,这个face的一个ambiguity啊,这个是怎么办呢,我发现a b两个地方拿到了一个呃,相同的效应啊。
实际上这个b这个地方是它是移动了二派,再加上这么一个相位哈,这个我们有时候啊把这种现象叫做face waking,face waking,但是有face win呢。
我们就需要face and waking哈,把这个这个一环一环的这个深度值校正起来,校正起来,啊这个距我们一般的话是嗯可以用两个频率啊,或者是降低这个调制频率而来实现呃。
这么一个face and working,但是我们发现用一个低调制频率,就和这种误差可能会比较大哈,一是哎d的调制频率会让这个噪声深度,噪声影响比较大,第二个是d的调制频率。
本身这个multipath的影响会比较大,但是呢我们又要想比较大,比较大的一个测量范围,就是比较大的一个vip的一个范围哈,我们就需要怎么办呢,这个时候人们就提出了,我们可不可以用两个很高的高频。
来产生一个波波啊,产生一个波波,最后实际上我只有两个高频在测量,但是呢我是又是用一个低频的一个win的范围行,诶这个时候怎么处理呢,我们可以看到这低频率可以做到一个。
非常大的一个呃face ambiguity的一个范围啊,就是我们可以测量范围,比如说嗯100兆的时候,我们只能做到1。5米,十兆呢我们就可以看到15米才会产生face wip,有15米已经非常好了哈。
15米非常好,所以说这个还是解决不了根本问题,所以说我们通常是用两个高频函数啊,来啊解决这么一个问题哈,目前我们有一个高的一个调整频率,一,有一个另一个高度特殊频率二,这两个频率会稍微有点不一样哈。
稍微有点不一样,michael period of small perio,据说也叫michael tom flight,我们传统的一个呃cofly shifty呢,在我们一个低的一个频率下哈。
我们组上用一个三个测量值哈,但是这个ml shift t呢我是用两个高频,两个高频有四个测量值来,我们看一下这个在我们connect boss里面的一个表现。
诶这个director radiance没问题,intel这是多多路径反射诶,我们最后诶通过两个相近的这个900兆,当然这个器件上调制是不大可能哈,现在可能啊最快最快的一个top器件就是嗯。
长光辰星的可以做到165兆哈,当然这个地方咱就先仿一下,再仿一下,我们发现这个957兆跟930兆,是两个调整,平均下他这个位置变的哈,都是相互不一样的,我们就可以通过这一点微小的变化来去啊。
把这个ambiguity去干掉,同时也会减少啊,这个对multipath本身对这个深度值的影响,哎这是我们本身的一个face map啊,face map这两个都是相互不重叠的,这每一圈都是一个nbc。
就超过二派的范围,就是产生这么一个彩虹圈哈,我们这个啊传统的一个top image呢,比如说这个10Hz的时候,我们这error有多大呢,这人有多大吗,我刚才仿的是这个1米哈,这个好家伙。
这个30%的这个误差哈,这个痣有点大,这个有点大,这个micro他们flying mei,它就是基本上和这个肯定box,本身已经非常接近了,已经非常接近了,就说下节课呢我们会继续的讲解。
我们facer imagine的第二部分哈,这个嗯会给大家讲一下呃,更加详细更加有趣的内容,当然除了这个fashion image到底是怎么实现的,还有一个我前2年好像是19年siggraph。
我们看到一篇论文叫那个叫那个,我们下节课会讲到哈,它是一个优化的一个连续波的调整,函数的一个内容哈,当时我们也看到这个不是很难啊,不是很难啊,但是看到这个效果啊,是真惊艳啊,这个难怪人家能种西瓜。
是不是,所以说今天的课程就到这里啊,同学们有什么问题啊,可以在现场问一下,我们下节课哎将会继续本节课的一个内容,继续来给大家讲解这个啊,in dtime flat image,这个课的内容是啊。
非常有兴趣,但今天的课程可能会涉及一些啊,非常底层的一些内容,可能会有些难哈,大家还是推推荐一下,大家去啊,好好消化一下啊,他这个可能可能有些同学没学过电路和信号,这个底层发生了啥,不是很清楚。
这个要需要啊,同学们再花点时间去看一看这个里面的内容,大家还有什么问题吗,好既然没有问题了,我们今天的课程就到这里,再次感谢啊,同学们来到games 204啊,我们下节课同一时间最近哦,对对对对。
忘了嗯,刚才说到了,我们下节课嗯,我们这个干脆就邀请一下这个,我们这个计算摄影鼻祖,是那样他们组里的一个啊工作者啊,我们就可以邀请他们来给大家给一个guest lecture,这个希望大家下节课可以啊。
准时收听啊,准时收听,这个咱说到做到啊,说邀请咱就邀请了,啊这个同学问有啥教材没有,这个,这个这个可能这个方向确实没有什么教材可以,那个大家还是可以看ppt哈。
这个我给大家啊花了很大的精力去收集这些ppt,大家还是可以看一下,这个低调制频率下消除multipath有啥好方法,那除了我们课上讲过的这个啊,这个消除modetest方法其实还是有其他的办法的哈。
比如我们两个呃低频情况下,我们测到了一个,比如说十兆跟15兆这两个低频,我们测到了一个multipath不一样哈,我们是可以通过这个不一样,搞一个嗯优化函数哈,优化函数来把这个去优化一下哈。
因为我就是通过这个差异来去算一下,第二种办法呢,其实之前我们组里在18年啊,做了一篇这个论文哈,就可以哦,还是还是得用两次测量,两次到三次测量,这个时候我们就可以用这个呃两次测量,搞一个小神经网络哈。
我们通过这个呃不同频率下测量的差异,来进行对深度值进行校正啊,这个论文的名字叫end to end time of flight imazing啊,你可以去搜一下啊,除了这个之外啊。
就我们圈里有一个好朋友叫郭麒,他有篇论文叫啊flat dataset,做了一篇这个啊,top就是这种消除multipath的一种方法,这个是18年18年那篇论文吗,18年诶,18年是19年。
叫flat data set,f l a t data set,这个是可以看一下哈,那天不是我的那篇,是我一个师兄的,但我那时候还比较小,那天是苏若晨,给同学们还有什么问题吗,好没有问题啊。
今天的课程就到这里,再次感谢同学们来到204,下节课,同学们来给介绍一下,那样大佬最近的一些工作啊。
20.现代游戏引擎架构:面向数据编程与任务系统 (Part 1) | GAMES104-现代游戏引擎:从入门到实践 - P1 - GAMES-Webinar - BV1EP411V7jx
哈喽大家好,欢迎大家回到game 4104,现在引擎的理论与实践啊,我是王鑫,那个时隔两周没和大家见面了,那这两周同学们过得怎么样,那个希望大家这个马上要开学了对吧,然后大家都能够顺利的返回学校。
那么暑假呢总是这个过得非常的快,而且今年暑假有个很大的特点,就是特别特别的热,真的我从来没见过这么热的暑假,那么所以的话呢那我们的课程的话呢,其实也进入到我们的尾声,也就是最后三节课。
也就是我们的高级课,那那么这里面的话呢其实也是我们开始逐渐去有时间去思考,我们的小伙伴们现在学的怎么样,大家进入这个课程有没有什么困难,所以今天的话呢我们首先跟大家讲一下。
我们现在课程组跟同学们交流下来,我们决定要做的几件事,第一件事情的话呢就是说实际上这个课程大家如果能真的跟下来,实际上是非常非常的挑战的,那么所以的话呢我们觉得确实就是要给同学们发一个证书对吧。
那么就是根据同学们完成的情况,那我们课程组呢也会很认真的去那个根据同学们提交上来的作业,然后给同学们打分,然后呢就是如果大家做的都是对的,我们给大家60分,如果做的非常好的话,是100分。
就所有的点全部实现了,那个我们当时作业的要求,所以的话呢如果就是说同学们每一个作业的话都做了,而且都能够及格的话,我们认为应该给大家发一个。
这个就是成功的毕业于game 104课程的这样的一个一个一个证书,那么如果同学们如果有两个作业能达到100分的话,我们认为是非常优秀的,那我们要给大家的这个就是不一样的,就是excellent。
那如果大家真的三三门作业做的都是满分的话,那我觉得一定是非常的outstanding了,所以这件事情的话实际上啊非常了非常有意思啊,就是我自己想想,要是我到时候毕业的时候找工作的时候,诶。
我game 4204课程我真的学完了游戏引擎,然后我而且我还拿到了all standing的话,嗯反正我觉得这个工作应该是蛮好找的对吧,这个好像说的很现实啊,今天找工作还是非常挑战。
然后的话呢就希望大家一起多多加油,多多努力,那么其实104课程的话呢,我觉得教的很多东西还真的是在就是游戏行业,甚至游戏行业之外都是蛮有用的一个东西,所以我自己也是蛮开心的,就是随着小引擎啊。
随着这个课程的话,同学们都说诶,教的这些东西能够帮助大家能够这个更快的适应自己的行业的需求,这个诶我觉得不错,挺棒的,然后呢另外一个课程组的话,我们肯定要鼓励大家去好好这个这个做作业了,对不对。
那但我们也很穷对吧,我们也没有太多的东西可以奖励大家,那我们怎么办呢,我们想了想嗯给大家,那个我们的t恤我们多印几件,就是所有的同学大家只要做完了这个我们的作业的,我们就是全部做完了。
而且60分以上我们成功毕业了,我们课程组就送给大家一些t恤吧,也是留作一个纪念,因为这半年能够坚持下来,其实真的非常的不容易,我自己讲的已经是累得要死要活了,今天他们说我的眼睛都肿了对吧。
那但是的话呢我觉得其实大家能坚持学下来也是非常的怎么说呢,非常的厉害,我觉得这真的是一种人生的考验和体验吧,那么然然后呢,因为考虑到好多同学刚刚进入到我们的课程呢。
后面的话我们也会想办法给大家更多的帮助,所以的话呢我们想这次作业交的时间的话,不一定要等课程结束,我们的课程大概在9月中下旬就最后一节课能讲完,104讲完哎,大家是不是有点舍不得。
反正我有点舍不得大家啊,虽然说每每每次都很累,但是每周跟大家来讲一讲我们的东西,那么那我们的作业的话呢,就是说我们想把它设在就是呃10月底给大家有更多的时间去完成作业。
而且课程那个时候没有那么紧张的时候,大家有什么困难啊,或者题目不知道怎么做的话,像我们的课程组的小伙伴去那个求助,然后呢我们尽可能给大家帮助,争取我们能够拿到毕业证书的同学越多越好对吧。
这个我也会很有成就感,那么这是第一件事儿,那第二件事情的话呢,就是哎呀这个是这个是我们欠大家很久的一个东西了,就是我们的皮克罗引擎,其实现在一直在开发。
然后的话接近过段时间可能会有一个比较大的update,上面给大家讲到的就是我们还是比较丧心病狂的,比如说我们会做gpu particle,做一个版本进去了。
我们甚至还在想把他那个rh那一层整个重新设计一遍,让它做得更漂亮一点,这个就作为程序员嘛,有的时候一旦代码开写了,你会忍不住不断的把它写得更好,但是呢我们课程的很多同学就是说这个引擎很棒。
但是我看不懂怎么办对吧,那些代码为什么这么架构,比如说大家一直想了解的,比如说我那个反射是怎么做的,对不对,那实际上呢我们就在这个地方立一个flag吧,就是等我们把104课程忙完之后。
我们争取在10月10号我们开始这个flag立在这了,就是我们开始跟同学们去讲解一下我们的这个pico的引擎,我们也尝试去回答一下,那么就是说呢因为那个尤里斯课程结束之后,大家可能很难找到我们了。
我们当然我们有自己的这个就是呃那个那个就是我们的b站上,好像是有个视频账号的吧,是我好像叫叫我的名字,然后但是的话呢就是说呃同学们也可以关注我们的微信公众号,我们专门为po这个课程做了一个微信公众号。
大家可以扫到那个旁边的二维码,加入到我们的那个就是呃微信公众号里面,然后大家在里面选择点击那个入群啊,点击那个入群之后呢,输入入群之后可以进到我们104课程的各个微信群里面。
这样的话我们如果后面有代码解读啊,这些东西我们就把这些资料分享给大家,有什么问题也可以告诉我们,就是说我们还挺希望就是说这个pico的效应器,我们一起合力把它做的越做越有趣对吧。
就是将来以后变成我们大家这个毕设的这个标配,那就太棒了对吧,人人手一个游戏引擎,就这么实现的对吧,其实我们都基于plog folk一个出来,所以说这个是啊就是10月10号我们争取能够开始启动了一件事吧。
但那个时候不一定是我了,可能是我们的课程组的,后面的无数的这个无名英雄跟我们一起来做这件事情好,那么第三件事情的话呢,就是哎回答一下上一节课同学们问我们的一些问题啊,我今天看到这些问题的时候。
我的第一反应就是我已经严重怀疑我们课程组是不是真的,这个筛出来是我们同学们问的问题,因为这些问题问的实在是太专业了对吧,第一个问题相对来讲还比较回答好回答。
就同学问我说诶这个就是到底游戏有没有可能去反作弊对吧,怎么能不能彻底解决它啊,这件事情的答案就是至少我目前知道的肯定是no搞不定的,因为这玩意儿真的是道高一尺,魔高一丈。
就是说这也是全世界所有的online gaming的一个极其头疼的问题,就是其实有很多种解决方法,首先从技术上来讲的话,你可以通过加密啊,通过各种方法来解决它,对不对,但是就像我讲的。
就是无论你用什么方法,总有人能破解,那么接下来呢你还有方法什么的,就是你从设计上就尽量避免就是别人去作弊的时候,他能够获益,那其实在比如说像有的游戏,它的彻底是放在服务器判定的,这当然是一种解法。
但是它会影响手感对吧,它不再用本地的物理或者其他东西来解决,它实际上是用那个就是其他的一些更多的可控制的,这种机制来进行判定,那么其实呢我自己个人觉得还有一个很有意思的方法。
就是说诶采取这种这个这种很奇怪的这种社交化的方法去打个例子,比如举个例子啊,比如说像vac对吧,就是那个那个steam上的那个反作弊的那个方法,他的做法是什么样的。
但是说诶你只要在一个产品里面被那个游戏的发现了,你作弊了,我给你报到vc去,那你整个steam账号可能都要受影响,我想想看,我stea上那么多的游戏对吧。
我要是在一个就是你们做了b导致了我那一串游戏都不能玩了,我想想打死我也不作弊了,我觉得这个可能是一个很好的方法了对吧,所以但是简简单来讲的话,就是说其实在游戏里面。
反作反作弊是一个很长期的一个艰苦卓绝的斗争,那么他是要持续的去不断更新,而且呢我一直认为它不是一个纯技术的问题,它实际上还需要就是说我们和社区一起,玩家一起来,就是说联动去解决和发现这些问题。
所以啊它永远是个动态的平衡,那么第二个问题呢,这个问题就非常的专业了,他说唉请这个说一下,就是微服务和分布式的服务器架构到底有什么区别,其实呢差两个不是区别的关系,我个人理解啊,什么意思呢。
分布式的服务器架构呢是一个标准的,也是最经典的一个服务器的选择方法,它相对于就是说你把所有的这个业务写到一个进山里面去了对吧,那你就不是分布式的,那分布式的意思就是说我会在多台物理机。
用多个进程或者是线程协同去工作,然后呢每个人各司其职就完成工作,但是这种这种分布式呢它的一个架构可以很多种,比如说我们可以把数据库的访问和游戏逻辑分开了,对不对,那我们在游戏里面开很多战局对吧。
那我每一个战局我会形成形成一个独立的game server,那你只要把一个整个游戏世界的业务分成一个一个的这个单独的进程,或者是现成的话。
那这就是叫做分布式的这个游戏的这个这个server arc architecture对吧,但是呢microservice呢它其实是一种设计理念,它我认为是分布式那个服务器架构的一种实现方法。
他是讲的一种思想是什么呢,就是说诶我会把这些这个就是说这个服务器在后台的一些东西,拆成尽可能干净和单一的我们的一个业务的实施单元,那比如说邮件聊天对吧,用户登录,比如说房间的匹配等等。
都有了变成独立的服务,那么这些服务的话呢,它其实呢microservice的一个点,就是最早期的microservice都是尽量争取是无状态的,就是希望诶我可以很容易地对他进行扩容啊。
对它进行这个减负啊,因为我要支持弹性的变化嘛,另外一个点是什么呢,就是说哎一旦有些服务一旦出了问题之后,诶,我可以通过那个服务发现对吧,我会发现这些问题,同时的话我会再去spa更多的这个服务出来。
所以呢它是一种就是说非常好的基于容器的一种这个服务器的分布式,服务器系统的一种实现方式,那么这里面无所谓高下吧,其实就如果大家真的去研究一下。
就是啊现代online game的这个游戏那个服务器架构的话呢,它也不是完全的microservice,它有些服务它是用mc service的架构去做的,但有些东西的话呢,它还是要全局的进行。
这个就是说自己去管理自己服务,所以这件事情的话呢,就像我一直在讲,就是当大家真的开始在做有性情的时候,千万不要纠结于某些具体的概念,一直要强调就是甜豆浆,咸豆浆到底哪个好,实际上是适合你这个snl的。
就是你最好的方案,那么第三个问题呢,哎呀那就是那就太难了,这个问题我觉得同学们,我们说诶如何构建一个就是全球能够联网对战的这样一个战斗系统,那这个问题要是真的大家能回答的清楚的话。
基本上你到很多的这个游戏的大厂里面,可以申请一些比较资深的开发者的这个职位了,确实这个事儿啊,问的还是挺挑战的,因为全球联网的这样的一个这个battle game,就是或者说这个开房间的这个游戏的话。
技术上还是有蛮多挑战的,因为这里面有比较大的一些挑战吗,比如说全球的这个物理连接的延迟对吧,你这么多的节点来回,他怎么去延迟,这么简单来讲的话呢,实际上第一个你的服务器可能是要进行分布式架构的。
就是说在世界各地它都有各种各样的服务器,那这些服务器之间的话呢,你要建立它的高速通讯,那么这里面的话一个简单的做法就是说你可以给他们加一些,就是我称为叫这个high speed。
那个那个那个主干线就是高速的那个干线,让你的全球各个机房之间,它是走专线,通讯速度要足够快,当然了,你再快也快不过光速,对不对,但这里面有个小有意思的小细节,就是说我们比如说从一个地方。
比如说中国连接到美国,或者从中国连接到欧洲,这个中间最大的延迟来自于什么地方呢,实际上并不是光传播的速度,光传播速度过去了,也就是差不多,如果没记错,应该也差不多也就100ms左右吧,或者是还不到。
但它真正慢在什么地方呢,是无数次的网关路由,就是说诶这个地方不停的转发转发,转发转发这个地方会导致很多的延迟,所以的话呢实际上就是如果你真的要做这种全球联网的游戏的话,那是一个很花钱的事。
因为你要在世界各地,要么就购买,要么自己建设这种专用的机房,机房之间用那个光纤尽可能直接连起来,当然这里面有些虚拟的这种叫做那个专用通道,这其实有很多的这个就是通讯服务商提供这样的服务,那么同时的吗。
还有一个技术呢,就是说哎比如说我在欧洲对吧,欧洲的用户是来自于各地,他们不要直接连你的服务器,其实你可以提供一些他们本地更方便的这些节点给大家举个例子吧,比如说如果你要去服务南美玩家。
大家可能看到南美洲是一块大洲,对不对,但是南美洲你看它的西侧有个国家叫治理,特别的长,然后那个山好像是应该是那个忘了哪个山很很长的,就是全世界最长有1万多公里,就是从北美洲一路贯穿下来到南美洲那个山。
那个山的左边和右边的话,虽然大家都在南美洲,但是它的网络是非常隔绝的,所以说你他那些国家连接,可能亚洲的速度都比连接套那个那个那个大陆的另外一边的速度要快。
所以说当你要去服务南美洲这样的一个一个一个世界的时候,你实际上在那个区域要做很多专门的处理的,你的服务器到底架在墨西哥还是阿根廷对吧,你千万不要加到智力取,那然后呢你跟北美到底是直接连接。
还是通过其他的方式,这里面就有一些很有意思的问题了,这个里面的话呢就下次有机会给大家详细讲,其实非常好玩,就是你可以看到全世界的这个网络的图图图谱,所以说这个问题的回答呢。
其实从它的概念层面上来讲还是不是很复杂的,但是呢你真的去实施它的时候,你发现你还真的需要知道全球的这个高速的光纤的分布图对,所以说这个问题是我这个问题的答案好。
那我们今天那个就是前面给大家的那个闲话就讲到这儿了,就是这个d o p dorange program programming,面向数据编程和job sim,但是我实际讲的顺序可能是倒过来的。
因为我今天在写这一趴的时候,我发现哇这里面的概念真的特别特别的多,然后这也是大家很期待很久的,因为什么呢,因为这些都是现在特别时髦的一些概念。
那今天的话呢我的责任就是尽可能用大家都能懂的语言给大家讲清楚,这里面的这个基础的想法和思想是什么,具体的s d k啊,什么东西,大家可以自己去查对吧,好,那我现在来挑战一下,看看能不能用这个简单的语言。
让大家有大学cs的基础,就能听得懂这两个比较前沿的这个游戏引擎开发的理念好,首先给大家讲一下,就是为什么要做这件事情,实际上的话呢在现代游戏引擎,它实际上是跑在我们这一代的各种各样的操作系统上面。
而操作系统呢又是跑在各种各样的意见上面,所以说呢其实游戏引擎最大的特点是什么,就是我们对performance要求非常高对吧,但是我们的带宽也是有限的,我们的这个算力也是有限的。
那你想我们在1/30秒啊,我们要完成大量的这种物理的这个逻辑的运算对吧,物理的模拟还有什么,我还要一瞬间炫出那种就是几百万,是上千万个像素里面无数的光的反射积分。
这些东西大家记得我们在rendering那一刻讲了,那天那些运算,大家觉得是不是很恐怖对吧,那我们后面讲到物理呀,讲到游戏逻辑啊,讲到一大堆这种动画这些系统的时候,大家想想这些都是不是很恐怖。
这个对算力的要求都太高了,但是呢游戏引擎还挺恐怖的,为什么诶,所以我1/30秒全部得算完诶结果我们的游戏玩家说我不满意,我要60帧啊,60帧感觉还不对,我要打电竞,我要120帧好了吗。
这个游戏引擎就疯掉了,我我怎么样才能够对吧,这个这个臣妾做不到这么高的要求,我就这么一个cpu,我怎么做到,所以说其实当我们在设计游戏引擎的时候,我们实际上是需要把操作系统。
把硬件的所有的性能几乎压榨到极限,这就是为什么所有的这个就是重型游戏团队或者是引擎团队的话,基本上都是游戏的这个这个硬件和操作系统性能的这个大师,就是这个道理,那么今天的话呢我们先从第一拍开始讲起。
就是这个并行编程,epio programming对吧,其实写到这一part的时候,我我当时准备这个课程的时候,我跟我们的课程组的小伙伴,我们就一边准备,一边在笑,为什么呢。
因为我这一趴说实话让我回想起了,我在大学上这个操作系统这门课的时候,我印象我上这门课的时候,那个老师啊年纪很大,是一个很一个老教授,每次敷这个眼镜,然后这样看着我们我们这么厚的一本教材。
然后每次我听他的课听得都昏昏欲睡,然后全部就忘了,然后考完试之后基本上全还给他了,但是呢当我们去准备这个power program programming的时候。
我发现哎呀哇老教授讲的所有的东西我又全部回想起来了,但我今天尽量不要切换到老教授模式,我还是用这个很简单的语言,争取把这个并行编程的一些有意思的东西给大家讲清楚。
因为它这个东西就是我们讲的job system的基础,就是首先的话第一点的话就是为什么要并行编程对吧,那其实这就是一个事实,就是说其实随着现在的晶体管,就是现在的那个就是集成电路的工艺。
已经almost hit到它的那个量子力学的上限了,大家都知道我们现在在做吐人类的突破,22nm,对不对,将来以后好像理论极限是0。5nm,但是这个应该是几乎不可能了对吧,再往下1nm。
那接下来再怎么上呢,也上不去了,对不对,那那怎么办,其实我们可以看到,在过去几年,如果大家经常买电脑的人,就知道就是好像这个核是越堆越多,但是呢主频现在基本上三点几个赫兹就上不去了吧。
我我们我们团队有一个就是英特尔送给我们的那个主频,可以冲到四点几,但是那那个那个声音已经是基本上有点吓人了,所以说事实上的话呢,就是啊我们的整个processor已经到了他的一个理论的上限了。
就是说我们再往上冲的话,无论是散热也好,功耗也好,都有很大的问题,所以这个问题的解决方法是什么呢,就是说哎我既然不能够把这个盒越做越快,那我能不能用更多的核解决这个问题,所以今天大家买电脑的话。
什么四核八核16核已经是它的常态了,那么当我们有这么多的核之后,实际上呢我们必然呢就要想办法,就是能够把这些和的算力全部利用起来,那这里面在传统的操作系统里面。
诶我们大家很熟悉的两个概念就是a进程和线程对吧,大家还记得进城和县城有什么区别对吧,我当时上操作系统课的时候,老教授这第一个问我的问题就是诶这个是什么问题对吧。
哦我们就说哦进程就是它有独立的存储单元对吧,它的存储是完全独立的,然后系统去管理的,而县城的话呢是在竞争之内的,然后呢它实际上是这个共享了这个存储的memory,所以线程写不好,互相内存会打架的对吧。
但是县城要比近程强了很多,但这个是windows下的,如果在linux下的话,a进程也可以做的很清亮等等,布拉布拉对吧,但这个确实是个很基本的概念,就是我自己的理解,就是说其实县城的话之间的话。
他会share很多这个memory的,这个memory就是我们数据交换通道吧,精神之间的话,实际上你要通过特殊的机制才能彼此去交换信息,所以这个是我们所有的并行化编程的一个foundation基础。
那么这个时候呢实际上我们就要去做multi tasking,就是说我既然有那么多盒,我就让他同时处理很多很多事情嘛,这样大家去回想一下,在前面这些课程中,我们讲的游戏引擎的话,大家有没有发现。
其实游戏引擎要做的事情其实特别的多对吧,我要做动画,我要做物理,我要做逻辑对吧,我还要做一些rendering的表现,所以其实这个时候我就是multi tasking的,那你如果有四个核,八核16核。
我非常的开心,我就用起来,那这么多tasking他在一起怎么去管理呢,这里面就讲到了两个很经典的模型,一个模型的就是preemptive multist tasking,就是抢占式的多任务诶。
这个就是操作系统的,哎呀这个我我真的不知不觉地进入了大学操作系统课的这个模式了,同志们,原谅我一下,我接下来要给大家发linux源代码了,我觉得那个时候我们还要去一边学操作系统。
一边要读一段linux源代码,给老师写个报告,说证明我真的读懂了,我看得懂这个是怎么回事,好那这个preemptive multitasking的意思就是说抢占式的,就是说当这个县城在跑的时候,诶。
或者这个任务在跑的时候,调度者如果认为你该出去了,他就把你调出去了,过一会儿再把你调回来,这个东西是调度者自己决定的,比如说我有一个high priority的事情来了对吧,我就要打断你。
我调个中段哥们儿,你出去,然后我再进来,那这个过程的话呢,这个听上去很霸道,但是大家仔细回想一下我们的操作系统,比如说windows操作系统它是什么性质,它就是抢占式的,因为你我不可能让你一个进程。
把我的这个整个所有的系统资源全吃光了,那我操作系统本身怎么跑呢,对不对,所以这也是一个比较常见的一种方式,那么另外一种呢就是那个nperimati,就是说非常难吃的marketca,他的意思就是什么呢。
哎你居然说你干活,我不会主动打断你,除非你自己说你自己结束了对吧,那这样的东西呢,其实它的好处是什么呢,就是你对它的运行值,如果所有的任务都是你给的话,其实你的控制能力是很强的对吧。
但是它的缺点是什么呢,就是万一你写坏了对吧,你一直把我的东西站在这,那我们其他所有人都被迫要去等着你,那这种东西其实在操作系统中实际上是很少用的,但是呢其实在有一些这个非常我们就是real time。
那个要求非常高的操作系统里面,也会采用这种非抢占式的这种多任务模型,那这两个东西呢,实际上对于我们整个就是说这个做我们的多线程系统来讲的话,这是两个两大基础对吧好,那既然有这样的一个东西。
我们就意识到一件事,就是说诶我们可以启动很多的线程对吧,那么县城和县城之间,他如果在不断的被打断,在多个任务之间来回切换,比如说我启动了20个thread,但是我只有四个核。
那我就意味着我要在这threat之间来回的去切换,对不对,那好那这个切换的时候你会发现这个系统它其实很不cheap,就非常的不廉价,为什么首先一个县城被打断的话,它是掉了一个中段。
那这个中段呢至少要2000多个cpu的cycle对吧,想想最快的a l u就是最快的指令,只要一个cycle对吧,但是呢大部分是不会不止一个一个circle。
但是2000多个circle实际上是很慢的诶,这个时候你如果因为你要去把占空间切换出去对吧,你要把计算机切换出去,那这个时候你会发现你掉进来的县城的时候,他这个数据并不在它的各级缓存里面。
它的要去从内存里面再去调这些数据的时候,那这个时候他的时间可能会从1万甚至到100万的这个circle之间,去这个这个delay,所以大家这里面建立一个概念是什么。
就是说县城之间的切换实际上是很贵很贵的,这也是后面我们讲为什么job system它的设计就是fiber base,就是那个job system,它是有道理的。
就是他意识到这件事情其实是非常的expensive,今天讲的东西稍微有点硬核啊,我会被迫,然后讲很多硬件是怎么操作的,操作系统怎么操作的对吧,大家甚至要理解各自的cash,但没关系,大家这个先姑且一听。
然后呢以后大家一边学一边自己写代码,一边就能感觉到,所以这其实是这个paralpmi我们经常会遇到的这样一个问题好,那其实呢在我们在写这种病性化编程的时候呢,我们一般来讲就是有两种不同的。
就是多线并行化的这个这个开发的问题,第一类问题呢实际上是非常简单,就是说ok你这些现车我不打断你对吧,你该做你做就做完,我不会把让你停下来。
那这样的好处就是它不会有这个threat interaction或thread,thread swapping的这样的问题,那这种问题呢它就是一堆独立的问题,它各自算完,然后大家一起收口这个模型的话呢。
其实最简单的一个案例就是蒙特卡罗积分对吧,我就是每一个负责撒1000个采样点对吧,然后我采一个100万个采样点,哎我这个事情就做完了,对不对,这挺好的,这个就他们这样的话呢。
其实不会遇到那个就是thread swapping的这个瓶颈,但是呢其实在真实的游戏的这个案例里面的话,我们没有办法把一个游戏所做的模拟分得那么清楚。
那我们彼此之间各个就是surprise之间有很多数据的这个依赖,还有什么呢,还有一种依赖关系叫dependency,就是说诶我得等你这个事情做完之后,我才能启动我的事情。
所以这个时候就会导致这个program parallel program变得非常的复杂,这个大家如果有一点点的经历写过多,就是那个就是说并形化编程的时候,这是大家一定会遇到的问题,那这个问题怎么解决呢。
其实呢有在这里面的话呢,首先你要理解就是这里面会产生的问题是什么,其实最容易出的问题就是dracing,就是什么意思,就是说同一个数据,当一个县城在读它的时候,另外一个线程正在改变它。
而且你如果这个逻辑判断是我读完,根据它的一个值,然后我再去做一个做一个操作的时候,很可能你读的时候是一个值,当你在对它进行操作的时候,那个值已经被修改了,另外一个值了,那么这两个东西是不是就有问题了。
就像大家有没有试过,就是比如说三四个小伙伴一起在墙上,就是大家一轮流的,其实他们写的东西对吧,其实你看到的和你打墙写的东西是完全不一样的,这个data ring的话呢。
实际上是围绕着所有的b计划编程的一个核心问题,产生的这种死锁,产生所有本都都是这件事情为核心的,那么这个时候呢其实最简单的做法是什么呢,哎我们做一个叫这个这个blocking的这个方法。
就是说其实这里面有一个词叫critical section对吧,就是以前我们在学多边那个就是学操作系统的时候,一开始老师跟你讲什么叫critical section,然后我就想哎呀。
这是个这个critical section,就是很重要的很艰难的部分对吧,critical这个词英文直接翻译过来就是这个这个这种感觉嘛,那其实呢它本质上讲的就是这一段代码对吧,我进去了,直到我出来。
你们不要捣乱对吧,你必须要让我完整的执行完,其他人都不许去执行这同一个点,就是在别的线程里面,这段代码不能够被同时重复执行,这样他就能保证当所有的人,比如说我们有计数器,比如说每个人都会啊注册学生对吧。
然后我们现在有个count叫学生的account,然后呢学生我要塞入到一个数据库的表里面,那这个这个学生呢抗的增加的时候对吧,如果我的数据库是个单线程的话好,那我们每个人去往数据里面塞东西呀。
我就通过一个mutex把它全部控制住,那其实这个critical section的话呢,在这个现代的编程语言里面有很多种实现方法了,比如说有这个真的显示的定义叫critical section。
也可以通过mutx对吧,还可以通过什么cm four这些东西,就是那个都能够去解决,叫信号量我都能去解决它,但是呢这样的一个东西的话呢,它实际上是一种阻断式的编程,诶,不好意思啊。
我怎么觉得我现在在讲这个哎真的在感觉在像讲操作系统啊,大家原谅我,我我好像回到了大学这个讲操作系统课的这个模式,而且我觉得我讲的很不好,因为一般来讲操作系统这一节讲到这个的时候。
老师会差不多一节课的时间给你讲这些东西,而我大概只有几分钟的时间给大家点一些这些概念,那我今天把重点还是放在后面,那么其实呢就是说这样的一种阻断式的这种变形化编程呢,它会产生一个比较严重的问题。
第一个呢会产生死锁对吧,大家想想看,就是说诶我再写一个变量,我在等个变量,其实很容易产生思索,另外一个问题是什么呢,这个问题其实是蛮头疼的,就是说呃我们假设并行化变成我同时启动了。
比如说几十个甚至上百个任务,其实在真正大型系统的时候,你并不能保证这些task他一定会成功,有些task甚至写的会自己死掉,那么这个时候如果有一个task failure的,甚至crash了。
它会导致整个系统的思索,这件事情是很难去,就是如果写代码的时候不小心的时候,很多时候就会出现这种很严重的问题,实际上当有一些更高优先级的任务进去之后,按照目前的这个调度的算法,它其实是不会被打断的。
所以说你的高优先级的东西很难去去去打断它,所以这也是因为你一旦进入critical section,它实际上是被彻底保护住的,所以你在这个lock的这种方法的话呢,有的时候你就会出现。
就是说你很难去动它,所以这是一种很古典的一个架构,那但是当我去架构一个很大型的系统的时候,其实lock就有个简单的做法,叫你lock尽量不用要用,它是越少越好。
待会我们讲的那个job system时候会讲的就是这个东西我会用,但是会用的非常非常的少,ok好,那接下来的话呢其实有一种叫lock free of编程了,他其实想法很简单,就是说既然你有些亮。
他很敏感,我也怕大家所有人去改变难度怎么办,我把它变成叫原子性操作,原子性操作其实很神奇啊,就是说实际上它是一个在硬件和底层支持支持的一个东西,其实我刚才也没搞清楚,就是cpu到底怎么实现这件事。
但是他确实是在硬件底层去实现了这样一件事,他能保证就是说你对这个变量的读写操作实际上是这个非常的,这个就是说不会被多个任务同时侵占,那这里面有两个概念,一个就是说诶我对数据从那个内存中读出来。
我去看这个数据多少对吧,漏漏到我一个,比如说跟我read安全的这样的一个存储空间,我去看他一眼就漏了他诶,或者说呢我要写进去,就是往那个就是那个那个计算机里面或者那个memory去写。
它就load和save,实际上是原子性操作的一个很核心的概念,其实说实话我最开始的理解的时候,lodc store,load store这个数据对我来讲还是有点抽象的,有的时候。
那么其实呢这里面有很多的概念,但听着都很高大上,比如说arm w对吧,就是你read modify and r就是a你去读它的数据,你去修改它的数据对吧,你还可以去写进去这个数据。
但其实有的时候我们要做cs对吧,就check and这个这个这个就是store,就check,我还还去啊set去改变它,所以这里面有一系列的原子形的指令。
但是它具体核心的想法就是说你不需要把一整段代码给它锁住,因为这段代码里面如果有崩溃啊,有什么样的问题,你控制不住,但是我只是在一个绝对保证能够执行下去的这个一个一个指令里面,去保证对这一个内存的数值。
比如说是integer也好,是个布尔也好,我对它的操作是具有原子性的,一般来讲是个integer了,所以其实呢这个是我们那个在做这个就是说啊多并行化编程的时候,用的另外一种方法。
而是用原子性操作来去实现它,那么这里面其实啊就是当我们去真的去实现一个闭性化操作的时候,你一般看到的如果大家知道有很多这种抓抓抓你的那个硬件啊,抓你的那个系统providing的这种软件。
你会看到一些很很就是如果你有很多个盒的话,你会看到一张图,这张图会让你很有压力,比如像我们这种强迫症的话,我比如说我看见我们的引擎跑起来之后,我发现哇我上面有很多的空洞,说明什么呢。
说明那段时间我的cpu是在那idol的,他在等着其他的任务在启动,但实际上大家如果在写多线程编程的时候,大概率你会写成这样,所以lock free编程的话,它的好处就是说它能够尽可能的避免死锁对吧。
通过这些人进行操作,但是呢因为这些原子变量之间的彼此等啊或者依赖啊,诶他彼此之间会产生很多的空洞,那么另外一种方编程的,但这个是理论上的叫wait free,就是等待几乎为零的这种编程,这种编程的话呢。
它事实上是有一套数学的方法,然后呢能保证我在多线程的研发中,我尽可能把所有的cpu资源全部占掉,但是wait free这个编程实际上挺复杂的,说实话我自己都没有完全搞清楚。
那么我我就是实际上现在大家能够看到的就是说,如果给你一个非常复杂的系统,你能保证这个系统彼此之间它对cpu的利用率是百分之百,这件事情几乎是不太可能的,到目前为止我没看到一个理论的架构。
但是呢你对一个具体的,比如说对一个对战对吧,对一个q对一个队列,你把它对他的操作,你把它变成wait free,这件事情现在在行业里面已经有些解法了。
那么如果你对一个性能非常critical的这样的一些应用,比如说我们写那种高频的这种这种通讯协议对吧,那你可能就是要用这种解释这种结构的编程,但这种编程的话呢,它是要需要严格的数学和推演证明。
就是他不会产生我们前面讲的各种各样的异常对吧,那所以的话呢就是我觉得对于我们做做引擎的时候,很多时候大家能掌握到lock free这一层就可以了,因为接下来大家看到这些gap啊。
在job system里面我们能够相对好的去解决这个问题,那么其实呢在这个多线程里面的问题比大家想象的更复杂一点是什么,就是说我们在写一段代码的时候,我们天然的认为这个代码会顺序的执行下来对吧。
12345,lone nine two,比如说我们小时候学的这个语言叫什么呢,叫解释执行语言,我不知道有多少同学真的还写过解释,比如现在的很多脚本语言,有些脚本语言还是解释执行。
那么你就可以看到非常清楚的,他一行一行一行一行的这个执行下来,我觉得最早的那个basic语言就是解释性语言,但是当我们去写一些高级语言,比如像c语言或者这些语言的时候,交给现代的编译器。
其实编译器编译完之后,你完全不知道他真实的用汇编是怎么去实现的,就是你定义了一个操作,一个在前,一个在后对吧,或者说你这个变量在用它,你你会认为这个变量我先设成了一个一个值,比如说零。
然后呢我对这个零进行加一,我后面对它重新设置支持成三,然后呢对三进行简易操作,我还是用a b这两个变量,但实际上编译器很可能会把你优化成两段无序的代码,为什么呢,因为它只要保证你顺序执行下来。
它的结果是一致的,他就认为这个是一个有效的变异,但它但这种情况的话,可能在多线程的情况下就会出很大的问题,这里面给大家举个简单的例子,比如说我们写两个线程,第一个我们一开始呢把a这个变量初始化零对吧。
b这个变量初始化一,那我们在进程一里面跑的这个函数是什么呢,a我把a等于b加一,那b一开始是什么,b是一对吧,那a就是什么呢,a就等于二了,对不对,然后呢这个时候呢b是我们把它设成零。
然后呢我后面做一个循环,我我的我的第二个新县城,我做个什么事呢,就是我现在循环就是当b不等于零的时候,那就意味着必备清零这件事情没有发生吗,那我就持续的循环,直到我b不等于零了,好,b等于零了,好。
b等于零之后,我就assert,我觉得验证我说a等于二这个东西,如果大家去理解一下,假设我们的代码真的按我们想的这个顺序去执行的时候,那我们看到的应该是这样一个结果对吧。
你会认为就是当你看到b等于零的时候,a一定是等于二的,对不对,但实际上编译器在编译这个左边的这个方程一的时候,他会认为那个上面那个a和下面那个b是两个无关的变量,他最后出来的b是等于零的。
a呢也是等于二的,但是呢它有可能b设为零,会在就是它用一个临时计算器这类次性把这个值设成零了,过一会儿它反应它的输给b,但是a呢会抢先的用那个前面那个b的值的话,另外一个b的值的话设成二了。
所以当你去这边去,假设说你看到b等于零的时候,a已经完成了等于二的这个操作的话,实际上这个假设是不成立的,而这种编译器的乱序的话,实际上是在我们写这个就是叫b型化开发的时候,是特别特别容易出问题的地方。
就是给大家讲一个非常恐怖的一件事情,就是说我们看到各种各样的这个这个这个就是那个意见,比如说啊从我们的pc x86 架构对吧,到这个power pc,然后呢再到这个就是我们的arm的架构。
实际上很多cpu它其实并不能保证,你一定按照你的这个代码的顺序依次执行下来,而且特别像arm架构,它是非常追求功耗和性能的,所以它要追求就是尽量的简单,所以它实际上当一段。
比如说你写了一个100行的函数对吧,编译器分析完之后,把你的函数框框切成大几段,然后这里的变量一通乱替代对吧,它最终保证结果是一样的,然后呢最后这个结果就出来了。
但是呢当我们人天然的去理解你这个函数执行顺序的时候,你会认为前面第一个section做完之后,我就会做第二个section对吧,那么第二个section是基于第一个39,这没有问题。
但是你在另外一个县城里面,假设这件事情的发生也是section one,section b,section two的话啊,second three的话,那你就完蛋了。
所以同学们如果在做这个移动端开发的时候,会遇到这样一种情况,就是说诶我有时候在pc的模拟器上,我的这个代码跑起来完全没有问题,为什么我的真机上就突然down的死去活来,其实就是这个原因,而且这一点的话。
其实在新一代的c加加的语法中,好像我没记错,好像是c加11吧,其实你可以显示的要求说编译器要确保这些东西的执行顺序是一致的,但是呢你所付出的代价是什么呢,实际上是它的执行性能会变得更差。
那为什么编译器它会把你乱序呢,因为实际上大家如果对现在cpu的原理,这个我后面也会提到,就是现在cp的原理是这样的,就是说我实际上要从内存中反复地读出你的数据和指令。
那么有的时候啊他这个cpu速度是很快的,就是我会就是他总是会饥饿的等着数据,所以呢现在cpu的话,他有的时候它不会等着你的指令一条条执行,根据你的指令在决定,从那时候读数据。
他恨不得把一个就是一大段函数,一整块的,就是切成很多块,一股脑扔进去,就是你的指令和数据同时在里面运行,这样它的结果才会快,所以现代编译器基本上也是按照这个原理去优化的。
但是呢它所带来的问题就是说在单线程的情况下,你不用去,就是这就是为什么大家去玩那个在做那个开程序开发的时候,我们有一个版本叫release版对吧,我们还有什么debug版。
大家会发现debug版有个特点是说你的函数执行的时候,基本上你可以认为它的顺序是对的,但是你一旦到release版的时候,诶,其实它的最后编译出来的汇编和他的执行逻辑,可能跟你想的完全不一样。
而且呢我反正我们自己这个有很多很惨痛的,这个我个人有很多很惨痛的经验,就是说如果你去写一个多线程的这样的一个代码的时候,在debug版诶,你发现没有什么问题对吧,逻辑都很成立,一堆246写得很开心。
但是你一旦写成这个release版,变成release版的时候,经常会莫名其妙的就挂掉了这个,而且这种bug是非常难查的,因为你根本不知道编译器的优化是什么样的。
所以其实当我们在进行这种就parallel programing的时候,实际上这也是一个大家必须要建立起来的一个基本常识,就是说其实一段函数在这个cpu中执行,它是乱序的。
那你就不能假设这些变量的值符合你writing下来的那个顺序,那么知道了这些基础的常识,那接下来就回到我们课程的这个正题了,就是那在游戏引擎中,我们怎么去做这个parallel programing呢。
对吧,我们要怎么做framework,那这里面的话呢其实最简单的做法,这也是大部分现代游戏引擎的一个做法,就是很简单,我们就把这个每一项任务分到一个thread rendering。
果然rendering的事对吧,simulation,simulation,比如像物理啊这些东西我都放在这,然后呢哎我逻辑一个县城一人一个县城,大家井水不犯河水。
那么彼此之间的通讯呢也是在就是这个这个每个fm开始的时候,我们交换一下数据,其实大家如果看一些比较古典的这个游戏引擎的话,很多都是这样一个架构,这个架构其实挺好使的,基本上你在比如说2~4个盒吧。
或者说这种情况下,这个架构其实真的很好,而且你可以摘得非常的清楚,但是这样的一个架构它会有什么问题呢,就是说你其实很难保证,就是说诶我这四个thread大家的就是在workload是一样的。
有的有的thread a workload很轻,很快就结束了,但是没办法,我只能等对吧,有些thread呢他work load很大,你得等着他,所以说这是个木桶效应。
就是所有人都等着那个最慢的那个worrid完成他的任务,我这我这一天才能结束,那同学们会说那很简单啊,如果是这样的话,我们要不要把这个这个这个比较重的workride把它拆掉对吧。
把它给那些很轻的workri,这件事情呢其实非常的复杂,很难,为什么呢,两件事情,第一个就是说这里面牵扯到了数据的访问的这个locality,就是我尽可能一个thread访问的数据是在一块地方。
比如说像rendering,我们希望rendering的数据就是集中在一起的,你不能说诶我这个对rendering的这个基础数据的访问。
记得render read里面我看到simulation thread有空,我就把它再放到session read里面,那完蛋了,那这个这个数据的访问有时候就会出现data ring的这个问题。
就是因为你这些数据全在一个这个threat里面去读写的话,它是安全的,但是你多个threat同时对这一块数据进行读写的话,刚才前面讲了那么多东西,大家知道这是很麻烦对吧。
所以说你很多时候这个数据你没办法去分,第二点是什么呢,就是说ok其实对于一个游戏来讲啊,对于不同的场景,其实是每个thread它的负载是完全不一样的,比如说就以这个游戏为例。
你有的场景他是渲染特别在某些环境下渲染特别慢,诶到另外一个环境下的话呢,哎因为游戏里的角色和敌人特别多,我的逻辑我的什么东西忙不过来对吧,而这个东西它全是在一个游戏里面。
你不能说根据这个我在玩到游戏的不同阶段,我把这些threat的分工做一些动态的调整,那这个的话会导致更多的bug,所以说对于这种固定的这种多线程的这种架构的话呢,它其实就有这样的一个问题。
这也是为什么就是说你如果大家去看固定作用这架构的这个providing,看起来它总是会有大概我的感觉,至少有1/3左右的计算资源是会浪费掉的吧,但这个数据不一定准,因为这只是个经验数据。
那么另外一种思路是什么样的,我们叫做 join,什么意思,就是说我明白就是我要分这么多的这个thread,动态的调整很很难,但是呢诶我发现一件事情,就是在游戏计算中,有些东西它的一致性非常高。
但是呢它计算量很大,比如说动画对吧,大家大家在前面那个动画课里面学到动画的结算,大家会知道那个动画其实是非常复杂的这种重复的运算,它一下子有很多的一些平衡,方程的这个这个那个那个就是那个线性代数啊。
这些方程的这些这个矩阵啊,这些运算,所以的话这些运算全部可以把它分开来,那这个时候呢其实就有一个pattern叫做 join,就是说我到了一定时候,我的那个固定好的线程,扔到哪。
扔到我们的叫works read,我们预先会申请好一些工作的thread,做完之后呢,这些thread诶我又把它收回来,我就收起来,然后就放到我的结果里面去,所以这个架构呢其实大家如果看现在的。
比如说像很多游戏就基于unreal,基于unity这个引擎的话,他基本上做的就是这个focking join的这个方法,这个方法的好处就是说诶我能够相对充分的利用你的多核算力资源。
你这个时候是四个核也好,你是八个核,你是六核,我都能相当的把你利用到,但是呢就是说他一定还是有很多的这个大家从这个图上就能看出来,有很多地方还是空的,对吧对了,我刚才忘了讲了。
就是固定的那个thread的那个方法呢,他一个最大的问题是,比如说你一上来分了四个thread对吧,你这时候如果遇到了一个双核的电脑怎么办,其实你就很尴尬了,对不对。
那你只能是把这些thread两个thread在一个和尚来回的竞争,当然讲了thread来回切换怎么样会很慢,对不对,那还有一种情况呢,这是一个幸福的烦恼,就是玩家配了一个八核16核的电脑。
你这个时候不能够游戏引擎,根据这种情况再去把这个虽然分布在动态的去改制是不可能的嘛,因为你很难去第八个这个事情,所以呢那你就眼睁睁地看着那么多的计算资源放在那儿,你用不了,所以大家可以看到有很多游戏啊。
虽然说你配了一个很高配的电脑,配了很多的盒,但是它就是你感觉游戏已经卡住了,但是呢你很多计算机cpu资源是没有被占用住的,其实就是这个原因,那么像这个 join的这个模型呢,它会能缓解这个问题。
因为他的workread的话的数量,其实很多时候跟你操作系统说你有多少盒给我,那我就把它开在这儿,所以就相当于说你有这些工人,我不用白不用,我就把你用起来了,所以这个方法的话呢。
它实际上比它的适应性要比这个固定thread要稍微好那么一点点,但是呢它所带来的问题,就像我们讲的,就是说它实际上还是有很多的工作,他非常难以去balance,他还是在中间产生了很多的gap好。
其实呢这个架构呢实际上是一个很经典的架构,去比如意大家去看虚幻的话,它实际上就有两种thread,一种叫name read,他就很明确的告诉你说这是for游戏逻辑的,这是for rendering对吧。
大家如果在循环,你们可以看得很清楚诶,他还有很多的这个那个working threat,它就是比如说把物理啊,我可以扔到这些threat上,把它整个去展开,就是大家如果看一个虚幻做的游戏的话。
你用那个profi攻击抓抓出来的图大概就是这个样子的,所以这个forehand join的话呢是一个比较呃,怎么说呢,比较清晰,也比较鲁棒的一个架构,实际上如果大家对多线程的要求没有那么高的话。
这也是一个非常好的一个架构好,那么实际上的话呢在多线程中的话呢,第三种方法呢就比这个要复杂一点了,join,它其实是一个数展开来再收回来,对不对,但实际上还有一种更复杂的就是叫那个task graph。
就是以前在有一个很著名的s d k t v b对吧,就是我可以创建很多的任务,我把这些任务呢设置好他们的dependency,然后呢我一下子扔给这些核去处理。
然后呢这些盒可以自动的根据我这些任务之间的dependency,去决定哪个先执行,哪个后执行,而且呢哪个能够并行执行这个东西,其实大家想想看,如果今天我们是在做一个多线程的这样一个系统的时候。
这是一个很自然我们认为就应该发生的事情,确实是这样,但是的话呢对于游戏这个scenario来讲,这个任务这点东西是非常重要的,为什么呢,因为游戏它所创建的大量的计算任务是有dependency的。
所以的话呢你确实你要用多核方法去运行它的时候,你又不想把这些任务写死在,比如说哎task 2,我就把你扔到the read,他三我就把你扔到这个animation thread。
实际上的话呢你必须要构建这个dependency gram,所以现在很多引擎你们用的也是这样一个技术,那这个task graph它怎么做呢,其实比大家想象的要简单得多,很淳朴,你看他就在你代码里面。
这个当然现在你可以用普通方法来很多,说什么代码的方法,就是我一个一个的link去构建,比如说诶我先表示说我task一的dependency是这个下一个是task对吧,我task一的下一个是task 3。
然后呢一个标记下来,最后呢你把这些dependency这些依赖关系其实全部做好之后,它就形成了下面的这一张图,然后呢这个系统拿到这个图之后,它就可以执行下去了,其实非常简单。
但是呢这个task graph它一个问题是什么呢,就是说其实他并没有这个就是说它的这个task这个树的构建,对于很多的技术来讲,第一个它是不透明的。
第二个的话呢它很多说假设这个task是执行了完一之后我才能执行二,但实际上在我们真实的游戏引擎里面发生的事情是什么呢,就是经常我一个任务执行到一半,我突然发现哎我得还得做很多很多的事情。
我才能把这个任务做完,大家想想看,比如说我要做一个角色的动画对吧,做到一半我突然意识到说我得先做完他的一些,比如说物理,我甚至会fork出一些就是它的这个伤害检测等等这些东西。
然后这些任务完成之后才能回到我的身上,所以大家如果看一个真实的游戏引擎里面这个这些任务的话,他们彼此是在运行中不断的创新,不断地设置越来越复杂的这种dependency。
而task graph这样一个静态的方式,它有两个很大的问题,第一个就是说它在动态不断的增加这些节点的时候,实际上是非常的复杂,第二件事情的话呢,就是说它早期的版本里面并没有实现一个就是a我做到一半。
我要等其他的事情做完之后,我才能继续走的这个东西,这个东西必须要靠后面的系统才能解决,所以呢经讲了这么多了,我们差不多讲了半个小时,我们才能回到我们今天的第一个主角就是job对吧。
这个是现代游戏引擎里面大家用的比较这个弹的比较多,而且呢也有引擎在实践的这样的一个东西,那首先讲这个job system之前,我们先讲一个概念叫携程core routine。
哎呀这个词听上去还是很高大上嘛对吧,这个携程呢实际上是一个什么呢,非常清亮的一个就是啊,这个这个叫什么叫叫做多线程的一个执行的一种上下文的一种方式,诶这个听上去这个听上去很不像讲人话了。
简单来讲就是说我一个函数执行到一半,我可以调一条指令叫做医药的,医药的意思就是英文就是让道的意思,就是说诶我是应该一半,我要等别的事情发生,我把我的执行权让过去,然后过一会儿的话。
诶别人再把我再去invoke,把我激活对吧,哎我又激活了,我又可以继续按照刚才那个东西去执行下去,大家想想这件事情是不是很有意思对吧,那如果我在c语言里面,我们写一个函数对吧,那我执行到一半。
我突然发现这个打个比方吧,比如说我现在是这个讲一个比较大好理解的例子啊,假设我是一个学生,我要去学校报名对吧,我这个我去准备报名的这个事情做到一半的时候,我突然发现我缺了材料,那怎么办。
我得去先把我这事让一下,我要去先找打印机去打印,我玩我所有的报名材料,我才能走下面的流程,那打印机打印不是说你要打当场就能打,对不对,那打印机他自己你要去要的出去让打印机打完。
直到打印机打完我的材料之后,我才能回来,继续进行我下面的操作,所以携程它的核心就是这个原理,就是你虽然在做一个任务,但这个任务中间的话我能让道出去,过一会儿别人回来的时候,我还能能回来。
那么这件事情的话呢,其实在现代的很多的高级语言里面,是特别是并行化编程里面,比如说像那个应该go语言本身原生态就支持了,对不对,比如说c sharp里面这些概念就用的特别多。
因为当我有这样的一个结构的时候,我去写一个变形化的编程的东西,就会非常的方便,但是非常的遗憾,就是说在c加加里面,携程这件事情就很蛋疼,我待会我解释为什么这个事情很头疼好。
那么其实呢携程相对于县城来讲的话呢,有一个很大的区别,就是说县城呢是一个调硬件的中断对吧,那整个硬件的这个中段,他的那个站啊,他的所有的这个环境啊,context全部会重置一遍。
所以它它的成本其实非常高的,大家如果对这个性能有所了解,你们就知道在线程之间切换的时候,其实操作系统成本是非常非常高,但是我最后我也长头跟大家讲,而携程呢其实它一般是由程序自己来定义的。
而且呢它一般在一个read里面,我可以把很多个携程之间来回切换,就是从从cpu来看的话,这还是一个sr没变过,从来没掉过终端,但其实是我们自己的程序逻辑,让它之间可以换来换去。
所以呢实际上这里面你根本没有激活kernel的switch,因为线程切换为什么那么贵,你要不断的去调一个中断,是整个kono级的东西,不就操作系统都知道这件事了,你想这事就麻烦了对吧。
就像你平时给你的小伙伴换个座位对吧,你们说作业我们俩互相换一下,你们自己商量一下,这事就完了,但是如果这件事被你教导主任发现了,你觉得这事儿就很麻烦了。
所以携程和线程的区别就在于就是说县城它是这个公事公办,那个就是整个系学校系统全部报备,而携程是什么呢,就相当于我们在在读书的时候,我跟我的同桌商量一下,我们俩把座位自己换换对吧。
这个事情诶叫我老班主任是发现不了的,也不需要向学校报备,所以而且呢我们可以今天画到这,明天放到哪,随便我们根据我们的需求,但这样的话实际上就会让整个的overhead下降非常多。
那么县城呢它其实比有两大的这个实现方啊,那个quot就是那个携程,它有哎呀,我觉得可挺好一点吧,老师现在现场我会叫叫错的,就是chroutine,实际上有两种类型,一种是什么呢,叫stuck for。
就是有状态的,我有站的,大家想想看一个函数执行到一半,诶,这里面函数里面有什么,有很多local web,对不对,还有很多计算机状态,比如说我现在这里面定义了一个本地的变量,比如叫a a5 。
哎算算到现在刚刚算到了100对吧,接下来就a加一了,这个时候你跟我说掉了个页的行啊,没问题,过一会儿我回来的时候,我这个a的这个所有的这些变量是不是要全部智慧了,但是大家想想看。
就是说其实在这个就是计算机语言里面,你这个东西你函数跳跳出去了没问题,但是那个时候又有其他的函数来了,那个占空间别人就要用掉了,那你这时候站怎么去恢复这件事情其实非常的难,但是呢如果你从编程的角度来讲。
就是程序员写代码的时候,你会天然的认为我要的后面我从医药的地方再回来的时候,实际上我的所有的local variable,我所有的计算器全部都应该不会被污染,否则这个代码就非常的一个逻辑。
写下来就非常的难,就像上次我跟大家讲的就是rpc的道理一样,就是为什么会有rpc这个东西,就是说当你真的去写这个系统的时候,你会天然就是我们人的大脑对一个事情业务逻辑的理解,天然的就是会一次记录下来。
举个例子,还是以刚才那个学生报名的这个例子啊,就是说你可能前面先去要来了,你的学号对吧,你要来的你的班级号,ok这个时候你打算打印你的申请表格的时候,这个时候你要的了,对不对,好这个表格打印回来之后。
这个这个表格纸打印回来,开始你要去填写了诶,你其实天然的assume,就是说我知道我的学号和我的班级号,但是记住在你完成这个报名这个任务里面,这个学号和班级号是你本本地的一个web对吧。
你的大脑实际上把这个东西记录下来,等你回来,等那张纸拿到的时候,你的大脑a resume,你整个这个corrine,然后呢,他就开始继续往下走下去了,所以其实这个stuck for这件事情非常的关键。
就是说我能不能把这个函数执行的所有的context恢复得了,那么相对于stuck for的话,另外一种的话就是stocklist,the cooking,这就是无障无状态的这样的一个口如瓶对吧。
那的意思就是我回来了,我不管了对吧,我就认为你前面所有llocal viable对吧,你的所有的计算机全部被清掉了,如果是这么做的话呢,其实这个携程就非常好写,给大家举个例子,比如在c加加里面。
c语言里面,你想实现这样的一个无状态的qing怎么写对吧,其实很简单,写一个go to直接从函数里面跳出去了,然后你有本事再跳回去,你整个状态就全错掉了,对不对,但这个事情就是我建议大家轻易不做这个。
因为其实他有很多很多的问题,但是呢stuck is cooking它的好处是什么呢,就是说它的实施起来相对来讲比较简单,但是的话呢他对这种quot的开发者来讲要求非常高,而且他一旦写不好。
它就会产生大量的bug,其实我在这里给大家顺便讲一下,就是游戏引擎开发的一个核心的一个挑战是什么,不是说我的技术越高端越好,实际上一个游戏引擎它只是一个基座,当我们去做一个不同的游戏产品的时候。
上面做应用的时候,很多人会基于你的接口进行开发,那么如果你对这个开发者要求特别高,比如他要非常能清晰的理解这种并行编程的各种坑的话,那基本上这个引擎是没人能用的,因为大家知道这个挑战太大了,对不对。
所以游戏引擎有个很重要的任务,就是把这个困难留给自己对吧,把最难的东西留给自己,然后呢对于开发者来讲,你能写这个简单的代码,能解决的问题,尽量用简单的代码,你能用脚本写完的是最好,脚本甚至不用写。
你能连几张图,连几个图片啊,连几个graff就能完成的任务,那是更好,所以说其实的话呢就是像stylist的quotine的话,除非在很底层,我们自己用,否则的话我们会尽量的不用。
但是呢就是它并不是一无是处的,因为对于这种无状态的苦入性,它一个很大的好处是什么,你不需要再去保存和恢复整个站啊和local register的这样的一个状态,所以说如果这些非常高频的代码。
然后呢它又是底层,就是只有少数几个很核心的同学去写的话,那其实你用stuck city也是一种方法,但是只是说写这个代码的同学水平要求非常高,这个地方讲起来就有点硬核了,大家仔细体会一下好。
那实际上哎我就我已经把两个比较已经写完了,讲完了,实际上确实就是这两个东西无所谓高下,但是呢就当我们作为一个引擎,我想做一个高度并行化的一个引擎,让所有的程序员。
就是游戏开发程序员也能享受这种并行化的乐趣的话,那我们会非常强烈的推荐,就是是就使用这种有状态的quoting,stuck,foot,quoting name stucklist quotine的话呢。
我们会建议就是说用在非常底层,只有少数人控制的,而且是非常native code,甚至你惹急了,你写汇编也没有问题对吧,但是呢cine有一个很大的一个难点是什么,就是说其实在这个就是说不同的操作系统下。
就是语言,就是native语言,比如像c c加加汇编这些底层语言,特别是c和c加加,它其实并不支持chrot这个机制,就比如说我们在这个windows下对吧,我们想实现quoting,这里面有一些方法。
比如说用boost boost有一套底层机制,它实际上是能帮你实现cking这种机制的,但是呢如果你仔细读它的代码,你发现它其实里面调用的是汇编是吧,就我调用了中段,我还得啊也没有调用中断。
但是我要把那个这个这个register寄存器啊,我要把战战堆栈全部给它导出来,那个占的大小你还得设置,就是说我要一上来告诉作系统,说诶我这个这个系统,我这我这个程序每一个函数它的占空间多大对吧。
有的占空间你设置小了,你的函数写着写着就占空间炸掉了,大家如果写过递归的代码的时候,清查或者有些函数这个局部变量又申请了太大的时候,你们会遇到一个问题叫占空间爆炸。
但是大家如果只是写一些什么python呢,或者h2 f语言,你可能很少遇到,但是如果大家写c加的同学会遇到这种情况好,那占空间如果设置的小了怎么办呢,对小了,首先我的性能。
比如说我的我的stock for quot出去的时候,那肯定性能会高嘛,因为我把这些这一段占空间对吧,把它拷贝出去保存起来,在内存中保存起来,在最后再恢复肯定是快的,但是呢诶哦如果是大了的话。
如果是大了的话,就是那你每一次的quotine yeld和他的这个resume,实际上它的成本就会高,所以你会发现很有意思,就是当你去写这样的一段系统的时候,其实你就要对对操作系统的调度的底层。
在内存中怎么去分配的,它的数据怎么存的,代码怎么存的,你要理解得非常的清楚,那么而且这个事情非常蛋疼的一件事情是什么呢,就是说你的这一套quot的底层实现机制在windows平台下对吧。
在那个playstation playstation好像是有native的去支持quartet这个东西,哎这是好消息啊,好像我如果没记错,好像是这样的,哎呀不好意思,这好像是保密的信息,我就不讲了。
那么那但是呢就是在那个就是说比如说在arm base的这个芯片上面的话,好像是你要自己通过一段汇编去实现这个类似于cor,肉体的这样的机制,但是好消息就是说这个这些方法怎么去实现的话。
其实在网上有很多的很好的一些样例对吧,大家实在不行,参考一下boost也能知道怎么做,所以的话呢这个是这个quotine,特别stuck for the quoting的话呢,是在我们的游戏引擎。
我们在构建很多基础的时候,它是我的一个foundation基础好,那么你有了这样一个就是,比如说你有一个stuck for cooking这样的东西的时候,接下来我们就开始我们有趣的东西了。
这个东西听上去非常的高大上啊,叫fiber base job system,fiber这个词呢呃怎么说呢,就是说叫管道叫光纤对吧,fiber英文光纤好像也是fiber对吧,那么这个词其实还是蛮高大上的。
这是windows的一个概念,为什么我对这个词特别有感情呢,因为就是十几年前,当我在美国去学架构下一代的引擎,就那个时候我们在架构design引擎的时候,我们那时候就想要加这个多线程高度并行化。
所以我们就会研究fiber这个技术,所以这个词我也觉得特别的酷对吧,其实fiber你可以理解成就是说我们架构一个就是非常高速的,这个就是说一个一个一个一个一个进程管一个一个县城管道。
然后呢我们可以在这里面高速的放很多的task,这些task里面可以自由地进行各种croot的这种,就携程的这个切换,那么而在这里面的话呢,就是说我们的很多的任务就可以自由地塞到这个fiber里面去。
往里面塞塞塞,然后fiber会不停的去执行,而且我塞进fiber里面的这些任务的话,这些就我们叫做drop对吧,诶它可以设置dependency,而且呢我还可以进行各种priority的设置。
他这个想法其实非常的简单,其实大家想想看,当我们去实现一个真正的多线程系统的时候,这个job system是不是我们最理想的情况对吧,原则上来讲的话,你有100个1000个不同的任务,我有十个核好了。
你们就大家就纷纷根据每个任务的长短,我去像搭积木一样的凑在一起,尽量确保所有的fiber他的任务是在同一时间进来的,乡间的时间完成。
所以这是fiber based the job system最简单的一个想法,那这里面有一个有意思的细节是什么呢,就是哎fiber你可以想象成一个works read,这里面有个workre概念对吧。
就是那这个我到底申请多少个mo,比如说我拿到一台我我的游戏跑起来之后,我的引擎跑起来哎,我发现这台电脑有四个核对吧,那我申请多少个works read,那么我假设发现这个电脑有16个核。
我申请多少个workra,这里面其实有一个trick,你比较缺口,这是也是那个fiber系统的一个很核心的一个一个思想,就是我尽可能的一个work read对应一个盒,这个盒呢可以是个逻辑盒。
也可以是个物理核,一般我们会对应那个逻辑和逻辑盒什么概念,就大家看现在cpu都是什么hyper thread,对不对对吧,它实际上是呃一个盒,但是它逻辑上分成两个盒,一般来讲我们一个hyperread。
我们会给它做一个working read,这样的好处是什么呢,就是说当我不停地往这里面去加不同各种各样的job的时候,我并不需要这些thread进行swap,这也是fiber系统最核心的一个思想。
就是说我们在这个系统里面,这个thread的这个swap几乎是零,这个是非常重要,因为刚刚刚刚刚大家在前面已经看到了,就是说每一次thread的切换实际上是非常昂贵的,所以哎你看这里面就很淳朴了。
对吧啊,那我就知道了,那我有多少个和我就生成生成多少个working thread,而这个时候呢实际上就比较简单了,就是说我们这个walking read,还有叫fiber,对吧,好。
我用户生成了一个一个的job的时候,我就把它压到这个fire里面去,我就让你们去依次去执行那thread就是个很working read,就是个可怜的工人,我就开始一个一个执行这些所有的job。
那么这里面的话呢,其实注意啊,这个job中间的话其实有两个很核心的概念,第一个就是他们之间是有优先级的,但是我还讲优先级怎么处理,还有一个是什么呢,他们之间是有dependency的。
就是说诶有些任务我要完成我这个job的时候,一个joke就它可以fork出来很多其他的jo对吧,那这个时候我最近我这个就是working read,怎么去调度呢。
这里面就讲到一个就是首先它有一个global的调度系统,它叫job scheduler,他会你你的job其实并不是直接递给working read,实际上是job scheduler。
他会根据每一个walking read,他的这个饱和状态,把这些任务扔进去,所以这个和大家刚才看到就是说比如说那个 join,那个模型应该不大不同,是什么呢。
他那个function join的那个模型啊,实际上是它还是一个单线程的,但只是到了一段之后,他突然生成了一大批诶类似的任务,你们做完之后再合到一起,它并不是真正的就是全b性化的这样一个模型。
而真正的全面信号模型一定是这样的一个系统,就是说诶我有个schedule对吧,你所有的这个硬件的计算资源对我来讲只是我的resource,都是我的worker,然后呢我来决定说,哎你这个任务大一点。
你就把它扔到这儿,你就做得长一点,你任务小一点,我就给你多塞几个小任务对吧,我尽量保证大家的任务基本是均衡的,而这里面就有一个非常有意思的问题了,就是说当我有这样的一个working task的话。
我是先认认据的任务先完成的,就是像一个q一样像个队列一样的,first thing first out,还是这个系最后扔进去的任务先执行对吧,last thing first out,这个是什么。
是个堆栈,大家想想看这个东西你天然的理解是什么,我相信所有的同学天然的理解都是,那肯定是first in first out嘛对吧,我先生的任务先做完嘛,对不对,后续容才能做嘛,诶这里面就是一个啊。
当我们做游戏引擎的drop sister的时候,大家要注意的一点,就是说其实在游戏引擎的工程实践中啊,我们实现我们的job system的时候,一般啊只能讲一般这个东西不一定呢,也许我讲的是不对的。
但是一般来讲我们会用last thing first thought,为什么呢,因为在游戏引擎中,很多的job产生的话,都是前一个job做到一半,突然发现我要产生四五个不同的job。
而这个job在执行到一半的时候又会产生新的job,所以他彼此之间是个dependency关系,那就意味着说我fork出来的这些任务如果不去完成的话,我的自身是完成不了的。
大家想想这件事情是不是这样的对吧,所以的话呢它的实施方法其实是last thing for salt,像个堆栈一样的,就这个很有意思,其实那个就这也是我们在企业去opl特别容易犯的一个错误。
好那这个时候呢我们的这些job的执行的时候,实际上是按照一个debeng事情,如果我这个时候发现我的job被药的掉之后,我们就会把那些医药掉的任务呢,我把它全扔到一个waiting list里面去。
诶这个事情这个事情谁干,schedule干吧,所以在这个working ride里面,你的任务塞进去了,并不是永久的,只要我会依次的去执行,对不对,但我一旦发现你被要了之后,我就把你框一扔。
我就执行下一个可以执行任务,大家想象一下,这样的话是不是我们就可以把这个cpu尽可能的利用起来,对吧,你你要等着你的那些dependence执行完之后,你才回得来才能执行的了。
而这里面的话呢还有一个很重要的东西叫job stein,什么意思,就是说在这些walking read里面的话,我堆了很多的jobs,对不对,实际上无论你做最好的再好的skype。
你实际上是并不能估计这个任务要跑多久呢,因为有两种情况,第一个这个任务的gb的运算太复杂了对吧,你看的是一个函数扩,但里面你给我算了一堆这个很神奇的这个计算,那我等半天等不了。
有的时候我的job是要等i o的对吧,这很疯狂,我的我要等i o画很久,那么还有一种可能性是什么呢,就是说哎你的教育本身不复杂,但是呢你又很你在这个过程中间产生了一堆dependency。
或者你天然的就有很多dependency,那就会导致什么问题呢,就导致了就是说我其实没有办法让就是我给你100件任务,实际上scheduler他是没有办法去estimate每个任务要执行的时间的。
所以就会出现一定会出现什么呢,就是有些workread他的活已经做完了,有些workread呢还剩下很多活没做完,那怎么办,哎,works read,会schedule。
很聪明的把那些没有做完的那些那个那个那个read里面的那些任务,偷过来,放到我们的这个空闲的这个sd里面,大家仔细看,这个想法其实非常的简单,就是job system。
它实际上就像有一个工厂里面有一个这个这个经理对吧,他时刻看每个工人手上的活儿,然后这个工人的这段任务的话,实际上呢第一个它又相对独立,但是呢他彼此有一有一赖,比如说我们造一个鞋对吧,有人去刷鞋底。
有人去做鞋面,有人在穿鞋带,对不对,还有一些其他的,比如说还有皮鞋,也有凉鞋在一起,那当这么多的任务,这些部件塞过来的时候,这个manager他就会不停地把这些任务堆到这些人前面。
然后不停地观察这些工人的状态,然后呢尽量保证每个人手上都有活干,而且呢他尽可能的他用lasting for all的这个方法,尽可能的保证,就是说它的前导工序做完之后再去完成后面的工序。
其实啊就是大家讲起来job sim是一个很高大上的概念,实际上说实话job si实现起来还是蛮复杂的,就是对于特别对游戏这种case来讲,但是呢它的最核心的思想就是这些。
就是说你其实它是一个更彻底的用多线程的思想,就是用那个parallel programing思想或者多线程就是b型化编程思想,充分的利用了所有的cpu的资源,这个东西我们个人认为就是在未来的话。
就是说嗯随着我们的计算机的核越来越多,比如说那x86 架构里面八个和16和32核,那一定是发生的事情,对不对,比如说army base的芯片中,我们的那个盒也会越来越多,为什么。
因为我不能够做得太快对吧,我要降低功耗,那这个时候其实job system,实际上还真的是一个未来的一个很重要的一种实施方式,那么job system呢实际上是一个非常好的一个系统吧。
就是说他实际上非常容易实现这个任务的这个schedule,而且呢它也能很容易地去处理这些任务之间的dependency,它不像刚才我讲的那个就是tx graph的方法对吧,你要很傻的。
在你的这个程序启动之前,要不断地定义这些task,定义它的dependency那个东西大家如果写写就知道就很难受,但是这个东西有没有用的,他graph其实也是有用的。
比如说在现在的很多的引擎或者一些平台里面的话,它有些大的模块之前在dependency还真得用中方法去定义下来,但是当我在产生几百个上千个上万个这样的db在一帧里面的时候。
实际上只有job system才能够非常高效地解决这个问题对吧,而且呢就是每一个job的这个堆栈它都是独立的,彼此之间不会去干扰,而且呢就是说因为它是基于可如亭的这个架构,而且我们也讲了。
就是说每个works read它是对应的一个物理的和,所以的话呢它就不存在这个context switching,因为他只要不触发硬件那个中断的话,它的效率就会非常的高,那么它的挑战是什么呢。
就是说首先的话c加加语言natively,它并不支持fiber对吧,你得自己实现一套这个quotine这个机制,但是我刚才讲了,就是说实在不行,用boost对吧,但是你如果嫌boost特别大,你怎么办。
你把boss里面的代码给抠出来,其实也是可以的对吧,就是说所以呢它只是在不同的那els平台上,时间的方法不一样,那么另外一个的话呢,就是其实这样的一个系统啊,真正实现起来其实挑战还是非常大的。
特别是就是你去实现drop system底层的时候,因为它的特点是什么呢,就是说对于上面的这些可入性的这些业务逻辑的开发者来讲的话,这些对你来讲全部透明了,你看不见的对吧,你觉得诶好像我的任务扔进去了。
我只要不停在中间,我说我在fork一个job job就出来了啊,任务数据结果就拿到了,很开心,那么这个任务你就可劲的写,写完之后你去跑起来之后,profile一看。
你发现诶这东西就像小砖头一样垒得整整齐齐的很,就是那个job system跑出来的这个系统,providing特别满足这种叫强迫症的患者,比如像我就是个强迫症患者,你会发现那个cpu看着特别的整齐。
很开心对吧,就是有种叫拼花拼图的快感,但是呢你真的要去实现这样的一个就是非常鲁莽和稳健的job系统,其实是要求你对这个就是多线程硬件的这个底层开发的话,要非常的了解,举个例子啊。
比如说在硬件开发中会产生很多数据racing的challenge挑战,甚至有时会产生一些非常难以这个被发现和debug的这个问题,举个例子,比如说我们内存中分配了一段空间。
你在一个县城里面说这个空间我认为无效了,我要把它的内存释放掉,对不对,过段时间我要检测一下这个释放是否成功,如果不成功的话,我就认为这个是我成功了,我就后面把这个东西干掉,如果这个数据没有被分配掉。
那被被释放掉,我就继续要的逻辑,但是这时候另外一个线程他发现这段内存空了,他又开始他又产生了一个新的分配任务,有可能那个heap就是那个我们的那个就是heap,就是那个系统管那个那个叫堆吧。
heap叫堆,对不对,就是他去分配这个内存的时候,他又把他的那段内存地址又重用了,而且呢他又好死不死的在那个地方放的数值又是一模一样的,好等后面的数据再接后面的逻辑,在另外一个系统的数据在检查它的时候。
它发生在你们的数据其实没有变化,他没有检测到这个数据已经变成guid,这个case case叫什么呢,我们叫做a b a case对吧,就是其实你认为就是在一个线程里面。
前面一段代码对着数据进行一些修改,后面再去检测的时候,发现它没有变,但是没变了,我的逻辑就就就就是就完全另外一个方向去走了,对不对,但实际上在这个两次中间的话,在另外一个县城里面。
我实际上已经把这个数据改过去一遍,我再改回来了,那这个时候其实你的业务就会产生一些非常深刻,就非常深的,非常难以发现的bug,这种bug的特点是什么呢,就是说呃你基本上可能跑几天几夜。
然后呢突然你的系统就异常了,就挂掉了,然后呢你几乎没有办法浮现他,因为那个内存分配它是很随机的过程,而且呢它还得随机到叫做那个地方的数据,恰巧又被写回到原来的状态,所以导致你很多的检测函数全部无效。
而当我们去真的去实现一个这样的一个对于上层开发者来讲,非常简单好用的这样的一个job system的时候,实际上在底层写它的人的话。
一定要非常小心谨慎地把所有的parallel programming可能出现的一些异常,k4 都得要防范掉,否则的话你狠狠的这个系统上线之后,就是说出现了一些就是我们最怕的问题就是什么。
就是说诶你如果我们百分之百会当场crush掉的bug,一般来讲对于我们来讲就很开心,说这种bug i crush掉了,比如说q a报上了这东西,crush,我就问第一个问题,说这东西能不能出现。
他说能复现,复现率多少百分之百,那我就很开心了,我说你们不要怕这个问题,我们一定能修得好对吧,但是的话呢如果他告诉我说诶我们这个服务器开服了三天之后,有概率突然就挂掉了哇,一般遇到这种问题的时候。
我们就很惨,我们真的很害怕,因为其实当我们去实现这样的一个b型化开发的时候,我们实现这样一个drop system的时候,大家一定要注意,就是说千万千万要把你的理论功底,基本功练得足够扎实。
然后呢才开始去搭这些底层系统,否则的话你很容易搭出一个看上去很酷的一个东西,但是当一些丰富和复杂的业务逻辑压进来之后,你会在各种很奇怪的地方挂掉,所以呢其实对于这种就是por program。
特别它的底层核心系统的话,我会建议大家就多做一些形式化的推导,就是说去从理论上证明这个东西是安全的,而不是完全基于自己的经验去看这东西是不是合理的,另外一个的话呢就是多去网上去查一些资料。
就是说实际上比如说像vivi啊,这上面有很多的那个那个那个国外有很多这个开发的这个网站,有很多程序员总结了很多的pattern,就是说当你去并行化的时候,在这个地方会遇到问题。
所以说呢虽然我们这节课讲的是高级课,讲的一个就是说诶怎么去实现一个这个非常彻头彻尾的一个,b性化的游戏引擎对吧,用job sim听上去很高大上,我也想去做,但是的话呢就是当大家真的去做这件事情的时候。
因为这里面你很可能吭哧吭哧搞了半年,然后呢整个系统挂的在这个四处漏风对吧。
20.现代游戏引擎架构:面向数据编程与任务系统 (Part 2) | GAMES104-现代游戏引擎:从入门到实践 - P1 - GAMES-Webinar - BV1Md4y1G7zp
那接下来呢我们给大家讲的是什么呢,诶就是家这个特别喜欢的d o p对吧,面向数据的变态,听上去非常高高大上,那么在我们先讲db之前呢,我先跟大家简单的就是快速的过一下。
就是说哎各种各样的编程的paradi,就是编程模式,比如说面向过程的编程对吧,面向对象的编程对吧,o那个o p我相信我们所有的同学学的最多的就是op了,对不对。
那其实的话呢其实在我们在做游戏引擎的时候啊,我们各种编程范式都会用的,其实呃这也是我后来进入到游戏引擎这个行业的时候,我才意识到一件事情就是1000,我总觉得自己写代码还不错对吧,就凭着一点小聪明。
我这个代码写的也挺好的,但是当你面对一个游戏引擎这么复杂的一个系统的时候,你会发现就是你不读书是不行的,真的游戏引擎真的要读书,就是说你要学各种各样的编程的pattern。
就是design pattern,大家我记得以前有本书吗,特别好玩,就是那个封面上是个女孩儿,戴了个眼镜,老师这样盯你看叫什么,design pattern,教你各种比如说listener啊。
什么这个机制啊,教你什么publisher,什么subscriber的这种pattern,你是面向过程的编程呢,你还是面向这个就是对象的编程呢对吧,这件事情其实对游戏引擎来讲是一个非常重要的一个。
这个怎么说呢,你一开始要想清楚的一个问题,那么这里面的话呢,其实在早期的游戏引擎其实比较简单,比如说我们要做一个小乌龟的这个这个这个游戏的话对吧,小乌龟吃豆子的这个游戏,那其实很简单。
就是他就是用面向过程就好了,一个函数对吧,我各种情况进来,我的数据做各种处理,我各种输入和输出其实非常的简单,但是呢其实我们一旦讲到这个现代游戏引擎的话,大家很天然的。
大家学过的component的系统对吧,我跟大家讲了数据集成是不是跟数据集成一模一样,对不对,不好使啊对吧,我成为一个g o,我可以派生出各种各样的东西对吧,vehicle啊,飞机啊,武器啊。
人呐什么都可以,万物皆goo嘛,对不对,那这个思想其实就是面向对象的思想,就是object oriented programming,这个思想其实非常的了不起啊,就是从它诞生至今的话。
实际上它非常非常的符合我们人类对这个世界的认知,所以他去写代码的时候,其实你写起来会很舒服对吧,那么object orange orange programming,它是不是完美的呢。
实际上今天呢我要开始去黑他了,你看这个我们为了都是我们的d o p就是面向数据编程,我们就开始开始狂黑我们的老朋友,这个op编程对吧,那o b编程它其实第一个问题是什么呢,他虽然理论上你会觉得很舒服。
但是他有很多的r一系,举个例子,比如说我一个actor,他的战斗逻辑,你比如说这里面的这个怪物,它是一个actor对吧,那个人也是actor,他们俩都是g o,那我现在怪物这个人去攻击怪物的这件事情。
我是把它写在这个,就是说人这个逻辑就是我去attack对吧,我去apply damage to,然后你们来传一个我我target的这个这个这个extra或者target的g o,还是说诶我被收集了。
我这个这个这个就是说我这个龙,我说been damaged对吧,我被人打了,那我谁来打击我,谁来打我的,他给我造成多少伤害,其实你会发现就是虽然面向对象看起来对这个数据分析的非常的清楚。
但是这个操作就这一个动作,他到底归属就是动作,在o里面就是一个function对吧,就比如大家想象一个目前对象的语言,你有很多的这个变量和这个这个类下面会有很多它的function。
那个function就是动作,那这个动作到底是挂在这个这个这个在这个案例里面,是这个攻击者呢还是受益者呢,其实这是有2亿性的,而且不同的程序员它有不同的写法,大家如果打开一些游戏的这个逻辑的代码。
比如说你们真的打开一个真正的商业级游戏的时候,你发现这种游戏它有很多的程序员一起写吗,你会发现有些程序员就喜欢从这个攻击者的角度去写对吧,有些程序员就喜欢从数据角度去写。
然后两啊这个这就是this is real,这是真的这个这样的,所以这个时候你就很蛋疼,就是面向对象的时候,你就会发现哎呀这个代码的一致性强迫症患者又受不了了,对不对。
那么第二个就是这个面向对象的一个很大的问题是什么呢,就是说它实际上是一个非常深的一个继承数对吧,那我现在假设我说我对一个东西造成了一个魔法伤害,我一层层的继承,其实我并不知道这个魔法伤害这件事情。
你是在base class里面,比如说gu内层做的呢,还是在actor这一层,是所有的actor都能受到魔法伤害呢,还是说你在这个monster这一层去这个继承去做的,所有的monster受到魔法伤害。
还是说派生出来说monster,你的这个flying呢,或者能变身的这个monster里面去,他能受到这个魔法伤害,大家想想看,是不是当你面对一个几百万行的这样的一个,真实的游戏引擎代码的时候。
你现在这个时候当你产生了一个需求,你想了解这个事情是在哪个函数实现的时候,你在这个深不见底的这个这个继承数里面去找到这个函数的继承,这其实也是一件非常逗那个头疼的一件事情,而且就像我刚才讲的。
就是它是具有很强的恶意性的,有的时候有的人觉得这个东西是个通用的需求,我要写到鸡肋去对吧,积累才是有道理的,有人觉得哎呀这个不行,这个是很特别的,我得把它写到派生类对吧。
排序内派到最后也发现诶这个东西又是共用了,怎么办,我们把这个函数又移到它的积累里面去,其实如果大家去写这个就是o的这个程序员的话,这是我们的日常,我们经常会在这样的一个这个纠结。
这到底属于这个叫基础服务还是属于特殊服务,这个问题上会纠结很久对吧,这个也是我们程序员之间的这个这个日常的对话,好,那还有o还有个什么问题呢,就是唉这个问题是我觉得也是很头疼的问题。
就是这个base class就是鸡肋,会做的非常的繁复,这里面就讲一个案例吧,在这里我们就不黑了,是你某个引擎吧,某引擎的actor积累,大家有时间可以研究一下,你会发现就是说我的天呐。
这个actor里面放了无数的功能,连迪巴克做这种功能在里面放了我,我当时第一次看到这个代码的时候,我就说我的天呐对吧,但是你仔细想想,如果我们在做一个真正的商业级游戏引擎的时候。
你会发现你还真的是这样的,这里面就是会放很多很多功能,所以就是当我去写一个非常小的功能的时候,我派生是actor的时候,我会发现就是我本来只要你这么几个功能,结果你给了我一个全家桶对吧。
这个大家想想这是不是一个商业销售的一个经典套路,其实在我们的程序员开发里面的话,我们有时候也会干这样的一件事情,所以其实这个base class的这个肮脏臃肿,实际上是oo的一个一个大型系统啊。
o o做到大系统做到后来大概率会发生的问题,那么如果大家这个想一切身感受这件事情的话,可以打开任何一个商业引擎的源代码,大家就知道我在讲什么,ok那另外一个的话呢就是o还有一个很大的问题。
就是性能其实很低,那为什么o的性能很低呢,因为其实o它非常符合我们人的直觉的数据的理解,对不对,但是大家发现没有这些数据啊,全部会分散到各个的这个object里面去。
而且呢这个object通过派生继承它的数据全是异构的,然后他就每一个对象的创生对吧,allocate deallocate都是这个分散的进行,所以它在内存中啊。
你们如果你们看一个oo系统的内存抓下来看的话,那真的就是东一块西一块,东一块西一块,那大家想想我们的cpu怎么去处理,是不是很疯狂对吧,很难受了,对不对是吧,所以它的内存是全是分散掉了。
那么另外一个是什么呢,就o里面它是有这个类的这个函数的这个派生和继承对吧,其实很多函数会被重载,那么还有什么呢,虚函数对大家知道什么虚函数的概念,对不对,好啦,那就是虚函数是什么,全是指针。
所以这段代码也是在内存中跳来跳去的,就是你本来这段execution的这段code是在一个地方,但是到另外一个执行到这块,突然跳到另一个地方执行另一个函数了。
所以其实o的系统如果你用profile工具去看的时候,你会发现它的profiling是非常的,就是怎么说呢,非常丑的,这边是我们一个图了,大家看到这个图,你不管你懂不懂,这个就是这个高性能开发。
你看到这个图基本上你就知道嗯,这个系统的性能做得不是很好,它非常的不稳定,所以其实欧有非常严重的这个性能的问题,那么第三个呢呃也就最后一个吧,就是o有一个更难的一个问题是可测试性,其实大家想象一下。
就是说我们一个非常复杂的系统,你们有几百个上千个模块对吧,那我经常的每一个模块,比如说我要去测算这个啊,比如说角色的某一个业务或者是某个业务逻辑,比如伤害结算这个逻辑是不是对的,就是这个逻辑应该被人改。
对不对,我要确保它的公式,最后的测试项全部都通过,那为了测试这个伤害的这个计算公式,我们需要干嘛呢,如果用oo写的系统的时候,我们需要把整个环境给创建起来,我们需要把这些这个所有的对象。
所有的东西全部拿起来,然后呢我再去测试,中间呢有一个函数,对不对,因为我要把整个对象全部创建起来,但实际上呢在现代那个就是说软件工程里面,我们一般讲的概念叫unit test,他最理想的情况是什么。
就是你一个庞大的code base对吧,你的代码可以一直改,但是我有比如说几十个上百个这种unit test,他只去测你的一个特定模块的数据,所以你为了测那个数据,你得把整个对象筛选出来。
那么对象一层套一层一层套一层,到了之后你就很难把一个面向对象的这个世界里面的一个对,就是一个元素提取出来,对它单独进行测试,所以o的话你面对一个非常重度的oa系统的话。
你对它进行这样的一个就是说unit test的话,这个代码其实是非常非常难写的,这也是欧啊传传统上有很大的一个问题,其实现在当然了,克服这个问题的话呢,方法不仅仅是这个就是面向数据编程。
其实还有其他的方法,在今天呢我们不展开,比如说面向函数编程对吧,那其实也能解决这个问题,但为什么有这样的不同的编程的parade存在,其实跟这个也是有很深的关系的,特别是在这种超大型的软件工程。
因为一个游戏引擎啊,真正的一个到达可以用的规模的话,一般都是几百万行以上的代码,那这个时候其实它的可测试性实际上要求也会非常的高,所以我们用前面大概十几节课,大概19节课给大家讲了。
这个就是这个游戏引擎的架构,一直在用o的速度跟大家讲,诶,我今天为了卖我们的这个面向数据编程,你看我就狠狠的把我们的oo也黑了一遍对吧,这个真的真的就是这个我们这个叫叫什么叫喜新厌旧,我们都是大猪蹄子。
看见好东西就把老就把这个新人胜旧人了,我们就把以前的小甜甜现在叫牛夫人了,好那接下来我们就看看我们的小甜甜长什么样啊,那其实呢我们的小甜甜叫什么呢,叫这个dorange programming。
面向数据编程这个名字听上去就非常的高大上,确实是很高大上,那么它的基础原理是什么样的,就是说事实上就是现代的计算机的发展,我们会发现一个很有趣的一个一个特点。
就是说我们的processor的速度其实是越来越快,但是呢我们的内存访问速度实际上增长的速度跟不上我们cpu速度,刚才就跟我前面讲了一个东西是矛盾啊,前面我讲哎呀这个cpu已经发展它它到它的这个瓶颈了。
对不对,我们必须要多喝了,诶怎么现在你又去讲说哎cpu发展速度很快,把这个内存甩到后面去了,其实所有的东西都是相对的,这没有办法,就是说确实在过去的20年里面的话,我们的cpu是以将近上千倍吧。
往下去讲,但是我们的内存访问速度呢大概也就提高了大概十倍左右,一个数量级左右,所以这两个之间的差距啊已经拉开了,将近是两个数量级以上了,2~3个数量级以上,所以这个实际上也是为什么就面向数据的。
其实这里面直接导致了,就是说现在计算机里面有很复杂的这个叫cash的这个机制,其实在前面讲render的时候,给大家已经讲过这件事情了,对不对,那么这也直接导致了就是我们面向数据编程这个思想的产生好。
那为了解决这个问题的话呢,其实在这个就是现代计算机架构里面的话呢,我们会做这样的一个叫cash机制,他你可以理解成就是他像个水泵一样的,就是说我们会cpu在最上面。
那好靠近最靠近cpu的是我们的l e cash,那也是最快速开的cash,它能体量也比较小对吧,那么接下来的话呢就是我们的第二季cash叫l two cash,一般来讲是十兆左右,现在已经很难。
我记得最早以前我买电脑的时候,只有两兆的l to cash,现在大家动不动可以买到十兆的l two cash,但是这个cash的话呢,它的速度大概比l e快慢一个数量级左右。
好af cash是不是直接跟内存一打交道,其实没有,早前只有两层,但现在的话一般还会做l3 级的cash,它呢一般是在就是十几兆到上百兆左右吧之间,那么它呢实际上是比l two开始又去卖一个数量级。
然后再到主内存,内存又慢一个数量级,所以它们之间依次是1000倍的差距,所以说当我cpu在进行处理的时候,无论是你的代码也好,还是你的数据也好,就通过这个cash。
就像那个就是像那个什么叫分级水那个水泵一样的,一一磅一磅一磅的,把你的这个水就是我们的这个data从内存导到我的l3 l3 ,导到l2 l2 导弹l1 ,然后cpu才能对这个数据进行处理。
那处理处理者诶发现数据没有了怎么办,哎那没办法了,现在l一找l一没有怎么办,问l r有没有r2 要有好,等一下你从l r调上来,如果l2 说唉我也没有怎么办,那好那不好意思,那就l3 掉。
l3 是我也没有,那怎么办,不好意思了,那就从内存去掉了,那这样的话一路问完之后,当你决定要从内存中调数据的话,那基本上是1000倍以上的这个这个成本速度变得很慢。
所以说今天我们在做整个高性能编程的时候,实际上这就是我们always keep in mind,就是脑子中一直有这个图,就是我们对这个cast是不friendly还是不friendly是吧。
那么其实呢这就导致了就是当我们在写编程的时候,我们会非常讲究的一件事情,就是说我们的数据是具有locality,就是数据总是很紧的在一起,我对数据处理的时候尽量是一次性处理,哎大家这个概念是不是很熟悉。
对的呀,因为我们在讲这个就是渲染的时候,讲到这个渲染如果要做得好,怎么能做得好,你必须要理解gpu是怎么工作的对吧,gpu里面有什么也有开始啊,对不对,他gpu里面那个他每一个那个rapper。
那个每个rap对吧,每一个这个这个库塔核对吧,他也是要从各个cast里面去读数据的,这个数据的话它也有显存,那实际上的话呢就是说它也是需要就是保证这个数据尽可能的local。
这样的话我的这个cpu才能疯狂的跑起来,才能读这个数据,那么这里面最简单的一个方法是什么呢,是s m d对吧,其实我们在讲render也已经讲过这个概念了。
就是现代的这个cpu基本上全部实现了m i d,就是说你对一个四个vector啊,就四个这个float的加减乘除,你可以把它看成一个vector for,然后呢可以一个指令全部做完。
它一次性会读四个内存的这个空间,而一次性写也是四个空间,所以当我们去packing这些data的时候,我们尽可能把它packing成这个a这个一个sm d的,比如16个bt的这样的一个一个空间。
这个东西够不够呢,其实还不够,其实这里面今天给大家的就是这里面的话呢就是内存中的cash啊,它的数据是有一个管理的原则的,实际上这个你们原则的话最简单的做法就是什么呢,什么意思。
就是说我的cash一旦满了之后,我会把那些不就是在最近一直用的东西,我会留住,把最不常用的东西,就最近最没有用的东西,我把它扔掉,其实是一个非常简单的一个调度算法。
但这个算法的话呢实际上是一个经典算法了,但实际上还有一种算法是什么呢,叫随机的扔掉诶,大家会觉得你不是这个很合理的,是说在cash里面,我最近最近听来的东西,扔掉这个这个留在这儿,这个不长。
那个就稍微远一点,不用的东西,那个留在那个扔掉,这不是一个正确的做法吗,那为什么你会采用随机呢,其实这是个概率的东西,就是说如果你的cash足够大,想象一下,比如说我有个将近1k或者一兆的时候。
假设我每一个tick我随机扔掉,比如说64k64 个bt对吧,或者是128个bt,它相对于整个这个存储空间来讲的话,可能是1‰左右的这个地方,那你这样随机扔掉的一个基础是什么,是这个数据没有被访问到。
你会发现就是说当你从概率去讨论那个去去讨论的时候,你会发现就是随机扔的策略,那么实际上就是说啊就是最近没被用到的数据,只要我被随机扔掉的话,实际上也符合了我们那个数学期望,就是说谁最久没被用到的东西。
它有最大的概率会被扔掉对吧,所以其实呢这也是一个cash管理的机制,那这也是为什么我们在pc数据和代码的时候尽可能的放到一起,那这个时候呢我们要需要理解一个概念,就是说在内存中啊,我们去管理数据的时候。
我们有个cast live概念,就是说一般来讲我们一次就是从这个比如说l2 l3 扔到l two,l two的l一的时候,我每次取的是一个cash line,就是大概是64个bt左右。
就数据一个line,一个line的读写,实际上他这个讲起来就比较抽象了,就是说比如说我们看到一个我们第一个变量,一个是一一个,就是说呃integer对吧,我们定义了一个数字,其实啊他在cpu里面。
如果它你对它进行计算的时候,他是不是在最顶级的cash,就l一里面有他的一段数字,有一段内存,对不对,它其实在l2 里面也有他的一段l3 里面也有一段,在内存中也有一段。
而且呢是整个操作系统和cpu要保证应该是cpu,它要保证在这三个cash和memory的它的数据最终是一致的,而这些数据的读和写的操作其实呢是按照一个cash light进行的,什么意思。
就是说我一次性去读这个64个bt的数据,我去对它进行赌的操作,如果这些数据发生改变了,我我要必须要写回主内存的时候,我也是一次性的一级一级地写下去,所以其实我们cpu在处理数据的时候。
他是用csl的机制进行处理,那这样就会导致一个非常有意思的事情,就是说诶我们如果做一个array,做一个啊,不是array做一个取证吧,那我们怎么去读它和写它的效率高呢。
其实这里面大家会发现就是同样的一个矩阵,如果你假设这个数据是按照这个一行一行的存储的对吧,那你逐行的去读它的话,你的效率可能比你拙劣的去读它的话要高个几百倍甚至上千倍,为什么呢。
因为当你这个矩阵非常大的时候对吧,它在内存中连续的程序,但是你如果按照列取程序的话,你虽然只是往下移了一个y坐标,加了个一,但是他在cast line上可能跳过了很多次。
然后他每一次要被迫的从my memory里面一层层的往上去放数据,所以这个时候它的效率其实是非常非常低的,所以这也是就是当我们去对数据进行访问的时候,它的order非常的重要。
你要是按照这个order来,但这个具体在写代码的时候,大家就能有感觉,大家可以测一下,如果有兴趣可以去测一下这件事情,那么其实dorange的programming它的核心点是什么呢。
就是他认为在游戏里面我的所有的这个事件发生的事情,所有事情的表达它都是数据,它也是一段数据,而这段代码和这个就是说数据放到一起,我核心关注的点是什么呢,就是说这个所有的这些东西在cpu中的话。
我要把它的cash miss降得越低越好,这里面开什么miss,哎我这段代码执行完了,我要我要加载新的代码了,新的代码也会导致我的开始miss,就是这里面我给大家显示一个profile。
就是说你会发现就是说导致了我们的时间消耗中,将近有7%的消耗是什么,就是我这段代码执行完了,我要被迫再去弄个新的一段代码,我有7%的性能损耗在这个地方,大家想象一下,这是不是远远的高于我们大家的想象。
我们一直觉得啊数据的访问,数据的处理比较慢,其实人家代码的优化都是这个当你做高性能编程的时候,你要去思考的问题,所以说呢其实在这种面向数据编程的时候,我们会把数据和代码看成一个整体。
而且我们尽可能的要保证数据和代码都尽可能紧密的,就是说在cash里面在一起,注意内存中他们可能分开,但是在cash里面我们要尽可能在一起,这样保证就这一段代码执行完之后,这些数据刚好能够处理完。
接下来的话呢哎我们在swap下一批的数据和代码在一起,这就相当于是说我这件事物的处理者和我要处理的事物都是在一起,千万不要就是a就像这个打个比方,现在工厂里面啊,比如说我们要做一个斜的加工。
我们还是继续鞋厂为例,那我们希望就是这个工人就能处理这些鞋的这个工人,和他的这个要处理的这个鞋的这些工艺,这些这些原料全部放在一起,这样的话呢他能一次性处理完之后。
哎我再把这些这个工人和谐这个这些制成的东西全swap出去,然后呢我再换一个工人和他家处理的东西,这样的话我的效率就会非常高,那千万不要出现什么情况呢,就第一就是这个工人只能做一道工序,做到一半。
你后面的工序需要另外一个功能来,你这个要切换工人,你要再从再从那个就是memory你把这个工人给调出来,对吧,那那我这些数据在这等这个工人了,还有一种情况是什么呢,就是说哎我工人是确实可以处理完这些鞋。
但是呢这些鞋的原料四散在各个地方,我做到一半,我要从别的地方去掉原料,这两种情况都是面向数据的编程,很核心的想去avoid就想去避免的一种情况,他有的时候比你传统的o o的话高。
就是一个到两个数量级是很正常的,大家听听这个数据是分开了,能快将近100倍,诶这个东西真的是不夸张的,就是说比如说像那个就是啊u体做过一个测试嘛。
就是他用那个他的那个d o t s的系统做了同样的一个东西,大概能快到呃,差不多接近100倍以上,这也是就是你的这个系统和代码必须要这样去架构,才能有这样的一个能力好。
那所以呢这个地方如果大家知道了这样一个基础知识之后,那第一点的话呢,诶我们在做这个任务的时候,我们尽量的避免就是说要对于他的执行顺序的这个order dependency。
这个在前面其实讲的编辑时候跟大家讲过了对吧,就是尽可能让它之间order是无关的,那这样的一个无关为什么这么重要呢,因为很简单,就是说其实这里面有很重要的概念叫fourth sharing,什么意思呢。
就是说我们有两个函数,他们在写一个,比如说读写一个变量,比如两个人再改一个变量,一个人在读,一个人在撇,但是如果这两个线程的任务,他们在写这个变量的时候啊,实际上它在内存中是在一个cash line。
就是正好在64个bt里面的话,这个时候对于整个系统来讲负载特别大,为什么呢,系统你去读它的时候,它要这个要确保就是这个数据all the way到l一的那个cash对吧,你去改了它另外一个人去。
比如说你去你去那个改了他另外一个人去读它的时候,或者两个人同时在写他的时候,他整个这个think和读的过程要专门的做两遍,才能让你的数据一致,所以呢当我们在写这样一个超高性能的这个系统的时候呢。
我们会尽量避免两个线程会同时读写一个csline,这个地方就要求意味着就是说你不要把很碎的数据,就是同时让两个线程去访问,基本上就是一个线程访问的数据就是自己的这一块,双方尽量不要有任何的交集。
否则的话你会发现就是说你觉得你没有做什么操作,而且你也保证了他们彼此之间写的地址是不一样,但是只是因为它的地址是在那个一个开始的范围里面,会导致整个这个内存swap的成本要高非常非常的多。
那么第二件事情呢也是代码上,就是大家以前会经常不注意的一件事情,就是说我们在写代码的时候,我们今天会写什么呢,写if else对吧,if something else something,对不对。
这个写起来非常的舒服,非常的逻辑清楚,但是其实在现在cpu里面啊,它实际上会做一个东西叫什么呢,叫branch prediction,就是我会预测这个branch对吧,哎这个人大家会觉得很奇怪。
你凭什么预测我这个事情发生什么呢,但是呢没办法,就是cpu呢他为了追求极高的性能,因为他会发现就是说在很多时候,你100次进入到这样的一个判断的时候。
可能有99次甚至100次走的都是上面那个branch对吧,可能是第101次会走到下一个branch,所以呢在实践中我们会发现就是branch prediction是个非常有效的,这样的一种方式去处理。
那这样的话,但是它就意味着他会提前把那一端就是这个branch if着你们的,比如说这100行代码提前加载到我的这个l e的cash里面去,这样cpu可以直接去执行它。
注意这里面就讲d o p的一个核心概念,就是说我们优化的不仅仅是数据啊,我们优化的还有代码,那么那这样的话呢,但是但是这时候如果你在下一次进来执行的时候,这个条件突然不满足的时候。
诶这一段代码在l一开始里面他就无效了,他就他要被swap出去了,然后我再去调那个else的那个200行代码,它掉进来,那这个一调的话,如果很不幸的是,这段代码既不在什么l two cash。
也不在l3 cash对吧,它是个冷代码,在在we meer里面的话,那就麻烦很大了,那这个系统就要等很久,所以说如果大家做个测试啊,你们去写用非常随机的数据去测这个就比较复杂的ef s的判断的时候。
你会发现这个代码的性能就会非常非常的低,其实就是因为cpu他在做这个branch prediction的时候,就会这个产生很多的错误,那这件事情呢举个例子,就是说比如说这个函数非常简单对吧。
说诶我从一到十,如果呢你这个中间中间这个这个这个a的这个数组里面的数字,假设大于十的时候,我就做第一种工作,如果不大于十,那我就做第二个工作,那其实你会发现就是他在前面走走走走,走到八也没有问题。
但走到11的时候,第四个元素的时候他就错了,好他就他就那个那个branch prediction,那个系统,就cpu里面他就会说好,那我把下面那个else里的代码给调进来诶,但是呢做着做着就发现不对了。
我靠这个你又小于十了,怎么办,哎我就把那个就是这个这个就是衣服那个代码也掉进来,这样来回倒腾的话,其实你的系统的性能就会下到非常的低,那这种情况你怎么办呢,其实呢有一个简单的方法对吧,答案就能想到了。
那我能不能先对这个数据进行排个序呢对吧,排完序之后,那这样的话你可以看到这个血液里面,它只有在11的时候需要做一个切换,而后面的话就基本上全是els的代码,前面全是if的代码。
那这个性能的话就是如果排序的时间不算进去的话,那假设你的这个读方式一方块二的话,其实是比较麻烦的话,那这个函数的性能它就会高于前面那个函数的性能,大家想想看,同样是逻辑的代码对吧。
你的实现方式不讲效率就会变得非常大,那真正的在就是我们的就是说这个更高性能的要求的时候,一般来讲我们会提前的把这个数据分好,就是简单来讲就是说我们尽可能避免复杂的这个if和else,而我们一上来的话呢。
会对数据,比如说我在创建每个数据的时候,我会加到不同的数据容器里面去,然后呢对于一个容器的处理,我只做一个逻辑,就是保证你的整个逻辑是高度一致性的,大家会觉得这件事情真的那么重要吗。
但是如果你真的在写这种高性能代码,特别是高频的代码的时候,大家就会意识到这件事情对性能的影响真的非常大,特别是这个branch prediction的话,对于这个就是性能的store。
因为那个时候你即使数据在cs里面也没有用了,为什么呢,因为它的代码要去swap,然后呢有的时候代码swap又会导致这个数据无效,所以是非常的麻烦,所以我们会非常就是鼓励大家。
如果你在写这种高性性能的计算的时候,尽量的减少这种branch的运算也是这个道理好,那其实呢第二个呢就是说哎对于这种超高性能的,这种高性能的这种代码的开发的话,你的数据该怎么去摆发。
其实刚才已经讲了一些基础的概念,就是说其实在memory的话,就是刚才讲的面向对象的话,它的数据就会通过各种各样的引用啊,指针啊,就是满天飞对吧,特别像现在很多同学已经不太接触。
比如像c和c加加这种底层语言的时候,其实大家对一个对象,它在内存中的到底怎么放的,大家其实没有感觉的,我们都扔到各种各样的容器里面,对不对,容器里面什么功能都有什么反射啊,什么都有特别特别的方便。
确实是这些代码写起来真的很方便,但是的话呢如果你要去写这种高性能的开发的时候,你还真的需要理解像汇编啊,像c语言这些底层的东西,那这个时候呢你去arrange你的memory的时候就要非常的讲究。
这里面有两个很重要的概念,一个叫array of structure a,一个叫structure array this sa,听上去是不是很这个这个这个很高大上啊对吧,但其实他的想法非常的简单。
就是大家想象一下,如果我们是用的这个oo的思想去定义或者一个定义一个结构,比如说一个particle,那它所有的属性是不是一个structure对吧,那我假设第比如说100个particle。
你最自然的在内存中的这个数据排布是不是a第一个particle的position,velocity对吧,它的color,它的age对吧,接下来是第二个,第三个,第四个,这叫什么呢。
叫array of structure,那这样的话当你对这个数据进行逻辑处理的时候,你会发现你需要没,比如说你你你做一个函数是要更新它的position的,你实际上是要读什么呢,它的velocity。
它的position对吧,但是呢你为了读这两个数据,你每一个party的时候,你都要跳过去跳,因为你要跳过后面的color和edge,这样的话你的cash coherence就不够好。
而当我要进行一种超高速的这种数据处理的时候,其实呢我们很多时候是把数据and structure array,就是同一个数据,我直接排成一个超长的r来进行处理,这个思想啊,其实大家再去看那个。
其实大家真的理解现代高性能高性能编程啊,实际上有一个东西是特别类似的,就是大家理解一下,比如像那个好像computer shader,像在gpu上,为什么同样一个运算上了gpu对吧。
上了显卡就变得非常的快,其实显卡他骨子里面就是一个以数据为驱动的这样编程,就是每一个shader你可以理解成是个函数啊,数据的话呢就非常紧致的,这样排不起来对吧。
那其实的话当我们去arrange这个data的时候,一般来讲如果你想追求高性能的话,我们会按s a的方法去排除他所有的数据好,那接下来的话呢就如果大家懂得这个概念,就进入了大名鼎鼎的这个叫ecs系统。
就是antity component system对吧,这个我相信如果对游戏引擎感兴趣的同学的话,对这几个概念就会耳熟能详,我我得首先道个歉,就是我不要首先我在这地方道个歉。
就是说实话这节课讲的东西实际上是比较晦涩的,而且要求你有一定的这个开发的这个基础,而且很多概念它概念有很多,但是呢你哪一个他之间的关联不像大家想的那么紧密,它很难做到一环套上,为什么。
就比如说最后我们讲的这个e c s系统的时候,它实际上就是前面那么多零散的概念,最后串在一起诶,大家提出了这样一个架构,就是说我们最开始去构建这样的一个游戏引擎的时候。
我们会天然的想到就是我之前讲的component base的系统,对不对,用组件搭出一个我们想要的东西,那么这里面的话呢,你最自然的一种实现方式是用那个用oo的方式。
你就会去构建很多的这样的一个一个对象,一层的派生继承,那显然它无论是它的代码是分散在各个那个就是类里面对吧,还有虚指虚函数只占一塌糊涂,另外一个他的数据也是非常的分散。
所以这样的代码执行起来它的效率非常低,如果同学们尝试写过用oo方法实现的,component base的这样的一个机构系统的话,当你的机构里面confirm变得多,当你的肌肉变得多的时,候。
你每一帧去tick它,你会发现它的性能比你想象的慢很多很多,有的时候你都很郁闷,你说我其实也没有做什么特别复杂的运算呢,那为什么我的系统性能这么慢呢,其实大家今天如果听懂了前面的第一个就是说就是cpu。
它和内存和cash是怎么写作的,你就知道其实很多时候我们会浪费在非常无效的,就是cpu等指令加载,等数据加载对吧,执行到一半发现数据没有了,我又去找那个数据,然后数据值现在一半又发现我要调另外一个函数。
这个函数是个虚指针,我要开始去找那个函数实现,然后找到那个函数之后,我要再把那段pc code再去加载,然后就这样来回倒腾,就搞得一塌糊涂,所以你可以认为这个工厂就是他没有人管理他所有的师傅。
就凭自己的感觉去做,做了缺原料,我就去从库房拿原料,这个我我只会做了这个鞋底,我会做斜面,哎,我就去把这个鞋底的鞋面去递给另外一个做鞋面的师傅,然后大家去做。
所以整个的这种就是说你可以认为整个任务的调度是完全无序的,所以这就是component base以及oo的导致了一个很大的问题,而ec s系统他就想彻底的解决这个方法,那他这个方法呢其实提出来之后。
它的核心想法是这样的,第一对所有的我们的g o哎,我们叫什么呢,我们叫做entity antt呢,在我们的这个就是component base系统里面,它是个什么呢,哎它是一个类对吧。
这个类里面可以装很多的component,对不对,那在这里面的话呢,nt t是非常非常的清亮,它就是一个i d i d指向了一组component,哎看这个我看到有点像,对不对,好。
第二个就是它的组件组件呢在ec里面的话呢,实际上指的就是一个一组一块数据,比如说一个位移数据,一个速度数据这些数据,而这个component的话,实际上它没有任何的业务逻辑,注意啊。
这个就和我们前面讲的这个component base这个系统不太一样了,因为以前我们讲每个component有什么有很多接口,对不对,有t k有什么get property啊。
set property啊,一堆的这种函数诶,但是在ec里面这个是特别容易弄混的,就是说他的component是不允许有业务逻辑的,它就是数据纯数据对吧,你可以对他读,可以对他写。
但是呢他本身并不知道自己的意义,而接下来有一个更抽象的概念叫system,system是什么呢,诶我来对这些component的进行处理,比如说我有一个比如说a moving system对吧。
它会根据你的速度去改你的position,对不对,我有一个叫health system这个函数,它会根据你这个受到的这个damage,然后呢去调整你的health值等等等等。
所以呢它实际上就是把数据全部打平,而且呢把这个数据从它的owner,从以前一个个的g o全部播出来,每一个每一类数据全集中在一起去存储对吧。
然后呢但是呢每一个机他只是说啊我用了这个多少号的这个transform,我用了多少号的velocity,诶这个用这样一种非常离散的关系关联在一起,而对这些数据的处理是由sim进行进行的。
而system他对数据的处理呢,它其实对着component的处理,它并不是一次只处理一个component,比如说我们前面在这个g o的这个component,这里面的话。
其实component很多时候它是对自己的member数据进行处理,对不对,但是呢这里面有一个概念,大家一定要弄清楚,就是system呢很多时候它会同时处理好几类component的数据。
因为这个他就是说因为我把这个单位拆得非常非常的细,所以ecs模型呢是一个大家提出的一个理论框架,就认为用这种模型的话构建的这种系统的话。
他能够充分的利用就是这个这个数据的cash的这个那个coherence对吧,充分的压榨这个这个就是面向数据编程的这种性能,因为你可以发现这个system去处理的时候,它的它的操作是非常简单的。
就是我很容易把system的计算分散到很多很多的和上面去,这也是ecs系统一个很大的一个优势,好这就是一个excel系统,大家听上去是不是有点晕了,很抽象了,对不对,确实啊这个概念其实是有点抽象了。
但是呢你会发现它在实际中的话真的很有用,那怎么去解释这件事情呢,我当我在背这门课的时候,实际上是非常难的,因为我觉得就是说呃你前面的这些概念全部理解完之后呢,大概能知道我在讲什么。
那带这东西的一个实际中的一个很好的应用是什么呢,哎我想到了一个帮手,就是我们把大名鼎鼎的unity的这个d o d o t s系统给搬出来对吧,unity呢提出了这个dorigin的text这个系统。
那么这个系统的话呢,好像现在他们已经决定放弃了,但没关系,不妨碍我们这个去拿它来去分析一下,就是说esc系统的整个应用,你会发现就是说unity自己在讲说他的d u t s系统呢有三个支柱系统。
第一个呢就是ec,它是重新的面向数据去组织它整个这个计算的这个框架,引擎的框架,第二个呢诶他有一个c sharp base的这个job system,为什么呢。
因为我既然已经把数据变成这个component了,都要变成这个system了,那我就非常容易地把它b性化处理,我做了一个非常简单的josim,然后呢他还做了一个很有意思的东西,是什么呢。
就是那个burst compiler,诶这个待会我会解释为什么他要重新做过confia这件事情,其实很很很很疯狂,就是你要还做过编译器,但是呢其实如果你想实现一个这么高的数据访问的这种那个效率。
同时呢你要去实现这种quart这些机制的时候,其实你会发现就c sharp的原生的机制,它的效率是很低的,因为c 12 p本质上是跑在一个虚拟机上的,它并不是native code对吧。
而break compiler他最可行干的事情是什么呢,把这个东西全部变成了native code,就是就是等价于汇编的代码了,所以说这在解决这个问题,所以说这三个系统在一起共同工作诶。
它能实现了就是unity的d u t s的这样一个架构好,那我们简单的讲一下,就unity ecs它是怎么做的,首先第一个呢他也是把这个系统分成了无数个component。
但是呢它在上面抽象了一个概念叫archittype,architeb,什么意思呢,就是你可以叫做啊原型或者叫泛型的概念吧,什么意思,就是说你会发现比如说我要做一个呃n p c。
他所要用到的这个component,大概就这几个对吧,我想做一个就是说诶可以跟我战斗的这个这个怪物对吧,比如说monster它也是一个arc tap,它可能会多一些,比如攻击属性啊。
做一些这种伤害属性啊,或者一些行为属性啊,比如说我在做一个比如说veo,它也有一些相应的这个components,所以其实你可以认为就是那个archetype就是什么呢,type of guo。
就是这一类的g o,我叫做一个archite,那为什么他要做这个ark type呢,其实有个很大的原因,就是说当我游戏中有几百个上千个这样的entity的时候。
每个nt底下面挂的component都是不一致的,对不对,那我这个system在处理这些ntt的时候,我不能一个一个的去找他们的,哎,你有没有这个component,对吧。
你这component的这个我能不能用,那就这个问题很慢,但是我有archittype的时候,他就会让我这个就是处理的过程,就是我知道哪些entity我要处理的速度会变得很快,而且也方便别人去理解好。
我有了这样一个ark type之后,诶,这张图是他最经典的一张图,就是说他首先把内存定义成了一个个的chk,然后呢他会把一类ark type的所有的component。
按照他的component的类型唉放在这里面,就是一个个的去去放在里面,这样的好处是什么呢,你们会发现一个细节,第一就是说一个唱歌里面一定是同一类的,二黑,太对吧。
那就意味着当我有个sim认为这个唱k我需要处理的时候,我只要去问这个唱歌的archive,比如说哦这是一个npc的archit好,我现在要做一个就是比如说对话的system。
那我知道n p c这个tap里面有几个我需要的,come on呢,一定有,那我就对他进行处理,如果你这个是什么呢,哎你这个是一个就是载具的这个这个ark type好。
那你这里面这几十个载具我是彻底不需要考虑的,因为我没有跟你对话的需求,那一个唱歌一般多大呢,就早期的架构里面一般它设成16k对吧,其实这个16k是主要是考虑到就是说cast line啊。
考虑到这个l e l cash它是怎么去swap的,但是其实在现代计算机里面的话呢,这个一个唱歌的大小,我看了一下最新的一些资料里面的话,就是说这个唱歌好像变了大了那么一丢丢,具体的数据。
我们团队他们好像上次看,我记得是像虚幻里面把它提到了128还或者是更大一点,这个具体数据大家可以去查,这个是你们可以调的这个量,那为什么他不是把所有的同类型的component的全放在一起呢。
因为第一个如果我把所有的比如transform com的全放在一起,这些东西数据量就已经非常非常大了,它实际上也不符合我们的那个就是locality这样要求,而它实际上就希望这一段的sistent。
这一段piece of code code,这个这个代码和他要处理的这一小块数据正好能塞进去,处理完,我再把整个的下一个system和他的唱歌part往里面一塞,除了外。
这样的话它整个这个数据啊处理起来效率是最高的,这个其实是这个我觉得就是ecs系统是一个非常巧妙的一个地方,就是大家如果去写这个系统的时候,我是非常建议大家这么去干的。
ok那其实这个时候呢系统就比较简单了对吧,比如说我们现在做一个就是这个我们的这个这个这个就是哎我往前去,这个move,我是update the position的系统呢非常简单。
我只要拿到你的这个velocity的component和translation component,那我做一个运算,其实exist你可以理解成一个运算。
怕算完之后这个唱唱你的所有的物体的这个translation的这边,这个这个component都会被更新一遍,因为你无论是毒啊,血啊,它都在一起,所以它基本上这个效率是非常非常高的。
所以说unit也想说e s s系统的效率,就是刚才我讲了接近100倍的这个提升了,那这里面有个非常有意思的一个东西了,就是说那为什么就是哎我们这个unity要费那么大力气,去写一个burst的系统对吧。
去做自己做个编译器呢,其实这里面有个很有意思的一个事情,就是说如果你整个这个系统它是用c hr去写的话,那这些无论你申请的一个array对吧,你申请你定义的一个数据结构。
它实际上在硬件上到底对应的是个什么东西,完全是一个黑盒,它是跑在那个c sharp的虚拟机里面的对吧,大家想一下,在c 12里面,我定义一个变量,它根本就不是一个变量,它有很多的东西。
它存储的数据只是很小的一块,它有很多反射数据对吧,但是如果你要写这么高高性能的,几乎和硬件一对一对抗的这种代码的话,实际上你最需要的是什么呢,你需要native的东西,所以在这个ecs里面的话。
你看着你在去写c 12 p对吧,实际上你定义了一个array,你要明确的告诉他说,我必须要写native的这种container,实际上就很像我们c语言定义的一个真的就是一段裸指针,就分配了一段东西。
那么的话呢这样的话它才能和内存中的memory 11的去对应起来,所以你对他的数据访问才是能够一一的连续的去去去映射起来,否则的话就是对于高级语言的话,他下面发生的事情基本上是不可控的。
那么因为它是非常native了,哎你要加什么呢,你要加很多的安全的check,比如说像你写c sharp的时候,你不用去管这个内存的释放对吧,但是当你去写这样的一个,你就要非常花很多力气去考虑。
就是他希望是在编译器层面,在job system层面去确保所有的这些logo的或者这些无用的资源会被释放掉,其实这件事情是非常非常困难的,所以呢这个时候他就会必须要写一个自己的compiler。
那去把你的这个c sharc,arc的这个语言编译成其实非常底层的语言,并且并且完成必要的检查,所以其实我自己去理解这个就是那个dot系统,我觉得他做的其实是件蛮艰难的事情。
就是说他希望把他的复杂度全部藏在自己的身上,对于这个上面的业务的开发者来讲的话,你不需要理解这么多复杂的这种cash啊,这种这种东西你只需要去按照我的这个follow去写一个c hr的一个东西。
才能实现我想要的高性能,这里面就有个细节,就是说如果你想实现这种d u p的开发的话,如果你用c sharp的这个基础语法,你是很难写出来,分配啊,释放啊,你根本不知道它内存在什么地方。
这些东西都变得非常的抽象和复杂,所以的话呢就是说unity它要实现这样一个d o t s的这个架构的时候,它就必须要实现自己的一个compiler。
把你的c sharp其实他只用了c sharp这个语法,把它编译成了,就是它其实你相当于用c sharp语法,实现一个类似的这样的一个一个功能,所以这也是为什么就是他要写burst。
就是我用burst的方法编译之后,这个性能又提高了很多倍对吧,所以这就是这个大家通过这个东西的话,大家就能理解,就是说为什么就是说呃我们去实现dot系统的话,它的难度有这么大。
那么这也是因为他一开始用c sharp这个语言的,一个一个就是怎么说呢,一个弊端其实cp是个非常了不起的语言,我觉得是非常好的一个语言,但是的话在高性能开发的时候,他确实有这样的一个弊端。
特别是做dop类型的开发,唉这一点就不得不提我们的另外一个很厉害的引擎,就是虚幻引擎了对吧,那我最近呢我因为备课嘛,我们就课程组织很有兴致的去研究了一下,那个arma 5的最新的那个max系统。
大家想象一下最近rr 5最好的那个demo是什么刺客,那个什么刺客信条说错了,那个就是matrix reload,不是mhc reload吧,那个就是matrix那个demo对吧。
在中间大家看到那个曼哈顿,对不对,中间有成千上万的汽车啊,行人啊这些东西,而max系统中呢哎实际上就是而要自己实现了一套就是类似于u s的,类似于job系统的这样一个东西。
那么其实这个mac系统的话怎么说呢,我觉得其实也没什么好讲的,其实很像unity的d o t s,我甚至怀疑他们两个互相抄作业呀,但我就不去评论到底谁抄谁了对吧,那反正就是我看到的就是nt系统呢。
它首先那个e c s系统里面,首先nt的概念,那t d里面的话呢,它也一样,也只是个i d,这里面的话每个i d呢它是有一个siri d。
就是他那个单独永远往上ci number只是往上递增的这样一个东西,实际上就保证了这个i d永远不会就是被重用,其实有点像我们在之前跟大家教大家做憨豆的时候,你会有一个salt对吧。
就保证你这个id可以被重用,但是你这个两个数字是不一样的,这是个小细节诶,我觉得它比较有意思的东西是什么呢,就是在max这个系统里面的话呢,它它的component叫什么呢,它叫fragment。
哎我觉得这个词叫的特别好,为什么想象一下,如果我是写这个unreal的mac系统的人,我就很淡很头疼,因为我那边已经有1000多万行的代码,他在里面定义了无数的component,component。
component,你现在告诉我说,因为我现在实现的是e c s的pattern,我这个东西也叫component,叫此component a非彼component对吧。
而且我个人觉得component这个名字取得确实不好对吧,因为component会让我想起向下com啊,这些接口就你天然的会认为数据服务在一起的东西,但实际上在e s架构里面。
component它就是purely的data,所以呢在这个max这个架构里面,它把它叫做什么呢,叫做fragment a,一小块内存的碎片,一小块数据诶,我觉得这个名字听上去就比较好。
所以的话呢其实整个component的话,它实际上就是放了很多的这个fragment的这个数据,那么对于每一个这个nt来讲的话呢,哎我就会拥有这样的一些类型的这个这个这个那个fragments,诶。
我觉得farm教堂很顺口,那第三个我觉得他做的我觉得蛮有意思的,订婚也就是说sim esim system对吧,system我刚才讲的它其实就是一个函数,它对着一堆这个component进行处理。
他现在既然把抗风的变成了叫fragment,怎么办呢,诶他这个名字我觉得也处理的非常的好,他叫processor处理器,这个很贴切啊,就是说你给了我这么多数据放在这儿,对不对。
我一个processor把你的数据全部处理完对吧,所以说其实max里面这两个几个秘密,我个人还是比较推崇的,而一个processor有两大核心功能,第一大功能是什么呢,我去query。
就是说我需要对哪些满足我的nt对吧,它比如说我每一个这个processor,我需要多少种那个这个这个你的fragments或者多少种component,这component我到底是对你的读还是写。
所以呢我先要做一件什么事情呢,我叫query,叫frankon query,那么他呢就会通过一段描述说诶,我对这个company我要去读这一类的component呢。
我要去读这一类的这个这个component,我要去写,不好意思啊,这个我一会儿component,一一会儿fragment,大家都原谅我,因为确实特别容易混,但是他讲的是同一个东西。
然后呢这个时候当我去对我的query,这就像写数据库一样的,我我先定义了一段数据库query函数,好,接下来一个query函数,一个query操作相机,我就把所有的这个mtt扫了一遍,把这些数据拿出来。
那怎么去扫呢,其实在这个mass里面,他也用了archith这个概念,实际上他这种quy实际上会cash的,就是他上一次query完之后,这一次如果你没有变化的话,我基本上是不用动的。
所以诶我就把所有的我所需要的这些这个archetype的,这些entity的这些fragment全部拿到,接下来干嘛呢,哎我去execute,大家去看他的excel那个代码其实也非常的简单。
就是因为你做完了query之后,其实所有的这些fragment的全部我都拿到了,拿到之后呢,注意啊,这里面只是引用,他,并不是真的把这个数据搬过来,为什么他不需要把这个数据搬过来。
因为对于每一类的ark type而言的话,它的数据全部也是集中存储在一个一个的唱片里面,所以说我对他进行处理的时候,我一个processor,我处理完这个唱歌之后,哎我接下来下一个依次切换的话。
也是整个唱歌给切过去的,所以在这个时候它后面的处理就会相对比较简单一点,所以其实这就是这个max系统它的最底层的一个东西,这个东西的好处是什么。
就是能够让我们在这个虚幻五中能看到那么庞大的一个曼哈顿对吧,陈陈应该是上千上万的这样的汽车啊,人物它其实就用这套系统来实现的,这也是一个c s系统的一个典型的应用案例,所以呢其实这里面我想多说一句啊。
就是e s系统听上去非常的高大上对吧,因为很多同学们一直想让我们去讲一下esc,甚至大家会说诶小引擎里面我们也是不是实现一个e4 ,好像我们的引擎用了ecs对吧,用了这个dp就是非常了不起的一件事情。
但是的话呢实际上我个人的理解啊,就是啊游戏其实是个非常复杂的一个东西,它里面的很多的业务其实非常的啊dependence非常的乱,也非常的复杂,几乎很难你把整个引擎变成e4 s的,这就是我一直在讲。
就是同学们在学了这些前沿的知识的时候,很多时候我们会陷入到那种就是这个理想主义的这种冲动,就是诶我要搭一个彻底基于dp,彻底基于ecs的这样的一个下一代的游戏引擎,这件事情说起来非常的高大上。
但是如果你真的作为一个实战型的这样的一个引擎的话,你会发现你所面临的问题和挑战绝对没有那么简单和抽象,所以的话呢其实ec的话呢对于引擎来讲,在有些单用的地方你就去用它非常的好。
但是呢不单用的地方千万不要到处用,因为它会极大的增加你的代码的维护的复杂度,而且这种代码的开发起来难度也非常大,因为它确实是很抽象的,就是很多人天然的它就很难理解为什么数据和它的业务分开了。
而且很多时候我们写一个业务逻辑都很下意识的就是数据,数据比数据c数据d对吧,我去问自己,问别人问一大堆,然后再说我东西怎么办,特别是大家如果写过真实的游戏逻辑的时候,你90%以上的逻辑都是这么写的。
所以说ec系统一般会用在一些非常确定的情况下,比如说强如虚幻五的话,它的ecs也是放在了mass这个非常特别的这个案例里面,很彻底的用e s s的价格去做了一遍。
它主体的引擎还是我们前面被我黑出翔的那个什么,就是面向对象的component pace的架构对吧,这里面我给大家解释一下,就是虽然我今天把这个o o黑的这个这个这个这个这个一无是处。
但实际上这只是为了烘托气氛,为了抬出我们今天的主角d o p,那实际上的话呢就是说在一个实战型的游戏引擎里面的话,这两种架构是同等重要,甚至坦白的讲了,oo的重要性可能还是更还是大那么一点点。
然后呢这个就是基于doo p啊,至于那个ecs这个系统的话,稍微小那么一点,这里面还有一个概念的话,我想给大家那个就是再去clarify一下,就是其实fiber base的这个job system的话。
和这个e s s有关系,但是也没有那么直接的关系,其实这个fiber base的这个job system呢,它更多的是一种high level这种任务之间的调度,而ec呢更像是在一些大量的运算的时候。
我是面向数据去组织我的预算,所以这两个之间是有交集的,但是呢他又不是完全的高度一致,所以说并不是说我用了这个就是这个e s s,我一定要实现fire的job system。
其实你用 join的这个pattern一样可以运行得很好对吧,我就生成一个静态的graph的方法,能不能做能做,其实啊如果我没记错的话,就是unity的dot和阿荣耀的这个就是max system的话。
它就不是一个完整的一个fiber base的这个job sim,实际上还是基于dependency graph构建了一个就是任务的fork出去,然后呢a回答说完再做下一件事,其实这个效果也是非常好的。
没有任何问题,所以我还是那句话,就是做游戏引擎架构,千万不要执念这个一定要实事求是,就是你什么地方,关键是把你的技术用对好,那基本上讲到这儿,哎我们这节课的东西基本上讲完了,那么首先我给大家再去道个歉。
就是说哎呀我觉得操作系统这门课第一个大学的时候我就没有学好,然后后来工作的时候,因为工作需要还认真的学了一下,但是今天我在讲这门课的时候,我突然发现就是给我的时间特别特别的少,所以像多线程啊。
像那些这个比如说像这个这个mutex啊,像这些什么原子啊,tomic的这些东西到底是怎么回事,实际上今天没有深度的去讲,这个同学们如果深度对这线性感兴趣的话,可以花时间去钻研。
你去理解这个job sim它的核心思想就差不多了,而且理解parallel programing它的挑战在哪里,dp也实际上dp实际上的话呢啊你真的去实现的时候,还是有很多细节需要注意的好。
那今天讲了这么多之后,我说哎呀今天讲的东西我感觉特别的散,零零散散零零散散对吧,我怎么去总结我今天做的所有的事情,我后来想到了一个很有意思的东西,就是这张图,这张图啊。
我觉得如果理解今天这节课讲的所有的东西,其实我们本质上讲的是一个高性能的编程,我们不会学的东西,因为我们更多的学的是怎么去表达一个业务逻辑,但是当你真的懂了怎么去编程之后,当你面对一个剧情系统。
它又像游戏引擎这样对性能这么critic的要求的时候,其实你就被迫要理解操作系统和硬件,cpu memory cash是怎么协作的,而这张图的话呢,同学们如果有时间可以一行一行的看下去。
你在这里面可以看到就是一个au指令到底花多少对吧,一个这个就是说硬件级别的中断,它到底要花多少,一个threat的contest swapping需要切多少对吧。
比如说我们的一个c j i i里面一个函数调用一个虚函式调用对吧,一次站的这个swap你要花多少代价,其实今天我们讲的几乎所有的这个解决方案,它本质上就是对着这张图来优化的。
所以同学们如果有志啊去做这个系统的架构师,对其实我个人一直认为就是说啊我们我们并不缺programmer,但是我们非常缺这个architecture,就是有至于进行这种底层这个系统架构的同学的话。
那我认为就是说今天这节课讲的东西,是非常值得大家去深入研究和钻研的,而且你去钻研他的影子是什么呢,把这张图看完,每一行看明白,说诶为什么这个地方会更慢,为什么地方好像会慢,一就是大家看到这里的差别。
不是说一个线性的,它是数量级的,就是十的零次方,一次方,二次方一直到十的多少次到四次方到五次方位,对不对,就会卖10万倍,甚至是个就是甚至是应该是最高是多少次方,我看一下十的四次方五存在对。
最高到100万倍很可啊,100对100万倍,所以是挺挺吓人的,对不对,所以说一个系统如果写的不好的话,同样的逻辑,同样的代码真的慢个100倍是很正常的。
所以这也是就是说啊就是说今天这节课想给大家讲讲的一个东西,就是说诶第一次让大家真的碰到影和,而引擎最内核的东西就是如何实现这种超高性能的编程,那么抓住这件事情的线索,也许是从这张图开始。
那么你这张图看懂了,你再回过头来看我们课程前面讲的东西,其实就是自然而然就通了,ok那这就是我今天这节课的主要内容,那么还是再次感谢我们的课程组啊,就是大家其实这里面大部分的工作都是我们团队去做的。
那我这个作为这个工具人负责把这些知识传递给大家,那么这里面的话呢,我们的课程组也分享了很多的资料,比如像cash系统到底是怎么work的,同学们可以看这些资料对吧。
比如说那些各种的这个blocking的方法,nblocking的方法对吧,这里面有一系列的文章会告诉大家是怎么去做的,其实这里面有很多的paradise,我认为是很重要的。
就是大家如果想去写一个b进化的程序的时候,千万千万不要土法炼钢,这个我是这个再次提醒大家,为什么呢,因为就是这是我们一直在做研发的一个一个一个观点,就是我认为太阳底下没新鲜事。
我们自己拍脑袋想到的很多的解决方法,实际上别人可能在10年前,20年前,别人已经遇到无数次了,而且整个行业提炼和总结出来的这些paradim,就这些范式的话。
实际上已经是几可以被理论证明是成功的一些东西,去follow他,千万不要发明创造,这是这是我觉得就是做大型软件系统的时候一个一个觉悟吧,那么接下来的话呢就是在游戏引擎里面诶。
你怎么去做parallel的那个framework,也有以下的这些文献对吧,那还有什么呢,就是大家想心心念念的d o p面向数据编程也是有这样的一些文档,会告诉大家怎么去做研发。
所以大家如果对今天讲的这几个两个高级话题的话,很感兴趣的话,尤其是如果特别是你自己已经是个从业者的话,我觉得这些文章很值得你去读,今天我们的reference的阵容是比较大的。
确实今天我们的课程组准备的也是比较用力的,最近的话因为准备这个课程,我们把max系统又整个看了一遍,对吧对,所以的话呢非常感谢我们课程组的小伙伴们,那ok那今天就是我的内容的主体。
最后的话同学们有什么问题啊,这个第一个问题是什么,把cpu跑满怎么办哦,我的天哪,这个问题实在是太硬核了吧,就是说如果自旋锁把cpu跑满,那这个其实我个人觉得就是可能还是你的任务的这个安排的不太合理。
一般cpu跑满之后,你可能会触发一系列的这个,比如说那个你的县城对吧,可能是堵在那儿了,你那个县城可能要被迫swap出去,所以的话呢其实我们在很多时候尽量避免把cpu全部跑满,但实际上的话呢。
如果cpu跑满之后,实际上很多地方很多的进程县城就会拥塞在那边,所以的话呃我我个人我至少我目前没有一个好的办法能解决它,我们比如像我们我们我们自己实现的是job system。
那job system的话,它会一直会monitoring每一个核的它的占用情况,如果这个核占用使用率到过一个阈值,比如说超过80%到90的时候,我们就不会再给他塞任何的job,就是db会挂在那儿。
但是我不会让他去启动,也就尽量避免这种跑满的情况,因为跑满之后的话,你的其实很多时候这个就是很多行为就会发生一些变化哇,这个问题也很难并行编程,有没有比较好的debug方法,对吧,呃实话实说。
至少我首先的话不是一个非常好的并行编程的开发者,因为医院这真的是非常的难,那么并行编程的话呢,其实很多时候我们是通过什么呢,通过log的方法去抓他,就在log里面去抓,看到一些一些数据出现了问题的时候。
我没有办法就疯狂的logo去看的,但是其实log有个很大的问题,就是说log本身在很多系统里面,它会就是说很慢,因为大家知道写那些文件速度是很慢的嘛,结果就导致了什么呢,导致了就是它的时序发生变化。
所以其实在运行编程里面,比如说我举个例子,比如说我们在做那个fiber系统的时候,实际上我们会去做一个功能,就是说一键把这个fiber变成dance read。
就是说把所有的job一次性依次按照dependent去执行,这样我先确保我的业务逻辑基本上是没有错的,如果我是发现我的业务逻辑,如果单线程执行是不会出问题的话,那我再去看是不是多线程出了问题。
比如说那个时候我就可以猜测某些资源会经营就会怎么样,所以其实多线程编程它本身debug就非常的难,这也是为什么,就是说我们会建议就是在做引擎的这种parallel programing的时候。
其实让尽可能少的程序员接触到多线程编程是一个正确的解法,就是说整个团队你们有几个技术最扎实,基础最扎实,而且思维最缜密的同学去把这个比如说带这个这个这个原子操作啊。
待遇这种就是说这个锁啊这些机制的东西把它做完,做成一个底座,而它的上面的程序员,它尽可能地去简单的去开发这些逻辑,而且他所访问的数据,它能调用的函数都尽可能约束好,最好上面的同学只去写脚本就好了。
他不用去关注下面真正发生的事情,这个其实也是就现代游戏引擎,因为你被迫要去支持这种多线程吗,那我会建议是这样的一个开发模式,否则的话就是说多线程开发到目前为止的话,第八个都是非常的困难。
当然现在vc的话,新的一代的vc实际上这方面做的已经越来越好了,能让你看到很多状态,但是他最难的一件事情就是说有的时候你出现你出现你,你发现出现出了问题,你根本断不下来。
你都不知道在哪个thread断下来,所以这个事情是很麻烦的一件事情,其实我们自己在写这样的一个就是job son的时候,其实啊我们的同学实际上在前期遇到了大量的就是非常非常难,第八个的一些问题。
真的有一个问题出来之后,很可能会花一两个星期才能把它找到啊,第三卸载线程和逻辑线程的那个同步怎么做啊,这个其实怎么说呢,现在大家比较常见的方法就是这个诶我做一个ring buffer对吧,我的逻辑的啊。
不是in buffer,就是我逻辑线程算完了,所有的值我一股脑扔给渲染,就是我一帧帧的,就是我相当于逻辑线程形成一个逻辑的friend,这个friend交给了渲染,渲染自己去做就好了。
那么实际上的话呢就是这个地方实际上就会导致一个问题,这个问题就是我们现在经常讲到的,就是诶渲染线程总是比逻辑线程要慢一帧,因为他要等逻辑全部算完之后,然后这个数据才到渲染。
这个目前的话呢大家也在探讨一些其他的可能的架构,就是说避免这样的问题,但至少目前为止啊,我觉得一个成熟的商业疫情,很多时候你还是被迫要让这个这个逻辑线上,现在要等逻辑限制全部算完之后。
否则的话他的数据会产生很多的奇怪的二维性,因为当你dio vs这么复杂的一个系统的时候,有的时候我们必须要给他每个每一个就是每一类任务吧,有一个清晰的分界线,就像我前面讲的。
就是如果你用固定线程法去这个区分游戏引擎的这个架构的时候,实际上这些这一类的任务,你不能够轻易地把它从渲染线程和逻辑线程啊,或者是那个防那个那个那个simulation,比如物理线程之间来回的跳。
因为他之前做的这个事情是完全不一样,而且他很多时候的数据这样的一个依赖关系的,所以我们就讲了job system,job system虽然听上去很高大上,但实际上在真实的游戏引擎里面。
你不太可能把整个引擎全部扔进job system,你可能只是把它部分的可能50%到60的workload,可以扔到那个job system里面去进行paralyzation,对物理呀,对啊。
那么它实际上都是要做一些特殊的处理,才能够真的跑得很好,所以这也是一个非常难的一个问题,所以说我还是讲就是大家讲一个理论是很简单的,但是你真的面对一个引擎的实际情况的时候。
你一般来讲你会对这个算法进行定制,就是为这个特殊的scenario去进行一些定制,好的好那个,那今天就先回答这几个问题,首先的话呢就跟大家讲,今天讲的东西确实是蛮硬核的,就是同学们的话千万不要好高骛远。
先把引擎的基础东西做好,这个东西的话就是说诶如果后面有时间的话,我们尝试在我们的小应勤里面可以做一些小小样给大家,让大家感受一下,但这个这个flag不能立,因为这个有可能会导致那个小雨已经变得过于复杂。
大家现在本来就看不懂了,但改完之后大家可能就更看不懂了对吧,ok好,那就是今天的课程,那我们谢谢大家,下一节课呢我们会去讲那个这个大家很期待的什么呢,哎lumen和nana对吧。
这也是现在最前沿的渲染的两个技术,那同样的跟这节课一样,也是全面的,是高能,所以的话呢这个同学们这个做好这个被这个高能电到的准备,没关系对吧,我们都是一帮很勇敢的人好,那就是今天我课程的全部内容好。
最后谢谢大家。
21.动态全局光照和Lumen (Part 1) | GAMES104-现代游戏引擎:从入门到实践 - P1 - GAMES-Webinar - BV1oe411u7DJ
我们几乎是准备到最后的一秒啊,这节课的内容实在是太硬核了,然后硬核到我们现在都没有办法准备,上一期同学问我们的问题,那我而且很多知识都是新鲜出炉,哎反正就是各种的这个这个情况百出,待会儿给大家讲。
那我现在呢先给大家讲一下,我们这一期要讲的内容,去那个大家说我的脸都肿了,还准备这篇那个这这次这个talk对吧,然后我们就在中秋节对着月光,我们去讲一下lumen对吧,其实讲论我觉得讲的不过瘾。
我们要把整个全局光照给大家讲一讲,那首先的话呢诶我这不是我的第一,不好意思了,果然已经偷一局晕了,首先的话呢,第一个就是我们的课程组,给大家讲一些有意思的好消息,我们课程组准备了一些小卡片,然后呢。
我们会在小卡片里面给同学们写一些,我们的祝福的话,然后呢,以后大家如果能收到我们课程组的,这个那个t恤衫呐或者什么小礼物的时候,我们也会附送这个小卡片给大家,所有拿到这个卡片的。
就是我们games 4104课程的口号是什么,人均一个自研引擎,所以的话呢我觉得所有想做,想探索自研引擎的小伙伴,都是勇气和智慧的化身,所以的话呢我们也想用这个小卡片一起,共勉一下我们这个社区。
你的这个每一个人确实我们很需要鼓励啊,到现在为止的话,我们在行业里还被称为是异类好,那就所以呢这是我们课程组的一种,大家精神共鸣的表达,那么另外一个的话呢,就是告诉大家一个好消息。
我们的pico引擎的话呢终于上新了,我们刚刚实现了一个小小的gpu particle,然后就是觉得好玩,其实也没有什么特别的意思,就是说啊就是你看上面一个问号,我们的party落下来之后。
地上会形成这样的一个pattern,这个其其其其其实呢是一个怎么说呢,你可以认为叫屠龙之技吧,就是说在现代的这个3a游戏里面的话,这是个蛮常用的一个基础系统,但是的话呢。
就是如果作为一个就是休闲游戏的话,一般不会用到这么复杂的功能,但是我们就是觉得很好玩对吧,也让大家看一看,就是其实引擎这东西没有那么神秘,就什么高端的算法啊,其实你都可以实现的,所以啊这个东西的话。
我们好像已经check in到,我们的那个github上了,大家可以上面可以下载,那这个我我个人觉得还蛮好玩的,等我们的课程上完之后的话呢,我们会尽可能的就是抽时间。
跟同学们去讲一讲皮克的引擎的代码解读,就是欢迎大家关注我们的微信公众号,这样的话我们拉了一个微信的小社区,这样大家可以就在社区里面可以去讨论,然后后面时间允许的话呢,我这边也在想当然。
我得我得我得说服我们课程组的小伙伴,大家已经累得都不行了,然后如果我们还有一点残残留的力气的话,我们就那个持续的给那个pico里面,注入一些有趣的代码,其实我个人还觉得挺好玩的。
因为我自己在做自己引擎的时候,觉得哎呀那个代码太大了,维护起来太难了对吧,当然pico的话呢我就觉得很很简单,很轻便,可以很easily的加入各种各样,我们想要的feature。
所以的话希望那个整个pico,变成我们大家的一个playground,大家一起去享受这个过程,而且呢现在课程组check的东西多一点吧,到后面的话,我是希望我们社区里的小伙伴。
我们一起来check in,这样的话我们把这东西越做越好玩,反正就是个开源社区好,然后第三个的话呢就是我必须得highlight一下,我们的同学们实在是太有才了,就是我们改作业改到了越来越怎么说呢。
就是呃应该来讲的话,就是说作为一个就是真的很有意思,就是说作为一个其实还没有怎么功能搭完,的一个开源小引擎的话,大家已经开始很快的把它变得越来越像个,专业的游戏引擎了,就是大家你像这种材质的调整啊对吧。
这个做的还是很酷吧,这个已经有点像那个,就是专业的游戏引擎的样子了,然后呢,我觉得很酷啊,这个后面如果大家需要什么工具的话,千万不要不好意思,直接来问我们课程组,说不定我们做着做着就把皮克我做了一个。
真的能做成一个商业游戏引擎的,这样的一个商业游戏的这样一个小引擎,反正无所谓,只是开源引擎,大家随便用,所以啊我觉得挺好的,就是说实话讲了快有大半年的课程了,然后看到这些东西的时候,还是蛮有成就感的。
觉得嗯,我这这大半年我们同学们没有一起很辛苦,然后呢最后一个就是说啊,本来我们今天准备了三个社区问答的问,题的问题,但是呢我后面时间实在来不及了,就是准备这些问题怎么回答,我说这边我先狗头保命对吧。
我先挖一个坑,就是下一次下一次讲none night的时候呃,我争取这个把一回答那个社区两波问题,就这这次这一波和下一次的这一波,这样的话呢这个实在才能回得上来,然后最后告诉大家一个这个有趣的消息。
就是啊,马上因为我们上次说,我们哎我们又是职业的这个歌王,本来说这个30万播放量的时候呢,我们会做一个彩蛋视频,但是呢我们就割啊割啊割割,一直割到现在已经快五上播完了,谢谢大同学们对我们的支持啊。
真的没有想到,就是我们会播放量会有这么大,那我们呢就做了一个就是这个彩蛋视频,这个视频还蛮有意思的,大家明天那个敬请期待对吧,有可能你们想看到的一些小伙伴,还有一些你想看到的一些有趣的东西啊。
会在那个彩蛋视频里看到,那就留留了一个小小的惊喜,留给明天,那我们就言归正传,就讲我们今天的课程主题内容啊,就是动态的全局光照和lumen啊,对这个lumen对吧,这个大名鼎鼎。
但是为什么我们备课背背到最后已经背的,就是我觉得就是属于这种不断的跑题,就是这个就是我们也是跑题小王子,就是本来准备个len,但是我们却突然决定想跟大家讲g i了,确实是呃。
我大概记得是准备到上个周五的时候吧,上个周五晚上我就说哎呀,我我我要把lman讲清楚,我如果不把以前的那个ji讲一遍的话,好像陆妹是讲不清楚的,因为如果大家不去讲那个g i的话呢。
大家看到rom里面的东西,那个技术细节就是无比的多,然后呢大家也不知道这个细节,感觉从天上掉下来的,其实它因为你们的很多算法,很多思想,在之前的一些gi的那个方法里面,都会出现过。
所以它很像一个算法的集大成者,然后索性就一不做二不休,我就说算了吧,我们就这节课擅自未经大家的那个许可,我们就擅自修改了主题对吧,我们把那个决定把那个什么pgc啊,把那个就是那个啊还有什么啊。
match making那个the motion match啊,这些东西我们把它先放在一边,这个大不了我们看二零系列的课程,给大家补上什么地方,又挖了一个更大的坑,我们一零还没讲完呢。
我们就开始讲二零系列的这个坑了,那么我们呢就是在一零系列,最后两节课的话,我们就放飞自我,就讲两个核心技术,一个讲lumen,一个讲null,那么如果lumen采取这种,叫做把它历史渊源的讲清楚。
我们在讲这个算法的话,nana的大概率也可能是不只是讲nana,可能把这种class base的rendering啊,这种新的这个visibility buffer match。
这种新的mesh apply啊,也可能也会跟大家去提一下,所以的话呢,我们是第一次这么任性的去准备一节课,但是呢任性归任性啊,工作量其实非常的大,而且呢说实话呃卫生这节课准备到最后的,几乎是到最后的。
我想想啊,对上课之前最后两分钟截稿,然后我们十分钟预热就开始上线了,真的是新鲜出炉,这里面的很多知识点是我刚刚确认过,可能不到15分钟,那为什么会这样呢,因为真的是细节特别多。
很多时候我们被迫要扒开源码去看源码,所以呢我不能讲这节课是在给大家讲课,更像的是什么呢,就是王鑫师兄带着大家一起做paper reading,我不知道大家以前有没有,在高校里面参加过这种科研组啊。
就科研组以前有个活动,就是说,我们比如说会把今年或者某个系列的文章,同学们分头,然后呢我们一起做paper reading,大家读完文章跟大家去讲,那么preparing的特点是什么呢。
就是说其实我也是新学的,我也是心理解的,但是呢我愿意用我的眼睛,用我的脑子先把它消化一遍,然后再和大家分享,这样的话降低大家去理解这部分的工作量,所以papi的特点是什么,有些东西讲的可能不一定正确。
但没关系,我在尽我最大的可能性去讲这个东西,那么今天这节课呢第二个特点呢就是说啊,其实lumen的话,实际上无论是咱们虚幻自己啊,还有很多优秀的同学都做过一些分享,可能我们讲的结构会跟船长。
那个常规的结构不太一样,我们更多的是从全局光照的思路,然后呢把它的算法重新组织了一遍,就是我想尝试一下,就这样讲,是不是更容易让大家,就是说跳过那些非常非常琐碎的细节。
而更多的去理解它整个算法的整体结构,其实我为什么一上来会先讲那个gi的话,其实也是出于这个思路,也就是说你去理解gi最宏观的结构,你才能理解lin是怎么产生的,那你基于机关机y的这个基础的,方法论的话。
其实你就能理解,如果有那么多算法,它到底分成几大块,这也是我们今天这节课,我想尝试的一个方法,因为确实这个我觉得既然开讲了,我觉得还是希望大家讲的能明白吧,而且这是我们是一零系列的课程对吧。
很多同学以前的基础可能还是需要补充的,那我这时候正好把基础给大家补一下,但好那就废话不多说了,首先讲global nomination对吧,那这节课首先的话呢,我要把我的祖师爷再抬出来一下,对不对。
这个render equation,我记得我在第五讲的时候讲的rendering的,第一节课的时候,我就讲过,recreation,简直是这个hello,也不叫也不叫害。
我们不能说应该就应该是指导我们了,整整这个多少年了,已经35年了对吧,确实30 30 35年以上了,那么确实是这,这也是反映了这个科学家的了不起啊,就是它可以放在mental类的,把一些物理现象的本质。
用这么简单的数学语言表达出来,那我们今天做的几乎所有的rendering的工作,都在去满足这个render equation对吧,这就像爱因斯坦说出e等于mc平方好,大家都很开心,讲清楚了。
但是你真的把原子弹造出来,你会发现这个后面的挑战,技术的工程的各种问题就是让你难如登天,所以我们会用曼哈顿工程工程,但是最终原子弹是炸出来了对吧。
但是这个这个就是我们的render increation的话,到现在为止啊,这个核弹还是没有爆炸,因为其实直到今天,我们并没有任何的一款游戏引擎。
能够做到实时的fly render equation来做,这个真的是非常非常难,但是我们现在在做什么呢,我们在无限的逼近这件事,我们在一步一步的逼近它,而今天为什么给大家这么着重的去讲一下,lumen。
我个人认为就是说,它代表了我们在一个关键领域,就是gi这个领域正在这个非常明显的,这是一个significant mstone,就是一个非常重要的一个mstone,我们在逼近这件事。
ok那这件事情为什么这么难呢,它其实最核心的点就是说直线光照,当一个光源照向你的时候,那就是个点点光照着你,那是比较简单的,但是g i的意思就是说你四面八方来的光,和我表面的b r d方程要进行积分。
最后能形成我的这个最宏光的光照,这个里面这个复杂的积分过程,其实是非常麻烦的,因为大家想想在现在计算机里面,对于屏幕上将近十个变量的pixel对吧,如果你是8k,你如果那就会更高。
那么这个时候每一个像素,每个点,我要去采集四面八方的各种各样的,这个这个这个射过来的光线,算出来的值,这个计算量有多大,那这个想想大家想想是不是很吓人,那么更复杂的是什么呢,就是这个光从什么地方来对吧。
他不是说诶我在四面八方摆了无数个灯,其实灯在哪里,灯是无限多的,就是说为什么呢,因为当一个你只在这个场景里面,你只放了一个光源,但这一个光源它可以把周围的无数个,就是物体的无数个叫小面片全部照亮。
那每一个被照亮的小面片,它就是一个光源来照亮你,那同时呢这些小面片照亮其他的地方,它会再反弹回来,又会形成新的光源,那么你如果是一次反弹,就是single bus对吧,如果多次反弹就是multibs。
这个我记得在前面讲range的时候,给大家讲过这件事情,那么这样的一个case的话,实际上对random来讲就是极为复杂的,大家想想这个数学上就很复杂,那么最著名的这个g i的一个案例是什么。
就是我们的右手边这张图叫什么,canair box对吧,这是大名鼎鼎的kair box,大家想想多简单,一个正方形的q,左边是那个红色,右边是绿色,上面放了一个光源,注意啊,这个光源它不是点光源。
它是一个面光源,光源本身你可以把它分解成无限度,然后下面就放一个长条,那个长方块,一个一个方形块,哎,就这一个conor box,实际上你想把它做的跟光猝死一样,就会真的是难以非常的非常的难。
然后如果这东西还要real time,你就更痛苦,那么这里面为什么这个光学现象,大家仔细看什么复杂,你看到没有,这边光射到那边红色的墙反弹回来,就会有很多红光,红光就会照亮这个那边那个长方块的。
那个那个那个就是它的左侧面对吧,同样的你看到又靠右边的墙上,也看到绿色的这个这个这个light leaking嘛,就是这种光color的leaking,那么这样的一种很复杂的光影效果。
其实就是这个gi的效果,而gi的效果的话呢,在真实的游戏场景里面会更复杂,那为什么gi这么重要呢,因为我们这以游戏为例,如果游戏它只能处理deoliking的话,比如说以一个这个这个博物馆为例的话。
那你看到的环境就是这样了,那为什么我们的游戏你看的不是这样的,因为我们还会加上一些hack,我记得我在讲专顿时,我就讲过一个最简单的hack叫什么呢,叫那个啊,就叫ambion light对吧,环境光。
那其实这个对gi的一个hack,但是那个效果其实很一般,而真正的积压的话,它的效果是非常丰富和复杂的,而对于现代游戏来讲的话,就是我们很多时候,比如说诶一个一个岩洞,我给他打个洞对吧。
围着一个房间打开窗子,你看到光射进了,拉开窗帘,对不对,或者说在一个昏暗的晚上,几个人打着手电对吧等等,各种复杂的这种光学现象,其实都需要在游戏中模拟,当然这也是我们。
现在rendering已经进入了一个恐怖谷,就是说过去我们可以一眼看出来说诶,这是计算机生成的画面对吧,比如说我们在玩super maria的时代,我们不会要求他画的很真实。
你只要那个maria做得足够可爱就完事儿了,对不对,至少在过去10年以前,游戏里的画面我们还不敢,这个基于说我们逼近于真实,但是现代的商业游戏的话,其实已经越来越逼近于。
就是至少是达到一个动画片的这种质量,其实已经跟动画片质量差不多了吧,已经逼近了,然后呢我们还要逼近什么的,其实现在很多游戏的,如果你看他的过场动画的话,其实非常像手持相机拍出来的,这种纪实电影。
所以说在未来10年的话,就是我们rendering的核心的挑战其实之一吧,只能说之一其实是gi,因为你不上gi的话,它的整个画面看上去就是塑料的,就是这个cg的呃,呃不要黑cg啊,cg也是很了不起的。
就是就是说我们那个游戏的这种画面,但是呢你上了g i之后,如果你再加上pp r材质对吧,再加上那个就是更丰富的几何细节,确实现在游戏引擎所说出来的东西,有的时候能达到以假乱真了,就大家可能如果看一下。
有些现代的一些东西释放出来的话,确实是很震撼,所以整个return rendering行业,游戏行业,我们已经在非常非常逼近那个恐怖谷,而这个恐股到底有多少年,我们才能爬得出来的话。
那就要看我我背的是造化了,所以这也是今天我们讲这节课的重点,就是也是鼓舞大家,更多人冲上去去解决这个问题,那么其实呢这个就是gation,最复杂的问题是什么呢,就是积分就是你对光各种各样的积分对吧。
那么积分怎么办,哎这里面就有一个大名鼎鼎的蒙特卡罗,integration,蒙特卡罗积分对吧,他的想法其实非常简单,就是对一个函数对吧,你就是从a到b,那么你假设是一段的。
比如说我做rendering的时候,不就是在一个半球上进行积分嘛,好嘛我怎么办,我对你进行采样嘛,我加了50个点,哒哒哒哒哒对吧,我把你的每个点踩完之后加在一起诶,平均一下啊,这应该加在一起对吧。
some of the area,然后呢我就能知道你的这个光的多多少,对于cd来讲,不就是把所有的方向的这个光全部给,那个他的设定的每一个那个radiance,全部算出来,加在一起。
就是我最后的样子嘛对吧,这就是这就是蒙特卡洛积分的一个方法,那这个方法呢,其实是我们过去可以说是差不多啊,这几十年吧大家在做gi的话,一般的方法,因为其实从这个quation提出来,就是过去的几十年。
我们的图形学行业真的是前赴后继啊,就是在解决这个全局光照的问题,这个问题真的是很痛苦很痛苦,那么其实呢我们就有大名鼎的motor,color retracing对吧,这个方法其实呢思想非常的简单。
就是我从眼睛的地方,对于屏幕上每个像素对吧,我就可以射中一个物体,这个物体的话呢我就往它四面八方去投射,这个瑞什么意思,就是找这些方向,有没有那个射来的这个光光光,然后呢这光呢射出去又打中物体。
其他的方面在那个点呢,他又踩四面八方去问,然后呢这样的话你就看到什么一次半死,两次半次三次bs,大家发现没有,它的计算复杂度是怎么上去的,我相信聪明的同学,可能马上就能感觉出来了。
这就是一个指数级的扩张啊,对不对,所以说做single box的话还可以,我勉强能忍对吧,但是我要做multi bounce的话,四次bx,那基本上就是要死人的对吧,而且呢。
这里面还有一个很有趣的一个点是什么呢,就这里面这张图还没有讲清楚啊,实际上在场景里面,他的那个真正亮的地方,像康奈尔box这种整体基本照亮的case,实际上是很特殊的,在很多场景里面。
比如说一个硕大无比的房间,它真正亮的地方,可能就是天顶的窗子那一点,但是你整个房间就是被这个multi bounce的,这个光照亮了,这个时候你在进行三零的时候,你会经常发现一件什么事情呢。
比如设设了啊50根锐,但是只有极少数的锐,所以他根本没踩到光,唉这个事情真的是会发生的,所以说这种蒙特卡罗吹醒在过去的几十年,他的一生之敌是什么呢,哎就是采样,如果采样不好,你就会发现我因为是随机的嘛。
对不对,所以我没有办法保证,我的pixel a和pixel b形成的这个采样的结果,它在结果是连续的,我们就会产生大量的noise,对这个你想想看,就是过去我们就算用那个就是cg p u对吧。
一帧渲染几个小时,其实我们为什么要选那么慢,就是大家如果听说过,以前做动画电影的成本为什么特别高对吧,一帧可以炫一个小时,大家想多恐怖,而且还是一个那个还是一个farm,就是一个render fm。
就渲染农场,它就是这个原因,就是因为你要做这种蒙特卡洛retracing啊,这简直是丧心病狂啊,对然后呢,所以呢这个里面最痛苦的事情是什么,就是sampling,就是说这这个概念的同学们。
一定要建立起来,因为你去理解啊,这个lumen的所有的他的技术上,用的各种各样的hack,它的hack实在太多了,但是呢它核心解决的这个问题啊,其实就是怎么更好的300的问题对吧。
怎么有效的去分布这些3伏林的问题,那么好三零就是重要的k了,因为你会发现,比如对于蒙,基于蒙特卡罗贝斯的这种方法的话,一般来讲你的三炮里数量越多一定是越好,但是呢你不是越越多的话,系统的消耗会不会越大。
它就会越慢对吧,这里面给了个例子,就是比如说像这里面做一个蒙卡reacing,如果只有一根瑞,就是我们的左上角那个的话,你可以看到很明显的noise,然后呢它每一张图呢大概是从左到右。
从上到下都是double一下的对吧,那你到后面那个就是已经二的16次方,26次方多少,就是每一个点要射出去,大概64000根瑞的时候诶,你看着已经很smooth了对吧,但是这如果是single bs。
我觉得几分钟你也许能选得出来,但是如果是multi bs的话,我反正已经不知道需要多久才能,算得出来了,所以说其实如何,三零是这种所有的蒙特卡罗贝斯的这种gi,犯法的一个核心点,那三怎么做呢。
那最简单的方法是什么,这个呢其实是最好的一个一个方法对吧,一个一个一个最传统的一个方法,但是呢其实如果你的信号,哎你这样的三里呢,他除非密度很高,否则的话呢,它可能是对采样率并不是有效的利用。
比如说我想三破一个,在一个空间上的一个广场对吧,对我的光照积分,那我如果这个窗只有一个窗子,它是有量的,你在你采用的两根锐中间的夹,夹在这个空间的话,如果飞出10米之外,他那个采样线之间的夹角。
很可能就把一个天井的窗子整个漏掉了,所以这事呢就是说unix 3的一个问题对吧,那么在这里面,我们发现一个很有意思的东西,这里面就是数学概念,我就不展开讲了,哎这里面有一个很有趣的概念叫pdf对吧。
probability distribution function叫pdf,这个不是我们的打印的文档的pdf啊,它就是一个数学上的一个定义,就是说我如果对这个函数的分布,它哪些地方有这个强烈的信号来。
一定要,实际上我如果采样也按照这个probability,进行分布的话,那我其实就可以用尽可能少的采样,获得尽可能逼近于它,想要的就是那个那个真实值对吧,就比如说我的信号是这样的。
那么我的这个pdf的话呢,如果是下面的这样一个波峰波谷的话,这个可以在数学上证明啊,就是这种采样肯定是比uniform采样,就同样的三零read是呀,如果你真的是纯随机的话,它会更好地帮我求出它的积分。
这个数学上我就不展开了,但是呢这个概念其实也很简单,比如说我在做设定的时候对吧,我尽可能朝着光比较亮的地方,尽可能沿着我的法线的这个正对的地方,去多设一些采样的锐,那这样的话我可以用少量的锐。
就可以获得我想要的结果,对不对,那其实这个东西的话呢,在我们过去讲什么,无论是基于这个所有做解压的方法,比如说啊我让我回想一下这个地方,我可能会有一点不一定对。
比如说我们最早做radiosity复数度算法,基于retracing的算法对吧,monocle retracing对吧,比如说还有风投来品对吧,其实基本上所有的这种方法大家都会。
如果一个基础方法提出来之后,就会有无数的这个这个衍生的work,就是在讨论说怎么去做它的important,三零,在同样的计算时间下,或者说这个在同样的采样率下,我能达到一个更好的,更稳定的。
更smooth的效果,如果大家搜索过去十几年的这个,graphics paper的话,你会发现有大量的这样的工作存在,而且这些工作呢,这个实际上是有非常大的实战价值的,所以这个是我们去理解gi算法的。
一个核心的技术点好,那其实的话呢就是刚才已经讲到了,就说哎那我要是这个做running的时候,我的pdf怎么选呢,哎这里面就很简单了,比如说回想我们的render equation对吧。
那我们最好的这个pf是什么呢,哎大家想到了,如果我的反射,大部分我可以假设是debu面的话,但你的这个我对光的这个敏感度,是不是这个诶一基本满足一个deflop对吧,就是你越这个基本上是你靠近我。
normal的方向,我会敏感一点对吧,就扩散嘛,然后呢你如果从侧面射来的光的话,其实就算你光线很强,我的感应度其实也是一般对吧,那这个default loop是很正常的,那么如果更猛一点的话。
比如说你假设是这个啊,那这样的话我们就比较一个例子吧,进行这种广场的这个积分的话,你可以发现同样是256个,那个那个那个就是sampling per pixel s s p p,你会发现它有很多噪点。
但是呢如果我们按照cos loop去分布,我们的点就是靠近那个天顶的那个地方,我分布的稍微密一点对吧,靠近下面我分布的稍微稀疏一点诶,我这个时候采样的噪点数就会下降很多,同样是256个sp。
那么对于那种非常glossy的东西的话,打个比方啊,比如说像这种符合gg x x材质的,大家还记得我们在前面讲的时候,讲过gg s对吧,我们理解那个就是那个p b2 的时候,就讲过gg s g s。
我记得以前我给大家讲的,就是它的特点是什么,跟音响一样的,叫高频足够尖,足够高a但是呢低频音域足够宽对吧,这是jjs方程的一个特点,那么简单来讲,gg s它个loop呢。
会比cos会更加的sharp一点诶,那如果我基于normal,基于g s我去进行优化的话呢,实际上我可以就是能够抓住更好的,glossy的这种效果,但是实际上这个东西是一个很。
我觉得很specific的东西,它不具有普遍意义对吧,那么所以呢在这里面我们讲的这一part的话,就让大家理解,就是说gi这个东西,它的核心就是说你要去找到一个好的一个,sample方法。
用尽可能少的瑞去获取,一个来自于四面八方的光场,对你影响的,这样的一个一个一个一个一个一个,怎么说呢,一个表达,也就是说我对于每个屏幕上的每个像素点,就是这是一小配match,然后我需要知道说哎。
我的前上下左右光到底是怎么样呢,我就去我就发瑞去问,那这个问他讲究,就是说你要问在正确的地方问,在重要的地方,其实这里面讲的三里只讲了那个,你表面才知自己其实呢还有跟光有关系。
所以这是我们在过去啊算gi的时候,经常会想到的问题,遇到的问题好,这是我们的基础知识好,接下来呢我们就讲诶在这个real time rendering,我怎么用,为什么呢,前面我讲的这些gi啊。
michelle retracing啊,至少在呃很多年以来吧,他是一个研究了很多年的一套算法,以前都是离线算法,什么意思,都是说在cpu上跑,但是呢它基本上是说电影for,这个就是这些领域去应用。
因为大家在我们普遍的尝试,我认为就是g i的东西,播游戏的话实在是太废了对吧,那从大概十几年前就有一些非常厉害的,这个同志们就再去想一个问题了,就是说诶我们有没有可能把gi做成real time。
真的在游戏这样的场景里面能用起来,所以我个人去理解啊,就是说这实际上是lumen的这个算法,它的源头的思想,那我首先呢要讲一个老祖宗的算法,叫reflective shadow map rsm。
这是一个2005年的工作,这个工作,为什么我会放到今天的第一趴来讲呢,因为就是说我个人认为啊,他是这一系列的方法的,一个就是说开山鼻祖就让启发了很多人,让大家敢于想象说啊。
原来real time机啊我也是可以做的对吧,那么这个reflective shmap呢,它核心解决的问题是什么呢,就是说我怎么把光注入到这个场景里面去,诶这个问命题听起来有点抽象啊。
这其实确实是有点抽象,那么当然了,因为我们很多同学可能没有学过,这个系统的图形学,其实在图形里面我们算gr有两类方法,一类最经典的就是蒙特卡拉retracing,对吧,那我们就是从屏幕射光。
然后不停的放肆放肆放肆放肆,这种方法是一个很经典的方法,但其实呢还有一个很著名的方法,是这个叫ftmapping,我不知道大家有没有听说过这个词,这个词儿红糖产品还是一个很棒的方法,这如果我没记错的话。
也是一个在我们腾讯学的黄金年代吧,也是一个英雄辈出的那个年代拍的,我应该没记错,应该是拍的harry哈,去这个写的,他是stanford的一个大教授,然后呢他有一本书叫冯特map这么厚。
然后普通mp的思想非常有意思,他说你们以前都是从相机那边去看对吧,我看到一个物体,我看那个物体有多少东西能照亮他,对不对,他说不行,我要从光那边看,他说呢你们看到所有的东西啊。
实际上都是从光源射出来的光子对吧,然后呢打到了物体表面,不停的反弹反弹反弹,然后呢你人眼的collect收集到了这些光子,所以你们的retracing呢都是从眼睛倒着去搞,仔细想想,对不对。
那为什么我们不去政治搞呢,用光学的最基础的概念,就是从光的角度去看这个问题呢,所以风头外面呢就算我今天不展开,其实哎呀今天时间实在来不及了,如果按照我的性子的话。
我可能花两页给大家讲讲phone map的技术方法,但简单讲了红糖发病的算法是这样的,就是说我是cost无数的风头,然后呢到一定条件,它在那个光子就会停留在物体表面,然后你最后shaking的时候呢。
其实那就是他的那个radiance,就是每一个表面点上,它它的整个光的那种分布诶,你在做设定的时候,就在这些收gather,好的这些风头上面进行收集,进行插值,然后最后给出这个设定。
这个就是photo mapping的一个核心思想,大家如果有兴趣的话,可以查这个这个资料,其实我自己去看整个lumen的这一脉的工作,我个人觉得起始于rs呃,只起于l r3 m。
终于这个就是说lumen的话,其实蛮有一点封腾map的味道,当然我不是原作者对吧,所以我不能猜测揣测他心里在想什么,反正我作为一个外人去看他的时候,我觉得老哥,你这个想法好像跟风台外面的思路很像嘛。
好那就不展开了,因为现在讲起来太抽象了,那我们先讲什么叫reflective china,他的想法其实非常简单,它基于一个observation,就是说我们在选英语的时候。
我们是不是要选一只shadow map,对不对,那大家觉得china map和我们正常的渲染,最大的不同是什么,他是从我们正常的渲染是从相机视角去看,而shadomap呢实际上是什么呢。
是从光的视角去看的,所以说我在炫耀shut map的时候,我只取了一个depth,对不对,但是大家想想,如果我在shut up那个camera,那个那个那个那个那个位置的话,我把场景整个炫一遍。
如果我把它再沿着这个光设定一遍的话,你其实是不是得到了,如果这个世界上没有积压的话,只有deflighting的话,是不是所有被照亮的面都在那个map里面,对不对。
所以我个人是觉得那个reflective shadow map,这个词啊,其实是非常让大家confuse,因为shadow map你会觉得它是shadow吗,就是变黑的东西吗,其实他那个是什么呢。
他那个实际上是我觉得叫做呃啊mination map啊,radiance map什么都好,我觉得挺好的,就是他就是在相机位置,然后呢就是啊从光的位置,你看到的所有被照亮的表面,注意啊,从从光的角度。
如果光没有multbs的话,其实那张图里面看到的,如果你是一个一个spotlight,就是说是一个追星光的话,还真的就是所有被第一次照亮的表面,不会多一个像素,也不会少一个像素,大家仔细想想。
是不是这个道理,诶这个rs就是基于这个想法,那如果我现在知道,就是所有在空间中被照亮的点,以及它被照亮的第一次照亮的时候,它的亮度的时候,这个亮度它可以散射出来,对不对,散射出来,那我在渲染任何一个。
我眼睛看到的点的时候,我是不是把这些空间上的这些被照亮的点,诶,把它的散射出来的radiance全部收集一下,我是不是就可以照我的点了,大家想对不对,这个想法其实非常简单。
那所以呢这里面这张图呢是原作者的图,我个人觉得这个图画的,其实非常的confuse啊,我今天不好意思啊,今天我也是跟大家现学先买,大家权当我是这个教研组内paper reading的节奏。
就教练组内ping的时候,我们是赶黑原作者的,我们说原组的图画的很不好,如果公开课让我们不敢讲呢,因为我们怎么敢diss这个这个原作者呢,所以今天如果我什么地方讲错了,冒犯了大家,一定要原谅我。
比如说我就觉得这张图画的,非常的confused对吧,你其实明明要讲的是这个xp那一点,对下面那个被照亮的那x那一点的影响,你为什么还要画一个,就是这个x q那一点。
你还画一个y那个点就非常的confuse,真的我看的就是当时这个图,我看着就非常的晕,所以我就用加了两个篮筐放在这儿,就是说其实那个下面的公式呢,它讲的就是就是光射到了,而且那个图画的特别特别糟糕的。
一点是什么,我给大家讲的就是那个上面是一个光源,他结果在那给我画了个camera,我也想,我也特别想喷他对吧,你那是个光嘛对吧,camera应该是我这样看过,就算camera,当然如果我理解错了。
大家对吧,我记得那期年利奇讲到这一趴的时候,他有一趴讲那个四次方,他说要吃键盘,那我目前觉得我也可以,如果我这段讲错了,我可能也可以是键盘好不好,可以吧,就是我觉得那个上面那个画的相机。
那个位置实际上是一个光,当然他可能那个是一个探照灯头对吧,看着很魔性对对,反正如果换成我的话,我可能换个大灯泡在那边,那么那你这个光呢射到xp那一点对吧,根据xp的,就是那个那个那个那那一面被照亮的话。
它会有个法向,那这样的话我就可以算出来说,根据number是模型对吧,你沿着各个角落,你就有这个散射系数,对不对好,那我对于x这点来讲的话,我就可以把那边的那个radiance给接过来。
然后呢再给我自己进行shading,那我就知道了那个我的多少在这里面,注意,就是这个readings衰减呢,是按照距离的平方衰减,那么这里面有一个梗啊,就是令其讲到这一趴的时候。
他说哎呀这个东西一定是二次方,不应该是四次方对吧,我跟大家老实的交代啊,我昨天前两天我备课的时候,我也对着黑板敲了半天,我觉得这是平方,怎么可能是四次方呢,后来我们吵了大概五分钟之后。
十分钟之后突然一下子意识到,卧槽不对,因为它上面那个就是那个xp减x啊,还有x减x p,它没有规划,就是它实际上还是一个带长度的一个向量,所以它下面除四次方,本质上是做了个规划而已。
所以四方真的是对的是吧,确实很有意思,但是我觉得原作者真的有点坑人对吧对,因为因为确实你很容易就会晕掉,但是呢他这样写也是有道理的,可能对于从shader实现来讲是速度最快的。
这是r s m的一个核心想法,ok好,那就很简单了嘛,那我既然知道,就是说诶对于x这个点来讲的话,对我的第一个光源我就找到了xp对吧,那以此类推,我可不可以把reflection杀在web上的。
每一个点都去搂一遍对吧,把你根据你们的距离啊,根据你们的头像,我这个方向观察,实际上我做了一个积分,是不是就能得到我想要的点,诶这个方法就对了对吧,但这个方法呢有个问题是什么呢,太粗暴了。
因为你想啊我的rs 3 m对吧,我选的最少,比如说512x512,那也是将近有这个啊,将近有几10万个点了,对不对,那你怎么可能就是全每个像素,就是每个screen speed像素都做一遍呢。
诶他又提出了一个想法,说我呢搞一个空tracy对吧,他其实这个空缺他是个hack,他那个时候还没想明白这个事,他就说呢,诶我我去随机的按照各个方向去去打,然后呢,如果我打的近,打得远。
我实际上就意味着我这个空啊,再再扩散对吧,那对于扩散的空大的地方,我给的权重高一点,扩散空小一点,我给的权重小一点,其实这样的一个方法它实际上就能够完成,就有点像蒙特卡罗是睡醒一样的,我就对诶。
那我刚才讲的那个词叫billions of leslighthouse对吧,那无数个小光源进行一次随机采样,他自己的原文中,他说的400多个三浦基本够用了,而且他是要用破破的方法分布这个三步。
但是后面的人有很多更好的方法,但是呢他就是说啊,就能得到一个基本可以的一个结果,那么其实呢这里面one more step,就是说实际上它这个空tracing啊,在后面的工作中。
大家可以发现就是我可以对r3 m7 做me,做秘书的好处,就是说我可以直接去sampling,比较高密谱的这个rs,这样的话呢,我觉得一次性我能三破的这个空会更大,如果你是非常debus的面对吧。
但是如果你是非常gloss这边的话呢,诶我就去三破的密比较低的点,我就要在那个尖角的地方多采样,这个地方就不至于细节我不展开,但是确实是,作为一个2005年的工作的话,我觉得已经非常了不起了。
是啊你现在已经有17年了对吧,那么其实呢,这里面还有一个很有意思的思路,他说呢哎虽然说我只需要做400次采样,我就能得到first bounce的gi的效果,但是的话呢这个还是很费,那我怎么办呢。
我把屏幕用half pixel,就是我我每隔两个我做一次对吧,这个时或者是每个四个都可以,那么这个时候呢我就去进行sa建议呃,我在做做那个shading的时候。
因为我只是做那个低频的那个in indirect,innovation,借机光照,间接光照,大家都知道它是低频的,它不像直接光照那么高频诶,这个时候实际上呢。
我收集过了低频的那个intrination的话,实际上可以旁边的pixel都可以共用,这是也是一个非常了不起的发现啊,因为我们后面再讲的那个lumen的那个。
就是那个screen space的那个prop的时候,他为什么16x16就work了,其实他自己讲,我有个什么伟大的observation对吧,但其实从这个rs m时代,大家就已经发现了这件事情。
就是interstation,我可以用低频,就是在screen space屏幕空间,我可以很低频的去采样,我就可以去拿它,那么这个时候呢,他又提出了一个非常有益的思想,就是说诶我对于每一个渲染的像素。
我要去用这些更稀疏,采样的这些这个ation的时候呢,我发现一个问题,就是说如果我我的那个采样点跟我,比如说那个空间位置相差特别大,或者是法向朝向很不公,我位置很不共,面对吧,那我就想办法。
我认为这是个无效的一个这个差值,那我要把这个差值踢掉,他肯定自己遇到了很多artifact的,所以呢他发现这个问题,他他就把这些得不到有效差值的,这些pixel呢把它标出来,这些pixel呢量不大。
对于整个屏幕来讲,可能不到1%甚至1‰,哎这1‰的pixel,我呢啊对他进行这种一次完整的采样,其实这个方法非常的了不起,因为这个observation其实大家如果研究呃。
像lumen里面的很多差距上吧,它的核心思想就这样,就屏幕上我有100万个像素点对吧,那我用这种这个低密度的这个光的采样,然后呢插值到每一个像素点,对它进行光照的时候,大部分时候是work的。
但是架不住有些时候他work怎么办,其实这个思想源头的话,我在r semi就已经看到,这个是非常的有意思的一点好,那这个方法呢其实又简单,其实也是work,早很早以前,无论是girls war啊。
就是还有那个girls for war,还有uncharted for,这里面其实都已经实现了这个,所以你可以看到一些手电筒照的那诶,旁边就照亮的效果,因为r3 m真的能做这件事。
而且他的案例里面用的也是个spotlight对吧,我刚才黑了一下那张图,画的不好,说不定人家说我就是特别适合做手电筒,人家说的说不定说不定是对的,那其实呢我觉得就是为什么。
我这这这是我很个人情感化的表达,就是非常感谢r s m,因为我认为他是个非常inspiring的一个work,你看他这两个老哥是原作者的言论文对吧,那么我觉得他非常easy。
第二个的就是说他实际上第一次啊,这至少是我自己研究,我自己总结下来,我发现他可能是我找到了第一个工作,就是想到了用这个方法,把这个光子真的注入到这个世界了对吧,第二个的话呢。
诶他在那个这句话写的不一定对,就是我我今天我们团队又查了一下,觉得好像他没有想到用mmap的方法,就是那个rs m码的方法,去实现高效率的空contracing,然后呢还有一个很重要的思想,就是说诶。
我可以在low resolution的screen space,里面去采集这些这个间接光照,同时呢我在真正渲染的时候,我在这个低频采样里面进行插值,但是我加上一个error check。
而且他也研发了一些早期的err车的方法,能够保证就是尽量减少这种artifact,当然了他这里面其实有很多的局限呢,毕竟是个早期算法,所以呢它只能解决single bce啊对吧。
他而且他也不检测visibility对吧,它会假设就是说,因为他他去向那光采样的时候,他一步检测的时候,我这个光是不被被遮挡,所以它其实有很多的artifact,但是呢毫无疑问,我觉得r s m是一个。
非常具有启发性的一个工作,所以大家去理解一些基础的思路,都积压在记录的时候,这个思路这个文章的话是,大家一定要简单的去读一读的这个工作好,那么有了rs m这个东西的时候呢。
哎我们接下来就想我们该干点什么了对吧,那既然光子已经住进去了,那我们就要让这个光在这个世界里面,流动起来呢,这里面就介绍一个我个人认为是一个啊,怎么说呢,是一个非常inspiring idea。
就是非常启发我们的想法,但是个人觉得他的数学上,好像是有那么一丢丢问题的,这么一个工作叫lpv,叫light propagation volu,就是在空间中传递光的,这样的一个一个个的涡轮,对吧。
好哎这个这句话好像说的跟废话似的对吧,不好意思,我也不知道怎么去表达他,那么其实呢最早是在cg 3里面,就是c cpp 2009年,距离现在大概13年前,有一个这个c应该是cos去讲了。
他是怎么去做这件事情的,他的核心想法怎么样呢,哎这地方我要倒一下我师弟的图了,闫妮琪的这个图,我觉得这个图画的非常的灵魂,我很喜欢我,我怀疑他可能是用格子纸画的,所以天然就具有这个vox lize的。
这个这个东西对吧,它就是世界,我把世界分成无数的格子,然后呢当我的光就照到这每个格子里面,格子里面如果有物体的话,我的这些光就会在里面形成bouncing,就会形成一样的ance的这个radiance。
这样的分布对吧,当我去设定任何一个点的时候,这个这个它其中纸张看过去的话,应该是camera space,诶,我camera不是任何一个点的话,我就可以去把这些wallet,里面的东西给它取出来对吧。
这张图就是一个非常灵魂的一个表达,确实是就是说想象一下我的空间,把它分成无数个大格子对吧,整个场景扔进去,光的射进去,那其实对于所有物体的地方的话,这些东西就会形成一个诶。
光的这个四面八方散射的这种叫reading,你可以认为它是个cube吧,也不是cube,说错了,是一个是个sfile,就是一个球面分布的一个函数对吧,这个函数的话呢,实际上你的整个东西被照亮。
就是基于这个球面分布的函数,那这个时候我怎么去做这种这个light,这种multipx啊,这些所有的效果呢,诶他老人家有一个很核心的东西,其实这个算法本身很长了对吧,很长,我觉得你不长个。
因为今天重点要讲ler,所以哦对跟大家声明一下,就是今天我讲的所有的在lumen之前的算法,都是点到为止,并不会讲它的细节,其实每个算法后面都有很多的细节,那为什么呢,因为我要每个算法都讲的很细的话。
那差不多这本身可以讲大概呃六七节课,没有什么太大的问题对吧,但是呢今天我们只有一节课的时间,我们讲len,所以时间已经快接近一个小时了,所以我要抓紧时间,那么这里面的话呢,我觉得最关键的是这个诶。
他的light怎么去propagation,在这一个个的vocsol里面怎么去propagation,他的想法其实非常的淳朴啊,就是说他这个radiance,照到这些像素里面之后,你不表现物体对吧。
我就把在这个worko里面的话,这个这个这个就是说四面八方的,这个反射的四面方的radio,我把它全部这个三骂就合到一起,那怎么合呢,这个是个球面的分布函数对吧,你怎么和这很麻烦,对不对。
你你多少个采样点才合适呢,诶他突然想到一个数学工具了,我为什么不用ssh呢,是发红外x对吧,这个时候伟大了,surf红帽x就起了作用,为什么呢,因为你无才,无论你采集无数个这个表面上的点。
其实它的这个贡献,其实都可以用加权累积在一起,他最后就可以用一个s去表达这个函数,就特别的好,所以呢我就得到了一个在这个worker里面,你的radiance的一个在空间上的分布场,那我有任何一个物体。
我想设定用它来shading的时候,实际上用这个s就可以拿得到好,那这个时候其实对于每个worker,我就可以拿到它内部光照的s h对吧,那实际上的话呢,哎我假设有表面上的这些worko的话。
我其实在空间上可以向无数个voxel,去扩散,这个扩散的话呢,他的想法就是哎我都是通过一个编码,我从左边出去对吧,我我就也以前s h的这一部分,我推到一套数学方程,我就传到另外一面去了,然后呢。
这种扩散呢本身呢有一个词叫propagation啊,这张图特别的迷啊,就是说就是我当时的问题就是诶,你这个radiance扩散出去之后,你这个box one内部还有没有readers。
如果你内部的radiance还在的话,那不好意思,你的能量是不守恒的对吧,你的radiance如果不在那个地方就是青黑啊,那为什么你们还能被照亮,你看到他最后那个propagation的感觉。
像是一种扩散的效果对吧,它甚至clan我可以做multi bs,但是呢我们自己在做数学推导的时候,我们会发现这个好像感觉,你这个光的扩散的这个这个速度,扩散的范围跟你做了多少次,你都是有关。
这种感觉就像是说呃,光是由有限速度,在各个这个voxel里面去传递的,所以所以其实我们在找原文啊,最后发现就是说lpv的方法,就是感觉它不太符合物理学原理,实话实说,然后呢,第二个的话呢就是说呃。
这里面有很多的hack,很多的细节,我们现在想不清楚,但是呢就是说他作为一个17年前的一个,老的算法呃,呃十大概15年前的老算法,我觉得13年前,我觉得他肯定有他自己的想法,因为为什么提这个算法呢。
我觉得最核心的就是他是第一个想到说,我把空间进行workout的划分,在每个worker里面去,cash这个radiance是怎么分布的,而且他想到说把这东西扔到s 10去了。
因为扔到s 10有很多好处,大家在前面讲到那个p p2 的时候,讲的l那个那个那个那个那个来image,baib 2的image barendering时候,大家就会发现。
就是说诶我球面上来了个天光对吧,我把它变成s h,就可以用我的这个b2 df进行卷积了,对不对,所以说这些东西呢,我认为都是他很好的一个工作,但是这个工作呢我个人觉得就是说太老了。
而且很多东西没有讲的特别清楚,反正我是没有讲明白,如果没有想明白,如果同学们有人真的看懂了,而且呢真的能彻底的理解它的物理学,意义的话,也希望同学们跟我们分享一下,反正呃我个人觉得呢。
它这个东西呢没有完全搞明白,不太影响就是对norman的理解,而在这之后呢,其实大家的方法就会更加的科学了,那这里面的话就会提到一个大名鼎鼎的,叫s v o g i了对吧。
这个呢是nvidia同学们都做的工作,这个老哥的想法呢其实非常的淳朴,就是说ok如果你对空间进行voxel划分,对吧,你分得多粗呢,分的多细呢对吧,你分的太粗,那你其实不能够准确地表达。
这个世界里面的光的这个分布,那你要分得太细,那我就有无数的worko诶,但是我们很自然的就知道,就是说其实呢你很多时候空间里面是空的,是没有worker的,而且很多时候worker呢你只需要存它的表面。
里面的东西,你看不见,你也不需要对吧,诶他们就提了一个方法,说我能不能用硬件的叫保守观赏画,保守观上听上去很高大上啊,但是你简单理解它,就是说对于那种很小的三角形,对于很薄的三角形。
它至少能保证诶你有一个pixel在那边,那你你对这个三角形进行三个方向照,照片的时候,把这个pixel这个三方向投影,你是不是就能得到他的works的表达,对不对,那好保守,光栅化。
它就能保证就是说这个再薄再小的三角形,它也能够works alize,这个其实在lumen里面啊,我想看他在莫斯沃克斯莱先生的时候,是用了这个技术的,对,好哦,然后呢哦他没有追求。
他用了另外一个就不展开,不重要,重要的是这个诶,那这样的话,我就把所有的surface的worko全部都收集起来,这个手看上去是不是特别的细腻,特别的漂亮,那我要表达这个空间就会非常的大,对不对。
那很自然而然的嘛,凡事不许怎么上树嘛,我们就上了一个诶八叉树对吧,为什么是八叉树,大家知道空间上我的每一个维度上的二分,那在空间上的就是二的三次方,八八次分对吧,那我就是里面没有物体。
我就分得特别特别的粗,直到分到有物体,这时候我对空间的形成的表达,但实际上你们去看原文的实现的时候啊,是非常复杂的,对于每一个节点,它不仅存了自己,它还存了周围的三个邻居啊,就是左边一个右边一个。
上面一个,下面就是就是周围,就把原来1x1变成3x3,为什么呢,因为他很多时候要做filtering的时候,诶,他做bina trainia templation的时候,我需要我的零。
所以那个数据结构是惊人的复杂,那这件事情的话,我们我们最近在整备课的时候,我们就努力的想找到它的源代码,但是很遗憾的是,我们这个信息渠道不够充沛,我们没有找到源代码,我们很关注的一些细节。
所以啊这个s v o g i的话,实际上mad给出了一个demo之后,其实就没有太多的这些就是细节的表达了,但是呢,用这种这个这个ospark box tra的这个思想,其实是一个非常啊怎么说呢。
非常经典的一个思路,因为大家在前面学到我们的引擎,running man的时候,好多次,很多地方大家都看到,用了一个就是稀疏的八叉树,对不对,就是基本上你只要空间结构表达,这个就是一个很自然的想法,嗯。
他所以我理解他们就是真的是,把这个很自然的淳朴的想法,在这个就是十几年,现在硬件上硬生生的把它实现了一遍,如果我没记错的话,一个工作,或者11年12年左右的一个工作,差不多现在有10年左右对吧。
那个时候我们的computer shor啊,这些东西还不算特别成熟,刚刚开始那个时代,那在这里面的话,还有一个非常有意思的想法,这个想法其实对后面的很多工作,影响特别大,就是说我去这个时候。
当我去设定任何一个物体的表面的时候,诶,我实际上不是一个在球面上的一个积分吗,那我怎么样呢,我采样呗,对不对,我采样的话,如果我只是采样一根锐的话,那你可能要采样几百根瑞,才能达到理想的效果,对不对。
那他的想法是什么呢,诶第一个我根据我的法向啊,根据这些东西,我可以猜出来说哪些地方对我比较重要,然后我想我重要的地方呢踩的不是一个锐,是一根空好,那这个空的特点就是一个一个空的,英文的意思就是圆锥。
它这个这个这个圆锥体啊展开的时候,它实际上在worko那个表达里面,是不是,他到后来会扫到越来越大颗粒的voxel,对不对,那你这个worko对空间的表达,是一个树状结构,就越往这个数越靠近根节点的话。
它每个我所表达的空间区域就越大,所以你那个时候你只要取到一个worker的值,实际上可能就是一个很大面积的,这个radiance的一个和对吧,是他的一个平均,所以这是个非常巧妙的思想。
就是你用这个思想的话呢,你实际上就可以反向的就可以查到,就是说你的光照到底是什么东西,所以这个思想实际上的话呢,在这个我觉得是s u g i里面,大家最早提出来的,我认为这思想其实非常的好。
因为你只要对空间进行hierarchy的这种,works lize的表达的话,你就有这样一个优势,就像我刚才讲的r s m对吧,它是那个光光,你可以认为它是对空间的光的一个表达。
那么你对它进行一层一层的密p的话,你实际上也能大概能实现这样的,一个效果啊,23m行不行哦,对他不行,他那个因为他每个pixel其实表达的是,在空间上可能距离很远的这个点。
ok但是worklife的表达确实有这个好处,所以这个是一个contracing,那么其实呢很遗憾s b u g i的话,很多细节我们查不到对吧,但是也不重要,因为实际上没有人在用这种算法,原因很简单。
那个spark spark archittree那个数据结构啊,在gpu上表达非常的复杂,那那一页被我去掉了,它其实是什么,一层index指向另外一层index,而且你指向的每个node的话。
他存的不是自己的数据,它是从它前后左右,上下邻居的数据全存一遍对吧,然后的话一层层要保证,这个数据都是一致的,怎么去对他们进行mini map场景更新了,怎么办啊,一堆的hack的细节。
但是呢这是所有的这个这个所有的hack,当v x积压来了之后,一切都被变成了浮云,为什么呢,大家突然发现一件事情,我为什么要这么麻烦呢,对吧,我为什么要把整个场景做进去呢,对于一个gr来讲。
对于我来说最重要的事情是什么,不就是我眼睛看到的这一片区域吗,对不对,那我眼睛看到的这片区域丢了呀,最重要的是什么呢,离我近处的区域啊,远处的远处的虚拟机还重不重要重要。
但是呢我不需要对它进行很高精度的采样,诶,这个时候我们传统的clip map的思想就来了,这里面我小小吐槽一下,就是那个clip map这个词,我觉得中文翻译其实挺糟糕的,叫什么剪切剪切图剪切面。
其实clip这个词英文的原意是什么,是回形针,所以可以map在我的理解中,它就像你一沓文件对吧,比如说我用一个回形针把它别到一起,它就形成了一就随手拿起来就是一组对吧。
但是这个中文翻译我也想好怎么翻译啊,但是确实他的那个中文翻译,确实让大家很confused,其实clip map的意思更像是说诶,我这些图最大的一张图1/2尺寸,1/4存这些东西。
我用回形针把它全叠到一起,你这一拎对吧,最上一层只有一张明星,只有一张名片那么大,最下面那张可能是张a4 纸那么大,诶你这个一拿就走,这感觉是不是很形象对吧。
其实那我对空间的workalize的表达的话,实际上也可以用clip map的这个方法,那很简单嘛,就是说离我相机近的地方,比如说方言呃,50米,30米啊,方圆50米对吧。
我用很密的这个这个vocal去表达,离我远一点的话呢,哎我粗一点,比如说半径这个最近的地方,比如半径0。5米对吧啊,每个vocal 0。5米远的地方呢就远一点,每个下一层是每个vox一米。
我就能表达50米对吧,再远一点是什么2米,我就能表达1百米,再再下一层的话,每一个是比如说这个这个4米对吧,我现在表达这个1百米范围,一般来讲做到12百米就差不多了对吧,既然很多时候。
你假设就是你在这个人的这个位置的话,这么远就可以了,那这样的话,我就不要这么费劲巴拉的去构建一个,sparse稀疏的ocitra结构,因为大家知道,稀疏的oxy结构是非常非常复杂,很难管理的。
那这样的话,我其实还是构建了一个树状结构,只是这个树状结构的话呢,是一个非常好的view dependent destiny distribution,所以vx jr为什么最后在这一这一盘里面。
几乎是这个碾压性的取代了suv g啊,也就是个道理,因为他对gpu更加的friendly,而且实现起来更加的清晰明确简单,那么这个时候呢,哎这里面要讲一个细节,就是那场景里面你相机总是在动,怎么办对吧。
那我怎么办呢,哎我这个这个clip map的话,我总是要更新的,这里面讲了一个细节,就是说就是那个可循环的这个uv,其实非常简单,就是说我设置你的宽度之后,我实际上在存储的地方是拿你的空间的xyz。
记住是空间的xy啊,size的x y,这样的好处是什么呢,当你的相机发生了变化之后,我的那个原来做的那些数据啊,它其实不用在内存中更新速速更新位置,也就是说,无论你是在内存还是应该主要是在显存。
在gpu中,你只要去覆盖掉另外那边的那个数据,实际上你做三破零的时候,你的数据是不会发生错误的,这是一个小的trick,但这个trick实际上在lm里面,它很多地方都用到了这样的一个trick。
这样的好处就是说但这电影给大家讲,就是如果我们要做这个,比如说啊,比如说virtual texture呀,很大的世界的空间的表达,我我我今天要做个cash结构对吧,跟我的没有dependent。
我的相机就随着我一直在动对吧,那这个数据在空间上uv怎么去packing呢,用这个思路是最好的,为什么呢,因为你每次只需要更新更新他边上那些数,据,边上那些数据呢,它其实是反向的,又写回来了。
而不需要把中间那些没有,没有必要更新的数据重新给它一个位置,因为大家知道在内存中,你做一次memory copy其实是很慢的,在显存中呢,你去牵引一下那个速度可能就更慢,为什么显存那个就是gpu。
你还得等一个下一个更新周期过来,那个就很麻烦了对吧,这里面我们就不中奖了,重点讲了,ok好,那实际上的话,这样的话我对整个空间就有一个workless,这个这样从近处到密,远处到稀疏的表达。
大家看这个图里面进出,他的vocal明显就细一点,远处呢粗一点诶,但是你有没有发现,好像远处也没有显得特别粗,对不对,诶,你有这样的一个视差,感觉就是对了,为什么呢。
因为远处它实际上voxel是越来越大,但是呢它基本上保证在screen space,给你感觉没有那么特别明显的变化,实际上它已经离你很远了,接下来我们看到月亮和太阳对吧,月亮和太阳看起来一般大。
但是大家知道,月亮和太阳正好直径相差400倍,距离相差400倍,所以你看上去就是一样的对吧,就科普一个天文学的小知识啊,好所以呢这个就是空间上的,你可以看到这样的一个vocal的,这样的一个表达。
ok好,那接下来就很简单了,就是说这里面是一个细节,就是说其实每个vocal啊,它不是纯黑或者纯白,就是能透光还是不透光,实际上呢它每一个workle,实际上是有一个opie。
就是你光从这边射过去的时候,因为你的worker毕竟是一个,1米乘1米的一个大方格对吧,你把一个mesh放进去,你很可能光透了多少,透了55%对吧,挡住了45%,对不对,这个时候其实你要沿着它各个方向。
算出它的opposity,算出它的那个就是说不透明度,为什么呢,当我积积分一个点的光照的时候,我在近处的voxl,可能近处就有vocal里有东西,但是呢它实际上里面可能只放了一只小,班里对吧。
他只挡住了这龌龊的,可能是1/3的地方,好你另外2/3实际上是继续要往后走,我当时我也讲的就是,其实这个就是基于这个vocal的,这个这个方法的话呢,你的沿着这条光路啊,一个空tracing下去的话。
它是一个一层层叠加的过程,不像大家想到的时候,我hit一个box,我就停在了,其实不是的,它是一个有半透明效果的,所以呢这里面你在一开始build这个,就是voxel的时候。
你实际上是有一个叫a pity的词,但注意呢这个pcity的人比较复杂,就是说他基本上每个方向,应该是三个方向都要算一遍,就是从左右看诶,他可能透了30%对吧,从上下看透了70%。
从这个这个前后看可能透了50%,它是不一样的,因为大家想三维空间的一个形体嘛对吧,所以这个opacity你可以理解成,就是它的这个阿尔法好,那这个时候呢。
这个地方就是我们把一个directional coverage,展现出来,大家可以看到,还有很多半透的或者灰色的取值,对于一个场景而言好,那这个时候呢我们的light用r s m。
就是用那个就是那个reflex map注入进去,那这个时候呢,我们在每一个表面的vocal上,都去收集他的radiance,然后这个时候呢,我这这是我们第一次光照下去。
大家可以看到这些被照亮的worko,这里面为什么只有这些点会亮的,就只有这些点他是接受到了直接光照,其他的点看不到,为什么呢,因为它全部被挡在shadow里面去了,而这个时候诶。
我们对于每个屏幕上的像素对吧,我们就进行这种空吹ing,但是这个里面你可以发布,你可以那个热搜是不用全屏幕,也可以,因为在那个rs原始方法大家也知道。
就是说他不用for resolution injure lighting,那个太废了,但没关系,我们就进行控,那么对于非常低,因为我屏幕上每个像素点,我知道你的roughness对不对。
对于你非常diffuse的表面,我怎么办呢,哎我就沿着那个normal的normal为主轴,408354面八方五的扫一扫对吧,那么如果你表面是非常speaker怎么办。
哎我就沿着那个反射方向反射向怎么算,很简单,你的normal拿到了,我眼睛看到了,对不对,我就开始沿着大概演的那个spark方向,去去搜一下,如果你那个地方是非常非常细的。
这个speker的这个那个就是呃,非常非常光滑的表面,怎么办,我不仅沿着只沿着那个反射方向,去取那个间接光照,我还把那个空变得非常的细,我这样就穿过去对吧,所以这个时候呢。
基于因为你的vocalist的表达,它是一层层的嘛,实际上我就可以就像刚才讲s p o g i,你们的思路一样的,就是我在这个clip map的不同的层,我就可以模拟这样的一个空缺性的结果。
刚才讲到一个细节是什么,就是他即使你这根瑞,因为我我在retracing的时候呢,我实际上只能找那个中间那根线去走,对不对,我现在打到了一个vocal,我并不是因为打到vocal我就停了。
那个vocal有直,但是呢如果他的这个opacity沿着这个方向,不唯一的话,说明什么,我这个光含着透露过去,对不对,就是那边的光还能透露过来,反过来了啊,那这时候怎么办,我再继续往后走,越往上走。
我的面积越大的时候,诶,我就取那个clip map里面更高的,mp的那个worko,这样我一次性就能覆盖很大的区域,那这些那个他的阿尔法呢就会不断的变低,对吧,就像我们这个东西很像什么呢,很像一个。
就大家知道,那个我们在绘制transparency的时候啊,就是大家都知道就是那个啊会染绘制透明,我记得我在渲染那一刻跟大家讲过,就是其实是现代图形学里面,很难的一个问题,return对吧。
我们这么多年一直在研究一个叫order,indepenindependent transparency,但是so far好像大家也没搞出来对吧很难,那么实际上的话呢,如果场景里的透明物。
我们都排好了序的话呢,那其实我们是知道怎么弄的,那同样的,就是对于这个就是空吹型来讲的话,我们沿着voxel就一路哒哒哒哒哒哒上去,这个这个透明物之间是被排好序的,所以我们把它阿尔法就不停的垒成垒成。
垒成垒成,但是呢当你的阿尔法小于某个阈值的时候,对吧,很接近于零的时候,我们就说哎你反正已经不透明了算了,我们就不往后走了,所以这样能让我们的算法更快一点。
所以呢它实际上就是along the read pass啊,每个voxl反射出来发射出来的radiance,然后呢把它opposity呢进行这个累积,直到这个满足我的那个阶段的阈值。
所以这个东西呢其实挺重要的,ok好,那其实基于这个东西呢,我们就能得到一个a,非常不错的一个ci的效果,所以y x r实际上是一个,我个人认为是个蛮好的一个算法,实际上它也被实装了。
我记得虚幻的有一代里面有一个插件,就可以实现vx t i,我我对这种所有能够实装的算法,我都是比较敬畏的,因为实际上你真的不敢把一个算法放出来,让大家在各种场景里测试的话,说明它本身的效率性能。
而且我个人觉得vx i的话,他对s v gi的话是一个呃取代性的,这个地位,也就是说你有了vcr的话,我个人觉得没有必要再去搞s b u g啊,那么复杂的一套东西了对吧。
我个人天生对复杂性系统我就比较抵触的,因为工程本身已经够难了对好,那么其实呢vx呢其实也有很多的问题了,比如说就是说唉实际上这种contracing啊,我们把这个opie累积。
它是一个approximation对吧,那对于比如像这个case里面啊,三角形它在那个vocal里面,他只挡了部分的阿尔法对吧,那个黄色的方块它也挡了部分阿法,虽然他两个阿尔法如果按照乘法累积的话。
它肯定不为零,对不对,但是在实际上光路上,如果你想象那是个平面啊,假假设不是三维平面,简单一点,它实际上那个里面的话,光已经是后面的vocal已经彻底无效了,但是呢我们没有办法进行有效的阻挡。
这个呢其实就是我们看到的特点是什么呢,就是light leaking,光被漏掉了,特别是对于那种啊一些很薄,还有一些很薄的物体,它已经小于我最放的那个vocal,但是还有可能它本身不是啊很细。
但是他距离我比较远,他只能用那个sparse的那个worko,去表达的时候,就是比较那个开lol worker表达的时候,光都会被漏过去,但这个所有的积压算法对吧,特别是早期的你都会遇到这个问题。
这个问题其实是我们全局十roll time的节,哀的一生之敌,真的这真的是我们的一生之敌,我并不认为luna完全解决了这个问题,ok好,所以呢这就是那个vcr的一个问题,那么讲到这儿的话呢。
基本上就是我们对空间的radiance啊,进行以vocal为单位,就是以一个个体数为单位,进行采样的思路对吧,到vx g i的话,我认为是这个这这一脉思路的集大成者,对吧,但是大家都是用了什么呢。
哎都是用了r s m对吧,就是这个这个就是把风筒,把光子注入到了我们的世界里面去,然后呢你用voxel对吧,无论是这个平铺直叙的voxel,还是这个就是hierarchy worko,来收集这些这个光。
注意这里面有个细节,就是所有的hierarchy base works的方法呢,它的一般都是做这种最fine level的vocal的,这个radiance的采集,采集完之后呢。
它上面的密封呢都是用密码一层层上去的,这样的好处,就是说会减少一些不必要的计算量,而且这样的话它的数据其实会更加的准确,而且呢它相当于做了一次free,所以这个结果看上去会更加的我刚才讲了,g i的话。
一做不好就会有很多的这种噪音的问题,好那这是一my思路,另外一门思路呢也是最近刚刚出来的,就是reen space los g i哎,这个screen space这个词就非常的好对吧。
我记得我们在讲rning的时候,讲过a s s s a o对吧,屏幕空间的那个a o怎么算对吧,这个这个ss reflection screen space的反射。
screen space gi的他的想法其实非常的淳朴,真的淳朴到极点了,这个文章呢这个工作呢最早是2015年,由寒霜first bad在那个gdc上,就是说这样的一个一个工作。
那这个你现在大概7年前了对吧,它的方法其实是非常非常的巧妙,原始思想很简单,就是说我们看到一个场景对吧,上面红色框框的,就是我直接渲染出来的东西,那我去渲染它的indirect lighting的时候。
假设你的表面非常的mirror的话,是不是只要在spring里面把它反一下,这些数据我就可以用了,对不对,所以我们在做spring space reflection的时候。
就是用了这么淳朴的一个思想对吧,那好如果我们只设一根锐,我们能踩到它的这个reflection对吧,它的它的这个就是镜面反射的话,mirror flection的话,那我可不可以设多跟瑞。
把这些渲染好的这些这些像素点,作为我的小光源,作为我全局光的小光源呢对吧,s s g i就是这么一个很淳朴的想法,非常淳朴对吧,那其实非常简单,就是说当我要设定一个这样的一个,黄色的点对吧。
那我假设屏幕也选好了之后,我在我知道你的normal对吧,我知道我的相机的方向,我就跟着normal,沿着相机那个反过来那个方向,我去反发散的设一些锐,然后呢,这些瑞呢就在我的rs里面去找一个空间上。
位置正确的点,那个点是亮的好,那就成我的闪光源对吧,那个点如果是红色,我就我知道了,我获得了一些红色的瑞典,那个点是个绿色,我就收到了一个绿色的瑞典,这样的话我就可以直接用屏幕空间的数据。
进行这样的一个这个indirect lighting,你想大家想想这个想法是不是很淳朴对吧,哎这个想法实际上呢他真的work,就是说那好,那我怎么在screen space里面进行这个这个。
这个进行这个remarching,就是recasting呢,它这里面一个最简单的做法是什么呢,就是remarching,大家如果听过我们前面节课,讲那个s s l的话,其实就知道这个方法其实很简单对吧。
就是我从那个一个点出发对吧,我就开始设一个锐下去,这个r呢我就一路走走走,找一个虚拟的间隔对吧,那我只要找到有一点,他的depth比我那个dex更靠前,说明什么,说明我被挡住了。
那这个时候呢我就认为我找到了一个焦点,那么其实呢这个就是fixed,那这个方法的话它是work,但是他要求那个间距特别特别的密,那这样的话是不是很慢,这里面就有一个很巧妙的算法。
这个算法我需要给大家简单讲一讲,就是大家知道我们在讲shadow的时候,讲到那个action的时候,讲到那个就是遮挡的时候,讲到一个东西叫hiit,什么叫hz呢,就是说呃是gpu提供了一个算法。
当然我们可以自己实现,那么在dx 12里面,很可能a啊应该是a p i就已经支持了,就是我在渲染的时候得到一个z8 分,它会自动的把我z8 法做成一层层的密码,但是呢它有个特点。
就是说每一个上一层密布里面的这个点,他在下一次你不对应了四个点吗,那我在这四个点的depth里面,我选取那个最小值,就是离我最近的那个值,也就是说最突出的那个点对吧,你可以认为就是说我一个瑞。
如果给你的孩子就给你开了,我的这个密度那个不相交的话,那我一定是不跟你相交了,但是呢如果我跟你相交了,那我有概率给你更细的某一个几何相交,所以它实际上把这个debuffer做成了一个。
hierarchy的结构,然后呢在数学上它又保证了,就是说你你的那个,就是你你的每一层higher的那个z啊,它一定是下面的那个真正的那个z的,诶,这个就是一个就有点像它的bounding样的。
就是每一层z就是shine,每每一层z都是他下一层z的那个bd对吧,那么好,当我有了hi的时候,我想做remarching呢,诶就比较简单了,我呢先往外走一格,发现我没有焦点,那我怎么办呢。
我往hiit那一层往上跑一层,这样的话我做一次testing的时候呢,我就相当于走了两格对吧,这个时候如果我发现还没有焦点,我就开始胆子更大了,胆子更肥了,我在wp 8再往上走一层。
我再做一次testing的话,我相当于做到了四个对吧,好诶,这个时候假设我不小心发现我hit了something了,对不对,但是呢刚才讲了hz呢,比如像这里面已经到了mp 2了对吧。
我就知道他可能交到了mp 1,或者p0 的某个点叫到哪个点,我不知道,那好我再把这个把这个锐呢,我再往回退一下,哎我都推到mp一上去,我发现诶好像跟mp一的那个东西有个交点,好吧。
那我再往下走就能回到密林的时候,诶,我这时候发现我的焦点实际上是在下面,所以这个方法大家仔细看啊,采样更复杂,但是大家会发现,我对整个screen space depth。
做了一个high的这样一个hierarchy的话,其实我每一次recasting,它理论上讲你就算把整个屏幕走一遍,它的复杂度是log 2的,也就是我们以前最喜欢的叫log的复杂度,对吧。
也就是说你就算是1024啊对吧,更高的分辨率,我可能十几步就走到了,如果你用uniform remarching,可能要走五六十步才能做到,而且它可以在很短的时间,你跨一个很大的距离。
所以这个东西的话呢,这个方法其实很有用的,它有的地方也用了这个算法,所以这个地方的话呢,也是一个很好的一个技巧,就是大家以后在screen space里面,无论你是做a o你还是做什么东西的时候。
你如果需要进行这种remarching testing的话,用hz肯定是比你那个用风的采样要好的,在这个s键里面呢,他就用了high z b的这个方法,我去做这个testing,诶。
这个效率也会更高了对吧,毕竟是这个游戏引公司去写的这个算法,所以工程实战性还是真的是非常强,ok好,那这个时候呢还有一个很有意思的点,这个思想也非常有意思,就是说诶。
那我现在这个我假设旁边的这个pixel对吧,我要对空间进行了几次采样,我用high的方法我求出了采样点,这个采样点我在离他很近的一个位置,我也要对进行球面采样的时候,这个采样点我能不能reuse。
能不能重用诶,他发现其实如果你不考虑visibility的话,那实际上是不是就不考虑这个光,这根光线到你这儿,被那个点到你这儿被遮挡关系的话,是不是他踩到了一个小灯泡对吧。
我们我们global nation,会把所有的这个空间上的物体,都可以想象成无数个灯泡嘛,它踩到了那个小灯泡,实际上也是你的小灯泡,所以说相当于帮你也做了一次采样。
所以这个light其实是可以被reuse诶,这个思想其实也是非常重要的,为什么呢,因为在我们真正做碳的积压的时候,你真的不可能对于每一个采样点的位置,设那么多的人去做,那这样整个计算就会爆炸的。
所以说经常我们会在相邻的空间的时候,我们把这个瑞啊,大家直接重用你,你采完了对吧,就是你往那个方向打了一个瑞,告诉我说在5米之外有一个点,在空间上的x y z位置,那我假设我认为那个点跟我之间。
是没有东西遮挡的话,那那个点上的它的那个它散射,它发射出来的这个radiance的话,我也可以直接使用,但是这个曲用的话,因为我和你的角度不同吧,那我会我的强度跟你有不同对吧,因为那是一个比如漫反射。
但是这个空间上那个颜色,它那个位置我可以直接用的,所以这个reuse这个思想的话,也是几乎所有的这种real time gi的话,会用的一个思路,这个思路其实我个人觉得是非常的好,而且这个思路的话呢。
其实还有一种宠物速度什么的,在temporal就是在实际上其实也可以重用,所以当我们做real time ji的时候,一定要善于用这个reusing这个思路好,这个应该比较好理解吧。
好那我有了这样一个东西的时候,那很简单了嘛,我就开始对吧,对于每一个方向,我就沿着这个空一路的哒哒哒,往上走去吹tracing了对吧,那么其实呢这种tron就像前面讲的,就是说它实际上相当于对这个。
这个对整个光照进行了一次,那个那个那个profiling进行了一次filtering,那么其实呢我们在这个就是说,在这个就是在估算的时候,其实我们需要考虑到很多的roughness,这些东西。
在前面这张图已经给大家讲过了一遍,对吧好,那其实s g i呢就是我今天讲的比较粗啊,因为我今天更多的时间给lumen,那么其实s g i,我个人认为是一个非常好的处理,这种比较偏高。
oc比较偏spectre的reflection,第二个呢它的质量其实不错的,它能补充很多的细节,但是他的问题呢就是说因为我在我是spring space,就是在屏幕空间没有的东西我就看不到。
比如像上面那个例子,比如说那个上面浮在空中的cube对吧,地上有个偏镜面的东西,那cube下面的底面的话应该是偏黑的,对不对,但是因为黑色的那个像素点没有,所以呢那边的反射的数据我就拿不到。
其实类似的这种artifact的话呢,在这个s加里蛮多的,但是不这个,但是呢这不影响s加加,作为一个非常有用的算法,那这个s加不是有很多artifact吗,那这些artifact呢在哪儿。
被基本上全部解决了呢,诶这个首先讲r c s g r s加,其实的啊是一个非常有用的方法,为什么呢,它有几个点特别的好,第一个呢它能够处理非常近的那种contact shadow。
因为你想他反而在switch用high z b对吧,就是那个那个high z buffer,进行这种re remarching,其实high贼b的精度是很高的,那么所以我能够比如说两个物体的交界面。
那么细腻的几个pixel的内容误差,他都他都能把你算出来,这个contact shadow他处理的很好,但是以前那个就是light william,就是那个worklife的方法呢。
这一点反而处理得很糟糕,第二个呢就是它的这个hit点计算,因为用了hz b的方法去算的比较准对吧,这比你那个就是一个worko去approximate,要准的多,第三个呢就是说无论你的场景有多复杂。
我不管你场景是有什么东西限制的,比如说你用了非常复杂的nana技术对吧,你这个场景里面还有什么,还有动态的物体,对不对,那对于我s j来讲,i don't care对吧。
我都对我来讲就是一张带深度的对吧,带亮度的这样的一个有million的,上百万个小灯泡组成的一张图而已,剩下我所有的计算都是基于这张图来做的,所以的话呢它实际上对场景的complex。
complexity是无感的,同时呢它也能够处理动态的物体,哎,这几个属性其实非常的重要的,为什么呢,因为这就是为什么,在这么复杂的len架构里面,sg还是有用的好,我讲到这儿呢。
基本上把我搜集到的lumen的,一些前导的研究工作,基本上就全部讲了一遍,这个有同学在弹幕里跟我们讲,说,讲到九点半了,我们的课程主体还没有开始,对是的,这就是我们一四的风格啊。
我们114其实还是蛮学术向的风格,我们以前在这个搞科研的时候,我们去理解一个算法的时候,一般都会做这个叫那个就是那个叫sway,我们会把所有相关的东西全部给它,sv一遍,这个东西为什么很重要呢。
因为接下来我要讲len,len其实是一个蛮难理解的一个系统,它里面的细节之多啊,真的是惊人啊,真的非常惊人,而且我们了解到很多的背后的故事嘛,就是他们这个团队做了好几年对吧。
持续不断的去在这个方向去推进,最终能够大成就是真的能变成,u e5 的当家花旦的这样一个feature,其实我们是做工程的人,我们是知道就是你做的一个技术科研,能变成一代引擎的核心feature。
这个难度之大是远远超过大家的想象的,因为你要面对很多很多很复杂的case,而且很多要求都是非常苛刻的,所以的话呢就是如果没有这些前导的,这个知识的,主要作为我来讲的话,是非常难以理解。
就是lumen他为什么这个地方这么干呢,ok好,那就废话不多说了,我们先回到我们今天的主角了,正菜上了鲁莽了对吧,肚子已经吃了半饱了,居然你才上我的主菜对,这就是我们风格好,再讲中文。
那其实呢首先呢我弟弟用几年lumen自己啊,这个这个原作者写的这个p p t,你可以发现它的特点,第一首先黑一下real time retracing对吧。
大家都会问real time reaching这么强,为什么你们还要用中文对吧,他就会说哎retracing很慢对吧,你如果想达到一个好的结果的话,第一个就是trc的话呢,很多硬件其实它是不支持的对吧。
那么就算是支持的话,n卡支持的还算是勉强可以,a卡支持的其实是比较糟糕的对吧,那么我还要在那个就是说呃,主机上其实是可以支持为tring,但效果怎么样,大家也是表示堪忧,其实我自己测算一下。
就是那个呃retracing的话,我当时测了一下,是应该是3070还是3080,我有点忘了,但是这个量级的差不多吧,就是我们预计下个比如四零系列的话,double一下triple一下可能也就差不多了。
那么你实际上用它来去做这个j i的话呢,实际上还是比较费力的,对不对,那么但是我们在做很多的场景的时候,比如说有些你最常见的印度的场景的话,那你你要多少根ray才能得到效果,他作者他自己说诶。
你需要500分ray在印度的时候,但是呢我们只能afford,就是能够只能付得起每一个pixel,1/2个人对吧,我才能够达到我们的这个,这个这个就是性能的criterion,就是性能的要求。
所以说我们要研发下lumen这样的一个系统,那么第二个的话就是三林的问题,三林确实是很难的问题,其实无论是这个过去的离线的算法,对我讲过,就是我们花了十几年时间,应该是几十年时间。
和这个important sa去做殊死搏斗,对吧,到现在为止的话呢,也没有特别让我们自己的满意,那么一旦到了real time,与三零数量又这么低的情况下,其实三破零的话呢,比如你这个例子里面。
就是你靠近窗口的时候,你的三零结果基本是可以接受的,你可以看到虽然说他还是很noisy啊,但是你可以通过filter解决这个结果,但是呢如果你离窗子稍微远一点,那filter也救不了。
你看到的就是一个一个的大色斑对吧,就是这就是你所有看所有的gi的结果啊,基本上你第一版做出来之后都是黑一块,白一块,这个特别的难受,但是这就是事实,就是就是所以说rendering。
他其实真的是个非常难的一个,一个一门学科,真的这个rendering的壁垒呃其实蛮高的,对你的数学基础的要求也是非常高好,那么还有什么呢,诶我们发现就是说其实啊我们的intro lighting。
其实可以在low resolution上面去这个踩出来,他的想法是什么呢,诶我在screen space放一大堆的probe,wiped的特点是什么呢,我就紧贴那些要被照好的物体表面。
我去获得这个pop,就pop就是这个光照嘛,这个球星的光照,然后呢我这些spars的采样,比如16x16个pixel,我再放一个他去照亮我的几何,但以我的几何呢,我每个每个像素去设定。
所以呢实际上它的高频,可以通过每一个表面上的法线的朝向啊,对吧,这些东西来产生这样的效果,最后我能产生一个非常逼真的效果,那就是我们的这个右边的结果,其实lumen它最核心的思想啊,就这么一个思想。
就是说我我我不要用retracing对吧,我在什么硬件都能跑起来,第二个的话呢,我尽可能的把三林做的优化,同时呢我的我的probe,尽可能贴着我真实要付的这个表面,要要绘制的表面去放。
这样的话让它的精度足够的高,能产生我这样的结果,这个就是三句话讲,len讲起来就是这样,是不是很简单,诶,这个你就被误导了,其实鲁曼里面的细节真的非常的多,那好那怎么讲len呢。
我自己呢就是这个自作主张,因为其实讲lumen的这个分享还是蛮多的,但是我发现就是我自己看起来还是蛮累的,因为我看到了一大堆的算法,但是我看不出结构,所以今天当我自己在整理这个思想的时候。
我把它分成了几个大的部分,分成几个阶段,我定义成飞一飞二飞三飞四,不好意思,因为时间特别仓促,我没有办法给它做一个整体的一个总结页,那我就那我就依次去讲好吧,我就讲诶,第一趴呢。
它的一大堆算法解决的是个什么问题呢,解决的是说我怎么样的,在一个任意硬件上能进行一个,非常快速的retracing,为什么呢,因为你无论是用什么样的一种解压的方法,你一定要解决一个核心的问题。
就是说我射出一根锐,我到底能不能交到一个物体,我交到的是谁对吧,那那这个这个retracing的话,我当然是可以用hardware releasing去做,其实roman最新的一些时间也讲了。
如果你用hardwretracing,我也可以调对吧,但是呢如果你没有呢,还有一个就是阿特瑞事情太废了,我希望它更快一点,那我怎么解决呢,哎len呢,他用了一个方法,其实是大名鼎鼎的sdf。
就是sign distance field,sdf这个概念很有名,我相信在同学们前面几节课都跟他讲过,对不对,就是叫空间距离场对吧,去什么意思,就是说我一个match在这。
那你的距离这个我空间上的任何一个点,那么如果你在我外面,那些距离全是正的对吧,当你到我的表面的时候,这个距离变成什么呢,变成零,哎你在我里面的时候,这就是反向的找到表面的距离,那什么那就是负数。
那么sdf就是这么一个简单的定义,那为什么sdf这么重要呢,其实我自己也一直在思考这个问题,我跟大家分享一下我个人的思考啊,就是说,其实sdf,在对于现代的渲染引擎来讲的话呢。
很可能会成为一个基础性的数学构建,那为什么sdf它有这样的一个,就是这么吸引人呢,就我后面会讲到他有些实际应用嘛,但是其实从纯数学上来讲,它是一个就是对于空间上的形体,形状的一个对偶的表达,什么意思呢。
就是大家知道在数学上啊,我可以用一种形式去表达一个,比如说形状一个或者一个一个一个属性,一个函数对吧,但这个函数呢,其实我可以把它换成另外一种形式,这两种形式之间其实是等价的,但是呢。
但是你一旦把它换成一种队伍形式的,去表达的时候,它会展现出很多非常良好的数学数学属性,那我很多计算就可以开展了,哎这个讲了讲起来有点有点抽象啊,但是呢我觉得如果立志做渲染的同学。
一定要这个把这句话理解透,那我跟大家解释一下,就是说过去我们对一个形体的表达,我们是用船狗对吧,去表达一个个的点线面表达,这非常好,这个好处是什么呢,它非常符合我们人的直觉,但是它的坏处是什么呢。
第一它是离散的,大家想想对不对对吧,它实际上是,无论是顶点数据和它形成的空间上的,这个面的数据之间其实是没有关系的对吧,它必须要通过index buffer关联起来,那好三角形和三角形之间的连接呢。
其实也是不存在的,为什么呢,因为它必须要通过顶点的,就是就共用两个相相同index的顶点,我才知道三角形两个边是连接起来的,所以大家以前在做很多match处理的时候呢。
我们会写大量的叫jason jason information,就是会做很多冗余的数据,把这些点线面的连接关系全部找起来,比如说过同一个点有哪些三角形啊,过同一个边有多少三角形啊,这样的话。
我在上面才能进行各种几何的处理对吧,但是sdf的话呢,它从数学上,时而是和那个表面是一个等价的一个变化,但是它的好处是什么呢,第一它是连续的呃,它吧第一个它是uniform的,第二呢它是空间上的一个厂。
这个厂的好处是什么,是连续的,连续的话讲一个最近比较高大上的概念,就是它是可微的,哎你既然可危了,你就能做很多事情了对吧,所以说呢其实sdf的话呢,实际上是一个非常重要的基础,这也是我也能理解。
就是为什么在这一代的就是硬件上,大家会大量的就是现代图形渲染,你们大量的使用sdf这个作为概念,我个人觉得sdf,它未来的潜力可能不止于此,我个人也会对这个问题非常的感兴趣,好了,这里的弹开一点。
我太喜欢这个,把一些我的思考给大家分享了,不过还好我们的小伙伴还很不介意,大家应该是不介意的吧,ok好的,那我们就就单开一笔讲讲sdf好,那既然有了sdf之后,诶,我们就可以对空间进行表达。
那最淳朴的表达是说诶,我对整个场景做一个df对吧,但是呢这样的话这个场景很大很大呀,对不对,那我就会浪费很多空间,存储这些无效的东西,所以很自然和直觉的想法就是,我对场景里的每一个mesh。
我去做个sdf,当然这些max的每个max自己的sdf的话,合到一起,它又会形成我整个空间的sdf对吧,但这里面是有一套数学变化了,我今天就不展开了,那我就形形成了一个promise的。
这样的话对于我的一个游戏场景来讲的话,我里面有成百上千甚至上万个对象的话,其实呢这上万个物体可能就是几百种物体,通过比如说平移旋转放缩得到的,那么这个时候呢,我只需要存着几百个物体的sdf。
我加上它的位移,这个这个transform的这个数据的话,其实我就可以把场景表达出来对吧,这个就是非常自然的一个表达,但这里面有一个细节,这是我自己现在还没推完呢,就是说如果我对这个物体的放缩。
不是uniform的,什么意思,就是x y z轴是等比例的放松,比如说x轴放长一点,y轴放短一点,那这个sdf方程怎么去找啊,好像是有那么一丢丢的问题对吧,这个好像呃,我现在还没想到数学上怎么变化。
但是呢如果你是等比例放缩的话,那无论是这个就是平移变化还是旋转变化,还是这个放缩变化,那都是可以的,sdf可以简单的变化,也就可以直接用,ok好,那我们如果有每个mesh的sdf的话呢。
哎这个时候我这里面讲个细节,就是说我们在工程实践中,如果用sdf表达一个match的时候,这也是lumen原来讲就是你特别细的,怎么办,你啊已经跨过了我最小的两个worker之间的,这个距离。
因为我毕竟是对空间进行点采样嘛,那怎么办,我就把你撑开了一点,也就是说否则的话呢,你这两边的这个面啊,它的那个那个那个distance值都是正直,没有一个负值,所以我就检测不到你那个面的存在。
这是sdf的一个问题,就是说对特别细的面我比较难以表达,但这个没关系,这个瑕不掩瑜,因为很多时候sf,我做的也不需要那么的精细好,一旦有了sdf之后呢,我们就会变得非常的efficient,做一些事情。
比如说我们做retracing对吧,我们做marsh,那marshine呢这里面最难的一个问题是什么,就是不长对吧,我记得我从最开始讲ranging的时候,都讲到这个问题对吧,比如说讲了hz b对不对。
但是如果不是screen space,在workspace里面我做个marching的话,那我怎么知道我这一步到底跨多远呢,我以前的做法只能是一步步的试对吧,我可以用object bounding去试。
但是绑定去试它也是很废的,但是你如果有整个这个场景的,这个这个这个就是sdf的话,实际上你从出发点出发,你的第一个这个它的第四层的距离的话,是不是就是你最安全的第一步的距离。
你就会从p0 点跳到p一点对吧,那你从p一点的话,你再去找一个,他的那个一个安全的距离对吧,他帮你,你可以找到p2 点,这样以此类推,我们可以非常快的就可以hit到物体的表面。
而且大家可以思考一下这个表面啊,实际上是比较安全的,就是因为你就算步子大了一点,你穿进去了,因为sdf它是有符号的嘛对吧,你进去之后给你个负数,那个负数就告诉你说你的表面应该在哪,你还可以弹回来。
所以这个retracing它是非既快又鲁棒,诶这是sdf非常多的好处,那么sdf第二个好处是什么呢,诶我假设做contracing contracing,我们举个例子。
比如说我们要做soft shadow对吧,其实soft shadow本质上讲了什么呢,就是说如果天空中有一个面积光源对吧,假设这个圆形的,那我这个时候我去看他的时候,大概多少面积会被挡住。
那么我现在假设求到了一个他那个点的话,实际上我军machine的时候,我根据你从那个点出发,到mash的最近的点的距离,我可以画一个画一个球,画一个圆出来对吧,那在那个点的,整个你的空的这个disc。
和那个最近的距离之间的disc之间,画一大一小两个圆,这两个圆之间的比值啊,是一个非常好的approximation,就是说你离那个东西有多远,但是大家很多都会讲说这肯定非常不准啊,确实是非常的不准。
但是呢它已经是非常好用的一个东西,而且这个东西的话呢,其实如果我们在做设定的时候,比如说我们在天上进行这种a lin,采样的时候,是不是sdf这时候也可以起作用对吧,所以sdf在这个时候它是非常的好用。
所以最早的时候就有人想到用sdf做soft shadow,但今天来看的话,这实在是大材小用了吧,sdf这么牛逼的东西可以做很多事情好,现在有的发展的话,就是可以考虑到就是说诶我可以。
如果你对一个物体做一个绑定,你生成的sdf很可能就是用采样很大对吧,那怎么办,哎我可以对它进行这个稀书画,你会发现很多的vocal它实际上都是空的,那么如果那些vocal。
他的diss都会大于那个阈值的话,我们就把它全部干掉,这样的话我用一个简单的index,我就可以把这个存储啊给它减少很多,那么这样的一个东西的话呢,实际上也是一个工程上的一个时间。
但确实可以有效地减少df的存储的空间,但是我个人呢一直在怎么说呢,我一直在怀疑一件事情,就是说确实你只要大于一个第四的阈值,你把那些vocal全部标为空对吧,你这样只要存那些有用的地方,有用的区域。
但是的话呢它会导致你的这个remarching的,这个迭代的步长就会变长,为什么呢,因为以前我一次性,比如说我从那个天上那个角进来的时候,我一次性可以这个跳一大步,但现在的话呢我发现哎。
我只能一步一步的试了对吧,但是这里面工程上肯定有办法可以解决,我们就不展开了,还可以进行l o d对吧,那我每一层的l o d实际上可以达到sdf,还有一个很有意思的属性。
就是说因为它是空间上连续的可导的,它的导数呢实际上是什么呢,是它的法向诶,这个很有意思对吧,你可以用它的反向求它的梯度啊,其实就是它的法向,而这个时候呢,也就是说我们用了一个uniform的一个表达。
可以表达出一个无限精度的一个mesh,我既能得到它的面积,而且又能对它进行快速的求交运算,还能够迅速的求出它连续的这个法线方向,这也是sdf很巧妙的一个点,所以呢你有了这样的这个sf。
它本身因为它是uniform的,它很容易做,大家想想我们以前做mash d的时候多痛苦,对不对,有多少软件专门是把你传给mesh,面片简化对吧,大家以前听说过什么simply count啊。
各种各样的中间键,但是如果你切换成s t f表达mesh的话,不好意思,他就是个当桑风铃的问题,它就是可以去表达,所以呢如果你有了这个就是说p r o d,你在用spss match的话。
实际上你的存储的话呢,就是用一个简单的表达,你可以大概省掉大概多少呢,我大概40%到60的空间就可以省,掉,在硬件上还是蛮可观的对吧好,而且呢因为你l o d比如说远处的物体,你可以先用这个lol的。
就是那个mp比较高的这样的一个sd f,等你到近处的时候,你切到这个比较近处的sdf,而且你如果用一些简单的,streaming的概念的话,这样也可以控制好你内存的消耗诶,这个时候是不是各种好处。
你全部占上了啊,对的,masdf确实是一个非常好的一种表达,那为什么就是鲁莽,它是上来的一个数学基础,就是sdf呢,就是因为它有一个应用场景,就是说假设你没有retracing的这个harder。
tracing,或者呢你有hardwretracing,但是性能太慢了,那这个时候sdf是一个非常好的real time的,这个就是recasting的一个一个基础的表达。
那么其实呢这种你用那个sf promise to,去做retreating的,其实还有一个挑战是什么呢,对于单个mesh来讲,你做retreating速度很快,你可以先找到他的绑定对吧,找到绑定之后。
开始从绑定直接按照s t f往里面打,但是你架不住场景的物体,数量特别特别的多对吧,那我比如说我一个人也打过去的话,我沿途所有的object我都得问一遍,这里面举的例子。
就是说如果把场景分成全是物体的话,那你的计算复杂度就会越来越高,这里面就讲它是hit了多少step,你会发现越靠近这些mash的边界,上的这些这些这些像素的话,它那个step就要走的非常的远。
这个时候怎么办呢,就是我我因为我要沿着那个方向,有很多的很多的瑞要去测试嘛,那一个非常简单的想法就是那好吧,既然你们都是分散了,对不对,那我现在已camera空间对吧,我把你这些这个这个s t f了。
合成一个大的低精度的这个global的sdf,对不对,这就是一个场景的表达,那当然了,这里面比较复杂,第一个就是从permission sdf怎么合成,一个就是global,这里面是有一些数学变换的。
那么第二个的话呢,就是说这里面假设有些物体移动了,有些物体的那个这个新的消失了,有些物体增加了,这里面你还要提供一套update的算法,在今天的课上我就不展开了,如果有兴趣的同学可以自己研究。
但是你就想理解一件事,in the end,最后呢能形成一个整个场景的sf,那如果整个场景有了sdf的话,其实在这里面进行,recasting的速度就会非常快,因为它不再依赖于一个一个的物体了对吧。
当然它的缺点是什么呢,因为存受制于存储空间,它不能像promise sdf那么精细。
他做的一般会比较粗,所以呢在lumen里面的话呢,这两个东西都用了,就是mash sdf,global sd f都有用,但是我们在最后跟大家讲,为什么两个都有用,那么实际上当我们这样去做的时候,诶。
一开始如果你每个物体做的话,你要测试多少物体,这里面这张图里面,如果灰阶是就是亮度是百分之百的话,那就意味着要200多次的物体测试对吧,但是你一旦做了global的sdf之后呢。
它物体的测试数量就会极大的下降,因为可以快速的找到一些近处的点,然后再根据周边的match去做的话,他就会快很多,所以你有了这样的一个架构啊,是实际上你再去做任何一个recasting的时候。
他其实就既不依赖于这个硬件的这个,这个hretreating,同时呢,它的速度又要比你直接去测那个,什么a b box啊,测那个ray和船go的焦点啊,这个要快很多很多。
ok那么就是说呢其实这个global sdf呢,在实际上的预算中,它会做成一个密不,就是四层密布在camera这边,近处呢我的sd f精度高一点,远处呢我s t f精度低一点,因为大家讲过sdf是什么。
它跟tt一样,他是一个非常uniform的表达,所以他又天然的去支持这个clip map对吧,所以你可以实现,比如进出50米,这个sdf密度高一点对吧,远处1百米,2百米。
4百米s d f d f精度就低一点,这个完美的,就是解决了我们各种各样求教的需求,所以说这个其实大家可以看到,就是说啊,为什么我刚才前面给sdf,这个贴了那么多金呢。
因为我们自己做rendering的底层研究啊,做了很久之后,你会发现其实数学表达非常的重要对吧,就是说当我还记得,我在以前学图形学的时候,学过什么叫影视曲面对吧,就是你一个形体。
你怎么样用数学方程去表达它,如果你是一个影视曲面的话,你可以做很多事情了对吧,它可以无限的被精细化,无限的被求导的一堆的好处,但是如果我把这个空间变成点云啊,变成离散化的三角形的话。
三角形是一个最糟糕的表达,为什么呢,它既是离散化的,同时它又是irregular,那么另外一个相应的表达是什么呢,诶就是点云对吧,就是worko的点云,我记得在之前我也讲过。
就voxel base的这个表达,那vox的表达呢,它的好处是说它是uniform的,它可以进行过滤,进行filtering r进行各种处理,但是呢它的坏处是什么呢,就是它还是离散化的。
所以sdf的话呢,这个属性其实我个人觉得还是比较完美的,但是sdf大家也不要高估,它并不能直接作为渲染,但是它可以很多渲染的计算,把它给这个进行这个迅速的这个优化掉,o,所以说这其实是lumen里面。
它最核心的一套防思想,在用一些简单的camera base的这个l o d的思想,实际上的话呢,我们就可以快速地提供这个场景的一个表,达,这个表达的上面做retracing的效率非常的高。
这样我就不依赖于hardware retracing了,好这是他的第一。