def set_face_normal(self, r: RayList, outward_normal: Vec3List) \ -> HitRecordList: self.front_face = (r.direction() * outward_normal).e.sum(axis=1) < 0 front_face_3 = Vec3List.from_array(self.front_face) self.normal = Vec3List( np.where(front_face_3.e, outward_normal.e, -outward_normal.e)) return self
def hit(self, r: RayList, t_min: float, t_max: Union[float, cp.ndarray]) \ -> HitRecordList: if isinstance(t_max, (int, float, cp.floating)): t_max_list = cp.full(len(r), t_max, cp.float32) else: t_max_list = t_max oc: Vec3List = r.origin() - self.center a: cp.ndarray = r.direction().length_squared() half_b: cp.ndarray = oc @ r.direction() c: cp.ndarray = oc.length_squared() - self.radius**2 discriminant_list: cp.ndarray = half_b**2 - a*c discriminant_condition = discriminant_list > 0 if not discriminant_condition.any(): return HitRecordList.new(len(r)).set_compress_info(None) # Calculate t positive_discriminant_list = ( discriminant_list * discriminant_condition ) root = cp.sqrt(positive_discriminant_list) non_zero_a = a - (a == 0) t_0 = (-half_b - root) / non_zero_a t_1 = (-half_b + root) / non_zero_a # Choose t t_0_condition = ( (t_min < t_0) & (t_0 < t_max_list) & discriminant_condition ) t_1_condition = ( (t_min < t_1) & (t_1 < t_max_list) & (~t_0_condition) & discriminant_condition ) t = cp.where(t_0_condition, t_0, 0) t = cp.where(t_1_condition, t_1, t) # Compression condition = t > 0 full_rate = condition.sum() / len(t) if full_rate > 0.5: idx = None else: idx = cp.where(condition)[0] t = t[idx] r = RayList( Vec3List(r.orig.get_ndarray(idx)), Vec3List(r.dir.get_ndarray(idx)) ) # Wrap up result point = r.at(t) outward_normal = (point - self.center) / self.radius result = HitRecordList( point, t, cp.full(len(r), self.material.idx, dtype=cp.int32) ).set_face_normal(r, outward_normal).set_compress_info(idx) return result
def new(length: int) -> HitRecordList: return HitRecordList( Vec3List.new_empty(length), cp.zeros(length, dtype=cp.float32), cp.zeros(length, dtype=cp.int32), Vec3List.new_empty(length), cp.empty(length, dtype=cp.bool) )
def new_from_t(t: cp.ndarray) -> HitRecordList: length = len(t) return HitRecordList( Vec3List.new_empty(length), t, cp.zeros(length, dtype=cp.int32), Vec3List.new_empty(length), cp.empty(length, dtype=cp.bool) )
def update(self, new: HitRecordList) -> HitRecordList: change = (new.t < self.t) & (new.t > 0) if not change.any(): return self change_3 = Vec3List.from_array(change) self.p = Vec3List(np.where(change_3.e, new.p.e, self.p.e)) self.t = np.where(change, new.t, self.t) self.material = np.where(change, new.material, self.material) self.normal = Vec3List( np.where(change_3.e, new.normal.e, self.normal.e)) self.front_face = np.where(change, new.front_face, self.front_face) return self
def compress(self, r: RayList, closest_so_far: cp.ndarray) \ -> Tuple[RayList, cp.ndarray]: condition = r.dir.length_squared() > 0 full_rate = condition.sum() / len(r) if full_rate > 0.5: self.idx = None return r, closest_so_far self.idx = cp.where(condition)[0] self.old_length = len(condition) new_r = RayList(Vec3List(r.orig.get_ndarray(self.idx)), Vec3List(r.dir.get_ndarray(self.idx))) new_c = closest_so_far[self.idx] return new_r, new_c
def compress(r, rec): condition = rec.t > 0 full_rate = condition.sum() / len(r) if full_rate > 0.5: return r, rec, None idx = cp.where(condition)[0] new_r = RayList(Vec3List(r.origin().get_ndarray(idx)), Vec3List(r.direction().get_ndarray(idx))) new_rec = HitRecordList(Vec3List(rec.p.get_ndarray(idx)), rec.t[idx], rec.material[idx], Vec3List(rec.normal.get_ndarray(idx)), rec.front_face[idx]) return new_r, new_rec, idx
def compress(r: RayList, rec: HitRecordList) \ -> Tuple[RayList, HitRecordList, Optional[np.ndarray]]: condition = rec.t > 0 full_rate = condition.sum() / len(r) if full_rate > 0.6: return r, rec, None idx: np.ndarray = np.where(condition)[0] new_r = RayList(Vec3List(r.orig.get_ndarray(idx)), Vec3List(r.dir.get_ndarray(idx))) new_rec = HitRecordList(Vec3List(rec.p.get_ndarray(idx)), rec.t[idx], rec.material[idx], Vec3List(rec.normal.get_ndarray(idx)), rec.front_face[idx]) return new_r, new_rec, idx
def update(self, new: HitRecordList) -> HitRecordList: if new.compress_idx is not None: idx = new.compress_idx old_idx = cp.arange(len(idx)) else: idx = slice(0, -1) old_idx = slice(0, -1) change = (new.t[old_idx] < self.t[idx]) & (new.t[old_idx] > 0) if not change.any(): return self change_3 = Vec3List.from_array(change) self.p.e[idx] = cp.where( change_3.e, new.p.e[old_idx], self.p.e[idx] ) self.t[idx] = cp.where( change, new.t[old_idx], self.t[idx] ) self.material[idx] = cp.where( change, new.material[old_idx], self.material[idx] ) self.normal.e[idx] = cp.where( change_3.e, new.normal.e[old_idx], self.normal.e[idx] ) self.front_face[idx] = cp.where( change, new.front_face[old_idx], self.front_face[idx] ) return self
def scatter(self, r_in, rec): condition = (rec.t > 0) & rec.front_face scatter_direction = rec.normal + Vec3.random_unit_vector(len(r_in)) scattered = RayList(rec.p.mul_ndarray(condition), scatter_direction.mul_ndarray(condition)) attenuation = Vec3List.from_array(condition) * self.albedo return scattered, attenuation
def get_ray(self, s, t): if len(s) != len(t): raise ValueError rd = Vec3.random_in_unit_disk(len(s)) * self.lens_radius u = Vec3List.from_vec3(self.u, len(s)) v = Vec3List.from_vec3(self.v, len(s)) offset_list = u.mul_ndarray(rd[0]) + v.mul_ndarray(rd[1]) origin_list = offset_list + self.origin horizontal_multi = Vec3List.from_vec3(self.horizontal, len(s)) vertical_multi = Vec3List.from_vec3(self.vertical, len(s)) direction_list = (horizontal_multi.mul_ndarray(s) - vertical_multi.mul_ndarray(t) + self.top_left_corner - self.origin - offset_list) return RayList(origin_list, direction_list)
def scatter(self, r_in: RayList, rec: HitRecordList) \ -> Tuple[RayList, Vec3List]: etai_over_etat = cp.where( rec.front_face, 1 / self.ref_idx, self.ref_idx ) unit_direction = r_in.direction().unit_vector() cos_theta = -unit_direction @ rec.normal cos_theta = cp.where(cos_theta > 1, 1, cos_theta) sin_theta = cp.sqrt(1 - cos_theta**2) reflect_prob = self.schlick(cos_theta, etai_over_etat) reflect_condition = ( (etai_over_etat * sin_theta > 1) | (random_float_list(len(r_in)) < reflect_prob) ) # total internal reflection reflected = (unit_direction.mul_ndarray(reflect_condition)).reflect( rec.normal.mul_ndarray(reflect_condition) ) # refraction refracted = (unit_direction.mul_ndarray(~reflect_condition)).refract( rec.normal.mul_ndarray(~reflect_condition), etai_over_etat ) direction = reflected + refracted condition = rec.t > 0 scattered = RayList( rec.p.mul_ndarray(condition), direction.mul_ndarray(condition) ) attenuation = Vec3List.from_array(condition) * Color(1, 1, 1) return scattered, attenuation
def scatter(self, r_in, rec): condition = (rec.t > 0) & rec.front_face scatter_direction = Vec3.random_in_hemisphere(rec.normal) scattered = RayList(rec.p.mul_ndarray(condition), scatter_direction.mul_ndarray(condition)) attenuation = Vec3List.from_array(condition) * self.albedo return scattered, attenuation
def __init__(self, point: Vec3List, t: cp.ndarray, mat: cp.ndarray, normal: Vec3List = Vec3List.new_zero(0), front_face: cp.ndarray = cp.array([])) -> None: self.p = point self.t = t self.material = mat self.normal = normal self.front_face = front_face
def __init__(self, point, t, mat, normal=Vec3List.new_zero(0), front_face=cp.array([])): self.p = point self.t = t self.material = mat self.normal = normal self.front_face = front_face
def scatter(self, r_in, rec): condition = (rec.t > 0) & rec.front_face reflected = (r_in.direction().unit_vector().reflect(rec.normal) + Vec3.random_in_unit_sphere_list(len(r_in)) * self.fuzz) condition = condition & (reflected @ rec.normal > 0) scattered = RayList(rec.p.mul_ndarray(condition), reflected.mul_ndarray(condition)) attenuation = Vec3List.from_array(condition) * self.albedo return scattered, attenuation
def decompress(r, a, idx, length): if idx is None: return r, a old_idx = cp.arange(len(idx)) new_r = RayList.new_zero(length) new_r.origin().e[idx] = r.origin().e[old_idx] new_r.direction().e[idx] = r.direction().e[old_idx] new_a = Vec3List.new_zero(length) new_a.e[idx] = a.e[old_idx] return new_r, new_a
def decompress(r: RayList, a: Vec3List, idx: Optional[np.ndarray], length: int) -> Tuple[RayList, Vec3List]: if idx is None: return r, a old_idx = np.arange(len(idx)) new_r = RayList.new_zero(length) new_r.orig.e[idx] = r.orig.e[old_idx] new_r.dir.e[idx] = r.dir.e[old_idx] new_a = Vec3List.new_zero(length) new_a.e[idx] = a.e[old_idx] return new_r, new_a
def scan_line(j: int, world: HittableList, cam: Camera, image_width: int, image_height: int, samples_per_pixel: int, max_depth: int) -> Img: img = Img(image_width, 1) row_pixel_color = Vec3List.from_vec3(Color(), image_width) for s in range(samples_per_pixel): u: np.ndarray = (random_float_list(image_width) + np.arange(image_width)) / (image_width - 1) v: np.ndarray = (random_float_list(image_width) + j) / (image_height - 1) r: RayList = cam.get_ray(u, v) row_pixel_color += ray_color(r, world, max_depth) img.write_pixel_list(0, row_pixel_color, samples_per_pixel) return img
def new_empty(length: int) -> RayList: return RayList(Vec3List.new_empty(length), Vec3List.new_empty(length))
def set_face_normal(self, r, outward_normal): self.front_face = (r.direction() * outward_normal).e.sum(axis=1) < 0 front_face_3 = Vec3List.from_array(self.front_face) self.normal = Vec3List( cp.where(front_face_3.e, outward_normal.e, -outward_normal.e)) return self
def ray_color(r: RayList, world: HittableList, depth: int) -> Vec3List: length = len(r) if not r.direction().e.any(): return Vec3List.new_zero(length) # Calculate object hits rec_list: HitRecordList = world.hit(r, 0.001, np.inf) # Useful empty arrays empty_vec3list = Vec3List.new_zero(length) empty_array_float = np.zeros(length, np.float32) empty_array_bool = np.zeros(length, np.bool) empty_array_int = np.zeros(length, np.int32) # Background / Sky unit_direction = r.direction().unit_vector() sky_condition = Vec3List.from_array((unit_direction.length() > 0) & (rec_list.material == 0)) t = (unit_direction.y() + 1) * 0.5 blue_bg = (Vec3List.from_vec3(Color(1, 1, 1), length).mul_ndarray(1 - t) + Vec3List.from_vec3(Color(0.5, 0.7, 1), length).mul_ndarray(t)) result_bg = Vec3List(np.where(sky_condition.e, blue_bg.e, empty_vec3list.e)) if depth <= 1: return result_bg # Per-material preparations materials: Dict[int, Material] = world.get_materials() material_dict: Dict[int, Tuple[RayList, HitRecordList]] = dict() for mat_idx in materials: mat_condition = (rec_list.material == mat_idx) mat_condition_3 = Vec3List.from_array(mat_condition) if not mat_condition.any(): continue raylist_temp = RayList( Vec3List(np.where(mat_condition_3.e, r.orig.e, empty_vec3list.e)), Vec3List(np.where(mat_condition_3.e, r.dir.e, empty_vec3list.e))) reclist_temp = HitRecordList( Vec3List( np.where(mat_condition_3.e, rec_list.p.e, empty_vec3list.e)), np.where(mat_condition, rec_list.t, empty_array_float), np.where(mat_condition, rec_list.material, empty_array_int), Vec3List( np.where(mat_condition_3.e, rec_list.normal.e, empty_vec3list.e)), np.where(mat_condition, rec_list.front_face, empty_array_bool)) material_dict[mat_idx] = raylist_temp, reclist_temp # Material scatter calculations scattered_list = RayList.new_zero(length) attenuation_list = Vec3List.new_zero(length) for key in material_dict: ray, rec = material_dict[key] ray, rec, idx_list = compress(ray, rec) scattered, attenuation = materials[key].scatter(ray, rec) scattered, attenuation = decompress(scattered, attenuation, idx_list, length) scattered_list += scattered attenuation_list += attenuation result_hittable = (attenuation_list * ray_color(scattered_list, world, depth - 1)) return result_hittable + result_bg
def new_zero(length): return RayList(Vec3List.new_zero(length), Vec3List.new_zero(length))
def new_empty(length): return RayList(Vec3List.new_empty(length), Vec3List.new_empty(length))
def ray_color(r, world, depth): length = len(r) if not r.direction().e.any(): return None, None, Vec3List.new_zero(length) rec_list = world.hit(r, 0.001, cp.inf) empty_vec3list = Vec3List.new_zero(length) empty_array_float = cp.zeros(length, cp.float32) empty_array_bool = cp.zeros(length, cp.bool) empty_array_int = cp.zeros(length, cp.int32) unit_direction = r.direction().unit_vector() sky_condition = Vec3List.from_array((unit_direction.length() > 0) & (rec_list.material == 0)) t = (unit_direction.y() + 1) * 0.5 blue_bg = (Vec3List.from_vec3(Color(1, 1, 1), length).mul_ndarray(1 - t) + Vec3List.from_vec3(Color(0.5, 0.7, 1), length).mul_ndarray(t)) result_bg = Vec3List(cp.where(sky_condition.e, blue_bg.e, empty_vec3list.e)) if depth <= 1: return None, None, result_bg materials = world.get_materials() scattered_list = RayList.new_zero(length) attenuation_list = Vec3List.new_zero(length) for mat_idx in materials: mat_condition = (rec_list.material == mat_idx) mat_condition_3 = Vec3List.from_array(mat_condition) if not mat_condition.any(): continue ray = RayList( Vec3List( cp.where(mat_condition_3.e, r.origin().e, empty_vec3list.e)), Vec3List( cp.where(mat_condition_3.e, r.direction().e, empty_vec3list.e))) rec = HitRecordList( Vec3List( cp.where(mat_condition_3.e, rec_list.p.e, empty_vec3list.e)), cp.where(mat_condition, rec_list.t, empty_array_float), cp.where(mat_condition, rec_list.material, empty_array_int), Vec3List( cp.where(mat_condition_3.e, rec_list.normal.e, empty_vec3list.e)), cp.where(mat_condition, rec_list.front_face, empty_array_bool)) ray, rec, idx_list = compress(ray, rec) scattered, attenuation = materials[mat_idx].scatter(ray, rec) scattered, attenuation = decompress(scattered, attenuation, idx_list, length) scattered_list += scattered attenuation_list += attenuation return scattered_list, attenuation_list, result_bg
def new_zero(length: int) -> RayList: return RayList(Vec3List.new_zero(length), Vec3List.new_zero(length))
def ray_color(r: RayList, world: HittableList, depth: int) \ -> Tuple[Optional[RayList], Optional[Vec3List], Vec3List]: length = len(r) if not r.direction().e.any(): return None, None, Vec3List.new_zero(length) # Calculate object hits rec_list: HitRecordList = world.hit(r, 0.001, cp.inf) # Useful empty arrays empty_vec3list = Vec3List.new_zero(length) empty_array_float = cp.zeros(length, cp.float32) empty_array_bool = cp.zeros(length, cp.bool) empty_array_int = cp.zeros(length, cp.int32) # Background / Sky unit_direction = r.direction().unit_vector() sky_condition = Vec3List.from_array((unit_direction.length() > 0) & (rec_list.material == 0)) t = (unit_direction.y() + 1) * 0.5 blue_bg = (Vec3List.from_vec3(Color(1, 1, 1), length).mul_ndarray(1 - t) + Vec3List.from_vec3(Color(0.5, 0.7, 1), length).mul_ndarray(t)) result_bg = Vec3List(cp.where(sky_condition.e, blue_bg.e, empty_vec3list.e)) if depth <= 1: return None, None, result_bg # Material scatter calculations materials: Dict[int, Material] = world.get_materials() scattered_list = RayList.new_zero(length) attenuation_list = Vec3List.new_zero(length) for mat_idx in materials: mat_condition = (rec_list.material == mat_idx) mat_condition_3 = Vec3List.from_array(mat_condition) if not mat_condition.any(): continue ray = RayList( Vec3List(cp.where(mat_condition_3.e, r.orig.e, empty_vec3list.e)), Vec3List(cp.where(mat_condition_3.e, r.dir.e, empty_vec3list.e))) rec = HitRecordList( Vec3List( cp.where(mat_condition_3.e, rec_list.p.e, empty_vec3list.e)), cp.where(mat_condition, rec_list.t, empty_array_float), cp.where(mat_condition, rec_list.material, empty_array_int), Vec3List( cp.where(mat_condition_3.e, rec_list.normal.e, empty_vec3list.e)), cp.where(mat_condition, rec_list.front_face, empty_array_bool)) ray, rec, idx_list = compress(ray, rec) scattered, attenuation = materials[mat_idx].scatter(ray, rec) scattered, attenuation = decompress(scattered, attenuation, idx_list, length) scattered_list += scattered attenuation_list += attenuation return scattered_list, attenuation_list, result_bg