git.fiddlerwoaroof.com
Browse code

feat: fixup diffuse material, add metalic and transparent materials

Edward authored on 23/03/2021 05:13:41
Showing 2 changed files
... ...
@@ -35,7 +35,7 @@
35 35
         (rand-min-max min max)
36 36
         (rand-min-max min max)))
37 37
 
38
-(declaim (inline vec+ vec* vec- vec/ random-in-unit-sphere))
38
+(declaim (inline vec+ vec* vec- vec/ random-in-unit-sphere negate))
39 39
 (defun random-in-unit-sphere ()
40 40
   (loop for p = (rand-vec3-min-max -1.0d0 1.0d0)
41 41
         while (>= (length-squared p)
... ...
@@ -97,6 +97,12 @@
97 97
               (vec3 (* vec1 a1)
98 98
                     (* vec1 b1)
99 99
                     (* vec1 c1))))))
100
+(defun negate (vec)
101
+  (fw.lu:vector-destructuring-bind (x y z) vec
102
+    (vec3 (- x)
103
+          (- y)
104
+          (- z))))
105
+
100 106
 (defun vec/ (vec it)
101 107
   (declare (optimize (speed 3)))
102 108
   (vec* (/ 1.0 it)
... ...
@@ -128,6 +134,14 @@
128 134
   (vec/ v
129 135
         (vec-length v)))
130 136
 
137
+(declaim (inline near-zero))
138
+(defun near-zero (vec)
139
+  (fw.lu:vector-destructuring-bind (x y z) vec
140
+    (let* ((s 1.0d-8))
141
+      (and (< (abs x) s)
142
+           (< (abs y) s)
143
+           (< (abs z) s)))))
144
+
131 145
 (defun call-with-ppm-header (stream size callback &optional (colors 255))
132 146
   (format stream "P3~%~d ~d~%~d~%"
133 147
           (size-width size)
... ...
@@ -220,7 +234,9 @@
220 234
                    :initform (vec3 (random 1.0d0)
221 235
                                    (random 1.0d0)
222 236
                                    (random 1.0d0)))
223
-   (material :initarg :material :reader .material :initform (lambertian-material (random 0.8)))))
237
+   (material :initarg :material
238
+             :reader .material
239
+             :initform (lambertian-material (random 0.8)))))
224 240
 
