Homework0: MultiBarnsleyFern

一直想用Taichi实现一个BarnsleyFern巴恩斯利蕨的分形程序,但开始的时候不是很明白Taichi的机制,故而踩了不少坑,主要是想用 ti.Matrix()@方法实现矩阵相乘,但调试一直出错.帖子最后会说一下个人的经验,大佬略过就好…

另外,论坛中已经有同学实现了BarnsleyFern的程序here,我主要作了如下改动:

  1. 将矩阵乘法和加法用ti.Matrix()ti.Vector()包装了一下
  2. 不同于原作者随着时间"舞动"的BarnsleyFern,本程序展现了BarnsleyFern生成的过程
  3. 加入另外两只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高级物理引擎实战课程.希望大家共同进步!

6 Likes