Last active
July 12, 2024 07:33
-
-
Save jesterKing/82008d04287c06e64972469caeefeef9 to your computer and use it in GitHub Desktop.
Chapter 6 Raytracing in one weekend
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| const std = @import("std"); | |
| pub fn build(b: *std.Build) void { | |
| const target = b.standardTargetOptions(.{}); | |
| const optimize = b.standardOptimizeOption(.{}); | |
| const exe = b.addExecutable(.{ | |
| .name = "raytrace", | |
| .target = target, | |
| .optimize = optimize, | |
| .root_source_file = b.path("raytrace.zig"), | |
| }); | |
| b.installArtifact(exe); | |
| } | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| const std = @import("std"); | |
| // set logging level to info. | |
| pub const std_options = .{ | |
| .log_level = .info, | |
| .logFn = myLogFn, | |
| }; | |
| const infinity = std.math.inf(f64); | |
| // log function that writes to stderr | |
| pub fn myLogFn( | |
| comptime _: std.log.Level, | |
| comptime _: @TypeOf(.EnumLiteral), | |
| comptime format: []const u8, | |
| args: anytype, | |
| ) void { | |
| std.debug.lockStdErr(); | |
| defer std.debug.unlockStdErr(); | |
| const stderr = std.io.getStdErr().writer(); | |
| nosuspend stderr.print(format, args) catch return; | |
| } | |
| const stdout = std.io.getStdOut().writer(); | |
| pub fn main() !void { | |
| var _world = HittableList.init(); | |
| defer _world.deinit(); | |
| var world = Hittable{.hittable_list = &_world}; | |
| const aspect_ratio : f64 = 16.0 / 9.0; | |
| const image_width : usize = 400; | |
| var image_height = @as(usize, @intFromFloat(image_width / aspect_ratio)); | |
| image_height = if (image_height < 1) 1 else image_height; | |
| const viewport_height = 2.0; | |
| const viewport_width = viewport_height * (@as(f64, @floatFromInt(image_width)))/(@as(f64, @floatFromInt(image_height))); | |
| const focal_length = 1.0; | |
| const camera_center = Point3.initZero(); | |
| const viewport_u = Vec3.init(viewport_width, 0.0, 0.0); | |
| const viewport_v = Vec3.init(0.0, -viewport_height, 0.0); | |
| const pixel_delta_u = viewport_u.divide(usize, image_width); | |
| const pixel_delta_v = viewport_v.divide(usize, image_height); | |
| const viewport_upper_left = | |
| camera_center | |
| .subtract(Vec3.init(0, 0, focal_length)) | |
| .subtract(viewport_u.divide(i32, 2)) | |
| .subtract(viewport_v.divide(i32, 2)); | |
| const pixel00_loc = | |
| viewport_upper_left | |
| .add( | |
| pixel_delta_u | |
| .add(pixel_delta_v) | |
| .multiply(f64, 0.5) | |
| ); | |
| var sphere1 = Sphere.init(Point3.init(0, 0, -1), 0.5); | |
| var sphere2 = Sphere.init(Point3.init(0, -100.5, -1), 100.0); | |
| const a1 = Hittable{.sphere = &sphere1 }; | |
| const a2 = Hittable{.sphere = &sphere2 }; | |
| try world.append(a1); | |
| try world.append(a2); | |
| const ppm_writer_log = std.log.scoped(.ppm_write); | |
| ppm_writer_log.info("Starting to write PPM image ...\n\n", .{}); | |
| try stdout.print("P3\n{} {}\n255\n", .{ image_width, image_height }); | |
| for (0..image_height) |h| { | |
| ppm_writer_log.info("\rScanlines remaining: {}", .{image_height - h}); | |
| for (0..image_width) |w| { | |
| const pixel_center = pixel00_loc.add(pixel_delta_u.multiply(usize, w)).add(pixel_delta_v.multiply(usize, h)); | |
| const ray_direction = pixel_center.subtract(camera_center); | |
| var ray = Ray{ | |
| .origin = camera_center, | |
| .direction = ray_direction, | |
| }; | |
| const col = ray_color(&ray, &world); | |
| writeColor(col); | |
| } | |
| try stdout.print("\n", .{}); | |
| } | |
| ppm_writer_log.info("\rDone. \n", .{}); | |
| } | |
| fn ray_color(r: *Ray, world: *Hittable) Color { | |
| var hit_record = HitRecord{}; | |
| if(world.hit(r, Interval.init(0.0, infinity), &hit_record)) { | |
| const N = hit_record.n; | |
| const col = Color | |
| .init(N.x + 1, N.y + 1, N.z + 1) | |
| .multiply(f64, 0.5); | |
| return col; | |
| } | |
| const unit_direction = r.direction.unitized(); | |
| const a :f64 = 0.5 * (unit_direction.y + 1.0); | |
| const blue = Color.init(0.2, 0.4, 0.7); | |
| const white = Color.init(1.0, 1.0, 1.0); | |
| return white | |
| .multiply(f64, 1.0 - a) | |
| .add( | |
| blue | |
| .multiply(f64, a) | |
| ); | |
| } | |
| fn writeColor(color: Color) void { | |
| const ir = @as(u8, @intFromFloat(255.999 * color.x)); | |
| const ig = @as(u8, @intFromFloat(255.999 * color.y)); | |
| const ib = @as(u8, @intFromFloat(255.999 * color.z)); | |
| nosuspend stdout.print("{} {} {} ", .{ ir, ig, ib }) catch return; | |
| } | |
| const Interval = struct { | |
| min: f64, | |
| max: f64, | |
| pub fn initInfinity() Interval { | |
| return Interval{ | |
| .min = -infinity, | |
| .max = infinity, | |
| }; | |
| } | |
| pub fn init(min: f64, max: f64) Interval { | |
| return Interval{ | |
| .min = min, | |
| .max = max, | |
| }; | |
| } | |
| pub fn contains(self: Interval, t: f64) bool { | |
| return (self.min <= t) and (t <= self.max); | |
| } | |
| pub fn surrounds(self: Interval, x: f64) bool { | |
| return (self.min < x) and (x < self.max); | |
| } | |
| }; | |
| const empty = Interval.init(infinity, -infinity); | |
| const universe = Interval.initInfinity(); | |
| const HitRecord = struct { | |
| p: Point3 = Point3.initZero(), | |
| n: Vec3 = Vec3.initZero(), | |
| t: f64 = 0.0, | |
| f: bool = false, | |
| pub fn set_face_normal(self: *HitRecord, r: *Ray, outward_normal: Vec3) void { | |
| self.f = r.direction.dot(outward_normal) < 0; | |
| self.n = if(self.f) outward_normal else outward_normal.neg(); | |
| } | |
| }; | |
| const HittableList = struct { | |
| objects: std.ArrayList(Hittable), | |
| pub fn init() HittableList { | |
| return HittableList{ | |
| .objects = std.ArrayList(Hittable).init(std.heap.page_allocator), | |
| }; | |
| } | |
| pub fn deinit(self: *HittableList) void { | |
| self.objects.deinit(); | |
| } | |
| pub fn append(self: *HittableList, object: Hittable) !void { | |
| try self.objects.append(object); | |
| } | |
| pub fn hit(self: HittableList, r: *Ray, ray_t: Interval, rec: *HitRecord) bool { | |
| var temp_rec = HitRecord{}; | |
| var hit_anything = false; | |
| var closest_so_far = ray_t.max; | |
| for(self.objects.items) |object| { | |
| if(object.hit(r, Interval.init(ray_t.min, closest_so_far), &temp_rec)) { | |
| hit_anything = true; | |
| closest_so_far = temp_rec.t; | |
| rec.t = temp_rec.t; | |
| rec.p = temp_rec.p; | |
| rec.n = temp_rec.n; | |
| rec.f = temp_rec.f; | |
| } | |
| } | |
| return hit_anything; | |
| } | |
| }; | |
| const Sphere = struct { | |
| center: Point3, | |
| radius: f64, | |
| pub fn init(center: Point3, radius: f64) Sphere { | |
| return Sphere{ | |
| .center = center, | |
| .radius = @max(0, radius), | |
| }; | |
| } | |
| pub fn hit(self: *Sphere, r: *Ray, ray_t: Interval, rec: *HitRecord) bool { | |
| const oc = self.center.subtract(r.origin); | |
| const a = r.direction.length_squared(); | |
| const h = r.direction.dot(oc); | |
| const c = oc.length_squared() - self.radius * self.radius; | |
| const discriminant = h * h - a * c; | |
| if(discriminant < 0) { | |
| return false; | |
| } | |
| const sqrtd = std.math.sqrt(discriminant); | |
| var root = (h - sqrtd) / a; | |
| if(!ray_t.surrounds(root)) { | |
| root = (h + sqrtd) / a; | |
| if(!ray_t.surrounds(root)) { | |
| return false; | |
| } | |
| } | |
| rec.t = root; | |
| rec.p = r.at(rec.t); | |
| const outward_normal = rec.p.subtract(self.center).divide(f64, self.radius).unitized(); | |
| rec.set_face_normal(r, outward_normal); | |
| return true; | |
| } | |
| }; | |
| const Hittable = union(enum) { | |
| sphere: *Sphere, | |
| hittable_list: *HittableList, | |
| pub fn hit(self: Hittable, r: *Ray, ray_t: Interval, rec: *HitRecord) bool { | |
| return switch(self) { | |
| inline else => |s| s.hit(r, ray_t, rec), | |
| }; | |
| } | |
| pub fn append(self: Hittable, object: Hittable) !void { | |
| switch(self) { | |
| .hittable_list => |l| try l.append(object), | |
| else => return, | |
| } | |
| } | |
| }; | |
| const Color = Vec3; | |
| const Point3 = Vec3; | |
| const Vec3 = struct { | |
| x: f64 = 0.0, | |
| y: f64 = 0.0, | |
| z: f64 = 0.0, | |
| pub fn initZero() Vec3 { | |
| return Vec3{}; | |
| } | |
| pub fn init(e0: f64, e1: f64, e2: f64) Vec3 { | |
| return Vec3{ | |
| .x = e0, | |
| .y = e1, | |
| .z = e2, | |
| }; | |
| } | |
| pub inline fn neg(self: Vec3) Vec3 { | |
| return Vec3{ | |
| .x = -self.x, | |
| .y = -self.y, | |
| .z = -self.z, | |
| }; | |
| } | |
| pub inline fn add(self: Vec3, other: Vec3) Vec3 { | |
| return Vec3{ | |
| .x = self.x + other.x, | |
| .y = self.y + other.y, | |
| .z = self.z + other.z, | |
| }; | |
| } | |
| pub inline fn subtract(self: Vec3, other: Vec3) Vec3 { | |
| return self.add(other.neg()); | |
| } | |
| pub inline fn multiply(self: Vec3, comptime T: type, t: T)Vec3 { | |
| const _t = if (@TypeOf(t) == f64 ) t else @as(f64, @floatFromInt(t)); | |
| return Vec3{ | |
| .x = self.x * _t, | |
| .y = self.y * _t, | |
| .z = self.z * _t, | |
| }; | |
| } | |
| pub inline fn divide(self: Vec3, comptime T: type, t : T) Vec3 { | |
| const _t = if (@TypeOf(t) == f64) t else @as(f64, @floatFromInt(t)); | |
| return self.multiply(f64, 1.0 / _t); | |
| } | |
| pub inline fn length(self: Vec3) f64 { | |
| return std.math.sqrt(self.length_squared()); | |
| } | |
| pub inline fn length_squared(self: Vec3) f64 { | |
| return self.x * self.x + self.y * self.y + self.z * self.z; | |
| } | |
| pub inline fn dot(self: Vec3, other: Vec3) f64 { | |
| return self.x * other.x + self.y * other.y + self.z * other.z; | |
| } | |
| pub inline fn cross(self: Vec3, other: Vec3) Vec3 { | |
| return Vec3.init( | |
| self.y * other.z - self.z * other.y, | |
| self.z * other.x - self.x * other.z, | |
| self.x * other.y - self.y * other.x, | |
| ); | |
| } | |
| pub inline fn unitized(self: Vec3) Vec3 { | |
| return self.divide(f64, self.length()); | |
| } | |
| }; | |
| const Ray = struct { | |
| origin: Point3 = Point3{}, | |
| direction: Vec3 = Vec3{}, | |
| pub fn at(self: Ray, t: f64) Point3 { | |
| return self.origin.add(self.direction.multiply(f64, t)); | |
| } | |
| }; | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment