- 定义:将不同材质内容应用于不同物体对象上的过程。
- 着色只考虑着色点的存在,不考虑其他物体的遮挡等,因此不考虑阴影处理
一些前期内容的定义:
- 着色点(Shading Point)
- 观测方向(Viewer Direction)
- 表面法线(Surface Normal)
- 光照方向(Light Direction)
- 表面参数(Surface Parameters):包括颜色、反射性等
一、布林·冯反射模型(Blinn-Phong Shading)
多种反射模型相加得到的结果:
环境光/间接光照(Ambient)+漫反射(Diffuse)+高光(Specular) = 布林·冯反射模型(Blinn-Phong Shading)
公式:
L
=
L
a
+
L
d
+
L
s
=
k
a
I
a
+
k
d
(
I
/
r
2
)
m
a
x
(
0
,
n
⋅
l
)
+
k
s
(
I
/
r
2
)
m
a
x
(
0
,
n
⋅
h
)
p
L=L_a + L_d + L_s = k_a I_a + k_d(I/r^2)max(0,n·l) + k_s(I/r^2)max(0,n·h)^p
L=La+Ld+Ls=kaIa+kd(I/r2)max(0,n⋅l)+ks(I/r2)max(0,n⋅h)p
1.漫反射(Diffuse Reflection)
光线打到某个物体表面的点后,在各个方向上产生均匀散射的现象,叫做漫反射;并且在所有观察方向上都是显示同一种颜色。
①朗伯余弦定律(Lambert`s cosine law)
表示单位面积上,从面元法线方向射入的光照辐射度与在θ 角方向上的光照辐射度的关系,公式:
I
(
θ
)
=
n
∗
c
o
s
θ
I(θ)=n*cosθ
I(θ)=n∗cosθ
②朗伯着色模型(Lambertian Shading)
基于朗伯余弦定律衍生出来的着色方式,公式为:
L
=
k
(
I
/
r
2
)
m
a
x
(
0
,
n
⋅
l
)
L=k(I/r^2)max(0,n·l)
L=k(I/r2)max(0,n⋅l)
- L代表漫反射光照辐射度(diffusely reflected light)
- k代表颜色的扩散系数(color diffuse coeffiicent),即吸收率
- 系数为1,代表当前点完全不吸收能量,接收多少能量反射多少能量
- 系数为0,代表当前点完全吸收能量,接收多少能量都不反射
- I/r^2代表光源到着色点时的能量;
- 光源是以发散的形式射出光线,因此以光源为中心,以一定距离为半径r所形成的球上的光照能量为I(假定能量守恒)
- 随着距离越大,球表面积越大,光照能量不变,单位点的光照能量越少(光照能量与距离成反比)
- 对应光照能量可以抽象成公式:I/r^2
- max(0, n·I)代表着色点能够接收的能量系数;取max是因为n·I有可能存在负数(即光线从第三、四象限射入),在只考虑反射的情况下这样的光线是不会被接收处理,因此系数应该为0
【光照对应的能量公式】
2.高光(Specular Highlights)
高光项是当观察方向和镜面反射方向足够接近的时候,所能观察到的一类光线反射项;公式为:
L s = k s ( I / r 2 ) m a x ( 0 , n ⋅ h ) p L_s=k_s(I/r^2)max(0,n·h)^p Ls=ks(I/r2)max(0,n⋅h)p
- Ls代表高光(Specular Highlightst)
- ks代表颜色的扩散系数(color diffuse coeffiicent),即吸收率
- I/r^2代表光源到着色点时的能量
- max(0, n·h)代表法线n和半程向量h是否足够接近
- 高光属于理想模型(并不完全符合真实世界的高光反射现象),因此这里可以不考虑max(0, n·I),即不考虑着色点能够接收的能量系数
- max(0, n·h)^p的指数p,用于降低cos函数的变化区间;通常情况下高光所表示的角度范围非常小(±5°左右),仅用一阶cos函数所表示的范围区间过大。在布林·冯模型中,指数p的值在100-200之间。
【cos函数不同阶下的图像变化】
【颜色的扩散系数ks和指数p的变化趋势】
1.半程向量(Half Vector)
在布林·冯模型中,引出一个名词为“半程向量”,即:光线入射方向l和观测方向v的向量相加后的二等分线(bisector);
公式(分母是为了作归一化处理):
h
=
b
i
s
e
c
t
o
r
(
v
,
l
)
=
v
+
l
∣
∣
v
+
l
∣
∣
h = bisector(v, l) = \frac{v+l}{||v+l||} \quad
h=bisector(v,l)=∣∣v+l∣∣v+l
具有以下特征:
- 通过比较半程向量h和法线方向n的方向(可查看点乘结果),能确认观测方向v是否能够看到高光R
- 引入半程向量的概念,能够更加方便确认观测方向v与高光R的位置关系
3.间接光照 (Ambient lighting)
光源并不直接照射在着色点上,而是被一次或者多次反射后的光
线入射到着色点。公式为:
L
a
=
k
a
I
a
L_a = k_a I_a
La=kaIa
- La为环境光/间接光照
- ka为间接光照系数
- Ia为该点接收到四面八方的间接光照强度(暂时认为各个方向的强度都是一样的)
具有以下特征:
- 与法线、入射方向、观测方向等均无关系,可以近似认为是一个常数,即某种颜色常数
- 作用是添加恒定颜色,用来填充黑色阴影
二、着色频率(Shading Frequencies)
根据不同的着色方式,有不同的着色频率。主要的着色频率分为三种:
- 面着色
- 顶点着色
- 像素着色
着色结果取决于着色频率+几何体形体的复杂程度(即面/点/像素的密集程度)
面着色(Flat Shading)
- 三个顶点所形成的三角形作为一个平面
- 该平面只有唯一的一个法线向量
- 只对该平面做着色处理
- 着色后形成的非光滑表面
顶点着色(Gouraud Shading)
- 对每个三角形的每个顶点进行一次着色,一个平面则进行三次着色
- 每个顶点都有自己的法线向量
- 对三个顶点着色后进行线性插值处理
像素着色(Phong Shading)
- 对每个像素点进行着色
- 比较适合平滑的平面模型着色
1.如何定义面的法线
以三角形为例,根据三角形的边的向量两两做叉乘,能够得到面的法线向量(方向+大小)
2.如何定义顶点的法线
几何形体是由有限个多边形组合而成,因此多边形之间一定会共线、共顶点;那么顶点法线可以通过对共享该顶点的所有面的法线求加权平均,即可得到顶点法线向量(方向、大小)
公式:
N
v
=
N
i
∑
i
∣
∣
N
i
∑
i
∣
∣
N_v = \frac{N_i \sum_{i}}{||N_i \sum_{i}||}
Nv=∣∣Ni∑i∣∣Ni∑i
注意:
- 不能直接做简单的平均,因为每个多边形面的大小、形式等不一致,直接平均求得的结果不准确
- 加权值可以考虑多边形的面积
3.如何定义像素的法线
可以取任意两个点,求出对应的法线后,再通过重心坐标插值(Barycentric Interpolation)计算得到像素点的法线。
关于重心坐标的内容,可以参考下面第五点
三、渲染管线(Graphics Pipeline)
定义:即将3D场景转换为屏幕上2D图像的一个处理过程;主要包括:
- 顶点处理(Vertex Processing)
- 会执行Model、View、Projection的变换
- 会执行顶点着色
- 会执行纹理映射
- 三角形处理(Triangle Processing)
- 光栅化(Rasterization)
- 会执行采样操作
- 片元处理(Fragment Processing)
- 会执行深度测试
- 会执行像素着色
- 会执行纹理映射
- 帧缓冲处理(Framebuffer Operations)
四、纹理(Texture)
①纹理映射(Texture Mapping)
又称纹理贴图,是将纹理空间中的纹理像素映射到屏幕空间中的像素的过程,用于增强真实感。
- 每一个三维空间中的点都能在对应的二维空间中找到对应的位置
- 纹理坐标系:即uv坐标系,u和v的范围都在(0,1)之间(约定俗成)
- 纹理可以多次使用
拓展:如何把空间中的点映射到纹理上?(几何参数化)
②如何使用纹理
- 给定一个着色点,做插值处理,得到对应的纹理坐标uv
- 根据纹理坐标uv到纹理图中找到对应位置的数据(颜色)
- 将颜色数据当做漫反射中的Kd(漫反射中颜色扩散系数)
③纹理放大(Texture Magnification)
当一个纹理贴图的分辨率不高(比如255*255),却需要引用在高分辨率的屏幕上(比如1024*768),那么需要对纹理贴图进行放大处理,即纹理贴图上的一个像素点(texel)将对应屏幕上一定范围(比如3*3范围内)的像素点(pixels),则会出现锯齿。
为了显示更加平滑,可以运用下列方法进行优化:
双线性插值(Bilinear Interpolation)
要显示平滑,则要保证每个像素点的颜色都是不同的;要想知道非像素中心点的颜色,就要分析该点与相邻像素中心点的位置关系,然后通过线性插值的方式求得颜色。
双线性插值,是指对一个点分别做垂直、水平两个方向上的线性插值
上图红点是需要求得颜色值的像素点P,那么需要:
- 获取P点邻近的四个纹理像素点u00、u01、u10、u11的数据
- 先在水平方向上做两次线性插值,即u00与u10、u01与u11做线性插值,得到点u0、u1
- 再在垂直方向上做一次线性插值,即u0与u1做线性插值,最终得到点P
【线性插值公式】
l
e
r
p
(
x
,
v
0
,
v
1
)
=
v
0
+
x
(
v
1
−
v
0
)
lerp(x, v_0, v_1) = v_0 + x(v_1-v_0)
lerp(x,v0,v1)=v0+x(v1−v0)
【双线性插值公式】
第一步:
u
0
=
l
e
r
p
(
s
,
u
00
,
u
10
)
u0 = lerp(s, u00, u10)
u0=lerp(s,u00,u10)
u
1
=
l
e
r
p
(
s
,
u
01
,
u
11
)
u1 = lerp(s, u01, u11)
u1=lerp(s,u01,u11)
第二步:
f
(
x
,
y
)
=
l
e
r
p
(
t
,
u
0
,
u
1
)
f(x, y) = lerp(t, u0, u1)
f(x,y)=lerp(t,u0,u1)
【得到的效果】
双三次线性插值(Bicubic Interpolation)
在双线性插值的基础上,原本的取周围4个像素点变为取周围16个像素点进行线性插值处理。
【得到的效果】
④纹理缩放(Texture Scale)
和纹理放大刚好相反,即高分辨率的纹理贴图运用在低分辨率屏幕上,需要将纹理进行缩放
相比于纹理放大,纹理缩放引起的问题相对更严重:引发走样(简单来讲,就是采样点不足且采样频率过快,引发信号的丢失)
为了显示更加平滑,可以运用下列方法进行优化:
超采样(Super Sampling)
【超采样后的结果】
超采样的原理在之前已经提到,就不重复说明;超采样的确能够解决问题,但是也会带来一些新的问题:
- 高质量带来的高开销:采样频率过高、采样点过多等等
特殊的纹理贴图格式(Mipmap)
通过提前生成的方式,对一个纹理贴图生成多个不同分辨率下的纹理贴图,并组合到一起形成一个mipmap;当需要使用的时候,再到mipmap找到对应分辨率的贴图
【Mipmap的样式】
具有以下性质:
- 执行效率高
- 查询结果不够精准
- 只能针对正方形区域做查询
- 存储量是原来的4/3(1+1/4+1/16+…)
【Q&A】
Q:如何将屏幕上单个像素点所在的区域位置映射到纹理贴图中?
A:取当前像素点所在区域的四个顶点或者相邻两个像素点的位置,并在纹理坐标UV中找到,然后计算UV坐标中当前像素点与其他点的距离,通过近似的方法求得一个矩形区域。
Q:如何根据采样点/像素点在当前UV坐标中的位置,到提前计算好的Mipmap里查询到对应的纹理?
A:已知采样点/像素点所在的近似矩形区域的边长,然后求其2的对数值n,得到的是对应第n层的Mipmap,再在这个Mipmap中以1:1大小找到对应的纹理;对应公式:
D
=
l
o
g
2
L
D=log_2L
D=log2L
Q:加入采样点/像素点区域映射到UV坐标后,得到的区域边长不为2的指数值,怎么办?(比如得到值为1.5,应当到第1.5层的Mipmap查询,但实际Mipmap只有整数层)
A:在其前后层的位置分别做一次双线性插值,然后在层与层之间做一次双线性插值,总称三线性插值(Trilinear Interpolation)
Mipmap的不足
Mipmap虽然能解决走样问题,但是因为仍然是近似处理,因此会出现过度模糊(Overblur)的现象。
原因是:
Mipmap预处理的纹理贴图为正方形区域;当单个采样点/像素点区域映射到UV坐标上如果是不规则矩形的情况,此时强制近似为正方形区域将扩大并获取不必要的纹理。
这种时候需要引入一个新的概念:各向异性过滤(Anisotropic Filtering)
各向异性过滤(Anisotropic Filtering)
各向异性过滤 (Anisotropic Filtering )是用来过滤、处理当视角变化导致3D物体表面倾斜时造成的纹理错误;而传统的双线性和三线性过滤技术都是各向同性(Isotropy)的,其各方向上矢量值是一致的,即正方形形式。
各项异性用到的方式是Ripmap,其具有以下特征:
- 占据内存空间是原来的三倍(Mipmap是原来的4/3)
- 能够进一步解决Mipmap所带来“过度模糊”的问题
- 和Mipmap不同,Mipmap只是按比例缩小纹理贴图;而Ripmap是压缩贴图
- 对角线和Mipmap一样,按比例缩小
- 从左到右,每一列的纹理贴图的V坐标长度按比例缩小
- 从上到下,每一行的纹理贴图的U坐标长度按比例缩小
当然,各向异性过滤并不能真正解决问题,只是进一步优化而已(Mipmap:正方形区域 -> Ripmap:长方形区域)
拓展:如何在Ripmap上进一步优化?(EWA过滤)
⑤纹理应用
在现代GPU里,纹理 = 数据内存+数据范围查询,因此纹理贴图不仅仅是一个普通图片,还可以用作于描述任意数据类的图像内容:
- 储存环境光
- 储存微表面
- 程序化纹理
- …
环境贴图(Environment Map)
- 环境贴图可以应用于渲染真实光照
- 环境贴图不考虑光源的位置,只考虑光源的方向(所以无论多远位置的光源都会被记录在环境贴图中)
球形环境贴图(Spherical Environment Map)
可以通过在环境中放置一个光滑的球体,并在球体上记录环境光的漫反射数据,获取其数据即可形成一张环境贴图
弊端:
- 数据映射后,会发现环境贴图的上下边界位置存在严重的压缩现象
立方体环境贴图(Cube Map)
在球形环境贴图(Spherical Environment Map)的基础上,将球体表面近似为立方体的六个表面;因为立方体中每个面都是均匀近似的,因此能够解决压缩问题
【展开之后的贴图】
弊端:
- 需要额外的计算(先计算环境光在立方体上的位置,再计算立方体在球体上的位置)
凹凸贴图/法线贴图(Bump Map/Normal Map)
凹凸贴图/法线贴图(Bump Map/Normal Map)可以在不改变一个物体的几何形体的情况下,描述出一个表面凹凸不平的物体
特点:
- 该贴图只包含高度信息,不包含角度信息
- 每个纹理值代表一个高度值
- 着色点的高度值不同,其法线也不同
- 能够大大降低描述凹凸不平的物体的内存和时间消耗
处理方式:
- 将凹凸贴图的高度值应用到每个像素点上
- 求更新后的每个像素点对应的法线
- 原来的表面法线:n(p) = (0, 1)
- 求当前像素点的切线的梯度(即斜率):dp = c * [h(p+1) - h(p) ],所以n(dp) = (1, dp)
- 因为新法线和切线相互垂直,因此新法线:n(p) = (-dp, 1).normalized()
缺点:
- 物体本身的凹凸会产生阴影并投影在物体表面上,但由于是实际像素点高度不变,因此无法反映这一特性
- 物体整体的投影边界还是圆滑的,也是因为实际像素点高度不变
位移贴图(Displacement Mapping)
特点:
- 功能和凹凸贴图/法线贴图一样,用于描述物体凹凸的表面
- 输入和凹凸贴图/法线贴图一样,都是通过高度值来描述物体
- 不同点是,位移贴图真正将像素点的高度进行了移动,而凹凸贴图/法线贴图只是通过计算新的法线来“隐藏”原像素点不变的事实
优点:
- 解决凹凸贴图/法线贴图的阴影问题
缺点:
- 物体三角形要足够细致(曲面细分),即纹理采样点、采样率要足够高
拓展:在使用位移贴图时,如何知道物体表面三角形划分足够细致?(DirecX的动态曲面细分,先简单划分,在应用位移贴图时再进行检查并进一步划分三角形)
五、重心坐标(Barycentric Coordinaties)
①插值(Interpolation)
- 为什么需要插值
- 希望在三角形内部得到一个平滑的过渡
- 哪里使用到插值:
- 纹理坐标
- 像素颜色
- 法线向量
- …
- 如何做插值
- 使用重心坐标
②重心坐标插值(Barycentric Interpolation)
- 定义:平面上有A1、A2、A3三点形成一个三角形,三角形内任意一点P(x, y),满足以下公式,则称P点的重心坐标为(α,β,γ)
P ( x , y ) = α A 1 + β A 2 + γ A 3 P(x, y)=\alpha A_1 + \beta A_2 + \gamma A_3 P(x,y)=αA1+βA2+γA3
α + β + γ = 1 \alpha + \beta + \gamma =1 α+β+γ=1 - 重心坐标插值可以分为一维、二维、三维三种情况,但是求像素点的法线只需要考虑一维情况即可
- 重心坐标必须做归一化(Normalize)处理
- 如果点在三角形内,则α,β,γ均为正数,否则至少存在一个非负数
【一维情况】
P点坐标:
t
=
t
x
1
+
(
1
−
t
)
x
2
t = tx_1 + (1-t)x_2
t=tx1+(1−t)x2
P点重心坐标:
(
t
,
1
−
t
)
(t, 1-t)
(t,1−t)
③ 如何求重心坐标
重心坐标中P点的α,β,γ,与P点和三角形顶点所围成的三个三角形面积比相关。
假设:
- 点P与顶点B、C围成的三角形的面积为Aa
- 点P与顶点A、C围成的三角形的面积为Ab
- 点P与顶点A、B围成的三角形的面积为Ac
则存在关系:
α
=
A
a
A
a
+
A
b
+
A
c
\alpha=\frac{A_a}{A_a+A_b+A_c}
α=Aa+Ab+AcAa
β
=
A
b
A
a
+
A
b
+
A
c
\beta=\frac{A_b}{A_a+A_b+A_c}
β=Aa+Ab+AcAb
γ
=
A
c
A
a
+
A
b
+
A
c
\gamma=\frac{A_c}{A_a+A_b+A_c}
γ=Aa+Ab+AcAc
④重心坐标的概念拓展
重心坐标这一概念不仅仅用于坐标上,还可以用于其他方面,比如:颜色插值、向量插值等
即公式
V
=
α
V
a
+
β
V
b
+
γ
V
c
V=\alpha V_a + \beta V_b + \gamma V_c
V=αVa+βVb+γVc
中的Va、Vb、Vc可以为任一属性(包括坐标),并且通过上面这种公式的线性变换得到点P的属性V(包括坐标)
P.S.针对三维空间的点,应该在三维空间中求点的重心坐标,而不是经过变换后,再在二维平面上求重心坐标;比如:深度插值、投影这类需要注意!!
六、阴影(Shadows)
阴影有两类:
- 硬阴影:由点光源、方向光源所形成的阴影,拥有明显的阴影边界(则一个点要么在阴影里,要么不在)
- 软阴影:由区域光源所形成的阴影,通常无明显的阴影边界
在光栅化中,如何绘制出阴影?可以通过阴影映射(Shadow Mapping)的方式进行绘制。
阴影映射(Shadow Mapping)
阴影映射(Shadow Mapping)是一个图像空间算法。具有以下特征:
- 不需要知道场景中的几何信息
- 会产生走样现象
①关键
- 如果一个点不在阴影里,则则摄像机和光源处都能看到该点
- 如果这个点在阴影里,则摄像机能看到该点,但是光源处看不到该点
②流程
- 在点光源处设置“虚拟相机”,然后从点光源出发对场景做一遍光栅化,并成像到二维图像中,然后记录各点的深度值(z-buffer)
- 从实际相机出发,对场景做一遍光栅化并记录各点深度值(z-buffer);然后场景中的每个点对应投影回点光源并在其二维图像上找到对应位置,比较其两次的深度值是否一致;如果不一致,说明为阴影点
【黄线所在交点为可见点(非阴影点)】
【红线所在交点为不可见点(阴影点)】
③存在问题
1.形成的阴影图有很多“脏数据”
原因:
深度值(z-buffer)本身是一个浮点数值,在比较是否相等的情况下,精确度不足,会导致非常相近的点不好判断
处理方案:
- 不比较是否相等,而是比较数值大小
- 指定一个误差值,比较时判断误差
2.形成的阴影图可能存在“走样”
原因:
阴影映射(Shadow Mapping)本身就是一个二维图像,拥有分辨率,当图像分辨率低而光栅化精度高时,会导致采样率不足引起“阴影走样”的现象。
处理方案:
- 提高阴影映射(Shadow Mapping)的分辨率
- 分辨率与精度问题需要根据实际情况的开销考虑