现代计算机图形学

着色(Shading)

Blinn-Phong Reflection Model

漫反射项(Diffuse Reflect)

数学公式

​ 其中 是笼统的漫反射系数, 为能量, 光源至反射点的距离, 分别为 漫反射点所在平面法向量 以及 漫反射点指向光源的向量。

高光项(Specular Term)

产生原因

​ 当物体绝对光滑时,光线打在在其表面呈现镜面反射。

​ 因此相对光滑的物体,光线打到其表面,反射光分布在镜面反射方向周围,此时观察方向与镜面反射方向接近,就可以看见高光,因此 高光项(Specular Term) 取决于我们的 观察方向(view direction),这是根据 经验 所得的结果。

image-20231008184202784

Blinn-Phong模型

​ Blinn-Phong模型很巧妙的修改了上述判断方式:

​ V 向量是否与镜面反射方向接近 <==> V 与 l 的 半程向量 是否与原法向相近

image-20231008200339678

数学公式

​ 其中 是笼统的镜面反射系数, 为能量, 光源至反射点的距离, 分别为 反射点所在平面法向量 以及 漫反射点指向光源的向量。

​ 而 是调整高光的范围的变量。如果没有参数 ,高光分布的区域会过于的分散,因此需要参数 控制高光范围。

image-20231008205353400

随着 p 增大,高光减弱速度也增大

效果展示

​ 根据Blinn-Phong模型创建测试项目,调整 参数并观察球体的变化,可以得到如下结果。

image-20231008210544162

Blinn-Phong 模型的高光效果展示

环境光项(Ambient Term)

产生原因

​ 光线在场景中经过复杂的反射,最终从四面八方发射并打在物体表面,因此称其 环境光。但是要真的添加环境光,需要十分庞大的计算量,这不利于游戏的实时渲染。

​ 而 Blinn-Phong模型 对环境光进行了简化:它将环境光视为恒定的,帮助我们给 物体未能被光线直射而产生的阴影部分 添加恒定的颜色,弥补我们忽略的环境反射光。

image-20231009145408084

数学公式

​ 在这里,Blinn-Phong模型对环境光的计算做一个大胆的猜想:

​ 其中 是环境光反射系数, 为环境光的能量系数。

​ 如果需要精确计算环境光,需要用到 全局光照 的知识,这些后续再谈。

Blinn-Phong模型整体效果

​ 将上述几项光相加,即可得到物体最后的渲染效果。

image-20231009145655891

​ 整合所有的数学公式:

着色频率(Shading Frequencies)

着色频率对Shading的影响

​ 开篇先提一个问题,是什么造成了下面三幅图的着色效果的不同?

image-20231009152634174

​ 观察这三个物体的边缘,可以发现这三个物体是相同的,只是因为 着色频率(Shading Frequencies)的不同导致他们的渲染精度发生了变化,下面我们来分别介绍三种着色频率。

Flat Shading

​ Flat Shading 是以每个三角形面的法向量为基准,渲染整个三角形面的方法。

​ 该方法在三角形面较少的的时,会导致渲染精度较差。

image-20231009153952156

Gourand Shading

​ Gourand Shading 是以每个三角形顶点的法向量为基准,先渲染每个顶点后,再用插值的方式渲染整个物体。

​ 该方法相比于 Flat Shading 拥有较好的渲染精度,但是需要考虑如何计算每个顶点的法向量。

image-20231009154742893

计算顶点法向量的方式

​ 当三角形构成的物体表示的是一个圆,那么我们可以根据圆的切面还原出顶点的法向量,这是最理想的情况。

​ 但实际情况是,三角形构成的物体表示的可能是一个复杂图形,此时我们可以通过 平均顶点周围面的法向量,近似得出顶点的法向量,该方法得到的向量还是比较准确的。

image-20231009163132129

Phong Shading

​ Phong Shading 是以每个三角形面的法向量为基准,先用插值的方法得到每个像素的法向量,最后逐一渲染每个像素。该方法有着最好的渲染精度,但是开销过大,且随着三角形面数增多,其优势也逐渐变小。

