图形学的基础是编程,在此基础上又有了三种需要学习的分支,分别是程序动画,渲染和仿真,这也就是为什么图形学难以入门的原因
程序动画的特点是使用较少的外部资源,基于一些自己制定的规则
在画布上最小的单元认为是像素,每一个像素填充上不同的颜色就可以认为是程序动画的简单实现
要完成一个程序动画总共可以分为六个步骤
第一步:准备画布
在做每一个程序动画的开始,codebase,也是分为四个步骤,初始化,设置数据(像素),渲染画布,gui实现可视化,在太极中的实现步骤如下
import taichi as ti
ti.init(arch = ti.cuda)
res_x = 512
res_y = 512
pixels = ti.Vector.field(3,ti.f32,shape = (res_x,res_y))
@ti.kernel
def render():
#在初始画布上进行渲染
for i,j in pixels:
color = ti.Vector([0.0,0.0,0.0])# init your canvas to black
pixels[i,j] = color
gui = ti.GUI("Canvas", res = (res_x,res_y))
for i in range(100000):
render()
gui.set_image(pixels)
gui.show
第二步:在画布上绘制颜色
基于此代码,我们就可以获得一个黑布,若是添加上下列的代码
@ti.kernel
def render(t:ti.f32):
# draw something on your canvas
for i,j in pixels:
r = 0.5 * ti.sin(t + float(i) / res_x) + 0.5
g = 0.5 * ti.sin(t + float(j) / res_y + 2) + 0.5
b = 0.5 * ti.sin(t + float(i) / res_x + 4) + 0.5
color = ti.Vector([r, g, b])
pixels[i, j] = color
gui = ti.GUI("Canvas", res=(res_x, res_y))
for i in range(100000):
t = i * 0.03
render(t)
gui.set_image(pixels)
gui.show()
这个画圆的方式就是一个简单的分型
@ti.kernel
def render(t:ti.f32):
#在初始画布上进行渲染
for i,j in pixels:
color = ti.Vector([0.0,0.0,0.0])# init your canvas to black
pos = ti.Vector([i,j])
center = ti.Vector([res_x/2.0,res_y/2.0])
radius = 100.0
r = (pos-center).norm()
if r < radius :
color = ti.Vector([1.0,1.0,0.0])
pixels[i,j] = color
gui = ti.GUI("Canvas", res = (res_x,res_y))
for i in range(100000):
t = i*0.03
render(t)
gui.set_image(pixels)
gui.show()
例如rgb绘制和图元绘制还可以生成这些图像
第三步: 绘制出基本单元,几何体
可以看出画出的圆比较粗糙,我们可以进行插值,最后会生成这样一个边缘具有模糊的圆
@ti.func
def circle(pos,center,radius,blur):
r = (pos-center).norm()
t = 0.0
if blur >0.0:
t = smoothstep(1.0,1.0-blur,r/radius)
return t
@ti.kernel
def render(t:ti.f32):
#在初始画布上进行渲染
for i,j in pixels:
color = ti.Vector([0.0,0.0,0.0])# init your canvas to black
pos = ti.Vector([i,j])
center = ti.Vector([res_x/2.0,res_y/2.0])
radius = 100.0
c = circle(pos,center,radius,0.1)
color = c*ti.Vector([0.0,0.0,1.0])
pixels[i,j] = color
gui = ti.GUI("Canvas", res = (res_x,res_y))
for i in range(100000):
t = i*0.03
render(t)
gui.set_image(pixels)
gui.show()
第四步:重复基本单元的放置,有效的一个方法是结块tiles
思想是在绘制出基本的图元之后,按照规则重复这些图元
@ti.kernel
def render(t:ti.f32):
#在初始画布上进行渲染
for i_,j_ in pixels:
color = ti.Vector([0.0,0.0,0.0])# init your canvas to black
tile_size = 64
i = mod(j_,tile_size)
j = mod(i_,tile_size)
pos = ti.Vector([i,j])
center = ti.Vector([tile_size/2.0,tile_size/2.0])
radius = tile_size/4.0
c = circle(pos,center,radius,0.1)
color = c*ti.Vector([0.0,0.0,1.0])
pixels[i_,j_] = color
gui = ti.GUI("Canvas", res = (res_x,res_y))
for i in range(100000):
t = i*0.03
render(t)
gui.set_image(pixels)
gui.show()
除了结块这种操作,还可以做分型,用一大堆子集去重复母集的操作,就像是不断的去套娃
只是单纯的加入了一个for循环,就可以实现这些效果
@ti.kernel
def render(t:ti.f32):
#在初始画布上进行渲染
for i_,j_ in pixels:
color = ti.Vector([0.0,0.0,0.0])# init your canvas to black
tile_size = 16
for k in range(3):
i = mod(j_,tile_size)
j = mod(i_,tile_size)
pos = ti.Vector([i,j])
center = ti.Vector([tile_size/2.0,tile_size/2.0])
radius = tile_size/2.0
c = circle(pos,center,radius,0.1)
color += c*ti.Vector([1.0,1.0,1.0])
color /= 2.0
tile_size *= 2
pixels[i_,j_] = color
gui = ti.GUI("Canvas", res = (res_x,res_y))
for i in range(100000):
t = i*0.03
render(t)
gui.set_image(pixels)
gui.show()
第五步:做动画化,额外添加时间轴
@ti.kernel
def render(t:ti.f32):
#draw something on your canvas
for i,j in pixels:
r = 0.5*ti.sin( t + float(i) / res_x) + 0.5
g = 0.5 * ti.sin(t + float(j) / res_y + 2) + 0.5
b = 0.5 * ti.sin(t + float(i) / res_x + 4) + 0.5
color = ti.Vector([r,g,b])
pixels[i,j] = color
gui = ti.GUI("Canvas", res = (res_x,res_y))
for i in range(100000):
t = i*0.03
render(t)
gui.set_image(pixels)
gui.show()
第六步: 加入些许的随机性元素
@ti.kernel
def render(t:ti.f32):
# draw something on your canvas
for i,j in pixels:
color = ti.Vector([0.0, 0.0, 0.0]) # init your canvas to black
tile_size = 16
for k in range(3):
center = ti.Vector([tile_size//2, tile_size//2])
radius = tile_size//2
pos = ti.Vector([mod(i, tile_size),mod(j, tile_size)]) # scale i, j to [0, tile_size-1]
blur =fract(ti.sin(float(0.1*t+i//tile_size*5+j//tile_size*3)))
c = circle(pos, center, radius, blur)
r = 0.5*ti.sin(float(0.001*t+i//tile_size)) + 0.5
g = 0.5*ti.sin(float(0.001*t+j//tile_size) + 2) + 0.5
b = 0.5*ti.sin(float(0.001*t+i//tile_size) + 4) + 0.5
color += ti.Vector([r, g, b])*c
color /= 2
tile_size *= 2
pixels[i,j] = color
gui = ti.GUI("Canvas", res=(res_x, res_y))
for i in range(100000):
t = i * 0.03
render(t)
gui.set_image(pixels)
gui.show()
引入柏林噪声,创造了一个既保有随机性,同时又具有局部连续性的东西,和白噪声不同,整体随机但是局部连续,生成这种噪声并不复杂
import taichi as ti
ti.init(arch = ti.cuda)
res_x = 1200
res_y = 675
pixels = ti.Vector.field(3, ti.f32, shape=(res_x, res_y))
@ti.kernel
def render(time:ti.f32):
# draw something on your canvas
for i,j in pixels:
color = ti.Vector([0.0, 0.0, 0.0]) # init your canvas to black
uv = ti.Vector([float(i) / res_x, float(j) / res_y]) - 0.5 # putting everything between -0.5 and 0.5
t = time * 0.1 + ((0.25 + 0.05 * ti.sin(time * 0.1))/(uv.norm() + 0.07)) * 2.2;
si = ti.sin(t)
co = ti.cos(t)
ma = ti.Matrix([[co, si], [-si, co]])
v1 = v2 = v3 = 0.0
s = 0.0
for k in range(90):
p = s * uv
ps = s
p = ma @ p # rotate
p += ti.Vector([0.22, 0.3])
ps += s - 1.5 - ti.sin(time * 0.13) * 0.1
# draw spiral curves
for l in range(8):
len2 = p.dot(p) + ps * ps
p = ti.abs(p) / len2 - 0.659
ps = ti.abs(ps) / len2 - 0.659
len2 = p.dot(p) + ps * ps
v1 += len2 * 0.0015 * (1.8 + ti.sin(uv.norm() * 13.0) + 0.5 - time * 0.2)
v2 += len2 * 0.0013 * (1.5 + ti.sin(uv.norm() * 14.5) + 1.2 - time * 0.3)
v3 += p.norm() * 0.003
s += 0.035
len = uv.norm()
v1 *= smoothstep(0.7, 0.0, len)
v2 *= smoothstep(0.5, 0.0, len)
v3 *= smoothstep(0.9, 0.0, len)
color[0] = v3 * (1.5 + ti.sin(time * 0.2) * 0.4)
color[1] = (v1 + v3) * 0.3
color[2] = v2
color += smoothstep(0.2, 0.0, len) * 0.85 + smoothstep(0.0, 0.6, v3) * .3
color = clamp(color, 0.0, 1.0)
pixels[i, j] = color
gui = ti.GUI("Canvas", res=(res_x, res_y))
for i in range(100000):
t = i * 0.03
render(t)
gui.set_image(pixels)
gui.show()