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 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 brdf(self, normal, lightdir, viewdir): halfway = ts.normalize(lightdir + viewdir) NoH = max(EPS, ts.dot(normal, halfway)) NoL = max(EPS, ts.dot(normal, lightdir)) NoV = max(EPS, ts.dot(normal, viewdir)) HoV = min(1 - EPS, max(EPS, ts.dot(halfway, viewdir))) ndf = self.roughness**2 / (NoH**2 * (self.roughness**2 - 1) + 1)**2 vdf = 0.25 / (self.ischlick(NoL) * self.ischlick(NoV)) f0 = self.metallic * self.color + (1 - self.metallic) * self.specular ks, kd = self.ks * f0, self.kd * (1 - f0) * (1 - self.metallic) fdf = self.fresnel(f0, NoV) strength = kd * self.color + ks * fdf * vdf * ndf / math.pi return strength
def ballBoundReflect(pos, vel, center, radius): ret = vel above = tl.distance(pos, center) - radius if above <= 0: normal = tl.normalize(pos - center) NoV = tl.dot(vel, normal) - 6 * tl.smoothstep(above, 0, -0.1) if NoV < 0: ret -= NoV * normal return ret
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 ballBoundReflect(pos, vel, center, radius, anti_fall=0, anti_depth=0.1): ret = vel above = tl.distance(pos, center) - radius if above <= 0: normal = tl.normalize(pos - center) NoV = tl.dot(vel, normal) if ti.static(anti_fall): NoV -= anti_fall * tl.smoothstep(above, 0, -anti_depth) if NoV < 0: ret -= NoV * normal return ret
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 render_triangle(model, camera, face): scene = model.scene L2W = model.L2W _1 = ti.static(min(1, model.faces.m - 1)) _2 = ti.static(min(2, model.faces.m - 1)) ia, ib, ic = model.vi[face[0, 0]], model.vi[face[1, 0]], model.vi[face[2, 0]] ta, tb, tc = model.vt[face[0, _1]], model.vt[face[1, _1]], model.vt[face[2, _1]] na, nb, nc = model.vn[face[0, _2]], model.vn[face[1, _2]], model.vn[face[2, _2]] a = camera.untrans_pos(L2W @ ia) b = camera.untrans_pos(L2W @ ib) c = camera.untrans_pos(L2W @ ic) # 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. normal = ts.normalize(ts.cross(a - b, a - c)) pos = (a + b + c) / 3 view_pos = (a + b + c) / 3 if ti.static(camera.type == camera.ORTHO): view_pos = ts.vec3(0.0, 0.0, 1.0) if ts.dot(view_pos, normal) <= 0: # shading color = ts.vec3(0.0) for light in ti.static(scene.lights): color += scene.opt.render_func(pos, normal, ts.vec3(0.0), light) color = scene.opt.pre_process(color) A = camera.uncook(a) B = camera.uncook(b) C = camera.uncook(c) 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 W = 1 # screen space bounding box M, N = int(ti.floor(min(A, B, C) - W)), int(ti.ceil(max(A, B, C) + W)) M.x, N.x = min(max(M.x, 0), camera.img.shape[0]), min(max(N.x, 0), camera.img.shape[1]) M.y, N.y = min(max(M.y, 0), camera.img.shape[0]), min(max(N.y, 0), camera.img.shape[1]) 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 in_screen = w_A >= 0 and w_B >= 0 and w_C >= 0 and 0 < X[ 0] < camera.img.shape[0] and 0 < X[1] < camera.img.shape[1] if not in_screen: continue zindex = 1 / (a.z * w_A + b.z * w_B + c.z * w_C) if zindex < ti.atomic_max(camera.zbuf[X], zindex): continue coor = (ta * w_A + tb * w_B + tc * w_C) camera.img[X] = color * model.texSample(coor)
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 brdf(self, normal, lightdir, viewdir): NoH = max(0, ts.dot(normal, ts.normalize(lightdir + viewdir))) ndf = 5 / 2 * pow(NoH, 12) strength = self.diffuse_color * self.diffuse + ndf * self.specular_color * self.specular return strength
def brdf(self, normal, lightdir, viewdir): NoH = max(0, ts.dot(normal, ts.normalize(lightdir + viewdir))) ndf = (self.shineness + 8) / 8 * pow(NoH, self.shineness) strength = self.color + ndf * self.specular return strength
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 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))