git.fiddlerwoaroof.com
Browse code

feat: antialiasing, diffuse materials, etc.

Edward authored on 21/03/2021 01:48:15
Showing 1 changed files
... ...
@@ -10,6 +10,12 @@
10 10
   (/ (* deg pi)
11 11
      180.0d0))
12 12
 
13
+(defun rand-min-max (min max)
14
+  (+ min
15
+     (* (- max min)
16
+        (random 1.0d0))))
17
+
18
+
13 19
 (defstruct (size (:type vector))
14 20
   width height)
15 21
 (defstruct (color (:type vector))
... ...
@@ -19,6 +25,23 @@
19 25
                  (:conc-name v3-))
20 26
   x y z)
21 27
 
28
+(defun rand-vec3 ()
29
+  (vec3 (random 1.0d0)
30
+        (random 1.0d0)
31
+        (random 1.0d0)))
32
+
33
+(defun rand-vec3-min-max (min max)
34
+  (vec3 (rand-min-max min max)
35
+        (rand-min-max min max)
36
+        (rand-min-max min max)))
37
+
38
+(declaim (inline vec+ vec* vec- vec/ random-in-unit-sphere))
39
+(defun random-in-unit-sphere ()
40
+  (loop for p = (rand-vec3-min-max -1.0d0 1.0d0)
41
+        while (>= (length-squared p)
42
+                  1)
43
+        finally (return p)))
44
+
22 45
 (defun vec+ (vec1 vec2)
23 46
   (declare (optimize (speed 3)))
24 47
   (fw.lu:vector-destructuring-bind (a b c) vec1
... ...
@@ -114,13 +137,26 @@
114 137
          nil)
115 138
 
116 139
 (defvar *color-depth* 255)
140
+
141
+(defun clamp (x min max)
142
+  (cond ((< x min) min)
143
+        ((> x max) max)
144
+        (t x)))
145
+
146
+(defvar *samples-per-pixel* 1)
117 147
 (defun format-color (s v _ __)
118 148
   #.(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)))))
149
+  (let ((scale (/ *samples-per-pixel*)))
150
+    (flet ((scale-to-depth (c)
151
+             (floor
152
+              (* *color-depth*
153
+                 (clamp (sqrt (* c scale))
154
+                        0.0d0 0.999d0)))))
155
+      (fwoar.lisputils:vector-destructuring-bind (r g b) v
156
+        (let ((r (scale-to-depth r))
157
+              (g (scale-to-depth g))
158
+              (b (scale-to-depth b)))
159
+          (format s "~4d ~4d ~4d" r g b))))))
124 160
 
125 161
 (defun write-colors (stream colors columns)
126 162
   (let ((intermediate ())
... ...
@@ -149,6 +185,7 @@
149 185
 (fw.lu:defclass+ hit-record ()
150 186
   ((p :initarg :p :reader .p)
151 187
    (time :initarg :time :reader .time)
188
+   (thing :initarg :thing :accessor .thing)
152 189
    (normal :initarg :normal :accessor .normal :initform ())
153 190
    (front-face :initarg :front-face :accessor .front-face :initform ())))
154 191
 (defun set-face-normal (hit-record r outward-normal)
... ...
@@ -165,7 +202,13 @@
165 202
   ())
166 203
 (fw.lu:defclass+ sphere (hittable)
167 204
   ((center :initarg :center)
168
-   (radius :initarg :radius)))
205
+   (radius :initarg :radius)
206
+   (material-color :initarg :color
207
+                   :reader material-color
208
+                   :initform (vec3 (random 1.0d0)
209
+                                   (random 1.0d0)
210
+                                   (random 1.0d0)))))
211
+
169 212
 (defgeneric hit (thing ray t-min t-max)
170 213
   (:method ((things list) (r ray) (t-min real) (t-max real))
171 214
     (let (temp-rec
... ...
@@ -211,7 +254,7 @@
211 254
                                                     radius)))
212 255
 
213 256
                          (values t
214
-                                 (set-face-normal (hit-record p root)
257
+                                 (set-face-normal (hit-record p root sphere)
215 258
                                                   r
216 259
                                                   outward-normal)))))))))
217 260
 
... ...
@@ -229,15 +272,27 @@
229 272
           (/ (- (- half-b) (sqrt discriminant))
230 273
              a)))))
231 274
 
