def render_func(self, pos, normal, dir, light_dir): color = ts.vec3(0.0) shineness = self.shineness half_lambert = ts.dot(normal, light_dir) * 0.5 + 0.5 lambert = max(0, ts.dot(normal, light_dir)) blinn_phong = ts.dot(normal, ts.mix(light_dir, -dir, 0.5)) blinn_phong = pow(max(blinn_phong, 0), shineness) refl_dir = ts.reflect(light_dir, normal) phong = -ts.dot(normal, refl_dir) phong = pow(max(phong, 0), shineness) strength = 0.0 if ti.static(self.lambert != 0.0): strength += lambert * self.lambert if ti.static(self.half_lambert != 0.0): strength += half_lambert * self.half_lambert if ti.static(self.blinn_phong != 0.0): strength += blinn_phong * self.blinn_phong if ti.static(self.phong != 0.0): strength += phong * self.phong color = ts.vec3(strength) if ti.static(self.is_normal_map): color = normal * 0.5 + 0.5 return color
def render_floor(self, camera): scene = camera.scene c_floor = ts.vec3(*self.floor_color) c_sky = ts.vec3(*self.sky_color) for X in ti.grouped(camera.img): W = ti.cast(ti.Vector([X.x, X.y, 1]), ti.f32) x = cook_coord(camera, W) world_dir = camera.trans_dir(ts.normalize(x)) if world_dir[1] > 0 or camera.pos[None][1] < floor_y: camera.img[X] = c_sky else: f = min(1, -world_dir[1] * 50) n = camera.untrans_dir(ts.vec3(0, 1, 0)) pos = camera.pos[None] + world_dir * ( camera.pos[None][1] - floor_y) / abs(world_dir[1]) #print(pos) pos = camera.untrans_pos(pos) #print(pos) color = ambient * c_sky * c_floor for light in ti.static(scene.lights): light_color = scene.opt.render_func(pos, n, \ ts.normalize(pos), light, c_floor) color += light_color camera.img[X] = c_sky * (1 - f) + color * f camera.nbuf[X] = n camera.zbuf[X] = 1 / pos.z
def render_particle(model, camera, index): scene = model.scene a = (model.L2C[None] @ ts.vec4(model.pos[index], 1)).xyz r = model.radius[index] A = camera.uncook(a) rad = camera.uncook(ts.vec3(r, r, a.z), False) M = int(ti.floor(A - rad)) N = int(ti.ceil(A + rad)) M = ts.clamp(M, 0, ti.Vector(camera.res)) N = ts.clamp(N, 0, ti.Vector(camera.res)) for X in ti.grouped(ti.ndrange((M.x, N.x), (M.y, N.y))): pos = camera.cook(float(ts.vec3(X, a.z))) dp = pos - a dp2 = dp.norm_sqr() if dp2 > r**2: continue dz = ti.sqrt(r**2 - dp2) if camera.fb.atomic_depth(X, a.z - dz): continue n = ts.vec3(dp.xy, -dz) normal = ts.normalize(n) view = ts.normalize(a + n) color = model.colorize(pos, normal) camera.fb['img'][X] = color camera.fb['normal'][X] = normal
def pixel_shader(self, pos, texcoor, normal, tangent, bitangent): ndir = self.sample('normal', texcoor, ts.vec3(0.0, 0.0, 1.0)) normal = ti.Matrix.cols([tangent, bitangent, normal]) @ ndir normal = normal.normalized() color = self.sample('color', texcoor, ts.vec3(1.0)) color = self.colorize(pos, texcoor, normal, color) return dict(img=color, normal=normal, tangent=tangent, bitangent=bitangent)
def trace(self, orig, dir): pos = orig color = ts.vec3(0.0) normal = ts.vec3(0.0) for s in range(100): t = self.calc_sdf(pos) if t <= 0: normal = ts.normalize(self.calc_grad(pos) - t) break pos += dir * t return pos, normal
def normal(pos): eps = 0.005 v1 = ts.vec3(1.0, -1.0, -1.0) v2 = ts.vec3(-1.0, -1.0, 1.0) v3 = ts.vec3(-1.0, 1.0, -1.0) v4 = ts.vec3(1.0, 1.0, 1.0) return ts.normalize(v1 * scene(pos + v1 * eps) + v2 * scene(pos + v2 * eps) + v3 * scene(pos + v3 * eps) + v4 * scene(pos + v4 * eps))
def get_volume_normal(self, pos): delta = 1e-3 x_delta = tl.vec3(delta, 0.0, 0.0) y_delta = tl.vec3(0.0, delta, 0.0) z_delta = tl.vec3(0.0, 0.0, delta) dx = self.sample_volume_trilinear(pos + x_delta) \ - self.sample_volume_trilinear(pos - x_delta) dy = self.sample_volume_trilinear(pos + y_delta) \ - self.sample_volume_trilinear(pos - y_delta) dz = self.sample_volume_trilinear(pos + z_delta) \ - self.sample_volume_trilinear(pos - z_delta) return tl.vec3(dx, dy, dz).normalized()
def get_ambient(camera, normal, view): c_floor = ts.vec3(0.75) c_sky = ts.vec3(0.8, 0.9, 0.95) refl_dir = -ts.reflect(-view, normal) refl_dir = ts.normalize(camera.trans_dir(refl_dir)) color = ts.vec3(ambient) if refl_dir[1] > 0: color *= c_sky else: f = min(1.0, -refl_dir[1] * 25.0) color *= c_sky * (1 - f) + c_floor * f return color
def pixel_shader(self, mid: ti.template(), pos, tex, nrm, tan, bitan): if ti.static(isinstance(mid, int)): return ti.static(self.materials[mid]).pixel_shader( pos, tex, nrm, tan, bitan) color = ts.vec3(0.0) if mid != 0: color = ts.vec3(1.0, 0.0, 1.0) # magenta, for debugging missing materials for i, material in ti.static(self.materials.items()): if mid == i: color = material.pixel_shader(pos, tex, nrm, tan, bitan) return color
def compute_tangent(dp1, dp2, duv1, duv2): if ti.static(0): return ts.vec3(0.0), ts.vec3(0.0) IDUV = ti.Matrix([[duv1.x, duv1.y], [duv2.x, duv2.y]]).inverse() DPx = ti.Vector([dp1.x, dp2.x]) DPy = ti.Vector([dp1.y, dp2.y]) DPz = ti.Vector([dp1.z, dp2.z]) T = ti.Vector([0.0, 0.0, 0.0]) B = ti.Vector([0.0, 0.0, 0.0]) T.x, B.x = IDUV @ DPx T.y, B.y = IDUV @ DPy T.z, B.z = IDUV @ DPz return T, B
def calc_gi(self, camera): scene = camera.scene for X in ti.grouped(camera.img): if self.enable_gi[None] == 0: continue # use an additional normal buffer at this time if camera.zbuf[X] > 0: z = 1 / camera.zbuf[X] pos = cook_coord(camera, ti.cast(ti.Vector([X.x, X.y, z]), ti.f32)) pos_world = camera.trans_pos(pos) norm_world = camera.trans_dir(camera.nbuf[X]) dy = pos_world[1] - floor_y ftot = 0.0 if dy > 0: ftot = form_factor_floor(norm_world, dy, self.radius) gtot = ts.vec3(0.0) if ti.static(self.grid is not None): base = self.grid.grid_index(pos_world) for dI in ti.grouped(ti.ndrange((-4, 5), (-4, 5), (-4, 5))): I = max(0, min(base + dI, self.grid.gridsize - 1)) for p in range(self.grid_n[I + dI]): i = self.grid_list[I + dI, p] if (self.particles[i] - pos_world).norm_sqr() \ > (self.gi_cutoff * self.boxlength) ** 2: continue f, gi = get_gi_factors(camera, scene, self.particles[i], pos_world, norm_world, self.radius, self.colors[self.type[i]]) ftot += f gtot += gi else: for i in range(self.particles.shape[0]): # don't compute for particles behind the normal if ti.dot(self.particles[i] - pos_world, norm_world) < 0: continue f, gi = get_gi_factors(camera, scene, self.particles[i], pos_world, norm_world, self.radius, self.colors[self.type[i]]) ftot += f gtot += gi camera.img[X] = camera.img[X] * ts.vec3(max(1 - ftot, 0)) + gtot * 0.3
def render_line(model, camera, face, w0=0, w1=1): posa, posb = face.pos # Position texa, texb = face.tex # TexCoord nrma, nrmb = face.nrm # Normal clra = [posa, texa, nrma] clrb = [posb, texb, nrmb] A = camera.uncook(posa) B = camera.uncook(posb) M = int(ti.floor(min(A, B) - 1)) N = int(ti.ceil(max(A, B) + 1)) M = ts.clamp(M, 0, ti.Vector(camera.fb.res)) N = ts.clamp(N, 0, ti.Vector(camera.fb.res)) B_A = (B - A).normalized() for X in ti.grouped(ti.ndrange((M.x, N.x), (M.y, N.y))): udf = abs((X - A).cross(B_A)) if udf >= w1: continue strength = ts.smoothstep(udf, w1, w0) color = ts.vec3(strength) camera.img[X] += color
def sample_volume_trilinear(self, pos): ''' Samples volume data at `pos` and trilinearly interpolates the value Args: pos (tl.vec3): Position to sample the volume in [-1, 1]^3 Returns: float: Sampled interpolated intensity ''' pos = tl.clamp(((0.5 * pos) + 0.5), 0.0, 1.0) \ * ti.static(tl.vec3(*self.volume.shape) - 1.0 - 1e-4) x_low, x_high, x_frac = low_high_frac(pos.x) y_low, y_high, y_frac = low_high_frac(pos.y) z_low, z_high, z_frac = low_high_frac(pos.z) x_high = min(x_high, ti.static(self.volume.shape[0] - 1)) y_high = min(y_high, ti.static(self.volume.shape[1] - 1)) z_high = min(z_high, ti.static(self.volume.shape[2] - 1)) # on z_low v000 = self.volume[x_low, y_low, z_low] v100 = self.volume[x_high, y_low, z_low] x_val_y_low = tl.mix(v000, v100, x_frac) v010 = self.volume[x_low, y_high, z_low] v110 = self.volume[x_high, y_high, z_low] x_val_y_high = tl.mix(v010, v110, x_frac) xy_val_z_low = tl.mix(x_val_y_low, x_val_y_high, y_frac) # on z_high v001 = self.volume[x_low, y_low, z_high] v101 = self.volume[x_high, y_low, z_high] x_val_y_low = tl.mix(v001, v101, x_frac) v011 = self.volume[x_low, y_high, z_high] v111 = self.volume[x_high, y_high, z_high] x_val_y_high = tl.mix(v011, v111, x_frac) xy_val_z_high = tl.mix(x_val_y_low, x_val_y_high, y_frac) return tl.mix(xy_val_z_low, xy_val_z_high, z_frac)
def intersect(self, orig, dir): ret, normal = INF, ts.vec3(0.0) for b in ti.static(self.balls): t, n = b.intersect(orig, dir) if t < ret: ret, normal = t, n return ret, normal
def intersect(self, orig, dir): ret, normal = INF, ts.vec3(0.0) for I in ti.grouped(ti.ndrange(*self.pos.shape())): t, n = self.make_one(I).do_intersect(orig, dir) if t < ret: ret, normal = t, n return ret, normal
def cal_look_at_mat(ro, ta, roll): ww = (ta - ro).normalized() uu = ts.cross(ww, ts.vec3(ts.sin(roll), ts.cos(roll), 0.0)).normalized() vv = ts.cross(uu, ww).normalized() return ts.mat([uu[0], vv[0], ww[0]], [uu[1], vv[1], ww[1]], [uu[2], vv[2], ww[2]])
def intersect_triangle(model, orig, dir, face): posa, posb, posc = face.pos texa, texb, texc = face.tex nrma, nrmb, nrmc = face.nrm L2C = model.L2W[None] posa = (L2C @ ts.vec4(posa, 1)).xyz posb = (L2C @ ts.vec4(posb, 1)).xyz posc = (L2C @ ts.vec4(posc, 1)).xyz nrma = (L2C @ ts.vec4(nrma, 0)).xyz nrmb = (L2C @ ts.vec4(nrmb, 0)).xyz nrmc = (L2C @ ts.vec4(nrmc, 0)).xyz tan, bitan = compute_tangent(posb - posa, posc - posa, texb - texa, texc - texa) hit = 1e6 clr = ts.vec3(0.0) sa, sb, sc = plucker_bcoor(orig, orig + dir, posa, posb, posc) if (sa >= 0 and sb >= 0 and sc >= 0) or (sa <= 0 and sb <= 0 and sc <= 0): snorm = sa + sb + sc sa /= snorm sb /= snorm sc /= snorm pos = posa * sa + posb * sb + posc * sc tex = texa * sa + texb * sb + texc * sc nrm = nrma * sa + nrmb * sb + nrmc * sc if dir.dot(pos - orig) > 1e-4: hit = (pos - orig).norm() orig, dir, clr = model.material.radiance(model, pos, dir, tex, nrm, tan, bitan) return hit, orig, dir, clr
def _render(self): for I in ti.grouped(self.img): self.img[I] = ts.vec3(0.0) self.zbuf[I] = 0.0 if ti.static(len(self.models)): for model in ti.static(self.models): model.render()
def __init__(self, volume_resolution, render_resolution, max_samples=512, tf_resolution=128, fov=30.0, nearfar=(0.1, 100.0)): ''' Initializes Volume Raycaster. Make sure to .set_volume() and .set_tf_tex() after initialization Args: volume_resolution (3-tuple of int): Resolution of the volume data (w,h,d) render_resolution (2-tuple of int): Resolution of the rendering (w,h) tf_resolution (int): Resolution of the transfer function texture fov (float, optional): Field of view of the camera in degrees. Defaults to 60.0. nearfar (2-tuple of float, optional): Near and far plane distance used for perspective projection. Defaults to (0.1, 100.0). ''' self.resolution = render_resolution self.aspect = render_resolution[0] / render_resolution[1] self.fov_deg = fov self.fov_rad = np.radians(fov) self.near, self.far = nearfar # Taichi Fields self.volume = ti.field(ti.f32, needs_grad=True) self.tf_tex = ti.Vector.field(4, dtype=ti.f32, needs_grad=True) self.render_tape = ti.Vector.field(4, dtype=ti.f32, needs_grad=True) self.output_rgba = ti.Vector.field(4, dtype=ti.f32, needs_grad=True) self.valid_sample_step_count = ti.field(ti.i32) self.sample_step_nums = ti.field(ti.i32) self.entry = ti.field(ti.f32) self.exit = ti.field(ti.f32) self.rays = ti.Vector.field(3, dtype=ti.f32) self.max_valid_sample_step_count = ti.field(ti.i32, ()) self.max_samples = max_samples self.ambient = 0.4 self.diffuse = 0.8 self.specular = 0.3 self.shininess = 32.0 self.light_color = tl.vec3(1.0) self.cam_pos = ti.Vector.field(3, dtype=ti.f32) volume_resolution = tuple(map(lambda d: d // 4, volume_resolution)) render_resolution = tuple(map(lambda d: d // 8, render_resolution)) ti.root.dense(ti.ijk, volume_resolution).dense(ti.ijk, (4, 4, 4)).place(self.volume) ti.root.dense(ti.ijk, (*render_resolution, max_samples)).dense( ti.ijk, (8, 8, 1)).place(self.render_tape) ti.root.dense(ti.ij, render_resolution).dense(ti.ij, (8, 8)).place( self.valid_sample_step_count, self.sample_step_nums) ti.root.dense(ti.ij, render_resolution).dense(ti.ij, (8, 8)).place( self.output_rgba) ti.root.dense(ti.ij, render_resolution).dense(ti.ij, (8, 8)).place( self.entry, self.exit) ti.root.dense(ti.ij, render_resolution).dense(ti.ij, (8, 8)).place(self.rays) ti.root.dense(ti.i, tf_resolution).place(self.tf_tex) ti.root.dense(ti.i, tf_resolution).place(self.tf_tex.grad) ti.root.place(self.cam_pos) ti.root.lazy_grad()
def radiance(self): outdir = ts.vec3(0.0) clr = ts.vec3(0.0) if ti.random() < self.emission: clr = ts.vec3(self.emission_color) elif ti.random() < self.specular: clr = ts.vec3(self.specular_color) outdir = ts.reflect(self.indir, self.normal) elif ti.random() < self.diffuse: clr = ts.vec3(self.diffuse_color) outdir = ts.randUnit3D() if outdir.dot(self.normal) < 0: outdir = -outdir #s = ti.random() #outdir = ts.vec3(ti.sqrt(1 - s**2) * ts.randUnit2D(), s) #outdir = ti.Matrix.cols([self.tangent, self.bitangent, self.normal]) @ outdir return self.pos, outdir, clr
def clear_framebuffer(self): self.max_valid_sample_step_count[None] = 0 for i, j, k in self.render_tape: self.render_tape[i, j, k] = tl.vec4(0.0) for i, j in self.valid_sample_step_count: self.valid_sample_step_count[i, j] = 1 self.output_rgba[i, j] = tl.vec4(0.0) self.output_rgb[i, j] = tl.vec3(0.0)
def render_func(self, pos, normal, viewdir, light, color): lightdir = light.get_dir(pos) costheta = max(0, ti.dot(normal, lightdir)) l_out = ts.vec3(0.0) if costheta > 0: l_out = self.brdf(normal, -viewdir, lightdir, color)\ * costheta * light.get_color(pos) return l_out
def generate(self, coor): orig = ts.vec3(0.0) dir = ts.vec3(0.0, 0.0, 1.0) if ti.static(self.type == self.ORTHO): orig = ts.vec3(coor, 0.0) elif ti.static(self.type == self.TAN_FOV): uv = coor * self.fov dir = ts.normalize(ts.vec3(uv, 1)) elif ti.static(self.type == self.COS_FOV): uv = coor * self.fov dir = ts.vec3(ti.sin(uv), ti.cos(uv.norm())) orig = (self.L2W[None] @ ts.vec4(orig, 1)).xyz dir = (self.L2W[None] @ ts.vec4(dir, 0)).xyz return orig, dir
def flush_taa(self): if ti.static(self.n_taa): self.ntaa[None] = 0 self.itaa[None] = 0 for I in ti.grouped(ti.ndrange(*self.res)): r = ts.vec3(0.0) for i in ti.static(range(self.n_taa)): self.taa[i, I] *= 0
def render(self, camera): for I in ti.grouped(ti.ndrange(*camera.res)): if camera.fb.idepth[I] != 0: continue id = I / ts.vec(*camera.res) * 2 - 1 dir = ts.vec3(id * ti.tan(camera.fov), 1.0) dir = v4trans(self.L2C[None].inverse(), dir, 0).normalized() color = self.sample(dir) camera.fb.update(I, dict(img=color))
def my_render_func(pos, normal, dir, light_dir): n = cartoon_level[None] refl_dir = ts.reflect(light_dir, normal) #refl_dir = ts.mix(light_dir, -dir, 0.5) NoL = pow(ts.dot(normal, refl_dir), 12) NoL = ts.mix(NoL, ts.dot(normal, light_dir) * 0.5 + 0.5, 0.5) strength = 0.2 if any(normal): strength = ti.floor(max(0, NoL * n + 0.5)) / n return ts.vec3(strength)
def do_render(self, scene): W = 1 A = scene.uncook_coor(scene.camera.untrans_pos(self.a)) B = scene.uncook_coor(scene.camera.untrans_pos(self.b)) M, N = int(ti.floor(min(A, B) - W)), int(ti.ceil(max(A, B) + W)) for X in ti.grouped(ti.ndrange((M.x, N.x), (M.y, N.y))): P = B - A udf = (ts.cross(X, P) + ts.cross(B, A))**2 / P.norm_sqr() XoP = ts.dot(X, P) AoB = ts.dot(A, B) if XoP > B.norm_sqr() - AoB: udf = (B - X).norm_sqr() elif XoP < AoB - A.norm_sqr(): udf = (A - X).norm_sqr() if udf < 0: scene.img[X] = ts.vec3(1.0) elif udf < W**2: t = ts.smoothstep(udf, 0, W**2) ti.atomic_min(scene.img[X], ts.vec3(t))
def render_func(self, pos, normal, viewdir, light): # TODO: move render_func to Light.render_func? if ti.static(isinstance(light, AmbientLight)): return light.get_color(pos) * self.get_ambient() lightdir = light.get_dir(pos) NoL = ts.dot(normal, lightdir) l_out = ts.vec3(0.0) if NoL > EPS: l_out = light.get_color(pos) l_out *= NoL * self.brdf(normal, lightdir, -viewdir) return l_out
def render(self): self.src.render() for I in ti.grouped(self.img): mid = self.src['mid'][I] pos = self.src['position'][I] texcoor = self.src['texcoord'][I] normal = self.src['normal'][I] tangent = self.src['tangent'][I] bitangent = normal.cross(tangent) color = ts.vec3(0.0) if mid != 0: color = ts.vec3(1.0, 0.0, 1.0) # magenta for debugging missing material if ti.static(len(self.mtllib)): for i, material in ti.static(enumerate(self.mtllib)): if ti.static(material is not None): if i == mid: color = material.pixel_shader( self, pos, texcoor, normal, tangent, bitangent) self.img[I] = color