本文最后更新于:2025年1月23日 晚上
辐射度量学(Radiometry)
立体角


I=ωΦω=r2Aω:立体角dω=r2rsinθ⋅dϕ⋅rdθ=sinθdθdϕ球面立体角:ω=∬S2dω=∫02πdϕ∫0πsinθdθ=4π
物理量
符号 |
名称 |
含义 |
单位 |
Φ |
辐射通量(radiant flux) |
单位时间内发出/吸收的能量 |
W |
E |
辐照度(irradiance) |
单位面积的辐射通量 |
W/m2 |
I |
辐射强度(radiant iIntensity) |
单位立体角的辐射通量 |
W/sr |
L |
辐射(radiance) |
单位投影面积的辐射强度 |
cd/m2 |
Spectral Power Distribution
光度学(Photometry)
与辐射度量学的关系

P=∫0∞s(λ)dλ∫0∞s(λ)k(λ)dλRP:Photometry 中的某个物理量R:Radiometry 中与之对应的物理量s(λ):SPD 函数k(λ):Photometry Curve 函数
物理量
名称 |
单位 |
举例[1] |
光通量(luminous flux) |
lm[=cd⋅sr] |
在任意包围光源的曲面上,通过光线的总亮度为4π lm |
光照度(illuminance) |
lux[=lm/m2] |
距离光源0.1m ,法线与光线夹角为60°的微小平面上,辐照度为 50lux |
光强强度(luminous intensity) |
cd |
在空间中的任意位置,辐射强度为1cd |
亮度(luminance) |
nit[=cd/m2] |
距离光源0.1m 处观察光源时,亮度为$ 100 \mathrm{nit}$ |
[1]:假设光源为均匀发光的点光源,空间中没有其他物体(不用考虑折射、反射)
- cd:基本单位之一;假设有一个光源,在某个方向上发出540×1012Hz(556nm)的单色辐射,且辐射强度为6831W/sr,则该方向上的光强度为1cd
平面方向改变导致总光通量减小,所以平均光照度就等于总光通量除以总面积(不是除以投影面积)
L(p,l)=dωdAcosθd2Φ(p,l)
- 假设光源为均匀发光、未被遮挡的点光源:
- 在光源的不同方位、不同距离处,辐射强度均相同
- 其他条件不变时,离光源越近的表面,辐照度越高;被直射的表面,比被斜射的表面辐照度高
- 可以将一条光线视为从某一点射出,或射入某一点(即无限小表面),始终占据无限小立体角的事物
亮度与感知亮度

- 亮度(luminance):单位投影面积的辐射强度经Photometry Curve转换而得的物理量
- 感知亮度(brightness):人眼感知光线后,产生的某种信号的强度
渲染方程
Lo(p,v,n)=Le(p,v)+Lr(p,v,n)Lr(p,v,n)=∬ΩLi(p,l)(l⋅n)f(p,l,v,n)dω注意到 l⋅n=cos⟨l,n⟩(兰伯特余弦定理)p:被观察点位置v:由p指向观察者位置的单位向量l:由p指向光源(或反射源)的单位向量n:p处的单位法向量Ω:以p为中心,n为朝向的半球面(理论上总有l⋅n≥0)Lo(p,v,n):从p沿v方向发出和反射光线的总亮度Le(p,v):从p沿v方向的自发光亮度Li(p,l):p处沿−l射到的光线亮度Lr(p,v,n):从p沿v方向的反射光亮度f(p,l,v,n):p处的双向反射分布函数 (BRDF)直接求解: Lr(p,l,v,n)=∬ΩLi(p,l,v,n)(l⋅n)f(p,l,v,n)dω=∫02πdθ∫02πLi(p,l,v,n)(l⋅n)f(p,l,v,n)sinϕdϕ其中,l={cosθsinϕ,sinθsinϕ,cosϕ}对于均匀发光的点光源: I≡4π srΦLi(p)≡r(p)2IΦ:光源总功率I:光强度r(p):光源到p的距离
- 渲染方程是二重积分
- 渲染方程不涉及颜色,要将其用于着色,首先忽略不可见光,然后考虑可见光范围内的SPD;而SPD过于复杂,将其转换为线性颜色空间中的坐标,然后亮度相加变为线性颜色空间中的颜色相加
色度学(Colorimetry)
- 颜色:人眼感知光线后,产生的某种信号(注意与“颜色数据”、“颜色分量”、“颜色空间坐标”区分)
- 灰度(Grayscale):用于描述亮度的一种数据
- 色度(Chromaticity):表示颜色的数据,可以分为亮度和色度;色度可以再分为色调和饱和度
颜色与SPD
上图中,两种不同的SPD在某个人看来可能是同种颜色(白色);其他人看来则可能略有区别
人眼成像
- 人眼中,一般有三种视锥细胞,它们对不同波长的光敏感度不同,三种细胞受到的刺激强度决定了颜色
- 不同人三种视锥细胞的数量不同(甚至种类不同/功能异常),导致不同人看相同SPD的光感知到的颜色不同
- 对于三色视觉者,颜色可以看成三维向量(每个分量上可分辨约100个值,总共能分辨约1003种颜色)
- 对于四色视觉者,颜色可以看成四维向量(总共能分辨约1004种颜色)
- 对于任何人,SPD到颜色不可能是单射
颜色空间
- 出于颜色通常是三维向量的考虑,颜色空间也是三维的
- SPD到颜色空间坐标不可能是单射(有严重信息损失)
线性颜色空间
有SPD到颜色空间坐标的映射T,满足T(a+b)=T(a)+T(b)的颜色空间是线性颜色空间
- 可见光各波长处功率密度翻倍,则颜色空间中对应的坐标分量也翻倍,满足这样条件的颜色空间称为线性空间
- 光线叠加的本质是SPD相加;在线性空间中,转换为坐标分量直接相加
线性RGB空间
L=∫0∞s(λ)dλL′=∫380780s(λ)dλR=∫380780s(λ)r(λ)dλG=∫380780s(λ)g(λ)dλB=∫380780s(λ)b(λ)dλL:亮度L′:可见光亮度s(λ):SPD函数
- 对于给定的SPD,将其与三个函数分别相乘,对波长积分,得到的三个值即RGB分量
- 某些SPD转换为RGB后得到的R分量可能为负数;换句话说,RGB被限定为正数时,无法表示所有(三色视觉者)可见颜色
线性XYZ空间
X=∫380780s(λ)x(λ)dλY=∫380780s(λ)y(λ)dλZ=∫380780s(λ)z(λ)dλ
- y曲线与Photometry Curve相同,因此亮度(或灰度)仅由Y分量决定
- 显然,用RGB与XYZ坐标间的转换是不符合数学的,只存在经验公式
x=X+Y+ZXy=X+Y+ZY注意到,有一系列不同的(X,Y,Z)映射到同一个(x,y)上
- 限定X+Y+Z=1,则每个(x,y)对应一个颜色空间坐标,将这些坐标对应的颜色绘制到平面上,即得到色度图
Gamma颜色空间
常见的Gamma校正公式:y=x1/γ(γ通常取2.2)x:线性空间颜色分量y:Gamma空间颜色分量
- 线性颜色空间坐标转换到Gamme颜色空间坐标的过程称为Gamma校正,逆过程称为反Gamma校正
- 最早的显示器使用阴极射线管(CRT),其输出亮度近似和输入电压的2.2次幂成正比;为了确保计算机最终输出的颜色分量和正确的输入电压成正比,渲染过程最终输出的颜色分量要进行Gamma校正
- 人眼感知颜色的特性和CRT的特性有相似之处;为了确保颜色分量均匀变化时,感知到的颜色也均匀变化,用户在各类软件中直接接触的颜色空间基本都是Gamma空间
- Gamma空间中坐标分量不可直接相加,不可用于计算灰度
渲染过程中的颜色
Spectral Rendering
- 依据SPD进行渲染
- 理论上符合物理,但实际计算时的精度/计算量依然有限;用于电影、工业设计软件等精度要求高的场合
- 并不直接使用渲染方程,而是分别在各个波长上计算不同材质的反射等过程
- 可以规定光源发出光线的SPD
- 可以规定各种材质对不同波长的光线的反射率等信息
RGB Rendering
-
依据RGB颜色分量进行渲染
-
理论上并不符合物理,但很多场合已经能取得令人满意的渲染结果;常见于各类交互式应用
-
并不直接使用渲染方程,而是将渲染方程中的亮度改为颜色分量
-
光源的RGB和强度粗略反映了发出光线的SPD
-
纹理的RGB粗略反映了各种材质对不同波长的光线的反射率等信息
颜色数据处理
- inverse EOTF:将渲染结果重新编码的函数;目前实际采用的输入输出是线性空间颜色②和Gamma空间颜色②,所以编码过程包含Gamma校正
- EOTF:将编码值转换为光学值的函数,与显示设备硬件特性有关;目前实际采用的输入是Gamma空间颜色②,因此EOTF包含类似逆Gamma校正的计算过程
- OETF:将光学值转换为编码值的函数,与拍摄设备的硬件特性有关,和inverse EOTF不是一个事物
- 处理过程:
- 对纹理颜色数据进行反Gamma校正
- 得到的线性空间颜色分量参与着色计算
- 对计算结果进行Tone Mapping,将结果映射到LDR颜色分量范围内
- 计算结果经由inverse EOTF转换为Gamma空间颜色分量,输出到帧缓冲中
- 帧缓冲中的颜色分量经EOTF转换为显示器发光元件的功率