232
-(defgeneric ray-color (ray world)
233
-  (:method ((ray ray) world)
275
+(defgeneric ray-color (ray world depth)
276
+  (:method :around (r w (depth integer))
277
+    (if (<= depth 0)
278
+        (vec3 1.0d0 1.0d0 1.0d0)
279
+        (call-next-method)))
280
+  (:method ((ray ray) world (depth integer))
234 281
     (multiple-value-bind (hit-p rec)
235
-        (hit world ray 0 infinity)
282
+        (hit world ray 0.001d0 infinity)
236 283
       (when hit-p
237
-        (return-from ray-color
238
-          (vec* 0.5
239
-                (vec+ #(1 1 1)
240
-                      (.normal rec)))))
284
+        (let ((target (vec+ (vec+ (.p rec)
285
+                                  (.normal rec))
286
+                            (random-in-unit-sphere))))
287
+          (return-from ray-color
288
+            #+(or)
289
+            (vec* (material-color (.thing rec)))
290
+            (vec* 0.75
291
+                  (ray-color (ray (.p rec)
292
+                                  (vec- target
293
+                                        (.p rec)))
294
+                             world
295
+                             (1- depth))))))
241 296
       (with-slots (direction) ray
242 297
         (let* ((unit-direction (unit-vector direction))
243 298
                (it (+ (* 0.5 (v3-y unit-direction))
... ...
@@ -247,24 +302,60 @@
247 302
                 (vec* it
248 303
                       #(0.5d0 0.7d0 1.0d0))))))))
249 304
 
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 305
 
257
-         (viewport-height 2.0d0)
258
-         (viewport-width (* aspect-ratio viewport-height))
259
-         (focal-length 1.0d0)
306
+(defclass camera ()
307
+  ((origin :initarg :origin :reader origin)
308
+   (lower-left-corner :initarg :lower-left-corner :reader lower-left-corner)
309
+   (horizontal :initarg :horizontal :reader horizontal)
310
+   (vertical :initarg :vertical :reader vertical)))
311
+
312
+(defun camera (&key
313
+                 (aspect-ratio 16/9)
314
+                 (viewport-height 4.0d0)
315
+                 (viewport-width (* aspect-ratio viewport-height))
316
+                 (focal-length 0.8d0))
317
+  (let ((origin (vec3 0d0 0.0d0 0.0d0))
318
+        (horizontal (vec3 viewport-width 0.0d0 0.0d0))
319
+        (vertical (vec3 0.0d0 viewport-height 0.0d0)))
320
+    (make-instance 'camera
321
+                   :origin origin
322
+                   :horizontal horizontal
323
+                   :vertical vertical
324
+                   :lower-left-corner (vec- (vec- (vec- origin
325
+                                                        (vec/ horizontal 2))
326
+                                                  (vec/ vertical 2))
327
+                                            (vec3 0 0 focal-length)))))
328
+(defgeneric get-ray (camera u v)
329
+  (:method ((camera camera) (u real) (v real))
330
+    (with-slots (origin horizontal vertical lower-left-corner) camera
331
+      (macrolet ((-> (v &body forms)
332
+                   (if forms
333
+                       `(-> (,(caar forms) ,v ,@(cdar forms))
334
+                            ,@(cdr forms))
335
+                       v)))
336
+
337
+
338
+        (ray origin
339
+             (-> lower-left-corner
340
+                 (vec+ (vec* u horizontal))
341
+                 (vec+ (vec* v vertical))
342
+                 (vec- origin)))))))
343
+
344
+(defun raytrace (out &optional (*samples-per-pixel* 10) (image-width 400) (max-depth 50))
345
+  (let* ((world (append (list (sphere #(0 0 -1) 0.5
346
+                                      #(0.5 0.5 0.0)))
347
+                        #+(or)
348
+                        (loop repeat 10
349
+                              collect (sphere (vector (rand-min-max -1 1)
350
+                                                      (rand-min-max -1 1)
351
+                                                      (rand-min-max -1.0d0 -.5d0))
352
+                                              (rand-min-max 0.2 0.5)))
353
+                        (list (sphere #(0 -100.5 -1) 100
354
+                                      #(0.5 0.5 0.5)))))
355
+         (aspect-ratio 4/3)
356
+         (image-height (* (floor (/ image-width aspect-ratio))))
260 357
 
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))))
358
+         (camera (camera)))
268 359
     (alexandria:with-output-to-file (s out :if-exists :supersede)
269 360
       (call-with-ppm-header s (make-size :width image-width :height image-height)
270 361
                             (lambda (s)
... ...
@@ -275,20 +366,28 @@
275 366
                                                                "~&Scanlines remaining: ~d ~s~%"
276 367
                                                                j
277 368
                                                                (local-time:now))
369
+                                                       (force-output s)
278 370
                                                     do
279 371
                                                        (loop for i from 0 below image-width
280 372
                                                              for u = (/ (* 1.0d0 i)
281 373
                                                                         (1- image-width))
282 374
                                                              for v = (/ (* 1.0d0 j)
283 375
                                                                         (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)
376
+                                                             for r = (get-ray camera u v)
377
+                                                             for color = (loop for s below *samples-per-pixel*
378
+                                                                               for u = (/ i
379
+                                                                                          (1- image-width))
380
+                                                                                 then (/ (+ i (random 1.0d0))
381
+                                                                                         (1- image-width))
382
+                                                                               for v = (/ j
383
+                                                                                          (1- image-height))
384
+                                                                                 then (/ (+ j (random 1.0d0))
385
+                                                                                         (1- image-height))
386
+                                                                               for r = (get-ray camera u v)
387
+                                                                               for pixel-color = (ray-color r world max-depth)
388
+                                                                                 then (vec+ pixel-color
389
+                                                                                            (ray-color r world max-depth))
390
+                                                                               finally (return pixel-color))
292 391
                                                              collect
293 392
                                                              (funcall c color))))
294 393
                                             image-width))