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 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 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
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 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 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 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 scatter(self, r_in: RayList, rec: HitRecordList) \ -> Tuple[RayList, Vec3List]: 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 hit(self, r: RayList, t_min: float, t_max: Union[float, np.ndarray]) \ -> HitRecordList: if isinstance(t_max, (int, float, np.floating)): t_max_list = np.full(len(r), t_max) else: t_max_list = t_max oc: Vec3List = r.origin() - self.center a: np.ndarray = r.direction().length_squared() half_b: np.ndarray = oc @ r.direction() c: np.ndarray = oc.length_squared() - self.radius**2 discriminant_list: np.ndarray = half_b**2 - a * c discriminant_condition = discriminant_list > 0 if not discriminant_condition.any(): return HitRecordList.new(len(r)) positive_discriminant_list = (discriminant_list * discriminant_condition) root = np.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 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 = np.where(t_0_condition, t_0, 0) t = np.where(t_1_condition, t_1, t) point = r.at(t) outward_normal = (point - self.center) / self.radius result = HitRecordList(point, t, np.full( len(r), self.material.idx)).set_face_normal(r, outward_normal) return result
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 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 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 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 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