Homework 0: 分形山脉

参考了一些有趣的shadertoy,实现了一个无限的分形山脉。
就是跑起来不是很快,metal后端不到10帧。有条件的建议用CUDA或者OpenGL跑一跑,可以在评论区分享一下效果。

video

这个程序需要使用一张纹理图作为生成山形的数据。


论坛可能会压缩图片,建议去 github仓库 下载原始文件。
纹理图片需要放在与程序文件相同目录下才能让程序运行。

分形的思想很简单,生成一层山体,然后在其上堆叠一层更小的山体,如此几次,实现更多细节。

当然还存在一些问题,山体上有很多毛刺,也能看到阶梯状的纹理。这是因为程序里只实现了双线性纹理过滤,导致纹理采样不够平滑。

如果使用三线性纹理过滤,这个问题会得到很好的解决。但是三线性纹理过滤写起来太繁琐了,还需要实现MipMap,我就不写了。

代码:

import taichi as ti
import numpy as np
import time
from PIL import Image

ti.init(arch=ti.gpu)

w, h = 800, 450
screen = ti.Vector(3, dt=ti.f32, shape=(w, h))

count_time = ti.var(ti.f32, shape=())

# 加载纹理
image = np.array(Image.open('texture.jpg'), dtype=np.float32)
image /= 256.0
tw, th = image.shape[0:2]
texture = ti.Vector(3, dt=ti.f32, shape=(tw, th))
texture.from_numpy(image)

count_time[None] = 0.0


# 双线性纹理过滤
@ti.func
def texture_bilinear_filter(u, v):
    u %= 1.0
    v %= 1.0
    u, v = u * tw, v * th
    left, right = int(u), int(u) + 1
    bottom, top = int(v), int(v) + 1
    t = u - left
    s = v - bottom
    col = ti.Vector([0.0, 0.0, 0.0])
    col = (1-t)*((1-s)*texture[left,bottom] + s*texture[right,bottom]) + \
            t*((1-s)*texture[left,top] + s*texture[right,top])
    return col


@ti.kernel
def draw():
    count_time += 0.1
    for i, j in screen:
        col = ti.Vector([0.0, 0.0, 0.0])
        p = ti.Vector([i / float(w), j / float(h), 1.0]) - 0.5
        d = ti.Vector([i / float(w), j / float(h), 1.0]) - 0.5
        p.z += count_time * 80.0
        d.y -= 0.4

        k = 1.5
        while k > 0.0:
            t = ti.Vector([0.0, 0.0, 0.0])
            e = ti.Vector([0.0, 0.0, 0.0])
            s = 0.5
            for m in range(0, 6):
                e = texture_bilinear_filter(0.3 + p.x * s / 3000.0,
                                            0.3 + p.z * s / 3000.0)
                s += s
                t += e / s
            col = 1.0 + d.x - t * k
            col.z -= 0.1
            if t.x > (p.y * 0.007 + 1.3):
                break
            p += d
            k -= 0.0015
        screen[i, j] = col


# video_manger = ti.VideoManager(output_dir='./results', framerate=10, automatic_build=False)
gui = ti.GUI("screen", (w, h))
for i in range(100000):
    draw()
    gui.set_image(screen.to_numpy())
    gui.show()
    # video_manger.write_frame(screen.to_numpy())

# video_manger.make_video(gif=True, mp4=True)

9 Likes