Browse code
feat: add raytracer
Implemented up to: https://raytracing.github.io/books/RayTracingInOneWeekend.html#antialiasing
Edward authored on 20/03/2021 09:09:43Showing 1 changed files
1 | 1 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,322 @@ |
1 |
+(defpackage :fwoar.lisp-sandbox.1 |
|
2 |
+ (:use :cl |
|
3 |
+ ) |
|
4 |
+ (:export )) |
|
5 |
+(in-package :fwoar.lisp-sandbox.1) |
|
6 |
+ |
|
7 |
+(define-symbol-macro infinity |
|
8 |
+ #.sb-ext:double-float-positive-infinity) |
|
9 |
+(defun deg2rad (deg) |
|
10 |
+ (/ (* deg pi) |
|
11 |
+ 180.0d0)) |
|
12 |
+ |
|
13 |
+(defstruct (size (:type vector)) |
|
14 |
+ width height) |
|
15 |
+(defstruct (color (:type vector)) |
|
16 |
+ r g b) |
|
17 |
+(defstruct (vec3 (:type vector) |
|
18 |
+ (:constructor vec3 (x y z)) |
|
19 |
+ (:conc-name v3-)) |
|
20 |
+ x y z) |
|
21 |
+ |
|
22 |
+(defun vec+ (vec1 vec2) |
|
23 |
+ (declare (optimize (speed 3))) |
|
24 |
+ (fw.lu:vector-destructuring-bind (a b c) vec1 |
|
25 |
+ (fw.lu:vector-destructuring-bind (a1 b1 c1) vec2 |
|
26 |
+ (vec3 (+ a a1) |
|
27 |
+ (+ b b1) |
|
28 |
+ (+ c c1))))) |
|
29 |
+(define-compiler-macro vec+ (&whole whole vec1 vec2) |
|
30 |
+ (cond ((and (vectorp vec1) |
|
31 |
+ (vectorp vec2)) |
|
32 |
+ (vec+ vec1 vec2)) |
|
33 |
+ ((vectorp vec1) |
|
34 |
+ (alexandria:once-only (vec2) |
|
35 |
+ `(fw.lu:vector-destructuring-bind (a b c) ,vec1 |
|
36 |
+ (vec3 |
|
37 |
+ (+ a (aref ,vec2 0)) |
|
38 |
+ (+ b (aref ,vec2 1)) |
|
39 |
+ (+ c (aref ,vec2 2)))))) |
|
40 |
+ ((vectorp vec2) |
|
41 |
+ (alexandria:once-only (vec1) |
|
42 |
+ `(fw.lu:vector-destructuring-bind (a b c) ,vec2 |
|
43 |
+ (vec3 |
|
44 |
+ (+ a (aref ,vec1 0)) |
|
45 |
+ (+ b (aref ,vec1 1)) |
|
46 |
+ (+ c (aref ,vec1 2)))))) |
|
47 |
+ (t whole))) |
|
48 |
+(defun vec- (vec1 vec2) |
|
49 |
+ (declare (optimize (speed 3))) |
|
50 |
+ (fw.lu:vector-destructuring-bind (a b c) vec1 |
|
51 |
+ (fw.lu:vector-destructuring-bind (a1 b1 c1) vec2 |
|
52 |
+ (vec3 (- a a1) |
|
53 |
+ (- b b1) |
|
54 |
+ (- c c1))))) |
|
55 |
+(defun vec* (vec1 vec2) |
|
56 |
+ (declare (optimize (speed 3))) |
|
57 |
+ (etypecase vec1 |
|
58 |
+ ((array * (3)) (fw.lu:vector-destructuring-bind (a b c) vec1 |
|
59 |
+ (fw.lu:vector-destructuring-bind (a1 b1 c1) vec2 |
|
60 |
+ (vec3 (* a a1) |
|
61 |
+ (* b b1) |
|
62 |
+ (* c c1))))) |
|
63 |
+ (double-float (fw.lu:vector-destructuring-bind (a1 b1 c1) vec2 |
|
64 |
+ (vec3 (* vec1 a1) |
|
65 |
+ (* vec1 b1) |
|
66 |
+ (* vec1 c1)))) |
|
67 |
+ (single-float (fw.lu:vector-destructuring-bind (a1 b1 c1) vec2 |
|
68 |
+ (vec3 (* vec1 a1) |
|
69 |
+ (* vec1 b1) |
|
70 |
+ (* vec1 c1)))) |
|
71 |
+ (number (fw.lu:vector-destructuring-bind (a1 b1 c1) vec2 |
|
72 |
+ (vec3 (* vec1 a1) |
|
73 |
+ (* vec1 b1) |
|
74 |
+ (* vec1 c1)))))) |
|
75 |
+(defun vec/ (vec it) |
|
76 |
+ (declare (optimize (speed 3))) |
|
77 |
+ (vec* (/ 1.0 it) |
|
78 |
+ vec)) |
|
79 |
+(defun dot (u v) |
|
80 |
+ (fw.lu:vector-destructuring-bind (a1 b1 c1) u |
|
81 |
+ (fw.lu:vector-destructuring-bind (a2 b2 c2) v |
|
82 |
+ (+ (* a1 a2) |
|
83 |
+ (* b1 b2) |
|
84 |
+ (* c1 c2))))) |
|
85 |
+(defun cross (u v) |
|
86 |
+ (fw.lu:vector-destructuring-bind (a1 b1 c1) u |
|
87 |
+ (fw.lu:vector-destructuring-bind (a2 b2 c2) v |
|
88 |
+ (vec3 (- (* b1 c2) |
|
89 |
+ (* c1 b2)) |
|
90 |
+ (- (* c1 a2) |
|
91 |
+ (* a1 c2)) |
|
92 |
+ (- (* a1 b2) |
|
93 |
+ (* b1 a2)))))) |
|
94 |
+ |
|
95 |
+(defun length-squared (v) |
|
96 |
+ (fw.lu:vector-destructuring-bind (x y z) v |
|
97 |
+ (+ (* x x) |
|
98 |
+ (* y y) |
|
99 |
+ (* z z)))) |
|
100 |
+(defun vec-length (v) |
|
101 |
+ (sqrt (length-squared v))) |
|
102 |
+(defun unit-vector (v) |
|
103 |
+ (vec/ v |
|
104 |
+ (vec-length v))) |
|
105 |
+ |
|
106 |
+(defun call-with-ppm-header (stream size callback &optional (colors 255)) |
|
107 |
+ (format stream "P3~%~d ~d~%~d~%" |
|
108 |
+ (size-width size) |
|
109 |
+ (size-height size) |
|
110 |
+ colors) |
|
111 |
+ (funcall callback stream)) |
|
112 |
+ |
|
113 |
+#.(progn (defmacro ig (&rest syms) `'(declare (ignore ,@syms))) |
|
114 |
+ nil) |
|
115 |
+ |
|
116 |
+(defvar *color-depth* 255) |
|
117 |
+(defun format-color (s v _ __) |
|
118 |
+ #.(ig _ __) |
|
119 |
+ (fwoar.lisputils:vector-destructuring-bind (r g b) v |
|
120 |
+ (format s "~4d ~4d ~4d" |
|
121 |
+ (round (* *color-depth* r)) |
|
122 |
+ (round (* *color-depth* g)) |
|
123 |
+ (round (* *color-depth* b))))) |
|
124 |
+ |
|
125 |
+(defun write-colors (stream colors columns) |
|
126 |
+ (let ((intermediate ()) |
|
127 |
+ (idx 0)) |
|
128 |
+ (funcall colors |
|
129 |
+ (lambda (color) |
|
130 |
+ (push color intermediate) |
|
131 |
+ (when (= (1- columns) |
|
132 |
+ (mod idx columns)) |
|
133 |
+ (format stream "~{~/fwoar.lisp-sandbox.1::format-color/~^ ~}~&" intermediate) |
|
134 |
+ (setf intermediate ())) |
|
135 |
+ (incf idx))) |
|
136 |
+ (when intermediate |
|
137 |
+ (format stream "~{~/fwoar.lisp-sandbox.1::format-color/~^ ~}~&" intermediate)))) |
|
138 |
+ |
|
139 |
+(fw.lu:defclass+ ray () |
|
140 |
+ ((origin :initarg :origin) |
|
141 |
+ (direction :initarg :direction))) |
|
142 |
+ |
|
143 |
+(defgeneric at (self it) |
|
144 |
+ (:method ((ray ray) (it number)) |
|
145 |
+ (with-slots (origin direction) ray |
|
146 |
+ (vec+ origin |
|
147 |
+ (vec* it direction))))) |
|
148 |
+ |
|
149 |
+(fw.lu:defclass+ hit-record () |
|
150 |
+ ((p :initarg :p :reader .p) |
|
151 |
+ (time :initarg :time :reader .time) |
|
152 |
+ (normal :initarg :normal :accessor .normal :initform ()) |
|
153 |
+ (front-face :initarg :front-face :accessor .front-face :initform ()))) |
|
154 |
+(defun set-face-normal (hit-record r outward-normal) |
|
155 |
+ (prog1 hit-record |
|
156 |
+ (with-slots (direction) r |
|
157 |
+ (let ((front-face (< (dot direction outward-normal) |
|
158 |
+ 0))) |
|
159 |
+ (setf (.front-face hit-record) front-face |
|
160 |
+ (.normal hit-record) (if front-face |
|
161 |
+ outward-normal |
|
162 |
+ (vec* -1 outward-normal))))))) |
|
163 |
+ |
|
164 |
+(defclass hittable () |
|
165 |
+ ()) |
|
166 |
+(fw.lu:defclass+ sphere (hittable) |
|
167 |
+ ((center :initarg :center) |
|
168 |
+ (radius :initarg :radius))) |
|
169 |
+(defgeneric hit (thing ray t-min t-max) |
|
170 |
+ (:method ((things list) (r ray) (t-min real) (t-max real)) |
|
171 |
+ (let (temp-rec |
|
172 |
+ (hit-anything nil) |
|
173 |
+ (closest-so-far t-max)) |
|
174 |
+ (loop for thing in things |
|
175 |
+ for (hit-p hit-rec) = (multiple-value-list |
|
176 |
+ (hit thing r t-min closest-so-far)) |
|
177 |
+ when hit-p do |
|
178 |
+ (setf hit-anything t |
|
179 |
+ closest-so-far (.time hit-rec) |
|
180 |
+ temp-rec hit-rec)) |
|
181 |
+ (when hit-anything |
|
182 |
+ (values hit-anything |
|
183 |
+ temp-rec)))) |
|
184 |
+ (:method ((sphere sphere) (r ray) (t-min real) (t-max real)) |
|
185 |
+ (uiop:nest (with-slots ((%center center) (%radius radius)) sphere) |
|
186 |
+ (let ((center %center) (radius %radius))) |
|
187 |
+ (with-slots ((%origin origin) (%direction direction)) r) |
|
188 |
+ (let ((origin %origin) (direction %direction))) |
|
189 |
+ (let* ((oc (vec- origin center)) |
|
190 |
+ (a (length-squared direction)) |
|
191 |
+ (half-b (dot oc direction)) |
|
192 |
+ (c (- (length-squared oc) |
|
193 |
+ (* radius radius))) |
|
194 |
+ (discriminant (- (* half-b half-b) |
|
195 |
+ (* a c)))) |
|
196 |
+ (if (< discriminant 0) |
|
197 |
+ (return-from hit nil) |
|
198 |
+ (let* ((sqrtd (sqrt discriminant)) |
|
199 |
+ (root (/ (- (- half-b) |
|
200 |
+ sqrtd) |
|
201 |
+ a))) |
|
202 |
+ (when (or (< root t-min) |
|
203 |
+ (< t-max root)) |
|
204 |
+ (setf root (/ (- sqrtd half-b) |
|
205 |
+ a)) |
|
206 |
+ (when (or (< root t-min) |
|
207 |
+ (< t-max root)) |
|
208 |
+ (return-from hit nil))) |
|
209 |
+ (let* ((p (at r root)) |
|
210 |
+ (outward-normal (vec/ (vec- p center) |
|
211 |
+ radius))) |
|
212 |
+ |
|
213 |
+ (values t |
|
214 |
+ (set-face-normal (hit-record p root) |
|
215 |
+ r |
|
216 |
+ outward-normal))))))))) |
|
217 |
+ |
|
218 |
+(defun hit-sphere (center radius r) |
|
219 |
+ (with-slots (origin direction) r |
|
220 |
+ (let* ((oc (vec- origin center)) |
|
221 |
+ (a (length-squared direction)) |
|
222 |
+ (half-b (dot oc direction)) |
|
223 |
+ (c (- (length-squared oc) |
|
224 |
+ (expt radius 2))) |
|
225 |
+ (discriminant (- (* half-b half-b) |
|
226 |
+ (* a c)))) |
|
227 |
+ (if (< discriminant 0) |
|
228 |
+ -1.0d0 |
|
229 |
+ (/ (- (- half-b) (sqrt discriminant)) |
|
230 |
+ a))))) |
|
231 |
+ |
|
232 |
+(defgeneric ray-color (ray world) |
|
233 |
+ (:method ((ray ray) world) |
|
234 |
+ (multiple-value-bind (hit-p rec) |
|
235 |
+ (hit world ray 0 infinity) |
|
236 |
+ (when hit-p |
|
237 |
+ (return-from ray-color |
|
238 |
+ (vec* 0.5 |
|
239 |
+ (vec+ #(1 1 1) |
|
240 |
+ (.normal rec))))) |
|
241 |
+ (with-slots (direction) ray |
|
242 |
+ (let* ((unit-direction (unit-vector direction)) |
|
243 |
+ (it (+ (* 0.5 (v3-y unit-direction)) |
|
244 |
+ 1.0d0))) |
|
245 |
+ (vec+ (vec* (- 1.0d0 it) |
|
246 |
+ #(1.0d0 1.0d0 1.0d0)) |
|
247 |
+ (vec* it |
|
248 |
+ #(0.5d0 0.7d0 1.0d0)))))))) |
|
249 |
+ |
|
250 |
+(defun raytrace (out) |
|
251 |
+ (let* ((world (list (sphere #(0 0 -1) 0.5) |
|
252 |
+ (sphere #(0 -100.5 -1) 100))) |
|
253 |
+ (aspect-ratio (/ 16.0d0 9.0d0)) |
|
254 |
+ (image-width 400) |
|
255 |
+ (image-height (* (floor (/ image-width aspect-ratio)))) |
|
256 |
+ |
|
257 |
+ (viewport-height 2.0d0) |
|
258 |
+ (viewport-width (* aspect-ratio viewport-height)) |
|
259 |
+ (focal-length 1.0d0) |
|
260 |
+ |
|
261 |
+ (origin (vec3 0 0 0)) |
|
262 |
+ (horizontal (vec3 viewport-width 0 0)) |
|
263 |
+ (vertical (vec3 0 viewport-height 0)) |
|
264 |
+ (lower-left-corner (vec- (vec- (vec- origin |
|
265 |
+ (vec/ horizontal 2)) |
|
266 |
+ (vec/ vertical 2)) |
|
267 |
+ (vec3 0 0 focal-length)))) |
|
268 |
+ (alexandria:with-output-to-file (s out :if-exists :supersede) |
|
269 |
+ (call-with-ppm-header s (make-size :width image-width :height image-height) |
|
270 |
+ (lambda (s) |
|
271 |
+ (write-colors s |
|
272 |
+ (lambda (c) |
|
273 |
+ (loop for j from (1- image-height) downto 0 |
|
274 |
+ do (format *trace-output* |
|
275 |
+ "~&Scanlines remaining: ~d ~s~%" |
|
276 |
+ j |
|
277 |
+ (local-time:now)) |
|
278 |
+ do |
|
279 |
+ (loop for i from 0 below image-width |
|
280 |
+ for u = (/ (* 1.0d0 i) |
|
281 |
+ (1- image-width)) |
|
282 |
+ for v = (/ (* 1.0d0 j) |
|
283 |
+ (1- image-height)) |
|
284 |
+ for r = (ray origin |
|
285 |
+ (vec- (vec+ (vec+ lower-left-corner |
|
286 |
+ (vec* u |
|
287 |
+ horizontal)) |
|
288 |
+ (vec* v |
|
289 |
+ vertical)) |
|
290 |
+ origin)) |
|
291 |
+ for color = (ray-color r world) |
|
292 |
+ collect |
|
293 |
+ (funcall c color)))) |
|
294 |
+ image-width)) |
|
295 |
+ (round *color-depth*))))) |
|
296 |
+ |
|
297 |
+(defun sample-image (out) |
|
298 |
+ (let ((image-width 256) |
|
299 |
+ (image-height 256)) |
|
300 |
+ (alexandria:with-output-to-file (s out :if-exists :supersede) |
|
301 |
+ (call-with-ppm-header s (make-size :width image-width :height image-height) |
|
302 |
+ (lambda (s) |
|
303 |
+ (write-colors s |
|
304 |
+ (lambda (c) |
|
305 |
+ (loop for j from (1- image-height) downto 0 |
|
306 |
+ do (format *trace-output* |
|
307 |
+ "~&Scanlines remaining: ~d ~s~%" |
|
308 |
+ j |
|
309 |
+ (local-time:now)) |
|
310 |
+ do |
|
311 |
+ (loop for i from 0 below image-width |
|
312 |
+ collect |
|
313 |
+ (let* ((r (/ (* i 1.0d0) |
|
314 |
+ (1- image-width))) |
|
315 |
+ (g (/ (* j 1.0d0) |
|
316 |
+ (1- image-height))) |
|
317 |
+ (b 0.15d0)) |
|
318 |
+ (funcall c (make-color :r r |
|
319 |
+ :g g |
|
320 |
+ :b b)))))) |
|
321 |
+ image-width)) |
|
322 |
+ (1- #.(expt 2 8)))))) |