Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Raytracer Demo #65

Merged
merged 2 commits into from
May 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading