[@toc]
!!!为什么计算机图形学和计算机视觉不一样?有何区别?!!!
模型(Model) -> 图像(Image): 计算机图形学(渲染 3D -> 2D)
图像(Image) -> 模型(Model): 计算机视觉(2D -> 3D)
向量 / 矢量 (Vector):
归一化 (Normalization):
点乘(Dot):
向量a = $\bigl(
叉乘(Cross):
向量a = $\bigl(
矩阵和向量的混合运算:
当矩阵和向量相乘时,始终将向量写为纵向量并写在后面。
关于矩阵的转置,(AB)^T = B^T A^T。
常规缩放
sx ,sy分别为 x,y 方向上的缩放系数。
(
x
p
y
p
)
\bigl(
变化关系可以表现为: (x,y) -> Ssx,sy => (xp,yp)
Reflection负缩放:
当sx,sy为负数时,在图像效果上表现为对称效果,称为负缩放。
(
x
p
y
p
)
\bigl(
Shear切变:
x , y两轴对应为:x = x + ay, y = y。
(
x
p
y
p
)
\bigl(
通用旋转:
(
x
p
y
p
)
\bigl(
齐次线性变换:
(
x
p
y
p
)
\bigl(
(
x
p
y
p
)
\bigl(
平移变换:
(
x
p
y
p
)
\bigl(
由于平移变换并不是传统意义上的线性变换,我们为了能够统一表示所有基础变换,我们可以增加一个维度来表示低维度的变换。
升维表示:
2D变换的本质:
2D point = (x,y,1) ^ T
2D vector = (x,y,0) ^ T
tx,ty为对应轴偏移量:
得出:
(
x
p
y
p
w
p
)
\bigl(
(
x
y
1
)
\bigl(
使用通用的式子表达所有2D基础变换:
(
x
p
y
p
w
)
\bigl(
A 通过某种变换到 B ,那么从B 回到 A 既是 逆变换 。在矩阵角度即求矩阵的逆矩阵。
由于2D基础变换的通用表示均是 3 × 3的矩阵,故可以将许多基础变换的3 x 3矩阵依次相乘,最后得到的和变换仍是一个 3 x 3 的矩阵,最后统一对原图形作一个合成变换,即为 变换的合成。
变换的分解为合成的逆运算。
同样类比2D升维 3D的表示法,可以得出3D空间的点和向量的本质如下:
(x,y,z,w)
:
3D point = (x,y,z,1) ^ T
3D vector = (x,y,z,0) ^ T
通常,当w != 0 时,3D点的真实坐标为:(x / w, y / w, z / w )。
3D基础变换的通式:
Tips: 先进行线性变换 再 进行平移变换。
(
x
p
y
p
z
p
1
)
\bigl(
尺寸变换
S(sx,sy,sz) =
(
s
x
0
0
0
0
s
y
0
0
0
0
s
z
0
0
0
0
1
)
\bigl(
平移变换
T(tx,ty,tz) =
(
1
0
0
t
x
0
1
0
t
y
0
0
1
t
z
0
0
0
1
)
\bigl(
旋转变换
按照右手螺旋定则,可以得到:
xy = z, yz = x, zx = y
Rx(α) =
(
1
0
0
0
0
c
o
s
α
−
s
i
n
α
0
0
s
i
n
α
c
o
s
α
0
0
0
0
1
)
\bigl(
Ry(α) =
(
c
o
s
α
0
s
i
n
α
0
0
1
0
0
−
s
i
n
α
0
c
o
s
α
0
0
0
0
1
)
\bigl(
Rz(α) =
(
c
o
s
α
−
s
i
n
α
0
0
s
i
n
α
c
o
s
α
0
0
0
0
1
0
0
0
0
1
)
\bigl(
案例:飞机的三种航行方式(旋转):Roll Yaw Pitch
Rodrigues(罗德里格斯旋转方程):
n为旋转轴的向量(根据线性变换的分解可知,任何旋转默认其旋转轴过圆心,故可以使用一个简单的向量表示旋转轴。),α为绕轴旋转的角度。I是单位矩阵
(
1
0
⋯
0
0
1
⋯
0
⋮
⋮
⋱
⋮
0
0
0
1
)
\bigl(
R(n,α) = cos(α)I + (1-cosα))n(n^T) + sin(α)
(
0
−
n
z
n
y
n
z
0
−
n
x
−
n
y
n
x
0
)
\bigl(
MVP:(model -> view -> projection)
视图变换就是:将场景中所有物体,包括相机,以其自身重心为原点的坐标系转换到以相机为原点的坐标系上。(世界坐标系 -> 相机坐标系)
摄像机的定义:要确定一个摄像机的位置信息,需要确定三个参量:
已知:旋转矩阵(正交矩阵)的逆 就等于 其转置矩阵。
有了以上定义,我们可以知道,Model -> View的运算分为以下几步:
正交投影(Orthographic):
定义:平行光投影,没有“近大远小”的视觉特征。
正交投影的数学原理:
定义 原本立方体的六个顶点参数为 f(far),n(near),l(left),r(right),t(top),b(bottom):
透视投影(Perspective):
定义:点光源投影(肉眼结构),存在“近大远小”的视觉特征。
透视投影的数学原理:
挤压过程中各坐标的变化为:
yp = (n / z) y , xp = (n / z) x。带入:
M(persp->ortho)
(
x
y
z
1
)
\bigl(
可推得:M =
(
n
0
0
0
0
n
0
0
?
?
?
?
0
0
1
0
)
\bigl(
根据待定系数法:带入 近处点
(
x
y
n
1
)
\bigl(
所以M矩阵的第三行为:
(
0
0
A
B
)
\bigl(
带入 远处平面的中心点
(
0
0
f
1
)
\bigl(
解得:A = n + f, B = -nf,M(persp->Mortho) =
(
n
0
0
0
0
n
0
0
0
0
n
+
f
−
n
f
0
0
1
0
)
\bigl(
带入 Mpersp = M(persp->Mortho) * Mortho即可。
垂直可视角(vertical fov):从相机处上中点到下中点所成的夹角。
纵横比(aspect ratio): 投影的宽度 / 高度。
根据图形,可推出以下结论:tan(fovY / 2) = t / |n|, aspect = r / t。
光栅:屏幕!!!
光栅化:在屏幕上绘制出图形即为 光栅化 。
视口变换:将一块[-1,1]的几何图形 转换为 [0,width] x [0,height] ,具体操作如下:
(
x
p
y
p
z
p
1
)
\bigl(
光栅化的步骤:
造成以上现象的原因是:
信号频率过高,而采样速率过慢以至于跟不上信号的变化。
如何降低走样率:
在光栅化的过程中通过GPU并行运算:
for (each Triangle T)
for (each sample (x,y,z) in T)
if (z < zbuffer[x,y])
{
frameBuffer[x,y] = rgb;
zbuffer[x,y] = z;
}
定义:给一个物体配置 材质 的过程叫做 着色。
高光: 一个物体上最亮的部分。
漫反射:明暗的交界处变化不是很明显的区域。
环境光照: 间接光照,通过多次漫反射照射到的区域,一般较暗。
漫反射Diffuse:
光通量:通过着色点的能量多少(沿着色点法线方向射入的光线数量)
在漫反射中,cos θ = I * n, I 着色点指向光源的向量,n是垂直于着色点表面的向量。θ为l和n的夹角。
最终光线表达式: Ld = kd(I / r²) max (0, n*I),
kd是漫反射系数,与着色面参数有关(颜色,光感等等),r是着色点距离点光源的距离。I / r² 指有多少光到达了着色点。加上角度即为 着色点吸收了多少能量。
镜面反射方案Specularly:
表达式: Ls = ks(I/r²)max(0, n*h)^p
ks指镜面反射系数,h为半程向量,算法为:h = (v + l).normalized,光照方向与视野方向的向量和做归一化。p用来控制高光的大小,一般会取 100数量级左右。
环境光Ambient:
环境光不关心其从哪射出,也不关心距离,仅在计算光照时最后加上这一项即可。
表达式:La = kaIa
Blinn-Phong光照模型:
L = La + Ld + Ls = kaIa + kd(I/r²)max(0,nI) + ks(I/r²)max(0,nh)^p
Flat Shading:
通过三角形法线对几何体面上的 每个三角面片 进行着色操作。
Gouraud Shading:
通过三角面片的 每个顶点 的法线方向进行着色,并通过插值运算对三角形内部进行着色。
Phong Shading:
通过三角面片上的 每个像素 进行着色。
Shader 着色器:
uv纹理贴图介绍:
重心坐标:
定义:已知三角形三个顶点A B C, 存在一个点P(x,y)满足 P = αA + βB + γC, α + β + γ = 1时,我们称P点为三角形的重心。 如何快速求解重心参数(奔驰定理):
分别连接 AP,BP,CP,PBC组成的三角形面积为SA,同理得出SB,SC,则:
α = SA / (SA + SB + SC), β = SB / (SA + SB + SC), γ = SC / (SA + SB + SC)
纹理放大:
当放大后纹理信息不足以支撑显示时,需要对其进行纹理放大,为了处理放大的模糊情况,我们可以采用以下方法进行修复:
线性插值:
表达式: lerp(x, v0, v1) = v0 + x(v1 - v0)
双线性插值 Bilinear:
取其中四个像素块,基于以上线性插值表达式,依次进行二次水平插值与竖直插值即可。
案例:
存在四个像素块u01,u11,u00,u10,对其进行以下操作:
//两次水平插值,s为目标点到 u00 点的水平距离
u0 = lerp(s, u00, u10)
u1 = lerp(s, u01, u11)
//一次竖直插值,t为目标点到 u00 点的竖直距离
(x,y) = lerp(t, u0, u1)
双三次插值 Bicubic:
取其中16个像素块,依次进行线性插值即可。
作用:允许 快速地,近似的,正方形的 范围查询(Range Query)
对于一个宫殿模型来讲:
Mipmap详解:
Mipmap的存储空间是相比原来额外的 1/3!
Mipmap仅仅使用了查询与多次插值运算就 实现了屏幕的范围快速查找,但它还有以下缺陷:
通过 各向异性过滤 可以将局限形状扩展为 矩形。(相比Mipmap长宽的压缩算法被分开)
纹理的另一种理解: Texture = Memory(存储) + Range Query(范围查询),本质就是数据。
我们之所以能够看到物体,并且看到物体不同的面,是因为周围有着环境光照,而环境光我们默认其由某一个固定方向的无穷远处射入,但在表达上,我们可以采用球形包围盒,方形包围盒等方式来可视化环境光照,都是为了描述来自不同方向的环境光照。
球形包围盒 Spherical Map:
方形包围盒 Cube Map:
凹凸贴图 Bump Mapping:
求某点P法线normal的数学方法:
位移贴图 Displacement Mapping:
Implicit(隐式):
具体实例:
Explicit(显式):
具体实例:
obj文件数据信息:
# 空间中的8个点
v vx vy vz # 1
v vx vy vz # 2
v vx vy vz # 3
...
# 纹理坐标
vt vu vv # 1
...
# 几何体的六个法向量
vn vnx vny vnz # 1
vn vnx vny vnz # 2
...
# 表面映射关系[(顶点索引,纹理坐标,法线)]
f 5/1/1 1/2/1 4/3/1 # 以v5,v1,v4三顶点构成的面,同理规定纹理坐标和法向量
f ...
贝塞尔曲线 Bezier Curve:
一种 显示存储 的曲线。
定义:
空间中存在四个点 p0,p1,p2,p3,要求一条曲线必须过起点和终点 p0和p3,并且在这两点的切线必须满足 t0 = 3(p1-p0), t1 = 3(p3 - p2)。
De Casteljau求贝塞尔曲线的算法
总结:其实该算法也是用了线性插值和递归的思想归纳出曲线必过的某点。
数学推导:
对于3点两段的贝塞尔模型来说:
b0^1(t) = (1 - t)b0 + tb1
b1^1(t) = (1 - t)b1 + tb2
b0^2(t) = (1 - t)b0^1(t) + tb1^1(t) = (1 - t)²b0 + 2t(1 - t)b1 + t²b2
推广到通用高阶贝塞尔曲线:
b0^n(t) = Σ ~ (j = 0) ^ (n) bjBj^n(t)
Bi^n(t) =
(
n
i
)
\bigl(
三维贝塞尔曲线同样适合以上定律。
性质:
逐段贝塞尔曲线 Piecewise Bezier Curves:
分段贝塞尔曲线需要处理的问题之一:
如何控制每一段连接过渡平滑,由此引申出下述不同的曲线连续:
更多类型的曲线 Splines样条
Spline 样条。
B-Splines B样条。
贝塞尔曲面的计算方法
:
网格细分 Mesh Subdivision
三角形网格细分的基础操作:
Loop Subdivision:
Catmull-Clark Subdivision:
网格简化 Mesh Simplification
边坍缩
网格重构 Mesh Regularization
一种 仅能处理点光源形成阴影 的方法。
Key: 不在阴影中的点必须满足两个条件:
处理步骤:
存在的问题:
软阴影:
光线追踪 与 光栅化:
关于光线:
初步理解:
该初步理解仍旧存在许多细节实现的问题,下面我们将依次解决:
光线方程与平面法线方程:
r(t) = o + td, 0 <= t < ∞
光线射线方程,t为某一时刻,o为光线射出点,d为光线方向向量。
平面的 点法式: (p - pprime) * N = 0,N是平面的法向量。
光线与物体的交互基础算法:
我们知道一个物体的表面是由许多三角面片组成的,那么我们需要判断光线是否穿过物体即需要判断光线是否穿过物体上的每一个三角形。为了这一目标,我们可以转换思维:
交互的加速算法:
在大型游戏实际游戏场景中,由成千上万游戏物体,我们如果要计算光线与所有物体的所有三角形的交点,性能极低,于是我们需要使用一些算法来优化该过程:
在3维几何中能够完全包裹住 几何物体 的立方体小盒。特殊的可以使用 Axis-Aligned BoundingBox(轴对齐包围盒)。
根据AABB包围盒我们可以得知:
以上思维会出现几个疑问:
总结下来,当且仅当 t(enter) < t(exit) && t(exit) >= 0时,光线才和盒子存在交点。
普通的包围盒:
解方程解出交点: t = [(pprime - o) · N] / (d · N)
轴对齐包围盒:
解方程解出单个维度的交点: t = [pxprime - ox] / dx
空间划分:
八叉树(四叉树) Oct-Tree ,K维的二叉树 KD-Tree,二叉空间分区树 BSP-Tree
KD Tree设计思路:
空间维度的划分
数据结构:
存在的问题:
Bounding Volume Hierarchy(BVH)
物体维度上的划分
在KD Tree的空间分块基础上,采用BVH划分物体的思想(完全不考虑空间上的覆盖关系),可以避开KD Tree重复计算三角形的问题。
BVH的大致步骤:
如何高效的划分物体:
BVH的数据结构与算法:
DataStruct:
Algorithm:
伪代码:
Intersect(Ray ray, BVH node)
{
if (ray misses node.boundingbox) return;
if (node is a leaf node)
{
test intersection with all objs;
return closest intersection
}
hit1 = Interaction(ray, node.child1);
hit2 = Interaction(ray, node.child2);
return the closer of hit1,hit2;
}
核心问题: 如何描述光照?
以及以下物理量的介绍:
Radiant Energy:
定义: 理解为能量,单位是 焦耳(J),表示为Q。
Radiant Flux:
定义: 单位时间的能量(功率), 单位是 瓦特(W) | 刘明(lm), 表示为 Φ = dQ / dt
Radiant Intensity
定义: 单位立体角的能量, 单位是 坎德拉(cd), 表示为 I(ω) = dΦ / dω
Solid Angles 立体角:
要了解立体角,我们要先了解2维平面中的弧度制:
θ = l / r, l是θ角对应的一段弧,r是圆的半径。
根据二维推出三维空间中,立体角中:
Ω = A / r², A是Ω角对应的弧面面积,r是圆的半径。
[在均匀点光源中,Intensity和方向无关。I = Φ / 4Π ]
经过推导,立体角 的相比 天顶角 θ 和 方位角 Φ 的变化公式为:
dω = dA / r² = sinθ dθ dΦ
Irradiance 辐照度
定义: 单位面积上对应的能量,单位是 W / m² = lm / m² = lux 。
公式: E(x) = dΦ(x) / dA
对点光源模型来说,Intensity是不变的,而Irradiance是变化的。
Radiance 辐亮度
定义: 单位立体角,单位照射面积下的功率,单位是 lm / srm² = cd / m² = nit。
从理解方面上想,Radiance即为Irradiance从某个方向射入的能量。
Bidirectional Reflectance Distribution Function(BRDF): 中文名为 双向反射分布函数
引入概念的目的:
已知 入射光能量和角度,射到物体表面会向多个方向辐射,辐射出去的能量跟角度不一样,求得给定方向辐射的能量是多少。
【总结下来:BRDF就是某一单位面积吸收能量后往某个方向反射出去的比例(能量分布)公式。】
物理意义:
物体表面和光线是如何作用的。
公式:
Lr = ∫H² fr Li(p,ωi)cosθi dωi, fr是BRDF的比例,后面是这块面积接收到的纬度相同的入射光的能量,H²是指描述光线传播的半球模型。
Reflection Equation
首先从反射方程角度入手:
Lr(x,ωr) = Le(x,ωr) + Li(x,ωi)f(x,ωi,ωr)(ωi,n),x是目标点,ωi是入射角,ωr是出射角
对于单个点光源模型来说,反射光应为 自己的光(右边第一项) + 入射光(光源) * BRDF比例 * 入射光夹角
对多个点光源来说,只需要再上述公式基础上做累加:
Lr(x,ωr) = Le(x,ωr) + ∑Li(x,ωi)f(x,ωi,ωr)(ωi,n)
而对于面光源来说,可以将其看作多个点光源集合,上述式子变为:
Lr(x,ωr) = Le(x,ωr) + ∫Ω Li(x,ωi)f(x,ωi,ωr)cosθi dωi
由于某点真实光照是由一次直接光照(自身发光)和若干次间接光照求和产生的效果,我们可以将渲染方程简化为:
l(u) = e(u) + ∫l(v) K(u,v)dv还可以简化为↓
L = E + KL,该方程是一个递归方程,求解出L为:
L = E + KE + K²E + …,由此我们引入全局光照 概念:
全局光照:某光源经过N次弹射后最终产生的效果。
概率论提要:
所需知识: PDF(Probability Density Functions) 概率密度分布方程。
p(x)满足条件:p(x) >= 0 and ∫p(x)dx = 1
X的数学期望:E[X] = ∫ xp(x) dx
Monte Carlo Intergration:
基础蒙特卡洛积分方程:
FN = (b - a) / N Σ(1->N) f(Xi)
蒙特卡洛积分方程:
∫f(x) dx = 1 / N Σ(1->N) f(Xi) / p(Xi)
基于以上方程,可以得知下述性质:
复习 Whitted-style ray tracing:
使用 Monte Carlo积分方程解 渲染方程:
得到 Lo(p, ω) ≈ 1 / N Σ(1->N) Li(p,ωi)fr(p,ωi,ωo)(n·ωi) / pdf(ωi)
即得到一个渲染算法:shade(p,ωo) ↓↓↓
//仅考虑直接光照的情况
shade(p, ωo) - p是采样点坐标,ωo是入射方向
//随机在PDF中选择N个方向ωi
Lo = 0.0
foreach ωi
Trace a ray r(p,ωi) //跟踪射线
if ray hit the light //如果射线达到了光源
Lo += (1 / N) * Li * fr * cos / pdf(ωi) //效果叠加
return Lo;
算法改进:
shade(p, ωo) - p是采样点坐标,ωo是入射方向
//随机在PDF中选择N个方向ωi
Lo = 0.0
foreach ωi
Trace a ray r(p,ωi) //跟踪射线
if ray hit the light //如果射线达到了光源
Lo += (1 / N) * Li * fr * cos / pdf(ωi) //效果叠加
else if ray hit an object at Q
Lo += (1 / N) * shade(q,-ωi) * fr * cos / pdf(ωi)
return Lo;
根据以上改进,我们发现当射入100跟光线时,后续递归深度2层以上即会造成计算量爆炸,所以我们使用蒙氏进行路径追踪时,仅考虑一条光线射入。
改进:
shade(p, ωo) - p是采样点坐标,ωo是入射方向
//随机在PDF中选择1个方向ωi
Trace a ray r(p,ωi) //跟踪射线
if ray hit the light //如果射线达到了光源
return (1 / N) * Li * fr * cos / pdf(ωi) //效果叠加
else if ray hit an object at Q
return (1 / N) * shade(q,-ωi) * fr * cos / pdf(ωi)
我们再引入如果尽可能还原现实中光线是无限次弹射的,使用 俄罗斯轮盘赌 思维改进算法:
离散型随机变量 数学期望: E = P * (Lo / P) + (1 - P)*0 = Lo
shade(p,ωo)
//规定一个概率值
P_RR = ?
ksi = Random.Range(0,1)
if (ksi > P_RR) return 0.0
Trace a ray r(p,ωi) //跟踪射线
if ray hit the light //如果射线达到了光源
return (1 / N) * Li * fr * cos / pdf(ωi) / P_RR
else if ray hit an object at Q
return (1 / N) * shade(q,-ωi) * fr * cos / pdf(ωi) / P_RR
这种算法已经没问题了,但效率方面较低。对于面积较小的光源来讲,光线打到光源的概率几乎为0,我们需要采用直接再光源采样去优化算法。