月度归档: 2022年2月

MIT 6.837:Texture Mapping 纹理映射

MIT 6.837:Texture Mapping 纹理映射

当遇到表面细节复杂的几何体时,利用几何(三角面)来表示这些细节需要巨大的计算开销,从而导致性能瓶颈。一种折中的办法是在平面的纹理上表示这些表面细节,而将纹理图片映射到几何体表面上,大大简化了几何的复杂程度。

二维纹理映射

透视投影后的插值

应用纹理到三角面上通常在投影完成后(fragment shader)进行,此时三角面不在具有空间中的坐标,而只保存了屏幕空间坐标与 Z 缓冲区(或 1/z 值)。因此,纹理的应用必须依赖屏幕空间坐标进行。如果直接对屏幕空间中的坐标进行线性插值,会得到怪异的结果,如下图所示。

50 1024x411 - MIT 6.837:Texture Mapping 纹理映射

在这个例子中,我们把一个方形的纹理映射到两个三角面上,这两个三角面拼成了一个斜的方形面。这两个三角面上的纹理的扭曲不相同。

一种解决此问题的方法是将大三角面细分为小三角面,以让三角形邻接处的扭曲程度接近,减少这种怪异的明显程度。但此方法的效果并不好,我们有其他办法从根本上解决此问题。

这种问题产生的原因是,在透视投影中,屏幕空间上的线性插值在空间中由于 z 坐标原因并不是线性分布的。这个问题在可见性中的 Z 缓冲区话题第一次被提及,而此次我们希望将插值运用在纹理映射中。

51 - MIT 6.837:Texture Mapping 纹理映射

我们可以通过比较在屏幕空间中的线性插值与世界空间中的线性插值,以得到两个插值之间的关系。假设只考虑 xOz 屏幕,则空间中的一条直线被投影到 z=1 上为 x 坐标的一段区间,屏幕空间中 x 坐标即可表达一个点,而世界空间中需要用一个有序对 (x, z) 来表达一个点。

$$
\begin{align}
& 屏幕空间: p(t)=p_1+t(p_2-p_1)=\frac{x_1}{z_1} + t \left( \frac{x_2}{z_2}-\frac{x_1}{z_1} \right) \\
& 世界空间: \begin{bmatrix} x\\z \end{bmatrix}=\begin{bmatrix} x_1\\z_1 \end{bmatrix} + s \left( \begin{bmatrix} x_2\\z_2 \end{bmatrix}-\begin{bmatrix} x_1\\z_1 \end{bmatrix} \right) \\
& 世界空间中点在屏幕空间的投影: p\left( \begin{bmatrix} x\\z \end{bmatrix} \right) = \frac{x_1+s(x_2-x_1)}{z_1+s(z_2-z_1)}
\end{align}
$$

联立屏幕空间与世界空间投影的插值表达式,可得 s 与 t 的关系如下

$$ s=\frac{tz_1}{z_2+t(z_1-z_2)} $$

如果 Z 缓冲区保存的是 1/z 或 w 值,则上式还可以写成

$$ s=\frac{tw_2}{w_1+t(w_2-w_1)} $$

因此,在插值参数为 t 的像素时,我们可以利用上式转换为空间中的插值参数 s,通过 s 的值来获取纹理中的颜色值,这样就可以得到正确的投影效果。

纹理的获取与使用

纹理的获取渠道很多,可以利用现实物体的照片生成纹理,也可自行绘制。如果使用照片生成纹理,直接将物体的三角面与照片中的一部分映射是有问题的,因照片本身也是一种投影结果,投影中会发生拉伸。因此,可以将照片再次投影到同一角度的几何体上,从而得到正常的纹理效果。

52 - MIT 6.837:Texture Mapping 纹理映射

如果使用自己绘制的纹理,则需要指定每一个三角面的纹理,如面数较多,纹理图片的数量也较多,不易管理。此时,可以将一些纹理拼成一张图片,并在模型中指定顶点与纹理图片坐标的对应关系。

52 1 - MIT 6.837:Texture Mapping 纹理映射
52 2 1024x714 - MIT 6.837:Texture Mapping 纹理映射

一种应用纹理颜色的方法是,将 Phong 模型中的漫反射系数替换为纹理图片中的采样值,从而将纹理与光照效果结合起来。

52 - MIT 6.837:Texture Mapping 纹理映射

