diff --git a/src/camera.zig b/src/camera.zig index f20737f..c96280b 100644 --- a/src/camera.zig +++ b/src/camera.zig @@ -1,9 +1,12 @@ const std = @import("std"); +const random = std.crypto.random; const zigimg = @import("zigimg"); const color = zigimg.color; const zm = @import("zmath"); +pub const Ray = @import("ray.zig"); + const log = std.log.scoped(.camera); const Camera = @This(); @@ -11,11 +14,13 @@ const Camera = @This(); pub const Options = struct { image_width: usize, aspect_ratio: f32, + samples_per_pixel: usize, }; image_height: usize, image_width: usize, aspect_ratio: f32, +samples_per_pixel: usize, focal_lenght: f32, viewport_height: f32, @@ -61,6 +66,7 @@ pub fn init(allocator: std.mem.Allocator, opts: Options) !Camera { .image_width = image_width, .image_height = image_height, .aspect_ratio = aspect_ratio, + .samples_per_pixel = opts.samples_per_pixel, .focal_lenght = focal_lenght, .viewport_height = viewport_height, @@ -83,8 +89,22 @@ pub fn deinit(self: *Camera) void { self.image.deinit(); } +pub fn getRay(self: *Camera, i: usize, j: usize) Ray { + const pixel_center = self.pixel00_loc + (zm.f32x4s(@as(f32, @floatFromInt(i))) * self.pixel_delta_u) + (zm.f32x4s(@as(f32, @floatFromInt(j))) * self.pixel_delta_v); + const pixel_sample = pixel_center + self.pixelSamplesSq(); + + const ray_direction = pixel_sample - self.camera_center; + return Ray.init(self.camera_center, ray_direction); +} + 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; } + +fn pixelSamplesSq(self: *Camera) zm.Vec { + const px = zm.f32x4s(-0.5 + random.float(f32)); + const py = zm.f32x4s(-0.5 + random.float(f32)); + return (px * self.pixel_delta_u) + (py * self.pixel_delta_v); +} diff --git a/src/interval.zig b/src/interval.zig index 9d39605..cface37 100644 --- a/src/interval.zig +++ b/src/interval.zig @@ -81,6 +81,12 @@ pub fn Interval(comptime T: type) type { return self.min < x and x < self.max; } + pub fn clamp(self: *const Self, x: T) T { + if (x < self.min) return self.min; + if (x > self.max) return self.max; + return x; + } + pub fn iter(self: *const Self) Iterator { return Iterator{ .interval = self.*, @@ -109,6 +115,12 @@ pub fn Interval(comptime T: type) type { pub fn surrounds(self: *const Self, x: T) bool { return self.min < x and x < self.max; } + + pub fn clamp(self: *const Self, x: T) T { + if (x < self.min) return self.min; + if (x > self.max) return self.max; + return x; + } }; } else { @compileError("Interval only supports Int and Float Types!"); diff --git a/src/main.zig b/src/main.zig index 211397e..51e9bb6 100644 --- a/src/main.zig +++ b/src/main.zig @@ -33,7 +33,7 @@ pub fn main() !void { const s = spall.trace(@src(), "Raytracer", .{}); // Raytracing part - var raytracer = try rayray.Raytracer.init(allocator, world, .{ .aspect_ratio = 16.0 / 9.0, .image_width = 400 }); + var raytracer = try rayray.Raytracer.init(allocator, world, .{ .aspect_ratio = 16.0 / 9.0, .image_width = 400, .samples_per_pixel = 100 }); defer raytracer.deinit(); const img = try raytracer.render(); diff --git a/src/rayray.zig b/src/rayray.zig index 7c10dcb..fe32310 100644 --- a/src/rayray.zig +++ b/src/rayray.zig @@ -64,15 +64,14 @@ pub const Raytracer = struct { threads[row].thread = t; } - // const stderr = std.io.getStdErr(); - // // defer stderr.close(); + const stderr = std.io.getStdErr(); - // var progress = std.Progress{ - // .terminal = stderr, - // .supports_ansi_escape_codes = true, - // }; - // var node = progress.start("Rendering Completed", num_threads); - // node.activate(); + var progress = std.Progress{ + .terminal = stderr, + .supports_ansi_escape_codes = true, + }; + var node = progress.start("Rendering Completed", num_threads); + node.activate(); while (true) { var done = true; @@ -93,7 +92,7 @@ pub const Raytracer = struct { if (done) break; } - // node.end(); + node.end(); return self.camera.image; } diff --git a/src/renderer.zig b/src/renderer.zig index e094513..f2948a1 100644 --- a/src/renderer.zig +++ b/src/renderer.zig @@ -36,12 +36,13 @@ pub fn run(ctx: Context, height: IntervalUsize, width: IntervalUsize) void { var width_iter = width.iter(); while (width_iter.nextExc()) |i| { - const pixel_center = ctx.cam.pixel00_loc + (zm.f32x4s(@as(f32, @floatFromInt(i))) * ctx.cam.pixel_delta_u) + (zm.f32x4s(@as(f32, @floatFromInt(j))) * ctx.cam.pixel_delta_v); - const ray_direction = pixel_center - ctx.cam.camera_center; - var ray = Ray.init(ctx.cam.camera_center, ray_direction); - const col = vecToRgba(rayColor(&ray, ctx.world)); + var col = zm.f32x4(0.0, 0.0, 0.0, 1.0); + for (0..ctx.cam.samples_per_pixel) |_| { + var ray = ctx.cam.getRay(i, j); + col += rayColor(&ray, ctx.world); + } - ctx.cam.setPixel(i, j, col) catch break; + ctx.cam.setPixel(i, j, vecToRgba(col, ctx.cam.samples_per_pixel)) catch break; } } } @@ -63,11 +64,19 @@ pub fn renderThread(ctx: Context, done: *std.atomic.Value(bool), row: usize, row done.store(true, .Release); } -fn vecToRgba(v: zm.Vec) zigimg.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]); +fn vecToRgba(v: zm.Vec, samples_per_pixel: usize) zigimg.color.Rgba32 { + const scale: f32 = 1.0 / @as(f32, @floatFromInt(samples_per_pixel)); + const intensity = IntervalF32.init(0.0, 0.999); + + const r_scaled = v[0] * scale; + const g_scaled = v[1] * scale; + const b_scaled = v[2] * scale; + const a_scaled = v[3] * scale; + + const r: u8 = @intFromFloat(256 * intensity.clamp(r_scaled)); + const g: u8 = @intFromFloat(256 * intensity.clamp(g_scaled)); + const b: u8 = @intFromFloat(256 * intensity.clamp(b_scaled)); + const a: u8 = @intFromFloat(256 * intensity.clamp(a_scaled)); return zigimg.color.Rgba32.initRgba(r, g, b, a); }