LiteralliJeff 发表于 2022-10-3 15:50

【图形基础】纹理采样小结

我们知道,渲染 3D 物体时,常用的一个方法是给 3D 物体上贴图来表现物体细节。



至于一张贴图如何贴在3D物体表面的,Real-Time Rendering 里有个介绍:


撇开物体表面到 贴图空间的坐标转换不说的话,单从拿到贴图空间坐标 (u,v) 后,到实际取到贴图上的值中间的过程可以称为纹理采样,也是本文着墨点。
纹理采样时相当于拿一个 归一化的 纹理坐标(u,v) 映射回真实贴图的像素坐标,假定贴图大小是 256 * 256, (u,v) = (0.6, 0.6), 对应的贴图像素坐标为 (256 * 0.6, 256 * 0.6) = (153.6, 153.6)。 这时问题来了,贴图上并不存在小数点的贴图像素怎么办,最简单的办法:
Nearest Point Sampling(最近点采样)

最近点采样其实就是四舍五入,(153.6, 153.6) 会被映射到 (154, 154), 这种做法在贴图的图像边缘采样时容易失真。下图展示了这一种失真1:



其行成原因大致图示:



这种失真对贴图有放大缩小的情况会更为明显,为了解决这些问题,催生出 Texture filtering 的做法,常见的有:
Bilinear texture filtering (双线性采样)

它的原理是取邻近4个纹素来算个加权平均值,举个栗子:



上图的每个带颜色的对应uv分别是:
红:(0.375, 0.375)
绿:(0.625, 0.375)
蓝: (0.375, 0.625)
白: (0.625, 0.625)
当对uv = (0.5, 0.5) 采样时:
0.25 * (255, 0, 0)
0.25 * (0, 255, 0)
0.25 * (0, 0, 255)
## + 0.25 * (255, 255, 25
5)
----------------
= (128, 128, 128)注意这里每次双线性采样都需要访问4次贴图,在没有 Texture cache 的情况下,性能是 最近点采样的4倍2
实际效果如下:



更为形象的细节图示:



双纯性采样可以改善放大、缩小带来的边缘跳变,但对于贴图形变则不太有效,常见的形变有透视带来的视角形变,比如常见地砖:



这里就要深究这种失真的根源,其实贴图采样带来的失真,往往都是贴图实际映射到屏幕上的像素,跟其uv换算出来的纹素(即贴图上实际对应的像素)非一一对应所造成的,相差越大失真越原重。上图的失真是由于一个像素覆盖了多个纹素,采样数跟不上。这时按道理是要提升采样数,来解决失真,但在实时渲染中也不能无限去提升采样数,较好的办法是可以先尽量把贴图的信号频率与真实的呈现频率一致,就是让像素和纹素的倍差不那么大。做法是给贴图生成等比缩小的mipmap:



让远处的地砖匹配对应层级的 mipmap 而不是原贴图,然后再对mipmap上的采样进行线性插值混合。在游戏中的 mipmap 一般长这样:



Trilinear Filtering (三线性插值)

三线插值简单来说是根据 d 来选择最近的两个 mipmap, 分别进行双线性插值,得到两个参考值,然后对这两个参考值再进行一次线性插值,得到最终结果:



三线性插值能对平行与投影平面的成像有较好的效果,它对 d 方向及 uv 空间都同样用线性插值的方式,它相当于假定在线性插值的方向上纹素和像素都是均匀分布的,也就是各向同性,参考下 mipmap 的生成方式,它是 u 和 v 同时等比缩放来产生 mipmap 的,也就是说 mipmap 本身也是各向同性的。可以实际应用中,投影的三角面总是和投影平面有一定的夹角,这就使得像素和纹素在 屏幕坐标的 x、y 影射到 uv 是非均匀变化的,也就是意味着它是各向异性的。当 delta x = 1, delta y = 1 对应的uv变化为 delta u 和 delta v , delta u 与delta v 差异越大时,失真越严重。
Anisotropic Filtering (各向异性过滤 )

各向异性的处理,自然是要把 uv 分别的变化频率考虑进来,变化多的多采样,变化少的少采样,其中的一种实现方式如下:



把像素反向投影到纹理空间,将在纹理空间形成一个不规则四边形,短边意味着屏幕像素会对这个方向超采样(高频率), 可以为来确定 mipmap 层级。长边方向,生成一条贯穿中心的线段,按过滤等级高低,在线段上进行多次采样并合成,得到最终采样结果。



ddx / ddy

上面都说到通过 对应的 mipmap 如何如何,那么到底怎么决定用哪一级的 mipmap 的呢?这就不得不提 ddx ddy
根据 nvidia 的文档,它们可以看成屏幕空间 x / y 方向的偏导数,来衡量两个方向上的变化率。
实际的实现中,GPU 常是以 2 * 2 为一个block 进行光栅化的,在这里的实现就是简单地相邻像素差:


一种常用的 mipmap 的方式,就会利用这个像素差为决定 mipmap level 的 d,如:
#define SUB_TEXTURE_SIZE 512.0
#define SUB_TEXTURE_MIPCOUNT 10

float MipLevel( float2 uv )
{
float2 dx = ddx( uv * SUB_TEXTURE_SIZE );
float2 dy = ddy( uv * SUB_TEXTURE_SIZE );
float d = max( dot( dx, dx ), dot( dy, dy ) );

// Clamp the value to the max mip level counts
const float rangeClamp = pow(2, (SUB_TEXTURE_MIPCOUNT - 1) * 2);
d = clamp(d, 1.0, rangeClamp);

float mipLevel = 0.5 * log2(d);
mipLevel = floor(mipLevel);   

return mipLevel;
}至此,纹理采样为一小结。
参考:

: Nearest-point sampling - UWP applications

: Bilinear texture filtering - UWP applications

: (https://developer.download.nvidia.com/cg/ddx.html)

: (https://developer.download.nvidia.com/cg/ddy.html)

: Texture Filtering (Bilinear vs. Trilinear vs. Anisotropic)

: http://fushigi-hako.site/2019/08/25/Notes-on-ddx-ddy/

: ddx and ddy · Computer Graphics

: 纹理映射 - rickerliang - 博客园
页: [1]
查看完整版本: 【图形基础】纹理采样小结