​ 需要注意的是,Phong Shading 不等于 Blinn-Phong模型,前者是渲染频率相关方法,后者是一个着色模型。

image-20231009155946904

插值法获得像素法向量

插值法 是通过已知的、离散的数据点,在范围内推求新数据点的方法。因此我们可以使用插值法,求得两个已知法向量的顶点之间,所有像素的法向量。

image-20231009164646768

使用插值法求每个像素的法向量

​ 不要忘记,在求得每个像素的法向量后,需要对其进行 标准化(normalize),也就是将其化为 单位向量。

简单纹理映射

前言

前面我们学习 Blinn-Phong 模型时,都提到了一个关键参数 —— 反射系数。该参数在一定程度上影响着物体反射光线的能力,我们因此能够看见五彩缤纷的世界。

但是在现代计算机图形学中,这个参数要如何进行记录呢?如果我们将该参数记录到3D模型上,当然可以正确的进行光线反射的模拟。但是,当我们想要更换模型的材质时,将会非常的麻烦:因为反射系数记录在模型上,这意味着你需要额外做一个只有材质不同的模型,这非常的麻烦。因此就有了 纹理 这一产物。

纹理简介

纹理 通常是一个 $11$ 的矩形图片,其中记录着一个物体表面的各种参数,包括他的材质,颜色,以及最重要的 *反射系数,等。3D模型上的每一个顶点,都对应其纹理矩形上的一个点,改点参数会在渲染时映射到模型的顶点上。只要有了对应的纹理,物体就可以正确的依照纹理渲染到我们的屏幕空间中。

有了纹理后,当我们需要为一个3D模型更换表面的材质,只需要更换其对应纹理即可。在游戏开发中,这样的开发模式很好的减少了美术老师的工作量。

三角形的重心坐标

使用原因

通过介绍,你应该已经知道纹理“是什么”了,接下来就应该讲“怎么做”—— 如何将纹理映射到3D模型的顶点。

但是在真正讲纹理映射前,我们来讲讲最基础的知识点 —— 三角形的重心坐标

通过简介我们应该已经知道,纹理是一张 相对连续 的图片,而3D模型上的顶点对应纹理上的一个 离散的点。但是模型顶点不是连续的,我们应该如何填充其三角形中的空白呢?

首先想到的方法就是 插值。可是三角形并不像 正方形 或是 圆形 一样,能够方便的做两点之间的线性插值,那应该怎么办?这时就可以用到 三角形的重心坐标

定义

三角形的重心坐标规定,三角形中的任意一点,可以通过三角形的三个顶点 A、B、C 表示,其表示方式为:

其中 ,并且 均不小于0(如果小于零则表示为三角形之外的点)。

像(, , )这样的三角形坐标系,就称之为三角形的 重心坐标

image-20231015152139390

使用重心坐标在三角形内部进行插值

使用重心坐标进行纹理映射

根据三角形重心坐标的定义,我们可以发现只要知道了三角形顶点坐标,就可以表示三角形中的任意一点。这不就是我们需要的吗?

将屏幕空间上像素的中点(, ),转换成其对应在3D模型中的点并以(, , )的形式表示,接着找到纹理中对应三角形的(, , )点,获取其参数并映射回3D模型,经过 Blinn-Phong模型着色(Shading)后,绘制到屏幕空间上。

image-20231015153719365

简单纹理映射:漫反射颜色

由于三角形的重心坐标会在进行变换(Transform)之后改变,因此不能使用屏幕空间中的重心坐标进行纹理映射。需要将屏幕空间中的坐标变换为原模型中的3D坐标,再进行纹理映射。前面如此复杂的纹理映射操作就是这么来的。

纹理过小/纹理放大

纹理放大产生的问题

当3D模型对应的 纹理过小 时,为了能将纹理完全覆盖对应模型,我们就需要将纹理放大。这是图形学中普遍存在的问题—— 纹理分辨率不足 问题。

但是 纹理放大 会出现很多问题,例如同一个纹理采样点,可能会覆盖多个像素采样点。

image-20231015154725600

纹理采样点覆盖多个屏幕像素采样点

应对方法

此时我们有多种方法应对,例如:Nearest(直接采用当前纹理样本点的信息)、Bilinear(双线性插值最近四个纹理样本点的信息)、或是Bicubic(双三次插值算法)

image-20231015160121512

三种不同方式的对应效果

可以很明显的看到,使用Nearest方法产生了比较严重的锯齿现象,可见这种方法虽然操作简单但效果不佳。因此这里我们详细讲述 Bilinear 算法

Bilinear(双线性插值算法)

简单介绍

既然直接使用当前的纹理采样点作为参数(Nearest)行不通,那我们就换一种方法。相信如果你看到这里,应该可以很快的想到一个我们经常会用到的方法 —— 插值

对啊,插值不就好了!既然直接用现有的参数行不通,那我们就使用采样点附近的纹理样本点的参数作线性插值,应该可以得到一个相对准确的数据,Bilinear算法也是这么来的。

Bilinear算法选择离采样点 最近的四个纹理样本点 进行 线性插值,采样点会根据插值后的参数进行着色(Shading)

image-20231015162129811

实际操作

如何 选取插值需要的纹理样本点 在上面已经提到过了,接下来要解决的是如何进行插值。

一维坐标系 上进行插值,只需要一步即可:

二维坐标系 上进行插值:

  • 首先需要进行 两次一维线性插值 得到两个辅助点:
  • 接着根据两个辅助点做线性插值:

image-20231015165611949

Bilinear 计算方法

如此反复,3D物体上的所有采样点都可以通过插值获得相对准确的纹理渲染信息,有效的避免了锯齿的产生。查看Bilinear效果图可以发现,对比Nearest方法,它对低分辨率纹理的着色效果还是不错的。

纹理过大

纹理过大产生的问题

前面说到 纹理过小 会产生 纹理分辨率不足 的问题,那么 纹理过大 会不会也产生一系列的问题呢?答案是肯定的。

image-20231016075107207

纹理过大产生的问题

仔细看上图会发现,当我们对一个纹理非常大的物体进行 点采样(Point sampled),物体离摄像机较远的位置产生了摩尔纹,离摄像机较近的位置产生了严重的锯齿现象。

那么为什么会出现这种情况,按照我们之前对纹理过小产生问题的分析结果,纹理足够大,分辨率足够高,不应该是图像更清晰吗?

image-20231016080214188

不同像素所覆盖的纹理样本点

在上图中,带阴影的黑色框代表着一个像素所能覆盖的纹理范围,如果我们取 蓝色的像素中心点 所对应的 纹理样本点 进行着色,所得到的图像显然不对:远处和近处都出现了 采样率不足 的情况。因此我们会看见近处出现 锯齿,而远处出现 摩尔纹,这与我们之前 采样率不足导致走样 的说法相同。

使用超采样(MSAA)处理

我们可以采用之前反走样的方式 —— 超采样(MSAA)方法对图像进行处理,可以看到效果还是不错的。

image-20231016111958219

原图像与MSAA图像对比

可是在计算机中这样的作法实在是太繁琐了:我们需要在像素内部进行额外的512次采样,经过平均计算之后才能得到结果。这在实际运用中是行不通的,我们需要一个更加快速的解。其实仔细分析一下问题产生的原因,从根源出发,我们或许能够的到问题的解决办法。

在一个像素需要进行渲染的时候,都需要对该点对应的纹理进行查询,查询的返回值应该是像素所 覆盖的纹理样本点的均值,此时如果我们直接返回像素中点对应的纹理参数,就会出现走样问题。

讲到这里你应该知道了,这就是一个 范围查询 的算法问题。如何快速的对一个区域内进行范围查询,是截至目前仍然在研究的问题。

