Real-Time Rendering 第五章 光照模型

5.1 Shading Models

shading model:决定了渲染管线的功能,也就是渲染的方式,根据不同的光照,视角,来渲染出不同的模型表面颜色

比如:Gooch shading model :根据顶点法线方向和光源的相对位置,来决定模型的表面颜色,法线朝向光源,暖色调,背向光源,冷色调

模型的表面颜色一般受视角方向和光照方向的影响

这就是一个简单的光照模型,其中表示限制值为0-1,这其中出现了两次差值,一次是Ccool和Cwarm的差值,然后是 hightlight 和 diffuse 的差值,可以用 lerp 表示

r 表示反射光,也可以用reflect (-l,n)表示 ,t就是一个半兰伯特模型

5.2 Light Sources

现实生活中的光照计算非常复杂,可能有不同的光源类型,不同的光源形状(shape)、颜色(color)、强度(intensity),还要考虑间接光的影响(PBS)

光照计算复杂的原因还有让shading model 以binary way 的方式,在有光照和没光照两种情况下做出不同的交互,比如阴影的表现

光照从出现到消失,可以用intensity 的差值表示,比如

 表示不受光照影响时的表面颜色,根据你想实现的效果,可以赋予它不同的值,比如(0,0,0)表示不受光照时为纯黑色,一般来讲,这部分代表了间接光照的效果,比如天空盒的颜色,或者反射的其它物体的光

如果光源方向l 和表面法线n 的角度超过90度,则该光源就不再影响该表面的颜色

光源到达表面的光线的密度叫做辐射度,它决定了光照的强度(intensity) ,从上图可以看出,辐射度和cos(l 与 n 之间的角度成反比),和 l 与 n  之间的角度成正比

更准确的说 当 n点乘l 为正值,也就是角度小于90度时,辐射度成正比,当点乘为负值时,光照对表面没有任何影响,表现为黑色,所以上面模型使用了半兰伯特模型,提高暗处的亮度

+ 好表示,最小值为0,上式表示多个光照使用了同样的光照模型,所以用了积分

5.2.1 Directional Lights 

平行光:光照方向l 和 光源颜色 Clight 都是一个常量,除了Clight 会在阴影处衰减,相对来说  平行光是没有位置属性的,因为它到表面的距离要远远大于场景的大小,它也可以扩展为 Clight 是可变的,根据不同的场景,比如模拟太阳落山

5.2.2 Punctual Lights(point light)

有位置,有方向,向各个方向发射的光强都是一样的,点光源和聚光灯(spot light)是同种类型的光源

 光照方向L ,根据表面点的位置不同而不同,计算一个向量的长度,可以用

r表示 表面到光源的距离,除了应用在光照方向上面,它也决定了光照的衰减

Clight 根据r变化而变化,光的辐射度和r的平方成反比,距离越远,光的强度越弱,不像平行光,光强只在一个方向上有变化,点光源是在一个二维的平面上有变化,光的辐射度和R的平方成反比

 上式也叫inverse-square light attenuation(平方衰减),r 表示当前的距离光源的距离,R0表示 点光源的半径,虽然从技术上来说,点光源的距离衰减是正确的,但是对于实际的阴影使用来说,有一些问题使得这个方程不那么理想

比如:当r 接近于0时,Clight 就是无限大,当r为0时,除数就为0了,这是不正确的,为了解决这个问题

我们加上了一个常数E,UE引擎中,E =1 cm ,根据不同的引擎,实际的值也不同

 也有的引擎使用上述公式,解决r=0的问题,Rmin 表示光源本身的半径,比如一个球是一个光源,光源的辐射半径是R0,球的半径是Rmin

第二个问题就是当r无限大的时候,Clight接近于没有,但是永远不为0,为了渲染的效率,我们希望在超过一个距离是,颜色直接为0 ,而不是继续计算它,但是要避免在该边界处,光照的突变,要有个过渡

还有一些引擎使用

 fdist(r) 叫做 distance falloff functions,而不是平方衰减函数,根据引擎的性能做出选择

比如游戏《Just Cause 2》,采用下图的方式,让过渡平滑的同时,节省些性能

 Spotlights

聚光灯,光强不仅和光源到表面的距离有关,还和关照方向有关

Fdir(l):表示光照随着光照方向的改变的函数