特殊的纹理

程序化生成纹理

如果能够得到一个函数 $f(x,y,z)$,其输入为要渲染位置的坐标,而输出为颜色,这样的函数也可以看做是纹理。上面基于图片的纹理可以看做是函数值离散化的函数,而如果规定了连续的函数,则纹理可以做到无限大分辨率。

这种纹理通常与噪声算法结合,用于生成一些具有一定随机性同时有一定规律的纹理,如树干、瓷器、动物毛皮花纹等。

53 - MIT 6.837:Texture Mapping 纹理映射

法线贴图

实际的渲染中,我们经常遇到需要在较简单的几何体上实现表面细节的需求。除了常规的材质颜色外,还有光照阴影等效果,这可以通过修改表面的法线的来完成。

一种方法是 Gouraud 渲染,在计算顶点值时,它先计算顶点周围面的平均法向量,再计算颜色,最后在三角面上对顶点颜色进行插值,得到一种较平滑的颜色效果。

另一种方法是 Phong 法线插值,它的插值对象是法线本身,对于每一个渲染的像素插值其法线,再计算颜色值。

54 - MIT 6.837:Texture Mapping 纹理映射

还可以直接在纹理中指定法线方向,利用图片的 RGB 三个通道来表示世界空间中的三维方向向量坐标,直接用纹理中的向量来代替原本的法向量,这样的方法叫做法线贴图。

另一种与法线贴图类似的方法叫凹凸贴图,它指定顶点需要看起来凹凸的程度,通过在此贴图上计算梯度值来改变法向量,形成看起来凹凸的情况。但这种方法无法正确处理自遮挡产生的阴影,因此凹凸程度不宜设置过大。

55 - MIT 6.837:Texture Mapping 纹理映射

置换贴图

置换贴图是凹凸贴图的改进。为了解决凹凸贴图无法正确处理自遮挡的问题,置换贴图应用时将先移动几何体中点的位置,再将新的顶点坐标作为之后阶段渲染的输入。由于置换贴图需要在世界坐标系修改顶点位置,其与上面的方法的应用阶段不同,一种方法是在几何的曲面细分阶段中完成。

56 - MIT 6.837:Texture Mapping 纹理映射

环境贴图

在渲染包含反射项的物体时,反射光线的起点可看成一个新的相机。因此,我们可以预先根据反射光线得到周围环境的图像,再将其直接应用在物体的反射项中,物体较小时可以假设反射线从同一个点发出。

56 - MIT 6.837:Texture Mapping 纹理映射

光照贴图

全局光照的计算开销很大,而场景中的很多物体是静止的。光照贴图通过预先计算那些静止物体的光照强弱,暂时缓存这一部分的光照结果,只需要处理动态物体的光照并合并结果即可。

纹理的混叠

混叠(Aliasing)指的是采样导致频域上不同周期之间的信号叠在一起,导致采样结果发生畸变。

纹理图片的分辨率是有限的,每次获取颜色时要对纹理图片这一离散信号进行采样,如采样率太低则容易发生混叠现象。

57 1024x487 - MIT 6.837:Texture Mapping 纹理映射

对于纹理来说,一种减少混叠的方法是使用 mipmap。

MIT 6.837:Rasterization 光栅化

MIT 6.837:Rasterization 光栅化

光栅化是渲染管线末端的环节,它将屏幕空间中由顶点定义的几何体转换为像素的颜色,生成最终呈现在用户眼前的画面。

直线的光栅化

直线的光栅化需要将给定端点连成的线段转化为一组屏幕中的像素。此处我们假设直线斜率 $m=\dfrac{\mathrm{d}y}{\mathrm{d}x}, 0 < m < 1$,其他情况均可通过对称等操作转化为此情况。

Naive 算法

29 - MIT 6.837:Rasterization 光栅化

将直线表达为方程 $y=y_1+m(x-x_1), m=\dfrac{y_2-y_1}{x_2-x_1}$,则可以对每个像素中心坐标 x 求出 y(x) 值,设置所有的像素 $(x, [y(x)])$ 即可。

但对于每一个像素横坐标 x 计算 y(x) 的值产生许多计算开销,由于像素 x 坐标之间差值固定为 1,故相邻像素的 y 坐标差值固定为斜率 m。这样,我们可以不必每次都计算直线的函数值,而是将 y 值自增 m 即可。

