为啥我的笔记本开机超级慢超级慢,Thinkpadegad E435,以前很快的啊

&figure&&img src=&https://pic4.zhimg.com/021fd3de3765faa06010_b.jpg& data-rawwidth=&841& data-rawheight=&442& class=&origin_image zh-lightbox-thumb& width=&841& data-original=&https://pic4.zhimg.com/021fd3de3765faa06010_r.jpg&&&/figure&&h2&写在前面&/h2&&p&开始学习图形学的人,尤其是计算机专业而不是数学专业,可能都有一个感觉,很多图形学公式让人完全摸不着头脑。这些公式看起来是微积分公式,但和高数里的不太一样,也不能套用高数课本上的方法推导。不少图形学教科书写得过于简略,导致初学者看这些公式如同看天书一样。教课书里莫名其妙写了几个公式,又莫名其妙地推导出了代码,代码里面还有一些完全不知道怎么来的参数。很多人因为这样的原因在图形学的门口就跪倒了。。。&/p&&p&由此还产生了一些自暴自弃的想法&/p&&p&&b&一、我高数是不是白学了?&/b&&/p&&p&比如经典的渲染等式&/p&&img src=&https://www.zhihu.com/equation?tex=L_o+%3D+%5Cint_%5COmega+%5Crho%28%5Comega_o%2C%5Comega_i%29+L_i+cos%28%5Ctheta_i%29+d%5Comega_i& alt=&L_o = \int_\Omega \rho(\omega_o,\omega_i) L_i cos(\theta_i) d\omega_i& eeimg=&1&&&p&虽然大学都学过基本的微积分,但这个积分怎么要算啊&br&&/p&&p&&b&二、积分其实能用乘法代替吧?!&br&&/b&&/p&&p&因为这样的积分公式&img src=&https://www.zhihu.com/equation?tex=L_%7B%5Comega_o%7D+%3D+%5Cfrac%7B1%7D%7Bcos%28%5Ctheta_i%29%7D%5Cint_%5COmega+L%28%5Comega_o%2C%5Comega_n%29G_1%28%5Comega_o%2C%5Comega_n%29%28%5Comega_o%5Ccdot+%5Comega_n%29D%28%5Comega_n%29d%5Comega_n+& alt=&L_{\omega_o} = \frac{1}{cos(\theta_i)}\int_\Omega L(\omega_o,\omega_n)G_1(\omega_o,\omega_n)(\omega_o\cdot \omega_n)D(\omega_n)d\omega_n & eeimg=&1&&&br&&/p&&p&推倒完成后写在代码里面是这样&/p&&div class=&highlight&&&pre&&code class=&language-glsl&&&span&&/span&
&span class=&k&&float&/span& &span class=&n&&Dr&/span& &span class=&o&&=&/span& &span class=&n&&GTR1&/span&&span class=&p&&(&/span&&span class=&n&&NdotH&/span&&span class=&p&&,&/span& &span class=&n&&mix&/span&&span class=&p&&(&/span&&span class=&mf&&.1&/span&&span class=&p&&,&/span&&span class=&mf&&.001&/span&&span class=&p&&,&/span&&span class=&n&&clearcoatGloss&/span&&span class=&p&&));&/span&
&span class=&k&&float&/span& &span class=&n&&Fr&/span& &span class=&o&&=&/span& &span class=&n&&mix&/span&&span class=&p&&(&/span&&span class=&mf&&.04&/span&&span class=&p&&,&/span& &span class=&mi&&1&/span&&span class=&p&&,&/span& &span class=&n&&FH&/span&&span class=&p&&);&/span&
&span class=&k&&float&/span& &span class=&n&&Gr&/span& &span class=&o&&=&/span& &span class=&n&&smithG_GGX&/span&&span class=&p&&(&/span&&span class=&n&&NdotL&/span&&span class=&p&&,&/span& &span class=&mf&&.25&/span&&span class=&p&&)&/span& &span class=&o&&*&/span& &span class=&n&&smithG_GGX&/span&&span class=&p&&(&/span&&span class=&n&&NdotV&/span&&span class=&p&&,&/span& &span class=&mf&&.25&/span&&span class=&p&&);&/span&
&span class=&k&&return&/span& &span class=&p&&((&/span&&span class=&mi&&1&/span&&span class=&o&&/&/span&&span class=&n&&PI&/span&&span class=&p&&)&/span& &span class=&o&&*&/span& &span class=&n&&mix&/span&&span class=&p&&(&/span&&span class=&n&&Fd&/span&&span class=&p&&,&/span& &span class=&n&&ss&/span&&span class=&p&&,&/span& &span class=&n&&subsurface&/span&&span class=&p&&)&/span&&span class=&o&&*&/span&&span class=&n&&Cdlin&/span& &span class=&o&&+&/span& &span class=&n&&Fsheen&/span&&span class=&p&&)&/span&
&span class=&o&&*&/span& &span class=&p&&(&/span&&span class=&mi&&1&/span&&span class=&o&&-&/span&&span class=&n&&metallic&/span&&span class=&p&&)&/span&
&span class=&o&&+&/span& &span class=&n&&Gs&/span&&span class=&o&&*&/span&&span class=&n&&Fs&/span&&span class=&o&&*&/span&&span class=&n&&Ds&/span& &span class=&o&&+&/span& &span class=&mf&&.25&/span&&span class=&o&&*&/span&&span class=&n&&clearcoat&/span&&span class=&o&&*&/span&&span class=&n&&Gr&/span&&span class=&o&&*&/span&&span class=&n&&Fr&/span&&span class=&o&&*&/span&&span class=&n&&Dr&/span&&span class=&p&&;&/span&
&/code&&/pre&&/div&&p&所以会觉得,都是乘法嘛。。。&/p&&p&&b&三、这些参数绝对都是瞎凑出来的!!!&/b&&/p&&img src=&https://www.zhihu.com/equation?tex=L_o%28v%29%3D%5Cpi+f%28l_c%2Cv%29%5Cotimes+c_%7Blight%7D%28l_c%5Ccdot++n%29& alt=&L_o(v)=\pi f(l_c,v)\otimes c_{light}(l_c\cdot
n)& eeimg=&1&& diffuse公式里的&img src=&https://www.zhihu.com/equation?tex=%5Cpi& alt=&\pi& eeimg=&1&&&br&&br&&p&&img src=&https://www.zhihu.com/equation?tex=%5Crho%28%5Comega_o%2C%5Comega_i%29+%3D+%5Cfrac%7BF%28%5Comega_o%2C%5Comega_h%29G%28%5Comega_o%2C%5Comega_i%2C%5Comega_h%29D%28%5Comega_h%29%7D%7B4%5Cleft%7C+%5Comega_g%5Ccdot%5Comega_o+%5Cright%7C+%5Cleft%7C+%5Comega_g%5Ccdot%5Comega_i+%5Cright%7C%7D+& alt=&\rho(\omega_o,\omega_i) = \frac{F(\omega_o,\omega_h)G(\omega_o,\omega_i,\omega_h)D(\omega_h)}{4\left| \omega_g\cdot\omega_o \right| \left| \omega_g\cdot\omega_i \right|} & eeimg=&1&& 微表面公式里的4&br&&/p&&p&有很多教课书上都没写清楚这些参数是怎么来的,让初学者觉得都是瞎凑的。&/p&&br&&br&&p&然而到底有没有直观的方法给人讲解这些公式呢,肯定是有的。因为图形学用的模型大部分还是来自于几何的,只要是几何模型多少还是能通过画图的方法解释的。所以会遇到这些问题,还是因为对图形学背后的数学模型不了解造成的。写本文的目的就是试图用直观的方法解释一些图形学中的基本公式和概念,如渲染公式,微表面模型等。&br&&/p&&br&&h2&从光照模型开始&/h2&&p&既然是研究渲染公式,光照显然是其中最基本的概念,首先要做的就是对光建立一些模型。从简单的中学物理角度来看,光之所以能照亮物体就是光子从光源发出,击中物体表面又反射到观察者眼中。而物体亮度高低显然和进入眼睛中的光子数量有关系。有个专门的物理量来描述,叫做光通量。光通量简单理解就是单位时间内某一表面接受到的光子总数量,和磁通量是类似的概念。这里要注意的是光通量不是亮度,我们通常认知的亮度应该是光通量的密度,也就是单位面积的光通量。&br&&/p&&p&了解了光通量和亮度的概念之后,我们可以来观察光是如何辐射到物体表面的。先假设有一个点状光源均匀向四面八方发射光子,它发射出去的光通量记作P。再在点光源外面套一个半径为1的球体(单位球体),球体的面积为4&img src=&https://www.zhihu.com/equation?tex=%5Cpi& alt=&\pi& eeimg=&1&&,可以算出球体上的亮度是:&/p&&img src=&https://www.zhihu.com/equation?tex=L_i+%3D+%5Cfrac%7BP%7D%7B4%5Cpi%7D+& alt=&L_i = \frac{P}{4\pi} & eeimg=&1&&&br&&p&如果在球体之外再放任意圆弧,如何求出圆弧上一点的亮度呢。这里就要作图分解一下了,假设取一个夹角&img src=&https://www.zhihu.com/equation?tex=%5Comega+& alt=&\omega & eeimg=&1&&。如图,只要是在夹角&img src=&https://www.zhihu.com/equation?tex=%5Comega+& alt=&\omega & eeimg=&1&&范围内,平行于球体表面的所有圆弧接受到的光通量应该是一样的,但随着距离增加,圆弧的面积呈平方增加。这样单位面积的光通量反而减少了,这就是中学物理中讲的光亮度在真空中会随着传播距离呈平方反比减少的原因。&/p&&p&另一方面如果要求的是任意平面的亮度呢。所谓任意平面也就是说受光面不一定平行于单位球面。求法是把平面先投影到球面,用投影面积作为受光面积来计算。这里要注意的是夹角&img src=&https://www.zhihu.com/equation?tex=%5Comega+& alt=&\omega & eeimg=&1&&不是真的有个角度,我们要求的是一点上的亮度,所以&img src=&https://www.zhihu.com/equation?tex=%5Comega+& alt=&\omega & eeimg=&1&&是个极小的角,有个数学名词叫做立体角,立体角在球面上覆盖的范围是就是球面积分里的微元。微元的投影要用雅可比行列式计算,计算过程放在附录,有兴趣的可以看看。&/p&&p&从计算结果来看其实可以看成直角三角形的投影。如图所示,&img src=&https://www.zhihu.com/equation?tex=%5Comega_i& alt=&\omega_i& eeimg=&1&&是光线的方向,&img src=&https://www.zhihu.com/equation?tex=%5Comega_n& alt=&\omega_n& eeimg=&1&&是平面的法线方向,&img src=&https://www.zhihu.com/equation?tex=%5Ctheta_i& alt=&\theta_i& eeimg=&1&&是光线和平面法线的夹角,P是受光的平面,设平面长度为L。假设有一个平面M垂直于&img src=&https://www.zhihu.com/equation?tex=%5Comega_i& alt=&\omega_i& eeimg=&1&&,求P到平面M的投影。简单的三角几何就能求出投影长度:&img src=&https://www.zhihu.com/equation?tex=L_m%3Dcos%28%5Ctheta_i%29L& alt=&L_m=cos(\theta_i)L& eeimg=&1&&&/p&&figure&&img src=&https://pic3.zhimg.com/df54b5a093b58a8fedcb6_b.jpg& data-rawwidth=&593& data-rawheight=&368& class=&origin_image zh-lightbox-thumb& width=&593& data-original=&https://pic3.zhimg.com/df54b5a093b58a8fedcb6_r.jpg&&&/figure&&br&&p&这也就是很多图形学公式里法线和光方向点乘法的由来,因为&img src=&https://www.zhihu.com/equation?tex=cos%28%5Ctheta_i%29+%3D+%5Comega_n%5Ccdot%5Comega_i& alt=&cos(\theta_i) = \omega_n\cdot\omega_i& eeimg=&1&&。综合上面的公式点光源在任意平面上一点的亮度如下:&/p&&img src=&https://www.zhihu.com/equation?tex=I%3D%5Cfrac%7Bcos%28%5Ctheta_i%29%7D%7Br%5E2%7DL_i+& alt=&I=\frac{cos(\theta_i)}{r^2}L_i & eeimg=&1&&&br&&p&另一种情况是无限光,无限光的定义就是光亮度不随距离变化,计算光照亮度时可以把&img src=&https://www.zhihu.com/equation?tex=r%5E2& alt=&r^2& eeimg=&1&&去掉。&/p&&img src=&https://www.zhihu.com/equation?tex=I_%7Binfi%7D%3Dcos%28%5Ctheta_i%29L_i+& alt=&I_{infi}=cos(\theta_i)L_i & eeimg=&1&&&br&&h2&从视线到视锥到立体角&/h2&&p&现在我们已经对光照模型有所理解了,这个模型和一般人直觉思维有所不同。中学物理对光和光传播的描述都是点和线,而图形学中讨论的光照模型多了很多和面积有关的结构。正是这些结构带来更复杂的公式。公式当然是复杂的,但我们能不能透过公式对这些结构有更直观的认识呢。一定是有的,因为我一直想绝大多数人实际上是没办法在公式这种抽象形式上思考问题的,只有很少数受过长期专门训练的人才能只通过抽象符号思维。大多数人还是习惯于有一个直观的模型来作为思考的基础。&br&&/p&&p&那么如何直观地描述光照模型的结构呢,还是要回到基本的光线追踪上来。之前说过光线追踪就是在屏幕外做一个虚拟的眼睛,描绘屏幕上每一个像素的时候从眼睛做一条射线通过要绘制的像素点射入屏幕内的三维空间,射线在三维空间击中的那个点就是要绘制的目标,然后根据该点的法线和光照信息计算出屏幕上像素应该是什么样的。&/p&&p&这里要打脸了,射线这种说法只是一个非常简化的模型。为什么这么说呢,因为即使屏幕的像素点再小,它也是有面积的,即使缩小到所谓无穷小,也不会是零。也就是说像素永远只能是一个面而不是一个点。这样的话所谓光线追踪的射线,其实应该是一个锥形,我们称为视锥,如图所示,这个视锥和屏幕的相交面就是像素。这样我们在这个像素上呈现的也不是射线在三维空间击中的一个点,而是视锥在三维物体表面上的投影区域。同样这个投影区域的光照信息不是一个点的光照信息那么简单。另外要注意到的是如果保持视锥位置不变,将三维物体沿着光锥中轴线移动,随着移动投影面积的大小也会变化。&/p&&figure&&img src=&https://pic3.zhimg.com/d80048a67b_b.jpg& data-rawwidth=&512& data-rawheight=&318& class=&origin_image zh-lightbox-thumb& width=&512& data-original=&https://pic3.zhimg.com/d80048a67b_r.jpg&&&/figure&&p&可惜的是目前的渲染技术架构没法使用这种模型,因为这种模型的计算要对每个像素的投影区域进行采样,这种计算量在很多情况下是无法达到的。这时候图形学家就要用别的方式在达到接近的效果。现在渲染公式的基本架构就是用立体角来计算,直观的讲就是保留原来射线追踪 的方式,以原来射线落点为球心面向法线方向做一个半球。在半球上来捕获面积和光辐射信息。这样做可以等同认为将投影区域面积缩小到无穷小,用半球上的立体角来计算微元投影面积。这样就保留了几何信息,因为微元面积也是有大小方向的,不像点是没有面积的。&br&&/p&&figure&&img src=&https://pic1.zhimg.com/8d98d078d5bacea93a6d669_b.jpg& data-rawwidth=&259& data-rawheight=&194& class=&content_image& width=&259&&&/figure&&h2&行踪神秘的PI&/h2&&p&下面我们回到渲染公式,也是图形学里最重要的公式。&/p&&img src=&https://www.zhihu.com/equation?tex=L_o+%3D+%5Cint_%5COmega+%5Crho%28%5Comega_o%2C%5Comega_i%29+L_i+cos%28%5Ctheta_i%29+d%5Comega_i& alt=&L_o = \int_\Omega \rho(\omega_o,\omega_i) L_i cos(\theta_i) d\omega_i& eeimg=&1&&&br&&p&这里面的符号我们已经认识大半了,&img src=&https://www.zhihu.com/equation?tex=%5Comega_i& alt=&\omega_i& eeimg=&1&&是光线入射的方向,&img src=&https://www.zhihu.com/equation?tex=d%5Comega_i& alt=&d\omega_i& eeimg=&1&&是入射光线的立体角微元面积,cos(&img src=&https://www.zhihu.com/equation?tex=%5Ctheta_i& alt=&\theta_i& eeimg=&1&&)是在入射光线垂直方向上的投影,&img src=&https://www.zhihu.com/equation?tex=L_i& alt=&L_i& eeimg=&1&&是入射光线的单位面积光通量。&/p&&p&剩下的就是&img src=&https://www.zhihu.com/equation?tex=L_o& alt=&L_o& eeimg=&1&&反射方向上单位面积光通量,也就是我们看到的亮度。&img src=&https://www.zhihu.com/equation?tex=%5Crho%28%5Comega_o%2C%5Comega_i%29& alt=&\rho(\omega_o,\omega_i)& eeimg=&1&&是入射光线和出射光线亮度的反射比例系数。整个积分要表达的意思就是在单位半球表面收集所有方向的入射光线亮度和面积信息以及对应的反射系数,用这些参数在半球表面的作积分。&/p&&figure&&img src=&https://pic2.zhimg.com/091ecdc664bbc81c9253f_b.jpg& data-rawwidth=&300& data-rawheight=&162& class=&content_image& width=&300&&&/figure&&p&在漫反射的情况下反射系数是个常数&img src=&https://www.zhihu.com/equation?tex=%5Crho%28%5Comega_o%2C%5Comega_i%29+%3D+%5Cfrac%7Balbedo%7D%7B%5Cpi%7D+& alt=&\rho(\omega_o,\omega_i) = \frac{albedo}{\pi} & eeimg=&1&&,albedo就是材质的颜色,这个可以理解,但是&img src=&https://www.zhihu.com/equation?tex=%5Cpi& alt=&\pi& eeimg=&1&&是怎么来的呢。&/p&&br&&p&要理解这个可以先设想一个最简单的情况,入射光遍布所有的方向,而且每个方向的亮度都是相同的常数。因为这样&img src=&https://www.zhihu.com/equation?tex=%5Crho& alt=&\rho& eeimg=&1&&和&img src=&https://www.zhihu.com/equation?tex=L_i& alt=&L_i& eeimg=&1&&都是常数,可以提到积分外面。公式变成:&/p&&img src=&https://www.zhihu.com/equation?tex=L_o%3D%5Crho+L_i%5Cint_%5COmega+cos%28%5Ctheta_i%29d%5Comega_i& alt=&L_o=\rho L_i\int_\Omega cos(\theta_i)d\omega_i& eeimg=&1&&&br&&p&需要的计算积分的部分只剩下&img src=&https://www.zhihu.com/equation?tex=%5Cint_%5COmega+cos%28%5Ctheta_i%29d%5Comega_i& alt=&\int_\Omega cos(\theta_i)d\omega_i& eeimg=&1&&&/p&&p&这个积分很多人都不认得,这里简单介绍一下,这是立体角积分。所谓立体角就是从球心作一个圆锥体,圆锥和球体相交的面就是立体角。&img src=&https://www.zhihu.com/equation?tex=d%5Comega_i& alt=&d\omega_i& eeimg=&1&&就是这个面无穷小时候的状态,也就是球体表面的微元。如果把上面的&img src=&https://www.zhihu.com/equation?tex=cos%28%5Ctheta_i%29& alt=&cos(\theta_i)& eeimg=&1&&去掉,&img src=&https://www.zhihu.com/equation?tex=%5Cint_%5COmega+d+%5Comega_i& alt=&\int_\Omega d \omega_i& eeimg=&1&&其实就是求球体表面积。&/p&&p&所以立体角的积分计算的时候可以换成我们熟悉的球体表面积分的方法来算。&/p&&img src=&https://www.zhihu.com/equation?tex=%5Cint_%7B%5COmega%7Dd%5Comega_i+%3D+%5Cint_%7B-%5Cpi%7D%5E%7B%5Cpi%7D%5Cint_%7B0%7D%5E%7B%5Cfrac%7B%5Cpi%7D%7B2%7D%7Dsin%28%5Ctheta%29d%5Ctheta+d+%5Cphi+& alt=&\int_{\Omega}d\omega_i = \int_{-\pi}^{\pi}\int_{0}^{\frac{\pi}{2}}sin(\theta)d\theta d \phi & eeimg=&1&&&br&&p&将&img src=&https://www.zhihu.com/equation?tex=cos%28%5Ctheta_i%29& alt=&cos(\theta_i)& eeimg=&1&&放回公式&img src=&https://www.zhihu.com/equation?tex=+%5Cint_%7B-%5Cpi%7D%5E%7B%5Cpi%7D%5Cint_%7B0%7D%5E%7B%5Cfrac%7B%5Cpi%7D%7B2%7D%7Dsin%28%5Ctheta%29cos%28%5Ctheta%29d%5Ctheta+d+%5Cphi+& alt=& \int_{-\pi}^{\pi}\int_{0}^{\frac{\pi}{2}}sin(\theta)cos(\theta)d\theta d \phi & eeimg=&1&&&br&&/p&&p&这里简单推导一下&/p&&p&因为&br&&/p&&img src=&https://www.zhihu.com/equation?tex=2sin%28%5Ctheta%29cos%28%5Ctheta%29+%3D+cos%28%5Ctheta%29sin%28%5Ctheta%29%2Bcos%28%5Ctheta%29sin%28%5Ctheta%29%3DD%28sin%28%5Ctheta%29sin%28%5Ctheta%29%29& alt=&2sin(\theta)cos(\theta) = cos(\theta)sin(\theta)+cos(\theta)sin(\theta)=D(sin(\theta)sin(\theta))& eeimg=&1&&&p&所以里面一层的积分可以写成&/p&&img src=&https://www.zhihu.com/equation?tex=%5Cint_%7B0%7D%5E%7B%5Cfrac%7B%5Cpi%7D%7B2%7D%7Dsin%28%5Ctheta%29cos%28%5Ctheta%29d%5Ctheta%3D%5Cfrac12%5Cleft%5B+sin%5E2%28%5Cfrac%5Cpi2%29-sin%5E2%280%29+%5Cright%5D%3D%5Cfrac12+& alt=&\int_{0}^{\frac{\pi}{2}}sin(\theta)cos(\theta)d\theta=\frac12\left[ sin^2(\frac\pi2)-sin^2(0) \right]=\frac12 & eeimg=&1&&&br&&p&再放入外层的积分&/p&&img src=&https://www.zhihu.com/equation?tex=%5Cint_%7B%5COmega%7Dcos%28%5Ctheta_i%29d%5Comega_i+%3D+%5Cint_%7B-%5Cpi%7D%5E%7B%5Cpi%7D%5Cint_%7B0%7D%5E%7B%5Cfrac%7B%5Cpi%7D%7B2%7D%7Dsin%28%5Ctheta%29cos%28%5Ctheta%29d%5Ctheta+d+%5Cphi+%3D%5Cfrac12+%5Cint_%7B-%5Cpi%7D%5E%7B%5Cpi%7Dd%5Cphi%3D%5Cpi& alt=&\int_{\Omega}cos(\theta_i)d\omega_i = \int_{-\pi}^{\pi}\int_{0}^{\frac{\pi}{2}}sin(\theta)cos(\theta)d\theta d \phi =\frac12 \int_{-\pi}^{\pi}d\phi=\pi& eeimg=&1&&&p&得到&br&&img src=&https://www.zhihu.com/equation?tex=L_o+%3D+%5Cpi%5Crho+L_i& alt=&L_o = \pi\rho L_i& eeimg=&1&&&br&&/p&&p&这里我们考虑一下,如果所有方向都有射向球面的光,而且每个方向的光强度都等于&img src=&https://www.zhihu.com/equation?tex=L_i& alt=&L_i& eeimg=&1&&,那么穿过球面的所有光通量之和,应该等于光强度乘以半球面积。&/p&&img src=&https://www.zhihu.com/equation?tex=F_i%3D2%5Cpi+L_i& alt=&F_i=2\pi L_i& eeimg=&1&&&br&&p&为了保持能量守恒,这所有的光通量应该全部射出球面,射出的光也应该每个方向强度都相同。上面我们已经按照公式算出一个方向的光强度&img src=&https://www.zhihu.com/equation?tex=L_o& alt=&L_o& eeimg=&1&&,如果所有射出方向光强度都相同的话同样可以计算射出球面的光通量:&/p&&img src=&https://www.zhihu.com/equation?tex=F_o+%3D+2%5Cpi+L_o%3D+2%5Cpi%2A%5Cpi%5Crho+L_i& alt=&F_o = 2\pi L_o= 2\pi*\pi\rho L_i& eeimg=&1&&&br&&p&这里要让能量守恒,射入的光通量和射出的光通量相等&img src=&https://www.zhihu.com/equation?tex=F_i%3DF_o& alt=&F_i=F_o& eeimg=&1&&,显然只有让&img src=&https://www.zhihu.com/equation?tex=%5Crho+%3D+%5Cfrac1%5Cpi& alt=&\rho = \frac1\pi& eeimg=&1&&。&/p&&p&又一个谜团揭开了。&/p&&h2&比微小更微小的面&/h2&&p&接下来我们再讨论图形学里面另一个著名的模型,就是微表面。微表面结构是在立体角的计算架构上发明的。上面说过微元的投影可以等同于平面的投影。这样我们可以想象视线落点扩大为一个平面,称为几何平面,而垂直视线的立体角微元也扩大为一个平面,称为投影平面。因为几何平面扩大了一个点的几何信息容量,我们可以在上面随意做一些起伏,这就是微表面。微表面要保证一点就是不论如何随即起伏,它在几何平面上的投影面积必须为1,就是归一化。投影平面有一个固定面积&img src=&https://www.zhihu.com/equation?tex=cos%28%5Ctheta_o%29%3D%5Comega_o%5Ccdot%5Comega_g& alt=&cos(\theta_o)=\omega_o\cdot\omega_g& eeimg=&1&&,是单位面积1投影在投影平面上的大小。微表面在投影平面的投影大小也应该是 &img src=&https://www.zhihu.com/equation?tex=cos%28%5Ctheta_o%29& alt=&cos(\theta_o)& eeimg=&1&&,但如图中所示,微表面有一些区域会被自己遮挡住,这部分称为阴影区域,用&img src=&https://www.zhihu.com/equation?tex=G%28%5Comega_o%2C%5Comega_i%29& alt=&G(\omega_o,\omega_i)& eeimg=&1&&表示。&/p&&p&另一个元素D是微表面法线的分布函数,作为函数就是输入一个法线向量,输出一个&img src=&https://www.zhihu.com/equation?tex=%5Cleft%5B+0%2C1+%5Cright%5D+& alt=&\left[ 0,1 \right] & eeimg=&1&&的数值,而且这个函数在所有方向上的积分应该为1。首先能想到的当然是高斯正态分布函数。实际上常用的Beckmann分布就是对高斯正态分布略微修改的结果。&/p&&p&这样我们可以得到一个等式。&/p&投影面积&img src=&https://www.zhihu.com/equation?tex=+%3D+%5Cint_%5COmega+G%28%5Comega_o%2C%5Comega_i%29+%28%5Comega_o%5Ccdot%5Comega_n%29D%28+%5Comega_n%29d%5Comega_n%3Dcos%28%5Ctheta_o%29& alt=& = \int_\Omega G(\omega_o,\omega_i) (\omega_o\cdot\omega_n)D( \omega_n)d\omega_n=cos(\theta_o)& eeimg=&1&&&figure&&img src=&https://pic4.zhimg.com/2bc042cc322b681913fe_b.jpg& data-rawwidth=&1190& data-rawheight=&690& class=&origin_image zh-lightbox-thumb& width=&1190& data-original=&https://pic4.zhimg.com/2bc042cc322b681913fe_r.jpg&&&/figure&&p&其中求阴影区域的大小是个麻烦的问题,因为G是在积分里的。实际上图形学在计算阴影区域的时候做了一个大胆的假设,认为阴影区域是一个全局几何性质和局部的法线没有关系,也就是说微表面的局部变化不会改变阴影区域的大小。这里完全是一个假设,没有经过数学证明。但可以画个图来说明这个假设是怎么来的。图中可以看到G区域不论如何弯曲都不会影响到阴影区域的大小。借着这个假设我们可以把G项从积分里拿出来,因为它和被积微元&img src=&https://www.zhihu.com/equation?tex=%5Comega_n& alt=&\omega_n& eeimg=&1&&没有关系。我们反而可以借助上面的等式来计算G。&/p&&br&&img src=&https://www.zhihu.com/equation?tex=G%28%5Comega_n%29+%3D+%5Cfrac%7Bcos%28%5Ctheta_o%29%7D%7B+%5Cint_%5COmega+%28%5Comega_o%5Ccdot%5Comega_n%29D%28+%5Comega_n%29d%5Comega_n%7D& alt=&G(\omega_n) = \frac{cos(\theta_o)}{ \int_\Omega (\omega_o\cdot\omega_n)D( \omega_n)d\omega_n}& eeimg=&1&&&p&这样就能按照公式推导出不同的分布函数D所对应的阴影函数G。&/p&&p&重新画一个图来说明光照模型从光线入射到出射的路程上都有哪些元素。&br&&/p&&figure&&img src=&https://pic4.zhimg.com/6d5f83cf3d5eeaad19904b_b.jpg& data-rawwidth=&395& data-rawheight=&313& class=&content_image& width=&395&&&/figure&&p&图中可以看到,微表面,几何平面,入射平面,出射平面,各自对应的面积是几何面积,入射面积,出射面积,还有入射光通量,出射光通量,入射亮度,出射亮度。&/p&&p&现在我们有了一个很对称的模型,入射亮度x入射面积=入射光通量,出射亮度x出射面积=出射光通量。为了保持能量守恒入射光通量应该等于出射光通量。这里稍微不同的是入射光通量要乘以菲涅尔系数,这是一个光学性质,可以简单认为物体吸收了部分光子。关于菲涅尔系数以后有机会再详细表述,这里先把模型推导下去。&img src=&https://www.zhihu.com/equation?tex=dF_i& alt=&dF_i& eeimg=&1&&是入射光通量,&img src=&https://www.zhihu.com/equation?tex=dFo& alt=&dFo& eeimg=&1&&是出射光通量,&img src=&https://www.zhihu.com/equation?tex=F%28%5Comega_o%29& alt=&F(\omega_o)& eeimg=&1&&是菲涅耳系数,加上微分符号是因为都是在微元表面计算的。&/p&&img src=&https://www.zhihu.com/equation?tex=dF_o+%3D+F%28%5Comega_o%29dF_i& alt=&dF_o = F(\omega_o)dF_i& eeimg=&1&&&p&入射亮度&img src=&https://www.zhihu.com/equation?tex=L_i& alt=&L_i& eeimg=&1&&,入射面积&img src=&https://www.zhihu.com/equation?tex=D_i& alt=&D_i& eeimg=&1&&,出射面积&img src=&https://www.zhihu.com/equation?tex=D_o& alt=&D_o& eeimg=&1&&,出射亮度&img src=&https://www.zhihu.com/equation?tex=L_o& alt=&L_o& eeimg=&1&&。要求的就是&img src=&https://www.zhihu.com/equation?tex=L_o& alt=&L_o& eeimg=&1&&。&br&&/p&&img src=&https://www.zhihu.com/equation?tex=L_o+%3DLi+%5Cfrac%7BF%28%5Comega_o%29Di%7D%7BD_o%7D& alt=&L_o =Li \frac{F(\omega_o)Di}{D_o}& eeimg=&1&&&br&&p&出射面积很好计算,就是几何面积在出射平面上的投影面积。&/p&&img src=&https://www.zhihu.com/equation?tex=D_o+%3D+cos%28%5Ctheta_o%29dA& alt=&D_o = cos(\theta_o)dA& eeimg=&1&&&p&在微表面结构下,我们可以认为只有部分微表面起到了发射作用。因为微表面朝向不定,有可能把光线发射到视线看不到的位置。只有微表面中正好能把光线从入射角度反射到视线角度的那些部分起到了作用。这一部分的法线正好在入射角度和视线角度的中间,称为半向量角:&/p&&img src=&https://www.zhihu.com/equation?tex=%5Comega_h+%3D+%5Cfrac%7B+%5Comega_i%2B%5Comega_o%7D%7B2%7D& alt=&\omega_h = \frac{ \omega_i+\omega_o}{2}& eeimg=&1&&&br&&p&&img src=&https://www.zhihu.com/equation?tex=dA%28%5Comega_h%29& alt=&dA(\omega_h)& eeimg=&1&&是微表面中法线方向为&img src=&https://www.zhihu.com/equation?tex=%5Comega_h& alt=&\omega_h& eeimg=&1&&的面积,这个面积投影到入射平面上,就是入射面积。&br&&/p&&img src=&https://www.zhihu.com/equation?tex=D_i%3D+cos%28%5Ctheta_h%29dA%28%5Comega_h%29+& alt=&D_i= cos(\theta_h)dA(\omega_h) & eeimg=&1&&&br&&p&设几何平面的微元面积为&img src=&https://www.zhihu.com/equation?tex=dA& alt=&dA& eeimg=&1&&,微表面中法线方向为&img src=&https://www.zhihu.com/equation?tex=%5Comega_h& alt=&\omega_h& eeimg=&1&&的概率为&img src=&https://www.zhihu.com/equation?tex=D%28%5Comega_h%29& alt=&D(\omega_h)& eeimg=&1&&,在几何平面的投影面积就是&img src=&https://www.zhihu.com/equation?tex=D%28%5Comega_h%29dA& alt=&D(\omega_h)dA& eeimg=&1&&,反过来要求微表面中法线方向为&img src=&https://www.zhihu.com/equation?tex=%5Comega_h& alt=&\omega_h& eeimg=&1&&的面积大小,就要把&img src=&https://www.zhihu.com/equation?tex=D%28%5Comega_h%29dA& alt=&D(\omega_h)dA& eeimg=&1&&投影回垂直于&img src=&https://www.zhihu.com/equation?tex=%5Comega_h& alt=&\omega_h& eeimg=&1&&的平面,需要使用雅克比行列式计算投影。&br&&/p&&img src=&https://www.zhihu.com/equation?tex=dA%28%5Comega_h%29%3D%5Cleft%7C+%5Cleft%7C%5Cfrac%7B%5Cpartial%5Comega_h%7D%7B%5Cpartial%5Comega_o%7D%5Cright%7C++%5Cright%7C++D%28%5Comega_h%29dA& alt=&dA(\omega_h)=\left| \left|\frac{\partial\omega_h}{\partial\omega_o}\right|
D(\omega_h)dA& eeimg=&1&&&br&&br&&img src=&https://www.zhihu.com/equation?tex=%5Cleft%7C+%5Cleft%7C%5Cfrac%7B%5Cpartial%5Comega_h%7D%7B%5Cpartial%5Comega_o%7D%5Cright%7C++%5Cright%7C+%3D%5Cfrac%7B%5Comega_i%5Ccdot%5Comega_h%7D%7B%5Cleft%7C+%5Comega_o%2B%5Comega_i+%5Cright%7C%5E2+%7D%3D+%5Cfrac%7B1%7D%7B4cos%28%5Ctheta_h%29%7D& alt=&\left| \left|\frac{\partial\omega_h}{\partial\omega_o}\right|
\right| =\frac{\omega_i\cdot\omega_h}{\left| \omega_o+\omega_i \right|^2 }= \frac{1}{4cos(\theta_h)}& eeimg=&1&&&br&&p&为什么要用雅克比行列式。如图所示,&img src=&https://www.zhihu.com/equation?tex=%5Ctheta_o& alt=&\theta_o& eeimg=&1&&变动的时候,&img src=&https://www.zhihu.com/equation?tex=%5Ctheta_h& alt=&\theta_h& eeimg=&1&&变动的幅度不同。因为微元的大小和这个变动有关,所以并不是感觉上两个面积应该相等。还有一个直观的理解,在&img src=&https://www.zhihu.com/equation?tex=%5Comega_o& alt=&\omega_o& eeimg=&1&&上面画一小段圆弧,然后将&img src=&https://www.zhihu.com/equation?tex=%5Comega_o& alt=&\omega_o& eeimg=&1&&平移到&img src=&https://www.zhihu.com/equation?tex=%5Comega_i& alt=&\omega_i& eeimg=&1&&上,等于做&img src=&https://www.zhihu.com/equation?tex=%5Comega_o& alt=&\omega_o& eeimg=&1&&和&img src=&https://www.zhihu.com/equation?tex=%5Comega_i& alt=&\omega_i& eeimg=&1&&的向量加法,这时候这一小段圆弧在半球上的投影就是&img src=&https://www.zhihu.com/equation?tex=%5Comega_h& alt=&\omega_h& eeimg=&1&&的微元面积。&/p&&figure&&img src=&https://pic3.zhimg.com/27c18e1a0b0b7f87af958cf_b.jpg& data-rawwidth=&456& data-rawheight=&353& class=&origin_image zh-lightbox-thumb& width=&456& data-original=&https://pic3.zhimg.com/27c18e1a0b0b7f87af958cf_r.jpg&&&/figure&&br&&p&雅克比行列式的推导详见附录。&/p&&p&公式整理之后得到&/p&&img src=&https://www.zhihu.com/equation?tex=L_o+%3D+Li%5Cfrac%7BF%28%5Comega_o%29Di%7D%7BD_o%7D%3DLi+%5Cfrac%7BF%28%5Comega_o%29D_i%7D%7B+cos%28%5Ctheta_o%29dA%7D%3D%5Cleft%7C+%5Cleft%7C%5Cfrac%7B%5Cpartial%5Comega_h%7D%7B%5Cpartial%5Comega_o%7D%5Cright%7C++%5Cright%7CLi%5Cfrac%7BF%28%5Comega_o%29D%28%5Comega_h%29G%28%5Comega_o%2C%5Comega_i%29cos%28%5Ctheta_h%29dA%7D%7Bcos%28%5Ctheta_o%29dA%7D& alt=&L_o = Li\frac{F(\omega_o)Di}{D_o}=Li \frac{F(\omega_o)D_i}{ cos(\theta_o)dA}=\left| \left|\frac{\partial\omega_h}{\partial\omega_o}\right|
\right|Li\frac{F(\omega_o)D(\omega_h)G(\omega_o,\omega_i)cos(\theta_h)dA}{cos(\theta_o)dA}& eeimg=&1&&&br&&img src=&https://www.zhihu.com/equation?tex=L_o+%3D+Li%5Cfrac%7BF%28%5Comega_o%29D%28%5Comega_h%29G%28%5Comega_o%2C%5Comega_i%29%7D%7B4cos%28%5Ctheta_o%29%7D& alt=&L_o = Li\frac{F(\omega_o)D(\omega_h)G(\omega_o,\omega_i)}{4cos(\theta_o)}& eeimg=&1&&&br&&p&大功告成,希望这篇短文能帮助图形学的初学者建立对光照,渲染模型的直观认识。&/p&&br&&br&&p&附录:雅可比行列式推导&/p&&p&设&img src=&https://www.zhihu.com/equation?tex=%5Cmu+%3D+cos%28%5Ctheta_h%29& alt=&\mu = cos(\theta_h)& eeimg=&1&&&/p&&p&出射向量可以写成&br&&/p&&img src=&https://www.zhihu.com/equation?tex=%5Comega_o+%3D+%28%5Csqrt%7B1-%5Cmu%5E2%7D+%2C0%2C%5Cmu%29& alt=&\omega_o = (\sqrt{1-\mu^2} ,0,\mu)& eeimg=&1&&&br&&p&半向量作为笛卡尔坐标函数(也可以看作坐标系),写成&br&&/p&&img src=&https://www.zhihu.com/equation?tex=%5Comega_h%28x_a%2Cy_a%29%3D%28x_a%2Cy_a%2C%5Csqrt%7B1-x_a%5E2-y_a%5E2%7D+%29& alt=&\omega_h(x_a,y_a)=(x_a,y_a,\sqrt{1-x_a^2-y_a^2} )& eeimg=&1&&&br&&p&出射向量和半向量的关系为&/p&&img src=&https://www.zhihu.com/equation?tex=%5Comega_o+%3D+2%28%5Comega_h%5Ccdot%5Comega_i%29%5Comega_h-%5Comega_i& alt=&\omega_o = 2(\omega_h\cdot\omega_i)\omega_h-\omega_i& eeimg=&1&&&br&&p&出射向量也写为笛卡尔坐标函数,写成&/p&&img src=&https://www.zhihu.com/equation?tex=x+%3D+2%28%5Csqrt%7B1-%5Cmu%5E2%7Dx_a%2B%5Cmu%5Csqrt%7B1-x_a%5E2-y_a%5E2%7D+%29x_a-%5Csqrt%7B1-%5Cmu%5E2%7D& alt=&x = 2(\sqrt{1-\mu^2}x_a+\mu\sqrt{1-x_a^2-y_a^2} )x_a-\sqrt{1-\mu^2}& eeimg=&1&&&br&&img src=&https://www.zhihu.com/equation?tex=y+%3D+2%28%5Csqrt%7B1-%5Cmu%5E2%7Dx_a%2B%5Cmu%5Csqrt%7B1-x_a%5E2-y_a%5E2%7D+%29y_a& alt=&y = 2(\sqrt{1-\mu^2}x_a+\mu\sqrt{1-x_a^2-y_a^2} )y_a& eeimg=&1&&&br&&p&这样可以求行列式,结果为&/p&&img src=&https://www.zhihu.com/equation?tex=J%3D4%5Cmu%5E2& alt=&J=4\mu^2& eeimg=&1&&&br&&p&因为笛卡尔坐标和球坐标系还差了一个&img src=&https://www.zhihu.com/equation?tex=%5Cmu& alt=&\mu& eeimg=&1&&&/p&&p&最后等式写成&/p&&img src=&https://www.zhihu.com/equation?tex=%5Cmu+d%5Comega_0+%3D+4%5Cmu%5E2+d+%5Comega_h& alt=&\mu d\omega_0 = 4\mu^2 d \omega_h& eeimg=&1&&&br&&p&所以&br&&img src=&https://www.zhihu.com/equation?tex=%5Cleft%7C+%5Cleft%7C%5Cfrac%7B%5Cpartial%5Comega_h%7D%7B%5Cpartial%5Comega_o%7D%5Cright%7C++%5Cright%7C+%3D+%5Cfrac%7B1%7D%7B4cos%28%5Ctheta_h%29%7D& alt=&\left| \left|\frac{\partial\omega_h}{\partial\omega_o}\right|
\right| = \frac{1}{4cos(\theta_h)}& eeimg=&1&&&/p&
写在前面开始学习图形学的人,尤其是计算机专业而不是数学专业,可能都有一个感觉,很多图形学公式让人完全摸不着头脑。这些公式看起来是微积分公式,但和高数里的不太一样,也不能套用高数课本上的方法推导。不少图形学教科书写得过于简略,导致初学者看这…
&figure&&img src=&https://pic1.zhimg.com/v2-775ad1daeb8de_b.jpg& data-rawwidth=&1280& data-rawheight=&720& class=&origin_image zh-lightbox-thumb& width=&1280& data-original=&https://pic1.zhimg.com/v2-775ad1daeb8de_r.jpg&&&/figure&&p&[按] 在上个月 [] 的 2016 金山技术开放日上,俺做了一个游戏引擎相关的分享。结束之后,俺断断续续地在零碎时间里把分享的内容整理了一下,为每张幻灯片配上了简单的文字注解,就形成了这篇小结。&/p&&p&参与了现场交流的同学可能会发现,这篇文字版跟现场版相比,虽然幻灯片保持原状,但文字注解部分略有出入 (增加的内容主要是第二部分“评估,运用和改造”,这一部分在现场时因为时间关系说得非常简略),还请见谅。 &/p&&h2&内容提纲&/h2&&figure&&img src=&https://pic4.zhimg.com/v2-f6edb96c23e_b.jpg& data-rawwidth=&1280& data-rawheight=&720& class=&origin_image zh-lightbox-thumb& width=&1280& data-original=&https://pic4.zhimg.com/v2-f6edb96c23e_r.jpg&&&/figure&&ul&&li&游戏引擎的&strong&十年变迁 ()&/strong& (对过去十年的游戏引擎发展的简要小结)&/li&&li&游戏引擎的&strong&评估,运用和改造&/strong& (对一次技术迭代周期内的主要实践的归纳)&/li&&li&游戏引擎的&strong&下一个十年&/strong& (一些很零碎的对未来十年可能值得深入的技术的思考和启发点)&/li&&/ul&&h2&第一部分:游戏引擎的&strong&十年变迁 ()&/strong&&/h2&&p&在这次分享中,我着重对比了个人相对有所了解的五款商业引擎,分别是 Unity、Unreal、CryEngine、Gamebryo 和 Torque。而下面划掉的开源引擎 OGRE/Irrlicht/cocos2d-x 性质不同,放在一起对比并不公平,所以排除在这一回讨论的范围以外。&/p&&p&&figure&&img src=&https://pic1.zhimg.com/v2-2df611f59a7fb0cb577fc9d_b.jpg& data-rawwidth=&1280& data-rawheight=&720& class=&origin_image zh-lightbox-thumb& width=&1280& data-original=&https://pic1.zhimg.com/v2-2df611f59a7fb0cb577fc9d_r.jpg&&&/figure&前几个 (Unity、Unreal、CryEngine) 比较知名,就不多做介绍了。这里简单介绍一下 Gamebryo 和 Torque。&/p&&p&Gamebryo 是当年 () 国内非常流行的商业引擎之一,业内不少大厂都曾拿这个引擎做过各类项目,也培养出一批熟悉 Gamebryo 的引擎程序员 (包括我在内——虽然我是经由 Unreal 2 启蒙,但只有在 Gamebryo 上的工作才让我看到了自己曾尝试着去实现的理想中引擎的影子)。 Gamebryo 的最强之处在于其设计上的普适性——与 Unreal 和 CryEngine 内含的 FPS 基因不同,Gamebryo 对于 MMO、赛车、休闲、动作等国内常见的游戏类型均能很好地适配。从基于 Gamebryo 的两大代表作《文明4》和《辐射3》在游戏类型上的跨度,就可以看出这种不同寻常的通用性。它的整体架构简洁有力,模块边界符合直觉,模块之间耦合性低,模块实现内聚性强。这些特性使得它的学习曲线相对平缓,新人容易培训和培养,因而也是我个人偏爱的一款引擎。Gamebryo 的弱点在于自身工具链不足,且与第三方的集成度偏低,以及由于前两者导致的引擎提升潜力受限。&/p&&p&而 Torque 则是前些年的低成本商业引擎的代表,其特色在于&strong&简单易用的编辑器&/strong&和&strong&独具特色的脚本&/strong&,基本上可以看作是 2010 年以前的 “史前版 Unity”,前述两大特色也被后来者 Unity 继承并发扬光大。&/p&&p&&figure&&img src=&https://pic3.zhimg.com/v2-d9e64f0ba74_b.jpg& data-rawwidth=&1280& data-rawheight=&720& class=&origin_image zh-lightbox-thumb& width=&1280& data-original=&https://pic3.zhimg.com/v2-d9e64f0ba74_r.jpg&&&/figure&在这张基本时间线的图上,我们可以看到在过去十年里这五款引擎大致的发展轨迹。其中可以看到,贯穿始末的是 Unreal 和 CryEngine,而另外三款引擎则流行于特定的历史时期。而即使是 Unreal 和 CryEngine,在漫长的发展过程中也自有其高峰和低谷。&/p&&p&&figure&&img src=&https://pic4.zhimg.com/v2-6a7bd16cb33fccbd4973_b.jpg& data-rawwidth=&1280& data-rawheight=&720& class=&origin_image zh-lightbox-thumb& width=&1280& data-original=&https://pic4.zhimg.com/v2-6a7bd16cb33fccbd4973_r.jpg&&&/figure&有了它们各自的时间线打底,我把这五个引擎各自比较有影响力的版本标注了上来,这样在以五年为跨度的单位 (垂直虚线) 上,每个引擎曾做过的大动作就一目了然了。有过相应项目经历的程序员,对这些曾经活跃一时的版本一定不会陌生。这里我们先不针对单独的版本一一细说,只是对每个引擎的大致活动情况了解一个大概。&/p&&p&&figure&&img src=&https://pic3.zhimg.com/v2-77f8c3a01ccb74fb4d981a676e0bd340_b.jpg& data-rawwidth=&1280& data-rawheight=&720& class=&origin_image zh-lightbox-thumb& width=&1280& data-original=&https://pic3.zhimg.com/v2-77f8c3a01ccb74fb4d981a676e0bd340_r.jpg&&&/figure&细心的同学也许已经注意到了,看起来很凑巧的是,这些引擎的大版本或多或少地聚集在我标出的以五年跨度为单位的竖直虚线附近。而这其中最为显著的就是 Unreal。这仅仅是一个巧合么,还是说,跟其他的时期相比,这些特定的年份有什么不同?&/p&&p&&figure&&img src=&https://pic3.zhimg.com/v2-ba2b8199ddf97a589c8df575_b.jpg& data-rawwidth=&1280& data-rawheight=&720& class=&origin_image zh-lightbox-thumb& width=&1280& data-original=&https://pic3.zhimg.com/v2-ba2b8199ddf97a589c8df575_r.jpg&&&/figure&站在 2016 往回看,我们意识到——在这十年中,这是仅有的两次有着整体产业级影响力的根本性的变迁和进化。如果说是两次技术革命 (Revolution) 可能不甚恰当,但要说这是两次影响范围甚广,使得游戏产业的深度和方向产生了根本性的延展的两次行业范围的演化 (Evolution),似乎并不为过。&/p&&p&其中第一次演化,是起源于 2002 年并于四年后 (约 2006 年前后) 渗透到整个行业的,被广泛应用于游戏引擎中的&strong&可编程图形流水线&/strong& (Programmable Graphics Pipeline);而第二次演化,则是起源于 2007 年的初代 iPhone 并在四年后 (约 2011 年前后) 渗透到整个行业的,改变了整个游戏行业生态的&strong&移动平台游戏开发&/strong& (Mobile Development)。&/p&&p&可编程流水线刚刚被引入到实时图形渲染的领域中时,是 3D 图形处理器发展最迅速的时期,从 Geforce 3 开始,nVidia 在硬件方面每半年就有一次较大的更新,同时期的各类图形技术一时间层出不穷,可谓是画面渲染质量提高最快的黄金时代。在2006年时,我任职于育碧上海,有幸参与了 &a href=&https://link.zhihu.com/?target=https%3A//zh.wikipedia.org/wiki/%25E7%25BB%%E5%E8%25A3%2582%25EF%25BC%259A%25E5%258F%258C%25E9%E9%%25E8%25B0%258D& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&“细胞分裂:双面间谍”&/a& 的开发,把&a href=&https://link.zhihu.com/?target=http%3A//www.gamersky.com/news/89.shtml& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&当时的游戏截图&/a& (注意最后一张是游戏中上海关卡的东方明珠夜景图) 拿到现在来看都并不过时。得益于复杂度迅速提高的材质和各种开脑洞的特效实现的不断涌现,整个行业在图形和渲染表现方面的探索非常迅速和深入,通过真实感图形渲染出来的游戏画面达到前所未有的逼真程度。&/p&&p&而移动平台的游戏开发则是另一次重要的变迁。在新的平台上,随着大量非传统核心类玩家的涌入,人们有着与传统 PC/Console 迥异的关注点,从单一的超高画面和沉浸感追求拓展为更多元的交互和体验。可以说直到如今,我们仍处于这次演化的余波之中。&/p&&p&我们注意到,这两次演化的共同点在于:它们皆非短时间内剧烈的革命,而都是跨度若干年,并于图中所标示的年份上遍及整个行业的产业级演化。而不同之处是:前一次的演化,在图形领域内前所未有地挖掘了电子游戏作为可视化交互艺术可能触及的“&strong&深度&/strong&”,而后一次则通过全新的平台和交互语言大大拓宽了行业所能触及的“&strong&广度&/strong&”。&/p&&p&&figure&&img src=&https://pic4.zhimg.com/v2-e74efc21c4ee53a317b6df_b.jpg& data-rawwidth=&1280& data-rawheight=&720& class=&origin_image zh-lightbox-thumb& width=&1280& data-original=&https://pic4.zhimg.com/v2-e74efc21c4ee53a317b6df_r.jpg&&&/figure&回到时间线中,我们可以发现,Unreal 和 Unity 对两次行业的演化有着强烈的预期和精准的判断——在这些决定命运的转折点上,他们要么在巨变来临前就做好了充分的准备,在行业变迁中始终屹立不倒,要么凭借着顺应趋势的特性集和服务乘风而起。&/p&&p&&figure&&img src=&https://pic4.zhimg.com/v2-eba4bb7d373d5932cf2ccad1afedbffe_b.jpg& data-rawwidth=&1280& data-rawheight=&720& class=&origin_image zh-lightbox-thumb& width=&1280& data-original=&https://pic4.zhimg.com/v2-eba4bb7d373d5932cf2ccad1afedbffe_r.jpg&&&/figure&现在我们重点来看一看 Gamebryo 、CryEngine 和 Turque,它们是在第一次行业演化中崛起的佼佼者,在各自的量级上均是罕有敌手。Gamebryo 的标准材质 (NiStandardMaterial) 对应着可编程流水线在现代商业引擎中最简易和灵活的实现,是那个年代 (2006) 最容易上手的商业引擎之一;CryEngine 的图形表现在孤岛危机时代是最强的引擎之一 (这里的两个“之一”二字仅仅是意思一下);而 Torque 更是为小型独立开发者提供了极易上手的编辑器和非常丰富的着色器开发套件 (基本上可以认为他们自己弄了个内置的低配版 Asset Store)。所有这些让它们在第一次行业演化中脱颖而出,在&a href=&https://link.zhihu.com/?target=http%3A//devmaster.net/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&http://devmaster.net&/a& (这是那个年代的一个专注游戏引擎和中间件目录的开发网站) 上成为提及率最高的几个商业引擎。&/p&&p&然而时过境迁,在推出了若干有影响力的版本之后,它们不约而同地在 2011 年前后平静了下来。很明显的是,它们并没有意识到时代的趋势,在移动平台崛起时,它们没有做出任何反应,反而把精力用在了事后看起来无关紧要的改进上。&/p&&p&Gamebryo 的用户希望引擎能提供更好的编辑器和工具链,而 Gamebryo 前后尽力做出的两版编辑器虽然看起来很不错但并未达到工业级的成熟度 (很像后期的 cocos2d-x) ——在拥有成功项目的支撑和回馈之前就陷入了兼容性的泥潭。而 CryEngine 似乎落入了“为了强大而强大”的漩涡,并未意识到在图形技术日趋成熟的时代,由于边际效应递减,图形上的优势越来越难以形成差异化。与老对手 Unreal 为 iOS 专门打造的瘦身版 UDK 截然相反的是,CryTek 精心打造的“新版” (Rebranded) CryEngine 甚至似乎刻意在逃避移动平台——它的主要特性是支持 Linux 和下一代主机 (PS4 / XBox One / Wii U)。&/p&&p&Torque 则是这三者中最为惋惜的。按照现在的眼光看,它是商业引擎中最便宜的 (~$200),它的目标群体是小型独立开发者和团体,它的编辑器像 Unity Editor 那样亲切好用,它的脚本 TorqueScript 神似 C#,它甚至有一个 &a href=&https://link.zhihu.com/?target=http%3A//www.garagegames.com/products/torque-3d/add-ons& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Torque 3D Store&/a& (可以看做是 Asset Store 的前身,现在仍然可以访问)。在 StackOverflow 上,你甚至能看到 (2009 年) 这样一个有趣的比较:“&a href=&https://link.zhihu.com/?target=http%3A//stackoverflow.com/questions/1780690/unity-vs-torque-game-engines-and-ide-environment& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Unity vs Torque game engines and IDE environment&/a&” 但是,由于对移动平台的无视,Torque 的用户源源不断地流向了 UDK 和 Unity。&/p&&p&Gamebryo 、CryEngine 和 Turque是三款定位迥异的游戏引擎,却有着极为相似的起落周期——在第一次演化中提供了各自领域最有价值的服务而崛起,和对第二次演化的无视甚至逃避导致的衰落。这三款引擎所有的有影响力的版本均在我们框定的第一次演化和第二次演化之间。在移动平台普及之后,这三款引擎再也没有推出一个有影响力的版本。&/p&&p&&figure&&img src=&https://pic4.zhimg.com/v2-8fb54d114a8a6e1aac8638_b.jpg& data-rawwidth=&1280& data-rawheight=&720& class=&origin_image zh-lightbox-thumb& width=&1280& data-original=&https://pic4.zhimg.com/v2-8fb54d114a8a6e1aac8638_r.jpg&&&/figure&我们拉近视角,近距离观察 Unreal 这个贯穿十年,历经波折却仍然稳步前行的游戏引擎,看看它的步调和动作是如何与时代趋势相匹配的。在图中我们不难看出,Unreal 总是能够捕捉并提前准备好对应的发布——细究每一条细节,Epic Games 在十年里完整地向我们诠释了“&strong&与时俱进&/strong&”的真义——在行业需要深度的时候提供足够的深度,在行业需要广度的时候提供足够的广度。(呃,一不留神成了 Epic 吹了~~)&/p&&p&&figure&&img src=&https://pic3.zhimg.com/v2-171a2c32cc866540caedde_b.jpg& data-rawwidth=&1280& data-rawheight=&720& class=&origin_image zh-lightbox-thumb& width=&1280& data-original=&https://pic3.zhimg.com/v2-171a2c32cc866540caedde_r.jpg&&&/figure&接下来是第一部分的结论——&strong&不是最强的,也不是最新最酷的,更不是最贵的,而是最适应变化的,活了下来。&/strong&这个结论是由达尔文的进化论金句略作修改而来,原文见下面的方框。&/p&&p&(第一部分完)&/p&&h2&第二部分:一次技术迭代周期&/h2&&p&&figure&&img src=&https://pic1.zhimg.com/v2-77e39119ecfc22b171ce31ee983a4e85_b.jpg& data-rawwidth=&1280& data-rawheight=&720& class=&origin_image zh-lightbox-thumb& width=&1280& data-original=&https://pic1.zhimg.com/v2-77e39119ecfc22b171ce31ee983a4e85_r.jpg&&&/figure&如果想要各种姿势自顶向下自底向上巨细无遗地了解游戏引擎的方方面面,可以看 Milo 老师的&a href=&https://zhuanlan.zhihu.com/p/& class=&internal&&游戏程序员的学习之路&/a&,这里就不多说了。我们抓一下挈领,把&strong&游戏引擎的评估&/strong&放在最重要的位置,试着解决一下关于 “How” 的几个问题——“How to evaluate” (评估)、“How to use” (运用)、“How to extend” (改造)。&/p&&h3&游戏引擎的评估&/h3&&p&此前我曾写过一篇文章:&a href=&https://link.zhihu.com/?target=http%3A//gulu-dev.com/post/-tech-evaluation& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&& 如何判断一个技术(中间件/库/工具)的靠谱程度?&/a& 这篇文章里我集中讨论了评估中间件需要注意的一些情况,在文末我写道,&/p&&blockquote&&p&“对中小规模的技术而言,上面的“望,闻,问,切”已经足以应付了。而对大型代码库/框架/引擎而言,又有一套不大一样的评估标准,另有曲径可探,咱们择日另行讨论,此处暂且按下不表。” &/p&&/blockquote&&p&当时给自己挖了个不大不小的坑。两年多过去,现在这个坑终于可以填上了。&/p&&p&&figure&&img src=&https://pic4.zhimg.com/v2-e_b.jpg& data-rawwidth=&1280& data-rawheight=&720& class=&origin_image zh-lightbox-thumb& width=&1280& data-original=&https://pic4.zhimg.com/v2-e_r.jpg&&&/figure&关于引擎评估,首当其冲的是三个简短的问题,我们一个一个来看。&/p&&p&首先,“&strong&是否经受过同类产品的考验&/strong&”是一个决定性的因素,这不仅仅意味着能否按时交付,技术风险高低——更多时候,这是你的团队不会因为计划外因素而意外搁浅的重要保障。我们知道,捡到一个存折带来的喜悦远远低于丢失一个等额的存折带来的痛苦,同样的,在幻想你的游戏大卖之前,先尽一切可能确保你的项目不会因为无法控制的因素夭折显然更有意义。没有经过考验的引擎就像是&strong&一杆没上过战场真刀真枪考验过的枪&/strong&,在它有效地为你杀敌之前,先祈祷它不会伤到自己吧。这也是市场上看到的山寨产品 (对国产游戏而言) 和续作 (对 3A 游戏而言) 这么多的直接原因。&/p&&p&其次,“&strong&好招人吗&/strong&”。这个问题表面上看是一个团队管理和人员招聘问题,而实际上却是一个学习曲线和培养成本的问题。在 Unreal 推出 UDK 之前,很多用 Unreal 引擎的小团队在快速出了原型之后都难以为继,这是因为那时的 Unreal 技术人员的培养成本很高,培养时间很长,在人员快速扩充时期,小团队很难消化新手和准新手给团队带来的负担。这就造成了巨大的反差:两三个高手可以拿着 Unreal 在两个月内迅速出一个华丽的原型,在期望值迅速提高之后,弄来一大票人吭哧了一年多,在项目节点上老板一看,哎哟我去,这可不还是那个原型吗~~等等,好像还不如刚开始那时候稳定了~&/p&&p&最后,“&strong&是否有代码&/strong&”。这个问题在&a href=&https://link.zhihu.com/?target=http%3A//gulu-dev.com/post/-tech-evaluation& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&此前文章&/a&里已有表述,这里引用一下:&/p&&blockquote&&p&最后说一下这里面一条俺认为比较重要的,也是当年带队的MMO项目里,被我列为头条编程规范的原则:&strong&绝对,绝对,绝对不要使用没有100%提供源码的第三方技术。&/strong&这是一条红线,不管这个技术有多强大,都绝对木有例外。程序猿们或多或少都有感触,在编程的世界里,CPU时序的不确定,存储IO的阻塞,其他进程对CPU/内存资源占用造成的扰动,后台进程如杀毒软件偶尔的锁定文件访问,公网路由的拥塞,都为运行着的程序施加了太多不可预知,不可控制的因素。而在这些不可控制的因素里面,允许在自己进程的地址空间内运行一些无法得知其本来面目的代码,是其中最危险也是最容易失控的那一类。反面例子太多,俺就不举了,也免得触物伤怀,影响心境。&/p&&p&......&/p&&p&关于“三个绝对”的问题,俺专门补充说明一下,&/p&&ul&&li&如果所谓的“软件大厂”是第一方,那通常咱们也没啥选择。就比如要在 Windows 上开发 3D 游戏,用闭源的 DirectX 也是理所当然。正如文中所说,所谓“三个绝对”是针对那些几乎总是有得选择的第三方库而言的。&/li&&li&使用软件大厂的闭源技术,也会带来不小的潜在隐患。大公司通常是不太搭理小团队的,如果你掉进的坑恰好在朋友圈里和网上找不到类似的案例或方案,那尝试跟所谓“软件大厂”交流或反馈一般都是做无用功。最终要么花更多的时间吭哧吭哧workaround,要么去掉对应模块了事。&/li&&li&作为程序员,当遇到问题时,你希望的是 a) 通过一路在源码中前后追溯,在解决问题之余,弄清楚前因后果,实实在在地增加自己相关领域的经验和认识呢,还是 b) 对着文档反复猜测和校验自己哪个参数有没有误传?&/li&&/ul&&p&其实到了关键时刻,有代码在手上,就是一颗定心丸,正所谓“&strong&源码之前,了无疑惑&/strong&”。当遇上奇怪的症状时,什么文档都比不上正在运行着的唾手可得的鲜活的代码。&/p&&/blockquote&&p&&figure&&img src=&https://pic3.zhimg.com/v2-d1d76b78b5_b.jpg& data-rawwidth=&1280& data-rawheight=&720& class=&origin_image zh-lightbox-thumb& width=&1280& data-original=&https://pic3.zhimg.com/v2-d1d76b78b5_r.jpg&&&/figure&接下来是针对引擎的两种主要的开发模式的选择,明面上的优势和劣势已经标注在图中了。简单地说,如果“&strong&求快&/strong&” (往往是需求方更强势) 占了上风,往往是“钻进去改”的框架式更合适;而如果“&strong&求稳&/strong&” (往往是实现方更强势) 的占了上风,则往往会以相对严谨的工程化思维来设计架构和实现。有的团队自打一开始就压根就没考虑过这问题,管它三七二十一先改了再说。一上来堆系统堆得很爽,进度喜人,到了后期处处是改到一半改不动的烂摊子,这种时候再加班加点加人手,硬啃硬怼硬编码,拼命拿战术上的勤奋去掩盖战略上的懒惰。&/p&&p&&figure&&img src=&https://pic4.zhimg.com/v2-60dae13fcadfe96c8528c_b.jpg& data-rawwidth=&1280& data-rawheight=&720& class=&origin_image zh-lightbox-thumb& width=&1280& data-original=&https://pic4.zhimg.com/v2-60dae13fcadfe96c8528c_r.jpg&&&/figure&接下来就到了具体内容的考察了。这一页上列出的三个因素是需要考虑的一级要素。为什么说 (从引擎角度看) “这三个因素直接决定了项目按期交付的可能性”呢?这是因为,对于给定的游戏类型和设计,这些因素直接影响到一个项目实际工程量的大小。&/p&&ul&&li&&strong&特性集&/strong& (feature set) 是引擎“&strong&能帮你解决的问题的集合&/strong&”。正如一个 CPU 的指令集那样,引擎的原生实现不仅能帮你省去自己实现这些特性的时间,而且往往是在特定环境下 (给定的问题域内) ,在这个引擎上所能实作出的最佳实践 (Best Practice)。有两个因素会把这种价值给迅速放大:第一,普通游戏开发团队的平均技术水准,通常是低于他们所使用的引擎的研发团队的;第二,普通游戏开发团队对引擎的熟悉程度和运用能力,在一定程度上同样也低于对应的引擎研发团队。所以&strong&已有的特性集与目标项目的匹配情况&/strong&,是我们格外关注的焦点。&/li&&li&&strong&工具链&/strong& (tool-chain) 是引擎提供的“&strong&团队工作流程的内部骨架&/strong&”。缺乏工具链或工具链不成熟的引擎是双刃剑,需要&strong&显著高于平均水准&/strong&的团队才能有效驾驭。Gamebryo 和 OGRE 这两款引擎设计优良,但在工具链上很弱,是两个很好的例子。虽然很多团队淹没在工具制作的泥潭里,但这种不成熟也恰恰给了那些强力团队一个难得的机会,围绕着特定的业务模型构建出&strong&以业务为导向的工具链&/strong&,形成了真正的团队级的核心竞争力,而这种核心竞争力是那些有着成熟工具链的引擎的团队极度难以形成的。总得来说,能力强,不怕延期,敢于投入的团队,才有机会扭转,获得业务导向的工具链带来的红利。&/li&&li&&strong&迭代时间&/strong& (iteration time) 是日常开发中无可争议的 &strong&No. 1 Time Killer&/strong&,直接影响你的工程效率。我在 Unreal 3 上工作时,有大量的场合 (如 Console Build) 是需要静态链接的,而 UE3 的链接奇慢无比,即使在中高配的电脑上也需要不下 10 分钟。而编译速度也很让人抓狂,即使有 Incredibuild 的分摊,一旦不幸改到头文件也是相当感人。运气不好的时候,一个小时改个两三次就一闪而过了 (下面这个 xkcd 真的一点也不夸张)。&/li&&/ul&&p&&figure&&img src=&https://pic3.zhimg.com/v2-27ed4c0bab8b5bfb2afe88_b.jpg& data-rawwidth=&413& data-rawheight=&360& class=&content_image& width=&413&&&/figure&由于在&strong&头文件的包含传染性&/strong&和&strong&预编译的物理依赖关系&/strong&上有欠考虑,不当依赖导致修改时的重编传染性极强,进而导致我们深入研究出一系列“避免动到头文件就能搞定需要的功能”的偏门神功,实在是说多了都是泪。好吧,反正这里已经黑了一把 UE3,干脆再来一个,负负得正吧。UnrealScript 是 Epic 专为虚幻引擎实现的脚本语言,无奈这货跟 C++ 的关系实在太紧密了 (如只要在涉及 native 的情况下改动变量就得重编 C++),紧密到运行时的动态能力已经损失殆尽,几乎已经没法拿来当脚本用了 (比如像 Lua 或 Python 那样轻松地在运行时 make change / run / reload )。好在 UE4 里已经把它去掉了,这里就不多说了。&/p&&p&这三个方面都会直接影响到一个项目的工程时间开销,是一级的重点考察因素。&/p&&p&&figure&&img src=&https://pic2.zhimg.com/v2-aeb1a7b85dc0dd3b2bb5b_b.jpg& data-rawwidth=&1280& data-rawheight=&720& class=&origin_image zh-lightbox-thumb& width=&1280& data-original=&https://pic2.zhimg.com/v2-aeb1a7b85dc0dd3b2bb5b_r.jpg&&&/figure&接下来是次一级的考察因素。这一级的考察因素侧重于引擎的工程质量,毕竟说到底,游戏引擎是一个软件项目——功能再花哨,如果根基不牢,那么做出来的游戏多半也是摇摇欲坠。团队规模越大,受工程质量高低的影响也会越大,严重时会直接影响到整个团队的节奏和士气,进而成为影响交付的难以克服的风险。&/p&&p&我们先来看耐受力 (exception tolerance) 。什么是耐受力?简单说就是对非正常流程的处理能力。能够使用系统性的策略,而不是事到临头草草地 assert 来处理非法输入;能够结构性地把自己可以识别和处置的错误从无数的未定义行为中区分出来。说白了,耐受力意味着“&strong&工程上的安全感&/strong&”。&/p&&p&举个例子,同样是使用未初始化变量,在C/C++里你可能啥事儿没有,也可能直接宕机,也可能在极为偶然的情形下宕机,也可能看起来没啥事儿而过很长时间以后宕机。而在 Lua 里却不会有问题,并不是后者比前者高明,而仅仅是因为它对这一类问题有良好的定义。&/p&&p&再比如说,一个编辑器,指定的操作稍微没有按照事先定义的流程来,立刻 assert ,非常敏感,一有风吹草动就 halt。很多程序员喜欢辩解说“尽早崩溃”是最佳实践——在遇到问题的第一时刻报错,报得越早越好,毕竟越接近问题发生的第一现场越方便他们调试。这个说法本身当然是没问题的——如果你用个默认值糊弄一下,或者是写某种容错逻辑来忍耐了破坏假定的行为,那么错误很有可能会蔓延到之后更加远离出问题的地方才爆发,那时丢失了源头和上下文,查错的成本会变得非常高。&/p&&p&以下详细说明部分节选自俺的一篇未发布的文章,详细说明了一下这个问题。&/p&&blockquote&&p&如果只考虑程序员,不考虑团队中同样依赖每日版本来工作的策划,美术和测试的话,这个思路是没有问题的。然而跟程序员不同,当发生崩溃时,团队内的其他成员能做的非常有限——以最快速度通知程序员,&strong&版本挂了&/strong& (The build is broken!!!)。如果坏的地方正好是他们工作的部分,那么他们只好停下来,等待修好才能继续工作。否则要么一直备有一个可靠的老版本 ,要么手动回滚。&/p&&p&现在请摘下程序员的帽子,假设自己是一个负责“测试多人副本,任务和活动”等业务逻辑相关的测试人员,每测一次都要花不少时间进入测试情境,偶尔甚至需要多人一起协同测试。那么一个跟你的工作毫不相关的底层崩溃,所带来的影响就会被迅速放大,很可能得完版本后的几个小时就在反复尝试和等待之中被消耗掉。&/p&&p&这是一种惊人的浪费。&/p&&p&给程序员巨大便利的“尽早崩溃”,对非程序员来说,意味着日常开发中的每一天,都要冒着被“不可控的因素”延误工作的风险。有人说,正确的姿势难道不是让程序员有更好的自律,在提交前做尽可能充分的测试,确保不要搞破坏吗?是的。可是即使是经验丰富的程序员也不能拍胸脯保证自己 bug-free, 更不能保证由若干人提交的若干“不相干”的改动集成到一起就能无缝地良好工作。让非程序员去承担这种因为版本不成熟导致的效率折扣,是&strong&既不公平也不高效&/strong&的。&/p&&p&说到“巨大便利”,不得不补充一个前缀——“本机上的”,也就是说,只有崩溃恰好发生在制造这个问题的程序员的机器上 (或可以方便地即时远程调试) 时,这种巨大的便利性才得到体现。考虑到发生在非程序员环境下的崩溃,不少情况下是由于环境配置错误等杂音所致,“有经验的”程序员往往不会浪费他们“宝贵的开发时间”,第一时间赶往现场开始分析和调试(打断自己的工作跑去协助调试,满头大汗弄了半天,发现是环境配置的问题/版本问题/别人代码导致的问题,足以唤醒一个温顺的程序猿内心的洪荒之力了)。更多的,他们会在 IM 上回个消息:“嗯,这个功能我提交前测试是正常的——你的环境干净吗?需要的数据都干净地重新生成了吗?第三方库的二进制文件更新了吗?你们几个人测试的版本一致吗?要不你 Cleanup / 重启 / 重新保存 / 重新建个账号试试?”,试图通过尽可能小的时间开销来帮助诊断和解决问题。长远来看,这些试图节省调试时间的沟通,会让“尽早崩溃”所带来的巨大便利慢慢地挥发殆尽。&/p&&p&一个不那么容易觉察却更为严重的系统性问题是,总是采用“尽早崩溃”的实践的团队所产出的代码库,随着系统内不同模块之间的交互(以及随之而来的各种假定)越来越多,往往倾向于通过更多的断言来让系统变得越来越敏感和脆弱。因为,&strong&认真细致地考虑模块间的依赖时序,并系统性地从结构上解决过深的模块间耦合&/strong&,总是比一个简单的断言要复杂得多。&/p&&p&“尽早崩溃”的主张是如此的简洁有力,以至于我们在那些应当通过改良结构,去除耦合来解决问题的时刻,往往简单地选择了使用断言来做一个时序上的约定。这种显式的指定会把系统的坏气味转化成太多的不必要依赖。的确,问题从表面上看起来变得更简单了——谁破坏了断言,导致了崩溃,谁就修呗——实质上,修来修去,把一个本质上可以剥离的简单交互,变成了严重依赖各种时序和条件的“靠巧合工作”的杂耍系统。&/p&&p&你看,“尽早崩溃”的简单性和便利性,在一些情况下反而成了一个让代码质量退化,鼓励系统熵不断增加的问题机制。那么问题来了,在满足了“必要的时候程序应当尽早崩溃”的基础上,还有什么可以选择的实践吗?&/p&&p&(以下略)...&/p&&/blockquote&&p&到这里我应该已经基本说清楚什么是耐受力了。在图中我提到的三点分别是特殊需求,坏数据,破坏性的改动。这些都很直白,通过验证一个引擎在这些方面的表现,我们很容易对它的耐受力作出判断。&/p&&p&&strong&可见性&/strong&也是重要的考虑因素。如果引擎能充分揭示自己的业务流程 (如何运作),生成的各类数据 (如何存储),关键模块的性能开销 (如何优化),那对各类基于引擎的二次开发才能更有信心,才能够最大限度地避免依赖了错误的假定。当出现问题时,也更容易查找和比对。而如何在维护最大的可见性的同时保持良好的封装和较低的耦合,同样是一个很大的话题,这里就不再细说了。&/p&&p&&figure&&img src=&https://pic3.zhimg.com/v2-a2a84b64bfba7a46ed5955f_b.jpg& data-rawwidth=&1280& data-rawheight=&720& class=&origin_image zh-lightbox-thumb& width=&1280& data-original=&https://pic3.zhimg.com/v2-a2a84b64bfba7a46ed5955f_r.jpg&&&/figure&接下来的页面上这些内容是更次要一些的因素,它们大多都很直白,这里我们挑着简单说一下。&/p&&ul&&li&(&strong&teamscale-friendly&/strong&) 不少引擎的工具链适应不了团队级的开发,尤其是大规模团队的开发。这类问题会在团队规模迅速增长,对引擎的平均理解程度迅速降低时暴露出来。&/li&&li&(&strong&3rd-party-friendly&/strong&) 商业引擎通常会有不少与第三方中间件的交互。考察这种交互的一个简单方法是观察那些“三不管”的薄弱地带。此前工作在 Unreal 上时,我曾短期地维护过一个模块,那个模块的业务逻辑依赖于 Unreal / Scaleform / Bink 这三方的适配。因为它们各自为政,在两两集成时,本来就是形式胜于实质,三方交互时就更为薄弱了。当大批量数据需要高频地在不同的中间件之间传递时,潜在的问题就会很容易集中爆发。&/li&&li&(&strong&industry standard-friendly&/strong&) 使用行业标准这一条就不多说了,我曾在一个游戏项目里见过七八个有着不同的 internal representation 的字符串类 (还不包括 std::string) 。光是字符串转换之间的各种细节,潜规则,优化手段,都够得上出本书了~ 想想你的工程师把他们宝贵的脑力消耗在这些事情上,实在是惊人的浪费~&/li&&/ul&&h3&游戏引擎的运用和改造&/h3&&p&说完了引擎的评估,接下来的运用和改造是很大的题目,对不同的项目类型也不尽相同。到这里不知不觉已有上万字了,为免冗长,我们提炼出一些相对通用的考虑和实践,就不考虑在单方面深入讨论了。&/p&&p&&figure&&img src=&https://pic2.zhimg.com/v2-43f1bdfbf210ce4cd2ccab7_b.jpg& data-rawwidth=&1280& data-rawheight=&720& class=&origin_image zh-lightbox-thumb& width=&1280& data-original=&https://pic2.zhimg.com/v2-43f1bdfbf210ce4cd2ccab7_r.jpg&&&/figure&在一个项目内涉及到引擎相关的部分,首当其冲的就是工作流的管理。项目是由人构成的,一个进行中的项目包含了许多显式或隐式的流程、约定和步骤,这些交互随着开发的进展不断动态变化。正如左下图那样,每个团队成员作为其中的一环或多环,有机地交互并推动项目的进展。要想让这个系统持续地有效运转,很重要的一点就是通过不断的观察、定位、梳理,来改进系统中响应速度跟不上整体节奏的环节。&/p&&p&在做阶段性回顾时,我们容易把目光聚焦在“什么做了,什么还没做”上,却容易忽略对影响响应速度和导致效率损失的因素的及时处置。如果能够持续不断地观察和优化这些敏感点,我们就能发现,抛开每个成员技术方向和能力的差别不谈,绝大多数响应问题是由于 (过多的) 依赖导致的。这些依赖既有内部的,也有外部的;既有业务逻辑需求驱动的逻辑依赖,也有物理性的资源和数据依赖——当然最多的还是由于“&strong&针对工作流的分析和梳理严重不足&/strong&” (俗话说的做到哪儿算哪儿) 导致的项目成员之间的无谓依赖。&/p&&p&有种常见的说法是 Daily Build 就是项目的心跳,保证每日构建的安全、自动和鲁棒是最重要的。然而我认为这只是工程意义上的心跳,一个游戏项目的真正心跳在于“&strong&持续的可感知的进展&/strong&” (Continuous sensable progress)。一个游戏项目内,任何一个可感知的点,不管是策划针对某个角色某个技能的构思或数值调整,还是美术对某个场景内某个特定氛围的塑造和创作,还是服务器程序对于一个特定功能 (如快速组队匹配) 的效率上的优化,都会通过或长或短的工作流程,最后在某个 Build 内以游戏内的一个可感知的点体现出来。我深切地感到,这样的&strong&基于实际体验点的持续而有节奏的交付&/strong&,才是一个健康的游戏项目的真实心跳。而这个真实心跳是否能良好运转,跟工作流的响应效率和依赖处置是直接挂钩的。&/p&&p&关于 hackers & scientists 的区分对待,也是值得讨论的一点。“黑客”式的工程师会准确而锋利地切中要害,他们敏感,敏锐,敏捷,有惊人的直觉和洞察力,对大部分问题都能在很短时间内直截了当地给出“行还是不行”的答案,相应地,他们多数时候“事了拂衣去”,不那么在意严谨和完备,不愿意陷入琐碎的工程细节,也对编写日志,测试用例等等一切“官僚主义的形式材料”满不在乎。而与此相对的是,“科学家”式的工程师们,普遍周全,周密,细心和细致,能不厌其烦地追究每一个细节并给出妥善的应对方案,他们无比在意工程的完整性和完备性,产出的代码精确、详实而可靠,充分而周祥地考虑了各种可能出现的隐患和边界条件。&/p&&p&很少有程序员能同时具备黑客和科学家的素质,所以这就要求我们能够感知并理解每个个体的行为方式,不断做针对性的调整和细化,从而让他们的积极特性在项目中能得到最大程度的放大。&/p&&p&&figure&&img src=&https://pic4.zhimg.com/v2-cdacb54e2a75b710deb4c8_b.jpg& data-rawwidth=&1280& data-rawheight=&720& class=&origin_image zh-lightbox-thumb& width=&1280& data-original=&https://pic4.zhimg.com/v2-cdacb54e2a75b710deb4c8_r.jpg&&&/figure&这里是关于同步节奏的管理,图上已有表述,不再多说。&/p&&p&&figure&&img src=&https://pic1.zhimg.com/v2-48ca432134ebebcf688bed_b.jpg& data-rawwidth=&1280& data-rawheight=&720& class=&origin_image zh-lightbox-thumb& width=&1280& data-original=&https://pic1.zhimg.com/v2-48ca432134ebebcf688bed_r.jpg&&&/figure&针对引擎局限性举的 Gamebryo 例子。&/p&&p&&figure&&img src=&https://pic3.zhimg.com/v2-4f468f8dbd1_b.jpg& data-rawwidth=&1280& data-rawheight=&720& class=&origin_image zh-lightbox-thumb& width=&1280& data-original=&https://pic3.zhimg.com/v2-4f468f8dbd1_r.jpg&&&/figure&这是魔兽世界里的兔子,详细的材料可见这里:&a href=&https://www.zhihu.com/question//answer/& class=&internal&&有哪些看起来很高端的技术其实原理很暴力很初级?&/a&。&/p&&p&&figure&&img src=&https://pic4.zhimg.com/v2-b255d0e15a0cf1ff3ec76_b.jpg& data-rawwidth=&1280& data-rawheight=&720& class=&origin_image zh-lightbox-thumb& width=&1280& data-original=&https://pic4.zhimg.com/v2-b255d0e15a0cf1ff3ec76_r.jpg&&&/figure&引擎不支持某个特性,并非总是坏事,也许正是你的游戏体现出差异性的好时机。有段时间,使用 UE3 开发的游戏扎堆出现,其中那些质量低下的作品,几乎总是让你一眼就能看出是在 UE3 上随便堆砌了些美术素材就放出来的昧心之作。而那些真正下功夫的 3A 之作,却总是能跨越技术的藩篱,(至少是在某一方面) 塑造出超越引擎能力的独特的体验。举个例子,如果 Android 本身在国内体验足够好的话,当时小米 MIUI 的独特本地化体验在 Android 阵营里也就没法那么出挑了。&/p&&p&什么是伪命题呢?就是那些本质上不存在,却因为某种局限性,稳定性或性能问题而浮现出来的需求。作为程序员,我们经常在提炼 xx 需求的时候发现,只要能把 yy 和 zz 弄好, xx 的问题自然而然就消解了。但机会窗口并非总是存在,也许一下没想通,没理会 yy 和 zz 的潜在问题,手一抖把 xx 做了,之后叠床架屋地做了 n 层,然后再想回来改就已经改不动了。举例的话,不少游戏的热更都是如此,就不具体说了。&/p&&p&&figure&&img src=&https://pic4.zhimg.com/v2-6dfeea573cf972e5aeb6f8_b.jpg& data-rawwidth=&1280& data-rawheight=&720& class=&origin_image zh-lightbox-thumb& width=&1280& data-original=&https://pic4.zhimg.com/v2-6dfeea573cf972e5aeb6f8_r.jpg&&&/figure&关于技术负债,只要能意识到这很大程度上并非负面的因素,不要有太重的心理负担就好。在处置这些欠下的负债时,要有勇气不断地断舍离,随时扔下负重,轻装上阵。不要舍不得删代码。&/p&&p&关于&strong&删代码&/strong&有一篇非常有意思的文章,非常推荐阅读:&/p&&ul&&li&&a href=&https://link.zhihu.com/?target=http%3A//programmingisterrible.com/post//write-code-that-is-easy-to-delete-not-easy-to& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&(需翻墙) Write code that is easy to delete, not easy to extend&/a&&/li&&/ul&&p&如果上面的链接无法访问的话可以看下面这个 Internet Archive Wayback Machine 版本的:&/p&&ul&&li&&a href=&https://link.zhihu.com/?target=http%3A//web.archive.org/web/11/http%3A//programmingisterrible.com/post//write-code-that-is-easy-to-delete-not-easy-to& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&(需翻墙) Write code that is easy to delete, not easy to extend (Internet Archive)&/a&&/li&&/ul&&p&这篇文章在&a href=&https://link.zhihu.com/?target=https%3A//news.ycombinator.com/item%3Fid%3D& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&& Hacker News 上的评论&/a&也很有趣,一并推荐。&/p&&p&&figure&&img src=&https://pic4.zhimg.com/v2-49ce7fabe15_b.jpg& data-rawwidth=&1280& data-rawheight=&720& class=&origin_image zh-lightbox-thumb& width=&1280& data-original=&https://pic4.zhimg.com/v2-49ce7fabe15_r.jpg&&&/figure&我曾不止一次地听到有项目组在一个进行中的项目里热切地讨论换引擎,当问到我时我常常会尴尬地笑笑。比较而言,这种动议的出发点往往是产品角度,一般出自项目经理或产品负责人,很少由工程师提出,故而讨论的结果不会影响到实际的执行,所以除了笑笑也贡献不了啥有意义的想法。实际情况是,更换引擎是一个难度指数显著较高的操作,一般的团队不一定能克服得了这个困难,而这种风险往往会被 (有意或无意地) 低估。哦,对了,我还发现,那些曾经投入地讨论换引擎的,往往是折腾完了之后最早开始怀念在老的引擎上的好日子的同学。当然了,他们会拿出一百个理由告诉你这么干是值得的,嗯,好吧,毕竟生命在于折腾嘛。&/p&&p&(第二部分完)&/p&&h2&第三部分:游戏引擎的&strong&下一个十年&/strong&&/h2&&p&&figure&&img src=&https://pic1.zhimg.com/v2-012fe258f1cc9fe603dcc8e_b.jpg& data-rawwidth=&1280& data-rawheight=&720& class=&origin_image zh-lightbox-thumb& width=&1280& data-original=&https://pic1.zhimg.com/v2-012fe258f1cc9fe603dcc8e_r.jpg&&&/figure&这一部分主要是我个人对游戏引擎方面的一些非常零碎的和个人化的整理和推断,以幻灯片的内容为主,文字材料从略,见谅。&/p&&p&&figure&&img src=&https://pic2.zhimg.com/v2-f0af4baf220_b.jpg& data-rawwidth=&1280& data-rawheight=&720& class=&origin_image zh-lightbox-thumb& width=&1280& data-original=&https://pic2.zhimg.com/v2-f0af4baf220_r.jpg&&&/figure&首先是下一代无线的标准——5G。我们可以看到,相较 4G,5G 的带宽和延时指标均提高到原来的 20 倍。用户面延时降低到了难以置信的 0.5ms。不仅仅是视频和直播类应用会从带宽和延时上受益,不少游戏引擎内的同步模块,拿这把尺子一衡量,立刻可以看出巨多的值得改进之处,就好像现代的图形引擎里,九十年代大当其道的 BSP 已经不见踪影了那样。传统引擎里的很多预测,补偿(&a href=&https://link.zhihu.com/?target=http%3A//gulu-dev.com/post/-id-network-model-evolution& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&这里&/a&和&a href=&https://link.zhihu.com/?target=http%3A//gulu-dev.com/post/-dynamic-prediction-and-latency-compensation& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&这里&/a&),都可以用更简明的方式去处理。&/p&&p&&figure&&img src=&https://pic4.zhimg.com/v2-ef9f26e318d528a0b46b175fd872eb56_b.jpg& data-rawwidth=&1280& data-rawheight=&720& class=&origin_image zh-lightbox-thumb& width=&1280& data-original=&https://pic4.zhimg.com/v2-ef9f26e318d528a0b46b175fd872eb56_r.jpg&&&/figure&在上图 (出处在这里:&a href=&https://link.zhihu.com/?target=https%3A//gist.github.com/jboner/2841832& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Latency Numbers Every Programmer Should Know&/a&) 我们可以看到,传统的磁盘寻道约耗时 10ms (&a href=&https://link.zhihu.com/?target=http%3A//baike.baidu.com/link%3Furl%3DeWpj_1GfHNw9O3WnFE7n0x9D2J_AHw94ESdXmyxRKNXNIskHaAqfDUK3k1ZaKMTDbZOFV2DMGdoTQWwEY9J0X_& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&百度百科上这个数据为 7.5~ 14ms&/a&),图上的问题值得思考:当你的网络连接速度远快于 (10x) 本机的磁盘寻道时,你的引擎将应该如何来设计和实现你的资源管理及网络同步机制?不夸张地说,这将是一个牵一发而动全身的问题。&/p&&p&&figure&&img src=&https://pic2.zhimg.com/v2-fdae98e4c_b.jpg& data-rawwidth=&1280& data-rawheight=&720& class=&origin_image zh-lightbox-thumb& width=&1280& data-original=&https://pic2.zhimg.com/v2-fdae98e4c_r.jpg&&&/figure&在 2016 年,我们见证了首代消费级的 VR 产品。由于技术尚不成熟,在视觉呈现上有明显的纱门效应。&/p&&p&&figure&&img src=&https://pic3.zhimg.com/v2-7bbd0e9f1ad1da2bdbc0f80dbf3f89dc_b.jpg& data-rawwidth=&1280& data-rawheight=&720& class=&origin_image zh-lightbox-thumb& width=&1280& data-original=&https://pic3.zhimg.com/v2-7bbd0e9f1ad1da2bdbc0f80dbf3f89dc_r.jpg&&&/figure&由于巨大的显示带宽需求,眼下的 VR 设备线材繁多,玩家行动受到颇多限制。&/p&&figure&&img src=&https://pic1.zhimg.com/v2-d9acbe3ac381_b.jpg& data-rawwidth=&1280& data-rawheight=&720& class=&origin_image zh-lightbox-thumb& width=&1280& data-original=&https://pic1.zhimg.com/v2-d9acbe3ac381_r.jpg&&&/figure&&p&nVidia 的 MRS 技术,把四周的拉伸区域使用较低的分辨率渲染,用于提高渲染效率和降低带宽需求。&/p&&figure&&img src=&https://pic2.zhimg.com/v2-8c28a2d39eec8bc47669f3_b.jpg& data-rawwidth=&1280& data-rawheight=&720& class=&origin_image zh-lightbox-thumb& width=&1280& data-original=&https://pic2.zhimg.com/v2-8c28a2d39eec8bc47669f3_r.jpg&&&/figure&&p&&a href=&https://link.zhihu.com/?target=http%3A//gulu-dev.com/post/vr/-oculus-connect-3-abrash& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Abrash 在 Connect 3 上提到&/a&的凹式渲染,用于动态地进一步降低需要传输和渲染的像素量。&/p&&p&&figure&&img src=&https://pic2.zhimg.com/v2-fb6ffc7ef0b6db581da0777_b.jpg& data-rawwidth=&1280& data-rawheight=&720& class=&origin_image zh-lightbox-thumb& width=&1280& data-original=&https://pic2.zhimg.com/v2-fb6ffc7ef0b6db581da0777_r.jpg&&&/figure&经由类似上面的技术,我们将有机会在不久后见到更细致的画面的同时,摆脱线缆的束缚。&/p&&p&&figure&&img src=&https://pic4.zhimg.com/v2-ea0bed4b8fd_b.jpg& data-rawwidth=&1280& data-rawheight=&720& class=&origin_image zh-lightbox-thumb& width=&1280& data-original=&https://pic4.zhimg.com/v2-ea0bed4b8fd_r.jpg&&&/figure&而真正的临场感 (Presence) 由于&a href=&https://link.zhihu.com/?target=http%3A//gulu

我要回帖

更多关于 笔记本电脑开机超级慢是什么原因 的文章

 

随机推荐