this repo has no description
1const std = @import("std");
2const random = std.crypto.random;
3
4const zigimg = @import("zigimg");
5const color = zigimg.color;
6const zm = @import("zmath");
7
8pub const Ray = @import("Ray.zig");
9const util = @import("util.zig");
10
11const log = std.log.scoped(.camera);
12
13const Camera = @This();
14
15pub const Options = struct {
16 image_width: usize,
17 aspect_ratio: f32,
18 samples_per_pixel: usize,
19 max_depth: usize,
20
21 vfov: f32 = 90,
22 look_from: zm.Vec = zm.f32x4s(0),
23 look_at: zm.Vec = zm.f32x4(0, 0, -1, 0),
24 vup: zm.Vec = zm.f32x4(0, 1, 0, 0),
25
26 defocus_angle: f32 = 0,
27 focus_dist: f32 = 10,
28};
29
30image_height: usize,
31image_width: usize,
32aspect_ratio: f32,
33
34samples_per_pixel: usize,
35samples_per_pixel_v: zm.Vec,
36max_depth: usize,
37
38vfov: f32,
39look_from: zm.Vec,
40look_at: zm.Vec,
41vup: zm.Vec,
42
43defocus_angle: f32,
44focus_dist: f32,
45
46// focal_lenght: f32,
47viewport_height: f32,
48viewport_width: f32,
49center: zm.Vec,
50
51viewport_u: zm.Vec,
52viewport_v: zm.Vec,
53pixel_delta_u: zm.Vec,
54pixel_delta_v: zm.Vec,
55u: zm.Vec,
56v: zm.Vec,
57w: zm.Vec,
58defocus_disk_u: zm.Vec,
59defocus_disk_v: zm.Vec,
60
61viewport_upper_left: zm.Vec,
62pixel00_loc: zm.Vec,
63
64image: zigimg.Image,
65
66pub fn init(allocator: std.mem.Allocator, opts: Options) !Camera {
67 const image_width = opts.image_width;
68 const aspect_ratio = opts.aspect_ratio;
69 const image_height = @as(usize, @intFromFloat(@as(f32, @floatFromInt(image_width)) / aspect_ratio));
70 if (image_height < 1) return error.ImageWidthLessThanOne;
71
72 const vfov = opts.vfov;
73 const look_from = opts.look_from;
74 const look_at = opts.look_at;
75 const vup = opts.vup;
76 const center = look_from;
77
78 const defocus_angle = opts.defocus_angle;
79 const focus_dist = opts.focus_dist;
80
81 // const focal_lenght: f32 = zm.length3(look_from - look_at)[0];
82 const theta = util.degreesToRadians(opts.vfov);
83 const h = @tan(theta / 2);
84 const viewport_height: f32 = 2 * h * focus_dist;
85 const viewport_width = viewport_height * (@as(f32, @floatFromInt(image_width)) / @as(f32, @floatFromInt(image_height)));
86
87 const w = zm.normalize3(look_from - look_at);
88 const u = zm.normalize3(zm.cross3(vup, w));
89 const v = zm.cross3(w, u);
90
91 // Calculate the vectors across the horizontal and down the vertical viewport edges.
92 const viewport_u = zm.f32x4s(viewport_width) * u;
93 const viewport_v = zm.f32x4s(viewport_height) * -v;
94
95 // Calculate the horizontal and vertical delta vectors from pixel to pixel.
96 const pixel_delta_u = viewport_u / zm.f32x4s(@as(f32, @floatFromInt(image_width)));
97 const pixel_delta_v = viewport_v / zm.f32x4s(@as(f32, @floatFromInt(image_height)));
98
99 // Calculate the location of the upper left pixel.
100 const viewport_upper_left = center - zm.f32x4s(focus_dist) * w - viewport_u / zm.f32x4s(2.0) - viewport_v / zm.f32x4s(2.0);
101 const pixel00_loc = viewport_upper_left + zm.f32x4s(0.5) * (pixel_delta_u + pixel_delta_v);
102
103 // Calculate the camera defocus disk basis vectors.
104 const defocus_radius = focus_dist * @tan(util.degreesToRadians(defocus_angle / 2));
105 const defocus_disk_u = u * zm.f32x4s(defocus_radius);
106 const defocus_disk_v = v * zm.f32x4s(defocus_radius);
107
108 // log.debug("image_width: {}, image_height: {}, aspect_ratio: {d:.2}, focal_lenght: {d:.1}", .{
109 // image_width,
110 // image_height,
111 // aspect_ratio,
112 // focal_lenght,
113 // });
114
115 return Camera{
116 .image_width = image_width,
117 .image_height = image_height,
118 .aspect_ratio = aspect_ratio,
119
120 .samples_per_pixel = opts.samples_per_pixel,
121 .samples_per_pixel_v = zm.f32x4s(@as(f32, @floatFromInt(opts.samples_per_pixel))),
122 .max_depth = opts.max_depth,
123
124 .vfov = vfov,
125 .look_from = look_from,
126 .look_at = look_at,
127 .vup = vup,
128
129 .defocus_angle = opts.defocus_angle,
130 .focus_dist = opts.focus_dist,
131
132 // .focal_lenght = opts.focal_lenght,
133 .viewport_height = viewport_height,
134 .viewport_width = viewport_width,
135 .center = center,
136
137 .viewport_u = viewport_u,
138 .viewport_v = viewport_v,
139 .pixel_delta_u = pixel_delta_u,
140 .pixel_delta_v = pixel_delta_v,
141 .u = u,
142 .v = v,
143 .w = w,
144 .defocus_disk_u = defocus_disk_u,
145 .defocus_disk_v = defocus_disk_v,
146
147 .viewport_upper_left = viewport_upper_left,
148 .pixel00_loc = pixel00_loc,
149
150 .image = try zigimg.Image.create(allocator, image_width, image_height, zigimg.PixelFormat.rgba32),
151 };
152}
153
154pub fn deinit(self: *Camera) void {
155 self.image.deinit();
156}
157
158pub fn getRay(self: *const Camera, i: usize, j: usize) Ray {
159 const offset = sampleSquare();
160 const pixel_sample = self.pixel00_loc +
161 (zm.f32x4s(@as(f32, @floatFromInt(i)) + offset[0]) * self.pixel_delta_u) +
162 (zm.f32x4s(@as(f32, @floatFromInt(j)) + offset[1]) * self.pixel_delta_v);
163
164 const ray_orig = if (self.defocus_angle <= 0) self.center else self.defocusDiskSample();
165 const ray_direction = pixel_sample - ray_orig;
166 const ray_time = util.randomF32();
167
168 return Ray.initT(ray_orig, ray_direction, ray_time);
169}
170
171fn sampleSquare() zm.Vec {
172 return zm.f32x4(util.randomF32() - 0.5, util.randomF32() - 0.5, 0, 0);
173}
174
175fn defocusDiskSample(self: *const Camera) zm.Vec {
176 const p = util.randomInUnitDisk();
177 return self.center + (zm.f32x4s(p[0]) * self.defocus_disk_u) + (zm.f32x4s(p[1]) * self.defocus_disk_v);
178}
179
180pub fn setPixel(self: *Camera, x: usize, y: usize, c: color.Rgba32) !void {
181 if (x >= self.image_width or y >= self.image_height) return error.OutOfBounds;
182 const i = x + self.image_width * y;
183 self.image.pixels.rgba32[i] = c;
184}
185
186fn pixelSamplesSq(self: *const Camera) zm.Vec {
187 const px = zm.f32x4s(-0.5 + random.float(f32));
188 const py = zm.f32x4s(-0.5 + random.float(f32));
189 return (px * self.pixel_delta_u) + (py * self.pixel_delta_v);
190}