Bresenham 算法

30 - MIT 6.837:Rasterization 光栅化

由于我们将直线的斜率限制在 0~1 之间,已经设置像素右边的像素只有可能是图中的 E 和 NE 两个。我们通过判断在此像素 x 坐标位置直线是否穿过 E 和 NE 的邻边来判断应该设置哪一个像素。

31 - MIT 6.837:Rasterization 光栅化

直线方程可写为 $y=mx+b$,可将其改写为到直线的有向距离 $D(x,y)=y-mx-b$,此距离为正说明点在直线上方,否则在下方。我们可以通过图中邻边中心点 M 对应的有向距离正负来判断直线是否穿过邻边。

32 - MIT 6.837:Rasterization 光栅化

而在 Bresenham 算法中,我们用误差项 $e=-D(x,y)$ 表示到直线下方(即 y 不变)像素中心点的距离,如此距离大于 0.5,说明应设置上方像素,否则设置下方像素。每判断完一个像素后,x 增加 1,e 增加 m,而 y 通过此时像素设置情况觉得不增加或增加 1。

圆的光栅化

33 - MIT 6.837:Rasterization 光栅化

假设圆在第一象限的部分,且按顺时针进行光栅化,可以使用和直线相同的算法。

定义有向距离函数 $D(x,y) = x^2+y^2-R^2$ 和误差项 $e=-D(x,y)$。对于每一个 x 坐标,计算 y-1 坐标的误差项 e,如不小于 0.5 则应保持 y 不变,否则 y 变为 y-1 且 e 自增 1。当 x 增加 1 时,根据误差项式 e 应增加 2x+1。

上面提到的利用 $D(x,y)$ 和 $e=-D(x,y)$ 函数,通过 $e$ 的导数来更新值的方法被称为 DDA(离散微分分析器,Discrete Differential Analyzer) 方法。

三角形的光栅化

Naive 算法

34 - MIT 6.837:Rasterization 光栅化

对每一个像素中心计算三角形的三条直线,如在直线内侧则设置。可以用包围盒(如 AABB)来减小枚举像素的范围。

基于直线光栅化的改进

35 - MIT 6.837:Rasterization 光栅化

先对三条边光栅化,再设置每一对点之间的所有像素。

显示设备的方法

三角面通常较小,而如果先进行直线光栅化反而可能有更大的计算开销,因此不如回退到 AABB 优化的 Naive 方法,伪代码如下

For every triangle
  ComputeProjection
  Compute bbox, clip bbox to screen limits
  For all pixels in bbox
    Compute line equations
    If all line equations>0 //pixel [x,y] in triangle	
      Framebuffer[x,y]=triangleColor

而此算法可以进一步优化,在之前我们提到过直线方程的计算开销较大,可以考虑预处理包围盒边界的方程值,在扫描时根据导数来更新方程值,这样每次计算像素时只需要做加法运算即可,伪代码如下

For every triangle
  ComputeProjection
  Compute bbox, clip bbox to screen limits
  Setup line eq 
    compute aidx, bidy for the 3 lines
    Initialize line eq, values for bbox corner
      Li=aix0+biy+ci
  For all scanline y in bbox
    For 3 lines, update Li
    For all x in bbox
      Increment line equations: Li+=adx
    If all Li>0 //pixel [x,y] in triangle	
      Framebuffer[x,y]=triangleColor

Gouraud 着色

36 - MIT 6.837:Rasterization 光栅化

得到了三个顶点的颜色值后,我们可以使用 Gouraud 着色得到更平滑的颜色。这种方法是在三角面中对顶点的颜色进行线性插值。对于某一通道的颜色,记 $R=a_Rx+b_Ry+c_R$,则根据三顶点的颜色可解出三系数,实现与直线方程类似的插值即可。

另一方法是利用顶点的重心坐标进行插值。

MIT 6.837:Graphics Pipeline 渲染管线

MIT 6.837:Graphics Pipeline 渲染管线

光线追踪是常用的技术,然而我们的计算机硬件并不总是通过光线追踪输出渲染结果,也有基于扫描转换的方法。渲染管线便是一种基于扫描转换的渲染方法的实现,它的一部分通过显示卡专用电路实现,一部分可以由开发者进行编程实现。

光线追踪与扫描转换的比较

