Applying Textures 应用纹理
原理:屏幕上的任何一个点找到对应的纹理上的点,然后直接查,这个点对应纹理上的哪个点。
那如果这么简单就好了!那么我们这么查,有什么问题呢?
Texture Magnification 纹理放大
What if the texture is too small?
如果纹理太小,我们去查,纹理就会被拉大,导致我们看到这样的现象:
Generally don’t want this — insufficient texture resolution
通常,我们不希望纹理不足,我们希望纹理上的一个像素就对应屏幕上的一个像素。
当一个点去查找纹理,查找的不是一个整数时怎么办?这个时候就四舍五入了,找一个近似的。但是这样就会挺丑的,肯定会造成不连续的现象,也就是锯齿。
A pixel on a texture — a texel
纹理上的一个像素也是有名字的:texel
Bilinear Interpolation 双线性插值
显然,这个时候就没有映射到纹理上的整点位置。按照最近查找,那么一定会找到这个点:
然后就会产生锯齿的感觉。
我们多找几个点,找临近的4个点试一试:
Take 4 nearest sample locations, with texture values as labeled.
取4个最近的样本位置,用纹理值作为标记。
And fractional offsets,
(
s
,
t
)
(s,t)
(s,t) as shown
分数偏移量
(
s
,
t
)
(s,t)
(s,t),如图所示
Linear interpolation 线性插值 (1D)
lerp
(
x
,
v
0
,
v
1
)
=
v
0
+
x
(
v
1
−
v
0
)
\operatorname{lerp}\left(x, v_{0}, v_{1}\right)=v_{0}+x\left(v_{1}-v_{0}\right)
lerp(x,v0,v1)=v0+x(v1−v0)
Two helper lerps 两个辅助的差值
u
0
=
lerp
(
s
,
u
00
,
u
10
)
u
1
=
lerp
(
s
,
u
01
,
u
11
)
\begin{aligned} &u_{0}=\operatorname{lerp}\left(s, u_{00}, u_{10}\right) \\ &u_{1}=\operatorname{lerp}\left(s, u_{01}, u_{11}\right) \end{aligned}
u0=lerp(s,u00,u10)u1=lerp(s,u01,u11)
Final vertical lerp, to get result: 最后一次差值,得出结果:
f
(
x
,
y
)
=
lerp
(
t
,
u
0
,
u
1
)
f(x, y)=\operatorname{lerp}\left(t, u_{0}, u_{1}\right)
f(x,y)=lerp(t,u0,u1)
这里就是做了两趟差值,水平差值两次,竖直差值一次,所以我们叫它双线性插值。
我的个人看法,就是将附近的四个点的颜色值进行加权平均。
处理出来的图像效果如下所示:
Bicubic 双立方插值
相比双线性差值,Bicubic不止取4个样本,而是取16个样本,每次4个进行差值,增大了开销,但也细化了图像。(虽然这张图看上去就那样,我们就假设看到的比Bilinear更清晰吧XD )
What if the texture is too large?
如果纹理过大,又会导致什么问题呢?
Point Sampling Textures — Problem
可以看到,远处出现了摩尔纹,近处出现了锯齿。
这个是什么形成的呢?当我们还在用中心点对中心点的方式时,会发现,像素覆盖的纹理区域过大时,也会走样。
那么,超采样能得到结果么?
答案是能,但是消耗太大了。
Antialiasing — Supersampling?
Will supersampling work?
- Yes, high quality, but costly 是的,质量高,但开销也高
- When highly minified, many texels in pixel footprint 当高度缩小时,许多texels会在像素覆盖面中出现
- Signal frequency too large in a pixel 在一个像素内的信号频率过大
- Need even higher sampling frequency甚至需要更高的采样频率
Let’s understand this problem in another way
- What if we don’t sample? 如果我们可以避免采样?
- Just need to get the average value within a range!只需要在一个范围内的平均值!
如果不采样就能知道这里的平均值是多少?那就好办了!
Point Query 点查询 vs. (Avg.) Range Query 范围查询
现在我们考虑一个问题:点查询和范围查询。
Different Pixels -> Different-Sized Footprints
假设我们用到的是同样的纹理,那么这幅图中的远处的像素就会覆盖到很多的纹理。
这里就要引入一个在图形学上广泛运用的经典概念——Mipmap
Mipmap
Allowing (fast, approx, square) range queries 允许(快速、近似、仅为正方形)的范围查询
它将纹理分成了很多层,不同的层的分辨率都不一样,每一层都在上一层的基础上将分辨率砍一半。
因此,它有 l o g 2 N log_2N log2N层。
那么,我们原本只有一张图,现在我们生成了这么多张图,我们总共引入了多大的额外的存储呢?
很简单,大家可以自己算一算。
就是原本的图的1/3。
这个额外的存储量似乎还比较可以接受。
Computing Mipmap Level D
计算第D层的Mipmap
Estimate texture footprint using texture coordinates of neighboring screen samples
使用相邻屏幕样本的纹理坐标来估计纹理覆盖面
可以用这张图来理解:越暖越深的颜色,表示层数越少,比如深红色表示第0层,蓝色表示查询第n层。
但是,这样查询的出来的图像一定会有缝。假如我们想查询第1.8层,怎么办呢?
差值。我们求出第一层和第二层的值,并且进行一个差值。
这就是三线性插值。
Trilinear Interpolation 三线性插值
相比之前不做第三次差值,开销只是多了一次差值计算,是可以接受的。
用了三线性插值的图像也变得很漂亮了
(当然,柱子上还是有不正常的地方,那是图形本身的问题,在这里我们不考虑。)
Mipmap Limitations
Mipmap的限制
远处的图像看上去就……已经完全合成一整块了,这个比Supersampling 512x的效果可差多了。
Anisotropic Filtering 各向异性过滤
各向异性过滤允许我们对长条形的这种区域进行快速的范围查询,结果也会好很多。生成的各向异性过滤图,总共的开销也会是原本的3倍,对比Mipmap,存储的代价变高了。
当然,在打游戏的时候,只要你显存足够,就把各向异性过滤开到最高就好。
各向异性过滤生成的这张图的名字叫Ripmap。
但是对于斜着的区域,它查询得就不是很好。
Irregular Pixel Footprint in Texture
EWA filtering
EWA过滤
- Use multiple lookups
使用多个查找 - Weighted average
加权平均值 - Mipmap hierarchy still helps
Mipmap层次结构仍然有帮助 - Can handle irregular footprints
可以处理不规则的覆盖面
用很多不同的圆形,去覆盖这个不规则的图形。但是代价就是多次查询。
纹理的作用
Many, Many Uses for Texturing 纹理真的有很多用途
In modern GPUs, texture = memory + range query (filtering) 在现代gpu中,纹理=内存+范围查询(过滤)
- Environment lighting 环境光
- Store microgeometry 存储微几何图形
- Procedural textures 可编程纹理
- Solid modeling 实体造型
- Volume rendering 立体渲染
Environment Map
环境光照、环境贴图:只记录它们的方向信息,认为它们无限远,没有深度信息。
Environmental Lighting 环境光照
正如世界地图那样,欧洲比地图上画的要大,南极洲也是。在世界地图上,靠近极点的位置被强烈地扭曲,这就是问题。
用一个立方体把球包住,创造一个球的包围盒。把光照信息存在立方体的表面上,得到立方体的六个表面。
每个球表面的像素都作法线,在立方体上找到对应的像素。这样可以减少扭曲。
扭曲变少了,但是需要根据方向来寻找在立方体上的哪一个面,增加了计算量。
利用纹理定义相对基本表面的高度,在纹理上查询相对高度,使法线发生变化(实际上是人为做的假的法线),法线变化引起着色变化,让球面看起来凹凸不平。
Bump Mapping 法线贴图
通过法线贴图定义一个纹理,但不去改变任何几何信息,该多少个三角形还是多少个三角形。
然后把任何一个像素的法线都做一个扰动。
扰动:通过定义不同位置的高度,与邻近位置的高度差,来重新计算法线。
由纹理定义的每个纹理的“高度移动”。
How to perturb the normal (in flatland) 如何计算扰动(不考虑二维和三维的空间,只考虑函数)
- 假设原本的点的法线 n ( p ) = ( 0 , 1 ) n(p)=(0,1) n(p)=(0,1)
- 在任何一个点处,计算凹凸贴图给出来的梯度(导数)——取邻近的两个点的高度相减,得到切线
- 得到法线,就是垂直于切线的方向。
- 在一维的情况下,求法线就是简单的几何函数,因为其实并没有求导,只是拿邻近的高度相减来代替真正的求导过程,这个过程就求了一个斜率k。再求一个垂直于切线的方向作为法线:
(
a
,
b
)
(a,b)
(a,b)的垂直向量为
(
−
b
,
a
)
(-b,a)
(−b,a),又因为
b
=
k
a
b=ka
b=ka,因此垂直向量为
(
−
k
a
,
a
)
(-ka,a)
(−ka,a),约分
a
a
a,就是
(
−
k
,
1
)
(-k,1)
(−k,1)。
How to perturb the normal (in 3D) 如何计算扰动(3d) - 假设原本的法线 n ( p ) = ( 0 , 0 , 1 ) n(p)=(0,0,1) n(p)=(0,0,1)
- 求出梯度,分别在水平和竖直方向变化一个单位,求出斜率
- 求出不同方向的导数(实际上是近似的)之后,就可以利用类似上一页的原理,直接改变正负来求出法线。
- 在局部坐标系里,原本的法线设为 ( 0 , 0 , 1 ) (0,0,1) (0,0,1),代入原本法线时,进行简单的坐标变换。
Displacement mapping 位移贴图
和凹凸贴图一样,都是用纹理定义高度。但位移贴图实际上会把顶点真的进行位移。