-
Notifications
You must be signed in to change notification settings - Fork 0
/
raytracer.html
292 lines (222 loc) · 8.27 KB
/
raytracer.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
<html>
<head>
<script src = 'http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js'></script>
</head>
<body>
<h1>Tracing Rays</h1>
<p>I like coding. In fact I like it so much that every now and then I make something completely pointless and yet quite cool. That way it feels more like art and less like business.</p>
<p>So without further ado:</p>
<h2>Javascript Raytracer</h2>
<canvas id = "rays" width = "150" height = "150">
This only works in a modern browser that supports the <canvas> element. For example Firefox 3.5+, Safari or Chrome.
</canvas>
<button id = "start_raytracer">Start</button>
<p><em>The raytracer is pretty processor intensive - I recommend you run it in Google Chrome which has the fastest javascript engine I've found. Also tested in Firefox/Safari. Will certainly not work in IE.</em></p>
<script type = "text/javascript">
var rays = {};
/** Maths **/
rays.vectorAdd = function(p1, p2){
return [p1[0] + p2[0], p1[1]+ p2[1], p1[2] + p2[2]];
}
rays.vectorSubtract = function(p1, p2){
return [p1[0] - p2[0], p1[1] - p2[1], p1[2] - p2[2]];
}
rays.vectorMultiply = function(p1, p2){
return [p1[0] * p2[0], p1[1] * p2[1], p1[2] * p2[2]];
}
rays.vectorScale = function(p1, scale){
return [p1[0] * scale, p1[1] * scale, p1[2] * scale];
}
rays.vectorDot = function(r1, r2){
return r1[0] * r2[0] + r1[1] * r2[1] + r1[2] * r2[2]
}
rays.vectorLength = function(ray){
return Math.sqrt(ray[0]*ray[0]+ ray[1]*ray[1] + ray[2]*ray[2]);
}
rays.vectorNormalise = function(r){
var len = rays.vectorLength(r);
return [r[0]/len, r[1]/len, r[2]/len]
}
/** Primitives **/
rays.Sphere = function(pos, radius, color, phong, reflection){
this.pos = pos;
this.radius = radius;
this.color = function(pt){return color};
this.phong = phong;
this.reflection = reflection || 0;
this.intersects = function(ro, rd){
var rdn = rays.vectorNormalise(rd);
var dst = rays.vectorSubtract(ro, this.pos);
var b = rays.vectorDot(dst, rdn);
var c = rays.vectorDot(dst, dst)- this.radius*this.radius;
var d = b*b-c;
if (d){
return -b - Math.sqrt(d)}
return false;
}
this.normal = function(pt){
return rays.vectorNormalise(rays.vectorSubtract(pt, this.pos));
}
};
rays.CheckerYPlane = function(y, col1, col2){
this.y = y;
this.col1 = col1;
this.col2 = col2;
this.phong = 0;
this.reflection = 0.1;
this.intersects = function(ro, rd){
var rdn = rays.vectorNormalise(rd);
return rays.vectorDot([0,1,0], rays.vectorSubtract([0,this.y,0], ro))/ rays.vectorDot([0,1,0], rdn);
}
this.normal = function(pt){
return [0,1,0];
}
this.color = function(pt){
var zig = pt[0] > 0 ? parseInt(Math.abs(pt[0])/50) % 2 == 0 : parseInt(Math.abs(pt[0])/50) % 2 != 0
var zag = pt[2] > 0 ? parseInt(Math.abs(pt[2])/50) % 2 == 0 : parseInt(Math.abs(pt[2])/50) % 2 != 0
if(!zig != !zag)// zig XOR zag
return this.col1;
return this.col2;
}
}
/** Scene Params **/
rays.eye = [0,0,0];
rays.cam_dist = 200; /* Cam plane is x,y,cam_dist */
rays.ambient = 0.1;
rays.max_depth = 1;
rays.scene = {}
rays.scene.objects = [
new rays.Sphere(pos = [300,0,900], radius = 200, color = [0xff,0,0], phong = 20, reflection = 0.2),
new rays.Sphere(pos = [0,100,1200], radius = 300, color = [0,0,0xff], phong = 10, reflection = 0.6),
new rays.CheckerYPlane(-200, [0x33,0x33,0x33], [0xdd,0xdd,0xdd])
];
rays.scene.lights = [
/* [[x,y,z], intensity(0-1), color] */
[[-300,300,0], 0.9, [0xff, 0xff, 0xff]]
]
/**
* Determine whether the ray from ro->rd intersects an object and if so return the nearest distance and object
*/
rays.intersection = function(ro, rd, max, min){
var min = min || 0.0;
var nearest = null;
var nearest_dist = max;
$.each(rays.scene.objects, function(){
var d = this.intersects(ro, rd);
if(d && d < nearest_dist && d > min){
nearest_dist = d;
nearest = this;
}
});
return [nearest_dist, nearest];
}
/**
* Trace a ray (ro->rd) through the scene
*/
rays.trace = function(ro, rd, depth){
var depth = depth || 0
var i = rays.intersection(ro,rd, 1.0E100000);
var nearest = i[1];
var dist = i[0];
if (nearest){
var intersection_point = rays.vectorAdd(ro, rays.vectorScale(rays.vectorNormalise(rd), dist));
var normal = nearest.normal(intersection_point);
//Ambient
var col_scale = rays.ambient;
var specs = [0,0,0];
$.each(rays.scene.lights, function(){
var lvec = rays.vectorSubtract(this[0], intersection_point)
var shadow = rays.intersection(intersection_point, lvec, rays.vectorLength(lvec), 0.05);
if (!shadow[1]) {
//Diffuse (Lambertian)
var diff_scale = rays.vectorDot(rays.vectorNormalise(lvec), normal) * this[1]
if (diff_scale > 0){
col_scale += diff_scale;
}
/* Someone hire me and get me to do something productive :) */
//Specular
var i = rays.vectorNormalise(lvec);
var r = rays.vectorSubtract(i, rays.vectorScale(normal, 2.0*rays.vectorDot(normal, i)));
var dp = rays.vectorDot(r, rays.vectorNormalise(rd))
if (dp >0){
var spec_scale = Math.pow(dp, nearest.phong)
if (spec_scale > 0){
specs = rays.vectorAdd(specs, rays.vectorScale(this[2], spec_scale*this[1]))
}
}
}
});
var col = rays.vectorAdd(rays.vectorScale(nearest.color(intersection_point), col_scale), specs);
//Reflection
if (nearest.reflection && depth<rays.max_depth){
var r = rays.vectorSubtract(rd, rays.vectorScale(normal, 2.0*rays.vectorDot(normal, rd)));
var refl = rays.trace(intersection_point, r ,depth+1)
col = rays.vectorAdd(rays.vectorScale(col, 1 - nearest.reflection),
rays.vectorScale(refl, nearest.reflection))
}
return col
}
else{
/* No intersection, background color */
return [0,0,0];
}
}
/**
* Distribute each pixel
*/
rays.renderImage = function(screen, ctx){
var x = 0;
var y = 0;
var busy = false;
var rint = setInterval(function(){
if (!busy){
busy = true;
//Update Stats
//if(x%100 == 0){
//$("#rpx").text(x);
//$("#rpy").text(y);
// $("#progress").text(parseInt((x*y)/(screen.width*screen.height)*100));
//}
//Trace Ray
var ray = rays.vectorSubtract([x - screen.width/2, y - screen.height/2, rays.cam_dist], rays.eye);
var col = rays.trace(rays.eye, ray);
ctx.fillStyle = 'rgb(' + parseInt(col[0]) + "," + parseInt(col[1]) + "," + parseInt(col[2]) + ')';
ctx.fillRect (x,screen.height-y,1,1);
//Update 'loop'
if(x<screen.width-1){
x +=1;
}else{
x = 0;
if (y<screen.height-1){
y +=1;
}else{
clearInterval(rint);
}
}
busy = false;
}
},0);
}
/** Onload setup the canvas and do shizzle **/
$(function(){
var screen = $("#rays")[0];
var ctx = screen.getContext('2d');
ctx.fillStyle = "#555";
ctx.fillRect (0, 0, screen.width, screen.height);
$("#start_raytracer").click(function(){
rays.renderImage(screen, ctx);
});
});
</script>
<h2>About</h2>
<p>I've always been interested in Ray-Tracers - I spent hours playing about with <em><a href = "http://www.povray.org/">Povray</a></em> back in high school — the idea that you can make such realistic pictures algorithmically fascinates me. I've started writing a couple of my own over the years, but always shelved them around when I hit a nasty vector issue.</p>
<p>Vector maths is tricky.</p>
<p>But when I opened up my text editor this morning to play with the canvas element some more, I knew that something big was in order, bigger than <a href = "http://peterbraden.co.uk/article/pong">pong</a>, bigger than <a href = "http://peterbraden.co.uk/article/tetrisnightmares">tetris</a>. I was going to write a ray-tracer in the browser in the canvas element.</p>
<p>And 8 hours later it's done. Nothing revolutionary, but satisfyingly realistic.</p>
<p>
View the page source to check out the code - there's only about 200 lines of it and it's pretty simple to follow. It's simple Phong shading on sphere and plane primitives.</p>
<p>
I had originally planned to distribute the computational load between multiple browsers using a cross-browser map-reduce. But I think that's a project for another day.</p>
<p><em>I'm not the first to make a canvas based ray-tracer. It seems there's <a href = "http://www.flog.co.nz/journal/javascript-raytracer/">other</a> <a href = "http://jupiter909.com/mark/jsrt.html">people</a> who like pointless code too.</em></p>
</body>
</html>