def render_photon_map(t: ti.i32, offset_x: ti.f32, offset_y: ti.f32): for i in range(n_grid): # Parallelized over GPU threads for j in range(n_grid): grad = height_gradient[i, j] * (1 - offset_x) * (1 - offset_y) + \ height_gradient[i + 1, j] * offset_x * (1 - offset_y) + \ height_gradient[i, j + 1] * (1 - offset_x) * offset_y + \ height_gradient[i + 1, j + 1] * offset_x * offset_y scale = 5.0 sample_x = i - grad[0] * scale + offset_x sample_y = j - grad[1] * scale + offset_y sample_x = ti.min(n_grid - 1, ti.max(0, sample_x)) sample_y = ti.min(n_grid - 1, ti.max(0, sample_y)) sample_xi = ti.cast(ti.floor(sample_x), ti.i32) sample_yi = ti.cast(ti.floor(sample_y), ti.i32) frac_x = sample_x - sample_xi frac_y = sample_y - sample_yi x = sample_xi y = sample_yi ti.atomic_add(rendered[x, y], (1 - frac_x) * (1 - frac_y)) ti.atomic_add(rendered[x, y + 1], (1 - frac_x) * frac_y) ti.atomic_add(rendered[x + 1, y], frac_x * (1 - frac_y)) ti.atomic_add(rendered[x + 1, y + 1], frac_x * frac_y)
def advect(field: ti.template(), field_out: ti.template(), t_offset: ti.template(), t: ti.i32): """Move field smoke according to x and y velocities (vx and vy) using an implicit Euler integrator.""" for y in range(n_grid): for x in range(n_grid): center_x = y - v[t + t_offset, y, x][0] center_y = x - v[t + t_offset, y, x][1] # Compute indices of source cell left_ix = ti.cast(ti.floor(center_x), ti.i32) top_ix = ti.cast(ti.floor(center_y), ti.i32) rw = center_x - left_ix # Relative weight of right-hand cell bw = center_y - top_ix # Relative weight of bottom cell # Wrap around edges # TODO: implement mod (%) operator left_ix = imod(left_ix, n_grid) right_ix = inc_index(left_ix) top_ix = imod(top_ix, n_grid) bot_ix = inc_index(top_ix) # Linearly-weighted sum of the 4 surrounding cells field_out[t, y, x] = (1 - rw) * ( (1 - bw) * field[t - 1, left_ix, top_ix] + bw * field[t - 1, left_ix, bot_ix]) + rw * ( (1 - bw) * field[t - 1, right_ix, top_ix] + bw * field[t - 1, right_ix, bot_ix])
def render_refract(t: ti.i32): for i in range(n_grid): # Parallelized over GPU threads for j in range(n_grid): grad = height_gradient[i, j] scale = 2.0 sample_x = i - grad[0] * scale sample_y = j - grad[1] * scale sample_x = ti.min(n_grid - 1, ti.max(0, sample_x)) sample_y = ti.min(n_grid - 1, ti.max(0, sample_y)) sample_xi = ti.cast(ti.floor(sample_x), ti.i32) sample_yi = ti.cast(ti.floor(sample_y), ti.i32) frac_x = sample_x - sample_xi frac_y = sample_y - sample_yi for k in ti.static(range(3)): refracted_image[i, j, k] = (1.0 - frac_x) * ( (1 - frac_y) * bottom_image[sample_xi, sample_yi, k] + frac_y * bottom_image[ sample_xi, sample_yi + 1, k]) + frac_x * ( (1 - frac_y) * bottom_image[ sample_xi + 1, sample_yi, k] + frac_y * bottom_image[ sample_xi + 1, sample_yi + 1, k] )
def grid_to_particles(): for p in particles.velocity: pos = particles.position[p] pos[0] = max(epsilon,min(grid_size_x*cell_size-epsilon,pos[0])) pos[1] = max(epsilon,min(grid_size_y*cell_size-epsilon,pos[1])) x = ti.cast(ti.floor(pos[0] / cell_size),ti.i32) y = ti.cast(ti.floor(pos[1] / cell_size),ti.i32) vel = ti.Vector([0.0,0.0]) cx = ti.Vector([0.0,0.0]) cy = ti.Vector([0.0,0.0]) sum_weights = ti.Vector([0.0,0.0]) for i,j in ti.ndrange(3,3): this_x = x+i-1 this_y = y+j-1 if this_x < 0 or this_x >= grid_size_x+1 or this_y < 0 or this_y >= grid_size_y+1: continue x_vel_pos = ti.Vector([this_x,this_y+0.5]) * cell_size y_vel_pos = ti.Vector([this_x+0.5,this_y]) * cell_size x_weight = kernel_2d(x_vel_pos-pos,cell_size) y_weight = kernel_2d(y_vel_pos-pos,cell_size) if y_vel_pos[0] > grid_size_x*cell_size: y_weight = 0 if x_vel_pos[1] > grid_size_y*cell_size: x_weight = 0 weights = ti.Vector([x_weight,y_weight]) velocity_contribution = grid.velocity[this_x,this_y] * weights #cx += grid.velocity[this_x,this_y][0] * x_weight * (x_vel_pos-pos) / (cell_size ** 2) #cy += grid.velocity[this_x,this_y][1] * y_weight * (y_vel_pos-pos) / (cell_size ** 2) cx += grid.velocity[this_x,this_y][0] * kernel_grad(x_vel_pos-pos,cell_size) * -1 cy += grid.velocity[this_x,this_y][1] * kernel_grad(y_vel_pos-pos,cell_size) * -1 vel += velocity_contribution sum_weights += weights if sum_weights[0] > 0: vel[0] /= sum_weights[0] cx /= sum_weights[0] if sum_weights[1] > 0: vel[1] /= sum_weights[1] cy /= sum_weights[1] particles.velocity[p] = vel particles.cx[p] = cx particles.cy[p] = cy
def sample_minmax(vf, p): # 2d case u, v = p s, t = u - 0.5, v - 0.5 iu, iv = ti.floor(s), ti.floor(t) a = sample(vf, iu, iv) b = sample(vf, iu + 1, iv) c = sample(vf, iu, iv + 1) d = sample(vf, iu + 1, iv + 1) return min(a, b, c, d), max(a, b, c, d)
def voxelize_triangles(self, num_triangles: ti.i32, triangles: ti.ext_arr()): for i in range(num_triangles): jitter_scale = ti.cast(0, self.precision) if ti.static(self.precision is ti.f32): jitter_scale = 1e-4 else: jitter_scale = 1e-8 # We jitter the vertices to prevent voxel samples from lying precicely at triangle edges jitter = ti.Vector([-0.057616723909439505, -0.25608986292614977, 0.06716309129743714]) * jitter_scale a = ti.Vector([triangles[i, 0], triangles[i, 1], triangles[i, 2]]) + jitter b = ti.Vector([triangles[i, 3], triangles[i, 4], triangles[i, 5]]) + jitter c = ti.Vector([triangles[i, 6], triangles[i, 7], triangles[i, 8]]) + jitter bound_min = ti.Vector.zero(self.precision, 3) bound_max = ti.Vector.zero(self.precision, 3) for k in ti.static(range(3)): bound_min[k] = min(a[k], b[k], c[k]) bound_max[k] = max(a[k], b[k], c[k]) p_min = int(ti.floor(bound_min[0] * self.inv_dx)) p_max = int(ti.floor(bound_max[0] * self.inv_dx)) + 1 p_min = max(self.padding, p_min) p_max = min(self.res[0] - self.padding, p_max) q_min = int(ti.floor(bound_min[1] * self.inv_dx)) q_max = int(ti.floor(bound_max[1] * self.inv_dx)) + 1 q_min = max(self.padding, q_min) q_max = min(self.res[1] - self.padding, q_max) normal = ti.normalized(ti.cross(b - a, c - a)) if abs(normal[2]) < 1e-10: continue a_proj = ti.Vector([a[0], a[1]]) b_proj = ti.Vector([b[0], b[1]]) c_proj = ti.Vector([c[0], c[1]]) for p in range(p_min, p_max): for q in range(q_min, q_max): pos2d = ti.Vector([(p + 0.5) * self.dx, (q + 0.5) * self.dx]) if inside_ccw(pos2d, a_proj, b_proj, c_proj) or inside_ccw(pos2d, a_proj, c_proj, b_proj): base_voxel = ti.Vector([pos2d[0], pos2d[1], 0]) height = int( -ti.dot(normal, base_voxel - a) / normal[2] * self.inv_dx + 0.5) height = min(height, self.res[1] - self.padding) inc = 0 if normal[2] > 0: inc = 1 else: inc = -1 self.fill(p, q, height, inc)
def insert(): ti.block_dim(256) for i in x: # It is important to ensure insert and p2g uses the exact same way to compute the base # coordinates. Otherwise there might be coordinate mismatch due to float-point errors. base = ti.Vector([ int(ti.floor(x[i][0] * N) - grid_offset[0]), int(ti.floor(x[i][1] * N) - grid_offset[1]) ]) ti.append(pid.parent(), base, i)
def make_nested(f): f = f * 40 i = int(f) if f < 0: if i % 2 == 1: f -= ti.floor(f) else: f = ti.floor(f) + 1 - f f = (f - 0.2) / 40 return f
def bilerp(vf, p): u, v = p s, t = u - 0.5, v - 0.5 iu, iv = ti.floor(s), ti.floor(t) #frac fu, fv = s - iu, t - iv a = sample(vf, iu, iv) b = sample(vf, iu + 1, iv) c = sample(vf, iu, iv + 1) d = sample(vf, iu + 1, iv + 1) return lerp(lerp(a, b, fu), lerp(c, d, fu), fv)
def bilerp(vf, p): u, v = p s, t = u - 0.5, v - 0.5 # floor iu, iv = ti.floor(s), ti.floor(t) # fract fu, fv = s - iu, t - iv a = sample(vf, iu + 0.5, iv + 0.5) b = sample(vf, iu + 1.5, iv + 0.5) c = sample(vf, iu + 0.5, iv + 1.5) d = sample(vf, iu + 1.5, iv + 1.5) return lerp(lerp(a, b, fu), lerp(c, d, fu), fv)
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 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 paintArrow(img: ti.template(), orig, dir, color=1, width=3, max_size=12, min_scale=0.4): res = ts.vec(*img.shape) I = orig * res D = dir * res J = I + D DL = ts.length(D) S = min(max_size, DL * min_scale) DS = D / (DL + 1e-4) * S SW = S + width D1 = ti.Matrix.rotation2d(+math.pi * 3 / 4) @ DS D2 = ti.Matrix.rotation2d(-math.pi * 3 / 4) @ DS bmin, bmax = ti.floor(max(0, min(I, J) - SW)), ti.ceil( min(res - 1, max(I, J) + SW)) for P in ti.grouped(ti.ndrange((bmin.x, bmax.x), (bmin.y, bmax.y))): c0 = ts.smoothstep(abs(sdLine(I, J, P)), width, width / 2) c1 = ts.smoothstep(abs(sdLine(J, J + D1, P)), width, width / 2) c2 = ts.smoothstep(abs(sdLine(J, J + D2, P)), width, width / 2) ti.atomic_max(img[P], max(c0, c1, c2) * color)
def fract(x): """ Дробная часть числа :param x: значение :return: дробная часть """ return x - ti.floor(x)
def sample_max(self, qf: ti.template(), coord): grid = coord * self.inv_dx - ti.Vector([0.5, 0.5, 0.5]) I = ti.cast(ti.floor(grid), ti.i32) max_val = qf[I] for i, j, k in ti.ndrange(2, 2, 2): max_val = max(max_val, qf[I + ti.Vector([i, j, k])]) return max_val
def g2p(self, dt: ti.f32): ti.block_dim(256) ti.block_local(*self.grid_v.entries) ti.no_activate(self.particle) for I in ti.grouped(self.pid): p = self.pid[I] base = ti.floor(self.x[p] * self.inv_dx - 0.5).cast(int) for D in ti.static(range(self.dim)): base[D] = ti.assume_in_range(base[D], I[D], 0, 1) fx = self.x[p] * self.inv_dx - base.cast(float) w = [ 0.5 * (1.5 - fx)**2, 0.75 - (fx - 1.0)**2, 0.5 * (fx - 0.5)**2 ] new_v = ti.Vector.zero(ti.f32, self.dim) new_C = ti.Matrix.zero(ti.f32, self.dim, self.dim) # loop over 3x3 grid node neighborhood for offset in ti.static(ti.grouped(self.stencil_range())): dpos = offset.cast(float) - fx g_v = self.grid_v[base + offset] weight = 1.0 for d in ti.static(range(self.dim)): weight *= w[offset[d]][d] new_v += weight * g_v new_C += 4 * self.inv_dx * weight * g_v.outer_product(dpos) self.v[p], self.C[p] = new_v, new_C self.x[p] += dt * self.v[p] # advection
def p2g_naive(): ti.block_dim(256) for p in x: u = ti.floor(x[p] * N).cast(ti.i32) for offset in ti.static(ti.grouped(ti.ndrange(extend, extend))): m3[u + offset] += scatter_weight
def bilerp(f: ti.template(), pos): p = float(pos) I = int(ti.floor(p)) x = p - I y = 1 - x return (f[I + V(1, 1)] * x[0] * x[1] + f[I + V(1, 0)] * x[0] * y[1] + f[I + V(0, 0)] * y[0] * y[1] + f[I + V(0, 1)] * y[0] * x[1])
def kernel(angle: ti.f32, view_id: ti.i32): for pixel in range(res * res): for k in range(marching_steps): x = pixel // res y = pixel - x * res camera_origin = ti.Vector([ camera_origin_radius * ti.sin(angle), 0, camera_origin_radius * ti.cos(angle) ]) dir = ti.Vector([ fov * (ti.cast(x, ti.f32) / (res_f32 / 2.0) - res_f32 / res_f32), fov * (ti.cast(y, ti.f32) / (res_f32 / 2.0) - 1.0), -1.0 ]) length = ti.sqrt(dir[0] * dir[0] + dir[1] * dir[1] + dir[2] * dir[2]) dir /= length rotated_x = dir[0] * ti.cos(angle) + dir[2] * ti.sin(angle) rotated_z = -dir[0] * ti.sin(angle) + dir[2] * ti.cos(angle) dir[0] = rotated_x dir[2] = rotated_z point = camera_origin + (k + 1) * dx * dir # Convert to coordinates of the density grid box box_x = point[0] + 0.5 box_y = point[1] + 0.5 box_z = point[2] + 0.5 # Density grid location index_x = ti.cast(ti.floor(box_x * density_res), ti.i32) index_y = ti.cast(ti.floor(box_y * density_res), ti.i32) index_z = ti.cast(ti.floor(box_z * density_res), ti.i32) index_x = ti.max(0, ti.min(index_x, density_res - 1)) index_y = ti.max(0, ti.min(index_y, density_res - 1)) index_z = ti.max(0, ti.min(index_z, density_res - 1)) flag = 0 if in_box(point[0], point[1], point[2]): flag = 1 contribution = density[index_z, index_y, index_x] * flag ti.atomic_add(field[view_id, y, x], contribution)
def halton(b, i): r = 0.0 f = 1.0 while i > 0: f = f / float(b) r = r + f * float(i % b) i = int(ti.floor(float(i) / float(b))) return r
def bilinearInterpolation(x, y): x_floor = ti.floor(x) x_dec = x - x_floor y_new = y[int(x_floor)] * (1. - x_dec[0]) * (1. - x_dec[1]) \ + y[int(x_floor) + ti.Vector([1, 0], ti.int32)] * x_dec[0] * (1. - x_dec[1]) \ + y[int(x_floor) + ti.Vector([0, 1], ti.int32)] * (1. - x_dec[0]) * x_dec[1] \ + y[int(x_floor) + 1] * x_dec[0] * x_dec[1] return y_new
def noise(p): i = ti.floor(p) a = i.dot(vec(1.0, 57.0, 21.0)) + vec(0.0, 57.0, 21.0, 78.0) f = ti.cos((p - i) * math.acos(-1.0)) * (-0.5) + 0.5 a = mix(ti.sin(ti.cos(a) * a), ti.sin(ti.cos(1.0 + a) * (1.0 + a)), f[0]) a[0] = mix(a[0], a[1], f[1]) a[1] = mix(a[2], a[3], f[1]) return mix(a[0], a[1], f[2])
def sample_max(field, p): p = clamp(p) grid_f = p * n - stagger grid_i = ti.cast(ti.floor(grid_f), ti.i32) return max( field[ grid_i ], field[ grid_i+I(1, 0) ], field[ grid_i+I(0, 1) ], field[ grid_i+I(1, 1) ] )
def bilerp(f: ti.template(), pos): p = float(pos) I = int(ti.floor(p)) x = p - I y = 1 - x ti.static_assert(len(f.meta.shape) == 2) return (f[I + V(1, 1)] * x[0] * x[1] + f[I + V(1, 0)] * x[0] * y[1] + f[I + V(0, 0)] * y[0] * y[1] + f[I + V(0, 1)] * y[0] * x[1])
def g2p_naive(s: ti.template()): ti.block_dim(256) for p in x: u = ti.floor(x[p] * N).cast(ti.i32) tot = 0.0 for offset in ti.static(ti.grouped(ti.ndrange(extend, extend))): tot += m1[u + offset] s[p] = tot
def pos_to_stagger_idx(pos, stagger): pos[0] = clamp(pos[0], stagger[0] * grid_x, w - 1e-4 - grid_x + stagger[0] * grid_x) pos[1] = clamp(pos[1], stagger[1] * grid_y, h - 1e-4 - grid_y + stagger[1] * grid_y) p_grid = pos / vec2(grid_x, grid_y) - stagger I = ti.cast(ti.floor(p_grid), ti.i32) return I, p_grid
def noise(p): i = ti.floor(p) f = fract(p) u = f * f * (3.0 - 2.0 * f) v1 = mix(hash(i + ti.Vector([0.0, 0.0])), hash(i + ti.Vector([1.0, 0.0])), u[0]) v2 = mix(hash(i + ti.Vector([0.0, 1.0])), hash(i + ti.Vector([1.0, 1.0])), u[0]) v3 = mix(v1, v2, u[1]) return -1.0 + 2.0 * v3
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 sample_bilinear(field, p): p = clamp(p) grid_f = p * n - stagger grid_i = ti.cast(ti.floor(grid_f), ti.i32) d = grid_f - grid_i return field[ grid_i ] * (1-d.x)*(1-d.y) + field[ grid_i+I(1, 0) ] * d.x*(1-d.y) + field[ grid_i+I(0, 1) ] * (1-d.x)*d.y + field[ grid_i+I(1, 1) ] * d.x*d.y
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)