Homework 0: Volumetric Light

``````import taichi as ti
import numpy as np
import math
import time

ti.init(arch=ti.opengl)

def vec(*xs):
return ti.Vector(list(xs))

pixel = ti.var(ti.f32, (512, 512))
shadow = ti.var(ti.f32, (256, 256, 256))
#forward = ti.Vector(3, ti.f32, ())
iTime = ti.var(ti.f32, ())

@ti.func
def scene(p):
c = ti.cos(iTime[None])
s = ti.sin(iTime[None])
t1 = ti.Matrix([
[1.0, 0.0, 0.0],
[0.0, c, -s],
[0.0, s, c],
])
c = ti.cos(iTime[None] * 1.2)
s = ti.sin(iTime[None] * 1.2)
t2 = ti.Matrix([
[c, 0.0, s],
[0.0, 1.0, 0.0],
[-s, 0.0, c],
])
return ti.abs(t1 @ t2 @ p).max() - 0.1 - s * 0.06

@ti.func
def phase(a, b):
cos = a.dot(b)# / (a.norm() * b.norm())
#return (0.1875 / math.pi) / (1 - cos ** 2)
return (0.1875 / math.pi) * (1 + cos ** 2)

@ti.func
def extinAt(p):
return 0.9

@ti.func
def absorAt(p):
return 0.4

@ti.func
def shadowAt(p):
p = (p + 2) * 0.25
I = ti.cast(p * 256, ti.i32)
shad = 1 - shadow[I]
return shad

@ti.kernel
def render_shadow():
light = vec(0.0, 0.8, 0.4).normalized()
dstep = 0.006

for I in ti.grouped(shadow):
shadow[I] = 0
#trans = 1.0
p = I * (4 / 256) - 2
for i in range(250):
p = p - light * dstep
sdf = scene(p)
if sdf < 0.0:
shadow[I] = 1

@ti.func
def lightAt(p, d):
light = vec(0.0, 0.8, 0.4).normalized()
dstep = 0.006

trans = 1.0
shad = shadowAt(p)
for i in range(250):
p = p + d * dstep
trans *= ti.exp(-dstep * extinAt(p))

return 20 * trans * shad

@ti.func
def radiance(p, d):
light = vec(0.0, 0.8, 0.4).normalized()
dstep = 0.006

trans = 1.0
total = 0.0
for i in range(250):
p = p + d * dstep
sdf = scene(p)
if sdf < 0.0:
total = 1.0
break
insca = extinAt(p) - absorAt(p)
total += trans * lightAt(p, d) * insca * phase(light, d) * dstep
return total

@ti.kernel
def render():
# Thanks to the issue #1109:
#fwd = forward[None]
#up = fwd.cross(vec(0.0, 1.0, 0.0)).normalized()
#right = up.cross(fwd)
#cam = ti.Matrix(cols=[fwd, right, up])
#o = -1.0 * fwd
o = vec(-1.0, 0.0, 0.0)
for i, j in pixel:
d = (vec(2.0, 2.0 / 512 * i - 1, 2.0 / 512 * j - 1)).normalized()
p = o
pixel[i, j] = radiance(p, d)

gui = ti.GUI('Volumetric Light')
while True:
for e in gui.get_events():
if e.key == ti.GUI.ESCAPE:
exit()
render_shadow()
for i in range(4):
render()
gui.set_image(pixel.to_numpy())
gui.show()
iTime[None] = iTime[None] + 0.01
``````

1 Like

Seeing many classmates uploading ray-marching homework and arguing Taichi doesn’t have 3D examples, here’s mine.

Thanks for your example.
I try it on my side.
The performance of OpenGL is quiet low (FPS around 5).
But, it would be worse if initializing tai with gpu or cuda.
I think, maybe, OpenGL is more efficient to drive the gpu card than tai.

Cool! I think this demo is better presented using a video. A single static image makes the shadow hard to see.

This reminds me of a shadertoy demo: https://www.shadertoy.com/view/Xdf3zB

By using ffmpeg, I’ve made a gif.
But I did’t find a good timestamp to cycle this gif.

3 Likes