225 241
 (defgeneric hit (thing ray t-min t-max)
226 242
   (:method ((things list) (r ray) (t-min real) (t-max real))
... ...
@@ -288,23 +304,113 @@
288 304
 
289 305
 (defgeneric scatter (material ray-in rec))
290 306
 
291
-(defun original-material (rec world depth)
307
+(defun original-material (rec ray world depth)
308
+  (declare (ignore ray world depth))
292 309
   (vec* 0.5
293 310
         (vec+ #(1 1 1)
294 311
               (.normal rec))))
295 312
 
296 313
 (defun lambertian-material (albedo)
297
-  (lambda (rec world depth)
298
-    (let ((target (vec+ (vec+ (.p rec)
299
-                              (.normal rec))
300
-                        (random-unit-vector))))
301
-      (vec* (material-color (.thing rec))
302
-            (vec* albedo
303
-                  (ray-color (ray (.p rec)
304
-                                  (vec- target
305
-                                        (.p rec)))
306
-                             world
307
-                             (1- depth)))))))
314
+  (lambda (rec ray world depth)
315
+    (declare (ignore ray))
316
+    (let ((scatter-direction (vec+ (.normal rec)
317
+                                   (random-unit-vector))))
318
+      (when (near-zero scatter-direction)
319
+        (setf scatter-direction (.normal rec)))
320
+
321
+      (vec* albedo
322
+            (ray-color (ray (.p rec)
323
+                            scatter-direction)
324
+                       world
325
+                       (1- depth))))))
326
+
327
+(defun reflect (v n)
328
+  (vec- v
329
+        (vec* 2.0d0
330
+              (vec* (dot v n)
331
+                    n))))
332
+
333
+(defun refract (uv n eta*/eta)
334
+  (let* ((cos-theta (min 1.0d0
335
+                         (dot (negate uv)
336
+                              n)))
337
+         (out-perp (vec* eta*/eta
338
+                         (vec+ uv
339
+                               (vec* cos-theta
340
+                                     n))))
341
+         (out-parallel (vec* (- (sqrt (abs (- 1.0d0
342
+                                              (length-squared out-perp)))))
343
+                             n)))
344
+    (vec+ out-perp out-parallel)))
345
+
346
+(defun metal-material (albedo)
347
+  (lambda (rec ray world depth)
348
+    (with-slots (direction) ray
349
+      (let* ((reflected (reflect (unit-vector direction)
350
+                                 (.normal rec)))
351
+             (scattered (ray (.p rec) reflected)))
352
+        (vec* albedo
353
+              (ray-color scattered
354
+                         world
355
+                         (1- depth)))))))
356
+
357
+(defun fuzzy-metal-material (albedo fuzz)
358
+  (lambda (rec ray world depth)
359
+    (with-slots (direction) ray
360
+      (let* ((reflected (reflect (unit-vector direction)
361
+                                 (.normal rec)))
362
+             (scattered (ray (.p rec)
363
+                             (vec+ reflected
364
+                                   (vec* fuzz
365
+                                         (random-in-unit-sphere))))))
366
+        (vec* albedo
367
+              (ray-color scattered
368
+                         world
369
+                         (1- depth)))))))
370
+
371
+(defun reflectance (cosine ref-idx)
372
+  (let* ((r0 (/ (- 1 ref-idx)
373
+                (+ 1 ref-idx)))
374
+         (r0 (* r0 r0)))
375
+    (+ r0
376
+       (* (- 1 r0)
377
+          (expt (- 1 cosine)
378
+                5)))))
379
+
380
+(defun dielectric-material (ir &optional (fuzz 0))
381
+  (lambda (rec ray world depth)
382
+    (with-slots (direction) ray
383
+      (let* ((attenuation (vec3 1.0d0 1.0d0 1.0d0))
384
+             (refraction-ratio (if (.front-face rec)
385
+                                   (/ 1.0d0 ir)
386
+                                   ir))
387
+             (unit-direction (unit-vector direction))
388
+             (normal (.normal rec))
389
+             (cos-theta (min 1.0d0
390
+                             (dot (negate unit-direction)
391
+                                  normal)))
392
+             (sin-theta (sqrt (- 1
393
+                                 (* cos-theta cos-theta))))
394
+             (cannot-refract (> (* refraction-ratio
395
+                                   sin-theta)
396
+                                1.0d0))
397
+             (direction (if (or cannot-refract
398
+                                (> (reflectance cos-theta
399
+                                                refraction-ratio)
400
+                                   (random 1.0d0)))
401
+                            (reflect unit-direction
402
+                                     normal)
403
+                            (refract unit-direction
404
+                                     normal
405
+                                     refraction-ratio)))
406
+             (scattered (ray (.p rec)
407
+                             (vec+ direction
408
+                                   (vec* fuzz
409
+                                         (random-in-unit-sphere))))))
410
+        (vec* attenuation
411
+              (ray-color scattered
412
+                         world
413
+                         (1- depth)))))))
308 414
 
309 415
 (defgeneric ray-color (ray world depth)
310 416
   (:method :around (r w (depth integer))
... ...
@@ -316,7 +422,7 @@
316 422
         (hit world ray 0.001d0 infinity)
317 423
       (when hit-p
318 424
         (return-from ray-color
319
-          (funcall (.material rec) rec world depth)))
425
+          (funcall (.material rec) rec ray world depth)))
320 426
       (with-slots (direction) ray
321 427
         (let* ((unit-direction (unit-vector direction))
322 428
                (it (+ (* 0.5 (v3-y unit-direction))
... ...
@@ -335,9 +441,9 @@
335 441
 
336 442
 (defun camera (&key
337 443
                  (aspect-ratio 16/9)
338
-                 (viewport-height 4.0d0)
444
+                 (viewport-height 2.0d0)
339 445
                  (viewport-width (* aspect-ratio viewport-height))
340
-                 (focal-length 0.8d0))
446
+                 (focal-length 1.0d0))
341 447
   (let ((origin (vec3 0d0 0.0d0 0.0d0))
342 448
         (horizontal (vec3 viewport-width 0.0d0 0.0d0))
343 449
         (vertical (vec3 0.0d0 viewport-height 0.0d0)))
... ...
@@ -406,8 +512,8 @@
406 512
            (u-loop j image-width image-height camera world max-depth c))))
407 513
      :name (format nil "worker-~d" id))))
408 514
 
409
-(defun start-workers ()
410
-  (loop for x below 8
515
+(defun start-workers (&optional (n 8))
516
+  (loop for x below n
411 517
         collect (start-worker x)))
412 518
 
413 519
 (defun shuffle (seq)
... ...
@@ -419,27 +525,169 @@
419 525
           finally (return (coerce arr (type-of seq))))))
420 526
 
421 527
 (defun raytrace (out &optional (*samples-per-pixel* 10) (image-width 400) (max-depth 50))
422
-  (let* ((world (append (list (sphere #(0 0 -1) 0.5
528
+  (let* ((world (append (list (sphere #(0 0 -1) .5
423 529
                                       #(0.5 0.5 0.0)
424
-                                      #'original-material))
530
+                                      (dielectric-material 0.4)
531
+                                      #+(or)
532
+                                      (lambda (rec ray world depth)
533
+                                        (declare (ignore rec world depth))
534
+                                        (with-slots (direction) ray
535
+                                          (fw.lu:vector-destructuring-bind
536
+                                              (x y z) (unit-vector direction)
537
+                                            (vector (abs x)
538
+                                                    (abs y)
539
+                                                    (abs z)))))))
425 540
                         (loop repeat 30
426
-                              collect (sphere (vector (rand-min-max -1.5 1.5)
427
-                                                      (rand-min-max -1.5 1.5)
428
-                                                      (rand-min-max -1.5d0 -1.0d0))
541
+                              collect (sphere (vector (rand-min-max -3 3)
542
+                                                      (rand-min-max -0.5 2)
543
+                                                      (rand-min-max -1.5d0 -0.5d0))
429 544
                                               (rand-min-max 0.2 0.5)
430 545
                                               (vector (random 1.0d0)
431 546
                                                       (random 1.0d0)
432 547
                                                       (random 1.0d0))
433
-                                              (if (= 1 (random 2))
434
-                                                  (lambertian-material
435
-                                                   (rand-min-max 0.25 0.75))
436
-                                                  #'original-material)))
548
+                                              (case (random 5)
549
+                                                (0 (lambertian-material
550
+                                                    (rand-min-max 0.25 0.75)))
551
+                                                (1 (metal-material
552
+                                                    (rand-min-max 0.25 0.75)))
553
+                                                (2 (fuzzy-metal-material
554
+                                                    (rand-min-max 0.25 0.75)
555
+                                                    (rand-min-max 0.01 0.75)))
556
+                                                (3 (dielectric-material
557
+                                                    (rand-min-max 0.1 2.4)))
558
+                                                (4 #'original-material)
559
+                                                (5
560
+                                                 (lambda (rec ray world depth)
561
+                                                   (declare (ignore rec world depth))
562
+                                                   (vector 1d0 1d0 1d0)
563
+                                                   #+(or)
564
+                                                   (with-slots (direction) ray
565
+                                                     (fw.lu:vector-destructuring-bind
566
+                                                         (x y z) (unit-vector direction)
567
+                                                       (vector (abs x)
568
+                                                               (abs y)
569
+                                                               (abs z)))))))))
570
+                        ;; #+(or)
437 571
                         (list (sphere #(0 -100.5 -1) 100
438 572
                                       #(0.5 0.5 0.5)
439 573
                                       (lambertian-material 0.2)))))
440 574
          (aspect-ratio 4/3)
441 575
          (image-height (* (floor (/ image-width aspect-ratio))))
442 576
 
577
+         (camera (camera :focal-length 1.0d0)))
578
+    (let ((mailbox (sb-concurrency:make-mailbox)))
579
+      (loop for j in (shuffle (loop for x from 0 to image-height collect x))
580
+            do
581
+               (sb-concurrency:send-message
582
+                (aref *thread-queues*
583
+                      (mod j
584
+                           (length *thread-queues*)))
585
+                (list *samples-per-pixel*
586
+                      j image-width
587
+                      image-height camera
588
+                      world max-depth
589
+                      (lambda (a b)
590
+                        (sb-concurrency:send-message
591
+                         mailbox
592
+                         (list a b))))))
593
+      (write-colors nil
594
+                    (lambda (c)
595
+                      (loop with messages = 0
596
+                            for it = (sb-concurrency:receive-message mailbox :timeout 2)
597
+                            while it
598
+                            do (destructuring-bind (color pos) it
599
+                                 (funcall c color pos))))
600
+                    image-width))))
601
+
602
+(defun refraction-scene
603
+    (out &optional (*samples-per-pixel* 10) (image-width 400) (max-depth 50))
604
+  (let* ((world (list (sphere #(0 0 -1) .5
605
+                              #(0.5 0.5 0.0)
606
+                              (lambertian-material #(0.7 0.3 0.3)))
607
+                      (sphere #(-1 0 -1) 0.5
608
+                              #(0 0 0)
609
+                              (dielectric-material 1.5))
610
+                      (sphere #(-1 0 -1) -0.4
611
+                              #(0 0 0)
612
+                              (dielectric-material 1.5))
613
+                      (sphere #(-1 0 -1) -0.2
614
+                              #(0 0 0)
615
+                              (lambertian-material
616
+                               #(0.8 0.6 0.2)))
617
+                      #+(or)
618
+                      (sphere #(1 0 -1) 0.5
619
+                              #(0 0 0)
620
+                              (dielectric-material 2.4))
621
+                      #+(or)
622
+                      (sphere #(-2 0 -1) 0.5
623
+                              #(0 0 0)
624
+                              (metal-material #(0.8 0.8 0.8)))
625
+                      (sphere #(1 0 -1) 0.5
626
+                              #(0 0 0)
627
+                              (metal-material #(0.8 0.6 0.2)))
628
+                      (sphere #(0 -100.5 -1) 100
629
+                              #(0.5 0.5 0.5)
630
+                              (lambertian-material #(0.8 0.8 0.0)))))
631
+         (aspect-ratio 16/9)
632
+         (image-height (* (floor (/ image-width aspect-ratio))))
633
+
634
+         (camera (camera)))
635
+    (let ((mailbox (sb-concurrency:make-mailbox)))
636
+      (loop for j in (shuffle (loop for x from 0 to image-height collect x))
637
+            do
638
+               (sb-concurrency:send-message
639
+                (aref *thread-queues*
640
+                      (mod j
641
+                           (length *thread-queues*)))
642
+                (list *samples-per-pixel*
643
+                      j image-width
644
+                      image-height camera
645
+                      world max-depth
646
+                      (lambda (a b)
647
+                        (sb-concurrency:send-message
648
+                         mailbox
649
+                         (list a b))))))
650
+      (write-colors nil
651
+                    (lambda (c)
652
+                      (loop with messages = 0
653
+                            for it = (sb-concurrency:receive-message mailbox :timeout 2)
654
+                            while it
655
+                            do (destructuring-bind (color pos) it
656
+                                 (funcall c color pos))))
657
+                    image-width))))
658
+
659
+(defun refraction-scene1
660
+    (out &optional (*samples-per-pixel* 10) (image-width 400) (max-depth 50))
661
+  (let* ((world (list (sphere #(0 0 -1) 1
662
+                              #(0.5 0.5 0.0)
663
+                              (dielectric-material 2.4))
664
+                      (sphere #(-0.5 0 -1) 0.5
665
+                              #(0 0 0)
666
+                              (dielectric-material 1.5 0.2))
667
+                      (sphere #(-0.5 0 -1) -0.4
668
+                              #(0 0 0)
669
+                              (dielectric-material 1.5 0.2))
670
+                      (sphere #(-0.5 0 -1) -0.2
671
+                              #(0 0 0)
672
+                              (metal-material
673
+                               #(0.8 0.6 0.2)))
674
+                      #+(or)
675
+                      (sphere #(1 0 -1) 0.5
676
+                              #(0 0 0)
677
+                              (dielectric-material 2.4))
678
+                      #+(or)
679
+                      (sphere #(-2 0 -1) 0.5
680
+                              #(0 0 0)
681
+                              (metal-material #(0.8 0.8 0.8)))
682
+                      (sphere #(0.5 0 -1) 0.5
683
+                              #(0 0 0)
684
+                              (metal-material #(0.8 0.6 0.2)))
685
+                      (sphere #(0 -100.5 -1) 100
686
+                              #(0.5 0.5 0.5)
687
+                              (lambertian-material #(0.8 0.8 0.0)))))
688
+         (aspect-ratio 16/9)
689
+         (image-height (* (floor (/ image-width aspect-ratio))))
690
+
443 691
          (camera (camera)))
444 692
     (let ((mailbox (sb-concurrency:make-mailbox)))
445 693
       (loop for j in (shuffle (loop for x from 0 to image-height collect x))
... ...
@@ -35,7 +35,7 @@
35 35
                   (spinneret:with-html-string
36 36
                     (:html
37 37
                      (:body
38
-                      (:canvas#out :width 1000 :height 1000)
38
+                      (:canvas#out :width 2000 :height 2000)
39 39
                       (:script
40 40
                        (ps:ps
41 41
                          (let* ((canvas (ps:chain document