Introduction

引言

GAMES201高级物理引擎实战是由胡渊鸣老师教授。本节主要讲述了老师与物理引擎的故事、太极编程语言以及该系列课程内容的介绍。

Physics Engine

“A physics engine is computer software that provides an approximate simulation of certain physical systems, such as rigid body dynamics (including collision detection), soft body dynamics, and fluid dynamics, of use in the domains of computer graphics, video games and film.”

—— https://en.wikipedia.org/wiki/Physics_engine

我们开门见山定义一下什么是物理引擎。在维基百科上可以看到物理引擎是一个计算机软件,它提供对物理系统近似的模拟,比如刚体物理的动力学、软体动力学以及流体动力学。主要的应用是在计算机图形、游戏和电影。

Simulate the world in your computer!

这么长的定义换成一句话说就是在电脑里模拟这个世界。

Application

除了刚才说到的这些应用,它还可以应用在工业软件中,在计算机中完成测试迭代;还有一个很重要的应用是电影的视觉特效,基本每个动画电影都有非常多的镜头用物理效果模拟出来的;虚拟现实和增强现实更不用多说,如果在VR里有一个真实的物理世界,那么沉浸感会增强很多,虚拟世界和现实世界的物理规律是一致的。

当然,大家最关心的物理引擎的用途还是在游戏里面,所以今天就仔细讲一讲游戏。

The Incredible machines

很小的时候就玩过很多游戏,它们构成了我童年的很大一部分,有一个我印象比较深的游戏叫做“不可思议的机器人”。这个游戏你可以摆入各种各样神奇的道具,模拟物理效果。

Angry Birds

愤怒的小鸟也是一款很经典的游戏。你可以通过拖拽弹弓,小鸟撞到游戏里各种障碍物,里面的猪碰到障碍物就会被消灭掉。

Phun (Algodoo)

另外一个更可定制一点的是纯物理引擎,不是游戏。它可以在里面放齿轮、滑轮、棱镜等等,你可以在里面创建一个物理世界。

Besieged

15年的时候又有一款比较有意思的游戏:围攻。你可以自己把各种个样的模块把它组装成一个攻城武器,去攻打各种各样的城池。

My Own Physical Simulation Story

下面我讲讲自己和物理引擎的故事,从初中二年级到博士三年级(2009~2020)。

My first physics engine

上图是09写的第一个物理引擎,其实非常简单,就是一个弹簧质点系统。

My own rigid body simulator

这是高中时候做的,那时学了一点刚体动力学,受到愤怒的小鸟的影响写了一个刚体物理引擎。

My rigid body game

这是大二写的基于物理引擎的游戏。

My fluid simulator

大概16年的时候写了流体的模拟。

My smoke simulator

17年的时候写了烟的模拟。

My continuum + cutting simulator

这是我第一篇SIGGRAPH,做的是切割的模拟,实现了固体和液体显示的耦合。

Differentiable Simulation

最近在搞可微模拟,希望求出初始状况的扰动对结果的影响。

Now

Keywords of this course

Discretization

我们会简单讲讲各种各样的离散化,不会讲的特别深。因为自己是计算机方面的,所以更多会从程序员的角度讲讲怎样直观的理解各种各样的离散化。

Efficient Solvers

我们会讲怎样写有效率的求解器,后面我们会详细介绍。

Productivity

同样我们会讲讲怎么会提高生产力,怎么样用比较短的代码实现一个高效metric。

Performance

当然我们也很在意性能,刚才提到很多模拟我们要等上一天的时间。

Hardware architecture

我们也会简单介绍一下硬件的体系结构,因为如果你要写高性能求解器,需要去了解硬件是什么样子的,否则很难把它全部利用起来。

Data Structures

还有一些复杂的数据结构,太极语言让这些数据结构的使用变得更加简单,后面我们会详细介绍。

Parallelization

我们也会讲讲怎么去做复杂的并行,因为现在并行硬件大行其道,CPU和GPU如果算吞吐量真的是天壤之别,一般能差上一个数量级。

Differentiability

最后我们会讲讲怎么求simulation的导数,做很多有意思的东西。

Taichi Programming Language Introduction

Overview

What is Taichi?

什么是太极呢?太极是一个高性能领域特定语言,它嵌入在python里面,所以它的语法和python非常非常的像,如果你会python,那么可以很快上手太极。

这门语言是专门用于计算机图形学而设计的,我们在设计的时候非常非常注重生产力和可移植性,这两个东西以前在计算机图形学里面是非常难以做到的。这里的生产力不是说游戏引擎,而是针对想研究图形学算法的人。

Getting started

Installation

我们先来看看如何安装。如果你有Python 3.6+可以直接用以下命令安装太极:

python3 -m pip install taichi

三个主流的操作系统都是支持的,太极程序既可以在CPU上运行又可以在GPU上运行(支持CUDA、OpenGL、Apple Metal)。如果你的CPU是一些神奇的配置或Python是3.9以上,那么需要下载太极源代码下来装了。

不同操作系统所支持的后端:

平台CPUCUDAOpenGLMetal
Windows可用可用可用不可用
Linux可用可用可用不可用
Mac OS X可用不可用不可用可用

(可用: 该系统上有最完整的支持;不可用: 由于平台限制,我们无法实现该后端)

在参数 arch=ti.gpu 下,Taichi 将首先尝试在 CUDA 上运行。如果你的设备不支持 CUDA,那么 Taichi 将会转到 Metal 或 OpenGL。如果所在平台不支持 GPU 后端(CUDA、Metal 或 OpenGL),Taichi 将默认在 CPU 运行。

这里我选择直接在Pycharm中安装太极:

Initialization

