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 do_render(self): model = self.model scene = model.scene L2W = model.L2W a = scene.camera.untrans_pos(L2W @ self.vertex(0).pos) b = scene.camera.untrans_pos(L2W @ self.vertex(1).pos) c = scene.camera.untrans_pos(L2W @ self.vertex(2).pos) A = scene.uncook_coor(a) B = scene.uncook_coor(b) C = scene.uncook_coor(c) B_A = B - A C_B = C - B A_C = A - C ilB_A = 1 / ts.length(B_A) ilC_B = 1 / ts.length(C_B) ilA_C = 1 / ts.length(A_C) B_A *= ilB_A C_B *= ilC_B A_C *= ilA_C BxA = ts.cross(B, A) * ilB_A CxB = ts.cross(C, B) * ilC_B AxC = ts.cross(A, C) * ilA_C normal = ts.normalize(ts.cross(a - c, a - b)) light_dir = scene.camera.untrans_dir(scene.light_dir[None]) pos = (a + b + c) / 3 color = scene.opt.render_func(pos, normal, ts.vec3(0.0), light_dir) color = scene.opt.pre_process(color) Ak = 1 / (ts.cross(A, C_B) + CxB) Bk = 1 / (ts.cross(B, A_C) + AxC) Ck = 1 / (ts.cross(C, B_A) + BxA) W = 1 M, N = int(ti.floor(min(A, B, C) - W)), int(ti.ceil(max(A, B, C) + W)) for X in ti.grouped(ti.ndrange((M.x, N.x), (M.y, N.y))): AB = ts.cross(X, B_A) + BxA BC = ts.cross(X, C_B) + CxB CA = ts.cross(X, A_C) + AxC if AB <= 0 and BC <= 0 and CA <= 0: zindex = pos.z #(Ak * a.z * BC + Bk * b.z * CA + Ck * c.z * AB) zstep = zindex - ti.atomic_max(scene.zbuf[X], zindex) if zstep >= 0: scene.img[X] = color
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(self, renderer): width = 1 A = self.begin B = self.end A, B = min(A, B), max(A, B) mold = tg.normalize(B - A) for X in ti.grouped( ti.ndrange((A.x - width, B.x + width), (A.y - width, B.y + width))): udf = abs(tg.cross(X - A, mold)) renderer.image[int(X)] = tg.vec3(tg.smoothstep(udf, width, 0))
def get_ray_direction(self, orig, view_dir, x: float, y: float): ''' Compute ray direction for perspecive camera. Args: orig (tl.vec3): Camera position view_dir (tl.vec3): View direction, normalized x (float): Image coordinate in [0,1] along width y (float): Image coordinate in [0,1] along height Returns: tl.vec3: Ray direction from camera origin to pixel specified through `x` and `y` ''' u = x - 0.5 v = y - 0.5 up = tl.vec3(0.0, 1.0, 0.0) right = tl.cross(view_dir, up).normalized() up = tl.cross(right, view_dir).normalized() near_h = 2.0 * ti.tan(self.fov_rad) * self.near near_w = near_h * self.aspect near_m = orig + self.near * view_dir near_pos = near_m + u * near_w * right + v * near_h * up return (near_pos - orig).normalized()
def do_render(self, scene): a = scene.camera.untrans_pos(self.a) b = scene.camera.untrans_pos(self.b) c = scene.camera.untrans_pos(self.c) A = scene.uncook_coor(a) B = scene.uncook_coor(b) C = scene.uncook_coor(c) B_A = B - A C_B = C - B A_C = A - C ilB_A = 1 / ts.length(B_A) ilC_B = 1 / ts.length(C_B) ilA_C = 1 / ts.length(A_C) B_A *= ilB_A C_B *= ilC_B A_C *= ilA_C BxA = ts.cross(B, A) * ilB_A CxB = ts.cross(C, B) * ilC_B AxC = ts.cross(A, C) * ilA_C normal = ts.normalize(ts.cross(a - c, a - b)) light_dir = scene.camera.untrans_dir(scene.light_dir[None]) pos = (a + b + c) * (1 / 3) dir = ts.vec3(0.0) color = scene.opt.render_func(pos, normal, dir, light_dir) color = scene.opt.pre_process(color) W = 0.4 M, N = int(ti.floor(min(A, B, C) - W)), int(ti.ceil(max(A, B, C) + W)) for X in ti.grouped(ti.ndrange((M.x, N.x), (M.y, N.y))): AB = ts.cross(X, B_A) + BxA BC = ts.cross(X, C_B) + CxB CA = ts.cross(X, A_C) + AxC udf = max(AB, BC, CA) if udf < 0: scene.img[X] = color elif udf < W: t = ts.smoothstep(udf, W, 0) ti.atomic_max(scene.img[X], t * color)
def nrm(self): posa, posb, posc = self.pos normal = ts.cross(posa - posc, posa - posb) return normal, normal, normal
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 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))