Something went wrong. Try again.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191const 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 util = @import("util.zig");
const log = std.log.scoped(.camera);
const Camera = @This();
pub const Options = struct { image_width: usize, aspect_ratio: f32, samples_per_pixel: usize, max_depth: usize,
vfov: f32 = 90, look_from: zm.Vec = zm.f32x4s(0), look_at: zm.Vec = zm.f32x4(0, 0, -1, 0), vup: zm.Vec = zm.f32x4(0, 1, 0, 0),
defocus_angle: f32 = 0, focus_dist: f32 = 10,};
image_height: usize,image_width: usize,aspect_ratio: f32,
samples_per_pixel: usize,samples_per_pixel_v: zm.Vec,max_depth: usize,
vfov: f32,look_from: zm.Vec,look_at: zm.Vec,vup: zm.Vec,
defocus_angle: f32,focus_dist: f32,
// focal_lenght: f32,viewport_height: f32,viewport_width: f32,center: zm.Vec,
viewport_u: zm.Vec,viewport_v: zm.Vec,pixel_delta_u: zm.Vec,pixel_delta_v: zm.Vec,u: zm.Vec,v: zm.Vec,w: zm.Vec,defocus_disk_u: zm.Vec,defocus_disk_v: zm.Vec,
viewport_upper_left: zm.Vec,pixel00_loc: zm.Vec,
image: zigimg.Image,
pub fn init(allocator: std.mem.Allocator, opts: Options) !Camera { const image_width = opts.image_width; const aspect_ratio = opts.aspect_ratio; const image_height = @as(usize, @intFromFloat(@as(f32, @floatFromInt(image_width)) / aspect_ratio)); if (image_height < 1) return error.ImageWidthLessThanOne;
const vfov = opts.vfov; const look_from = opts.look_from; const look_at = opts.look_at; const vup = opts.vup; const center = look_from;
const defocus_angle = opts.defocus_angle; const focus_dist = opts.focus_dist;
// const focal_lenght: f32 = zm.length3(look_from - look_at)[0]; const theta = util.degreesToRadians(opts.vfov); const h = @tan(theta / 2); const viewport_height: f32 = 2 * h * focus_dist; const viewport_width = viewport_height * (@as(f32, @floatFromInt(image_width)) / @as(f32, @floatFromInt(image_height)));
const w = zm.normalize3(look_from - look_at); const u = zm.normalize3(zm.cross3(vup, w)); const v = zm.cross3(w, u);
// Calculate the vectors across the horizontal and down the vertical viewport edges. const viewport_u = zm.f32x4s(viewport_width) * u; const viewport_v = zm.f32x4s(viewport_height) * -v;
// 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 = center - zm.f32x4s(focus_dist) * w - 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);
// Calculate the camera defocus disk basis vectors. const defocus_radius = focus_dist * @tan(util.degreesToRadians(defocus_angle / 2)); const defocus_disk_u = u * zm.f32x4s(defocus_radius); const defocus_disk_v = v * zm.f32x4s(defocus_radius);
// 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,
.samples_per_pixel = opts.samples_per_pixel, .samples_per_pixel_v = zm.f32x4s(@as(f32, @floatFromInt(opts.samples_per_pixel))), .max_depth = opts.max_depth,
.vfov = vfov, .look_from = look_from, .look_at = look_at, .vup = vup,
.defocus_angle = opts.defocus_angle, .focus_dist = opts.focus_dist,
// .focal_lenght = opts.focal_lenght, .viewport_height = viewport_height, .viewport_width = viewport_width, .center = center,
.viewport_u = viewport_u, .viewport_v = viewport_v, .pixel_delta_u = pixel_delta_u, .pixel_delta_v = pixel_delta_v, .u = u, .v = v, .w = w, .defocus_disk_u = defocus_disk_u, .defocus_disk_v = defocus_disk_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 deinit(self: *Camera) void { self.image.deinit();}
pub fn getRay(self: *const Camera, i: usize, j: usize) Ray { const offset = sampleSquare(); const pixel_sample = self.pixel00_loc + (zm.f32x4s(@as(f32, @floatFromInt(i)) + offset[0]) * self.pixel_delta_u) + (zm.f32x4s(@as(f32, @floatFromInt(j)) + offset[1]) * self.pixel_delta_v);
const ray_orig = if (self.defocus_angle <= 0) self.center else self.defocusDiskSample(); const ray_direction = pixel_sample - ray_orig; const ray_time = util.randomF32();
return Ray.initT(ray_orig, ray_direction, ray_time);}
fn sampleSquare() zm.Vec { return zm.f32x4(util.randomF32() - 0.5, util.randomF32() - 0.5, 0, 0);}
fn defocusDiskSample(self: *const Camera) zm.Vec { const p = util.randomInUnitDisk(); return self.center + (zm.f32x4s(p[0]) * self.defocus_disk_u) + (zm.f32x4s(p[1]) * self.defocus_disk_v);}
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: *const 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);}