|
| 1 | +// helpers .. merge and download binary file. |
| 2 | + function m(a,b) { for (var i in b) a[i]=b[i]; return a; } |
| 3 | + function fileAsArray(name,complete,prog) { |
| 4 | + var r=m(new XMLHttpRequest(),{responseType:'arraybuffer',onprogress:prog,onload:function(){complete&&complete(this.response)}}); |
| 5 | + r.open("GET",name,true); r.send(); |
| 6 | + } |
| 7 | + |
| 8 | +// helpers .. |
| 9 | + function rX(m, angle) { |
| 10 | + var s=Math.sin(angle),c=Math.cos(angle),a10=m[4],a11=m[5],a12=m[6],a13=m[7],a20=m[8],a21=m[9],a22=m[10],a23=m[11]; |
| 11 | + m[4]=a10*c+a20*s;m[5]=a11*c+a21*s;m[6]=a12*c+a22*s;m[7]=a13*c+a23*s; |
| 12 | + m[8]=a10*-s+a20*c;m[9]=a11*-s+a21*c;m[10]=a12*-s+a22*c;m[11]=a13*-s+a23*c; |
| 13 | + return m; |
| 14 | + }; |
| 15 | + function rY(m, angle) { |
| 16 | + var s=Math.sin(angle),c=Math.cos(angle),a00=m[0],a01=m[1],a02=m[2],a03=m[3],a20=m[8],a21=m[9],a22=m[10],a23=m[11]; |
| 17 | + m[0]=a00*c+a20*-s;m[1]=a01*c+a21*-s;m[2]=a02*c+a22*-s;m[3]=a03*c+a23*-s; |
| 18 | + m[8]=a00*s+a20*c;m[9]=a01*s+a21*c;m[10]=a02*s+a22*c;m[11]=a03*s+a23*c; |
| 19 | + return m; |
| 20 | + }; |
| 21 | + function t(m,v) { |
| 22 | + var x=v[0],y=v[1],z=v[2]; |
| 23 | + m[12]=m[0]*x+m[4]*y+m[8]*z+m[12]; |
| 24 | + m[13]=m[1]*x+m[5]*y+m[9]*z+m[13]; |
| 25 | + m[14]=m[2]*x+m[6]*y+m[10]*z+m[14]; |
| 26 | + m[15]=m[3]*x+m[7]*y+m[11]*z+m[15]; |
| 27 | + return m; |
| 28 | + } |
| 29 | + function i() { return new Float32Array([1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]) }; |
| 30 | + |
| 31 | +// png compressed raw file support.. |
| 32 | + function png_to_raw(png) { |
| 33 | + var c = document.createElement('canvas'), x=c.width=png.width, y=c.height=png.height, gl=c.getContext('webgl'); |
| 34 | + |
| 35 | + var texture = gl.createTexture(); |
| 36 | + gl.bindTexture(gl.TEXTURE_2D, texture); |
| 37 | + gl.texImage2D( gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, png); |
| 38 | + |
| 39 | + fb = gl.createFramebuffer(); |
| 40 | + gl.bindFramebuffer(gl.FRAMEBUFFER, fb); |
| 41 | + gl.framebufferTexture2D( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); |
| 42 | + |
| 43 | + var res = new Uint8Array(x*y*4); |
| 44 | + gl.readPixels(0,0,x,y,gl.RGBA,gl.UNSIGNED_BYTE,res); |
| 45 | + |
| 46 | + gl.deleteTexture(texture); |
| 47 | + gl.deleteFramebuffer(fb); |
| 48 | + return res.buffer; |
| 49 | + } |
| 50 | + |
| 51 | +// Download polygon and accelerator data. (they're called .html to fool github pages into compressing them ..) |
| 52 | + var todo=2,map,polyData; |
| 53 | + |
| 54 | + var mapI = new Image(), polyDataI = new Image(); |
| 55 | + mapI.onload = function() { map = new Float32Array(png_to_raw(this)); if(!--todo) allthere(); }; |
| 56 | + polyDataI.onload = function() { polyData = new Float32Array(png_to_raw(this)); if (!--todo) allthere()}; |
| 57 | + mapI.src = 'map2.bin.png'; |
| 58 | + polyDataI.src = 'polydata2.bin.png'; |
| 59 | + |
| 60 | +// All data is there .. compile shaders and render .. |
| 61 | + function allthere(){ |
| 62 | + |
| 63 | + // settings. |
| 64 | + var gridres=128,totX=4096,totY=391,pbrtShowrender=true,pbrtBounces=2,pbrtBatch=1,pbrtSamples=Math.floor((parseInt(document.location.hash.slice(1))||200)/pbrtBatch); |
| 65 | + var pbrtGrid = { bbox : new Float32Array([-16.35320053100586,-3.3039399147033692,-13.719999885559082,31.68820018768311,13.706639957427978,24.6798002243042])}; |
| 66 | + |
| 67 | + // Shader helpers. |
| 68 | + function createShader(gl, source, type) { var shader=gl.createShader(type); gl.shaderSource(shader, source); gl.compileShader(shader); return shader; } |
| 69 | + function createProgram(gl, vertexShaderSource, fragmentShaderSource) { |
| 70 | + var program = gl.createProgram(); |
| 71 | + gl.attachShader(program, createShader(gl, vertexShaderSource, gl.VERTEX_SHADER)); |
| 72 | + gl.attachShader(program, createShader(gl, fragmentShaderSource, gl.FRAGMENT_SHADER)); |
| 73 | + gl.linkProgram(program); |
| 74 | + return program; |
| 75 | + }; |
| 76 | + |
| 77 | + // Offscreen float buffers. |
| 78 | + function createOffscreen(gl,width,height) { |
| 79 | + var colorTexture = gl.createTexture(); |
| 80 | + gl.bindTexture(gl.TEXTURE_2D, colorTexture); |
| 81 | + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA32F, width, height, 0, gl.RGBA, gl.FLOAT, null); |
| 82 | + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); |
| 83 | + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); |
| 84 | + gl.bindTexture(gl.TEXTURE_2D, null); |
| 85 | + |
| 86 | + var depthBuffer = gl.createRenderbuffer(); |
| 87 | + gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer); |
| 88 | + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, width, height); |
| 89 | + gl.bindRenderbuffer(gl.RENDERBUFFER, null); |
| 90 | + |
| 91 | + var framebuffer = gl.createFramebuffer(); |
| 92 | + gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer); |
| 93 | + gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, colorTexture, 0); |
| 94 | + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthBuffer); |
| 95 | + gl.bindFramebuffer(gl.FRAMEBUFFER, null); |
| 96 | + |
| 97 | + return { |
| 98 | + framebuffer:framebuffer,colorTexture:colorTexture,depthBuffer:depthBuffer,width:width,height:height,gl:gl, |
| 99 | + bind : function() { this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.framebuffer); this.gl.viewport(0,0,this.width,this.height); }, |
| 100 | + unbind : function() { this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null); }, |
| 101 | + delete : function() { this.gl.deleteRenderbuffer(this.depthBuffer); this.gl.deleteFramebuffer(this.framebuffer); this.gl.deleteTexture(this.colorTexture); } |
| 102 | + } |
| 103 | + } |
| 104 | + |
| 105 | + // setup output canvas, gl context and offscreen. |
| 106 | + var canvas = m(document.body.appendChild(document.createElement('canvas')),{width:800,height:450}); |
| 107 | + m(canvas.style,{width:'800px',height:'450px'}); |
| 108 | + var gl = canvas.getContext('webgl2'); if (!gl) alert('webGL2 required !'); |
| 109 | + var ext1 = gl.getExtension('EXT_color_buffer_float'); if (!ext1) alert('videocard required'); |
| 110 | + var ofscreen1 = createOffscreen(gl,canvas.width,canvas.height); |
| 111 | + |
| 112 | + // Shaders for accumulation and tonemap. |
| 113 | + var vs2=`#version 300 es |
| 114 | + #define POSITION_LOCATION 0 |
| 115 | + precision lowp float; |
| 116 | + layout(location = POSITION_LOCATION) in vec3 position; |
| 117 | + void main() { gl_Position = vec4(position, 1.0); }`; |
| 118 | + |
| 119 | + var fs2=`#version 300 es |
| 120 | + precision lowp float; |
| 121 | + precision lowp sampler2D; |
| 122 | + uniform sampler2D accum; |
| 123 | + uniform float count; |
| 124 | + out vec4 color; |
| 125 | + void main() { color = vec4(sqrt(texelFetch(accum,ivec2(gl_FragCoord.xy),0).rgb*count),1.0); } |
| 126 | + `; |
| 127 | + |
| 128 | + // Actual Path tracing shaders. |
| 129 | + var vs=`#version 300 es |
| 130 | + #define POSITION_LOCATION 0 |
| 131 | + precision highp float; |
| 132 | + precision highp int; |
| 133 | + uniform mat4 MVP; |
| 134 | + layout(location = POSITION_LOCATION) in vec3 position; |
| 135 | + out vec3 origin; |
| 136 | + void main() |
| 137 | + { |
| 138 | + origin = (MVP * vec4(0.0,0.0,0.0,1.0)).xyz; |
| 139 | + gl_Position = vec4(position, 1.0); |
| 140 | + }`; |
| 141 | + |
| 142 | + var fs=`#version 300 es |
| 143 | + precision highp float; |
| 144 | + precision highp int; |
| 145 | + precision highp sampler3D; |
| 146 | + precision highp sampler2D; |
| 147 | +
|
| 148 | + #define EPSILON 0.000001 |
| 149 | +
|
| 150 | + uniform vec2 resolution; |
| 151 | + uniform float inseed; |
| 152 | + uniform int incount; |
| 153 | + |
| 154 | + uniform mat4 MVP, proj; |
| 155 | +
|
| 156 | + uniform sampler3D grid; |
| 157 | + uniform sampler2D tris; |
| 158 | + uniform vec3 bbina, bbinb; |
| 159 | +
|
| 160 | + vec3 bboxA, bboxB; |
| 161 | +
|
| 162 | + in vec3 origin; |
| 163 | + out vec4 color; |
| 164 | + |
| 165 | + uint N = ${pbrtSamples}u, i; |
| 166 | +
|
| 167 | + float seed; |
| 168 | + float minc(const vec3 x) { return min(x.x,min(x.y,x.z)); } |
| 169 | + |
| 170 | + float random_ofs=0.0; |
| 171 | + vec3 cosWeightedRandomHemisphereDirectionHammersley( const vec3 n ) { |
| 172 | + float x = float(i)/float(N); |
| 173 | + i = (i << 16u) | (i >> 16u); |
| 174 | + i = ((i & 0x55555555u) << 1u) | ((i & 0xAAAAAAAAu) >> 1u); |
| 175 | + i = ((i & 0x33333333u) << 2u) | ((i & 0xCCCCCCCCu) >> 2u); |
| 176 | + i = ((i & 0x0F0F0F0Fu) << 4u) | ((i & 0xF0F0F0F0u) >> 4u); |
| 177 | + i = ((i & 0x00FF00FFu) << 8u) | ((i & 0xFF00FF00u) >> 8u); |
| 178 | + vec2 r = vec2(x,(float(i) * 2.32830643653086963e-10 * 6.2831) + random_ofs); |
| 179 | + vec3 uu=normalize(cross(n, vec3(1.0,1.0,0.0))), vv=cross( uu, n ); |
| 180 | + float sqrtx = sqrt(r.x); |
| 181 | + return normalize(vec3( sqrtx*cos(r.y)*uu + sqrtx*sin(r.y)*vv + sqrt(1.0-r.x)*n )); |
| 182 | + } |
| 183 | +
|
| 184 | + vec4 trace( inout vec3 realori, const vec3 dir) { |
| 185 | + float len=0.0,l,b,mint=1000.0; |
| 186 | + vec2 minuv, mintri, cpos; |
| 187 | + vec3 scaler=vec3(bbinb/${gridres}.0)/dir,orig=realori,v0,v1,v2; |
| 188 | + for (int i=0;i<150;i++){ |
| 189 | + vec3 txc=(orig-bboxA)*bboxB; |
| 190 | + if ( txc != clamp(txc,0.0,1.0)) break; |
| 191 | + vec3 tex=textureLod(grid,txc,0.0).rgb; |
| 192 | + for(int tri=0; tri<512; tri++) { |
| 193 | + if (tex.b<=0.0) break; cpos=tex.rg; tex.rb+=vec2(3.0/4096.0,-1.0); |
| 194 | + v1 = textureLodOffset(tris,cpos,0.0,ivec2(1,0)).rgb; |
| 195 | + v2 = textureLodOffset(tris,cpos,0.0,ivec2(2,0)).rgb; |
| 196 | + vec3 P = cross(dir,v2); float det=dot(v1,P); if (det>-EPSILON) continue; |
| 197 | + v0 = textureLod(tris,cpos,0.0).rgb; |
| 198 | + vec3 T=realori-v0; float invdet=1.0/det; float u=dot(T,P)*invdet; if (u < 0.0 || u > 1.0) continue; |
| 199 | + vec3 Q=cross(T,v1); float v=dot(dir,Q)*invdet; if(v<0.0||u+v>1.0) continue; |
| 200 | + float t=dot(v2, Q)*invdet; if (t>EPSILON && t<mint) { mint=t; mintri=cpos; minuv=vec2(u,v); } |
| 201 | + } |
| 202 | + b=max(0.0,-tex.b-1.0); txc=fract(txc*${gridres}.0); |
| 203 | + l=minc(scaler*mix(b+1.0-txc,-b-txc,vec3(lessThan(dir,vec3(0.0)))))+EPSILON*50.0; |
| 204 | + len += l; |
| 205 | + if (mint <= len) { |
| 206 | + realori += dir*(mint); |
| 207 | + mintri += vec2(0.0,1.0/4.0); |
| 208 | + vec3 n0 = -textureLod(tris,mintri,0.0).rgb; |
| 209 | + vec3 n1 = -textureLodOffset(tris,mintri,0.0,ivec2(1,0)).rgb; |
| 210 | + vec3 n2 = -textureLodOffset(tris,mintri,0.0,ivec2(2,0)).rgb; |
| 211 | + return vec4(normalize(n0*(1.0-minuv.x-minuv.y) + n1*minuv.x + n2*minuv.y),mint); |
| 212 | + } |
| 213 | + orig += dir*l; |
| 214 | + } |
| 215 | + return vec4(0.0); |
| 216 | + } |
| 217 | + |
| 218 | + void main() |
| 219 | + { |
| 220 | + bboxA=bbina; bboxB=1.0/bbinb; i=uint(incount); |
| 221 | + vec2 fc = vec2(gl_FragCoord.xy), fcu=fc/resolution; |
| 222 | + seed = inseed +fcu.x+fcu.y; |
| 223 | + vec2 aa = fract(sin(vec2(seed,seed+0.1))*vec2(43758.5453123,22578.1459123)); |
| 224 | + random_ofs = fract(gl_FragCoord.x * gl_FragCoord.y * inseed + aa.x)*6.2831; |
| 225 | + vec4 view = proj * vec4((fc+aa)/(resolution/2.0)-1.0,0.0,1.0); |
| 226 | + view = normalize(MVP*vec4(view.xyz/view.w,0.0)); |
| 227 | + vec3 orig=origin,v1=(bboxA-orig)/view.xyz,v2=v1+(bbinb-vec3(0.2))/view.xyz,far=max(v1,v2),near=min(v1,v2); |
| 228 | + float en=max(near.x,max(near.y,near.z)), ex=min(far.x,min(far.y,far.z)); |
| 229 | + if (ex < 0.0 || en > ex) { color=vec4(1.0); return; } |
| 230 | + orig += max(0.0,en)*view.xyz; |
| 231 | + vec4 hit=trace(orig,view.xyz); |
| 232 | + if (hit.w <= 0.0) { color.rgb = vec3(1.0); return; } |
| 233 | + hit=trace(orig, -cosWeightedRandomHemisphereDirectionHammersley(hit.xyz)); |
| 234 | + if (hit.w <= 0.0) { color.rgb = vec3(0.8); return; } |
| 235 | + }`; |
| 236 | + |
| 237 | + // Upload polygon and acceleration data. |
| 238 | + var texture = gl.createTexture(); |
| 239 | + gl.activeTexture(gl.TEXTURE0); |
| 240 | + gl.bindTexture(gl.TEXTURE_3D, texture); |
| 241 | + gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); |
| 242 | + gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); |
| 243 | + gl.texImage3D( gl.TEXTURE_3D, 0, gl.RGB32F, gridres, gridres, gridres, 0, gl.RGB, gl.FLOAT, map ); |
| 244 | + |
| 245 | + var texture2 = gl.createTexture(); |
| 246 | + gl.activeTexture(gl.TEXTURE1); |
| 247 | + gl.bindTexture(gl.TEXTURE_2D, texture2); |
| 248 | + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); |
| 249 | + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); |
| 250 | + gl.texImage2D( gl.TEXTURE_2D, 0, gl.RGB32F, totX, totY*4, 0, gl.RGB, gl.FLOAT, polyData ); |
| 251 | + |
| 252 | + // Create the path tracing program, grab the uniforms. |
| 253 | + var program = createProgram(gl, vs, fs); |
| 254 | + var mvpLocation = gl.getUniformLocation(program, 'MVP'); |
| 255 | + var pLocation = gl.getUniformLocation(program, 'proj'); |
| 256 | + var uniformgridLocation = gl.getUniformLocation(program, 'grid'); |
| 257 | + var uniformtrisLocation = gl.getUniformLocation(program, 'tris'); |
| 258 | + var uniformSeed = gl.getUniformLocation(program, 'inseed'); |
| 259 | + var uniformCount = gl.getUniformLocation(program, 'incount'); |
| 260 | + var uniformbbaLocation = gl.getUniformLocation(program, 'bbina'); |
| 261 | + var uniformbbbLocation = gl.getUniformLocation(program, 'bbinb'); |
| 262 | + var uniformresLocation = gl.getUniformLocation(program, 'resolution'); |
| 263 | + |
| 264 | + // Create the accumulation program, grab thos uniforms. |
| 265 | + var program2 = createProgram(gl, vs2, fs2); |
| 266 | + var uniformAccumLocation = gl.getUniformLocation(program2, 'accum'); |
| 267 | + var uniformCountLocation = gl.getUniformLocation(program2, 'count'); |
| 268 | + |
| 269 | + // Setup the quad that will drive the rendering. |
| 270 | + var vertexPosBuffer = gl.createBuffer(); |
| 271 | + gl.bindBuffer(gl.ARRAY_BUFFER, vertexPosBuffer); |
| 272 | + gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 0, 1, -1, 0, 1, 1, 0, 1, 1, 0, -1, 1, 0, -1, -1, 0]), gl.STATIC_DRAW); |
| 273 | + gl.bindBuffer(gl.ARRAY_BUFFER, null); |
| 274 | + |
| 275 | + var vertexArray = gl.createVertexArray(); |
| 276 | + gl.bindVertexArray(vertexArray); |
| 277 | + var vertexPosLocation = 0; |
| 278 | + gl.enableVertexAttribArray(vertexPosLocation); |
| 279 | + gl.bindBuffer(gl.ARRAY_BUFFER, vertexPosBuffer); |
| 280 | + gl.vertexAttribPointer(vertexPosLocation, 3, gl.FLOAT, false, 0, 0); |
| 281 | + gl.bindBuffer(gl.ARRAY_BUFFER, null); |
| 282 | + gl.bindVertexArray(null); |
| 283 | + |
| 284 | + // Setup some matrices (stole values from jimmy|rig) |
| 285 | + var matrix = new Float32Array([6.123234262925839e-17, 0, 1, 0, -0.8660253882408142, 0.5, 5.302876566937394e-17, 0, -0.5, -0.8660253882408142, 3.0616171314629196e-17, 0, 6.535898208618164, 19.320507049560547, -4.0020835038019837e-16, 1]); |
| 286 | + var matrix2 = new Float32Array([1.7777777910232544,0,0,0,0,1,0,0,0,0,0,-0.49950000643730164,0,0,1,0.5005000233650208]); |
| 287 | + var viewportMV = new Float32Array(matrix); |
| 288 | + var accum_count=1, diff=true, abort=false; |
| 289 | + |
| 290 | + // frame handler. |
| 291 | + function frame() { |
| 292 | + // Do we need to restart rendering (i.e. viewport change) |
| 293 | + if (diff) { |
| 294 | + matrix.set(viewportMV); |
| 295 | + ofscreen1.bind(); gl.clear(gl.COLOR_BUFFER_BIT); ofscreen1.unbind(); |
| 296 | + accum_count=1; |
| 297 | + abort=undefined; |
| 298 | + diff=false; |
| 299 | + } |
| 300 | + |
| 301 | + // Render more samples. |
| 302 | + if (!abort) { |
| 303 | + // Bind the offscreen and render a new sample. |
| 304 | + ofscreen1.bind(); |
| 305 | + gl.useProgram(program); |
| 306 | + gl.uniformMatrix4fv(mvpLocation, false, matrix); |
| 307 | + gl.uniformMatrix4fv(pLocation, false, matrix2); |
| 308 | + gl.uniform1i(uniformgridLocation, 0); |
| 309 | + gl.uniform1i(uniformtrisLocation, 1); |
| 310 | + gl.uniform3fv(uniformbbaLocation, pbrtGrid.bbox.slice(0,3)); |
| 311 | + gl.uniform3fv(uniformbbbLocation, pbrtGrid.bbox.slice(3,6)); |
| 312 | + gl.uniform2fv(uniformresLocation, new Float32Array([canvas.width,canvas.height])); |
| 313 | + gl.uniform1f(uniformSeed,Math.random()); |
| 314 | + gl.uniform1i(uniformCount,(accum_count)%pbrtSamples); |
| 315 | + |
| 316 | + gl.activeTexture(gl.TEXTURE0); |
| 317 | + gl.bindTexture(gl.TEXTURE_3D, texture); |
| 318 | + gl.activeTexture(gl.TEXTURE1); |
| 319 | + gl.bindTexture(gl.TEXTURE_2D, texture2); |
| 320 | + |
| 321 | + gl.enable(gl.BLEND); |
| 322 | + gl.blendFunc(gl.ONE,gl.ONE); |
| 323 | + gl.bindVertexArray(vertexArray); |
| 324 | + gl.drawArrays(gl.TRIANGLES, 0, 6); |
| 325 | + gl.bindVertexArray(null); |
| 326 | + gl.disable(gl.BLEND); |
| 327 | + ofscreen1.unbind(); |
| 328 | + |
| 329 | + // Display progress (mixdown from float to ldr) |
| 330 | + gl.useProgram(program2); |
| 331 | + gl.uniform1i(uniformAccumLocation, 0); |
| 332 | + gl.uniform1f(uniformCountLocation, 1.0/accum_count); |
| 333 | + gl.activeTexture(gl.TEXTURE0); |
| 334 | + gl.bindTexture(gl.TEXTURE_2D, ofscreen1.colorTexture); |
| 335 | + |
| 336 | + gl.bindVertexArray(vertexArray); |
| 337 | + gl.drawArrays(gl.TRIANGLES, 0, 6); |
| 338 | + gl.bindVertexArray(null); |
| 339 | + |
| 340 | + gl.bindTexture(gl.TEXTURE_2D, null); |
| 341 | + |
| 342 | + // Stop if we're done. |
| 343 | + if (++accum_count>pbrtSamples) abort =true; |
| 344 | + } |
| 345 | + requestAnimationFrame(frame); |
| 346 | + } |
| 347 | + requestAnimationFrame(frame); |
| 348 | + |
| 349 | + var angle=-Math.PI/2, angle2=Math.PI/3,zoom=0; |
| 350 | + canvas.oncontextmenu =function(e) { e.preventDefault(); e.stopPropagation(); } |
| 351 | + canvas.onmousemove = function(e) { |
| 352 | + if (!e.buttons) return; |
| 353 | + if (e.buttons==1) { angle += e.movementX/100; angle2 += e.movementY/100; } else { zoom += e.movementX/10; } |
| 354 | + viewportMV = t(rX(rY(i(),angle),angle2),[0,4,-20+zoom]); |
| 355 | + diff = true; |
| 356 | + } |
| 357 | + }; |
0 commit comments