-
Notifications
You must be signed in to change notification settings - Fork 0
/
raytracer.js
257 lines (203 loc) · 7.16 KB
/
raytracer.js
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
function RayTracer(canvasID) {
/**
* Initializes an instance of the RayTracer class.
* You may also wish to set up your camera here.
* (feel free to modify input parameters to set up camera).
*
* @param canvasID (string) - id of the canvas in the DOM where we want to render our image
*/
// setup the canvas
this.canvas = document.getElementById(canvasID);
// set BVH
this.doBVH = false
// setup the background style: current options are 'daylight' or 'white'
this.sky = 'daylight';
// initialize the objects and lights
this.objects = new Array();
this.lights = new Array();
this.spp = parseInt(document.getElementById('spp').value) || 1
// CREATE THE CAMERA
// params
let camParams = {
eye: vec3.fromValues(0, 2, 10),
center: vec3.fromValues(
-0.07164544612169266,
0.15180052816867828,
0.212920486927032477),
up: vec3.fromValues(0.0, 1.0, 0.0),
fov: 0.7853981633974483,
a: this.canvas.width / this.canvas.height
}
this.camera = new Camera(camParams)
}
RayTracer.prototype.draw = function () {
/**
* Renders the scene to the canvas.
* Loops through all pixels and computes a pixel sample at each pixel midpoint.
* Pixel color should then be computed and assigned to the image.
**/
// get the canvas and the image data we will write to
let context = this.canvas.getContext('2d');
let image = context.createImageData(this.canvas.width, this.canvas.height);
// numbers of pixels in x- and y- directions
const nx = image.width;
const ny = image.height;
// this.rootBVHNode = new BVHNode(this.objects, 0, this.objects.length - 1)
// loop through the canvas pixels
for (let j = 0; j < ny; j++) {
for (let i = 0; i < nx; i++) {
// compute pixel color
let color = vec3.create();
//sample per pixel
for (let s = 0; s < this.spp; s++) {
// compute pixel coordinates in [0,1] x [0,1]
let px = (i + Math.random()) / nx; // sample at pixel center
let py = (ny - j - Math.random()) / ny; // canvas has y pointing down, but image plane has y going up
// YOU NEED TO DETERMINE PIXEL COLOR HERE (see notes)
// i.e. cast a ray through (px,py) and call some 'color' function
let ray = this.genRay(px, py)
vec3.add(color, color, this.color(ray, 10))
}
//average color
vec3.scale(color, color, 1 / this.spp)
// set the pixel color into our final image
this.setPixel(image, i, j, color[0], color[1], color[2]);
}
}
context.putImageData(image, 0, 0);
}
/**
* Fucntion to traverse the bvh nodes and return the smallest intersetction
*
*/
// RayTracer.prototype.bvhTraverse = function (root, ray) {
// }
RayTracer.prototype.background = function (ray) {
/**
* Computes the background color for a ray that goes off into the distance.
*
* @param ray - ray with a 'direction' (vec3) and 'point' (vec3)
* @returns a color as a vec3 with (r,g,b) values within [0,1]
*
* Note: this assumes a Ray class that has member variable ray.direction.
* If you change the name of this member variable, then change the ray.direction[1] accordingly.
**/
if (this.sky === 'white') {
// a white sky
return vec3.fromValues(1, 1, 1);
}
else if (this.sky === 'daylight') {
// a light blue sky :)
let t = 0.5 * ray.direction[1] + 0.2; // uses the y-values of ray.direction
if (ray.direction == undefined) t = 0.2; // remove this if you have a different name for ray.direction
let color = vec3.create();
vec3.lerp(color, vec3.fromValues(.5, .7, 1.), vec3.fromValues(1, 1, 1), t);
return color;
}
else
alert('unknown sky ', this.sky);
}
RayTracer.prototype.setPixel = function (image, x, y, r, g, b) {
/**
* Sets the pixel color into the image data that is ultimately shown on the canvas.
*
* @param image - image data to write to
* @param x,y - pixel coordinates within [0,0] x [canvas.width,canvas.height]
* @param r,g,b - color to assign to pixel, each channel is within [0,1]
* @returns none
*
* You do not need to change this function.
**/
let offset = (image.width * y + x) * 4;
image.data[offset] = 255 * Math.min(r, 1.0);
image.data[offset + 1] = 255 * Math.min(g, 1.0);
image.data[offset + 2] = 255 * Math.min(b, 1.0);
image.data[offset + 3] = 255; // alpha: transparent [0-255] opaque
}
RayTracer.prototype.color = function (ray, depth) {
if (depth === 0) {
// reached max depth
return this.background(ray)
}
// collide ray
let hit = this.hit(ray)
if (!hit) return this.background(ray)
let color = vec3.create();
if (hit.hit_obj.material){
vec3.mul(color, hit.hit_obj.material.ka, vec3.fromValues(1.0, 1.0, 1.0))
//iterate over lights
for (const light of this.lights) {
// calculate the light direction to create ray from
let light_dir = vec3.create()
vec3.sub(light_dir, light.location, hit.point)
let shadow_ray = new Ray(light_dir, hit.point)
let blocked_hit = this.hit(shadow_ray)
if (!blocked_hit) {
vec3.add(color, color, hit.hit_obj.material.shade(ray, light, hit))
}
}
// SCATTER
let scatter_ray = hit.hit_obj.material.scatter(ray, hit)
if (scatter_ray) {
let scatter_color = this.color(scatter_ray, depth - 1)
// mix colors
vec3.scaleAndAdd(color, vec3.scale(vec3.create(), color, 0.5), scatter_color, 0.5)
}
} else {
color = hit.hit_obj.color
}
return color
}
let x = 0;
RayTracer.prototype.hit = function (ray) {
// OVER OBJECTS
if (this.doBVH){
let rootHit = this.rootBVHNode.hit(ray, .001, 1e20)
if (x < 3){
console.log(totalHits)
console.log(totalTrianglesHit)
x++
}
totalHits = 0;
bothDefined = 0;
totalTrianglesHit = 0;
return rootHit
} else {
let minIntersection;
for (const obj of this.objects) {
let intersection = obj.hit(ray, .001, 1e20)
if (intersection) {
if (!minIntersection || intersection.param_t < minIntersection.param_t ) {
minIntersection = intersection
}
}
}
return minIntersection
}
}
function Light(params) {
// describes a point light source, storing the location of the light
// as well as ambient, diffuse and specular components of the light
this.location = params.location; // location of the light
this.color = params.color || vec3.fromValues(1, 1, 1); // default to white (vec3)
this.ld = vec3.create()
vec3.scale(this.ld, this.color, 0.75)
this.ls = vec3.create()
vec3.scale(this.ls, this.color, 0.75)
this.la = this.color
// you might also want to save some La, Ld, Ls and/or compute these from 'this.color'
}
RayTracer.prototype.genRay = function (px, py) {
this.pu = (-this.camera.wid / 2.0) + (px * this.camera.wid)
this.pv = (-this.camera.h / 2.0) + (py * this.camera.h)
this.pw = - this.camera.d
this.q = vec3.fromValues(this.pu, this.pv, this.pw)
this.p = vec3.create()
vec3.transformMat3(this.p, this.q, this.camera.orthoBasis)
return new Ray(this.p, this.camera.eye)
}
function Ray(direction, origin) {
this.x0 = origin.slice()
this.direction = vec3.create()
vec3.normalize(this.direction, direction)
}