初始化太极非常重要,每当你运行一个太极程序的时候总是调用一下:

import taichi as ti

ti.init(arch=ti.cuda)

其中arch可以选择在什么硬件上跑,建议大家使用ti.cputi.gpu

Data

Data types

太极里支持的数据类型和C非常像,它有8、16、32、64位的整型,整型里又分为带符号的和不带符号的,还有32、64位的浮点数类型。

目前太极不支持bool类型:truefalse,所有的比较返回值都是用整型存储。当然并不是所有的后端都支持所有的类型,因为比如OpenGL可能不支持64位的整数和浮点数,文档中有一个表详细写了每个后端支持什么样的数据类型。

Tensors

接下来我们介绍数据里最重要的概念Tensors,一般来讲可以认为张量就是高维数组。你可以认为0维张量就是标量,1维的张量就是向量,2维的张量是一个矩阵,在太极中张量和矩阵会严格区分,是两个完全不一样的概念。

import taichi as ti
ti.init()

a = ti.var(dt=ti.f32, shape=(42, 63)) # A tensor of 42x63 scalars
b = ti.Vector(3, dt=ti.f32, shape=4) # A tensor of 4x 3D vectors
C = ti.Matrix(2, 2, dt=ti.f32, shape=(3, 5)) # A tensor of 3x5 2x2 matrices

loss = ti.var(dt=ti.f32, shape=()) # A (0-D) tensor of a single scalar

a[3, 4] = 1
print('a[3, 4] = ', a[3, 4]) # "a[3, 4] = 1.000000"

b[2] = [6, 7, 8]
print('b[0] = ', b[0][0], b[0][1], b[0][2]) # print(b[0]) is not yet supported

loss[None] = 3
print(loss[None]) # 3

  • a是一个标量的张量,这个张量有 42x63 个元素,每一个元素是一个标量
  • b是一个向量的张量,这个张量由4个3D vector组成的张量
  • C是一个张量的矩阵,这个矩阵有 3x5 个元素,每一个元素是 2x2 的矩阵
  • lossshape为空,说明它是一个0D的张量,也就是一个元素的张量:标量

不难看出,shape是这个张量的真正维度,前面的数字表述的是这个量里面的每个元素的维度。

Computation

Kernels

计算里最重要的概念是kernel,太极中kernel是用于计算的函数。

太极kernel中自己有一个语言,这个语言和Python非常非常像,唯一的区别是这个语言会被即时编译的。太极自带一个编译器把kernel里面的语言编译成高性能kernel,这样就能摆脱Python运行很慢的问题。

Functions

太极的func可以被kernel调用,但不可以被Python调用。

Scalar math

Matrices and linear algebra

物理模拟很多时候要用到线性代数,ti.Matrix是用来做小矩阵的。如果有很大的阶数需要考虑要不要用tensors而不是数组,因为太极中的一些机制会导致大数组非常慢,但小数组就会非常非常快。

Parallel for-loops

接下来我们来讲一讲并行for循环。太极里有两种for循环:

  • Range-for loops 和Python的for循环没有区别
  • Struct-for loops 遍历稀疏张量里所有的元素
Range-for loops

要注意的是我们只会自动并行最外层的for循环,如果在for前面嵌套一个if就不会被自动并行,被认为是一个Serial,做过并行编程的同学应该很容易理解。

Struct-for loops

import taichi as ti
ti.init(arch=ti.gpu)

n = 320
pixels = ti.var(dt=ti.f32, shape=(n * 2, n))

@ti.kernel
def paint(t: ti.f32):
for i, j in pixels:
pixels[i, j] = i * 0.001 + j * 0.002 + t

paint(0.3)

我们定义了一个叫做pixelstensors,每一个元素是32位的浮点数,shape为 640x320。

Atomic Operations

并行中很多时候需要原子操作,在太极里面例如x[i]+=1自动是原子操作。

Taichi-scope v.s. Python-scope

  • Taichi-scope 任何被ti.kernelti.func修饰的函数
  • Python-scope 任何不在Taichi-scope的代码
Playing with tensors in Taichi-scope

import taichi as ti
ti.init()

a = ti.var(dt=ti.f32, shape=(42, 63))
b = ti.Vector(3, dt=ti.f32, shape=4)
C = ti.Matrix(2, 2, dt=ti.f32, shape=(3, 5))

@ti.kernel
def foo():
a[3, 4] = 1
print('a[3, 4] = ', a[3, 4])

b[2] = [6, 7, 8]
print('b[0] = ', b[0], ' b[2] = ', b[2])

C[2, 1][0, 1] = 1
print('C[2, 1] = ', C[2, 1])

foo()
Phases of a Taichi program

Putting everything together

import taichi as ti
ti.init(arch=ti.gpu)

n = 320
pixels = ti.var(dt=ti.f32, shape=(n * 2, n))

@ti.func
def complex_sqr(z):
return ti.Vector([z[0]**2 - z[1]**2, z[1] * z[0] * 2])

@ti.kernel
def paint(t: ti.f32):
for i, j in pixels:
c = ti.Vector([-0.8, ti.cos(t) * 0.2])
z = ti.Vector([i / n - 1, j / n - 0.5]) * 2
iterations = 0
while z.norm() < 20 and iterations < 50:
z = complex_sqr(z) + c
iterations += 1
pixels[i, j] = 1 - iterations * 0.02

gui = ti.GUI("Julia Set",res=(n * 2, n))

for i in range(1000000):
paint(i * 0.03)
gui.set_image(pixels)
gui.show()

Debugging

Debug mode

下面是怎么调试太极陈谷。在初始化时设置debug = True时会做一些额外的检查,比如图中的数组越界。

Thank you!