Skip to content

Commit

Permalink
Raytracer Demo (#65)
Browse files Browse the repository at this point in the history
  • Loading branch information
IridescentRose authored May 18, 2024
1 parent 1f5ca8c commit c21982d
Show file tree
Hide file tree
Showing 12 changed files with 806 additions and 0 deletions.
1 change: 1 addition & 0 deletions showcase/build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const carts = .{
.{ "blobs", @import("blobs") },
.{ "plasma", @import("plasma") },
.{ "metalgear_timer", @import("metalgear_timer") },
.{ "raytracer", @import("raytracer") },
.{ "neopixelpuzzle", @import("neopixelpuzzle") },
};

Expand Down
1 change: 1 addition & 0 deletions showcase/build.zig.zon
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
.blobs = .{ .path = "carts/blobs" },
.plasma = .{ .path = "carts/plasma" },
.metalgear_timer = .{ .path = "carts/metalgear-timer" },
.raytracer = .{ .path = "carts/raytracer" },
.neopixelpuzzle = .{ .path = "carts/neopixelpuzzle" },
},
.paths = .{
Expand Down
19 changes: 19 additions & 0 deletions showcase/carts/raytracer/build.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
const std = @import("std");
const sycl_badge = @import("sycl_badge");

pub const author_name = "Nathan Bourgeois";
pub const author_handle = "iridescentrose";
pub const cart_title = "Raytracer";
pub const description = "Raytracing in One Weekend raytracer on badge!";

pub fn build(b: *std.Build) void {
const optimize = b.standardOptimizeOption(.{});
const sycl_badge_dep = b.dependency("sycl_badge", .{});

const cart = sycl_badge.add_cart(sycl_badge_dep, b, .{
.name = "raytracer",
.optimize = optimize,
.root_source_file = b.path("src/main.zig"),
});
cart.install(b);
}
12 changes: 12 additions & 0 deletions showcase/carts/raytracer/build.zig.zon
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.{
.name = "raytracer",
.version = "0.0.0",
.dependencies = .{
.sycl_badge = .{ .path = "../../.." },
},
.paths = .{
"build.zig",
"build.zig.zon",
"src",
},
}
153 changes: 153 additions & 0 deletions showcase/carts/raytracer/src/camera.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
const Camera = @This();
const vec = @import("vec.zig");
const Point3 = vec.Point3;
const Vec3 = vec.Vec3;
const Color3 = vec.Color3;
const std = @import("std");
const Ray = @import("ray.zig");
const hit = @import("hit.zig");
const HittableList = hit.HittableList;
const HitRecord = hit.HitRecord;
const Interval = @import("interval.zig");
const cart = @import("cart-api");

const aspect_ratio = 5.0 / 4.0;
const image_width: usize = 160;
const image_height: usize = @intFromFloat(@as(f32, @floatFromInt(image_width)) / aspect_ratio);

const theta: f32 = vfov / 180.0 * std.math.pi;
const h: f32 = std.math.tan(theta / 2.0);
const viewport_height: f32 = 2 * h * focus_distance;
const viewport_width: f32 = viewport_height * @as(f32, @floatFromInt(image_width)) / @as(f32, @floatFromInt(image_height));
const vfov: f32 = 20;
const lookat = Point3.init(0, 0, 0);
const vup = Vec3.init(0, 1, 0);
const defocus_angle: f32 = 0.6;
const focus_distance: f32 = 10.0;

lookfrom: Vec3,
pixel00_location: Vec3,
pixel_delta_u: Vec3,
pixel_delta_v: Vec3,
camera_center: Vec3,
samples: usize,
max_children: usize,
rand_engine: std.rand.DefaultPrng,
random: std.rand.Random,
u: Vec3,
v: Vec3,
w: Vec3,
defocus_disk_u: Vec3,
defocus_disk_v: Vec3,

pub fn init(position: Vec3) Camera {
var self: Camera = undefined;
self.lookfrom = position;
self.camera_center = self.lookfrom;

self.w = self.lookfrom.sub(lookat).unit_vector();
self.u = vup.cross(self.w).unit_vector();
self.v = self.w.cross(self.u);

var viewport_u = self.u.mul_scalar(viewport_width);
var viewport_v = self.v.mul_scalar(-viewport_height);

self.pixel_delta_u = viewport_u.div_scalar(@floatFromInt(image_width));
self.pixel_delta_v = viewport_v.div_scalar(@floatFromInt(image_height));

self.rand_engine = std.rand.DefaultPrng.init(0);
self.random = self.rand_engine.random();
self.samples = 25;
self.max_children = 7;

var viewport_upper_left = self.camera_center.sub(self.w.mul_scalar(focus_distance)).sub(viewport_u.div_scalar(2.0)).sub(viewport_v.div_scalar(2.0));
self.pixel00_location = viewport_upper_left.add(self.pixel_delta_u.add(self.pixel_delta_v).mul_scalar(0.5));

const defocus_radians = (defocus_angle / 2.0) / 180.0 * std.math.pi;
const defocus_radius = focus_distance * @tan(defocus_radians);

self.defocus_disk_u = self.u.mul_scalar(defocus_radius);
self.defocus_disk_v = self.v.mul_scalar(defocus_radius);

return self;
}

fn pixel_sample_square(self: *Camera) Vec3 {
const x = -0.5 + self.random.float(f32);
const y = -0.5 + self.random.float(f32);

return self.pixel_delta_u.mul_scalar(x).add(self.pixel_delta_v.mul_scalar(y));
}

fn defocus_disk_sample(self: *Camera) Vec3 {
const p = Vec3.random_in_unit_disk(self.random);
return self.camera_center.add(self.defocus_disk_u.mul_scalar(p.data[0])).add(self.defocus_disk_v.mul_scalar(p.data[1]));
}

pub fn get_ray(self: *Camera, i: u32, j: u32) Ray {
var pixel_center = self.pixel00_location.add(self.pixel_delta_u.mul_scalar(@floatFromInt(i))).add(self.pixel_delta_v.mul_scalar(@floatFromInt(j)));
var pixel_sample = pixel_center.add(self.pixel_sample_square());

const ray_origin = if (defocus_angle <= 0) self.camera_center else self.defocus_disk_sample();
const ray_direction = pixel_sample.sub(ray_origin);

return Ray.init(ray_origin, ray_direction);
}

pub fn render(self: *Camera, world: *HittableList) !void {
var j: u32 = 0;
while (j < image_height) : (j += 1) {
//std.debug.print("Scanlines Remaining: {}\n", .{image_height - j});

var i: u32 = 0;
while (i < image_width) : (i += 1) {
var col = Color3.zero();

for (0..self.samples) |_| {
const ray = self.get_ray(i, j);
_ = col.addEq(self.ray_color(ray, self.max_children, world));
}

const samples: f32 = @floatFromInt(self.samples);
col = col.div_scalar(samples);

const index: usize = i + j * image_width;
const color = vec.color3_to_color(col);
cart.framebuffer[index] = .{
.r = @truncate(color.rgb.r >> 3),
.g = @truncate(color.rgb.g >> 2),
.b = @truncate(color.rgb.b >> 3),
};
}
}
//std.debug.print("Done!\n", .{});
}

fn ray_color(self: *Camera, ray: Ray, depth: usize, world: *HittableList) Color3 {
var hit_record: HitRecord = undefined;

if (depth == 0) {
return Color3.zero();
}

if (world.hit(ray, Interval{ .min = 0.001, .max = std.math.inf(f32) }, &hit_record)) {
var scattered: Ray = undefined;
var attenuation: Color3 = undefined;

const mat = hit_record.mat;
if (mat.vtable.scatter(mat.ptr, self.random, ray, hit_record, &attenuation, &scattered)) {
var col = self.ray_color(scattered, depth - 1, world);
return col.mul(attenuation);
}

return Color3.zero();
}

var dir = ray.direction.unit_vector();
const a = 0.5 * (dir.y() + 1.0);

const white = Color3.init(1.0, 1.0, 1.0);
const blue = Color3.init(0.5, 0.7, 1.0);

return white.mul_scalar(1.0 - a).add(blue.mul_scalar(a));
}
116 changes: 116 additions & 0 deletions showcase/carts/raytracer/src/hit.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
const vec = @import("vec.zig");
const Vec3 = vec.Vec3;
const Point3 = vec.Point3;
const Interval = @import("interval.zig");
const Ray = @import("ray.zig");
const std = @import("std");
const material = @import("material.zig");
const Material = material.Material;

pub const HitRecord = struct {
point: Point3,
normal: Vec3,
t: f32,
front_face: bool,
mat: Material,

pub fn set_face_normal(self: *HitRecord, ray: Ray, outward_normal: Vec3) void {
self.front_face = ray.direction.dot(outward_normal) < 0;
self.normal = if (self.front_face) outward_normal else outward_normal.negate();
}
};

pub const Hittable = struct {
ptr: *anyopaque,
vtable: *const VTable,

pub const VTable = struct {
hit: *const fn (ctx: *anyopaque, ray: Ray, ray_t: Interval, hit_record: *HitRecord, ret_addr: usize) bool,
};

pub fn rawHit(self: Hittable, ray: Ray, ray_t: Interval, hit_record: *HitRecord, ret_addr: usize) bool {
return self.vtable.hit(self.ptr, ray, ray_t, hit_record, ret_addr);
}
};

pub const Sphere = struct {
center: Point3,
radius: f32,
mat: Material,

pub fn init(center: Point3, radius: f32, mat: Material) Sphere {
return Sphere{ .center = center, .radius = radius, .mat = mat };
}

pub fn hit(ctx: *anyopaque, ray: Ray, ray_t: Interval, hit_record: *HitRecord, ret_addr: usize) bool {
_ = ret_addr;
const self: *Sphere = @ptrCast(@alignCast(ctx));

const oc = ray.origin.sub(self.center);
const a = ray.direction.length_squared();
const half_b = oc.dot(ray.direction);
const c = oc.length_squared() - self.radius * self.radius;

const discriminant = half_b * half_b - a * c;
if (discriminant < 0)
return false;

const sqrtd = std.math.sqrt(discriminant);

var root = (-half_b - sqrtd) / a;

if (!ray_t.surrounds(root)) {
root = (-half_b + sqrtd) / a;
if (!ray_t.surrounds(root))
return false;
}

hit_record.t = root;
hit_record.point = ray.at(root);
const outward_normal = hit_record.point.sub(self.center).div_scalar(self.radius);
hit_record.set_face_normal(ray, outward_normal);
hit_record.mat = self.mat;

return true;
}

pub fn hittable(self: *Sphere) Hittable {
return .{
.ptr = self,
.vtable = &.{
.hit = hit,
},
};
}
};

pub const HittableList = struct {
objects: std.ArrayList(Hittable),

pub fn init(allocator: std.mem.Allocator) !HittableList {
var self: HittableList = undefined;
self.objects = std.ArrayList(Hittable).init(allocator);
return self;
}

pub fn hit(self: *HittableList, ray: Ray, ray_t: Interval, hit_record: *HitRecord) bool {
var record: HitRecord = undefined;
var hit_anything: bool = false;
var closest_so_far = ray_t.max;

for (self.objects.items) |item| {
const is_hit = item.vtable.hit(item.ptr, ray, ray_t, &record, @returnAddress());

if (is_hit) {
// Make sure record is only set if it's closer
if (record.t < closest_so_far) {
hit_anything = true;
closest_so_far = record.t;
hit_record.* = record;
}
}
}

return hit_anything;
}
};
32 changes: 32 additions & 0 deletions showcase/carts/raytracer/src/interval.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
const std = @import("std");

const Interval = @This();

max: f32,
min: f32,

pub fn empty() Interval {
return .{ .min = std.math.inf(f32), .max = -std.math.inf(f32) };
}

pub fn universe() Interval {
return .{ .min = -std.math.inf(f32), .max = std.math.inf(f32) };
}

pub fn contains(self: *const Interval, x: f32) bool {
return self.min <= x and x <= self.max;
}

pub fn surrounds(self: *const Interval, x: f32) bool {
return self.min < x and x < self.max;
}

pub fn clamp(self: *const Interval, x: f32) f32 {
if (x < self.min) {
return self.min;
} else if (x > self.max) {
return self.max;
}

return x;
}
Loading

0 comments on commit c21982d

Please sign in to comment.