├── .gitignore
├── README
├── black_wall.html
├── blocker.html
├── direct.html
├── direct_occlusion.html
├── gi2d.js
├── index.html
└── white_wall.html
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | *.o
3 | *.swp
4 | *.pyc
5 | *.swo
6 |
--------------------------------------------------------------------------------
/README:
--------------------------------------------------------------------------------
1 | GI2D is Copyright (C) 2012 Limbic Software, Inc.
2 | http://www.limbic.com/
3 | http://labs.limbic.com/gi2d/
4 |
5 | Redistribution and use in source and binary forms, with or without
6 | modification, are permitted provided that the following conditions are met:
7 | * Redistributions of source code must retain the above copyright
8 | notice, this list of conditions and the following disclaimer.
9 | * Redistributions in binary form must reproduce the above copyright
10 | notice, this list of conditions and the following disclaimer in the
11 | documentation and/or other materials provided with the distribution.
12 | * Neither the name of the Limbic Software, Inc. nor the
13 | names of its contributors may be used to endorse or promote products
14 | derived from this software without specific prior written permission.
15 |
16 | THIS SOFTWARE IS PROVIDED BY LIMBIC SOFTWARE, INC. ''AS IS'' AND ANY
17 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 | DISCLAIMED. IN NO EVENT SHALL LIMBIC SOFTWARE, INC. BE LIABLE FOR ANY
20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 |
27 | === What is this? ===
28 |
29 | This repository contains an example implementation of Global Illumination in 2D, as described in the paper:
30 |
31 | https://cs.dartmouth.edu/~wjarosz/publications/jarosz12theory.html
32 |
33 | === How to use ===
34 |
35 | Open index.html in any html 5 + canvas supporting browser (works on mobile phones, too!).
36 |
37 | === Note ===
38 |
39 | Please let me know if you find any issues, and feel free to submit any patches
40 | or pull requests!
41 |
42 | If you like this, please consider checking out our games:
43 |
44 | http://bit.ly/get-nuts
45 | http://bit.ly/zombie-gunship
46 | http://bit.ly/towermadness
47 |
48 | Enjoy! <3
49 |
50 | Volker
51 | http://www.limbic.com
52 | http://www.twitter.com/volcore
53 | http://volcore.limbicsoft.com
54 |
--------------------------------------------------------------------------------
/black_wall.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Global Illmuniation in 2D Example
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | Refresh
13 | # Samples:
14 | # Hemispherical Samples:
15 |
16 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/blocker.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Global Illmuniation in 2D Example
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | Refresh
13 | # Samples:
14 | # Hemispherical Samples:
15 |
16 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/direct.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Global Illmuniation in 2D Example
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | Refresh
13 | # Samples:
14 | # Hemispherical Samples:
15 |
16 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/direct_occlusion.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Global Illmuniation in 2D Example
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | Refresh
13 | # Samples:
14 | # Hemispherical Samples:
15 |
16 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/gi2d.js:
--------------------------------------------------------------------------------
1 | kWallWidth = 5.0;
2 | kSceneBorder = 200.0;
3 | kIrradianceColor = "#FF0000";
4 | kIrradianceNoOcclusionGradientColor = "#18B017";
5 | kIrradianceGradientColor = "#D4D41B";
6 | kIrradianceNoOcclusionHessianColor = "#5CB0FF";
7 | kIrradianceHessianColor = "#2930FF";
8 | kBackgroundColor = "#C3D9FF";
9 | kLightSourceColor = "#EBE54D";
10 | kWallColor = "#aaaaaa";
11 |
12 | function Sqr(x) { return x * x; }
13 | function Rotate2(x, angle) {
14 | var c = Math.cos(angle);
15 | var s = Math.sin(angle);
16 | return [c * x[0] - s * x[1], s * x[0] + c * x[1]];
17 | }
18 | function Dot2(x, y) {
19 | return x[0] * y[0] + x[1] * y[1];
20 | }
21 | function Scale2(s, x) {
22 | return [s*x[0], s*x[1]];
23 | }
24 | function Add2(x, y) {
25 | return [x[0] + y[0], x[1] + y[1]];
26 | }
27 | function Sub2(x, y) {
28 | return [x[0] - y[0], x[1] - y[1]];
29 | }
30 | function Length2(x) {
31 | return Math.sqrt(Dot2(x, x));
32 | }
33 | // Matrices are stored row major
34 | // m[0] m[1]
35 | // m[2] m[3]
36 | function Scale22(s, m) {
37 | return [s*m[0], s*m[1],
38 | s*m[2], s*m[3]];
39 | }
40 | function Outer22(a, b) {
41 | return [a[0]*b[0], a[0]*b[1],
42 | a[1]*b[0], a[1]*b[1]];
43 | }
44 | function Identity22() {
45 | return [1, 0, 0, 1];
46 | }
47 | function Add22(a, b) {
48 | return [a[0]+b[0], a[1]+b[1], a[2]+b[2], a[3]+b[3]];
49 | }
50 | function Sub22(a, b) {
51 | return [a[0]-b[0], a[1]-b[1], a[2]-b[2], a[3]-b[3]];
52 | }
53 | function Transform22(m, v) {
54 | return [m[0]*v[0] + m[1]*v[1], m[2]*v[0], v[3]*v[2]];
55 | }
56 |
57 | function Line(start, end) {
58 | this.start = start;
59 | this.end = end;
60 | this.length = function() {
61 | return Math.sqrt(Sqr(this.end[0] - this.start[0]) + Sqr(this.end[1] - this.start[1]));
62 | }
63 | this.locationForParameter = function(t) {
64 | return [(this.end[0] - this.start[0]) * t + this.start[0],
65 | (this.end[1] - this.start[1]) * t + this.start[1]];
66 | }
67 | this.direction = function() {
68 | var length = this.length();
69 | return [(this.end[0] - this.start[0])/length,
70 | (this.end[1] - this.start[1])/length];
71 | }
72 | this.normal = function() {
73 | return Rotate2(this.direction(), Math.PI/2.0)
74 | }
75 | this.orientedNormal = function(x) {
76 | var n = this.normal();
77 | if (Dot2(n, start) > Dot2(n, x)) {
78 | return Scale2(-1, n);
79 | }
80 | return n;
81 | }
82 | this.intersect = function(x, d) {
83 | var denom = (start[1] - end[1])*d[0] - (start[0] - end[0])*d[1];
84 | var numa = (start[0] - end[0])*(x[1] - end[1]) - (start[1] - end[1])*(x[0] - end[0]);
85 | var numb = d[0]*(x[1] - end[1]) - d[1]*(x[0] - end[0]);
86 | if (denom == 0.0) {
87 | return -1;
88 | }
89 | var ua = numa/denom;
90 | var ub = numb/denom;
91 | if (ua > 0.0 && ub >= 0.0 && ub <= 1.0 ) {
92 | return ua;
93 | }
94 | return -1.0;
95 | }
96 | }
97 |
98 | function Wall(line, irradiance, albedo) {
99 | this.line = line;
100 | this.irradiance = irradiance;
101 | this.albedo = albedo;
102 | this.radiance = function() {
103 | // Assuming diffuse emission, the irradiance is distributed evenly into all directions
104 | return this.irradiance / (2.0 * Math.PI);
105 | }
106 | }
107 |
108 | function IrradianceSample(E, dE, dEocc, ddE, ddEocc) {
109 | this.E = E;
110 | this.dE = dE;
111 | this.dEocc = dEocc;
112 | this.ddE = ddE;
113 | this.ddEocc = ddEocc;
114 | }
115 |
116 | function GI2D(canvas) {
117 | this.numDirectSamples_ = 100;
118 | this.canvas_ = canvas;
119 | this.walls_ = [];
120 | this.currentScale_ = [1.0, 1.0];
121 | this.currentOffset_ = [0.0, 0.0];
122 | this.numHemiRays_ = 8;
123 | this.setCanvas = function(canvas) {
124 | this.canvas_ = canvas;
125 | }
126 | this.setNumHemiRays = function(num) {
127 | this.numHemiRays_ = num;
128 | }
129 | this.setNumDirectSamples = function(num) {
130 | this.numDirectSamples_ = num;
131 | }
132 | this.addWall = function(wall) {
133 | this.walls_.push(wall);
134 | return this.walls_.length-1;
135 | }
136 | this.computeScaleOffset = function() {
137 | var ctx = this.canvas_.getContext("2d");
138 | var width = ctx.canvas.width;
139 | var height = ctx.canvas.height;
140 | // Compute bounds of geometry
141 | var min = [99999999.9, 99999999.9];
142 | var max = [-99999999.9, -99999999.9];
143 | for (var wall_idx in this.walls_) {
144 | var wall = this.walls_[wall_idx];
145 | for (var i = 0; i < 2; ++i) {
146 | min[i] = Math.min(wall.line.start[i], min[i]);
147 | min[i] = Math.min(wall.line.end[i], min[i]);
148 | max[i] = Math.max(wall.line.start[i], max[i]);
149 | max[i] = Math.max(wall.line.end[i], max[i]);
150 | }
151 | }
152 | // Compute isotropic scale and anisotropic offsets to map it into the canvas
153 | var border = kSceneBorder*2.0;
154 | var scale = [(width - border)/(max[0]-min[0]), (height - border)/(max[1]-min[1])];
155 | if (scale[0] < scale[1]) {
156 | scale[1] = scale[0];
157 | } else {
158 | scale[0] = scale[1];
159 | }
160 | var offset = [(width - (max[0]-min[0]) * scale[0])/2.0 - min[0] * scale[0], (height - (max[1]-min[1]) * scale[1])/2.0 - min[1] * scale[1]];
161 | this.currentScale_ = scale;
162 | this.currentOffset_ = offset;
163 | }
164 | this.pointToWorld = function(x) {
165 | return [x[0] * this.currentScale_[0] + this.currentOffset_[0], x[1] * this.currentScale_[1] + this.currentOffset_[1]];
166 | }
167 | this.drawScene = function() {
168 | var ctx = this.canvas_.getContext("2d");
169 | var width = ctx.canvas.width;
170 | var height = ctx.canvas.height;
171 | // Clear the canvas
172 | ctx.clearRect (0, 0, width, height);
173 | // Draw background
174 | ctx.fillStyle = kBackgroundColor;
175 | ctx.fillRect(0, 0, width, height);
176 | // Fetch scale and offset
177 | this.computeScaleOffset();
178 | var scale = this.currentScale_;
179 | var offset = this.currentOffset_;
180 | // Draw the scene geometry
181 | for (var wall_idx in this.walls_) {
182 | var wall = this.walls_[wall_idx];
183 | var a = [wall.line.start[0] * scale[0] + offset[0], wall.line.start[1] * scale[1] + offset[1]];
184 | var b = [wall.line.end[0] * scale[0] + offset[0], wall.line.end[1] * scale[1] + offset[1]];
185 | var n = wall.line.normal();
186 | var width = kWallWidth;
187 | ctx.beginPath()
188 | ctx.moveTo(a[0] + width * n[0], a[1] + width * n[1]);
189 | ctx.lineTo(b[0] + width * n[0], b[1] + width * n[1]);
190 | ctx.lineTo(b[0] - width * n[0], b[1] - width * n[1]);
191 | ctx.lineTo(a[0] - width * n[0], a[1] - width * n[1]);
192 | ctx.closePath()
193 | ctx.strokeStyle = "#000000";
194 | if (wall.irradiance > 0) {
195 | ctx.fillStyle = kLightSourceColor;
196 | } else {
197 | if (wall.albedo < 0.01) {
198 | ctx.fillStyle = "#000000";
199 | } else {
200 | ctx.fillStyle = kWallColor;
201 | }
202 | }
203 | ctx.lineWidth = 3.0;
204 | ctx.stroke();
205 | ctx.fill();
206 | }
207 | }
208 | // Intersect returns distance and which wall has been hit
209 | this.intersect = function(x, d) {
210 | var min_distance = 1e20;
211 | var id = -1;
212 | for (var wall_idx in this.walls_) {
213 | var wall = this.walls_[wall_idx];
214 | var distance = wall.line.intersect(x, d);
215 | if (distance > 0 && distance < min_distance) {
216 | id = wall_idx;
217 | min_distance = distance;
218 | }
219 | }
220 | if (id != -1) {
221 | return [min_distance, id];
222 | }
223 | return null;
224 | }
225 | this.sampleDirect = function(x, n) {
226 | // Pick random point on light source
227 | var light_idx = 0;
228 | var wall = this.walls_[light_idx];
229 | var lambda = Math.random();
230 | var y = wall.line.locationForParameter(lambda);
231 | var light_length = wall.line.length();
232 | var difference = Sub2(y, x);
233 | var distance = Length2(difference);
234 | var d = Scale2(1.0/distance, difference);
235 | // Intersect
236 | var intersection = this.intersect(x, d);
237 | var rejected = false;
238 | if (intersection != null) {
239 | var r = intersection[0];
240 | var wall_idx = intersection[1];
241 | if (wall_idx != light_idx || r < distance-1e-5 || Dot2(n, d) < 0) {
242 | return null;
243 | }
244 | }
245 | // Debug Draw
246 | var cos_light = -Dot2(wall.line.orientedNormal(x), d);
247 | var L = wall.radiance() * cos_light/distance;
248 | var p = 1/light_length;
249 | return [L, p, d];
250 | }
251 | this.trace = function(x, d, first_bounce) {
252 | var intersection = this.intersect(x, d);
253 | if (intersection == null) {
254 | return [d, 1e20, 0];
255 | }
256 | var distance = intersection[0];
257 | var wall_idx = intersection[1];
258 | var wall = this.walls_[wall_idx];
259 | var n = wall.line.normal();
260 | var y = Add2(x, Scale2(distance, d));
261 | var brdf = wall.albedo / 2.0;
262 | // Invert normal if pointing in the other direction
263 | if (n[0] * d[0] + n[1] * d[1] > 0) {
264 | n = [-n[0], -n[1]];
265 | }
266 | // Compute the radiance
267 | var radiance = 0.0;
268 | if (first_bounce) {
269 | // On first bounce, add direct lighting
270 | radiance += wall.radiance();
271 | }
272 | // sample direct lighting
273 | if (this.numDirectSamples_ > 0) {
274 | for (var i = 0; i < this.numDirectSamples_; ++i) {
275 | var direct = this.sampleDirect(Add2(y, Scale2(1e-5, n)), n);
276 | if (direct) {
277 | var L_direct = direct[0];
278 | var p_direct = direct[1];
279 | var d_direct = direct[2];
280 | var c_direct = Dot2(d_direct, n);
281 | radiance += c_direct * L_direct * brdf / p_direct / this.numDirectSamples_;
282 | }
283 | }
284 | }
285 | // round robin termination of path tracing
286 | // at least 50% chance
287 | var p_term = Math.max(1.0-wall.albedo, 0.5);
288 | var p_refl = 1.0-p_term;
289 | var rv = Math.random();
290 | // rr termination?
291 | if (rv < p_term) {
292 | return [n, distance, radiance]
293 | }
294 | // trace reflected ray
295 | var theta = Math.asin(2.0*Math.random()-1.0);
296 | var r_d = Rotate2(n, theta);
297 | var r_o = Add2(y, Scale2(1e-5, r_d));
298 | var trace_result = this.trace(r_o, r_d, this.numDirectSamples_ <= 0);
299 | var r_n = trace_result[0];
300 | var r_r = trace_result[1];
301 | var r_L = trace_result[2];
302 | // compute reflected radiance
303 | var icosine = Dot2(n, r_d);
304 | var p_ray = icosine/2.0;
305 | radiance += icosine * r_L * brdf / p_refl / p_ray;
306 | return [n, distance, radiance]
307 | }
308 | this.irradianceForPoint = function(x, n_x) {
309 | // Precompute some values
310 | var tangent = Rotate2(n_x, Math.PI/2);
311 | // Integrate over the hemisphere
312 | // Returns an IrradianceSample
313 | var E = 0.0;
314 | var dE = [0.0, 0.0];
315 | var dEocc = [0.0, 0.0];
316 | var ddE = [0, 0, 0, 0];
317 | var ddEocc = [0, 0, 0, 0];
318 | var pL = 0.0; // Previous radiance
319 | var pR = 1e20; // Previous radius
320 | for (var ray_index = 0; ray_index < this.numHemiRays_; ++ray_index) {
321 | // Stratified sampling
322 | var theta = Math.asin(2.0*(ray_index+Math.random())/this.numHemiRays_-1.0);
323 | // Compute the direction of the ray, and path trace it
324 | var d = Rotate2(n_x, theta);
325 | var trace_result = this.trace(x, d, true);
326 | // extract results
327 | var n_y = trace_result[0];
328 | var r = trace_result[1];
329 | var L = trace_result[2];
330 | var y = [x[0] + d[0] * r, x[1] + d[1] * r];
331 | // Accumulate the irradiance
332 | var cosine_theta = Math.cos(theta);
333 | var probability = cosine_theta/2.0;
334 | E += L * cosine_theta / probability;
335 | // Compute occlusion free gradient
336 | var xy = [d[0] * r, d[1] * r];
337 | var r2 = r*r;
338 | var cosine_theta_y = -Dot2(xy, n_y)/r;
339 | var ga = Scale2(3.0 / r2, xy);
340 | var gb = Scale2(1.0 / (cosine_theta * r), n_x);
341 | var gc = Scale2(1.0 / (cosine_theta_y * r), n_y);
342 | dE = Add2(dE, Scale2(cosine_theta * L / probability, Add2(Sub2(ga, gb), gc)));
343 | // Compute correct gradient
344 | var tim = Math.asin(2.0*ray_index/this.numHemiRays_-1.0);
345 | var ctim = Math.cos(tim);
346 | var grad_scale = (L - pL) * Sqr(ctim) / Math.min(r, pR);
347 | dEocc = Add2(dEocc, Scale2(grad_scale, tangent));
348 | // Compute occlusion free hessian
349 | var r3 = r2*r;
350 | var r4 = r2*r2;
351 | var ha = Scale22(15 / r4, Outer22(xy, xy));
352 | var hb = Scale22( 3 / r2, Identity22());
353 | var hc = Scale22( 1 / (cosine_theta * cosine_theta_y * r2), Add22(Outer22(n_x, n_y), Outer22(n_y, n_x)));
354 | var hd = Scale22( 3 / (cosine_theta * r3), Add22(Outer22(n_x, xy), Outer22(xy, n_x)));
355 | var he = Scale22( 3 / (cosine_theta_y * r3), Add22(Outer22(n_y, xy), Outer22(xy, n_y)));
356 | ddE = Add22(ddE, Scale22(cosine_theta * L / probability, Add22(Sub22(Sub22(Sub22(ha, hb), hc), hd), he)));
357 | // Compute correct hessian
358 | var stim = Math.sin(tim);
359 | ddEocc = Add22(ddEocc, Scale22(3 * (L - pL) * Sqr(ctim) * stim / Sqr(Math.min(r, pR)), Outer22(tangent, tangent)));
360 | // Store values for this hemicircle stratum
361 | pL = L;
362 | pR = r;
363 | }
364 | // Normalize
365 | E /= this.numHemiRays_;
366 | dE = Scale2(1.0/this.numHemiRays_, dE);
367 | ddE = Scale22(1.0/this.numHemiRays_, ddE);
368 | // return the samples
369 | return new IrradianceSample(E, dE, dEocc, ddE, ddEocc);
370 | }
371 | this.drawIrradianceOnWall = function(wall_idx, num_samples, value_scale) {
372 | var ctx = this.canvas_.getContext("2d");
373 | var width = ctx.canvas.width;
374 | var height = ctx.canvas.height;
375 | // Fetch scale and offset
376 | this.computeScaleOffset();
377 | var scale = this.currentScale_;
378 | var offset = this.currentOffset_;
379 | // Sample irradiance at discrete locations on the wall
380 | var wall = this.walls_[wall_idx];
381 | var length = wall.line.length()
382 | var sample_irradiances= [];
383 | var max_irradiance = 0;
384 | var start = new Date().getTime();
385 | for (var sample = 0; sample < num_samples; ++sample) {
386 | var t = sample / num_samples;
387 | var p = wall.line.locationForParameter(t);
388 | var n = wall.line.normal();
389 | var irradiance_sample = this.irradianceForPoint(p, n);
390 | max_irradiance = Math.max(irradiance_sample.E, max_irradiance);
391 | sample_irradiances.push(irradiance_sample);
392 | }
393 | var end = new Date().getTime();
394 | var time = end - start;
395 | // The drawing function
396 | var draw_function = function(color, value) {
397 | ctx.strokeStyle = color;
398 | ctx.lineWidth = 3.0;
399 | ctx.beginPath()
400 | for (var sample = 0; sample < num_samples; ++sample) {
401 | var t = (sample+0.5) / num_samples;
402 | var p = wall.line.locationForParameter(t);
403 | p = [scale[0]*p[0]+offset[0], scale[1]*p[1]+offset[1]];
404 | var s = value(sample);
405 | var x = [p[0] + n[0] * s, p[1] + n[1] * s];
406 | if (sample == 0) {
407 | ctx.moveTo(x[0], x[1]);
408 | } else {
409 | ctx.lineTo(x[0], x[1]);
410 | }
411 | }
412 | ctx.stroke();
413 | ctx.closePath();
414 | }
415 | // Draw the irradiance
416 | draw_function(kIrradianceColor, function(idx){
417 | return value_scale * sample_irradiances[idx].E / max_irradiance + kWallWidth;
418 | });
419 | // Draw the gradient
420 | draw_function(kIrradianceNoOcclusionGradientColor, function(idx){
421 | var n = wall.line.normal();
422 | var tangent = Rotate2(n, Math.PI/2.0);
423 | var grad = Dot2(tangent, sample_irradiances[idx].dE);
424 | return value_scale * grad / max_irradiance + kWallWidth;
425 | });
426 | // Draw the occlusion gradient
427 | draw_function(kIrradianceGradientColor, function(idx){
428 | var n = wall.line.normal();
429 | var tangent = Rotate2(n, Math.PI/2.0);
430 | var grad = Dot2(tangent, sample_irradiances[idx].dEocc);
431 | return value_scale * grad / max_irradiance + kWallWidth;
432 | });
433 | // Draw the hessian
434 | draw_function(kIrradianceNoOcclusionHessianColor, function(idx){
435 | var n = wall.line.normal();
436 | var tangent = Rotate2(n, Math.PI/2.0);
437 | // Evaluate gradient in tangent direction
438 | var grad = Dot2(tangent, Transform22(sample_irradiances[idx].ddE, tangent));
439 | return value_scale * grad / max_irradiance + kWallWidth;
440 | });
441 | // Draw the occlusion hessian
442 | draw_function(kIrradianceHessianColor, function(idx){
443 | var n = wall.line.normal();
444 | var tangent = Rotate2(n, Math.PI/2.0);
445 | // Evaluate gradient in tangent direction
446 | var grad = Dot2(tangent, Transform22(sample_irradiances[idx].ddEocc, tangent));
447 | return value_scale * grad / max_irradiance + kWallWidth;
448 | });
449 | // Draw debug text
450 | ctx.fillStyle = "#444444";
451 | ctx.font = "bold 12px Arial";
452 | ctx.fillText("Computation took "+(time/1000)+" s", width-175, height-10);
453 | }
454 | this.drawLegend = function() {
455 | var ctx = this.canvas_.getContext("2d");
456 | var width = ctx.canvas.width;
457 | var height = ctx.canvas.height;
458 | var items = [
459 | ["Irradiance", kIrradianceColor, false],
460 | ["Irradiance Gradient (no occlusion)", kIrradianceNoOcclusionGradientColor, false],
461 | ["Irradiance Gradient (occlusion)", kIrradianceGradientColor, false],
462 | ["Irradiance Hessian (no occlusion)", kIrradianceNoOcclusionHessianColor, false],
463 | ["Irradiance Hessian (occlusion)", kIrradianceHessianColor, false],
464 | ["Wall", kWallColor, true],
465 | ["Light Source", kLightSourceColor, true]
466 | ];
467 | var offset = 10;
468 | // Draw irradiance
469 | for (var i = 0; i < items.length; ++i) {
470 | var item = items[i];
471 | ctx.fillStyle = "#000000";
472 | ctx.font = "bold 16px Arial";
473 | ctx.fillText(item[0], 30, height-offset);
474 | if (item[2]) {
475 | // Draw a box
476 | ctx.strokeStyle = "#000000";
477 | ctx.fillStyle = item[1];
478 | ctx.beginPath();
479 | ctx.moveTo(5, height-offset);
480 | ctx.lineTo(5, height-10-offset);
481 | ctx.lineTo(25, height-10-offset);
482 | ctx.lineTo(25, height-offset);
483 | ctx.lineTo(5, height-offset);
484 | ctx.stroke();
485 | ctx.fill();
486 | ctx.closePath();
487 | } else {
488 | // Draw a line
489 | ctx.strokeStyle = item[1];
490 | ctx.beginPath();
491 | ctx.moveTo(5, height-5-offset);
492 | ctx.lineTo(25, height-5-offset);
493 | ctx.stroke();
494 | ctx.closePath();
495 | }
496 | offset += 20;
497 | }
498 | }
499 | }
500 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Global Illumination in 2D Examples
5 |
6 |
7 | Test Scenes from the paper
8 | direct no occlusion scene
9 | direct with occlusion scene
10 | blocker test scene
11 | white wall test scene
12 | black wall test scene
13 | Resources
14 | The main Javascript
15 | The official Github page
16 | The paper
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/white_wall.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Global Illmuniation in 2D Example
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | Refresh
13 | # Samples:
14 | # Hemispherical Samples:
15 |
16 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------