聚光灯的光照区域是关于中间的光照方向左右对称的,是一个锥体,中间的光照方向我们用向量s 表示,所以 Fdir(l) 可以表示成 光照方向-l 与 s 之间的夹角 和 s 之间的关系,-l 表示指向物体表面的方向,l 表示 点指向光源位置

当然这个夹角也是有范围的,用表示,超过这个角度,值为0 ,也就是不受该聚光灯影响,它可以在计算距离衰减之前,就过滤掉哪些不受光源影响的表面,因为角度超过了限制,就不用再计算距离衰减了

同时也有一个penumbra angle θp 角度,在这个角度内,光照强度为1

不同的引擎,采取的方法也不一样

5.2.3 Other Light Types

Directional and punctual lights 对表面颜色的影响主要受光照方向的影响,不同类型的光源有不同的计算光照方向的方法,除了上述提到的光源类型,游戏《Tomb Raider》还有一个capsule lights 它的光照方向是距离光源最近点的方向

我们现在讨论的光源都是抽象的,在现实当中 light sources 有大小,形状,可以从多个角度照射到表满,在渲染中我们叫 area lights,它们的使用在渐渐增多,  Area-light 的渲染技术大致分为两类,: 由于区域光被遮挡,模拟边缘的阴影是一种,模拟区域光对模型表面的影响是第二种,th. 对于光滑的镜面表面来说,第二种类型的照明是最明显的,在这种表面上,光的形状和大小可以从它的反射中清楚地辨别出来。

5.3 Implementing Shading Models

shading model的实现:就是把我们上述的公式,用代码表示出来

5.3.1 Frequency of Evaluation

在设计 shading 实现的时候, 它的计算能力是根据 frequency of evaluation来划分的,首先,确定该计算的结果,是否在整个draw call当中都是保持不变的,如果是 则这个计算可以由CPU执行(比如顶点数据),然后通过图形API 作为uniform shader inputs传递下去,而GPU来计算哪些更耗费性能的计算.

在这个条件下,也有很大的范围空间,比如 一个常量表达式,在整个draw call 中都是一样的,但是可以通过配置改变,这样的计算在shader 编译的时候,就计算了,根本不需要额外的输入,也就是不是运行时计算的 ,在应用安装时就计算了

另一种就是在应用运行时,计算结果实时改变的,但是计算的频率很低,比如计算一天中的光照,就分为几个阶段

其它的类型包括计算结果实时变化的,且更新频率很高,比如连接视图和透视矩阵,每一帧都在变化等等,根据计算的频率,把uniform shader inputs分组,比如合批,有助于提高性能,也能够降低GPU 更新的频率

如果计算的结果在一个draw call 中会变化,就要通过varying shader inputs 传递给可编程shader 阶段,理论上来讲,shading computations 可以在任何可编程阶段执行,每一个可编程阶段有不同的 evaluation frequency:

现在大部分计算都在片元着色器中执行,顶点着色器只执行一些坐标变换或者顶点扰动,

 

 片元和顶点中计算光照的区别

造成这种错误的原因是在顶点着色器中就进行插值计算了,应该是在片元着色器中差值,虽然这能节省一部分性能,但是插值计算本身的消耗就是非常低的

注意 尽管vertex shader 输出的是单位法线,差值也会更改它们的长度,所以normals需要在 片元着色器中再单位化一次,如果法线长度在顶点之间变化非常大,通常在差值前和差值后进行一次 归一化(normalize),比如在 vertex 和 pixel shaders各进行一次

除了表面法线,视角方向,以及光照方向,也需要在片元着色器中计算,通过向量相减,这个计算非常快,并且在差值之前不要归一化

 5.3.2 Implementation Example

比如:

上述式子可以转换成:

Csurface 通常存储在顶点中,或者是贴图中

上面公式实现,需要shader 动态遍历 光源,这是用了shader的动态分支 功能,但是不适用于光源比较多的场景

shader model 不是一个单独的过程,是需要大量的工作要做的

在shader 之前 ,我们需要先定义一些变量

shader inputs 分为两种,一种叫 uniform inputs, 在CPU中计算,在整个draw call 中是常量,比如你定义的一些属性,或者顶点的一些属性,模型坐标,贴图坐标等,另一种叫 varying inputs, 在顶点着色器和片元着色器中是可以改变的,比如世界坐标,世界空间下的法线,这些是通过计算得到的.

