def random_in_unit_sphere() -> Vec3: p = Vec3(random(), random(), random()) * 2.0 - Vec3(1, 1, 1) while p.length()**2 >= 1: p = Vec3(random(), random(), random()) * 2.0 - Vec3(1, 1, 1) return p
def scatter(self, r_in: Ray, hitPoint: Vec3, hitNormal: Vec3, \ attenuation: Vec3, scattered: Ray): reflected = super().reflect(r_in.direction.unit(), hitNormal) scattered.set( Ray(hitPoint, reflected + super().random_in_unit_sphere() * self.fuzz)) attenuation.set(self.albedo) return Vec3.dot(scattered.direction, hitNormal) > 0
def refract(v: Vec3, n: Vec3, ni_over_nt: float, refracted: Vec3) -> bool: uv = v.unit() dt = Vec3.dot(uv, n) discriminant = 1 - ni_over_nt * ni_over_nt * (1 - dt * dt) if discriminant > 0: refracted.set((uv - n * dt) * ni_over_nt - n * (discriminant**0.5)) return True else: return False
def hit(self, r: Ray, t_min: float, t_max: float, rec: HitRecord): hit_anything = False closest_so_far = t_max temp_rec = HitRecord(0, Vec3(0, 0, 0), Vec3(0, 0, 1)) for item in self.hit_list: if item.hit(r, t_min, closest_so_far, temp_rec): hit_anything = True closest_so_far = temp_rec.t rec.set(temp_rec) return hit_anything
def __init__(self, look_from: Vec3, look_at: Vec3, up: Vec3, \ vert_FOV: float, aspect_ratio: float, \ aperture: float, focal_dist: float): self.lens_radius = aperture / 2 theta = vert_FOV * pi / 180 half_height = tan(theta / 2) half_width = aspect_ratio * half_height self.w = (look_from - look_at).unit() self.u = Vec3.cross(up, self.w).unit() self.v = Vec3.cross(self.w, self.u) self.eye = look_from self.lower_left = self.eye - self.u * half_width * focal_dist - self.v * half_height * focal_dist - self.w * focal_dist self.horizontal = self.u * 2 * half_width * focal_dist self.vertical = self.v * 2 * half_height * focal_dist
def scatter(self, r_in: Ray, hitPoint: Vec3, hitNormal: Vec3, \ attenuation: Vec3, scattered: Ray): reflected = super().reflect(r_in.direction, hitNormal) attenuation.set(Vec3(1, 1, 1)) if Vec3.dot(r_in.direction, hitNormal) > 0: outward_normal = hitNormal * -1 ni_over_nt = self.reflective_index cosine = self.reflective_index * Vec3.dot( r_in.direction, hitNormal) / r_in.direction.length() else: outward_normal = hitNormal ni_over_nt = 1 / self.reflective_index cosine = -1 * Vec3.dot(r_in.direction, hitNormal) / r_in.direction.length() refracted = Vec3(0, 0, 0) if self.refract(r_in.direction, outward_normal, ni_over_nt, refracted): reflect_prob = self.schlick(cosine, self.reflective_index) else: reflect_prob = 1.0 if random() < reflect_prob: scattered.set(Ray(hitPoint, reflected)) else: scattered.set(Ray(hitPoint, refracted)) return True
def lerp(pixels, cam, world): for x in range(RES_WIDTH): for y in range(RES_HEIGHT): col = Vec3(0.0, 0.0, 0.0) for _ in range(LERP_SAMPLE_DENSITY): u = float(x + random()) / RES_WIDTH v = float(y + random()) / RES_HEIGHT r = cam.get_ray(u, v) # p = r.point_at_param(2.0) col = col + color(r, world, 0) col = col / LERP_SAMPLE_DENSITY col = Vec3(col.x**0.5, col.y**0.5, col.z**0.5) ir = min(255, int(255.99 * col.x)) ig = min(255, int(255.99 * col.y)) ib = min(255, int(255.99 * col.z)) pixels[x, RES_HEIGHT - 1 - y] = (ir, ig, ib)
def hit(self, r: Ray, t_min: float, t_max: float, rec: HitRecord): oc = r.origin - self.center a = Vec3.dot(r.direction, r.direction) b = Vec3.dot(oc, r.direction) c = Vec3.dot(oc, oc) - (self.radius) * (self.radius) discriminant = b * b - a * c h0 = (-b - discriminant**(0.5)) / a h1 = (-b + discriminant**(0.5)) / a h = where((h0 > 0) & (h0 < h1), h0, h1) pred = (discriminant > 0) & (h > 0) if discriminant > 0: rec.t = where(pred, h, t_max) rec.hitPoint.set(r.point_at_param(rec.t)) rec.normal.set((rec.hitPoint - self.center)) / self.radius rec.material = self.material return True else: return False
def color(r: Ray, world: Hitable, depth: int) -> Vec3: rec = HitRecord(0, Vec3(0, 0, 0), Vec3(0, 0, 1)) if world.hit(r, 0.001, MAX_RAY_LENGTH, rec): attenuation = Vec3(0, 0, 0) scattered = Ray(Vec3(0, 0, 0), Vec3(1, 0, 0)) if depth < 50 and \ rec.material.scatter(r, rec.hitPoint, rec.normal, attenuation, scattered): return attenuation * color(scattered, world, depth + 1) else: return Vec3(0, 0, 0) else: unit_direction = r.direction.unit() t = 0.5 * (unit_direction.y + 1) return Vec3(1.0, 1.0, 1.0) * (1.0 - t) + Vec3(0.5, 0.7, 1.0) * t
def scatter(self, r_in: Ray, hitPoint: Vec3, hitNormal: Vec3, \ attenuation: Vec3, scattered: Ray): target = hitPoint + hitNormal + super().random_in_unit_sphere() scattered.set(Ray(hitPoint, target - hitPoint)) attenuation.set(self.albedo) return True
def reflect(v: Vec3, n: Vec3): return v - n * 2 * Vec3.dot(v, n)
def random_scene(): hit_list = [] hit_list.append( Sphere(Vec3(0, -1000, 0), 1000, Lambertian(Vec3(0.5, 0.5, 0.5)))) for a in range(-11, 12): for b in range(-11, 12): choose_mat = random() center = Vec3(a + 0.9 * random(), 0.2, b + 0.9 * random()) if (center - Vec3(4, 0.2, 0)).length() > 0.9: if choose_mat < 0.8: #diffuse hit_list.append( Sphere( center, 0.2, Lambertian( Vec3(random() * random(), random() * random(), random() * random())))) elif choose_mat < 0.95: #metal hit_list.append( Sphere( center, 0.2, Specular( Vec3(0.5 * (1 + random()), 0.5 * (1 + random()), 0.5 * (1 + random())), 0.5 * random()))) else: #glass hit_list.append(Sphere(center, 0.2, Dielectric(1.5))) hit_list.append(Sphere(Vec3(0, 1, 0), 1.0, Dielectric(1.5))) hit_list.append( Sphere(Vec3(-4, 1, 0), 1.0, Lambertian(Vec3(0.4, 0.2, 0.1)))) hit_list.append( Sphere(Vec3(4, 1, 0), 1.0, Specular(Vec3(0.7, 0.6, 0.5), 0.0))) return hit_list
0.5 * (1 + random())), 0.5 * random()))) else: #glass hit_list.append(Sphere(center, 0.2, Dielectric(1.5))) hit_list.append(Sphere(Vec3(0, 1, 0), 1.0, Dielectric(1.5))) hit_list.append( Sphere(Vec3(-4, 1, 0), 1.0, Lambertian(Vec3(0.4, 0.2, 0.1)))) hit_list.append( Sphere(Vec3(4, 1, 0), 1.0, Specular(Vec3(0.7, 0.6, 0.5), 0.0))) return hit_list if __name__ == '__main__': seed() look_from = Vec3(3, 3, 2) look_to = Vec3(0, 0, -1) dist_to_focus = (look_from - look_to).length() aperture = 2.0 cam = Camera(look_from, look_to, Vec3(0, 1, 0), 90, RES_WIDTH / RES_HEIGHT, aperture, dist_to_focus) # hit_list = [] # hit_list.append(Sphere(Vec3(0,0,-1), 0.5, Lambertian(Vec3(0.1, 0.2, 0.5)))) # hit_list.append(Sphere(Vec3(0,-100.5,-1), 100, Lambertian(Vec3(0.8, 0.8, 0)))) # hit_list.append(Sphere(Vec3(1,0,-1), 0.5, Specular(Vec3(0.8, 0.6, 0.2)))) # hit_list.append(Sphere(Vec3(-1,0,-1), 0.5, Dielectric(1.5))) # hit_list.append(Sphere(Vec3(-1,0,-1), -0.45, Dielectric(1.5))) hit_list = random_scene() world = HitableList(hit_list) # S is the screen coordinates (x0, y0, x1, y1)
def random_in_unit_disk(): while True: p = Vec3(random(), random(), 0) * 2.0 - Vec3(1, 1, 0) if Vec3.dot(p, p) >= 1.0: break return p