diff --git a/.gitignore b/.gitignore index 262a202..aec55a6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ -/zig-cache -/zig-out +**/zig-cache +**/zig-out /out diff --git a/src/camera.zig b/src/camera.zig new file mode 100644 index 0000000..0849914 --- /dev/null +++ b/src/camera.zig @@ -0,0 +1,79 @@ +const std = @import("std"); + +const zigimg = @import("zigimg"); +const color = zigimg.color; +const zm = @import("zmath"); + +const log = std.log.scoped(.camera); + +const Camera = @This(); + +image_height: usize, +image_width: usize, +aspect_ratio: f32, + +focal_lenght: f32, +viewport_height: f32, +viewport_width: f32, +camera_center: zm.Vec, + +viewport_u: zm.Vec, +viewport_v: zm.Vec, +pixel_delta_u: zm.Vec, +pixel_delta_v: zm.Vec, + +viewport_upper_left: zm.Vec, +pixel00_loc: zm.Vec, + +image: zigimg.Image, + +pub fn init(allocator: std.mem.Allocator, image_width: usize, aspect_ratio: f32) !Camera { + const image_height = @as(usize, @intFromFloat(@as(f32, @floatFromInt(image_width)) / aspect_ratio)); + if (image_height < 1) return error.ImageWidthLessThanOne; + + const focal_lenght: f32 = 1.0; + const viewport_height: f32 = 2.0; + const viewport_width = viewport_height * (@as(f32, @floatFromInt(image_width)) / @as(f32, @floatFromInt(image_height))); + const camera_center = zm.f32x4s(0.0); + + // Calculate the vectors across the horizontal and down the vertical viewport edges. + const viewport_u = zm.f32x4(viewport_width, 0, 0, 0); + const viewport_v = zm.f32x4(0, -viewport_height, 0, 0); + + // Calculate the horizontal and vertical delta vectors from pixel to pixel. + const pixel_delta_u = viewport_u / zm.f32x4s(@as(f32, @floatFromInt(image_width))); + const pixel_delta_v = viewport_v / zm.f32x4s(@as(f32, @floatFromInt(image_height))); + + // Calculate the location of the upper left pixel. + const viewport_upper_left = camera_center - zm.f32x4(0, 0, focal_lenght, 0) - viewport_u / zm.f32x4s(2.0) - viewport_v / zm.f32x4s(2.0); + const pixel00_loc = viewport_upper_left + zm.f32x4s(0.5) * (pixel_delta_u + pixel_delta_v); + + log.debug("image_width: {}, image_height: {}, aspect_ratio: {d:.2}, focal_lenght: {d:.1}", .{ image_width, image_height, aspect_ratio, focal_lenght }); + + return Camera{ + .image_width = image_width, + .image_height = image_height, + .aspect_ratio = aspect_ratio, + + .focal_lenght = focal_lenght, + .viewport_height = viewport_height, + .viewport_width = viewport_width, + .camera_center = camera_center, + + .viewport_u = viewport_u, + .viewport_v = viewport_v, + .pixel_delta_u = pixel_delta_u, + .pixel_delta_v = pixel_delta_v, + + .viewport_upper_left = viewport_upper_left, + .pixel00_loc = pixel00_loc, + + .image = try zigimg.Image.create(allocator, image_width, image_height, zigimg.PixelFormat.rgba32), + }; +} + +pub fn setPixel(self: *Camera, x: usize, y: usize, c: color.Rgba32) !void { + if (x >= self.image_width or y >= self.image_height) return error.OutOfBounds; + const i = x + self.image_width * y; + self.image.pixels.rgba32[i] = c; +} diff --git a/src/ray.zig b/src/ray.zig new file mode 100644 index 0000000..0e41229 --- /dev/null +++ b/src/ray.zig @@ -0,0 +1,19 @@ +const zm = @import("zmath"); + +const Ray = @This(); + +orig: zm.Vec, +dir: zm.Vec, + +pub fn init(origin: zm.Vec, direction: zm.Vec) Ray { + return Ray{ + .orig = origin, + .dir = direction, + }; +} + +pub fn color(self: *Ray) zm.Vec { + const unit_direction = zm.normalize3(self.dir); + const a = 0.5 * (unit_direction[1] + 1.0); + return zm.f32x4s(1.0 - a) * zm.f32x4s(1.0) + zm.f32x4s(a) * zm.f32x4(0.5, 0.7, 1.0, 1.0); +} diff --git a/src/rayray.zig b/src/rayray.zig index 8875ef5..6367799 100644 --- a/src/rayray.zig +++ b/src/rayray.zig @@ -3,6 +3,10 @@ const std = @import("std"); const spall = @import("spall"); const zigimg = @import("zigimg"); const color = zigimg.color; +const zm = @import("zmath"); + +const Camera = @import("camera.zig"); +const Ray = @import("ray.zig"); const log = std.log.scoped(.rayray); @@ -16,7 +20,7 @@ pub const Raytracer = struct { pub fn init(allocator: std.mem.Allocator) !Self { return .{ .allocator = allocator, - .camera = try Camera.init(allocator, 256 * 10, 256 * 10), + .camera = try Camera.init(allocator, 400, 16.0 / 9.0), }; } @@ -30,9 +34,9 @@ pub const Raytracer = struct { defer s.end(); const rows: usize = try std.Thread.getCpuCount(); - const row_height = @divTrunc(self.camera.height, rows); + const row_height = @divTrunc(self.camera.image_height, rows); const num_threads = blk: { - if (self.camera.height % rows == 0) { + if (self.camera.image_height % rows == 0) { break :blk rows; } break :blk rows + 1; @@ -44,7 +48,7 @@ pub const Raytracer = struct { defer self.allocator.free(threads); for (0..num_threads) |row| { - const t = try std.Thread.spawn(.{}, r, .{ &self.camera, row, row_height }); + const t = try std.Thread.spawn(.{}, render_thread, .{ &self.camera, row, row_height }); threads[row] = t; } @@ -55,55 +59,34 @@ pub const Raytracer = struct { return self.camera.image; } - fn r(cam: *Camera, row: usize, height: usize) void { + fn render_thread(cam: *Camera, row: usize, height: usize) void { spall.init_thread(); defer spall.deinit_thread(); const s = spall.trace(@src(), "Render Thread {}", .{row}); defer s.end(); - for (0..height) |iy| { - const y = iy + height * row; - if (y >= cam.height) break; - - for (0..cam.width) |x| { - const col = blk: { - if (iy <= height - 5) { - @setRuntimeSafety(false); - break :blk color.Rgba32.initRgba( - @intCast(x), - @intCast(y), - 0, - 255, - ); - } - break :blk color.Rgba32.initRgba(0, 0, 255, 255); - }; - - cam.setPixel(x, y, col) catch break; + for (0..height) |ij| { + const j = ij + height * row; + if (j >= cam.image_height) break; + + for (0..cam.image_width) |i| { + const pixel_center = cam.pixel00_loc + (zm.f32x4s(@as(f32, @floatFromInt(i))) * cam.pixel_delta_u) + (zm.f32x4s(@as(f32, @floatFromInt(j))) * cam.pixel_delta_v); + const ray_direction = pixel_center - cam.camera_center; + var ray = Ray.init(cam.camera_center, ray_direction); + const col = vecToRgba(ray.color()); + + cam.setPixel(i, j, col) catch break; } } } }; -pub const Camera = struct { - width: usize, - height: usize, - image: zigimg.Image, +fn vecToRgba(v: zm.Vec) color.Rgba32 { + const r: u8 = @intFromFloat(255.999 * v[0]); + const g: u8 = @intFromFloat(255.999 * v[1]); + const b: u8 = @intFromFloat(255.999 * v[2]); + const a: u8 = @intFromFloat(255.999 * v[3]); - pub fn init(allocator: std.mem.Allocator, width: usize, height: usize) !Camera { - const img = try zigimg.Image.create(allocator, width, height, zigimg.PixelFormat.rgba32); - - return Camera{ - .width = width, - .height = height, - .image = img, - }; - } - - pub fn setPixel(self: *Camera, x: usize, y: usize, c: color.Rgba32) !void { - if (x >= self.width or y >= self.height) return error.OutOfBounds; - const i = x + self.width * y; - self.image.pixels.rgba32[i] = c; - } -}; + return color.Rgba32.initRgba(r, g, b, a); +}