Sejak kelahiran bahasa pengaturcaraan Python, falsafah terasnya adalah untuk memaksimumkan kebolehbacaan dan kesederhanaan kod. Pencarian Python untuk kebolehbacaan dan kesederhanaan hampir gila. Satu fakta boleh mengesahkan ini: selagi anda memasukkan arahan "import ini" dalam direktori akar sistem Python dan menekan kekunci Enter, puisi Inggeris kecil akan dicetak serta-merta, yang diterjemahkan ke dalam bahasa Cina secara kasar bermaksud:
“Cantik lebih baik daripada hodoh, tersurat lebih baik daripada tersirat.
Sederhana lebih baik daripada kompleks, kompleks lebih baik daripada rumit.
Leper lebih baik daripada bersarang, jarang lebih baik daripada padat.
Kebolehbacaan adalah penting...”
Kesederhanaan adalah lebih baik daripada kerumitan, dan kebolehbacaan adalah penting. Tidak dinafikan bahawa Python sememangnya sangat berjaya dalam mencapai matlamat ini: ia adalah bahasa yang paling mesra pengguna untuk dipelajari, dan program Python biasa biasanya 5 hingga 10 kali lebih pendek daripada kod C++ yang setara. Malangnya, ada tangkapan: Kesederhanaan Python datang dengan mengorbankan prestasi Malah, program Python adalah 10 hingga 100 kali lebih perlahan daripada rakan sejawat C++ mereka. Oleh itu, nampaknya terdapat pertukaran kekal antara kelajuan dan kesederhanaan, dan adalah mustahil untuk mana-mana bahasa pengaturcaraan mempunyai kedua-duanya.
Tetapi jangan risau, semua harapan tidak hilang.
Bahasa pengaturcaraan Taichi ialah percubaan untuk memanjangkan bahasa pengaturcaraan Python dengan struktur yang menyokong pengkomputeran tujuan umum dan berprestasi tinggi. Ia menyokong pembenaman lancar ke dalam Python, sambil memanfaatkan semua kuasa pengkomputeran dalam komputer anda - termasuk keupayaan CPU berbilang teras dan, lebih penting lagi, prestasi GPU.
Dalam artikel ini kami akan menunjukkan contoh program yang ditulis menggunakan Taichi. Program ini menggunakan GPU untuk melakukan simulasi fizik masa nyata bagi sehelai kain yang jatuh pada sfera, sambil memberikan hasilnya pada masa yang sama.
Menulis simulator fizik GPU masa nyata bukanlah tugas yang mudah, tetapi kod sumber Taichi yang melaksanakan rutin ini sangat mudah. Selebihnya artikel ini akan membimbing anda melalui keseluruhan pelaksanaan supaya anda dapat merasakan apa yang ditawarkan oleh Taichi, dan betapa hebatnya dan mesra penggunanya.
Sebelum kita mula, anda juga boleh meneka berapa banyak baris kod yang mengandungi program ini. Sudah tentu, anda akan menemui jawapannya di penghujung artikel.
Program kami akan memodelkan sehelai kain sebagai sistem spring-jisim. Lebih khusus lagi, kami mewakili kain ini sebagai grid N×N jisim titik, di mana titik bersebelahan disambungkan oleh spring. Rajah di bawah, yang disediakan oleh Matthew Fisher dari Universiti Stanford, menggambarkan struktur ini.
Pergerakan sistem spring jisim ini dipengaruhi oleh 4 faktor:
Untuk kesederhanaan, kami tidak mengendahkan perlanggaran diri kain. Program kami bermula pada t=0. Kemudian, pada setiap langkah simulasi, ia memajukan masa dengan dt pemalar kecil. Program ini menganggarkan perkara yang akan berlaku kepada sistem dalam tempoh masa yang kecil ini dengan menilai pengaruh setiap satu daripada 4 faktor yang dinyatakan di atas, dan mengemas kini kedudukan dan halaju setiap titik jisim pada penghujung langkah masa. Kedudukan zarah yang dikemas kini kemudiannya digunakan untuk mengemas kini imej yang diberikan pada skrin.
Walaupun Taichi ialah bahasa pengaturcaraan yang tersendiri, ia wujud sebagai pakej Python, yang boleh dipasang dengan hanya menjalankan pip install Taichi.
Untuk menggunakan Taichi dalam program Python, anda perlu mengimport Taichi menggunakan alias ti:
import taichi as ti
Prestasi program Taichi akan dimaksimumkan jika mesin anda mempunyai GPU Nvidia dengan CUDA tahap sokongan permainan. Jika ini berlaku, tambah baris kod berikut selepas pernyataan import di atas:
ti.init(arch=ti.cuda)
Jika anda tidak mempunyai GPU CUDA, Taichi masih boleh diakses melalui API grafik lain seperti ti. metal, ti.vulkan dan ti .opengl) untuk berinteraksi dengan GPU anda. Walau bagaimanapun, sokongan Taichi untuk API ini tidak menyeluruh seperti sokongannya untuk CUDA. Jadi, buat masa ini, kami menggunakan CPU sebagai bahagian belakang pengiraan:
ti.init(arch=ti.cpu)
Jangan risau, Taichi akan berjalan dengan sangat pantas walaupun ia hanya berjalan pada CPU. Selepas memulakan Taichi, kita boleh mula mengisytiharkan struktur data yang digunakan untuk menerangkan kain spring jisim. Untuk melakukan ini, kami menambah baris kod berikut:
N = 128 x = ti.Vector.field(3, float, (N, N)) v = ti.Vector.field(3, float, (N, N))
Tiga baris ini mengisytiharkan x dan v sebagai tatasusunan dua dimensi bersaiz N×N, di mana setiap elemen tatasusunan ialah tiga dimensi vektor nombor titik terapung. Di Taichi, tatasusunan dipanggil "medan" dan kedua-dua medan merekodkan kedudukan dan halaju jisim titik masing-masing. Ambil perhatian bahawa jika anda memulakan Taichi untuk dijalankan pada GPU CUDA, medan/tatasusunan ini akan disimpan secara automatik dalam memori GPU. Selain kain, kita juga perlu menentukan bola di tengah:
ball_radius = 0.2 ball_center = ti.Vector.field(3, float, (1,))
Di sini, bahagian tengah bola ialah medan 1D bersaiz 1, yang komponen individunya ialah vektor apungan 3D. Selepas mengisytiharkan medan yang diperlukan, mari kita mulakan medan ini dengan data yang sepadan pada t=0. Kami ingin memastikan bahawa, untuk mana-mana pasangan titik bersebelahan pada baris atau lajur yang sama, jarak antara mereka adalah sama dengan cell_size=1.0/N. Ini dicapai dengan rutin pemula berikut:
def init_scene(): for i, j in ti.ndrange(N, N): x[i, j] = ti.Vector([i * cell_size, j * cell_size / ti.sqrt(2), (N - j) * cell_size / ti.sqrt(2)]) ball_center[0] = ti.Vector([0.5, -0.5, 0.0])
Di sini, anda tidak perlu risau tentang makna di sebalik setiap nilai x[i,j] - ia hanya dipilih supaya kain itu sejajar pada 45 darjah Di bawah sudut, rujuk gambar di bawah.
在每个时间步中,我们的程序都会模拟影响布料运动的4个因素:重力、弹簧内力、阻尼和与红球的碰撞。其中,重力是最容易处理的。
下面是实现这一点的代码:
@ti.kernel def step(): for i in ti.grouped(v): v[i].y -= gravity * dt
这里有两点需要注意。首先,语句for i in ti.grouped(x)意味着将循环迭代x的所有元素,而不管x中有多少维度。其次,也是最重要的是:注解@ti.kernel意味着Taichi将自动并行运行函数中的任何顶级for循环。在本例中,Taichi将并行更新v中每个N*N向量的y分量。
接下来,我们来处理弦线的内力计算问题。首先,请注意前面图形中的每个质点最多连接到八个邻接质点。这些连接在我们的程序中表示如下:
links = [[-1, 0], [1, 0], [0, -1], [0, 1], [-1, -1], [1, -1], [-1, 1], [1, 1] links = [ti.Vector(v) for v in links]
从物理角度来看,系统中的每个弹簧s都用固定长度l(s,0)初始化。在任何时间t,如果s的当前长度l(s,t)超过l(s,0),则弹簧将在其端点上施加力,将它们拉在一起。相反,如果l(s,t)小于l(s,0),则弹簧会将端点彼此推开。这些力的大小始终与l(s,0)-l(s,0)的绝对值成正比。此交互由以下代码段捕获:
for i in ti.grouped(x): force = ti.Vector([0.0,0.0,0.0]) for d in ti.static(links): j = min(max(i + d, 0), [N-1,N-1]) relative_pos = x[j] - x[i] current_length = relative_pos.norm() original_length = cell_size * float(i-j).norm() if original_length != 0: force +=stiffness * relative_pos.normalized() * (current_length - original_length) / original_length v[i] +=force * dt
请注意,这个for循环仍应作为substep函数中的顶级for循环,该函数用@ti.kernel注解。这样可以确保并行计算施加到每个质点的弹簧力。stiffness在此是一个常数,用于控制弹簧长度变化的程度。在上述程序中,我们使用stiffness =1600指定它的值。在现实世界中,当弹簧振动时,弹簧中储存的能量会消散到周围环境中,其振动最终停止。为了捕捉这种效应,在每个时间步,我们稍微降低每个点的速度大小:
for i in ti.grouped(x): v[i] *= ti.exp(-damping * dt)
在此,damping取固定值2。
我们还需要处理布料和红球之间的碰撞。要做到这一点,我们只需将质点与球接触时的速度降低到0。这样可以确保布料“挂”在球上,而不是穿透球或向下滑动:
if (x[i]-ball_center[0]).norm() <= ball_radius: v[i] = ti.Vector([0.0, 0.0, 0.0])
最后,我们用每个质点的速度更新其自身的位置:
x[i] += dt * v[i]
这就是我们对一块质量弹簧布料进行并行模拟所需的全部代码。
我们将使用Taichi内置的基于GPU的GUI系统(昵称是“GGUI”)渲染布料。GGUI使用Vulkan图形API进行渲染,因此请确保您的计算机上安装了Vulkan(https://docs.taichi.graphics/lang/articles/misc/ggui)。GGUI支持渲染两种类型的3D对象:三角形网格和粒子。在我们的示例中,将把布料渲染为三角形网格,把红色球渲染为单个粒子。
GGUI表示一个三角形网格,包含两个Taichi场:一个顶点(vertices)场和一个索引(indices)场。顶点场是一个一维场,其中每个元素提取是一个表示顶点位置的三维向量,可能由多个三角形共享。在我们的应用程序中,每个点质量都是一个三角形顶点,因此我们可以简单地将数据从x复制到vertices:
vertices = ti.Vector.field(3, float, N * N) @ti.kernel def set_vertices(): for i, j in ti.ndrange(N, N): vertices[i * N + j] = x[i, j]
请注意,每一帧都需要调用set_vertices,因为顶点位置不断被模拟更新。
我们的布料是用一个质点的N×N网格表示,也可以被看作一个由(N-1)×(N-1)小正方形组成的网格。每个正方形都将渲染为两个三角形。因此,总共有(N-1)×(N-1)×2个三角形。每个三角形将在顶点场中表示为3个整数,该场记录顶点场中三角形顶点的索引。以下代码片段捕获了这一结构:
num_triangles = (N - 1) * (N - 1) * 2 indices = ti.field(int, num_triangles * 3) @ti.kernel def set_indices(): for i, j in ti.ndrange(N, N): if i < N - 1 and j < N - 1: square_id = (i * (N - 1)) + j #正方形的第一个小三角形 indices[square_id * 6 + 0] = i * N + j indices[square_id * 6 + 1] = (i + 1) * N + j indices[square_id * 6 + 2] = i * N + (j + 1) #正方形的第二个小三角形 indices[square_id * 6 + 3] = (i + 1) * N + j + 1 indices[square_id * 6 + 4] = i * N + (j + 1) indices[square_id * 6 + 5] = (i + 1) * N + j
请注意,与函数set_vertices不同,函数set_indices只需要调用一次。这是因为三角形顶点的索引实际上并没有改变——只是位置在改变。
为了将红球渲染为粒子,我们实际上不需要准备任何数据,我们之前定义的ball_center和ball_radius变量就是GGUI所需要的全部内容。
至此,我们已经介绍完本文示例程序的所有核心函数!下面代码展示了我们如何调用这些函数:
init() set_indices() window = ti.ui.Window("Cloth", (800, 800), vsync=True) canvas = window.get_canvas() scene = ti.ui.Scene() camera = ti.ui.make_camera() while window.running: for i in range(30): step() set_vertices() camera.position(0.5, -0.5, 2) camera.lookat(0.5, -0.5, 0) scene.set_camera(camera) scene.point_light(pos=(0.5, 1, 2), color=(1, 1, 1)) scene.mesh(vertices, indices=indices, color=(0.5, 0.5, 0.5), two_sided = True) scene.particles(ball_center, radius=ball_radius, color=(0.5, 0, 0)) canvas.scene(scene) window.show()
需要注意的一个小细节是,我们将在主程序循环中的每一帧调用函数step()30次,而不是调用一次。这样做的目的就是让动画不会运行得太慢。把上述所有代码放在一起,整个程序应该是这样的:
import taichi as ti ti.init(arch=ti.cuda) # 另一种可选择方案: ti.init(arch=ti.cpu) N = 128 cell_size = 1.0 / N gravity = 0.5 stiffness = 1600 damping = 2 dt = 5e-4 ball_radius = 0.2 ball_center = ti.Vector.field(3, float, (1,)) x = ti.Vector.field(3, float, (N, N)) v = ti.Vector.field(3, float, (N, N)) num_triangles = (N - 1) * (N - 1) * 2 indices = ti.field(int, num_triangles * 3) vertices = ti.Vector.field(3, float, N * N) def init_scene(): for i, j in ti.ndrange(N, N): x[i, j] = ti.Vector([i * cell_size , j * cell_size / ti.sqrt(2), (N - j) * cell_size / ti.sqrt(2)]) ball_center[0] = ti.Vector([0.5, -0.5, -0.0]) @ti.kernel def set_indices(): for i, j in ti.ndrange(N, N): if i < N - 1 and j < N - 1: square_id = (i * (N - 1)) + j # 1st triangle of the square indices[square_id * 6 + 0] = i * N + j indices[square_id * 6 + 1] = (i + 1) * N + j indices[square_id * 6 + 2] = i * N + (j + 1) # 2nd triangle of the square indices[square_id * 6 + 3] = (i + 1) * N + j + 1 indices[square_id * 6 + 4] = i * N + (j + 1) indices[square_id * 6 + 5] = (i + 1) * N + j links = [[-1, 0], [1, 0], [0, -1], [0, 1], [-1, -1], [1, -1], [-1, 1], [1, 1]] links = [ti.Vector(v) for v in links] @ti.kernel def step(): for i in ti.grouped(x): v[i].y -= gravity * dt for i in ti.grouped(x): force = ti.Vector([0.0,0.0,0.0]) for d in ti.static(links): j = min(max(i + d, 0), [N-1,N-1]) relative_pos = x[j] - x[i] current_length = relative_pos.norm() original_length = cell_size * float(i-j).norm() if original_length != 0: force +=stiffness * relative_pos.normalized() * (current_length - original_length) / original_length v[i] +=force * dt for i in ti.grouped(x): v[i] *= ti.exp(-damping * dt) if (x[i]-ball_center[0]).norm() <= ball_radius: v[i] = ti.Vector([0.0, 0.0, 0.0]) x[i] += dt * v[i] @ti.kernel def set_vertices(): for i, j in ti.ndrange(N, N): vertices[i * N + j] = x[i, j] init_scene() set_indices() window = ti.ui.Window("Cloth", (800, 800), vsync=True) canvas = window.get_canvas() scene = ti.ui.Scene() camera = ti.ui.make_camera() while window.running: for i in range(30): step() set_vertices() camera.position(0.5, -0.5, 2) camera.lookat(0.5, -0.5, 0) scene.set_camera(camera) scene.point_light(pos=(0.5, 1, 2), color=(1, 1, 1)) scene.mesh(vertices, indices=indices, color=(0.5, 0.5, 0.5), two_sided = True) scene.particles(ball_center, radius=ball_radius, color=(0.5, 0, 0)) canvas.scene(scene) window.show()
注意到,上述代码总行数仅有91行!
我希望你喜欢本文中提供的上述示例程序!如果的确如此,下面几个不同挑战等级的任务留给你:
Akhir sekali, mari kita semak perkara yang dibenarkan oleh Taichi untuk kita laksanakan dalam 91 baris kod Python di atas: Sistem spring jisim dengan 10,000 titik jisim dan kira-kira 100,000 spring.
halaman Github
, di mana! anda boleh mendapatkan dokumentasi terperinci dan banyak contoh projek Taichi, semuanya menarik. Akhir sekali, jika anda juga percaya dengan misi membangunkan bahasa yang mesra dan berkuasa untuk pengkomputeran selari, anda amat dialu-alukan untuk menyertai Taichi sebagai penyumbang sumber terbuka. Tajuk asal: Panduan Pemula untuk Pengkomputeran Berprestasi Tinggi dalam Python, pengarang: Dunfan LuAtas ialah kandungan terperinci Panduan pengenalan kepada pengkomputeran berprestasi tinggi dengan Python berdasarkan Taichi. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!