入门图形学11——几何
11.1 几何的应用
- 我们之前用三角形代替几何图元,这是一种很简单而基础的表示方法,但在真实世界里,很明显我们并不能用这一种简单的图形表示所有的几何形状,因此本节我们将来看一看几何方面诸多的表示方法与应用。
11.1.1 复杂曲面
- 几何方面表示的其中一个典型例子就是复杂的曲面,比如赛车游戏中光滑的汽车模型,就不单是用三角形来进行近似表示了。
11.1.1 复杂曲面的例子——地平线4
11.1.2 毛发渲染
- 毛发渲染同样十分需要几何层面的知识与优化,因为其数量巨大,而渲染与存储它更是业界一直在探寻的难题。
- 通过一些手段我们可以更加顺滑优化地表示它。最典型的毛发渲染就是最终幻想系列。
11.1.2 复杂的毛发渲染例子——最终幻想15
11.1.3 几何体优化
- 对于场景中大量的几何体聚集的情况,我们也需要通过各式各样的方法进行优化,最耳熟能详的应该是LOD技术
- 这个技术与前文提到过的MIPMAP技术结合使用可以得到很好的性能优化与画面权衡表现的作用,经典的例子就是漫威蜘蛛侠中城市场景。
11.1.3 几何体优化的例子——漫威蜘蛛侠1
11.2 几何的表示方法
- 几何的表示分为显式与隐式。
11.2.1 隐式的表示
- 隐式表示实际上是一种判断方法,满足该条件的点就都可以存在。
- 而一般也是以数学公式体现的,比如大家高中学过的点线面的公式,再比如我们会在高数中学到的体的公式,它不告诉我们具体的位置,我们可以将其理解为一种函数。
11.2.2 隐式的表示实例
- 虽然隐式表达非常不直观,但是它可以用来检测一个固定的点在不在这个几何体上。下面我们来看显式的表示方法。
11.2.2 显式的表示
- 第一种就是直接将几何体给出,比如建模得到的一个复杂几何体。
- 第二种就是通过参数映射方式,在一个二维平面中的点我们用对应的函数去映射到三维空间之中,这和展UV的概念类似。
11.2.2 参数映射表示法
11.2.3 CSG表示法
- 当然几何的表示方法远远不止以上说的这些,CSG(constructive Solid Geometry)就是一种表示法,它是通过两个简单几何体进行各式各样的运算来得到新的几何体,比如交并补等等。这种运算也被称为布尔运算。
11.2.3 CSG 的具体案例
11.2.4 距离表示法
- 距离表示法也叫做距离场表示法。这种表示法不去定义具体的几何元素,而是定义任意一点到他的距离,最经典的应用就是融球,在星铁中匹诺康尼场景中就有体现。
11.2.4 距离场融球案例——星铁
- 下面我们来细致理解一下距离函数,或者说叫有向距离场。
- 首先如果我们想混合两个物体在屏幕上呈现的占比,我们如果直接混合的话,会得到并非平滑过度的值。
11.2.4 非平滑过度的直接混合
- 而SDF函数就可以让我们根据两个面之间的距离关系,平滑的混合这些值,你可以理解为原本其并没有如此细分属性,而距离函数让其本身具备了这样多元的属性,如果我们再对其进行操作,就可以过度的很顺滑了。
11.2.4 SDF函数后的混合方式
11.2.5 显示表示方法示例
- 点云,顾名思义,由很多点形成的画面,最具代表性的就是高斯抛雪球方法呈现出来的3维立体建模,感兴趣的同学可以浏览下面网页。
- https://lumalabs.ai/
11.2.5 视频生成的AI三维建模
- 面片,就是引擎与DCC软件中常用到的基本几何形体表示元素。一般多为三角面或四边形,如果在引擎中(Unity举例)打开线框预览模式就能看出整个物体是以一个一个面片组成。
11.2.5.1 Unity中的线框显示
- 我们的EasyShader使用了一种以这样的数据格式作为代表的模型输入格式——TheWavefront Object File(.OBJ)格式。
- 这种格式的特点就是,一切以数值记录,因此存储空间小,我们可以看到有面法线,面纹理坐标,顶点等一系列数据,我们通过读取这些数据,在软件中用我们的算法绘制图形。
11.2.5.2 .obj格式实例
11.2.6 曲线——贝塞尔曲线
- 贝塞尔曲线是一种平滑曲线的表示方式,它在很多软件中都有应用,比如平滑运动,或平滑线条线段等等,我们往往通过它来实现一些自由的曲线表示,下面我们来深入了解贝塞尔曲线的原理。
- 首先,我们定义曲线的方式可以用近似的思想体现。也就是如果我们希望有一条曲线,那么我们就先定义几条直线。
11.2.6 贝塞尔曲线第一步——找到第一个中心点
- 在B0到B2的阶段,我们让其拥有一个过渡值b1,想象一下,我们规定从b0到b1需要经过一个单位时间。而我们可以在其上任意取一个时间t,现在假设是t = 1/3 处。我们在此处可以取一个点,我们将其命名为b01
11.2.6.1 贝塞尔曲线第二步——找到第一个t时间点
- 现在,我们只需要继续这个过程,通过不同的参数t就可以绘制出不同样式的贝塞尔曲线了,这种绘制方法叫做二阶贝塞尔曲线。
11.2.6.2 贝塞尔曲线第3-n步——递归绘制
- 大家可以通过下面这个网站直观地看到演示,还可以跟着互动~
- https://www.longluo.me/projects/bezier/
- 下面我们来看它的数学表达式。本质上得出每一个点的过程就是进行线性插值的过程,每一个点都可以用起始点和终点进行线性插值,而且下一层的点一定可以用上一层进行表示
11.2.6.3 贝塞尔曲线的推导
- 对于B02我们就可以用最底层的B0 B1以及B2表示
11.2.6.4 第二阶贝塞尔曲线表达式
- 你是否能总结出规律?
- 没错实际上它就是一个二项式的形式,这样我们就可以使用多项式展开将其扩展到N维。
11.2.6.5 多项式展开
- 我们进一步总结
11.2.6.6 N阶贝塞尔曲线表示法
- 至此我们就可以表述任意曲线了,对三维同样适用,至此我们解决了开头提到的第一个问题,现在你也可以在软件中造出一台精美绝伦的车辆模型了~
11.2.7 逐段的贝塞尔曲线
- 前面我们了解了单独的贝塞尔曲线,但是我们会发现,如果想去定义更复杂的曲线形状,往往单纯用贝塞尔曲线就不够了,我们需要进一步完善这个方法。
- 各位在使用PS时应该会发现,有一个很常用但是也很难懂的工具——钢笔工具。
- 它本身实际上就是一个多段的贝塞尔曲线,通过这个工具你可以绘制出几乎任意你想要的特定曲线。
11.2.7 Ps中的钢笔工具实例
- 逐段定义的贝塞尔曲线一般具备四个点,分别是起点终点以及两个对应的控制点。通过调控控制点的位置来控制曲线的弯曲程度。
- 逐段链接的贝塞尔曲线也有不同的链接方式,其中在几何上连续的贝塞尔曲线叫做C0连续,也就是第一段贝塞尔曲线的终点与第二段贝塞尔曲线的起点相等。
- 而如果他们的切线方向也相同并且控制点等距,我们就称之为C1连续。
11.2.7.1 贝塞尔曲线连续的方式
11.2.8 样条曲线
- 样条在很多DCC软件中也有广泛使用,比如blender中颜色渐变节点中渐变方式就有样条这一类型。
- 样条本身也是一种曲线,满足一定的连续性,是一种简单可控的曲线。
- B-Splines也叫β样条。叫做基函数样条,我们之前总结出的贝塞尔曲线公式就叫做基函数。β样条也是对贝塞尔曲线的扩展。
- 因为贝塞尔曲线牵一发动全身的特点,因此在设计上不符合直觉,因此诞生出来了β样条,它可以对局部的细节进行调整。在此我们不多赘述,感兴趣的同学:
- 【清华大学-计算机图形学基础(国家级精品课)】 https://www.bilibili.com/video/BV13441127CH/?p=13&share_source=copy_web&vd_source=18d60239a339ad21d3b3f050742622f4
11.2.8 blender中颜色渐变的β样条模式
11.3 曲面
11.3.1 贝塞尔曲面
- 前文我们提到了贝塞尔曲线,它是二维的,在三维中,同样我们也可以利用贝塞尔公式去定义曲面,只不过它是一个数组的形式。
- 我们通过在空间中定义一系列的贝塞尔曲线,来获得所需的控制点。通过这些控制点作为一条贝塞尔曲线的表示方法,来形成一个贝塞尔曲面。
11.3.1 贝塞尔曲面的形成
- 因为贝塞尔曲线的性质,我们可以定义两个参数对应之前我们所提到的UV坐标,通过这两个点的变换,我们就可以得到该曲面在UV坐标上的映射,以此得到UV贴图。
11.3.2 模型细分
- 我们之前了解到,模型是由众多三角面组成。而三角面是由众多顶点组成的,在着色部分我们讲过,增加三角形顶点数量可以拥有更好的着色效果,而增加三角形顶点数量的方法便被成为细分。
- 我们在Blender等DCC软件中,细分是一个基础的操作。它本质上就是让模型顶点变化频率增加,能够更加顺滑的体现出细节。
11.3.2 blender中的模型细分
- 接下来,我们来看它的原理。首先,细分大致可以分为两步操作。
- 首先第一步,创建更多新的三角形,这一步很简单,我们可以取原三角形三边中点,分别连线,形成四个新的三角形。这样的划分方式也叫做Loop细分(发明人姓Loop)。
- 接下来,我们就需要处理新形成的三角形的位置。
- 对于一个新的顶点,往往被几个三角形所共享,我们就可以利用就顶点的位置对新顶点进行加权平均,这样就能获得更加平滑的新三角形。
11.3.2.1 新生成顶点的处理方式
- 下一步,我们还需要来看老顶点如何更新,新顶点有一部分是要听从于老顶点的变化,因此老顶点的更新十分重要。
- 老顶点的更新需要参考两部分,一部分来源于这些老顶点自身,一部分来源于其周围的顶点,同样是进行一定的插值运算,其中有两个参考值,一个是变换目标的’度’,这是图论的概念,实际上很好理解,就是该点与多少个顶点相连接。另一个参考值为u,是根据度来进行的变化,最后通过这两个参考值对图形进行插值。
11.3.2.2 旧顶点的更新方式
- 非三角面细分
- 以上我们提到的Loop细分方法是针对三角面进行细分的,而往往我们在实际操作中遇到的网格体不止有三角面一种。接下来介绍的这种Catmull方法就是针对更普遍的网格细分法。
- 我们先将网格分为四边形与非四边形两种类型。随后我们定义一个概念叫做奇异点(Extraordinary vertex),也就是度不为4的点,度的概念希望你还记得,也就是所连接的边的数量。
- 我们进行细分的方式就是,首先取三角形中点以及面的中点,并将其连接,我们就能得到一系列新的多边形。
11.3.2.3 CatMull细分法
- 经过上述变换后,我们会发现,奇异点数量增加了。我们可以总结出规律,在非四边形面中增加点,增加出的一定是一个奇异点,非四边形面都消失了。
- 但如果我们继续细分之后奇异点数量就不会再继续增加了。
11.3.2.4 CatMull细分法-进一步细分
- CatMull 等细分方法应用的领域非常多,可以提升画面的表现力以及精度,但有些时候,尤其是在游戏里,我们不希望所有的面都是高精度的,这样会很卡,所以就要牵扯到我们的下一个话题,有细分就有简化,接下来我们来看模型面数的简化,比如Lod技术以及UE的独家技术Nannite网格体。
11.4 模型的简化
- 首先,模型的简化与模型的细分相反,是为了减少三角形面数而诞生的优化手段,不同面数的三角形带来的整体质感是不一样的。
11.4 Blender中应用了不同等级的细分修改器后的结果
- 而对于一些距离我们很遥远的物体,其往往不需要呈现出复杂的细节,因此在游戏中,会将其应用为面数比较低的模型。
- 这样的模型先前是需要美术人员手动生成低模,并添加到对应的LOD层级之中。而在Ue中,一种叫做Nannite网格体的技术,可以为我们自动生成这样的低模,今年大火的黑神话就是得益于这样的的技术,让其扫描出的模型能够放心大胆的放入场景之中。
11.4.1 Nannite网格体
- 根据Ue官方文档,我们可以了解到,Nannite在保证帧率流畅的同时,将渲染精度提升了数个数量级。并且支持自动处理细节级别,也就是我们前文提到的LOD。不需要再手动设置这些单个的网格体。
- Ue会根据摄像机远近,自然地调整LOD级别,增加或减少三角形面数。Nannite会将模型拆分为三角形集群,并根据流动只渲染所能见到的细节。
11.4.1 Nannite网格体实例
- 想了解更多内容的各位可以在Ue官方文档中查看
- https://dev.epicgames.com/documentation/zh-cn/unreal-engine/nanite-virtualized-geometry-in-unreal-engine
11.4.2 原理及细节
- 接下来,我们来看一下模型简化的原理,是如何做到不破坏原有三角面的大致结构的前提下,还能减少三角面面数的呢?
- 边坍缩算法
- 边坍缩算法的理解上很简单,我们需要减少三角面数,三角面是由诸多边组成的,我们想要减少三角面数,只需要减少三角边数就好了,也就是将一条边压缩为一个点。
11.4.2 边坍缩算法
- 原理上很好理解,但实现上并不容易,因为我们会发现,如果单纯的压缩,会导致新生成的模型与原本模型差距巨大。导致模型走样。
- 因此我们引入了一个新的方法,叫做二次误差度量,这个意思是我们检测这个新生成的点,到原本的点所在平面的距离的平方和,合适此值最小,何时为最佳生成点。
11.4.2.1 二次误差度量
- 但是面对大量的三角形面,我们需要用更加效率的方式存储并排序,这就涉及到数据结构中的优先队列结构,以及贪心算法了。
- 优先队列
- 优先队列是一种抽象的数据类型,每个元素都有各自的优先级,优先级最高的元素首先得到服务,通常使用堆实现。是找到我们所需元素的合适结构。
- 贪心算法
- 贪心算法叫做算法,实际上是一种策略,一切都为了找到最优解,实际上我们所做的这件事本身就是贪心算法的一种体现。
- 贪心算法可以有以下几步
- 建立数学模型来描述我们的问题
- 将求解问题分出若干子问题,也叫单一原则
- 3.对每一子问题求解,得到局部最优解
- 4.将子问题的局部最优解汇总形成总体最优解。
- 当然实际的应用有很多,各位可以自行了解。
- https://pleasant233.oss-cn-beijing.aliyuncs.com/20241202100132.png
11.5 写在最后
- 至此,我们初入图形学的脚步就告一段落了,回顾我们的历程,一共前前后后写了将近4万多字,大致带领大家与我一起初识了图形学光栅化的入门内容,并且编写了一个大家自己的渲染器。
- 我分享本课的目的是总结自己学习的过程,说实话,很多东西第二遍看才算基本弄懂,我也是现学现卖。作为第一节分享课,我们在实践与理论中进行学习,所有枯燥乏味的知识我都尽量通俗并寻找实际应用中的案例讲解。希望各位阅读时不会有太多不理解的地方吧。(尽力了~)
- 本系列课将持续增添删改内容。下一部分打算做光线追踪方面的入门课,并且做Shader的语法与基础入门。带领大家正式进入应用的领域。
- 最后,再次感谢各位的支持,感谢我校的zy老师,101课程的闫令琪老师等诸多学术大牛的启蒙。祝大家能够在学业中,有所精进,有所收获!我们下一段分享课再见!
参考资料
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 Pleasant233!