想要快速的进行范围查询,我们可以预先将已经查询好的数据保存起来,等真正进行范围查询时,返回对应的值即可。这就是我们后面要讲到的 Mipmap

Mipmap

简介

Mipmap 最大的作用就是允许 快速的近似的正方形的 范围查询(注意着三个特点)。

我们可以先来看一下他在计算机中树如何存储的。

image-20231016114944066

Mipmap在计算机中存储的内容

image-20231016115539840

Mipmap的层级结构

可以发现他在分辨率上是逐层递减的,且前一层所占空间是后一层的四倍,它就像一个金字塔,层数越高,横截面积越小。根据等比数列的求和公式算出,Mipmap所占的存储空间仅为原图的 .

Mipmap的使用方法

介绍完Mipmap,我们回到着色本身,到底应该如何判断像素处于Mipmap的哪一个层级,并获取正确的纹理信息呢?

我们可以使用屏幕空间中 相邻采样点的纹理坐标 估计 纹理的被覆盖面积 ,然后计算 Mipmap 层级

image-20231016143450842

Mipmap层级计算方法以及面积计算

可以发现,图中一块像素的覆盖范围 并不是一个正方形,因此使用Mipmap作为该点的纹理纹理信息还是不够准确,只能是对当前实际纹理信息的近似。

如果我们对求得的 Mipmap层级参数 采用取最近整数作为近似手段,得到的效果为下图所示:

image-20231016143616273

取离采样点最近的Mipmap层级作为纹理信息进行渲染

渲染效果并不是很理想,不同深度区域之间的分割线十分的明显。

由于参数)可能并不是一个整数,如果直接取最接近的Mipmap层级作为其对应像素的纹理信息,着色后的效果必然会出现错误。那么如何使得不同深度的像素有着一个平滑的过渡?

此时还是得请出我们的老朋友 —— 插值。使用插值法,对与最近的两个层级进行插值,得到的结果才最接近采样点真正的纹理信息。

其具体步骤是:首先获取采样点在最近的两个 Mipmap层级 上进行 Bilinear双线性插值 后的结果,接着对两个结果进行线性插值运算,所得结果就是当前采样点的纹理信息。

image-20231016145428392

使用插值法计算采样点纹理信息

最终效果

可以看到使用上述插值法,计算落在 Mipmap层级 之间的采样点对应的纹理信息,

image-20231016150250007

使用双层Mipmap插值法后的渲染效果

存在的缺陷

首先来看一下,使用 Mipmap 后能否改善我们最初因为 纹理过大 而产生的问题。

image-20231016151225490

对过大纹理使用 Mipmap 后的效果

好像近处物体的锯齿现象改善了,但是远处的物体虽然没有了摩尔纹,但是颜色又糊成了一团,这是为什么?

这就是Mipmap本身的缺陷。Mipmap 只有在近似正方形的范围查询下表现良好,如果出现了长条状的范围查询,其也会发生 走样 的现象,表现在图片上就是色彩糊作一团。

image-20231016152110929

屏幕空间中的像素点对应到纹理表面,可能为一个长条

要解决这个问题,就需要使用 各项异性过滤(Anisotropic Filtering)来对 Mipmap 进行优化。

各项异性过滤(Anisotropic Filtering)

image-20231016154115228

各项异性过滤——Ripmap

使用各项异性过滤后,对于在水平或竖直方向上的长条状范围查询,我们可以查询 Ripmap 中被压缩的图像部分,可以在一定程度上缓解 Mipmap 对远处物体进行着色时产生的走样问题。

使用各项异性后,图像的着色效果 显著提高,远处物体的细节部分恢复:

image-20231016155204296

采用各向异性后的效果

当然相对于Mipmap,各向异性Ripmap的 缺点 也是显著的:

  • Mipmap所占空间仅为原图的,而Ripmap所占空间为原图的倍,空间开销增大
  • Ripmap对斜方向上的长条状查询依旧没有办法,因此只是 部分优化 Mipmap