注意:在顶点着色器中,法线并没有归一化,因为它在模型中就是已经归一化过得,如果不进行一些操作,比如顶点混合vertex blending ,nonuniform scaling,就不用进行归一化,上面的操作会改变法线的长度

5.3.3 Material Systems

Rendering frameworks 一般很少由一个shader 渲染组成,比如一个模型,可能有多个material,material  system 就是专门来处理多个材质球,多个shading models 的情况。

shader 仅仅是流水线中的可编程阶段,它是最底层的图像API ,所以交互起来可能不太友好,material system 让交互更加的形象,可以在面板上显示出来

material  和shader  不是一对一的关系,有的material 由多个shader 组成,一个shader 也可以在多个material中共享

最通用的就是 parameterized materials,parameterized materials需要两种类型的material entity,material templates 和 material instances,简单来讲就是基类和子类的关系,material templates有一组共同的参数,比如贴图,颜色,instance 在此基础上加上一组特有的参数

这些参数可以在运行时,通过uniform inputs 传入进去,也可以在编译阶段,把值代入

一个比较通用的编译时参数,就是bool 值,用来控制shader分支的,会在编译时就代入进去,从而只编译满足的shader 代码

material 的参数 和 shader的参数并不总是一对一的,比如我们声明了属性,还要在shader中声明对应的变量,从而使用material的参数,这些参数可以保持不变,但也可以在shader中变化,这种变化不会体现到material 中

material system 一个很重要的功能就是:把不同的shader  function 独立出来,然后控制它们的组合,来达到不同的目的

GPU不支持代码在编译后链接(post-compilation linking of code fragments),每一个shader 阶段 都是单独编译的,所以通常通过 #include, #if,  #define 指令来编译不同的代码块

在设计shader variants system的时候,首要问题就是怎么在编译时通过条件判断执行不同的变体,或者在运行时通过动态分支选择不同的变体,在之前硬件不发达的情况下,动态分支判断非常慢,所以都是在编译时判断,在现在,动态分支的判断变得非常快,尤其当所有的pixel 都是走同一个分支时,但是如果判断多了,也就意味着 寄存器多了,则 占有率就下降了,性能也就下降了,所以 编译时判断 也是非常重要的,它避免了一些不必要的变体计算

现在的 material system 同时包含了运行时变体和编译时变体,虽说负担从编译时转向了运行时,但随着变体的增加,编译时的变体仍然很多

material system 通过采取不同的策略,来减少编译的数量了大小,比如:

• Code reuse—把公用的功能写在一个文件中,然后通过 #include 访问

• Subtractive—使用编译时预处理,或者动态分支,来删除掉哪些用不到的变体.

• Additive—通过图形软件

除了上述考虑,还要考虑硬件的适用性,需要支持不同的平台,并且是复制到代码最小化

5.4 Aliasing and Antialiasing

想象一个大的黑色三角形在白色背景上缓慢移动。当一个像素被三角形覆盖时,表示该像素的颜色值,应该平滑的过渡到黑色,但是通常发生的情况是,当像素的中心被覆盖时,像素颜色立即从白色变成黑色。像素的颜色在这种变化中非黑即白的表现就叫锯齿。锯齿是颜色突变造成的

5.4.1 Sampling and Filtering Theory

渲染图片其实就是采样的过程

上图显示了一个连续的信号,每个一段时间采样一次,形成了一个离散的数字信号,然后再通过filtering 重新形成一个(reconstructed)连续的信号

当采样结束时,就会出现锯齿,一个经典的案例就是用电影摄影机拍摄的车轮

因为轮子的转速要远远高于摄像机记录的速度,所以你看起来轮子会出现 倒转,不转  ,或者转的很慢的情况,这是因为车轮的图像是在一系列的时间步骤中拍摄的,这种效果被称为temporal aliasing

 在计算机图形中,temporal aliasing常见例子有栅格化线或三角形边缘的“锯齿”、被称为“萤火虫”的闪烁的高光,以及带有格子图案的纹理被缩小

