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 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 sample(self, v): x = ts.clamp(int(v[0]), 0, self.wid - 1) y = ts.clamp(int(v[1]), 0, self.hgt - 1) RGBA = self.buf[x, y] R = float((RGBA & 0x00FF0000) >> 16) / 255.0 G = float((RGBA & 0x0000FF00) >> 8) / 255.0 B = float((RGBA & 0x000000FF)) / 255.0 return ti.Vector([R, G, B])
def softray(ro, rd, hn): res = 1.0 t = 0.0005 h = 1.0 for _ in range(0, 40): h = scene(ro + rd * t) res = ti.min(res, hn * h / t) t += ts.clamp(h, 0.02, 2.0) return ts.clamp(res, 0.0, 1.0)
def getGlow(minPDist): mainGlow = minPDist * 1.2 mainGlow = pow(mainGlow, 32.0) mainGlow = ts.clamp(mainGlow, 0.0, 1.0) outerGlow = minPDist * 0.4 outerGlow = pow(outerGlow, 2.0) outerGlow = ts.clamp(outerGlow, 0.0, 1.0) return ti.Vector([10.0, 5.0, 3.0, min(mainGlow + outerGlow, 1.0)])
def denoise(self, alpha: ti.template()): ti.static_print('denoise', alpha) if ti.static(alpha != 0): for I in ti.grouped(self.buf): center = ts.clamp(self.buf[I]) around = ts.clamp((self.buf[I + ts.D.x_] + self.buf[I + ts.D.X_] + self.buf[I + ts.D._x] + self.buf[I + ts.D._X]) / 4) #amax = ts.clamp(max(self.buf[I + ts.D.x_], self.buf[I + ts.D.X_], self.buf[I + ts.D._x], self.buf[I + ts.D._X])) #amin = ts.clamp(min(self.buf[I + ts.D.x_], self.buf[I + ts.D.X_], self.buf[I + ts.D._x], self.buf[I + ts.D._X])) #if center <= amin + throttle or center >= amax - throttle: self.buf[I] = center * (1 - alpha) + around * alpha
def update_display(): for i in ti.grouped(x): j = i.dot(tl.vec(N, 1)) model.pos[j] = x[i] xa = x[tl.clamp(i + tl.D.x_, 0, tl.vec(*NN) - 1)] xb = x[tl.clamp(i + tl.D.X_, 0, tl.vec(*NN) - 1)] ya = x[tl.clamp(i + tl.D._x, 0, tl.vec(*NN) - 1)] yb = x[tl.clamp(i + tl.D._X, 0, tl.vec(*NN) - 1)] normal = (ya - yb).cross(xa - xb).normalized() model.nrm[j] = normal model.nrm[N**2 + j] = -normal
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 UniformSampleSphere(self, u1, u2): z = 1.0 - 2.0 * u1 r = ti.sqrt(ts.clamp(1.0 - z * z, 0.0, 1.0)) phi = 2.0 * 3.1415926 * u2 x = r * ti.cos(phi) y = r * ti.sin(phi) return ti.Vector([x, y, z])
def accel(v: ti.template(), x: ti.template(), dt): for i in ti.grouped(x): acc = x[i] * 0 for d in ti.static(links): disp = x[tl.clamp(i + d, 0, tl.vec(*NN) - 1)] - x[i] dis = disp.norm() acc += disp * (dis - L) / L**2 v[i] += stiff * acc * dt v[i] *= ti.exp(-damp * dt)
def teture2D(self, u, v): x = ts.clamp(u * self.wid, 0.0, self.wid - 1.0) y = ts.clamp(v * self.hgt, 0.0, self.hgt - 1.0) # lt rt # *--------* # | ↑wbt | # | ← * | # | wlr | # *--------* # lb rb lt = ti.Vector([ti.floor(x), ti.floor(y)]) rt = lt + ti.Vector([1, 0]) lb = lt + ti.Vector([0, 1]) rb = lt + ti.Vector([1, 1]) wbt = ts.fract(y) wlr = ts.fract(x) #print(x,y,lt,wbt,wlr) return ts.mix(ts.mix(self.sample(lt), self.sample(rt), wlr), ts.mix(self.sample(lb), self.sample(rb), wlr), wbt)
def ambocc(pos, nor): occ = 0.0 sca = 1.0 for i in range(0, 5): hr = 0.01 + 0.12 * float(i) / 4.0 aopos = nor * hr + pos dd = scene(aopos) occ += -(dd - hr) * sca sca *= 0.95 return ts.clamp(1.0 - 3.0 * occ, 0.0, 1.0)
def splat(data, weights, v, pos, cp): tot = data.shape dim = len(tot) I0 = ti.Vector.zero(int, len(tot)) I1 = ti.Vector.zero(int, len(tot)) w = ti.zero(pos) for k in ti.static(range(len(tot))): I0[k] = ts.clamp(int(pos[k]), 0, tot[k] - 1) I1[k] = ts.clamp(I0[k] + 1, 0, tot[k] - 1) w[k] = ts.clamp(pos[k] - I0[k], 0.0, 1.0) for u in ti.static(ti.grouped(ti.ndrange(*((0, 2), ) * len(tot)))): dpos = ti.zero(pos) I = ti.Vector.zero(int, len(tot)) W = 1.0 for k in ti.static(range(len(tot))): dpos[k] = (pos[k] - I0[k]) if u[k] == 0 else (pos[k] - I1[k]) I[k] = I0[k] if u[k] == 0 else I1[k] W *= (1 - w[k]) if u[k] == 0 else w[k] data[I] += (v + cp.dot(dpos)) * W weights[I] += W
def substep(): for i in ti.grouped(x): acc = x[i] * 0 for d in ti.static(links): disp = x[tl.clamp(i + d, 0, tl.vec(*NN) - 1)] - x[i] length = L * float(d).norm() acc += disp * (disp.norm() - length) / length**2 v[i] += stiffness * acc * dt for i in ti.grouped(x): v[i].y -= gravity * dt v[i] = tl.ballBoundReflect(x[i], v[i], ball_pos, ball_radius, 6) for i in ti.grouped(x): v[i] *= ti.exp(-damping * dt) x[i] += dt * v[i]
def render_particle(model, camera, index): scene = model.scene L2W = model.L2W a = model.pos[index] r = model.radius[index] a = camera.untrans_pos(L2W @ a) 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) zindex = 1 / (a.z - dz) if zindex < ti.atomic_max(camera.fb['idepth'][X], zindex): continue n = ts.vec3(dp.xy, -dz) normal = ts.normalize(n) view = ts.normalize(a + n) color = ts.vec3(1.0) color = model.colorize(pos, normal, color) camera.fb['img'][X] = color camera.fb['normal'][X] = normal
def sample(field: ti.template(), P): ''' Sampling a field with indices clampped into the field shape. :parameter field: (Tensor) Specify the field to sample. :parameter P: (Vector) Specify the index in field. :return: The return value is calcuated as:: P = clamp(P, 0, vec(*field.shape) - 1) return field[int(P)] ''' shape = ti.Vector(field.shape()) P = ts.clamp(P, 0, shape - 1) return field[int(P)]
def mandelbox(z): offset = z dz = 1.0 for _ in range(0, 10): # box fold z = ts.clamp(z, -1.0, 1.0) * 2.0 - z # ball fold r2 = ts.dot(z, z) if r2 < min_radius[None]: tmp = (fix_radius[None] / min_radius[None]) z *= tmp dz *= tmp elif r2 < fix_radius[None]: tmp = fix_radius[None] / r2 z *= tmp dz *= tmp z = scale * z + offset dz = dz * ti.abs(scale) + 1.0 return ts.length(z) / ti.abs(dz)
def get_random_light_prim_index(self): index = (int)(ts.clamp(ti.random() * self.light_count, 0.0, self.light_count)) - 1 return self.light[index]
def render_triangle(model, camera, face): scene = model.scene L2W = model.L2W posa, posb, posc = model.pos[face[0, 0]], model.pos[face[1, 0]], model.pos[ face[2, 0]] texa, texb, texc = model.tex[face[0, 1]], model.tex[face[1, 1]], model.tex[ face[2, 1]] nrma, nrmb, nrmc = model.nrm[face[0, 2]], model.nrm[face[1, 2]], model.nrm[ face[2, 2]] posa = camera.untrans_pos(L2W @ posa) posb = camera.untrans_pos(L2W @ posb) posc = camera.untrans_pos(L2W @ posc) nrma = camera.untrans_dir(L2W.matrix @ nrma) nrmb = camera.untrans_dir(L2W.matrix @ nrmb) nrmc = camera.untrans_dir(L2W.matrix @ nrmc) pos_center = (posa + posb + posc) / 3 if ti.static(camera.type == camera.ORTHO): pos_center = ts.vec3(0.0, 0.0, 1.0) dpab = posa - posb dpac = posa - posc dtab = texa - texb dtac = texa - texc normal = ts.cross(dpab, dpac) tan, bitan = compute_tangent(-dpab, -dpac, -dtab, -dtac) # NOTE: the normal computation indicates that a front-facing face should # be COUNTER-CLOCKWISE, i.e., glFrontFace(GL_CCW); # this is to be compatible with obj model loading. if ts.dot(pos_center, normal) <= 0: clra = model.vertex_shader(posa, texa, nrma, tan, bitan) clrb = model.vertex_shader(posb, texb, nrmb, tan, bitan) clrc = model.vertex_shader(posc, texc, nrmc, tan, bitan) A = camera.uncook(posa) B = camera.uncook(posb) C = camera.uncook(posc) scr_norm = 1 / ts.cross(A - C, B - A) B_A = (B - A) * scr_norm C_B = (C - B) * scr_norm A_C = (A - C) * scr_norm # screen space bounding box M = int(ti.floor(min(A, B, C) - 1)) N = int(ti.ceil(max(A, B, C) + 1)) 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))): # barycentric coordinates using the area method X_A = X - A w_C = ts.cross(B_A, X_A) w_B = ts.cross(A_C, X_A) w_A = 1 - w_C - w_B # draw eps = ti.get_rel_eps() * 0.2 is_inside = w_A >= -eps and w_B >= -eps and w_C >= -eps if not is_inside: continue zindex = 1 / (posa.z * w_A + posb.z * w_B + posc.z * w_C) if zindex < ti.atomic_max(camera.fb['idepth'][X], zindex): continue clr = [ a * w_A + b * w_B + c * w_C for a, b, c in zip(clra, clrb, clrc) ] camera.fb.update(X, model.pixel_shader(*clr))
def sample(data, pos): tot = data.shape # static unfold for efficiency if ti.static(len(data.shape) == 2): i, j = ts.clamp(int(pos[0]), 0, tot[0] - 1), ts.clamp(int(pos[1]), 0, tot[1] - 1) ip, jp = ts.clamp(i + 1, 0, tot[0] - 1), ts.clamp(j + 1, 0, tot[1] - 1) s, t = ts.clamp(pos[0] - i, 0.0, 1.0), ts.clamp(pos[1] - j, 0.0, 1.0) return \ (data[i, j] * (1 - s) + data[ip, j] * s) * (1 - t) + \ (data[i, jp] * (1 - s) + data[ip, jp] * s) * t else: i, j, k = ts.clamp(int(pos[0]), 0, tot[0] - 1), ts.clamp( int(pos[1]), 0, tot[1] - 1), ts.clamp(int(pos[2]), 0, tot[2] - 1) ip, jp, kp = ts.clamp(i + 1, 0, tot[0] - 1), ts.clamp( j + 1, 0, tot[1] - 1), ts.clamp(k + 1, 0, tot[2] - 1) s, t, u = ts.clamp(pos[0] - i, 0.0, 1.0), ts.clamp(pos[1] - j, 0.0, 1.0), ts.clamp(pos[2] - k, 0.0, 1.0) return \ ((data[i, j, k] * (1 - s) + data[ip, j, k] * s) * (1 - t) + \ (data[i, jp, k] * (1 - s) + data[ip, jp, k] * s) * t) * (1 - u) + \ ((data[i, j, kp] * (1 - s) + data[ip, j, kp] * s) * (1 - t) + \ (data[i, jp, kp] * (1 - s) + data[ip, jp, kp] * s) * t) * u
def render_triangle(model, camera, face): posa, posb, posc = face.pos # Position texa, texb, texc = face.tex # TexCoord nrma, nrmb, nrmc = face.nrm # Normal pos_center = (posa + posb + posc) / 3 if ti.static(camera.type == camera.ORTHO): pos_center = ts.vec3(0.0, 0.0, 1.0) # NOTE: the normal computation indicates that a front-facing face should # be COUNTER-CLOCKWISE, i.e., glFrontFace(GL_CCW); # this is to be compatible with obj model loading. if ts.dot(pos_center, ts.cross(posa - posc, posa - posb)) >= 0: tan, bitan = compute_tangent(posb - posa, posc - posa, texb - texa, texc - texa) # TODO: node-ize this clra = [posa, texa, nrma] # TODO: interpolate tan and bitan? merge with nrm? clrb = [posb, texb, nrmb] clrc = [posc, texc, nrmc] A = camera.uncook(posa) B = camera.uncook(posb) C = camera.uncook(posc) scr_norm = ts.cross(A - C, B - A) if scr_norm != 0: # degenerate to 'line' if zero B_A = (B - A) / scr_norm A_C = (A - C) / scr_norm shake = ts.vec2(0.0) if ti.static(camera.fb.n_taa): for i, s in ti.static(enumerate(map(ti.Vector, TAA_SHAKES[:camera.fb.n_taa]))): if camera.fb.itaa[None] == i: shake = s * 0.5 # screen space bounding box M = int(ti.floor(min(A, B, C) - 1)) N = int(ti.ceil(max(A, B, C) + 1)) M = ts.clamp(M, 0, ti.Vector(camera.fb.res)) N = ts.clamp(N, 0, ti.Vector(camera.fb.res)) for X in ti.grouped(ti.ndrange((M.x, N.x), (M.y, N.y))): # barycentric coordinates using the area method X_A = X - A + shake w_C = ts.cross(B_A, X_A) w_B = ts.cross(A_C, X_A) w_A = 1 - w_C - w_B # draw eps = ti.get_rel_eps() * 0.2 is_inside = w_A >= -eps and w_B >= -eps and w_C >= -eps if not is_inside: continue # https://gitee.com/zxtree2006/tinyrenderer/blob/master/our_gl.cpp if ti.static(camera.type != camera.ORTHO): bclip = ts.vec3(w_A / posa.z, w_B / posb.z, w_C / posc.z) bclip /= bclip.x + bclip.y + bclip.z w_A, w_B, w_C = bclip depth = (posa.z * w_A + posb.z * w_B + posc.z * w_C) if camera.fb.atomic_depth(X, depth): continue posx, texx, nrmx = [a * w_A + b * w_B + c * w_C for a, b, c in zip(clra, clrb, clrc)] color = ti.static(model.material.pixel_shader(model, posx, texx, nrmx, tan, bitan)) if ti.static(isinstance(color, dict)): camera.fb.update(X, color) else: camera.fb.update(X, dict(img=color))
def apply_grad(self, lr: float, gamma: float, max_grad: float): for i in self.tf_tex: self.tf_momentum[i] = gamma * self.tf_momentum[i] + \ lr * tl.clamp(self.tf_tex.grad[i], -max_grad, max_grad) self.tf_tex[i] -= self.tf_momentum[i] self.tf_tex[i] = ti.max(self.tf_tex[i], 0)
def smoothstep(edge0, edge1, x): t = ts.clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0) return t * t * (3 - 2 * t)
def render_pixels(): for i in range(particle_table_len[None]): position = particle_pos[i].xy pix = int(position * kResolution) display_image[tl.clamp(pix, 0, kResolution - 1)] += 0.3
def SchlickFresnel(self, u): m = ts.clamp(1.0 - u, 0.0, 1.0) m2 = m * m return m2 * m2 * m
def get_normal_at(self, i): xa = self.pos[ts.clamp(i + ts.D.x_, 0, ts.vec2(*self.shape))] xb = self.pos[ts.clamp(i + ts.D.X_, 0, ts.vec2(*self.shape))] ya = self.pos[ts.clamp(i + ts.D._x, 0, ts.vec2(*self.shape))] yb = self.pos[ts.clamp(i + ts.D._X, 0, ts.vec2(*self.shape))] return (ya - yb).cross(xa - xb).normalized()
def render_triangle(model, camera, face): scene = model.scene L2C = model.L2C[None] # Local to Camera, i.e. ModelView in OpenGL posa, posb, posc = face.pos texa, texb, texc = face.tex nrma, nrmb, nrmc = face.nrm 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 pos_center = (posa + posb + posc) / 3 if ti.static(camera.type == camera.ORTHO): pos_center = ts.vec3(0.0, 0.0, 1.0) dpab = posa - posb dpac = posa - posc dtab = texa - texb dtac = texa - texc normal = ts.cross(dpab, dpac) # NOTE: the normal computation indicates that a front-facing face should # be COUNTER-CLOCKWISE, i.e., glFrontFace(GL_CCW); # this is to be compatible with obj model loading. if ts.dot(pos_center, normal) <= 0: tan, bitan = compute_tangent(-dpab, -dpac, -dtab, -dtac) # TODO: node-ize this clra = model.vertex_shader( posa, texa, nrma, tan, bitan) # TODO: interpolate tan and bitan? merge with nrm? clrb = model.vertex_shader(posb, texb, nrmb, tan, bitan) clrc = model.vertex_shader(posc, texc, nrmc, tan, bitan) A = camera.uncook(posa) B = camera.uncook(posb) C = camera.uncook(posc) scr_norm = ts.cross(A - C, B - A) if scr_norm != 0: # degenerate to 'line' if zero B_A = (B - A) / scr_norm A_C = (A - C) / scr_norm shake = ts.vec2(0.0) if ti.static(camera.fb.n_taa): for i, s in ti.static( enumerate(map(ti.Vector, TAA_SHAKES[:camera.fb.n_taa]))): if camera.fb.itaa[None] == i: shake = s * 0.5 # screen space bounding box M = int(ti.floor(min(A, B, C) - 1)) N = int(ti.ceil(max(A, B, C) + 1)) M = ts.clamp(M, 0, ti.Vector(camera.fb.res)) N = ts.clamp(N, 0, ti.Vector(camera.fb.res)) for X in ti.grouped(ti.ndrange((M.x, N.x), (M.y, N.y))): # barycentric coordinates using the area method X_A = X - A + shake w_C = ts.cross(B_A, X_A) w_B = ts.cross(A_C, X_A) w_A = 1 - w_C - w_B # draw eps = ti.get_rel_eps() * 0.2 is_inside = w_A >= -eps and w_B >= -eps and w_C >= -eps if not is_inside: continue # https://gitee.com/zxtree2006/tinyrenderer/blob/master/our_gl.cpp if ti.static(camera.type != camera.ORTHO): bclip = ts.vec3(w_A / posa.z, w_B / posb.z, w_C / posc.z) bclip /= bclip.x + bclip.y + bclip.z w_A, w_B, w_C = bclip depth = (posa.z * w_A + posb.z * w_B + posc.z * w_C) if camera.fb.atomic_depth(X, depth): continue clr = [ a * w_A + b * w_B + c * w_C for a, b, c in zip(clra, clrb, clrc) ] camera.fb.update(X, model.pixel_shader(*clr))