208 lines
7.2 KiB
Python
208 lines
7.2 KiB
Python
import taichi as ti
|
|
import numpy as np
|
|
import random
|
|
import math
|
|
|
|
ti.init(arch=ti.cuda)
|
|
|
|
def flatten_specs(specs_list):
|
|
"""把 specs_list 拉平为 numpy 数组 + 索引表"""
|
|
starts, ends, colors = [], [], []
|
|
frame_offset, frame_count = [], []
|
|
offset = 0
|
|
for specs in specs_list:
|
|
frame_offset.append(offset)
|
|
frame_count.append(len(specs))
|
|
for (s, e, c) in specs:
|
|
starts.append(s)
|
|
ends.append(e)
|
|
colors.append(c)
|
|
offset += len(specs)
|
|
return (
|
|
np.array(starts, dtype=np.float32),
|
|
np.array(ends, dtype=np.float32),
|
|
np.array(colors, dtype=np.float32),
|
|
np.array(frame_offset, dtype=np.int32),
|
|
np.array(frame_count, dtype=np.int32),
|
|
)
|
|
|
|
def render_whole(specs_list, H=480, W=640, fx=500, fy=500, cx=240, cy=320, radius=21.5):
|
|
img = ti.Vector.field(4, dtype=ti.f32, shape=(H, W))
|
|
starts, ends, colors, frame_offset, frame_count = flatten_specs(specs_list)
|
|
total_cyl = len(starts)
|
|
n_frames = len(specs_list)
|
|
z_min = min(starts[:, 2].min(), ends[:, 2].min())
|
|
z_max = max(starts[:, 2].max(), ends[:, 2].max())
|
|
|
|
# ========= 相机内参 =========
|
|
znear = 0.1
|
|
zfar = max(min(z_max, 25000), 10000)
|
|
C = ti.Vector([0.0, 0.0, 0.0]) # 相机中心
|
|
light_dir = ti.Vector([0.0, 0.0, 1.0])
|
|
|
|
c_start = ti.Vector.field(3, dtype=ti.f32, shape=total_cyl)
|
|
c_end = ti.Vector.field(3, dtype=ti.f32, shape=total_cyl)
|
|
c_rgba = ti.Vector.field(4, dtype=ti.f32, shape=total_cyl)
|
|
n_cyl = ti.field(dtype=ti.i32, shape=()) # 实际数量
|
|
f_offset = ti.field(dtype=ti.i32, shape=n_frames)
|
|
f_count = ti.field(dtype=ti.i32, shape=n_frames)
|
|
frame_id = ti.field(dtype=ti.i32, shape=()) # 当前帧号
|
|
z_min_field = ti.field(dtype=ti.f32, shape=())
|
|
z_max_field = ti.field(dtype=ti.f32, shape=())
|
|
|
|
z_min_field[None] = z_min
|
|
z_max_field[None] = z_max
|
|
|
|
# # ====== 拷贝数据一次 ======
|
|
c_start.from_numpy(starts)
|
|
c_end.from_numpy(ends)
|
|
c_rgba.from_numpy(colors)
|
|
f_offset.from_numpy(frame_offset)
|
|
f_count.from_numpy(frame_count)
|
|
|
|
@ti.func
|
|
def sd_cylinder(p, a, b, r):
|
|
pa = p - a
|
|
ba = b - a
|
|
h = ba.norm()
|
|
eps = 1e-8
|
|
res = 0.0
|
|
if h < eps:
|
|
res = pa.norm() - r
|
|
else:
|
|
ba_n = ba / h
|
|
proj = pa.dot(ba_n)
|
|
proj_clamped = min(max(proj, 0.0), h)
|
|
res = (pa - proj_clamped * ba_n).norm() - r
|
|
return res
|
|
|
|
@ti.func
|
|
def scene_sdf(p):
|
|
best_d = 1e6
|
|
best_col = ti.Vector([0.0, 0.0, 0.0, 0.0])
|
|
fid = frame_id[None] # 从 field 里读出来,变成一个普通 int
|
|
off = f_offset[fid]
|
|
cnt = f_count[fid]
|
|
for i in range(cnt): # 只遍历实际数量
|
|
a = c_start[off + i]
|
|
b = c_end[off + i]
|
|
r = radius
|
|
col = c_rgba[off + i]
|
|
d = sd_cylinder(p, a, b, r)
|
|
if d < best_d:
|
|
best_d = d
|
|
best_col = col
|
|
return best_d, best_col
|
|
|
|
@ti.func
|
|
def get_normal(p):
|
|
e = 1e-3
|
|
dx = scene_sdf(p + ti.Vector([e, 0.0, 0.0]))[0] - scene_sdf(p - ti.Vector([e, 0.0, 0.0]))[0]
|
|
dy = scene_sdf(p + ti.Vector([0.0, e, 0.0]))[0] - scene_sdf(p - ti.Vector([0.0, e, 0.0]))[0]
|
|
dz = scene_sdf(p + ti.Vector([0.0, 0.0, e]))[0] - scene_sdf(p - ti.Vector([0.0, 0.0, e]))[0]
|
|
n = ti.Vector([dx, dy, dz])
|
|
return n.normalized()
|
|
|
|
@ti.func
|
|
def pixel_to_ray(xi, yi):
|
|
u = (xi - cx) / fx
|
|
v = (yi - cy) / fy
|
|
dir_cam = ti.Vector([u, v, 1.0]).normalized()
|
|
Rcw = ti.Matrix.identity(ti.f32, 3)
|
|
rd_world = Rcw @ dir_cam
|
|
ro_world = C
|
|
return ro_world, rd_world
|
|
|
|
@ti.kernel
|
|
def render():
|
|
depth_near, depth_far = ti.max(z_min_field[None], 0.1), ti.min(z_max_field[None] + 6000, 20000) # 能渲染出来的点,最大12000
|
|
for y, x in img:
|
|
ro, rd = pixel_to_ray(x, y)
|
|
t = znear
|
|
col_out = ti.Vector([0.0, 0.0, 0.0, 0.0])
|
|
for _ in range(300):
|
|
p = ro + rd * t
|
|
d, col = scene_sdf(p)
|
|
if d < 1e-3:
|
|
# n = get_normal(p)
|
|
# diff = max(n.dot(-light_dir), 0.0)
|
|
# lit = 0.3 + 0.7 * diff
|
|
# col_out = ti.Vector([col.x * lit, col.y * lit, col.z * lit, col.w])
|
|
# break
|
|
|
|
n = get_normal(p)
|
|
diff = max(n.dot(-light_dir), 0.0)
|
|
|
|
# === Blinn-Phong 镜面反射 ===
|
|
view_dir = -rd.normalized()
|
|
half_dir = (view_dir + -light_dir).normalized()
|
|
spec = max(n.dot(half_dir), 0.0) ** 32 # shininess=32,越小越散,越大越锐
|
|
|
|
depth_factor = 1.0 - (p.z - depth_near) / (depth_far - znear)
|
|
depth_factor = ti.max(0.0, ti.min(1.0, depth_factor))
|
|
|
|
# 原来的 diffuse/ambient 光照
|
|
diffuse_term = 0.3 + 0.7 * diff
|
|
base = col.xyz * diffuse_term * depth_factor
|
|
|
|
# 镜面高光(叠加到原有结果上)
|
|
highlight = ti.Vector([1.0, 1.0, 1.0]) * (0.5 * spec) * depth_factor
|
|
|
|
col_out = ti.Vector([base.x + highlight.x,
|
|
base.y + highlight.y,
|
|
base.z + highlight.z,
|
|
col.w])
|
|
break
|
|
|
|
if t > zfar:
|
|
break
|
|
t += max(d, 1e-4)
|
|
img[y, x] = col_out
|
|
|
|
frames_np_rgba = []
|
|
for f in range(len(specs_list)):
|
|
# start_time = time.time()
|
|
frame_id[None] = f
|
|
render()
|
|
arr = np.clip(img.to_numpy(), 0, 1)
|
|
# end_time = time.time()
|
|
# print(f"Frame {f} time: {end_time - start_time} seconds")
|
|
arr8 = (arr * 255).astype(np.uint8)
|
|
frames_np_rgba.append(arr8)
|
|
|
|
return frames_np_rgba
|
|
|
|
|
|
def random_cylinder():
|
|
"""生成一根随机圆柱 (start, end, color)。"""
|
|
# 起点 [-200,200]^2, z 在 [-300,-100]
|
|
ax = random.uniform(-200, 200)
|
|
ay = random.uniform(-200, 200)
|
|
az = random.uniform(300, 400)
|
|
start = [ax, ay, az]
|
|
|
|
# 随机方向和长度
|
|
theta = random.uniform(0, 2*math.pi)
|
|
phi = random.uniform(-math.pi/4, math.pi/4) # 倾斜角
|
|
L = 100
|
|
dx = math.cos(phi) * math.cos(theta)
|
|
dy = math.cos(phi) * math.sin(theta)
|
|
dz = math.sin(phi)
|
|
end = [ax + dx * L, ay + dy * L, az + dz * L]
|
|
|
|
# 随机颜色 (RGB + alpha=1)
|
|
color = [random.random(), random.random(), random.random(), 1.0]
|
|
|
|
return (start, end, color)
|
|
|
|
def generate_specs_list(num_frames=120, min_cyl=10, max_cyl=120):
|
|
"""生成 specs_list,每帧有若干随机圆柱."""
|
|
specs_list = []
|
|
for _ in range(num_frames):
|
|
n_cyl = random.randint(min_cyl, max_cyl)
|
|
specs = [random_cylinder() for _ in range(n_cyl)]
|
|
specs_x_shift = [([spec[0][0] + 50, spec[0][1], spec[0][2]], [spec[1][0] + 50, spec[1][1], spec[1][2]], spec[2]) for spec in specs]
|
|
specs_list.append(specs)
|
|
specs_list.append(specs_x_shift)
|
|
return specs_list
|