当以很低的频率采样时,锯齿就会出现,源新号被采样之后,形成了一个比原来频率低很多的新的新号,为了让一个新号被正确采样,采样的频率需要是原来信号频率的至少两倍,这个理论叫做  sampling theorem, 这个采样频率叫 Nyquist rate 或者 Nyquist limit,在原理论中,Nyquist limit 被称之为maximum frequency,也就是采样频率也是有上限的,band-limited

 也就是说提高采样率,是能够完美重构出原来的信号曲线的

采用点采样,是无论被采样的物体多么小,我们都可以采样成功

Reconstruction

 给定一个band-limit采样信号,我们现在将讨论如何从采样信号重建原始信号

为了reconstruction必须使用filter, 请注意,滤波器的值应该始终为1,也就是和原信号的值,保持一致,否则重建信号可能会出现增大或缩小。

 

上面这个filter 滤波器非常差,因为重构出来的图像是非连续的,使用box filter 的原因是因为它简单,方法就是把滤波器 和 采样后的信号的y值重合

上面的这个叫做 low-pass filter (低通滤波器),它的频率是sin(2πf),f是滤波器的频率,鉴于此,低通滤波器去除所有频率高于滤波器定义的特定频率的频率成分。

 为什么叫低通滤波器?它其实是box filter,当它与信号相乘时,它会去除滤波器宽度以上的所有频率

在使用滤波器后,得到了一个连续的信号。然而,在计算机图形学中,我们不能直接显示连续信号,但我们可以对连续信号重新采样到另一个size的信号

Resampling

Resampling 用来放大或者缩小信号,这里的放大或者缩小不是指y值上的放大缩小,而是指采样的间隔,比如原始信号的坐标x 为 (0, 1, 2, . . . ),resample 之后,我们的得到的采样点间隔a, a > 1, 就是downsampling, a < 1, upsampling.

downsample  发生在原始信号的频率非常高,这时我们只能降低采样的频率,结果就是是原来的图像稍微模糊一点

5.4.2 Screen-Based Antialiasing

如果图像的边缘不经过采样和过滤器处理,就会出现锯齿,有一个基于屏幕的通用thread,也就是 对渲染管线输出的结果进行操作.同时需要明确的是 没有一种抗锯齿的方法是十全十美的,都有各自的有点和缺点,根据不同的情形,选择不同的算法

 上面讲的黑色三角形在白色背景上缓慢移动的例子,一个原因是采样频率太慢,另一个是采样点只有中心点一处,通用的策略是,多个个采样点,然后共同决定该像素的颜色

 n 是采样点的数量,w 是该采样点决定最终颜色的权重,所有点的权重加起来和为1, c(i,x,y)在该位置下的颜色,也就是不同的位置的采样点的颜色是不同的

如果只有一个采样点,那么返回的是该像素中心点的颜色值,w=1

这种计算一个像素中多个采样点的抗锯齿方法,称为 过采样或者超级采样(supersampling (or oversampling)),也叫full-scene antialiasing (FSAA)或者 “supersampling antialiasing” (SSAA)

比如:渲染一个高分辨率的场景图像,然后经过重采样,生成一张屏幕大小的图像,比如 需要一张1280*1024的图像,你先生成了一张2560 × 2048的图像,也就是 原一个像素包含了四个像素的大小,所以每个像素要采样四次,然后通过box filter 过滤,这种方法非常耗性能,因为每次采样都需要重走一遍pass,并且各自包含了z-depth,所以FSAA的优点就是简单

过采样的原理就是利用 accumulation buffer(累计缓存),它的大小和屏幕大小相同,而不是屏幕外的大图像,只不过每一个颜色通道包含了更多的bit,比如一个4个采样点的图像,生成了四张image,这四张图像是在不同的采样点下生成的,需要把这四张图像的数据拷贝到屏幕上,这也是耗费性能的地方。所以这种方式视情况而定,如果性能不是很吃紧,就可以使用

Multisampling antialiasing (MSAA) 通过执行一次shader pass ,然后结果在采样点之间共享,从而减少了性能耗费。

 

 同样也是四个采样点,拥有各自的color 和z-depth,但是 shader pass 只执行一次,如果所以的采样点都被颜色覆盖了,则以中间点的数据为准,计算color,如果只是覆盖了一部分,则GPU  会自动调整采样点的位置,这种行为叫做 centroid sampling 或者 centroid interpolation

