├── .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 | 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 | 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 | 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 | 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 | 13 | # Samples: 14 | # Hemispherical Samples: 15 |
16 | 31 | 32 | 33 | 34 | --------------------------------------------------------------------------------