一直想用Taichi实现一个BarnsleyFern巴恩斯利蕨的分形程序,但开始的时候不是很明白Taichi的机制,故而踩了不少坑,主要是想用 ti.Matrix()
的@
方法实现矩阵相乘,但调试一直出错.帖子最后会说一下个人的经验,大佬略过就好…
另外,论坛中已经有同学实现了BarnsleyFern的程序here,我主要作了如下改动:
- 将矩阵乘法和加法用
ti.Matrix()
和ti.Vector()
包装了一下 - 不同于原作者随着时间"舞动"的BarnsleyFern,本程序展现了BarnsleyFern生成的过程
- 加入另外两只BarnsleyFern,调整了其颜色和相位差
code
import taichi as ti
import math
import time
ti.init(arch=ti.gpu)
width_one = 660
height = 1000
width = width_one * 3
pixels = ti.var(dt=ti.f32, shape=(width, height, 3))
next_point = ti.Vector(2, dt=ti.f32, shape=(1,))
mat = ti.Matrix(2, 2, dt=ti.f32, shape=(4,))
vec = ti.Vector(2, dt=ti.f32, shape=(4,))
@ti.func
def generatePoint(x, y, t):
r = ti.random()
i = 0
next_point[0] = ti.Vector([[x], [y]])
mat[0] = ti.Matrix([[0.0, 0.0], [0.0, 0.16]])
mat[1] = ti.Matrix([[0.85, 0.04+t], [-0.04-t, 0.85]])
mat[2] = ti.Matrix([[0.20+t, -0.26-t], [0.23+t, 0.22-t]])
mat[3] = ti.Matrix([[-0.15, 0.28], [0.26, 0.24]])
vec[0] = ti.Vector([[0.0], [0.0]])
vec[1] = ti.Vector([[0.0], [1.6]])
vec[2] = ti.Vector([[0.0], [1.6]])
vec[3] = ti.Vector([[0.0], [0.44]])
if(r < 0.01):
i = 0
elif(r < 0.86):
i = 1
elif(r < 0.93):
i = 2
else:
i = 3
next_point[0] = mat[i] @ next_point[0] + vec[i]
return next_point[0][0, 0], next_point[0][1, 0]
@ti.kernel
def drawPoint(t: ti.f32, n: ti.i32, c: ti.i32):
for i in range(0, 100000):
x = 0.0
y = 0.0
for j in range(0, n):
x, y = generatePoint(x, y, t)
if c == 1:
pixels[(int)((x/6+0.5)*width_one), (int)(y*height/12), 0] += 0.01
pixels[(int)((x/6+0.5)*width_one), (int)(y*height/12), 1] += 0.02
pixels[(int)((x/6+0.5)*width_one), (int)(y*height/12), 2] += 0.005
elif c == 2:
pixels[(int)((x/6+1.5)*width_one), (int)(y*height/12), 0] += 0.01
pixels[(int)((x/6+1.5)*width_one), (int)(y*height/12), 1] += 0.01
pixels[(int)((x/6+1.5)*width_one), (int)(y*height/12), 2] += 0.005
elif c == 3:
pixels[(int)((x/6+2.5)*width_one), (int)(y*height/12), 0] += 0.01
pixels[(int)((x/6+2.5)*width_one), (int)(y*height/12), 1] += 0.005
pixels[(int)((x/6+2.5)*width_one), (int)(y*height/12), 2] += 0.01
gui = ti.GUI("BarnsleyFern", (width, height))
timer = 0.0
while(True):
pixels_fresh = pixels
for c in range(1, 4):
for i in range(0, 20):
time.sleep(0.1)
drawPoint(math.sin(timer)*0.03, i, c)
pixels_img = pixels.to_numpy()
gui.set_image(pixels_img)
gui.show()
# gui.show(f'frame/{ts:04d}.png')
if i == 40:
pixels_fresh = pixels
pixels = pixels_fresh
time.sleep(1)
timer += 2.4
pixels.fill(0)
个人心得
众所周知,ti.Matrix()
有两种 Declaration方式,一种是t = ti.Matrix([[a,b], [c,d]])
,另一种是t = ti.Matrix(2, 2, dt=ti.f32, shape=(4,1)
,如果在ti.kernel
中需要对之前声明的Matrix进行赋值等修改操作,则一定得选择后面那种 Declaration的方式(猜测是因为Taichi不允许变量在Taichi-Scope中Declaration,后面那种其实并没有改变变量所指向的地址,而只是改变了其中的元素),另外也验证了ti.Vector
真的是ti.Matrix
的特例,在维度相同的情况下,可以混合使用.
总之,学会了很多,感谢Taichi开发组成员,感谢GAMES201高级物理引擎实战课程.希望大家共同进步!