Skip to content

Commit 4196f17

Browse files
committed
Initial commit.
0 parents  commit 4196f17

28 files changed

+930292
-0
lines changed

1-ray-math.lisp

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2+
;; Math utils for ray tracer ;;
3+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
4+
5+
(defun sq (x) (* x x))
6+
7+
(defun mag (x y z) ; [1]
8+
(sqrt (+ (sq x) (sq y) (sq z))))
9+
10+
(defun unit-vector (x y z) ; [2]
11+
(let ((d (mag x y z)))
12+
(values (/ x d) (/ y d) (/ z d))))
13+
14+
(defstruct (point (:conc-name nil)) ; [3]
15+
x y z)
16+
17+
(defun distance (p1 p2) ; [4]
18+
(mag (- (x p1) (x p2))
19+
(- (y p1) (y p2))
20+
(- (z p1) (z p2))))
21+
22+
(defun minroot (a b c) ; [5]
23+
(if (zerop a)
24+
(/ (- c) b)
25+
(let ((disc (- (sq b) (* 4 a c))))
26+
(unless (minusp disc)
27+
(let ((discrt (sqrt disc)))
28+
(min (/ (+ (- b) discrt) (* 2 a))
29+
(/ (- (- b) discrt) (* 2 a))))))))
30+
31+
;; [1] Magnitude of a vector is the square root of the sum of its squared components.
32+
;; [2] To get the unit vector one divides each component by the magnitide.
33+
;; This returns three separate values, not a compound datastructure of any kind.
34+
;; usage e.g. (multiple-value-call #'mag (unit-vector 23 12 47)) => 1.0
35+
;; [3] :conc-name of nil gives access functions of merely (x p) (y p) (z p),
36+
;; not point-x, point-y & point-z, i.e. 'point-' being the default.
37+
;; super neat, almost dangerously so, but we have that flexibility.
38+
;; [4] To get the (Euclidean) distance we subtract one point from another
39+
;; component-wise, then take the magnitude.
40+
;; (setf p1 (make-point :x 1 :y 2 :z 3))
41+
;; (setf p2 (make-point :x -1 :y 7 :z 5))
42+
;; (distance p1 p2) => 5.7445626
43+
;; (distance p2 p1) => 5.7445626
44+
;; [5] Find the roots then return the min. Familiar eqn (-b +/- sqrt(b^2 - 4ac)) / 2a
45+
;; Here we're only interested in the min of the two.
46+
;; Why will be explained later when we explain the tracer.
47+
;; The zerop case is for when not a quadratic, i.e. y = bx+c.

2-ray-tracer.lisp

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
;;;;;;;;;;;;;;;;;;
2+
;; Ray Tracer ;;
3+
;;;;;;;;;;;;;;;;;;
4+
5+
(defstruct surface color) ; [1]
6+
7+
(defparameter *world* nil) ; [2]
8+
(defconstant eye (make-point :x 0 :y 0 :z 200)) ; [3] <- to move eye,
9+
; change this.
10+
(defun tracer (pathname &optional (res 1)) ; [4]
11+
(with-open-file (p pathname :direction :output)
12+
(format p "P2 ~A ~A 255" (* res 100) (* res 100)) ; [5]
13+
(let ((inc (/ res))) ; [6]
14+
(do ((y -50 (+ y inc))) ; [7]
15+
((< (- 50 y) inc)) ; [8]
16+
(do ((x -50 (+ x inc))) ; [9]
17+
((< (- 50 x) inc))
18+
(print (color-at x y) p)))))) ; <- all we need to do is print the pixel color.
19+
20+
(defun color-at (x y) ; [10]
21+
(multiple-value-bind (xr yr zr)
22+
(unit-vector (- x (x eye)) ; <- dir. from eye to pixel. we'll trace
23+
(- y (y eye)) ; back from eye to source, through pixel.
24+
(- 0 (z eye)))
25+
(round (* (sendray eye xr yr zr) 255)))) ; [11]
26+
27+
(defun sendray (pt xr yr zr) ; [12]
28+
(multiple-value-bind (s hit-pt) (first-hit pt xr yr zr)
29+
(if s ; [13]
30+
(multiple-value-bind (xs ys zs) (unit-vector 0 0 -200) ; [14] <- to place light source
31+
(* (lambert s hit-pt xr yr zr) (surface-color s))) ; [15] elsewhere than at eye
32+
0))) ; [16] (but see note 14).
33+
34+
(defun first-hit (pt xr yr zr) ; [17]
35+
(let (surface hit-pt dist) ; <- collect these vals for closest point ; [18]
36+
(dolist (s *world*) ; [19]
37+
(let ((h (intersect s pt xr yr zr))) ; [20]
38+
(when h ; <- current s was hit by the ray
39+
(let ((d (distance h pt))) ; <- distance from hit-pt to eye
40+
(when (or (null dist) (< d dist)) ; <- if closest so far
41+
(setf surface s hit-pt h dist d)))))) ; <- update all hit pt vars
42+
(values surface hit-pt))) ; [21]
43+
44+
(defun lambert (s int xs ys zs) ; [22]
45+
(multiple-value-bind (xn yn zn) (normal s int)
46+
(max 0 (+ (* xs xn) (* ys yn) (* zs zn)))))
47+
48+
;; A Ray Tracer is the rendering algorithm deluxe; yields the best images, but takes the most time.
49+
;; Benefits: easy to get real optical effects, defined using geometric objects, easy to implement.
50+
51+
;; This Ray Tracer
52+
;; - black and white images
53+
;; - single light source at same position as eye
54+
;; - hence look like flash photographs
55+
;; Hence, Optional Improvements
56+
;; - move the light source [x]
57+
;; - add another light source [ ] (harder)
58+
59+
60+
;; [1] We'll program the tracer to this interface, then define specific surfaces later.
61+
;; All we need to establish that interface is surface-color, which here is a gray level.
62+
;; [2] We'll put surfaces in here and iterate through it to trace.
63+
;; [3] Eye is a point set back from the image plane; image plane is coincident with the x y plane.
64+
;; This means eye will only see objects which have negative z coordinates.
65+
;; Q. Why is an image plane alone not enough? Why do we need an eye if the eye is just a point?
66+
;; A. Find a window - on the glass surface is the image we'll construct. Walk toward then away
67+
;; away, and notice how the image changes. With eye closer to the image plane, more of the
68+
;; world is seen, which also means objects on the fixed size image (the glass) become smaller.
69+
;; If you move back, less of world is seen, & those objects which are seen become larger.
70+
;; Thus, a vantage point is necessary to allow us compute what to display. No vantage point, no image.
71+
;; An image is here constructed by considering all the rays of light passing from the world to
72+
;; the position of the eye, but is captured at the point those rays hit the image plane.
73+
;; Your eye itself is similar but has some differences. There, the vantage point, your retina, is fixed.
74+
;; Q. Must the eye always lie on the central axis?
75+
;; A. No. The plane is transparent. Movement of the eye in x y directions simply means you see
76+
;; different regions of the world, whereas z direction affects only image scale.
77+
;; Again, you can do this with a window, and think about the image on the glass pane.
78+
;; Note, the axes in the diagram in the book are wrong, need to reverse the directions,
79+
;; i.e. use negative values to get the directions indicated in the book on each axis.
80+
;; [4] Go pixel by pixel along the image plane, tracing the light back into the simulated world.
81+
;; Here we simply get the color of the pixel with color-at, and write that to a file.
82+
;; Hence, the entire rest of the program is in terms of 'getting the color of a pixel'.
83+
;; Calls color-at, which is all we need to find our way into the rest of the program.
84+
;; [5] Param res is image plane size in hundreds of pixels square.
85+
;; The filetype is a simple ascii format called pgm.
86+
;; The header is as per format here, P2 type, breadth, height & max int size per pixel.
87+
;; Hence, 100x100 will be 10k ints of max 256 (white).
88+
;; [6] (/ res) is same as (/ 1 res)
89+
;; [7] var init update; y from -50, incd by 1/res
90+
;; [8] test; at standard res, test passes until y reaches 50; then it fails, ending loop.
91+
;; [9] Same for each x within y; tracing an xy plane.
92+
93+
;; [10] To get a color at a point on the plane, we define a unit vector in the direction
94+
;; of that point on the plane from the eye, then send a ray in that direction.
95+
;; sendray will ultimately return a color. in ray tracing we trace back from the eye.
96+
;; multiple-value-bind binds the values returned from its first arg, for use later,
97+
;; like let; allows three values to be passed w/o any premature container.
98+
;; [11] sendray returns a value in (0,1), here we round it to 8 bit, 0-255.
99+
;; [12] Get the first hit surface or none, and the intersect point, and if a surface was hit,
100+
;; compute the amount of light to return & return it, else, return 0, for no light.
101+
;; Could call this from pixel position instead of eye position; should work same. try it.
102+
;; [13] Surface was hit on way back to light source.
103+
;; [14] If want to move light source to position other than at the eye, compute a unit vector
104+
;; as desired here and pass it to lambert. Otherwise, for light at the eye, pass xr yr zr.
105+
;; Vector must be from outside the plane, i.e. -ve z coord, or no reflection is possible
106+
;; (and here we only calculate reflection, not lightsource itself or transparency).
107+
;; *BUT*, while this roughly works, shouldn't lambert direction also depend on pixel?
108+
;; PROGRESS STEP: pencil & paper diagrams of what's happening.
109+
;; [15] Amount of light is lambert coefficient times surface's intrinsic color; reflectivity.
110+
;; [16] Background color, convention black.
111+
;; [17] Get the surface if any our ray tracing back to the source hits first, along with point if so.
112+
;; Just like sendray, this takes point and direction. No transparency, hence want only first hit.
113+
;; This is a brute method of course.
114+
;; [18] Initialise all three to nil. Dist to hold d during update, & be overwritten if smaller d found.
115+
;; [19] To find first-hit, we use a slow brute method for now.
116+
;; Consider all surfaces in world.
117+
;; [20] Intersect finds the solutions to the eqn. of that surface that also satisfy line of the ray.
118+
;; If ray goes in and out other side, there will be two roots. In that case, the min root is
119+
;; the nearest. Hence, intersect uses minroot. If another surface in world also intersects,
120+
;; it will only win if it's the nearer.
121+
;; [21] Return closest hit point of closest surface, or nil nil if no hits.
122+
;; [22] To get intensity of the ray, we use lamberts law. That means dot product
123+
;; of unit normal vector and the unit vector in direction of light source.
124+
;; Originaly program uses light source at eye, in which case use the ray itself.
125+
;; We also added an option to use a different ray, in [14] above.
126+
;; [23] Wonder if this file might be neater with docstrings than comments, in time.

3-ray-surfaces.lisp

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
;;;;;;;;;;;;;;;;;;;;
2+
;; Ray Surfaces ;;
3+
;;;;;;;;;;;;;;;;;;;;
4+
5+
(defstruct (sphere (:include surface)) ; [1]
6+
radius center)
7+
8+
(defun defsphere (x y z r c) ; [2]
9+
(let ((s (make-sphere
10+
:radius r
11+
:center (make-point :x x :y y :z z)
12+
:color c)))
13+
(push s *world*)
14+
s))
15+
16+
(defun intersect (s pt xr yr zr) ; [3]
17+
(funcall (typecase s (sphere #'sphere-intersect))
18+
;(typecase s (cube #'cube-intersect))
19+
s pt xr yr zr))
20+
21+
(defun sphere-intersect (s pt xr yr zr) ; [4]
22+
(let* ((c (sphere-center s))
23+
(n (minroot (+ (sq xr) (sq yr) (sq zr))
24+
(* 2 (+ (* (- (x pt) (x c)) xr)
25+
(* (- (y pt) (y c)) yr)
26+
(* (- (z pt) (z c)) zr)))
27+
(+ (sq (- (x pt) (x c)))
28+
(sq (- (y pt) (y c)))
29+
(sq (- (z pt) (z c)))
30+
(- (sq (sphere-radius s)))))))
31+
(if n
32+
(make-point :x (+ (x pt) (* n xr))
33+
:y (+ (y pt) (* n yr))
34+
:z (+ (z pt) (* n zr))))))
35+
36+
(defun normal (s pt) ; [5]
37+
(funcall (typecase s (sphere #'sphere-normal))
38+
s pt))
39+
40+
(defun sphere-normal (s pt) ; [6]
41+
(let ((c (sphere-center s)))
42+
(unit-vector (- (x c) (x pt))
43+
(- (y c) (y pt))
44+
(- (z c) (z pt)))))
45+
46+
;; [1] Include struct surface, which just adds the property color.
47+
;; [2] Add a sphere to the world.
48+
;; [3] Find the intersect to whatever the surface is.
49+
;; So long as we have only spheres, can just read this fn as sphere-intersect.
50+
;; It checks type & calls corresponding function.
51+
;; [4] See book.
52+
;; [5] Find the normal to whatever the surface is.
53+
;; [6] For sphere, this is unit vector from point to center.

4-ray-client.lisp

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
;;;;;;;;;;;;;;;;;;
2+
;; Ray Client ;;
3+
;;;;;;;;;;;;;;;;;;
4+
5+
(defun ray-test (&optional (res 1)) ; [1]
6+
(setf *world* nil)
7+
(defsphere 0 -300 -1200 200 .8)
8+
(defsphere -80 -150 -1200 200 .7)
9+
(defsphere 70 -100 -1200 200 .9)
10+
(do ((x -2 (+ x 1))) ; [2]
11+
((> x 2))
12+
(do ((z 2 (+ z 1)))
13+
((> z 7))
14+
(defsphere (* x 200) 300 (* z -400) 40 .75))) ; [3]
15+
(tracer (make-pathname :name "spheres-shifted.pgm") res))
16+
17+
18+
;; [1] res is hundreds of pixels square, e.g. 1 = 100x100, 2 = 200x200
19+
;; [2] This sets up a grid of small spheres
20+
;; Recall syntax of do is var init update, test result.
21+
;; Hence, first do runs from -2 to 2, second from 2 to 7.
22+
;; [3] All small spheres in this grid are of same size.
23+
;; They all have the same y coord, so are spread on the xz plane.
24+
25+
; Notes
26+
; - lightsource in orig. code is always at point of eye
27+
; we modded that though to allow it to be moved.
28+
; - "Not even optimised as a lisp program, let alone as a ray tracer"
29+
30+
; - FIXME: "merely adding type & inline declarations, s13.3, will make this program
31+
; more than twice as fast"
32+
33+
; - Ray tracing itself is amenable to parallelisation, tracing each ray is
34+
; computationally distinct.
35+
; - Focus to start is on simplicity of program and understanding the geometry.
36+
37+
; Discussion
38+
; this program has enabled us to understand more about optics
39+
; because to get a believable image, we need to get the physics working right
40+
; that's a pretty amazing thing to have at your fingertips.
41+
; e.g. we need to simulate reflection correctly, a convincing image
42+
; means we may have modelled it correctly. also, visual result lets us see,
43+
; to some extent, that our algorithm is correct.
44+
45+
; "To solve a problem, describe it, specify it in algorithmic terms, implement it,
46+
; test it, debug and analyze it. Expect this to be an iterative process. [p. 110]"
47+
; "AI programming is largely exploratory programming; the aim is often to discover
48+
; more about the problem area." < Norvig.
49+
50+
; Observations
51+
; Welcome to world of lisp
52+
; Programs are compact; hard part is the math;
53+
; but given that the math is hard, lisp expresses it pretty nicely.
54+
; program is not big, fewer LOC than in other langs.

0 commit comments

Comments
 (0)