MSAA 比FSAA 快的地方就是,它只进行一次shader pass,这种思想进一步提升为不需要为每一个采样点都保留color 和 z-depth,只需保存不同的color 和 z-depth,从而脱离的采样点的位置,这种方式叫EQAA , 如果超过存储的颜色数量,则该采样点作废,比如你有一万个采样点,这是不现实的。

当所有的采样点 都计算完毕,就需要计算最终的颜色值了,根据上面的算法,但是如果使用了HDR的颜色时,需要先映射到0-255范围内,这非常耗性能,所以一般是贴图映射

采样之后就需要filter了,之前使用的是box filter,在现代GPU中,你可以使用任意类型的filter,

更好的效果是记录一下上一帧的结果,然后和当前帧进行混合,只不过权重不一样

Sampling Patterns

采样模式,就是我们上图的那样,就是采样点的位置不同,以及数量不同,好的选择是避免两个采样点离得太近,这样意义不大,一般采样模式,内置于硬件当中

 

5.5 Transparency, Alpha, and Compositing 

一种照射半透明物体的方法叫screen-door transparency,这种方法简单,缺点是只能渲染一个半透明物体

另一种更常见的方法是,采用透明度混合的方法

一个像素的 alpha 值不仅仅表示透明度,也可以表示颜色的覆盖度,比如肥皂泡覆盖了四分之三的像素, 也就是0.75的部分接近于透明,能够让十分之九的光线进入眼睛,所以它是十分之一的不透明, 则它的 alpha是 0.75 × 0.1 = 0.075. 但是,当我们使用 MSAA 采样算法时,我们需要考虑采样点本身的透明度, 四分之三的采样点都受泡泡的影响,因此,每一个采样点的透明度都是0.1

5.5.1 Blending Order

 Cs 是当前shader计算得到的颜色值,Cd 是color buffer中原来的颜色

当模拟透过玻璃看其他物体的效果是,上述公式就不怎么管用了,在现实世界中,透过红色玻璃观看蓝色物体,蓝色物体通常看起来很暗,因为这个物体反射的光很少、

另一种常用地混合方法,是相加

它适用于发光的物体,能够让颜色更亮更鲜艳

为了时渲染效果正确,我们必须在所有的不透明物体渲染完毕后,再渲染,但是这种问题就是,z-buffer中只能存储一个物体的深度值,当有多个不透明物体时,就不能渲染出正确的结果了

一种方法就是对渲染物体进行排序,但是如果物体穿插,就无法进行排序了,首先要关闭深度写入,因为这种大致的排序比较简单,所有也经常用,还有就是拆分模型,另一个方法就是渲染背面,再渲染前面

上图表示 source alpha 为0.7  destination alpha 为0.6  ,然后这个片元整体的透明度为0.88

5.5.2 Order-Independent Transparency

处理透明度混合的方法有很多种,都有一定的优点和缺点,一种是根据z-depth 剥离层,一个pass可以剥离很多层,然后进行混合,这种方法缺点是性能消耗高,pass数不确定

一种是额外开辟一个缓存,k-buffer,用来存储该像素覆盖的各个片元,相当于一个链表,然后计算平均颜色值,这个不好的地方是需要额外的内存,并且内存上限不可预知

一种是上述方法的改进,对各个fragment 的值进行加权,根据距离加权和根据透明度加权,计算最后的值,但是如果在一个大场景中,距离差不多,透明度也差不多,最后的权值差不多,会和真实场景有点区别

5.6 Display Encoding

当我们计算光照,纹理或者其它颜色操作时,我们需要在 linear  空间下. 为了避免产生不同的效果,display buffers 和 textures 使用 nonlinear encodings 的情况,我们必须考虑进去. 也就是说: shader 输出的color 在 [0, 1] 范围内,然后乘以一个  1/2.2 次幂,也就是大约0.45, 这个叫做 gamma correction(伽马校正).

在最初的CRT阶段,也就是二极管阶段,输出的颜色和输入的电压乘一个幂率的关系,大概是2.2

也就是你的颜色本来是0.5,输出的颜色为0.25左右,

简而言之: 我们sRGB 的图,它是经过display transfer function 的,也就是已经处理过了,要比线性空间要暗一些,我们采样的时候,会把值转换到线性空间,然后当我们最终写入到framebuffer的时候,进行一次伽马校正,让颜色提亮一些,最后,经过屏幕显示出来的,就正常了

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值