- 现代游戏引擎中,还会使用颜色分级(Color Grading),**白平衡(White Balance)**等技术进一步改善图像色彩
动态范围
- 动态范围:(某时刻某个范围内)可变信号的最大值和最小非零值的比值
- 着色计算得到的颜色分量的动态范围可能很大,而发光元件的功率有限,未必能显示计算得到的颜色分量
- 图形学中,常讨论渲染过程中颜色分量的动态范围,或显示器显示图像的动态范围
动态范围 |
线性空间的颜色分量最大值 |
说明 |
HDR |
不限制 |
经Tone Mapping转换到LDR |
LDR |
1.0 |
之后可以进行Gamma校正 |
Tone Mapping
- Tone Mapping:通过某种函数将整个非负数范围内的颜色值映射到一定范围内
- Tone Mapping在线性颜色空间中进行,不改变颜色空间,但映射后颜色空间坐标已不能反映真实亮度

上图中使用了一种较为简单的映射函数;更好地区分高亮度区域和更好地区分低亮度区域难以兼顾
- 和强行截断超出范围的颜色分量相比,Tone Mapping使整幅图像颜色分量的平均值变低,而平均对比度变高
线性代数
向量与矩阵
m×n矩阵:A=⎣⎢⎢⎢⎡a11a21⋮am1a12a22⋮am2⋯⋯⋱⋯a1na2n⋮amn⎦⎥⎥⎥⎤=[aij]m×n=[A1A2⋯An]=⎣⎢⎢⎡A1A2⋯Am⎦⎥⎥⎤矩阵乘法:Am×p+Bp×n=Cm×n(行m-列p-行p-列n),其中cij=Ai⋅Bj(点积)向量:a=⎣⎡xayaza⎦⎤,aT=[xayaza]点积:a⋅b=aTb=Σaibi叉积:a×b=⎣⎡0za−ya−za0xaya−xa0⎦⎤⎣⎡xbybzb⎦⎤=⎣⎡yazb−ybzaxazb−xbzaxayb−xbya⎦⎤投影:a∙b=∣b∣a⋅b∣b∣b若b为单位向量,则a∙b=(a⋅b)b长度:∣a∣=a⋅a夹角:cos⟨a,b⟩=∣a∣∣b∣a⋅b
四元数
四元数:q=q0+vq=q0+q1i+q2j+q3k(q0,q1,q2,q3为实数)共轭四元数:q∗=q0−vq四元数的二范数:∣q∣=(q02+∣vq∣2)21=q02+q12+q22+q32p⋅q=q0p0+q1p1+q2p2+q3p3(类似向量点乘)p×q=vp×vq=(p2q3−p3q2)i+(p3q1−p1q3)j+(p1q2−p2q1)k(类似向量叉乘)pq=(p0q0−v1⋅v2)+(p0v2+q0v1+v1×v2)(默认乘法)q−1=∣q∣2q∗,q1q2=q3⇔q2=q1−1q3四元数表示旋转:q=cos2θ+sin2θvq(∣vq∣=1⇒∣q∣=1)p=vpr=qpq−1q:表示旋转的四元数,θ为旋转角,vq为转轴(必须是归一化的)p:表示初始坐标的四元数(实部为0,虚部系数即坐标)r:表示旋转后坐标的四元数(结果中虚部系数即坐标)
变换
n维空间中有变换A,若∀向量a,b,实数k,有:{A(a+b)=A(a)+A(b)A(ka)=kA(a),则A为线性变换n维空间中的线性变换总是可以表示为b=Ln×na一切仿射变换可以表示为b=Ln×na+t引入齐次坐标后,b=Ln×na+t可以表示为b′=M(n+1)×(n+1)a′,其中b′=[b1],a′=[a1],M=[L0t1]
二维
缩放(x,y)倍:L=[x00y]切变:Lx=[10s1],Ly=[1s01]绕原点逆时针旋转θ:L=[cosθsinθ−sinθcosθ]
三维
- 规定i×j=−k(左手系,同Unity世界坐标系相同);在此前提下,绕轴旋转均表示,从该轴的负半轴看向正半轴,在看到的平面内逆时针旋转
缩放(x,y,z)倍:L=⎣⎡x000y000z⎦⎤切变:Lxy=⎣⎡10s01t001⎦⎤,Lxz=⎣⎡1s00100t1⎦⎤,Lyz=⎣⎡100s10t01⎦⎤绕坐标轴旋转θ:Lx=⎣⎡1000cosθsinθ0−sinθcosθ⎦⎤,Ly=⎣⎡cosθ0−sinθ010sinθ0cosθ⎦⎤,Lz=⎣⎡cosθsinθ0−sinθcosθ0001⎦⎤绕单位向量w=⎣⎡xyz⎦⎤旋转θ:L=I+sinθJ+(1−cosθ)J2,其中J=⎣⎡0z−y−z0xy−x0⎦⎤
有符号面积
有符号面积:S1=21(CP×BP)⋅nn=∣CA∣∣BA∣CA×BA重心坐标:(α,β,γ)=(SS1,SS2,SS3)⇔P=αA+βB+γCP在三角形ABC内⇔S1,S2,S3>0记a=CP×BP,注意到必然有a∥n(同向/反向/a=0)因此S1>0⇔c.x×n.x>0(只求符号,不必求面积)
- 边的方向会影响法向量方向,因此计算有符号面积时点的顺序必须统一(如以上公式)
渲染管线
- 渲染管线的各个步骤不是简单串行,而是流水线式并行:
- 一步尚未完全完成时,部分结果先被用于下一步的计算
- 一帧中靠后的步骤尚未完成时,下一帧中靠前的步骤已经开始
概念
典型前向渲染管线
- 应用阶段:
- 加载数据到显存:输入纹理、顶点、图元、光源、相机等数据
- 设置渲染状态
- 发出Draw Call
- 几何阶段
- 顶点(Vertex)着色器:对顶点进行模型变换(模型空间→世界空间),观察变换(世界空间→观察空间),投影变换(观察空间→裁剪空间)(合称MVP变换)
- 图元装配:根据顶点索引及相关参数,按照指定的方式计算出所有要渲染的图元,这一步可能进行背面剔除
- 几何(Geometry)着色器:可选的,对输入的图元(以给定的规则)进行变换、修改
- 曲面细分(Tessellation)着色器:可选的,用镶嵌化技术增加三角形面及顶点的数量
- 齐次裁剪:由硬件自动进行齐次除法(裁剪空间→NDC),并裁剪NDC外的图元,这一步可能进行遮挡剔除
- 屏幕映射:进行视口变换(NDC→屏幕)
- **光栅化(Resterization)**阶段:
- 片元着色器:对于每个图元,进行覆盖计算;对于覆盖的每个片元,着色计算、深度计算等,输出到片元缓冲中
- 测试混合:根据片元缓冲进行测试混合,更新深度缓冲、颜色缓冲、透明度缓冲等,得到最终颜色
- 像素后处理(pixel processing)阶段:可选的,如FXAA
延迟渲染(Deffered Rendering)

- 延迟渲染中使用的多组缓冲区统称为G-Buffer
- 多组缓冲区可以看成多张贴图,每张存储不同信息,如深度、法线、纹理颜色、粗糙度、金属度等
- 每个像素位置上始终只维护一套信息(不会像片元缓冲那样出现overdraw现象)
- 延迟渲染与前向渲染的区别在光栅化阶段:
- 对于每个图元,进行覆盖计算;对于覆盖的每个片元,计算其深度,如果小于当前该位置深度则更新G-Buffer
- 处理完所有片元后,根据G-buffe在每个片元位置进行一次光照计算
- 屏幕范围内,被不透明物体遮挡的部分无需渲染是理所应当的,这是使用延迟渲染的根本原因
- 前向渲染将透明与不透明物体一起渲染,仅仅是逻辑简单,会导致overdraw
- 延迟渲染无法处理半透明物体,因为深度、法线等数据只能覆盖,不能混合
- 对于半透明物体,需要额外的Pass渲染(如依然利用前向渲染管线)
- 原本前向渲染的计算量越大,使用延迟渲染越有必要
- 计算量过大的常见原因是光源数量过多;正因如此,一些前向渲染管线会限制场景中的光源总数
- 延迟渲染解决的是overdraw问题
- 如果要解决光源数量增加导致的计算量增大,可以对相机拍摄的范围分块(每个光源仅影响部分区块),即分块延迟渲染
- 延迟渲染的缺点:
- 显存带宽占用大(G-Buffer每帧都在变换),需要尽可能压缩G-Buffer空间
- 光照算法灵活性受限(G-Buffer存储的是数据,不同光照算法的不同行为及其不同参数难以存储)
Forward+渲染
应用阶段
Draw Call
- Draw Call是由CPU向GPU发出的一条绘制指令,每条指令渲染若干个图形(如
glDrawElements
)
- **批量渲染(Batch)**可以减少CPU与GPU的通信次数,以降低开销;常见的做法是把多个对象使用的多种缓冲区分别合成(每种类型的缓冲区合称为一个)
- CPU与GPU流水线式并行,Draw Call存放在GPU缓冲区中,CPU不必等到GPU完成一个渲染命令才发送下一个渲染命令
- 一条Draw Call通常要访问以下数据(OpenGL中,这些数据在上下文中,需要在发出DrawCall前设置好):
- 顶点缓冲区:各顶点的位置、法线方向,及纹理坐标等
- 索引缓冲区:规定各个顶点按照怎样的顺序组成三角形
- 材质:可能包含纹理贴图、纹理映射方式、法线贴图、PBR材质等数据
- 着色器程序:顶点着色器,片元着色器,及其他可选着色器;包括各着色器中的参数(uniform变量)
- 其他设置:深度测试,混合模式,剔除模式等
几何阶段
- 输入:顶点缓冲区、索引缓冲区,顶点着色器程序等
- 输出:变换(除了数值改变,还可能删除/增加顶点/三角形)后的顶点缓冲区、索引缓冲区等(不会修改原始缓冲区,而是使用显存中另外的区域)
- 以下所有空间中,均假定使用左手系
顶点着色器
- 对于输入的所有顶点,(并行地)逐顶点调用顶点着色器
- 变换过程可以在CPU中进行(顶点缓冲区中动态更新变换后数据),也可以在着色器中进行(顶点缓冲区设置好变换前数据,并动态更新用于变换的着色器参数)
模型空间
- 以模型的指定点为原点,模型的右方为x轴,上方为y轴,前方为z轴
- 坐标有xyzw分量。为了进行变换运算,加入额外的w分量(表示坐标的向量,其w为1;表示矢量的向量,其w为0)
世界空间
Pworld=MmodelPmodelMmodel=TRSS=⎣⎢⎢⎡r0000s0000t00001⎦⎥⎥⎤R=⎣⎢⎢⎡10000cosαsinα00−sinαcosα00001⎦⎥⎥⎤⎣⎢⎢⎡cosβ0sinβ00100−sinβ0cosβ00001⎦⎥⎥⎤⎣⎢⎢⎡cosγsinγ00−sinγcosγ0000100001⎦⎥⎥⎤T=⎣⎢⎢⎡100001000010xyz1⎦⎥⎥⎤(x,y,z):物体坐标(α,β,γ):物体欧拉角(r,s,t):物体缩放
- 由模型空间经模型变换而得,引擎预设好原点,右方为x轴,上方为y轴,前方为z轴
- 缩放→绕模型自身的坐标轴旋转→平移
观察空间
Pview=MviewPworldMview=RTR=⎣⎢⎢⎡10000cosα−sinα00sinαcosα00001⎦⎥⎥⎤⎣⎢⎢⎡cosβ0−sinβ00100sinβ0cosβ00001⎦⎥⎥⎤⎣⎢⎢⎡cosγ−sinγ00sinγcosγ0000100001⎦⎥⎥⎤T=⎣⎢⎢⎡100001000010−x−y−z1⎦⎥⎥⎤(x,y,z):相机坐标(α,β,γ):相机欧拉角
- (在世界空间下看)Unity中的相机通常朝向X轴正方向,OpenGL中则与之相反
- 由世界空间经观察变换而得,相机的右方为x轴,上方为y轴,前方为z轴
- 平移→绕相机的坐标轴旋转
裁剪空间
拍摄区域可以偏离相机中心,只是游戏引擎通常不支持
正交投影:Pndc=MorthoPviewMortho=STT=⎣⎢⎢⎡100001000010−2r+l−2t+b−2n+f1⎦⎥⎥⎤S=⎣⎢⎢⎡r−l20000t−b20000n−f200001⎦⎥⎥⎤l,r,b,t,n,f:观察空间坐标分量(而不是世界空间)
透视投影:Pndc=MperspPviewMpersp=MorthoMsquishMsquish=⎣⎢⎢⎡n0000n0000n+f100−nf0⎦⎥⎥⎤Mortho同正交投影
- 由观察空间经投影变换而得,各个点的坐标的w分量表示该点到相机的距离
- 正交投影和透视投影的矩阵统称为投影矩阵;模型变换、观察变换、投影变换合称MVP变换
- 透视投影可视为两次变换的叠加,第一次先将平截头体“压缩”为长方体,之后的步骤和正交投影相同
图元装配
- 根据顶点索引及相关参数(如上图),按照指定的方式计算出所有要渲染的图元
齐次裁剪
归一化设备坐标(NDC)
- 裁剪空间中各点经过齐次除法(每个点除以自身的w分量)得到NDC,因此NDC必然是边长为2的立方体中
裁剪
- 裁剪NDC外的图元,对于部分处于NDC外的图元,裁剪时会产生新的图元
- 可能进行遮挡剔除
屏幕映射
屏幕空间
Pscreen=MviewportPndcMviewport=⎣⎢⎢⎡2w00002h0000102w2h01⎦⎥⎥⎤w:屏幕宽度h:屏幕高度
- 由NDC经视口变换而得,屏幕左下角为原点,区域大小等于屏幕分辨率
光栅化阶段
- 输入:变换后的顶点缓冲区、索引缓冲区,片元着色器程序,材质,其他渲染设置等
- 输出:更新片元缓冲和深度缓冲
- 片元(Fragment)缓冲:存放各个像素上的若干个片元的缓冲区
- 深度缓冲(Z-Buffer):每个像素上,当前已渲染的片元的最小深度(深度即到相机的距离,能拍到的范围内深度总是为正)
片元着色器
覆盖计算
- 计算输入的各个三角形覆盖的片元范围
- 通常使用采样的方式进行覆盖计算;先确定三角形的AABB,然后逐片元采样,判断其是否位于三角形内部(如果使用此方式,则覆盖计算与着色可以合并)
- 也可以使用多边形扫描转换算法等方式
深度计算
- 计算各个片元的深度,并更新深度缓冲
- 如果片元是半透明的(访问材质以确定是否透明),不更新深度缓冲
- 着色计算时,也可能用到片元深度,如环境光遮蔽(用的是片元深度计算结果,而不是等深度缓冲更新完再计算着色)
- 理论上,先计算完所有片元的深度,进行深度测试后,再对保留的片元着色计算效率更高;但实际上,这几步不是简单串行,而是流水线式并行,破坏GPU并行性可能反而导致效率降低
着色计算
以下涉及颜色的计算,均在线性空间中进行
测试混合
-
输入:片元缓冲,深度缓冲
-
输出:更新颜色缓冲
-
颜色缓冲:当前已处理片元混合后的颜色
-
透明度缓冲:当前已处理片元混合后的透明度
-
模板(Stencil)缓冲:常见用途是计算哪些像素需要渲染
-
帧(Frame)缓冲:颜色、深度、透明度、模板等缓冲构成的整体
-
此阶段利用各个缓冲将同一位置的若干个片元混合为一个像素;各缓冲记录每个像素位置上的仅一个数据(不同于片元缓冲)
模板测试
- 如果开启了模板测试,比较当前片元参考值和模板缓冲中的模板值,如果不通过则舍弃片元,通过则更新模板缓冲
深度测试
- 如果开启了深度测试,比较当前片元和深度缓冲中的深度,如果不通过则舍弃片元,通过则更新深度缓冲
混合
- 最常见的混合方式是 新背景色 = alpha × 前景色 + (1 - alpha)× 背景色
后处理
几何体表示
- 不论显示还是隐式表示,描述几何体时可能使用不同种类的对象:
- 顶点:网格
- 内点:曲线/曲面方程,Bezier曲线
- 离散点:点云
- 立方体:体素
- 基本几何体:CSG,过程生成
曲线连续性
- 函数连续点:左极限、右极限、函数值均存在且相等;任意阶导数(若存在)也都是不同的函数
- 讨论连续性时,通常用参数方程表示曲线,将一组参数方程视为一个函数,该函数的值和任意阶导数(若存在)均为向量
有参数方程表示的曲线v(t):v+(t0)=v−(t0)⇒v(t)在t0处连续(C0连续性)v+(n)(t0)=v−(n)(t0)⇒v(t)在t0处具有Cn连续性
显式表示
- 直接或间接的给出几何体中某种对象的信息(数量、位置、顺序等)
- 能直接或间接地获取几何体种某种对象的信息,但不容易判断某对象是否位于几何体内部
点云
- 用足够多的点直接记录物体表面的点的位置
- 数据结构简单,但需要的点的数量极多
- 常见于实体扫描的结果,之后通常转换成Mesh再用于渲染
网格(Mesh)
- 以若干多边形拼合来表示物体表面
- 最常见的表示方式,其中又以纯三角形网格最常见
网格细分(Subdivision)
- 增加三角面的个数,增加更多的形状细节
- 细分不依赖于"实际物体"信息,因此需要某种算法**“猜测实际细节”**
网格简化(Simplification)
网格规则化(Regularization)
- 尽可能保持网格形状不变,尽可能使各个三角面形状接近正三角形且大小接近
参数映射(Parameter Mapping)
上图中,没有关于左侧几何体的信息,无法确定其是否为显式表示
- 对于一个显式表示的几何体,在其基础上经过映射,间接给出新的几何体的对象信息
Bezier Curve
- 给定n个控制点,再给定参数t,可以确定曲线上的一个点;点足够多后,可以近似地绘制出曲线
Bn表示有n个点的点集,bin表示点集中的第i个点有函数F:Bn−1=F(Bn,t),其中bin−1=(1−t)bin+tbi+1n⇒B1=Fn−1(Bn,t)⇒b11=i=1∑nbin(1−t)n−iti(b11为最终要求的点)
- 曲线段的起点和终点必然是第一个和最后一个控制点
- 对所有控制点进行仿射变换,曲线的形状不变(投影变换可能改变形状)
- (0≤t≤1时)曲线总是位于所有控制点构成的凸包(≠连成的多边形)内部
上图中,黑色点为相邻两条贝塞尔曲线的控制点,蓝色点为一条贝塞尔曲线的控制点
- 绘制较为复杂的曲线时,需要使用多个控制点,每个控制点对曲线的控制力较低
- 可以逐段绘制贝塞尔曲线(习惯上,每段使用四个控制点)
- 单段贝塞尔曲线内部总有C1连续性;一个黑色控制点与相邻的两个蓝色控制点(见上图)共线时,能确保黑色控制点处有C1连续性
Bicubic Bezier Surface
- 给定m条"平行"且间隔均匀的贝塞尔曲线(见上图),给定参数u,v,可以确定曲面上的一个点;点足够多后,可以近似地绘制出曲面
- 计算过程:
- 在每条贝塞尔曲线上,根据u,求曲线上的点
- 将曲线上的各点视为新的控制点,根据v,求新的曲线上的点,即为曲面上一点
隐式表示
- 不给出几何体中某种对象的信息,而是描述几何体中的对象满足的条件
- 能直接判断某种对象是否位于几何体内部,但不容易(离散化地)获取几何体的某种对象
曲面方程&有向距离场
f(p)表示某点到物体表面的距离f(p)<0⇔p在曲面内令f(p)=0,即得到曲面方程
- 引入max、min等非基本运算,则可以用有向距离场描述更复杂的曲面(如组合而成的非连通曲面)
- 有向距离场的复杂度不会随物体数量的膨胀而明显增大
Level Set Methods
- 计算出一定范围内若干个离散位置的函数值,存放在某种集合中
- 距离函数难以用函数表达时,或者说难以直接计算时(可能需要使用特殊方式计算,开销大),使用此方法可以事先计算好一些离散的值,以供查询
Constructive Solid Geometry
- 在一些基本图形的基础上,通过“集合”运算表示复杂图形
- 用表达式表示一系列的运算,而表达式又可以表示为树,称为CSG树
过程生成
- 用函数描述(从基本几何体开始)生成几何体的过程
- 分形(Fractals):强调函数可以递归调用,生成自相似图形(和CSG树的主要不同点)
多边形扫描转换
X-扫描线算法
- 输入:用顶点序列表示的多边形,颜色缓冲
- 顶点坐标必须是离散化的,否则无法解决误差问题
- 顶点序列必须表示正常的多边形,可以利用极角排序生成符合要求的顶点序列
- 输出:修改颜色缓冲,将多边形内部的区域填色
- 算法步骤:
- 输入顶点序列,确定所有顶点的y的范围
- 对于范围内的每个y的值,用一条直线与多边形求交,得到若干个交点
- 如果交点与顶点重合,则考虑该顶点的两条边,边必须位于扫描线上方(不含平行)才会记为一个交点(下闭上开)
- 可以证明,每条扫描线与多边形的交点数必然是偶数
- 将交点按照某种方式排序,然后两两配对,每对交点之间的点便是需要填色的区域
- 求出交点后,将其离散化(必须统一采用四舍五入,而不是四舍六入五留双),确定区间;之后必须采用左闭右开或右闭左开的规则
- 主要开销来自求交和排序,需要使用有向边,有向边表,活性边表来加速
- 有向边:以y较小的端点为起点,y较大的端点为终点的边(若某条边两端点的y值离散化后相等,直接忽略)
- x:表示与当前扫描线的交点的x分量;一开始设为起点的x分量,随扫描线上移而改变
- yMax:终点的y分量离散化后**-1(-1实现下闭上开);扫描线大于(-1则此处不取等)**此值后,将此有向边从活性边表中移除
- deltaX:y增加1时x的改变量
- 有向边表:包含所有有向边的集合
- 活性边表:与当前扫描线有交点的有向边构成的集合
- 扫描线上移的过程中,根据有向边的yMax和有向边表的索引不断更新活性边表;活性边表为空时,算法结束
- 每当新的边加入表(已有边的顺序不会发生变化),按照各有向边当时的x排序,若x相同,则按照deltaX排序
- 每当更新交点后,直接按表中边的顺序两两配对交点
边标志算法
-
输入输出与X-扫描线算法相同
-
算法步骤:
- 输入顶点序列,确定包含所有顶点的AABB
- 对每条边进行扫描转换(若某条边两端点的y值相等,直接忽略),得到若干离散化的点,以特殊颜色标记这些点
- 类似X扫描线算法,每条边的终点被忽略(下闭上开)
- 离散化每条边时,每次令y+1然后计算x的值,这和X扫描线算法类似,和一般的直线扫描转换不同
- 扫描转换某条边得到某个离散化的点时,如果该点已标记为特殊颜色,则重置为默认颜色
- 从下往上遍历AABB的每一行,每一行内从左往右遍历每个像素,逐像素判断是否进行填色
- 用一个bool变量表示当前点是否需要填色,一开始为false,向左遍历时,每当遇到特殊颜色的点,则取反
- 保持true的点,以及由false变为true的点要填色,而由true变为false的点不填色(左闭右开)
- 算法结束后,仍为特殊颜色的点重置为默认颜色
裁剪
- 输入带裁剪线段和裁剪窗口范围,输出裁剪后保留的线段
Cohen-Sutherland算法
- 要保留的区域有四条边,将其延长为直线,将空间划分为9个区域
- 对于每一个点,分配一个区域码(4bit)。4位分别表示,该点是否位于l1上侧/l2下侧/l3右侧/l4左侧
- 算法过程:
- 输入线段两个端点的位置,计算两个端点的区域码
- 两个区域码进行按位与,若结果不是0000,舍弃线段,结束
- 若两点均为0000,保留线段,结束
- 对于不为0000的一点,找到最低位的1,将该位对应的直线与线段求交,用交点代替原本的点,回到2
- 区域码的初始值并不完全决定算法的结果,如区域码分别为0101和1000时,无法确定线段是否完全位于裁剪窗口外
LiangBarskey算法
l:r=r0+u(r1−r0)r0,r1:线段两个端点的坐标
-
要保留的区域有四条边,将其延长为直线,将待裁剪线段用参数方程表示(因此是有向的),其端点分别对应u=0,u=1处
-
根据带裁剪线段的方向,直线与窗口的至多四个交点加上起点、终点,可以分为两组:
- 入组:有向直线从窗口外部进入窗口的点,及线段起点
- 出组:有向直线从窗口内部离开窗口的点,及线段终点
- 如果有要保留的线段,那么只可能是入组中u最大的点(记为uIn),及出组中u最小的点为端点的线段(记为uOut),且必须满足uIn<uOut
-
算法过程:
- 输入线段两个端点的位置,确定参数方程
- 令uIn=0,uOut=1(即起点和终点)
- 求直线与裁剪窗口左边界的交点
- 如果交点属于入组,则更新uIn,若已经有uIn>uOut,结束
- 如果交点属于出组,则更新uOut,若已经有uIn>uOut,结束
- 如没有交点(与左边界平行),且直线位于左边界的左侧,结束
- 类似3,如果没有结束,继续依次求与右、下、上边界的交点
- 根据uIn,uOut计算要返回的两个端点
-
直线与窗口左右边界的两个交点,必然分别属于入组、出组(上下同理),由此可以减少判断入组/出组的次数
图像信号
二维傅里叶变换
二维离散傅里叶变换:F(u,v)=x=0∑M−1y=0∑N−1f(x,y)e−2jπ(Mux+Nvy)逆变换:f(x,y)=MN1u=0∑M−1v=0∑N−1F(u,v)e2jπ(Mux+Nvy)F(u,v):变换结果(复变函数)R(u,v):实部I(u,v):虚部的实系数对于变换结果的任意一点(u0,v0):A=∣F∣=R2+I2w=u2+v2v=(u,v)ϕ=arctan[RI]A:振幅w:频率v:方向ϕ:相位
- 图像可以分解为无数二维正弦波的叠加,二维正弦波除了具有频率、幅度、相位,还具有方向
右上图像为左上图像的振幅谱
- 图像的谱:包括振幅/相位谱,均为灰度图,以u/v为横纵坐标,图像中心点为原点,各点的灰度为该点对应波的振幅/相位
- 谱中,一点到中心点的距离表示信号频率,一点相对于中心点的方向表示信号的方向
- 处理时可能会将原图复制若干份拼接起来,再计算中间一份的信号(本质上是延拓图像信号的定义域)
- 原图如果不是自拼接的,那么连接处一定会存在高频信号(因此傅里叶变换的结果图中出明亮的水平和竖直线)
采样
- 采样定理:对模拟信号(连续信号)采样时,采样频率至少要到达模拟信号最高频率的两倍,才能还原出原始信号
- 渲染过程中,“模拟信号”对应纹理(纹理仍然是离散的),“离散信号”对应渲染得到的图像
- 走样来源于采样频率(时间频率/空间频率)不足
- 走样可能导致锯齿、摩尔纹、视错觉等现象

- 采样频率不足时,在采样前使用低通滤波能够缓解走样(最常见的是均值滤波)
反走样
- 默认片元采样方式:对于每个片元,使用一个采样点,直接取当时的采样结果为输出结果
超采样(Super Sampling)
- 每个片元使用多个采样点,分别计算采样结果,对其加权平均得到输出结果
- 采样的开销成倍增加
多重采样(Multiple Sampling)
时间采样(Temporal Sampling)
- 用于片元着色器中反走样
- 每个片元使用当前采样点,以及前若干帧内“相同位置”的片元的采样结果,对其加权平均得到输出结果
- 采样点(相对于片元中心)的位置不必固定,可以随时间按一定规律变动
- 每个片元中心会映射到某物体的某个纹理上的某个位置,(近似)映射到同一物体的同一纹理的同一纹理坐标的两个片元中心,称其为“相同位置”的片元
- 重投影:使用速度缓冲,来确定上一帧的某个片元与当前帧的哪个片元“位置相同”,
- 数据验证:使用某种规则来对前后采样结果对比检查,确定是否可能发生物体抖动、遮挡关系变化、光照变化等特殊情况,视情况考虑是否放弃前若干帧的采样结果
- 不增加总采样次数,开销较低
滤波
空间域滤波
一元函数卷积:g(t)=h∗f=∫−∞∞h(t−τ)f(τ)dτ二元函数卷积:g(x,y)=h∗f=∫−∞∞∫−∞∞h(x−u,y−v)f(u,v)dudv
- 将图像视为二元函数,空间域滤波的本质是卷积运算。而图像是离散的,所以卷积运算的具体方法是在图像上应用"模板"
- 均值滤波:以默认方式应用一个模板;效果为,某像素的颜色变为周围像素颜色的加权平均
- 中值滤波:应用一个模板,模板的作用仅仅是规定一个区域;某像素的颜色变为自身及周围若干个像素的颜色的“中位数”
- 微分锐化
- Roberts锐化算法:对原图像应用两个模板,两个结果的和为结果
- Priwittl锐化算法:对原图像应用两个模板,两个结果的平方和为结果
- Sobe锐化算法:过程同Priwittl锐化算法

频率域滤波
- 将图像视为二元函数,频率域滤波的过程是固定的:
- 预处理
- 对图像进行离散二维傅里叶变换(空间域→频率域)
- 对变换结果进行某种处理
- 对图像进行离散二维逆傅里叶变换(频率域→空间域)
- 后处理
高通滤波
低通滤波
着色
- 计算各片元的颜色,待之后测试混合得到像素颜色,通常涉及以下值:
- 原始颜色:由纹理映射确定计算方式
- 直接光照亮度:由光照模型确定计算方式(计算直接光照亮度时不考虑遮蔽)
- 遮蔽值:计算阴影的本质是计算遮蔽值(直接光照亮度和遮蔽值分别独立地计算)
- 间接光照亮度:全局光照中包含若干种计算间接光照的方法
着色方式
-
**Flat Shading:**每个面着色一次(法线方向为任意两边的叉积),该面的所有片元颜色相同
-
**Gouraud Shading:**每个顶点着色一次,每个片元的颜色根据所属三角形顶点的颜色,利用重心坐标插值而得(直接对颜色插值完全不符合光学规律)
-
**Phong Shading:**每个片元着色一次
法线计算
法线贴图(Normal Map)
- 直接规定每个纹素的法线方向(法线是三维方向向量,可以用RGB图记录)
- 每个片元不再通过顶点插值计算法线方向,而是通过纹理映射计算
- 仅改变法线方向只能呈现出部分凹凸效果
位移贴图(Displacement Map)
- 直接规定每个纹素相对于所属模型平面的凹凸量(规定凹下或凸起的方向总是垂直于平面,所以可以用灰度图记录)
- 位移贴图在几何阶段就会发挥作用,会改变若干顶点的位置,乃至增加新的顶点和三角面(利用动态曲面细分技术)
- 每个片元不再通过顶点插值计算法线方向,而是映射到位移贴图,根据该点附近纹素的凹凸量直接计算出法线
- 位移贴图不仅改变法线方向,影响直接光照计算;还改变几何数据,影响阴影计算等(不影响渲染以外的系统)
纹理映射
-
纹理不仅包括颜色贴图,还包括法线贴图、粗糙度贴图、环境光贴图等,这些贴图中包含的信息统称为材质
-
纹理空间:纹理左下角为原点,纹理右上角为(1,1)(如果纹理坐标超出此范围,则需要考虑纹理的延展方式)
- 若纹理分辨率为N×N,则纹素在纹理空间内是边长为1/N的正方形
- 片元在屏幕空间内是边长为1的正方形,映射到纹理空间后为形状和大小不确定的四边形
-
理想情况下,进行纹理映射时,计算其在纹理空间中对应的区域,结果为该区域中所有纹素的平均值
-
顶点纹理坐标:为模型设定纹理时,每个顶点的纹理坐标便已确定,存放在顶点缓冲区中
-
片元纹理坐标:根据所属面各顶点的纹理坐标,利用重心坐标插值而得
Mipmap
- Mipmap:基于原纹理额外生成的若干个纹理;每次将前一次的纹理长和宽压缩为1/2
- 原纹理每四个纹素(2×2)一组,其平均值存放在一个纹理中,以此方式压缩
- 占用的总显存为原纹理的133%
上左图中,每个纹素的尺寸为1/16,上右图中,每个纹素的尺寸为1/8;尺寸介于这两个值之间片元会被映射到这两个mipmap上
- 三线性插值(Trilinear):求出纹理空间内某片元的尺寸,映射到尺寸最接近的两张mipmap上,两张mipmap中分求双线性插值,其结果再求线性插值
- 片元尺寸:一个片元与其相邻片元的纹理坐标的差值可以近似表示该片元在纹理空间内的尺寸
各项异性过滤
直接光照
Blinn-Phong模型
- 认为反射光=环境光(Ambient)+漫反射(Diffuse Reflection)光+镜面反射光(Specular Reflection)
- 规定了漫反射和镜面反射的计算方式(只考虑直接光照)
渲染方程中:Lr(p,l,v,n)=∬ΩLi(p,l)(l⋅n)f(p,l,v,n)dω对于物体表面任意一点p0,规定f(l,v,n)=kd+ks(l⋅h)α,并忽略间接光照⇒Lr(l,v,n)=Ld(l,v,n)+Ls(l,v,n)⇒Lr(l,v,n)=i∑[kdLi(l⋅n)+ks(l⋅h)pLi(l⋅n)]其中h=∣l+v∣l+vLi:第i个光源直射p0处的亮度Ld(l,v,n):p0处沿v方向的漫反射光亮度Ls(l,v,n):p0处沿v方向的镜面反射光亮度kd:p0处的漫反射系数ks:p0处的镜面反射系数p:可调参数,p越大越集中
- 实际用于着色时,需要将亮度改为颜色,RGB三个通道上的kd,ks可以不同(由贴图规定)
- 越光滑的物体,其镜面反射越强,即反射光越集中于与入射光对称的方向上(因此高光区域随光源和观察点变化而变化)
- 难以表现出各种不同的材质,塑料感强
Macrofacet模型
- 基于微表面模型对Blinn-Phong模型的改进
- 物体的表面从微观的角度看,是凹凸不平的;物体表面的法线越趋近于同一个方向,看起来越光滑;法线越分散,看起来越粗糙
渲染方程:Lr(p,l,v,n)=∬ΩLi(p,l)(l⋅n)f(p,l,v,n)dω对于物体表面任意一点p0,规定f(l,v,n)=kd+4 (l⋅n)(v⋅n)DFG,并忽略间接光照⇒Lr(l,v,n)=Ld(l,v,n)+Ls(l,v,n)⇒Lr(l,v,n)=i∑[kdLi(l⋅n)+4 (l⋅n)(v⋅n)DFGLi(l⋅n)]D=π((n⋅h)2(α2−1)+1)2α2其中h=∣l+v∣l+vF=F0+(1−F0)(1−(v⋅h))5G=g(l)g(v)其中g(x)=(1−k)n⋅x+kn⋅xk=8(α+1)2D:描述法线的分散程度F:描述菲涅尔现象的强度G:描述表面凹凸带来的吸光能力(值越大,吸光能力越弱)α:p0处的粗糙度F0:p0处的菲涅尔反射率
- 粗糙度决定了法线分散程度及凹凸程度,进而影响DFG,进而影响镜面反射;用一个0~1的无量纲数表示
- 粗糙度不影响漫反射,漫反射仅与材质有关(所以有漫反射系数,而没有镜面反射系数)
- 实际用于着色时,需要将亮度改为颜色,RGB三个通道上的DFG和kd可以不同(由贴图规定)
- 菲涅尔反射率决定菲涅尔现象的强度;金属较高,非金属较低,介于两者之间的值看起来不真实(如半导体)
- 能比较好真实地表现各种材质
PBR材质
阴影
Shadow Map
对于上图,P1无阴影,P2有阴影但是被剔除,P3有阴影
-
将光源视为摄像机,“拍摄”一个区域;用灰度图记录结果,即Shadow Map(类似屏幕空间)
- 本质上是从光源发出若干射线(均匀分布于“视锥”中),每条线与(不透明)物体求交,“片元”中记录第一个交点的距离(也可记录深度,类似深度缓冲)
- Shadow Map在运行时动态更新,每帧更新若干次
- 每个光源有独立的Shadow Map,独立地进行遮蔽计算
- 屏幕空间和Shadow Map采样率不同、方位不同,导致屏幕空间的片元和Shadow Map中的“像素”并不一一对应
-
将片元某个位置到光源的距离记为A,ShadowMap中对应位置值记为B:
- A=B:表示光源可直射该片元
- A>B:表示该片元与光源之间有物体遮挡
- A<B:理论上不会发生
-
然而实际上计算A,B时必然产生误差:
- A=B:完全相等的可能性几乎为0
- A>B:理论上A=B的位置,有可能因误差进入这种情况,进而导致错误地认为该位置被遮蔽
- A<B:理论上A=B的位置,有可能因误差进入这种情况,因此也认为光源可直射该片元
-
为避免错误,计算出A后,将其适当地减小一个值,该值被称为阴影偏移(Shadow Bias)
- 设置阴影偏移会导致一些“原本存在”的阴影“消失”,尤其是细小凹凸处
-
由于精度有限,Shadow Map仅适用于以漫反射为主的材质(因为亮度变化平滑)
级联阴影(Cascade Shadow)
- 将光源照射区域按照到相机的距离分为几个阶段,对于不同阶段内的光源,采用不同的Shadow Map精度
- 两个阶段间需要实现平滑过渡
- 开销较大
PCF
- 利用Shadow Mapping能够计算某光源在某片元的光照是否被遮挡,计算的结果只有被遮挡、不被遮挡两种,因此阴影很“硬”
- PCF是对Shadow Map中的遮蔽值滤波(最简单的是均值滤波),使阴影“软化”
- 现实中,通常离光源越远的位置,阴影越“软”,为此,可以在更远的位置使用更大的卷积核,以取得更明显的滤波效果,这种做法被称为PCSS
VSSM
Vitual Shadow Map
- 类似虚拟内存,随着可见区域变化,动态加载和释放Shadow Map,以节约显存空间
全局光照
- 全局光照指直接光照和间接光照(环境光),重点在于如何计算间接光照
- 最简单的做法是,将环境光设为常量
环境光遮蔽(Ambient Occlusion)
- 着色时,对于间接光照,最简单的做法是设为常量;引入环境光遮蔽后,对于每个片元,计算遮蔽值,间接光照亮度=最大间接光照亮度×遮蔽值
- 粗略地认为每个片元对应地位置上,光均匀地从半球面射入,估算这些光线被遮蔽的比例,即得到遮蔽值
- 屏幕空间是二维的,深度缓冲属于屏幕空间,根据各位置不同的深度值,可以构造出一个三维空间(类似于立方体积木,以下称为**“积木空间”**)
SSAO+
- 在积木空间中,在某片元的位置附近取若干个采样点,判断其是否位于积木内部
- 遮蔽值=位于积木内部的采样点比例
- 由于采样的随机性,相邻片元间的遮蔽值可能有较大波动,需要平滑处理
HBAO
- 在积木空间中,以某片元为中心发射若干条射线,计算各方向上积木的遮蔽角度
- 一个方向的遮蔽值=遮蔽角度/90°,遮蔽值=各个方向遮蔽值的平均值
环境光遮蔽贴图
- 将物体表面预计算好的间接光照遮蔽值存在纹理中(灰度图)
- 仅考虑自身对自身的遮蔽,不考虑其他物体,适用于有明显凹凸的物体
- 作用于片着色计算阶段,对此贴图采样,影响着色结果
环境映射(贴图)

- 使用贴图记录单个物体表面的的间接光照的颜色(包括折射等传统光栅化渲染管线难以计算的)
- 可以预计算(通常使用光线追踪)生成,也可以通过拍摄现实场景得到;如果场景在变化,也可以动态更新(仅使用环境贴图的物体需要计算)
- 环境贴图可以是球面或立方体面(均存在“不均匀映射”的问题)
- 作用于片元着色计算阶段,对此贴图采样得到颜色,参与颜色相加
光照贴图
场景中所有物体的所有表面的着色结果统一记录在一张贴图上;与单纯的一张图片不同,静态场景还是可以从不同的方位拍摄
- 使用贴图记录整个场景的经由直接+间接光照后的最终颜色(可以只计算间接光照,运行时计算直接光照)
- 由**预计算(光栅化渲染管线难以计算,通常使用光线追踪计算)**生成,常用于静态场景
- 作用于片元着色计算阶段,对此贴图采样直接得到颜色
Light Probe
Light Probe不是真实存在的物体,不会产生遮挡;完全不会被光照的位置不需要放置Light Probe
- 在场景中放置大量Light Probe,计算反射到其球面上的间接光照结果(生成方式类似环境贴图)
- Light Probe不需要每一帧更新,而是光源或某些物体运动后,更新其附近的Light Probe;更新不需要立即执行,可以延迟以实现负载均衡
- Light Probe的采样点数量较少,能用于实时渲染,得到的间接光照结果远达不到精确
- 作用于片元着色计算阶段,查找片元附近最近的几个Light Probe,对其采样,加权平均得到间接光照颜色,参与颜色相加
- 获取了片元的法线方向,便可以确定Light Probe上的采样点坐标
- 常用的权重是片元(的世界坐标)到各Light Probe球心的距离的反比
后处理
FXAA
光线追踪
- 光线追踪通常被用于光栅化的着色这一步中,计算间接光照,而不是整个取代光栅化渲染管线
- 光线追踪中对光的假设:
- 光线在物体表面发生反射和折射时,均会发生分散,不再完全集中为一条直线。反射的分散称为漫反射,折射的分散称为散射
Ray Casting
- 原始的光线追踪,仅计算直接光照
- 渲染过程:
- 对于每个像素,从相机向该片元发出射线
- 计算射线与场景中物体的第一个交点
- 根据交点坐标,获取材质信息(包含光线反射率)
- 连接交点与光源,计算入射光亮度,进而计算反射入成像点的光线亮度
Whitted-Style Ray Tracing
- 渲染过程:
- 类似Ray Casting,计算第一个交点
- 计算交点的镜面反射和**折射(不考虑散射)**光路,进而计算交点;不断递归的(有次数上限)
- 对于递归树上的叶节点,考虑光源直射该点,然后反射到上一级节点的光亮度;逐级递归,最终汇总到第一个交点
- 物体表面材质各不相同,总是可以分成反射、折射、漫反射的部分,漫反射的部分的计算方式和光栅化渲染管线相同(只考虑光源直射后的漫反射)
- 由于未考虑散射,渲染结果不真实
Monte Carlo Path Tracing
蒙特卡洛积分法:待计算表达式:S=∫abs(x)dx确定一个合适的连续随机变量X,满足∫abfX(x)dx=1进行n次采样,结果为{X1,...,Xn},则蒙特卡洛积分结果为:S′=n1i=1∑nfX(Xi)s(Xi)蒙特卡洛积分计算渲染方程:Lr(p,v,n)=∬ΩLi(p,l)(l⋅n)f(p,l,v,n)dω≈N1i=1∑np(ωi)Li(p,li)(li⋅n)f(p,li,v,n)若进行半球面均匀采样,p(ωi)=2π1
- 渲染过程:
- 类似Ray Casting,计算交点
- 在交点处半球面(如果要计算折射,改为球面)范围内进行**"随机"采样**,计算随机方向上的反射光路(递归的,通常会设置次数上限)
- 对于递归过程中计算出的每个交点,获取材质信息(包含光线反射率、折射率),并计算入射光亮度
- 根据各交点的入射光亮度,逐级回溯(每次折射、反射均损失部分亮度),以蒙特卡洛积分法得到射入成像点的光线总亮度
- 本质上,蒙特卡洛积分是用离散点的平均值代表整个积分范围内函数的平均值
- 理论上,采样方向足够多时,能够得出正确的结果
- 采样方向不够多时,如果采样方向固定不变则可能导致物体表面明暗相间(空间噪声),采样方向改变可能导致同一个位置忽明忽暗(时间噪声)
- 可以根据采样点的位置以及环境信息,估计采样点的重要性,以进行加权平均(重要性采样)
- 要能够实时渲染,目前的性能仅支持每个交点取一个采样方向(仅在第一个交点处取多个采样方向?),因此必须采用某些方式改善噪声问题(滤波,反走样)
射线求交
与曲线方程
以参数方程表示射线:p=o+td(t>0)以曲面方程表示曲面:f(p)=0则交点满足:f(o+td)=0,解方程得到交点
与轴对齐长方体
与网格
- 未使用任何场景划分时,通常先判断射线与网格的轴对齐包围盒是否有交点;有交点再逐个与三角形面求交
先与三角形所在平面求交,再判断是否位于三角形内:
以参数方程表示射线:p=o+td(t>0)以点法式表示平面:(p−p0)⋅n=0p0:平面内任意一点n:单位法线则交点满足:(o+td−p0)⋅n=0⇒t=b⋅n(p′−o)⋅n若t>0,求对应点pt,判断pt是否位于三角形内
利用重心坐标判断是否有交点:
假设射线上一点在三角形P0P1P2内部:o+td=αP0+βP1+(1−α−β)P2⇒⎣⎡tb1b2⎦⎤=s1⋅P1P01⎣⎡s2⋅P0P2s1⋅P0Os2⋅d⎦⎤其中s1=d×P0P2,s2=P0O×P0P1若t>0,b1>0,b2>0,b1+b2<1,则射线与三角形有交点
场景划分
- 进行射线求交等计算时,通常先快速粗略地判断哪些物体需要计算,然后再逐个进行精确计算
空间划分(Spatial Partitions)
- 将整个空间划分为若干个不相容的区域,形成某种数据结构
- 物体可能同时属于若干个区域,需要某种方式避免重复求交计算
- 将空间划分为大小相同的网格,每个网格记录哪些物体占用了自身
- 空间划分过程:
- 统计物体总数,以及各个物体的空间范围,进而确定整个空间的范围
- 将空间划分为数量合适(习惯上,网格总数=30×物体总数)的网格
- 遍历每个物体,计算其占用的网格,更新网格数据
- 射线求交过程:
- 将射线“离散化”,计算其经过的网格
- 获取每个经过网格中记录的的物体,逐个与之求交
- 时间和空间开销较大,主要是因为物体分布不均匀
K-Dimensional-Tree
- 逐步划分空间,形成树状结构
- 不论是否为叶节点,其对应区域必然为轴对齐长方体
- 仅叶节点记录哪些物体占用了自身对应的区域
- 空间划分过程:?
- 射线求交过程:
- 使用一个队列记录节点,一开始令根节点入队
- 如果队列中没有节点,结束;如果队列中有节点,出队一个节点,计算射线是否经过该区域
- 如果经过,且当前节点为叶节点,令射线与该节点记录的所有物体求交;如果为非叶节点,令其子节点入队。回到2
物体划分(Object Partitions)
- 以每个物体为最小单元,划分到某种数据结构中
- 数据结构中的各个(“同级”)元素,其占用的区域可能有所重合,但其包含的物体不会重合
Bounding Volume Hierarchy(BVH)
- 逐步划分物体,形成树状结构
- 每个叶节点直接与单个物体的包围盒一一对应;每个非叶节点则记录了包围其所有子物体的包围盒
- 物体划分原则:
- 一开始,所有物体属于同一个根节点
- 对节点逐层划分,直到得到的叶节点仅包含一个物体
- 划分一个节点时,总是选择一个坐标轴(例如,某结点包围盒x轴方向最长,则选择x轴),其中物体按照其位置的x分量排序,前半和后半分别分入一个子节点
- 射线求交过程:
- 使用一个队列记录节点,一开始令根节点入队
- 如果队列中没有节点,结束;如果队列中有节点,出队一个节点,计算射线是经过该包围盒
- 如果经过,且当前节点为叶节点,令射线与该节点对应的物体求交;如果为非叶节点,令其子节点入队。回到2
- 目前最常用的场景划分方式;避免了同一物体属于多个节点,以及划分不均匀的问题
其他渲染技术
透明物体渲染
- 透明物体意味着背后的面无法剔除,意味着overdraw,为了避免渲染大量透明物体(如云雾)开销过大,可以将透明物体单独取出来,渲染到分辨率更低的缓冲上;即透明物体和不透明物体有各自的一套缓冲区,将两者的颜色缓冲依据深度缓冲按特定规则混合
地形渲染
Height Map
原理
- 记录大量均匀分布(俯视角下均匀)的顶点的高度,通过它生成mesh
数据结构
- 巨大的地图被分成若干大小相同的block,每个block通常采用四叉树的结构存储。block中还包含纹理、法线贴图等数据
剔除
- 巨大的地形必然需要剔除和动态加载,地形以block形式存储,只需要以block为单位剔除即可
纹理混合
- 纹理混合常见于地形渲染中(如过渡区域)
- 使用heightmap,只渲染每个位置上高度较高的纹理。过渡区域可以依据权重调节高度,以实现平滑的过渡
虚拟纹理