def my_render_func(pos, normal, dir, light_dir): n = 4 # cartoon level #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, max(0, ts.dot(normal, light_dir)), 0.6) strength = 0.2 if any(normal): strength = ti.floor(max(0, NoL * n + 0.5)) / n return ts.vec3(strength)
def evaluate(self, N, V, L, mat_buf, mat_id): outputC = ti.Vector([0.0, 0.0, 0.0]) pdf = -1.0 NDotL = N.dot(L) NDotV = N.dot(V) if ((NDotL > 0.0) & (NDotV > 0.0)): H = (L + V).normalized() NDotH = H.dot(N) LDotH = H.dot(L) VDotH = H.dot(V) Cdlin = UF.get_material_color(mat_buf, mat_id) metal = UF.get_material_metallic(mat_buf, mat_id) rough = UF.get_material_roughness(mat_buf, mat_id) Cdlum = 0.3 * Cdlin.x + 0.6 * Cdlin.y + 0.1 * Cdlin.z # spec=0.5 spectint=0 sheentint=0.5 Ctint = ti.Vector([1.0, 1.0, 1.0]) if Cdlum > 0.0: Ctint = Cdlin / Cdlum Cspec0 = ts.mix(ti.Vector([0.04, 0.04, 0.04]), Cdlin, metal) Csheen = ts.mix(ti.Vector([1.0, 1.0, 1.0]), Ctint, 1.0) FL = self.SchlickFresnel(NDotL) FV = self.SchlickFresnel(NDotV) Fd90 = 0.5 + 2.0 * LDotH * LDotH * rough Fd = ts.mix(1.0, Fd90, FL) * ts.mix(1.0, Fd90, FV) Fss90 = LDotH * LDotH * rough Fss = ts.mix(1.0, Fss90, FL) * ts.mix(1.0, Fss90, FV) ss = 1.25 * (Fss * (1.0 / (NDotL + NDotV) - 0.5) + 0.5) specularAlpha = max(0.001, rough) Ds = self.GTR2(NDotH, specularAlpha) FH = self.SchlickFresnel(LDotH) Fs = ts.mix(Cspec0, ti.Vector([1.0, 1.0, 1.0]), FH) roughg = self.sqr(rough * 0.5 + 0.5) Gs = self.smithG_GGX(NDotL, roughg) * self.smithG_GGX( NDotV, roughg) Fsheen = FH * Csheen Dr = self.GTR1(NDotH, 0.001) Fr = ts.mix(0.04, 1.0, FH) Gr = self.smithG_GGX(NDotL, 0.25) * self.smithG_GGX(NDotV, 0.25) outputC = (Fsheen + Cdlin * (1.0 / M_PIf)) * Fd * (1.0 - metal) + Gs * Fs * Ds outputC *= NDotL diffuseRatio = 0.5 * (1.0 - metal) specularRatio = 1.0 - diffuseRatio pdfGTR2 = Ds * NDotH pdfGTR1 = Dr * NDotH pdfSpec = pdfGTR2 / (4.0 * abs(LDotH)) pdfDiff = NDotL / M_PIf pdf = diffuseRatio * pdfDiff + specularRatio * pdfSpec return outputC, pdf
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(t: ti.f32): for i, j in pixels: p = ti.Vector([2.0 * i - WIDTH, 2.0 * j - HEIGHT]) / min(WIDTH, HEIGHT) # background-color bcol = ti.Vector([1.0, 0.8, 0.7 - 0.07 * p[1] ]) * (1.0 - 0.25 * p.norm()) # animate tt = mod(t, 1.5) / 1.5 ss = pow(tt, 0.2) * 0.5 + 0.5 ss = 1.0 + ss * 0.5 * ti.sin(tt * 6.283184 * 3.0 + p[1] * 0.5) * ti.exp(-4.0 * tt) p *= ti.Vector([0.5, 1.5]) + ss * ti.Vector([0.5, -0.5]) # shape p[1] -= 0.25 a = ti.atan2(p[0], p[1]) / 3.141592 r = p.norm() h = abs(a) d = (13.0 * h - 22.0 * h * h + 10.0 * h * h * h) / (6.0 - 5.0 * h) # color s = 0.75 + 0.75 * p[0] s *= 1.0 - 0.4 * r s = 0.3 + 0.7 * s s *= 0.5 + 0.5 * pow(1.0 - clamp(r / d, 0.0, 1.0), 0.1) hcol = ti.Vector([1.0, 0.5 * r, 0.3]) * s pixels[i, j] = ts.mix(bcol, hcol, smoothstep(-0.01, 0.01, d - r))
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 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 raymarch(ro, rd): p = ro glow = 0.0 for _ in range(700): dS = getDist(p) glow = max(glow, 1.0 / (dS + 1.0)) bdir = -1.0 * p.normalized() bdist = p.norm() dS = min(dS, bdist) * 0.04 if dS > 30.0: break if bdist < 1.0: break bdist = pow(bdist + 1.0, 2.0) bdist = dS * 1.0 / bdist rd = ts.mix(rd, bdir, bdist) p += rd * max(dS, 0.01) gcol = getGlow(glow) c_rgb = ts.mix(ti.Vector([0.0, 0.0, 0.0]), ti.Vector([gcol[0], gcol[1], gcol[2]]), gcol[3]) return ti.Vector([c_rgb[0], c_rgb[1], c_rgb[2], 1.0])
def blueorange(rgb): ''' Convert RGB value (vector) into blue-orange colormap (vector). :parameter rgb: (3D vector) The RGB value, X compoment is the R value, can be eitier float or int. :return: The return value is calculated as `sqrt(mix(vec(0, 0.01, 0.05), vec(1.19, 1.04, 0.98), color))`. ''' blue = ts.vec(0.00, 0.01, 0.05) orange = ts.vec(1.19, 1.04, 0.98) return ti.sqrt(ts.mix(blue, orange, rgb))
def apply_transfer_function(self, intensity: float): ''' Applies a 1D transfer function to a given intensity value Args: intensity (float): Intensity in [0,1] Returns: tl.vec4: Color and opacity for given `intensity` ''' length = ti.static(float(self.tf_tex.shape[0] - 1)) low, high, frac = low_high_frac(intensity * length) return tl.mix( self.tf_tex[low], self.tf_tex[min(high, ti.static(self.tf_tex.shape[0] - 1))], frac)
def cube(self, a, b): aaa = self.add_v(tl.mix(a, b, tl.D.yyy)) baa = self.add_v(tl.mix(a, b, tl.D.xyy)) aba = self.add_v(tl.mix(a, b, tl.D.yxy)) aab = self.add_v(tl.mix(a, b, tl.D.yyx)) bba = self.add_v(tl.mix(a, b, tl.D.xxy)) abb = self.add_v(tl.mix(a, b, tl.D.yxx)) bab = self.add_v(tl.mix(a, b, tl.D.xyx)) bbb = self.add_v(tl.mix(a, b, tl.D.xxx)) self.add_f4([aaa, aba, bba, baa]) # back self.add_f4([aab, bab, bbb, abb]) # front self.add_f4([aaa, aab, abb, aba]) # left self.add_f4([baa, bba, bbb, bab]) # right self.add_f4([aaa, baa, bab, aab]) # bottom self.add_f4([aba, abb, bbb, bba]) # top
def raycast(self, sampling_rate: float): ''' Produce a rendering. Run compute_entry_exit first! ''' for i, j in self.valid_sample_step_count: # For all pixels for sample_idx in range(self.sample_step_nums[i, j]): look_from = self.cam_pos[None] if self.render_tape[i, j, sample_idx - 1].w < 0.99 and sample_idx < ti.static( self.max_samples): tmax = self.exit[i, j] n_samples = self.sample_step_nums[i, j] ray_len = (tmax - self.entry[i, j]) tmin = self.entry[ i, j] + 0.5 * ray_len / n_samples # Offset tmin as t_start vd = self.rays[i, j] pos = look_from + tl.mix( tmin, tmax, float(sample_idx) / float(n_samples - 1)) * vd # Current Pos light_pos = look_from + tl.vec3(0.0, 1.0, 0.0) intensity = self.sample_volume_trilinear(pos) sample_color = self.apply_transfer_function(intensity) opacity = 1.0 - ti.pow(1.0 - sample_color.w, 1.0 / sampling_rate) # if sample_color.w > 1e-3: normal = self.get_volume_normal(pos) light_dir = ( pos - light_pos).normalized() # Direction to light source n_dot_l = max(normal.dot(light_dir), 0.0) diffuse = self.diffuse * n_dot_l r = tl.reflect(light_dir, normal) # Direction of reflected light r_dot_v = max(r.dot(-vd), 0.0) specular = self.specular * pow(r_dot_v, self.shininess) shaded_color = tl.vec4( ti.min(1.0, diffuse + specular + self.ambient) * sample_color.xyz * opacity * self.light_color, opacity) self.render_tape[i, j, sample_idx] = ( 1.0 - self.render_tape[i, j, sample_idx - 1].w ) * shaded_color + self.render_tape[i, j, sample_idx - 1] self.valid_sample_step_count[i, j] += 1 else: self.render_tape[i, j, sample_idx] = self.render_tape[ i, j, sample_idx - 1]
def raycast_nondiff(self, sampling_rate: float): ''' Raycasts in a non-differentiable (but faster and cleaner) way. Use `get_final_image_nondiff` with this. Args: sampling_rate (float): Sampling rate (multiplier with Nyquist frequence) ''' for i, j in self.valid_sample_step_count: # For all pixels for cnt in range(self.sample_step_nums[i, j]): look_from = self.cam_pos[None] if self.render_tape[i, j, 0].w < 0.99: tmax = self.exit[i, j] n_samples = self.sample_step_nums[i, j] ray_len = (tmax - self.entry[i, j]) tmin = self.entry[ i, j] + 0.5 * ray_len / n_samples # Offset tmin as t_start vd = self.rays[i, j] pos = look_from + tl.mix( tmin, tmax, float(cnt) / float(n_samples - 1)) * vd # Current Pos light_pos = look_from + tl.vec3(0.0, 1.0, 0.0) intensity = self.sample_volume_trilinear(pos) sample_color = self.apply_transfer_function(intensity) opacity = 1.0 - ti.pow(1.0 - sample_color.w, 1.0 / sampling_rate) if sample_color.w > 1e-3: normal = self.get_volume_normal(pos) light_dir = (pos - light_pos).normalized( ) # Direction to light source n_dot_l = max(normal.dot(light_dir), 0.0) diffuse = self.diffuse * n_dot_l r = tl.reflect(light_dir, normal) # Direction of reflected light r_dot_v = max(r.dot(-vd), 0.0) specular = self.specular * pow(r_dot_v, self.shininess) shaded_color = tl.vec4( (diffuse + specular + self.ambient) * sample_color.xyz * opacity * self.light_color, opacity) self.render_tape[ i, j, 0] = (1.0 - self.render_tape[i, j, 0].w ) * shaded_color + self.render_tape[i, j, 0]
def noise(x): i = ti.floor(x) f = fract(x) u = f * f * (3.0 - 2.0 * f) v1 = ts.mix(hash(i + ti.Vector([0.0, 0.0, 0.0])), hash(i + ti.Vector([1.0, 0.0, 0.0])), u[0]) v2 = ts.mix(hash(i + ti.Vector([0.0, 1.0, 0.0])), hash(i + ti.Vector([1.0, 1.0, 0.0])), u[0]) v12 = ts.mix(v1, v2, u[1]) v3 = ts.mix(hash(i + ti.Vector([0.0, 0.0, 1.0])), hash(i + ti.Vector([1.0, 0.0, 1.0])), u[0]) v4 = ts.mix(hash(i + ti.Vector([0.0, 1.0, 1.0])), hash(i + ti.Vector([1.0, 1.0, 1.0])), u[0]) v34 = ts.mix(v3, v4, u[1]) return ts.mix(v12, v34, u[2])
def pre_process(self, color): blue = ts.vec3(0.00, 0.01, 0.05) orange = ts.vec3(1.19, 1.04, 0.98) return ti.sqrt(ts.mix(blue, orange, color))
def frensel(self, view, halfway, color): f0 = ts.mix(ts.vec3(self.specular), color, self.metallic) hdotv = min(1, max(ti.dot(halfway, view), 0)) return (f0 + (1.0 - f0) * (1.0 - hdotv)**5) * self.ks