Compare commits

..

7 Commits

4 changed files with 189 additions and 34 deletions

View File

@ -1,33 +1,70 @@
const vec = @import("./vec.zig"); const vec = @import("./vec.zig");
const std = @import("std");
const Ray = @import("./ray.zig").Ray; const Ray = @import("./ray.zig").Ray;
const Point3 = vec.Point3; const Point3 = vec.Point3;
const Vec3 = vec.Vec3; const Vec3 = vec.Vec3;
const random_in_unit_disk = vec.random_in_unit_disk;
const math = std.math;
const pi = math.pi;
const tan = math.tan;
// Utility Functions
fn degrees_to_radians(degrees: f32) f32 {
return degrees * pi / 180.0;
}
pub const Camera = struct { pub const Camera = struct {
origin: Point3, origin: Point3,
lower_left_corner: Point3, lower_left_corner: Point3,
horizontal: Vec3, horizontal: Vec3,
vertical: Vec3, vertical: Vec3,
u: Vec3,
v: Vec3,
w: Vec3,
lens_radius: f32,
pub fn init() Camera { pub fn init(
const aspect_ratio = 16.0 / 9.0; lookfrom: Point3,
const viewport_height = 2.0; lookat: Point3,
vup: Vec3,
vfov: f32, // vertical field-of-view in degrees
aspect_ratio: f32,
aperature: f32,
focus_dist: f32,
) Camera {
const theta = degrees_to_radians(vfov);
const h = tan(theta / 2);
const viewport_height = 2 * h;
const viewport_width = aspect_ratio * viewport_height; const viewport_width = aspect_ratio * viewport_height;
const focal_length = 1.0;
const origin = Point3.init(0, 0, 0); const w = lookfrom.sub(lookat).unit();
const horizontal = Vec3.init(viewport_width, 0.0, 0.0); const u = vup.cross(w).unit();
const vertical = Vec3.init(0.0, viewport_height, 0.0); const v = w.cross(u);
const origin = lookfrom;
const horizontal = u.scale(focus_dist * viewport_width);
const vertical = v.scale(focus_dist * viewport_height);
const lower_left_corner = origin.sub(horizontal.scale(0.5)).sub(vertical.scale(0.5)).sub(w.scale(focus_dist));
const lens_radius = aperature / 2;
return Camera{ return Camera{
.origin = origin, .origin = origin,
.horizontal = horizontal, .horizontal = horizontal,
.vertical = vertical, .vertical = vertical,
.lower_left_corner = origin.sub(horizontal.scale(0.5)).sub(vertical.scale(0.5)).sub(Vec3.init(0, 0, focal_length)), .lower_left_corner = lower_left_corner,
.u = u,
.v = v,
.w = w,
.lens_radius = lens_radius,
}; };
} }
pub fn get_ray(camera: Camera, u: f32, v: f32) Ray { pub fn get_ray(camera: Camera, s: f32, t: f32) Ray {
return Ray.init(camera.origin, camera.lower_left_corner.add(camera.horizontal.scale(u).add(camera.vertical.scale(v).sub(camera.origin)))); const rd = random_in_unit_disk().scale(camera.lens_radius);
const offset = camera.u.scale(rd.x()).add(camera.v.scale(rd.y()));
return Ray.init(camera.origin.add(offset), camera.lower_left_corner.add(camera.horizontal.scale(s).add(camera.vertical.scale(t).sub(camera.origin).sub(offset))));
} }
}; };

View File

@ -16,6 +16,7 @@ const Hittable = hittable.Hittable;
const HittableList = hittable_list.HittableList; const HittableList = hittable_list.HittableList;
const Labertian = material.Labertian; const Labertian = material.Labertian;
const Metal = material.Metal; const Metal = material.Metal;
const Dielectric = material.Dielectric;
const Material = material.Material; const Material = material.Material;
const Point3 = vec.Point3; const Point3 = vec.Point3;
const Ray = ray.Ray; const Ray = ray.Ray;
@ -24,6 +25,12 @@ const Vec3 = vec.Vec3;
const info = std.log.info; const info = std.log.info;
const random_in_hemisphere = vec.random_in_hemisphere; const random_in_hemisphere = vec.random_in_hemisphere;
const write_color = color.write_color; const write_color = color.write_color;
const math = std.math;
const pi = math.pi;
const cos = math.cos;
const create_labertian = material.create_labertian;
const create_metal = material.create_metal;
const create_dielectric = material.create_dielectric;
fn ray_color(r: Ray, world: Hittable, depth: isize) Color { fn ray_color(r: Ray, world: Hittable, depth: isize) Color {
// If we've exceeded the ray bounce limit, no more light is gathered. // If we've exceeded the ray bounce limit, no more light is gathered.
@ -76,31 +83,75 @@ fn render_row(q: *Queue(Task)) void {
} }
} }
} }
fn random_scene() anyerror!Hittable {
var prng = std.rand.DefaultPrng.init(0);
const rand = prng.random();
var world = HittableList.init();
const ground_material = create_labertian(Color.init(0.5, 0.5, 0.5));
try world.add(Hittable{ .sphere = Sphere.init(Point3.init(0, -1000, 0), 1000, ground_material) });
var a: isize = -11;
while (a < 11) : (a += 1) {
var b: isize = -11;
while (b < 11) : (b += 1) {
const choose_mat = rand.float(f32);
const center = Point3.init(@intToFloat(f32, a) + 0.9 * rand.float(f32), 0.2, @intToFloat(f32, b) + 0.9 * rand.float(f32));
if (center.sub(Point3.init(4, 0.2, 0)).length() > 0.9) {
if (choose_mat < 0.8) {
const albedo = Color.random_min_max(0, 1).mul(Color.random_min_max(0, 1));
const sphere_material = create_labertian(albedo);
try world.add(Hittable{ .sphere = Sphere.init(center, 0.2, sphere_material) });
} else if (choose_mat < 0.95) {
const albedo = Color.init(
0.5 + rand.float(f32) / 2.0,
0.5 + rand.float(f32) / 2.0,
0.5 + rand.float(f32) / 2.0,
);
const fuzz = rand.float(f32) / 2.0;
const sphere_material = create_metal(albedo, fuzz);
try world.add(Hittable{ .sphere = Sphere.init(center, 0.2, sphere_material) });
} else {
const sphere_material = create_dielectric(1.5);
try world.add(Hittable{ .sphere = Sphere.init(center, 0.2, sphere_material) });
}
}
}
}
const material1 = create_dielectric(1.5);
try world.add(Hittable{ .sphere = Sphere.init(Point3.init(0, 1, 0), 1.0, material1) });
const material2 = create_labertian(Color.init(0.4, 0.2, 0.1));
try world.add(Hittable{ .sphere = Sphere.init(Point3.init(-4, 1, 0), 1.0, material2) });
const material3 = create_metal(Color.init(0.7, 0.6, 0.5), 0.0);
try world.add(Hittable{ .sphere = Sphere.init(Point3.init(4, 1, 0), 1.0, material3) });
return Hittable{ .hittable_list = world };
}
pub fn main() anyerror!void { pub fn main() anyerror!void {
// Image // Image
const aspect_ratio: f32 = 16.0 / 9.0; const aspect_ratio: f32 = 3.0 / 2.0;
const image_width = 400; const image_width = 1200;
const image_height = @floatToInt(isize, @intToFloat(f32, image_width) / aspect_ratio); const image_height = @floatToInt(isize, @intToFloat(f32, image_width) / aspect_ratio);
const samples_per_pixel = 100; const samples_per_pixel = 500;
const max_depth = 50; const max_depth = 50;
// World // World
var tmp_world = HittableList.init(); const world = try random_scene();
const material_ground = Material{ .labertian = Labertian{ .albedo = Color.init(0.8, 0.8, 0.0) } };
const material_center = Material{ .labertian = Labertian{ .albedo = Color.init(0.7, 0.3, 0.3) } };
const material_left = Material{ .metal = Metal{ .albedo = Color.init(0.8, 0.8, 0.8), .fuzz = 0.3 } };
const material_right = Material{ .metal = Metal{ .albedo = Color.init(0.8, 0.6, 0.2), .fuzz = 1.0 } };
try tmp_world.add(Hittable{ .sphere = Sphere.init(Point3.init(0, -100.5, -1), 100, material_ground) });
try tmp_world.add(Hittable{ .sphere = Sphere.init(Point3.init(0, 0, -1), 0.5, material_center) });
try tmp_world.add(Hittable{ .sphere = Sphere.init(Point3.init(-1, 0, -1), 0.5, material_left) });
try tmp_world.add(Hittable{ .sphere = Sphere.init(Point3.init(1, 0, -1), 0.5, material_right) });
const world = Hittable{ .hittable_list = tmp_world };
// Camera // Camera
const cam = Camera.init(); const lookfrom = Point3.init(13, 2, 3);
const lookat = Point3.init(0, 0, 0);
const vup = Vec3.init(0, 1, 0);
const dist_to_focus = 10;
const aperature = 0.1;
const cam = Camera.init(lookfrom, lookat, vup, 20, aspect_ratio, aperature, dist_to_focus);
// Render // Render
const Node = Queue(Task).Node; const Node = Queue(Task).Node;

View File

@ -1,3 +1,4 @@
const std = @import("std");
const vec = @import("./vec.zig"); const vec = @import("./vec.zig");
const Ray = @import("./ray.zig").Ray; const Ray = @import("./ray.zig").Ray;
const HitRecord = @import("./hittable.zig").HitRecord; const HitRecord = @import("./hittable.zig").HitRecord;
@ -6,21 +7,47 @@ const Vec3 = vec.Vec3;
const Color = vec.Color; const Color = vec.Color;
const random_unit_vector = vec.random_unit_vector; const random_unit_vector = vec.random_unit_vector;
const random_in_unit_sphere = vec.random_in_unit_sphere; const random_in_unit_sphere = vec.random_in_unit_sphere;
const pow = std.math.pow;
pub fn create_labertian(albedo: Vec3) Material {
return Material{ .labertian = Labertian{ .albedo = albedo } };
}
pub fn create_metal(albedo: Vec3, fuzz: f32) Material {
return Material{ .metal = Metal{ .albedo = albedo, .fuzz = fuzz } };
}
pub fn create_dielectric(ir: f32) Material {
return Material{ .dielectric = Dielectric{ .ir = ir } };
}
pub const Material = union(enum) { pub const Material = union(enum) {
labertian: Labertian, labertian: Labertian,
metal: Metal, metal: Metal,
dielectric: Dielectric,
pub fn scatter(material: Material, r_in: Ray, rec: HitRecord) ?ScatterRec { pub fn scatter(material: Material, r_in: Ray, rec: HitRecord) ?ScatterRec {
return switch (material) { return switch (material) {
.labertian => |labertian| labertian.scatter(r_in, rec), .labertian => |labertian| labertian.scatter(r_in, rec),
.metal => |metal| metal.scatter(r_in, rec), .metal => |metal| metal.scatter(r_in, rec),
.dielectric => |dielectric| dielectric.scatter(r_in, rec),
}; };
} }
}; };
pub const ScatterRec = struct { attenuation: Color, scattered: Ray }; pub const ScatterRec = struct { attenuation: Color, scattered: Ray };
fn reflect(v: Vec3, n: Vec3) Vec3 {
return v.sub(n.scale(v.dot(n) * 2));
}
fn refract(uv: Vec3, n: Vec3, etai_over_etat: f32) Vec3 {
const cos_theta = @minimum(-uv.dot(n), 1.0);
const r_out_perp = uv.add(n.scale(cos_theta)).scale(etai_over_etat);
const r_out_parallel = n.scale(-@sqrt(@fabs(1.0 - r_out_perp.length_squared())));
return r_out_perp.add(r_out_parallel);
}
pub const Labertian = struct { pub const Labertian = struct {
albedo: Vec3, albedo: Vec3,
pub fn scatter(labertian: Labertian, r_in: Ray, rec: HitRecord) ?ScatterRec { pub fn scatter(labertian: Labertian, r_in: Ray, rec: HitRecord) ?ScatterRec {
@ -53,7 +80,37 @@ pub const Metal = struct {
return null; return null;
} }
} }
fn reflect(v: Vec3, n: Vec3) Vec3 { };
return v.sub(n.scale(v.dot(n) * 2));
var prng = std.rand.DefaultPrng.init(0);
const rand = prng.random();
pub const Dielectric = struct {
ir: f32, // Index of Refraction
pub fn scatter(dielectric: Dielectric, r_in: Ray, rec: HitRecord) ?ScatterRec {
const attenuation = Color.init(1, 1, 1);
const refraction_ratio = if (rec.front_face) 1.0 / dielectric.ir else dielectric.ir;
const unit_direction = r_in.direction().unit();
const cos_theta = @minimum(-unit_direction.dot(rec.normal), 1.0);
const sin_theta = @sqrt(1 - cos_theta * cos_theta);
const cannot_refract = refraction_ratio * sin_theta > 1;
const direction = if (cannot_refract or reflectance(cos_theta, refraction_ratio) > rand.float(f32))
reflect(unit_direction, rec.normal)
else
refract(unit_direction, rec.normal, refraction_ratio);
const scattered = Ray.init(rec.p, direction);
return ScatterRec{
.attenuation = attenuation,
.scattered = scattered,
};
}
fn reflectance(cosine: f32, ref_idx: f32) f32 {
// Use Schlick's approximation for reflectance.
var r0 = (1 - ref_idx) / (1 + ref_idx);
r0 = r0 * r0;
return r0 + (1 - r0) * pow(f32, (1 - cosine), 5);
} }
}; };

View File

@ -47,12 +47,15 @@ pub const Vec3 = struct {
const t = u.v * v.v; const t = u.v * v.v;
return t[0] + t[1] + t[2]; return t[0] + t[1] + t[2];
} }
/// Return value in unit cube from -1, 1 pub fn cross(u: Vec3, v: Vec3) Vec3 {
pub fn random() Vec3 { return Vec3.init(u.v[1] * v.v[2] - u.v[2] * v.v[1], u.v[2] * v.v[0] - u.v[0] * v.v[2], u.v[0] * v.v[1] - u.v[1] * v.v[0]);
}
/// Return value in unit cube from [min, max).
pub fn random_min_max(min: f32, max: f32) Vec3 {
return Vec3.init( return Vec3.init(
rand.float(f32) * 2 - 1, min + (max - min) * rand.float(f32),
rand.float(f32) * 2 - 1, min + (max - min) * rand.float(f32),
rand.float(f32) * 2 - 1, min + (max - min) * rand.float(f32),
); );
} }
pub fn near_zero(vec: Vec3) bool { pub fn near_zero(vec: Vec3) bool {
@ -63,9 +66,16 @@ pub const Vec3 = struct {
} }
}; };
pub fn random_in_unit_disk() Vec3 {
while (true) {
const p = Vec3.init(rand.float(f32) * 2 - 1, rand.float(f32) * 2 - 1, 0);
if (p.length_squared() >= 1) continue;
return p;
}
}
pub fn random_in_unit_sphere() Vec3 { pub fn random_in_unit_sphere() Vec3 {
while (true) { while (true) {
const p = Vec3.random(); const p = Vec3.random_min_max(-1, 1);
if (p.length_squared() > 1) continue; if (p.length_squared() > 1) continue;
return p; return p;
} }