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