Browse code
feat: antialiasing, diffuse materials, etc.
Edward authored on 21/03/2021 01:48:15
Showing 1 changed files
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)) |