光线追踪扫描转换
可渲染的几何体所有便于和光线求交的几何体只有多边形
并行化每个像素单独考虑,可以并行扫描时几何体的遮挡关系需要考虑,无法并行
效率低,通常不适用于实时应用能够满足实时应用需求
硬件实现难以实现便于实现,已有成熟技术
渲染效果高,法线与颜色平滑较低,无法处理阴影、反射、透射等情况
流程对于每个像素追踪一条光线,根据光线与物体的交点计算颜色对于每个几何体在 buffer 中绘制像素

渲染管线的结构

1024x641 - MIT 6.837:Graphics Pipeline 渲染管线

渲染管线是如上图所示的由多个处理阶段构成的系统。整个管线的输入是场景中几何体、光照、观察点、成像平面等信息,每一级的输出为下一级的输入,最终输出渲染结果,即在指定成像平面上得到的平面图像。

下面分阶段介绍渲染管线的各个级。

模型变换(Modeling Transformation)

1 - MIT 6.837:Graphics Pipeline 渲染管线

模型的顶点被定义在自身的坐标系中,而将模型放置在场景中时,顶点的位置会发生改变。这一步完成模型坐标系到场景(世界)坐标系的转换。

着色(Shading)

2 - MIT 6.837:Graphics Pipeline 渲染管线

根据指定的光照模型(如 Phong 模型等)与法线、材质、光源等信息计算顶点的颜色。

视口变换(Viewing Transformation)

3 - MIT 6.837:Graphics Pipeline 渲染管线

相机被放置在场景中的某个位置,要便于渲染,需要将场景中的物体转换到观察者的坐标系中。这一步完成从场景坐标系到观察者视角的标准坐标系(即视口坐标系,由相机位置、观察方向和向上方向定义)的转换。

裁剪(Clipping)

4 - MIT 6.837:Graphics Pipeline 渲染管线

当使用透视投影方法时,观察范围是从相机位置出发的一个锥形。我们把锥形中取得的近平面与远平面之间的空间取出,使用标准化变换将此不规则体变为标准设备坐标系,并舍弃此范围之外的几何体,以减少扫描转换开销。

投影(Projection)

5 - MIT 6.837:Graphics Pipeline 渲染管线

将标准设备坐标系中的物体投影至成像平面上,即三维空间到二维空间的转换。

光栅化(扫描转换,Scan Conversion 或 Rasterization)

6 - MIT 6.837:Graphics Pipeline 渲染管线

将以顶点定义的三角形转换为屏幕空间中的像素,并在扫描过程中对颜色、深度信息等插值,以便得到每个像素的颜色值。

可见性(Visibility)

每个像素只显示距离观察者最近(即深度最浅)三角形的颜色信息。

坐标系

这里介绍在渲染管线各个阶段涉及到的坐标系。

模型坐标系(Object Space)

7 - MIT 6.837:Graphics Pipeline 渲染管线

建模时定义的坐标系,三维美术在此坐标系中定义模型的各个顶点与三角面信息。

世界坐标系(World Space)

8 - MIT 6.837:Graphics Pipeline 渲染管线

需要渲染的场景所在的坐标系,此坐标系中定义模型的位置,通过模型变换将模型的顶点转换为此坐标系中的坐标。

相机坐标系(Eye Space 或 Camera Space)

9 - MIT 6.837:Graphics Pipeline 渲染管线

根据相机位置定义的坐标系,原点即相机位置,两轴由观察方向、向上方向定义,另一轴通过右手定则得到。场景中的物体顶点通过视口变换转换到此坐标系中,以便后续裁剪、转换工作。

标准设备坐标系(Normalized Device Coordinates 或 Clip Space)

10 - MIT 6.837:Graphics Pipeline 渲染管线

将相机坐标系中远平面、近平面之间的物体映射到坐标范围为 $[-1, -1, -1] \sim [1, 1, 1]$ 的正方体的坐标系。映射后超过此坐标范围的三角面将被删除,以减少光栅化的计算。

屏幕坐标系(Screen Space)

11 - MIT 6.837:Graphics Pipeline 渲染管线

二维坐标系,反应各个物体投影至屏幕空间时的坐标,但第三维度(深度,z 坐标)也被记录下,用于可见性判断。

坐标系与渲染管线各阶段的关系

12 - MIT 6.837:Graphics Pipeline 渲染管线