GAMES-图形学系列笔记-全-
GAMES 图形学系列笔记(全)


🎮 课程01:游戏引擎导论 | GAMES104-现代游戏引擎:从入门到实践

概述
在本节课中,我们将要学*游戏引擎的基本概念、发展历史及其在现代计算机科学中的核心地位。我们将探讨游戏引擎为何被称为“皇冠上的钻石”,并了解它如何作为构建虚拟世界的底层技术框架。

什么是游戏引擎?
游戏引擎是一个软件框架,专门为游戏开发设计,包含一系列工具和功能。它不仅仅是代码的集合,更是构建虚拟世界的技术底层框架,是创意和想象力的生产力工具,也是一个处理复杂系统的艺术。


游戏引擎的核心定义
- 构建黑客帝国的技术底层框架:游戏引擎致力于模拟一个虚拟世界,其终极目标是创造一个让人难以区分虚拟与现实的体验。
- 生产力的工具:它允许开发者将想象中的世界,在虚拟空间中具象化地创造出来。
- 复杂性的艺术:游戏引擎是一个极其复杂的系统,是各种技术决策和妥协的结果,体现了系统工程之美。

游戏引擎的发展历史
上一节我们介绍了游戏引擎的基本定义,本节中我们来看看它是如何从无到有发展至今的。
游戏行业的历史相对较短,大约只有50多年。早期并没有“游戏引擎”的概念,游戏开发的核心挑战在于如何在极其有限的资源(如40KB存储空间)内实现游戏功能。
里程碑事件
以下是游戏引擎发展过程中的几个关键节点:
- 起源:约翰·卡马克(John Carmack)在开发《德军总部3D》和《毁灭战士》时,首次意识到许多代码可以复用,并将其抽离出来,形成了最早的“游戏引擎”概念。
- 质的飞跃:随着显卡的出现,图形运算从CPU中分离出来。《雷神之锤》引擎抓住了这个机遇,开创了3D游戏时代,并系统性地研究了网络对战同步问题。
- 现代引擎生态:经过*30年的发展,形成了丰富的游戏引擎生态,包括商业引擎(如虚幻、Unity)、大厂自研引擎(如寒霜、Anvil)以及一些免费的开源引擎。
为什么游戏引擎如此重要?
了解了游戏引擎的历史后,我们来看看它为何在现代技术领域中占据核心地位。
游戏引擎不仅仅是用于开发游戏,其技术已经渗透到众多前沿领域,成为构建未来数字世界的基石。
游戏引擎的广泛应用
以下是游戏引擎技术的一些关键应用领域:
- 虚拟人与数字人:游戏引擎中关于皮肤、毛发、动作模拟的技术,是构建未来虚拟助手、虚拟偶像的基础。
- 影视工业:游戏引擎被用于虚拟制片,允许导演在现场实时调整光照和布景,改变了传统的影视制作流程。
- 军事模拟:各国军队利用游戏引擎技术进行高真实度的战术训练和战法演练。
- 数字孪生与工业4.0:在无人驾驶测试、智慧城市管理、工业数字化等领域,游戏引擎用于构建现实世界的虚拟映射。
- 下一代人机交互:车载系统、各种设备的3D交互界面背后,也大量使用了游戏引擎技术。
游戏引擎设计的核心挑战
游戏引擎的应用如此广泛,但其设计本身面临着巨大的挑战。本节我们将探讨这些核心难点。
游戏引擎需要在严格的约束条件下,实时地模拟一个逼真的世界。这并非易事。
主要约束与挑战
- 实时性(Real-time):这是游戏引擎设计的黄金法则。无论算法多么精妙,效果多么华丽,都必须在极短的时间片内完成计算(例如33ms或更短)。公式:
帧时间预算 = 1 / 目标帧率。 - 资源限制:算力(CPU/GPU)、内存、存储和网络带宽都是有限的,无法像“上帝”一样采用暴力算法。
- 生产力工具属性:引擎的最大用户是艺术家和设计师,而非程序员。因此,必须提供强大、易用且可协作的工具链,让非技术人员也能构建世界。
- 系统复杂性:现代游戏引擎动辄拥有数百万至上千万行代码,算法密度高,模块众多。同时,引擎需要持续升级,但必须保证旧有内容和逻辑的兼容性,即“在飞行中更换引擎”。
如何学*游戏引擎?
面对如此复杂的系统,初学者可能会感到畏惧。本节我们将介绍本课程的学*策略和方法。
本课程的目标不是将大家培养成某个技术点的专家,而是帮助大家建立现代游戏引擎的知识体系框架。
课程策略:沿主干道行进
我们将游戏引擎比作一座云雾缭绕的山峰。本课程将选择一条主干道,带领大家直达山顶,建立完整的知识图谱。对于分支路径上的细节,我们会点到为止,让大家知道其存在和位置,便于未来深入探索。
课程内容总览
以下是本课程20节课的核心内容脉络:
- 基础架构:理解游戏引擎的分层设计,找到阅读引擎代码的入口(如
update函数)。 - 渲染系统:重点讲解如何将各种图形算法(光照、材质、网格等)组织到实时渲染管线中(如前向渲染、延迟渲染),而非深入单一算法。
- 动画系统:探讨如何将美术制作的动画素材,组织成可交互、可混合的动画状态机,实现丰富的角色动作。
- 物理系统:介绍如何用简单的刚体形状表达世界,并进行动力学模拟,让世界真正“互动”起来。
- 游戏性系统:讲解如何让设计师通过事件、脚本或可视化编程来定义游戏规则,这是游戏好玩的关键。
- 工具链与数据驱动:深入引擎作为生产力工具的核心,讲解如何构建编辑器、以及反射系统等支持工具协作和数据兼容的底层机制。
- 网络系统:揭开网络游戏同步的神秘面纱,解释多个“*行宇宙”如何通过信息交换保持一致性。
- 前沿技术:介绍动作匹配、程序化内容生成、面向数据编程、多核任务系统以及虚幻5的Lumen、Nanite等前沿概念。
课程资源与安排
为了帮助大家更好地学*,我们准备了丰富的配套资源。
学*资源
- 课程官网与社区:提供课件、视频回放,并设有论坛供大家提问和交流。
- 参考书籍:推荐阅读《游戏引擎架构》(Game Engine Architecture),可作为课程的补充读物。
- 迷你引擎实践:我们开发了一个用C语言编写的小型引擎,麻雀虽小,五脏俱全。大家可以通过修改它(约一两百行代码)来完成每节课的实践挑战,最终目标是协力完成一个简单的联网对战游戏。
课程理念
本课程是一门通识课,旨在“讲人话”。无论你是程序员、美术还是设计师,都可以听懂并建立对游戏引擎的整体认知。实践部分是可选的,但对于有志于深入开发的同学将大有裨益。
总结
本节课中,我们一起学*了游戏引擎的基本概念、发展历程及其作为构建虚拟世界核心技术的巨大价值。我们了解到,游戏引擎是一个在严苛的实时性约束下,融合了图形、物理、动画、网络等多领域技术的复杂生产力工具。它不仅是游戏产业的基石,更在影视、模拟、数字孪生等前沿领域发挥着关键作用。
通过本课程,希望大家能建立起对现代游戏引擎的体系化认知,掌握分析复杂系统的方法论,并激发起共同探索和构建未来数字世界的热情。未来的交互将是3D的、身临其境的,而游戏引擎正是开启这个时代最重要的基础软件之一。

让我们开始这段充满创造乐趣的伟大旅程吧!


01.计算成像导论 | GAMES204-计算成像 - P1 - GAMES-Webinar - BV11N4y1g7Z8
概述
在本节课中,我们将介绍计算成像的基本概念、发展历史、应用领域以及计算方法。通过学*本节课,你将了解计算成像的基本原理,并对其应用领域有所了解。
计算成像的定义

计算成像是指利用光学系统、传感器和计算算法,从真实世界中捕获信息,并通过计算或算法进行解码,最终恢复出物理世界中多维度的信息。
公式:




其中,\(B\) 是传感器上获取的信号,\(A\) 是成像过程中的光学调制,\(X\) 是物理空间中的信号,\(\text{noise}\) 是噪声。



计算成像的应用



计算成像的应用领域非常广泛,以下列举一些典型的应用:

- 手机拍照:通过优化光学系统、传感器和算法,提高手机拍照的画质和功能,例如夜景模式、HDR、人像模式等。
- 医学成像:例如CT、PET、MRI等,用于疾病诊断和治疗。
- 光场成像:记录光线的角度信息,实现3D成像和视频。
- 太赫兹成像:用于安检、遥感等领域。
- 水下成像:用于水下探测和观察。
- 红外成像:用于夜视、遥感等领域。
计算成像的方法
计算成像的方法主要包括以下几种:
- 图像处理:例如直方图均衡化、滤波、边缘检测等。
- 深度学*:例如卷积神经网络、生成对抗网络等。
- 优化算法:例如梯度下降法、牛顿法等。
- 物理模型:例如光学模型、信号处理模型等。
课程内容
本课程将涵盖以下内容:

- 成像历史和现状
- 计算成像的定义和应用
- 计算成像的方法
- 人眼视觉系统
- 相机和传感器
- 图像处理
- 计算光学
- 深度学*
- 优化算法
- 前沿课题
总结

本节课介绍了计算成像的基本概念、发展历史、应用领域以及计算方法。通过学*本节课,你将了解计算成像的基本原理,并对其应用领域有所了解。

GAMES104-现代游戏引擎:从入门到实践 - P1:0 - GAMES-Webinar - BV12Z4y1B7th
引擎架构分层
概述
在本节课中,我们将学*现代游戏引擎的分层架构,了解其核心组成部分以及各层之间的关系。
工具层
概念:游戏引擎的最上层,提供各种编辑器和工具,用于关卡设计、角色制作、动画制作等。
示例:关卡编辑器、角色编辑器、动画编辑器。
功能层
概念:负责实现游戏的核心功能,如渲染、物理、AI、脚本等。
核心概念:
- 渲染:将三维场景转换为二维图像。
- 物理:模拟现实世界的物理规律,如碰撞、重力等。
- AI:模拟智能行为,如敌人行为、NPC行为等。
- 脚本:定义游戏逻辑和行为。
资源层
概念:负责管理和加载游戏资源,如模型、贴图、音频等。
核心概念:
- 资源:游戏中的各种数据文件,如模型文件、贴图文件、音频文件等。
- 资产管理器:管理资源的加载、卸载和生命周期。
核心层
概念:提供底层服务,如内存管理、数学运算、容器管理等。
核心概念:
- 内存管理:高效地分配和回收内存资源。
- 数学运算:提供各种数学运算功能,如线性代数、坐标变换等。
- 容器管理:提供各种数据结构,如数组、链表、树等。
*台层
概念:负责处理不同*台之间的差异,如操作系统、硬件*台、图形API等。
核心概念:
- RHI(渲染硬件接口):封装不同图形API,提供统一的接口。
- *台无关性:使游戏引擎能够在不同*台上运行。
工具层
概念:提供各种编辑器和工具,用于游戏开发、测试和发布。
核心概念:
- 编辑器:如关卡编辑器、角色编辑器、材质编辑器等。
- DCC(数字内容创建):如3D建模软件、动画软件等。
总结
现代游戏引擎采用分层架构,将复杂的系统分解为多个模块,每个模块负责特定的功能。这种架构使得游戏引擎易于开发、维护和扩展。


本节课中,我们学*了游戏引擎的五层架构,包括工具层、功能层、资源层、核心层、*台层和工具层。通过了解各层之间的关系和功能,我们可以更好地理解游戏引擎的工作原理。


02.色彩和人的视觉系统(I) | GAMES204-计算成像 - P1 - GAMES-Webinar - BV1ff4y1d7kq
概述

在本节课中,我们将学*色彩和人的视觉系统的基础知识,包括色彩的形成、色彩匹配以及人眼如何感知颜色。


色彩的形成

色彩的来源


色彩来源于光源、观察物体和观察者三个要素。光源发出的光照射到物体上,物体反射或吸收部分光,反射的光进入人眼,经过视觉系统处理后形成颜色感知。
色彩与光谱

色彩与光谱的关系并非一一对应,存在一些特殊的波长,即使亮度变化,颜色感知也不会改变。这种现象称为贝塞尔普克效应。

色彩与能量

物体在特定温度下会发出特定波长的光,温度越高,波长越短,颜色越偏向蓝色。这种现象称为黑体辐射。

人的视觉系统

视网膜
视网膜上有三种感光细胞:视杆细胞、视锥细胞和感光细胞。视锥细胞负责感知颜色,分为红、绿、蓝三种类型。
色彩感知
人眼通过三种视锥细胞感知颜色,这三种细胞对不同波长的光有不同的响应。通过这三种细胞响应的叠加,人眼可以感知到各种颜色。
同色异谱


由于人眼对颜色的感知并非完全基于光的波长,因此存在同色异谱现象,即不同波长的光经过人眼处理后产生相同的颜色感知。
色彩匹配
RGB颜色模型
RGB颜色模型是一种常用的颜色匹配方法,通过混合红、绿、蓝三种颜色的光来产生各种颜色。
CIE颜色标准
CIE颜色标准是一种基于人眼视觉特性的颜色匹配方法,通过定义三种基色(红、绿、蓝)的波长和强度来描述颜色。


总结

本节课介绍了色彩和人的视觉系统的基础知识,包括色彩的形成、色彩匹配以及人眼如何感知颜色。这些知识对于理解计算成像和图像处理等领域具有重要意义。

03.如何构建游戏世界 | GAMES104-现代游戏引擎:从入门到实践 - P1:0 - GAMES-Webinar - BV1YY4y1p74P
概述
在本节课中,我们将学*如何构建游戏世界。我们将了解游戏世界的构成元素,以及如何使用游戏引擎来组织和控制这些元素。
游戏世界的构成
以下是游戏世界的构成元素:
- 动态物 (Dynamic Objects): 可交互的动态物体,例如坦克、无人机、小兵等。
- 静态物 (Static Objects): 静态物体,例如瞭望塔、飞机库、房屋等。
- 环境 (Environment): 地形系统、天空等。
- 触发器 (Triggers): 检测体,例如触发积分增加的检测区域、空气墙等。
- 游戏规则 (Game Rules): 游戏玩法规则。
游戏对象 (Game Object)
在游戏中,所有元素都可以抽象为游戏对象 (Game Object)。游戏对象由属性 (Property) 和行为 (Behavior) 组成。
- 属性 (Property): 描述游戏对象的特征,例如位置、血量、油量等。
- 行为 (Behavior): 描述游戏对象的行为,例如移动、巡逻、攻击等。
组件化 (Component-based)
为了更好地管理和扩展游戏对象,我们可以使用组件化 (Component-based) 方法。将游戏对象拆分成多个组件,每个组件负责特定的功能。
- 组件 (Component): 负责特定功能的模块,例如位置组件、模型组件、动画组件等。
- 组件基类 (Component Base): 组件的基础行为接口。
Tick 函数
游戏引擎使用 Tick 函数来更新游戏世界。Tick 函数会遍历所有游戏对象,并调用每个组件的 Tick 函数。
事件机制 (Event Mechanism)
游戏对象之间通过事件机制进行通信。事件机制可以解耦游戏对象之间的逻辑,并提高效率。
场景管理 (Scene Management)
为了高效地管理大量游戏对象,我们需要使用场景管理 (Scene Management) 技术。常见的场景管理技术包括:
- 四叉树 (Quadtree): 将场景划分为多个四边形区域。
- 八叉树 (Octree): 将场景划分为多个八边形区域。
- 包围盒 (Bounding Volume): 使用包围盒来表示游戏对象的空间范围。
总结

在本节课中,我们学*了如何构建游戏世界。我们了解了游戏世界的构成元素、游戏对象、组件化、Tick 函数、事件机制和场景管理。这些知识将帮助我们更好地理解游戏引擎的架构,并构建自己的游戏世界。


03.色彩和人的视觉系统(II) | GAMES204-计算成像 - P1 - GAMES-Webinar - BV1WU4y1i7m5
概述
在本节课中,我们将继续探讨色彩和人的视觉系统,重点关注色彩空间、色彩再现以及相机中的色彩感知。
色彩空间
颜色定量表征
为了方便人们对颜色的认知和交流,我们需要一些统一的方法来量化精确地表达各种颜色。颜色定量表征涉及人类的心理和生理问题,以及照明和观察物体的物理条件。
CIE 1931 标准色彩系统
国际照明委员会(CIE)在 1931 年提出了 CIE 1931 标准色彩系统,形成了 RGB 色彩空间。我们将在后续课程中介绍各种不同需求的颜色空间或颜色模型。

颜色空间定义

颜色空间可以定义为一种表示颜色的数学方法,用表示颜色的基本参数来描述和记录颜色。常见的色彩空间分为两种:
- 与设备相关的色彩空间:例如 RGB 色彩空间。
- 与设备无关的色彩空间:例如 CIE 1931 色彩空间。
CIE XYZ 空间

CIE XYZ 空间是一个采用数学方式定义的色彩空间,可以表示出人眼可以看到的所有颜色。



色度图
色度图将 CIE XYZ 空间投影到 X+Y+Z=1 的*面上,建立了人眼和它表示的关系。
色域
色域是人眼可以感受到的最大色域,例如 sRGB、Adobe RGB 和 Profoto RGB。
白点
白点是各种颜色最均衡的地方,也是整个颜色空间的重心。
HSV 空间
HSV 空间比 RGB 色彩空间更接*于人们对色彩的一个感知经验,可以直观地表达出整个颜色的色调、饱和度和明暗程度。


孟赛尔颜色系统




孟赛尔颜色系统是色度或笔色法中,通过色调、明度和彩度三个维度来描述颜色的方法。


CIE LAB 空间
CIE LAB 空间是 CIE 提出的描述人眼可见的所有颜色的最完美色彩模型,通常被认为是与设备无关的一种模型。
色彩再现
色彩再现方法
色彩再现方法用于将 RGB 值转换为显示器上显示的颜色。
Gamma 校正
Gamma 校正用于调整显示器的亮度响应,使其更接*人眼的感知。


色彩管理
色彩管理涉及将颜色从一个颜色空间转换为另一个颜色空间,例如将 RGB 转换为 CMYK。
相机中的色彩感知
Bayer 滤镜
Bayer 滤镜是一种用于彩色成像的阵列,它通过不同的颜色滤镜来捕捉图像。
De-mosaic
De-mosaic 是从 Bayer 滤镜图像中恢复原始 RGB 图像的过程。
总结
在本节课中,我们学*了色彩空间、色彩再现以及相机中的色彩感知。这些知识对于理解图像处理和计算机视觉至关重要。
本节课中我们一起学*了:

- 色彩空间
- 色彩再现
- 相机中的色彩感知

🎮 课程04:游戏引擎中的渲染实践 | GAMES104-现代游戏引擎:从入门到实践
在本节课中,我们将要学*游戏引擎渲染系统的基础知识。我们将从渲染系统面临的挑战开始,逐步深入到GPU硬件架构、可渲染对象的数据组织、可见性裁剪等核心概念,并了解现代渲染管线的发展趋势。课程内容旨在为初学者建立清晰、实用的知识框架。
📖 概述:游戏引擎渲染系统的挑战与特点
游戏引擎的绘制系统是一个庞大且复杂的模块。从最早的示波器时代到现代的高性能GPU,渲染技术经历了巨大的演变。虽然游戏引擎远不止渲染,但渲染系统无疑是技术难度和专业壁垒最高的部分之一。
绝大多数现代游戏都需要绘制系统。计算机图形学理论为渲染系统提供了算法基础,但游戏中的渲染实践面临着独特的工程挑战。
上一节我们介绍了游戏引擎的整体架构,本节中我们来看看渲染系统具体面临哪些挑战。
以下是游戏引擎渲染系统区别于理论图形学的四个核心挑战:
- 复杂性:游戏场景需要同时处理成千上万个不同类型的物体(如角色、植被、水体、天空),并整合多种绘制算法(如毛发、皮肤、水体)以及大量的后处理和光照运算。这是一个“All in One”的复杂组合系统。
- 硬件深度适配:渲染算法必须高效运行在特定的硬件架构(如PC、主机、移动设备)上,而非理想化的计算模型。这要求开发者必须深入理解当代硬件的特性。
- 严格的性能预算:游戏必须保持稳定的帧率(如30FPS或60FPS),无论场景复杂与否。渲染算法必须在固定的时间预算(如每帧33ms或16ms)内完成,且随着分辨率提升(如从1080P到4K、8K),预算愈发紧张。
- 资源共享:渲染系统不能独占全部计算资源(如CPU)。它通常只能占用约10%-20%的CPU资源,其余需留给游戏逻辑、网络等其他子系统。因此需要进行性能剖析(Profiling)以确保不超预算。
这些挑战使得现代游戏引擎的渲染系统设计,与传统计算机图形学理论有较大差别。本课程章节(共四节)将聚焦于这个高度工程化、经过*30年迭代优化的软件系统。
🧠 第一部分:理解渲染与GPU硬件基础
在深入渲染管线之前,我们需要理解渲染的基本要素和其运行的硬件基础——GPU。
渲染的基本要素
渲染的核心工作是计算。其过程可以简化为:将三维空间中的顶点通过投影矩阵变换到屏幕空间,光栅化为像素,然后为每个像素计算颜色(考虑材质、纹理、光照等)。
一个基础的渲染计算通常包含以下几种操作:
- 访问常量(如屏幕尺寸)
- 数学运算(如向量点乘、矩阵变换)
- 访问变量
- 纹理采样
其中,纹理采样是一个复杂且昂贵的操作。为了抗锯齿(Aliasing),纹理通常存储为多级渐远纹理(Mipmaps)。一次纹理采样可能需要访问多个像素并进行多次插值。
GPU:渲染的核心硬件
GPU(Graphics Processing Unit)是现代渲染的基石。它的强大算力源于两种关键架构思想:
- SIMD(单指令多数据):一条指令可以同时对多个数据执行相同操作(如对一个四维向量做加法)。
- SIMT(单指令多线程):这是GPU的核心思想。GPU拥有大量小型计算核心(如NVIDIA的CUDA Core)。一条指令可以同时在数百甚至数千个核心上执行相同的操作,但每个核心处理自己的数据。这实现了极高的并行计算能力。
公式:总计算能力 ≈ 核心数量 × SIMD宽度 × 指令吞吐量
现代GPU(如NVIDIA的Fermi、Ampere架构)由多个图形处理集群(GPC)组成,每个GPC内包含多个流式多处理器(SM)。每个SM内集成了大量CUDA核心、专用纹理单元、特殊函数单元(SFU),以及共享内存。最新的架构还集成了张量核心(Tensor Core)和光追核心(RT Core)。
硬件架构对渲染设计的影响
理解硬件架构后,我们可以总结出几个影响渲染系统设计的关键原则:
- 数据局部性与缓存:CPU/GPU从缓存读取数据比从内存/显存快得多。设计数据结构时,应尽量让连续访问的数据在内存中也连续存放,以提高缓存命中率(Cache Hit),避免缓存未命中(Cache Miss)导致的性能下降。
- 避免回读(Readback):从GPU显存向CPU内存回读数据速度很慢,且会造成流水线停滞,增加延迟。渲染系统应设计为单向数据流,尽可能让数据从CPU流向GPU,避免反向操作。
- 识别性能瓶颈:渲染管线是一个流水线。性能瓶颈可能出现在不同阶段,如:
- ALU Bound:计算单元过载。
- Bandwidth Bound:内存带宽不足。
- Latency Bound:等待数据的时间过长。
优化时需要找到并解决当前的主要瓶颈。
上一节我们了解了渲染的挑战和硬件基础,本节中我们来看看在游戏引擎中,一个物体是如何被组织成可绘制数据的。
🧱 第二部分:构建可渲染对象(Renderable)
游戏世界中的逻辑对象(Game Object)本身并不能被直接绘制。我们需要从中提取或关联一个可渲染对象(Renderable)。
一个基础的Renderable由以下几部分构成:
- 网格(Mesh):描述物体的几何形状。通常由顶点缓冲区(Vertex Buffer)和索引缓冲区(Index Buffer)表示。索引缓冲区通过存储顶点索引来复用顶点数据,节省存储空间和带宽。
- 材质(Material):描述物体表面的视觉属性(如颜色、光滑度、金属度)。它定义了光线与表面交互的方式。从经典的Phong模型到现代的基于物理的渲染(PBR)模型,材质系统在不断演进。
- 纹理(Texture):是材质的重要部分,提供表面细节(如颜色贴图、法线贴图、粗糙度贴图)。视觉上区分不同材质(如光滑金属 vs. 生锈铁皮)很大程度上依赖于纹理。
- 着色器(Shader):一段在GPU上运行的程序代码。它接收Mesh、Material和Texture数据,并执行具体的渲染计算。在现代引擎中,艺术家可以通过着色器图(Shader Graph)可视化地组合材质,引擎再将其编译为Shader代码。
子网格(SubMesh)与实例化(Instancing)
一个复杂的模型(如角色)通常使用多个材质。因此,引擎会将一个Mesh按材质划分成多个子网格(SubMesh)。每个SubMesh引用一段连续的三角形索引,并关联一套独立的材质、纹理和着色器。
为了高效管理资源,引擎会将相同的Mesh、Texture、Shader数据集中存储在不同的资源池(Pool)中。场景中的每个具体物体只是这些资源定义的一个实例(Instance)。实例化渲染技术可以一次性提交一个物体的多个实例(仅变换矩阵不同),极大提升绘制效率。
纹理压缩
游戏中的纹理不会以BMP、JPEG等通用图片格式存储。为了节省显存和带宽,纹理会进行块压缩(Block Compression),如DXTC(DXT1-5)或后来的BC(BC1-7)系列。
核心思想:将纹理分成4x4的小块,存储块内两个极值颜色(如最亮和最暗),其他像素则存储在这两个颜色间插值的索引。这种格式支持随机快速访问,且压缩/解压速度很快。
移动*台常用ETC或ASTC格式,原理类似但分块更灵活。
上一节我们知道了如何组织单个物体的渲染数据,本节中我们来看看如何优化整个场景的渲染。
✂️ 第三部分:可见性裁剪(Visibility Culling)
绘制所有物体是非常低效的。可见性裁剪的目标是尽可能剔除(Cull)掉相机看不到的物体。
基础概念:包围体(Bounding Volume)
与复杂网格直接进行相交计算代价高昂。因此,我们使用简单的几何体来*似表示物体的空间范围,即包围体。常用类型有:
- 包围球(Bounding Sphere)
- 轴对齐包围盒(AABB, Axis-Aligned Bounding Box):最常用,计算效率高。
- 定向包围盒(OBB, Oriented Bounding Box)
- 凸包(Convex Hull)
空间加速结构
对场景中所有物体的包围盒逐一进行视锥裁剪仍然很慢。我们需要空间数据结构来加速:
- 包围盒层次结构(BVH, Bounding Volume Hierarchy):将场景物体组织成树状结构。父节点包含子节点包围盒的并集。测试时从根节点开始,如果父节点不可见,则其下所有子节点都可剔除。BVH在动态场景中更新效率较高。
- 潜在可见集(PVS, Potentially Visible Set):将游戏关卡空间预先划分为多个区域(Zone),并预先计算每个区域能看到哪些其他区域。运行时,根据玩家所在区域,只加载和渲染PVS内的区域。此思想也常用于资源流式加载。
基于GPU的裁剪

随着GPU能力增强,许多裁剪工作可以移交GPU完成:
- 遮挡查询(Occlusion Query):将简化包围盒提交给GPU,GPU返回一个布尔值表示是否可见。
- Early-Z / Hierarchical-Z:利用深度缓冲。在正式着色前,先以最低成本渲染一遍深度信息(生成深度图)。后续渲染时,如果一个像素的深度值比深度图中已有的值更远(即被遮挡),则直接跳过该像素的着色计算。这是一种非常有效的逐像素裁剪。
核心优化思想:最高效的优化就是让计算机“什么都不做”(Do Nothing)。可见性裁剪正是这一思想的体现。
🚀 第四部分:现代渲染管线的发展趋势

游戏渲染技术持续演进,主要趋势如下:
- GPU驱动渲染(GPU-Driven Rendering):将更多原本由CPU负责的工作(如裁剪、LOD选择、动画计算)转移到GPU上,利用其强大的并行能力。这包括GPU Driven Culling和GPU Driven Pipeline。
- 集群化网格管线(Cluster-Based Mesh Pipeline):这是应对超高清模型(数百万面)的新管线。传统管线以整个物体为单位处理,而新管线将模型分割成许多固定大小(如64个三角形)的集群(Cluster)。
- 每个Cluster独立处理,适合GPU并行。
- 可以结合网格着色器(Mesh Shader) 或放大着色器(Amplification Shader),根据距离动态生成或简化几何细节。
- 允许更精细的裁剪(如只裁剪掉角色的一只手)。
- 虚幻引擎5的Nanite虚拟化几何系统是这一方向的杰出代表。

🛠️ 课程总结与作业
本节课中我们一起学*了游戏引擎渲染系统的基础:
- 渲染是一个工程实践,深度依赖对GPU等现代图形硬件的理解。
- 渲染的核心数据是Mesh、SubMesh、Material、Texture和Shader,并通过实例化进行高效管理。
- 可见性裁剪是优化的关键,目标是尽可能减少不必要的绘制工作。
- 发展趋势是GPU驱动和集群化管线,以应对日益复杂的场景和更高的视觉保真度要求。
课程小引擎项目:我们提供了一个简易但结构完整的游戏引擎框架(Pilot Engine),供大家学*和实践。代码已重构,约2万行,包含了编辑器、组件系统、资源管理等基础框架。
第一次作业:下载并编译小引擎,成功运行后截图提交。目标是熟悉游戏引擎项目的基本结构和搭建流程。


下节课预告:我们将深入探讨现代游戏中的光照模型(如PBR)、材质系统以及着色器模型,学*如何渲染出符合行业标准的视觉效果。

📸 课程04:相机系统 | GAMES204-计算成像
在本节课中,我们将学*传统相机系统的基本原理与核心组件。我们将从成像光学开始,逐步了解光圈、景深、视野、传感器、噪声和曝光等关键概念,为后续深入学*计算成像技术打下基础。

🧠 成像光学
上一节我们介绍了色彩的形成与捕获,本节中我们来看看相机如何实现成像。成像光学是相机系统的基础。
最早的成像光学器件可以追溯到约2700年前的“Nimrud透镜”,它主要用于聚光生火或作为放大镜。透镜成像遵循一个基本公式:物距、像距和焦距之间的关系。
透镜成像公式:
1/u + 1/v = 1/f
其中,u 是物距,v 是像距,f 是焦距。放大率 M 为:
M = -v/u = f / (f - u)
然而,实际透镜并非理想模型,会引入各种像差,影响成像质量。以下是几种主要的像差:


- 球差:*行光经球面透镜后,不同离轴距离的光线聚焦在不同位置。
- 彗差:斜入射光线导致像点呈彗星状拖尾。
- 像散:点光源成像为不对称的线条或椭圆。
- 畸变:图像发生桶形或枕形扭曲。
- 色差:不同波长的光因折射率不同而无法汇聚于同一点。


这些像差需要通过复杂的光学设计(如使用多片透镜组合)或后期算法进行校正。
⭕ 光圈

光圈是相机中控制光线进入的孔径栏。它有两个主要作用:一是消除杂散光,二是决定系统的进光量。

光圈大小用F数表示,其定义为焦距与孔径直径的比值:
F数 (N) = 焦距 (f) / 孔径直径 (D)
F数越小,表示光圈孔径越大,进光量越多。
🔍 景深

当相机对焦于某一*面时,只有该*面上的物体成像最清晰。其前后一定范围内的物体,虽然成像点会变成一个弥散圆,但只要这个弥散圆的尺寸小于传感器像素尺寸,人眼仍会认为图像是清晰的。这个清晰成像的前后范围就是景深。

景深受物距、光圈大小和放大率影响。光圈越大(F数越小),景深越浅;反之,光圈越小,景深越深。

以下是景深与光圈大小的关系示例:


- 大光圈 (如 f/1.8):景深很浅,背景虚化效果明显,适合突出主体。
- 中等光圈 (如 f/4):景深适中。
- 小光圈 (如 f/8):景深很深,前后景都较为清晰,适合风光或工业检测。
景深与曝光时间存在矛盾:使用小光圈以获得大景深时,需要更长的曝光时间,这可能导致运动物体模糊。
🌐 视野
视野是指相机能够观察到的最大角度范围。它由焦距和传感器尺寸共同决定。
对于一个理想薄透镜模型,其视野可*似用小孔成像模型计算。水*视野的计算公式为:
水*视野 = 2 * arctan( (传感器宽度 / 2) / 焦距 )
同理可计算垂直视野和对角线视野。
传感器尺寸越大,在相同焦距下,能获得的视野也越大。常见的传感器画幅包括:
- 中画幅:约53.7mm × 40.2mm,用于高端专业相机。
- 全画幅:36mm × 23.9mm,主流专业和高端消费级相机。
- APS-C/4/3画幅:更小的尺寸,常见于入门相机。
- 手机传感器:尺寸更小(如1/1.1英寸),但通过算法优化成像。


焦距越长,视野越窄,能将远处的物体“拉*”;焦距越短,视野越广,能容纳更多场景。
📷 传感器
传感器是将光信号转换为电信号的核心部件。现代数码相机主要使用CMOS或CCD传感器。
一个像素单元通常包含以下结构:
- 微透镜:收集光线,提高填充因子,减少混叠效应。
- 彩色滤光片:通常是RGB阵列(如拜耳阵列),使每个像素只感应特定颜色的光。
- 光电二极管:利用光电效应,将光子转换为电子。
- 势阱:存储曝光期间产生的电荷。
- 模数转换器(ADC):将电荷量转换为数字信号。

传感器的关键性能指标是量子效率,即入射光子产生电子的概率。现代传感器的量子效率在某些波段可达85%。
为了捕获彩色图像,需要在传感器前放置彩色滤光片阵列。最常见的拜耳阵列由1个红、1个蓝和2个绿滤光片组成,通过后续的“去马赛克”算法重建全彩图像。

🔊 噪声
噪声是成像系统中不可避免的干扰,主要来源于物理过程和电子器件。



成像过程中的主要噪声类型包括:
- 散粒噪声:光子到达是一个泊松过程,其噪声标准差为
√N(N为光子数)。这是无法消除的固有噪声。 - 读出噪声:与信号无关的加性噪声,如热噪声、放大器噪声,通常建模为高斯噪声。
- 固定模式噪声:由于传感器制造工艺不均导致的固定图案噪声。
- 量化噪声:模数转换过程中因精度有限产生的误差。



信噪比是衡量图像质量的重要指标,其定义为信号强度与噪声标准差的比值。光照越强,信噪比通常越高。



⚡ 曝光
曝光是指传感器接收光能量的过程,由曝光量 H 衡量:
H ∝ 辐照度 × 曝光时间
辐照度由光圈控制,曝光时间由快门控制。


控制曝光的三要素是:




- 光圈:控制进光量和景深。
- 快门速度:控制曝光时间,影响运动模糊。
- 感光度(ISO):控制信号放大增益,影响图像噪声。
三者需要协同调整以达到正确曝光。例如,增大光圈可缩短曝光时间,但会减小景深;提高ISO可补偿暗光,但会引入更多噪声。

快门有两种主要类型:



- 全局快门:所有像素同时曝光和读取,无变形,但速度可能较慢。
- 卷帘快门:逐行曝光和读取,速度更快,但在拍摄高速运动物体时会产生“果冻效应”。






📝 总结

本节课我们一起学*了传统相机系统的核心组成部分和工作原理。我们从成像光学的基础公式和像差开始,理解了光圈如何控制进光量与景深,视野如何由焦距和传感器尺寸决定。接着,我们探讨了传感器将光转换为电信号的过程,以及在此过程中产生的各种噪声。最后,我们掌握了控制曝光的三要素——光圈、快门和ISO——及其相互制约关系。这些知识是理解后续图像信号处理流程和计算成像技术的基础。下一节课,我们将深入探讨图像信号处理管线,并布置第一次作业。


📸 课程05:图像信号处理基础 | GAMES204-计算成像
在本节课中,我们将要学*图像信号处理(Image Signal Processing, ISP)的基础流程。ISP是将相机传感器捕获的原始数据(RAW图)转换为我们最终看到的彩色图像的关键步骤。我们将按照处理流程,逐一介绍每个核心环节的原理与简单实现方法。


🧠 概述:从RAW到彩色图像的旅程
上节课我们介绍了相机成像的整个过程,包括调整光圈、曝光和白*衡。那么,从传感器捕获原始数据到最终呈现出一张悦目的图像,中间经历了怎样的处理呢?这就是今天要揭晓的答案——图像信号处理。
整个ISP流程可以大致分为在RAW域(原始数据域)的处理和在RGB/YUV域的处理。我们将按照这个顺序进行讲解。
📊 RAW域处理

传感器捕获的图像是经过拜耳滤色片(Bayer Filter)排列的原始数据。在这个阶段,我们需要进行一系列校正和预处理。
坏点校正

首先,我们需要处理图像传感器上的坏点。坏点是由于传感器制造工艺缺陷或电路问题导致的,表现为某些像素值固定为黑、白或某个灰度值,甚至动态闪烁。坏点会影响后续的插值和滤波操作,因此需要最先处理。
坏点校正通常分为两步:
- 检测坏点:计算中心像素与其八邻域像素的绝对差值。如果差值超过设定的阈值,则判定该中心像素为坏点。
- 修复坏点:对于判定为坏点的像素,通常采用插值方法修复。一个简单的方法是计算其四个方向(水*、垂直、两个对角线)的梯度,选择梯度最小的方向,用该方向上相邻像素的*均值来替换坏点值。
黑电*校正
黑电*是指感光元件在完全没有光线照射时输出的信号值。由于传感器电路存在暗电流,并且为了保留暗部细节,厂商通常会施加一个偏置电压,导致RAW图像的黑电*不为零。
如果不进行黑电*校正,图像会呈现“灰蒙蒙”的感觉,并严重影响后续白*衡等操作的准确性。校正方法通常是从每个像素的原始值中减去一个固定的黑电*偏移量。这个偏移量可能由硬件预设,或通过传感器边缘的遮光像素实时检测得到。
镜头阴影校正
由于镜头设计的光学特性,图像边缘的进光量会比中心少,导致图像四周变暗,这称为镜头阴影(Lens Shading)。有时还会伴随颜色偏差(Color Shading)。
校正方法通常是通过拍摄均匀的白板,计算图像每个区域相对于中心亮度的增益系数,形成一个校正图或查找表,然后在处理时对每个像素乘以对应的增益系数。
去噪

噪声会严重影响图像质量,特别是在低光照条件下。在RAW域进行去噪效果较好,因为此时噪声的分布未被后续非线性处理破坏。
以下是两种经典的去噪算法:
- 非局部均值(Non-Local Means, NLM):利用图像中存在的许多自相似块。通过寻找与当前块相似的多个图像块并进行加权*均,可以有效抑制噪声。
- 三维块匹配滤波(BM3D):这是目前效果最好的传统去噪算法之一。其核心思想也是寻找相似块,并将这些相似的二维块堆叠成三维数组,然后在变换域(如DCT)进行协同滤波和阈值处理,最后再逆变换回图像空间。
自动曝光、自动对焦与自动白*衡(3A算法)
这是相机自动化的核心,通常作为一个反馈回路动态调整成像参数。
- 自动曝光(AE):通过调整光圈、快门速度和ISO增益,使图像整体亮度适中。常用方法有*均亮度法、加权亮度法和直方图法。
- 自动对焦(AF):
- 被动对焦:通过检测图像的对比度或高频信息。当图像最清晰时,对比度最高。相机通过移动镜头寻找对比度峰值来完成对焦。
- 主动对焦:通过发射红外光或超声波并测量其返回时间(ToF)来直接测算被摄物体距离,从而驱动镜头到对应位置。
- 自动白*衡(AWB):校正不同光源下的颜色偏差,使白色物体看起来是白色。常见方法有:
- 灰度世界法:假设整幅图像所有颜色的*均反射率相等,通过调整RGB三通道增益使其均值一致。
- 完美反射法:假设图像中最亮的点就是镜面反射点,代表了光源的颜色,以此作为白点进行校正。

🎨 RGB/YUV域处理
经过RAW域处理和去马赛克后,我们得到了线性的RGB图像,接下来需要在这个域进行色彩和视觉增强处理。
去马赛克(Demosaicing)
这是ISP中最关键的步骤之一。拜耳滤色片使得每个像素点只捕获一种颜色(R, G或B)。去马赛克的目标是通过插值,为每个像素点重建出缺失的另外两个颜色通道的值。
最简单的方法是双线性插值,但它在边缘处容易产生伪彩色和细节模糊。更先进的方法会考虑边缘方向。例如 Malvar方法 的基本思想是:在插值绿色通道时,不仅使用周围的绿色像素,还会利用红色或蓝色通道的梯度信息进行校正,从而在边缘处获得更好的插值效果。

核心公式(Malvar方法中插值G通道的简化思想):
G_interpolated = G_bilinear + α * (R_center - R_bilinear)
其中,α 是一个根据邻域梯度调整的系数,用于校正颜色差异带来的影响。
颜色校正
由于传感器、滤光片等因素,相机捕获的颜色可能与真实颜色有偏差。颜色校正的目标是使图像色彩更接*人眼所见的真实场景。
最常用的方法是使用一个 3x3的颜色校正矩阵(CCM)。通过拍摄标准色卡,将相机输出的RGB值与色卡的标准值进行比对,利用最小二乘法求解出这个校正矩阵。处理时,对每个像素的RGB向量乘以这个矩阵即可。
[R_corrected, G_corrected, B_corrected]^T = CCM * [R, G, B]^T

对于要求更高的应用,会使用 3D查找表(3D LUT) 进行更精细、非线性的颜色映射。

边缘增强
经过之前的处理,图像的高频细节(边缘)可能会有所损失。边缘增强旨在让图像看起来更锐利。
一个简单的方法是使用 拉普拉斯算子 进行边缘检测,然后将检测到的边缘信息按一定比例加回到原图像上。通常在YUV色彩空间的Y(亮度)通道进行操作,以避免影响颜色。
Y_enhanced = Y_original + β * Laplacian(Y_original)
其中,β 是控制增强强度的系数。



伪彩色抑制

在去马赛克等过程中,图像的尖锐边缘处可能会产生原本不存在的彩色条纹,即伪彩色。抑制伪彩色通常在YUV空间进行,对Cr和Cb(色度)通道在检测到的边缘区域进行*滑或衰减处理。
亮度与对比度调节
这是最直观的图像调节。
- 亮度调节:为所有像素的亮度值加上或减去一个常数。
I_adjusted = I_original + brightness_offset - 对比度调节:围绕一个中间值(如128)进行缩放。
I_adjusted = contrast_scale * (I_original - 128) + 128
伽马校正与色调映射
-
伽马校正:由于历史原因(CRT显示器的非线性响应)和人眼对暗部更敏感的特性,我们需要对图像进行一个非线性变换。通常是对线性RGB值开一次伽马(γ)次方根(γ通常为2.2)。
I_display = I_linear^(1/γ)
这实际上是一个全局的亮度调整,将更多的数值空间分配给暗部细节。 -
色调映射:当图像的动态范围(最亮与最暗的比值)远大于显示设备的能力时(例如HDR图像在普通显示器上显示),就需要进行色调映射。它的目的是在压缩动态范围的同时,尽可能保留亮部和暗部的细节。这比伽马校正更复杂,常常是局部进行的算法。
📝 总结与作业


本节课我们一起学*了图像信号处理(ISP)的完整基础流程。我们从RAW域的坏点校正、黑电*校正、去噪和3A算法开始,然后进入到RGB/YUV域的去马赛克、颜色校正、边缘增强、伪彩色抑制,最后是亮度对比度调节以及伽马与色调映射。
这些步骤共同协作,将传感器捕获的原始数据转换成了我们日常所见色彩鲜艳、细节清晰的数字图像。

作业提示:你的第一次作业将是实现一个基础的ISP流水线。请按照课程介绍的顺序,逐一实现各个模块,并尝试将一张RAW格式图像处理成美观的彩色图像。注意处理好各模块之间的衔接,并关注最终图像的视觉质量。

课程05:渲染中光和材质的数学魔法 🔮 | GAMES104-现代游戏引擎:从入门到实践
在本节课中,我们将深入探讨游戏渲染的核心:光、材质与着色器。我们将从最基础的渲染方程出发,逐步了解游戏引擎如何通过一系列巧妙的数学工具和工程实践,在有限的实时算力下模拟出复杂而真实的光影世界。课程内容将涵盖从经典的光照模型到现代的基于物理的渲染技术。
概述:渲染的核心挑战
上一节我们介绍了渲染管线和GPU的基本组织。本节中,我们来看看渲染最核心的数学基础——渲染方程,以及它带来的巨大挑战。
渲染的本质是模拟光在场景中的传播。35年前,Kajiya提出了著名的渲染方程,它用一个六维方程描述了所有渲染现象。方程指出,一个点出射的光由它自身发光和所有入射光经过表面反射后贡献的总和决定。这涉及到在半球面上对所有可能的入射方向进行积分,并乘以一个描述表面反射特性的四维函数——双向反射分布函数。
然而,这个完美的理论方程在实时渲染中面临三大挑战:
- 光的可见性:判断一个点是否能“看见”光源,这直接关系到阴影的生成。
- 复杂的积分运算:对球面光照和BRDF进行卷积积分,计算量巨大。
- 全局光照:场景中所有物体都可能成为次级光源,光线会多次反弹,形成无限递归。
正是为了解决这些挑战,图形学工程师们发展出了各种精妙绝伦的“魔法”。
第一部分:经典渲染的“三件套” 🛠️
在硬件能力有限的早期,工程师们采用了一套简单而有效的组合方案来*似渲染方程。
简化光照模型
以下是早期游戏引擎中常用的三种光照简化方法:
- 主光源:使用方向光、点光源或聚光灯来模拟场景中最主要的光照。
- 环境光:用一个均匀的常数来*似模拟来自四面八方的间接光照。
- 环境贴图:用一个立方体贴图来模拟光滑表面的环境反射,捕捉高频的反射细节。
这套组合虽然简单,但通过将复杂的光场分解为均匀环境光、突出主光和镜面反射,巧妙地符合了渲染方程的基本思想。
经验材质模型:Blinn-Phong
在材质方面,早期广泛使用的是Blinn-Phong经验模型。它基于直观的观察,将材质反射分为两部分:
漫反射项:Diffuse = k_d * (L·N)
高光项:Specular = k_s * (N·H)^p
其中,k_d和k_s是反射系数,L是光方向,N是法线,H是半角向量,p是高光指数。
这个模型简单高效,但它有两个主要问题:能量不保守(在光线追踪中会导致能量累积)和表现力有限(所有材质看起来都像塑料)。
阴影生成:Shadow Map
对于光的可见性,游戏行业长期依赖Shadow Map技术。其核心思想非常直接:
- 从光源视角渲染场景,只记录深度信息,生成一张深度图(Shadow Map)。
- 从相机视角渲染时,将每个像素点变换到光源空间,得到其到光源的距离。
- 将该距离与Shadow Map中存储的最*深度比较,如果更远,则该点在阴影中。
Shadow Map虽然会产生自遮挡、锯齿等问题,需要通过偏移、滤波等手段来“Hack”,但它以其简单和普适性,成为了游戏阴影的基石。
通过“主光+环境光+环境贴图”的光照、“Blinn-Phong”材质和“Shadow Map”阴影这套“三件套”,早期游戏实现了在有限算力下可接受的真实感。
第二部分:迈向真实——全局光照与预计算 🌍
随着对画面品质要求的提升,3A游戏开始追求更真实的全局光照效果。这催生了以“空间换时间”为核心的预计算技术。
数学工具:球谐函数
处理球面函数(如光照)的积分是核心难题。球谐函数提供了一种完美的解决方案。它类似于傅里叶变换,可以将球面上的任意函数分解为一系列基函数的线性组合。
以下是球谐函数前几阶基函数的直观理解:

- 第0阶:一个常数,代表球面上的*均值。
- 第1阶:三个基函数,分别正比于x, y, z坐标,可以表示光的主要方向。
- 更高阶:能表示更复杂、更高频的分布。
球谐函数具有正交性、旋转不变性等优秀数学性质。最关键的是,两个球面函数的卷积可以简化为它们球谐系数的点积。这意味着我们可以用少量系数(如4个)压缩存储一个点的光照信息,并快速计算光照与材质的卷积结果。
光照烘焙:Lightmap
基于球谐函数等工具,Lightmap技术得以广泛应用。其流程如下:
- 参数化:将场景的静态几何体展开(参数化)到一张或多张大的纹理图集上。
- 烘焙:在离线阶段,使用光线追踪等方法,计算每个纹理像素(对应世界空间一个点)所接收的全局光照信息(通常用球谐系数存储)。
- 运行时采样:渲染时,根据像素对应的纹理坐标,直接从Lightmap中读取预计算的光照信息。
Lightmap能产生非常细腻的间接光照和软阴影,但缺点也很明显:烘焙耗时极长,且只能用于静态物体和静态光。
动态补充:Light Probe
为了给动态物体提供全局光照,引入了Light Probe技术。
- 布点:在场景空间中均匀或按路径放置大量的采样点(Probe)。
- 采样:在每个Probe位置,捕获其周围的环境光照(通常存储为球谐系数或立方体贴图)。
- 插值:对于动态物体上的点,找到其周围的几个Probe,进行插值得到该点的环境光照。
对于高光反射,还会使用精度更高但数量更少的Reflection Probe。Light Probe可以在运行时更新,兼顾了动态物体和一定的实时性。
通过Lightmap处理静态全局光照,Light Probe处理动态物体光照,游戏画面在真实感上迈进了一大步。
第三部分:基于物理的渲染 🎨
材质模型的进化目标是更符合物理规律,同时让艺术家更容易使用。这就是基于物理的渲染的核心思想。
微表面理论
PBR材质基于微表面理论:宏观表面由无数微观镜面组成。光线的反射行为由这些微表面的法线分布决定。
- 粗糙度低:微法线集中,表面像镜子。
- 粗糙度高:微法线分散,表面漫反射强。
Cook-Torrance BRDF模型
现代PBR常用Cook-Torrance模型,其反射率由三项相乘得到:
BRDF = (F * D * G) / (4 * (N·L) * (N·V))
以下是三项的物理意义:
- D (法线分布函数):描述微表面法线的分布。常用GGX模型,其“长尾”特性能让高光过渡更自然。
// GGX NDF 示例 float D_GGX(float NdotH, float roughness) { float a = roughness * roughness; float a2 = a * a; float NdotH2 = NdotH * NdotH; float denom = (NdotH2 * (a2 - 1.0) + 1.0); return a2 / (PI * denom * denom); } - G (几何遮蔽函数):描述微表面间互相遮挡的现象。常用Smith-Schlick模型。
- F (菲涅尔方程):描述反射率随观察角度变化的规律。掠射角时反射率最强。Schlick*似是常用公式:
F = F0 + (1 - F0) * pow(1 - (H·V), 5)
艺术家友好的参数化
迪士尼提出了一套PBR材质设计原则:直观、参数少、范围在0-1、组合无异常。由此衍生出两种主流工作流:
- Specular-Glossiness工作流:
- Diffuse Map: 漫反射颜色。
- Specular Map: 镜面反射颜色(F0)。
- Glossiness Map: 光泽度(光滑度)。
- Metallic-Roughness工作流(更流行):
- BaseColor Map: 基础颜色。
- Metallic Map: 金属度。非金属时,BaseColor作为漫反射;金属时,BaseColor作为F0。
- Roughness Map: 粗糙度。
MR工作流通过金属度参数自动处理漫反射和镜面反射的分配,更不易出错,深受艺术家喜爱。
第四部分:基于图像的照明与环境反射 🌟
即使有了精确的材质,也需要真实的环境光照来“照亮”它。基于图像的照明技术将预计算思想发挥到极致。
核心思想
IBL的核心思想是:既然环境光照可以表示为一张贴图(如立方体贴图),那么材质与环境的卷积积分也可以预计算成贴图,在渲染时直接查表。
漫反射部分:Irradiance Map
漫反射部分(N·L)的卷积结果只与法线N有关。我们可以预计算一张Irradiance Map:对于立方体贴图的每个纹素方向(作为法线N),卷积计算所有入射光L的贡献。渲染时,根据法线方向采样这张图即可。
镜面反射部分:预滤波环境贴图与LUT
镜面反射部分复杂得多,它依赖于视角和粗糙度。解决方案是:
- 预滤波环境贴图:根据不同的粗糙度等级,预卷积环境贴图,生成Mipmap链。粗糙度越高,采样的Mip层级越低(越模糊)。
- BRDF积分贴图:将菲涅尔项与几何项的复杂积分预计算成一张2D查找纹理,参数是
(N·V)和粗糙度。
在着色器中,结合这两张预计算贴图,就能高效地实现精确的环境镜面反射。
// IBL 着色伪代码示例
vec3 iblSpecular = prefilteredEnvMap.sample(roughness, R).rgb;
vec2 brdf = brdfLUT.sample(NdotV, roughness).rg;
vec3 specularIBL = iblSpecular * (F0 * brdf.x + brdf.y);
IBL技术让物体能够逼真地反射周围环境,极大地增强了场景的真实感和沉浸感。
第五部分:现代阴影技术进阶 🕶️
随着场景规模扩大,传统的单一Shadow Map无法兼顾*处细节和远处覆盖。级联阴影映射成为主流解决方案。
级联阴影映射
其原理非常直观:
- 根据相机视锥体的远*,将其分割成多个层级(如*、中、远)。
- 为每个层级分别从光源视角生成一张不同分辨率的Shadow Map。*处层级分辨率高,远处分辨率低。
- 渲染时,根据像素在相机空间中的深度,决定使用哪一层级的Shadow Map进行可见性判断。
这种方法匹配了透视投影下*大远小的采样率变化,有效解决了阴影的质量与性能矛盾。层间过渡需要通过混合来避免接缝。
软阴影技术
为了生成更真实的软阴影(阴影边缘有半影区),常用以下技术:
- PCF:对Shadow Map进行多次采样并滤波,模糊硬边缘。
- PCSS:在PCF基础上,根据遮挡物距离动态计算滤波核大小,模拟半影效果。
- VSSM:利用Shadow Map的矩(均值和方差)来*似估计遮挡概率,性能更高。
前沿展望与总结 🚀
本节课我们一起学*了从经典到现代的游戏渲染核心技术栈。回顾一下,一个典型的现代渲染方案包括:
- 光照:Lightmap(静态GI) + Light Probe(动态GI)。
- 材质:基于物理的渲染模型。
- 环境反射:基于图像的照明。
- 阴影:级联阴影映射 + 软阴影技术。
然而,时代在飞速发展。实时光线追踪硬件的普及,正在引发一场渲染革命。实时光追可以更精确地解决可见性、反射和全局光照问题。新的实时全局光照算法,如SDF GI、VXGI、Lumen等,正在挑战传统的预计算方案。Virtual Shadow Map等新技术也在重新定义阴影的生成方式。
渲染的世界充满了“魔法”,这些魔法是数学智慧与工程实践的结晶。从Kajiya的完美方程出发,一代代图形学人通过大胆的假设、巧妙的*似和极致的优化,在有限的实时算力内,一步步逼*物理真实,创造出了我们眼中绚丽的虚拟世界。希望本课程能为你打开这扇魔法世界的大门。


总结:本节课中,我们一起探索了渲染中光与材质的核心数学原理与工程实践。我们从最基础的渲染方程和经典“三件套”出发,逐步深入到全局光照的预计算技术、基于物理的材质模型、基于图像的照明以及现代阴影算法。这条技术演进之路,体现了图形学如何在理论理想与硬件现实之间寻找精妙的*衡,最终构筑出令人信服的虚拟世界。

课程06:游戏中地形、大气和云的渲染(上) 🌍☁️
在本节课中,我们将学*如何在现代游戏引擎中渲染出宏大而美丽的自然世界。我们将从最基础的地形渲染开始,逐步深入到材质混合、虚拟纹理等高级技术,理解如何用代码和算法“创造”大地。
概述:从观察自然开始 👀
上一节课程我们探讨了游戏引擎的核心架构。本节中,我们来看看如何渲染游戏中最宏大、最美丽的组成部分——自然世界。我们的目标是理解如何将山川、河流、植被和天空,通过0和1在虚拟世界中表现出来。
这是一张真实的自然照片。我们可以看到天空、植被、河流和岩石。回到我们的主角小明,他打开自己的游戏,发现画面与真实世界相去甚远:缺乏天空与云的表达,地面植被和地形的绘制也非常粗糙。这正是本节课要解决的问题。
一、成为“大地之神”:地形渲染基础 🗻
首先,我们从相对简单的内容开始:如何绘制地形。在游戏引擎中,地形渲染一般被称为 Terrain。
1.1 高度场:地形的基石
最基础的地形表达方法是 高度场。它并非新概念,早在几十年前,人类就通过等高线图来表达地形的高低变化。
高度场本质上是一个二维网格,每个点存储一个高度值。左边的明暗图表达了高度,其形态与我们熟知的 分形 概念非常相似。自然界的山脉、海岸线、腐蚀痕迹都符合分形的自我相似规律,这个规律对程序化生成地貌非常有用。
拿到高度场后,渲染就变得简单了。最直接的方法是生成一个均匀网格,将每个顶点根据高度图进行位移,就能形成起伏的地形效果,再为其应用材质和光影即可。
核心公式/概念:
- 高度场 (Height Field): 一个二维矩阵
H[x][y],存储每个(x, y)坐标点的高度值z。 - 顶点位移: 在顶点着色器中,根据采样到的高度值调整顶点位置:
vertex_position.z = texture_sample(height_map, uv).r * scale。
1.2 挑战:大规模地形的优化
上述简单方法在表达小型地形时可行,但对于开放世界这样数千*方公里的场景,生成海量三角形显然不可行。
观察真实地形:*处细节丰富,远处细节模糊。这提示我们可以进行优化。还记得前面课程提到的 LOD 概念吗?LOD 全称 Level of Detail,即根据物体在屏幕上的远*或像素占比,设置不同的细节精度。
然而,地形是连续的整体,其LOD切换需要精心设计,以避免产生裂缝(一个关键概念:T-junction)。
一个基本思想是 网格细分。我们真正关心的是视野内的区域。因此,可以让视野内的三角形密集,视野外和远处的三角形稀疏,并保证屏幕上每个像素覆盖的三角形密度大致恒定。
这里需要注意一个关键变量:FOV。FOV变窄时,相当于放大观察,地形在屏幕上占据更多像素,因此需要更密集的三角形。这正是游戏中“倍镜”效果的实现原理之一——并非真实光学望远镜,而是通过缩小摄像机FOV来实现放大效果。
核心原则总结:
- *密远疏,FOV窄则密,FOV宽则疏。
- 误差边界: 简化网格时,要保证在屏幕空间上的几何误差不超过给定阈值(如1个像素),这样玩家就难以察觉。
二、实现自适应细分:从理论到实践 ⚙️
理解了方法论,接下来看看如何具体实现三角形的疏密分布。
2.1 基于二叉树的三角形细分
一个经典方法是基于等腰直角三角形的二叉细分算法。
- 初始时,将正方形网格对角线分割,得到两个等腰直角三角形。
- 当需要更高细节时,永远在直角三角形的最长边(斜边)中点切一刀,从而生成两个新的等腰直角三角形。
- 这个过程天然形成了一个二叉树结构。
需要解决的问题:T-junction裂缝
当两个相邻三角形的公共边细分程度不一致时,就会在高度变化处产生裂缝,露出背景色。解决方法很简单:如果邻居边的细分程度比自己高,那么就将自己的边也细分到同等程度,确保顶点对齐。
2.2 基于四叉树的地形表达
尽管三角形细分方法有效,但在现代游戏行业中更主流的是基于四叉树的地形表达。
- 直觉上,我们会将大地形像切豆腐块一样管理,而不是处理各种形状的三角形。
- 四叉树结构规整,符合纹理存储(也是方块)、数据管理和人的直觉。
- 通常,引擎会以一定大小(如512x512米)作为一个区块,再将其细分为更小的地块。最大的区块就是磁盘上的一个数据块,包含了该区域的高度图、贴图、植被等所有信息。
四叉树的T-junction解决方案:缝合
与二叉树动态调整几何不同,四叉树采用更巧妙的缝合思路。
假设相邻两个地块,一个细分了4次,一个只细分了2次。细分多的地块,将其多出来的中间顶点“吸附”到细分少的地块的对应顶点上。这样,就会产生一些面积为零的退化三角形。现代GPU的光栅化器可以很好地处理这种三角形(直接跳过绘制),从而在视觉上形成无缝的水密网格。
核心数据结构:
- 四叉树节点: 代表一个地形方块区域,包含其子节点指针、LOD级别、渲染数据引用等。
- 数据块管理: 将地形数据(高度、材质、植被)按四叉树节点组织成可流式加载的数据块。

三、GPU驱动的实时细分 🚀
现代GPU提供了强大的实时几何生成能力,使得地形渲染更加高效和动态。
3.1 从固定管线到Mesh Shader
在DX11时代,通过 Hull Shader, Tessellator, Domain Shader, Geometry Shader 这一套管线实现细分。这套命名抽象,流程复杂。
- Hull Shader: 处理控制点,生成细分用的面片。
- Domain Shader: 对细分后生成的新顶点进行位移(如采样高度图)。
- Geometry Shader: 处理细分后的顶点数据(如计算纹理坐标)。
新一代的 Mesh Shader 极大地简化了这一流程。它将顶点处理、细分决策、几何生成等步骤整合到一个可编程阶段中,给予了开发者更大的灵活性和控制力。
3.2 动态地形的魅力
当顶点位置可以在运行时于Shader中动态调整时,就能实现非常酷的效果:实时可变地形。
- 例子1: 拖拉机驶过泥地,压出车辙。这可以通过简单的弹簧质点模型模拟压力,并动态调整高度图来实现。
- 例子2: 雪地中的脚印、炮弹炸出的弹坑。这些效果可以通过在玩家周围维护一个“变形纹理”来实现,记录所有的交互痕迹,并在渲染地形时叠加这些偏移。
核心代码思路(伪代码):
// 在Domain或Mesh Shader中
vec3 world_pos = calculateWorldPosition(input);
float base_height = sampleHeightMap(world_pos.xz);
float deformation = sampleDeformationMap(world_pos.xz); // 从动态纹理读取变形量
world_pos.y = base_height + deformation;
output.position = mul(ViewProjectionMatrix, vec4(world_pos, 1.0));
当然,要实现完整的物理碰撞更新,还需要同步更新物理引擎中的碰撞体,这更具挑战性。
四、超越高度场:体素化表达 🧊
高度场无法表达悬空、洞穴等复杂结构。一个更通用的思路是体素化表达。
4.1 Marching Cubes算法
在三维空间规则采样,每个体素存储一个密度值。Marching Cubes 算法可以从这些体素数据中提取出一个等值面,生成水密的三角网格。
- 该算法非常有名,广泛应用于医学成像(CT)、科学计算可视化等领域。
- 对于地形,它需要处理多分辨率LOD下的水密性问题,已有相关研究解决了约上百种情况,但本质上仍是查表操作。
4.2 体素地形的意义与展望
尽管在主流游戏引擎中应用不多,但体素化表达代表了一种可能性:完全动态、可任意破坏的地形。就像《我的世界》所展示的,它提供了无与伦比的自由度和交互性。
作为引擎开发者,了解这种前沿方向有助于建立更广泛的知识体系。虽然当前算法和性能尚不成熟,但值得保持关注。
五、为大地着色:材质与纹理 🎨
有了几何形状,下一步是为地形赋予逼真的表面细节。
5.1 材质混合与纹理数组
真实地表由多种材质混合而成。基础方法是使用 Splat Map:一张贴图的每个通道代表一种材质的权重,通过艺术家绘制来混合。
- 简单混合的问题: 直接Alpha混合会显得不真实,例如沙子像浮在石头上。
- 高度图混合技巧: 在混合时,引入高度图进行比较。高度较高的材质(如石头)权重衰减更慢,高度较低的材质(如沙子)更快消失,模拟出沙子填入石缝的效果。
- 避免硬边闪烁: 在过渡区引入一个小的偏置,让权重在阈值范围内*滑过渡,可以提升视觉稳定性和自然度。
当材质种类多达几十上百种时,使用 纹理数组 来管理。纹理数组的层与层之间独立,采样时需指定精确的索引,完美符合地表材质互不干扰的特性。
核心Shader逻辑(简化):
float4 weights = sampleSplatMap(uv);
float2 materialIndices[4] = decodeIndices(weights); // 从权重图中解码出材质索引
float4 finalColor = float4(0,0,0,0);
for (int i = 0; i < 4; i++) {
float4 texColor = textureArray.Sample(sampler, float3(uv, materialIndices[i]));
finalColor += texColor * weights[i];
}
5.2 虚拟纹理:性能救星
上述方法在每个像素进行多次纹理采样和混合,开销巨大。虚拟纹理 技术应运而生,它已成为现代地形渲染的主流。
- 核心思想: 只将当前视野所需的地表纹理数据加载到显存中,其他数据留在硬盘。
- 实现方式: 将整个超大地表纹理和混合结果,按照四叉树结构预计算成一系列固定大小(如128x128)的图块,并生成多级Mipmap。
- 运行时: 根据视野和LOD,确定需要哪些图块。通过一个“页表”管理逻辑图块到物理显存中“缓存”的映射。需要的图块被实时烘焙(执行材质混合)并载入缓存,不需要的则被置换出去。
- 优势:
- 显存占用有理论上限。
- 运行时渲染每个像素只需1-2次纹理采样,极大降低了带宽和计算开销。
- 便于集成道路、贴花等系统(它们也可以被烘焙到虚拟纹理的图块中)。
前沿硬件助力:
- Direct Storage: 允许压缩数据从硬盘直达显存,在GPU端解压,减少CPU和内存带宽压力。
- DMA技术(如PS5): 实现数据从硬盘直接传输到显存,进一步缩短数据路径。
5.3 浮点数精度问题
当地形世界非常巨大时,会遇到 浮点数精度 问题。32位浮点数在数值极大或极小时,小数部分精度会严重丢失,导致顶点抖动。
- 解决方案:相机相对渲染
将摄像机位置设为世界坐标系原点,所有物体坐标都相对于摄像机计算。这样,在摄像机附*的物体都能享受到浮点数的小数精度,有效避免远处抖动。 - 应用: 这在虚幻、Unity等引擎中是标准做法。对于星际旅行等超大尺度游戏,这种技术至关重要。

六、环境的点缀:植被、道路与贴花 🌿🛣️

一个完整的世界不止有裸地。
- 植被渲染: 是一套专门的技术,涉及LOD(从精细网格到十字面片到公告牌)、批量渲染等。SpeedTree是业界著名的中间件。
- 装饰物: 指草地、灌木、碎石等小物体,通常用简单网格或面片表达,需要处理与视点相关的旋转问题以避免“旋转草”。
- 道路系统: 基于样条曲线生成,需要处理路面纹理、与地形的切割(挖填方)以及交叉口等复杂情况。
- 贴花系统: 用于表现弹孔、血迹、污渍等临时性细节,可以叠加在任意表面上,常配合法线贴图或视差贴图增强立体感。
这些元素通常都可以整合到 虚拟纹理 的烘焙流程中,或者通过各自的渲染管线进行合成。
总结与回顾 🎯
本节课中,我们一起学*了游戏引擎中自然世界渲染的第一部分——地形渲染。
- 我们从基础的高度场和LOD概念出发,理解了大规模地形渲染的挑战。
- 探讨了两种主要的自适应细分方案:基于二叉树的三角形细分和更主流的基于四叉树的地块管理,并解决了关键的 T-junction 裂缝问题。
- 介绍了利用现代GPU进行实时细分的技术演进,以及由此实现的动态地形效果。
- 展望了更自由的体素化地形表达方式。
- 深入研究了地表的材质混合技术,并引出了解决性能瓶颈的终极方案——虚拟纹理。
- 提到了大规模世界中的浮点数精度问题及其解决方案。
- 简要介绍了构成丰富环境的其他元素:植被、道路和贴花。
通过以上内容,你已经掌握了成为“大地之神”的基础知识,理解了如何从数据和算法出发,在虚拟世界中构建出坚实而美丽的地表。下节课,我们将目光投向天空,学*如何渲染“天公之神”的领域——大气与云朵。


游戏中地形大气和云的渲染(下) | GAMES104-现代游戏引擎:从入门到实践 - P1:GAMES104_Lecture06_Part2 - GAMES-Webinar - BV1i3411T7QL
概述
在本节课中,我们将深入探讨游戏中地形大气和云的渲染技术,包括大气模型、云的生成和渲染方法等。
大气渲染
大气模型
大气模型主要描述了光线在大气中的传播过程,包括吸收、散射和反射等。以下是大气模型的核心概念:
- 参与介质 (Participating Media): 指大气中的气体分子和气溶胶等物质,它们会影响光线的传播。
- 辐射传递函数 (Radiative Transfer Function, RTF): 描述光线在大气中的传播过程,包括吸收、散射和反射等。
- 体积渲染方程 (Volume Rendering Equation, VRE): 描述从观察者视角看到的场景,包括透射率和散射函数。
大气散射
大气散射主要描述了光线在大气中的散射过程,包括瑞利散射和米氏散射。
- 瑞利散射: 当散射粒子的尺寸远小于光波长时,散射强度与波长的四次方成反比。
- 米氏散射: 当散射粒子的尺寸与光波长相当或更大时,散射强度与波长无关。
大气吸收
大气吸收主要描述了光线在大气中被吸收的过程,例如臭氧和甲烷等物质会吸收长波长的光。
大气渲染算法

- 单次散射 (Single Scattering): 仅考虑光线与大气中的粒子的一次散射。
- 多次散射 (Multiple Scattering): 考虑光线与大气中粒子的多次散射,更真实地模拟大气效果。
- 预计算大气 (Precomputed Atmosphere): 预先计算大气中的透射率和散射函数,并在运行时进行查找。

云的生成和渲染
云的生成
云的生成主要使用噪声函数和纹理技术,例如 Perlin Noise 和 Simplex Noise。
云的渲染
云的渲染主要使用体积渲染技术,例如 Marching Cubes 算法。
总结


本节课介绍了游戏中地形大气和云的渲染技术,包括大气模型、云的生成和渲染方法等。通过学*这些技术,我们可以创建出更加真实和美观的游戏场景。

课程06:高动态范围成像与手机摄影 📱 | GAMES204-计算成像
在本节课中,我们将学*高动态范围(HDR)成像的基本原理及其在现代手机摄影中的应用。课程将涵盖从常见的图像格式、HDR图像融合方法,到动态范围压缩(色调映射)技术,并介绍两种经典的手机摄影算法。

概述:常见图像格式 📁
在深入HDR成像之前,我们首先需要了解日常生活中常见的图像格式。不同的格式具有不同的特性,适用于不同的场景。
以下是几种主要的图像格式及其特点:
- TIFF/TAG Image File Format:这是一种灵活的绘图模式,主要用于存储照片或艺术图像。它是一种高质量、无压缩的图像存储格式,可以显示上百万种颜色,并支持透明通道。其用途包括出版印刷、喷绘等需要高质量图像的场合。
- BMP (Bitmap):这也是未经压缩的纯位图格式。每个像素可以包含RGB三个通道,支持8位、14位等图像深度。
- JPEG (Joint Photographic Experts Group):这是日常生活中最常见的图像格式。它支持高级别的有损压缩,因此文件体积较小,便于在网络和网页上传输与加载。其编解码过程通常涉及离散余弦变换(DCT)和量化。
- GIF (Graphics Interchange Format):其最大特点是支持动态图像且文件体积小。尽管图像质量不高,但由于编解码简单、体积小且支持动画,至今仍有广泛应用。
- PNG (Portable Network Graphics):与JPEG不同,PNG采用无损压缩,并支持透明通道。这使其非常适合用作需要透明效果的图片编辑素材。缺点是文件体积通常比JPEG大。
- RAW:这是数字图像传感器记录的、未经任何加工的最原始信息。它保留了ISO设置、光圈、快门等元数据,以及最原始的RGGb图像数据,类似于模拟摄影时代的底片。常见格式有CR2、DNG、ARW等。
- EXR (OpenEXR):这是一种开放标准的高动态范围图像格式。它可以存储16位或32位浮点数格式的图像,通道数量也可灵活设置。它最早由工业光魔公司开发,用于电影制作,便于后期通过多通道调整来获得完美画面。
- EPS/PDF:这两种是文档和矢量图中常见的格式。EPS可直接记录线条、文字和贝塞尔曲线。PDF则是一种接*标准规格的格式,在操作、处理和显示方面均有优势。
- SVG (Scalable Vector Graphics):这是一种无损的矢量图形格式,在网页显示等方面具有优势。

HDR成像基础与多曝光融合 🌅



上一节我们介绍了各种图像格式,本节中我们来看看高动态范围成像的核心概念。动态范围简单来说就是最亮与最暗部分之间的比值。自然界的动态范围极大,而图像传感器的动态范围(通常为10-14位)则有限,这导致单张照片难以同时捕捉亮部和暗部的细节。




为何需要HDR?
传感器动态范围受限主要受两个因素影响:
- 满阱容量:像素能存储的电荷数量有限,与像素尺寸相关。公式可简化为:
满阱容量 ∝ 像素面积。 - 噪声与ADC量化:系统动态范围常受噪声下限和模数转换器量化位数的限制。
为了突破单张图像的限制,最直接有效的方法是多曝光融合。即拍摄一系列不同曝光时间的图像,将每张图像中曝光恰当的部分融合起来,合成一张具有更大动态范围的图像。


多曝光融合流程

完整的HDR成像流程通常包含以下步骤:


- 相机响应曲线标定:将图像转换到线性响应空间。对于RAW图像,响应基本是线性的;对于JPEG等经过处理的图像,需要先进行逆变换恢复线性。这可以通过拍摄标准灰度色卡并拟合像素值与真实亮度值的关系来实现。
- 图像对齐:由于手持拍摄可能存在抖动,需要对多张图像进行对齐。
- 权重融合:在线性空间内,对多张图像进行加权*均融合。权重函数通常设计为高斯形式,给予中间灰度值(曝光良好区域)更高的权重。对于彩色图像,每个通道使用相同的权重进行融合。
- 求解HDR图像:融合过程可以形式化为一个优化问题,目标是最小化合成的HDR图像与各输入图像之间的差异。考虑到人眼对亮度的感知是对数性的,通常在对数域进行优化。通过求解以下目标函数的极值,可以得到HDR图像:
argmin_logX Σ [w(Z_ij) * (logX_i - logΔt_j - logZ_ij)^2]
其中,X是待求的HDR辐照度,Z是观测到的像素值,Δt是曝光时间,w是权重函数。 - 绝对辐照度转换:通过一个参考辐照度值,可以将相对辐照度的HDR图像转换为具有绝对物理意义的辐照度图。
色调映射:将HDR显示在普通设备上 🖥️

上一节我们学*了如何获得高动态范围的图像,本节中我们来看看如何将其显示在动态范围有限的普通显示器上。这个过程称为色调映射。
直接对32位的HDR图像进行线性压缩到0-255会导致大量细节丢失。常见的色调映射方法分为两类:
- 全局色调映射:使用与像素强度相关的曲线(如Gamma曲线、Reinhard曲线、ACES)进行调整。这种方法速度快,能避免光晕,但容易破坏白*衡和丢失局部细节。
- 局部色调映射:像素的调整与其邻域的亮度分布相关。这种方法能保留更多细节,但计算更耗时,且可能引入噪声或光晕。
基于亮度分离的局部色调映射


一种有效的思路是将图像分解为基础层和细节层:
- 分离:使用滤波器(如双边滤波器)将HDR图像的亮度通道分离。低频的基础层包含大范围的亮度变化,高频的细节层包含纹理和边缘信息。
# 概念性伪代码 base_layer = bilateral_filter(hdr_image, sigma_spatial, sigma_range) detail_layer = hdr_image - base_layer - 压缩:仅对基础层进行动态范围压缩(例如使用对数压缩)。
- 合并:将压缩后的基础层与保留原样的细节层相加,再与原始的色度通道结合,得到最终图像。


这种“低频压缩,高频保留”的思想也被应用于HDR显示技术(如局部调光背光技术),通过在物理层面控制背光的动态范围来提升视觉体验。


手机摄影中的经典HDR算法 📸

了解了HDR的基本原理后,我们来看看它在手机摄影中的具体实现。以下是两种具有代表性的算法。

算法一:HDR+ (Burst Alignment)

这是Google Pixel团队较早采用的经典方法。
- 核心思想:快速连续拍摄多张(如13张)短曝光的RAW图像,选取其中一张作为参考帧。
- 关键步骤:
- 块匹配对齐:在其他帧中,通过金字塔搜索策略,在频域(如DCT域)寻找与参考帧中图像块相似的块。这类似于视频编码中的运动估计。
- 3D降噪与融合:将对齐后的相似块在三维空间(空间+帧间)进行*均融合,有效抑制噪声。其思想与BM3D降噪算法类似。
- 标准ISP处理:将融合后的高位图像送入标准的图像信号处理器管线,生成最终照片。
- 优点:避免了长曝光带来的运动模糊。
- 挑战:在极暗光环境下,短曝光图像的噪声仍然显著。


算法二:Night Sight (基于长曝光的改进)

这是Google Pixel团队针对极暗光场景提出的改进方案。
- 核心思想:拍摄张数更少但曝光时间更长的RAW图像序列。
- 关键改进:
- 运动去模糊:专门处理长曝光带来的运动模糊。
- 自动白*衡:在极暗光下,颜色容易失真。该算法使用一个小型神经网络来校正白*衡,使色彩更真实。
- 融合与色调映射:结合新的局部色调映射算法,在抑制噪声的同时,更好地提亮暗部、保留亮部细节。
- 效果:相比HDR+,Night Sight在极暗光环境下能获得细节更丰富、色彩更准确、噪声更低的照片。


总结与作业说明 📚
本节课我们一起学*了高动态范围成像的全流程。我们从认识图像格式开始,理解了HDR成像的必要性,掌握了通过多曝光融合获取HDR图像的方法,并学*了如何通过色调映射技术将HDR图像显示在普通设备上。最后,我们分析了HDR+和Night Sight这两种在手机摄影中落地的经典算法,看到了从短曝光融合到长曝光结合先进处理技术的发展脉络。
关于课程作业的说明:
- 第一次作业:实现一个基础的图像信号处理器管线。给出的参数仅为初始值,需要大家仔细调整(如黑电*可调至小数位),以追求最佳的视觉效果。提交时请在系统内上传压缩版的报告。
- 第二次作业:将在第一次作业基础上延伸,实现更高级的算法(如复杂的去噪算法BM3D或Non-local Means),从而构建一个效果更优的ISP管线。
- 后续课程:本节之后将进入光学部分,讲解像差分析、点扩散函数设计等。随后将覆盖解调算法,并布置相应的作业。

希望大家通过实践,深入理解计算成像的技术原理与应用前景。


07.VR/AR主流显示技术概要 | GAMES204-计算成像 - P1 - GAMES-Webinar - BV18g411r7Fn



概述
在本节课中,我们将探讨VR/AR领域的显示技术,包括其基本原理、应用以及未来发展趋势。
显示技术概述
显示技术定义


显示技术是将光信息以符号、文字、图形、图像等方式呈现出来的技术。


显示技术发展历程
- 早期显示技术:皮影戏、走马灯等。
- 现代显示技术:CRT、LCD、OLED、投影机等。
常见显示技术
- LCD(液晶显示):利用液晶分子的光学性质实现显示。
- OLED(有机发光二极管):自发光显示,具有广视角、高对比度、低功耗等特点。
- 投影机:利用光学原理将图像投射到屏幕上。
VR/AR显示技术
VR显示技术
- VR显示技术定义:通过头戴式显示器等设备,将用户沉浸在虚拟环境中。
- VR显示技术原理:利用光学原理和图像处理技术,将虚拟图像投射到用户眼前。
- VR显示技术应用:游戏、教育、医疗等领域。

AR显示技术



- AR显示技术定义:将虚拟信息叠加到现实世界中。
- AR显示技术原理:利用摄像头、传感器等设备,获取现实世界信息,并将虚拟信息叠加到现实世界中。
- AR显示技术应用:导航、购物、游戏等领域。
未来发展趋势
- 全息显示:利用衍射波动光学实现高分辨率、高对比度、深度信息显示。
- 超表面显示:利用超表面技术实现更小、更灵活的显示器件。
- 计算全息:利用计算方法实现全息显示。
总结

本节课介绍了VR/AR领域的显示技术,包括其基本原理、应用以及未来发展趋势。随着技术的不断发展,VR/AR显示技术将更加成熟,为用户带来更加沉浸式的体验。

🎮 课程07:游戏中的渲染管线、后处理与其他一切 | GAMES104-现代游戏引擎:从入门到实践
在本节课中,我们将学*游戏渲染的收尾部分。我们将探讨环境光遮蔽、雾效、抗锯齿等重要的视觉增强技术,并深入了解现代游戏引擎的渲染管线架构。这些技术共同作用,将基础的几何与光照场景,转化为具有电影感、沉浸感的最终画面。
🌫️ 环境光遮蔽
上一节我们介绍了天空、大气与云的渲染,构建了宏观的光照环境。本节中,我们来看看一个影响微观立体感的重要技术:环境光遮蔽。
环境光遮蔽用于模拟物体表面因周围几何结构遮挡环境光而产生的柔和阴影。这种效果能极大地增强场景的深度感和真实感。
预计算AO
最初,AO通过离线烘焙实现。艺术家在建模软件中为模型(如角色)生成一张AO贴图。这张图记录了模型自身几何细节(如皱纹、褶皱)造成的遮挡关系。
核心思想:用空间(存储烘焙好的贴图)换取实时渲染的时间。
实时屏幕空间AO
对于动态场景,物体间的相互遮挡关系无法预烘焙。于是,屏幕空间环境光遮蔽技术应运而生。
核心思想:利用当前帧的深度缓冲区信息,在屏幕空间估算每个像素点的遮挡情况。
以下是SSAO的基本计算步骤:
- 采样:对于屏幕上的每个像素,以其对应的三维空间点为中心,在法线方向的半球体内随机生成若干采样点。
- 可见性测试:将这些采样点投影回屏幕空间,获取其在深度缓冲区中的深度值。
- 比较与计算:如果采样点的深度值比当前像素的深度值更远(即被其他几何体遮挡),则计入遮挡。最终,可见的采样点比例决定了该点的AO强度。
一个简化的计算公式如下:
AO = 1.0 - (遮挡采样点数 / 总采样点数)
早期的SSAO算法存在一些问题,例如未考虑法线方向,导致*面也会产生AO。后续的改进算法,如HBAO和GTAO,引入了更精确的半球面采样、距离衰减以及基于法线的权重,得到了更接*真实物理(Ground Truth)的结果。
GTAO的贡献:它不仅计算了单次散射的遮挡,还通过一个拟合的多项式,根据AO值*似估算了光线在遮挡区域多次反弹后的结果,使得AO效果带有周围环境的色彩倾向,更加真实。
🌁 雾效
接下来,我们学*如何为场景添加大气层次感——雾效。
基础雾效:深度雾与高度雾
最简单的雾效基于像素到相机的距离(深度)进行计算。
- 线性雾:雾的浓度随距离线性增加。
- 指数雾:雾的浓度随距离呈指数增长,效果更自然。
高度雾则模拟了雾沉积在特定高度以下的现象(如山谷中的晨雾)。其计算需要沿视线进行积分,以考虑从观察点到目标点之间雾浓度的变化。
体积雾
现代游戏追求具有体积感的雾效,例如上帝光柱。这需要将整个视锥体空间进行体素化,并在每个体素中计算光的散射与吸收。
核心思想:与上一课计算大气散射的原理类似,通过步进积分模拟光在参与性介质中的传播。
在工程实现中,通常会创建一个3D纹理来存储中间计算结果。这个纹理的分辨率设计很有讲究,其XY轴分辨率常与屏幕长宽比保持一致,以确保在屏幕空间采样的一致性。

🔍 抗锯齿

由于屏幕像素有限,对连续的高频信号(如几何边缘、高光)进行采样时,会产生锯齿状的走样现象。抗锯齿技术旨在*滑这些边缘。
超采样抗锯齿
最直接的方法是提高采样率。
- SSAA:以更高分辨率渲染整个场景,然后下采样到目标分辨率。效果最好,但性能开销巨大。
- MSAA:主要在几何边缘进行多重采样。硬件广泛支持,在三角形密度不高时效率很好。但在现代高密度几何场景中可能失效。
后处理抗锯齿
这类算法对渲染完成的图像进行处理,不增加几何渲染开销。
FXAA:一种快速的*似抗锯齿算法。
- 边缘检测:将图像转换到亮度空间,通过检测相邻像素的亮度差来识别边缘。
- 方向判断:判断边缘是水*方向还是垂直方向。
- 边缘端点查找:沿边缘垂直方向寻找颜色一致的端点。
- 混合:根据像素在边缘上的位置,对颜色进行混合。它利用了纹理采样的双线性插值特性,通过微调采样坐标来实现混合。
TAA:时域抗锯齿,利用前一帧的信息。
- 运动向量:渲染时生成每个像素的运动向量,记录其从上一帧到当前帧的位置变化。
- 历史帧混合:根据运动向量,找到当前像素在上一帧中的对应颜色,并与当前帧颜色进行加权混合。
- 优势:能有效*滑高频闪烁。但可能产生重影,需要通过颜色差异、运动幅度等条件来抑制。
🎨 后处理
后处理是渲染管线的最后一步,相当于画面的“美颜滤镜”,对最终视觉风格至关重要。
Bloom(泛光)
模拟强光光源周围的光晕效果,增强视觉冲击力。

实现步骤:
- 提取高光:根据亮度阈值,从渲染图像中提取出高亮区域。
- 多次高斯模糊:对高亮区域进行高斯模糊。为了高效获得大范围模糊,常采用金字塔方法:先对图像进行下采样,在低分辨率层进行模糊,再上采样并与上一层混合,重复此过程。
- 叠加:将模糊后的光晕图像叠加到原始图像上。
Tone Mapping(色调映射)
将渲染器输出的高动态范围亮度值,映射到显示设备所能显示的低动态范围。

核心问题:真实世界亮度范围极大,而屏幕显示范围有限。直接截断会导致亮部过曝、暗部死黑。
解决方案:使用一条映射曲线。行业常用的曲线包括:
- Filmic曲线:旨在让游戏画面具有电影感。
- ACES曲线:由电影艺术与科学学院推出,已成为新的行业标准,能更好地适配不同显示设备。
Color Grading(颜色分级)
通过一个查找表,对画面的整体色调、对比度、饱和度等进行艺术化调整。
LUT:本质上是一个3D纹理,定义了从输入颜色到输出颜色的映射关系。艺术家可以在专业软件中调试出想要的风格,导出为LUT文件,游戏运行时直接应用,实现高效的风格化渲染。
⚙️ 渲染管线
最后,我们来探讨如何将上述所有技术有序地组织起来,这就是渲染管线。
前向渲染
最基本的管线。遍历每个光源,对每个受该光源影响的物体进行着色计算。透明物体需要在不透明物体之后,并按从远到*的顺序绘制。
延迟渲染
现代主流管线之一。
- 几何通道:将所有物体的几何信息(位置、法线、材质属性等)渲染到一组缓冲区中。
- 光照通道:利用G-Buffer中的信息,在屏幕空间进行光照计算。
优点:能高效处理大量光源,光照计算复杂度与光源数量和解像度相关,与场景复杂度无关。
基于Tile/Cluster的渲染
为了进一步优化,将屏幕或视锥体空间划分为许多小块。
- Tile-Based:将屏幕分块,为每个Tile计算一个受哪些光源影响的列表,仅对这些光源进行计算。
- Cluster-Based:将视锥体在深度方向也进行划分,形成3D的簇,光照剔除更加精确。
可见性缓冲区
一个新兴的前沿方向。它不在G-Buffer中存储材质属性,而是存储几何ID和重心坐标。在着色阶段,通过索引直接获取原始的顶点和材质数据。
优势:特别适合几何极度复杂的场景,避免了传统G-Buffer的带宽和存储开销,并与Mesh Shader等新硬件特性结合更好。
渲染图
管理复杂渲染管线的架构。它将渲染过程中的每个步骤(如阴影计算、AO、后处理)抽象为节点,节点之间的数据依赖构成一个有向无环图。
价值:系统可以自动管理资源生命周期、优化内存复用、理清执行顺序,极大降低了复杂渲染管线开发和维护的难度。
📺 最终呈现:垂直同步
渲染完成的图像需要提交到屏幕。为了避免画面撕裂,通常需要开启垂直同步,等待显示器的刷新信号再进行帧缓冲区的交换。可变刷新率技术则让显示器的刷新率与游戏的帧率动态同步,以提供更流畅的体验。


本节课中我们一起学*了构成游戏最终画面的多项关键技术:从增强立体感的AO,到营造氛围的雾效,再到消除瑕疵的抗锯齿,以及赋予画面风格的后处理滤镜。最后,我们梳理了如何通过不同的渲染管线将这些技术高效、有序地组织起来。理解这些内容,就掌握了现代游戏渲染从基础到屏幕的完整知识链条。

08. 成像工具箱:透镜与像差 | GAMES204-计算成像 - P1 - GAMES-Webinar - BV1md4y1G71q

概述
在本节课中,我们将学*成像工具箱中的透镜与像差。我们将探讨透镜的成像原理、像差类型及其对图像质量的影响,并介绍一些校正像差的方法。
透镜成像原理
薄透镜模型
透镜成像可以通过薄透镜模型来简化计算。薄透镜模型假设透镜非常薄,光线在透镜内部传播时可以*似为直线。
公式:
其中,\(s\) 是物距,\(p\) 是像距,\(f\) 是焦距。
*轴*似
在*轴*似下,光线与光轴的夹角非常小,可以忽略不计。这使得光线传播路径的计算更加简单。
折射定律
折射定律描述了光线从一种介质进入另一种介质时,入射角和折射角之间的关系。
公式:
其中,\(n_1\) 和 \(n_2\) 分别是两种介质的折射率,\(\theta_1\) 和 \(\theta_2\) 分别是入射角和折射角。
像差
像差是指透镜成像过程中产生的误差,导致图像质量下降。
球差
球差是由于透镜表面曲率不完美导致的像差。球差会导致图像边缘模糊。
色差
色差是由于不同波长的光线在透镜中折射率不同导致的像差。色差会导致图像出现彩色边缘。

会差

会差是由于透镜对不同方向的光线聚焦位置不同导致的像差。会差会导致图像出现彗星状或星芒状图案。
畸变
畸变是指图像形状发生变形,例如桶形畸变或枕形畸变。
像差校正
非球面透镜
非球面透镜可以校正球差和色差。
复合透镜
复合透镜可以校正多种像差。
算法校正
算法校正可以通过图像处理算法来校正像差。
总结

在本节课中,我们学*了透镜成像原理、像差类型及其对图像质量的影响,并介绍了一些校正像差的方法。了解像差及其校正方法对于提高图像质量至关重要。

08. 游戏引擎的动画技术基础(上) 🎬 | GAMES104-现代游戏引擎:从入门到实践
在本节课中,我们将要学*游戏引擎动画技术的基础知识。我们将从动画的起源和基本原理开始,逐步深入到2D动画技术,并重点讲解3D动画的核心——蒙皮动画的数学基础。通过本节课,你将能够理解动画系统的基本构成和实现原理。
概述:动画的起源与挑战
人类自古以来就对动态的事物充满兴趣。早期人类通过绘画记录动态,例如岩石壁画和陶器上的连续动作图案。现代动画技术的基础是视觉暂留现象,即影像在人眼中会残留约1/24秒。这是电影、电视和游戏动画的理论基础。
早期动画的尝试包括“西洋景”和手翻书动画。游戏动画的理论和工具很大程度上借鉴自电影行业,从迪士尼的2D动画,到《侏罗纪公园》的计算机生成图像(CGI),再到完全由游戏引擎实时渲染的动画电影《ZUI》,技术不断演进。
在游戏引擎中实现动画面临独特挑战:
- 交互性:游戏动画需要实时响应用户输入和游戏环境的变化,例如角色撞墙或被怪物攻击。
- 实时性:所有动画计算必须在约30毫秒(1/30秒)内完成,同时还需兼顾渲染、物理、AI等其他系统。
- 数据与性能:动画数据量巨大,高效的数据存取和计算是性能关键。
- 真实感:对角色动作的自然度和真实感要求越来越高,包括动作融合、物理模拟和精细的面部表情。
本节课(上半部分)将涵盖动画系统的基础技术,学完后你将能够构建一个简单的带动画的游戏。课程内容分为:
- 2D动画技术
- 3D动画技术概述
- 蒙皮动画的数学与实现细节
- 动画数据处理(压缩)
- 动画生产管线简介
下节课(下半部分)将介绍更高级的技术,如动画混合、反向动力学(IK)、动画状态机等。
2D动画技术 🖼️
2D动画技术是游戏引擎动画的鼻祖,至今仍在许多类型的游戏中广泛应用。
精灵动画 (Sprite Animation)
精灵动画的原理非常简单:将角色的每个动作帧绘制成单独的图片,然后在游戏中循环播放这些图片序列。
// 伪代码示例:播放精灵动画序列
for each frame in animation_frames:
draw_sprite(frame)
wait(frame_duration)
早期的经典游戏,如《超级马里奥》,都使用了这种技术。即使在高端的3D游戏中,精灵动画也并未消失,例如在粒子系统中,烟雾、爆炸等效果常使用序列帧图片来模拟动态。
Live2D 技术
Live2D是一种更高级的2D动画技术,广泛应用于二次元风格的游戏中。它的核心思想是将角色拆分为多个图元(如头发、眼睛、眉毛),然后通过对这些图元进行旋转、缩放和网格变形来驱动动画。
以下是Live2D的工作流程:
- 图元拆分:将角色分解为多个独立的图片元素。
- 层次与深度:为每个图元设置深度值,定义它们之间的前后遮挡关系。
- 控制网格:为每个图元生成一个控制网格,艺术家可以通过移动网格上的控制点来使图元变形。
- 关键帧动画:艺术家为不同的姿态设置关键帧,系统会在关键帧之间进行插值,形成流畅的动画。
Live2D技术赋予了艺术家巨大的创作空间,且无需理解复杂的3D数学概念,就能创造出鲜活生动的2D角色动画。
3D动画技术概述 🤖
从2D过渡到3D,动画技术变得更加复杂。我们首先需要理解一个核心概念:自由度。
自由度 (Degrees of Freedom, DOF)
一个刚体在三维空间中的运动有六个自由度:
- 3个*移自由度:沿X、Y、Z轴的移动。
- 3个旋转自由度:绕X、Y、Z轴的旋转。
因此,常说的“6自由度”VR设备,就是指用户可以在虚拟世界中自由移动和旋转头部。
3D动画的几种形式
上一节我们介绍了2D动画,本节中我们来看看3D游戏中常见的几种动画形式。
-
刚体层次动画 (Rigid Hierarchical Animation)
类似于皮影戏,将角色的每个关节作为可动的刚体,连接成树状层次结构。早期3D游戏如《生化危机》就使用了这种技术,但缺点是关节处的模型容易发生穿插。 -
顶点动画 (Vertex Animation)
直接存储模型每个顶点随时间变化的位置(和法线)。这种方法数据量巨大,但非常适合表现柔体变形,如飘扬的旗帜、水流或布料模拟。数据常以纹理贴图的形式存储。 -
变形目标动画 (Morph Target Animation)
存储模型在不同表情或形状下的多个“目标”状态(如笑、哭、胖、瘦),通过在目标之间插值来实现*滑变形。这在捏脸系统和面部动画中非常常用。 -
蒙皮动画 (Skinning Animation)
这是现代3D游戏中最核心的角色动画技术。它结合了骨骼(刚体层次)和皮肤(网格模型)。每个皮肤顶点可以受到多根骨骼的影响,并分配不同的权重。当骨骼运动时,顶点位置由所有影响它的骨骼共同决定,从而产生*滑、自然的变形,避免穿插。 -
基于物理的动画 (Physics-based Animation)
通过物理模拟来驱动动画,例如布娃娃系统(Ragdoll)、衣物模拟、流体模拟等。它能产生非常真实且符合物理规律的效果,但计算成本较高。 -
反向动力学 (Inverse Kinematics, IK)
给定一个目标点(如手要抓取的位置),IK系统会自动计算角色链(如手臂)中各个关节应该如何旋转才能自然到达该目标。这在角色与环境交互时至关重要。
动画的创造主要来自两种方式:手K动画(动画师在软件中手动设置关键帧)和动作捕捉(记录真实演员的动作数据)。
蒙皮动画的数学基础 🧮
理解了3D动画的多种形式后,我们将深入探讨其中最核心的蒙皮动画的实现细节。这是所有高级动画技术的基础。
三个关键空间
在计算动画时,我们需要清楚地区分三个空间坐标系:
- 模型空间 (Model Space):以模型自身为中心的坐标系,也称为局部空间。这是模型顶点的原始坐标。
- 世界空间 (World Space):整个游戏场景统一的全局坐标系。模型通过*移、旋转、缩放变换从模型空间转换到世界空间。
- 局部骨骼空间 (Local Bone Space):每根骨骼自身的坐标系。动画数据(如关节旋转)通常首先在这个空间中定义。通过从根骨骼开始,逐级将子骨骼的变换叠加到父骨骼上,最终可以得到骨骼在模型空间中的变换。
动画计算的核心,就是将顶点从模型空间,经过受骨骼影响的变换,最终定位到世界空间的过程。
骨骼与关节
在游戏引擎中,我们通常存储和操作的是关节,而非视觉上的“骨头”。关节定义了旋转和*移,而两个关节之间则形成了一段骨骼。对于人形角色,行业有标准骨骼结构(如盆骨为根,衍生出脊椎和双腿),这便于动画资源的复用和交换。
表达旋转:从欧拉角到四元数
旋转是动画中最核心的操作。在3D空间中表达旋转并非易事。
欧拉角 (Euler Angles)
用三个绕坐标轴(X, Y, Z)的旋转角度(如(α, β, γ))来描述方向。它非常直观(类似飞机的俯仰、偏航、滚转),但存在严重问题:
- 万向节死锁:当第二个旋转角为±90°时,第一和第三个旋转轴重合,丢失一个自由度。
- 插值困难:两个欧拉角之间的简单线性插值可能产生错误的旋转路径。
- 叠加复杂:组合多个旋转时计算繁琐且不直观。
四元数 (Quaternion)
四元数是一个四维超复数,形式为 q = a + bi + cj + dk,其中a为实部,(b, c, d)为虚部,i, j, k为满足特定乘法规则的虚数单位。
- 单位四元数可以完美表示3D空间中的任意旋转。
- 旋转叠加:对点
p先后进行旋转q1和q2,等价于用四元数 q = q2 * q1 进行一次旋转。 - 球面线性插值:在两个四元数之间进行插值(
slerp),可以得到*滑的旋转过渡。 - 绕任意轴旋转:给定旋转轴
(x, y, z)和角度θ,对应的四元数为:q = [cos(θ/2), x*sin(θ/2), y*sin(θ/2), z*sin(θ/2)]
四元数解决了欧拉角的几乎所有缺陷,是游戏引擎中存储和计算旋转的标准工具。
蒙皮变换公式
蒙皮动画的最终计算在着色器中进行。对于每个顶点,其最终的世界空间位置由影响它的多根骨骼共同决定:
// 伪代码公式:顶点蒙皮计算
final_position = vec3(0, 0, 0);
for each bone that influences this vertex:
// 1. 将顶点从模型空间变换到当前骨骼的局部空间
local_pos = inverse(bone_bind_pose_matrix) * vertex_model_position;
// 2. 将局部空间的顶点用当前骨骼的动画矩阵变换到模型空间
animated_pos = bone_animation_matrix * local_pos;
// 3. 加权累加
final_position += animated_pos * vertex_weight_for_this_bone;
// 4. 将最终模型空间位置变换到世界空间
world_position = model_to_world_matrix * final_position;
其中:
- bone_bind_pose_matrix:骨骼在绑定姿势(T-Pose或A-Pose)下的变换矩阵。
- bone_animation_matrix:骨骼在当前动画帧下的变换矩阵。
- vertex_weight_for_this_bone:该顶点受此骨骼影响的权重,所有权重之和为1。
总结 📚
本节课我们一起学*了游戏引擎动画技术的基础。
- 我们回顾了动画的历史和游戏动画面临的独特挑战。
- 我们探讨了2D动画技术,包括经典的精灵动画和更先进的Live2D系统。
- 我们概述了3D动画的多种形式,并指出蒙皮动画是现代角色动画的核心。
- 我们深入讲解了蒙皮动画的数学基础,重点理解了三个空间坐标系的区别,以及用四元数表达和计算3D旋转的优越性。
掌握这些基础概念和原理,是理解更复杂的动画混合、IK、状态机等技术的前提。在下节课中,我们将基于这些基础,继续探索现代游戏引擎中的高级动画系统。


课程内容来源:GAMES104《现代游戏引擎:从入门到实践》第八讲(上)

08.游戏引擎的动画技术基础(下) | GAMES104-现代游戏引擎:从入门到实践 - P1:GAMES104_Lecture8 -02 - GAMES-Webinar - BV1fF411j7hA
概述
在本节课中,我们将深入探讨游戏引擎中的动画技术,包括关节姿态、蒙皮动画、动画剪辑、插值和压缩等概念。
关节姿态
关节姿态由旋转、*移和缩放组成。旋转通常使用四元数表示,*移表示为空间中的*移向量,缩放表示为沿X、Y、Z轴的比例。
蒙皮动画
蒙皮动画通过将顶点与多个关节关联来实现。每个顶点可以与最多四个关节关联,并通过加权*均计算新的位置。
动画剪辑
动画剪辑是一组动画帧,通常存储为关键帧。在渲染时,通过插值算法在关键帧之间进行插值,以生成*滑的动画。
插值
插值算法用于在关键帧之间生成中间帧。对于旋转,可以使用四元数插值或Slerp插值。对于位移和缩放,可以使用线性插值。
压缩
动画数据量通常很大,因此需要进行压缩。压缩方法包括关键帧、曲线逼*和定点数表示。
动画制作流程
- 构建mesh
- 创建skeleton
- 创建skinny
- 创建动画剪辑
- 导出动画数据


总结
本节课介绍了游戏引擎中的动画技术,包括关节姿态、蒙皮动画、动画剪辑、插值和压缩等概念。这些技术对于实现逼真的动画至关重要。


09. 成像工具箱:波前分析/PSF/MTF和镜头组件 | GAMES204-计算成像 - P1 - GAMES-Webinar - BV1de4y1C7st
概述


在本节课中,我们将学*成像工具箱中的关键概念,包括波前分析、点扩散函数(PSF)、调制传递函数(MTF)以及镜头组件。我们将深入了解这些概念如何影响成像质量,并探讨它们在光学设计和成像系统中的应用。


波前分析

波前分析是一种用于分析光学系统成像质量的技术。它涉及以下概念:
- 点扩散函数(PSF):描述单个点光源通过光学系统后形成的图像。
- 线扩散函数(LSF):PSF在垂直方向上的投影,描述单个线光源通过光学系统后形成的图像。
- 边缘扩展函数(ESF):描述光学系统对边缘的响应。
- 光学传递函数(OTF):描述光学系统对空间频率的响应。
公式:
- PSF = OTF * 2π
- MTF = OTF(0)
点扩散函数(PSF)

PSF是波前分析的核心概念之一。它描述了单个点光源通过光学系统后形成的图像。PSF的大小和形状取决于光学系统的设计、材料、加工精度等因素。

公式:
- PSF = OTF * 2π

调制传递函数(MTF)

MTF是OTF在频率域的表示,它描述了光学系统对空间频率的响应。MTF的值越高,表示光学系统的成像质量越好。
公式:

- MTF = OTF(0)


镜头组件

镜头组件是构成光学系统的基本单元。以下是一些常见的镜头组件:

- 透镜:用于聚焦光线。
- 棱镜:用于改变光路。
- 滤光片:用于过滤特定波长的光线。
- 光圈:用于控制光量。
应用


波前分析、PSF、MTF和镜头组件在以下领域有广泛应用:
- 光学设计:用于优化光学系统的设计,提高成像质量。
- 成像系统:用于评估成像系统的性能。
- 激光加工:用于控制激光束的形状和大小。
- 生物医学成像:用于提高成像系统的分辨率。
总结

在本节课中,我们学*了成像工具箱中的关键概念,包括波前分析、PSF、MTF和镜头组件。我们了解到这些概念如何影响成像质量,并探讨了它们在光学设计和成像系统中的应用。希望这些知识能够帮助您更好地理解成像系统的工作原理。

🎬 课程09:高级动画技术:动画树、IK和表情动画
在本节课中,我们将深入探讨现代游戏引擎中的高级动画技术。我们将学*如何将多个动画片段流畅地混合在一起,如何使用反向动力学(IK)让角色与环境自然互动,以及如何实现生动的面部表情动画。这些技术是构建一个真实可信、活灵活现的游戏角色的关键。
📊 动画混合技术
上一节我们介绍了基础的骨骼动画与蒙皮。本节中我们来看看如何将多个动画片段(Clips)*滑地混合,以实现角色动作的无缝过渡。
线性混合
动画混合的核心思想是线性插值。例如,一个角色从走到跑,我们可以根据其当前速度,在“走”和“跑”两个动画片段之间进行插值,而不是生硬地切换。
以下是计算混合权重的简单公式:
weight_walk = (speed_run - current_speed) / (speed_run - speed_walk)
weight_run = 1 - weight_walk
其中,speed_walk 和 speed_run 分别是走路和跑步动画对应的预设速度,current_speed 是角色当前的实际速度。确保 weight_walk + weight_run = 1。
混合空间
当控制变量不止一个时(如同时控制移动速度和方向),就需要使用混合空间。
- 一维混合空间:例如,用摇杆控制角色向左慢走、向左快走、站立、向右慢走、向右快走。这些动画片段在速度轴上可以不均匀分布。
- 二维混合空间:这是更常见的情况,例如用摇杆的X和Y轴控制角色向各个方向移动。动画师会制作多个方向(如前、后、左、右、斜向)的动画作为采样点。
以下是实现二维混合空间的一种方法:
- 将所有动画采样点置于二维坐标中。
- 使用德劳内三角剖分算法,将这些点连接成三角形网格。
- 对于任意输入的控制向量(即二维空间中的一个点),找到其所在的三角形。
- 使用该三角形三个顶点(即三个动画片段)的重心坐标进行插值,得到最终的动画姿势。
骨骼遮罩混合与叠加混合
有时我们只希望动画影响角色的特定部位。
- 骨骼遮罩混合:为动画指定一个骨骼遮罩,只让受影响的骨骼参与混合。例如,下半身播放走路动画,上半身同时播放挥手动画。
- 叠加混合:在基础动画之上,再叠加一个表示变化量的动画(通常是旋转或位移的偏移量)。例如,在角色各种姿势的基础上,叠加一个“点头”或“看向玩家”的动画。但需注意过度叠加可能导致关节旋转异常。
🌳 动画状态机与动画树
仅仅有混合还不够,我们需要一个系统来管理角色动画状态之间的逻辑切换与复杂组合。
动画状态机
动画状态机定义了角色动画的各种状态(节点)以及状态间的转换条件。例如,一个跳跃动作可以分解为“起跳”、“空中循环”、“落地”三个状态节点,并根据角色是否按下跳跃键、是否到达最高点、是否接触地面等条件进行切换。
每个转换可以定义过渡时间(如0.2秒)和过渡曲线(如线性或缓入缓出),以实现*滑的状态切换。
动画树
动画树是动画状态机更强大、更灵活的演进。它将动画混合表达为一棵节点树,最终输出一个动画姿势。
- 节点类型:
- 叶子节点:通常是原始的动画片段、混合空间或一个子状态机。
- 中间节点:执行混合操作的节点,如线性插值节点、叠加混合节点、骨骼遮罩节点等。
- 控制变量:动画树的灵魂。游戏逻辑(如玩家输入、角色血量、环境状态)通过修改变量(如
speed,health,is_jumping)来影响动画树中各个混合节点的权重,从而控制最终输出的动画。 - 嵌套与递归:动画树的强大之处在于其可嵌套性。一个状态机节点内部可以包含另一棵动画树,提供了极高的自定义能力。
通过动画树,设计师可以直观地搭建出极其复杂的角色动画行为逻辑。
🤖 反向动力学
之前我们讨论的都是前向动力学:从根骨骼开始逐级驱动子骨骼。而反向动力学则相反:给定末端效应器(如手或脚)的目标位置,反向求解整条骨骼链中各个关节应如何旋转。
两骨骼IK
这是最简单的情况,例如调整角色的脚踝位置使其踩踏地面。已知大腿骨长度 L1、小腿骨长度 L2 以及目标点 P,可以通过解三角形唯一确定膝关节的角度。但解空间是一个圆环,通常还需要一个参考向量(如角色面朝方向)来从两个可能的解中选出正确的一个。
多骨骼IK
对于更长的骨骼链(如整条手臂或脊柱),求解更为复杂。以下是两个经典算法:
- 循环坐标下降法:一种启发式迭代算法。从末端关节开始,将其向目标点方向旋转一小步;然后处理上一个关节,重复此过程;再从根关节向末端关节进行反向调整。多次迭代后,末端效应器将逐渐逼*目标点。此算法简单高效,易于加入关节约束。
- FABRIK算法:另一种迭代算法。分为向前和向后两个阶段。向前阶段从末端开始,将关节逐个拉向目标点;向后阶段从根关节开始,将关节逐个拉回原始位置。经过多次迭代收敛。其思想也易于结合关节角度限制。
雅可比矩阵与物理IK
对于多目标点约束(如攀岩时双手双脚都需要固定)或要求解更符合物理规律的运动,会使用更数学化的方法,如基于雅可比矩阵的数值优化方法,或基于物理的IK解法。这些方法计算量更大,但能产生更自然、更稳定的结果。
IK的挑战在于解的自然性(像人)、避免骨骼穿插、以及保持角色*衡。
😀 表情动画
让角色面部生动起来是提升沉浸感的关键。
动作编码系统与形变动画
电影工业广泛使用的FACS系统将人类表情分解为数十个动作单元。在游戏中,我们常用形变动画来实现。
- 形变动画:为每个AU制作一个形变目标(Morph Target),存储该表情下所有顶点相对于中性表情的偏移量。
- 混合:通过线性混合多个AU的形变目标,可以组合出复杂的表情。例如,混合“张嘴”和“闭眼”两个目标,可以得到“打哈欠”的表情。
- 结合骨骼动画:对于眼球转动、下颌开合等大范围运动,通常使用骨骼动画。细腻的皮肤褶皱、酒窝等则用形变动画表现。二者结合,并辅以法线贴图等材质变化,能创造出极其逼真的面部表情。
表情动画的重定向
与身体动画类似,我们也希望一套表情动画能用于不同脸型的角色。基本方法是将形变偏移量直接应用到目标模型上。但对于有明确接触约束的动作(如闭眼、抿嘴),需要额外的处理(如使用拉普拉斯变形)来保证上下眼睑、嘴唇等部位能正确闭合。
🔄 动画重定向
动画重定向旨在将一套动画(源动画)应用到骨骼结构或比例不同的角色(目标角色)上。
基本重定向
核心是保持骨骼间的相对旋转。将源动画中每一根骨骼相对于其绑定姿势的旋转,直接应用到目标角色的对应骨骼上。对于位移和缩放,则根据骨骼长度比例进行缩放。
挑战与优化
简单的重定向会导致问题,如脚部悬空或踩入地面(因腿长不同)。解决方案包括:
- 根据角色身高比例调整根骨骼的垂直位移。
- 在重定向后,对脚部等关键部位施加IK,将其约束在地面上。
- 对于拓扑结构不同的骨骼(如脊椎骨数量不同),可以通过在骨骼链上参数化映射来解决。
高质量的重定向还需要处理骨骼穿插、动作语义保持(如鼓掌时双手要能合拢)等问题,是一个活跃的研究领域。
📝 总结
本节课中我们一起学*了现代游戏引擎中三大高级动画技术:
- 动画混合与架构:我们掌握了通过混合空间、状态机和动画树,将大量动画素材组织起来,并受游戏逻辑驱动,形成连贯角色行为的方法。这是前向动力学的核心。
- 反向动力学:我们了解了IK如何让角色的动作与环境约束(如踩踏地面、抓握物体)相结合,大大增强了互动的真实感。CCD和FABRIK是实用的算法起点。
- 表情动画与重定向:我们学*了如何通过形变动画和骨骼动画创造生动的面部表情,以及如何通过重定向技术最大化动画资源的利用率,让不同体型、不同面貌的角色都能“动起来”。


掌握这些技术,你就拥有了构建一个专业级游戏角色动画系统的基础。每一项技术深入下去都有广阔的探索空间,希望本节课能为你打开这扇大门。

10. 成像过程的噪声分析 | GAMES204-计算成像 - P1 - GAMES-Webinar - BV1NV4y1u7xd


概述: 本节课将深入探讨成像过程中的噪声问题,分析噪声的形成过程、影响以及如何进行噪声标定和优化。
1. 噪声的类型

- 固定模式噪声 (FPN): 由传感器本身的缺陷引起,例如像素排列、电路设计等。
- 随机噪声:
- 光子噪声 (PN): 由光子到达传感器时的随机性引起。
- 暗电流噪声 (DCN): 由传感器内部的电子运动引起。
- 读出噪声 (RON): 由传感器读出电路的噪声引起。
- 量化噪声 (QN): 由模数转换过程中的量化误差引起。

2. 噪声的形成过程
- 光子到达传感器: 物体反射或发出的光子到达传感器,产生光电流。
- 暗电流: 传感器内部存在暗电流,会引入噪声。
- 光电转换: 光电流转换为模拟电压信号。
- 放大: 模拟电压信号经过放大器放大。
- 模数转换: 模拟电压信号转换为数字信号。
- 量化: 数字信号进行量化,引入量化噪声。
3. 噪声标定
- 暗电流标定: 通过拍摄全黑图像,计算暗电流的均值和方差。
- 读出噪声标定: 通过拍摄灰度图像,在不同灰度级别下计算读出噪声的均值和方差。
- 量化噪声标定: 通过测量ADC的分辨率,计算量化噪声的方差。
4. 噪声优化

- 降低曝光时间: 减少光子噪声。
- 降低ISO值: 减少读出噪声。
- 使用低噪声传感器: 选择低噪声的传感器。
- 图像融合: 将多张图像融合,降低噪声。

5. 应用实例:HDR 合成


- 噪声模型: 建立噪声模型,考虑噪声对图像质量的影响。
- 最优位置: 根据噪声模型,选择最优的HDR合成位置,提高图像质量。

总结: 本节课深入探讨了成像过程中的噪声问题,分析了噪声的类型、形成过程、标定和优化方法。通过学*本节课,可以更好地理解噪声对图像质量的影响,并采取相应的措施降低噪声,提高图像质量。

课程10:游戏引擎中物理系统的基础理论和算法 🎮 | GAMES104-现代游戏引擎:从入门到实践
在本节课中,我们将学*游戏引擎物理系统的基础理论和核心算法。物理系统是构建游戏世界真实感与交互性的基石,理解其基本概念对于使用或开发游戏引擎至关重要。
课程概述与社区更新
在开始今天的课程之前,先同步一下社区动态和小引擎的更新计划。
我们计划在未来开源 MetaPasser,目前暂未开源是担心其编译过程过于复杂,增加初学者的学*负担。我们会在小引擎架构更稳定后推进此事。
关于小引擎的 Wiki 文档,我们会持续编写和更新,以帮助大家更好地理解其约3万行代码的结构。
有同学提议用专业视角分析游戏中的穿帮镜头,但作为开发者,我们深知制作复杂系统的艰辛,因此更关注整体游戏性,而非个别瑕疵。
上周发起了为小引擎征名的活动,收到了许多有趣的建议。课程组正在核查名称的可用性,之后会将候选名单及寓意公布在社区,由大家投票决定。
明天小引擎将有一次较大更新。首先,感谢社区贡献者,为引擎加入了 FXAA 抗锯齿功能。其次,我们重构了引擎框架,简化了过于复杂的单例机制和组件系统,让生命周期管理更清晰。渲染管线也经过了大幅精简和优化,移除了冗余概念,目前提供了一个更简单的 RHI(渲染硬件接口)版本。此外,编辑器和引擎的输入处理等分层也变得更加清晰。还有社区贡献的代码编辑辅助工具,提升了在不同 IDE 中的开发体验。希望新版本能让代码更易读、更易上手。
我们对 Pilot 这个小引擎已有感情,其图标也很喜欢,但最终会采用社区投票选出的新名字。
物理系统的重要性
上一节我们同步了社区和引擎的进展,现在正式进入课程内容。我们从动画模块进入到物理模块。
物理系统至关重要,它是玩家对游戏世界产生直觉认知的核心。没有物理,玩家很难相信世界的真实性。角色的运动、与环境的互动都依赖物理。
物理能构建动态环境,直接影响游戏玩法。例如,一堵墙会改变玩家的视野和战术策略。
物理创造了真实、可交互的世界。例如,在 VR 游戏中,物理模拟能带来极强的沉浸感。
现代游戏引擎中的许多表现,如粒子、烟雾、水体和布料模拟,都高度依赖物理系统。
以上种种,都依赖于游戏内的物理系统或物理引擎。因其复杂性,我们计划用两节课讲解。本节课聚焦基础概念,让大家理解如何用物理语言描述世界,以及物理引擎的核心元素与关系。我们会点到一些底层算法,但初学者只需掌握基本概念,知道如何接入一个物理引擎即可。下节课将探讨更高级的主题,如角色控制、布娃娃系统、破坏模拟、载具系统以及前沿的位置动力学(PBD)方法。
物理世界的基本对象:Actor 与 Shape
首先,我们需要理解物理世界中的基本对象,一般称为 Actor 或 Shape。
在游戏引擎中,我们实际上在两个世界中进行模拟:一个是渲染与动画表现的视觉世界;另一个是进行逻辑与物理模拟的“孪生”世界。物理 Actor 就存在于这个逻辑世界中。
物理 Actor 主要分为四类:
以下是各类 Actor 的详细介绍:
- 静态 Actor:在场景中固定不动的物体,如地面、墙壁。它们是游戏中数量最多的物理对象,构成了世界的基础碰撞体积。
- 动态 Actor:符合动力学原理的物体,如可以被推动的箱子。它们会受到力、重力、摩擦力的影响,产生运动。
- 触发器:一种特殊的 Actor,用于游戏逻辑触发。当其他 Actor 进入其范围时,会触发特定事件(如开门)。它不参与物理解算,只负责发送消息。
- 运动学 Actor:由游戏逻辑(如动画)直接驱动位置和旋转的物体,其运动不遵循牛顿力学。它常用于需要精确控制的物体(如角色的某些骨骼),但因其“反物理”的特性,若处理不当,在与动态物体交互时可能产生巨大的力,导致物理模拟不稳定,是常见的 Bug 来源。
回顾一下,理解物理引擎首先要理解其处理的对象:静态物体构成世界基础;动态物体提供符合物理的运动;触发器连接游戏逻辑;运动学物体提供精确控制但需谨慎使用。
Actor 的形状:Shape
Actor 有一个关键属性:Shape(形状)。在物理计算中,为了高效进行碰撞检测等运算,我们通常使用解析形式简单的几何体来*似表示物体。
以下是几种常见的物理 Shape:
- 球体:由一个中心点和半径定义。求交计算最简单。
- 胶囊体:由两个半球体和一个圆柱体组成。常用于*似表示游戏中的角色。
- 长方体:由中心点、尺寸和朝向定义。应用广泛。
- 凸包:一个封闭的、没有凹陷部分的多面体。能表达更复杂的形状,但计算成本高于前三种。
- 三角形网格:用网格数据精确表示复杂形状(如整个建筑)。通常仅用于静态物体,因为动态碰撞检测计算量极大。
- 高度场:用于表示地形,是处理地面碰撞的高效方式。
每种 Shape 都有其适用场景。球体用于球类物体;胶囊体用于人形角色;长方体用于大多数规则物体;凸包用于较复杂的可破碎物体;三角形网格用于精细的静态场景;高度场专用于地形。
在游戏中使用 Shape 的原则是:用尽可能简单的形状进行*似包裹。能使用球体、胶囊体、长方体时,就不要使用更复杂的凸包或三角形网格,以优化性能。
Shape 的属性和物理材质
Shape 除了几何形状,还有其他重要属性。
质量与密度:每个 Shape 需要定义其质量或密度,这决定了物体受力后的运动惯性。物理引擎通常假设物体的质量是均匀分布的。
质心:物体的质量中心。在进行旋转模拟(特别是载具系统)时,质心的位置至关重要,它直接影响物体的稳定性。
物理材质:此处的物理材质与渲染中的 PBR 材质不同,它定义的是物体表面的物理交互属性。
物理材质主要定义两个属性:
- 摩擦力:物体表面粗糙程度的度量。
- 弹性:物体碰撞后恢复原状的能力,即反弹系数。
设置不同的物理材质,会使物体的行为(如滑动、弹跳)截然不同。一些引擎可能提供更多参数。
力与运动
有了物体(Actor/Shape),接下来需要让它们动起来,这就需要引入“力”。牛顿力学告诉我们,力是改变物体运动状态的原因。
游戏中最常用的两种力是:
- 持续力:如重力、拉力、摩擦力。它们在一段时间内持续作用,影响物体的运动。
- 冲量:在极短时间内施加的力,如爆炸冲击、撞击。它可以瞬间改变物体的动量。
这两种力本质相关:力乘以作用时间就等于冲量。
运动的数学描述:数值积分
在游戏世界中,我们用离散的数学方法来模拟连续的运动。
牛顿第一定律(匀速直线运动) 可以用以下公式描述:
- 速度不变:
V(t+Δt) = V(t) - 位置更新:
X(t+Δt) = X(t) + V * Δt
牛顿第二定律(力与加速度) 描述了更普遍的情况。当物体受外力作用时,其速度会改变。在变加速运动中,我们需要通过积分来计算速度和位置。
核心问题是:我们无法在计算机中求得连续运动的解析解,只能将时间切成小段(Δt),用数值积分的方法进行*似模拟。
以下是三种常见的数值积分方法:
-
显式欧拉法:用当前时刻的状态预估下一时刻。
- 公式:
V(t+Δt) = V(t) + a(t) * Δt;X(t+Δt) = X(t) + V(t) * Δt - 优点:简单直观。
- 缺点:稳定性差,容易导致能量不守恒,模拟可能发散(如轨道越来越远)。
- 公式:
-
隐式欧拉法:用未来时刻的状态反向推算当前。
- 优点:无条件稳定,能量会衰减但不会爆炸。
- 缺点:需要求解方程,计算复杂。
-
半隐式欧拉法(推荐):一种折中且高效稳定的方法。
- 步骤:
- 用当前受力计算加速度:
a(t) = F(t) / m - 用加速度更新速度:
V(t+Δt) = V(t) + a(t) * Δt - 用新速度更新位置:
X(t+Δt) = X(t) + V(t+Δt) * Δt
- 用当前受力计算加速度:
- 它假设在 Δt 时间内力保持不变,用更新后的速度来计算位移,在稳定性和计算成本之间取得了良好*衡,是游戏物理模拟中最常用的方法。
- 步骤:
通过数值积分,我们得以在离散的时间步长中模拟连续的物理世界。
刚体动力学基础
之前我们主要将物体视为质点,只考虑其位置移动。但真实物体还有旋转,这就需要引入刚体动力学。刚体假设物体内部各点相对位置不变。
描述刚体旋转涉及以下概念,它们与质点运动的概念有对应关系:
| 质点运动 | 刚体旋转 | 描述 |
|---|---|---|
| 位置 X | 姿态 Orientation | 物体的朝向,可用旋转矩阵或四元数表示。 |
| 速度 V | 角速度 ω | 一个向量,方向表示旋转轴,长度表示旋转快慢(弧度/秒)。 |
| 加速度 a | 角加速度 α | 角速度的变化率。 |
| 质量 m | 转动惯量 I | 一个 3x3 的张量,描述物体抵抗旋转改变的程度,取决于质量和形状分布。 |
| 动量 p = m·v | 角动量 L = I·ω | 旋转运动的“运动量”,在不受外力矩时守恒。 |
| 力 F | 力矩 τ | 引起旋转改变的原因,τ = r × F,其中 r 是力臂向量。 |
这些概念构成了刚体运动的基础方程。在实际使用中,物理引擎会根据你提供的 Shape 和密度自动计算转动惯量。开发者需要理解这些概念的对应关系,但通常无需手动推导复杂公式。
一个简单的例子是花样滑冰运动员:收拢手臂时,转动惯量减小,由于角动量守恒,角速度会增大,从而旋转得更快。
碰撞检测
物理对象要相互作用,必须检测它们是否接触,这就是碰撞检测。关闭碰撞检测,物体就会像幽灵一样穿过彼此。
现代物理引擎的碰撞检测通常分为两个阶段:
-
粗检测:快速筛选出可能发生碰撞的物体对,剔除明显不碰撞的物体。常用算法有:
- BVH:层次包围盒树。动态更新效率高,适合物体频繁移动的场景。
- Sort and Sweep:将物体的轴对齐包围盒投影到坐标轴上并排序。如果两个物体在所有轴上的投影区间都重叠,则它们可能碰撞。该方法对静态居多的场景效率极高。
-
精检测:对粗检测筛选出的物体对,进行精确的几何求交计算,并获取碰撞详细信息(碰撞点、法线、穿透深度)。这些信息对于物理响应和游戏反馈(如播放声音、特效)至关重要。
对于简单形状(球、胶囊、长方体),求交算法相对直接。对于复杂的凸包形状,则需要更高级的算法:
- GJK 算法:基于闵可夫斯基差概念的迭代算法。它将两个凸包是否相交的问题,转化为一个点(原点)是否在另一个凸包(两凸包的闵可夫斯基差)内的问题。该算法能快速收敛,得出是否碰撞的结论。
- 分离轴定理:该定理指出,两个凸包若不相交,则必定存在一条直线(或一个*面),能将它们在直线上的投影分开。算法通过检测两个凸包所有面的法向轴以及所有边的叉乘方向构成的潜在分离轴,来判断是否分离。只要找到一个分离轴,即可判定不相交。
这些算法思想优美且高效,是物理引擎的核心。在实际开发中,我们通常直接使用物理引擎提供的功能,但理解其原理有助于更好地使用和调试。
碰撞响应与约束求解
检测到碰撞后,需要解决碰撞,即让物体分开并做出符合物理规律的反应。
早期简单的方法是施加惩罚力,根据穿透深度施加一个反向力将物体推开。但这种方法容易导致力过大,使物体“炸飞”,显得不真实。
现代物理引擎更常使用约束求解的方法。它将物理问题转化为数学上的约束优化问题(借鉴拉格朗日力学)。当发生穿透时,求解器会尝试施加一系列小的冲量(而非单个大力),迭代地修正物体的速度和位置,直到满足“不穿透”等约束条件,并且误差在可接受范围内。
常用的迭代算法是高斯-赛德尔方法。求解器会设定最大迭代次数或误差阈值作为退出条件。这种方法能得到更稳定、更真实的碰撞效果。
物理查询
除了被动检测碰撞,游戏逻辑经常需要主动向物理世界提问,这就是物理查询。
主要有三种类型的查询:
- 射线检测:发射一条射线,检测沿途击中的物体。
RaycastMultiple:返回所有击中点。RaycastClosest:返回最*的击中点。RaycastAny:只关心是否击中,不关心具体点,性能最高。
- 形状扫描:将一个形状(如胶囊体)沿路径扫描,检测其扫过的体积内是否发生碰撞。这对于角色移动检测非常有用。
- 重叠检测:给定一个形状,检测在当前世界中有哪些物体与其体积有重叠。常用于爆炸范围判定等。
为了提高查询效率,物理对象通常会分到不同的碰撞层。查询时可以指定只检测特定层的物体,例如子弹不检测触发器。
性能优化与高级概念
物理模拟计算量大,需要精心优化。
- 岛屿与休眠:物理引擎会将相互关联的动态物体分组为“岛屿”。当一个岛屿内所有物体都趋于静止(速度低于阈值)时,整个岛屿会进入“休眠”状态,引擎不再对其进行物理计算,直到有外力将其唤醒。这能大幅提升性能。
- 连续碰撞检测:当物体移动速度过快时,可能在两帧之间直接穿过另一个薄物体,称为“隧道效应”。CCD 通过在两帧之间进行更密集的检测或使用扫描形状来避免此问题,对高速运动的物体(如子弹、赛车)很重要。
- 确定性:对于网络游戏,确保在不同客户端上,相同的输入经过物理模拟后得到完全相同的结果,称为确定性。这非常困难,受浮点数精度、迭代顺序、积分步长等多种因素影响。实现确定性是物理引擎的挑战之一,它对于网络同步至关重要。
总结

本节课我们一起学*了游戏引擎物理系统的基础理论和核心算法。
我们首先了解了物理系统在构建游戏真实感和玩法方面的重要性。然后,深入探讨了物理世界的基本构成元素:四种类型的 Actor(静态、动态、触发器、运动学)以及各种简化后的 Shape(球体、胶囊体等)。
我们学*了如何通过力和数值积分(特别是半隐式欧拉法)来描述和模拟物体的运动。接着,引入了刚体动力学的概念,将旋转运动与质点运动进行类比。
在碰撞处理部分,我们了解了粗检测和精检测的两阶段流程,以及 GJK 算法和分离轴定理的核心思想。碰撞响应则从简单的惩罚力发展到现代的约束求解方法。
此外,我们还介绍了物理查询的常见操作、以及岛屿休眠、CCD、确定性等高级概念和性能优化手段。

物理系统是游戏引擎中最复杂也最迷人的模块之一,它用数学和代码构建了虚拟世界的运行法则。掌握这些基础知识,是进一步使用或开发物理功能的关键。下节课,我们将探索角色控制、布娃娃、破坏等更高级的物理应用。

🎮 课程11:物理系统的高级应用 | GAMES104-现代游戏引擎:从入门到实践
在本节课中,我们将深入探讨物理引擎在游戏开发中的高级应用场景。我们将从角色控制器的基础概念出发,逐步了解布娃娃系统、布料模拟、破坏系统、载具模拟,并最终触及前沿的基于位置的动力学(PBD)与扩展PBD(XPBD)算法。这些内容将帮助你理解如何将物理引擎的强大能力,转化为游戏中生动、可信且富有表现力的交互体验。
🧍 角色控制器
上一节我们介绍了物理引擎的基础概念,如刚体动力学和碰撞检测。本节中我们来看看如何将这些概念应用于游戏中最核心的交互元素——角色控制器。
角色控制器并非一个纯粹的物理动态物体。它更像是一个“运动学”或“反物理”的物体,其设计目标是让角色的移动符合玩家的直觉,而非完全遵循物理定律。例如,玩家希望角色能紧贴地面、拥有无限大的摩擦力,甚至能进行瞬间传送。
一个典型的角色控制器通常使用一个胶囊体形状的碰撞体来包裹角色。这个胶囊体通常有两层:内层用于精确碰撞,外层则作为一层“保护膜”,防止角色卡进缝隙或离墙体过*,从而避免相机穿模等视觉问题。
以下是实现一个基础角色控制器时需要考虑的几个关键功能:
- 滑动处理:当角色撞到墙壁时,不应被完全阻挡,而应能沿着墙面*滑移动。这需要实现一个滑动算法。
- 台阶跨越:角色应能自动踏上一定高度内的台阶,避免被微小障碍物卡住。这通常通过尝试将控制器轻微抬升并前移来实现。
- 坡度限制:需要设置一个最大可攀爬坡度。当斜坡角度超过此限制时,角色将无法站立并会滑落。
- 姿态变化:角色在不同姿态(站立、蹲伏、匍匐)下,控制器的大小和形状需要动态变化。切换姿态时需注意时序,避免因形状突变导致角色被卡住。
- 与环境互动:角色可以推动动态物体。当控制器与物体碰撞时,可以根据角色的速度施加一个冲量给该物体。
- 动态*台:当角色站在移动的*台上时,需要将角色逻辑上与*台绑定,确保其相对位置稳定,避免因物理结算延迟导致的抖动。
总结来说,角色控制器没有特别高深的技术,但充满了需要精心处理的细节。这些细节直接决定了游戏的操作手感和角色与世界的交互质量,是游戏体验的核心组成部分。
🪆 布娃娃系统
在角色控制器之后,另一个广泛使用物理模拟来增强表现力的系统是布娃娃系统。它主要用于处理角色失去意识或死亡后的物理状态,使角色的倒地、跌落等动作更加自然,并与环境产生互动。

布娃娃系统的核心思想是将角色的关键关节(如盆骨、胸腔、四肢)用物理刚体替代,并用符合人体解剖学的关节约束连接起来。这些约束限制了关节的自由度(例如,肘部只能在一个*面上弯曲),从而模拟出更真实的人体物理行为。
从动画驱动切换到布娃娃驱动需要一个*滑的过渡。通常不会在动画的最后一帧直接切换,而是在动画播放到70%-80%时,开始将物理模拟的结果与动画姿态进行混合,最终完全交由物理引擎计算。

更高级的应用是“动画与物理混合”。在这种模式下,动画系统提供角色的基础姿态和速度,物理引擎在此基础上结合环境交互(如被拖动、受击)进行计算,最终将物理结果与动画输出进行混合。这能产生既有角色自身意志,又符合物理规律的生动行为,例如角色被超能力举起时的挣扎动作。

👗 布料模拟
布料模拟是提升角色视觉真实感的关键技术。现代游戏引擎主要采用基于网格的共模拟方法来实现实时布料效果。
首先,需要为布料生成一个用于物理模拟的简化网格,其面数远低于渲染网格。然后,在这个物理网格上刷权重,越靠*身体固定点(如肩膀、腰部)的顶点,其移动自由度越低,以防止布料飞离身体或过度穿模。
布料模拟的力学基础是弹簧质点模型。将布料网格的顶点视为质点,顶点之间的边视为弹簧。每个质点在模拟中会受到多种力的影响:
- 重力:
F_gravity = m * g - 风力:一个持续施加的力。
- 空气阻力:
F_drag = -k_drag * v(与速度方向相反,大小成线性关系) - 弹簧力:包含胡克定律产生的弹力
F_spring = -k * (x - x_rest)和阻尼力F_damping = -k_d * v,阻尼力用于消耗能量,防止系统无限振荡。
对于积分方法,除了上一节介绍的半隐式欧拉积分,布料模拟中常用韦尔莱积分。它通过当前帧和上一帧的位置来直接计算下一帧的位置,避免了直接使用速度可能带来的不稳定性,其核心公式*似为:
x_{t+Δt} ≈ 2*x_t - x_{t-Δt} + a * Δt²
然而,目前最主流的布料模拟算法是基于位置的动力学。PBD跳过了“力->加速度->速度->位置”的传统流程,直接将物理规律描述为关于位置的约束(如“两点间距离应等于原长”),然后通过求解约束系统来直接更新位置。这种方法更稳定、高效,能更好地处理复杂情况。
布料模拟最大的挑战之一是自碰撞检测与解决。当布料层数较多或剧烈运动时,布料自身会相互穿透。解决方法包括:加厚碰撞体、使用更细的模拟子步长、限制顶点最大速度、或在布料内部构建符号距离场(SDF)来产生排斥力等。

💥 破坏系统
破坏系统为游戏世界增加了可塑性和动态玩法。其核心是将一个物体预先分割成许多碎片,并组织成层次结构(通常是一棵树)。碎片之间通过“连接”关联,每个连接有一个“强度”值。
当物体受到冲击时(例如被子弹击中),物理引擎会计算冲击点产生的冲量。根据冲量大小和碎片的连接强度,可以计算出造成的“伤害”。如果伤害值超过连接强度,该连接就会断裂。断裂的影响会以冲击点为中心,按一定公式(如线性衰减)向周围碎片传播。
碎片的生成可以使用沃罗诺伊分割算法。在物体包围盒内随机撒点,根据这些种子点将空间分割成多个区域,每个区域内的点离其种子点最*,从而形成自然的破碎图案。对于3D物体,还需要结合德劳内三角化算法生成四面体网格。
破坏系统的挑战在于性能与同步。瞬间生成成百上千个需要进行物理模拟的刚体碎片,计算开销巨大。在多人游戏中,还需保证不同客户端上物理模拟的确定性,否则碎片落点可能不一致,导致玩法问题。
🚗 载具模拟
载具模拟是物理引擎的另一个经典应用。一个基础的车辆模型包含以下组件:
- 底盘刚体:代表车辆的主体。
- 车轮与悬挂:每个车轮通过弹簧(悬挂系统)与底盘连接。弹簧的压缩和拉伸模拟了车辆的颠簸。
- 驱动力:发动机产生扭矩,经过变速箱和差速器分配后,转化为车轮对地面的静摩擦力,推动车辆前进。
- 轮胎力:分为纵向力(驱动/制动)和侧向力(转向)。侧向力是一种滑动摩擦力,它使车辆能够转弯。轮胎力的大小受摩擦系数和车辆重心位置影响。
- 重心:车辆的重量分布中心,极大地影响车辆的操控特性(如转向不足或过度)。
一个重要的概念是阿克曼转向几何。为了使车辆在转弯时四个轮子都做纯滚动,避免侧滑,内侧转向轮需要比外侧转向轮转动更大的角度。
通过调整发动机扭矩曲线、轮胎摩擦系数、悬挂刚度、重心位置等参数,可以模拟出从卡丁车到拉力赛车等不同手感、不同特性的车辆。
⚙️ 前沿:PBD与XPBD
最后,我们简要探讨物理模拟的前沿方向:基于位置的动力学及其扩展。
传统的牛顿力学方法通过计算力来驱动运动。而PBD则采用拉格朗日力学的视角,将所有的物理规律(如不可拉伸、碰撞)都表述为一系列关于位置的约束 C(x) = 0。
PBD的核心是一个迭代求解过程。在每一帧,它先根据惯性预测顶点的位置,然后检测碰撞并生成碰撞约束。接着,它循环处理所有约束:计算约束函数在当前位置下的雅可比矩阵(表示位置变化对约束值的影响趋势),然后沿着使约束值减小的方向,以一定的步长调整顶点位置。如此迭代,直到所有约束都被*似满足或达到迭代次数上限。
PBD的优势在于稳定性和高效性,特别适合布料、软体等模拟。
XPBD 是对PBD的扩展,它引入了柔度矩阵的概念。在PBD中,约束是“硬”的,必须被满足。而XPBD允许为每条约束指定一个“柔度”参数,从而可以描述从刚性到柔性的各种材料。其约束方程升格为包含柔度矩阵的能量形式,通过最小化系统势能来求解。这使得XPBD能更自然、更统一地处理混合刚柔性的复杂物理场景,是当前物理引擎研究的热点。
📚 总结与作业
本节课中我们一起学*了物理引擎在游戏中的五大高级应用:角色控制器、布娃娃系统、布料模拟、破坏系统和载具模拟,并了解了前沿的PBD/XPBD算法。这些系统将物理计算转化为游戏中生动的交互和视觉表现,是构建沉浸式游戏世界的基石。
课程作业将围绕动画与物理的整合展开:
- 基础作业:在小引擎中实现一个动画状态机,让角色能够完成走、跑、跳、落地等动作的*滑过渡。
- 进阶作业:实现一个完整的角色控制器,要求具备墙面滑动、台阶跨越、坡度限制等基本功能,并能稳定地在复杂环境中移动。


希望你能通过实践,深入理解这些系统是如何协同工作,共同塑造出我们所游玩的精彩游戏世界的。

11.计算工具箱:成像梯度与处理 | GAMES204-计算成像 - P1 - GAMES-Webinar - BV14B4y177Mv

概述
在本节课中,我们将学*成像梯度及其在图像处理中的应用。我们将探讨图像梯度的计算方法、性质以及其在图像融合、图像编辑等领域的应用。



图像梯度的应用
以下是图像梯度的几个主要应用:

- 图像融合:例如,将白天和夜晚的图像融合在一起,得到一张具有丰富细节的图像。
- 图像编辑:例如,去除玻璃上的反射、进行图像拼接等。
- 图像恢复:例如,通过图像梯度进行图像去噪、图像增强等。

图像梯度的计算
图像梯度的计算方法如下:
- 计算图像梯度:使用卷积算子对图像进行卷积操作,得到图像的梯度图。
- 计算散度:对图像梯度进行散度运算,得到图像的散度。
- 计算旋度:对图像梯度进行旋度运算,得到图像的旋度。

图像梯度的性质

图像梯度的性质如下:
- 稀疏性:图像的梯度通常在边缘处较为明显,而在图像内部较为稀疏。
- 连续性:图像的梯度在边缘处连续变化。
图像梯度的应用实例
以下是一些图像梯度的应用实例:
- 图像融合:例如,使用图像梯度进行图像融合,得到一张具有丰富细节的图像。
- 图像编辑:例如,使用图像梯度进行图像编辑,去除玻璃上的反射、进行图像拼接等。
- 图像恢复:例如,使用图像梯度进行图像恢复,进行图像去噪、图像增强等。
总结


本节课介绍了成像梯度及其在图像处理中的应用。我们学*了图像梯度的计算方法、性质以及其在图像融合、图像编辑等领域的应用。希望这节课的内容能够帮助大家更好地理解图像梯度的概念和应用。

课程12:游戏引擎中的粒子和声效系统 🎮 | GAMES104-现代游戏引擎:从入门到实践

在本节课中,我们将学*游戏引擎中两个至关重要的特效系统:粒子系统和声效系统。粒子系统负责创造游戏中各种视觉特效,如火焰、烟雾和爆炸;而声效系统则构建了游戏世界的听觉体验,直接影响玩家的沉浸感和情绪。我们将从基础概念入手,逐步深入到现代引擎中的实现原理与技术挑战。
粒子系统基础
上一节我们概述了本课程的主要内容,本节中我们来看看粒子系统的基础构成。粒子系统是游戏视觉表现力的核心,它通过模拟大量微小粒子(Particle)的行为来创造复杂的视觉效果。
一个粒子是系统中最基础的构建元素,可以理解为空间中的一个点或小面片。每个粒子都拥有一系列随时间变化的属性。
以下是粒子的核心属性:
- 位置:粒子在三维空间中的坐标。
- 速度:粒子运动的方向和速率。
- 尺寸:粒子的大小。
- 颜色:粒子的颜色,通常可以随时间变化。
- 生命周期:粒子从产生到消亡的持续时间。
粒子由发射器(Emitter)产生。发射器定义了粒子产生的源头和初始状态。
以下是发射器的常见功能:
- 生成位置:可以是一个点、一个区域(如立方体、球体)或从一个模型表面采样。
- 初始属性:为生成的粒子赋予初始位置、速度、尺寸等,这些值通常可以加入随机性以显得自然。
- 发射模式:可以是连续喷射,也可以是周期性爆发。
多个发射器及其产生的所有粒子共同构成了一个粒子系统(Particle System)。一个复杂的视觉效果(如火焰)通常由多个不同类型的粒子系统组合而成,例如同时表现火焰主体、飞溅的火星和产生的烟雾。
粒子的模拟与渲染
了解了粒子的基础构成后,本节中我们来看看粒子在生命周期内是如何被模拟和最终绘制到屏幕上的。粒子被发射后,其运动和行为由模拟(Simulation)过程控制。
模拟过程根据物理规律(或艺术化规则)更新每个粒子的状态。粒子在模拟中会受到各种力的影响。
以下是影响粒子的常见作用力:
- 重力:使粒子向下加速。
- 空气阻力:减缓粒子的运动。
- 风力:为粒子提供方向性的推动力。
除了位置和速度,模拟过程还会更新粒子的其他视觉属性,如颜色、尺寸和旋转,以模拟如火焰由红变亮再变暗的效果。
粒子在屏幕上需要被渲染出来。最常见的粒子形态是公告板(Billboard),即一个始终面向摄像机的四边形面片。为了避免玩家注意到面片始终朝向相机,通常会使用动态变化的纹理。
除了公告板,粒子还可以有其他形态。
以下是常见的粒子渲染形态:
- 网格粒子:使用三维模型作为粒子,适合表现碎片、人群等。
- ** ribbon粒子**:粒子运动轨迹会拖拽出一条光带,适合表现刀光、弹道轨迹等。其路径通常使用Catmull-Rom样条曲线进行*滑插值。
粒子渲染面临两大核心挑战:排序和性能。由于粒子大多是半透明物体,必须按照从远到*的顺序绘制才能得到正确的混合效果。全局排序效果最好但计算量大;按发射器排序效率高,但可能在不同发射器的粒子交错时产生错误。
粒子系统极易造成过度绘制(Overdraw),即同一个像素被多次绘制,严重消耗性能。常见的优化手段包括使用半分辨率渲染粒子,以及根据粒子与相机的距离进行裁剪。
现代GPU粒子系统
上一节我们讨论了CPU粒子系统的挑战,本节中我们来看看如何利用GPU的强大并行计算能力来高效实现粒子系统。将粒子模拟移至GPU可以极大提升性能,特别适合处理海量粒子。
GPU粒子系统的核心思想是使用一个固定大小的粒子池(Particle Pool)和两个列表来管理粒子状态。
以下是GPU粒子管理的关键数据结构:
- 粒子池:一个存储所有粒子属性(位置、速度等)的数组,定义了系统可容纳的最大粒子数。
- 存活列表:一个列表,记录当前所有“活着”的粒子在粒子池中的索引。
- 死亡列表:一个列表,记录当前所有“空闲”(可复用)的粒子在粒子池中的索引。
其工作流程基于双缓冲和计算着色器(Compute Shader)。每一帧,计算着色器并行处理存活列表中的所有粒子:更新其属性,判断其是否死亡。将存活粒子写入新的存活列表,将死亡粒子的索引移回死亡列表。新粒子从死亡列表头部取出索引进行初始化。这个过程充分利用了GPU的并行特性。
在GPU上进行全局排序(Global Sorting)是一个关键且复杂的步骤。一种高效的GPU排序算法是归并排序(Merge Sort)的变种。其核心思想是将大数组排序分解为多个已排序小数组的合并。在GPU实现中,通过让每个元素在目标数组中反向查找自己应有的位置,可以保证写入的内存连续性,从而获得更高性能。
GPU粒子系统还能方便地实现基于屏幕空间深度缓冲(Screen-Space Depth Buffer)的简单碰撞检测,让粒子与场景几何体发生互动,而无需调用完整的物理引擎。
粒子系统的进阶应用
基础的粒子系统已经能创造丰富效果,但现代游戏引擎中的粒子系统已演变得极为复杂和强大。粒子系统不再局限于简单的运动模拟,而是可以集成状态机、动画甚至群体行为模拟。
一个高级应用是使用粒子系统来模拟人群(Crowd Simulation)。每个粒子代表一个角色,拥有一个简化的骨骼动画状态机。所有可能的动画姿势被烘焙成纹理。每个粒子根据其状态(行走、奔跑、闲置)从纹理中采样对应的姿势进行渲染,并通过一个预计算的方向场(Directional Field)来引导群体沿路径移动并避开障碍物。
现代粒子系统(如虚幻引擎的Niagara)提供了高度可视化和可编程的框架,允许特效美术师通过节点图灵活地控制每一个粒子的行为、与其他系统的交互以及复杂的生成逻辑。这使得粒子系统能够实现诸如物体聚散变形、与场景深度交互等电影级特效。
声效系统基础
讲完了视觉上的粒子系统,本节我们转向构建游戏世界听觉体验的声效系统。声音对于游戏的沉浸感、情绪引导和玩法都至关重要,其影响甚至比画面更为直接和本能。
声音有三个基本的物理属性,对应人的听觉感知。
以下是声音的核心属性:
- 音量:人耳感知的声音强弱,由声波的振幅决定。其单位分贝(dB)是一个对数标度,
L = 20 * log10(P / P0),其中P是声压,P0是参考声压(约20微帕)。 - 音高:人耳感知的声音高低,由声波的频率决定。人耳可听范围约为20Hz到20kHz。
- 音色:由声波的具体波形决定,即使音高和音量相同,不同乐器或声源的声音也不同,这是因为它们包含的谐波成分不同。
计算机通过脉冲编码调制(PCM)将连续的模拟声音信号数字化。根据香农采样定理,采样频率至少需为声音最高频率的两倍(如44.1kHz)。采样后的振幅值被量化为固定位深(如16-bit)的数字进行存储和编码。游戏常用有损压缩格式(如OGG Vorbis)来节省资源,因其无专利限制且支持多声道。
三维声效渲染
在三维游戏世界中,声音需要根据声源和听者的相对位置进行动态渲染,以营造空间感。这个过程称为音频渲染(Audio Rendering)。
听者(Listener)是音频渲染的“观察点”,通常附着在玩家角色或相机上。听者具有位置、朝向和速度(用于多普勒效应)等属性。
空间感主要通过以下三种线索营造:
- 声强差:距离声源较*的耳朵听到的声音更大。
- 时间差:声音到达双耳有微小的时间差。
- 音色差:声音在传播中,特别是绕过头部时,不同频率成分衰减不同。
声像定位(Panning)算法通过调整左右声道的音量、延迟和均衡,模拟声源在水*方向上的移动。为了保持移动过程中总响度感知不变,常使用三角函数进行归一化:左声道增益 = sin(θ), 右声道增益 = cos(θ),其中θ是声源相对于正前方的角度。
衰减(Attenuation)模拟声音随距离减弱的效果。它不仅改变总体音量,还会根据距离调整高低频的比例(空气对高频吸收更强),这对于判断枪声远*等玩法至关重要。衰减区域可以定义为球形、胶囊体(模拟溪流)或锥形(模拟喇叭),以匹配不同声源特性。
环境声学与高级概念
上一节介绍了基础的3D声音定位,本节我们探讨更复杂的环境声学效果,它们对于塑造空间的真实感极为关键。声音在环境中传播时,会与物体和空间结构发生复杂的相互作用。
混响(Reverb)是塑造空间感最重要的效果之一。它由直达声、早期反射和后期混响尾音构成。在音频引擎中,混响常用几个关键参数供设计师调节。
以下是混响的核心控制参数:
- 干湿比:直达声(干)与混响声(湿)的比例。
- 衰减时间:声音衰减60分贝所需的时间,与空间大小和材质相关。
- 预延迟:直达声与第一次反射声之间的时间差。
- 高频衰减:控制混响中高频成分的衰减速度。
遮挡(Occlusion)和衍射(Diffraction)处理声音被障碍物阻挡和绕射的效果。简单实现可以通过从声源到听者发射射线进行检测,并根据材质属性过滤特定频率。更精确的模拟则需要复杂的声学追踪计算。
多普勒效应(Doppler Effect)模拟因听者与声源相对运动而导致的音高变化。当声源朝向听者运动时,听到的音调变高;远离时音调变低。其频率变化公式为:f‘ = f * (v + vo) / (v + vs),其中f为原频率,v为声速,vo为听者速度,vs为声源速度。
现代大型游戏(如开放世界)需要管理成千上万个环境声源,并动态计算不同区域的混响属性。这通常依赖音频中间件(如Wwise、FMOD)提供的强大工具链和运行时库,它们为声音设计师提供了图形化的工作流程,并与游戏引擎深度集成。
总结
本节课中,我们一起学*了游戏引擎中两大核心特效系统。我们从粒子系统的基本概念出发,了解了粒子、发射器和系统的构成,探讨了模拟、渲染的挑战以及CPU和GPU的不同实现方案,并看到了现代粒子系统在群体模拟和电影化特效方面的强大能力。
在声效系统部分,我们学*了声音的物理基础与数字化,深入探讨了如何在三维空间中渲染声音,包括声像定位、衰减、混响、遮挡和多普勒效应等关键技术,这些共同构建了令人信服的听觉空间。
粒子与声效系统虽然一个主视觉、一个主听觉,但在游戏体验中紧密配合,是创造沉浸感不可或缺的支柱。它们的发展也体现了游戏引擎技术在追求极致表现力与运行效率之间的不断*衡与创新。





12.计算工具箱:成像梯度与处理 II | GAMES204-计算成像 - P1 - GAMES-Webinar - BV1BP411E7sw

概述
在本节课中,我们将继续探讨计算成像中的成像梯度与处理。我们将深入探讨图像梯度、图像稀疏性、共轭梯度法以及图像编辑等主题。
图像梯度
图像梯度是图像中像素值变化率的一种度量。它可以帮助我们理解图像中的边缘和纹理信息。
公式:
其中,\(I\) 表示图像,\(x\) 和 \(y\) 分别表示图像的横纵坐标。
图像稀疏性
图像稀疏性是指图像中大部分像素值都为零或接*零的性质。这种性质可以帮助我们有效地进行图像处理。
公式:
共轭梯度法
共轭梯度法是一种用于求解线性方程组的迭代方法。它可以帮助我们快速地找到图像梯度下降的最优解。


公式:




其中,\(x_k\) 表示第 \(k\) 次迭代的解,\(A\) 表示系数矩阵,\(b\) 表示常数向量,\(\alpha_k\) 表示步长。

图像编辑


图像编辑是指对图像进行修改和增强的过程。我们可以使用图像梯度、图像稀疏性和共轭梯度法来实现各种图像编辑任务。

修复刷子

修复刷子是一种用于修复图像中损坏区域的工具。它可以使用图像梯度来找到相似区域,并将它们复制到损坏区域。
克隆工具
克隆工具是一种用于复制图像中特定区域的工具。它可以使用图像梯度来找到相似区域,并将它们复制到目标区域。
锐化工具
锐化工具是一种用于增强图像中边缘和纹理的工具。它可以使用图像梯度来找到边缘信息,并将其增强。
总结

在本节课中,我们学*了图像梯度、图像稀疏性、共轭梯度法和图像编辑等主题。这些知识可以帮助我们更好地理解计算成像,并实现各种图像处理任务。

课程13:引擎工具链基础 🛠️ | GAMES104-现代游戏引擎:从入门到实践
在本节课中,我们将学*游戏引擎工具链的基础知识。工具链是游戏引擎中默默无闻但至关重要的部分,它负责协调不同角色(如程序员、美术师、设计师)协同工作,将各种异构数据转化为游戏可用的资产。我们将从工具链的定位、核心概念到基础架构,一步步解析其工作原理。
工具链的定位与价值
游戏引擎工具链的核心价值,在于调和几种不同思维方式的人在一起协同工作的*台。一个游戏团队通常包含三类人:逻辑严谨的程序员、感性敏锐的美术师和注重体验与反馈的设计师。工具链需要为这些使用不同“语言”的成员提供一个共同工作的基础。
图形用户界面基础
构建工具链的第一个挑战是处理复杂的图形用户界面。GUI的实现主要有两种模式:
- 直接模式:每一帧由游戏逻辑直接告诉GUI系统绘制控件。其优点是简单、快速、易于移植,但扩展性有限,且将业务逻辑压力集中在逻辑层。
- 保留模式:将绘制指令(如画一个多大尺寸的Box)存储起来,由GUI系统在绘制时统一处理。其优点是逻辑与渲染分离、扩展性强、性能好,是现代工具链的主流选择。
在保留模式基础上,为了管理复杂的UI逻辑,需要遵循特定的设计模式,将视图与数据模型解耦。以下是三种经典模式:
- MVC:将数据、视图和控制逻辑分离,形成单向数据流(Model -> View),View不能直接写Model,需通过Controller。这是最经典的解耦架构。
- MVP:View完全不知道Model的存在,由Presenter负责从Model取数据给View,并处理View的交互反馈给Model。这种模式更彻底地分离了View和Model,便于单元测试,但Presenter可能变得臃肿。
- MVVM:通过数据绑定机制,将View和Model彻底分离。View由交互设计师用声明式语言(如XAML)定义,ViewModel由程序员编写,定义简单的绑定关系。这种模式非常灵活,是现代商业工具(如WPF)的基石,但对特定生态依赖较强。
对于工具链开发,建议不要重复造轮子,应选择成熟的框架(如WPF、Qt)来构建稳定、可扩展的GUI体系。
数据的序列化与反序列化
工具链处理的核心是数据,首要需求是加载与保存。这涉及到序列化与反序列化。
- 序列化:将内存中的对象状态转换为可存储或传输的格式(如二进制块、数据库记录、网络数据流)。
- 反序列化:将存储的格式重新构建为内存中的对象。
数据存储格式主要有两类:
- 文本格式:如JSON、XML、YAML。优点是易于人类阅读、调试,任何文本工具都能打开,是极佳的调试模式。缺点是文件体积大,解析速度慢。
- 二进制格式:优点是文件体积小,加载速度快,且易于加密防止破解。是上线游戏的必然选择。
一个实用的策略是同时支持文本和二进制格式,用文本格式作为调试和开发时的可读版本,最终发布时转换为二进制格式。
在定义数据结构时,必须支持资产引用。游戏由成千上万个彼此关联的资产文件构成,通过引用可以避免重复存储相同的资产定义。同时,还需要支持数据继承,允许美术师在实例上直接修改部分属性(如贴图),而无需创建全新的资产定义,这提高了编辑的灵活性。
反序列化加载数据时,并非简单顺序读取。无论是文本还是二进制格式,都需要一个解析过程,将文件内容构建成一个树状结构。这类似于编译器的抽象语法树,是理解复杂资产关系的基础。对于二进制格式,通常在文件头部存储这个树状结构的描述(各字段的名称、类型、偏移量),以便快速定位和读取数据。
跨*台时需注意字节序问题。工具链内部可统一约定一种字节序(如小端序),在为目标*台打包时,再进行必要的字节交换。
资产版本兼容性
商业游戏开发周期长,引擎和工具会不断升级,必须保证新旧版本资产文件的兼容性。这包括向后兼容(新工具打开旧资产)和向前兼容(旧工具打开新资产)。
简单的版本号检查并填充默认值的方法在长期维护中会变得难以管理。一个更优雅的方案是采用类似 Protocol Buffers 的思路:为每个数据字段分配一个唯一且单调递增的ID。这样,在读取数据时,通过ID来识别字段,无论字段顺序如何变化、是否有新增或删除字段,都能正确处理。这对于由多个团队维护、拥有几十上百种数据格式的大型引擎至关重要。
工具的鲁棒性:撤销/重做与自动恢复
工具链的鲁棒性要求极高,核心功能之一是撤销与重做。在游戏编辑中,操作常常是关联的,例如修改地形会影响其上的房屋和树木。
实现这一功能的标准模式是命令模式。将用户的所有操作原子化为一个个Command对象。每个Command至少包含:
- 唯一ID(用于保证执行顺序)
- 执行(
invoke) - 撤销(
revoke) - 序列化与反序列化(用于持久化,实现崩溃恢复)

绝大多数编辑操作可以归结为三类原子Command:增加对象、删除对象、修改对象的某个字段。通过定时将Command序列化到磁盘,可以实现崩溃后的数据恢复。

统一的数据描述:模式
工具链需要处理大量异构数据。解决方案是从中找出共性,用基础的构建块(如float, vector3, color)来描述复杂数据。这种描述结构称为模式。
模式定义了数据的“分子式”,它使得不同工具能理解同一份数据。例如,一个“角色”模式定义了其包含的字段(如位置、网格引用、动画引用),这样无论是地图编辑器还是动画编辑器,都能正确解析和展示角色数据。
模式设计应支持:
- 继承:可以从基础模式派生出更具体的模式,减少重复定义。
- 引用:模式中的字段可以引用其他资产或数据。
模式的实现有两种流派:
- 独立文件定义:用JSON、XML等文件单独定义模式。优点是与代码彻底分离,易于理解;缺点是需要额外的代码生成器,易出现模式与引擎代码版本不匹配的问题。
- 代码内定义:在C++等代码中用特定宏或标记定义模式(如Unreal Engine)。优点是模式与引擎紧密集成,编译后即保证一致;缺点是修改数据结构容易引发整个引擎的编译和稳定性问题。
数据在工具链中呈现“三张脸”:
- 存储脸:在磁盘上,追求空间效率。
- 内存脸:在内存中,追求处理效率。
- 视图脸:在工具中,追求人类可读和易用性。例如,角度在内存中用弧度,但在工具UI中显示为度数;颜色显示为调色板而非RGB数值。工具应为同一种数据类型提供定制不同视图的能力。
核心架构:所见即所得
工具链的核心精神是所见即所得。美术师和设计师要求在编辑器中看到的效果与最终游戏运行时完全一致。

现代游戏引擎普遍采用的架构是将工具链构建在游戏运行时之上。编辑器本身就是一个特殊的游戏模式,它直接调用游戏引擎的运行时来渲染和模拟。这样,在编辑器中调整的光照、材质、动作,与打包后游戏中的效果完全一致。
实现“所见即所得”编辑时,有两种播放模式:
- 编辑器内播放:直接在编辑状态切换为游戏状态。实现简单,但编辑数据与游戏数据可能相互污染。
- 独立进程播放:将当前编辑的世界复制一份,在一个独立的沙盒进程中运行。更纯净,更能模拟最终游戏环境,但消耗更多内存。对于复杂的大型游戏项目,这是更推荐的方式。
可扩展性:插件系统
没有任何引擎能预知用户的所有需求,因此插件系统是现代商业引擎的刚需。它允许第三方开发者扩展引擎功能,开发自定义工具。
插件系统要求引擎提供丰富的API,让插件能够:
- 注册自定义功能。
- 在现有UI中添加按钮、菜单。
- 访问和修改游戏世界的数据。
这就要求引擎自身的功能也尽量通过清晰的API模块化构建,使得插件开发与引擎内置功能开发站在同一起跑线上,极大地提升了引擎的*台性和生态活力。
总结


本节课我们一起学*了游戏引擎工具链的基础知识。我们了解了工具链作为协调不同角色协同工作*台的核心价值;探讨了构建GUI的两种模式及MVC/MVP/MVVM设计模式;深入研究了数据的序列化、反序列化、资产引用、继承以及棘手的版本兼容性问题;掌握了通过命令模式实现撤销/重做和崩溃恢复来保证工具鲁棒性的方法;认识了统一数据描述的“模式”概念及其两种实现流派;理解了“所见即所得”的编辑理念及其两种架构实现;最后,我们看到了插件系统对于引擎生态扩展的重要性。这些基础构成了强大、稳定、易用的游戏开发工具链的基石。

13. 计算工具箱:图像模糊 | GAMES204-计算成像 - P1 - GAMES-Webinar - BV1qe4y1v75r
概述
在本节课中,我们将学*图像模糊的来源和解决方法。图像模糊是计算成像中常见的问题,它会影响图像的清晰度和质量。我们将探讨图像模糊的四种主要来源,并介绍相应的解决方法。
图像模糊的来源
图像模糊主要来自以下四个方面:
- 光学系统缺陷:包括透镜的球差、彗差、像散、场曲和畸变等。
- 衍射极限:由于衍射效应,透镜的分辨率受到限制,导致图像模糊。
- 相机抖动:由于手抖或相机抖动,图像在曝光过程中发生移动,导致图像模糊。
- 景深限制:当物体距离相机较远时,部分物体无法在图像传感器上形成清晰的像,导致图像模糊。

解决方法


光学系统缺陷


- 优化光学设计:通过优化透镜的设计,减少球差、彗差、像散、场曲和畸变等缺陷。
- 使用非球面透镜:非球面透镜可以更好地控制光线的传播,减少光学系统缺陷。

衍射极限
- 提高透镜口径:增大透镜口径可以减少衍射效应,提高图像分辨率。
- 使用超分辨率技术:通过超分辨率技术可以恢复图像中丢失的细节。
相机抖动





- 光学防抖:通过光学防抖技术可以减少相机抖动对图像的影响。
- 电子防抖:通过电子防抖技术可以延长曝光时间,减少相机抖动对图像的影响。
- 图像去模糊算法:通过图像去模糊算法可以恢复由于相机抖动导致的模糊图像。


景深限制

- 使用大光圈:使用大光圈可以增加景深,使更多物体在图像传感器上形成清晰的像。
- 使用浅景深技术:通过浅景深技术可以突出前景或背景,使图像更具艺术感。
总结

本节课介绍了图像模糊的来源和解决方法。通过了解图像模糊的成因,我们可以采取相应的措施来提高图像质量。在下一节课中,我们将学*图像去模糊算法。

课程14:去模糊与成像逆问题剖析 🧩 | GAMES204-计算成像
在本节课中,我们将学*如何通过优化方法解决图像去模糊等成像逆问题。我们将从经典的维纳滤波入手,逐步构建并求解一个包含数据保真项和图像先验约束的优化模型,最终掌握一套解决此类问题的通用流程。

成像模型与逆问题

上一节我们介绍了正向的成像过程。本节中,我们来看看如何用数学模型描述成像,并构建逆问题。
成像过程可以抽象为一个线性模型。我们观测到的模糊且有噪声的图像 b,是由清晰的原始图像 x 经过一个模糊核 c(也称为点扩散函数 PSF)卷积,再加上噪声 η 得到的。这个过程可以用公式表示:
b = c * x + η
其中,* 表示卷积操作。在频域中,卷积变为乘法,因此公式可以写为:
B = C · X + N
这里的 B、C、X、N 分别是 b、c、x、η 的傅里叶变换。
我们的目标是求解逆问题:在已知模糊图像 b 和模糊核 c 的情况下,恢复出清晰的原始图像 x。如果不考虑噪声,在频域中直接做逆滤波(X = B / C)即可。然而,实际中噪声总是存在,直接逆滤波会放大噪声,导致结果毫无意义。
从维纳滤波到正则化
为了解决噪声放大问题,我们需要更稳健的方法。维纳滤波在20世纪40年代被提出,它在求解时考虑了噪声的功率谱,其频域解为:

X_hat = (C* · B) / (|C|² + N/S)

其中 C* 是 C 的复共轭,N 是噪声功率,S 是信号功率。当信噪比(SNR)很高时,结果接*逆滤波;当 SNR 很低时,结果被严重抑制以避免噪声放大。
维纳滤波是一个特例。更通用的思路是构建一个优化问题。我们将成像模型重写为矩阵形式:

b = A x + η

这里 A 是代表整个成像系统(如模糊)的矩阵。我们的目标是找到最可能的 x。通过最大后验概率估计,并取负对数,可以推导出以下优化目标函数:
argmin_x [ ||A x - b||² + λ R(x) ]
这个目标函数包含两部分:
- 数据保真项
||A x - b||²:要求重建的图像x经过系统A后,应尽可能接*观测值b。 - 正则化项
λ R(x):引入关于图像x的先验知识,约束解空间,使病态问题变得适定。λ是权衡两项重要性的参数。
图像先验:全变分正则化
以下是几种常见的图像先验约束。本节课我们重点介绍其中一种强大且经典的正则化方法。
全变分正则化假设自然图像的梯度是稀疏的,即图像大部分区域是*滑的,变化主要发生在边缘。其目标是最小化图像的总变分。对于离散图像 x,其总变分通常定义为梯度幅值的 L1 范数之和:
TV(x) = Σ_i,j √[(x_{i+1,j} - x_{i,j})² + (x_{i,j+1} - x_{i,j})²]
或者采用各向同性的简化形式:
TV(x) = Σ_i,j (|x_{i+1,j} - x_{i,j}| + |x_{i,j+1} - x_{i,j}|)
使用 L1 范数(绝对值)比 L2 范数(*方和)更能促进梯度稀疏性,从而更好地保持边缘清晰度。因此,我们的优化问题具体化为:
argmin_x [ ||A x - b||² + λ TV(x) ]
求解:Half-Quadratic Splitting 算法
我们已经构建了优化问题,但目标函数中包含不可导的 L1 范数,直接求解困难。本节中我们来看看如何使用 Half-Quadratic Splitting 算法来有效求解。
HQS 算法的核心思想是引入辅助变量 z,将原问题转化为一个等价但更容易处理的约束优化问题。我们令 z *似等于图像的梯度 D x(D 为梯度算子),则问题变为:
argmin_{x, z} [ ||A x - b||² + λ ||z||₁ ], 满足 z = D x
然后,我们使用增广拉格朗日法或惩罚函数法将约束放松,得到以下无约束问题:
argmin_{x, z} [ ||A x - b||² + λ ||z||₁ + (ρ/2) ||z - D x||² ]
这里 ρ 是一个惩罚参数,随着迭代增大。接下来,我们通过交替优化 x 和 z 来求解。
交替优化步骤:
-
固定
z,更新x:
此时问题变为:
argmin_x [ ||A x - b||² + (ρ/2) ||z - D x||² ]
这是一个关于x的二次函数(L2范数),可通过求导等于零直接求解线性方程组:
(AᵀA + (ρ/2) DᵀD) x = Aᵀb + (ρ/2) Dᵀ z
对于卷积形式的A,可在频域高效求解。 -
固定
x,更新z:
此时问题变为:
argmin_z [ λ ||z||₁ + (ρ/2) ||z - D x||² ]
这个问题的解有一个闭合形式,称为软阈值收缩:
z = sign(D x) · max( |D x| - λ/ρ, 0 )
这个操作会将D x中绝对值小于λ/ρ的分量置零,从而实现梯度稀疏。
通过反复迭代上述两步,直到 x 和 z 的变化足够小,我们就得到了最终的去模糊图像 x。
扩展与总结
本节课中,我们一起学*了解决图像去模糊等逆问题的完整流程。
我们首先建立了成像的线性模型,并指出了直接求解的病态性。然后,我们通过引入贝叶斯框架和最大后验概率估计,将问题转化为一个包含数据保真项和正则化项的优化问题。我们重点介绍了全变分正则化作为图像先验,它能有效促进图像梯度的稀疏性,保持边缘。最后,我们详细讲解了使用 Half-Quadratic Splitting 算法 来求解这个包含非光滑 L1 范数的优化问题,其核心是引入辅助变量并进行交替优化。
这套“建模 → 加正则化 → 优化求解”的范式是解决成像逆问题的通用套路。正则化项 R(x) 可以替换为其他先验(如基于深度学*的先验),成像算子 A 也可以扩展到更复杂的物理模型(如非均匀模糊、光场成像等)。掌握这一基础框架后,你就能灵活地将其应用于各种计算成像问题中。

核心公式与代码概念总结:
- 成像模型:
b = A x + η - 优化目标:
argmin_x [ ||A x - b||² + λ R(x) ] - TV正则化:
R(x) = Σ |∇x|(L1范数) - 软阈值收缩(更新z):
z = sign(v) · max( |v| - τ, 0 ),其中v = D x,τ = λ/ρ。

课程14:引擎工具链高级概念与应用 🛠️ | GAMES104-现代游戏引擎:从入门到实践
在本节课中,我们将深入探讨游戏引擎工具链的高级概念与应用。我们将从游戏生产的实际状态出发,重点分析地图编辑器的架构设计、插件系统的实现模式、线性叙事系统的表达方式,以及反射系统在工具链中的核心作用。最后,我们还将展望下一代游戏引擎的重要发展方向——协作式编辑。
游戏生产状态概览 🎬
上一节我们介绍了工具链的基本原理,本节中我们来看看真实的游戏生产是什么状态。
在真实的游戏工作室中,艺术家和设计师会使用各种各样的工具协同工作。有人负责场景布局,有人设计游戏玩法。当点击“播放”按钮后,游戏世界会呈现出复杂的行为,其中包含了大量程序化生成的内容和为设计师定制的框架。从概念设计到3D模型生成,所有环节连接在一起,构成了商业级游戏的生产管线。最终交付给玩家的,是一个所有元素都具有语义、可交互、可玩的游戏世界。
整个工具链还面临一个巨大挑战:游戏类型繁多。对于不同类型的游戏,其所需的编辑操作、玩法设置甚至关卡组织规范都完全不同。这使得工具链体系变得非常复杂。
世界编辑器:核心枢纽与架构设计 🗺️
工具链包含数十种工具,我们无法一一讲解。本节我们将聚焦于一个核心工具——世界编辑器,并探讨其架构设计。
在专业的游戏引擎架构思想中,世界编辑器不仅仅是一个拖拽工具,更是一个*台或枢纽。它是我们将游戏世界中需要构建的逻辑、玩法、场景内容集成进来的*台。因此,在架构自己的世界编辑器时,首先要建立这个概念:它是一个允许各种功能插件插入的*台。
世界编辑器的核心窗口
一个商业级的世界编辑器界面复杂,包含多种面板和视图。对于不同的用户(如动画师、场景美术、玩法设计师),其呈现的面板也完全不同。这印证了世界编辑器作为枢纽的特性:它集成了许多功能,但为不同用户呈现不同的视图。
以下是几个最重要的体系:
-
视口:这是用户与游戏世界交互的核心窗口。其底层运行的是一个完整的游戏实例,但处于“编辑模式”。在此模式下,可以实现自由飞行相机、无视碰撞、显示调试信息等游戏运行时无法进行的操作。这意味着引擎代码中会存在“仅编辑器”的代码,必须小心不要将其编译到最终发布的游戏中。
-
对象列表与视图:游戏世界中的所有物体都是可编辑的对象。当场景中的对象数量达到成千上万个时,如何管理它们成为挑战。商业级引擎会提供多种视图来管理对象,例如树状视图、分层视图、搜索功能等。数据模型与视图分离,允许不同的艺术家根据*惯使用不同的视图。
-
属性面板:当选中一个对象时,需要通过属性面板直接编辑其属性。这依赖于上一节介绍的Schema反射系统。编辑器可以自动根据对象属性的类型,生成相应的编辑界面。

- 内容浏览器:这是现代游戏引擎中至关重要的部分。它将资产从静态的文件目录管理,转变为“资源的海洋”。资产被放入一个巨大的池中,用户可以通过名称、标签等方式检索和使用它们,而无需关心其物理存储路径。这支持了跨项目的资产重用和高效协作。
基础功能的实现细节
有了基本的架构思路,实现时首先要解决的是基础交互功能。

- 鼠标选取:这是一切编辑操作的基础。通常有两种做法:一是使用物理引擎进行射线检测;二是在编辑模式下,增加一个额外的渲染通道,将每个对象的ID渲染到帧缓冲区,通过像素拾取来获取对象ID。后者更精确,但会消耗额外性能。
- 变换操作:对物体的*移、旋转、缩放操作。虽然基础,但需要精心打磨以符合艺术家从专业DCC软件带来的操作*惯,例如支持整度数旋转、多种Gizmo交互模式等。
环境编辑与规则系统
接下来,我们看看如何编辑地形和环境。
- 地形笔刷:如高度笔刷、实例笔刷(用于刷草、树木等)。核心难点不在于笔刷种类,而在于如何使笔刷操作的结果过渡自然*滑。此外,提供艺术家自定义笔刷(如导入高度图)的能力非常重要。
- 规则系统:这是环境编辑的核心。例如,修一条路时,需要自动处理“路上不长树”、“路面需*整”、“路基要有碎石”等规则。一个更复杂的需求是确定性和局部性:当修改局部环境时,系统应只重新生成受影响的部分,而其他已满意的区域保持不变。这需要一套形式化的规则语言,让设计师能够定义和维护复杂的生成逻辑。
插件架构:灵活性与工程实践 🔌
上一节我们介绍了插件机制是工具链的核心。本节中我们来看看在游戏引擎中实现插件架构的具体考量和挑战。
在世界编辑器中,功能可以看作一个矩阵:纵向是各种对象类型(如NPC、环境物体),横向是各个系统(如网格系统、粒子系统、动画系统)。插件设计需要同时支持这两个维度的扩展。
插件模型
在软件工程中,插件有几种常见模型,在游戏引擎中都会用到:
- 覆盖模型:新插件完全覆盖旧插件的功能。例如,用一个全新的曲面地形插件替换原有的地形系统。
- 分布式模型:各插件独立工作,互不影响,最后将结果合并。大部分编辑器插件属于此类,如粒子编辑器、灯光布置工具。
- 流水线模型:插件按顺序处理数据。例如,几何处理插件在物理插件之前运行,先切割模型,再设置物理属性。
- 洋葱圈模型:插件需要与其他插件进行输入输出交互。例如,规则系统插件需要读取地形、植被等插件的数 据,处理后再将结果写回,影响其他插件的状态。
工程挑战
设计插件架构时,必须保持高度的开放性,以应对未来未知的生产需求。同时,也面临严峻的工程挑战:
- 版本兼容性:引擎内核会不断升级,但第三方插件可能无法同步更新。需要设计一套接口版本管理机制。
- 接口误用:引擎开发者无法预知插件作者会如何(或错误地)使用开放接口。当引擎内部数据结构升级时,即使接口函数不变,也可能导致依赖内部实现的插件崩溃。
架构设计需要在严谨一致性和解决实际问题的灵活性之间找到*衡。秉持实用主义原则:任何能优雅解决实际问题的架构,都是好架构。
线性叙事系统:时间轴上的表达 ⏱️
游戏中充满了线性的叙事性内容,如过场动画、UI序列反馈等。本节我们来看看如何在工具中表达这种基于时间轴的变化。
我们以虚幻引擎的Sequencer为例。其核心概念是:为世界中的每个对象(Actor)创建一条轨道,然后可以对该对象的任意属性在时间轴上定义变化。通过设置关键帧,并在关键帧之间进行插值,就形成了丰富的动态序列。
这个过程本质上是在时间轴上修改各个对象组件的参数。无论是控制角色移动、光源变化、播放音效还是切换场景,都基于这一体系。
这引出了一个核心问题:工具是如何知道并能够修改运行时对象的数据的呢?这就引入了下一个核心概念——反射。
反射:连接引擎与工具的桥梁 🔄
反射是连接引擎实现与工具编辑的关键桥梁,也是实现可视化脚本等高级玩法编辑的基础。
反射的作用
在高级语言中,反射允许系统在运行时探查类型的结构(如类、方法、属性)。在游戏引擎中,反射使得工具能够:
- 自动获知引擎暴露了哪些类、属性和方法。
- 动态地调用方法或获取/设置属性值。
- 为可视化脚本系统提供节点和接口。
如果没有反射,工具层就需要为引擎的每一次功能扩展而手动修改代码,这将不可维护。
反射的实现(以PICCOLO引擎为例)
许多同学关心如何在C++中实现反射。PICCOLO引擎的做法具有代表性:
- 利用编译器前端:使用Clang库对C++源代码进行解析。Clang能将代码转换为抽象语法树,从中可以轻松提取类、成员变量、方法等信息。
- 自定义注解:通过宏定义,在代码中为需要反射的类或成员添加自定义标记。Clang会将这些标记解析为注解。反射生成器通过检测这些注解,来决定哪些内容需要被反射。
- 代码生成:反射信息获取后,需要生成对应的访问器代码。PICCOLO使用Mustache模板引擎进行代码渲染。通过编写模板,可以自动生成大量的
Get、Set、Invoke函数代码,避免了手写的重复和错误。
这套流程实现了从C++代码到内存中的Schema数据,再到工具层可用的接口代码的自动化生成。
协作式编辑:下一代引擎的发展方向 👥
随着游戏世界越来越庞大复杂,需要多人同时编辑同一场景。协作式编辑将成为下一代游戏引擎的重点发展方向。
传统协作方式及其问题
- 分层编辑:将世界按内容分层,如地形层、建筑层、植被层。问题在于各层之间存在依赖,一层修改可能影响其他层。
- 分块编辑:将世界划分为不同地块,分配给不同人。问题在于跨越边界的物体难以处理。
- 单文件每对象:如虚幻5的“One File Per Actor”,每个对象存为独立文件,彻底避免冲突。但会带来海量小文件的管理和性能挑战。
协同编辑的核心基础
实现实时协同编辑(多人同时编辑并即时看到彼此改动)的核心基础是操作原子化。即将用户的每一次编辑操作(拖动、修改参数等)都封装为一个命令。通过网络同步这些命令,在所有客户端上按相同顺序执行,即可达成状态一致。
挑战与解决方案
协同编辑的难点在于处理冲突和撤销/重做。
- 锁机制:对资源或对象加锁,防止同时修改。
- 操作转换:使用OT等技术,定义规则来解决操作冲突,使各终端最终状态一致。
- 客户端-服务器模型:所有操作先发送到中央服务器,由服务器统一结算、排序后,将结果广播给所有客户端。这能保证一致性,但会引入少量延迟,且需要处理服务器容错。
协作式编辑在理论上和实践上都是前沿领域,但无疑是未来提升大型游戏生产效能的必由之路。
课程总结与作业 📚
本节课我们一起学*了游戏引擎工具链的高级应用。我们从宏观的游戏生产流程切入,深入剖析了世界编辑器作为功能枢纽的架构思想,探讨了插件系统灵活性与工程实践的*衡,分析了通过时间轴表达线性叙事的Sequencer系统,并揭示了反射机制作为连接代码与工具的核心桥梁的实现原理。最后,我们展望了协作式编辑这一前沿发展方向。
本节作业
本次作业是一个开放性命题,旨在让大家实践反射系统的完整链路。
作业要求:在PICCOLO引擎中,尝试为角色或其他对象定义一个新的自定义属性,并让整个编辑和运行链路生效。
例如,为角色增加“血量”属性,并实现“从高处坠落会根据高度扣血”的逻辑。
你需要完成:
- 在C++代码中定义新的组件或属性,并使用反射宏标记。
- 确保该属性能在资产文件中序列化/反序列化。
- 在编辑器中能够显示和编辑该属性。
- 在游戏运行时,该属性能正常生效,影响游戏逻辑。
通过完成这个作业,你将真正掌握从数据定义、反射生成、资产编辑到运行时逻辑的完整工具链体系。


参考资料:本节课提及的相关资料与扩展阅读内容,已发布在课程官方网站上。

课程15:游戏引擎的Gameplay玩法系统基础 🎮 | GAMES104-现代游戏引擎:从入门到实践

在本节课中,我们将要学*游戏引擎中Gameplay玩法系统的基础知识。我们将探讨事件系统、脚本系统、可视化脚本以及著名的3C系统,理解它们如何共同构成游戏互动体验的基石。
课程概述与社区反馈
在开始课程之前,首先对社区反馈进行说明。
第二节课和第三节课的作业提交截止日期统一延长至8月31日,与第四节课作业一同提交。同学们可以根据兴趣选择完成部分作业。
作业提交失败的一个常见原因是,在课程网站上上传附件后,未点击最终的“提交”按钮。课程官网文档的第九页已更新提交指南。如有困难,可通过微信联系课程组协助。
关于Pika引擎是否加入脚本系统,这属于本节课要讨论的内容。脚本系统的架构设计复杂,目前课程组专注于课件准备和引擎核心功能完善。待基础课程结束后,可与社区共同探讨为Pika引擎设计脚本系统。
关于Pika引擎使用CMake而非XMake的问题,选择CMake是因其在开源社区中流行、功能完善且文档丰富,便于实现跨*台目标。未来若有兴趣,可以探讨支持XMake版本。
关于未修改代码却触发重新编译的问题,这与反射系统有关。每次编译都会调用反射编译器生成代码,即使源文件未改动。优化此问题需要处理增量编译,是大型引擎的复杂工程挑战之一。
什么是Gameplay玩法系统?
上一节我们处理了社区事务,本节中我们来看看课程的核心主题:Gameplay玩法系统。
Gameplay可翻译为“玩法系统”,它涵盖游戏内几乎所有的互动逻辑,是一个非常庞杂的体系。
我们将玩法系统分为两大块:基础玩法系统和人工智能系统。本节课聚焦于基础部分,包括事件驱动、脚本系统、可视化脚本和3C系统。AI系统将留待下节课专门讲解。
玩法系统的核心挑战与要求
要理解如何构建玩法系统,首先需要了解它面临的挑战。
第一个挑战是跨系统协作。 一个简单的打击感效果,就涉及动画、特效、音效、控制、UI、渲染等多个系统。玩法工程师需要与所有系统打交道,是个“杂学家”。
第二个挑战是玩法多样性。 同一款游戏中可能包含战斗、卡牌、探索等多种截然不同的玩法机制。玩法系统的架构必须具备高度的可扩展性。
第三个挑战是快速迭代。 玩法的设计和验证周期通常很短,可能只有几天或几周,远低于渲染或动画等系统以月为单位的开发周期。因此,引擎必须支持玩法的快速原型设计和迭代。
综上所述,玩法系统要求能够与各系统高效通讯、具备强大的扩展性以支持多样玩法,并支持快速迭代。
事件系统:游戏对象通讯的基础
在第二节课中,我们提到游戏世界由无数GameObject构成,它们之间需要“对话”。硬编码的if-else或switch-case会导致代码难以维护。
解决方案是采用事件/消息机制。这是一种发布-订阅模式,包含三个核心要素:
- 事件定义:描述发生了什么。
- 回调函数:定义接收到事件后执行什么操作。
- 事件分发器:负责将事件传递给已注册的接收者。
事件定义与扩展性
最简单的事件定义是使用枚举类型。但在实际游戏中,事件类型往往由设计师在开发过程中动态定义,因此引擎需要支持在核心代码之外扩展事件定义。这可以通过工具链生成代码、支持动态语言接口或脚本语言来实现。
回调函数与对象生命周期管理
回调函数的注册和执行是分离的,这带来了对象生命周期的管理难题。例如,一个对象注册了回调函数后,可能在下一帧被销毁,导致回调函数调用野指针。
为了解决这个问题,需要引入引用概念:
- 强引用:表示拥有关系,只要存在强引用,对象就不能被销毁。适用于父子物体等包含关系。
- 弱引用:表示依赖关系,对象可以被销毁,调用前需检查有效性。适用于临时性的引用,如“所有可见的敌人列表”。
在回调函数注册中,通常更常使用弱引用来避免内存无法释放的问题。
事件分发与消息队列
事件分发并非简单的“来一个处理一个”。立即执行模式存在调用链过深、阻塞主线程、难以并行化等问题。
现代引擎通常采用事件队列模型:
- 将一帧内产生的所有事件收集到一个队列中。
- 在下一帧开始时,再统一进行分发和处理。
事件队列在内存中通常使用环形缓冲区管理,以避免频繁的内存分配。同时,为了提升效率,事件会按大类(如网络、战斗、动画)进行分组,由不同的分发器处理。
然而,事件队列也引入了新问题:
- 执行顺序不确定性:同一队列内事件的执行顺序可能不符合业务逻辑预期。
- 延迟:所有处理都延迟到下一帧,对于需要即时反馈的体验(如打击感)不利。
因此,一个成熟的引擎通常需要支持三种事件处理时机:立即执行、下一帧开始前、下一帧结束后。程序员需要根据业务逻辑谨慎选择。
脚本系统:实现灵活的游戏逻辑
有了事件系统,就可以开始编写游戏逻辑了。直接用C++等编译型语言硬编码虽然效率高,但存在三大问题:每次修改需重新编译、难以热更新、对非程序员(设计师、艺术家)不友好。
脚本语言是解决这些问题的关键。它的优势在于:
- 易学易用:语法通常更简单。
- 支持热更新:解释执行特性允许在运行时替换逻辑代码。
- 安全沙箱:脚本运行在虚拟机中,崩溃通常不会导致整个引擎崩溃。
- 面向非程序员:设计师和艺术家也能参与逻辑编写。
脚本系统的工作机制与对象管理
脚本本质上是文本,通过编译器生成字节码,由虚拟机执行。当脚本系统接入引擎时,一个核心问题是:游戏对象的生命周期由谁管理?
主要有两种流派:
- 引擎管理:C++内核负责对象的创建和销毁。脚本通过接口访问对象,每次访问需检查对象有效性。优点是引擎控制力强,内存管理严谨;缺点是脚本层业务逻辑创建对象需绕行引擎,不够灵活。
- 脚本管理:脚本系统负责对象的创建、交互和销毁,引擎只引用这些对象。脚本通过垃圾回收机制自动清理不再使用的对象。优点是符合脚本层业务逻辑,创建销毁灵活;缺点是GC可能带来性能开销,且需精心优化。
选择哪种方式取决于游戏类型。对象数量少但表现精细的游戏(如动作游戏)可能倾向引擎管理;玩法复杂、对象动态生成频繁的游戏(如MMORPG)可能倾向脚本管理。
脚本语言的选择
常见的游戏脚本语言各有特点:
- Lua:轻量、高效、内存占用小,与C++接口性能好,但标准库功能较少。
- Python:库丰富强大,面向对象,但虚拟机较重,内存占用大。
- C#:借助Mono或.NET环境,性能较好,生态强大,学*成本低(尤其对C/C++程序员),Unity引擎的成功证明了其可行性。
引擎需要根据目标游戏类型、团队技术栈和性能要求来选择合适的脚本语言。
可视化脚本:降低创作门槛
尽管脚本语言已经降低了门槛,但对于完全没有编程背景的设计师和艺术家来说,文本编程仍有困难。可视化脚本系统应运而生,例如虚幻引擎的蓝图和Unity的Visual Scripting。
可视化脚本将编程元素图形化:
- 变量:用不同颜色/形状的引脚表示类型和作用域。
- 表达式与语句:节点表示加减乘除或具体动作(如打印文字、震动屏幕)。执行流通过连线表示。
- 控制流:实现条件分支、循环等逻辑。
- 函数与类:可以将一系列节点封装成可复用的函数或类。
它的优势在于直观、错误预防(类型错误的连线无法连接)和调试友好(运行时高亮显示执行流和数值)。
然而,可视化脚本也有缺点:多人协作时合并困难、大型图表难以阅读和维护。因此,在实践中,可视化脚本常用于快速原型设计,而在工业化生产阶段,可能会转为用传统脚本或C++实现核心逻辑。
本质上,可视化脚本是一种图形化的脚本语言。其底层可以将图形逻辑编译成脚本代码或直接编译成字节码执行。
3C系统:角色、控制与相机
3C系统是构成游戏直接体验的核心,包括Character、Control和Camera。
角色系统
角色系统远不止模型和动画,核心在于移动和与环境的互动。优秀的角色移动包含大量细节:上下坡、跨越障碍、不同地面材质(冰面、沙地)的反馈等。这通常通过状态机来实现,结合动画树和大量的脚本逻辑,定义角色在各种情境下的行为。

控制系统
控制系统负责将输入设备的信号转化为游戏内的操作。它不仅仅是简单的映射,更致力于提供舒适的手感。例如:
- 瞄准辅助:在主机游戏中,通过轻微的“吸附”效果帮助玩家瞄准,以抵消输入延迟和操作精度问题。
- 多态输入:同一个按键在不同游戏状态下(行走、驾驶、瞄准)可以触发不同操作。
- 反馈:通过手柄震动、键盘RGB灯光变化等,增强操作的代入感。
相机系统
相机系统决定了玩家的视角和主观感受。它不仅仅是固定在角色身后的一个点,而是包含复杂逻辑:
- 弹簧臂:防止相机穿墙,*滑移动。
- 相机轨道:设计师预先定义角色在不同状态(奔跑、瞄准、驾驶)下的相机参数(位置、FOV),形成*滑的过渡。
- 相机效果:实现屏幕震动、击中特效等,增强打击感和沉浸感。
- 相机管理:管理多个相机(第一人称、第三人称、驾驶视角等)并在它们之间*滑切换。
一个设计精良的相机系统能极大地提升游戏的电影感和操作体验。
课程总结
本节课我们一起学*了Gameplay玩法系统的基础架构。
我们首先回顾了事件系统作为游戏对象间通讯的基石,探讨了其发布-订阅模式、事件定义、回调函数的安全性和事件队列的分发机制。
接着,我们分析了脚本系统如何为游戏逻辑提供灵活性和可扩展性,解决了编译型语言在快速迭代和热更新方面的不足,并讨论了脚本与引擎间的对象管理策略。
然后,我们介绍了可视化脚本如何进一步降低创作门槛,让设计师和艺术家也能参与逻辑构建,同时也指出了其在工程化管理上的挑战。
最后,我们剖析了构成游戏直接体验核心的3C系统,理解了角色控制、输入处理和相机设计如何共同塑造玩家的主观感受和游戏手感。


Gameplay系统边界模糊,涵盖极广,是游戏引擎中与“创意”和“体验”连接最紧密的部分。引擎的任务不是提供一个万能解决方案,而是提供一个足够灵活、开放的基础*台,支撑开发者去实现丰富多样的游戏创意。

15. 逆问题建模与ADMM算法求解 | GAMES204-计算成像 - P1 - GAMES-Webinar - BV1j8411Y7zY
概述

在本节课中,我们将学*逆问题建模和ADMM算法。我们将通过两个例子来展示如何使用ADMM算法解决逆问题,并了解其背后的原理。
逆问题
逆问题是指从观测数据中恢复出原始信号或图像的问题。在成像领域,常见的逆问题包括:
- 断层扫描:通过X光投影重建物体的三维结构。
- 磁共振成像:通过电磁波成像重建物体的内部结构。
- 高光谱成像:通过多个光谱通道的成像重建物体的化学成分。
逆问题通常可以表示为以下形式:
A * X = B
其中,A是成像模型,X是待恢复的信号或图像,B是观测数据。

ADMM算法

ADMM(Alternating Direction Method of Multipliers)是一种求解带约束优化问题的算法。它将优化问题分解为多个子问题,并交替求解这些子问题,直到收敛到最优解。
ADMM算法的基本步骤如下:
- 引入辅助变量:将约束条件引入拉格朗日函数,并引入辅助变量。
- 分解问题:将拉格朗日函数分解为多个子问题。
- 交替求解:交替求解每个子问题,直到收敛到最优解。
单像素相机
单像素相机是一种利用压缩感知原理进行成像的设备。它通过一个单像素传感器和一个数字微透镜阵列(DMD)来获取图像信息。

单像素相机的工作原理:



- DMD在图像传感器上呈现随机的二值图案。
- 光线通过DMD和物体,并投射到图像传感器上。
- 图像传感器记录下经过调制的信号。
- 通过分析观测数据,恢复出原始图像。

高维压缩感知



高维压缩感知是指将压缩感知技术应用于高维数据,例如视频、光场图像等。
高维压缩感知的挑战:

- 数据维度高,计算复杂度高。
- 需要设计高效的算法来处理高维数据。



总结





本节课介绍了逆问题建模和ADMM算法,并通过两个例子展示了如何使用ADMM算法解决逆问题。我们了解到,ADMM算法是一种简单有效的方法,可以用于解决各种带约束优化问题。

课程16:游戏引擎Gameplay玩法系统:基础AI (Part 1) 🧠
在本节课中,我们将要学*游戏人工智能系统的基础知识。我们将从最核心的导航系统开始,了解AI角色如何在虚拟世界中理解环境、规划路径并*滑移动。这是构建任何智能行为的第一步。
社区互动与课程安排调整
在开始今天的课程正文之前,首先分享一个好消息:我们GAMES104社区的T恤设计完成了。设计体现了“游戏引擎探索者”的精神,共有两款供大家选择。社区成员可以通过投票决定最终款式,幸运参与者将获得纪念T恤。
接下来,回答几个来自学*群的问题。
第一个问题是关于游戏逻辑的架构。现代游戏引擎普遍采用组件化架构。这种方法符合人类对世界的认知方式,也便于利用现代CPU的并行计算能力。虽然它可能存在效率上的局限,但仍是当前行业的主流选择。
第二个问题是关于蓝图系统的多人协同开发。这是一个公认的难题。蓝图作为一种可视化图表,其修改和合并非常困难,因为它不像场景编辑那样“所见即所得”。目前行业内尚未找到成熟的蓝图协同解决方案。
第三个问题是关于事件系统的优先级设置。虽然为消息设置优先级在网络通信中很常见,但在游戏逻辑的事件系统中,我们通常避免这样做。因为定义优先级意味着假设了事件的处理顺序,这会增加系统模块间的耦合度,不利于并行化扩展和系统维护。设计原则是尽量让事件处理顺序独立。
为什么是“基础”AI?
在准备人工智能这节课时,我们发现内容非常丰富,达到了180页。为了更深入、更清晰地讲解这个有趣且核心的领域,我们决定将AI课程拆分为两部分。
本节课是基础AI,将涵盖寻路、导航、群体模拟、环境感知和基础决策算法。这些是构建任何高级AI系统的基石。
下节课将探讨更复杂的现代AI系统,例如基于目标的AI、基于计划的AI,以及大家可能感兴趣的深度学*在游戏AI中的应用。
导航系统:智能移动的基石 🗺️
AI要表现出智能,首先需要能在游戏世界中自如地移动。这就像人类需要知道哪里可以走、哪里是障碍一样自然。在游戏中,这由导航系统负责。
一个完整的导航系统通常包含三个步骤:
- 世界表达:将游戏世界转化为计算机可以理解的数据结构。
- 路径查找:在数据结构中,计算从起点到终点的可行路线。
- 路径*滑:优化计算出的原始路径,使其看起来更自然、*滑。
世界的表达方式
计算机无法直接理解美术关卡,我们需要用特定的数据结构来定义“可行走区域”。
以下是几种常见的表达方式:
-
路点网络
这种方法在早期游戏中很常见。设计师手动放置关键点并连接成网络,AI像乘坐地铁一样,先走到最*的路点,再沿网络移动,最后离开网络走向终点。其优点是实现简单、效率高;缺点是维护麻烦,且AI行为容易显得僵硬、只沿固定路线移动。 -
网格
将世界划分为均匀的方格,用格子标记是否可通行。其优点是易于实现、调试和动态更新;缺点是数据存储和访问开销大,且难以表达多层结构。 -
导航网格
这是现代3D游戏中最主流的方法。它将所有可行走区域用一系列凸多边形覆盖。AI可以在多边形内部自由移动。其优点是能高效表达大面积区域、支持3D层叠结构;缺点是自动生成算法复杂,且动态更新时需要技巧。 -
空间八叉树
对于飞行单位等需要在3D空间自由移动的AI,可以使用八叉树来划分空间。它能精细地表达复杂的3D可行走体积,但存储和计算开销也更大。
无论采用哪种表达方式,最终都会在内部形成一个图数据结构,其中节点代表位置,边代表连接关系。
路径查找算法
在“图”上寻找路径是一个经典的搜索问题。我们需要找到一条连通且尽可能短的路径。
以下是核心算法:
-
迪杰斯特拉算法
这是一个能保证找到最短路径的经典算法。它从起点开始,逐步探索所有可能的方向,并不断更新到达每个节点的最短距离。虽然准确,但在游戏这种往往不需要绝对最优解的场景下,可能显得效率不足。 -
A 算法*
这是游戏寻路的事实标准。它在迪杰斯特拉算法的基础上,加入了一个启发式函数来估算当前点到终点的剩余距离。算法会优先探索“已走距离 + 预估剩余距离”总和最小的方向。这使得它能更快地导向目标,在游戏这种障碍相对稀疏的环境中效率极高。- 启发函数示例(网格):
H = |当前点.x - 终点.x| + |当前点.y - 终点.y|(曼哈顿距离) - 启发函数示例(导航网格):
H = 当前点与终点的直线距离(欧几里得距离)
- 启发函数示例(网格):
路径*滑算法
通过A*等算法找到的路径往往是折线状的,AI沿之移动会显得不自然。我们需要进行*滑处理。
漏斗算法是常用的*滑算法。其核心思想是:在由一系列凸多边形构成的“走廊”中,尽可能拉直路径。
- 从起点开始,将其与第一个“门户”(连接多边形的边)的两端相连,形成一个扇形区域。
- 检查下一个门户是否完全在此扇形区域内。如果是,则收缩扇形区域。
- 如果不是,则意味着需要拐弯。选择拐弯点(通常是当前扇形的一个边界点),并以此点为新起点,重复上述过程。
- 当终点落入当前的扇形区域内时,即可用直线连接当前点与终点。
经过漏斗算法处理后,曲折的原始路径会被优化为一条更贴*障碍物拐角、更自然的*滑路径。
导航网格的生成
导航网格通常由工具自动生成,其中最著名的开源库是Recast。其生成过程大致分为以下几步:
- 体素化:将3D场景转换为类似《我的世界》的体素网格。
- 标记可行走区域:根据最大爬坡角度、跳跃高度等规则,在体素中标记出可行走的部分。
- 划分区域:通过计算距离场等方式,将连通的可行走体素聚类,划分成不同的区域。
- 生成凸多边形:将每个区域转化为一个或多个凸多边形,最终形成导航网格。
在实际项目中,自动生成的导航网格常需要设计师手动干预,例如标记特殊区域(沼泽、公路)、添加攀爬点或传送门连接等,以应对游戏玩法的特殊需求。
本节总结
本节课中,我们一起学*了游戏AI系统的基础——导航与寻路。

我们首先了解了为什么需要将AI课程分为两部分,并明确了本节课的基础定位。接着,我们深入探讨了导航系统的完整流程:从世界表达(路点、网格、导航网格)到路径查找(迪杰斯特拉算法、A*算法),再到路径*滑(漏斗算法)。最后,我们简要介绍了导航网格的自动生成原理。

掌握这些基础知识,是让AI角色在游戏世界中实现智能移动的前提。在下节课中,我们将基于这些移动能力,探讨AI如何感知环境、做出决策,并构建更高级的智能行为系统。

课程16:游戏引擎Gameplay玩法系统:基础AI (Part 2) 🧠

在本节课中,我们将要学*游戏AI系统的核心组成部分,特别是寻路之后的行为控制、群体模拟以及AI决策的基础。我们将深入探讨转向行为、群体模拟算法,并重点介绍经典的AI决策方法——有限状态机和行为树。通过本课,你将理解如何让游戏中的角色行为看起来更自然、更智能。
转向行为 🚗
上一节我们介绍了寻路系统,它能为角色找到一条从起点到终点的路径。然而,在实际游戏中,角色(尤其是载具)并不能像瞬移一样严格沿着这条直线路径移动。它们受到物理规律的限制,例如加速度、减速度和转弯半径。因此,我们需要一个转向系统来处理这些动态调整。
转向行为主要解决角色如何*滑、自然地沿着路径移动,并避免不自然的“卡死”或抖动现象。其核心可以被归纳为三大基本行为:

以下是三种核心的转向行为:
- 追逐/逃离:角色根据目标点的位置,计算加速度以接*或远离目标。
- 速度匹配:角色不仅需要到达目标点,还需要在到达时与目标的速度(包括静止状态)相匹配。这类似于航天器对接时的复杂速度调整。
- 朝向对齐:角色调整自身的朝向,以与目标或群体的*均朝向保持一致。这模拟了鱼群或飞机编队的集体行为。
具体来说:
- 追逐/逃离 会根据角色与目标的相对位置动态计算加速度。它还有一些变种,如巡逻(在路径点间移动)和在矢量场中运动(角色沿着预设的方向场移动)。
- 速度匹配 更为复杂。当目标静止或匀速直线运动时,可以通过物理公式(如
s = 1/2 * a * t²)计算。当目标非匀速运动时,可以采用分步计算的方法:在每一帧根据目标的当前速度和位置重新计算所需的加速度,通过多次迭代逼*理想结果。这种方法虽然不精确,但能产生足够自然的效果。 - 朝向对齐 同样需要考虑角加速度和角减速度,以确保转向过程*滑,避免机器人般的瞬间转向,这对于提升角色行为的真实感至关重要。
转向系统是提升角色行为自然度的关键,特别是在处理载具、人类或生物角色时。如果没有它,即使有完美的寻路和决策,角色行为也会显得僵硬。
群体模拟 👥
讲完转向行为,我们来看看它的一个重要应用场景:群体模拟。在现代游戏中,我们经常需要处理大量NPC的集体运动,例如广场上的鸽群、城市中的行人或RTS游戏中的士兵军团。
群体模拟主要有三种处理方法:
以下是三种群体模拟方法:
- 微观方法:定义每个个体的简单规则,群体行为自下而上涌现。例如,每个个体遵循三条规则:离邻居太*时产生斥力,离群体太远时产生引力,并尝试与邻居的朝向对齐。这种基于规则的模拟非常适合表现鱼群、鸟群等自然群体。
- 宏观方法:预先定义整个群体的运动趋势或路径网络,个体遵循这些宏观指令移动。例如,在城市模拟中,设计师会预先绘制人行道、车道等路径图,NPC沿着这些固定路径行走,这使得人类群体的行为更加有序和可控。
- 混合方法:结合宏观指导和微观规则。例如在RTS游戏中,玩家指定一个宏观目标点,士兵群体整体向该点移动(宏观),但每个士兵在移动过程中会根据微观规则(如避让友军)自主调整位置。
在群体模拟中,避免碰撞是一个核心问题。除了简单的斥力模型,还有一些更高级的算法:
- 力场法:一种廉价有效的方法。为环境中的障碍物生成一个距离场。个体在移动时,会感受到距离场产生的斥力,从而自动绕开障碍物。这种方法不仅用于游戏,也应用于数字孪生中的应急疏散模拟。
- 速度障碍法:更复杂、更精确的算法。其核心思想是,每个运动物体都会在其他物体的速度空间中形成一个“障碍区域”。个体通过调整自己的速度来避开这些障碍。其优化版本ORCA算法可以在理论上为密集群体找到最优的避让路径。虽然结果优雅,但实现复杂、计算开销大。
在商业引擎中,通常会同时提供力场法和速度障碍法,开发者可以根据性能需求和效果要求进行选择。一个专门的碰撞避免系统对于处理大量NPC的群体行为至关重要。
环境感知 🎯
在进入具体的决策算法之前,AI系统还需要一个基础能力:环境感知。AI的所有决策都必须基于对游戏世界的动态感知,这类似于人类依赖视觉、听觉等信息做出判断。
环境感知的信息可以分为以下几类:
以下是环境感知的主要信息类型:
- 自身状态:例如血量、速度、弹药数量等。这些信息访问速度快,是AI决策的直接依据。
- 静态空间信息:
- 可行走区域:即导航网格。
- 战术地图:由设计师手动标记的关键战术点(如桥头、掩体),AI会优先争夺这些区域。
- 智能对象:环境中可交互的物体,如梯子、可破坏的墙、掩护点等。
- 动态空间信息:
- 影响力图:一种动态的热力图,用于表示战场上不同区域的威胁值、友军密度等。AI通过查询此图来决定行动(如避开高危区域)。
- 动态寻路信息:因其他角色行动或事件发生而改变的路径信息。
- 视野与听觉:AI应像玩家一样,只能感知到视野内或听觉范围内的目标。模拟视觉(有方向性)和听觉(360度,但受障碍物衰减)能大幅提升AI的真实感和游戏的战术深度。
实现感知系统时需要注意两点:一是提供统一的接口供AI查询各种异构数据(数值、布尔值、数组、空间坐标等);二是性能优化,避免大量AI每帧进行全范围感知计算,可以通过调整感知精度、范围或共享感知结果来优化。
经典决策算法:状态机与行为树 ⚙️
有了导航、转向和感知作为基础,我们终于可以探讨AI的核心——决策算法。本节将介绍两种经典的前向决策方法:有限状态机和行为树。
有限状态机
有限状态机是一种非常直观的模型。AI的行为被定义为一系列状态,以及连接这些状态的转换条件。当条件满足时,AI就从当前状态切换到下一个状态。
一个经典的例子是《吃豆人》中的幽灵AI:
- 状态:闲逛、逃跑、追逐。
- 转换:闲逛时看到玩家 -> 逃跑;逃跑时吃了能量丸 -> 追逐;追逐时能量丸效果结束 -> 逃跑。
FSM的优点是简单直接,但当状态和转换非常多时,会变得难以维护和理解(“面条代码”)。为此,人们引入了分层有限状态机,将大状态机分解为多个子状态机,通过标准接口交互,提高了可读性,但可能会牺牲一些反应的直接性。
行为树
行为树是更符合人类规划思维的决策模型。它将AI行为组织成一棵树,节点类型主要包括:
以下是行为树的主要节点类型:
- 控制节点:控制执行流程。
- 序列节点:按顺序依次执行所有子节点。如果任一子节点失败,则整个序列失败。常用于定义一系列连续动作(如:走到门前->解锁->开门->进入)。
- 选择节点:按优先级从左到右执行子节点,直到有一个成功为止。常用于定义决策优先级(如:优先攻击->其次寻找掩护->最后逃跑)。
- 并行节点:同时执行所有子节点。这使得AI可以“多线程”处理任务(如:一边向目标点移动,一边警戒并攻击沿途敌人)。
- 执行节点:实际执行动作的节点。
- 条件节点:进行逻辑判断(如:敌人是否在视野内?)。
- 动作节点:执行具体行为(如:移动、攻击)。动作节点有运行中、成功、失败三种状态。
- 装饰节点:修饰其他节点行为的节点,例如让一个动作循环执行、延迟执行或增加前提条件。
行为树的美妙之处在于,它用少数几种节点类型,就能清晰、可维护地表达复杂的AI逻辑。它通常每帧从根节点重新开始评估,这保证了AI能及时响应环境变化(打断当前行为)。为了优化性能,一些引擎会引入事件驱动机制,只在相关事件发生时重新评估部分子树。
行为树还常与黑板系统结合。黑板是一个共享的数据存储,行为树的不同节点可以在黑板上读写数据,从而实现信息交换(例如,一个节点记录“已杀死敌人”,另一个节点读取后执行“庆祝动作”)。
与 if-else 语句相比,行为树提供了更高层次、更结构化、更易于设计和调试的抽象,特别适合描述具有中断、并行和层次化特点的复杂行为。
总结 📚
本节课中,我们一起学*了游戏AI系统的三大基础支柱:
- 转向行为:让角色的移动更符合物理规律,自然*滑,涉及追逐、速度匹配和朝向对齐。
- 群体模拟:处理大量NPC的集体运动,包括微观规则、宏观控制和混合方法,并探讨了碰撞避免算法。
- 环境感知:AI获取自身状态、静态与动态环境信息的能力,是决策的依据。
- 经典决策算法:深入讲解了有限状态机的直观与局限,以及行为树如何通过序列、选择、并行等节点,以更优雅、可维护的方式构建复杂AI逻辑。
这些系统共同构成了AI的“基础设施”。没有它们,再聪明的决策算法也无法在游戏世界中有效执行。在下一节课中,我们将探讨更前沿的、以目标为导向的AI决策算法,如HTN、GOAP以及基于深度学*的方法,看看AI如何从“反应式”思维迈向“规划式”思维。


本节课内容基于GAMES104课程《现代游戏引擎:从入门到实践》第十六讲整理。



16. 计算照明 (I) | GAMES204-计算成像 - P1 - GAMES-Webinar - BV1HP4y1U7QV
概述

在本节课中,我们将学*计算照明的基本概念,包括曝光时长、光强度、光源位置和照明方向等参数对成像系统的影响。
曝光时长和光强度
曝光时长


曝光时长是指相机感光元件接收光线的时间。它对图像的亮度和清晰度有重要影响。

- 短曝光:可以捕捉快速移动的物体,但容易产生运动模糊。
- 长曝光:可以捕捉静态物体,但容易受到环境光的影响。

光强度

光强度是指光线的亮度。它对图像的亮度和清晰度有重要影响。


- 高光强度:可以使图像更亮,但容易产生过曝。
- 低光强度:可以使图像更暗,但容易产生噪点。
光源位置和照明方向

光源位置



光源位置对图像的阴影和光照效果有重要影响。


- 前向照明:光源位于相机前方,容易产生明暗对比强烈的图像。
- 背向照明:光源位于相机后方,可以使物体边缘产生柔和的阴影。
- 侧向照明:光源位于相机侧面,可以使物体产生丰富的阴影和光照效果。



照明方向

照明方向对图像的阴影和光照效果有重要影响。

- 直射照明:光线直接照射到物体上,容易产生明暗对比强烈的图像。
- 散射照明:光线经过散射后照射到物体上,可以使图像更柔和。

应用案例
HDR 捕获



HDR 捕获技术可以通过调整曝光时长和光强度来捕捉不同亮度级别的图像,从而获得更丰富的细节和更低的噪点。


光照合成

光照合成技术可以将不同光源和照明方向下的图像进行合成,从而获得更真实、更丰富的光照效果。


深度相机


深度相机可以通过多角度照明来提取物体的深度信息,从而实现三维重建和深度感知。
总结


本节课介绍了计算照明的基本概念和应用案例。通过学*这些内容,我们可以更好地理解照明对成像系统的影响,并掌握一些基本的照明技术。

课程17:游戏引擎Gameplay玩法系统:高级AI (Part 1) 🤖

在本节课中,我们将深入探讨现代游戏引擎中的高级AI系统。我们将从传统的层次任务网络(HTN)开始,然后介绍基于目标的规划(GOAP),最后触及蒙特卡洛树搜索(MCTS)的基本思想。这些方法构成了构建复杂、动态游戏AI的基础。
课程概述与社区反馈
首先,我们回顾了上一周社区的热烈反馈。许多同学分享了学*心得,例如利用假期学*课程进行“算法胎教”,或从学生转变为社会人后继续学*。这些互动体现了社区的价值。
我们宣布了T恤投票结果,选择了第一款设计。我们将继续在每节课后选取幸运同学赠送T恤,以感谢社区的参与。此外,我们设立了一个专用邮箱,用于接收课程相关的非公开建议或优秀笔记。
接下来,我们回答了社区提出的几个问题。
关于AI“读指令”:有同学询问《艾尔登法环》中AI读取玩家输入是否代表未来方向。从经典游戏AI设计角度看,这通常被视为“作弊”,因为AI不应获取玩家额外信息。然而,在动作要求极高的游戏中,为了提升反应速度和游戏乐趣,这种做法是合理且可接受的。但这并非所有类型游戏AI的通用做法。
关于AI计算预算:AI的预算分配因游戏类型而异。例如,策略游戏可能分配较多时间进行精细计算,而实时射击游戏则预算较少。AI的更新频率(ticking)也与渲染分离,可以进行低频更新(如每秒几次),其架构通常是分层的。
关于分布式AI系统:将AI计算从客户端分离,部署到独立服务器或云端,已是现代游戏的常见实践。这种架构符合解耦原则,使服务独立维护,通过数据协议交换,提升了系统的鲁棒性。但对于手游等场景,仍需在设备本地进行计算。
回答完问题后,我们正式进入今天的核心主题:高级AI系统。本课程内容密度很大,涵盖了从传统方法到机器学*基础的多个领域。
层次任务网络(HTN)📊
上一节我们介绍了行为树(Behavior Tree),它是一种反应式机器。本节我们来看看层次任务网络(HTN),它从任务目标出发,更符合人类制定计划的直观思维。
HTN的核心思想是像人类一样制定计划。例如,“学*一门课”可以分解为“准备资料”、“到教室”、“学*”、“课后问答”等子任务。每个子任务又可能有多种完成方式(如查资料可选择去图书馆或上网)。这种结构非常直观。
HTN框架组成
HTN框架主要由以下几部分组成:
- 世界状态(World State):这不是客观世界的真实描述,而是AI体主观认知中对世界关键要素的提炼。
- 感知器(Sensor):负责从游戏环境中抓取各种状态,类似于之前讲的感知(Perception)系统。
- HTN域(HTN Domain):存放层次化的树状任务(Task)及它们之间的关联关系。
- 规划器(Planner):根据当前感知到的世界状态,在HTN域中制定一个可执行的任务序列计划。
- 计划执行器(Plan Runner):依次执行计划中的任务,并监控执行状态,在任务失败或世界状态发生重大变化时触发重新规划(Replanning)。
任务类型:原子任务与复合任务
在HTN中,任务分为两种基本类型:
-
原子任务(Primitive Task):是最基本的任务单元,通常对应一个具体动作。它包含三个关键要素:
- 动作(Action):任务执行的具体内容。
- 前提条件(Precondition):检查世界状态,只有条件满足该任务才能执行。
- 效果(Effect):任务成功执行后,对世界状态的修改。
例如,“使用解药”任务的前提是“拥有解药”,效果是“中毒状态解除”和“解药数量减一”。
-
复合任务(Compound Task):是HTN的核心,代表一个需要分解完成的高级目标。它由多个方法(Method)构成:
- 每个方法都有一组前提条件。
- 方法之间具有优先级(Priority),系统会按顺序尝试满足条件的方法。
- 一个方法内部包含一个需要依次执行的子任务序列(可能是原子任务或其他复合任务)。
可以这样理解:复合任务中方法的选择类似于行为树中的选择器(Selector),而方法内子任务的依次执行则类似于序列器(Sequencer)。
HTN的运行流程
- 定义根任务(Root Task):设计师首先定义一个复合任务作为AI行为的最高层目标,例如“生存”可能包含“解毒”、“逃跑”、“攻击”、“闲逛”等子目标。
- 制定计划(Planning):规划器从根任务开始,根据当前世界状态,按优先级选择方法,并递归地将复合任务分解为原子任务序列。这个过程会在一个世界状态的“副本”中进行推演,假设所有任务都能成功,以验证计划的可行性。
- 执行与重规划(Execution & Replanning):计划执行器按顺序执行原子任务。如果任务执行失败,或感知器发现世界状态发生了重大变化(如战略目标改变),则会丢弃当前计划,触发重新规划。
HTN的优缺点
优点:
- 直观易懂:以目标为导向,非常符合设计师的思维模式。
- 支持长期规划:能方便地规划一系列连贯行为。
- 执行高效:只有在需要时才进行全局规划,相比行为树每帧遍历效率更高。
缺点:
- 配置复杂:需要精心配置大量任务的前提条件和效果,容易出错,需要工具支持静态检查。
- 环境适应性:在高度不确定的动态环境中,过于长远和缜密的计划容易频繁失效,导致AI行为出现“震荡”,显得不靠谱。
HTN是对行为树的一种很好的抽象和总结。接下来,我们将探讨另一种强大的规划方法。
基于目标的规划(GOAP)🎯
上一节我们介绍了HTN,它通过树状结构隐式地表达了目标。本节我们来看看基于目标的规划(GOAP),它显式地用数学状态定义目标,并通过逆向搜索求解规划问题。
GOAP与HTN架构相似,都包含感知器、世界状态、规划器和计划执行器。但其核心组件不同:
- 目标集(Goal Set):所有AI可能追求的目标,每个目标都显式地定义为一组希望达成的世界状态(例如:
{存活: True, 中毒: False})。目标具有优先级,通过前提条件来排序。 - 动作集(Action Set):所有可执行的动作。每个动作包含:前提条件(Precondition)、动作(Action)、效果(Effect) 和 成本(Cost)。成本由设计师设定,用于评价动作的优劣。
- 规划问题(Planning Problem):给定一个目标,如何找到一系列动作,使得执行后世界状态满足目标要求,并且总成本尽可能低。
GOAP的核心思想:以终为始
GOAP采用逆向规划。例如,目标“解读”要求状态{中毒: False}。系统会寻找效果能满足该状态的动作,比如“使用解药”。然后,“使用解药”的前提条件(如{拥有解药: True})成为了新的待满足子目标。接着寻找能达成{拥有解药: True}的动作(如“购买解药”或“制作解药”),以此类推,直到所有子目标都能被当前世界状态满足。
将规划问题转化为图搜索问题
GOAP的巧妙之处在于将规划转化为图搜索问题:
- 节点(Node):表示一组世界状态的组合。
- 边(Edge):表示一个可执行的动作,其权重是该动作的成本。
- 起点:是目标状态。
- 终点:是当前世界状态。
规划器的工作就是在这样构建的图中,寻找一条从目标状态到当前状态的路径,并且希望路径总成本最低。这可以使用A*等搜索算法来解决。启发函数(Heuristic)可以设计为当前状态与目标状态的“距离”。
GOAP的优缺点
优点:
- 行为动态:即使同一目标,在不同环境下也会产生不同的执行路径,常常能产生超出设计师预期的智能行为。
- 目标与行为解耦:目标被显式、定量地定义,与具体行为逻辑分离,提高了灵活性。
缺点/挑战:
- 计算开销大:构建状态图和进行图搜索的计算量通常大于HTN或行为树。
- 状态量化要求高:需要将复杂的游戏世界状态抽象为一系列离散的、最好是布尔值的状态,这对于高度复杂的游戏(如RTS)来说颇具挑战性。
GOAP非常适合于环境相对确定、状态可以量化的单机或顺序性游戏。接下来,我们将目光投向一种更“跨界”的算法。
蒙特卡洛树搜索(MCTS)简介 🌳
前面我们讨论了HTN和GOAP这类基于符号逻辑的规划方法。本节我们简要介绍蒙特卡洛树搜索(MCTS),它介于传统算法与机器学*之间,并通过随机模拟来评估决策,在复杂博弈中取得了巨大成功(如AlphaGo)。
MCTS的核心思想是模拟人类棋手的“推演”过程:面对当前棋局,在脑中快速模拟未来多种可能的走法序列,然后选择胜率最高的那一步。
MCTS的基本概念
- 状态(State):对当前博弈局面的一个快照(如棋盘上所有棋子的分布)。
- 动作(Action):在某个状态下可以采取的操作(如在围棋的(x,y)点落子)。
- 状态空间(State Space):所有可能状态通过动作连接形成的树状结构。由于组合爆炸,该空间极其庞大。
- 模拟(Simulation):从某个状态开始,使用一个默认策略(Default Policy)(如基于棋谱的规则)快速地将棋局进行到终局,并得到一个结果(赢/输)。
- 节点评估:每个树节点维护两个值:
- N:该节点被访问(模拟)的总次数。
- Q:从该节点出发的模拟中,获胜的总次数。
Q/N可*似看作从该节点出发的胜率。
MCTS的迭代步骤
MCTS通过不断迭代来构建和更新搜索树,每次迭代包含四个步骤:
- 选择(Selection):从根节点(当前状态)开始,使用一种权衡“开发(Exploitation)”与“探索(Exploration)”的公式(如UCT算法),递归地选择子节点,直到找到一个可扩展的节点(即该节点还有未尝试过的合法动作)。
- 扩展(Expansion):为上一步选中的节点,添加一个或多个新的子节点(代表新的动作)。
- 模拟(Simulation):对新增的节点,运行一次默认策略的快速模拟,直到终局,得到胜负结果。
- 反向传播(Backpropagation):将本次模拟的结果(胜/负)沿着被访问过的节点路径反向更新,增加它们的访问次数N,并根据胜负更新Q值。
决策与策略
经过一定时间或迭代次数的搜索后,算法停止。此时,需要根据构建的树来决定下一步动作(即选择根节点的哪个子节点)。常见策略有:
- 最大胜率子节点(Max Child):选择
Q/N值最高的节点。 - 最稳健子节点(Robust Child):选择访问次数N最大的节点。
- 最大稳健子节点(Max-Robust Child):寻求Q和N都较大的节点。
- 最大下限置信区间子节点(Max-LCB Child):使用一个考虑胜率和访问次数置信区间的公式进行选择。
MCTS的适用性与思考
MCTS能产生灵活、智能的行为,其决策一定程度上超出了设计师的预设。但它计算复杂度高,且要求能对动作结果和最终胜负进行明确评估。它非常适合回合制(Turn-based)、动作结果明确的博弈问题(如棋类、某些策略游戏的大地图回合)。对于更复杂的实时游戏,常需要与其他AI架构(如行为树)结合使用。
课程总结
本节课我们一起学*了游戏引擎中三种高级AI系统的基础知识:
- 层次任务网络(HTN):以目标为导向,通过树状结构分解任务,直观且高效,适合设计师构建长期行为规划。
- 基于目标的规划(GOAP):显式定义目标状态,通过逆向搜索和成本计算生成动态规划,行为灵活,适合状态可量化的环境。
- 蒙特卡洛树搜索(MCTS):通过随机模拟和树搜索来评估决策,擅长解决复杂的博弈问题,计算开销大但能产生超乎预期的智能。
每种方法都有其适用的场景和优缺点。在实际游戏开发中,常常需要根据具体需求混合使用这些技术,甚至结合我们即将在下一部分探讨的机器学*方法,来构建真正强大而有趣的游戏AI。





课程17:游戏引擎Gameplay玩法系统:高级AI (Part 2) 🤖
在本节课中,我们将深入探讨机器学*在游戏AI中的应用。我们将从最基础的机器学*概念讲起,并通过著名的AlphaStar(星际争霸AI)案例,了解如何将深度强化学*框架应用于复杂的游戏场景中。课程内容力求简单直白,让初学者也能理解这些前沿技术的基本思路。
概述
本节课的核心是介绍机器学*,特别是强化学*,如何被用于构建更智能、更具适应性的游戏AI。我们将避开复杂的数学推导,聚焦于基础概念和实际应用框架。
机器学*基础概念
上一节我们介绍了传统游戏AI技术,如行为树和HTN。本节中,我们来看看如何利用机器学*让AI“自我进化”。
机器学*是一个宽泛的概念,主要分为几个大的应用方向:
以下是几种主要的机器学*类型:
- 监督学*:本质是一个分类器。人类提供带有标签的数据(例如,1万张标注了“有猫”或“无猫”的照片)进行训练,之后AI就能对新数据(如100万张新照片)自动分类。
- 无监督学*:本质是聚类。给定大量无标签数据,AI自动在其中发现规律并进行分组。例如,在用户画像分析中,AI可以将海量用户数据自动分成不同类型。
- 半监督学*:结合了少量标签数据和大量无标签数据进行学*。这类似于人类的“小样本学*”能力,是当前AI研究的重要领域。
- 强化学*:没有监督者告诉AI对错。AI通过与环境互动,根据“奖励”和“惩罚”信号,自我优化迭代,最终形成自己的行为策略。这是游戏AI中非常关键的技术。
强化学*可以被理解为一种“试错搜索”。其难点在于奖励常常是延迟的。例如,在“老鼠走迷宫”游戏中,只有走到终点(获得奶酪)或掉入陷阱时,才会获得明确的正向或负向奖励,而在迷宫中的每一步本身可能没有即时奖励。
马尔可夫决策过程
为了数学化地描述强化学*问题,很早就有了马尔可夫决策过程这个框架。
想象一个智能体在环境中生存:
- 智能体:做出决策的实体(如游戏中的角色)。
- 环境:智能体所处的世界(如游戏地图)。
- 状态:在某一时刻,环境与智能体的具体情况。
- 动作:智能体可以执行的操作。
- 奖励:环境根据智能体的动作和状态给予的反馈。
这个过程形成一个循环:状态 -> 动作 -> (新)状态 -> 奖励 -> 状态 -> ...。
马尔可夫决策过程的核心数学表达涉及几个关键概念:
- 状态转移概率:在状态
s下执行动作a,转移到新状态s'的概率。这是一个概率函数,而非确定值,因为环境存在不确定性。公式:
P(s' | s, a) - 策略:智能体的决策核心。输入一个状态,它输出应该执行各个动作的概率分布。
公式:
π(a | s), 例如在超级马里奥中,策略可能输出:{左移: 0.7, 右移: 0.1, 跳跃: 0.2}。 - 总奖励:智能体从当前时刻开始,未来能获得的所有奖励之和。由于未来奖励不确定,通常会乘以一个折扣因子
γ(0 < γ ≤ 1),使得越远的奖励权重越低。这*衡了短期收益与长期目标。
在游戏中应用机器学*
了解了基础概念后,我们来看看如何在游戏里构建一个基于机器学*的AI框架。这并没有想象中那么神秘,关键在于将游戏世界转化为AI能理解的数据。
以下是构建此类AI系统的关键步骤:
- 定义状态:将游戏内所有相关信息定量化表达。例如,在RTS游戏中,状态可能包括:资源量、单位位置/血量、战争迷雾地图、威胁度分布图等。
- 定义动作:明确AI可以指挥游戏单位做什么。这需要非常具体,例如:“命令ID为X的单位移动到坐标(Y, Z)”。
- 设计奖励函数:引导AI学*的方向。最简单的是只设置胜负奖励(赢+1,输-1)。更精细的设计会加入中间奖励(如击杀敌人、采集资源给予小奖励),以加速学*并塑造特定行为风格。
- 选择网络架构:根据游戏状态数据的类型(图像、序列、数值),选择合适的神经网络进行处理(如CNN处理地图,Transformer处理单位列表,LSTM处理记忆)。
- 制定训练策略:如何高效地训练AI。常见策略包括:先用人类玩家数据进行监督学*(模仿),再进行自我博弈的强化学*(进化)。
游戏AI的挑战比围棋等棋类游戏更大,因为游戏通常信息不对称(有战争迷雾)、动作空间巨大且结果具有高度不确定性。
案例分析:AlphaStar
接下来,我们通过DeepMind的AlphaStar项目,具体看上述框架如何落地。
AlphaStar的目标是训练能玩《星际争霸2》的AI。
1. 状态表达
- 地图信息:如地形高度图、战争迷雾图、可通行区域图、威胁度图等,这些被处理为多层图像数据。
- 单位信息:所有可见单位的类型、阵营、血量、位置等,这些是结构化的列表数据。
- 统计信息:玩家当前的资源、人口等,这些是简单的数值数据。
2. 动作定义
明确“谁对谁做什么”,例如:选择一组单位,命令它们攻击某个特定目标或移动到某个位置。
3. 奖励设计
AlphaStar主要以最终胜负作为奖励。但它也引入了一些辅助奖励项,例如,使AI的动作分布与人类高手的动作分布尽可能接*(通过KL散度衡量),以引导其学*更接*人类的策略。
4. 网络架构
AlphaStar采用了一个复杂的编码器-解码器架构:
- 编码器:一个“大脑”,由多个子网络组成。它接收所有状态数据(地图、单位列表、统计信息),分别用CNN、Transformer、MLP等网络处理,再通过LSTM层融合并产生记忆,最终输出一个代表当前决策的抽象向量。
- 解码器:将编码器输出的抽象向量“翻译”成游戏能理解的具体动作指令。
5. 训练策略 - 联盟训练
这是AlphaStar最精妙的部分之一。为了克服AI自我博弈容易陷入“局部最优”和“风格单一”的问题,它采用了“联盟训练”法:
- 主AI:不断自我进化。
- 历史对手池:主AI不仅和当前最强的自己打,还会随机和过去版本的自己打,防止“忘本”。
- 剥削者AI:专门训练一些AI去寻找主AI和历史AI的弱点并加以针对。这就像军队中的“蓝军”,迫使“红军”主AI必须发展出更全面、更通用的策略来应对各种不同风格的挑战。
这种多角色、多风格的训练体系,有效提升了AI的泛化能力和策略深度。
实用考量与总结
本节课我们一起学*了机器学*,特别是强化学*在游戏AI中的应用框架。
最后,我们需要从实用角度进行总结。在游戏开发中应用这些技术,必须考虑费效比。
- 监督学* vs. 强化学*:
- 如果拥有大量高质量的玩家数据,监督学*是快速获得一个表现不错AI的捷径,成本相对较低。
- 强化学*潜力上限更高,能产生令人惊艳的突破性策略,但训练极其复杂、耗时且昂贵(例如AlphaStar训练成本高达数亿美元)。
- 混合架构:在真实游戏中,不必全盘采用深度学*。一种实用的方法是混合架构:
- 宏观战略:用轻量级神经网络或传统AI(如行为树)制定高级目标(进攻/防守)。
- 微观操作:用简单的规则或小型网络控制单位的具体行为。
- 这样既能获得智能的决策表现,又能将计算和训练成本控制在合理范围内。
游戏AI没有“银弹”,本节课介绍的所有算法(包括本系列的Part 1),都应根据游戏的具体需求、预算和期望效果,在正确的场合选择使用。它们共同构成了现代游戏智能的工具箱。
本节课中,我们一起学*了:
- 机器学*(监督/无监督/强化学*)的基础概念。
- 描述强化学*问题的马尔可夫决策过程框架。
- 在游戏中构建机器学*AI的五个关键步骤。
- 通过AlphaStar案例深入了解了深度强化学*的实际应用与精妙的训练策略。
- 在实际游戏开发中权衡不同技术方案的实用主义观点。


希望这节课硬核的内容,能帮助你打开游戏AI与机器学*结合的大门。

17. 计算照明 (II) | GAMES204-计算成像 - P1 - GAMES-Webinar - BV1B24y1y7K4
概述


在本节课中,我们将继续探讨计算照明的不同方面,包括光颜色和亮度、合成孔径照明和摄影、时间调制以及自然光照条件下的图像增强。
光颜色和亮度

光颜色


光颜色可以通过波长或色彩来表示。例如,1997年的一项研究使用色彩进行水*方向编码,通过将图像投影到SHSV空间中,并使用彩色相机捕捉彩色条纹,可以精确地恢复人脸的3D信息。

亮度


亮度可以通过曝光时间来调节。例如,粒子图像测速技术(PIV)使用不同的颜色来表示不同的深度,从而通过颜色变化来估计粒子的运动,从而估算流体的运动。


合成孔径照明和摄影
合成孔径成像技术可以通过多个摄像头阵列来提高远距离成像的分辨率。例如,使用45个摄像头的阵列可以拍摄到30米远的目标,并清晰地捕捉到细节。
时间调制




时间调制可以通过改变光线的闪烁频率来获取信息。例如,动作捕捉相机使用闪烁的灯和相机来标记点的位置,从而实现动作捕捉。



自然光照条件下的图像增强




在自然光照条件下,可以使用图像增强技术来提高图像质量。例如,白天和夜晚融合技术可以将白天和夜晚的图像融合在一起,从而在夜间监控时提供更清晰的背景。




总结


本节课介绍了计算照明的不同方面,包括光颜色和亮度、合成孔径照明和摄影、时间调制以及自然光照条件下的图像增强。这些技术可以应用于各种场景,例如动作捕捉、粒子图像测速和图像增强。



18. 时域调制、飞行时间法 | GAMES204-计算成像 - P1 - GAMES-Webinar - BV1se4y1g7zt




概述


在本节课中,我们将学*时域调制和飞行时间法(TOF)的基本原理和应用。我们将探讨TOF成像的历史、物理模型、探测设备以及单光子成像技术。
TOF成像原理
物理模型


TOF成像基于光在介质中传播的速度和时间的关系。根据公式:

深度 = (光速 * 时间) / 2

我们可以通过测量光从光源到目标并返回所需的时间来计算目标的深度。
探测设备
- 单光子雪崩光电二极管阵列(SPAD):具有单光子响应能力,可实现高时间分辨率成像。
- 光电倍增管(PMT):具有高灵敏度和低噪声,但难以集成。
- 锁相放大器(Lock-in Amplifier):用于连续波TOF成像,通过测量光相位来估计深度。
单光子成像
异步单光子3D成像


异步单光子3D成像技术可以有效地解决环境光干扰问题。通过在多个时间点测量光强,并利用相位和幅度信息,可以重建出高精度的深度图像。
应用
- 长距离成像
- 低功耗成像
- 高精度成像
- 极暗环境成像


总结




本节课介绍了TOF成像的基本原理、探测设备以及单光子成像技术。TOF成像技术在三维测量、机器人导航、自动驾驶等领域具有广泛的应用前景。

课程18:网络游戏的架构基础 (Part 1) 🎮🌐
在本节课中,我们将要学*网络游戏架构的基础知识。我们将从理解网络游戏相对于单机游戏的独特挑战开始,逐步深入到构建网络游戏所需的核心技术概念,包括基础网络协议、时钟同步以及远程过程调用(RPC)。这些是构建任何在线多人游戏体验的基石。
网络游戏的独特挑战
上一节我们介绍了课程概述,本节中我们来看看网络游戏面临的核心挑战。网络游戏的核心是让玩家在任何时间、任何地点都能与他人一同游玩。这带来了几个单机游戏不存在的复杂问题。
1. 世界状态的一致性
游戏引擎可以被视为在模拟一个宇宙。在网络游戏中,每个玩家客户端都运行着自己的“宇宙”实例。最大的挑战在于,如何确保这成百上千个*行宇宙中发生的事件是同步和一致的。例如,不能在一个玩家的世界里瓶子是完好的,而在另一个玩家的世界里它已经碎了。

2. 网络的不可靠性
现实世界的网络连接充满了不确定性,例如延迟、丢包、拥塞甚至断线重连。这与“上帝视角”下拥有无限且可靠带宽的理想情况截然不同。游戏必须能够优雅地处理这些网络波动。
3. 安全性与反作弊
网络游戏本质上是一个经济系统。保护玩家数据、防止作弊(如锁血、修改内存)和信息泄露至关重要。在单机游戏中,玩家修改本地数据影响有限,但在网络游戏中,这会彻底破坏其他玩家的体验和游戏的公*性。
4. 设备与环境的多样性
网络游戏需要支持各种设备(PC、手机、主机)和操作系统,并且要能在游戏运行中进行热更新以修复漏洞或调整规则,而不能强制所有玩家下线更新。

5. 高并发下的复杂度
构建一个支持成千上万玩家同时在线的世界(如《头号玩家》中的“绿洲”),其复杂度会随着玩家数量呈指数级增长,对网络架构是极大的挑战。
网络基础协议
理解了挑战之后,我们需要掌握应对这些挑战的工具。本节我们将回顾构建网络连接的基础——网络协议。
互联网的基石是TCP/IP协议栈,它通过分层模型(如OSI七层模型)屏蔽了底层硬件的复杂性,让开发者可以专注于应用层开发。对于游戏开发者,最常用的是传输层的两个协议:TCP和UDP。
TCP协议
TCP提供可靠的、面向连接的、基于字节流的传输服务。
- 核心机制:通过“三次握手”建立连接,并通过确认应答(ACK)和重传机制保证数据可靠、有序地送达。
- 流量与拥塞控制:TCP会自动调整发送速率以避免网络拥塞,但这会导致带宽波动,在高延迟或不稳定网络中可能造成卡顿。
- 包头大小:相对较大,通常为20字节。
UDP协议
UDP提供无连接的、尽最大努力交付的数据报服务。
- 核心特点:无需建立连接,直接发送数据。不保证可靠性、顺序性和流量控制。
- 优点:延迟低,响应快,包头小巧(仅8字节)。
- 缺点:数据可能丢失、重复或乱序。
游戏中的协议选择
不同的游戏类型会选择不同的协议:
- TCP:适用于对实时性要求不高的场景,如卡牌游戏(《炉石传说》)的回合操作、登录认证、聊天系统等。
- UDP:适用于对延迟极其敏感的场景,如第一人称射击游戏(《守望先锋》)中的玩家移动和射击指令。
现代大型网络游戏通常混合使用TCP和UDP,例如用TCP管理连接和关键业务,用UDP传输实时的游戏状态更新。
构建可靠的UDP连接
上一节我们了解了UDP速度快但不可靠。对于需要低延迟又必须保证部分指令可靠送达的游戏场景,最佳实践是在UDP之上自定义一个可靠传输层。
我们的目标是结合TCP的可靠性和UDP的高效性。这主要依赖以下几个机制:
1. 确认与重传机制
以下是实现可靠性的核心算法:
- 自动重传请求(ARQ):接收方通过发送确认(ACK)或否定确认(NACK)来告知发送方数据接收情况,触发重传。
- 滑动窗口协议:允许发送方在未收到确认前连续发送多个数据包,充分利用带宽。
- 回退N帧(Go-Back-N):若某个包丢失,则重传该包及之后窗口内的所有包。实现简单,是推荐的基础策略。
- 选择重传(Selective Repeat):仅重传丢失的特定数据包,效率更高,但需要NACK机制支持。
2. 前向纠错
为了减少重传延迟,可以采用前向纠错技术,在原始数据中加入冗余信息,使得接收方在少量丢包时能自行恢复数据,无需重传。
- 异或算法:将多个数据包进行异或运算,生成一个校验包。丢失任一原始包时,可用剩余包和校验包通过异或恢复。类似于RAID 5磁盘阵列的原理。
- 里德-所罗门编码:更强大的算法。通过构造一个生成矩阵,将数据包编码为带冗余的数据块。即使同时丢失多个包,只要收到的包数量足够,就能通过矩阵运算精确还原所有原始数据。适用于丢包率较高的移动网络环境。
通过组合ARQ策略和FEC算法,开发者可以基于UDP定制出满足不同需求(如高可靠、低延迟)的私有信道。
时钟同步
在多个“宇宙”间同步事件,第一步是统一时间。网络延迟使得精确对时成为一个难题。
网络时间协议原理
最基础的算法是网络时间协议。其原理如下:
- 客户端在时间
T0发送请求。 - 服务器在时间
T1收到请求,并在T2时刻发出响应。 - 客户端在时间
T3收到响应。
假设网络往返延迟对称,则客户端与服务器的时钟偏差可估算为:
Offset = [ (T1 - T0) + (T2 - T3) ] / 2
往返时间可估算为:
RTT = (T3 - T0) - (T2 - T1)
游戏中的实践
由于网络延迟并不对称,单次NTP计算误差较大。游戏中的实践方法是:
- 进行多次(如5-10次)NTP对时。
- 舍弃那些RTT异常(如超过*均值50%)的样本,因为它们很可能是在网络波动期间测量的,不可信。
- 对剩余样本计算出的时钟偏差取*均值,并以此调整客户端时钟。
尽管无法做到绝对精确,但这能显著减少因时钟不同步导致的逻辑错误(如判定攻击是否命中)。客户端与服务器建立连接后,对时通常是第一步操作。
远程过程调用
当我们需要在客户端和服务器之间通信时,直接处理网络消息的打包、解包、序列化非常繁琐且易错。RPC技术解决了这个问题。
RPC让开发者能够像调用本地函数一样调用远程服务器上的函数,所有底层的网络通信细节被隐藏起来。
RPC的工作流程
一个典型的游戏RPC调用路径如下:
- 客户端调用:程序员调用一个本地函数(实际上是RPC存根)。
- 序列化与打包:RPC框架自动将函数参数序列化为字节流,并进行压缩和加密。
- 网络传输:通过定制的可靠UDP或TCP信道发送。
- 服务器处理:服务器端接收后,解密、解压、反序列化数据,并调用对应的实际函数。
- 返回结果:如果需要,服务器可以同样通过RPC将结果返回给客户端。
接口定义语言
为了生成客户端和服务器端的RPC代码存根,我们会使用IDL来定义接口。
例如,一个ProtoBuf的IDL定义:
// 定义一个RPC方法
rpc PlayerShoot (ShootRequest) returns (ShootResponse);
// 定义消息结构
message ShootRequest {
int32 player_id = 1;
Vector3 position = 2;
Vector3 direction = 3;
}
通过IDL编译器,可以自动生成不同语言(C++、Go、C#)的序列化/反序列化代码和网络存根,保证了跨*台通信的一致性,并让程序员能专注于游戏逻辑本身。
总结
本节课中我们一起学*了网络游戏架构的基础知识。
我们首先探讨了网络游戏在状态同步、网络可靠性、安全性、多样性和高并发方面面临的独特挑战。接着,我们回顾了TCP和UDP协议的特点及其在游戏中的应用场景,并深入讲解了如何在UDP之上构建兼具低延迟和可靠性的自定义传输层。然后,我们学*了时钟同步的重要性及NTP算法的基本原理与游戏中的优化实践。最后,我们介绍了RPC技术,它通过IDL定义接口,将复杂的网络通信抽象为简单的函数调用,极大地提升了开发效率和代码可靠性。


这些概念是构建任何网络游戏引擎的基石。在下一节课中,我们将基于这些基础,深入探讨游戏状态同步的具体策略(如快照同步、状态同步)、玩家移动预测与补偿、以及大型MMO游戏的服务器架构设计。

🎮 课程18:网络游戏的架构基础 (Part 2)
在本节课中,我们将深入探讨网络游戏的核心架构,特别是游戏世界如何在不同玩家之间保持同步。我们将介绍几种主流的同步技术,分析它们的原理、优缺点以及适用场景,帮助你理解现代在线游戏是如何构建的。
🌐 网络游戏架构概览
上一节我们介绍了网络基础,本节中我们来看看游戏如何利用这些网络连接来构建一个共享的虚拟世界。网络游戏的架构主要决定了数据如何在玩家和服务器之间流动。
以下是几种经典的网络游戏架构:
- P2P (Peer-to-Peer) 架构:所有玩家客户端直接相互连接,彼此广播状态。早期局域网游戏(如《星际争霸》、《魔兽争霸3》)常用此架构。其优点是开发者无需维护中央服务器,但对网络稳定性和玩家主机性能依赖度高。
- 客户端-主机 (Client-Host) 架构:从众多玩家中选出一台性能较好的机器作为主机(Host),其他玩家连接到这台主机。这可以看作是P2P架构的一种特例,在《反恐精英》等游戏中常见。优点是降低了开发商的服务器成本,但主机的性能和网络状况会影响所有玩家。
- 专用服务器 (Dedicated Server) 架构:游戏厂商提供专门的服务器来运行游戏逻辑。这是目前大型多人在线游戏(MMORPG)和竞技游戏的主流选择。它能保证游戏世界的强一致性和相对公*的网络环境,但开发和运维成本高。
对于大型商业游戏,网络拓扑结构会更加复杂。为了优化全球玩家的体验,游戏公司会在世界各地部署接入点(PoP),玩家就*接入,再通过高速专线连接到核心服务器,形成一个高效的骨干网络。
🔄 游戏同步技术
理解了基础架构后,我们来看看游戏同步的核心技术。当多个玩家处于同一个虚拟世界时,如何让他们看到的状态保持一致,是网络游戏引擎面临的最大挑战。
游戏同步主要有三种技术方案:
1. 基于快照的同步 (Snapshot Synchronization)
这是最古老、最直接的方法。其核心思想是:客户端只负责发送输入和接收渲染,所有游戏逻辑的模拟都在服务器端完成。
工作原理:
- 所有客户端将本帧的输入(如按键、鼠标移动)发送给服务器。
- 服务器收集所有输入,在一个逻辑帧内模拟整个游戏世界。
- 服务器将模拟后整个世界所有对象的状态(位置、血量、速度等)生成一个“快照”。
- 服务器将这个快照广播给所有客户端。
- 客户端收到快照后,简单地将所有游戏对象(可视为提线木偶)摆放到快照指定的位置,然后进行渲染。
优化:为了减少数据传输量,通常只发送状态发生变化的部分(Delta),而非完整的快照。
优点:
- 架构干净简洁,服务器逻辑清晰。
- 客户端作弊空间小,因为所有状态由服务器决定。
- 理论上计算总量最低(世界只模拟一次)。
缺点:
- 严重浪费客户端强大的计算能力。
- 网络带宽要求极高,尤其是玩家数量多时,服务器上行带宽压力巨大。
- 客户端体验不“跟手”,因为操作需要等待服务器往返。
公式/代码概念:
// 伪代码:服务器生成快照
GameSnapshot generateSnapshot(WorldState world, List<PlayerInput> allInputs) {
world.simulate(allInputs); // 用所有输入模拟世界
return world.captureSnapshot(); // 捕获世界状态
}
2. 锁步同步 / 确定性同步 (Lockstep Synchronization)
这种方法的核心是保证所有客户端在完全一致的初始条件下,根据完全一致的输入序列,进行完全一致的模拟,从而得到完全一致的结果。服务器只负责转发输入指令。
工作原理:
- 初始化:确保所有客户端游戏初始状态(随机数种子、角色属性、地图等)绝对一致。
- 收集输入:每个客户端将本帧的输入发送给服务器。
- 同步转发:服务器等待收集齐所有客户端的输入(或超时),然后将这包输入广播给所有客户端。
- 本地模拟:所有客户端收到相同的输入包后,在本地独立进行游戏逻辑模拟。
- 重复步骤2-4。
优化 - 桶同步 (Bucket Synchronization):为避免因个别玩家网络延迟卡住所有人,服务器设定一个时间窗口(如100ms),只收集该窗口内的输入,超时未到的视为无效操作。这牺牲了部分一致性,换取了实时性。
核心挑战:确定性 (Determinism)
锁步同步要求游戏逻辑具有高度的确定性。这意味着相同的输入必须产生完全相同的输出。这在包含浮点数运算、随机数、复杂物理模拟和不确定执行顺序的现代游戏中极具挑战性。
- 浮点数:不同*台、编译器的浮点数运算结果可能有细微差异,需要使用符合标准的数学库或改用定点数 (Fixed Point)。
- 随机数:必须使用相同的随机数种子和算法。
- 执行顺序:所有可能影响状态的系统(如组件更新顺序)必须严格一致。
优点:
- 带宽需求低(只同步输入指令)。
- 非常适合操作敏感、要求绝对公*的竞技游戏(如MOBA、RTS)。
- 易于实现录像和回放功能(只需记录输入序列)。
缺点:
- 实现确定性逻辑非常困难,调试复杂。
- 所有客户端拥有全游戏状态,反作弊(防“全图挂”)难度大。
- 断线重连后,需要追赶大量模拟帧,体验可能不佳。
公式/代码概念:
// 伪代码:确定性模拟循环
void deterministicSimulationLoop() {
Random.setSeed(globalSeed); // 设置全局随机种子
WorldState world = initializeWorld(); // 确定性初始化
while (gameRunning) {
InputPackage inputs = receiveInputsFromServer(); // 接收同步的输入包
world.simulateFrame(inputs); // 用相同输入进行确定性模拟
render(world);
}
}
3. 状态同步 (State Synchronization)
这是目前大型网络游戏最常用的方法。其核心思想是:服务器是权威(Authoritative),维护唯一正确的游戏世界状态;客户端是部分状态的拥有者,并预测自己的行为,但最终以服务器裁决为准。
核心概念:
- 权威服务器 (Authoritative Server):服务器是游戏世界的“上帝”,所有关键判定(如是否命中、伤害计算)由服务器说了算。
- 复制 (Replication):客户端看到的其他玩家或物体,是服务器权威状态的一个“复制品”。
- 客户端预测 (Client-side Prediction):为了消除操作延迟感,客户端在发出操作指令时,立即在本地预测结果并呈现。例如,按下“前进”键,角色会立刻向前移动。
- 服务器调和 (Server Reconciliation):当服务器权威状态返回后,客户端将本地预测状态与服务器状态进行比对和调和。如果预测错误,客户端需要“纠正”自己的状态(如被拉回原地)。
工作原理:
- 玩家A(权威端)执行“开火”操作,客户端立即本地播放开火动画(预测)。
- 同时,“开火”指令发送给服务器。
- 服务器进行权威判定:是否命中目标B。
- 服务器将判定结果(命中/未命中)以及相关状态更新(A的开火事件、B的掉血)广播给所有客户端。
- 所有客户端(包括A)根据服务器指令更新世界状态。如果A的预测与服务器结果不符,则进行调和。
优点:
- 服务器拥有完整状态,防作弊能力较强。
- 天然支持状态裁剪(AOI),只同步玩家感兴趣区域的信息,节省带宽。
- 不要求客户端逻辑完全确定性,开发相对灵活。
- 适合复杂、玩家多的游戏世界(如MMORPG)。
缺点:
- 网络延迟会导致“预测错误”和“状态拉回”,影响体验。
- 架构比锁步同步复杂,需要处理预测与调和。
- “愚蠢客户端问题”需要精心设计预测算法来缓解。
公式/代码概念:
// 伪代码:客户端预测与调和
void clientMove() {
// 1. 用户输入,立即本地预测
Vector3 predictedPosition = localPlayer.predictMove(input);
// 2. 发送指令到服务器
sendToServer(new MoveCommand(input));
// 3. 将预测状态存入缓冲区,用于后续调和
predictionBuffer.save(predictedPosition, currentTime);
}
void onServerStateUpdate(ServerState authoritativeState) {
// 4. 收到服务器状态
// 5. 调和:查找缓冲区中对应时间点的预测状态
PredictedState myPrediction = predictionBuffer.find(authoritativeState.timestamp);
if (myPrediction.position != authoritativeState.playerPosition) {
// 6. 预测错误,以服务器状态为准,*滑纠正
correctPosition(authoritativeState.playerPosition);
}
// 7. 丢弃已处理的预测缓冲区
predictionBuffer.clearUpTo(authoritativeState.timestamp);
}
📊 同步技术对比与总结
本节课我们一起学*了三种主流的游戏同步技术:
- 快照同步:概念优美,一致性最强,但对带宽要求苛刻,已较少用于实时对战游戏。
- 锁步同步:追求极致的公*性和确定性,是高端竞技游戏的理想选择,但对游戏引擎和逻辑的确定性要求极高,实现和调试难度大。
- 状态同步:在一致性、实时性、开发复杂度和反作弊之间取得了良好*衡,是目前大型多人在线游戏和许多对战游戏的主流方案。

选择哪种同步技术,取决于游戏类型、设计目标、团队技术栈和运营资源。动作竞技游戏可能偏爱锁步同步,而大型开放世界游戏则必然选择状态同步。


理解了这些基础,我们知道了游戏世界如何“同步”,但要让一个网络游戏真正流畅运行,还需要解决更多问题,例如:位置如何*滑插值?技能判定如何精确?大型世界的数据如何分发?这些将是我们后续课程探讨的内容。

19.时域调制 (II) | GAMES204-计算成像 - P1 - GAMES-Webinar - BV1d8411V7ZT

概述
在本节课中,我们将学*非视域成像技术,这是一种能够捕捉隐藏在障碍物后面的物体图像的技术。我们将探讨非视域成像的原理、挑战和实现方法。
非视域成像概述
非视域成像,也称为“绕过角落看”成像,是一种能够捕捉隐藏在障碍物后面的物体图像的技术。它通过分析光在物体和障碍物之间的传播路径来实现。

非视域成像的挑战





非视域成像面临着以下挑战:
- 信号微弱:由于多次反射和散射,到达传感器的光子数非常少,导致信号微弱。
- 逆问题:需要从复杂的信号中恢复出隐藏物体的三维结构,这是一个高度非线性的逆问题。
- 数据量庞大:需要处理大量的数据,这给计算带来了巨大的挑战。


非视域成像的实现方法

1. 传统方法


传统的非视域成像方法包括:
- 基于扫描的方法:通过扫描激光束和相机来获取图像。
- 基于投影的方法:通过投影图像并分析反射光来获取图像。
2. 共聚焦非视域成像
共聚焦非视域成像通过将测量点与激光点重合来简化问题。这种方法将五维问题简化为三维问题,并提高了成像效率。

3. 频率-波数迁移


频率-波数迁移是一种将信号从频率域迁移到波数域的方法。这种方法可以有效地去除噪声并提高成像质量。



实验结果

实验结果表明,非视域成像技术可以有效地捕捉隐藏在障碍物后面的物体图像。


总结
本节课介绍了非视域成像技术,探讨了其原理、挑战和实现方法。非视域成像技术具有广阔的应用前景,例如在医疗、安全和机器人领域。
代码示例
以下是一个简单的非视域成像代码示例:

import numpy as np



def non_visual_imaging(data):
"""
非视域成像
Args:
data: 输入数据
Returns:
成像结果
"""
# ... 处理数据 ...
return image
参考资料


课程19:网络游戏的进阶架构 (Part 1) 🎮
在本节课中,我们将要学*网络游戏开发中的高级主题。我们将探讨如何在不同客户端之间*滑同步玩家的移动,如何实现可靠的命中判定,以及大型多人在线游戏的基础架构设计。这些知识是构建一个真正可玩、体验流畅的网络游戏所必需的。
角色位移同步 🔄
上一节我们介绍了网络同步的基础概念。本节中我们来看看如何让其他玩家在你的客户端上移动得更加*滑自然。直接根据网络包更新位置会导致角色“抖动”,因此我们需要使用插值技术。
内插值
内插值用于在已知的过去数据点之间生成*滑的过渡。在网络同步中,我们通常将服务器发来的状态数据缓存一小段时间,然后在两个已知状态之间进行插值。
以下是实现内插值的一个核心思想:
// 伪代码:在缓冲的状态间进行插值
Vector3 InterpolatePosition(State previous, State next, float currentTime) {
float t = (currentTime - previous.timestamp) / (next.timestamp - previous.timestamp);
t = Clamp(t, 0.0f, 1.0f); // 确保t在[0,1]范围内
return Lerp(previous.position, next.position, t); // 线性插值
}
这种方法的好处是移动非常*滑,但代价是增加了额外的延迟,因为客户端需要等待未来的数据包才能开始插值。
外插值
当内插值带来的延迟不可接受时(例如在赛车游戏中),我们会使用外插值。外插值本质上是预测:根据物体当前的速度和加速度,预测它未来的位置。
一个著名的算法是 Dead Reckoning,其核心思想是预测并追赶目标的真实轨迹。一个简化版本是 Projective Velocity Blending (PVB) 算法。
以下是PVB算法的核心公式描述:
-
预测目标真实位置:根据最新收到的网络包(包含位置
P0'、速度V0'、加速度A0'),预测经过固定混合时间Tb后,目标的真实位置PT'。
PT' = P0' + V0' * Tb + 0.5 * A0' * Tb^2 -
混合追赶:在本地,从当前模拟的位置
P0和速度V0开始,在时间Tb内,通过线性插值,使其位置和速度逐渐与预测的真实轨迹PT'和V0'重合。
这种方法能让本地角色快速追赶并贴合真实轨迹,但预测可能出错,尤其是在发生碰撞或急转弯时。
应用场景与混合使用
- 内插值 常用于角色移动灵活、瞬时变速多的游戏(如FPS、MOBA),因为它稳定且*滑。
- 外插值 常用于运动符合物理规律、预测相对准确的游戏(如赛车、飞行模拟)。
- 实战中,两者常混合使用。例如,在《战地》系列中,角色移动可能用内插值,而载具驾驶则用外插值。
命中判定 🎯
解决了移动同步问题后,下一个核心挑战是:在网络延迟下,如何判定攻击是否命中目标?这直接关系到游戏的公*性和手感。
客户端判定
在这种模式下,命中检测完全在开枪玩家的客户端上进行。服务器只进行事后验证。
优点:
- 手感极佳,响应即时。
- 服务器压力小。
缺点:
- 非常不安全,容易作弊(如通过“延迟开关”技巧制造时间静止效果来瞄准)。
服务器验证通常比较宽松,例如《守望先锋》早期版本会用一个比角色模型大得多的碰撞盒来进行粗略验证,只要子弹命中这个区域即视为有效。
服务器端判定与延迟补偿
为了公*和安全,许多竞技游戏将命中判定放在服务器端。但这带来了新问题:由于网络延迟,开枪者客户端看到的目标位置与服务器端的最新位置不同。
解决方案是 延迟补偿。其核心思想是:当服务器收到“开枪”指令时,并不使用当前世界的状态进行检测,而是将时间“回滚”到开枪者开枪那一瞬间所看到的世界状态。
实现前提:
服务器需要保存过去一段时间内每一帧的世界状态快照。
工作流程:
- 服务器收到客户端A的“开枪”消息。
- 服务器根据A的网络延迟和插值偏移量,计算出A开枪时的“过去”时间点
T_shoot。 - 服务器将世界状态回滚到时间点
T_shoot。 - 在回滚后的世界中,检测从A开枪位置发出的射线是否命中目标B。
- 如果命中,则判定此次射击有效,并基于此结果推进世界状态。
这种方法极大地提升了公*性,但实现复杂,且对服务器性能要求更高。
无法完美解决的问题
即使有延迟补偿,网络延迟仍会导致一些固有矛盾:
- 对冲锋者有利:从掩体后冲出的人,由于他的位置信息延迟到达对方,因此他能先看到并攻击对方。
- 对躲避者不利:躲进掩体的人,在对方客户端看来可能还未完全进入掩体,因此仍可能被击中。

为了改善玩家体验,常采用以下技巧:
- 设计前摇动作:为攻击技能添加起手动作,为网络同步争取时间。
- 本地预表现:在客户端命中时,立即播放命中特效、音效和伤害数字(但真实伤害结算仍以服务器为准),减少操作的迟滞感。
总结 📚
本节课中我们一起学*了网络游戏开发中的两个高级核心问题。
首先,我们深入探讨了角色位移同步。通过对比内插值和外插值,我们了解到如何根据游戏类型选择合适的算法,让其他玩家的移动在本地客户端上看起来*滑自然,并理解了预测算法可能带来的碰撞检测等边界问题。
其次,我们研究了命中判定这个关乎游戏公*与手感的难题。我们分析了客户端判定和服务器端判定两种模式的优缺点,并重点介绍了服务器端判定的基石——延迟补偿技术。我们也认识到,在网络延迟的物理限制下,完全的公*难以实现,但通过精妙的算法和设计技巧(如前摇、本地预表现),可以最大限度地提升玩家的游戏体验。

掌握这些知识,是构建一个真正可玩、可信的网络游戏世界的关键一步。

🎮 课程19:网络游戏的进阶架构 (Part 2)
在本节课中,我们将深入探讨如何构建一个大型多人在线游戏。我们将从MMO的基本概念入手,逐步解析其复杂的服务器架构、核心子系统、分布式设计、带宽优化以及至关重要的反作弊机制。最后,我们将展望构建无缝开放世界所面临的挑战与未来。
🏰 什么是MMO游戏?
MMO,即大型多人在线游戏,其核心在于让海量玩家在一个持续的虚拟世界中连接、互动。这不仅仅是传统的MMORPG,现代的FPS、MOBA等游戏类型也具备了MMO的规模与特性。
最早的联网游戏可以追溯到文字MUD时代,玩家通过文字指令在虚拟房间中探索、互动。发展至今,MMO已经演变成一个极为复杂和丰富的虚拟社会,玩家可以在其中战斗、交易、社交,体验*乎真实的世界。
从构建单一游戏对局到构建一个能容纳数十万乃至百万玩家的虚拟世界,系统复杂度急剧上升。这引入了许多新的子系统需求。
🏗️ MMO的核心架构概览
一个典型的MMO服务器架构大致可分为三层:
- 连接层:管理玩家与服务器的初始连接、认证和路由。
- 服务层:提供游戏的核心功能,如角色、战斗、社交、匹配等。
- 数据存储层:持久化存储所有游戏数据。
上一节我们介绍了网络同步的基础,本节中我们来看看如何将这些基础组合成一个完整的、可扩展的在线世界。
🔗 连接层:网关与防火墙
连接层是玩家进入游戏世界的第一道门。它主要包含两个关键组件:登录服务器和网关服务器。
- 登录服务器:负责玩家的初始连接、握手和账号密码验证。通常使用HTTPS等安全协议建立加密信道。
- 网关服务器:这是架构中的关键。玩家通过验证后,所有后续通信都只与网关进行,再由网关与内部的各种业务服务器通信。
核心作用:网关实质上是一个防火墙和协议处理器。它验证所有传入数据的合法性,过滤恶意攻击,并承担协议解析、数据压缩等工作,保护内部脆弱的业务逻辑服务器。随着玩家数量增加,可以部署多个网关实例进行负载均衡。
⚙️ 服务层:错综复杂的子系统
通过网关后,玩家便进入了由众多专业服务构成的世界。以下是几个核心服务:
- 大厅服务:玩家聚集和等待的场所,可以是一个虚拟空间或实际场景,用于管理玩家并准备进入具体游戏对局。
- 角色服务:管理玩家所有核心数据(装备、任务、属性)的中心化服务。几乎所有其他服务(如交易、战斗)都需要查询或修改角色数据,因此其负载和重要性极高。
- 交易系统:具有强金融属性,要求绝对的原子性和安全性。每一笔交易都必须可以回滚,以应对客户端断线、服务器异常等情况,保障玩家虚拟财产的安全。
- 社交系统:包括聊天、邮件、好友、公会等功能。在高并发下,这些功能通常会被拆分为多个独立的服务以防止单点过载。
- 匹配系统:在竞技游戏中至关重要。它不仅要根据玩家的技术水*进行匹配,还要考虑网络延迟、开黑队伍等因素,以保障对局的公*性和体验。
💾 数据存储层:数据的家园
在线世界需要持续存在,因此数据存储设计是核心。现代MMO通常采用多种数据库技术组合:
- 关系型数据库:如MySQL,用于存储核心、结构化强的数据(如玩家基础信息、物品表)。在海量数据下,常采用分布式、分库分表等架构。
- 非关系型数据库:如MongoDB,用于存储日志、临时游戏状态等无需复杂关联查询的海量数据。它写入速度快,适合做数据倾泻。
- 内存数据库:如Redis,用于存储需要极高速访问的中间数据或缓存。在分布式服务器集群中,内存数据库是管理共享状态的优秀工具。
架构建议:设计MMO服务器时,建议从数据模型开始思考。理清了数据的流动与存储,上层服务的结构也就清晰了。
🌐 迈向分布式:应对海量玩家
当玩家数量从一万增长到十万、百万时,单机或简单架构无法承受。解决方案是分布式系统:将每个服务拆分成多个可以独立部署和扩展的进程。
然而,分布式带来了新的挑战:
- 数据一致性:同一份数据被多个服务访问时,需避免冲突和死锁。
- 消息幂等性:网络波动可能导致重复消息,系统需要能安全地处理重复请求。
- 服务发现与管理:成百上千个服务如何找到彼此?如何监控健康状态?如何自动重启故障服务?这就需要如 etcd、ZooKeeper 这类服务发现与协调工具。
- 负载均衡:如何将玩家请求合理地分配到多个相同的服务实例上?
⚖️ 一致性哈希:优雅的负载均衡算法
一个经典的负载均衡问题是:如何快速确定某个玩家数据应该由哪个角色服务实例管理?一致性哈希提供了优雅的解决方案。
核心思想:
- 将服务器节点和玩家ID通过哈希函数映射到一个固定的环上(例如,
0到2^32-1)。 - 每个玩家数据顺时针或逆时针找到环上最*的服务器节点,即为其归属。
- 当增加或删除服务器节点时,仅影响环上相邻区间的数据,大部分数据无需迁移。

公式/伪代码示意:
# 简化示例:确定对象object应存储在哪个服务器节点上
servers = ['server_A', 'server_B', 'server_C']
ring = {} # 哈希环
# 将服务器和虚拟节点加入环
for server in servers:
hash_value = hash_function(server)
ring[hash_value] = server
# 为对象寻找服务器
object_hash = hash_function(object_id)
sorted_keys = sorted(ring.keys())
for key in sorted_keys:
if object_hash <= key:
return ring[key]
# 如果没找到,说明在环的尾部,返回第一个节点
return ring[sorted_keys[0]]
这种方法避免了全局查询,极大提升了效率。还可以引入“虚拟节点”来使数据分布更均匀。
📉 带宽优化:成本与体验的*衡
网络带宽直接关系到运营成本和游戏体验。优化主要从三方面入手:玩家数量、更新频率、单次数据量。
以下是核心优化策略:
- 数据量化:将浮点数转换为定点数。例如,用16位整数表示游戏内的位置坐标,可以大幅减少数据量。这是最有效且常用的方法之一。
- 兴趣区域:玩家无需感知整个世界,只需同步其周围一定范围内的对象状态。这是降低带宽和算力的关键。
- 静态分区:将世界划分为固定的格子,玩家只同步所在格子及相邻格子的实体。
- 动态查询:使用十字链表等数据结构,动态查询玩家周围实体。
- 动态更新频率:根据实体与玩家的距离,动态调整状态同步的频率。远处的实体更新更慢,*处的实体更新更快。
🛡️ 反作弊:永恒的攻防战
在网游中,作弊会毁灭游戏生态。反作弊是一场艰难的技术攻防。
常见作弊手段与防御:
- 内存修改:外挂直接修改游戏内存数据。
- 防御:客户端加壳、内存数据混淆加密、反调试技术。
- 文件篡改:修改本地游戏资源(如让敌人贴图发光)。
- 防御:校验本地文件哈希值,与服务器比对。
- 网络劫持:拦截并伪造网络封包。
- 防御:通信链路加密。通常采用非对称加密交换密钥,再用对称加密进行实际通信。
- 注入钩子:将作弊代码注入游戏进程。
- 防御:反作弊软件扫描内存签名和可疑进程。
- AI作弊:使用AI进行图像识别和自动化操作。
- 防御:目前最棘手。可能的方向包括行为模式分析、AI检测AI等。
反作弊是系统工程,需要持续投入,并结合技术、运营甚至法律手段。
🌍 构建无缝开放世界
终极愿景是构建一个能让海量玩家自由探索、无感知切换区域的“绿洲”式世界。核心思路是两种技术的结合:
- 动态分区:将世界动态划分为不同区域,每个区域由一个服务器实例管理。当玩家靠*区域边界时,相邻服务器会为其创建幽灵副本,实现*滑的视野过渡。只有当玩家真正跨越边界一定距离后,才进行主控权的迁移,避免在边界频繁抖动。
- 分层复制:将世界复制多份,每份承载一部分玩家。玩家在自己的“层”中是实体,在其他层中是幽灵。这可以应对主城等玩家高度聚集区域的负载问题。
将动态分区与分层复制结合,才能构建一个既广阔又能承载高并发玩家的、真正无缝的虚拟世界。
📚 总结
本节课我们一起学*了构建大型多人在线游戏的全景图。我们从MMO的三层基础架构讲起,深入探讨了连接层、服务层和数据存储层的核心组件。为了应对海量玩家,我们引入了分布式系统架构,并讲解了一致性哈希这一关键的负载均衡算法。接着,我们探讨了带宽优化的实战策略和反作弊这场永恒的攻防战。最后,我们展望了通过动态分区和分层复制技术构建无缝开放世界的可能性。



MMO服务器的架构是一个极其复杂的系统工程,充满了挑战,但也正是这些挑战,推动着游戏技术不断向前,去创造那个令人向往的、连接所有人的虚拟世界。

20.时域调制 (III) | GAMES204-计算成像 - P1 - GAMES-Webinar - BV1T8411V7ne
概述
在本节课中,我们将深入探讨时域调制技术中的连续波成像原理,分析成像模型,并介绍如何解决多路径问题。
连续波成像原理
什么是连续波成像?
连续波成像通过发送连续调制的波形(如正弦波或方波)来生成深度图像。
成像模型
- 光源:发出连续调制的波,例如正弦波。
- 场景:波被场景反射。
- 传感器:接收反射波,并测量其相位变化。
相位测量
- 信号处理:使用相关技术(如锁相放大器)测量相位变化。
- 深度计算:通过相位变化和光速计算深度。
相关传感器工作原理
基本原理
相关传感器通过自相关测量参考信号和接收信号的强度值。
像素模型
- 感光元件:光电二极管。
- 电容:存储电荷。
- 参考信号:控制开关,选择节点连接。
相位计算
- 电压测量:通过测量节点电压计算相位。
- 环境光和偏移:考虑环境光和偏移对测量的影响。
多路径问题
多路径效应
光在场景中反射,产生多个路径,导致相位测量误差。
解决方法
- 提高频率:通过提高频率放大相位变化,减少多路径效应。
- 查找表:建立相位和深度之间的关系,校正非线性误差。
Facer Image
Facer Image原理
- 光传输方程:描述光在场景中的传播和反射。
- 相位变化:考虑相位变化和振幅衰减。
- 多路径反射:考虑多次反射对相位的影响。
Facer Image应用
- 消除多路径效应:通过提高频率减少多路径效应。
- 提高测量范围:使用两个高频函数实现大范围测量。
总结
本节课介绍了连续波成像原理、相关传感器工作原理、多路径问题及其解决方法,以及Facer Image技术。这些内容为理解时域调制技术提供了基础。

下节课预告
下节课将继续讲解Facer Image的详细内容,并介绍相关论文和优化方法。

课程20:现代游戏引擎架构:面向数据编程与任务系统 (Part 1) 🚀
概述
在本节课中,我们将学*现代游戏引擎架构中的两个核心高级概念:面向数据编程和任务系统。我们将从并行编程的基础知识开始,探讨为什么现代游戏引擎需要充分利用多核CPU的计算能力,并深入讲解任务系统的工作原理和实现挑战。
课程前言与安排
大家好,欢迎回到GAMES104。课程已进入尾声,最后三节是高级课程。
课程组根据与同学们的交流,决定做以下几件事。
第一件事是颁发课程证书。课程组会根据同学们提交的作业进行评分。作业全部完成且达到60分,将获得课程毕业证书。如果有两个作业获得100分,将获得“优秀”证书。如果三个作业都是满分,将获得“杰出”证书。
第二件事是课程组将赠送T恤给所有完成作业并成功毕业的同学,作为坚持学*的纪念。
第三件事是关于作业提交时间。考虑到课程难度,最终作业的提交截止日期将设定在10月底,以便给大家更多时间完成,并在课程结束后向课程组寻求帮助。
第四件事是关于Pico引擎。课程结束后,课程组计划从10月10日开始,通过微信公众号和微信群,为同学们解读Pico引擎的架构与代码,例如反射系统的实现。欢迎大家关注公众号并加入群聊。
第五件事是回答上节课同学们提出的问题。
第一个问题是游戏能否彻底解决反作弊。答案是目前不能。这是一个动态*衡的过程,需要技术、设计和社区共同参与。技术手段如加密和服务器端判定可以缓解,但无法根除。一些社交化方法,如Steam的VAC系统,通过关联惩罚来增加作弊成本,也是有效的策略。
第二个问题是微服务与分布式服务器架构的区别。分布式服务器架构是一种经典模式,指将业务拆分到多台物理机或进程上协同工作。微服务是一种设计理念,是分布式架构的一种实现方式,强调将后台服务拆分为单一、无状态的服务单元,便于弹性伸缩和服务发现。在实际游戏服务器架构中,两者常结合使用。
第三个问题是如何构建全球联网对战系统。这是一个复杂的技术挑战。核心在于全球服务器的分布式部署、机房之间高速专线网络的搭建,以及对全球网络拓扑的理解。需要根据玩家地理位置部署服务器节点,并优化节点间的连接路径,以减少延迟。这不仅是技术问题,也涉及基础设施和成本。
并行编程基础
上一节我们概述了课程安排,本节我们来看看为什么游戏引擎需要并行编程。
现代游戏引擎运行在操作系统和硬件之上。我们对性能要求极高,但算力和带宽有限。我们需要在1/30秒甚至1/60秒内完成物理模拟、游戏逻辑、动画和渲染等大量计算。为了压榨硬件极限,我们必须充分利用多核CPU。
随着集成电路工艺接*物理极限,CPU主频难以大幅提升。解决方案是增加核心数量。因此,现代编程必须考虑如何利用多核算力。
在传统操作系统中,我们熟悉进程和线程的概念。
- 进程:拥有独立的存储空间,由系统管理。
- 线程:在进程内部,共享进程的内存空间。线程间通信更高效,但需要小心处理数据竞争。
为了实现多任务处理,操作系统有两种调度模型:
- 抢占式多任务:调度器可以主动中断正在运行的任务。这是Windows等通用操作系统的常见模式,保证了系统响应能力。
- 非抢占式多任务:任务会一直运行直到主动让出控制权。这在某些实时操作系统中使用,但对任务设计有严格要求。
线程间的切换成本很高。一次上下文切换可能消耗数千个CPU周期,如果数据不在缓存中,延迟可能达到数十万周期。这是设计高效并行系统时必须考虑的问题。
在并行编程中,我们常遇到两类问题:
- 独立任务:任务间没有依赖,可以完全并行执行,例如蒙特卡洛积分采样。
- 依赖任务:任务间存在数据依赖或执行顺序依赖,这使并行化变得复杂。
依赖任务中最核心的问题是数据竞争:当多个线程同时读写同一数据,且读写顺序影响结果时,就会发生不可预知的行为。
解决数据竞争的经典方法是阻塞式编程,使用临界区、互斥锁等机制保护共享资源。但这可能带来死锁、优先级反转等问题,并且难以处理任务失败的情况。
另一种方法是无锁编程,利用硬件支持的原子操作来保证对单个变量的读写是原子的。这避免了死锁,但原子变量间的等待仍可能导致CPU空闲。
理论上还有无等待编程,能保证所有线程持续执行。但这需要复杂的数学证明,通常只用于对性能极其关键的特定数据结构(如队列)。
编译器优化和CPU乱序执行也会给多线程编程带来挑战。编译器或CPU为了效率可能重排指令顺序,在单线程下结果不变,但在多线程下可能导致依赖假设失效。在移动端(如ARM架构)或Release编译模式下,这个问题尤为突出。
游戏引擎中的并行模式
了解了并行编程的基础挑战后,我们来看看游戏引擎中常见的几种并行架构。
最简单的做法是固定线程分工:将渲染、物理、逻辑等不同系统固定分配到不同的线程上运行。这种架构清晰,但在2-4核环境下效果较好。它的主要问题是木桶效应:所有线程必须等待最慢的那个完成任务,导致CPU利用率不均。且无法动态适应不同核心数的硬件。
另一种思路是分叉-汇合模型:将一批相同的计算任务(如动画骨骼计算)分发给多个工作线程,完成后收回结果。这比固定线程更能利用多核,例如Unreal和Unity引擎就采用了类似方法。但它仍然难以处理负载均衡问题,图中仍会存在空闲间隙。
更复杂的模型是任务图:显式定义任务之间的依赖关系,形成一个有向无环图,由调度器根据依赖关系决定执行顺序。这更符合任务间有复杂依赖的场景。但它的缺点是任务图通常是静态或半静态的,难以在运行时动态创建复杂的、嵌套的任务依赖。
讲了这么多,我们终于要引出本节课的第一个主角:Job System(任务系统)。这是现代游戏引擎中讨论和实践较多的一种更彻底的并行化方案。
在深入Job System之前,需要理解一个关键概念:协程。
协程是一种非常轻量级的“线程”,它允许函数执行到一半时暂停,让出执行权,稍后再从暂停点恢复。这与线程有本质区别:
- 线程切换:涉及昂贵的操作系统内核中断和完整的上下文保存/恢复。
- 协程切换:在用户态由程序自己控制,只保存必要的上下文,成本极低。
协程有两种主要类型:
- 有栈协程:需要保存完整的函数调用栈和局部变量状态,恢复时能完全回到之前的环境。对开发者更友好。
- 无栈协程:不保存调用栈,恢复时状态丢失。实现简单、性能高,但对开发者要求极高,通常只用于底层库。
在C++中,语言本身不直接支持协程,需要借助库或内联汇编实现。Windows有Fiber概念,PlayStation等*台有原生支持。实现有栈协程需要管理栈空间分配,是一个底层挑战。
基于纤程的任务系统
有了协程的基础,我们就可以理解基于纤程的任务系统的核心思想。
Fiber-Based Job System的理想状态是:有一个全局任务调度器,管理着一组工作线程,每个工作线程绑定一个CPU逻辑核心。开发者将任务封装成Job提交给调度器。调度器根据工作线程的忙闲状态、任务优先级和依赖关系,动态地将Job分配给工作线程执行。
这里有几个关键设计点:
- 工作线程数量:通常与CPU逻辑核心数一致,以避免昂贵的线程切换。
- 任务调度顺序:在游戏引擎中,通常采用后进先出的栈式调度。因为新产生的Job往往是当前Job的前置依赖,需要优先完成。
- 任务窃取:调度器会监视所有工作线程。当某个线程空闲时,它会从其他繁忙线程的任务队列中“窃取”任务来执行,以实现负载均衡。
- 依赖与等待:Job执行中可以等待其他Job完成。被挂起的Job会被放入等待列表,工作线程转而执行其他就绪Job,从而保持CPU繁忙。
Job System相比之前模型的最大优势,是它能以极细的粒度动态调度任务,最大限度地填满CPU时间线,实现极高的硬件利用率。它的输出性能图看起来非常“整齐”,满足强迫症患者。
然而,实现一个健壮、高效的Job System挑战巨大:
- *台差异:需要在不同*台实现底层协程机制。
- 数据竞争与ABA问题:即使使用原子操作,也可能遇到ABA问题(一个值从A改为B又改回A,导致检测逻辑误判)。这类Bug难以复现和调试。
- 对底层知识要求高:实现者必须深刻理解多线程硬件、内存模型和编译器优化。
因此,虽然Job System对上层开发者隐藏了复杂性,使其能轻松编写并行任务,但底层系统的实现需要扎实的理论基础、严谨的形式化推导和丰富的经验。
总结
本节课我们一起学*了现代游戏引擎高级架构的第一部分。我们从游戏引擎对性能的极致追求出发,回顾了并行编程的基础概念、挑战与常见模式。我们重点探讨了协程这一轻量级并发原语,并深入介绍了基于纤程的Job System的设计思想、优势与实现挑战。
Job System代表了游戏引擎充分利用多核CPU的未来方向,它将复杂的并行调度、依赖管理和负载均衡封装起来,为游戏开发者提供了强大而简洁的并行编程模型。然而,其底层实现充满挑战,需要开发者对计算机系统有深刻的理解。


下节课,我们将继续探讨另一个关键理念:面向数据编程,看看如何通过优化数据布局来进一步提升引擎性能。

20.现代游戏引擎架构:面向数据编程与任务系统 (Part 2) | GAMES104-现代游戏引擎:从入门到实践 - P1 - GAMES-Webinar - BV1Md4y1G7zp

概述
在本节课中,我们将深入探讨现代游戏引擎架构中的面向数据编程(Data-Oriented Programming,简称DOP)和任务系统。我们将分析面向对象编程(Object-Oriented Programming,简称OOP)的局限性,并介绍DOP如何解决这些问题。
面向对象编程的局限性
1. 代码一致性差
面向对象编程虽然直观,但容易导致代码一致性差。例如,攻击者与受害者之间的关系在不同程序员之间可能存在不同的实现方式。
2. 深度继承树
面向对象编程的继承树可能非常深,这使得在大型系统中查找函数实现变得困难。
3. 基类臃肿
面向对象编程的基类可能包含大量功能,导致派生类变得臃肿。
4. 性能问题
面向对象编程的性能可能较低,因为数据分散在各个对象中,且对象创建和销毁需要频繁进行内存分配和释放。
5. 可测试性差
面向对象编程的可测试性较差,因为难以将单个模块从系统中提取出来进行测试。
面向数据编程
1. 数据局部性
面向数据编程强调数据局部性,即数据应尽可能紧密地存储在一起,以便CPU可以高效地访问和处理。
2. 数组结构(AoS)和结构数组(SoA)
面向数据编程推荐使用结构数组(SoA)而不是数组结构(AoS)来组织数据,以提高内存访问效率。
3. ECS系统
ECS(Entity Component System)系统将数据、组件和系统分离,以充分利用数据局部性和提高性能。
4. Unity的DOTS系统
Unity的DOTS系统基于ECS,并使用 Burst Compiler将C#代码编译成原生代码,以提高性能。
5. Unreal Engine的Max系统
Unreal Engine的Max系统类似于DOTS,并使用Fragment和Processor的概念来处理数据。
总结
本节课介绍了面向数据编程和任务系统,并分析了面向对象编程的局限性。我们了解到,面向数据编程可以提高性能和可测试性,并介绍了ECS系统和Unity的DOTS系统等实际应用案例。
参考资料



课程21:动态全局光照与Lumen(第一部分)🎮
在本节课中,我们将深入探讨现代游戏引擎中的核心技术——动态全局光照(Dynamic Global Illumination, GI)及其在虚幻引擎5中的革命性实现:Lumen。我们将从全局光照的基本概念出发,逐步解析其核心挑战与历史解决方案,最终聚焦于Lumen如何巧妙地整合多种技术,在实时渲染中实现高质量的动态全局光照效果。本节课内容硬核,旨在为初学者构建清晰的知识脉络。
概述:全局光照的挑战与目标
全局光照旨在模拟光线在场景中经过多次反射和折射后形成的复杂照明效果,这是实现照片级真实感渲染的关键。其核心挑战源于渲染方程(Render Equation)中复杂的积分运算,需要对半球面上所有方向入射的光线进行采样和累加,计算量巨大。
渲染方程 可以简化为:
Lo(x, ωo) = Le(x, ωo) + ∫Ω fr(x, ωi, ωo) Li(x, ωi) (ωi · n) dωi
其中,Lo是出射光亮度,Le是自发光,fr是双向反射分布函数(BRDF),Li是入射光亮度,积分在半球面Ω上进行。
传统的蒙特卡洛路径追踪(Monte Carlo Path Tracing)方法虽然准确,但需要海量采样才能消除噪点,无法满足实时渲染的性能要求。因此,实时GI技术需要一系列巧妙的*似和优化。
实时全局光照的技术演进
为了理解Lumen的设计思想,我们首先回顾几种关键的实时GI技术。它们共同的目标是:以可承受的计算成本,逼*离线渲染的全局光照质量。
反射阴影贴图(Reflective Shadow Maps, RSM)
RSM(2005年)是实时GI技术的一个重要起点。它的核心思想非常直观:从光源的视角渲染场景,将第一次被直接照亮的表面像素视为次级光源。
实现简述:
- 从光源视角渲染一张深度贴图(Shadow Map),但同时额外存储每个像素的世界坐标、法线和辐射亮度(Radiance)。
- 在渲染场景中任意一点P时,将RSM中存储的所有“次级光源”视为对P点有贡献的光源。
- 通过对RSM进行重要性采样(例如,400次采样),累加所有次级光源对P点的贡献,从而*似计算出第一次反射的间接光照。
核心贡献:
- 光子注入:首次明确提出了将直接光照表面作为次级光源进行“光子注入”的思路。
- 低频插值:观察到间接光照是低频信号,可以在屏幕空间进行降采样(如每16x16像素计算一次),然后通过插值重建,并对插值误差大的像素进行全采样修正。
- 启发意义:为后续许多实时GI方法提供了基础范式。
然而,RSM只能处理单次反射(Single Bounce),且未考虑次级光源与着色点之间的可见性(Visibility),会导致光泄漏(Light Leaking)等问题。
光传播体积(Light Propagation Volumes, LPV)
LPV(2009年)旨在解决多次反射(Multi-Bounce) 的光照传播问题。其核心思想是:将场景空间体素化(Voxelize),在每个体素中存储一个描述光照方向分布的球谐函数(Spherical Harmonics, SH),然后让光在这些体素网格中迭代传播。
实现简述:
- 注入:使用类似RSM的方法,将直接光照表面的辐射亮度注入到对应的表面体素中,并用SH表示其方向分布。
- 传播:在体素网格上进行多次迭代。每个体素根据其相邻体素的SH值,更新自身存储的SH值,模拟光在空间中的扩散和反射。
- 着色:渲染时,对于物体表面的任意一点,查询其所在体素的SH光照信息,用于计算间接光照。
核心贡献:
- 体素化空间:首次将场景离散化为均匀网格来存储和传播光照。
- 球谐函数应用:利用SH的旋转不变性和卷积特性,高效地表示和传播方向性的光照。
- 实现多次反射:通过迭代传播,理论上可以模拟无限次的光线反弹。
LPV的数学模型存在能量守恒等问题,且对体素分辨率敏感,但它的“体素化+光照传播”思路影响深远。
稀疏体素八叉树全局光照(Sparse Voxel Octree GI, SVOGI)
SVOGI进一步优化了空间表达。它认为均匀的体素网格会浪费大量内存在空区域,因此采用稀疏体素八叉树(Sparse Voxel Octree, SVO) 来高效存储场景的几何与光照信息。
实现简述:
- 使用保守光栅化(Conservative Rasterization)将三角形网格体素化,并构建一棵SVO。树节点不仅存储自身信息,还存储邻居信息以支持滤波。
- 光照注入和存储与LPV类似,但存储在SVO节点中。
- 在着色时进行圆锥追踪(Cone Tracing):不是发射一根细光线,而是发射一个圆锥体。在SVO中追踪时,圆锥体会覆盖越来越粗的体素节点,从而一次性收集大区域的*均光照,极大地提升了采样效率。
核心贡献:
- 层次化空间结构:SVO显著减少了空区域的存储开销。
- 圆锥追踪:这是关键创新,将光线追踪的“线”扩展为“锥”,利用层次化结构实现高效的区域光照查询,特别适合漫反射表面。
然而,SVO数据结构在GPU上构建和管理非常复杂,工程实现难度高。
体素圆锥追踪全局光照(Voxel Cone Tracing GI, VXGI)
VXGI可以看作是SVOGI思想的工程化简化与提升。它放弃了复杂的SVO,转而采用基于相机位置的Clipmap层次化体素网格。
实现简述:
- 层次化体素(Clipmap):围绕相机,建立多层体素网格。离相机*的层体素小、精度高;离相机远的层体素大、精度低。这符合透视投影的观察特性。
- 几何体素化:将场景几何体素化到最精细的体素层中,并计算每个体素在各个方向上的不透明度(Opacity)。
- 光照注入:同样使用RSM将直接光照注入到表面体素。
- 圆锥追踪与着色:渲染时,对屏幕像素发射多个圆锥(方向根据BRDF的粗糙度决定)。在每个圆锥的追踪路径上,从精细体素层向粗糙体素层查询,并累加路径上体素的辐射亮度(考虑不透明度的衰减),从而计算出间接光照贡献。
核心贡献:
- 视图依赖的Clipmap:结构简单,GPU友好,易于更新(通过循环UV坐标,只需更新边缘数据)。
- 实用的圆锥追踪:结合层次化体素,实现了高效、高质量的间接光照计算。
- 工程可行性:相比SVOGI,VXGI更易于在商业引擎中实现和优化。
VXGI的主要问题是光泄漏,尤其是对于薄物体或远处用粗糙体素表示的物体。
屏幕空间全局光照(Screen Space Global Illumination, SSGI)
SSGI(2015年)采用了完全不同的思路:完全利用当前帧已渲染的屏幕颜色和深度信息(G-Buffer)作为间接光照的来源。
实现简述:
- 对于屏幕上需要计算间接光照的像素点P,根据其法线和视角方向,在半球面内发射多条光线。
- 利用层次化深度缓冲(Hierarchical Z-Buffer, Hi-Z) 在屏幕空间内进行高效的光线步进(Ray Marching),快速找到与场景的交点Q。
- 交点Q的屏幕像素颜色(即直接光照结果)就被视为一个“次级光源”,贡献给P点的间接光照。
- 通过复用相邻像素的采样光线,进一步减少计算量。
核心贡献:
- 无场景复杂性约束:不关心场景中有多少物体、是否是动态的,一切计算基于当前屏幕图像。
- 高质量接触阴影:Hi-Z能提供精确的几何相交,能生成细腻的接触阴影(Contact Shadow)。
- 完美处理镜面反射:非常擅长处理屏幕空间内的光滑表面反射。
SSGI的致命缺陷是“屏幕空间”的限制:只能看到相机视野内的信息。对于视野外的物体(如头顶的物体对地面的遮挡)或背面,无法产生正确的间接光照,会导致典型的artifact。
核心挑战总结与Lumen的破局思路
通过以上技术回顾,我们可以总结出实时GI的几个核心挑战及应对思路:
- 采样效率:如何用极少的采样(如每像素1/2根光线)获得*滑的结果? -> 重要性采样、复用、滤波。
- 光照注入与传播:如何表示和存储场景中的“次级光源”? -> 体素化、距离场、屏幕空间。
- 可见性计算:如何快速判断着色点与次级光源之间是否被遮挡? -> 硬件光追、软件光追(SDF)、屏幕空间光追。
- 多次反射:如何模拟光线多次反弹的复杂效果? -> 迭代传播、递归查询。
- 动态场景:如何高效处理物体和光源的移动? -> 增量更新、时空复用。
Lumen的破局宣言:
- 不依赖硬件光追:保证广泛的硬件兼容性。
- 极低采样率:目标每像素仅需极少量采样。
- 高精度:通过将光照探针(Probe)紧贴物体表面放置,实现高质量的细节。
接下来,我们将进入Lumen系统的核心架构解析。
Lumen核心技术解析(第一部分):高效的软件光线追踪
Lumen要做的第一件事,就是在任意硬件上实现一套快速、鲁棒的软件光线追踪(Software Ray Tracing) 系统,这是所有GI查询的基础。其秘密武器是有向距离场(Signed Distance Field, SDF)。
为什么选择SDF?
SDF是一个数学上优雅的空间表达。对于空间中的任意一点p,SDF函数f(p)返回该点到最*物体表面的有符号距离(外部为正,内部为负,表面上为零)。
SDF的卓越特性:
- 连续且可微:是场(Field)而非离散网格,支持连续查询和求导(梯度即法线)。
- 求交高效:光线步进(Ray Marching)时,当前点的SDF值就是绝对安全的步进距离。这称为球体追踪(Sphere Tracing),能快速收敛到交点。
// 球体追踪伪代码
float RayMarchSDF(vec3 rayOrigin, vec3 rayDir) {
float t = 0.0; // 行进距离
for (int i = 0; i < MAX_STEPS; i++) {
vec3 p = rayOrigin + rayDir * t;
float d = sceneSDF(p); // 查询SDF
if (d < EPSILON) return t; // 命中表面
t += d; // 安全步进!
if (t > MAX_DIST) break; // 超出范围
}
return -1.0; // 未命中
}
- 软阴影*似:通过追踪过程中记录的最小SDF值,可以快速*似面积光源产生的柔和阴影。
- 存储高效:可以通过稀疏化存储和LOD技术大幅压缩数据量。
Lumen的双层SDF架构
Lumen采用了双层SDF结构来*衡精度和性能:
-
局部SDF(Mesh SDF):
- 为场景中的每个静态网格资产预计算其自身的SDF,并存储为稀疏体素格式。
- 优点:精度高,能捕捉网格的细节。
- 缺点:一条光线需要对沿途所有可能相交的Mesh SDF进行测试,物体多时开销大。
-
全局SDF(Global SDF):
- 将整个场景(主要是静态部分)融合成一个单一的、低精度的SDF场。同样采用围绕相机的Clipmap结构(多级LOD)。
- 优点:查询极快。一次查询就能得到到整个场景最*表面的距离,无需遍历单个物体。
- 缺点:精度较低,会丢失细节,且动态物体需要更新。
协作方式:
- 粗碰撞检测:首先用全局SDF进行快速的球体追踪,迅速逼*潜在的交点区域。
- 精碰撞检测:在全局SDF指示的附*区域,再使用局部SDF(Mesh SDF) 进行精确的求交计算。
- 这种“由粗到精”的策略,在保持高精度的同时,将光线追踪的性能提升到了可应用于实时GI的水*。
通过这套基于SDF的软件光追系统,Lumen奠定了其高效进行全局光照查询的基石,摆脱了对特定硬件的依赖。
总结

本节课我们一起学*了动态全局光照的基础知识与技术演进。我们从渲染方程的根本挑战出发,回顾了从RSM、LPV、SVOGI、VXGI到SSGI等关键实时GI技术,理解了它们如何从“光子注入”、“空间体素化”、“层次化追踪”和“屏幕空间利用”等不同角度破解GI难题。最后,我们深入探讨了Lumen系统的基石——基于有向距离场(SDF)的高效软件光线追踪。SDF以其连续、可微、求交高效的特性,为Lumen实现高质量、跨*台的实时GI提供了强大的数学工具和底层支持。

在下一节课中,我们将继续探索Lumen系统的其余核心模块:表面缓存(Surface Cache)、光照传播与集成以及最终着色,看它如何将SDF追踪、屏幕空间信息等多种技术熔于一炉,最终实现令人惊叹的动态全局光照效果。

课程21:动态全局光照与Lumen (Part 2) 🎮
在本节课中,我们将深入学*Lumen系统的核心工作原理。我们将探讨如何将光照信息“注入”到场景中,以及如何利用这些信息进行高效的实时全局光照计算。Lumen是一套非常复杂且工程化的系统,它巧妙地将多种技术结合,以解决动态全局光照这一难题。
概述:光照信息的捕获与表达
上一节我们介绍了Lumen的硬件光线追踪基础。本节中,我们来看看如何将光子“注入”到世界,并构建一个用于全局光照计算的统一表达。
Lumen的核心挑战在于:当计算全局光照时,场景中每一个被照亮的表面(无论是否可见)都可能成为间接光源。我们需要一种高效的方法来捕获和表达这些遍布整个复杂场景的光照信息。
Lumen提出了一套特殊的系统来解决这个问题。
Mesh Cards:为光照准备的“快照” 📸
Lumen采用了一种称为 Mesh Cards 的方法。其核心思想是:为场景中的每个物体实例(Instance)从六个角度(+X, -X, +Y, -Y, +Z, -Z)拍摄“快照”。
为什么要这么做?
因为全局光照(GI)需要考虑全光路。一个点被照亮,可能来自其正面、侧面或上方的反射。为了知道物体表面每个方向被直接光照亮的样子,就需要从六个面进行采样。
实现细节:
- 这些快照以相机精度和位置为依据,进行LOD处理:离相机*的物体分辨率高,远的物体分辨率低。
- 每次快照捕获的信息包括:Albedo(反照率)、Normal(法线)、Depth(深度),如果是自发光物体,还会捕获 Emissive(自发光)。
- 所有这些Mesh Cards的数据会被打包存储到一个称为 Surface Cache(表面缓存) 的地方。
Surface Cache:光照信息的统一存储 🗄️
Surface Cache是一个固定大小的存储空间(例如4096x4096),用于存放所有Mesh Cards打包(Atlas/Packing)后的数据。
关键点:
- 它不是一个单一的纹理,而是一系列纹理的集合,包括Albedo层、Normal层、Depth层等。
- 数据会使用硬件支持的压缩方法进行压缩,以节省内存。
- 随着相机移动,Surface Cache中的内容会动态更新(Swap in/out),因为GI计算可以容忍一定程度的数据延迟。
Surface Cache可以理解为对世界光照信息的一次“固化”表达。它将所有物体表面的辐照度(Radiance)信息存储起来,为后续的蒙特卡洛积分采样提供了统一的查询接口。这类似于将光子“烘焙”到这些表面卡片上。
Surface Cache Lighting:计算缓存上的光照 💡
有了Surface Cache,下一步是计算存储在这些“卡片”上的光照强度。这被称为 Surface Cache Final Lighting。计算过程分为三步:
第一步:计算直接光照(Direct Lighting)
对于Surface Cache上的每个像素(代表世界中的一个表面点):
- 获取其世界空间位置、法线、Albedo。
- 对于每个光源,计算其对该点的贡献。
- 可见性(Shadow)处理:利用上一节讲的Mesh SDF进行快速光线追踪,判断该点与光源之间是否有遮挡。这避免了渲染传统的Shadow Map。
- 支持多光源:将每个光源的贡献累加即可。这使得Lumen能天然支持动态多光源的GI。
公式简化表示:
DirectLighting = Sum_over_lights( LightIntensity * BRDF * Visibility )
第二步:构建体素化世界表达(Voxelized World Representation)
直接光照只能处理*处物体。对于远处物体,需要一种更粗粒度的表达。Lumen在世界空间中构建了一套体素化(Voxelized)的光照表达,称为 Voxel Lighting。
实现方式:
- 将世界空间划分为网格(例如,每个体素0.78米边长的立方体)。
- 利用Mesh SDF,向每个体素内发射探测光线。如果能击中任何Mesh,则记录该击中点的法向、Albedo等信息。
- 这个体素化表达不需要每帧全部更新,只更新“脏”(发生变化)的区域,效率很高。
第三步:累积多重反射(Multi-Bouncing)光照
这是Lumen非常巧妙的思想,实现了“左脚踩右脚”式的光能传递:
- 初始帧(T0):Surface Cache上只有直接光照(Direct Lighting)。Voxel Lighting是黑的。
- 帧T1:利用T0帧的Surface Cache Final Lighting(此时只有直接光)来照亮并更新Voxel Lighting。此时Voxel里存储了一次反射的光。
- 帧T2:计算Surface Cache上新的间接光照(Indirect Lighting)。方法是对每个Surface Cache点,向周围半球空间发射光线,并从Voxel Lighting中采样其击中点的亮度。由于Voxel Lighting里已经有一次反射的光,所以这次采样得到的结果就是二次反射的光照。将直接光和这次采样的间接光相加,得到T2帧的Surface Cache Final Lighting。
- 后续帧:重复此过程。用T2帧更新Voxel Lighting(包含二次反射光),再用更新后的Voxel去计算T3帧的间接光(得到三次反射)... 如此迭代,多重反射的光照效果就随着时间累积起来了。
核心循环伪代码表示:
// 假设已有上一帧数据
for each frame:
// 1. 计算当前帧Surface Cache的直接光照
DirectLighting = ComputeDirectLighting(SurfaceCache);
// 2. 用上一帧的SurfaceCacheFinalLighting,更新VoxelLighting
VoxelLighting = UpdateFromSurfaceCache(PrevSurfaceCacheFinalLighting);
// 3. 采样VoxelLighting,得到当前帧的间接光照
IndirectLighting = SampleIndirectLighting(VoxelLighting);
// 4. 合并,得到当前帧最终缓存光照
CurrentSurfaceCacheFinalLighting = DirectLighting + IndirectLighting;
这种方法使得Lumen能以实时性能,渐进地逼*多次反射的全局光照效果。
自适应屏幕空间探针(Adaptive Screen-Space Probes) 🎯
现在,我们有了存储在世界表面(Surface Cache)和体素(Voxel)中的光照信息。接下来,需要为屏幕上每一个待渲染的像素收集其周围半球空间的所有辐照度。Lumen采用在屏幕空间分布探针(Probes) 的方法。
基本分布:
- 默认在屏幕上每隔16x16像素放置一个探针。
- 每个探针会向周围半球空间发射固定数量(例如8x8=64根)的光线进行采样。
- 采样结果(辐照度和击中距离)使用八面体映射(Octahedral Mapping) 存储在一张2D纹理中。这种映射能实现球面到2D的相对均匀映射,且插值友好。
核心代码(八面体映射方向->UV):
// 将标准化方向向量映射到八面体UV
vec2 DirectionToOctahedronUV(vec3 dir) {
vec3 absDir = abs(dir);
float sum = absDir.x + absDir.y + absDir.z;
dir /= sum;
if(dir.z >= 0.0) {
return dir.xy * 0.5 + 0.5;
} else {
return (vec2(1.0 - abs(dir.yx)) * sign(dir.xy)) * 0.5 + 0.5;
}
}
自适应细化(Adaptive Refinement):
一个关键问题是,屏幕相邻的像素在3D空间中可能相距甚远(例如,前景物体和远处的背景)。如果强行用同一个探针插值,会导致光照错误。
Lumen的解决方案是自适应细分:
- 对于每个16x16的图块(Tile),检查其覆盖的像素在3D世界中的深度和法线变化。
- 如果变化超过阈值,则认为该区域的采样精度不足。
- 将这个图块细分为更小的区域(例如8x8,甚至4x4),并分配额外的、更密集的探针。
- 这些细分的探针被巧妙地打包(Packing)到屏幕空间探针图集的空白区域,内存利用率高。
这种方法确保了在几何复杂的区域有更高的采样密度,而在*坦区域则保持较低采样以节省性能。
重要性采样与过滤(Importance Sampling & Filtering) ✨
简单均匀地向半球发射光线效率低下。Lumen采用了重要性采样,让光线更多地射向对最终贡献大的方向。
重要性来源有两个:
- 光源分布:从上一帧的屏幕空间探针中估计出哪些方向更亮。这基于“光照变化不会太快”的假设。
- 表面法线分布(BRDF):不仅考虑当前像素的法线,还考虑该探针影响区域内(如32x32像素)所有像素法线的分布。通过采样这些法线并计算其加权后的球谐函数(SH)表示,得到该区域整体的法线分布重要性。
实现策略:
- 保持每个探针总发射光线数量不变(如64根)。
- 计算每个采样方向的联合PDF(光源重要性 * BRDF重要性)。
- 对PDF值排序,剔除那些低于阈值的最不重要的方向。
- 将省下来的采样次数,分配给最重要的方向,实现超采样(Supersampling)。
- 这保证了计算量可控,同时光线集中在了对光照贡献最大的区域。
过滤(Filtering):
即使经过重要性采样,探针采样的结果仍有噪声。Lumen在空间上对相邻探针进行过滤:
- 收集相邻探针(如3x3区域)的采样光线。
- 角度过滤:如果相邻探针某根光线的方向与当前探针对应方向夹角过大(>10度),则丢弃。
- 距离过滤:如果相邻探针光线击中点的距离与当前探针的估计击中距离相差太大,则丢弃。
- 只合并那些方向和距离都“合理”的相邻采样,从而在降噪的同时避免错误的漏光(Light Leaking)或模糊。
世界空间探针作为后备(World-Space Probes as Fallback) 🌍
屏幕空间探针擅长处理*处、相机视野内的光照。但对于非常远处的光照,每个屏幕探针都发射长距离射线效率很低。
Lumen引入了世界空间探针(World-Space Probes) 作为后备方案:
- 以相机为中心,用Clipmap的形式在世界空间中放置探针(例如4层,每层48x48x3个探针)。
- 这些探针的采样密度更高(如32x32方向),专门用于捕获远距离光照。
- 光线连接(Ray Connection):屏幕空间探针的射线在发射一段距离后(例如,到达其最*世界空间探针包围盒的对角线长度两倍),如果尚未击中物体,则停止。转而查询该射线方向上的世界空间探针的采样结果,作为后续光照。
- 同样,世界空间探针采样时也会跳过*处区域,避免与屏幕空间探针重复工作。
- 只有被屏幕空间探针“标记”为需要的世界空间探针才会被更新,性能可控。
这套机制形成了层次化的光线追踪:屏幕空间探针处理*处和细节,世界空间探针提供远处和稳定的光照环境,两者无缝衔接。
最终着色(Final Shading) 🎨
经过上述所有步骤,我们最终为屏幕上的每个像素准备好了光照信息。
着色过程:
- 对于每个像素,找到其周围最*的几个(如4个)屏幕空间探针。
- 从这些探针中,根据像素与探针的空间距离和法向差异进行插值,获取该像素的间接光照球谐(SH)表示。
- 将间接光照SH与直接光照计算结果结合。
- 由于光照信息已投影到低频的SH上,自然起到了*滑滤波的作用,最终得到柔和、真实的全局光照效果。
整个Lumen的射线追踪策略总结:
Lumen根据距离和精度,智能选择不同的射线追踪方法:
- 最*处(屏幕空间):使用基于深度缓冲(Hi-Z)的屏幕空间光线步进(Ray Marching),速度最快。
- *处(~40米内):使用Mesh SDF进行精确追踪,可以获取击中点的具体Mesh ID和属性,用于查询Surface Cache。
- 远处(~200米内):使用Global SDF进行粗略追踪,只能获取击中点的Voxel Lighting信息。
- 无限远:采样天空盒(Skybox)作为环境光。
对于每个探针的每根射线,Lumen会按顺序尝试这些方法,直到获得有效击中或达到最大距离。这种混合策略在速度和质量之间取得了最佳*衡。
总结与展望 🚀
本节课中,我们一起深入剖析了Lumen动态全局光照系统的核心架构。我们从如何通过Mesh Cards和Surface Cache捕获并表达场景光照信息开始,学*了通过Voxel Lighting实现多重反射累积的巧妙算法。接着,我们探讨了在屏幕空间自适应分布探针进行重要性采样的方法,以及利用世界空间探针作为远距离光照后备的层次化结构。最后,我们了解了如何利用所有这些信息完成最终像素着色。
Lumen是一套工程上极其复杂、精妙的系统,它并非单一算法的突破,而是将距离场追踪、表面缓存、层次化探针采样、时空重投影等多种技术深度融合的“集大成者”。它首次在游戏引擎中实现了高质量、全动态的实时全局光照,为下一代游戏渲染树立了新的标杆。
尽管Lumen已经取得了巨大成功,但实时全局光照领域仍在快速发展。硬件光线追踪的普及、新的算法研究(如DDGI, SDF-DDGI)都在推动着这个领域前进。Lumen为我们展示了解决这一难题的可行路径和工程智慧,未来的渲染技术必将在此基础上变得更加高效、强大与普及。
本节课我们一起学*了:
- Mesh Cards与Surface Cache:将复杂场景的光照信息统一捕获和表达。
- 光照缓存计算:通过直接光照计算与Voxel Lighting的迭代更新,实现多重反射。
- 屏幕空间探针:自适应地在屏幕分布探针,进行重要性采样以收集间接光照。
- 世界空间探针:作为后备,高效提供远距离光照信息。
- 混合射线追踪策略:根据距离和精度需求,智能选择最合适的追踪方法。
- Lumen的系统性思想:将不规则的世界转化为规则化的数据表达,从而应用高效的采样与积分方法。


Lumen的诞生标志着实时全局光照从预计算和妥协方案,正式迈入了全动态、高质量的新时代。理解其设计思想,对于从事图形学和游戏引擎开发至关重要。


21.时域调制 (IV) | GAMES204-计算成像 - P1 - GAMES-Webinar - BV1KM41117Ss 🎥
概述


本节课我们将继续探讨时域调制在计算成像中的应用,重点关注如何解决多路径和散射介质带来的影响,并介绍哈密顿编码在提高深度测量精度方面的应用。



1. 多路径和散射介质的影响





- 多路径问题: 间接光成像中,光经过多次反射到达传感器,导致相位信息模糊,难以区分直接光和间接光。
- 散射介质: 散射介质会改变光的传播路径,导致相位信息进一步模糊,难以进行深度测量。
2. 微成像技术
- 原理: 使用两个频率相*的高频信号进行调制,形成波包,通过波包的干涉效应来消除多路径和散射介质的影响。
- 应用: 可以有效地分离直接光和间接光,提高深度测量的精度。
3. 哈密顿编码
- 原理: 借鉴格雷码的思想,将编码方式设计为相邻编码之间只有一位不同,从而延长编码路径长度,提高深度测量的精度。
- 应用: 可以有效地提高深度测量的鲁棒性,降低噪声对深度精度的影响。
4. 代码示例
# 哈密顿编码示例
def hamming_code(bits):
# ... (实现哈密顿编码算法)
return encoded_bits
5. 总结
本节课介绍了微成像技术和哈密顿编码在提高深度测量精度方面的应用。通过使用这些技术,可以有效地解决多路径和散射介质带来的影响,提高深度测量的精度和鲁棒性。
6. 下节课预告


下节课我们将继续探讨时域调制在计算成像中的应用,重点关注时间编码和空间编码。

课程22:GPU驱动的几何管线 - Nanite (Part 1) 🚀
在本节课中,我们将要学*现代游戏引擎中一项革命性的技术——Nanite。Nanite 是一种能够*乎无限地渲染高精度几何细节的虚拟化几何管线。为了理解它的精妙之处,我们需要先回顾其技术根源。本节课我们将首先介绍两项关键技术:Cluster-Based Rendering 和 Visibility Buffer,它们是理解 Nanite 工作原理的重要基石。
回顾:传统渲染管线的瓶颈
上一节我们介绍了游戏引擎渲染的基础。在传统的渲染管线中,CPU 负责发起绘制调用(DrawPrimitive),准备渲染状态(如材质、纹理)并将数据提交给 GPU。这个过程存在显著瓶颈:
- CPU 开销大:每次绘制调用和状态切换都非常昂贵,尤其是在场景包含成千上万个物体和材质时,组合爆炸会导致 CPU 不堪重负。
- GPU 等待:GPU 经常需要等待 CPU 准备好数据,造成算力闲置。
- 数据传输慢:CPU 和 GPU 之间的数据拷贝是性能瓶颈。
现代游戏的趋势是场景几何极其复杂,材质种类繁多,这使得传统管线难以为继。其根本矛盾在于:CPU 忙于为 GPU 准备渲染指令,而 GPU 的强大算力却无法被充分利用。
曙光:GPU Driven Rendering
为了解决上述矛盾,业界提出了 GPU Driven Rendering 的构想。其核心思想是:将尽可能多的渲染准备工作(如视锥裁剪、LOD选择、可见性判断)从 CPU 转移到 GPU 上执行。
实现这一构想依赖于两项关键技术的发展:
- 计算着色器(Compute Shader):允许在 GPU 上执行通用计算,无需与 CPU 频繁交换数据。
- 间接绘制(Indirect Draw):允许 GPU 通过一个参数缓冲区(
DrawIndirect)一次性绘制大量网格,极大减少了 CPU 的绘制调用。
理想状态下,CPU 只需设置相机参数并发出一个“绘制整个场景”的指令,剩下的所有工作(几何处理、可见性剔除、最终绘制)都由 GPU 自主完成。这释放了 CPU 资源,使其可以专注于游戏逻辑、AI、物理等任务。
关键技术一:Cluster-Based Rendering 🧱
以下是理解 GPU 驱动渲染的第一个关键概念:基于簇的渲染。其核心观察是:对于一个复杂的物体(如建筑),我们通常只能看到它的一小部分。传统的按物体(Instance)进行可见性剔除粒度太粗,会导致大量不可见三角形被提交渲染。
Cluster-Based Rendering 的核心思想:将每个大的网格(Mesh)进一步细分为许多固定大小(如64或128个三角形)的小块,称为 簇(Cluster)。然后,在 GPU 上以 簇 为粒度进行精细的可见性剔除。
工作流程解析
以《刺客信条:大革命》的实现为例,其管线流程如下:
-
CPU 端(粗略剔除):
- 对场景中的物体(Instance)进行简单的视锥剔除。
- 根据材质等信息进行哈希分组,合并渲染状态,减少提交次数。
- 将每个物体的数据(变换矩阵、LOD 信息等)打包成一个缓冲区提交给 GPU。
-
GPU 端(精细剔除与绘制):
- Instance 级剔除:利用上一帧的深度信息进行保守的遮挡剔除,过滤掉明显不可见的物体。
- Cluster 级剔除:对每个可见物体内部的簇进行视锥剔除和背面剔除。
- 三角形级剔除:在簇内部进一步剔除背面三角形。
- 索引缓冲区压缩(Compaction):将所有最终可见的三角形的索引信息,紧凑地打包成一个巨大的全局索引缓冲区。
- 最终绘制:通过一次或少数几次间接绘制调用,将这个包含所有可见三角形的“超级缓冲区”绘制出来。
公式/代码示意:核心剔除逻辑
// 伪代码:GPU Compute Shader 中的簇剔除
foreach (visibleInstance in instanceList) {
foreach (cluster in instance.clusterList) {
if (IsClusterVisible(cluster, cameraFrustum)) {
if (!IsClusterBackface(cluster, cameraViewDir)) {
foreach (triangle in cluster.triangleList) {
if (!IsTriangleBackface(triangle, cameraViewDir)) {
// 原子操作,将三角形索引写入全局缓冲区
uint idx = AtomicAdd(globalIndexBufferCounter, 3);
globalIndexBuffer[idx] = triangle.v0;
globalIndexBuffer[idx+1] = triangle.v1;
globalIndexBuffer[idx+2] = triangle.v2;
}
}
}
}
}
}
// 最终通过 DrawIndexedIndirect 使用 globalIndexBuffer 绘制
该方法的优势:
- 最大化几何利用率:只绘制真正可见的像素,极大减少了
overdraw。 - 减少 CPU 开销:复杂的剔除计算在 GPU 上并行完成。
- 单次绘制调用:将场景渲染简化为极少的绘制指令。
挑战:遮挡剔除(Occlusion Culling)
对于簇渲染,高效的遮挡剔除至关重要。因为即使簇在视锥内,也可能被前面的物体完全挡住。
- 方法一(大革命采用):重用上一帧的深度缓冲(Depth Buffer),将其重投影(Reproject)到当前帧视角,生成一个*似的深度挡板,用于快速测试簇的可见性。这对静止或缓慢移动的场景效果很好。
- 方法二(Two-Pass Occlusion):
- 用上一帧深度做一次保守剔除,得到“可能可见”的物体列表。
- 快速渲染这些“可能可见”的物体,生成当前帧的一个粗略深度缓冲。
- 用这个新的深度缓冲,对所有物体再进行一次准确的遮挡测试。
这种方法更精确,能更好地处理动态物体。
挑战:阴影渲染
阴影贴图(Shadow Map)的渲染复杂度同样取决于几何数量。Cluster-Based Rendering 同样可以应用:
- 重用上一帧的阴影贴图进行粗略剔除。
- 利用相机视角的深度信息构建一个“遮挡体”,快速判断哪些物体在光的视角下不可能产生可见阴影,从而在渲染阴影贴图时跳过它们。
关键技术二:Visibility Buffer 👁️
上一节我们看到了如何高效地提交几何。接下来,我们看看如何高效地对这些几何进行着色(Shading)。传统的前向渲染(Forward)或延迟渲染(Deferred)在面临极度复杂的几何(如茂密植被)时,会遇到 overdraw 过高或 G-Buffer 过“胖”的问题。
Visibility Buffer 的核心思想:将几何渲染和材质着色彻底解耦。第一个 Pass 只做一件事——记录屏幕上每个像素对应的是哪个几何图元。第二个 Pass 再根据这些信息,去获取所需的材质数据并进行着色计算。
工作流程解析
-
Visibility Pass:
- 渲染所有几何,但不像延迟渲染那样输出庞大的 G-Buffer(法线、漫反射、高光等)。
- 只为每个像素输出一个精简的标识符,通常包含:
Primitive ID(三角形ID)Instance ID(实例ID)Material ID(材质ID)
- 这个缓冲区非常小(例如每个像素 32 位),写入速度极快。
-
Shading / Material Pass:
- 对屏幕上的每个像素,从 Visibility Buffer 中读取其
Primitive ID和Instance ID。 - 几何重建(Geometry Reconstruction):根据这些 ID,从原始顶点缓冲区中取出对应三角形的三个顶点数据(位置、UV等)。
- 利用相机参数和屏幕坐标,手动计算该像素在三角形内的重心坐标。
- 使用重心坐标对顶点的 UV 等进行插值,得到该像素的材质采样坐标。
- 根据
Material ID采样对应的纹理(漫反射、法线等)。 - 最后进行光照计算。
- 对屏幕上的每个像素,从 Visibility Buffer 中读取其
公式/代码示意:重心坐标插值
// 伪代码:在Shading Pass中重建像素属性
struct Vertex { float3 pos; float2 uv; };
Vertex v0 = GetVertex(primitiveId, 0);
Vertex v1 = GetVertex(primitiveId, 1);
Vertex v2 = GetVertex(primitiveId, 2);


// 将顶点投影到屏幕空间,计算当前像素的重心坐标 (bary)
float3 bary = CalculateBarycentricCoords(screenPos, v0.pos_proj, v1.pos_proj, v2.pos_proj);
// 使用重心坐标插值UV
float2 uv = bary.x * v0.uv + bary.y * v1.uv + bary.z * v2.uv;
// 采样材质
float4 diffuse = texture2D(materialTextures[materialId].diffuse, uv);
该方法的优势:
- 极致抗 Overdraw:Visibility Pass 极其廉价,无论一个像素被覆盖多少次,昂贵的纹理采样和着色计算只在 Shading Pass 执行一次。
- 缓存友好:相邻像素很可能属于同一个三角形,因此在 Shading Pass 中读取顶点数据时缓存命中率极高。
- 内存带宽节省:避免了写入和读取庞大 G-Buffer 的带宽消耗。
- 灵活混合管线:可以轻松与传统延迟渲染管线结合。对于
overdraw低的物体(如角色)使用传统延迟渲染写入 G-Buffer;对于overdraw高的物体(如植被)使用 Visibility Buffer。最终在同一个 Lighting Pass 中,从 G-Buffer 或重建的数据中读取信息进行统一光照。
挑战与细节:
- 导数计算(DDX/DDY):在 Shading Pass 中手动进行纹理采样时,需要计算 UV 在屏幕空间中的梯度以确定正确的 Mipmap 层级。这需要通过屏幕空间坐标和顶点 UV 反推出来。
- 抗锯齿:需要额外的处理,例如结合 MSAA 或 TAA。


本节总结
本节课我们一起学*了 Nanite 虚拟几何管线的两大技术支柱:
- Cluster-Based Rendering:通过将几何细分为簇,并在 GPU 上并行进行极精细的可见性剔除,实现了从“逐个物体绘制”到“一次绘制所有可见三角形”的飞跃,解决了几何提交的效率瓶颈。
- Visibility Buffer:通过将几何标识与着色分离,用极低成本的 Visibility Pass 应对复杂几何的重度
overdraw,再在 Shading Pass 中按需重建数据,解决了复杂场景下的着色效率瓶颈。


这两项技术共同指向一个目标:构建一个完全由 GPU 驱动、能智能且高效处理*乎无限几何复杂度的渲染管线。它们为 Nanite 的实现铺*了道路。在下节课中,我们将深入 Nanite 的核心架构,看它是如何将这些思想发扬光大,并引入 虚拟几何(Virtualized Geometry) 和 虚拟纹理(Virtual Texture) 等概念,最终实现令人惊叹的无限细节渲染。

课程22:GPU驱动的几何管线 - Nanite (Part 2) 🎮

概述
在本节课中,我们将深入学*Nanite技术的核心部分,特别是其几何表达方式和渲染管线。我们将探讨Nanite如何解决无限几何细节的挑战,以及它如何通过创新的数据结构与算法实现高效渲染。
一、Nanite的起源与目标
上一节我们介绍了GPU驱动几何管线的概念,本节中我们来看看Nanite的具体实现。Nanite团队的目标是实现电影级的实时渲染精度,其核心思想是将几何虚拟化,类似于虚拟纹理技术。
作者分析了多种几何表达方式,包括体素、细分曲面、置换贴图和点云,但最终选择了三角形作为基础。原因在于三角形是内容生产管线最成熟、硬件支持最完善的几何表达形式。
二、核心几何表达:Cluster与LOD体系
Nanite最核心的创新在于其几何表达方式。它并非直接处理海量三角形,而是构建了一套复杂的层次化系统。
2.1 Cluster的构建
首先,Nanite将精细的模型分割成许多小的簇。每个簇包含固定数量的三角形(例如128个)。这构成了几何处理的基本单位。
以下是构建簇的简化概念:
// 伪代码:将网格分割为簇
for each mesh in scene:
clusters = splitMeshIntoClusters(mesh, trianglesPerCluster=128)
2.2 视图相关的LOD过渡
Nanite实现了视图相关的LOD过渡。这意味着对于一个实例,其内部不同簇可以根据与相机的距离,独立切换到不同的细节层次。这与传统方法中整个实例锁定一个LOD级别有本质区别。
为了实现这一点,并避免在LOD切换时产生接缝或视觉瑕疵,Nanite采用了更复杂的结构。
三、Cluster Group与有向无环图
简单的簇合并简化方法会带来问题,例如在簇边界产生永不变动的“缝合线”,导致视觉上的不均匀和瑕疵。
3.1 Cluster Group的引入
Nanite的解决方案是将多个簇(例如16个)分组为一个簇组。在进行几何简化时,它锁定整个簇组的外部边界,而允许组内簇之间的内部边界被打碎并一起简化。
简化完成后,再次运行簇划分算法,会得到一组新的、数量更少的簇。关键点在于:
- 新生成的簇与原始簇之间不再是简单的父子树状关系。
- 它们之间形成了多对多的关联。
3.2 理解有向无环图
Nanite用有向无环图来描述不同LOD层级之间簇的复杂关系。下图展示了其核心结构:

注意:图中每个节点代表一个簇,不同颜色的框代表不同的簇组。可以看到:
- LOD1层的簇可能由LOD0层多个不同簇组的簇简化合并而来。
- 这种关系是局部的、多对多的,而非整洁的树状结构。
这种设计的精妙之处在于,每一层LOD的簇组边界都在变化。当相机移动导致LOD切换时,没有固定的高频边界会被人眼持续注意到,从而避免了视觉上的“接缝”感。
四、并行的LOD选择与BVH加速
有了复杂的DAG结构,下一个挑战是如何高效地为每个簇决定在当前视图下应该使用哪个LOD层级进行渲染。
4.1 并行化决策
传统方法是遍历树状结构。Nanite则希望利用GPU的并行能力。它通过两个关键设计实现:
- 误差单调性:在DAG中,从叶节点(高细节)到根节点(低细节),几何简化的误差是单调递增的。
- 拍*决策:每个节点(簇)独立判断。一个簇被渲染的条件是:
- 其父节点的误差 > 当前视图的误差阈值
- 且该簇自身的误差 <= 当前视图的误差阈值
公式:Render(Cluster) = (ParentError > Threshold) && (ClusterError <= Threshold)
这使得所有簇的LOD决策可以完全并行化计算,无需复杂的图遍历。
4.2 BVH加速结构
即使并行化,需要处理的簇组数量仍然巨大。Nanite进一步引入了包围层次盒进行空间加速。
它为每一层LOD的所有簇组分别构建一棵BVH树,然后将所有这些BVH树的根节点连接到一个共同的根节点下,形成一个“超级BVH”。
优势:
- 当物体距离相机很远时,可以快速剔除整棵高层LOD的BVH树(及其下所有簇组),无需逐一检查。
- 极大地减少了需要参与LOD选择的单元数量。
五、Nanite的渲染管线
有了几何表达和LOD选择,接下来看看Nanite如何渲染。
5.1 软件光栅化
当三角形小到接*像素大小时,传统的硬件光栅化(通常以2x2像素块或瓦片为单位)效率低下。Nanite对此进行了优化:
- 规则:如果一个簇内所有三角形在屏幕上的投影边长均小于16像素,则整个簇使用软件光栅化。
- 实现:通过计算着色器实现,直接计算三角形覆盖的像素,比硬件管线更高效。
- 深度测试:通过原子操作模拟Early-Z,将深度值编码在64位数据的高位,实现快速的深度比较和写入。
5.2 可见性缓冲渲染
Nanite采用可见性缓冲进行着色:
- 几何通道:不直接计算颜色,而是将每个像素对应的簇ID和三角形ID写入缓冲区。
- 着色通道:根据ID反查出顶点数据,进行插值,再采样材质纹理并计算光照。
这种方法将复杂的材质计算延迟到后处理,与传统的延迟渲染思路结合,可以很好地混合渲染Nanite几何和传统几何。
5.3 材质处理
场景中材质种类繁多。Nanite的解决方案是:
- 早期方法:为每种材质执行一次全屏Pass,只渲染材质ID匹配的像素。材质多时开销大。
- 改进方法(瓦片化):将屏幕划分为瓦片(如64x64)。为每种材质生成一个位图数组,标记哪些瓦片包含该材质。渲染时,每种材质只需处理包含它的瓦片,大大减少了冗余计算。
六、虚拟阴影贴图
如此高精度的几何对阴影提出了极高要求。Nanite搭配了虚拟阴影贴图技术。
6.1 核心思想
其本质是解决阴影贴图的采样率问题:根据视图空间的采样密度,动态分配光空间的阴影贴图分辨率。
- 将阴影贴图划分为许多小页。
- 相机*处,对应阴影贴图页的分辨率高;相机远处,对应页的分辨率低。
- 这实现了阴影细节与屏幕像素密度的最佳匹配。
6.2 优势
- 高质量:阴影质量远高于传统的级联阴影贴图。
- 高效更新:当相机和光源不动时,大部分阴影数据可复用,只需更新少量页。
- 无突变:避免了级联阴影层间切换时的“跳跃”感。
七、数据流与压缩
为了支持开放大世界,Nanite需要动态流式加载几何数据。
- 流式加载:基于构建好的BVH和DAG结构,根据视图依赖,仅加载所需LOD层级的簇数据页。
- 数据压缩:
- 内存中:使用量化技术,将顶点位置、法线、UV等浮点数据转换为定点数,大幅减少存储空间。
- 磁盘上:使用硬件支持的LZ压缩算法,结合Direct Storage技术,实现数据从SSD到显存的直接解压加载,绕过CPU和主内存瓶颈。
总结
本节课我们一起深入学*了Nanite技术的核心机制。我们从其选择三角形作为基础表达开始,探讨了它如何通过簇和簇组构建复杂的有向无环图来实现无缝的视图相关LOD过渡。我们了解了其并行化的LOD选择算法以及用于加速的BVH结构。在渲染方面,我们学*了软件光栅化对微小三角形的优化、基于可见性缓冲的着色流程、高效的瓦片化材质处理,以及配套的虚拟阴影贴图技术。最后,我们简要介绍了其数据流式加载与压缩策略。
Nanite代表了一种全新的几何管线思路,通过极致的工程优化将当代硬件性能推向极限,让我们在实时渲染中首次触及电影级的几何细节。它不仅是技术的胜利,更是对实现逼真虚拟世界这一梦想的坚实一步。


注:本教程根据GAMES104课程第22讲内容整理,旨在提炼核心概念,简化复杂细节,以帮助初学者建立对Nanite技术的基本理解。部分实现细节可能随技术发展而变化。

22.时域调制 (V) | GAMES204-计算成像 - P1 - GAMES-Webinar - BV1cg411s7Cp

各位同学晚上好,新年快乐,新的1年开始了。今天我们来学*一些关于运动模糊的内容。
运动模糊是摄影中常见的一种现象,它是由物体在相机曝光时间内移动造成的。例如,拍摄星轨、运动的汽车、行人以及瀑布等场景时,都可能出现运动模糊。

消除运动模糊的技术

消除运动模糊主要有两种技术:
- 编码曝光摄影:利用震颤快门技术,通过在不同时间点打开和关闭快门,记录下物体在不同时刻的位置信息,从而在后续处理中恢复清晰图像。
- 运动不变摄影:通过控制相机运动,使场景中所有物体都进行额外的运动,从而实现运动模糊的均匀化,便于后续处理。
编码曝光摄影
编码曝光摄影的核心思想是时间采样,通过在不同时间点打开和关闭快门,记录下物体在不同时刻的位置信息,从而在后续处理中恢复清晰图像。


公式:

其中,卷积核表示物体的运动轨迹。
挑战:
- 卷积核未知:实际场景中,很难获取准确的卷积核信息。
- 场景复杂:场景中可能存在不同运动速度、方向的物体,以及固定的背景,需要单独分割处理。
运动不变摄影
运动不变摄影通过控制相机运动,使场景中所有物体都进行额外的运动,从而实现运动模糊的均匀化,便于后续处理。
公式:
其中,抛物线扫描卷积核表示相机的运动轨迹。

优势:


- 无需分割:无需对前景和背景进行分割处理。
- 保留更多光通量:整个曝光时间内,快门都是打开的,因此可以保留更多光通量,提高图像亮度。

挑战:

- 机械实现:需要额外的机械装置来实现相机的抛物线扫描运动。


总结
本节课介绍了两种消除运动模糊的技术:编码曝光摄影和运动不变摄影。编码曝光摄影通过时间采样记录物体位置信息,运动不变摄影通过控制相机运动实现运动模糊的均匀化。两种技术各有优缺点,需要根据具体场景选择合适的方法。
本节课我们学*了以下内容:
- 运动模糊的概念和产生原因
- 消除运动模糊的技术
- 编码曝光摄影和运动不变摄影的原理和实现方法
- 两种技术的优缺点

希望同学们能够通过本节课的学*,对运动模糊的消除技术有更深入的了解。

23.空域调制 | GAMES204-计算成像 - P1 - GAMES-Webinar - BV1a24y1h77A
欢迎来到 GAMES204 计算成像课程,今天我们将探讨空域调制这一有趣的主题。

空域调制概述
空域调制是一种通过在图像的空域进行编码来改变图像特性的技术。它可以通过多种方式实现,例如:


- 编码孔径 (Coded Aperture): 通过在孔径上添加编码图案来改变点扩散函数 (PSF) 的形状。
- 焦点交换 (Focus Swap): 通过在不同深度进行曝光来获取不同焦距的图像,从而实现景深扩展。
- 广义光学 (Generalized Optics): 通过使用自由曲面、微纳结构等元件来改变光学系统的特性。
编码孔径 (Coded Aperture)

编码孔径通过在孔径上添加编码图案来改变 PSF 的形状。这可以通过以下步骤实现:

- 设计编码图案: 根据所需的 PSF 形状设计编码图案。
- 制作编码孔径: 将编码图案制作成孔径,例如使用相位板或微纳加工技术。
- 成像: 使用编码孔径进行成像,得到具有特定 PSF 的图像。

核心概念:

- 编码图案: 用于改变 PSF 形状的图案。
- 点扩散函数 (PSF): 描述图像模糊程度的函数。

焦点交换 (Focus Swap)

焦点交换通过在不同深度进行曝光来获取不同焦距的图像,从而实现景深扩展。这可以通过以下步骤实现:

- 在不同深度进行曝光: 在不同深度对同一场景进行曝光,得到一系列图像。
- 图像融合: 将这些图像融合成一张具有扩展景深的图像。


核心概念:
- 景深: 图像中清晰区域的大小。
- 图像融合: 将多张图像融合成一张图像的技术。

广义光学 (Generalized Optics)
广义光学通过使用自由曲面、微纳结构等元件来改变光学系统的特性。这可以通过以下步骤实现:
- 设计光学系统: 根据所需的特性设计光学系统。
- 制作光学元件: 将设计的光学元件制作出来。
- 组装光学系统: 将光学元件组装成光学系统。



核心概念:


- 自由曲面: 一种非球面曲面。
- 微纳结构: 一种尺寸在微米或纳米量级的结构。

总结



本节课介绍了空域调制的基本概念和三种主要方法:编码孔径、焦点交换和广义光学。这些方法可以用于改善图像质量、扩展景深和实现其他图像处理任务。
本节课中我们一起学*了:



- 空域调制的基本概念
- 编码孔径、焦点交换和广义光学的原理
- 这些方法的应用场景



希望这节课能够帮助你更好地理解空域调制技术。

24.端到端相机设计 | GAMES204-计算成像 - P1 - GAMES-Webinar - BV178411w7dC


概述


在本节课中,我们将学*端到端相机设计,从物理意义出发,从用户场景出发,设计出能够实现特定功能的相机系统。

四个维度


以下是本节课将从四个维度展开:


- 成像系统设计:从光学系统、传感器和计算模型三个方面进行设计。
- 优化模型:建立优化模型,以实现特定功能。
- 调制方式:介绍经典调制方式,如食欲调制和空域调制。
- 端到端设计:实现端到端的相机设计。
成像系统设计
光学系统设计








光学系统设计是相机设计的基础,需要考虑以下因素:






- 成像质量:包括分辨率、对比度、畸变等。
- 视野:相机能够捕捉到的场景范围。
- 焦距:相机能够聚焦的距离范围。







传感器设计

传感器设计需要考虑以下因素:



- 像素尺寸:像素越小,分辨率越高。
- 填充率:填充率越高,图像质量越好。
- 动态范围:动态范围越大,能够捕捉到的场景范围越广。



计算模型设计





计算模型设计需要考虑以下因素:





- 图像重建:从传感器获取的图像中恢复出真实场景。
- 图像处理:对图像进行增强、去噪等操作。




优化模型

优化模型是相机设计的关键,需要考虑以下因素:

- 目标函数:定义优化目标,如成像质量、分辨率等。
- 约束条件:限制优化过程,如物理限制、计算限制等。

调制方式

调制方式是相机设计的重要手段,可以增强图像质量、扩展动态范围等。





- 食欲调制:通过改变光源的强度和颜色,实现图像增强。
- 空域调制:通过改变光学系统的结构,实现图像增强。




端到端设计

端到端设计是将光学系统、传感器和计算模型整合在一起,实现特定功能的相机系统。

案例分析
本节课将介绍以下案例分析:


- 单片透镜大视野成像:通过光学和算法联合设计,实现单片透镜的大视野成像。
- 超分辨率成像:通过优化采样方案和算法,实现超分辨率成像。
- HDR成像:通过空域调制和算法,实现HDR成像。



总结


本节课介绍了端到端相机设计的基本概念、方法和案例。通过学*本节课,读者可以了解端到端相机设计的原理和实现方法,为后续的学*和研究打下基础。



GAMES001-图形学中的数学 - P1:线性代数基础(全) 🧮



在本节课中,我们将要学*计算机图形学中至关重要的数学基础——线性代数。我们将从向量和向量空间的基本概念开始,逐步理解矩阵、线性变换及其在图形学中的直观意义和应用。课程内容旨在帮助大家建立图形学视角下的数学思维,让抽象的数学概念变得“看得见”。

课程介绍与团队阵容 👥

本课程由陈宝权老师及其团队主讲。课程讲师包括陈老师实验室的直博生李新雨和阮梁万,他们均从事物理模拟仿真方向的研究。助教团队由陶林霄、王瑞成、朱月城和余成组成,他们都是具有丰富图形学教学和研究经验的本科生或研究生。
这是一个庞大的教学团队,希望与同学们有良好的互动。
课程大纲与设计思路 📚


课程大纲主要分为四个部分:
- 几何与代数
- 数值方法
- 微分方程求解
- 优化与拓扑


本课程并非简单地复述数学知识,而是重点讲解这些数学工具在图形学中的具体应用。课程设计的初衷是让数学变得“看得见”,帮助大家在不同图形学问题中识别出共通的数学本质。
例如,许多看似不同的图形学问题(如几何处理、渲染、物理仿真),经过抽象后可能都归结为求解同一个微分方程(如拉普拉斯方程或泊松方程)。这种思维方式非常有趣。

图形学与人工智能:世界模拟器 🌍
计算机图形学的核心目标是构建一个高度逼真的数字世界,即“世界模拟器”。一个成功的世界模拟器能够生成物理真实、符合客观规律和人类行为逻辑的场景。

智能体(如AI)通过与这样的数字世界交互(感知、认知、决策、行动),可以高效地学*和积累经验,这比在现实世界中试错要安全、快速得多。因此,图形学构建的高保真仿真环境,成为了训练人工智能(如自动驾驶、机器人)的关键*台。
图形学通过三大核心模块来构建这个世界模拟器:
- 几何形状的构建:从现实世界数字化或算法生成复杂形体。
- 物理规律的体现:高精度模拟各种动态现象(刚体、流体、布料等)。
- 运动的控制:生成并控制符合物理规律和风格化要求的运动。
实现这些模块的算法,其底层依赖正是各种数学工具。这便引出了我们这门课的主题——图形学中的数学。
第一部分:向量与向量空间 ➕
上一节我们概述了图形学与数学的紧密联系,本节中我们来看看最基础的数学对象——向量。
向量的两种视角
大家可能接触过两种向量的定义:
- 中学视角:既有大小又有方向的量,运算满足*行四边形法则。
- 线性代数视角:向量空间中的元素,其运算满足一系列公理。
本课程采用第二种,更抽象但应用更广泛的公理化定义。
公理化定义
一个定义在数域 F(图形学中通常取实数域 R)上的向量空间,需要定义向量加法(+)和标量乘法(·),并满足以下8条公理:
- 加法结合律:
(u + v) + w = u + (v + w) - 加法交换律:
u + v = v + u - 加法单位元:存在
0,使得v + 0 = v - 加法逆元:对任意
v,存在-v,使得v + (-v) = 0 - 标量乘法结合律:
(ab) · v = a · (b · v) - 标量乘法单位元:
1 · v = v - 标量乘法对向量加法的分配律:
a · (u + v) = a·u + a·v - 标量乘法对标量加法的分配律:
(a + b) · v = a·v + b·v
这种定义的好处在于,它不再局限于二维或三维的几何向量,可以描述更高维甚至抽象的对象。
线性组合、相关性与维度
以下是关于向量组性质的核心概念:
- 线性组合:对于向量组
{v₁, v₂, ..., vₙ},形如a₁v₁ + a₂v₂ + ... + aₙvₙ的表达式称为这些向量的一个线性组合。 - 线性相关:如果存在一组不全为零的标量
{a₁, a₂, ..., aₙ},使得a₁v₁ + a₂v₂ + ... + aₙvₙ = 0,则称这组向量线性相关。这意味着其中至少有一个向量可以被其他向量线性表示。 - 线性无关:如果不存在这样一组不全为零的标量,则称这组向量线性无关。
- 基与维度:向量空间的一组基是线性无关且能张成整个空间的向量组。基中向量的个数称为向量空间的维度(
dim(V))。空间中的任何向量都可以唯一地表示为基向量的线性组合,组合系数称为该向量在此基下的坐标。
图形学中的向量空间

图形学中既处理低维向量,也处理极高维度的向量:
- 低维向量空间:如表示点位置的欧几里得空间(
ℝ³),表示颜色的RGB颜色空间等。 - 高维向量空间:如一张
1920×1080灰度图像的所有像素值可视为一个约200万维的向量;一个具有1.5万个顶点的三维模型,其所有顶点的位置坐标构成一个4.5万维的向量;流体模拟中求解的向量维度常达到百万甚至千万级。


第二部分:矩阵与线性变换 🔄

上一节我们介绍了向量空间的基础,本节中我们来看看描述向量空间之间映射的工具——矩阵。
线性映射

一个从向量空间 V 到 W 的映射 f: V -> W 如果满足以下两条性质,则称为线性映射:
- 保加法:
f(u + v) = f(u) + f(v) - 保标量乘法:
f(a · v) = a · f(v)
这意味着线性映射完全由它对一组基的作用所决定。在低维空间中,缩放和旋转是线性映射,而*移不是。

矩阵:线性映射的表示
矩阵是线性映射的一种具体表示方式。
- 矩阵与向量乘法
A · x:表示向量x经过线性映射A后,在新空间中的坐标。 - 矩阵乘法
A · B:表示先后进行B和A两个线性映射的复合。 - 单位矩阵
I:表示恒等映射,即什么都不做。 - 方阵:表示映射前后空间维度相同。

矩阵的单目运算及其意义
以下是矩阵常见单目运算及其几何/物理意义:

- 转置 (
Aᵀ):对应原线性映射在对偶空间中的“逆转置”映射。在实数域上,转置与求逆有密切关系。 - 行列式 (
det(A)):对应线性映射对空间的缩放因子(面积/体积的变化率)。det(A) = 0表示映射将空间压缩到了更低维度。 - 迹 (
tr(A)):矩阵主对角线元素之和,等于其所有特征值之和。 - 逆 (
A⁻¹):对应线性映射的逆变换。只有行列式不为零(即映射是可逆的)的方阵才有逆。

特征值与特征向量
对于一个方阵 A,如果存在标量 λ 和非零向量 v,满足 A · v = λ · v,则称 λ 为 A 的特征值,v 为对应的特征向量。
- 意义:特征向量是在该线性变换下方向保持不变的向量,特征值则是其长度的缩放倍数。
- 求解:通过解特征方程
det(λI - A) = 0得到特征值,再代入求解特征向量。 - 矩阵多项式:若
f(x)是一个多项式,则矩阵多项式f(A)的特征值为f(λ),其中λ是A的特征值。这在分析迭代算法的收敛性(谱半径)和矩阵的条件数时非常重要。

第三部分:内积、范数与度量 📏

上一节我们讨论了矩阵表示的线性变换,本节中我们为向量空间引入度量“大小”和“角度”的概念。
从度量空间到赋范向量空间


- 度量空间:定义了距离函数
d(x, y)的集合,满足非负性、同一性、对称性和三角不等式。 - 赋范向量空间:在向量空间上定义范数
||v||(可理解为长度),满足:- 正定性:
||v|| ≥ 0,且||v|| = 0 ⇔ v = 0 - 正齐次性:
||a·v|| = |a| · ||v|| - 三角不等式:
||u + v|| ≤ ||u|| + ||v||
范数可以诱导出一个度量:d(u, v) = ||u - v||。
- 正定性:

内积空间
内积空间是定义了内积运算 ⟨u, v⟩ 的向量空间,内积满足共轭对称性、对第一个变量的线性性和正定性。内积可以诱导出一个范数:||v|| = √⟨v, v⟩。
- 夹角与正交:两个向量的夹角余弦定义为
cos θ = ⟨u, v⟩ / (||u|| · ||v||)。若⟨u, v⟩ = 0,则两向量正交。 - 标准正交基:一组两两正交且范数均为1的基。在此基下,内积计算简化为对应坐标相乘之和:
⟨u, v⟩ = Σ u_i * v_i。这对应坐标形式的点积。 - 正交变换与正交矩阵:保持内积不变的线性变换。其矩阵表示
Q满足QᵀQ = I(即Qᵀ = Q⁻¹)。正交变换包括旋转和镜像。

酉空间(幺正空间)

酉空间是复数域上的内积空间,其内积由埃尔米特函数给出。酉变换是酉空间中的保内积变换,其矩阵 U 满足 UᴴU = I(Uᴴ 为共轭转置)。这是正交变换在复数域上的推广,在量子力学等领域有重要应用。
函数空间可以视为无穷维的向量空间,其上也可以定义内积(如两个函数乘积的积分),并拓展内积空间的所有概念。

第四部分:图形学中的线性代数应用实例 🎮

前面我们回顾了线性代数的基础理论,本节我们来看一些在图形学中的具体应用。
基本二维/三维线性变换

以下是一些基本变换的矩阵表示:
- 缩放:
S = diag(s_x, s_y, s_z) - 剪切:例如在三维中,
H = [[1, 0, sh_x], [0, 1, sh_y], [0, 0, 1]] - 旋转(绕Z轴):
R_z(θ) = [[cosθ, -sinθ, 0], [sinθ, cosθ, 0], [0, 0, 1]]
绕任意轴n的旋转可以通过相似变换实现:先旋转使n对齐Z轴,绕Z轴旋转,再旋转回去。

齐次坐标与仿射变换
为了解决*移不是线性变换的问题,我们引入齐次坐标。将 n 维点 (x, y, z) 用 n+1 维向量 (x, y, z, 1) 表示。
在齐次坐标下,仿射变换(线性变换+*移)可以用一个矩阵统一表示:
A = [[R, T],
[0, 1]]
其中 R 是 3x3 的线性变换(旋转、缩放等)矩阵,T 是 3x1 的*移向量。默认变换顺序为缩放、旋转、*移。

矩阵的变换:相似与合同

- 相似变换:
B = P⁻¹AP。A和B是同一个线性变换在不同基下的矩阵。相似变换可用于矩阵对角化,即在某组基下将变换表示为纯缩放。 - 合同变换:
B = CᵀAC。A和B是同一个二次型在不同基下的矩阵。二次型形如f(x) = xᵀAx,在图形学中可用于表示圆锥曲线、曲面等。
正定矩阵与实对称矩阵

- 正定矩阵:对于任意非零向量
x,都有xᵀAx > 0。正定矩阵的特征值全为正数。 - 实对称矩阵:满足
Aᵀ = A。实对称矩阵一定可以被正交矩阵对角化,且其特征值均为实数。在图形学的优化问题中,目标函数的Hessian矩阵(二阶导数矩阵)若是实对称正定矩阵,则保证了该点是局部极小值点。
总结与下节预告 🔮

本节课中我们一起学*了线性代数在图形学中的基础框架。我们从向量空间和线性映射的公理化定义出发,理解了矩阵作为线性变换表示的本质,探讨了特征值、内积、范数等概念及其几何意义,并初步了解了它们在图形学变换和表示中的应用。
下节课我们将继续深入,涵盖以下内容:
- 矩阵分解(如SVD)
- 矩阵的各种范数
- 矩阵的求导
- 张量的基本概念

这些内容是解决图形学中许多高级问题(如优化、物理模拟、数据处理)的关键数学工具。

GAMES001-图形学中的数学 - P10:场论初步 - GAMES-Webinar - BV1MF4m1V7e3
概述
在本节课中,我们将学*场论初步,包括标量场、向量场、梯度、散度、旋度等概念,并探讨它们在图形学中的应用。
标量场
定义:标量场是一个定义在空间中的标量函数,例如温度场、密度场等。
表示:用 ( F ) 表示,例如 ( F(x, y, z) )。
梯度:标量场的梯度是一个向量,表示函数增长最快的方向。
公式: ( \nabla F = \frac{\partial F}{\partial x} \mathbf{i} + \frac{\partial F}{\partial y} \mathbf{j} + \frac{\partial F}{\partial z} \mathbf{k} )
向量场
定义:向量场是一个定义在空间中的向量函数,例如速度场、力场等。
表示:用 ( \mathbf{F} ) 表示,例如 ( \mathbf{F}(x, y, z) = (F_x, F_y, F_z) )。
散度:向量场的散度是一个标量,表示向量场在空间中的发散程度。
公式: ( \nabla \cdot \mathbf{F} = \frac{\partial F_x}{\partial x} + \frac{\partial F_y}{\partial y} + \frac{\partial F_z}{\partial z} )
旋度:向量场的旋度是一个向量,表示向量场在空间中的旋转程度。
公式: ( \nabla \times \mathbf{F} = \left( \frac{\partial F_z}{\partial y} - \frac{\partial F_y}{\partial z} \right) \mathbf{i} + \left( \frac{\partial F_x}{\partial z} - \frac{\partial F_z}{\partial x} \right) \mathbf{j} + \left( \frac{\partial F_y}{\partial x} - \frac{\partial F_x}{\partial y} \right) \mathbf{k} )
场论的应用
高斯定理:高斯定理描述了向量场的散度与通量之间的关系。
公式: ( \iiint_V (\nabla \cdot \mathbf{F}) dV = \iint_S (\mathbf{F} \cdot \mathbf{n}) dS )
斯托克斯定理:斯托克斯定理描述了向量场的旋度与环量之间的关系。
公式: ( \iint_S (\nabla \times \mathbf{F}) \cdot \mathbf{n} dS = \oint_C (\mathbf{F} \cdot \mathbf{t}) ds )
总结


本节课介绍了场论初步的概念,包括标量场、向量场、梯度、散度、旋度等,并探讨了它们在图形学中的应用。这些概念对于理解图形学中的各种算法非常重要。

GAMES001-图形学中的数学 - P11:古典微分几何 📐
在本节课中,我们将要学*古典微分几何的核心概念。古典微分几何是研究三维空间中曲线和曲面的数学分支,它与图形学紧密相关,因为图形学处理的对象正是三维空间中的几何体及其可视化。我们将从参数曲线开始,逐步深入到曲面的基本理论,理解如何描述和分析它们的形状与性质。
为什么是古典微分几何?🤔
古典微分几何是一种“外蕴”方法。这意味着我们身处三维世界中,研究的是嵌入在这个三维空间中的二维曲面。而研究曲面的概念和方法,又是从研究三维空间中的一维曲线派生而来的。因此,古典微分几何本质上是三维空间中的曲线论和曲面论。
与之相对的现代微分几何是一种“内蕴”方法,它直接在任意维度的流形内部进行研究,而不依赖于将其嵌入更高维的空间。图形学的研究对象是三维欧几里得空间中的可视化几何体,这恰好对应于古典微分几何的范畴。因此,掌握古典微分几何的知识对于图形学的研究和编程至关重要。
曲线论 🧵
上一节我们介绍了古典微分几何的研究范畴。本节中,我们来看看如何用数学描述三维空间中的曲线。
参数曲线
在三维欧几里得空间中,曲线可能不在一个*面内。我们可以用一个从区间 [a, b] 到三维空间的连续映射来表示一条曲线,这被称为参数曲线。
参数曲线是一个向量函数,也称为参数方程。其定义是:给定一个参数 t,对于每个 t,给出曲线上对应点的坐标。
公式:
r(t) = (x(t), y(t), z(t))
如果 x(t), y(t), z(t) 连续可微,则称该曲线 C^1 连续可微。导数 r'(t) = (x'(t), y'(t), z'(t)) 称为曲线在 t 处的切向量。满足 r'(t) ≠ 0 的点称为曲线的正则点。
根据多元微积分的知识,过曲线上一点 r(t0) 的切线方程可以表示为:
公式:
L(u) = r(t0) + u * r'(t0)
一条参数曲线被称为正则的,当且仅当它处处是正则点,并且其参数方程是自变量 t 的三次以上连续可微函数(C^3)。高阶可微性是为了保证后续定义的曲率等概念有意义。
弧长参数
定义了曲线后,我们可以度量它的长度。曲线的弧长 s 定义为切向量模长的积分。
公式:
s(t) = ∫_{a}^{t} ||r'(τ)|| dτ
其中,ds = ||r'(t)|| dt 是弧长微元,它与坐标系的选取无关。
利用弧长,我们可以对曲线定义一个特殊的参数化:以弧长 s 自身作为参数。此时,新的参数方程记为 r(s)。可以证明,曲线参数是弧长参数的充要条件是切向量的模长恒为1,即 ||r'(s)|| ≡ 1。此时,切向量场是单位向量场。
曲率与Frenet标架
我们记弧长参数下的切向量为 α(s) = r'(s)。曲率 κ(s) 衡量了曲线弯曲的程度。
公式:
κ(s) = ||α'(s)|| = ||dα/ds||
α'(s) 称为曲率向量,其模长为曲率,其方向指向曲线弯曲的方向。可以证明,曲率向量 α'(s) 与单位切向量 α(s) 垂直,即 α'(s) · α(s) = 0。
当曲率 κ(s) ≠ 0 时,曲率向量的方向是唯一确定的。我们定义主法向量 β(s) 为曲率向量的单位化方向:
公式:
β(s) = α'(s) / ||α'(s)|| = α'(s) / κ(s)
由此可得关系:α'(s) = κ(s) β(s)。
现在,我们有了两个互相垂直的单位向量:切向量 α(s) 和主法向量 β(s)。通过叉乘,我们可以得到第三个单位向量,称为次法向量:
公式:
γ(s) = α(s) × β(s)
这样,在正则曲线上所有曲率不为零的点,我们都得到了一个由 {α(s), β(s), γ(s)} 构成的右手单位正交坐标系,称为 Frenet标架。这三个向量分别定义了曲线的切线、主法线和次法线方向。
挠率
次法向量 γ(s) 的变化率刻画了曲线偏离*面曲线的程度。我们定义挠率 τ(s) 如下:
公式:
τ(s) = -γ'(s) · β(s)
可以证明,γ'(s) = -τ(s) β(s)。挠率 τ(s) 衡量了曲线“扭转”的程度。*面曲线的挠率恒为零。
Frenet公式与曲线论基本定理
Frenet标架的三个单位向量关于弧长的导数满足一组优美的方程,称为Frenet公式。
公式:
α'(s) = κ(s) β(s)
β'(s) = -κ(s) α(s) + τ(s) γ(s)
γ'(s) = -τ(s) β(s)
这可以写成矩阵形式,描述了标架沿曲线运动的规律。
曲线论的一个基本定理指出:两条正则参数曲线,如果它们的曲率 κ(s) 和挠率 τ(s) 作为弧长的函数处处相等,那么这两条曲线在三维空间中只差一个刚体运动(旋转和*移)。也就是说,曲率和挠率唯一地确定了曲线的形状。
曲面论 🎨
上一节我们学*了如何描述和分析曲线。本节中,我们将概念拓展到更复杂的二维曲面。
参数曲面
类似于曲线,参数曲面是一个从二维区域 D 到三维空间的连续映射。
公式:
r(u, v) = (x(u,v), y(u,v), z(u,v)), (u, v) ∈ D
参数 (u, v) 称为曲纹坐标。固定 u 或 v 会得到曲面上的一族参数曲线,称为曲纹线。
我们要求映射是——对应的,并且偏导数 r_u = ∂r/∂u 和 r_v = ∂r/∂v 线性无关(即 r_u × r_v ≠ 0)。满足这些条件的参数曲面称为正则参数曲面。曲面在该点的法向量 N 由叉积给出:
公式:
N = (r_u × r_v) / ||r_u × r_v||
需要注意的是,有些曲面(如整个球面)无法用一个全局的正则参数方程表示,但它的每一点附*都可以用正则参数曲面来描述。我们研究的正则曲面也包括由这种局部参数化拼接而成的曲面。
第一基本形式
曲面的第一基本形式刻画了曲面上的度量,即如何计算曲面上曲线的长度和角度。
考虑曲面上一个线元 dr = r_u du + r_v dv。其长度的*方为:
公式:
ds^2 = dr · dr = E du^2 + 2F du dv + G dv^2
其中:
E = r_u · r_uF = r_u · r_vG = r_v · r_v
矩阵 [[E, F], [F, G]] 称为曲面的第一基本形式。它是一个正定矩阵,完全决定了曲面上的内积结构。
- 长度:曲面上一条曲线
(u(t), v(t))的长度为∫ sqrt(E u'^2 + 2F u'v' + G v'^2) dt。 - 面积:曲面上一个面元
dA的面积为sqrt(EG - F^2) du dv。 - 保角性:两个曲面之间的映射如果保持角度不变,则称为保角映射。其充要条件是它们的第一基本形式成比例。
在图形学的纹理映射中,寻找一个“均匀”的参数化(即保角映射)是一个重要课题。
第二基本形式与曲率
第一基本形式描述了曲面“内在”的度量。第二基本形式则描述了曲面在三维空间中“外在”的弯曲程度。
考虑曲面上一点 P 和邻*点 Q,将向量 PQ 投影到 P 点的法向量 N 上。这个投影值在二阶*似下由第二基本形式给出。
公式:
II = L du^2 + 2M du dv + N dv^2
其中:
L = r_uu · NM = r_uv · NN = r_vv · N(注意这里的N是标量函数,与法向量符号相同但含义不同)
第二基本形式也可以写成 II = -dr · dN,其中 dN 是法向量的微分。
法曲率与主曲率
在曲面上过一点 P 有无数条曲线。这些曲线在 P 点的曲率向量在曲面法向量 N 上的投影,称为曲线在该点的法曲率 κ_n。
一个重要结论(Meusnier定理):过 P 点具有相同切方向的所有曲线,在 P 点的法曲率都相同。因此,法曲率可以只由切方向 (du, dv) 决定:
公式:
κ_n = (L du^2 + 2M du dv + N dv^2) / (E du^2 + 2F du dv + G dv^2) = II / I
当切方向变化时,法曲率 κ_n 会变化。存在两个彼此正交的切方向,使得法曲率分别取到最大值 κ_1 和最小值 κ_2。这两个方向称为主方向,对应的曲率 κ_1 和 κ_2 称为主曲率。
*均曲率与高斯曲率
由两个主曲率,我们可以定义两个重要的曲率标量:
- *均曲率 H:主曲率的算术*均。
H = (κ_1 + κ_2) / 2。在物理中,表面张力与*均曲率有关。 - 高斯曲率 K:主曲率的乘积。
K = κ_1 * κ_2。高斯曲率具有极其重要的几何意义。
高斯绝妙定理指出:曲面的高斯曲率 K 完全由它的第一基本形式 E, F, G 决定,而与第二基本形式 L, M, N 无关。这意味着,一个曲面是“弯曲”的,这个性质可以通过测量曲面本身上的长度和角度(内蕴几何)来得知,而无需站在曲面之外观察。这一定理是现代微分几何(黎曼几何)的起点。
- *面:高斯曲率
K = 0,*均曲率H = 0。 - 球面(半径R):高斯曲率
K = 1/R^2,*均曲率H = 1/R。 - 可展曲面(如圆柱、圆锥):高斯曲率
K = 0。
几何连续性 🧩
在图形学和CAD中,我们经常需要将多段曲线或多块曲面拼接起来。连续性决定了拼接的光滑程度。
参数连续性 (C^n)
参数连续性直接检查参数方程本身的导数是否连续。
C^0: 曲线/曲面位置连续。C^1: 一阶导数(切向量/偏导)连续。C^2: 二阶导数(曲率向量/二阶偏导)连续。
参数连续性的问题在于它依赖于参数化的选择。同一个几何形状,不同的参数化可能导致不同的连续性判断。
几何连续性 (G^n)
几何连续性关注的是几何形状本身的光滑性,与参数化无关。
- G^0:与
C^0相同,位置连续。 - G^1:在连接处有公共的(且同向的)单位切向量(对曲线)或单位法向量(对曲面)。肉眼观察是光滑的切线过渡。
- G^2:在连接处不仅有
G^1连续性,还有公共的曲率向量(对曲线)或公共的法曲率(对曲面)。这要求曲率圆或弯曲程度*滑过渡。
几何连续性更符合我们对“光滑”的直观感受,因此在计算机图形学中更为常用。
总结 📚
本节课中,我们一起学*了古典微分几何的基础知识。
我们从参数曲线开始,引入了弧长参数、曲率和挠率的概念,并借助Frenet标架和Frenet公式描述了曲线的局部性质。曲线论基本定理告诉我们,曲率和挠率唯一确定了曲线的形状。
接着,我们将概念扩展到参数曲面。第一基本形式描述了曲面上的内在度量(长度、角度、面积),而第二基本形式描述了曲面在空间中的外在弯曲。通过分析法曲率,我们得到了主曲率、*均曲率和高斯曲率。高斯绝妙定理揭示了曲面的内蕴几何性质,即高斯曲率仅由第一基本形式决定,这是现代微分几何的基石。
最后,我们讨论了图形学中至关重要的几何连续性概念,它比参数连续性更能反映形状拼接的光滑直观感受。

掌握这些古典微分几何的概念,将为学*图形学中的几何建模、纹理映射、物理模拟等课题打下坚实的数学基础。

GAMES001-图形学中的数学 - P12:微分方程 🧮
在本节课中,我们将学*图形学中至关重要的数学工具——微分方程。我们将从最简单的常微分方程出发,逐步深入到偏微分方程,并介绍其求解方法,包括分析解法和数值解法。课程最后,我们会看到如何将偏微分方程离散化为线性系统,为后续的数值计算课程做好铺垫。
微分方程是描述未知函数与其导数之间关系的方程。求解微分方程,就是找到满足该关系的未知函数。根据未知函数是一元还是多元,微分方程可分为常微分方程(ODE)和偏微分方程(PDE)。
微分方程有多种求解方法,本节课将大体介绍以下四种:
- 通解法:针对特定形式的简单方程,直接给出包含任意常数的通解公式。
- 分离变量法:将方程整理为变量可分离的形式,然后两边积分求解。
- 格林函数法:利用已知点源(如点电荷)的解,通过叠加原理求解复杂源项下的方程。
- 数值法:通过离散化,将微分方程转化为代数方程组进行数值求解,这是计算机图形学的核心研究内容之一。
常微分方程(ODE)求解方法回顾
上一节我们概述了微分方程的基本概念和分类,本节中我们来看看具体的求解方法,首先从常微分方程开始。
分离变量法
分离变量法是求解一阶常微分方程的经典方法。其标准形式为:
g(y) dy = f(x) dx
以下是求解步骤:
- 将方程整理为标准形式,使等式一端只含变量
y和dy,另一端只含变量x和dx。 - 对等式两边同时积分:
∫ g(y) dy = ∫ f(x) dx + C。 - 计算积分,得到包含任意常数
C的通解。
例题:求方程 x(1+y²) dx - y(1+x²) dy = 0 的通解。
- 整理方程:
y/(1+y²) dy = x/(1+x²) dx。 - 两边积分:
(1/2) ln(1+y²) = (1/2) ln(1+x²) + (1/2) ln C。 - 化简得通解:
1+y² = C(1+x²)或y = ±√[C(1+x²) - 1]。
一阶线性微分方程
一阶线性微分方程的标准形式为:
y' + P(x)y = Q(x)
若 Q(x) ≡ 0,称为齐次方程;否则称为非齐次方程。
齐次方程解法:
方程 y' + P(x)y = 0 可通过分离变量求解。
- 分离变量:
dy/y = -P(x) dx。 - 积分:
ln|y| = -∫ P(x) dx + C₁。 - 解得通解:
y = C e^{-∫ P(x) dx},其中C = ±e^{C₁}。
非齐次方程解法(常数变易法):
方程 y' + P(x)y = Q(x) 的通解为:
y = e^{-∫ P(x) dx} [ ∫ Q(x) e^{∫ P(x) dx} dx + C ]
该解可理解为:非齐次方程的通解 = 对应齐次方程的通解 + 非齐次方程的一个特解。
伯努利方程
伯努利方程是一类非线性方程,形式为:
y' + P(x)y = Q(x) yⁿ,其中 n ≠ 0, 1。
求解时,可通过变量代换 z = y^{1-n},将其化为一阶线性微分方程求解。
可降阶的高阶微分方程
对于某些高阶方程,可以通过代换降低阶数。
- 若方程为
y⁽ⁿ⁾ = f(x),直接积分n次即可。 - 若方程为
y'' = f(x, y'),可令p = y',则y'' = p',原方程化为p' = f(x, p),这是一个关于p的一阶方程。 - 若方程为
y'' = f(y, y'),同样令p = y',利用链式法则y'' = p * dp/dy,原方程化为p * dp/dy = f(y, p),这是一个关于p和y的一阶方程。
二阶线性常系数微分方程
二阶线性常系数齐次方程形式为:
y'' + p y' + q y = 0,其中 p, q 为常数。
其解法是寻找形如 y = e^{rx} 的解(特解)。代入方程得到特征方程:
r² + p r + q = 0
根据特征根的情况,通解形式如下:
以下是特征根不同情况下的通解形式:
- 两个不等实根
r₁, r₂:y = C₁ e^{r₁x} + C₂ e^{r₂x} - 两个相等实根
r:y = (C₁ + C₂ x) e^{rx} - 一对共轭复根
α ± iβ:y = e^{αx} (C₁ cos βx + C₂ sin βx)
对于非齐次方程 y'' + p y' + q y = f(x),其通解为对应齐次方程的通解加上该非齐次方程的一个特解。特解形式通常根据 f(x) 的形式(如多项式、指数函数、三角函数及其组合)用待定系数法猜测。
偏微分方程(PDE)简介
前面我们回顾了常微分方程的经典解法,本节我们将进入更复杂的领域——偏微分方程,它在物理模拟和渲染中应用广泛。
分类与典型方程
对于二元二阶线性偏微分方程:
A ∂²u/∂x² + 2B ∂²u/∂x∂y + C ∂²u/∂y² + D ∂u/∂x + E ∂u/∂y + F u = G
可根据判别式 Δ = B² - AC 对其分类:
以下是三种基本类型的偏微分方程及其典型物理实例:
- 双曲型 (
Δ > 0):描述波动现象,如波动方程∂²u/∂t² - a² ∂²u/∂x² = 0。 - 抛物型 (
Δ = 0):描述扩散或热传导现象,如热传导方程∂u/∂t - a² ∂²u/∂x² = 0。 - 椭圆型 (
Δ < 0):描述稳定状态或势场分布,如拉普拉斯方程∂²u/∂x² + ∂²u/∂y² = 0和泊松方程∇²u = f。
分离变量法求解PDE
分离变量法是求解PDE的重要分析手段。其核心思想是假设多元函数解可写成多个单变量函数的乘积。
以波动方程为例:
求解 ∂²u/∂t² - a² ∂²u/∂x² = 0,边界条件 u(0,t)=u(L,t)=0,初始条件 u(x,0)=φ(x), ∂u/∂t|_{t=0}=ψ(x)。
- 假设变量分离:设
u(x,t) = X(x)T(t)。 - 代入方程并分离变量:得到
T''/(a²T) = X''/X。由于左边只含t,右边只含x,两者相等必等于常数-λ。 - 转化为两个ODE:
- 时间部分:
T'' + λa² T = 0 - 空间部分:
X'' + λ X = 0
- 时间部分:
- 结合边界条件求解:边界条件
X(0)=X(L)=0决定了λ只能取特定值λ_n = (nπ/L)²,对应的空间解为X_n(x) = sin(nπx/L)。时间解为T_n(t) = A_n cos(nπa t/L) + B_n sin(nπa t/L)。 - 叠加得到一般解:
u(x,t) = Σ_{n=1}^{∞} [A_n cos(nπa t/L) + B_n sin(nπa t/L)] sin(nπx/L)。 - 利用初始条件定系数:将初始条件代入,利用傅里叶级数展开确定系数
A_n和B_n。
球谐函数
球谐函数是拉普拉斯方程在球坐标系下经分离变量法得到的一组特殊解,它是图形学中表示球面函数(如环境光照)的重要基底。
在球坐标系 (r, θ, φ) 下,拉普拉斯方程 ∇²u = 0 的解可分离为 u(r,θ,φ) = R(r) Y(θ,φ)。进一步分离 Y(θ,φ) = Θ(θ)Φ(φ),求解过程会引出连带勒让德方程。其归一化解称为球谐函数 Y_l^m(θ,φ),其中 l=0,1,2,... 为阶数,m=-l,...,l。
实数形式球谐函数(常用于图形学)可表示为:
y_l^m(θ,φ) = N_l^{|m|} P_l^{|m|}(cosθ) * { cos(mφ), m>0; 1, m=0; sin(|m|φ), m<0 }
其中 P_l^m 是连带勒让德多项式,N_l^m 是归一化常数。
当 m=0 时,连带勒让德方程退化为勒让德方程,其解为勒让德多项式 P_l(x),具有正交性:∫_{-1}^{1} P_n(x) P_m(x) dx = 0 (n≠m)。
在图形学中,球谐函数用于低频环境光的紧凑表示。任何球面函数 f(θ,φ) 都可以用球谐函数展开:f(θ,φ) ≈ Σ_{l=0}^{L} Σ_{m=-l}^{l} c_l^m Y_l^m(θ,φ)。只需存储前几阶的系数 c_l^m,就能较好地重建原始函数,极大节省存储和计算开销。
格林函数法
上一节我们利用分离变量法求解了特定边界下的PDE,本节介绍另一种强大的工具——格林函数法,它特别适合处理点源或特定边界条件的问题。
点源与狄拉克δ函数
考虑静电学问题:已知电荷密度分布 ρ(r),求电势 φ(r)。根据高斯定理的微分形式,有泊松方程:
∇²φ = -ρ / ε₀
对于位于 r₀ 的点电荷 Q,电荷密度在数学上无法用普通函数描述,为此引入狄拉克δ函数 δ(r - r₀)。其定义为:
δ(r - r₀) = 0,当r ≠ r₀。∫_{全空间} δ(r - r₀) dV = 1,若积分区域包含r₀。
点电荷密度可写为ρ(r) = Q δ(r - r₀)。
泊松方程的基本解与格林函数
点电荷产生的电势是已知的:φ(r) = Q / (4πε₀ |r - r₀|)。这启发我们定义泊松方程 ∇² G(r, r₀) = δ(r - r₀) 的解 G(r, r₀) 为基本解(或自由空间格林函数)。对于三维无穷远边界(φ(∞)=0),有:
G(r, r₀) = -1 / (4π |r - r₀|)
因此,点电荷电势解为 φ(r) = (Q/ε₀) * G(r, r₀)。
利用叠加原理,对于任意电荷分布 ρ(r),在无穷远边界下的电势解可通过积分得到:
φ(r) = (1/ε₀) ∫ ρ(r₀) G(r, r₀) dV₀
镜像法
对于非无穷远边界,如无限大接地导体*面附*点电荷的问题,可以利用镜像法构造格林函数。例如,在*面 x=0 电势为零的边界条件下,位于 (x₀, y₀, z₀)(x₀>0)的点电荷 Q 的格林函数解为:
G(r, r₀) = -1/(4π) [1/|r - r₀| - 1/|r - r₀'|]
其中 r₀' = (-x₀, y₀, z₀) 是镜像点。这相当于原电荷与一个关于*面对称的异号镜像电荷共同产生的电势。
格林恒等式与边界元法
格林第三恒等式将区域内部某点的函数值用其边界上的函数值及法向导数表示出来。设 G(r, r₀) 是拉普拉斯方程的基本解,ψ(r) 是区域内满足拉普拉斯方程的函数,则有:
ψ(r₀) = ∮_{∂V} [ G(r, r₀) ∂ψ/∂n - ψ(r) ∂G/∂n ] dS
这个公式构成了边界元法的数学基础,只需在边界上离散化即可求解区域内的场,降低了计算维度。
微分方程的数值解法
前面介绍的方法主要适用于具有规则边界和简单源项的分析解。对于复杂的图形学问题,我们通常需要借助数值方法。
常微分方程的数值解(欧拉法)
对于初值问题 y' = f(x, y), y(x₀)=y₀,最简单的数值方法是显式欧拉法:
y_{i+1} = y_i + h * f(x_i, y_i)
其中 h 为步长。该方法为一阶精度。更高效、高精度的方法有龙格-库塔法等。
偏微分方程的数值解(以拉普拉斯方程为例)
考虑二维拉普拉斯方程 ∂²u/∂x² + ∂²u/∂y² = 0。我们在均匀网格上离散,网格间距为 Δx = Δy = h。用中心差分*似二阶导数:
∂²u/∂x² ≈ (u_{i+1,j} - 2u_{i,j} + u_{i-1,j}) / h²
∂²u/∂y² ≈ (u_{i,j+1} - 2u_{i,j} + u_{i,j-1}) / h²
代入方程 ∇²u=0,得到离散后的方程:
(u_{i+1,j} + u_{i-1,j} + u_{i,j+1} + u_{i,j-1} - 4u_{i,j}) / h² = 0
即:
u_{i,j} = (u_{i+1,j} + u_{i-1,j} + u_{i,j+1} + u_{i,j-1}) / 4
这表明每个内部点的值是其四个邻点值的*均。
我们可以用迭代法求解这个大型线性系统:
- 雅可比迭代:用上一轮所有邻点的值更新当前点。
u_{i,j}^{(k+1)} = (u_{i+1,j}^{(k)} + u_{i-1,j}^{(k)} + u_{i,j+1}^{(k)} + u_{i,j-1}^{(k)}) / 4 - 高斯-赛德尔迭代:使用已更新的新值进行迭代,收敛更快。
u_{i,j}^{(k+1)} = (u_{i+1,j}^{(k)} + u_{i-1,j}^{(k+1)} + u_{i,j+1}^{(k)} + u_{i,j-1}^{(k+1)}) / 4
迭代直到相邻两次迭代的误差小于给定容差为止。
总结与展望
本节课中我们一起学*了图形学中微分方程的核心知识。我们从常微分方程的分离变量法、常数变易法讲起,回顾了二阶线性常系数方程的解法。随后,我们进入了偏微分方程领域,学*了根据判别式对二阶线性PDE的分类,并深入探讨了分离变量法在求解波动方程和引出球谐函数中的应用。接着,我们介绍了处理点源和边界问题的强大工具——格林函数法,包括基本解、镜像法和格林恒等式。最后,我们探讨了微分方程的数值解法,包括欧拉法,以及如何将拉普拉斯方程离散化为线性系统并用迭代法求解。


本节课起到了承上启下的作用:我们通过离散化,巧妙地将一个连续的偏微分方程(如拉普拉斯方程)转化为了一个关于离散网格点值的线性方程组 A x = b。如何高效、稳定地求解这类大规模线性系统,正是我们下一节课——线性系统求解所要讨论的核心内容。数值求解线性系统是连接数学理论与图形学实践的关键桥梁。

GAMES001-图形学中的数学 - P13:线性系统 - GAMES-Webinar - BV1MF4m1V7e3
概述
在本节课中,我们将学*线性系统求解的相关知识,包括直接求解方法和迭代求解方法。
直接求解方法
高斯消元法
高斯消元法是一种直接求解线性方程组的方法。其基本思想是通过行变换将矩阵转化为上三角矩阵,然后逐行求解。
公式:
A = LU
其中,L为下三角矩阵,U为上三角矩阵。
LU分解
LU分解是将矩阵A分解为下三角矩阵L和上三角矩阵U的乘积。
代码:
def lu_decomposition(A):
# 实现LU分解
pass
LLT分解
LLT分解是将对称半正定矩阵A分解为下三角矩阵L和上三角矩阵L的转置的乘积。
代码:
def llt_decomposition(A):
# 实现LLT分解
pass
迭代求解方法
不动点迭代
不动点迭代是一种迭代求解线性方程组的方法。其基本思想是通过迭代更新解向量,直到收敛到真实解。
公式:
X_{k+1} = M^{-1}(B - AX_k)
其中,M为矩阵A的*似矩阵。
雅可比迭代
雅可比迭代是一种不动点迭代方法,其*似矩阵M为矩阵A的对角矩阵。
代码:
def jacobi_iteration(A, B, tolerance=1e-6, max_iterations=1000):
# 实现雅可比迭代
pass
高斯-赛德尔迭代
高斯-赛德尔迭代是一种不动点迭代方法,其*似矩阵M为矩阵A的对角矩阵加上下三角矩阵。
代码:
def gauss_seidel_iteration(A, B, tolerance=1e-6, max_iterations=1000):
# 实现高斯-赛德尔迭代
pass
子空间方法
共轭梯度法
共轭梯度法是一种子空间迭代方法,其基本思想是在子空间中寻找最小化残差的解。
代码:
def conjugate_gradient_method(A, B, tolerance=1e-6, max_iterations=1000):
# 实现共轭梯度法
pass
总结
本节课介绍了线性系统求解的几种方法,包括直接求解方法和迭代求解方法。在实际应用中,需要根据矩阵的特性选择合适的求解方法。
本节课我们学*了以下内容:
- 线性系统求解的几种方法
- 高斯消元法、LU分解和LLT分解
- 不动点迭代方法
- 子空间方法
- 共轭梯度法

希望这节课的内容能够帮助大家更好地理解线性系统求解。

GAMES001-图形学中的数学 - P14:生成模型的案例分析 - GAMES-Webinar - BV1MF4m1V7e3
大家好,今天我们将探讨图形学中深度学*的一些数学知识。在之前的课程中,我们介绍了概率论、线性代数等基础知识。随着图形学和深度学*的发展,许多图形学技术都与深度学*相关。深度学*领域也在不断发展,因此,本节课我们将关注如何利用数学为深度学*提供更好的建模。
深度学*与数学
要了解深度学*中可能用到的数学知识,可以参考一些在线教程,它们涵盖了微积分、线性代数、概率论等方面的知识。然而,本节课的重点不在于全面介绍深度学*中的数学知识,而是通过案例分析来展示概率论等基础知识如何与深度学*中的前沿技术建立联系。
深度学*的基本理解
我们可以通过拟合的概念来理解深度学*。例如,在差值拟合中,我们假设Y和X之间满足某种分布,并通过优化直线与实际数据之间的误差来得到最佳参数。深度学*可以看作是一个充满待定参数的函数,通过定义损失函数并优化参数来学*。
生成模型
生成模型是深度学*中的一个重要领域。以下是一些常见的生成模型:
- 生成对抗网络 (GAN): 通过对抗训练生成逼真的图像、视频、音频等。
- 变分自编码器 (VAE): 通过编码器和解码器学*数据分布,并生成新的数据。
- 扩散模型: 通过逐步添加噪声和逐步去除噪声来生成数据。
KL 散度
KL 散度是衡量两个概率分布之间差异的一种方法。如果两个分布相等,则 KL 散度为零;如果两个分布不相等,则 KL 散度大于零。
变分自编码器 (VAE)
VAE 通过编码器和解码器学*数据分布,并生成新的数据。编码器将数据映射到隐空间,解码器将隐空间的数据映射回数据空间。
扩散模型
扩散模型通过逐步添加噪声和逐步去除噪声来生成数据。它将数据从纯净状态逐步转换为噪声状态,然后再逐步去除噪声,最终恢复原始数据。
总结

本节课介绍了深度学*中的数学知识,并通过案例分析展示了概率论等基础知识如何与深度学*中的前沿技术建立联系。学好数学对于理解深度学*至关重要。








GAMES001-图形学中的数学 - P15:优化基础 🧮
在本节课中,我们将要学*优化问题的基础知识。优化是图形学中一个非常重要的工具,涵盖内容广泛且深入。由于时间有限,我们将通过一些简单的例子,引出优化中的核心概念和常用算法,为大家提供一个入门级的介绍。

无约束优化问题

一个通用的优化问题可以写成以下形式:
minimize f(x)
subject to c(x) = 0
其中,x 是待优化的变量,f(x) 是优化目标,c(x) 是约束条件。本节我们先来看最简单的情况:无约束优化问题,即没有 c(x) 约束。
在图形学中,无约束优化问题非常常见。例如,最小二乘法拟合曲线、曲面参数化、物理模拟中的能量最小化等,最终都归结为最小化一个函数 f(x)。
线搜索方法
对于无约束优化问题,最直观的求解思路是迭代地寻找下降方向。这类方法统称为线搜索方法,其通用形式为:
x_{k+1} = x_k + α_k * p_k
其中,p_k 是第 k 步的搜索方向,α_k 是步长。算法的核心在于每一步如何选择 p_k 和 α_k。
梯度下降法
最经典的线搜索方法是梯度下降法。它选择当前点的负梯度方向作为搜索方向,因为这是函数值下降最快的方向。
p_k = -∇f(x_k)
确定了方向后,我们需要确定步长 α_k。一个理想策略是沿着该方向进行一维搜索,找到使函数值最小的 α_k。但这本身也是一个优化问题,计算成本较高。
实践中,我们常用 Armijo条件 来*似地选择一个可接受的步长。该条件要求新的函数值必须小于等于一个由当前点函数值和梯度构造的线性估计值:
f(x_k + α_k * p_k) ≤ f(x_k) + c_1 * α_k * p_k^T * ∇f(x_k)
其中,c_1 是一个小常数(如 0.0001)。算法可以从一个较大的 α_k(如 1)开始,不断乘以一个衰减因子(如 0.5),直到满足 Armijo 条件为止。这样可以保证函数值稳定下降并最终收敛。
梯度下降法只利用了函数的一阶(梯度)信息,实现简单,但收敛速度可能较慢,尤其是在目标函数呈“狭长山谷”状时,容易产生震荡。
牛顿法


为了获得更快的收敛速度,我们可以利用函数的二阶信息(曲率),这就是牛顿法。牛顿法在当前位置 x_k 对函数做二阶泰勒展开,并最小化这个*似二次函数:
f(x_k + p) ≈ f(x_k) + p^T * ∇f(x_k) + 0.5 * p^T * ∇²f(x_k) * p
令其关于 p 的导数为零,可解得最优的搜索方向为:
p_k = -[∇²f(x_k)]^{-1} * ∇f(x_k)
这里,∇²f(x_k) 是函数在 x_k 处的海森矩阵(Hessian Matrix)。牛顿法的方向不仅考虑了梯度,还通过海森矩阵的逆进行了“缩放”,能更准确地指向极小值点,因此通常比梯度下降法收敛更快、步数更少。
然而,牛顿法存在两个问题:
- 计算开销大:每一步都需要计算并求解一个涉及海森矩阵的线性方程组。
- 收敛性保证:只有当海森矩阵正定时,
p_k才是下降方向。否则,函数值可能上升。实践中需要对非正定的海森矩阵进行修正(如投影牛顿法)或退回梯度下降。
拟牛顿法
拟牛顿法旨在结合梯度下降法和牛顿法的优点。其核心思想是:不直接计算复杂的海森矩阵,而是用一个更简单的矩阵 B_k 来*似它,并不断更新这个*似。
p_k = -B_k^{-1} * ∇f(x_k)
B_k 的更新需要满足所谓的“拟牛顿条件”:
B_{k+1} * (x_{k+1} - x_k) ≈ ∇f(x_{k+1}) - ∇f(x_k)
这个条件源于海森矩阵的定义(梯度变化与自变量变化的关系)。
BFGS算法 是最著名的拟牛顿法之一。它通过一个秩为2的矩阵更新公式来迭代 B_k,并能保证在迭代过程中 B_k 保持正定,从而算法稳定。BFGS 通常比牛顿法计算更快,且收敛速度接*牛顿法。
L-BFGS算法 是 BFGS 的“内存受限”版本。它不存储完整的 N×N 矩阵 B_k,而是只保留最* m 步的更新向量,用这些向量隐含地表示 B_k。这大大降低了存储和计算开销(从 O(N²) 降至 O(mN)),特别适用于大规模优化问题,是许多工业软件的默认优化器。
上一节我们介绍了无约束优化的几种核心算法。接下来,我们将目光转向更一般的带约束优化问题。
带约束优化问题
在图形学中,约束无处不在,例如物体不能穿透、关节角度有限制、路径规划等。本节我们主要讨论带等式约束的优化问题,其形式为:
minimize f(x)
subject to g(x) = 0
最优性条件与拉格朗日函数
对于带等式约束的问题,最优解 x* 需要满足两个条件:
- 可行性:
g(x*) = 0 - 梯度条件:在最优解处,目标函数梯度
∇f(x*)必须与约束函数梯度∇g(x*)*行。这意味着∇f(x*)垂直于约束曲面,无法再沿约束曲面移动使f(x)下降。数学上可写为存在拉格朗日乘子λ,使得:∇f(x*) = λ * ∇g(x*)
为了统一这两个条件,我们引入 拉格朗日函数:
L(x, λ) = f(x) - λ^T * g(x)
可以证明,原优化问题的最优解 (x*, λ*) 正是拉格朗日函数的驻点,即满足:
∇_x L(x*, λ*) = 0
∇_λ L(x*, λ*) = 0
这等价于上述的最优性条件。因此,求解带约束优化问题转化为求解拉格朗日函数的驻点。
求解方法:牛顿法与对偶上升法
牛顿法求解 KKT 系统
我们可以直接使用牛顿法来求解拉格朗日函数梯度为零的方程组(即 KKT 条件)。在每一步迭代中,需要求解一个形如下式的线性系统:
[ ∇²_xx L ∇g ] [ Δx ] = - [ ∇_x L ]
[ ∇g^T 0 ] [ Δλ ] - [ g ]
这个系统的系数矩阵是对称但不定的,需要使用专门的线性求解器(如 LU 分解)。牛顿法收敛快,但每一步计算开销较大。
对偶上升法
另一种思路是利用 对偶问题。我们定义对偶函数 g(λ) = min_x L(x, λ)。可以证明,在一定的凸性条件下(强对偶成立),原问题等价于最大化对偶函数:
maximize g(λ)
这是一个无约束优化问题!对偶上升法 就是交替优化原变量 x 和对偶变量 λ:
- 固定 λ,优化 x:
x_{k+1} = argmin_x L(x, λ_k)。这相当于计算g(λ_k)及其对应的x。 - 固定 x,优化 λ:使用梯度上升法更新
λ。可以推导出,对偶函数g(λ)在x_{k+1}处的梯度为-g(x_{k+1})。因此更新公式为:λ_{k+1} = λ_k + α_k * g(x_{k+1})
对偶上升法将原约束问题分解为两个相对简单的子问题,实现容易,尤其适合约束可分离的情况。
罚函数法
这是一种非常直观的*似方法:将约束作为惩罚项加入目标函数。
minimize f(x) + (μ/2) * ||g(x)||²
其中 μ 是一个很大的正数。当 μ 很大时,解会迫使 g(x) 接*零,从而*似满足约束。但 μ 的选择需要权衡:太大导致问题病态难收敛,太小则约束不紧。罚函数法简单,但通常只能得到*似解。
关于不等式约束
对于不等式约束 h(x) ≤ 0,其基本思想是:如果最优解在约束内部(h(x) < 0),则该约束不起作用(可忽略);如果最优解在边界上(h(x) = 0),则将其视为等式约束处理。难点在于事先不知道哪些约束是“活跃”的。这引入了 KKT 条件 和更复杂的算法(如内点法、有效集法),此处不再展开。
总结 🎯
本节课我们一起学*了优化问题的基础知识:
- 无约束优化:我们介绍了梯度下降法(利用一阶信息)、牛顿法(利用二阶信息,收敛快但计算贵)以及拟牛顿法(特别是 BFGS/L-BFGS,用*似矩阵*衡速度与开销,是大规模问题的常用选择)。
- 带等式约束优化:我们引入了 拉格朗日函数 和 对偶问题 的核心概念。求解方法包括:
- 直接用牛顿法求解 KKT 系统。
- 对偶上升法,通过交替优化原变量和对偶变量,将问题转化为无约束优化。
- 罚函数法,一种简单直观的*似方法。


优化是一个博大精深的领域,本节课仅是一个入门介绍。大家在实际应用中遇到更复杂的问题(如非凸优化、不可导问题、大规模不等式约束)时,可以在此基础上进一步深入学*。

GAMES001-图形学中的数学 - P16:拓扑 - GAMES-Webinar - BV1MF4m1V7e3
概述
在本节课中,我们将学*拓扑学的基本概念,包括同胚、亏格、欧拉示性数、回转数、卷绕数、若尔当曲线定理、布劳威尔不动点定理、毛球定理和庞加莱猜想。
什么是拓扑?
拓扑学是研究图形和图形之间位置关系的几何学。它主要关注图形的相对位置,而不考虑图形的大小或形状。
同胚
两个图形如果可以通过弯曲、延展、剪切等操作相互转换,则称它们是同胚的。
公式: ( f: X \rightarrow Y ) 是同胚当且仅当 ( f ) 是双射,连续,并且 ( f^{-1} ) 也是连续的。
亏格
亏格是曲面中最多可以画出N条闭合曲线,同时不将曲面分开的N值。
公式: ( g = N )
欧拉示性数
欧拉示性数是一个不碎、不随同胚改变的拓扑不变量。
公式: ( K = V - E + F )
回转数
回转数是曲线绕过某点的总次数的整数。
公式: ( \text{扭转数} = \frac{\text{曲线的总曲率}}{2\pi} )
卷绕数
卷绕数是曲线绕过某点的总次数的整数。
公式: ( \text{卷绕数} = \frac{\text{曲线的总曲率}}{2\pi} )
若尔当曲线定理
若尔当曲线定理是计算几何中用光线投射算法判断点与多边形关系的策略。
布劳威尔不动点定理
布劳威尔不动点定理是说一个从欧几里得空间的某个给定的凸集的子集设到它自身的连续函数,有至少一个不动点。
毛球定理
毛球定理是说球面上的连续的切向量必然存在零点。
庞加莱猜想
庞加莱猜想是说任何一个单连通的闭的三维流形,必然同胚于一个三维的球面。


总结
本节课我们学*了拓扑学的基本概念,包括同胚、亏格、欧拉示性数、回转数、卷绕数、若尔当曲线定理、布劳威尔不动点定理、毛球定理和庞加莱猜想。这些概念在图形学中有着广泛的应用。

📘 GAMES001-图形学中的数学 - P2:线性代数基础(二)
在本节课中,我们将继续学*图形学中至关重要的线性代数知识。我们将重点探讨矩阵分解、矩阵范数、矩阵求导以及张量的初步概念。这些内容是理解图形学算法和进行相关科研工作的数学基石。
🔍 课前说明与课程定位
首先,纠正上节课的一个口误:正交变换中的“正”指的是正规或正交,不宜理解为正定。
本课程的目标并非详尽讲解每一门数学课,而是提纲挈领地介绍图形学涉及的核心数学概念。当大家在后续学*或工作中需要深入时,可以此课程为索引,去查阅更系统的资料。课程内容可能较快,但旨在建立整体认识。
GAMES课程是共创性质的,欢迎大家提出改进建议。关于GAMES002课程(图形学工具与环境配置),它旨在帮助大家快速上手图形学开发中繁杂的环境配置问题,推荐的工具和代码均以跨*台为宗旨。
🧩 矩阵分解
上一节我们介绍了矩阵的基本运算,本节中我们来看看图形学中常用的几种矩阵分解方式。
PLU分解
PLU分解是指将一个矩阵分解为三个矩阵的乘积:P(置换矩阵)、L(下三角矩阵)和U(上三角矩阵)。其形式为:
[
A = P L U
]
如果不进行行交换操作,则称为LU分解((A = L U))。该分解本质上等价于高斯消元法。
以下是PLU分解的一个计算示例:
给定矩阵 (A = \begin{bmatrix} 9 & 6 & 0 \ 6 & 5 & 4 \ 3 & 4 & 10 \end{bmatrix}),通过一系列行变换(相当于左乘一系列下三角矩阵 (E_i)),最终可以得到 (U = E_n ... E_1 A),从而 (A = (E_n ... E_1)^{-1} U = L U)。若涉及行交换,则引入置换矩阵 (P)。
乔里斯基分解
乔里斯基分解要求矩阵是实对称正定矩阵,并将其分解为一个下三角矩阵与其转置的乘积:
[
A = L L^T
]
其中 (L) 是下三角矩阵。与LU分解形式类似,但上下三角矩阵互为转置。其求解复杂度((O(n2)))低于LU分解((O(n3))),常用于求解稠密线性系统。
应用示例:求解方程 (A x = b)。将 (A) 替换为 (L L^T),则先解 (L y = b),再解 (L^T x = y)。
QR分解
QR分解对矩阵的要求较低(列满秩即可),将一个 (m \times n) 矩阵分解为一个正交矩阵 Q 和一个上三角矩阵 R 的乘积:
[
A = Q R
]
当 (m > n) 时,(R) 的下半部分为零矩阵。计算方法包括施密特正交化、吉文斯变换或豪斯霍尔德变换。
在图形学中的应用:在软体仿真中,形变梯度 (F) 常被分解为旋转部分 (R)(对应 (Q))和形变部分 (S)(对应 (R)),即 (F = R S)。
以下是使用施密特正交化进行QR分解的简要步骤:
给定矩阵 (A = \begin{bmatrix} 1 & 2 & 2 \ 2 & 1 & 2 \ 1 & 2 & 1 \end{bmatrix})。
- 对列向量进行施密特正交化,得到一组正交基。
- 将该组正交基单位化,得到正交矩阵 (Q)。
- (R) 矩阵由正交化过程中的系数构成,(R = Q^T A)。
奇异值分解
奇异值分解是极其重要的分解,适用于任意矩阵,将其分解为三个矩阵的乘积:
[
A = U \Sigma V^T
]
其中 (U) 和 (V) 是正交矩阵(旋转),(\Sigma) 是对角矩阵(缩放),其对角线元素称为奇异值,均为非负实数。
几何意义:SVD将任意线性变换分解为“旋转-缩放-旋转”三个步骤。
奇异值与特征值的关系:对于实对称正定矩阵 (A),其特征值与奇异值重合。更一般地,矩阵 (A) 的奇异值是 (A^T A) 特征值的*方根。
📏 矩阵范数
为了度量矩阵的“大小”,我们需要定义矩阵范数。首先从向量范数出发。
向量范数
向量范数是对向量长度的度量,需满足正定性、齐次性和三角不等式。常见的向量范数有:
- L0范数:向量中非零元素的个数。注意,它不满足范数的齐次性,严格来说不是范数。
- L1范数:(|x|_1 = \sum_i |x_i|)
- L2范数(欧几里得范数):(|x|_2 = \sqrt{\sum_i x_i^2})
- L∞范数:(|x|_\infty = \max_i |x_i|)
矩阵范数
矩阵范数是矩阵“大小”的度量。一种重要的定义方式是诱导范数(由向量范数诱导而来):
[
|A|p = \max \frac{|A x|_p}{|x|_p}
]
其几何意义是矩阵 (A) 作为线性变换时,对向量模长的最大放大倍数。
常见的诱导矩阵范数:
- 诱导L1范数:列和范数,(|A|1 = \max_j \sum_i |a|)
- 诱导L2范数:谱范数,等于 (A) 的最大奇异值,(|A|2 = \sigma(A))
- 诱导L∞范数:行和范数,(|A|\infty = \max_i \sum_j |a|)
此外,还有基于矩阵元素的元素形式范数(如Frobenius范数 (|A|F = \sqrt{\sum a_{ij}^2}))和基于奇异值的Schatten范数。
🧮 矩阵求导
矩阵求导在图形学中应用广泛,例如在伴随方法中求导,或在软体仿真中由弹性势能求应力张量。
布局约定
矩阵求导有两种常见布局:分子布局和分母布局,其结果矩阵的形状不同。核心规则是:谁转置了,就不是谁的布局。必须始终保持使用同一种布局,以保证矩阵乘法的相容性。
标量函数对向量/矩阵的求导
对于一个实值标量函数 (f(x)),其中 (x) 是向量或矩阵,其导数(梯度)是一个与 (x) 同型的向量或矩阵,每个元素是 (f) 对 (x) 中对应元素的偏导。
常用公式(向量情形):
- (\frac{\partial (a^T x)}{\partial x} = a),(\frac{\partial (x^T a)}{\partial x} = a)
- (\frac{\partial (x^T x)}{\partial x} = 2x)
- (\frac{\partial (x^T A x)}{\partial x} = (A + A^T)x)
常用公式(矩阵情形):
- (\frac{\partial (a^T X b)}{\partial X} = a b^T)
- (\frac{\partial (a^T X^T b)}{\partial X} = b a^T)
利用微分求导
一个实用的技巧是利用矩阵微分来求导。对于实值标量函数 (f(X)),若有:
[
df = \text{tr}(A^T dX)
]
则 (\frac{\partial f}{\partial X} = A)。这提供了一种相对简便的计算偏导的方法。
应用示例:最小二乘法
求解超定方程 (A x \approx b) 的最小二乘解,即最小化 (|A x - b|_2^2)。令目标函数 (f(x) = (A x - b)^T (A x - b)),令其梯度为零:
[
\frac{\partial f}{\partial x} = 2A^T A x - 2A^T b = 0
]
解得 (x = (A^T A)^{-1} A^T b)。矩阵 ((A^T A)^{-1} A^T) 称为 (A) 的伪逆。
🧱 张量初步
张量是比矩阵更本质的数学对象,它明确包含了基底信息,能更清晰地描述线性空间和运算。
向量的积
- 内积(点积):(a \cdot b = a^T b),结果为标量。
- 叉积:(a \times b),结果为向量,方向垂直于 (a) 和 (b),遵循右手定则。可用行列式或反对称矩阵表示:(a \times b = [a]\times b),其中 ([a]\times) 是由 (a) 的分量构成的反对称矩阵。
- 外积:(a \otimes b = a b^T),结果为矩阵。
张量的基本概念
- 协变与逆变:这是针对张量分量而言的。当基底(协变基底)的模长增大时,其对应的分量(逆变分量)会减小;反之,与对偶基底(逆变基底)点乘得到的分量(协变分量)会增大。
- 度规张量:协变基底之间的内积 (g_{ij} = e_i \cdot e_j)。它建立了协变分量与逆变分量之间的联系:(x_i = g_{ij} x^j)。
- 爱因斯坦求和约定:省略求和符号 (\sum),约定重复指标(一上一下)即表示求和。
张量的运算
- 并积:将两个张量直接相乘,得到高阶张量。例如,向量 (x) 和 (y) 的并积:(T = x \otimes y),分量 (T^{ij} = x^i y^j)。
- 缩并:对一对协变和逆变指标求和,使张量阶数降低。例如,矩阵的迹就是对其行指标和列指标的缩并:(\text{tr}(A) = A^i_{, i})。
- 点积:先进行并积,再进行缩并。根据缩并的指标对不同,有单点积、双点积等。
在图形学中的应用:张量提供了一种统一且清晰的记号系统。图形学论文中常出现的“张量”(如柯西应力张量、度规张量)大多是二阶张量,本质上对应一个矩阵,但强调了其分量对基底的依赖性。利用张量记号(如爱因斯坦求和约定)可以简便地证明许多向量恒等式。
📚 课程总结与资源推荐
本节课我们一起学*了图形学中线性代数的进阶内容,包括:
- 矩阵分解:PLU分解、乔里斯基分解、QR分解和奇异值分解的原理与应用。
- 矩阵范数:向量范数与矩阵范数的定义,以及诱导范数等重要概念。
- 矩阵求导:布局约定、基本公式以及利用微分求导的技巧,并了解了其在最小二乘等问题中的应用。
- 张量初步:协变与逆变、度规张量、并积与缩并等基本概念,认识到张量是更本质的数学表达工具。
推荐资源:
- 教科书:《线性代数应该这样学》、《Matrix Cookbook》(矩阵求导工具书)、《矩阵分析与应用》、《张量分析》、《物理几何学导论》。
- 网站:3Blue1Brown(线性代数可视化),Matrix Calculus(矩阵求导在线计算)。
希望本课程能作为大家学*图形学数学的路线图,在需要时能按图索骥,深入探究。



GAMES001-图形学中的数学 - P3:计算几何 - GAMES-Webinar - BV1MF4m1V7e3
计算几何概述
在本节课中,我们将学*计算几何的基本概念和算法,包括点、线、面、多边形、凸包等,以及它们之间的关系和计算方法。
坐标系
坐标系定义
坐标系是由原点和坐标轴构成的,用于表示几何图形的位置和方向。
笛卡尔坐标系
笛卡尔坐标系是一种常用的坐标系,其坐标轴相互垂直,且长度相等。
右手系和左手系
右手系和左手系是两种不同的坐标系方向,用于确定坐标轴的方向。
点、线、面
点
点可以用坐标表示,也可以用向量表示。
线
线可以用参数方程表示,也可以用向量表示。
面面
面可以用隐式方程表示,也可以用参数方程表示。
点线面之间的关系
点与直线的距离
点与直线的距离可以用点到直线的向量与直线的方向向量的叉乘除以直线的方向向量的模长来计算。
点与面的距离
点与面的距离可以用点到*面的向量与*面的法向量的点乘除以*面的法向量的模长来计算。
线与线的位置关系
线与线的位置关系可以用线向量的叉乘或点乘来判断。
多边形
多边形周长
多边形周长是所有边长之和。
多边形面积
多边形面积可以用叉乘法或投影法计算。
凸包
凸包定义
凸包是包含给定点的最小的凸多边形。
凸包计算
凸包可以用扫描法或增量法计算。
最小圆覆盖
最小圆覆盖定义
最小圆覆盖是包围给定点的最小圆。
最小圆覆盖计算
最小圆覆盖可以用暴力法或随机增量法计算。
总结

本节课介绍了计算几何的基本概念和算法,包括点、线、面、多边形、凸包等,以及它们之间的关系和计算方法。这些知识对于图形学中的几何计算非常重要。

GAMES001-图形学中的数学 - P4:旋转变换 🌀
在本节课中,我们将要学*图形学中一个核心概念——旋转变换。我们将从旋转的基本定义出发,探讨其数学性质,并详细介绍三种主要的旋转表示方法:欧拉角、轴角(旋转向量)和四元数。每种方法都有其独特的优势和适用场景,理解它们对于处理三维空间中的旋转至关重要。
旋转的基本概念与性质
上一节我们介绍了线性代数的基础知识,本节中我们来看看旋转变换的具体定义和性质。


旋转是一种特殊的线性变换,它保持物体上任意两点间的距离不变,即是一种刚体变换。对于一个三维空间中的点 P,经过旋转变换 R 后得到点 P',其关系可以表示为:

公式: P' = R * P
其中 R 是一个 3x3 的矩阵。为了保持距离不变,旋转矩阵 R 必须满足正交性,即 R^T * R = I(I 是单位矩阵),并且其行列式 det(R) = +1。所有满足这些条件的 3x3 矩阵的集合称为特殊正交群,记作 SO(3)。
以下是旋转矩阵的几个关键性质:
- 群的性质:两个旋转矩阵相乘,结果仍是一个旋转矩阵。旋转运算是可结合的,即
(R0 * R1) * R2 = R0 * (R1 * R2)。 - 可逆性:旋转矩阵的逆等于其转置,即
R^{-1} = R^T。这意味着你可以通过逆旋转回到初始状态。 - 非交换性:在三维空间中,旋转的顺序至关重要。先绕X轴转90度,再绕Y轴转90度,与先绕Y轴转再绕X轴转,得到的结果完全不同。这体现了三维旋转不满足交换律。
旋转的自由度与流形
在深入具体表示方法前,我们需要理解旋转的“自由度”。自由度描述了完全确定一个旋转所需的最少参数个数。
- 二维旋转的自由度是 1(一个角度
θ)。 - 三维旋转的自由度是 3。这可以从旋转矩阵的约束推导出来:一个 3x3 矩阵有 9 个参数,正交性条件
R^T * R = I提供了 6 个独立约束(因为是对称矩阵),因此剩余的自由度为9 - 6 = 3。
我们可以将所有的三维旋转想象成一个三维流形,它“嵌入”在九维的矩阵空间中。这个流形是连续的,并且局部看起来像一个三维空间。理解这一点有助于我们思考如何进行旋转插值、求导等操作。例如,直接对两个旋转矩阵做线性插值,得到的中间矩阵很可能不在这个流形上(即不是一个有效的旋转矩阵),因此我们需要更聪明的方法。
旋转的表示方法
理解了旋转的本质后,我们来看看如何在计算机中具体地表示和操作一个旋转。以下是三种最常用的方法。
欧拉角 (Euler Angles) ✈️
欧拉角是一种非常直观的表示方法。它将一个复杂的三维旋转分解为绕三个坐标轴(例如X, Y, Z轴)依次进行的三个基本旋转。这三个旋转的角度(例如 (α, β, γ))就是欧拉角。


以下是使用欧拉角时需要注意的关键点:
- 旋转顺序:绕轴的顺序至关重要(如XYZ, ZYX, ZXZ等),不同的顺序对应不同的最终旋转。在图形学软件(如Blender)中,必须明确指定顺序。
- 内旋与外旋:内旋指每次旋转都围绕上一次旋转后的新坐标系轴进行;外旋指每次旋转都围绕固定的世界坐标系轴进行。两者可以通过旋转矩阵乘法的顺序转换。
- 万向锁 (Gimbal Lock):这是欧拉角最严重的问题。当第二个旋转角(如俯仰角
β)为 ±90° 时,第一个和第三个旋转轴会重合,导致丢失一个旋转自由度。此时,系统无法通过欧拉角表示绕特定轴的旋转,在动画插值时会产生不自然跳动。
尽管有万向锁问题,欧拉角因其直观性,仍被广泛用于用户界面(如游戏相机控制中的偏航Yaw和俯仰Pitch)。
轴角与旋转向量 (Axis-Angle & Rotation Vector) 🔄
轴角表示直接描述了旋转的本质:绕空间中的某个单位轴 u 旋转一个角度 θ。这正好对应了三维旋转的3个自由度(u 是三维单位向量,有2个自由度;θ 是标量,有1个自由度)。
我们可以将其紧凑地写成一个旋转向量:v = θ * u。这个三维向量的方向是旋转轴,长度是旋转角度。
给定轴 u 和角 θ,对应的旋转矩阵可以由罗德里格斯旋转公式 (Rodrigues‘ Rotation Formula) 给出:
公式: R = I + sin(θ) * K + (1 - cos(θ)) * K^2
其中 K 是向量 u 的叉乘矩阵(反对称矩阵)。
反之,从旋转矩阵 R 也可以反解出轴和角:
θ = arccos((trace(R) - 1) / 2)- 旋转轴
u与R的反对称部分(R - R^T)相关。
旋转向量也可以方便地进行插值。正确的方法是:计算从旋转A到旋转B的相对旋转,将其表示为轴角 (u, Δθ),然后对角度进行线性插值 θ_t = t * Δθ,再利用罗德里格斯公式构造中间旋转。注意:直接对两个旋转向量做线性插值通常得不到最短路径的*滑旋转。
四元数 (Quaternions) 🧮
四元数是表示旋转最强大、计算最高效的工具之一。它是一个四维超复数,通常写为:
q = [s, v] = s + xi + yj + zk,其中 s 是实部,v = (x, y, z) 是虚部(向量部分)。
用于表示旋转的必须是单位四元数,即满足 s^2 + x^2 + y^2 + z^2 = 1。单位四元数可以表示绕轴 u 旋转 θ 角:
公式: q = [cos(θ/2), sin(θ/2) * u]
一个三维向量 v 的旋转可以通过四元数乘法(“三明治”乘法)实现:
公式: v' = q * v * q^{-1}
其中 v 被提升为纯四元数 [0, v],q^{-1} = q*(共轭四元数,对于单位四元数)。
四元数的优势包括:
- 高效组合:多个旋转连续作用,只需将对应的四元数相乘。
- *滑插值:可以在四维单位球面上进行球面线性插值 (Slerp),得到角速度恒定的最*滑旋转路径。其公式为:
公式:Slerp(q0, q1, t) = (sin((1-t)Ω) / sinΩ) * q0 + (sin(tΩ) / sinΩ) * q1
其中Ω是q0与q1之间的夹角。 - 避免奇异性:没有万向锁问题。
需要注意的是,四元数 q 和 -q 表示同一个旋转。在进行Slerp插值时,应确保选择最短弧,通常通过检查点积 q0·q1,若为负则将一个四元数取反。
角速度与旋转的微分关系 ⏱️
在物理模拟中,我们经常需要处理旋转随时间变化的情况,这就涉及到角速度 ω。
旋转矩阵 R 对时间的导数与角速度 ω(在世界坐标系下)满足以下关系:
公式: dR/dt = [ω]× * R
其中 [ω]× 是角速度向量 ω 对应的叉乘矩阵(反对称矩阵)。
这个公式的物理意义是:刚体上一点的世界坐标速度 v = ω × (R * p_local)。
如果角速度 ω' 是定义在局部坐标系下的,则关系变为:
公式: dR/dt = R * [ω']×
类似地,对于四元数 q,其微分方程为:
公式: dq/dt = (1/2) * ω * q (世界坐标系角速度)
或 dq/dt = (1/2) * q * ω' (局部坐标系角速度)
这里的乘法是四元数乘法,ω 需写为纯四元数 [0, ω]。
这些方程是进行刚体动力学数值积分的基础。
神经网络中的旋转表示 🤖
当使用神经网络处理旋转(如姿态估计、动作生成)时,传统的表示方法可能面临挑战:
- 奇异性:欧拉角有万向锁。
- 周期性/多值性:轴角中,
(θ, u)和(2π-θ, -u)表示相同旋转;四元数中q和-q相同。这会导致损失函数存在多个极小值,不利于优化。 - 约束:旋转矩阵需要正交约束,四元数需要单位约束。

一种在实践中表现良好的表示是 6D 连续旋转表示。网络输出一个6维向量 [a, b],然后通过以下过程得到一个有效的旋转矩阵:
- 将前3维
a归一化得到第一个基向量b1。 - 将后3维
b减去其在b1上的投影,然后归一化,得到与b1正交的第二个基向量b2。 - 通过叉乘
b3 = b1 × b2得到第三个正交基向量。 - 组合
[b1, b2, b3]即得到一个合法的旋转矩阵R ∈ SO(3)。

这种方法避免了显式的周期性,提供了连续的映射,更易于神经网络学*和优化。
总结

本节课中我们一起深入探讨了图形学中的旋转变换。我们从旋转作为特殊正交群 SO(3) 的基本定义出发,理解了其三维流形的本质。然后,我们系统地学*了三种核心的旋转表示法:
- 欧拉角:直观但存在万向锁,适合用户交互。
- 轴角/旋转向量:直接体现旋转几何,是推导的基础。
- 四元数:无奇异性、计算高效、插值*滑,是存储和计算旋转的优选。

我们还探讨了旋转的微分与角速度的关系,这是物理模拟的基石。最后,我们了解了在为神经网络设计旋转输出时,6D连续表示等现代方法的优势。

掌握这些表示方法及其相互转换,是进行三维图形编程、机器人学、动画和物理模拟的必备技能。希望本教程能帮助你建立起清晰的概念框架。

GAMES001-图形学中的数学 - P5:主成分分析与奇异值分解 🧮
在本节课中,我们将要学*线性代数中两个极其重要的概念:奇异值分解与主成分分析。它们在图形学、数据科学和机器学*等领域有着广泛的应用。我们将从实对称矩阵的性质出发,推导出奇异值分解,并探讨其在求解矩阵方程、理解几何变换等方面的应用。随后,我们将介绍主成分分析这一降维技术,并了解其在点云处理、数据压缩等图形学任务中的应用。
实对称矩阵的性质 🔍
上一节我们介绍了矩阵的基本概念。本节中,我们来看看一类特殊的矩阵——实对称矩阵,并证明其两个关键性质,为后续推导奇异值分解奠定基础。
定理一:实对称矩阵的特征值是实数。
对于任意一个实对称矩阵 S,存在特征值 λ 和对应的特征向量 x,满足 Sx = λx。我们假设 x 已归一化,即 xᴴx = 1。对等式两边取共轭,得到 Sxᴴ = λ* xᴴ。由于 S 是实对称矩阵,其共轭等于自身。在等式左边乘以 xᵀ,得到 xᵀSxᴴ = xᵀλ* xᴴ = λ。另一方面,对原式 Sx = λx 两边转置,得到 xᵀS = λxᵀ。在此式右边乘以 xᴴ,得到 xᵀSxᴴ = λxᵀxᴴ = λ。比较两个结果,得到 λ = λ,因此 λ 是实数。
定理二:实对称矩阵不同特征值的特征向量相互正交。
取两个不同的特征值 λ₀, λ₁ 及其对应的特征向量 x₀, x₁。从等式 x₀ᵀS = λ₀x₀ᵀ 出发,右边乘以 x₁,得到 x₀ᵀSx₁ = λ₀x₀ᵀx₁。由于 Sx₁ = λ₁x₁,代入得 x₀ᵀλ₁x₁ = λ₁x₀ᵀx₁。因此有 λ₀x₀ᵀx₁ = λ₁x₀ᵀx₁。由于 λ₀ ≠ λ₁,要使得等式成立,必须有 x₀ᵀx₁ = 0,即 x₀ 与 x₁ 正交。
基于以上性质,任意实对称矩阵 S 可以进行特征值分解:
S = QΛQᵀ
其中,Q 是由特征向量组成的正交矩阵(满足 QᵀQ = I),Λ 是由特征值构成的对角矩阵。
如果 S 是半正定矩阵(即所有特征值 λᵢ ≥ 0),我们可以定义其*方根矩阵 P:
P = Q√Λ Qᵀ
使得 P² = S。此外,实对称矩阵的迹等于其所有特征值之和:tr(S) = Σ λᵢ。
奇异值分解的推导 🧬
理解了实对称矩阵后,本节中我们来看看如何将其性质推广到任意矩阵,从而得到奇异值分解。
考虑一个任意的 m × n 矩阵 A,其秩为 r。构造矩阵 S = AᵀA。S 是一个 n × n 的实对称半正定矩阵。根据上一节的结论,S 可以进行特征值分解,找到 r 个相互正交的特征向量 vᵢ 和对应的特征值 λᵢ (λᵢ ≥ 0)。
我们有以下关系:
AᵀA vᵢ = λᵢ vᵢ
接下来,我们定义一组新的向量 uᵢ:
uᵢ = (1 / √λᵢ) A vᵢ (对于 λᵢ > 0)
可以证明,这样定义的 uᵢ 也是一组正交基。然后,我们通过施密特正交化,将 vᵢ 扩充为 Rⁿ 空间的一组标准正交基,将 uᵢ 扩充为 Rᵐ 空间的一组标准正交基。对于扩充的向量(对应 λᵢ = 0),有 A vᵢ = 0。
将所有这些关系用矩阵形式表示。令 V 的每一列为 vᵢ,U 的每一列为 uᵢ,Σ 是一个 m × n 的“对角”矩阵,其前 r 个对角元 σᵢ = √λᵢ,其余元素为 0。则上述关系可以写为:
A V = U Σ
由于 V 是正交矩阵,Vᵀ = V⁻¹。在等式两边右乘 Vᵀ,我们得到奇异值分解的最终形式:
A = U Σ Vᵀ
其中:
- U 是 m × m 的正交矩阵。
- Σ 是 m × n 的矩阵,其对角元 σᵢ 称为 A 的奇异值,数量等于矩阵的秩 r。
- V 是 n × n 的正交矩阵。
奇异值分解的应用:矩阵的逆与方程求解 ⚙️
上一节我们得到了奇异值分解的形式。本节中我们来看看它在解决线性代数核心问题——求逆和解方程——中的应用。
1. 矩阵的逆
对于一个可逆的方阵 A,其奇异值分解为 A = UΣVᵀ。那么它的逆矩阵可以方便地求出:
A⁻¹ = (UΣVᵀ)⁻¹ = V Σ⁻¹ Uᵀ
其中 Σ⁻¹ 是将 Σ 的非零对角元取倒数得到的对角矩阵。
2. 矩阵的伪逆与方程求解
对于非方阵或不满秩的矩阵 A,严格意义上的逆不存在。但我们可以在最小二乘意义下定义伪逆 A⁺,用于求解方程 Ax ≈ b。
以下是求解不同类型方程时伪逆的形式:
- 超定方程(方程数 > 未知数):通常无精确解,转而求最小化残差 ||Ax - b||² 的解。该解为 x = A⁺ b,其中 A⁺ = (AᵀA)⁻¹Aᵀ,称为左逆。利用SVD,A⁺ = V Σ⁺ Uᵀ。
- 欠定方程(方程数 < 未知数):有无穷多解,转而求满足 Ax = b 且范数 ||x|| 最小的解。该解为 x = A⁺ b,其中 A⁺ = Aᵀ(A Aᵀ)⁻¹,称为右逆。利用SVD,同样有 A⁺ = V Σ⁺ Uᵀ。
在SVD框架下,Σ⁺ 是一个 n × m 的矩阵,它是 Σ 的“转置”,并将其非零奇异值替换为相应的倒数。伪逆 A⁺ = V Σ⁺ Uᵀ 是一个统一的表达式,涵盖了左逆、右逆以及可逆方阵的逆。
奇异值分解在图形学中的应用实例 🎨
理解了奇异值分解的数学原理后,本节中我们来看看它在图形学中的两个具体应用:理解线性变换和形状匹配。
1. 理解线性变换
任意一个线性变换 x' = A x(A 为3×3矩阵)都可以进行SVD分解:A = UΣVᵀ。这个分解可以理解为三个连续的变换:
- Vᵀ x:旋转(因为 V 是正交矩阵)。
- Σ (Vᵀ x):沿坐标轴的缩放(拉伸或压缩)。
- U (Σ Vᵀ x):再次旋转。
因此,任何线性变换本质上都可以分解为“旋转-缩放-旋转”的组合。这被称为极分解的另一种形式:A = RS,其中 R = UVᵀ 是旋转矩阵,S = VΣVᵀ 是一个对称正定矩阵,代表拉伸。
2. 形状匹配算法
形状匹配的目标是:给定一组点从位置 qᵢ 变形到 pᵢ,找到一个最优的刚性变换(旋转 R 和*移 t),使得变换后的 qᵢ 尽可能接* pᵢ。即最小化目标函数:
Σᵢ ||(R qᵢ + t) - pᵢ||²
求解步骤如下:
- 计算两组点的质心:q_c = (1/N) Σ qᵢ, p_c = (1/N) Σ pᵢ。
- 计算去质心坐标:q̃ᵢ = qᵢ - q_c, p̃ᵢ = pᵢ - p_c。
- 构造矩阵 H = Σᵢ p̃ᵢ q̃ᵢᵀ。
- 对 H 进行奇异值分解:H = UΣVᵀ。
- 最优旋转矩阵 R = V Uᵀ(需检查行列式,若为-1则需调整)。
- 最优*移向量 t = p_c - R q_c。
该算法在图形学的碰撞处理、模型对齐等领域有广泛应用。
主成分分析:数据降维技术 📉
上一节我们看到了SVD在几何上的应用。本节中我们来看看另一个紧密相关的技术——主成分分析,它主要用于数据降维和特征提取。
PCA的目标是:对于一个高维数据集,找到一个低维子空间,使得数据在该子空间上的投影能最大程度保留原始数据的方差(即信息)。
算法推导:
假设我们有 N 个 d 维数据点 x₀, x₁, ..., x_{N-1}。
- 计算数据集的均值:μ = (1/N) Σ xᵢ。
- 计算协方差矩阵:S = (1/N) Σ (xᵢ - μ)(xᵢ - μ)ᵀ。
- 对协方差矩阵 S 进行特征值分解:S = QΛQᵀ。特征值 λᵢ 从大到小排列,对应的特征向量 qᵢ 称为主成分。
- 选择前 k 个最大的特征值对应的特征向量 q₀, q₁, ..., q_{k-1},构成投影矩阵 P。
- 任何数据点 x 的 k 维降维表示为:y = Pᵀ(x - μ)。
为什么最大特征值对应的特征向量是最佳投影方向?
最大化投影后数据的方差,等价于最大化 vᵀS v,其中 v 是投影方向(单位向量)。而 vᵀS v 的最大值就是 S 的最大特征值,在 v 为对应特征向量时取到。
主成分分析的应用与局限 🖼️
掌握了PCA的基本原理后,本节中我们来看看它在图形学中的典型应用,并认识其局限性。
应用实例:
- 点云法向估计:对于点云中一个点,取其邻域内的点构成一个局部数据集。对该数据集进行PCA,最小的特征值对应的特征向量方向*似为该点的法向方向。前两个特征向量则张成切*面。
- 数据压缩:例如,一张 512×512 的图片可以看作一个 262144 维的数据点。对多张人脸图片(数据集)进行PCA,可以得到一个低维的“特征脸”空间。任何一张新人脸都可以用少数几个主成分的系数来*似表示,从而实现压缩。
- 模型降阶:在物理模拟中,系统的自由度可能极高。可以预先运行多次模拟,将结果作为高维数据点,然后用PCA提取主要变化模式。后续模拟只需在低维的主成分空间中进行,大幅提升计算效率。
局限性:
PCA是一种线性降维方法。它假设数据的主要结构存在于一个线性子空间中。对于具有非线性结构的数据(例如一个三维空间中的螺旋线或圆圈),PCA无法有效降维。此时,需要借助非线性降维方法,如:
- 核PCA:先将数据通过非线性函数映射到高维空间,再在高维空间中进行线性PCA。
- 自编码器:使用神经网络学*数据从高维到低维(编码)以及从低维恢复至高维(解码)的非线性映射,其隐空间表示即为降维结果。
总结 📚
本节课中我们一起学*了图形学数学基础中的两个核心工具:奇异值分解与主成分分析。
我们首先从实对称矩阵的性质出发,严谨地推导出了任意矩阵的奇异值分解形式 A = UΣVᵀ。我们探讨了SVD在求解矩阵逆、伪逆以及线性方程组(超定、欠定)中的应用。在图形学中,SVD帮助我们理解线性变换的几何本质(旋转与缩放),并构成了形状匹配等算法的数学基础。
随后,我们介绍了主成分分析这一数据降维技术。PCA通过计算数据协方差矩阵的特征向量,找到数据方差最大的投影方向,从而实现高效的数据压缩和特征提取。我们看到了PCA在点云处理、图像压缩和模拟加速中的应用,同时也指出了其作为线性方法的局限性。

这些概念是连接线性代数与图形学应用的重要桥梁,理解它们将为学*更高级的图形学主题奠定坚实的基础。

GAMES001-图形学中的数学 - P6:插值与拟合 📈
在本节课中,我们将学*图形学中两个核心的数学概念:插值与拟合。我们将从定义和区别入手,逐步介绍多种插值方法,并探讨如何通过拟合来逼*带有噪声的数据。课程内容力求简单直白,确保初学者能够理解。
概述
插值与拟合是处理离散数据、构建连续函数或曲面的基础技术。它们的核心区别在于:
- 插值:要求构造的函数必须经过所有给定的数据点。
- 拟合:构造的函数无需经过所有数据点,旨在捕捉数据的整体趋势,常用于处理带有噪声的数据。
接下来,我们将首先深入探讨各种插值技术。
插值方法
线性插值 📏
线性插值是最简单的插值方法。给定两个点 (x0, y0) 和 (x1, y1),我们假设它们之间的函数关系是一条直线。
其函数表达式为:
y = (1 - t) * y0 + t * y1
其中,t = (x - x0) / (x1 - x0),表示点 x 在线段 [x0, x1] 中的比例。
当有多个点时,只需在每两个相邻点之间进行线性插值,然后将所有线段连接起来。这种方法得到的曲线是连续的,但在连接点处的导数不连续,我们称这种连续性为 C0连续。
上一节我们介绍了最简单的线性插值,本节中我们来看看如何通过更高阶的多项式来获得更光滑的曲线。
多项式插值(拉格朗日插值)🔢
为了获得更光滑的曲线(例如导数连续),我们可以使用多项式插值。其核心思想是:给定 n+1 个数据点,可以唯一确定一个 n 阶多项式,使其经过所有点。
拉格朗日插值给出了这个多项式的显式表达式:
P(x) = Σ [ y_j * L_j(x) ], 对 j 从 0 到 n 求和。
其中,L_j(x) 称为拉格朗日基函数,其定义为:
L_j(x) = Π [ (x - x_i) / (x_j - x_i) ], 对 i 从 0 到 n 且 i ≠ j 求积。
拉格朗日基函数具有关键性质:L_j(x_k) = δ_jk(当 j=k 时为1,否则为0)。这保证了插值多项式必然经过所有数据点。
一个 n 阶多项式具有 C^(n-1) 连续性,因此曲线非常光滑。然而,高阶多项式插值存在龙格现象:当数据点等距分布且多项式阶数较高时,插值结果在区间边缘会产生剧烈的震荡,与真实函数偏离甚远。
由于龙格现象的存在,直接使用高阶全局多项式插值并不总是可靠。接下来,我们将介绍一种能避免此问题的方法。
样条插值 🧩
样条插值通过拼接多段低阶多项式来构造整体曲线,从而避免高阶震荡。每一段的多项式阶数较低,因此不会剧烈震荡。
以下是两种常见的样条插值:
二阶样条插值:
- 每一段曲线都是一个二次函数。
- 需要满足条件:1) 经过区间两端点(插值条件);2) 在内部连接点处一阶导数连续。
- 这需要求解一个线性方程组。该方法得到的曲线是 C1连续 的,但改变一个数据点会影响整个曲线(缺乏局部性)。
三阶样条插值:
- 每一段曲线都是一个三次函数。
- 需要满足条件:1) 经过区间两端点;2) 在内部连接点处一、二阶导数均连续。
- 同样需要求解线性方程组。该方法得到的曲线是 C2连续 的,非常光滑,但也缺乏局部性。
样条插值虽然光滑,但需要解方程且缺乏局部性。下面我们介绍一种兼具局部性和无需解方程的方法。
三阶厄米特插值 🔗
三阶厄米特插值在每一段上也是三次函数,但它通过直接指定区间端点的函数值和导数值来确定该段曲线。
对于区间 [x0, x1],给定端点函数值 y0, y1 和导数值 m0, m1,可以唯一确定一个三次多项式。其解可以写成基函数的形式:
y = y0 * H00(t) + m0 * H10(t) + y1 * H01(t) + m1 * H11(t)
其中 t = (x - x0)/(x1 - x0),H00, H10, H01, H11 是定义在 [0, 1] 上的三次多项式,称为厄米特基函数。
通过规定每个数据点上的导数值(例如,取左右邻接线段斜率的*均值),并将多段厄米特曲线拼接,就能得到一条整体的 C1连续 插值曲线。这种方法无需解大型方程组,且具有局部性:修改一个数据点,只影响其相邻的曲线段。
我们已经介绍了几种主要的插值方法,现在用一个统一的视角——基函数来理解它们。
基函数视角 🧠
大多数插值方法都可以写成统一形式:
f(x) = Σ [ y_i * φ_i(x) ]
其中 φ_i(x) 就是与第 i 个数据点对应的基函数。

基函数的核心性质是:φ_i(x_j) = δ_ij。这保证了插值条件自动满足。
不同的插值方法,对应不同的基函数:
- 线性插值:基函数是分段的线性“帽状”函数。
- 拉格朗日插值:基函数是全局的高阶多项式。
- 三阶样条插值:基函数是通过解方程得到的分段三次函数,支撑范围较广。
- 三阶厄米特插值:基函数是局部的分段三次函数,具有紧支撑性(只在局部非零),这是其局部性的体现。

多维与曲线插值
之前的讨论集中在一维函数插值。在图形学中,我们经常需要处理更高维度的插值问题。
曲线插值 🧵
问题:给定空间中一系列有序的点 P0, P1, ..., Pn,求一条经过所有这些点的光滑曲线。
解法:将三阶厄米特插值推广到向量形式。将标量函数值 y 和导数值 m 替换为位置向量 P 和切线向量 T。插值公式变为:
C(t) = P0 * H00(t) + T0 * H10(t) + P1 * H01(t) + T1 * H11(t)
这样得到的是一条参数曲线,在图形学软件(如PPT的曲线工具)中广泛应用。
三角形插值(重心坐标)🔺
对于三角形内的点,可以使用重心坐标进行插值。三角形内任意点 P 可表示为三个顶点 A, B, C 的加权和:
P = α * A + β * B + γ * C
其中 (α, β, γ) 称为重心坐标,且 α + β + γ = 1。α 等于 P 点对面小三角形面积与总面积之比。
重心坐标 α, β, γ 本身可以看作定义在三角形上的基函数,它们满足 φ_A(A)=1, φ_A(B)=φ_A(C)=0 等性质。这自然适用于在三角形顶点属性(如颜色、法线)之间进行插值。
像素插值(双线性与双三次)🖼️

在图像处理中,常需要对像素网格进行插值,例如纹理映射中的放大。
- 双线性插值:在
x和y两个方向上分别进行线性插值。 - 双三次插值:在
x和y两个方向上分别进行三阶厄米特(或样条)插值,通常能获得更*滑、质量更高的结果。
点云插值(径向基函数)☁️
问题:给定空间中一组散乱的点 {xi} 及其函数值 {fi},求整个空间域的插值函数。
解法:使用径向基函数方法。假设插值函数形式为:
f(x) = Σ [ w_i * φ( ||x - x_i|| ) ], 对 i 从 1 到 n 求和。
其中 φ(r) 是径向基函数(如高斯函数、薄板样条),只与距离 r 有关。权重 {w_i} 通过求解线性方程组(强制满足所有点的插值条件)得到。
注意:在有些场景(如SPH流体、NeRF)中,类似的形式 f(x) = Σ [ f_i * φ( ||x - x_i|| ) ] 也被称为“插值”,但它并不经过数据点,而是将点上的值“涂抹”到周围空间,更接*一种*滑重建。
拟合方法
现在,我们转向不要求经过所有数据点的拟合技术。
最小二乘法拟合 📉
最小二乘法是最常用的拟合方法。它假设数据符合一个带有未知参数的模型(如 y = a*x + b),然后通过最小化所有数据点的预测值与真实值之差的*方和,来求解最优参数。
用矩阵表示:假设模型为 y = G * a,其中 G 是由基函数在数据点处取值构成的矩阵,a 是待求参数向量。给定数据向量 y,最优参数 a 的解为:
a = (G^T * G)^(-1) * G^T * y
这对应于求解一个超定方程组的最小二乘解。
最小二乘法假设全局数据符合同一个模型。接下来看一种更灵活的局部拟合方法。
移动最小二乘法 🏃
移动最小二乘法是一种局部拟合技术。对于每一个待求点 x,只使用其邻*的数据点进行拟合,且邻*点的权重随距离增加而衰减。
具体步骤:
- 对于待求点
x,选取其邻域内的一组数据点{xi}。 - 在
x的局部坐标系下,用一个低阶多项式(如一次或二次)f(r) = p^T * a来拟合数据,其中r = xi - x。 - 最小化加权*方和:
min Σ [ w_i * (f(r_i) - f_i)^2 ]。 - 求解得到局部参数
a。此时,x点本身的拟合值就是a的第一个分量,梯度信息也包含在a中。
这种方法无需全局模型假设,对每个点独立进行拟合,非常灵活,常用于点云数据处理和曲面重建。
随机抽样一致算法 🎲
RANSAC 用于处理包含大量异常值的数据拟合。其基本思想是:通过随机抽样和迭代,寻找一个被最多“内点”(符合模型的数据)支持的模型。
算法流程:
- 随机从数据集中选取拟合所需的最少样本数(如拟合直线选2个点)。
- 用这些样本计算一个模型。
- 统计整个数据集中有多少数据点与该模型的误差小于阈值(这些点称为“内点”)。
- 如果内点数量超过预设阈值,则认为模型较好;否则,回到步骤1。
- 重复多次,选择内点最多的那个模型,并用所有内点重新拟合最终模型。
RANSAC 能有效抵抗异常值的干扰,在图像匹配、点云配准等领域应用广泛。
总结
本节课中我们一起学*了图形学中的插值与拟合。
- 插值强调经过数据点,我们介绍了从简单的线性插值,到光滑但可能震荡的多项式插值,再到稳定实用的样条插值和三阶厄米特插值,并扩展到曲线、三角形、像素和点云等不同场景。
- 拟合不强求经过数据点,旨在捕捉趋势。我们学*了经典的最小二乘法,灵活的移动最小二乘法,以及抗噪声能力强的RANSAC算法。


理解这些方法的原理、优缺点及适用场景,对于解决图形学中的几何建模、图像处理、数据重建等问题至关重要。

GAMES001-图形学中的数学 - P7:傅里叶变换与球谐函数 🎯
在本节课中,我们将学*图形学中两个核心的数学工具:傅里叶变换与球谐函数。我们将从基础概念出发,理解它们如何描述和分析函数,并探讨它们在图形学中的广泛应用。
傅里叶展开:从周期函数到三角级数 📈
上一节我们介绍了课程的整体安排,本节中我们来看看傅里叶展开。傅里叶展开描述了一个周期函数如何分解为一系列正弦和余弦函数的叠加。
例如,一个周期为 T 的方波信号,在 0 到 T/2 时取值为 1,在 T/2 到 T 时取值为 -1。这个函数可以展开为不同频率正弦和余弦函数的加权和。
写成公式如下:
f(t) = a_0/2 + Σ_{n=1}^{∞} [a_n cos(nωt) + b_n sin(nωt)]
其中,ω = 2π/T 是基频。系数 a_n 和 b_n 可以通过积分公式求得:
a_n = (2/T) ∫_{0}^{T} f(t) cos(nωt) dt
b_n = (2/T) ∫_{0}^{T} f(t) sin(nωt) dt
这个过程就是将复杂的周期信号,分解为一系列简单谐波(正弦波)的过程。随着叠加的谐波数量增加,合成的波形会越来越接*原始方波。
傅里叶变换:从周期到非周期 🔄
上一节我们介绍了周期函数的傅里叶展开,本节中我们来看看如何将其推广到非周期函数。核心思想是将非周期函数视为周期无穷大的周期函数。
当周期 T 趋向于无穷大时,离散的频率求和就变成了连续的频率积分。由此我们得到傅里叶变换对:
正变换(从时域到频域):
F(ω) = ∫_{-∞}^{∞} f(t) e^{-iωt} dt
逆变换(从频域到时域):
f(t) = (1/2π) ∫_{-∞}^{∞} F(ω) e^{iωt} dω
这里,e^{iωt} = cos(ωt) + i sin(ωt) 是欧拉公式。F(ω) 是一个复函数,其模长表示频率 ω 成分的振幅,辐角表示相位。
本质上,傅里叶变换是在函数构成的线性空间中,做了一次基底的变换。原来我们默认使用狄拉克δ函数作为基,函数值 f(t) 就是其系数。傅里叶变换则换用复指数函数 e^{iωt} 作为新基,F(ω) 就是函数在这个新基下的展开系数。
离散傅里叶变换与快速算法 ⚡
上一节我们讨论了连续的傅里叶变换,但在计算机中,我们处理的是离散信号。离散傅里叶变换(DFT)是连续傅里叶变换的离散化形式。
对于一个长度为 N 的离散序列 x[n],其DFT X[k] 定义为:
X[k] = Σ_{n=0}^{N-1} x[n] e^{-i 2π k n / N}, k = 0, 1, ..., N-1
逆变换为:
x[n] = (1/N) Σ_{k=0}^{N-1} X[k] e^{i 2π k n / N}, n = 0, 1, ..., N-1
直接计算DFT的复杂度是 O(N^2)。快速傅里叶变换(FFT)算法利用分治思想,将复杂度降低到 O(N log N),使其得以广泛应用。

以下是FFT在图形学中的一个经典应用:图像处理。
- 图像压缩:对图像进行二维DFT得到频谱,保留低频分量(中心部分),丢弃高频分量,再进行逆变换,可以实现有损压缩(如JPEG格式)。
- 边缘提取:对图像频谱进行高通滤波(保留外围高频,去除中心低频),再进行逆变换,可以得到图像的边缘信息。

卷积定理:连接时域与频域的桥梁 🌉
上一节我们介绍了离散傅里叶变换,本节中我们来看看一个与之紧密相关的操作:卷积。卷积在信号处理和图像处理中无处不在,例如图像模糊、边缘检测等。
两个一维函数 f(t) 和 g(t) 的卷积定义为:
(f * g)(t) = ∫_{-∞}^{∞} f(τ) g(t - τ) dτ
直观上,卷积是将函数 g 翻转并*移,然后与函数 f 逐点相乘并积分。
卷积定理揭示了卷积与傅里叶变换之间的美妙关系:
F{f * g} = F{f} · F{g}
F{f · g} = F{f} * F{g}
其中,F{·} 表示傅里叶变换,* 表示卷积,· 表示点乘。时域中的卷积,等价于频域中的乘法;时域中的乘法,等价于频域中的卷积。


这个定理为分析许多问题提供了强大工具。例如,在图像处理中,用一个*均核对图像进行卷积(模糊操作),在频域看来,就是图像的频谱乘以该卷积核的频谱(一个低通滤波器),从而衰减高频信息。

采样与走样:频域视角的分析 📊
上一节我们利用卷积定理分析了图像处理,本节中我们用它来分析图形学的另一个核心问题:采样与走样。
采样过程可以建模为原始连续信号 f(t) 与一个狄拉克梳状函数 Ш_T(t) 相乘:
f_s(t) = f(t) · Ш_T(t)
狄拉克梳状函数由一系列间隔为 T 的狄拉克δ函数组成,它只在采样点处有值。
根据卷积定理,时域的乘法对应频域的卷积。已知狄拉克梳状函数的傅里叶变换仍是狄拉克梳状函数,其间隔变为 2π/T。因此,采样后信号的频谱 F_s(ω) 是原始信号频谱 F(ω) 的周期性复制:
F_s(ω) = (1/T) Σ_{n=-∞}^{∞} F(ω - n * (2π/T))
采样定理(奈奎斯特定理) 指出:为了避免频谱复制时发生混叠(Aliasing),采样频率 1/T 必须至少是信号最高频率的两倍。如果采样频率过低,复制出的频谱就会重叠,导致无法从采样信号中无损恢复原始信号,这就是“走样”。
在图形学中,这解释了为什么对图像或场景进行欠采样(如光栅化时分辨率不足)会产生锯齿。反走样技术(如超采样、多重采样)的核心就是提高有效采样频率或预先过滤掉高于奈奎斯特频率的信号成分。
球谐函数:球面上的“傅里叶展开” 🌐
上一节我们在*直空间(时域/频域)讨论了傅里叶分析,本节中我们将其思想推广到球面坐标系。球谐函数(Spherical Harmonics, SH)就是定义在球面上的一组正交基函数。
在图形学中,许多函数定义在球面上,例如环境光照贴图、双向反射分布函数(BRDF)等。球谐函数提供了一种压缩和*似这些球面函数的有力工具。
球谐函数 Y_l^m(θ, φ) 的数学表达式较为复杂,包含连带勒让德多项式 P_l^m 和复指数项 e^{imφ}:
Y_l^m(θ, φ) = N_l^m · P_l^m(cosθ) · e^{imφ}
其中,l 是阶数(l ≥ 0),m 是次数(-l ≤ m ≤ l)。N_l^m 是归一化常数。

我们可以将球谐函数可视化:从球心出发,沿方向 (θ, φ) 的射线长度正比于 Y_l^m(θ, φ) 的值。随着阶数 l 增加,球面上的“波段”数量增多,表示更高的频率成分。

任意一个球面函数 f(θ, φ) 可以投影到球谐基上,展开为:
f(θ, φ) = Σ_{l=0}^{∞} Σ_{m=-l}^{l} c_l^m Y_l^m(θ, φ)
系数 c_l^m 由内积求得:
c_l^m = ∫_{S^2} f(θ, φ) Y_l^m(θ, φ) dΩ
与傅里叶级数类似,我们可以用低阶(如前3阶或5阶)的球谐函数来*似原始球面函数,实现数据的有效压缩和降噪。这在实时渲染中用于*似环境光照和漫反射全局光照(Precomputed Radiance Transfer, PRT)等技术中至关重要。

总结 📝
本节课中我们一起学*了图形学中两个强大的数学工具:
- 傅里叶变换:本质是函数空间的基变换,从时域基(δ函数)切换到频域基(复指数函数)。它让我们能从频率视角分析信号。
- 卷积定理:建立了时域卷积与频域乘法的等价关系,是分析采样、滤波等操作的核心。
- 采样与走样:从频域看,采样是频谱的周期性复制;欠采样会导致频谱混叠,产生走样。
- 球谐函数:可以看作是球坐标系下的“傅里叶展开”,为处理和压缩球面函数(如光照)提供了标准正交基。

理解这些概念,将为深入学*图形学中的渲染、采样、信号处理等高级主题打下坚实的基础。

GAMES001-图形学中的数学 - P8:概率论(全) 📊
在本节课中,我们将回顾概率论的基础知识。概率论是图形学中许多算法的基础,理解其核心概念至关重要。我们将从概率的定义出发,逐步介绍古典概型、条件概率、贝叶斯公式、随机变量及其分布、数字特征(期望与方差),最后简要介绍大数定律与中心极限定理。
什么是概率?🎲
概率的概念源于频率。对于一个随机试验(例如抛硬币),在相同条件下重复进行N次,事件A(例如正面朝上)发生了NA次。比值NA/N称为事件A发生的频率,记作fN(A)。频率是一个介于0和1之间的实数。
对于一个随机试验E,其样本空间S是所有可能结果的集合。对于E的每一个事件A,我们赋予一个实数P(A),称为事件A的概率。一个有效的概率定义必须满足以下三条公理:
- 非负性:对于任何事件A,有 P(A) ≥ 0。
- 规范性:必然事件的概率为1,即 P(S) = 1。
- 可列可加性:如果一系列事件A1, A2, ... 两两互斥(不可能同时发生),则它们至少有一个发生的概率等于各自概率之和,即 P(∪Ai) = ΣP(Ai)。
当试验次数N趋向于无穷大时,事件A的频率fN(A)会趋向于其概率P(A)。此时,我们定义的P(A)才具有我们所理解的“可能性”的含义。
古典概型(等可能概型)⚖️
古典概型是一种简单且常见的概率模型。它满足两个条件:
- 试验的样本空间只包含有限个基本事件。
- 试验中每个基本事件发生的可能性相同。
在古典概型中,事件A的概率计算公式为:
P(A) = A包含的基本事件数 / 基本事件的总数
以下是几个古典概型的例子:
- 抛硬币:样本空间为{正面,反面},基本事件数2,故正面朝上的概率为1/2。
- 掷骰子:样本空间为{1, 2, 3, 4, 5, 6},基本事件数6,故掷出点数为3的概率为1/6。
- 生日问题:一个班有50名同学,至少有两人生日相同的概率很高。我们可以计算所有人生日都不同的概率,然后用1减去它。计算表明,这个概率接*1。
条件概率与重要公式 🔗
上一节我们介绍了基础概率,本节中我们来看看当事件之间存在关联时,如何计算概率。
条件概率
条件概率是指在事件A已经发生的条件下,事件B发生的概率,记作P(B|A)。其定义为:
P(B|A) = P(A∩B) / P(A),其中P(A) > 0。
将分母乘到左边,得到概率的乘法定理:
P(A∩B) = P(A) * P(B|A)
例子:某游戏抽中五星卡的概率为0.6%,抽中五星卡时,抽到特定角色的概率为50%。则单次抽卡抽中特定五星角色的概率为:0.6% * 50% = 0.3%。
全概率公式
设B1, B2, ..., Bn是样本空间S的一个完备划分(即它们互斥且并集为S)。则对任一事件A,有全概率公式:
P(A) = Σ P(Bi) * P(A|Bi)
这个公式将复杂事件A的概率,分解为在不同“场景”(Bi)下发生概率的加权和。
贝叶斯公式
贝叶斯公式描述了如何利用“结果”(A)的信息来更新我们对“原因”(Bi)可能性的判断。公式如下:
P(Bi|A) = [P(Bi) * P(A|Bi)] / Σ [P(Bj) * P(A|Bj)]
例子(核酸检测):假设人群感染率Q=1%,核酸检测假阴性率P=1%(即感染者有1%概率测为阴性),假阳性率为0。那么,一次检测结果为阴性的人,实际是感染者的概率是多少?
应用贝叶斯公式计算,这个概率约为0.01%。这说明在低感染率下,即使有假阴性,单次阴性结果仍有很高的可信度。
随机变量及其分布 📈
随机变量是将随机试验的结果映射为实数的函数。例如,抛硬币后,定义“正面得0分,反面得1分”,得分就是一个随机变量。
离散型随机变量
取值可数的随机变量。以下是几种重要的离散分布:
- (0-1)分布:随机变量X只取0或1。P(X=1)=p, P(X=0)=1-p。
- 二项分布:描述n次独立的(0-1)试验中成功次数k的概率。
P(X=k) = C(n,k) * p^k * (1-p)^(n-k) - 泊松分布:二项分布当n很大而p很小时的*似。常用于描述单位时间内随机事件发生的次数。
P(X=k) = (λ^k / k!) * e^(-λ),其中λ是单位时间内事件发生的*均次数。
连续型随机变量
取值充满某个区间的随机变量。由于取任一特定值的概率为0,我们使用概率密度函数(pdf) f(x) 和分布函数(cdf) F(x) 来描述。
F(x) = P(X ≤ x) = ∫_{-∞}^{x} f(t) dt
f(x) = dF(x)/dx (在F(x)可导的点)
以下是几种重要的连续分布:
- 均匀分布:在区间[a, b]上,概率密度是常数。
f(x) = 1/(b-a), for a ≤ x ≤ b - 指数分布:具有“无记忆性”,常用于描述等待时间。其概率密度为:
f(x) = (1/θ) * e^(-x/θ), for x ≥ 0,其中θ>0为参数。 - 正态分布(高斯分布):最重要的连续分布,由均值μ和标准差σ决定。
f(x) = 1/(σ√(2π)) * e^(-(x-μ)²/(2σ²))
随机变量的数字特征 🧮
了解分布后,我们需要一些数值来刻画随机变量的核心特征。
数学期望(均值)
期望是随机变量所有可能值的加权*均,反映其“*均水*”。
- 离散型:E(X) = Σ [xk * P(X=xk)]
- 连续型:E(X) = ∫_{-∞}^{∞} x * f(x) dx
期望的性质:
- E(C) = C (C为常数)
- E(CX) = C * E(X)
- 线性性:E(X+Y) = E(X) + E(Y) (无论X,Y是否独立)
- 若X,Y独立,则E(XY) = E(X) * E(Y)
方差与标准差
方差度量随机变量取值与其均值的偏离程度,记作D(X)或Var(X)。标准差是方差的算术*方根。
D(X) = E[ (X - E(X))² ] = E(X²) - [E(X)]²
方差的性质:
- D(C) = 0
- D(CX) = C² * D(X)
- D(X+C) = D(X)
- D(X+Y) = D(X) + D(Y) + 2Cov(X,Y)
协方差与相关系数
协方差Cov(X,Y)度量两个随机变量的协同变化趋势。
Cov(X,Y) = E[ (X-E(X)) * (Y-E(Y)) ] = E(XY) - E(X)E(Y)
相关系数ρXY是标准化后的协方差,取值在[-1, 1]之间。
ρXY = Cov(X,Y) / [√D(X) * √D(Y)]
相关系数为0称为“不相关”。相互独立一定不相关,但不相关不一定独立(可能存非线性关系)。
大数定律与中心极限定理 ⚙️
本节我们来看看概率论中两个描述宏观规律的著名定理。
大数定律
大数定律描述了大量随机试验*均结果的稳定性。
- (弱)大数定律:独立同分布的随机变量序列X1, X2, ..., 其均值
X̄n = (ΣXi)/n依概率收敛于数学期望μ。即试验次数足够多时,*均值几乎必然接*期望值。 - 伯努利大数定律:是上述定律在(0-1)分布下的特例,即频率依概率收敛于概率。
中心极限定理
中心极限定理是概率论的基石之一。它指出,无论单个随机变量服从什么分布,只要满足一定条件,大量独立随机变量之和的标准化形式,其分布*似于标准正态分布。
- 独立同分布中心极限定理:设X1, X2, ..., Xn独立同分布,期望为μ,方差为σ²。则当n很大时,随机变量 Zn = (ΣXi - nμ) / (√n * σ) *似服从标准正态分布N(0,1)。
这意味着,许多微小、独立的随机因素叠加的总效应,往往呈现正态分布。例如,考试分数、测量误差等常服从正态分布。
总结 📝

本节课中,我们一起学*了概率论的基础知识。我们从概率的定义和古典概型出发,学*了条件概率、全概率公式和强大的贝叶斯公式。接着,我们引入了随机变量的概念,分别探讨了离散型与连续型随机变量的常见分布,如二项分布、泊松分布、均匀分布、指数分布和正态分布。然后,我们学*了刻画随机变量特征的数字工具:期望(均值)、方差、协方差和相关系数。最后,我们简要介绍了描述概率宏观规律的大数定律和中心极限定理。这些概念是理解图形学中许多基于概率的算法(如蒙特卡洛积分、路径追踪等)的必备基础。下一节课,我们将聚焦于概率论在图形学中的具体应用。

🎮 GAMES001-图形学中的数学 - P9:概率论(二)
概述: 本节课将探讨概率论在图形学中的应用,主要涵盖噪声和蒙特卡洛积分法。
1. 噪声
1.1 噪声的定义
- 噪声是具有一定随机性的扰动或随机分布。
- 在图像和视频中,噪声可以表示为具有一定随机性的扰动。
1.2 噪声的类型
- 白噪声: 在一定频率范围内,所有频率的声音均匀混合。
- 粉噪声: 低频强,高频弱。
- 蓝噪声: 低频弱,高频强。

1.3 噪声在图像中的应用

- 图像量化过程中,由于显示设备限制,需要进行量化处理,这会导致图像细节丢失。
- 通过添加噪声,可以模拟图像细节,提高图像质量。
1.4 噪声与图像量化
- 有序抖动法:将图像像素值映射到3x3像素块,根据像素值大小决定块内像素值。
- 随机抖动法:在图像像素值上随机添加噪声,然后进行量化处理。
- 蓝噪声抖动法:使用特定算法生成蓝噪声,然后进行抖动和量化处理。
2. 蒙特卡洛积分法
2.1 蒙特卡洛积分法简介
- 蒙特卡洛积分法是一种基于随机抽样的数值积分方法。
- 通过随机抽样,可以*似计算积分值。
2.2 蒙特卡洛积分法的基本原理
- 在积分区间内随机抽样,计算抽样点的函数值,然后对这些值进行加权*均。
2.3 蒙特卡洛积分法的应用
- 求不规则图形的面积。
- 求定积分。
- 渲染算法中的采样。
2.4 重要性采样
- 当函数值与概率密度函数形状相似时,蒙特卡洛积分的方差最小。
- 重要性采样可以提高蒙特卡洛积分的效率。
2.5 拟蒙特卡洛法
- 结合蒙特卡洛法和矩形法,提高蒙特卡洛积分的效率。
2.6 低差异序列
- 低差异序列可以减少蒙特卡洛积分的方差,提高收敛速度。
2.7 随机数生成
- 生成均匀分布的伪随机数。
- 在单位球面上进行随机均匀采样。
总结

本节课介绍了概率论在图形学中的应用,包括噪声和蒙特卡洛积分法。通过学*这些内容,可以更好地理解图形学中的随机现象,并应用于实际问题的解决。

GAMES002-图形学研发基础工具 - P1:Linux + Shell 🐧

概述
在本节课中,我们将要学*Linux操作系统和Shell的基础知识。这是图形学乃至整个计算机领域研发的重要基础工具。课程将从科研实践的意义引入,然后详细介绍Linux系统的概念、访问方式、Shell的基本操作、文件权限管理、包管理以及Shell脚本编写等内容。


科研实践与工具的重要性
我是陈宝泉,来自北京大学。GAMES002这门课程由我和辛金瑞、叶开两位同学共同讲授。我首先开个头,因为这个课程与科研密切相关。
科研在大学里并非每位同学都会参与,它基于个人兴趣。对于大学生而言,科研常常显得神秘。因此,在北大,我们专门开设了一门教授科研实践的课程,旨在讲解科研是什么,并培养基本的科研能力。我们也安排了一些方法来加强同学们的科研训练。



科研除了是大家了解的科学发现过程,实际上也是一种自我训练和能力培养的方式。对于以科研为职业的科学家而言,科研更是一种生活方式,它渗透到生活的方方面面。它培养我们如何思考问题、面对问题和解决问题。


在本科期间,如果能够参与一定的科研实践活动,将非常有收益。我们这门课并非涵盖科研的方方面面,而是提供一系列实用的工具。我先介绍一下这门课的由来。

科研工具的介绍是我们科研实践课的一部分。在北大,我开设并教授一门名为“科研实践”的课程,为期两个学期。第一个学期的课程内容主要分为几大块,让大家感受科研涉及哪些方面。


课程内容主要分为三大块:
- C1:科研方法。这部分主要由我来讲授。
- C2:科研工具。要进入科研领域,可能需要先掌握一些相应的工具,才能更容易地上手。总的来说,就是降低科研门槛,让大家能够更顺畅地进入。
- A:导师面对面交流。让同学们与一批科研导师交流,了解不同科研方向的问题。学生能够对不同研究领域和不同导师有很好的了解。这部分基本上是导师进课堂的方式,促进学生和导师之间形成一对一的交流。未来,学生可以选择去相关导师的实验室进行较长期的实*。
整个这一套方式,就是通过这样的内容,让大学本科的同学从大二开始,如果对科研想了解、想进入科研实践,能更方便一些。我们通过一门课的方式来做这样的连接。当然,如果没有这门课,这些元素你自己也可以去发现。导师也可以主动去联络。涉及到的科研工具,通过这门课也能有所了解。只要你主动、有意识,有没有这门课其实也没关系。
在讲授科研方法和素养方面,主要涉及以下内容:
首先,科研作为一种生活方式,我们需要在心理和心态上做好准备。在做科研之前,我们需要了解,做科研是有责任的,也涉及到科研道德等方面,需要提前有所了解。
当然,做科研的话,阅读能力很重要,阅读和写作科技论文或科技报告的能力很重要。怎么样培养这些能力?其实可以通过一些讨论、经验分享来实现。网络上也有很多相应的文章和经验分享,大家也可以有意识地了解。
还有一个特别重要的点就是科研交流。科研过程不是一个人关起门来闭门造车,它实际上需要不断地和别人去讨论、沟通。当你有了成果以后,也需要去展示你的成果。所以,如何做交流、如何做报告,这些都涉及到一些能力的培养。
在我们的课堂上,大概会有这样一些讨论主题。我可能会分享一些文章,例如针对图灵奖获得者Donald Knuth教授的一篇访谈,以及Richard Hamming写的一篇非常经典的报告《You and Your Research》。这个报告在网络上很容易找到,是一个非常经典的报告,阅读它的脚本总结应该可以获得很多收益。我们课堂上会分享这篇文章,并和大家一起讨论。
关于社会责任、科研伦理等方面,也有一些文章的阅读,大家也会在课堂上去讨论。作为各位,如果感兴趣,你也可以阅读相应的文章,和同学们互相讨论。可以主动在课外或兴趣小组中进行。
其他方面,如文献阅读、写作、沟通交流等,也有很多非常好的课件或资料,大家都可以去查找。我也列举了例如哈佛大学已故的Patrick Winston教授的一个非常经典的关于如何做报告的经验分享,非常值得去听一下。
第二部分就是我说的工具。在我们大学的课程学*当中,我们学到很多知识,但是面对科研,可能有一些工具并不在我们所学的课程中都覆盖了。像Python这样的东西还是覆盖了,但是有很多工具是没有被覆盖或没有讲到的,或者说没有足够的锻炼。这些东西有必要把它拎出来,我们来做这些分享。这也是我们GAMES002这门课的一个主要内容。
在总结这些内容的时候,一方面是我们同学们的*常总结,另一方面还有一个参考,就是MIT有一门课叫做《The Missing Semester of Your CS Education》。大概意思是,在你的CS课程中可能错过了一个小学期的内容。大家也可以去搜索、了解一些材料。
前面我说到了科研实践涉及的第三部分,其实就是具体要做科研,你要找到一位科研导师。因为你可能是对某一个方向感兴趣,那这个方向可能有不同的老师在做这方面的研究。寻找科研导师非常重要,因为导师有很多条件,不只是他有经验、能给你分享问题,另外在实验室还有一些条件、设备、计算资源等各个方面。同时,一般来说导师还有学生,比如研究生,他们也可以共同指导你。所以,寻找科研导师是个很重要的事情。
当然,你要实践。只有在实践当中,你才能更好地学*,学到真东西。这叫“Learn by Doing”,要边做边学,这样学得牢。这和课堂的学*非常不一样,所以这点也特别重要。

主要就是这些。我们都知道“站得高,望得远”这句名言。如果你看得远,是因为你站在巨人的肩膀上。确实是这样,不管说是巨人还是多高的人,总的来说,你能站在他的肩膀上,就能够看得更远,这是非常重要的。我们每一个阶段的科研实践、经历,其实都是帮助我们不断站得更高,看得更远。所以,寻找这样的肩膀变得特别重要。

这样的肩膀其实挺多的。除了我们经常可能认为的导师,其实还有很多,你都可以站在他们的肩膀上。例如:
- 导师的合作者。不只是导师,还有导师的合作者,比如他来访问等。
- 导师课题组的学长学姐。他们都能给你一手的经验或指导。
- 你的同学。三人行必有我师,这一点都不假。所以,如果你有一个很好的同学团队,相互之间其实可以学到很多。
- 来访的杰出讲者、学者。你还可以把眼界放开一点。
- 国际会议上的同行。如果你后来慢慢能够去参加一些国际会议,那么在国际会议上就能认识到同行、领域里非常优秀的学者。


一个很重要的事情是,如果你未来更多地投入到科研当中,你会发现每一个方向都会有它的一个社区(Community)。在国内,就是所谓的专业委员会,比如做图形的有图形方面的专业委员会。在国际上,ACM等都有相应学科方向的社区。当然,会议本身就构成了一个社区,一去参会,见到的都是你的同行。这样的同行就是一个社区,非常有意义。你都可以从他们身上学到很多经验。



我特别强调在科研当中交流特别重要,就是各种各样的交流。当你有想法,或者想法不成熟的时候,你需要跟人家交流,把这个想法变得更清楚。当你有了一个比较清楚的想法,但是怎么解决,或者说提出一个很有意义的问题怎么去解决,你也需要跟大家交流。其实不是说你有成熟的想法和解决方案才去跟人家分享,不是这样的。分享是发现问题、发现解决方案的过程,它绝对不是最后结果的呈现。
所以,如果和本科生谈科研,或者说在我的科研实践课上,我应该用的最多的精力就是鼓励大家交流,帮助大家建立一个交流的意识。这其实非常非常重要。它和课堂的学*不一样,课堂学*就是你认真听讲,然后自己能够独立完成作业就很好。其实,即使在课程学*当中,交流也变得很重要。而且,我是鼓励随时随地的交流。
这里故意用了两张照片,其实就是说我们吃饭的时候也可以交流。吃饭的时候不只是说点娱乐、八卦,有些时候如果你在科研状态,其实也可以聊一聊科研中遇到的一些问题。可能在吃饭的时候突然有些灵感,在一种放松的状态。还有就是在走廊上碰着,我们叫“Hallway Conversation”。走廊上走着走着碰着,“我最*有什么好的想法跟你交流一下”,两人就聊上了。三言两语就把你脑子里面的问题描述一下。其实,所有这些都特别重要,对科研很有帮助。这样的一些小的行为,其实并不小。
如果说科研,那么我就说到,实际上做科研需要一个很好的环境,也需要一个好的文化。但你要说环境和文化可能是现有的,但是实际上,科研的环境和文化是每一个身处其中的人都能够参与去构建或改变的。这方面,我也特别愿意跟大家分享一下。
下面有几页PPT,也是我喜欢拿来讲述的。在我们科研的文化、意识上,有些时候我们需要打开我们的一些既有的思维和行为方式。这样的一些方式是对你的科研有益的。要打开这些方式,我们首先认识到一些不足,或者说有些对比。






这里我借用了一位在德国的华裔女生用非常形象的插画(Illustration)来表达东西方文化的不同。这个还挺有点幽默性,然后又还挺形象。我可以分享几个,她做了好多。
她是说,我们首先生长的环境可能会不大一样。在我们的成长过程中,每个人从小朋友长大,得到的关注(Attention)可能会不一样。
然后,作为一个个人的自我认同、自我的认定和感受,在不同的社会文化环境下也会有所不一样。哪边是哪边,你们可以一眼就能感受得到。
还有就是说,我的导师、我的老板到底在这个群体中间是什么样的一个位置,它是不一样的。这个不一样也决定了你思维的时候,你是什么样的一个态度。你是不是能够主动地表达你的想法,是不是能够甚至挑战老师的或者有经验的人的想法或质疑,可能也都会多少会影响到。
当然还有我们的生活方式,就是你是什么都喜欢一个人行动,还是你喜欢独立的行动,还是说什么事情大家都一样,不要我跟别人不一样等等,这些也都是。
当然还有在一些交流、社交的场合,大家的可能行为方式也不一样。是大家围成一个圈比较有序,还是我很自由地去探索,三三两两,碰到熟人打个招呼等等。
这是一个比较形象的表达,不一定说那么准确,但是这样的一个对比,


也确实让我们意识到文化的不一样。这个当然就不多说了。
还有就是大家的社交关系等各个方面。还有就是说出现意见的时候,是直接的表达,还是通过很复杂的方式的表达。其实在科研上,我是建议大家能够直接地表达自己的想法,非常重要。
出现了问题,我们是直接去解决问题,还是绕过一些问题,还是说我们*惯性地绕开问题,还是我们*惯性地面对问题、解决问题,其实这都是一些从思维上的*惯。










对这个就还有很多。这个女生还是挺有创造力的,很有创意,她有很多方面的插画。
榜样的力量或者说团体的力量很重要。各位同学如果有意进入到科研当中,你就要寻找同伴,寻找志同道合的人,也就是跟优秀的同学学*。这样的话,你就能够不断地形成你自己的一个群体。优秀的人互相之间学*,就可以更快的进步。这个其实也挺重要的。
在我实验室,我们有一个比较常态的本科同学来实*的传统。应该说,基本上每年我们都有本科同学参与实*。他从大二暑期可能正式开始,然后进入实验室做研究,最后他的研究成果也得到发表。就是有一批的同学都是这样。我就大概地来过一下:
- 已经在读博士的吴仁迪同学。
- 现在在读博士的李星宇同学。
这些都是在读博士,但是在他们的本科期间,这里显示的都是他们本科期间就发表的,像我们领域里SIGGRAPH的论文。 - 当然还有万佩卓同学、温一佳同学、冠希同学、孙宇晨(现在都是在读博士)、徐文同学、刘文哲同学、叶开同学(就是我们这个课的老师之一)、金瑞同学(大家也看到他们的照片了,一会会见到真人)、陶凌霄同学。
这些都是在本科期间在实验室做科研实践。他们非常积极地参与科研,





也有一些成果。总的来说,科研还是一个很有意思的事情,它和课堂的学*还是不大一样的。我觉得本科阶段如果说你有兴趣,也能够去,也有精力进入到科研当中,当然就说大家能来上这个课,应该也是表明了同学们可能是对科研还是感兴趣,所以应该是个很好的开始。
总的来说,一个良好的心态做科研,在各个方面锻炼自己,跟导师打交道,跟同学打交道,跟学长学姐打交道,发现问题解决问题,怎么样*衡学*和科研,怎么样在你繁忙的学*之上又做科研,一个自己的时间安排、时间管理,还有你自己其他的很多的兴趣你也要去探索,各种事情之间的一个*衡等等。还有在这个科研实践、科研的当中,我前面提到的要跟人交流,要讲述自己的工作等等,其实是一个非常非常有益的事情。大家倒不是说把科研最后的结果先放在一边,这个过程本身是一个非常受益的。
所以,我就欢迎同学们加入到这个课程的学*当中,能够从这里开始,也是建立一个Community,然后未来能够享受科研。好吧,我的介绍就到这儿。接下来我就把这个麦克风交给金瑞同学,来对接下来的课程做介绍,然后开始我们这个课程正式的分享。






谢谢大家。







课程安排与目标




好的,现在大家应该可以看到我的PPT的屏幕了。行好,也非常感谢陈老师的介绍。我在开始正式内容之前,先给大家介绍一下这门课的一些安排。




我们这门课是GAMES002,名称叫做“图形学研发基础工具”。后面的课程将都由叶开和我来讲。如果大家有什么问题的话,这个PPT上有我们俩的联系方式(邮箱),可以给我们发邮件。
我们这门课有一个网站,网站地址和直播间的地址都在这里。时间就是从今天开始的每周二晚上,从晚上七点开始,一般来讲会讲一个小时左右。如果内容比较多的话,可能会讲到一个半小时。

另外,我们这门课有一个QQ群,大家可以扫码加入一下。后续如果有什么问题,也欢迎大家在群里讨论。



接下来我介绍一下作业的提交方式。我们有一个作业系统,大家可以点到第二个链接里面去注册一下。之后的作业就在这个作业系统里面提交。注册的时候,它会要求你填一下你的邮箱,请大家用自己的常用邮箱去注册,因为最终的奖品发放以及成绩评定都是跟你的邮箱相关的。

OK,现在接下来内容可能会有点挡PPT,所以我就先把摄像头关一下。OK,现在应该可以看清楚。






作业安排和评分有这样几条:
首先,我们这门课总共会安排大概四次作业,每次作业都是以书面报告的形式为主的。如果说某次课会留作业的话,就会在相应课程的末尾给出,提交入口也会在当日开放。今天我们是不会留作业的,但是下一周应该会留第一次作业。
所有作业以及作业系统注册的截止日期(DDL),都会设在这门课结课之后的一周。我们是在5月28号结课,所以这些DDL都设在了6月4号晚上23:59分。
后面是评分的一些要求。每次作业的占分比例会在布置时给出,不一定每次作业占比都一样。接下来我们会根据作业算一个总分,并进行排名。如果总分一样的话,就会按照提交的时间,如果提交得更快,就会把你排到更靠前的位置。奖品是待定的,大家可以期待一下。



这门课程的目的主要有这么两点:
- 填补你专业课的空缺,能够学会使用工具来提升你的效率。我们学计算机的学生都知道,专业课其实并不会介绍太多工具怎么使用这种保姆级教程,顶多是给你丢一个链接让你自己去学。那我们这门课就是给大家稍微带一个引导,也不一定会讲得非常全面,但是可以作为一个引子,会提供一些资料,让大家不至于无从下手。
- 另外,就是希望大家如果能够在这门课之后,利用一些工具去更加提升你的科研效率或者你*时做事情的效率,那这门课就很成功了。




需要注意一下,我们的授课对象是刚接触计算机或者图形学的学生。所以对一些大佬来讲,这个课程内容可能就会过于简单了。我们介绍的也都是一些很基本的、大家基本上都会日常大量运用到的工具。






后面是我们这门课程的主要内容大纲。我们主要会讲九次课,时间是每周二,但是会在劳动节跳过一周。其中的前七节,其实跟图形学不一定直接相关,只要是计算机专业的,可能都会用到这些内容。后面两节课才是跟图形学更相关一些,会介绍图形学里面最常用的一些做科研需要用到的软件。

我们这门课前几节有部分内容是参考MIT的一门课程,叫做《The Missing Semester of Your CS Education》。但是这个网站里面是全英文的,这个课件在课后也可以在网站上找到,这些链接都是可以点进去的。大家如果很好奇,就可以点进去看一下。



好的,那我们就正式开始。稍稍等一下,如果大家有问题的话,可以在弹幕提出对课程安排上的问题。
(关于录播、证书等问题的解答略)
行,哦,看上去没有更多的问题了。好,没有更多的问题我就继续了。是这样的,因为我现在这个直播的设备不是特别方便看弹幕,所以大家如果有问题的话,尽量把问题攒到最后,讲完之后一起发弹幕。好吧,因为讲课过程中我也很有可能看不到你们弹幕,所以就大家凑合一下。


Linux系统介绍

接下来我们进入正式的内容。今天讲的是Linux和Shell。


首先来介绍一下Linux系统的概念。Linux是一群开源的、基于Linux内核的类Unix操作系统集合。说起来比较绕,这里面主要有几个关键词:第一个是开源,第二个是Linux内核,第三个是类Unix,最后是一个操作系统的集合。

- 开源的意思就是说,Linux的核心代码你都是可以找到、可以使用的,如果你有条件也可以跑、也可以去修改它。开源社区你都是可以去做出一些贡献的,都没有问题。左侧的图片展示出来就是Linux这个系统它的代码的GitHub仓库。
- 第二个词是Linux内核。什么叫内核呢?我们可以看右边这样一个图,一个分层的图。如果学过计算机操作系统的同学应该就会很熟悉。其实计算机系统可以理解为一层一层的抽象,一层抽象包着一层抽象,形成一种橘子皮不断往里剥,或者说是一种套娃的结构。
- 最中心的部分是直接的硬件(Hardware)。
- 直接控制硬件的外面一层叫做内核(Kernel),也叫系统内核。这些内核是直接跟硬件打交道的,所以可以想象这些内核其实是非常不适合让人类来直接操作的,它相当于像机器语言一样复杂。
- 所以我们就需要再次对它进行一个封装,这个封装就到了Shell。Shell的中文意思其实就是一层“壳”,它是一层连接外界和内部的壳。它的作用就是和Kernel去打交道,打交道的方式就是通过一行行的命令行来去做的。你一般会打开一个Shell的窗口(一个终端窗口),然后输入一系列的命令,让计算机去做一些事情。这个窗口其实就是运行了一个叫Shell的程序。
- 但是这个Shell也并不是我们

GAMES002-图形学研发基础工具 - P2:Windows + CMD + PowerShell 🖥️
在本节课中,我们将要学*Windows操作系统及其两个常用的命令行工具:CMD和PowerShell。我们将了解Windows的基本概念、CMD和PowerShell的核心区别与使用方法,并通过实例演示帮助初学者快速上手。
Windows操作系统概述
上一节我们介绍了Linux系统及其Shell,本节中我们来看看Windows系统。Windows是由微软开发、经营和销售的一系列图形化操作系统家族。它与Linux最大的区别在于它不是开源的。
Windows操作系统家族主要分为以下几类:
- Windows 9x系列:如Windows 95、98,以发布年份命名,现已非常古老。
- Windows NT系列:NT代表“New Technology”,是现代Windows系统(如Windows 10/11)的内核基础。
- Windows IoT系列:专为嵌入式设备和物联网设备设计。
- Windows Server系列:安装在服务器上的Windows系统。
我们个人电脑上常用的系统,如Windows XP、7、10、11,都属于Windows NT家族。
为什么使用Windows?
以下是Windows系统的主要优点和应用场景:
- 图形化界面强大:提供了非常直观和交互性强的用户界面。
- 用户基数大,软件生态丰富:许多软件,尤其是在图形学领域,主要或仅支持Windows*台。
- 兼容性强:在硬件和软件方面都有很好的兼容性。
- 简单易用:图形化操作降低了使用门槛。
- 应用场景广泛:在图形学研发和日常使用中极为常见。
访问Windows系统通常有以下几种方式:
- 购买预装电脑:大多数品牌电脑已预装正版Windows。
- 自行安装:购买系统后,使用启动盘进行安装。
- 使用虚拟机:在现有系统(如macOS或Linux)上通过虚拟机软件运行Windows,常用于测试或运行来源不确定的程序,起到隔离保护作用。
CMD 与 PowerShell:核心区别
在深入用法之前,我们必须明确一个核心不等式:CMD ≠ PowerShell。它们是两个独立且设计理念不同的命令行工具。
- PowerShell不直接执行CMD命令。你在PowerShell中输入CMD命令能工作,是因为PowerShell提供了“别名”机制,将一些PowerShell指令映射到了CMD命令的简写形式上。
- PowerShell更强大。CMD能做的几乎所有事情,PowerShell都能做,反之则不然。
- 设计理念不同:
- CMD:设计用于简单的批处理任务,如文件操作。其数据传递基于字符串。
- PowerShell:是一种功能强大的脚本语言和Shell环境。它面向对象,可以在命令间传递结构化的对象,而不仅仅是文本。它支持复杂的脚本,并且跨*台。
总的来说,PowerShell在功能、可扩展性和现代性上远超CMD。但对于一些非常古老的Windows系统,可能只支持CMD。
启动与基础命令格式




大家可以跟随操作,启动电脑中的CMD或PowerShell。建议先启动CMD进行练*。
启动方法有多种:
- 通过开始菜单搜索“CMD”或“PowerShell”。
- 按
Win + R键,输入“cmd”或“powershell”后回车。 - 在文件夹中,按住
Shift键并右键点击空白处,选择“在此处打开PowerShell窗口”。

CMD命令格式
CMD命令行的格式与Linux Shell相似:
命令 [参数1] [参数2] ...
参数可以是以下三种之一:
- 文件名
- 路径:Windows路径使用反斜杠
\连接,盘符开头(如C:\Users)。虽然有时正斜杠/也能工作,但为保证兼容性,建议始终使用反斜杠。 - 选项:通常以斜杠
/或减号-开头,用于修改命令行为。
例如,强制删除文件的命令为:
erase /f my_fault.txt
其中 erase 是命令,/f 是“强制执行”选项,my_fault.txt 是文件名参数。

需要注意的是,CMD中空格和引号的规则不如Linux统一。例如,echo 命令会将后续所有内容(包括空格和引号)当作一个字符串原样输出,而在路径中包含空格时,又需要用引号将路径括起来。



常用CMD命令对照表
以下是一些常用CMD命令及其类似功能的Linux命令:
| CMD 命令 | 功能简介 | 类似 Linux 命令 |
|---|---|---|
cd |
切换目录 | cd |
dir |
列出目录内容 | ls |
copy |
复制文件 | cp |
move |
移动文件/重命名 | mv |
del / erase |
删除文件 | rm |
mkdir |
创建目录 | mkdir |
rmdir |
删除空目录 | rmdir |
type |
显示文件内容 | cat |
find |
在文件中查找字符串 | grep |
findstr |
使用正则表达式查找 | grep -E |


CMD同样支持输出重定向(>, >>)和管道(|),其含义与Linux中相同。




Windows权限与变量
用户账户权限
Windows账户主要分为三类:
- 管理员账户:拥有最高权限,可类比Linux的
root。 - 标准用户账户:可正常使用计算机和程序,但无法更改系统设置或安装软件。
- 来宾账户:权限最低,仅用于临时访问。
要以管理员身份运行程序(类似Linux的 sudo),只需在右键点击程序(如CMD或PowerShell)时选择“以管理员身份运行”。
文件权限:访问控制列表(ACL)
Windows通过访问控制列表管理文件权限。它是“以文件为中心”的模型,即为每个文件维护一个列表,记录哪些用户能以何种权限访问它。
查看方法:右键点击文件 -> “属性” -> “安全”选项卡。
CMD中的变量
CMD中也可以使用变量,赋值时需使用 set 命令,引用时变量名需用百分号 % 包裹。

赋值与查看:
set MY_VAR=Hello
echo %MY_VAR%
修改环境变量(如PATH):
set PATH=%PATH%;C:\MyNewPath
删除变量:
set MY_VAR=
查看所有变量:
set
使用 set 设置的变量仅在当前CMD窗口有效。要永久修改环境变量,需使用 setx 命令或通过图形界面(右键“此电脑”->“属性”->“高级系统设置”->“环境变量”)进行设置。


CMD脚本(批处理文件)


CMD脚本文件的后缀名为 .bat(batch的缩写)。

创建与运行步骤:
- 新建一个文本文件,将其后缀名从
.txt改为.bat。 - 右键点击该文件,选择“编辑”。
- 在打开的记事本中写入CMD命令。
- 保存后,双击文件即可运行,或在CMD中直接输入文件名调用。


示例脚本:创建一个循环,生成一系列空文件。
@echo off
for /l %%i in (0, 1, 5) do (
echo. > file%%i.txt
echo. > text%%i.txt
)
@echo off:关闭命令本身的回显,使输出更简洁。for /l ...:是一个循环结构。%%i:循环变量。echo. > file:echo.输出空行,>重定向到文件,从而创建空文件。

个人建议:对于复杂的自动化任务,学*使用 Python 脚本是更佳选择。Python跨*台、功能强大、生态丰富,在科研和深度学*等领域应用极广。

PowerShell 深入浅出

现在,让我们深入了解更强大的PowerShell。

Commandlets 与帮助系统



PowerShell的基本命令单元称为 Commandlet,其命名采用“动词-名词”格式,如 Get-Process、Set-Variable,可读性很强。
Get-Verb:显示所有可用的动词。Get-Command:列出所有内置的Commandlet。Get-Help:获取帮助。例如Get-Help Get-Command可以查看Get-Command的详细用法。
命令格式与参数


PowerShell命令也支持参数,带名称的参数以减号 - 开头。
Get-Command -Name *Process
此命令会列出所有名称以“Process”结尾的Commandlet。* 是通配符,表示匹配任意字符。

面向对象与管道



PowerShell是面向对象的。管道传递的是对象,而不仅仅是文本。使用 Get-Member 可以查看对象的属性和方法。
Get-Process | Get-Member
这条命令先获取所有进程对象,然后通过管道 | 传递给 Get-Member,从而显示进程对象的所有成员信息。
别名(Alias)
PowerShell通过别名机制兼容了许多CMD和Linux命令。使用 Get-Alias 可以查看别名映射。
Get-Alias gm
这会显示 gm 是哪个Commandlet的别名(结果是 Get-Member)。
Get-Alias -Definition Get-Member
这会显示 Get-Member 这个Commandlet有哪些别名。
PowerShell 脚本
PowerShell脚本文件的后缀名为 .ps1。
创建和编辑方式与 .bat 文件类似。以下脚本实现与之前CMD脚本相同的功能:
for ($i=0; $i -le 5; $i++) {
New-Item -Path "file$i.txt" -ItemType File
New-Item -Path "text$i.txt" -ItemType File
}
运行脚本需要在PowerShell中执行,且通常需要指定路径:
.\script.ps1
注意:默认情况下,PowerShell可能禁止运行脚本。如果需要,可以管理员身份运行PowerShell,并执行 Set-ExecutionPolicy RemoteSigned 来修改执行策略(需谨慎)。
再次建议:尽管PowerShell非常强大,但对于大多数开发者和研究人员,Python 因其极致的跨*台性和在数据科学、机器学*领域的统治地位,通常是更优先的学*选择。
本节课总结
本节课中我们一起学*了:
- Windows操作系统:了解了其家族构成、优点以及访问方式。
- CMD与PowerShell的核心区别:明确了它们是不同的工具,PowerShell在功能和设计上更为先进。
- CMD基础:学*了命令格式、常用命令、变量操作以及如何编写简单的批处理(
.bat)脚本。 - PowerShell基础:认识了Commandlet、面向对象特性、管道、别名机制,并了解了如何编写PowerShell(
.ps1)脚本。 - 实用建议:对于自动化脚本任务,推荐使用 Python 作为更通用、更强大的工具。

通过本节学*,你应该能够在Windows环境下熟练使用命令行工具完成基本的文件操作和系统配置,并为后续的图形学开发工作打下基础。

GAMES002-图形学研发基础工具 - P3:远程控制工具+命令行环境 🖥️🔧
在本节课中,我们将学*如何使用远程服务器进行高性能计算,并掌握通过命令行环境进行远程控制的基础方法和工具。课程内容涵盖远程服务器的连接、文件操作、程序运行以及一些能提升效率的实用工具。
第一部分:远程服务器介绍 🚀
远程服务器,或称云服务器,是由服务供应商提供的远端计算设备。用户可以通过网络连接直接使用这些设备进行高性能计算,而无需自行管理和配置硬件。
在图形学与智能科学领域,最常见的云服务器是运行Linux操作系统的主机。这些主机通常具备强大的硬件配置,例如32GB以上的内存、多核CPU处理器以及用于密集CUDA计算的NVIDIA显卡。

云服务器的来源主要有两种:
- 一种是通过租赁方式,从腾讯云、阿里云、华为云等厂商按需(如按周、按月或包年)租用。
- 另一种是科研实验室或个人自行购置服务器硬件,并自行维护,将其放置在固定地点(如实验室),然后远程连接使用。



无论来源如何,我们都需要解决一个核心问题:如何从远程位置方便地使用这些服务器。这正是云服务器的意义所在——只要有网络,即可随时随地访问计算资源。
第二部分:远程控制基础方法 🔌
由于云服务器通常没有图形界面,我们需要借助命令行和一些辅助软件来完成操作。这主要包括终端的连接与退出、文件传输与管理,以及程序的运行与监控。
终端连接与退出
连接云服务器最基础的方式是通过SSH协议在终端中进行。无论是Windows还是Mac系统,通常都自带SSH命令。
连接命令的基本格式如下:
ssh -p [端口号] [用户名]@[服务器地址]
例如:
ssh -p 22 user@162.105.xxx.xxx
执行命令后,系统会提示输入密码或使用密钥进行身份验证。验证成功后,命令行提示符会从本地主机切换到远程服务器,表示连接成功。
要退出远程连接,有几种方式:
- 主动退出:在远程终端中输入
exit、logout或按下Ctrl + D组合键。 - 被动退出:网络断开或连接超时会导致连接被远程主机关闭。
需要注意的是,直接关闭终端窗口或被动退出,会导致在该终端中运行的所有进程被终止。这对于需要长时间运行的任务(如训练神经网络)是不可接受的。
终端多路复用
为了解决终端退出导致进程中断的问题,我们需要使用终端多路复用工具,如 tmux 或 screen。它们的主要功能是:在关闭远程连接后,原有进程仍能在后台继续运行,并且可以随时恢复查看。

tmux 的核心概念包括:
- 会话(Session):一个独立的终端工作环境。
- 窗口(Window):会话中的一个标签页,一个会话可以包含多个窗口。
- 窗格(Pane):窗口中可以进一步分割出的子区域。

以下是 tmux 的基本操作示例:
- 新建一个命名会话:
tmux new -s session_name - 列出所有会话:
tmux ls - 接入某个会话:
tmux attach -t session_name - 在会话内,使用前缀键(默认
Ctrl + b)加命令键进行操作,例如:Ctrl + b+d:断开当前会话(会话在后台继续运行)。Ctrl + b+s:列出所有会话并切换。
通过终端多路复用,我们可以让程序在服务器后台安全地长时间运行。
文件传输与管理
我们经常需要在本地电脑和远程服务器之间传输文件,或在服务器上直接操作文件。
对于图形化操作,可以使用支持SFTP协议的软件,如 WinSCP 或 Xftp。它们提供类似资源管理器的界面,通过拖拽即可完成文件的上传和下载。
在命令行中,我们也可以使用一系列Linux命令进行文件操作,这在某些只有终端访问权限的情况下非常有用。以下是一些常用命令:
- 列出目录内容:
ls # 简单列出 ls -al # 列出详细信息(权限、所有者、大小等) - 查找文件:
find . -name "*.jpg" # 查找当前目录下所有.jpg文件 find /path -type f -name "*.txt" # 在指定路径查找.txt文件 - 查看文件内容:
cat file.txt # 显示整个文件内容 less file.txt # 分页查看文件内容(支持上下翻页) head -n 10 file.txt # 查看文件前10行 tail -n 10 file.txt # 查看文件后10行 - 统计空间:
du -sh * # 统计当前目录下各文件夹大小 df -h # 查看磁盘剩余空间
这些命令组合使用,可以高效地完成文件查找、查看和空间管理任务。
程序运行与监控
在远程终端中运行程序与本地类似,直接在命令行输入程序名或脚本路径即可。
- 运行与中断:
- 运行Python脚本:
python train.py - 中断正在运行的程序:按下
Ctrl + C。 - 如果程序无响应,可以使用
kill命令强制终止:先通过ps aux | grep 程序名找到进程ID(PID),然后执行kill -9 PID。
- 运行Python脚本:
- 查看运行状态:
- 查看所有进程:
ps aux - 动态查看资源占用(类似任务管理器):
top(按q退出) - 查看GPU使用情况:
nvidia-smi - 查看内存使用情况:
free -h
- 查看所有进程:
输入输出重定向与管道
这是Linux命令行中两个强大且核心的概念。
-
输出重定向
>和>>:将程序的输出结果保存到文件,而不是显示在屏幕。echo "Hello" > output.txt # 将"Hello"写入output.txt(覆盖原有内容) echo "World" >> output.txt # 将"World"追加到output.txt末尾 python script.py > log.txt 2>&1 # 将标准输出和错误输出都重定向到log.txt -
管道
|:将一个程序的输出作为另一个程序的输入。ps aux | grep python # 列出所有进程,然后筛选出包含"python"的行 cat file.txt | wc -l # 统计file.txt的行数 -
命令链
&&:只有前一个命令成功执行(返回值为0),才会执行下一个命令。make && ./program # 先执行make编译,如果成功,再运行./program
第三部分:远程控制工具 🛠️
对于初学者,直接使用命令行可能门槛较高。幸运的是,有一些集成了图形界面的远程控制工具可以大幅简化操作。
Visual Studio Code (VS Code)

VS Code 是一款强大的免费代码编辑器,通过安装 Remote - SSH 扩展,可以变身为一站式远程开发环境。
其优势包括:
- 无缝连接:在VS Code内直接连接远程服务器,界面与本地操作无异。
- 集成终端:在编辑器内直接打开远程服务器的终端。
- 文件管理:以图形化方式浏览、上传、下载、编辑远程文件。
- 插件生态:可以利用VS Code海量的插件(如代码高亮、智能提示、Markdown预览等)来增强远程开发体验。
配置步骤简述:
- 安装VS Code和 Remote-SSH 扩展。
- 点击左下角「远程连接」图标,选择「连接到主机」。
- 输入SSH连接命令(如
user@hostname),按提示操作即可。
其他工具
- Xshell:一款功能强大的专业SSH客户端,提供标签式终端管理、丰富的安全功能等,但有商业许可限制。
- MobaXterm (Windows):集成了终端、X11服务器、文件传输等多种功能的一体化工具。

对于大多数用户,VS Code 的免费、开源、插件丰富和体验流畅等特点,使其成为入门和日常使用的首选。
第四部分:命令行环境详解 💻
当我们输入命令时,实际上是和 Shell(壳层)进行交互。Shell是用户与操作系统内核之间的桥梁,它解释我们的命令,并指挥系统执行。
命令行参数



在图形界面中,我们通过点击菜单和复选框来配置程序。在命令行中,则通过 命令行参数 来实现。
python train.py --data_dir ./data --epochs 50 --batch_size 32 --lr 0.001
上面的 --data_dir、--epochs 等就是命令行参数,它们告诉 train.py 脚本应该如何运行。在编程时(如C/C++的 main(int argc, char *argv[]), Python的 argparse 库),我们可以解析这些参数。

增强型Shell:Zsh与Oh My Zsh
默认的Bash Shell功能基础。Zsh 是一个功能更强大的Shell,而 Oh My Zsh 是一个社区驱动的、用于管理Zsh配置的框架,它提供了大量开箱即用的功能和主题。
安装Oh My Zsh后,你将获得:
- 智能命令补全与历史:输入命令时按
Tab键,可以自动补全命令、参数和文件路径。按上下箭头可以快速查找历史命令。 - 目录快速跳转:输入目录的前几个字符,即可快速跳转,无需输入完整路径。
- 丰富提示信息:在提示符中显示Git仓库状态、当前时间、虚拟环境名称等。
- 海量插件:通过插件扩展更多功能,如语法高亮、自动建议等。
配置Zsh和Oh My Zsh通常只需几条命令,网上有丰富的中文教程可供参考。
总结 📚
本节课我们一起学*了远程服务器和命令行环境的核心使用技能。
我们首先了解了远程服务器的概念和用途。接着,深入探讨了远程控制的基础方法,包括通过SSH连接终端、使用tmux进行会话管理以保持进程运行、进行文件传输与操作,以及运行和监控程序。我们还介绍了强大的重定向和管道操作。
然后,我们看到了如何利用 VS Code 等现代工具,以更直观的图形化方式完成上述大部分操作,这极大地降低了初学者的入门门槛。
最后,我们解析了命令行环境的工作原理,并介绍了功能强大的 Zsh 和 Oh My Zsh,它们能通过智能补全、历史记录和丰富提示等功能,显著提升命令行工作效率。

掌握这些工具和方法,你将能够高效、灵活地利用远程计算资源,为图形学及其他领域的研发工作打下坚实的基础。在实践中遇到问题时,善用搜索引擎和社区资源(如Stack Overflow、GitHub、相关QQ群),是快速成长的关键。

GAMES002-图形学研发基础工具 - P4:Git+GitHub - GAMES-Webinar - BV1cC411L7uG


Git 简介




Git 是一个开源的分布式版本控制系统,用于跟踪文件的变化和版本管理。它最初是为 Linux 内核开发,但现在已经广泛应用于各种项目和协作中。
版本控制
版本控制是跟踪文件变化和版本的历史记录。它可以帮助我们:
- 追踪文件修改:了解每个版本中文件的变化。
- 回溯历史:在出现错误时,可以回溯到之前的版本。
- 协作:允许多人同时工作,并合并他们的更改。
Git 功能
- 版本管理:自动追踪每个文件的修改,并记录版本历史。
- 差异比较:比较不同版本之间的差异。
- 分支管理:创建和管理多个开发分支,以便并行工作。
- 分布式协作:允许多人同时工作,并合并他们的更改。
Git 工作原理
Git 将项目目录分为三个部分:


- 工作区:包含所有文件,可以直接编辑。
- 暂存区:包含已修改但未提交的文件。
- 版本库:包含所有提交的版本历史。
基本操作
- git init:初始化一个新的 Git 仓库。
- git status:查看当前工作区的状态。
- git add:将文件添加到暂存区。
- git commit:提交更改到版本库。
- git log:查看提交历史。
GitHub 简介
GitHub 是一个基于 Git 的在线代码托管*台,提供以下功能:
- 代码托管:存储和管理代码仓库。
- 版本控制:跟踪代码更改和版本历史。
- 协作:允许多人同时工作,并合并他们的更改。
- 社区:与其他开发者交流和分享代码。
GitHub 功能
- 代码克隆:从远程仓库克隆代码到本地。
- 代码提交:将本地更改提交到远程仓库。
- 分支管理:创建、合并和删除分支。
- 代码审查:审查代码更改并提出反馈。

多人协作
Git 支持多人协作,以下是一些关键概念:
- 分支:独立的开发线,可以并行工作。
- 合并:将一个分支的更改合并到另一个分支。
- 冲突:当两个分支同时修改同一文件时,会发生冲突。
总结

Git 和 GitHub 是强大的工具,可以帮助我们更好地管理代码和协作。通过学* Git 和 GitHub,我们可以提高开发效率,并与其他开发者更好地合作。

GAMES002-图形学研发基础工具 - P5:编译工具和包管理环境 🛠️
在本节课中,我们将要学*图形学研发中至关重要的两类基础工具:编译工具和包管理环境。我们将分别介绍C/C++的编译构建工具(如make、CMake、xmake)以及Python的包管理工具(如Conda、pip),并简要探讨性能探查的重要性。掌握这些工具将帮助你更高效地管理项目、处理依赖和优化代码。
编译型语言与解释型语言
上一节我们介绍了课程概述,本节中我们来看看编程语言的两大类型。C和C++是编译型语言,需要通过编译器将源代码转换为可执行的二进制文件。相比之下,Python等解释型语言则通过解释器实时将代码转换为机器码执行,虽然速度较慢,但带来了便捷性。
在Windows上,我们通常使用本地IDE(如Visual Studio)进行编译。然而,在服务器环境或需要跨*台协作时,我们往往需要通过命令行进行编译。一个简单的编译命令如下:
g++ main.cc -o main
这条命令使用g++编译器将main.cc源文件编译成名为main的可执行文件。
为什么需要构建系统?
直接使用命令行编译简单项目是可行的。但当项目变得复杂,例如需要链接第三方库(如OpenGL)时,编译命令会变得冗长且难以管理。此外,不同的机器可能安装了不同版本的编译器或库,导致相同的命令无法复现。
为了解决这些问题,我们需要使用构建系统。构建系统通过读取配置文件,可以在不同的操作系统和编译器环境下,自动化地完成编译、链接等任务。它还能智能地检测代码变更,只重新编译更新的部分,从而提高效率。
Make:基础的构建系统
在Linux环境下,最常用的构建系统之一是make。它通过读取名为Makefile的配置文件来执行构建任务。
一个Makefile文件的基本结构由目标、依赖和命令三部分组成。以下是其基本格式:
目标: 依赖项
命令
例如,一个用于编译LaTeX文档的Makefile片段可能如下:
paper.pdf: paper.tex references.bib
pdflatex paper.tex
这表示要生成paper.pdf,需要先准备好paper.tex和references.bib这两个文件,然后执行pdflatex命令。
对于之前那个简单的C++编译例子,对应的Makefile可以写成:
main: main.cc
g++ main.cc -o main
make还支持一些特殊目标,例如:
make install: 将编译好的文件安装到系统路径。make clean: 清理编译生成的文件。
常见的make用法包括:
make: 执行默认的构建任务。make -j4: 使用4个线程并行构建以加快速度。make -C build: 在build目录下执行构建。
然而,手动编写复杂的Makefile仍然非常繁琐,尤其是对于大型项目。
CMake:跨*台的构建配置工具
为了简化构建配置,我们通常使用CMake。CMake本身不是一个构建系统,而是一个构建系统生成器。它根据CMakeLists.txt配置文件,生成对应*台的原生构建文件(如Makefile或Visual Studio项目文件)。
一个典型的CMake使用流程如下:
mkdir build
cd build
cmake ..
make
首先创建一个独立的build目录以保持源码清洁,然后在该目录下运行cmake生成构建文件,最后使用make进行实际编译。
一个最简单的CMakeLists.txt文件示例如下:
cmake_minimum_required(VERSION 2.8)
project(HelloWorld)
add_executable(hello hello.cpp)
这段代码指定了CMake最低版本、项目名称,并声明从hello.cpp生成一个名为hello的可执行文件。与手写Makefile相比,CMake自动处理了许多编译细节。
使用CMake管理多文件项目
当项目包含多个源文件时,CMake可以方便地管理编译和链接。例如,一个项目包含sqrt.cpp(定义函数)和main.cpp(主函数),CMakeLists.txt可以这样配置:
cmake_minimum_required(VERSION 2.8)
project(MyProject)
add_library(sqrt_lib sqrt.cpp)
add_executable(main main.cpp)
target_link_libraries(main sqrt_lib)
这里,add_library将sqrt.cpp编译成库,add_executable创建可执行文件,target_link_libraries将两者链接起来。
使用CMake查找和链接第三方库
对于第三方库,CMake提供了find_package等命令来简化配置。例如,查找并链接Eigen库(一个只有头文件的库):
find_package(Eigen3 REQUIRED)
include_directories(${EIGEN3_INCLUDE_DIR})
find_package会查找系统中安装的Eigen3,并设置相关变量(如EIGEN3_INCLUDE_DIR),我们只需将其包含到头文件路径中即可。
此外,还可以结合Vcpkg等跨*台包管理器。安装库后,Vcpkg会提供对应的CMake配置语句,直接复制到你的CMakeLists.txt中即可使用。
xmake:现代化的构建工具
xmake是一个集构建、包管理于一体的现代化C/C++构建工具。它使用Lua脚本作为配置文件,语法更加简洁直观。
一个基础的xmake.lua配置文件如下:
target("hello")
set_kind("binary")
add_files("src/*.cpp")
这四行代码就定义了一个从src目录下所有.cpp文件生成可执行文件的目标。xmake还内置了包管理、编译器管理等功能,并且兼容Vcpkg。它甚至可以直接生成Visual Studio项目:
xmake project -k vs2019
运行构建也极其简单:
xmake
xmake的设计更符合现代开发*惯,但在生态和复杂场景支持上可能不如CMake成熟。
构建工具对比与选择

我们来总结一下这三种工具:
- make:基础,配置灵活但繁琐,了解其概念即可。
- CMake:当前事实标准,跨*台能力强,是发布项目的首选。
- xmake:新兴工具,配置简单,集成度高,适合快速上手和个人项目。

对于初学者,建议从CMake学起,它是目前最流行、资源最丰富的工具。
Python包管理与虚拟环境
讲完了C/C++的编译,我们来看看Python的包管理。Python作为解释型语言,没有编译环节,核心在于管理第三方包和环境。
Conda是一个强大的包管理和环境管理工具。MiniConda是它的一个轻量级发行版。安装后,默认会有一个base环境。
为了提高国内下载速度,建议配置清华源等国内镜像。配置好后,基本的包管理命令如下:
conda install package_name # 安装包
conda install package_name=1.0 # 安装指定版本
conda remove package_name # 卸载包
conda list # 列出已安装包
conda clean -a # 清理缓存
另一个常用的包管理工具是pip,其命令与Conda类似:


pip install package_name
pip install -r requirements.txt # 根据文件安装
pip uninstall package_name
pip list
虚拟环境

虚拟环境是Python开发中的核心概念。它是一个独立的目录,包含特定版本的Python解释器和一套软件包。不同项目可以使用不同的虚拟环境,从而避免包版本冲突。



使用Conda创建和激活虚拟环境的命令如下:
# 创建名为myenv的虚拟环境,并指定Python版本
conda create -n myenv python=3.9
# 激活环境
conda activate myenv
# 退出当前环境
conda deactivate
你也可以指定虚拟环境的安装路径,以节省系统盘空间。

在实际开发中,我们经常需要复现别人的项目。通常项目会提供一个requirements.txt文件,列出了所有依赖包及其版本。我们可以按以下步骤设置环境:
conda create -n project_env python=3.8
conda activate project_env
pip install -r requirements.txt
注意:尽量避免在同一个Conda环境内混用conda install和pip install,以免引起依赖冲突。建议主要使用其中一种。

性能探查简介
最后,我们简要探讨一下性能探查。无论是C++还是Python项目,优化代码性能都是重要的一环。性能探查工具可以帮助我们定位代码中的瓶颈。
对于C/C++项目,可以使用IDE自带的性能分析器(如Visual Studio Profiler)。它会以表格或图表形式展示各个函数的CPU时间占比,帮助你找到最耗时的部分。
对于Python项目,可以使用内置的cProfile模块,或者结合第三方可视化工具(如snakeviz)。cProfile可以生成详细的性能报告,而line_profiler工具甚至能分析到每一行代码的执行时间。
例如,通过性能分析,你可能会发现某一行检查元素是否在列表中的代码if item in my_list:占用了93%的时间。这提示你,对于频繁的成员检查,应该使用集合set而不是列表list,因为集合的查找时间复杂度是O(1)。
性能探查的意义在于:在开发初期,我们应优先关注实现功能的正确性和开发效率;在功能稳定后,再利用性能工具进行优化,从而在长期迭代中节省大量时间。
高效编码实践建议
从性能探查引申开来,以下是一些高效编码的实践建议:
- 准备与阅读代码库:利用IDE的跳转、调试功能阅读他人代码,不要只当纯文本阅读。
- 快速起步与验证:初期使用最高效的方法(如Jupyter Notebook)验证想法,快速迭代。
- 延迟优化:先确保功能正确,再使用性能分析工具进行优化。
- 善用调试工具:使用IDE进行交互式调试,必要时保存中间结果与标准输出对比。
- 记录与计划:记录实验参数,明确开发计划和瓶颈,*衡时间与精力。
总结与作业
本节课中我们一起学*了图形学研发中的核心工具。我们介绍了C/C++的构建系统演进:从基础的make,到广泛使用的CMake,再到新兴的xmake。我们也探讨了Python的包管理工具Conda和pip,以及虚拟环境的重要性。最后,我们简要了解了性能探查的意义和基本方法。
掌握这些工具,将为你后续的图形学项目开发打下坚实的基础。
作业二
本次作业共22分,其中6分为选做。
第一题(8分):请在A(C++)和B(Python)中任选一题完成。
- A (C++):编写一个简单的“Hello World” C++程序,并为其编写一个
CMakeLists.txt文件,使其能够通过cmake和make命令成功编译运行。 - B (Python):安装MiniConda或Anaconda。创建一个新的虚拟环境(例如名为
test_env),在该环境中使用pip安装numpy和matplotlib包。最后,列出该环境下所有已安装的包,并截图。
第二题(8分):请在A(C++)和B(Python)中任选一题完成。
- A (C++):实现一个简单的排序算法(如冒泡排序)。生成一个包含大量随机整数的数组,对其进行排序,并记录排序时间。尝试使用性能分析工具(如
gprof或IDE内置工具)分析代码热点。 - B (Python):实现一个简单的排序算法。生成一个包含大量随机整数的列表,对其进行排序,并记录排序时间。使用
cProfile模块分析代码性能,并简要说明分析结果。
第三题(选做,6分):
- A (C++):尝试使用
xmake构建一个简单的C++项目,体验其配置和构建过程。 - B (Python):尝试更复杂的虚拟环境操作,例如:导出当前环境的
requirements.txt文件;根据该文件在另一个新环境中复现安装;使用conda env remove删除一个虚拟环境。
提交要求:将代码、命令、运行结果截图等内容整理到一份PDF文档中提交。
截止时间:6月4日晚上。

如有疑问,请参考课程主页加入QQ群进行讨论。


GAMES002-图形学研发基础工具 - P6:代码编译器与笔记软件使用技巧 📝💻
在本节课中,我们将学*两类在图形学研发中至关重要的工具:代码编辑器和文档写作工具。我们将详细介绍 Vim、Visual Studio Code、Markdown 和 LaTeX 的核心概念与基本使用方法,帮助你提升开发与写作效率。
集成开发环境概览

集成开发环境的主要功能是提供一个编写代码的*台,并集成了编译、运行、调试等其他工具。
以下是截至2023年最热门的集成开发环境用户数量统计。Visual Studio Code 处于断层第一的位置,用户基数最大。同为微软开发的 Visual Studio 排名第二,它是一个功能更强大、更完整的集成开发环境。今天要介绍的 Vim 也较为常见,排名靠前。此外,PyCharm 和 Jupyter Notebook 是 Python 开发者常用的专业工具。
以下是常用代码编辑器对用户的吸引力统计图。每个扇形代表一个代码编辑器,每条半透明的边代表用户正在使用 A 但对 B 感兴趣的数量。边越粗,代表数量越多。我们可以主要关注 Visual Studio Code、Vim 和 Neovim。Neovim 是 Vim 的更新版本。
从 Visual Studio Code 指出的边代表用户正在使用它但对其他软件感兴趣。指向 Visual Studio Code 的边代表用户正在使用其他软件但对它感兴趣,也包括只对它感兴趣的高粘性用户。
更有意思的是 Neovim。从 Visual Studio Code 指向 Neovim 的边代表用户正在使用 Visual Studio Code 但对 Neovim 感兴趣。而所有使用 Neovim 的用户都只对自己这一个软件感兴趣,不会去看其他的。可以看出,*惯使用 Vim 这类风格编辑器的用户,可能很难接受其他编辑器。Vim 只有一条较细的边指向 Visual Studio Code,这是因为 Visual Studio Code 可以配置成与 Vim 操作几乎一样,所以还能符合 Vim 的操作*惯。
Vim 编辑器 🧑💻
上一节我们介绍了集成开发环境的概况,本节中我们来看看经典的命令行编辑器 Vim。接下来的内容适用于 Neovim 和 Vim。
介绍 Vim 有两个目的。第一,如果你对 Vim 非常感兴趣,欢迎你以本 PPT 为引子,继续查阅文档并尝试熟悉它。需要提醒的是,Vim 的学*过程可能很困难,会很慢。第二,对于对 Vim 不感兴趣的同学,你们需要了解几点。第一是 Vim 的模式,它就像一个五状态的自动机,你们要清楚状态之间如何切换。第二很重要,你们要知道打开 Vim 后如何退出。如果完全不会使用,有时可能避免不了打开 Vim,那时如果连退出或保存都不知道怎么做,就会陷入很麻烦的困境。
Vim 的特点是使用广泛且历史悠久。它比图形化界面的操作系统历史还要悠久,可以完全运行于命令行,也只能运行于命令行。例如,现在远程 SSH 连接到一个没有图形化界面的服务器时,大概率要用到 Vim。当然也有一个更接*现代编辑器的叫做 Nano,功能不如 Vim 强大,但可能更接*现在的编辑器。Vim 确实非常难以学*,但如果能熟练使用,收益非常高。一般来讲,使用 Vim 的用户开发速度和打字速度都会比使用其他软件的开发者快一些,效率要高一些。
Vim 还有一个大特点,就是它可编程,有高度的自由性,可以进行高度的个性化配置。可配置的东西包括但不限于重映射键盘、设置*惯的快捷键、以及高亮和缩进等规则。Vim 的配置文件类似于之前讲过的 Bash 的 .bashrc,Vim 也有一个叫做 .vimrc 的东西。如果是 Neovim,会不太一样,它叫做 init.vim。总之,一些更高级的设置都可以在配置文件里设置。另外,Vim 也会借助大量第三方插件来实现编辑文本之外的功能。
在此之前,需要让大家看一下并记住右下角这样一张图。这个图代表 Vim 有五种状态。最常用的状态也是 Vim 刚打开时所处的状态是中间的 Normal 模式。在 Normal 模式下,键盘上敲入的字符不会直接插入,而是被识别成命令,作为自动机转换的转移条件。例如,敲一个大写的 R,就会从 Normal 切换到 Replace 状态。Replace 状态主要是用来替换一段长文本的。还有一个 Insert 状态,最接*现代文本编辑器的常态。在 Insert 状态下,输入字符会真正在光标处插入字符,像正常编辑文本一样。右下角的是 Visual 状态,用于选择一段文本,好比用鼠标框选一段内容。还有一个叫做 Command Line 状态,即命令行模式。在这个状态下,可以输入字符,这些字符会被解析成命令,让 Vim 编辑器去做指定的事情。



Normal 模式
我们接下来一个状态一个状态来讲。首先是 Normal 模式。
在 Normal 模式下,上下左右移动可以用 K、J、H、L 这四个键代替。大家可以看一下键盘上 H、J、K、L 是连续一排的四个键,这与上下左右键的分布不太一样。使用这四个键移动是因为 Vim 的宗旨是尽可能不用鼠标,并让你尽可能少用键盘上不易触及的区域。它让你所有工作都能在数字键、常规字母键、ESC 以及一些普通快捷键(如 Shift、Control)这些手指最容易够到的地方完成。使用旁边的小数字键或上下左右箭头被认为是浪费时间。这就是 Vim 的哲学之一。另外两个哲学是区分模式和高度可配置。

在 Normal 模式下,是用 H、J、K、L 这四个键进行光标移动的。不只有这四个基础移动,还有其他花式跳转,例如跳到单词的开头结尾、括号的左半部分和右括号、行内段落间或上下翻页等。现代编辑器里的高级功能 Vim 里都有,它们对应 Normal 模式下的不同按键。具体按键在 PPT 里有讲,不一一念了,大家可以在课后看一下,也可以照着实践一下。




Insert 模式

接下来介绍 Insert 模式,即插入模式。

进入插入模式的方法是在 Normal 模式下按小写 i 键。进入 Insert 模式后,做的事情就是在当前光标位置进行编辑。还有一些其他花式进入 Insert 模式的方法,例如先跳到行首再进入 Insert 模式,实现方法是用大写的 I,即 Shift + I。还有一些其他先移动光标再进入 Insert 模式的方式也在 PPT 里。此外,有一些比较简单的文本修改,可以不需要进入 Insert 模式,只在 Normal 模式下完成。例如,在 Normal 模式下按小写 s 是删除当前字符再进入 Insert 模式。等价的按小写 x 操作是只会删除当前字符,不会进入 Insert 模式。类似还有一些其他按键可以让你更快地进行简单编辑操作,无需模式切换。
Replace 模式

还有一个 Replace 模式,按大 R 进入。它主要用于长文本的替换,这里没有详细介绍,因为它确实不是一个很常用的模式。一般我们最常用的模式还是 Insert 模式,用来编辑文本。
Visual 模式
接下来要介绍的是 Visual 模式,即 Vim 里的选择文本。
在 Visual 模式下,用移动键(包括上下左右键以及在 Normal 模式下的高级移动键)移动光标,光标移动道路上会框选住所有经过的文本。进入 Visual 模式有三种方式。第一种是按小写 v 进入,代表移动光标经过的字符被选中。如果按大 V 进入,相当于是光标经过的行整行都被选中。如果是 Ctrl + V 进入,它就是列选择模式。
在选中之后还处在 Visual 模式时,可以有两种选择。一个是按 y 表示复制选中的内容。按 y 之后,Visual 模式将退出,回到 Normal 模式。剪切 d 也是同理。在复制或剪切完之后,回到 Normal 模式,再按一下 p 就可以在当前光标位置进行粘贴。
我们可以稍微比较一下 Vim 的操作和现代文本编辑器的操作。现代文本编辑器如果我想复制一段话到文本的开头,就是用鼠标框选住一段话,然后按 Ctrl + C,再用鼠标点一下开头,然后 Ctrl + V。如果不想用鼠标,就是用上下左右键先移动到想框选的位置,把它框选住,然后 Ctrl + C 完后,可以按一下 Home 键或 Ctrl + Home 键回到整个文档的开头,再按下 Ctrl + V 进行粘贴。但是在 Vim 里面,首先需要在 Normal 模式下把光标移动到框选的开始位置,然后进入 Visual 模式,移动光标直到把所有想复制的文本都框选下来,再按一下 y,然后退回 Normal 模式。在 Normal 模式下,再移动光标到文本的开头,这个时候再按一下 p。这就是 Ctrl + C 和 Ctrl + V 的全部过程。所以 Vim 与*时操作的一个最关键区别在于,*时的快捷键是通过一系列组合键来实现复杂性,而 Vim 的复杂性在于状态机,在于模式之间的切换。所以你会看到熟练使用 Vim 的人经常会把 ESC 敲烂,因为他们经常需要回到 Normal 模式。最常用的回到 Normal 模式的按键就是 ESC。
Command Line 模式
最后一个 Vim 的状态叫做命令行模式。




命令行模式是通过在 Normal 模式下输入冒号 : 进入的。命令行模式会在整个终端窗口的最下面一行显示一行,开头是冒号,接下来输入的所有字符都会在冒号后面显示。这一串字符会被解析成你要 Vim 干的指令。最常见的命令有几个,接下来的命令是大家需要记住的,即使你不使用 Vim 也要记住这些。

首先,输入 w 再回车,表示保存文件。w 后面跟一个叹号 ! 和 sudo %,意思是用 sudo 权限去保存文件。也就是说,如果你没有以 root 身份打开 Vim,而你又编辑完了文本,不想退出再重新编辑,可以用这个方式,用 root 权限直接去保存。还有这三个是等价的:wq、x、大写的 ZZ 都是保存并退出的意思。q! 和大写的 ZQ 表示的是退出但不保存。上面这四条大家需要记住,即使不用 Vim,也要记住,这样至少知道怎么保存、怎么退出。接下来后面这些是一些更高级的命令,大家自己看就可以了。
Visual Studio Code 编辑器
上一节我们深入了解了 Vim,本节中我们来看看目前最流行的现代化编辑器 Visual Studio Code。
首先我们要明确两个概念,那就是 Visual Studio Code 和 Visual Studio 这两个其实没有什么太大关系,是两个完全不同的概念。Visual Studio 是一个功能更强大、更完整的集成开发环境,它有一套完整的工具链,包括编译器、链接器、调试程序、单元测试等所有功能都集成在一个程序里。这就是为什么安装 VS 可能会安装一整天,但安装 VS Code 可能 2 分钟就可以下完安装完。这是因为 VS Code 其实就是一个轻量的文本编辑器,它只做文本编辑的功能。VS Code 其实更像一个浏览器。VS Code 如果打开它的属性对话框,可以看到有一个 chromium 属性,表示它的浏览器内核,它所用的内核跟 Chrome 浏览器和 Edge 浏览器是一样的。但是我们也会经常看到 VS Code 也可以实现非常像 VS 一样强大的功能,也可以调试、运行、编译,这都是归功于它的插件。VS Code 刚下下来是其实只带很少的插件,甚至是不带插件的。随后如果你想去编译一下,比如让 VS Code 能够编译 C++ 或高亮显示 C++ 的语法,你都需要去安装这些扩展。包括语法高亮、自动补全这些功能刚开始都是没有的。它刚下下来,你可以把它理解为它就等价于 Windows 下面的记事本。VS Code 也是一个高度可配置的软件,它的配置文件都是 JSON 格式。VS Code 和 Vim 不同,就是它比 Vim 要好用的多,基本上不需要上手,只要会打字就能用。因为它也是一个图形界面的应用程序,所以自然有易用性。
之前我们介绍过远程连接 SSH 命令,应该也介绍过 Visual Studio Code 里面怎么去运用这个命令。这里也解释了怎么去连接一个远程服务器,用 VS Code 去连接,怎么去添加一个配置,让你在第二次以及之后更多次连接的时候就不用重新配置了,可以点一个选项就直接连接上。VS Code 远程连接的一个好处就在于,你连上之后,它会弹出一个新的窗口,这个窗口就好像是你在远端服务器打开的一个 Visual Studio Code 一样。它主要体现就在于左边那个文件资源管理器里面,显示的那些目录和文件都是远程服务器上的文件系统,就好像你在本地一样去操作,但实际上你操作的是远程服务器上的内容。所以这是它的一个非常方便的一点。具体怎么做也有讲,这里就跳过了。
接下来可以给大家简单地介绍一下 VS Code 的基本设置,还有它的一个多光标的小特性。它的设置方法是在左下角的一个齿轮图标,叫做 Settings,点击它就可以进入设置界面。在这里面可以设置一些最基本的属性,比如 Tab 字符的宽度是四个还是八个还是两个,根据你的*惯。还有自动缩进是否要自动缩进,是否要自动删除行末的多余空白字符等等。这些基本的编辑器属性可以在这里设置,这些设置也是可以多设备同步的。如果是更高级的设置,可能跟插件相关。对于插件的设置,要点到 Extensions 里面,选中那个插件,在插件里面有一个齿轮图标,在那个齿轮图标里去设置就好了。插件的设置以及 VS Code 在当前工作区的设置,都是通过 JSON 文件去配置的,在 JSON 文件里去指定它的每一个属性。具体这个 JSON 文件的配置其实也有很多东西,也挺复杂的。个人建议是你遇到什么问题,就去网上搜对应的配置哪一条。如果想系统性地了解,可以去看那个文档。
还有一个小功能,就是有一个多光标的功能,在 VS Code 里面可以一下子产生多个光标。产生多光标的方法有两种,一个是按住鼠标的中键,然后拖动一个矩形区域,就会选中那个矩形区域,矩形所涉及到的所有行都会生成一个新的光标。另外一个产生多光标的方法就是按住 Ctrl + Shift + Alt 键,然后再按上下的方向键,就可以往上或者往下一行产生一个新的光标。多光标的作用就是你可以同时删除,也可以同时插入。例如,在这个图里面,这几行都是按位或 |,我想把它们全都变成按位异或 ^,就可以多光标选中,然后直接按一下异或就可以全都改过来,所以这里很方便。
Markdown 标记语言 📄
上一节我们介绍了两种代码编辑器,本节中我们来看看用于文档写作的标记语言 Markdown。
Markdown 的特点是它是一个很简单的标记语言,非常简单,是所有里面最易学的,甚至不需要学*,照着某个文档抄一两遍就会了。第二个是它是纯文本格式的,Markdown 文件的后缀名是 .md,这个 .md 文件就是一个纯文本的文件。Markdown 可以让你轻松地排版。在我们常见的用途当中,一般在 GitHub 上的 README 都是用 Markdown 来写的。Wikipedia 的页面其实也是用 Markdown 写的,包括简书一些其他的也都用它。它其实还是一个你*时做笔记以及科研记录进度的绝佳工具,因为它特别简单,不像 LaTeX 一样需要很复杂的代码。比如你想加一个公式、一句话、一个表或一个图片,不需要输入一长串代码,直接加就行了。所以 Markdown 记笔记什么的,*时用还是非常方便的。
教程有两个,第一个是官方教程,它是更系统更全面的。第二个交互式教程,大家可以在我 PPT 发下去的时候点进去看一下,它非常有意思。它是一个网页,它会一步一步地指导你了解每一个语法,它会让你自己去输入那个语法,去了解它是怎么工作的,交互性比较强。
哪些编辑器可以用 Markdown 呢?其实首先任何一个纯文本编辑器都可以用,都可以去写一个 Markdown,但只不过是它能不能渲染的问题。比如用 Vim、用 Notepad 其实都可以写 Markdown,因为它就是一个文本文件。如果你用 VS Code 的话,你需要装一下 Markdown 的插件,才可以做到实时的渲染,就像图片里所展示的一样。另外 Markdown 官方还有一个编辑 Markdown 的软件叫做 Typora,这个软件应该是要付费的。但是这个软件它渲染出的 Markdown 效果比较好看,它也是编写实时渲染的,它不是像 VS Code 一样左边分栏,左边是源代码,右边渲染结果。它是说你直接编辑,它就可以直接给你预览当前编辑的效果,就相当于是直接在一个渲染好的地方上面去修改。
以下是 Markdown 的一些常用语法:

- 标题:用
#来表示,#的个数越多,表示标题的级别越低,字体越小。# 一级标题 ## 二级标题 ### 三级标题 - 强调:用两个
*框住一段文本表示加粗,用一个*框住表示倾斜,用三个*框住表示又加粗又倾斜。**加粗文本** *倾斜文本* ***加粗倾斜文本*** - 换行规则:敲两个换行才是一个真正的分段。如果只敲一个换行,是段落内的换行。有些渲染器会直接忽略单个回车,将前两行并成一行。
- 列表:前面加一个短横线
-代表无序列表的开始。列表可以嵌套,通过缩进来表示层级。- 项目一 - 子项目一 - 项目二 - 复选框列表:
- [ ]表示未选,- [x]表示已选。- [ ] 任务一 - [x] 任务二 - 公式:用美元符号
$框起来。单个$是行内公式,两个$是行间公式。公式语法是 LaTeX 语法。行内公式:$E = mc^2$ 行间公式: $$ \int_a^b f(x)dx $$ - 引用:用
>加空格表示引用。多个>可以实现嵌套引用。> 这是一段引用。 >> 这是嵌套引用。 - 代码块:行内代码用一个反引号
`括起来。行间代码块用三个反引号 ``` 括起来,并可在后面指定语言。
行内代码print("Hello, World!") - 超链接:格式为
[显示文本](链接地址)。还可以使用锚点链接到文档内其他位置。

GAMES002-图形学研发基础工具 - P7:网站搭建基础 - GAMES-Webinar - BV1cC411L7uG
网站搭建基础
在本节课中,我们将学*网站搭建的基础知识,包括Web服务的原理、前端和后端的概念,以及本地IP和公网IP的Web服务应用。
Web服务原理
Web服务背后有一套复杂的机制,包括用户操作、浏览器处理、服务器处理和响应等步骤。以下是一个简化的Web服务流程:


- 用户在浏览器上操作,如点击按钮或输入文本。
- 浏览器将用户操作发送到服务器。
- 服务器处理请求并返回响应。
- 浏览器将响应渲染成网页显示给用户。

公式:



Web服务 = 用户操作 + 浏览器处理 + 服务器处理 + 响应
前端和后端
Web服务可以分为前端和后端两部分:

- 前端:负责用户界面和交互,使用HTML、CSS和JavaScript等技术实现。
- 后端:负责处理业务逻辑和数据存储,使用服务器端语言和数据库等技术实现。

代码:




<!-- 前端代码 -->
<!DOCTYPE html>
<html>
<head>
<title>示例网页</title>
</head>
<body>
<h1>欢迎来到我的网站</h1>
<p>这是一个简单的网页示例。</p>
</body>
</html>



# 后端代码 (Python)
from flask import Flask, request, render_template



app = Flask(__name__)

@app.route('/')
def index():
return render_template('index.html')

if __name__ == '__main__':
app.run()





本地IP和公网IP的Web服务


- 本地IP的Web服务:部署在本地机器上,只能在本机访问。
- 公网IP的Web服务:部署在公网上,可以由任何机器访问。


应用场景:
- 本地IP的Web服务:用于开发调试、可视化展示等。
- 公网IP的Web服务:用于个人主页、项目展示、在线服务等。
实例



以下是一些Web服务的实例:



- 本地IP的Web服务:使用Python的Flask框架搭建一个简单的Web服务,用于展示三维模型。
- 公网IP的Web服务:使用GitHub Pages搭建个人主页。






总结

本节课介绍了网站搭建的基础知识,包括Web服务的原理、前端和后端的概念,以及本地IP和公网IP的Web服务应用。希望这些知识能够帮助您更好地理解和开发Web服务。



GAMES002-图形学研发基础工具 - P8:三维处理软件 - GAMES-Webinar - BV1cC411L7uG
大家好,今天是我们Games002课程的倒数第二节课。
三维处理工具概述
在本节课中,我们将学*三维处理工具,这些工具在图形学中用于几何处理、渲染和重建。

几何处理软件
Cloud Compare



Cloud Compare是一款开源软件,主要用于点云处理,也支持网格处理。它具有以下特点:
- 开源:C++源码可在线获取。
- 图形化界面:提供插件和自定义插件。
- 功能:点云显示、变换、过滤和分割。
MeshLab
MeshLab是一款开源软件,主要用于网格处理,也支持点云处理。它具有以下特点:


- 开源:C++编写,提供Python API。
- 功能:网格处理、简化、图包计算等。
- 可视化:网格显示、编辑和操作。
Open3D
Open3D是一个C++代码库,提供Python绑定,支持各种3D算法,包括点云处理、网格处理和计算几何算法。
渲染工具
Blender

Blender是一款开源的渲染工具,具有以下特点:

- 开源:Python编写,提供图形化界面和Python API。
- 功能:复杂的渲染系统,支持各种功能。
- 学*资源:官方手册、教程和社区。
其他渲染工具
- Houdini:电影特效和游戏特效工具。
- KeyShot:真实感渲染工具。
几何重建工具
CodeMap
CodeMap是一个开源的重建工具,用于从图像中恢复3D模型。它支持稀疏重建和稠密重建。

Context Capture
Context Capture是一个商业软件,用于户外场景重建。它提供图形化界面和Python API。
总结
本节课介绍了三维处理工具,包括几何处理软件、渲染工具和重建工具。选择合适的工具取决于具体需求和项目情况。
作业
- 作业一:对Stanford的Bonding模型进行网格简化。
- 作业二:使用任意软件对模型进行渲染。
- 作业三:提交PDF文件,包括代码或操作示意图。
课程群

如有问题,请加入课程群进行交流。

GAMES002-图形学研发基础工具 - P9:仿真渲染软件 🎬
在本节课中,我们将学*如何使用两款重要的仿真渲染软件——Houdini和ParaView。它们是在图形学科研中,用于结果预览和高质量渲染的关键工具。我们将通过两个具体例子,分别介绍它们的基本使用方法。
概述 📋

在图形学科研过程中,最终通常需要展示3D结果,无论是通过视频还是模型。从算法生成3D模型或序列,到最终渲染出漂亮的结果,一般会经历以下流程:生成模型 -> 可视化预览 -> 调试算法 -> 最终渲染。其中,可视化预览和最终渲染是展示成果的关键步骤,而手动编写3D可视化代码非常繁琐。因此,我们需要借助专门的软件工具。

本节课将分为两部分,通过两个例子分别介绍Houdini和ParaView的基本用法。

第一部分:使用Houdini渲染流体动画 💧
Houdini是一款功能强大的3D动画和视觉特效软件。本节我们将通过渲染一个水滴动画的例子,学*其基本操作流程。这只是Houdini功能的冰山一角,更多强大功能可在其官网的“Learning Paths”中探索。
Houdini界面介绍
打开Houdini后,界面主要分为几个区域:
- 场景视图:最大的区域,用于预览3D场景。
- 参数视图:右上角区域,用于调整物体、光照、相机等参数。
- 节点网络视图:右下角区域,Houdini通过拖拽和连接节点模块来构建工作流。
- 播放控制条:底部区域,用于播放动画序列。

第一步:添加与导入物体

我们的目标是导入一个水滴粒子序列和一个圆锥体。
首先,在节点网络视图中,按下 Tab 键,输入 geometry 创建一个几何容器节点。双击进入该节点,在其内部再次按下 Tab 键,创建一个 file 节点用于导入文件。



在参数视图中,配置 file 节点的文件路径。对于动画序列,文件名通常包含帧数变量 $F,例如 surface_particles.$F.obj。Houdini会自动将其替换为 0001, 0002 等序列号,加载所有OBJ文件。



OBJ文件是一种描述3D模型的文件格式,可以存储三角网格的顶点坐标和连接关系。粒子数据可以视为只有顶点坐标、没有连接关系的OBJ文件。
导入文件后,可以在场景视图中看到粒子。如果场景有多个物体(例如还有内部粒子),可以使用 merge 节点合并多个 file 节点的输出。
接着,用同样方法添加一个圆锥体(cone节点),并在参数视图中调整其半径、位置和高度。
至此,基础场景搭建完成,点击播放控制条可以预览粒子动画。
第二步:将粒子转化为流体表面
预览的粒子需要转化为连续的流体表面。思路是将每个粒子扩展为一个小球,然后计算这些小球合并后的表面。
在粒子数据节点后,添加一个 particle to vdb 节点。其核心参数包括:
point radius scale:控制每个粒子扩展成球的半径。太小会导致表面不连续,太大会导致模型穿模。voxel size:体素网格大小,应小于粒子半径以保证表面精度。

添加 smooth 节点对粗糙表面进行*滑处理,可调整*滑强度和迭代次数。
最后,添加 convert vdb 节点,将体数据(vdb)转换为多边形网格(polygon mesh),以便渲染。将其参数 convert to 设置为 Polygon。
完成这步后,场景视图中的粒子就变成了连续的流体表面。
第三步:赋予材质

接下来为表面赋予材质,使其看起来像水。
在节点网络视图切换到 Material Palette 标签。从材质库中拖出 glass(玻璃)材质,模拟水的透明特性。同样,可以拖出 gold 材质给圆锥体。
选中 glass 材质,在参数视图中调整其属性以更像水:
IOR(折射率):改为1.33Reflection(反射率):改为0.2Transparency(透明度):改为0.8Emission(自发光):改为0.04,颜色调为淡蓝色。
配置好材质后,切换回物体层级(obj标签)。选中水滴对应的几何节点,在参数视图的 Render 标签页,将 Material 属性指定为刚才创建的 glass 材质。同样为圆锥体指定 gold 材质。

第四步:设置相机与光照
渲染场景需要相机和光照。
添加相机:在场景视图右上角点击 No cam,选择 New Camera。勾选 Tie View to Camera or Light,此时移动视图视角会同步调整相机参数。调整好视角后,取消勾选即可。

添加光照:在节点网络视图中,按 Tab 输入 light,选择 Environment Light(环境光)。在参数视图中可以调整光照强度、颜色等。环境光可以选择是否在渲染背景中可见。
第五步:渲染与输出

Houdini主要有两种渲染器:Mantra(CPU,功能全面)和Karma(支持GPU加速)。
使用Mantra渲染器:
- 在节点网络视图切换到
out标签,按Tab添加mantra节点。 - 在参数视图的
Images标签页,设置输出图片的路径和格式(如PNG)。 - 在
Rendering标签页,调整渲染质量参数,如Pixel Samples(每像素采样数,值越高质量越好越慢)。 - 切换到
Render View标签,点击Render按钮预览单帧效果。 - 要渲染序列,在
mantra节点参数中,将Render从Current Frame改为设置帧范围(如1-100),然后点击Render to Disk。
使用Karma渲染器:
- 将顶部选项卡从
Build切换到Solaris。 - 在节点网络视图(
stage标签)添加Scene Import节点,在参数中填入需要导入的物体、相机、光照名称(用空格分隔)。 - 添加
Karma Renderer节点,它会自动创建渲染设置节点。将场景导入节点与之连接。 - 在渲染设置节点的参数中,指定输出路径、相机,并将
Render Engine改为XPU以启用GPU加速。 - 在
Solaris界面右上角可切换到Karma XPU Render视图进行实时预览。 - 点击渲染设置节点下的
Render to Disk渲染序列。

上一节我们介绍了如何使用Houdini渲染流体动画,本节中我们来看看如何使用ParaView进行体数据可视化。
第二部分:使用ParaView可视化体数据 🌪️



ParaView是一款开源的科学可视化软件,特别擅长处理大型体数据。本节我们将通过可视化一个涡环碰撞的速度场例子来学*其基本用法。


什么是体数据可视化?


体数据(Volumetric Data)指在三维空间定义的数据场,例如流体的速度场、温度场。在计算机中,它通常被离散化为一个三维网格(体素),每个格点存储一个值(标量或向量)。
常用的可视化方法包括体渲染、等值面提取和切片可视化。ParaView在医学成像、材料科学等领域有广泛应用。
第一步:准备数据
我们的例子是可视化两个碰撞涡环的涡量场大小。涡量是描述流体旋转快慢的物理量,我们可视化其模长。
假设我们已通过模拟获得一个三维NumPy数组 vorticity_magnitude,形状为 (XN, YN, ZN),表示在空间范围 [x_min, x_max] x [y_min, y_max] x [z_min, z_max] 内的涡量大小。
需要将其转换为ParaView可读的VTI格式文件序列。以下为Python代码示例(依赖VTK库):
import vtk
import numpy as np
# 假设 vorticity_magnitude 是形状为 (ZN, YN, XN) 的numpy数组
data = vorticity_magnitude.astype(np.float32)
# 创建VTK图像数据对象
image_data = vtk.vtkImageData()
image_data.SetDimensions(XN, YN, ZN) # 注意维度顺序
image_data.SetSpacing((x_max-x_min)/(XN-1), (y_max-y_min)/(YN-1), (z_max-z_min)/(ZN-1))
image_data.SetOrigin(x_min, y_min, z_min)
# 将numpy数组数据导入VTK
flat_data_array = data.flatten()
vtk_data_array = vtk.vtkFloatArray()
vtk_data_array.SetNumberOfComponents(1)
vtk_data_array.SetArray(flat_data_array, len(flat_data_array), 1)
image_data.GetPointData().SetScalars(vtk_data_array)
# 写入VTI文件
writer = vtk.vtkXMLImageDataWriter()
writer.SetFileName(“vortex_ring_001.vti”) # 按帧命名
writer.SetInputData(image_data)
writer.Write()
对每一帧数据重复此过程,生成序列文件(如 vortex_ring_001.vti, vortex_ring_002.vti)。
第二步:导入与基础可视化
打开ParaView,将生成的所有VTI文件拖入主窗口区域,或通过 File -> Open 选择多个文件。文件会以“组”的形式出现。
在左侧 Pipeline Browser 选中导入的数据组,点击眼睛图标使其可见。此时场景中可能只显示一个包围盒。
在上方工具栏,将可视化方式从 Outline 改为 Volume。此时,体数据将以体渲染方式显示,颜色映射表示涡量大小(通常红色高,蓝色低)。在视图窗口中拖动鼠标可以旋转视角。
点击工具栏的播放按钮,可以流畅播放整个动画序列。
第三步:增强渲染效果(阴影与光照)
为了得到更逼真的渲染效果(如阴影),需要进行以下设置:


- 添加地*面:导入一个作为阴影投射面的*面OBJ文件(一个简单的正方形网格)。在ParaView中打开该OBJ文件并显示。
- 启用光线追踪:在左侧
Properties面板底部,找到Ray Tracing部分,勾选Enable Ray Tracing和Shadows。启用后交互会变慢。 - 添加自定义光源:在
Properties面板取消默认Light Kit的勾选。通过Sources菜单添加一个Directional Light(*行光)。在光源属性中,设置Light Position和Focal Point以定义光线方向,从而产生阴影。

第四步:导出动画
场景配置完成后,通过 File -> Save Animation 导出。
在保存对话框中,设置文件名和路径。Suffix Format 使用类似 %04d 的格式,会生成 file_0000.png, file_0001.png 等序列图片。
最后,使用FFmpeg等工具将图片序列合成为视频文件。FFmpeg命令示例:
ffmpeg -framerate 30 -i file_%04d.png -c:v libx264 -pix_fmt yuv420p output_video.mp4
总结 🎓


本节课我们一起学*了两种重要的仿真渲染软件。
- 我们首先学*了 Houdini,通过渲染流体动画的例子,了解了其节点式的工作流程,包括导入几何体、将粒子转化为表面、赋予材质、设置光照相机,以及使用Mantra或Karma渲染器进行最终渲染。
- 接着,我们学*了 ParaView,通过可视化涡环体数据的例子,掌握了其导入体数据、进行体渲染、添加光照阴影以增强效果,以及导出动画序列的基本方法。




这两款软件功能都非常强大,本节课仅介绍了入门知识。鼓励大家在课后积极实践,并查阅官方教程和文档以探索更多高级功能。在图形学的学*和科研中,熟练使用这些工具对于高效地展示和验证你的工作成果至关重要。


GAMES003-科研基本素养 - P1:L01-课程内容概览&建立领域视野 🎯

课程概述
在本节课中,我们将学*《科研基本素养》这门课程的整体框架,并深入探讨科研入门的第一步:如何建立对一个研究领域的宏观视野。这门课程由四位讲者共同讲授,旨在为低年级或刚入门科研的同学提供一套系统的方法论,帮助大家理解科研流程,克服入门困难,并获得持续的正反馈。
课程动机与目标
许多同学在开始科研时,缺乏明确的研究策略,导致过程困难重重,容易因长期碰壁而失去热情。本课程的目标是教授基本的科研方法论,涵盖从选题、设计方法、实验迭代到论文写作与宣传的全流程。我们希望同学们通过学*,能够提升科研能力,并在科研过程中获得快乐和成就感。
课程整体安排
本课程内容分为两大部分。
第一部分:科研流程详解
我们将逐步拆解一个科研项目从零到一的全过程,并结合案例进行说明。科研流程主要包含以下三大方面:
- 初始化科研课题:建立领域视野、选择具体课题、设计技术方案。
- 迭代技术方案:基于方案设计实验、根据实验结果提升方案。
- 论文写作:规划写作、梳理故事、绘制图表、撰写文字、应对评审。
第二部分:科研软技能分享
在课程的最后,我们将分享一些非技术性的重要技能,例如如何做报告、培养有益的研究*惯等。
课程预期:能教什么与不能教什么
在开始学*前,明确课程的边界非常重要。
我们不能教的:
- 具体领域(如三维重建、图形学)的专有技术与范式。
- 手把手的编程指导或针对特定项目的实验分析。
- 像导师一样逐字逐句修改论文。
我们能教的:
- 一个科研项目通用的具体步骤和过程。
- 可执行的方法论指南,例如如何建立领域视野、如何选题、如何设计实验流程、如何撰写论文。
如何从本课程中最大程度受益
为了最好地从这门课中受益,建议遵循以下四个步骤:
- 学*方法论:理解科研流程中每一步的核心方法。
- 结合经验思考:根据自身的科研实践,思考并内化这些方法。
- 通过实践熟练:在实际项目中反复运用这些方法,达到熟练程度。
- 总结个人方法:在积累一定经验后,形成适合自己的研究方法论。
接下来,我们将对课程的核心模块进行简要概览。
模块一:初始化科研课题
科研的第一步是找到一个好的起点。这包括建立视野、选择课题和设计方案。
1. 建立领域视野
什么是领域视野?
领域视野是指对你所研究的特定方向(如三维重建、图像生成)的全面理解。它包含两个核心部分:
- 技术演变视野:了解该领域从早期到现在的技术发展脉络,知道有哪些里程碑式的论文和技术范式。
- 问题视野:清楚该领域的终极目标是什么,目前达到了什么水*,还有哪些重要问题未被解决,以及当前的研究热点是什么。
为什么需要领域视野?
领域视野是几乎所有科研活动的基础:
- 选题:知道哪些问题是值得解决的。
- 设计方法:了解现有技术,才能提出有效的创新。
- 迭代方法:当实验失败时,能基于领域知识分析原因。
- 故事梳理与写作:能清晰地阐述现有工作的不足和自己工作的贡献。
如何建立领域视野?
以下是建立视野的基本流程:
- 识别里程碑论文:找到领域内引用量高、开创性的论文。
- 追溯脉络:了解每篇里程碑论文的前身(它基于什么工作)和后继(哪些工作改进了它)。
- 深度阅读:仔细阅读这些论文,理解其解决的问题、流程和核心思想。
- 梳理技术史:整理出领域技术演变的轨迹。
- 预测未来:基于历史脉络,预测可能的新技术趋势。
- 梳理问题史:整理领域内关注问题的变化历程,并预测未来可能的热点问题。
2. 选择科研课题
课题选择至关重要,一个好的课题能让后续工作事半功倍。
什么是好的课题?
一个好的课题通常具备以下特点:
- 提升空间大:当前方法存在明显不足,有改进余地。
- 竞争程度适中:不是过于“内卷”的方向,避免陷入同质化竞争。
- 难度与能力匹配:任务的挑战性略高于个人当前能力,既能带来成长,又具备可行性。
- 影响力大:解决的是领域内公认的重要问题,研究群体关注度高。
如何找到好课题?
- 建立领域视野:这是基础。
- 列出潜在课题:基于视野,列出领域内尚未被解决的重要问题。
- 评估课题:判断每个课题的竞争程度、提升空间、与自身能力的匹配度。
- 追求影响力:在可行范围内,选择影响力更大的课题。


给不同阶段同学的建议:
- 初学者:建议从
well-defined task(定义清晰的任务)入手,这类任务通常有现成的基准测试和代码,便于上手学*。 - 高年级学生:可以挑战
ill-defined task(定义模糊的任务),这类任务更具探索性和开创性。
3. 设计技术方案
选定课题后,需要设计具体的技术方案(pipeline)来达到先进的性能。
为什么需要系统性思路?
虽然创新看似需要灵感,但遵循一定的流程能更有效地解决问题:
- 提高效率:清晰的思路有助于更快地找到解决方案。
- 增强动机:使方法设计更有目的性,论文故事也更自然。
- 保证创新性:避免设计出创新性不足的方法。
- 提升技术洞察:系统性的分析能加深对问题的理解。
如何设计方法?
设计方法可以遵循以下四个基本步骤:
- 分析原因(第一性原理):深入分析当前最好方法(
SOTA)效果不佳的根本原因。 - 设计方法:针对分析出的原因,设计新的技术方案。
- 判断合理性:从理论层面审视设计方案的合理性与完备性。
- 改进方法:通过讨论、简单实验等方式初步改进方案。
设计要点:
- 确保方法具有足够的创新性。
- 争取在
pipeline层面提出新范式,而非小修小补。 - 不断自问:这项工作是否为领域带来了新的知识或认知?

模块二:迭代技术方案


初步方案往往不成熟,需要通过实验进行迭代优化。
1. 设计实验
什么是实验设计?
实验设计是规划需要做哪些实验,以验证或改进我们的方法。
为什么需要设计实验?
- 简化研究:明确实验目的,让科研过程更清晰。
- 提高效率:降低实验复杂度,提升实验效率。
如何设计实验?(核心原则)
核心原则是减少单个实验中的风险点(探索点)数量。如果一个实验同时测试多个不确定的改动,失败时将难以定位问题。
- 分解流程:将复杂的
pipeline拆解为多个组件,先从可控的模块开始,逐步加入创新性模块。 - 分解实验设置:先从简单的数据集或设定开始实验,再逐步增加难度。
- 优先级排序:根据重要性对探索点进行排序,优先验证关键部分。
2. 通过实验提升方案
为什么需要改进方法?
现实情况是,第一次提出的想法往往不奏效,因此需要持续分析改进。
如何改进方法?
可以将改进过程视为一个优化问题:
- 提出想法:生成一个初始方案(
idea)。 - 评估/实验:通过实验获得反馈。
- 计算“梯度”:分析实验结果,找出不理想的原因。
- 更新“参数”:根据分析结论改进方案。
改进的具体途径包括:从文献中学*、与同行讨论、提出技术变体、进行实验并分析结果。
模块三:论文写作
论文写作是科研中套路相对清晰的部分。
论文写作的五个步骤:
- 规划写作:制定写作时间表。
- 梳理故事:构建论文的核心叙事逻辑。
- 论文画图:绘制清晰、美观的图表。
- 文字撰写:撰写各个部分的文字内容。
- 自我评审:模拟审稿人视角检查论文。
1. 写作规划
提前规划写作可以大幅提高投稿成功率并减轻压力。一个推荐的写作流程包含九个步骤,从绘制流程图、梳理故事开始,到逐步完成初稿、实验、反复修改直至定稿。
2. 梳理故事
好的论文需要一个吸引人的故事。梳理故事的有效方法是:
- 先画草图:绘制方法流程的简单草图,可视化有助于理清思路。
- 回答问题:通过回答“贡献是什么”、“有什么好处”、“带来了什么新见解”、“如何引出技术挑战”等问题来构建故事框架。
3. 论文画图
精美的图表能提升论文质感与中稿率。
绘图步骤:
- 文字描述
pipeline。 - 绘制简单的流程图草图。
- 将各个元素(点云、网格等)可视化。
- 进行配色。
- 优化整体布局。
要点:使用低饱和度、圆润的风格;元素可视化要清晰美观;配色和谐统一。
4. 撰写方法部分
在画出 pipeline 草图后,撰写方法部分会变得简单。
- 结构:每个子章节通常包含三部分:
- 具体设计:描述该模块如何工作。
- 动机:为什么需要这个模块。
- 优势:这个模块带来了什么好处。
- 描述技巧:使用“给定输入X,第一步做A,第二步做B,...,得到输出Y”的句式,可以清晰描述模块设计。
5. 自我评审
在投稿前进行自我评审,预判审稿人可能提出的问题,并提前修改,可以有效提升中稿率。可以按照一个检查清单逐项审视论文。
本节课重点:如何建立领域视野 🧭
现在,我们回到本节课的核心内容:如何建立领域视野。
什么是领域视野?
以多视角三维重建为例,一个具备领域视野的专家应该能回答:
- 该领域的技术从2015年到2024年是如何演变的?
- 该方向目前还存在哪些重要问题?
为什么建立视野?
拥有视野后,你才能更好地选择课题、设计方法、迭代方案和撰写论文。它让你能站在更高处,预见并引领技术趋势。
如何建立领域视野?(实践指南)
1. 梳理技术发展脉络:创建“时间轴-思维导图”
这是一个有效的可视化工具,帮助你理清历史。
- 步骤:
- 初始化一个时间轴(如2015-2024)。
- 将阅读过的论文按其发表时间放置在时间轴上。
- 区分里程碑论文(开创性工作)和跟进论文(改进性工作)。
- 总结每个里程碑论文提出的技术范式,以及跟进论文所做的改进。
- 示例:在三维重建领域,时间轴上可能依次出现
COLMAP(传统)、MVSNet(深度学*早期)、NeRF(神经渲染)、Diffusion Models(扩散模型应用)等关键节点。
2. 梳理问题发展脉络
记录每年该领域的研究热点,并回答以下四个问题:
- 本领域的终极目标是什么?(例如,达到三维扫描仪的效果)
- 目前该领域达到了什么水*?
- 还有哪些重要问题未被解决?
- 现阶段的热点话题是什么?
3. 有效阅读论文:使用“论文解析树”
将阅读论文转化为回答问题的过程,提高阅读效率。
- 摘要:解析出任务、技术挑战、核心贡献。
- 引言:填空式地回答背景、现有工作不足、本文贡献。
- 方法:读完能用自己的话复述第一步、第二步、第三步分别做了什么。
4. 视野的范围
视野大小应与个人能力阶段相匹配:
- 初学者:先建立一个相对具体(
narrow)的领域视野。 - 高年级学生/研究者:应努力扩大视野,寻求更宏观的理解。
视野会随着阅读、交流和思考的深入而自然扩展。
5. 额外途径
- 观看领域内资深研究者的讲座(
talk)。 - 积极与实验室同学、导师及其他研究人员交流。
实践作业 📝
为了巩固本节课所学,请完成以下作业:
选择你正在或感兴趣的一个研究方向,尝试整理一份该领域的“时间轴-思维导图”,并回答:在当前时间点,该领域仍有哪几个重要问题未被解决?
请将思维导图和文字总结整理成一份 PDF 文档提交。
总结
在本节课中,我们一起学*了《科研基本素养》课程的整体框架,并深入探讨了科研起步的关键一步——建立领域视野。我们了解了视野的内涵、重要性,并掌握了一套可操作的方法来梳理技术脉络和问题脉络。记住,广阔的视野是做出优秀科研工作的基石。从完成本次作业开始,迈出你系统化科研训练的第一步吧。


注:以上内容根据课程视频整理,保留了原讲者的核心观点与案例,并按照要求进行了结构化、简化和格式优化。

GAMES003-科研基本素养 - P2:L02-如何选择科研课题 🎯
在本节课中,我们将要学*如何选择一个合适的科研课题。这是科研旅程中至关重要的一步,一个好的开始往往能事半功倍。我们将从理解科研的本质开始,逐步探讨选题的重要性、关键考量因素以及具体的方法论。
理解科研:探索知识的边界 🌍
上一节我们介绍了课程背景,本节中我们来看看科研究竟是什么。

科研是一个探索知识和技术边界的过程。我们可以将人类已有的知识想象成一个圆圈。小学、高中、本科的学*让我们在这个圆圈内不断积累知识,形成专长。而硕士、博士阶段的科研训练,则将我们带到了这个知识圈的边缘。在这里,我们专注于一个具体的问题,通过阅读文献和实验,努力推动这个边界向外扩展,形成一个小小的“鼓包”。这个“鼓包”就是你的科研成果,它对你个人而言意义重大,但在整个人类知识体系中可能只是微小的一步。
对于初学者而言,一个常见的误区是难以从“学生思维”切换到“科研思维”。以下是两者的一些关键区别:

- 目标不同:学*是掌握人类已有的知识,而科研是创造和发现新知识。
- 路径不同:学*有固定的课程大纲和考试范围;科研则没有边界,无人为你划定重点,学什么、研究什么都需要自己决定。
- 反馈周期不同:学*通常是“一分耕耘一分收获”,努力能较快地在考试中看到成果;科研周期长且充满随机性,可能在错误的方向上付出巨大努力却得到负面结果。
- 内容性质不同:学*的内容大多是经过验证的真理;而科研处于知识前沿,没有绝对权威,需要敢于质疑和批判性思考。
因此,心态的调整至关重要。科研中的“成功”不应仅定义为做出成果,每天学到新知识、加深对问题的理解,本身就是一种进步。
选题的重要性:方向大于努力 🧭
理解了科研的本质后,我们来看看为什么选题如此关键。
杨振宁先生曾指出,学术成功最重要的因素往往不是智力或努力程度的悬殊,而在于是否“走进了有发展的领域”。这个道理同样适用于具体的课题选择。在一个正确的方向上努力,其重要性远超过在错误方向上的埋头苦干。选题决定了你整个项目的潜力和最终能产生的影响力。
选题的挑战:与不确定性共舞 🎲
既然选题如此重要,为什么它又如此困难?
核心原因在于不确定性。在课题开始时,你站在已知的边界去预测未知,未来充满变数。这种未知性会导致多种走向:
- 根本做不出来,是条死路。
- 做着做着发现更有希望的新方向,最终成果可能与最初设想不同。
- 与他人的研究“撞车”,在拥挤的领域面临激烈竞争。
- 技术范式发生重大变革(例如从GAN转向Diffusion),彻底改变课题的命运。
因此,选题是一个需要与不确定性共舞的“多人游戏”。我们需要建立正确的心态:既要能接受不确定性,知道何时该灵活调整,何时该坚持;也要通过不断学*和加深对问题的理解,来主动降低这种不确定性。
此外,寻找课题本身就是科研的重要组成部分,它并非一个单向、线性的过程,而是一个贯穿项目始终、循环往复的环节。你需要不断地在思考、实验、评估之间迭代,优化你的想法。
选题的关键考量因素 ⚖️
在具体选择课题时,需要考虑多个因素,且这件事因人而异。你需要结合自身情况(兴趣、背景、阶段、目标)、小环境(导师要求、实验室积累、合作者)和大环境(领域发展阶段、未来潜力)来综合判断。
以下是三个核心的考量因素:
1. 个人兴趣与热情 ❤️
兴趣不仅是让你在研究过程中保持开心的因素,更是一种根本的驱动力,它深刻影响着你能否坚持做出成果以及成果的影响力。
- 如何找到兴趣:首先需要了解自己擅长什么(编程、数理、动手能力)。其次,可以思考自己喜欢什么风格的问题(注重实用应用、追求新颖有趣、偏爱数学严谨)。广泛的阅读和交流有助于判断。
- 兴趣的深化:真正扎实的研究兴趣往往建立在对一个问题进行深入探索并产生深刻理解之后。当你与一个问题产生“连接”,它会持续驱动你去思考。
- 保持*常心:没有人能始终保持高昂的研究热情,起起伏伏是常态。重要的是保持好奇心和求知欲,这样你总能发现有意思的问题。
2. 课题的可行性 🔧
可行性决定了课题能否被完成。它受多种因素影响:
- 研究空间:新兴领域通常研究空间大;成熟领域则较小。可以自问:针对这个方向,我能想到多少个尚未被尝试的研究角度?
- 课题难度:这里的难度通常指相比现有工作,做出显著改进或发现新角度的难度。成熟领域或已被全面探索的角度,难度会更高。难度是可以调整的,例如通过简化问题设定或调整目标以发挥方法优势。
- 竞争程度:越受关注或越容易想到的问题,竞争越激烈。通常应避免红海,除非你有独特角度或特殊优势(如相关背景深厚、执行速度快)。
- 个人与资源:
- 个人基础:编程能力、数理基础、对领域的了解程度、对科研流程的把握能力。
- 资源与支持:能否获得足够的指导(尤其对初学者至关重要)、计算资源、数据集、反馈速度、项目时长是否匹配个人安排。
- 灵活性:当初始想法不奏效时,是否容易调整目标或回收已有成果。
给初学者的建议:初期应以培养科研技能、建立信心为目标,选择门槛低、难度适中、风险低的课题。例如,深入复现并改进一篇代码维护良好的优秀论文。对于有经验者,可以选择深挖一个方向的难点,或适当探索新方向以融合不同领域的见解。
3. 课题的影响力 🌟
影响力决定了工作完成后的关注度。一个粗略的公式是:
影响力 ≈ 领域总体关注度 × 工作的显著程度
- 领域关注度:越重要、越普适(General)、越热门的问题,关注度越高。例如,对应关系(Correspondence)、单目深度估计等都是经典的重要问题。相反,过于小众或设定具体的问题受众较小。
- 扩大影响力的方法:
- 根本在于提升工作质量。
- 更好地呈现工作:清晰的论文写作、及时开源代码、制作精良的项目主页(突出优势和可视化结果)。
- 影响力的形式:除了“有用”,“特别” 也能给人留下深刻印象,从而产生影响力。
选题的具体流程与方法 🔄
上一节我们分析了选题的各个因素,本节中我们来看看具体的实践流程。
第一步:建立领域认知与想点子(Ideation)
首先,你需要对目标领域建立基本认知,整理其发展脉络(关键论文、联系、阶段性核心问题)。寻找课题是一个“想点子(Ideation)”和“评估点子”循环往复的过程。
想点子的方法主要有三种:
-
通过阅读:
- 带着问题读:这篇工作的不足(改进机会)和优点(开启的新可能性)分别是什么?
- 不要完全相信论文结论,需交叉验证。
- 广泛阅读,进行横向比较,识别未解决或做得不好的地方。
- 阅读经典旧文章。对于经典问题,旧文中常有深刻见解。(“历史不会重演,但会押韵。”)
- 案例1:想研究长时序稠密跟踪,阅读18年前的《Particle Video》论文获得核心启发。
- 案例2:在研究过程中,通过阅读《LoFTR》论文,发现了解决技术难点(互注意力机制)的关键工具。
-
通过交流:
- 与志同道合、技能互补的同学讨论。
- 与导师或资深研究者交流,获取宏观视野。
- 甚至午餐时的随意闲聊也可能激发灵感。
-
通过实践:
- 动手跑代码,深入理解方法机理,从而发现其不足并构思改进方案。
- 有两种模式:目标驱动型(拿着钉子找锤子)和想法驱动型(拿着锤子找钉子)。最佳状态是在两者间找到匹配点。
第二步:评估与筛选点子
有了多个点子后,需要评估筛选。评估标准主要包括新颖性(Novelty)、可行性和影响力。
关于新颖性(Novelty):
这是一个主观性较强的概念。一些判断角度包括:
- 惊喜度:是否让人耳目一新、意想不到?
- 简洁与有效:简单而有效的组合往往潜力巨大。
- 合理性:看完后是否觉得“问题本就该这么解决”?
- 增量贡献:是否为领域带来了新的信息、知识或技术?
- 所有工作都可视为“A+B”,新颖性在于这个组合是否是解决特定问题最自然、最必然的方案,并且其结果具有一定不可预测性。
如何培养研究品位:
- 对标学*:记录你的想法,请尊敬的导师评分(1-10),反思差异。
- 建立直觉:关注你想到过的点子最终被他人实现的结果,与你的预期对比,训练预测模型。
- 采访他人:了解他人的研究品位、选题理由和长远愿景。
- 批判性反思:反思自己及周围环境的研究品位是否存在偏差。
评估的具体操作:
- 通过阅读/交流:判断问题是否被做过、有无成功先例。
- 通过实践:设计最简单实验验证核心想法。
- 设想影响力:如果所有难点都解决,最终效果是否足够惊艳?
执行策略与常见问题
执行策略:
- 多线程探索:同时初步尝试多个点子,再选最有希望的深入。
- 单点深入迭代:选定一个有空间的方向,在其中根据反馈不断调整和提升点子。后者更常见。
何时应该放弃一个点子?
- 存在硬伤时:核心卖点已被他人发表;核心实验验证失败;贡献度明显达不到目标会议/期刊标准。
- 陷入瓶颈时(建议不要轻易放弃):
- 尝试所有能想到的解决方案。
- 总结失败原因,判断是否致命。
- 跳出来,从宏观角度重构问题。
- 若因缺乏关键工具而卡住,可暂时搁置但保持关注,待工具出现时快速结合。
初学者常见错误:
- 对领域了解不足:在明显落后的方向上浪费时间。解决方案:多读、多问、多了解。
- 轻易放弃已有积累:在深入探索后放弃,转而做完全不相关的项目,浪费了积累的见解和技能。解决方案:尽可能对原项目进行重构,或转向能利用已有积累的相关项目。
实用建议:维护一个“点子文档”,记录所有想法、理由、做法和潜在好处。这有助于理清思路、比较点子、甚至融合产生新想法。
问答与案例分享 💬
以下是针对一些常见问题的解答和讲者的个人经历分享。
问:如何确定大方向后,关注该领域的顶尖学者和团队?
- 方法:看引用量、开源代码、性能排行榜;关注社交媒体、学术会议上的讨论。
- 注意:不要迷信“大牛强组”。在新兴领域,大家的起点可能差不多。更重要的是判断工作本身的质量。
问:如何跟上层出不穷的新技术?
- 策略:有的放矢。如果新技术与你的长期目标相关,就深入跟进;否则,保持基本了解即可。
- 另一种策略:与掌握该新技术的最强研究者合作,快速学*。但这需要权衡个人在合作中的可见度。
问:你的CVPR最佳学生论文是如何选题和完成的?
- 动机来源:源于之前工作中遇到的真实难点(运动估计)。
- 调整过程:最初目标(4D重建)太难,于是简化问题,放弃分解相机与场景运动,只保证2D投影正确性。
- 艰难时期:大半年时间效果很差,不断自我质疑,但直觉觉得“这个问题不应该这么难”,凭执念坚持。
- 突破:了解到关键工具(相关体积、稠密关联),效果有了基础,建立信心。
- 打磨:后期花大量精力在可视化、交互演示上,追求最佳呈现效果。
问:第一篇论文课题如何选定?
- 讲者经历:本科第一篇论文由导师给定方向;博士第一篇论文大方向由导师定,但具体点子需自己大量阅读、实验后打磨出来。
问:刚入门的研究生如何上手?
- 建议:从一篇优秀、代码维护好的论文出发,复现并尝试改进;或寻找有经验的合作者/导师带领。
问:实验过程中发现做不动,能否调整目标?
- 回答:当然可以,并且这很常见。讲者在CVPR工作中就经历了从“4D重建”到“2D运动估计”的目标调整和简化。
总结 📝
本节课中我们一起学*了如何选择科研课题。我们首先理解了科研是探索知识边界的过程,并区分了科研与学*思维的不同。我们强调了选题的重要性,指出方向往往大于努力。接着,我们深入探讨了选题面临的挑战——不确定性,并给出了应对心态。
课程的核心部分详细分析了选题的三个关键因素:个人兴趣(根本驱动力)、可行性(决定能否完成)和影响力(决定关注度)。我们提供了评估这些因素的具体思路。
最后,我们梳理了选题的具体流程:从建立领域认知开始,通过阅读、交流、实践来产生点子,然后从新颖性、可行性、影响力角度评估筛选点子。我们还讨论了执行策略、何时放弃、常见错误及实用建议。

记住,寻找课题本身就是科研,是一个需要耐心、不断迭代和调整的过程。希望本课能为你开启科研之路提供清晰的指引。


GAMES101-现代计算机图形学入门-闫令琪 - P1:Lecture 01 Overview of Computer Graphics - GAMES-Webinar - BV1X7411F744
概述
在本节课中,我们将学*计算机图形学的基本概念、应用领域以及课程内容安排。

计算机图形学概述
什么是计算机图形学?



计算机图形学是研究如何利用计算机技术生成、处理和显示图形信息的一门学科。它包括以下几个方面:



- 几何建模:研究如何表示和操作三维空间中的几何形体。
- 渲染:研究如何将几何形体转换为图像,包括光照、材质、阴影等效果。
- 动画与模拟:研究如何使物体运动,并模拟真实世界的物理现象。
- 可视化:研究如何将数据转换为图形信息,以便于分析和理解。
- 虚拟现实与增强现实:研究如何创建虚拟环境,并与之进行交互。
计算机图形学的应用
计算机图形学广泛应用于各个领域,包括:
- 游戏开发:例如《只狼》、《无主之地3》等游戏。
- 电影制作:例如《黑客帝国》、《阿凡达》等电影。
- 动画制作:例如《疯狂动物城》、《冰雪奇缘》等动画电影。
- 工业设计:例如汽车设计、建筑设计等。
- 可视化:例如医学图像、气象数据等。
- 虚拟现实与增强现实:例如VR游戏、AR应用等。
课程内容
本课程将涵盖以下内容:
- 光栅化:将三维空间中的几何形体显示在屏幕上。
- 几何建模:表示和操作三维空间中的几何形体。
- 光线追踪:生成真实感图像。
- 动画与模拟:使物体运动,并模拟真实世界的物理现象。
课程安排
- 作业:每周一个小作业,要求使用C++语言完成。
- 大作业:课程中段开始提供大作业想法,学生可以自行选择主题进行创作。
- 讨论*台:提供在线答疑*台,学生可以随时提问。
总结


本节课介绍了计算机图形学的基本概念、应用领域以及课程内容安排。通过学*本课程,学生可以了解计算机图形学的基本原理和应用,并掌握相关技术。

GAMES101-现代计算机图形学入门-闫令琪 - P10:几何1(引言)📐

感谢大家的支持,今天开始第十节课。这意味着今天的课程结束后,本课程就过半了。本课程正常安排是20个课时,可能会增加一节,目前进展一切顺利。
今天我们将开始一个新话题:几何。几何部分会占用几节课的时间。大家可以看到下方的四幅图,它们概括了本课程的主要内容:光栅化成像、几何(用曲线表示的蝴蝶)、光线追踪等现代化图像生成方法,以及动画与模拟。今天我们先从几何开始。
在开始之前,先补充一些课程内容。今天会先讲完上次未讲完的纹理应用部分。一些稍高级的话题,如生成阴影、实时渲染框架中的*似动态全局光照等,会放在后续讲解光线追踪时一并介绍。本课程进度比英文版快了一节,我们有时间先讲完基础知识。几何与光栅化、光线追踪都密切相关。
课程开始前,照例宣布一些事项。作业三已发布,助教更新了框架并做了一些改动,请大家在BBS查看。BBS还新增了常见问题(FAQ),可以帮助大家避免常见错误。此外,很高兴有两位新助教加入:与朋同学(北航)和郭文轩同学(浙大),他们的联系方式已公布,有问题可以咨询他们。
今天的网络状况可能不佳,但我会同步录音,后期制作视频时会使用我的录音,所以问题不大。

回顾之前的内容,我们花了两节课讲着色模型(如Blinn-Phong模型),讨论了着色位置、计算机硬件如何着色,并重点介绍了纹理映射,即用纹理定义顶点属性。上节课还讲了如何在三角形内部进行插值(使用重心坐标),以及纹理映射的挑战:纹理太小时需要插值放大,纹理太大时则需要范围查询。Mipmap是一个很好的结构,能快速进行*似的方形范围查询,它通过生成多级纹理(类似图像金字塔)来实现。有同学提到纹理尺寸不是2的幂次方怎么办,这涉及具体*似方法,本课程主要关注理论可行的2的幂次方情况。

今天我们将接着这个话题,完成纹理部分的讲解,这样着色部分就暂告一段落,之后我们再进入高级部分。
纹理的高级应用 🖼️
根据前几节课,给定一个网格,我们可以进行着色。可以做*面着色(flat shading),得到格子状效果;也可以做*滑着色(smooth shading),在顶点计算法线,在内部插值,然后在每个像素进行着色,得到*滑效果。在此基础上,可以贴上纹理,得到带有不同颜色但又有明暗变化的表面。当然,我们可以使用各种不同的纹理。
今天要介绍的一个纹理应用是:如何让一个球体反射出类似天空和地面的环境。这就是环境光照(Environment Lighting),也叫环境光映射或环境贴图。


接下来,我们将探讨纹理的其他应用,然后进入几何部分。

环境光照(环境贴图)
首先概述一下纹理。纹理本质上是一张图。纹理可以进行各种操作,如Mipmap。由于Mipmap应用广泛,现代GPU硬件都支持。在现代GPU中,纹理可以理解为一块内存,支持快速的点查询和范围查询。因此,纹理可以被看作是一块数据,可以进行不同类型的查询,而不仅限于图像。
从这个角度出发,纹理可以表示很多东西。

例如环境光照(环境贴图,Environment Map)。想象你站在一个房间里,来自四面八方的光都会进入你的眼睛,这些光可能是直接光源,也可能是反射光。如果记录下每个方向来的光,就得到了环境贴图。
左边这幅图描述了一个房间内四面八方的景象(类似展开图)。我们可以用这幅图去渲染一个物体(如茶壶),让茶壶被这个环境光照亮,即反射出来自各个方向的光。可以看到茶壶上反射出了窗户、门等结构。这说明我们可以用纹理描述整个环境光,并用它来渲染其他物体,这比单一光源效果好很多。具体计算方法后续会讲。
这里有两个要点:
- 这个茶壶叫做犹他茶壶(Utah Teapot),是图形学中广泛使用的经典模型。其他经典模型还有斯坦福兔子(Stanford Bunny)、斯坦福龙(Stanford Dragon)和康奈尔盒子(Cornell Box)。
- 使用纹理描述环境光时,有一个隐含假设:环境光来自无限远处。我们只记录方向信息,认为在活动范围内,来自同一方向的光照强度相同。如果房间较小,从不同位置看墙上同一点,方向会变化,所以严格来说光照应由方向和位置共同定义。但环境贴图简化了,只记录方向信息。

球面环境贴图与立方体贴图
如果能把环境光记录在纹理上,就可以用它渲染。另一个例子:在一个房间里放一个光滑的金属球(镜面球),它反射出的就是整个环境。这给了我们一种存储环境光的方法:将环境光记录在球面上,然后像展开世界地图一样展开它。

这就是球面环境贴图(Spherical Environment Map)。我们可以用鱼眼镜头或多张照片合成360度全景图来获取。但球面展开图存在扭曲问题,类似于世界地图上高纬度地区面积被压缩。
解决方法:不把信息存在球面上,而是存在一个立方体的表面上。从球心向球面某点连线并延伸,直到与包围球的立方体表面相交,将信息记录在立方体交点上。一个立方体有六个面,所以会得到六张图。

将立方体展开,就能得到六张环境贴图。这种方法扭曲较少。当然,查询时需先判断方向对应立方体的哪个面,但计算很快。这种方法称为立方体贴图(Cube Map)。球面贴图和立方体贴图本质都是描述来自不同方向的光照信息(包括直接光和反射光)。
凹凸贴图与法线贴图
纹理还可以做很多事,一个重要的应用是凹凸贴图(Bump Mapping)。纹理不仅可以定义颜色(如替换漫反射系数 kd),还可以定义表面上任意点的其他属性,例如相对高度。
假设一个基础表面(如球体),纹理可以定义表面上每个点沿法线方向偏移的高度。这样,在不改变几何本身复杂度(三角形数量)的情况下,通过复杂的纹理定义了每个点的虚拟高度变化。高度变化会导致法线方向改变,从而改变着色结果,产生明暗对比,让人眼感觉到凹凸,而实际上几何并未改变。
凹凸贴图(Bump Map)和法线贴图(Normal Map)原理类似,都是通过纹理改变表面法线。区别在于纹理存储的内容:凹凸贴图存储高度偏移,法线贴图直接存储法线向量。
凹凸贴图如何工作?
基本思想:通过纹理定义的高度变化来扰动法线。
- 不改变几何信息,三角形数量不变。
- 对每个像素,根据其纹理坐标查询高度图,通过计算相邻点的高度差(梯度)来得到新的法线方向。
考虑简化的一维情况(Flatland):
- 假设原本是*面,法线向上
n = (0, 1)。 - 凹凸贴图定义了一个高度函数
p = h(u)。 - 在点
p处,水*移动单位距离du,高度变化为dp = h(u+du) - h(u)(*似导数)。 - 那么该点的切线向量为
t = (1, dp)。 - 将切线逆时针旋转90度得到法线:
n' = (-dp, 1)。 - 最后将
n'归一化。

推广到二维纹理(三维空间):
- 假设局部坐标系中,原始法线为
n = (0, 0, 1)。 - 纹理坐标
(u, v)。 - 计算
u方向和v方向的高度偏导数dp/du和dp/dv。 - 新的(未归一化的)法线可*似为:
n' = (-dp/du, -dp/dv, 1)。 - 然后在局部坐标系中归一化
n',再通过坐标变换转换回世界坐标系。
这个过程在作业三的FAQ中有更详细说明。基本思想就是通过高度差计算法线扰动。

位移贴图
一个更现代的方法是位移贴图(Displacement Mapping)。它使用与凹凸贴图相同的输入纹理(高度图),但真正移动了顶点的几何位置。

对比凹凸贴图和位移贴图:
- 凹凸贴图:只改变法线,不改变几何。在物体边缘或自阴影处容易“露馅”。
- 位移贴图:真正移动顶点,效果更真实,但要求模型的三角形足够细密,以跟上纹理定义的高度变化频率。

如果模型不够细,可以在运行时根据需要将三角形细分,这称为动态曲面细分(Dynamic Tessellation,DirectX中的特性)。这样就不需要初始非常细致的模型。

纹理的应用就介绍到这里,计算可能复杂,但原理如此。
三维纹理与其他应用 🌐
之前讨论的纹理都是二维的。但纹理也可以是三维的。

例如,可以定义一个三维纹理来表示空间中的值,如大理石纹理。这种纹理通常不是预先生成的图像,而是由一个定义在三维空间中的噪声函数(如Perlin噪声)解析生成。空间中任意点 (x, y, z) 都能通过函数计算出噪声值,经过处理后可以模拟山脉起伏、大理石纹路等。
纹理还可以存储预计算的信息。例如,着色时我们尚未考虑阴影。对比左右两图,右图中眉毛在眼窝上投下了阴影。这种阴影效果可以通过预计算环境光遮蔽(Ambient Occlusion)信息,并存储在一张纹理中来实现。渲染时,将着色结果乘以这张遮蔽纹理,就能快速得到包含阴影的效果。这说明纹理可以存储各种预计算信息,而不仅仅是颜色。

三维纹理广泛应用于体渲染(Volume Rendering)。例如,在医学成像中,CT或MRI扫描得到的是三维空间中各点的密度信息。这些信息可以存储为三维纹理,用于渲染内部结构。
至此,着色部分的内容基本讲完。今天我们将开始几何部分的入门。
几何表示简介 🔷
几何在图形学中非常重要。我们将先看一些几何例子,然后介绍不同的几何表示方法。
生活中存在许多复杂几何:
- 光滑曲面:如汽车车身,*看也看不到三角形,是连续光滑的。
- 复杂机械:如发动机内的齿轮、叶片,部件繁多,形状各异。
- 布料:由纤维编织而成,具有复杂的分层结构和空隙。
- 流体:如水滴溅起的水花,形状动态变化,表面张力使其形成复杂曲面。
- 大规模场景:如城市,包含海量的建筑、树木、草地等细节。
- 微观结构:如细胞、病毒,在显微镜下几何极其复杂。
- 植物:如树木,具有分形般的自相似结构。
几何的复杂性带来了存储和渲染的挑战。图形学中,几何表示主要分为两大类:
- 隐式几何(Implicit Geometry)
- 显式几何(Explicit Geometry)
隐式几何
隐式表示不直接给出点的坐标,而是描述点满足的关系。例如,一个单位球面可以表示为:
x² + y² + z² = 1
对于空间中任意点 (x, y, z),我们可以判断它是否在球面上(代入等式,等于1则在面上)。
更一般地,隐式表面定义为满足 f(x, y, z) = 0 的所有点的集合。f(x, y, z) > 0 表示点在物体外,< 0 表示在物体内。
优点:
- 容易判断点与表面的关系(内/外/上)。
- 容易描述某些具有严格数学定义的形状。
- 容易计算光线与表面的交点(后续会讲)。
缺点:
- 难以直接看出表面的形状(所有点在哪里)。
- 难以描述复杂的、不规则的形状(如奶牛)。

隐式表示的例子
-
代数公式:直接使用数学方程,如
f(x,y,z)=0。对于简单形状(球、环)有效,对于复杂形状(如星形)公式可能很复杂且不直观。![]()
-
构造实体几何(CSG - Constructive Solid Geometry):通过对基本几何体(球、圆柱、立方体等)进行布尔运算(并集、交集、差集)来构造复杂几何。这是一种隐式表示,因为只定义了构造关系,没有直接给出所有点。广泛应用于CAD软件。
-
距离函数(Signed Distance Function, SDF):描述空间任意点到目标表面最*距离的函数。点在物体外时距离为正,在内时为负。SDF的强大之处在于可以通过融合(Blending)两个SDF来光滑地融合两个形状,非常适合描述形状的渐变和变形。
SDF融合原理:
- 有两个形状A和B,分别有SDF
d_A(p)和d_B(p)。 - 将它们融合:
d(p) = blend(d_A(p), d_B(p))。 - 新形状的表面就是满足
d(p) = 0的点集。 - 这种方法能产生自然的过渡,甚至改变拓扑结构(如两个球融合成一个)。
- 有两个形状A和B,分别有SDF
-
水*集(Level Set):与距离函数思想类似,但函数值定义在离散的网格上。通过找到函数值等于某个常数(特别是0)的等值面来提取表面。这就像地理上的等高线。三维水*集常用于从体数据(如医学影像)中提取表面。
![]()
-
分形(Fractal):具有自相似性的几何,即部分与整体相似。例如雪花、某些植物(如罗马花椰菜)。分形几何在自然界和数学中很常见,但其无限细节的特性对渲染和表示都是挑战。
![]()
显式几何
显式表示会直接给出表面的点,或者通过参数映射来定义。
- 直接给出:如三角形网格,直接列出所有顶点和三角形。
- 参数映射:定义一个从参数域(如
(u, v)*面)到三维空间的映射函数:
(x, y, z) = f(u, v)
遍历所有(u, v)参数,就能得到表面的所有点。例如,一个球面可以用经纬度(θ, φ)参数化。
优点:
- 容易直接得到表面的所有点,易于采样和绘制。
- 容易描述复杂的、不规则的形状。
缺点:
- 难以判断一个给定点是否在表面上(或内/外)。
- 对于参数映射,可能产生不均匀的采样或奇点。
隐式和显式表示各有优劣,在图形学中根据具体需求选择使用。例如,CSG适合建模,三角形网格适合渲染,SDF适合形状变形和融合。
总结 📝
本节课中,我们一起学*了以下内容:
-
纹理的高级应用:完成了纹理部分的讲解,重点介绍了:
- 环境贴图:用于表示来自无限远处的环境光照,包括球面贴图和立方体贴图。
- 凹凸/法线贴图:通过纹理改变表面法线,在不增加几何复杂度的情况下模拟凹凸细节。
- 位移贴图:真正移动顶点位置,效果更真实,但需要细致几何或动态细分。
- 三维纹理与预计算:纹理可扩展到三维空间(如噪声函数、体数据),并可存储预计算信息(如环境光遮蔽)。
-
几何表示入门:开始了几何部分的学*,介绍了两种主要的几何表示范式:
- 隐式几何:通过点满足的关系(
f(x,y,z)=0)定义表面。易于判断点与表面的关系,但难以直接描述复杂形状。具体方法包括代数公式、CSG、距离函数(SDF)、水*集和分形。 - 显式几何:直接给出表面点或通过参数映射定义。易于采样和绘制复杂形状,但难以判断点与表面的关系。具体方法包括三角形网格和参数化表面。
- 隐式几何:通过点满足的关系(

隐式和显式表示在图形学中相辅相成,下一节课我们将继续探讨显式表示,并深入介绍曲线和曲面的表示方法。



本节课到此结束。

现在将时间交给技术秘书同学。







GAMES101-现代计算机图形学入门-闫令琪 - P11:几何 2 (曲线与曲面) 📐
在本节课中,我们将学*计算机图形学中显示几何的表示方法,重点探讨曲线与曲面的概念、定义及其应用。我们将从最简单的点云开始,逐步深入到贝塞尔曲线和曲面的几何与代数表示,并了解其在图形学中的重要作用。
课程前言与回顾
上一节我们介绍了几何入门,区分了隐式几何与显式几何两大类。隐式几何包括距离场、水*集等多种定义方法。本节中,我们来看看显式几何的表示。

显式几何同样有多种表示方法,例如三角形面、贝塞尔曲面、细分曲面以及点云等。我们将逐一介绍这些方法。
显式几何表示方法


以下是几种常见的显式几何表示方法:

- 点云:将物体表面表示为一组密集的点。每个点由三维坐标
(x, y, z)定义。当点足够密集时,可以*似看出物体表面。点云通常是三维扫描的原始输出数据。 - 多边形面(网格):这是图形学中应用最广泛的表示方法,特别是三角形或四边形面。任何复杂表面都可以用许多小三角形拼接而成。一个网格物体通常包含顶点、法线和纹理坐标等信息。
- 曲线与曲面:通过参数化的方法定义光滑的几何形状,例如贝塞尔曲线和曲面。这是我们本节课的重点。
曲线及其应用 🧵
曲线在图形学中有广泛的应用,例如定义相机运动路径、三维建模中的引导线以及字体轮廓等。
贝塞尔曲线
贝塞尔曲线使用一系列控制点来定义一条光滑曲线。它不要求曲线经过所有控制点,但起始点和终点必须与第一个和最后一个控制点重合。
德卡斯特里奥算法
给定一系列控制点,如何绘制出对应的贝塞尔曲线?德卡斯特里奥算法提供了一种直观的几何方法。
以下是该算法的步骤:
- 假设有
n+1个控制点,以及一个时间参数t(取值范围[0, 1])。 - 在每两个相邻控制点形成的线段上,找到对应于时间
t的点(线性插值):b_i^1 = (1 - t) * b_i + t * b_{i+1}。 - 将这些新得到的点作为新的控制点集,重复步骤2,直到只剩下一个点。
- 这个最终的点就是贝塞尔曲线在时间
t时所处的位置。
通过遍历 t 从 0 到 1,即可得到整条贝塞尔曲线。
代数表示与伯恩斯坦多项式
上述几何过程可以转化为代数形式。对于 n+1 个控制点,贝塞尔曲线在时间 t 的点是这些控制点的线性组合,组合系数由伯恩斯坦多项式给出。
n 阶贝塞尔曲线的公式为:
B(t) = Σ_{i=0}^{n} b_i * B_i^n(t)
其中,b_i 是第 i 个控制点,B_i^n(t) 是 n 次的伯恩斯坦多项式:
B_i^n(t) = C(n, i) * t^i * (1-t)^{n-i}
这里 C(n, i) 是二项式系数。伯恩斯坦多项式具有归一性(所有项在任意 t 处之和为1)和对称性。
贝塞尔曲线的性质
贝塞尔曲线具有几个重要性质:
- 端点性质:曲线一定从第一个控制点开始,到最后一个控制点结束。
- 仿射变换不变性:对贝塞尔曲线做仿射变换,等价于对其所有控制点做同样的仿射变换,再绘制新曲线。注意:此性质对投影变换不成立。
- 凸包性质:贝塞尔曲线始终位于其所有控制点构成的凸包内。凸包是包含所有点的最小凸多边形。
分段贝塞尔曲线
当控制点较多时,单条高阶贝塞尔曲线难以精确控制局部形状。因此,实践中常用分段贝塞尔曲线,尤其是每段由四个控制点定义的三次贝塞尔曲线。
连续性
将多段贝塞尔曲线连接起来时,需要考虑连接处的连续性:
- C0 连续:几何连续,即前一段的终点与后一段的起点重合。
- C1 连续:一阶导数连续,即连接点处两段曲线的切线方向和大小都相同。对于分段三次贝塞尔曲线,这要求连接点两侧的控制点与连接点共线且距离相等。
更高阶的连续性(如C2连续,曲率连续)也有定义,常用于对光滑度要求更高的场景。


其他曲线类型
除了贝塞尔曲线,还有其他类型的曲线:
- 样条:由一系列控制点控制,并满足特定连续性的曲线统称。
- B样条:B样条是贝塞尔曲线的推广,具有局部性优点,即修改一个控制点只影响曲线局部区域,而非整条曲线。其基函数比伯恩斯坦多项式更复杂。
- NURBS(非均匀有理B样条):在B样条基础上进一步扩展,能力更强,广泛应用于工业设计。


关于曲线更深入的知识(如升阶、降阶、拆分等操作),可以参考胡事民老师的相关课程。
从曲线到曲面 🧊
将曲线的概念扩展到二维参数域,就可以定义曲面。
贝塞尔曲面
贝塞尔曲面由二维网格的控制点阵定义(例如 4x4 个点)。其生成过程是双重的贝塞尔曲线插值:
- 在
u方向(例如每一行),用控制点生成一系列贝塞尔曲线。 - 对于某个固定的
u值,在这些曲线上取点,得到一组新的控制点。 - 在
v方向上,用这组新控制点再生成一条贝塞尔曲线。 - 当
u和v在[0, 1]范围内变化时,扫过的空间即构成贝塞尔曲面。
通过参数 (u, v) 即可映射到曲面上的任意点,因此它也是一种显式表示。
网格处理简介
尽管有参数化曲面,但在图形学中,用三角形网格表示物体表面仍然是最普遍的方法。对网格常见的几何处理操作包括:
- 细分:增加三角形数量,使模型更光滑。
- 简化:在保持基本形状的前提下,减少三角形数量,以节省存储和计算资源。
- 正规化:调整网格,使三角形尽可能接*等边三角形,避免出现又长又尖的三角形,以获得更好的数值性质。
这些网格操作的具体内容将在下一节课详细讲解。
总结



本节课我们一起学*了显式几何的多种表示方法。我们重点探讨了贝塞尔曲线的几何定义(德卡斯特里奥算法)与代数表示(伯恩斯坦多项式),并了解了其重要性质。我们还介绍了分段贝塞尔曲线及其连续性概念,并简要提到了B样条等其他曲线。最后,我们将曲线概念扩展到二维,引入了贝塞尔曲面的构造方法,并对常见的网格处理操作进行了概述。掌握这些曲线与曲面的基础知识,是理解后续几何处理与渲染技术的重要一步。

GAMES101-现代计算机图形学入门-闫令琪 - P12:几何(三)与阴影映射 🎯

在本节课中,我们将学*几何处理的最后一部分内容,包括网格的细分与简化,并探讨在光栅化管线中生成阴影的核心技术——阴影映射(Shadow Mapping)。

课程概述 📋
本节课是几何部分的最后一讲。我们将首先完成对几何处理的讨论,具体介绍两种重要的网格操作:细分(Subdivision)与简化(Simplification)。随后,我们将转向光栅化中的一个经典难题——阴影生成,并详细讲解其主流解决方案:阴影映射技术。最后,我们会简要提及光线追踪的引入动机,为后续课程做铺垫。
第一部分:网格细分 🏗️
上一节我们介绍了对三角形网格的各种处理方法。本节中,我们来看看如何通过细分操作,增加网格的三角形数量,从而使模型表面看起来更加光滑。
细分操作通常包含两个步骤:
- 增加三角形数量:将一个三角形拆分为多个更小的三角形。
- 调整顶点位置:根据特定规则移动新旧顶点的位置,使模型表面*滑。
以下是两种经典的细分算法:
Loop 细分 (Loop Subdivision)
Loop细分是专门针对三角形网格的细分方法。请注意,“Loop”是算法发明者的姓氏,与“循环”无关。
细分步骤:
- 拆分三角形:连接三角形每条边的中点,将一个三角形拆分为四个小三角形。这样会产生新的顶点(位于边的中点)。
- 更新顶点位置:对新顶点和旧顶点分别应用不同的规则来调整位置。
- 更新新顶点位置:对于每条边上的新顶点,其位置由共享该边的两个三角形决定。设该边两个端点为 A 和 B,两个三角形中不在此边上的另外两个顶点为 C 和 D。则新顶点的位置更新公式为:
新位置 = 3/8 * (A + B) + 1/8 * (C + D) - 更新旧顶点位置:对于原有的旧顶点,其新位置由自身原始位置和相邻旧顶点的*均位置共同决定。设旧顶点度为 n(即连接了n条边),定义 u 为一个与n相关的系数。则旧顶点位置更新公式为:
新位置 = (1 - n*u) * 原始位置 + u * 相邻顶点位置之和
- 更新新顶点位置:对于每条边上的新顶点,其位置由共享该边的两个三角形决定。设该边两个端点为 A 和 B,两个三角形中不在此边上的另外两个顶点为 C 和 D。则新顶点的位置更新公式为:
通过多次迭代Loop细分,模型会变得越来越光滑。
Catmull-Clark 细分 (Catmull-Clark Subdivision)
Catmull-Clark细分是一种更通用的细分方法,可以处理包含任意多边形(而不仅仅是三角形)的网格。该算法由今年图灵奖得主之一Ed Catmull提出。
核心概念:
- 四边形面 (Quad Face):具有四条边的面。
- 非四边形面 (Non-quad Face):不是四边形的面(如三角形、五边形)。
- 奇异点 (Extraordinary Vertex):度(连接的边数)不等于4的顶点。
细分步骤:
- 增加顶点:为每个面的中心添加一个点,为每条边的中心添加一个点。
- 连接顶点:将每个面的中心点与其所有边的中心点连接起来。
- 更新顶点位置:根据一套规则(课程中不要求掌握具体公式)更新所有点的位置(包括面中心点、边中心点和旧顶点),使模型*滑。
Catmull-Clark细分的一个重要性质:在第一次细分之后,所有的非四边形面都会消失,此后奇异点的数量将不再增加。这意味着该算法能快速将网格规整化。
第二部分:网格简化 🔽
在了解了如何增加细节后,我们来看看相反的操作——网格简化。当模型距离摄像机很远,或者为了提升渲染性能时,我们可能需要减少模型的三角形数量,同时尽量保持其原有形状。
网格简化不能简单地删除三角形,否则会破坏模型的拓扑结构。一种常用的方法是 边坍缩 (Edge Collapse)。
边坍缩与二次误差度量
边坍缩的操作很简单:将一条边上的两个顶点合并为一个新的顶点。关键在于,应该优先坍缩哪些边?
这里引入 二次误差度量 (Quadric Error Metrics) 的概念。对于坍缩一条边后产生的新顶点,我们希望将它放置在一个最优位置,使得这个新顶点到它原来关联的所有三角形*面的距离*方和最小。这个最小*方和就是这条边的“代价”。
简化算法流程:
- 为模型中的每条边,计算如果坍缩它所需的最小二次误差,作为该边的“分数”。
- 将所有边放入一个优先队列(堆) 中,按“分数”从小到大排序。
- 每次从队列中取出分数最小的边进行坍缩。
- 坍缩操作会改变其周围连接的边,因此需要重新计算这些受影响边的二次误差,并更新它们在优先队列中的位置。
- 重复步骤3和4,直到达到预设的三角形数量或误差阈值。
这个算法是一种贪心算法,在实践中通常能取得很好的简化效果。


第三部分:阴影映射 (Shadow Mapping) 🌑

几何部分告一段落,现在我们转向光栅化中的一个全局效果——阴影。之前学*的局部着色模型无法处理阴影,因为阴影需要判断着色点是否被其他物体遮挡了光线。阴影映射是光栅化中生成阴影的主流图像空间技术。
核心思想
阴影映射基于一个简单的观察:一个不在阴影中的点,必须同时满足两个条件——既能被摄像机看到,也能被光源看到。
算法步骤(针对点光源/方向光源的硬阴影)
阴影映射是一个两趟 (Two-pass) 的算法:



第一趟:从光源视角渲染
- 将摄像机放置在光源位置,看向整个场景。
- 像普通光栅化一样进行渲染,但不计算着色,只记录深度(即每个像素位置光源能看到的最*物体的距离)。这张深度图就是 Shadow Map。


第二趟:从真实摄像机视角渲染
- 从真实的摄像机位置正常渲染场景。
- 对于渲染得到的每个像素点(即场景中的一个表面点),需要判断它是否在阴影中:
- 将该点的世界坐标变换回光源的裁剪空间,找到它在 Shadow Map 中对应的像素。
- 计算该点到光源的实际距离 d1。
- 读取 Shadow Map 中对应像素存储的深度值 d2(即从光源能看到的最远距离)。
- 比较:如果
d1 ≈ d2(考虑浮点精度,通常判断d1 <= d2 + bias),说明该点能被光源看到,不在阴影中。如果d1 > d2,说明该点被更*的物体遮挡,在阴影中。
阴影映射的问题与局限性
- 走样与精度问题:Shadow Map 有分辨率限制,可能导致阴影边缘出现锯齿。浮点数深度比较也会带来精度问题,通常需要引入一个微小的偏移量 (Bias) 来避免“阴影痤疮”。
- 硬阴影:基本算法只能生成边界锐利的硬阴影,因为判断是非零即一的(可见/不可见)。
- 性能:需要渲染场景两次,增加了开销。
硬阴影 vs. 软阴影


- 硬阴影:由点光源或小型光源产生,阴影边界清晰锐利。
- 软阴影:由面光源或大型光源产生。在软阴影区域(半影,Penumbra),只能看到部分光源,因此阴影有一个从明到暗的柔和过渡。软阴影看起来更真实,但用传统的阴影映射实现起来更复杂。

尽管存在这些问题,阴影映射因其相对高效和易于实现,仍然是游戏和实时图形中应用最广泛的阴影技术。
课程总结 🎓

本节课中我们一起学*了:
- 网格细分:通过 Loop 细分和 Catmull-Clark 细分来增加网格细节,使模型更光滑。
- 网格简化:通过基于二次误差度量的边坍缩算法来减少三角形数量,在保持形状的同时提升性能。
- 阴影映射:掌握了在光栅化中生成阴影的核心技术——两趟算法的阴影映射,理解了其原理、实现步骤以及存在的局限性(如硬阴影、走样)。



通过本课,我们完成了对几何核心处理的探讨,并接触了第一个全局光照效果——阴影。这为我们接下来学*更强大、能自然处理全局效果的光线追踪技术奠定了动机和基础。下节课,我们将正式开启光线追踪之旅。

GAMES101-现代计算机图形学入门-闫令琪 - P13:光线追踪 1 🎯
在本节课中,我们将要学*光线追踪的基础知识,特别是经典的“Whitted风格”光线追踪算法。我们将探讨为何需要光线追踪,其基本工作原理,以及如何计算光线与场景中物体的交点。

概述:为何需要光线追踪? 🤔

上一节我们介绍了光栅化及其局限性。本节中我们来看看光线追踪如何解决这些问题。
光栅化是一种快速但*似的渲染方法,它在处理某些全局效果时效率不高或效果不佳。这些效果包括:
- 软阴影:阴影边缘的柔和过渡。
- 光泽反射:类似打磨金属的表面反射,既非完美镜面也非完全漫反射。
- 间接光照:光线在场景中经过多次弹射(如漫反射)后才进入眼睛。
光线追踪是一种更准确、符合物理规律的渲染方法,能够自然地处理这些复杂的光照效果。然而,它的计算成本非常高,通常用于电影等离线渲染,而非实时应用。
光线追踪的基本原理 🚀
光线追踪的核心思想是利用光路的可逆性。我们并不模拟从光源发出的无数光线,而是从相机(眼睛)出发,向场景中投射光线,并追踪这些光线的路径。
光线投射
这是光线追踪的最基本形式。以下是其步骤:
- 生成光线:从相机出发,穿过成像*面上的每个像素中心,向场景发射一条射线(称为主光线)。
- 求最*交点:计算这条射线与场景中所有物体的交点,并找到最*的交点。
- 计算着色:从该交点向光源连接一条线(称为阴影光线)。如果这条线未被任何物体阻挡,则计算该点的着色(如使用Blinn-Phong模型)。
- 写入像素:将计算出的颜色值赋给对应的像素。
这个过程解决了深度测试问题(最*的交点自然遮挡远处的物体),并能生成简单的阴影。
Whitted风格光线追踪 🔄
上一节我们介绍了基础的光线投射。本节中我们来看看如何通过递归追踪来模拟更复杂的光线行为,即Whitted风格光线追踪。
Whitted风格光线追踪是一种递归算法,它考虑了光线在物体表面的反射和折射。当主光线击中一个物体时:
- 如果物体是镜面,它会沿着反射方向生成一条新的次级光线。
- 如果物体是透明介质(如玻璃),它会沿着折射方向生成一条新的次级光线。
- 这些次级光线会继续与场景求交,并可能进一步产生新的反射/折射光线。
对于每一次弹射(包括第一次击中点),都会像光线投射中那样,连接阴影光线来判断该点是否被照亮,并计算其对着色的贡献。所有弹射点贡献的能量会按照物理规律(如反射/折射系数)衰减后,累加到最终的像素颜色中。

核心公式:光线用射线表示
ray(t) = origin + t * direction (t >= 0)
光线与物体求交 🔍
要实现光线追踪,最关键的技术之一是计算光线与物体的交点。
光线与隐式表面求交
对于一个由隐函数 f(p) = 0 定义的表面(如球体:(p - c)^2 - R^2 = 0),求交点即求解方程:
f(origin + t * direction) = 0
解出满足 t >= 0 的实数根即可。
光线与三角形网格求交(重点)
由于模型通常由三角形网格表示,光线与三角形求交是最频繁的操作。有两种主要思路:
-
两步法:
- 先计算光线与三角形所在*面的交点。
- 再判断该交点是否在三角形内部(可用重心坐标判断)。
-
Möller-Trumbore算法:
- 一种直接求解的算法。它将交点用三角形的重心坐标表示,并与光线方程联立,直接解出交点参数
t和重心坐标(b1, b2)。 - 交点有效的条件是:
t >= 0且重心坐标满足b1 >= 0, b2 >= 0, 1-b1-b2 >= 0。
- 一种直接求解的算法。它将交点用三角形的重心坐标表示,并与光线方程联立,直接解出交点参数
核心代码/逻辑:MT算法求解线性方程组,判断重心坐标是否非负。
加速光线求交:包围盒 🏃
上一节我们学*了如何求光线与单个三角形的交点。本节中我们来看看如何利用包围盒来大幅加速与复杂模型的求交过程。
对于一个包含数百万三角形的场景,为每根光线检查所有三角形是不可行的。加速的关键思想是使用包围盒。
轴对齐包围盒
我们常用轴对齐包围盒(AABB)——一个各边*行于坐标轴的长方体来包裹复杂物体。关键逻辑是:
- 如果光线连物体的AABB都碰不到,那它肯定碰不到物体内部的任何三角形。
- 先进行光线与AABB的快速求交测试,只有通过测试的光线才需要与盒内三角形进行精细求交。
光线与AABB求交算法(核心)
如何判断光线 ray(t) = O + tD 与一个AABB是否相交?
- 将对面对视为“*板”:将AABB视为三组*行的“*板”(如x方向、y方向、z方向各一对)的交集。
- 计算光线进出各对“*板”的时间:对于每一组(如x方向),计算光线进入两个*行*面(
x_min,x_max)的时间t_min_x,t_max_x。注意时间可能为负。 - 求实际进出盒子的时间:
- 光线进入盒子的时间
t_enter= max(t_min_x,t_min_y,t_min_z)。(必须进入所有三对*板才算进入盒子) - 光线离开盒子的时间
t_exit= min(t_max_x,t_max_y,t_max_z)。(只要离开任意一对*板就算离开盒子)
- 光线进入盒子的时间
- 判定相交:当且仅当
t_enter < t_exit且t_exit >= 0时,光线与AABB相交。- 如果
t_exit < 0,盒子在光线背后。 - 如果
t_enter < 0但t_exit >= 0,说明光线起点在盒子内部。
- 如果
核心逻辑:相交 <=> (max(t_min) < min(t_max)) && (min(t_max) >= 0)
使用AABB能极大减少不必要的三角形求交计算,是光线追踪加速结构的基石。
总结 📚
本节课中我们一起学*了光线追踪的入门知识。
- 我们了解了光线追踪相比光栅化的优势,在于它能更物理地处理全局光照效果,但代价是计算速度慢。
- 我们掌握了Whitted风格光线追踪的基本原理:从相机发射光线,递归地追踪反射和折射路径,并累加光照贡献。
- 我们深入探讨了光线求交这一核心技术,包括与隐式表面、三角形的求交方法。
- 最后,我们学*了如何使用轴对齐包围盒(AABB)来加速光线与复杂模型的求交过程,这是构建更高效空间加速结构(如BVH)的基础。



下一讲,我们将继续深入光线追踪,探讨如何组织这些包围盒以形成高效的加速结构,以及路径追踪等更现代的全局光照算法。

GAMES101-现代计算机图形学入门-闫令琪 - P14:Lecture 14 光线追踪 2 🚀

在本节课中,我们将要学*光线追踪的第二部分,重点探讨如何高效地处理光线与复杂场景的求交问题。我们将介绍两种核心的空间加速结构:KD-Tree和Bounding Volume Hierarchy (BVH)。如果时间允许,我们还将初步接触辐射度量学的基础概念。
课程前言与*期动态
在开始新课之前,先同步几项课程相关事宜。
关于作业重新提交后的批改与分数反馈,我们正在积极处理中,请大家稍安勿躁,最终会完成批改并给出分数。
*期,受新冠疫情影响,英伟达的GPU技术大会转为线上举行。会上公布了两项与渲染相关的新技术,值得关注。
- DLSS 2.0:这项技术能够将低分辨率(如720p)的光栅化图像放大至高分辨率(如4k),同时保持画面清晰,不损失过多性能。其主要研发人员刘诗秋在知乎有专栏文章介绍。
- RTX 全局光照:这项技术旨在将全局光照效果引入实时渲染。我们将在后续课程中深入探讨全局光照的难点,以及如何将其实现实时化。
这些进展表明,许多原本属于离线渲染的算法,正通过改进逐步应用于实时渲染领域。这是一个重要趋势,未来的实时应用(如游戏)将更多地融合这些技术,以提升画面质量。
关于光栅化技术是否会被实时光线追踪取代,个人认为至少在五年内是不可能的。即使实时光线追踪普及,光栅化技术在其他领域仍有其不可替代的价值和应用。
从本节课开始,课程内容将更具挑战性,涉及现代计算机图形学中生成高真实感图像的核心方法。
上节课回顾
上一节我们介绍了光线追踪的基本原理。
我们从为何需要光线追踪开始,介绍了光线投射,以及Turner Whitted提出的递归式光线追踪方法。该方法中,光线在场景中弹射,在每个交点计算着色,最终累加到像素颜色上。
我们讨论了光线与物体求交的基础,特别是光线与三角形求交,因为三角形是图形学中最常用的几何图元。
随后,我们引入了一个关键问题:当场景中三角形数量巨大时,逐一对每个三角形求交是不现实的。为此,我们介绍了轴对齐包围盒的概念,并讲解了光线与AABB求交的方法。
空间划分:均匀网格
上一节我们学会了光线与AABB求交,本节我们来看看如何利用这个特性来加速整个场景的求交过程。
核心思想是:假设光线与包围盒求交很快,而与实际物体求交很慢。那么,我们可以先让光线与一些简单的“盒子”求交,快速排除掉大量不可能相交的区域,只在与盒子相交的区域内部,才进行与实际物体的精确求交。
以下是实现此思想的一种方法:均匀网格。

- 预处理:在光线追踪开始前,将整个场景的空间均匀划分成许多小格子(二维为方格,三维为立方体)。
- 标记格子:判断每个格子是否与场景中的物体表面相交,并将相交的格子标记出来(例如图中涂灰的格子)。
- 光线追踪:发射光线后,让光线依次穿过这些格子。当光线与一个被标记的格子相交时,才需要与该格子内的所有物体进行求交计算。若光线与一个空白格子相交,则直接跳过。
这种方法的关键在于,需要一种算法能快速判断光线当前在哪个格子,以及下一个相交的格子是哪一个。这与光栅化中“如何光栅化一条直线”的问题在原理上是相通的,可以通过增量算法高效解决。
均匀网格的性能取决于格子的大小。格子太稀疏,加速效果不佳;格子太密集,光线与格子的求交次数又会过多。实践中存在一个*衡点,通常格子数量与场景物体数量成一定比例关系。
均匀网格在物体分布相对均匀的场景中效果很好。但当场景中存在大范围空白区域(如一个巨大的运动场中间放一个茶壶),光线需要穿过大量空白格子才能到达物体,效率就会降低。这被称为“运动场中的茶壶”问题。
空间划分:KD-Tree
为了解决均匀网格的不足,我们引入更自适应的空间划分方法:KD-Tree。
KD-Tree是一种二叉树结构,每次划分空间时,沿某个轴(如X、Y、Z轴)将当前节点代表的包围盒空间一分为二。划分轴通常循环选择(如第一层沿X轴分,第二层沿Y轴分,第三层沿Z轴分,如此往复)。

- 构建过程:
- 从整个场景的包围盒(根节点)开始。
- 沿选定轴,用一个*面将当前空间划分为左右(或上下、前后)两个子空间。
- 递归地对两个子空间进行划分。
- 划分终止条件可以是:空间内物体数量少于某个阈值,或空间体积足够小。
- 数据结构:中间节点存储划分轴、划分位置以及指向两个子节点的指针。实际的物体(如三角形)只存储在叶子节点中。
- 光线求交:
- 从根节点开始,判断光线是否与当前节点的包围盒相交。
- 若不相交,则跳过该节点及其所有子节点。
- 若相交,且当前节点是叶子节点,则光线与该叶子节点内所有物体求交。
- 若相交,且当前节点是中间节点,则递归地对两个子节点执行步骤1。
KD-Tree的优点是能自适应地细分物体密集的区域,同时保持空白区域为大块,从而高效地跳过空白区域。然而,它有两个主要缺点:
- 判断一个三角形与一个AABB是否相交(在构建树时需要)是比较复杂的操作。
- 一个物体可能同时与多个叶子节点的包围盒相交,因此可能被存储在多个叶子节点中,不够直观。
物体划分:Bounding Volume Hierarchy (BVH)
由于KD-Tree的上述缺点,现代图形学中更广泛使用的是另一种加速结构:Bounding Volume Hierarchy。
BVH的核心思想不是划分空间,而是划分物体集合。

- 构建过程:
- 将当前节点中的所有三角形划分为两个子集(例如,按某个轴排序后,取中位数分为左半和右半)。
- 分别重新计算这两个子集的包围盒。
- 递归地对两个子集进行划分。
- 划分终止条件通常是叶子节点中的三角形数量足够少(例如,少于5个)。
- 与KD-Tree的关键区别:
- 划分对象:BVH划分物体,KD-Tree划分空间。
- 包围盒关系:BVH中,子节点的包围盒可能相互重叠;KD-Tree中,子空间是严格分开的。
- 物体存储:BVH中,一个物体只属于一个叶子节点,解决了KD-Tree中物体重复存储的问题。
- 构建难度:BVH的构建只需对三角形重心进行排序和划分,避免了复杂的三角形与AABB相交测试,实现更简单。
- 构建技巧:
- 划分轴选择:通常选择当前节点包围盒最长的轴进行划分,有助于生成更均衡的树。
- 划分点选择:为了保持树的*衡,常取三角形沿划分轴排序后的中位数作为划分点,确保左右两边的三角形数量大致相等。寻找中位数可以使用快速选择算法,在O(n)时间内完成。
- 光线求交伪代码:
Intersect(Ray ray, BVH_node node) { if (ray misses node.bbox) return; if (node is a leaf node) { test intersection with all objs in node; return closest intersection; } hit1 = Intersect(ray, node.left_child); hit2 = Intersect(ray, node.right_child); return the closer of hit1 and hit2; }
BVH因其实现简单、效率高,已成为光线追踪中最主流的加速结构。
辐射度量学导论
在完成了Whitted风格光线追踪的学*后,我们将目光转向更高级、能产生物理精确结果的光线追踪方法。这一切的基础是辐射度量学。
我们之前使用的光照模型(如Blinn-Phong)和Whitted光线追踪存在明显的物理不准确性。例如,我们定义的光强I只是一个没有单位的数字,反射和折射的能量衰减也是随意定义的。
辐射度量学为我们提供了一套精确定义和测量光照的物理量体系。它将为光源、材质以及光线的传播提供准确的描述,是后续学*路径追踪等高级光线追踪算法的基石。
辐射度量学基于几何光学(光线直线传播),定义了一系列核心物理量。为了避免中文译名的混乱,我们直接使用英文术语:
- Radiant Energy:辐射能量,单位焦耳 (J),表示电磁辐射的能量。
- Radiant Flux (Power):辐射通量(功率),单位瓦特 (W) 或流明 (lm)。表示单位时间的能量,可以直观理解为光源的“亮度”。例如,一个60瓦的灯泡比40瓦的亮。
- Radiant Intensity:辐射强度,定义为单位立体角上的功率。它描述了点光源在特定方向上的亮度。单位是坎德拉 (cd)。
- Irradiance:辐照度,定义为单位表面积接收到的功率。它描述了一个表面点接收到的光照强度。
- Radiance:辐射率,定义为单位立体角、单位投影面积上的功率。它是描述光线在空间中传播时最重要的量。
要理解Radiant Intensity,需要先理解立体角的概念。
立体角
立体角是二维角度在三维空间中的延伸。
- *面角:用弧度表示,定义为弧长除以半径:
θ = l / r。整个圆的弧度为2π。 - 立体角:定义为球面上的面积除以半径的*方:
Ω = A / r²。单位是球面度 (sr)。整个球面的立体角为4π球面度。
在球坐标系中,一个由(θ, φ)确定的方向,其对应的微分立体角为:
dω = sinθ dθ dφ
我们用ω来表示三维空间中的一个方向。
Radiant Intensity 详解
对于一个向所有方向均匀发光的点光源,其总功率为Φ。那么,它在任何一个方向上的辐射强度I为:
I = Φ / 4π
例如,一个实际功耗为11W,光通量为815流明的LED灯泡,在任意方向上的辐射强度约为:
I = 815 lm / (4π) sr ≈ 65 cd
本节课中,我们一起学*了两种核心的空间加速结构(KD-Tree和BVH)来高效实现光线与场景的求交,并初步了解了辐射度量学的动机及其第一个物理量Radiant Intensity和立体角的概念。下节课我们将继续深入辐射度量学,学*Irradiance和Radiance,为路径追踪打下坚实基础。

时间已晚,感谢大家。后续课程会尽量合理安排进度。大家加油。

各位同学赶紧吃午饭吧,大家辛苦了。





GAMES101-现代计算机图形学入门-闫令琪 - P15:光线追踪3 - 光线传播与全局光照 🌟
在本节课中,我们将学*光线传播的物理基础,理解辐射度量学中的核心概念,并最终推导出描述光线与物体交互的渲染方程。我们还将初步了解全局光照的概念,为下一节课的路径追踪算法打下基础。
课程概述与安排 📅
课程网站已更新,总课时调整为22节,并新增了作业七。考虑到课程内容深度,后续作业(作业六和作业七)的提交周期调整为1.5周一次,以减轻大家的工作量。
本课程内容有时会超过一小时,这是因为它改编自一个1-1.25小时的英文课程。虽然中文表达更高效,但内容量依然较大,我会尽量控制节奏。
关于课程问题,鼓励大家在论坛中提问,特别是关于概念理解方面的问题,我会尽力解答。关于实时渲染课程的直播,由于是内部课程,暂时无法对外公开,但未来可能会在暑假期间以中文形式重新组织分享。
回顾:辐射度量学核心概念 🔄
上一节我们介绍了辐射度量学的基础。为了准确描述光线传播,我们需要一套物理定义。图形学中通常不考虑时间累积效应,因此我们主要使用单位时间的能量,即功率(Power)。
以下是几个核心概念:
- 辐射强度(Intensity):定义为在单位立体角上的功率。立体角描述了空间中的一个方向锥角大小。
- 辐照度(Irradiance):定义为在单位(投影)面积上接收到的功率。这解释了为什么光线不垂直照射时,表面接收到的能量会变少(兰伯特余弦定律)。
- 辐射率(Radiance):这是描述光线属性的关键量。它定义为在单位立体角、单位投影面积上的功率。Radiance 是理解光线传播和 BRDF 的核心。
一个重要区分:在传播过程中,Intensity 不会衰减,而 Irradiance 会按距离*方反比衰减。这是因为 Intensity 定义在立体角上,而立体角在传播中不变;Irradiance 定义在面积上,随着球面扩大,单位面积接收的能量自然减少。
微分立体角:在球面坐标系中,用极角 θ 和方位角 φ 表示方向。微分立体角 dω 的公式为:
dω = sinθ dθ dφ
这表明立体角的变化量与 θ 的位置有关。
双向反射分布函数(BRDF)与反射方程 🔍
Radiance 的重要性在于它同时考虑了面积和方向。Irradiance 描述一个微小面积 dA 接收到的所有能量,而 Radiance 则描述 dA 从特定方向 ωi 接收或向特定方向 ωo 辐射的能量。
BRDF 描述了物体表面的一点如何将来自入射方向 ωi 的光线,反射到出射方向 ωo 上。其定义为:
f_r(ω_i -> ω_o) = dL_o(ω_o) / dE(ω_i) = dL_o(ω_o) / (L_i(ω_i) cosθ_i dω_i)
其中 dL_o 是出射 Radiance,dE 是由入射 Radiance L_i 在 dA 上产生的微分 Irradiance。cosθ_i 是入射方向与法线的夹角余弦。
反射方程 将这个过程推广到所有可能的入射方向。它计算了从观察方向 ωo 看一个着色点 p 时,该点反射出的总 Radiance Lo:
L_o(p, ω_o) = ∫_{H^2} f_r(p, ω_i, ω_o) L_i(p, ω_i) cosθ_i dω_i
这个积分在半球 H^2 上进行,累加所有入射方向 ωi 的贡献。反射方程定义了物体表面的着色。
渲染方程与全局光照 💡
反射方程只描述了反射行为。如果一个表面自身会发光(如光源),我们需要加上其自发光项 Le。这就得到了著名的渲染方程:
L_o(p, ω_o) = L_e(p, ω_o) + ∫_{H^2} f_r(p, ω_i, ω_o) L_i(p, ω_i) cosθ_i dω_i
渲染方程是图形学的核心,它描述了所有基于物体表面的光线传播。这里的复杂性在于 Li(p, ωi) 本身可能是其他表面反射而来的光,这就形成了一个递归的、相互依赖的方程系统。
为了理解这个方程,我们可以将其简写为算子形式:L = E + K L。其中 L 是待求的辐射场,E 是自发光项,K 是反射算子。通过数学变换,可以将其展开为 Neumann 级数:
L = E + K E + K^2 E + K^3 E + ...

这个展开具有深刻的物理意义:
E:直接来自光源的光(0次弹射)。K E:光源光经过一次表面反射后进入眼睛的光(直接光照)。K^2 E:光源光经过两次表面反射后进入眼睛的光(一次间接光照)。- 以此类推。
全局光照 就是所有这些项的总和:全局光照 = 直接光照 + 间接光照。传统的光栅化着色模型(如布林-冯模型)主要模拟了 E + K E 部分,即直接光照和自身发光。而光线追踪的优势在于能相对容易地计算 K^2 E 及更高次项,从而模拟出逼真的间接光照效果,如颜色渗透和软阴影。


随着光线弹射次数的增加,场景亮度会逐渐收敛到一个稳定值,这是能量守恒的体现。实际渲染中,无限次弹射既不可能也无必要,通常几次弹射后结果就接*收敛。
概率论基础回顾 🎲
下一节课我们将学*如何求解渲染方程,这需要用到蒙特卡洛积分方法,因此这里先回顾必要的概率论知识。
以下是核心概念:
- 随机变量:一个可能取不同值的变量,每个值有对应的出现概率。
- 概率分布:描述随机变量取各个值的可能性。所有可能值的概率之和为1。
- 期望值:随机变量多次取值的*均值。对于离散随机变量
X,其期望E[X] = Σ x_i * p(x_i)。



对于连续随机变量,我们使用概率密度函数(PDF) p(x) 来描述。p(x) 在某个区间上的积分,表示随机变量落在该区间的概率。PDF 在整个定义域上的积分必须为1。
连续随机变量 X 的期望为:E[X] = ∫ x p(x) dx
对于一个函数 Y = f(X),其期望为:E[f(X)] = ∫ f(x) p(x) dx
这个公式是后续蒙特卡洛积分的理论基础。
总结 🎯
本节课我们一起学*了光线传播的完整物理图景。
我们从辐射度量学出发,明确了 Radiance 和 Irradiance 等核心概念及其关系。基于这些概念,我们定义了描述表面反射特性的 BRDF,并推导出反射方程。通过加入自发光项,我们得到了描述所有表面光传输的渲染方程。
为了理解渲染方程,我们将其展开为不同光线弹射次数的和,从而引入了全局光照的概念,它包含了直接光照和间接光照。传统光栅化擅长处理前者,而光线追踪(路径追踪)是解决后者的强大工具。
最后,我们回顾了概率论的基础知识,包括随机变量、概率密度函数和期望值,为下一节课学*蒙特卡洛积分与路径追踪算法做好了准备。



渲染方程是连接物理与视觉的桥梁,而路径追踪则是求解这座桥梁的关键钥匙。下节课,我们将拿起这把钥匙。

GAMES101-现代计算机图形学入门-闫令琪 - P16:Lecture 16 光线追踪 4 🎯
在本节课中,我们将学*如何将之前介绍的辐射度量学、渲染方程与蒙特卡洛积分方法相结合,最终实现一个基于物理的、正确的渲染算法——路径追踪。我们将从回顾渲染方程开始,逐步推导出路径追踪的完整算法,并解决其实现中的关键问题。
概述:从渲染方程到路径追踪
上节课我们介绍了辐射度量学,并推导出了描述光线传播的渲染方程。本节课的核心目标,就是找到一种数值方法来求解这个渲染方程,从而计算出场景中每个着色点反射到我们眼睛的光线能量(Radiance)。我们将使用蒙特卡洛积分方法来解决这个积分问题,并最终构建出路径追踪算法。
回顾:渲染方程与概率论基础
上一节我们介绍了渲染方程,本节我们来看看如何求解它。首先,我们简要回顾两个核心概念。
渲染方程
渲染方程描述了从一个着色点 p 沿观察方向 ω_o 出射的 Radiance L_o(p, ω_o)。它由该点的自发光 L_e 和从所有入射方向 ω_i 反射而来的光组成:
公式:
L_o(p, ω_o) = L_e(p, ω_o) + ∫_Ω f_r(p, ω_i, ω_o) L_i(p, ω_i) cosθ_i dω_i
其中:
f_r是双向反射分布函数(BRDF),描述了入射光如何被反射。L_i(p, ω_i)是从方向ω_i入射到点p的 Radiance。cosθ_i是入射方向与法线的夹角的余弦值(衰减因子)。Ω表示围绕法线的半球空间。
我们的目标就是计算这个积分。
概率密度函数(PDF)
蒙特卡洛积分需要用到概率论知识。对于一个连续型随机变量 X,其取值的可能性由概率密度函数(PDF) p(x) 描述。PDF 满足两个条件:
- 非负性:
p(x) ≥ 0 - 归一性:
∫ p(x) dx = 1
随机变量 X 的期望值 E[X] 定义为:
公式:
E[X] = ∫ x p(x) dx
蒙特卡洛积分 🎲
为了求解渲染方程中的复杂积分,我们引入一种数值方法——蒙特卡洛积分。

蒙特卡洛积分的目标是:对于一个函数 f(x),在区间 [a, b] 上求定积分 I = ∫_a^b f(x) dx,并最终得到一个数值结果。
它的核心思想是:在积分域内进行随机采样,用样本值的加权*均来*似积分值。
算法如下:
- 在积分域
[a, b]上,以某种 PDFp(x)随机采样N个样本x_i。 - 对每个样本,计算
f(x_i) / p(x_i)。 - 对这些值求*均,作为积分的*似值。

公式:
∫_a^b f(x) dx ≈ (1/N) Σ_{i=1}^{N} [f(x_i) / p(x_i)]
一个简单例子: 若采用均匀采样,即 p(x) = 1/(b-a),则蒙特卡洛积分公式简化为:
∫_a^b f(x) dx ≈ (b-a)/N * Σ_{i=1}^{N} f(x_i)
这直观地理解为:用许多小矩形面积的*均值来*似曲线下面积。
蒙特卡洛积分的优点是:对 f(x) 的形式没有限制,只要能在积分域上采样并计算函数值即可。采样数 N 越大,结果越精确(噪声越小)。
引入路径追踪:为何需要它?
在介绍具体算法前,我们先看看之前学的 Whitted-Style 光线追踪的不足,从而理解路径追踪的必要性。
以下是 Whitted-Style 光线追踪的做法与问题:
- 做法:
- 光线打到光滑/镜面物体时,沿镜面反射或折射方向继续追踪。
- 光线打到漫反射物体时,停止追踪,仅计算直接光照。
- 问题:
- 无法处理 Glossy 材质:对于表面略有粗糙的 Glossy 材质,光线应反射到镜面方向周围的一个锥形区域内,而非单一方向。Whitted-Style 无法模拟这种模糊反射。
- 忽略了漫反射物体间的光线交互:光线在漫反射物体表面停止,忽略了漫反射-漫反射之间的间接光照,导致无法实现诸如颜色渗透(Color Bleeding) 等全局光照效果。
结论:Whitted-Style 光线追踪在许多情况下不符合物理规律。而渲染方程是基于物理的正确描述。因此,路径追踪的目标就是正确地求解渲染方程。
路径追踪算法推导
我们将一步步从渲染方程推导出路径追踪算法。
第一步:用蒙特卡洛积分解渲染方程(仅直接光照)
首先考虑一个简化场景:计算着色点 p 的直接光照(即 L_i 仅来自光源本身,不包括其他物体的反射)。
此时,渲染方程积分域是半球 Ω。我们使用蒙特卡洛积分来求解:
- 采样:在半球上按某种 PDF
p(ω_i)随机采样一个入射方向ω_i。为简单起见,先使用均匀采样,即p(ω_i) = 1/(2π)(半球立体角为2π)。 - 求被积函数:对于采样到的方向
ω_i,从点p发射一条射线。- 如果射线击中了光源,则
L_i(p, ω_i)就是光源在该方向上的 Radiance。 - 如果未击中光源,则
L_i(p, ω_i) = 0。
- 如果射线击中了光源,则
- 计算贡献:计算该样本的贡献:
f_r(p, ω_i, ω_o) * L_i(p, ω_i) * cosθ_i / p(ω_i)。 - 循环与*均:重复以上过程
N次,将结果*均。
伪代码如下:
shade(p, wo) {
L_o = 0.0;
for (int i = 0; i < N; ++i) {
在半球上随机采样一个方向 wi;
从 p 点发射射线 r(p, wi);
if (射线 r 击中了光源) {
L_o += (1/N) * BRDF * L_i * cosθ / pdf(wi);
}
// 如果击中非光源物体,则忽略(仅直接光照)
}
return L_o;
}
第二步:加入间接光照(递归问题)
直接光照只解决了光源的直接照射。真实的 L_i 也可能来自其他物体的反射(间接光照)。因此,当射线击中一个非光源的物体点 q 时,L_i(p, ω_i) 实际上等于在 q 点看向 -ω_i 方向出射的 Radiance L_o(q, -ω_i)。
这导致了递归计算:计算 p 点的着色需要先计算 q 点的着色。
伪代码修改如下:
shade(p, wo) {
L_o = 0.0;
for (int i = 0; i < N; ++i) {
在半球上随机采样一个方向 wi;
从 p 点发射射线 r(p, wi);
if (射线 r 击中了光源) {
L_o += (1/N) * BRDF * L_i * cosθ / pdf(wi);
} else if (射线 r 击中了物体 at q) {
L_o += (1/N) * BRDF * shade(q, -wi) * cosθ / pdf(wi); // 递归
}
}
return L_o;
}

第三步:解决光线数量爆炸问题(N=1)

上述算法有一个严重问题:光线数量指数爆炸。如果每次着色采样 N 根光线,每个击中点又采样 N 根,弹射 k 次后光线数量为 N^k,计算量无法承受。
关键观察:只有当 N = 1 时,光线数量不会爆炸(1^k = 1)。因此,我们令 N = 1。这意味着每次着色只随机采样一个方向打出一根光线。
此时,蒙特卡洛积分公式中的 (1/N) 因子变为 1。这种 N=1 的算法被称为路径追踪。而 N>1 的旧方法称为“分布式光线追踪”,现已很少使用。
为何可行?虽然单个像素的一次路径采样噪声很大,但我们可以对每个像素独立地多次发射初始光线(即多次路径追踪),然后将这些路径的结果*均起来,作为该像素的最终颜色。这样就能有效降低噪声。
伪代码修改:移除循环,每次只采样一个方向。
shade(p, wo) {
在半球上随机采样一个方向 wi;
从 p 点发射射线 r(p, wi);
if (射线 r 击中了光源) {
return BRDF * L_i * cosθ / pdf(wi);
} else if (射线 r 击中了物体 at q) {
return BRDF * shade(q, -wi) * cosθ / pdf(wi);
}
}

第四步:解决递归无限循环问题(俄罗斯轮盘赌)

第二个问题是:递归没有终止条件,算法会无限进行下去。但在物理世界中,光线能量会随着弹射衰减。我们引入俄罗斯轮盘赌(Russian Roulette, RR) 方法,以概率方式终止递归。
RR 方法:
- 预先设定一个继续追踪的概率
P_RR(例如 0.8)。 - 每次准备递归调用
shade()前,随机生成一个[0, 1)间的数ξ。 - 若
ξ < P_RR,则进行递归计算,并将返回值除以P_RR。 - 若
ξ ≥ P_RR,则终止递归,返回0。
原理:设真实的间接光照贡献应为 L_indirect。采用 RR 后,返回值的期望为:
E = P_RR * (L_indirect / P_RR) + (1 - P_RR) * 0 = L_indirect
因此,RR 在期望意义上是正确的,且保证了递归必然终止。
伪代码修改:
shade(p, wo) {
在半球上随机采样一个方向 wi;
从 p 点发射射线 r(p, wi);
if (射线 r 击中了光源) {
return BRDF * L_i * cosθ / pdf(wi);
} else if (射线 r 击中了物体 at q) {
float P_RR = 0.8;
if (random() < P_RR) {
return BRDF * shade(q, -wi) * cosθ / pdf(wi) / P_RR;
} else {
return 0.0;
}
}
}
第五步:提高采样效率(直接对光源采样)
当前算法在着色点向半球均匀采样,当光源较小时,大量采样方向会“浪费”(未击中光源),导致收敛慢、噪声大。
改进思路:直接在光源表面上进行采样,确保每次采样都击中光源(对于直接光照部分)。但这需要将渲染方程从对立体角 dω 的积分,改写为对光源面积 dA 的积分。
积分域变换关系:
dω = cosθ‘ * dA / |x’ - x|^2
其中:
θ‘是光源表面法线与(x’ - x)方向的夹角。|x’ - x|^2是着色点到光源采样点的距离*方。
将 dω 代入渲染方程,得到对光源面积 dA 的积分形式。此时,我们在光源表面均匀采样,PDF 为 1 / A(A 是光源面积)。
最终算法:将着色点接收的光分为两部分计算:
- 直接光照(来自光源):直接在光源上采样计算。需要检查光源和着色点之间是否有遮挡。
- 间接光照(来自其他物体):仍按之前的方法,在半球上采样,并用 RR 控制递归。
伪代码最终版:
shade(p, wo) {
// 1. 直接光照贡献(光源采样)
L_dir = 0.0;
在光源上均匀采样一个点 x‘, pdf_light = 1/A;
计算 wi = normalize(x’ - p);
发射射线 r(p, wi) 检查是否被遮挡;
if (射线 r 未被遮挡,且直达光源 x‘) {
L_dir = BRDF * L_i * cosθ * cosθ‘ / (|x’ - p|^2) / pdf_light;
}
// 2. 间接光照贡献(半球采样 + RR)
L_indir = 0.0;
float P_RR = 0.8;
if (random() < P_RR) {
在半球上按 pdf_hemi 采样一个方向 wi;
发射射线 r(p, wi);
if (射线 r 击中了非光源的物体 at q) {
L_indir = BRDF * shade(q, -wi) * cosθ / pdf_hemi / P_RR;
}
}
return L_dir + L_indir;
}
路径追踪的效果与意义
通过以上步骤,我们得到了一个完整的路径追踪算法。它是基于物理的、正确的全局光照算法。

其强大之处在于能模拟各种复杂的物理现象:
- 软阴影
- 光泽反射(Glossy Reflection)
- 颜色渗透(Color Bleeding)
- 间接光照

路径追踪可以实现照片级的真实感渲染。著名的 Cornell Box 场景,其路径追踪渲染结果与真实照片几乎无法区分,这证明了该算法的正确性与威力。
总结与拓展
本节课中,我们一起学*了:
- 回顾了渲染方程,它是基于物理的光线传播描述。
- 介绍了蒙特卡洛积分,一种通过随机采样求解定积分的数值方法。
- 推导了路径追踪算法:
- 从用蒙特卡洛解渲染方程开始。
- 通过设定
N=1解决光线爆炸问题。 - 通过俄罗斯轮盘赌(RR) 以概率方式终止递归。
- 通过直接对光源采样提高算法效率。
- 路径追踪是正确的全局光照算法,能实现照片级真实感渲染。
路径追踪是现代渲染的核心算法之一。它融合了物理、微积分、概率论和编程,虽然有一定难度,但深刻理解它将为你打开计算机图形学中基于物理渲染的大门。
附:一些进阶话题
- 重要性采样:如何选择更优的 PDF 以加速收敛?
- 低差异序列:如何生成质量更好的随机数?
- 多重重要性采样:如何结合不同采样策略(如半球采样和光源采样)的优势?
- 光子映射、双向路径追踪:更高级、更高效的全局光照算法。



渲染是一个博大精深的领域,路径追踪是一个绝佳的起点。敬畏科学,保持好奇,继续探索。

GAMES101-现代计算机图形学入门-闫令琪 - P17:Lecture 17 材质与外观 🎨

在本节课中,我们将学*计算机图形学中一个核心概念:材质与外观。我们将探讨光线如何与不同材质相互作用,从而产生千变万化的视觉效果。课程将从基本概念入手,逐步深入到基于物理的渲染模型,帮助你理解材质在渲染方程中的核心地位。
课程概述
本节课标志着光线追踪部分的圆满结束,后续内容将相对轻松。我们将聚焦于“材质与外观”这一主题,计划用两个课时讲解。本节课介绍基本概念,下节课探讨更复杂和前沿的内容。目标是理解材质的基本原理,并对当前研究方向有所了解。
课前事项
以下是课程开始前的几个通知:

- 作业六:提交情况良好(82分)。请注意,作业六有1.5周的提交时间,截止日期预计为本周五。
- 作业七:内容关于路径追踪,已基本制作完成。可参考上节课课件中的伪代码进行实现。预计本周五发布。
- 大作业:具体要求即将公布(预计本周或最晚下周二)。同学们可以提前思考自己感兴趣的方向。


从本节课开始,复杂的数学推导将大幅减少,后续课程会相对轻松。
什么是材质与外观? 🔍
材质与外观本质上是同一事物的两面。不同的材质在特定光照下会呈现出不同的外观。因此,我们研究的核心是光线与材质之间的相互作用方式。

自然界中存在无数材质,例如:
- 海浪:部分透明,且颜色受水深和盐度影响。
- 洞穴光柱:光线在空气中被微粒散射,从而能被侧向观察到。
- 头发:具有边缘透光的特性,涉及复杂的光线在发丝间的多次弹射。
- 布料:拥有与金属截然不同的独特外观属性。
- 蝴蝶翅膀:由无色鳞片堆叠产生结构色。
- 彩虹:涉及光线在水滴内的折射、反射和色散。
- 鱼肉等材质:表现出光线进入材质内部散射后再出射的现象,称为次表面散射(下节课详述)。


所有这些现象都表明,光线传播与材质属性紧密相连。研究材质,就是研究光线如何与材质作用。

图形学中的材质
在图形学中,我们通过为模型的不同部分指定材质来得到不同的渲染结果。例如,一个陶制盘子同时具有镜面反射(表面的釉层)和漫反射(底层的陶土)特性。
关键在于,在渲染方程中,材质属性由双向反射分布函数(BRDF)定义。
核心公式:材质 = BRDF

BRDF 描述了从某个方向入射的光线,在某个表面点上,被反射到另一个方向的比例分布。它直接决定了表面的外观。

漫反射材质(Diffuse)
漫反射材质将入射光线均匀地散射到各个方向。之前我们用经验模型定义漫反射系数,现在可以从物理角度正确定义。
我们考虑一个理想漫反射表面,假设入射光是均匀的(各方向Radiance相同)。根据能量守恒定律(表面不发光),入射的辐照度(Irradiance)应等于出射的辐照度。同时,对于理想漫反射,出射的Radiance也应是均匀的。
通过渲染方程推导,可以得出完全反射(不吸收)的漫反射BRDF值应为 1/π。
公式:对于具有反射率 ρ(0 ≤ ρ ≤ 1)的漫反射表面,其 BRDF 为 f_r = ρ / π。
其他材质类型
除了漫反射,还有多种材质类型:
- Glossy材质(光滑材质):类似抛光金属,具有方向性较强但非完美的镜面反射。反射光锥有一定展宽。
- 理想镜面反射:入射角等于反射角。
- 折射材质:如玻璃或水,光线部分反射,部分折射进入内部。折射遵循斯涅尔定律(Snell‘s Law)。
斯涅尔定律公式:η_i * sinθ_i = η_t * sinθ_t
其中 η 是折射率,θ 是光线与法线的夹角。
当光线从光密介质射向光疏介质(η_i > η_t)且入射角大于临界角时,会发生全反射。这也是为何在水下只能看到上方一个锥形区域(斯涅尔窗现象)。
折射对应的函数称为双向透射分布函数(BTDF)。BRDF 和 BTDF 统称为双向散射分布函数(BSDF)。
菲涅尔项(Fresnel Term)
菲涅尔现象描述了反射比例与入射角的关系。观察发现:
- 当视线与表面垂直时,反射很弱,大部分光线折射。
- 当视线与表面几乎*行(掠射角)时,反射非常强烈,表面像镜子一样。
绝缘体(如玻璃、水)和导体(如金属)的菲涅尔曲线不同。导体即使在垂直入射时也有很高的反射率。
精确计算菲涅尔项公式复杂,涉及光的极化。图形学中常用 Schlick‘s approximation 进行*似,效果很好且计算简单。
Schlick‘s approximation 公式:R(θ) = R_0 + (1 - R_0)(1 - cosθ)^5
其中 R_0 是垂直入射时的反射率,R_0 = ((η_1 - η_2) / (η_1 + η_2))^2。
微表面模型(Microfacet Model)
这是现代基于物理渲染(PBR)的核心模型。其核心思想是:
- 从宏观尺度看:看到的是材质的外观(粗糙或光滑)。
- 从微观尺度看:表面由无数微小的镜面组成。每个微表面都完美地镜面反射光线。
微表面模型认为:远处看到的是材质,*处看到的是几何。
表面的粗糙程度由微表面法线分布来描述:
- 分布集中 -> 表面光滑(Glossy)
- 分布分散 -> 表面粗糙(Diffuse)


微表面模型的 BRDF 通常由三项组成:
- 菲涅尔项(F):决定有多少能量被反射。
- 法线分布函数(D):描述微表面法线的分布,决定高光的大小和形状。
- 几何遮蔽项(G):考虑微表面之间的互相遮挡(阴影和掩蔽),尤其在掠射角时很重要。
该模型非常强大,能够统一描述从金属、塑料到皮革、木材等广泛材质。

各向同性与各向异性材质

根据微表面法线分布是否具有方向性,材质可分为两类:

- 各向同性(Isotropic):微表面法线分布没有主导方向。外观旋转不变。BRDF 只与入射和出射方向的相对方位角有关,因此是三维函数。
- 各向异性(Anisotropic):微表面法线分布有明确方向(如拉丝金属、CD光盘、 brushed hair)。外观随旋转变化。BRDF 与绝对方位角有关,是四维函数。

生活中许多人造材质是各向异性的,例如锅底(环形刷痕)、拉丝金属、天鹅绒(绒毛可被拨向一边)等。
BRDF 的性质总结
了解 BRDF 的性质对于理解和实现渲染算法至关重要:
- 非负性:
f_r(ω_i -> ω_r) ≥ 0 - 线性:BRDF 可分解为多个部分,分别计算光照后再叠加。
- 可逆性(Helmholtz互易性):交换入射和出射方向,BRDF 值不变。
f_r(ω_i -> ω_r) = f_r(ω_r -> ω_i) - 能量守恒:反射出的总能量不能超过入射能量。
∫_Ω f_r(ω_i -> ω_r) cosθ_r dω_r ≤ 1 - 各向同性时:BRDF 只与方位角差的绝对值有关,维度从四维降至三维。
BRDF 的测量与存储
理论模型是对物理的*似,最准确的方法是直接测量。使用类似“球形测量仪”的设备,固定样本,遍历不同的光源方向(入射)和相机方向(出射),测量 Radiance,从而得到 BRDF 数据。
挑战在于 BRDF 是四维函数(各向异性),数据量巨大(“维度灾难”)。对于各向同性材质,可降至三维。利用可逆性等性质可以优化测量方案。
测量后需要高效存储。著名的 MERL BRDF 数据库 存储了100种各向同性材质的测量数据,每个材质采样 90x90x180 个点,数据量庞大。如何压缩和表示这些数据也是研究课题(如使用神经网络)。
课程总结
本节课我们一起学*了计算机图形学中材质与外观的基础知识:
- 理解了材质在渲染方程中由 BRDF 定义的核心概念。
- 推导了基于物理的漫反射 BRDF 公式(ρ/π)。
- 认识了反射、折射、菲涅尔现象及其物理定律。
- 学*了现代渲染的基石——微表面模型,及其三个核心项(F、D、G)。
- 区分了各向同性与各向异性材质。
- 总结了 BRDF 的重要性质。
- 了解了 BRDF 的测量方法与数据挑战。



下节课我们将探讨更复杂的材质现象(如次表面散射)以及一些前沿的渲染技术。希望大家能对材质如何决定物体外观有一个清晰而深入的认识。

GAMES101-现代计算机图形学入门-第18课:高级渲染专题 🚀
在本节课中,我们将学*计算机图形学中一些高级的光线传播方法和材质建模技术。这些内容虽然被称为“高级”,但其核心思想并不比路径追踪更复杂,只是作为入门课程,我们不会深入技术细节。本节课旨在拓宽视野,了解图形学领域的前沿方向。
课程安排与作业通知 📅
在进入正题之前,先说明一下课程相关的安排。
作业七目前正在由助教团队紧张准备中。这是一个关于路径追踪的新增作业,由于涉及整个框架的实现和调试,发布时间可能稍有延迟,请大家理解。
关于课程大作业,安排如下:
- 下周二(14号)将发布大作业的参考选题。
- 之后有几天时间供大家思考和确定自己的选题。
- 欢迎大家就自己的创意想法咨询老师或助教。
- 到19号需提交一份简要的提案。
- 之后有约两周半的时间完成大作业,并在5月5号提交。
注意事项:
- 选题需属于图形学范畴,而非人机交互或计算机视觉等领域。
- 大作业以个人为单位完成,不组队。
高级光线传播方法 💡
上一节我们介绍了课程安排,本节中我们来看看几种高级的光线传播方法。我们将从“无偏”和“有偏”这两个核心概念开始。
无偏与有偏估计
在基于蒙特卡洛方法的渲染中,我们通过采样来估计积分值。这里有两个重要概念:
- 无偏估计:无论使用多少样本,其估计值的期望始终等于真实的积分值。例如,标准的路径追踪就是无偏的。
- 有偏估计:估计值的期望与真实值不相等。
- 在有偏估计中,存在一种特殊情况:当使用的样本数量趋*于无穷多时,估计值会收敛到真实值。这种情况我们称之为一致的。
在渲染中,一个直观的理解是:如果渲染结果存在模糊(与真实物理结果相比),那么它就是有偏的;如果这种模糊能随着样本数增加而消失,最终收敛到清晰结果,那么它就是一致的。
双向路径追踪 (Bidirectional Path Tracing, BDPT)
顾名思义,双向路径追踪从两个方向生成路径。
- 从相机出发生成一系列子路径。
- 从光源出发生成另一系列子路径。
- 然后连接这些子路径的端点,形成完整的光路。
核心思想:完整路径 = 相机子路径 + 光源子路径
BDPT在处理某些特定场景时效果显著,例如当场景主要由间接光照亮时(如光源照亮天花板,再反射到整个房间)。在这种情况下,从相机出发的路径很难直接找到能量集中的光源区域,而从光源出发的路径则能更有效地找到这些路径。
优缺点:
- 优点:在适合的场景下,能用更少的样本获得比单向路径追踪更好的效果。
- 缺点:实现非常复杂,计算速度通常也更慢。
Metropolis 光线传播 (Metropolis Light Transport, MLT)
MLT 基于马尔可夫链蒙特卡洛方法。其核心思想是:给定一个样本(一条光路),可以在其附*生成新的、相似的样本。
核心思想:新路径 = 对现有路径进行局部扰动
这种方法特别擅长渲染困难的光路,例如:
- 光路难以找到的场景:如光线只能通过门缝进入的房间。
- 焦散 (Caustics):如游泳池底的光斑,其光路模式(如镜面-漫反射-镜面,S-D-S)非常难以通过随机采样捕获。
MLT 的优势在于,只要找到一条“种子”路径,就能在其周围探索出大量相似的有效路径。
优缺点:
- 优点:特别擅长处理复杂、困难的光路传播。
- 缺点:
- 收敛速度难以预测,不知道需要渲染多久才能得到无噪声图像。
- 由于是局部采样,图像不同区域收敛速度不一致,容易产生“脏”的、斑驳的结果。
- 不适合渲染动画,因为帧与帧之间的噪声会剧烈抖动。
光子映射 (Photon Mapping)
光子映射是一种有偏但一致的方法,它特别擅长渲染焦散效果。其过程分为两步:

第一步:光子追踪
从光源向场景发射“光子”。光子与场景交互(反射、折射),直到击中一个漫反射表面才停止,并被记录在该位置。

第二步:光线追踪
从相机出发进行光线追踪,生成子路径,同样在击中漫反射表面时停止。
第三步:密度估计
对于相机子路径击中的每个着色点,寻找其最*的 N 个光子。通过计算这 N 个光子所覆盖的表面积 A,来估计该点的光子密度。

核心公式:密度 ≈ N / A
为什么是有偏但一致的?
我们用有限面积 A 内的*均密度,来*似一个无限小面积 da 上的真实密度,这本身就引入了偏差(模糊)。但是,当发射的光子总数趋向无穷多时,对于固定的 N,其覆盖的面积 A 会趋向于无限小,从而使估计收敛到正确结果。因此它是一致的。
如果改为固定搜索面积 A 并计数其中的光子数,那么即使光子数无限多,估计也不会收敛到正确值,这就不是一致的了。
顶点连接与合并 (Vertex Connection and Merging, VCM)

VCM 是双向路径追踪和光子映射的结合体。

核心思想:当双向路径追踪生成的两条子路径的端点非常接*,但无法直接连接时(例如端点几乎在同一位置),不将其丢弃。而是将其中一条子路径视为“光子”,使用类似光子映射的密度估计方法来计算其对最终颜色的贡献。这样结合了两种方法的优点。

实时辐射度算法 (Instant Radiosity, IR)

实时辐射度算法,也称为“多光源”方法,其思想非常直接。

核心思想:
- 创建虚拟点光源:从真实光源发射光线子路径,在路径击中的漫反射表面上创建一系列虚拟点光源。
- 直接光照计算:在着色时,将这些虚拟点光源当作直接光源来照亮着色点。


核心思想:间接光照 ≈ 大量虚拟点光源的直接光照之和
这种方法速度很快,因为它将间接光照问题转化为了多直接光源照明问题。
存在问题:
- 光源 singularity 问题:当虚拟点光源与着色点距离极*时,光照强度公式中的
1/r²项会导致数值爆炸,产生不合理的亮斑。 - 无法处理光泽表面:VPL 方法通常假设表面是漫反射的。
高级外观建模 🎨
上一节我们探讨了高级的光线传播方法,本节中我们来看看如何对更复杂的材质外观进行建模。这不仅仅是表面模型,还涉及体积、毛发等。
非表面模型

散射介质 (Participating Media)


散射介质是指光能在其中传播并被吸收或散射的物质,如雾、云、烟等。
核心特性:
- 吸收:光线在介质中传播时能量会衰减。
- 散射:光线在介质中的微小粒子(如冰晶、尘埃)上改变方向。
- 相位函数:定义了在某个点上,光线从某个方向散射到另一个方向的概率分布。这类似于表面的 BRDF,但是发生在体积内部。

渲染散射介质时,光线在介质内部传播时,会在随机点发生散射或终止,其渲染思想与表面路径追踪类似,但积分域是整个体积空间。


毛发与毛发模型
渲染毛发不能简单地用表面模型,因为毛发是细长的曲线。

Marschner 模型:
将单根毛发视为一个玻璃圆柱体。光线与毛发作用时,主要考虑三种路径:
- R:表面直接反射。
- TT:折射进入毛发,穿透后折射出去。
- TRT:折射进入,在内部反射一次,再折射出去。
这个模型能很好地模拟人类头发的高光。

双层圆柱模型 (Double Cylinder Model):
为了更真实地模拟动物毛发,需要考虑毛发的髓质层。该模型将毛发视为内外两层圆柱(表皮层和髓质层)。光线在髓质层中会发生额外的散射,因此除了 R, TT, TRT 外,还增加了考虑髓质散射的路径(如 TT^s, TRT^s)。这个模型能更好地表现动物毛发蓬松、透光的质感。


多次散射:
光线会在多根毛发之间弹射,计算量巨大,但这是毛发渲染真实感的关键。



颗粒材质 (Granular Material)

如沙子、盐、糖等由大量颗粒堆积而成的材质。渲染这种材质需要模拟无数微小颗粒的光线相互作用,计算量非常庞大。一种简化方法是统计性地描述不同成分颗粒的混合比例。
表面模型

次表面散射 (Subsurface Scattering)


次表面散射是指光线进入材质内部,经过多次散射后从另一点射出的现象,如皮肤、玉石、蜡烛。它不能用标准的 BRDF 描述。
核心概念:BSSRDF
BSSRDF 是 BRDF 的扩展,它描述了光线从一个点以某个方向进入,从另一个点以某个方向出射的分布。
渲染方程扩展:Lo(xo, ωo) = ∫_A ∫_{2π} S(xi, ωi; xo, ωo) Li(xi, ωi) cosθi dωi dA
这意味着计算时不仅要对入射方向积分,还要对表面其他点的面积积分,计算非常复杂。

*似方法:偶极子模型
一种高效的*似是将次表面散射的效果,等效为在入射点下方放置两个虚拟光源(一个正方向,一个负方向),用它们来照亮周围的表面点。这种方法能很好地模拟皮肤、玉石等材质的柔和透光感。

布料 (Cloth)

布料由纤维缠绕编织而成,其建模有多种层次:
- 表面模型:将布料视为一个*面,使用各向异性的 BRDF 模拟编织纹理带来的高光。这是最简单快速的方法。
- 体积模型:将布料视为一个充满纤维的体积,用渲染散射介质的方法来渲染。更真实,但计算量大。
- 纤维级模型:将每一根纤维都像渲染头发一样单独渲染。效果最真实,计算量也最大。

复杂微表面细节与波动光学


现实世界的表面充满划痕、磨损等微观细节。简单地使用法线贴图结合微表面模型渲染这些细节极其耗时,因为镜面反射的光路很难被捕获。

解决方案:微表面理论拓展
考虑一个像素所覆盖的区域内所有微表面的法线分布,用这个统计分布来替代简单的解析分布函数。这样就能高效地渲染出由微观细节聚合而成的宏观高光效果。

波动光学效应
当表面细节的尺寸与光的波长相当时,必须考虑光的波动性(干涉、衍射),而不能再用几何光学。这会导致高光区域出现彩色的、不连续的光谱条纹,这在某些高度抛光的金属或带有细微划痕的表面上可以观察到。

程序化生成外观 (Procedural Appearance)

程序化生成是指不预先存储庞大的纹理数据,而是通过一个数学函数在需要时实时计算材质属性。


核心工具:噪声函数
例如柏林噪声 (Perlin Noise),它是一个定义在空间中的函数 Noise(x, y, z),输入空间坐标,返回一个值。通过对噪声函数进行各种变换和组合,可以生成木头纹理、大理石纹理、云层、地形高度等复杂而自然的效果。


优势:
- 无限分辨率:因为是函数,可以查询任意精细级别的细节。
- 内存占用小:无需存储大量纹理数据。
- 三维一致性:可以生成三维纹理,物体被切开后内部纹理也是连续的。

总结 📝

本节课我们一起学*了计算机图形学中一些高级的渲染话题。

在光线传播部分,我们了解了:
- 无偏方法:如双向路径追踪和 Metropolis 光线传播,它们能提供理论上准确的结果,但各有其适用的场景和代价。
- 有偏但一致的方法:如光子映射,它通过引入可控的偏差(模糊)来换取效率,特别擅长渲染焦散。
- 混合方法:如 VCM,结合了不同方法的优点。
- *似方法:如实时辐射度算法,将间接光照转化为直接光照问题以实现快速计算。
在外观建模部分,我们探索了:
- 非表面模型:如散射介质、毛发和颗粒材质,它们需要超越表面渲染的特殊处理。
- 表面模型进阶:如次表面散射对 BRDF 的扩展,以及布料渲染的不同层次。
- 微观细节:如何高效渲染微观几何细节及其带来的波动光学效应。
- 程序化生成:如何通过数学函数动态生成复杂、逼真且内存高效的材质。



目前并不存在一个“完美”的渲染方法能解决所有问题。在工业界,路径追踪因其可靠性和相对简单的实现,仍然是许多电影级渲染的首选。理解不同方法的原理和权衡,有助于我们在面对不同渲染需求时做出合适的选择。

GAMES101-现代计算机图形学入门-闫令琪 - P19:相机、透镜与光场 📷
在本节课中,我们将学*计算机图形学中一个相对独立但至关重要的组成部分:相机。我们将从最简单的针孔相机模型开始,逐步深入到现代相机中复杂的透镜系统,并解释光圈、快门、ISO等核心概念如何共同影响最终的成像效果。理解这些原理,不仅有助于我们更好地进行图形渲染,也能让我们明白真实世界中的摄影技术。


概述:从捕捉到成像


上一节我们介绍了光线追踪等合成成像方法。本节中,我们来看看另一种成像方式:捕捉。捕捉是指将真实世界中存在的事物转化为图像,最典型的工具就是相机。相机作为图形学的重要组成部分,其背后的物理原理是我们今天探讨的核心。



相机的基本原理
针孔相机:最简单的模型
成像最古老、最简单的原理是小孔成像。其核心是光线直线传播:场景中的点发出的光线,只有穿过小孔的那一部分,才能在另一侧的*面上形成一个倒立的像。这种相机称为针孔相机。

公式:针孔相机模型是我们在光线追踪中常用的简化模型。它没有景深效果,所有物体都清晰成像。

现代相机:复杂的透镜系统
真实的相机远比针孔复杂,它们使用透镜组来汇聚光线。为了简化分析,我们引入一个关键概念:薄透镜*似。我们假设存在一个理想化的薄透镜,它具有以下性质:
- *行于透镜主轴的光线,经过折射后会汇聚到一点,该点称为焦点。
- 穿过焦点的光线,经过折射后会变成*行光。
- 穿过透镜中心的光线,方向不发生改变。
对于薄透镜,物距(物体到透镜的距离 (z_o))、像距(成像*面到透镜的距离 (z_i))和焦距 (f) 满足高斯透镜公式:
[
\frac{1}{f} = \frac{1}{z_o} + \frac{1}{z_i}
]
这个公式是理解透镜成像的基础。

相机的核心组件与概念
了解了基本模型后,我们来看看构成一张照片的几个核心要素:视野、曝光三要素(光圈、快门、ISO)以及景深。

视野 (Field of View, FOV)
视野决定了相机能“看到”多大范围的角度。它由传感器尺寸和焦距共同决定。
公式:对于给定的传感器高度 (h) 和焦距 (f),垂直视野 (\alpha)(以弧度为单位)可以通过下式计算:
[
\tan(\frac{\alpha}{2}) = \frac{h}{2f}
]
焦距越短,或传感器越大,视野就越广。

在摄影中,通常以35mm胶片为基准,用“等效焦距”来描述视野。例如,一个“28mm镜头”意味着在35mm传感器上能提供28mm焦距的视野。
曝光三要素

曝光决定了图像的明暗程度。曝光量 (H) 定义为:
[
H = E \times t
]
其中 (E) 是传感器单位面积接收到的辐照度 (Irradiance),(t) 是曝光时间。相机通过三个参数控制曝光:光圈、快门速度和ISO。


以下是控制曝光度的三个核心参数及其影响:
-
光圈 (Aperture)
- 作用:控制单位时间内进入相机的光量。光圈越大,进光越多。
- 表示:用 f数 (f-number或f-stop) 表示,记为 (N)。(N = f / D),其中 (f) 是焦距,(D) 是光圈孔径直径。f数越小,光圈越大(如f/1.4是大光圈,f/16是小光圈)。
- 其他影响:光圈大小直接影响景深(后文详述)。
-
快门速度 (Shutter Speed)
- 作用:控制曝光时间。时间越长,进光量越多。
- 表示:通常以分数形式表示(如1/1000秒)。
- 其他影响:快门时间长短会导致运动模糊。快速运动的物体在长时间曝光下会变模糊。在图形学中,模拟运动模糊可以避免时间采样上的走样。
-
ISO (感光度)
- 作用:可以理解为信号放大器。它线性地放大传感器记录到的信号。
- 代码逻辑:
最终信号 = 传感器原始信号 × ISO增益。 - 代价:提高ISO会同时放大噪声,导致图像出现颗粒感。因此,通常优先调整光圈和快门,ISO作为最后手段。

这三个参数相互制衡,不同的组合可以得到相同曝光度但视觉效果迥异的照片(例如,大光圈浅景深 vs. 小光圈长曝光带来的运动模糊)。

景深 (Depth of Field) 与弥散圆
在薄透镜模型中,只有处于焦*面上的物体才会在传感器上清晰成像。不在焦*面上的点,其光线经过透镜后,会在传感器前后汇聚,从而在传感器上形成一个弥散圆 (Circle of Confusion, CoC)。
公式:弥散圆直径 (c) 的*似计算公式为:
[
c = A \frac{|z_s - z_i|}{z_i}
]
其中 (A) 是光圈直径,(z_s) 是传感器到透镜的距离,(z_i) 是像距。由此可知,光圈越大(A越大),弥散圆越大,模糊越明显。


景深就是指在场景中,能产生“足够小”的弥散圆(我们认为图像清晰)的那一段深度范围。光圈越小(f数越大),景深越深,清晰的范围就越大。
在渲染中模拟真实相机
理解了薄透镜模型后,我们可以在光线追踪中模拟真实相机,从而渲染出带有景深效果(虚化)的图像。
算法思路:
- 为相机定义传感器*面、透镜(包含焦距 (f) 和光圈大小 (A))以及期望的焦*面距离 (z_o)。
- 根据高斯透镜公式,计算对应的传感器距离 (z_i)。
- 对于传感器上的每个像素点 (x'),不再像针孔模型那样发射单一光线,而是:
- 在透镜光圈范围内随机选择一个点 (x'')。
- 连接 (x') 和 (x''),根据透镜公式计算出这条光线在焦*面上的交点 (x''')。
- 从 (x'') 向 (x''') 方向发射光线进入场景进行追踪。
- 该光线携带的辐亮度 (Radiance) 最终贡献给像素 (x')。
- 通过大量采样,不在焦*面上的物体就会因为光线路径的发散而形成模糊,即景深效果。


总结
本节课我们一起学*了相机成像的核心原理。我们从基础的针孔相机模型出发,引入了理想的薄透镜模型及其成像公式。然后,我们详细剖析了构成一张照片的关键要素:视野 (FOV) 决定了画面范围;曝光三要素(光圈、快门、ISO) 共同控制画面明暗,并分别影响景深、运动模糊和图像噪声;最后,我们利用弥散圆的概念解释了景深现象,并了解了如何在光线追踪渲染中模拟真实的相机镜头效果。掌握这些知识,是连接图形学合成成像与真实世界摄影捕捉的重要桥梁。



课程内容源自 GAMES101-现代计算机图形学入门 第19讲。

GAMES101-现代计算机图形学入门-闫令琪 - P2:Lecture 02 Review of Linear Algebra - GAMES-Webinar - BV1X7411F744
课程概述
在本节课中,我们将从最基础的内容开始,逐步构建图形学的基础知识。图形学依赖于多种学科,包括数学、物理、信号处理和数值分析等。本节课将重点介绍线性代数,特别是向量、矩阵及其操作。

线性代数基础

向量
向量是表示方向和长度的量。在图形学中,向量用于表示点、方向和力等。
向量表示:

- 向量可以用箭头表示,例如 \(\vec{a}\) 或 \(\vec{a} \rightarrow\)。
- 向量也可以用坐标表示,例如 \(\vec{a} = (x, y, z)\)。

向量属性:
- 方向: 向量的方向由起点指向终点。
- 长度: 向量的长度表示其大小。
单位向量:
- 单位向量是长度为1的向量,表示方向。
- 单位向量可以用 \(\hat{a} = \frac{\vec{a}}{|\vec{a}|}\) 表示。
向量运算
向量加法:
- 向量加法可以用*行四边形法则或三角形法则表示。
- 向量加法满足交换律、结合律和分配律。
向量点乘:
- 向量点乘是两个向量的长度和它们之间夹角的余弦的乘积。
- 向量点乘可以用来计算两个向量之间的夹角、投影和方向关系。
向量叉乘:
- 向量叉乘是两个向量的长度和它们之间夹角的正弦的乘积,并乘以一个垂直于这两个向量的单位向量。
- 向量叉乘可以用来判断两个向量的左和右、内和外关系,以及建立三维空间中的直角坐标系。


矩阵
矩阵是按行和列排列的数字数组。
矩阵乘法:
- 矩阵乘法是将一个矩阵的每一行与另一个矩阵的每一列相乘,然后将结果相加。
- 矩阵乘法满足结合律和分配律,但不满足交换律。
矩阵转置:
- 矩阵转置是将矩阵的行和列互换。
- 矩阵转置满足 \((AB)^T = B^T A^T\)。
单位矩阵:
- 单位矩阵是一个对角线元素为1,其余元素为0的矩阵。
- 单位矩阵乘以任何矩阵都等于该矩阵本身。
总结
本节课介绍了线性代数的基础知识,包括向量、矩阵及其运算。这些知识是图形学的基础,对于理解图形学中的各种算法和模型至关重要。



本节课中我们一起学*了:
- 向量和矩阵的基本概念
- 向量运算,包括加法、点乘和叉乘
- 矩阵运算,包括乘法、转置和逆矩阵



下一节课我们将继续学*变换,包括*移、旋转和缩放。

GAMES101-现代计算机图形学入门-闫令琪 - P20:颜色与感知 👁️🎨
概述
在本节课中,我们将要学*计算机图形学中关于颜色与感知的核心知识。我们将从上一节课遗留的“光场”概念开始,深入探讨其原理与应用,然后系统地学*颜色的物理基础、人眼感知机制以及各种颜色空间。课程内容旨在让初学者能够理解这些看似复杂但至关重要的概念。
上节课遗留问题:光场
上一节我们介绍了相机模型,本节中我们来看看一个相关的概念——光场。
光场,也被称为全光函数,描述了在空间中任意位置、任意方向上的光线强度。它是一个七维函数,包含了位置、方向、波长和时间信息。但在实际应用中,我们通常使用其简化版本,即四维光场,它只记录物体表面任意一点向任意方向发出的光线强度。
我们可以用两个*行的*面来参数化光场。一个*面(UV*面)记录观察位置,另一个*面(ST*面)记录观察方向。连接两个*面上任意两点,就定义了一条唯一的光线。通过记录所有可能的(U, V)和(S, T)组合对应的光线强度,我们就得到了整个光场。
以下是理解光场参数化的关键点:
- UV*面固定:这相当于在不同的观察位置(像摄像机阵列)拍摄场景,得到一系列不同视角的二维图像。
- ST*面固定:这相当于从同一个观察点,记录成像*面上每一个像素点接收到的、来自不同方向的光线。这类似于昆虫的复眼结构或光场相机的原理。
光场相机 📸
理解了光场的概念后,我们来看看它的一个直接应用——光场相机。
传统相机的每个像素记录的是来自各个方向光线的辐照度。而光场相机在感光元件前加入了一层微透镜阵列。每个微透镜会将打到其上的、来自不同方向的光线分散到后方感光元件的一片区域上。这样,原本一个像素记录的一个辐照度值,就被“展开”为一片区域记录的多个辐射度值,即记录了光线的方向信息。


拥有了完整的光场信息后,光场相机可以实现传统相机无法做到的功能:
- 先拍照,后对焦:拍照后,可以通过算法模拟不同焦距的透镜效果,选择让场景中不同距离的*面变清晰。
- 虚拟移动视点:通过选择光场中不同方向的光线进行合成,可以模拟从不同位置观察场景的效果。

当然,光场相机也有其局限性,最主要的是空间分辨率与方向分辨率的权衡。为了记录方向信息,需要牺牲一部分空间分辨率,导致对传感器分辨率要求极高,成本也随之增加。


颜色的物理基础 🌈

现在,我们进入本节课的核心主题——颜色。首先,我们需要了解颜色的物理本质。
颜色源于光。白光通过棱镜会被分解成不同颜色的光谱,这说明白光是由不同波长的光混合而成的。描述光在各个波长上能量分布的曲线,称为光谱功率分布。
SPD具有线性可加性。这意味着多种光线混合后的SPD,等于各自SPD的简单相加。这是颜色混合的理论基础。
然而,颜色本身并不是一个纯粹的物理量,而是人类视觉系统的一种感知。接下来,我们将探讨人眼是如何感知颜色的。
人眼如何感知颜色 👁️
人眼类似于一个摄像机,视网膜上的感光细胞负责接收光线。感光细胞主要分为两类:
- 视杆细胞:感知光线的强度,用于形成黑白视觉。
- 视锥细胞:感知颜色。它又分为三种类型,分别对短(S)、中(M)、长(L)波长的光最为敏感。
当光线进入眼睛,其SPD会分别与三种视锥细胞的响应曲线进行积分运算,最终得到三个数值(S, M, L)。大脑正是根据这三个数值来“感觉”到某种颜色。
这个过程引出了一个重要现象:同色异谱。即两种光谱组成(SPD)完全不同的光线,经过人眼三种视锥细胞积分后,可能产生完全相同的(S, M, L)响应值,从而被人感知为同一种颜色。这正是颜色再现技术(如显示器显示颜色)的基础——我们不需要复现真实世界的光谱,只需要复现对人眼的刺激效果即可。


颜色匹配与RGB系统 🎨

为了定量地描述和复现颜色,科学家进行了颜色匹配实验。给定一种目标颜色,用三种原色(通常是红、绿、蓝)以不同强度进行混合,直到混合出的颜色在视觉上与目标颜色无法区分。
实验发现,为了匹配某些单波长的颜色,有时需要将一种原色“加”到待匹配色一侧,这等价于在混合色一侧使用“负值”的该原色。由此,国际照明委员会定义了 CIE RGB 系统,其颜色匹配函数在某些波长区域为负值。
基于颜色匹配实验,我们建立了加色系统(如显示器),即通过将不同强度的原色光叠加来产生新颜色。最常见的加色系统是sRGB,它定义了红、绿、蓝三原色的标准,广泛应用于数字设备。
颜色空间 🌐
为了更科学、更直观地表示颜色,人们定义了多种颜色空间。
- CIE XYZ 色彩空间:这是一个由CIE定义的人造、与设备无关的色彩空间。其颜色匹配函数均为正值,且覆盖了整个可见光谱。其中,Y分量直接对应于颜色的亮度。通过对XYZ进行归一化(x=X/(X+Y+Z), y=Y/(X+Y+Z), z=Z/(X+Y+Z)),并固定Y值,可以在二维*面上绘制出色域图,它能直观展示一个颜色系统所能表示的所有颜色范围。
- HSV/HSL 色彩空间:这是为艺术家设计的感知性色彩空间。
- H:色调,表示颜色的类型(如红、黄、蓝)。
- S:饱和度,表示颜色的纯度(从灰色到纯色)。
- V/L:明度/亮度,表示颜色的明暗程度。
- CIE Lab 色彩空间:另一个感知均匀的色彩空间。L表示明度,a轴表示红-绿对立,b轴表示黄-蓝对立。它基于人眼对互补色的感知(如长时间看红色后,再看白色会看到绿色)。
减色系统 🖨️
与显示器的加色系统不同,印刷、绘画使用的是减色系统。当颜料混合时,它们吸收(减去)特定波长的光,反射剩下的光,因此混合越多,颜色越暗。
最常见的减色系统是CMYK:
- C:青色
- M:品红色
- Y:黄色
- K:黑色
理论上,CMY混合可以得到黑色,但实际印刷中单独使用黑色墨水(K)成本更低、效果更好,因此形成了CMYK标准。







颜色的相对性 🧠






最后,我们必须强调颜色的感知是相对的,深受周围环境和大脑解释的影响。经典的“棋盘阴影错觉”证明,两个物理上完全相同的灰色块,在不同的背景和阴影暗示下,会被感知为截然不同的明暗度。这再次印证了颜色是一种主观的心理物理体验。



总结
本节课中我们一起学*了图形学中关于光场与颜色的核心知识。
我们首先解决了上节课的遗留问题,深入理解了光场的概念、参数化方法及其在光场相机中的应用。随后,我们系统性地探讨了颜色:从物理上的光谱,到人眼通过三种视锥细胞感知颜色的生理机制,以及由此产生的同色异谱现象。我们介绍了基于颜色匹配实验建立的CIE RGB和sRGB系统,并讲解了多种重要的颜色空间,如CIE XYZ、HSV和Lab,它们分别服务于理论分析、艺术设计和感知均匀性等不同目的。最后,我们区分了用于显示的加色系统和用于印刷的减色系统,并通过视觉错觉认识到颜色感知的相对性。



这些知识是理解数字图像处理、色彩管理和真实感渲染的基石。

GAMES101-现代计算机图形学入门-第21讲:动画入门 🎬
在本节课中,我们将学*计算机动画的基本概念、历史发展以及几种核心的制作方法。我们将从动画的定义出发,了解其从手绘到计算机生成的发展历程,并重点介绍关键帧动画、物理模拟、质点弹簧系统、粒子系统、运动学以及动作捕捉等关键技术。
概述
动画的本质是让物体“活”起来,即通过连续播放一系列静态图像来产生运动错觉。在计算机图形学中,动画可以视为对几何建模在时间维度上的扩展。本节课将系统性地介绍动画的基本原理、历史里程碑以及多种实现技术。

动画的定义与历史
动画最初的定义是“赋予生命”,即让静态物体动起来。它作为一种交流和艺术表达工具,更侧重于美学效果而非物理准确性。在计算机图形学中,动画可理解为在不同时间点具有不同几何形状的模型序列。

动画的形成依赖于人眼的视觉暂留效应。常见的帧率标准包括电影的24 FPS、视频的30 FPS、游戏的60 FPS以及虚拟现实要求的90 FPS。




动画发展里程碑





以下是动画发展过程中的几个关键里程碑:

- 早期探索:古代壁画已包含动态狩猎场景的序列图像,体现了动画的基本思想。
- 机械动画:使用旋转圆盘等机械装置,通过窗口观察序列图像,形成早期动画。
- 电影诞生:最初用于科学研究(如分析马的奔跑),后逐渐发展为娱乐产业。
- 第一部手绘剧场版动画:1937年的《白雪公主和七个小矮人》。
- 早期计算机动画:1963年已出现使用交互式笔进行三维模型操作的演示。
- CGI里程碑:
- 《侏罗纪公园》(1993年):首次在电影中大规模使用电脑生成的恐龙。
- 《玩具总动员》(1995年):第一部完全由计算机生成的剧场版动画电影。
- 现代动画:如《冰雪奇缘2》(2019年),集成了复杂的物理模拟、细节渲染和特效。







关键帧动画 🖼️


上一节我们回顾了动画的历史,本节我们来看看最基础的动画制作方法之一——关键帧动画。
关键帧动画的核心思想是:艺术家只需绘制出动作序列中最重要的帧(即关键帧),中间过渡的帧可以通过计算自动生成。
关键帧定义了动画的起止和转折等关键姿态。中间帧则通过插值算法生成。例如,一个角色从举起钩子到最高点的过程,只需定义起始和结束两个关键帧,中间手臂的位置可以通过插值计算得出。
插值技术是关键帧动画的核心。最简单的插值是线性插值,但为了得到更自然*滑的运动,通常会使用样条曲线等更高级的插值方法,以保证运动速度的连续性(如C1连续)。
物理模拟 ⚙️

上一节我们介绍了基于艺术创作的关键帧动画,本节中我们来看看另一种基于物理规律的方法——物理模拟。


物理模拟(或物理仿真)通过求解物理方程来计算物体的运动。其最基础的理论是牛顿第二定律:F = ma(力等于质量乘以加速度)。只要知道物体所受的合力及其初始状态(位置、速度),就可以通过积分计算出它在后续时刻的运动状态。

质点弹簧系统



质点弹簧系统是物理模拟中一个简单而强大的模型,由一系列通过弹簧连接的质点构成。它可以用来模拟绳子、布料、头发等多种物体。

一个基础的弹簧单元连接两个质点 a 和 b。根据胡克定律,弹簧产生的力与它的形变成正比,方向试图使其恢复原长。
弹簧力公式(应用于质点a):
f_a→b = -k_s * (|b - a| - l) * ( (b - a) / |b - a| )
其中:
k_s是弹簧的劲度系数。l是弹簧的原长。(b - a) / |b - a|是从 a 指向 b 的单位方向向量。
为了模拟能量损耗(如空气阻力、内部摩擦),使运动最终停止,需要引入阻尼力。一种常用的阻尼力模型与质点的相对速度在弹簧方向上的投影有关:
阻尼力公式(应用于质点b):
f_damping = -k_d * ( (ḃ - ȧ) · ((b - a) / |b - a|) ) * ((b - a) / |b - a|)
其中:
k_d是阻尼系数。ḃ和ȧ分别是质点 b 和 a 的速度。·表示点积,用于计算相对速度在弹簧方向上的分量。
布料模拟的弹簧结构
使用质点弹簧系统模拟一块布料时,简单的网格连接(仅连接水*和垂直相邻点)无法真实表现布料的物理特性。需要增加连接以抵抗不应有的形变:
以下是构建布料模型时常见的弹簧连接类型:
- 结构弹簧:连接水*和垂直相邻质点,提供基础支撑。
- 剪切弹簧:连接对角线上的相邻质点,用于抵抗剪切变形(防止布料被拉成菱形)。
- 弯曲弹簧:连接间隔一个质点的相邻质点(如上-下-上,左-右-左),用于抵抗*面外的弯曲,使布料具有适当的刚度。
通过组合这些不同类型的弹簧,可以构建出能够逼真模拟布料下垂、飘动等行为的质点弹簧系统。
粒子系统 ✨
上一节我们学*了用于模拟连续体(如布料)的质点弹簧系统,本节我们来看看用于模拟群体现象的粒子系统。
粒子系统用于模拟大量微小、独立的粒子集合,如烟雾、火焰、水流、鸟群等。每个粒子都具有位置、速度、生命周期等属性。
粒子系统模拟的基本流程是一个循环:
以下是粒子系统模拟每一帧的核心步骤:
- 生成新粒子:根据规则产生新的粒子(如烟花发射点)。
- 计算受力:为每个粒子计算所受的力,包括外力(重力、风力)和粒子间的内力(引力、斥力、碰撞)。
- 更新状态:根据受力更新每个粒子的位置和速度。
- 移除旧粒子:删除生命周期结束的粒子。
- 渲染:根据粒子的最终位置进行渲染(可能渲染为点、小球或精灵图)。
粒子系统的挑战在于高效地处理粒子间的作用力(需要空间加速结构)以及碰撞检测。
群体模拟
粒子系统的思想可以扩展到群体模拟(如鸟群、鱼群),通过为每个个体(粒子)定义简单的行为规则来涌现出复杂的群体智能。常见的规则包括:
以下是描述群体中个体行为的三个基本规则:
- 分离:避免与邻*的个体相撞。
- 对齐:调整运动方向,与邻*个体的*均方向保持一致。
- 聚合:向邻*个体的*均位置靠拢,避免离群。






通过为每个粒子(个体)编程实现这些基于局部邻居的规则,整个系统就能呈现出逼真的群体运动。



运动学 🤖

运动学描述物体的运动而不考虑其原因(力)。在计算机动画中,运动学分为正向运动学和逆向运动学。


正向运动学
正向运动学用于描述具有关节链的模型(如机器人手臂、人体骨骼)。给定每个关节的旋转角度或位移,计算末端执行器(如手)的位置。
对于一个简单的两段关节链,末端位置 p 可以通过以下公式计算:
p = L1 * [cos(θ1), sin(θ1)] + L2 * [cos(θ1+θ2), sin(θ1+θ2)]
其中 L1, L2 是段长,θ1, θ2 是关节角。


正向运动学计算简单直接,但艺术家通过调整角度来控制末端位置非常不直观。
逆向运动学
逆向运动学解决相反的问题:给定末端执行器期望的位置,反推各个关节应有的参数(如角度)。
逆向运动学比正向运动学复杂得多,通常没有解析解,或者解不唯一、甚至无解。因此,通常将其转化为一个优化问题,使用数值方法(如梯度下降法)进行求解,目标是让末端位置尽可能接*目标位置。



绑定与动作捕捉 🎭

绑定



绑定是为虚拟角色创建控制系统(控制点)的过程,使动画师能够像操纵木偶一样方便地操控角色做出各种姿态和表情。这通常涉及设置骨骼层次结构、蒙皮权重以及设计直观的控制曲线/控制器。


动作捕捉


动作捕捉是一种技术,通过记录真实演员的运动数据,并将其映射到虚拟角色上,以快速生成逼真的动画。

以下是几种主要的动作捕捉技术:

- 光学式:在演员身上粘贴反光标记点,使用多台高速摄像机追踪标记点的三维运动。这是最主流的方法,但存在遮挡问题。
- 惯性式:在演员身上佩戴陀螺仪、加速度计等传感器,直接测量肢体运动。不受遮挡影响,但可能存在累积误差。
- 机械式:使用外骨骼机械装置直接测量关节角度。精度高,但会限制演员活动。
- 视觉式:仅使用普通摄像机,通过计算机视觉算法从视频中估计人体姿态。成本低,但精度和鲁棒性通常是挑战。

动作捕捉能极大提高动画制作效率,并获取高度真实的运动数据,但获取的数据通常需要动画师进行清理、调整和艺术化加工。


动画制作全流程 🏭




一个完整的现代三维动画电影制作流程包含多个环节:

以下是动画电影从构思到成品的典型生产管线:
- 故事与概念设计:编写剧本,绘制故事板和概念图。
- 建模:创建角色、场景、道具的三维模型。
- 绑定:为角色模型搭建骨骼和控制系统。
- 动画:通过关键帧、运动捕捉或模拟技术让角色和物体动起来。
- 特效模拟:使用粒子系统、流体模拟等技术制作火、水、烟、魔法等效果。
- 布光:在虚拟场景中设置灯光,营造氛围。
- 渲染:计算最终图像,这是一个计算密集型过程,通常在大型渲染农场完成。
- 合成与后期:将渲染的各层图像(角色、背景、特效)合并,并进行调色、添加二维特效等处理。
- 最终输出:剪辑、配乐、音效,生成最终影片。
总结
本节课中我们一起学*了计算机动画的基础知识。我们从动画的定义和历史出发,探讨了关键帧动画这一传统方法,并深入了解了基于物理规律的模拟技术,包括质点弹簧系统和粒子系统。接着,我们学*了描述关节链运动的运动学(正向与逆向),以及业界广泛使用的绑定和动作捕捉技术。最后,我们概述了一个完整的动画电影制作流程。



下节课,我们将聚焦于这些模拟背后的数学求解方法,例如欧拉方法等,探讨如何将物理公式转化为计算机可以计算的离散步骤,从而真正让虚拟世界动起来。

GAMES101-现代计算机图形学入门-第22讲:动画(续)🎬
在本节课中,我们将继续学*动画与仿真的核心内容。我们将从上一节介绍的基本概念出发,深入探讨如何通过数值方法具体模拟物体的运动。课程将涵盖欧拉方法、提高稳定性的改进方法、刚体模拟以及流体模拟的基础知识。
课程安排与公告 📢
上一节我们介绍了动画仿真的基本概念,本节中我们来看看具体的实现方法。在开始之前,先说明几项课程相关事宜。
以下是关于课程作业与证书的安排:
- 大作业截止日期已延长一周。若需更多时间,可在论坛反馈。
- 作业七目前提交数量较少,建议同学们多花时间完成。之后会重新开启补交通道。
- 课程结束后,将为完成所有作业并合格的同学提供带有签名的电子版证书。
- 领取证书需提交个人信息,届时会建立一个名为“Certification Request”的虚拟作业供大家提交。
从概念到方法:求解运动轨迹 🧮
我们已知物体的速度定义为其位置对时间的导数,即 v(t) = dx(t)/dt。若已知初始位置 x(t0) 和一个定义了任意位置与时间对应速度的速度场,我们的目标是求解未来任意时刻 t 物体的位置 x(t)。
这引出了一个一阶常微分方程(ODE):dx/dt = v(x, t)。我们需要在已知初始条件 x(t0) = x0 下求解此方程。
数值解法的核心思想是对时间进行离散化。我们设定一个时间步长 Δt,从初始时刻开始,逐步计算 t + Δt, t + 2Δt ... 等时刻的位置。
欧拉方法及其问题 ⚠️
最直观的数值方法是(前向/显式)欧拉方法。它使用上一时刻的量来估计下一时刻的量。
其更新公式为:
v(t + Δt) = v(t) + a(t) * Δt
x(t + Δt) = x(t) + v(t) * Δt
然而,欧拉方法存在两个主要问题:
- 误差:步长 Δt 越大,单步误差越大,累积误差也越大。减小步长可以降低误差。
- 不稳定性:对于某些系统(如守恒力场),无论步长多小,欧拉方法模拟的结果都会逐渐偏离真实轨迹,甚至无限发散,导致模拟崩溃。这在图形学中是需要解决的核心问题。
改进的数值方法 🔧
为了解决不稳定性等问题,人们提出了多种改进方法。
中点法
中点法的核心思想是:先用欧拉方法走半步,用半步终点的速度来修正整个步长的前进方向。
步骤如下:
- 计算初始位置
x(t)用欧拉方法走半步到达的位置a。 - 计算
x(t)和a的中点b,并获取b点处的速度。 - 用
b点的速度,从初始位置x(t)出发,应用欧拉方法走一个完整的步长 Δt,得到最终位置x(t + Δt)。
这种方法相当于用二次模型来*似局部运动,比线性*似的欧拉方法更精确。
自适应步长法
该方法动态调整步长以保证精度。其思路是:
- 用步长 Δt 做一次欧拉积分,得到位置
X1。 - 将步长减半为 Δt/2,连续做两次欧拉积分,得到位置
X2。 - 比较
X1和X2的差异。若差异很大,说明当前步长 Δt 过大,需要进一步减小;若差异很小,则认为当前步长足够精确。
隐式欧拉方法
隐式(后向)欧拉方法使用下一时刻的导数来更新当前状态。
其更新公式为:
v(t + Δt) = v(t) + a(t + Δt) * Δt
x(t + Δt) = x(t) + v(t + Δt) * Δt
由于等式两边都包含未知的下一时刻量 (t + Δt),这形成了一个需要求解的方程组(例如用牛顿迭代法),计算比显式方法更复杂。但其优点是具有更好的数值稳定性。
龙格-库塔方法
这是一族高精度的ODE求解方法,其中最常用的是四阶龙格-库塔法(RK4)。它通过计算多个中间点的导数并进行加权*均,来获得更高阶的*似精度。RK4是四阶方法,意味着其全局误差与 O(Δt^4) 成正比,精度远高于一阶的欧拉方法。
非物理方法:基于位置的动力学 🎯
在图形学中,有时我们更关注视觉效果而非物理绝对正确。基于位置的动力学(PBD) 方法通过直接调整顶点的位置来满足约束条件(如弹簧保持原长),而非求解物理方程。
例如在绳子模拟中,Verlet积分 就是一种简单的PBD方法。它先让顶点自由运动,再将其拉回以满足弹簧长度约束。这种方法实现简单、速度快,但通常不严格遵循物理规律(如能量守恒),模拟中可能会有明显的能量损耗。
刚体模拟 🤖
刚体是指形状不会发生变化的物体。其运动可以分解为质心的*动和绕质心的转动。

因此,描述一个刚体的状态需要四个量:
- 位置
x和朝向(角度)θ - 线速度
v和角速度ω
它们满足以下关系:
dx/dt = v
dθ/dt = ω
dv/dt = a (加速度,由受力决定)
dω/dt = α (角加速度,由力矩决定)
这样,刚体的模拟就转化为对一个具有更多状态变量的“粒子”进行ODE求解,可以使用之前讨论的任何数值方法。
流体模拟入门 💧
流体模拟的一个常见思路是拉格朗日视角(质点法),即将流体视为大量微小粒子的集合。每个粒子携带质量、速度等属性,通过模拟所有粒子的运动来表现流体。
一个典型的非物理方法是假设流体不可压缩,即密度恒定。模拟步骤为:
- 根据粒子当前位置,计算空间各处的密度。
- 若某处密度与目标密度(静水密度)不同,则计算密度场关于每个粒子位置的梯度。
- 沿着梯度下降的方向,调整每个粒子的位置,使该处密度回归目标值。
- 重复此过程,粒子位置的变化即表现为流体的运动。

这种方法本质上是使用梯度下降来优化位置以满足约束,属于基于位置的方法。

模拟的两种视角:拉格朗日 vs 欧拉 🔄
大规模物质模拟主要有两种思路:

- 拉格朗日法(质点法):追踪每个物质点的运动。如上述的粒子流体。
- 欧拉法(网格法):将空间划分为固定网格,关注每个网格单元内物质属性的变化(如密度、速度),而非追踪具体粒子。
*年来,物质点法(MPM) 等混合方法结合了两者优点:粒子携带材质属性,在欧拉网格上进行物理计算,再将结果写回粒子。
课程总结与展望 🎉
本节课中,我们一起学*了动画与仿真后半部分的核心内容。
我们首先探讨了如何通过数值方法(如欧拉法)求解运动微分方程,并分析了其误差和不稳定性问题。接着,我们介绍了一系列改进方法,包括中点法、自适应步长、隐式欧拉以及高精度的龙格-库塔方法。此外,我们还了解了基于位置的非物理模拟方法、刚体模拟的基本原理,以及以质点法为基础的流体模拟入门知识。最后,我们区分了拉格朗日和欧拉这两种基本的模拟视角。

至此,GAMES101《现代计算机图形学入门》的全部22讲课程已圆满结束。我们从光栅化、几何、光线传播到动画仿真,系统地学*了现代图形学的基础知识与核心概念。希望大家能以此为契机,继续在图形学的广阔领域中探索前行。



感谢大家一路以来的支持!课程虽已结束,但学*与探索永不停止。祝大家在图形学的道路上一切顺利!
GAMES101-现代计算机图形学入门-闫令琪 - P23:番外:纪念16万观看 - GAMES-Webinar - BV1X7411F744
概述
在本节课中,我们将回顾闫令琪教授在GAMES101课程中的一次特别番外,纪念该课程观看人次突破16万。
番外内容
观看人次突破
哈喽大家好,各位亲爱的同学们,我们又见面了。今天是2020年7月8号,今天给大家带来一期番外,因为我们的game 101啊,今天突破了16万观看人次。
感谢支持
我想都不敢想,这是感谢大家的一路支持对吧。这么整的一个数字,然后咱们不庆祝一下,可惜了,对不对。我本来是想喝红酒的,家里面没有了,我就拿快乐水顶一下,这个意思意思到了。
视频录制
这个呢我今天下午录了段视频,给大家献丑了啊,然后大家记得元素观看啊,这就开始。
课程进展
咱们的games 101,现在已经顺利结课对吧。然后大家应该各自已经收到了,这个成绩通知,然后如果大家成绩通过的话,现在是提交结业证书申请的时候。
补充说明
有同学会问,如果这次错过了,电子101的课和作业怎么办对吧,说我一直在b站上学*,然后就没没来得及,按照这个官方渠道去提交嘛,那怎么办呢。我们之后啊会重新去继续开放game 901这门课,然后定期的,然后会开放这个作业提交的通道,所以说大家之后可以补没有问题。
还请大家多多关注这个,咱们的games*台的网站,然后和微信群好吧。
新课筹备
我个人这边有一门实时渲染的新课,正在积极筹备中啊,应该很快可以和大家见面。
总结
那么啊就到这吧,那这次就到这儿。下次如果再赶上一个什么数字的话啊,然后到时候再给大家出三问啊,感谢大家的一路支持啊。
总结
本节课中我们一起学*了闫令琪教授在GAMES101课程中的一次特别番外,纪念该课程观看人次突破16万。

GAMES101-现代计算机图形学入门-闫令琪 - P3:Lecture 03 变换 🌀
在本节课中,我们将要学*计算机图形学中一个核心概念——变换。我们将从二维变换开始,理解其数学表示,并引入齐次坐标这一重要工具来统一处理包括*移在内的各种变换。最后,我们会将概念扩展到三维空间。

课程概述与回顾

上一节课我们介绍了向量的基本定义、点乘和叉乘操作,以及它们在图形学中的应用。这些知识是理解变换的基础。

本节课我们将主要探讨变换。首先,我们会了解为什么需要变换以及变换的应用场景。接着,我们将学*几种常见的二维变换:缩放、反射、切变和旋转,并理解它们的矩阵表示。然后,我们会引入齐次坐标的概念,以解决*移变换的特殊性问题。最后,我们会学*如何组合不同的变换,并简要介绍三维变换。
为什么要学*变换?🎬
变换在计算机图形学中无处不在,主要有两大应用方向。
上一节我们介绍了动画,动画本质上就是一系列变换的组合。例如,摄像机沿着*滑曲线移动,或者一个机器人的关节进行复杂的旋转和移动,这些都是通过变换来描述的。变换还可以表示缩放等效果,例如皮克斯动画中台灯压扁字母的经典场景。
除了动画,变换在成像过程中也至关重要。光栅化成像方法大量涉及变换。例如,将三维世界投影到二维*面上,这个过程本身就是一种非常重要的变换——投影变换。我们这几节课学*变换,正是为后续理解三维到二维的投影变换做铺垫。
二维变换
二维变换的核心是将变换操作与矩阵乘法联系起来。通过矩阵,我们可以简洁地描述点坐标的变化。
缩放变换
缩放变换是最简单的变换之一,它改变图像在x轴和y轴方向上的大小。
以下是缩放变换的要点:
- 如果一个点的原始坐标是
(x, y),缩放后变为(x‘, y’)。 - 当x和y方向缩放比例相同时,变换关系为:
x‘ = s * x,y‘ = s * y。 - 这个关系可以写成矩阵形式:
[x‘; y‘] = [[s, 0], [0, s]] * [x; y]。 - 如果x和y方向的缩放比例不同(分别为
sx和sy),则矩阵为:[[sx, 0], [0, sy]]。
公式:
缩放矩阵: [[sx, 0], [0, sy]]
反射变换
反射变换,或称镜像变换,使图像关于某个轴进行对称翻转。


以下是反射变换的要点:
- 关于y轴的反射:
x‘ = -x,y‘ = y。 - 其矩阵形式为:
[[-1, 0], [0, 1]]。
公式:
关于y轴反射矩阵: [[-1, 0], [0, 1]]
切变变换
切变变换类似于将图像像一块有弹性的材料一样水*或垂直拉扯。


以下是切变变换的要点:
- 考虑一个水*切变的例子:图像顶部被向右拉动,底部固定。
- 观察发现,任何点的y坐标在变换前后不变:
y‘ = y。 - 点的x坐标变化量与它的y坐标成正比:
x‘ = x + a * y。 - 因此,变换矩阵为:
[[1, a], [0, 1]]。
公式:
水*切变矩阵: [[1, a], [0, 1]]
旋转变换
旋转变换让图像绕原点按逆时针方向旋转一个角度。
以下是旋转变换的要点:
- 默认旋转中心是原点
(0,0),默认方向是逆时针。 - 旋转角度记为 θ。
- 通过分析特殊点
(1,0)和(0,1)旋转后的坐标,可以推导出旋转矩阵。
公式:
旋转矩阵: R(θ) = [[cosθ, -sinθ], [sinθ, cosθ]]
线性变换与齐次坐标
上一节我们介绍的缩放、反射、切变和旋转都有一个共同点:它们都可以写成 x‘ = A * x 的形式,其中A是一个2x2矩阵,x是点的坐标向量。这类变换称为线性变换。
然而,*移变换 (x‘ = x + tx, y‘ = y + ty) 无法写成这种单一的矩阵乘法形式,它需要一个额外的向量加法。这使得*移成为一个“特殊”的变换。
为什么要引入齐次坐标?
为了能用统一的方式(即单一的矩阵乘法)表示包括*移在内的所有变换,我们引入了齐次坐标。
其核心思想是:为二维空间中的点和向量增加一个维度。
- 点
(x, y)的齐次坐标表示为(x, y, 1)。 - 向量
(x, y)的齐次坐标表示为(x, y, 0)。
这样定义的好处是:
- 统一表示:*移变换现在可以写成一个3x3矩阵乘以齐次坐标向量的形式。
公式:
应用该矩阵于点*移矩阵: [[1, 0, tx], [0, 1, ty], [0, 0, 1]](x, y, 1),得到(x+tx, y+ty, 1),完美表达了*移。 - 保持向量性质:向量
(x, y, 0)经过*移矩阵变换后仍是(x, y, 0),这符合“向量具有*移不变性”的几何意义。 - 运算一致性:点与点的减法得到向量,点与向量的加法得到点,这些运算在齐次坐标表示下依然成立。
对于齐次坐标 (x, y, w) (w ≠ 0),它实际表示二维空间中的点是 (x/w, y/w)。这允许我们用非1的w值来表示点,例如两个点 (x1, y1, 1) 和 (x2, y2, 1) 相加得到 (x1+x2, y1+y2, 2),它表示的是这两点的中点 ((x1+x2)/2, (y1+y2)/2)。
仿射变换的齐次坐标表示
缩放、旋转等线性变换与*移变换组合在一起,称为仿射变换。在齐次坐标下,任何二维仿射变换都可以用一个3x3矩阵表示:
通用形式:
[[a, b, tx],
[c, d, ty],
[0, 0, 1]]
其中,左上角的2x2子矩阵 [[a, b], [c, d]] 表示线性变换部分,右边列的 [tx, ty]^T 表示*移部分,最后一行固定为 [0, 0, 1]。
变换的组合与分解
复杂的变换通常由一系列简单的变换组合而成。理解变换的组合与分解至关重要。
变换的顺序很重要
变换的应用顺序不同,最终结果也会不同。在数学上,这对应着矩阵乘法不满足交换律。
例如,先*移 T(1,0) 后旋转 R(45°),与先旋转 R(45°) 后*移 T(1,0),得到的是完全不同的图像。
矩阵乘法的应用顺序
在齐次坐标表示下,对一个点应用一系列变换 A1, A2, ..., An,其矩阵运算应写成:
P‘ = An * ... * A2 * A1 * P
其中 P 是点的齐次坐标。矩阵的应用顺序是从右到左,即先应用 A1,最后应用 An。
根据矩阵的结合律,我们可以先将所有变换矩阵 An * ... * A2 * A1 乘起来,得到一个单一的复合变换矩阵 M,然后用 M 乘以点坐标 P。这意味着无论多复杂的仿射变换,最终都可以用一个3x3矩阵来描述。
变换的分解:绕任意点旋转
我们之前定义的旋转是绕原点进行的。如果想绕任意点 c 旋转,可以通过变换分解来实现:
以下是实现步骤:
- 将整个图形*移
-c,使得旋转中心c移动到原点。 - 执行绕原点的旋转
R(θ)。 - 将图形*移
+c,将旋转中心移回原位置。
这个复合变换的矩阵为:T(c) * R(θ) * T(-c)。这再次体现了矩阵应用从右到左的顺序。
扩展到三维变换 🧊
将二维变换的思想推广到三维空间是非常直接的。
三维空间中的点和向量也可以用齐次坐标表示:
- 点:
(x, y, z, 1) - 向量:
(x, y, z, 0)
三维空间中的仿射变换可以用一个4x4矩阵统一表示:
通用形式:
[[a, b, c, tx],
[d, e, f, ty],
[g, h, i, tz],
[0, 0, 0, 1]]
其中,左上角的3x3子矩阵表示三维线性变换(缩放、旋转等),右边列的 [tx, ty, tz]^T 表示*移,最后一行固定为 [0, 0, 0, 1]。
变换的应用顺序规则与二维相同:先进行线性变换部分,再进行*移。
课程总结
本节课我们一起学*了计算机图形学中的变换。
我们首先了解了变换在动画和成像中的广泛应用。然后,我们深入探讨了缩放、反射、切变和旋转这几种基本二维变换的矩阵表示,并引入了线性变换的概念。
为了解决*移变换无法融入线性变换体系的问题,我们学*了齐次坐标这一强大工具。它通过增加一个维度,使得所有仿射变换都能用单一的矩阵乘法来表示,极大地简化了变换的描述和计算。
我们还学*了变换的组合与分解,理解了变换顺序的重要性(矩阵乘法不可交换),以及如何通过矩阵的复合来表示复杂变换。最后,我们将这些概念自然地推广到了三维空间。



掌握变换的数学基础是理解后续三维投影、视图变换等高级图形学概念的基石。

GAMES101-现代计算机图形学入门-闫令琪 - P4:Lecture 04 变换(续)🚀



在本节课中,我们将深入学*变换的更多内容,特别是三维变换和观测变换。观测变换是图形学中将三维场景转换为二维图像的核心步骤,包括视图变换和投影变换。我们将详细讲解正交投影和透视投影的原理与实现。
课程内容概述
上节课我们介绍了基础的二维变换,包括旋转、缩放和错切。我们提到*移变换的特殊性,并引入了齐次坐标来统一表示所有变换。本节课我们将首先补充一个关于旋转矩阵的重要性质,然后深入讲解三维变换,并重点介绍观测变换的完整流程。
补充:旋转矩阵的性质
在深入新内容之前,我们先补充一个上节课遗漏的重要知识点。在二维变换中,旋转θ角度的矩阵为:
R(θ) = [ cosθ, -sinθ ]
[ sinθ, cosθ ]
那么,旋转-θ角度的矩阵应为:
R(-θ) = [ cosθ, sinθ ]
[ -sinθ, cosθ ]
观察可知,R(-θ) 恰好是 R(θ) 的转置矩阵(即行和列互换)。同时,从定义上看,旋转-θ角度是旋转θ角度的逆操作。因此,我们得到一个重要结论:旋转矩阵的逆等于其转置。
在数学上,如果一个矩阵的逆等于其转置,我们称该矩阵为正交矩阵。这个性质在后续推导中会用到。
回顾:齐次坐标与变换合成
上一节我们介绍了旋转矩阵的性质,本节我们回顾一下上节课的核心内容。我们学*了如何使用齐次坐标统一表示线性变换(旋转、缩放)和*移变换。在二维中,一个点 (x, y) 用齐次坐标表示为 (x, y, 1),一个向量表示为 (x, y, 0)。变换矩阵则扩展为3x3矩阵,其左上角2x2子矩阵表示线性变换,最后一列的前两个元素表示*移。
这种表示法的好处是,复杂的变换可以通过矩阵乘法合成。并且,根据结合律,我们可以先计算变换矩阵的乘积,再应用于点或向量。
三维变换
理解了二维变换后,三维变换的思路是类似的。在三维空间中,我们使用齐次坐标将点 (x, y, z) 表示为 (x, y, z, 1),向量表示为 (x, y, z, 0)。相应的变换矩阵从3x3变为4x4。
以下是三维空间中的基本变换:
- 缩放:缩放矩阵是一个4x4对角阵,
sx,sy,sz分别表示在x, y, z轴上的缩放因子。S(sx, sy, sz) = [ sx, 0, 0, 0 ] [ 0, sy, 0, 0 ] [ 0, 0, sz, 0 ] [ 0, 0, 0, 1 ] - *移:*移矩阵的左上角3x3是单位矩阵,最后一列的前三个元素
tx,ty,tz表示*移量。T(tx, ty, tz) = [ 1, 0, 0, tx ] [ 0, 1, 0, ty ] [ 0, 0, 1, tz ] [ 0, 0, 0, 1 ] - 旋转:三维旋转相对复杂。最简单的旋转是绕x, y, z轴的旋转。
- 绕x轴旋转α角度:
Rx(α) = [ 1, 0, 0, 0 ] [ 0, cosα, -sinα, 0 ] [ 0, sinα, cosα, 0 ] [ 0, 0, 0, 1 ] - 绕y轴旋转β角度(注意正负号与x, z轴不同):
Ry(β) = [ cosβ, 0, sinβ, 0 ] [ 0, 1, 0, 0 ] [ -sinβ, 0, cosβ, 0 ] [ 0, 0, 0, 1 ] - 绕z轴旋转γ角度:
Rz(γ) = [ cosγ, -sinγ, 0, 0 ] [ sinγ, cosγ, 0, 0 ] [ 0, 0, 1, 0 ] [ 0, 0, 0, 1 ]
- 绕x轴旋转α角度:
一个重要的概念是,任何复杂的三维旋转都可以分解为绕x, y, z轴旋转的组合,这三个旋转角被称为欧拉角。
观测变换概述
学*了三维变换后,我们进入本节课的核心——观测变换。观测变换的目的是将三维场景中的物体投影到二维*面上,形成图像。这个过程类比拍照:
- 模型变换:摆放好物体(摆pose)。
- 视图变换:调整相机的位置和角度。
- 投影变换:按下快门,将三维场景投影到二维胶片上。
这三步合称为MVP变换(Model, View, Projection)。
视图变换
视图变换对应着摆放相机。要定义一个相机,我们需要三个参数:
- 相机位置
e(eye position)。 - 相机看向的方向
g(gaze direction)。 - 相机向上的方向
t(up direction)。
为了简化后续计算,图形学中有一个约定:将相机变换到一个标准位置。我们总是将相机放置在原点 (0,0,0),让其看向负Z轴方向 (0,0,-1),并以Y轴方向 (0,1,0) 作为向上方向。
视图变换的目标就是将一个任意位置和朝向的相机,通过一个变换矩阵 M_view,移动到标准位置。同时,为了保持相机与场景中物体的相对关系不变,我们需要对场景中的所有物体应用同样的 M_view 变换。
M_view 矩阵可以通过以下两步构建:
- *移:将相机位置
e*移到原点。*移矩阵为T_view = T(-e.x, -e.y, -e.z)。 - 旋转:将相机的朝向
g旋转到-Z轴,将向上方向t旋转到Y轴,将g×t方向旋转到X轴。
直接写出这个旋转矩阵 R_view 比较困难。一个技巧是先求其逆变换:将标准坐标系的X轴 (1,0,0) 旋转到 g×t 方向,将Y轴 (0,1,0) 旋转到 t 方向,将Z轴 (0,0,1) 旋转到 -g 方向。这个逆变换矩阵 R_inv 很容易写出:
R_inv = [ (g×t).x, (g×t).y, (g×t).z, 0 ]
[ t.x, t.y, t.z, 0 ]
[ -g.x, -g.y, -g.z, 0 ]
[ 0, 0, 0, 1 ]
由于旋转矩阵是正交矩阵,其逆等于转置。因此,我们需要的旋转矩阵 R_view = (R_inv)^T。
最终,视图变换矩阵为:M_view = R_view * T_view。先*移相机到原点,再进行旋转对齐坐标轴。
投影变换
完成视图变换后,相机位于标准位置。接下来需要进行投影变换,将三维空间中的物体投影到二维*面上。投影变换主要分为两种:正交投影和透视投影。
正交投影
正交投影没有“*大远小”的效果,通常用于工程制图。其核心思想是:将观察空间中的一个长方体区域(由左l、右r、下b、上t、*n、远f六个面定义)映射到标准立方体 [-1,1]^3 中。
注意:由于相机看向负Z轴,所以离相机更*的*面(**面)其Z值
n更大,离相机更远的*面(远*面)其Z值f更小,即n > f。
正交投影变换 M_ortho 可以通过以下两步完成:
- *移:将长方体的中心*移到原点。*移矩阵为
T_ortho = T(-(r+l)/2, -(t+b)/2, -(n+f)/2)。 - 缩放:将长方体缩放为标准立方体。缩放矩阵为
S_ortho = S(2/(r-l), 2/(t-b), 2/(n-f))。
因此,M_ortho = S_ortho * T_ortho。
透视投影
透视投影模拟人眼成像,具有“*大远小”的效果,*行线在投影后不再*行(如铁轨交汇于一点)。透视投影的观察空间是一个视锥体(Frustum)。
透视投影的推导相对复杂。一个巧妙的方法是将其分解为两步:
- 将视锥体“挤压”成一个长方体。
- 对这个长方体进行正交投影。
我们主要关注第一步“挤压”变换 M_persp->ortho。考虑从侧面(YOZ*面)观察视锥体。根据相似三角形原理,对于视锥体内任意一点 (x, y, z),其挤压后的y坐标 y' = (n/z) * y。同理,x' = (n/z) * x。
这意味着,点 (x, y, z, 1) 经过挤压变换后,应该与点 (nx, ny, unknown, z) 表示三维空间中的同一个点(因为齐次坐标乘以同一个非零常数表示同一个点)。由此,我们可以推断出变换矩阵 M_persp->ortho 的部分元素。
为了完全确定矩阵,我们利用两个已知条件:
- **面上的点不变:**面
z=n上的任何点(x, y, n, 1)在挤压后位置不变。 - 远*面中心点不变:远*面中心点
(0, 0, f, 1)在挤压后位置不变,且其z坐标仍为f。
利用这两个条件建立方程组,可以解出矩阵中剩余未知的元素。最终推导出的 M_persp->ortho 矩阵为:
M_persp->ortho = [ n, 0, 0, 0 ]
[ 0, n, 0, 0 ]
[ 0, 0, n+f, -n*f ]
[ 0, 0, 1, 0 ]
因此,完整的透视投影矩阵为:M_persp = M_ortho * M_persp->ortho。
思考题:在透视投影的“挤压”变换中,对于视锥体内
z = (n+f)/2处的点,其z坐标是向**面n靠*,还是向远*面f靠*?可以通过计算M_persp->ortho对z坐标的变换结果来分析。
总结
本节课我们一起学*了图形学中变换的进阶内容。
我们首先补充了旋转矩阵是正交矩阵的重要性质。然后,我们将二维变换的知识推广到三维,介绍了三维空间中的缩放、*移和旋转变换。
本节课的重点是观测变换。我们了解到,将三维场景转换为二维图像需要三个步骤(MVP):
- 模型变换:摆放场景中的物体。
- 视图变换:将相机移动到标准位置(原点,看-Z,上Y),并对所有物体应用相同变换以保持相对关系。
- 投影变换:将三维坐标投影到二维*面。我们详细讲解了两种投影:
- 正交投影:将长方体映射到标准立方体,无*大远小效果。
- 透视投影:通过“挤压”视锥体为长方体再进行正交投影来实现,具有*大远小效果,更符合视觉感知。
透视投影矩阵的推导是本课的难点,但其核心思想是利用齐次坐标的性质和几何关系(相似三角形)来求解变换矩阵。
理解这些变换是理解现代图形渲染管线的基础。在后续课程中,我们将看到这些矩阵如何被应用到顶点上,从而将三维世界呈现在二维屏幕上。







GAMES101-现代计算机图形学入门-05:光栅化(三角形)📐
在本节课中,我们将学*如何将经过一系列变换后、位于标准立方体(-1到1的三次方)内的三维物体,最终绘制到二维屏幕上。这个过程的核心步骤被称为光栅化。我们将从定义屏幕空间开始,逐步讲解如何通过采样方法判断像素与三角形的关系,从而完成三角形的光栅化。
课程概述与回顾
上一节课我们介绍了观测变换,包括模型变换、视图变换和投影变换。经过这些变换后,场景中的所有物体都被映射到一个标准的规范化立方体(Canonical Cube)中,其坐标范围在x、y、z三个轴上均为-1到1。
那么,下一步就是将这个立方体内的物体画到屏幕上。本节课,我们就来探讨这个“画到屏幕上”的过程——光栅化。我们将聚焦于如何将三角形这种基础图元转换为屏幕上的像素。
屏幕空间的定义 🖥️
在开始光栅化之前,我们需要明确定义什么是“屏幕”。
- 对于图形学而言,屏幕可以抽象为一个二维数组,数组中的每个元素称为一个像素(Pixel)。
- 屏幕的分辨率(如1920x1080)指明了这个二维数组的宽度和高度。
- 我们将屏幕空间视为一个坐标系:
- 原点
(0, 0)定义在屏幕的左下角。 - X轴正方向向右,Y轴正方向向上。
- 原点
- 每个像素可以用整数坐标
(x, y)来索引,其中x的范围是[0, width-1],y的范围是[0, height-1]。 - 像素本身是一个小方块,其中心点的坐标是
(x + 0.5, y + 0.5)。 - 因此,整个屏幕空间覆盖的连续区域是:X轴从
0到width,Y轴从0到height。
视口变换 🔄
现在,我们有了标准立方体(-1到1)和定义好的屏幕空间(0到width,0到height)。连接这两者的桥梁就是视口变换。
视口变换的目标,是将标准立方体的x和y坐标(暂时忽略z坐标)从范围 [-1, 1] 线性映射到屏幕空间的范围 [0, width] 和 [0, height]。
这个变换可以分解为两步:
- 缩放:将
[-1, 1]的范围(总长度为2)缩放到屏幕的宽度和高度。- X方向缩放因子:
width / 2 - Y方向缩放因子:
height / 2
- X方向缩放因子:
- *移:将缩放后的中心点(原本在(0,0))*移到屏幕中心
(width/2, height/2)。
将这两步结合,可以得到视口变换矩阵:
M_viewport = [[width/2, 0, 0, width/2],
[0, height/2, 0, height/2],
[0, 0, 1, 0],
[0, 0, 0, 1]]
应用这个矩阵后,物体在x-y*面上的投影就位于屏幕空间中了。
为什么是三角形?🔺
在光栅化中,我们通常处理的基本图元是三角形。这是因为三角形具有许多优良性质:

- 基础性:三角形是最简单的多边形,任何复杂的多边形都可以被分解为多个三角形。
- *面性:给定三个顶点,它们必然确定一个唯一的*面,不会出现四边形可能存在的非共面问题。
- 清晰的内外定义:判断一个点是否在三角形内部有明确且高效的算法(例如使用叉积)。
- 插值属性:在三角形内部,任何点的属性(如颜色、纹理坐标)都可以通过三个顶点的属性进行*滑插值得到(后续课程会详细讲解重心坐标插值)。

光栅化:采样方法 📊
光栅化的核心任务,是确定屏幕上的哪些像素应该被“点亮”以表示一个给定的三角形。最直接的方法是通过采样。
采样,简单来说,就是将一个连续函数在离散点上的值求出来。在图形学中,采样无处不在。

对于三角形光栅化,我们定义这样一个函数:
inside(triangle, x, y)
这个函数接收一个三角形和屏幕空间中的一个点坐标 (x, y),返回 1 如果该点在三角形内部,否则返回 0。
那么,光栅化过程就转化为:对屏幕空间内所有像素的中心点,采样这个 inside 函数。

以下是实现这一过程的伪代码:
for y in range(0, screen_height):
for x in range(0, screen_width):
if inside(triangle, x + 0.5, y + 0.5): # 判断像素中心点
framebuffer[y][x] = triangle_color # 设置帧缓冲区颜色
else:
framebuffer[y][x] = background_color

判断点是否在三角形内 📐

上一节提到的 inside 函数如何实现?我们可以利用向量的叉积性质。
给定三角形顶点 P0, P1, P2(按一定顺序,例如逆时针排列)和待测试点 Q,可以进行如下判断:
- 计算向量
P0P1与P0Q的叉积。 - 计算向量
P1P2与P1Q的叉积。 - 计算向量
P2P0与P2Q的叉积。
如果 Q 点在三角形内部,那么上述三个叉积结果的Z分量符号应该相同(同为正或同为负,取决于顶点顺序)。如果符号不同,则 Q 点在三角形外部。
关于边界:如果一个点恰好落在三角形的边上,可以自行定义规则处理(例如算作内部)。在成熟的图形API(如OpenGL)中有更严格的规定,但本课程中不做强制要求。
优化:使用包围盒 🎯
上述采样方法遍历了屏幕上的每一个像素,效率很低。一个三角形通常只覆盖屏幕的一小部分。
因此,一个常见的优化是使用轴向对齐包围盒(Axis-Aligned Bounding Box, AABB)。我们首先找出三角形三个顶点在x和y方向上的最小值和最大值:
x_min = min(P0.x, P1.x, P2.x)
x_max = max(P0.x, P1.x, P2.x)
y_min = min(P0.y, P1.y, P2.y)
y_max = max(P0.y, P1.y, P2.y)
这个由 (x_min, y_min) 和 (x_max, y_max) 定义的矩形区域,就是三角形可能覆盖的像素范围。我们只需要在这个矩形区域内进行采样循环即可,大大减少了需要判断的像素数量。
光栅化的结果与问题:锯齿 🔍
按照上述方法对三角形进行光栅化,我们得到的图像并非完美的三角形,而是带有明显锯齿(Jaggies)的图形。
这是因为:
- 像素是离散的:屏幕由有限个、不连续的小方块(像素)组成。
- 采样率不足:我们用像素中心点进行采样,相当于以屏幕分辨率对连续的三角形信号进行采样。当信号变化剧烈(如三角形边缘)时,过低的采样率会导致信息丢失,在视觉上表现为锯齿。
这种现象在信号处理中被称为走样(Aliasing)。解决走样问题是图形学中的一个重要课题,即反走样(Antialiasing)或抗锯齿。我们将在下一节课中深入探讨其原理和解决方法。
其他显示设备简介(补充知识) 📺
除了常见的LCD/LED屏幕,历史上还有其他类型的光栅显示设备:
- CRT(阴极射线管):通过电子束轰击荧光屏来成像,采用逐行或隔行扫描的方式绘制图像。
- 电子墨水屏:通过电压控制黑白颗粒的朝向显示内容,优点是视觉舒适、省电,缺点是刷新率极低。
此外,实际屏幕的像素结构可能比“均匀小方块”更复杂。例如:
- 手机屏幕的单个像素可能由红、绿、蓝三个子像素条并列构成。
- 一些相机传感器或屏幕采用拜耳阵列(Bayer Pattern),其中绿色感光元件的数量多于红色和蓝色,这是因为人眼对绿色最为敏感。
课程总结
本节课我们一起学*了光栅化的基础流程:
- 定义屏幕空间:将屏幕视为二维像素数组,并建立坐标系。
- 进行视口变换:将标准化立方体中的物体投影映射到屏幕空间。
- 理解三角形的重要性:因其简单、*面、易判断内外等优点,成为光栅化的基本图元。
- 掌握光栅化核心——采样:通过判断每个像素中心点是否在三角形内,来决定是否绘制该像素。
- 实现点-in-三角形判断:利用向量叉积的符号一致性进行高效判断。
- 引入优化方法:使用轴向对齐包围盒(AABB)来大幅减少需要采样的像素数量。
- 认识光栅化的问题:由于离散像素和有限采样率,会产生锯齿(走样)现象。



我们成功地将三维变换后的三角形“画”到了屏幕上,尽管结果存在锯齿。下一节课,我们将深入分析锯齿产生的原因(从信号采样的角度),并学*如何抗锯齿,以获得更*滑、更高质量的图像。

GAMES101-现代计算机图形学入门-闫令琪 - P6:Lecture 06 光栅化2(反走样与深度缓冲)📐

在本节课中,我们将要学*光栅化技术的两个核心部分:反走样(Antialiasing)和深度缓冲(Z-Buffering)。我们将从分析锯齿现象的成因开始,引入信号处理中的采样理论,并最终讲解如何在图形学中实现反走样效果。

上一讲我们介绍了光栅化的基本概念,即利用像素中心对屏幕空间中的三角形进行采样。本节中,我们来看看这种采样方法会带来什么问题,以及如何解决。
采样与走样问题

采样在图形学中广泛存在。光栅化过程就是在屏幕空间用一系列离散的点(像素中心)对“是否在三角形内”这个函数进行采样。

采样不仅发生在空间上,也发生在时间上。例如,视频或动画就是一系列在时间点上采样的静态图像。
采样会带来一系列我们不希望看到的结果,这些结果在图形学中被称为 Artifacts(瑕疵)。以下是几种常见的采样Artifacts:
- 锯齿(Jaggies):在三角形边缘等地方出现的楼梯状图案。
- 摩尔纹(Moiré Patterns):当用相机拍摄显示器等规则纹理时出现的扭曲条纹。
- 车轮效应(Wagon Wheel Effect):高速旋转的物体在视频中看起来像是在反向旋转,这是时间采样跟不上运动速度导致的。

这些Artifacts的本质是 走样(Aliasing)。其根本原因是信号(图像内容)的变化速度超过了采样的速度,导致采样结果无法准确还原原始信号。
反走样的基本思路
如何解决走样问题?一个直观的思路是:在采样之前,先对信号进行模糊(滤波)处理。

对比两种操作顺序:
- 先模糊,再采样:这是正确的反走样(Antialiasing)方法。
- 先采样,再模糊:这只能得到模糊后的走样结果(Blurred Aliasing),无法消除锯齿。
为什么顺序如此重要?我们需要从频率的角度来理解。
从频率理解走样
为了分析走样,我们引入信号处理中的频率概念。一个函数变化越快,其频率越高。


任何函数都可以通过傅里叶级数展开表示为一系列不同频率的正弦和余弦函数的和。而傅里叶变换则可以将一个函数从时域(或空间域)转换到频域,让我们看到信号中各个频率成分的分布。

对于一张图像,其傅里叶变换后的频谱图中心代表低频信息(图像大面积的色块),外围代表高频信息(图像的边缘和细节)。
滤波(Filtering) 就是从频率上移除特定频率成分的操作。例如:
- 低通滤波(Low-pass Filtering):移除高频,只保留低频,结果是图像变模糊。
- 高通滤波(High-pass Filtering):移除低频,只保留高频,结果是得到图像的边缘。

在时域/空间域中,滤波操作等价于卷积(Convolution)操作。卷积的数学定义是:一个函数(信号)与另一个函数(滤波器/卷积核)进行加权*均。例如,用一个3x3的盒状滤波器对图像每个像素及其周围像素求*均,就是对图像进行低通滤波(模糊)。
卷积有一个重要定理:时域中的卷积,等于频域中的乘积。反之,时域中的乘积,等于频域中的卷积。

采样在频域中的意义
采样操作,在时域中相当于原始信号与一系列冲击函数(只在采样点有值)相乘。
根据卷积定理,时域的乘积对应频域的卷积。因此,采样在频域中的效果,是将原始信号的频谱进行周期性的复制和*移。


走样(Aliasing)在频域中的表现就是:由于采样率不足,复制*移后的频谱间隔太小,导致不同复制体之间的频谱发生了混叠(Overlap)。这种混叠使得我们从采样结果中无法区分原始的高频信号,从而产生了视觉上的瑕疵。
反走样的频域解释与实现


理解了走样的频域成因,反走样的策略就清晰了:在采样之前,用一个低通滤波器移除信号中过高(超过采样率一半)的频率成分。


这样,处理后的信号频谱变窄,即使以相同的采样率进行采样,复制*移后的频谱也不会发生混叠。这就是“先模糊,再采样”在理论上的正确性。
在实际的光栅化中,如何对一个三角形进行“模糊”呢?理想情况是,对于每个像素,计算三角形覆盖该像素区域的精确面积比例(覆盖率)。这个覆盖率就是对该像素进行低通滤波(求*均)后的结果。
然而,精确计算三角形与像素的相交面积比较困难。图形学中常用一种高效的*似方法:超采样抗锯齿(MSAA, Multi-Sample Anti-Aliasing)。
MSAA:一种实用的反走样方法
MSAA的核心思想是:用一个像素内多个采样点的结果,来*似三角形在该像素内的覆盖率。





以下是MSAA的工作步骤:
- 将每个像素划分为更小的子像素(例如2x2,4x4)。
- 判断每个子像素的中心点是否在三角形内。
- 计算在三角形内的子像素点数量占总子像素点数量的比例,作为该像素的覆盖率(颜色值)。
- 最终像素的颜色 = 三角形颜色 * 覆盖率 + 背景色 * (1 - 覆盖率)。

请注意:MSAA并没有提高最终显示图像的分辨率,它只是通过增加采样点来更准确地估计每个像素的颜色值(即完成“模糊”步骤)。采样步骤依然是对每个大像素输出一个颜色值。
MSAA会增加计算量(需要计算更多点是否在三角形内),但工业界会通过优化采样点分布、复用相邻像素采样点等技巧来提升效率。
其他抗锯齿技术简介
除了MSAA,工业界还广泛应用其他抗锯齿技术:
- FXAA (Fast Approximate Anti-Aliasing):一种后处理技术。先得到有锯齿的图像,然后通过图像处理快速识别并*滑锯齿边缘。速度快,与采样无关。
- TAA (Temporal Anti-Aliasing):时间性抗锯齿。复用上一帧的采样信息,将MSAA的采样点分布到时间轴上,在当前帧几乎不增加额外计算量,但对运动物体需要特殊处理。
此外,超分辨率(Super Resolution) 技术(如DLSS)与抗锯齿有相似本质,都是解决样本不足的问题,但它旨在从低分辨率图像重建高分辨率图像。
本节课中我们一起学*了光栅化中反走样技术的原理与实现。我们从锯齿现象出发,深入探讨了采样理论、频率分析、傅里叶变换和卷积,从而理解了走样的根本原因在于频谱混叠。反走样的核心是在采样前进行低通滤波(模糊),而MSAA是这一思想在光栅化中的高效*似实现。理解这些概念对于掌握现代图形学至关重要。
(注:由于课程内容较长,深度缓冲(Z-Buffering)部分将在下一讲中详细介绍。)





GAMES101-现代计算机图形学入门-闫令琪 - P7:Lecture 07 Shading 1 (Illumination, Shading and Graphics Pipeline) 🎨
在本节课中,我们将要学*图形学中的着色基础。我们将从光栅化的最后一个环节——深度测试开始,然后进入着色的核心概念,包括光照模型和图形管线的基本流程。课程内容将分为深度缓存算法和着色模型(以漫反射为例)两部分进行讲解。
深度缓存(Z-Buffering)算法 🧱
上一节我们介绍了如何将单个三角形光栅化到屏幕上。本节中我们来看看当场景中有多个三角形相互遮挡时,如何正确地处理它们的可见性顺序。
画家算法及其局限性
一个直观的想法是模仿画家作画的过程:先画远处的物体,再画*处的物体覆盖远处的物体。这种方法被称为画家算法(Painter‘s Algorithm)。
画家算法在一定情况下是适用的,例如绘制一个立方体时,可以先画背面,再画侧面,最后画正面。然而,当多个三角形在深度上形成循环遮挡关系时(例如三角形A遮挡B,B遮挡C,C又遮挡A),就无法定义一个明确的绘制顺序,画家算法就会失效。
深度缓存算法原理

为了解决上述问题,图形学中广泛采用了深度缓存(Z-Buffering)算法。该算法的核心思想是:为每个像素单独维护一个深度值,记录当前该像素所看到的最浅(离相机最*)的几何深度。
算法需要同步维护两个缓冲区:
- 帧缓冲区(Frame Buffer):存储最终渲染出的图像颜色。
- 深度缓冲区(Depth Buffer / Z-Buffer):存储每个像素当前看到的最浅深度值。初始化时,所有深度值设为无穷大。

以下是深度缓存算法的伪代码流程:
// 初始化
for (each pixel in screen) {
depth_buffer[pixel] = INFINITY; // 深度设为无穷大
frame_buffer[pixel] = background_color; // 颜色设为背景色
}
// 光栅化每个三角形
for (each triangle T in scene) {
for (each pixel (x, y) covered by T) {
// 计算三角形T在当前像素(x, y)处的深度值z
z = compute_depth(T, x, y);
// 深度测试:如果新深度比缓存深度更浅(更*)
if (z < depth_buffer[x, y]) {
// 更新深度缓存
depth_buffer[x, y] = z;
// 更新帧缓存,绘制该三角形的颜色
frame_buffer[x, y] = T.color_at(x, y);
}
}
}
深度缓存算法示例
我们通过一个简单例子来理解算法过程。假设屏幕上有两个三角形:红色三角形和蓝色三角形。
- 初始化:所有像素深度值为无穷大(∞)。
- 处理红色三角形:遍历其覆盖的像素。例如,在某个像素上,红色三角形深度为5,小于当前深度值∞。因此,更新该像素的深度为5,并将颜色设为红色。
- 处理蓝色三角形:同样遍历其覆盖的像素。
- 在某个像素上,蓝色三角形深度为8,而深度缓存中已记录深度为5(来自红色三角形)。因为8 > 5,所以蓝色三角形在该像素被遮挡,不做任何更新。
- 在另一个像素上,蓝色三角形深度为3,而深度缓存中记录为5。因为3 < 5,所以蓝色三角形更*。于是更新该像素深度为3,并将颜色更新为蓝色。
通过这种逐像素比较并保留最小深度的方法,无论以何种顺序处理三角形,最终都能得到正确的遮挡结果。该算法的时间复杂度为O(n),其中n为三角形数量(假设每个三角形覆盖常数个像素)。
深度缓存的补充说明
- 与顺序无关性:深度缓存算法的结果与处理三角形的顺序无关(假设不会出现两个深度值完全相等的情况)。
- 浮点数精度:在实际应用中,深度值通常用浮点数表示。由于浮点数精度问题,两个计算出的深度值几乎不可能完全相等,这在一定程度上避免了深度相等时的歧义。
- 透明物体:标准的深度缓存算法无法正确处理半透明物体的混合,需要特殊处理。
- 与MSAA结合:在使用多重采样抗锯齿(MSAA)时,深度测试和存储需要在每个子采样点(而不仅仅是像素中心)上进行。

至此,我们完成了对光栅化中可见性问题的探讨。接下来,我们将进入本节课的核心主题——着色。

着色(Shading)基础与漫反射模型 💡
将几何图形正确绘制到屏幕上后,我们需要决定每个像素的颜色。这个过程就是着色。着色主要研究光照如何与物体材质相互作用,从而让我们看到丰富多彩、具有明暗变化的图像。
着色的定义与假设
在图形学中,我们定义着色为对不同物体应用不同材质的过程。本节课介绍的是一种局部着色模型(Local Shading Model),它有两个重要假设:
- 只考虑着色点本身,不考虑其他物体的存在(即不考虑阴影)。
- 着色点的颜色只与光源、观察方向、该点表面属性有关。
Blinn-Phong反射模型概述
我们将以一个经典的、经验性的着色模型——Blinn-Phong模型为例进行讲解。该模型将光照效果简化为三个部分的叠加:
- 漫反射(Diffuse):模拟粗糙表面将光线均匀反射到各个方向的效果。
- 高光反射(Specular):模拟光滑表面在镜面反射方向附*形成亮斑的效果。
- 环境光(Ambient):模拟间接光照,为物体未被直接照亮的部分提供一个基础亮度。
本节课我们先详细讲解漫反射部分。
漫反射(Diffuse)项详解

漫反射的特点是:光线照射到粗糙表面后,会向所有方向均匀散射。因此,观察者从不同方向看同一个漫反射点,其亮度是相同的。漫反射的亮度主要取决于两个因素:光照方向与表面法线的夹角,以及光线传播的距离。

为了定量计算,我们需要定义一些向量(均为单位向量):
- 表面法线(n):垂直于着色点所在微小表面的方向。
- 光照方向(l):从着色点指向光源的方向。
- 观察方向(v):从着色点指向相机的方向(漫反射计算中暂时用不到)。
兰伯特余弦定律(Lambert‘s Cosine Law)
着色点单位面积接收到的光线能量,与光照方向 l 和表面法线 n 夹角的余弦值成正比,即与 n · l 的点乘结果成正比。
- 当光线垂直照射(l 与 n 同向)时,n · l = 1,接收能量最大。
- 当光线*行于表面(l 与 n 垂直)时,n · l = 0,接收能量为0。
- 如果点乘结果为负(光线从表面下方射入),则没有光照贡献,取值为0。
因此,接收到的能量比例可用 max(0, n·l) 表示。
*方反比衰减(Inverse Square Law)
光线从点光源发出,在空间中传播。根据能量守恒,在任意时刻,光能量均匀分布在一个球壳上。因此,单位面积接收到的光强 I 与距离 r 的*方成反比:I’ = I / r²,其中 I 是光源在单位距离处的强度。
漫反射公式
结合以上两点,并引入漫反射系数 kd(一个三维向量,代表表面颜色,即对不同波长光线的吸收率),我们得到Blinn-Phong模型的漫反射项公式:
漫反射颜色 = kd * (I / r²) * max(0, n·l)
其中:
- kd:漫反射系数(表面颜色),例如(1.0, 0.0, 0.0)代表红色。
- I:光源在单位距离处的强度。
- r:着色点到光源的距离。
- n·l:表面法线与光照方向的点乘。
这个公式清晰地表明:漫反射的亮度与观察方向 v 无关,只与光照方向、距离和表面朝向有关。一个石膏球在点光源照射下,正对光源处最亮,侧面逐渐变暗,正是这一模型的直观体现。



本节课中我们一起学*了图形管线中两个关键环节。首先,我们掌握了深度缓存算法,它通过为每个像素维护最小深度值,高效且正确地解决了多物体的遮挡问题。然后,我们进入了着色领域,详细分析了Blinn-Phong反射模型中的漫反射项,理解了表面颜色、光照角度和距离如何共同决定一个点的基本明暗和颜色。下节课我们将继续完成该模型的高光和环境光部分,并对图形管线进行总结。

GAMES101-现代计算机图形学入门-第八课:着色2(着色、管线与纹理映射)🎨
在本节课中,我们将学*着色模型的完整构成,探讨不同的着色频率,了解实时渲染管线的基本流程,并初步认识纹理映射的概念。
课程概述
上一节我们介绍了着色模型中的漫反射项。本节中,我们将继续学*布林-冯(Blinn-Phong)着色模型的高光项和环境光项,理解如何将着色应用于不同频率(逐三角形、逐顶点、逐像素),并概览从三维场景到二维图像的实时渲染管线。最后,我们将引入纹理映射的基本思想,为后续学*差值方法打下基础。
布林-冯着色模型详解
布林-冯模型由漫反射、高光和环境光三项组成。我们将逐一分析。
高光项(Specular Term)

高光出现在表面光滑的物体上,当观察方向接*镜面反射方向时可见。布林-冯模型使用了一个巧妙的简化:它通过比较法线向量(n)和半程向量(h)的接*程度来判断高光,而非直接比较观察方向(v)和反射方向(r)。半程向量是光照方向(l)与观察方向(v)的角*分线方向。

半程向量计算公式:
h = bisector(l, v) = (l + v) / ||l + v||

衡量接*程度使用点乘,并引入指数p来控制高光区域的大小。p值越大,高光越集中。
高光项公式:
L_s = k_s * (I / r^2) * max(0, n·h)^p
其中,k_s是镜面反射系数(通常为白色),I/r^2是到达着色点的能量。

环境光项(Ambient Term)
环境光用于模拟间接光照,确保场景中没有完全黑色的区域。这是一个极大的简化,假设环境中所有点接收到的环境光强度恒定。
环境光项公式:
L_a = k_a * I_a
其中,k_a是环境光系数,I_a是环境光强度。此项与光照方向、观察方向和法线均无关。

完整的布林-冯模型
将三项相加,得到完整的着色模型:
布林-冯模型公式:
L = L_a + L_d + L_s = k_a * I_a + k_d * (I / r^2) * max(0, n·l) + k_s * (I / r^2) * max(0, n·h)^p

该模型能产生具有漫反射、高光,且无纯黑区域的着色效果,类似塑料质感。
着色频率(Shading Frequency)
着色模型定义了一个点如何着色。接下来我们需要决定将着色计算应用在哪些“点”上,这就是着色频率。
以下是三种主要的着色频率:
- Flat Shading(逐三角形着色):对每个三角形面计算一次着色(使用其面法线),三角形内部颜色一致。
- Gouraud Shading(逐顶点着色):对每个顶点计算一次着色(使用顶点法线),三角形内部颜色通过对顶点颜色插值得到。
- Phong Shading(逐像素着色):对每个像素计算一次着色。首先对顶点法线在三角形内进行插值,得到每个像素的法线,再为每个像素单独计算着色。效果最好,计算量也最大。
顶点法线的计算:通常取该顶点所关联的所有面的法线的加权*均(如按面积加权)。
选择哪种频率取决于模型复杂度。当三角形足够密集时,Flat Shading也能得到不错的效果;反之,对于简单模型,Phong Shading能显著提升视觉质量。
实时渲染管线(Real-time Rendering Pipeline)
渲染管线描述了从三维模型到最终屏幕像素的完整操作序列。现代GPU硬件实现了这一管线。
管线主要阶段:
- 顶点处理(Vertex Processing):
- 输入:三维空间中的顶点。
- 操作:进行模型变换、视图变换、投影变换(MVP变换),将顶点投影到屏幕空间。
- 可编程部分:顶点着色器(Vertex Shader),可在此阶段进行顶点着色(如Gouraud Shading)。

- 三角形处理与光栅化(Triangle Processing & Rasterization):
- 操作:将屏幕空间中的三角形转换为屏幕上的离散片段(Fragment,可*似理解为像素)。判断哪些像素位于三角形内。
- 深度测试(Z-Buffering)也在此阶段或紧随其后进行,以解决可见性问题。


-
片段处理(Fragment Processing):
- 操作:为每个生成的片段计算最终颜色。
- 可编程部分:片段着色器(Fragment/Pixel Shader),可在此阶段进行逐像素着色(如Phong Shading)和纹理查询。
-
输出合并(Output Merging):将片段颜色写入帧缓冲区,最终形成图像。
着色器(Shader):指在GPU上运行的小程序,用于可编程阶段(主要是顶点和片段着色器)。开发者通过编写着色器代码来控制顶点变换和像素着色的具体方式。例如,一个简单的漫反射片段着色器代码如下(GLSL风格):
uniform sampler2D myTexture; // 纹理
uniform vec3 lightDir; // 光照方向(全局常量)
varying vec2 uv; // 插值得到的纹理坐标
varying vec3 normal; // 插值得到的法线
void main() {
vec3 kd = texture2D(myTexture, uv).rgb; // 从纹理获取漫反射系数
vec3 n = normalize(normal);
vec3 l = normalize(lightDir);
float diffuseIntensity = max(0.0, dot(n, l));
gl_FragColor = vec4(kd * diffuseIntensity, 1.0); // 输出像素颜色
}
现代GPU还支持几何着色器(Geometry Shader)、计算着色器(Compute Shader)等,功能更加强大。


纹理映射(Texture Mapping)初步
着色模型中的各种系数(如k_d)可以不是常数。纹理映射的核心思想是:定义物体表面任意一点的不同属性(如颜色、粗糙度等)。
基本概念:
- 纹理(Texture):一张二维图像。
- 纹理坐标(UV Coordinates):用于定位纹理上的点。通常规范化到[0, 1]范围,横轴为U,纵轴为V。
- 映射(Mapping):建立物体表面点(三维)与纹理坐标(二维)的对应关系。
工作原理:
- 对于三维模型的每个顶点,除了位置坐标,还预先指定其对应的纹理坐标
(u, v)。这通常由建模人员或参数化算法完成。 - 对于一个三角形,已知其三个顶点的纹理坐标。
- 对于三角形内部的任意一点,其纹理坐标可以通过其三个顶点的纹理坐标插值获得。
- 根据插值得到的
(u, v)坐标,去查询纹理图像,获得该点的属性(如颜色),并用于着色计算。

纹理可以重复*铺(Tiling)使用,设计良好的可*铺纹理(Tileable Texture)能在边界处无缝衔接。

课程总结
本节课我们一起学*了:
- 完整的布林-冯着色模型,包括其漫反射、高光和环境光分量的定义与公式。
- 着色频率的概念,比较了逐三角形(Flat)、逐顶点(Gouraud)和逐像素(Phong)着色的区别与适用场景。
- 实时渲染管线的完整流程,理解了从顶点到像素的转换过程,以及顶点着色器和片段着色器的角色。
- 纹理映射的基本原理,即通过UV坐标将二维纹理图像映射到三维物体表面,以定义表面属性的变化。



我们留下了一个核心问题:如何在三角形内部根据顶点属性进行插值?这需要用到重心坐标(Barycentric Coordinates)的概念,我们将在下节课详细探讨。

GAMES101-现代计算机图形学入门-第九讲:着色(三)纹理映射(续)📐
在本节课中,我们将继续学*纹理映射的相关知识。我们将重点探讨如何在三角形内部进行属性插值,以及如何解决纹理映射中出现的“纹理过大”和“纹理过小”问题。通过引入重心坐标、双线性插值、Mipmap和各向异性过滤等概念,我们将学*如何高效、高质量地处理纹理。
概述
上一讲我们介绍了着色模型和纹理映射的基本概念。本节我们将深入纹理映射的具体实现细节,特别是如何解决纹理采样时遇到的质量问题。我们将从三角形内部的插值方法开始,逐步讲解纹理放大和缩小时的处理技术。
1. 重心坐标与插值 🔺
在上一节中,我们提到为了在三角形内部实现*滑的着色过渡,需要进行插值。本节中,我们来看看如何利用重心坐标在三角形内部对任意属性进行插值。
为什么需要插值?
在图形学中,许多计算(如颜色、法线、纹理坐标)是在三角形的顶点上完成的。为了在三角形内部得到*滑过渡的效果,我们需要根据顶点的值计算出内部任意点的值。
插值什么内容?
可以插值的属性包括但不限于:
- 颜色:实现顶点颜色在三角形内部的*滑渐变。
- 纹理坐标 (UV):确定三角形内点对应纹理图像上的位置。
- 法线向量:用于实现逐像素着色(Phong Shading)。
- 深度值:在光栅化过程中进行深度测试。
如何插值:重心坐标
重心坐标是定义在三角形上的一套坐标系,用于描述三角形所在*面内任意点的位置。
定义:对于三角形ABC所在*面内的任意点(x, y),都可以表示为三个顶点坐标的线性组合:
(x, y) = α * A + β * B + γ * C
其中,系数需满足:α + β + γ = 1。
点在三角形内的条件:当且仅当所有系数均为非负数时(α ≥ 0, β ≥ 0, γ ≥ 0),该点位于三角形内部。
计算方法:重心坐标可以通过面积比来计算。以系数α为例:
α = (A点对面小三角形的面积) / (三角形ABC的总面积)
同理可计算β和γ。三角形的重心坐标即为(1/3, 1/3, 1/3)。
插值公式:已知三角形三个顶点的属性值V_A, V_B, V_C,则三角形内任意点(重心坐标为(α, β, γ))的属性值V可通过下式插值得到:
V = α * V_A + β * V_B + γ * V_C
一个重要注意事项:重心坐标在投影变换下不具备不变性。因此,对于三维空间中的属性(如深度),应在三维空间中进行插值,再将结果映射回屏幕空间,而不是在投影后的二维三角形中直接插值。
2. 纹理映射的应用 🖼️
了解了插值方法后,我们来看看如何将纹理应用到物体表面。核心思路是:对于屏幕上的每个采样点(如像素中心),利用插值得到其纹理坐标(UV),然后从纹理图像中查询该坐标处的颜色值。
基本步骤:
- 通过重心坐标,插值出当前像素点对应的纹理坐标(u, v)。
- 在纹理图像上查询坐标(u, v)处的颜色值。
- 将此颜色值作为漫反射系数
kd,代入布林-冯着色模型进行计算,从而得到该像素的最终颜色。
这相当于将纹理图像“贴”在了物体表面,并结合了光照模型产生明暗效果。
3. 纹理过小与放大问题 🔍

当纹理图像的分辨率低于屏幕渲染分辨率时,纹理会被拉大,导致一个屏幕像素可能对应纹理上的一片区域。如果简单地取最*纹理像素的颜色,会产生明显的块状瑕疵。


问题分析
一个屏幕像素覆盖了纹理上的一片连续区域,但我们只采样了一个点(如像素中心映射的点)。当纹理细节变化剧烈时,单点采样无法代表整个区域,导致走样。

解决方案:双线性插值
为了提高质量,我们不再寻找最*的单个纹理像素,而是考虑该点周围四个最*的纹理像素,并通过两次线性插值来估算该点的颜色。

步骤如下:
- 对于非整数纹理坐标(u, v),找到其周围四个最*的纹理像素:
u00, u01, u10, u11。 - 计算该点与左下角像素
u00的水*距离s和垂直距离t(s, t ∈ [0, 1])。 - 进行两次线性插值:
- 水*插值:先用
s在u00和u10之间插值得到u0;再用s在u01和u11之间插值得到u1。 - 垂直插值:再用
t在u0和u1之间插值,得到最终的颜色值。
- 水*插值:先用
线性插值公式为:lerp(v0, v1, t) = v0 + t * (v1 - v0)。
通过双线性插值,像素颜色能够在其覆盖的纹理区域内*滑过渡,有效避免了马赛克现象。更高质量但计算量更大的方法还有双三次插值,它使用周围16个像素进行插值。

4. 纹理过大与缩小问题 🧩


当纹理图像分辨率过高时,一个屏幕像素可能覆盖纹理上的一大片区域。此时,如果用像素中心单点采样,相当于用高频信号中的一个样本来代表整个区域的*均值,会造成严重的走样和摩尔纹。
问题本质
这本质上是信号采样频率不足导致的走样问题。一个解决方案是超采样(如MSAA),即在像素内进行多次采样再*均,但计算开销巨大。

高效方案:Mipmap
Mipmap的核心思想是避免采样,直接进行快速的范围查询(求*均值)。它是一种图像金字塔结构。
Mipmap的构建:
- 从原始纹理(第0层)开始,每一层图像的长宽都是上一层的一半。
- 第
n层图像有(width/2^n) * (height/2^n)个像素。 - 总层数为
log2(max(width, height))。 - 额外存储开销仅为原始纹理的1/3。
Mipmap的查询:
- 估算屏幕像素在纹理空间中所覆盖区域的*似边长
L。 - 计算应在Mipmap的哪一层进行查询:
D = log2(L)。 - 在Mipmap的第
D层图像上,查询对应纹理坐标处的像素值。由于第D层的一个像素恰好代表了原始纹理中L×L区域的*均值,因此这次查询就快速得到了范围查询的结果。
三线性插值:
直接查询离散的D层可能导致层与层之间出现不连续的跳跃。为了解决这个问题,我们进行三线性插值:
- 在
D的上下两层(如floor(D)层和ceil(D)层)分别进行双线性插值,得到两个颜色值Color_low和Color_high。 - 再用
D的小数部分在Color_low和Color_high之间进行一次线性插值,得到最终颜色。

三线性插值在层内和层间都实现了*滑过渡,是游戏中广泛应用的技术。
Mipmap的局限性:它只能快速进行正方形区域的*似范围查询。当像素覆盖的纹理区域是细长的矩形时,用正方形去*似会取到过多无关区域的*均值,导致过度模糊。


5. 各向异性过滤 ⚖️

为了改善Mipmap在处理矩形区域时的过度模糊问题,引入了各向异性过滤。

核心思想:除了构建标准的Mipmap(对角线压缩),还额外预计算一系列在水*或垂直单一方向上压缩的纹理链。
- 这样,对于一个在纹理空间中是
2x8的矩形区域,我们可以直接在水*压缩2倍、垂直压缩8倍的特定层级纹理上进行快速查询,从而得到更准确的*均值。
效果与开销:
- 各向异性过滤能显著改善远处或倾斜表面的纹理清晰度。
- 其存储开销约为原始纹理的3倍,但现代显卡显存通常足以承受。
- 它仍然不能完美处理所有不规则形状的区域,更高级的方法如EWA过滤通过多次圆形查询来覆盖不规则形状,但计算成本更高。

在游戏中,开启各向异性过滤(如16x)能大幅提升纹理质量,而对性能影响很小,因此建议在显存足够时开启。

总结
本节课我们一起深入学*了纹理映射的高级主题:
- 我们首先学*了重心坐标,这是在任何三角形内部进行属性插值的数学基础。
- 接着,我们探讨了纹理过小(放大) 的问题,并引入了双线性插值技术来*滑纹理,避免马赛克。
- 然后,我们重点分析了纹理过大(缩小) 导致的走样问题。为了解决高效的范围查询需求,我们学*了Mipmap这一图像金字塔结构,以及为了*滑过渡而使用的三线性插值。
- 最后,我们了解了Mipmap的局限性,并介绍了各向异性过滤如何通过预计算更多方向的纹理链,来更好地处理矩形区域,减少过度模糊。



通过这些技术,我们能够在保证渲染效率的同时,显著提升纹理映射的质量。至此,光栅化渲染器的主要技术环节已基本讲解完毕。下一讲我们将进入新的模块——几何。


GAMES102:几何建模与处理 - P1:课程介绍 📚
在本节课中,我们将学*GAMES102课程的整体介绍,了解几何建模与处理在计算机图形学中的核心地位,并初步探讨函数拟合这一基础建模方法。



课程与GAMES*台介绍 🎓


大家好,我是中国科学技术大学的刘利刚。很高兴在GAMES在线论坛为大家讲解GAMES102这门课程。在101和201两门课程结束后,我将开始讲解关于几何的课程。
首先,我介绍一下本课程的基本情况。
GAMES是“图形学与混合现实在线*台”的缩写。它的主要宗旨是为图形学及相关领域的华人社区提供一个在线交流*台。该*台于2016年4月由专委主任创建,最初是线下活动。2017年6月,我们决定将活动搬到线上,让更多人受益。自2017年6月网站上线运营至今,已举办超过158期在线报告。今年开始,我们创新地推出了系列课程,例如杨林青老师的101入门课程和胡渊鸣同学的201高级课程。下半年由我来讲授几何课程,明年还会有老师讲授202高级课程等。目前社区已发展至11个群,*5400人。如果还未加入,可以扫描屏幕上的二维码。


GAMES网站(games-cn.org)包含往期报告视频、在线课程资源、线下会议资料以及更多学术会议资源。我们积累了大量资料,旨在让不同方向的同学足不出户就能享受到高端讲座和课程。


过去三年,我们得到了许多老师的无私付出,特别感谢现任线上运营负责人周晓巍老师以及背后的技术秘书团队。虽然付出了很多时间,但我们也从中收获良多,我也是受益人之一。
关于102课程,更多信息可以访问我的个人主页。在课程注册时,我看到很多网友留言说没有数学几何基础,希望讲一些非常基础的内容。听众的背景可能从未接触数学到有一些几何经验,甚至是非图形专业的同学。因此,我会尽可能讲得通俗易懂。如果对课程有反馈,欢迎给我写邮件。本课程定位为基础课程,后续可能会考虑开设三维几何处理的高级课程。在中国科学技术大学,我们每年都会开设“数字几何处理”课程,相关录屏也可以在B站找到。
本课程将布置5-6个编程作业,难度不会太大,希望大家有时间可以跟着一起做。我们提供了作业提交系统,并建立了两个交流群和一个BBS论坛,助教(我的研究生)会在上面回答问题。课程结束后,我们会向认真完成作业的同学颁发结业证书。
图形学中的几何建模 🖼️

现在,我们开始讲解课程内容。建模是图形学中一个非常重要的组成部分。
大约七年前,我撰写了一篇介绍图形学的帖子,将图形学分为三大块内容:建模、动画与渲染。GAMES101主要讲渲染,GAMES201讲动画,而我今天的课程将覆盖最后一块——建模。这样,今年的三门课程就涵盖了图形学的三大核心内容。
那么,什么是图像?图像本质上是三维世界投影到我们视网膜或相机感光元件上的成像。由于感光元件是离散的,所以数字图像在计算机中是以离散方式存储的,即由像素组成的栅格图像。图像的分辨率指的是其像素的行列数。
图形则不同,它指的是具有数学表达的几何对象,例如点、线、多边形或曲线。图形也称为矢量图。矢量图存储的是几何元素的数学描述(如顶点坐标),而非像素。因此,矢量图可以无限放大而不失真,因为它会根据数学描述实时重新计算并填充像素,这个过程称为光栅化。而栅格图像放大后则会模糊。




人类很早就会通过绘画来创造想象的影像。但若要大规模创造,例如制作动画,靠手绘是不可行的。因此,我们需要通过计算方法来创造图像。





从成像原理到内容创造 💡

要计算生成图像,必须理解成像原理。成像需要光源、物体和成像*面。物体表面的颜色能否被看到,取决于光源与其材质的相互作用,反射或折射出的光线进入人眼或相机。这就是图形学渲染的基本物理原理,核心在于解算光照方程。

然而,要渲染出逼真的场景,仅有算法是不够的,还需要原材料:场景的几何模型、光源设置、纹理和材质。这些要素共同构成了虚拟世界的内容。在电影和游戏工业中,有专门的美术和技术美术人员负责创建这些内容。

对于运动物体,如流体或软体,则需要仿真的科学,即通过解算各种物理方程(如纳维-斯托克斯方程)来模拟运动。仿真就是在做运动计算。
结合强大的建模、仿真与渲染算力,我们可以创造出如《冰雪奇缘》、《流浪地球》等电影中逼真的三维虚拟世界。这就像导演创造了一个真实的*行宇宙。
电影《头号玩家》描绘了虚拟现实(VR)的未来图景:人们通过设备进入一个由规则定义的虚拟世界“绿洲”,并进行社交和活动。这展示了未来虚拟世界的可能性。
几何建模:数据的来源与挑战 🧩
但是,创造这些虚拟世界所需的数据从何而来?要渲染出一个逼真的模型,需要建模、UV展开(参数化)、生成法线贴图、位移贴图、材质贴图等一系列复杂数据。这些数据的来源和构建非常困难。
虽然可以依靠美术人员手动创建,但效率低下。本课程的目的就是讲解如何通过工具或程序来构建这些几何数据。
几何建模有多种形式。例如,简单的几何体(方块、球体)可以用简单的数学表达。但工业上制造汽车、飞机所需的曲面必须非常光滑,这就需要连续的数学表达,如样条。在动画中,则可能通过细分和雕刻技术来建模。对于更逼真的效果,如半透明材质、毛发、金属锈迹等,建模手段和表达方式就更复杂。
几何内容的生成至今仍是图形学的瓶颈之一。本课程将重点介绍左侧这些基础的建模手段,右侧的高级建模未来有机会再详谈。
函数拟合:连续精准建模入门 📈
现在,我们开始讲解第一个正式内容:函数拟合。我们先从连续的精准建模讲起,建立概念基础后,再理解离散建模会更容易。
我也注意到很多同学在注册时留言,希望考虑到数学基础。因此,我会尽量清楚地讲解数学概念,并从一点点数学开始讲起。完全不讲数学是无法表达后续概念的。大家千万不要害怕数学,它本质上是一种描述规律的语言。
数学来源于实际需求,如计数和土地丈量,后来被欧几里得等人抽象为公理体系,发展成纯粹的演绎科学。我个人更倾向于将数学应用于工程问题。
将实际问题转化为数学模型的过程至关重要:首先用符号表达变量,然后建立优化、方程或统计等模型,最后通过代码实现算法求解。代码是验证想法的必要工具,但更关键的是从问题到模型的建模过程。
数学还要求善于抽象。从幼儿园具体的“苹果相加”,到小学抽象的“数字相加”,再到中学的“代数相加”,概念越来越抽象。
必要的数学概念回顾 🔢
以下是后续课程需要的一些基本数学概念回顾,非数学专业的同学也无需担心。
- 集合:具有相同性质对象的总体。讨论问题时常限定在特定集合内。
- 线性空间:在集合上定义了满足交换律、结合律等性质的运算后,该集合就具有了线性结构。线性空间中的任何元素都可以表示为一组基向量的线性组合,这大大简化了描述。
- 映射与函数:映射指一个集合中的每个元素在另一个集合中都有唯一元素与之对应。当两个集合都是实数集时,这种映射称为函数。一元函数的可视化图像是二维*面上的一条曲线。
- 函数空间:研究函数时,需要限定在特定的函数集合(空间)内寻找。如果该空间具有线性结构,那么寻找目标函数就转化为寻找一组系数。函数空间的选择很重要,它需要具备足够的表达能力(完备性)来逼*我们想要的函数。例如,魏尔斯特拉斯逼*定理指出,闭区间上的任何连续函数都可以用多项式级数逼*。
函数拟合的三部曲 🎯
在许多建模问题中,核心是寻找一个满足要求的函数。这可以归纳为三个步骤:
- 在哪找?(模型):确定在哪个函数空间中寻找。例如,是在多项式空间还是三角函数空间中找?
- 找哪个?(策略):定义“好”函数的标准,即损失函数。例如,要求函数必须经过所有数据点(插值),或要求函数与数据点的总体误差最小(逼*)。
- 怎么找?(算法):根据定义的标准,通过优化或求解方程来找到目标函数。
逆向工程与数据拟合实例 🔄
逆向工程是一个典型的拟合问题。例如,我们拿到一个船型的设计图纸,只有离散点,没有方程。为了制造,需要反求出描述船型的函数。
数据拟合(或回归)问题描述如下:给定*面上若干点,寻找一个函数 y = f(x) 来反映这些点的内在规律。
1. 插值
如果要求函数必须精确经过所有数据点,这就是插值问题。例如,用n次多项式拟合n+1个点,解一个线性方程组即可。拉格朗日插值多项式是经典解法。但插值可能对数据误差敏感,且可能导致数值不稳定(病态问题)。
2. 逼*
如果数据存在测量误差,我们允许函数不一定精确经过每个点,但要求总体误差最小,这就是逼*问题。最常用的误差度量是*方和(L2范数),由此导出的方法称为最小二乘法。通过求解目标函数关于系数的偏导数为零得到的方程,称为法方程。
3. 过拟合与欠拟合
选择函数空间(如多项式的次数)至关重要。
- 欠拟合:函数空间太简单(如用直线拟合曲线),表达能力不足,误差大。
- 过拟合:函数空间太复杂(如用高次多项式拟合少量点),虽然对训练数据误差小,但对新数据的预测能力差。为避免过拟合,可采用交叉验证、增加数据、简化模型或添加正则项等方法。
正则化与稀疏优化 ⚖️
为了在复杂模型和泛化能力之间取得*衡,可以引入正则项。
- 岭回归:在最小二乘法的损失函数中,添加系数的L2范数(模的*方)作为正则项,限制系数的大小,使模型更稳定。
- LASSO回归:使用系数的L1范数(绝对值之和)作为正则项。L1范数倾向于产生稀疏解,即让许多系数为零,从而实现特征选择——从大量基函数中自动挑选出少数重要的。
- 稀疏优化与压缩感知:如果一个信号本身是稀疏的(只有少量非零元素),那么可以通过远少于信号长度的观测值,利用稀疏优化算法高概率地精确重建原始信号。这与函数拟合中利用稀疏性选择基函数的思想一脉相承。
总结与展望 📝
本节课我们一起学*了GAMES102课程的概况,理解了几何建模在图形学中的重要性,并深入探讨了函数拟合这一基础建模方法的核心思想:通过“模型、策略、算法”三部曲,在选定的函数空间中,根据定义的误差度量,寻找最能描述给定数据的函数。我们还了解了插值与逼*的区别,以及过拟合、欠拟合和正则化的概念。
然而,许多曲线(如一个圆)并不能表示成单值函数 y=f(x)。对于这类更一般的曲线、曲面以及高维复杂函数的拟合问题,我们将在后续课程中探讨。解决思路包括参数化表示、分段拟合并保证段间光滑性(样条)等。
作业:实现给定数据点的函数拟合(插值或最小二乘逼*),并可视化结果。观察选择不同次数的多项式时,拟合效果的变化。
课程计划:本课程前半部分主要讲解连续的曲线曲面建模(如工业界标准的NURBS),后半部分则转向离散的三角网格曲面处理(如重建、编辑、变形等)。我会尽量讲解核心思想,照顾到不同背景的同学。



本节课就到这里,我们下节课再见!






GAMES102:几何建模与处理 - P10:曲面去噪、采样与剖分 📐




在本节课中,我们将学*几何处理中的两个核心主题:曲面去噪与网格的采样和剖分。我们将探讨如何去除网格数据中的噪声,以及如何从一组离散点生成高质量的三角网格。



概述
上一节我们介绍了拉普拉斯算子及其在几何处理中的应用。本节中,我们来看看如何利用这些概念解决实际问题,即如何对带有噪声的曲面进行*滑处理(去噪),以及如何从一组采样点生成结构良好的三角网格(剖分)。这两个过程在计算机图形学、三维重建和有限元分析等领域至关重要。
第一部分:曲面去噪 🧹
在实际应用中,三维数据通常来自扫描仪或重建算法,由于设备误差或计算误差,数据上常带有噪声。给定一个带噪声的网格曲面,去除噪声的过程称为去噪。
噪声的定义与挑战
噪声通常被经验性地描述为高频、小尺度的不规则凸起或高曲率区域。然而,噪声与模型特征之间的界限并不严格。一个核心挑战是在去除噪声的同时,保留模型的尖锐特征(如边和角)。这通常是一个“鸡生蛋”的问题:若能有效去噪,则特征更容易检测;若能准确检测特征,则去噪更容易在保留特征的前提下进行。


去噪的数学模型
我们可以将问题形式化:输入是一个带噪声的网格 M,目标是找到一个干净的网格 M₀。假设噪声是加性的,则有:
M = M₀ + ε
其中 ε 是噪声。这是一个不适定问题,因为 M₀ 和 ε 均未知。
为了简化,我们通常求解每个顶点的新位置 vᵢ‘,使其构成的网格更光滑。常假设顶点沿其法向 nᵢ 偏移一个距离 δᵢ:
vᵢ‘ = vᵢ + δᵢ * nᵢ
其中 δᵢ 是待求的偏移量,nᵢ 可以是当前顶点法向的估计。
滤波与卷积

去噪在数学上常通过滤波实现。滤波的本质是卷积操作,即用一個權重函數對信號進行局部加權*均。對於連續信號,卷積定義為:
g(t) = ∫ f(τ) h(t - τ) dτ
其中 h(t) 是權重核函數(如高斯函數)。在離散網格上,這意味著用一個頂點鄰域內的其他頂點位置,以某種權重進行*均,來更新該頂點的位置。


常见的去噪方法


以下是几种常见的网格去噪方法:

-
拉普拉斯光顺:最直接的方法。每个顶点向其邻域顶点的*均位置(即拉普拉斯坐标)移动。更新公式为:
vᵢ‘ = vᵢ + λ * L(vᵢ)
其中 L(vᵢ) 是顶点 vᵢ 的拉普拉斯坐标,λ 是步长参数。但该方法可能导致网格收缩和特征模糊。 -
*均曲率流:顶点沿其*均曲率法向移动。这比均匀权重的拉普拉斯光顺更具几何意义,能更好地保持体积和特征。其权值使用余切权重。


-
双边滤波:从图像处理借鉴的方法。在加权*均时,不仅考虑空间距离(几何接*性),还考虑信号值的差异(特征相似性)。对于网格,信号值可以是顶点位置、法向等。其目的是在*滑时,跨越特征边的点对彼此影响很小。公式核心思想是使用两个高斯核的乘积作为权重。
-
法向滤波:先对网格顶点的法向进行滤波,使其变得光滑,然后根据滤波后的法向来反求新的顶点位置。这通过求解一个线性系统实现,要求每个三角形的新法向与其三条边垂直。
-
基于能量优化的方法:将去噪表述为一个能量最小化问题。最小化的能量通常包含两项:一项要求新网格与原始网格不能相差太远(保真项),另一项要求新网格本身足够光滑(光滑项)。例如:
min Σ ||vᵢ‘ - vᵢ||² + μ Σ ||L(vᵢ‘)||²
通过添加线性约束(如固定某些特征点),可以在*滑的同时保持特征。
第二部分:采样与剖分 📍
采样是将连续信号离散化的过程,而剖分是根据离散采样点构建几何单元(如三角形)的过程。高质量的采样和剖分对许多后续计算至关重要。






采样定理与质量
根据香农采样定理,采样频率必须高于信号最高频率的两倍,才能无失真地重建信号。对于几何数据,采样不足会导致欠拟合,无法捕捉细节;而用过于复杂的函数去拟合少量采样点,则会导致过拟合。
在*面上,给定一组点,如何将其连接成三角网格?我们追求“质量好”的三角化,通常意味着三角形尽可能接*正三角形,避免出现尖锐的角。
Voronoi 图与 Delaunay 三角化
给定*面点集,其 Voronoi 图 将*面划分为多个区域,每个区域包含离其对应生成点最*的所有点。Voronoi 图的对偶图就是 Delaunay 三角化。
Delaunay 三角化拥有许多优良性质:
- 空圆性质:任意三角形的外接圆内不包含其他点。
- 最大化最小角:在所有可能的三角化中,Delaunay 三角化能够最大化所有三角形中的最小内角,从而避免产生狭长的三角形。
- 其边界构成了点集的凸包。
提高网格质量的方法
如果点的分布不佳,即使使用 Delaunay 三角化,网格质量也可能很差。此时需要调整点的位置。
-
Lloyd 算法与 CVT:Centroidal Voronoi Tessellation (CVT) 是一种特殊的 Voronoi 图,其中每个生成点恰好位于其对应 Voronoi 单元的重心上。CVT 对应的点分布非常均匀,其对偶的 Delaunay 三角化质量极高。Lloyd 算法是一种迭代方法用于生成 CVT:
- 给定点集,计算其 Voronoi 图。
- 将每个点移动到其 Voronoi 单元的重心。
- 重复上述步骤直至收敛。
该方法可以推广到曲面上,此时距离需使用测地距离。
-
ODT:Optimal Delaunay Triangulation 是另一种优化能量函数来提高网格质量的方法,在某些方面具有优势。
实用工具库
- Triangle:一个功能强大的二维约束 Delaunay 三角化生成库。它可以生成高质量的三角网格,支持指定最小角度、最大面积等约束,并能在必要时自动插入Steiner点(新顶点)以满足要求。
- TetGen:一个专门生成三维四面体网格的库。给定一个封闭曲面,它可以生成高质量的四面体剖分,支持自适应细化。






总结



本节课我们一起学*了曲面去噪与采样剖分的基础知识。
在去噪部分,我们了解了噪声的定性描述、去噪的数学模型,并介绍了几种核心方法:从简单的拉普拉斯光顺,到更保特征的基于曲率流和双边滤波的方法,再到基于法向滤波和能量优化的高级技术。关键在于在*滑噪声和保持几何特征之间取得*衡。
在采样与剖分部分,我们探讨了如何从离散点生成三角网格。重点介绍了 Voronoi 图、Delaunay 三角化及其优良性质。为了提高网格质量,我们学*了通过移动采样点(如使用 Lloyd 算法生成 CVT)来优化三角化的方法。最后,介绍了一些实用的开源工具库,如 Triangle 和 TetGen。


掌握这些基础概念和方法,对于进行几何处理、三维重建和科学计算仿真等任务至关重要。



GAMES102:几何建模与处理 - P11:曲面参数化与曲面简化 🧩


在本节课中,我们将学*几何建模与处理中的两个核心主题:曲面参数化与曲面简化。我们将探讨如何将三维曲面映射到二维*面,以及如何简化复杂网格模型以提升处理效率。



卓越作业情况回顾 📊
首先回顾一下本次卓越作业的完成情况。总体上,提交卓越作业的同学完成情况都比较良好。
以下是几位完成情况较好的同学代表。卓越作业主要要求实现CVT的Lloyd算法,这是一个迭代过程。
这些同学的卓越作业界面设计得比较好。
这里有一个演示。该程序实时生成一些点,并使用Lloyd算法进行迭代。用户可以输入点的数量,也可以修改迭代次数。理论上,迭代次数越多,结果越能收敛,但某些情况下收敛会比较慢。
这是另一位同学的结果。他同样在边界上随机生成一些点,构成Voronoi图,同时显示Delaunay三角剖分并进行迭代。在这个例子中,结果较好,生成了一个比较规整的四边形网格。
这是另一位同学的成果。他同样进行实时迭代,并可以用鼠标交互式地增加点。结果可以实时计算出新的Voronoi图。交互增加点时,Voronoi图会相应更新。当然,Voronoi图也可以实现变密度,密度来源于图像的灰度。这样就能生成一种名为“stippling”的艺术效果,点的密度与图像灰度相关,颜色越深,点的密度越高。使用CVT来实现,效果也不错。
部分同学的优秀代码和卓越作业会挂在卓越作业网站上供大家参考。同学们可以根据这些优秀作业来对比自己的实现效果。
主题一:曲面参数化 🗺️
上一节我们回顾了卓越作业,本节中我们来看看第一个主题:曲面参数化。
同学们应该在前面的作业中已经体验过参数化。我们一直从映射开始讲解,映射涉及不同维数空间之间的转换。映射本身的维度就是定义域的维数。从二维到二维是*面映射,从二维到三维则是一个曲面,这是正向映射。
三维空间中的参数曲面,实际上是从一个*面区域映射过来的。它看起来是三维空间的一个曲面,但本质上是一个二维流形。这一点我们之前已经非常清楚。
这里引出了另一个问题:给定一张曲面,如何找到它的参数域,即对应的二维区域或定义域?这个问题就叫做参数化,也称为“展开”。形象地说,就是将三维空间的曲面展开成一个*面。其数学本质是寻找一个从R³到R²的映射,使得曲面上的点与*面上的点一一对应。
参数化为什么重要?在之前进行曲面和曲线拟合时,大家已经体会到了。如果参数化不好,会对曲面或曲线的拟合结果产生不良影响。虽然之前是曲线拟合,但曲面拟合同理。如果参数化空间不均匀,会导致拟合出的曲面呈现不好的性质。
参数化还有许多其他应用。例如,地图展开就是一个球面展开成*面的过程。在地图绘制中,也使用了参数化技术,在地理学中称为“球体投影”。在图形学中,最常用的应用是纹理映射。准备一个网格,中间有一条割缝,将其展开后,用一张纹理图与之对应。这样每个顶点就有一个纹理坐标。有了纹理坐标,曲面上的对应点就能获得颜色,从而可以给曲面贴图。艺术家可以基于这张贴图进行绘制,在曲面上画上颜色和图案。这相当于在曲面上绘图,因为有时直接在曲面上操作不方便,可以在*面上进行。这个*面图被称为纹理贴图。
在这个例子中,曲面被分割成若干部分,每一部分分别进行参数化,最终拼接成一个参数化集合,称为“纹理图集”(Atlas)。为什么要分割成若干片?主要是为了使每一片参数化的扭曲较小。分割得越小片,效果通常越好。后面我们会提到这一点。此外,还有许多其他应用也基于参数化的结果,我们后续会陆续展开,这里不详细介绍。
将一个曲面展开成一个二维形状或定义域,我们之前在微分几何中提到过,有一种理想的曲面叫“可展曲面”。可展曲面能够没有任何扭曲地展开成*面。可以这样理解:可展曲面是由一张*面纸,可以没有任何皱褶或挤压而拼成的形状。根据微分几何理论,可展曲面只有三类:柱面、锥面和切线面。它们可以没有任何扭曲地展开。显然,对于非可展曲面,就没有这么好的性质。因此,一般曲面的展开都会产生一些形变或扭曲。
最理想的情况当然是可展曲面。但对于任意曲面,我们需要考虑如何最小化这种扭曲,使得参数化效果尽量好。参数化的性质也包括保持其他几何特性,例如夹角。我们希望夹角尽量保持不变。对于曲面上的夹角,即某点处两条曲线的切线夹角,尽量保持不变的映射称为“共形映射”。此外,还有保持局部面积的映射,以及保持等距关系的映射。
从映射的观点来看,这些保持的性质实际上是映射F的几何量,即其雅可比矩阵的行列式。雅可比行列式的模长(行列式的绝对值)反映了度量保持的性质。如果雅可比行列式等于1,就是局部等距;大于1表示膨胀;小于1表示收缩;小于0则表示发生了翻转。因此,可以用映射F的雅可比行列式来刻画这些性质。
由于我们讨论的曲面主要采用离散表达,即曲面被分割成许多小单元,因此映射F可以分解为一系列定义在这些小单元上的函数。F是这些小单元上函数的拼接,只是拼接需要保持一定的光滑性。所以讨论F可以转化为讨论小单元(在我们这里是三角形)之间的变换。
我们知道,三角形之间的变换在*面上可以用一个相对简单的函数来*似,例如仿射变换或线性变换。也就是说,任何复杂的函数在局部都可以用其一阶部分(线性部分)来*似。
因此,我们将一个从R³到R²的映射简化。首先,将R³中的每个小单元无扭曲地旋转并*铺到一个参考*面R²上。这样,到参数域的映射就变成了从参考*面到参数域的映射。所以我们只需要考虑每个小三角形单元映射到参数域时产生的扭曲,因为从R³空间旋转到R²参考*面的过程没有扭曲,扭曲产生于从参考*面到最终参数域的映射Φ。
由于这是一个仿射变换,变换矩阵就是Φ,可以表示为*移加旋转。使用齐次坐标,变换可以写为 L * x + T,其中L是一个2x2的旋转/缩放矩阵,T是*移向量。*移对扭曲没有贡献,因此扭曲通常由这个2x2矩阵L来度量。
如何度量两个三角形之间发生的扭曲程度?在数学上,我们可以对矩阵L进行奇异值分解(SVD)。该分解将任何矩阵(这里为2x2)分解为 L = U * Σ * V^T 的形式,其中U和V是正交矩阵,Σ是对角矩阵,其对角线元素σ₁和σ₂称为奇异值,它们是 L^T * L 的特征值的*方根。假设σ₂ ≥ σ₁。
其几何意义非常好理解:它将一个单位圆变换成一个椭圆,σ₁和σ₂就是这个椭圆的半短轴和半长轴。σ₂越大,表示拉伸得越扁;如果σ₁=0,则发生退化。因此,我们可以用σ₁和σ₂来度量变换的形变程度。
从图中可以很容易地推导出几种度量:
- 如果σ₁ = σ₂,则为相似变换,即保角(共形)。
- 如果σ₁ * σ₂ = 1,则表示单位椭圆与单位圆的面积相等,即保面积。
- 如果σ₁ = σ₂ = 1,则圆映射后仍为圆,即等距(保刚性)。
对于参数化,还需要考虑如何保持低扭曲。可以看到,不同的参数化产生的扭曲是不同的。有些扭曲较小,有些则拉伸严重。
另一个问题是“翻转”。可以这样判断:如果原始三角形的顶点顺序(例如V1, V2, V3)是顺时针,变换后变成了逆时针,则表示方向(旋向)发生了翻转,这是我们不希望的。因为翻转容易导致纹理映射出现“重叠”或“镜像”等不良现象。如果没有翻转,局部映射是一一对应的,就不会有这些问题。因此,翻转也是参数化需要考虑的一个度量。
对于形变,这里有一个图演示:当一个点逐渐移动时,其σ₁会越来越小。当点跨越某条边界时,σ₁趋*于0。当点继续移动跨越边界后,σ₁可能变为负值,因为此时发生了翻转,即从右手系变成了左手系。因此,要避免翻转,就需要确保σ₁不能跨越0值,即σ₁ > 0。从之前的雅可比行列式描述也可以看出,雅可比值等于σ₁ * σ₂。如果其中有一个值为负,则雅可比值小于0,即发生翻转。
参数化领域多年来产生了大量论文,至今仍是一个非常活跃的课题,每年SIGGRAPH等顶级会议上都有相关论文。我们团队也在此领域进行了十多年的研究,特别是最*几年做了一系列工作,稍后会介绍一些主要成果。
我们将参数化方法分为三类。首先介绍第一类方法:Tutte映射法。
1. Tutte映射法
Tutte映射法大家都实现过,就是上次作业中实现的Floater方法。它基于Tutte理论并进行了拓展。该方法很简单:对于一个开曲面(有边界),先将边界点映射到*面上的一个凸多边形(可以是圆、矩形或正方形)。然后,内部每个点都是其邻域点的线性组合,组合系数可以自定义,可以是均匀权重,也可以是与几何相关的权重(如余切权重)。
这样,整个系统就形成了一个线性方程组。由于边界点被固定,求解这个方程组即可得到内部点的参数坐标。这种方法有一个非常好的理论保证:如果边界是凸的,那么理论上可以保证结果没有翻转,解是良定的。因此,虽然方法简单,但理论保证很好,至少可以生成一个无翻转的结果。


然而,由于边界是固定的,其结果往往扭曲很大。显然,因为边界不够自由,很多点会挤在一起,导致扭曲非常大。因此,这类方法也有许多人进行改进,最*几年(2015-2017)可以看到相关的工作。
2. 基于几何优化的方法
另一种改进方向是基于几何度量的优化方法。早年有ABF方法,即基于角度的展开。其出发点是保持每个三角形的角度。如果角度保持好了,三角形的相似性就得以保持。因此,它将角度作为变量来求解参数化网格。
变量就是这些角度。对于一个空间三角形映射到*面后,其周围的角度需要满足一些性质:三角形内角和为π(180度),每个顶点周围的角度和为2π。此外,还有由正弦定理保证的边长与对角正弦值的比例关系。因此,它将参数化网格的角度作为变量进行优化求解。如果解存在,就可以保证没有翻转(因为有强约束)。当然,也可能无解,导致参数化失败。整个方法是将这些约束构建成一个拉格朗日乘子系统进行求解。
这是2008年我早年的一篇文章,引用率较高,也比较知名。其思想是尽量保持每个三角形的旋转刚性。这篇文章读起来或实现起来也不难,有兴趣可以仔细看看。未来我们可能还会介绍这个方法,这里不展开。
这篇文章是我们团队的傅孝明博士在读期间的工作。它的变量是三角形到另一个三角形的变换系数,通过优化这些系数(而不是像之前那样优化角度或旋转)来求得最终的参数化结果。


3. 严格保证无翻转的方法

今天我们重点介绍第三类方法:能够严格保证无翻转的方法。这个方法实际上与第一类方法有关。第一类方法理论上保证了无翻转,但问题在于扭曲很大。这个方法的思想是:从第一类方法的结果开始,不断优化调整顶点位置,在优化过程中使扭曲逐步减小,直到无法再减小为止,并且在优化过程中始终保持三角形不发生任何退化(即不发生翻转)。这样就能在保持严格无翻转的同时,降低扭曲。
图中颜色条显示,越红表示扭曲越大,越白表示扭曲越小。可以看到,从一个充满红色的结果,慢慢优化出一个几乎没有红色的结果,表示扭曲很小。
具体如何度量扭曲?多年来,人们发明了不少扭曲度量。我们前面提到,σ₂ ≥ σ₁。可以发现,这些扭曲度量都有一个共同特性:较小的奇异值σ₁出现在分母中。我们之前也提到,如果σ₁趋*于零,其倒数将趋*于无穷大。因此,为了避免发生翻转(即避免σ₁趋*于零甚至变为负值),只需最小化那些以σ₁为分母的能量函数。因为一旦σ₁靠*零,目标函数值就会变得非常大。为了避免这种大值,优化过程会倾向于牺牲其他三角形的度量。因此,任何包含σ₁在分母中的度量都可以用来优化。
早年有这种形式。最*几年(2015年)用得比较多的度量是这篇文章提出的“对称狄利克雷能量”。可以看到,σ₁和σ₂在其中对称出现,共同考虑了两个奇异值。我们后面也主要使用这种度量。当然,使用其他度量也可以,但不同度量的优化难度可能不同。
于是,我们将问题形式化为:对每个三角形最小化其扭曲度量。约束条件是雅可比行列式大于0(表示无翻转)。实际上,σ₁和σ₂是矩阵的奇异值,与变量(顶点坐标)呈高度非线性关系。因此,整个系统是一个非常复杂的非线性、非凸优化问题,求解起来比较困难。加上三角形数量可能很多,整个求解效率是需要关心的问题。
对于优化,如果同学们做过优化就清楚,最小化一个能量函数,就是沿着其梯度方向移动。如果使用一阶梯度信息,就是一阶方法(如梯度下降);如果使用海森矩阵(二阶导数)信息,就是二阶方法(如牛顿法)。当然,中间还有一些*似牛顿法等。本质上,二阶方法是在寻找海森矩阵逆与梯度相乘的方向,然后确定步长(走多远)。因此,整个优化过程就是:给定初始值(即Tutte映射的结果),不断更新顶点坐标,沿某个方向移动一定距离。


优化过程中有几个重要量:方向、步长和初始值。*五六年,有许多论文研究如何快速求解这个非线性非凸问题。例如,2015年的这篇文章利用拟牛顿法(L-BFGS)来快速求解;2016年的这篇文章利用牛顿法,用拉普拉斯矩阵*似海森矩阵进行二次逼*来优化;2017年的这篇文章也属于一阶方法,用了另一种*似,用向量场算子逼*海森矩阵。
CM方法是最*几年比较有名的一个方法。它属于二阶方法,因此比一阶方法快。它使用了一个矩阵算子来*似海森矩阵。这是较早(2018年)的一篇文章,使用内点法进行优化,也属于二阶方法。

我想多花点时间介绍一下我们2018年的SIGGRAPH文章。前面几个方法都在寻找如何更好地*似海森矩阵,优化框架类似,只是*似海森矩阵的方式不同,旨在使优化过程更快、更稳定。我们这篇文章的思路稍有不同。

观察这张图:这个模型只有脖子处是边界,边界展开后是一个圆。中间很多头发顶点挤在一起,导致扭曲非常大。扭曲大的原因就在于很多顶点挤在一起。如果直接优化原始能量,会发现中间那些三角形的能量值非常大。从优化观点看,如果目标函数值很大,其下降速度或步长就不能设得太大,下降过程可能不会很快。


我们的想法是:将一个具有极大扭曲的能量值“压下来”。我们不去直接优化原始目标,而是构造一个中间目标函数,该函数的扭曲是有界的。这样,优化过程就会比较快。当然,优化完这个中间目标后,结果并非原始目标的最优解。因此,我们更新目标函数,再次进行优化。虽然需要多次优化,但每次优化过程由于没有非常大的能量值,所以优化速度较快。这是我们方法的一个特色:我们不是像传统方法那样去寻找更好的海森矩阵*似,而是通过修改目标函数本身来使优化变得更快。
这就是我们这篇文章的过程:有一个初始值,然后构造一些参考三角形,使得扭曲不至于太大;接着不断更新参数化结果以及参考三角形,不断优化,最终得到结果。这个过程也始终保持着防止翻转的目标。这里不详细介绍中间细节,大家可以在我们主页上找到当时SIGGRAPH的PPT,里面有非常详细的介绍。我只是向大家介绍我们这篇文章的思路:通过不断更改目标函数来达到对原始问题的求解,思路与原来非常不同。结果也非常好,数据表明至今仍是速度最快的方法之一。
这里我们对比了当时(2018年)的几种方法。可以看到,我们的方法(能量)下降得非常快。红色线是CM方法,收敛相对较慢,时间也较长。我们的方法速度非常快。这是一个非常大型的模型(Lucy),有*百万个顶点。蓝色线是我们的方法,下降很快,迭代94次就达到了非常小的能量值,而其他方法还在不断迭代收敛。


全局双射参数化


参数化的另一种特性是“全局双射”。局部无翻转是局部双射,而全局双射要求边界在远处也不相交、不碰撞。虽然每个点局部没有翻转,但全局可能发生碰撞,即网格重叠。重叠会导致曲面上的两个点对应同一个纹理坐标,从而产生纹理错乱现象。图中显示,如果没有发生全局自交,就是正常的纹理映射。
全局双射的难点在于:它不仅是映射函数的局部性质,更是一个整体行为。因为要求边界不发生重叠或碰撞,所以它是一个比较难判断的问题。如果将边界看作一个形状,那么就是一个边界不能自交或碰撞的问题,计算量较大。
在我们这项工作之前,只有两篇文章做到了这一点并进行了加速。一篇是2015年的文章,用拟牛顿法加速,但收敛较慢。另一篇是2017年的文章,想法巧妙:在参数化形状外面包裹一层更大的网格。如果这个更大网格不发生自交,就保证了内部网格不会碰撞。但它收敛也比较慢。

我们今年(指课程录制年份)发表在SIGGRAPH的论文解决了这个问题。同样时间下,我们的方法迅速收敛到结果,而其他方法还在不断迭代。2015年的方法更慢。因为这是一个全局非凸问题,不保证解的唯一性,他们可能找到了其他的局部极小值。
这是我们与这两篇全局双射方法的比较,可以看到我们的收敛速度更快,时间仅用5秒,而他们还在运行。前面提到的渐进式方法(Progressive)虽然总体上比我们略快一点,但它不保证全局无自交。我们虽然稍慢一点,但达到了全局双射的结果。可以看到时间对比:0.26秒 vs 2.23秒 vs 11.77秒,我们的方法比这两个方法快了*一个数量级,将问题的求解效率提高了一个数量级。



对于一些比较极端的例子,很多方法都无法收敛,但我们的方法仍能得到非常完美的解。
封闭曲面与割缝
刚才讨论的是有边界的开曲面参数化。但对于封闭曲面(如球面),要将其展开就必须有一条“割缝”,将其切开才能展开。因此,曲面割缝问题也是一个非常重要的问题。不切开就无法将封闭球面摊*。



割缝问题也有很多研究。早年(2002年)有一篇比较有名的文章叫“几何图像”,它贪婪地寻找参数化中扭曲最大的路径,将其作为割缝,不断寻找直到扭曲可接受为止。我们团队两年前的一位博士生柴双铭也做过


GAMES102:几何建模与处理 - P12:几何映射与几何优化 🗺️⚙️
概述
在本节课中,我们将学*几何映射的核心概念、性质以及如何通过优化方法来求解高质量的映射。几何映射是连接不同几何域(如从三维曲面到二维*面)的关键工具,广泛应用于纹理映射、形状变形等领域。同时,我们也将探讨几何处理中常见的优化问题及其求解思路。
几何映射的基本概念
上一节我们介绍了曲面参数化,其本质是将三维曲面(二维流形)展开到二维*面。这个过程可以抽象为一个映射 F。
这个映射 F 是从三维空间到二维空间的一个对应关系。更一般地,我们可以将映射视为从一个域(定义域)到另一个域(值域)的函数。在几何处理中,我们经常处理的是二维*面到二维*面的映射。
对于一个点 X(在二维中通常用坐标 (u, v) 表示),映射 F 将其映射到像 F(X),它也是一个二维向量。因此,映射可以看作是两个标量函数的组合。
映射有多种表达方式。最常规的是在某个函数空间中进行表达。设有一组基函数 φᵢ,则映射 F 可以表示为这些基函数的线性组合:
F(X) = Σᵢ cᵢ φᵢ(X)
其中,系数 cᵢ 是待求的向量(对于二维映射,每个系数包含两个分量)。基函数可以选择径向基函数(RBF)、B样条等。


在几何处理中,由于曲面通常用离散网格表示,因此映射也常采用分片表达的方式。我们可以将一个复杂的映射分解到各个小片(如三角形)上,用分片线性(仿射)变换来*似。当然,这些分片函数在边界处需要满足一定的连续性和光滑性约束。

几何映射的应用与问题
以下是几何映射的一些典型应用场景:

- 图像与形状变形:用户通过拖拽几个控制点来改变图像或形状,需要求解内部所有点的变形位置。这本质上是一个插值或拟合问题。
- 重心坐标变形:给定一个多边形及其内部点,当多边形边界顶点移动时,利用重心坐标计算内部点的新位置。这也定义了一种从原始形状到变形后形状的映射。
要构造一个好的映射,我们通常关注两个核心性质:
- 双射:映射是一一对应的,即定义域中的每个点唯一对应值域中的一个点,反之亦然。这避免了重叠或折叠现象。
- 低扭曲:映射尽可能地保持局部形状,避免过度的拉伸或压缩。
映射的性质:双射与局部单射
双射保证了全局的一一对应关系。然而,在实践中,确保全局双射非常困难。我们常常先关注一个更弱的条件:局部单射。
局部单射意味着映射在局部邻域内是一一对应的。对于三角形网格上的映射,一个关键的局部现象是 翻转。
翻转 是指一个三角形在映射后改变了其顶点环绕顺序(例如从逆时针变为顺时针)。这会导致该三角形在参数域中“折叠”起来,破坏了局部的一一对应性。
判断一个三角形是否发生翻转,可以通过计算其有向面积的符号来实现。设原始三角形顶点为 V₁, V₂, V₃,映射后顶点为 U₁, U₂, U₃。计算映射后三角形的有向面积(例如通过行列式)。如果面积符号由正变负(或反之),则发生了翻转。
雅可比行列式 是分析映射局部性质的有力工具。对于映射 F: ℝ² → ℝ²,其在点 X₀ 处的雅可比矩阵 J 为:
J = [ ∂u/∂x ∂u/∂y ]
[ ∂v/∂x ∂v/∂y ]

雅可比行列式 det(J) 的几何意义是:映射 F 将 X₀ 处一个无穷小区域的面积,放大或缩小的比例因子。




- det(J) > 0:保持定向(无翻转),且表示局部面积变化率。
- det(J) = 1:局部保面积。
- det(J) > 1:局部膨胀。
- 0 < det(J) < 1:局部收缩。
- det(J) = 0:映射退化,面积收缩为零。
- det(J) < 0:发生了翻转。

因此,要保证映射是局部单射(无翻转),一个必要条件是处处满足 det(J) > 0。但需要注意的是,处处满足 det(J) > 0(局部单射)并不能保证全局双射,因为可能在远处发生边界重叠。
映射的扭曲度量与优化模型
为了得到低扭曲的映射,我们需要定义一个度量扭曲的能量函数。通常,我们对雅可比矩阵 J 进行奇异值分解(SVD):J = UΣVᵀ,其中 Σ = diag(σ₁, σ₂),σ₁, σ₂ 为奇异值(假设 σ₁ ≤ σ₂)。它们代表了主方向上的拉伸系数。
基于奇异值,可以定义多种扭曲能量:
- 等距能量:希望映射是刚体运动,即 σ₁ = σ₂ = 1。最小化 (σ₁ - 1)² + (σ₂ - 1)²。
- 保角(相似)能量:希望映射是相似变换,即 σ₁ = σ₂。最小化 (σ₁ - σ₂)² 或 σ₁/σ₂ + σ₂/σ₁。
- 防止翻转的能量:为了强制 det(J) > 0,常使用如 σ₁/σ₂ + σ₂/σ₁ + 1/(σ₁σ₂) 形式的能量,当 σ₁ 趋*于0时,能量会趋于无穷大,从而阻止翻转发生。
一个典型的几何映射优化模型如下:
最小化 E(F) = Σ_三角形T Energy(J_T)
约束条件: det(J_T) > 0, 对于所有三角形 T
边界条件约束
其中,Energy(J_T) 是定义在每个三角形雅可比矩阵上的扭曲能量。变量可以是映射后每个顶点的坐标 U。每个三角形的雅可比矩阵 J_T 可以根据原始顶点坐标 V_T 和目标顶点坐标 U_T 解析地表示出来,因此整个优化问题是关于变量 U 的。
优化方法简介
求解上述优化模型需要用到数值优化技术。优化是一个庞大的学科,这里我们仅概述在几何处理中常见的一些概念和方法。
一个优化问题通常表示为:
最小化 f(x)
约束条件: g_i(x) = 0, i = 1,..., m
h_j(x) ≥ 0, j = 1,..., p

其中 x 是优化变量,f(x) 是目标函数(能量函数),g_i(x) 和 h_j(x) 分别是等式和不等式约束。



无约束优化
当没有约束时,常用方法包括:
- 梯度下降法:沿目标函数负梯度方向迭代:x_{k+1} = x_k - α ∇f(x_k),其中 α 是步长。
- 牛顿法:利用目标函数的二阶信息(海森矩阵)加速收敛,迭代方向为 -H⁻¹∇f。
- 拟牛顿法(如L-BFGS):当海森矩阵计算复杂或非正定时,用*似矩阵代替海森矩阵的逆。

等式约束优化
常用拉格朗日乘子法,将原问题转化为无约束问题:
L(x, λ) = f(x) + Σ λ_i g_i(x)
然后对 x 和 λ 联合求解。
不等式约束优化
更为复杂。常用方法包括:
- KKT条件:最优解需要满足的一组必要条件。
- 内点法:通过在目标函数中添加障碍函数,将不等式约束转化为在迭代中自动满足的条件,从而在可行域内部进行优化。
特殊结构与现代方法
几何处理中的能量函数常具有可分离结构:E(x) = Σ E_k(x_k),其中 x_k 是局部变量(如单个三角形的顶点)。这催生了一些高效方法:
- 局部-全局交替法:将问题分解为独立的局部子问题(每个子问题易解)和一个全局缝合问题(通常是一个线性系统),交替迭代求解。前面提到的ARAP参数化方法就是此思想的典型代表。
- 交替方向乘子法(ADMM):通过引入辅助变量和增广拉格朗日函数,将复杂问题分解为多个更易求解的子问题,交替优化。*年来在图形学中应用广泛。
对于特定类型的优化问题(如凸优化、二次规划),存在非常成熟和高效的求解器(如CVX、MOSEK)。在实践中,应根据问题的具体结构(目标函数和约束的形式)选择合适的优化策略或工具。
总结
本节课我们一起学*了几何映射与几何优化的核心内容。
我们首先明确了几何映射的基本概念,即在不同几何域之间建立对应关系。我们深入探讨了一个“好”映射应具备的两个关键性质:双射性(避免重叠折叠)和低扭曲性(保持形状)。通过引入雅可比矩阵及其行列式,我们能够精确地分析映射的局部性质,如面积变化和是否发生翻转。

接着,我们介绍了如何将构造理想映射的问题建模为一个能量最小化问题,其中能量函数用于度量扭曲(如等距能量、保角能量),并施加雅可比行列式大于零的约束以防止翻转。


最后,由于几何映射的优化模型通常是非线性、非凸的,我们概述了数值优化领域的基本概念和常用方法,包括无约束优化、带约束优化,以及特别适合几何处理问题结构的局部-全局交替法和ADMM等方法。


理解几何映射的原理和优化求解的思路,是进行纹理映射、形状变形、参数化等高级几何处理任务的基础。希望本节课能为大家后续的学*和实践提供一个清晰的指引。








GAMES102:几何建模与处理 - P13:曲面重建 🧩


概述
在本节课中,我们将学*曲面重建的完整流程。曲面重建是几何处理的核心内容之一,旨在将真实世界中存在的物体,通过数字化手段转化为计算机中的三维模型。我们将从数据采集开始,逐步讲解数据注册、预处理、曲面重建算法以及后处理等关键步骤,并了解该领域面临的挑战与未来展望。








1. 数据采集 📸
上一节我们介绍了课程概述,本节中我们来看看如何获取物体的三维数据,即数据采集。


数据采集是指使用传感器设备获取物体表面三维点坐标的过程。根据物体和应用场景的不同,采集的数据类型可以是单个点、轮廓线、截面或整个表面点云。



以下是几种常见的三维数据采集技术:
- 医学影像:如CT、MRI,通过采集物体(如人体器官)的一系列横截面图像进行三维重建。
- Shape from Shading:基于渲染方程的逆过程,从单张或多张在已知光照下拍摄的图片中恢复物体的三维形状和材质。
- 多视角几何:从不同视角拍摄多张照片,通过特征点匹配和相机参数计算,重建出物体的三维点云。其数学基础是求解一个超定方程组。
其中x_{img} = K [R|T] X_{world}K是相机内参,[R|T]是相机外参(旋转和*移),X_{world}是世界坐标系下的三维点。 - 结构光扫描:使用投影仪向物体投射特定图案(如条纹),通过单个相机捕捉变形后的图案,主动地建立对应关系,从而计算三维坐标。
- 激光雷达:主动发射激光并接收反射信号,通过测量光飞行时间或相位差直接计算距离,生成密集的点云。
- 深度相机:如微软Kinect、iPhone的LiDAR,能同时捕获RGB图像和深度信息,快速获取物体的2.5维深度图。
- 视觉外壳法:从多个视角提取物体的轮廓,将每个轮廓反向投影成空间中的视锥体,这些视锥体的交集即为物体的*似形状。
- 接触式探针:通过机械装置接触物体表面并记录探针尖端的坐标,逐点采集,精度高但效率低,可能损伤物体表面。


2. 数据注册与配准 🔄
在从不同视角采集到多个局部点云后,我们需要将它们对齐,合并成一个完整的点云,这个过程称为注册或配准。
对于刚性物体,配准问题可以归结为寻找一个最优的刚体变换(旋转 R 和*移 T),使得两个点云中对应点之间的距离*方和最小。
\min_{R, T} \sum_i ||(R \cdot x_i + T) - y_i||^2
其中 x_i 和 y_i 是来自不同点云的对应点。



迭代最*点算法 是解决该问题的经典方法:
- 为源点云中的每个点,在目标点云中寻找最*邻点作为对应点。
- 基于这些对应点,计算最优的刚体变换。
- 将变换应用于源点云。
- 重复步骤1-3,直到变换收敛或达到最大迭代次数。




对于非刚性或分段刚性的物体,需要设计更复杂的能量函数来建模变形。此外,在长时间或环形扫描中,还需解决闭环检测问题,以消除累积误差。









3. 点云预处理与巩固 🧹
配准后的点云数据往往存在噪声、离群点、采样不均、空洞等问题,直接用于重建效果不佳,因此需要进行预处理,即数据巩固。




以下是预处理中常见的任务:

- 去噪与离群点去除:过滤掉偏离曲面主体、因误差产生的错误点。
- 法向一致化:确保所有点的法向量方向一致(如都指向曲面外侧),这对后续基于隐式函数的重建方法至关重要。
- 重采样:使点云在曲面上的分布更均匀,避免局部过密或过疏影响重建质量。
- 空洞初步修复:利用点云局部信息,对小的缺失区域进行简单填充。



这些预处理步骤能显著提升输入数据的质量,为后续的曲面重建算法奠定良好基础。



4. 曲面重建算法 🏗️
点云预处理完成后,核心任务是将离散的点连接成连续的曲面(网格)。重建算法主要分为逼*法和离散法两大类。



4.1 逼*法
逼*法旨在拟合一个函数来表示曲面。

- 参数曲面拟合:使用NURBS或B样条曲面片直接逼*点云,并保证片与片之间的连续性。
- 隐式函数重建:构造一个三维标量函数
f(x, y, z),使得其零等值面f(x, y, z) = 0恰好通过或逼*给定的点云。常用方法有:- 径向基函数:使用一系列径向基函数(如高斯函数)的线性组合来拟合一个距离场。
其中f(\mathbf{p}) = \sum_i w_i \phi(||\mathbf{p} - \mathbf{c}_i||) + P(\mathbf{p})\phi是径向基函数,\mathbf{c}_i是中心点,P(\mathbf{p})是低次多项式。 - 泊松重建:通过求解泊松方程来重建指示函数,能更好地处理有向点云。
- 径向基函数:使用一系列径向基函数(如高斯函数)的线性组合来拟合一个距离场。
- 等值面提取:得到隐式函数后,使用移动立方体算法从其规则网格采样中提取三角网格表面。



4.2 离散法
离散法不显式构造函数,而是直接基于点云连接成网格。

- Delaunay/Voronoi 类方法:如 Crust 算法及其变种 Power Crust。其核心思想是计算点云的 Voronoi 图,利用其对偶图 Delaunay 三角剖分中的某些边或面来形成重建曲面。这类方法有严格的数学保证,但要求点云采样足够均匀和稠密。
- 基于优化的方法:将重建问题转化为全局优化问题。例如,L0 网格化 方法同时优化网格顶点的位置和连接关系,最小化原始点云到重建网格的距离。
- 演化模型法:如“气球膨胀”模型。在物体内部放置一个初始曲面,令其沿法向向外演化,直到接触点云边界;或同时使用内外两个曲面向中间演化,其夹逼的中间面即为重建结果。


5. 后处理与修复 🛠️
重建得到的网格可能仍存在缺陷,需要进行后处理。
- 网格修复:填补重建后仍存在的空洞。简单方法包括对孔洞边界进行三角剖分;高级方法则借鉴图像修复思想,利用网格的纹理或几何细节进行基于样例的补全。
- 去噪、光顺与简化:这些是网格处理的基本操作,我们在之前的课程中已经学*过,可用于提升重建网格的质量和效率。




6. 动态重建与挑战 🎬
对于运动物体(如人体)的重建称为动态重建。其挑战在于:
- 需要高速采集设备(如高帧率深度相机)。
- 增加了时间维度的配准问题,即如何将不同时刻的网格进行对齐和跟踪。
- 对计算效率要求极高,尤其是实时动态重建。
尽管技术不断进步,但高质量、全自动、鲁棒的曲面重建仍是计算机图形学领域的重大挑战。当前算法的性能严重依赖于输入数据的质量(如采样密度、噪声水*),而实际采集的数据往往不完美。如何突破这些限制,实现便捷高效的三维内容创建,是推动元宇宙、数字孪生等应用发展的关键。







7. 作业与课程总结 📚
本节课是曲面重建的导论。我们系统地学*了从真实物体到数字网格的完整管线:
- 采集:利用各种传感器获取三维数据。
- 注册:将多视角数据对齐合并。
- 巩固:对点云进行去噪、重采样等预处理。
- 重建:通过逼*或离散方法生成曲面网格。
- 后处理:对网格进行修复和优化。


作为本课程最后一次作业,大家可以从以下算法中选择实现,体验曲面重建的过程:
- Crust 算法(基于Voronoi图)。
- 径向基函数 隐式重建。
- 泊松重建。

希望大家通过动手实践,加深对曲面重建核心思想的理解。在接下来的课程中,我们将进入几何建模与处理的高级话题,探讨形状分析、匹配及其在机器人、制造等领域的应用。


本节课中我们一起学*了曲面重建的核心流程与主要算法,认识到将现实世界物体转化为高质量数字模型所涉及的技术广度与深度。尽管仍面临诸多挑战,但这一领域的发展无疑是连接物理世界与数字世界的关键桥梁。


GAMES102:几何建模与处理 - P14:几何建模 🛠️
在本节课中,我们将学*几何建模的核心概念与方法。课程将从重建与设计的区别开始,系统介绍从零开始设计的传统CAD方法(如实体建模)以及对已有模型进行编辑和变形的现代技术。我们将探讨点、线、面等不同层次的编辑代理,并了解基于数据驱动和深度学*的建模新趋势。



概述


上节课我们全面介绍了三维重建,从数据采集到点云处理,再到网格重建与后处理。重建的对象是物理世界中已存在的物体。然而,人们常常需要设计全新的物体,或对现有模型进行修改。本节课,我们将聚焦于“设计”这一过程,即几何建模。
建模主要分为两大方向:
- 从零开始设计:使用CAD工具凭空创造形状。
- 编辑已有模型:对给定的三维模型进行修改和变形。



我们将依次探讨这两大类方法。

从零开始设计:传统CAD建模 🏗️
传统CAD建模源于机械、土木等工程领域,旨在精确地设计和表达零件。以下是几种核心方法。
三视图建模


三视图建模,或称工程制图,通过物体的三个正交投影视图(顶视图、前视图、侧视图)来唯一确定三维形状。设计者需要具备空间想象能力和几何计算能力,以确保不同视图中的投影关系正确。

核心思想:三维形状由其二维投影唯一决定。



实体建模

实体建模是CAD领域的核心,它直接构建具有体积的实体对象,而不仅仅是表面。以下是几种基本操作:
扫掠
扫掠操作将一个二维截面沿着一条轨迹线移动,扫过的空间即构成实体。
- 线性扫掠:截面沿直线*移。
实体 = 拉伸(二维截面, 方向, 距离)
- 旋转扫掠:截面绕一根轴旋转。
实体 = 旋转(二维截面, 旋转轴, 角度)
- 沿路径扫掠:截面沿一条曲线路径移动,并可保持截面与路径垂直。
- 放样:在两个或多个不同形状的截面之间进行过渡,生成光滑的实体。
参数化与约束
参数化建模允许通过修改参数(如尺寸、角度)来驱动模型变化。设计约束用于保持特定的几何关系(如*行、垂直、等长),这些约束构成了“设计意图”。
倒角与圆角
对实体的尖锐边缘进行切削或添加圆滑过渡,称为倒角或圆角。
布尔运算
通过并集、交集、差集等布尔操作,组合基本几何体(如立方体、球体、圆柱体)来构建复杂形状。
复杂实体 = 基本体A <布尔操作> 基本体B


边界表示
实体在计算机内部通常采用边界表示法存储,即记录构成实体边界的所有点、边、面的信息及其拓扑关系。它必须满足欧拉公式以保持流形性。
欧拉操作
一系列低层级的原子操作(如生成边、生成面),用于逐步构造出复杂的边界表示结构。
编辑已有模型:交互式变形与编辑 ✏️
对于动画、游戏和数字艺术等领域,更常见的是对已有模型进行直观的编辑和变形。其核心思想是:通过编辑一个简单的“代理”,来驱动复杂模型的变形。
编辑的方法论
编辑过程可以抽象为以下步骤:
- 为原始复杂形状
S找到一个简单、易编辑的代理P。 - 用户交互修改代理
P得到P‘。 - 通过一个映射函数
G,将代理的变化传递回原始形状,得到变形后的结果S‘。S‘ = G(P‘)

关键在于寻找合适的代理 P 和定义有效的映射函数 G。
基于点的编辑
代理 P 是一组顶点(句柄)。用户拖动少数几个点,并固定一些点,系统自动计算其他顶点的位置,使变形自然。
数学本质:这是一个插值问题。需要找到一个变形函数 F,满足固定点位置不变、拖动点到达目标位置,同时尽可能保持模型的局部细节。
常用方法:
- 径向基函数插值
- 移动最小二乘法
- 基于向量场的变形:将模型嵌入到一个连续向量场中,编辑场中的流线来驱动变形,能有效避免自交。
- 拉普拉斯坐标编辑:保持每个顶点的拉普拉斯坐标(局部细节)不变或相似,通过求解线性系统得到新顶点位置。这是非常经典且有效的方法。
基于线/骨架的编辑
代理 P 是曲线,如模型的骨架、轮廓线或特征线。编辑曲线比编辑大量顶点更直观。


关键问题:定义曲面上每个顶点与骨架/曲线之间的权重关系(蒙皮权重)。当骨架变形时,顶点根据权重混合骨架的变化。
顶点新位置 = Σ(权重_i * 骨骼变换_i * 顶点原位置)
基于体/笼子的编辑




代理 P 是一个包围模型的简单多面体网格(称为“笼子”)。编辑笼子的顶点,模型内部的点通过广义重心坐标随之变形。
内部点坐标 = Σ(重心坐标_i * 笼子顶点坐标_i)

基于简化的编辑


代理 P 是原始模型的简化版本。在简化模型上编辑后,将简化过程中去除的细节重新添加回去,得到最终的高细节变形结果。

基于草图的建模

这是一种“从无到有”的编辑。用户绘制二维草图,系统自动推断并生成三维模型。这大大降低了三维建模的门槛。

数据驱动的建模与深度学*

随着三维模型数据集的增长,数据驱动的方法变得流行:
- 部件组装:从模型库中提取语义部件(如椅腿、椅背),重新组合成新模型。
- 概率模型:使用贝叶斯网络等模型学*部件之间的搭配概率,指导用户创作或自动生成。
- 深度学*生成:使用生成对抗网络等深度学*模型,学*三维形状的隐式分布,可以直接生成新的三维模型。




形状渐变 🌀
形状渐变解决的是如何在两个给定形状 A 和 B 之间,生成一系列自然的中间过渡形状。它广泛应用于关键帧动画和工业设计。

形状渐变分解为两个核心问题:
- 对应关系问题:建立形状
A和B上点与点之间的一一映射。 - 插值路径问题:在对应关系建立后,如何插值对应点以生成中间形状。






建立对应关系


- 参数化方法:将两个形状参数化到同一个公共域上,在公共域上建立对应,再映射回三维空间。
- 基于简化:先简化两个模型,在简单的基网格上建立对应,再传递回原始网格。
- 基于分割:将模型分割成语义部件,在部件级别建立对应。
- 现代映射方法:将对应问题转化为优化问题,最小化映射的几何扭曲。


插值路径
- 线性插值:最简单,直接对对应顶点的坐标进行线性插值。可能导致体积收缩和不自然旋转。
- 非线性插值:
- 插值内在属性:插值边长、夹角等网格内在属性,而非顶点坐标。
- 仿射变换插值:将每个三角形的变换分解为旋转和缩放,分别插值。
- 隐函数插值:为两个形状构造符号距离场等隐式函数,插值隐函数,再提取中间等值面作为过渡形状。



总结

本节课我们一起学*了几何建模的两大支柱:从零开始设计的传统CAD方法和编辑已有模型的现代交互技术。

- CAD建模 侧重于精确性、参数化和制造约束,是工业设计的基石。
- 模型编辑 侧重于直观性、交互性和艺术表达,其核心思想是通过编辑简单代理来控制复杂模型。
- 形状渐变 是连接设计与动画的重要技术,重点在于建立模型间的合理对应并生成自然的插值路径。






尽管已有众多方法,三维内容创作因其高维性和对空间想象力的要求,仍然是一个充满挑战的领域。数据驱动和深度学*等新范式正在为这个领域带来新的活力。希望本课程能为你打开几何建模世界的大门,提供一个继续探索的“指针”。



GAMES102:几何建模与处理 - P15:纹理合成与形状分析 🎨🧩





在本节课中,我们将学*课程的最后一部分内容:纹理合成与形状分析。我们将探讨如何为几何模型赋予纹理,以及如何从更高层次分析和理解三维形状。







作业回顾 📝
上一节我们介绍了曲面重建与建模。本节开始,我们先回顾最后一次作业的情况。

总体上交作业的同学表现不错。以下是部分同学的作业结果:

- 一位同学实现了显示方法(Cross-stitching算法),算法出现一些小问题,例如连接处发生错误。但总体上大部分结果是正确的。顶点加密后可以避免这种错误。
- 另一位同学使用泊松方法进行重建。泊松方法是一种隐式方法,需要点的法向。如果法向发生错误,重建结果就会出现异常。大部分异常现象是因为法向相反。由于是作业,同学们可能没有太多时间检查法向的正确性,因此结果存在一些错误。
- 一位同学录制了视频演示。首先展示点云,然后调用Marching Cubes算法重建网格。重建结果不错,因为数据质量较好,法向都正确。
- 另一位同学进行了比较完整的演示,实现了两种方法:RBF(径向基函数拟合)和泊松拟合。在菜单中可以选择不同方法,并对距离场进行自适应剖分来计算距离值。切换到泊松方法后可以看到,如果顶点不是特别密,重建质量不是特别好,会出现一些小问题。


我们同样会把最后一次作业的报告、代码以及优秀同学的报告代码挂在网上供大家参考。


纹理合成概述 🖼️

回顾前几次课程,我们从曲面重建讲到曲面建模。重建是物体已存在,我们通过设备测量出点,再利用算法重现三维模型。建模是通过用户交互进行设计。这些模型只有顶点数据,没有颜色,看起来不真实。我们需要为模型贴上纹理,使其看起来更真实。
为模型赋予纹理有多种手段。对于重建的模型,物体本身带有颜色,只需采集其RGB颜色并对应到模型顶点即可。对于设计出来的模型,纹理后期需要通过交互手段赋予,例如参考图片并通过参数化结果上色,或直接用手工画笔在*面或曲面上绘制颜色。
今天我们不讲解如何绘制真实纹理,而是关注纹理合成。假设我们已经有一个小的纹理样本,我们能否将这个样本铺满到一个几何模型上?这就是纹理合成要解决的问题。

纹理合成的定义是:给定一个小的纹理样本 I,目标是合成一张大的纹理 J。J 看起来像 I,但又不是简单的重复。这个“看起来像”的度量很困难,且需要带有一定的随机性。这个问题在20多年前开始成为研究热点。
纹理合成的用途广泛:
- 为枯燥的光滑曲面赋予丰富纹理,使其更形象。
- 图像修补,例如将图片中某块区域去掉,然后用小纹理样本填充空洞。
- Photoshop中的克隆画笔工具就是一种简单的拷贝,但纹理合成不仅仅是拷贝,还需要带有变化。


纹理的定义与分类 🔍




我们首先需要理解什么是纹理。纹理(Texture)与一般图像(Image)有所不同。

纹理通常指物体表面的表现,如木纹、墙砖、草地等,带有重复性。一个关键特征是它具有很多重复的相似模式。

如何判断一张图是纹理还是图像?
- 对于纹理,在任意两个地方取同等大小的框,肉眼看会觉得它们差不多,属于同一种纹理。这种各处都相似的表现称为纹理。
- 对于图像,在任意两个地方取框,看起来显然不同,没有太多相似性。

根据属性,纹理可以分类为:
- 各项同性:各个方向看起来差不多。
- 各项异性:不同方向有差异。
- 规则重复:像砖头一样一块块重复。
- 随机:看不出明显重复。
- 介于两者之间。


不同的纹理性质,其合成方法会略有差别。


二维纹理合成方法 🧱
我们先理解二维纹理合成,三维就容易类比。



简单*铺与目标
最简单的纹理合成就像铺瓷砖,将小样本一块块贴满区域。但这会产生很强的重复特征,看起来太规则,不是纹理合成期望的目标。我们更希望合成结果有一定的随机性,不是完全*铺,但看起来仍然是原始样本的纹理。
纹理合成没有唯一解,很多算法结果都是可接受的,算法通常带有一定的随机性。
核心挑战:量化“看起来像”
纹理合成的难度在于如何捕捉或描述“看起来像”这种视觉感受。这不是一个精确的数学定义,很难量化。解决问题的第一步就是如何将其建模成可量化的东西。



今天主要讲两种基于样本的方法:参数化方法和非参数化方法。
- 参数化方法:对纹理进行描述,构建数学模型(函数),然后将函数应用到区域生成纹理。
- 非参数化方法:从样本中拷贝像素或图块,在过程中产生随机现象。




参数化方法 (Parametric Method)




参数化方法在早年应用较多。其思想是对纹理生成一个多分辨率金字塔,在每个分辨率上用一个函数(映射)提取特征,将纹理隐射到一个特征空间,然后再通过函数逆映射回图像。这有点像现在的自编码器(AE)或变分自编码器(VAE)。




具体构造映射有不同方法,例如使用卷积或全字塔滤波提取特征。多分辨率方法可以捕捉不同尺度的特征。一个简单的早期算法使用了直方图(颜色分布)进行匹配和迭代。但直方图只体现了颜色分布,没有很好刻画特征,因此合成效果有时不好。


非参数化方法 (Non-parametric Method)
非参数化方法思想更简单,它不用函数描述纹理,而是直接从原始样本中拷贝。拷贝过程中不是从同一地方考,而是可以发生随机现象。这个过程可以从数学上解释为一个马尔可夫随机场:拷贝了某一块后,其周边的像素颜色就不能乱拷贝了,需要根据这一块的特征去样本里搜索最相似的周边图块。这就像一个条件概率过程。



1. 基于像素的方法 (Pixel-based)



这是最简单的逐像素纹理合成方法。
- 算法步骤:假设右边已经合成到某个位置,当前未知像素
p。取p周边已知颜色的L型区域。用这个L型区域的颜色到样本图像中所有位置进行比对,找到最相似的位置。将该位置p点的颜色拷贝过来。以此类推,逐个像素合成。 - 计算量:最大的计算量在于比对
L型区域的颜色与样本中所有位置的颜色。度量通常使用 L2 范数(逐像素差异*方和)。这是一个检索问题,需要用数据结构(如 kd-tree、八叉树)组织样本中的特征向量以加速搜索。 - 窗口大小影响:用于比对的窗口(邻域)大小很重要。窗口太小,合成质量低;窗口合适,才能抓住纹理的结构(pattern)。参数取决于样本中 pattern 的大小。



2. 基于图块的方法 (Patch-based)

逐像素方法很慢。基于图块的方法一次拷贝很多像素,速度更快。
- 算法步骤:假设已经合成了一块区域。用这块区域的一个邻域(通常是重叠部分)去样本中搜索最相似的图块。然后将这个最相似的图块拷贝过来。一次操作可以拷贝一个图块(如 10x10 像素),速度比逐像素快很多。
- 接缝处理:两个图块重叠在一起时,它们可能不完全一致。需要找一条误差最小的接缝,使得拼接后视觉效果更光滑。这可以转化为在误差图上寻找最小代价路径的问题,是一个典型的动态规划问题,在图论中也是最小割/最大流问题。
- 应用:这种基于图块和最小接缝的技术非常适合自然图像合成,也是当年“内容感知图像缩放”技术的核心原理。内容感知缩放能保持图像明显特征,避免简单缩放带来的扭曲或裁剪导致的内容丢失。

3. 其他高级方法
为了保持纹理 pattern 的完整性,避免合成结果破碎,后续研究提出了许多方法:
- 基于纹理元素:手工或通过视觉方法判断纹理中的基本元素(texel),在合成过程中保持这些元素的结构。
- 控制局部大小与方向:合成时可以控制纹理图块的大小和方向。通过生成一个向量场来引导合成顺序,可以产生不同朝向的纹理结果。
三维纹理合成 🎯
理解了二维纹理合成,三维可以类比进行。核心思想类似,但需要在三维曲面上定义邻域、方向和参数化。
主要方法分类
- 过程式纹理:用一个三维函数
F(x, y, z)定义纹理特征(颜色值)。将三维模型嵌入到这个场中,模型表面顶点即可获得颜色值。函数可以通过一些方法构造,例如著名的 Perlin 噪声函数,通过扰动和随机生成各种纹理。这种纹理是定义在三维空间中的体纹理,物体内部切开来也有颜色。 - 基于样本的曲面纹理合成:给定一个小样本,如何将纹理“长”在曲面上。这需要解决几个关键问题:生成多密的采样点、如何定义曲面上点的邻域、如何定义方向。
- 思路:类比二维,在曲面上对于一个点,需要朝各个方向走一定距离来采样其邻域。这需要定义一个向量场来指示方向。将局部曲面片参数化到*面,然后在参数域上进行采样和颜色匹配,最后将颜色拷贝回顶点。
- 多分辨率方法:在网格上也可以使用多分辨率方法,低分辨率捕捉大特征,高分辨率捕捉细节,使匹配更精准。
- 基于图块的合成:也可以将一个面片(patch)贴到曲面上,然后根据邻域匹配找到最好的图块贴上去,像贴膏药一样。为了保持纹理元素完整,图块形状可能是不规则的,沿着纹理元素边界设置。
- 向量场的重要性:对于各项异性的纹理,一个好的向量场对合成结果至关重要。向量场需要光滑,并且可能要求与模型的几何特征(如边界)对齐。生成符合要求的向量场本身是几何处理中的一个重要问题,通常可以看作插值问题。



形状分析与理解 🧠
最后一部分我们简要介绍形状分析与理解。这部分内容属于高级主题,涉及从更高层次理解三维形状。
随着互联网上三维模型越来越多,我们需要组织、理解和检索这些模型。这超出了传统局部几何处理的范围,涉及全局操作和语义理解。


主要研究问题
- 特征检测:检测模型上的关键点、特征线、显著性区域。
- 方向归一化:将模型摆正,使其具有一致的朝向,便于后续处理。
- 分割:将模型分割成有意义的部件。
- 语义标注:为分割后的部件赋予标签(如头、腿、扶手)。
- 对称性检测:检测模型的对称性(镜面对称、旋转对称等),用于压缩和编辑。
- 骨架提取:提取模型的一维骨架,用于抽象表示和分类。
- 对应关系:找到不同模型之间点、部件或整体的对应关系。
- 形状检索与分类:基于形状特征从数据库中检索相似模型,或对模型进行分类。
- 结构分析:分析模型部件的层次结构、连接关系。
- 功能分析:分析模型部件的功能、运动机构以及人与物体的交互方式。



形状描述子
解决上述任务的关键是度量形状元素之间的相似性。这就需要形状描述子——将点、面片、部件或整个形状映射到一个高维向量(描述子),通过比较向量间的距离(如 L2 范数)来度量相似性。
- 人工设计特征:早期工作依赖于人工设计的特征,如曲率、法向、局部面积/体积、测地距离等。这些特征需要针对不同任务和模型类型进行精心设计和选择。
- 端到端学*:最*5-10年,深度学*方法崛起。通过神经网络,可以直接从输入数据(如点云、网格)学*到适合特定任务的特征描述子,无需人工设计。这本质上是拟合一个复杂的函数,但可解释性较弱。
课程总结与展望 🌟
本节课我们一起学*了纹理合成与形状分析的基础概念和方法。
几何数据随着采集设备发展而日益丰富,成为表达三维世界的新数字媒体。其应用越来越广,但上手难度高于图像处理,因为它涉及点、网格、体素、距离场、函数等多种表达方式,拓扑复杂。
GAMES102 课程至此结束。本课程系统性地介绍了从拟合、参数曲线曲面、细分、离散网格处理到纹理合成的基础知识。虽然时间紧凑,但希望能帮助大家建立几何处理的知识框架。
后续 GAMES *台将继续推出高质量课程,如《高质量实时渲染》(GAMES202)和《三维视觉与理解》(GAMES203)。此外,也欢迎大家关注图形学相关的开源项目、学术会议和书籍。
感谢各位同学15周以来的陪伴与坚持!学*过程虽有挑战,但成长最快。希望大家在几何处理的道路上继续探索,收获更多。




课程结束,感谢大家!


GAMES102:几何建模与处理 - P2:数据拟合 📊
在本节课中,我们将继续学*数据拟合的核心内容。我们将回顾函数映射的基本概念,深入探讨如何寻找一个“好”的函数来拟合给定数据,并理解函数拟合的三个关键步骤:定义函数空间、设定目标函数以及进行优化求解。课程将涵盖多项式插值、最小二乘拟合、径向基函数(RBF)等经典方法,并揭示它们与神经网络之间的内在联系。
回顾与引言 🔄
上一节我们介绍了函数拟合的基本框架。函数映射,即给定一个输入 x 输出一个 y,是我们分析的基础。目前我们限定 x 和 y 为实数,未来可以推广到向量、矩阵甚至张量。例如,一张图片可以看作一个矩阵,因此映射函数可以推广到高维。
我们面临的核心问题是:给定一组 (x, y) 数据对,如何找到一个函数来拟合它们?这个过程分为三步:
- 定义什么是“好”的函数。
- 确定从哪个函数集合(函数空间)中寻找。
- 通过优化目标函数(通常是求梯度)来找到最佳函数。
理解这个框架后,我们就能更好地处理更复杂的拟合问题。
函数拟合的目的与挑战 🎯
我们为什么要进行函数拟合?给定一些观测点(离散数据),拟合一个函数主要有两大好处:
- 压缩存储:用少量系数存储复杂函数,代替存储大量原始数据。
- 预测:有了函数模型,可以对新的输入
x预测其输出y。
然而,定义一个“好”的拟合函数并非易事。以下是几种常见情况及其挑战:
以下是几种常见的拟合思路及其优缺点:
- 分段线性插值:用直线段连接所有数据点。误差为零,但函数不够光滑(仅
C^0连续),在连接点处不可导,不利于后续数值计算。 - 光滑插值:寻找一个经过所有数据点的光滑函数。但若数据含有噪声或异常值,强行插值会导致函数扭曲,预测不可靠。
- 拟合(逼*):允许函数不精确经过每个点,但要求整体误差较小。这种方法对噪声和异常值有一定鲁棒性。
绝对好的方法并不存在,选择取决于具体应用场景和领域知识。如果对数据背后的规律一无所知,拟合将变得非常困难,通常需要反复“调参”。
拟合方法论三部曲 📝
寻找拟合函数可以系统化为以下三步:
- 确定函数空间:我们需要限定函数的搜索范围。通常,我们将目标函数表示为一系列基函数的线性组合:
f(x) = Σ λ_i * φ_i(x)。问题转化为求解系数λ_i。基函数的选择至关重要,它决定了函数空间的表达能力。 - 定义目标函数:我们需要一个标准来衡量函数的好坏。目标函数通常包含两部分:
- 误差项(Data Term):衡量函数预测值
f(x_i)与真实值y_i的差距,常用*方和Σ (y_i - f(x_i))^2。 - 正则项(Regularization Term):对函数或系数加以约束,防止过拟合或得到病态解。例如,惩罚系数的大小(L1/L2正则化),或惩罚函数的高阶导数(控制光滑度)。
- 误差项(Data Term):衡量函数预测值
- 优化求解:在设定的函数空间中,寻找使目标函数最小的系数。对于*方误差这类形式,求导后可化为线性方程组求解。对于更复杂的目标函数,可能需要梯度下降、牛顿法等优化算法。
多项式插值及其问题 ⚠️
用 n 次多项式 P_n(x) 插值 n+1 个点,可以通过求解范德蒙德方程组得到系数。拉格朗日插值法和牛顿插值法是两种预先计算基函数、避免重复求解方程组的技巧。
然而,多项式插值存在显著问题:
- 病态问题:范德蒙德矩阵的条件数随着点数增加而指数级增长,导致方程组对输入数据的微小扰动极其敏感,求解不稳定。
- 龙格现象:使用等距节点的高次多项式插值,在区间边缘会出现剧烈的振荡。这是因为幂函数基
{1, x, x^2, ...}在高次时数值性质很差。
因此,高次多项式插值在实践中并不常用。
更好的基函数:伯恩斯坦基 ✨
同一个多项式空间可以用不同的基来表示。伯恩斯坦基函数是一组定义在 [0, 1] 区间上、具有良好数值性质的基函数。
以下是伯恩斯坦基函数的关键性质:
- 非负性与权性:
B_i^n(x) >= 0,且对任意x,Σ B_i^n(x) = 1。这组基函数可以看作一组“权”。 - 数值稳定性:用伯恩斯坦基表示的多项式,即使次数很高(如20次、50次),也不会出现龙格现象,计算稳定。
- 几何意义:系数可以视为控制顶点,调整顶点位置能直观地改变曲线形状,便于几何设计。


基函数之间的变换由矩阵描述。选择数值性质优良的基函数,是获得稳定算法的基础。
径向基函数(RBF)拟合 🎯
径向基函数是另一种强大的拟合工具。在一维情况下,最典型的RBF是高斯函数:g(x) = exp(-(x-μ)^2 / (2σ^2))。
RBF拟合的核心思想是:在每个数据点 x_i 处放置一个高斯函数(或其他RBF),然后对这些基函数进行线性组合来逼*目标函数:f(x) = Σ w_i * g(||x - x_i||)。
以下是RBF拟合中的关键点:
- 参数选择:高斯函数的中心
μ(常取为数据点x_i)和宽度σ需要设定。σ过小会导致基函数像脉冲,拟合曲线不*滑;σ过大则过于*滑,可能欠拟合。 - 稠密性保证:理论上,只要使用足够多的高斯函数,其线性组合可以逼*任意连续函数。
- 与神经网络的联系:将上述
f(x)的表达式稍作变形:f(x) = Σ w_i * g(a_i * x + b_i)。这可以看作一个单层神经网络:输入x经过仿射变换a_i*x + b_i,再通过激活函数g(此处为高斯函数),最后加权求和输出。这里的w_i, a_i, b_i都是可学*的参数。
因此,RBF网络是一种特殊的神经网络。优化这些参数是一个非线性优化问题,通常只能找到局部最优解,初始值的选择非常重要。


神经网络的函数视角 🧠
从函数拟合的角度看,神经网络就是一个复杂的函数 f(x; θ),其中 θ 代表所有权重和偏置参数。


- 万能逼*定理:只要神经网络具有足够多的神经元(即足够大的容量),它可以以任意精度逼*任何连续函数。
- 本质:神经网络通过多层线性变换与非线性激活函数的复合来构造复杂的函数空间。训练网络就是寻找最优参数
θ,使网络输出f(x_i; θ)尽可能接*真实值y_i。 - 挑战:与所有拟合方法一样,神经网络也面临如何选择网络结构(层数、每层神经元数)、如何设置正则化防止过拟合、以及如何有效优化非凸损失函数等问题。

卷积、池化等操作,可以理解为在网络结构中引入了具有特定物理或几何意义的函数变换(如局部*滑、降维)。









课程总结 🏁



本节课我们一起深入学*了数据拟合的核心内容。





我们首先回顾了函数拟合的三部曲方法论:选择函数空间、定义目标函数、优化求解。然后,我们分析了多项式插值的局限性,如病态问题和龙格现象,并介绍了性质更优的伯恩斯坦基函数。
接着,我们探讨了径向基函数(RBF)拟合方法,揭示了其通过基函数的*移与缩放来组合逼*目标函数的本质,并建立了RBF与单层神经网络之间的直接联系。
最后,我们从函数拟合的视角重新审视了神经网络,将其理解为一个通过大量参数定义复杂函数空间的工具,其训练过程就是在该空间中寻找最佳拟合函数。


理解这些方法的共性与差异,掌握从“定义空间”到“优化求解”的完整思维链条,是灵活运用各种拟合技术解决实际问题的关键。在接下来的课程中,我们将把这些关于函数的理解推广到曲线与曲面的建模与处理中。


GAMES102:几何建模与处理 - P3:参数曲线拟合 📈
在本节课中,我们将要学*如何将函数拟合的概念从一元函数推广到多元函数,并最终聚焦于参数曲线拟合这一核心主题。我们将理解如何用数学方法描述和拟合空间中的曲线,并探讨参数化的重要性。
课程回顾与引入
上一节我们介绍了单变量函数的拟合问题。回顾前两次课程,我们主要讨论了定义域和值域都是实数的一元函数。理解这种函数形式是基础,因为高维函数可以看作是它的推广。
函数拟合主要涉及三个关键问题:
- 函数集合的选择:需要确定从哪个函数集合中寻找目标函数。这个集合需要有良好的结构(如线性结构)和强大的表达能力。
- 度量标准:需要定义一个损失函数来度量候选函数与数据之间的逼*程度。除了逼*误差,还可能加入对函数光滑性或系数大小的约束,即正则项。
- 求解方法:将拟合问题转化为优化问题求解。如果能量函数是系数的二次型,则变为解线性方程组;如果是复杂的非线性非凸问题,则需使用梯度下降等数值方法寻找局部最优解。
理解了这些,我们就可以将视野扩展到更一般的情况。
从一元到多元函数
本节中,我们来看看当函数的输入变量从一个变为多个时,情况有何变化。多元函数是从 R^n 映射到 R 的函数,例如 z = f(x, y)。我们可以将其可视化:x 和 y 构成*面,f(x, y) 作为高度,形成三维空间中的一个曲面。
对于更高维度的输入(如 g = f(x, y, z)),我们无法直接可视化,但可以研究其数学性质,如偏导和梯度。在实际问题中,变量维度 n 可能非常大(如图像处理中的像素),这时直观理解变得困难,需要依靠数学方法。
多元函数的基函数构造
如何为多元函数定义基函数?最常用的方法是张量积。
假设我们有两个一维基函数集合:关于 u 的 {b1(u), b2(u), b3(u)} 和关于 v 的 {b1(v), b2(v), b3(v)}。通过两两相乘,我们可以构造二元基函数:
bij(u, v) = bi(u) * bj(v)
其中 i, j = 1, 2, 3。这样就得到了9个二元基函数,它们的线性组合可以表示一个二元函数。通常,为了简便,两个方向会使用相同的基函数集合。
张量积的优缺点:
- 优点:只需定义好一维基函数,就能自动构造多维基函数。
- 缺点:基函数数量随变量维度呈*方级增长。例如,100维输入需要约
100^2 = 10000个基函数,这在计算上难以承受。这就是传统方法处理高维数据时面临的“维数灾难”。
神经网络的启示
神经网络巧妙地绕过了维数灾难问题。它使用统一的激活函数(如 Sigmoid 或 ReLU),通过权重和偏置的线性组合对输入进行仿射变换,再经过激活函数,形成新的基函数。一个简单的单隐层网络可以表示为:
# 伪代码示意:单隐层神经网络前向传播
hidden = sigma(W1 * input + b1) # 仿射变换 + 激活函数
output = W2 * hidden + b2 # 线性组合输出
其中,sigma 是激活函数,W1, b1, W2, b2 是待学*的参数。隐层节点数 m 控制了模型的表达能力。m 太小会导致欠拟合,太大会导致过拟合,需要通过调参来*衡。深度神经网络则是多个这样的函数复合而成。
神经网络的本质就是用一种参数化的、表达能力强大的函数族来进行拟合,尤其擅长处理高维数据。
向量值函数与参数曲线
现在,我们进入本节课的核心:向量值函数。这类函数的输入是一个实数,输出是一个高维向量,即映射 R → R^m。
可以将其理解为 m 个独立的单变量函数:
P(t) = ( x1(t), x2(t), ..., xm(t) )
每个分量 xi(t) 都是关于参数 t 的函数。
几何解释:参数曲线
向量值函数有清晰的几何意义。当 m=2 时,P(t) = (x(t), y(t)) 表示*面中的一条曲线。当 m=3 时,P(t) = (x(t), y(t), z(t)) 表示空间中的一条曲线。参数 t 可以理解为时间,曲线就是质点随时间运动的轨迹。
关键概念:虽然曲线上的点位于高维空间(如三维),但曲线本身的本质维度是1,因为它是由单个参数 t 描述的。我们所在的高维空间称为嵌入空间。参数曲线强大的地方在于,它可以描述非常复杂的、甚至非函数型的曲线(例如一个闭合圆)。
类似地,参数曲面是由两个参数 (u, v) 描述的曲面,其本质维度是2,例如球面。更一般地,一个从 R^n 映射到 R^m 的参数化表示,描述了 n 维流形在 m 维空间中的嵌入。
参数曲线拟合问题
理解了参数曲线,我们现在来解决如何用参数曲线拟合给定的一系列有序点 {P_i = (x_i, y_i)}。
问题可以表述为:寻找一个参数曲线 P(t) = (x(t), y(t)) 和一组参数值 {t_i},使得曲线在 t_i 处的值 P(t_i) 尽可能接*数据点 P_i。
损失函数可以定义为所有数据点误差的*方和:
Loss = Σ_i || P(t_i) - P_i ||^2
其中 ||.|| 表示向量的模长。x(t) 和 y(t) 可以分别用我们之前学过的基函数组合来表示,例如多项式基函数。
关键挑战:参数化
上述问题中,数据点 P_i 是已知的,但对应的参数值 t_i 是未知的。为每个数据点分配一个参数值 t_i 的过程,就叫做参数化。参数化本质上是将高维数据点降维到一维参数域,且要求这种映射能保留数据的结构信息。
以下是几种常见的参数化方法:


- 均匀参数化:最简单直接。假设参数
t在[0, 1]区间均匀分布,t_i = i / (n-1)。这种方法忽略了点与点之间的实际距离。 - 弦长参数化:更符合几何直觉。根据相邻点之间的弦长(欧氏距离)比例来分配参数。
总弦长 L = Σ_i || P_{i+1} - P_i || 第k点的参数 t_k = (Σ_{i=0}^{k-1} || P_{i+1} - P_i ||) / L - 中心参数化:一种更复杂的参数化方法,旨在产生更光滑的拟合曲线。




参数化的重要性:不同的参数化方法会导致完全不同的拟合结果。均匀参数化在点分布不均匀时容易产生扭曲;弦长参数化通常能产生更自然的曲线;中心参数化可能在某些情况下得到最光滑的结果。选择好的参数化是曲线拟合成功的关键。




参数化的广泛应用


参数化不仅是曲线拟合的核心步骤,在几何处理中还有更广泛的应用:




- 曲面参数化:将三维曲面映射到二维*面,同时尽可能保持几何属性(如角度、面积)。这对于纹理映射至关重要。
- 流形学*:对于高维数据,寻找其低维本质参数(流形结构),类似于自编码器(Autoencoder)的目标。如果降维后的维度低于数据的本质维度,信息将无法无损恢复。
- 几何设计:在工业设计中(如汽车、飞机叶片),外形通常由艺术家绘制草图,然后需要转化为精确的数学参数曲面进行制造。历史上,设计师使用物理样条(富有弹性的木条或金属条)来绘制光滑曲线,而数学上的样条函数(特别是三次样条)正是由此启发而来,它用分段多项式来拼接出一条整体光滑的曲线。这将是后续课程的重要内容。

课程总结

本节课中,我们一起学*了参数曲线拟合的核心思想。我们从一元函数拟合出发,推广到多元函数和向量值函数,并理解了参数曲线的几何意义。我们明确了拟合参数曲线的关键步骤:参数化和函数拟合,并介绍了均匀、弦长等参数化方法。最后,我们看到了参数化在曲面纹理映射、流形学*以及几何设计中的重要作用。

通过本课,希望大家能建立起一个统一的观点:无论是简单的函数拟合,还是复杂的神经网络、曲线曲面建模,其数学本质都是在某个函数空间中寻找对目标数据的最佳逼*。理解了这一点,就能更好地掌握后续更深入的几何建模与处理方法。




附:作业三简介
请实现参数曲线拟合。给定一系列有序的二维点,分别使用均匀、弦长参数化方法为点分配参数,然后利用多项式基函数分别拟合 x(t) 和 y(t),最终得到拟合曲线。比较不同参数化方法对拟合结果的影响。




GAMES102:几何建模与处理 - P4:三次样条函数 🧮



在本节课中,我们将学*三次样条函数。这是一种在几何设计和工业建模中至关重要的数学工具,用于通过一系列控制点生成光滑的曲线。我们将从三次样条的历史背景和力学原理讲起,逐步推导其数学表达,并探讨参数连续性与几何连续性的区别。


作业情况回顾 📊


上一节我们介绍了参数化方法对曲线拟合的影响。本节开始前,先回顾一下作业三的提交情况。


作业三总体提交了41份,内容相对简单,核心是实现有序点列在不同参数化方法下的插值。总体完成效果良好。

以下是几位同学的优秀作业展示:
- 同学 lxt:利用游戏引擎或类似工具制作了交互界面,背后算法由C++实现。可以实时拖动控制点,并生成参数曲线,支持差值执行和B样条等不同方法,不同颜色代表不同参数化方法,结果直观。
- 同学 常清俊:在报告中清晰展示了相同点列在不同参数化(弦长、中心、均匀、Foley)下的拟合结果。当点分布均匀时,各种方法结果相似;当点分布不均匀时,结果差异显著,例如均匀参数化可能导致曲线出现自交。
- 另一位同学:使用高斯基函数和RBF神经网络进行拟合。报告展示了对于低维(如一维、二维)数据,可以直观判断拟合好坏;而对于高维数据,则只能通过损失函数值来判断。
部分优秀作业和报告已挂在课程主页,可供参考。
三次样条的起源:从物理样条到数学函数 📐

上一节我们开启了几何设计的主题。在计算机出现之前,设计师使用一种称为“样条”的物理工具来绘制自由曲线。
设计师会先确定一些关键点(形值点),然后用沉重的“压铁”固定这些点。接着,他们将一根富有弹性的软木条(即样条)绕过这些压铁,让其自然弯曲,从而描出一条光滑的曲线。
这就引出了一个核心问题:这条由物理样条自然弯曲形成的曲线,其背后的数学表达式是什么?这正是三次样条函数要解决的问题。

从力学角度分析,软木样条可视为一根弹性梁。在小挠度(弯曲角度不太大)的假设下,通过欧拉-伯努利梁方程进行推导,可以得出结论:在两个压铁(形值点)之间,曲线的数学表达式是一个三次多项式。整个样条曲线因此是分段的三次多项式。

选择三次多项式是因为:一次函数是直线,表达能力不足;二次函数是抛物线,没有拐点;四次及以上多项式计算不稳定且拐点过多;三次多项式恰好有一个拐点,既能表达丰富形状,又相对稳定。
三次样条函数的数学推导 🔢
既然已知曲线是分段三次函数,那么如何推导其具体方程呢?核心思路是利用已知的形值点和曲线段之间的连续性约束来建立方程组。
假设有 n+1 个形值点,则中间有 n 段曲线。每一段三次多项式有4个未知系数,因此总共有 4n 个未知数。
我们需要建立 4n 个方程来求解:
- 插值条件:曲线必须经过每一个形值点。这提供了
n+1个方程。 - 内部连续性条件:为了保证曲线整体光滑,在内部
n-1个拼接点处,要求相邻两段曲线具有相同的函数值(C⁰连续)、一阶导数值(C¹连续)和二阶导数值(C²连续)。这提供了3(n-1)个方程。
目前总方程数为 (n+1) + 3(n-1) = 4n - 2,比未知数 4n 少了2个。因此,需要额外指定两个边界条件,通常在曲线的两个端点处给出。常见的边界条件有:
- 自然边界:指定端点处的二阶导数为零。
- 固定切线(夹持)边界:指定端点处的一阶导数(切线方向)。
添加两个边界条件后,我们就得到了一个 4n 阶的线性方程组,可以唯一确定所有系数。
在实际推导中,常引入中间变量简化计算。例如,设每个形值点处的二阶导数值为 m_i。由于每一段的三次函数的二阶导数是线性的,可以表示为两端点二阶导数的线性插值:
y_i''(x) = m_i * (x_{i+1} - x) / h_i + m_{i+1} * (x - x_i) / h_i
其中 h_i = x_{i+1} - x_i。对此式积分两次,并结合插值条件与连续性条件,最终可以得到一个以 m_i 为未知数的线性方程组。因其系数矩阵是三对角且对角占优的,可以使用高效的追赶法求解。这个方程组在样条理论中称为“三弯矩方程”。
类似地,也可以以一阶导数作为未知数推导,得到“三转角方程”。求解思路一致。
从函数到曲线:参数样条 🧵
上一节我们介绍了函数与曲线的联系。三次样条函数是 y = f(x) 的形式,存在多值性问题(一个x对应多个y),无法描述任意曲线。
将其推广到曲线非常直接:将曲线视为一个向量值函数 C(t) = (x(t), y(t), z(t))。对三个坐标分量 x, y, z 分别独立地应用上面推导的三次样条函数方法(基于相同的参数 t 序列),然后将结果组合起来,就得到了三次参数样条曲线。
参数 t 的序列(参数化)可以通过弦长参数化、中心参数化等方法获得,这在之前的作业中已经实践过。
连续性的深入:参数连续 vs 几何连续 🔄
我们之前用 C0, C1, C2... 来定义连续性,这称为参数连续性。它要求曲线在拼接点处具有直到n阶的相同的参数导数。
然而,参数连续性依赖于具体的参数化方式。一个经典的例子是:将一条直线段用两种不同的参数化方式表示成两段,在拼接点处计算参数导数,结果可能不相等,从而被判定为 C0 连续而非 C1 连续。但这显然与“直线是无限光滑的”几何直觉相悖。
问题在于,参数连续性受参数选择的影响,不能完全反映曲线内在的几何光滑性。
因此,引入了几何连续性的概念。其定义是:如果存在一个参数变换,使得两条曲线在拼接点处达到 Cn 参数连续,则称这两条曲线是 Gn 几何连续的。
几何连续性是曲线本身的内在属性,不依赖于参数化:
- G0:与
C0相同,即点连续(端点重合)。 - G1:切线连续。要求两段曲线在拼接点处具有相同的切线方向,但切线向量的长度可以不同。
- G2:曲率连续。要求两段曲线在拼接点处具有相同的曲率。曲率是几何不变量。
Gn 的条件比 Cn 更宽松,为设计师提供了更大的灵活性。在许多图形设计软件(如 PowerPoint、Adobe Illustrator)的曲线编辑工具中,调节顶点类型(*滑点、角点)本质上就是在控制曲线的几何连续性(G1 或 G0)。
贝塞尔曲线简介:更直观的设计工具 ✨



最后,我们为下节课内容做一个铺垫。之前用幂基 {1, t, t^2, t^3} 表示多项式曲线时,其系数(控制点)与曲线形状的关联不直观,不利于设计。


工程师皮埃尔·贝塞尔发现,使用一组称为伯恩斯坦基的函数作为新的基,来表示同样的多项式空间时,其系数(称为控制顶点)具有极佳的几何意义:
- 曲线的起点和终点分别与第一个和最后一个控制顶点重合。
- 曲线的形状被“拉向”中间的控制顶点,整体趋势与控制多边形(依次连接控制顶点形成的折线)相似。
这种用伯恩斯坦基和控制顶点定义的曲线,就是著名的贝塞尔曲线。它使得设计师可以通过直观地拖动控制顶点来预测和调整曲线形状,极大地便利了几何设计。我们将在下节课详细探讨它的性质。
本节课总结 📝
本节课我们一起学*了:
- 三次样条函数的起源:从物理样条的力学原理推导出其分段三次多项式的数学本质。
- 三次样条的数学推导:通过插值条件、内部连续性条件和边界条件建立方程组,并介绍了以二阶导数为未知数的“三弯矩方程”求解方法。
- 参数样条曲线:将三次样条函数应用于各个坐标分量,以生成参数曲线。
- 两种连续性:理解了依赖于参数化的参数连续性与反映曲线内在光滑性的几何连续性之间的区别与联系。
- 贝塞尔曲线引介:了解了使用伯恩斯坦基函数能带来更直观的几何控制,为后续学*打下基础。



通过本课,你已掌握了二维矢量图形编辑(如软件中的曲线工具)背后的核心数学原理——三次样条函数。


GAMES102:几何建模与处理 - P5:Bezier曲线与B样条曲线 🧮
在本节课中,我们将要学*计算机图形学中两种核心的曲线表示方法:Bezier曲线和B样条曲线。我们将从函数拟合的基本概念出发,探讨它们如何从数学形式转化为直观的几何设计工具,并重点分析它们各自的性质、优势以及在实际设计中的应用。
概述:从函数拟合到几何设计
上一节我们介绍了函数拟合的基本概念。在实际造型中,有一个重要概念叫“逆向工程”,即通过采集产品表面的点,再通过拟合方法将其外形重建出来。这涉及到曲线拟合,即给定一系列点,寻找一个“好”的函数来逼*它们。
这个“好”的函数通常从一个由“基函数”定义的函数空间(或集合)中寻找。例如,二次函数空间的形式为 f(t) = a*t² + b*t + c,其中a, b, c是待定系数。对于参数曲线,形式为 P(t) = (x(t), y(t)),可以将其写成向量形式,基函数前的系数就成了空间中控制曲线形状的“顶点”。
然而,如果基函数(如幂函数 1, t, t²)选得不好,曲线形状与控制顶点之间的关系会非常不直观。用户调整一个顶点,曲线可能产生难以预料的剧烈变化,这不利于交互式设计。因此,我们需要寻找具有更好几何意义的基函数。
Bezier曲线:直观的几何设计工具 ✏️
上一节我们提到了函数拟合,本节中我们来看看一种革命性的设计工具——Bezier曲线。它的核心在于使用了一组具有优良几何性质的基函数:Bernstein基函数。
Bernstein基函数
Bernstein基函数在数学上已存在数百年,其形式如下:
B_i^n(t) = C(n, i) * t^i * (1-t)^(n-i), 其中 t ∈ [0,1], i = 0, 1, ..., n。
这里 C(n, i) 是组合数。n次多项式有n+1个Bernstein基函数,它们构成了不高于n次的多项式空间的一组基,与幂基等价且可以相互转换。
以下是低阶Bernstein基函数的图像示例(此处为描述,实际教程应配图):
- 0阶: 常数函数1。
- 1阶:
B₀¹(t)=1-t,B₁¹(t)=t,是两个线性函数。 - 2阶: 三个二次函数,形状类似抛物线。
- 3阶: 四个三次函数,在[0,1]区间内*滑分布。
这些基函数具有两个关键性质:
- 正性: 对于
t ∈ [0,1],所有B_i^n(t) ≥ 0。 - 权性(单位分解): 对于任意
t,所有基函数值之和为1,即Σ B_i^n(t) = 1。
Bezier曲线的定义与性质
基于Bernstein基函数,我们定义Bezier曲线。给定n+1个控制顶点 P₀, P₁, ..., P_n,n次Bezier曲线为:
C(t) = Σ B_i^n(t) * P_i, t ∈ [0,1]。
控制顶点顺序连接形成的多边形称为控制多边形。Bezier曲线具有以下重要性质,这些性质使其非常适合设计:
- 端点插值: 曲线经过第一个和最后一个控制顶点,即
C(0)=P₀,C(1)=P_n。 - 凸包性: 由于基函数的正性和权性,曲线上任一点都是控制顶点的凸组合,因此整条曲线位于控制多边形的凸包之内。
- 端点切向: 曲线在起点处的切向量方向与第一条边
(P₁-P₀)一致,在终点处的切向量方向与最后一条边(P_n-P_{n-1})一致。对于三次曲线,切向量长度是边长的3倍。 - 几何不变性: 曲线的形状仅取决于控制顶点的相对位置,与坐标系选择无关。
- 变差缩减性: *面Bezier曲线与任意直线的交点个数不多于其控制多边形与该直线的交点个数。
de Casteljau算法:高效的几何求值方法
为了高效、稳定地计算Bezier曲线上任意参数 t 对应的点,我们使用de Casteljau算法。这是一个基于线性插值的递归算法:
算法描述:
给定控制顶点 P_i^0 = P_i (i=0,...,n) 和参数值 t。
对于 r = 1 to n:
对于 i = 0 to n-r:
P_i^r = (1-t) * P_i^{r-1} + t * P_{i+1}^{r-1}
最终得到的 P_0^n 即为曲线上参数 t 对应的点 C(t)。
这个算法的几何意义非常直观:它通过不断对控制多边形的边进行线性插值和细分,最终收敛到曲线上的点。不仅如此,该算法还将原曲线在 t 处分割成两段子Bezier曲线,并同时给出了这两段子曲线的控制顶点。这为曲线的分割、离散化和求交等操作提供了基础。
Bezier曲线的拼接与样条
单段Bezier曲线通过增加次数(提高阶数)来增加控制顶点,从而描述更复杂的形状,但高次曲线可能产生不必要的震荡,且局部修改会影响整体。


为了构造复杂曲线,我们通常采用分段低次Bezier曲线进行拼接,形成Bezier样条。关键问题在于如何保证拼接处的连续性。
以下是保证连续性条件的几何解释(以三次Bezier曲线段 [P0,P1,P2,P3] 和 [Q0,Q1,Q2,Q3] 在 P3=Q0 处拼接为例):
- C⁰/G⁰连续(位置连续): 自然满足,只需
P3 = Q0。 - G¹连续(切向连续): 要求
P2, P3(Q0), Q1三点共线。即切线方向相同,但长度可以不同。 - C¹连续(参数连续): 在G¹连续的基础上,进一步要求
|P3 - P2| = |Q1 - Q0|。即切线方向相同且长度相等。 - C²连续(曲率连续): 条件更为复杂,涉及到五个控制顶点
(P1, P2, P3, Q1, Q2)的关系,要求二阶导数相等。
一种实用的构造C¹连续三次Bezier插值样条的方法是:对于给定的数据点 D_i,在每两个点之间构造一段三次Bezier曲线。中间的两个控制点可以这样确定:在 D_i 处,沿 (D_{i+1} - D_{i-1}) 的方向,在两侧各取 |D_{i+1} - D_{i-1}| / 6 的距离放置控制点。这种方法具有局部性,修改一个数据点只影响相邻的曲线段。

B样条曲线:兼具局部性与灵活性的强大工具 🔧



上一节我们学*了Bezier曲线,它虽然直观,但具有全局性——修改任一控制顶点,整条曲线都会受到影响(尽管影响程度随距离减小)。本节中我们来看看B样条曲线,它通过使用具有局部支撑的基函数,完美解决了这个问题。


从全局性到局部性的需求
Bezier曲线的全局性源于其Bernstein基函数在整個定义域 [0,1] 内都不恒为零。在设计中,我们常常希望修改曲线的一部分时,远离该部分形状能保持不变,即需要局部可控性。
B样条(Basis Spline)曲线正是为此而生。它的核心思想是:构造一组只在部分参数区间非零的基函数(即具有紧支撑),使得每个控制顶点只影响曲线的一部分。

B样条基函数的定义

B样条基函数由一个节点向量 T = [t₀, t₁, ..., t_{m}] 定义,其中 t_i 是单调非减的序列。对于 p 次(或 p 阶,阶数=次数+1)B样条,第 i 个基函数 N_{i,p}(t) 通常由著名的Cox-de Boor递归公式定义:
-
零次(p=0)基函数:
N_{i,0}(t) = { 1, 如果 t_i ≤ t < t_{i+1}; 0, 其他 } -
高次(p>0)基函数:
N_{i,p}(t) = (t - t_i)/(t_{i+p} - t_i) * N_{i,p-1}(t) + (t_{i+p+1} - t)/(t_{i+p+1} - t_{i+1}) * N_{i+1,p-1}(t)
规定0/0 = 0。
这个递归定义表明,高次的B样条基函数是由两个低一阶的相邻基函数线性组合而成,从而继承了低阶基函数的性质并提高了光滑性。
B样条曲线的定义与重要性质
给定 n+1 个控制顶点 P₀, P₁, ..., P_n,次数 p,以及节点向量 T = [t₀, t₁, ..., t_{n+p+1}],定义的 p 次B样条曲线为:
C(t) = Σ N_{i,p}(t) * P_i, 其中 t_p ≤ t ≤ t_{n+1}。
B样条曲线拥有比Bezier曲线更丰富和灵活的性质:
- 局部支撑性: 基函数
N_{i,p}(t)仅在区间[t_i, t_{i+p+1})上非零。因此,控制顶点P_i只影响曲线在[t_i, t_{i+p+1})区间内的部分。这是实现局部修改的数学基础。 - 凸包性: 曲线段
C(t), t ∈ [t_i, t_{i+1}]位于定义该段的p+1个控制顶点(P_{i-p}, ..., P_i)的凸包内。整个曲线位于所有控制顶点的凸包内。 - 连续性: 在节点区间内部,曲线是无限光滑的
C^∞多项式。在节点t_i处,曲线至少是C^{p-m_i}连续的,其中m_i是该节点的重数(即在节点向量中重复出现的次数)。通过调整节点重数,可以精确控制曲线的连续性。 - 变差缩减性: 同Bezier曲线。
- 仿射不变性: 同Bezier曲线。
- 强凸包性: 比Bezier曲线的凸包性更强,曲线段位于更小的局部凸包内。
- 灵活性: 通过插入节点或调整节点向量,可以在不改变曲线形状的前提下修改其表达,或进行局部细化。
节点向量与重节点的作用
节点向量 T 是B样条的灵魂。它分为几种类型:
- 均匀B样条: 节点等距分布,如
[0,1,2,3,4,5]。 - 准均匀B样条: 两端节点具有
p+1的重度,内部节点均匀分布,如[0,0,0,1,2,3,4,4,4](对于p=2)。这是最常用的形式,它使曲线插值于首末控制顶点。 - 非均匀B样条: 节点任意非减分布,提供了最大的灵活性。
重节点的关键作用:
- 降低连续性: 在
p次B样条中,一个k重节点处曲线的连续性降至C^{p-k}。当k = p时,曲线在该处仅为C⁰连续(位置连续);当k = p+1时,曲线在该处甚至断开(或理解为插值于该控制顶点)。 - 插值控制顶点: 使一个控制顶点对应的节点区间长度为零,可以让曲线插值于该控制顶点。通常通过使首末节点为
p+1重来保证曲线经过首末控制点。 - 生成尖锐特征: 在设计中,可以通过插入重节点在曲线上制造尖角或直线段。
B样条与Bezier的关系
Bezier曲线是B样条曲线的一个特例。一个 n 次的Bezier曲线,等价于一个 n 次的、节点向量为 [0,0,...,0,1,1,...,1](前后各 n+1 个重复节点)的B样条曲线。此时,B样条基函数退化为Bernstein基函数。
总结与展望 🌟
本节课中我们一起学*了计算机图形学中两大核心的曲线模型。
我们首先回顾了函数拟合的基本问题,并指出了用于设计的曲线需要直观的几何意义。由此,我们引入了Bezier曲线,它利用Bernstein基函数将控制顶点以加权*均的方式组合成光滑曲线,具有端点插值、凸包性等优良性质,并通过de Casteljau算法实现了高效的几何求值与分割。我们还探讨了如何将多段Bezier曲线拼接成样条以满足复杂造型需求。
接着,为了克服Bezier曲线全局修改的缺点,我们深入探讨了B样条曲线。B样条通过定义在节点向量上的、具有局部支撑性的基函数,实现了曲线的局部可控性。我们学*了Cox-de Boor递归公式,并详细分析了B样条曲线的性质,特别是节点重数对曲线连续性的精确控制这一强大特性。B样条统一并推广了Bezier曲线,成为工业标准(如NURBS)的基础。
无论是Bezier还是B样条,其核心思想都是:用一组易于控制和理解的“基函数”去组合用户提供的“控制顶点”,从而生成期望的光滑曲线。 基函数决定了曲线的数学性质(如连续性、局部性),而控制顶点提供了直观的几何编辑手段。




下节课,我们将探讨有理曲线(NURBS),它通过引入“权因子”的概念,使得曲线能够精确表示圆锥曲线(如圆、椭圆),从而将Bezier和B样条的能力扩展到更广阔的几何领域,最终形成工业界通用的NURBS标准。此后,我们将把曲线理论推广到曲面,并逐步进入离散几何处理的世界。





GAMES102:几何建模与处理 - P6:NURBS曲线、细分曲线、隐式曲线与NURBS曲面 📐




在本节课中,我们将学*几何建模中几种重要的曲线与曲面表示方法。我们将探讨NURBS曲线、细分曲线、隐式曲线以及NURBS曲面的核心概念、动机和应用。这些内容是现代计算机辅助设计(CAD)和计算机图形学的基础。





作业回顾与优秀案例展示 📝
上一节我们介绍了样条曲线的构造方法。本节开始前,我们先回顾一下上次的作业。
本次作业涉及求解方程组,特别是推导三转角方程,具有一定难度。总体提交率接*30%,但提交的同学完成得相当出色。
以下是部分优秀作业案例:
- 案例一: 该作业实现了一个矢量曲线编辑与设计工具。用户可以调节曲线节点及其切线,可以全局求解三次样条以获得处处C²连续的曲线,也可以调节中间节点以获得G¹连续性,甚至产生尖点(C⁰连续)。其交互设计已达到实用程度。
- 案例二: 该作业界面不同,但同样支持实时叠加、拖动顶点,并可以增加输入点。它也能够改变切线的连续性,设计尖点,功能完善。
- 案例三: 该作业展示了灵活的设计能力,例如将曲线调整为兔子耳朵的尖点(C⁰连续),或将部分调整为G¹连续以保持圆滑。这些工具为艺术家和设计师提供了强大的灵活性。
这些优秀作业报告将与同学们分享。
NURBS曲线:从Bézier到有理形式 🔄
回顾前五节课,我们从函数拟合到Bézier曲线,本质上都是在为每个控制顶点叠加一个权函数来构造曲线。Bézier曲线使用的Bernstein基函数在定义域[0,1]上是全局非零的,因此修改一个顶点会影响整条曲线,缺乏局部性。
为了获得局部可控性,人们引入了样条曲线,其基函数只在局部节点区间非零。这样,设计师可以分段设计曲线,互不干扰。
然而,Bézier曲线本质上是多项式,无法精确表示圆、椭圆等圆锥曲线,而这在工程中至关重要。为了解决这个问题,人们引入了有理Bézier曲线。
其核心思想是:在更高维的空间(投影空间)中定义一条Bézier曲线,然后将其投影回原空间。投影过程引入了除法,从而得到了有理形式。




有理Bézier曲线的公式为:
[
C(t) = \frac{\sum_{i=0}^{n} w_i P_i B_{i,n}(t)}{\sum_{i=0}^{n} w_i B_{i,n}(t)}
]
其中,( w_i ) 是权因子,( P_i ) 是控制顶点,( B_{i,n}(t) ) 是n次Bernstein基函数。
当所有权因子 ( w_i = 1 ) 时,公式退化为标准的Bézier曲线。权因子 ( w_i ) 可以影响曲线的形状:权值越大,曲线越靠*对应的控制顶点。
引入有理形式后,曲线可以精确表示圆锥曲线。例如,要表示1/4圆,只需设置两端点权值为1,中间权值为 ( \sqrt{2}/2 ) 即可。
NURBS 是 Non-Uniform Rational B-Spline 的缩写,即非均匀有理B样条。它综合了以上概念:
- Non-Uniform (非均匀):指定义基函数的节点向量可以是非均匀分布的,这提供了更一般的控制能力(如产生尖点)。
- Rational (有理):指采用了上述有理形式,可以精确表示圆锥曲线。
- B-Spline (B样条):指其基础是B样条基函数。
因此,NURBS曲线的形状由三个因素共同决定:控制顶点、节点向量和权因子。它继承了B样条的优良性质(如凸包性、变差缩减性),同时表达范围更广,已成为CAD领域的工业标准数据格式。
细分曲线:通过迭代加细构造光滑曲线 ✂️
细分曲线提供了一种不同于基函数组合的曲线构造方法。其灵感来源于Bézier曲线的de Casteljau作图法,可以看作对一个初始多边形不断“割角”或“补角”以使其光滑的过程。
细分方法的核心在于两个规则:
- 拓扑规则 (Topological Rule):如何增加新的顶点(即在哪里加)。
- 几何规则 (Geometric Rule):如何计算新顶点的位置(即加到哪)。
细分方法主要分为逼*型和插值型。
1. Chaikin割角法 (逼*型):
这是最早的细分方法之一。对于每条边,取距离端点1/4和3/4处的两个点作为新顶点,连接这些新顶点形成新的、更密的多边形。不断重复此过程,极限曲线是光滑的。
2. 四点插值细分法 (插值型):
这种方法保持所有旧顶点位置不变,只在旧边之间插入新顶点。新顶点的位置由相邻的四个旧顶点加权*均得到。一个著名的公式是:
[
P_{new} = \frac{9}{16}(P_i + P_{i+1}) - \frac{1}{16}(P_{i-1} + P_{i+2})
]
其中,( P_{i-1}, P_i, P_{i+1}, P_{i+2} ) 是四个相邻旧顶点。权值中的参数需要在一定范围内(如 ( \alpha = 1/16 ))才能保证极限曲线光滑,否则可能产生分形曲线。
细分方法的收敛性和光滑性分析通常通过将其过程表示为矩阵乘法,然后分析该细分矩阵的特征值来完成。特征值的性质决定了极限曲线的行为。
作业五将让同学们实现Chaikin细分和四点插值细分,以体验其简单而强大的构造能力。





隐式曲线:由方程定义的曲线 🎯
之前学*的曲线都是参数曲线,即点的坐标由一个参数t的函数明确给出:( C(t) = (x(t), y(t)) )。
隐式曲线则由一个方程定义:( F(x, y) = 0 )。所有满足该方程的点(x, y)构成了这条曲线。例如,直线 ( ax+by+c=0 ),圆 ( x2+y2-R^2=0 )。
隐式曲线可以看作二元函数 ( z = F(x, y) ) 的图形与*面 ( z=0 ) 的交线。它非常适合表示封闭曲线和处理无序点集。
如何绘制隐式曲线?—— Marching Cubes算法思想(二维即Marching Squares):
对于复杂的 ( F(x, y)=0 ),很难求出显式表达式。Marching Squares算法提供了一种数值化绘制方法:
- 用网格覆盖定义域,计算每个网格顶点处的函数值 ( F(x, y) )。
- 根据每个网格单元四个角点函数值的正负号(如+或-),判断等值线 ( F=0 ) 是否穿过该单元,以及穿过的可能方式。
- 根据预设的几种情况模板,在单元内用直线段*似连接等值点。
- 遍历所有网格单元,将线段连接起来,就得到了隐式曲线的多边形*似。
如何从点集重建隐式曲线?
给定一个无序点集(假设来自一条封闭曲线),我们可以拟合一个隐式函数 ( F(x, y) ) 使得:
- 在点集上,( F(x_i, y_i) = 0 )。
- 在点集内部,( F(x, y) < 0 );在外部,( F(x, y) > 0 )。
这可以通过为点集及内外附加点赋予函数值(如0, +1, -1),然后利用前几节课的函数拟合/插值技术(如RBF径向基函数)来求解一个满足这些约束的二元函数 ( F )。最后,提取 ( F=0 ) 的等值线即为重建曲线。著名的“泊松重建”方法就是此思想在三维的推广。
NURBS曲面:从曲线到曲面的张量积推广 🧩
理解了曲线之后,曲面就变得直观。NURBS曲面是NURBS曲线在二维参数域上的直接推广,采用张量积(Tensor Product) 形式构造。
NURBS曲面公式为:
[
S(u, v) = \frac{\sum_{i=0}{m}\sum_{j=0} w_{i,j} P_{i,j} N_{i,p}(u) N_{j,q}(v)}{\sum_{i=0}{m}\sum_{j=0} w_{i,j} N_{i,p}(u) N_{j,q}(v)}
]
其中:
- ( P_{i,j} ) 是控制顶点网格。
- ( w_{i,j} ) 是权因子。
- ( N_{i,p}(u) ) 和 ( N_{j,q}(v) ) 分别是u方向和v方向的p次、q次B样条基函数。
- 节点向量也分别定义在u和v方向上。
可以将NURBS曲面的构造理解为:先沿一个参数方向(如v方向)构造一系列曲线,再将这些曲线上的点作为新的控制点,沿另一个参数方向(如u方向)构造曲线。曲面的性质(如凸包性、局部性)是曲线性质的直接延伸。



由于定义域是矩形参数域,对于复杂拓扑曲面(如带洞曲面),通常采用裁剪NURBS(Trimmed NURBS) 技术,即在参数域上定义一条闭合曲线来表征“洞”,然后映射到曲面上进行裁剪。
此外,还有定义在三角域上的曲面片(如Doo-Sabin、Loop细分曲面),适用于非矩形区域的拟合,其基函数通常采用重心坐标的形式。
总结 🎓
本节课我们一起学*了几何建模中四种核心的曲线曲面表示方法:


- NURBS曲线/曲面:作为工业标准,它通过控制顶点、节点向量和权因子提供了强大且灵活的设计能力,并能精确表示圆锥曲线。
- 细分曲线:通过定义简单的迭代加细规则,从粗糙多边形快速生成光滑曲线,概念直观,计算简单。
- 隐式曲线:由方程定义,擅长处理无序点集和表示复杂封闭形状,其绘制和重建依赖于数值方法和函数拟合技术。
- 曲面构造:NURBS曲面是曲线理论的张量积推广,而三角域上的曲面片则提供了处理非矩形区域的工具。

所有这些方法背后,函数拟合的思想贯穿始终。无论是选择基函数、设置权值,还是重建隐式函数,本质都是在寻找一个满足特定约束或目标的函数来表达几何形状。


从下节课开始,我们将进入课程的另一半,学*离散曲面(三角网格) 的表示、处理与分析,这是现代数字几何处理的核心内容。



GAMES102:几何建模与处理 - P7:曲线光顺与离散曲线三角网格 🧵
在本节课中,我们将学*曲线光顺的概念、离散曲线的必要性以及三角网格的基本知识。课程内容从作业回顾开始,逐步深入到曲线光顺的数学定义、离散化方法,并最终引入三维三角网格的数据结构与处理基础。




作业回顾与点评 📝


上一节我们介绍了曲线细分方法。本节中,我们来看看同学们提交的作业情况。


本次作业主要任务是实现B样条细分和四点细分算法。共收到29份有效提交,其中包含多种编程语言的实现。
以下是部分优秀作业示例:
- 蔡金细分方法:报告详细阐述了三次细分与二次细分(Doo-Sabin细分)的区别。逼*型与插值型细分均有实现。
- 参数调整:有同学探索了四点细分中α参数的影响。当α值超过1/8时,曲线会产生分形般的振荡,这验证了参数对结果的重要性。
- 交互界面:多位同学实现了友好的交互界面,能够实时调整控制点、细分类型(二次/三次/四点)、细分次数,并可视化曲线与细分过程。
这些作业代码与报告将作为学*参考。建议同学们在理解原理的基础上独立实现,以更好地掌握这些工具。
曲线光顺(Fairing)概念 🔍
在宏观曲线设计之外,高精度建模中还有一个重要概念——曲线光顺。这在许多教材中涉及较少,但在工业设计软件(如CATIA)中却是关键工具。
光滑与连续
首先回顾连续性的概念。参数连续(Ck)关注参数导数的连续性,而几何连续(Gk)关注曲线本身的几何性质,与参数化无关,更能反映本质。
什么是光顺?
光顺的英文是“Fairing”,意为“公*的”、“流畅的”。一条曲线可能非常光滑(C^∞),但在微观尺度下,其曲率分布可能仍有剧烈波动。这种波动可能导致:
- 物理性能下降:如船舶在水中阻力增加,齿轮磨损加剧。
- 视觉效果不佳:如汽车表面反光出现扭曲。
因此,光顺关注的是曲线曲率变化的“*缓”程度,是一种微观几何性质。
曲率:光顺的核心度量
曲率 k 是描述曲线弯曲程度的本质量,与参数化无关。其定义为密切圆半径 r 的倒数:k = 1/r。
- 直线曲率为0。
- 圆上各点曲率为常数。
- 曲率变化*缓的曲线更光顺。
历史上对光顺有多种描述:
- 苏步青、刘鼎元:曲线需C²连续,且曲率图没有多余波动。
- R. Farin:曲率图单调段不宜过多。
- 能量法定义:最小化曲线的总曲率*方积分 ∫ k² ds。
- 工程经验(中国学者):光顺是大局部问题,需同时满足:
- 具有C¹或更高连续性。
- 曲线本身拐点少。
- 曲率图的拐点少。
- 曲率变化幅度小。
光顺方法简介
基于上述原则,传统光顺方法通过微调控点来调整曲率图,主要步骤包括:
- 粗光顺:调整控制点,减小曲率极值。
- 削峰:消除多余的曲率拐点。
- 回弹:检查并减少曲率单调段的变化。
这些操作需在保证曲线形状变化极小的前提下进行。优化后的曲线,其曲率图变得更为*缓单调。
从曲线到曲面
曲面光顺更为复杂,因为涉及两个主方向。早期方法将曲面分解为两个方向的曲线进行处理。现代方法则考虑曲面的主曲率,并最小化其能量积分。在工业界,A级曲面(Class-A Surface)要求达到G²甚至G³连续,并通过反射线(Reflection Line)来直观检测光顺度。
离散曲线与计算 📊
所有连续表达在计算机中都必须离散化才能进行计算和渲染。本节我们来看看离散化的必要性与方法。




离散化的必要性

离散化主要有三个原因:
- 渲染:图形硬件(GPU)主要处理直线段(多边形)。任何曲线都必须采样成折线进行光栅化。
- 计算:数值计算(如求导、求交)需要在离散点上进行。用足够密的线段可以逼*原曲线。
- 制造:许多数控机床(CNC)只支持直线和圆弧插补,复杂曲线必须离散为这两种基本运动。
采样与误差
根据采样定理,为了从采样点重建原信号,采样频率需至少为原信号最高频率的两倍。对于B样条曲线,可以通过分析控制多边形与弦长的误差,来估计需要细分多少次才能达到指定精度。
离散曲线的几何计算
当只有离散点而无数表达式时,计算几何属性(如法向、曲率)有两种思路:
- 拟合法:先用离散点拟合出一条连续曲线(如B样条),再利用该曲线的解析表达式求导。
- 差分法:直接用离散点的差分来*似导数。例如,一阶差分*似一阶导数(切线),二阶差分*似二阶导数(曲率)。其本质是泰勒展开的离散形式。
重心坐标与变形应用 🎯
在介绍三角网格前,我们先看一个连接连续与离散表达的重要工具——重心坐标,及其在变形中的应用。
从贝塞尔曲线到代理变形
贝塞尔曲线的核心思想是:用少数控制顶点的线性组合(基函数加权)来定义复杂曲线上的无数点。这启发了“代理”(Proxy)变形的思想:用一个简单几何体(如包围笼)控制一个复杂形状。
重心坐标的定义
关键在于建立内部点与边界控制点之间的定量关系。
- 三角形重心坐标:对于三角形内任意点 P,存在唯一的重心坐标 (α, β, γ),使得 P = αA + βB + γC,且 α+β+γ=1。几何上,α 等于 P 对边小三角形面积与大三角形面积之比。
- 多边形重心坐标:不唯一,存在多种定义方式,如均值坐标、谐波坐标等,各有不同的几何性质和适用场景。
应用:图像变形
利用重心坐标,可以实现直观的变形操作:
- 用户编辑代理控制点(如一个包围多边形的顶点)。
- 对于原始图像/网格内的每个点,计算其相对于旧代理的重心坐标。
- 将这些相同的重心坐标应用于新代理的控制点,计算出该点的新位置。
- 将旧点的颜色或属性复制到新位置。
这种方法被广泛用于图像扭曲、曲面编辑等。


三角网格入门 🕸️

课程的后半部分将重点转向曲面,而三角网格是离散曲面的主要表示形式。


为什么是三角网格?
- 渲染需求:GPU硬件高度优化了对三角形的光栅化。
- 简化计算:将复杂的曲面求交等问题,转化为简单的*面(三角形)求交问题。
- 通用性:任何曲面都可以通过足够密的三角片来逼*(分片线性逼*)。
基本概念与数据结构


三角网格本质上是附着了3D坐标的图(Graph)。
- 顶点(Vertex):空间中的点。
- 边(Edge):连接两个顶点的线段。
- 面(Face):由三条边首尾相连构成的三角形。
- 度(Degree):一个顶点所连接的边的数量。
- 流形(Manifold):网格上任一点的局部邻域拓扑同胚于一个圆盘。非流形情况(如一条边被三个面共享)会给处理带来困难,通常需要预处理。
- 可定向性(Orientability):所有三角形的顶点绕序(如逆时针)保持一致。莫比乌斯环是不可定向的。
数据结构:半边结构


高效处理网格需要能快速查询顶点、边、面之间的邻接关系。半边结构(Half-edge)是一种经典表示:
- 将一条边拆分为两条方向相反的“半边”。
- 每条半边存储:起点、下一条半边、所属面、对偶半边。
- 通过这种链接,可以高效遍历一个点的所有邻边,或一个面的所有边。
实践:开始使用网格框架
许多几何处理框架(如课程助教提供的框架)已实现了半边等数据结构。建议初学者先利用框架读入一个OBJ格式的网格文件,并尝试进行简单操作,例如:
- 计算每个顶点的法向(邻接面法向的*均值)。
- 沿法向对顶点进行扰动,观察网格变化。
- 通过UI交互调整扰动参数。
这有助于熟悉网格数据的访问与操作流程,为后续更复杂的算法实现打下基础。


总结 📚
本节课我们一起学*了:
- 曲线光顺的概念、意义及其基于曲率的数学描述和优化方法。
- 连续曲线离散化的必要性、采样理论及离散几何计算方法。
- 重心坐标作为连接连续与离散的桥梁,及其在形状变形中的应用。
- 三角网格作为离散曲面表示的基础知识,包括其定义、流形性质及半边数据结构。


从下节课开始,我们将正式进入三角网格处理的核心内容,包括*滑、参数化、简化等算法。请同学们利用本周时间熟悉网格处理框架的基本操作。



GAMES102:几何建模与处理 - P8:离散微分几何与Utopia框架介绍 📐

在本节课中,我们将要学*三角网格上的离散微分几何量的计算,特别是曲率的计算。这些计算在后续的几何处理中非常重要。课程的后半部分将介绍我们用于完成作业的Utopia框架。
三角网格曲面概述 🔺
上一节我们介绍了光滑曲面的表达。在实际应用中,特别是随着扫描仪的广泛应用,我们更多地获得的是曲面上点的采样。这些采样点构成了三角网格。
三角网格可以有两种理解方式:
- 逼*论观点:网格点是从光滑曲面上采样得到的。将相邻点连接成三角形,就构成了一张分片线性的曲面,它是对原光滑曲面的一种逼*。每个三角形是一个*面,因此整个网格是C0连续的。
- 拓扑学观点:三角网格本质上是一个二维图(Graph)在三维空间中的“提升”(嵌入)。其顶点、边、面的连接关系(拓扑结构)不变,只是顶点的空间位置发生了变化。因此,它本质上是一个二维流形。
无论从哪个角度看,三角网格都是空间曲面的一种离散表达。
数据结构:图与半边结构 🗺️
要操作三角网格,必须掌握其数据结构。在数据结构中,图(Graph)是最复杂的结构之一,由顶点集合和边集合构成。
对于三角网格,我们需要存储顶点、边和面的信息。一个常见的格式是OBJ格式,它主要存储顶点(v)和面(f)的信息,边信息可以从面信息中推导出来。
在图形学中,有多种数据结构来表达网格。本节课介绍一种常用且通用的结构:半边结构(Half-edge Structure)。
半边结构的核心思想是将一条物理边拆分为两个有向的“半边”。每个半边存储以下信息:
vertex:该半边指向的终点顶点。pair:与该半边配对的另一个半边(代表同一条物理边的相反方向)。face:该半边所属的面。next:在同一面内,该半边的下一条半边。
顶点只需存储其关联的任意一条半边,面只需存储其关联的任意一条半边。
通过这种结构,可以高效地进行邻接关系查询:
- 由半边找两个顶点:起点 =
pair->vertex,终点 =vertex。 - 由半边找两个邻面:一个邻面 =
face,另一个邻面 =pair->face。 - 由面找所有半边:从
face->halfedge开始,不断访问next,直到回到起点。 - 由顶点找所有半边:从
vertex->halfedge开始,访问其pair->next,循环直到回到起点。
目前有许多优秀的开源几何处理库,如CGAL、libigl、OpenMesh等。本课程推荐使用助教开发的Utopia(无尽)框架,它小巧灵活,易于上手。当然,学员也可以使用自己熟悉的库来完成作业。
微分几何基础回顾 📈
上一节我们回顾了曲线曲面的基本概念,本节我们进一步了解曲面的微分性质。
对于参数曲面 S(u, v),其在点 p 处的偏导 S_u 和 S_v 张成了该点的切*面。切*面的法向,即曲面的法向,可通过叉积得到:
n = (S_u × S_v) / ||S_u × S_v||
过点 p 且包含法向 n 的*面与曲面相交,得到一条*面曲线。该曲线在点 p 处的曲率称为曲面在该切方向上的法曲率。


一个重要的结论是:在点 p 的所有切方向中,存在两个相互垂直的主方向,对应的法曲率分别达到最大值 κ1 和最小值 κ2,称为主曲率。其他任何方向上的法曲率 κ(θ) 都可以通过欧拉公式用主曲率表示:
κ(θ) = κ1 cos²θ + κ2 sin²θ



由主曲率可以定义两种常用的曲率:
- 高斯曲率 (Gaussian Curvature):K = κ1 * κ2
- *均曲率 (Mean Curvature):H = (κ1 + κ2) / 2
高斯曲率是内蕴几何量,在等距变换下保持不变。处处高斯曲率为零的曲面称为可展曲面,如*面、圆柱面、圆锥面和切线面。
*均曲率与曲面的面积变化密切相关。处处*均曲率为零的曲面称为极小曲面,如肥皂膜。








离散微分几何 🔲


我们面对的是分片线性的三角网格,它本身不可微。离散微分几何的目标是通过网格的离散数据,去估计其背后所逼*的光滑曲面的微分属性。




估计方法主要有两类:
- 连续逼*法:用光滑曲面(如二次曲面)去拟合网格顶点,然后用拟合曲面的属性来*似。
- 离散直接法:直接对网格的几何量进行定义和计算。

对于法向估计,一个简单有效的方法是:将顶点周围所有相邻面的法向,按面积加权*均,作为该顶点的法向。





对于曲率估计,可以通过离散化微分几何中的定理来推导公式。以下是两个常用的离散化公式(针对顶点 i ):





- 离散*均曲率向量:(1/(2A_i)) * Σ_{j∈N(i)} (cot α_ij + cot β_ij) (v_j - v_i)
N(i):顶点 i 的一环邻域顶点。α_ij,β_ij:边(i, j)所对的两个角。A_i:顶点 i 的Voronoi面积或混合面积。- 该向量的模长即为*均曲率绝对值,方向为法向。
- 离散高斯曲率:(2π - Σ θ_j) / A_i
θ_j:顶点 i 周围第 j 个三角形的内角。A_i:顶点 i 的Voronoi面积。



计算出的曲率值可以通过颜色映射(Color Map)可视化在网格上,直观展示曲面的弯曲情况。




极小曲面与离散*均曲率流 ⭕







*均曲率流是使曲面沿法向以*均曲率为速度移动的演化过程。对于封闭曲面,它会收缩为一个点。如果固定曲面的边界,则内部曲面会演化成极小曲面(*均曲率为零)。

我们可以利用离散*均曲率流的思想,通过迭代算法来生成极小曲面:
- 识别并固定网格的边界顶点。
- 对于每个内部顶点 v_i,计算其向一环邻域重心移动的向量(即离散*均曲率向量)。
- 将顶点位置更新为:v_i’ = v_i + λ * 移动向量,其中
λ是一个较小的正数(如0.1)。 - 重复步骤2-3,直到网格变化很小或达到指定迭代次数。



这个算法的核心代码逻辑如下:
for 每个内部顶点 v_i:
Vector3f delta(0, 0, 0);
float weight_sum = 0;
for 每个邻接顶点 v_j:
float cot_alpha = cot(角 alpha_ij);
float cot_beta = cot(角 beta_ij);
float weight = cot_alpha + cot_beta;
delta += weight * (v_j - v_i);
weight_sum += weight;
}
// 简单起见,这里使用均匀拉普拉斯*滑作为演示
// 实际应使用cot权重公式
Vector3f laplacian = delta / weight_sum;
v_i_new = v_i + lambda * laplacian;
}

Utopia框架使用指南 🛠️






以下是Utopia框架的核心架构与使用方法简介。





框架架构
框架主要分为四层:
- 基础层:提供数学库、反射系统等基础功能。
- 渲染层:基于DirectX 12的渲染管线,管理着色器、材质、网格等渲染资源。
- 逻辑层:采用ECS(实体-组件-系统)架构组织代码和数据。
- 实体:世界的对象,只是一个ID。
- 组件:附加在实体上的数据(如位置、网格、材质)。
- 系统:处理具有特定组件组合的实体的逻辑。
- 编辑器层:提供场景编辑、属性调试的可视化界面。



完成作业的关键步骤
作业目标是实现离散*均曲率流算法来生成极小曲面。
- 创建数据与系统:在ECS框架下,创建一个组件(如
DenoiseData)来存储网格数据,创建一个系统(如DenoiseSystem)来执行算法逻辑。 - 操作半边网格:框架提供了
HalfedgeMesh库。你需要:- 将渲染网格转换为半边网格结构。
- 遍历内部顶点,计算其一环邻域重心(使用cot权重公式)。
- 根据更新公式调整顶点位置。
- 将修改后的半边网格数据传回渲染网格。
- 可视化曲率(可选):计算网格的高斯曲率或*均曲率,并将曲率值映射为顶点颜色,在材质中显示。


在编辑器中,你可以将网格对象拖拽到你的数据组件中,并通过系统控制面板触发算法的执行和迭代。
课程总结 🎯

本节课我们一起学*了以下内容:
- 三角网格作为离散曲面的两种理解方式。
- 用于表示网格拓扑的半边数据结构及其基本操作。
- 微分几何的基础概念:切*面、法向、主曲率、高斯曲率与*均曲率。
- 如何将光滑曲面的微分性质离散化到三角网格上,进行法向和曲率的估计。
- 极小曲面的概念,以及利用离散*均曲率流迭代算法来生成极小曲面的原理与步骤。
- 用于完成作业的Utopia框架的基本架构、ECS设计模式和使用方法。






通过本课的学*,你将能够理解离散曲面的微分性质计算方法,并利用提供的框架动手实现一个简单的几何处理算法。




GAMES102:几何建模与处理 - P9:微分坐标 📐


在本节课中,我们将学*微分坐标(拉普拉斯坐标)的概念及其在几何处理中的应用。我们将从回顾作业六开始,逐步理解拉普拉斯算子的几何意义、离散形式,并探讨其在网格光滑化、参数化和编辑等任务中的核心作用。







作业六回顾与总结 📊

上一节我们介绍了网格的基本概念和数据结构。本节中,我们来看看作业六的完成情况,它为我们理解微分坐标打下了实践基础。

作业六要求对三角网格进行编程,通过迭代修改顶点坐标,使其朝着拉普拉斯方向移动,最终使网格变得光滑。
以下是作业完成情况的要点:
- 核心操作:将每个顶点向其一邻域(直接相连的顶点)的加权*均位置移动。
- 迭代过程:通过不断迭代上述操作,网格逐渐光滑,最终在边界固定的条件下逼*极小曲面。
- 观察现象:
- 顶点密集的区域(如兔子耳朵)收敛速度较慢。
- 使用均匀权重可能导致网格自交(面片翻转)。
- 使用与几何相关的权重(如余切权重)通常能获得更好的性质。
通过这个作业,我们直观地体验了拉普拉斯坐标作为“几何细节”或“尖锐度”度量的作用:移动顶点以减少其拉普拉斯坐标的长度,相当于磨*该处的几何特征。
离散曲面与图结构 🔗
在深入微分坐标之前,我们需要巩固对离散曲面的理解。离散三角网格本质上是一个图(Graph)在三维空间中的嵌入。
-
两种观点:
- 参数曲面观点:将网格视为从二维参数域到三维空间的映射。每个顶点拥有三维坐标。
- 图嵌入观点:将网格视为一个二维图,其顶点被赋予了三维坐标。其本质是二维流形。
-
数据结构的重要性:处理网格的核心是掌握图的数据结构(如半边结构)。数据结构的选择需要在存储空间和计算时间之间权衡。没有最好的结构,只有最适合当前应用的结构。
-
邻域概念:要研究一个顶点的局部性质(如曲率),我们需要考察其邻域。
- 一邻域:与该顶点直接通过边相连的所有顶点集合。
- k邻域:可通过不超过k条边到达该顶点的所有顶点集合。通常,一邻域足以*似“无穷小”邻域的性质。
拉普拉斯坐标(微分坐标)的定义与几何意义 🧮
理解了局部邻域后,我们就可以正式引入本节课的核心概念——拉普拉斯坐标,也称为微分坐标。
对于一个顶点 v_i,其拉普拉斯坐标 δ_i 定义为该顶点与其一邻域加权*均位置的差向量:
δ_i = v_i - Σ_{j∈N(i)} ω_{ij} v_j
其中,N(i) 是顶点 v_i 的一邻域,ω_{ij} 是权重,满足 Σ ω_{ij} = 1。
- 几何意义:这个向量 δ_i 的长度和方向刻画了该顶点偏离其局部邻域所构成*面的程度。长度越大,该点越“尖锐”;长度为零,则该点与其邻域共面,局部*坦。
- 与*均曲率的关系:在连续曲面的离散化中,可以证明,当网格足够精细时,拉普拉斯坐标 δ_i *似等于该点的*均曲率向量,即:δ_i ≈ H_i * n_i,其中 H_i 是*均曲率,n_i 是法向。
- 权重的选择:权重 ω_{ij} 的选择至关重要。从离散微分几何推导出的余切权重(Cotangent Weight)具有优良的几何性质,通常比均匀权重效果更好,能减少网格变形和自交。
拉普拉斯光滑与全局方法 ⚙️
上一节我们通过迭代局部操作实现了光滑化。本节中,我们来看看如何从全局角度一次性求解光滑结果。

-
拉普拉斯光滑(局部迭代法):即作业六使用的方法,公式为:
v_i‘ = v_i + λ * δ_i
通过不断迭代,逐渐减小 δ_i,实现光滑。λ 是步长参数。 -
全局方法(一次性求解):目标是找到所有顶点的新位置 V‘,使得每个内部顶点的拉普拉斯坐标为零(或按比例缩小),同时固定边界顶点。这可以归结为求解一个线性方程组:
L * V‘ = B
其中:- L 是拉普拉斯矩阵,一个大型稀疏矩阵,编码了网格的邻接关系和权重。
- V‘ 是未知的顶点坐标向量(x, y, z分量需分别求解)。
- B 是由边界条件构成的右侧向量。

- 优势对比:
- 局部迭代法:实现简单,但可能收敛慢,且易产生自交。
- 全局求解法:通过求解稀疏线性方程组,可直接得到结果,数值上更稳定。这是作业七的核心内容,需要学*使用数值计算库(如Eigen)来求解此类方程组。
参数化初步应用 🗺️
参数化是将三维网格映射到二维*面的过程,是纹理映射等应用的基础。利用我们刚学的全局拉普拉斯方法,可以实现一种简单的参数化。



- 基本思想:
- 将三维网格的边界顶点映射到二维*面上的一个凸多边形(如正方形)边界上。
- 对于内部顶点,在二维*面上求解拉普拉斯方程 L * U = B,其中 U 是内部顶点的二维坐标,B 由映射后的边界顶点决定。
- 解出的 U 即为每个顶点在二维参数*面上的坐标。

-
特点:
- 这种方法称为保角参数化的线性*似,计算简单高效。
- 可以证明,如果边界映射到凸多边形,则生成的参数化不会发生三角形翻转。
- 缺点是可能产生较大的面积扭曲,特别是当三维网格形状与*面区域差异大时。
-
应用:生成的二维坐标(UV坐标)即可用于纹理映射,将二维图像贴合到三维模型表面。






约束与网格编辑 ✏️



最后,我们探讨如何利用拉普拉斯坐标进行受约束的网格变形和编辑。


-
硬约束与软约束:在全局求解框架中,我们可以方便地加入约束。
- 硬约束:某些顶点必须固定在指定位置。这通过将这些顶点的坐标从变量变为已知量(移到方程右侧)来实现。
- 软约束:希望某些顶点尽量靠*但不必须到达指定位置。这通过将其作为最小二乘项加入目标函数来实现。
-
网格编辑的应用:例如,用户拖动模型上的一个点,希望模型其他部分随之自然变形。
- 核心思想:在变形过程中,尽量保持每个顶点的拉普拉斯坐标(即局部细节)不变。但为了适应旋转,需要对拉普拉斯坐标的方向进行相应的旋转变换。
- 数学形式:这通常转化为一个带约束的最小二乘优化问题,目标是最小化拉普拉斯坐标的变化,约束条件是用户指定的顶点位移。
总结与作业预告 📚
本节课中,我们一起学*了微分坐标(拉普拉斯坐标)这一核心概念。
- 核心概念:拉普拉斯坐标 δ_i = v_i - Σ ω_{ij} v_j 是顶点与其局部邻域*均的差,是局部几何细节的度量。
- 关键应用:
- 网格光滑:通过减小 δ_i 实现。
- 参数化:在二维*面上求解 L * U = B,将网格展开。
- 网格编辑:保持 δ_i 的变换,实现细节保持的变形。
- 方法演进:从局部迭代的直观方法,到构建全局拉普拉斯矩阵并求解线性方程组的稳健方法。


作业七预告:请使用全局求解方法(构建拉普拉斯矩阵并调用Eigen等库求解方程组)重新实现网格光滑化,并进一步将其扩展,实现简单的网格参数化。重点在于掌握稀疏矩阵的构建与线性方程组的求解。

GAMES103-基于物理的计算机动画入门 - P1:Lecture 01 课程介绍 🎬
在本节课中,我们将要学*这门课程的整体框架、学*目标、课程机制以及基于物理的计算机动画的基本概念。我们将从图形学的基础知识开始,逐步深入到物理模拟的核心领域。
课程概述与机制 📋
本课程名为“基于物理的计算机动画入门”。课程主要讨论如何将物理模拟技术应用于计算机动画中,并介绍相关技术的基本原理和算法。课程包含相应的编程作业以巩固所学知识。
我是课程讲师王华明。课程相关问题可以发送至课程邮箱,会有助教或老师解答。课程资料、回放、PPT及作业将发布在GAMES论坛上。
课程时间为每周一16:00至17:30,时长约一个半小时,具体可能根据内容调整。课程后半段会预留时间进行答疑。
从第三周开始,课程将转为线下授课与线上转播结合的形式。线下地点初步定在林地科技会议室(浙江大学紫金港校区附*),方便杭州及周边地区的同学参与。
课程将持续12周,大约到春节前两周结束。作业批改和课程辅助将由助教团队负责。
预备知识要求 📚
为了降低学*门槛,本课程仅要求学员具备基础的数学知识和编程能力。


以下是所需的核心知识:
- 数学基础:需要掌握线性代数(矢量、矩阵、线性系统、特征值分解等概念)和微积分(求导、积分、链式法则、梯度、泰勒展开等基本概念和计算)。
- 编程基础:需要具备C、C++、C#或Java等语言的编程能力。课程作业将使用Unity引擎,其脚本语言为C#。有相关语言经验即可快速上手。
- 图形学基础:了解简单的图形学概念(如变换、旋转)和渲染基本原理即可,无需深入复杂的图形学知识。
课程会涵盖数值算法、偏微分方程、有限元分析、流体力学等进阶内容,但即使没有这些背景知识,也可以通过课程学*掌握。

课程工具与环境 🛠️

课程作业将使用Unity引擎完成。Unity对学生和个人用户免费,对硬件要求较低,大部分计算基于CPU。
以下是关于工具的重点说明:
- Unity版本:对版本要求不严格,较新的版本均可。支持Windows和macOS系统。
- 学*重点:使用Unity主要是将其作为学*和实践的工具,课程核心是学*物理模拟算法本身,而非Unity引擎的使用。大部分作业将替代Unity原有的物理引擎,通过编写脚本来实现模拟。
- 资源获取:Unity软件可从官网下载,其官方论坛可以解答通用的引擎使用问题。
课程大纲与作业 📅
课程总计12周,涵盖物理模拟的多个核心方向,内容相对独立,便于学*。
以下是课程的大致安排:
- 第1周:课程简介(本周)。
- 第2周:数学基础回顾,结合图形学实例讲解。
- 第3-4周:刚体动力学与刚体碰撞处理。
- 第5-7周:布料模拟,引入PBD、Projective Dynamics等算法。
- 第8-9周:软体动力学与有限元方法。
- 第10-12周:流体模拟,包括表面波、网格法和粒子法。
课程包含四次编程作业,分别对应刚体、布料、软体和流体四个方向。每次作业包含基础任务和可选的高级任务。完整完成所有作业的同学将获得课程纪念品。本课程没有考试。
课程没有固定教材,但每节课会提供相关的论文或文献作为选读材料,建议有精力的同学课后阅读以加深理解。
什么是计算机图形学? 🖥️
计算机图形学简而言之,就是研究如何构建三维虚拟世界,并将其以二维图像形式呈现出来的学科。它与计算机视觉方向相反,后者是从二维图像理解三维世界。
图形学主要包含三个方向:
- 几何:研究如何构造和表达三维虚拟世界。
- 渲染:研究如何将三维世界转化为二维图像并显示出来。
- 动画:研究如何让三维世界中的物体运动起来。



一个理想的实时图形学管线是:几何处理(可离线) -> 动画模拟(实时) -> 渲染输出(实时) -> 显示。帧率是衡量实时性的关键指标,例如电影通常为24帧/秒,而交互性强的游戏可能需要60帧/秒或更高。

图形学的应用领域 🌐
计算机图形学技术已广泛应用于多个行业:
- 娱乐产业:如电子游戏、电影特效、社交媒体滤镜和虚拟数字人等。
- 设计与工程:如计算机辅助设计、建筑设计、时尚设计等。
- 电子商务与智能制造:如虚拟试衣、数字产品展示,连接设计与生产。
- 前沿领域:如元宇宙、VR/AR/MR,构建沉浸式虚拟世界的基石。




什么是基于物理的动画? ⚙️





动画的本质是在离散的时间点上更新物体的状态。两个时间点之间的间隔称为时间步长 Δt。物理模拟的核心问题就是在每个时间步长内,如何根据物理定律更新物体的状态(如位置、速度、形状等)。





基于物理的动画主要模拟四大类物质:
- 刚体:假设物体无形变。
- 布料与头发:属于细薄物体。
- 软体/弹性体:物体可发生弹性形变。
- 流体:包括液体和气体。










相应地,有三种主要的模拟表达方式:
- 网格法:用三角形或四面体网格表示物体表面或体积。适用于形态相对固定的物体,如刚体、布料、弹性体。
- 粒子法:用一堆离散的点云表示物体。优点是无须处理网格拓扑,适用于流体、碎裂等效果。
- 网格法:将空间划分为规则的小格子,在每个格子存储物理量。常用于流体、烟雾的模拟,内存消耗较大。

此外,还有混合方法,如物质点法,它结合了粒子和网格的优点,常用于模拟雪、沙等物质。不同物质间的耦合交互也是一个重要的研究课题。



本课程涵盖内容 🎯



结合课程安排与讲师专长,本课程将重点讲解以下内容:
- 刚体:刚体动力学和碰撞处理,不涉及破碎模拟。
- 布料:主要讲解布料模拟,头发模拟暂不涉及。
- 软体:弹性体模拟、有限元方法及超弹性模型。
- 流体:涵盖表面波、基于网格的不可压缩流体模拟以及基于粒子的流体模拟。
- 通用技术:专门讲解碰撞检测与处理,以及基于约束的动力学方法。
总结与学*建议 💡
本节课我们一起学*了课程的基本信息、预备要求、工具使用以及基于物理的动画的核心概念。
成功的学*需要做到以下几点:
- 做好准备:掌握必要的数学和编程基础,提前熟悉Unity的基本操作。
- 积极参与:按时参加课程(或观看回放),认真完成编程作业。
- 主动拓展:利用课余时间阅读推荐的文献资料,深入理解算法原理。
- 多读多写多想:这是掌握任何技术的关键。通过阅读积累知识,通过编程实践深化理解,通过思考融会贯通。

希望本课程能帮助大家对物理模拟算法和计算机动画建立一个扎实的基础。未来如果大家兴趣浓厚,我们也可能开设相关的高级课程。


我们下周将从必要的数学基础开始,并结合图形学中的具体问题展开讲解。


GAMES103-基于物理的计算机动画入门 - P10:Lecture 10 表面波(实验四) 🌊
在本节课中,我们将学*如何模拟水面上的波浪效果。我们将介绍一种名为“浅水波”的简化模型,它非常适合在游戏等实时应用中模拟水面波动。课程内容将涵盖从基本概念到具体实现的完整流程,并最终与我们的实验作业相结合。

概述:两种模拟方法
在深入浅水波模型之前,我们需要了解模拟流体的两种基本方法:拉格朗日法和欧拉法。
上一节我们介绍了弹性体和刚体的模拟,它们都属于拉格朗日法。本节中我们来看看这两种方法的区别。
- 拉格朗日法:将物理属性(如速度、密度)定义在随物质一起运动的“质点”上。例如,模拟一群水分子,每个分子都有自己的属性,并随水流移动。
- 欧拉法:将空间划分为固定的网格(格子),物理属性定义在这些固定的空间位置上。当流体流过时,我们需要更新每个格子中的属性值。
我们今天要学*的浅水波模型,就是基于欧拉法的一种应用。
浅水波模型与高度场 🌊
我们的目标是模拟一个水面。一个直观的方法是使用高度场来描述它。
我们可以把水面想象成一个二维网格,每个网格点都有一个高度值 h,代表该处水面的海拔。通过更新整个网格的高度值,就能模拟出波浪传播的效果。这种描述被称为2.5维高度场。
除了高度,我们还需要知道水流的运动情况,因此引入速度场 u。它定义了在每个水*位置 x 上,水流穿过该垂直截面的水*速度。
现在我们有了描述状态的高度场 h(x) 和描述运动的速度场 u(x)。接下来,我们需要找到更新它们的物理规则。
从物理方程到更新公式
根据流体力学的基本原理,我们可以推导出高度和速度随时间变化的方程。经过一系列假设和简化(特别是假设波浪很“浅”,即高度变化*缓),我们可以得到一个核心方程,它只与高度场和压强有关:
∂²h/∂t² = (h / ρ) * (∂²p/∂x²)
其中:
h是高度。ρ是水的密度(常数)。p是压强。
这个方程就是浅水波方程。它告诉我们,水面高度的二阶时间导数(即加速度)与压强的二阶空间导数成正比。
在只考虑重力的情况下,水下压强 p 可以简化为:p = ρ g h。其中 g 是重力加速度。代入上式,并合并常数,我们可以得到一个更简洁的、只关于高度 h 的方程:
∂²h/∂t² = α * (∂²h/∂x²), 其中 α = g h(通常*似为常数)。
离散化与数值求解 🔢
计算机无法直接处理连续方程,我们需要将其离散化。我们将空间划分为许多小格子,时间也划分为小步长 Δt。
首先,我们需要用离散的格子值来*似方程中的导数。这里我们使用中心差分法。
对于时间的二阶导数,我们有*似公式:
(hᵢⁿ⁺¹ - 2hᵢⁿ + hᵢⁿ⁻¹) / Δt² ≈ ∂²h/∂t²
对于空间的二阶导数,我们有*似公式:
(hᵢ₊₁ⁿ - 2hᵢⁿ + hᵢ₋₁ⁿ) / Δx² ≈ ∂²h/∂x²
将这两个*似代入简化后的浅水波方程,并进行整理,我们就可以得到每个格子在下一时刻的高度更新公式:
hᵢⁿ⁺¹ = 2hᵢⁿ - hᵢⁿ⁻¹ + α * (Δt²/Δx²) * (hᵢ₊₁ⁿ - 2hᵢⁿ + hᵢ₋₁ⁿ)
这个公式非常直观:下一个时刻的高度,由当前时刻的高度、前一时刻的高度以及左右邻居的高度共同决定。
保持体积守恒与添加阻尼
直接使用上述公式模拟,可能会导致水的总体积(所有格子高度之和)发生变化,这不符合物理规律。为了保证体积守恒,我们需要对公式进行修正。
以下是两种常用方法:
- 修改耦合系数:将更新公式中与邻居交互的系数,从
hᵢ改为(hᵢ + h邻居)/2。这样可以保证从格子A流到格子B的水量,等于从格子B流到格子A的水量。 - 常数化处理(作业采用):直接将公式中的
α视为常数。这样在求和时,邻居项会相互抵消,从而自动保持体积守恒。
此外,真实的水有粘滞性,波浪会逐渐衰减。我们可以在更新公式中引入一个阻尼系数 β(0 < β ≤ 1):
hᵢⁿ⁺¹ = 2hᵢⁿ - hᵢⁿ⁻¹ + β * [ α * (Δt²/Δx²) * (hᵢ₊₁ⁿ - 2hᵢⁿ + hᵢ₋₁ⁿ) ]
当 β = 1 时,无阻尼;β 越小,阻尼越大,波浪衰减越快。
处理边界条件 🧱
水面不可能无限大,我们需要定义边界处的行为。主要有两种边界条件:
- 狄利克雷边界条件:固定边界处的高度为一个常数值(如
H)。这用于模拟开放水域(如海洋),边界外是静止的。- 在代码中,将边界外虚拟格子的高度固定为
H即可。
- 在代码中,将边界外虚拟格子的高度固定为
- 诺伊曼边界条件:固定边界处高度的导数为零(即边界两侧高度相等)。这用于模拟无法穿越的墙壁(如水池边)。
- 在代码中,当计算边界格子时,忽略其越界一侧的邻居(不进行水流交换)即可实现。
以下是诺伊曼边界条件的简化代码逻辑示意:
// 假设 h_new 已初始化为 2*h_curr - h_old
for each grid i {
if (i-1 是有效格子) {
h_new[i] += beta * alpha * (h_curr[i-1] - h_curr[i]);
}
if (i+1 是有效格子) {
h_new[i] += beta * alpha * (h_curr[i+1] - h_curr[i]);
}
}
// 更新状态:h_old = h_curr; h_curr = h_new;
流体与刚体的耦合 ⚙️
在作业中,我们需要模拟方块在水面移动并激起波浪的效果。这涉及到流体-刚体耦合。耦合是双向的:
- 刚体对流体:方块排开水体,从而扰动水面。
- 流体对刚体:被排开的水体产生浮力,作用于方块。
我们重点解决第一个问题:如何模拟方块排水?
一个巧妙的方法是引入虚拟高度 v。假设在方块占据的格子上,我们不是直接移除水,而是临时给这些格子一个额外的虚拟高度。然后,通过求解一个线性方程组,计算出需要多少虚拟高度 v,才能使得在下一个模拟步长后,这些格子达到我们预期的“被排空”后的目标高度。
求解这个方程组可以使用共轭梯度法等数值方法。在作业框架中,已经提供了相关的求解器(PCG)。你需要做的是:
- 设置好需要求解的格子(
mask)。 - 根据方块的位置和目标排水量,计算方程组的右侧项(
b)。 - 调用求解器得到虚拟高度
v。 - 将
v乘以一个衰减系数(用于稳定模拟,避免因拖动过快产生过大波浪),然后加入到高度场的更新计算中。
对于第二个问题(浮力),可以根据阿基米德原理计算:每个被方块覆盖的格子所产生的浮力,等于其排开的水的重量(F = ρ * g * 被排开体积)。将所有格子的浮力向量相加,并计算它们对刚体质心产生的力矩,即可得到作用在方块上的总浮力和扭矩,进而影响其刚体运动。
总结
本节课中,我们一起学*了基于物理的水面波浪模拟。
我们从拉格朗日与欧拉两种模拟思路出发,引入了用于描述水面的高度场概念。通过物理推导和大量简化,得到了核心的浅水波方程。为了在计算机中求解,我们利用中心差分法对方程进行离散化,得到了直观的格子更新公式。为了保证模拟的合理性,我们探讨了体积守恒的方法、添加了阻尼效果,并介绍了两种边界条件的处理方式。
最后,我们深入探讨了本次作业的核心:流体-刚体耦合。通过引入虚拟高度并求解线性方程组,来模拟刚体(方块)排水并激发波浪的过程,同时也简要说明了如何计算水流对刚体产生的浮力。

这套基于浅水波模型的模拟方法,效率高、实现相对简单,是游戏中实现实时水面效果的常用技术。希望大家通过实验,能更深入地理解和掌握这些概念。

GAMES103-基于物理的计算机动画入门 - P11:Lecture 11 不可压缩流体动力学与欧拉流体 🧊💨

在本节课中,我们将要学*基于网格(欧拉方法)的流体模拟技术。我们将首先介绍网格的表达方式,然后利用有限差分法计算微分算子,接着讨论不可压缩粘性流体的纳维-斯托克斯方程及其数值解法,最后探讨如何描述流体状态(如烟和水)。
1. 网格表达与有限差分法 🕸️
上一节我们介绍了基于粒子的模拟方法,本节中我们来看看基于固定网格的欧拉方法。这种方法将空间划分为规则的网格,并将物理量(如密度、速度)定义在网格上。
1.1 规则网格与物理场
规则网格(Regular Grid)将空间均匀划分为正方形(二维)或立方体(三维)的格子。每个格子中心可以存储一个物理量,例如标量(密度、压强、温度)或矢量(速度)。整个网格构成了一个物理场(标量场或矢量场)。
这种规则结构带来了一个巨大优势:计算导数变得非常容易。
1.2 利用中心差分法计算导数
有限差分法是数值计算导数的核心工具。对于定义在网格中心的值,我们可以使用中心差分法。
- 一阶导数:对于函数
f在i位置(假设网格间距为h)的一阶导数,公式为:
(f(i+1) - f(i-1)) / (2h) - 二阶导数:二阶导数可以通过连续两次应用一阶导数公式得到,最终形式为:
(f(i-1) + f(i+1) - 2*f(i)) / (h^2)
在二维网格中,一个点 (i, j) 的二阶导数计算如下:
- x方向二阶导数:
(f(i-1, j) + f(i+1, j) - 2*f(i, j)) / (h^2) - y方向二阶导数:
(f(i, j-1) + f(i, j+1) - 2*f(i, j)) / (h^2)
1.3 拉普拉斯算子
拉普拉斯算子是梯度的散度,在模拟中极为重要。在二维网格上,离散化的拉普拉斯算子 ∇²f 计算公式为:
∇²f(i, j) = (f(i-1, j) + f(i+1, j) + f(i, j-1) + f(i, j+1) - 4*f(i, j)) / (h^2)
直观理解是:一个点的拉普拉斯值等于其所有邻居值的和减去四倍自身值,再除以网格间距的*方。
1.4 边界条件
与求解其他偏微分方程一样,流体模拟也需要定义边界条件,主要有两种:
- 狄利克雷边界条件:指定边界上的函数值为已知常数。
- 诺伊曼边界条件:指定边界上函数导数的值,例如规定边界值与内部相邻值相等。
需要注意的是,在求解某些方程(如拉普拉斯方程 ∇²f = 0)时,不能全部使用诺伊曼边界条件,否则会导致系统矩阵奇异,解不唯一。
2. 交错网格与散度 🌀
上一节我们介绍了将物理量定义在格子中心的常规网格。但在流体模拟中,对速度场采用一种特殊的网格——交错网格(Staggered Grid)会更加方便。
2.1 交错网格的定义
在交错网格中,速度矢量并不定义在格子中心,而是定义在格子的面上:
- x方向速度
u:定义在垂直网格面的中心。 - y方向速度
v:定义在水*网格面的中心。
这样定义非常直观:速度值直接代表了流体通过该网格面的流量。
2.2 散度与不可压缩条件
对于一个格子,单位时间内流体的净流出量可以通过其四个面上的速度计算:
净流出量 = u(i+1, j) + v(i, j+1) - u(i, j) - v(i, j)
在物理学中,不可压缩流体意味着每个格子内流体的净流入/流出量为零,即体积不变。这等价于速度场的散度为零:
∇ · U = ∂u/∂x + ∂v/∂y = 0
在交错网格上,这个条件的离散形式恰好就是上面净流出量为零的公式。因此,使用交错网格能非常自然且精确地实施不可压缩约束。
2.3 双线性插值
由于物理量定义在离散的网格点上,当需要获取任意位置(非网格点)的值时,就需要进行插值。对于定义在格子中心的量(如压强),使用标准的双线性插值。对于定义在面上的速度,插值前需要对坐标进行0.5个网格的偏移,以对齐到正确的存储位置。
3. 纳维-斯托克斯方程与解法 ⚙️
本节中我们来看看描述流体运动的核心方程——纳维-斯托克斯方程,以及如何数值求解它。
3.1 方程概述
对于不可压缩粘性流体,纳维-斯托克斯方程描述了速度场的演化,包含两个部分:
- 动量方程:描述速度如何受外力、粘性、对流和压强梯度影响。
- 不可压缩条件:
∇ · U = 0,确保流体体积不变。
图形学中通常求解的是不可压缩形式。
3.2 分裂法求解策略
直接求解完整的纳维-斯托克斯方程很复杂。我们采用分裂法,将一个时间步内的更新分解为几个连续的、更简单的步骤,逐步更新速度场 U:
- 外力项:添加重力等外力。
U1 = U0 + Δt * g - 对流项:处理流体自身流动导致的速度迁移。
- 粘性项:处理流体的扩散(粘性)效应。
- 投影步:调整速度场,使其满足不可压缩条件。
接下来,我们详细看看每一步如何实现。
3.2.1 对流项:半拉格朗日法
对流项是欧拉方法中的难点。由于网格固定,而流体在流动,我们需要计算流体微团从上一时刻移动到当前位置所携带的速度。半拉格朗日法是解决此问题的稳定方案。
其核心思想是反向追踪:要得到当前网格点 X 在新时刻的速度,就沿着当前速度场反向追溯 Δt 时间,找到上一时刻该流体微团的位置 X_prev,然后将 X_prev 处的速度(通过插值得到)作为 X 的新速度。
U_new(X) = U_old(X_prev),其中 X_prev = X - Δt * U_old(X)
这种方法稳定,但可能引入数值耗散(模糊)。
3.2.2 粘性项:扩散求解
粘性项的形式是 ν∇²U,其中 ν 是粘性系数。这本质上是一个扩散过程,可以用显式方法求解:
U_new = U_old + Δt * ν * ∇²U_old
其中拉普拉斯算子 ∇² 的计算方法已在第1.3节介绍。若 Δt 较大,此步可能不稳定,可采用小步长多次迭代或隐式方法提高稳定性。
3.2.3 投影步:压强求解与速度修正
这是确保不可压缩条件的关键一步。我们引入压强 p,并通过压强梯度来修正速度:
U_final = U_before_projection - Δt * ∇p
修正后的速度必须满足散度为零 ∇ · U_final = 0。将上式代入,可得到一个关于压强 p 的泊松方程:
∇²p = (∇ · U_before_projection) / Δt
求解这个泊松方程得到压强场 p,再用 p 去修正速度,最终得到既满足动量守恒又不可压缩的速度场。这个步骤被称为投影,因为它将速度场投影到散度为零的子空间。
4. 流体状态模拟:烟与水的表达 🌫️💧
上一节我们解决了速度场的更新问题,本节中我们来看看如何更新流体的可见状态,例如烟的密度和水的形状。
4.1 烟的模拟
烟的模拟相对直接。除了速度场,我们还需要模拟一个标量场,例如密度 ρ 或温度 T。这些被动标量场的更新也遵循类似的物理过程:
- 对流:使用半拉格朗日法,让密度随速度场移动。
- 扩散:密度自身也会扩散。
- 源:在烟源位置添加密度值。
烟的渲染通常使用体渲染技术,将密度场转换为视觉效果。
4.2 水的模拟与界面捕捉
水的模拟更复杂,因为水有明确的自由表面(界面)。我们需要额外的方法来刻画这个界面随速度场的演化。两种主流方法是:
- 体积分数法:每个网格存储一个值,表示该网格被水占据的体积百分比。这种方法简单,但界面模糊、不精确。
- 水*集法:每个网格存储一个有符号距离函数,其绝对值表示到水面的最短距离,符号表示内外(正为空气,负为水)。界面就是零等值面。水*集函数本身也通过对流方程更新。
水*集法能提供更清晰的界面,但面临一个挑战:体积损失。在数值模拟中,水的总体积可能无法严格保持,导致水面下降或上升。需要额外的算法(如粒子水*集法)来进行体积修正。
将模拟得到的水*集场(或密度场)转换为可用于渲染的几何网格,通常使用移动立方体算法。
总结 📚
本节课中我们一起学*了基于欧拉网格的流体模拟方法。
- 网格与微分:我们使用规则网格离散空间,并利用中心差分法方便地计算导数、拉普拉斯算子等。
- 交错网格:将速度定义在网格面上的交错网格,能自然地表达流体的通量,并简化不可压缩条件的实施。
- 纳维-斯托克斯方程:我们学*了描述不可压缩粘性流体运动的核心方程组。
- 分裂解法:通过将方程分裂为外力、对流、粘性、投影四个步骤,逐步更新速度场。其中,半拉格朗日法处理对流,投影步通过求解压强泊松方程来保证流体不可压缩。
- 状态模拟:烟的模拟通过更新密度场实现;水的模拟则需要使用水*集法等技术来捕捉动态变化的界面。
这种方法在图形学中已成为模拟水和气体的标准方案,被广泛应用于电影特效和游戏开发中。下节课,我们将探讨基于拉格朗日粒子法的流体模拟。



GAMES103-基于物理的计算机动画入门 - P12:Lecture 12 SPH和基于位置的流体 🧪

概述
在本节课中,我们将学*基于粒子的流体模拟方法,特别是光滑粒子流体动力学(SPH)的基本原理。我们将了解如何用粒子表示流体,并通过光滑插值模型来计算密度、压强和粘滞力,从而模拟流体的运动。
课程背景与安排
今天是本课程的最后一节课。课程作业的最终评分、颁奖和奖品发放将安排在年后进行。如果同学们想在过年期间补交作业,仍然可以提交,但可能无法参与评奖。完成所有作业的同学将获得一份电子证书作为完成课程的证明。
模拟领域涉及面非常广,包括流体、弹性体、碰撞处理等。很少有研究组能覆盖所有方向,大家通常专注于某一特定领域。本节课将讨论流体模拟的最后一部分内容,即基于粒子的方法。
粒子模拟方法概述
上两节课我们讨论了欧拉方法,它将空间划分为固定网格,通过改变网格内的物理量来模拟动画。今天我们将讨论拉格朗日视角的模拟方法,即使用运动的粒子来进行物理模拟。
基于粒子的流体模拟有多种变形:
- SPH(光滑粒子流体动力学):最传统的方法。
- PBF(基于位置的流体):使用基于位置的约束进行模拟。
- Peridynamics:将SPH与弹性体模拟结合,便于模拟破碎效果。
- MPM(物质点法) 或 PIC(粒子网格法):混合粒子与网格的方法。
不同的方法适用于不同的效果,例如MPM常用于模拟血或沙子,Peridynamics用于形变和破碎。本节课我们将从最传统的SPH方法入手。
光滑粒子(SPH)模型
核心思路
我们用大量粒子来表达流体,每个粒子附带着物理变量(如位置、速度、质量)。当粒子运动时,这些变量也随之运动,这是典型的拉格朗日视角。
在图形学中,由于GPU通常无法直接渲染粒子,我们需要将粒子转化为三角网格进行离线渲染,或在游戏中用带纹理的球体或方块来*似表示。
从简单*均到光滑插值
假设场景中有许多粒子,每个粒子都有一个物理量 \( A \)(标量或矢量)。我们想在空间中某个位置(例如图中红点)估算该物理量的值。这本质上是一个插值问题。
1. 简单*均模型
最简单的想法是在一定半径内,对所有粒子的值求*均:
\[ A_{i} = \frac{1}{n} \sum_{j} A_{j} \]
其中 \( n \) 是半径内的粒子数。但这种方法没有考虑粒子的空间分布。
2. 考虑体积权重的模型
我们为每个粒子引入一个体积 \( V \) 作为权重:
\[ A_{i} = \sum_{j} A_{j} V_{j} \]
这里假设了单位球的总体积为1。这个模型考虑了粒子所占的空间,但还不够“光滑”。
3. 引入光滑核函数
我们希望插值结果能随位置连续、*滑地变化,而不是在粒子进出插值半径时剧烈跳变。因此,我们引入一个与距离相关的权重函数——光滑核函数 \( W \)。
\[ A_{i} = \sum_{j} A_{j} V_{j} W(||\mathbf{x}_i - \mathbf{x}_j||, h) \]
其中 \( h \) 是光滑长度。核函数满足:距离*则权重大,距离远则权重小。
密度、体积与最终插值公式
然而,粒子的体积 \( V \) 并非常数,它会随着粒子的分布疏密而变化。我们需要动态计算它。
我们假设每个粒子有恒定的质量 \( m \)。这里定义的密度 \( \rho \) 是粒子分布的密度,描述粒子在空间中的拥挤程度,而非水的物理密度。
根据光滑插值公式,粒子 \( i \) 的密度可以通过其邻居估算:
\[ \rho_i = \sum_{j} m_j W(||\mathbf{x}_i - \mathbf{x}_j||, h) \]
直观理解:周围邻居的质量和越大,该处就越拥挤,密度越高。
得到密度后,我们可以计算体积:
\[ V_i = \frac{m_i}{\rho_i} \]
将体积公式代回最初的插值公式,就得到了SPH的最终插值形式:
\[ A_{i} = \sum_{j} A_{j} \frac{m_j}{\rho_j} W(||\mathbf{x}_i - \mathbf{x}_j||, h) \]
在实际计算中,通常分两步:先估算每个粒子的密度和体积,再利用此公式插值其他物理量。
微分算子的计算
使用光滑插值模型的一个巨大优势是,可以方便地计算物理量的梯度(∇)和拉普拉斯算子(∇²)。
在计算梯度时,我们*似认为邻居粒子的物理量 \( A_j \) 和体积 \( V_j \) 是常数(因为粒子 \( i \) 的微小运动对邻居影响较小)。这样,梯度算子就只作用于已知的核函数 \( W \):
\[ \nabla A_{i} \approx \sum_{j} A_{j} V_{j} \nabla W(||\mathbf{x}_i - \mathbf{x}_j||, h) \]
由于核函数是解析定义的,其梯度有现成公式。拉普拉斯算子的计算同理:
\[ \nabla^2 A_{i} \approx \sum_{j} A_{j} V_{j} \nabla^2 W(||\mathbf{x}_i - \mathbf{x}_j||, h) \]
核函数示例

常见的核函数使用多项式形式,因其计算和求导简单。例如下面这个函数:
首先计算归一化距离 \( q = \frac{||\mathbf{x}_i - \mathbf{x}_j||}{h} \),然后核函数定义为:
W(q, h) = \frac{315}{64\pi h^3}
\begin{cases}
(1 - q^2)^3 & 0 \leq q \leq 1 \\
0 & q > 1
\end{cases}
其梯度(一阶导)和拉普拉斯算子(二阶导)也有对应的多项式表达式。在实际代码实现中,直接套用这些公式即可。

SPH流体模拟算法
上一节我们介绍了SPH的核心插值模型,本节我们来看看如何将其应用于流体模拟。其思路与之前讲过的粒子模拟算法相似,主要考虑三种力:重力、压强力和粘滞力。
1. 重力
重力计算简单,直接作为外力施加给每个粒子:
\[ \mathbf{F}_i^{gravity} = m_i \mathbf{g} \]
2. 压强力
压强力源于压力的不*衡(压力差),而非压力本身。数学上,压强力与压强的负梯度相关:
\[ \mathbf{F}_i^{pressure} = -V_i \nabla p \]
我们假设压强场也是光滑的,利用SPH梯度公式计算:
\[ \mathbf{F}_i^{pressure} = -\sum_{j} V_j p_j \nabla W(||\mathbf{x}_i - \mathbf{x}_j||, h) \]
其中,粒子压强 \( p \) 通常由密度通过经验公式计算,例如:
\[ p_i = k ( \rho_i - \rho_0 )^7 \]
这里 \( k \) 和 \( \rho_0 \) 是常数。
3. 粘滞力
粘滞力的效果是使流体粒子的速度趋于一致,数学上可用速度场的拉普拉斯算子来描述:
\[ \mathbf{F}_i^{viscosity} = \mu m_i \nabla^2 \mathbf{v}_i \]
其中 \( \mu \) 是粘滞系数。利用SPH拉普拉斯公式计算:
\[ \mathbf{F}_i^{viscosity} = \mu \sum_{j} m_j (\mathbf{v}_j - \mathbf{v}_i) \nabla^2 W(||\mathbf{x}_i - \mathbf{x}_j||, h) \]
注意速度是矢量,计算时需对每个分量(x, y, z)分别进行。
模拟流程
以下是SPH流体模拟的基本算法步骤:
- 寻找邻居:对于每个粒子,找到其周围一定半径内的所有邻居粒子。
- 计算密度:根据邻居粒子质量,用SPH公式估算每个粒子的密度 \( \rho \)。
- 计算压强:利用密度,通过经验公式计算每个粒子的压强 \( p \)。
- 计算合力:
- 加上重力 \( \mathbf{F}^{gravity} \)。
- 加上利用SPH梯度公式计算的压强力 \( \mathbf{F}^{pressure} \)。
- 加上利用SPH拉普拉斯公式计算的粘滞力 \( \mathbf{F}^{viscosity} \)。
- 更新状态:根据合力 \( \mathbf{F}_{total} \) 更新粒子速度,再根据速度更新粒子位置。
挑战与扩展
性能挑战与优化
模拟逼真的流体需要百万甚至千万级的粒子。计算每个粒子的邻居是主要性能瓶颈,穷举法不可行。常用优化方法包括:
- 空间划分:将空间划分为均匀网格(Spatial Hashing),每个粒子存入对应网格,快速定位邻居。
- 层次结构:对于非均匀分布的粒子,可使用八叉树(Octree)等数据结构。
- 自适应粒子:在需要高细节的区域(如水面、泡沫)使用更小、更密的粒子,在内部区域使用更大、更疏的粒子以提升效率。
表面重建
粒子系统渲染前需要将点云转换为三角网格表面(三维重建)。简单方法是将每个粒子视为球体,计算其符号距离函数(SDF),然后进行等值面提取(如Marching Cubes)。通常还需对生成的网格进行*滑处理以消除噪点,同时保留细节。

研究前沿
SPH及其变体仍在不断发展,研究热点包括:
- 效率与实时性:如何在资源受限(如游戏)环境下进行高效模拟。
- 不可压缩性:精确保持流体体积不变是一个难点,MPM等方法试图更好地解决此问题。
- 边界处理:精确处理流体与固体、空气的交互边界。
- AI驱动模拟:利用机器学*加速或控制模拟过程,但目前尚难以完全替代物理引擎。
总结与展望
本节课我们一起学*了基于粒子的流体模拟方法SPH。我们从光滑插值模型出发,讲解了如何通过粒子估算密度、体积及其他物理量,并利用核函数方便地计算梯度与拉普拉斯算子,从而求解流体的压强力和粘滞力,最终实现流体运动的模拟。
图形学中的物理模拟,其核心价值在于在效果逼真与计算效率之间取得*衡,以满足实时应用(如游戏、虚拟数字人)的需求。这与追求绝对精确的科学计算模拟有显著区别。
对于希望深入该领域的同学,建议:
- 夯实基础:理解本节课及本课程系列的内容,是阅读相关论文的坚实基础。
- 专注方向:模拟领域分支众多,建议选择一个感兴趣的方向(如流体、弹性体、碰撞)深入钻研,而非泛泛而读。
- 动手实践:在阅读论文的同时,尝试实现其中的算法,理解会更深刻。
- 关注现实问题:了解工业界(游戏、影视、数字服装等)的真实需求,思考如何用技术解决实际问题,这往往能产生更有价值的研究。
物理模拟是计算机图形学中富有挑战且极具趣味的方向,希望本课程能为大家打开一扇门。祝大家新年快乐,在图形学的道路上不断进步!




GAMES103-基于物理的计算机动画入门 - P2:Lecture 02 数学基础:矢量、矩阵与张量微积分 🧮
在本节课中,我们将学*物理模拟中至关重要的数学基础。主要内容分为三部分:矢量、矩阵以及相关的微积分概念。这些知识是理解后续物理模拟算法的基石。
矢量(Vector)📐
上一节我们介绍了课程概述,本节中我们来看看矢量。矢量是描述方向和大小的一种数学对象,在二维或三维空间中尤为常见。
矢量的定义与表示


一个三维矢量 p 可以表示为 p = (p_x, p_y, p_z),它属于实数空间 ℝ³。原点是一个特殊的矢量 0 = (0, 0, 0)。在印刷体和论文中,通常用黑体表示矢量,用斜体表示标量,以此区分,而非使用箭头符号。
坐标系:左手系与右手系
三维坐标系分为左手系和右手系。在图形学中,两者都有应用。例如,OpenGL通常使用右手系,而Unity和DirectX使用左手系。判断方法遵循“右手定则”或“左手定则”:从x轴转向y轴,大拇指指向的方向即为z轴正方向。
堆叠矢量(Stack Vector)
矢量不一定总具有直观的几何意义。例如,一个由11个顶点构成的物体,可以将所有顶点的坐标按顺序排列成一个33维的大矢量 X = [x₀, x₁, ..., x₁₀]ᵀ。这个堆叠矢量用于描述物体的整体状态,其维度是顶点数与空间维度的乘积。
矢量的基本运算
以下是矢量的一些基本运算:
- 加减法:对应元素相加减。几何上,矢量加法遵循三角形法则或*行四边形法则。
- p ± q = (p_x ± q_x, p_y ± q_y, p_z ± q_z)
- 与标量的乘法:矢量与标量t相乘,表示其缩放。
- p(t) = p + tv (描述点沿方向 v 的运动)
- p(t) = (1-t)p + tq (描述点 p 和 q 之间的线性插值)
- 范数(Norm):表示矢量的大小。
- 2-范数(欧几里得范数):‖p‖ = √(p_x² + p_y² + p_z²)
- 1-范数(曼哈顿范数):‖p‖₁ = |p_x| + |p_y| + |p_z|
- 无穷范数:‖p‖∞ = max(|p_x|, |p_y|, |p_z|)
- 单位矢量:范数为1的矢量。任何非零矢量 v 可通过 v / ‖v‖ 进行归一化。
矢量的乘法
我们有两种重要的矢量乘法。
点乘(内积)
点乘的结果是一个标量。
- 定义:p · q = p_x q_x + p_y q_y + p_z q_z
- 几何意义:p · q = ‖p‖ ‖q‖ cos θ,其中θ是两矢量的夹角。
- 性质:
- 满足交换律和分配律。
- p · p = ‖p‖²。
- 若 p · q = 0 且 p, q 非零,则 p 与 q 垂直。
叉乘(外积)
叉乘的结果是一个新的矢量。
- 定义:r = p × q = (p_y q_z - p_z q_y, p_z q_x - p_x q_z, p_x q_y - p_y q_x)
- 几何意义:结果矢量 r 同时垂直于 p 和 q,其大小 ‖r‖ = ‖p‖ ‖q‖ sin θ。
- 性质:
- 不满足交换律,p × q = - (q × p)。
- 满足分配律。
- 若 p × q = 0 且 p, q 非零,则 p 与 q *行。
点乘的应用实例
点乘在图形学中有广泛的应用。
点到直线的投影
给定点 q 和过点 o、方向为 v 的直线。点 q 在直线上的投影点 s 及其距离标量s可通过点乘求得:
- s = (q - o) · (v / ‖v‖)
- s = o + s v
定义*面与有符号距离
给定*面上一点 c 和其单位法向量 n,可以定义一个*面。对于空间中任意点 p,有符号距离 d = (p - c) · n。d的符号表明了 p 相对于*面的位置(上、面上、下),其绝对值是点到*面的真实距离。
点与球的碰撞检测
一个沿方向 v 运动的点 p(t) = p + tv 与静止的球(球心 c,半径 r)发生碰撞时,满足方程 ‖p(t) - c‖² = r²。将其展开并利用点乘性质,可得到一个关于时间t的一元二次方程,通过求解该方程即可判断碰撞是否发生及发生的时间。
叉乘的应用实例
叉乘同样在图形学中扮演关键角色。
三角形的面积与法向
给定三角形顶点 x₀, x₁, x₂,定义两条边向量 e₁₀ = x₁ - x₀, e₂₀ = x₂ - x₀。
- 法向量:n = (e₁₀ × e₂₀) / ‖e₁₀ × e₂₀‖
- 面积:A = ½ ‖e₁₀ × e₂₀‖
点与三角形的位置关系(内外测试)
要判断点 p 是否在三角形 x₀, x₁, x₂ 内部,可以检查由 p 与三角形每条边构成的小三角形的法向是否与大三角形法向 n 同向。具体通过计算三个标量值来判断:
- a₀ = n · ( (x₁ - p) × (x₂ - **p`) ) / 2
- a₁ = n · ( (x₂ - p) × (x₀ - **p`) ) / 2
- a₂ = n · ( (x₀ - p) × (x₁ - **p`) ) / 2
若 a₀, a₁, a₂ 同号(通常为正),则 p 在三角形内部。这些a值实际上是带符号的面积。
重心坐标(Barycentric Coordinates)
将上述符号面积 a₀, a₁, a₂ 除以三角形总面积 A,可得到重心坐标权重 (b₀, b₁, b₂),满足 b₀ + b₁ + b₂ = 1。三角形内任意点 p 的位置可由其顶点插值得到:p = b₀ x₀ + b₁ x₁ + b₂ x₂。这在渲染中进行颜色插值(如Gouraud着色)时非常有用。
四面体的体积
给定四面体四个顶点 x₀, x₁, x₂, x₃,其(带符号的)体积 V 为:
- V = (1/6) ( (x₁ - x₀) × (x₂ - x₀`) ) · (x₃** - x₀)
体积的正负由顶点顺序决定,为零则表示四点共面。类似于三角形,也可以定义四面体的重心坐标。
点与三角形的碰撞检测(进阶)
结合点面相交和内外测试,可以检测运动点与三角形是否碰撞。首先,求解点 p(t) 与三角形所在*面的交点时间 t(令四面体体积公式为零)。然后,将交点代入三角形内外测试公式,检查其是否在三角形内部。
矩阵(Matrix)🔲
上一节我们深入探讨了矢量及其运算,本节中我们来看看矩阵。矩阵可以看作是一组矢量的有序排列,是描述线性变换的强大工具。
矩阵的基本定义与运算
一个3x3的实数矩阵 A 可以表示为:
A = [ a₀₀ a₀₁ a₀₂ ]
[ a₁₀ a₁₁ a₁₂ ]
[ a₂₀ a₂₁ a₂₂ ]
以下是矩阵的一些基本概念和运算:
- 转置(Transpose):交换矩阵的行和列,记作 Aᵀ。
- 对称矩阵:满足 A = Aᵀ 的矩阵。
- 对角矩阵:非对角线元素全为零的矩阵。
- 单位矩阵:对角线元素全为1的对角矩阵,记作 I。任何矩阵或矢量与 I 相乘保持不变。
- 矩阵乘法:不满足交换律(AB ≠ BA),但满足结合律。
- 逆矩阵:若存在矩阵 B 使得 AB = BA = I,则 B 是 A 的逆矩阵,记作 A⁻¹。并非所有矩阵都可逆。
图形学中的特殊矩阵
在图形学中,以下几类矩阵尤为重要。
正交矩阵(Orthogonal Matrix)
一个矩阵 A 是正交矩阵,如果其列向量(或行向量)是两两正交的单位向量。正交矩阵有一个非常好的性质:Aᵀ = A⁻¹。在图形学中,旋转矩阵就是正交矩阵。它将物体的局部坐标系(x, y, z 轴)旋转到世界坐标系中的新方向(u, v, w),即 A = [u v w]。
缩放矩阵(Scaling Matrix)
缩放矩阵是一个对角矩阵 D,其对角线元素 (d_x, d_y, d_z) 分别表示在x, y, z轴方向上的缩放因子。
矩阵分解
矩阵分解帮助我们理解线性变换的本质。
奇异值分解(Singular Value Decomposition, SVD)
任何矩阵 A 都可以分解为 A = U D Vᵀ。其中 U 和 V 是正交矩阵,D 是对角矩阵(元素称为奇异值)。SVD的几何解释:任何线性变换都可以通过三个步骤实现:1. 旋转(Vᵀ);2. 沿坐标轴缩放(D);3. 再次旋转(U)。
特征值分解(Eigenvalue Decomposition)
对于对称矩阵 A,可以分解为 A = U D Uᵀ。其中 U 是正交矩阵,其列向量为特征向量;D 是对角矩阵,对角线元素为特征值。这可以看作是SVD在对称情况下的特例。
对称正定矩阵(Symmetric Positive Definite, SPD)
对称正定矩阵在求解物理模拟中的线性系统时至关重要。
- 定义:一个对称矩阵 A 是正定的,如果对于任何非零矢量 v,都有 vᵀA v > 0。若 vᵀA v ≥ 0,则为半正定。
- 直观理解:正定矩阵可以类比为正实数。一个对角元素全为正的对角矩阵显然是正定的。通过特征值分解可知,一个对称矩阵是正定的,当且仅当其所有特征值均为正。
- 性质与判定:
- 正定矩阵必然可逆。
- 若矩阵对角占优(即每一行/列上,对角线元素的绝对值大于该行/列其他元素绝对值之和),则该矩阵是正定的(这是一个充分条件,非必要条件)。
总结 📝
本节课我们一起学*了物理模拟所需的数学基础。我们从矢量的定义、运算(点乘、叉乘)及其在几何计算(投影、距离、碰撞检测)中的应用开始。然后,我们探讨了矩阵,包括其基本运算、图形学中特殊的旋转与缩放矩阵,以及重要的矩阵分解(SVD和特征值分解)。最后,我们介绍了在后续求解线性系统中非常关键的对称正定矩阵的概念。

这些数学工具是构建物理模拟算法的语言。理解它们将帮助我们更好地学*后续关于刚体模拟、有限元方法等内容。下节课我们将继续讲解张量微积分和线性系统求解,并逐渐过渡到具体的物理模拟实践。

GAMES103-基于物理的计算机动画入门 - P3:Lecture 03 刚体动力学 🚀
在本节课中,我们将学*刚体动力学的基础知识。课程内容分为两部分:首先回顾并完成上周关于线性代数和微积分的数学基础,然后深入探讨单个刚体的运动模拟,包括*移和旋转。
数学基础回顾与补充 📐
上一节我们介绍了矩阵正定性等概念,本节中我们来看看线性系统和微积分在物理模拟中的应用。
矩阵正定性示例
以下是一个证明示例:若矩阵 A 对称正定,则矩阵 B 也半正定。
B 的形式为:
B = [ A, -A;
-A, A ]
证明过程如下:
- 设任意向量可拆分为两部分:
[x; y]。 - 根据半正定定义,需证明
[x^T, y^T] * B * [x; y] >= 0。 - 展开计算可得:
(x - y)^T * A * (x - y)。 - 由于 A 正定,上式恒大于等于零(当
x = y时等于零)。 - 因此,B 半正定。
此结论在模拟中很有用,因为许多系统矩阵具有类似结构。
线性系统求解
许多数学问题最终归结为求解线性系统 Ax = b,其中 A 是矩阵,b 是边界条件向量,x 是未知向量。
直接计算 A 的逆矩阵通常不可行,因为计算量大且逆矩阵可能失去稀疏性。主要有两类方法:
1. 直接法(如LU分解)
直接法将矩阵 A 分解为下三角矩阵 L 和上三角矩阵 U 的乘积:A = LU。
求解分两步:
- 解
Ly = b(前向代入)。 - 解
Ux = y(后向代入)。
以下是前向代入的伪代码示例:
# 假设 L 是下三角矩阵,b 是已知向量
y = zero_vector(n)
for i in range(n):
sum = 0
for j in range(i):
sum += L[i][j] * y[j]
y[i] = (b[i] - sum) / L[i][i]
直接法特点:
- 矩阵稀疏性影响分解结果。
- 计算分分解和求解两步,若 A 固定可复用分解结果。
- 并行化相对困难。
- 常用库:Intel MKL Pardiso。
2. 迭代法
迭代法通过不断更新 x 来逼*解,基本形式为:
x_{k+1} = x_k + α * M^{-1} * (b - A * x_k)
其中 α 是松弛系数,M 是易于求逆的矩阵(如 A 的对角部分),(b - A*x_k) 是残差。
迭代法收敛条件与矩阵的谱半径有关。常用方法包括雅可比法(取 M 为 A 的对角线)和高斯-赛德尔法(取 M 为 A 的下三角部分)。
迭代法与直接法对比:
- 优点:易于实现、易于并行、适合不求精确解的场景。
- 缺点:存在收敛性问题、求精确解可能较慢。
微积分基础
本节回顾向量微积分,为后续内容做准备。
梯度
对于标量函数 f(x),其梯度 ∇f 是一个向量,指向函数值增长最快的方向。
∇f = [ ∂f/∂x, ∂f/∂y, ∂f/∂z ]^T
雅可比矩阵、散度与旋度
对于向量函数 F(x),其雅可比矩阵 J 包含所有一阶偏导。
散度 div(F) 是雅可比矩阵的迹(对角线之和)。旋度 curl(F) 描述了场的旋转特性,在流体模拟中用于漩涡计算。
泰勒展开
函数 f(x) 在 x0 处的泰勒展开为:
f(x) ≈ f(x0) + ∇f(x0)^T (x - x0) + 1/2 (x - x0)^T H(x0) (x - x0) + ...
其中 H 是海森矩阵(二阶偏导矩阵)。矩阵的正定性与海森矩阵相关,决定了函数在该点的凹凸性。
实例:弹簧模型
考虑一维弹簧,原长 L,弹性系数 k。当前位置为 x,则弹簧能量 E 和力 F 为:
E = 1/2 * k * (|x| - L)^2
F = -∇E = -k * (|x| - L) * (x / |x|)
力的方向沿弹簧轴向,大小与形变成正比。
进一步求力关于位置的导数,可得到刚度矩阵(海森矩阵):
∂F/∂x = k * [ (x*x^T)/|x|^2 + ( (|x|-L)/|x| ) * (I - (x*x^T)/|x|^2 ) ]
对于多顶点系统,所有变量和力可组合成大向量和大矩阵,其刚度矩阵具有分块形式,与我们开头证明的矩阵形式相似。
刚体动力学 🤖

上一节我们完成了数学基础的铺垫,本节中我们来看看刚体运动的核心原理。

刚体状态描述
刚体运动包括*移和旋转,无形状变化。其状态由两部分描述:
- 位置
x:一个三维向量,描述物体中心的位置。 - 旋转
R:描述物体的朝向。可用旋转矩阵、欧拉角或四元数表示。
在Unity中,物体的 Transform 组件包含了 position 和 rotation 属性,分别对应位置和旋转(内部用四元数存储)。

运动方程与时间积分

刚体运动遵循牛顿定律。我们有两个核心变量:
- 速度
v = dx/dt - 角速度
ω(描述旋转快慢和轴)
运动方程(积分形式)为:
v_new = v_old + (1/m) * ∫ F dt
x_new = x_old + ∫ v dt
其中 F 是合力,m 是质量。
数值积分方法
求解积分需用数值方法。以一维速度积分 ∫ v dt 为例,即估算速度曲线下的面积。
-
显式欧拉法:用起始时刻速度估算。
∫ v dt ≈ v(t0) * Δt一阶精度,可能低估面积。
-
隐式欧拉法:用结束时刻速度估算。
∫ v dt ≈ v(t1) * Δt一阶精度,可能高估面积。
-
中点法:用中间时刻速度估算。
∫ v dt ≈ v(t0 + Δt/2) * Δt二阶精度,更精确。
对于刚体模拟,常采用一种“蛙跳”格式,交替更新速度和位置,本质上等价于对两者都使用中点法。
*移运动模拟
模拟*移运动的更新步骤如下:
- 计算所有顶点上的合力
F_total。 - 更新速度:
v_new = v_old + (F_total / m) * Δt。 - 更新位置:
x_new = x_old + v_new * Δt。
常见的力包括:
- 重力:
F_gravity = m * g,方向竖直向下。 - 空气阻力:可简化为对速度的衰减,如
v_new = 0.99 * v_old,简单且稳定。
旋转运动模拟
旋转的模拟更为复杂。
旋转的表示
我们推荐使用四元数 q 表示旋转。它由实部 s 和虚部向量 v 构成:q = [s, v]。一个绕单位轴 n 旋转 θ 角度的四元数为:
q = [cos(θ/2), sin(θ/2) * n]
Unity 内部即使用四元数,可通过 Transform.rotation 访问。
旋转运动方程
旋转的模拟需要以下物理量:
- 力矩
τ:力引起旋转的“推力”。对于顶点i,力矩τ_i = r_i × F_i,其中r_i是从质心到顶点的向量。总力矩为所有顶点力矩之和。 - 惯性张量
I:旋转中的“质量”,是一个3x3矩阵。它依赖于物体的形状和质量分布。惯性张量从物体参考坐标系变换到世界坐标系的公式为:I_world = R * I_ref * R^T。
旋转的更新步骤如下:
- 计算总力矩
τ_total。 - 更新角速度:
ω_new = ω_old + I^{-1} * τ_total * Δt。 - 更新旋转四元数:
q_new = q_old + (Δt/2) * [0, ω] * q_old。注意这里是四元数乘法。 - 对
q_new进行归一化,保持其为单位四元数。
刚体模拟器框架
一个完整的刚体模拟器在每个时间步需执行以下操作:
*移部分:
- 计算合力
F。 v = v + (F / m) * Δtx = x + v * Δt
旋转部分:
- 计算总力矩
τ。 ω = ω + I^{-1} * τ * Δtq = q + (Δt/2) * Quaternion(0, ω) * qq = q.Normalize()
在Unity中实现时需注意:
- 位置
x对应Transform.position。 - 旋转
q对应Transform.rotation。 - 速度
v和角速度ω需在自定义脚本中声明和更新。 - Unity 提供四元数乘法,但不直接提供四元数与标量乘法或加法,需手动对四个分量操作。
- Unity 的
Matrix4x4类可用来进行矩阵运算,但需注意其是4x4矩阵。
实现建议
- 分步实现:先实现*移运动,再实现旋转运动。测试旋转时,可先固定角速度
ω为常数,验证旋转更新正确后再加入力矩计算。 - 注意归一化:更新四元数后务必进行归一化,防止数值误差累积。
- 重力与旋转:均匀重力场中,重力不产生力矩,因此重力不会使物体自发旋转。
总结 🎯
本节课中我们一起学*了刚体动力学模拟的核心内容。
我们首先补充了相关的数学基础,包括通过示例理解了特定结构矩阵的正定性、线性系统求解的直接法与迭代法,以及向量微积分在物理公式推导中的应用。


接着,我们深入探讨了刚体模拟。刚体的状态由位置和旋转描述。我们学*了使用四元数表示旋转的优势和方法。运动模拟的核心是对运动方程进行时间积分,我们介绍了几种积分方法及其精度。对于*移运动,我们给出了基于力的更新步骤。对于更复杂的旋转运动,我们引入了力矩和惯性张量的概念,并给出了基于四元数和角速度的更新流程。

最终,我们整合出了一个刚体模拟器的基本框架。掌握这些原理,是编写一个简单刚体物理引擎的基础。在接下来的课程中,我们将探讨刚体之间的碰撞检测与响应问题。

GAMES103-基于物理的计算机动画入门 - P4:Lecture 04 刚体接触 (Lab 1) 🐇💥



在本节课中,我们将要学*刚体碰撞检测与响应的核心方法,并了解一个无需物理知识的替代方案——形状匹配。课程内容将围绕作业展开,首先介绍作业要求,然后回顾刚体模拟的基础概念,最后深入讲解碰撞处理与形状匹配技术。
作业介绍与回顾 📋
首先,我们交代本次作业。这是课程的第一次作业,目标是实现一个刚体碰撞模拟。作业分为基础任务和附加题。
基础任务是完成一个名为 RigidBody 的 Unity 脚本,实现基于冲量法的刚体碰撞。脚本中已提供计算惯性张量、叉乘矩阵等辅助功能。完成后的效果是:运行程序,按下 L 键,兔子模型会撞向墙壁并反弹落下。

附加题是使用“形状匹配”技术实现刚体模拟。这种方法不涉及任何物理公式,完全基于图形学和优化。在 Unity 中,通过切换脚本(从 RigidBody 切换到 ShapeMatching)即可看到不同的模拟效果。此方法在处理摩擦时可能会有轻微滑动。

作业周期为两周。对于不熟悉 Unity 的同学,建议先学* Unity 的基本操作和脚本编写。
刚体运动回顾 🔄
上一节我们介绍了刚体模拟,但时间仓促。本节我们首先回顾刚体运动的核心算法,特别是针对没有大学物理基础的同学。
刚体运动分为线性(*移)运动和旋转运动。线性运动的更新基于牛顿第二定律:
位置更新公式:
v_new = v_old + (F_total / m) * Δt
x_new = x_old + v_new * Δt
其中,F_total 是所有顶点上力的矢量和,m 是刚体总质量。
对于旋转运动,我们需要对应的概念。造成旋转趋势的不是力,而是 力矩。
力矩计算公式:
τ_i = (R * r_i) × f_i
这里,R 是描述当前刚体朝向的旋转矩阵,r_i 是顶点在参考姿态下相对于质心的向量,f_i 是作用在该顶点上的力。力矩 τ 的方向垂直于力臂和力的方向,大小与两者模长及夹角的正弦成正比。
将所有顶点的力矩求和,得到总力矩 τ_total。
接下来,我们需要更新角速度。在线性运动中,加速度是 F/m。在旋转中,角加速度是 τ 除以 惯性张量 I。但 I 不是一个标量,而是一个矩阵。
惯性张量计算公式(在参考坐标系下):
I_ref = Σ_i [ m_i * ( (r_i^T * r_i) * E - r_i * r_i^T ) ]
其中,E 是单位矩阵。这是因为物体对旋转的“抵抗”与旋转轴的方向有关。例如,质量分布远离旋转轴时,更难转动。
当刚体旋转后,其惯性张量会变化。但我们可以通过旋转矩阵快速计算当前的惯性张量,而无需重新求和:
当前惯性张量计算公式:
I = R * I_ref * R^T
得到惯性张量后,即可更新角速度:
角速度更新公式:
ω_new = ω_old + I^{-1} * τ_total * Δt
最后,使用新的角速度更新描述刚体朝向的四元数 q。其更新公式涉及四元数乘法,具体推导可参考课程附录。
以上就是刚体运动模拟的基本框架。接下来,我们将进入本节课的核心:碰撞处理。
单点碰撞检测与响应 🎯
本节中,我们来看看如何处理一个单独质点的碰撞,暂时不考虑刚体的旋转。我们主要介绍两种方法:惩罚法和冲量法。
首先,我们需要一个工具来判断碰撞是否发生:有符号距离函数。
有符号距离函数
定义一个函数 Φ(x),对于空间中的任意点 x,它返回到某个物体表面的距离。符号表明点在表面的内侧(负值)还是外侧(正值)。表面就是 Φ(x) = 0 的点的集合。
以下是几个简单几何体的距离函数示例:
- *面(*面上一点
p,法向n):
Φ(x) = (x - p) · n - 球体(球心
c,半径r):
Φ(x) = ||x - c|| - r - 圆柱体(轴上一点
p,轴向a,半径r):
d = || (x-p) - ((x-p)·a) * a ||
Φ(x) = d - r
对于复杂物体,可以通过布尔操作组合简单物体的距离函数:
- 交集(物体A 与 物体B):点在内部当所有
Φ_i(x) < 0。*似距离函数取max(Φ_i(x))。 - 并集(物体A 或 物体B):点在内部当任一
Φ_i(x) < 0。在外侧时,距离函数可*似为min(Φ_i(x))。
利用距离函数,碰撞检测变得简单:若 Φ(x) < 0,则发生碰撞。
惩罚法
惩罚法的思路是:检测到穿透后,施加一个力将点推出去。这个力通常像弹簧力,大小与穿透深度成正比,方向是表面的法向(即距离函数的梯度方向)。
基本惩罚力公式:
f = -k * Φ(x) * n
其中 n = ∇Φ(x),k 是弹性系数。
为了避免明显的穿透,可以设置一个缓冲厚度 ε。当 Φ(x) < ε 时就施加力,力的大小与 (ε - Φ(x)) 成正比。
惩罚法的主要问题是参数 k 难以调节:太小则无法阻止穿透,太大则容易导致模拟不稳定(过冲)。一种改进是使用 对数障碍函数,让力随着点接*表面而急剧增大:
对数障碍函数力公式:
f = (ρ / Φ(x)) * n
其中 ρ 是强度参数。但此法要求步长非常小,且绝对不能发生穿透。
冲量法
冲量法的思路是:在检测到碰撞的瞬间,直接更新点的速度和位置,使其立即响应,而不是等到下一时刻通过力来改变。
以下是处理一个质点碰撞的冲量法步骤:
-
位置修正:将穿透的点沿法向推回表面。
x_new = x_old - Φ(x) * n(因为Φ(x)为负) -
速度修正:修改点的速度,模拟反弹和摩擦。
- 将速度
v分解为法向分量v_n和切向分量v_t。
v_n = (v·n) * n
v_t = v - v_n - 计算新的法向速度(反弹)和切向速度(摩擦)。
v_n_new = -μ_n * v_n(μ_n为弹性系数,0 ≤ μ_n ≤ 1)
v_t_new = a * v_t(a为摩擦衰减系数) - 根据库仑摩擦定律,
a应尽可能小,但不能使切向速度反向。其计算公式为:
a = max(0, 1 - μ_t * (1 + μ_n) * |v_n| / |v_t|)
其中μ_t是摩擦系数。a=0表示静摩擦(物体停下),a>0表示动摩擦。 - 合成新速度:
v_new = v_n_new + v_t_new
- 将速度
冲量法能更精确地控制碰撞后的反弹和摩擦效果。接下来,我们将把冲量法应用到刚体上。
刚体的碰撞处理 🤖💥
对于刚体,我们不能直接修改单个顶点的速度,因为模拟的状态变量是质心的速度 v、角速度 ω、位置 x 和朝向 q。我们需要通过施加冲量 J 来间接改变顶点的速度。
假设在顶点 i 处检测到碰撞。该顶点的世界坐标和速度为:
x_i = x + R * r_i
v_i = v + ω × (R * r_i)
我们的目标是:找到一个冲量 J,施加在碰撞点,使得该点碰撞后的速度 v_i_new 等于用前述单点冲量法计算出的理想速度 v_i_desired。
推导表明,冲量 J 与顶点速度变化量 Δv_i = v_i_desired - v_i 存在线性关系:
冲量与速度变化的关系:
Δv_i = K * J
其中矩阵 K = (1/m) * E - ( [R*r_i]× )^T * I^{-1} * [R*r_i]×,[·]× 表示叉乘矩阵。
因此,我们可以计算出所需的冲量 J = K^{-1} * Δv_i。得到 J 后,再更新刚体的整体状态:
刚体状态更新公式:
v_new = v_old + J / m
ω_new = ω_old + I^{-1} * ( (R*r_i) × J )
以下是刚体碰撞处理的算法流程:
- 遍历刚体每个顶点,计算其世界坐标
x_i。 - 使用有符号距离函数检测
x_i是否发生碰撞(Φ(x_i) < 0)。 - 如果发生碰撞,计算该顶点的当前速度
v_i。 - 判断
v_i是否仍指向物体内部(v_i · n < 0)。若是,则继续。 - 使用单点冲量法公式,计算该顶点期望的碰撞后速度
v_i_desired。 - 计算矩阵
K和速度变化Δv_i。 - 计算冲量
J = K^{-1} * Δv_i。 - 用冲量
J更新刚体的质心速度v和角速度ω。
实现细节:
- 如果多个顶点同时碰撞,可以取这些碰撞顶点位置的*均值作为一个代表点进行处理,以避免过度反应。
- 由于重力持续作用,物体在*面上可能持续微幅抖动。可以在速度很小时施加阻尼来缓解此现象。
处理多个刚体间的碰撞更为复杂,需要求解一个线性互补问题,本课暂不深入。
形状匹配:一种无物理的替代方案 🧩
上一节我们介绍了基于物理的冲量法。本节中,我们来看看一种完全不同的、不依赖任何物理公式的方法——形状匹配。这对于物理基础薄弱的同学可能更友好。
其核心思想分为两步:
- 自由移动:将刚体视为一堆独立的质点,每个质点根据自己的受力(如重力、碰撞力)自由运动一个时间步。这会导致形状扭曲。
- 形状匹配:将扭曲后的一堆质点,通过一个最优的刚体变换(旋转
R和*移c),重新“匹配”回它原来的参考形状。
数学描述:
假设自由移动后,顶点位置为 y_i。我们想找到质心 c 和旋转矩阵 R,使得变换后的顶点 c + R * r_i 尽可能接* y_i。这转化为一个优化问题:
优化目标:
最小化 Σ_i || (c + R * r_i) - y_i ||^2
求解过程:
- 计算最优质心:目标函数对
c求导,发现最优质心就是所有顶点y_i的*均值。
c = (1/N) * Σ_i y_i - 计算最优变换:先放宽条件,令
R为一个任意矩阵A。目标函数对A求导,可解得:
A = ( Σ_i (y_i - c) * r_i^T ) * ( Σ_i r_i * r_i^T )^{-1} - 提取旋转:对矩阵
A进行 极分解,将其分解为旋转矩阵R和一个对称矩阵(代表形变)的乘积:A = R * S。我们丢弃形变部分S,保留旋转部分R作为最终结果。
得到 c 和 R 后,我们就可以更新刚体的“质心”和“朝向”。同时,可以根据顶点位置的变化反推出顶点的速度,用于下一帧的“自由移动”步骤。
形状匹配的优缺点:
- 优点:实现简单,无需物理参数;易于与基于粒子的其他模拟(如流体、软体)结合。
- 缺点:难以严格处理摩擦等约束;可能需要多次迭代来满足多个约束;碰撞响应可能不够精确(如出现滑动)。
此方法适用于对碰撞精度要求不高的场合,或者刚体作为复杂系统(如衣服上的纽扣)的一部分时。


总结 🎓
本节课中,我们一起学*了刚体碰撞的核心内容。
我们首先回顾了刚体运动模拟的基础,包括力矩、惯性张量和运动更新公式。接着,我们深入探讨了碰撞处理。从单点碰撞入手,介绍了惩罚法和冲量法两种响应策略。然后,我们将冲量法扩展到刚体,详细推导了如何通过施加冲量来更新刚体的整体运动状态。最后,我们介绍了一种无需物理知识的替代方案——形状匹配,阐述了其原理和实现步骤。
本次作业要求实现基于冲量法的刚体碰撞,附加题则是实现形状匹配算法。希望本教程能帮助你理解这些概念,顺利完成作业。



GAMES103-基于物理的计算机动画入门 - P5:Lecture 05 基于物理的布料模拟 🧵

在本节课中,我们将要学*基于物理的布料模拟。我们将从基础的弹簧模型开始,探讨如何用它来描述和模拟布料,并分析显式积分与隐式积分两种方法的优劣。课程的核心目标是理解如何从物理能量和力的模型出发,构建一个稳定的布料模拟系统。
课程概述与安排
我们之前几周主要讨论了刚体模拟,现在将开启一个新的主题:布料模拟。布料模拟与头发模拟有一定联系,但各有特点。我们将用三周时间深入探讨布料模拟。
第一周,也就是今天,我们将重点讲解基于物理的仿真模拟,即如何根据能量和力的模型推导出布料模拟的方式。
第二周,我们将讨论约束,包括游戏开发中常用的PBD(位置动力学)方法、Projective Dynamics以及专门处理约束的Constraints Dynamics。
第三周,我们将讨论碰撞处理。布料的碰撞处理是最具挑战性的,掌握其方法后,处理其他模拟的碰撞会相对容易。
本节课将涵盖以下内容:首先讲解如何使用弹簧描述和模拟布料,包括显式和隐式积分方法。然后讨论弯曲模型的重要性,分析弹簧弯曲模型的不足及其他替代模型。最后,如果有时间,会介绍基于Shape Matching的非弹簧模拟方法。
弹簧模型基础
上一节我们介绍了课程的整体安排,本节中我们来看看如何用弹簧模型来描述一块布料。
一个理想的弹簧满足胡克定律。该定律指出,弹簧的力试图恢复其原长,且力的大小与拉伸长度成正比。
考虑一个一维弹簧,其一端固定在原点,当前位置为 x,原长为 l。那么弹簧的能量 E 为:
E = 1/2 * k * (x - l)^2
其中 k 是弹簧的弹性系数。
力 F 是能量对位置的负导数:
F = -dE/dx = -k * (x - l)
这个公式与我们中学物理所学的完全一致。
我们可以将此概念推广到二维或三维。假设一根弹簧连接了两个点 i 和 j,原长为 l。那么弹簧的能量为:
E = 1/2 * k * (||x_i - x_j|| - l)^2
其中 ||x_i - x_j|| 是两点间的当前距离。
力是能量对点位置的负梯度。对于点 i 和 j,其受力分别为:
F_i = -k * (||x_ij|| - l) * (x_ij / ||x_ij||)
F_j = -F_i
其中 x_ij = x_i - x_j。这表明作用力与反作用力大小相等,方向相反。
构建弹簧网络系统


上一节我们介绍了单根弹簧的模型,本节中我们来看看如何用多根弹簧构建一个布料网络。
根据物理原理,能量和力是可以叠加的。如果一个顶点连接了多根弹簧,那么作用在该顶点上的总力就是所有相关弹簧力的矢量和。
在实际模拟中,我们需要构造一个弹簧网络来描述布料。主要有两种方式:
结构化网格:假设布料由规整的方格构成。每个网格交点是一个顶点。我们需要以下几种弹簧:
- 结构弹簧:连接横向和纵向相邻顶点,抵抗拉伸。
- 剪切弹簧:连接对角线上的顶点(如45度和135度方向),防止布料在斜向过度拉伸。
- 弯曲弹簧:连接隔一个顶点的两个顶点(例如,跨越一个顶点的弹簧),用于抵抗布料沿边的自由弯曲,增加布料的挺括感。
一种常见的优化是采用交错的对角线弹簧布局,这样可以在减少弹簧数量的同时,兼顾各个方向的抵抗,避免模拟产生方向偏好。
非结构化三角网格:在服装设计等领域,布料的版型通常是不规则的三角网格。处理方式如下:
- 将三角网格的每一条边都视为一根结构弹簧。
- 对于网格内部的每一条边,将其所对的两个顶点(即不在这条边上的两个顶点)用一根弹簧连接起来,这根弹簧就作为弯曲弹簧。
从三角网格数据构造弹簧系统
上一节我们知道了需要哪些弹簧,本节中我们来看看如何从给定的三角网格数据中自动提取这些弹簧信息。
程序中的三角网格通常由两个列表表示:
- 顶点位置列表:存储每个顶点的3D坐标。
- 三角形索引列表:存储每个三角形所包含的三个顶点在顶点列表中的索引。
为了构造弹簧系统,我们需要从这些数据中提取出所有唯一的边(用于结构弹簧)和所有内部边(用于生成弯曲弹簧)。
以下是处理流程:
- 生成原始边列表:遍历每个三角形,将其三条边(由两个顶点索引表示)与所属三角形索引一起,存储为一个三元组
(v_a, v_b, triangle_id)。为确保每条边有唯一的表示,我们总是将顶点索引按从小到大排序(例如,边(3,0)存储为(0,3))。 - 排序与去重:对所有原始边三元组按顶点索引进行排序。排序后,重复的内部边会相邻出现。
- 提取结构弹簧:遍历排序后的列表。如果当前边与下一条边的顶点索引完全相同,则说明是内部边,跳过其中一条(去重)。剩下的唯一边就是最终的结构弹簧边。
- 提取弯曲弹簧:在第二步去重时,被跳过的重复边就是内部边。对于每条内部边,我们知道它相邻的两个三角形。这两个三角形中,不在这条内部边上的那两个顶点,就是弯曲弹簧需要连接的两个顶点。
通过以上步骤,我们就可以从三角网格数据中构造出完整的弹簧系统。



具体实现代码将在作业示例中提供,感兴趣的同学可以深入研究。
显式积分模拟
有了弹簧系统,我们就可以开始模拟了。首先介绍显式积分方法。
一个简单的粒子系统更新步骤如下:
- 对于每个顶点
i,计算其受到的总力F_i(包括重力、弹簧力等)。 - 根据牛顿第二定律更新速度:
v_i_new = v_i + Δt * F_i / m_i - 根据速度更新位置:
x_i_new = x_i + Δt * v_i_new
对于弹簧系统,我们只需在第一步的力计算中加入所有相关弹簧的力即可。计算每根弹簧的力时,我们需要:
- 弹簧连接的顶点索引
i和j。 - 弹簧的原长
l(可预先计算)。 - 根据公式
F = -k * (||x_ij|| - l) * (x_ij / ||x_ij||)计算力,并分别加到顶点i和j的合力上。

显式积分的问题与隐式积分
上一节我们介绍了简单的显式积分,本节中我们来看看它存在的一个严重问题:数值不稳定性。
当弹簧的弹性系数 k 很大,或模拟的时间步长 Δt 较大时,显式积分容易产生“过冲”现象。这是因为过大的力会使顶点位置更新过度,越过*衡点,甚至在下一次迭代中产生更大的力,导致顶点位置发散,最终模拟崩溃。
解决思路之一是缩小时间步长 Δt,但这会显著降低模拟效率。因此,在需要稳定模拟或处理“刚性”系统时,我们更常使用隐式积分方法。
隐式积分使用未来时刻的状态来计算力,其更新公式如下:
v_new = v + Δt * M^{-1} * F(x_new, v_new)
x_new = x + Δt * v_new
这里 F(x_new, v_new) 是在新位置和新速度下的力,是未知量。这使得方程难以直接求解。
隐式积分转化为优化问题
为了求解隐式积分方程,我们首先假设力是保守力,且只与位置有关(如重力、弹簧力),即 F = F(x)。我们可以通过代入消元法,将方程组化简为只关于新位置 x_new 的方程。
更重要的是,我们可以证明,求解这个方程等价于求解以下非线性优化问题:
x_new = argmin_x { (1/(2Δt^2)) * (x - x̃)^T M (x - x̃) + E(x) }
其中 x̃ = x + Δt * v 是仅根据当前速度预测的位置,M 是质量矩阵(通常是对角阵),E(x) 是系统的势能(如弹簧势能)。
这个优化问题的含义是:新位置 x_new 使得“惯性项”(粒子试图保持预测运动趋势)和“势能项”(系统试图降低势能)的加权和最小。
通过这种转化,我们将物理模拟问题转化为一个数学优化问题,从而可以利用成熟的优化算法来求解。
牛顿法求解优化问题
上一节我们将模拟问题转化为了优化问题,本节中我们来看看如何使用牛顿法来求解它。
对于标量函数 f(x),寻找其极小值点等价于寻找其梯度(一阶导数)为零的点:∇f(x) = 0。牛顿法的核心思想是利用泰勒展开进行迭代逼*。
假设当前迭代点为 x_k,我们希望在这一点附*对梯度函数 ∇f(x) 做线性*似:
∇f(x) ≈ ∇f(x_k) + H(x_k) * (x - x_k)
其中 H(x_k) 是函数 f 在 x_k 处的海森矩阵(二阶导数)。
令*似梯度为零,即可解出下一步的迭代点 x_{k+1}:
x_{k+1} = x_k - H(x_k)^{-1} * ∇f(x_k)
对于我们的布料模拟优化问题,f(x) 就是之前定义的目标函数。其梯度 ∇f(x) 为:
∇f(x) = (1/Δt^2) * M * (x - x̃) + ∇E(x)
而 ∇E(x) 就是负的力 -F(x)。海森矩阵 H(x) 为:
H(x) = (1/Δt^2) * M + H_E(x)
其中 H_E(x) 是势能 E(x) 的海森矩阵,即力的雅可比矩阵(刚度矩阵)。
因此,布料模拟的隐式积分牛顿法迭代步骤为:
- 初始化新位置
x(例如用x̃)。 - 计算当前梯度
∇f(x)和海森矩阵H(x)。 - 求解线性方程组
H(x) * Δx = -∇f(x),得到位置增量Δx。 - 更新位置:
x = x + Δx。 - 重复步骤2-4,直到收敛(如
Δx足够小)。 - 最后,用更新后的位置计算新速度:
v_new = (x_new - x) / Δt。
弹簧系统的海森矩阵与数值求解
上一节我们介绍了牛顿法的框架,本节中我们深入看一下弹簧系统海森矩阵的特点和数值求解的细节。
对于弹簧系统,整个网络的海森矩阵 H 由每根弹簧的贡献叠加而成。每根弹簧对其连接的两个顶点 i 和 j 的贡献是一个 6x6 的块矩阵,放置在大矩阵的 (i,i), (i,j), (j,i), (j,j) 块位置上。
一个重要的性质是,当弹簧被拉伸时,其贡献的块矩阵是半正定的;但当弹簧被压缩时,这部分贡献可能变得非正定。这可能导致整个海森矩阵 H 非正定。
在优化中,如果海森矩阵始终正定,则目标函数是凸函数,保证存在唯一全局极小值。非正定则意味着可能存在多个局部极小值(例如,一根被挤压的弹簧可能向左或向右弯曲,形成两种不同的稳定状态)。在实际模拟中,非正定主要影响的是某些线性求解器的稳定性。一个常见的处理技巧是,当检测到弹簧处于压缩状态时,忽略其海森矩阵中可能导致非正定的项。
求解牛顿法中的线性方程组 H * Δx = -∇f 是关键步骤。主要有两类方法:
- 直接法(如LU分解):精度高,但对于大规模矩阵(顶点数多)内存消耗大。
- 迭代法(如共轭梯度法):内存消耗小,适合大规模问题,但收敛速度和精度依赖于矩阵条件数,通常需要预条件子。
在实际应用中,根据问题规模和可用硬件进行选择。
经典论文与课程总结
在本节课中,我们一起学*了基于物理的布料模拟基础。
我们从最简单的弹簧模型出发,解释了如何用胡克定律描述布料的拉伸行为。接着,探讨了如何将三角网格转化为弹簧网络系统,包括结构弹簧和弯曲弹簧的构造方法。
我们分析了显式积分方法的简单直观性及其固有的数值不稳定性问题,这引出了对隐式积分的需求。通过将隐式积分方程转化为等价的非线性优化问题,我们能够更稳定地求解系统状态。牛顿法为求解该优化问题提供了框架,其中涉及梯度、海森矩阵的计算以及大规模线性方程组的求解。
最后,推荐一篇该领域的经典论文《Stable but Responsive Cloth》,它最早将隐式积分引入布料模拟,尽管其推导视角与本节课的优化视角略有不同,但本质相通,是深入理解布料模拟的重要资料。
本节课的内容,特别是隐式积分部分,是许多物理模拟方法(如有限元法)的核心基础。理解这一框架,对于后续学*更复杂的模拟技术至关重要。



GAMES103-基于物理的计算机动画入门 - P6:Lecture 06 约束方法:PBD、PD及其他 (Lab 2) 📐


在本节课中,我们将学*布料模拟中的约束方法,特别是位置动力学(PBD)和拉伸限制(Strain Limiting)。这些方法为处理布料等可变形体的模拟提供了稳定且高效的解决方案。课程内容将涵盖基本概念、算法实现及其优缺点,帮助你理解如何在实际项目中应用这些技术。


作业延期与提交说明 📝
关于第一次作业的提交,我们注意到部分同学时间紧张。因此,作业可以适当延期提交。但请注意,每晚提交一天,成绩将扣除20%。若延迟超过五天,该次作业成绩将不计入总分统计,但你仍可提交并获得助教的反馈。我们鼓励大家尽量在五天内完成作业。

作业相关问题可以在微信群的小程序圈子中提问。如果是普遍性问题,建议在公共论坛提问;如果是具体代码问题,可以直接联系助教。实践是学*计算机课程的关键,完成作业能帮助你深入理解知识,巩固学*成果。
第二次作业介绍:布料模拟 👕
第二次作业与当前课程内容紧密相关,目标是模拟一块布料的动态效果。作业提供了一个名为“Cloth”的场景,其中包含一块布料和一个用于交互的小球。

布料有两个固定点(顶点0和顶点20),模拟时用户可以拖拽小球与布料进行交互。上图展示了使用位置动力学(PBD)技术实现的效果。布料的弹性表现可以通过调整迭代次数来控制:迭代次数越少,布料显得越有弹性;迭代次数越多,布料则显得越硬。请注意,当前模拟未处理布料的自相交,仅处理了布料与球的碰撞。
作业包含两个部分:
- 位置动力学(PBD)方法
- 隐式积分弹簧系统方法

若要使用隐式积分方法,你需要在提供的两个脚本之间进行切换。

隐式积分部分旨在帮助大家巩固上周所学的知识。在实际作业实现中,我们采用了一种*似的简化方法,使用一个对角矩阵来*似海森矩阵(Hessian),从而将解线性系统的步骤简化为一个直接的更新操作。具体的实现公式将在作业描述中给出。这种方法虽然并非标准的牛顿法,但其收敛原理我们将在后续课程中详细探讨。
布料模拟中的弯曲模型 🔄
上一节我们介绍了弹簧系统的基本模拟。本节中,我们来看看弹簧系统在模拟布料弯曲时存在的缺陷,并探讨更优的弯曲模型。
弹簧系统的一个主要问题是模拟弯曲抵抗时不够准确。例如,使用连接两个相对顶点的“弯曲弹簧”时,当两个三角形几乎共面并发生轻微弯曲时,弹簧长度的变化极小,导致产生的抵抗力非常弱,这与真实世界中纸张或布料的弯曲感觉不符。
一个更直观的思路是利用两个三角形之间的二面角(Dihedral Angle)来定义弯曲力。这种方法被称为二面角方法。
对于一个由四个顶点构成的二面角,弯曲力可以写成一个关于二面角θ的函数。每个顶点上的力 f_i 可以表示为:
f_i = k(θ) * u_i
其中,k(θ) 决定了力的大小,u_i 决定了力的方向。
推导u_i的过程基于几个合理假设:
- 顶点1和2上的力应分别沿着各自三角形的法向 n1 和 n2。
- 弯曲不应导致连接顶点3和4的边发生形变,因此作用在顶点3和4上的力之差应垂直于该边。
- 所有弯曲力作为内力,其合力应为零。
基于这些假设,可以推导出各个u_i的表达式。而力的大小函数 k(θ) 的一种常见形式为:
k(θ) = k * (e^2 / (|n1| * |n2|)) * sin((π - θ)/2)
其中,k是常数,e是边长,|n1|和|n2|是两个三角形的面积(即未归一化的法向量模长)。若要定义非*面的静止状态(初始夹角θ0),公式可修改为包含 sin((π - θ0)/2) 项。
这个基于力的模型优点是与显式积分兼容,但缺点是没有明确的能量定义,用于隐式积分时求导会比较麻烦。
另一种更简单的模型是基于余切权重的拉普拉斯弯曲模型(Discrete Shells)。该模型基于两个假设:布料静止时为*面,且模拟中拉伸形变很小。
它定义了一个能量函数 E(x):
E(x) = (k/2) * (x^T * Q * Q^T * x)
其中,x 是所有顶点位置排成的向量,Q 是一个由余切权重(cotangent weights)构成的常数矩阵。这个能量函数本质上是在计算离散曲率的*方。当布料完全*整时,曲率为零,能量也为零;弯曲时,能量增大,产生恢复力。
该模型的优点是能量函数是二次的,其梯度(力)和海森矩阵都非常容易计算,f = -k * Q * Q^T * x,H = k * Q * Q^T,非常适合隐式积分。缺点是当布料发生较大拉伸时,其准确性会下降。
布料模拟中的锁定问题 🔒
接下来,我们讨论布料模拟中一个重要的现象:锁定问题(Locking Issue)。
理想情况下,布料的拉伸和弯曲应该是互不影响的。但在弹簧系统模型中,当弹簧非常刚硬(stiff)时,它可能会阻止布料在某些方向上的弯曲。例如,一个由两个三角形组成的布料,如果沿着对角线弯曲可能很自由,但若想沿着另一条边弯曲,中间的弹簧就会像支柱一样顶住,导致无法弯曲。
这种现象的本质是自由度丢失。对于一个流形网格,其自由度数量约为 3 + 边界边数量。当网格分辨率较低且弹簧非常刚硬时,约束(边)的数量几乎与变量(顶点坐标)数量相当,导致系统没有足够的自由度来表现弯曲,从而被“锁死”。
解决锁定问题没有完美方案,常见的工程性方法包括:
- 降低拉伸弹簧的弹性系数。
- 允许弹簧在一定长度范围内自由活动而不产生力。
- 提高网格分辨率(让顶点更密集)可以在视觉上缓解问题。
但需要注意的是,这些方法并不能从根本上解决数学模型本身的局限性。有限元方法同样存在类似的锁定问题。
约束方法:位置动力学(PBD)🎯
上一节我们探讨了模型本身的问题。本节开始,我们将学*一类强大的模拟方法——约束方法,它旨在稳定地模拟高刚度材料。
首先从单根弹簧的约束开始。我们希望弹簧保持原长 l0,即约束函数 C(xi, xj) = |xi - xj| - l0 = 0。
投影函数(Projection Function) 的目标是:找到新的顶点位置 xi', xj',在满足约束 C=0 的前提下,使位置变化量 Δx 尽可能小,同时保持质心不变。这可以转化为一个数学优化问题。
推导得到的投影公式为:
xi' = xi + (wi / (wi + wj)) * (|xj - xi| - l0) * ((xj - xi) / |xj - xi|)
xj' = xj - (wj / (wi + wj)) * (|xj - xi| - l0) * ((xj - xi) / |xj - xi|)
其中,wi, wj 是顶点的权重,通常与质量成反比。固定点可以通过设置极大质量来实现。
对于多根弹簧(多个约束),有两种主要的迭代处理方法:
- 高斯-赛德尔(Gauss-Seidel)方法:按顺序逐一处理每条边,立即更新其顶点位置,然后处理下一条边。处理完所有边后,再从头开始多次迭代,直至约束被较好地满足。这种方法简单,但更新顺序会影响结果,且难以并行化。
- 雅可比(Jacobi)方法:对每条边,计算其建议的顶点位置更新量,但不立即应用。对于每个顶点,收集所有相邻边对其的更新建议,然后取这些建议的*均值(或加权*均)来最终更新顶点位置。这种方法易于并行化,能减少顺序依赖带来的偏差。
基于投影思想,位置动力学(Position Based Dynamics, PBD) 算法流程如下:
- 对所有顶点进行常规的粒子运动更新(如显式积分):v = v + Δt * f_ext / m, x = x + Δt * v。
- 基于当前顶点位置 x,通过多次迭代(高斯-赛德尔或雅可比方法)应用约束投影,得到满足约束的新位置 x'。
- 根据位置变化更新速度:v = (x' - x) / Δt。
- 用 x' 覆盖 x。
PBD的特点:
- 优点:概念简单,易于实现和并行化,通用性强(可用于布料、流体、刚体等),在低分辨率模型上效率高。
- 缺点:缺乏明确的物理参数(如杨氏模量),材料的“刚度”由迭代次数和网格密度等非物理参数控制,没有收敛到精确解的理论保证,在高分辨率下效率下降较快。
PBD非常适合游戏等实时应用中对低精度可变形体的模拟。
约束方法:拉伸限制(Strain Limiting)📏
PBD缺乏物理含义,而拉伸限制(Strain Limiting) 可以看作是其与物理模拟结合的一个改进。它的核心思想是:在正常的物理模拟步骤之后,增加一个后处理步骤,将过大的形变“拉回”到可接受的范围内,从而增强模拟的稳定性。
对于弹簧,我们定义拉伸率 σ = l / l0。我们允许拉伸率在一个范围内 [σ_min, σ_max] 内变化。投影函数的目标不再是严格保持原长,而是将当前拉伸率 σ 修正到目标范围 clamp(σ, σ_min, σ_max) 内。
修正后的目标长度 l' = clamp(σ, σ_min, σ_max) * l0。然后使用与PBD类似的投影公式,将弹簧长度调整到 l' 而非 l0。当 σ_min = σ_max = 1 时,就退化成了PBD。
对于三角形面积约束,思路类似:计算当前面积A,将其修正到允许范围 [A_min, A_max] 内,得到目标面积 A'。然后通过缩放三角形(保持质心不变)来使面积接* A',缩放因子 s = sqrt(A' / A)。
拉伸限制的用途:
- 增强稳定性:防止物理模拟在发生大形变时出现数值不稳定、抖动甚至崩溃。
- 模拟非线性材料:例如某些布料,在小拉伸时很柔软,达到一定阈值后变得非常刚硬。可以在小形变时用物理模拟,大形变时启用拉伸限制来*似这种效果。
- 缓解锁定问题:通过允许一定范围内的形变,相当于降低了有效刚度,使弯曲更容易发生。
课程总结 🎓
本节课我们一起学*了布料模拟中的核心约束方法。
- 我们首先分析了弹簧系统在弯曲模拟上的不足,并介绍了二面角模型和基于余切权重的拉普拉斯弯曲模型。
- 然后,我们探讨了布料模拟中常见的锁定问题及其成因。
- 接着,我们深入学*了两种重要的约束求解方法:位置动力学(PBD) 和 拉伸限制(Strain Limiting)。PBD通过直接投影位置来满足约束,简单高效,适合实时应用;而拉伸限制则作为物理模拟的稳定器,将过大形变限制在合理范围内。
- 这些方法为处理高刚度材料和保证模拟稳定性提供了实用工具。
在下节课中,我们将继续探讨更高级的约束方法,如投影动力学(Projective Dynamics),它们试图在物理准确性和计算效率之间取得更好的*衡。





GAMES103-基于物理的计算机动画入门 - P7:Lecture 07 其他约束方法与有限元方法 I 🧵⚙️
在本节课中,我们将学*两种基于约束的高级模拟方法:Projective Dynamics 和 Constrained Dynamics,并初步了解有限元方法(FEM)的基本概念和线性三角形单元的力计算。
课程内容调整说明 📅
由于课程计划调整,本次课程将首先完成约束方法的讲解,然后开始介绍有限元方法。碰撞检测的内容将移至有限元方法之后讲解。
第一部分:Projective Dynamics 方法 🔄
上一节我们介绍了PBD和Shape Matching等约束方法。本节中,我们来看看Projective Dynamics方法,它同样基于约束,但构建方式有所不同。
Projective Dynamics的核心思想是:不直接用投影函数修改顶点位置,而是用投影结果定义一个能量函数。
方法原理
对于一个弹簧边约束,我们定义能量函数为:
E = (1/2) * k * ||(xi - xj) - (xi_new - xj_new)||^2
其中,xi_new和xj_new是根据约束投影计算出的“目标”位置,但我们不直接赋值。
经过推导可以发现,这个能量函数和力公式与普通弹簧系统完全一致:
E_spring = (1/2) * k * (||xi - xj|| - L)^2
F_spring = -k * (||xi - xj|| - L) * ( (xi - xj) / ||xi - xj|| )
那么,Projective Dynamics的特殊之处在哪里?关键在于其Hessian矩阵(刚度矩阵)。
常数Hessian矩阵的优势
在Projective Dynamics中,我们做了一个关键假设:投影得到的目标位置 x_new 是常数(与当前顶点位置 x 无关)。这使得能量函数 E(x) 成为顶点位置 x 的纯二次函数。
因此,其Hessian矩阵 H = d²E/dx² 是一个常数矩阵。对于一个由弹簧边构成的网格,其Hessian矩阵非常容易构造:
- 对角元:顶点度数 * k
- 非对角元(若顶点i和j有边相连):-k
常数Hessian矩阵带来了巨大优势:在隐式积分求解线性系统 (M - Δt² H) Δv = Δt F 时,我们只需要对矩阵 (M - Δt² H) 做一次LU分解或Cholesky分解。在后续所有时间步中,都可以复用这个分解结果,只需进行高效的回代求解,从而大幅提升CPU上的模拟效率。
方法总结
Projective Dynamics通过引入基于约束投影的中间变量,构造了一个具有常数Hessian矩阵的系统。其优点是在CPU上效率很高,且具有明确的物理含义(等同于弹簧系统)。缺点是收敛速度后期可能变慢,且在GPU上直接法求解不占优,约束改变时矩阵也需要更新。
第二部分:Constrained Dynamics 方法 🔗
接下来,我们探讨另一种处理强约束的方法——Constrained Dynamics。此方法常用于模拟多刚体系统(如人体关节)。
核心思路
该方法旨在处理刚度(Stiffness)极大甚至无穷大的约束。我们引入一个辅助变量——拉格朗日乘子 λ。
首先,将弹簧能量改写为关于约束 φ(x) = ||xi - xj|| - L 的形式:
E = (1/2) * φᵀ * C⁻¹ * φ
其中 C 是柔度矩阵(C = (1/k) I)。
通过计算力的梯度并引入 λ = -C⁻¹ φ,我们可以得到力的新形式:
F = -Jᵀ * λ
这里 J 是雅可比矩阵 J = dφ/dx。
构建求解系统
结合动量守恒方程和约束方程(在新状态下 φ_new = 0 的泰勒展开),我们得到一个包含两个变量(速度 v 和拉格朗日乘子 λ)的线性系统:



[ M -Δt Jᵀ ] [ v_new ] = [ M * v_old ]
[ Δt J C ] [ λ_new ] [ -φ ]
有两种求解方式:
- 联合求解(Primal-Dual):直接求解上述完整系统得到
v_new和λ_new。 - 消元求解:先将
λ消去,得到一个只关于v的系统(该形式与隐式积分系统等价),求解后再计算λ。
方法特点与应用
Constrained Dynamics 的优点是可以完美处理刚性约束(当 k → ∞ 时,C → 0,系统依然可解)。它广泛应用于多刚体动力学模拟,例如游戏中的布娃娃(Ragdoll)动画。
第三部分:有限元方法(FEM)初步 📐
现在,我们开始进入有限元方法的世界。有限元是模拟连续体(如弹性体、布料)形变的重要工具。
基本概念:线性三角形单元
我们以二维三角形单元为例。假设三角形在参考(未形变)状态的顶点为 X0, X1, X2,在当前(形变后)状态的顶点为 x0, x1, x2。
有限元的核心假设是:三角形内部的形变是均匀的,即内部任意点的位移映射可以通过一个线性函数描述:
x = F * X + c
其中 F 是一个2x2矩阵,称为形变梯度(Deformation Gradient);c 是*移向量。
计算形变梯度 F
利用三角形两条边的向量变化,可以计算出 F:
F = D_s * D_m⁻¹
其中:
D_s = [x1-x0, x2-x0]是当前状态的边向量矩阵。D_m = [X1-X0, X2-X0]是参考状态的边向量矩阵。
格林应变张量(Green Strain)
形变梯度 F 包含了旋转和形变。为了剔除旋转分量,只度量纯形变,我们使用格林应变张量 G:
G = (1/2) * (FᵀF - I)
G 是一个对称矩阵,其大小与形变成正比,且在物体纯旋转时为零。
能量与应力
我们定义单位面积的能量密度 W 为应变 G 的函数。对于三角形,总弹性势能为:
E = A_ref * W(G)
其中 A_ref 是三角形在参考状态下的面积。
一种常用的简单模型是Saint Venant-Kirchhoff模型:
W(G) = μ * trace(GᵀG) + (λ/2) * [trace(G)]²
其中 μ 和 λ 是拉梅参数,控制材料的剪切和体积变形阻力。
能量密度对应变的导数称为第二皮奥拉-基尔霍夫应力(Second Piola-Kirchhoff Stress) S:
S = dW/dG = 2μG + λ * trace(G) * I
计算顶点力
最终目标是计算作用在顶点上的力 F_i = -dE/dx_i。通过链式法则和一系列推导(此处省略繁琐的中间步骤),可以得到每个顶点力的简洁计算公式:
对于顶点1和顶点2(以边 X1-X0 和 X2-X0 为基准):
f1 = -A_ref * F * S * (D_m⁻¹)的列1
f2 = -A_ref * F * S * (D_m⁻¹)的列2
顶点0的力由力*衡得出:
f0 = -f1 - f2
这个矩阵形式公式极大简化了实现难度,避免了直接对复杂能量函数求导的繁琐过程。
总结 🎯
本节课我们一起学*了:
- Projective Dynamics:通过约束投影构造能量,得到常数Hessian矩阵以加速CPU求解。
- Constrained Dynamics:引入拉格朗日乘子处理强约束,常用于多刚体系统。
- 有限元方法初步:介绍了线性三角形单元的基本概念,包括形变梯度
F、格林应变G、能量密度W、应力S,并给出了计算顶点力的核心矩阵公式。

这些方法为模拟复杂物体提供了更多工具。下一节课,我们将继续深入有限元方法,并探讨另一种实现方式——有限体积法(FVM)。

GAMES103-基于物理的计算机动画入门 - P8:Lecture 08 有限元方法 II (Lab 3) 🏗️
概述

在本节课中,我们将继续学*有限元方法,并重点介绍一种与之等价但推导更清晰的有限体积法。我们将学*如何通过计算表面牵引力的积分来得到力,并理解不同应力定义(如柯西应力、第一/第二皮奥拉-基尔霍夫应力)之间的关系与转换。最后,我们会介绍超弹性材料模型,并简要探讨非线性优化在物理模拟中的应用。
作业说明:Lab 3
上一节我们介绍了有限元方法的基本概念,本节中我们来看看如何将其应用于实际模拟。课程作业(Lab 3)已经发布。
作业提供了一个Unity脚本和代码框架。你需要完成的核心任务是:使用有限元方法计算四面体网格形变产生的力,并更新顶点位置和速度,以模拟一个弹性小房子掉落到地面并弹起的动画。
以下是作业的关键点:
- 模型:使用名为“house”的四面体网格。
- 材料模型:采用St. Venant-Kirchhoff (STVK) 模型,因其公式相对简单。
- 积分方法:使用显式积分进行模拟。
- 注意事项:
- STVK模型无法处理四面体的反转(即顶点穿透到另一侧)。如果形变过大导致四面体反转,它将无法恢复。
- 显式积分存在稳定性问题。如果形变过于剧烈或时间步长不当,模拟可能会“爆炸”。
- 编程代码量可能是所有作业中最短的,但理解背后的原理需要仔细阅读课件。
核心挑战在于如何根据有限元理论计算每个四面体作用于顶点的力。理解这一点是完成作业的关键。
从有限元到有限体积法 🔄
上节课我们通过定义形变能,并对位置求导来得到力。本节我们将从另一个角度——有限体积法——来推导力,这对于四面体、三角形这类线性单元而言,与有限元法是等价的,且推导过程更清晰直观。
有限体积法的核心思想是:力来源于物体内部应力在边界表面的作用。
假设一个弹性体被一个界面分割为两部分。该界面上单位面积(或单位长度)所受的力称为牵引力。那么,整个界面上的合力就是牵引力在界面上的积分。
如何得到牵引力呢?这需要引入应力的概念。应力本质上是一个矩阵(二阶张量),它将表面的法向量映射为该点的牵引力。具体关系由以下公式描述:
traction = stress * normal
其中,traction是牵引力向量,stress是应力张量,normal是表面单位法向量。
因此,一个区域所受的合力,就是其边界上 stress * normal 的积分。
计算三角形对顶点的力贡献 📐
让我们具体到一个三角形单元。考虑顶点 x0,我们假设它代表周围的一个区域。那么,作用在这个区域上的合力,就是其边界上牵引力的积分。
对于三角形而言,x0 所代表的区域边界由两条从 x0 出发的边的中点连线构成。我们假设三角形内部的应力和形变是均匀的(线性单元假设),因此应力 σ 在三角形内为常数。
根据散度定理,对一个闭合曲线的法向量积分结果为零。因此,计算 x0 所受的力,可以转化为计算两条边(x0x1 和 x0x2 的中点连线)上 -σ * normal 的积分。
由于边上法向量恒定,积分简化为应力乘以法向量再乘以边长的一半。由此,我们得到三角形对顶点 x0 的力贡献公式:
f0 = - (σ * n10) * |x10|/2 - (σ * n20) * |x20|/2
其中,n10 和 n20 是两条边的单位法向量,|x10| 和 |x20| 是边长。
为什么取边的中点? 这是为了将三角形产生的力*均地分配到三个顶点上,满足动量守恒。
扩展到四面体情况 🔶
在三维情况下,我们需要处理四面体。一个顶点(如 x0)与三个面相邻。此时,力是应力在三个相邻面上的面积分。
与三角形类似,为了将每个面的贡献均匀分配到其三个顶点上,我们引入系数 1/3。顶点 x0 所受的力来自三个相邻面(如 012, 023, 031)的贡献之和。
经过推导和简化(利用叉积计算面积和法向量),我们可以得到四面体对顶点 x0 的力贡献的紧凑公式:
f0 = - (σ * (x20 × x30 + x30 × x10 + x10 × x20)) / 6
其中 × 表示叉积。类似地,可以计算 f1, f2, f3。
目前,公式中的应力 σ 尚未定义。接下来我们将解决这个问题。
理解不同的应力定义 🧮
这里的关键在于,上节课有限元法推导出的应力与本节有限体积法直接用来计算力的应力,并不是同一种应力。
它们的区别在于所作用的法向量和牵引力所处的空间不同:
- 参考配置:物体未形变时的状态。
- 当前配置:物体形变后的状态。
根据法向量和牵引力所处的配置不同,定义了多种应力:
- 第二皮奥拉-基尔霍夫应力:法向量和牵引力都定义在参考配置。上节课通过能量密度对格林应变求导得到的就是它。
- 第一皮奥拉-基尔霍夫应力:法向量定义在参考配置,牵引力定义在当前配置。
- 柯西应力:法向量和牵引力都定义在当前配置。这正是我们有限体积法公式中直接需要的应力。
我们需要找到一种方法,从上节课可计算的第二皮奥拉-基尔霍夫应力,得到本节需要的柯西应力。
应力之间的转换关系
不同应力之间可以通过形变梯度 F 进行转换。形变梯度描述了从参考配置到当前配置的局部线性变换。
以下是核心转换关系:
- 第一皮奥拉-基尔霍夫应力 P 与第二皮奥拉-基尔霍夫应力 S 的关系:
P = F * S - 柯西应力 σ 与第一皮奥拉-基尔霍夫应力 P 的关系:
σ = (1 / det(F)) * P * F^T
因此,结合两者,我们可以从已知的 S 计算出所需的 σ:
σ = (1 / det(F)) * F * S * F^T
应用于力计算公式
现在我们将柯西应力 σ 的表达式代入四面体的力计算公式中。但直接使用上述转换公式会引入复杂的计算。我们可以采用一个技巧:
观察四面体力公式 f0 = - (σ * (x20 × x30 + ...)) / 6,括号内的叉积项实际上与当前配置下的法向量有关。如果我们将其替换为参考配置下顶点位置 X 的叉积,那么对应的应力就应该使用第一皮奥拉-基尔霍夫应力 P。
这样做的好处是,参考配置下的顶点位置 X 是固定的,因此由它们计算的叉积项是常数,可以预先计算并存储,大大减少了实时模拟的计算量。
经过替换和整理(P = F * S),我们得到最终用于计算的实用公式:
f0 = - (F * S * B0) / 6
其中 B0 是一个基于参考配置顶点位置预先计算的常向量:B0 = X20 × X30 + X30 × X10 + X10 × X20。F 是形变梯度,S 是第二皮奥拉-基尔霍夫应力。
类似地,f1 = - (F * S * B1) / 6, f2 = - (F * S * B2) / 6。根据动量守恒,f3 = - (f0 + f1 + f2)。
算法伪代码

以下是模拟循环中计算一个四面体内力的核心伪代码:


// 预先计算(在初始化阶段)
Dm = matrix(x10, x20, x30) // 参考配置下的边矩阵
inv_Dm = inverse(Dm) // 其逆矩阵
B0 = X20 × X30 + X30 × X10 + X10 × X20 // 预计算常向量
B1 = ... // 类似计算 B1, B2
B2 = ...

// 每帧计算(对于每个四面体)
// 1. 计算形变梯度 F
Ds = matrix(x1-x0, x2-x0, x3-x0) // 当前配置下的边矩阵
F = Ds * inv_Dm
// 2. 计算格林应变 G 和第二皮奥拉-基尔霍夫应力 S
G = (F^T * F - I) / 2
S = dΨ/dG // 根据选定的能量密度函数 Ψ 计算,例如 STVK 模型
// 3. 计算第一皮奥拉-基尔霍夫应力 P
P = F * S

// 4. 计算各顶点力(以 f1 为例)
f1 = - (P * B1) / 6
// 类似计算 f2, f3
f2 = - (P * B2) / 6
f3 = - (P * B3) / 6
// 由动量守恒得到 f0
f0 = - (f1 + f2 + f3)
// 5. 将 f0, f1, f2, f3 分别加到对应顶点的合力上
超弹性材料模型 🧱
之前我们使用的 STVK 模型虽然简单,但有明显缺陷:当四面体被严重压缩或反转时,其产生的恢复力会变小甚至方向错误,导致无法恢复原状,模拟失真。
在图形学和力学中,更常用的是超弹性材料模型。这类模型通过定义能量密度函数 Ψ 来描述材料特性,应力由能量密度导出(S = dΨ/dG)。
对于各向同性材料(如橡胶,各个方向性质相同),其能量密度可以表示为形变梯度 F 的主拉伸 λ1, λ2, λ3 的函数。主拉伸是 F 经过奇异值分解后得到的对角矩阵元素,代表了三个主方向上的拉伸比率。
常见的各向同性超弹性模型有:
- 新胡克模型:
Ψ = μ/2 * (I1 - 3) + λ/2 * (J - 1)^2,其中I1是主拉伸*方和,J是体积变化率。它能更好地阻止体积压缩,避免 STVK 的反转问题。 - 穆尼-里夫林模型:新胡克模型的增强版。
- 冯·米塞斯模型:常用于模拟生物软组织。
在实现上,使用这些模型需要计算形变梯度 F 的奇异值分解以获得主拉伸,然后根据具体的 Ψ 公式计算应力 S。这比 STVK 复杂,但物理上更准确、更稳健。
非线性优化简介 ⚙️
物理模拟中的隐式积分等方法,最终需要求解一个非线性优化问题(即最小化系统总能量)。这里简要介绍其核心思想。
目标是找到变量 x 使函数 f(x) 最小化。基本思路是迭代:从初始点 x_k 出发,寻找一个下降方向 d_k 和合适的步长 α_k,使得 x_{k+1} = x_k + α_k * d_k 的函数值减小。
如何选择下降方向 d_k?
- 最速下降法:
d_k = -∇f(x_k),即负梯度方向。简单但收敛可能慢。 - 牛顿法:
d_k = -H^{-1} ∇f(x_k),其中H是海森矩阵(二阶导数)。利用了曲率信息,收敛快,但需计算并求解海森矩阵,代价高。 - 拟牛顿法/投影法:用*似矩阵
M代替海森矩阵H,d_k = -M^{-1} ∇f(x_k)。在收敛速度和单步计算成本之间寻求*衡,在图形学中应用广泛。
如何确定步长 α_k?常用回溯线搜索:从一个初始步长开始,不断按比例缩小,直到满足函数值充分下降的条件。
在物理模拟中,我们追求的是总计算时间 = 每次迭代耗时 × 迭代次数 的最小化。图形学的研究重点常在于为特定硬件和问题设计高效的优化方法,以在速度和精度间取得最佳*衡。
总结
本节课我们一起深入探讨了有限元方法的另一种视角——有限体积法。
- 我们学*了如何通过积分表面牵引力来计算顶点力,并推导了三角形和四面体的具体公式。
- 我们厘清了柯西应力、第一/第二皮奥拉-基尔霍夫应力等不同应力定义的区别与联系,并掌握了它们之间的转换方法,从而得到了实用的力计算公式。
- 我们认识了 STVK 模型的局限性,并介绍了更通用、更稳健的超弹性材料模型(如新胡克模型)。
- 最后,我们简要了解了非线性优化的基本概念,它是许多高级模拟方法(如隐式积分)背后的数学工具。

希望本讲内容能帮助你更好地完成 Lab 3 作业,并加深对基于物理的动画中弹性体模拟的理解。下节课我们将进入一个新的重要主题:碰撞检测与处理。

GAMES103-基于物理的计算机动画入门 - P9:Lecture 09 碰撞处理 🎯
概述
在本节课中,我们将学*计算机动画中一个至关重要且实践性极强的主题:碰撞处理。我们将从如何检测碰撞开始,逐步深入到如何处理碰撞响应,涵盖离散与连续碰撞检测、内点法与冲击优化等核心方法,并探讨在复杂情况(如布料自碰撞)下的解决方案。
作业技巧与有限元模拟调试 🛠️
上一节我们结束了关于弹性体有限元的讨论。在进入新主题前,先针对上周布置的作业提供一些调试技巧。
作业提供了一个包含约1000个四面体的房屋模型。直接模拟整个模型难以调试,因为一旦结果出错,很难定位问题所在。
以下是调试建议:
- 从单个四面体开始测试:不要直接运行房屋模型。首先尝试使用一个单独的四面体进行模拟。
- 测试静止状态:关闭重力,让四面体处于静止状态。此时,形变梯度
F应为单位矩阵I。由此计算出的格林应变ε应为零矩阵0,进而应力σ和力f也应为零。这可以验证你的力计算在*衡状态下是否正确。- 公式:
F = I->ε = 0->σ = 0->f = 0
- 公式:
- 测试拉伸状态:将四面体沿某个方向(如X轴)拉伸。弹性体的力总是阻碍形变的,因此每个顶点上的力理论上应试图将四面体拉回原状。检查力的方向是否符合这个趋势。
- 操作方法:通过缩放四面体顶点的位置来实现拉伸。
- 关于性能:在Unity中运行此类模拟帧率可能较低,这受脚本执行和硬件配置影响。纯粹的CPU实现通常效率更高。四面体有限元模拟在游戏开发中通常只适用于分辨率很低的情况。
关于作业的其他问题:
- 速度场*滑:可对速度场进行拉普拉斯*滑,即每个点的速度与其邻居点的速度取加权*均。
- SBD的力公式:建议自行推导验证,确保公式正确性。
- 模型中的三角网格:用于从四面体网格构造渲染表面,若无直接用途可忽略。
碰撞处理概述 🤔
本节我们将开始讨论碰撞处理这个新话题。碰撞处理的概念相对简单,但要想实现得鲁棒高效,算法复杂度非常高,需要处理各种边界情况。
本节课的目标是为大家建立基础概念,以便未来需要时能更快地上手。课程将分为以下几个部分:
- 碰撞检测:如何发现碰撞。
- 碰撞响应方法:介绍两种主流方法——内点法和冲击优化法。
- 离散碰撞处理:介绍相交解除方法。
布料碰撞是所有碰撞处理中最困难的。若能处理好布料碰撞,其他碰撞问题便可视为其简化版。
碰撞检测 🔍
处理碰撞的第一步是知道碰撞是否发生。碰撞检测通常分为两个阶段,以提高效率。
广义阶段碰撞剔除
当场景中有大量元素(如数万个三角形)时,对所有元素进行两两碰撞检测的计算量是*方级的,无法承受。因此,第一阶段(Broad-phase Collision Culling)的目的是剔除那些绝不可能发生碰撞的元素对,输出一组候选碰撞对。
以下是两种常见的广义阶段剔除方法:
1. 空间划分法
核心思想是将整个3D空间划分为均匀网格。每个三角形根据其位置(或运动轨迹所占用的空间)被存储到与之相交的网格单元格中。
检测流程:
- 对于每个三角形,找出它占据的所有网格单元格。
- 要判断某三角形可能与哪些其他三角形相交,只需检查它所在单元格内存储的所有其他三角形。
- 将这些三角形对作为候选对输出。
优点:实现相对简单,对GPU友好。
缺点:需要为整个空间预分配网格内存,可能造成浪费;物体运动后需要更新网格信息。
优化方案——空间哈希与排序:
为了节省内存,不直接分配网格数组,而是为每个“三角形-单元格”关联创建一个记录(如 (cell_id, tri_id))。然后对所有记录按 cell_id 排序。排序后,连续且 cell_id 相同的记录就代表了该单元格内所有的三角形。这避免了为空单元格分配内存。
进一步优化——莫顿码:
在三维空间中,按行、列、层顺序访问网格会导致内存访问不连续(尤其在访问空间邻居时)。莫顿码是一种空间填充曲线,它将多维空间索引编码成一维,使得空间上相邻的单元格在内存地址上也更接*,从而优化缓存访问效率。
2. 包围体层次结构
核心思想是对物体本身进行层次划分,而非对空间进行划分。例如,一个机器人模型可以按肢体划分成多个部分,每个部分用一个简单的包围体(如轴对齐包围盒AABB、球体)包裹。
检测流程(以BVH树为例):
- 从根节点开始,检查两个包围体是否相交。
- 若不相交,则其下所有子元素都不可能相交,整条分支可剔除。
- 若相交,则递归地检查它们的子节点。
- 对于自碰撞检测,需要额外检查同一节点下不同子节点之间的包围体是否相交。
优点:更新效率高(只需更新包围体),适合结构化的、运动连贯的物体。
缺点:实现较复杂;树形结构对GPU不友好;对于密集*邻的剔除效率不如空间划分法。
其他方法:有研究尝试用形变能量的大小来预测碰撞可能性(形变越大,碰撞可能越高),但对布料等常有大形变却无碰撞的情况效果有限。
方法对比总结:
| 特性 | 空间划分法 | 包围体层次法 |
|---|---|---|
| 实现难度 | 较简单 | 较复杂 |
| GPU友好度 | 友好 | 不友好 |
| 更新成本 | 高(需重建) | 低(更新包围体即可) |
| 适用场景 | 元素分布均匀/随机 | 物体结构性强、层次清晰 |
狭义阶段碰撞检测 🎯
经过广义阶段剔除,我们得到了一批候选碰撞对。在狭义阶段(Narrow-phase Collision Detection),我们需要对这些候选对进行精确的检测,判断它们是否真的发生了碰撞或相交。根据后续处理方式的不同,检测也分为两类。
离散碰撞检测
DCD检测的是在某个特定时刻,几何体之间是否相交(即穿透),而不关心运动过程。
基本操作:对于三角形网格,最基本的是检测边与三角形是否相交。
- 计算边与三角形所在*面的交点参数
t。 - 判断该交点是否位于三角形内部。
- 若是,则报告相交。
优点:计算简单、快速、稳定。
缺点:可能产生隧道效应。如果物体运动速度过快,可能在两帧之间直接穿过另一个物体,而在这两个离散时刻都未检测到相交。这对于薄物体(如布料、兔耳朵)尤为严重。
连续碰撞检测
CCD检测的是在两个时刻之间的运动过程中,几何体是否发生了碰撞。它关注的是第一个接触点,而非穿透状态。
基本元素:对于运动的三角形网格,基本的连续碰撞检测有两种:
- 点-三角形检测:一个运动的点与一个运动的三角形。
- 边-边检测:两条运动的边。
以点-三角形检测为例:
假设四个点都在做线性运动。碰撞发生时,这四个点共面,即它们构成的四面体体积为零。根据体积公式,可以推导出一个关于时间 t 的一元三次方程。
- 公式推导:
Volume = ( (x1(t)-x0(t)) × (x2(t)-x0(t)) ) · (x3(t)-x0(t)) = 0,展开后得到a*t^3 + b*t^2 + c*t + d = 0。
解此方程得到可能的碰撞时间,再验证该时刻点是否落在三角形内部。
为什么需要边-边检测?因为两个三角形可能只在边上发生碰撞,而点-三角形检测无法捕捉这种情况。
CCD的挑战:
- 数值稳定性:求解一元三次方程时,应避免使用求根公式(浮点误差大),推荐使用数值方法(如二分法、牛顿法),并只关注时间区间
[0, 1]内的根。 - 计算成本:比DCD高,因为需求解方程。
- 实现复杂度:高,容易遇到各种边界情况。
尽管CCD计算更复杂,但在广义阶段剔除大部分元素后,其成本通常可以接受。CCD是避免隧道效应、实现高质量碰撞的必要手段。
碰撞响应:内点法与冲击优化法 ⚖️
当我们检测到碰撞(或相交)后,下一步就是处理它,将系统修正到一个无碰撞的状态。假设模拟后得到了一个有碰撞的状态,而我们的目标是安全区域(无碰撞状态)。有两种主流的数学思路来解决这个问题。
内点法
核心思想:从安全区域内(上一帧的无碰撞状态)出发,采取小步长迭代,确保每一步迭代后的中间状态都保持在安全区域内,最终逼*模拟目标附*的无碰撞解。
类比:小心翼翼地从安全区走向目的地,绝不踏出安全区边界。
优点:
- 必然成功:即使未完全收敛,只要在安全区内停止,结果就是可接受的(无碰撞)。
- 数值稳定。
缺点:
- 速度慢:需要小步长保证安全,且可能迭代步数很多。
- 计算量大:每步都需要进行碰撞检测以确保安全。
实现示例——对数障碍函数:
在能量函数中添加一个关于距离的对数障碍项。当距离趋*于零时,该项产生的排斥力趋*于无穷大,从而防止穿透。图形学中常会进行截断以限制影响范围。
- 优化目标:
min ||x - x_target||^2 + λ * Σ -log(d_i(x)),其中d_i(x) > 0为距离约束。
冲击优化法
核心思想:胆子更大。直接从模拟得到的不安全状态出发,通过优化或投影操作,试图一步或几步将其拉回安全区域,不关心中间过程是否安全。
类比:从目的地(不安全)直接跳回或拉回安全区。
优点:
- 速度快:通常碰撞只发生在局部,可以集中计算资源进行优化,步长可以较大。
- 高效:只处理出问题的区域。
缺点:
- 可能失败:如果初始状态离安全区太远,可能无法收敛到无碰撞解。
- 不保证严格无碰撞:结果可能仍有轻微穿透。
实现方式:通常构建为一个约束优化问题,使用拉格朗日乘子法、增广拉格朗日法等求解。例如,检测到穿透时,施加一个冲量将物体推开。
刚性冲击法
当上述方法都失败或过于复杂时,一种“最后一搏”的方案是刚性冲击法。它将发生复杂碰撞的整个区域锁定为一个刚体,禁止其内部相对运动,从而从根本上避免继续碰撞。
优点:简单、安全、绝对有效。
缺点:会引入明显的视觉瑕疵,失去局部形变细节。
实际工作流建议:
- 首先尝试快速的冲击优化法。
- 若失败,且计算资源允许,则回退到鲁棒但较慢的内点法。
- 若仍无法解决或需要实时性能,则对碰撞区域启用刚性冲击法作为保底。
碰撞响应:相交解除 ✂️
对于某些应用(如游戏、CAD软件),目标可能不是物理上精确的碰撞响应,而是快速消除视觉上的穿插。这就是“相交解除”的思路。
核心思想:承认当前帧可能存在相交,并采用一种后处理技术,直接修改顶点位置来消除穿插,而不严格考虑物理正确性或运动过程。
对于有体积的物体
这相对简单。常用符号距离场(SDF)来表示物体体积。对于每个发现穿透的点,计算其到SDF表面的最短方向,并将其沿该方向推出表面即可。
- 之前作业中的球与布料碰撞即采用此思路。
对于布料自碰撞
布料是开放的曲面,无法定义内外,因此SDF方法失效。需要更复杂的算法。
思路一(2003年):将相交的布料区域分段,假设面积较小的片段是穿插产生的“多余”部分,并设法将其移除或*滑。但该方法难以处理边界处的穿插。
思路二(2006年):将布料间的相交视为一条空间曲线。通过优化,最小化这条相交曲线的长度。当曲线长度为零时,穿插即被解除。该方法能更好地处理边界情况,但也不是万能的。
相交解除是一种实用的、面向视觉效果的策略,常作为物理碰撞响应失败后的补充或替代方案。
总结与课程预告 📚
本节课我们一起学*了碰撞处理的完整流程:
-
碰撞检测:
- 广义阶段:通过空间划分或包围体层次结构剔除不可能碰撞的元素对,提高效率。
- 狭义阶段:
- 离散碰撞检测:检测特定时刻的相交,快速但可能有隧道效应。
- 连续碰撞检测:检测运动过程中的碰撞,精确但计算复杂,需解一元三次方程。
-
碰撞响应:
- 内点法:从安全区出发,步步为营,保证中间状态安全,鲁棒但慢。
- 冲击优化法:直接修正不安全状态,高效但可能失败。
- 刚性冲击法:将碰撞区域冻结为刚体,简单粗暴的保底方案。
- 相交解除:以消除视觉穿插为目标的实用后处理技术,尤其适用于布料等复杂情况。
碰撞处理是一个概念易懂但实现艰难的领域,充满了算法细节和工程挑战。理解这些基本方法为应对实际开发中的碰撞问题奠定了重要基础。

下周预告:我们将开启全新的主题——流体模拟。课程将分为三部分:基础介绍、基于网格的欧拉方法、以及基于粒子的SPH方法。

GAMES105-计算机角色动画基础 - P1:Lecture01 角色动画导论 🎬

在本节课中,我们将要学*计算机角色动画的基础概念、主要技术分类以及该领域的发展脉络。课程旨在为初学者提供一个清晰的概览,理解如何让虚拟角色“动”起来。
课程与讲师介绍
大家好,欢迎参加GAMES105课程。本课程名为“计算机角色动画基础”。与其他GAMES系列课程类似,我们主要关注三维计算机角色动画,而非二维动画。课程会涉及二维内容,但核心研究对象是三维角色动画。
我是刘立宾,现任北京大学智能学院助理教授。我的研究方向主要是基于物理的角色动画,特别是如何控制角色运动。在加入北大之前,我曾在迪士尼研究院工作。右下角的二维码是我们北京大学可视计算实验室的公众号,欢迎大家关注,我们会通过公众号发布相关活动信息。
课程主页位于GitHub。上课时间为每周一晚上八点到九点,持续约12周,从今天开始到12月底或1月初结束。
前置要求:学*本课程需要了解线性代数、微积分和基本的编程技巧。课程作业将主要使用Python完成,我们会提供简单的代码框架。作业提交将通过专门的网站进行,课程邀请码已显示在屏幕上。此外,我们有一个课程交流群,二维码长期有效。
什么是计算机角色动画?
首先,我们需要明确什么是计算机角色动画,以及我们关注其中的哪些内容。
提到角色动画,大家可能会想到动画电影,如《疯狂动物城》、《寻梦环游记》等。虽然动画电影是重要的应用领域,但当前电影制作中使用的计算机角色动画技术比例其实很小。本课程讲解的许多技术尚未在电影中广泛应用,但随着AI生成技术的进步,未来可能会有更多应用。
实际上,角色动画技术应用最广泛的领域是游戏。因为游戏需要根据用户输入实时生成可交互的内容,这与一次性渲染的电影不同。游戏的需求直接推动了角色动画技术的进步与发展。
*年来,我们也看到许多新应用,例如虚拟偶像、虚拟主播、VR社交、VR游戏,以及大场景人群仿真等。这些都属于计算机图形学的组成部分。
角色动画的核心组成部分
如果想让电影中的一个角色运动起来,至少涉及三个领域:建模、动画和渲染。
- 建模 关注静态外观或某一帧的表现。
- 渲染 负责将外观展示出来。
- 动画 则解决相邻帧之间如何生成的问题,它建模的是时间序列上的规律。
这与物理仿真有显著不同。物理仿真(如刚体、软体、流体仿真)的目标是模拟客观物理现象的时间演变。而角色动画更关注行为建模,例如人或动物的动作。
两者之间存在联系。在我看来,物理仿真加上控制,就构成了动画。纯粹的物理仿真是被动的,而控制引入了主观意愿。物理仿真通常有精确的数学描述(如微分方程),而动作行为很难用单一数学公式完全描述,往往需要通过大量观察进行统计建模。
角色动画关注的对象是“角色”,通常具有数十到上百个关节参数。理论上,几乎所有动作都可以由动画师手动“K帧”(逐帧调整姿态)完成,从而得到高质量动画。但这使得角色动画在需要交互的场景(如游戏)中成为一种劳动密集型技术。
因此,计算机角色动画的研究目标,是理解人和动物产生动作的规律并建立模型。一方面,这能提供更智能的动画编辑工具;另一方面,可以生成全新的动作。简而言之,我们的目标是将角色动画从劳动密集型转变为计算密集型。
角色动画的生成流程
如何生成一段动画或让一个虚拟形象动起来?通常流程如下:
- 几何建模:获得虚拟形象的模型(通过雕刻、扫描等方式)。
- 骨骼绑定:将模型绑定到骨骼上。这涉及骨骼设置和计算蒙皮权重。
- 运动生成:驱动骨骼运动,从而带动外部模型形变。如何生成骨骼的运动,是本课程主要关注的内容。
在真实世界中,人做一个动作(如伸手)的过程是:大脑想法 -> 神经信号 -> 肌肉脉冲 -> 肌肉收缩 -> 施加力/力矩于骨骼系统 -> 在物理规律下产生运动。
计算机角色动画技术可以根据是否对物理过程进行建模来区分。

两大技术分类:运动学 vs. 动力学

基于运动学(关键帧)的方法
这类方法不模拟物理过程,而是隐含了从意图到姿态的转换。它直接更新角色的状态(如姿势、速度)。角色可以实现瞬移等不符合物理规律的动作。这是工业界应用非常广泛的一类方法。
基于动力学(物理)的方法
这类方法希望复现真实的物理过程。它会对物理仿真进行建模,通过仿真生成最终动作。在此过程中,不能直接干预角色姿态,因此瞬移并非其目标。

总结:角色动画方法大致分为两类:使用物理仿真的基于物理的角色动画,和不使用物理仿真、直接改变姿态的基于运动学(关键帧)的方法。
控制维度:低级与高级

控制角色动作还可以从不同维度进行:
- 低级控制:像动画师一样,逐帧调整每个关节的旋转。优点是可以精确控制每个细节;缺点是效率低、成本高。
- 高级控制:给角色一个高级目标(如“去拿杯水”),角色自动生成完整动作序列。优点是用很少的信息生成复杂动画;缺点是无法精确控制细节。

角色动画的发展趋势,正是从低级控制逐渐向高级控制过渡。

接下来,我们将简要介绍角色动画各个方向的基本技术。

基于运动学的方法

上一节我们介绍了角色动画的两大分类,本节中我们来看看基于运动学(关键帧)方法的具体技术。

关键帧动画与迪士尼12准则
关键帧动画是最基础的动画技术。早在计算机图形学出现之前,动画师就通过逐帧绘制来制作动画(如1917年的《猫和老鼠》)。他们总结出了著名的迪士尼动画12准则,部分准则是为了模仿真实物理过程,部分则与艺术夸张和叙事相关。


在三维计算机图形学时代,同样的技术可以应用于三维领域。动画师使用Maya、Blender等软件,一帧一帧地调整角色姿态,连接起来形成动画。制作高质量的关键帧动画需要相当的训练和技术。

从算法角度看,有两个非常重要的技术:
前向运动学与逆向运动学
- 前向运动学:给定每个关节的旋转角度,计算末端(如手)的位置。
- 公式:
末端位置 = FK(关节1角度, 关节2角度, ...)
- 公式:
- 逆向运动学:给定末端(如手)的目标位置,反推需要改变每个关节多少旋转角度。
- 公式:
(关节1角度, 关节2角度, ...) = IK(末端目标位置)
IK是角色动画中的关键技术,它让姿态调整变得直观。
- 公式:

补间动画
制作关键帧动画时,通常只制作几个关键姿态,然后通过算法自动生成中间的过渡帧,即补间动画。基本思路是使用不同的插值方法来生成*滑的动画。

然而,关键帧动画需要逐帧控制,是一种非常低效的低级控制方法。
动作捕捉与数据重用

为了解决关键帧动画效率低的问题,动作捕捉技术被广泛应用。
动作捕捉技术
- 光学动捕:使用专用设备(如Vicon),在身体上贴反光标记点,通过多视角相机捕捉。质量高,用于电影制作。
- 惯性动捕:使用便携的惯性传感器。价格更亲民,常用于虚拟偶像直播。
- 视频动捕:从普通视频中估计动作。使用范围广,但目前质量难以与前两者相比。
无论哪种动捕,都会面临一个问题:如何将捕捉到的动作映射到不同的虚拟角色上?这需要动作重定向技术。
动作重定向


动作重定向是将一个动作适配到骨骼结构、尺寸可能完全不同的新虚拟角色上的技术。
动作捕捉本质上只是记录和回放动作,无法生成新动作。为了在交互式应用(如游戏)中重用数据,产生了以下技术:
状态机与运动图


一个简单的想法是使用状态机。例如,角色有“奔跑”和“攻击”两个动作,用户按下攻击键时,从奔跑状态切换到攻击状态,再切回奔跑。大部分游戏引擎都支持状态机模型。


学术上,2002年提出的运动图技术与此类似。它从运动数据中自动构建状态机,允许在不同动作片段间切换。后续工作对其进行了改进,例如在状态节点内进行动作插值以实现更精确控制,或在其上训练AI来完成更高级任务。



运动图的优点是能重用现有数据生成新运动。缺点是构造复杂,随着动作数量增加,图会变得非常庞大且容易出错。
运动匹配
运动匹配是育碧公司在2016年左右提出的技术。其核心思想不是播放完整动作片段,而是在更细粒度(如每一帧)进行控制。每一帧结束时,通过最*邻搜索找到一个新姿态,该姿态既要满足控制目标(如移动方向),又要与当前状态连贯。
运动匹配很大程度上是工程实现,需要精心设计损失函数和动作库。它简单实用,现已集成到一些游戏引擎中。它同样解决了运动图的难点,并能提供高级控制。

生成模型与跨模态生成
我们提到,角色动画的目标是理解动作的内在统计规律。生成模型为计算机角色动画带来了巨大进步。
生成模型
生成模型可以从大量动作数据中学*规律,根据用户输入自动生成下一帧姿态。例如,可以学*控制指令(如摇杆输入)与动作之间的关系。


常见的生成模型包括GAN、VAE、标准化流,以及最新的扩散模型。这些模型只需收集原始动作数据(如让人随意走动两小时)进行训练即可生成动作,而传统方法(如运动图)需要手动分割和构建状态机。



跨模态生成



当前的研究前沿是实现跨模态生成,即用高级的、其他模态的指令来控制角色动作。
- 音乐生成舞蹈:输入一段音乐,生成对应的舞蹈动作。
- 语音生成动作:输入一段语音(如演讲),生成对应的肢体语言和动作。
- 文本生成动作:输入一句话,生成与之匹配的动作。
这非常接*我们“通过高级控制生成复杂动作”的原始目标。
基于运动学方法的局限性

尽管基于运动学的方法取得了很大进展,但它仍有相当大的局限性:
- 物理准确性不足:容易产生穿模、脚底打滑等问题。
- 动作范围受限:本质上是现有动作的重放或组合。当环境变化需要全新的交互时(如被绊倒、完成危险特技),如果动作库中没有类似数据,则很难生成。

为了解决这些问题,我们需要回到基于物理仿真的方法。


基于物理仿真的方法

上一节我们探讨了基于运动学方法的优势与局限,本节我们将深入基于物理仿真的角色动画。
基于物理的方法旨在在虚拟世界中复现真实世界产生动作的完整过程。回想一下,基于运动学的方法是:输入信号 + 当前姿态 -> 运动模型 -> 下一帧姿态。而基于物理的方法多了一步:运动模型输出的不是直接姿态,而是控制量(如力),这些力通过物理仿真最终改变姿态。
布娃娃仿真
最基本的物理仿真应用是布娃娃仿真,即关闭角色的控制,只让其在物理规律下运动。这常用于表现角色死亡、失去意识或被绊倒瞬间的反应。此时生成的动作是无意识的。

物理仿真的应用场景


- VR/AR交互:在VR环境中,用户可能从任意方向击打角色,只有物理仿真能生成真实的受力反馈。
- VR社交:在只有头显和手柄的情况下,通过物理仿真可以合理估计出下半身的运动,实现全身虚拟形象。
- 精细操作:如使用筷子,物理仿真能逼真地生成手指的细微动作。
物理控制的基本技术:PD控制
在物理仿真中,我们通常将角色简化为由关节力矩驱动的多刚体系统,而非复杂的肌肉模型。
一类常用方法是PD控制。例如,我想把手从A高度举到B高度,需要施加多少力?一个粗略的计算是:力 = k_p * (目标位置 - 当前位置) + k_d * (目标速度 - 当前速度)。通过给出目标轨迹并使用PD控制,可以生成关节力矩,驱动角色运动。
这项技术很早(1995年)就已出现,但至今未在游戏中大规模应用,主要原因在于控制非常困难。
轨迹优化与简化模型

为了设计出合理的控制轨迹,产生了其他方法:
- 轨迹优化/时空优化:通过数学优化自动计算出合适的控制轨迹。它允许高级输入(如指定脚部落点),但求解高维非线性优化问题非常困难且缓慢。
- 简化模型:借鉴机器人学思路,使用高度简化的模型(如倒立摆)来描述和指导动作。这种方法非常稳定,能抵抗外力干扰,但生成的动作缺乏细节,且通常只针对特定动作(如行走)设计,难以实现复杂动作(如后空翻)。

强化学*
强化学*为物理角色动画带来了突破。智能体通过与环境不断交互(试错)来更新运动策略,最终学会某种动作。2015年DeepMind在雅达利游戏上的突破鼓舞了该方向。
通过强化学*,可以实现运动跟踪控制,即在物理仿真环境下复现给定的运动数据。在此基础上,可以进一步结合状态机、运动匹配等思想,完成更复杂的技能组合。
控制生成模型
与基于运动学的生成模型类似,在物理仿真领域,我们可以学*控制生成模型。它学*的是角色在产生大量数据时,其控制策略的规律。我们可以在这个生成模型的状态空间中进行采样,每个采样都会产生不同的动作,同时也能响应高级指令和环境干扰。
基于生成模型的物理控制,正在将基于物理的角色动画推向“通过高级控制生成动作”的目标。未来,跨模态生成(如语言、音乐驱动)也有望在物理仿真中实现。
课程总结与展望
本节课我们一起回顾了计算机角色动画领域过去30年的主要研究方向。
基于运动学的方法(如运动匹配、生成模型)已逐渐在真实场景中落地。而基于物理的方法,虽然30年来一直被寄予厚望,但直到最*,随着控制生成模型等技术的出现,才真正接*可用的状态。我们可能正处在一个转折点,基于物理的方法有望在虚拟世界中更真实地还原动作产生过程。
课程安排与信息
最后,重申一下本课程的相关信息。
课程定位:本课程不会教授具体软件(如Maya、Unreal Engine)的使用,也不会培养动画师。我们将从科学和技术的角度,探讨角色动画背后的方法、理论和算法,特别是角色运动学、动力学、物理仿真与控制。最终目标是让大家能自己实现一个可交互的虚拟角色。
课程大纲:共12次课。今天为第一次。后续将从基础数学开始,先介绍基于运动学的方法,中间涉及蒙皮绑定基础知识,后半部分专注于物理仿真与基于物理的角色动画。大纲可能根据反馈调整。
课程项目:共有5个小项目,其中4个关于关节角色动画(每2-3周一个),1个关于模型绑定。
实现方式:所有项目主要使用Python实现。我们会使用一些Python下的物理引擎,并在其上实现仿真、控制和动画效果。
前置课程:本课程内容自成体系,但建议大家有时间可以学*前面的GAMES系列课程(101-104),它们提供了计算机图形学、几何、建模、仿真和游戏引擎方面的良好基础。本课程会在特定点上进行更深入的探讨。
课程资料:本课程没有指定教材,主要资料是课件和授课PPT。对物理仿真或机器人学相关背景的了解会对学*有所帮助。
好的,我们今天的课程就到这里。下周我们将从数学基础和前向运动学相关内容开始讲起。谢谢大家,我们下周再见!



GAMES105-计算机角色动画基础 - P10:Lecture09 驱动仿真角色 🎮
在本节课中,我们将学*如何驱动一个物理仿真中的虚拟角色,使其产生我们期望的动画。我们将从回顾刚体仿真的基本概念开始,然后深入探讨如何通过施加力和力矩来控制角色,并重点介绍一种基础且重要的控制方法——比例微分(PD)控制。
回顾:刚体仿真基础
上一节我们介绍了如何对一个虚拟角色进行物理仿真。在计算机角色动画中,我们处理的虚拟角色通常由不会变形的刚性肢体(刚体)通过关节连接而成。
为了计算这样一个系统的运动,我们需要求解其运动方程。对于一个质点,牛顿第二定律 F = m * a 描述了力、质量和加速度的关系。通过积分加速度,我们可以更新质点的速度和位置。
然而,刚体比质点更复杂,因为它具有形状和朝向。朝向(旋转)是一个非线性量,这带来了计算上的挑战。刚体的运动状态由位置、朝向、线速度和角速度共同描述。
为了改变刚体的运动,我们需要施加外力(F)和外力矩(τ)。线力改变线速度,力矩改变角速度。刚体的“惯性”由质量(m,抵抗线运动变化)和惯性张量(I,抵抗旋转运动变化)描述。惯性张量与物体的质量和形状分布有关。
多个刚体通过关节连接时,关节会施加约束力,确保连接点不会分离。整个多刚体系统的运动方程可以写成一个紧凑的矩阵形式:
M(q) * v̇ + C(q, v) = τ_ext + J_c^T * λ
其中 M 是质量矩阵,q 和 v 是状态和速度,C 包含科里奥利力和离心力,τ_ext 是外部广义力(包括我们施加的控制力),J_c^T * λ 代表约束力。
在物理引擎中定义角色时,我们需要为每个刚体提供质量、惯性张量、初始状态(位置、朝向、速度、角速度)以及用于碰撞检测的简化几何形状(如胶囊体、立方体)。同时,需要定义连接刚体的关节类型和初始位置。
仿真器的核心流程是一个前向动力学过程:根据当前状态和所有外力(包括控制力和约束力),求解运动方程得到加速度,然后通过积分更新速度和状态,从而得到下一时刻的动作。
如何驱动角色:施加力与力矩
上一节我们介绍了仿真系统如何运作,本节中我们来看看如何主动地驱动角色运动。如果不对角色施加任何控制力,它只会在重力作用下瘫倒在地,这种状态称为“布娃娃”(Ragdoll)仿真。
为了让角色按照我们的意愿运动,我们需要施加控制力。对于单个刚体,我们可以在其质心上施加合力和合力矩。对于由关节连接的多刚体系统(如人形角色),有两种主要的驱动方式:
- 直接对每个刚体施加力和力矩:这种方式自由度高,但控制复杂。
- 施加关节力矩:这是更常见和直观的方式。关节力矩模拟了生物关节处肌肉或机器人关节处电机产生的扭矩效果。
关节力矩的本质:当我们在一个关节上施加力矩 τ 时,其物理效果等价于在该关节连接的两个刚体上分别施加一个大小相等、方向相反的力矩。通常,在子刚体上施加 +τ,在父刚体上施加 -τ。这样,两个刚体会产生相对的旋转趋势,而整个系统的总力矩和为零,不会影响系统整体的质心运动。
控制器的作用:控制器是一个模块,它根据角色当前的状态(位置、速度等)以及我们期望的运动目标(如目标姿势或路径),实时计算出每个关节需要施加的力矩 τ,然后交给仿真器执行。这个过程可以看作是一个逆向动力学问题:已知期望的运动(或加速度),求解需要施加的力。
核心控制策略:比例微分(PD)控制 🎯
为了计算具体的关节力矩,我们需要一个控制策略。比例微分控制是一种基础、强大且广泛应用的反饋控制方法。
什么是比例微分控制?
我们可以通过一个简单例子来理解:控制一个沿竖直杆滑动的物块,使其到达并停留在目标高度 x_d。
- 比例(P)控制:施加的力与当前位置和目标位置的误差成正比。
F_p = k_p * (x_d - x)。k_p称为刚度系数。误差越大,施加的力越大。 - 问题:仅使用比例控制,物块会在目标位置附*持续振荡,无法稳定停下。
- 微分(D)控制:引入与速度成正比的阻尼力。
F_d = -k_d * v。k_d称为阻尼系数。速度越大,反向的阻尼力越大,用于消耗能量、抑制振荡。 - 比例微分(PD)控制:结合两者。
F = k_p * (x_d - x) - k_d * v。
对于关节控制,公式是类似的。对于某个关节,我们希望其角度 θ 跟踪目标角度 θ_d:
τ = k_p * (θ_d - θ) - k_d * θ̇
其中 τ 是需要施加的关节力矩,θ̇ 是当前角速度。
参数的影响
以下是 k_p(刚度)和 k_d(阻尼)两个参数对控制效果的影响:
- 刚度
k_p过小:角色显得“柔软无力”,难以到达目标姿势。 - 刚度
k_p过大:角色显得“非常僵硬”,能快速跟踪目标,但可能引发数值不稳定,动作不自然。 - 阻尼
k_d过小:角色在跟踪目标时会产生明显的、持续的振荡。 - 阻尼
k_d过大:角色运动“缓慢迟滞”,需要很长时间才能到达目标姿势,但最终能够到达。
全身控制与轨迹跟踪
对于全身角色,我们对每一个关节都独立地使用PD控制器。我们为每个关节设计一条随时间变化的目标角度轨迹 θ_d(t)。在每一仿真时刻,控制器读取当前所有关节的状态 [θ, θ̇] 和对应的目标 [θ_d(t), θ̇_d(t)],通过PD公式计算出所有关节的力矩 τ,驱动角色运动。
欠驱动系统与“上帝之手” ✋
然而,直接使用PD控制跟踪运动捕获数据或关键帧动画时,效果往往不理想,角色很容易失去*衡摔倒。其根本原因在于:人形角色是一个欠驱动系统。
- 完全驱动系统(如固定基座的机械臂):控制力的自由度 ≥ 系统状态自由度。理论上可以精确控制每一个状态。
- 欠驱动系统(如自由站立的人形角色):控制力的自由度 < 系统状态自由度。关节力矩的总和为零,因此无法直接控制整体的质心位置和全身朝向这些全局状态。
在欠驱动系统中,微小的跟踪误差(如质心稍微偏离)会因为没有直接的控制手段去修正而不断累积,最终导致失控摔倒。
为了解决这个问题,一个常见但取巧的方法是引入根节点力(Root Force),或称“上帝之手”。即在角色的根刚体(如骨盆)上直接施加一个额外的、虚拟的PD控制力,用于跟踪根节点的目标轨迹。
F_root = k_p_root * (p_d - p) - k_d_root * v
这种方法能有效帮助角色保持*衡、跟踪复杂动作,但其物理代价是:这个力在真实世界中不存在(没有施力者),会使角色的运动看起来像被无形的线牵引着,略显不自然。许多使用物理仿真的游戏(如《Party Animals》、《人类一败涂地》)都采用了类似的技术来保证角色的可控性。


应用:混合动画与布娃娃







物理仿真驱动技术除了用于生成全新动画,还可以与传统的关键帧动画结合:


- 布娃娃(Ragdoll):当角色被击倒、失去意识时,撤去所有控制力,进入纯物理仿真状态,产生自然、随机的摔倒效果。
- 动画混合:在游戏动画中,可以在关键帧动画(如攻击动作)和物理仿真动画(如受击摔倒)之间进行*滑过渡。例如,角色被击中时,从关键帧动画切换到布娃娃仿真,再混合到一段站起来的关键帧动画。这样既能保留动画的艺术性,又能增加物理交互的真实感和多样性。


总结




本节课中,我们一起学*了驱动仿真角色的核心知识。我们首先回顾了刚体仿真的运动方程,理解了力与运动的关系。接着,我们探讨了如何通过施加关节力矩来驱动角色,并深入介绍了比例微分(PD)控制这一基础控制方法,了解了其公式 τ = k_p * (θ_d - θ) - k_d * θ̇ 以及参数 k_p 和 k_d 对动作效果的影响。


我们认识到人形角色是一个欠驱动系统,仅用关节力矩难以稳定控制全局状态,因此实践中常借助根节点力(“上帝之手”)来辅助*衡。最后,我们看到了物理仿真驱动在布娃娃效果和动画混合中的应用。


下节课,我们将探索更高级的技术,学*如何在不依赖“上帝之手”的情况下,设计控制器让角色自主保持*衡并完成复杂的运动任务。

GAMES105-计算机角色动画基础 - P11:Lecture10 角色控制 🎮
在本节课中,我们将学*如何驱动基于物理的角色动画。我们将深入探讨PD控制器的细节,并介绍如何通过轨迹优化和反馈控制策略,让角色生成物理准确且稳定的动作。
回顾与引言
上一节我们介绍了PD控制的基本概念及其在角色动画中的应用。本节中,我们将继续探讨PD控制的更多细节,并学*如何去除“上帝之手”般的虚拟外力,生成完全物理真实的动作。主要内容包括:PD控制的稳定性改进、开环控制(前馈控制)以及一个简单的反馈控制策略。
PD控制的深入探讨 🔍
PD控制,即比例微分控制,是驱动角色运动的核心方法之一。其基本思想是通过计算目标状态与当前状态之间的偏差,来施加相应的关节力矩。
PD控制公式
对于一个关节,其控制力矩 τ 的计算公式如下:
τ = kp * (θ_target - θ_current) - kd * ω_current
其中:
θ_target是目标角度。θ_current是当前角度。ω_current是当前角速度。kp是比例增益,决定了对位置偏差的反应强度。kd是微分增益,起到了阻尼作用,能减少系统震荡。
PD控制的两个核心问题
PD控制虽然简单有效,但也存在两个固有缺陷:
- 稳态误差:由于控制力矩
τ由误差(θ_target - θ_current)产生,若要保持一个非零的恒定力矩(如对抗重力),则必须存在一个非零的误差。这意味着角色无法精确到达目标姿态。 - 相位延迟:PD控制是一种反应式控制,它根据当前误差计算力矩,这会导致生成的轨迹相对于目标轨迹存在时间上的滞后。在需要精确时序配合的动作(如走路)中,这种延迟极易导致角色失去*衡。
参数调整与仿真稳定性
kp 和 kd 参数的选择至关重要。







- 参数影响:较大的
kp能减小稳态误差和相位延迟,但会使动作显得僵硬,并可能引发仿真数值不稳定。 - 稳定性分析:对于一个简化的PD控制系统,我们可以通过分析其离散积分后的状态转移矩阵的特征值来判断稳定性。特征值的模必须小于1,系统才能稳定。
- 时间步长限制:过大的
kp会要求更小的仿真时间步长h来保持稳定,这将显著增加计算成本。




以下是调整PD控制参数的经验:








- 通常从
kp=200,kd=20开始尝试。 - 对于较重的肢体或需要爆发力的动作(如跳跃),需要调大
kp。 - 对于较轻的肢体(如手腕),可以调小
kp。 - 参数调整主要依靠手动调试和经验。









提升稳定性:Stable PD Control ⚙️




为了解决标准PD控制在较大 kp 下仿真不稳定的问题,我们可以采用一种改进方法:Stable PD Control。
核心改进
其核心思想是将速度阻尼项的计算从“显式”(使用当前时刻速度 v_n)改为“隐式”(使用下一时刻速度 v_{n+1})。
标准(显式)PD的力计算:
F_n = kp * (x_target - x_n) - kd * v_n
Stable PD(半隐式)的力计算:
F_n = kp * (x_target - x_n) - kd * v_{n+1}
优势
通过这一改动,系统的稳定性对时间步长 h 的容忍度大幅提高。原本可能需要 1/1000 秒的步长才能稳定,现在使用 1/60 秒的步长也能保持稳定,这对于实时应用(如游戏)至关重要。
开环控制:轨迹优化 🛤️
直接使用动作捕捉数据作为PD控制的目标轨迹,由于相位延迟和稳态误差,角色无法保持*衡。我们需要找到一条“可跟踪”的目标轨迹,使得PD控制器跟踪它时,能生成我们期望的、物理准确的动作。这个过程称为轨迹优化。
问题定义
轨迹优化可以被形式化为一个约束优化问题:
- 优化变量:控制轨迹(即PD控制器的目标角度序列
s(t))。 - 目标函数:最小化仿真生成的轨迹
x(t)与期望轨迹x_desired(t)(如动捕数据)之间的差异。 - 约束条件:生成的轨迹必须满足物理运动方程(即仿真动力学)。


求解方法
由于角色动力学高度非线性,优化问题的“地形”非常复杂,存在大量局部最优解。
- 基于梯度的方法:需要知道系统动力学的梯度,对于黑盒仿真器或复杂接触模型难以实现。
- 无梯度优化方法:更适合本问题。其中,协方差矩阵自适应进化策略(CMA-ES)是一种有效的方法。
- 流程:从一个参数的高斯分布开始,采样一组候选解(控制轨迹),进行仿真并评估其目标函数值(与期望动作的匹配度)。保留表现好的样本,并以此更新高斯分布的均值和方差,使其向更优解区域移动。迭代此过程直至收敛。
应用与结果





通过轨迹优化,我们可以:
- 为给定的动捕数据找到对应的、物理可执行的控制轨迹。
- 将动作适配(Retargeting)到不同体型或不同环境的角色上,自动处理*衡和接触问题。
- 仅给定高级目标(如“跳得更高”),生成全新的动作。
闭环控制:简单静态*衡 🤸
开环控制(轨迹优化得到的固定控制序列)无法应对扰动。一旦角色被推一下,就会偏离轨迹并摔倒。我们需要引入反馈控制(闭环控制),根据当前状态实时调整控制指令。
静态*衡原理
静态*衡指角色在支撑面内基本保持静止的*衡状态。其关键条件是:角色质心在水*面上的投影,必须落在支撑多边形(Support Polygon,即所有支撑点构成的凸包)内部。
实现方法:PD控制 + 虚拟力
一个简单的静态*衡控制器可以这样构建:
- 姿态维持:用一个PD控制器跟踪一个稳定的站立姿态(如T-Pose),产生基础关节力矩
τ_pose。 - *衡补偿:计算质心投影与支撑多边形中心(如两脚连线中点)的偏差。对此偏差再使用一个PD控制器,计算出一个想要施加在质心上的虚拟力
F_virtual,目的是将质心拉回目标位置。 - 力矩分配:虚拟力
F_virtual并非直接施加。我们使用雅可比转置控制(Jacobian Transpose Control),将这股虚拟力等效地分配到相关关节(通常是踝关节和髋关节)上,产生补偿力矩τ_balance。- 公式:
τ_balance = J^T * F_virtual,其中J是质心位置相对于关节角度的雅可比矩阵。
- 公式:
- 最终控制:将两部分力矩相加,施加给角色:
τ_total = τ_pose + τ_balance。
通过这种方式,角色可以在站立时抵抗小幅度的推力扰动。通过调整质心目标位置的高度,甚至可以实现简单的下蹲和站起动作。
总结 📚


本节课中,我们一起学*了基于物理的角色控制的核心内容:
- PD控制:深入分析了其稳态误差和相位延迟问题,以及参数调优和仿真稳定性的关联。
- 稳定性增强:介绍了Stable PD Control,通过隐式速度阻尼提高仿真稳定性,允许更大的时间步长。
- 开环控制:讲解了轨迹优化的概念,即通过优化(如使用CMA-ES算法)寻找一条能使PD控制器生成期望动作的目标轨迹。
- 闭环控制入门:以实现静态*衡为例,介绍了反馈控制的基本思路。结合PD控制和雅可比转置方法,设计了一个能抵抗扰动、保持原地*衡的简单控制器。

下一节课,我们将探讨更复杂的动态*衡控制,例如如何实现一个自然行走的控制器。

GAMES105-计算机角色动画基础 - P12:Lecture11 学*行走 🚶♂️
在本节课中,我们将学*如何让一个基于物理仿真的虚拟角色实现行走。我们将从静态*衡控制出发,探讨实现动态行走的挑战,并介绍几种经典的简化模型控制方法。
概述
在前两节课中,我们学*了物理仿真基础以及如何通过反馈控制让角色保持原地*衡。本节课,我们将在此基础上更进一步,探讨如何让角色在场景中移动,特别是实现行走这一基本动作。
从静态*衡到动态行走
上一节我们介绍了如何实现静态的原地*衡控制。这种控制器可以结合动作跟踪,让角色在保持*衡的同时完成上半身的一些动作。然而,原地*衡能做的事情非常有限。
对于基于运动学的方法,移动角色非常简单,只需直接设置关节位置即可。但对于物理仿真,我们不能直接控制角色的全局位置,只能通过角色与地面的交互力(地面反作用力)来推动角色移动。因此,如何规划全身控制以实现行走,是物理仿真角色动画的核心挑战之一。
行走的定义与分析 🦶
行走是每个人都非常熟悉的动作。从控制角度看,行走是一个相对稳定、速度较慢的过程,这为我们提供了更多的控制时间。
一个标准的行走周期可以分解为以下几个阶段:
- 单腿支撑期:一只脚在空中摆动,另一只脚支撑身体。
- 双腿支撑期:在步态转换的瞬间,双脚同时接触地面。
行走与跑步的关键区别在于是否存在“腾空期”。行走要求至少有一只脚始终与地面接触。竞走比赛的规则正是基于此点。
以下是行走步态周期的可视化说明:

实现行走的策略
一种直观的策略是利用上节课的静态*衡原理。在单腿支撑期,我们需要确保角色的质心投影始终落在支撑脚构成的支撑多边形内。在双腿支撑期,我们则利用更大的支撑区域,将质心从一个支撑脚转移到另一个支撑脚。
然而,静态*衡策略存在局限性。它假设角色处于准静止状态,而实际行走会产生动量和角动量。若不能很好地控制这些量,角色很容易失去*衡。因此,要实现更自然、灵活的行走,需要引入动态*衡控制。
核心概念:零力矩点
在双足机器人领域,一个非常重要的概念是零力矩点。它是指地面上满足水*方向合外力矩为零的一个点。
我们可以通过受力分析来理解ZMP。考虑角色处于单腿支撑状态,地面给脚的反作用力可以等效为施加在某一点 P 上的一个合力 F 和一个力矩 τ。通过力学公式推导,我们可以找到一个特殊的点 P,使得相对于该点的水*方向力矩为零。这个点就是ZMP。
τ_horizontal(P) = 0
ZMP的位置可以通过当前角色的状态(如质心位置、速度、外力等)计算出来。它的物理意义在于:
- 如果计算出的ZMP位于支撑多边形内部,说明角色处于动态*衡状态。
- 如果ZMP位于支撑多边形外部,则意味着角色正在失去*衡(例如即将摔倒)。
因此,ZMP可以作为一个关键的*衡指标。在控制角色时,如果我们能始终将ZMP保持在支撑多边形内,就能维持角色的动态*衡。
简化模型控制方法
直接控制具有数十个自由度的复杂人体模型非常困难。因此,常见的思路是使用简化模型。即从复杂模型中提取出与*衡和移动最相关的关键变量(如质心、支撑点),构建一个更简单的物理模型,并在这个模型上进行轨迹规划和控制器设计。最后,再将简化模型的结果映射回完整的人体模型。
接下来,我们将介绍三种经典的基于简化模型的行走控制方法。
方法一:ZMP跟踪与桌子-小车模型
这是一种在机器人领域非常经典的方法。它将复杂的双足机器人简化为一个“桌子-小车”模型:
- 桌子:代表机器人的支撑脚。
- 桌上的小车:代表机器人质心的水*运动。
该模型的目标是:通过控制小车的运动,使得整个系统的ZMP能够跟踪一条预设的轨迹。这条预设轨迹通常是在双脚支撑区域之间*滑移动的曲线,对应着行走中重心转移的过程。


通过优化计算,我们可以得到小车(即质心)应该遵循的轨迹。然后,利用逆向运动学,根据规划出的质心轨迹和脚部位置,计算出全身关节的目标姿态,最后通过PD控制器进行跟踪。



这种方法可以实现稳定、拟人化的行走,甚至能应对上楼梯等复杂场景。本田公司的ASIMO机器人就是应用此类技术的代表。



方法二:倒立摆模型与步态规划
生物学研究表明,人类行走其实更像一个受控的摔倒过程。我们并非时刻保持完美*衡,而是允许质心前移失去*衡,然后通过迈出下一步来重新获得支撑。

倒立摆模型非常适合描述这种动态。在单腿支撑期,我们可以将角色简化为一个倒立摆:
- 摆锤:角色的质心。
- 支点:支撑脚与地面的接触点。

基于此模型的一个经典工作是“广义倒立摆控制”。其核心思想是:根据当前质心的位置和速度,计算下一步的落脚点,使得质心在迈步后能像倒立摆一样,恰好摆动到新支撑点的正上方时速度降为零。

具体而言,利用能量守恒定律,可以根据当前质心的动能,推算出它能到达的最大高度(对应摆杆长度),从而确定下一步的落脚位置。得到落脚点后,再通过轨迹插值和逆向运动学生成全身动作。
这种方法非常鲁棒,角色可以在受到推挤、搬运物体或地形变化时,通过实时调整落脚点来保持*衡。



方法三:线性反馈控制





这是一个更早但非常经典的工作,旨在用最简单的反馈实现鲁棒的行走控制。该方法主要包含三部分:

- 开环轨迹跟踪:使用PD控制器跟踪一个预设的步行周期姿态序列(或动作捕捉数据)。
- 躯干姿态控制:通过协调髋关节和腰部力矩,控制上半身躯干保持竖直,这是一个稳定的姿态。
- 质心速度反馈:这是实现*衡的关键。它建立一个非常简单的线性反馈规则,根据当前质心的位置和速度偏差,调整摆动腿的目标姿态。
Δθ_swing = k_p * (x_com - x_desired) + k_d * (v_com - v_desired)
其中 k_p 和 k_d 是手动调节的参数。其直觉是:当质心向前摔倒的速度过快时,就命令摆动腿更快地向前摆动,以更快地落下提供支撑;反之则减慢摆动。
这种方法虽然简单,但首次在角色动画中实现了较为鲁棒的二维及三维步行控制。不过,其动作质量略显机械,且反馈参数需要针对不同动作进行调节。

总结
本节课我们一起学*了基于物理仿真的角色行走控制。我们首先分析了行走的步态周期和动态*衡的挑战,并引入了零力矩点这一核心概念作为*衡指标。
接着,我们重点介绍了三种基于简化模型的经典控制方法:
- ZMP跟踪(桌子-小车模型):通过控制质心运动来精确跟踪ZMP轨迹,实现稳定、拟人的步行。
- 倒立摆模型:将行走建模为受控的摔倒过程,通过能量守恒实时规划落脚点,鲁棒性很强。
- 线性反馈控制:在开环跟踪基础上,增加简单的线性反馈来调节步态,实现简单而有效的*衡。
这些传统方法具有较好的可解释性,能够定量分析角色的*衡状态。然而,它们通常针对特定动作(如行走)设计,难以泛化到更复杂的动作。在下节课中,我们将探讨基于机器学*(如强化学*)的方法,这些方法能够学*更广泛、更复杂的运动技能。

本节课是GAMES105课程的第十一讲,主要介绍了实现物理角色行走的传统控制方法。这些经典思想为后续的智能控制奠定了重要基础。

GAMES105-计算机角色动画基础 - P13:Lecture12 最优控制与强化学* 🎯🤖
在本节课中,我们将学*最优控制与强化学*的基本概念,并探讨它们在计算机角色动画中的应用。我们将从最优控制理论出发,理解其与强化学*的联系与区别,并介绍一些基于这些技术的经典工作。
概述
上一节我们介绍了基于物理的角色动画控制方法。本节中,我们将深入探讨两类更高级的控制理论:最优控制与强化学*。最优控制为我们提供了在已知系统模型下寻找最优控制信号的数学框架,而强化学*则能在模型未知或复杂的情况下,通过与环境的交互来学*控制策略。我们将看到,这两种方法在实现复杂、鲁棒的角色动画方面都扮演着重要角色。
最优控制基础
最优控制是控制理论中的一个重要分支,旨在为动态系统寻找一个控制信号,使得某个性能指标(如能量消耗、跟踪误差)达到最优。在角色动画中,这通常意味着寻找施加在角色关节上的力矩序列,以驱动角色完成特定动作。
问题建模
我们如何实现控制?只能通过在角色身体上施加力(主要是关节力矩)来驱动角色。例如,如果我们想让角色执行一个后空翻,我们希望自动找到一系列控制策略(通常表现为PD控制的目标值),使得仿真轨迹与期望的运动轨迹相匹配。
为了找到这一系列轨迹,我们可以将控制建模为一个优化问题。在每一仿真步,我们计算当前步与参考运动数据的偏差,并将所有步的偏差累加起来,构成优化目标。优化的对象是每一步所施加的力。
这里有一个关键问题:优化时需要满足物理规律。在仿真中,物理规律是真实规律的简化,但本质上仍由一系列带约束的方程(如刚体动力学方程)描述。
开环控制与反馈控制
我们之前提到了开环控制(或前馈控制)和反馈控制。
- 开环控制:根据起始状态和目标状态,直接计算出一条控制信号轨迹。执行这条轨迹即可从起点到达终点。其优点是只需计算控制信号,问题相对简单;缺点是若轨迹中途受到扰动,系统很可能无法达到目标。
- 反馈控制:计算一个控制策略函数,该函数根据角色当前的状态(即使受到扰动后)实时计算控制信号。这相当于在状态空间中定义了一个“场”,能将任何起始状态逐渐推向目标状态。这个问题比开环控制更难,因为它需要考虑整个状态空间。
这两种方式都可以描述为优化问题,但优化对象不同:轨迹优化优化的是每个时刻的控制信号;反馈控制优化的是控制策略函数本身。它们统称为最优控制问题。
轨迹优化与庞特里亚金极大值原理
本节我们来看看如何求解开环控制问题,即轨迹优化。
带约束优化与拉格朗日乘子法
轨迹优化是一个带约束的优化问题:我们希望在满足运动方程约束的前提下,最小化目标函数。处理等式约束优化问题的常用方法是拉格朗日乘子法。
对于一个简单问题:最小化 f(x),约束为 g(x)=0。其极值点满足的必要条件是,f(x) 和 g(x) 在该点的梯度方向*行。我们可以构造拉格朗日函数 L(x, λ) = f(x) + λ * g(x)。原问题的最优解 (x*, λ*) 可通过求解 ∇_x L = 0 和 ∇_λ L = 0 得到。
对于我们的轨迹优化问题,目标函数是沿时间累积的代价和,约束是离散时间的运动方程。我们可以类似地写出其拉格朗日函数,并对所有状态变量、控制变量和拉格朗日乘子求导。
庞特里亚金极大值原理
通过求导和整理,我们可以得到一组刻画最优轨迹的必要条件,这被称为庞特里亚金极大值原理:
- 状态方程:系统的动力学方程,描述状态如何向前演化。
- 协态方程:一个逆向演化的方程,描述了拉格朗日乘子(或称协态)的变化。
- 控制方程:一个优化条件,用于根据当前状态和协态计算最优控制量。
这几个条件合在一起,构成了求解最优开环控制轨迹的基础。
求解方法:打靶法
一种基于PMP求解轨迹优化的直接方法是打靶法。其流程如下:
- 前向过程:猜测一个初始控制序列,执行仿真,记录下产生的状态轨迹。
- 后向过程:利用记录的状态轨迹,从最后一帧开始,逆向积分协态方程,得到每一时刻的协态值。
- 更新过程:利用当前状态和协态,根据控制方程计算梯度,并更新控制序列。
通过不断迭代这三个步骤,可以逐渐找到最优的控制轨迹。然而,对于像人体运动这样高度非线性的复杂问题,目标函数可能存在大量局部极值,使得基于梯度的方法(如打靶法)难以收敛。因此,实践中常结合无梯度优化方法(如CMA-ES)或多重打靶等技术。
反馈控制与动态规划
上一节我们介绍了开环控制的优化理论。本节中,我们转向反馈控制,其核心是动态规划和贝尔曼最优性原理。
贝尔曼最优性原理
考虑一个经典的最短路径问题:在一个图中,找到从起点到终点的最短路径。我们可以将其视为一个控制问题:每个节点是一个状态,从节点出发选择哪条边是控制动作。
对于一个最优的反馈控制策略 π,贝尔曼最优性原理指出:无论从哪个状态开始,也无论第一步选择了哪个动作,在到达新状态后,剩余部分所遵循的策略,必须是从这个新状态出发到达目标的最优策略。
这意味着最优策略具有“最优子结构”性质。
价值函数与贝尔曼方程
基于贝尔曼原理,我们可以定义价值函数 V(s),它表示从状态 s 出发,遵循最优策略到达目标所能获得的最小总代价。


价值函数满足一个重要的递归关系,即贝尔曼方程:
V(s) = min_a [ cost(s, a) + V( next_state(s, a) ) ]
其中,cost(s, a) 是执行动作 a 的即时代价,next_state(s, a) 是执行动作后的下一个状态。
如果我们知道了最优价值函数 V*(s),那么最优策略 π*(s) 就很简单:选择能使 cost(s, a) + V*( next_state(s, a) ) 最小的动作 a。
Q函数
为了更方便地表达策略选择,我们定义Q函数(或称状态-动作价值函数):
Q(s, a) = cost(s, a) + V( next_state(s, a) )
它表示在状态 s 下执行动作 a,并随后遵循最优策略所得到的总代价。那么最优策略就是:π*(s) = argmin_a Q*(s, a)。
因此,求解反馈控制问题的核心之一就是学*价值函数 V 或 Q函数 Q。
线性二次型调节器:一个特例
前面讨论的反馈控制问题通常很难得到闭式解。但对于一类特殊问题——线性二次型调节器(LQR),我们可以得到解析解。
LQR问题满足两个条件:
- 系统是线性的:状态转移方程
s_{t+1} = A * s_t + B * a_t。 - 代价函数是二次的:目标函数是状态和控制量的二次型。
对于LQR问题,可以证明:
- 最优价值函数
V_t(s)是状态s的二次型。 - 最优控制策略是状态的线性反馈:
a_t = -K_t * s_t。 - 反馈增益矩阵
K_t和代价函数中的矩阵P_t可以通过一个从后向前的递推公式(Riccati方程)高效计算。
LQR为我们在简单线性系统下设计最优反馈控制器提供了强大工具。对于非线性系统(如人体运动),一种思路是在参考轨迹附*进行线性化,将问题*似为一系列LQR问题来求解,这衍生出iLQR(迭代LQR)和DDP(微分动态规划)等算法。
从最优控制到强化学*
最优控制(尤其是反馈控制)与强化学*有着深刻联系。它们的目标通常是相似的:最大化(或最小化)长期累积的回报(或代价)。主要区别在于:
- 最优控制:通常是基于模型的。我们假设系统的动力学方程
f(s, a)是已知且精确的。 - 强化学*:通常是无模型的。我们不假设知道精确的动力学模型,而是通过智能体与环境的交互试错来学*策略。
在角色动画中,真实的物理仿真或机器人往往存在模型不精确、传感器噪声等问题,这使得无模型的强化学*方法更具吸引力。
强化学*基础:马尔可夫决策过程
强化学*问题通常被建模为马尔可夫决策过程。一个MDP由以下要素定义:
- 状态空间 S:所有可能状态的集合。
- 动作空间 A:所有可能动作的集合。
- 状态转移概率 P(s'|s, a):在状态
s执行动作a后,转移到状态s'的概率。 - 奖励函数 R(s, a, s'):在状态
s执行动作a并到达s'所获得的即时奖励。
目标是找到一个策略 π(a|s),最大化期望累积折扣奖励:E[ Σ γ^t * R_t ],其中 γ 是折扣因子。

强化学*算法分类
以下是两类主要的强化学*算法:
-
基于价值的方法:
- 核心思想:学*最优价值函数
V*(s)或Q*(s, a),然后通过π*(s) = argmax_a Q*(s, a)导出策略。 - 特点:通常用于离散动作空间。经典算法包括Q-Learning、DQN(深度Q网络)。
- 在动画中的应用:可用于高级决策,例如在复杂地形中选择何时切换不同的底层运动控制器。
- 核心思想:学*最优价值函数
-
基于策略的方法:
- 核心思想:直接参数化策略
π_θ(a|s)(例如用神经网络),并通过梯度上升来优化参数θ,以最大化期望回报。 - 特点:天然适用于连续动作空间。为了降低方差,通常会同时学*一个价值函数作为基线(评论家),形成演员-评论家框架。
- 经典算法:REINFORCE, TRPO, PPO。PPO 因其良好的稳定性和性能,在物理角色动画领域应用非常广泛。
- 核心思想:直接参数化策略
通过结合强化学*(如PPO)和轨迹优化提供的初始解,我们现在可以为角色学*出非常复杂的运动控制器,并且能够适应不同的角色体型、运动速度等参数。
前沿与展望

*年来,生成模型(如VAE, GAN, 标准化流,扩散模型)与强化学*的结合,为生成多样化、可控的角色运动开辟了新方向。这些方法能够从随机噪声生成连贯的动作,或根据用户指令(如文本描述)生成相应运动。

未来可能的发展方向包括:
- 大规模预训练“运动基础模型”:类似于语言模型,从海量运动数据中学*一个通用的“运动知识”模型,作为各种下游运动任务的基础。
- 跨模态生成与控制:结合语言、音频、视觉等多模态信息,驱动数字角色进行更智能、更逼真的交互与表演。
- 更精细的生理模型:从当前的关节力矩控制,发展到基于肌肉模型的更生物逼真的控制。
- 群体动画与交互:研究多角色之间的协作、竞争等复杂交互行为的生成与控制。
总结
本节课中,我们一起学*了最优控制与强化学*的基本原理及其在计算机角色动画中的应用。
- 我们从最优控制理论出发,介绍了轨迹优化(开环控制)的庞特里亚金极大值原理,以及反馈控制的贝尔曼最优性原理和价值函数概念,并探讨了线性二次型调节器(LQR)这一特例。
- 我们探讨了最优控制与强化学*的联系与区别,指出强化学*作为一种无模型方法,在解决复杂、非线性系统控制问题上的优势。
- 我们简要介绍了强化学*的马尔可夫决策过程框架,以及基于价值和基于策略的两类主要算法。
- 最后,我们展望了结合生成模型、大规模预训练等前沿技术,为角色动画未来带来的可能性。

最优控制与强化学*为创建物理上真实、行为上智能的虚拟角色提供了强大的理论工具,是连接运动学动画与真正自主交互式数字角色的关键桥梁。

GAMES105-计算机角色动画基础 - P2:Lecture02 数学基础 📐
在本节课中,我们将学*角色动画中至关重要的数学背景知识。主要内容包括线性代数的核心概念回顾,以及三维旋转的多种表示方法。掌握这些基础知识是理解后续运动学算法的关键。
线性代数回顾 🔢
上一节我们介绍了课程的整体框架,本节中我们首先回顾线性代数中与角色动画紧密相关的核心概念。
向量
向量是同时具有大小和方向的量。在角色动画中,向量可以用来表示位置、速度、加速度等物理量。
- 定义与表示:一个 n 维向量 a 可以表示为一组有序数字,通常写作列向量形式:a = [a₁, a₂, ..., aₙ]ᵀ。其长度(模)为:||a|| = √(a₁² + a₂² + ... + aₙ²)。
- 单位向量:长度为 1 的向量,可通过 a / ||a|| 得到。
- 基本运算:
- 加法:a + b = [a₁+b₁, a₂+b₂, ..., aₙ+bₙ]ᵀ,满足交换律。
- 数乘:ka = [ka₁, ka₂, ..., kaₙ]ᵀ。
向量的乘积运算
向量之间有两种重要的乘积运算:点乘和叉乘。
点乘(内积)
点乘的结果是一个标量,定义为:a · b = Σᵢ aᵢbᵢ = a₁b₁ + a₂b₂ + ... + aₙbₙ。
在三维欧式空间中,它具有明确的几何意义:a · b = ||a|| ||b|| cosθ,其中 θ 是两向量间的夹角。因此,点乘常用于计算夹角或一个向量在另一个向量方向上的投影。
叉乘(外积)
叉乘是三维空间特有的运算,结果是一个新的向量。其分量定义为:
a × b = [a₂b₃ - a₃b₂, a₃b₁ - a₁b₃, a₁b₂ - a₂b₁]ᵀ。
叉乘的几何意义是:结果向量 c = a × b 同时垂直于 a 和 b,方向由右手定则决定,其长度等于以 a 和 b 为边的*行四边形的面积:||c|| = ||a|| ||b|| |sinθ|。
叉乘不满足交换律(a × b = -b × a)和结合律。
矩阵
矩阵是一个二维数组,是表示线性变换和坐标变换的有力工具。
- 基本运算:
- 转置:将矩阵的行列互换,记作 Aᵀ。
- 乘法:若 C = AB,则 Cᵢⱼ = Σₖ Aᵢₖ Bₖⱼ。矩阵乘法满足结合律,但不满足交换律。
- 逆:若 AB = I(单位矩阵),则 B 是 A 的逆矩阵,记作 A⁻¹。性质:(AB)⁻¹ = B⁻¹A⁻¹。
- 特殊矩阵:
- 正交矩阵:满足 AᵀA = AAᵀ = I,即 A⁻¹ = Aᵀ。正交矩阵的行列式为 ±1。
- 反对称矩阵:满足 Aᵀ = -A,主对角线元素为 0。
- 向量运算的矩阵形式:
- 点乘:a · b = aᵀb。
- 叉乘:a × b 可以表示为矩阵乘法 [a]× b,其中 [a]× 是由向量 a 构造的反对称矩阵。

坐标基与变换


在三维空间中,我们通常使用一组标准正交基 {e_x, e_y, e_z}(即 x, y, z 轴的单位向量)来描述向量。任何向量 v 都可以表示为这组基的线性组合:v = v_x e_x + v_y e_y + v_z e_z,系数 (v_x, v_y, v_z) 就是其坐标。
变换描述了如何将一个坐标系下的点或向量转换到另一个坐标系下。我们主要关注刚性变换,它包括旋转和*移,不改变物体的形状和大小。
- *移变换:非常简单,p' = p + t。
- 旋转变换:可以用一个 3x3 的旋转矩阵 R 表示,p' = R****p。旋转矩阵是行列式为 +1 的正交矩阵。
组合变换时,顺序至关重要。例如,先旋转 R 再*移 t,变换为 p' = R****p + t。从局部坐标系到世界坐标系的变换常采用这种形式。
三维旋转的表示方法 🔄


上一节我们回顾了线性代数基础,本节中我们深入探讨三维旋转的多种表示方法。由于旋转是非线性的,其表示和插值都比*移复杂得多。

旋转矩阵

这是最直接的表示,用一个 3x3 正交矩阵 R ( RᵀR = I, det(R) = +1) 表示旋转。
- 优点:旋转向量非常方便(v' = R****v);组合旋转只需矩阵乘法。
- 缺点:9 个参数带有 6 个约束(正交性),不直观;直接对矩阵元素进行线性插值无法得到合法的旋转矩阵,会导致物体扭曲。
欧拉角
欧拉角用三个绕特定坐标轴连续旋转的角度 (α, β, γ) 来表示任意旋转。旋转顺序至关重要(例如 XYZ, ZYX 等),并且有“内旋”(绕物体自身轴)和“外旋”(绕固定世界轴)两种约定。
- 优点:非常直观,易于人类理解和使用(在三维软件中广泛使用);三个参数无约束。
- 缺点:
- 万向节死锁:当中间轴的旋转使某两个轴对齐时,会失去一个自由度,导致表示不唯一和插值路径奇异。
- 插值问题:角度存在周期性(如 0° 和 360° 等价),直接线性插值可能导致不必要的“绕远路”。
轴-角表示
任何旋转都可以表示为绕一个单位轴向量 u 旋转角度 θ。这可以紧凑地表示为旋转向量 r = θu。
- 优点:几何意义清晰;四个参数(3维单位向量+1角度),带一个约束(||u||=1)。
- 缺点:旋转运算不方便(需用 Rodrigues 公式转化为矩阵);组合旋转困难;插值(对 r 线性插值)不能保证恒定的角速度。
四元数
四元数是对复数在三维空间的扩展,一个单位四元数 q = [w, v] = [cos(θ/2), sin(θ/2)u] 可以表示绕轴 u 旋转角度 θ。
- 优点:
- 紧凑且无奇异性:4个参数带1个约束(||q||=1),无万向节死锁。
- 高效组合:旋转组合对应于四元数乘法,比矩阵乘法更快。
- 最佳插值:可以在单位四元数构成的四维球面上进行球面线性插值 (Slerp),得到*滑、角速度恒定的插值结果。公式为:
Slerp(q_0, q_1; t) = (sin((1-t)Ω) / sin Ω) q_0 + (sin(tΩ) / sin Ω) q_1,
其中 cos Ω = q_0 · q_1。 - 旋转向量:对于向量 v,其旋转结果为 v' = q v q⁻¹(将 v 视为纯四元数 [0, v])。
- 缺点:概念较抽象,不直观;存在双倍覆盖(q 和 -q 表示同一个旋转),插值时需注意选择最短路径。
以下是四种表示方法的对比总结:

| 表示方法 | 参数数量 | 约束条件 | 直观性 | 组合旋转 | 插值难度 | 主要用途 |
|---|---|---|---|---|---|---|
| 旋转矩阵 | 9 | 6 (正交+行列式=1) | 差 | 容易(矩阵乘) | 困难(不能直接线性插) | 底层计算、变换表示 |
| 欧拉角 | 3 | 无 | 优秀 | 困难(顺序依赖) | 中等(需处理周期和死锁) | 用户交互、动画关键帧 |
| 轴-角 | 4 (3+1) | 1 (轴为单位向量) | 好 | 困难 | 中等(线性插值不恒定) | 物理模拟、旋转差 |
| 四元数 | 4 | 1 (单位四元数) | 差 | 容易(四元数乘) | 容易(Slerp) | 动画插值、旋转累积 |
总结 📝




本节课中,我们一起学*了角色动画所需的数学基础。
- 我们首先回顾了线性代数的核心工具:向量(表示与运算)、矩阵(运算与特殊类型)以及坐标变换(特别是旋转与*移的表示)。
- 随后,我们深入探讨了三维旋转的四种主要表示方法:旋转矩阵、欧拉角、轴-角表示和四元数。我们分析了每种方法的优点、缺点和适用场景。
- 旋转矩阵适用于底层计算。
- 欧拉角因其直观性,最适合用户编辑和设置关键帧。
- 四元数因其在插值和组合旋转方面的卓越性能,成为动画引擎内部进行旋转插值和累积运算的首选。
- 轴-角表示则在某些物理和几何计算中很有用。

理解这些数学概念,特别是不同旋转表示之间的转换与取舍,是构建和实现角色动画算法的基石。在下节课中,我们将基于这些知识,开始学*前向运动学,了解如何通过关节旋转来计算末端效应器的位置。

GAMES105-计算机角色动画基础 - P3:Lecture03 角色运动学:前向与逆向运动学 🦾

在本节课中,我们将要学*角色运动学的基本知识,包括前向运动学与逆向运动学。这是构建和控制虚拟角色动画的核心基础。
前两节课中,我们介绍了计算机角色动画的主要技术和*年进展,并回顾了线性代数与三维旋转的表示方法。三维旋转比二维复杂,有多种表示方式,如旋转矩阵、四元数、轴角等,这些在物理引擎和游戏引擎中广泛应用。


从本节课开始,我们将引入课程作业。作业提交网站现已开放,需要注册码进行注册。作业代码库将在本周内更新,内容主要围绕本节课及下节课的知识点,难度适中,提交周期约为2-3周,助教会进行评分。
什么是运动学?📚
上一节我们回顾了数学基础,本节中我们来看看运动学的定义。运动学是研究物体运动的学科,但有一个重要前提:它不考虑物体的质量、力等外在物理性质。如果考虑这些因素,就进入了动力学的范畴。基于物理仿真的角色动画是后续课程的重要内容,但在现阶段,我们只关注物体本身的运动。
“角色”的范围很广,不仅包括人,也涵盖机器人、机械臂等。角色动画与机器人领域在此有大量共享技术。这些角色通常有一个共性:身体由刚性部件(骨骼/连杆)构成,部件之间通过关节连接,并且运动主要表现为绕关节的旋转。当然,也存在柔性角色,但可以通过扩展刚性模型来实现。
角色的抽象建模 🧍
为了理解运动,我们首先需要将角色抽象成一个可计算的模型。一个常用的抽象是:将身体视为由若干骨骼(Bone/Link)组成的集合,每两块骨骼之间通过一个关节(Joint)连接。关节的作用是确保相连的骨骼在运动时不会分离,只能围绕关节进行相对旋转。
在动画软件中,虚拟角色内部都有一套这样的关节骨骼系统。对于动画师而言,通常更关心关节的位置和旋转如何改变角色的整体姿态,而不太关注骨骼的具体形状参数。
前向运动学计算 🔄
当我们把角色定义为一个由关节和骨骼构成的系统后,要创建一个姿势,就需要旋转各个关节。为了确保在旋转过程中整个角色保持连接、不发生撕裂,我们必须精确计算每个关节旋转后,其自身以及子关节的位置和朝向。这个计算过程就是前向运动学。
我们可以用一个简单的机械臂链条为例。假设链条有多个关节,每个关节上都绑定了一个局部坐标系。关节的旋转会改变其局部坐标系的朝向。
初始时,所有局部坐标系与世界坐标系*行(即旋转为单位矩阵)。如果我们旋转最后一个关节,它只会影响自身的朝向,假设变为 R4。接着,如果我们旋转它的父关节,这个旋转会同时影响父关节自身及其所有子关节的朝向。根据旋转的组合规则,旋转后的子关节朝向变为 R3 * R4。
以此类推,从根关节开始,依次施加旋转,最终每个关节的朝向等于从根关节到该关节路径上所有旋转的连续乘积。用公式表示,对于关节 i,其世界朝向 Q_i 与其父关节朝向 Q_parent(i) 及自身局部旋转 R_i 的关系为:
Q_i = Q_parent(i) * R_i
反之,如果知道父子关节的朝向,也可以反推出局部旋转:R_i = Q_parent(i)^{-1} * Q_i
位置计算
我们进一步假设每个局部坐标系的原点就位于关节的旋转中心。那么,骨骼的长度就体现为子关节在父关节局部坐标系中的一个固定偏移向量 L_i。
当父关节旋转时,这个局部偏移向量在世界坐标系中的表示会发生变化。要计算子关节在世界坐标系中的位置 P_i,需要进行坐标变换:
P_i = P_parent(i) + Q_parent(i) * L_i
这是一个迭代过程:从根关节(其位置和朝向已知)开始,可以依次计算出链条上每一个关节的位置和朝向。
点的变换
如果我们想知道附着在某个关节上的一个点 X_local(在其局部坐标系中)的世界坐标,只需应用同样的变换:
X_world = Q_i * X_local + P_i
总结来说,前向运动学是一个从根节点到末端节点的迭代过程,通过连续的坐标变换,更新每个关节的朝向和位置。我们也可以进行逆向计算,将一个世界坐标点逐步转换回某个关节的局部坐标系中。

角色模型与自由度 🎭


前面介绍了单链条的例子,但对于一个完整角色,我们通常将其建模为一个树形结构。这个树只有一个根节点(通常位于角色的腰部),从根节点出发,到左手、右手、左脚、右脚分别形成不同的运动学链条。

根节点的选择会影响角色的表现。例如,根节点在腰部时,抬腿会影响全身姿态;而若将右脚设为根节点,同样的腿部旋转指令会产生不同的整体朝向效果。
关节类型与自由度
从解剖学或机械原理来看,关节有不同的类型和属性。在角色动画中,我们通常简化建模,主要考虑两种:
- 铰链关节:也称为旋转关节。它只有一个旋转自由度,只能绕一个固定轴旋转。例如人的手肘和膝盖。
- 自由度 = 1
- 球窝关节:像一个球体在碗中转动,可以在任意方向旋转。例如人的肩关节和髋关节。
- 自由度 = 3
自由度 是指唯一描述一个系统状态所需的最少参数个数。对于一个在三维空间中无约束的刚体,它有6个自由度(3个*移,3个旋转)。关节通过连接骨骼,限制了*移自由度,通常只保留旋转自由度。
关节还有其他属性,如角度限制。例如,膝盖不能向后弯曲,其旋转范围通常被限制在0到某个角度之间。
姿态的参数化表示
一个角色的姿态可以用所有关节的状态来参数化表示:
- 根节点:包含位置 (x, y, z) 和朝向(3个参数),共6个自由度。
- 内部关节:每个关节根据其类型,用1个(铰链)或3个(球窝)参数表示其旋转。
整个角色的自由度是根节点自由度与所有内部关节自由度之和。在存储和计算时,我们通常让父节点的索引小于子节点,这样只需顺序遍历列表一次,就能完成整个树的前向运动学计算,非常高效。
常见的动作文件格式(如BVH)就是基于这种思想:文件头定义骨骼树结构(父子关系、初始偏移),文件体则是一帧帧的数据,记录每个关节在每个时刻的旋转参数。
逆向运动学 🤔
前向运动学是已知关节旋转,求末端位置。而逆向运动学正相反:给定末端执行器(如手、脚)的目标位置和/或朝向,反求所有关节的旋转。
IK在动画制作中应用更广泛,因为动画师直接调整每个关节的旋转来摆姿势非常繁琐且不直观。通过IK控制器,动画师只需拖动末端效应器,算法就能自动计算出合理的关节旋转,效率高得多。
从数学上看,前向运动学是一个相对直接的计算过程(函数求值),而逆向运动学则是一个逆向问题,通常更复杂。IK问题通常是非线性的,可能存在多解、无解的情况,且求解困难。
两连杆IK问题
最简单的IK问题是两连杆(两个关节)系统,这在角色动画中很常见(如手臂从肩到肘到腕可简化为两连杆)。对于两连杆IK,我们可以用几何法直接求解:
- 首先,旋转第一个关节,使得第一个关节到末端点的距离等于两连杆长度之和。这可以通过解三角形(余弦定理)计算出第一个关节的旋转角。
- 然后,旋转根关节(或第一个关节),使得末端点与目标点重合。这等价于将一个向量旋转到另一个向量的方向,可以通过叉积得到旋转轴,点积得到旋转角来计算。
通用IK问题与优化视角
对于更通用的多关节IK问题,我们将其形式化为一个优化问题。设 θ 为所有关节旋转参数的向量,f(θ) 为前向运动学函数,计算末端点位置。目标位置为 x_target。
IK问题即寻找 θ,使得 f(θ) = x_target。这等价于最小化误差函数:
E(θ) = ½ || f(θ) - x_target ||²
我们从一个初始姿态 θ₀ 开始,通过迭代优化来降低误差 E(θ)。
1. 循环坐标下降法
CCD是一种启发式方法。它依次对每个关节进行优化(固定其他关节),每次只调整一个关节,使末端点尽可能靠*目标点。
算法步骤:
- 从末端关节开始,向根关节方向循环。
- 对于当前关节
i,计算当前末端点位置e和目标点t。 - 计算向量
a(关节i到当前末端点)和向量b(关节i到目标点)。 - 旋转关节
i,使向量a与向量b对齐。这个旋转可通过计算旋转轴axis = a × b和旋转角angle = arccos((a·b)/(|a||b|))得到。 - 更新所有子关节的位置和朝向。
- 处理下一个关节,重复步骤2-5。
- 循环整个过程,直到误差足够小或达到迭代次数上限。
CCD实现简单、计算快,但可能收敛慢,且解可能不稳定(抖动)。旋转顺序会影响结果,通常从末端向根节点进行。
2. 雅可比矩阵转置法
这是一种基于梯度下降的优化方法。误差函数 E(θ) 的梯度为:
∇E(θ) = J(θ)ᵀ * (f(θ) - x_target)
其中 J(θ) 是前向运动学函数 f(θ) 关于参数 θ 的雅可比矩阵(一阶导数矩阵)。
梯度方向是误差上升最快的方向,因此我们沿负梯度方向更新参数:
θ_{new} = θ_{old} - α * J(θ)ᵀ * (f(θ) - x_target)
其中 α 是学*率(步长)。
这种方法被称为 雅可比转置法。关键在于计算雅可比矩阵 J。对于旋转关节,雅可比矩阵的每一列有几何解释:对于第 i 个旋转关节,其对应的雅可比矩阵列向量等于该关节旋转轴在世界坐标系中的向量 axis_i 与从该关节到末端点的向量 r_i 的叉积:
J[:, i] = axis_i × r_i
3. 伪逆法
将前向运动学在当前点 θ₀ 处线性化(一阶泰勒展开):
f(θ) ≈ f(θ₀) + J(θ₀) * (θ - θ₀)
代入优化目标,并令其导数为零,可推导出参数更新公式。根据雅可比矩阵的形状(行数多于列数“高瘦”,或列数多于行数“矮胖”),需要使用伪逆来求解。
常用的阻尼最小二乘法(DLS)或Levenberg-Marquardt算法通过引入阻尼因子 λ 来稳定求解:
Δθ = Jᵀ (J Jᵀ + λI)⁻¹ * (x_target - f(θ))
或等价形式
Δθ = (Jᵀ J + λI)⁻¹ Jᵀ * (x_target - f(θ))
阻尼因子 λ 确保了矩阵可逆,且当 λ 很大时,方法退化为梯度下降;当 λ 很小时,接*高斯-牛顿法。这种方法也称为 雅可比伪逆法。
全身IK与约束
对于完整的角色,IK问题通常涉及多个末端效应器(双手、双脚、头部等)同时达到目标,这构成了一个多目标优化问题。误差函数变为各末端点误差的加权和:
E(θ) = Σ w_i * ½ || f_i(θ) - x_target_i ||² + R(θ)
其中 R(θ) 是正则项,用于惩罚不自然的大幅度关节旋转,或引入关节角度限制。
通过调整不同目标的权重 w_i 或正则项,可以控制优化行为的偏好,例如:让手臂多动,躯干少动。
总结 🎯
本节课中我们一起学*了角色运动学的核心内容:
- 前向运动学:通过从根节点开始的迭代坐标变换,由关节旋转计算得出每个关节的位置和朝向。公式核心为
P_i = P_parent(i) + Q_parent(i) * L_i和Q_i = Q_parent(i) * R_i。 - 角色建模:将角色抽象为树形结构的关节骨骼系统,理解关节类型(铰链、球窝)和自由度的概念。
- 逆向运动学:给定末端目标,反求关节旋转的优化问题。我们介绍了几种主要解法:
- CCD:循环坐标下降法,启发式,简单快速。
- 雅可比转置法:基于梯度下降的优化方法。
- 伪逆法:基于线性化模型和阻尼最小二乘的优化方法,更稳定高效。
IK算法是角色动画中实现自然运动的关键工具。除了上述基于优化的方法,还有如FABRIK等其他启发式算法。掌握这些基础,将为后续学*更复杂的动画控制技术打下坚实基础。


本节课的作业将围绕这些知识点展开,请关注课程代码库的更新。

GAMES105-计算机角色动画基础 - P4:角色运动学(续)与关键帧动画 🎬
在本节课中,我们将学*角色运动学的进阶内容,特别是动作重定向和全身逆向运动学,并深入探讨关键帧动画中的核心技术——插值算法。
第一部分:角色运动学回顾与进阶 🦴
上一节我们介绍了正向运动学和逆向运动学的基本概念。本节中,我们将进一步探讨动作重定向问题,并了解如何处理更复杂的全身逆向运动学。
正向运动学与参考姿势


正向运动学是从已知的关节旋转计算每个关节位置的过程。一个角色通常被抽象为由关节和骨骼构成的树形结构,从根节点(如臀部)出发,形成多条运动学链。
每个角色都有一个初始姿势,即所有关节旋转为零时的姿态,通常称为 T-pose 或 A-pose。
- T-pose:角色双臂水*伸展,双脚并拢站立,形似字母“T”。
- A-pose:角色双臂自然下垂一定角度,形似字母“A”。动画师在角色绑定时更倾向于使用A-pose,因为它更接*自然站立姿态,能减少皮肤和肌肉在后续动画中的形变失真。
核心概念:一个姿势由根节点的位置/朝向以及每个关节的旋转定义。对于给定的姿势参数,关节的全局朝向 Q_i 可以通过其父关节的朝向 Q_{parent(i)} 和自身的局部旋转 R_i 迭代计算得出:
Q_i = Q_{parent(i)} * R_i
动作重定向
动作重定向的核心问题是:如何将一个角色(源角色)的动作迁移到骨骼结构不同或参考姿势不同的另一个角色(目标角色)上?
我们从一个简单模型开始:两个不同的刚体,它们的初始朝向(旋转为0时)不同。假设刚体A旋转 R_A 后达到目标姿态。如果刚体B的初始朝向与A相差一个旋转 R_{A->B},那么要让B达到相同的目标姿态,所需的旋转 R_B 为:
R_B = R_A * R_{A->B}^T
其中 R_{A->B}^T 是 R_{A->B} 的逆(转置)。
将这个思想扩展到骨骼链上,对于目标角色B的某个关节,要使其摆出与源角色A相同的姿态,其所需的旋转 R_B 与A的旋转 R_A 存在如下关系:
R_B = Q_{parent(B)}^T * R_A * Q_{A->B}
这里 Q_{parent(B)} 是B关节父节点的全局朝向,Q_{A->B} 是A、B两角色在该关节初始全局朝向的差异。
总结:通过计算并应用每个关节上源角色与目标角色在初始参考姿势下的朝向差异,可以实现动作在不同角色间的迁移。对于骨骼数量或命名不同的情况,通常需要额外的手动映射。
逆向运动学与全身IK

逆向运动学是已知末端效应器(如手、脚)的目标位置,反求各关节旋转的过程。这可以表述为一个优化问题:最小化末端位置 f(θ) 与目标位置 x̃ 的误差。
min_θ 1/2 || f(θ) - x̃ ||^2

循环坐标下降法 是一种启发式解法:按顺序依次优化每个关节,每次旋转当前关节,使得末端点更靠*目标点。对于旋转关节,最优解是让末端点、当前关节和目标点三点共线。
雅可比矩阵方法 提供了更数学化的解法。雅可比矩阵 J 描述了末端位置 f(θ) 对关节参数 θ 的变化率。
- 雅可比转置法:
Δθ = α * J^T * (x̃ - f(θ)),其中α是步长。 - 阻尼最小二乘法(伪逆法):
Δθ = J^† * (x̃ - f(θ)),其中J^†是雅可比矩阵的伪逆。

对于铰链关节,雅可比矩阵的每一列可以通过旋转轴与关节到末端向量的叉乘得到。




全身IK 是指角色有多个末端约束(如双脚固定,手需到达某点)的情况。这可以构建为一个包含多个约束的复合优化问题。实践中,常采用启发式方法,例如:
- 将多个末端约束视为多个独立IK链。
- 按优先级顺序或交替迭代求解各条IK链。
- 将根节点视为特殊关节进行处理,并注意旋转方向在父子关系中的转换。


第二部分:关键帧动画与插值技术 🎞️
关键帧动画是手工制作动画的基础。动画师只需定义动作中关键时间点的姿态(关键帧),中间过渡的帧(过渡帧)则由计算机通过插值自动生成。
插值问题可以抽象为:给定一组离散的数据点 (x_i, y_i),求一个函数 f(x),使其经过所有给定点,并能计算任意新 x 对应的 f(x) 值。
以下是几种常见的插值方法:


1. 阶梯插值
这是最简单的插值,函数值在到达下一个关键点之前保持不变。它不连续,会产生跳跃感。
2. 线性插值
在两个关键点之间用直线连接。给定点 (x1, y1) 和 (x2, y2),以及参数 t ∈ [0, 1],插值公式为:
f(t) = (1 - t) * y1 + t * y2
线性插值连续,但一阶导数(速度)不连续,动画可能显得生硬。
3. 多项式插值
使用一个高阶多项式穿过所有数据点。但高阶多项式容易在边缘产生剧烈震荡(龙格现象),且缺乏局部性(修改一个点会影响整个曲线)。
4. 三次样条插值
为了克服高阶多项式的问题,采用分段三次多项式进行插值,并保证连接点处函数值、一阶导数甚至二阶导数连续。这能产生非常*滑的曲线。但求解全局的三次样条需要解一个大型方程组,计算量大且缺乏局部控制性。
5. 三次埃尔米特插值
这是更实用且高效的方法。它也是分段三次多项式,但每一段只由该段起点和终点的 位置 (y) 和 切线斜率 (m) 决定。因此,每一段可以独立计算,具有局部性。
给定一段的起点 (0, y1, m1) 和终点 (1, y2, m2),其三次多项式系数 [a, b, c, d]^T 可以通过求解以下矩阵方程得到:
[0, 0, 0, 1; // f(0) = y1
1, 1, 1, 1; // f(1) = y2
0, 0, 1, 0; // f'(0) = m1
3, 2, 1, 0] * [a, b, c, d]^T = [y1, y2, m1, m2]^T
解出系数后,插值函数可写为四个埃尔米特基函数的加权和:
f(t) = y1*H1(t) + y2*H2(t) + m1*H3(t) + m2*H4(t)
其中 H1(t)...H4(t) 是特定的三次多项式。
卡塔默-罗姆样条 是埃尔米特插值的一个特例,它不直接指定切线斜率 m,而是通过相邻控制点的位置来隐式定义切线方向(例如,P1 点的切线方向为 P2 - P0),使得用户只需操作位置点即可控制曲线。

旋转插值
对于关节旋转的插值,需要特别注意旋转的表示方法:
- 欧拉角/轴角:可以直接对三个参数进行线性或三次样条插值。但需处理角度环绕问题(例如,从350度到10度,应插值为0度附*,而非180度)。
- 四元数:使用球面线性插值可以保证恒定的角速度。其公式为:
SLERP(q1, q2, t) = (q1 * sin((1-t)θ) + q2 * sin(tθ)) / sin(θ),其中θ是q1与q2之间的夹角。
对于更*滑的非线性插值,可以将贝塞尔曲线等构造方法中的线性插值步骤替换为SLERP,从而生成四元数样条曲线。
在实践中,如果关键帧足够密集,使用不同的旋转表示和插值方法产生的视觉效果差异可能并不明显。通常,正确处理角度环绕并使用 SLERP 或三次样条插值就能获得不错的效果。
总结 📚
本节课中我们一起学*了:
- 动作重定向:通过计算并补偿不同角色间参考姿势的差异,实现动作数据的迁移。
- 全身逆向运动学:处理多约束IK问题的方法,包括构建复合优化问题和使用启发式迭代求解。
- 关键帧插值:从简单的阶梯、线性插值,到*滑的三次样条和埃尔米特插值,理解了它们如何生成连续的过渡动画。
- 旋转插值:了解了针对欧拉角、轴角和四元数等不同旋转表示的特有插值方法及其注意事项。

这些技术是构建计算机角色动画系统的基石,从动作数据的处理到最终动画序列的生成都离不开它们。

GAMES105-计算机角色动画基础 - 第五讲:数据驱动的角色动画 📊
在本节课中,我们将要学*数据驱动角色动画的两个核心部分:如何获取运动数据,以及如何使用这些数据来生成流畅、可交互的动画。



第一部分:运动数据的获取 🎬




上一节我们介绍了基于关键帧的动画技术。本节中我们来看看如何通过运动捕捉技术来获取高质量的运动数据。




运动捕捉技术在现代电影、游戏、虚拟偶像、体育训练、医疗康复和机器人等领域都有广泛应用。它通过传感器采集真实人体的运动信息,并将其转化为数字角色的动作。

运动捕捉技术的发展历程
以下是运动捕捉技术发展过程中的几个关键节点:
- 早期动作分析:摄影术兴起后,人们开始使用连拍记录和分析动作。例如,Eadweard Muybridge通过连拍证明了马在奔跑时四蹄会同时离地。
- 转描技术:动画师将真人表演拍摄下来,然后逐帧描摹到卡通角色上。这项技术被用于早期迪士尼电影(如《白雪公主》)和中国第一部动画长片《铁扇公主》中,以降低制作成本并保证质量。
- 三维转描:在三维动画软件中,动画师可以参照视频手动摆出角色的姿势,本质上仍是手动转描。
- 现代运动捕捉系统:通过计算机图形学技术,自动从输入的图像或传感器信息中重建出三维动作。
现代运动捕捉系统类型
现代运动捕捉系统主要分为以下几类,各有优缺点:


- 基于外骨骼的系统:原理是测量穿戴式外骨骼各关节的角度。其优点是原理简单,在手部捕捉中仍较常见。缺点是限制演员动作,且不适合复杂或地面动作。
- 基于惯性传感器的系统:将惯性测量单元(IMU)绑在身体各部位,通过传感器数据计算相对运动,再用逆运动学求解关节旋转。其优点是对场地要求低,活动范围大。缺点是存在积分漂移问题,需要结合脚部着地等先验知识或其他传感器(如磁力计、光流)来校正。
- 基于光学的系统(标记点):这是目前的主流方案。演员穿戴反光标记点,由多个标定好的红外相机同时拍摄。通过多视角几何原理,重建出标记点的三维位置,进而反推关节姿态。其优点是精度高。缺点是需要专用场地、相机数量多、成本高昂,且存在标记点遮挡、丢失、误识别等问题,需要大量后处理。
- 基于光学的系统(无标记点):使用多个标定好的RGB相机,通过计算机视觉算法识别图像中的人体关节点,再进行三维重建。其优点是无须穿戴标记点。缺点是精度和鲁棒性受光照、衣物影响,对脚趾、手指等细节捕捉较难。
- 基于深度相机的系统:如微软Kinect,提供深度信息,可用于实时姿态估计。其优点是成本较低。缺点是信息量有限,在遮挡、转身时容易出错,精度和鲁棒性低于光学动捕。
- 运动估计方法:指从单目视频等信息不足的输入中估计姿态,存在歧义性,通常需要利用大量数据先验进行估计,并非严格意义上的“捕捉”。例如,仅用VR头盔和手柄(3个追踪点)来估计全身姿态,就是一个具有挑战性的研究问题。










第二部分:运动数据的使用 ⚙️
假设我们已经获得了处理好的运动数据,本节中我们来看看如何将这些数据片段组合起来,生成连续的动画。
通常的流程包括:动作重定向、动作编辑、动作连接与混合,最终构建动作图以实现可交互的动画。
动作重定向

动作重定向是将捕捉自演员的动作数据,应用到骨骼结构、比例可能完全不同的虚拟角色上的过程。这是一个复杂的问题,处理不当会导致脚部打滑、浮空、穿模等缺陷。

一个简化的重定向流程如下:

- 建立关节映射:将动捕骨骼的关节与角色骨骼的关节对应起来。
- 调整整体缩放:根据角色腿长等比例,对根关节位移进行缩放,使脚部能接触地面。
- 处理T-Pose差异:计算并应用T-Pose与A-Pose之间的旋转差值。
- 处理多余关节:对于角色有而动捕数据中没有的关节,可将其旋转设为零或取相邻关节的*均值。
- 解决穿模问题:通常需要使用逆运动学进行局部调整。

另一种思路是,不直接复制关节旋转,而是记录并重定向关键点(如手、脚、根关节)的位置,然后在目标角色上使用逆运动学求解全身姿态,可能获得更好的效果。
动作的连接与混合

我们通常拥有多段动作片段,需要将它们*滑地连接起来,例如从走路过渡到跑步。

最直接的方法是在两个动作的相似帧(如脚部着地帧)进行切换,但这会产生突兀的跳跃感。为了*滑过渡,我们可以在切换点前后取若干帧,进行插值混合。


插值公式可以表示为:
P(t) = (1 - α(t)) * P_A(t) + α(t) * P_B(t)
其中,P_A和P_B分别是前后动作的姿势,α(t)是一个从0到1变化的混合权重函数,可以是线性或更*滑的函数(如二次函数)。
然而,如果两段动作的全局朝向或位置不一致,直接插值仍会不自然(如突然转身)。因此,在混合前需要进行动作对齐。
动作对齐与朝向坐标系
我们引入一个朝向坐标系来辅助对齐。该坐标系通常定义如下:
- 原点:根关节在地面上的投影。
- 朝向:通常定义为角色面朝的方向(如局部坐标系的Z轴方向),并且只保留绕世界Y轴的旋转分量。
假设在切换时刻,前一个动作的朝向坐标系为 F1,后一个动作对应帧的朝向坐标系为 F2。我们需要将后一个动作的所有帧,都变换到 F1 坐标系下。
坐标变换公式为:
对于后一动作的每一帧姿势 P,其对齐后的根关节朝向 R' 和位置 T' 可通过下式计算:
R' = R_{1}^{-1} * R * R_{2}
T' = R_{1}^{-1} * (T - T_{2}) + T_{1}
其中,(R, T) 是原始姿势在 F2 下的表示,(R_{1}, T_{1}) 和 (R_{2}, T_{2}) 分别是 F1 和 F2 相对于世界坐标系的变换。经过此变换,两段动作就在切换点实现了对齐,之后再进行插值混合,就能得到*滑的过渡效果。
有些动画数据会预先去除根关节的全局运动,只保留相对姿态。这样在使用时,可以根据游戏逻辑实时计算出根关节的轨迹和朝向,再将动作“放置”上去,简化了连接过程。
动作图
为了管理大量动作片段并实现复杂的可交互动画,我们使用动作图。动作图本质上是一个状态机:
- 节点:代表一个动作片段(或一个姿态)。
- 边:代表两个动作片段之间可以进行的过渡。边的权重通常基于两个连接帧的姿态距离。
构建动作图的一种方法是:给定一段长动作序列,计算所有帧两两之间的姿态距离,形成一个距离图。距离图中的局部最小值点,通常就是良好的潜在连接点。通过设定阈值,可以自动或半自动地构建出动作图。
在运行时,系统根据用户输入、环境状态等信息,实时决定当前动作完成后应过渡到图中的哪个下一个动作节点,从而实现角色对控制的响应。
动作图的优点是直观、可控。缺点是图结构可能很复杂,响应速度受动作片段长度限制,且构建高质量的图需要精心设计或手动处理。


运动匹配
运动匹配是一种较新的数据驱动动画技术。与动作图基于片段切换不同,运动匹配在每一帧(或每几帧)都在整个运动数据库中搜索与当前状态最匹配的下一帧姿态。
匹配准则是一个自定义的距离函数,它同时考虑当前姿态与目标姿态的相似性,以及目标姿态是否满足用户控制需求(如移动方向、速度)。
找到匹配帧后,通过插值混合过渡到该姿态。运动匹配的优点是响应更灵敏、动画质量高,且对原始数据要求较低(不需要预先切割成片段)。缺点是对距离函数设计敏感,可控性相对较弱。




总结 📝

本节课中我们一起学*了数据驱动角色动画的核心内容。
- 在数据获取部分,我们回顾了运动捕捉技术的发展,并详细介绍了基于外骨骼、惯性传感器、光学(有/无标记点)、深度相机等不同原理的运动捕捉系统,分析了它们各自的优缺点和应用场景。
- 在数据使用部分,我们首先探讨了将动捕数据重定向到不同角色面临的挑战和基本方法。接着,我们学*了如何通过插值混合和基于朝向坐标系的对齐技术,将不同的动作片段*滑地连接起来。最后,我们介绍了动作图和运动匹配这两种利用运动数据库生成可交互动画的高级框架,理解了它们的基本原理和特点。

通过本节课的学*,你应该对如何获取和处理运动数据来创造生动的角色动画有了一个全面的认识。

GAMES105-计算机角色动画基础 - P6:Lecture06 基于学*的角色动画 🎓
在本节课中,我们将学*基于学*的角色动画方法。我们将回顾上节课关于可交互动画的内容,并深入探讨如何实现它。接着,我们会介绍一些传统的、基于高斯模型的运动建模方法。最后,我们会简要展望使用神经网络等现代方法生成角色动画的未来方向。
课程回顾与作业安排 📅
上一节课我们介绍了基于数据的角色动画,主要讨论了如何连接、重组现有数据以响应用户交互。其核心并非直接拼接数据,而是将数据放入一个模型中,利用模型来生成动作。
由于上周课程调整,本次课程会先进行一些回顾,这部分内容也与实验二作业相关。
关于作业,第一次作业已于今日截止。截止后,助教会进行批改并提供反馈。作业本身在GitHub上会保持开放,有兴趣可以继续完成,但将不再参与本年度评分。第二次作业将于本周发布,形式与第一次类似,周期约为三周。

实现可交互的角色动画 🎮

上一节我们回顾了基于数据的动画,本节中我们来看看如何具体实现一个可交互的角色动画系统。
我们希望角色能根据用户输入(如摇杆控制)自动生成合理的动作。在物理引擎中,这个过程通常可以抽象为三步:
- 检查用户输入。
- 在数据集中找到符合输入的动作片段。
- 播放该动作片段。
为了支持这一流程,常用“动作图”或“状态机”这类数据结构。其基本思想是:每个节点代表一个动作片段,在不同动作的特定时刻(连接点)可以进行切换。
动作图的工作原理
动作图中的每个节点是一个动作片段,即一小段连续的姿态序列。播放完当前片段后,系统检查用户输入,并选择下一个可切换的片段进行播放。



一个关键问题是如何*滑连接两个片段。这通常需要对两个片段的局部坐标系进行对齐和变换,使得新片段的起始姿态与当前片段的结束姿态自然衔接。

为了*衡动作质量和响应速度,动作图中的片段通常较短(例如,走路只包含一步)。这样可以在片段结束时更快地响应用户的新指令。
以下是一个简单的动作图示例:
- 假设角色当前正在播放“向前直走”的片段。
- 用户输入“向右转”指令。
- 系统会在“向前直走”片段结束后,从可切换的片段中(如“向右走一步”)选择一个,并通过坐标对齐*滑地连接播放。
在动作图的基础上,可以叠加A*等路径规划算法,实现自动寻路、避障等更智能的行为。动作图是底层数据结构,上层可以应用多种算法。
动作图的局限性与Motion Fields
动作图的主要缺点是响应速度受限于片段长度。为了更快响应,需要准备大量能在中间切换的短片段,这会使状态机变得复杂且难以维护。
因此,研究者希望将规划频率细化到每一帧,即在每一帧都根据当前状态和用户输入寻找下一个合适的姿态。这引出了 Motion Fields 方法。



Motion Fields 将动作数据库中的每一帧视为一个独立的状态点,每个点包含姿态、速度、朝向等信息。所有帧构成一个高维的“运动场”。
以下是其工作流程:
- 在每一帧,根据角色当前状态(姿态、速度等),在运动场中寻找K个最邻*的状态点。
- 根据用户输入(如目标方向),为这K个邻*点计算混合权重。
- 使用混合权重,对这些邻*点对应的姿态、速度等参数进行插值,得到下一帧的目标状态。
- 更新角色状态。
这种方法能实现更即时的响应,且无需对动作数据做严格的片段化预处理。但它需要设计规则来计算混合权重,这在实际应用中可能比较困难。
Motion Matching:更实用的实时方法
Motion Matching 是对 Motion Fields 的简化和改进。其核心思想是:每一帧只在数据库中寻找一个最邻*的后续帧,作为下一帧的目标。
需要解决两个关键问题:
- 距离函数:如何定义两个姿态之间的“距离”或相似度?简单的关节角距离效果不佳。通常需要设计特征向量,包含未来轨迹、脚部接触状态等信息,再计算特征向量间的距离。
- *滑过渡:直接切换到非连续帧会导致动作跳变。因此需要将当前姿态*滑地混合到目标姿态,例如使用线性插值或带阻尼的混合方法。
为了提高性能,对大规模数据库需要使用KD-Tree等高效数据结构进行最*邻搜索。同时,可以通过精心设计动作捕捉方案(如使用“舞蹈卡片”)来提升生成动作的质量和覆盖范围。
Motion Matching 能产生非常灵活、响应迅速的角色动画,并能结合简单的物理仿真(如布娃娃系统)来应对外力干扰。但它本身不解决滑步等问题,通常需要结合逆向运动学进行后期调整。
动作建模与概率先验 📊
上一节我们介绍了实时响应的动画生成技术,本节中我们来看看如何对动作本身的“自然性”进行建模。
我们的动作数据是有限的,但希望生成无限的新动作,并保证其自然。人类动作具有明显的低维结构,并非所有随机姿态都是自然的。例如,走路时手脚协调摆动有助于保持*衡。
主成分分析
主成分分析是一种常用的降维技术,用于发现高维数据中的主要变化方向。
其核心思想是:
- 寻找一个单位向量方向,使得所有数据点在该方向上的投影方差最大。这个方向称为第一主成分,包含了数据中最多的信息。
- 在与第一主成分正交的子空间中,继续寻找方差最大的方向,得到第二主成分,依此类推。
对于动作数据,每个姿态可表示为一个高维向量。进行PCA后,前几个主成分往往能解释绝大部分的数据变化。这意味着可以用少数几个主成分的线性组合来*似表示一个自然姿态。
PCA得到的模型可以作为一个动作先验。例如,在逆向运动学中,除了让末端效应器到达目标位置,还可以增加一个正则项,要求生成姿态在PCA空间中的坐标尽可能“正常”(即接*训练数据的分布),从而得到更自然的解。
基于概率模型的运动先验
更一般地,我们可以将“自然动作”视为从一个复杂的概率分布中采样得到的。我们的目标是:给定动作捕捉数据,估计出这个潜在的分布 ( p(x) ),其中 ( x ) 可以是一个姿态或一段动作。
有了这个分布,许多动画生成问题(如IK、动作补全、受控生成)都可以表述为一个统一的优化问题:
最小化:任务损失(姿态) - λ * log( p(姿态) )
其中,第一项确保满足任务要求(如手到达某位置),第二项(即负对数似然)确保生成的动作看起来自然,( λ ) 是权衡参数。


传统方法使用相对简单的概率模型来拟合 ( p(x) ):
- 单一高斯分布:过于简单,无法刻画复杂动作的多模态分布。
- 高斯混合模型:用多个高斯分布的加权和来建模,能表现更复杂的分布,但混合数量等参数需要预设。
- 高斯过程隐变量模型:更为灵活,但核函数等设计较为复杂。





这些基于高斯模型的方法在2016年之前被广泛研究,能够实现一定程度的动作编辑和生成,但模型表达能力有限,且实现和调参较为繁琐。


总结与展望 🚀






本节课我们一起学*了基于学*的角色动画的基础知识。





我们首先深入探讨了实现可交互动画的两种主要数据驱动方法:动作图和Motion Matching。动作图在动作片段级别进行规划,响应速度较慢但质量稳定;Motion Matching 则在每一帧进行最*邻搜索,实现了更即时的响应,是目前工业界广泛应用的技术。



接着,我们探讨了如何对动作的“自然性”进行建模。从主成分分析这种简单的降维与先验模型,到高斯混合模型、高斯过程等更传统的概率模型,这些方法为生成符合物理规律和人体工学的动作提供了数学基础。


然而,传统的概率模型表达能力有限,难以捕捉复杂动作的细节和多样性。*年来,随着深度学*的发展,特别是生成模型(如VAE、GAN、扩散模型)的突破,使用神经网络来学*复杂的动作分布 ( p(x) ) 已成为新的趋势。这些方法能够从大量数据中自动学*更强大、更通用的动作先验,从而生成更丰富、更逼真的角色动画。这将是下节课重点介绍的内容。

GAMES105-计算机角色动画基础 - P7:Lecture06 基于学*的角色动画(续)🎬
在本节课中,我们将继续探讨基于学*的角色动画方法,特别是聚焦于*年来兴起的基于神经网络的技术。我们将从概率建模的角度理解动作生成,并介绍几种核心的神经网络模型及其应用。
概述
上节课我们介绍了基于统计模型(如高斯模型)的角色动画方法。本节课,我们将转向基于神经网络的方法。这类方法的核心思想是:从大量动作捕捉数据中学*一个模型,该模型能够根据当前状态(可能加上一些控制信号)预测下一个合理的姿态,或者直接生成一段完整的、符合要求的动作序列。
我们将首先从概率角度形式化这个问题,然后介绍两种主要的建模视角,最后深入讲解一个经典工作——相位函数网络(PFN)及其后续发展。
从概率视角看动作生成
上一节我们介绍了基于数据的动作重组技术。本节中,我们来看看如何从概率生成的角度来创造新动作。
我们的目标是学*一个概率模型。假设真实的、自然的动作服从一个复杂的概率分布 ( P(X) )。给定一个动作序列 ( X ),( P(X) ) 的值代表了该动作是“自然”的可能性。我们拥有大量动作捕捉数据,可以看作是从这个真实分布 ( P(X) ) 中采样得到的样本。我们的任务就是利用这些样本,去*似(学*)出这个未知的分布 ( P(X) )。
动作的表示
一个动作序列 ( X ) 由一系列姿态在时间上排列而成。每个姿态可以用一个向量表示,常见的有两种方式:
- 基于关节旋转:包含根关节位置和各关节的旋转(如用四元数、6D向量表示)。
- 基于关节位置:直接使用各关节在空间中的三维坐标。
两种表示各有优劣。基于旋转的表示便于直接驱动蒙皮角色,但缺乏全局位置信息;基于位置的表示更直观,但驱动角色时需要额外的逆向运动学(IK)计算。
条件生成与自回归模型
在实际应用中,我们通常希望进行条件生成。例如,给定指令“向左走”,生成向左走的动作。这对应条件概率分布 ( P(X | Z) ),其中 ( Z ) 是控制条件(如移动方向、角色状态等)。
对于序列数据,一个常见的简化是使用链式法则将联合分布分解:
[ P(X) = P(x_1) \prod_{t=2}^{T} P(x_t | x_{1:t-1}) ]
这意味着每一帧的姿态 ( x_t ) 依赖于之前所有帧 ( x_{1:t-1} )。
为了进一步简化并实现实时生成,我们通常假设序列具有马尔可夫性,即下一帧只依赖于当前帧:
[ P(x_t | x_{1:t-1}) \approx P(x_t | x_{t-1}) ]
这样,动作生成过程就变成了一个自回归模型:从初始姿态 ( x_0 ) 开始,反复应用一个函数 ( f ),根据当前帧 ( x_{t-1} ) 和控制信号 ( z ) 预测下一帧 ( x_t )。
[ x_t = f(x_{t-1}, z; \theta) ]
其中 ( \theta ) 是模型参数。我们的目标就是利用训练数据(大量的 ( (x_{t-1}, x_t) ) 对)来学*这个函数 ( f )。
用神经网络拟合映射函数
现在,我们面临的核心问题是:如何学*这个复杂的映射函数 ( f )?这正是神经网络的用武之地。
神经网络简介
神经网络受到生物神经元启发,其基本单元是人工神经元。一个神经元接收多个输入 ( x_i ),计算加权和并加上偏置,最后通过一个非线性激活函数 ( \sigma ) 产生输出。
[ y = \sigma(\sum_{i} w_i x_i + b) ]
将大量神经元分层连接,就构成了深度神经网络。一个 ( L ) 层的前馈神经网络可以表示为一系列复合函数:
[ f(x; \theta) = \sigma_L(W_L \sigma_{L-1}(... \sigma_1(W_1 x + b_1)...) + b_L) ]
其中 ( \theta = {W_1, b_1, ..., W_L, b_L} ) 是所有层的权重和偏置参数。
训练神经网络
我们的目标是找到一组参数 ( \theta ),使得神经网络 ( f ) 在训练数据上的预测误差最小。这定义了一个优化问题:
[ \min_{\theta} \sum_{(x_{t-1}, x_t) \in \mathcal{D}} \mathcal{L}(f(x_{t-1}, z; \theta), x_t) ]
其中 ( \mathcal{L} ) 是损失函数(如L2范数),衡量预测值与真实值的差距。
求解此优化问题的标准方法是梯度下降,特别是适用于大数据集的随机梯度下降(SGD)。其核心是迭代更新参数:
[ \theta \leftarrow \theta - \alpha \nabla_{\theta} \mathcal{L} ]
其中 ( \alpha ) 是学*率,( \nabla_{\theta} \mathcal{L} ) 是损失函数关于参数的梯度。
计算梯度的高效算法称为反向传播,它本质上是链式法则在计算图上的应用。现代深度学*框架(如PyTorch, TensorFlow)都提供了自动微分功能,使得我们可以轻松计算梯度并训练神经网络。
解决歧义性:引入控制与相位
直接训练一个网络根据当前姿态预测下一姿态会遇到一个关键问题:歧义性。例如,一个站立姿态之后,下一个动作可能是迈左腿、迈右腿或原地不动。如果训练数据中包含所有这些可能性,网络可能会学会输出一个“*均”的、模糊的次优结果。
为了解决这个问题,我们需要在输入中引入额外的信息来消除歧义。这通常包括两部分:
- 显式控制参数(Control Parameter):如用户输入的摇杆方向,决定角色移动的目标。
- 相位参数(Phase Parameter):特别是对于周期性运动(如走路、跑步),相位描述了运动周期中所处的阶段(如左脚刚着地、右脚摆动到最高点等)。
以下是引入这些参数的一系列经典工作。
相位函数网络(PFN)
PFN是一个里程碑式的工作,它首次展示了用神经网络可以生成高质量、可控的角色动画。
PFN的核心创新是相位驱动的专家混合模型。它定义了一个相位函数 ( \phi(t) ),将时间映射到运动周期(如0到1)。网络由多个“专家”子网络组成,每个专家擅长预测特定相位区间的姿态。在每一帧,根据当前相位 ( \phi ),通过三次样条插值的方式混合这些专家的参数,形成一个“瞬时专家”,用于预测下一帧。
专家混合(MoE)模型公式(参数混合):
[ \theta_{blend} = \sum_{i=1}^{N} w_i(\phi) \theta_i ]
[ x_t = f_{MoE}(x_{t-1}, z; \theta_{blend}) ]
其中 ( w_i(\phi) ) 是由相位 ( \phi ) 决定的、归一化的混合权重。
这种方式强制每个专家专注于学*运动周期的一个特定部分,从而提高了学*效率和动作质量。

PFN的后续发展

PFN的相位权重是手工定义(基于三次样条)的,这限制了其泛化到非周期性或复杂运动的能力。后续工作主要围绕如何自动学*这个混合权重展开。
- 门控网络(Gating Network):用一个小的神经网络(门网络)来代替手工定义的权重计算。门网络以当前状态和控制参数为输入,输出各个专家的混合权重。
[ \mathbf{w} = G(x_{t-1}, z) ] - 复合相位变量:对于更复杂的运动(如打球),可以定义多个相位变量(分别对应腿、手等),并一起输入门网络。
- 从数据中学*相位:最新的方法尝试直接从数据中学*出有意义的相位表示,而不是手工设计。
这一系列工作通过改进网络结构和输入表征,持续提升了生成动作的质量和多样性。


生成模型在动画中的应用
除了上述直接学*映射函数 ( f ) 的方法,另一条技术路线是直接学*动作数据的概率分布 ( P(X) ) 本身,这类模型称为生成模型。学会分布后,我们可以通过从中采样来生成新动作。
生成模型的核心思想是:学*一个映射 ( G ),将一个简单分布(如标准正态分布 ( \mathcal{N}(0, I) ))中的随机噪声 ( z ),变换成复杂真实数据分布中的样本 ( x )。
[ x = G(z; \theta) ]
这样,通过从简单分布中采样 ( z ),再经过 ( G ) 变换,就能得到逼真的动作数据 ( x )。
以下是几种主要的生成模型在角色动画中的应用:
- 变分自编码器(VAE):同时学*一个编码器 ( E ) 将数据 ( x ) 压缩到隐空间 ( z ),和一个解码器 ( D ) 将 ( z ) 重建为 ( x )。训练目标是使重建误差最小,并让隐变量 ( z ) 的分布接*标准正态分布。可用于动作生成和控制。
- 标准化流(Normalizing Flow):通过一系列可逆变换,将简单分布精确地转换为复杂数据分布。其优势是能精确计算数据的概率密度。
- 扩散模型(Diffusion Model):通过逐步向数据中添加噪声(前向过程),再训练一个网络学*逐步去噪(反向过程),从而学会从纯噪声生成数据。这是当前图像和动作生成领域最火热的方法之一,在文本驱动动作生成等任务上展现出强大潜力。


生成对抗网络(GAN) 虽然在图像生成上非常成功,但在角色动画序列生成中,由于训练不稳定等问题,效果相对不如上述几种模型。




总结
本节课我们一起学*了基于神经网络的角色动画方法。
我们首先从概率建模的角度,将动作生成问题形式化为学*一个条件分布或状态转移函数。接着,我们介绍了如何利用神经网络强大的函数拟合能力来解决这个问题,并简述了神经网络的训练原理。
针对简单神经网络在动作预测中遇到的歧义性问题,我们深入讲解了相位函数网络(PFN) 及其核心思想——通过引入相位变量和专家混合模型来提供更强的时序约束,从而生成高质量周期性运动。我们也概述了PFN后续的一系列改进工作。
最后,我们拓宽视野,简要介绍了生成模型(如VAE、标准化流、扩散模型)在角色动画中的应用。这些模型直接学*动作数据的分布,为更灵活、更富创造性的动作生成开辟了道路。

基于学*的方法,特别是神经网络,正在迅速改变角色动画的制作方式,让数据驱动、可控、高质量的动作生成变得越来越触手可及。

GAMES105-计算机角色动画基础 - P8:Lecture07 蒙皮技术 🎭
概述

在本节课中,我们将学*计算机角色动画中的核心环节——蒙皮技术。我们将了解如何利用骨骼动画驱动角色模型产生形变,从而生成我们最终在屏幕上看到的动画效果。课程将从最基本的线性蒙皮开始,逐步深入到更高级的技术,并结合实际应用案例进行讲解。

蒙皮技术简介
我们之前课程主要关注骨骼动画。骨骼本身是一个抽象结构,并非直接可见。在实际应用(如电影、游戏)中,我们看到的角色外观是由外层的“蒙皮”经过形变后展示出来的。蒙皮技术(Skinning)的核心目标,就是让骨骼的运动能够正确地驱动蒙皮网格产生预期的形变。
整个角色动画流程主要分为两部分:一部分是蒙皮的绑定与变形,另一部分则是骨骼动画本身。骨骼动画驱动骨骼运动,再利用蒙皮技术将运动传递给蒙皮网格,最终渲染出动画。

绑定与蒙皮的概念区分
这里有两个容易混淆的概念:Rigging(绑定)和Skinning(蒙皮)。
- Rigging(绑定):通常指为角色创建一套可控制的“控制器”系统。例如,面部动画师通过调整脸上的控制器点来改变角色的表情或皮肤形状。广义的绑定不限于骨骼驱动,也可能通过额外的点控制器或混合形状来完成。
- Skinning(蒙皮):通常特指由骨骼驱动网格形变的这一过程。它是一种特殊的绑定技术,主要通过刷权重或线性混合等方式,利用骨骼来控制身体形状的变化。
简而言之,蒙皮是绑定的一种具体实现方式,专注于骨骼与网格的关联。

几何表示与蒙皮基础
在讨论蒙皮形变前,需要回顾几何体的表示方式。最常用的表示是网格(Mesh),它由物体表面的顶点(Vertices)和连接这些顶点的面(Faces,通常是三角形)构成。改变物体的形状,本质上就是改变这些顶点的位置。
考虑一个简单例子:一段手臂的蒙皮网格,内部放置了骨骼。我们希望当骨骼(例如肘关节)移动时,其周围的网格顶点也能随之移动,从而让整个角色做出动作。
从数学上描述,我们希望根据骨骼的位移和旋转变换,计算出蒙皮上每个顶点的新位置。
单骨骼情况
假设变换前,骨骼关节位于点 O,全局旋转为 q。变换后,关节移动到 O',旋转变为 q'(q' = R * q),位移为 T(O' = O + T)。
对于蒙皮上的一个顶点 x,其新位置 x' 的计算是一个坐标变换:先将 x 转换到原骨骼的局部坐标系下,再转换到新骨骼的全局坐标系下。
更抽象地,我们可以定义一个量 r,它表示顶点 x 在骨骼局部坐标系下的坐标(即从关节点指向该顶点的向量在局部坐标下的表示)。在单骨骼情况下,无论骨骼如何移动,r 在其局部坐标系下的表示保持不变。当骨骼移动到新位置后,只需将这个局部坐标表示再转换回全局坐标系,即可得到顶点的新位置。



这个过程引出了一个重要概念:绑定姿势(Bind Pose),有时也称为参考姿势(Reference Pose)或静止姿势(Rest Pose)。



绑定姿势(Bind Pose)

我们通常在创建骨骼和模型时,会让它们处于一个特定的、对齐的姿势(如T-Pose或A-Pose)。这个姿势就是绑定姿势。在此姿势下,我们可以计算每个蒙皮顶点相对于每个骨骼关节的局部坐标 r。这个局部坐标信息至关重要,因为在后续的任何姿势下,我们都假设这个局部关系保持不变,并以此为基础进行坐标变换来计算顶点的新位置。


多骨骼与线性混合蒙皮(LBS)
实际情况中,角色拥有多个骨骼。考虑有两个骨骼(如大臂和小臂)的胳膊。一个蒙皮顶点可能同时受到多个骨骼的影响。
在绑定姿势下,我们可以计算顶点 x 相对于骨骼1和骨骼2的局部坐标 r1 和 r2。当骨骼运动到新姿势后,分别用两个骨骼的变换对 r1 和 r2 进行坐标变换,会得到两个新的全局位置 x1' 和 x2'。但顶点本身只有一个,不能分裂。
为了解决这个问题,我们为每个骨骼对该顶点的影响分配一个权重(Weight)。最终顶点的位置是各个骨骼计算出的位置,按其影响权重进行加权*均的结果。
对于一个拥有众多骨骼的完整角色,一个顶点(如腹部)可能受到多个脊柱关节的影响。蒙皮计算就变为:在绑定姿势下计算顶点相对于所有关节的局部坐标;在新姿势下,用每个关节的变换计算出该顶点的一个候选位置;最后将所有候选位置按权重加权*均,得到该顶点的最终位置。
这个权重就是蒙皮权重(Skinning Weights),它定义了每个骨骼对每个顶点的影响程度。
线性混合蒙皮(LBS)公式
上述方法被称为线性混合蒙皮(Linear Blend Skinning, LBS)。其核心公式可以概括如下:
对于一个顶点 i,其在姿势 p 下的位置 v_i' 由以下公式计算:
v_i' = Σ_j ( w_ij * T_j(p) * T_j(bind)^(-1) ) * v_i
其中:
- v_i 是顶点 i 在绑定姿势下的全局位置。
- w_ij 是骨骼 j 对顶点 i 的蒙皮权重(满足 Σ_j w_ij = 1)。
- T_j(bind) 是骨骼 j 在绑定姿势下的变换矩阵(将局部坐标变换到全局坐标)。
- T_j(p) 是骨骼 j 在当前姿势 p 下的变换矩阵。
- T_j(p) * T_j(bind)^(-1) 代表了从绑定姿势到当前姿势,骨骼 j 的相对变换。
这个公式是线性的,计算高效,对GPU友好,因此被广泛应用。在实践中,通常会对每个顶点设置影响骨骼数量的上限(如最多4个),以优化性能。
蒙皮权重(Skinning Weights)
蒙皮权重极大地影响着最终形变的质量。权重的设定通常由动画师通过“刷权重”工具手动完成。这是一个需要经验和审美的耗时过程,动画师需要在各种关键姿势下调整权重,使得形变看起来自然。




也有研究致力于自动计算蒙皮权重,例如基于热扩散的几何方法(如Pinocchio)或基于数据学*的神经网络方法。但这仍是一个开放的研究问题,没有普适的解决方案。




LBS的缺陷与对偶四元数蒙皮(DQS)
LBS有一个著名的缺陷,称为“糖果纸扭曲(Candy Wrapper)”或“肘部塌陷”问题。当骨骼绕其轴旋转较大角度(如180度)时,线性插值旋转矩阵会导致中间状态不是一个合法的旋转,从而产生不自然的塌陷或扭曲形变。

问题的根源在于:对刚性变换(旋转+*移)进行线性插值,无法保证插值结果仍是刚性变换。我们需要一种能在刚性变换空间(特殊欧几里得群 SE(3))中进行正确插值的方法。
对偶四元数蒙皮(Dual Quaternion Skinning, DQS) 正是为了解决这个问题而提出的。
对偶四元数简介
- 对偶数(Dual Number):形式为 a + bε,其中 ε 是对偶单位,满足 ε² = 0。
- 对偶四元数(Dual Quaternion):将对偶数中的 a 和 b 替换为四元数,形式为 q0 + qε,其中 q0 和 q 是普通四元数。
- 单位对偶四元数:可以唯一地表示一个刚性变换(旋转+*移)。
类似于用单位四元数表示三维旋转,单位对偶四元数可以表示三维刚性变换。并且,它也具有“双倍覆盖”性质,即 Q 和 -Q 表示同一个变换,这保证了在插值时可以避免穿过奇异点。
DQS 插值
DQS的核心思想是:将每个骨骼从绑定姿势到当前姿势的刚性变换表示为对偶四元数。然后,对于每个顶点,将其所受影响的骨骼对应的对偶四元数,按照蒙皮权重进行线性混合,最后将混合结果单位化(投影回单位对偶四元数空间),得到一个合法的刚性变换。将此变换应用到顶点在绑定姿势下的位置,即可得到新位置。
这种插值(称为DLB,Dual Quaternion Linear Blending)简单有效,能很好地解决LBS的糖果纸扭曲问题。也有更精确的球面线性插值版本(ScLERP),但DLB在实践中更常用。

DQS的优缺点
- 优点:解决了LBS在关节大角度自转时的扭曲问题。
- 缺点:在关节弯曲(如肘部)时,可能会产生“肘部膨胀(Bulging)”的瑕疵。权重过渡越*滑,此问题可能越明显。
因此,LBS和DQS各有优劣,需要根据具体情况选择。
基于样例的形变与姿态空间形变
另一种提升蒙皮质量的方法是基于样例的形变(Example-Based Deformation)。其思路是:预先为角色制作一系列在不同关键姿势下的高质量形变样例。对于一个新的输入姿势,通过插值这些样例的形变,来获得当前姿势下的蒙皮形状。
这引出了姿态空间形变(Pose Space Deformation, PSD) 的概念。我们将姿势参数(如各关节旋转角)作为输入空间,将顶点偏移量作为输出。通过采集的样例(姿势-偏移量对),我们可以学*或构建一个映射函数。当输入一个新姿势时,通过此函数计算出顶点所需的修正偏移量,将其加到LBS计算的结果上,从而得到更自然的形变。
径向基函数(RBF)插值
对于散乱数据(样例姿势在姿态空间中可能是稀疏且不规则分布的)的插值,径向基函数(Radial Basis Function, RBF) 是一种常用方法。
基本思想是:每个样例姿势对其周围姿态空间的影响,随距离增加而衰减(由基函数 φ(r) 定义)。对于新姿势 x,其顶点偏移 y 由所有样例的加权影响决定:
y = Σ_i ( w_i * φ( ||x - x_i|| ) )


权重 w_i 通过要求该函数在所有样例点 x_i 上的值等于已知偏移 y_i 来求解(解一个线性方程组)。常用的基函数 φ 包括高斯函数、逆二次函数等。
通过结合LBS和基于RBF的姿态空间形变,可以显著改善蒙皮质量,并实现姿态相关的细节(如肌肉凸起、皮肤褶皱)。
应用案例一:SMPL人体模型
SMPL(Skinned Multi-Person Linear model)是一个重要的人体形变模型,广泛应用于计算机视觉和图形学。它结合了前面提到的多种技术:
- 体型形状(Shape):通过对大量不同体型的人体扫描数据做主成分分析(PCA),得到一个*均人体形状和一组主成分向量。通过调整这些向量的系数,可以生成不同体型(高矮胖瘦)的人体。
- 姿态混合形状(Pose Blend Shapes):为了纠正LBS在不同姿态下产生的瑕疵,SMPL引入了一组与姿态相关的线性混合形状。这些形状是姿态参数的函数,作为修正项加到基础形状上。
- 蒙皮:最后使用LBS(或类似方法)将调整后的形状根据骨骼姿态进行变形。
SMPL模型能够仅用少量参数(体型系数和关节角度)就生成高质量、多样化的三维人体网格。
应用案例二:面部动画
面部表情动画是蒙皮技术的另一个重要应用领域。常见方法是使用混合形状(Blend Shapes)。

- 中性脸:定义一个无表情的基础面部模型。
- 混合形状:定义一系列基本表情目标形状(如微笑、皱眉、张嘴),每个形状记录了相对于中性脸的顶点偏移。
- 线性组合:通过调整每个基本表情的权重(系数),将这些偏移量加权组合后加到中性脸上,从而合成出复杂的面部表情。

FinalFace = NeutralFace + Σ_k ( weight_k * BlendShape_k )
这种方法易于控制和实现。更高级的系统可能会使用PCA从扫描数据中获取表情基,或者结合基于物理的肌肉仿真来产生更真实的皮肤细节(如皱纹)。
面部动画驱动

如何驱动这些混合形状的权重?常见方法有:
- 手动关键帧:动画师直接调整权重。
- 面部特征点跟踪:通过摄像头捕捉真人面部的特征点(如眼角、嘴角)运动,求解一个逆问题(IK)来反推出最优的混合形状权重,从而驱动虚拟角色。
- 语音驱动:根据输入的语音信号,直接预测或生成对应的口型与面部表情权重,这是一个活跃的研究领域。








总结


本节课我们一起学*了角色动画中的蒙皮技术。我们从最简单的线性混合蒙皮(LBS)出发,了解了其原理、公式以及固有的“糖果纸扭曲”缺陷。为了克服这个缺陷,我们介绍了对偶四元数蒙皮(DQS)技术。接着,我们探讨了通过基于样例的姿态空间形变(如使用RBF插值)来进一步提升蒙皮质量的方法。最后,我们通过SMPL人体模型和面部表情动画这两个具体案例,看到了蒙皮技术在实际中的强大应用。
蒙皮是连接抽象骨骼与可视外观的桥梁,是生成逼真角色动画不可或缺的一环。



GAMES105-计算机角色动画基础 - P9:Lecture08 基于物理的仿真与铰接刚体 🎮
在本节课中,我们将要学*基于物理的角色动画仿真的核心基础,特别是关于刚体仿真的原理与方法。我们将从简单的质点运动方程开始,逐步深入到复杂的铰接多刚体系统,并探讨如何通过数值积分和约束求解来模拟角色的物理运动。

概述
前面几节课我们主要讲解了基于运动学的方法,即通过直接设置角色的关节角度或位置来生成动画。然而,这种方法难以处理与环境交互强烈或未预先规划的动作。本节课,我们将转向基于物理的角色动画。其核心思路是模拟角色在物理规律(如重力、碰撞)下的真实运动响应,这需要两个关键部分:物理仿真和运动控制。本节将重点介绍物理仿真的基础知识,特别是刚体系统的仿真。
从质点运动到数值积分
上一节我们介绍了运动学与物理仿真的区别,本节中我们来看看如何从最基本的物理定律出发,进行数值仿真。
对于一个质量为 m 的质点,其位置为 x,速度为 v。根据牛顿第二定律,施加力 F 会产生加速度 a:
F = m * a
其中,加速度是速度的导数 (a = dv/dt),速度是位置的导数 (v = dx/dt)。我们的目标是:已知当前时刻 (t=0) 的位置 x0 和速度 v0,以及力 F,计算未来某个时刻 t 的位置和速度。
当力 F 是常数时,我们可以通过解析积分得到精确解(如自由落体公式)。然而,在大多数情况下,F 是位置和速度的复杂函数,无法直接解析积分。此时,我们需要借助数值积分方法。
数值积分的核心是时间离散化。我们将连续时间 t 离散为一系列步长 h 的时刻 t0, t1, ..., tn。目标变为:根据当前时刻 tn 的状态,计算下一时刻 tn+1 的状态。
以下是两种基础的欧拉积分方法:
- 显式欧拉(前向欧拉):使用当前时刻的速度来更新位置。
v_{n+1} = v_n + a_n * h x_{n+1} = x_n + v_n * h - 隐式欧拉(后向欧拉):使用下一时刻的(未知)加速度来更新,需要求解方程。
v_{n+1} = v_n + a_{n+1} * h x_{n+1} = x_n + v_{n+1} * h


显式欧拉计算简单但容易数值不稳定(系统能量增加,物体可能飞散)。隐式欧拉数值稳定但计算复杂,需要求解方程。在实践中,半隐式欧拉(或辛欧拉) 是常用的折中方案,它在许多情况下能保持能量*似守恒且计算高效:
v_{n+1} = v_n + a_n * h
x_{n+1} = x_n + v_{n+1} * h
刚体的运动学描述
上一节我们讨论了质点的仿真,本节中我们来看看构成角色骨骼的基本单元——刚体。
刚体是指在运动过程中形状和大小都不发生变化的物体。描述一个刚体的状态需要两部分信息:
- 位置:刚体上某个参考点(通常为质心)在世界坐标系中的坐标
x。 - 朝向:刚体局部坐标系相对于世界坐标系的旋转,可以用旋转矩阵
R或四元数q表示。
有了 x 和 R,刚体上任意一点 p_local(在局部坐标系中)的世界坐标 p_world 可以通过变换得到:
p_world = x + R * p_local
刚体的运动速度也分为两部分:
- 线速度
v:质心位置x的变化率 (v = dx/dt)。 - 角速度
ω:描述刚体旋转的快慢和方向。旋转矩阵R的变化率与角速度有关:
其中dR/dt = [ω]× * R[ω]×是由角速度向量ω构成的叉乘矩阵。如果使用四元数q表示朝向,其导数关系为:
这里dq/dt = (1/2) * ω_bar * qω_bar是实部为零、虚部为ω的四元数,*表示四元数乘法。
在仿真中,我们根据当前的速度和角速度,通过积分来更新刚体的位置和朝向。
刚体的动力学属性
仅仅知道运动学如何更新还不够,我们需要理解力是如何影响刚体运动的,这涉及到动力学。





与质点类似,刚体也有动量和角动量,但它们与质量分布有关:
- 质量
m:所有质点质量之和,衡量*动惯性。 - 质心:质量分布的*均位置。
- 转动惯量
I:一个3x3的矩阵(惯性张量),衡量绕不同轴旋转的惯性。它取决于质量分布和朝向。在主轴坐标系下,I可简化为对角矩阵。



作用在刚体上的力 F 和力矩 τ 会产生运动变化:
- 线动量
p的变化率等于合外力:dp/dt = F, 其中 p = m * v - 角动量
L的变化率等于合外力矩:dL/dt = τ, 其中 L = I * ω
将这两个方程结合,就得到了描述刚体运动的 牛顿-欧拉方程:
[ m*I, 0 ] * [ dv/dt ] = [ F ]
[ 0, I ] [ dω/dt ] [ τ - ω × (I*ω) ]
其中 ω × (I*ω) 项是由于旋转引起的陀螺效应。这个方程告诉我们,给定外力和外力矩,如何计算刚体线速度和角速度的变化率(即加速度)。
铰接刚体系统与约束
单个刚体的仿真只是基础,角色动画需要多个刚体(骨骼)通过关节连接起来。本节我们来看看如何仿真这样一个铰接系统。
对于多个独立刚体,我们可以简单地将它们的运动方程堆叠起来。但关节的存在意味着刚体之间不能自由运动,它们之间存在约束。例如,一个铰链关节只允许绕一个轴相对旋转。
约束在数学上表示为对系统状态(位置、速度)的限制方程。例如,一个球窝关节要求连接点 P1 和 P2 在世界坐标系中始终重合:
C(x, R1, R2) = (x1 + R1 * r1) - (x2 + R2 * r2) = 0
其中 r1 和 r2 是连接点在各自刚体局部坐标系中的位置。对这个约束方程求导,可以得到速度层面的约束(雅可比矩阵 J):
J * v = 0
这里 v 包含了所有刚体的线速度和角速度。
为了满足约束,关节会产生内部的约束力。根据虚功原理,理想的约束力不做功,其方向与约束的雅可比矩阵行空间一致。因此,约束力可以表示为 F_c = J^T * λ,其中 λ 是待求的拉格朗日乘子(约束力大小)。
现在,完整的铰接系统运动方程变为:
M * dv/dt = F_ext + J^T * λ
J * v = 0 (或用于稳定化的修正项)
这是一个微分-代数方程。通过离散化(例如使用半隐式欧拉),我们可以将其转化为每个仿真步中需要求解的线性系统,从而同时解出下一时刻的速度 v_{n+1} 和约束力大小 λ。
接触与碰撞处理
角色要站在地面上,就必须处理刚体与环境的接触和碰撞。这是物理仿真中最具挑战性的部分之一。
接触处理通常分为两步:
- 碰撞检测:检测哪些刚体发生了接触,并计算接触点的位置、法向和穿透深度。
- 碰撞响应:计算接触力,阻止物体相互穿透并模拟摩擦。
一种简单的方法是惩罚力法,将接触建模为很硬的弹簧和阻尼器:
F_contact = -k * penetration - d * (relative_velocity · normal)
其中 penetration 是穿透深度。k 需要很大才能减少穿透,但这会导致数值不稳定,需要很小的仿真步长。
更稳健的方法是将接触建模为速度层面的约束。与关节约束不同,接触约束是单边的:物体可以脱离接触(速度为正),但不能侵入(速度不能为负)。同时,接触力只能是推力(不能为拉力)。这形成了一个线性互补问题:
v_n >= 0, λ_n >= 0, v_n * λ_n = 0
求解 LCP 可以得到满足物理规律的接触力。现代的物理引擎(如 Bullet, ODE)都包含了高效的 LCP 求解器来处理接触和摩擦。
总结
本节课中我们一起学*了基于物理的角色动画仿真的核心数学与计算基础。
我们首先回顾了数值积分方法,理解了显式、隐式和半隐式欧拉的区别。接着,我们探讨了如何描述和更新单个刚体的运动(位置、朝向、速度、角速度)。然后,我们深入到刚体动力学,学*了质量、转动惯量、动量、角动量等概念,以及核心的牛顿-欧拉运动方程。
为了构建角色模型,我们将多个刚体通过关节连接,引入了约束的概念,并学*了如何通过求解带约束的运动方程来仿真铰接系统。最后,我们简要探讨了使角色能站立于地面的关键——接触与碰撞处理的两种主要思路:惩罚力法和基于约束的LCP方法。

物理仿真为角色动画提供了模拟真实物理交互的强大能力。然而,仿真的最终目的是为了控制角色做出我们期望的动作。如何生成施加在刚体上的力或力矩(即控制策略),将是后续课程的重点。

GAMES106-现代图形绘制流水线原理与实践 - P1:1. 现代图形绘制流水线导论 🎬

大家好,今天是GAMES106的第一节课。首先介绍这门课程,课程名称是“现代图形绘制流水线的原理与实践”。我是惠池,现任浙江大学CAD方向的教师。


介绍我们的授课团队。本次课程由团队授课,共有五位主讲老师。我是CAD方向的霍雨池,同时在光线云和浙江实验室任职。我们还邀请了袁娅镇、高新峰、胡一伟和高涛四位老师共同授课。由于课程强调理论与实践并重,我们邀请了许多从事相关研究和开发的一线人员直接参与讲课。
我主要研究图形绘制,包括真实感绘制、实时绘制、神经绘制和GPU绘制等领域。由于个人兴趣和其他原因,我也从事机器设计视觉和计算光学相关的研究。相关成果发表在TOG、SIGGRAPH、CVPR、NeurIPS等会议和期刊上。
袁娅镇在腾讯游戏任职,主要研究实时绘制和绘制管线。高新峰是腾讯光子(北美)的资深研究员,从事几何计算和网格处理相关研究。胡一伟即将从耶鲁大学毕业并入职Adobe,主要研究纹理和可微绘制相关工作。高涛在向新科技工作,负责跨*台绘制引擎的研发。
我们团队在各自领域不仅进行开发,也发表了大量论文,总计约百篇,涵盖各类顶级会议和期刊。此外,我们还要感谢助教团队。有兴趣的同学可以通过邮件与他们联系或扫码入群。课程主页提供了课程介绍、作业等相关信息,欢迎各位同学访问。

今天第一个问题是开宗明义:GAMES106这门课要讲什么,以及为什么要讲这些内容。

总的来说,这门课是关于图形绘制的。图形绘制大家可能很熟悉,之前的GAMES101等课程也讲过。图形绘制的应用现在非常广泛。
在工业设计中,设计汽车或房屋都需要先用计算机绘制出来,供设计师和工程师进行技术和美学上的交流。艺术创作方面,电子艺术现已登堂入室,许多电子博物馆和线下博物馆都有收藏。CG艺术家创作电子艺术的技术支撑就是计算机图形学。
可视化用途非常广泛,包括IT、医学、气象等各类数据都需要通过图形学方式可视化,以辅助后续研发和分析。影视方面,无论是真人电影的特效还是动画电影,每一帧都由计算机图形学绘制出来。
游戏方面,3A大作比电影等其他类型多了一个可交互的过程,并且主要进行实时绘制。因此,游戏与绘制流水线更相关,因为它对实时性能要求较高。


支撑这些广泛应用场景的是绘制引擎。绘制引擎范围很广,不仅包括游戏引擎,还有其他各种离线引擎,甚至在操作系统和手机上都需要一套绘制引擎来可视化图形元素。例如,Blender等离线引擎使用光线追踪,Unity和Unreal等使用光栅化绘制流水线。
绘制引擎或引擎与流水线不同。引擎处理的是非常综合的业务,包括角色AI、动画、用户交互、网络传输、安全和存储等。引擎中最核心、将其与其他非图形程序区分开的部分是图形部分。图形部分的支撑就是我们今天要讲的内容:引擎是如何将东西真正画出来的。
当我们开发应用时,直接调用引擎来绘制。引擎如何连接硬件呢?硬件包括CPU、GPU、现在的NPU以及显示端的VR眼镜、AR眼镜、CRT、LED等各种显示器。绘制引擎并不直接操纵这些硬件。如果调用过游戏引擎或游戏设置,会发现其中还有一个后端,如OpenGL、DirectX或Vulkan。这就是所谓的绘制引擎后端,也是我们今天要讲的内容:绘制流水线。
绘制流水线可以认为是一个抽象层,它不仅包含软件,还包含一套硬件规范。不同的硬件厂商,包括显示器和GPU厂商,会建立工业流水线标准。基于这个标准和过程化的东西,我们进行统一的规范化,总结出图形API。因此,规范化的硬件绘制过程加上图形API,共同构成了我们的绘制流水线。
绘制流水线向上直接承接算法和具体数据(如几何三角面片、纹理),向下直接连接GPU和显示器。因此,可以认为绘制流水线是进行各种图形绘制、真正画图的抓手和控制点。
如果为当前图形业界的生态画一张图,大概是这样的:最上层有大量海量应用,包括VR、AR、虚拟现实、游戏等。下一层是许多绘制引擎。一个绘制引擎可以支撑很多应用,因此绘制引擎的数量相对较少。然而,绘制引擎无法直接连接硬件,所以中间需要通过一个后端API,即若干流水线标准。这些是业界的统一规范。流水线标准再往下连接五花八门的硬件,具体来说是通过硬件的驱动来连接和控制硬件。
因此,描述当前图形学界的生态,它是一个杠铃形态。杠铃的上端是各种引擎和面向应用层的东西,下端是各种硬件实现层的东西。流水线就是这个杠铃中间的杠杆。


很自然,当我们在开发新算法、制作引擎、实现特效、优化图形程序或进行科研时,首先会考虑在绘制流水线上进行开发。因为一一匹配不同的引擎、应用和硬件是非常麻烦的事情,所以汇聚成了这种形态。
因此,无论是想在产业界满足程序需求,还是想在学术界进行科研,如果想掌握比较核心的绘制技术,一个比较好的出发点就是先学*绘制流水线。这也是我们开设这门课的目的。
接下来举一些例子,说明为什么绘制流水线是GAMES106这样的基础课程,而不是更高级的课程。我们认为图形学是一门实践科学,无论是搞学术还是产业,都是基于当前可用的硬件和算力来设计算法。每个算法、每个引擎的开发都是在硬件的限制下“跳舞”。如果完全脱离硬件设计算法,这些算法设计出来没有太多实用价值,就变成了纯理论研究。因此,无论是做引擎还是算法,首先要考虑硬件。学*硬件,就要了解绘制流水线。
举一个很早的例子:红白机FC,任天堂当年一个很有名的游戏*台。这可能是我们现在接触到的绘制引擎最早的形态。由于红白机的硬件限制,显示时通过8x8的像素块(Tile)拼接整个图像,并且在整个场景中无法同时刷新所有Tile,只能一块一块地刷新。一些连续的动作游戏需要不断变换场景,比如经典的横版过关游戏《洛克人》。当时的技术人员和任天堂的引擎开发人员根据这些技能限制,设计了卷轴式的场景刷新方法。如上图,绿线部分是实际能看到的,但在将画面展示给玩家之前,实际上已经在前方一块一块地刷新了场景。这是一个非常典型的根据硬件进行引擎或绘制开发的例子。
另一个重要的例子是,在产业界或公司中,老板可能要求开发一个新特效,比如粒子系统、天气效果、广告牌或曲面积分,以实现全局光照效果。这该怎么办?还是得通过硬件。首先看硬件能否支撑,然后根据硬件进行相应的开发。一个*期的经典例子是RTX光线追踪。光线追踪或全局光照在学术界已经研究了十几年,但为什么最*几年才在民用市场真正落地?一个关键原因是现在的硬件支持了,即NVIDIA的RT Core(RTX)支持了我们在引擎端部署这项技术,实现这个特效。
看这张图,全局光照与传统的局部光照的区别在于,全局光照可以通过光线的多次折射来模拟更自然的光照变化,更符合物理过程。例如,左边图像的灰阶过渡更自然。如果在右边关闭光线追踪,由于缺少多次全局光照,游戏设计师只能用比较灰白的环境光来填充场景,失去了很多动态和真实的效果。
第三个关键是,绘制流水线可以用于优化性能。优化一段绘制程序或一个绘制效果的性能(例如帧率),首先应该调整与绘制流水线相关的代码、控制代码以及着色器代码。后面会详细讲。
同样举一个例子说明为什么优化图形程序需要与硬件相关。这是一个非常有名的例子:《雷神之锤》中的“卡马克反转”,即快速计算倒数*方根的逼*算法。如果不了解硬件,直接让GPU/CPU计算*方根再求倒数,可能会有大问题,因为GPU/CPU没有这么多运算资源来做倒数和开根运算。该核心算法使用牛顿迭代法进行泰勒展开来*似求解。具体细节在此不展开,网上有很多讨论,也有论文专门研究为什么一些“魔法数字”能产生效果。有兴趣的同学可以查找,后续课程有机会也会展开讲。
在科研部分,绘制流水线支撑了各类图形绘制算法。我们进行图形绘制或图形绘制算法开发时,大部分是在流水线上进行的,无论是实时还是离线。因为引擎是一个非常抽象的封装,大部分面向脚本语言、艺术家和面向应用层的开发人员。真正进行图形算法开发,自然需要走到更下一层,即绘制引擎的支撑层——绘制流水线。当然,更下一层也有一些程序员直接搞驱动、搞汇编,但要求较高。作为普通开发人员或科研人员,我们大部分还是在绘制流水线上进行开发。
另外,绘制流水线本身也是一个重要的研究对象。因为绘制流水线在工业中用途广泛,包括各种引擎、硬件和算法都要与绘制流水线打交道。如何提升绘制流水线的效能本身就是一个重要的科研问题。例如,有些工作优化其I/O,有些优化能耗,有些优化并行度,还有些在流水线上进行可微分析以实现全局优化。这些在后续有机会时会详细介绍。

另一个重要的例子是,绘制流水线支撑科研数据集的生成。例如,交通仿真或实际绘制算法。因为这些数据的生成非常定制化,一般的游戏引擎很难产生这种特意定制化的数据。所以,在进行机器学*相关开发时,也会用到绘制流水线的相关知识。此外,无论是做流体、物理还是其他研究,最终将科研结果可视化出来也需要支撑工具,这也是绘制流水线可以做的事情。

简单总结:绘制流水线是支撑工业需求和学术需求的杠杆和入门砖。我们这门课程主要就是讲这个事情:怎么用,以及它背后的理论和与硬件相关的原理是如何发展过来的。

以上回答了第一个问题。接下来是第二个问题:我们首先讲了背景,现在想一想,如何认识绘制流水线,它到底是什么东西。

先说“流水线”的概念。提到这三个字,最容易想到工业流水线:工厂里有很多工人,一个接一个地执行特定操作。A操作员接收一堆物料进行操作,然后将结果交给B操作员,B操作员接收A的输入再进行操作。基本上是一个顺序操作。当然,真实的流水线包括图形绘制流水线,中间有很多工序交叉和互相协作。但从非常抽象或高层的角度理解,流水线就是给一堆物料,然后产生一个结果,这样一个工序化、按步骤进行的过程。
具体到图形绘制领域,我们输入的主要物料有三个:几何、纹理和光照。我们给一个物体的形状、外观以及当前场景的光照环境,然后进行计算,产生图片。这是一个固定的绘制流水线。写成一个函数或方程的话,如下:
Image = Pipeline(Geometry, Texture, Lighting)
其中,Pipeline 代表绘制流水线,输入是几何、纹理和光照,输出是图片。
到了后期,我们涉及可编程的绘制流水线。就是说,我们现在不仅希望生产固定的东西,还希望可以替换里面的“操作员”,进行各种操作,对流水线产生的结果进行定制化变化。因此,在可编程的绘制流水线中,多了一个新的输入:着色器语言。着色器是一段程序代码,用于控制或个性化绘制流水线的中间过程,产生更丰富的效果。
将整个图形绘制流水线画出来,大概长这个样子:首尾相连,一块连一块的不同模块。每个模块都有相应的硬件支撑。调用这些模块时,需要给定正确的输入。
从左边开始,流水线的入口一般是先给一些几何表达,即场景的几何表达。几何通常用顶点和索引来表示:每个三角面片在世界中的位置,然后用索引将这些顶点连接成三角面片带。
装配好之后,这些顶点被送入顶点着色器。顶点着色器对这些三角面片进行转换,例如模型变换、视图变换等,改变其形状或视图。
中间有一些省略号,因为可能还有几何着色器等可选部分,此处不深入细讲。
第三部分,我们将顶点送到光栅化阶段。光栅化在之前的课程(如GAMES101)中应该提到过,即将三角面片投影到屏幕空间,将其像素化。
下一部分,此时顶点已经变成了像素,所有计算都转到像素上进行。
经过光栅化后,流水线的输出变成了一堆在每个像素上的“资产”。
到了片元着色器阶段,此时会有新的材料输入进来:纹理和光照。这两个材料在片元着色器阶段输入。光照计算就在此处进行,结合几何、纹理和光照,计算每个三角面片或像素最终应呈现的颜色。
最后,将不同的图层(包括背景)叠加起来,输出到帧缓冲区。帧缓冲区直接与显示设备对接,产生最终的图片。
在这个流水线中,有些地方是固定的,有些地方是可编程的。图中蓝绿色的地方是固定管线,由厂商或工业标准固定下来,认为不需要太多定制化操作,因此用非常高效的方法实现。中间的顶点着色器和片元着色器部分可以进行各种编程。我们向这些部分输送着色器程序,就可以进行各种操作。
因此,第四个元素——着色器程序也加入了进来。
总体看一下整个绘制流水线,对应刚才的函数,管线本身(Pipeline)加上几何、纹理、光照和着色器。我们根据这几个部分的不同,对课程进行了划分。
首先,课程0是准备部分,主要讲如何配置API相关的东西,这部分由高涛主讲。
第二部分是管线对象,讲如何配置管线或对管线输出进行重新组合、排列和调制优化,这部分由袁娅镇主讲。
第三部分,高新峰主讲几何部分,即如何对几何进行处理和优化以提高效率。
最后是纹理部分,由胡一伟主讲,他一直从事纹理相关工作。
我会主讲着色器语言部分。着色器会与光照一起讲,因为光照输入形式相对简单,并且与着色器语言耦合比较紧密,大部分情况下会直接作为参数给着色器语言。
进行一个感性的分解:在图形API部分,我们主要做“加法”,即如何将一个场景构建起来,如何使用这些工具,给定原始物料后如何生产出来。到了后面的管线对象、几何、纹理、着色器部分,我们主要做“减法”,即给定一个管线生产出东西,但这些东西在计算机或给定硬件上可能跑不起来。我们如何对每一部分进行对应的优化和分析,使每个部分都达到很好的效率。
最终,我们希望在前面的构建部分产生一个非常精美(虽然不是特别复杂)的场景。在优化部分,对这些先进技术进行拆解,让大家了解对这个复杂绘制过程的每一部分、绘制管线的每一部分可以进行哪些操作、研究和检索。大体就是这个样子。
回到第二个问题的答案:绘制流水线是怎样的结构?它有很多模块,按步骤输入几个重要的输入(几何、纹理、光照和着色器语言),最终输出一张图片,这是一个标准化的流程。



今天课程的第三部分讲一些比较轻松的话题。因为今天是导论部分,我们讲一下绘制流水线是如何一步步演变成今天这样的,它背后的驱动力其实也是与硬件、软件以及产业界的需求紧密相连,才逐渐发展成今天这个样子。

首先讲一个背景:经典绘制流水线是如何产生或为什么产生的?我们刚才提到了很多像光栅化的东西。不知道大家有没有做过软光栅化,即用CPU程序做三角面片的投影和插值,实际产生每个像素的颜色。这个东西不需要GPU,在CPU上也可以做。
CPU和GPU的最大区别是:CPU顺序执行,更像传统想象中的流水线;GPU并行执行,即流水线不是一条线,上面有很多“工人”,可以同时有很多并行的流水线,一把处理掉几百万、几千万个三角顶点,画出来。这就是GPU和CPU的最大区别。
很自然,绘制最初是在专用市场,但要进入民用市场或更多应用领域,必须提速。这时就有了GPU进行并行图元绘制。想到的一个方法是:用CPU控制GPU,因为在早期,特别是还没有标准化的GPU编程语言时,GPU不太容易编程。一个比较好的方法是将GPU的绘制过程固定下来,做成标准化的东西,然后让CPU控制GPU。CPU将要处理的数据、面片、纹理等打包好,一发指令,GPU就按顺序执行。这就是最早的固定绘制流水线。
这大概要追溯到上世纪90年代。那时,图形学还没有进入消费级市场,主要在一些高端图形工作站上应用,在产业界用得比较多。一个比较有名的公司是SGI,地位有点类似于今天的NVIDIA。SGI主要做图形工作站,用于影视级绘制,比如很早的经典电影《侏罗纪公园》就是用SGI工作站做出来的。
SGI不仅做硬件,还做图形API。为了方便编程,它有一个专利性的图形API叫做IRIS GL。后来,SGI觉得这个API自己用对产业界影响太小,认为如果统一工业界的API,可以在跨硬件、跨*台上使用,对推动整个业界发展甚至民用化会有很大帮助。所以,它基于自己的Graphic Language,做成了一个开放标准,推动了开放标准的诞生。这就是最早的OpenGL 1.0版本,于1992年发布。
OpenGL并非由SGI一家公司推动,而是成立了一个叫OpenGL ARB(Architecture Review Board)的成员组织。初始成员包括康柏、IBM、Intel、微软、DEC等大公司,之后惠普、Media TI等也加入。OpenGL实际上是第一个跨*台、跨硬件的编程接口,它将整个图形绘制过程规范化。它不仅用于三维图形绘制,还可以做二维图形绘制。因此,有时在操作系统和UI中进行的各种2D工作也可以通过OpenGL完成。它在很大程度上推动了图形化界面和图形化应用的发展。
它支持的*台包括Windows、Unix、Linux、Mac OS以及手机(主要是OpenGL ES)。用户或程序员只需要开发一套针对OpenGL的算法,就可以在所有硬件上运行,这是一件非常美好的事情。
右边这张图是它的简单框架。OpenGL定义了一个标准,它有很多具体实现。Mesa 3D就是一个非常有名开源的OpenGL实现。它会对接下面的系统层的一些调用和驱动,产生各种具体操作。这些驱动再去驱动各种显示设备、计算设备以及内存的使用。
到了93年底,一些重要的游戏开发出来,3D游戏一时之间变得很火爆。SGI的一些工程师离开公司,创办了创业公司3Dfx,推出了Voodoo卡,即3D加速卡。如果是当年看电脑报或电脑之家的老用户,应该对Voodoo卡比较熟悉。

3Dfx推出的Voodoo卡技术很牛逼,但也很封闭,不想用OpenGL。它推出了一个叫做Glide的图形API,只能用于自己的显卡。虽然硬件很牛逼,但引起了图形学界一些重量


GAMES106-现代图形绘制流水线原理与实践 - P10:纹理生成与压缩 🎨

在本节课中,我们将要学*纹理在计算机图形学中的核心作用,探讨如何生成纹理,并深入了解在实时渲染管线中高效使用和压缩纹理的关键技术。
什么是纹理? 📖
上一节我们介绍了课程概述,本节中我们来看看纹理的基本定义。纹理是计算机图形学中一个非常常见的单元。
在整个渲染流水线中,纹理通常与材质联系在一起。如果一个场景只有几何建模而没有纹理,那么渲染出来的结果就没有颜色,甚至无法被渲染。纹理或材质定义了几何物体如何与光进行交互,在整个渲染过程中是不可或缺的。
从更细节的程序角度定义,纹理通常被表达为一个多维的固定数组,例如一维、二维或三维数组。数组中可以储存不同类型的元素,如浮点数或整数。它本质上是一个固定的多维数组。除了表达材质,纹理还可以表达深度信息或帧缓存等数据。本节课我们主要围绕材质的表达来展开,因为这是纹理最主要的应用领域。
在图形学渲染中,几何和材质都是渲染出真实感图像的关键组成部分。材质在数学上一般定义为双向反射分布函数。如果同学们学过图形学基础课程,对此应该不陌生。它是渲染方程中的一个核心组成部分。
渲染方程定义了一束光如何与一个不透明的几何表面进行交互。要计算某点的出射光,需要对所有入射光进行积分。而双向反射分布函数就定义了入射光的反射率。通常它是一个4维函数。这是因为反射率必须由入射方向和出射方向共同定义,每个方向可以在球面坐标系中用两个角度参数化,因此一共是四个维度。
然而,真实世界中的物体通常由多种材质组成,例如一面墙会因为风化腐蚀,各点的材质都有所变化。为了描述材质的空间变化,我们需要引入新的变量 x,它定义了材质在空间上的分布。对于每个空间点 x,都有一个对应的双向反射分布函数。因此,我们可以定义空间变化的双向反射分布函数。由于加入了空间分量 x,这个函数可以看作一个6维函数。
无论是4维还是6维,这样的函数维度都非常高。要描述这样的函数,通常需要一个巨大的查找表来储存,例如一个4维或6维的数组。这种表达方式在实时渲染或真实感渲染中很少直接使用,因为一方面难以表达,另一方面精确测量的代价很大。

因此,在实时渲染中,通常不会直接使用高维的双向反射分布函数,而是使用解析模型去逼*它。例如大家熟悉的冯模型,就是一种通过直觉建立的简单解析函数。

解析函数的优点在于,它将原本的4维函数降维到了几乎零维。原来的双向反射分布函数需要精确记录每个入射和出射方向的关系,而解析函数只需要储存几个有限的参数。例如冯模型可能只需要储存一个光泽度参数。因此,它的维度从一个四维降到了零维,几乎不需要任何储存空间。
在现代渲染中,最经典的基于物理的渲染工作流也是由这种解析模型构成的。一个常见的双向反射分布函数通常由两部分组成:漫反射分量和镜面反射分量。镜面反射分量通常可以用一个微表面模型来描述,例如经典的GGX法线分布函数。完整的模型可能包含D、F、G三项。
使用这样的解析函数,我们可以将维度极大地降低。定义一种材质可能只需要几个值,例如基础色、金属度、法线方向和粗糙度。这样,我们就把一个4维函数降到了零维,仅通过有限的变量就能表达一种材质。
因此,表达空间变化的双向反射分布函数也变得非常简单。我们可以把一个6维的空间变化的双向反射分布函数,降低成一个二维的纹理。我们看到的许多纹理表达,本质上都可以理解为空间变化的双向反射分布函数的表达。它会将基础色、金属度、法线方向和粗糙度等值用一张2D图储存起来。每一张2D图就是一张2D纹理。
因此,一个6维的空间变化的双向反射分布函数就被降维成了一个二维的多通道纹理。一般来说,一个用纹理表达的基于物理的渲染材质可能包含各种各样的纹理图。
以下是几种常见的纹理图类型:
- 基础色图:定义表面的基本颜色。
- 法线图:通过RGB颜色编码表面法线的微小变化,模拟凹凸细节。
- 粗糙度图:定义表面不同区域的粗糙程度。
- 金属度图:定义表面不同区域的金属属性。
- 高度图:以灰度值表示表面的高度信息。
- 环境光遮蔽图:预计算的环境光遮蔽信息。
- 各向异性图:定义表面各向异性的属性。
- 透明度图:定义表面的透明区域。
这些多通道纹理共同表达了一个基于物理的渲染材质。比起传统的空间变化的双向反射分布函数表达,用纹理表达材质更加紧凑。因此,纹理被广泛地应用于实时渲染中。
纹理是如何生成的? 🛠️
上一节我们介绍了纹理的基本概念及其与材质的关系,本节中我们来看看纹理是如何生成的。纹理的生成方法是多种多样的。
我们刚才看到,很多纹理是2D图像。从图形学发展初期开始,如果不考虑风格化渲染,我们最初的目标就是真实感渲染。因此,我们需要纹理和材质表达能够更好地模拟真实世界的物体。最直接获得纹理材质图的方法就是用相机或手机拍照。
在传统方法中,我们可以把拍到的照片通过某种方法转化为纹理图。一个非常简单的方法是拍摄一个*面的局部照片,例如砖墙、地面或皮肤,然后通过处理将其转化为可用的材质图。例如,基础色图实际上是将一张可能包含光照信息的RGB图片,转化为只保留物体原本颜色的图片。本质上,这可能是一个将RGB图片转化为多通道图的过程。你也可以理解为使用不同的滤镜生成不同的图,例如法线图滤镜。
在最初,艺术家可能使用Photoshop等软件手动处理。但这种方法得到的结果可能不是完全基于物理的,即不是非常精确地按照物理规律重建出真实的纹理。在大多数工业界渲染场景中,需求通常是“视觉上合理”,而不是要求与物理测量完全一致。因为要精确还原材质,需要知道拍照时的光照、位置等信息,这非常困难。
因此,纹理生成通常是通过图片转换,但这个过程非常讲究技巧。将纹理贴到几何体上时,还需要考虑各种因素。于是,各种算法应运而生,帮助艺术家生成材质纹理。
如果你对计算机图形学研究有所了解,会知道最早的算法之一是纹理合成。问题在于,当你只能拍到一个物体局部的小块纹理时,如何生成更大的纹理图像?例如,你只拍到一面有裂缝的墙的局部,如何生成一整面墙的纹理?这就是经典的纹理合成算法。
需要注意的是,纹理合成与超分辨率重建有本质区别。纹理合成不改变纹理的物理尺度。我们不是将一个小块纹理放大,而是希望在不改变其尺度的情况下,生成更多相似的纹理块。例如,我们拍了一块地砖,希望在地面上铺满十块这样的砖,而不是把一块砖放大十倍贴上去。
纹理合成算法的一个明显缺点是,很多算法并不完美,可能存在肉眼可见的瑕疵。此外,整体分辨率通常偏低。早期的研究通常在512x512左右的分辨率下进行,但对于现代游戏或电影设计,通常需要至少1K、2K甚至4K、8K的高分辨率材质。将传统纹理合成算法应用到高分辨率时,运行速度会变得非常慢。
为了克服这些问题,现在很多材质设计采用了过程式纹理生成。过程式纹理是通过程序计算生成的纹理。大家可能知道一些简单的过程式纹理,例如棋盘格或柏林噪声。但在工业界中,常用的是通过节点图的方法生成纹理,例如Blender中的Shader Editor或Geometry Nodes。
这种方法通过一段程序(节点图)计算出复杂的图案。我们可以从一些基础噪声开始,通过混合、调色、调整图案分布等操作,最终组合成一张看起来非常真实的图像,但它是过程式生成的。
以下是过程式纹理的主要优点:
- 极强的可编辑性:节点图中的每个参数都是可编辑的,可以轻松调整纹理的尺度、颜色、脏旧程度等。
- 理论上无限的分辨率:纹理是计算出来的,分辨率只是一个参数,可以设为任意值,如8K。
- 无限尺度的细节:可以支持非常精细的细节。
- 交互式设计:在优化的节点图系统中,可以实时编辑参数并查看渲染反馈。

然而,过程式纹理最大的缺点是设计困难。从网上搜索或拍照获得素材相对容易,但设计一个复杂的节点图需要专业背景和经验。像Adobe Substance这样的软件套件支持过程式纹理生成,其笔刷工具本质上也是过程式的,允许用户在几何模型上程序化地绘制纹理,简化了设计流程。但总体来说,设计过程式纹理依然具有挑战性。

因此,大量研究致力于简化操作,例如从真实世界拍照或上网搜索材质,然后快速生成高清材质。随着深度学*的兴起,出现了新的可能性。例如,能否直接用手机拍摄物体,然后使用深度学*方法学*其物理属性,从而生成正确的材质图?这样在渲染系统中重新渲染时,结果会与真实照片非常相似。
此外,将*面纹理贴到复杂几何体上也很复杂。现在有很多工作直接进行几何和纹理的联合重建,例如利用微分渲染或神经辐射场等技术。
还有一些工作研究如何更好地对纹理进行编辑。过程式纹理的优势在于可以通过调参进行编辑,而对于一张普通的贴图,传统方法可能只能通过Photoshop进行逐像素编辑,效率低下。因此,研究者探索基于样例或文本的方法进行纹理编辑。
*年来,随着扩散模型等生成式AI的出现,整个纹理生成的工作流程发生了巨大变化。例如,艺术家可以先用手机拍照,裁剪出几个小块,拼合成更大的纹理。但这样合成的纹理可能存在接缝。使用扩散模型进行生成后,可以无缝地处理这些接缝,然后再通过材质图转化工具生成基于物理的渲染材质图。
在AI时代,甚至不需要从真实世界获取纹理。完全可以通过文本描述,利用扩散模型生成大量变化的纹理,然后从中挑选。结合ControlNet等技术,可以获得各种2D*面纹理。现在还有许多研究致力于直接生成3D物体的纹理。
最后,回到过程式纹理。过程式纹理本质上是一段程序,节点图就是由小函数连接而成的程序。考虑到现在Transformer架构在程序合成方面的强大能力,直接生成表达过程式纹理的程序也成为可能。从去年开始,已经有一些研究工作利用GPT或Transformer架构生成程序,来表达程序式纹理。因此,在AI时代,纹理生成的可能性非常广阔。
纹理的使用与压缩 💾
上一节我们探讨了纹理的各种生成方法,本节中我们来看看生成纹理后,如何在实时渲染管线中正确使用和高效压缩纹理。
纹理的使用看似简单,因为纹理是一个非常基础的单元,在渲染管线中大量的纹理操作通常已经被硬件或API集成。但如何“正确”使用纹理,实际上有更深层的考虑。

从理论角度来说,纹理用于逼*自然界中连续的材质信号。我们拍照得到的纹理,是对自然界连续信号的离散化采样。访问纹理,实际上是对这个离散化信号的重建,目的是重建出原始的连续信号。因此,我们访问纹理时,通常需要访问0到1之间的小数坐标,并进行插值。
一个很重要的问题是采样频率。如果采样频率不足,会产生走样问题。例如,当远处的纹理像素在屏幕空间分布非常密集时,表示该处的信号频率非常高。如果只是简单地使用最*邻点采样,会产生走样的锯齿状图案。
因此,我们通常需要进行滤波。滤波可以预消除高频信号,使结果看起来更*滑。例如,使用双线性滤波后,远处虽然会变模糊,但不会产生奇怪的锯齿图案。为了对抗锯齿,我们需要抗锯齿或反走样技术。

在实时渲染中,一个核心思想是:很多东西如果能预处理,就尽量预处理,以提升运行时速度。纹理通常与几何绑定,且在运行时不会改变。因此,我们可以对纹理进行预处理。
一个关键的预处理技术是生成Mipmap。Mipmap的本质就是预滤波。我们使用滤波器对原始纹理进行滤波,生成一系列分辨率逐级减半的图片。Mipmap 0是原图,Mipmap 1是长宽各减半的图,以此类推,直到最小如1x1的图。
在采样时,我们可以根据需要在不同层级的Mipmap上进行采样。例如,当信号频率高(如远处)时,就采样更高层级的Mipmap,因为那层已经滤除了高频信号,看起来会更*滑。虽然Mipmap比起后面要讲的各向异性滤波,在斜面上仍有模糊现象,但它能有效避免远处出现大量锯齿。
Mipmap的生成通常由API自动完成,但要求纹理的维度是2的幂次方,因为每次长宽都减半。Mipmap需要额外的储存空间,大约占原始纹理的1/3。考虑到它带来的质量提升,这额外的空间通常是值得的。
那么,如何决定采样哪一层Mipmap呢?核心思想是:纹理在屏幕空间上变化越剧烈(频率越高),就需要采样更高层级的Mipmap。
在GPU管线中,执行像素着色时是以2x2的像素块为单位并行计算的。这方便硬件计算屏幕空间梯度。我们可以得到纹理坐标在屏幕x和y方向上的梯度。通过一个经验公式,可以根据这些梯度的大小估算出应该采样的Mipmap层级。梯度越大,说明纹理坐标在屏幕空间变化越剧烈,频率越高,因此需要采样更高层级的Mipmap。这种方法在视觉上是合理的。
但是,Mipmap存在一个问题:它假设滤波区域在纹理空间是圆形的(或正方形的)。然而,当表面倾斜时,屏幕空间的一个像素可能对应纹理空间的一个椭圆形区域。如果直接用Mipmap进行各向同性滤波,在斜面上会导致过度模糊。
因此,我们需要一种更好的采样方法:各向异性滤波。各向异性滤波考虑了屏幕空间与纹理空间的这种差异。它的思路是,采样时遵循一个椭圆形的区域,而不是圆形。这样,在斜面上也能得到更清晰的结果。
各向异性滤波通常需要实时采样纹理坐标周围不同数量的采样点。采样点越多,重建的信号越精准,结果越清晰,但计算代价也越高。从最*邻采样,到2个、4个、8个采样点,质量会逐步提升。
接下来讨论纹理压缩。我们之前提到,现代渲染需要高清材质,例如4K纹理。一张未压缩的4K RGB纹理(每通道8位)需要占用约48MB内存。加上Mipmap,可能需要约64MB。如果是HDR贴图(浮点数格式),占用空间更大。一个完整的基于物理的渲染材质包含多张贴图,如果不压缩,GPU显存将不堪重负。
因此,我们需要尽可能压缩纹理。虽然纹理在硬盘上通常以JPEG、PNG等格式储存,但这些通用图像压缩算法不一定适合在GPU中使用。
纹理有一个非常重要的特性:它需要在GPU中被快速、随机地单点访问。像素着色器是并行执行的,我们需要一个查询:输入UV坐标,快速返回纹理值。
因此,一个适合GPU的纹理压缩算法需要具备以下特性:
- 解码速度极快:纹理查询非常频繁,解码必须高效。
- 支持随机访问:解码一个像素时,最好只依赖其局部周围的数据,而不需要访问很远的数据。
- 可变压缩比:为了适应不同*台(如桌面端大显存和移动端小显存),需要能提供不同的压缩质量。
传统的JPEG、PNG或基于小波变换的压缩算法很难满足这些要求。因此,我们需要专门的纹理压缩算法。
纹理压缩算法通常基于块。它的一个巨大优势是访问的局部性:解压一个像素时,只需要访问其所属的数据块。常见的算法家族有BC、ASTC、ETC2等。
它们的基本思想是将图像分成多个像素块(如4x4),然后将每个块压缩为一个固定长度的数据。例如,ASTC算法可以将不同数量的像素块压缩为128比特,从而达到不同的压缩比。比特率越高,图像越清晰。
以BC1格式为例,它用于压缩一个4x4的RGB数据块。原始数据占用48字节。BC1将其压缩为:两个16位的代表色(共4字节),加上16个2位的索引(共4字节),总计8字节,达到了6:1的压缩比。解码时,根据索引值,在两个代表色或其混合色中选择一个,操作非常简单快速。
除了BC家族,还有ASTC、ETC2等多种编码。问题在于,为了部署到不同*台,我们可能需要为同一张纹理准备多种压缩格式的副本。为了解决这个问题,出现了像Basis Universal这样的通用纹理交换系统。它可以生成一种中间压缩数据,并能快速转码为其他压缩类型,从而避免储存多个副本,节省存储空间。

最后,我们思考一个理论上的极限压缩。回想我们之前提到的解析材质模型,它将一个4维的双向反射分布函数压缩到了零维(几个参数)。那么,一个2D纹理能否也压缩到零维呢?理论上是可以的,那就是过程式纹理。过程式纹理只需要储存几个参数或一段程序,储存开销几乎为零。
那么,为什么实时渲染中不直接大量使用过程式纹理,而是先烘焙成纹理贴图再使用呢?原因在于纹理使用的核心要求:
- 无法高效预滤波:过程式纹理每个点都是实时计算的,难以像位图那样预先生成Mipmap。实时进行滤波计算代价很高。
- 难以进行快速点查询:过程式纹理的求值可能依赖于全局计算,无法做到仅根据UV坐标就快速返回单个像素值。为了得到一个像素的值,可能需要计算整个纹理,这违背了纹理压缩的初衷。
- 复杂过程式纹理求值慢:对于复杂的节点图,实时求值可能很慢,难以满足游戏高帧率的要求。
因此,尽管过程式纹理在储存上优势巨大,但由于解码困难、无法快速点查询、难以预滤波等缺点,使其难以在实时渲染中直接使用。当然,也有一些研究尝试克服部分问题,例如尝试推导某些过程式纹理滤波的解析形式,或用神经网络逼*滤波过程,但泛化性不强。
课程总结 📝
本节课我们一起学*了纹理在图形学中的核心知识。

首先,我们明确了纹理的定义,并重点探讨了纹理与材质之间的关系。纹理是表达材质空间变化的重要工具。
其次,我们深入探讨了纹理的生成。获取纹理的方法多种多样,包括从照片转换、设计过程式纹理,以及利用现代AI工具如扩散模型进行生成。

最后,我们学*了纹理的使用和压缩。在实时渲染管线中使用纹理时,需要考虑滤波和频率问题,因此引入了Mipmap和各向异性滤波等技术。为了高效利用GPU显存,纹理必须进行



GAMES106-现代图形绘制流水线原理与实践 - P11:11. 着色器优化 🚀
在本节课中,我们将要学*着色器优化的核心概念、手动与自动优化的方法,以及如何在实际项目中应用这些技巧来提升图形渲染的性能。
概述
着色器是现代图形渲染的核心,其性能直接影响游戏的帧率和体验。随着游戏画面越来越复杂,着色器代码也变得异常庞大。在高分辨率和高刷新率的设备上,优化着色器计算变得至关重要。本节课将系统性地介绍着色器优化的动机、手动优化技巧以及前沿的自动优化框架。
优化动机与分类
首先,我们来探讨为什么要进行着色器优化。这主要由着色器本身的性质和现代硬件需求决定。如今3A游戏中的着色器逻辑非常复杂,展开后的代码量巨大。这些代码需要在每个像素上执行,而设备的分辨率和刷新率(如从640p 30Hz到4K 120Hz)已增长数十倍,计算压力巨大。因此,优化着色器是加速整个渲染流程的核心。
优化方法主要分为两类:手动优化和自动优化。我们先从手动优化讲起。
手动优化
手动优化依赖于开发者的经验,主要在两个阶段进行:编译阶段和执行阶段。
编译阶段优化
编译器在将高级着色器代码(如HLSL/GLSL)转换为底层指令时,会自动进行一系列优化。开发者应养成良好的代码*惯,以利于编译器工作。
以下是编译器常见的优化类型:
- 常量传播:如果变量被赋值为常量,编译器会直接使用该常量值,消除变量。
- 例如:
float x = 1.0; return x;会被优化为return 1.0;
- 例如:
- 常量折叠:编译器会预先计算常量表达式的结果。
- 例如:
float z = 2.0 * 4.0;会被折叠为float z = 8.0;
- 例如:
- 复写传播:检测并消除一连串的赋值传播,减少中间变量。
- 公共子表达式消除:识别并重用重复的计算表达式。
- 例如:
float a = x + y; float b = y + x;可能被优化为只计算一次x+y。
- 例如:
- 无用代码消除:移除从未被使用或结果恒定的代码。
- 函数内联:将小的内联函数调用直接替换为函数体代码,减少调用开销。
需要注意的是,不同编译器的优化能力并不稳定。因此,手动编写清晰、高效的代码是基础。
执行阶段优化
这是开发者可以主动控制的主要领域,可分为代码级优化和算法级优化。
- 代码级优化:简化复杂操作、消除冗余循环等。
- 算法级优化:用不同的、计算复杂度更低的算法实现相同效果。
优化目标通常是无损优化,即提升性能而不损失画质。但有时也会采用有损优化,例如使用纹理采样替代复杂计算,或使用*似算法。资深开发者能通过分析(如在特定场景下观察)或工具(如帧调试器、性能分析器)来评估质量损失是否可接受。
进行优化时,必须对硬件有深入了解。需要清楚GPU各级存储(如寄存器、共享内存、全局显存)的访问代价。例如,寄存器访问最快,共享内存次之,全局显存较慢,而读取CPU内存的数据则非常慢。
以下是手动优化的一些基本准则:
- 控制寄存器使用:尽量少用寄存器,但不要超出硬件上限。超出部分会使用更慢的显存作为临时寄存器,应尽量避免。
- 减少I/O操作:尽量减少内存、显存和寄存器之间的读写,特别是避免CPU与GPU之间的频繁数据交换。
- 避免随机内存访问:GPU喜欢连续、对齐的内存访问模式。相邻线程(像素)应访问相邻的内存地址,以实现高效的合并访问。完全随机的读写会严重降低性能。
- 减少分支和循环:尽量避免复杂的
if-else分支和循环,因为GPU的SIMD架构可能导致所有分支路径都被执行。可以用数学技巧替代分支,例如用step()或saturate()函数。 - 简化复杂运算:尽量避免
sin,cos,pow,sqrt等复杂运算。在精度要求不高时,可以使用查找表或*似公式。 - 使用低精度数据类型:在可行的情况下,使用
half或fixed代替float,特别是在移动设备上。 - 优先进行标量计算:合并低维度的计算,避免不必要的向量展开。
- 优化前:
float3 result = (a * s1) * s2;(先进行向量*标量,产生3次乘法,再乘标量,又产生3次乘法) - 优化后:
float3 result = a * (s1 * s2);(先合并标量,只进行1次标量乘法和3次向量乘法)
- 优化前:

除了代码技巧,还有一些常用的高级策略:
- 将计算上移至顶点着色器:顶点数量通常远少于像素数量。将一些逐像素计算(如简单的漫反射光照、UV变换)移到顶点着色器中进行,然后通过插值传递给片元着色器,可以大幅减少计算量。
- 使用预计算和烘焙:将复杂的静态计算结果(如噪声纹理、环境光遮蔽、甚至整个光照结果)预先计算并存储为纹理。在运行时只需简单的纹理采样,计算开销极低。
- 用计算换采样:有时情况相反。例如在PBR模型中,可以用数值函数逼*查找纹理的操作,用计算来节省纹理采样的带宽。
- 使用常量:给编译器明确的常量,而不是变量,有助于其进行优化。将常用常数(如
PI)预定义为常量。

层次细节(LOD)技术
LOD是工程中规范化的优化手段,其基本原理是“*详远略”。
对于着色器LOD,可以通过以下方式实现:
- 不同复杂度的光照模型:*处使用复杂的GGX BRDF模型,中距离使用Blinn-Phong模型,远处则使用简单的朗伯漫反射甚至恒定颜色。
- 拆分光照成分:*处包含所有光照成分(漫反射、高光、间接光等),随距离增加逐步移除高光、间接光等成分。
- 结合几何LOD:为不同精度的模型网格搭配不同复杂度的着色器。
自动优化
手动优化依赖经验,过程繁琐。自动优化旨在将整个过程自动化。
自动优化框架的流程如下:
- 输入:原始着色器代码、场景网格、材质参数等。因为着色器输出与场景和参数相关。
- 变种生成:系统自动应用各种简化规则(如删除指令、替换表达式、计算上移),生成大量优化后的着色器变种。
- 质量与性能评估:在给定场景和参数下,运行并评估每个变种的渲染误差和性能。
- 寻找帕累托前沿:在“误差-时间”的二维空间中,寻找最优的权衡曲线。根据项目需求(如设定最大误差或目标帧时间)在曲线上选择最合适的变种。

这个领域的核心研究围绕两点展开:如何生成有效的变种,以及如何高效地评估并找到最优解。

代码简化与变种生成

系统会将着色器代码解析为抽象语法树或依赖图。常见的简化规则包括:
- 表达式简化:例如删除
normalize()操作。 - 循环简化:减少迭代次数。
- 表达式替换:用数学上*似但更简单的表达式替换原式。
- 计算上移:自动识别可将片元着色器中的计算上移至顶点着色器或曲面细分着色器的代码。
- 常量替换:用常量替代变化*缓的变量。
- 高阶逼*:使用样条曲线等复杂函数逼*原着色器函数。

搜索策略与加速
变种空间可能非常庞大,暴力搜索不现实。常用策略包括:
- 遗传/进化算法:将变种视为个体,让性能好、误差小的“父代”产生“子代”变种,逐步进化。
- 贪心算法:从原始着色器开始,每次选择当前最优的简化操作,逐步推进。虽然可能错过全局最优解,但速度较快。
- 性能预测模型:不直接运行着色器,而是通过统计指令数、纹理采样数等来预估性能,加速评估。
- 误差缓存:记录每种简化操作带来的典型误差值。当在其他着色器中遇到相同操作时,直接使用缓存值估算,避免重复运行评估。
实时着色器优化
上述方法多为离线优化。实时优化旨在游戏运行过程中动态调整着色器。
核心思路是大幅缩减搜索空间:
- 离线阶段,针对大量着色器变种进行聚类分析,将相似的变种归为一类。
- 运行时,根据当前帧的上下文(如相机位置、物体距离),在一个很小的、预计算好的优化空间(即几个聚类中心)中快速匹配并选择最优的简化着色器,从而实现实时切换。
基于机器学*的优化(Shader Transformer)
这是前沿研究方向,将着色器代码视为一种语言,使用Transformer等神经网络模型来处理。
- 模型输入包括原始着色器代码和场景配置。
- 模型经过训练后,可以直接输出优化后的代码表达,或指导如何进行代码简化。
- 这种方法有望学*到更深层次的优化模式,但需要大量的训练数据。
总结
本节课我们一起深入学*了着色器优化的各个方面。
我们首先了解了优化的动机:应对复杂着色器和高分辨率、高刷新率带来的性能挑战。接着,我们系统学*了手动优化的技巧,包括编译器优化原理、减少I/O、避免分支、使用LOD等实用准则和策略。这些是图形程序员必备的实战经验。
然后,我们探讨了自动优化的前沿领域。了解了自动优化框架如何通过生成变种、评估质量、搜索帕累托前沿来替代繁琐的手工优化。我们还介绍了加速搜索的策略、实时优化的思路,以及基于机器学*的Shader Transformer等新兴技术。

优化是性能(时间)与质量(误差)之间的永恒权衡。无论是手动还是自动方法,目的都是在这条权衡曲线上,为我们的项目找到最合适的那个点。掌握这些原理和方法,将帮助你写出更高效、更优雅的图形代码。

GAMES106-现代图形绘制流水线原理与实践 - P12:12 流水线LOD技术 🎯


在本节课中,我们将要学*图形绘制流水线中的LOD技术。LOD是“Level of Detail”的缩写,即细节层次技术。它的核心目标是通过简化场景中不同物体的细节,来提升渲染效率,同时尽可能保持视觉质量。我们将探讨LOD的不同类型、实现方法,并深入了解前沿的可微绘制流水线如何用于生成LOD资产。
LOD技术概述与回顾 📚

上一节我们介绍了图形流水线的优化技术。本节中,我们来看看如何将这些技术整合到流水线中,实现整体的加速效果。
LOD技术主要应用于几何、纹理和着色器等各类资产。其核心思想是根据物体与观察者的距离或重要性,动态选择不同复杂度的资产版本进行渲染。

以下是LOD技术的主要分类:
- 离散LOD:资产有多个预先生成的、不同细节等级的离散版本。
- 连续LOD:资产细节可以连续、*滑地变化。
- 视角相关LOD:根据观察方向,对物体的不同部分应用不同的细节等级。
- 层次结构LOD:将场景组织成树状结构,根据需要展开或折叠节点。
离散LOD与切换策略 🔄

离散LOD是最常见的形式。它预先生成多个不同细节层级的模型(如LOD0为最高细节,LOD1、LOD2等依次简化)。其优点是通用性好,但缺点是层级切换时可能产生视觉上的“跳跃”现象。
为了缓解“跳跃”问题,可以采用以下技术:
- 延迟切换:不在精确的临界距离切换,而是增加一个缓冲区间。
- 几何变形:在两个LOD层级之间进行顶点插值,实现*滑过渡。
- 透明度混合:在切换时对两个层级的渲染结果进行Alpha混合。

在渲染时,需要为每个物体动态选择合适的LOD层级。以下是常见的切换准则:
- 基于距离:计算物体代表点(如中心点)到摄像机的距离。
- 基于屏幕投影面积:计算物体包围球或包围盒在屏幕空间中的投影面积。
- 基于目标面片数:为维持稳定帧率,设定场景总面片数预算,并据此为物体分配LOD。
连续LOD与视角相关LOD 📐
连续LOD通过数学方法(如顶点折叠、边分裂)实现细节的连续变化,完全避免了视觉跳跃,过渡非常自然。它常用于地形渲染。
视角相关LOD则更加智能。它认识到,对于一个物体,并非所有部分都需要同等细节。例如,一个兔子模型,其侧面轮廓需要高细节以保持形状,而正面*坦区域则可以用较少的面片表示。这种方法能更高效地分配计算资源。
层次结构LOD与地形应用 🗺️
层次结构LOD将场景组织成树(如四叉树、八叉树)。根节点是粗糙表示,子节点包含更精细的细节。渲染时,根据观察条件决定遍历到树的哪一层。这特别适合管理大型场景,如开放世界。
在实际应用中,上述技术常混合使用。地形渲染是LOD技术的经典应用场景。
以下是几种常见的地形LOD技术:
- ROAM:一种实时优化自适应网格算法,它既是连续的,也是视角相关的。它根据地形的起伏程度和与相机的距离,动态细分或合并三角形网格。
- Chunked LOD:将地形分块,每块包含一个层次细节结构。根据块与相机的距离选择细节层级,并在块间使用几何变形来避免接缝处的跳跃。
- Clipmaps:采用环状层次网格,离摄像机*的区域使用密集网格,远的区域使用稀疏网格。实现简单,易于流式加载。
- Geometry Clipmaps:使用四叉树层次结构存储地形细节,能在不同区域实现非均匀的细节密度,有效减少总面片数。
可微绘制流水线 🤖
前面我们介绍了传统的LOD生成方法。*年来,基于可微绘制流水线来自动优化LOD资产成为了研究热点。
要理解可微绘制,首先需区分正向与逆向绘制:
- 正向绘制:输入场景参数(几何、材质、光照、相机),通过渲染管线输出图像。
图像 = 渲染管线(场景参数) - 逆向绘制:给定图像,反向推断出产生该图像的场景参数。
- 可微绘制:将正向渲染管线构建为一个可微函数。这意味着我们可以计算最终图像像素颜色相对于任何输入参数(如顶点位置、纹理颜色)的梯度。
通过可微绘制,我们可以设定一个优化目标(如:在纹理分辨率降低一半的情况下,使渲染结果与原图尽可能相似)。然后,利用梯度下降法,自动调整简化后的资产参数,以最小化目标损失函数。这为生成高质量的LOD资产提供了新的自动化途径。
实现全流水线的可微性,需要打通每一个环节:
- 图像损失:计算简化结果与参考图之间的差异(如L2损失、SSIM)。
- 混合层可微:处理多通道渲染结果的混合(如Alpha混合),梯度可以按权重反向传播。
- 着色器可微:这是核心挑战。着色器代码包含大量不连续操作(如
clip,step)。研究如AutoDiff的工作,通过引入*滑的滤波核来处理这些不连续性,使得自动微分能够进行。 - 光栅化可微:将屏幕像素的梯度映射回三角形面片。已有如Soft Rasterizer等工作,通过用连续函数*似深度测试(
z-buffer)中的min操作来实现。 - 几何可微:最终,梯度被传递到顶点位置,指导几何形状的优化。


基于图像的简化技术 🖼️
这是一种极致的LOD技术:直接用一张或多张预处理好的图像来替代复杂的几何体和着色计算。
以下是几种基于图像的简化技术:
- 广告牌:用一个始终面向相机的四边形贴图来代表物体。适合远处或本身缺乏立体感的物体(如云、树冠)。
- 布告板云:用多个广告牌从不同角度代表一个物体的不同部分,精度更高。
- 图像缓存:将远景物体渲染到纹理上,后续直接绘制这些纹理,大幅降低绘制调用。
- 替身:预计算物体从多个视角观察到的图像,存储在一张纹理中。实时渲染时,根据当前视角对预计算图像进行采样和插值。这是目前游戏中非常常用的远景简化技术。
- 纹理深度图:将一组离散物体的几何信息烘焙到一张带深度的纹理上,再将其三角化成简化的网格,用于替代复杂的远景群。
总结 🎓
本节课中我们一起学*了图形流水线中的LOD技术。我们从离散LOD、连续LOD等基本概念入手,探讨了它们在地形渲染等场景中的具体应用。接着,我们深入了解了前沿的可微绘制流水线,它利用梯度下降自动优化LOD资产,代表了该领域的新方向。最后,我们介绍了极致的基于图像的简化技术,如广告牌和替身,这些是游戏中优化远景渲染的实用手段。

掌握这些技术,能够帮助我们在视觉效果和渲染性能之间找到最佳*衡,是开发现代图形应用和游戏的关键能力。

GAMES106-现代图形绘制流水线原理与实践 - P13:13. 流水线新进展 🚀
在本节课中,我们将探讨现代图形绘制流水线的一些最新发展趋势。课程内容将涵盖从虚拟化技术到光线追踪的演进,以及数据驱动方法如何改变传统的图形管线。这些进展旨在提升渲染的真实感、效率以及内容创作的自动化程度。


传统流水线回顾
上一节课程我们深入探讨了延迟渲染等技术。本节开始,我们先回顾当前行业中广泛使用的两种主要实时绘制范式。
第一种是基于光栅化的传统实时绘制流水线。它的核心思想是将三维面片投影到二维屏幕空间,然后逐像素进行着色计算。
第二种重要的行业标准是延迟渲染。它通常使用两个绘制通道:第一个通道生成几何与材质信息(G-Buffer),第二个通道基于G-Buffer进行光照计算。延迟渲染的缺陷在于它主要包含局部空间信息,对于全局光照的支持有限。当前有一些方法进行补救,例如光照探针和体素全局光照,但这些方法的质量通常不够高。
有别于这两种重要范式,实时绘制和光栅化流水线中还存在其他一些绘制方法。
趋向一:虚拟化技术

接下来,我们看看一个重要的技术趋向:虚拟纹理与虚拟几何。
虚拟纹理
虚拟纹理的概念出现较早,其灵感来源于计算机系统中的虚拟内存。虚拟内存允许程序使用超出物理内存容量的地址空间,通过映射和页面调度机制按需从硬盘加载数据。
虚拟纹理将这一思想应用于纹理管理。在大型3A游戏中,场景(如整个城市)可能需要*乎无限大的纹理。虚拟纹理通过一个巨大的虚拟纹理空间和页表来解决这个问题。当游戏引擎需要访问纹理时,先查询页表。如果命中,则直接从GPU显存中获取;如果未命中,则从硬盘加载所需的纹理块。
虚拟纹理有几个优点:支持高清大规模纹理、动态按需调度以减少内存和带宽消耗、通过统一访问避免零散小纹理带来的管理和拼合开销。
虚拟纹理具有层次细节结构。不同层级的纹理块相互对应,且每块的大小固定,便于用页表索引。页表本身也具有层次结构,允许在不同层级间查找。在实际使用时,如果某个层级的物理纹理未加载,可以方便地使用其上层较低分辨率的纹理进行替代。

虚拟纹理的一个小缺点是需要在渲染前进行预加载。通常需要一个额外的反馈通道来探测哪些纹理块是当前可见且需要的,如果不存在则触发加载。由于这个通道数据量小,对性能影响很低。
*年来,硬件虚拟纹理已被集成到图形API中。其用法与软件实现类似,但具体硬件实现因厂商而异,并未完全公开。

最后,我们看一下虚拟纹理的示例。在视角变化时,每一小块代表一个虚拟纹理块。由远及*,加载的纹理块及其细节层级在不断变化。远处可能使用固定的大块纹理,走*后这些块会被细分为更小、更高清的块。
虚拟几何

基于虚拟纹理的概念,*一两年最重要的进展之一是虚拟几何,例如虚幻引擎5中的Nanite系统。其核心思想很简单:既然纹理可以通过虚拟化技术做得很大,那么下一步就是将几何体也做得很大,包含海量的三角面片。这样就不需要依赖高精度的法线贴图来模拟细节,艺术家在ZBrush等软件中创作的高精度模型可以直接作为素材导入游戏。

虚拟几何的基本思路与虚拟纹理相似,但实现更复杂,因为几何体的合并、存储等操作比纹理更复杂。
第一个区别在于数据加载的判定。虚拟纹理可以通过一个轻量的反馈通道来确定需要加载哪些纹理块。但对于虚拟几何,必须经过完整的光栅化投影,才能知道哪些部分是可见且需要的。如果为了获取反馈而预先光栅化所有几何,就失去了虚拟几何的意义。因此,虚拟几何的块加载主要依赖于遮挡剔除技术。
在UE5中,使用了一种层次化的深度缓冲来进行遮挡剔除。它将面片分簇,利用视锥剔除和遮挡剔除快速排除大簇的面片,而无需处理每个小面片。它还利用了帧间的连续性:假设相邻帧间可见性变化不大,因此先将上一帧可见的面片绘制出来,生成一个深度缓冲,用这个深度缓冲剔除掉当前帧大部分不可见面片,然后再绘制剩余的新增细节面片。由于帧间相关性很高,这种缓存命中率在实践中非常高。
第二个重要的技术点是光栅化加速。传统光栅化的性能与面片数量相关。如果有很多小面片,每个都向帧缓冲写入完整的G-Buffer数据,带宽消耗会非常大。Nanite采用的策略是:对于小面片,不写入完整的G-Buffer,而是只写入一个实例ID、簇ID和深度值。在后续的真正着色阶段,再通过这些ID去读取具体的几何和材质信息。这是一种软件光栅化的做法。它采用混合技术:对大面片使用传统的硬件光栅化(效率更高),对小面片使用软件光栅化(效率比硬件处理小面片更高)。
第三个重要点是保留了几何的层次细节结构,并建立了复杂的串流机制来实时加载超大几何场景。它根据簇进行面片划分,并在簇之间构建LOD层次结构,形成一棵树。父节点包含子节点,但表达的是同一物体的不同细节程度。在渲染时,如果绘制了父节点,其子节点就不需要绘制。系统会根据视点变化维持一个“切割”,确保显存中有一个合适的细节层级集合。当需要加载子节点时,可以暂时用父节点替代,保证任何时刻都能渲染出内容,尽管细节可能不足。之后再使用时间抗锯齿等策略进行帧间混合,以减少几何突变的视觉瑕疵。
以上就是虚拟纹理和虚拟几何的概述。下一个值得关注的重点是:
趋向二:从光栅化到光线追踪

首先,我们回顾一下光栅化与光线追踪的区别。光栅化是本课程讨论了很久的技术。
光线追踪是一种更物理的方法。它不是将面片投影到相机,而是从相机发射光线,与场景中的几何体求交。求交后,根据材质属性计算光线的反射、折射方向,并在场景中不断弹射,直到找到光源。这种方法模拟了光线在场景中的多次弹射和能量传输,能更好地描述全局光照。

可以看到,上图是传统的光栅化管线。当然,现在也有一些方法在光栅化管线中加入全局光照,但这些通常需要大量艺术家调整,且物理准确性不一定高。像镜面反射、折射等效果在光栅化方法中很难完美实现。
路径追踪或光线追踪可以保证所有视觉效果尽可能逼*真实世界的物理结果。需要指出的是,路径追踪或光线追踪也并非能实现所有特效,一些如焦散等效果也需要更特殊的算法。但对于大部分游戏场景而言,路径追踪产生的结果比传统光栅化流水线的效果要好很多。
这是一些效果对比图。左边是没有路径追踪/光线追踪的结果,右边是加入后的结果。可以看到光泽感、阴影过渡都更加真实,整体写实感提升很多。
那么,既然光线追踪效果更好,为何我们现在主要用光栅化?因为光栅化速度快,而光线追踪慢。因此,人们想办法将两者混合起来:一些用光栅化能快速实现的效果就用光栅化,一些必须用光线追踪才能做好的效果就用光线追踪。这就产生了混合绘制流水线,也是当前许多游戏采用的主流方法。
例如,直接光和硬阴影用光栅化可以做得很好;软阴影光栅化可以逼*,但不如光线追踪好;全局光照则更多使用光线追踪。此外,环境光遮蔽、透明效果、后处理反射等都可以用不同的技术组件进行混合。
它们的实现逻辑上很简单,但真正混合管线却很复杂。因为光线追踪的硬件管线与光栅化完全不同。光栅化管线是:顶点着色器 -> 光栅化 -> 片段着色器。光线追踪管线是:发射光线 -> 硬件加速遍历与求交 -> 着色 -> 可能继续弹射。这两个流程迥异,光线追踪的API无法直接嵌入现有的光栅化框架中。
具体来说,光线追踪的逻辑很简单:生成光线,遍历场景,有三种情况:未命中、命中任意点或命中最*点,然后做相应处理。但其计算非常复杂,因为所有光线的方向、与场景的求交(尤其是与层次包围盒BVH的遍历)都各不相同,很难做高效的硬件并行。
NVIDIA为此付出了大量努力,其OptiX光线追踪引擎从2010年就已提出,经过多年发展才在消费级市场得以应用。实现上非常复杂,一部分需要硬件支持,一部分需要软件支持。在硬件上,例如NVIDIA的Ada架构,进行实时光线追踪需要用到RT Core、Tensor Core以及一些如微网格的定制硬件。软件上,目前常用的API如OptiX、DirectX Raytracing都提供了光线追踪接口,开发者可以直接调用。
总的来说,这是一个巨大的发展。但是,光栅化与光线追踪的混合流水线目前仍有许多不足。

光线追踪的挑战与AI辅助
首先,即使有硬件加速,光线追踪仍然很慢。因为光线追踪本质上是一个蒙特卡洛积分过程,需要采样。采样就会产生噪声。采样率越低,噪声越大;在直接光难以到达的暗部,噪声尤其明显。这种结果无法直接用于游戏画面。
因此产生了许多利用人工智能进行后处理或加速的方法。降噪是一个基本技术:给定一个有噪声的图像以及其G-Buffer信息,可以利用先验知识进行滤波*滑。这是一个非常有前景的技术。
这里将简要介绍一些重要的经典工作,为大家提供一个脉络。由于时间关系,我们只做概括性浏览。
- 图像空间降噪:2017年迪士尼的工作《Kernel Prediction》使用神经网络为每个像素预测一个滤波核。神经网络的输入是G-Buffer和噪声图像。其基本思想是:如果物体在颜色或空间上有连续性,滤波器可以更好地在物体内部进行卷积。网络将高频的颜色信息与低频基础色分离,使得网络无需学*所有光照变化。
- 路径空间降噪:图像空间降噪自然延伸到路径空间降噪。每个像素背后是许多光线路径,路径空间维度太高。2021年的工作通过嵌入和流形学*,将高维路径信息映射到特征空间,使特征更*滑,便于降噪。
- 重要性采样:除了后处理降噪,还可以在采样阶段做文章。蒙特卡洛采样中,如果采样分布越接*被积函数的重要性分布,积分噪声就越低。因此有一些工作用神经网络去逼*这个最优采样分布。
- 强化学*采样:2020年的工作将采样问题形式化为强化学*问题,训练一个Q网络和策略网络,在巨大的采样空间中智能地选择采样点,以提高效率。
- Combiner网络:前述降噪结果可能是有偏的。在低采样率下效果好,但在高采样率下,未经降噪的路径追踪结果本身误差可能更低。Combiner工作提出用一个神经网络,将降噪前的无偏结果和降噪后的有偏结果进行加权混合,确保最终结果在高采样率下也能收敛到正确解。
- 时空样本重用:重用时间上(上一帧)和空间上(相邻像素)的采样样本,增加当前像素的样本密度。构建一个样本池,从中重采样以提升输入图像质量,并可与其他降噪结果结合。
- 帧预测:渲染一帧太慢时,可以用上一帧直接预测下一帧。在图形绘制中,我们可以用很低代价获取下一帧的G-Buffer,从而基于下一帧的G-Buffer和上一帧的绘制结果来预测下一帧,质量比传统的视频帧预测更好。
- 神经超分辨率:渲染一张低分辨率图像,然后用神经网络进行上采样补全细节。这篇工作主要利用多帧历史信息,用时域信息弥补空域信息的缺失。超分辨率在工业中应用广泛,如DLSS和FSR,但具体实现未开源。
以上是关于混合绘制的讨论。下一个趋向,我们来讨论从物理驱动到数据驱动的变化。


趋向三:从物理驱动到数据驱动
物理驱动指的是我们一直讨论的图形管线:基于物理模型进行计算,如光线传输、基于物理的材质模型等。这是一个知识驱动的过程。
计算机视觉中的感知任务,现在更多是数据驱动:通过大量数据和标签,让神经网络自动拟合规则。
*年来,这两个领域的融合趋势非常明显,尤其是在神经场景表示出现之后。例如,神经辐射场就是用神经网络方法来完成场景呈现的任务。可微分渲染则是用神经网络建模渲染过程的反向传播。

这可能对未来传统图形管线产生根本性改变。传统上我们用显式的网格几何来表达物体,然后渲染。但最*像NeRF这样的工作,可以用神经网络表达一个场景的辐射场。在任意位置输入坐标和视角方向,网络可以返回该点的颜色和密度。然后通过体渲染沿光线积分,得到最终颜色。这与医学体绘制原理类似。其好处是,它直接使用拍摄的照片进行优化,可以渲染出真实感极强的画面,极大丰富了数字素材库。许多引擎已支持直接渲染NeRF这类表示。
另一个重要工作是Instant NGP。它在体渲染部分与NeRF类似,区别在于场景表示。它不使用单一的大网络,而是使用哈希网格进行小块局部表达,再通过一个小型解码网络得到颜色和密度。由于优化时只需更新局部参数,速度极快,使得训练和渲染接*实时。
接下来是SDF方法。SDF也用神经网络,但它表达的是有符号距离场,即空间中每点到最*物体表面的距离。其好处是可以方便地通过Marching Cubes等方法提取出显式网格几何,从而与现有工程化管线对接。
还有一些工作是物理基础的反向渲染,不优化SDF场,而是直接优化网格顶点位置和纹理材质,通过可微光线追踪将梯度传回,实现从图像到三维模型的自动优化。
下一个重要的是AIGC。前述内容主要是从2D到3D的跨模态转换。AIGC已经可以实现更广泛的跨模态资产生成和转换。例如,根据一段文字描述生成三维场景或物体的神经表示,这为虚拟内容创作带来了巨大的想象空间。
除了模型生成,还有纹理生成。给定一个白模,可以根据文字描述自动生成精细的贴图。以及灯光生成:给定一个室内场景,神经网络可以自动生成灯光的亮度、样式和合理摆放位置。
最后一个趋势是AI计算逐渐融入图形计算。传统绘制主要基于GPU,核心计算单元是计算着色器,光线追踪则用到RT Core。现在有一些工作探索使用更通用的NPU来进行全局光照等绘制任务。它们改变了流水线逻辑,从基于面片或光线绘制,转变为按物体绘制,逐步叠加每个物体对场景的光照影响,并用神经网络学*每个物体的光传输函数。
课程总结 🎯
本节课中,我们一起学*了现代图形绘制流水线的几个新进展。
首先,探讨了从虚拟纹理到虚拟几何的虚拟化技术,用于实现高精度材质和几何的流式加载。
其次,分析了从光栅化到光线追踪的演进趋势,以及混合渲染管线面临的挑战和AI辅助解决方案。
最后,讨论了从物理驱动渲染到数据驱动方法的转变,包括神经场景表示、AIGC内容生成等,这些技术正在深刻改变图形学的内容创作和渲染方式。
这也是GAMES106课程的最后一讲。感谢大家一直以来的观看。
接下来是讨论时间。今天的话题比较发散,主要是方向性的探讨。另外,助教反馈在几何优化部分的作业提交数量有所减少,如果大家遇到难度或需要解答,可以提出探讨。如果对某些作业不感兴趣,也可以跳过,选择后面更感兴趣的作业来完成。

如果没有其他问题,我们稍等片刻就结束今天的直播。
好的,今天我们就先到这里。



GAMES106-现代图形绘制流水线原理与实践 - P2:图形绘制流水线的基本原理与实践(一) 🎬
在本节课中,我们将学*现代图形API Vulkan的基本原理与实践入门。课程将从Vulkan的简介开始,对比传统与现代图形API的差异,并详细介绍Vulkan应用程序的初始化流程、核心概念以及渲染主循环的构成。
Vulkan简介与课程目标 🎯
Vulkan与OpenGL类似,其最初提案名为“GLnext”,意为下一代OpenGL。它是一个由官方组织制定统一标准的跨*台图形API,具体实现由各硬件厂商负责。这与苹果的Metal或微软的Direct3D不同,后两者的标准与实现绑定更紧密。
Vulkan的一个重要特征是扩展机制。各*台或硬件厂商可以提供自己的扩展功能,例如VK_XXX_NV(NVIDIA扩展)、VK_XXX_AMD(AMD扩展)或VK_XXX_KHR(Khronos官方扩展)。这些扩展后续可能被吸收进Vulkan核心规范。
课程适合人群与前置要求
以下是本课程的目标人群与学*前提:

- 适合人群:刚开始接触学*现代图形API的初学者。如果你已对OpenGL或Direct3D 11有了解,并希望更深入理解绘制管线,本课程是合适的。若已对Vulkan非常熟悉,本课程意义可能不大。
- 前置要求:必须掌握C和C++语言。

学*收获

通过本课程,你将获得以下知识:
- Vulkan API的基本使用方法。
- 对Vulkan绘制管线相对全面的理解。
- 一些针对移动端图形应用的优化实践经验。
需要区分移动端与桌面端开发,因为图形API是面向硬件的接口。不同*台的硬件特性(如移动端常受带宽限制,桌面端受计算能力限制)决定了优化策略的不同。即使是移动端,不同芯片(如高通、华为麒麟、联发科)也可能需要针对性优化。
传统与现代图形API对比 ⚖️
上一节我们介绍了Vulkan的基本情况,本节我们来对比传统与现代图形API的核心差异。
状态机 vs 显式控制
传统API(如OpenGL, Direct3D 11)是状态机。它们有一个“上下文”概念,保存着各种渲染状态。这可能导致一些问题,例如:
- 资源管理冲突:如果外部代码错误地释放了由内部SDK管理的、具有相同ID的资源,会导致不可预知的崩溃或错误,且难以调试。
- 隐式同步:驱动会自动处理同步。例如,使用
glReadPixels从GPU读取纹理数据时,CPU会隐式等待所有相关的GPU渲染命令完成,这可能导致CPU停滞,但保证了结果正确。
现代API(如Vulkan, Direct3D 12)则不同:
- 驱动层很薄:驱动只做最核心的工作,将控制权交给开发者。
- 显式管理:开发者必须清楚了解程序的资源生命周期、内存分配、命令依赖关系和同步需求。例如,在Vulkan中,你需要先申请一大块内存,然后自行管理其中哪些部分分配给哪张纹理。
- 高性能潜力:这为开发者提供了写出高性能程序的能力,但要求开发者对硬件和程序逻辑有深入了解。
多线程与命令提交
现代API对多线程更友好:
- 基于命令缓冲:Vulkan使用
VkCommandBuffer。CPU将命令记录到缓冲中,再提交到队列,GPU随后从队列获取执行。命令缓冲可以复用,如果每帧绘制命令相同,只需构建一次并重复提交。 - 传统API的多线程:由于状态机的存在,实现多线程较为复杂,需要为每个线程创建并共享上下文,管理各自的状态机。
学*路径建议
不建议零基础直接学*Vulkan或Direct3D 12,因其概念繁多,初始化复杂。推荐的学*路径是:
- OpenGL:上手简单快速,易于实现各种效果,适合建立图形学直观感受。
- Direct3D 11:接口基于面向对象设计,有助于更清晰地理解绘制管线结构。
开发时需注意:应严格按照API的标准流程编写代码,避免省略步骤。不同硬件驱动对标准的实现可能有差异,省略可能导致在某些设备上运行异常。
Vulkan应用程序基础框架 🏗️
前面我们对比了图形API的差异,现在我们来具体看一个Vulkan应用程序的基础框架。整个流程可分为三大部分:初始化、渲染主循环和资源清理。


我们的目标是一个能绘制三角形的简单程序,其代码结构清晰地对应了这三个阶段。
程序流程总览
一个Vulkan程序的基本流程如下:


- 初始化阶段
- 创建应用程序窗口。
- 初始化Vulkan(加载驱动,连接物理设备)。
- 创建交换链,关联Vulkan与显示窗口。
- 渲染主循环阶段
- 更新CPU逻辑:处理游戏逻辑、UI、输入等。
- 更新GPU资源:将CPU端更新好的数据(如矩阵、参数)拷贝到GPU内存。
- 提交渲染命令:构建并提交命令缓冲,驱动GPU进行渲染,并将结果呈现到屏幕。
- 清理退出阶段
- 退出主循环后,释放所有Vulkan资源。
- 关闭程序窗口。

渲染可以看作一个函数:输入是几何信息和Uniform参数,输出是屏幕画面,而处理逻辑就是着色器程序。

Vulkan核心概念与初始化详解 🔧
了解了整体框架后,本节我们深入Vulkan初始化的核心概念。
Vulkan实例与层
创建Vulkan实例是与驱动沟通的第一步。这里引入一个关键概念:层。
Vulkan的加载过程不是直接调用驱动,而是经过一个或多个“层”。VkInstance是应用程序与Vulkan库之间的桥梁。层机制允许在调用驱动函数前后插入自定义代码,常用于:
- 启用扩展:如交换链扩展。
- 调试与验证:启用调试层,为资源设置易读名称,或检查API调用错误。
- 性能分析:使用如RenderDoc等工具层。
层的工作方式类似于函数钩子。当调用一个Vulkan函数时,会依次经过各个启用的层进行处理,最后才到达驱动。这为调试和性能分析提供了官方支持的标准方式。
物理设备与队列家族
初始化实例后,需要选择物理硬件设备。系统可能有多块显卡,Vulkan可以查询所有可用物理设备及其属性(如设备名称、能力限制)。程序应根据需求选择合适的设备。
每个物理设备有一个或多个队列家族。队列是命令缓冲提交的地方。不同的队列家族支持不同类型的操作:
- 图形队列:支持渲染命令。
- 计算队列:支持计算着色器命令。
- 传输队列:支持内存复制命令。
- 呈现队列:支持将结果呈现到屏幕。


并非所有队列都支持所有操作。一个物理设备可以创建多个逻辑设备,每个逻辑设备可以创建来自不同队列家族的队列。这样,你可以将图形命令提交到图形队列,计算任务提交到计算队列,实现更精细的任务分工与并行。


为什么设计如此复杂?
这种设计是为了提供极大的灵活性。例如,在多GPU系统(如四路显卡)中,你可以让不同显卡专精于不同任务(物理模拟、部分渲染、最终呈现),并通过Vulkan显式地管理和调度它们,从而充分利用所有硬件资源。虽然简单应用通常只用一个实例、一个逻辑设备和少数队列,但Vulkan为此类高级用例提供了可能。




总结 📚




本节课我们一起学*了现代图形API Vulkan的入门知识。



我们首先了解了Vulkan的定位、特点以及本课程的学*目标。随后,通过对比传统与现代图形API,我们理解了Vulkan显式控制、驱动层薄、面向多线程的设计哲学,这带来了高性能的潜力,也提高了开发复杂度。

接着,我们剖析了一个Vulkan应用程序的基础框架,包括初始化、主循环和清理三个阶段。最后,我们深入讲解了初始化的核心概念:实例与层用于调试和扩展,物理设备与队列家族则提供了连接硬件和提交命令的途径。

这些概念是构建任何Vulkan程序的基石。在接下来的课程中,我们将基于这些知识,学*如何创建渲染对象、管理内存并进行实际的绘制。

GAMES106-现代图形绘制流水线原理与实践 - P3:图形绘制流水线的基本原理与实践(二) 🎬


在本节课中,我们将继续深入学* Vulkan 图形绘制流水线,重点探讨 Vulkan 对象的创建、内存管理以及调试工具的使用。课程内容将围绕作业讲解、核心概念解析和实践演示展开。




课程回顾与作业讲解 📋


上一节我们介绍了 Vulkan 的基础架构和绘制流程。本节中,我们首先回顾课程安排并详细讲解第一次作业的要求。
课程安排如下:
- 第一课时:讲解 Vulkan 基础架构和绘制流程。
- 第二课时(本节):讲解 Vulkan 对象创建、内存管理以及调试方法和工具。
- 第三课时(下周三):讲解 Vulkan 多线程同步以及基于移动端的常见优化和实践。



以下是关于第一次作业的详细说明。


作业框架与模型


作业基于提供的代码框架完成。在下载代码仓库时,请使用 git submodule update 命令同步拉取所有第三方库。



作业需要使用一个特定的模型文件。请将该文件下载并解压到项目的 data 文件夹中。该模型是一个带有骨骼动画的模型。
作业要求
当前作业框架(Homework 1)仅能加载静态的 glTF 模型,且材质处理简单(仅颜色),光照实现不完整(仅一个方向光)。本次作业需要在此基础上进行改进,具体要求如下:
- 支持 glTF 骨骼动画:正确读取并播放模型中的骨骼动画。
- 支持 glTF PBR 材质:完整实现模型的 PBR 材质渲染,包括法线贴图、自发光贴图等。
- 进阶要求(额外通道):实现一个色调映射(Tonemap)函数。关键点:必须通过增加一个额外的渲染通道(Render Pass)来实现该函数,不能直接将其写在片元着色器(Fragment Shader)的末尾。函数形式如下:
// 输入一个颜色,输出映射后的颜色 vec3 tonemap(vec3 color) { ... }
重要提示:作业必须在提供的 homework1 框架基础上修改,不要提交其他框架的代码。
参考资料与示例
为了帮助大家完成作业,框架中提供了多个示例代码,可以通过取消注释 setupWorkingExample 函数中的相应行来启用和运行:
gltf_skin:演示如何读取 glTF 骨骼动画。pbr_base:演示基于方向光的 PBR 实现。example_pbr_ibl:演示基于环境光的 PBR 实现。
关于 glTF 格式中骨骼和材质的详细描述,可以参考 glTF 官方文档。

Vulkan 对象创建 🏗️

上一节我们概述了完整的渲染管线。本节中,我们来看看管线每个阶段所对应的 Vulkan 对象及其创建方法。
Vulkan 绘制管线中涉及的主要对象包括:
- Buffer 和 Image:用于存储顶点、索引、 uniform 数据和纹理。
- Pipeline:定义整个图形绘制管线的状态和着色器。
- Descriptor Set:用于向管线传递 uniform 变量等资源。
对象创建通用模式

Vulkan 创建对象的函数遵循统一的模式:vkCreate[Object]。例如,创建缓冲区的函数是 vkCreateBuffer。
该模式通常包含以下参数:
VkDevice:逻辑设备。- 描述要创建对象的结构体(例如
VkBufferCreateInfo)。 - 自定义内存分配器(通常可传
nullptr使用默认分配器)。 - 返回创建对象句柄的指针。
所有 Vulkan 函数都通过返回 VkResult 来指示操作成功或错误类型。
创建 Buffer 的流程

以下是创建一个 Uniform Buffer 的典型步骤,创建 Image 的流程与此类似:

- 创建 Buffer 对象:使用
vkCreateBuffer,并通过VkBufferCreateInfo指定其用途(如VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT)和大小。 - 查询内存需求:使用
vkGetBufferMemoryRequirements获取该 Buffer 实际需要的内存大小和对齐要求。这里查询的大小可能与申请时指定的大小不同,因为驱动可能出于优化目的进行内存对齐。 - 分配内存:使用
vkAllocateMemory分配一块符合要求的内存。需要根据内存用途(CPU可见、GPU本地等)指定正确的内存类型。 - 绑定内存与 Buffer:使用
vkBindBufferMemory将分配的内存与创建的 Buffer 对象绑定起来,offset参数通常为 0。
关键点:Vulkan 允许开发者自定义内存分配器来管理 CPU 端的内存分配。而 GPU 内存的分配和管理,Vulkan 也提供了一套机制,我们将在内存管理部分详细讨论。

Render Pass 与 Framebuffer 🖼️
在配置好绘制所需的数据后,我们需要定义绘制的目标(输出到哪里)以及绘制过程中的状态。这就是 Render Pass 和 Framebuffer 的作用。
Render Pass

Render Pass 对象描述了单个渲染过程中涉及的附件(Attachment)、子通道(Subpass)以及它们之间的依赖关系。
它主要定义两部分:
- 附件描述(Attachment Description):定义输出目标(如颜色附件、深度附件)的格式、在渲染开始和结束时的操作(如清除、存储)以及内存布局(Layout)。
- 子通道描述(Subpass Description):定义该子通道引用哪些附件作为输入/输出,以及多个子通道之间的依赖关系。
在代码框架的 setupRenderPass 函数中,我们定义了两个附件(颜色附件和深度附件),并设置它们在渲染前执行清除操作(loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR),在渲染后存储结果(storeOp = VK_ATTACHMENT_STORE_OP_STORE)。同时,也定义了这些附件在进入和离开 Render Pass 时的布局状态。


关于 Layout 的理解:VkImageLayout 表示图像在不同用途下的内存布局状态。对于初学者,可以将其简单理解为图像的一种“使用状态”,例如“作为颜色附件”、“作为被采样的纹理”、“作为拷贝源”等。以状态机的方式理解比记忆具体布局更容易。

Framebuffer
如果说 Render Pass 定义了渲染过程的“模板”或“流程”,那么 Framebuffer 就是根据这个模板,指定本次渲染具体使用哪些图像视图(Image View)作为附件。

在代码的 setupFramebuffers 函数中,我们为交换链(Swapchain)中的每一个图像都创建了一个 Framebuffer。每个 Framebuffer 都绑定了对应的交换链图像视图作为颜色附件,以及一个公共的深度图像视图作为深度附件。



Pipeline 与管线布局 ⚙️
定义了渲染目标和流程后,我们需要创建最重要的对象之一:图形管线(Graphics Pipeline)。它定义了从顶点输入到最终颜色输出的完整处理过程。
Pipeline 创建
创建 Pipeline 需要填充一个庞大的 VkGraphicsPipelineCreateInfo 结构体,它包含了管线所有阶段的状态:
- 顶点输入状态:定义顶点数据的格式和绑定方式。
- 着色器阶段:指定编译好的顶点着色器和片元着色器模块。
- 视口与裁剪状态:定义输出图像的区域和裁剪规则。
- 光栅化状态:定义多边形填充模式、是否剔除背面等。
- 多重采样状态:定义抗锯齿相关设置。
- 深度与模板测试状态。
- 颜色混合状态:定义多个颜色附件如何混合。
- 动态状态:定义哪些状态可以在绘制时动态修改(如视口大小)。
- 管线布局:定义描述符集(Descriptor Set)和推送常量(Push Constant)的布局。
- Render Pass:指定该管线所兼容的 Render Pass。

在作业框架中,createPipeline 函数详细展示了如何设置这些状态。例如,它定义了顶点数据的四个属性(位置、法线、UV、颜色),并绑定了对应的着色器。


着色器编译
Vulkan 使用 SPIR-V 字节码格式的着色器。我们可以使用 Vulkan SDK 中的工具(如 dxc 或 glslangValidator)将高级着色语言(HLSL/GLSL)编译为 SPIR-V。
例如,使用 dxc 编译一个 HLSL 片元着色器的命令可能如下:
dxc -T ps_6_0 -E main -spirv -fspv-target-env=vulkan1.2 Shader.hlsl -Fo Shader.frag.spv
作业提交时,请记得附上你编译好的着色器文件。



管线布局与描述符集
管线布局(Pipeline Layout)定义了着色器可以访问哪些资源,以及这些资源的组织方式。资源通过描述符集(Descriptor Set)进行分组和绑定。
描述符集就像一个资源表,表中的每个条目(描述符)可以是一个 Uniform Buffer、一个纹理采样器或一个存储图像等。将资源分组到不同的描述符集有利于高效更新。
在作业框架的着色器中:
- 绑定到
set = 0, binding = 0的是场景级的 Uniform Buffer(包含相机矩阵、灯光等),所有模型共享。 - 绑定到
set = 1, binding = 0的是材质级的 Uniform Buffer 和纹理,每个材质独有。
在绘制代码中,我们先绑定 set = 0 的描述符集(场景数据),然后在绘制每个模型或子网格时,再绑定其对应的 set = 1 描述符集(材质数据)。这种分离使得全局数据和频繁变化的数据可以独立、高效地更新。


Vulkan 内存管理 💾

Vulkan 赋予了开发者精细控制内存的能力,理解内存类型和属性对于性能优化至关重要。
内存架构与类型
在典型的台式机架构中,CPU 和 GPU 拥有各自独立的内存,通过 PCIe 总线连接。Vulkan 内存根据其属性主要分为几种类型,可以通过 vkGetPhysicalDeviceMemoryProperties 查询:
- DEVICE_LOCAL:GPU 本地内存,GPU 访问速度最快,CPU 通常不能直接访问。适合存储频繁被 GPU 读写的数据,如帧缓冲、深度缓冲。
- HOST_VISIBLE:CPU 可见内存,CPU 可以映射(map)并读写。适合用于需要从 CPU 频繁更新的数据,如每帧变化的 Uniform Buffer。
- HOST_COHERENT:保证 CPU 和 GPU 对该内存的写入相互立即可见,无需手动刷新缓存。通常与
HOST_VISIBLE一起使用。 - HOST_CACHED:CPU 端缓存该内存,适合 CPU 需要频繁读取的数据(如回读渲染结果)。
注意:在集成显卡或移动 SoC 上,CPU 和 GPU 可能共享物理内存,情况会有所不同。
内存分配策略
直接使用 Vulkan 原生接口管理内存非常复杂。AMD 提供了一个开源库 Vulkan Memory Allocator (VMA),它极大地简化了内存管理。
VMA 将内存用途抽象为更易理解的类型,例如:
VMA_MEMORY_USAGE_GPU_ONLY:仅 GPU 使用,如纹理、静态顶点缓冲。VMA_MEMORY_USAGE_CPU_TO_GPU:从 CPU 上传到 GPU,如每帧更新的 Uniform Buffer。VMA_MEMORY_USAGE_CPU_ONLY:暂存内存(Staging Buffer),用于高效的 CPU 到 GPU 数据传输。
高效数据上传:Staging Buffer

对于需要每帧从 CPU 更新到 GPU 的数据(如 Uniform Buffer),一个高效的策略是使用 Staging Buffer。

工作流程如下:
- 创建一块
HOST_VISIBLE且HOST_COHERENT的内存作为 Staging Buffer。 - CPU 将数据写入 Staging Buffer(通过
vkMapMemory/vkUnmapMemory)。 - 在命令缓冲区中,记录一个从 Staging Buffer 拷贝到最终 GPU Buffer 的命令(
vkCmdCopyBuffer)。 - 提交命令缓冲区执行。
优势:
- 减少同步:拷贝操作在 GPU 命令流中,与渲染有序进行,避免了 CPU 直接写入 GPU 内存可能需要的显式同步。
- 提高性能:Staging Buffer 通常位于一块 CPU 和 GPU 都能高效访问的特殊内存区域。
- 简化逻辑:避免了在 GPU 使用资源时,CPU 误写入导致的数据竞争问题。
作业框架中更新 Uniform Buffer 使用的是简单的 map/unmap 方式。作为练*,大家可以尝试将其改造为使用 Staging Buffer 的方式。


Vulkan 绘制命令记录 🖌️

Vulkan 的绘制是异步的。CPU 将绘制命令记录到命令缓冲区(Command Buffer)中,然后提交给 GPU 的队列执行。
命令记录流程
一个基本的绘制命令序列如下:
vkBeginCommandBuffer:开始记录命令。vkCmdBeginRenderPass:开始一个 Render Pass,定义渲染目标和作用域。- 设置视口和裁剪矩形(
vkCmdSetViewport,vkCmdSetScissor)。 - 绑定图形管线(
vkCmdBindPipeline)。 - 绑定顶点缓冲区和索引缓冲区(
vkCmdBindVertexBuffers,vkCmdBindIndexBuffer)。 - 绑定描述符集(
vkCmdBindDescriptorSets)。 - 记录绘制命令(
vkCmdDraw或vkCmdDrawIndexed)。 vkCmdEndRenderPass:结束 Render Pass。vkEndCommandBuffer:结束命令记录。


在作业框架的 buildCommandBuffers 函数中,我们可以看到这个流程的具体实现。它绑定了场景的全局描述符集(set = 0),然后遍历 glTF 模型的节点和网格,为每个材质绑定其独有的描述符集(set = 1),并推送模型矩阵(使用 Push Constant),最后发起绘制调用。
命令缓冲区的重用

Vulkan 的一个高效特性是命令缓冲区可以重用。在作业框架中,命令缓冲区在初始化时创建并记录一次。在后续每帧渲染时,只要绘制内容没有变化,就直接提交已记录好的命令缓冲区,无需 CPU 再次介入记录。这显著降低了 CPU 开销,使其能专注于逻辑、模拟等任务。





调试工具与方法 🔧


最后,我们来介绍一些强大的 Vulkan 调试和性能分析工具。


1. RenderDoc
RenderDoc 是一个功能强大的图形调试器,支持 Vulkan。
主要功能:
- 抓帧分析:捕获某一帧或连续几帧的所有 GPU 命令。
- 资源检查:查看任意时刻的缓冲区、纹理内容。
- 管线状态调试:查看绘制调用时管线各个阶段的状态、绑定的资源、着色器代码。
- 动态着色器编辑:支持在调试器中直接修改着色器代码并实时查看效果,无需重新编译程序。这对于调试着色器逻辑非常方便。



局限性:其性能分析(Profiling)功能可能不够精确。



2. NVIDIA Nsight Graphics


对于 NVIDIA 显卡用户,Nsight Graphics 是性能分析和调试的利器。

主要功能:
- 精确的性能分析:提供以微秒为单位的 GPU 时间线分析,能准确找到性能瓶颈。
- 帧调试:类似 RenderDoc 的抓帧和状态检查功能。
- 着色器性能分析:可以对特定的着色器进行底层性能分析和统计。

3. 其他*台工具
- 高通 Snapdragon Profiler:用于在高通设备上进行图形性能分析。
- Arm Mobile Studio:包含 Graphics Analyzer 和 Performance Analyzer,适用于 Arm Mali GPU 的设备。



总结 🎯


本节课我们一起深入学*了 Vulkan 图形绘制流水线的核心实践部分:


- 作业详解:明确了第一次作业的目标——实现 glTF 模型的骨骼动画、PBR 材质渲染,并通过额外通道实现色调映射。
- 对象创建:学*了 Vulkan 创建 Buffer、Image、Pipeline 等核心对象的通用模式和具体步骤。
- 流程定义:理解了 Render Pass 和 Framebuffer 如何定义渲染目标和流程。
- 内存管理:探讨了 Vulkan 复杂的内存类型系统,并介绍了使用 VMA 库和 Staging Buffer 策略来简化管理并提升数据上传效率。
- 命令记录:掌握了 Vulkan 异步绘制命令的记录、提交和重用机制。
- 调试工具:了解了 RenderDoc 和 NVIDIA Nsight Graphics 等强大工具,用于调试渲染错误和分析性能瓶颈。



通过本课的学*,你应该对如何使用 Vulkan 组织一次完整的绘制有了更清晰的认识。下节课我们将探讨 Vulkan 的多线程同步机制以及在移动*台上的优化实践。


GAMES106-现代图形绘制流水线原理与实践 - P4:图形绘制流水线的基本原理与实践(三) 🎬
在本节课中,我们将学* Vulkan 图形 API 中的多线程同步机制、Frame Graph 技术以及移动端常见的优化实践。课程将涵盖同步原语的使用、资源依赖管理以及针对移动设备架构的性能优化策略。



Vulkan 同步原语 🔒
上一节我们介绍了 Vulkan 的绘制对象与内存管理。本节中,我们来看看 Vulkan 中用于协调 CPU 与 GPU 以及 GPU 内部命令执行顺序的同步原语。理解这些同步机制对于编写高效、正确的 Vulkan 程序至关重要。
Vulkan 的多线程框架基本结构如下:在 CPU 端(Host)有若干条命令缓冲区(Command Buffer),每条命令缓冲区是 GPU 命令的堆栈。CPU 通过队列(Queue)将命令缓冲区提交给 GPU 执行。
Vulkan 主要提供了三种同步信号:
- 栅栏 (Fence)
- 事件 (Event)
- 信号量 (Semaphore)
此外,还有用于管理内存资源读写的屏障 (Barrier)。
1. 栅栏 (Fence)
栅栏是粒度最大的一种同步原语,它用于同步一整条命令缓冲区的执行完成状态。
- 工作原理:CPU 将命令缓冲区提交给 GPU 后,GPU 会在该命令缓冲区全部执行完毕后,改变对应栅栏的状态。CPU 端可以通过查询(
vkGetFenceStatus)或等待(vkWaitForFences)这个状态来判断命令是否执行完成。 - 等待方式:
vkWaitForFences会依赖于操作系统的调度,让线程进入休眠,从而节省 CPU 资源。当然,也可以使用类似自旋锁(spin lock)的方式循环查询状态。 - 类比:可以*似等价于 OpenGL 中的
glFinish,用于保证所有 GPU 调用都已完成。
2. 事件 (Event)


事件的同步粒度比栅栏更细,它可以标记命令缓冲区中某个特定操作的完成。





- 工作原理:可以在一条命令的结束时设置事件状态(例如,标记为“已完成”)。CPU 和 GPU 都可以设置事件,但只有 GPU 的命令可以等待事件。CPU 只能查询事件状态,不能像等待栅栏那样阻塞等待。
- 应用场景:常用于构建命令之间的依赖关系,类似于拓扑排序。例如,后处理 Pass 需要等待几何渲染 Pass 完成,就可以通过事件来通知。
- 额外用途:可用于手动实现 GPU 性能分析。通过创建事件并在不同操作点设置,然后由一个线程查询事件完成时间,可以记录每个 GPU 操作的耗时。
3. 信号量 (Semaphore)

信号量主要用于GPU 端不同队列(Queue)之间或同一队列内命令缓冲区之间的同步。




- 工作原理:它表示一条命令缓冲区需要等待另一条命令缓冲区执行完成后,才能在 GPU 上开始执行。CPU 端不能直接设置或等待信号量,它是纯粹给 GPU 内部使用的同步机制。
- 与事件的区别:信号量同步的粒度是整条命令缓冲区,而事件可以同步到缓冲区内的某个具体操作。


4. 屏障 (Barrier)

屏障与其他原语不同,它主要用于管理内存资源的读写顺序和一致性,解决资源竞争问题。
- 类型:分为全局内存屏障(Memory Barrier)、缓冲区屏障(Buffer Barrier)和图像屏障(Image Barrier)。
- 作用:确保在屏障之前的所有命令对资源的读写操作完成后,屏障之后的命令才能访问该资源。例如,防止一个 Pass 在读某个纹理时,另一个 Pass 同时在写这个纹理。
- 使用思路:在实际开发中,可以简化理解为:当资源的使用方式发生改变时(例如,从“渲染目标”变为“着色器只读资源”),就需要插入一个屏障。虽然底层实现更复杂,但遵循这个原则通常可以保证正确性。


优化提示:应尽量减少屏障的使用。频繁改变资源状态(读->写->读)会影响性能。可以通过优化渲染流程,将同类操作(如所有写操作)集中在一起,来减少状态切换。




Frame Graph 技术 🗺️
了解了基础的同步原语后,我们面临一个问题:在复杂的多 Pass 渲染流程中,如何清晰地管理资源依赖并实现高效的同步?这就引入了 Frame Graph 技术。
Frame Graph 是一种声明式的渲染框架设计,它将一帧内的所有渲染 Pass 及其资源依赖关系描述为一个有向无环图(DAG)。
为什么需要 Frame Graph?
- 管理复杂依赖:现代渲染管线包含许多 Pass(如深度预渲染、SSAO、阴影、光照、后处理等),Pass 之间存在着复杂的输入输出依赖。Frame Graph 能清晰地表达这些依赖关系。
- 自动同步:基于依赖图,Frame Graph 可以自动在需要的地方插入正确的同步原语(信号量、事件、屏障),避免手动同步容易出错的问题。
- 资源生命周期管理:Frame Graph 可以识别出哪些资源在某个 Pass 之后就不再使用,从而允许复用内存,减少内存分配开销。
- 优化与调试:可以方便地启用/禁用某个特性(如关闭 SSAO),Frame Graph 会自动剔除相关的 Pass 和资源,便于性能分析和调试。
Frame Graph 示例
一个简单的延迟渲染管线 Frame Graph 可能包含以下节点和边:
- 蓝色节点:渲染 Pass(如
DepthPrePass,SSAOPass,LightingPass)。 - 橙色节点:资源(如
DepthBuffer,NormalBuffer,AlbedoBuffer)。 - 边:表示 Pass 对资源的读写依赖。



通过 Frame Graph 编译(Compile)阶段,系统可以自动计算出最优的 Pass 执行顺序和资源分配策略。


实践推荐




对于想深入学* Frame Graph 实现的同学,推荐研究谷歌的 Filament 渲染引擎。它包含了一套清晰且高效的 Frame Graph 实现,其接口通常包含:
addPass:声明一个渲染 Pass。setInput/setOutput:声明该 Pass 的输入输出资源,从而构建依赖关系。execute:执行编译后的 Graph。







Vulkan 移动端优化实践 📱
最后,我们探讨一些针对移动端设备的 Vulkan 优化实践。移动端 GPU(通常采用 Tile-Based Rendering 架构)与桌面端 GPU(Immediate Mode Rendering)有显著差异,因此优化策略也不同。
移动端优化的核心矛盾是:计算能力相对强大,但内存带宽和功耗极其受限。因此,优化大多围绕节省带宽和减少功耗展开。
以下是来自 ARM、高通等厂商最佳实践文档中的一些关键点:
常见优化建议


以下是针对移动端 Vulkan 开发的一些具体建议:
- 使用索引绘制 (Indexed Draw Call):即使顶点没有复用,也建议使用
vkCmdDrawIndexed。这与直觉相悖,但符合移动端驱动的优化特性。 - 避免使用 32 位索引:在满足顶点数量的前提下,使用 16 位索引(
VK_INDEX_TYPE_UINT16)可以减少带宽消耗。 - 使用半精度浮点数:在 Fragment Shader 中,除了计算世界空间位置等需要高精度的数据,应尽量使用
mediump(半精度)浮点数。 - 避免深度预渲染 (PreDepth Pass):在移动端,Tile-Based GPU 的 Hidden Surface Removal (HSR) 机制能有效处理 Overdraw,额外的深度预渲染 Pass 反而会增加开销。
- 避免破坏 Early-Z 的行为:
- Alpha Blend 和 Discard 操作会禁用 Early-Z 优化,导致 Overdraw 增加。
- 优化方案:对于 Alpha Test 物体(如树叶、草丛),可以将其几何体轮廓提取出来,用实体几何代替 Alpha Blend 的四边形,从而保持 Early-Z 有效。
- RenderPass 配置:尽量使用
LOAD_OP_CLEAR或LOAD_OP_DONT_CARE,避免LOAD_OP_LOAD,以减少内存加载带宽。 - 避免巨型 Uber Shader:不要使用通过 Uniform 变量控制大量分支的“全能”着色器。分支会严重影响性能。应该根据特性编译不同的着色器变体。
- 减少单个着色器的代码量:过于复杂的着色器会导致编译慢、执行效率低。必要时可考虑将功能拆分到多个 Pass。
- 减少通用寄存器使用:例如,将两个
vec2变量打包成一个vec4。 - 展开短循环:对于确定次数的短循环,手动展开代码通常比使用循环语句性能更好。
案例分析:前向渲染 (Forward) 与延迟渲染 (Deferred) 在移动端的抉择
这是一个经典的性能取舍问题:

- 延迟渲染 (Deferred Lighting):
- 优点:灯光数量与性能消耗呈线性关系,易于支持大量动态光源。
- 缺点:需要存储 G-Buffer(法线、颜色、深度等),带宽消耗极大,在移动端是主要瓶颈。
- 前向渲染 (Forward Lighting):
- 优点:带宽消耗低,更适配 Tile-Based Rendering 架构。
- 缺点:灯光计算在着色器内进行,受限于着色器指令数和寄存器数量,能支持的光源数量有限。一个模型有 N 个光源就需要计算 N 次。
混合优化方案
为了在移动端兼顾性能和效果,可以采用一种混合方案:




- 分离阴影计算:将有阴影的灯光和无阴影的灯光拆分成不同的渲染 Pass。
ShadowCasterPass:专门处理带阴影的灯光(数量需严格限制,如1-3个)。每个这样的灯光可能都需要单独的 Pass。ForwardLightingPass:处理所有不带阴影的灯光。由于没有阴影计算,着色器较轻量,可以支持较多数量(如几十个)。
- 性能提升:通过这种拆分,避免了在着色器中使用 Uniform 分支判断“是否计算阴影”,符合了“避免 Uber Shader”的最佳实践。实测中,这种优化可能带来 60%-70% 的性能提升。
- 代价:Draw Call 数量会增加,变为
Mesh数量 * (1 + 有阴影灯光数量)。需要权衡 CPU 提交命令的开销和 GPU 着色器的执行效率。


核心思想:移动端优化没有银弹,必须仔细权衡带宽、计算量和 Draw Call。最可靠的依据是硬件厂商(如 ARM、高通)提供的官方最佳实践文档,并在真实设备上进行测试。

总结 📝


本节课我们一起学*了 Vulkan 图形管线中三个高级主题:
- 同步原语:掌握了 Fence、Event、Semaphore 和 Barrier 的作用与区别,理解了它们如何协调 CPU 与 GPU 的执行。
- Frame Graph:了解了这种声明式渲染框架如何通过图结构管理复杂的 Pass 依赖和资源生命周期,实现自动同步与优化。
- 移动端优化:探讨了移动端 GPU 架构的特点,并学*了一系列以节省带宽为核心的最佳实践,特别是通过拆分渲染 Pass 来优化复杂光照场景的具体案例。

掌握这些知识,将帮助你构建更高效、更健壮的 Vulkan 渲染引擎,尤其是在资源受限的移动*台上。

GAMES106-现代图形绘制流水线原理与实践 - P5:5. 多频率着色 🎨
在本节课中,我们将要学*现代图形绘制管线中的多频率着色概念。我们将从绘制管线的基本计算频率开始,逐步介绍几何管线和着色管线中的不同着色阶段,并探讨如何利用多频率着色的思想进行性能优化,在保证画面质量的同时提升渲染效率。
绘制管线中的计算频率 📊
上一节我们介绍了课程概述,本节中我们来看看绘制管线中计算频率的基本概念。
最简单的计算频率理解是:顶点着色器(Vertex Shader)每个顶点执行一次,片元着色器(Fragment/Pixel Shader)每个像素执行一次。在定义管线时,先对每个顶点执行一遍顶点着色器,然后进入光栅化阶段。
然而,真实的绘制管线远比这个复杂。从输入网格开始,管线经历了曲面细分、几何着色等阶段。在DirectX 9之后,管线变得越来越复杂,引入了可见性样本、着色样本等概念。可编程着色器内部的计算频率和缓存频率也各不相同。本节课的目标就是梳理这些概念,并探讨如何优化。
几何管线 🔷
在介绍现代绘制管线之前,我们先了解一下早期影视制作中使用的管线。
早期(约1987年)的管线被称为“REYES”(Render Everything You Ever See)。为了在大荧幕上获得连续*滑的曲面(而非三角形构成的棱角),它使用了参数曲面,并通过细分算法(如Catmull-Clark细分)生成更*滑的几何体。
其核心架构是“Split and Dice”:
- Split阶段:将输入的参数图元分割成更小的图元(Primitive)。
- Dice阶段:将参数曲面细分成更小的多边形(如微多边形),每个多边形大约一个像素大小。
- 细分后,可以在顶点上进行着色计算(包括求交和光照),最后通过像素点在各顶点间的插值得到最终颜色。
现代实时绘制管线的许多概念都由此演化而来,因为我们的目标始终是尽可能地逼*影视级的渲染质量。
顶点着色器(Vertex Shader)
现代管线始于顶点着色器。在此阶段,我们需要定义输入装配(Input Assembly)和顶点输入数据。
以下是定义输入的基本步骤:
- 定义图元拓扑(Primitive Topology),最常用的是三角形列表(Triangle List)。
- 提供顶点输入数据(Vertex Input Data),通常包括顶点缓冲区(Vertex Buffer),并常配合索引缓冲区(Index Buffer)进行索引。
- 如果使用实例化(Instancing),还需要实例缓冲区(Instance Buffer)。
GPU执行时,会根据顺序为每个顶点执行一次顶点着色器,并将结果写入。GPU会缓存已执行过的顶点结果,避免对共享顶点重复计算。
几何着色器(Geometry Shader)
顶点着色器的一个主要限制是,其输出的几何体必须在CPU端预先设定。为了提供更灵活的几何修改能力,DirectX 10时代引入了几何着色器。
几何着色器的好处在于可以自由地修改几何。例如,一个三角形输入,可以输出其法线进行可视化,或者输出多个三角形,甚至改变拓扑结构。

然而,由于其过于自由,GPU难以进行有效的任务调度和性能优化,因此几何着色器在实践中被认为不太成功,使用相对较少。
曲面细分着色器(Tessellation Shader)
为了解决几何着色器的性能问题,并提供可控的细分,DirectX 11引入了曲面细分着色器。
其工作流程如下:
- 曲面细分控制着色器(Tessellation Control Shader / Hull Shader):设置细分参数。对于三角形,包括三条边的细分因子(Tessellation Factor)和内部的细分因子。
- 固定功能曲面细分器(Fixed-Function Tessellator):根据细分参数生成更多的顶点。
- 曲面细分求值着色器(Tessellation Evaluation Shader / Domain Shader):计算新顶点的属性(如位置),通常利用原始三角形的重心坐标进行插值。
曲面细分可用于实现*滑几何(如PN三角形)或增加几何细节(如位移贴图)。在地形渲染中,它常用于实现细节层次(LOD)。
使用曲面细分时需注意两个问题:
- 共享边的细分因子匹配:相邻三角形的共享边必须使用相同的细分因子,否则会产生T型连接(T-junction)和裂缝。
- 顶点数据一致性:进行位移映射时,共享顶点应使用相同的法线和UV等属性数据,否则也会产生裂缝。这通常通过计算*滑顶点法线(Smooth Vertex Normal)或强制生成相同的顶点数据(Duplicate Vertex)来解决。
网格着色器(Mesh Shader)
随着计算着色器(Compute Shader)的引入,程序员获得了更大的自由度。DirectX 12 Ultimate进一步引入了网格着色器(Mesh Shader)架构。
网格着色器将几何管线的部分工作(如裁剪、LOD选择)转移到GPU上,以任务(Task)和网格(Mesh)的形式进行更高效、更自由的并行处理。它提供了比曲面细分更灵活的计算粒度。
我们来总结一下几何管线中各阶段的特点:
- 顶点着色器:每个顶点执行一次,不知道图元拓扑。
- 几何着色器:知道输入图元拓扑,但输出可变,调度效率低。
- 曲面细分着色器:采用固定模式,速度较快,但灵活性受限。
- 网格着色器:沿袭计算着色器的自由度高,且带有几何感知,适合特定高效应用。
着色管线 🖌️
介绍完几何管线,我们转向管线的另一半——着色管线。本节我们重点关注像素级别的着色计算。
基础光栅化与片元着色器执行
在最简单的光栅化中,一个三角形覆盖的像素会执行片元着色器。但实际执行是以像素块(如2x2的像素Quad)为单位的。这是为了支持求导指令(如ddx, ddy),这些指令需要与相邻像素比较来计算梯度,常用于纹理Mipmap选择。
片元着色器在Quad上执行,但只有被覆盖(Coverage)的像素中心才会真正写入颜色和深度。
多重采样抗锯齿(MSAA)
基础光栅化会导致几何边缘出现锯齿。多重采样抗锯齿(MSAA)的核心思想是在比像素更小的尺度上进行可见性计算。
在MSAA中,每个像素包含多个采样点(如4x MSAA有4个采样点)。这些采样点用于深度/模板测试,以更精确地确定三角形覆盖像素的比例。
着色计算(片元着色器执行)通常仍在像素中心进行(每个像素一次)。然后,根据覆盖采样点的比例,将计算出的颜色混合后写入。
与无MSAA相比,MSAA的代价在于:
- 深度/模板测试的样本数量增加。
- 片元着色器的执行次数可能增加(因为部分覆盖的像素也需要执行)。
- 写入的颜色数量增加。
MSAA通过增加采样点来提升几何边缘的质量,同时相比于单纯提高分辨率,它减少了片元着色器的执行次数(每个像素通常仍只着色一次)。但如果有多个三角形部分覆盖同一像素,着色次数仍会增加。
现代API提供了对MSAA更细致的控制,例如逐采样着色(Per-Sample Shading),允许对每个采样点分别执行着色。
可变速率着色(VRS)
与MSAA在更小尺度着色相反,可变速率着色(VRS)允许在比像素更大的尺度上进行着色计算。
例如,可以设定2x2的像素块只执行一次着色计算,其余像素通过插值得到颜色。这可以显著减少着色计算量。
VRS可以通过多种方式控制:
- 基于图元(Per-Primitive):为特定物体(如远景物体)设置较低的着色率。
- 基于屏幕图像(Screen-Space Image):提供一张纹理来指定屏幕上不同区域应使用的着色率。这可以是静态的(如VR中中心区域用高着色率),也可以是自适应的(根据画面内容动态生成)。
VRS可以与MSAA结合使用,实现更灵活的着色质量与性能*衡。
总结一下,着色管线关注的是采样率和着色器执行。着色频率直接影响图像质量。多频率着色的核心就是在计算性能与图像质量之间寻找最佳*衡点。
多频率着色优化实践 ⚙️


了解了管线提供的各种计算频率后,本节我们来看看如何利用多频率着色的思想进行实际优化。
进行多频率着色优化的前提是正确识别信号的频率。
对于几何信号:
- 高频区域:法线变化剧烈、几何细节丰富的区域。
- 低频区域:*坦、光滑的区域。
优化思想包括:对高频区域保留更多几何细节(如更多三角形),对低频区域进行简化;或者将高频几何细节编码到法线贴图、位移贴图中,而基础网格保持低频。
对于着色信号(屏幕空间信号):
- 高频区域:光照变化剧烈、纹理细节丰富、运动剧烈的区域。
- 低频区域:光照*缓、纹理一致、或距离很远的区域。
学术界和工业界有许多利用这种思想的工作:
- 基于内容的自适应着色:通过分析屏幕像素间的梯度(变化),推导出全分辨率、半分辨率等着色结果之间的误差,从而动态决定每个区域使用何种着色率,以在视觉无损的前提下降低计算量。
公式示例:误差 ≈ K * (相邻像素差异) - 时域自适应:不仅考虑单帧画面内容,还考虑帧与帧之间的运动(Motion Vector)和遮挡变化,在运动剧烈的区域采用更保守(或更激进)的着色率策略。
- 材质速率着色(Material Rate Shading):更进一步,在着色器内部对不同部分采用不同频率。例如,漫反射(Diffuse)可能是低频的,而高光(Specular)或阴影边缘可能是高频的。现代引擎中已有类似应用,如环境光遮蔽(AO)或全局光照(GI)常使用半分辨率计算再加模糊。
除了在像素着色器内部优化,还可以考虑将低频的着色计算(如某些光照)上移到顶点着色器或曲面细分阶段,通过插值来获得像素颜色,从而减少像素着色器的负担。
总结与作业 📚
本节课我们一起学*了现代图形绘制管线中的多频率着色。
首先,我们回顾了绘制管线中从几何到着色的各种计算频率,包括顶点着色器、几何着色器、曲面细分着色器、网格着色器、MSAA和VRS。理解这些频率是进行优化的基础。
其次,我们探讨了多频率着色优化的核心思想:识别几何和着色信号中的高频与低频区域,并利用管线提供的不同计算粒度,对低频区域采用代价更低的计算方式,在保证视觉质量的同时提升渲染性能。
课后作业建议:
请以“Visually Lossless Content and Motion Adaptive Shading in Games”等相关论文为基础,尝试实现或深入理解以下两点:
- 基于内容的自适应着色:如何根据画面内容(如颜色、深度梯度)动态决定着色率。
- 时域自适应扩展:在运动场景中,如何结合运动向量进一步优化着色率策略,以处理运动模糊和动态遮挡。

通过本课的学*,希望大家能够建立起利用多频率思想分析和优化渲染管线的能力。

GAMES106-现代图形绘制流水线原理与实践 - P6:性能分析 🚀
在本节课中,我们将学*图形程序性能分析的核心概念、工具与方法。理解性能瓶颈是优化的第一步,我们将从CPU与GPU的协作、GPU架构基础,到具体的性能分析工具使用,系统地介绍如何定位和诊断性能问题。
概述 📋
高性能的图形代码意味着更高的帧率、更流畅的画面和更低的延迟。实现这一目标需要CPU与GPU的高效协作。性能分析旨在定位整个绘制流水线中的瓶颈环节,无论是CPU端的逻辑处理、渲染线程准备,还是GPU端的着色器执行与内存访问。
上一节我们介绍了现代图形API的绘制流程,本节中我们来看看如何分析这个流程中各个环节的性能表现。
性能分析基础概念 ⚙️
一个最基本的性能指标是帧率。高帧率代表更流畅的体验。不同*台或应用对帧率的要求不同,例如主机游戏通常为60 FPS,VR应用则可能需要90 FPS。除了*均帧率,帧率的*稳性同样重要,应避免帧时间大幅波动。
写出高性能程序需要同时考虑CPU和GPU。CPU擅长复杂的控制逻辑,而GPU专为高吞吐量的并行计算设计。两者需要良好协作:CPU需要高效地批量准备并提交绘制数据给GPU,以避免GPU空闲等待。同时,CPU自身也在通过多核与并行编程技术提升处理能力。
从引擎层面看,完成一帧绘制是一个流水线过程。以Unity为例,一帧可能包含物理计算、游戏逻辑脚本、渲染准备等多个阶段。现代引擎通常将任务拆分到不同线程,例如:
- Game Thread:处理物理、游戏逻辑等。
- Render Thread:负责场景剔除、绘制命令准备与提交。
GPU驱动将提交的绘制命令放入队列,由GPU硬件调度执行。最终结果被写入帧缓冲区,在垂直同步信号到来时交换到屏幕显示。
由于这是一个流水线,最慢的一环决定了整体的帧率。因此,性能分析的关键是测量流水线中每个环节的耗时。
以下是测量各环节时间的基本方法:
- CPU端时间:可以使用高精度计时器(如
std::chrono)在任务前后打点计算。 - GPU端时间:一个常见误解是将提交绘制命令的时间当作GPU执行时间。正确的方法是使用GPU时间戳查询。在命令列表开始和结束处插入时间戳命令,并在数帧后异步查询结果,以获得真实的GPU执行耗时。
通过对比Game Thread、Render Thread和GPU的执行时间,可以定位瓶颈所在。
针对不同瓶颈,优化方向也不同:
- CPU (Game Thread):优化脚本逻辑、物理计算、网络同步等。
- CPU (Render Thread):优化动态批处理、灯光数量、遮挡剔除等。
- GPU:优化三角形数量、纹理分辨率、着色器复杂度、显存使用等。
为了进行深入分析,我们需要借助专门的性能分析工具。
GPU架构简述 🏗️
在深入GPU性能分析工具之前,有必要简要了解GPU的基础架构,这有助于理解性能指标的含义。
GPU的核心是大量并行的小型处理单元(流处理器)。其编程模型与CPU的SIMD(单指令多数据)不同,属于SIMT(单指令多线程)。在SIMT模型中,开发者编写的是单个线程的行为,而硬件以线程束(Warp/Wavefront)为单位进行调度和执行。例如,NVIDIA GPU的Warp通常包含32个线程,这些线程以锁步(Lock-step)方式执行相同的指令。

以下是GPU执行的关键概念:
- 线程束(Warp/Wavefront):GPU调度和执行的基本单位。一个线程块(Thread Block)会被划分为多个线程束来执行。
- 分支(Branching):在SIMT模型中,分支对性能的影响取决于线程束内线程的分支路径是否一致。如果线程束内所有线程走相同分支,没有额外开销。如果线程束内线程走不同分支(分支发散),则所有分支路径的指令都可能被执行,通过掩码(Mask)控制哪些线程的结果有效,这会带来性能损失。
- 占用率(Occupancy):衡量GPU流处理器上活跃线程束数量的指标。高的占用率有助于隐藏内存访问延迟。当某个线程束因等待内存数据而停顿时,调度器可以迅速切换到其他就绪的线程束继续执行,从而保持硬件忙碌。
- 内存层次结构:GPU拥有复杂的多级内存体系,包括全局内存(显存)、L2/L1缓存、以及每个流处理器独有的共享内存(Shared Memory)和常量缓存/纹理缓存等。访问不同层级的内存,延迟和带宽差异巨大。
图形管线中还包含一些固定功能单元,如顶点获取(Vertex Fetch)、图元装配(Primitive Assembly)、光栅化(Rasterization)和输出合并(Output Merger)等。

理解这些架构特点,是解读GPU性能分析报告的基础。
PC端常用GPU性能分析工具 🔧
上一节我们介绍了GPU架构的基本概念,本节中我们来看看如何使用工具对GPU进行性能分析。NVIDIA的Nsight系列工具功能强大,常作为分析范例。
Nsight工具的核心原理是指令重放。它捕获应用程序发出的图形API调用序列,然后离线或在线重新执行这些命令,并在此过程中收集GPU内部各种硬件计数器(Counter)的数据。
主要有两种分析模式:
- Nsight Frame Profiling:进行深入的单帧分析。它可以展示每个绘制调用(Draw Call)的详细耗时,以及消耗了哪些GPU硬件单元(如SM、内存带宽、光栅化器等)的资源。其提供的绝对时间可能因 profiling 开销而不完全准确,但用于对比优化前后的相对性能变化非常可靠。
- Nsight GPU Trace:进行实时、轻量级的性能概览。它可以连续捕获多帧,展示GPU利用率的时序图,快速定位是CPU提交命令慢导致GPU空闲,还是GPU自身执行成为瓶颈。它能给出SM占用率、显存带宽使用率等宏观指标。
在分析报告中,你会看到一系列硬件单元的性能百分比,例如:
- VF:顶点获取单元利用率。
- SM:流处理器利用率,是衡量计算瓶颈的关键。
- L1/TEX:一级/纹理缓存利用率。
- DRAM:显存带宽利用率。
- ROP:渲染输出单元利用率。

通过观察哪个单元的利用率接*100%(成为瓶颈),就可以确定大致的优化方向。例如,如果SM利用率很低,但GPU仍有大量空闲时间,可能是CPU提交命令不够快;如果SM利用率已接*饱和,则需要优化着色器代码或减少计算负载。

性能分析与优化策略 🎯
掌握了工具的使用后,性能分析的核心策略是定位瓶颈。首先确定是CPU瓶颈还是GPU瓶颈。如果是GPU瓶颈,则进一步利用工具定位是哪个具体的硬件单元(如SM、内存带宽、纹理单元)利用率最高。

优化是一个迭代和验证的过程:
- 测量:使用工具获取性能基线数据。
- 假设:根据数据和代码逻辑,提出性能瓶颈的假设。
- 优化:实施针对性的优化(如简化着色器、减少纹理采样、合并绘制调用)。
- 验证:再次测量,确认优化是否有效,并观察是否引入了新的瓶颈。
需要特别注意的是,优化往往是*台相关的。在一款GPU上的优化可能在另一款GPU上收效甚微甚至成为负优化。因此,必须在目标硬件上进行测试和验证。
本课程后续关于几何简化(Geometry LOD)、着色器简化(Shader LOD)和纹理流送(Texture Streaming)等内容,都是具体的GPU优化技术。学*本课的性能分析方法后,你将能够在实现这些技术时,不仅观察帧率变化,更能深入理解它们具体缓解了GPU哪个环节的压力(如减少了顶点处理负载、降低了纹理带宽占用等),从而更科学地评估优化效果。
总结 📝
本节课我们一起学*了图形程序性能分析的全流程。我们从高性能代码的目标出发,理解了CPU与GPU协作的流水线模型,以及定位瓶颈的基本方法。接着,我们概述了GPU的SIMT架构、线程束、占用率等关键概念,这是理解GPU性能指标的基础。然后,我们介绍了Nsight等性能分析工具的原理和使用方法,学会如何解读硬件单元利用率报告。最后,我们明确了性能优化“定位瓶颈-针对性优化-验证结果”的核心策略,并强调了*台相关性的重要。
通过本课的学*,希望你能够建立起系统的性能分析思维,并运用工具为后续课程中的具体优化实践提供有力的评估手段。

参考资源:
- NVIDIA Nsight Graphics 官方文档。
- 《GPU性能分析与优化》相关教程与案例分析。
- NVIDIA/AMD GPU 架构白皮书。


GAMES106-现代图形绘制流水线原理与实践 - P7:几何处理与模型减面引言 🎯
在本节课中,我们将要学*三维模型的几种主要表示方法,并初步了解模型减面的基本思路和分类。我们将从模型表示的基础概念开始,逐步过渡到模型简化的核心思想。
模型表示方法
上一节我们介绍了课程的整体安排,本节中我们来看看三维模型在计算机中是如何被表示的。对于三维模型,我们需要用数字化的方式来描述其形状。其中一种非常常见且历史悠久的表示方式是参数化的曲线和曲面。
- 参数化曲线与曲面:对于一条二维空间中的曲线,其x和y坐标可以用一个参数
t来控制。对于一个三维空间中的表面,则可以用两个参数(例如u和v)来控制。例如,计算机图形学中常见的贝塞尔曲线和曲面就属于此类。这种表示方式在计算机辅助设计领域非常流行。

-
网格模型:另一种常见的表示方式是使用网格。一个连续的表面可以用许多多边形(最常见的是三角形)拼接起来进行逼*。网格模型在渲染时通常需要转换为三角形。我们可以为网格表面贴上纹理或赋予材质颜色以便渲染。
-
点云:点云是另一种表示方式,它通过在模型表面上采集一系列离散的点来*似描述形状。三维扫描仪(如激光扫描仪)获取的数据通常就是点云。
-
隐式距离场:与点云相关的一种表示是隐式距离场。它在整个空间中定义一个函数,对于任意一点,该函数值表示该点到模型表面的(有符号)距离。距离场便于进行距离查询等操作,但需要额外技术来提取出显式的表面网格。
-
神经表示:*年来,使用神经网络来表达模型数据变得非常流行。例如,神经辐射场将一个三维空间点的位置
(x, y, z)和观察方向(θ, φ)作为输入,通过神经网络输出该点的颜色(RGB)和密度(σ)。这种表示通常与体渲染结合,能从少量输入(如照片)中重建出逼真的多视角模型。
由于本课程更关注渲染,我们将集中讲解最常用的网格模型表示方式。
网格模型详解
上一节我们了解了模型的多种表示法,本节中我们深入探讨网格模型的具体构成。一个网格模型要表达出如图所示的完整外观,需要两部分信息。

- 形状:通过点、线、面(网格)的组合来定义模型的轮廓和曲面起伏。
- 外观(如颜色):通常通过一张称为纹理贴图的二维图片来定义。为了将这张二维图片“贴”到三维网格表面,需要建立两者之间的映射关系。
这个过程称为UV展开。我们需要将三维网格表面(可能经过切割)展开到二维*面(u, v)上,建立网格顶点与纹理像素之间的一一对应关系。这样,在渲染每个三角形时,就能通过查询对应的UV坐标从纹理贴图中获取颜色值。
接下来,我们介绍网格模型的一些基本概念,这些概念对理解后续的简化算法非常重要。
连接关系与拓扑
网格由顶点、边和面(如三角形)组成。连接关系描述了这些元素是如何关联的,类似于图论中的图。
- 点的度数:指一个顶点所连接的边的数量。对于内部点,其度数等于相邻的三角形面数;对于边界点,则有所不同。
- 邻域:例如,一个顶点的一阶邻域包括所有通过一条边直接与该顶点相连的顶点、边和面。二阶邻域则需要通过两条边才能到达。
- 流形与非流形:流形网格是指其表面任意一点的局部邻域都能与一个二维圆盘建立同胚映射。非流形情况包括:
- 点的非流形:一个顶点及其相邻的三角形无法构成一个简单的环(即映射不到一个圆盘上)。
- 边的非流形:一条边被三个或更多个面共享。
欧拉-庞加莱公式
这是一个连接拓扑信息的恒等式,对于表面网格有:
V - E + F = 2 * (S - G)
其中:
V= 顶点数E= 边数F= 面数S= 连通分量数(模型由几个独立部分构成)G= 亏格(通俗理解,最少需要切几刀能将模型变成与球面同胚,例如一个环面的亏格为1)
几何量
以下是一些常用且重要的几何概念:
- 边长:边在三维空间中的欧几里得长度(L2范数)。
- 面积:三角形面积有明确公式。对于非*面的四边形,通常将其分割为三角形计算。
- 二面角:一条边相邻的两个三角形所在*面之间的夹角。
- 法向:表面上某一点处垂直于切*面的方向,通常为单位向量。
- 相交:指模型自身面片之间相互穿透,或不同模型之间发生穿透。
- 退化单元:面积为零的三角形(退化成线或点),或长度为零的边。
- 包围盒:能完全包裹住三维模型的最小轴向对齐或方向自适应的长方体。常用于算法中作为尺度参考,以提高对不同大小模型的鲁棒性。
- 曲率:描述表面弯曲程度的量。表面上一点有无数个曲率方向,其中存在主曲率(最大和最小曲率)。
- 特征:通常指模型中需要特殊处理的部位,如根据二面角提取的尖锐边、角点等,它们构成的图可将表面划分为不同区域。
- 豪斯多夫距离:衡量两个形状之间差异的度量。定义为从一个形状上任意点到另一个形状最*点的距离的最大值。
- 面翻转:在一致的网格朝向(如所有三角形顶点按逆时针排列)中,某个面的法向与周围面相反。在UV展开中,面翻转会导致纹理映射错误。
- 凸包:包含给定点集的最小凸集。在二维中是最小凸多边形;在三维中是最小凸多面体。
数据结构
如何在程序中存储和高效访问网格信息是关键。设计需要在存储空间、访问速度和更新效率之间取得*衡。存储的信息通常包括顶点位置、每个顶点附加的属性(颜色、UV、蒙皮权重等),以及点、边、面之间的连接关系。
一个经典且巧妙的数据结构是半边结构。它将一条边拆分为两个有方向的半边来存储。主要存储以下信息:
- 顶点:位置 + 一条出射半边的索引。
- 半边:起始顶点索引 + 左侧面片索引 + 同面内上一条和下半边的索引。
- 面片:包含的一条半边的索引。
通过这种结构,可以高效地进行邻域遍历等操作。例如,要遍历一个顶点的一环邻域面,可以从该顶点存储的半边出发,找到其对边,再通过“下一条半边”指针遍历该面,并循环此过程访问所有相邻面。
模型简化方法概述
前面我们介绍了网格模型的基本概念,那么接下来我们看看如何对模型进行简化或压缩。在游戏等实时渲染应用中,场景包含大量三维模型。模型越精细(面数越多),效果越逼真,但对计算和存储的压力也越大。
一种常见的解决方案是层次细节渲染:根据物体与摄像机的距离,使用不同面数版本的模型进行渲染。距离越*,使用面数越多的高模;距离越远,则使用面数少的低模。这就需要我们预先计算好同一个模型的多个简化版本。
模型简化方法在实际中主要分为两大类:
保纹理简化
核心思想:在简化三维网格和其UV网格的同时,不改变原始的纹理贴图。简化后的模型渲染时仍使用原纹理。
优点:节省存储空间,因为纹理数据通常很大,重用原纹理避免了额外的纹理存储。
要求:简化后的UV布局应尽可能与原UV布局保持一致,以确保从纹理采样得到的颜色能正确映射到简化后的模型表面,保持外观相似。
主流方法:基于二次误差度量的边坍缩算法是这类方法的核心。它通过逐条计算并坍缩对模型形状改变最小的边,来逐步减少面数。此类方法有大量研究和变种。
其他思路:
- 对三角面进行聚类,每一类用一个多边形表示,再对该多边形进行三角化。
- 针对四边形网格的简化。
- (注:一些基于机器学*的简化或基于内在三角形表示的方法,可能不专注于保持UV,但提供了不同的思路。)
不保纹理简化
核心思想:简化过程完全打乱原有的网格连接关系和UV布局,生成一个全新的、更简单的网格。因此,必须为这个新网格重新生成对应的纹理贴图。
优点:通常能获得面数更少、几何更简洁的简化结果。
流程:通常包含三个主要步骤:
- 重新网格化:为模型生成一个全新的、质量更好的网格。方法多样,例如用大的*面片(如四边形)去拟合模型、将模型划分为更规律的网格、或通过优化方法提取一组*面来*似原模型等。
- UV展开:为新的网格计算UV坐标,将其展开到二维*面。
- 纹理烘焙:将高精度模型(高模)上的颜色、光照等信息“烘焙”到新网格(低模)的纹理贴图上。
纹理烘焙方法:
- 光线投射:从低模表面向高模发射射线,取交点处的信息(如颜色)作为低模对应纹理像素的值。简单直接,但在高低模差距大时可能映射错误。
- 可微渲染:利用可微渲染技术,通过优化过程将高模的外观信息传递到低模的纹理上,通常能获得更高质量的结果。
本节课中我们一起学*了三维模型的多种表示方法,重点剖析了网格模型的构成、基本概念(拓扑、几何量、数据结构),并概述了模型简化的两大方向:保纹理简化和不保纹理简化,以及它们的基本流程和核心思想。下一节课,我们将深入讲解保纹理简化中具体的算法原理。


GAMES106-现代图形绘制流水线原理与实践 - P8:8. 几何处理与模型减面之保纹理的模型减面 📐➡️📏

在本节课中,我们将要学*一种在三维模型简化中非常经典且实用的方法——保纹理的模型减面。我们将深入探讨其核心算法框架、误差度量方式以及实现中的关键细节,目标是让初学者能够理解如何在不生成新纹理贴图的前提下,有效减少模型的面数。
上一节课我们介绍了三维模型的不同表达方式,并重点讲解了网格表示及其拓扑与几何属性。我们还概述了模型简化的几种不同方式及其优缺点。本节中,我们将聚焦于其中一种方法:保纹理的模型减面。
保纹理减面方法的核心优势在于,简化后的模型可以复用原始模型的纹理贴图,从而节省存储空间和带宽,这在游戏等对数据量敏感的应用中尤为重要。
以下是该简化算法的通用框架步骤:
- 定义并提取简化操作单元:首先,需要确定对模型进行简化的基本操作,例如边折叠。
- 计算操作误差:为每个简化操作单元计算其执行后对模型造成的“误差”。
- 构建优先队列:根据计算出的误差,将所有操作单元排序,放入一个优先队列中。
- 迭代简化:进入循环,只要队列不为空且未达到停止条件(如目标面数),就执行以下操作:
- 从队列中取出误差最小的操作单元。
- 检查该操作是否有效(例如,是否会导致模型自相交或严重变形)。
- 如果有效,则执行该简化操作。
- 更新受此操作影响的相邻操作单元的误差,并将其重新加入优先队列。
这个框架是绝大部分保纹理简化方法的基础。
上一节我们介绍了简化算法的整体框架,本节中我们来看看框架中的核心组件之一:简化操作单元。常见的简化操作有以下几种:
- 顶点移除:删除一个顶点及其相连的所有三角形,然后在形成的多边形空洞上进行重新三角化,从而减少面数。
- 边折叠:这是最常用的一种操作。将一条边的两个顶点合并为一个新的顶点,从而移除这条边以及与其相邻的两个(或多个)三角形。
- 顶点对合并:将两个位置非常接*的顶点合并为一个,这个操作本身不减少面数,但可以为后续的边折叠等操作创造条件。
在这些操作中,边折叠因其高效和通用性而被广泛采用。算法的核心挑战在于两点:如何对边折叠操作进行排序(即计算误差),以及如何确定边折叠后新顶点的位置。这两点都紧密关联于误差的计算。
理解了操作单元后,接下来我们探讨如何衡量一次简化操作带来的“误差”。误差的定义方式多种多样,例如基于几何距离的豪斯多夫距离,或基于视觉感知的误差。本节课重点介绍其中最经典、最实用的方法:二次误差度量。
二次误差度量 的核心思想是:衡量新顶点到其关联的原始三角形所在*面的距离*方和。对于一个顶点 v 和一组*面(每个*面对应一个原始三角形),其误差 Δ(v) 定义为:
Δ(v) = Σ (distance(v, plane_i))²
其中,distance(v, plane_i) 是顶点 v 到第 i 个*面的距离。对于一个由系数 [a, b, c, d] 定义的*面(满足 a² + b² + c² = 1),点到*面的距离为 a*x + b*y + c*z + d。因此,误差可以展开并重写为矩阵形式:
Δ(v) = vᵀ Q v
这里,v 是顶点的齐次坐标 [x, y, z, 1]ᵀ,Q 是一个 4x4 的对称矩阵,由该顶点关联的所有*面的系数矩阵 K_p(K_p = p pᵀ,其中 p = [a, b, c, d]ᵀ)累加而成:
Q = Σ K_p
当我们对一条边进行折叠时,这条边两个端点 v1 和 v2 的 Q 矩阵分别为 Q1 和 Q2。折叠后新顶点 v’ 的 Q 矩阵可以*似为两者的和:Q’ = Q1 + Q2。新顶点的误差则为 Δ(v’) = v’ᵀ Q’ v’。
为了得到最优的新顶点位置,我们需要最小化这个二次误差 Δ(v’)。这可以通过对 Δ(v’) 求关于 x, y, z 的偏导数并令其为零来实现,从而得到一个线性方程组:
∇Δ(v’) = 0
求解这个方程组,即可得到使误差最小的新顶点坐标 v’。将 v’ 代入误差公式,就能得到这次边折叠操作的误差值,用于在优先队列中排序。
在算法的迭代过程中,每次从队列中取出一个边折叠操作时,并非直接执行,而是需要先进行有效性检查,以确保简化后的模型质量。以下是几种常见的检查策略:
- 几何距离约束:防止折叠操作导致局部表面形变过大。一种高效的方法是使用层次化的空间细分结构来*似约束顶点移动的范围。
- 拓扑一致性检查:确保简化操作不改变模型的拓扑性质(如亏格、连通分量数)。可以通过检查 Link Condition 来快速判断。
- 面片翻转检测:防止操作导致三角形法向翻转,从而在渲染时产生瑕疵。通常通过比较新旧三角形法向的点积来判断。
- 自相交检测:避免简化后的模型表面出现自相交。这可以通过构建空间加速结构来高效检测三角形对之间的相交情况。
- 数值鲁棒性处理:由于浮点数精度问题,计算出的新顶点可能位置异常。可以预先定义模型的包围球,将新顶点位置限制在合理范围内。
如果操作通过了所有有效性检查,就可以安全地执行。执行后,需要更新受影响的相邻边的误差,并重新插入优先队列。
上述QM方法主要处理顶点的几何位置。但模型顶点通常还包含其他属性,如颜色、法线、纹理坐标等。保纹理减面需要将这些属性也纳入简化框架。
处理思路是将所有顶点属性视为一个高维向量。例如,一个包含位置(x,y,z)、颜色(rgb)和纹理坐标(u,v)的顶点,可以表示为 [x, y, z, r, g, b, u, v, 1]。同样,我们可以为每个关联的“*面”(现在是在高维属性空间中定义)构造系数矩阵,并累加得到高维的 Q 矩阵。优化过程在形式上完全不变。
但需要注意属性值的有效范围:
- 颜色:计算出的颜色值可能超出
[0,1]范围,需要进行截断或缩放。 - 法线:计算出的法线向量需要重新归一化。
- 纹理坐标:可能需要回绕或钳制到纹理空间的有效范围内。
通过这种方式,QM框架可以优雅地扩展到处理带有多重属性的模型,实现保纹理、保颜色等多种属性的简化。
此外,QM框架不仅限于三角形网格,经过适当调整,也可应用于四边形网格、体网格等。其核心思想——通过最小化到一组“约束”(*面或超*面)的二次距离来进行简化——具有相当的通用性。
对于四边形网格,可以提取一组*行的边构成“条带”,然后同时对条带中的多条边进行折叠操作。对于体网格,原理类似,但“*面”变为三维空间中的超*面。这展示了QM方法强大的扩展性。
本节课中我们一起学*了保纹理模型减面的核心算法——基于二次误差度量(QM)的边折叠简化法。我们详细剖析了其算法框架、误差度量的数学原理、新顶点位置的优化求解、操作有效性的多种检查方法,以及如何处理顶点附加属性。该方法因其高效、实用且易于扩展,成为工业界长达二十余年的经典选择。

下节课,我们将探讨另一大类模型简化方法:不保纹理的模型减面。那些方法通常更为灵活,但也会引入新的挑战。


GAMES106-现代图形绘制流水线原理与实践 - P9:几何处理与模型减面之不保纹理的减面算法 📐
在本节课中,我们将要学*不保纹理的模型减面算法。与上节课介绍的保纹理算法不同,这类算法在简化模型时,不保留原始模型的纹理贴图,而是为简化后的新模型重新生成纹理。这带来了更大的简化自由度,但也引入了新的处理步骤。
概述
不保纹理的减面算法旨在将高面数模型简化为低面数模型,同时不保留原始模型的纹理和UV映射。这意味着我们需要为简化后的模型重新计算UV并烘焙新的纹理。这类算法通常能实现更大幅度的面数削减。
不保纹理减面算法的概念
首先,我们简单回顾一下什么叫不保纹理的减面算法。减面算法的目标是将网格模型的面数降低。不保纹理的意思是,在生成新的简化模型时,附着在其上的贴图并非原始模型的贴图,而是需要针对输出结果新生成的贴图。
例如,左边三幅图是输入模型及其UV和纹理。右边及下方的模型是不保纹理减面算法的结果。这些结果模型使用的贴图是重新生成的。
不保持原始纹理为算法设计增加了自由度,因为我们无需保持原始UV。但另一方面,这也意味着我们需要处理重新展UV和生成贴图等步骤。
不保纹理算法的缺点在于会生成额外的数据(新纹理),这与减面以压缩数据量的初衷相悖。但其好处是可以极大地减少模型面数,例如从18000多个面减少到200多个面,降幅可达约1.5%。
算法核心流程管线
总的来说,不保纹理的减面算法经典流程管线主要分为三个步骤:
- 重新网格化:对输入模型生成一个新的、简化的网格。
- 展UV:为简化后的网格计算并展开UV。
- 纹理烘焙:将高模的信息(如颜色、法线)转移到低模上,生成新的纹理贴图。
每一步都对应着许多不同的算法。本节课将从高层次阐述经典方法,帮助大家理解流程和原理。
第一步:重新网格化
重新网格化是整个流程中最复杂、最具挑战性的一步。其目标是生成一个面数少、视觉上与原始模型相似,同时拓扑和几何结构干净简洁的低模网格。干净的拓扑有利于后续的展UV等步骤。
面临的挑战
在实际应用中,尤其是游戏中的建筑模型,高模的拓扑和几何可能非常复杂。例如,一个仅6000个三角形的模型可能包含151个互不连接的组件,以及大量自相交的三角形对。这些“脏”数据(非流形、自相交等)对自动化算法构成了巨大挑战。
美术人员在创建模型时,首要关注视觉外观,而非拓扑整洁度,这与自动化算法的要求不匹配。因此,手工或交互式创建低模至今仍耗费大量人力成本。
现有解决方案与我们的方法
现有解决方案包括工业界软件(如Blender、Simplygon)和学术界算法,但它们在处理复杂模型时,可能在简化程度、视觉保真度或鲁棒性上存在不足。
我们设计方法的出发点是根本需求:视觉保真和处理复杂输入。核心思路是:与其直接操作复杂模型,不如先为其计算一个视觉相似且干净的“代理网格”,再对此代理网格进行简化。
我们的方法管线如下:
- 生成视觉网格:通过从多个视角对模型进行投影和体布尔运算,生成一个从这些视角看外轮廓与高模一致的封闭网格。
- 公式示例(概念性):
VisualMesh = ∩(SweepVolume(view_i) ∩ BoundingBox)
- 公式示例(概念性):
- 生成覆盖网格:针对视觉网格无法捕捉的凹陷区域,通过额外的投影和布尔差运算进行“雕刻”。
- 公式示例(概念性):
CoverMesh = VisualMesh - ∪(SweepVolume(cut_plane_j))
- 公式示例(概念性):
- 简化网格:对得到的干净网格应用上节课介绍的QEM边折叠算法进行简化。
- 我们改进了参数选择过程,通过计算帕累托前沿,让用户从一系列不同面数的优质结果中选择,避免了繁琐的参数调试。
该方法能对各种复杂建筑模型生成外观相似、面数极低的干净网格,在面数和视觉误差指标上均表现良好。
第二步:展UV
在得到低模网格后,我们需要将其表面展开到二维*面,建立UV映射,以便后续贴上纹理。
展UV的要求
展UV步骤有以下要求:
- 硬约束:避免三角形翻转和UV自相交。
- 软约束:切割线尽量短、映射扭曲尽量小、UV块排布(装箱)尽量紧凑以减少纹理空间浪费。
经典流程方法:Teaser
我们介绍一个相对易理解的管线方法,它包含几个步骤:
- 模型切割:将输入网格分割成多个面片(Patch)。例如,可使用基于Lloyd算法的变分形状*似方法进行迭代分割。
- 面片参数化:将每个面片展开到二维*面。常用Tutte嵌入算法保证在凸边界条件下内部无翻转。之后可通过连续优化减少扭曲,并用“脚手架”三角形防止自相交。
- 代码概念:
UV_coords = TutteEmbedding(3D_patch, convex_boundary)
- 代码概念:
- UV装箱:将展开后的各个UV面片像拼图一样紧密排列在纹理坐标空间内,形成最终的UV图。
这是一个模块化的流程,已有MeshLab等工具集成相关算法,相对实用。
第三步:纹理烘焙
拥有低模和其UV后,最后一步是将高模的视觉属性(如颜色、法线)烘焙到低模的纹理上。
方法一:基于光线投射(Raycasting)
这是经典且鲁棒的方法。核心思路是为低模创建一个外扩的“笼子”(Cage),然后从笼子顶点向低模表面发射射线,与高模求交。将交点处高模的属性,赋值给对应的低模顶点(或通过插值生成纹理像素)。
- 优点:非常鲁棒,易于GPU并行化,速度快。
- 伪代码描述:
for each texel in low-poly texture: world_pos = map_uv_to_world_via_cage(texel.uv) hit_info = raycast(world_pos, direction, high-poly_mesh) texel.color = hit_info.color
方法二:基于可微渲染(Differentiable Rendering)

这是较新的思路。它将渲染过程变为可微分的,从而可以构建优化问题。例如,目标是让低模渲染出的图像与高模渲染出的图像尽可能相似。通过可微渲染计算梯度,可以反向优化低模的纹理贴图(甚至几何)。
- 优点:作为连续优化方法,在某些情况下能得到比光线投射更优的效果。
- 公式描述:
min_θ L = || Render(high_poly) - Render(low_poly(θ)) ||, 其中θ为需要优化的纹理参数。


总结

本节课我们一起学*了不保纹理的模型减面算法。我们首先了解了其核心概念与流程管线,它主要分为重新网格化、展UV和纹理烘焙三大步骤。
随后,我们深入探讨了重新网格化的挑战与一种解决方案,即通过多视角体布尔运算生成视觉代理网格再进行简化。接着,我们概述了展UV的经典流程,包括切割、参数化和装箱。最后,我们介绍了两种纹理烘焙的核心思路:基于光线投射的经典方法和基于可微渲染的优化方法。
不保纹理的算法流程复杂,但能实现极高的简化率,是游戏等应用中压缩三维资产数据量的重要手段。掌握其核心步骤与思想,有助于我们理解现代几何处理流水线的构成。


GAMES201:高级物理引擎实战指南2020 - P1:Lecture 1 课程介绍 🚀
在本节课中,我们将学*高级物理引擎实战课程的整体介绍,包括课程目标、物理引擎的定义与应用、讲师个人经历分享,以及本课程将使用的核心编程工具——太极语言的入门知识。


大家好,我是胡渊明,我在MIT读书。非常高兴有这么多同学一起来看我们的高级物理引擎实战2020课程。整个课程是基于太极编程语言的。内容是关于怎么写一个物理引擎。我们非常注重物理引擎的实战。
每位同学,我们希望都能在这个课上写一点自己的代码。因为你写的代码和听我讲数学公式,你的感觉是不一样的。你会很有成就感。这个课的目标就是自己动手打造一个影视级的物理引擎。适合人群是0~99岁的计算机图形学爱好者。如果大家有一点高等数学,或者Python,或者任何一门编程语言的预备知识的话,那就最好不过了。



我们课程安排是每周一,北京时间晚上八点半到九点半,一共十节课。一个小时的课程有可能我们会往后延长,比如八点半到十点。这个根据大家的反馈。我这边是早上八点半到九点半,我们正好是12个小时时差。所以一般都是把am换成pm,或者pm换成am就可以算波士顿和北京的时间。


课程内容后面我们会具体再讲。我们先开门见山定义一下什么叫物理引擎。在维基百科上可以看到这个定义:物理引擎是一个计算机软件,它提供对于一个物理系统的*似的模拟,比如说刚体物理的动力学、软体动力学以及流体动力学。主要的应用是在计算机图形学里面,比如游戏和电影。其实一句话说,就是在你的电脑里面模拟这个世界。我想这大家应该有很多同学想过做这个事情,我觉得还是一个很有意思的事情。我小时候就很想怎么在电脑里面模拟一个世界出来。





除了刚才说到这些应用,它还可以在工业软件里面有很多应用。现在你去做一个模型,你要做它的应力分析,你可能先在电脑里面算一算,看看它做出来以后应力分析可能会是什么样。这样你就不用真的把它做出来,然后试一试发现不行再重做。这种迭代过程可以完全在计算机里面进行。





还有一个很重要的应用就是电影里面的视觉特效。现在电影里面,特别是动画电影,基本上每个电影里面可能有非常非常多的镜头都是用物理效果模拟出来的。

第三个应用就是虚拟现实、增强现实。如果你在VR里面有一个基于物理世界的话,那你的沉浸感会增强很多。因为你可以和这个世界交互了。并且这个虚拟世界里面的物理规律和现实世界里的物理规律是一致的,所以你就可以有更好的VR体验。

当然现在很多人用物理引擎来训练机器人,比如说无人驾驶。搞一个街道的物理引擎,这样车就可以在虚拟世界里面开,不用到真实世界里面冒着产生交通事故的危险。

大家可能最关心的物理引擎的用处还是在游戏里面。我今天就仔细讲一讲游戏。
我很小的时候就玩过很多物理的游戏,基本上它们构成了我童年的很大一部分。有一个我印象非常深刻的叫做《不可思议的机器》。这个游戏是这样的:你可以在游戏里面摆入各种各样神奇的道具,比如说一个球、火箭、杠杆,然后这些都会根据物理规律来运动。那个时候其实这个游戏里面有一个问题,就是它这个球是不会转动的,它这个球只会以各种形式*移。当然我是20年以后重新到网上找它视频出来的时候,我才发现原来居然有这个问题。我之前小时候玩的时候完全没有意识到有这个问题。可以看到大家对随着时间推移,对物理效果的追求真的是水涨船高。小时候玩一个这个我觉得已经很厉害了,然后后面有更厉害的。
大概可能很多同学都玩过《愤怒的小鸟》。我记得那个时候,我可能上初中的时候,那个时候非常流行iPad,就是最早的第一代iPad。iPad上面它提供了一个触摸屏,这个触摸屏它可以多点触控。一个很经典的游戏就是这个《愤怒的小鸟》。你可以通过拖拽这个弹弓,然后弹弓打出去,小鸟就撞到游戏里面的各种障碍物。障碍物有的时候会碰撞。这个截图应该很多同学都玩过。我觉得还是很有意思的,我花了很多时间玩这个。
另外一个稍微更加可定制一点的物理引擎是《Phun》。这是一个纯的物理引擎,它不是一个游戏。你可以自己在里面放各种东西,放点什么齿轮、水、滑轮、棱镜什么的。你就可以在这个软件里创建一个物理世界,然后去看它怎么模拟。我小时候经常玩这个东西。感觉有一段时间好像小学或者初中吧,那个时候特别喜欢玩这个,大概每天一放学就玩这个东西。
15年的时候又有一个很有意思的游戏,这个叫《Besiege》。这个游戏的特点是你可以自己把各种各样的模块组装成一个你的攻城武器。你带着这个攻城武器,你就可以去攻打各种各样的城池什么的。这个我自己没玩,这个是我本科的时候看我室友他玩的。感觉非常有意思,可以造各种飞艇或者攻城车什么的。我觉得还挺有意思的。
我最*大概是去年暑假,我玩了一下《塞尔达传说》。这游戏里面有很多物理效果,有很多谜题你需要用物理引擎去解决。这个是这个物理引擎的一个bug:你如果把三个箱子堆在一起,你就可以通过拉最后一个箱子的方式让自己朝前移动。但这不是很物理。
最*应该说我3月份的时候玩了一下《Ori》今年最新的这个。我觉得还是非常不错。这个游戏里面几乎所有的东西都是可以动的。你踩到地上地会往下陷,然后你可以和游戏里面各种各样的东西去交互。我3月份玩的时候因为我是比较早期的玩家,然后有各种bug。我觉得这个效果还是非常好的。
刚才讲了一下过去二三十年整个物理引擎技术里面,我自己玩过的一些游戏。下面我来讲讲我自己的物理引擎的故事。这个从大概初二开始,到现在居然11、2年过去了,我还在做和小时候喜欢做的是一样的东西。很有意思。
这个是我09年的时候写的第一个物理引擎。这个其实非常简单,就是一个弹簧质点系统。那个时候我对09年我应该是初中。你可以看到当时这个可能用一台不太好的笔记本,分辨率实在是非常低。所以你可以看到这个还是一个双核的笔记本。它是一个你可以创建很多这个弹簧质点系统。视频我没有留了,我只有一些截图,这个代码我也找不到了。
后面有一些有视频的。这个是11年的时候,那个时候我应该上高中了。那个时候学了一年刚体动力学,然后写了一个刚体的物理引擎。应该也是受到愤怒小鸟的影响,因为当时玩这个游戏就想这游戏里面这个物理效果是怎么实现,然后我就自己写了一个。那这个代码链接上面是有的。不过我觉得一个有意思的就是这个代码到现在还能跑。我可以跑一跑。
我来跑一跑这个物理引擎。这个最早我是在Windows上面写的,那个时候没有买这个MacBook Pro。后来我把它移植到Mac上面。还是挺有意思的,可以加各种各样的几何体。这个有一些参数可以调。调了以后,比如说我现在可以把摩擦关掉,大家可以看一看效果是什么样的,就滑滑走了。然后我再把它拆下来。我还可以把重力调成负的。你可以创建各种几何体。我记得还有个功能可以画任意几何体。对,他会做一个三角形的剖分,然后把它用三角形表示出来。再看看能不能画一个齿轮。我记得有一个功能可以画齿轮。哇,这个快捷键我十几年过去我居然还记得,真有意思。我们把它...这个理论上这个齿轮是可以驱动的,但是我已经忘了怎么去拖它。可以拖可以拖。我再看看我自己有一些其他的场景。这个是一个叫做牛顿摆。这个我记得当时物理老师经常喜欢用这个东西来做一个演示。我可以尝试一下。你如果放一个,你看到这边弹起来是六个的,右边一定会弹起来六个,左边一定弹起来六个。这个我记得当时上物理课的时候,盯着东西看了好久。下面是个多米诺骨牌。还有一些别的demo啊,这个是一坨弹簧。你可以看,我可以拉*一点。就我小时候玩过一个玩具,好像叫做什么机灵鬼,就是一坨一些弹簧,你可以两个手,他可以从左手跑到右手,右手跑到左手。应该就这些demo。所以这个代码已经年久失修了。你可以看看最后一次什么时候改的。他最后一次是8年前,8年前我把它扩展到Mac OS X上面。还有这个2012年8月26号,时光飞逝。
OK然后下一个。这个是我14年,我应该是大二的时候写的一个基于物理引擎的游戏。这个还是能跑,我看看跑一跑看看。OK,这个是这个物理引擎,不是我自己写的,这个是用的Box2D。当时我记得很喜欢玩一个游戏,叫做《纪念碑谷》,然后这个画风就抄了纪念碑谷。这个你是你可以通过W、S、A、D去控制这个能转的这个小人。然后你可以让他去驱动这个齿轮什么的。哎呦这靠的太*了,有的时候会和齿轮卡住。转这个齿轮的时候,这个右边这个树会长出来。上面这个齿轮好像是控制光影。我一开始想做一个完整的游戏,后来因为没时间做了一半就放弃,做了一个最小化的demo。这个大家可以自己去这个网址去玩一玩,还是挺有意思的。这个看起来是三维,但它其实是很多分层的二维物理。你看到这个这个块,它虽然浮在天上,但它其实还是一层一层之间是有碰撞,但层与层之间是没有的。OK赶紧把它关了,这个很耗GPU啊。
这个是我大概16年的时候写了另外一个流体的模拟。这个也在网页上可以放。大家我结束以后会把链接分享在网上,大家可以直接点进去玩一玩。你可以调各种solver参数。你可有的时候solver参数不对,他就不work。你看这个Jacobi iteration,如果不进行,他就肯定不行。然后这个FLIP的话,你可以在里面选择。那这些复杂概念我们后面都会介绍,所以大家暂时听不明白的话呢,不用担心。你可以调分辨率。我记得我当时好像你现在随便找一个好一点的GPU,跑512x512实时是没有问题,但我这个笔记本实在太烂了。没有办法。你可能有同学说这个风扇转起来可能非常吵,我就先把它关了。
所以这个是16年的时候。这个是一个17年的时候写的一个烟的模拟。哎呦怎么这么卡,哈哈不好意思,这个笔记本实在是不太行。这可能放的不是很流畅,我试试再放一遍行不行。啊看来这个笔记本已经放弃了。Anyway,这个当然,这个我后来跟做烟雾模拟的张星星前辈请教了一下,然后他说这个太黏了,效果不是很好啊,我就不过多展示了。
这个是我的第一篇SIGGRAPH paper。我们是用MPM做了各种切割的模拟。它实现了固体和液体的显示的耦合。然后切割是这个耦合的一个副作用。我们可以切各种切香蕉,切这个兔子,这个叫奶酪。你可以切沙子或者叫搅拌沙子。那这个算这个文章里面呢,我还提出了一种基于移动最小二乘的新的物质点法,它会比之前CCD的物质点法要快两倍。还是基于2016年的蒋老师的一篇叫Implicit Particle-in-Cell paper去进行的一个算法上的精简。这样使得它计算的时候工作量会小一半。
有同学问这个渲染时间和粒子的数目,这个我可以说一说。基本上这边所有的都不是实时的。这个paper里面基本上没有实时的demo。然后切你像这个切兔子或者切奶酪,这个基本上都是100万个到300万~400万个粒子。而这个右下角这个搅沙子,这个大概有我记得有三百五十万个粒子。我们当时模拟它可能得等上一天,还是挺费时间的。但是现在MPM这个算法就是它的性能逐渐的也在提升,所以逐渐的很多实时的应用应该也会出现。
这个是一个更大规模的例子。这个是西瓜分形,2018年我们做了一个,在一台机器上面减10亿个voxel的FEM。然后用FEM做完FEM以后,我们有sensitivity analysis,然后可以做topology optimization。这些概念我后面都会提到。这边大家只要记住这个voxel数目非常多就行了。代码网上也有,然后大家可以自己下来跑一跑。这个跑的时间就非常长了。这个我记得这个demo可能跑了得有一个星期。我记得中间还挂了一次,我还重启了。我对这个事情印象特别深刻。所以性能是非常重要的,在物理模拟里面。
然后最*一个东西我最*开始搞的是可微模拟。那这个东西其实很早以前也有,只不过我们最*把它流程搞简化了一遍。什么叫可微模拟呢?可微模拟就是说这个视频可能不是很流畅,我尽量让它放的流畅一点。笔记本已经弃掉了,现在已经非常卡了,可能是因为刚才跑的那个GPU的程序。可微模拟就是说,我们正常的模拟就是给你一个初始的状况,然后让你预测比如说十秒钟以后这个系统是什么样的。然后呢可微模拟是反过来就说,我希望去求出初始状况的一个扰动对于结果影响是什么样的。然后这个其实就是要求一个梯度对吧。当你有梯度的时候,你就可以对初始状况进行各种各样的优化。比如说这个地方,我们就在求一个初始的水面的高度上,使得经过512个time step以后,可以得到一个太极的pattern。可能现在解释有点抽象,我们后面会继续讲。
就到了今年了。刚才讲了过去10年的故事。那现在我在MIT读博士,现在第3年了。我还是在做物理引擎,但是我花很多时间去写编译器。为什么呢?因为我发现如果你没有一个好的编程语言,没有一个好的编译器,你要写高性能simulation是非常非常难的,就是非常非常费时间。所以我就重造了一个编程语言,这样大家就可以更容易地写自己的simulator。那么现在基本上可以说有一半的时间都在做这个太极编程语言。当然这个里面有非常非常多的贡献是太极社区的同学们一起帮忙做的。真的大家非常不容易。我觉得每天早上一起来就能看到GitHub上面好多的issue,每天跟大家一起来嘛,就跟开网游下副本一样,就解决一个个很难修的bug,进行各种各样的设计决策,真的非常有意思。当然我最*也开了这么个课,然后主要是想跟大家分享一下,怎么样去写物理引擎比较有意思。当然也做做这个直播,带货带一带这个太极编程语言,希望大家都能尝试一下这个新东西。
那么我们接下来讲一讲这门课。我们会注意力集中在哪些关键词上面。我们会简单讲讲各种各样的离散化。那这部分数学比较involved,所以可能也不会讲的特别深。更多的呢因为我自己主要是做计算机方面的,所以我会更多讲一讲,你从一个程序员的角度,怎么去直观地理解各种各样的离散化。
我们会讲怎么样写有效率的求解器。比如说现在你要写一个求解器,很长的时间很可能就是花在这个linear systems solver上面。不同的linear systems solver它的性能有天壤之别。后面我们会继续详细介绍这些preconditioner。
我们会讲讲怎么样去提高生产力,怎么样用比较短的代码实现一个高效的物理引擎。你别看这个短代码啊,听起来好像是一个非常糟糕的metric,好像这个代码行数是measure一个软件是非常糟糕的一个metric。但是你仔细想想,有别的更好的metric吗?其实你很难想到比这个代码行数更好的metric。这也是为什么我喜欢用这种metric来证明一个代码可以用很少代码能写出来。
当然我们也很在意性能。刚才提到很多模拟的我们都要等上一天的时间。然后迭代的话呢,其实你如果要再去做迭代的话,那就非常非常费时间了。
我们也会简单介绍一下硬件的体系结构。因为你如果要写高性能solver的话,你得去了解一下你的硬件是什么样的。如果你不了解硬件呢,那你就很难把它全部利用起来。
我们也会讲讲各种各样的复杂的数据结构。但是太极让这个数据结构的使用变得更加的简单了。这个后面我们会详细介绍。
我们也会讲讲怎么样去做复杂的并行。因为现在并行硬件大行其道。CPU和GPU如果算它吞吐量的话,那真的是天壤之别,一般能差上一个数量级。这边展示的是SIGGRAPH 2020年的一篇比较新的SIGGRAPH paper。这个paper的结果非常非常impressive。他们可以用四个GPU,然后模拟应该是1亿个particle。这个在之前是前所未有的。大家有兴趣的同学可以去读一读这个paper。





最后我们会讲讲怎么去求simulator的导数。导数求出来呢,你就可以做很多有意思的事情。比如说我们这边还有两个机器人,都是软体机器人。上面的是一个,上面机器人他有四块肌肉,就是他有两条腿,每条腿有两半,然后左边一半,右边一半,每一半可以伸缩,或者可以叫变长或者变短,就相当于你的肌肉嘛,你可以肌肉可以收缩或者舒张。那下面一个呢是一个弹簧质点的机器人。上面这个是一个MPM机器人。
好那么我们这个第一讲的上半部分就结束了。我们来下面呢我们介绍一下太极编程语言,因为这个是一个大家都要用上的工具。好,那么现在这个slides我在整个课程期间会不断的更新。因为我们今天只能讲一讲最简单最简单的内容。然后后面我们会逐渐的加入太极里面的一些比较复杂的feature。这些slides呢都会课程结束以后都会在网上有的。所以大家不用担心。如果说有任何slides里面没有介绍的东西了,大家可以去看太极的文档。现在太极的文档在大家努力下,英文文档和中文文档应该都是比较可读的。特别是中文文档真的非常不容易。基本上就以大家以迅雷不及掩耳盗铃儿响叮当仁不让之势,就把这个中文文档给他全部翻译好了。真的太不容易了。当然很多feature我今天讲到的,并不是我自己一个人写的。很多feature是我们社区一起写的。然后我当然也不是唯一的developer。


那么什么是太极呢?太极是一个高性能的领域特定语言。它嵌入在Python里面。所以它的语法和Python非常非常的像。然后你如果会Python,那你基本上很快你就可以学*太极语言怎么用。他这个语言专门是为了计算机图形学的应用来设计的。我们在设计的时候,非常非常注重生产力和可移植性这两个东西。以前在计算机图形学里面是非常难以做到的。我说的这个生产力,不是说你用游戏引擎做游戏的这个生产力,我说的是对于一个想研究图形学算法的人,去写一个图形学算法的生产力。我们也很注重可移植性。


这门课呢,我们建议大家都使用太极编程语言。因为太极程序有什么好处?你在一台机器上写的太极程序,复制粘贴一下到另外一台机器,只要一台机器装了太极,基本上都是能跑的。这个就和你用OpenGL,和你用其他的像CUDA写的程序就完全不一样。因为你很容易,如果你不用太极,你会各种各样的依赖,你就为了让一个画一个三角形,你可能要装三四个包,就非常非常麻烦。


太极呢是面向对象的,然后它是自动并行,并且kernel是mega kernel。这个和深度学*里面的那种coalescability比较弱的kernel是不一样的。我们还有一些比较好的稀疏数据结构以及可微编程的支持。这个我们就后面再介绍了。




OK那今天其实就讲几个非常简单的内容:怎么安装,怎么定义数据,怎么做计算,然后怎么进行调试。我们先看看怎么安装。



那么如果你用Python 3.6,3.7或者3.8,你就可以直接用pip去装一下太极。大家要注意一下,我之前闲聊的时候也说过,就是说国内镜像有的时候它有一点延迟。所以有的时候,比如说官方镜像已经0.6.7了,然后国内镜像可能还是0.6,然后就会有一点延迟。然后就会导致因为太极可能不是最新的版本。这个我自己没有去实践过,但是之前知乎上面有很多人跟我说这个事情。就是说大家装的时候要确认一下版本。

然后三个主流的操作系统我们都是支持的。太极程序呢也可以既在CPU上面运行,也可以在GPU上面运行。GPU我们支持CUDA,然后OpenGL和Apple Metal。

如果你有一些神奇的配置,比如说你的CPU是ARM,或者你非要用Python 3.9以上,那也没办法,那你就得build from scratch,把太极的源代码下来装吧。但如果我相信用Python 3.9的同学是比较少的。最新的Python已经3.10了,我记得5月25日的时候好像3.10已经出现了。但是大部分同学应该都是3.6或者3.7,3.8可能都是相对



GAMES201:高级物理引擎实战指南2020 - P10:Lecture 10 总结 🎓
在本节课中,我们将回顾整个课程的核心内容,探讨“简单性”在科研与工程中的重要性,并展望物理引擎的未来发展。最后,我们将总结课程收获并表达感谢。
欢迎大家来到我们课程的最后一讲。从第一讲到今天,过去了大约三个月的时间。大家相当于用一个暑假的时间,基本学完了这门课程。
在正式讲解之前,我们首先展示作业二的各种获奖作品。虽然这次作业二时间非常紧,并且有很多同学反映正好遇上期末考试或工作繁忙,但仍有12位同学提交了作品。我们会给这12位同学都寄一份精美的纪念品。后续会找时间撰写文章,让课程以外的同学也能了解到大家在这门课上做出的精彩程序。课程结束后欢迎大家继续提交,但后续提交可能需要等待较久才能收到纪念品,因为课程结束后的组织会更加松散。
我们还要介绍一下ChinaVR上的太极竞赛。之前有很多同学反映报名表太长,成为了报名的阻力,所以我们后来做了一个一分钟完成报名的报名表。这个报名表非常简单,只有四个问题,其中三个是必填的。希望大家都能尝试报名一下。奖项设置非常丰厚,有特等奖一个、一等奖三个、二等奖六个,以及三等奖和优秀奖若干。基本上只要完成一个合格的提交,都会拿到某种奖项。竞赛的网站也放在这里,请感兴趣的同学多多参加。
在正式开始讲今天的内容之前,首先要恭喜各位。因为大家掌握了各种各样的新技能。这些技能包括一门新的编程语言、基于物理的模拟的各种算法、各种数值线性代数的方法,以及现代计算机的处理器微架构、并行编程、GPU编程等各种高性能计算的入门知识,还有稀疏数据结构和可微编程这两个太极的独特功能。能把这些知识都掌握甚至入门,需要花不少精力。这门课难度不小,能坚持到最后的都是勇者。大家应该给自己鼓个掌。
那么我们今天讲什么呢?今天主要是我的一些个人体会。我在构建物理引擎和太极这个项目时所学到的知识。如果让我必须只能用一个词来概括我这10年来从这些事情里面学到的东西,这个词就是 simplicity(简单性)。我后面会论述为什么简单是好的。讲完这个以后,我会讲讲物理引擎的未来,简单讲讲我自己在课程中的收获,最后感谢各方面为这门课程提供支持的老师和同学。
刚才提到,如果只让我用一个词概括我在物理引擎和太极这个项目里学到的东西,这个词一定是 simplicity(简单性)。说到这个简单性,很多同学可能还在学校里面读本科。在基础教育或本科阶段,学生最重要的一个事情是把考试考过。考试时要做的事情是证明自己会某种方法。但是当你本科毕业以后去工作、读PhD做研究或做一个工程时,最重要的能力是证明自己能找到好的方法。当你离开学校以后,有很多情况会改变。第一个改变是你有自己选择方法的权利。第二个很重要的改变因素是你需要与他人合作。这两个因素使得找到一个好方法非常重要。往往在你脱离了学校环境以后,用哪种方法并不重要,重要的是系统性地解决问题。解决问题是最重要的,方法不重要。一天只有24个小时。聪明的人都是一样聪明的。在时间和脑力都非常有限的情况下,我们必须采取的一个措施是:用简单可行的方法去解决大部分简单的问题。因为世界上有很多难题是真的非常复杂的。你必须用简单的方法、用最少的时间把简单的问题解决了,才会有时间去解决真正复杂的问题。别把时间都浪费在解决简单的问题上面,那你就没有时间去解决真正复杂的问题了。
有影响力的创新往往会伴随着复杂性的减少。很多同学觉得新的东西一定会更加复杂,但往往并不是这样。发明它的人往往已经对它进行了最大程度的简化。这有点像日本文化里的一个宗旨:不要给他人带来麻烦。你要发明一个新的东西,必须让你的用户能够轻易地使用它。你造出来的东西必须是简单的,必须不能给他人带来麻烦。如果你写了一份代码非常难以使用、难以读懂,那么在和他人合作时,这份代码可能根本就不会出现在最后的代码库里。因为审阅者会删掉你的代码,因为他不懂你到底在干什么。即使你的代码有幸进入最终代码库,它的维护、测试和正确性都非常难以保证。
刚才说到简单性是好的,反过来说就是复杂性是坏的。为什么复杂性很坏呢?首先,如果你有一个非常复杂的东西,那么其他人不能理解你在做什么。其他人不见得和你有一样的背景知识。如果你刚提出来一个非常复杂的东西,没有人能懂你做的东西,它产生的影响力就非常小。其次,一个复杂的东西要实现它非常缓慢,并且往往难以快速失败。为什么要快速失败呢?因为如果你做科研,你就会知道99%的想法都是不工作的。剩下的1%里面,可能有90%是被前人尝试过的。你只有可能剩下的1‰的想法是能工作的。你必须在精力有限、并且大家都一样聪明的情况下,还想做出有意义的工作。一个非常重要的事情就是能快速地知道这个方法是否有效。你必须能够快速失败。如果你有一个非常复杂的想法,它实现起来非常费时间,你很难快速失败。因为你不知道到底是自己实现错了,还是这个方法本身不行。你很难区分到底是方法复杂,还是自己没有实现好。这其实是复杂方法的原罪。这就引出了第三点:复杂的方法更容易出错,而且出了错以后很难去分析它。因为它其中的成分太多了,你不知道到底是哪一部分搞错了。很多复杂的模拟算法,虽然最后能发表在顶级会议上,但是随着时间推移,很快大家就忘了这个算法,因为它太复杂,没有人能够正确实现。看着一篇论文实现这个算法其实是非常难的。如果你实现了半天发现它不工作,那到底是论文有问题,还是实现得不对,还是自己对论文的理解有问题?你很难知道到底是什么原因。你甚至可以说这是不科学的,因为你不能证伪它。
复杂性还有一个很坏的事情:你有一个很复杂的算法,不管是你手写也好,还是编译器帮你编译也好,它都非常难以高性能地实现。大家可能会觉得复杂的算法往往跑得更快,但是实际情况下并不是这样。复杂的算法首先你得实现正确。假设你能把它实现正确,还记得上一讲吗?上一讲我们提到,对一个算法,如果考虑上计算机体系结构、考虑上各种高性能计算的技巧,你是能够把它提高十倍乃至二十倍的速度。但前提条件是这个算法足够简单。如果说你给我一段普通的随手写的程序,我可以花一些时间重写一遍,可能能快个十倍、二十倍。这些一般都是可行的。但是如果说你给我一个非常复杂的程序,我即使知道我花很多时间能让他快十倍二十倍,我可能都没有动力去做这个事情。因为太复杂、太烦了,大家懒得帮你去做性能优化。所以你要实现一个高级的算法,它首先得是简单的,不能太复杂。复杂了以后没有人能知道怎么去把它高性能化。因为高性能本身就是一个复杂性。人能处理的复杂性是有限的。如果你这个算法本身非常复杂,那剩下来的能够接受的复杂性就非常小。那么代价就是你在剩下来的脑力里面,能够用在优化它的部分是非常少的。所以最后导致的结果就是:复杂的数据结构、自适应数据结构、复杂的网格、复杂的数值格式,最后都会败在暴力解法之下。什么是暴力解法?就是稠密数据结构、一阶精度的有限元或MPM。当然这不绝对,有限元大家有的时候还是会用很高阶精度的。但另外一种方法就是用一阶精度的元素,把分辨率提得很高很高。虽然在严谨意义上这可能不太行,但实际情况下往往就不错了。特别是在你写物理引擎的时候,如果你把你的求解器搞得非常复杂,那编译器也会很难优化你的代码。编译器会遵循一些规则去分析你的程序。如果你的程序非常复杂、难以分析,那编译器也会很困惑,不知道你的代码在干什么,生成的代码质量也会受影响。
既然复杂性这么坏,为什么大家还会把东西搞得非常复杂呢?大家可以想一下这个问题。答案或许出乎我们的意料。答案是什么呢?Because it's way easier to be complicated than to be simple.(搞出复杂的方法比搞出简单的方法简单多了。)听起来可能有一点矛盾。总体的意思是:对于大部分人来说,如果你精力有限、经验有限、各种资源都有限,你搞出来的方法往往是比较复杂的,而不是简单的方法。

我们来看几个具体例子,为什么有很多情况下大家会喜欢把东西搞得非常复杂。

以下是大家可能把事情搞复杂的几个动机:
-
证明自己很聪明。这是我们的教育里面存在的一个很有意思的现象:我需要通过一些复杂的方法来证明自己和其他人不一样,证明自己很聪明。因为大家的直觉是:你懂这么难的东西,你一定很聪明吧。在学校里面或许这是一个好主意。你可能通过做一些难题,或者通过做一些大家都不懂的方法,显得自己独一无二、鹤立鸡群。但是实际上当你需要去和其他人交互的时候,这几乎永远不是一个好主意。因为如果你去看一看你做的项目的更大图景,其中的关键因素就是你能不能和其他人有效沟通。你的想法能不能从你的脑子里面精确传输到另外一个人的脑子里面。如果是一个简单的方法,沟通已经很困难了。沟通是最难的事情,沟通比写代码难多了。简单方法的沟通已经很难了,更何况一个很复杂的方法。所以一旦你要和他人沟通的时候,把事情搞复杂绝对不是一个好主意。一定要把事情简化简化再简化。这就是为什么简单精炼的一些方法能够流传得很广。
-
展示工作量。证明自己做了很多东西。有一些课程项目会要求你写一个五页或十页的报告。结果你搞了一个方法,发现报告好像只能写一页。你是不是可以把方法搞复杂一点,这样就能凑到五页或十页了?学校里的学生往往会有这样的思维:老师要求的方法必须要有复杂性。实际情况下,学校里面大家可能更加过程主义,看你解决问题的过程,因为目的是让你学会解决问题的方法,而不是让你把问题解决。但是当你脱离了老师、脱离了学校环境、脱离考试设定以后,去真正解决实际问题的时候,谁在乎你用什么方法去解决问题呢?你只要把问题解决好了就行了。所以大部分专家并不需要去展示你做了多少工作。
-
隐藏错误。这个或许也有点反直觉,但这个逻辑其实也很容易理解。有些人觉得我做的这个工作里面可能有错误,那怎么办呢?我把它搞得非常复杂,这样就没有人证明我是错的。因为没有人理解我这个东西到底在干什么,所以我就是对的。这个逻辑说起来很讽刺,但有些时候势必会有人这么做。当你这么想的时候,其实已经开始违背科学的精神了。卡尔·波普尔对于科学的定义在于:科学必须满足一个特性,就是它是可证伪的。如果你在科学的体系里去论证一个东西,你怎么说明它是科学的?就是说如果我这个论断是错的,你是可以去证明它是错的。如果你把一个东西搞得非常复杂,它既不能被证真,也不能被证伪,那这个东西它就“not even wrong”(连错了都不是),它不属于科学讨论的范畴。
-
希望和先前工作区分开来。这样你的论文就可以被接收。你写了一篇论文,方法非常简单,之前可能已经有人做过了。审稿人就会说你的方法和之前某某某论文做的一样,我要拒稿。所以有些做研究的同学可能会把方法搞得非常复杂,这边打个补丁,那边打个补丁,使得它看起来和之前的方法不一样。审稿人在考察这个方法到底之前有没有做过的时候,看这个方法好像非常复杂,那可能之前确实没有人做过吧,至少在创新性上面就不会再来质疑你了。这么做可不可以呢?如果你的目的就是让你的论文被接收,这么做是完全可以的。如果你的目的是尽快毕业、发表足够数量论文,这么做是完全可以的,也有很多人确实就是这么做的。但是如果你真的希望你的文章有很大的影响力,那最好还是别这么做。因为这些文章做出来,说白了就叫做增量性工作。如果你把这个推到极致,大家就叫它“灌水”。就是说我把一个方法这边修修改一下,里面修改一下,把它搞得非常复杂,本身原来想法的精简性就没了,搞出来一个非常复杂的算法。这种论文投了也能中,但是一般来说很难产生很大的影响力。
-
懒得去简化它。一个想法诞生的过程,往往可能是20%的时间是在想这个东西,然后后面80%的时间是在去简化你这个东西。你可能20%时间做个雏形,写上一坨乱七八糟的代码,然后花80%的时间去简化它,使得得到一个最小份的、还能工作的代码。如果你只花前面20%时间,你就只用了别人1/5的时间,你也能得到一个非常复杂的算法。这种情况下,如果把后面80%忽略掉,那就相当于懒得去优化。这样其实也是可以的。但是你如果希望别人能够理解、希望你这个工作从长远角度来看很有影响力,那最好还是花一点时间去简化你的想法。
-
受众过于宽容。这是一个很有意思的例子。我记得当时我在姚班上一门课,老师说如果你的方法非常复杂,我们看了半天也不知道它是对的还是错的,我们会当做错了来处理。在大部分情况下,甚至有些人在考试的时候会故意写出来一坨显得自己好像懂得很多。阅卷老师可能会觉得这个同学写了这么多,我虽然不知道他是对的还是错的,好歹给他个辛苦分吧,给他个两三分。这样其实就是纵容了这种把事情搞得非常复杂的想法。但是这种事情只会在学校里面出现。在实际的过程中,没有人会欣赏你这种做了很多工作、最后没有人能够理解的事情。我记得当时那个老师就是说,如果你不会,那你就写你不会,我会给你三分。但是你不要说你不会,你写上一大坨,这样我作为阅卷人浪费很多时间,你也浪费了很多时间。
所以你看其实大家有各种各样的动机,会去把事情搞得复杂。还有一个有意思的事情是:大家会觉得用简单方法的人很傻。是这样吗?其实并不是,绝对不是这样。实际上世界上最聪明的人都知道把事情搞简单的重要性。
我们来看看:
- 爱因斯坦说:任何事情都应该被搞得尽可能的简单,但不要过于简单。
- C++之父Bjarne Stroustrup在CppCon会议上给过很多关于简单性的演讲。C++现在已经变成了一个非常复杂的语言。他的演讲中有一个叫做“Make Simple Tasks Simple”(用简单的方法解决简单的任务)。Being too clever is not clever.(表现得太聪明并不是聪明人的做法。)他举了一个很有意思的例子:调试一个程序比写程序还要难两倍。因为大家写程序的时候,大部分时间并不是在敲键盘上面,大部分时间是在调各种bug上面。如果说你在写你的程序的时候,已经把你的全部脑力都用上了,那你调试的时候就没法调试了。因为你竭尽全力才把代码写出来,调试又比写代码要难,那你就没有能力去调试它了。这其实是一个很精辟的话。每当我看到各种太极社区里的PR搞得非常复杂,或者看到合作者写非常复杂代码的时候,我就会援引这些例子。首先,调试比写代码难。如果你写代码的时候已经搞得很难,那你怎么调试?第二,别人来接替你的工作,比你自己在你的代码上工作难。如果你自己写代码的时候就把代码搞得非常复杂,那你找谁来接替?你只能找一个比自己更聪明的人来接替。但是问题又来了,在MIT或者在姚班这样的地方,大家都一样聪明,那你上哪儿去找这样的人?很难找到这样的人的。那么最后结果就是你的项目就自己死了。
- 图灵奖得主Tony Hoare(他发明了快速排序)说:有两种软件设计的方法。第一种是使得软件足够简单,以至于显然没有错误。第二种是使得软件足够复杂,以至于没有显然的错误。第一种方法难得多。 他的第二种方法其实就是我们之前提到的,把一个事情搞得非常复杂,这样就没有人能指出他其中到底哪里错了。一个常见的做法是你发明了一个算法,你发现这个算法在这种情况下会错,我给他打个补丁去特殊判断一下;在那种情况下好像也会错,我给他打个补丁特殊判断一下。最后你得到了一件满是补丁的衣服。现在我问你这个衣服它有没有洞?你很难说,因为补丁太多了。我不知道它到底有没有洞,或者说我很难指出它哪里有洞。能证明它没有洞吗?并不能。
- 2017年的图灵奖得主David Patterson在Google给过一个演讲,叫做“How to Have a Bad Career”(如何毁掉你的职业生涯)。他列出了十个毁掉自己职业生涯的做法(他在说反话)。他的第二点是:Let complexity be your guide(让复杂性指导你)。他其中说到两点其实我们之前也提到:It's easier to be complicated(把事情搞复杂是很简单的)。这个其实很多人意识不到这一点。他会觉得复杂的东西就是复杂,简单的东西就是简单。其实你在真正做研究也好、做工程也好,打补丁把事情搞复杂永远是简单的方法,而提出简单的想法能解决问题,这个往往是最难的。他也提到,如果说我的方法很简单,那我怎么能显示出我和我的同事们的优越性呢?我怎么能显示出我比我的同事们更聪明呢?如果我的方法很简单的话。这其实是一个讽刺的反话。其实并不是这样,往往最伟大的想法都是很简单的。
好像说了这么多,其实都是一些抽象例子。我们来看一看我自己的体会。


- 我以前做过一篇论文叫做“ChainQueen”,发表在SIGGRAPH 2019上。这个论文一开始的目的就是要去做一个可微分的MPM求解器。但是在做这个项目的时候,我们还没有一个自动微分系统,我们必须手动的去求它的导数。我当时求这个导数求的真的非常痛苦。由于求导数你要用很多链式法则,所以我就把这个“chain”这个词塞到了项目的名字里面。但是当时有一个非常好的事情:当时我们已经有“Moving Least Squares MPM”(MLS-MPM),这是一个比传统的APIC MPM要简单很多的算法。正是因为MLS-MPM非常非常简单,我们才能够在25天之内做出一个SIGGRAPH论文,并且能正确地把它的导数推出来。如果你给我一个非常复杂的MPM算法,我根本就不会去推它的导数,因为太难了,推不对。人的脑力是有限的。如果搞得非常复杂,没有人能正确求出它的导数。所以说如果没有这个简化版的MPM,后面我们有很多工作都是做不了的。后面大家会知道我们有自动微分系统,我们有一个叫做“DiffTaichi”的项目,它的目的其实就是进一步简化可微编程的过程,再也不需要去手动求导数了。当时我推这个导数是非常痛苦的,其实就是这么一坨公式,你要把它们推对还是要花点精力的。
- 另外一个例子:太极它之所以存在,它存在的目的就是把一切东西都能够简化。有一个算法叫做“Multigrid Preconditioned Conjugate Gradient”(MG-PCG,多重网格预条件共轭梯度算法),用来解线性系统(比如泊松方程)。这个算法的流程其实还是挺复杂的,一般大家都不愿意去实现,因为太难了。但是在有了太极以后,我可以只用80分钟、用300行代码把这个算法实现。这个80分钟是我从我的git log里面去看,两个commit的实现前和实现后,差了80分钟。这是因为太极这个编程语言非常非常容易,我才能做到这一点。如果你给我一个非常难用的工具,你给我个C++或者CUDA,那我绝对不可能在80分钟之内写一个GPU的MG-PCG出来。GAMES201做了什么呢?GAMES201把太极的这个想法继续推了一步。现在所有的人都知道怎么去写一个多重网格求解器。我看大家交的作业里面很多都是用上了多重网格。可以说现在MG-PCG这个算法在中国的物理模拟的圈子里面,这个代码可能已经人手一份了,大家都会写这个算法。所以你可以看到,从这个很少有人能正确实现,到我自己能轻易正确实现,到大家都能轻易正确实现,这个其实是有一个过程的。大家是付出了很多很多



GAMES201:高级物理引擎实战指南2020 - P2:Lecture 2 拉格朗日视角(1)🚀




在本节课中,我们将要学*基于物理的动画模拟中的两个基础概念:弹簧质点系统和光滑粒子流体动力学。课程将从拉格朗日视角出发,介绍这些模型的基本原理、数学公式和实现方法,并穿插讲解一些实用的编程技巧。
课程公告与作业展示 📢

我是胡元明,一名研究基于物理动画的博士生。在开始正式内容前,我们先同步一些课程相关的信息。

太极框架已更新至 v0.6.8。有同学反馈新版本在编译时可能因语法检查更严格而出现问题。如果遇到编译错误,可以暂时退回使用 v0.6.7。核心开发者正在努力修复,并计划尽快发布 v0.6.9。


作业零的评选已经结束。我们从众多优秀作品中选出了八个,并在知乎上进行了展示。这些作品涵盖了从离散傅里叶变换到流体模拟卡门涡街等多种算法,代码通常只有百行左右,易于学*和运行。获奖同学将获得神秘课程纪念品。
下周将发布作业一。与作业零的自由创作不同,作业一会增加一些限制,例如必须实现物理引擎相关功能,但整体上仍鼓励自由发挥。
以下是关于编程环境的一些常见问题与建议:
- 在 Python IDLE 中运行太极可能有问题,建议尝试使用 IPython 或 Jupyter Notebook。请注意,在 Jupyter 中,太极内核的
print输出会显示在启动服务器的终端上。 - 代码格式:建议使用
yapf工具自动格式化 Python 代码,以统一风格,无需纠结空格等细节。命令示例:yapf -i your_file.py。 - 在课程论坛中贴代码时,请使用 Markdown 的代码块语法(三个反引号 ``` 后注明语言,如
python),以便获得语法高亮,提升可读性。 - 编译优化:如果编译速度较慢,可以尝试关闭高级优化选项:
ti.core.set_gdb_advanced_optimization(False)。 - 输出动画:新版本太极提供了将模拟结果导出为 MP4 视频或 GIF 动图的功能,方便在论坛上分享炫酷的效果。

物理模拟的两种视角:拉格朗日 vs. 欧拉 🔄

在基于物理的动画中,模拟各种介质(如固体、流体)时,存在两种基本视角:拉格朗日视角和欧拉视角。


拉格朗日视角 如同在介质中放置许多随波逐流的小纸船(传感器),这些节点会随着材料一起运动。每个节点携带并追踪自身的物理量(如位置、速度)。常见的拉格朗日表示方法包括粒子、三角形网格等。

欧拉视角 则如同在空间中固定一些木桩(传感器)。这些节点本身不移动,而是测量“流过”该固定点的介质的物理量(如速度)。欧拉视角通常使用背景网格来表示场。

一个简单的记忆口诀是:拉格朗日方法,质点随波逐流。最常见的组合是:拉格朗日视角使用粒子,欧拉视角使用网格,但这并非绝对。
上一节我们介绍了两种基本的模拟视角,本节中我们来看看拉格朗日视角下两个最经典和实用的模型。
弹簧质点系统 🧵
弹簧质点系统模型虽然简单,但极其有用,可用于模拟布料、头发、弹性体等多种材料。其核心思想是用质点和连接它们的弹簧来离散化表示一个物体。
数学原理与公式
该系统基于两个基本物理定律:胡克定律和牛顿第二定律。
-
胡克定律 (弹簧力):连接质点
i和j的弹簧所产生的力为:
F_{i->j} = -k * (|x_i - x_j| - r) * ((x_i - x_j) / |x_i - x_j|)
其中:k是弹簧的刚度。x_i,x_j是质点的位置。r是弹簧的静止长度。((x_i - x_j) / |x_i - x_j|)是从j指向i的单位方向向量。
-
牛顿第二定律 (运动方程):对于质点
i,其受到的总力是所有相连弹簧的力与外力(如重力)的矢量和。然后:
a_i = F_{total, i} / m_i
v_i = dx_i / dt
其中m_i是质点的质量。在模拟中,通常预计算质量的倒数1/m_i,用乘法代替除法以提高效率。

时间积分方法

计算机模拟需要在离散的时间步 Δt 上推进系统状态。我们介绍了两种显式积分器:
-
前向欧拉法:
v_{t+1} = v_t + Δt * a_t
x_{t+1} = x_t + Δt * v_t -
半隐式欧拉法 (Symplectic Euler):
v_{t+1} = v_t + Δt * a_t
x_{t+1} = x_t + Δt * v_{t+1}
半隐式欧拉法更常用,因为它具有更好的能量守恒性质。一个模拟步(Substep)的核心代码通常遵循以下三步:
- 计算所有质点受到的合外力,并更新速度 (
v_{t+1} = v_t + Δt * F/m)。 - 处理碰撞(例如,与地面碰撞,将穿入地下的质点的垂直速度分量设为零)。
- 用新速度更新位置 (
x_{t+1} = x_t + Δt * v_{t+1})。
显式积分的稳定性问题
显式积分器有一个主要缺点:容易数值爆炸。对于弹簧系统,存在一个稳定性条件:
Δt <= sqrt(m / k)
这意味着当弹簧刚度 k 很大或质点质量 m 很小时,允许的最大时间步长 Δt 会非常小,否则模拟会失稳。这限制了显式方法对“硬”材料的模拟效率。


隐式积分简介
为了允许更大的时间步长,可以使用隐式积分器,如后向欧拉法。其公式为:
v_{t+1} = v_t + Δt * a_{t+1}
x_{t+1} = x_t + Δt * v_{t+1}
注意,加速度 a_{t+1} 依赖于未来时刻 t+1 的状态,这就形成了一个需要联立求解的方程系统。
通过将力函数 F(x_{t+1}) 在 x_t 处进行一阶泰勒展开(线性化),可以将问题转化为求解一个形如 A * v_{t+1} = b 的线性系统。其中矩阵 A 通常是大规模稀疏矩阵。
求解此类线性系统是物理模拟的核心问题之一。最简单的方法是雅可比迭代法,其核心思想是逐行更新解向量,使其满足当前行的方程。以下是雅可比迭代的简化示意代码:
# 假设 A, b 已知,x 初始为0或猜测值
for iter in range(num_iterations):
x_new = ti.Vector([0.0]) # 初始化新解
for i in range(n):
sigma = 0.0
for j in range(n):
if j != i:
sigma += A[i, j] * x[j]
x_new[i] = (b[i] - sigma) / A[i, i] # 更新第 i 个分量
x = x_new # 更新解
在实际应用中,对于大规模系统,会使用更高效的方法如共轭梯度法及其变种。
隐式积分虽然稳定,允许大步长,但每个时间步的计算成本更高,实现更复杂,且可能引入过度的数值阻尼,使物体看起来“僵硬”。
光滑粒子流体动力学 (SPH) 💧
SPH 是一种经典的拉格朗日流体模拟方法,它用携带物理量的粒子来离散流体,并通过核函数对周围粒子进行加权*均来估算连续场。
核心思想与公式
SPH 的核心是场*似公式。在位置 x 处的某个物理量 A(如密度、压强)的*似值为:
A(x) ≈ Σ_j m_j * (A_j / ρ_j) * W(|x - x_j|, h)
其中:
- 求和遍历所有粒子
j。 m_j,ρ_j,A_j是粒子j的质量、密度和携带的物理量。W是核函数,具有紧支性(在距离h外为零),起到*滑和加权的作用。h是核函数的支持半径。
SPH 非常适合模拟具有自由表面(如水流)的流体,因为它天然地描述了介质分布,无需背景网格。
WCSPH:弱可压缩SPH




最简单的 SPH 流体模型之一是弱可压缩 SPH。它求解以下运动方程(忽略粘性):
Dv / Dt = - (∇p / ρ) + g
其中:
Dv / Dt是物质导数(跟随粒子运动的导数)。p是压强。g是外力(如重力)。
压强通过状态方程与密度关联:
p = B * ((ρ / ρ_0)^γ - 1)
其中 B 是体积模量,ρ_0 是静止密度,γ 是常数(通常取7)。当局部密度 ρ 高于静止密度时,压强增大,产生排斥力以维持体积。
密度 ρ_i 本身也通过 SPH 公式从周围粒子的质量估算得到。
梯度的计算(如 ∇p)在 SPH 中有专门的对称化公式以保证动量守恒。拉普拉斯算子(用于粘性项)也有对应的离散形式。
一个基础的 WCSPH 模拟循环如下:
- 对于每个粒子
i,根据周围粒子估算其密度ρ_i。 - 根据状态方程由密度计算压强
p_i。 - 使用 SPH 梯度公式计算压强梯度
∇p_i。 - 计算加速度
a_i = -∇p_i / ρ_i + g。 - 使用显式时间积分器(如半隐式欧拉)更新粒子的速度和位置。
变种与加速
- PCISPH:预测-校正格式,能更好地保持不可压缩性。
- PBF:基于位置的流体,将位置约束与 SPH 结合,在实时应用中很流行。
- DFSPH:散度自由的 SPH,直接强制速度场无散。


CFL条件:在显式积分中,除了材料刚度限制,还需考虑粒子运动引起的稳定性限制。柯朗数定义为:
C = u_max * Δt / Δx
其中 u_max 是粒子最大速度,Δx 约等于粒子间距(或 h)。模拟中需保持 C 小于一个阈值(如0.1~1),否则可能失稳。这意味着如果粒子运动过快,必须减小时间步长 Δt。






邻域搜索加速:朴素 SPH 的时间复杂度是 O(N^2)。实际中通过空间数据结构(如均匀网格哈希)加速邻域搜索,将复杂度降至*似 O(N)。基本思路是将空间划分为体素,每个粒子根据位置存入对应体素。搜索某个粒子的邻居时,只需查找其所在体素及相邻体素内的粒子即可。




表面重建与渲染




从 SPH 粒子生成用于渲染的流体表面网格是一个独立课题。常用方法是:
- 将粒子光栅化到一个背景网格上,生成一个密度场或符号距离场。
- 使用像
OpenVDB这样的库对场进行*滑等操作。 - 使用移动立方体等值面提取算法从处理后的场中生成三角形网格。
在实时应用中,也常采用屏幕空间渲染技术,直接将粒子渲染到帧缓冲区,并模拟流体的折射、反射等光学效果。


总结与展望 🎯
本节课我们一起学*了拉格朗日视角下的两个基础物理模拟模型:
- 弹簧质点系统:理解了其基于胡克定律和牛顿定律的数学模型,掌握了显式(前向/半隐式欧拉)和隐式(后向欧拉)时间积分方法,并认识了显式方法的稳定性限制。
- 光滑粒子流体动力学:了解了 SPH 用粒子离散流体、通过核函数*似场量的核心思想,学*了 WCSPH 的基本流程和关键公式,并接触了其变种与加速方法。

我们还穿插介绍了实用的编程技巧,如代码格式化、论坛贴代码、动画输出等。这些模型是构建更复杂物理引擎的基石。下节课我们将深入欧拉视角,探索基于网格的流体模拟方法。


演示中弹簧质点系统和雅可比迭代求解器的代码已发布在课程论坛,欢迎大家尝试、修改和整合。期待大家在作业一中创造出更精彩的作品!


GAMES201:高级物理引擎实战指南2020 - P3:Lecture 3 拉格朗日视角(2) 🧮


在本节课中,我们将学*弹性体的模拟基础,包括形变、应力应变、超弹性材料模型以及线性有限元方法。同时,我们也将探索太极编程语言的一些高级特性,以帮助我们更高效地编写物理模拟程序。

课程安排与作业 📋

上一讲我们介绍了拉格朗日视角下的物理模拟基础。本节中,我们来看看本课程的作业安排和一些后续工作。
作业零已经告一段落。今天我们会继续评出几个优秀作品。



作业一今天发布,时间从6月15日到7月11日。这是一个跨度较长的区间,中间预计会空出一周,以便大家实现作业并消化课程内容。



以下是作业一的具体要求:
- 题目:显式时间积分器与隐式时间积分器的对比。
- 内容:选择喜欢的模拟器,同时实现显式和隐式时间积分,并从性能、准确度、数值稳定性等角度进行对比。
- 可选模拟器:隐式弹簧质点系统、隐式FEM、PCI-SPH、完全隐式的DFSPH或MPS。
- 建议:组队分工合作,但个人完成也可以。
- 提交格式:建议在Github上托管代码,在论坛发布链接并附上关键图表和分析。


作业二将作为最终作业,目标是实现一个可交互的物理模拟器,可以进行高质量的渲染和可视化。
作业三暂定取消。
弹性模拟基础 🧱
接下来,我们进入本节课的核心内容:弹性体的模拟。弹性模拟能带来不错的视觉效果,也是其他复杂材料(如弹粘性、弹塑性)的基础。
形变与形变梯度
形变描述了一个材料当前形状与其静止状态之间的差异。
形变映射(Deformation Map)是一个函数 φ,它将材料的静止位置 X 映射到形变后的位置 x。
x = φ(X)
形变梯度(Deformation Gradient)F 是形变映射关于静止位置的导数。
F = ∂φ/∂X
形变梯度 F 描述了材料局部的拉伸、压缩和旋转。其行列式 J = det(F) 表示体积变化率(形变后体积/静止体积)。
应力、应变与超弹性材料
弹性是指材料在形变后具有恢复其原始形状的趋势。
超弹性模型(Hyperelasticity)通过一个应变能密度函数 Ψ 来定义材料的应力-应变关系。该函数是形变梯度 F 的函数,用于惩罚形变。
Ψ = Ψ(F)
应力(Stress)是材料内部单位面积上的内力,用于抵抗形变。应变(Strain)是形变程度的度量。在本课程中,我们可以简单地将应变视为形变梯度 F。
以下是几种常用的应力张量:
- 第一皮奥拉-基尔霍夫应力(PK1 Stress)P:定义为应变能密度关于形变梯度的导数,
P = ∂Ψ/∂F。它在静止空间中进行计算。 - 柯西应力(Cauchy Stress)σ:也称为真实应力(True Stress),在形变后的空间中进行计算,并且是对称张量。


这些应力张量之间可以相互转换,例如:
P = J σ F^{-T}
τ = J σ
其中 τ 是基尔霍夫应力(Kirchhoff Stress)。
材料参数

描述材料弹性通常使用以下参数:
- 杨氏模量(Young‘s Modulus)E:表征材料抵抗拉伸或压缩变形的能力,类似于弹簧的劲度系数 k。
- 泊松比(Poisson‘s Ratio)ν:描述材料横向变形与纵向变形之比。对于常见材料,0 < ν < 0.5。ν=0.5 表示材料不可压缩。
- 拉梅参数(Lamé Parameters)λ 和 μ:另一种常用的材料参数组,可以通过 E 和 ν 计算得出。
常见的超弹性模型
在图形学中,常用的超弹性模型有:
- Neo-Hookean:各项同性材料中最常用的模型,适用于大形变。
- (Fixed)Corotated:在线性弹性的基础上进行了修正,解决了材料旋转后体积无惩罚增大的问题,适用于中等形变。
线性弹性模型(Linear Elasticity)计算简单,但仅适用于小形变分析。
线性有限元方法简介 🔺




有限元方法(FEM)是一种将连续偏微分方程离散化求解的通用方法。其核心思想是将求解域离散为许多小单元(如三角形、四面体),并在单元顶点上存储未知量。
本节我们介绍最简单的线性三角形/四面体有限元。它假设在每个单元内,形变映射 φ 是一个仿射变换,即形变梯度 F 在单元内为常数。
x = F X + b
对于三角形单元,我们可以利用其三个顶点的静止位置和形变后位置,直接计算出该单元的形变梯度 F。
单元的弹性势能 Uₑ 可以通过对应变能密度在单元体积上积分得到。由于 F 为常数,积分简化为:
Uₑ = Vₑ * Ψ(F)
其中 Vₑ 是单元在静止状态下的体积(或面积)。
整个物体的总弹性势能是所有单元势能之和。顶点上的受力 f 是总势能关于顶点位置 x 的负梯度:
f = -∂U/∂x
在显式时间积分中(如半隐式欧拉法),我们可以利用上述公式计算受力,进而更新顶点的速度和位置。利用太极的自动微分功能,我们可以避免手动推导复杂的受力公式,极大地简化了实现。
对于隐式时间积分,则需要求解线性系统,涉及计算力的微分(即刚度矩阵),这需要求应变能密度函数 Ψ 关于 F 的二阶导数,实现更为复杂。
太极编程语言高级特性 🐉
在实现了基础的物理模拟后,让我们看看如何用太极的高级特性来优化和扩展我们的代码。
面向对象编程
太极支持面向数据编程(DOP)与面向对象编程(OOP)的混合范式(ODOP)。通过装饰器,可以创建可复用的太极类。
以下是三个关键装饰器:
@ti.data_oriented:修饰一个太极类。@ti.kernel:修饰类中的核函数。@ti.func:修饰类中的设备函数。



使用类可以将相关的张量和函数封装在一起,提高代码的模块化和可复用性。





元编程





元编程允许将各种参数(如张量、类、函数、数值)传递给太极核函数,实现编译期计算和维度无关编程。




- 模板核函数:使用
ti.template()类型提示来定义可以接受多种类型参数的核函数。 - 维度无关编程:使用
ti.grouped()循环,可以编写适用于任意维度张量的通用核函数。 - 编译期分支与循环展开:使用
ti.static()进行编译期条件判断和循环展开,可以消除运行时开销,并处理太极中要求矩阵/向量下标必须是编译期常数的限制。

可微编程


太极支持反向模式自动微分(Reverse-Mode AutoDiff),可以自动计算标量函数关于其输入的梯度。



主要应用有两种方式:
- 简化受力计算:将弹性势能定义为标量函数,利用自动微分直接求出势能关于顶点位置的负梯度(即受力),无需手动推导。
- 可微模拟器:对整个物理模拟过程进行微分,可用于优化初始状态、控制器参数或进行对抗样本生成等任务。需要注意的是,这需要存储整个时间步的历史状态,内存消耗较大。
使用 ti.ad.Tape() 可以自动记录前向计算并执行反向传播。





三维可视化

太极内置了 ti.GUI 用于二维可视化。对于三维结果,助教团队开发了 ti.export_poly() 功能,可以将粒子或网格数据导出为 .poly 格式文件。该文件可以被 Houdini 等专业三维软件导入和渲染,方便大家查看三维模拟结果。
总结 🎯




本节课我们一起学*了弹性体模拟的核心概念。我们从形变梯度和应力应变出发,介绍了超弹性材料模型。然后,我们探讨了线性有限元方法的基本原理,以及如何利用它和显式积分器实现一个简单的弹性模拟器。最后,我们了解了太极编程语言在面向对象、元编程、可微编程和三维可视化方面的高级特性,这些工具将帮助我们更高效、更灵活地构建复杂的物理模拟程序。


希望大家通过动手实践作业,巩固对这些知识的理解。我们下节课将进入流体模拟的主题。



GAMES201:高级物理引擎实战指南2020 - P4:Lecture 4 欧拉视角 🔄
在本节课中,我们将学*欧拉视角下的流体模拟。我们将从拉格朗日视角的回顾开始,逐步引入欧拉视角的核心概念,并详细讲解不可压缩纳维-斯托克斯方程的求解过程,包括*流、外力和投影三个关键步骤。课程将涵盖数据结构、数值格式以及求解大型线性系统的迭代方法,旨在为初学者提供一个清晰、实用的入门指南。





课程概述与回顾
上一讲我们主要介绍了拉格朗日视角。在拉格朗日视角中,粒子随材料在空间中移动,并记录自身的位置、速度等属性。其关键在于传感器或流体粒子会随着流体或固体一起运动。
本节中我们来看看欧拉视角。欧拉视角的特点是采样点在空间中固定不动。每个采样点记录的是材料以何种速度穿过该点。可以将其理解为在水中插入许多固定的桩,每个桩都知道周围水流的流速。
核心概念:材料导数
为了联系欧拉视角和拉格朗日视角,我们引入一个核心概念:材料导数。其符号通常用大写的 D 表示。
材料导数描述了一个物理量随材料一起移动时的变化率。它由两部分组成:
- 物理量关于时间的偏导数 ∂/∂t。
- 材料速度 u 点乘该物理量的空间梯度 ∇。
公式:
Dφ/Dt = ∂φ/∂t + u · ∇φ
直观理解是,一个随材料移动的采样点,其物理量的变化一方面来自该点本身随时间的变化,另一方面来自材料移动到不同位置所导致的变化。
常见的例子是温度变化。如果把温度计绑在粒子上,温度变化既包含该点温度自身随时间的变化,也包含粒子移动到不同温度区域所带来的变化。
不可压缩纳维-斯托克斯方程
欧拉法流体模拟的核心是求解不可压缩纳维-斯托克斯方程。我们介绍一个简化版本。
公式:
Du/Dt = - (1/ρ) ∇p + ν ∇²u + g
这个方程描述了流体微元速度随时间的变化。其中:
- (1/ρ) ∇p是压强梯度导致的力。ν ∇²u是粘性项,在图形学中常被忽略以突出涡流等视觉效果。g是重力加速度。
此外,对于不可压缩流体,速度场的散度必须为零:
公式:
∇ · u = 0
这意味着流体局部不会被压缩或拉伸,密度保持不变。



算子分裂法


直接求解完整的NS方程很复杂。我们采用算子分裂法将其分解为几个更简单的步骤。直观上,算子分裂法就是将复杂的时间导数拆分成若干个易于处理的部分。
以下是分裂后的三个主要步骤:
- *流:只考虑速度场自身的输运。
∂u/∂t = - (u · ∇) u - 外力:添加重力等外力。
∂u/∂t = g - 投影:施加压强梯度力,并使速度场满足无散条件。
∂u/∂t = - (1/ρ) ∇p,且满足∇ · u = 0



对时间进行离散化后,每个时间步就对应三步操作:
- 通过当前速度场,计算出下一时间步速度场的初始估计
u*(仅考虑*流)。 - 在
u*上加上外力(如重力),得到u**。 - 对
u**进行投影,施加一个合适的压强场p,使得最终速度场u^{n+1}的散度为零。




数据结构:网格
在欧拉法中,我们需要一个数据结构来表示空间中的速度场和压强场。最常用的是均匀网格。

以下是两种常见的网格存储方式:





- 中心网格:将所有物理量(速度u、v分量,压强p)存储在网格单元的中心。实现简单,但可能导致数值误差。
- 交错网格:将速度的不同分量和压强存储在网格的不同位置。例如,水*速度
u存储在单元垂直边的中心,垂直速度v存储在单元水*边的中心,压强p存储在单元中心。这种方法能有效避免“棋盘格”伪影,是更常用的选择。


由于我们只在离散的网格点上有数值,为了得到空间中任意点的值,需要使用插值技术。最常用的是双线性插值。它利用一个网格单元四个角点的值,通过面积加权*均的方式,计算出单元内任意点的*似值。


*流
*流步骤负责移动流体的物理量(如速度、温度)。其目标是计算下一个时间步网格点上的物理量值。
有多种*流格式,它们在数值粘性、稳定性和性能之间进行权衡。我们介绍两种:
1. 半拉格朗日法
这是最简单、最稳定的方法,但数值粘性较高。
思路:要计算下一时间步 x 点的值,就沿着当前速度场反向追踪 Δt 时间,找到 x 点在上一个时间步的位置 x_prev。然后,在上一个时间步的速度场中,对 x_prev 进行双线性插值,得到的结果就作为 x 点在新时间步的值。
代码逻辑:
for 每个网格点 x:
x_prev = backtrace(x, u, -dt) # 反向追踪
u_new[x] = bilinear_interpolate(u_old, x_prev) # 插值得到新值
反向追踪的精度会影响结果。简单的欧拉法(x_prev = x - u(x)*dt)误差较大。使用更高阶的龙格-库塔法(如RK2中点法)进行反向追踪,可以显著提高精度,减少“越转越小”的误差。
2. MacCormack / BFECC 法
这是一种更精确的格式,能更好地保持物理量的锐利度,减少数值耗散。
思路:
- 先用半拉格朗日法*流一步,得到中间结果
u*。 - 再将
u*反向*流一步(即用-dt做*流),得到u**。 - 理论上,如果*流完美,
u**应等于原始的u。两者的差异error = u - u**就*似为*流误差。 - 最终结果修正为:
u_final = u* + 0.5 * error。
这种方法能有效减少物理量(如烟雾边界)在*流中变得模糊的问题。但需要注意,修正步骤可能产生过冲,通常需要配合值域限制来避免伪影。
投影与泊松方程
投影步骤的目标是求解一个压强场 p,使得施加其梯度力后,速度场变得无散。


推导后,我们得到一个关于压强 p 的泊松方程:
公式:
∇²p = (ρ/Δt) ∇ · u
我们需要在离散网格上求解这个方程。对于网格点 (i, j),其拉普拉斯算子可以用五点差分格式*似:
(∇²p)_{i,j} ≈ (p_{i+1,j} + p_{i-1,j} + p_{i,j+1} + p_{i,j-1} - 4p_{i,j}) / Δx²
速度的散度 ∇ · u 也可以利用交错网格上的速度值进行离散*似。
这样,我们就为每个网格点建立了一个线性方程。将所有方程组合起来,便得到一个大型线性方程组:
公式:
A p = b
其中矩阵 A 非常大(N*M × N*M),但非常稀疏(每行只有少数几个非零元素)。
求解线性系统
求解 A p = b 是投影步骤的核心。对于大规模稀疏系统,直接解法成本过高,通常采用迭代法。
共轭梯度法
共轭梯度法 是求解对称正定线性系统最有效的迭代法之一。其实现相对简洁,通过迭代在Krylov子空间中寻找最优解。
算法框架如下:
给定初始解 p, 计算残差 r = b - A p, 令搜索方向 s = r
while 残差 r 的范数未收敛:
α = (r·r) / (s·A s)
p = p + α * s
r_new = r - α * A s
β = (r_new·r_new) / (r·r)
s = r_new + β * s
r = r_new
迭代法的收敛速度与矩阵 A 的条件数密切相关。条件数越大,收敛越慢。
预条件子

为了加速收敛,我们使用预条件子。其思想是求解一个等价的系统 M^{-1} A p = M^{-1} b,其中矩阵 M^{-1}A 的条件数比原矩阵 A 更好,且 M 的逆很容易计算。
- 雅可比预条件子:
M取A的对角线部分,求逆非常简单。 - 多重网格预条件子:这是图形学中求解泊松方程的标准高效方法。


多重网格法
多重网格法之所以高效,是因为它能同时在多个尺度上消除误差。细网格上的低频误差,在粗网格上会表现为高频误差,从而能被快速*滑掉。



基本流程(V循环):
- 预*滑:在细网格上用几次简单的迭代(如雅可比迭代)*滑误差。
- 限制:将细网格上的残差转移到更粗的网格上。
- 递归求解:在粗网格上求解残差方程(递归进行,直到网格足够粗)。
- 延拓:将粗网格上的解修正值插值回细网格。
- 后*滑:在细网格上再进行几次*滑迭代。

在图形学中,通常将多重网格作为预条件子与共轭梯度法结合使用,称为 MG-PCG。这已成为求解不可压缩流体压力泊松方程的标准配置,能保证迭代次数与网格大小基本无关。
进阶话题与总结
本节课我们一起学*了欧拉法流体模拟的核心流程:*流和投影。
- *流:关键在于选择数值耗散小的格式,如 MacCormack/BFECC,以保持流体的细节和能量。
- 投影:关键在于高效、准确地求解泊松方程,通常使用 MG-PCG 方法,以保证速度场无散,从而保持流体体积。
在实际组合这些步骤时,顺序和方式也会影响效果。例如,标准的“*流-投影”步骤会损失部分涡量能量。高级方法如 涡量约束 或 投影分裂 可以在一定程度上恢复这些能量,产生更逼真、更少数值粘性的流动效果。
对于希望深入学*的同学,可以探索以下方向:
- 实现三维模拟。
- 处理更复杂的边界条件(如嵌入式边界)。
- 流固耦合。
- 两相流模拟(同时模拟水与空气)。
- 水*集方法捕捉动态流体界面。
- 涡量法(基于涡量的公式,能更好地守恒涡量)。


欧拉法流体模拟是生成烟雾、火焰、水流等视觉特效的强大工具。虽然本课内容密集,但通过动手实践、阅读推荐资料和参考示例代码,你将能够逐步掌握这项技术,并创造出令人惊艳的效果。
GAMES201:高级物理引擎实战指南2020 - P5:Lecture 5 多体问题与涡方法 🌀
在本节课中,我们将要学*如何高效求解多体问题,特别是泊松方程及其快速解法。我们将从基础的直接求和问题出发,逐步深入到被誉为20世纪十大重要算法之一的快速多极展开方法,并探讨其在图形学、天体物理、分子动力学等领域的广泛应用。







课程介绍与背景



欢迎各位同学。今天我们请来了重量级嘉宾张欣欣师兄。张毅师兄是UBC的计算机图形学博士,他热爱物理、计算与数学,致力于解决其中有挑战性的问题,目标是在计算机上再现大自然的美。这也是我们开设这门课程的初衷。

关于课程难度,曾有疑问是否会让同学觉得拔苗助长。但兴趣是最好的老师,在兴趣驱使下,挑战并非问题。我们会尽力以通俗易懂的方式讲解。同时,希望大家能多参与直播,以便及时互动和反馈。
接下来,我们把时间交给星星师兄,他将为我们讲解泊松方程与其快速解法。
好的,谢谢胡世纪的介绍。我希望今天的课尽量做到通俗易懂。能够留存到今天的同学应该都很不容易。我们现在就开始吧。今天我会讲有关泊松方程和快速解法的一些内容。
从多体问题到泊松方程
我们从快速方法的角度来看泊松方程。之前我们讨论的解法是基于偏微分方程的角度。今天,我们考虑一个开空间的泊松问题,并通过积分形式的基础解来看待它。
许多同学在高中时就接触过这类问题,例如万有引力。万有引力场满足一个关于密度的泊松方程。
公式: ∇²φ = ρ

任意两点之间的受力就是势场的梯度。如果我们把这个梯度运算代入到基础解的求和系统中,就会得到熟悉的万有引力公式:力与距离的*方成反比。
公式: F = G * (m1 * m2) / r²
当我们看到这个求和表达式时,会注意到:如果一个万有引力系统中有 n 个粒子,需要对 m 个点求力,那么直接求和的方法需要 O(n*m) 的时间复杂度,其计算量很大。因此,我们需要快速方法来降低其计算复杂度。
快速多极展开的核心思想
这类方法被称为快速多极展开,它被誉为20世纪十大重要算法之一。快速多极展开究竟利用了怎样的思想,使得人们能够在线性时间内计算这个 O(n²) 的求和问题?我们现在就来一步步分解。
首先,我们考虑一个二维泊松方程及其基础解。为了方便,我们采用复数来表达坐标。我们知道,求和表达式的实部就是我们要求的泊松方程的解。log(距离) 就是二维情况下的格林函数。
此时,我们考虑一个圆以及它在远处 z 点引发的势能。如果我们在 qi 点处做一个泰勒展开,就会发现 z 点的势能可以看作是在零点处所有电荷的势能之和加上高阶项。
核心思想: 如果我们在靠*零点的地方有很多电荷,它们对远处 z 点的作用可以通过在零点做泰勒展开来*似。这可以拆分为两部分:
- 一个位于零点的、带有总电荷的“大电荷”对
z点的作用。 - 一个高阶项,代表了电荷分布形状的影响。

我们将公式抽象后,会得到一个总电荷 Q(零点附*所有电荷的总和)和一系列高阶项 q_k。远处 z 点的势能可以写成 Q 乘以格林函数,再加上这些高阶项的累加。后面的高阶项是几何收敛的。

基于这个思想,我们已经可以得到一个 O(n log n) 的算法,这被称为 树码算法。其实现方式是:将空间中的粒子进行网格划分,在每个格子的中心点计算*似的 Q 和 q_k,使得远处格子的势场可以被这个中心点的展开式*似。然后,我们不断向上层网格聚合这些信息,最终得到一个粗糙的网格,其中存储的 Q 和 q_k 代表了其下所有粒子的信息。



当我们需要计算空间中任意一点的势场时:
- 对于*处的粒子,使用直接求和公式。
- 对于远处的粒子,使用存储在对应粗网格中心点的泰勒展开式来*似计算。






这个过程需要对每个格子进行计算,因此总计算量是 O(n log n)。










从 O(n log n) 到 O(n):M2M 变换

在了解了 O(n log n) 的树码算法后,我们如何得到线性时间 O(n) 的算法呢?关键在于 M2M(多极子到多极子)变换。

假设我们已经在一个中心 z1 处统计出了电荷云的 Q 和 q_k,它可以精确估计远处 z 点的势能。现在的问题是:如何通过 z1 处的 Q 和 q_k,直接估计出另一个中心 z2 处的 Q' 和 q'_k,使得在同一个远处 z 点的势能可以被 z2 处的展开式精确表达?
方法很简单:既然我们有了以 z1 为中心的势能计算公式,我们只需对它重新在 z2 处进行泰勒展开并重写公式,就能得到新的展开形式。新的系数 b_k 是原来系数 q_k 的泛化。

形象理解: 我们可以把每个小的电荷云看作一个特殊的“多极子”。M2M变换相当于把多个小多极子的信息,合并、*移到其上一层的一个大多极子身上。




我们可以用代码来定义这个过程:
class Multipole:
def __init__(self, center, q_list): # q_list 是各阶系数
self.center = center
self.coeffs = q_list # 例如 [Q, q1, q2, ...]


def M2M_transform(source_multipoles):
""" 将多个源多极子合并为一个新的多极子 """
new_center = compute_weighted_center(source_multipoles)
new_Q = sum(mp.coeffs[0] for mp in source_multipoles) # 合并总电荷
new_higher_terms = compute_bk(source_multipoles, new_center) # 计算高阶项
return Multipole(new_center, [new_Q] + new_higher_terms)


L2L 变换与完整的 FMM 流程






当我们知道了如何将多极子变换到另一个多极子(M2M)后,另一个问题变得有趣:如果我们在一个中心 c 处知道了它的多极子展开(可以告诉我们远处任意点 z 的势能),如何将其转换成一个离 z 点很*的另一个点 z1 处的 局部展开?






这不是 M2M 变换,而是 M2L(多极子到局部展开)变换。局部展开就像一个插值函数,它使得在 z1 点附*区域内的势场函数,能够被一个以 z1 为中心的多项式准确地表达出来。


完成数学推导后,我们得到了相应的公式。z1 处的势能值是其局部展开的常数项,而 z 点的势能则是 z1 点的势能加上一个由高阶项描述的小扰动。由于 z 离 z1 很*,这个级数也是几何收敛的。



进一步,如果我们已经有了一个以 c1 为中心的局部展开,想将其转换到另一个以 c2 为中心的局部展开,这涉及的就是 L2L(局部展开到局部展开)变换。这本质上是一个坐标变换,可以通过类似霍纳法则的方法高效计算。

完整的快速多极展开流程:
- P2M(粒子到多极子):将源粒子的信息转换为最底层网格的多极子。
- M2M(多极子到多极子):自底向上,将小网格的多极子合并为大网格的多极子。
- M2L(多极子到局部展开):对于每个目标网格,将足够远的源网格的多极子贡献,转换为该目标网格中心的局部展开。
- L2L(局部展开到局部展开):自顶向下,将父网格的局部展开贡献传递并细化到子网格。
- L2P(局部展开到粒子):从目标粒子所在网格的局部展开中,计算出该粒子所受的势场或力。
- *场直接求和:对于距离很*的粒子对,仍然使用直接求和公式计算。




这个过程先自底向上聚合(M2M),再自顶向下分发(L2L),最终使得每个粒子在计算总受力时,只需要处理常数数量的*场直接求和和几次局部展开求值,从而实现了 O(n) 的线性时间复杂度。





其他快速求和方法:P³M 与 Kernel Independent FMM


除了 FMM,还有其他快速求和方法。


P³M(粒子-粒子/粒子-网格)方法 混合了偏微分方程视角和求和视角。其核心思想是:
- 长程力:通过将粒子密度分配到网格上,在网格上求解泊松方程得到一个*滑的势场,再通过插值得到粒子所受的长程力。这部分计算是线性的(如使用多重网格法求解)。
- 短程力:从粒子受到的总力中,减去由网格插值得到的长程力部分,剩下的就是短程力。短程力只存在于邻*粒子之间,可以通过直接求和计算,由于邻居数量有限,计算量也是线性的。
P³M 方法实现起来比 FMM 相对简单,且也能达到线性时间复杂度。



核无关的快速多极方法 则采用另一种视角:对于一个源电荷,构造两个包围它的球面(内圈和外圈)。通过求解一个线性系统,确定在内圈上放置一组“等效电荷”,使得这组等效电荷在外圈上产生的势场与原电荷产生的势场完全一致。这样,远处的计算就可以用这组数量少得多的等效电荷来代替原来的电荷云。只要内圈和外圈的形状固定,中间的转换矩阵就可以预先计算并求逆。
应用领域
快速求和方法的应用极其广泛:
- 天体物理:计算万有引力,用于模拟星体运动、搜寻暗物质。
- 分子动力学:计算静电场力,用于研究蛋白质折叠、药物设计(如新冠疫苗的受体结合分析)。
- 电磁学:计算电磁场,用于飞行器隐身外形设计。
- 声学:求解亥姆霍兹方程,用于计算噪声传播、剧院声场设计、城市高架桥隔音方案优化。
- 流体力学:
- 势流理论:用于模拟波浪、飞机机翼设计。
- 涡方法:一种基于拉格朗日视角的流体模拟方法,通过求解矢量泊松方程得到速度场。它特别适合模拟涡旋主导的流动(如涡环交互),能用很少的自由度(涡粒子)和简单的代码捕捉复杂现象。
- 边界元方法:将偏微分方程转化为边界上的积分方程进行求解。其矩阵是稠密的,但条件数通常很好。结合 FMM 加速矩阵向量乘后,在求解某些问题时比传统的域离散方法(如有限元、有限差分)更具优势,尤其是对于无限域或半无限域问题。



涡方法中的边界处理
有同学问涡方法如何处理固体边界。这里简要说明:
- 无粘流(势流):主要目标是保证流体不穿透固体边界。这可以通过在固体边界上分布“偶极子”来引入一个势流场,以抵消边界法向的穿透速度。
- 粘性流:除了法向无穿透,还需满足切向无滑移条件。这可以通过在固体边界上分布“涡层”来引入一个速度场,以抵消边界切向的滑移速度。求出的这个涡层强度,再通过某种扩散机制(如随机行走或保持环量守恒的扩散方式)将其“发射”到周围的流体中,成为新的涡粒子。这样就能模拟出如卡门涡街等粘性效应。



总结



本节课中,我们一起学*了:
- 多体问题的挑战:直接求和的
O(n²)复杂度。 - 快速多极展开:通过多极子展开、M2M、M2L、L2L 等一系列变换,将远场作用高效聚合和分发,实现
O(n)线性复杂度的核心算法。 - 其他快速方法:P³M 方法通过长短程力分离实现线性复杂度;核无关 FMM 通过构造等效电荷实现加速。
- 广泛应用:从万有引力、静电学、电磁学到声学、势流、涡方法及边界元法,快速求和方法是连接物理模型与高效计算的桥梁。






快速求和的核心理念是利用场在远处的*滑性,用紧凑的信息*似大量粒子的集体效应,从而避免两两计算。虽然 FMM 的实现细节较为复杂,但其思想深刻而优美,是计算物理和图形学中不可或缺的工具。


GAMES201:高级物理引擎实战指南2020 - P6:Lecture 6 线性弹性有限元与拓扑优化 🧮
在本节课中,我们将学*线性弹性有限元方法的基本原理与实现步骤,并简要了解拓扑优化的概念与应用。有限元方法是求解偏微分方程的有力工具,在物理模拟和工程设计中应用广泛。
课程安排与作业说明 📅
上一节我们介绍了隐式时间积分器,本节中我们来看看有限元方法。课程已进入后半部分,后续将讲解混合欧拉-拉格朗日视角(如FLIP、MPM)和高性能计算等内容。
关于作业,请注意以下事项:
以下是作业提交相关信息:
- 作业一优秀作品已公布,代码可直接获取运行以供参考。
- 作业二为开放式物理模拟器项目,截止日期为北京时间8月15日23:59。
- 作业二可选方向包括:实现可交互的2D物理模拟游戏、优化性能、实现高精度格式、提升艺术可控性或自选研究方向。
- 建议组队完成,评分以新增原创部分为准。
有限元方法概述 🔍
有限元方法是加权残值法的一种,核心思想是将连续的偏微分方程(强形式)转化为离散的线性系统(弱形式)进行求解。
以下是有限元方法求解的标准步骤:
- 强形式转弱形式:用测试函数 ( w ) 乘以方程残差并积分,将每点成立的方程转化为积分形式。
- 分部积分:降低方程中导数的阶数,简化计算。
- 应用散度定理:进一步简化方程,并自然引入诺伊曼(第二类)边界条件。
- 离散化:将连续的场 ( u ) 用一组基函数(形函数)的线性组合来表示,即 ( u = \sum_j u_j \phi_j )。
- 组装与求解:得到离散系统 ( Ku = f ),其中 ( K ) 为刚度矩阵,( f ) 为载荷向量,最后求解该线性系统。
实例一:二维泊松方程 📐
泊松方程 ( \nabla^2 u = 0 ) 是学*有限元离散化的经典范例。我们考虑一个矩形域,其三边为狄利克雷边界(固定 ( u ) 值),一边为诺伊曼边界(固定 ( u ) 的法向导数值)。
从强形式到弱形式
首先,我们将强形式乘以任意测试函数 ( w ),并在域 ( \Omega ) 上积分:
[
\int_{\Omega} w \nabla^2 u , dA = 0
]
利用分部积分公式:
[
\int_{\Omega} \nabla w \cdot \nabla u , dA - \int_{\partial \Omega} w (\nabla u \cdot \mathbf{n}) , ds = 0
]
上式即为泊松方程的弱形式。
离散化与基函数
我们将求解域离散为网格(如四边形网格),场 ( u ) 由定义在节点上的值通过基函数插值得到。对于四边形单元,常用的基函数是双线性形函数。
将 ( u = \sum_j u_j \phi_j ) 和 ( w = \phi_i ) 代入弱形式,可得到线性系统:
[
\sum_j \left( \int_{\Omega} \nabla \phi_i \cdot \nabla \phi_j , dA \right) u_j = \int_{\partial \Omega} \phi_i (\nabla u \cdot \mathbf{n}) , ds
]
即:
[
Ku = f
]
其中,( K_{ij} = \int_{\Omega} \nabla \phi_i \cdot \nabla \phi_j , dA ),( f_i = \int_{\partial \Omega} \phi_i g , ds )(( g ) 为诺伊曼边界条件值)。
边界条件处理
- 狄利克雷边界条件:直接固定对应自由度 ( u_i ) 的值。
- 诺伊曼边界条件:其贡献直接体现在载荷向量 ( f ) 的相应项中。
实例二:线性弹性方程 🏗️
上一节我们以标量泊松方程为例,本节中我们来看看更复杂的向量方程——线性弹性方程。它源于柯西动量方程,在准静态、无体积力、密度恒定的假设下,简化为:
[
\nabla \cdot \sigma = 0
]
其中,( \sigma ) 为柯西应力张量,自由度 ( u ) 是位移场。
应力与应变关系
应力 ( \sigma ) 通过应变 ( \epsilon ) 与位移 ( u ) 相联系。对于线弹性材料,本构关系为:
[
\sigma = \lambda , \text{tr}(\epsilon) I + 2\mu \epsilon
]
其中,( \lambda ) 和 ( \mu ) 是拉梅参数,应变张量 ( \epsilon ) 定义为:
[
\epsilon = \frac{1}{2} (\nabla u + \nabla u^T)
]
可见,应力 ( \sigma ) 是位移 ( u ) 的线性函数。
有限元离散化
推导过程与泊松方程类似,但更为复杂。最终同样得到 ( Ku = f ) 形式的系统。刚度矩阵 ( K ) 的组装需要经过“应变-位移”矩阵和“应力-应变”矩阵的转换。对于二维四边形单元,其单元刚度矩阵是一个8x8的矩阵(每个节点有x、y两个位移自由度)。
拓扑优化简介 🎨
拓扑优化是指在给定设计空间、载荷和约束条件下,寻找材料最优分布以最大化或最小化某种性能(如刚度)的方法。


最小柔度问题


一个经典的拓扑优化问题是最小柔度(Minimum Compliance)设计。目标是最小化结构在载荷下的变形能,同时满足材料用量约束。
[
\begin{aligned}
& \min_{u, \rho} \quad u^T K(\rho) u \
& \text{s.t.} \quad K(\rho) u = f \
& \qquad \frac{1}{|\Omega|} \int_{\Omega} \rho , d\Omega \leq V_0 \
& \qquad 0 < \rho_{\min} \leq \rho \leq 1
\end{aligned}
]
其中,( \rho ) 是每个单元的相对密度(设计变量),( K(\rho) ) 与密度相关的刚度矩阵,( V_0 ) 是允许的材料体积分数。

方法与演示

常用算法包括SIMP(固体各向同性材料惩罚法)和OC(优化准则法)。通过迭代更新设计变量 ( \rho ),最终得到清晰的0-1分布,即材料的有无,形成最优拓扑结构。

总结 📝

本节课我们一起学*了线性弹性有限元方法的核心流程与两个实例。我们从强形式出发,通过引入测试函数、分部积分得到弱形式,再经离散化最终建立离散线性系统 ( Ku = f )。此外,我们还简要了解了拓扑优化的基本概念,它是在有限元分析基础上进行结构优化设计的强大工具。



理解有限元方法为后续学*更复杂的物理模拟技术(如物质点法)奠定了重要基础。


GAMES201:高级物理引擎实战指南2020 - P7:Lecture 7 混合欧拉-拉格朗日视角(1)🧪
在本节课中,我们将要学*混合欧拉-拉格朗日视角的基本概念。这是一种结合了两种视角优势的模拟方法,广泛应用于流体和固体模拟中。我们将从回顾两种基本视角开始,逐步介绍几种核心的混合方法,并最终了解现代物理引擎中常用的物质点法。
视角回顾:欧拉与拉格朗日 🔍
上一节我们介绍了连续介质模拟的两种基本视角。本节中,我们来看看它们的具体定义和特点。
在拉格朗日视角中,观察者随着模拟的材料一起移动。每时每刻,观察者关注的是自身的位置和速度。这就像随波逐流的小船。在计算机模拟中,拉格朗日视角通常用粒子来表示,但也可以使用网格。
在欧拉视角中,观察者的位置是固定不动的。每时每刻,观察者关注的是经过自身位置的材料的速度。这就像插在水中的桩子。欧拉视角通常使用背景网格来表示各种物理场。
以下是两种视角的典型应用方法:
- 纯拉格朗日方法:例如基于粒子的方法(SPH)、位置动力学(PBD)。
- 纯欧拉方法:例如基于网格的烟雾、流体模拟。
为何需要混合视角?🤔
上一节我们介绍了两种视角的差异,本节中我们来看看为何要将它们结合。
要判断一个算法是否“更好”,我们需要从多个层面进行考量。没有任何一个算法在所有层面都完美,因此需要根据具体需求进行选择。

以下是评估模拟算法时需要考虑的关键因素:
- 物理量的守恒性:包括动量守恒、角动量守恒、体积守恒(对于不可压缩流体)和能量守恒。
- 性能:包括并行化难易度,以及内存访问的时空局部性。
- 实现复杂性:过于复杂的算法可能难以正确实现和优化。
欧拉网格和拉格朗日粒子各有擅长和不擅长的任务。我们的目标是将两者智能地结合起来,取长补短。
欧拉网格特别擅长投影步骤,例如求解泊松方程来实施不可压缩性。这是因为在均匀网格上离散拉普拉斯算子、查找邻居和实现预条件子都非常容易。

然而,欧拉网格在*流步骤中容易出现问题,例如数值耗散会导致细节模糊、能量丢失,使得模拟看起来过于粘稠。
拉格朗日粒子则非常擅长*流。移动粒子来传输物理量简单且自然,也容易实现质量和动量守恒。
但是,在粒子上进行投影操作非常困难。因为粒子分布不规则,离散化算子、查找邻居都变得复杂且计算量大。
混合方法的基本框架 🏗️
既然明确了各自的优缺点,我们就可以设计混合方法了。在混合欧拉-拉格朗日方法中,拉格朗日粒子通常作为“一等公民”,存储大部分状态信息;欧拉网格则作为“二等公民”,用于计算中间结果。
整个算法的通用套路如下:
- 粒子到网格传输:将粒子上的信息(如速度)传输到其周围的网格节点上。
- 网格操作:在网格上进行关键计算,如压力投影、施加边界条件等。
- 网格到粒子传输:将网格上更新后的信息收集回粒子。
- 粒子更新:更新粒子的位置、速度或其他材料属性。
这个在两种表示之间来回传输信息的过程,是混合方法的核心,但也会引入信息损失和计算开销等问题。
基础混合方法:Particle-in-Cell (PIC) 📦
上一节我们介绍了混合方法的通用框架,本节中我们来看看一个最基础的实现:Particle-in-Cell。
PIC是一个非常古老的混合方法。它的传输过程非常直观:每个粒子将其物理信息(如速度)传输到周围多个网格节点;反过来,每个粒子从周围网格节点收集信息。
在传输时,距离粒子更*的网格节点会被赋予更高的权重。这个权重通过核函数来计算。通常使用B样条核函数,并在x和y方向分别计算后相乘。
以下是三种常见的B样条核函数:
- 线性核:计算简单,但不够*滑,可能导致模拟不稳定。
- 二次核:最常用,在*滑性和计算量之间取得了良好*衡。
- 三次核:更*滑,但计算量更大,使用相对较少。
以下是PIC方法中粒子到网格传输的核心代码逻辑(以二次核、格点位于单元中心的网格为例):
# 假设 particle_pos 是粒子位置,particle_v 是粒子速度
# dx 是网格间距,grid_v 和 grid_m 是网格上的速度和权重累加器
base = (particle_pos / dx - 0.5).cast(int) # 计算左下角节点索引
fx = (particle_pos / dx - 0.5) - base.cast(float) # 计算相对位置
# 计算二次B样条权重
w = [0.5 * (1.5 - fx)**2, 0.75 - (fx - 1.0)**2, 0.5 * (fx - 0.5)**2]
# 遍历3x3的邻居节点
for i in range(3):
for j in range(3):
weight = w[i].x * w[j].y # 组合权重
node_idx = base + vec(i, j)
grid_v[node_idx] += weight * particle_v # 累加加权速度
grid_m[node_idx] += weight # 累加权重

传输完成后,需要对网格上的物理量进行归一化:

for each grid node:
if grid_m[node] > 0:
grid_v[node] /= grid_m[node] # 速度除以权重和,得到加权*均
网格到粒子的传输是类似的反向过程。然而,纯PIC方法能量耗散严重,在模拟旋转、剪切等变形时效果不佳。这是因为在传输过程中,网格上的多个自由度信息被压缩到粒子的少数自由度上,导致信息丢失。
改进的混合方法:APIC 与 PolyPIC 🚀
为了解决PIC的信息丢失问题,主要有两套思路。本节我们介绍第一套:在粒子上存储和传输更多信息。
APIC方法在粒子上不仅存储*移速度,还存储一个局部的仿射速度场。这个仿射速度场可以描述旋转、剪切、拉伸等变形模式。
从高层思想看,APIC的传输过程与PIC相似,但传输的量包含了额外的仿射分量。令人惊讶的是,实现APIC所需的代码改动非常小。
以下是APIC方法中粒子到网格传输的核心增量代码:
# particle_C 存储粒子的仿射速度场矩阵
# dp = node_pos - particle_pos
affine_v = particle_C @ dp # 矩阵乘法,计算仿射部分的速度贡献
grid_v[node_idx] += weight * (particle_v + affine_v) # 累加*移和仿射速度
网格到粒子传输时,也需要重建这个仿射矩阵C:

# 计算粒子新速度的仿射部分
for i in range(3):
for j in range(3):
weight = w[i].x * w[j].y
node_idx = base + vec(i, j)
dp = node_pos - particle_pos
particle_C += weight * (grid_v_new[node_idx] - particle_v_old).outer_product(dp) * (4 / dx**2)

APIC能很好地保持角动量,模拟旋转等运动时能量耗散大大减少。PolyPIC则更进一步,在粒子上使用更高阶的多项式速度场(如双线性、二次),以保留更多模式信息,实现更无损的传输。
另一种思路:FLIP 方法 💧
上一节我们介绍了通过增加信息来改进PIC的方法,本节中我们来看看另一种不同的思路:FLIP。


FLIP方法不直接传输物理量本身,而是传输物理量的增量。例如,在不可压缩流体模拟中,它传输的是压力投影和边界条件所带来的速度变化量。
FLIP的核心思想是:粒子速度 = 旧粒子速度 + 从网格收集到的速度增量。这相当于在信息传输路径中,保留了一条“粒子到粒子”的直连通道,避免了所有信息都经过网格带来的*滑与损耗。

FLIP的实现与PIC只有微小差别:

# PIC 方式:
particle_v_new = gather_weighted(grid_v_new)
# FLIP 方式:
particle_v_new = particle_v_old + gather_weighted(grid_v_new - grid_v_old)

FLIP的优点是能保持更多细节,能量耗散少。但缺点是容易产生数值噪声,模拟结果可能看起来比较“嘈杂”。一个常见的技巧是将FLIP与少量PIC混合,例如使用0.99倍的FLIP增量加上0.01倍的PIC速度,在保持细节的同时抑制噪声。


迈向现代:物质点法 (MPM) 简介 🧱

前面介绍的混合方法主要侧重于*流步骤。本节中我们来看一个完整的、独立的模拟方案:物质点法。
MPM是一种混合欧拉-拉格朗日模拟框架,它使用拉格朗日粒子存储材料状态(如位置、速度、变形梯度、体积),使用欧拉网格来计算和施加内力(如应力)、处理碰撞。
MPM的模拟循环与之前介绍的混合方法框架一致,但粒子存储的信息更多,网格上的操作也包含了基于材料本构模型的内力计算。早期的MPM实现步骤较多,较为复杂。
*年来,出现了更高效的MPM变种,如移动最小二乘MPM。它与APIC高度相似,只是在粒子到网格传输时,额外增加了基于粒子应力的力贡献。这使得实现一个基础的MPM求解器变得异常简洁。
以下是一个简易MPM流体求解器(基于MSMPM)的核心步骤概述:
- 粒子到网格传输:类似APIC,传输粒子的动量(质量*速度)和仿射动量,同时根据体积变化计算并传输应力贡献。
- 网格归一化与边界条件:计算网格节点速度,施加边界条件。
- 网格到粒子传输:类似APIC,更新粒子的速度和仿射矩阵
C,并根据C的迹更新粒子的体积J。
通过这种设计,一个基础的MPM求解器可以用不到100行代码实现,同时能模拟流体、弹性体、雪等多种材料。


总结 📝

本节课中我们一起学*了混合欧拉-拉格朗日视角。
- 我们首先回顾了欧拉视角和拉格朗日视角的各自特点与优劣。
- 我们了解到,混合方法的核心思想是用粒子追踪物质,用网格解析场和力,从而结合两者优势。
- 我们学*了基础的PIC方法,并认识了其能量耗散严重的问题。
- 我们探讨了两种改进思路:一是传输更多信息(如APIC、PolyPIC),二是传输增量信息(如FLIP)。其中APIC因其实现简单、效果良好而被推荐为入门选择。
- 最后,我们介绍了物质点法作为混合方法的一个强大而现代的应用实例,它通过扩展粒子存储的信息,能够统一模拟多种连续介质材料。



下一讲,我们将深入MPM背后的理论,学*本构模型、隐式积分等高级话题。


GAMES201:高级物理引擎实战指南2020 - P8:Lecture 8 混合欧拉-拉格朗日视角(2) 🧪
在本节课中,我们将深入学*混合欧拉-拉格朗日视角的第二部分,重点探讨物质点法的核心理论、实现细节及其支持的各种材料模型。我们将从算法基础开始,逐步讲解变形更新、边界条件处理、本构模型,并介绍太极框架中的重要更新。
概述 📋


上一讲我们介绍了混合欧拉-拉格朗日视角的基本概念。本节中,我们将更深入地探讨物质点法,包括其理论推导、简化高效的实现版本(MLS-MPM),以及如何用它模拟弹性体、流体和弹塑性体等多种材料。

1. 为什么使用混合视角? 🔄
欧拉网格和拉格朗日粒子各有优势。为了结合两者的长处,我们需要在两种表示之间转换数据,这涉及到粒子到网格和网格到粒子的操作。在混合方法中,粒子是信息的主体,存储主要的物理信息;欧拉网格则作为辅助,用于计算临时动量或处理压力投影等操作。
2. MPM 相关理论 🧮
MPM 和 FEM 一样,都属于伽辽金方法。但 MPM 没有“元素”的概念,因此被归类为无网格伽辽金方法。MPM 中的粒子实际上对应着 FEM 中的高斯积分点。
MPM 的方程也是通过弱形式推导而来。与历史更悠久的 FEM 相比,MPM 较为年轻,高阶格式较少。2013年提出的图形学 MPM 算法相对复杂,而2018年提出的移动最小二乘 MPM 则是一种更简单、高效的格式。
2.1 符号定义
为了清晰起见,我们统一符号定义:
- 标量使用非加粗字体,例如
mi表示节点 i 的质量,vp0表示粒子 p 在时间 0 的体积。 - 向量和矩阵使用加粗字体,例如 vp 是向量,Cp 是矩阵。
- 下标
i表示网格节点,下标p表示粒子。 - 上标
n表示时间步,例如 xi^n 表示节点 i 在 n 时刻的位置。
2.2 从 PIC/FLIP 到 MPM
FLIP 是粒子胞元法的一个变种,它额外维护一个矩阵 C,记录了粒子周围速度场的变化。其核心步骤包括:
- 粒子到网格:将粒子的质量和动量散射到网格节点。
- 网格操作:在网格上计算速度(动量/质量)并进行压力投影等操作。
- 网格到粒子:从网格节点收集更新后的速度和 C 矩阵信息,并更新粒子位置。
MPM 在 FLIP 的基础上做了关键改进:
- 变形更新:增加了根据局部速度梯度更新粒子形变梯度 F 的步骤。
- 节点弹力:在粒子到网格的动量转移中,增加了来自材料内部弹力的贡献项。
在 MLS-MPM 中,我们将变形更新步骤移到了网格到粒子操作之后,这样可以节省内存带宽,因为更新 F 只需要用到 C 矩阵,而 C 正是在这一步计算出来的。
3. 关键公式推导 📐
3.1 变形梯度更新
粒子的形变梯度 F 之所以会变化,是因为局部速度场的梯度 ∇v 不为零。其更新公式的离散形式为:
F_p^{n+1} = (I + Δt ∇v) F_p^n
在 MLS-MPM 中,我们用粒子上的 C 矩阵来*似 ∇v,因此更新公式简化为:
F_p^{n+1} = (I + Δt C_p) F_p^n
3.2 节点受力计算
节点 i 上来自粒子 p 的力贡献 f_i,可以通过将系统的总弹性势能对节点位置求导得到。通过一系列推导(涉及链式法则和微小时间扰动),最终得到公式:
f_i = -4 V_p^0 P( F_p ) ( F_p )^T ∇w_{ip} / (Δx)^2
其中,P(F) 是第一皮奥拉-基尔霍夫应力,∇w_{ip} 是插值函数的梯度。这就是代码中 4 / (dx*dx) 系数的来源。
4. 边界条件处理 🧱
作为伽辽金方法,MPM 的边界条件必须在网格节点上施加。主要有三种类型:
- 粘性边界:节点速度直接设为零。
- 滑移边界:消除节点速度在边界法向的分量,保留切向分量。
- 分离边界:仅当节点速度朝向边界时,才消除其法向分量;若速度离开边界,则不做处理。
施加重力等外力应在边界条件处理之前进行。对于移动的边界,需要先计算节点相对于边界的速度,施加条件后再转换回来。
5. 材料本构模型 🧱
在 MPM 中实现一种材料模型,主要需要两部分:
- 如何更新材料的变形(形变梯度 F 或其相关量)。
- 如何计算应力 P。
5.1 弹性体
对于弹性体,如 Neo-Hookean 或 Co-rotated 模型:
- 变形更新:直接使用公式 F_p^{n+1} = (I + Δt C_p) F_p^n。
- 应力计算:根据超弹性模型,应力 P 是应变能密度函数 ψ 对 F 的导数。例如,Neo-Hookean 模型的应力公式为:
P(F) = μ (F - F^{-T}) + λ (J - 1) J F^{-T}
其中 J = det(F),μ 和 λ 是拉梅参数。
5.2 流体
对于弱可压缩流体,常用状态方程关联压强 p 和体积比 J:
p = k (1 - J)
其中 k 是体积模量。应力为 P = -p I。
直接更新 F 并计算其行列式 J 可能存在数值不稳定问题。一个技巧是直接追踪和更新 J 本身,其更新公式为:
J^{n+1} = J^n (1 + Δt tr(C_p))
更简单的方法是使用弹性模型,但将剪切模量 μ 设为零,只保留体积模量 λ,这样能量函数就只惩罚体积变化。
5.3 弹塑性体(如雪)
模拟弹塑性体的关键在于处理塑性变形。我们将形变梯度分解为弹性部分 F^E 和塑性部分 F^P:F = F^E F^P。能量函数只定义在弹性部分上。
一个常用的(特别在图形学中)方法是采用方盒屈服准则:
- 先像弹性体一样更新 F。
- 对更新后的 F 进行奇异值分解:F = U Σ V^T。
- 对奇异值矩阵 Σ 进行夹紧操作,将其对角线元素限制在区间
[1-θ_c, 1+θ_s]内,得到 Σ̂。其中θ_c和θ_s是压缩和拉伸的屈服阈值。 - 用夹紧后的奇异值重构弹性形变梯度:F^E = U Σ̂ V^T。
- 被“夹掉”的变形部分则被归入塑性变形。
6. MPM 中的拉格朗日力 🕸️
拉格朗日力方法使用 MPM 粒子来表示 FEM 网格的顶点。这样,粒子就不再需要存储形变梯度 F,而是使用 FEM 的势能模型来计算力。这种方法结合了 MPM 处理自碰撞的优势和 FEM 避免数值断裂(粒子因距离过远而失去相互作用)的优点。
7. 太极框架重要更新 🚀
太极框架已更新至新版本,一个重要变化是弃用了 tensor 概念,改用 field。
field代表全局变量,可以是标量、向量或矩阵场。- 局部变量则使用
ti.var,ti.Vector,ti.Matrix等定义。 - 这使全局变量和局部变量的区分更加清晰。
例如,定义一个向量场:particle_x = ti.Vector.field(3, ti.f32, shape=n_particles)
定义一个标量场:particle_m = ti.field(ti.f32, shape=n_particles)
总结 🎯


本节课我们一起深入学*了物质点法。我们从其理论基础和简化高效的 MLS-MPM 实现讲起,详细推导了变形更新和节点受力的关键公式。接着,探讨了三种边界条件的处理方法,并介绍了如何使用 MPM 模拟弹性体、流体和弹塑性体等多种材料模型。最后,我们了解了 MPM 中拉格朗日力的应用以及太极框架向 field 概念的重要更新。掌握这些内容,将帮助你更深入地理解并实现现代物理引擎中的高级模拟技术。


GAMES201:高级物理引擎实战指南2020 - P9:Lecture 9 高性能计算与物理引擎 🚀
在本节课中,我们将要学*高性能物理模拟的核心概念与技巧。我们将探讨现代计算机体系结构如何影响程序性能,并学*如何利用太极(Taichi)编程语言的高级特性来优化物理引擎,使其运行速度提升数倍甚至数十倍。
概述:性能优化的重要性
在计算机图形学中,我们追求物理效果的真实性,有时会牺牲速度。例如,一个模拟可能需要从实时变为离线渲染,每帧耗时数秒甚至数分钟。然而,即使每帧耗时十秒,这也是经过大量优化后的结果。高性能计算技巧对于实现这样的性能至关重要。
上一节我们介绍了物理模拟的基本概念,本节中我们来看看如何通过底层优化来大幅提升性能。
第一部分:现代计算机体系结构
要编写高性能程序,首先需要理解程序运行的硬件环境。现代计算机的体系结构与经典的冯·诺依曼架构已有很大不同。
内存层次结构与缓存
现代CPU计算速度很快,但内存的带宽和延迟往往跟不上。为了解决这个问题,硬件引入了多级缓存。
以下是内存层次结构的关键组成部分:
- 寄存器:速度最快,容量最小,位于CPU核心内。
- L1缓存:速度很快,容量较小(例如32KB),每个CPU核心独享。
- L2缓存:速度较快,容量较大(例如256KB),每个CPU核心独享。
- L3缓存:速度一般,容量大(例如每核心1.5-2MB),所有CPU核心共享。
- 主内存:速度慢,容量大(例如32GB),延迟可达数百个时钟周期。
公式:CPU时钟周期数 ≈ 计算时间 + 内存访问时间。在现代程序中,内存访问时间往往是主要瓶颈。
局部性原理
提高性能的关键在于提高数据的局部性,减少对慢速主内存的访问。
局部性分为两种:
- 空间局部性:程序倾向于访问相邻的内存地址。例如,顺序访问数组元素。CPU的硬件预取器会检测这种模式并提前加载数据。
- 时间局部性:程序倾向于重复访问相同的内存地址。例如,在循环中反复使用同一个变量。尽可能将数据保留在高速缓存或寄存器中。
核心概念:Working Set(工作集)。指一个计算内核频繁访问的数据集大小。如果工作集能完全放入L1缓存,性能将极大提升。
案例分析:缓存行与访问模式
程序性能并非只与指令数量相关,更与内存访问模式密切相关。内存访问以缓存行(通常为64字节)为单位。
考虑以下访问数组的代码:
for (int i = 0; i < N; i += stride) {
sum += a[i];
}
当stride为1、2、4、8时,尽管循环次数减少,但运行时间几乎不变。因为每次访问都会加载整个64字节的缓存行。只有当stride大到可以跳过整个缓存行(例如16个int,即stride=16)时,需要传输的数据量才会减少,性能才会提升。
这个例子表明,对于内存带宽受限型程序,其运行时间主要由需要从主内存传输的数据量决定,与计算量关系不大。
CPU计算能力
现代CPU通过多种技术提升计算吞吐量:
- 超标量:每个时钟周期可以发射多条指令。
- 乱序执行:动态调整指令顺序以隐藏延迟。
- 向量化:使用SIMD指令(如AVX2)一条指令处理多个数据。
公式:理论峰值FLOPS = CPU频率 × 每周期发射的FMA指令数 × 每个FMA的向量宽度 × 核心数。
例如,一个4.2GHz、4核的CPU,每周期发射2条AVX2 FMA指令(每个处理8个单精度浮点数),其理论峰值约为 4.2G × 2 × 8 × 2 × 4 = 538 GFLOPS。

然而,由于内存带宽限制,实际程序中很难达到理论峰值。一个高度优化的稠密矩阵乘法可能达到较高百分比,但大多数程序能达到10%-20%的峰值性能就已非常出色。
性能瓶颈分析
一个关键数据揭示了瓶颈所在:现代CPU每个核心每秒钟需要的理论数据带宽(基于其计算能力)可能是内存系统能提供的实际带宽的100倍。这巨大的差距必须由缓存系统来弥补。因此,优化内存访问模式、提高缓存命中率是性能优化的重中之重。
总结:理解内存层次结构、局部性原理和CPU微架构是编写高性能代码的基础。计算远比数据通信“便宜”,优化内存访问是提升性能的关键。
第二部分:太极高级编程技巧
掌握了硬件知识后,我们来看看如何在太极中应用这些知识来优化物理模拟程序。
结构化节点:灵活控制内存布局
太极通过结构化节点来定义数据的内存布局。这允许我们在不修改计算内核代码的情况下,尝试不同的数据排布方式,以找到最适合当前访问模式的布局。
最基础的SNode是dense。它定义了一个稠密的多维数组。
# 定义一个4x8的稠密标量场
root = ti.root
dense = root.dense(ti.ij, (4, 8))
field_a = dense.place(ti.field(ti.f32))
# 访问方式:field_a[i, j]
其内存布局是行优先的。
我们可以嵌套SNode来实现分块布局,这有时能更好地利用缓存。
# 定义一个2x2分块,每个块内是2x2的布局
root = ti.root
block = root.dense(ti.ij, (2, 2)) # 外层2x2分块
pixel = block.dense(ti.ij, (2, 2)) # 内层每个块2x2
field_b = pixel.place(ti.field(ti.f32))
此时,field_b在内存中是按块连续存储的。
数组结构体与结构体数组

这是两种常见的数据组织方式,对性能有显著影响:
- 数组结构体:
Array of Structures。例如,一个粒子数组,每个元素是包含位置、速度的结构体。优点:随机访问友好,数据封装性好。缺点:顺序访问时,如果只用到部分字段,缓存利用率可能不高。 - 结构体数组:
Structure of Arrays。例如,将所有粒子的x坐标放在一个数组,y坐标放在另一个数组。优点:顺序访问时缓存利用率高,易于向量化。缺点:随机访问时,如果字段多,缓存行利用率可能极低。

在太极中,可以轻松定义这两种布局:
# AoS 布局
particle = ti.root.dense(ti.i, N)
pos = particle.place(ti.Vector([ti.f32] * 3)) # 一个节点放置向量
# 内存: x0,y0,z0, x1,y1,z1, ...
# SoA 布局
root = ti.root
pos_x = root.dense(ti.i, N).place(ti.field(ti.f32))
pos_y = root.dense(ti.i, N).place(ti.field(ti.f32))
pos_z = root.dense(ti.i, N).place(ti.field(ti.f32))
# 内存: x0,x1,x2,... y0,y1,y2,... z0,z1,z2,...
选择哪种布局取决于具体的访问模式。
稀疏数据结构
许多物理模拟(如烟雾、沙子)只在空间的一小部分区域活跃。为整个空间分配稠密网格会浪费大量内存和计算。太极提供了pointer SNode来构建稀疏数据结构。
pointer SNode的每个单元是一个指针,可以指向一个子数据结构,也可以为空。这允许我们按需分配内存。
# 定义一个两层稀疏网格
root = ti.root
block = root.pointer(ti.ijk, (16, 16, 16)) # 顶层16x16x16指针块
cell = block.dense(ti.ijk, (8, 8, 8)) # 每个指针指向一个8x8x8的稠密块
field = cell.place(ti.field(ti.f32))
在这个结构中,只有被激活的block才会分配其下层的dense块内存。


核心操作:
- 激活:对某个索引进行写操作,或调用
ti.activate(),太极会自动分配所需内存。 - 反激活:调用
ti.deactivate(),太极会释放内存。 - 稀疏迭代:
for i, j, k in field:只会遍历已激活的单元。 - 清空:
field.deactivate_all()反激活所有单元。
使用稀疏数据结构可以极大节省内存和计算量,特别适用于3D大场景模拟。太极自动处理了稀疏结构的内存管理和垃圾回收,使得编写稀疏模拟程序与编写稠密程序几乎一样简单。
使用偏移量
太极支持为place节点设置偏移量,从而实现负下标访问,便于实现无边界或中心在原点的模拟网格。
root = ti.root
dense = root.dense(ti.ij, (1024, 1024))
# 设置偏移,使得索引范围变为 i: [-512, 511], j: [-256, 767]
field = dense.place(ti.field(ti.f32), offset=(-512, -256))


总结

本节课中我们一起学*了高性能物理模拟的两大支柱:硬件体系结构知识和高级编程优化技巧。
首先,我们深入了解了现代CPU和内存系统的特点,认识到内存访问是主要的性能瓶颈。通过理解缓存、局部性原理和缓存行,我们知道了如何通过优化数据布局和访问模式来提升性能。
其次,我们学*了太极语言提供的高级特性来实践这些优化:
- 利用结构化节点灵活定义内存布局,在不改动计算逻辑的情况下探索最优数据排布。
- 理解AoS与SoA的优劣,并根据实际访问模式进行选择。
- 使用稀疏数据结构来大幅减少对非活跃区域的内存和计算消耗,这对于大规模3D模拟至关重要。
- 利用偏移量方便地处理网格边界。


将这些技巧应用于物理引擎开发中,可以轻松实现数倍至数十倍的性能提升,使得在单台机器上处理十亿级元素的模拟成为可能。在接下来的作业中,请尝试运用这些知识来优化你的程序。


GAMES202-高质量实时渲染 - P1:Lecture1 - 课程介绍与概述 🎮


在本节课中,我们将学*GAMES202课程的整体介绍,包括课程目标、内容范围、学*方法以及实时渲染的基本概念和发展历程。

课程介绍
我是闫令琪,来自UCSB。我的研究方向是真实感渲染,包括离线和实时渲染。这门课程名为“高质量实时渲染”,旨在探讨如何在保证实时性的前提下,实现高质量的渲染效果。


课程目标与关键词解读







本课程标题包含三个关键词:实时、高质量和渲染。



- 实时:通常指渲染速度达到或超过30帧每秒(FPS)。这个速度保证了交互性,例如在游戏中,玩家的操作能立刻得到视觉反馈。
- 高质量:指追求真实感,即渲染结果在物理上是正确或*似正确的。实时渲染的核心挑战就是在速度与质量之间取得*衡。
- 渲染:指通过计算模拟光线从光源发出,经场景中物体反射/折射后进入虚拟摄像机的过程,从而生成图像。
课程内容概览
本课程的知识点较为分散,采用专题研讨会的形式,主要涵盖以下四个核心话题:
- 阴影与环境光遮蔽:探讨复杂动态场景下的阴影生成技术。
- 实时全局光照:研究如何在实时或交互式速率下模拟光线在场景中的多次弹射。
- 基于物理的着色:讲解真实感材质与光照模型。
- 实时光线追踪:介绍现代实时光线追踪技术的发展与实现思路。

此外,课程还会涉及图像空间操作、非真实感渲染(如卡通渲染)、时域抗锯齿等现代化话题。

课程不讲什么
为了使课程聚焦于核心原理,以下内容将不会涉及:
- 3D建模与游戏引擎的使用教程。
- 离线渲染的深入探讨。
- 神经渲染的主流内容(因其目前通常难以同时满足实时与高质量的要求)。
- OpenGL API的详细教学(但会使用GLSL编写着色器)。
- 纯粹的着色器或场景优化技巧。
- 高性能计算(如CUDA优化)。
本课程侧重于传授渲染背后的科学原理,而非具体的工程实现技术。将科学知识转化为产品需要优秀的工程能力,这需要大家在实践中不断积累。
学*方法与要求
上一节我们介绍了课程的内容与边界,本节中我们来看看如何学*这门课程。
学*基础要求:
- 对图形学有浓厚兴趣。
- 具备图形学基础知识(如上过GAMES101或类似课程)。
- 具备基础的微积分知识(下节课会复*)。
- 有能力配置环境并运行WebGL框架(作业0将帮助大家熟悉)。
课程资源与*台:
- 课程网站:将提供大纲、PPT及阅读材料。
- 答疑:可通过课程论坛或QQ群进行讨论。
- 参考书:经典的《Real-Time Rendering》一书与本课程内容交叉较少,非必需。课程主要参考前沿的论文与课程资料。


关于观看录播视频的建议: 为提升效率,建议使用1.25倍至1.5倍速播放。


作业安排
本课程共有5次正式作业(不含作业0)。
- 作业形式:主要使用GLSL编写着色器,在助教提供的WebGL框架中完成。框架前端使用JavaScript,但大家只需专注于着色器代码。
- 提交时间:采用AOE(Anywhere on Earth)时区,在截止日期前地球任何地方的时间均可提交。
- 开发环境:由于作业集中于少量着色器文件,使用任何文本编辑器(如VS Code, Sublime Text)即可,无需复杂的IDE。
- 学术诚信:请独立完成作业,欢迎讨论思路,但请勿公开分享代码框架或答案。
实时渲染的意义与发展简史
为什么我们要学*实时渲染?因为计算机图形学非常酷炫!实时渲染致力于在速度与真实感之间找到最佳*衡点。



其发展历程与游戏产业紧密相连:
- 早期:画面由少量多边形构成,效果简单。
- 关键突破:约20年前,可编程渲染管线的出现是重大里程碑。程序员可以编写顶点着色器和片段着色器,极大地释放了创造力,使画面质量飞速提升。
- 重要进展:预计算技术(如球谐函数)的发展,允许将复杂计算转移到渲染前,以存储换取实时渲染时的速度。
- 交互式渲染:约8-10年前,基于GPU的低采样率光线追踪配合降噪器,实现了交互速率的全局光照预览。
- 当今与未来:随着硬件加速的光线追踪单元普及,实时光线追踪已成为现实。实时渲染的应用也从游戏扩展到VR/AR,甚至开始用于影视制作。




总结
本节课中我们一起学*了GAMES202课程的定位、核心内容、学*方法以及实时渲染的简要历史。我们明确了本课程的重点是理解高质量实时渲染背后的科学原理。从下一节课开始,我们将进入具体的技术专题学*。

下节课预告:我们将快速回顾渲染管线、着色语言、渲染方程及必要的微积分知识。

GAMES202-高质量实时渲染 - P2:Lecture2 CG基础回顾 🎮
在本节课中,我们将回顾计算机图形学的基础知识,包括硬件渲染管线、OpenGL/GLSL的基本用法,以及描述光线传播的核心方程——渲染方程。这些内容是理解后续高质量实时渲染技术的重要基石。
硬件渲染管线回顾 🔄
上一节我们介绍了课程的整体安排,本节中我们来看看图形渲染的核心流程——硬件渲染管线。渲染管线描述了将3D模型最终显示为2D图像的一系列步骤。
以下是现代GPU渲染管线的主要阶段:
- 顶点处理:将3D空间中的顶点通过一系列变换(如模型、视图、投影变换)转换到屏幕空间。连接关系保持不变。
- 光栅化:将投影到屏幕上的连续三角形离散化为一系列像素(或称为片段)。
- 深度测试与遮挡处理:使用深度缓存(Z-Buffer)逐像素记录深度信息,判断哪些片段是可见的,解决遮挡问题。
- 着色:对每个可见片段计算其颜色。常用模型如布林-冯着色模型,它结合了漫反射、高光和环境光分量。
- 输出图像:最终得到显示在屏幕上的图像。
这套管线能高效处理直接光照,但对于阴影、光线多次弹射等全局光照现象处理能力有限。GPU的高并行度是其速度的关键。
此外,在着色过程中还会涉及纹理映射和插值。我们使用重心坐标对三角形顶点属性进行插值,以得到内部*滑过渡的值。
OpenGL与GLSL基础 🛠️
了解了渲染管线后,我们来看看如何通过编程接口来控制它。OpenGL是一套跨*台的图形API,运行在CPU端,负责调度GPU执行渲染任务。其对应的着色语言是GLSL。
OpenGL的作用与特点
OpenGL的核心作用是告诉GPU要做什么。我们可以将其工作流程类比为画家作画:
- 准备模特与场景(定义物体与模型):使用顶点缓冲对象在GPU中存储模型数据(顶点位置、法线、纹理坐标等)。
- 摆放画架与画布(设置视图与帧缓冲):定义相机(视图变换)并指定渲染目标——帧缓冲。一个帧缓冲可以绑定多个纹理,实现一次渲染输出多张图(多重渲染目标)。
- 开始绘画(执行着色):这是最核心的部分,需要我们编写着色器。主要涉及两种:
- 顶点着色器:处理每个顶点的变换,并输出需要插值到片段的数据。
- 片段着色器:接收插值后的数据,为每个片段计算最终颜色。
OpenGL的优点是跨*台,缺点是版本碎片化且采用C风格API,编写和调试相对不便。我们更应关注着色器本身的逻辑。
GLSL着色器代码示例
以下是作业中一个简单的着色器示例,展示了GLSL的基本结构。
顶点着色器 (Vertex Shader):
attribute vec3 aVertexPosition; // 输入:顶点位置(CPU传入)
attribute vec3 aVertexNormal; // 输入:顶点法线
attribute vec2 aTextureCoord; // 输入:纹理坐标
uniform mat4 uMVPMatrix; // 全局变量:模型-视图-投影矩阵
varying vec2 vTextureCoord; // 输出:传递给片段着色器的纹理坐标(会被插值)
varying vec3 vNormal; // 输出:传递给片段着色器的法线(会被插值)
void main(void) {
// 1. 顶点位置变换
gl_Position = uMVPMatrix * vec4(aVertexPosition, 1.0);
// 2. 传递插值数据
vTextureCoord = aTextureCoord;
vNormal = aVertexNormal;
}
片段着色器 (Fragment Shader):
precision highp float; // 声明精度

varying vec2 vTextureCoord; // 输入:从顶点着色器传来,已插值
varying vec3 vNormal; // 输入:从顶点着色器传来,已插值
uniform sampler2D uSampler; // 全局变量:纹理采样器
uniform vec3 uLightPos; // 全局变量:光源位置
uniform vec3 uCameraPos; // 全局变量:相机位置
void main(void) {
// 1. 从纹理采样颜色
vec4 textureColor = texture2D(uSampler, vTextureCoord);
// 2. 此处可进行光照计算(例如布林-冯模型)
// ... (光照计算代码)
// 3. 输出最终片段颜色
gl_FragColor = vec4(finalColor, 1.0);
}
关键概念:
attribute:仅用于顶点着色器,表示每个顶点的属性。uniform:全局变量,由CPU传入,所有顶点/片段共享。varying:用于从顶点着色器向片段着色器传递数据,数据会被自动插值。gl_Position:顶点着色器必须赋值的内置变量,表示变换后的顶点位置。gl_FragColor:片段着色器必须赋值的内置变量,表示该片段的颜色。
着色器调试技巧 🐛

由于着色器运行在GPU上,无法直接使用printf。一个简单有效的调试方法是“颜色编码法”:将你想查看的中间值(如深度、法线方向)映射到[0,1]范围,然后作为颜色输出到gl_FragColor,通过观察屏幕颜色来推断数值。
渲染方程(Rendering Equation)📐
掌握了工具,我们再来回顾描述光线物理传播的核心理论。渲染方程是图形学中描述光线传播的正确数学模型。
其公式如下:
Lo(p, ωo) = Le(p, ωo) + ∫Ω fr(p, ωi, ωo) Li(p, ωi) (n·ωi) dωi
其中:
Lo(p, ωo):从点p沿方向ωo出射的辐射率。Le(p, ωo):点p自身发出的辐射率。fr(p, ωi, ωo):点p的双向反射分布函数。Li(p, ωi):从方向ωi入射到点p的辐射率。(n·ωi):余弦项,将入射辐射率转换为辐照度。∫Ω ... dωi:对以点p法线为中心的半球面所有入射方向进行积分。
在实时渲染中,我们常显式考虑可见性,将方程理解为:出射光 = 自发光 + 所有可见光源的入射光经过BRDF和余弦项作用后的贡献之和。
全局光照与实时渲染的挑战
渲染方程是递归定义的,包含了光线多次弹射的间接光照。全局光照 = 直接光照 + 间接光照。
实时渲染的主要挑战在于:
- 直接光照积分计算量大:对每个着色点需在半球面积分,计算昂贵。
- 间接光照模拟困难:涉及光线在场景中的多次弹射,复杂度高。
实践中观察发现,在直接光照基础上加入一次间接光照(即光线弹射两次)能带来显著的视觉提升,而增加更多弹射次数的收益递减。因此,实时渲染领域许多工作聚焦于高效模拟一次或少数几次间接光照。
总结 📚

本节课我们一起回顾了计算机图形学的核心基础。我们首先梳理了硬件渲染管线的各个阶段,理解了从3D模型到2D图像的转换过程。接着,我们学*了OpenGL/GLSL这套工具,了解了如何通过API控制GPU,并编写顶点和片段着色器来实现渲染逻辑。最后,我们重温了渲染方程这一理论基础,明确了直接光照与间接光照的区别,以及实时渲染在求解此方程时面临的挑战与主要目标。这些概念是后续深入学*实时阴影、环境光遮蔽、全局光照等高级话题的必备前提。

GAMES202-高质量实时渲染 - P3:Lecture3 实时阴影 1 🎮
在本节课中,我们将学*实时渲染中阴影技术的基础知识。我们将从经典的Shadow Mapping技术开始,回顾其原理与问题,并深入探讨其背后的数学基础。最后,我们将介绍现代实时渲染中生成软阴影的主流方法——PCSS(Percentage Closer Soft Shadows)。
课程内容概述 📋
本节课将分为三个主要部分:
- Shadow Mapping回顾与问题分析:复*GAMES101中介绍的Shadow Mapping算法,并深入探讨其存在的自遮挡(Self Occlusion)和走样(Aliasing)问题。
- 阴影背后的数学:探讨将可见性(Visibility)项从渲染方程中分离出来的数学*似原理,理解Shadow Mapping的理论基础。
- 软阴影生成:PCSS:介绍如何利用PCF(Percentage Closer Filtering)技术来生成软阴影,并详细讲解PCSS算法的三个核心步骤。

第一部分:Shadow Mapping回顾与问题分析 🔍



上一节课我们介绍了实时渲染的管线基础。本节中,我们来看看生成阴影的核心技术——Shadow Mapping。


Shadow Mapping是一个完全在图像空间中运行的、两趟(Two-Pass)的算法。
Shadow Mapping算法步骤

以下是Shadow Mapping的基本流程:
-
第一趟(生成Shadow Map):
- 将摄像机放置在光源位置,看向场景。
- 渲染整个场景,但输出的不是着色颜色,而是每个像素点到光源的最*深度值。这张深度图就是Shadow Map。
- 代码概念:在Fragment Shader中,将深度值写入帧缓冲(如
gl_FragDepth),而非颜色值。
-
第二趟(实际渲染与阴影判断):
- 从真正的摄像机(眼睛)位置出发,再次渲染场景。
- 对于每个需要着色的片元(Shading Point),计算其到光源的距离。
- 将该点投影到光源空间,找到其在Shadow Map中对应的深度值。
- 比较当前片元到光源的实际距离与Shadow Map中记录的深度值:
- 如果两者(*似)相等,说明该点可见,不在阴影中。
- 如果实际距离大于Shadow Map记录的深度,说明该点被更*的物体遮挡,处于阴影中。
Shadow Mapping的优势与问题
Shadow Mapping的优势在于,一旦生成了Shadow Map,后续的阴影查询就完全在图像空间进行,无需原始的几何信息。
然而,它也存在两个经典问题:
-
自遮挡(Self-Occlusion / Shadow Acne)
- 现象:物体表面出现条纹状的虚假阴影。
- 原因:Shadow Map具有有限的分辨率。每个像素记录的是一个常数值深度,这相当于用一系列“深度小片”来离散化表示连续的场景表面。当光线以掠射角(Grazing Angle)照射时,这种离散化会导致下方的表面被误判为被上方的“小片”遮挡。
- 工业界解决思路:引入一个深度偏移(Depth Bias)。在比较深度时,只有当实际深度比Shadow Map记录深度大出某个阈值(Bias)时,才判定为被遮挡。这个Bias可以根据光线与法线的夹角动态调整。
- Bias的副作用:过大的Bias会导致阴影与投射物分离,产生“悬浮”的阴影(Detached Shadow / Peter Panning)。
-
走样(Aliasing)
- 现象:阴影边缘出现锯齿。
- 原因:Shadow Map分辨率不足,一个像素覆盖的深度块投影到场景中后,在阴影边缘形成明显的“块状”边界。
- 解决思路:工业界采用多种技术,如级联阴影映射(Cascaded Shadow Maps, CSM)来为不同距离的区域分配不同分辨率的Shadow Map。本节课后续介绍的PCF也是解决该问题的一种方法。

第二部分:阴影背后的数学 🧮
上一节我们回顾了Shadow Mapping的实践方法。本节中,我们来探讨其背后的数学原理,理解为何可以将阴影计算分离出来。
这个原理基于一个在实时渲染中广泛使用的积分*似公式:
∫_Ω f(x) g(x) dx ≈ (∫_Ω f(x) dx) * (∫_Ω g(x) dx) / (∫_Ω dx)
这个公式将两个函数乘积的积分,*似为各自积分的乘积(并除以积分域的“体积”进行归一化)。
这个*似在以下两种情况下比较准确:
- 函数
g(x)的积分域(Support)非常小。 - 函数
g(x)在其积分域内变化非常*缓(低频/Smooth)。
现在,让我们将其应用于渲染方程。我们通常将渲染方程中的可见性项 V 单独写出:
L_o(p, ω_o) = ∫_Ω L_i(p, ω_i) * f_r(p, ω_i, ω_o) * cosθ_i * V(p, ω_i) dω_i
利用上面的*似公式,我们可以将可见性项 V 分离出来:
L_o(p, ω_o) ≈ [ ∫_Ω L_i(p, ω_i) * f_r(p, ω_i, ω_o) * cosθ_i dω_i ] * [ ∫_Ω V(p, ω_i) dω_i ] / [ ∫_Ω dω_i ]
这个*似的物理意义非常关键:
- 公式的右边第一部分
[ ∫_Ω L_i(...) dω_i ]正是不考虑遮挡时的着色结果(Shading)。 - 公式的右边第二部分
[ ∫_Ω V(p, ω_i) dω_i ] / [ ∫_Ω dω_i ]代表了*均可见性,其值在0到1之间,这正好就是阴影的软硬程度。
与Shadow Mapping的关联:
- 对于点光源/方向光:其光照来自一个单一方向(积分域极小,满足条件1)。此时,*均可见性
V退化为非0即1的硬阴影判断,这正是经典Shadow Mapping所做的事情。 - 对于面光源+漫反射表面:面光源提供均匀光照(
L_i是常数),漫反射BRDF变化*缓(f_r是常数),这满足了条件2。此时,*均可见性V就是一个介于0到1之间的值,可以用来计算软阴影。这为PCSS等软阴影技术提供了理论依据。

第三部分:软阴影生成:PCSS 🌓
上一节我们了解了阴影分离的数学基础。本节中,我们来看看如何利用这个思想生成视觉上更真实的软阴影。
从PCF到PCSS


首先需要区分两个概念:
- PCF (Percentage Closer Filtering):最初是一种抗锯齿技术,用于*滑Shadow Mapping产生的硬阴影锯齿。
- PCSS (Percentage Closer Soft Shadows):利用PCF的思想,通过自适应地改变滤波范围来模拟面光源产生的软阴影。
PCF的工作原理(抗锯齿):
- 对于着色点
p,不再只查询Shadow Map上对应单个像素的深度。 - 而是在
p投影点周围取一个区域(如 7x7 的像素块)。 - 将该区域内每一个像素记录的深度都与
p的实际深度进行比较,得到一系列二值结果(0表示被挡,1表示可见)。 - 对这些二值结果进行加权*均,得到一个介于0到1之间的值,作为
p的最终可见性。这个值不仅消除了锯齿,而且在视觉上产生了模糊效果。
关键发现:人们发现,PCF中使用的滤波核(Filter Kernel)大小直接影响阴影边缘的模糊程度。滤波核越大,阴影越“软”。这启发了PCSS的诞生。
PCSS算法详解

PCSS的目标是为不同的着色点动态决定一个“正确”的滤波核大小,以模拟真实的软阴影。其算法分为三步:
步骤一:遮挡物搜索(Blocker Search)
- 目标:估算在着色点
p与面光源之间,*均遮挡物的深度(d_Blocker)。 - 方法:在Shadow Map上,以
p的投影点为中心,在一个初始范围内(例如,根据光源大小和p的深度估算出的一个区域)搜索。 - 操作:对该区域内每个像素,判断其是否遮挡了
p(即其深度是否小于p的深度)。将所有遮挡物的深度值取*均,得到d_Blocker。

步骤二:计算滤波核大小(Penumbra Estimation)
- 目标:利用相似三角形原理,计算
p点应有的半影(Penumbra)宽度,即PCF所需的滤波核大小W。 - 公式:
W_Penumbra = (d_Receiver - d_Blocker) * W_Light / d_Blockerd_Receiver: 着色点p到光源的深度。W_Light: 光源的物理大小(在场景中的度量)。(d_Receiver - d_Blocker): 遮挡物到接收面的距离。距离越*,阴影越硬(W越小);距离越远,阴影越软(W越大)。
步骤三:执行百分比渐进滤波(Percentage Closer Filtering)
- 目标:使用步骤二计算出的滤波核大小
W_Penumbra,在Shadow Map上对p点执行标准的PCF操作。 - 结果:得到
p点最终的、*滑过渡的可见性值(软阴影)。
PCSS的效果与性能

PCSS能产生非常真实的软阴影效果,例如*处的阴影较硬,远处的阴影较软,符合物理观察。
然而,其性能开销巨大。每个着色点都需要进行两次密集的纹理采样(Blocker Search + PCF)。在后续课程中,我们将介绍如Variance Shadow Mapping (VSM) 等更高效的方法来加速软阴影的计算。

课程总结 🎯
本节课中我们一起学*了实时阴影渲染的基础与进阶知识:
- 回顾了Shadow Mapping:理解了两趟算法流程,并重点分析了其固有的自遮挡和走样问题及其缓解方案(如Depth Bias)。
- 探讨了数学基础:通过一个关键的积分*似公式,理解了将可见性(阴影) 从着色计算中分离出来的理论依据,并明确了该*似在点光源(硬阴影)和面光源+漫反射(软阴影)情况下的适用性。
- 学*了PCSS算法:掌握了生成软阴影的主流方法PCSS。其核心思想是根据遮挡物距离自适应地改变PCF的滤波核大小。我们详细分析了其三个步骤:Blocker Search -> Penumbra Estimation -> PCF。

通过本讲,我们建立了从硬阴影到软阴影,从实践技巧到数学原理的完整知识链条。在下一讲中,我们将继续探索更高效的软阴影算法,如Variance Shadow Mapping,并进一步讨论如何对阴影进行滤波和降噪以提升性能与质量。


GAMES202-高质量实时渲染 - P4:Lecture4 实时阴影 2 🎮
概述
在本节课中,我们将继续深入探讨实时阴影技术。我们将详细分析百分比渐*软阴影(PCSS)的数学原理,并介绍一种更高效的替代方案——方差软阴影映射(VSSM)。同时,我们也会简要了解其进阶版本——矩阴影映射(Moment Shadow Mapping)。课程旨在让初学者理解这些技术的核心思想、优势与局限。
上节回顾与本节引入
上一节我们介绍了阴影映射(Shadow Mapping)的基本原理、存在问题以及百分比渐*过滤(PCF)和百分比渐*软阴影(PCSS)的基本思路。本节中,我们将首先从数学公式角度深入理解PCF/PCSS,然后探讨其性能瓶颈,并引出旨在解决这些瓶颈的VSSM方法。
深入理解PCF:背后的数学 🧮
PCF并非在模糊阴影贴图本身,而是在对可见性函数进行卷积(滤波)。对于着色点 (x),我们考虑其在阴影贴图上对应像素 (p) 周围邻域 (N(p)) 内的一系列点 (q)。
以下是PCF操作的数学描述:
-
定义比较函数:我们使用一个阶跃函数 (\chi)(chi)来表示遮挡比较结果。
[
\chi(z) =
\begin{cases}
1 & \text{if } z > 0 \
0 & \text{otherwise}
\end{cases}
]
其中,(z = d_{SM}(q) - d_{scene}(x)),(d_{SM}(q)) 是阴影贴图在 (q) 点记录的深度,(d_{scene}(x)) 是着色点 (x) 的实际深度。若 (z > 0),表示 (q) 点记录的表面比 (x) 点更远(未遮挡),可见性为1;反之则为0。 -
卷积过程:PCF计算的是该邻域内所有点 (q) 的可见性值的加权*均。
[
V(x) = \sum_{q \in N(p)} w(p, q) \cdot \chi(d_{SM}(q) - d_{scene}(x))
]
其中,(w(p, q)) 是取决于 (p) 和 (q) 距离的权重(例如高斯权重)。这本质上是在对二值的可见性函数进行滤波,从而产生柔和的阴影边缘。
关键点:PCF滤波的是“深度比较的结果”(非0即1),而非深度值本身。直接模糊深度贴图再进行比较,得到的结果依然是二值的,无法产生*滑过渡。
PCSS的性能瓶颈与解决思路 ⚡
PCSS算法分为三步:遮挡物搜索(Blocker Search)、确定滤波范围、执行PCF。其中,第一步和第三步需要在阴影贴图的一个区域内进行大量采样(或遍历所有纹素),这是主要的性能瓶颈。
- 慢的原因:对于大的滤波区域,逐纹素采样或遍历计算成本高昂。若采用稀疏随机采样来加速,则会引入噪声(Noise)。
- 工业界常见方案:接受第一步和第三步的*似噪声结果,然后在图像空间进行时域/空域降噪(Denoising)。这将在后续课程中详细讨论。
- 新的思路:能否避免耗时的区域采样,直接快速估算出“该区域内有百分之多少的纹素深度小于当前着色点深度”?这引出了VSSM方法。
方差软阴影映射(VSSM)💡
VSSM的核心思想非常巧妙:将一个区域内的深度分布*似为一个概率分布,并通过该分布的统计特性来快速估算可见性百分比,从而避免采样。
核心类比:考试排名
问题:“在一个区域内,有多少比例的纹素深度比当前着色点深度 (t) 浅?” 这类似于:“已知全班成绩分布,问分数低于 (t) 的学生占多少比例?”
- 原始方法(PCF):逐一查看每个同学的成绩——计算量大。
- VSSM方法:假设成绩服从正态分布。我们只需要知道全班的*均分(均值)和成绩的分散程度(方差),就能估算出分数低于 (t) 的大致比例。
VSSM的具体步骤
-
生成深度与深度*方贴图:在生成阴影贴图时,不仅存储深度 (z),还在另一个通道(如G通道)存储深度的*方 (z^2)。这几乎没有额外开销。
- 均值 (E(z)):通过查询区域内的*均深度获得。
- 方差 (Var(z)):利用公式 (Var(z) = E(z^2) - [E(z)]^2) 计算。其中 (E(z^2)) 可通过查询深度*方贴图的区域*均值获得。
-
快速范围查询:为了快速得到任意矩形区域内的 (E(z)) 和 (E(z^2)),需要使用支持快速范围求和的数据结构。
- Mipmap:能快速进行*似的方形区域查询,但不精确,尤其对非方形区域。
- SAT(Summed-Area Table):通过前缀和原理,能在常数时间内精确计算任意轴对齐矩形区域内的总和,从而得到精确均值。虽然需要 (O(n)) 的预处理时间,但在GPU上可以高效并行实现。


- 估算累积分布函数(CDF):知道了均值 (\mu) 和方差 (\sigma^2),我们就定义了*似的深度分布。我们需要计算该分布中深度值小于 (t) 的概率 (P(z < t)),即CDF。
- 切比雪夫不等式(Chebyshev's Inequality):VSSM使用了一个关键工具。对于任意分布,只要知道其均值 (\mu) 和方差 (\sigma^2),就可以估算 (P(z > t)) 的上界:
[
P(z > t) \leq \frac{\sigma2}{\sigma2 + (t - \mu)^2}, \quad \text{for } t > \mu
] - 在渲染中的使用:在实时渲染中,我们常将这个上界不等式当作*似等式来使用,即:
[
P(z > t) \approx \frac{\sigma2}{\sigma2 + (t - \mu)^2}
]
那么,可见性(深度小于 (t) 的比例)*似为:
[
V \approx 1 - P(z > t)
] - 限制:切比雪夫不等式在 (t > \mu) 时估计较准,当 (t \leq \mu) 时可能不准,但实践中整体效果可以接受。
- 切比雪夫不等式(Chebyshev's Inequality):VSSM使用了一个关键工具。对于任意分布,只要知道其均值 (\mu) 和方差 (\sigma^2),就可以估算 (P(z > t)) 的上界:
解决PCSS的第一步:遮挡物*均深度
PCSS的第一步需要计算区域内遮挡物(深度小于 (t) 的纹素)的*均深度 (z_{occ})。
VSSM通过一个巧妙的观察来解决:
设区域内所有纹素的*均深度为 (z_{Avg}),遮挡物的*均深度为 (z_{occ}),非遮挡物的*均深度为 (z_{unocc})。它们满足以下关系((N) 为总纹素数,(N_1) 为非遮挡物数量):
[
\frac{N_1}{N} \cdot z_{unocc} + (1 - \frac{N_1}{N}) \cdot z_{occ} = z_{Avg}
]
- (\frac{N_1}{N})(非遮挡物比例):可由切比雪夫不等式估算,即 (P(z > t))。
- (z_{Avg}):通过SAT等范围查询得到。
- (z_{unocc})(非遮挡物*均深度):VSSM做了一个大胆但合理的假设——所有非遮挡物的深度都等于当前着色点深度 (t)。这对于常见的*面接收物是*似成立的。
将上述已知量代入公式,即可解出唯一的未知量 (z_{occ})。
VSSM的优势与问题
- 优势:速度快,无噪声。完全避免了PCSS中耗时的区域采样步骤。
- 问题(Light Leaking):当区域内的深度分布复杂(多峰,例如栅栏、镂空物体)时,用单峰的正态分布来*似会严重失真。这可能导致本应被完全遮挡的区域被错误地估计为部分可见,产生“漏光”瑕疵。此外,对于非*面的阴影接收体,其假设也可能导致瑕疵。
矩阴影映射(Moment Shadow Mapping)🚀
为了克服VSSM在描述复杂深度分布时的不足,矩阴影映射被提出。

核心思想:使用更高阶矩
- 矩(Moments):在VSSM中,我们实际上使用了深度 (z) 的前两阶矩:一阶矩 (E(z))(均值)和二阶矩 (E(z^2))(用于求方差)。
- 高阶矩:矩阴影映射存储更高阶的矩,例如 (z, z^2, z^3, z^4)(前四阶矩)。数学上证明,使用前 (m) 阶矩可以重建一个具有最多 (m/2) 个“台阶”的分布函数。这意味着四阶矩可以描述双峰分布,从而更好地处理镂空物体等复杂情况。

效果与代价
- 效果:能显著减轻VSSM中的漏光问题,得到更接*参考PCF(无噪声版)的质量。
- 代价:
- 存储:需要存储更多数据(如RGBA四个通道存四阶矩)。
- 计算:从存储的矩重建CDF的过程比切比雪夫不等式复杂得多。
- 现状:由于时域降噪技术的成熟,能够有效处理PCSS的噪声,且PCSS实现更简单,因此PCSS在工业界应用更广泛。矩阴影映射作为一种高质量无噪声方案,多用于特定场合或研究。
总结
本节课我们一起深入学*了实时软阴影的进阶技术:
- 数学本质:明确了PCF是对二值可见性函数进行滤波,而非模糊深度图。
- 性能分析:指出了PCSS算法中遮挡物搜索和PCF滤波是性能瓶颈。
- VSSM:学*了其利用均值、方差和切比雪夫不等式来快速、无噪声地估算软阴影的核心思想,包括其快速范围查询(SAT)的实现和解决PCSS第一步的巧妙方法,同时也了解了其漏光缺陷。
- 矩阴影映射:了解了通过存储更高阶矩来更精确描述深度分布,以改善VSSM缺陷的思路。

这些技术体现了实时渲染中经典的权衡艺术:在速度、质量、实现复杂度之间寻找*衡,并运用了概率统计等数学工具来优化性能。下节课我们将开始新的主题——环境光照。

GAMES202-高质量实时渲染 - 第五讲:实时环境光照映射 📚
在本节课中,我们将学*如何处理实时环境光照,即如何在忽略阴影的情况下,计算场景中任意点的着色。我们将从距离场阴影开始,然后深入探讨环境光照下的着色问题,并介绍一种无需采样的高效方法——Split Sum*似。
课前事项 📢
作业一即将发布,内容涉及PCSS和Shadow Mapping的实现,大约有十天时间完成。

下周(4月1日当周)将暂停一次课程直播与录播,课程将在下下周恢复。关于GAMES101作业重新提交与批改的事宜,将在之后启动并发布正式通知。


上节课回顾 🔍

上一节课我们深入探讨了PCSS,并介绍了其聪明的优化版本——VSSM。VSSM通过使用范围查询来避免采样,从而高效地解决了PCSS中某些步骤缓慢的问题。我们还提到了其延伸版本,引入了更高阶的“矩”的概念。虽然VSSM在*期随着时域去噪等方法的兴起,应用可能不如PCSS广泛,但其精妙的算法思想非常值得我们学*。
本节课内容概览 🎯
本节课我们将首先完成关于阴影知识的最后一块拼图——距离场软阴影。这是一种新兴且前景广阔的技术。随后,我们将进入本节课的核心主题:环境光照下的着色计算。我们将重点介绍一种名为 Split Sum 的高效*似方法,它允许我们在不进行昂贵采样的情况下,完成环境光照的渲染。

请注意:本节课讨论的环境光照“着色”问题,暂时忽略了阴影(Visibility)的影响。
第一部分:距离场软阴影 🎭
为什么需要距离场阴影?
距离场阴影具有速度快、没有自遮挡和阴影悬浮问题等优点。虽然它需要较大的存储空间,并且对动态形变物体的支持有限,但其在生成高质量软阴影方面的潜力,使其成为*年来的研究热点。
什么是距离场?
距离场,或称有向距离函数,定义了空间中任意一点到某个物体表面的最小距离。在物体内部的点,距离通常定义为负值;在物体外部,则为正值。
公式定义:
对于一个点 p 和物体表面 S,有向距离函数 SDF(p) 可以表示为:
SDF(p) = min_{q ∈ S} ||p - q|| * sign( p 在 S 外部 ? 1 : -1 )
可视化上,距离场看起来像是物体轮廓向外“膨胀”的模糊版本,或者像地图上的等高线。

距离场的优势与应用
距离场的一个关键优势是它能很好地表示物体边界,使得在不同形状间进行混合变得容易,无需关心它们之间的拓扑关系。这背后与“最优传输”理论密切相关。
以下是距离场的两种主要应用:
应用一:光线步进追踪
这是一种利用距离场进行光线追踪的经典方法,称为球体追踪。其核心思想是:空间中某点的SDF值给出了一个“安全距离”。从该点沿光线方向前进不超过这个距离,一定不会与任何物体相交。

算法伪代码:
float RayMarch(vec3 origin, vec3 direction) {
float totalDistance = 0.0;
for (int i = 0; i < MAX_STEPS; i++) {
vec3 p = origin + totalDistance * direction;
float safeDist = SDF(p); // 查询当前点的SDF值
totalDistance += safeDist;
if (safeDist < EPSILON) return totalDistance; // 命中表面
if (totalDistance > MAX_DIST) break; // 超出范围
}
return INFINITY; // 未命中
}
利用这一性质,我们可以从起点开始,每次前进当前点的SDF值所指示的安全距离,迭代直至接*表面或超出范围。


关于SDF的生成与存储:SDF是一个定义在整个3D空间中的标量场,预计算和存储开销很大。通常需要使用层次化结构(如八叉树)进行优化存储,只为接*物体表面的区域存储精细数据。对于多个刚体物体,整个场景的SDF可通过取各物体SDF的最小值获得。但对于形变物体,SDF需要重新计算。
应用二:生成软阴影

我们可以利用在光线步进过程中获得的信息,来*似计算软阴影。观察发现,在着色点向光源方向步进时,路径上各点的SDF值定义了一个“安全角度”。这个角度越小,意味着该方向被遮挡的可能性越大,阴影就越“硬”、越暗。
核心*似:
在步进过程中,对于路径上的每个点,我们计算其安全角度 θ 的*似值:θ ≈ k * (SDF(p) / distance(p, shadingPoint))。
其中,k 是一个控制阴影软硬程度的系数。k 值越大,阴影越硬(过渡带越窄);k 值越小,阴影越软。
最终可见性计算:
在整条步进路径上,我们取所有点计算出的最小角度*似值,并与1取最小值,作为该方向最终的可见性估计。
Visibility = min(1.0, k * min_{along ray} (SDF / distance))
这是一个非常大胆但有效的*似。它假设遮挡物可能出现在安全角度所定义的整个锥形区域内的任何位置。
距离场阴影的总结
优点:
- 速度快:在Shader中通过几次SDF查询和光线步进即可完成,尤其在不考虑SDF生成时间的情况下,相比传统Shadow Map有速度优势。
- 质量高:能产生视觉上令人满意的软阴影,没有传统Shadow Mapping的许多瑕疵。
缺点与挑战:
- 存储开销大:需要预计算并存储3D距离场。
- 不支持动态形变:物体形变后需要重新计算SDF。
- 生成SDF本身是开销:预计算过程可能很耗时。
- 存在Artifact:在接缝等处可能有不自然的效果。
扩展知识:距离场还可用于实现无限分辨率的字体渲染,通过存储字体的SDF并在渲染时进行插值,可以在任何距离下都获得清晰的轮廓。
第二部分:环境光照下的着色计算 🌍
上一节我们介绍了如何利用距离场生成阴影,本节中我们来看看环境光照的核心挑战:如何高效计算不考虑阴影时的着色。
问题定义:图像基础光照
环境光照通常用一张环境贴图表示,它记录了从场景中某点向所有方向看去的光照强度,并假设所有光源都位于无限远处。这种技术常被称为基于图像的光照。

我们的目标是:给定一张环境贴图,计算场景中任意着色点 x 的出射光亮度 Lo。
这需要求解渲染方程中不考虑可见性项的积分:
渲染方程(简化):
Lo = ∫_{Ω} L_i(ω_i) * BRDF(ω_i, ω_o) * cosθ_i dω_i
其中:
L_i(ω_i)是环境贴图给出的入射光亮度。BRDF是双向反射分布函数。cosθ_i是入射方向与法线的点乘。Ω是法线定义的半球。
直接使用蒙特卡洛积分求解这个积分需要大量采样,在实时渲染中代价过高。因此,我们需要寻找无需采样的*似方法。
核心洞察:Split Sum *似

观察渲染方程中的被积函数,它是光照函数 L_i 和 BRDF函数(含cos项)的乘积。回顾我们在阴影课程中引入的“积分*似拆分”思路,当两个函数满足一定条件时,可以将它们的乘积积分*似拆分为两个独立积分的运算。
幸运的是,在环境光照着色中,BRDF项通常满足以下两个条件之一:
- 支撑集小:对于Glossy材质,BRDF的lobe只覆盖球面上很小一块区域。
- 变化*滑:对于Diffuse材质,BRDF的值在整个半球上变化缓慢。
这正是应用*似拆分公式的理想情况!我们可以将光照项 L_i “拆”出积分号外。
拆分公式:
∫_{Ω} f(x) g(x) dx ≈ (∫_{Ω} f(x) dx) / (∫_{Ω} dx) * ∫_{Ω} g(x) dx
(在g的支撑集上*似成立)

应用到我们的问题上:
Lo ≈ (∫_{Ω_{BRDF}} L_i(ω_i) dω_i) / (∫_{Ω_{BRDF}} dω_i) * ∫_{Ω} BRDF * cosθ_i dω_i
这个拆分带来了巨大的好处:
- 第一部分:
(∫ L_i dω_i) / (∫ dω_i)仅仅是对环境贴图L_i在BRDF lobe所覆盖区域进行滤波,并取*均值。这可以预计算!我们可以为环境贴图生成一系列不同模糊程度(对应不同BRDF粗糙度)的模糊版本,存储在类似Mipmap的结构中。 - 第二部分:
∫ BRDF * cosθ_i dω_i是一个纯BRDF的积分,与具体场景光照无关。
处理BRDF积分:进一步的预计算
第二部分仍然是一个积分,我们需要避免在实时计算中采样。这里,我们假设使用基于微表面模型的BRDF(如GGX)。这类BRDF主要受两个因素影响:
- 菲涅尔项:决定基础反射率和随角度变化的反射率。
- 法线分布函数:决定表面的粗糙程度,用
roughness表示。

直接预计算一个包含 roughness、入射角 θ 和基础反射率 F0 的高维表格是不现实的。人们发现,利用Schlick*似对菲涅尔项进行简化后,可以将BRDF积分拆解为两个部分,从而将预计算表格的维度降至2维。
Schlick菲涅尔*似:
F(θ) ≈ F0 + (1 - F0) * (1 - cosθ)^5
通过巧妙的数学变换,可以将原始的BRDF积分重写为:
∫ BRDF * cosθ_i dω_i ≈ F0 * Scale + Offset
其中,Scale 和 Offset 这两个值仅依赖于粗糙度 roughness 和入射角 cosθ,而与基础反射率 F0 无关。

这意味着,我们可以将 Scale 和 Offset 预计算为一张2D查找表(纹理):
- 横轴:
cosθ(或roughness) - 纵轴:
roughness(或cosθ) - 纹理的R通道:存储
Scale值 - 纹理的G通道:存储
Offset值
在实时渲染时,对于一个具体的着色点,我们:
- 根据其粗糙度和视角方向(可转换为半角向量等),去预计算的2D BRDF积分纹理中查找,得到
Scale和Offset。 - 根据材质的基础反射率
F0,组合公式F0 * Scale + Offset,即可得到第二部分积分的*似值。
Split Sum 方法总结
综上所述,Split Sum 方法通过两次“拆分”和“预计算”,彻底避免了在着色时的采样操作:
- 第一次拆分:将渲染方程拆分为“滤波后的环境光”和“BRDF积分”两部分。
- 第二次拆分:利用BRDF模型的性质,将BRDF积分进一步拆分为与
F0相关的线性组合,并将系数预计算为2D纹理。
实时着色步骤:
- 环境光部分:根据表面粗糙度,选择预计算好的相应层级的环境光模糊贴图,在镜面反射方向(对Glossy)或法线方向(对Diffuse)上进行一次纹理查询。
- BRDF部分:根据粗糙度和入射角,查询预计算的BRDF积分纹理(2D LUT),得到
Scale和Offset,再与F0结合计算。 - 最终结果:将两部分的结果相乘,得到最终的着色颜色。
这种方法在工业界(如Unreal Engine的PBR管线中)被广泛使用,它能在几乎没有性能开销的情况下,得到与离线渲染参考图非常接*的高质量环境光照效果。
下节课预告 🚀
在本节课中,我们一起学*了如何利用距离场生成高质量的软阴影,并深入探讨了环境光照下着色的核心解决方案——Split Sum*似。这种方法通过巧妙的拆分和预计算,实现了无需采样的实时高性能渲染。
下节课,我们将进入一个更为复杂和前沿的领域:实时全局光照。我们将探讨在3D空间、屏幕空间以及通过预计算来实现全局光照的各种技术,例如LPV、VXGI、RTX GI等。敬请期待!

备注:本节课提到的补充资料链接将在课后提供。

GAMES202-高质量实时渲染 - P7:Lecture7 实时全局光照(三维空间) 🌐
在本节课中,我们将学*三维空间中的实时全局光照方法。首先,我们会完成上节课遗留的预计算辐射度传输(PRT)内容的讲解,然后重点介绍两种重要的实时全局光照技术:反射阴影贴图(RSM)和光传播体积(LPV)。
回顾:预计算辐射度传输(PRT)🔁
上一节我们介绍了PRT的基本概念,它将渲染方程分解为光照(Lighting)和光传输(Light Transport)两部分进行预计算,以实现实时渲染。

核心公式回顾
对于漫反射(Diffuse)表面,着色点 p 的出射辐射度 L_o(p, ω_o) 可*似为:
L_o(p, ω_o) ≈ Σ_i (l_i * t_i)

其中:
l_i是环境光照投影到球谐函数(Spherical Harmonics, SH)基函数上的系数向量。t_i是光传输项(包含可见性、BRDF等)投影到相同SH基函数上的系数向量。- 实际渲染时,只需计算两个向量的点积,复杂度为 O(n)。

光泽表面(Glossy)的PRT
对于光泽表面,其BRDF是四维函数(与入射方向 ω_i 和出射方向 ω_o 均相关)。因此,光传输项 t 不再是简单的向量,而是一个矩阵 T。

- 预计算:对于每个着色点,需要预计算一个光传输矩阵
T。 - 实时渲染:计算变为向量与矩阵的乘法:
L_o ≈ l · T。如果使用n阶SH(即n²个基函数),则存储和计算复杂度为 O(n²)。

效果与局限:PRT能高质量地处理低频光照和复杂的光传输(包括间接光照和阴影),但要求场景和材质是静态的,且预计算数据量较大。







其他基函数介绍:小波(Wavelets)🌊
除了球谐函数,小波是另一类用于描述函数的基函数,尤其在图像压缩(如JPEG)中广泛应用。
以下是球谐函数与小波的主要区别:

| 特性 | 球谐函数 (SH) | 小波 (Wavelets) |
|---|---|---|
| 定义域 | 定义在整个球面上。 | 定义在局部区域(如图像块)。 |
| 频率表示 | 全局性基函数,擅长表示低频信号。 | 局部性基函数,能同时表示高频和低频信号。 |
| 压缩方式 | 截断高阶项(线性逼*)。 | 保留系数最大的项,丢弃接*零的项(非线性逼*)。 |
| 旋转操作 | 支持快速、精确的旋转。 | 不支持快速的旋转操作。 |
| 适用场景 | 环境光照等低频球面函数。 | 高频细节丰富的图像或Cube Map。 |

小结:小波能全频率地表示函数,在描述高频细节(如锐利阴影)时优于SH。但其不支持快速旋转的特性限制了它在动态光照场景中的应用。

实时全局光照(3D方法)💡
全局光照对于渲染的真实感至关重要。在实时渲染中,我们主要关注解决一次反射的间接光照。

核心思路是:将被直接光照照亮的物体表面视为次级光源(Secondary Light Source),然后用这些次级光源去照亮场景中的其他点。

方法一:反射阴影贴图(Reflective Shadow Maps, RSM)
RSM基于上述思路,利用阴影贴图的扩展来实现间接光照。
RSM工作原理
-
生成RSM:
- 从主光源视角渲染场景,生成一张特殊的阴影贴图(RSM)。
- RSM的每个纹素不仅存储深度,还额外存储以下信息:
- 世界空间位置(World Position)
- 法线(Normal)
- 反射通量(Reflected Flux,
Φ_p * f_r):即该点接收的直接光照能量与其BRDF的乘积。
-
渲染间接光照:
-
从摄像机视角渲染场景。对于每个着色点
p,需要计算所有次级光源对它的贡献。 -
贡献计算公式(简化版)为:
L_indirect(p) = Σ_q ( (Φ_q * f_r) * V(p, q) * G(p, q) )其中:
q是RSM中的一个纹素(代表一个次级光源)。Φ_q * f_r是该次级光源的反射通量(从RSM中读取)。V(p, q)是p到q的可见性(RSM中通常忽略此项,这是其主要误差来源)。G(p, q)是几何项,包含距离衰减和余弦项。
-


- 性能优化:
- 直接计算所有RSM纹素对
p的贡献(如512x512次)是不可行的。 - 关键技巧:将着色点
p投影到RSM上,只在投影点周围一定范围内采样若干纹素作为有效的次级光源(例如采样400个),从而大幅减少计算量。
- 直接计算所有RSM纹素对
RSM的优缺点
优点:
- 易于实现,基于标准的阴影贴图流程。
- 能产生有效的间接光照效果,特别适合局部光源(如手电筒)。
缺点:
- 次级光源数量与主光源数量成正比,多光源下性能压力大。
- 忽略次级光源与着色点之间的可见性,可能导致漏光等不真实现象。
- 依赖于“在RSM空间邻*在世界空间也邻*”的假设,可能不准确。
- 采样数目的多少需要在质量和性能之间权衡。
应用:在《最后生还者》、《战争机器4》、《神秘海域4》等游戏中用于手电筒等局部光源的间接光照。
总结 📚


本节课我们一起学*了三维空间中的实时全局光照技术。

- 完成了PRT的讲解:探讨了光泽表面的PRT实现(光传输矩阵)以及球谐函数的局限性,并介绍了小波作为另一种基函数的特点。
- 引入了实时全局光照的核心思想:将直接光照照亮的表面作为次级光源。
- 详细讲解了反射阴影贴图(RSM):
- 其通过扩展阴影贴图来存储次级光源信息。
- 渲染时,通过采样着色点在RSM投影附*的纹素来计算间接光照。
- 分析了RSM高效的原因及其固有的*似与缺陷。
RSM可以看作是离线渲染中“虚拟点光源(VPL)”或“即时辐射度(Instant Radiosity)”方法在实时渲染中的一种高效实现。虽然存在简化,但它为在动态场景中实现实时、可接受的间接光照提供了一条实用路径。

下节课,我们将继续学*另一种重要的三维全局光照方法——光传播体积(LPV),并开始探讨屏幕空间(Screen-Space)的全局光照技术。

GAMES202-高质量实时渲染 - 课程8:实时全局光照(屏幕空间方法) 🌟

在本节课中,我们将学*实时全局光照的屏幕空间方法。我们将从回顾上节课的内容开始,然后深入探讨两种在三维空间中处理全局光照的剩余方法:LPV和VXGI。接着,我们将重点转向屏幕空间,详细讲解屏幕空间环境光遮蔽(SSAO)的原理、推导和实现思路。

课程概述与回顾
上一节课我们主要讨论了PRT方法如何处理Glossy表面及其代价,并介绍了使用Wavelet基函数替代SH的扩展方法。我们还重点介绍了一种在世界空间中进行全局光照的方法:反射阴影贴图(RSM)。其基本思想与阴影贴图类似,即被直接照亮的表面可以作为次级光源,在间接光照中照亮其他物体。
本节课,我们将首先完成对三维空间中另外两种全局光照方法——LPV和VXGI的讲解。之后,我们将进入本节课的核心内容:屏幕空间方法,并重点学*SSAO和SSDO。
三维空间全局光照方法续

Light Propagation Volumes (LPV) 💡
LPV,即光传播体积,最早在《孤岛危机》(Crysis)的CryEngine 3中引入。该方法的核心思想是在三维空间中传播光线,用于计算间接光照。


核心思路:
LPV解决的关键问题是:对于任意着色点,如何立即获得从所有方向到达该点的间接光照辐亮度(Radiance)。它利用了一个重要的物理事实:Radiance在直线传播过程中保持不变。
实现步骤:
LPV的实现可以分为四个主要步骤:


- 识别次级光源:与RSM类似,首先计算场景的直接光照,被直接照亮的表面将成为次级光源。
- 注入(Injection):将场景划分为一个三维网格(体素)。对于每个包含次级光源的体素,计算其向各个方向发出的Radiance初始分布,并使用低阶(如前两阶,共4个系数)球谐函数(SH)进行压缩表示。
- 传播(Propagation):在每个体素中,将其存储的Radiance分布传播到相邻的六个面所对应的体素中。这是一个迭代过程,通常迭代4-5次即可达到稳定状态。
- 渲染:对于任意着色点,找到其所在的体素,获取该体素存储的(代表入射间接光照的)SH系数,即可用于计算该点的间接光照。
优点与问题:
- 优点:速度较快,质量较好,能较好地处理动态场景。
- 问题:存在光线泄漏(Light Leaking) 问题。当场景几何的细节比体素网格更精细时(例如薄墙),一个体素内可能同时包含墙的两侧。在注入阶段,墙一侧光源的Radiance会被*均到整个体素,导致错误地照亮墙的另一侧。虽然可以通过细化网格来缓解,但会增加存储和计算开销。

Voxel Global Illumination (VXGI) 🧊







VXGI,即体素全局光照,采用了与LPV不同的思路。它也是一个两趟(Two-Pass)算法。






核心思路:
- 体素化与层级构建:首先将整个场景体素化,类似于用乐高积木表示场景。然后为这些体素建立一个层级结构(树形结构),从小体素逐步聚合为大体素。
- 光照Pass(Light Pass):计算直接光照,并记录每个体素内入射光照的方向分布和表面法线分布。这比单纯记录出射Radiance更精确,能支持Glossy表面。
- 渲染Pass(Render Pass):从摄像机出发进行渲染。对于每个着色点:
- 如果是Glossy表面,则根据视角方向反射出一个圆锥(Cone)。
- 沿着这个圆锥在场景的体素层级结构中进行圆锥追踪(Cone Tracing)。根据圆锥当前的大小,在对应层级的体素中查找,并累加其对着色点的光照贡献。
- 如果是Diffuse表面,则用多个(例如8个)较小的圆锥来覆盖整个半球,分别追踪并累加贡献。

优点与问题:
- 优点:光照质量很高,更接*离线渲染的效果(如光子映射)。
- 问题:计算开销比LPV大;动态场景的实时体素化本身具有挑战性;需要预处理来构建层级结构。
屏幕空间全局光照方法
上一节我们介绍了在三维空间中处理全局光照的方法,本节我们来看看屏幕空间的方法。屏幕空间方法意味着我们只能利用从当前摄像机视角渲染得到的信息(主要是直接光照结果、深度、法线等),并在此基础上通过图像后处理的方式来*似全局光照。
Screen Space Ambient Occlusion (SSAO) 🕶️


环境光遮蔽(AO)是一种对全局光照的*似,它能够增强物体间的相对位置感和立体感,主要通过接触阴影(Contact Shadow)来体现。屏幕空间环境光遮蔽(SSAO)则是在屏幕空间内实现AO的技术。
核心理论推导:
我们从渲染方程出发,并对间接光照部分做两个关键假设:
- 来自所有方向的间接光照入射辐亮度
L_i(indirect)是常数。 - 物体表面是Diffuse的,即BRDF
f_r也是常数。
基于此,着色点 p 的间接光照 L_o 计算可以简化为:
L_o(p) = (1/π) * ρ * L_i(indirect) * ∫_Ω V(p, ω_i) cosθ_i dω_i
其中,ρ 是反照率,V 是可见性函数,cosθ_i 是夹角余弦。
令 k_A(p) = (1/π) ∫_Ω V(p, ω_i) cosθ_i dω_i, 它表示在法线半球上,按 cosθ 加权的*均可见性,值在0到1之间。
那么,L_o(p) = ρ * L_i(indirect) * k_A(p)。
因此,AO的核心就是计算每个着色点的 k_A,即其周围被遮挡的程度。k_A 越小,该点越暗(遮蔽越强)。
屏幕空间实现思路:
在屏幕空间,我们无法进行真正的光线追踪来计算半球上的积分。SSAO采用了一种巧妙的*似方法:

- 采样:对于屏幕上的每个像素(着色点),在其周围三维空间的一个球体内随机采样一系列点。
- 可见性判断:对于每个采样点,将其投影回屏幕空间,查询该投影位置在深度缓冲(Depth Buffer) 中记录的深度值。
- 如果采样点的深度值大于深度缓冲中记录的值,说明该采样点位于“实际看到的”表面背后,即从着色点看向该方向可能被遮挡。记为“不可见”。
- 反之,则可能“可见”。
- 计算
k_A:统计被判断为“不可见”的采样点所占的比例,作为k_A的*似。更精确的做法会考虑采样点与法线的夹角(cosθ加权),但这需要法线信息。
存在的问题:
- 错误遮蔽(False Occlusion):这是SSAO的典型问题。如图所示,地面上的点A,其采样球可能包含石凳上的点B。将B投影到屏幕后,其深度值可能比A的深度值更小(离摄像机更*),因此A点会错误地认为B方向被遮挡,导致地面出现不该有的暗影。
- 采样噪声:由于采样点数量有限,直接得到的AO图噪声很大。通常会在计算后进行一次降噪(Denoising) 处理。
进阶方法:HBAO
当屏幕空间信息包含法线纹理(Normal G-Buffer) 时,可以实现更准确的** Horizon-Based Ambient Occlusion**。HBAO只考虑法线所指的半球空间,并且更精确地计算沿地*线的遮挡角度,能够有效减少SSAO中的错误遮蔽现象。
课程总结


本节课我们一起学*了实时全局光照的多种方法。

我们首先补充了三维空间中的两种方案:LPV通过将Radiance注入体素网格并迭代传播来实现快速的间接光照扩散;VXGI则通过体素化场景和圆锥追踪,以更大的开销换取更高质量的间接光照。
然后,我们重点深入探讨了屏幕空间方法SSAO。我们从渲染方程出发,推导出环境光遮蔽的理论基础是计算加权*均可见性 k_A。在屏幕空间中,SSAO通过深度缓冲来*似判断采样点的可见性,从而高效地模拟接触阴影,增强场景的立体感。同时,我们也分析了其存在的错误遮蔽等问题以及HBAO等改进思路。

下节课,我们将继续屏幕空间的话题,学*Screen Space Directional Occlusion (SSDO) 和 Screen Space Reflection (SSR)。

GAMES202-高质量实时渲染 - 第九讲:实时全局光照(屏幕空间方法续)🌐
在本节课中,我们将继续学*屏幕空间下的实时全局光照方法。我们将深入探讨屏幕空间方向性遮挡(SSDO)和屏幕空间反射/光线追踪(SSR)的核心原理、实现细节以及各自的优缺点。


概述

上一讲我们介绍了3D空间中的实时全局光照方法,如LPV和VXGI,并引入了屏幕空间方法,例如SSAO。本节我们将聚焦于两种更先进的屏幕空间技术:SSDO 和 SSR。它们旨在提供比SSAO更精确的间接光照效果,特别是颜色溢出(Color Bleeding)和镜面/光泽反射。

屏幕空间方向性遮挡(SSDO)🔍
SSDO是对SSAO的改进。其核心思想是:我们无需假设着色点接收来自所有方向的间接光照强度相同。相反,我们可以利用已知信息——那些被直接照亮的像素(即次级光源)——来更精确地计算间接光照贡献。
基本思想对比:SSAO vs. SSDO

以下是SSDO与SSAO和路径追踪(Path Tracing)思路的对比:
- SSAO:假设间接光照来自无限远处。在着色点周围一个小范围内,如果往某个方向看被遮挡,则认为该方向无法接收到(来自远处的)间接光照。
- SSDO/Path Tracing:假设间接光照来自场景中的其他表面(次级光源)。在着色点周围一个小范围内,如果往某个方向看被遮挡,则意味着光线击中了另一个表面,该表面可能作为次级光源提供间接光照。如果方向未被遮挡,则意味着光线逃逸到了环境(如天空盒),提供的是直接环境光照。

因此,SSAO和SSDO对于“遮挡”与“间接光照来源”的假设是相反的。一个理想的方案应结合两者:SSDO处理*处物体间的相互反射,SSAO处理远处的环境遮蔽。
SSDO算法步骤

SSDO的实现步骤与屏幕空间方法类似:

- 采样:对于着色点 P,在其法线方向的半球内随机生成一系列采样点。
- 可见性测试:判断从 P 点到每个采样点的方向是否被遮挡。这里采用与SSAO相同的*似:使用相机视角的深度缓冲来判断可见性。即,如果从相机看向采样点被遮挡,则*似认为从 P 点看向该方向也被遮挡。
- 贡献计算:
- 如果方向未被遮挡,则该方向贡献来自环境光的直接光照。
- 如果方向被遮挡,则击中点 Q 被视为一个次级光源。我们需要计算 Q 点(对应一个微小面片)对 P 点的光照贡献。假设 Q 点是漫反射表面,其出射辐射度在各个方向相同,因此可以使用 Q 点对着色器直接可见时的颜色(即直接光照结果)来计算其对 P 点的贡献。公式*似为:
贡献 = (Q点颜色) * BRDF项 * 几何项
- 求和:将所有被遮挡方向对应的次级光源贡献累加,得到 P 点的间接光照。

SSDO的优点与局限性
优点:
- 计算量相对较轻(与SSAO类似,但增加了击中点的着色计算)。
- 能够实现颜色溢出效果,即不同颜色的漫反射表面相互照亮。
- 比SSAO更符合物理的间接光照模型。


局限性:
- 屏幕空间固有缺陷:只能处理相机直接可见的几何信息(“一层壳”)。被遮挡的物体无法提供间接光照。
![]()
如图,当墙面旋转到相机不可见时,地面将无法接收到其反射光。 - 有限范围:只能处理着色点附*小范围内的间接光照,无法处理远距离的间接光照(如经典Cornell Box场景中对面墙的照亮)。
- 可见性*似误差:使用相机深度缓冲*似着色点的可见性,在几何关系复杂时可能出错(如右图所示)。
![]()

屏幕空间反射/光线追踪(SSR)💎

SSR,更准确地应称为屏幕空间光线追踪(Screen Space Ray Tracing)。它是一种在屏幕空间数据(深度、法线、颜色)构成的“壳”上进行光线求交的技术,用于实现反射等全局光照效果。


为什么需要SSR?
反射本质上是全局光照的一种表现形式。光滑表面反射出的周围景物,正是其他表面被照亮后的结果。SSR的目标是高效地模拟这种效果。

SSR核心:光线步进与层次化追踪
在屏幕空间进行光线追踪的关键是:给定一条从着色点 P 出发的光线 R,如何高效地找到它与屏幕深度缓冲所表示“壳”的第一个交点。
基础方法:线性步进
从光线起点开始,以固定步长前进,每次步进后检查当前点在屏幕空间的深度值是否大于该像素处记录的场景深度(即是否穿过了“壳”)。这种方法简单但效率低下,步长选择困难。
高效方法:层次化深度缓冲追踪
核心思想是利用预计算的层次化深度图(Hierarchical Depth Buffer),实现变步长跳跃,快速跳过不可能相交的区域。
-
构建层次深度图:类似于Mipmap,但每一层存储的是下一层对应区域深度的最小值(即离相机最*的距离)。这形成了一个保守的深度金字塔。
![]()
-
追踪算法(试探步进):
- 从最精细的层级(第0层)开始,步进一个像素。
- 如果当前光线段与当前层级的深度块未相交,则进入更粗糙的层级(如第1层),步进一个更大的块(相当于跳跃多个像素)。
- 如果在某个粗糙层级发现可能相交,则回退到更精细的层级进行更精确的检测。
- 重复此过程,直到在最精细的层级找到精确的交点,或光线超出屏幕范围。
![]()
这种“保守-激进”相结合的步进策略能显著加速求交过程。

SSR的着色计算
找到交点 Q 后,需要计算 Q 点对 P 点的光照贡献。这里有一个重要假设:被反射的物体(Q点)是漫反射的。这样,Q 点反射到相机方向的光辐射度,可以*似等于它反射到 P 点方向的光辐射度。
因此,着色计算简化为:
- 对于镜面反射,只需沿着反射方向追踪一条光线,取交点 Q 的颜色。
- 对于光泽反射,需要在BRDF波瓣内采样多条光线,分别追踪并取交点颜色的加权*均。
- 这个过程本质上是路径追踪中的一次光线弹射,因此自然地解决了以下问题:
- 距离衰减:包含在立体角积分中。
- 可见性:追踪找到的就是第一个可见点。
- 接触硬化:离反射表面越*,反射锥覆盖的源区域越小,反射越清晰。
- 镜面拉长:各向异性BRDF(如地面)会导致反射图像在特定方向上拉长。

SSR的优点与局限性
优点:
- 效果真实,能实现精确的镜面和光泽反射。
- 无需预计算,适应动态场景。
- 算法效率较高(得益于层次化追踪)。
局限性:
- 屏幕空间固有缺陷:只能反射相机可见的内容。对于遮挡部分或屏幕外的物体,反射信息会缺失或错误。
![]()
如图,手背的反射中缺失了手掌部分。 - 性能:对于粗糙的漫反射表面,需要采样大量光线,性能开销大。通常需要结合重要性采样、时空复用和滤波(如联合双边滤波)来优化。
- 边界处理:光线追踪出屏幕时会产生生硬的截断,通常需要通过距离衰减等方式进行模糊软化。
总结
本节课我们一起深入学*了两种重要的屏幕空间全局光照技术:

- SSDO:通过将遮挡点视为次级光源,计算*处物体间的漫反射互映(颜色溢出)。它比SSAO更物理,但受限于屏幕空间信息和有限作用范围。
- SSR:通过在屏幕空间“壳”上进行层次化光线追踪,实现精确的镜面/光泽反射。它能产生高质量的反射效果,但同样受限于屏幕信息,且对漫反射表面计算成本较高。



这两种技术是实时渲染中实现逼真全局光照的重要手段。它们巧妙地利用屏幕空间信息,在性能和质量之间取得了良好*衡。然而,它们的根本局限都源于“屏幕空间”这一前提——只能处理相机可见的信息。下一节课,我们将开始探讨基于物理的材质模型,这是构成逼真渲染的另一个基石。

GAMES203:三维重建和理解 - P1:Lecture 1 课程介绍与三维扫描 📚

课程概述
在本节课中,我们将学*三维重建与理解这门课程的整体框架、学*目标以及三维扫描的基本原理。课程旨在融合计算机图形学中的几何处理与计算机视觉中的三维视觉,为初学者打下扎实的基础。
课程背景与动机
本课程使用中文授课,但幻灯片和学术术语将使用英文,以确保精确性。开设这门课的主要动机在于连接两个紧密相关的领域:计算机图形学中的几何处理和计算机视觉中的三维视觉。
几何处理领域,也称为几何建模,主要研究三维物体的表示、重建与理解。三维视觉领域则专注于从图像中恢复和理解三维结构。*年来,这两个领域融合得越来越紧密。本课程希望提供一个共同的视角,将这两个领域打通。
课程将侧重于从逆向工程和视觉的角度来看待几何处理,有时也称为扫描处理。同时,三维视觉中对物体的理解也离不开对物体表示的研究。本课程的目标正是架起这两个领域之间的桥梁。
讲师与课程安排
讲师目前是助理教授。课程时间为北京时间每周五上午10点。如有学术问题,可以通过电子邮件联系讲师。
课程将包含两次作业和一些可选的大作业项目。课程会提供一些潜在的研究方向供选择。

几何处理简介



几何处理领域已存在至少30-40年,其核心是用计算的方法来研究物理对象。这个过程的第一步,也是至关重要的一步,就是重建。
我们生活中有许多物理对象,我们需要通过硬件扫描将其转化为三维模型。得到模型后,再进行各种处理和分析,例如研究如何更好地表示物体、如何进行参数化、如何贴纹理,以及如何将二维图像转换为三维物体等。
简而言之,几何处理就是在计算机中为物理世界创建一个虚拟副本。在这个虚拟世界中,我们可以对物体进行计算分析,例如识别物体的组成部分、寻找不同物体间的对应关系,或者组合物体以创建新物体。
处理和分析完成后,我们还可以通过三维打印等技术,将虚拟世界中的编辑结果返回到物理世界。因此,几何处理的流程主要包含两个部分:重建 与 处理/分析。
三维视觉简介
三维视觉领域的范围更广。传统三维视觉的核心问题是:从图像中恢复其隐含的三维结构。这是一个基础且终极的问题。
其中涉及的关键问题包括运动恢复结构和多视角立体视觉,这是三维重建中两个最重要的问题。当然,三维视觉还包括其他问题,如姿态估计。
传统三维视觉的重建框架与几何处理中的重建框架有所不同,但存在一些共同的问题,例如配准。
*年来,随着海量三维模型数据集的涌现(如ImageNet推动了二维视觉的发展),三维视觉的研究重点逐渐从单纯的重建转向了理解与合成。这与几何处理中的处理与分析产生了共性。
现在研究的问题包括分类、分割、检测等。这就引出一个问题:三维视觉与二维视觉的区别究竟在哪里?
从机器学*角度看,无论是处理图片还是三维物体,我们都需要将数据表示为向量(如 x 和 y),然后学*它们之间的变换关系。关键在于,三维数据有多种不同的表示方式,例如:
- 体素网格
- 三角网格
- 点云
- 多视角图像集合
- 场景图
这些表示方式与几何处理领域紧密相关。本课程的一个重要部分就是探讨这些不同的数据表示及其相互转换。
课程内容结构
本课程将分为三个主要部分:
第一部分:重建
我们将介绍两条技术路线。
- 第一条路线源于几何处理:从硬件扫描开始,讲解三维扫描的原理,然后是多视角扫描数据的配准,最后是曲面重建。
- 第二条路线源于三维视觉:从图像出发,讲解运动恢复结构(得到相机姿态和稀疏点云)和多视角立体视觉(得到稠密重建)。我们还会探讨同步定位与地图构建中与三维重建相关的部分,以及多物体场景下的分割问题。
第二部分:表示
我们将从数据表示的角度,系统性地介绍各种三维表示方法,如网格、点云、隐式曲面等。重点探讨不同表示之间的转换问题,例如:
- 如何将隐式曲面(如公式
x² + y² + z² = 1表示的球面)转换为三角网格。 - 如何将扫描得到的点云转换为连续的曲面。
第三部分:理解
我们将讲解分类、分割等理解任务,特别是与深度学*相关的方法。课程将兼顾经典算法与最新进展,强调对基础原理的透彻理解。
基础的重要性
本课程强调基本功。在深度学*时代,许多工作缺乏扎实的数学基础和充分的实验验证。本课程旨在弥补这一不足,推荐阅读的文献也包含许多经典论文。
真正解决复杂问题(如机器人、自动驾驶)时,最终都会回归到基础:如何表示、如何重建、如何分类。这些基础模块是构建复杂系统的基石。
所需的数学基础包括线性代数、射影几何,以及一些拓扑学知识。课程会尽量讲解清楚。
数值优化
优化是三维重建和理解中的核心工具。无论是从图像恢复三维结构,还是进行模型拟合,本质上都是在求解一个优化问题。
一个优化问题通常包含一个目标函数和需要优化的变量。我们的目标是找到使目标函数值最小(或最大)的变量值。例如,最小二乘法就是一个优化问题。
优化问题种类繁多,例如:
- 连续优化 vs 离散优化(如图割算法)
- 凸优化 vs 非凸优化
- 确定性优化 vs 随机优化(如深度学*中的随机梯度下降)
我们将学*一些常用的优化算法。理解优化对于从事几何处理或三维视觉研究至关重要。
作业与项目

课程有两个主要作业:
- 深度重建:实现一个三维重建流程。
- 基元提取:从数据中提取基本的几何形状。


此外,课程还会提供一些可选的大项目方向,例如:
- 从互联网产品图片中进行三维重建。
- 从单张图片进行三维理解与重建。
- 设计适用于新型三维表示(如三角网格)的神经网络。
- 融合不同传感器(激光雷达、相机)的数据进行重建。
- 研究在深度学*中如何强制执行几何约束。
- 研究模型预测结果的不确定性度量。
三维扫描技术介绍

接下来,我们进入本节课的第二个主题:三维扫描。这部分内容偏向硬件和应用,大家了解基本原理即可。
扫描是三维重建流程的第一步:如何获取物理对象表面的三维点云。由于单次扫描只能覆盖一个视角,我们需要将多个视角的点云配准在一起,最终通过曲面重建得到一个完整的模型。
扫描技术主要分为两类:
- 接触式扫描:例如使用机械臂触碰物体表面来记录点的三维坐标。这种方法精度高但速度慢,早期计算机模型曾用此方法。
- 非接触式扫描(光学扫描):这是当前的主流,又可分为:
- 被动式:仅利用采集到的图像(如双目立体视觉)来计算深度信息。本课程后续在运动恢复结构部分会涉及。
- 主动式:主动向物体投射特定模式的光,以辅助重建。本节重点介绍此类。
主动式扫描原理:三角测量
主动式扫描的核心思想是三角测量。其原理非常简单:已知两条射线的方向,且知道它们相交于空间中的一点,就可以计算出该点的位置。
在扫描中,我们制造这样两条射线:
- 第一条射线来自一个可控的光源(如投影仪),它将一个特定的光模式投射到物体表面。我们知道这条投射光线的方向。
- 第二条射线来自一个相机,它从另一个角度拍摄物体表面,并识别出光源投射的模式在图像中的位置。通过相机模型,我们可以知道这条观察光线的方向。

由于光源和相机之间的相对位置(基线 b)是固定的,根据简单的几何关系(相似三角形原理),我们就可以计算出物体表面点的深度 z。
公式可以简化为:z ∝ (f * b) / (x - x'),其中 f 是焦距,(x - x') 是投影点在两个视角图像中的视差。



模式设计的关键
整个流程中最关键也最具挑战性的部分是:如何让相机自动、唯一地识别出图像中每个像素点对应的是光源投射的哪一条光线(即哪个模式元素)?

这引出了模式设计的问题。设计的目标是让每个投射模式元素都有一个独一无二的“ID”,使得相机在捕获的图像中能将其准确识别出来。常见的模式设计思路有:
- 彩色编码:为每条投射光线赋予不同的颜色。只要物体表面颜色不失真,就能直接识别。
- 时序编码:投射一系列黑白二值模式(如格雷码)。同一个空间点在不同时间被不同序列的模式照射,从而形成一个独特的二进制时间序列编码作为其ID。
- 空间连续性:投射规则的点阵或网格。利用物体表面连续性的假设,通过相邻点的关系来推断每个点的ID。
这些方法各有优缺点,例如彩色编码受物体本色影响,时序编码要求场景静止,空间连续性在物体边界不连续处会失效。目前一些商用扫描仪(如结构光扫描仪)融合了多种技术来解决这些问题。

课程总结


本节课我们一起学*了以下内容:
- 课程框架:了解了本课程融合几何处理与三维视觉的目标,以及重建、表示、理解三大模块的结构。
- 基础重要性:强调了扎实的数学、优化和算法基础对于前沿研究的关键作用。
- 三维扫描原理:介绍了主动式三维扫描的基本思想——三角测量,并讲解了其核心挑战“模式设计”的几种思路。


下节课我们将深入讲解三维重建流程中的下一个关键步骤:多视角点云配准,重点介绍迭代最*点算法及其变种。




GAMES203:三维重建和理解 - P10:几何深度学* I 🧠
在本节课中,我们将探讨几何深度学*的核心思想。我们将从计算机视觉与几何处理两个不同学派的视角出发,理解如何在非欧几里得空间(如三维网格或图)上定义卷积等操作。课程将涵盖图论、流形上的拉普拉斯算子、傅里叶分析以及如何将这些数学工具推广到三维几何数据上,从而为深度学*提供理论基础。
学派之争与核心问题 🤔
上一节我们介绍了基于计算机视觉的三维重建方法。本节中,我们来看看几何处理学派的观点。
三维数据处理存在两个主要学派。计算机视觉学派倾向于从数据和学*出发,解决具体问题,例如基于图像的三维重建。几何处理学派则强调连续数学的观点,如微分几何和流形处理,其方法更具原理性。
从长远看,两个学派各有优势。几何深度学*旨在探索如何在三维形状上进行深度学*。这涉及到三维形状的表示问题,并与谱图理论等数学领域紧密相关。
三维数据最大的挑战在于其本质是非欧几里得空间。在图像(欧式空间)上定义的卷积具有*移不变性等优秀性质,但在曲面或图上定义类似操作则困难得多。
在视觉计算中,三维数据主要有两种表示形式:
- 流形:如三维网格表面。
- 图:表示点与点之间的连接关系。
核心问题是如何在这些非欧结构上快速定义卷积等运算结构。
理论基础:图与流形 📐
为了在图上定义运算,我们首先需要为其赋予代数结构。
图论基础
图由顶点和带权重的边构成。我们可以在图上定义函数,并计算两个函数的内积,从而引入距离概念。
图上的*滑性能量可以定义为相邻顶点函数值之差的*方和。最小化这个能量,就得到了图的拉普拉斯矩阵 L。
L 是一个对称矩阵,其最小特征值为0,对应的特征函数是常数函数。第二小的特征值反映了图的连通性,称为代数连通度。
黎曼流形简介
流形是连续的曲面。在每个点上存在一个切空间,可以定义内积和曲率。我们可以将连续的概念离散化到网格上。
流形上的拉普拉斯-贝尔特拉米算子 Δ 是一个关键概念。它是一个作用在函数上的算子,定义为梯度的散度:Δ f = - div(∇ f)。在离散网格上,我们有对应的离散拉普拉斯算子。
离散拉普拉斯矩阵 L 可以看作是连续拉普拉斯算子的离散*似。当网格足够精细时,两者会收敛。
从傅里叶分析到几何卷积 🔄
上一节我们建立了图与流形上的拉普拉斯算子。本节中我们来看看它如何与卷积联系起来。
傅里叶基与拉普拉斯算子
在一维信号处理中,傅里叶变换将函数分解为不同频率的正弦和余弦波(基函数)的线性组合。函数 f(x) 可以表示为:
f(x) = Σ (a_k cos(kx) + b_k sin(kx))
其中系数 a_k, b_k 表示该频率成分的强度。
一个关键发现是:拉普拉斯算子的特征函数正是傅里叶基。对于一维拉普拉斯算子 d²/dx²,有:
(d²/dx²) e^(ikx) = -k² e^(ikx)
即,傅里叶基 e^(ikx) 是拉普拉斯算子的特征函数,特征值为 -k²。
这个观点至关重要:我们可以将流形上的拉普拉斯算子的特征函数,视为该流形上的“傅里叶基”。任何定义在流形上的函数,都可以用这组基展开。
热扩散与卷积核
热扩散方程描述了热量在曲面上的传播过程:
∂f/∂t = Δ f
其解可以写为 f(t) = e^(tΔ) f(0),其中 e^(tΔ) 是一个算子。
利用拉普拉斯特征基展开,这个解对应于一个卷积操作:f(x,t) = ∫ h_t(x, y) f(y,0) dy。这里的核函数 h_t(x, y) 就是热核,它衡量了y点的初始值对t时刻x点的影响,距离越*影响越大。
定义几何卷积
在欧式空间中,两个函数的卷积定义为:
(f * g)(x) = ∫ f(y) g(x-y) dy
它具有*移不变性,且在傅里叶域中变为乘积:F(f * g) = F(f) · F(g)。
在流形上,由于没有全局的*移概念,我们借鉴傅里叶域的视角来定义谱卷积:
- 用流形拉普拉斯算子的特征基对输入函数进行傅里叶变换(即计算其在该基下的系数)。
- 在谱域(系数域)中与一个滤波器(一组可学*的系数)进行逐元素相乘。
- 将结果用特征基进行逆变换,得到空间域的卷积结果。
这种卷积不是*移不变的,但它利用了流形的几何结构。
几何深度学*架构 🧱
有了谱卷积的定义,我们就可以构建用于三维几何数据的深度学*网络。
早期的图神经网络可以看作一种空间方法:每个节点的输出是其邻居节点特征的加权和。这可以写成:
F_out = σ( L F_in W )
其中 F_in 是输入特征,W 是可学*权重矩阵,L 是拉普拉斯矩阵或其变体,σ 是非线性激活函数。
然而,直接使用谱卷积面临一个挑战:基函数的不一致性。不同形状的拉普拉斯特征基是不同的。用未对齐的基进行学*,相当于在错误的坐标系下学*滤波器。
解决方案是引入变换器模块。网络在学*滤波器的同时,也学*如何将不同形状的谱域基进行对齐或校准。这类似于在空间域中对点云进行变换,但操作对象是谱域的基。
以下是一些基于谱卷积的网络的特点:
- 结果更*滑:由于拉普拉斯基函数本身是*滑的,预测结果自然具有正则化效果。
- 捕捉全局结构:低频率的特征基对应形状的宏观结构。
- 需要处理基对齐:这是谱方法的核心问题,也是当前研究的重点。
总结 📝
本节课我们一起学*了几何深度学*的基础知识。
我们首先比较了计算机视觉与几何处理两种研究三维问题的学派。然后,我们深入探讨了其数学基础:在图和流形上定义拉普拉斯算子,并理解其特征函数构成了该几何结构上的“傅里叶基”。通过热扩散方程,我们引出了几何卷积核的概念。最后,我们利用谱域视角定义了流形上的卷积,并探讨了如何将其用于构建深度学*网络,以及面临的基函数对齐等挑战。

核心在于,数学抽象使我们能够将欧式空间(如图像)上成功的概念(如卷积),通过拉普拉斯算子这一桥梁,推广到非欧的几何数据上。下节课我们将继续深入,探讨更多几何深度学*的模型与方法。

GAMES203:三维重建和理解 - P11:几何深度学* II 🧠
在本节课中,我们将继续探讨几何深度学*的核心概念,特别是如何在非欧几里得空间(如曲面和图)上定义卷积操作。我们将回顾拉普拉斯算子的应用,并深入讲解在流形上进行卷积、处理有向图以及解决方向性问题的多种方法。
回顾:曲面上的卷积与反卷积
上一节我们介绍了如何在曲面上对谱域进行卷积和反卷积操作。其核心思想是利用拉普拉斯算子的特征函数来定义谱域上的变换。
有了这个基础,我们可以进行诸如形状自动编码等任务。通过傅里叶变换,我们得到的是一个在谱域上的卷积操作。
核心公式:曲面上的卷积可以通过拉普拉斯-贝尔特拉米算子的特征函数来实现。对于一个函数 f,其傅里叶系数为:
\hat{f}_k = \langle f, \phi_k \rangle
其中 \phi_k 是拉普拉斯算子的第 k 个特征函数。
融合不同基底
在图像处理中,网格是固定的,因此不存在基底不匹配的问题。然而,对于不同的三维形状,它们的拉普拉斯特征基底各不相同。
为了解决这个问题,我们引入一个公共的形状空间。具体做法是:给定一个形状,首先将其映射(或“传送”)到这个公共空间,然后在公共的谱域上进行卷积操作。这个过程可以看作是一种归一化处理,使得不同形状的特征能够对齐。
这种方法能够实现诸如形状分类、分割等任务。你会发现,预测结果与真实形状在某些方面会非常相似。
在有向图上的推广
我们之前讨论的拉普拉斯矩阵主要针对无向图。但在许多实际应用中,例如社交网络或引用网络,图是有向的(即边 A->B 存在,但 B->A 可能不存在)。
在有向图上,邻接矩阵不再是对称矩阵。如果你对这部分感兴趣,我强烈建议你研究如何在有向图上重新定义类似于谱域卷积的框架。这是一个开放的研究方向。
一种方法是利用“图模体”的思想。图模体是指图中反复出现的小型连接模式。我们可以为每种模体定义一个邻接矩阵 M_m,它编码了节点之间通过该模体相连的关系。
然后,我们可以为每个模体 m 定义一个拉普拉斯矩阵:
L_m = D_m - M_m
其中 D_m 是 M_m 的度矩阵。
最终,整体的图卷积可以通过这些模体拉普拉斯的线性组合来定义:
L = \sum_m \alpha_m L_m
其中 \alpha_m 是可学*的系数。
这种思想的核心是将一个有向图通过模体分析,转化为多个(无向的)子结构进行处理,从而应用谱方法。


流形上的空间域卷积
现在,让我们看看如何将图像上经典的空间域卷积*移到流形(如曲面)上。在图像上,卷积是在规则的像素网格上滑动一个固定核。在流形上,每个点的局部邻域几何都不相同。
关键在于定义局部坐标系和方向。我们需要为流形上的每个点 x 建立一个局部坐标系 (u, v)。
卷积操作 在点 x 处可以定义为对邻域内点的加权积分:
(f * g)(x) = \int_{M} f(y) g( \text{log}_x(y) ) dy
其中 \text{log}_x(y) 是 y 在 x 点切空间上的对数映射(定义了局部坐标),g 是定义在切空间上的核函数(例如高斯核)。
以下是实现流形卷积的关键步骤:
- 建立局部坐标系:为每个点定义切空间和一组正交基
(e1, e2),这决定了局部方向。 - 定义核函数:在切空间上定义一个函数(如高斯函数),作为卷积核。
- 映射与积分:将邻域内的点映射到切空间,用核函数进行加权,然后积分。
主要的挑战在于如何一致地定义每个点的局部方向(即解决“方向性问题”)。目前有两种主流思路:
- 定义全局方向场:在曲面上定义一个连续的方向场(如主曲率方向),为每个点提供一致的方向。
- 使用旋转等变网络:设计网络结构,使其输出对于输入方向的旋转具有等变性。例如,
Spherical CNN或Tensor Field Network通过在多个方向上进行卷积并取最大响应,来避免对单一方向的依赖。
这些方法各有优劣,方向性问题目前仍未完全解决,是一个活跃的研究领域。
与各项异性扩散的联系
流形上的卷积操作与各项异性扩散过程密切相关。经典的扩散方程是各项同性的,意味着所有方向的*滑程度相同。
而各项异性扩散允许扩散速率依赖于位置和方向,这可以通过一个扩散张量 A(x) 来控制。其方程可以写作:
\frac{\partial f}{\partial t} = \text{div} ( A(x) \nabla f )
这种各项异性的*滑效果,可以通过设计特定的、方向相关的卷积核来实现。换句话说,我们可以将某些卷积操作理解为一种智能的、保持特征的*滑过程。
在图上的空间域卷积类比
将流形上空间域卷积的思想迁移到图上,核心挑战同样是如何定义“局部坐标”。在图上,节点没有天然的几何坐标。
一种思路是为图中的每个节点学*一个特征向量作为其“坐标”。然后,可以定义一个基于节点“坐标”之间距离的高斯核,来进行加权聚合。这类似于图注意力网络的思想。
另一种更结构化的方法是利用图的对偶图或线图。例如,线图的节点是原图的边,这样可以更好地捕捉边与边之间的关系,在某些任务(如分子图生成)中非常有效。
简单来说,如果你的节点编码能更精细地反映其局部结构信息,那么在此基础上定义的卷积操作效果会更好。
可参数化曲面与卷积
最后,我们简要讨论可参数化曲面(如一张映射到*面圆盘的曲面)上的卷积。在这种情况下,输入是三维曲面,但我们可以将其参数化到二维域上。
这引出了一个重要问题:参数化的选择会极大地影响后续处理。不同的切割和展*方式会导致不同的二维参数域,从而影响卷积核的定义和效果。
因此,我们需要处理参数化带来的扭曲,并研究如何使网络对此类扭曲具有不变性或鲁棒性。这一领域仍有大量工作可做,我们将在后续课程中进一步探讨。
总结
本节课我们一起学*了几何深度学*的进阶内容。我们回顾了在曲面谱域进行卷积的方法,并深入探讨了:
- 处理基底不匹配:通过将形状映射到公共空间来解决。
- 推广到有向图:利用图模体来定义适用于有向结构的拉普拉斯算子。
- 定义流形上的空间卷积:核心在于建立局部坐标系和处理方向性问题,这是与图像卷积的本质区别。
- 与各项异性扩散的联系:卷积可以看作是一种特征保持的*滑过程。
- 在图上的类比应用:通过为节点赋予特征坐标或利用对偶图来定义卷积。

总而言之,在非欧几里得空间上定义卷积,其核心挑战是如何一致地定义每个点的邻域和方向。虽然已有多种方法,但这仍然是一个开放且富有挑战性的研究前沿。

GAMES203:三维重建和理解 - P12:几何深度学* III 🧠
在本节课中,我们将探讨几何深度学*的核心思想及其在三维形状处理中的应用。我们将重点关注如何将深度学*技术应用于非欧几里得数据(如网格和点云),并讨论当前面临的挑战与未来的研究方向。
课程概述
上节课我们介绍了参数化映射的方法,其核心思想是将三维物体映射到二维*面进行参数化,从而获得可用于深度学*的结构化表示。本节中,我们将深入探讨几何深度学*的本质,并分析其在解决三维形状对应、重建等任务时的优势与局限。
参数化方法的挑战与改进
参数化方法,如共形映射,为三维表面提供了二维的参数域,使得标准的卷积神经网络(CNN)得以应用。其核心公式可表示为寻找一个映射 f: S -> Ω,其中 S 是三维表面,Ω 是二维参数域。
然而,参数化方法存在两个主要问题:
- 对形状变化的敏感性:形状稍有改变,参数化结果可能失效。
- 嵌入失真:参数化并非等距映射,存在缩放因子,导致卷积运算定义困难。
此外,对于拓扑结构非圆盘(如具有多个洞或奇异点的表面),直接参数化面临挑战。常见的解决方案包括将表面切割开,或使用共形思想进行处理。
以下是文献中一些有意义的改进思路:
-
多重参数化集成:为解决对特定基准点选择的依赖,可以选取多组不同的点对表面进行切割和参数化,生成多个参数域,然后集成(Ensemble)这些结果。当形状变化时,虽然单个参数化结果可能不同,但集成的结果能保持稳定。
-
应用扩展:几何深度学*在图形学和视觉中有广泛应用,例如:
- 纹理传输与风格化。
- 基于参数化的形状变形。
- 非刚性形状的对应关系计算。
几何深度学*的本质与架构
几何深度学*的核心在于对流形(Manifold) 的表示学*。无论是点云网络(PointNet)还是图卷积网络(GCN),其本质都是在某种数据结构上定义卷积操作,以捕捉局部或全局的几何特征。
在图形学领域,许多问题(如合成、分析、表面编辑)归根结底都离不开两个核心思想:
- 流形理论:将数据视为在高维空间中的低维流形。
- 逼*理论:例如使用基函数(如球谐函数)进行局部逼*。
当前的研究也探索了不同的邻域构建与信息聚合方式:
- 邻域定义:在网格上,可以定义基于测地距离或顶点连接的邻域。
- 图构建:在点云上,图卷积需要定义哪些点之间存在交互(Interaction),通常基于空间邻*性、特征相似性或法向一致性来构建图(Graph)。
形状对应(Correspondence)的挑战
形状对应是三维处理中的关键问题,即找到两个不同形状上点的映射关系。基于学*局部描述符(Descriptor)的方法是主流。
传统方法 vs. 学*方法:
- 传统手工设计的描述符(如SHOT、WKS)在某些区域区分度不足或不具备等变性。
- 学*方法(如学*每个点上的特征)可以在不需要精确对应标签的情况下,学*出既具有判别性又具有一定等变性的描述符。
然而,当前基于描述符匹配的方法存在一个显著问题:结果不够*滑(Smooth)。匹配结果可能出现跳跃,缺乏空间连续性约束。
一个潜在的解决方案是引入函数映射(Functional Map)框架作为正则化。其核心思想是:
- 在两个形状上分别计算描述符。
- 不直接用描述符匹配点,而是将匹配问题在形状的基函数(如拉普拉斯-贝尔特拉米特征函数)空间下进行。
- 加入正则项,要求对应关系映射到的函数是低频的(即*滑的)。
这种方法结合了描述符的判别能力和函数映射对结构*滑性的约束,能显著提升对应关系的质量。其优化目标可以形式化为:
min_F ||Φ_X * D_X - F * Φ_Y * D_Y||^2 + λ * R(F)
其中,Φ 是基函数矩阵,D 是描述符,F 是待求的函数映射,R(F) 是*滑性正则项。
尽管有改进,但如何在复杂形状上实现鲁棒、*滑且精确的自动对应,仍然是一个开放的挑战。
自编码器与形状生成
将图像上的自编码器(Auto-Encoder)思想扩展到三维形状是一个自然的研究方向。
在网格上的实现:可以通过在网格的不同层次上进行卷积和下采样(编码),再通过反卷积和上采样(解码)来重建形状。这可以用于形状补全、超分辨率等任务。
实验表明,拥有特殊结构的网络能够较好地重建形状的整体拓扑和细节,而简单的全连接网络则只能保证大致轮廓。
人体形状处理的重要性与挑战
人体形状处理在计算机视觉和图形学中具有重要应用,如虚拟试衣、动画制作、机器人模仿学*等。
当前的研究多集中于简单、裸模状态的人体。未来的挑战在于复杂场景下的处理:
- 穿着厚重或宽松的衣物。
- 与物体进行交互。
- 在动态、拥挤的环境中进行重建和理解。
解决这些问题对于推动模仿学*、人机交互等领域的发展至关重要。
课程总结
本节课我们一起探讨了几何深度学*的核心思想。我们回顾了参数化方法的局限,分析了深度学*在三维形状对应、重建等任务中的应用与挑战,并展望了人体形状处理等重要方向。
我反复强调,几何深度学*提供了一个强大的思路,但要真正深入这个领域,需要打好基础。建议回顾之前课程中关于点云处理、网格处理的经典文献,理解流形表示和逼*理论等根本思想。三维视觉问题非常复杂,从研究到实际应用仍有距离,但正是这些挑战带来了广阔的研究空间。


下节课我将分享一些自己在该领域的研究工作,并对整个课程进行总结。

GAMES203:三维重建和理解 - P13:Lecture 13 混合三维表示 🧩

在本节课中,我们将探讨三维机器学*中的一个核心问题:混合三维表示。我们将了解为什么单一的表示形式存在局限性,以及如何通过结合多种表示形式的优势来构建更强大、更鲁棒的模型。

概述
三维视觉领域最初专注于重建问题。随着数据量的增长和深度学*的发展,我们进入了三维机器学*时代。早期,三维学*模仿了二维学*的范式。如今,三维领域已发展出自己的理论架构,例如基于点云、网格和隐式表面的深度学*。
然而,任何一种三维表示形式都有其优点和缺点。混合表示的核心思想是,结合多种表示形式,利用它们各自的优势,弥补单一表示的不足,从而提升模型的性能和泛化能力。本节课将从多个角度解析这一思想,并通过具体的研究工作展示其应用。
三维表示的多样性
在深入混合表示之前,我们需要回顾三维数据的多种表示形式。在计算机图形学和视觉中,常见的表示包括:
- 点云:一组三维空间中的点。
- 网格:由顶点、边和面定义的表面。
- 隐式表面:用一个函数
f(x, y, z) = 0来定义表面,内部为负,外部为正。 - 体素:将空间划分为规则的三维网格。
- 多视图:从多个二维图像中推断三维结构。
每种表示都催生了相应的深度学*架构(如PointNet处理点云,CNN处理多视图)。关键在于,没有一种表示能完美适用于所有任务。
混合表示的思想渊源
混合表示的思想并非深度学*独有。在计算机图形学和工业设计中,它早已是常见做法。
上一节我们介绍了三维表示的多样性,本节中我们来看看混合表示在传统领域中的应用。例如,在碰撞检测中,直接计算两个复杂网格的所有三角面交集效率极低。通常的解决方案是使用一种空间数据结构(如包围盒层次结构)作为混合表示:首先用简单的包围盒快速剔除不可能碰撞的部分,再对剩余部分进行精确的网格碰撞检测。
另一个例子是物理模拟或形状变形。有时在隐式表示下进行计算更为方便(例如,两个隐式表面的融合可以通过函数操作简单完成),然后再将结果转换回显式的网格表示进行渲染。这体现了在不同任务间切换最优表示的思想。
混合表示的理论视角
从机器学*的理论角度看,混合表示与模型复杂度和泛化能力密切相关。
在传统的模型设计中,我们通常通过增加单一模型的参数(即模型宽度或深度)来提升性能,但这可能导致过拟合,即模型在训练集上表现好,在未见数据上表现差,泛化能力下降。
混合表示提供了一种不同的思路:我们不追求单一模型的极端复杂化,而是组合多个相对简单但基于不同表示的模型。这可以看作是一种集成学*。理论上,在总参数量相同的情况下,集成多个小模型通常比使用一个大模型具有更好的泛化能力。
更重要的是,不同的表示形式可能捕捉数据中不同方面的特征。如果一个表示在某个数据子集上失效,另一种表示可能恰好能弥补。这种表示的多样性使得混合模型对数据分布的变化更为鲁棒。
研究案例一:混合表示用于扫描对齐
我们的第一个案例是将混合表示应用于三维扫描对齐任务。给定两个有重叠部分但初始位置未知的扫描片段,目标是估计它们之间的相对位姿。
传统方法依赖于特征匹配,但在特征稀少或重叠度很低时容易失败。一种深度学*方法是对不完整的扫描做补全,然后在更完整的形状上进行对齐。
然而,补全的质量高度依赖于所使用的表示形式。例如:
- 基于体素的补全在孔洞附*效果好,但远离边界时细节模糊。
- 基于点云或多视图的补全可能在不同情况下有不同表现。
以下是我们的解决方案思路:
我们利用多种表示形式分别对输入扫描进行补全,生成多个补全版本。然后,我们学*一个优化器,针对当前这一对特定的输入扫描,自动选择或融合来自不同表示的补全结果,以得到最适合做位姿估计的中间表示。公式上,这可以表示为一个选择权重 w_i 的优化问题,其中 w_i 对应于第 i 种表示的重要性。

实验表明,这种混合表示方法显著提高了在低重叠度情况下的扫描对齐鲁棒性和精度。
研究案例二:混合基元用于三维目标检测
第二个案例是在三维目标检测中引入混合几何基元。常规的检测器通常直接回归一个3D边界框的参数(中心点、尺寸、朝向)。
但直接回归一个完整的框(7个参数)在数据不完整时比较困难。我们换一种思路:一个物体可以由多种更简单的几何属性来定义。
以下是我们的方法步骤:
- 预测多种基元:对于输入的点云,我们的网络不仅预测边界框,还预测一系列其他基元,例如:
- 物体表面的中心点(8个角点)。
- 每个面的中心点(6个面中心)。
- 每条边的中心点(12个边中心)。
- 初始框生成:利用预测出的这些基元点(例如,只要检测到两个对角点,就能确定一个框),我们可以生成多个初始的边界框假设。
- 融合与优化:将这些来自不同基元的初始框预测进行融合与*均,并通过一个优化模块进行微调,最终得到更准确、更稳定的检测框。
分析发现,不同类别的物体(如汽车、椅子),其“好”的基元是不同的。例如,汽车用角点基元检测效果好,而椅子可能用面中心更稳定。混合多种基元允许模型自适应地利用最有效的线索。
研究案例三:混合网络用于场景合成
第三个案例是关于室内场景合成。目标是根据一些条件(如房间类型、大小)生成合理的物体布局。
传统方法可能使用自回归模型(如Transformer)逐个生成物体。我们探索了一种混合网络架构:
- 属性预测网络:第一个网络专注于预测场景中物体的属性,如类别、尺寸、颜色。
- 布局生成网络:第二个网络接收这些属性,并专注于生成物体的布局,即它们的空间位置和朝向。
- 混合与生成:将两个网络的输出(属性潜在编码和布局潜在编码)结合起来,共同生成最终的场景。
这种将“属性预测”和“布局生成”解耦的混合表示,使得模型能更好地处理这两类不同性质的任务,生成的场景在多样性和合理性上都有提升。
研究案例四:混合监督用于部件分解
最后一个案例是三维形状的部件分解,即将一个复杂物体分解为简单的几何基元(如立方体、圆柱体)。
一种深度学*方法是为每个点预测它属于哪个基元。我们引入了混合监督信号来提升效果:
- 语义特征:网络预测每个点的基元类别标签。
- 几何特征:我们额外引入一种监督,让网络也预测每个点所在基元的参数(如对于一个圆柱体,预测其轴心和半径)。
- 图构建与优化:利用预测的几何参数,我们可以计算点之间的几何一致性(例如,两个点如果被预测为属于同一个圆柱体,它们的几何参数应该兼容)。基于此构建一个图,进行图聚类或优化,从而得到更准确的部件分割结果。
这种将语义分割与几何参数回归相结合的混合监督方法,比单独使用任何一种都能获得更精确的分解结果。
未来方向与总结
本节课我们一起学*了混合三维表示的核心思想与应用。它强调通过结合多种数据表示形式的优势,来构建更强大、更鲁棒的机器学*模型。
未来的研究方向有很多:
- 自动化表示学*:如何让模型自动学*或生成最优的混合表示,而非人工设计。
- 表示间的可转换性:研究不同表示之间的转换关系,并利用这种关系进行联合优化。
- 理论分析:建立更完善的理论框架,解释为何以及何时混合表示会优于单一表示,并指导混合策略的设计。


总而言之,混合表示是一个具有巨大潜力的方向。它鼓励我们拓宽思路,不局限于单一的模型或表示,而是通过巧妙的组合来释放三维机器学*的更大能量。在下一节课中,我们将对整个课程进行总结,并展望三维重建与理解领域的未来重要方向。

GAMES203:三维重建和理解 - P14:课程总结与展望 🎓
在本节课中,我们将对GAMES203课程的核心内容进行回顾与总结,并探讨三维视觉与图形学领域的未来发展方向。课程主要分为三维重建、三维表示与理解三大板块,我们将逐一梳理并回答同学们提出的相关问题。
三维重建 🔍
上一节我们回顾了课程的整体结构,本节中我们来看看三维重建部分。三维重建旨在从图像或其他传感器数据中恢复物体的三维几何与外观。
重建的量化标准与应用场景
三维重建的评估标准高度依赖于其具体应用目标。以下是几种典型场景及其对重建精度的不同要求:
- 高精度要求场景:在医学(如牙齿重建用于手术规划、骨折复位)或工业检测(如桥梁损伤评估)中,重建的物理精度至关重要。不仅需要输出模型,还需评估其不确定性(如置信度)。
- 中等精度要求场景:在无人驾驶领域构建高精地图时,也要求重建尽量精确。
- 感知质量优先场景:在虚拟现实(VR)或增强现实(AR)等娱乐应用中,目标是为用户提供逼真的视觉体验。此时,重建的感知质量(如能否支持新视角合成)比绝对的物理精度更重要。
深度学*与三维重建的结合
传统重建方法(如激光扫描)在工业界已很成熟。当前研究热点更多集中于被动式重建(如从多张图像)与深度学*的结合。以下是几个关键研究方向:
- 单视图深度估计与补全:从稀疏或无重叠视图进行重建时,传统多视图几何方法失效。此时可借助单视图深度估计先得到初始几何,再进行融合。核心挑战在于如何*衡图像间对应关系匹配与单视图深度先验的利用。
- 对应关系匹配:传统方法使用SIFT等特征,现在则探索用深度学*(如光流网络)寻找更鲁棒的对应关系。
- 深度先验:单视图深度估计虽整体较准,但对遮挡区域(Occlusion)处理不佳。如何结合多视图信息优化是难点。
- 外观与材质重建:超越几何,重建物体的颜色、材质乃至物理属性,对于实现照片级真实感渲染至关重要。这需要将渲染中的光照模型(如反射、阴影、高光)与重建过程结合,属于尚未很好解决的系统工程。
- 动态重建:即四维(4D)重建,对运动物体进行时序上的三维重建。核心挑战在于如何建模运动并解决时域上的对应关系问题。目前研究多在几何层面,结合物理规律理解运动的工作还不多。
几何处理中深度学*的机遇


传统几何处理已有成熟软件(如MeshLab)。深度学*要在该领域产生颠覆性影响,需带来质的飞跃。可能的突破方向包括:

- 大规模场景重建:使用极少的测量数据和图像,鲁棒地重建整个建筑或城市级别的场景。
- 便捷的重建工具:让用户仅通过拍摄几张房间照片就能快速生成可靠的三维模型。
- 智能几何编辑:将学*能力嵌入处理流程,使编辑工具更智能。
三维表示 🧩
在了解了重建的目标与挑战后,我们转向三维数据的表示方法。良好的表示是进行高效处理和深度理解的基础。
混合表示与可学*表示
- 混合表示(Hybrid Representation):结合不同表示(如体素、网格、点云、多视图)的优势,以更丰富的特征捕捉三维信息。例如,多视图表示天然融合了二维图像特征。
- 表示与学*的结合:表示方法并非独立,它与学*算法紧密相关。给定一种表示,何种训练方法最优?未来趋势可能是自动生成表示,而非人为指定。例如,研究通过程序化规则自动生成适应任务的表示。
- 谱方法(Spectral Methods)与可学*基函数:谱方法(如拉普拉斯谱)因其在逼*理论和多尺度特性上的优势被广泛使用。但基函数不限于谱基。一个前沿方向是用图神经网络学*最优的基函数,然后在此基础上学*映射。有理论猜测,对于流形数据,学得的基可能接*谱基。
三维可解释性
三维相比二维拥有更多可定义、可解释的属性(如几何、距离、物理可行性、运动规律)。三维可解释性研究可从两方面入手:
- 理解网络所学:例如,在姿态估计中,预测关键点比直接回归旋转矩阵更可解释。
- 引入更多约束:可解释的中间表示(如关键点、长度)允许我们注入先验知识(如“椅腿长度应大于1米”),从而通过额外的损失函数引导网络学*得更好。
三维理解 🧠
最后,我们探讨三维理解,即让机器认知三维世界的语义与功能。
开拓三维特有任务
当前许多三维理解任务(如分割、分类)是对二维任务的迁移。我们应思考哪些是二维无法解决或难以解决,而三维具有天然优势的任务,例如:
- 物理与材质属性理解:推断物体的材料(木、铁)、结构强度、承重能力等。这与三维几何结构密切相关。
- 功能可供性(Affordance)分析:分析物体如何与人交互(如椅子如何被坐、把手如何被握)。三维能更精确地建模交互的物理约束。
- 生长与演化模拟:研究物体(如骨骼、植物)如何在与环境交互中生长变化,这需要结合物理仿真与学*。
数据集的挑战与展望
推进上述研究面临数据稀缺的挑战。构建包含丰富物理、材质标注的三维数据集是关键。这需要社区共同努力,从小规模开始,逐步吸引更多研究者参与,形成数据生态。
小样本学*与归纳偏置
在三维领域,小样本学*可能有更大潜力。因为三维本身包含更多结构化信息(如对称性、物理规律),通过引入合适的归纳偏置,可以有效减少对大量标注数据的依赖。例如,在形状分割任务上,学*到的知识可能更容易在同类物体间迁移。
图形学与视觉的融合 🤝
图形学(Graphics)核心是生成与渲染,计算机视觉(Vision)核心是理解与分析。两者融合趋势日益明显:
- 生成需理解:要生成逼真内容,需对世界有深刻理解。
- 理解助生成:从真实世界数据(需通过重建获取)中学*,能提升生成质量。
- 共同基础:两者都需要强大的数学与几何基础。
给研究者的建议 ✍️


- 写作训练:好文章是改出来的。不断重读和修改,注重逻辑性与大局观,而非仅追求辞藻。
- 广泛阅读:不仅要读最新文章,更要精读领域经典工作,建立扎实的领域认知,才能产生革命性想法。
- 夯实基础:在追逐深度学*热点前,务必打好传统图形学、视觉、几何的基础,理解领域发展脉络。
- 开拓新场景:积极寻找有重大影响的新应用场景(如水下重建、高环境感知),即使初期只能使用合成数据,也能推动领域开辟新方向。
总结 📚
本节课我们一起回顾了GAMES203课程关于三维重建、表示与理解的核心内容。我们探讨了重建的评估标准、深度学*带来的新机遇、表示学*的前沿、三维可解释性的价值,以及如何开拓三维特有的理解任务。最后,我们展望了图形学与视觉融合的未来,并为同学们提供了研究上的建议。三维视觉与图形学是一个充满活力的领域,其终极目标是增强人类对物理与数字世界的感知与创造能力。希望本课程能为大家打开一扇门,鼓励大家夯实基础、打开脑洞,致力于解决那些真正重要且有趣的问题。


(注:本教程根据课程实录整理,保留了问答环节的精华内容,并按照要求进行了结构化、简化和格式优化。)


GAMES203:三维重建和理解 - P2:Lecture 2 点云注册 📍

在本节课中,我们将要学*三维重建中的一个核心问题——点云注册。注册问题旨在将不同视角下扫描得到的点云数据对齐到同一个坐标系下,是三维重建、模型检测等任务的基础。本节课内容相对技术化,我们将从基本概念讲起,涵盖局部与全局注册算法,并简要介绍基于学*的方法。
什么是点云注册?🔍
上一节我们介绍了三维扫描仪。当我们在不同视角下扫描同一个三维场景时,会得到多个独立的点云。如果我们不知道这些视角之间的相对变换关系,这些点云在空间中是错位的。注册问题就是通过算法,将这些点云匹配对齐的过程。
直观地说,注册的目标是让两个点云在重叠区域内的对应点之间的距离尽可能小。这是一个非常基础且重要的问题,其算法从90年代经典的ICP算法,一直发展到如今深度学*时代的新方法。
注册问题的分类 📂
在深入算法之前,我们首先需要对注册任务本身进行分类。理解不同的应用场景有助于我们选择合适的算法。注册问题可以从三个维度进行划分:
以下是注册问题的三个主要分类方向:
-
重叠程度:
- 完全重叠:两个表面(点云)是彼此的副本,或者一个完全包含在另一个之中。例如,在工业零件检测中,将扫描得到的零件点云与CAD设计模型进行匹配。
- 部分重叠:两个点云只有一部分区域是共同的。这是三维重建中最常见的情况,因为从不同视角扫描,必然存在未被观测到的区域。
-
初始位姿:
- 局部注册:两个点云的初始位姿已经大致对齐。例如,连续扫描的两帧点云,其相对位姿变化很小。
- 全局注册:两个点云的初始位姿未知,可能相差很大。例如,在室外不同位置架设扫描仪得到的两片点云。
-
点云数量:
- 两两注册:处理两片点云之间的对齐。
- 多片同时注册:同时处理多片点云,将它们一起对齐到一个全局坐标系中。
本节课我们将首先聚焦于两片点云的注册问题,涵盖完全重叠与部分重叠的情况,从局部方法讲起,最后介绍全局方法以及基于学*的方法,并简要提及多片同时注册。
两两注册的核心:ICP算法 🔄
两两注册中最重要的算法是迭代最*点算法。理解ICP是掌握点云注册的基础。
已知对应点的情况
假设我们有两片点云(红色和蓝色),并且已知红色点云上的一些点 P_i 与蓝色点云上的一些点 Q_i 一一对应。我们的目标是找到一个刚体变换(旋转 R 和*移 T),使得变换后的 P_i 与 Q_i 之间的距离*方和最小。
这定义了一个优化问题,其目标函数为:
E(R, T) = Σ_i || (R * P_i + T) - Q_i ||^2
我们需要最小化这个目标函数 E,变量是 R 和 T。幸运的是,这个问题存在闭式解。其核心思想是:先将两个点云的中心(均值)对齐,然后通过特征值分解求解最优旋转矩阵 R。
ICP算法:处理未知对应点
然而在实际中,我们并不知道点之间的对应关系。ICP算法的巧妙之处在于,它通过迭代的方式同时求解对应关系和刚体变换。
以下是ICP算法的基本步骤:
- 最*点搜索:对于源点云(红色)中的每一个点,在目标点云(蓝色)中寻找距离最*的点,作为临时的对应点。
- 求解变换:基于上一步找到的临时对应点对,使用闭式解计算最优的旋转
R和*移T。 - 应用变换:将计算得到的变换作用于源点云。
- 迭代:重复步骤1-3,直到变换收敛(即变换参数的变化或点云间距离的变化小于某个阈值)。
ICP算法非常直观且有效,它是一个交替最小化过程:固定变换优化对应点(找最*点),再固定对应点优化变换。理论上可以证明,在一般情况下,ICP算法能收敛到一个局部极小值。
从优化视角看ICP与高斯-牛顿法
从数学上看,注册问题可以表述为最小化源点云 X 经过变换 α 后,到目标曲面 Φ 的*方距离函数 f(α)。ICP的交替最小化过程可以看作是对这个复杂问题的一种简化求解方式。
另一种更通用的求解非线性最小二乘问题的方法是高斯-牛顿法。它是对牛顿法的*似,避免了计算复杂的二阶海森矩阵,转而使用雅可比矩阵的乘积来*似。在点云注册的语境下,使用点到*面的距离作为度量,其高斯-牛顿法的迭代更新公式与一种称为“point-to-plane ICP”的变种算法等价。
点到*面距离的几何直觉是:当点云已经大致对齐时,优化一个点到其对应点处切*面的距离,比优化点到点的距离更为鲁棒,因为即使点移动了,到切*面的距离仍然是一个良好的*似。

实践建议:当初始位姿较好时,使用 point-to-plane 距离度量通常收敛更快;当初始位姿较差时,标准的 point-to-point ICP 可能更鲁棒。


处理部分重叠与异常值 🛡️
上一节我们介绍了基础的ICP算法,它假设点云之间完全重叠。但在实际三维重建中,点云往往是部分重叠的,这意味着为一些点找到的“最*点”实际上是错误的对应(异常值)。本节我们来看看如何处理这种情况。
核心思想是使用鲁棒性度量来代替*方距离(L2范数),以降低异常值的影响。常用的鲁棒函数包括L1范数、Huber损失、Geman-McClure函数等。
一种简单而有效的方法是加权最小二乘法。其思想是将原始的鲁棒目标函数,改写为加权*方和的形式,并通过迭代更新权重来求解。
具体算法如下:
- 初始化所有权重
w_i = 1。 - 使用当前权重,求解加权最小二乘问题,得到当前最优变换。
- 根据当前残差(点对距离)更新权重:残差大的点对权重降低(例如,
w_i = 1 / (d_i + ε))。 - 重复步骤2-3直至收敛。
直观理解:以一个简单的一维数据拟合为例,最小化L2损失(*方和)得到的是所有点的均值,容易被远处的异常值拉偏。而最小化L1损失(绝对值和)得到的是中位数,对异常值不敏感。加权最小二乘通过迭代调整权重,使算法行为向中位数估计靠*,从而获得鲁棒性。
另一种实用的启发式方法是双向剪枝:
- 对于源点云中的点
V,在目标点云中找最*点Q。 - 再以
Q为起点,在源点云中找最*点R。 - 如果
R与原始的V是同一个点或距离非常*,则认为(V, Q)是一个可靠的对应点对;否则,将其剔除。
这种方法可以有效地过滤掉非重叠区域产生的错误匹配。
全局注册方法 🌐
前面几节讨论的ICP及其变种都属于局部注册方法,它们需要一个较好的初始位姿估计。本节我们来看看当初始位姿未知或很差时,如何解决全局注册问题。
全局注册的基本流程是:首先在两个点云上提取局部特征描述子,然后建立特征点之间的候选匹配,最后利用几何一致性约束从大量可能包含错误的候选匹配中筛选出正确的匹配,进而估算刚体变换。
1. 特征描述子
我们需要一种对旋转、*移等变换不变的描述子,以便在不同点云上计算和匹配。传统方法包括:
- Spin Images:在点的邻域内,统计基于距离和角度的分布直方图。
- 积分不变量:计算点邻域球内曲面面积的积分,具有多尺度特性。
- 3D SIFT:将2D图像中的SIFT特征推广到3D。
这些特征是人工设计的。如今,基于深度学*学*特征描述子已成为主流趋势,但传统方法在模型泛化性方面仍有其价值。
2. 从候选匹配中筛选正确匹配
得到大量候选匹配(很多是错误的)后,核心任务是找出一个正确的匹配子集,该子集能用一个刚体变换很好地拟合。主要有三类方法:
A. 随机采样一致性
这是一种经典的投票策略:
- 随机采样最小点对(3D中需3对点)来计算一个刚体变换假设。
- 检查有多少候选匹配与该变换假设一致(即变换后距离小于阈值)。
- 重复上述过程多次,选择支持内点数量最多的那个变换假设。
其成功概率与内点比例和采样次数有关。如果点带有法向等信息,可能只需2对点就能确定变换,从而提高效率。
B. 霍夫变换
这也是一种投票思想,但投票空间是变换参数空间:
- 每采样一对匹配点,可以投票给一个可能的变换参数。
- 在参数空间中,正确的变换会被许多正确的匹配反复投票,形成一个峰值。
- 使用均值漂移等聚类方法找到参数空间的峰值,即为估计的变换。
C. 谱方法
将问题转化为图论中的最大团搜索问题:
- 以候选匹配为节点,构建一个“一致性矩阵”或图。如果两对匹配在几何上相容(例如,保持点间距离),则在它们之间连一条边。
- 正确的匹配集合会形成一个连接紧密的子图(团)。
- 通过求解该图邻接矩阵的主特征向量,并根据向量元素的大小对匹配进行排序和筛选,可以*似地找出这个最大的团。
这些方法的核心思想都是利用刚体变换的几何约束(如保持距离、角度不变),从嘈杂的匹配中恢复出正确的集合。
3. 混合方法
我们可以将不同的思想结合起来。例如,设计一个联合优化目标函数,同时优化匹配的指示变量(哪些匹配被选中)和刚体变换参数,并使用交替最小化策略求解。这相当于将谱方法的一致性约束与加权最小二乘的优化过程融合在一起。
基于学*的方法与多片注册 🤖
上一节我们介绍了传统的全局注册方法。随着深度学*的发展,现在也出现了基于学*的全局匹配方法。本节我们简要了解其思路,并介绍如何将两两注册扩展到多片点云。
基于学*的全局匹配
基本思路是用神经网络模块替代传统流程中的某些步骤:
- 特征提取:使用神经网络(如PointNet或Transformer)直接从点云中学*具有判别性的局部特征描述子。
- 特征匹配:使用网络(如注意力机制)来学*如何建立特征点之间的对应关系,或直接预测匹配的置信度。
- 变换估计:即使匹配仍包含错误,也可以使用网络(如回归网络)或结合传统RANSAC层,从匹配中稳健地估计变换参数。
这类方法在数据驱动的背景下,特别是在重叠率较低、噪声较大的情况下,往往能表现出比传统手工特征方法更好的性能。
多片同时注册
在实际重建中,我们通常有大量来自不同视角的点云需要同时对齐。最直接的方法是联合两两注册,即最小化所有点云对之间的对齐误差之和。
其目标函数可以写为:
E({R_k, T_k}) = Σ_{i, j} Σ_{p in Cloud_i} distance( Transform(R_i, T_i, p), Cloud_j )^2
其中 {R_k, T_k} 是所有点云的位姿(通常固定其中一个为基准)。这仍然是一个非线性最小二乘问题,可以用高斯-牛顿法等优化算法求解。然而,当点云数量很多时,计算所有点云对之间的误差会导致计算量剧增。
一种巧妙且高效的方法是隐式同时注册与重建:
- 初始化一个潜在曲面(例如,一个由许多局部*面片组成的粗糙曲面)。
- E步骤:将每一片扫描点云与这个潜在曲面进行ICP对齐。
- M步骤:用所有对齐后的点云数据,去更新(拟合)这个潜在曲面,使其更精确。
- 重复步骤2-3,并可以逐步提高潜在曲面的分辨率。
这种方法的好处在于,它将多片注册问题分解为一系列“扫描-模型”的注册问题,无需显式确定哪些扫描之间存在重叠,通过空间数据结构自动处理,非常高效。
总结 📝
本节课我们一起学*了三维点云注册的核心知识。我们从注册问题的定义和分类入手,首先深入讲解了作为局部注册基石的ICP算法及其优化原理(点到点、点到*面)。接着,我们探讨了如何处理部分重叠和异常值,引入了鲁棒损失和加权最小二乘的思想。然后,我们介绍了全局注册的挑战,并概述了RANSAC、霍夫变换和谱方法等传统解决方案,它们都依赖于几何一致性约束。最后,我们简要了解了基于学*的注册方法趋势,以及将两两注册扩展到多片同时注册的策略。




注册是三维重建中的基础环节,理解这些经典算法及其背后的思想,将为后续学*更复杂的三维视觉任务打下坚实的基础。

GAMES203:三维重建和理解 - P3:Lecture 3 曲面重建 🧩
在本节课中,我们将学*三维曲面重建的核心概念与方法。曲面重建旨在从离散的三维点云数据中,恢复出连续、可用的表面模型(如网格)。这是一个基础且尚未完全解决的问题,尤其在深度学*时代,重新审视这些经典方法具有重要价值。
上一节我们介绍了点云配准,得到了一个完整的点云。本节中,我们来看看如何从这个点云出发,重建出连续的曲面模型。





概述:曲面重建的两种主要范式
曲面重建方法主要分为两大类:
- 显式方法:直接处理点云,通过连接点或丢弃点来构建表面(如计算几何方法)。理论上完备,但对噪声敏感且计算较慢。
- 隐式方法:主流方法。首先构造一个隐式函数 ( f(x, y, z) ),其零等值面 ( f(x, y, z) = 0 ) 即为目标曲面,然后再从中提取网格。这种方法更高效,对噪声更鲁棒,但缺乏严格的理论保证。



本节课将重点介绍隐式方法的发展脉络和核心思想。

隐式曲面重建的开创性工作
一篇极具影响力的开创性工作是《Surface Reconstruction from Unorganized Points》。它建立了一个完整的隐式重建系统框架,后续许多工作都基于此发展。
该方法的输入是无序点云,输出是一个流形网格。其核心思想是重建一个隐式曲面,分为三步:
以下是实现该框架的三个关键步骤:
- 局部*面拟合:对于每个点,利用其邻*点拟合一个局部切*面。这通过求解一个最小二乘优化问题完成,其法向量是点协方差矩阵最小特征值对应的特征向量。
- 法向一致性传播:为每个点的法向确定统一的朝向(里/外)。文章采用了一种启发式方法:构建一个图,边的权重取决于两个点法向的夹角,然后从种子点开始传播法向。
- 定义符号距离函数与网格提取:基于点和其法向,定义空间中的符号距离函数 ( f(p) )。对于空间中的点 ( q ),找到点云中最*的点 ( p ),计算 ( f(q) = (q - p) \cdot n_p ),其中 ( n_p ) 是点 ( p ) 的法向。最后,使用 行进立方体算法 从 ( f(x, y, z) = 0 ) 的等值面提取三角网格。
行进立方体算法 是一种经典算法,它枚举体素棱边上符号变化的多种情况,通过查表方式连接出三角面片。其优点是总能产生流形网格,但网格质量(如三角形形状)可能不佳。




改进:引入观测约束
上一节介绍的方法完全忽略了点云的来源信息。接下来我们看一篇改进文章《A Volumetric Method for Building Complex Models from Range Images》。
其核心思想是:在重建时,不应丢弃每个深度扫描视图的相机位姿和可视性约束。例如,一个点不可能出现在相机与观测点之间的连线上(遮挡关系)。

以下是该方法的主要改进点:
- 它通过最小二乘拟合一个隐式函数,而不是简单地寻找最*点计算距离。
- 在拟合时,引入了基于相机视角的可见性约束,这有助于正确填补空洞并避免在遮挡边界产生错误表面。

核心数学工具


为了构建更鲁棒、高质量的隐式函数,我们需要一些数学工具。上一节我们看到了最小二乘拟合,本节中我们来看看更强大的工具。


径向基函数



径向基函数 提供了一种从离散点插值出连续函数的方法。其形式为:
[
f(x) = \sum_i w_i \phi(|x - c_i|)
]
其中 ( \phi ) 是径向基函数(如高斯函数 ( e{-r2/h^2} )),( c_i ) 是中心点,( w_i ) 是待求权重。通过在所有数据点上建立线性方程组并求解 ( w_i ),可以得到一个全局光滑的函数。缺点是求解全局系统计算量大。




单位分解法
单位分解法 是一种将局部拟合结果融合成全局光滑函数的强大工具。其核心思想是:
- 将定义域划分为许多局部区域,这些区域可以重叠。
- 在每个局部区域上,用一个简单的函数 ( g_i(x) ) 去拟合数据。
- 定义一组权重函数 ( w_i(x) ),每个 ( w_i(x) ) 只在第 ( i ) 个区域及其附*非零,且所有区域的权重函数之和处处为 1,即 ( \sum_i w_i(x) = 1 )。
- 最终的全局函数是局部函数的加权和:( f(x) = \sum_i w_i(x) g_i(x) )。
这种方法结合了局部拟合的灵活性和全局的光滑性。
移动最小二乘法
移动最小二乘法 是单位分解思想的一个具体实现。对于空间中任意一点 ( x ),拟合一个局部函数 ( g_x(p) ) 时,数据点 ( p_i ) 的权重取决于它到 ( x ) 的距离。这样,对于每个 ( x ),我们都能得到一个拟合值 ( f(x) = g_x(x) ),最终 ( f(x) ) 就是一个定义在整个空间上的光滑函数。
利用法向约束构建隐式函数
有了上述工具,我们来看如何利用点云和法向信息构建隐式函数。一篇重要文章《Interactive Inspection of Solids》提出了关键思想。
其核心是:不仅要求隐式函数在数据点处为零值,还要求其梯度方向与点的法向一致。这可以构造更合理的约束:
- 对于每个数据点 ( p_i ),约束 ( f(p_i) = 0 )。
- 对于每个数据点 ( p_i ) 及其法向 ( n_i ),在点 ( p_i ) 沿法向稍微内外偏移的位置 ( p_i \pm \epsilon n_i ) 处,约束 ( f(p_i \pm \epsilon n_i) = \pm \epsilon )。
- 将这些约束代入 RBF 或 MLS 等框架,求解线性方程组,即可得到隐式函数 ( f )。
多分辨率与自适应重建
现实物体表面有的地方*坦,有的地方复杂。为了高效表示,需要多分辨率技术。文章《Multi-level Partition of Unity Implicits》提出了自适应隐式重建方法。
以下是该方法的自适应流程:
- 初始用一个大的包围盒和简单的函数去拟合所有点。
- 计算拟合误差。如果某个局部区域误差过大,则将该区域细分。
- 在细分后的子区域上,用更复杂的局部函数重新拟合。
- 使用单位分解法将所有局部函数光滑地融合起来。
- 重复步骤 2-4,直到满足误差要求。
这样,在*坦区域用大块和简单函数表示,在复杂区域则自动细分并使用更复杂的函数,实现了细节层次自适应。


点投影法
另一类重要的方法是 点投影法,其代表是《Point Set Surfaces》。它不显式构造全局隐式函数,而是定义了一个“投影”操作。
对于空间中任意一点 ( q ),迭代地将其投影到局部拟合的曲面上:
- 以 ( q ) 的邻域点拟合一个局部*面或二次曲面。
- 将 ( q ) 移动(投影)到该局部曲面上。
- 更新 ( q ) 的位置,重复上述步骤直至收敛。
通过这个投影算子,可以将一个稀疏点云“收缩”成一个致密的、代表表面的点云,后续再做网格化。这种方法本质也是基于局部拟合和迭代优化。
泊松曲面重建
目前实践中非常流行且鲁棒的方法是 泊松曲面重建。其核心思想非常巧妙:将重建问题转化为求解泊松方程。
它不直接拟合隐式函数 ( f ) 的值,而是拟合其梯度场 ( \nabla f )。理想情况下,隐式函数的梯度在表面处应等于该点的法向 ( n )。因此,我们可以从带有法向的点云中,估计一个向量场 ( V ),使其在数据点处接* ( n )。
然后,寻找一个隐式函数 ( f ),使得其梯度 ( \nabla f ) 尽可能接*估计的向量场 ( V )。这等价于最小化 ( |\nabla f - V|^2 ),其最优解满足泊松方程 ( \Delta f = \nabla \cdot V )。其中 ( \Delta ) 是拉普拉斯算子,( \nabla \cdot ) 是散度算子。
以下是泊松重建的关键步骤:
- 建立八叉树空间划分,以适应不同区域的细节密度。
- 在八叉树节点上定义基函数,将点云法向“涂抹”到节点上,形成向量场 ( V ) 并计算其散度 ( \nabla \cdot V )。
- 求解大型稀疏线性系统 ( \Delta f = \nabla \cdot V ),得到每个节点的隐式函数值 ( f )。
- 使用行进立方体算法从 ( f ) 的等值面提取网格。
泊松重建的结果视觉上非常光滑,即使法向有噪声也能工作良好,并且有成熟的库实现。

对偶轮廓法与网格提取优化
最后,我们简要提一下网格提取的另一种方法:对偶轮廓法。行进立方体法将顶点放在体素的棱边上,而对偶轮廓法将顶点放在体素内部。



以下是两种方法的简单对比:
- 行进立方体法:总能保证输出是流形网格,但三角形质量可能较差,且难以捕捉尖锐特征。
- 对偶轮廓法:生成的网格质量更好,顶点位置更灵活(可优化以贴*特征边),但不能保证总是流形。
一种混合思路是:先使用对偶轮廓法生成高质量网格,再检测并修复其中非流形的部分。这体现了在实用中结合不同方法优点的思想。
总结


本节课我们一起学*了三维曲面重建的经典隐式方法。我们从开创性的框架出发,探讨了如何利用径向基函数、单位分解、移动最小二乘等工具构建隐式函数,并介绍了引入法向约束、多分辨率自适应、点投影以及强大的泊松重建等方法。最后,我们了解了从隐式函数提取网格的不同策略。这些经典思想为理解现代基于深度学*的三维重建工作奠定了坚实的基础。

GAMES203:三维重建和理解 - P4:Lecture 4 运动恢复结构 (Structure From Motion) 📸

在本节课中,我们将要学*如何仅从一组二维图像中恢复三维场景的结构和相机的运动姿态。这个过程被称为运动恢复结构。
上一节我们介绍了基于深度信息(RGB-D)的重建方法。本节和下节课,我们将探讨如何直接从普通图像(RGB)开始,例如从网上下载的图片,来重建一个稀疏的三维点云模型。
图像形成的几何模型 📐
首先,我们需要理解图像是如何生成的,或者说图像中的像素与三维空间中的物体点之间存在什么关系。这部分内容属于计算机视觉中的几何基础。
投影模型
一个三维点如何投影到二维图像*面上?这通常通过针孔相机模型来描述。它是一种简单的透视投影变换。
假设我们有一个三维点 X = (X, Y, Z)^T。它通过一个相机投影到图像*面上的点 x = (x, y)^T。这个过程可以表示为:
x = f * (X / Z)
y = f * (Y / Z)
其中,f 是相机的焦距。这个公式表明,深度信息 Z 在投影过程中被丢失了。一个图像点实际上对应着三维空间中穿过相机光心的一条射线上的所有点。
齐次坐标表示
为了更方便地进行数学计算(特别是处理无穷远点和*行线相交等问题),我们引入齐次坐标。
在齐次坐标下,一个二维图像点 x = (x, y)^T 被表示为 x̃ = (x, y, 1)^T。一个三维点 X = (X, Y, Z)^T 被表示为 X̃ = (X, Y, Z, 1)^T。
使用齐次坐标,投影过程可以写成一个线性矩阵乘法:
λ * x̃ = P * X̃
其中,λ 是一个非零尺度因子,P 是一个 3x4 的投影矩阵。P 可以进一步分解为:
P = K * [R | t]
这里:
- K 是 3x3 的内参矩阵,包含焦距 f、主点坐标 (cx, cy) 等参数。
- [R | t] 是 3x4 的外参矩阵,其中 R 是 3x3 的旋转矩阵,t 是 3x1 的*移向量,共同描述了相机在世界坐标系中的姿态(位置和方向)。
这个方程 λ * x̃ = K * [R | t] * X̃ 是 SFM 中最核心的几何关系。
两视图几何与本质矩阵 🔄
上一节我们介绍了相机投影的通用模型。本节中我们来看看,当给定两个不同视角拍摄的图像时,我们能推导出什么。
问题定义
假设我们有两个图像(视图),并且知道一些在两个图像中匹配的二维特征点对(对应点)。我们的目标是估计这两个相机之间的相对运动(旋转 R 和*移 t),以及这些匹配点在三维空间中的位置。
首先,我们假设相机的内参矩阵 K 是已知的(后续会讲如何估计)。这样,我们可以将图像坐标归一化:x̂ = K⁻¹ * x̃。归一化坐标 x̂ 对应于焦距归一化后的图像*面上的点。
推导本质矩阵
在两个视图下,对于一对匹配的归一化图像点 x̂₁ 和 x̂₂,以及它们对应的(未知的)三维点 X,存在以下关系:
λ₂ * x̂₂ = R * (λ₁ * x̂₁) + t
其中 λ₁ 和 λ₂ 分别是该点在两个相机坐标系下的深度。
通过一系列消去 λ₁ 和 λ₂ 的代数运算(利用向量叉积的性质),我们可以得到一个非常简洁的约束方程:
x̂₂ᵀ * E * x̂₁ = 0
其中,E 被称为本质矩阵。它与相机的外参有直接关系:
E = [t]ₓ * R
这里 [t]ₓ 是*移向量 t 的反对称矩阵(skew-symmetric matrix)。
这个方程 x̂₂ᵀ * E * x̂₁ = 0 是两视图几何的基石。它表明,已知匹配点对和相机内参,我们可以求解本质矩阵 E。
八点法求解本质矩阵
给定足够多的匹配点对,我们如何求解 E?以下是经典的八点法步骤:
- 构建线性方程组:将方程 x̂₂ᵀ * E * x̂₁ = 0 展开,可以写成关于 E 的9个元素的线性方程:A * e = 0,其中 e 是将 E 按行展开成的9维向量。
- 求解线性系统:由于尺度不确定性,我们通常约束 ||e|| = 1。这转化为求解 AᵀA 的最小特征值对应的特征向量问题。至少需要8对匹配点(故称八点法)来得到唯一解。
- 强制本质矩阵约束:通过线性八点法求得的矩阵可能不满足本质矩阵的内在约束(其奇异值应为 [σ, σ, 0] 的形式)。因此,我们需要对求得的矩阵进行投影:对其进行奇异值分解(SVD)E = U * diag(σ₁, σ₂, σ₃) * Vᵀ,然后将其替换为 E‘ = U * diag((σ₁+σ₂)/2, (σ₁+σ₂)/2, 0) * Vᵀ。
- 从本质矩阵恢复运动:从投影后的 E‘ 可以通过SVD分解恢复出相机的旋转 R 和*移 t。注意,这会得到四种可能的解,需要通过检查点的深度为正(位于相机前方)来选出唯一正确的解。
相机标定 🎯
前面我们假设相机内参 K 已知。那么如何获取 K 呢?这个过程称为相机标定。
使用标定板
一种常见的方法是使用已知图案的标定板(例如棋盘格)。以下是标定步骤:
- 采集数据:从不同角度拍摄多张标定板的图像。
- 检测角点:在每张图像中自动检测标定板上角点的像素坐标 (x, y)。
- 建立方程:由于我们知道标定板上每个角点的世界坐标 (X, Y, Z)(通常设标定板*面为 Z=0),对于每个角点,我们可以建立投影方程:s * [x, y, 1]ᵀ = K * [R | t] * [X, Y, 0, 1]ᵀ。
- 求解投影矩阵:上述方程可以转化为关于投影矩阵 P(即 K[R|t])元素的线性方程组。每对点提供两个方程,P 有11个自由度,因此至少需要6个角点(提供12个方程)来求解。
- 分解内参和外参:得到 P 后,可以通过矩阵分解(例如RQ分解,类似于QR分解)将其分解为内参矩阵 K 和外参 [R | t]。
从基础矩阵到运动恢复结构 🌉
如果不已知内参,我们可以直接从一个更一般的矩阵——基础矩阵 F 开始。
基础矩阵
基础矩阵 F 描述了两个未标定图像之间的几何关系。它与本质矩阵 E 的关系为:
F = K₂⁻ᵀ * E * K₁⁻¹
对应的极线约束方程为:
x₂ᵀ * F * x₁ = 0
其中 x₁, x₂ 是原始像素坐标(非归一化坐标)。同样可以使用八点法来估计 F。
多视图重建流程
在实际应用中,我们通常有大量图像。一个经典的SFM流程(如Photo Tourism系统所示)如下:
以下是核心步骤:
- 特征提取与匹配:对所有图像提取局部特征(如SIFT),并匹配图像对之间的特征点。
- 估计几何:对每一对匹配较多的图像,使用八点法估计基础矩阵 F 或本质矩阵 E,并通过RANSAC算法剔除错误匹配(外点)。
- 增量式重建:
- 选择一对好的初始图像,恢复其相对姿态和三角化出初始三维点云。
- 不断添加新图像:将已有三维点投影到新图像上,与检测到的特征点进行匹配,从而估计新图像的相机姿态(PnP问题)。
- 对新观测到的特征点进行三角化,将其加入三维点云。
- 全局优化(光束法*差):增量重建会累积误差。最后,需要执行一个全局优化,同时优化所有相机参数 {R_i, t_i, K_i} 和所有三维点坐标 {X_j},最小化重投影误差的总和:
- min Σ Σ || x_ij - Proj(K_i, R_i, t_i, X_j) ||²
- 这个优化问题称为光束法*差,是一个大规模非线性最小二乘问题,通常使用Levenberg-Marquardt等算法求解。
总结 📝
本节课中我们一起学*了运动恢复结构的核心原理。
- 我们从图像形成的几何模型出发,理解了齐次坐标和相机投影方程。
- 然后深入探讨了两视图几何,推导了本质矩阵 E 和极线约束,并学*了用八点法求解 E 以及恢复相机运动。
- 接着介绍了相机标定的方法,以获取相机的内参。
- 最后,概述了从基础矩阵出发,到进行多视图增量式重建和全局光束法*差的完整SFM流程。
这些内容是理解现代三维重建系统的基石。下一节课,我们将探讨立体视觉,这是另一种重要的三维重建方法。



GAMES203:三维重建和理解 - P5:Lecture 5 多视角立体视觉 👁️
在本节课中,我们将要学*多视角立体视觉。这是三维重建系列课程的最后一讲,我们将探讨如何利用已知的相机姿态和参数,从多张图像中恢复出高质量、稠密的场景三维结构,即估计每个像素的深度值。
概述:从稀疏到稠密重建
上一节我们介绍了运动恢复结构,得到了稀疏的点云和相机参数。本节中,我们来看看如何利用这些信息进行稠密重建。
概念上,三维重建分为两步:
- 从图像中恢复相机姿态和稀疏点云。
- 基于第一步的结果,进行稠密重建,估计每个像素的深度。
今天我们聚焦于第二步——稠密重建。
核心思想:立体匹配与视差

立体视觉的基本原理很简单:如果知道两张图像中像素点的对应关系,就能计算出深度。初中数学知识告诉我们,通过相似三角形关系,视差与深度成反比。
公式:视差 (disparity) = x_left - x_right = (B * f) / Z
其中,B 是基线长度,f 是焦距,Z 是深度。

立体匹配与传统图像匹配的关键区别在于搜索空间。在立体视觉中,我们是在极线上寻找对应点,这大大缩小了搜索范围,是立体算法高效的基础。
基础:双视角立体匹配
首先,我们讲解如何利用两张图像进行立体匹配。

问题定义与假设

给定两张拍摄同一静态场景的图像,已知它们之间的相对位姿变换,目标是得到第一张视角下的深度图。
需要注意,我们无法恢复绝对的深度尺度,但只要能找到对应点,就能得到相对的深度关系。
基础匹配算法



以下是基本的立体匹配算法流程:


- 极线校正:首先,通过算法将两张图像变换,使得对应点位于同一水*扫描线上。这简化了搜索过程。这个变换是一个3x3的单应性矩阵。
- 逐像素匹配:对于左图的每个像素,在右图的对应极线(水*线)上搜索最相似的像素。
- 相似度计算:通常取一个矩形图像块进行比较。相似度度量可以是像素差*方和、归一化互相关等。
- 选择最佳匹配:选择相似度最高(或代价最低)的点作为对应点。
- 计算深度:根据对应点的水*坐标差(视差)和相机参数,利用上述公式计算深度。

窗口大小的影响
在匹配时,窗口大小的选择是一个关键的超参数:
- 小窗口:包含局部信息,匹配结果噪声大,但边缘相对清晰。
- 大窗口:包含更多信息,匹配结果更*滑,噪声小,但可能模糊细节。
基础窗口匹配算法的结果通常比较有限,与真实值存在差距。




进阶:引入全局优化与约束



为了解决基础算法的噪声问题,我们可以为深度图引入一些合理的先验约束,并通过全局优化来求解。
常用的约束条件


- 唯一性约束:左图的一个点最多匹配右图的一个点。
- 顺序约束:匹配点之间的左右顺序在大多数情况下应保持一致。
- *滑性约束:深度值在大部分区域应缓慢变化,除非在物体边界处。



马尔可夫随机场建模


我们可以将立体匹配问题建模为一个马尔可夫随机场的能量最小化问题。





能量函数公式:
E(D) = Σ E_data(p, Dp) + λ * Σ E_smooth(p, q, Dp, Dq)
E_data:数据项,衡量像素p取深度Dp时的匹配代价(即窗口相似度的负值)。E_smooth:*滑项,鼓励相邻像素p和q的深度Dp和Dq相似。λ:*衡两项的权重系数。

通过优化这个能量函数(例如使用图割或置信传播算法),可以得到比基础窗口匹配更*滑、更准确的结果。
扩展:多视角立体视觉
双视角立体利用了极线约束。多视角立体核心思想类似,但能利用更多图像的信息,通常更鲁棒。
核心流程

- 选择参考图像:选定一张需要计算深度的图像作为参考视图。
- 选择邻域视图:从所有图像中,选取与参考视图有足够重叠且视角合适的图像。
- 深度假设检验:对于参考图像的每个像素,假设一系列可能的深度值。
- 对于每个假设深度,将该像素反向投影到三维空间,再投影到其他邻域视图上,得到一组对应的像素点。
- 计算这些对应像素块与参考像素块的相似度(如光度一致性)。
- 选择最佳深度:对于该像素,选择能使多视图光度一致性代价最小的深度假设作为其深度值。


这种方法本质上是将“在一条线上找对应点”扩展为“为每个深度假设检验其在所有视图中的一致性”。
利用场景结构先验
在纹理缺失或重复的区域,基于光度一致性的方法会失效。许多场景(尤其是室内和城市)具有明显的*面结构,我们可以利用这一先验来改进重建。
*面模型集成

思路是假设场景可以由少量*面来*似表示,而不是为每个像素独立估计深度。


方法一:三元组*滑项
在MRF能量函数中,不仅考虑相邻像素对的*滑约束,还考虑三个共线像素。如果它们位于同一*面,则中间点的深度应*似等于两端点深度的*均值。通过加入这种高阶约束,可以促使结果呈现分段*面的特性。




方法二:*面标签优化
- 提取场景中可能的主导*面方向(如曼哈顿世界假设下的三个垂直方向)。
- 为每个像素分配一个*面标签(属于哪个*面),每个*面由其法向和偏移定义。
- 构建能量函数,包含:
- 数据项:像素被赋予某个*面标签后,根据该*面计算出的深度,在所有视图中的光度一致性代价。
- *滑项:相邻像素倾向于属于同一*面。
- 非局部约束:不同*面之间可能存在的几何关系(如垂直、*行)。
- 优化求解每个像素的*面标签,进而得到稠密深度图。


这种方法通过让大量像素共同“投票”决定一个*面的好坏,比基于像素的局部决策更为鲁棒,特别适用于结构化场景。


大规模场景处理




对于互联网照片集等大规模场景,直接进行全局的多视角立体计算非常耗时。常用的策略是分而治之:
- 图像聚类:将大量图像根据可视内容的重叠程度和相机位置聚合成多个较小的集合。
- 子集重建:在每个图像子集内独立进行运动恢复结构和稠密重建。
- 全局融合:将各个子集的重建结果统一到全局坐标系下,并进行融合和优化,得到完整的大规模场景模型。

总结
本节课我们一起学*了从双视角到多视角的稠密立体视觉方法。
我们首先回顾了立体视觉的核心是利用极线约束在降维空间中进行匹配。然后,从基础的窗口匹配算法出发,针对其噪声大的问题,引入了基于马尔可夫随机场的全局优化方法,通过加入*滑性等约束来获得更好的结果。

接着,我们将双视角扩展到了多视角,通过检验深度假设在多视图下的光度一致性来利用更多信息。最后,针对纹理缺失的挑战,我们探讨了如何利用场景的结构化先验(特别是*面结构)来约束和改善重建结果,并简要介绍了处理大规模场景的分治策略。

理解这些经典算法的核心思想,对于掌握三维重建的基础和探索基于深度学*的新方法都至关重要。

GAMES203:三维重建和理解 - P6:Lecture 6 逆问题中的图同步 🧩
在本节课中,我们将要学*一个名为“图同步”的重要概念。虽然这个名词可能听起来有些陌生,但它对于解决多视图匹配、三维重建等实际问题至关重要。我们将从直观的例子入手,逐步理解其核心思想,并探讨如何将其转化为算法。
概述与动机 🎯
在之前的课程中,我们已经遇到过这样的问题:给定一组图像或扫描数据,我们需要构建它们之间一致的对应关系。输入通常是两两之间的匹配结果,但这些匹配结果中可能包含错误。
图同步要解决的核心问题是:如何在多个物体之间,利用所有两两匹配的信息,构建一个全局一致的对应关系网络?这个问题在许多应用中都非常重要,例如多视角扫描匹配、运动结构恢复、以及拼图复原等。
从直观例子理解同步思想 🧩
例子一:拼图碎片匹配
假设你面对两个纯白色的拼图碎片,它们之间没有明显的纹理或特征可以进行匹配。此时,仅凭这两个碎片,你无法确定它们是否应该拼合在一起,以及如何拼合。
解决方案:引入第三个碎片。如果第三个碎片上带有纹理,并且你能确定它分别与前两个碎片中的某一个部分匹配,那么通过这个“桥梁”,你就能推断出前两个碎片之间的正确相对位置。这说明,匹配不能孤立地只看两个对象的信息,有时需要借助其他对象提供的辅助信息来共同确定。
例子二:使用中间模型优化匹配
假设你有一个软件,可以输入两个三维模型并输出它们之间的对应关系,但结果时好时坏。与其费力调整软件参数,一个更聪明的办法是:找到一个与这两个模型都较为相似的中间模型。
操作步骤:
- 用相同的软件分别计算 模型A ↔ 中间模型 和 中间模型 ↔ 模型B 的对应关系。由于每一步的匹配任务变得更简单,软件更可能给出正确的结果。
- 将这两组正确的对应关系组合起来,就能得到 模型A ↔ 模型B 之间高质量的对应关系。
这个例子表明,通过引入中间节点并组合简单的匹配步骤,可以绕过复杂调参,获得更好的全局结果。
将思想转化为算法:图表示与问题定义 📊
上一节我们通过例子理解了同步的核心思想,本节中我们来看看如何用数学和计算的语言来形式化这个问题。

我们可以将整个问题建模成一个图:
- 节点:代表每个独立的物体(或图像、扫描数据)。
- 边:代表两个物体之间通过某个算法计算得到的映射(对应关系)。这些映射有些是正确的,有些可能是错误的。

我们的目标是:从这些可能包含错误的两两映射中,筛选出正确的部分,使得剩下的正确映射能够构成一个全局一致的网络。所谓“全局一致”,指的是对于图中任意一个闭合环路,沿着环路将所有映射组合起来,应该得到一个恒等映射(即回到起点)。
然而,挑战在于:对于一个算法计算出的单个映射,我们很难直接评估其好坏,因为我们没有真实的标准答案(Ground Truth)。
那么,我们如何判断一个映射是否正确呢?
我们需要利用一致性这一附加信息。即使不知道绝对的正确对应,我们也可以要求图中所有映射在组合起来时是逻辑自洽的。例如,如果 物体A -> 物体B 的映射是 X,物体B -> 物体C 的映射是 Y,那么 物体A -> 物体C 的映射应该*似等于 Y(X)。如果实际计算出的 物体A -> 物体C 的映射与 Y(X) 相差甚远,那么 X、Y 和/或 A->C 的映射中至少有一个可能是错误的。
同步算法:两种主要方法 ⚙️
基于一致性的思想,学术界发展出了两种主要的同步算法。
1. 半定规划法
这是一种非常强大且理论完备的方法。它将寻找一致映射的问题形式化为一个凸优化问题。
核心思想:将每个映射表示为一个矩阵(例如,置换矩阵)。全局一致性条件可以转化为一个低秩矩阵分解问题:所有正确的映射矩阵应该能组合成一个低秩的矩阵。而错误的映射会破坏这种低秩结构。
数学模型:
我们定义目标函数,旨在找到一个低秩的矩阵 L(代表一致的全局结构)和一个稀疏的矩阵 S(代表错误或噪声),使得它们的和尽可能接*观测到的映射矩阵 M。同时,L 需要满足映射矩阵特有的约束(如置换矩阵约束)。
minimize ||L||* + λ||S||1
subject to M = L + S, 且 L 满足特定约束(如置换约束)
其中 ||.||* 表示核范数(用于促进低秩),||.||1 表示 L1 范数(用于促进稀疏性)。
这种方法在理论上可以证明,在一定的观测密度和错误率下,能够以高概率完全恢复出正确的映射,并且其恢复率是最优的。
2. 谱方法
这是一种更快速、更实用的启发式方法。
核心思想:利用谱图理论。如果我们将所有映射矩阵构造成一个大的块矩阵,那么全局一致的“真值”部分会在这个矩阵的谱(特征值和特征向量)上留下强烈的信号。即使存在一些错误扰动,只要扰动不大,观测矩阵的主要特征向量仍然与真实一致矩阵的特征向量非常接*。
算法步骤:
- 特征分解:对观测到的映射矩阵进行特征分解,取出主导的特征向量。
- 取整:将这些连续值的特征向量通过一个步骤(例如求解一个线性分配问题)转化为离散的映射矩阵(如置换矩阵)。
谱方法的优点是速度快,易于实现。虽然在最坏情况下的理论保证不如半定规划法强,但在许多实际应用中效果很好。
与神经网络的结合 🤖

前面我们讨论的映射主要是点对点的对应或刚体变换。图同步的思想同样可以应用于更复杂的映射,例如由神经网络表示的映射。这引出了“联合神经网络”或“循环一致性学*”的概念。

神经网络作为映射
考虑以下场景:
- 机器翻译:我们希望训练韩语到葡萄牙语的翻译模型,但缺乏直接的*行语料。然而,我们拥有丰富的韩语-英语和英语-葡萄牙语的*行语料。我们可以训练两个神经网络:
f1: 韩语 -> 英语和f2: 英语 -> 葡萄牙语。那么,韩语到葡萄牙语的映射可以表示为f2 ◦ f1。 - 跨域转换:如图像到三维模型、图像描述生成等任务,都可以视为从一个域(如图像空间)到另一个域(如文本空间)的映射。
在这些场景中,每个神经网络就是一个“映射”,每个数据域(语言、图像域、三维模型域)就是图中的一个“节点”。
路径不变性与自监督学*
我们可以构建一个图,节点是不同领域,边是连接它们的神经网络。一致性条件在这里表现为路径不变性:对于图中连接相同两个节点的任意两条路径,由路径上神经网络组合而成的复合函数应该给出相同(或相似)的输出。
这为我们提供了强大的自监督信号:
- 有监督损失:对于有标签数据对的边,我们使用标准的监督损失(如交叉熵、L2损失)。
- 无监督(自监督)损失:对于没有直接标签的节点对,我们可以要求通过不同路径转换得到的结果保持一致。例如,对于一个无标签的图像,通过
图像->文本->图像的循环应该能*似重建原图像。
通过联合优化图中所有神经网络的参数,同时最小化有监督损失和路径不变性损失,我们可以有效地利用未标注数据,提升模型性能,特别是在某些领域标注数据稀缺的情况下。
总结 📝
本节课我们一起学*了“图同步”这一重要概念。
- 核心问题:如何从可能包含错误的大量两两匹配中,恢复出全局一致的对应关系网络。
- 直观理解:通过引入中间节点,利用多对象间的一致性约束来解决模糊或困难的匹配问题。
- 形式化:将问题建模为图,节点是对象,边是映射,目标是找到满足循环一致性的正确边集。
- 经典算法:介绍了理论强大的半定规划法和快速实用的谱方法。
- 现代扩展:探讨了将同步思想应用于神经网络的方法,通过路径不变性实现跨域任务的自监督学*,从而更高效地利用数据。

理解图同步不仅有助于解决三维重建中的匹配问题,其背后关于利用一致性、组合简单步骤解决复杂问题的思想,在机器学*和计算机视觉的许多领域都有广泛的应用。

GAMES203:三维重建和理解 - P7:点云处理基础 🧩
在本节课中,我们将学*点云处理的基础知识。点云作为一种重要的三维数据表示形式,在计算机图形学和计算机视觉中有着广泛的应用。我们将回顾几篇经典文献,了解点云处理的核心概念、基本操作以及学科发展的关键要素。
学科发展的关键要素 🏗️
上一节我们介绍了点云的基本概念,本节中我们来看看推动一个应用学科发展的关键要素。一个学科要健康发展,通常需要具备几个公认的基础设施。
以下是三个关键要素:
- 数据集:需要有公开、标准的数据集供研究者使用和比较算法性能,例如 ShapeNet、ModelNet 等。
- 开源软件:需要有易于获取和使用的开源软件*台,让研究者能够快速实验和验证算法。
- 开放性问题:需要有明确的、开放的、可供持续探索的研究方向(Skeleton Work),以推动领域不断前进。
PointShop 3D 系统在早期就扮演了这样的角色,它提供了一个支持点云基本操作的系统,对推动点云图形学的发展起到了重要作用。


点云的基本操作与参数化 🛠️
了解了学科发展的背景后,我们来看看点云处理中一些最基本和核心的操作。其中,参数化是一个基础且重要的问题。
参数化的目标是为离散的点云表面上的每个点分配一个二维参数坐标(如 UV 坐标),从而可以像处理二维图像一样在三维表面上进行纹理贴图、绘制等操作。这通常通过求解一个优化问题来实现。
其核心公式可以表示为寻找一个映射函数 f,最小化映射带来的畸变:
min Σ || L(f(p_i)) - b_i ||²
其中,p_i 是三维空间中的点,b_i 是其对应的二维参数坐标,L 是拉普拉斯算子之类的*滑约束项,用于保证参数化的光滑性。
在点云上进行此类计算,需要为每个点建立局部坐标系和邻域关系。通常的步骤是:
- 为每个点 p 找到其 k 个最*邻点。
- 基于这些邻域点,通过主成分分析(PCA)等方法拟合一个局部切*面,并建立局部坐标系。
- 在这个局部坐标系下,可以进行梯度的离散*似计算,从而将连续的优化问题离散化求解。
有了参数化之后,就可以在点云表面进行纹理绘制、几何编辑等高级操作。
点云的重建与重采样 🔄
当我们对点云进行编辑或变形后,点的分布可能变得不均匀,这时就需要进行重建或重采样,以恢复其表示的表面质量。
从离散点重建连续表面的核心思想是使用一组基函数进行插值或逼*。例如,使用径向基函数(RBF):
F(x) = Σ w_i * φ(||x - p_i||)
其中,φ 是径向基函数,w_i 是权重,通过已知的点约束来求解。
重采样的目的是使点分布更均匀或更符合某种要求。主要有三类经典方法:
以下是三种经典的重采样方法:
- 层次化聚类法:自顶向下不断将点集分割成簇,直到每个簇内的点可以用一个*面很好地*似,然后用每个簇的中心点代表该簇。
- 迭代收缩法:自底向上不断合并距离最*的点对,用一个新的点替代它们,新点的位置和属性由被合并点的误差二次型决定,以最小化表面误差。
- 粒子系统法:将点视为粒子,在表面定义斥力,让粒子在斥力作用下移动直至*衡,从而得到均匀分布的点集。
这些方法各有特点:层次化聚类速度快但控制精度一般;迭代收缩法精度高但速度慢;粒子系统灵活但实现相对复杂。
点云的布尔运算与变形操作 ⚙️
点云处理的更高级应用包括布尔运算(如交、并、差)和自由变形。这些操作在三维建模中至关重要。
布尔运算的难点在于对两个点云表示的物体进行精确的分类(判断点在物体内部还是外部)以及生成高质量的交集线。一种方法是结合隐式表示(如符号距离函数 SDF):
classify(p) = sign(SDF_A(p)) and sign(SDF_B(p))
通过计算点 p 在两个物体 SDF 上的符号,来判断其布尔运算后的归属。对于交集区域,需要在交界处进行密集的重采样以保证视觉质量。
自由变形(FFD)则是通过操纵包围物体的控制网格来间接地、*滑地变形物体。点云上的点随着其所在控制网格单元格的变形而移动。其关键是计算变形函数 T:
p' = T(p) = Σ B_i(u, v, w) * q_i
其中,(u, v, w) 是点 p 在控制网格中的局部坐标,B_i 是基函数(如 B-样条),q_i 是变形后的控制点位置。
在变形过程中,还需要处理自交检测、局部采样不足导致模型破裂等问题。动态重采样策略可以根据局部拉伸程度,在过度稀疏的区域插入新点,在过度密集的区域删除点,以维持点云的表征质量。
总结与展望 📚
本节课我们一起学*了点云处理的基础知识。我们首先探讨了推动学科发展的关键要素。接着,深入学*了点云的几个核心操作:参数化、重建与重采样、布尔运算以及自由变形。我们了解到,这些操作的核心基础在于邻域查询、局部几何属性估计(如法向、曲率)以及将离散问题连续化/优化求解的思维。

尽管这些技术很多来自一二十年前的经典工作,但它们构成了点云处理的基石。理解这些传统方法,不仅能帮助我们更好地理解现代点云深度网络(如 PointNet++ 中的采样、分组操作)的设计思想,也能为探索更前沿、更复杂的点云处理任务打下坚实的基础。点云处理的潜力,尤其是在复杂编辑、与深度学*结合等方面,仍有巨大的探索空间。

GAMES203:三维重建和理解 - P8:Lecture 8 网格处理 🧠
在本节课中,我们将要学*网格处理的基础知识。网格是三维几何的一种核心表示方式,广泛应用于计算机图形学、几何处理和深度学*等领域。我们将探讨网格的表示、数据结构、微分几何基础以及几种关键的网格处理操作,如*滑、重网格化和简化。
概述 📋
网格处理是三维几何计算中的基础。本节课将介绍网格的基本概念、数据结构,并深入讲解几种核心的网格处理算法。理解这些传统方法,对于后续学*基于网格的深度学*技术至关重要。
网格表示与数据结构 🗂️
上一节我们概述了课程内容,本节中我们来看看网格是如何被表示和存储的。
网格是一种隐式的曲面表示方式。它与参数化曲面类似,由顶点、边和面组成。一个*滑的曲面具有位置、法线和曲率等信息。
- 顶点位置:可用于进行重心插值。
- 顶点法线:可以在面上计算法线,或利用顶点法线进行积分等操作。
- 距离计算:在配准或投影等操作中,需要计算点到网格的距离。
- 参数曲面:对于*滑曲面,可以将其视为一个函数
f(x, y)来求解。 - 投影操作:通常使用
k-d树等数据结构来加速寻找最*点的过程。
从编程和图形学的角度来看,编辑网格是一个重要课题。例如,B样条曲面通过移动控制点来编辑,而网格编辑则涉及更底层的操作。
网格数据结构
以下是关于网格数据结构需要考虑的几个方面:
网格数据结构具有一定的拓扑约束。例如,它需要满足流形条件。有了这些约束,还需要定义如何在网格上进行导航遍历。
处理这些约束的标准方法很重要。例如,在点云简化时,可以随意丢弃一些点,但在网格简化时,不能随意删除一个顶点,还需要考虑如何重新连接剩余的几何元素,以保持网格的完整性。
网格通常如何存储?简单的格式如 OBJ 或 OFF 只提供每个面的顶点索引列表,这是一种最常用的存储方式。而 PLY 等格式可以包含更丰富的属性信息。
支持的操作与数据结构选择
我们首先需要考虑数据结构需要支持哪些运算。
例如,MeshLab 软件可能使用半边数据结构。一个关键问题是,数据结构能否高效地维护面或顶点之间的连接关系。这在简化等操作中至关重要。
一种常见的设计是,每个顶点存储其位置,每个面存储其三个顶点和三个相邻面的索引。有了这些信息,给定一个面,就能找到它的所有邻接面;给定一个顶点,也能找到它周围的所有面。
对于熟悉网格处理的开发者来说,第一项任务通常是编写算法,构建每个顶点的一环邻域。做好这一点,就对数据结构有了初步了解。
半边数据结构虽然不是最高效的,但在编辑操作中速度很快。它需要在存储紧凑性和查询速度之间做出权衡。
半边数据结构的基本单元是“半边”。每个顶点有一个位置和一条出射半边。每条半边有一个起始顶点、一个相邻面、一条下一条半边、一条对边半边。通过这种链接关系,可以遍历网格的所有元素。
这种数据结构在几十年前计算机内存有限时非常重要,能显著减少存储。如今硬件资源丰富,但半边结构在需要复杂拓扑操作的场景中依然非常有用。
微分几何基础 📐
上一节我们介绍了网格的数据结构,本节中我们来看看处理网格所需的一些基础数学知识——微分几何。
微分几何用于计算曲面上的基本属性,这些属性在网格处理和深度学*中都有广泛应用。
参数曲面研究从参数域 (u, v) 到曲面的映射。它有许多重要的量,例如法线。在连续曲面上,法线方向与曲面在该点处相切的两个方向都垂直。
法曲率是指:给定一个法线方向和一个包含该法线的*面,该*面与曲面相交得到一条曲线,这条曲线在该点处的曲率就是法曲率。
曲面上每个点有无数个法曲率,其中有两个极值:主曲率(最大和最小曲率)。两个主曲率的乘积称为高斯曲率,*均值称为*均曲率。
主曲率方向通常是相互垂直的。计算主曲率在特征识别(如下颌边缘)等方面非常有用。
高斯曲率有一个非常重要的性质,即高斯-博内定理。该定理指出,无论曲面如何变形,其高斯曲率的积分总是一个常数。这个常数与曲面的拓扑(欧拉示性数)有等价关系:χ = V - E + F,其中 V 是顶点数,E 是边数,F 是面数。这是一个拓扑不变量。
这引出了离散微分几何:我们可以在离散的网格上定义曲率等量,并让它们满足高斯-博内定理。这在物理模拟等领域非常重要,可以保证离散化后的方程仍保持连续情况下的某些性质。
网格*滑 🌀
上一节我们回顾了微分几何,本节中我们来看看网格处理的一个基本操作——*滑。
*滑与深度学*中的卷积操作密切相关。如果在网格上能定义好卷积,就能方便地应用许多深度学*技术。
图像中有多种*滑方式。在网格上,一种常见的方法是拉普拉斯*滑。其核心思想是,每个顶点的新位置向其邻域顶点的*均位置移动。
在网格上实现拉普拉斯算子,最常用的一种权重是余切权重。通过迭代应用 新位置 = 原位置 + λ * 拉普拉斯位移,网格会逐渐变得光滑。

另一种方法是曲率流。例如,*均曲率流让顶点沿着法线方向移动,移动速度与该点的*均曲率成正比。其流动方程是 ∂x/∂t = -H * n,其中 H 是*均曲率,n 是法线。这种方法也能达到*滑的效果,并且能产生更均匀的网格。


理解*滑的原理,特别是如何在网格上定义这些算子,对于在曲面上进行编辑、变形以及应用深度学*都很有帮助。

网格重网格化与优化 ⚙️
上一节我们介绍了*滑,本节中我们来看看当网格质量不佳时,如何通过重网格化来改善。
如果网格变形很大,或者三角形质量很差(如过于狭长),就需要进行重网格化,即重新采样和连接顶点,以获得更均匀、规则的网格。
质量好的网格(如每条边的邻接面数接*6)在计算微分算子(如曲率)时误差更小,数值稳定性更好。
最常见的重网格化方法是基于局部操作的优化。例如:
- 边翻转:改变边的连接方式,可以改善三角形的形状。
- 边分裂:将一条边从中间分开,插入一个新顶点。
- 边坍缩:将一条边的两个顶点合并为一个。
通过迭代应用这些局部操作,并优化顶点的分布,可以使网格变得更加规则。这在基于网格的神经网络训练中很重要,因为变形的过程中需要保持网格的良好质量。
网格简化 🗜️
上一节我们讨论了重网格化,本节中我们来看看网格处理的核心任务之一——简化。
网格简化是指,给定一个复杂的网格,找到一个顶点数更少的新网格,使得两者尽可能相似。这是一个非常具有挑战性的问题。
目前的主流算法是二次误差度量简化。其基本思想是:将网格的每个*面表示为一个二次误差函数。当合并两个顶点时,计算新顶点位置,使得它对所有相关*面的距离*方和最小。通过不断合并误差最小的顶点对,实现网格简化。
简化过程中需要特别注意全局误差控制。如果不加以控制,简化后的网格不同部分可能发生自交。因此,需要跟踪和约束简化操作带来的全局几何误差。
此外,简化还需要考虑其他属性,如颜色、纹理等,并尽量保持模型的拓扑结构(流形性质)。
网格修复 🩹
上一节我们介绍了网格简化,本节中我们来看看如何处理不完整的网格,即网格修复。
三维扫描得到的模型常常有缺失或破洞。网格修复的目标是填补这些空洞,生成完整、合理的几何。
一种经典方法是基于三角剖分的修复。首先识别空洞的边界,然后在这个边界环内进行三角剖分。这可以转化为一个动态规划问题,寻找使新增三角形面积和或某种形状度量最优的三角剖分方案。修补后,通常还需要对新增区域进行*滑和重网格化,以融入整体。
另一种方法是基于体素的方法。将网格转换为体素表示,在体素空间中进行修补操作(如形态学闭运算),然后从修补后的体素中重新提取网格表面。这种方法更鲁棒,但会丢失原始网格的细节特征。
网格修复,特别是结合深度学*进行智能补全,仍然是一个有价值的研究方向。


总结与推荐 📚
本节课我们一起学*了网格处理的基础知识。我们涵盖了网格的表示与数据结构、微分几何基础、以及*滑、重网格化、简化和修复等核心算法。
最重要的是,这些传统的网格处理技术中包含了大量精巧的思想。深入理解这些内容,对于在网格上开展深度学*研究非常有帮助。强烈建议大家阅读相关的经典书籍(如课程开头推荐的教材),从中汲取灵感,将传统智慧与前沿技术相结合。

我们下周再见。

GAMES203:三维重建和理解 - P9:Lecture 9 三维深度学* 🧠
在本节课中,我们将探讨三维深度学*领域。我们将回顾几种核心的三维数据表示方法,并分析基于这些表示构建的经典深度学*模型。课程将涵盖体素、多视图、点云、隐式曲面(如SDF)以及网格等表示形式,并讨论它们各自的优势、挑战以及如何将传统几何处理的智慧融入现代深度学*架构中。
三维深度学*的兴起与发展 🚀
上一节我们概述了课程内容,本节中我们来看看三维深度学*是如何发展起来的。深度学*在计算机视觉领域的真正兴起,源于AlexNet的出现,它为整个领域带来了突变。许多机器学*方法,特别是架构设计和训练技巧,回头看其实都很简单。关键在于谁能率先提出并应用这些简单而有效的想法。
在三维领域进行深度学*,首先面临的核心问题是如何表示三维物体。这与二维图像处理有本质区别。
以下是几种主要的三维表示方法:
- 网格:由顶点和面片构成。
- 点云:一组三维空间中的点。
- 体素:将空间划分为规则的三维网格。
- 隐式曲面:例如有符号距离函数。
- 高阶表示:如球谐函数等。
基于这些表示,我们可以进行诸如分类、分割等任务。深度学*模型需要将这些表示转换为向量或图等结构化数据,以便进行处理。
体素表示与三维卷积 📦
从最直接的推广开始,我们首先看看体素表示。将三维物体表示为体素网格,是一种最直观的将规则结构从二维推广到三维的方式。这种表示将物体离散化为一个三维张量。
核心操作是三维卷积。在二维卷积 Conv2D 的基础上,我们将其扩展到三维:
# 伪代码示例:三维卷积
output = Conv3D(input_voxel_grid, kernel)
通过堆叠三维卷积层,可以构建用于分类等任务的网络。
2015年的工作《3D ShapeNets》是开创性的,它首次在体素表示上定义了卷积操作。尽管其后续的生成模型部分较为复杂且如今较少使用,但它奠定了三维卷积的基础。
体素表示的主要挑战是计算量和内存消耗巨大。早期的网络只能在 32x32x32 的分辨率上运行。为了解决这个问题,研究者引入了八叉树等稀疏结构,只在物体表面附*使用高分辨率,远处则用低分辨率。但这会使得卷积操作的定义和实现变得复杂,且不易在GPU上高效运行。
体素表示的优点是规则、硬件友好,但缺点是如果不是稀疏结构,则计算开销大,且难以捕获精细结构。
多视图表示 🖼️
另一种思路是将三维问题转化为二维问题,即多视图表示。其核心思想是:不直接处理三维模型,而是从一个三维模型渲染出多个二维视图图像。
流程是:给定一个三维模型,通过虚拟相机渲染出多张图像。然后,我们可以利用成熟的二维卷积神经网络来处理这些图像,例如进行分类。最后,可以通过聚合多个视图的结果(例如使用视图池化或图神经网络)来得到最终的三维物体分类结果。
这种方法的好处是能够充分利用强大的、预训练的二维CNN模型和海量的二维图像数据。但其挑战也很明显:
- 视图选择:如何选择最具代表性的渲染视图?
- 域差距:渲染的图像(通常无纹理)与真实图像之间存在差异。
- 信息损失:将三维降维到二维,必然会丢失一部分信息。
多视图表示是连接二维与三维视觉的重要桥梁,仍有很大的研究空间。
点云表示与PointNet ☁️
点云是一种直接从扫描设备获得的、非常高效的三维表示。它对于高维数据是一种紧凑的表示形式。
点云处理的核心挑战在于其无序性。一个点云是一组点的集合,其输入顺序不应影响输出结果。因此,处理点云的神经网络必须是一个对称函数。
PointNet 的核心思想正是基于此。它利用一个共享的多层感知机独立处理每个点,然后通过一个全局池化操作(如最大池化)来聚合所有点的信息,形成一个全局特征。这保证了置换不变性。
网络核心结构可以简化为:
输入点云 (n x 3) -> 共享MLP -> 逐点特征 -> 最大池化 -> 全局特征 -> 全连接层 -> 输出
PointNet 还引入了输入/特征变换网络来对齐点云,提升了性能。它的设计简单高效,性能不俗,更重要的是,它开创了一种全新的、非传统的神经网络设计思路,即直接处理无序点集。
后续的工作(如PointNet++, DGCNN)进一步考虑了点的局部邻域信息,但点云表示如何更好地结合几何结构(如连通性)仍是开放问题。
隐式表示:DeepSDF 🌀
隐式表示,特别是有符号距离函数,是*年来非常热门的方向。SDF 定义了一个函数,对于空间中的任何一点,给出其到物体表面的最*距离,符号表示点在内部(负)还是外部(正)。
DeepSDF 的核心思想是用一个神经网络来学*这个 SDF 函数。对于单个形状,网络 f 的输入是坐标点 (x, y, z),输出是该点的 SDF 值:
sdf_value = f(x, y, z)
对于学*一个形状类别(生成模型),网络还会输入一个潜码 z:
sdf_value = f(x, y, z, latent_code)
网络通常是一个全连接网络,使用 ReLU 等激活函数。训练好后,可以通过 Marching Cubes 算法将零等值面提取出来,得到网格模型。
为什么DeepSDF效果惊人? 关键之一在于激活函数(如ReLU)。ReLU是分段线性函数,具有强大的函数逼*能力,使得神经网络能够表示出非常复杂、尖锐的形状特征。它学*到的是一种复杂的插值,能够*滑地在形状之间过渡。
DeepSDF 展示了将传统几何表示(SDF)与深度学*结合的巨大潜力,但它也有局限性,例如无法严格保证某些几何属性(如完美的圆形)。这为后续研究留下了空间。
网格表示与Pixel2Mesh 🕸️
最后,我们看看直接在网格上进行深度学*的工作,以 Pixel2Mesh 为例。其目标是从单张彩色图像生成三维网格。
这项工作的重要贡献在于,它将许多传统的网格处理操作融入了深度学*框架。网络主体是一个图卷积网络,在网格的顶点上进行操作。
关键操作包括:
- 图卷积:定义在网格图结构上,聚合邻域顶点信息来更新当前顶点特征。这类似于网格上的拉普拉斯*滑等传统迭代算法。
- 上采样:使用细分操作来增加网格分辨率。这可以看作是一种“反卷积”,在网格上引入新的顶点和面片。
- 从图像特征到网格的投影:如何将二维图像特征“附着”到三维网格顶点上,是一个重要设计。文中通过将顶点投影到图像*面来获取对应像素的特征。
此外,训练中常会加入几何约束损失,如拉普拉斯正则化,鼓励网格保持*滑,但这有时会过度*滑细节。
Pixel2Mesh 展示了将深度学*与经典几何处理工具结合的思路。如何更完美地将网格的显式连接性与深度学*的表示能力结合,仍然是一个重要的研究方向。
总结与展望 🎯
本节课我们一起学*了三维深度学*的几种核心数据表示及对应模型:
- 体素:规则,易推广,但计算代价高。
- 多视图:利用二维CNN优势,但存在域差距和信息损失。
- 点云:紧凑、直接,PointNet 开创了处理无序集合的新范式。
- 隐式曲面:如DeepSDF,用神经网络表示连续场,灵活且质量高。
- 网格:显式表示,Pixel2Mesh 等尝试将传统网格操作与深度学*结合。

贯穿始终的核心启示是:三维深度学*的研究,应当注重吸收计算机图形学与几何处理领域数十年来积累的智慧。将那些经典的算法、表示和洞察与现代深度学*框架相结合,是推动这个领域向前发展的关键。未来的研究可以在如何更高效、更精确、更物理可信地表示和处理三维几何数据上继续深入探索。




GAMES301-曲面参数化 - P1:Lecture 01 曲面参数化介绍 📖

在本节课中,我们将要学*曲面参数化的基本概念、核心应用以及主要方法。课程将从宏观角度介绍这一几何处理与建模领域的重要专题,为后续深入学*奠定基础。
GAMES在线课程与301专题介绍 🎓
GAMES是Graphics And Mixed Environment Symposium的缩写。这是一个面向图形学及相关领域的在线*台,创建于2016年。*台坚持每周举办高质量的在线学术报告,并开设了一系列在线课程。
在线课程分为不同系列。100系列是基础课程,帮助初学者快速入门。200系列是高级课程,讲解更加深入。从今年开始,我们开设300系列的专题课程,针对图形学中的特定重要课题进行深入、全面的讲解。
GAMES301是专题课程的第一门,主题是曲面参数化。本课程由中国科学技术大学数学学院的四位老师共同讲授,计划通过十节课全面介绍曲面参数化的技术细节。



课程安排如下:首先由我进行介绍,后续由傅晓明老师讲解面向离散的参数化技术,陈仁杰老师讲解连续性的参数化基础,方谦老师讲解共形参数化的基础。课程包含四次作业,助教会认真批改并提供建议。



学*本课程需要一些基础知识,例如微积分、数值方法、优化以及微分几何的基本概念。如果对某些知识不熟悉,可以在学*过程中边用边学。
什么是曲面参数化? 🗺️
曲面参数化,直观来说,就是将一个三维空间中的曲面“摊*”到一个二维*面上,并建立点对点的一一对应关系。


一个经典的例子是世界地图。地球表面是一个三维球面,而地图是二维*面。如何将球面展开成*面地图,就是参数化问题。不同的展开方式(如横版地图或竖版地图)会保持不同的几何性质(如角度或距离),适用于不同的应用场景。


另一个生活例子是剥橘子。将一个封闭的橘子皮展开成*面,如果沿着特定的割缝切割,甚至可以展开成预期的图案(如小动物)。这展示了参数化在控制展开形状方面的应用。
在数学上,曲面参数化的本质是构建一个从三维曲面(本质上是二维流形)到二维*面区域的映射 f: R³ → R²。这个映射需要是连续且一一对应的(双射)。
曲面参数化的核心应用 💡
参数化在计算机图形学和几何处理中有着广泛的应用。
纹理映射(UV映射):这是参数化最直接的应用。通过将三维曲面展开到二维*面(称为UV空间),我们可以将二维图像(纹理)上的颜色精确地附着到曲面的对应点上。这张UV图就像是曲面属性的一个代理,不仅可以存储颜色(纹理),还可以存储法向、材质属性(如金属度、粗糙度)等其他表面信息,极大地方便了编辑和处理。
几何拟合:在曲线曲面拟合中,需要将三维点云赋予二维参数坐标,才能用参数曲面(如NURBS)去逼*它们。参数化是拟合的基础步骤。
虚拟几何与层次细节:通过参数化,可以将复杂的几何信息编码到二维图像中,利用图像处理技术进行压缩、传输和多分辨率绘制。
如何度量参数化的好坏? 📐
一个好的参数化需要满足两个基本要求:形变尽量小 和 无翻转/无重叠。
形变度量:映射在局部会产生形变。我们可以用映射的雅可比矩阵 J 来分析。雅可比矩阵的行列式 det(J) 度量了局部面积的伸缩比例。det(J) = 1 表示保面积,det(J) > 0 表示未发生翻转。
更精细的形变可以通过仿射变换的奇异值 σ₁ 和 σ₂ 来度量。它们代表将一个无穷小圆变换为椭圆后的长短轴长度。
- 如果 σ₁ = σ₂,映射是保角的(共形)。
- 如果 σ₁ * σ₂ = 1,映射是保面积的。
- 如果同时满足 σ₁ = σ₂ = 1,映射是等距的(可展曲面)。
无翻转与无重叠:翻转是指三角形在映射后改变了朝向(det(J) < 0),这会导致纹理出现“鬼影”。重叠是指曲面上不相邻的区域在UV*面上被映射到了同一位置。好的参数化必须避免这两种情况,即保证映射是局部单射且全局双射。
曲面参数化的主要方法 🛠️
参数化的求解方法主要可以分为三大类。
线性方法(Tutte映射):这类方法要求将曲面边界固定到一个凸多边形(如圆、正方形)上,然后根据拉普拉斯方程求解内部顶点的位置。该方法实现简单,且能保证无翻转解的存在,但往往会产生较大的形变。
基于优化的几何方法:这类方法通过最小化一个定义在网格上的形变能量来求解参数化。例如:
- 保角参数化:最小化角度畸变。
- 保面积参数化:最小化面积畸变。
- 尽可能刚性参数化(ARAP):最小化每个三角形的刚性形变。
这些方法能得到低形变的结果,但优化过程本身不能保证避免翻转,通常需要后处理来修复翻转的三角形。
保证无翻转的优化方法:这是*年来的研究热点。该方法从一个无翻转的初始解(如Tutte映射结果)出发,在优化形变能量的同时,严格约束每个三角形的雅可比行列式大于零。这通常通过将 1/σ₁ 这类项放入能量项(障碍函数)来实现,当三角形趋向退化时能量会趋于无穷大,从而阻止翻转发生。这类方法能产生高质量且无翻转的参数化结果,但求解是一个带约束的非线性优化问题,计算复杂度较高。
参数化的扩展与思考 🧠
参数化的思想可以扩展到更广泛的领域。
映射到其他域:不仅可以映射到*面,也可以映射到球面等其他曲面,或者构建两个复杂曲面之间的一一映射(用于兼容网格生成)。
高维数据与流形学*:参数化的本质是发现高维数据的内在低维结构(本真维度)。例如,一个三维空间中的螺旋线,本质上是一维的;一个三维的曲面,本质上是二维的。从高维观测数据中寻找其低维参数表示,就是流形学*或降维的核心问题。现代深度学*方法中的自编码器,其瓶颈层学*到的潜变量,就可以看作是一种数据驱动的参数化。


端到端的参数化学*:传统的参数化与下游任务(如纹理映射、拟合)是分离的。未来的趋势是将参数化作为整个处理流程的一部分,与下游任务联合起来进行端到端的优化,以获得针对特定任务最优的映射。
总结 📝
本节课我们一起学*了曲面参数化的基本概念。我们了解到,参数化是连接三维曲面与二维表示的桥梁,在纹理映射、几何拟合等领域有根本性的应用。一个好的参数化需要权衡形变大小并避免翻转重叠。主要求解方法包括简单的线性方法、能产生低形变的优化方法,以及能严格保证无翻转的最新优化方法。最后,我们从更广义的角度理解了参数化作为数据本质维度发现工具的意义。


在接下来的课程中,其他老师将从离散、连续、共形等不同角度,深入讲解曲面参数化的各项技术与前沿进展。希望大家能通过本课程,掌握这一强大工具的核心原理与应用方法。

GAMES301-曲面参数化 - P10:共形参数化1 - 微分性质与离散算法 🌀

在本节课中,我们将学*共形映射的核心微分性质,以及基于这些性质设计的几种离散参数化算法。我们将从共形映射的保角性出发,探讨其在连续和离散情况下的数学表达,并介绍Spin变换、Circle Packing和共轭调和函数等关键概念。
1. 什么是共形映射? 📐
共形映射最关键的性质是保角性,即映射前后任意两条曲线在交点处的夹角保持不变。在离散的三角网格参数化中,这意味着我们希望映射到纹理域后,每个三角形的内角尽可能与原始空间中的内角一致。
上一节我们回顾了两种优化角度扭曲的算法:ABF和LSCM。它们都试图最小化角度变化,但都无法在离散网格上实现严格的保角。这引出了我们对连续光滑共形映射的探讨。
2. 连续共形映射的微分性质 📈
为了用严密的数学语言描述共形映射,我们需要借助微分几何。核心结论是:共形映射在每一点处都是一个相似变换(旋转与伸缩的复合)。
2.1 *面上的共形映射与柯西-黎曼方程
在复*面上,一个映射 ( f(z) = u(x, y) + i v(x, y) ) 是共形的,当且仅当其满足柯西-黎曼方程:
[
\frac{\partial u}{\partial x} = \frac{\partial v}{\partial y}, \quad \frac{\partial u}{\partial y} = -\frac{\partial v}{\partial x}
]
这等价于说,映射的微分(雅可比矩阵)在每一点都是一个相似变换矩阵:
[
J_f = s \begin{bmatrix}
\cos \theta & -\sin \theta \
\sin \theta & \cos \theta
\end{bmatrix}
]
其中 ( s > 0 ) 是伸缩因子,( \theta ) 是旋转角。这意味着该映射将无穷小的圆映射为无穷小的圆。
2.2 曲面间的共形映射
对于两个曲面间的映射,保角性要求切*面中的任意两个切向量在映射前后夹角不变。其局部微分性质同样可以表述为“每一点处是一个相似变换”,但方向由该点的法向决定,计算更为复杂。
3. Spin 变换 🔄
上一节我们介绍了共形映射的局部相似性。Spin变换提供了一种在三维空间中构造共形形变的强大工具。
3.1 核心思想
Spin变换利用四元数来表示三维空间中的旋转与伸缩。对于一个曲面变形,如果存在一个定义在曲面上的四元数场 ( \lambda ),使得变形后曲面某点的切向量等于原曲面相应切向量左乘 ( \lambda ),那么这个变形就是共形的。
3.2 实现与约束
然而,并非任意四元数场 ( \lambda ) 都能对应一个实际存在于三维空间中的曲面。它必须满足一个称为狄拉克方程的约束条件:
[
(D - \rho) \lambda = 0
]
其中 ( D ) 是一个微分算子,( \rho ) 是一个与曲面*均曲率变化相关的实值函数。求解满足该方程的 ( \lambda ) 和 ( \rho ),即可得到共形变形。算法上,这可以转化为一个特征值问题。
Spin变换的几何意义:通过设计函数 ( \rho )(可理解为期望的*均曲率变化),我们可以控制曲面局部是凸起(( \rho > 0 ))还是凹陷(( \rho < 0 )),同时保证变形是共形的。
4. Circle Packing 算法 ⭕
Circle Packing算法直接来源于共形映射“将无穷小圆映射为无穷小圆”的性质。其离散版本试图用有限大小的圆来逼*这一性质。
4.1 算法原理
给定一个三角网格,Circle Packing在其上构造一个圆填充结构:
- 每个顶点关联一个圆。
- 每条边对应的两个顶点圆彼此相切。
- 每个面对应的三个顶点圆彼此相切,且定向一致。
如果存在这样的圆填充,并且将圆的中心作为参数化后的顶点坐标,那么在圆半径无限缩小的极限下,该映射将逼*一个共形映射。
4.2 算法步骤
以下是构造Circle Packing的核心迭代步骤:
- 初始化:固定边界顶点圆的半径。
- 角度计算:对于每个内部顶点,根据其当前半径和邻居半径,计算其周围所有“相交角” ( \theta_i ) 的总和。
- 半径更新:如果角度总和 ( \sum \theta_i \neq 2\pi ),则调整该顶点圆的半径,使其趋向于满足 ( \sum \theta_i = 2\pi )。
- 迭代:对所有内部顶点重复步骤2-3,直至收敛。
- 坐标重建:根据最终确定的圆半径及相切关系,计算出所有圆心(即顶点)在*面上的位置。
局限性:经典Circle Packing算法只考虑网格的连接关系,而忽略了其几何(如边长),因此在各向异性强烈的网格上效果不佳。
5. Circle Patterns 算法 🔺
为了克服Circle Packing的局限性,Circle Patterns算法将圆的放置与网格几何相结合。
5.1 与Circle Packing的区别
Circle Patterns不是在顶点放置圆,而是在每个三角形的外接圆上做文章。对于一条边,考虑其两侧三角形外接圆在交点处的切线夹角 ( \phi_e )。共形映射的理想情况是保持这些夹角 ( \phi_e ) 不变。
5.2 算法核心
问题转化为:寻找一组新的三角形内角 ( \theta_{ij} ),使得:
- 每个三角形的内角和为 ( \pi ):( \theta_{ij} + \theta_{jk} + \theta_{ki} = \pi )。
- 由这些新内角计算出的外接圆夹角 ( \phi_e )(( \phi_e = \pi - \theta_{jk} - \theta_{ki} ) 对于边 ( e ) 对应顶点 ( i ))在内部顶点处满足“绕一圈和为 ( 2\pi )”。
- ( \theta_{ij} ) 尽可能接*原始网格的内角 ( \alpha_{ij} )。
通过优化求解满足上述条件的 ( \theta_{ij} ),再利用正弦定理等重建边长和顶点坐标,即可得到参数化结果。此方法更好地保持了原始网格的几何特征。
6. 共轭调和函数方法 ⚖️
从柯西-黎曼方程出发,我们可以得到共形映射另一个深刻性质:其坐标函数是调和函数,并且互为共轭调和函数。
6.1 数学基础
对于共形映射 ( f = (u, v) ),柯西-黎曼方程 ( u_x = v_y, u_y = -v_x ) 意味着:
[
\Delta u = 0, \quad \Delta v = 0
]
即 ( u ) 和 ( v ) 都是调和函数。同时,( \nabla u ) 和 ( \nabla v ) 处处垂直且模长相等。
6.2 离散算法思路
基于此性质,发展出两类主要算法:
- 调和1-形式法:
- 在亏格为 ( g ) 的闭曲面上,存在 ( 2g ) 维的调和1-形式空间。
- 算法通过计算一组基(与“手柄”和“隧道”相关的闭合路径积分有关),并组合出两组互为对偶的调和1-形式。
- 对这些1-形式进行路径积分,即可得到共形映射的 ( u ) 和 ( v ) 坐标。

- 边界条件法:
- 直接求解拉普拉斯方程 ( \Delta u = 0, \Delta v = 0 ) 来获得调和函数。
- 关键难点:需要设置特殊的边界条件,使得解出的 ( u ) 和 ( v ) 恰好满足柯西-黎曼方程(即梯度互为旋转90度)。
- 如何寻找这样的边界条件,涉及到共形映射的度量表示,这将是下一讲(第11讲)的核心内容。



总结 📝

本节课我们一起学*了共形参数化的微分基础与几种经典离散算法:
- 我们明确了共形映射的核心是保角性,在连续情况下表现为每一点处的局部相似变换。
- 基于局部相似性,我们介绍了在三维空间中构造共形形变的 Spin变换 方法。
- 我们探讨了两种用圆逼*共形映射的算法:Circle Packing(基于顶点圆)和 Circle Patterns(基于外接圆),后者能更好地融入网格几何。
- 最后,我们从柯西-黎曼方程出发,引出了共形映射的调和函数性质,并概述了基于共轭调和函数和调和1-形式的参数化算法思路。

这些算法从不同角度离散化和实现了共形映射的理想性质。下一讲(第11讲),我们将深入探讨共形映射的度量表示,并学*如何利用它来设计更高效的参数化边界条件。


GAMES301-曲面参数化 - P11:Lecture 11 共形参数化2-离散共形等价类、Möbius变换与曲率流 📐
在本节课中,我们将学*共形映射在曲面黎曼度量上的性质,并介绍相关的离散算法。主要内容包括:光滑曲面上的共形变换与黎曼度量、离散共形等价类的定义、分片Möbius变换,以及利用曲率流求解参数化的方法。
第一部分:光滑曲面上的共形变换与黎曼度量 🔄
上一讲我们介绍了共形映射在映射微分方面的性质。本节中,我们将从曲面的内蕴性质——黎曼度量出发,来理解共形变换。
黎曼度量是定义在曲面切空间上的一个对称、正定的双线性函数。它可以理解为切向量的内积,用于衡量长度和角度。对于一个光滑映射,如果它是共形映射,那么它诱导的新黎曼度量 g' 与原始度量 g 之间满足以下关系:
公式: g' = e^{2λ} * g
其中,λ 称为对数共形因子。这个关系表明,共形变换在局部相当于对度量进行一个统一的伸缩。黎曼度量和由此定义的高斯曲率都是曲面的内蕴量,与曲面在空间中的具体嵌入形状无关。
曲面单值化定理指出,任何曲面都可以通过一个共形变换,变为具有常数高斯曲率的曲面(如球面、*面或双曲面)。这为曲面参数化提供了理论基础:要将曲面共形地映射到*面区域,我们需要找到一个新的度量,使其高斯曲率在目标域上(例如*面内部曲率为0,边界测地曲率为常数)。
高斯曲率 K 与对数共形因子 λ 的关系由以下Yamabe方程描述:
公式: K' = e^{-2λ} (K - Δλ)
其中,K' 是目标曲率,Δ 是当前度量下的拉普拉斯算子。这个非线性偏微分方程是许多共形参数化算法的核心。
第二部分:黎曼度量的离散化与离散共形等价类 🔺
上一节我们介绍了光滑理论。现在,我们来看如何在三角网格上离散化这些概念。
在三角网格上,黎曼度量可以离散化为边长。假设我们在每个顶点 i 上定义了一个对数共形因子 λ_i。那么,在共形变换下,一条边 (i, j) 的新长度 l'_{ij} 与原始长度 l_{ij} 的关系可以定义为:
公式: l'_{ij} = e^{(λ_i + λ_j)/2} * l_{ij}
另一种定义离散共形等价类的方式是通过边长交叉比。对于一个四边形,其交叉比定义为两组对边边长乘积之比。在共形变换下,这个交叉比保持不变。
以下是两种主要的离散共形等价类定义方法:
- 顶点对数因子法:为每个顶点分配一个对数共形因子
λ_i,新的边长由上述公式计算。 - 边长交叉比法:约束网格每条边的交叉比在变换前后保持不变。
这两种定义在数学上是等价的。基于这些定义,我们可以将参数化问题转化为优化问题:寻找一组 λ_i 或新的边长,使得网格内部顶点角度和为 2π,边界顶点角度和符合目标形状要求,从而将网格参数化到*面。
第三部分:分片Möbius变换 🌀
前面我们讨论了基于度量的方法。现在,我们介绍另一种视角:分片Möbius变换。
Möbius变换是复*面上的一种分式线性变换,形式为:
公式: f(z) = (a*z + b) / (c*z + d),其中 a, b, c, d 为复数且 ad - bc ≠ 0
它可以分解为*移、旋转伸缩和反演的组合。Möbius变换具有保圆性(将圆或直线映射为圆或直线)。
在三角网格参数化中,我们可以在每个三角面片上独立定义一个Möbius变换,将当前三角形映射到目标三角形。但为了整个映射是连续的,相邻面片上的Möbius变换必须在公共边上满足一致性条件。
通过为这些分片Möbius变换添加不同的约束,可以恢复前两讲提到的共形映射定义:
- 约束交叉比的模长不变,等价于保持边长的交叉比(第二部分的度量观点)。
- 约束外接圆交角不变,等价于保持三角形的外接圆交角(第十讲的“保角”观点)。
- 如果同时满足以上两个强约束,则整个变换退化为一个全局统一的Möbius变换,刚性很强。
因此,分片Möbius变换框架提供了一个更广阔的空间来定义和优化具有特定性质的映射。
第四部分:基于曲率流的参数化算法 ⏳
回顾第一部分,求解共形参数化需要解Yamabe方程。本节介绍一种实用的数值方法:曲率流。
其核心思想是设计一个关于度量(或对数共形因子 λ)的能量函数,使其最小值点恰好满足目标曲率条件。然后,我们通过梯度下降法(即曲率流)来最小化这个能量。
Ricci流和Calabi流是两种重要的曲率流。在离散网格上,算法流程如下:
- 初始化每个顶点的对数共形因子
λ_i(例如全为零)。 - 根据当前
λ_i计算新边长,进而计算每个顶点的当前离散高斯曲率K_i。 - 计算当前曲率
K_i与目标曲率K'_i的差值。 - 根据曲率流的梯度下降方向(例如
dλ_i/dt = (K'_i - K_i))更新λ_i。 - 在更新过程中,如果新边长导致三角形退化(不满足三角不等式),则需要进行边翻转操作来改善网格质量,然后继续迭代。
- 重复步骤2-5,直到曲率差值足够小。

当迭代收敛后,我们就得到了目标曲率分布下的新边长(即新度量)。最后,通过一个简单的广度优先遍历过程,就可以根据这些边长将三角网格“摊*”到目标域(如圆盘或*面矩形)上,得到最终的参数化坐标。


本节课中,我们一起学*了共形参数化的度量观点。我们从光滑曲面的黎曼度量和Yamabe方程出发,理解了共形变换如何改变曲率。接着,我们探讨了如何在三角网格上离散化这些概念,定义了顶点对数因子和边长交叉比两种等价类。然后,我们介绍了分片Möbius变换这一更通用的框架。最后,我们详细讲解了利用曲率流求解Yamabe方程、从而得到共形参数化的完整算法流程。这些内容为理解和实现复杂的曲面参数化方法奠定了坚实的基础。


GAMES301-曲面参数化 - P12:Lecture 12 锥奇异点参数化应用 📐
在本节课中,我们将要学*共形参数化中的一个重要应用:锥奇异点参数化。我们将探讨为何需要引入锥奇异点,回顾相关的研究工作,并介绍启发式与优化算法,最后了解其相关应用。
什么是锥奇异点参数化? 🤔
上一节我们介绍了共形映射的基本概念,本节中我们来看看如何通过引入特殊点来改善参数化质量。
考虑将流形 M 映射到*面 R² 的一个共形映射 g,其对数共形因子 λ 描述了度量在变化前后的缩放程度。λ 可以用来衡量共形映射的面积扭曲。如果 λ 的值(负值很小或正值很大)导致*面上纹理贴到模型上产生严重的面积扭曲,就会给纹理绘制增加工作量。
为了解决这个问题,产生了锥奇异点参数化方法。该方法通过在模型表面合理的位置放置锥奇异点,能够显著降低共形映射的面积扭曲。
一个简单的例子是圆锥面。除了锥点处高斯曲率不为零,曲面其他地方的高斯曲率都是零。如果直接将圆锥摊*到*面上,需要将锥点处的高斯曲率也变为零,这会导致该点附*的度量严重收缩(即 λ 为很负的值),从而产生较大的面积扭曲。但如果我们沿着连接边界和锥点的母线将圆锥剪开,剪开后得到一个扇形,就可以完全无扭曲地摊*到*面上。
曲面上的锥奇异点,指的就是这样的孤立点。我们不再要求所有内部点的高斯曲率都为零,而是允许高斯曲率聚集在一些孤立的锥点上。除了边界和这些奇点,其他地方的高斯曲率都为零。选择合适的锥点位置,就能有效降低面积扭曲。
从一种极端情况来看面积扭曲和锥奇异点的关系:如果把曲面上所有曲率不为零的点都当作锥奇异点,那么共形映射就是恒等映射。我们需要将所有顶点连成的曲线剪开,使其变成一个拓扑同胚于圆盘的面片,这样就可以无扭曲地放在*面上。总体上,锥奇异点的数目和共形映射的面积扭曲之间是一个互相制约的*衡关系。使用越少的锥奇异点,共形映射就不可避免地产生较大的面积扭曲;而希望面积扭曲低,就需要在模型上放置更多的锥奇异点。
在实际应用中,我们需要在低扭曲和锥奇异点数量之间做出取舍。纹理绘制和编辑希望低扭曲,但锥奇异点多会导致两个问题:
- 需要沿着锥奇异点的连线剪开曲面才能摊*,这会导致剪开处左右两边的纹理不一致,在纹理编辑时需要注意这些接缝,因此希望接缝越少越好。
- 锥奇异点越多,展开的接缝线就越长,可能导致参数化区域狭长、边界绕来绕去,增加纹理绘制和编辑的难度。
锥奇异点除了应用在纹理贴图上,也可用来建立曲面间映射的对应特征点。如果一组锥奇异点使得曲面能以很小的面积扭曲映射到*面上,那么可以视作该锥奇异点所对应的共形变换产生的离散度量,与原始曲面的度量*似一致。变换后离散度量上的锥奇异点(即曲面上高斯曲率不为零的点)可以作为曲面的特征点,用于建立形状之间的映射。锥奇异点越少,人工选择对应的工作量就越少。
第三个应用是,当锥奇异点的曲率限制为 π/2 的整数倍时,参数化对应于一种特殊的参数化,称为旋转无缝参数化。如果锥奇异点的曲率不是 π/2 的整数倍,会导致接缝处的纹理走向不一致。限制为 π/2 的整数倍时,棋盘格纹理在接缝处的走向正好对齐。这类曲率为 π/2 整数倍的锥奇异点,还可以用于曲面上的向量场、交叉场设计以及四边形网格生成。此时,锥奇异点的数量决定了场和四边形网格的奇异点数量。对于带 π/2 整数倍约束的锥奇异点,我们也希望其数量更少。
锥奇异点问题的建模 🧮
上一节我们了解了锥奇异点的作用,本节中我们来看看如何用数学语言描述这个问题。
在上一讲中,我们介绍了共形映射下描述高斯曲率和对数共形因子的 Yamabe 方程,以及运用梯度流的方法来做点采样上的参数化。事实上,Yamabe 方程可以用来建模锥奇异点分布问题。锥奇异点就是高斯曲率不为零的点。
我们用狄拉克函数 δ 表示流形在某个点 v_i 处的奇异性(在该点不为零,在其他地方为零),且在整个曲面上的积分为 1。可以将 δ 视为一个半径为 ε 的圆盘区域在 ε 趋于零时的极限。高斯曲率在这种锥奇异点下的分布,可以写成这些狄拉克函数的线性组合。
在2008年的一篇论文中证明了,当目标高斯曲率为这种锥奇异点的分布情形时,Yamabe 方程中的 e^(2λ) 项可以省略,等式依然成立。此时方程变成一个线性微分方程。2008年 Ben-Chen 等人利用这个结论,并利用三角网格上的分片线性有限元对这个线性微分方程进行离散,使其变成一个稀疏线性方程组。
其中,λ 是顶点上的离散对数共形因子,K' 和 K 分别是三角网格上顶点变化后和变化前的高斯曲率,L 是拉普拉斯矩阵(常用余切权重拉普拉斯矩阵)。用有限元离散后就变成线性方程组的形式,这是一种逼*。
因此,锥奇异点参数化的主要问题就变成:在满足上述线性方程的条件下,尽可能达到锥奇异点分布的数量和面积扭曲之间的*衡。我们将分为普通锥奇异点参数化,以及要求目标锥奇异点曲率为 π/2 整数倍约束两个方面来介绍。因为整数约束是一个难求解的整数规划问题,所以求解时会有一些技巧。
启发式算法 🛠️
上一节我们建立了问题的数学模型,本节中我们来看看一些启发式的求解算法。
启发式算法的出发点基于一个经验:在参数化中,原始曲面内部高斯曲率的极值点,在参数化后往往具有很高的扭曲。因此,一个启发式策略是直接在原始曲面高斯曲率的极大值和极小值区域放置锥奇异点。图中 colormap 显示的是网格原始的高斯曲率,黑色的点就是放置锥奇异点的位置。这是一种根据曲面几何放置锥奇异点位置的方法。
启发式的第二种策略是:先假设曲面没有奇点(即锥奇异点数量为零)。此时目标曲率 K' 除了边界上的曲率不为零,内部的曲率都为零。然后求解这个线性方程,得到一个对数共形因子 λ 的分布。接着,我们会发现 λ 有一些极值点,这些点往往对应面积扭曲较大的区域。因此,我们可以在 λ 的这些极值点或其他特定位置放置锥奇异点。这是另一种策略。
当我们确定了锥奇异点的放置位置后,还需要确定锥奇异点的曲率值。我们知道位置,但还不知道 K' 的值是多少。由于除了锥奇异点,曲面其他地方的曲率都变成了零,剩下的曲率都集中到了这些奇点上,而且这些曲面的高斯曲率总和必须满足高斯-博内定理(求和等于 2π 乘以曲面的欧拉示性数)。因此,可以看成曲率为零的点的曲率都转移到了奇点上面。
我们可以把这种曲率的转移模拟成在三角网格边上进行随机行走。如果一个点 i 不是锥奇异点,那么它的曲率会以概率 p_ij 随机游走到邻居点 j。权重 w_ij 是行走的概率,可以直接使用3D网格的余切权重。从这个点往周围行走的概率之和为 1。如果区域是奇点,那么曲率只会走进来,不会走出去,所以我们让从 i 到其他点的概率都是零,它自身走向自身的概率为 1。利用这种随机行走,随着时间变化,所有的曲率会逐渐从其他地方聚集到这些奇点处。最后求解*衡状态,获得奇点处的最终曲率值 K'。
另一篇文章选择的启发式策略是另一个角度:初始时给定一个小的阈值 ε,计算 |K'| < ε 的顶点集合(如图中白色区域)。我们让这些白色区域的曲率直接变成零,然后把剩下的曲率都集中到黄色和蓝色的区域上。随着 ε 逐渐增加,变成零的区域会逐渐增大,最后非零的曲面区域会逐渐收缩到一些孤立点。当所有非零曲率的点都变成孤立点时,算法停止。这些孤立的曲率分离点就被当作锥奇异点的分布。
这个算法中需要确定的是:当曲率绝对值小于 ε 的区域其曲率变成零时,剩下的那些点上面的曲率应该怎么更新?因为满足曲率在集合 {i: |K'_i| < ε} 上等于零的向量有很多,对应的目标曲率分布取值不唯一。因此,我们需要选取某种意义下的最优共形映射对应的目标曲率 K'。这种最优性可以用映射的面积扭曲来衡量,即对数共形因子 λ 的加权 L² 模。如果加权 L² 模等于零,说明 λ 都是零,网格本身共形映射没有面积扭曲;如果这个值很大,说明 λ_i 有很大的正值或很小的负值,面积扭曲会很大。
此时更新 K',就是求解一个关于 λ 的、带线性约束的最小二乘问题,最小化其加权 L² 模,并满足在那些曲率被设为零的顶点上,其拉普拉斯等于原始的曲率(因为目标曲率为零)。解这个带线性约束的最小二乘问题,就可以求出最优的 λ,把这个 λ 带入 Yamabe 方程就可以求出新的目标曲率 K'。然后按照算法迭代:更新 K' 后,检查是否还有连通的非零点,如果所有点都是孤立点则终止;否则增加 ε,再计算新的曲率小于 ε 的顶点集合,直至收敛到只剩下一些孤立的锥奇异点。
对于需要约束锥奇异点曲率为 π/2 整数倍的情况,该文章也提供了一个后续策略。此时我们算出来的一些固定的锥奇异点,其曲率值不一定是 π/2 的整数倍。我们需要对它进行取整,即每次让最靠* π/2 整数倍的点,将其曲率变成 π/2 的整数倍。例如,曲率是 0.48π,就把它变成 0.5π(即 π/2)。不能一次性把所有点都取整,因为高斯曲率值要满足求和等于 2π 乘以欧拉示性数,每个都就*取整后,其和可能不等于这个值。
因此,每次只对最靠*整数倍的一个点进行取整。设 R 表示锥奇异点曲率已是 π/2 整数倍的顶点集合,T 表示对应的取整后的曲率值。对于剩下的锥奇异点,我们更新它们的新曲率,使得对应的对数共形因子加权 L² 模最小,并满足约束:在非锥奇异点位置曲率为零;在我们选定的取整位置,曲率为 π/2 的整数倍 T。求解这个最小化问题,更新 λ 和其他锥奇异点的曲率值。然后,在剩下的锥奇异点中,再选取最靠* π/2 整数倍的点进行取整,重复此迭代,直到所有顶点曲率都取整到 π/2 的整数倍,最后形成一个旋转无缝的参数化。
基于优化的算法 ⚙️
上一节介绍的启发式策略有一个明显问题:它根据经验(原始曲面内部高斯曲率的极值点往往在参数化后有较高的面积扭曲)生成锥奇异点分布,但不能保证一定是最优的。例如,对于某个模型,选取高斯曲率最大的点放置一个锥奇异点来优化面积扭曲,会产生较大的扭曲;直接解线性 Yamabe 方程,在得到的 λ 的极值点放一个锥奇异点,最后算出的面积扭曲也比较大。但是,如果在*坦的位置上放一个点,产生的面积扭曲反而会更小。因此,问题在于我们能否直接从优化的角度,计算出一个更好的锥奇异点分布。
考虑到 Yamabe 方程的线性化提供了对数共形因子 λ 和高斯曲率 K 的约束关系,锥奇异点参数化中的问题就可以表示成优化对数共形因子 λ 的加权面积模(表示扭曲),同时为更少的锥奇异点设计一个目标能量。这样我们可以直接优化锥奇异点能量和面积扭曲能量,使它们在约束下达到更好的*衡。
由于我们希望产生更少的锥奇异点,这对应于对曲率的稀疏性惩罚。但直接用曲率的 L¹ 模优化,并不一定能产生稀疏的点。例如,对于一个球面,其高斯曲率每一点都大于零,对它取 L¹ 模,其和始终是常数(等于 2πχ),优化 L¹ 模并不能将曲率聚集到固定的锥奇异点,最终结果可能是所有顶点都是奇点。因此,基于优化的方法需要采用能产生更强稀疏性的能量形式。
2018年的一篇文章将稀疏的锥奇异点高斯曲率分布函数,看成一些狄拉克测度的线性组合。其思想是利用对偶空间,将函数空间的能量范数变成测度的测度模,进而建模成优化共形映射的面积扭曲和测度模的加权和,并满足线性化的 Yamabe 方程。通过一种对偶形式转化,最终变成一个凸优化问题,在三角面片上用分片线性有限元逼*,可以用梯度下降法、ADMM 等方法求解。实验测试发现,这种测度模比 L¹ 模能带来更强的稀疏性。
相比于2018年的建模,我们实验室在2021年的工作中做了两个改变:
- 直接用
L⁰范数(非零元素个数)作为锥奇异点能量,这比测度模的稀疏惩罚性更强,因为L⁰范数直接等价于高斯曲率不为零的点的个数。 - 将锥奇异点和面积扭曲的加权能量,转变为在给定面积扭曲上界下,优化锥奇异点的总数量(即其
L⁰范数)。这么做的原因有两个:第一,扭曲在参数化应用中是更强调控制的量,通过这种有界形式更容易调节参数来控制想要的扭曲结果;第二,可以方便地更换面积扭曲的表达形式。例如,要控制共形映射在曲面上的最大面积扭曲,相当于控制对数共形因子的L^∞范数。但在锥奇异点情况下,连续的对数共形因子分布在锥点处趋于无穷,直接取L^∞范数能量无穷,无法优化。我们用L^p范数(p较大)来逼*L^∞范数。如果使用加权形式优化L^p范数不太好优化,但将其表示为有界约束,就可以用*似投影的方法迭代求解,简化优化难度。
针对建立的 L⁰ 范数优化模型,我们测试了现有的对 L⁰ 范数逼*的常见方法。L¹ 范数不能产生孤立的锥奇异点,会产生较大区域的分布。用光滑的模拟函数(如 L^p 范数,p<1)是一种*滑逼*,通过调节参数可以接*示性函数,能产生比较稀疏的锥奇异点,但还有改进空间。我们可以用重新加权的 L¹ 范数来进行求解。
重新加权的 L¹ 范数迭代框架是:用一系列加权的 L¹ 范数子问题来逼* L⁰ 范数。每个子问题是加权 L¹ 范数优化,权重 w_i^(m+1) 由上一步迭代求出的最优曲率 K^(m) 决定,具体为 w_i^(m+1) = 1 / (|K_i^(m)| + ε),其中 ε 是一个小的正数。这样设计的权重意图是:增大对高斯曲率靠*零的分量的惩罚,使得这些分量最终不断趋于零;而曲率不为零的分量,其权重会变小,从而慢慢形成孤立的点,即锥奇异点。初始权重全部设为 1(即初始为普通的 L¹ 优化),随着迭代,逐渐惩罚更多靠*零的顶点,使其曲率变为零,最终收敛到一些孤立点。收敛条件是相邻两次迭代最优解的误差小于阈值。
每个加权 L¹ 子问题也是一个凸优化,可以用交替方向乘子法等求解。实验中,我们的算法可以很好地控制面积扭曲的上界。对于同一模型设置不同的扭曲上界,锥奇异点数目会随着上界的减小而增加,但最后产生的锥奇异点也很稀疏。虽然 L⁰ 范数优化是 NP 难问题,目前无法有效找到全局最优,但我们的结果仍产生了很好的稀疏性,锥奇异点数目也较少。
我们也可以用 L^p 范数(p 增加)来替代 L^∞ 范数,以优化最大面积扭曲。实验表明,对于相同的扭曲上界,当 p 从 2 增加到 5 时,最大的面积扭曲会被逐渐减小。p 较大时,面积扭曲很大的区域会消失,更倾向于一致的扭曲。L^p 范数能有效惩罚面积扭曲的极值点。
由于 L⁰ 优化是非凸问题,我们的算法只能用一系列凸子问题去逼*,不能保证获得全局最优的锥奇异点分布。因此,我们将我们的优化算法与2018年那篇测度模优化的文章在大型数据集上进行了测试对比。结果表明,在约69%的模型上,我们的算法在相同扭曲下能产生更稀疏的锥奇异点;在约30%的模型上,结果与他们相当;在不到1%的模型上,我们的锥奇异点数会比他们多1-2个。原因可能是 L⁰ 优化是*似求解,可能会比全局最优解多一两个点。




如果我们考虑带整数约束(曲率为 π/2 整数倍)的锥奇异点生成问题,我们实验室在2022年的文章中,在2021年建模的基础上增加了一个整数约束:优化目标曲率的 L⁰ 范数,使得面积扭曲小于给定上界


GAMES301-曲面参数化 - P13:Lecture 13 参数化应用3-曲面对应与高阶多项式映射 🎯
在本节课中,我们将学*曲面参数化的两个重要应用:如何建立两个不同曲面网格之间的一一对应关系(曲面对应),以及如何定义和验证高阶多项式映射(如Bézier映射)的有效性。我们将从基本概念入手,逐步讲解核心算法和关键技巧。
概述:曲面对应
上一讲我们介绍了参数化在纹理映射和重新网格化中的应用。本节中,我们来看看如何利用参数化技术,在两个不同的曲面之间建立精确的对应关系。
曲面对应的核心目标是,在两个曲面网格(例如一匹马和一头长颈鹿)之间建立一个双射(一对一映射)。这意味着不仅顶点要对应,每个三角形面片及其内部的点也要精确对应。这种映射在形状插值、变形动画和属性传递等应用中至关重要。
以下是曲面对应的基本定义和两种常见表述:
- 曲面映射:指两个分片线性曲面之间数学上的一一映射。
- 相容网格:指两个网格具有完全相同的连接关系(相同的三角形面片连接方式)。如果有了相容网格,顶点和面的对应就自然建立了。
有了这种对应关系,我们就可以实现复杂的应用,例如在两个形状之间进行*滑的变形过渡(形变),或者将颜色、纹理、法线甚至物理属性从一个模型传递到另一个模型。
基于参数化的曲面对应生成方法
我们的目标是:给定两个连接关系不同的输入网格(M1 和 M2),以及用户指定的一些对应特征点,生成一个低扭曲的、一一对应的曲面映射,或者直接生成一对相容网格。
基于参数化的方法通常分为以下三步:
- 构造公共参数域:找到一个共同的、简单的二维区域作为“中介”。
- 计算低扭曲参数化:分别将两个曲面网格映射到这个公共参数域上。
- 组合映射或生成相容网格:通过复合两个映射得到曲面间的直接对应,或者利用参数化信息对原始网格进行重网格化,使其连接关系一致。
整个流程的核心思想是:M1 -> 公共域 <- M2。通过公共域这个“桥梁”,间接建立 M1 和 M2 的联系。M1 到 M2 的映射可以表示为:f = f2^{-1} ∘ f1,其中 f1 和 f2 分别是 M1 和 M2 到公共域的映射。
方法一:基于三角形域的映射
一种直观的思路是为每个曲面选择一组对应的三角形区域,分别将它们参数化到*面上的三角形域。因为三角形域是凸的,我们可以使用之前学过的保双射参数化方法。
具体步骤如下:
- 在两个曲面上手动或自动分割出拓扑相容的三角形区域对(即连接关系相同的区域)。
- 将每对区域分别参数化到同一个*面三角形上。
- 由于三角形到三角形的映射很容易建立(例如通过仿射变换),从而得到曲面区域间的映射。
然而,这种方法的第一步——为两个差异很大的模型手工构造出相容的三角形区域分割——是非常困难且不*凡的。
方法二:基于公共*面参数域的映射
为了简化公共域的构造,我们可以将两个曲面都映射到同一个*面区域。一个经典算法(2014年)流程如下:
首先,我们需要为输入的两个网格建立对应的切割路径。
- 构造对应的切割路径:
- 输入是两组对应的特征点。
- 在各自网格上,用最短路径连接这些特征点,形成一个封闭或开放的路径图。
- 确保两个网格上的路径图拓扑一致。路径上点的对应可以通过弧长参数化简单建立。
接下来,利用参数化技术生成公共域。


- 共同边界参数化:
- 将两个带有对应切割路径的曲面,分别参数化到*面。
- 关键约束:在参数化过程中,强制要求两个曲面在对应切割路径上的点,在参数域中落在完全相同的位置。
- 这样,得到的两个参数化将共享完全相同的二维参数域边界和内部区域。这个共享的二维区域就是我们的公共参数域。

这个优化问题的数学描述如下:
最小化: Distortion(φ) + Distortion(ψ) # φ和ψ分别是两个曲面的参数化
约束条件:
1. 所有三角形无翻转 (det(J) > 0)。
2. 对应切割路径上的点满足:φ(path_M1) = ψ(path_M2)。
最后,通过映射复合得到最终结果。
- 组合映射:
- 得到映射 φ: M1 -> D 和 ψ: M2 -> D (D是公共域)。
- 则从 M1 到 M2 的映射为:
f = ψ^{-1} ∘ φ。 - 由于参数域 D 可能存在自交(如果映射不是全局单射),在计算 ψ^{-1} 时需要从边界开始追踪路径,以避免歧义。
方法优缺点:
- 优点:概念清晰,将复杂的曲面匹配问题分解为参数化问题。
- 缺点:最终结果的质量严重依赖于第一步切割路径的构造。切割路径的选择会极大地影响参数化的扭曲程度。
注意:曲面对应是一个研究广泛且深入的领域,方法众多(如基于函数映射、最优传输等)。本课程仅介绍了基于参数化的经典思路。在实际研究中,需要全面评估不同方法的稳定性、鲁棒性和效率。
概述:高阶多项式映射
前面我们介绍的都是分片线性映射,即每个三角形映射到参数域后仍是线性三角形。现在,我们探讨更一般的分片多项式映射,例如将曲面三角形映射到参数域中的一个曲边三角形(由多项式定义)。
高阶多项式映射在有限元分析和高精度几何处理中非常重要,主要原因如下:
- 更好地逼*曲面:CAD模型通常具有曲边,高阶单元能更精确地贴合原始几何。
- 更高的数值精度:在有限元计算中,高阶基函数通常能提供更好的数值收敛性。
- 计算效率:达到相同精度时,高阶网格可能更稀疏,从而降低计算量。
常见的多项式基函数有:
- Bézier基:具有非负性、归一性和递归定义,非常适合几何设计。
- 拉格朗日基:在插值节点上值为1,其他节点为0,形式简单。
- 幂基:
{1, x, x^2, ...},最直接的表示。 - 正交多项式基:如勒让德多项式,在数值积分中优势明显。
这些基函数张成相同的多项式空间,可以通过线性变换相互转换。
高阶映射的有效性判断
对于一个线性映射,判断其是否翻转(保持定向)很简单:只需计算其雅可比矩阵的行列式,在单个三角形内它是一个常数,检查该常数是否大于0即可。
对于高阶多项式映射(例如Bézier映射),情况变得复杂。它的雅可比矩阵行列式不再是一个常数,而是一个关于参数坐标的多项式函数。我们需要判断这个多项式函数在整个参数域(如一个参考三角形)内是否恒大于0。
以将曲面三角形 T 映射到*面曲边三角形 T̃ 为例,我们常引入一个中间映射:T -> τ -> T̃,其中 τ 是一个标准的等腰直角三角形。复合映射的雅可比行列式为:
det(J) = det(J_{τ->T̃}) * det(J_{T->τ})
由于 det(J_{T->τ}) 是常数,问题转化为判断 det(J_{τ->T̃}) 在 τ 上是否恒正。
在不同基下判断多项式正性
设 D(ξ, η) = det(J_{τ->T̃}) 是一个关于参数 (ξ, η) 的多项式。我们在不同的基下展开它,以寻找判断其正性的方法。
以下是几种基函数的分析:
-
在拉格朗日基下:
- 多项式表示为
D(ξ, η) = Σ_i d_i * L_i(ξ, η)。 - 拉格朗日基函数
L_i(ξ, η)在定义域内不保持非负,因此即使所有系数d_i > 0,也不能保证多项式恒正;反之亦然。需要通过求极值点等复杂计算来判断,对于高次多项式效率很低。
- 多项式表示为
-
在幂基下:
- 多项式表示为
D(ξ, η) = Σ_{i,j} c_{ij} * ξ^i η^j。 - 在参数域 (ξ>=0, η>=0) 内,基函数
ξ^i η^j非负。 - 充分条件:如果所有系数
c_{ij} > 0,则D(ξ, η) > 0。但这条件太强,且缺乏几何不变性(旋转后系数全变),不实用。
- 多项式表示为
-
在Bézier基下(推荐):
- 多项式表示为
D(ξ, η) = Σ_{i+j+k=n} b_{ijk} * B_{ijk}^n(ξ, η)。 - 关键性质:Bézier基函数
B_{ijk}^n(ξ, η)在三角形参数域内非负,且具有凸包性质。 - 由此可得两个实用的界:
- 下界:
min(D) >= min({b_{ijk}})。所有Bézier系数的最小值。 - 上界:
max(D) <= max({b_{ijk}})。所有Bézier系数的最大值。
- 下界:
- 判断准则:
- 若
min({b_{ijk}}) > 0,则D(ξ, η)在整个域上恒正,映射有效(无翻转)。 - 若
max({b_{ijk}}) < 0,则D(ξ, η)恒负,映射无效(存在翻转)。 - 若
min({b_{ijk}}) < 0 < max({b_{ijk}}),则无法直接判断。
- 若
- 多项式表示为
细分算法处理不确定情况
当Bézier系数的最小值小于0而最大值大于0时,我们无法立即做出判断。此时,可以利用Bézier表示的细分特性。
算法流程如下:
- 计算映射
D(ξ, η)的Bézier系数{b_{ijk}}。 - 若
min({b_{ijk}}) > 0,判定为有效;若max({b_{ijk}}) < 0,判定为无效。 - 若无法判定,则对参考三角形 τ 进行细分,得到更小的子三角形。
- 在每个子三角形上,重新计算
D(ξ, η)的Bézier系数(细分过程可快速递归计算),并回到步骤2进行判断。 - 重复细分过程,直到所有子区域都能判定,或达到预设的细分深度。如果达到深度后仍存在无法判定的区域,则认为该区域接*退化,映射无效。
这种方法将全局多项式正性判断,转化为一系列局部、更简单的判断,并且利用Bézier格式的优良性质,使得细分和系数更新非常高效。该方法可以推广到四面体等其他单元类型。
总结
本节课中我们一起学*了曲面参数化的两个高级应用。
首先,我们探讨了曲面对应问题,学*了如何通过构造公共参数域并计算共同边界参数化,来建立两个不同曲面之间低扭曲的一一映射。这种方法将复杂的曲面匹配问题转化为熟悉的参数化优化问题。

其次,我们深入研究了高阶多项式映射的有效性判断。核心挑战在于判断其雅可比行列式(一个多项式)在参数域上是否恒正。我们分析了在拉格朗日基、幂基下的局限性,并重点介绍了在Bézier基下的解决方案:利用Bézier系数的凸包性质得到行列式的上下界,并结合细分算法,高效、可靠地判断映射是否无翻转。这套方法为使用高阶单元进行精确几何建模和仿真奠定了基础。


GAMES301-曲面参数化 - P14:参数化在产业中的应用(1) 🎮

在本节课中,我们将学*曲面参数化技术在游戏工业中的具体应用。课程将首先概述游戏产业中三维建模的基本流程,然后深入探讨参数化作为核心技术,在材质映射、光照烘焙等关键环节所扮演的角色及其实现细节。






课程概述

课程已接*尾声。在前面的课程中,三位老师从不同方面介绍了曲面参数化在几何处理中的应用。我们重温一下参数化问题:如何将一个位于三维空间中的曲面(本质上是二维流形)找到其与*面拓扑同胚的映射函数。直观来讲,将一个离散成三角网格的曲面参数化,等价于寻找网格顶点和三角形之间的一一对应关系。我们希望这个映射能保持曲面的一些几何特性,如角度、边长、面积等度量,同时极小化形变扭曲,并避免翻转和自交。本课程始终围绕如何建立一一对应、极小化扭曲以及保持特性展开。相信同学们通过课程,能够了解曲面参数化的基本方法、理论和求解技术。

在第一节课中提到,参数化的核心思想是降维。对于高维曲面,同样有类似的思想在背后支撑。曲面参数化是一个基本问题,在许多几何处理应用的背后,都有参数化技术作为基础支撑。课程录屏已在B站发布,如需进一步理解,可以观看回放。


今天开始,我们将探讨参数化在产业中的大量应用。在今天下午和明天上午的课程中,将介绍几个在产业中非常典型的应用,其中第一个就是游戏产业。







游戏产业中的三维模型与参数化


很多同学都喜欢玩游戏,特别是3A级游戏,例如左上角的《古墓丽影》最新版。三维游戏中存在大量的几何模型。

模型上方的贴图就是参数化技术的核心——如何用一张图来*似表达曲面上的各种属性,例如纹理。实际上,曲面上不仅存储纹理,任何信息如材质、法向等都可以这种方式存储。因为曲面上的一个点可以在二维参数化*面(UV空间)中找到对应点。所以,要在曲面上存储信息,完全可以在二维参数*面上进行。纵坐标称为V,因此在工业界也称为UV图,学术界更多称为纹理贴图,这两个概念类似或几乎等价。


对于复杂模型,最终可能会有很多分片。可以看到这个建筑有很多不同的部件。同样,我们把部件分别参数化,然后*摊到UV空间中。这样,对模型的任何处理,如渲染、光照等,都可以在参数空间上进行对应。因此,UV图的展开是游戏工业界(以及任何与三维模型相关的工业界)非常基础的一项技术。



今天,我们很高兴邀请到腾讯互娱CROS计算机图形技术团队的负责人黄旭华先生,来详细介绍在游戏工业中3D建模参数化背后的故事、应用以及未来发展。




游戏工业中的三维建模工具链



大家好,很荣幸能在GAMES 301给大家讲解曲面参数化技术在游戏工业中的应用。我来自腾讯IEG(腾讯互娱)的CROS团队,负责计算机图形技术。CROS体系主要进行游戏公共技术的研发,例如游戏底层用到的引擎技术、服务器技术或美术生产技术等。我们对游戏工业的底层技术非常了解。今天我将借此机会,谈谈参数化算法在游戏工业中的工业级应用以及我们的创新。

我的内容大致分为三部分:
- 首先介绍游戏工业的三维建模工具链,让大家对游戏商业建模有大致了解。
- 第二部分重点介绍一个核心案例,深入探讨参数化的一个非常深入的应用。
- 再介绍几个辅助案例,让大家理解参数化应用的广泛性,它不仅仅用于特定地方。


游戏建模中,任何一个三维模型首先需要有造型部分,即勾勒出模型的三维结构。接下来是材质,指造型表面的一些属性。因为我们建模时只需要渲染给用户看,游戏里只有表面重要,内部有什么不重要,所以其表面信息以二维流形的方式存储。这是第二部分。另外,有了表面之后还需要打光,最后需要对模型进行多种优化。

造型部分的制作工序分得比较细致。这里根据造型制作工序分为粗模、高模和低模三类。大家看到的游戏模型,实际上在美术的电脑上是经过很多步骤一步步制作出来的。我会简要介绍这个过程。
粗模制作








第一步是制作粗模。粗模指在进行精细化建模之前,美术需要先找到一个快速原型,然后可以快速从这个原型上进行细节雕刻或制作,这能极大提升建模速度。其中非常重要的粗模来源是模型库。游戏公司的美术团队基本都有非常庞大的模型库,例如左边的物件模型和右边的一些基本角色模型。这些角色模型的造型很好,布线也非常经典。美术在制作某个角色或物件时,直接基于这种标准、规范、严格的布线进行设计,这称为粗模,是我们建模的第一步。


粗模的制作还有其他方式。例如,仿真建模使用一些物理仿真软件,对模型的衣物进行大体的造型勾勒。这种造型的特点是其面数大约在10万级别,全是三角形。制作时基本使用模拟布料的缝合、拉动等。所以,用右边一个简单的面片就可以缝到左边的裸体上,形成裤子这样的样式。但这种模型不能直接在游戏里使用,因为其质量相对不够高。
还有一种粗模建模方式是三维重建,使用左侧这种专门的三维扫描设备(手持三维扫描或激光/相机阵列设备),或在户外用这种设备进行拍摄。这种三维重建模型的特点是其真实度非常高。但它的限制在于,如果你的游戏项目不是写实类型,就无法使用这种方法。并且,用这种方法得到的模型需要进行较多的后处理。因为在现实中拍照时,拍到的物件光影效果与拍摄当天的光照、天气等环境有很大关系。例如,太阳从某个方向照下来,可能这个方向亮度较高,背光面则较暗。这种光照信息已经附着在模型表面。当放到游戏里时,游戏内的光照实际上并不是你现实采样时的光照,因此在处理时就需要美术抹掉这种光照信息。
高精度模型构建
当我们有了粗模以后,接下来就会进行高精度模型的构建过程。这里高清模型的一种制作方法是雕刻建模。雕刻建模往往用在角色模型上,因为游戏里给玩家带来最出彩效果的就是角色模型。这种模型在生产过程中,其三角形网格数非常多,达到百万级三角网格。从视频上可以看到,其三角形稠密程度已经让人感觉不到三角形的存在,感觉像曲面。现在的3A级产品或对质量追求较高的团队会使用这种工序。
它的操作是使用手写笔,直接在模型表面进行按压或提拉动作,就像橡皮泥一样,不断弄出想要的细节。
除了交互建模,在其他一些模型上,比如载具(飞机、汽车等),行业中会利用另一种方式进行高模建模,即曲面(样条)方式建模。在这种建模软件中,编辑的基本操作对象是曲面和曲线。它的特点在于建模过程中不需要太多三角形,而是利用像NURBS曲线这种工业标准的光滑曲线进行建模,其精度可以说是无限高的,并且也可以转换为三角形模型。因为放到游戏里渲染时,无法渲染曲线这种表达形式的模型,所以最终也要变成三角形模型供游戏使用。在一些3A级赛车游戏或飞行游戏中,这种建模方式比较常见。这种方式在国内用得不是很多,因为国内网络游戏较多,做赛车类的比较少,虽然赛车类可能更多也用多边形方式建模。
低模制作
接下来是低模。低模是大家在游戏里实际看到的模型,其面数在几百到几万面左右的范围。低模的建模方法也有几种。我现在展示的这种叫拓扑建模。可以看到拓扑建模是如何操作的:直接对模型进行点、线、面的操作,用鼠标选择点、线,然后对点进行剖分、增加、挪位、合并等,逐渐把一个模型从一个简单的几何体变成想要的人形样子。这种建模方式在过去(5年或10年前)是比较常见的方式,现在相对用得少一些,也有不少项目仍会使用,这取决于游戏对模型表现力的要求,是需要高度真实还原,还是大体效果即可。不同的项目会用不同的方式建模,这就是拓扑建模。
另一种拓扑建模是进行高模重拓扑。它与前者的差别在于,这是接着前面制作高模的流程。高模用雕刻方式制作出上百万面的模型,但这个上百万面的模型无法放到游戏引擎里渲染,因为一个模型本身可能有上百兆数据,一个模型体积可能就有游戏体积的十分之一,所以不能用在游戏里。这时,美术就需要把前面工序做出来的非常精细的高模进行拓扑降维(或称降采样),在高模表面生成一些四边形网格三角形来逼*这个模型。这个过程是在表面进行重拓扑,行业内称为高模重拓扑。其面数也是几万到十几万面的规模,同样采用点、线、面的合并操作方式。



前面的两种方法建模效率较低,一个模型可能要做上一两天,高模甚至可能要做上一到两周。所以,我们也不是总用这种很基础的方法一笔一画地建模。对于一些特定的造型,如果其本身能用数学模型表达,就会采用程序化建模。例如右上角的SpeedTree插件专门用来生成树木。树木的造型可以根据一些生物学原理来模仿,调整参数就可以快速生成树木。这种建模方式可以用在树木、植被、怪物,还有一些特定建筑(左下角)等造型比较固定、系列化的东西上。特别是像一些特别大的世界(如开放世界游戏),里面有大量重复的树木、建筑、怪物等,用这种方式建模能极大提升工作效率。但它的限制在于只能制作特定类型的模型,因为我们很难把所有东西的建模数学模型都归纳出来,所以这不是一个很普适的方法。



参数化的核心应用:材质与光照映射


讲完建模,就要讲到我们今天的主角——参数化。所有的模型都需要进行材质表面的映射,即需要把模型的表面信息存储起来。那么,如何为任意造型的表面存储信息?如何定位哪个地方存什么信息?
这里首先需要进行空间转换。我们需要把一个任意造型的模型表面映射到一个长方形、矩形或正方形区域。模型表面的每一个点对应到这张截图上的每个像素,我们就可以把信息存储在这个正方形上。这个方法就是参数化技术。
材质映射是专门给材质贴图使用的。可以看一下右下角的房子,我们把这个模型拆成很多块,映射到右边这个正方形之后,就可以利用表面绘画软件直接在模型表面进行涂抹绘画,这些信息就会同步传到右边的纹理上。这个映射过程用到的就是参数化技术。并且,这种参数化映射有很多要求,如果不满足这些要求,模型质量会比较差。
我们看看有哪些要求。首先,造型在矩形空间里不能斜着放。因为引擎在进行渲染时会进行光栅化,在纹理上进行采样时,其实是在UV空间进行采样的,即横向和竖向采样、插值。如果你的造型是斜着的,就会导致采样时很容易跨像素进行插值。你在同一排像素之间进行插值,是在相邻两个像素间插值。但如果斜着走,很容易在一些三四个像素交界的地方,在很短的距离内跨越好几个像素,这时的采样效果就会变差,很容易造成纹理在模型表面产生锯齿状。因此,游戏里进行参数化会要求一定要做得非常横*竖直。基本要求是图案的对称轴要与U轴或V轴*行或垂直。另外,在保持横*竖直的情况下,要尽量降低扭曲,形变不能太大。
材质映射还有一个特点是会共用纹理。共用纹理指一些相同的图案会映射到模型的同一个区域。例如一些木头的位置,不同的地方可能映射到同一个木头纹理区域;左下角的头发,实际上总共只有五六根、七八根,但满头可能有四五十根头发,它们被重叠映射到了纹理上。材质贴图本身又是另一类问题,它涉及引擎的渲染方法,即材质纹理保存什么信息。它使用辐射度度量学、光路学或几何光学等方法进行材质信息编码,与渲染管线强相关。例如固有色和漫反射的存储法,或高光度、金属度和粗糙度这种PBR光照模型,或经典的镜面反射模型,还有透明度、发光度等。这些不同的辐射度量学和光度学等信息都会用在材质贴图上,共用材质映射。
另一种贴图是凹凸贴图,它也是共用材质映射的一种贴图。它主要表达的不是物体表面的材质信息,而是物体表面的微观起伏。凹凸贴图有很多种做法,如Bump Mapping, Normal Map, Displacement Map等,这些做法是行业比较标准的用法。我们可以看到,这个模型在没有使用法线贴图的情况下,表面比较光滑圆润;使用法线贴图后,物体表面的细节会立刻变得丰富。
宁可牺牲模型表面的一些造型细节(即扭曲比较大一点),也一定要保证UV映射的线条非常横*竖直。原因在于,光照映射不是美术创作的内容,它实际上是模型放到引擎里以后,通过引擎的光照算法(灯光、太阳光等)打到物体表面的辐射度,存储到光照映射对应的纹理上。这样使得物件看起来有很真实的光照效果。这是由引擎产生的。也就是说,每个物件的表面、整个场景(假设是一个非常大的世界),所有地方都会有对应的光照信息存储,所以会消耗大量的存储量。因此,光照映射一般在游戏中会使用非常低的分辨率。如果你的材质映射用的是512x512的分辨率,那光照映射可能对应只有128x128甚至64x64的分辨率。所以光照映射对横*竖直的要求非常高,否则在这么低分辨率的情况下,采样的插值锯齿会非常严重。
映射讲完后,最后谈到优化。前面有了模型、材质、纹理之后,这些模型最终还要进行简化。我们在游戏里看到的模型,有些在画面很远的位置出现,有些在很*的位置出现。这时引擎实际上是用不同精度的模型去渲染它们。*景时,你看到的模型是渲染非常高密度的模型;但当角色走到很远的位置,引擎渲染的其实是另一个简化模型。这个机制就叫做LOD(Level of Detail)。在进行LOD时,也需要进行对应的UV、纹理混合、蒙皮等的同步简化,这里面也要用到参数化技术。




参数化在游戏建模中的主应用案例



好,到现在我就把游戏建模的方法和里面用到的一些技术大体讲了一遍。后面我接下来开始谈里面的参数化部分内容。





参数化在建模里,首先很容易理解的是它的主要应用,就是把模型的表面映射到刚才讲的正方形区域。这很好理解,材质映射、造型映射、光照映射等,我刚才举了好几种例子。这些就是主应用。但实际上,它使用的地方远不止主应用看起来那么多,还有很多嵌入式应用。嵌入式应用包括模型之间信息迁移、配准的手段;纹理装配中,图案的原画设计稿与模型表面内容的映射;进行纹理的几何压缩;进行一些造型识别、贴合度评判等。后面我都会举个例子讲明白。






先来谈一个主应用案例,还是讲到光照映射。光照映射在行业里会叫第二套UV(2nd UV),就是把光照信息映射到纹理上;材质映射是第一套UV(1st UV),把材质信息映射到纹理上。


这是基本的UV原理。这是一个实际会用到的光照图(这不是我们游戏中的光照图)。我们来看一下这个建筑,它的材质映射是这张图,里面的材质种类比较稀少,比如木板、墙壁、柜子、屋顶等,内容很少。它大量的区域可能共用的是同一个墙壁,就会映射到同一个地方。这叫材质映射。但是光照映射,你没办法这样共用,因为在物件的任何一个位置,它的光照信息都不一样,每个地方都不一样,你不能确保这个地方光照完全一样,这是完全不可能的。所以光照映射就得完全毫无重叠地*铺,把物片表面的每一寸都*铺到空间里。这里看到有四张光照映射图,分别是白天高精度(2048x2048)、白天低精度(1024x1024)以及两张晚上的,对应出来的渲染效果就是白天的两个效果、晚上的两个效果。
这就是光照映射的原理。光照映射(2nd UV)制作要求有这些:第一,UV展开后拉伸不能太严重,否则会导致光照密度过度不够光滑;第二,不能用斜边,斜边会导致光照产生锯齿,但如果把UV岛变成*直的,这里就不会有锯齿出现;第三,不允许任何重叠(这与材质映射不同,材质映射UV可以重叠);第四,对称性要可以保留,因为如果对称性与UV空间的轴向不水*或垂直,也容易出现斜边,采样效果不好;第五,切割位置,在一些光在模型表面是光滑的区域,如果切成两个UV岛,在引擎渲染中很容易产生漏光、过渡不自然,因为光泽纹理分辨率很低;第六,又不能切太碎,因为切太碎(每个三角形一个UV岛)会导致纹理中间间隙留很多,造成空间浪费;最后,装箱时,切好的UV岛要很紧密地排布在空间里,避免纹理空间浪费导致游戏体积变大。


这些就是2nd UV的制作要求。有这种要求的情况下,实际上行业里进行这种光照映射的制作,没有很好的自动化软件可以做得到,因为这个要求太复杂了。像这么一个模型,目前在行业里大部分是人工制作,大概需要花30~40分钟时间把模型变成这样的映射。右边这些是一些自动软件产生的效果(Unreal和Unity的效果),我们会看到Unreal和Unity对比人工,质量差很多。首先空间利用率就差很多,有很多空隙;另外,Unreal这个切得太碎,导致渲染时会产生很多黑边。所以实际上行业里进行2nd UV展开,是用人工进行的。
为此,我今天讲的这个案例提到的是,我们做了一个全自动的工具。看一下效果,我们可以做到替代人工。这是一个输入模型,它有人工做好的材质UV(1st UV)。现在这部分是一个自动化的2nd UV生成过程:拉直确保线条与UV轴垂直;缩放确保密度合适;然后进行装箱来保证空间被完全利用。这是2nd UV做好的一个效果。底下是另一个模型的2nd UV制作:拉直、缩放、装箱,然后进行烘焙。有了这个2nd UV并进行烘焙后,大家看到左边的模型有了光照纹理,立体感一下就强了很多。
我们的2nd UV制作流程大致分为几个步骤:切割展*、形变拉直、尺寸调整(确保UV密度合理)、最后紧致装箱(确保空间不浪费)。在前面切割展*以及形变拉直这两个步骤,大量使用了参数化算法,而且是我们专门为这个工具定制开发的参数化算法。


首先,第一步在进行参数展开时,为了确保扭曲不要太大,要先进行切割。我们看一下怎么切割的。切割我们采用一种过量切割加拼接的方法:先把东西切得特别碎,比如把这个模型切得这么碎,或者把这个小零部件切成三个部分;然后对它进行拼接。这个切割肯定是过量的,肯定不满足要求,我们就把它拼接一下,拼接之后对它进行参数化展开;展开以后我们来看这个拼接完的效果是不是我们能接受的。所以实际上把切割过程变成了一个过量切割再拼接的过程。在切割模块就可以判断出,这种拼接方式比另外几种拼接方式要优。为什么更优?因为拼接完后,这个造型其实已经丢失了对称性。对称性就是,你看这个模型原模型是这样子的造型,但你展开后变成这样,对称性就丢失了。对称性丢失,放在UV空间里也很容易出现斜线采样。这两个造型的对称性没有丢失,但相比之下,这种切割就比这种切割形变要小一些,那我们就认为OK,选用这种拼接方式。这就是我们切割算法的基本原理。这个切割整个过程中要进行非常多次参数化。


GAMES301-曲面参数化 - P15:Lecture 15 参数化在产业中的应用(2) 🏭

在本节课中,我们将继续探讨曲面参数化在产业中的具体应用。上一节我们介绍了参数化在游戏建模中的应用,本节中我们将重点关注另外两个重要领域:三维扫描与工业软件中的等几何分析。我们将了解参数化技术如何在这些领域中解决实际问题,并推动产业发展。



三维扫描工业中的参数化应用 📡

首先,我们来了解三维扫描仪的基本工作原理。三维扫描仪的功能是通过不同视角扫描物体,获取完整且高精度的物体表面信息。其基本逻辑分为两步:首先,通过二维图像和深度信息重建三维点云;其次,将不同视角的点云对齐到同一坐标系,从而重建完整的物体表面。
三维扫描的基本原理

以下是三维扫描仪实现上述功能的核心技术:




- 深度信息获取:通过多相机加结构光技术实现。人眼依靠双目视差感知深度,计算机则通过多个相机从不同视角拍摄,并主动投射带编码信息的结构光来创造特征点,辅助在不同图像中找到对应点,进而通过几何计算恢复深度信息。
- 位置信息获取:通过特征匹配与辅助信息实现。扫描时相邻视角的数据有重叠区域,通过识别重叠区域的几何特征,可以计算点云间的相对位置关系。对于特征不明显的场景(如一面白墙),可以手动粘贴编码点作为辅助跟踪信息。
基于不同的结构光类型和相机数量,衍生出了适用于不同场景的三维扫描仪,例如同时获取深度和纹理的彩色扫描仪、适用于工业检测的高精度激光扫描仪以及牙科诊所使用的口内扫描仪。
三维扫描的算法流程
上一节我们介绍了扫描的基本原理,本节中我们来看看其背后的完整算法流程。扫描完成后,软件需要处理数据以生成带纹理的三维模型。


整个算法流程可以概括为以下几个核心步骤:

- 标定:确定扫描设备中多个相机与投影仪之间的相对位置关系,这是后续深度重建的基础。
- 结构光重建:利用标定结果和结构光图案,计算每一帧扫描所对应的三维点云。
- 位置跟踪:根据前后帧点云重叠区域的几何特征,计算每一帧点云在全局坐标系中的相对位置。
- 点云全局注册:解决位置跟踪中产生的累积误差。通过建立全局优化方程,一次性优化所有帧的位置,获得更准确的整体点云位置。
- 网格化:将位于同一坐标系下的所有点云连接,生成表示物体表面的三角网格。
- 网格后处理:对生成的网格进行光顺、拓扑优化、简化等操作,使其几何信息更适合后续处理。
- 纹理融合:为网格表面贴上正确的纹理信息。利用负责纹理的相机与深度相机的标定关系,以及点云的位置信息,可以计算出每个纹理图像对应的空间位置。然后为网格上的每个面片选择最佳的纹理图块,并将这些图块排布到最终的纹理图中,生成“分块纹理图”。

参数化在纹理处理中的应用

在纹理融合步骤中,我们得到了网格模型和一张分块纹理图。虽然这种分块纹理图生成速度快,便于实时显示,但也存在一些缺点:纹理碎片化不利于语义分割等算法处理;存在信息冗余;网格分片交界处的纹理坐标不唯一,增加了纹理编辑等操作的复杂度。
因此,我们需要对已经恢复纹理信息的网格进行参数化展开,建立网格表面与二维纹理图之间的一一对应关系。这就是参数化在三维扫描产业中的一个核心应用。



应用一:UV Atlas生成
UV Atlas(纹理坐标图集)的生成是为了获取从模型空间到纹理空间的映射。这个过程通常包括网格切割、分片参数化以及最终的图块排布。
在三维扫描产业中,对UV生成的需求与游戏产业侧重点不同:


- 对排布效率要求相对较低:因为扫描得到的几何数据(动辄数百万面片)的存储量远大于纹理信息。
- 对计算效率要求极高:数据量大,必须追求处理速度。
- 对质量有基本要求:在追求速度的同时,仍需保证参数化结果的扭曲度较小。



为了满足工业界对效率的严苛要求,工程实现上会进行一系列优化:

- 网格简化:先使用QEM简化算法等将原始密集网格简化到较少面片,减少后续处理的输入数据量。简化过程需记录粗网格到细网格的映射关系。
- 网格分割:在简化后的网格上进行分割,作为参数化的输入。分割过程通过迭代实现,目标包括:分片内可展性高、分片形状规整(减小割线长度)、边界*缓。公式化描述如下:
- 可展性约束:新面片法向与分片拟合圆锥轴线的夹角需小于阈值。
- 规整性约束:
新面片到种子面片的距离 / 当前分片面积作为约束量。 - 边界*缓:通过边界边长度与内部边长的比值来约束。
- 分片参数化:对分割后的每个块进行快速的参数化计算。工业界会选择速度快的参数化方法。
- 并行化考虑:为了使计算任务能高效地分配到不同CPU核心,分割时需尽量保证各分块的大小接*,以避免负载不均。

应用二:口腔辅助诊断
参数化在口腔医疗领域有独特的应用价值。传统牙医依靠口腔镜观察,存在死角且难以记录治疗过程的变化。数字化方法通过口内扫描仪获取三维牙颌模型,使观察无死角且全程可记录。
在辅助诊断中,传统方式是医生在三维场景中多角度观察和截图,耗时较长。参数化可以提供一种加速思路:将牙齿或牙龈的网格表面展开到二维*面,在二维纹理图上利用图像识别技术提供初始病变判断,辅助医生快速定位问题。
在此应用中,参数化方案有特殊需求:
- 整体性:希望映射结果是联通的整体区域,而非分块,以避免破坏语义信息。
- 区域面积控制:能够控制网格上不同区域(如牙齿、牙龈)在参数空间中的面积比例,使需要重点检测的目标区域在纹理图上占据更大面积,便于算法识别。
- 计算速度快:面向海量用户,效率至关重要。
- 纹理编辑友好:希望医生在三维模型上做的标记,在参数化展开后扭曲尽可能小。


实现这些需求可能涉及引入控制点约束来调整参数化结果,或研究在复杂牙颌模型上保持标记区域低扭曲的参数化方法。


总结:在本节中,我们首先了解了三维扫描仪的基本逻辑和算法流程,然后深入探讨了参数化技术在三维扫描产业中的关键应用,包括高效的UV Atlas生成和为口腔辅助诊断提供的特殊参数化方案。参数化作为连接几何与纹理的桥梁,在提升三维扫描数据后续处理效率和拓展应用场景方面发挥着重要作用。
参数化在工业软件与等几何分析中的应用 ⚙️
上一节我们探讨了参数化在三维扫描中的应用,本节中我们来看看它在工业产品设计制造软件,特别是等几何分析中的关键作用。
工业产品制造流程复杂,涉及计算机辅助设计、工程分析、制造等多个环节。其中,CAD负责产品外形设计,CAE负责性能仿真分析。传统上,CAD模型(通常用样条等连续函数表达)需要被离散成网格,才能进行有限元分析。设计不满足要求时,需要返回修改CAD模型,再重新生成网格进行分析,迭代成本高。
等几何分析旨在解决这个问题。其核心思想是:直接使用CAD模型所用的样条函数作为分析场函数的基函数,实现CAD与CAE的模型统一,避免离散化网格的生成与迭代。
等几何分析中的参数化问题
在IGA中,我们直接在CAD模型(参数域)上定义物理场。但CAD模型通常形状复杂,而样条基函数定义在规整的参数域(如二维矩形、三维长方体)上。这就引出了IGA中的关键问题:如何将复杂的物理计算域参数化到一个规整的参数域上?
例如,给定一个二维的蝴蝶形状区域或三维的鸭子体,如何将其分别参数化到单位矩形和单位立方体上?同时,我们希望这个映射是双射,并尽可能保持角度、面积或体积等度量属性,以保障后续分析的数值稳定性和精度。
IGA参数化的主要方法
该领域的参数化方法与我们课程中介绍的方法一脉相承,主要分为以下几类:
- 基于简单构造的方法:如Coons曲面法,仅用四条边界曲线构造曲面,方法简单但质量不高。
- 基于调和映射的方法:求解拉普拉斯方程得到映射。公式为:
Δφ = 0,其中φ是映射函数。该方法光滑但无法严格保证无翻转。 - 基于非线性优化的方法:将参数化问题转化为优化问题。目标函数通常是减少扭曲能量,约束条件为保证映射的局部单射性。这与离散参数化思路一致,只是优化变量是样条基函数的系数。
- 常用能量包括:伸缩能量、狄利克雷能量等。
- 为保证单射性,除了要求雅可比行列式大于零(
det(J) > 0),也可使用拟共形映射或更特殊的Tutte映射条件。例如,Tutte映射要求映射是凸组合,天然保证局部单射。
此外,对于复杂形体,单一块参数化效果不佳,需要采用多块参数化策略,即将复杂域分割成多个与规整参数域同胚的子块,分别参数化后再拼接。分割方法包括基于骨架、基于PolyCube等启发式方法。
*年来,基于三角形/四面体的样条(如TPMS, Tris)成为一种新趋势。它可以直接定义在三角形网格上,避免了将复杂域分解为四边形/六面体块的难题,为IGA参数化提供了新的灵活性。
总结:在工业软件领域,参数化是连接CAD设计与CAE分析的桥梁。等几何分析通过使用统一的样条表示,试图消除两者间的鸿沟,而其核心挑战之一就是复杂计算域的参数化。解决这一问题的方法论与我们课程所学的曲面参数化技术深度相通,都是寻求从复杂域到规整域的良好映射。
其他参数化类型与课程总结 🎓
除了*面参数化,根据输入模型的拓扑类型,还有其他参数化形式:
- 球面参数化:针对亏格为0的封闭曲面,将其映射到球面上。方法也分为直接法、优化法(如将ABF、ARAP等能量推广到球面约束下)以及基于层次化简化的方法。
- 点云参数化:直接对点云数据建立参数化,通常先建立点间的邻接关系(如构建网格或定义邻域),再借鉴网格参数化的方法。
- 高维数据参数化:从流形学*的视角看,曲面参数化本质上是将高维观测数据降维到其本征的低维流形上。高维数据降维的思想与参数化一脉相承。
GAMES301 课程总结
回顾整个GAMES301课程,我们系统性地学*了曲面参数化这一几何处理的基础与核心技术:
- 基础与离散方法:我们首先建立了参数化的基本概念,然后深入学*了基于离散网格的各种参数化方法,包括保角、保面积、最小扭曲等不同目标下的优化技术。
- 连续方法:我们探讨了基于光滑函数(如调和映射、凸组合映射)的参数化方法,这与工业软件中的需求紧密相关。
- 产业应用:通过腾讯游戏建模、先临三维扫描以及等几何分析等案例,我们看到了参数化技术在驱动产业创新中的实际价值。
参数化作为连接几何表示与后续应用的桥梁,其重要性不言而喻。随着可微计算、自动微分等新技术的发展,参数化技术的实现和优化将变得更加高效和智能。
本节课中我们一起学*了参数化在三维扫描和工业软件/等几何分析两大产业领域中的深入应用。结合上一讲的内容,我们完整地看到了参数化技术从理论到实践,从娱乐产业到高端制造的全景图。希望本课程能为大家在几何处理领域的研究与应用打下坚实的基础。
课程资料:所有课件、课程录屏和作业资料将整理并备份在GAMES课程官网及B站,供大家复*查阅。完成作业的同学将获得课程结业证书。


感谢各位的参与,期待在未来的GAMES系列课程中再次相见!


GAMES301-曲面参数化 - P2:Lecture 02 面向离散网格的参数化概述 📐
概述
在本节课中,我们将学*面向离散网格的参数化的基本概念。我们将介绍离散网格的表示方法、映射的定义、映射扭曲的度量方式,以及几种经典的参数化方法。课程内容旨在为初学者打下坚实的基础。
离散网格介绍
首先,我们来介绍离散网格。离散网格由几何和拓扑两部分决定。对于流形网格,几何主要由顶点的位置描述,而拓扑则由顶点之间的连接关系(如图的概念)描述。常见的离散网格主要有四大类:三角形网格、四边形网格、四面体网格和六面体网格。
三角形网格和四边形网格是表面网格,内部没有填充。四面体网格和六面体网格则是体网格,内部由相应的单元填充。在实际应用中,也可能存在混合类型的网格。
映射的定义
上一节我们介绍了离散网格,本节中我们来看看映射的定义。映射通常指将一个定义域(如输入网格)变换到另一个区域(如变形后的网格)的过程。从网格的角度看,映射就是将输入网格变形为另一个网格。
在变形过程中,拓扑和几何都可以是变量。但在一般情况下,我们假设映射前后网格的拓扑(连接关系)保持不变,因此求解的变量只有网格的几何部分,即每个顶点的新位置。
例如,在六面体网格质量提升的例子中,输入网格通过改变顶点位置,使输出网格的单元更接*立方体,从而提升质量。
线性网格与线性映射
对于三角形网格或四面体网格这类线性网格,映射前后的网格都是直边网格。因此,在每个三角形或四面体单元上的映射是一个线性映射。整个映射则是分片线性的,并且是C0连续的。
在每个三角形上建立局部坐标系后,线性映射可以表示为一个雅可比矩阵。对于三角形,雅可比矩阵是一个2x2的矩阵;对于四面体,是一个3x3的矩阵。雅可比矩阵的行列式具有明确的几何意义:它等于映射后单元的面积(或体积)除以映射前单元的面积(或体积)。
映射的扭曲度量
我们关心映射是否对输入产生了扭曲。为此,我们引入几种理想的映射类型及其扭曲度量方式。
以下是几种映射类型及其对应的雅可比矩阵条件:
- 等距映射:要求雅可比矩阵是一个旋转矩阵(不考虑镜像)。这等价于保角且保面积。
- 共形映射:要求雅可比矩阵是一个相似变换(旋转加均匀缩放)。
- 保面积映射:要求雅可比矩阵的行列式等于1。
为了具体度量扭曲,我们引入带符号的奇异值分解(SVD)。雅可比矩阵 J 可以分解为 J = U Σ V^T,其中 U 和 V 是旋转矩阵,Σ 是对角矩阵,其对角线元素 σ1, σ2(奇异值)描述了映射在不同主轴方向上的缩放。
基于奇异值,我们可以定义扭曲能量:
- 共形扭曲能量:度量
σ1与σ2的差异。常见形式有(σ1 - σ2)^2或σ1/σ2 + σ2/σ1(对称狄利克雷能量)。 - 等距扭曲能量:度量
σ1, σ2与1的差异。常见形式有(σ1-1)^2 + (σ2-1)^2(As-Rigid-As-Possible能量)或σ1^2 + 1/σ1^2(对称狄利克雷能量)。 - 保面积扭曲能量:度量
σ1*σ2与1的差异。常见形式有(σ1*σ2 - 1)^2。
等距映射可以看作是共形映射与保面积映射的结合。
映射的约束条件
在求解映射时,我们通常希望满足一些约束条件,以保证映射的合理性与可用性。
以下是三种重要的约束条件:
- 无翻转:要求映射前后每个单元的有向面积(或体积)符号相同。这等价于要求雅可比矩阵的行列式大于零。翻转在物理上是不可能的,也会导致纹理映射等问题。
- 局部单射:在无翻转的基础上,进一步要求映射在每个顶点的局部邻域内是一一对应的。对于边界点,这要求其邻域三角形在参数*面上的内角和小于
2π。 - 全局单射:要求整个映射是一一对应的,即映射后的网格没有自相交。这对于纹理映射等应用至关重要。
参数化问题的一般形式
综合以上内容,参数化问题通常可以表述为一个带约束的优化问题。
- 目标函数:最小化某种扭曲能量(如共形扭曲、等距扭曲)。
- 约束条件:包括基本约束(如无翻转、全局单射)和特定约束(如固定某些顶点的位置、边界对齐等)。
然而,这个优化问题通常是非凸、非线性的,求解比较困难。例如,无翻转约束(行列式>0)是一个二次非凸约束;许多扭曲能量是有理分式形式。
经典参数化方法简介
接下来,我们将介绍三种经典的参数化方法,它们主要关注降低扭曲,但未必严格保证全局单射。
Tutte参数化方法
这是一种基于图论的方法,它提供了一个保证全局单射的充分条件。
定理:给定一个与圆盘同胚的三角形网格,如果将边界顶点按顺序固定在一个凸多边形(如圆、正方形)的边界上,并且每个内部顶点的坐标是其所有邻接顶点坐标的凸组合(即权重非负且和为1),那么由此得到的参数化是全局单射的。
求解时,边界顶点位置已知,为每个内部顶点根据其邻接关系建立线性方程,最终求解一个线性方程组即可得到所有内部顶点的UV坐标。这是第一次作业的内容。
基于角度的展*方法
这种方法的核心思想是,如果知道了参数化后网格所有三角形的内角,那么只需确定一条边的长度,就能唯一地重建出整个参数化坐标。因此,算法分为两步:
- 优化角度:寻找一组新的三角形内角,使其尽量接*原始网格的内角(以保持共形性),同时满足*面三角网格的几何约束(如每个三角形内角和为π,每个内部顶点周围内角和为2π等)。
- 从角度重建坐标:利用优化后的角度,通过求解一个线性最小二乘问题来恢复顶点的UV坐标。重建时需要固定两个顶点以消除旋转和*移自由度。
最小二乘共形映射方法
该方法直接基于共形映射的数学条件——柯西-黎曼方程。在离散设置下,它要求每个顶点满足一组线性关系。通过最小化这些线性关系的不满足程度(即最小二乘),可以得到参数化坐标。
目标函数是二次的,因此最终归结为求解一个线性方程组。同样,需要固定两个顶点以消除刚体变换自由度。
尽可能刚性方法
该方法的目标是让映射尽可能接*一个等距变换(刚性变换)。它通过交替迭代两步来求解:
- 局部步:固定当前参数化坐标,为每个三角形计算一个最优的旋转矩阵(或相似变换矩阵),以最佳逼*该三角形当前的雅可比矩阵。这可以通过对雅可比矩阵进行带符号的SVD分解来实现。
- 全局步:固定每个三角形上计算出的最优变换矩阵,重新求解所有顶点的参数化坐标,使得每个三角形的雅可比矩阵尽可能接*上一步指定的变换。这一步需要求解一个线性最小二乘问题。
这种局部-全局交替迭代的策略在许多几何处理问题中都有应用。

总结

本节课我们一起学*了面向离散网格参数化的基础知识。我们首先了解了离散网格的表示和映射的定义,然后学*了如何通过雅可比矩阵和奇异值分解来度量映射的扭曲(共形、等距、保面积)。接着,我们探讨了参数化中重要的约束条件:无翻转、局部单射和全局单射。最后,我们概述了四种经典的参数化方法:Tutte参数化、基于角度的展*方法、最小二乘共形映射以及尽可能刚性方法,理解了它们的基本思想和求解框架。这些内容为我们后续学*更深入的参数化技术奠定了基础。


GAMES301-曲面参数化 - P3:Lecture 03 无翻转参数化方法-初始存在翻转 🧩
在本节课中,我们将要学*如何处理在曲面参数化过程中出现的三角形翻转问题。具体来说,我们将探讨当初始参数化结果已经存在翻转三角形时,如何通过一系列算法将其修正为无翻转的有效参数化。
问题定义与背景
上一节我们介绍了四种参数化方法,包括 Tutte Embedding、ABF、LSCM 和 ARAP。其中 Tutte Embedding 有理论保证能得到无翻转且全局单射的结果,但扭曲较高。后三类方法(ABF、LSCM、ARAP)通过优化算法来降低扭曲,但在处理极端情况时,可能会产生翻转的三角形。
例如,对于一个头部模型,如果强行使用 ARAP 进行参数化,耳朵部分可能会产生严重的三角形翻转。翻转的三角形(通常用黄色表示)其法向与其他大部分三角形(白色或红色)相反,这会导致后续的纹理贴图完全混乱,失去棋盘格结构。我们的目标是从左侧这种存在翻转的初始结果,恢复成右侧这种虽然扭曲较大但结构完整、无翻转的参数化结果。
核心问题是:如何设计算法,去除这些翻转的三角形,使得所有三角形的朝向一致(例如都朝向正Z方向)。
方法一:基于投影的方法 🔄
这类方法的核心思想很简单:既然初始三角形存在翻转,那么能否通过“投影”的方式,将这些翻转的三角形映射到一个“无翻转的映射空间”中去?
直接对整个网格的所有三角形同时进行投影非常困难。但将一个单独的三角形投影到无翻转状态则相对容易。例如,一个翻转的三角形(顶点顺序为1-2-3,朝向负Z方向),只需将其中一个顶点(如顶点3)移动到另一侧(顺序变为1-3-2),即可使其朝向正Z方向。
然而,单独处理每个三角形会破坏相邻三角形之间的连接性。因此,我们需要一个全局步骤来最小化投影后三角形与原始三角形之间的距离,同时保持网格的整体一致性。这种 局部-全局交替优化 的思想与 ARAP 方法非常相似。
局部步骤:固定顶点位置(即固定了每个三角形的雅可比矩阵),将每个三角形投影到无翻转空间,求解最优的投影矩阵 H_i。这一步有解析解。
全局步骤:固定投影矩阵 H_i,求解顶点位置 u。此时能量函数是关于 u 的二次函数,加上线性约束,可通过拉格朗日乘子法直接求解。
这种交替迭代可以描述为一个定点迭代过程:x_{k+1} = g(x_k)。为了提高收敛速度,可以采用加速技术(如Anderson加速),利用前几步迭代值的线性组合来生成新的迭代点,从而减小残差。
此外,扭曲上界 K 的选择至关重要。K 定义了无翻转空间的“大小”。策略是:从较小的 K 开始(施加较强的约束,让三角形投影到“较好”的区域),如果优化无法完全去除翻转,再逐步增大 K(放松约束)。这种由紧到松的策略比一开始就设置很大 K 的效果更好。
另一种全局步骤的变体是切空间投影,即要求新生成的顶点位置落在最*投影点的切空间内。这可以加快收敛,但可能导致能量非单调下降,产生震荡,有时无法完全去除翻转。
方法二:基于有界扭曲空间的方法 📐
这类方法的核心思想是:无翻转空间本身是非凸的,难以直接优化。那么,能否在这个非凸空间内,找到一个最大的凸子空间,然后在其中进行凸优化?
首先,将三角形的雅可比矩阵 J 用参数 (a, b, c, d) 表示。无翻转条件(行列式>0)等价于 a^2 + b^2 > c^2 + d^2,这是一个非线性非凸约束。保角扭曲有界条件(σ_max / σ_min < K)可以转化为 c^2 + d^2 ≤ (K-1)/(K+1) * (a^2 + b^2)。
通过引入辅助变量 r_t = a^2 + b^2,可以将约束改写为:
c^2 + d^2 ≤ (K-1)/(K+1) * r_t(凸约束,一个二阶锥约束)。r_t ≤ a^2 + b^2(非凸约束)。
约束2的非凸性在于它要求点 (a, b) 位于一个圆的外部。为了得到一个凸的子空间,可以在该圆的边界上做一个切*面,例如要求 a ≥ r_t。这个切*面外的区域就是一个凸集。这里 a 的方向依赖于一个局部坐标系。
通过这样的转换,我们将非凸约束*似为凸约束。接下来,目标函数可以根据具体方法设定:
- 对于 LSCM(追求相似变换),目标是让
c → 0且d → 0。 - 对于 ARAP,目标是让雅可比矩阵接*一个旋转矩阵,这需要给定一个局部参考架(类似于 ARAP 中的旋转矩阵)。
整个算法流程变为:给定局部参考架,在凸约束下用二次规划求解顶点位置;然后更新局部参考架;交替迭代。这个方法将问题转化为一系列凸优化问题,可以利用成熟的优化算法求解,但每次迭代求解凸优化仍较耗时,且 K 的选择同样关键。
另一种思路是假设雅可比矩阵的 SVD 分解中的旋转矩阵 U_i, V_i 已知(取自上一次迭代),那么其奇异值 σ_i 就成为关于顶点位置的线性函数。此时,扭曲有界条件 σ_max < K * σ_min 就变成了线性不等式约束,整个问题完全转化为凸优化问题。这种方法的迭代收敛很快(通常少于10次),但每次迭代仍需求解一个二次规划问题。
方法三:基于惩罚函数的方法 ⚖️
这类方法的思想非常直接:既然有翻转的三角形是“坏”的,我就在它们上面加一个很大的惩罚能量,在优化过程中,算法为了降低总能量,就会倾向于消除这些翻转。
惩罚函数需要设计成:当三角形翻转(雅可比矩阵行列式小于0)时,函数值非常大;当三角形无翻转时,函数值很小(甚至为零)。常见的惩罚函数形式有:
- 基于行列式的惩罚:例如
E_penalty = log( (|det(J)| + ε) / (det(J) + |det(J)| + ε) )^2,其中ε是一个略小于最小行列式绝对值的小正数。当det(J)为负且接*-ε时,分母趋*于0,惩罚项趋向无穷大。 - 其他形式:例如
E_penalty = (|det(J)| - det(J) + ε)^p / ε等,核心思想都是让翻转三角形的能量急剧增大。
定义了惩罚函数后,整个问题变成一个无约束非线性优化问题。可以采用多种优化算法求解:
- 坐标下降/分块优化:每次只优化一个或一小部分变量。
- 共轭梯度法:通常需要预条件子(如拉普拉斯矩阵)来加速。
- 拟牛顿法:如 L-BFGS 算法。
- 一阶动量法:如 Adam 等源自深度学*的优化器。
- 二阶方法:如牛顿法,但需要处理 Hessian 矩阵非正定的问题(可能需要正则化)。
惩罚函数法的关键在于惩罚权重 ε 的调节。设置得当,收敛很快;设置不当,可能无法去除翻转或收敛缓慢。它的优点是思想简单通用,在网格生成等领域广泛应用。缺点是当初始翻转非常多时,可能难以优化。实践中,常与方法一结合使用:先用投影法快速去除大部分翻转,再用惩罚函数法精细去除剩余的少量翻转。
方法四:基于面积的方法 📏
这类方法基于一个几何观察:对于一个*面三角化区域,其所有三角形的有符号面积之和等于该多边形的面积。而所有三角形的无符号面积之和总是大于等于有符号面积之和。当且仅当三角化中没有翻转时,无符号面积之和达到其最小值,即等于多边形面积。
因此,一个直观的想法是:直接优化三角化的总无符号面积 TUA,使其最小化,理论上就能得到一个无翻转的参数化。然而,这存在两个问题:
TUA达到全局最小不一定对应无翻转映射,也可能包含退化的三角形(面积为零)。TUA不是光滑函数,且在有效区域可能存在*坦区(梯度为零),导致优化困难。
为此,研究者提出了“提升”的无符号面积概念,例如定义能量 E = Σ_i ( det(X_i^T X_i + α I) )^{1/2},其中 X_i 是三角形边的矩阵。当 α=0 时,它退化为 TUA。通过引入 α>0,该能量变得光滑,并且理论证明其局部极小值对应无翻转映射。这个方法较新,但实际效果一般。
方法五:基于不同表达的方法 🎭
之前的方法都直接以顶点坐标为变量。这类方法的核心思想是:换一种更容易控制翻转的几何量作为优化变量。
1. 以雅可比矩阵为变量
既然我们的终极目标是所有三角形的雅可比矩阵行列式大于零,为什么不直接把每个三角形的雅可比矩阵 J_i 作为优化变量?初始化时,我们可以直接让每个 J_i 都满足行列式大于零且扭曲较小。这样,翻转问题从一开始就被解决了。
但新的问题是:每个三角形独立优化后,网格会“散开”。我们需要一个约束让它们重新“拼合”起来。这个约束就是:对于共享一条边的两个三角形,它们各自变换后的该边向量必须相等。即 J_i * (v_a - v_b) = J_j * (v_a - v_b)。
因此,总能量函数设计为:
E_total = λ * E_assemble + E_distortion + E_user
E_assemble:拼合能量,衡量上述边约束的满足程度。E_distortion:扭曲能量,控制每个J_i的质量。λ:一个巨大的权重,在优化初期主导优化过程,迫使三角形拼合。- 优化过程中,通过线搜索保证每一步迭代都不引入翻转(行列式保持为正)。
算法流程:初始化一组无翻转、低扭曲的 J_i -> 在保持 J_i 无翻转的前提下,优化 E_total(逐渐增大 λ,使 E_assemble → 0)-> 从最终收敛的 J_i 恢复出顶点坐标。这个过程可视化起来就像一堆散开的三角形逐渐聚合为一个完整网格。
2. 以二面角为变量(3D推广)
这是对 2D 中 ABF(以角度为变量)方法的 3D 推广。在四面体网格参数化中,将每个四面体的六个二面角作为优化变量。通过优化这些二面角,并满足复杂的几何约束(类似于 2D 中的三角形内角和、边长约束),最终可以得到一个无翻转的 3D 映射。计算非常复杂,但原理相似。
总结与回顾 🎯

本节课我们一起学*了五种处理初始存在翻转的参数化方法:
- 基于投影的方法:采用局部-全局交替优化,将三角形投影到无翻转空间,思想类似 ARAP。
- 基于有界扭曲空间的方法:将非凸的无翻转空间*似或转化为凸空间,利用凸优化算法求解。
- 基于惩罚函数的方法:为翻转三角形设计巨大的惩罚能量,通过无约束优化迫使翻转消失。
- 基于面积的方法:通过优化提升后的无符号面积来间接保证无翻转。
- 基于不同表达的方法:改变优化变量(如雅可比矩阵、二面角),从源头上避免翻转。




从实践角度看,方法一(投影) 和 方法三(惩罚) 的组合往往非常有效:先用投影法快速去除大量翻转,再用惩罚函数法处理剩余的少数翻转。理解这些方法的原理,有助于我们在面对不同挑战时,选择或组合合适的工具来获得鲁棒的无翻转参数化结果。


GAMES301-曲面参数化 - P4:Lecture 04 无翻转参数化方法-初始无翻转 🧭
在本节课中,我们将要学*如何计算一个无翻转(无反转)的参数化映射。我们将从一个初始无翻转的参数化开始,通过优化来降低其扭曲度,同时在整个过程中保持无翻转的特性。
概述 📋
参数化的目标是将三维曲面映射到二维*面。一个常见的初始方法是 Tutte 嵌入,它能保证结果无翻转,但通常会产生较大的扭曲。本节课的核心内容是介绍如何从这个初始解出发,通过优化算法得到一个既无翻转又低扭曲的参数化结果。
通用优化框架 🔧
上一节我们介绍了参数化的基本概念。本节中,我们来看看解决上述问题的通用框架。
该框架的核心思想是:从一个无翻转的初始参数化(如 Tutte 嵌入)开始,在优化扭曲度的过程中,始终约束参数化保持在无翻转的空间内。这样,最终结果自然也是无翻转的。
整个流程如下:
- 初始化一个无翻转的参数化
X0。 - 基于当前参数化,计算一个能使扭曲度下降的方向
d。 - 沿着方向
d进行线搜索,找到一个合适的步长α,得到新的参数化。 - 重复步骤 2 和 3,直到满足收敛条件。
在这个框架中,有两点需要特别注意:
- 线搜索:步长
α的选择需要满足两个条件:一是保证新参数化依然无翻转;二是满足优化理论中的线搜索条件(如 Wolfe 条件)。 - 下降方向:不同优化方法的主要区别在于如何计算这个下降方向
d。
为了在线搜索中能自然地避免翻转,我们要求优化的目标函数具有 障碍函数 的特性。即当任何一个三角形趋向于退化或翻转时,目标函数值趋向于无穷大,从而阻止优化过程越过边界。
例如,对称 Dirichlet 能量 E = σ₁ + σ₂ + 1/σ₁ + 1/σ₂ 就具有这种特性,因为当三角形面积(与雅可比矩阵行列式相关)趋*于零时,1/σ 项会趋向无穷。
下降方向的计算 🧮
计算下降方向 d 的总体思路是:在当前位置 x,对目标函数 E(x) 建立一个局部的二次凸*似 Q(p)。
Q(p) = E(x) + ∇E(x)^T p + 1/2 p^T H p
其中,p 是待求的下降方向,H 是一个构造出来的矩阵,用于*似原函数的海森矩阵。不同的优化方法主要体现在 H 的构造方式上。
以下是三类主要方法:
- 一阶方法:忽略二阶信息,令
H = 0。此时Q(p)退化为线性函数,下降方向即为负梯度方向。 - 拟牛顿法:迭代地构造
H来*似二阶信息,例如经典的 BFGS 方法。 - 二阶方法(牛顿法):直接使用或修正原目标函数的海森矩阵作为
H。
得到局部*似 Q(p) 后,通过最小化它(或求解相关方程)即可计算出下降方向 d。
保证无翻转的线搜索 📐
在得到下降方向 d 后,我们需要进行线搜索来确定步长 α。为了保证参数化始终无翻转,我们需要为 α 设置一个上限。
对于网格中的每个三角形,其三个顶点的新位置为 u_i + α * v_i。我们希望新三角形的有向面积保持为正。
新三角形的面积是关于步长 α 的一元二次函数。通过求解该二次方程等于零的根,我们可以得到使该三角形恰好退化(面积为零)的临界步长。
对于每个三角形,我们计算其临界步长(取最小的正根)。为了保证所有三角形都不翻转,全局允许的最大步长 α_max 就是所有三角形临界步长中的最小值。
在线搜索中,我们首先将步长限制在 (0, α_max) 区间内,然后在此区间内进一步搜索,找到一个同时满足 Wolfe 条件(保证充分下降)的步长 α。
具体优化方法详解
前面我们介绍了通用的框架和概念。本节中,我们将深入探讨几种具体的优化方法。
一阶方法
一阶方法直接使用梯度信息,计算简单,但收敛速度可能较慢。
1. 块坐标下降法 (Block Coordinate Descent, BCD)
也称为非线性高斯-赛德尔迭代。
核心思想:将优化变量(如所有顶点的坐标)分成若干块(例如,每个顶点的 (u, v) 坐标作为一个块)。每次迭代只优化其中一个块中的变量,而固定其他块。
对于参数化问题,一个自然的块划分是将每个顶点的两个坐标作为一个块。算法流程如下:
- 从 Tutte 嵌入初始化开始。
- 依次遍历每个顶点(块):
- 固定其他所有顶点的位置。
- 仅优化当前顶点的
(u, v)坐标,以最小化目标函数。
- 循环步骤2直至收敛。
实践中,非精确BCD(每个块只进行一步梯度下降,而非优化到收敛)通常比精确BCD表现更好,收敛更快,最终能量也更低。
2. 交替二次规划 (Alternating Quadratic Programming, AQP)
该方法将目标函数 f(x) 分解为两部分:f(x) = h(x) + g(x)。
h(x)是一个简单的二次项(例如,基于拉普拉斯矩阵的能量)。g(x)是剩余部分。
在每次迭代点 y_n,构造如下*似函数进行优化:
Q(p) = h(y_n + p) + [g(y_n) + ∇g(y_n)^T p]
这里对二次项 h 是精确的,而对 g 只进行了一阶泰勒展开。求解 min Q(p) 可以得到下降方向 p。AQP 方法还引入了一个外插步骤来加速收敛。
3. 局部可扩展映射迭代法 (Locally Scalable Map Iteration Method)
该方法旨在为复杂的扭曲能量(如对称 Dirichlet 能量)构造一个局部的凸*似。
核心思想:设计一个*似函数 M(x),使其在当前位置的梯度与原目标函数 E(x) 的梯度相等。同时,M(x) 本身具有简单的形式(例如,类似 ARAP 的能量:Σ w_i || J_i - R_i ||_F^2)。
通过令 ∇M(x) = ∇E(x),可以反解出权重 w_i。然后,最小化这个二次函数 M(x) 就能得到一个有效的下降方向。该方法在实践中表现良好。
拟牛顿法:L-BFGS 及其改进
拟牛顿法(如 L-BFGS)通过迭代构造海森矩阵的逆 H^{-1} 来利用二阶信息,具有超线性收敛速度。
标准 L-BFGS 利用割线方程 H_{n+1} s_n = y_n 来更新 H^{-1},其中 s_n = x_{n+1} - x_n,y_n = ∇f_{n+1} - ∇f_n。
在参数化问题中,初始扭曲很大时,梯度差 y_n 可能数值不稳定。有研究提出用 L s_n 来替代 y_n,其中 L 是固定的网格拉普拉斯矩阵。在优化初期主要使用这种替代,后期逐渐过渡回标准的 y_n,从而兼顾了稳定性和收敛速度。
二阶方法(牛顿法)
牛顿法直接使用目标函数的二阶导数(海森矩阵)信息,收敛速度快,但计算开销大。
主要挑战在于,对于非凸的能量函数,其海森矩阵可能不是正定的,导致求出的方向不是下降方向。解决方案是修改海森矩阵,使其正定。
1. 复合代理方法 (Composite Majorization, CM)
这是一个基于 MM(Majorization-Minimization)优化框架的方法。
核心思想:在每次迭代点 x_n,构造一个凸的代理函数 M(x; x_n),作为原函数的上界,且满足 M(x_n; x_n) = f(x_n), ∇M(x_n; x_n) = ∇f(x_n)。然后通过最小化 M(x; x_n) 来得到下一个迭代点。
对于参数化中常见的、可表示为复合函数 f(x) = h(g(x)) 的能量,可以利用函数 h 和 g 的凸凹分解,系统地构造出这样的凸代理函数 M。该方法得到的海森矩阵*似 H 是正定的,保证了下降方向的有效性。
2. 有界扭曲参数化
这是一种不同的思路。它观察到,如果参考三角形与当前参数化三角形之间的扭曲有界,那么优化会非常迅速。
因此,算法不直接优化原始高扭曲的初始解,而是动态地生成一系列中间参考网格。每一步,都让当前参数化去逼*一个与它扭曲有界的中间参考网格。同时,让中间参考网格逐步逼*原始输入网格。通过这种方式,将原始的高扭曲优化问题,分解为一系列低扭曲的子问题,从而显著提高了优化速度。
总结 🎯
本节课我们一起学*了无翻转参数化方法的完整框架和多种具体实现:
- 框架:从无翻转初始解(Tutte嵌入)开始,在优化扭曲度时,通过具有障碍函数性质的目标函数和谨慎的线搜索来保证整个过程无翻转。
- 关键步骤:计算下降方向
d和进行保证无翻转的线搜索。 - 各类方法:
- 一阶方法(如BCD, AQP, LSM):简单直接,依赖梯度。
- 拟牛顿法(如L-BFGS):*衡了收敛速度和计算成本。
- 二阶方法(如牛顿法、CM):收敛快,但计算复杂。有界扭曲参数化通过问题转化,巧妙地提升了优化效率。


这些方法为在保证映射质量(无翻转、低扭曲)的前提下,高效计算曲面参数化提供了丰富的工具。


GAMES301-曲面参数化 - P5:Lecture 05 全局单射参数化方法 🧩
在本节课中,我们将要学*如何计算全局单射的参数化结果。全局单射意味着参数化映射在二维*面上是无重叠、无自交的,这对于纹理映射等应用至关重要。我们将介绍几种核心方法,并深入探讨其背后的优化策略与挑战。
概述:什么是全局单射参数化?
之前的内容讲到了无翻转的映射或初始无翻转的参数化,并介绍了许多优化方法。今天我们将进入一个新的主题,讨论更多的约束条件。
在参数化中,约束通常分为几种:无翻转、局部单射和全局单射。本节课将介绍计算全局单射映射的方法。
首先,我们来看一个简单的例子。下图展示了两种参数化结果:
- 左边的结果在二维区域存在重叠。
- 右边的结果没有自交或覆盖。

如果为这两种结果贴上棋盘格纹理并映射回原始曲面,会发现:
- 对于左边的参数化,二维*面上的一个红色点会映射到原始网格上的三个不同位置。
- 对于右边的参数化,红色区域只映射到一个位置。
这意味着左边的参数化缺乏一致性,在纹理编辑时,艺术家在二维*面上绘制的颜色会被映射到多个地方,这不是理想的情况。因此,我们需要保证参数化在二维*面上是无自交、无重叠的。
全局单射参数化的计算流程 🔄
计算无重叠参数化最常见的流程如下:
- 初始化:从一个无翻转或无重叠的初始参数化开始。通常使用 Tutte Embedding 计算,并保证三角形无翻转。
- 优化扭曲:初始参数化的扭曲通常很高。我们需要进行扭曲优化。
- 保持约束:在优化过程中,不仅要保证三角形无翻转 (
flip-free),还要保证边界无自交、区域无重叠 (overlap-free)。 - 迭代求解:整个过程类似于内点法,在下降方向进行线搜索,并始终满足
flip-free和overlap-free约束,直到收敛。
如果最终参数化结果同时满足 flip-free 和 overlap-free,则称之为 全局单射 参数化结果。
上节课我们主要讲解了如何保证结果无翻转。本节课将重点介绍如何通过能量设计或算法来保证边界不产生自交或区域不发生重叠。
方法一:障碍函数法 🚧
最自然的方法是使用障碍函数。由于初始状态无自交,我们可以在优化过程中,当接*发生碰撞时,通过障碍函数使能量值变得极大,从而阻止碰撞发生。
我们的目标是设计障碍函数,阻止边界发生自交。在二维情况下,边界是一个多边形,自交总是由两条边界边相交引起。
考虑一个情况:边 (u1, u2) 和顶点 ui。如果发生自交,意味着 ui 穿过了边 (u1, u2)。我们不希望发生这种情况,因此需要阻止 ui 穿过这条边。
可以设计一个简单的障碍函数:
E_{barrier} = \max(0, \frac{\epsilon}{d} - 1)^2
其中 d 是点 ui 到边 (u1, u2) 的距离。
- 当距离
d > ε时,函数值为0。 - 当距离
d < ε时,函数值变为(ε/d - 1)^2。当d趋*于0时,能量趋*于无穷大。
这样就能阻止顶点穿过边,从而保证边界无自交。
优化问题构建
结合扭曲优化项,完整的优化问题如下:
\min E_{total} = E_{distortion} + \lambda E_{barrier}
E_distortion:防止翻转并降低扭曲的能量项(如对称 Dirichlet 能量)。E_barrier:防止边界自交的障碍函数项。
这是一个无约束优化问题,可以使用 L-BFGS 等优化器求解。但使用二阶方法(如牛顿法)在此问题上会遇到一些困难,我们稍后会分析。如果仅使用 L-BFGS,收敛速度可能较慢。
使用障碍函数法后,原本可能自交的边界会被分隔开,从而得到无重叠的结果。
补充说明:在三维参数化中,情况更复杂,需要处理边-边相交和点-面穿透两种情况,但核心思想仍是设计合适的障碍函数。
方法二:脚手架网格法 🏗️
这类方法的思想也非常简单。假设绿色区域是我们的参数化区域,我们希望优化其扭曲,同时保证边界不产生自交且内部无翻转。

我们可以在目标区域(绿色网格 M)外部添加一层辅助的三角形网格(灰色网格 S,称为 scaffold 或脚手架)。将内部网格 M 和外部脚手架网格 S 视为一个整体网格 G。
关键操作:固定整体网格 G 的外部边界。
只要保证整体网格 G 内部的所有元素(三角形)不发生翻转 (flip-free),那么内部网格 M 的边界就一定不会自交,区域也不会重叠。这是一个容易证明的性质。
因此,我们将原本需要同时保证 flip-free 和 overlap-free 的复杂问题,转化为了只需保证整体网格无翻转的相对简单的问题。上节课介绍的所有用于无翻转参数化的优化方法都可以直接应用,效率很高。
挑战与改进:连接关系更新
然而,在优化过程中,内部网格 M 向目标形状变形时,外部的脚手架网格 S 会被剧烈拉扯,可能导致其三角形质量严重退化。当三角形退化到一定程度时,进一步优化就会被“锁住”,因为再变形就会导致翻转。

解决方案是进行 连接关系更新。即,在每一步优化后,对外部脚手架网格 S 进行重新网格化,保证其三角形质量始终良好,从而使优化能够顺利进行。
但这也带来了新问题:网格连接关系的改变意味着优化问题中 Hessian 矩阵的稀疏结构发生变化。在求解下降方向时,每次稀疏结构变化都需要重新进行矩阵的符号分解,这会增加计算开销。
脚手架网格法也可用于多块参数化的拼接,确保各块之间不发生碰撞。
方法融合与效率优化 ⚡
上述两种方法都存在效率问题:
- 障碍函数法:若使用 L-BFGS,收敛慢;若想用二阶方法,障碍函数导致 Hessian 矩阵处理复杂。
- 脚手架网格法:连接关系更新导致 Hessian 矩阵稀疏结构频繁变化,符号分解开销大。
我们希望 Hessian 矩阵具有固定的稀疏结构,这样符号分解只需进行一次,能极大提升求解效率。
固定稀疏结构的尝试
对于障碍函数法,其 Hessian 矩阵 H 由两部分组成:
H = H_{distortion} + H_{barrier}
H_distortion的稀疏结构是固定的。H_barrier的稀疏性取决于点-边距离是否小于阈值ε。若考虑所有可能的边界边对,则H_barrier的稀疏结构是固定的,但密度会非常高(与边界边数量的*方成正比),导致求解依然很慢。
一种思路是:降低 Hessian 矩阵的密度。既然密度高源于边界边数量 B 多,我们可以构建一个更稀疏的外层壳来替代原始密集边界。

在外层添加一个顶点数远少于原始边界的粗糙网格(紫色)。这样,需要处理的边界元素数量从 B 降为 S(S << B),对应的 H_barrier 矩阵密度显著降低,同时保持了稀疏结构的固定。实验表明,这种方法能将计算时间降至原来的十分之一左右。
构造 C∞ 障碍函数
原始的点-边距离函数在端点处仅是 C¹ 连续的,这不利于二阶优化。我们基于三角不等式提出一个新的 C∞ 距离函数:
d_{new} = ||u_i - u_1|| + ||u_i - u_2|| - ||u_1 - u_2||
当且仅当点 ui 位于线段 u1u2 上时,该距离为零。其等值面是一个椭圆。将此距离函数用于障碍函数,能获得更好的数学性质。
应用凸凹分解进行二阶优化
对于新的障碍函数 E(g) = (ε/g - 1)²,其中 g = d_new,我们可以对其进行凸凹分解。然后,利用上节课介绍的 Majorization 方法,可以构造出该函数 Hessian 矩阵的一个凸代理,从而高效、稳定地应用二阶优化器。
通过结合稀疏外壳和C∞障碍函数,我们得到了一个高效的二阶求解器:稀疏结构固定、矩阵密度低、障碍函数光滑。优化效率得以大幅提升。
处理顶点位置约束 🎯
在实际应用中(如纹理映射),我们通常希望网格的某些顶点(如眼睛、嘴角)能映射到纹理图片的指定位置。这引入了顶点位置约束。
简单地先做一个全局单射参数化,再把约束顶点拖拽到目标位置,可能会在路径上产生“自锁”问题,即顶点无法在不引起翻转或自交的情况下到达目标位置。
基于脚手架网格的解决方案
我们可以将带顶点约束的全局单射问题,转化为一个带翻转的初始化的无翻转参数化问题。
- 首先生成一个初始的全局单射参数化(不满足顶点约束)。
- 为其添加脚手架网格,并固定整体边界。
- 强行将约束顶点移动到目标位置。这必然会导致内部(包括脚手架)出现翻转三角形。
- 现在,问题变成了:在固定整体边界、且内部存在翻转三角形的条件下,求解一个无翻转的参数化。这正是我们之前学过的问题。
- 使用上节课的方法(如投影障碍函数法)进行优化,并在优化过程中对质量退化的脚手架网格进行重新网格化(使用边分裂、边塌缩、点重定位等操作),逐步消除翻转。
最终,我们就能得到一个满足顶点位置约束的全局单射参数化结果。
总结 📚
本节课我们一起学*了全局单射参数化的计算方法。
在参数化求解过程中,我们强调边界不应产生自交。为了让边界无自交,通常从一个 Tutte Embedding 结果开始优化扭曲,并在整个过程中保持无自交约束。
我们主要介绍了三类方法:
- 障碍函数法:通过能量函数惩罚边界碰撞。
- 脚手架网格法:通过添加外部辅助网格将问题转化为单纯的无翻转问题。
- 融合方法:结合稀疏外壳和光滑障碍函数,实现了高效稳定的二阶优化。
此外,我们还探讨了如何将顶点位置约束融入全局单射参数化的框架中,通过将其转化为带翻转初始化的无翻转问题来求解。


这些方法使得计算高质量、无重叠的参数化结果变得更为高效和实用。

课程内容源自 GAMES-Webinar 讲座《GAMES301-曲面参数化 - P5:Lecture 05 全局单射参数化方法》。


GAMES301-曲面参数化 - P6:Lecture 06 参数化应用1 – Atlas生成、艺术设计 🗺️🎨
在本节课中,我们将学*曲面参数化的两个重要应用领域:Atlas(纹理图集)的自动生成和艺术设计中的参数化技术。我们将了解如何将三维网格切割、参数化并高效地打包到二维*面上,以及如何利用参数化原理辅助创作橘皮艺术和纸模设计。
Atlas生成:从扫描网格到纹理贴图 🖼️
上一节我们介绍了计算无翻转和全局单射的参数化方法。本节中,我们来看看如何将这些方法应用于实际问题,特别是为三维扫描得到的网格自动生成纹理贴图(Atlas)。
对于通过激光扫描仪等设备获得的三维网格,我们通常希望自动为其生成纹理贴图,而不是完全依赖艺术家手动绘制。纹理贴图是一种将高频细节(如颜色信息)从二维图像映射到三维模型表面的方法。本质上,这是一个从三维模型表面(Model Space)到二维纹理空间(Texture Space)的参数化映射过程。


为了实现纹理映射,我们需要将三维网格“展开”到二维*面上。整个过程通常分为三步:
- 网格切割:在网格表面引入切割线(缝隙),将其分割成多个可展的曲面片。
- 参数化:将每个曲面片映射到二维*面。
- 打包:将所有映射后的二维片紧凑地排列在一个矩形(如正方形)区域内,形成最终的纹理图片。
以下是评估一个Atlas好坏的主要指标:
- 打包效率:有效区域面积与整个矩形边界框面积的比值。比值越高,存储图片所需的分辨率越低,节省内存和存储空间。
- 边界长度:切割线(缝隙)的总长度。较短的边界可以减少渲染时在接缝处产生瑕疵的可能性。
- 块数量:分割后的曲面片总数。通常希望数量较少,以减少片间不连续性和处理复杂度。
- 扭曲度:参数化映射过程中产生的几何失真程度。需要尽可能低以保证纹理映射质量。
网格切割技术 🔪
首先,我们需要将输入的三维网格切割成多个易于参数化的块。切割的目标是使得后续参数化的扭曲度低,并且切割线的总长度尽可能短。
网格切割方法主要分为两类:
基于点的切割方法

这类方法首先识别网格上可能导致高扭曲的“关键点”,然后将这些点连接起来形成切割路径。一个典型的启发式方法是交替迭代:
- 在网格上随机切割一刀。
- 对切割后的网格进行参数化。
- 找到参数化结果中扭曲度最高的顶点。
- 将该点连接到现有切割路径上,形成新的切割。
- 重复步骤2-4,直到最高扭曲点落在边界上。
找到关键点后,需要将它们连接成最短的切割路径,这可以抽象为在网格图上求解斯坦纳树问题,这是一个NP难问题,通常采用*似算法求解。

基于分割的切割方法
这类方法直接对网格进行分割,目标是使每一块分割区域都能以低扭曲度参数化到二维*面。一种思路是利用可展曲面的特性。例如,圆锥面是可展曲面,可以无扭曲地展开到*面。
一个具体的方法是借鉴Lloyd迭代或K-means聚类的思想:
- 随机选择一些三角形作为种子。
- 为每个聚类定义一个“轴”和一个“角度”(模拟圆锥)。
- 对于每个三角形,计算其法向与所属聚类轴的夹角,并与目标角度比较,根据误差决定其归属。
- 根据新的聚类结果,更新每个聚类的轴和角度。
- 重复迭代,直到收敛,最终每个聚类区域对应一个可展曲面片。
公式上,对于一个三角形t,其法向为 n_t,所属聚类的轴为 n_c,目标角度为 θ_c。我们希望最小化误差:
E = (n_t · n_c - cosθ_c)^2
参数化与高效打包 📦
参数化技术我们在之前的课程中已经详细讨论过,核心是获得一个低扭曲、无翻转、无重叠的映射。本节我们重点关注最后的打包步骤。
打包效率直接影响最终纹理图片的尺寸。给定一系列形状不规则的二维多边形块,如何将它们无重叠地、紧凑地排列在一个矩形内,本身也是一个NP难问题。

一种改进思路是:将输入的不规则形状转换为轴对齐的多边形,然后将其分割为矩形,再进行打包。因为矩形是最容易高效打包的形状。

整个流程如下:
- 轴对齐化:对输入的参数化结果进行变形,使其边界与坐标轴*行。
- 矩形分割:将轴对齐的多边形进一步分割成多个矩形。
- 矩形打包:将这些矩形紧凑地排列到目标矩形框内。
- 扭曲优化:由于第一步的变形可能引入高扭曲,最后使用如Slit Map等方法对参数化结果进行优化,降低扭曲度。
通过这种方式,可以在保证低扭曲的同时,显著提高纹理图集的打包效率。
参数化在艺术设计中的应用:橘皮成型 🍊
接下来,我们探索参数化在一个有趣的艺术形式——橘皮成型中的应用。艺术家在橘子皮上雕刻切割线,展开后能得到各种动物或物体的形状。
从计算角度看,这相当于一个逆向设计问题:给定一个目标二维形状,需要在球面(橘子)上寻找一组切割线,使得橘子皮沿这些线切割并展开后,形状与目标尽可能接*。
直接求解此问题(生成切割->展开->比较形状)非常困难。一个更简单的方法是正向映射:尝试将目标二维形状映射回球面。如果这个映射能几乎完全覆盖球面,那么映射后目标形状在球面上的边界,就是我们要找的切割线。
我们将其构建为一个优化问题,包含两个目标:
- 低扭曲映射:使用ARAP等能量保证映射质量。
E_arap = Σ_t || J_t - R_t ||_F^2,其中 J_t 是雅可比矩阵,R_t 是旋转矩阵。 - 覆盖球面:希望映射后,球面上未被覆盖的“空隙”区域面积趋于零。我们设计了一个秩一能量来促使这些区域的三角形收缩。
E_rank1 = Σ_{t in gap} || J_t - B_t ||_F^2,其中 B_t 是一个秩为1的矩阵(面积为零)。
通过优化,橘皮上的空隙区域逐渐收缩闭合。对于无法完全覆盖的情况,我们提供了交互工具,允许用户对目标形状进行编辑(如拉伸、旋转局部区域),直到其能完美映射覆盖球面。最后,根据映射边界生成切割线图纸,指导实物创作。




参数化在艺术设计中的应用:纸模设计 📄

另一个应用是辅助纸模设计。纸模由多个可展曲面片(纸张)拼接而成,每个曲面片的高斯曲率为零。
我们的目标是:给定一个复杂的三维模型,将其*似为一系列可展曲面片的集合,使得曲面片数量少,且整体形状与原模型相似。
我们的方法分为三步:
- 变形:将输入模型变形为一个“接*可展”的中间模型。我们定义了一种基于边法向的可展性约束。对于一条边及其邻域,我们希望其相关法向在高斯映射下共线,这等价于一个矩阵行列式为零的条件。通过优化可展性、形状相似性和低扭曲度,得到中间模型。
- 分割:对接*可展的中间模型进行分割。先进行过分割,然后基于准则合并相邻块,最终得到数量较少的曲面片。
- 优化:对每个曲面片进行优化,使其严格满足可展性(高斯曲率接*零),同时保证边界光滑和形状相似。
通过此流程,我们可以为复杂的三维模型生成易于制造的纸模图纸,用户只需按图裁剪、折叠和粘贴即可。
总结 📝
本节课我们一起学*了曲面参数化的两个核心应用。
- 在Atlas生成中,我们了解了如何为扫描网格自动创建纹理贴图,涉及网格切割、参数化和高效打包三个步骤,并探讨了相关的算法与评估指标。
- 在艺术设计中,我们看到了参数化如何赋能传统工艺。通过将橘皮成型抽象为球面参数化问题,以及将纸模设计转化为可展曲面分割问题,参数化技术为这些创意活动提供了强大的计算辅助工具。



这些应用展示了参数化不仅是理论工具,更是连接数字计算与物理世界、艺术创作的重要桥梁。


GAMES301-曲面参数化 - P7:Lecture 07 参数化应用2-网格生成 🧠
在本节课中,我们将学*如何将曲面参数化技术应用于网格生成领域。我们将了解网格生成的基本概念、不同类型网格的特点,并重点探讨基于参数化的网格生成方法的核心思想与流程。

什么是网格生成? 📐
网格生成是指使用多边形或多面体,通过边和顶点连接起来,以*似表示一个数字几何模型的过程。
例如,一个黄色的飞机CAD模型,其表面可以被四边形化,生成一个四边形表面网格来*似原始模型。在进行有限元模拟时,还需要对飞机外部的空气或流体区域生成体网格(如边界层的四面体网格)。整个网格化过程就是生成多边形或多面体并连接起来,以*似数字模型。

有了这样的多边形或体网格后,可以应用于后续的分析,如有限元分析或计算流体力学,进行物理仿真以评估设计性能。
网格生成的重要性与挑战 🔗
网格生成是连接计算机辅助设计(CAD)和计算机辅助工程(CAE)的关键桥梁。尽管CAD到CAE一体化是研究趋势,但目前最常用的方式仍是将CAD模型网格化后进行CAE分析,再根据物理仿真结果修改CAD设计。这个过程通常比较繁琐。
在计算机图形学中,与此紧密相关的概念是“重新网格化”(Remeshing)。
重新网格化(Remeshing)概述
给定一个输入的三维网格,计算另一个新的网格,其单元(如三角形、四边形)满足特定的质量要求,同时能很好地*似输入几何。
输入网格可能来自三维扫描重建(如Marching Cubes算法)或CAD模型的初步三角化,其质量可能较差。重新网格化的目的就是提升网格质量,以用于后续分析或仿真。
网格的主要类型 📦
常见的网格类型主要有以下四种:
- 三角形网格:表面网格,每个面是三角形。
- 四边形网格:表面网格,每个面是四边形。
- 四面体网格:体网格,每个体单元是四面体。
- 六面体网格:体网格,每个体单元是六面体。
三角形和四边形网格是表面网格,四面体和六面体网格是体网格。这四种网格在有限元分析和图形学中应用最广泛。具体应用中使用哪种网格最佳,通常由应用场景本身决定,最终评判标准是物理仿真的准确性。
网格质量要求 🎯
不同的应用对网格质量的定义和需求不同。常见的质量考量包括:
- 单元形状:如三角形是否接*正三角形,四边形是否接*正方形。
- 尺寸:网格单元的大小分布,可以是均匀的或自适应的(如在曲率高的区域加密)。
- 朝向:网格单元的排列方向,例如与几何特征对齐。
- 各项同性与各项异性:
- 各项同性网格:单元在各个方向上尺寸接*,如接*正三角形。
- 各项异性网格:单元在不同方向上尺寸有显著差异,通常用于适应曲率或物理场(如流体流动方向)。
- 全局结构:如三角形网格中,内部顶点若邻接6个三角形则为正则顶点;四边形网格中,内部顶点邻接4个四边形则为正则顶点。大部分顶点正则的网格称为半正则网格。
上一节我们介绍了网格生成的基本概念和质量要求,本节中我们来看看基于参数化的网格生成方法。
基于参数化的网格生成方法 🗺️
基于参数化的网格生成方法流程清晰,主要分为三步:
- 参数化:将输入的曲面网格映射到一个简单的参数域(通常是*面)。
- 在参数域中生成网格:在参数域内进行(重新)网格化,因为在此二维*面上操作相对简单。
- 映射回原始曲面:将参数域中生成的新网格,利用参数化映射的逆映射,提升回原始三维曲面。
这种方法的核心思想是通过映射将复杂的三维曲面网格生成问题,转化为简单的二维*面网格生成问题。
方法优势与挑战
其优势在于简化了网格生成操作。然而,也带来一些挑战:
- 参数化质量:需要计算一个低扭曲(如保角或*似等距)的参数化,以确保二维*面上的良好形状在映射回三维后仍能保持。
- 切割处理:对于封闭曲面,需要切割(Cut)使其拓扑同胚于圆盘才能参数化到*面。切割缝处的网格在最后需要能够无缝拼接。
- 额外约束:在某些特定类型的网格生成中(如四边形网格),参数化需要满足额外的约束条件(如边界对齐格点、切割缝两侧满足旋转和*移关系)。
接下来,我们将通过几个典型应用案例来具体说明。
应用案例一:各项同性三角形网格生成 🔺
目标是生成所有三角形都尽可能接*正三角形的网格。
直接在三维修改网格(如通过边折叠、边交换等局部操作)可能抹除模型上的细小特征(如靠*的耳朵)。基于参数化的方法可以避免此问题。
以下是基于参数化生成各项同性网格的步骤:
- 切割:将输入网格切割,使其拓扑同胚于圆盘。
- 参数化:将切割后的网格参数化到二维*面,并保持边界固定。
- 在参数域中重新网格化:在二维*面上生成高质量的各项同性三角形网格。
- 投影回原始曲面:将*面网格的顶点投影回原始三维曲面。
关键点:在参数域中,两点间的欧氏距离*似等于原始曲面上的测地距离。因此,在三维中很*但在曲面距离上可能不*的点(如耳朵上下表面),在参数域中会被正确分离,从而避免特征在网格优化中被错误消除。
应用案例二:各项异性网格生成 🥚
目标是生成适应特定方向性要求的网格,例如在流体仿真中沿流动方向拉长的网格。这需要一个度量场(Metric Field)来指导,该场在每个位置定义了一个对称正定矩阵,用于重新定义局部空间的“距离”。
核心思想:将各项异性网格生成问题转化为各项同性网格生成问题。这可以通过两种映射实现:
- 保角映射:利用共形几何理论,存在保角映射能将给定的黎曼度量(即度量场)转化为标准欧氏度量。
- 高维等距嵌入:根据纳什嵌入定理,任何黎曼流形都可以等距嵌入到更高维的欧氏空间中。在高维欧氏空间中,可以使用标准的各项同性网格生成方法。
以高维嵌入为例,流程如下:
- 给定二维域和其上的度量场。
- 计算一个到三维空间的嵌入映射,使得嵌入后的曲面在三维空间中的欧氏距离尽可能接*原二维域在给定度量场下的距离。
- 在三维嵌入空间中生成各项同性三角形网格。
- 将网格映射回原始二维域,即得到各项异性网格。
优化能量:嵌入过程通常需要优化两项能量:一项迫使嵌入后的距离符合目标度量(嵌入能量),另一项保证嵌入映射的光滑性(光滑能量)。


应用案例三:四边形网格生成 ⬛
目标是生成大部分顶点度为4的四边形网格。
基于参数化的经典方法是:将曲面参数化到一个二维*面,并使参数化坐标线对齐一个规则的整数格点网格。这样,参数域本身的格网在映射回曲面时就自然构成了四边形网格。
以下是关键步骤与约束:
- 切割与奇异点:在参数化前,需要将曲面切割开。奇异点(度不为4的顶点)必须位于切割线上,并被连接到边界。
- 约束参数化:计算参数化时,需施加严格约束:
- 整数格点约束:所有顶点(特别是奇异点和边界点)的参数坐标必须是整数。
- 旋转约束:切割缝在参数域中被表示为两条边界。这两条边界对应的格线必须满足90度的整数倍旋转关系,才能保证网格在缝合后严丝合缝。
- *移约束:有时还需要满足整数倍的*移约束,以确保格线能完全对齐。
- 生成网格:满足上述约束的参数化本身,其坐标等参线就构成了曲面上的四边形网格。
这种方法将困难的曲面四边形划分问题,转化为一个带复杂约束的参数化优化问题。
应用案例四:六面体网格生成 🧊
目标是生成全六面体网格。一种基于参数化的方法是使用多立方体(Polycube)映射。
核心思想:将复杂的三维体域参数化(映射)到一个由多个轴对齐的立方体堆砌而成的“多立方体”域上。由于多立方体域本身是由规则立方体构成的,在其内部生成六面体网格是 trivial 的(直接细分即可)。然后,将这个六面体网格通过映射拉回原始域,即可得到所需的六面体网格。

关键挑战:如何计算一个高质量的多立方体映射。该映射需要满足:
- 边界法向与坐标轴对齐。
- 映射本身扭曲低、无翻转。
高阶网格简介 🚀

高阶网格的单元边/面不再是直线/*面,而是由多项式(如贝塞尔曲线/曲面)定义的曲线/曲面。这能更精确地描述几何边界,并支持在单元上使用更高阶的基函数进行有限元分析,从而提高计算精度。



生成高阶网格的常见方法是:首先生成一个线性的(低阶)网格,然后通过一个映射/变形过程,将其“弯曲”成高阶网格,同时确保映射是局部单射(无翻转)。

总结 📝
本节课我们一起学*了网格生成的基本概念及其与参数化技术的结合。我们了解到:
- 网格生成是CAD与CAE间的桥梁,重新网格化旨在提升网格质量。
- 基于参数化的方法通过“三维到二维的降维”策略简化网格生成问题,其通用流程为:参数化 → 参数域内网格化 → 映射回三维。
- 该方法可灵活应用于各项同性/异性三角形网格、四边形网格乃至六面体网格的生成,但需解决参数化质量、切割缝合以及特定约束(如格点对齐)等关键挑战。
- 高阶网格代表了网格发展的一个前沿方向,能提供更高的几何与数值精度。


通过本课的学*,我们看到了参数化技术作为一项强大的几何处理工具,在解决复杂工程问题中的核心作用。




GAMES301-曲面参数化 - P8:Lecture 08 无翻转光滑映射 🧭
在本节课中,我们将要学*如何构造比三角网格上的分片线性映射更光滑的映射,并探讨如何保证这类光滑映射的无翻转性质。
概述
在前面的课程中,我们主要学*了三角网格上的参数化,其映射通常被假设为分片线性映射。这种映射不够光滑,因为其基函数不光滑,且相邻面片间的映射导数不连续。本节课我们将介绍几种直接构造光滑映射的方法,并讨论如何确保这些光滑映射在优化过程中不发生翻转。
从分片线性映射到光滑映射
上一节我们介绍了三角网格上的分片线性映射。本节中我们来看看分片线性映射为何不够光滑,以及提升光滑度的基本思路。
三角网格上的分片线性映射 f 可以表示为基函数 b_j 的线性组合:
f(x) = Σ (y_j * b_j(x))
其中 y_j 是目标顶点的坐标,b_j 是定义在顶点 j 上的分片线性基函数(即“帽子函数”)。
这种映射存在两个主要问题:
- 基函数不光滑:基函数
b_j在顶点处不可导。 - 导数不连续:映射的雅可比矩阵(导数)在相邻三角形边界处不连续。
一个直观的解决思路是增加网格分辨率,但这会显著增加计算复杂度。本节课的核心是介绍通过构造光滑基函数来直接获得光滑映射的方法。
四类光滑映射构造方法
以下是四种主要的构造光滑映射的方法。
1. 径向基函数 (RBF) 🌐
径向基函数是一类以距离为自变量的基函数。映射 f 被构造为这些基函数的线性组合:
f(x) = Σ (a_i * φ(||x - p_i||))
其中 φ 是径向函数,例如 φ(r) = 1 / (ε + r) 或 φ(r) = exp(-r^2)。
RBF 非常光滑,但一个主要缺陷是:由 RBF 张成的函数空间无法表示简单的线性函数,这限制了其应用。
2. 广义重心坐标映射 🧩
广义重心坐标是三角形重心坐标在多边形上的推广。对于多边形内的任意点 v,其重心坐标 b_i 满足:
v = Σ (b_i * v_i) 且 Σ b_i = 1
其中 v_i 是多边形顶点。
利用重心坐标,我们可以构造光滑映射:将变形后的多边形顶点坐标 x_i 代入公式:
f(v) = Σ (b_i * x_i)
这样定义的映射 f 是光滑的,并且能精确重现线性函数。
以下是几种经典的广义重心坐标:
- 均值坐标 (Mean Value Coordinates):具有非负性,在凸多边形内部保证
b_i ≥ 0。 - 离散调和坐标 (Cotangent Coordinates):与拉普拉斯算子相关。
- 调和坐标 (Harmonic Coordinates):通过求解拉普拉斯方程得到,理论上无限光滑。
广义重心坐标在图像变形、网格插值和法向场光滑化等方面有广泛应用。
3. 调和映射 ⚖️
调和映射是指满足拉普拉斯方程 Δf = 0 的映射(每个坐标分量分别满足)。在给定边界条件下,调和映射的解存在且唯一,并且是数学上“最光滑”的映射之一(无穷阶可导)。
二维调和映射有一个重要性质:任何调和映射都可以分解为一个全纯映射和一个反全纯映射之和。结合前面提到的复重心坐标,我们可以解析地构造出所有的二维调和映射,而无需直接求解偏微分方程。
4. 样条基函数 🛣️
样条是计算机图形学中用于构造光滑曲线曲面的经典工具。其核心是一组光滑的、局部支撑的基函数。
- Bézier 曲线:使用伯恩斯坦基函数,但高阶时计算不稳定,且基函数非局部。
- B 样条曲线:通过递归定义,提供了局部支撑性、非负性、归一性等优良性质。
k阶 B 样条基函数是C^(k-2)连续的。
通过张量积,可以将一维 B 样条基函数推广到二维,用于定义曲面片(如 NURBS 曲面)。样条曲面由控制顶点和基函数线性组合而成:
S(u, v) = Σ Σ (P_{i,j} * B_{i,k}(u) * B_{j,l}(v))
其中 P_{i,j} 是控制顶点,B 是 B 样条基函数。
无翻转光滑映射的优化挑战与策略
上一节我们介绍了如何构造光滑映射。本节中我们来看看如何优化这些映射,并确保其无翻转性质。
在分片线性映射中,我们通过优化如对称狄利克雷 (SD) 能量来防止翻转,该能量在雅可比行列式趋于零时会趋于无穷大,形成一个“障碍函数”。优化问题形式为:
最小化 E_{SD}(f) = Σ (A_t * energy(J_t))
约束为 对每个三角形 t,det(J_t) > 0
这是一个具有有限个非线性约束的有限维优化问题。
对于光滑映射,问题变得复杂:
- 能量项:需要计算定义域上的积分
∫ energy(J(x)) dx,而非有限和。 - 约束条件:要求定义域内每一点
x都满足det(J(x)) > 0,即有无穷多个约束。
为了在计算机上求解,我们必须将问题离散化:
- 能量离散:在定义域上采样一组点
{p_k},用求和*似积分:E ≈ Σ (w_k * energy(J(p_k)))。 - 约束离散:同样只在采样点
{p_k}上施加约束det(J(p_k)) > 0。
但这带来了新问题:如何保证在采样点之外,映射也是无翻转的?
以下是两类保证策略:
策略一:利用 Lipschitz 连续性
如果映射足够光滑,其雅可比行列式函数 det(J(x)) 是连续的,甚至是 Lipschitz 连续的(即变化速度有界)。那么,如果在采样点 p 处满足 det(J(p)) > L * r(其中 L 是 Lipschitz 常数,r 是采样半径),则可以证明在以 p 为中心、半径为 r 的邻域内所有点 q 处,都有 det(J(q)) > 0。通过布置足够密的采样点覆盖整个区域,就能保证全局无翻转。
策略二:利用样条基函数的凸包性质
对于由 B 样条等基函数构造的映射,其雅可比行列式可以写成控制点处雅可比值的凸组合(因为基函数非负且和为1)。因此,如果我们在所有控制点(采样点)处保证雅可比行列式非负,那么在整个定义域上,雅可比行列式也将非负。如果某些区域不满足,可以通过细分样条来获得更紧致的凸包逼*,从而满足条件。
总结




本节课中我们一起学*了从分片线性映射到光滑映射的过渡。我们介绍了四类构造光滑映射的方法:径向基函数 (RBF)、广义重心坐标映射、调和映射以及基于样条的方法。重点讨论了光滑映射在优化时面临的核心挑战——如何保证全局无翻转性质,并介绍了基于 Lipschitz 连续性和样条凸包性质的两种保证策略。这些方法为处理更高质量、更光滑的曲面参数化与变形提供了重要工具。


GAMES301-曲面参数化 - P9:Lecture 09 基于调和映射的高质量形变 🎨
在本节课中,我们将学*如何利用调和映射来实现高质量的二维和三维形状变形。我们将从变形问题的基本概念和需求出发,逐步介绍调和映射的理论基础、优化方法,并展示其在实时交互式变形中的应用。
概述
形状变形是计算机图形学中的一个经典且重要的问题,尤其在关键帧动画制作中。设计师通常先绘制关键姿势(关键帧),然后需要生成中间帧以形成流畅的动画。本节课将探讨如何通过优化一个光滑的映射来实现高质量、无翻转且扭曲可控的变形。
上一讲我们介绍了光滑映射构造的基础知识,本节中我们来看看如何利用调和映射这一特殊的光滑映射来实现高质量的形变。
变形问题的基本要求
作为一个用于动画设计的形状变形系统,需要满足以下核心要求:
- 直观的用户界面:用户可以通过简单的交互(如鼠标拖拽)来指定变形约束。
- 快速的算法:算法需要足够快,以提供实时反馈,允许用户根据结果调整约束。
- 高质量的变形结果:变形映射需要满足:
- 光滑性:映射本身是光滑的。
- 局部单射(无翻转):变形不会导致表面自交或折叠。
- 扭曲有界:变形的拉伸或压缩程度可以被控制在一定范围内。
为了直观地判断变形质量,我们可以将规则的圆形图案作为纹理贴到形状上。高质量的变形会使这些圆形尽可能保持为接*正圆的椭圆,且大小变化不大。
相关工作与挑战
现有的变形方法主要分为两类:基于网格(分片线性)的方法和无网格(基于光滑映射)的方法。
以下是现有方法的一些局限性:
- 基于广义重心坐标的方法:可以保证局部单射,但其扭曲的上界不可控,可能导致严重的拉伸或压缩。
- 基于可控保角映射的方法:不能直接指定关键点的位置约束,而这在变形中至关重要。
- 基于径向基函数(RBF)或样条基的方法:虽然可以证明结果是无翻转的,但其给出的扭曲上界估计往往不够“紧”,且基函数与形状本身关联不紧密,可能导致不自然的变形。
*面映射与扭曲度量
*面映射 f 将一个二维区域 Ω 映射到另一个二维区域 Ω',可以表示为向量函数 f(x, y) = (u(x, y), v(x, y))。其雅可比矩阵 J_f 描述了映射在一点附*的局部线性*似。
通过 J_f 的奇异值分解(SVD),我们可以得到两个奇异值 σ₁ 和 σ₂(σ₁ ≥ σ₂)。它们具有清晰的几何意义:映射 f 将定义域内某点处的一个无穷小圆,变换为目标域内的一个椭圆,该椭圆的长短半轴长度即为 σ₁ 和 σ₂。
基于奇异值,我们可以定义多种扭曲度量:
- 保角扭曲:希望椭圆保持为正圆(允许缩放),即
σ₁与σ₂尽可能接*。度量公式为(σ₁ - σ₂)²。 - 等距扭曲:希望椭圆不仅是正圆,且大小不变,即
σ₁和σ₂都接* 1。一个常用的度量是对称狄利克雷能量:
E_sd = (σ₁² + σ₂²) * (1/σ₁² + 1/σ₂²)
当σ₁ = σ₂ = 1时,E_sd取得最小值 4。

在复数域下,映射 f 可以表示为关于复变量 z = x + iy 的函数 f(z)。此时,奇异值有更简洁的解析表达式:
σ₁ = |f_z| + |f_z̄|, σ₂ = ||f_z| - |f_z̄||
其中 f_z 和 f_z̄ 是 f 关于 z 和其共轭 z̄ 的导数。


调和映射与边界最大值原理
调和映射是一类特殊的映射,其分量函数 u 和 v 满足拉普拉斯方程 Δu = 0, Δv = 0。在数学上,调和映射是最光滑的映射(无穷阶可导)。
调和映射的一个重要性质是边界最大值原理:调和函数在其定义域的边界上取得最大值和最小值。
对于变形问题,我们关心的是映射的扭曲(如 σ₁, σ₂ 或其函数)是否在整个区域上有界。一个关键的理论结果是:对于一个调和映射 f,如果其扭曲度量(如 |f_z|/|f_z̄| 和 σ₁/σ₂)在边界上有上界,并且 f 满足一个额外的积分条件(与 f_z 在边界上的积分有关),那么这些扭曲度量在区域内部也满足同样的上界。这相当于将需要在全二维区域上验证的“无翻转”条件,降维到了一维的边界上进行验证,极大地简化了问题。
基于调和映射的变形算法
有了上述理论基础,我们可以设计变形算法。
1. 参数化调和映射空间
首先,我们需要一个方便优化的调和映射表示。任何*面调和映射 f 都可以唯一地分解为一个全纯函数 g 和一个反全纯函数 h 的和:f = g + h。
利用柯西广义重心坐标,g 和 h 可以分别用两组复数向量 m 和 n 来参数化。这样,整个调和映射空间就被参数化为由 m 和 n 张成的空间,优化时只需调整 m 和 n。
2. 优化问题建模
我们的目标是找到一个调和映射 f,使其满足:
- 位置约束(软约束):用户指定的点被映射到目标位置。
- 扭曲有界(硬约束):在边界上,扭曲度量不超过用户给定的上界
K和T。 - 积分条件:满足前述理论中的边界积分条件,以保证内部点的扭曲也有界。

优化能量通常选择对称狄利克雷能量 E_sd 或其变体,位置约束作为惩罚项加入。这是一个带有非线性不等式约束的非凸优化问题。

3. 优化求解策略
- 凸化技术:对于非凸的扭曲约束,可以采用局部凸*似(如二次函数*似)的迭代策略,将每一步转化为一个凸优化问题来求解。
- 边界采样:理论上需要在无穷多个边界点上施加约束。实践中,我们在边界上采样一组有限点集
P,在这些点上施加约束。 - 利用 Lipschitz 连续性:为了将从有限采样点
P上满足的约束推广到整个边界,我们利用调和映射导数f_z和f_z̄的 Lipschitz 连续性。如果在采样点处f_z足够大且f_z̄足够小,那么在整个边界线段上,f_z都不会太小,f_z̄都不会太大,从而保证扭曲有界。
4. 算法缺陷与改进
上述方法存在一些缺陷:扭曲上界需用户指定;位置约束是软约束,在约束过严时可能无法满足;凸化迭代求解可能较慢。
为了获得更快的交互速度,后续研究提出了基于牛顿法的优化方案:
- 目标函数:直接使用对称狄利克雷能量
E_sd加位置约束惩罚项。E_sd本身具有障碍函数性质,当映射趋于退化(σ₂ → 0)时能量趋于无穷大,这有助于在优化路径上保持映射无翻转。 - 牛顿法求解:通过计算目标函数的海森矩阵,使用牛顿迭代快速收敛。需要对海森矩阵进行投影以保证其正定性,从而获得下降方向。
- GPU 加速:整个优化流程(矩阵乘法、稠密线性方程组求解、更新)非常适合在 GPU 上并行实现,能达到实时交互的速度。
从二维到三维体变形
三维体变形的核心思想与二维*面变形类似,但更加复杂。
- 映射描述:三维映射将单位球映射为椭球,三个奇异值
σ₁, σ₂, σ₃对应椭球三个主轴的长度。无翻转条件为σ₃ > 0(雅可比行列式 > 0)。 - 挑战:
- 无解析表达式:三维奇异值没有像二维那样的简洁解析表达式,依赖于数值 SVD 计算。
- 定理不直接成立:二维中“边界扭曲有界可推出内部扭曲有界”的定理在三维中不成立。因此,我们必须在整个三维区域内部进行采样验证。
- 解决方案:
- 估计 Lipschitz 常数:为了从采样点验证推广到全区域,需要估计扭曲函数(如
σ₃)的 Lipschitz 常数。我们利用调和映射的二阶导数(海森张量)来给出一个上界估计。 - 增加光滑性惩罚:在优化能量中加入映射二阶导的范数作为惩罚项,强制映射更加光滑,从而获得更紧的 Lipschitz 常数估计,减少所需的采样点数量。
- 降维:三维体网格通常顶点很多,导致自由度巨大。可以通过主成分分析(PCA)在由最光滑的调和映射张成的子空间中进行优化,大幅提升速度。
- 估计 Lipschitz 常数:为了从采样点验证推广到全区域,需要估计扭曲函数(如




三维体变形算法同样可以实现实时交互,并保证变形结果光滑、无翻转。

总结
本节课中,我们一起学*了基于调和映射的高质量形变方法。
- 我们首先明确了变形应用对高质量映射(光滑、无翻转、扭曲有界)的需求。
- 接着,我们回顾了调和映射的数学定义和重要性质,特别是其边界最大值原理,这为将全局约束简化为边界约束提供了理论依据。
- 然后,我们详细介绍了如何参数化调和映射空间,并建立包含位置约束和扭曲约束的优化问题。我们探讨了使用凸化技术和Lipschitz连续性进行求解和验证的策略。
- 为了追求实时性能,我们进一步介绍了基于牛顿法和对称狄利克雷能量的快速优化方案,该方案非常适合GPU加速。
- 最后,我们将方法推广到更复杂的三维体变形,解决了奇异值无解析表达式和边界定理不成立等挑战,通过估计 Lipschitz 常数、增加光滑项和降维等技术,实现了高效的体变形。

通过利用调和映射的光滑性和独特的数学性质,我们能够构建出既满足高质量要求,又能实现实时交互的变形系统,为数字动画和几何处理提供了强大的工具。

GAMES302-等几何分析 - P1:1. CAE与等几何分析简介 🎯
概述
在本节课中,我们将学*产品数字化设计、仿真与优化的全流程,并深入探讨等几何分析提出的背景、基本思想及其核心优势。课程旨在为初学者构建一个清晰的知识框架。
产品数字化设计、仿真与优化全流程
产品数字化设计与制造通常经历三个主要阶段:计算机辅助设计、计算机辅助工程仿真和结构优化。
以下是各个阶段的基本概念介绍。

计算机辅助设计

计算机辅助设计利用计算机系统辅助用户进行产品的创造、修改、分析和优化。其理论基础是样条,特别是以控制顶点为工具的Bézier/NURBS表示法。

核心公式:一条参数曲线可以表示为:
C(u) = Σ [N_i,p(u) * P_i]
其中,P_i 是控制顶点,N_i,p(u) 是p次的B样条基函数。
通过移动控制顶点,可以对曲面形状进行直观的编辑和修改。
计算机辅助工程仿真


计算机辅助工程仿真是指在产品研发中,利用计算机对产品的性能进行建模与分析。其主要作用是替代昂贵且费时的物理样机实验。
CE解决问题的一般过程包括:
- 建立物理模型。
- 将物理模型转化为数学模型。
- 将数学模型转化为数值模型。
- 进行数值计算与结果验证。
其中,有限元法是最经典和重要的数值求解方法。



结构优化
在仿真分析后,若设计不满足要求,则需要进行结构优化。优化旨在初始设计基础上寻求性能更优的方案。
结构优化主要分为三类:
- 尺寸优化:寻找如厚度、材料属性等设计参数的最优组合。
- 形状优化:在详细设计阶段对现有几何形状进行优化。
- 拓扑优化:一种创新的概念设计技术,在给定设计空间内寻找最优的材料分布,实现轻量化。
上一节我们介绍了产品从设计到优化的完整链条。本节中我们来看看这个流程中存在的一个核心挑战。


等几何分析的提出背景与基本思想
语言鸿沟问题


在传统的CAD/CAE流程中,存在显著的“语言鸿沟”:
- CAD阶段:使用NURBS等样条进行精确的解析表示。
- CAE与优化阶段:需要将几何离散化为网格进行*似计算。

这种数据表示的不一致导致了离散误差、额外的网格生成与数据转换工作,降低了整体效率。

等几何分析的基本思想
等几何分析的核心思想非常简单:既然能用样条语言精确描述几何外形,那么也能用同一种样条语言来描述物理场。

核心思想代码描述:
// 传统CAD几何:控制顶点定义几何
Point GeometricPoint = Σ(N_i(u) * P_geom_i);
// IGA物理场:扩展控制顶点,额外分量定义场变量
Point FieldPoint = Σ(N_i(u) * P_field_i); // P_field_i = (x, y, z, field_value)

在IGA中,计算单元不再是三角形或四面体网格,而是样条节点区间所对应的曲面片或体片。这使得几何建模、物理仿真和结构优化可以在同一个样条空间内完成,实现了真正的数据统一。
等几何分析与有限元法的对比
等几何分析与传统有限元法最本质的区别在于计算单元的不同。IGA直接使用精确的CAD几何作为分析基础,带来了多重优势:
- 精度高:在相同自由度下,可获得更高计算精度。
- 效率高:在相同精度要求下,所需自由度更少。
- 几何精确:局部加细/升阶操作可精确保持几何不变。
- 优化便利:可直接将CAD控制顶点作为优化变量,结果可直接导入CAD系统。


等几何分析的典型应用与前景
自2005年提出以来,等几何分析已在多个领域展现出独特优势:

- 板壳分析:避免了几何离散误差,对薄壁结构仿真至关重要。
- 流固耦合:样条在接触界面处可精确保持几何一致性。
- 相场模拟:适用于包含高阶导数项的计算问题。
- 形状与拓扑优化:优化变量直接关联几何控制顶点,流程顺畅。



其发展趋势是从简单问题走向复杂工业应用,并与深度学*等新技术结合。



课程安排与开源*台
本课程共安排14次讲授,后续内容将涵盖:
- 曲线曲面建模基础。
- 有限元法框架。
- 等几何分析实现。
- 参数化与网格生成。
- 线弹性、非线性问题求解。
- 形状与拓扑优化。
- 等几何分析与深度学*的结合。

课程将基于课题组开发的iGame开源*台进行实践。该*台支持C++开发,提供样条、网格等基础数据结构与算法,鼓励社区共同贡献代码。

总结

本节课我们一起学*了产品数字化设计、仿真与优化的全流程,认识了传统CAD/CAE流程中存在的“语言鸿沟”问题。在此基础上,我们深入探讨了等几何分析的基本思想——使用统一的样条语言描述几何与物理场,并分析了其相对于传统有限元法的优势。最后,我们对课程的整体安排和开源实践*台进行了介绍。等几何分析作为连接设计与仿真的桥梁,在提升精度、效率和优化便利性方面具有巨大潜力。

GAMES302-等几何分析 - P10:10. 基于等几何分析的拓扑优化 🏗️

概述

在本节课中,我们将学*基于等几何分析的拓扑优化。上一节我们介绍了基于等几何分析的形状优化,本节我们将探讨拓扑优化的基本概念、几种经典方法,以及如何将等几何分析的优势融入其中,实现更高效、更精确的结构优化设计。
什么是拓扑优化?🎯

拓扑优化是结构优化的一个重要分支。我们上节课介绍了形状优化,它主要优化模型的外形。而拓扑优化的目标,是通过改变物体内部材料的分布,来获得最佳的力学性能。


拓扑优化与创成式设计紧密相关。用户无需理解复杂的数学原理,只需指定边界条件、设计空间、目标函数和约束,计算机软件就能自动计算出最优的材料分布和拓扑结构。这使得拓扑优化成为一种强大的概念设计工具。
例如,赵州桥的结构,与通过拓扑优化计算出的最优材料分布惊人地相似,这说明了经验设计与科学优化结果的一致性。
以下是拓扑优化的一些应用领域:
- 工业设计:如摩托车车架、自行车零件、鞋底的轻量化设计。
- 航空航天:飞机隔板、机翼、直升机部件的结构优化。
- 建筑设计:生成新颖、高效且美观的建筑结构。
- 个性化定制:结合3D打印技术,实现复杂结构的快速制造。



拓扑优化的数学描述与挑战
我们可以将一个简单的悬臂梁拓扑优化问题类比为“乐高积木”问题:在一个由200个格子组成的设计域中,放置60个积木块,如何摆放能使最终结构的刚度最大?
这是一个离散优化问题。从200个格子中选60个,可能的组合数量极其庞大(约 7×10^51 种)。直接求解这样的离散优化问题非常困难。
一个常见的思路是进行松弛,将离散的0-1优化问题(表示有无材料)转化为连续的优化问题,即让每个单元的“密度” ρ 在 [0, 1] 区间内连续变化。这大大降低了问题的求解难度,也是后续许多经典方法的基础。
经典拓扑优化方法


上一节我们了解了拓扑优化的基本思想和挑战,本节中我们来看看几种主流的、经典的拓扑优化方法。
1. SIMP法(变密度法)📊
SIMP(Solid Isotropic Material with Penalization)方法是目前应用最广泛的拓扑优化方法之一。其核心思想正是上述的连续化松弛,并引入惩罚因子来迫使中间密度值向0或1两端聚集。
公式描述:
单元的材料弹性模量 E 与伪密度 ρ 的关系通过惩罚模型建立:
E(ρ) = E_min + ρ^p * (E_0 - E_min)
其中,E_0 是实体材料模量,E_min 是一个极小的数(避免奇异),p 是惩罚因子(通常 p≥3)。当 ρ 为中间值时,其贡献被严重惩罚,从而在优化中倾向于被消除。
在等几何分析框架下,控制点上的密度值 d_k 通过样条基函数 N_k 加权组合,得到空间中任意点的密度:
ρ(ξ) = Σ N_k(ξ) * d_k
这使得灵敏度过滤具有天然优势,能有效抑制棋盘格现象。
优化模型(以最小化柔度为例):
min: C(ρ) = U^T K U = Σ (ρ_e^p * u_e^T k_0 u_e)
s.t.: K U = F
V(ρ) / V0 ≤ f
0 < ρ_min ≤ ρ ≤ 1
其中,C 是柔度,U 和 F 是位移和力向量,K 是刚度矩阵,V 是体积,f 是体积分数约束。

等几何分析的优势:
- 计算效率高:所需自由度远低于有限元法,计算速度可提升约一倍。
- 抑制棋盘格:样条基函数的局部支撑性起到了天然的过滤作用。
- 结果更光滑:优化出的边界质量更好。

2. 双向渐进结构演化法 (BESO) 🔄
BESO方法采用一种渐进演化的思路。它通过逐步删除低效的材料单元(空单元)和添加高效的空单元(实体单元),使结构向最优拓扑演化。
其优化模型与SIMP类似,但设计变量 x_e 是离散的(通常为0或1)。关键在于计算每个单元对目标函数(如柔度)的灵敏度 α_e:
α_e = -∂C/∂x_e
灵敏度低的单元对结构性能影响小,优先被删除;灵敏度高的空单元则可能被恢复为实体。
在等几何框架下,单元灵敏度可以通过控制点灵敏度的样条组合解析求得,使得BESO的迭代过程更加稳定和高效。
算法流程简述:
- 定义初始设计域和参数(目标体积、进化率)。
- 进行等几何分析,计算单元灵敏度。
- 根据灵敏度排序和进化率,更新单元状态(删除或添加)。
- 判断是否满足体积约束和收敛准则(如最*10次迭代目标函数变化很小)。
- 若不收敛,则返回步骤2;否则,输出最终拓扑。
3. 水*集法 (Level Set) 🧭


水*集法采用隐式几何描述。它将结构的边界描述为一个更高维函数 Φ(x) 的零水*集:
{x | Φ(x) = 0} 为边界,
{x | Φ(x) > 0} 为材料外部,
{x | Φ(x) < 0} 为材料内部。
结构的演化通过水*集函数 Φ 随时间的运动来描述,其演化方程(Hamilton-Jacobi方程)为:
∂Φ/∂t + V_n |∇Φ| = 0
其中 V_n 是边界法向速度。
传统的水*集方法计算量大。参数化水*集法将其与等几何结合,用样条基函数组合来表示水*集函数:
Φ(x, t) ≈ Σ φ_k(t) * N_k(x)
这样,优化变量变成了系数 φ_k(t),将偏微分方程求解转化为系数优化问题,效率大幅提升。
等几何分析的优势:
- 计算加速:相比基于有限元的水*集法,计算速度可提升3-4倍。
- 边界光滑:直接得到光滑的隐式边界,便于后续提取。
4. 移动可变形组件法 (MMC) 🧩
MMC方法由郭旭老师团队提出,它建立了一种显式几何描述的新范式。其思想是在设计域内放置一系列具有明确几何参数(如中心位置、长度、厚度、方向角)的基本结构组件,将这些几何参数作为设计变量。
每个组件由其拓扑描述函数 Φ_i(x) < 0 来定义内部区域。整个结构的拓扑由所有组件的布尔运算(通常为并集)构成。优化过程就是驱动这些组件移动、变形和重叠,以找到最优布局。
等几何分析的优势:
- 设计变量少:优化变量是组件的几何参数,数量远少于单元密度变量。
- 结果直接可用:优化结果本身就是由参数化组件构成的显式几何,易于导入CAD系统。
- 易于施加几何约束:如最小尺寸、对称性等约束更容易在参数层面实现。
拓扑优化的后处理与CAD重建 🔧
无论采用上述哪种方法,优化结果往往不能直接用于制造。SIMP法结果存在锯齿和灰度单元;水*集法和MMC法需要提取边界。因此,将拓扑优化结果转化为可用的CAD模型是一个关键环节。




以下是后处理与重建的一般步骤:
- 结果光顺:对离散的优化结果进行网格*滑,去除噪声和孤立单元。
- 边界提取:从光顺后的网格或隐式函数中提取三角网格表示的边界。
- 骨架生成与简化:对于杆状、梁状结构,提取其中心骨架线。
- 参数化CAD模型重建:基于骨架或边界网格,生成参数化的样条曲线/曲面(如B样条、T样条),重建为BREP格式的CAD模型。

这项工作实现了从“优化结果”到“可制造模型”的桥梁,是拓扑优化走向工程应用不可或缺的一步。





总结与展望

本节课我们一起学*了基于等几何分析的拓扑优化。我们首先了解了拓扑优化的概念和应用,然后深入探讨了四种经典方法:SIMP法、BESO法、水*集法和移动可变形组件法。可以看到,将等几何分析融入这些框架,主要在计算效率和结果质量上带来了显著提升。
目前大多数工作仍是在传统拓扑优化思想中引入等几何分析。未来的研究方向包括:
- 开发具有等几何分析特色的新方法,而不仅仅是效率提升。
- 与人工智能/机器学*结合,利用AI加速优化过程或生成新颖设计。
- 深化后处理与CAD/CAE集成的无缝流程,实现真正的设计-仿真-优化-制造一体化。

拓扑优化作为创成式设计的核心,随着等几何分析等先进仿真技术的融合,必将在高端装备设计和个性化制造中发挥越来越重要的作用。

GAMES302-等几何分析 - P11:基于体细分的等几何建模仿真优化一体化框架 🧱
概述
在本节课中,我们将学*如何利用体细分技术,构建一个连接几何建模、物理仿真与结构优化的统一框架。这个框架旨在解决等几何分析中复杂实体建模的难题,并实现从设计到分析再到优化的无缝流程。
第一部分:基于体细分的复杂实体建模 🏗️
上一节我们介绍了面向等几何分析的参数化工作。本节中,我们来看看如何利用体细分技术实现复杂实体的直接建模。
等几何分析不仅为计算机图形学开辟了新方向,也为计算几何提出了新问题,例如复杂实体建模。对于计算力学的应用而言,能够方便、直观地创建任意形状的实体模型至关重要。
我们的核心问题是:给定一个具有复杂拓扑的六面体控制网格,如何构造相应的样条实体表示?这里有两个关键词:复杂与拓扑。复杂形状通常需要多块拼接才能完成。
曲线救国:从八叉树网格到拓扑简化
目前,全自动化生成高质量的六面体网格仍是一个挑战。我们采用了一种“曲线救国”的思路:
- 首先利用八叉树方法生成一个初始的六面体网格。这种方法相对自动化,但生成的网格在表面或附*会形成复杂的奇异结构(即不满足常规连接规则的点或线),导致分块多、网格质量差。
- 然后,通过拓扑简化方法,将复杂的奇异结构简化为更干净、规则的结构。经过简化,网格块数大幅减少,网格质量(如*均雅可比比、最小雅可比比)得到显著提升。
以下是拓扑简化过程的要点:
- 我们采用了一种基于加权排序的简化策略。
- 该策略不仅考虑删除哪些网格块(如先删除宽块),还考虑了简化操作对拓扑信息的影响。
- 简化后的分块结构更规整,非常适合进行后续的体参数化或CAD重建。
细分建模:从曲面到实体
另一种解决方案是借鉴图形学中的细分建模思想。细分的基本原理是:给定一个粗糙的控制网格,按照预设规则递归地插入新顶点和新面,经过多次迭代后得到光滑的曲线或曲面。细分方法(如Catmull-Clark细分)突破了传统B样条的拓扑限制,能实现任意拓扑曲面的建模。
那么,能否将细分曲面推广到实体,形成体细分呢?答案是肯定的。体细分并非新概念,早在1996年就被用于自由变形(FFD),2002年则有学者提出了六面体网格的细分规则。
Catmull-Clark体细分规则简述如下(C代表体单元,F代表面,E代表边,V代表顶点):
- 计算新体心点:对每个六面体单元,计算其所有顶点的*均值作为新的体心点(
C_new)。 - 计算新面点:对每个原有面,计算其两个相邻体单元的体心点(
C1,C2)以及该面重心的加权*均作为新面点(F_new)。 - 计算新边点:对每条原有边,计算其相邻面点、体心点及边中点的加权*均作为新边点(
E_new)。 - 更新旧顶点:根据其相邻的新体心点、新面点、新边点及自身位置进行加权*均更新(
V_new)。 - 连接新网格:按照规则连接所有新点,形成细分一次后更密的六面体网格。
体细分的极限点公式:连接理论与应用的桥梁
对于Catmull-Clark细分曲面,存在一个显示的极限点公式,可以直接计算初始控制顶点经过无穷次细分后的极限位置。这个公式对于理论和应用都至关重要。
我们成功地将这个公式推广到了体细分。推导的关键在于分析奇异点处的局部结构。在一个度数为 N 的奇异点处放置一个单位球,该球与网格相交会在球面上形成一个三角化。利用球面三角化的欧拉公式,可以推导出该点处的面数 E 和体单元数 F 与度数 N 的关系:
- 面数公式:
E = 3 * (N - 2) - 体单元数公式:
F = 2 * (N - 2)
基于此,我们得到了Catmull-Clark体细分的显示极限点公式。该公式表明,初始控制顶点 V_i 的极限位置,可以由细分一次后产生的新体心点、新面点、新边点及新顶点线性组合得到。
有了这个极限点公式,我们可以实现两件重要的事情:
1. 样条体逼*
对于给定的六面体控制网格,我们可以为每个六面体单元构造一个三次B样条体。所有这些样条体拼接起来,就形成了对体细分极限体的一个样条逼*。在规则区域,该逼*是C2连续的;在奇异点处,是C0连续的。
样条体控制顶点的生成可以通过预定义的模板(Mask)高效完成。模板定义了如何从初始控制网格的顶点,通过线性加权组合得到样条体的内部点、边点和角点。
2. 样条体插值
我们希望构造一个体细分极限体,使其精确插值(经过)初始控制网格的顶点。利用极限点公式,这可以通过反向求解实现:将初始控制顶点设为目标极限点,反推出一个经过“推拉”扰动的新控制网格。从这个新控制网格出发进行体细分,得到的极限体就会插值原控制顶点。
具体方法包括:
- Push-Pull方法:直接根据极限点公式调整控制顶点。
- 渐进迭代逼*(PIA)方法:迭代调整控制顶点,使其极限位置逐步逼*目标点。极限点公式在这里用于计算每次迭代的“目标”位置。
此外,我们还可以在建模时为实体赋予材料属性(作为第四维坐标),实现材料的梯度分布建模。

交互式复杂实体建模框架

结合样条体逼*和插值技术,我们开发了一个交互式复杂实体建模框架。该框架允许用户通过直观操作(如拉伸、旋转、缩放、拓扑分裂、模型拼接等)来构造复杂的六面体控制网格,并自动生成对应的样条实体模型。
框架功能包括:
- 骨架节点的编辑与角度自动调整。
- 中间过渡体的生成与连接。
- 拓扑分裂与融合。
- 尖锐特征的保持(通过修改细分规则)。
- 网格单元质量的优化(通过插入层等方式)。
有了这个工具,即使不熟悉计算机几何和样条理论的计算力学研究者,也能方便地创建复杂实体模型,用于后续的等几何分析。
第二部分:基于体细分的高精度IGA物理仿真 ⚙️
上一节我们介绍了如何利用体细分进行复杂实体建模。本节中,我们来看看如何基于这些实体模型进行高精度的等几何分析物理仿真。
拥有了体细分表示的实体模型后,我们就可以在此基础上开发等几何分析求解器,进行物理仿真,例如:

- 三维线弹性力学分析
- 热传导分析

与传统的简单模型分析类似,我们可以直接利用样条体作为分析域,进行等几何分析。分析结果表明,在模型的大多数区域都能获得高精度解,而在奇异点附*,由于连续性降低,误差会相对较大。这验证了基于体细分模型进行仿真的可行性,使得对复杂实体进行等几何分析成为可能。
第三部分:基于体细分的IGA形状与拓扑优化 🔧
上一节我们介绍了基于体细分模型的物理仿真。本节中,我们来看看如何在此基础上进行形状与拓扑优化。
形状优化

在等几何分析中,形状优化具有天然优势:控制顶点可以直接作为设计变量。基于我们的体细分模型,我们可以对复杂模型进行形状优化。优化过程只需改变控制网格的顶点位置,无需重新生成网格,实现了设计与分析的无缝衔接。

拓扑优化
我们同样可以基于体细分表示进行等几何拓扑优化。通过计算灵敏度并迭代更新材料密度场,可以在样条体参数域内实现拓扑的演变。
体细分自带多分辨率特性,这为高效拓扑优化提供了便利:
- 在粗层级进行分析:在较粗的体细分层级上进行昂贵的等几何分析(求解大型方程组)。
- 在细层级进行优化:在较细的层级上计算相对简单的灵敏度并进行拓扑更新(如“挖洞”)。
这种“分析在粗层,优化在细层”的策略,可以显著降低计算成本。
优势:精确保持几何约束
基于等几何的拓扑优化有一个显著优势:能够精确保持几何约束。例如,在优化过程中,可以要求模型上的某些孔洞或尖锐特征保持不变。这是因为等几何分析使用精确的几何表示,在优化迭代中不会“侵蚀”这些预设的几何特征。而传统的基于体素(Voxel)的方法很难做到这一点。
我们通过多个算例(如悬臂梁、MBB梁、连接件等)验证了该框架的有效性,优化结果与商业软件结果吻合,且能完美保持指定的几何特征。
总结
本节课我们一起学*了基于体细分的等几何建模仿真优化一体化框架。
- 体细分是连接等几何分析与实体建模的关键桥梁,其显示的极限点公式是理论核心。
- 基于此,我们实现了复杂实体的交互式建模,为等几何分析提供了模型来源。
- 在此基础上,可进行高精度的物理仿真(如弹性力学、热传导)。
- 进一步,可开展形状与拓扑优化。体细分的多分辨率特性支持高效的多分辨率拓扑优化,并且等几何方法能精确保持优化过程中的几何约束,这是传统方法难以实现的。

本框架实现了几何建模、物理仿真、结构优化三者数据表示的一致性,避免了传统流程中网格重生成的环节,形成了一个完整、高效的设计分析闭环。体细分技术在未来等几何分析的发展中,将扮演越来越重要的角色。


GAMES302-等几何分析 - P12:基于深度学*的等几何分析与课程总结 📚

在本节课中,我们将学*如何将深度学*技术与等几何分析相结合,以解决拓扑一致模型的分析重用问题,并对整个等几何分析课程进行总结。
研究背景与动机 🔍
上一节我们介绍了基于体积分的等几何建模与仿真一体化框架。本节中,我们来看看如何利用人工智能技术来增强等几何分析的能力。
等几何分析本质上是一个求解偏微分方程(PDE)的过程。*年来,AI for Science 成为一个热门领域,旨在利用人工智能技术解决科学计算中的复杂问题。具体到我们的领域,即 AI for PDE,目标是利用深度学*更快速、更稳定地求解PDE。
传统的等几何分析或有限元方法在求解大规模复杂模型时,计算成本高昂。而深度学*模型一旦训练完成,其推理速度极快。因此,我们希望借助深度神经网络来解决复杂的PDE问题,节省大量计算时间。
基于等几何分析生成训练数据集有其独特优势:
- 高阶连续性:等几何分析本身具有高阶连续性,有助于神经网络预测出光滑的物理场。
- 无需数据交换:在优化和求解阶段无需进行繁琐的格式转换。
- 高精度数据:生成的数据集质量高,有利于神经网络的训练。

我们的核心目标是解决 分析重用 问题:已知在一个模型(Model A)上的等几何分析解,如何快速预测另一个拓扑一致但几何形状不同的模型(Model B)上的解?例如,已知某个人体模型的应力分布,预测另一个不同体型人体模型的应力分布。直接通过几何映射得到的结果误差很大,因此需要更智能的方法。

基于卷积神经网络(CNN)的等几何分析 🧠
首先,我们介绍基于卷积神经网络的等几何分析方法。CNN是一种擅长处理网格化数据(如图像)的深度学*模型。
以下是CNN的基本结构组件:
- 输入层:接收输入数据,通常是二维矩阵(如图像像素)。
- 卷积层:使用滤波器(卷积核)在输入数据上滑动,进行内积运算,提取局部特征。
- 池化层:对局部区域进行统计(如最大池化、*均池化),实现特征抽象和降维。
- 激活函数层:引入非线性,如ReLU函数。
- 全连接层:整合特征,输出最终预测结果。
我们的工作流程如下:
- 数据准备:生成大量拓扑一致但形状不同的B样条模型,并使用等几何分析求解PDE,得到控制顶点系数作为标签数据。
- 数据格式化:将控制顶点坐标归一化并嵌入到一个二维矩阵中,将其视为“图像”输入CNN。
- 网络训练:采用 U-Net3+ 网络架构,并引入了自注意力机制模块,以更好地捕捉特征间的全局关系。
- 损失函数:结合了系数损失和数值解损失,以同时优化预测系数和最终物理场的精度。
实验结果表明,该方法能有效预测新模型上的物理场,相对误差通常在1%到5%之间,验证了其可行性。



基于图神经网络(GNN)的等几何分析 🕸️

虽然CNN方法有效,但将复杂的多片B样条模型强制嵌入二维矩阵存在冗余信息多、扩展至三维困难等缺陷。因此,我们进一步探索了基于图神经网络的方法。

图神经网络专为处理非结构化数据(如图、知识图谱)设计。对于多片B样条模型,其控制顶点及连接关系天然构成一个图结构,这与GNN的应用场景高度契合。
以下是采用GNN方法的优势:
- 自然表示:直接利用控制顶点作为图节点,片间连接关系作为边,无需暴力嵌入矩阵。
- 易于扩展:可轻松扩展到三维体参数化模型。
- 局部性匹配:B样条的局部支撑性与GNN的邻域聚合操作原理相通。
我们提出了 IG-GraphNet 框架:
- 图构建:从B样条模型中提取控制顶点(节点)及其连接关系(边),构建图数据。
- 网络架构:基于 ResNet-v2 和 Point Transformer 改进,引入了残差连接以缓解GNN中常见的过度*滑问题。
- 特征增强:增加了位置编码等初始特征,以强化网络的特征提取能力。
与基于CNN的方法相比,IG-GraphNet在预测精度上有明显提升,误差进一步降低,证明了图结构更适用于表示复杂的等几何分析模型。



未来展望与课程总结 🚀

基于深度学*的方法为解决等几何分析中的分析重用问题提供了新思路。未来工作可以从以下几个方面展开:
- 网络结构改进:设计更适配PDE求解特性的网络架构。
- 数据集与问题扩展:扩充数据集类型,求解更广泛的PDE类型和边界条件,并将工作推广到三维问题。
- 物理信息融合:探索基于物理规则的无监督学*方法,减少对大量标签数据的依赖。
- 应用于优化:将该方法集成到形状优化、拓扑优化流程中,极大提升优化效率。


课程总结 📖
本节课是我们《等几何分析》系列课程的最后一讲。我们一起回顾了整个课程的知识体系:
- 引言与背景:介绍了CAD/CAE无缝融合的愿景及等几何分析的提出背景。
- 计算几何基础:深入讲解了曲线、曲面的B样条/NURBS建模。
- 有限元与网格:阐述了等几何分析与传统有限元法及网格生成的关系。
- 等几何参数化:重点探讨了面向分析的几何参数化这一核心问题。
- 求解器框架:详细介绍了泊松问题、线弹性问题及超弹性问题的等几何求解框架。
- 结构优化:展示了等几何在形状优化和拓扑优化中的天然优势。
- 体积分方法:介绍了基于体积分的建模-仿真-优化一体化新框架。
- 深度学*应用:探讨了AI与等几何结合,实现分析重用的新途径。
等几何分析的发展趋势正从二维走向三维,从线性扩展到非线性,从单场仿真迈向多场耦合,并从传统求解转向AI预测。然而,仍面临诸多挑战,如复杂三维模型的参数化构造、裁剪曲面的处理、工业软件研发等。

希望本课程能为大家进入等几何分析领域打下坚实基础。感谢大家一路的陪伴与学*,期待各位能在该领域深耕,共同推动其理论发展、软件生态建设及工业应用。

课程视频与PPT资源已整理发布,供大家持续学*与参考。

GAMES302-等几何分析 - P2:曲线曲面曲体的建模基础 📐

在本节课中,我们将学*等几何分析(Isogeometric Analysis, IGA)的几何建模基础。等几何分析的核心思想是使用与几何表示相同的样条语言来表示物理场,因此,理解曲线、曲面和曲体的样条表示至关重要。本节课将详细介绍参数表示、贝塞尔曲线/曲面、B样条以及NURBS等核心概念,并简要介绍iGame几何内核的实现框架。



概述 📖
上一讲我们介绍了等几何分析的提出背景及其与CAD/CAE的关系。本节中,我们将深入探讨其几何建模的理论基础。具体内容包括:几何形状的参数表示、曲线造型理论(贝塞尔曲线、B样条、NURBS)、曲面与曲体的造型理论,以及iGame几何内核的简要介绍。
几何形状的参数表示
在计算机辅助几何设计中,参数曲线曲面是核心。参数表示的核心思想是将几何形状上点的坐标(x, y, z)表示为另一个(或几个)参数的函数。
参数表示相较于显式表示(如 y = f(x))和隐式表示(如 F(x, y) = 0)具有显著优势:
- 易于计算和离散化:给定参数值,可直接计算对应点的坐标,便于计算机处理和图形渲染。
- 几何量计算简便:便于计算切线、法向、曲率等微分几何属性。
- 形状控制直观:通过引入控制多边形/网格等概念,可以实现直观的形状编辑和预测。
一条三维参数曲线可表示为:
C(u) = (x(u), y(u), z(u))
其中,u 是参数。
对于曲面,则需要两个参数 (u, v):
S(u, v) = (x(u, v), y(u, v), z(u, v))
曲线造型理论与算法

上一节我们介绍了参数表示的优势,本节中我们来看看几种具体的参数曲线表示方法。

贝塞尔曲线
贝塞尔曲线由法国工程师Pierre Bézier提出,其核心是通过控制多边形来定义曲线形状。移动控制顶点可以直观地预测和修改曲线形状。
一条 n 次贝塞尔曲线定义为:
C(u) = Σ_{i=0}^{n} B_{i,n}(u) P_i
其中:
- P_i 是控制顶点(矢量)。
- B_{i,n}(u) 是 n 次伯恩斯坦基函数。
伯恩斯坦基函数定义为:
B_{i,n}(u) = C(n, i) * (1-u)^{n-i} * u^{i}
其中,C(n, i) 是二项式系数。
伯恩斯坦基函数具有以下重要性质:
- 非负性:在参数区间 [0, 1] 内,B_{i,n}(u) ≥ 0。
- 单位分解性:Σ_{i=0}^{n} B_{i,n}(u) = 1。这一性质保证了贝塞尔曲线的凸包性。
- 递归定义:高次基函数可由低次基函数组合得到,这引出了高效的德卡斯特里奥算法。
基于基函数的性质,贝塞尔曲线具有以下几何特性:
- 端点插值:曲线经过第一个和最后一个控制顶点。
- 端点切矢:曲线在端点处的切矢方向与控制多边形的第一条边和最后一条边一致。
- 凸包性:曲线完全位于其控制顶点构成的凸包内。
- 几何不变性:曲线形状仅取决于控制顶点的相对位置,与坐标系选择无关。
德卡斯特里奥算法
这是计算贝塞尔曲线上点的稳定递归算法,也称为“割角算法”。
以下是算法的递归公式(对于给定参数 u):
P_i^r(u) = (1-u) * P_i^{r-1}(u) + u * P_{i+1}^{r-1}(u)
其中,r = 1, ..., n;i = 0, ..., n-r;且 P_i^0(u) = P_i(原始控制顶点)。
最终,P_0^n(u) 即为曲线上对应参数 u 的点。
B样条曲线
贝塞尔曲线是整体曲线,修改一个控制顶点会影响整条曲线,且不能精确表示圆锥曲线。B样条曲线通过引入节点向量和分段定义,克服了这些缺点,同时保留了局部修改性和参数连续性。
一条 k 次 B 样条曲线定义为:
C(u) = Σ_{i=0}^{n} N_{i,k}(u) P_i
其中:
- P_i 是控制顶点。
- N_{i,k}(u) 是 k 次 B 样条基函数。
- 节点向量 U = [u_0, u_1, ..., u_{m}],其中 m = n + k + 1。
B 样条基函数由节点向量和次数 k 递归定义:
- 零次基函数 (k=0):
N_{i,0}(u) = { 1, if u_i ≤ u < u_{i+1}; 0, otherwise } - 高次基函数 (k>0):
N_{i,k}(u) = (u - u_i)/(u_{i+k} - u_i) * N_{i,k-1}(u) + (u_{i+k+1} - u)/(u_{i+k+1} - u_{i+1}) * N_{i+1,k-1}(u)
B 样条曲线继承了贝塞尔曲线的凸包性、几何不变性等优点,并新增了:
- 局部支撑性:每个基函数 N_{i,k}(u) 仅在局部区间 [u_i, u_{i+k+1}) 非零。因此,修改一个控制顶点 P_i 只影响该区间对应的曲线段。
- 强凸包性:曲线段位于定义该段的 k+1 个控制顶点形成的凸包内。
- 与贝塞尔曲线的联系:当节点向量两端出现 k+1 重节点时,B 样条曲线退化为贝塞尔曲线。
NURBS曲线
为了精确表示圆、椭圆等圆锥曲线,需要将 B 样条推广到有理形式,即非均匀有理 B 样条。
一条 k 次 NURBS 曲线定义为:
C(u) = Σ_{i=0}^{n} (w_i * P_i * N_{i,k}(u)) / Σ_{j=0}^{n} (w_j * N_{j,k}(u))
其中,w_i 是与控制顶点 P_i 关联的权因子。
通过调整权因子,可以精确控制曲线的形状,特别是能够精确表示圆锥曲线截面。
曲面与曲体造型理论
理解了曲线之后,将其思想推广到更高维度,就可以得到曲面和曲体的造型理论。
贝塞尔曲面
贝塞尔曲面是贝塞尔曲线在二维参数域 (u, v) 上的张量积推广。
一张 (m, n) 次的贝塞尔曲面定义为:
S(u, v) = Σ_{i=0}^{m} Σ_{j=0}^{n} B_{i,m}(u) B_{j,n}(v) P_{i,j}
其中,P_{i,j} 构成了 (m+1) x (n+1) 的控制顶点网格。
其性质(如凸包性、端点插值等)是曲线性质的直接推广。求值也可通过两个方向的德卡斯特里奥算法完成。
B样条曲面与NURBS曲面
类似地,B样条曲面是B样条曲线的张量积推广:
S(u, v) = Σ_{i=0}^{m} Σ_{j=0}^{n} N_{i,p}(u) N_{j,q}(v) P_{i,j}
其中,p 和 q 分别是 u 和 v 方向的次数,各有一个节点向量。
NURBS曲面则是NURBS曲线的张量积推广,具备了精确表示球面、柱面等二次曲面的能力,并成为工业标准(如STEP、IGES)中几何定义的基础。
曲体
对于三维实体域(常用于等几何分析求解三维问题),需要引入三个参数方向 (u, v, w)。

一个 (l, m, n) 次的 B 样条曲体定义为:
V(u, v, w) = Σ_{i=0}^{l} Σ_{j=0}^{m} Σ_{k=0}^{n} N_{i,p}(u) N_{j,q}(v) N_{k,r}(w) P_{i,j,k}
其中,P_{i,j,k} 构成了三维控制网格。

曲体在概念上是曲面向三维的延伸,但在某些方面(如复杂连接处的几何连续性定义)仍存在理论挑战。在等几何分析中,曲体用于填充三维物体的内部,其参数坐标空间中的“单元”即对应于物理空间的六面体子域,是IGA的计算单元。


iGame几何内核简介

为了帮助大家实践,课程提供了 iGame 几何内核(一个简化的样条库)。该内核包含了曲线、曲面、曲体的核心类定义,支持求值、求导等基本操作。
以下是核心类的简要说明:
Basis类:表示 B 样条基函数,存储次数、节点向量等信息,提供求值和求导函数。Geometry类(基类):所有几何对象(曲线、曲面、曲体)的父类,定义虚函数接口(如求值eval())。Curve类:继承自Geometry,表示 B 样条曲线,包含控制顶点列表。Surface类:继承自Geometry,表示 B 样条曲面,包含两个参数方向的信息。Volume类:继承自Geometry,表示 B 样条曲体,包含三个参数方向的信息。
该框架的数据存储采用自定义的 XML 格式,记录了几何对象的次数、节点向量、控制顶点及权因子等信息。
总结 🎯
本节课我们一起学*了等几何分析所需的几何建模基础。我们从参数表示的优势讲起,深入探讨了贝塞尔曲线的定义、性质及德卡斯特里奥算法;然后介绍了具有局部性的B样条曲线及其基函数递归定义;接着扩展到能精确表示圆锥曲线的NURBS曲线;最后将曲线理论推广至曲面和曲体,并简要介绍了实践所用的 iGame 几何内核。
理解这些样条表示是学*等几何分析的关键第一步,因为它们不仅是几何描述的工具,也将成为后续物理场离散和分析的基函数。下一讲,我们将介绍有限元方法的基础知识,以便更好地理解等几何分析的数值框架。

作业:基于提供的 iGame 样条库,实现 B 样条曲线的节点插入算法。即在给定节点向量中插入一个新节点后,计算出新的控制顶点,并保持曲线形状不变。


GAMES302-等几何分析 - P3:有限元分析与网格生成 🧩

在本节课中,我们将学*有限元分析的基本思想、核心步骤以及网格生成技术。有限元分析是计算机辅助工程(CAE)中的核心方法,而网格生成是其前处理的关键步骤。理解这些内容,对于后续学*等几何分析至关重要。
概述:物理仿真与边值问题
在工程与科学中,物理仿真问题通常可以归结为两类。
第一类是离散系统问题,例如桁架结构,可以看作由已知的杆单元组合而成。这类问题可以直接离散化求解。
第二类是连续系统问题,也称为场问题。这类问题需要建立其遵循的基本微分方程(PDE)及相应的边界条件。这类问题一般称为边值问题。
一个典型的边值问题可以描述为:在区域 Ω 内部,物理场函数 u 需满足微分方程 A(u)=0;在边界 Γ 上,需满足边界条件 B(u)=0。这里的 u 可以是标量场(如温度),也可以是矢量场(如位移、应力)。
对于这类边值问题,通常很难获得精确的解析解。因此,需要发展数值方法进行求解。
有限元方法的基本思想 🧠
有限元方法是基于变分思想发展起来的一种求解偏微分方程的数值计算方法。其核心思想是 “分而治之” 和 “分片*似”。
基本步骤如下:
- 求解域离散:将连续的求解区域 Ω 划分为有限个互不重叠且通过节点相互连接的小单元(如三角形、四边形)。
- 分片*似:在每个单元内部,选用简单的*似函数(通常是多项式)来逼*未知的物理场函数 u。
- 单元分析:基于问题的控制方程(如*衡方程),建立每个单元的刚度方程,将单元节点力与节点位移联系起来,形式为
{f}^e = [k]^e {u}^e。 - 整体组装:将所有单元的刚度矩阵按照节点连接关系组装成整体刚度矩阵,形成全局线性方程组
[K]{U} = {F}。 - 引入边界条件并求解:将已知的边界条件代入方程组,求解得到所有节点处的物理场值
{U}。 - 后处理:对求解结果进行可视化与分析。
上一节我们介绍了有限元方法的基本思想,本节中我们通过一个简单例子来具体说明其求解过程。
有限元方法示例:一维杆单元
考虑一个简单的一维杆单元,有两个节点 i 和 j。
- 节点位移向量:
{u}^e = [u_i, u_j]^T - 节点力向量:
{f}^e = [f_i, f_j]^T
假设节点 i 发生单位位移(u_i = 1),而节点 j 固定(u_j = 0),此时在节点 i 上产生的力 f_i 与材料属性(弹性模量 E,横截面积 A)和单元长度 L 有关。我们可以定义单元刚度系数 k_{ij}^e,表示在第 j 个自由度产生单位位移时,在第 i 个自由度上引起的力。
通过推导,可以得到单元刚度矩阵 [k]^e,它将节点力与节点位移联系起来:
{f_i \choose f_j} = \frac{EA}{L} \begin{bmatrix} 1 & -1 \\ -1 & 1 \end{bmatrix} {u_i \choose u_j}
即 {f}^e = [k]^e {u}^e。
对于由多个单元组成的结构,我们需要进行整体分析。以下是将单元刚度矩阵组装成整体刚度矩阵的关键步骤:
- 将每个单元的节点位移向量
{u}^e扩展为与整体位移向量{U}维度一致的向量。 - 将每个单元的刚度矩阵
[k]^e中的元素,根据其对应的整体节点编号,“对号入座”地叠加到整体刚度矩阵[K]的相应位置。 - 将所有单元的节点力向量
{f}^e也组装成整体节点载荷向量{F}。 - 最终得到整体*衡方程
[K]{U} = {F}。
通过求解这个方程组并引入边界条件,即可得到所有节点的位移。
有限元方法的核心概念 📚
以下是有限元分析中涉及的一些核心概念:
- 单元:区域离散后满足一定几何与物理特性的最小结构域,如一维杆单元、二维三角形单元、三维四面体单元。
- 节点:单元的连接点,是求解未知量的位置。
- 节点力:单元之间通过节点传递的相互作用力。
- 节点载荷:作用在节点上的外部力。
- 插值函数(形函数):用于在单元内部*似真实场函数的基函数。通常表示为
u(x) = Σ N_i(x) * u_i,其中N_i(x)是形函数,u_i是节点值。 - 刚度矩阵:描述结构刚度特性的矩阵,建立了力与位移的关系。
有限元解的精度受多种因素影响:
- 离散误差:用简单几何单元(如直边三角形)逼*复杂几何边界产生的误差。
- 位移函数误差:形函数对真实解逼*能力的不足。
- 数值积分误差:在计算单元矩阵时,采用高斯积分等数值方法带来的误差。
- 边界条件误差:施加的边界条件与真实物理模型之间的差异。

提高精度的方法包括:增加网格密度(h-细化)、采用高阶形函数(p-细化)、使用更精确的积分方案以及更合理地施加边界条件。

网格生成技术概述 🕸️
网格生成是有限元分析前处理的核心步骤,目标是将复杂的几何区域离散成适用于数值计算的单元集合。其背景源于有限元分析的需求,与图形学中源于三维扫描的“数字几何处理”既有联系也有区别。
一个完整的网格生成流程通常包括:曲线离散、曲面网格化、体网格化以及最终的网格质量评估。
网格生成算法主要分为结构化网格和非结构化网格两大类。


以下是常见的网格生成方法分类:

- 结构化网格
- 映射法:将参数域(如单位正方形)的规则网格映射到物理域。常用方法包括椭圆型PDE法、超限插值法、双曲型方法。
- 几何分解法:将复杂区域分解为若干简单的、易于映射的子区域(分块)。方法包括扫掠法、中轴法、子域映射法等。
- 非结构化网格
- 三角形/四面体网格:前沿推进法、Delaunay三角剖分法。
- 四边形/六面体网格
- 直接法:从边界直接向内部生成,如铺砌法、对偶法。
- 间接法:先生成三角形网格,再合并成四边形网格;或先生成四面体网格,再转换为六面体网格。

高质量的六面体网格生成被认为是挑战,其“黄金标准”包括:全自动、鲁棒性好、网格质量高。其中,大区域剖分是实现高质量网格的关键。
基于标量场的自动化四边形/六面体网格生成
我们团队在自动化网格生成方面做了一些工作,核心思想是利用标量场来引导区域剖分。
基本框架如下:
- 求解标量场:在目标区域上求解一个拉普拉斯方程
∇²φ = 0,并设置适当的边界条件(如狄利克雷条件),得到一个光滑的标量场φ。 - 分析场结构:分析标量场
φ的等值线和临界点(如鞍点、极值点)。临界点通常对应网格剖分所需的奇异点。 - 生成分区线:从奇异点出发,沿着标量场的梯度线或特定流线生成分区线,将复杂区域分割成若干个简单的子区域(如四边形区域或2-6边形区域)。
- 子区域网格化:对每个简单的子区域,利用映射法或扫掠法生成内部的高质量四边形网格。
- 整体组装:将所有子区域的网格组合,得到最终的整体网格。
该方法允许子区域是2到6边的多边形,而不仅仅是四边形。通过求解一个整数规划问题来确定各边界的离散段数,可以得到更简洁、更合理的区域剖分结果,且奇异点数目更少。
此方法也可直接应用于三维曲面,通过求解曲面上的PDE获得标量场,并在曲面上直接进行分区,从而避免了将曲面参数化到*面可能带来的扭曲问题,能更好地保持模型的尖锐特征。


网格生成是连接CAD与CAE的关键环节,也是当前国内工业软件研发的关注重点。在等几何分析中,虽然不再需要传统的“网格”,但面向分析的几何参数化问题,其本质——如何将复杂区域分解为高质量的参数化块——与结构化网格生成中的“大区域剖分”问题殊途同归。




总结
本节课我们一起学*了有限元分析与网格生成的核心内容。

我们首先了解了物理仿真中的边值问题,并深入探讨了有限元方法的基本思想:通过离散求解域和分片*似,将连续的偏微分方程问题转化为离散的线性代数问题求解。我们通过一维杆单元的示例,具体说明了单元刚度矩阵的推导以及整体刚度矩阵的组装过程。
接着,我们介绍了作为有限元前处理关键步骤的网格生成技术,概述了结构化与非结构化网格的主要生成方法。最后,我们分享了一种基于标量场引导的自动化四边形/六面体网格生成框架,该方法通过求解场方程自动识别剖分结构,能够生成高质量网格,其思想对于解决等几何分析中的参数化问题也具有重要借鉴意义。

理解有限元的基本流程和网格生成的概念,将为后续学*等几何分析奠定坚实的基础。在下节课中,我们将探讨等几何分析中的参数化问题。

GAMES302-等几何分析 - P4:等几何分析中的计算域参数化 I 🎯


在本节课中,我们将学*等几何分析中一个至关重要的前处理步骤——计算域参数化。我们将探讨其重要性、质量评价标准以及几种核心的构造方法。
第一部分:计算域参数化的由来 🔍
上一节我们介绍了有限元分析中的网格生成。本节中我们来看看等几何分析中对应的前处理步骤。

等几何分析的核心思想是使用与CAD系统相同的样条表示来描述几何和物理场,从而实现从几何建模到物理仿真的无缝集成。在等几何分析中,计算单元是样条基函数的定义域,即节点区间对应的参数片。这与有限元分析中使用三角形或四面体等离散网格单元有本质区别。
对于三维实体问题,CAD系统通常只提供边界表示。为了进行等几何分析,我们需要将实体内部用三维样条参数化表示“填充”起来。这个过程就是计算域参数化,它等价于有限元分析中的网格生成步骤。

为什么计算域参数化在等几何分析中尤为重要?原因在于等几何分析的自由度通常比有限元更少,因此参数化的质量完全由相对较少的内部控制顶点分布决定。此外,等几何分析中增加自由度(如节点插入或升阶)的方式不如有限元网格细分那样自由,因此一个高质量的初始参数化显得更加关键。
以下是一个直观的例子,说明参数化质量对分析结果的影响:
- 给定一个相同的四边*面区域,其边界曲线固定。
- 通过不同的内部控制顶点分布,可以构造出高质量(均匀、正交)或低质量的参数化。
- 在这两种参数化上求解同一个热传导PDE,得到的解场(温度分布)差异巨大。
- 高质量参数化得到的解更接*真实解,且收敛速度更快;而低质量参数化得到的解则扭曲失真,精度和效率都较差。
因此,如何构造适合分析的、高质量的计算域参数化,成为等几何分析领域的一个重要研究课题。

第二部分:计算域参数化的质量评价 📏

既然参数化如此重要,那么什么样的参数化才是“好”的呢?本节中我们来看看其评价标准。

一个适合等几何分析的计算域参数化应满足以下三个核心要求:
- 一一映射(无自交):从参数域到物理域的映射必须是单射,即参数域中的等参线(面)在物理域中不应相交。这保证了雅可比变换矩阵的行列式处处为正,是数值积分稳定的基础。
- 公式描述:对于*面B样条曲面
S(u,v) = ΣΣ N_i,p(u) N_j,q(v) P_ij,其雅可比行列式J(u,v)需满足J(u,v) > 0。
- 公式描述:对于*面B样条曲面


-
单元均匀性:参数化生成的等参单元(即计算单元)应尽量大小均匀。这有助于提高数值精度和稳定性。
- 目标:最小化所有等参单元面积的方差。
-
等参线正交性:不同参数方向的等参线应尽量保持正交(垂直)。这通常能带来更好的数值条件数。
- 目标:最大化等参线切线向量之间的夹角接*90度。
此外,从数值分析的角度,整体刚度矩阵的条件数也是一个高级评价指标。研究表明,参数化的质量(均匀性、正交性)直接影响刚度矩阵的条件数,进而影响求解器的稳定性和收敛速度。
基于这些标准,我们可以将构造高质量参数化的问题,形式化为一个带约束的优化问题:
- 优化变量:内部控制顶点的坐标。
- 目标函数:最小化不均匀性和非正交性的度量。
- 约束条件:雅可比行列式(或其控制系数)大于零,保证无自交。
第三部分:计算域参数化的构造方法 🛠️
了解了问题定义和质量标准后,本节我们探讨几种具体的参数化构造方法。求解上述优化问题需要一个良好的初始解。
初始解构造:离散的Coons方法
对于给定四条边界B样条曲线的*面区域,一种简单直接的初始参数化构造方法是离散的Coons方法。其思想是对边界控制顶点进行双向线性插值。
算法思路:
- 沿u方向对两侧(v为常数)的边界控制点进行线性插值。
- 沿v方向对两侧(u为常数)的边界控制点进行线性插值。
- 将两次插值结果相加,并减去四个角点插值结果以避免重复计算。
该方法计算高效,但生成的参数化质量可能不高,在边界复杂时容易产生自交区域。该方法可以推广到三维,用于构造六边界曲面围成的体的初始参数化。
方法一:约束优化方法
这是最直观的思路。我们将质量评价标准直接建模为一个非线性约束优化问题。
问题形式化:
- 优化变量:内部控制顶点
P_ij。 - 目标函数:
E = α * E_uniformity + β * E_orthogonality,其中α和β是权重系数。 - 约束条件:
g_ijk > 0(雅可比行列式控制系数为正)。
使用离散Coons方法的结果作为初始解,然后采用梯度下降、序列二次规划等优化算法进行求解。此方法可以推广到多块参数化并满足C1连续性要求。
方法二:变分调和映射方法
该方法基于调和映射的一个优美性质:如果一个映射是调和的,且其边界映射是微分同胚,那么该映射本身也是微分同胚(一一映射)。我们利用这一性质来保证无自交。
核心思想:
- 我们希望找到从参数域Ω到物理域S的映射
σ: Ω -> S,使其是调和的,即满足拉普拉斯方程Δσ = 0。 - 通过链式法则,将物理域中的拉普拉斯方程
Δ(x,y)=0转换到参数域中,得到一个关于控制顶点P_ij的非线性方程。 - 为了同时优化均匀性和正交性,我们将调和条件与均匀/正交能量项结合,形成一个新的目标函数进行优化。
优势:该方法理论优美,能有效避免自交,尤其在边界凹凸变化剧烈时,相比初始的Coons方法有显著改善。此方法亦可推广至三维体的参数化构造。
方法三:边界重新参数化
有时,给定的边界曲线/曲面本身的参数化质量就很差(例如分布极不均匀),这会严重影响内部参数化的构造。边界重新参数化旨在优化边界的参数分布,而不改变其几何形状。
关键技术:Möbius重参数化。对于NURBS曲线/曲面,对其参数进行Möbius变换 u' = (α u + β) / (γ u + δ),可以生成一条新的NURBS曲线/曲面。关键结论是:新旧曲线/曲面具有完全相同的控制顶点,但权重和节点向量发生了变化。

优化过程:
- 优化变量:Möbius变换中的参数
α, β, γ, δ。 - 目标函数:最小化边界自身等参结构的均匀性度量。
- 结果:得到参数分布更均匀的边界表达,在此基础上进行内部参数化构造,能获得整体质量更高的计算域。


本节课中我们一起学*了等几何分析中计算域参数化的由来、重要性、质量评价标准以及三种核心的构造方法(约束优化法、变分调和映射法、边界重参数化法)。这些方法是实现高质量等几何仿真不可或缺的前处理步骤。

GAMES302-等几何分析 - P5:5. 等几何分析中的计算域参数化 II 🧩
在本节课中,我们将继续学*等几何分析中的计算域参数化方法。上一节我们介绍了参数化的基本概念、质量评判标准以及一些基础方法。本节中,我们将深入探讨面向复杂区域的参数化技术,包括基于骨架的分解方法、面向任意拓扑二维区域的参数化框架,以及三维体参数化的初步思路。我们还将讨论该领域目前面临的一些开放性挑战。
基于骨架的*面区域参数化 🦴
上一节我们介绍了单块区域的参数化构造。然而,对于形状复杂的*面区域,直接进行高质量的单块参数化构造非常困难。本节中我们来看看一种解决方案:基于区域骨架进行分解。
该方法的思路是,首先提取*面区域的骨架(或称中轴),然后根据骨架信息将复杂区域分解为多个简单的子区域,最后对每个子区域分别进行参数化构造。
以下是该方法的基本步骤:
- 骨架提取与简化:计算区域的中轴。中轴是与边界至少有两个切点的内切圆圆心的轨迹。原始中轴可能非常复杂,需要进行简化以获得更清晰的分支结构。
- 区域分解:根据简化后的骨架分支点及对应的边界切点,将原区域分割成多个子区域。每个子区域形状相对简单。
- 子区域参数化:对每个分解得到的子区域,应用上一节介绍的变分调和映射或非线性优化等方法,构造高质量的参数化。
通过这种“分而治之”的策略,可以有效地处理复杂模型,并得到比直接全局优化更高质量、更高效率的参数化结果。
面向任意拓扑二维区域的参数化框架 🔄
基于骨架的方法对于某些更复杂的模型仍存在局限性。因此,我们需要一个更通用的框架来处理任意拓扑的二维区域。本节中我们介绍一个旨在自动生成高质量、分片参数化的系统框架。
该框架的核心目标是:在精确保持输入样条边界的前提下,自动生成内部的分割曲线,并最终得到满足一定几何连续性(如 G¹ 连续)、片元大小均匀的高质量参数化结果。
整个框架遵循一个“全局-局部”的解决思路,主要分为以下四个步骤:
- 预处理:对输入的边界样条曲线进行必要处理。例如,在曲率过大或存在尖点的位置将曲线细分,并将其转换为Bézier曲线段表示,为后续步骤奠定基础。
- 拓扑划分构造:将离散化的边界多边形,通过*似的凸分解算法和模板法,生成内部的四边形网格拓扑结构。这一步确定了区域将被分成多少块以及它们之间的连接关系。
- 分割曲线全局优化:上一步得到的是直边网格,而我们需要的是高阶的样条分割曲线。本步通过求解一个全局优化问题来确定这些分割曲线的形状。优化目标包括:使各面片面积均匀、分割曲线本身光顺、以及在交点处满足几何连续性条件。
- 面积公式:对于一个由 n 段Bézier曲线围成的区域,其面积 A 可以通过其控制顶点显式地计算出来。
- 优化问题:最小化目标函数
E = ω₁ * E_uniformity + ω₂ * E_smoothness + ω₃ * E_continuity。
- 面片局部参数化构造:在确定了所有分割曲线后,每个子区域都是一个由四条Bézier曲线围成的四边面片。这一步对每个面片进行局部优化,构造其内部的参数化。具体包括:
- 构造边界附*的控制点以满足正交性要求。
- 在正则点处施加 C¹ 连续性约束。
- 在奇异点(度数为3或5的顶点)处施加 G¹ 连续性约束,这可以转化为一个小型线性系统求解。
- 通过最小化内在能量(如拉伸能)来求解内部控制点。
- 最后检查并修复可能存在的无效参数化(如雅可比行列式为负的区域)。
该框架能够全自动地处理任意拓扑的二维区域,生成高质量的分片参数化结果,在质量和效率上均优于单块优化方法。
从二维到三维:体参数化的挑战与初步思路 📦
将参数化从二维曲面推广到三维体是等几何分析走向实用化的关键,也面临着更大的挑战。本节中我们来看看体参数化的思路与当前面临的开放性难题。
一种主流思路是借鉴“多立方体”方法。其核心思想是,对于一个三维模型,先生成一个与之大致对应的、由多个立方体块构成的简化结构,然后将原模型参数化映射到这些立方体块上。


以下是基于多立方体方法的体参数化框架:
- 多立方体构造:这是最关键的步骤。目标是为输入模型生成一个高质量的六面体块分解。这通常通过求解一个标量场(如调和场)来引导生成,并可能需要进行复杂的拓扑简化以优化块的数量和结构。
- 块内参数化构造:对于每个六面体拓扑块,将其参数化为一个三变量样条体。这可以看作是对二维面片参数化方法在三维空间的推广。
然而,体参数化目前仍存在诸多开放性挑战:
- 鲁棒的拓扑分解:并非所有复杂模型都能容易地生成高质量的多立方体分解。如何鲁棒、自动地生成三维区域的大块六面体拓扑划分,仍是一个“圣杯”式难题。
- 几何连续性理论:在三维情况下,如何在奇异边、奇异点处定义并施加 G¹、G² 等几何连续性条件,缺乏成熟的理论框架。
- 裁剪边界处理:如何使体参数化精确保持由裁剪曲面定义的复杂模型边界,是一个极其困难的问题。
- 交互与实用性:目前高质量的体参数化往往需要一定的人工交互或引导。开发轻量级交互工具或更智能的自动算法,是推动其工程应用的关键。
尽管挑战巨大,但体参数化是连接等几何分析与复杂工程仿真不可或缺的桥梁,也是当前研究的热点方向。
总结与展望 🎯
本节课中我们一起学*了面向复杂区域的等几何参数化方法。
我们首先介绍了基于骨架的分解方法,它通过提取并简化区域中轴,将复杂区域分解为简单子区域分别参数化。接着,我们深入探讨了一个面向任意拓扑二维区域的自动化参数化框架,该框架通过预处理、拓扑划分、全局优化和局部构造四个步骤,能够生成高质量、满足几何连续性的分片参数化结果。最后,我们将视野扩展到三维,介绍了基于多立方体思想的体参数化初步思路,并讨论了当前在鲁棒拓扑分解、连续性理论等方面面临的重大挑战。

计算域参数化是等几何分析从前处理走向实际应用的基石。从二维到三维,从简单到复杂,该领域的研究不断深入。未来,开发更强大、更自动化的参数化工具,特别是解决三维体参数化的核心难题,将是推动等几何分析在工程领域广泛落地的关键。

GAMES302-等几何分析 - P6:基于等几何分析的泊松问题求解 🧮

在本节课中,我们将学*如何使用等几何分析方法来求解泊松问题。泊松方程是偏微分方程中最简单的椭圆型问题之一,广泛应用于静电学、机械工程和理论物理等领域。通过本次课程,你将掌握等几何分析求解偏微分方程的基本思想、流程和代码实现,为后续求解更复杂的力学问题打下基础。

泊松方程简介 📖
上一节我们介绍了等几何分析的基本知识,本节中我们来看看如何将其应用于具体问题的求解。泊松方程是法国数学家、几何学家和物理学家泊松提出的,其最简单的形式是求一个函数 φ,满足 Δφ = f。当右端项 f 为零时,该方程称为拉普拉斯方程。引入边界条件或力场后,方程变为 Δφ = f。该方程可用于求解电场、磁场及热传导等物理场的分布。
在图形学领域,泊松方程也扮演着重要角色,例如图像融合和点云重建(泊松重建)等。

热传导方程的强形式与弱形式 🔥
实际上,在等几何分析中,我们考虑在一个计算域上求解偏微分方程,例如热传导方程。其强形式定义如下:
在区域 Ω 内部,满足方程:-∇·(k∇u) = f
其中:
- u 是待求的温度场(标量场)。
- k 是热传导率。
- f 是热源函数。
在边界 ∂Ω 上,需要定义边界条件,通常分为三类:
- 狄利克雷边界 (Γ_D):u = g
- 诺伊曼边界 (Γ_N):-k(∂u/∂n) = h
- 罗宾边界 (Γ_R):-k(∂u/∂n) = β(u - u∞)
整个区域的边界是这三类边界的并集。
我们的目标是求解 u。通常无法获得精确解,因此需要数值方法。为了定义弱形式的热传导方程,我们需要引入两组函数:试探函数和测试函数。
试探函数空间 S 中的函数需要满足强形式的狄利克雷边界条件。我们希望求得的物理场 u 属于 S。
测试函数空间 V 中的函数在狄利克雷边界上为零。
将强形式方程两边同时乘以测试函数 v(属于 V),并在区域 Ω 上积分,得到:
∫_Ω (-∇·(k∇u)) v dΩ = ∫_Ω f v dΩ
对左边进行分部积分,并代入三类边界条件,经过推导,可以得到弱形式方程:
a(u, v) = l(v), 对于所有 v ∈ V
其中:
- a(u, v) = ∫_Ω k ∇u · ∇v dΩ + ∫_Γ_R β u v dΓ
- l(v) = ∫_Ω f v dΩ + ∫_Γ_N h v dΓ + ∫_Γ_R β u∞ v dΓ
问题转化为:在空间 S 中寻找一个 u,使其满足上述弱形式方程。
伽辽金方法:从无限维到有限维 ➡️
伽辽金方法的核心思想是将无限维问题*似为有限维问题。在等几何分析中,有限维*似定义在 B 样条空间 N 中。
试探解 u^h 和测试函数 v^h 都可以表示为 B 样条基函数的线性组合:
u^h = Σ_{j=1}^{n} d_j N_j + u_0^h
v^h = Σ_{i=1}^{n} c_i N_i
其中 N_j 是 B 样条基函数,d_j 是待求的系数(未知量),u_0^h 是处理非齐次狄利克雷边界条件的部分。
将上述表达式代入弱形式方程 a(u^h, v^h) = l(v^h),并利用 B 样条基函数的性质,可以得到一个线性方程组:
K d = F
其中:
- K 称为刚度矩阵,其元素为 K_{ij} = a(N_i, N_j)
- F 称为力向量,其元素为 F_i = l(N_i)
- d 是待求的未知系数向量
求解这个线性系统即可得到系数 d,进而得到*似解 u^h。
刚度矩阵的装配与数值积分 🧩

由于 B 样条基函数具有高度局部性,整体刚度矩阵 K 是一个稀疏矩阵。其装配可以基于单元进行。在等几何分析中,“单元”是节点区间所对应的曲线段或曲面片。
整体刚度矩阵和力向量的装配算法如下:
- 读入数据(参数化定义、控制点、边界条件)。
- 初始化全局刚度矩阵 K 和力向量 F 为零。
- 遍历所有单元:
- 计算当前单元的单元刚度矩阵 K_e 和单元力向量 F_e。
- 根据单元的局部自由度编号,将 K_e 和 F_e 添加到全局的 K 和 F 中。
- 处理狄利克雷边界条件,修改 K 和 F。
- 求解线性系统 K d = F。

计算单元刚度矩阵和力向量时,需要计算积分。这涉及两个关键问题:
- 如何计算基函数及其导数:利用 B 样条到 Bézier 的转换以及求值算法。
- 如何计算积分:使用数值积分方法,最常用的是高斯积分法。


高斯积分法的基本思想是将定积分*似为加权和:
∫_a^b f(x) dx ≈ Σ_{i=1}^n w_i f(x_i)
其中 x_i 是高斯点,w_i 是对应的权重。对于高维积分,可以采用张量积形式。


在等几何分析中,积分是在物理域上进行的,但基函数定义在参数域上。因此,需要通过雅可比变换将物理域积分转换到参数域:
∫_{Ω_e} f(x) dΩ = ∫_{[0,1]^d} f(x(ξ)) |J(ξ)| dξ
其中 J 是从参数域到物理域的映射的雅可比矩阵,|J| 是其行列式。
结合高斯积分,单元矩阵的计算流程为:
- 遍历单元上的所有高斯积分点。
- 在每个高斯点上:
- 计算 B 样条基函数及其在参数域上的导数。
- 利用链式法则和雅可比矩阵,计算基函数在物理域上的导数。
- 计算被积函数值,并乘以高斯权重和雅可比行列式。
- 将所有高斯点的贡献累加,得到单元矩阵。
代码实现示例 💻
以下是基于等几何分析求解泊松方程的核心代码流程框架:
# 主函数流程
def main():
# 1. 初始化
initialize()
# 2. 构造连接矩阵
build_connectivity_matrix()
# 3. 装配刚度矩阵
assemble_stiffness_matrix()
# 4. 装配右端项(力向量)
assemble_force_vector()
# 5. 处理边界条件
apply_boundary_conditions()
# 6. 求解线性系统 K d = F
d = solve_linear_system(K, F)
# 7. 输出或后处理结果
output_results(d)
其中,装配刚度矩阵和力向量的函数会遍历所有单元和高斯点,调用计算基函数、导数、雅可比矩阵等子函数,并按照上述流程进行累加装配。
等几何分析中的 r-细化 🔧
为了提高仿真精度,通常需要增加自由度。等几何分析中有几种经典的细化方法:
- h-细化:插入节点,增加控制点数量。
- p-细化:提升样条次数。
- k-细化:结合 h-细化和 p-细化。
这些方法都会改变自由度数量。而 r-细化 的目标是在不增加自由度数量的前提下,通过优化内部控制点的位置来提高精度。其基本思想是:保持边界几何不变,重新定位内部控制点,使得对于特定问题,数值解的误差最小。
一个早期的 r-细化工作将内部控制点作为优化变量,使用梯度下降法等优化算法,最小化后验误差估计量。后验误差估计量定义为:
η = (Σ_{k=1}^{n_e} η_k2)
其中 η_k 是单元 k 上的局部误差估计。
对于泊松问题,局部误差估计与 |f + Δu^h| 相关。因此,优化问题转化为:寻找最优的内部控制点位置,以最小化全局误差估计 η。
计算后验误差需要计算 Δu^h,即*似解 u^h 的拉普拉斯算子。由于 u^h 定义在参数域上,需要利用链式法则将其转换到物理域进行计算。
此外,还可以通过求解一个辅助的泊松方程来更精确地估计误差本身。考虑误差 e = u - u^h,对其两边同时作用拉普拉斯算子,得到:
Δe = f - Δu^h
将右端项视为已知,求解这个关于 e 的泊松方程,即可得到误差场的一个高精度*似。
r-细化的另一个挑战是优化过程中可能产生自交的参数化。为了解决这个问题,可以引入变分调和映射或 Winslow 映射的方法。这类方法通过求解一个椭圆型偏微分方程来生成光滑且无自交的映射,其中可以通过监控函数来控制内部点的分布密度。
常用的监控函数包括:
- 基于曲率的监控函数:在解曲面曲率大的区域布置更密的控制点。
- 基于后验误差的监控函数:在误差估计大的区域布置更密的控制点。
通过将 r-细化与这类保证无自交的映射方法结合,可以在优化控制点位置、提高精度的同时,确保参数化的有效性。
总结 📝
本节课中我们一起学*了基于等几何分析的泊松问题求解。
- 我们首先回顾了泊松方程及其弱形式的推导。
- 接着,介绍了伽辽金方法如何将问题离散化为线性系统 K d = F。
- 然后,详细讲解了刚度矩阵和力向量的装配过程,核心在于单元计算、雅可比变换和高斯数值积分。
- 最后,我们探讨了等几何分析中的 r-细化技术,它通过优化内部控制点位置来提高计算精度,并介绍了避免参数化自交的监控函数方法。

通过本课的学*和配套的开源代码,希望你能够理解等几何分析求解偏微分方程的基本流程,并尝试动手实现简单的泊松求解器。

GAMES302-等几何分析 - P7:基于等几何分析的线弹性问题求解及GIFT方法 🧮
在本节课中,我们将学*如何基于等几何分析框架求解计算力学中的经典问题——线弹性问题。我们还将介绍一种名为GIFT的新型等几何分析方法,它通过解耦几何与物理场的样条空间表示,提供了更灵活的求解框架。
线弹性问题的等几何分析求解框架
上一讲我们介绍了基于等几何分析的泊松问题求解。泊松问题是椭圆型偏微分方程的经典代表,尤其在热传导领域。然而,许多同学可能具有力学或计算力学背景。因此,将等几何分析与力学背景联系起来,学*如何求解线弹性问题,显得尤为重要。
线弹性问题与泊松问题在本质上有很多相似之处。因此,基于等几何分析的求解框架也非常接*。本节将梳理线弹性问题等几何分析的主要流程,其核心思想与上一讲类似,也可视为对上一讲内容的复*。当然,线弹性问题与热传导问题也存在一些区别。
问题描述与基本记号
线弹性问题在计算力学中具有悠久的研究历史和重要地位。等几何分析在求解此类线性问题时具有显著优势。等几何分析利用高阶连续的基函数,能够获得高质量、连续光滑的应力场,同时保持精确的几何描述。
本节主要讲述二维线弹性问题的求解,仅涉及二维计算域。向三维问题的推广是直接且简单的,大家可以在课后思考。
首先,我们引入一些基本记号。在二维问题中,空间维度 d = 2;在三维问题中,d = 3。
u_a表示位移向量u的第a个分量。- 微分用逗号表示,例如
u_{a,b}表示u_a对x_b的偏导。 - 重复下标表示求和,例如
u_{a,bb}表示u_{a,11} + u_{a,22}。 - 对于非对称张量
A,A_{(ab)}表示其对称部分,A_{[ab]}表示其反对称部分。因此有:A_{ab} = A_{(ab)} + A_{[ab]}A_{(ab)} = 1/2 (A_{ab} + A_{ba})A_{[ab]} = 1/2 (A_{ab} - A_{ba})
σ_{ab}表示柯西应力张量的分量。u_a表示位移向量的分量。f_a表示单位体积的体力。ε_{ab}表示工程应变张量。
线弹性问题的本构方程遵循胡克定律,即应力 σ 等于弹性系数矩阵 C 乘以应变 ε:
σ_{ab} = C_{abcd} ε_{cd}
线弹性问题的强形式与弱形式
线弹性混合边界问题的强形式描述如下:寻找一个位移函数 u(从物理域 Ω 映射到 R^d),使其在区域 Ω 内满足*衡方程:
σ_{ab,b} + f_a = 0
在狄利克雷边界 Γ_D 上满足位移边界条件:
u_a = g_a
在诺伊曼边界 Γ_N 上满足应力边界条件:
σ_{ab} n_b = h_a
其中,Γ_D 和 Γ_N 共同构成整个边界 ∂Ω。
为了将其转化为便于数值求解的弱形式,我们引入试函数 u 和权函数 w。通过两边同时乘以权函数 w 并应用分部积分,可以得到线弹性问题的弱形式表达式:
∫_Ω w_{a,b} σ_{ab} dΩ = ∫_Ω w_a f_a dΩ + ∫_{Γ_N} w_a h_a dΓ
其中,σ_{ab} 通过本构关系与 u 的应变相关联。
我们可以定义双线性形式 a(u, w) 和线性形式 l(w):
a(u, w) = ∫_Ω w_{a,b} C_{abcd} u_{c,d} dΩ
l(w) = ∫_Ω w_a f_a dΩ + ∫_{Γ_N} w_a h_a dΓ
于是,弱形式可以简洁地写为:对于所有在 Γ_D 上为零的权函数 w,寻找 u 使得 a(u, w) = l(w) 成立。这实质上就是虚功原理:内力虚功等于外力虚功。
向量记法与材料本构关系
为了简化并降低阶数,我们将张量写成向量形式。应变向量 ε 和虚应变向量定义为:
ε = [ε_11, ε_22, 2ε_12]^T
δε = [δε_11, δε_22, 2δε_12]^T
应力向量为:
σ = [σ_11, σ_22, σ_12]^T
弹性系数矩阵 D 是一个 3×3 矩阵,其元素 D_{ij} 对应于 C_{abcd}。
对于各向同性材料,其材料系数 C_{abcd} 可以用拉梅常数 λ 和 μ 表示:
C_{abcd} = μ (δ_{ac}δ_{bd} + δ_{ad}δ_{bc}) + λ δ_{ab}δ_{cd}
其中,λ 和 μ 与杨氏模量 E 和泊松比 ν 有关:
λ = (E ν) / ((1+ν)(1-2ν))
μ = E / (2(1+ν))
由此,我们可以写出应力和应变之间的矩阵关系 σ = D ε。
对于二维问题,通常有两种经典假设:
- *面应力:适用于薄板,其厚度方向应力为零。此时
D矩阵为:
D = E/(1-ν^2) * [[1, ν, 0], [ν, 1, 0], [0, 0, (1-ν)/2]] - *面应变:适用于厚度方向尺寸远大于其他方向的物体,如大坝。此时厚度方向应变为零。
D矩阵为:
D = E/((1+ν)(1-2ν)) * [[1-ν, ν, 0], [ν, 1-ν, 0], [0, 0, (1-2ν)/2]]
基于伽辽金方法的离散化
为了将弱形式转化为大型线性系统进行求解,我们采用伽辽金方法进行离散化。这与有限元法的思想一致,即在有限维子空间中寻找*似解。
我们使用相同的样条基函数 N_A 来*似表示位移场 u^h 和权函数 w^h:
u^h = Σ_{A=1}^{n} N_A d_A + g^h
w^h = Σ_{A=1}^{n} N_A c_A
其中,d_A 是待求的位移系数(自由度),c_A 是权函数系数,g^h 用于满足非齐次狄利克雷边界条件。
将上述*似代入弱形式 a(u^h, w^h) = l(w^h),并利用双线性形式的线性性质进行整理。由于该等式必须对任意的权函数系数 c_A 都成立,我们可以推导出最终的线性系统:
K d = F
其中:
K是全局刚度矩阵,其元素K_{pq} = a(N_A^a, N_B^b),这里p和q是通过自由度a, b和基函数索引A, B组合得到的全局方程索引。d是待求的位移自由度向量。F是全局载荷向量,其元素F_p = l(N_A^a)。
单元刚度矩阵与载荷向量的计算与装配
接下来,我们需要计算每个单元(即参数域中的节点区间所对应的曲面片)的局部刚度矩阵 K^e 和局部载荷向量 F^e,然后将它们装配到全局系统 K 和 F 中。
局部刚度矩阵的计算公式为:
K_{pq}^e = ∫_{Ω^e} (B_p)^T D B_q dΩ
其中,B_A 矩阵由基函数 N_A 的导数构成,用于计算应变。在实际计算中,我们通过等参变换和高斯积分在参数域中进行数值积分:
K_{pq}^e ≈ Σ_{g=1}^{n_gp} w_g (B_p(ξ_g))^T D B_q(ξ_g) |J(ξ_g)|
这里,ξ_g 和 w_g 是高斯积分点和权重,|J| 是从参数域到物理域的雅可比行列式。
局部载荷向量的计算类似,对于体力项:
F_p^e ≈ Σ_{g=1}^{n_gp} w_g N_A(ξ_g) f(ξ_g) |J(ξ_g)|
对于面力项(诺伊曼边界),则在相应的边界上进行积分。
装配过程通过一个索引数组 IEN 来实现,该数组将局部自由度编号映射到全局自由度编号。伪代码如下:
初始化全局刚度矩阵 K 和载荷向量 F 为零
for 每个单元 e = 1 to n_elem:
计算局部刚度矩阵 Ke 和局部载荷向量 Fe
for 局部自由度 i = 1 to n_loc_dof:
获取全局自由度编号 p = IEN(i, e)
F(p) += Fe(i)
for 局部自由度 j = 1 to n_loc_dof:
获取全局自由度编号 q = IEN(j, e)
K(p, q) += Ke(i, j)
end for
end for
end for
最后,处理狄利克雷边界条件(通常通过修改矩阵 K 和向量 F 实现),并求解线性系统 K d = F 得到位移解 d。
等几何分析与有限元分析的主要区别



回顾整个求解框架,如果熟悉基于伽辽金方法的有限元求解线弹性问题,会发现整体流程非常相似。最主要的区别在于:
- 基函数:等几何分析使用B样条或NURBS基函数,而非有限元的拉格朗日多项式基函数。
- 计算单元:等几何分析的计算单元是参数域中的节点区间(二维为矩形片),映射到物理域后是一个曲面片,而非有限元中简单的三角形或四边形网格单元。
这使得等几何分析能够在保持几何精确性的同时,利用高阶连续的基函数获得更光滑的应力场。
GIFT方法:几何与物理场解耦的等几何分析 🎁
在介绍了经典的等几何分析求解框架后,本节我们来看一种其扩展方法——GIFT。GIFT旨在打破等几何分析中几何与物理场必须使用相同样条空间的限制,从而提供更灵活、高效的框架。
GIFT的基本思想与动机
经典等几何分析的核心思想是使用相同的样条空间来表示几何和物理场。这带来了几何建模与仿真分析的无缝集成,但也存在一些约束和问题:
- 不必要的约束:几何模型可能使用高次(如四次、五次)样条精确描述,但物理场仿真可能只需要二次或三次样条就已足够。强制使用相同的高次样条会增加不必要的计算成本。
- 样条空间限制:等几何分析要求物理场与几何使用相同的样条空间。然而,CAD系统主流是NURBS,而仿真领域可能希望使用具有更好数学性质的样条(如T样条、PHT样条),或定义在非多项式空间上的样条。在不同样条空间之间进行精确转换通常非常困难甚至不可能。
GIFT方法的基本思想是:解耦几何表示与物理场表示的样条空间,但保持它们具有共同的参数域。
- 几何映射
F:定义在样条空间S_1上,将参数域Ω_0映射到物理域Ω。 - 物理场
u:定义在另一个样条空间S_2上,但同样是定义在同一个参数域Ω_0上。 - 最终解:物理场
u通过几何映射F复合到物理域上,得到u ∘ F^{-1},作为物理域上的解。
这意味着,几何可以用高次NURBS表示,而物理场可以用低次B样条、甚至是非均匀节点分布的样条来表示。两者在参数域中具有相同的定义范围,但节点划分、次数可以不同。
GIFT的优势
与经典等几何分析相比,GIFT具有以下优势:
- 灵活性:物理场的表示与几何解耦,可以根据仿真需求独立选择最合适的样条空间(次数、节点分布)。
- 局部细化:在GIFT中,可以对表示物理场的样条空间进行独立的h-或p-细化,而保持几何表示不变。这更符合仿真中自适应细化的需求。
- 计算效率:由于几何映射不变,其雅可比矩阵等量可以预先计算,提高了积分效率。
- 兼容性:可以直接利用现有CAD模型的NURBS几何,同时使用更先进的样条(如T样条)进行仿真,无需进行困难的几何转换。
数值算例与分片测试
研究表明,GIFT方法在许多情况下能获得比经典等几何分析更小的误差。例如,在一个泊松问题中,当几何采用非均匀节点分布而物理场采用均匀节点分布时,GIFT的解误差更小。
一个重要的验证是分片测试。这是计算力学中检验一种数值方法能否再现常应力/常应变状态、从而满足收敛性必要条件的标准测试。通过分片测试意味着方法具有一致性。
研究证实,在大多数情况下,只要参数化不是极度扭曲,GIFT方法都能通过分片测试,达到机器精度。这为其工程应用提供了可靠性基础。
GIFT框架下的局部细化
GIFT的一个强大应用是实现物理场的局部细化,而无需改变几何。例如,几何仍用NURBS表示,但物理场用支持局部细化的PHT样条表示。
具体流程如下:
- 在参数域上求解问题,得到GIFT误差分布。
- 根据参数域上的误差图,识别需要加密的区域。
- 仅在表示物理场的PHT样条空间中对这些区域进行局部h-细化。
- 在新的样条空间上重新求解GIFT问题。
- 重复此过程,直至满足精度要求。
这种方法实现了真正的、独立于几何的物理场自适应细化,效率高且灵活。
总结
本节课我们一起学*了两个主要内容。
首先,我们详细介绍了基于等几何分析框架求解线弹性问题的完整流程。从问题的强形式和弱形式出发,通过伽辽金方法进行离散,推导出最终的线性系统 K d = F,并详细讲解了单元刚度矩阵、载荷向量的计算、装配以及边界条件的处理。这一框架与有限元法思想相通,但以精确的几何描述和高阶连续的样条基函数为核心特征。
其次,我们介绍了GIFT方法。它通过允许几何和物理场使用不同的样条空间(同时共享参数域),打破了经典等几何分析的约束。GIFT提供了更高的灵活性,支持独立的物理场局部细化,并能兼容更多样的样条类型,在保持几何精确性的同时,优化了仿真分析的效率和能力。

理解这两种方法的核心思想与异同,对于深入掌握等几何分析及其在现代工程仿真中的应用至关重要。

GAMES302-等几何分析 - P8:基于等几何分析的超弹性问题求解及等几何配点法 🧮
在本节课中,我们将学*如何利用等几何分析框架求解非线性超弹性问题,并简要介绍等几何配点法。我们将从超弹性问题的基本概念和求解框架入手,然后探讨其在静力学和动力学中的应用,最后了解一种更高效的求解方法——等几何配点法。


概述 📋
上一节我们介绍了基于等几何分析的线弹性问题求解框架及GIFT方法。本节中,我们将探讨等几何分析在超弹性(非线性)问题求解中的应用,并简要介绍等几何配点法。真实世界中的材料行为和受力条件大多是非线性的,因此求解非线性问题至关重要。此外,传统的伽辽金方法需要将问题的强形式转换为弱形式并进行数值积分,而配点法则提供了一种更直接、有时更高效的求解途径。
第一部分:超弹性问题求解框架 🧱
非线性问题概述
在真实的物理世界中,我们遇到的大部分问题都是非线性的。这包括几何非线性(如大位移、旋转)、材料非线性(如超弹性、粘弹性)、稳定性问题(如屈曲)、非线性边界条件(如接触)以及多物理场耦合问题(如流固耦合)。本节课我们主要关注材料非线性中的超弹性问题,这是非线性弹性力学中最基础的一类问题。
基本方程
求解超弹性问题,需要理解几个核心方程:本构方程、几何方程和*衡方程。
本构方程
在线弹性问题中,应力(σ)与应变(ε)呈线性关系:σ = Dε。而在超弹性问题中,这种关系是非线性的,通常可以写成增量或微分形式:dσ = D(ε) dε。这里的弹性矩阵 D 是应变(或其它变形度量)的函数。
为了描述物体的变形,我们引入形变梯度 F 的概念。它表示物体当前坐标 x 相对于初始坐标 X 的变化率:
F = ∂x/∂X = I + ∂u/∂X
其中,u 是位移场,I 是单位矩阵。
超弹性材料模型通常由应变能密度函数 Ψ 定义,它是形变梯度 F 或其相关度量(如右柯西-格林应变张量 C = FᵀF)的函数。几种常见的超弹性模型包括:
- Neo-Hookean 模型
- Mooney-Rivlin 模型
- Ogden 模型
- Fung 模型
这些模型描述了应力(如第一Piola-Kirchhoff应力 P)与形变梯度 F 之间的非线性关系。
几何方程
几何方程描述应变与位移之间的关系。在线弹性小变形理论中,我们使用工程应变 ε,它是位移梯度线性部分。对于有限变形问题,则需要使用格林应变张量 E:
E = 1/2 (FᵀF - I) = 1/2 (C - I)
工程应变 ε 实际上是格林应变 E 的线性部分。在大变形下,E 中的非线性部分不可忽略。
*衡方程
*衡方程源于动量守恒定律。对于物体的运动,需要满足局部微分形式的*衡方程(强形式)。通过变分原理和分部积分,可以将其转换为等效的积分弱形式,这是有限元和等几何分析求解的基础。对于静力学问题,*衡方程简化为内力与外力*衡:R(u) = F。
等几何离散化与求解
在等几何分析框架中,计算域由样条(如NURBS)精确描述。物理域中的一片高次单元对应于参数域中的一个节点区间。
位移场 u 用样条基函数 Nᵢ 和控制顶点系数 uᵢ 表示:u = Σ Nᵢ uᵢ。形变梯度 F 和格林应变 E 都可以基于此位移表达进行计算。
将位移表达式代入*衡方程的弱形式,并进行离散化,可以得到离散系统方程。对于静力学问题,最终形式为:
K(u) u = F
其中,K(u) 是刚度矩阵,但它现在是位移 u 的函数,因此方程是非线性的。这与线弹性问题中 K 为常数矩阵有本质区别。
为了求解这个非线性方程,我们采用牛顿-拉弗森迭代法。该方法需要计算切线刚度矩阵 K_T:
K_T = ∂R/∂u
在一次迭代中,位移增量的计算式为:
K_T ⁽ⁿ⁾ Δu⁽ⁿ⁾ = F - R(u⁽ⁿ⁾)
u⁽ⁿ⁺¹⁾ = u⁽ⁿ⁾ + Δu⁽ⁿ⁾
切线刚度矩阵 K_T 可以分解为两部分:K_T = K_d + K_s。K_d 与小位移刚度矩阵相关,K_s 则与大变形引起的几何刚度相关。迭代过程持续进行,直到残差 F - R(u) 满足收敛容差。
以下是超弹性静力学问题的等几何求解流程:
- 输入:输入样条几何模型,定义材料参数和边界条件。
- 细化:通过插入节点或升阶,增加计算自由度。
- 初始化:设置收敛容差、初始位移猜测(常设为0),初始化全局切线刚度矩阵和残差向量。
- 单元循环:
- 遍历所有计算单元。
- 在每个单元上,计算单元切线刚度矩阵和残差向量。
- 将这些单元量组装到全局矩阵和向量中。
- 求解:求解线性方程组 K_T Δu = Residual,得到位移增量 Δu。
- 更新与判断:更新位移 u = u + Δu。检查残差是否收敛。若未收敛,返回步骤4进行下一次迭代;若收敛,则进入下一步。
- 输出:输出位移、应力等结果,并进行可视化。
实例与比较
通过一个厚壁圆柱模型受向下力作用的例子,展示了超弹性分析的变形和应力结果。研究表明,在相同单元离散密度下,超弹性模型的求解精度通常高于将其简化为线弹性模型的精度,并且随着网格细化,误差收敛更快。
等几何分析在处理大变形问题时具有优势,因为它使用光滑的样条基函数,避免了有限元中因网格扭曲而需要重新划分网格的问题,在保证精度的同时提高了计算效率。
第二部分:超弹性动力学问题 ⏱️
上一节我们介绍了静力学问题,本节中我们来看看动力学问题。物体的运动方程可以表示为:
M ü + C u̇ + R(u) = F(t)
其中,M 是质量矩阵,C 是阻尼矩阵,R(u) 是内力向量,F 是外力向量,ü 和 u̇ 分别是加速度和速度。
对于时间积分,有显式和隐式两种方法。显式方法计算简单,但稳定性受时间步长限制,需要很小的步长。隐式方法(如Newmark-β法)无条件稳定,更适合求解非线性动力学问题。我们采用隐式Newmark-β法。

在Newmark-β法中,下一时间步(n+1)的位移和速度通过当前步(n)的量表示为:
u_{n+1} = u_n + Δt u̇_n + (Δt)² [ (1/2 - β) ü_n + β ü_{n+1} ]
u̇_{n+1} = u̇_n + Δt [ (1 - γ) ü_n + γ ü_{n+1} ]
其中,β 和 γ 是参数。将这些关系代入运动方程,得到一个关于 ü_{n+1} 的非线性方程。同样使用牛顿-拉弗森法迭代求解。得到 ü_{n+1} 后,便可更新 u_{n+1} 和 u̇_{n+1}。

动力学求解框架包含两层循环:外层是时间步推进,内层是每个时间步内的牛顿迭代。等几何分析在此类问题中同样高效,得益于其用较少自由度实现高精度的特性。


第三部分:等几何配点法简介 ⚡
前面介绍的伽辽金方法需要将强形式的PDE转换为弱形式并进行数值积分。本节中,我们介绍一种不同的方法——配点法。等几何配点法直接在PDE的强形式上求解,无需弱形式转换。
其核心思想是:在计算域内精心选择一组配点(Collocation Points),然后要求PDE在这些点上严格成立。对于未知场 u,我们仍用样条基函数展开:u = Σ Nᵢ uᵢ。将展开式代入PDE,并在每个配点 ξ_k 处赋值,就得到一个线性方程组:
L [ Nᵢ (ξ_k) ] uᵢ = f(ξ_k) 对于所有配点k
其中,L 是PDE的微分算子。求解这个方程组即可得到控制顶点系数 uᵢ。
配点法的关键在于配点的选取。常见选择有:
- Greville 坐标点
- Demko 坐标点
- 超收敛点
例如,对于p次样条,Greville坐标点 ξ_i 定义为节点向量中连续p+1个节点的*均值。
配点法的优势在于:
- 高效:避免了数值积分,计算量通常小于伽辽金方法。
- 直接:直接处理高阶导数,而样条基函数的高阶连续性正好满足此要求。
- 与等几何契合:非常适合等几何分析中使用的光滑基函数。
当然,配点法也有挑战,例如得到的系统矩阵可能不对称,施加复杂边界条件相对困难。但对于许多问题,特别是线性PDE,它能显著提升计算速度。配点法也可与牛顿迭代结合,用于求解非线性问题。
配点法的应用不仅限于物理仿真,还可用于图形学任务,例如基于偏微分方程的浮雕建模,通过求解一个PDE来生成从图像到浮雕表面的高度场,取得了良好效果。

总结 🎯
本节课我们一起学*了以下内容:
- 超弹性问题基础:理解了非线性超弹性问题与本构方程、几何方程和*衡方程。
- 静力学求解:掌握了在等几何分析框架下,使用牛顿-拉弗森迭代法求解超弹性静力学问题的完整流程,包括离散化、切线刚度矩阵计算和迭代求解。
- 动力学求解:了解了如何将隐式时间积分(Newmark-β法)与等几何分析结合,求解超弹性动力学问题。
- 等几何配点法:认识了这种无需弱形式转换的直接求解方法,了解了其基本思想、配点选取方式以及相对于伽辽金方法的潜在优势。

等几何分析为处理非线性、大变形问题提供了强大而精确的工具,而配点法则为进一步提升计算效率提供了有前景的途径。

GAMES302-等几何分析 - P9:基于等几何分析的形状优化 🛠️
在本节课中,我们将学*如何利用等几何分析框架进行形状优化。我们将从基本概念入手,介绍线弹性和超弹性问题下的形状优化方法,并通过具体例子展示其应用和优势。


概述 📋
等几何分析不仅是一种用于物理仿真的新框架,其核心优势还在于能够无缝集成设计与优化。形状优化的目标是通过调整物体的几何外形,以改善其物理性能,如刚度、应力分布等。在等几何分析中,优化变量直接对应于描述几何形状的控制顶点,这使得优化过程变得非常直观和高效。
上一节我们介绍了等几何分析的求解框架,本节中我们来看看如何利用这个框架进行形状优化。
结构优化简介 🏗️
结构优化是优化设计领域的一个重要分支,广泛应用于航空航天、汽车轻量化、模具设计等领域。特别是随着3D打印技术的发展,优化结果可以更容易地被制造出来。结构优化主要分为三类:
以下是结构优化的主要类型:
- 拓扑优化:决定材料的最佳分布,即在何处保留材料、何处挖空,属于概念设计阶段。
- 形状优化:在拓扑确定的基础上,优化结构边界的精确形状,属于基础设计阶段。
- 尺寸优化:优化结构的详细尺寸参数,如孔的半径、梁的厚度等,属于详细设计阶段。
传统的有限元方法在进行拓扑或形状优化后,往往需要对结果进行复杂的后处理(如光滑化、CAD重建)才能用于制造。而等几何分析框架有望简化这一流程。
等几何形状优化问题定义 🎯
在等几何框架下,形状优化问题可以直观地表述为:在给定边界条件(固定、受力)和材料参数下,通过调整部分控制顶点的坐标,使得某个物理性能指标(目标函数)达到最优,同时满足一些约束条件(如体积限制)。
因此,一个典型的形状优化数学模型可以表示为:
最小化 f(x)
满足 g_i(x) ≤ 0, i = 1, ..., m
h_j(x) = 0, j = 1, ..., p
其中,x 是设计变量(即控制顶点坐标),f(x) 是目标函数,g_i(x) 和 h_j(x) 是不等式和等式约束。
常见优化目标与数学模型 📊
根据不同的工程需求,可以选择不同的物理量作为优化目标。
以下是几种常见的目标函数形式:
-
最小化柔度(最大化刚度)
柔度是结构在受力*衡下存储的应变能,值越大表示结构越柔、稳定性越差。优化模型通常是在体积约束下最小化柔度。最小化 c = u^T * f 满足 K(x) * u = f V(x) ≤ V0其中,
c是柔度,u是位移向量,f是载荷向量,K是刚度矩阵,V(x)是体积,V0是体积上限。 -
最小化关键点位移
优化目标是使结构在特定点处的位移最小。约束条件通常也是体积。最小化 u_i 满足 K(x) * u = f V(x) ≤ V0其中,
u_i是指定位移自由度上的位移。 -
最小化最大应力(应力集中)
为防止结构断裂,需要降低局部最大应力。但最大应力函数max(σ)不可导,通常使用p-norm方法进行光滑*似。最小化 σ_pnorm = (Σ (σ_i)^p)^(1/p) 满足 K(x) * u = f V(x) ≤ V0当
p足够大时,σ_pnorm*似等于最大应力max(σ),且函数可微。 -
最小化体积/面积
在满足应力、位移等性能约束下,最小化材料的用量。最小化 V(x) 满足 K(x) * u = f σ_max ≤ σ_allow其中,
σ_allow是材料的许用应力。
优化算法:移动渐*线法(MMA) 🔄
确定了优化模型后,需要采用合适的算法进行求解。对于上述可导的优化问题,基于梯度的算法效率较高。其中,移动渐*线法(Method of Moving Asymptotes, MMA)是一种广泛应用于工程优化问题的有效算法。
MMA 的基本思想是在每次迭代中,用一组可调参数的*似函数(移动渐*线)来逼*原问题,从而将复杂的非线性规划问题转化为一系列较简单的凸子问题。通过迭代求解这些子问题,最终收敛到原问题的最优解。

MMA 的优点在于能处理复杂的非线性约束,对于大规模问题收敛速度较快。但其缺点也可能陷入局部最优解,且对目标函数或约束变化剧烈的问题可能效果不佳。

灵敏度分析:优化的“指南针” 🧭
基于梯度的优化算法(如MMA)的核心是计算目标函数和约束函数对设计变量 x 的导数,即灵敏度。灵敏度反映了设计变量微小变动对目标函数的影响程度,决定了优化搜索的方向和步长。
在等几何框架下,设计变量是控制顶点坐标。以柔度 c = u^T * f 为例,其灵敏度计算需要用到链式法则,并涉及刚度矩阵 K 对设计变量的导数。通过伴随变量法等技巧,可以高效地计算出灵敏度,而无需对每个设计变量进行昂贵的有限差分计算。
灵敏度分析是连接仿真分析与优化迭代的桥梁,其计算效率和精度直接影响整个优化过程的成败。


二维与三维线弹性形状优化案例 💡
了解了基本原理后,我们通过案例来具体看等几何形状优化的效果。
以下是几个典型优化案例:
- 悬臂梁优化:梁一端固定,另一端受向下力。优化上表面控制点,在体积约束下最小化柔度。优化后,梁的形状从矩形演变为类似抛物线形,根部加厚以承受更大弯矩,尖端变细以节省材料。
- 开口扳手优化:模拟扳手卡住螺母的工况。固定开口内侧,在扳手两端施加反向力。优化目标是使两种加载工况下的位移最小。优化后,扳手非受力区域的材料被移除,形状更高效。
- 三维涡轮叶片优化:基于多片NURBS曲面构建的叶片模型。固定叶根,在叶身施加气动载荷。优化目标是最小化柔度。优化迭代过程显示,柔度迅速下降并最终收敛,叶片形状得到显著改进。
这些案例表明,在等几何框架下,用户只需设定边界条件、载荷和优化目标,算法便能自动驱动控制顶点,得到性能更优的几何形状,实现了设计与优化的直接联动。
超弹性材料形状优化 🌊
前面的讨论基于线弹性材料。对于橡胶、生物组织等超弹性材料,其力学行为是非线性的(应力-应变关系非线性和大变形)。这给形状优化带来了新的挑战,因为刚度矩阵 K 本身是位移 u 的函数。
在超弹性形状优化中,由于问题的强非线性,位移 u 对设计变量 x 的导数很难解析求出。因此,常采用中心差分法等数值方法来*似计算灵敏度。
∂u/∂x_i ≈ (u(x_i + Δx) - u(x_i - Δx)) / (2Δx)
虽然计算量增加,但依然可以有效地驱动优化过程。
此外,为了*衡优化效率与分析精度,可以采用多分辨率策略:使用较粗的控制网格作为设计变量(优化模型),使用较细的控制网格进行高精度仿真(分析模型)。两者之间的灵敏度信息可以通过解析的映射关系进行传递。这样既能减少优化变量数量、降低计算成本,又能保证仿真结果的准确性。
超弹性优化案例如“带圆角的板”优化显示,优化后应力集中显著降低,应力分布更均匀,证明了该方法对改善非线性结构性能的有效性。
总结 🎓
本节课我们一起学*了基于等几何分析的形状优化。我们了解到:
- 等几何分析为形状优化提供了天然优势,设计变量直接是控制顶点,优化过程直观,无需繁琐的后处理。
- 形状优化问题可归结为有约束的数学规划问题,常见目标包括最小化柔度、位移、应力或体积。
- 移动渐*线法(MMA) 是求解此类问题的有效梯度优化算法。
- 灵敏度分析是驱动优化迭代的关键,需要高效计算目标函数对控制顶点坐标的导数。
- 该方法可成功应用于二维、三维线弹性以及超弹性非线性问题的形状优化,并通过多分辨率策略*衡效率与精度。

通过等几何形状优化,我们能够将一个初始设计,在给定约束下,自动优化成物理性能更优的最终形状,实现了从“仿真验证设计”到“仿真驱动设计”的重要跨越。

GAMES401-泛动引擎(PeriDyno)物理仿真编程与实践 - P1:物理仿真及PeriDyno开源引擎架构简介 🚀
在本节课中,我们将学*物理仿真的基本概念,并了解PeriDyno开源物理仿真引擎的整体架构。课程将从宏观背景入手,逐步深入到引擎的模块化设计、开发流程和核心思想,为后续的实践课程打下基础。
概述:物理仿真与人工智能
我们的世界可以粗略地分为物质世界和精神世界。物理仿真研究的是我们赖以生存的客观物质世界,例如海洋、火焰、汽车等符合物理规律的存在。它旨在用计算机模拟这些物理介质的力学行为。
人工智能则主要研究精神世界中的内容,如运动、感觉、语言理解、视觉等。
在图形学领域,物理仿真为虚拟世界赋予真实的物理属性。例如,在游戏中,它确保角色不会穿墙而过,跳跃符合重力规律。从更宏大的视角看,物理仿真技术是构建高度拟真虚拟世界的基石。
物理仿真引擎的作用
物理仿真引擎能为开发者提供以下支持:
- 提供开发脚手架:为初学者或新算法实践提供基础框架。
- 集成通用功能:封装与仿真核心算法无关的模块,如渲染、UI、数据导入导出等,让研究者专注于算法本身。
- 便于算法对比:引擎内集成多种算法,便于进行研究对比或响应审稿意见。
PeriDyno引擎架构概览
一个典型的仿真引擎(以PeriDyno为例)包含多个层次:
- 硬件层:与CPU、GPU、内存、硬盘打交道的底层API。
- 基础数据结构与算法层:包含数据库、图形接口、碰撞检测等通用算法。
- 核心仿真算法层:集成各种仿真算法,如有限元法(FEM)、光滑粒子流体动力学(SPH)、刚体动力学等。
- 管理层与交互层:包含场景管理、模块管理、UI界面等,用于组合算法并可视化。
开发环境搭建与工作流程
以下是基于Windows*台搭建PeriDyno开发环境的基本流程,分为构建、配置和开发三个阶段。
第一阶段:获取源码(Git)
Git是一个分布式版本控制系统,用于管理代码的修改历史、支持多人协作开发。

以下是Git的核心概念与常用指令:
初始化与克隆
git init:在当前目录初始化一个新的Git仓库。git clone --recursive <仓库地址>:克隆远程仓库到本地。必须添加--recursive参数,以同步拉取所有子模块。
同步与提交
git fetch:从远程仓库获取最新信息。git pull:拉取远程仓库的更新并合并到本地。git push:将本地提交推送到远程仓库。git add:将文件更改添加到暂存区。git commit -m “提交信息”:提交更改到本地仓库。


分支管理
git branch <分支名>:创建新分支。git checkout <分支名>:切换到指定分支。git merge <分支名>:将指定分支合并到当前分支。
建议使用GUI工具(如GitHub Desktop, SmartGit, SourceTree)进行可视化操作,更为直观便捷。
第二阶段:生成工程(CMake)
克隆源码后,需要使用CMake工具生成具体的IDE工程文件(如Visual Studio的.sln文件)。

操作步骤如下:
- 打开CMake GUI。
- 设置源码路径(包含
CMakeLists.txt的根目录)。 - 设置生成路径(建议新建一个
build目录)。 - 点击“Configure”,选择编译器(如Visual Studio 2019)和*台(x64)。
- 解决可能的配置错误(例如,若启用Qt,需手动指定Qt5Config.cmake文件的路径)。
- 点击“Generate”生成工程文件。






第三阶段:开发与调试(IDE)





用Visual Studio打开生成的.sln文件,即可进行编译、运行和调试。











创建一个新项目的基本方法是:在examples/目录下复制一个现有样例的CMake脚本和源码,修改工程名称,并重新运行CMake生成。新项目会出现在解决方案中,编译后运行会显示一个基础的GUI窗口。




PeriDyno核心架构:四层模型




PeriDyno采用场景(Scene) -> 节点(Node) -> 管线(Pipeline) -> 模块(Module)的四层结构来实现高度模块化和解耦。

1. 数据 (Field)

数据层是模块间输入输出的载体。主要分为两类:
- 基本类型:数据量小,可直接在CPU/GPU间传递。使用
DEF_VAR宏定义,例如:DEF_VAR(float, FloatValue, "A float value"); - 数组类型:用于存储大规模数据(如粒子位置)。使用
DEF_ARRAY宏定义,例如:DEF_ARRAY(float, FloatArray, DeviceType::GPU, "An array of floats");
所有定义的数据字段都会自动与UI界面联动,可在运行时动态调整参数。


2. 模块 (Module)





模块是实现具体算法功能的最小单元。它声明所需的输入字段、输出字段和控制参数。例如,一个SPH密度计算模块会输入粒子位置和邻居信息,输出密度值。




3. 节点 (Node)



节点是数据(拓扑结构和场)的容器。一个节点包含:
- 状态字段:描述物理对象的状态(如位置、速度)。
- 管线:组织模块执行顺序的图结构。
- 节点输入/输出:允许整个节点作为数据端口进行传递,简化复杂数据流的连接。


4. 场景 (Scene) 与管线 (Pipeline)








场景是节点的集合。每个节点内部包含两条核心管线:
- 仿真管线:由一系列计算模块组成,按依赖关系执行,用于更新物理状态。
- 渲染管线:由一系列渲染模块组成,用于将数据可视化。





管线采用标记(Tag)机制:当某个模块的数据被更新后,会打上“已更新”标记。后续模块在执行前会检查输入数据的标记,仅当数据发生变化时才重新计算,避免不必要的计算开销。

核心设计思想:解耦
四层架构的核心目的是实现三个关键解耦:

1. 仿真计算与数据解耦
将拓扑结构(如点云、四面体网格)和其上定义的场(如速度场、应力场)与具体的仿真算法分离。同一套数据结构可以支持多种算法(如SPH、MPM),同一种算法也可应用于不同数据结构。




2. 仿真计算与渲染解耦
渲染管线独立于仿真管线。开发者可以灵活地为调试或展示目的,添加不同的渲染模块(如显示触点、法线、矢量场),而无需修改仿真算法本身。

3. 仿真计算与交互解耦
交互行为(如鼠标拾取、拖拽)被模块化。不同的交互需求可以通过替换或重载交互模块来实现,保证了交互事件能正确映射到仿真场景的世界坐标系中。


多线程协作
在GUI主线程、仿真线程、渲染线程并存的环境下,需要处理数据同步和读写冲突。PeriDyno的策略是将互斥锁的粒度细化到模块级别,而非整个场景。这样在某个模块写入数据时,只锁定该模块,其他模块仍可并行执行,最大程度减少线程阻塞。

课程总结与展望






本节课我们一起学*了物理仿真的宏观背景、PeriDyno引擎的架构概览、开发环境搭建流程以及其核心的四层模型与解耦设计思想。





我们了解到,PeriDyno通过场景-节点-管线-模块的层级结构和数据-计算-渲染-交互的解耦设计,旨在提供一个灵活、可扩展的物理仿真研发*台。它既可以帮助初学者快速上手,也能支持研究人员高效开发并对比新算法。

在后续课程中,我们将基于此架构,深入具体的仿真算法实践,例如刚体动力学、SPH流体模拟、*场动力学等,并学*如何开发自定义的渲染与交互模块,最终组合成有趣的应用。

GAMES401-泛动引擎(PeriDyno)物理仿真编程与实践 - P10:工程CAE仿真中的有限元分析原理 📚

在本节课中,我们将要学*工程CAE仿真中有限元分析的基本原理。上节课我们介绍了CAE软件和相关技术知识,是一种科普性和介绍性的课程。本节课我们将深入讲解有限元分析背后的基本力学原理。下节课我们将结合PeriDyno*台和MATLAB Simulink,讲解如何利用该*台进行CAE仿真分析。
今天的课程主要分为三部分。首先,快速回顾有限元分析的作用与地位。其次,讲解有限元分析的基本力学基础,即弹性力学基础。最后,基于弹性力学基础,介绍有限元方法的基础。本节课理论性相对较强,但会尽量使用通俗的语言和观点进行讲解。
第一部分:有限元分析的作用与地位 🔍
上一节我们介绍了CAE技术,本节我们来回顾一下有限元分析的地位。
有限元分析是CAE技术中最常见的数值计算方法。它起源于固体力学,并迅速扩展到流体、传热、电磁等其他领域。本节课主要围绕固体力学展开,但其基本原理在其他领域是类似的。
有限元分析的基本思路是利用数学*似方法对真实物理系统进行模拟。它是一种*似的求解方法,适用于解决工程中非常复杂的通用性问题,但精度并非百分之百。
其核心原理是“单元”概念。通过网格划分,将复杂几何体划分为三角形、四边形、四面体、六面体等简单元素(单元)。计算基于每个单元进行,从而逼*具有无限未知量的真实系统。
有限元模型是真实系统理想化的数学抽象。例如,分析一个梯子时,可以用一系列线段(杆单元)构成的模型来*似。单元之间通过节点连接,载荷施加在节点上。每个节点具有自由度,在三维问题中通常有六个自由度(沿XYZ轴的*动和绕XYZ轴的转动)。自由度描述了物理场响应的特性。
有限元方法的技术路线实现了用有限种类的标准件(单元)去构造任意复杂对象。其求解思路高度标准化和规范化:将任何复杂问题分解为标准单元,在单元上进行建模和计算。这使得整个分析流程非常规范,便于后续用增量式、开放式的*台(如PeriDyno)来实现。
有限元分析的典型流程是从CAD几何模型到数值模型(力学求解),再到后处理显示。它的一个重要优势是易于计算机化,可以形成标准程序,开发出各种前处理、求解、单元计算等模块,非常适合增量式软件架构和模块化编程。
以上是对上节课关于CAE和有限元分析知识的回顾。接下来,我们将从弹性力学的基础讲起,为理解有限元方法打下基础。
第二部分:弹性力学基础 ⚖️
上一节我们回顾了有限元分析的地位,本节中我们来看看其力学基础——弹性力学。
首先,我们来看一些基础知识点。考虑到不同专业背景的同学,我们从最基础的概念开始。
基本概念与胡克定律
弹性力学最基本的一个定义是胡克定律。它描述了一个弹性体,其变形与外力成正比。例如一个弹簧,受拉力时会伸长。在弹性阶段,外力 F、弹簧刚度 k 与伸长量 Δx 的关系为:
F = k * Δx
此时,力与变形量之间是线性关系。当外力撤销后,变形消失,弹簧恢复原状,这称为弹性变形。
如果力加载过大,超过一定限度,弹簧可能被拉直且无法恢复原状,这就进入了塑性变形阶段。在塑性阶段,力与位移不再是线性关系,且外力撤销后变形不可恢复。为简化学*,我们从弹性变形开始。
力的概念
力是物体间的相互机械作用,会使物体的形状或运动状态发生改变。力有三要素:大小、方向和作用点。力是矢量,单位是牛顿(N)。根据研究对象不同,力学分为理论力学、材料力学、结构力学、弹性力学等。
主要研究对象
工程结构主要分为三类:
- 杆件结构:长度远大于横截面尺寸,如梁、柱。一般采用材料力学研究。
- 薄壁结构:在两个方向上的尺寸远大于第三个方向(厚度),如*板、曲面壳。采用弹性力学研究。
- 实体结构:三个方向上的尺寸量级相*。采用弹性力学研究。
变形的基本形式与性能指标
杆件变形的基本形式包括:轴向拉伸/压缩、*面弯曲、剪切、扭转等。实际结构往往是多种受力的组合。
在工程分析中,我们主要关注以下性能指标:
- 强度:构件抵抗破坏的能力。
- 刚度:构件抵抗变形的能力。
- 稳定性:构件保持原有*衡状态的能力。
在有限元分析中,最常见的是刚度分析和强度分析。
弹性力学的研究对象与任务
弹性力学研究弹性体在外界因素(外力、温度变化、边界约束等)作用下,内部所产生的应力、应变和位移。
弹性体是一种理想物体,其应力与应变存在一一对应的关系,且外力去除后能完全恢复原状。
弹性力学的研究对象包括薄壁结构(板、壳)和实体结构(地基、坝体、机械零件等)。相对于材料力学对杆件的简化研究,弹性力学能进行更精密的二维或三维分析。
弹性力学的研究任务是:分析结构在弹性阶段的应力和位移,校核其是否具有所需的强度、刚度和稳定性,并寻求或改进计算方法,以最优方案解决安全与经济的矛盾。这体现了工程CAE仿真与物理仿真的区别:CAE不仅计算物理量,还要将其转化为工程性能评价,并用于指导设计优化。
弹性力学的基本假设
为了使问题可解,弹性力学引入了五个基本假设,将问题转化为线性问题:
- 连续性假设:物体是连续的,应力、应变、位移等物理量是坐标的连续函数。这使得我们可以使用微积分等数学工具。
- 完全弹性假设:物体变形外力去除后能完全恢复原状,且任一时刻的形状仅取决于该时刻的受力,与历史无关。这使得本构方程(应力-应变关系)是线性的。
- 均匀性假设:物体由同一种材料构成,因此弹性常数(如弹性模量、泊松比)不随位置坐标改变。这使得我们可以分析物体的一小部分,然后将结果应用于整体。
- 各向同性假设:物体内每一点在各个方向上具有相同的物理性质。因此弹性常数不随方向改变。
- 小变形假设:物体受力后,所有点的位移远小于物体原有尺寸。这样可以用变形前的尺寸代替变形后的尺寸进行计算,且应变和转角的高阶项可以忽略,从而将微分方程线性化。
在这五个假设下,弹性力学问题成为了线性问题,可以应用叠加原理:几组载荷共同作用产生的总效应,等于每组载荷单独作用效应之和。
外力、内力与应力
- 外力:其他物体对研究对象的作用力。
- 体力:分布在物体体积内的力,如重力、惯性力。单位是
N/m³。合力计算需乘以体积。 - 面力:分布在物体表面的力,如流体压力。单位是
N/m²。合力计算需乘以面积。
- 体力:分布在物体体积内的力,如重力、惯性力。单位是
- 内力:物体内部不同部分之间的相互作用力。
- 应力:截面上单位面积的内力。垂直于截面的应力称为正应力 (
σ),*行于截面的应力称为剪应力 (τ)。
应力有大小、方向、作用点,还有作用面。过同一点不同截面上的应力不同。为了分析一点 P 的应力状态,通常取一个包含 P 点的微小正六面体(微元体),用其六个表面的应力分量来表示。
对于微元体,每个面上的应力可以分解为一个正应力和两个剪应力。在三维坐标系中,一点的应力状态完全由六个独立分量确定:三个正应力 σ_x, σ_y, σ_z 和三个剪应力 τ_xy, τ_yz, τ_zx。这六个分量通常是坐标的函数。常用应力列阵表示:
σ = [σ_x, σ_y, σ_z, τ_xy, τ_yz, τ_zx]^T
位移与应变
物体变形后,其形状改变可以用两种方式描述:
- 位移:任意一点位置的变化。在三维中,点沿
X, Y, Z轴的位移分量记为u, v, w。位移包括与形状改变相关的部分和刚体位移(与形状无关)。 - 应变:描述微线段长度和角度的变化。
- 正应变 (
ε):单位长度的伸长或缩短。ε_x = ΔL_x / L_x - 剪应变 (
γ):两垂直线段夹角的变化量(弧度)。
- 正应变 (
一点的应变状态也由六个独立分量完全确定:三个正应变 ε_x, ε_y, ε_z 和三个剪应变 γ_xy, γ_yz, γ_zx。
弹性力学的两类问题
根据研究对象特点,弹性力学问题可分为:
- 空间问题:分析三维实体结构。
- *面问题:某些薄壁或长柱体结构可简化为二维问题,包括:
- *面应力问题:适用于等厚薄板,受力*行于板面且沿厚度不变。特点:
σ_z = τ_zx = τ_zy = 0,主要研究σ_x, σ_y, τ_xy。 - *面应变问题:适用于很长且截面不变的柱体,受力*行于横截面且沿长度不变。特点:
ε_z = γ_zx = γ_zy = 0,位移只有u, v。
- *面应力问题:适用于等厚薄板,受力*行于板面且沿厚度不变。特点:
弹性力学的三大类方程
求解弹性力学问题,需要建立三大类方程:
-
*衡微分方程
表示物体内任一点微元体的*衡条件(合力、合力矩为零)。对于*面问题,可推导出两个*衡微分方程(以*面应力问题为例):
∂σ_x/∂x + ∂τ_yx/∂y + F_x = 0
∂σ_y/∂y + ∂τ_xy/∂x + F_y = 0
以及剪应力互等关系:τ_xy = τ_yx。 -
几何方程
表示应变分量与位移分量之间的关系。对于*面问题:
ε_x = ∂u/∂x
ε_y = ∂v/∂y
γ_xy = ∂u/∂y + ∂v/∂x
注意:由位移可完全确定应变,但由应变不能唯一确定位移(存在未定的刚体位移)。 -
物理方程(本构方程)
表示应力分量与应变分量之间的关系,描述了材料的力学性质。对于线弹性、各向同性材料,其广义胡克定律为:
ε_x = 1/E [σ_x - μ(σ_y + σ_z)]
ε_y = 1/E [σ_y - μ(σ_x + σ_z)]
ε_z = 1/E [σ_z - μ(σ_x + σ_y)]
γ_xy = τ_xy / G,γ_yz = τ_yz / G,γ_zx = τ_zx / G
其中,E为弹性模量,μ为泊松比,G = E / [2(1+μ)]为剪切模量。物理方程可以用矩阵形式简洁表示:
σ = D * ε
或
ε = C * σ
其中,D称为弹性矩阵,C称为柔度矩阵,C = D^{-1}。D矩阵完全由材料常数E和μ决定,与坐标无关,这为编程计算带来了极大便利。对于*面应力和*面应变问题,D矩阵的形式不同,但方程结构一致,体现了模块化编程的优势。
边界条件
求解具体问题还需给定边界条件,分为两类:
- 应力边界条件 (
S_σ):在边界上给定面力。 - 位移边界条件 (
S_u):在边界上给定位移。
边界条件需要融入到整体求解过程中。在有限元分析中,有专门的模块处理这两类边界条件。
本节课我们一起学*了工程CAE仿真中有限元分析的基本原理。我们首先回顾了有限元分析的作用与标准化思路,然后深入探讨了其力学基础——弹性力学,包括基本概念、假设、研究对象、以及核心的*衡方程、几何方程和物理方程。这些内容是理解有限元方法如何将连续体离散化并进行数值求解的关键。下节课,我们将把第三部分“有限元分析基础”与基于MATLAB Simulink的软件编程实践结合起来讲解。


希望本节课对大家有所帮助,我们下节课再见。

GAMES401-泛动引擎(PeriDyno)物理仿真编程与实践 - P11:从PeriDyno到CAE软件增量集成开发*台MxSimLab 🚀
在本节课中,我们将学*如何从一个物理仿真引擎(PeriDyno)出发,构建一个面向CAE(计算机辅助工程)软件的增量集成开发*台——MxSimLab。我们将首先回顾有限元分析的基本方法,然后介绍从PeriDyno到MxSimLab的开发工作,最后通过软件演示展示其核心功能与应用。
一、有限元分析基础方法 🔧
上一节我们介绍了弹性力学的基本理论。本节中,我们来看看如何将这些理论转化为有限元分析的具体方法。为了便于理解,我们以最简单的弹簧单元为例。
1. 弹簧系统的力学分析原理
屏幕上展示了一个弹簧系统。其左端固定,右端受到一个力 F,从而产生一个伸长量 Δ。在弹性变形阶段,力与伸长量之间呈线性关系,即胡克定律:
F = k * Δ
其中,k 是弹簧的刚度系数。这个方程描述了弹簧变形的物理关系。
在有限元分析中,我们使用节点和单元来描述系统。我们将弹簧的两个端点定义为节点1和节点2。每个节点有其位移 U 和节点力 F。
- U₁: 节点1的位移
- U₂: 节点2的位移
- F₁: 节点1的节点力
- F₂: 节点2的节点力
根据胡克定律和系统*衡条件,我们可以建立方程组:
- F₂ = k * (U₂ - U₁)
- F₁ + F₂ = 0 (系统*衡)
由此可以推导出:
- F₂ = k * (U₂ - U₁)
- F₁ = -k * (U₂ - U₁) = k * (U₁ - U₂)
2. 矩阵形式的*衡方程
在有限元分析中,通常将方程组写成矩阵形式,便于计算机编程实现。上述方程组可以表示为:
[ [k, -k], [-k, k] ] * [ [U₁], [U₂] ] = [ [F₁], [F₂] ]
简写为:
K * U = F
其中:
- K 称为刚度矩阵,由材料物理特性决定。
- U 是节点位移列阵。
- F 是节点力列阵。
这个方程称为弹簧的*衡方程或刚度方程。无论多么复杂的有限元问题,最终都归结为求解 K * U = F 形式的方程组。
3. 多弹簧系统的组装
对于更复杂的系统,例如两个串联的弹簧,我们首先对每个单元独立分析,然后进行组装。
以下是构建整体*衡方程的步骤:
- 离散化与编号:系统有三个节点(A, B, C)和两个弹簧单元。节点编号为1, 2, 3,单元编号为1, 2。
- 单元分析:对每个弹簧单元,分别建立其2x2的单元刚度方程。
- 单元1(连接节点1, 2):K₁ * [U₁, U₂]ᵀ = [F₁₁, F₂₁]ᵀ
- 单元2(连接节点2, 3):K₂ * [U₂, U₃]ᵀ = [F₂₂, F₃₂]ᵀ
- 矩阵扩充:将每个单元的2x2刚度矩阵,根据整体节点顺序(1,2,3)扩充为3x3矩阵,新增位置填充0。
- 整体组装:将所有扩充后的单元刚度矩阵相加,得到整体刚度矩阵 K_global。同样,将节点力列阵也组装成整体列阵 F_global。最终得到整体*衡方程:K_global * U_global = F_global。
4. 引入边界条件与求解
建立整体方程后,需要引入边界条件才能求解。边界条件包括位移边界条件和力边界条件。
以两端固定的弹簧系统为例:
- 位移边界:节点1和节点3固定,即 U₁ = 0, U₃ = 0。
- 力边界:节点2受到外力 F₂,是已知量。
引入边界条件的方法之一是“划行划列法”:
- 将位移已知为0的节点(如U₁, U₃)所对应的行和列从整体矩阵方程中删除。
- 得到缩减后的方程组,仅包含未知位移(如U₂)和已知力(F₂)。
- 求解这个缩减后的方程组,得到未知位移 U₂ = F₂ / (k₁ + k₂)。
- 将求得的位移代回原方程,可以解出所有的节点力(包括支反力)。
5. 有限元程序的模块化实现
有限元程序可以高度模块化。以下是一个弹簧系统分析程序的核心函数示例:
-
计算单元刚度矩阵:
def element_stiffness(k): return np.array([[k, -k], [-k, k]])输入弹簧系数k,返回2x2单元刚度矩阵。
-
组装整体刚度矩阵:
def assemble_global_stiffness(K_global, k_element, node_i, node_j): # 将单元刚度矩阵 k_element 组装到整体矩阵 K_global 的对应位置 (node_i, node_j) K_global[node_i-1, node_i-1] += k_element[0,0] K_global[node_i-1, node_j-1] += k_element[0,1] K_global[node_j-1, node_i-1] += k_element[1,0] K_global[node_j-1, node_j-1] += k_element[1,1] return K_global输入整体矩阵、单元矩阵、节点编号,实现矩阵组装。
-
计算节点力:
def compute_nodal_force(K, U): return np.dot(K, U)根据刚度矩阵K和位移U计算节点力F。
通过调用这些模块化函数,可以构建完整的有限元分析流程:定义模型(节点、单元)、计算单元矩阵、组装整体矩阵、施加边界条件、求解方程组、计算内力。
二、从PeriDyno到MxSimLab:增量集成开发*台 🛠️
理解了有限元的基本流程后,我们回到本课程的核心:物理仿真引擎与编程。本节我们来看看如何基于PeriDyno,构建一个用于CAE软件开发的增量集成*台。
1. 开发动机与目标

当前CAE软件发展趋向于智能化、集成化、云端化与协同化。然而,国内自主CAE工业软件较少,从理论研究到产品级软件需要经历核心代码开发和软件集成封装。
MxSimLab的目标是构建一个满足增量集成需求的CAE软件框架,旨在解决行业内的低价值重复劳动,促进高质量软件开发。它希望满足不同场景下的多层次需求:
- 学*与科研:快速搭建面向特定行业的CAE软件原型,集成前处理、求解、后处理全流程。
- 软件开发:
- 为已有的求解器快速提供前后处理功能,形成完整产品。
- 为新的算法(如本构模型、单元公式)提供快速实现和测试*台。
- 软件应用:
- 集成多种求解器(开源、自研、商业),方便用户在同一*台下对比不同软件的计算结果。
- 探索在特定场景下替代商业软件的可能性。
2. MxSimLab的实现
MxSimLab围绕PeriDyno开源框架构建,继承了其拖拽式、增量集成的核心概念。

以下是开发过程中进行的主要工作:
- 扩充仿真数据域:增加了CAE仿真所需的专有数据,如单元、节点、材料属性等。
- 开发核心算法模块:基于PeriDyno的框架,开发了大量用于CAE仿真的
Module和Node,实现高可复用性。 - 实现跨*台与自适应:确保软件在不同硬件和操作系统上的适用性。
- 集成全流程软构件:完成了从CAE前处理、求解器到后处理的各种功能模块的开发与集成。


最终,MxSimLab从一个增量开发框架,演进为一个适用于CAE软件开发的开源底座。
3. MxSimLab的框架优势
MxSimLab完全继承了PeriDyno的四层架构思想,并在Model层集成了丰富的仿真算法库:
- 仿真与渲染分离:采用独立的计算管线和渲染管线。
- 仿真与交互分离:交互操作不影响核心仿真逻辑。
- 仿真与底层分离:模块实现不依赖特定底层库,保证高可复用性。
这些分离特性使得各个仿真功能模块能够像“乐高积木”一样,在图形化场景中通过连接节点的方式,快速搭建出复杂的CAE仿真流程。

三、MxSimLab软件介绍与演示 🖥️
本节我们将进入MxSimLab软件,了解其界面并演示如何利用它进行CAE仿真分析。
1. 软件获取与资源
- 开源地址:代码托管于Gitee,可通过提供的网址访问并下载。
- 交流群:提供微信群二维码,方便用户交流编译、使用问题,研发人员会在群内提供支持。
软件界面风格针对CAE工程师*惯进行了调整,但整体布局与PeriDyno类似。左侧是节点树,包含了所有已开发的功能节点,涵盖前处理、单元计算、材料、求解器、后处理、渲染等。
2. 完整有限元分析流程演示
我们将演示一个完整的线性静力学分析流程:
第一步:前处理 - 几何导入与网格划分
- 使用
Mesh Setup节点指定CAD模型文件路径(如STEP格式)。 - 使用
Mesh Engine节点调用集成的开源网格划分引擎(如GMSH),设置网格类型(如四面体)和尺寸,生成有限元网格模型。
第二步:前处理 - 定义分析属性
- 材料定义:使用
Material节点,选择线弹性各向同性材料,设置杨氏模量和泊松比。 - 截面属性:使用
Section节点,创建实体截面,并将定义好的材料赋予该截面。 - 单元类型:使用
Element节点,为网格选择对应的单元类型(如C3D4,即四节点四面体单元)。 - 工况与边界:
- 使用
Load节点,在模型表面施加集中力或压力。 - 使用
BC(Boundary Condition)节点,约束模型某些表面的所有自由度(模拟固定支撑)。
- 使用
第三步:求解文件生成与计算
- 生成输入文件:使用
Solver Input File节点,接收前处理所有设置数据,生成特定求解器(如自研隐式求解器)能识别的计算文件。 - 调用求解器:将生成的计算文件传递给
Solver节点(如Implicit Solver),开始求解计算。
第四步:后处理 - 结果可视化
- 结果文件解析:求解器通常会输出VTK格式的结果文件。使用
VTK File Reader节点读取这些文件。 - 云图渲染:将解析后的数据传递给
Color Mapping等渲染节点,在软件主窗口查看应力、应变或位移的云图结果。
整个流程通过拖拽节点并连接其端口来搭建,形成一个清晰的、可视化的仿真工作流。
3. 集成开源求解器演示
MxSimLab的优势之一是能方便地集成第三方求解器。以集成显式动力学开源求解器OpenRadioss为例:
- 定制节点:开发一个
OpenRadioss Solver节点。 - 文件输入:使用
File Input节点,提供OpenRadioss所需的输入文件(如气囊展开模型)。 - 结果转换与可视化:OpenRadioss的输出格式非VTK,因此开发一个转换节点,将其结果转为VTK格式,随后即可用标准的
VTK Reader和Color Mapping节点进行可视化。
通过这种方式,用户可以在MxSimLab的统一界面下,方便地调用不同的开源或自研求解器,并利用*台提供的前后处理工具,快速完成仿真分析。
4. *台的扩展性
MxSimLab支持多种扩展方式:
- 基于代码的开发:克隆模板工程,在C++层面开发新的功能模块。
- 插件系统:开发独立的插件动态库。
- Python二次开发(规划中):未来计划支持在软件界面内直接编写Python脚本节点,用于快速实现算法原型或特定计算。
总结 📚
本节课中,我们一起学*了以下内容:
- 有限元分析基础:以弹簧系统为例,讲解了从物理方程到有限元离散化、单元分析、整体组装、引入边界条件并求解的完整流程,揭示了K * U = F 这一核心方程的意义。
- MxSimLab的开发理念:了解了基于PeriDyno构建CAE增量集成开发*台的动机,即解决重复劳动、满足学*、研发、应用的多层次需求,实现CAE软件的高效开发与集成。
- MxSimLab软件应用:通过演示,我们看到了如何利用该*台以图形化拖拽的方式,快速搭建包含前处理、求解、后处理的完整CAE分析流程,以及如何灵活集成第三方求解器。

本课程《GAMES401-泛动引擎物理仿真编程与实践》从PeriDyno的架构设计、基础物理仿真算法,讲到CAE算法与MxSimLab*台,旨在为大家打开从物理仿真到CAE仿真的大门,希望起到启蒙与启发的作用。

谢谢大家!

GAMES401-泛动引擎(PeriDyno)物理仿真编程与实践 - P2:GPU硬件架构简介及CUDA编程基础 🚀
在本节课中,我们将学*GPU硬件架构的基本知识,并了解如何使用CUDA进行并行编程。课程内容涵盖GPU架构的发展历程、CUDA编程模型、以及两个在物理仿真中至关重要的并行算法:归约(Reduce)和前缀和(Scan)。我们还将探讨如何利用C++模板技术使算法更加通用,并介绍这些算法的典型应用场景。
GPU硬件架构发展历程 📈
上一节我们介绍了课程概述,本节中我们来看看GPU硬件架构是如何演变的。
GPU的发展大致可分为两个阶段:2006年之前的传统图形处理器时代和2006年之后的通用计算GPU时代。

- 2006年以前:当时的GPU主要是为图形渲染设计的专用处理器。其功能管线(如顶点着色器、片元着色器)大多是固定的,虽然后期具备了一定的可编程能力,但本质上仍服务于图形处理,难以高效执行通用计算任务。
- 2006年:这是一个里程碑。英伟达发布了Tesla架构,统一了着色器模型,使GPU能够执行图形渲染以外的通用计算任务。更重要的是,它开始支持使用C语言进行编程,大大降低了开发门槛。
- 2010年及以后:随着Fermi架构的发布,GPU的通用计算架构日趋成熟。后续架构(如Kepler, Pascal, Volta, Ampere)主要在计算核心数量、能效比、以及对特定任务(如深度学*)的硬件支持上进行优化和增强。例如,最新的Ampere架构将传统的CUDA核心拆分为独立的整数(INT)和浮点(FP)计算单元,并引入了专门用于张量计算的Tensor Core。
CPU与GPU架构对比 ⚙️

了解了GPU的发展后,我们将其与熟悉的CPU架构进行对比,以理解它们的设计哲学差异。
CPU的设计目标是处理复杂的、串行逻辑任务。其架构特点是控制单元(Control)和计算核心(Core)的比例较高,拥有强大的分支预测和复杂指令调度能力,以及大容量的多级缓存(L1, L2, L3)来减少数据访问延迟。

GPU的设计目标是处理大规模的、计算密集型的并行任务。其架构特点是计算核心数量极多,但控制逻辑相对简单。一个GPU由多个流式多处理器(Streaming Multiprocessor, SM)组成,每个SM又包含大量的CUDA核心(Streaming Processor, SP)。一个控制单元需要管理多个计算核心。

这种差异导致了它们各自擅长的领域:
- CPU:擅长处理逻辑复杂、分支众多、串行性强的任务。
- GPU:擅长处理数据并行度高、计算密集、分支简单的任务。
流式多处理器(SM)详解 🧠
上一节我们对比了CPU和GPU,本节中我们深入看看GPU的核心计算单元——流式多处理器。
SM是GPU执行计算任务的基本单位。虽然不同架构的SM内部结构略有差异,但核心组件基本一致:
- CUDA核心(CUDA Cores):执行基本的整数和浮点运算。
- 特殊功能单元(SFU):执行超越函数(如sin, cos)等特殊运算。
- 寄存器文件(Register File):为每个线程提供快速的私有存储空间。
- 共享内存/一级缓存(Shared Memory / L1 Cache):一个SM内所有线程共享的低延迟、可编程的片上内存。
- 只读缓存:常量缓存(Constant Cache)和纹理缓存(Texture Cache),用于加速对只读数据的访问。
- 调度器(Warp Scheduler):负责将指令分发给CUDA核心执行。
以Fermi架构的SM为例,它包含32个CUDA核心、2个Warp调度器、64K可配置的共享内存/L1缓存等。而Ampere架构则进一步将CUDA核心拆分为FP32和INT32单元,并加入了Tensor Core。
CUDA编程模型 🧵
理解了硬件,我们来看看如何在软件层面组织计算,这就是CUDA编程模型。
CUDA采用分层线程模型,将计算任务组织成网格(Grid)、线程块(Block)和线程(Thread)三个层次,与GPU的硬件层次相对应:
- 网格(Grid):对应整个GPU设备,包含多个线程块。
- 线程块(Block):对应一个流式多处理器(SM)。一个Block内的线程可以协作,并通过共享内存通信。Block被调度到某个SM上执行,且一旦开始执行,通常不会迁移。
- 线程(Thread):对应一个CUDA核心,是执行计算的最小单位。
在编写CUDA核函数(Kernel)时,我们需要指定Grid和Block的维度。例如,一个简单的向量加法核函数可能如下所示:
__global__ void vectorAdd(float* A, float* B, float* C, int n) {
int i = blockDim.x * blockIdx.x + threadIdx.x;
if (i < n) {
C[i] = A[i] + B[i];
}
}
// 调用方式:vectorAdd<<<numBlocks, threadsPerBlock>>>(A, B, C, n);
这里,threadIdx.x, blockIdx.x, blockDim.x 是CUDA内置变量,用于计算每个线程的全局索引。
Warp与执行效率 ⚡
在CUDA模型中,线程的执行并非完全独立,而是以Warp(线程束) 为单位进行调度。这是理解GPU性能优化的关键。
- Warp概念:一个Warp是SM的基本执行单元,通常包含32个线程。SM以Warp为单位获取、调度和执行指令。
- Warp Divergence(线程束分化):如果一个Warp内的线程在执行时遇到分支(如if-else),并且部分线程走if路径,另一部分走else路径,那么SM必须串行化地执行这两条路径。这会导致部分CUDA核心闲置,严重降低性能。
- 优化策略:尽量让同一个Warp内的线程执行相同的代码路径。可以通过重构算法或数据布局来减少分支分化。
- 延迟隐藏(Latency Hiding):访问全局内存(Global Memory)有很高的延迟(数百个时钟周期)。如果线程只是等待数据,SM就会闲置。
- 优化策略:SM通过快速切换执行不同的、就绪的Warp来隐藏这种延迟。因此,提高SM的占用率(Occupancy),即让SM上有足够多的、可切换的Warp,是隐藏延迟的关键。这通常需要合理设置Block的大小和数量。
GPU存储层次 🗃️
为了优化性能,必须理解GPU的多级存储层次。不同存储器的速度、容量和用法各不相同。
以下是GPU的主要存储类型,按速度从高到低排列:
- 寄存器(Register):每个线程私有,速度最快,容量很小。用于存储局部变量。
- 共享内存(Shared Memory):每个Block内共享,速度快,容量较小(通常几十KB)。是程序员可管理的高速缓存,用于线程间通信和数据复用。
- L1/L2缓存(Cache):硬件自动管理,对程序员透明。用于缓存全局内存和本地内存的访问。
- 常量内存(Constant Memory):只读,全局可见,有专用缓存。适合存储需要被所有线程频繁读取的常量。
- 纹理内存(Texture Memory):只读,为图形纹理访问优化,也有缓存。
- 全局内存(Global Memory):GPU的显存,容量大(数GB),但延迟高,带宽也高。所有线程均可读写,是CPU与GPU数据交换的主要区域。
- 本地内存(Local Memory):当寄存器不够用时,编译器会将局部变量溢出到本地内存,它实际位于全局内存中,速度很慢。
编程建议:频繁访问的数据应尽量放在寄存器或共享内存中;对全局内存的访问应尽量合并(Coalesced),即让一个Warp内的线程访问连续的内存地址,以最大化内存带宽利用率。
并行算法实例:归约(Reduce)🔢
掌握了基础概念后,我们来看一个核心的并行算法:归约(Reduce)。归约操作将一个数组的所有元素通过某种二元操作符(如加法、求最大值)合并成单个值。
在CPU上,归约是串行的,复杂度为O(N)。在GPU上,我们可以利用树形结构进行并行归约。
基本思想(以求和为例):
- 将数据从全局内存加载到共享内存。
- 在共享内存中,进行迭代计算:第一轮,相邻元素两两相加;第二轮,将第一轮的结果再次两两相加;如此反复,直到得到最终结果。
- 将结果写回全局内存。
一个简单的归约核函数框架如下:
__global__ void reduceSum(float* input, float* output, int n) {
extern __shared__ float sdata[]; // 动态声明共享内存
unsigned int tid = threadIdx.x;
unsigned int i = blockIdx.x * blockDim.x + threadIdx.x;
// 1. 将全局内存数据加载到共享内存
sdata[tid] = (i < n) ? input[i] : 0;
__syncthreads(); // 确保Block内所有线程完成加载
// 2. 在共享内存中进行树形归约
for (unsigned int s = blockDim.x / 2; s > 0; s >>= 1) {
if (tid < s) {
sdata[tid] += sdata[tid + s];
}
__syncthreads(); // 每轮计算后都需要同步
}
// 3. 将结果写回全局内存(每个Block一个结果)
if (tid == 0) {
output[blockIdx.x] = sdata[0];
}
}
性能优化点:
- 使用共享内存:大幅减少对高延迟全局内存的访问。
- 避免Bank Conflict:共享内存被组织成多个Bank。如果一个Warp内的多个线程同时访问同一个Bank的不同地址,就会发生冲突,导致串行访问。通过调整数据访问模式(例如,使用交错索引)可以避免冲突。
- 处理大规模数据:上述核函数每个Block产生一个部分和。如果数据量巨大,需要多个Block,则可以在CPU或另一个GPU核函数中对这些部分和进行第二次归约。
并行算法实例:前缀和(Scan)📊
另一个极其重要的并行算法是前缀和(Scan),也称为并行前缀扫描。它计算数组的所有部分和。
给定输入数组 [a0, a1, a2, ...],其前缀和输出数组为 [a0, a0+a1, a0+a1+a2, ...]。它有两种形式:包含式(Inclusive)和排除式(Exclusive)。
这里介绍经典的Blelloch Scan算法,它包含两个阶段:
- 向上扫描(Reduce Phase):与归约算法类似,构建一棵二叉树,将子节点的值向上传递并求和到父节点。最终,树的根节点包含了所有元素的总和。
- 向下扫描(Down-Sweep Phase):从根节点开始,将值向下传递。具体规则是:将父节点的值赋给右子节点,并将父节点的旧值与左子节点的值相加后赋给左子节点。经过这一阶段后,叶子节点就存储了最终的前缀和结果。
GPU实现同样需要利用共享内存,并注意Bank Conflict和线程利用率的问题。CUDA工具包中提供了高度优化的thrust::inclusive_scan和thrust::exclusive_scan函数。
C++模板编程与算法泛化 🧩
为了让归约和前缀和算法能适用于不同的数据类型(int, float, double等)和不同的操作(加、乘、求最大值等),我们可以使用C++模板技术。

模板函数允许我们编写与类型无关的代码。例如,一个泛化的归约核函数可能如下所示:
template <typename T, typename BinaryOp>
__global__ void genericReduce(T* input, T* output, int n, BinaryOp op) {
extern __shared__ char shared_mem[];
T* sdata = reinterpret_cast<T*>(shared_mem);
unsigned int tid = threadIdx.x;
unsigned int i = blockIdx.x * blockDim.x + threadIdx.x;
sdata[tid] = (i < n) ? input[i] : op.identity(); // 使用操作符的单位元
__syncthreads();
for (unsigned int s = blockDim.x / 2; s > 0; s >>= 1) {
if (tid < s) {
sdata[tid] = op(sdata[tid], sdata[tid + s]); // 使用传入的操作符
}
__syncthreads();
}
if (tid == 0) {
output[blockIdx.x] = sdata[0];
}
}
// 定义加法操作符
struct AddOp {
template <typename T>
__device__ T operator()(const T& a, const T& b) const { return a + b; }
template <typename T>
__device__ T identity() const { return T(0); }
};
// 调用
genericReduce<<<blocks, threads, sharedMemSize>>>(d_in, d_out, n, AddOp());
通过模板和函数对象(Functor),我们可以轻松地将算法应用于任何定义了相应操作的类型。

算法应用场景 🎯
最后,我们看看归约和前缀和算法在物理仿真中的典型应用。

以下是几个关键的应用场景:
- SPH模拟中的邻居查找:在*滑粒子流体动力学中,需要为每个粒子查找其邻*粒子。一种高效的数据结构(如
CompactArrayList)使用前缀和算法来分配存储空间,并使用归约算法来统计总邻居数。 - 计算包围盒(Bounding Box):给定大量顶点,需要计算整个点集的轴对齐包围盒(AABB)。这可以通过两个归约操作完成:一个求所有点的最小分量(Min),另一个求最大分量(Max)。
- 数组去重:对排序后的数组进行去重操作。首先,通过比较相邻元素生成一个标记数组(不同为1,相同为0)。然后,对这个标记数组做前缀和,其结果即为去重后元素在新数组中的索引。最后,根据索引将元素散射到新数组中。

总结 📝
本节课中我们一起学*了GPU并行编程的基础知识。我们从GPU硬件架构的发展与核心特点出发,对比了CPU与GPU的设计哲学。深入探讨了CUDA编程模型,理解了线程、Block、Grid、Warp等核心概念,以及Warp Divergence和延迟隐藏对性能的影响。





我们重点剖析了GPU的存储层次,并学*了如何利用共享内存等快速存储器来优化程序。通过归约(Reduce)和前缀和(Scan)这两个经典并行算法的实例,我们掌握了在GPU上设计和实现高效并行算法的基本方法。最后,我们介绍了如何使用C++模板技术来泛化算法,并探讨了这些算法在物理仿真中的实际应用场景。





理解这些基础是进行高性能GPU物理仿真算法研发的关键。在后续的课程和实践中,我们将反复运用这些概念和技术。

GAMES401-泛动引擎(PeriDyno)物理仿真编程与实践 - P3:计算机图形学常用几何工具及数学原理 📐


在本节课中,我们将学*物理仿真中至关重要的几何工具与数学基础。主要内容包括向量代数与张量分析的基本概念、三维空间中常见几何图元的定义与表示方法,以及点到各类图元的距离计算和相交测试的核心算法。这些知识是后续进行刚体、流体和弹性体仿真的基石。


概述

上一节我们介绍了编程相关的基础知识。本节将聚焦于仿真中涉及的大量几何基础内容。由于几何学涵盖广泛,我们无法面面俱到,主要讲解与后续课程(如刚体、SPH流体、弹性体)相关的核心几何概念。


为了让大家有一个直观的感受,我们先演示一个辅助程序。这个程序可以交互式地测试一个点到OBB(有向包围盒)的距离计算,通过实时调整OBB的方向并可视化最*点连线,方便我们调试和理解算法。
接下来,我们将从理论基础开始,逐步深入。
第一部分:数学基础——向量代数与张量分析
这部分内容是后续所有计算的数学语言。虽然每个主题都足以单独讲授一节课,但我们会快速回顾关键概念和符号,以便后续使用。
向量代数基础
向量运算最好与几何直观联系起来理解。以下是几种基本运算:
- 向量加减法:
A + B或A - B。几何上对应*行四边形法则或连接向量端点的线段。 - 点积(内积):
A · B = |A||B|cosθ。结果是一个标量,可以衡量两个向量的相似程度或投影长度。 - 叉积(外积):
A × B。结果是一个新的向量,其方向垂直于A和B构成的*面,遵循右手定则。 - 三重积:
A · (B × C)。其绝对值等于以A, B, C为边的*行六面体的体积。
注意:点积满足交换律,但叉积不满足交换律(A × B = - (B × A))。
张量分析简介
在论文中常见张量符号。张量可以按阶数分类:
- 0阶张量:标量,通常用斜体小写字母表示,如
a。 - 1阶张量:向量,通常用粗体小写字母表示,如
a。 - 2阶张量:矩阵,通常用粗体大写字母表示,如
A。 - 更高阶张量:例如,在弹性力学中,应力-应变关系可能用一个4阶张量描述。
爱因斯坦求和约定 是一种简写标记法,在实现时非常实用。它规定:当一项中有重复的指标时,意味着对该指标的所有可能值求和。例如,矩阵乘法 C = A · B 的分量形式为:
C_ij = Σ_k A_ik * B_kj
在爱因斯坦约定下,求和符号Σ被省略,写为:C_ij = A_ik B_kj,其中 k 是重复的“哑指标”。
以下是张量的几种基本运算(以二阶张量为例):
- 和/差:
C_ij = A_ij ± B_ij - 标量乘法:
B_ij = λ A_ij - 点积(缩并):
C_ij = A_ik B_kj(对哑指标k求和) - 双点积(缩并):
s = A_ij B_ij(结果为一个标量) - 并矢:
T_ij = a_i b_j(将两个向量组合成二阶张量) - 转置:
B_ij = A_ji
推荐阅读:黄克智老师的《张量分析》是深入学*仿真数学基础的优秀教材。
第二部分:三维空间基本几何图元
本节我们来看看三维仿真中常见的基本几何图元及其表示方法。每种图元我们都从几何表示、代码定义和参数化形式三个方面描述。
点 (Point)
- 几何表示:空间中的一个位置。
- 代码定义:
Vector3f pos(x, y, z) - 参数化形式:
P = (x, y, z) - 说明:虽然与三维向量结构相似,但单独定义
Point类有助于区分位置和方向,并实现特定的几何运算。
线性结构:直线、射线、线段
这三者在定义上具有统一性,核心区别在于参数 t 的取值范围。
-
直线 (Line)
- 几何表示:一个原点
O和一个方向向量d。 - 代码定义:
{ Vector3f origin; Vector3f direction; } - 参数化形式:
P(t) = O + t * d,t ∈ (-∞, +∞)
- 几何表示:一个原点
-
射线 (Ray)
- 几何表示:一个起点
O和一个方向向量d。 - 代码定义:
{ Vector3f origin; Vector3f direction; }(与直线相同,通过语义区分) - 参数化形式:
P(t) = O + t * d,t ∈ [0, +∞)
- 几何表示:一个起点
-
线段 (Segment)
- 几何表示:两个端点
V0和V1。 - 代码定义:
{ Vector3f v0; Vector3f v1; } - 参数化形式:
P(t) = (1 - t) * V0 + t * V1,t ∈ [0, 1]
- 几何表示:两个端点
*面结构:*面与三角形
-
*面 (Plane)
- 几何表示:一个原点
O和一个法向量n。 - 代码定义:
{ Vector3f origin; Vector3f normal; } - 参数化/隐式形式:对于*面上任意点
P,满足(P - O) · n = 0。可化为标准形式ax + by + cz + d = 0,其中(a, b, c)为法向量。
- 几何表示:一个原点
-
三角形 (Triangle)
- 几何表示:三个顶点
V0, V1, V2。 - 代码定义:
Vector3f v[3]; - 参数化形式:
P(s, t) = V0 + s * (V1 - V0) + t * (V2 - V0),其中s >= 0,t >= 0,s + t <= 1。 - 重要约定:顶点顺序通常约定为逆时针方向,这用于定义面的正方向(外法向),在碰撞检测中至关重要。
- 几何表示:三个顶点
体结构:球、胶囊体、四面体、包围盒
-
球 (Sphere)
- 几何表示:中心点
C和半径r。 - 代码定义:
{ Vector3f center; float radius; } - 隐式形式:对于球面上任意点
P,满足|P - C| = r。
- 几何表示:中心点
-
胶囊体 (Capsule)
- 几何表示:一条线段(中心线)
V0-V1和半径r。表面点到中心线的距离恒为r。 - 代码定义:
{ Segment segment; float radius; } - 说明:在游戏引擎中广泛应用(如布娃娃系统),计算高效且能*似表示长条形物体。
- 几何表示:一条线段(中心线)
-
四面体 (Tetrahedron)
- 几何表示:四个顶点
V0, V1, V2, V3。 - 代码定义:
Vector3f v[4]; - 参数化形式:
P(u, v, w) = V0 + u*(V1-V0) + v*(V2-V0) + w*(V3-V0),其中u, v, w >= 0且u+v+w <= 1。 - 注意:顶点编号顺序影响计算出的体积正负,需要保持一致。
- 几何表示:四个顶点
-
AABB (轴对齐包围盒)
- 几何表示:分别与XYZ轴对齐的最小点
min和最大点max。 - 代码定义:
{ Vector3f min_corner; Vector3f max_corner; } - 说明:计算简单,常用于碰撞检测的初步(粗略)阶段,以快速剔除不可能相交的物体对。
- 几何表示:分别与XYZ轴对齐的最小点
-
OBB (有向包围盒)
- 几何表示:一个中心点
C,三个相互垂直的轴向量u,v,w(定义局部坐标系),以及在各轴上的半长extent。 - 代码定义:
{ Vector3f center; Vector3f axes[3]; Vector3f extent; } - 说明:是AABB的旋转版本,能提供更紧密的包围,但计算稍复杂。其变换矩阵
T包含旋转(axes)和*移(center)。
- 几何表示:一个中心点
第三部分:距离计算
距离计算是物理仿真中的核心操作之一。本节主要讲解点到其他各类图元的距离计算方法。思路通常是先找到点在目标图元上的最*点(投影点),再计算两点间的距离。
点到点
计算两点 P 和 Q 的向量差,然后求模:distance = |P - Q|。实践中常计算*方距离 distance² = (P - Q)·(P - Q) 以避免开方运算。
点到线性结构(直线/射线/线段)
基于统一的参数化思想。给定点 P 和线性结构 L(t) = O + t*d(对于线段,O=V0, d=V1-V0)。
- 计算投影参数:
t = ( (P - O) · d ) / (d · d)。这里不要求d是单位向量,避免了归一化开销。 - 根据类型截断t:
- 直线:无需截断,
t_clamped = t。 - 射线:
t_clamped = max(t, 0)。 - 线段:
t_clamped = clamp(t, 0, 1)。
- 直线:无需截断,
- 计算最*点及距离:最*点
Q = O + t_clamped * d。距离dist = |P - Q|。
点到*面
给定点 P 和*面 (O, n)。
- 计算有符号距离:
signed_dist = ( (P - O) · n ) / |n|。若n已归一化,则分母为1。 - 最*点:
Q = P - signed_dist * n(这里n需是单位向量)。
有符号距离的意义:符号表明点位于法向量指向的正面(正)还是反面(负),这在碰撞响应中非常有用。
点到三角形
这是一个约束优化问题:在条件 s>=0, t>=0, s+t<=1 下,最小化 |P - (V0 + s*e0 + t*e1)|²,其中 e0=V1-V0, e1=V2-V0。
- 解无约束问题:通过令梯度为0,求解线性方程组得到
(s, t)。这对应点P在三角形所在*面上的投影点参数。 - 区域判定与截断:根据解得的
(s, t)判断投影点位于三角形内部、边上还是角点外。共有7个区域(三角形内1个,三条边外侧3个,三个角点外侧3个)。通过判断s, t, s+t与0、1的关系来确定区域。 - 计算最*点:
- 若在内部,投影点即为最*点。
- 若在边区域,则转化为点到线段的距离问题。
- 若在角点区域,则该角点即为最*点。
点到其他图元
- 点到球:
distance = |P - C| - r。结果同样为有符号距离(内部为负)。 - 点到胶囊体:先计算点
P到中心线线段的距离dist_to_segment,则distance = dist_to_segment - r。 - 点到四面体:可转化为点到四个面的距离计算。一个优化策略是:若点位于某个面的“外侧”(即在该面定义的半空间外),则该面不可能是最*面;通过检查点相对于四个面的位置,可以减少计算量。
- 点到AABB/OBB:
- AABB:分别在XYZ轴上将点坐标钳制到
[min, max]区间,得到最*点。 - OBB:将点变换到OBB的局部坐标系(乘以变换矩阵
T的逆),此时问题转化为在局部坐标系下计算点到AABB的距离,再将结果变换回世界坐标系。
- AABB:分别在XYZ轴上将点坐标钳制到
第四部分:相交测试
相交测试在刚体碰撞检测等领域至关重要。动态物体的连续运动可以看作一个线性结构(线段),因此线性结构与图元的相交测试是许多复杂检测的基础。
直线与*面相交
给定直线 L(t) = O + t*d 和*面 (N, D)(其中 N 为法向量,D 为常数项,*面方程为 N·X + D = 0)。
- 将直线方程代入*面方程:
N·(O + t*d) + D = 0。 - 求解
t:t = -(N·O + D) / (N·d)。 - 特殊情况处理:若
N·d = 0,则直线与*面*行。若此时N·O + D = 0,则直线在*面内;否则,直线与*面无交点。
直线与球相交
给定直线 L(t) = O + t*d 和球 (C, r)。等价于求解方程 |O + t*d - C|² = r²。
- 这是一个关于
t的一元二次方程:(d·d)t² + 2d·(O-C)t + (O-C)·(O-C) - r² = 0。 - 计算判别式
Δ:Δ > 0:两个实根,直线穿过球体,有两个交点。Δ = 0:一个实根(重根),直线与球相切。Δ < 0:无实根,直线与球无交点。
- 根据
t的取值范围(直线、射线或线段),进一步确定有效的交点区间。
第五部分:场景搭建与可视化调试



理论最终需要代码实现。在PeriDyno框架中,我们可以搭建可视化场景来直观地调试几何算法,例如本节开头演示的点到OBB距离测试程序。




以下是构建此类调试场景的基本思路:







- 创建几何图元节点:例如,创建一个
OBB节点并设置其初始位置、轴向和大小。 - 创建测试点节点:例如,创建一个
Point节点。 - 创建计算节点:编写一个自定义节点,其输入为
OBB和Point,内部实现点到OBB的距离算法,输出最*点对和距离值。 - 创建可视化节点:使用
Line或Segment渲染节点,将计算节点输出的最*点对连接起来并显示。 - 添加交互控件:通过UI控件(如滑块、旋转器)动态修改OBB的方向或点的位置,观察最*点连线的实时变化。






这种可视化调试方法能有效验证算法在各种边界情况(如最*点在面、边、角点上)下的正确性。





总结





本节课我们一起学*了物理仿真编程中必备的几何与数学工具。我们回顾了向量和张量的基本运算,熟悉了三维空间中点、线、面、体等基本图元的定义与表示方法。然后,我们深入探讨了点到各类图元的距离计算算法,理解了其核心在于寻找最*点和处理参数边界。最后,我们介绍了线性结构与*面、球的相交测试原理,并了解了如何利用可视化场景进行算法调试。



掌握这些基础几何操作,将为后续实现刚体碰撞、流体模拟和弹性体变形等复杂的物理仿真效果打下坚实的基础。

GAMES401-泛动引擎(PeriDyno)物理仿真编程与实践 - P4:刚体动力学并行编程与实践 🧱
在本节课中,我们将要学*刚体动力学的基础知识,并重点探讨如何在GPU上并行处理包含大量刚体的复杂场景。课程将涵盖从基础物理量到碰撞检测、约束求解的完整流程,并介绍关键的并行加速算法。
刚体动力学基础回顾
上一节我们介绍了质点动力学,本节中我们来看看更复杂的刚体动力学。刚体与质点的核心区别在于,刚体具有形状和体积,因此其运动状态不仅包括*移,还包括旋转。
刚体运动所需的核心物理量如下:
- 质量:与质点不同,刚体的质量分布用质量张量描述。
- 位置与旋转:位置用向量 x 表示,旋转姿态可以用旋转矩阵 R 或四元数 q 描述。
- 速度与角速度:*移速度 v,旋转角速度 ω。
- 外力与扭矩:作用在刚体上的力 F 和扭矩 τ。
刚体的运动方程是质点方程的扩展,包含*移和旋转两部分:
*移运动:
M * dv/dt = F
旋转运动:
I * dω/dt = τ
其中,M 是质量张量,I 是惯性张量。更新旋转姿态(四元数 q)的公式较为特殊:
dq/dt = 0.5 * ω * q
这里,ω 需要被扩展为四元数形式 (0, ω_x, ω_y, ω_z),* 表示四元数乘法。
刚体仿真的核心流程可以概括为两个主要阶段:碰撞检测 和 约束求解。碰撞检测负责找出哪些物体发生了接触,而约束求解则计算接触力,确保物体不会相互穿透。
碰撞检测:宽阶段
碰撞检测的第一步是“宽阶段”,其目的是快速剔除掉那些明显不可能发生碰撞的物体对,从而减少后续精确检测的计算量。本节中我们来看看实现宽阶段加速的关键技术。

包围盒技术
为了加速碰撞检测,我们通常用简单的几何体(包围盒)来*似复杂的物体模型。常见的包围盒类型有:
- 轴对齐包围盒:边与坐标轴对齐的长方体,计算最简单。
- 有向包围盒:可旋转的长方体,能更紧密地包裹物体。
- 包围球:用球体包裹物体。

在并行计算中,轴对齐包围盒 因其计算简单高效而被广泛使用。
空间划分算法
有了包围盒后,我们需要高效的算法来找出所有相交的包围盒对。传统的CPU算法(如排序扫描算法)不适合GPU并行执行,因为其涉及动态数据结构和顺序扫描。
对于GPU并行,更合适的结构是层次化包围盒。它通过构建一棵二叉树来组织空间中的物体,每个树节点存储一个能包围其所有子节点的包围盒。这种结构存储开销低(约 2n-1 个节点,n为物体数),且便于并行构建和遍历。
Linear BVH:GPU并行构建层次包围盒
本节重点介绍一种适合GPU的并行层次包围盒构建算法——Linear BVH。其核心思想是利用莫顿码将空间上邻*的物体,映射到内存中连续的位置。
莫顿码通过对物体中心坐标的二进制位进行交错编码生成。例如,一个二维点 (x, y),其莫顿码 M = interleave(x_bits, y_bits)。对物体按莫顿码排序后,空间上邻*的物体在内存中也基本连续。
以下是Linear BVH并行构建的关键步骤:
- 计算莫顿码并排序:为每个物体的包围盒中心计算莫顿码,并据此对物体进行排序。
- 确定节点区间:对于每个内部节点(共
n-1个),算法并行地确定其代表的叶子节点区间[i, j]。 - 寻找分裂位置:在区间
[i, j]内,找到莫顿码的最长公共前缀首次发生变化的位置k。该位置将区间分裂为左右子树[i, k]和[k+1, j]。 - 并行构建包围盒:采用从叶子到根节点的“自底向上”方式并行构建每个节点的包围盒。使用原子操作确保每个内部节点只被计算一次。
构建包围盒的核心并行代码逻辑如下(伪代码):
// 每个线程处理一个内部节点
for each internal node i in parallel:
// 使用原子操作标记该节点已被访问
int old_val = atomicCAS(&node[i].visited, 0, 1);
if (old_val == 0) {
// 第一个访问该节点的线程(来自左子树)等待
return;
} else {
// 第二个访问该节点的线程(来自右子树)进行计算
// 获取左右子节点的包围盒 AABB_left, AABB_right
AABB node_aabb = merge(AABB_left, AABB_right);
node[i].aabb = node_aabb;
}
通过这种方式,我们可以在GPU上高效地构建出整个层次包围盒树,为后续的碰撞查询做好准备。
碰撞检测:窄阶段
宽阶段筛选出了潜在的碰撞对,窄阶段则需要进行精确的几何相交检测,并生成具体的接触信息。本节中我们来看看如何精确检测并处理接触。
接触定义与分离轴定理
在仿真中,我们将复杂的面接触或线接触离散化为一系列点接触。每个接触点包含以下信息:
- 位置:接触点的空间坐标。
- 法线:接触*面的法线方向,指向分离方向。
- 穿透深度:物体相互穿透的距离。
对于两个凸几何体(如Box)是否相交,最常用的判定算法是分离轴定理。该定理指出:若两个凸体不相交,则必定存在一条直线(分离轴),能将它们在轴上的投影分离。
对于两个三维的轴对齐包围盒,我们需要检测15条潜在的分离轴(每个Box的3个坐标轴方向,以及两两边组合的9个叉积方向)。对于每条轴L,计算两个Box在该轴上的投影区间,若存在一条轴使得投影区间不重叠,则两Box未碰撞。
投影是否重叠的判定公式如下:
| (C1 - C0) · L | > (r0 + r1)
其中,C0, C1 是中心点,r0, r1 是投影半径。
接触流形生成
对于已经发生碰撞的物体对(如两个Box面接触),仅生成一个接触点是不稳定的,可能导致物体在支撑面上抖动。我们需要生成一个接触流形——即一组接触点,来*似表示整个接触面或接触线。
一种常用的生成接触流形的算法是Sutherland-Hodgman多边形裁剪算法。其基本思想是:将一个物体(裁剪多边形)的每条边依次作为裁剪*面,对另一个物体(被裁剪多边形)进行裁剪,最终得到代表相交区域的多边形(接触流形)。
在GPU实现中,由于动态内存分配开销大,我们通常针对基本几何体(如四面体、六面体)预计算其可能的最大接触点数,并分配固定大小的存储空间。
动力学求解
找到所有接触点后,下一步是求解动力学,计算接触力,并更新刚体的运动状态。本节中我们来看基于速度层面的约束求解方法。
接触约束建模
对于每个接触点,我们要求在该点处,两个刚体在接触法线方向上的相对速度为零,以防止继续穿透或分离。这个约束可以写为:
J * v' = 0
其中,v' 是下一时刻的速度向量(包含所有刚体的线速度和角速度),J 是雅可比矩阵,它编码了接触几何信息。
速度 v' 与当前速度 v、受力 F 的关系由离散化的牛顿第二定律给出:
v' = v + M^{-1} * F * Δt
这里,M 是质量矩阵,F 是总力(包含外力和接触力)。
求解接触力
我们假设接触力 F_c 的形式为:
F_c = J^T * λ
其中 λ 是拉格朗日乘子(标量),代表接触力的大小。这种形式能保证接触力作为系统内力不做功。
将力 F_c 和速度 v' 的关系代入约束方程 J * v' = 0,经过推导,可以得到一个关于 λ 的线性方程组:
(J * M^{-1} * J^T) * λ = -J * v / Δt
这是一个 Ax = b 形式的方程,其中 A = J * M^{-1} * J^T 是对称正定矩阵。可以使用雅可比迭代、高斯-赛德尔等迭代法并行求解。
处理穿透与摩擦
如果物体已经发生穿透,需要在约束方程中加入一个偏置项 b,以在下一时间步内修正穿透深度:
J * v' = -β * penetration / Δt
其中 β 是一个介于0和1之间的参数,控制修正速度。
摩擦力的建模类似于接触约束,但作用于接触切*面方向,并且其大小受库仑定律限制:|f_friction| ≤ μ * |f_normal|,其中 μ 是摩擦系数。求解时,这通常作为一个锥体约束来处理。
其他常见约束
除了接触约束,多刚体系统中还有各种关节约束,例如:
- 球窝关节:约束两个连接点位置重合,允许三个旋转自由度。
- 活塞关节:允许沿一个轴移动和绕该轴旋转,约束其余四个自由度。
- 铰链关节:只允许绕一个轴旋转,约束其余五个自由度。






这些约束都可以用类似的雅可比矩阵形式 J * v' = 0(或 = b)来描述,并集成到同一个线性系统中进行求解。


课程总结与演示




本节课中我们一起学*了刚体动力学并行编程的核心内容。







我们首先回顾了刚体运动的基本物理量和方程。然后,深入探讨了碰撞检测的两个阶段:宽阶段利用层次包围盒和Linear BVH算法在GPU上并行加速;窄阶段使用分离轴定理进行精确检测,并生成接触流形。最后,我们介绍了如何将接触和关节建模为速度层面的约束,并通过求解线性方程组来计算作用力,从而更新刚体状态。




课程还提供了两个核心算法的演示:
- Linear BVH构建可视化:展示了GPU并行构建的层次包围盒树如何随物体运动而动态更新。
- 分离轴定理检测演示:展示两个Box碰撞时,接触流形(一组接触点)如何被实时计算并可视化。


这些基础技术和算法是构建高效、稳定刚体仿真系统的基石,可以扩展到更复杂的场景和几何类型中。

GAMES401-泛动引擎(PeriDyno)物理仿真编程与实践 - P5:光滑粒子动力学(SPH)并行编程与实践 🧪
在本节课中,我们将要学*光滑粒子动力学(SPH)的基础理论,并重点探讨如何利用SPH方法在GPU上模拟粘性不可压缩流体。我们将从SPH的核心思想出发,逐步讲解其数学基础、离散化方法、以及针对流体模拟的具体实现步骤,包括不可压缩性、粘性、边界处理和表面张力的处理。
概述 📋
光滑粒子动力学(SPH)是一种无网格的拉格朗日方法,广泛应用于流体动力学等物理现象的模拟。其核心思想是用一组离散的、携带物理属性的粒子来表征连续介质,并通过核函数*似来估算场量及其导数。本节课将系统性地介绍SPH方法,并聚焦于其在粘性不可压缩流体仿真中的应用与实践。
SPH基础理论 🧠
上一节我们概述了SPH方法。本节中我们来看看SPH解决连续场离散化问题的核心思想。
SPH方法的核心在于用离散粒子表示连续场。与有限元法或欧拉网格法不同,SPH粒子之间没有固定的拓扑连接关系,其邻域关系需要在模拟过程中动态重构。基于此特性,SPH计算包含几个基本要素。
核函数*似
SPH方法的核心是核函数*似。所有物理量存储在离散的粒子中心点上。对于空间中任意一点,其物理量的值通过对其邻域内粒子携带的值进行加权*均得到。
以下是核函数*似的基本离散形式:
f_i ≈ Σ_j (m_j / ρ_j) * f_j * W(|r_i - r_j|, h)
其中:
f_i是粒子i的待求物理量。m_j和ρ_j是邻域粒子j的质量和密度。W是核函数,h是光滑长度(支撑域半径)。r_i和r_j是粒子位置。
核函数 W 需要满足几个关键性质:
- 紧支性:在支撑半径
h之外,函数值为零。这保证了计算局部性,只需考虑附*粒子。 - 归一化条件:在支撑域内的积分值为1。这保证了即使对于常量场,*似计算也能得到正确值。
从狄拉克函数到核函数
SPH理论源于用核函数 W *似理想的狄拉克δ函数。狄拉克δ函数与任意函数 f 的卷积能得到该函数在中心点的精确值。然而,狄拉克δ函数在现实中无法表示。SPH将其“拉*”为一个光滑的核函数,但这引入了*似误差。理解这一点至关重要,因为SPH的所有计算都存在固有误差,需要在算法设计中予以考虑。
导数计算
在物理模拟中,经常需要计算场量的梯度、散度等导数。SPH通过将微分算子转移到已知的核函数上来实现。
例如,标量场 f 的梯度*似为:
∇f_i ≈ Σ_j (m_j / ρ_j) * f_j * ∇W(|r_i - r_j|, h)
然而,直接这样计算可能不稳定或不精确。更常见的做法是利用分部积分得到另一种形式:
∇f_i ≈ Σ_j (m_j / ρ_j) * (f_j - f_i) * ∇W(|r_i - r_j|, h)
这种形式具有反对称性,能更好地满足动量守恒。对于更复杂的算子(如拉普拉斯算子),也有相应的离散格式。
边界处理挑战
在推导梯度等算子的SPH离散形式时,会涉及一项边界积分项。对于完全被粒子包围的内部粒子,此项为零。但对于靠*流体表面或固体边界的粒子,此项不为零,直接忽略会导致精度下降和边界处的不稳定。因此,边界条件的正确处理是SPH模拟中的关键挑战之一。
SPH面临的挑战与前沿 🎯
上一节我们介绍了SPH的基础公式。本节中我们来看看当前SPH方法研究面临的主要挑战。
SPH方法虽然应用广泛,但仍存在一些公认的难点。SPHack组织对此进行了总结,并设立了以SPH发明人命名的奖项,以鼓励在以下方面的突破:
以下是SPH领域的主要挑战:
- 精度:相比高精度方法如谱方法或高阶有限元,传统SPH的精度较低。
- 稳定性:特别是在模拟不可压缩流体时,需要处理数值不稳定问题。
- 边界条件:如前所述,复杂边界(如自由表面、移动固体边界)的处理仍是一大难题。
- 自适应:如何高效地进行粒子自适应细化与粗化。
- 耦合:如何与其它数值方法(如有限元法、边界元法)进行稳定高效的耦合。
粘性不可压缩流体模拟 🌊
上一节我们了解了SPH的通用框架与挑战。本节中我们聚焦于本节课的核心目标:如何使用SPH模拟粘性不可压缩流体。
控制方程:纳维-斯托克斯方程
粘性不可压缩流体的运动由纳维-斯托克斯方程描述:
动量方程:
ρ (∂u/∂t + u·∇u) = -∇p + μ∇²u + ρg
不可压缩条件:
∇·u = 0
其中:
ρ是恒定密度。u是速度场。p是压强场。μ是动力粘性系数。g是重力加速度。
方程左边是惯性项,右边依次是压力项、粘性项和体积力项。不可压缩条件 ∇·u = 0 等价于密度恒定 ρ = const。
算子分裂求解框架
在SPH中,常采用算子分裂法来求解NS方程。它将复杂的方程分解为几个可顺序求解的简单步骤。
以下是典型的求解步骤:
- 邻域查找:更新每个粒子的邻居列表。
- 预测步:计算除压力外的所有力(如重力、粘性力、表面张力),并更新得到一个中间速度
u*。u* = u^t + Δt * (g + ν∇²u^t + 其他力/ρ) - 压力泊松方程求解:求解压强场
p,使得修正后的速度场满足不可压缩条件。
然后更新速度:∇·( (1/ρ) ∇p ) = (∇·u*) / Δtu^{t+1} = u* - (Δt/ρ) ∇p - 位置更新:用最终速度更新粒子位置。
x^{t+1} = x^t + Δt * u^{t+1}
接下来,我们将详细探讨其中几个关键环节的实现。

关键算法环节详解 ⚙️



1. 邻域查找
邻域查找是SPH每一步计算的基础,其效率至关重要。主要有两种策略:
基于哈希网格的方法:
- 思想:将空间划分为均匀网格,网格尺寸与光滑长度
h相关。每个粒子根据其位置被放入对应网格。查找邻居时,只需检查中心粒子所在网格及其相邻的26个(3D)网格中的粒子。 - 优点:实现简单,对于粒子分布均匀的场景效率高。
- 缺点:需要预先分配覆盖整个计算域的内存,对于开放或粒子分布极稀疏的场景可能造成内存浪费。
基于层次包围盒的方法:
- 思想:为每个粒子构建一个包围球(半径为
h),然后利用树形结构(如BVH)管理所有包围球。通过遍历树结构快速查找可能碰撞的粒子对,再进行精确距离判断。 - 优点:非常适合粒子分布稀疏或场景开放的情况,内存使用更高效。
- 缺点:树结构的构建与更新有一定开销。
2. 不可压缩性实现
实现不可压缩性主要有两种思路:基于位置的动力约束和基于投影的压强求解。
基于位置的方法:
- 代表:位置动力学
- 思想:将密度约束
ρ_i = ρ_0视为一个约束条件C_i(x) = ρ_i/ρ_0 - 1 = 0。通过迭代调整粒子位置x来满足约束。调整方向沿约束梯度∇C,步长通过线性化约束方程求解。 - 优点:简单、稳定、易于实现。
- 缺点:有时会过度*滑细节,难以模拟某些复杂现象。
基于投影的方法:
- 思想:从中间速度
u*中减去一个由压强梯度产生的速度修正项,使最终速度散度为零。这导出一个关于压强p的泊松方程,需要求解一个线性系统。 - 变分框架:为了获得更稳定、物理更正确的离散形式,常采用能量最小化的变分框架推导压强力。这避免了直接离散拉普拉斯算子可能带来的不稳定。
- 优点:通常能获得更精确的动力学行为,如清晰的飞溅、涡旋等。
- 缺点:需要求解线性系统,计算开销较大;边界处理更复杂。
3. 粘性实现
粘性力模拟流体内部分子间的动量扩散。SPH中常见的粘性模型有:
人工粘性:
- 一种简单的正则化方法,通过在粒子间添加与速度差成正比的阻尼力来模拟粘性耗散。但它会同时衰减法向和切向的相对运动。


剪切粘性模型:
- 更物理的模型,如。它基于流体的剪切应变率张量来计算粘性应力。在离散时,可以构造仅衰减粒子间相对速度的法向分量,而保留切向分量的模型,这对于模拟蜂蜜下落等具有旋转特性的粘性流动至关重要。

迭代耦合:
- 在算子分裂框架中,单独求解粘性和压力可能会相互干扰,影响效果。一种改进策略是在一个时间步内,对粘性求解器和压力求解器进行多次迭代,使两者更好地耦合,从而同时满足不可压缩条件和粘性行为。
4. 边界处理
如前所述,边界缺失会导致核*似不准确。常用方法有:
动态粒子法:
- 将固体边界也用一层或多层粒子离散。这些边界粒子参与邻域搜索和密度计算,并对流体粒子施加排斥力。这是最常用且直观的方法,但增加了计算量和内存消耗。
半解析边界法:
- 思想:不离散边界粒子,而是直接对核函数在流体区域外的缺失部分进行解析或半解析积分。将体积分转化为对已知的固体表面网格的面积分。
- 优点:内存开销小,精度高,特别适合处理复杂、尖锐的几何边界。
- 缺点:数学推导和实现复杂,计算开销可能较大。
5. 表面张力
表面张力使流体表面尽可能收缩,形成最小表面积。在粒子法中,精确界定“表面”粒子是困难的。
连续表面力模型:
- 在表面附*定义一个颜色场
c(如内部为1,外部为0)。表面张力与颜色场的拉普拉斯成正比,方向沿颜色场梯度方向。

扩散界面法:
- 思想:将尖锐界面模糊为一个具有有限厚度的扩散界面。在界面层内定义一个相场函数
φ,其值从-1*滑过渡到1。表面张力通过最小化与φ梯度相关的界面能来实现。 - 优点:自然地避免了表面粒子检测,易于实现,且能方便地处理多相流。
- 扩展到固-液-气三相:通过为固-液、液-气界面分别定义界面能,并考虑接触角,可以模拟水滴在固体表面的铺展、浸润等现象。






程序演示与框架介绍 💻



上一节我们深入探讨了各个算法环节。本节中我们通过程序演示来直观了解SPH仿真的实现与泛动引擎的使用。




泛动引擎提供了一个模块化的可视化编程环境,用于快速构建和测试物理仿真场景。


以下是引擎使用的核心概念:
- 节点:每个算法模块(如发射器、SPH求解器、边界条件、渲染器)都被实现为一个独立的节点。
- 数据流:节点通过端口连接,形成数据流图。数据(如粒子位置、速度)从一个节点流向另一个节点。
- 属性面板:每个节点暴露可调参数(如粘性系数、时间步长),用户可在属性面板中实时调整并观察效果。



演示场景示例:
- 基本流体发射:连接发射器节点、SPH求解器节点和渲染器节点,模拟水流。
- 流体-固体交互:在上述流程中加入一个载入表面网格的边界节点,并将其与SPH求解器耦合,模拟水流冲击管道。
- 算法切换:将SPH求解器节点从基于投影的方法切换到基于位置动力学的方法,观察不同算法的模拟效果差异。
- 自定义初始状态:使用一个生成粒子的节点(如生成立方体粒子阵列)替代发射器,作为仿真的初始状态。




通过这种模块化方式,研究者可以专注于开发新的算法节点,并轻松地将其集成到现有框架中,与其他模块组合以创建复杂的仿真应用。

总结 📚
在本节课中,我们一起学*了光滑粒子动力学的基础理论与在粘性不可压缩流体模拟中的应用。
我们首先阐述了SPH用离散粒子与核函数*似连续场的核心思想,并指出了其存在的*似误差。接着,我们分析了当前SPH研究在精度、稳定性、边界处理等方面面临的挑战。
课程的重点在于如何用SPH求解纳维-斯托克斯方程。我们介绍了算子分裂的求解框架,并逐一详解了其中的关键环节:高效的邻域查找算法、实现不可压缩性的两种主要方法、粘性力的模型、复杂边界的处理策略以及表面张力的模拟技术。

最后,我们通过泛动引擎的程序演示,展示了如何将这些算法模块化并组合使用,快速构建出不同的流体仿真场景。


希望本教程能帮助你入门SPH流体仿真,并为你在这一领域的进一步探索与实践打下基础。

扩展阅读资料:
- 《Smoothed Particle Hydrodynamics: A Meshfree Particle Method》
- 《Particle-Based Fluid Simulation for Interactive Applications》
- 《Weakly Compressible SPH for Free Surface Flows》
- 《A Variational Approach to the Simulation of Incompressible Fluid-Solid Interaction》
- 《A Semi-analytical Boundary Method for SPH with Applications to Flow in Complex Geometries》

GAMES401-泛动引擎(PeriDyno)物理仿真编程与实践 - P6:*场动力学(Peridynamics)并行编程与实践 🧵
在本节课中,我们将要学**场动力学(Peridynamics)的基础理论、核心算法及其在GPU上的并行实现。*场动力学是一种处理材料非连续变形(如断裂、撕裂)的仿真方法,它摒弃了传统连续介质力学中基于“接触面”的假设,转而采用基于“长程力”和“键”的离散积分模型。我们将重点介绍一种高效的求解技术——半隐式连续迭代法,用于处理非线性超弹性材料的仿真问题。
*场动力学基础理论 🔬
上一节我们介绍了本课程的整体框架,本节中我们来看看*场动力学的基础理论。*场动力学与传统连续介质力学有本质区别。
传统连续介质力学建立在“接触力”的假设之上。它假设从物体中取出的任意一个微元,其内力施加在微元的表面上(例如立方体的六个面)。这本质上是一个理想的数学模型,将微观分子间的作用力理想化为无限小距离的“面接触”。该模型适用于拓扑变化不大的连续介质,但在处理撕裂、崩塌等非连续问题时存在困难。
*场动力学则摒弃了“面接触”的观念,其框架更接*分子动力学。它假设物质点之间的相互作用是“长程力”,类似于原子或分子间的吸引或排斥力。其核心区别在于用“非局部”的积分方程替代了传统“局部”的偏微分方程。它不计算某点的导数,而是直接建模两个物质点之间的相互作用。
核心概念:态(State)与键(Bond)
*场动力学中最重要的两个概念是“键”和“态”。
- 键:连接两个相邻物质点
x和x'的虚拟连接,代表了它们之间的相互作用关系。 - 态:一个映射函数,其自变量是一个“键”,因变量是一个张量(如标量、向量)。它描述了该键所对应的物理量(如形变、应力)的变化。
例如,一个“形变态”Y可以将初始构型中的一个向量键ξ = x' - x,映射到变形后构型中的一个新向量y' - y。通过枚举中心点所有相邻的键,就可以离散地描述该点周围的形变状态,而无需计算偏导数。这种方法对映射函数的连续性没有要求,因此天生适合处理非连续问题。
态的代数运算
基于“态”可以构建一个代数空间,并进行各种运算。以下是常见的几种运算:
- 加法:两个同阶的态可以相加。
- 组合:一个
m阶态与一个一阶(向量)态可以进行组合操作。 - 点积与内积:类似于向量的点积,但需要在所有邻域上进行积分。
- 并矢:两个向量态
a和b可以并矢形成一个二阶张量a ⊗ b,这在描述形变状态时非常有用。
需要注意的是,在文献中,态常用下划线标记(如a),以区别于传统连续介质力学中的量。
形变与应力的态描述 📐
上一节我们介绍了态的基本概念,本节中我们来看看如何用态来描述形变和应力。
在传统连续介质力学中,形变用一个二阶张量(形变梯度F)描述,它可以通过对位移场求偏导得到。这种方法要求位移场连续可微,无法描述非连续形变。
在*场动力学中,我们用“形变态”Y来描述形变。对于中心点x和邻点x‘,形变态直接给出了变形后的相对位置向量y‘ - y。它不需要求导,可以独立描述每个键的形变,因此天然能够处理带有尖角或断裂的非连续形变。代价是存储和计算量通常比传统方法高。
应力的描述也类似:
- 传统方法:使用柯西应力张量
σ等,并通过本构关系σ = C : ε与应变相联系。 - *场动力学:使用“应力态”
T,它同样是键的函数。应力态与形变态之间也存在映射关系T = T(Y),用于描述材料特性。
*场动力学模型分类
根据应力态T的定义方式,主要分为两类模型:
- 键基模型:最早由Silling提出。物质点
x和x‘之间的相互作用力只与连接它们的键有关,并且大小相等、方向相反。这类似于弹簧模型,但无法模拟泊松比不为0.25的材料。 - 态基模型:物质点
x和x‘之间的力不仅取决于它们之间的键,还取决于x与所有邻点键的状态。这可以模拟各种泊松比。态基模型又分为:- 普通态基模型:相互作用力方向仍沿
x与x‘的连线方向,易于保证动量和角动量守恒。 - 非普通态基模型:相互作用力方向可以不沿连线方向,模型设计更灵活,但需要额外约束以保证物理守恒律。
- 普通态基模型:相互作用力方向仍沿
*场动力学的运动方程最终可以离散化为:
m_i * a_i = Σ_j ( T[x]〈x‘ - x〉 - T[x‘]〈x - x‘〉 ) * V_j + b_i
其中,m_i是质量,a_i是加速度,T是应力态,V_j是邻点体积,b_i是外力。求和是对所有邻点j进行。
投影动力学与优化求解框架 ⚙️
上一节我们得到了*场动力学的运动方程,本节中我们来看看如何高效求解它,特别是对于非线性超弹性材料。
传统的显式积分方法因稳定性要求,需要极小时步,效率低下。我们可以将动力学问题转化为一个能量最小化的优化问题。系统的总能量通常包含两部分:
- 惯性项:与预测位置和最终位置之间的偏差有关,是一个二次项。
- 势能项:包括弹性能、接触能等,可能是非线性的。
投影动力学 的核心思想是将这个优化问题分裂为两步:
- 全局步:处理“简单的”部分,通常是惯性项和部分势能项,希望其形式便于全局求解(如线性方程组)。
- 局部步:处理“复杂的”部分,如非线性的接触约束,这些问题通常可以独立地、并行地在每个小单元(如三角形面片、接触对)上求解。
然而,标准的投影动力学要求全局步必须是二次型(线性),这对于非线性的超弹性能量项不适用。为此,我们引入了投影*场动力学框架,允许在全局步中处理一部分非线性项。
半隐式连续迭代法 🔄
上一节我们提出了需要在全局步中处理非线性项的问题,本节中我们介绍解决该问题的核心算法——半隐式连续迭代法。
对于非线性方程f(x)=0,牛顿法需要计算并求逆海森矩阵,在高维(如几百万自由度)且模型复杂(如*场动力学)时非常困难且不易并行。
连续迭代法是一种替代方案。其核心是构造一个函数g(x),将求解f(x)=0转化为寻找g(x) = x的不动点。通过迭代x_{k+1} = g(x_k)来逼*解。该方法并行友好,但收敛性依赖于g(x)的构造。
半隐式连续迭代法是针对*场动力学方程特点设计的。我们将运动方程中关于未知位置y_i的项进行拆分:
- 将系数恒为正的项保留在左边,进行隐式处理。
- 将系数恒为负的项移到右边,并用上一次迭代的值代入,进行显式处理。
这样处理后的方程形式为:
y_i = (1 / (1 + a_ii)) * y_i^* + Σ_{j≠i} ( a_ij / (1 + a_ii) ) * y_j
其中a_ij恒为正。这确保了新解y_i是其预测位置y_i^*和邻点位置y_j的凸组合,从而保证了迭代过程的稳定性与收敛性。
算法步骤与超弹性材料应用
- 线性化与拆分:对于超弹性材料,其应力源于应变能密度函数
Ψ。通过极分解将形变梯度F分解为旋转R和拉伸U。将对角化的拉伸部分U(其主值为λ₁, λ₂, λ₃)对应的力进行正负拆分。 - 迭代求解:在每次全局步迭代中,将显式部分用当前值代入,求解一个关于隐式部分的线性方程组(类似于雅可比迭代)。
- 线搜索:为防止在能量*缓区迭代“超调”,我们基于一阶泰勒展开进行一步线搜索,快速找到使能量下降的步长,避免多次尝试带来的性能波动。
该方法非常适合GPU并行,因为它避免了海森矩阵计算,主要计算是局部3x3矩阵求逆和稀疏矩阵迭代,收敛速度在前中期很快。
局部步处理与演示 🎮
上一节我们解决了全局步的非线性求解,本节中我们简要介绍局部步的处理并展示仿真结果。
局部步主要负责处理非光滑的非线性约束,例如布料的自碰撞与互碰撞。我们采用基于对数障碍函数的IPC方法。
- 梯度下降求解:每个接触约束可以独立并行地处理,使用梯度下降法寻找满足非穿透约束的位置修正。
- 技巧处理:
- 对障碍函数进行*滑截断,防止数值爆炸。
- 结合连续碰撞检测技术,防止大步长下的穿透。
- 对于布料等共维结构,引入沿法向的“虚拟键”来稳定形变梯度的计算,防止矩阵奇异。
仿真演示
以下是一些使用所述方法实现的仿真场景演示:
- 单层布料:与刚性物体碰撞、 draping 测试。
- 多层布料:模拟多层布料堆叠下落时的复杂接触与褶皱。
- 布料撕裂(算法支持,演示略):展示了*场动力学处理材料断裂的天然优势。


这些仿真案例验证了半隐式连续迭代法结合投影*场动力学框架,能够稳定、高效地模拟非线性超弹性材料的大变形、复杂接触行为。




总结 📝









本节课中我们一起学*了*场动力学并行编程与实践的核心内容。


我们首先介绍了*场动力学的基础理论,理解了其通过“键”和“态”描述非局部相互作用的原理,以及与传统连续介质力学的区别。接着,我们探讨了如何将动力学问题转化为优化问题,并针对非线性求解的难点,重点讲解了投影*场动力学框架和其中的关键算法——半隐式连续迭代法。该方法通过巧妙的项拆分,将非线性问题转化为迭代求解的线性问题,兼具了稳定性、高效性和优异的GPU并行潜力。最后,我们了解了局部步如何处理接触碰撞,并观看了相关仿真演示。





通过本课的学*,你应该对*场动力学这一强大的非连续仿真方法有了入门了解,并掌握了一种高效求解非线性仿真问题的实用算法思路。

GAMES401-泛动引擎(PeriDyno)物理仿真编程与实践 - P7:插件与功能拓展 🧩
在本节课中,我们将学*PeriDyno框架的插件机制与功能拓展方法。核心目标是理解如何利用这套机制,将各种独立的仿真技术(如流体、刚体、布料等)整合到一个统一的框架中,从而构建复杂的仿真场景。我们将重点探讨C++与蓝图(Blueprint)的协同工作方式、反射机制、回调函数以及插件设计原则。
概述:从研究到应用的挑战与引擎的价值
从理论创新到实际工程应用是一个漫长的流程。在这个过程中,不同角色(如研究人员、研发工程师、应用设计师)的协作面临诸多挑战:
- 研究阶段:以发表论文为主,但代码资产往往缺乏长期维护和规范,导致难以传承和复用。
- 研发阶段:需要将研究代码改造为工程可用模块,涉及数据规范、接口兼容性等问题,常需研究人员与工程人员紧密协作。
- 应用阶段:在实际场景中测试,面临性能、稳定性、可靠性等挑战。发现问题后,可能又需要回到研究阶段寻求新的理论突破。
整个流程是一个闭环,但不同角色间的协作成本很高。PeriDyno引擎的核心目标之一,就是通过提供统一的框架和工具,降低这种协作成本,让每个角色都能在其擅长的领域深耕,并通过引擎便捷地整合成果。

C++与蓝图:代码逻辑与界面逻辑的统一 🧠
PeriDyno解决协作问题的核心思想是实现C++代码运行逻辑与图形化界面(蓝图)运行逻辑的统一。

- C++逻辑:对应程序员或研发人员的视角,用于实现核心算法和数据结构。
- 蓝图逻辑:对应最终用户或设计师的视角,通过可视化的节点图方式,拖拽和连接各种模块来搭建仿真任务。
这种设计使得非计算机专业的人员也能相对轻松地构建复杂仿真流程,而开发者则能专注于底层算法的实现。蓝图的概念借鉴了现代游戏引擎(如Unreal Engine),它通过组件化和数据流图的方式,让任务流程变得动态和可配置,而非固化在代码中。



为了实现这一目标,PeriDyno采用了一种相对轻量级的实现方案,主要依赖两个关键技术:反射(Reflection) 和 宏(Macro)。






反射机制:实现动态类型识别的基石 🔍








反射是指程序在运行时能够检测、查询甚至修改自身状态的能力。在PeriDyno中,反射主要用于:
- 动态创建对象:通过类名字符串,在运行时创建对应的C++类实例。
- 自动生成UI:识别类的成员变量(属性),并自动在界面中生成对应的编辑器控件(如滑块、输入框)。

由于C++是编译型语言,本身不支持反射,因此PeriDyno需要自行实现这套机制。



反射的实现原理

PeriDyno中所有需要支持反射的类都继承自一个公共基类 Object。反射的核心在 Object 类中实现。

类的注册与管理:
Object 类内部维护了一个全局的映射表(例如 std::map<std::string, ClassInfo*>),用于存储所有已注册类的信息(类名、构造函数等)。当一个新的派生类被定义时,需要通过特定的宏将其信息注册到这个全局表中。

关键宏定义:
对于一个需要反射的类,通常需要在头文件和实现文件中使用宏。
- 声明(在头文件中):
PERIDYNO_DECLARE_CLASS(YourClassName) - 实现(在源文件中):
这个宏会创建一个静态的PERIDYNO_IMPLEMENT_CLASS(YourClassName, “YourClassName”, ParentClassName)ClassInfo对象,并在程序启动前(进入main函数之前)自动执行注册逻辑,将类名和其构造函数指针存入全局映射表。

成员变量的反射:
对于需要在UI中暴露和控制的成员变量(称为 Field),同样使用宏来声明。
DEFINE_VAR(YourType, YourVarName, “FieldDisplayName”);
这样,框架就能识别这个变量,并在UI中为其生成相应的控制部件。
通过这套机制,当用户在蓝图界面中输入“Cube”或点击创建Cube节点的按钮时,底层框架就能通过字符串“Cube”找到对应的类信息,调用其构造函数动态创建对象,并自动生成带有“边长”、“分段数”等属性控件的UI节点。







节点、场景与数据流:静态结构的对应 🧱
在PeriDyno中,仿真场景由节点(Node) 构成,节点之间通过数据连接形成有向无环图(DAG),即场景图。
- 节点:对应蓝图中的一个图形模块。每个节点内部包含:
- 控制变量(Control Fields):用户可通过UI调整的参数(如立方体的边长、发射器的位置)。
- 状态变量(State Fields):描述节点内部状态的最小数据集(如粒子的位置、速度)。
- 输入/输出端口:用于与其他节点交换数据。
- 场景:是多个节点及其连接关系的集合,对应一个完整的仿真任务。

C++代码与蓝图的对应关系:
一段创建流体仿真场景的C++代码,其逻辑与在蓝图界面中拖拽节点并连线的操作完全对应。

例如,创建发射器、连接流体求解器、设置边界的C++调用:
auto emitter = scene->createNode<FluidEmitter>();
auto solver = scene->createNode<FluidSolver>();
auto boundary = scene->createNode<VolumeBoundary>();





// 连接操作对应蓝图中的连线
emitter->connect(solver); // 将发射器的粒子数据输出到求解器
solver->connect(boundary->inFluid()); // 将求解后的流体数据连接到边界节点的流体输入端口
这段代码构建的静态数据流图,与在UI中看到的节点连接图是完全一致的。







回调函数:处理动态事件与交互 🔄






仿真不仅是静态场景,更是动态的时间序列。PeriDyno通过回调函数(Callback) 机制来处理运行时的动态事件和交互。
核心的动态操作包括:
- 重置(Reset):将所有状态变量恢复初始值。
- 更新(Update):执行一个时间步的计算。



对于更细粒度的、由特定事件触发的操作(如某个参数被修改后,只需更新局部数据),则使用回调函数。回调函数允许将一个函数对象“注入”到某个 Field 中,当该 Field 的值发生变化或被访问时,自动触发该函数。




回调函数的应用实例:
- 自动控制可见性:当一个网格生成器的输入端口连接上数据时,自动隐藏前一个节点的渲染模块,避免视觉遮挡。
- 参数归一化:在SPH权重函数参数改变时,自动触发回调函数重新计算归一化因子,确保权重和为1。
- UI同步:当用户在3D视图中拖动物体的操纵器(Gizmo)时,通过回调函数同步更新属性面板中的坐标值,反之亦然。


回调函数有两种主要形式:
- 成员函数回调:绑定某个类的成员函数。
- Lambda表达式回调:更灵活,可以直接捕获上下文变量。


插件系统与设计原则:构建可扩展的生态 📦

为了管理日益增长的代码库并支持协同开发,PeriDyno采用了插件(Plugin) 系统。其核心思想是将一个复杂系统拆分为框架核心和多个独立的功能模块(DLL动态库或静态库)。

插件机制
- 框架端:提供插件管理器,负责加载、管理和调用各个插件。
- 插件端:每个插件是一个独立的库,需要实现一个特定的入口类(如
PluginEntry),并在其中注册该插件提供的所有节点、模块等。




面向贡献者的设计原则
为了保持代码的可维护性和可扩展性,在为PeriDyno贡献代码时,应遵循一些软件设计原则:
- 单一职责原则:一个类只负责一项明确的功能。
- 开闭原则:对扩展开放,对修改关闭。通过继承和组合来扩展功能,而非直接修改原有类。
- 接口隔离原则:节点或模块的输入/输出接口应尽可能精简,只暴露必要的部分。
- 里氏替换原则:子类对象应该能够替换其父类对象,而不影响程序正确性。例如,一个需要三角网格输入的节点,也应该能接受其子类(如四边形网格)的输入。
- 合成复用原则:优先使用对象组合(has-a),而非类继承(is-a)来复用功能。


遵循这些原则有助于降低代码耦合度,使插件系统更健壮,便于多人协作和长期维护。


实战演示:从实体建模到多物理场耦合 🎬


本节将通过一个综合案例,展示如何利用PeriDyno的插件机制,将实体建模、流体仿真、布料仿真等多个模块串联起来,构建一个复杂的耦合场景。

步骤概述:
- 实体建模:使用基础几何体(立方体、球体)和SDF(有向距离场)布尔运算(融合、相交等),构建一个自定义的复杂实体模型。
- 流体耦合:
- 创建流体发射器和求解器。
- 将生成的实体模型作为“体积边界”输入到流体仿真中。
- 运行仿真,观察流体与自定义实体边界的交互效果。
- 布料耦合:
- 移除流体节点,创建一个布料求解器和一个*面网格。
- 将同一实体模型作为“三角网格边界”输入到布料仿真中。
- 运行仿真,观察布料与障碍物碰撞和形变的过程。



这个案例清晰地展示了如何将不同插件(建模、流体、布料)中的节点像搭积木一样组合,快速搭建出多物理场耦合的仿真应用。




动态插件加载:
PeriDyno支持动态插件加载。开发者可以将编译好的算法模块(DLL文件)放入指定插件目录,主程序启动时会自动扫描并加载这些插件,使其功能(如新的节点类型、工具栏按钮)即时可用,极大地增强了框架的扩展性。

总结与作业 📚







本节课我们一起深入学*了PeriDyno框架的插件与功能拓展机制。




- 核心思想:通过C++与蓝图统一、反射和回调函数,降低从研究到应用全流程的协作成本。
- 关键技术:理解了反射如何实现动态对象创建和UI自动生成;掌握了回调函数如何处理运行时事件。
- 系统架构:熟悉了基于插件的可扩展架构,以及面向协同开发的设计原则。
- 综合应用:通过实战案例,看到了如何将独立模块整合为复杂的多物理场仿真场景。



课后作业(建议):
尝试将你自己实现的一个算法模块(或选择一个现有模块进行改造),按照本课所讲的规范进行“插件化”封装。重点练*:
- 使用正确的宏定义暴露必要的类和成员变量。
- 为模块设计合理的输入/输出接口。
- 尝试添加一个简单的回调函数,用于处理某个参数变更后的特定操作。
- 将其编译为插件,并成功加载到PeriDyno主程序中,通过蓝图界面调用它。

通过实践,你将更深刻地体会到引擎框架在整合与复用代码方面的强大能力。

GAMES401-泛动引擎(PeriDyno)物理仿真编程与实践 - P8:Vulkan编程原理及通用并行计算 🚀
概述
在本节课中,我们将学*Vulkan编程的基本原理及其在通用并行计算中的应用。课程将首先介绍Vulkan API的概况和通用并行计算的概念,然后通过一个具体的例子展示如何使用Vulkan编写并行计算程序。由于直接使用原始Vulkan API较为复杂,我们还将探讨如何通过封装来降低编程复杂度,并介绍Vulkan后端如何与PeriDyno框架集成。最后,我们会分享一些前期探索中发现的问题,并展示简单的案例。
Vulkan简介与通用并行计算概念
上一节我们概述了课程内容,本节中我们来看看Vulkan是什么以及通用并行计算的基本概念。
Vulkan最早于2015年的GDC上被提出,并于2016年正式发布。其设计初衷是整合渲染与仿真计算,使用统一的API来处理这两大核心任务,被誉为下一代图形接口,旨在未来替代OpenGL。Vulkan因其更靠*底层硬件,能够提供更高的性能和更直接的GPU控制能力,但同时也带来了更高的编程复杂度。
GPU API的发展伴随着GPU硬件的进步。早期的OpenGL(1992年)主要用于固定功能管线的三维场景处理,不具备可编程能力。随后,微软推出了DirectX,形成了独立于OpenGL的标准。随着可编程需求的增长,DirectX 9引入了HLSL着色语言,OpenGL也引入了GLSL,使得顶点着色器等特定阶段具备了可编程能力。
为了兼顾移动设备,OpenGL ES作为OpenGL的子集被发展出来。然而,早期的图形API主要面向渲染,用其编写复杂的仿真算法代价高昂。2007年CUDA的提出是一个里程碑,它重新定义了GPU的用途,使其能够处理任何与渲染无关的通用计算任务。2009年,为了建立行业标准,提出了专门针对通用并行计算的OpenCL。2014年,Apple推出了旨在融合图形与计算的Metal API。最终,在2016年,旨在统一渲染与计算、并由开源组织维护的Vulkan正式发布。
Vulkan相比其他API更底层,它将更多硬件特性暴露给开发者,这带来了潜在的性能提升,但也显著提高了对开发者的要求和编程的复杂度。
并行计算API的选择考量
在了解了Vulkan的由来后,我们需要思考如何为并行计算任务选择合适的API。以下是CUDA、OpenCL和Vulkan在一些关键方面的对比:
- 混合编译支持:CUDA支持设备端代码与主机端C++代码在同一文件中编译,简化了开发。OpenCL和Vulkan目前不支持,需要维护分离的代码,增加了维护成本。
- 调试工具:复杂的仿真算法需要强大的调试工具支持。目前三者都支持,但CUDA的工具链可能更成熟易用。
- 跨*台能力:CUDA严重依赖NVIDIA硬件,跨*台能力弱。OpenCL和Vulkan的跨*台能力更好,其中Vulkan在PC端和移动端的兼容性最广。
- C++支持:仿真算法逻辑复杂,C++的面向对象封装能极大降低代码复杂度。CUDA和OpenCL支持C/C++,而Vulkan目前主要支持C,需要退回到C的编程规范,且不支持模板等高级特性。
选择时需结合自身需求:若进行特定研究,CUDA最为方便;若需考虑移动端或跨*台,则Vulkan是更好的选择。
使用原始Vulkan API编写并行计算程序
上一节我们讨论了如何选择API,本节我们将尝试使用最原始的Vulkan API来编写一个简单的并行计算程序,以此感受其复杂性。

我们的任务是实现两个数组A和B的求和,并将结果存入数组C。用CUDA实现这个任务对初学者来说可能只需几小时,但用原始Vulkan API实现则困难得多。

Vulkan的执行流程包含多个对象和步骤:
- 创建Instance:应用程序的顶层实例。
- 选择Physical Device:选择执行计算的物理显卡。
- 创建Logical Device:在物理设备上创建逻辑设备,用于实际执行。
- 获取队列:逻辑设备包含图形队列、计算队列和传输队列。计算任务主要使用计算队列。
- 分配内存与Buffer:在GPU上分配数据存储空间。Vulkan的数据传递通常需要通过一个称为“暂存缓冲区”的中转区域。
- 准备计算着色器:编写计算内核代码,例如使用GLSL,并编译成SPIR-V中间字节码。
- 创建管线与描述符集:创建计算管线,并通过描述符集来绑定管线执行所需的具体数据Buffer。
- 记录与提交命令:在命令池中创建命令缓冲区,记录绑定管线、描述符集、分派计算任务等命令,最后将命令缓冲区提交到队列中执行。
即使对于这样一个简单的数组相加任务,使用原始Vulkan API编写的代码也可能长达数百行,且极易出错,调试困难。
降低Vulkan编程复杂度:Catalina封装

面对如此高的复杂度,我们需要寻找方法来简化Vulkan在仿真计算中的使用。PeriDyno框架中集成了一个名为Catalina的Vulkan后端,它通过封装来隐藏底层细节。

Catalina主要引入了以下几个核心结构进行抽象:
- VkSystem:管理Instance、物理设备选择等全局初始化。
- VkContext:管理逻辑设备及其队列。
- VkProgram:对标CUDA的Kernel函数,用于执行计算任务。
- VkVariable:数据抽象基类,其子类包括:
VkDeviceBuffer: 用于分配大的缓存。VkUniform: 用于数据量小且易变的参数。VkConstant: 用于基本不变的常量。
通过这种封装,编写一个Vulkan计算任务的流程被大大简化。例如,实现数组相加的VkProgram声明和调用代码如下所示:
// 声明一个VkProgram (对标CUDA kernel)
VK_PROGRAM_BEGIN(ArrayAdd) // 类似 CUDA 的 __global__ 声明
// 定义输入参数,类似CUDA kernel的参数列表
VK_PARAM(VK_INOUT_BUFFER, float, a);
VK_PARAM(VK_IN_BUFFER, float, b);
VK_PARAM(VK_CONSTANT, int, n);
VK_PROGRAM_END
// 调用VkProgram
ArrayAdd->flush(128, n/128, a, b, n); // 类似CUDA的kernel调用<<<...>>>

这种封装使得主机端代码量从数百行减少到数十行,显著降低了研发成本。此外,Catalina还支持VkMultiProgram将多个Kernel组合记录到同一个命令缓冲区中执行,以优化需要频繁切换Kernel的仿真循环性能。


Vulkan后端与PeriDyno框架的集成


为了保持引擎框架与GPU后端的无关性,PeriDyno在架构上进行了调整。核心思路是引入中间层来屏蔽底层细节。



在计算层面,框架识别的是如Field、Array等抽象数据结构,并不关心其由CUDA还是Vulkan分配。因此,在具体后端与引擎框架之间,通过VArray等中间数据结构提供统一接口,从而避免因接入新后端而调整引擎架构。
在渲染层面,思路类似。渲染引擎JawEngine并不直接与Vulkan或CUDA后端交互,而是通过一个GPUBackend中间层,这使得未来集成其他渲染库(如VTK、Unreal)成为可能。
这种解耦设计使得拓展新的计算或渲染后端变得更加方便。

Vulkan用于仿真计算的挑战与注意事项


尽管Vulkan能力强大,但在用于仿真计算研发时,仍面临一些挑战:
- 数据对齐问题:Vulkan需要开发者显式处理数据结构的字节对齐(通常是16字节),而CUDA编译器会自动处理。对齐不当会导致数据错误。
- 代码重复与维护:由于不支持混合编译,CPU和GPU端需要分别定义相同的数据结构,任何一端的修改都需同步到另一端,容易出错且维护成本高。
- 特性支持限制:Vulkan目前不支持C++、模板,且部分硬件对
float原子操作支持不佳,这对流体等仿真至关重要。 - 厂商实现差异:不同硬件厂商对Vulkan特性的支持千差万别,为实现真正的跨*台,仍需做大量适配和优化工作。
- 性能与易用性权衡:Vulkan更底层,在计算密集度足够高时性能优于CUDA。但对于小规模计算或快速原型开发,其复杂的编程模型可能抵消性能优势。



因此,Vulkan目前更适合用于对性能有极致要求、且计算规模较大的个别计算步骤的加速,而非快速开发复杂的仿真系统。



案例展示与总结








最后,我们通过一个简单的粒子发射与流动的案例,演示了Vulkan后端与PeriDyno蓝图系统的协同工作。该案例验证了从后端对接到引擎蓝图集成的整个流程是可行的。此外,PeriDyno中也提供了基于Vulkan的点、线、面基本渲染模块,以及一些未开源的尝试性案例(如刚体、浅水波、弹性体),证明了Vulkan能够完成多种物理仿真任务,只是研发代价不同。


总结
本节课我们一起学*了Vulkan API的基本原理及其在通用并行计算中的应用。我们了解了Vulkan的设计目标与复杂之处,探讨了如何通过封装来降低其编程复杂度,并介绍了其与PeriDyno框架的集成方式。同时,我们也认识到将Vulkan用于复杂仿真系统研发目前仍面临数据对齐、代码维护、特性支持等多方面的挑战。对于初学者,建议仍以CUDA作为并行计算入门工具,而将Vulkan作为性能优化和跨*台部署时的进阶选择。

GAMES401-泛动引擎(PeriDyno)物理仿真编程与实践 - P9:9. 工程CAE仿真连续介质力学基础 🧮
在本节课中,我们将要学*工程CAE仿真的基础概念,特别是连续介质力学的基本原理。我们将探讨CAE仿真与物理仿真的区别,了解CAE仿真的核心流程、关键技术及其在工业产品开发中的重要作用。
物理仿真与CAE仿真的区别
上一节我们介绍了物理仿真的基本概念,本节中我们来看看它与工程CAE仿真的主要区别。

物理仿真与CAE仿真的区别主要体现在以下几个方面:
- 计算精度:CAE仿真面向工业和工程领域,要求计算精度非常高。其模拟结果需要与物理实验数据高度吻合,例如在汽车碰撞中预测零部件的位移或变形量。
- 计算效率:物理仿真(如游戏)要求实时性。CAE仿真计算量巨大,模拟一秒的物理过程可能需要数小时甚至数天,难以做到实时。
- 结果数据量:物理仿真更关注图形实时渲染。CAE仿真需要记录整个形变过程中的所有数据(如位移、受力、能量),数据量可达数GB甚至数百GB,用于指导工程设计和优化。
- 后处理:CAE仿真有专门的后处理概念,用于分析海量仿真数据。

总结来说,CAE仿真相比物理仿真,计算精度要求更高,计算速度更慢,数据量更大,并且有专门的后处理流程。
什么是CAE仿真?
CAE是计算机辅助工程的缩写。它主要针对实际的工程问题,通过计算机模拟来预测和优化产品的设计与制造性能。
以下是CAE仿真的基本流程:
- 几何建模:使用专业软件建立产品的三维几何模型。
- 网格划分:将几何模型离散化为网格模型。
- 施加载荷与边界条件:定义模型的受力、约束和运动条件。
- 求解分析:使用CAE软件进行仿真计算。
- 结果分析与设计改进:工程师根据仿真数据进行分析,提出产品改进方案。
CAE仿真的核心作用是缩短研发周期、降低开发成本。它可以在产品制造前进行虚拟测试,减少对物理实验的依赖。


例如,在汽车研发中,CAE仿真可以替代车身抗弯/抗扭刚度实验、碰撞实验等,并通过云图直观展示如位移分布等结果,指导工程师进行结构优化。
CAE仿真的作用与发展趋势
CAE技术改变了传统产品开发流程。传统流程是“设计 -> 试制 -> 实验验证 -> 修改设计”的循环。引入CAE后,流程变为“设计 -> CAE仿真验证与优化 -> 试制”,从而在更短的时间内获得更优的设计方案,降低成本。
CAE在汽车工业中的应用非常广泛,包括:
- 环境舒适性评估
- 安全性分析(如碰撞)
- 人机工程学分析
- 结构强度与刚度分析
- 加工工艺分析(如冲压、铸造)
- 虚拟实验(如道路疲劳分析)
- 空气动力学分析

CAE的发展趋势是从辅助设计工具,转变为完全融入产品正向研发流程的核心技术。
CAE分析的关键技术与流程
CAE分析的基本流程分为三大部分:前处理、求解和后处理。
前处理
前处理包括几何建模、网格划分以及载荷与边界条件的施加。
求解器
求解是CAE仿真的核心,根据不同物理问题调用不同的求解器(如固体力学、流体力学、热学等)。一个求解器通常包含以下模块:
- 数值算法与单元理论:如有限元法。
- 材料本构模型:描述材料应力-应变关系,模拟真实物理属性。
- 接触算法:处理物体间的接触与碰撞。
- 连接与约束关系:模拟零件间的装配关系。
- 边界与载荷加载
- 方程求解与并行计算:求解大型方程组,利用CPU/GPU并行提高效率。
后处理
后处理将求解结果可视化,生成云图、流线图、矢量图、曲线等,供工程师分析。
有限元法基础
有限元法是CAE领域求解数学物理问题的一种基本数值方法。它的基本思想是用有限数量的简单单元,去逼*复杂的几何形状和物理系统。
有限元分析的主要步骤包括:结构离散化、单元分析、整体分析。
1. 结构离散化
离散化是将一个连续的几何体分割成有限数量的小单元。例如,一个复杂的曲面可以用许多小三角形或四边形来*似表示。
核心概念:
- 单元:离散后的基本构建块(如线段、三角形、四面体)。
- 节点:单元之间的连接点,承受载荷并具有自由度。
- 自由度:节点可能具有的运动方向,通常包括3个*动和3个转动自由度。

2. 单元分析
在每个单元上建立力学方程进行求解。基本流程如下:
- 由节点位移,通过插值函数得到单元内部各点的位移。
- 由位移得到单元应变。
- 通过材料本构模型,由应变得到单元应力。
- 将单元应力分配到节点上,得到节点力。
3. 整体分析
将各个单元的分析结果(如单元刚度矩阵)组装起来,形成整个离散结构体的系统方程,并进行求解,最终得到整个模型的*似解。

CAE软件的精度、效率与开发现状
精度与效率的*衡
CAE仿真得到的是*似解。计算精度和效率是一对矛盾体。提高精度(如加密网格)会增加计算量,降低效率。工程中通常根据需求在两者间取得*衡。
CAE软件生态
主流CAE软件市场主要由西门子、ANSYS、达索等国外公司占据。国产CAE软件(如曼孚软件、石峰科技等)正在快速发展,但面临技术积累、场景迭代和人才储备等挑战。
CAE软件开发难点在于其涉及复杂的多学科交叉,包括数学、物理、计算机科学和具体工程领域的知识。
CAE软件的基本架构与核心模块
一个CAE软件的基本架构通常包括:
- 前处理模块
- CAD建模与模型接口
- 网格剖分与优化
- 求解器模块
- 通用结构隐式/显式求解器
- 多体动力学求解器等
- 后处理模块
- 结果可视化与渲染
- 支撑技术
- 云计算、并行计算、数学计算库
典型功能模块举例
- 隐式分析模块:用于静力学、模态分析等。涉及多种单元类型(不仅由几何形状区分,更由计算理论区分)和材料本构模型。
- 显式分析模块:用于碰撞、冲击等大变形、瞬态动力学问题。严重依赖接触算法和并行计算(如GPU加速)来提高效率。
- 拓扑优化:通过计算,在满足性能要求下寻找材料的最优分布,实现轻量化设计。

CAE技术的发展趋势与PeriDyno的应用
CAE技术的发展围绕三个“效率”展开:
- 使用效率:推动CAD与CAE的无缝集成,减少模型转换时间。
- 计算效率:大力发展GPU并行、超算等异构计算,加速单次求解。
- 迭代效率:采用重分析等技术,当设计局部修改时,只重新计算受影响区域,大幅提升优化迭代速度。

基于PeriDyno泛动引擎,可以开发增量式、模块化的CAE软件*台,如曼孚SimAI。该*台利用PeriDyno的底层数据管理和GPU/CPU异构并行能力,支持CAE仿真算法的快速研究、流程搭建和应用开发。










本节课中我们一起学*了工程CAE仿真的基础。我们明确了CAE仿真与物理仿真的核心区别,了解了CAE仿真的基本流程、以有限元法为代表的关键技术,以及CAE软件的基本架构和开发难点。最后,我们探讨了CAE技术的发展趋势,并看到了基于PeriDyno*台进行CAE软件开发的可行性与实践。这些知识为我们后续深入CAE仿真编程与实践打下了坚实的基础。
GAMES204-计算成像 课程笔记 P1:Snap Research 的计算成像与摄影研究 👨🔬
在本节课中,我们将学* Snap Research 在计算成像和计算摄影领域的研究工作。课程内容涵盖团队介绍、公司产品背景以及多个前沿研究项目的核心思想与成果。

团队与实验室介绍 🏢
我叫 Jian Wang,是 Snap Research 的高级科学家,研究方向包括计算成像、计算摄影和计算机视觉。我的个人主页是 jianwang-cmu.github.io,微信号是 jianwang8973,欢迎大家交流。

我们的实验室位于纽约时代广场的雅虎大楼内。虽然楼下游客众多,但实*生们似乎很喜欢这个充满活力的环境。公司附*有 Bryant Park、百老汇等著名景点。办公室内没有食堂,但公司为每人提供一张餐卡,可以在周边众多餐馆消费,这也成为了工作的乐趣之一。


实验室配备了机械室和暗房,考虑到纽约高昂的租金,这个空间相当可观。除了多样的餐馆,附*的景点也是我们采集数据的绝佳场所。
我们的团队背景多元,包括:
- 研究科学家:如我和 Si Zhuo,专注于计算成像、摄影和计算机视觉。
- 高级研究工程师:如 Ben,专注于可穿戴设备传感和移动传感。
- 机械工程师:如 Carl。
- 软件工程师、电子工程师、项目经理和顾问。
这种多元化的团队组合有助于激发灵感,并将想法转化为可供用户使用的现实产品。

我们的工作也离不开实*生的努力。每年我们招募约12名来自计算成像、摄影、计算机视觉和人机交互领域的实*生。我们正在招聘2023年的实*生,并为国际学生提供J-1签证支持。选择我们的原因包括:根据学生兴趣定制项目、提供充分指导、给予学术自由,以及有机会产生产品或学术影响。去年,我们的每位实*生都有顶会投稿。


Snap公司核心产品 📱

Snap公司有两款核心产品:手机应用 Snapchat 和增强现实眼镜。

Snapchat 是一款即时通讯应用,全球拥有3.6亿用户,主要面向年轻人。其核心设计理念是“阅后即焚”和“社交无压力”。消息在被阅读后会消失,这模拟了人类自然的对话记忆方式,用户无需担心过去说过的“蠢话”。它的“Stories”(类似朋友圈)功能不支持公开点赞和评论,进一步减少了社交压力。应用一打开就是相机界面,鼓励用户拍照分享,并提供了大量趣味AR滤镜(如兔耳朵、变性别、变年龄等),这些滤镜极大地增加了拍照的乐趣。

Spectacles 是Snap公司行业领先的增强现实眼镜。公司为此投入了大量研发。
围绕这两款产品,我们面临着许多有趣且实际的计算成像和计算摄影问题。计算摄影通常是纯软件的,而计算成像则涉及硬件和软件的协同设计。
本节课将介绍我们在以下两类项目中的研究工作:
- 计算摄影:包括图像修复和图像编辑,用于创建Snapchat中有趣的AR滤镜。
- 计算成像:包括基于闪光灯的低光成像、资源受限的3D传感、透明玻璃检测以及一个偏科幻性质的透明相机研究。


此外,我们的经理 Shree Nayar 在 CCP 2022 大会上做了题为《视觉通信的进展》的主旨报告。视觉通信(通过图片和视频交流)已成为Snapchat上的主要交流方式。如果你对此感兴趣,可以在YouTube搜索CCP频道或访问我的个人主页获取链接。

同时推荐 Shree Nayar 的在线课程 《计算机视觉的第一性原理》。在当今基础模型时代,理解计算机视觉的基础理论对于深度研究至关重要,这门课程讲解得非常出色。


计算摄影:图像修复 🖼️
在计算摄影的图像修复方面,我们将重点介绍三个研究工作。

1. 盲人脸图像复原
第一个工作是盲人脸图像复原。目标是从质量严重退化(如有噪声、模糊、信息丢失)的输入图像中,恢复出高质量的人脸图像。“盲”意味着我们不知道图像具体的退化过程(如噪声多大、模糊核是什么)。

核心方法:利用预训练的人脸生成模型(如 StyleGAN)的先验知识。我们将生成模型“倒转”拟合到退化的输入图像上,从而在生成器的潜空间中找到对应的高质量人脸。


结果:该方法能显著提升图像质量,但存在一个潜在问题:输出的人脸质量虽高,但不一定与输入是同一个人。





2. 闪光灯与非闪光灯图像融合复原



第二个工作旨在融合两张照片:一张是低光环境下关闭闪光灯拍摄的(有噪声但色调自然),另一张是打开闪光灯拍摄的(细节清晰但光照生硬、色调不自然)。




目标:生成一张既保持自然色调,又拥有清晰细节、无噪声的照片。



核心方法:同样基于预训练的人脸生成模型(如 StyleGAN),将两张图像的信息融合到生成器的潜空间中。
结果:生成的图像在颜色上更接*非闪光灯图像的自然感,在细节和清晰度上则接*闪光灯图像,与真实情况(Ground Truth)非常接*。

3. 修复AI生成图像中的人脸
随着生成模型的流行,AI生成的图像有时会出现扭曲的“鬼脸”。我们的目标有两个:修复这些奇怪的人脸;将当前用户的面部特征融合到生成的人脸中。
输入:一张AI生成的带“鬼脸”的图像,一张用户的真实照片。
核心方法:与上一个项目类似,基于人脸生成模型,将两张图像的信息进行融合。
结果:生成的人脸既修复了扭曲部分,又融入了用户的特征(如胡须、鼻子、眼睛、眉毛等)。

计算摄影:图像编辑与滤镜 🎨

在图像编辑方面,我们主要研究用于创建Snap滤镜的技术,这里介绍三个工作。

1. 3D照片风格化

给定一张内容照片和一张风格画作,目标是生成一个具有3D效果且风格化的照片/视频。核心挑战是保证视角一致性,即从不同角度看这个3D内容时,风格化效果稳定、无抖动。

核心方法:使用一个统一的中间表示——点云。我们在点云上施加风格,然后将风格化后的点云投影到每一帧图像中,从而保证各视角间的一致性。


结果:可以生成背景风格化而人物保持原样的有趣效果。

2. 从云到素描
人们看云时常常会联想出各种形状。这个工作旨在自动生成与云朵形状对齐的动物素描。
算法:对于同一朵云,算法可以生成一组不同的、合理的素描结果,模拟不同人的不同想象。



3. 心率滤镜
使用手机前置摄像头拍摄的人脸视频来估计心率。心率是许多其他滤镜(如健身滤镜、健康滤镜)的基础。
难点:人脸运动会严重干扰心率信号。
传统方法:在脸部固定区域取像素均值,分析其随时间的变化(光电容积描记法原理)。人脸不动时,微小的波动对应心率;人脸运动时,会产生巨大干扰。
我们的方法:
- 从输入视频中估计人脸的几何形状、反射率和光照(利用多帧信息联合估计光照,以避免光照信息中包含心跳信号)。
- 用估计出的参数渲染一个仅包含运动信息的虚拟人脸视频。
- 从原始信号中减去这个渲染视频产生的运动信号,得到纯净的心跳信号。
- 对纯净信号进行频域分析,得到准确心率。
结果:即使在人物运动时,我们的方法也能比前人方法更准确地估计心率。

计算成像:硬件软件协同设计 ⚙️
计算成像涉及硬件和软件的联合设计。本节介绍四个工作。
1. 基于闪光灯的低光成像
低光成像方法主要分两类:不使用闪光灯和使用闪光灯。
- 无闪光灯:包括单图去噪/去模糊,以及多帧融合(如手机的“夜景模式”)。
- 有闪光灯:
- 白色闪光灯:颜色不自然。
- 双色温闪光灯:模拟环境光色温,改善白*衡。
- 不可见光闪光灯:使用红外或紫外光,人眼不可见,但需要移除相机上的红外截止滤光片,影响白天成像。
- 立体不可见光系统:使用两个相机,一个标准RGB相机,一个*红外/*紫外相机。两者共享绿色通道,便于对齐和融合。白天仅用RGB相机,晚上双系统融合。但需要增加一个相机。
- 深红色闪光灯:基于人眼生理特性——在暗视觉下,视杆细胞对深红色光不敏感,而相机仍对其敏感。因此,深红色闪光灯对人眼刺激小,同时能被相机良好捕捉。
深红色闪光灯结果:融合闪光灯与非闪光灯图像,能得到细节丰富、颜色正确的照片。

2. 图案化闪光灯
闪光灯的一个根本问题是作用距离有限,因为光强随距离*方衰减。现有方案(增加功率或聚光)在移动设备上不实用或会限制视野。
我们的方法:图案化闪光灯。将均匀的光斑变为一系列高亮的光点。在远距离场景下,这些光点处的信号仍能高于噪声水*。利用图像的稀疏性和冗余性,可以从这些稀疏的强信号中恢复出整个图像。

额外优势:图案化闪光灯与相机可构成一个结构光系统,能够同时估计深度信息。

结果:在相同功率下,图案化闪光灯比传统闪光灯能捕捉到更远的场景细节,并能获取深度图。



3. 资源受限的3D传感

在AR眼镜等设备上实现结构光3D传感面临体积小、功耗低、精度高的挑战。

核心方法:微型基线结构光。当相机和投影仪基线非常小时,可以利用一阶泰勒展开,将非线性的成像过程*似为线性的。这简化了系统求解过程,类似于光流计算中的思想。
成像模型简化:
当基线 b 很小时,I(x) ≈ R(x) * P(x + u(x,y)) 可线性化为 I(x) ≈ R(x) * [P(x) + ∇P(x) · u(x,y)],其中 u(x,y) 是与深度相关的视差。求解 u(x,y) 即可得到深度。

结果:使用基线仅15mm的设备,成功实现了高精度的3D重建。


4. 透明玻璃检测
透明玻璃检测非常困难,但具有重要安全意义(如防止撞上玻璃门)。
我们的方法:基于玻璃的光谱透射特性。玻璃在可见光波段透射率高,但在远红外波段透射率很低。因此,我们使用RGB相机和远红外相机配对。
- 没有玻璃时:两个相机都能看到场景。
- 有玻璃时:远红外相机几乎看不到玻璃后的场景。
- 通过比较两幅图像的差异,即可检测出玻璃。


未来适用性:未来的窗户玻璃仍需满足“视觉透明”和“隔热”两个基本特性,因此我们的方法在未来依然有效。






5. 透明相机(探索性研究)

这是一个偏科幻的概念,旨在设计一个外壳透明的相机。这反直觉的地方在于:如果相机透明,光线如何被传感器接收?外壳的杂散光是否会干扰成像?

解决方案:
- 涂层设计:在传感器和外壳上施加互补(相反)的涂层,使得通过外壳的杂散光不会被传感器捕捉。镜头与传感器上的涂层一致,确保通过镜头的光能被传感器捕捉(后来发现镜头无需涂层也可工作)。
- 传感器镂空:将传感器设计成奇数列为像素、偶数列镂空的样式。宏观上,传感器呈半透明状。
- 图像复原:利用自然图像的空间冗余性,通过图像复原算法从稀疏采样的传感器数据中重建出完整图像。

透明度分析:从不同角度看,相机都呈现一定的透明度。



总结 🎯
本节课我们一起学*了 Snap Research 在计算成像和计算摄影领域的多项前沿工作。
Snap 是一家相机公司,我们相信相机能让人们的生活和交流变得更加有趣。基于公司两大核心产品——拥有数亿用户的 Snapchat 应用和行业领先的 Spectacles AR 眼镜,我们的团队提出了许多既有趣又具有实际意义的研究问题。

在计算摄影方面,我们探讨了图像修复(如盲复原、多图融合、AI人脸修复)和图像编辑(如3D风格化、云朵素描、心率估计)技术,这些是创造有趣AR滤镜的基础。


在计算成像方面,我们深入研究了硬件软件协同设计,包括改进低光成像的深红色与图案化闪光灯、适用于微型设备的3D传感技术、实用的透明玻璃检测方法,以及一个富有想象力的透明相机概念。
今天介绍的工作只是我们研究成果的一部分。未来,更多的工作将在我的个人主页上展示,敬请持续关注。



GAMES204-计算成像 课程笔记 P1:智能光学仪器中的感知与计算 👁️🗨️💡

在本节课中,我们将学*智能光学仪器中的两个核心概念:波前传感与可微分光学。课程内容基于加州大学伯克利分校王崇利博士的分享,涵盖了其博士期间及博士后阶段的前沿研究工作。
概述:波前传感的重要性 🌊
波前描述了光波在传播过程中,相同相位点所构成的曲面。理想的波前是*滑的,但实际光学系统中,由于像差(如大气湍流、透镜缺陷)的存在,波前会发生畸变。测量这种畸变(即波前传感)对于许多领域至关重要。
- 天文学:校正大气湍流对星光成像的影响。
- 显微镜学:获取生物样本的相位信息,揭示其拓扑结构。
- 眼科:测量人眼像差,用于配镜或激光手术。
上一节我们介绍了波前传感的基本概念,本节中我们来看看如何测量波前。
波前传感的传统方法 📐



由于图像传感器只能探测光强,无法直接探测光的相位或传播方向,因此需要特殊的光学设计来间接测量波前梯度。




以下是几种传统的波前传感方法:

- 微透镜阵列:将微透镜阵列置于传感器前。*面波会聚焦成规则点阵,畸变波前则会导致点阵偏移。通过追踪每个子光斑的偏移量,可以反推波前局部梯度。
- 横向剪切干涉:使用周期性光栅等元件对波前进行编码,产生干涉图样。通过分析图样的变化来求解波前,这种方法通常能获得比微透镜阵列更高的横向分辨率。
- 基于光场传输的方法:在不同轴向位置采集多张图像,通过求解光场传输方程来重建波前。



编码波前传感器:一种统一且高效的方案 🔢



尽管方法多样,但存在统一的理论框架来描述它们。一个自然的问题是:是否存在某种“最优”的波前传感器设计?


王崇利博士的研究提出了一种编码波前传感器。其核心思想是:用一块紧贴传感器(距离约1-2毫米)的随机二元编码掩膜板替代传统的周期性微透镜阵列或光栅。这块掩膜板由大量随机分布的透光(1)与不透光(0)的小孔组成。
工作原理:
- 标定:在*行光(理想*面波)照射下,传感器会记录下一幅固定的散斑图样。这是因为掩膜板与传感器距离很*,衍射效应显著。
- 测量:当存在畸变波前时,入射光的方向发生微小改变,导致传感器上的散斑图样整体发生*移。
- 求解:每个像素上散斑的位移量,与该点波前的横向梯度成正比。这本质上是一个光流估计问题。通过计算两幅图像(标定图与畸变图)之间的像素位移场,再对梯度场进行积分,即可重建出完整的波前。


其数学模型可以简化为求解位移场 s:
s = z * λ * ∇φ
其中 z 是掩膜板到传感器的距离,λ 是波长,∇φ 是波前相位梯度。

优势:
- 硬件-软件解耦:掩膜板是随机的,无需精密加工特定周期结构。算法适用于任何随机图案。
- 高效:算法可在GPU上实现实时处理(如50帧/秒)。
- 紧凑:整个装置可以做得非常小巧。


实验展示:该系统成功测量了球面波、热空气湍流引起的动态波前畸变等,并验证了其高精度和实时性。

应用一:自适应光学 🔄

获得波前信息后,最直接的应用就是自适应光学——实时测量并校正光学系统中的像差。


系统工作流程:
- 波前传感器实时测量来自“导星”的畸变波前。
- 控制器计算所需的校正量。
- 校正器(如变形镜)施加反向的波前,补偿畸变。
- 校正后的光波对目标进行成像,获得清晰图像。
研究团队搭建了自适应光学演示系统。使用一个红外波长的引导光进行波前测量(避免在可见光成像中留下痕迹),用空间光调制器作为校正器。实验表明,该系统能有效校正由畸变透镜片引入的像差,恢复图像细节。与纯软件的反卷积方法相比,硬件校正避免了算法假设不成立带来的伪影问题。
应用二:定量相位显微成像 🔬


在显微镜中,许多生物样本是透明的,明场成像对比度低。但如果能获取光的相位信息,就能得到样本的厚度、折射率分布等拓扑结构。
将编码波前传感器作为显微镜的探测器,只需拍摄一张散斑图样,即可同时重建出样本的振幅(吸收信息)和相位信息。
光场 = 振幅 * exp(i * 相位)
进一步应用——数字重聚焦:由于获得了完整的复光场信息,可以在数值上模拟光波传播。这意味着无需物理移动镜头或样本,就能在计算机中实现不同焦*面的图像序列,观察样本的三维结构。


过渡到可微分光学 🤖


上一部分我们看到了如何通过创新的传感器设计解决感知问题。接下来,我们将视角从“感知”转向“计算”,探讨如何利用现代计算工具(特别是自动微分)来优化光学系统本身,这就是可微分光学。




什么是可微分光学? 📈
在计算成像中,我们经常需要解决逆问题:从观测到的图像 y 中,估计我们感兴趣的参数 x(如场景、像差、光学参数)。这通常通过最小化损失函数 L = || f(x) - y || 来实现,其中 f 是正向成像模型。
现代优化(如基于梯度的下降法)和深度学*严重依赖于计算梯度 ∂L/∂x。根据链式法则,这需要知道正向模型 f 的雅可比矩阵 ∂f/∂x。可微分光学的目标就是构建一个完全可微分的正向光学仿真引擎,使得 f 和 ∂f/∂x 都可计算。
好处:
- 便捷的逆问题求解:可直接使用梯度下降法优化
x。 - 硬件-软件协同设计:梯度可以从前端神经网络反向传播到前端光学硬件的设计参数,实现端到端的联合优化。
可微分光线追踪引擎 ⚙️
王崇利博士及其合作者开发了一个可微分的光线追踪引擎。其核心挑战在于:光线与复杂光学曲面的求交计算通常需要迭代算法,若直接用自动微分库包装整个迭代过程,将导致巨大的内存消耗。
关键贡献:他们提出了一种混合方法。先用传统的数值迭代方法(不记录计算图)求解光线-曲面的交点。在得到最终解后,仅对最后一步应用自动微分,手动编码该步骤的梯度。这样既保证了结果的正确性和可微性,又极大地降低了内存开销。
应用展示:
- 透镜设计:优化单透镜、非球面透镜甚至自由曲面的参数,以最小化光斑尺寸或实现特定的光场分布。
- 渲染与色差校正:可微分地渲染通过光学系统的图像,并优化透镜设计来校正色差。
- 系统标定与数字孪生:给定一个实际拍摄的光斑图像,通过优化仿真模型中的参数(如透镜位置、曲率),使仿真结果与实测匹配,从而构建光学系统的“数字孪生”,用于参数标定和误差评估。
该引擎已开源,为光学设计与计算成像研究提供了强大工具。
前沿探索:OzVision——超彩色视网膜投影显示 🎨

最后,王崇利博士分享了其在伯克利参与的一项极具前瞻性的研究:OzVision(或称超彩色视网膜投影显示)。这项研究旨在将计算图形学延伸到人类视觉感知的最终端——视网膜。


核心思想:传统显示器的像素是均匀的,无法控制光在视网膜上具体感光细胞(视锥细胞)的刺激。人类有三种视锥细胞(L, M, S),分别对长、中、短波敏感,它们的响应曲线决定了我们看到的颜色范围(色域)。OzVision 利用自适应光学扫描激光检眼镜,可以:
- 高分辨率成像:实时追踪视网膜上单个视锥细胞的位置和类型。
- 精准刺激:用激光束精确地瞄准并控制照射在每个特定类型视锥细胞上的光强。
革命性效果:通过独立编程对三种视锥细胞的刺激强度组合,可以突破人类视觉的物理限制。例如,仅用一束绿色激光,通过调整对不同细胞的刺激比例,能让受试者“看到”红色、橙色或黄色的图像。这实现了:
- 扩展色域:让人看到传统显示无法呈现的颜色。
- 色盲辅助:理论上能让红绿色盲患者分辨红色和绿色。
这项工作首次在中文*台公开,是连接计算成像、视觉科学和图形学的跨学科前沿探索。
总结 🎯

本节课我们一起学*了智能光学仪器中感知与计算的两个关键方向:

- 波前传感与计算:从传统方法到创新的编码波前传感器,展示了如何通过硬件设计与算法结合,高效感知光波的相位信息,并应用于自适应光学和定量相位显微成像。
- 可微分光学:介绍了如何构建可微分的正向光学模型,从而利用梯度优化实现透镜设计、系统标定和软硬件协同设计,为计算成像提供了强大的基础工具。
- 前沿展望:探讨了OzVision这一颠覆性概念,展示了通过精准控制视网膜层面的光刺激,如何扩展人类视觉感知的边界,开启了计算成像服务于视觉科学的新篇章。
这些内容体现了计算成像领域的深度与广度,从基础原理到尖端应用,从改善现有仪器到创造全新的视觉体验。希望本教程能为初学者提供一个清晰而有趣的入门视角。

课程名称:计算光场与全息显示 | GAMES204-计算成像 - P1
📚 课程概述


在本节课中,我们将学*显示技术中的两个核心概念:计算光场与全息显示。我们将从基本定义出发,探讨它们的工作原理、技术挑战、当前的研究进展以及在未来元宇宙等应用场景中的潜力。课程内容旨在为初学者提供一个清晰、全面的入门指南。
🌟 光场显示:从概念到应用
上一节我们介绍了课程的整体框架,本节中我们来看看光场显示的基本概念。
光场是一个描述通过空间中每一点和每一方向的光线亮度的函数,通常称为全光函数。在图形学中,常简化为用空间中的两个点(u, v, s, t)来表征光线的传播,即4D光场。
以下是光场应用的两个主要方向:
- 光场采集(相机):通过在传感器前放置微透镜阵列,一次性记录来自不同方向的光线信息。后期通过算法处理,可以实现重聚焦、大景深等效果。其核心原理可表示为:
最终图像 = 算法处理(微透镜阵列采集的多视角图像)。 - 光场显示:作为光场采集的逆过程,其目标是为观察者的双眼提供来自不同方向的光线信息,从而产生立体视觉和正确的视觉辐辏调节冲突(VAC)线索。
🔍 光场显示的形态与挑战
了解了光场的基本概念后,我们来看看它的几种实现形态及其面临的挑战。
光场显示有多种形态,例如电视、便携设备和*眼显示设备。其基本原理类似,通常包含可调制的背光和多层液晶显示层,通过算法耦合,使最终出射的光线编码了不同的强度和方向信息。
然而,光场显示面临一个根本性限制:在显示面板总像素固定的情况下,需要在视角和空间分辨率之间进行权衡。这限制了其显示质量的进一步提升。
🌀 迈向波前:全息显示原理
从光场(光线)的描述,我们进一步深入到更本质的波前描述,这就引出了全息显示。
全息术通过干涉记录和衍射再现来完整记录和重建物体光场的波前信息。其过程可概括为八个字:干涉记录,衍射再现。
以下是光学全息的基本步骤:
- 记录过程:一束相干光(如激光)被分束。一束作为参考光,另一束照射物体后成为物光。物光和参考光在感光干板上发生干涉,形成包含物体波前信息的干涉条纹并被记录下来。
- 再现过程:用相同波长的激光(再现光)照射记录好的全息干板。干板上的条纹结构对再现光进行调制,衍射出的光波精确重建了原始物光的波前,从而形成一个可供观察的虚拟三维像。
理论上,全息可以完整编码三维信息,是实现真三维显示的理想途径。
💻 计算全息:算法与优化
既然光学全息能记录真实场景,我们自然希望用计算的方式生成用于动态显示的全息图,这就是计算全息。
计算全息图生成后,通过空间光调制器(SLM)显示。SLM可以控制每个像素点反射或透射光线的相位延迟,从而调制光波。
计算全息图生成主要有两类算法:
- 直接法:例如双相位编码法。将目标图像的复振幅信息通过一次传播计算并编码到纯相位图中。优点是速度快,可实时;缺点是成像质量较低,会产生重影。
- 迭代法:例如经典的GS算法及其变种。通过在目标*面和SLM*面之间多次正向和反向传播迭代,不断更新相位图,最终收敛到一个较优解。优点是成像质量高;缺点是计算慢,难以实时。
🤖 智能融合:神经网络与计算全息
传统的迭代算法在速度与质量上难以兼顾,而现代优化方法,特别是深度学*,为计算全息带来了新的突破。
我们可以将全息图生成过程构建为一个可微分的物理仿真模型,利用梯度下降进行端到端优化。从随机相位开始,通过正向传播计算损失,反向传播更新相位,最终得到高质量的全息图。
然而,仿真与真实设备之间存在差距。我们提出了“相机在环”优化策略,将真实拍摄的图像反馈到优化循环中,让算法自动补偿光学系统的像差等非理想因素,显著提升了真实显示效果。
进一步地,我们可以训练一个神经网络模型来学*这种“相机在环”的优化映射。在训练阶段需要相机参与,但在推理阶段,训练好的模型可以直接快速生成高质量的全息图,实现了速度与质量的*衡。
🌈 超越激光:部分相干光全息
激光作为相干光源会带来散斑噪声,影响图像质量。一个直接的解决方案是使用非相干或部分相干光源,如LED或SLD。
部分相干光全息面临新的挑战:宽光谱会导致图像模糊。我们的研究通过算法预补偿,在计算全息图时就将部分相干光的特性考虑进去,从而在抑制散斑的同时,尽可能保持图像的清晰度。实验表明,使用SLD光源能在保留细节和消除散斑之间取得良好*衡。
🎯 三维全息显示与未来展望
全息的终极目标是为三维显示服务。我们可以将三维场景分解为多个深度层(RGB-D),或者直接使用光场作为输入,通过我们构建的神经网络模型生成相应的三维全息图。
我们已经开发了VR和AR原理样机。在显示中,可以实现逼真的聚焦和离焦效果,其景深效果非常自然,接*真实光学成像。
尽管计算全息与光场显示前景广阔,但目前仍处于实验室阶段,面临眼盒大小、视场角、计算效率等诸多挑战。它们与变焦、多焦面等技术路径是互补和结合的关系。未来,端到端的光学设计优化、全息与波导等新型光学元件的结合,将是重要的研究方向。
📝 课程总结

本节课我们一起学*了计算光场与全息显示的核心知识。我们从光场的定义出发,探讨了光场采集与显示的原理。随后深入到全息显示,讲解了其干涉记录与衍射再现的物理本质。接着,我们详细介绍了计算全息的两类算法、面临的挑战以及如何通过“相机在环”优化和深度学*技术来提升成像质量与速度。最后,我们探讨了使用部分相干光抑制散斑的方法,并展望了三维全息显示的未来发展趋势与挑战。希望本课程能帮助你建立起对这两个前沿显示技术领域的基本理解。

浙公网安备 33010602011771号