├── screenshot.png ├── README.md └── Cornell box ├── shaders ├── material.js ├── position.js ├── normals.js ├── display.js ├── momentMove.js ├── radianceAccum.js ├── history.js ├── atrous.js └── radiance.js ├── index.html ├── dependencies ├── HDRCubeTextureLoader.js ├── utils.js ├── RGBELoader.js ├── DRACOLoader.js └── orbitControls.js └── main.js /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Domenicobrz/Realtime-path-tracing-in-chrome/HEAD/screenshot.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Realtime-path-tracing-in-chrome 2 | A (bad) attempt at implementing wavelet filtering and temporal reprojection in threejs 3 | 4 | [Live demo here](https://domenicobrz.github.io/webgl/projects/RTPTCornellBox/) 5 | 6 | 7 | -------------------------------------------------------------------------------- /Cornell box/shaders/material.js: -------------------------------------------------------------------------------- 1 | let material_vs = ` 2 | attribute vec4 aMaterial; 3 | 4 | varying vec4 vMaterial; 5 | 6 | void main() { 7 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 8 | 9 | vMaterial = aMaterial; 10 | } 11 | `; 12 | 13 | let material_fs = ` 14 | varying vec4 vMaterial; 15 | 16 | void main() { 17 | gl_FragColor = vMaterial; 18 | } 19 | `; 20 | 21 | export { material_fs, material_vs }; -------------------------------------------------------------------------------- /Cornell box/shaders/position.js: -------------------------------------------------------------------------------- 1 | let position_vs = ` 2 | varying vec3 vWorldSpacePosition; 3 | 4 | void main() { 5 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 6 | vWorldSpacePosition = position; 7 | } 8 | `; 9 | 10 | let position_fs = ` 11 | varying vec3 vWorldSpacePosition; 12 | 13 | void main() { 14 | gl_FragColor = vec4(vWorldSpacePosition, 1.0); 15 | } 16 | `; 17 | 18 | export { position_fs, position_vs }; -------------------------------------------------------------------------------- /Cornell box/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Cornell box/shaders/normals.js: -------------------------------------------------------------------------------- 1 | let normal_vs = ` 2 | varying vec3 vWorldSpaceNormal; 3 | varying vec3 vWorldSpacePosition; 4 | 5 | void main() { 6 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 7 | vWorldSpaceNormal = normal; 8 | vWorldSpacePosition = position; 9 | } 10 | `; 11 | 12 | let normal_fs = ` 13 | varying vec3 vWorldSpaceNormal; 14 | varying vec3 vWorldSpacePosition; 15 | 16 | uniform vec3 uCameraPos; 17 | 18 | void main() { 19 | // vec3 viewDir = normalize(vWorldSpacePosition - uCameraPos); 20 | 21 | vec3 normal = normalize(vWorldSpaceNormal); 22 | // if(dot(viewDir, normal) > 0.0) normal = -normal; 23 | 24 | gl_FragColor = vec4(normal, 1.0); 25 | } 26 | `; 27 | 28 | export { normal_fs, normal_vs }; -------------------------------------------------------------------------------- /Cornell box/shaders/display.js: -------------------------------------------------------------------------------- 1 | let display_vs = ` 2 | varying vec2 vUv; 3 | 4 | void main() { 5 | gl_Position = vec4(position, 1.0); 6 | vUv = uv; 7 | } 8 | `; 9 | 10 | let display_fs = ` 11 | varying vec2 vUv; 12 | 13 | uniform sampler2D uTexture; 14 | 15 | vec3 acesFilm(const vec3 x) { 16 | const float a = 2.51; 17 | const float b = 0.03; 18 | const float c = 2.43; 19 | const float d = 0.59; 20 | const float e = 0.14; 21 | return clamp((x * (a * x + b)) / (x * (c * x + d ) + e), 0.0, 1.0); 22 | } 23 | 24 | void main() { 25 | vec3 color = texture2D(uTexture, vUv).xyz; 26 | color *= pow(2.0, -1.0); 27 | vec3 mapped = acesFilm(color); 28 | 29 | // // gamma correction 30 | // mapped = pow(mapped, 1.0 / 2.2); 31 | 32 | 33 | 34 | 35 | // float exposure = 0.1; 36 | // vec3 mapped = color; 37 | // mapped *= pow(2.0, exposure); 38 | 39 | // // -- filmic correction 40 | // mapped *= 0.6; 41 | // mapped = ((mapped * mapped) * 2.51 + mapped * 0.03) / (mapped * mapped * 2.43 + mapped * 0.59 + 0.14); 42 | 43 | // -- gamma correction + clamp 44 | mapped = pow(mapped, vec3(1.0 / 2.2)); 45 | mapped = clamp(mapped, 0.0, 1.0); 46 | 47 | 48 | 49 | 50 | gl_FragColor = vec4(mapped, 1.0); 51 | } 52 | `; 53 | 54 | export { display_fs, display_vs }; -------------------------------------------------------------------------------- /Cornell box/shaders/momentMove.js: -------------------------------------------------------------------------------- 1 | let momentMove_vs = ` 2 | attribute vec3 oldPosition; 3 | 4 | 5 | varying vec3 vFragPos; 6 | varying vec3 vOldFragPos; 7 | 8 | varying mat4 modelViewMat; 9 | varying mat4 vProjectionMatrix; 10 | 11 | // varying vec2 vOldNDCPos; 12 | 13 | void main() { 14 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 15 | 16 | vFragPos = position; 17 | vOldFragPos = oldPosition; 18 | 19 | modelViewMat = modelViewMatrix; 20 | vProjectionMatrix = projectionMatrix; 21 | } 22 | `; 23 | 24 | let momentMove_fs = ` 25 | varying vec3 vFragPos; 26 | varying vec3 vOldFragPos; 27 | 28 | uniform mat4 uOldModelViewMatrix; 29 | 30 | varying mat4 modelViewMat; 31 | varying mat4 vProjectionMatrix; 32 | 33 | // varying vec2 vOldNDCPos; 34 | 35 | void main() { 36 | 37 | vec4 ndcOldPos = vProjectionMatrix * uOldModelViewMatrix * vec4(vOldFragPos, 1.0); 38 | vec4 ndcNewPos = vProjectionMatrix * modelViewMat * vec4(vFragPos, 1.0); 39 | 40 | ndcOldPos.xyzw /= ndcOldPos.w; 41 | ndcNewPos.xyzw /= ndcNewPos.w; 42 | 43 | ndcOldPos.xy = ndcOldPos.xy * 0.5 + 0.5; 44 | ndcNewPos.xy = ndcNewPos.xy * 0.5 + 0.5; 45 | 46 | gl_FragColor = vec4(ndcOldPos.xy - ndcNewPos.xy, 0.0, 1.0); 47 | // gl_FragColor = vec4(normalize(ndcOldPos.xy - ndcNewPos.xy) * 0.5 + 0.5, 0.0, 1.0); 48 | // gl_FragColor = vec4(ndcOldPos.xy, 0.0, 1.0); 49 | } 50 | `; 51 | 52 | export { momentMove_fs, momentMove_vs }; 53 | -------------------------------------------------------------------------------- /Cornell box/shaders/radianceAccum.js: -------------------------------------------------------------------------------- 1 | let radianceAccum_vs = ` 2 | varying vec2 vUv; 3 | 4 | void main() { 5 | gl_Position = vec4(position, 1.0); 6 | vUv = uv; 7 | } 8 | `; 9 | 10 | let radianceAccum_fs = ` 11 | varying vec2 vUv; 12 | 13 | uniform sampler2D uCurrentRadiance; 14 | uniform sampler2D uAccumulatedRadiance; 15 | uniform sampler2D uHistoryBuffer; 16 | uniform sampler2D uMomentMoveBuffer; 17 | 18 | uniform float uMaxFramesHistory; 19 | 20 | void main() { 21 | 22 | vec2 reprojUVOffs = texture2D(uMomentMoveBuffer, vUv).xy; 23 | 24 | vec3 newRad = texture2D(uCurrentRadiance, vUv).xyz; 25 | vec3 accumulatedRad = texture2D(uAccumulatedRadiance, vUv + reprojUVOffs).xyz; 26 | 27 | 28 | float maxFrames = uMaxFramesHistory; 29 | float history = min(texture2D(uHistoryBuffer, vUv).x, maxFrames); 30 | 31 | // float lambda = ((maxFrames - history) / maxFrames) * 0.95 + 0.05; 32 | // REMEMBER: this is an exponential average, and apparently the perceived variance 33 | // WILL be lower if we accumulate more than $maxFrames samples (the variance reduction 34 | // stops at around 2 * $maxFrames samples, that's why the atrous filter multiplies 35 | // the clamped history by 0.5 36 | float lambda = (((maxFrames+1.0) - history) / (maxFrames+1.0)); 37 | 38 | 39 | // float material = texture2D(uMaterialBuffer, vUv).w; 40 | // lambda = material == 3.0 ? 0.1 : lambda; 41 | 42 | vec3 updatedAccum = newRad * lambda + accumulatedRad * (1.0 - lambda); 43 | 44 | gl_FragColor = vec4(updatedAccum, 1.0); 45 | } 46 | `; 47 | 48 | export { radianceAccum_fs, radianceAccum_vs}; -------------------------------------------------------------------------------- /Cornell box/shaders/history.js: -------------------------------------------------------------------------------- 1 | let historyTest_vs = ` 2 | varying mat4 vProjectionViewMatrix; 3 | varying vec3 vFragPos; 4 | varying vec3 vNormal; 5 | 6 | uniform vec3 uCameraPos; 7 | 8 | void main() { 9 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 10 | 11 | vProjectionViewMatrix = projectionMatrix * modelViewMatrix; 12 | vFragPos = position; 13 | vNormal = normal; 14 | 15 | // vec3 viewDir = normalize(vFragPos - uCameraPos); 16 | // if(dot(viewDir, normal) > 0.0) { 17 | // vNormal = -vNormal; 18 | // } 19 | } 20 | `; 21 | 22 | let historyTest_fs = ` 23 | varying mat4 vProjectionViewMatrix; 24 | varying vec3 vFragPos; 25 | varying vec3 vNormal; 26 | 27 | uniform sampler2D uNormalBuffer; 28 | uniform sampler2D uPositionBuffer; 29 | uniform sampler2D uMomentMove; 30 | uniform vec2 uInvScreenSize; 31 | 32 | void main() { 33 | vec4 projected = vProjectionViewMatrix * vec4(vFragPos, 1.0); 34 | projected /= projected.w; 35 | vec2 uv = projected.xy * 0.5 + 0.5; 36 | 37 | 38 | // vec2 testOffsets[5]; 39 | // testOffsets[0] = vec2(0.0, 0.0); 40 | // testOffsets[1] = vec2(0.0, -1.0 * uInvScreenSize.y); 41 | // testOffsets[2] = vec2(0.0, 1.0 * uInvScreenSize.y); 42 | // testOffsets[3] = vec2(-1.0 * uInvScreenSize.x, 0.0); 43 | // testOffsets[4] = vec2(1.0 * uInvScreenSize.x, 0.0); 44 | 45 | // vec3 success = vec3(1.0); 46 | // vec2 olduv = uv + texture2D(uMomentMove, uv).xy; 47 | // for(int i = 0; i < 5; i++) { 48 | // // reprojection test 49 | // vec3 oldNormal = texture2D(uNormalBuffer, olduv + testOffsets[i]).xyz; 50 | // vec3 oldPosition = texture2D(uPositionBuffer, olduv + testOffsets[i]).xyz; 51 | // vec3 normal = normalize(vNormal); 52 | // if(dot(oldNormal, normal) < 0.9) { 53 | // success = vec3(0.0); 54 | // break; 55 | // } 56 | 57 | // // if(length(oldPosition - vFragPos) > 0.1) success = vec3(0.0); 58 | // } 59 | 60 | 61 | 62 | vec3 success = vec3(1.0); 63 | 64 | // reprojection test 65 | vec2 olduv = uv + texture2D(uMomentMove, uv).xy; 66 | vec3 oldNormal = texture2D(uNormalBuffer, olduv).xyz; 67 | vec3 oldPosition = texture2D(uPositionBuffer, olduv).xyz; 68 | 69 | vec3 normal = normalize(vNormal); 70 | 71 | 72 | if(dot(oldNormal, normal) < 0.9) success = vec3(0.0); 73 | if(length(oldPosition - vFragPos) > 0.25) success = vec3(0.0); 74 | 75 | gl_FragColor = vec4(success, 1.0); 76 | } 77 | `; 78 | 79 | 80 | 81 | 82 | 83 | 84 | let historyAccum_vs = ` 85 | varying vec2 vUv; 86 | 87 | void main() { 88 | gl_Position = vec4(position, 1.0); 89 | vUv = uv; 90 | } 91 | `; 92 | 93 | let historyAccum_fs = ` 94 | varying vec2 vUv; 95 | 96 | uniform sampler2D uHistoryTest; 97 | uniform sampler2D uHistoryAccum; 98 | uniform sampler2D uMomentMove; 99 | 100 | void main() { 101 | 102 | // quesito interessante.. l'accumulazione va fatta basandosi 103 | // sul valore accumulato del pixel reproiettato o su quello corrente? 104 | vec2 olduv = vUv + texture2D(uMomentMove, vUv).xy; 105 | 106 | float lastTestResult = texture2D(uHistoryTest, vUv).x; 107 | float accum = texture2D(uHistoryAccum, olduv).x; 108 | 109 | float updatedAccum = lastTestResult < 0.5 ? 0.0 : accum + 1.0; 110 | gl_FragColor = vec4(vec3(updatedAccum), 1.0); 111 | } 112 | `; 113 | 114 | 115 | export { historyTest_fs, historyTest_vs, historyAccum_vs, historyAccum_fs }; -------------------------------------------------------------------------------- /Cornell box/dependencies/HDRCubeTextureLoader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Prashant Sharma / spidersharma03 3 | * @author Ben Houston / http://clara.io / bhouston 4 | */ 5 | 6 | import { 7 | CubeTexture, 8 | DataTexture, 9 | FileLoader, 10 | FloatType, 11 | HalfFloatType, 12 | LinearEncoding, 13 | LinearFilter, 14 | Loader, 15 | NearestFilter, 16 | RGBAFormat, 17 | RGBEEncoding, 18 | RGBFormat, 19 | UnsignedByteType 20 | } from "./three.module.js"; 21 | import { RGBELoader } from "./RGBELoader.js"; 22 | 23 | var HDRCubeTextureLoader = function ( manager ) { 24 | 25 | Loader.call( this, manager ); 26 | 27 | this.hdrLoader = new RGBELoader(); 28 | this.type = UnsignedByteType; 29 | 30 | }; 31 | 32 | HDRCubeTextureLoader.prototype = Object.assign( Object.create( Loader.prototype ), { 33 | 34 | constructor: HDRCubeTextureLoader, 35 | 36 | load: function ( urls, onLoad, onProgress, onError ) { 37 | 38 | if ( ! Array.isArray( urls ) ) { 39 | 40 | console.warn( 'THREE.HDRCubeTextureLoader signature has changed. Use .setDataType() instead.' ); 41 | 42 | this.setDataType( urls ); 43 | 44 | urls = onLoad; 45 | onLoad = onProgress; 46 | onProgress = onError; 47 | onError = arguments[ 4 ]; 48 | 49 | } 50 | 51 | var texture = new CubeTexture(); 52 | 53 | texture.type = this.type; 54 | 55 | switch ( texture.type ) { 56 | 57 | case UnsignedByteType: 58 | 59 | texture.encoding = RGBEEncoding; 60 | texture.format = RGBAFormat; 61 | texture.minFilter = NearestFilter; 62 | texture.magFilter = NearestFilter; 63 | texture.generateMipmaps = false; 64 | break; 65 | 66 | case FloatType: 67 | 68 | texture.encoding = LinearEncoding; 69 | texture.format = RGBFormat; 70 | texture.minFilter = LinearFilter; 71 | texture.magFilter = LinearFilter; 72 | texture.generateMipmaps = false; 73 | break; 74 | 75 | case HalfFloatType: 76 | 77 | texture.encoding = LinearEncoding; 78 | texture.format = RGBFormat; 79 | texture.minFilter = LinearFilter; 80 | texture.magFilter = LinearFilter; 81 | texture.generateMipmaps = false; 82 | break; 83 | 84 | } 85 | 86 | var scope = this; 87 | 88 | var loaded = 0; 89 | 90 | function loadHDRData( i, onLoad, onProgress, onError ) { 91 | 92 | new FileLoader( scope.manager ) 93 | .setPath( scope.path ) 94 | .setResponseType( 'arraybuffer' ) 95 | .load( urls[ i ], function ( buffer ) { 96 | 97 | loaded ++; 98 | 99 | var texData = scope.hdrLoader.parse( buffer ); 100 | 101 | if ( ! texData ) return; 102 | 103 | if ( texData.data !== undefined ) { 104 | 105 | var dataTexture = new DataTexture( texData.data, texData.width, texData.height ); 106 | 107 | dataTexture.type = texture.type; 108 | dataTexture.encoding = texture.encoding; 109 | dataTexture.format = texture.format; 110 | dataTexture.minFilter = texture.minFilter; 111 | dataTexture.magFilter = texture.magFilter; 112 | dataTexture.generateMipmaps = texture.generateMipmaps; 113 | 114 | texture.images[ i ] = dataTexture; 115 | 116 | } 117 | 118 | if ( loaded === 6 ) { 119 | 120 | texture.needsUpdate = true; 121 | if ( onLoad ) onLoad( texture ); 122 | 123 | } 124 | 125 | }, onProgress, onError ); 126 | 127 | } 128 | 129 | for ( var i = 0; i < urls.length; i ++ ) { 130 | 131 | loadHDRData( i, onLoad, onProgress, onError ); 132 | 133 | } 134 | 135 | return texture; 136 | 137 | }, 138 | 139 | setDataType: function ( value ) { 140 | 141 | this.type = value; 142 | this.hdrLoader.setDataType( value ); 143 | 144 | return this; 145 | 146 | } 147 | 148 | } ); 149 | 150 | export { HDRCubeTextureLoader }; 151 | -------------------------------------------------------------------------------- /Cornell box/dependencies/utils.js: -------------------------------------------------------------------------------- 1 | import * as THREE from "./three.module.js"; 2 | 3 | let Utils = { } 4 | 5 | Utils.smoothstep = function(t) { 6 | return t * t * (3 - 2 * t); 7 | } 8 | 9 | let onceMemory = { } 10 | Utils.once = function(tag) { 11 | if(!onceMemory[tag]) { 12 | onceMemory[tag] = true; 13 | return true; 14 | } 15 | 16 | return false; 17 | } 18 | 19 | Utils.parseIncludes = function( string ) { 20 | var utils_includepattern = /#include <(.*)>/gm; 21 | 22 | function replace( match , include ) { 23 | var replace = THREE.ShaderChunk[ include ]; 24 | return Utils.parseIncludes( replace ); 25 | } 26 | 27 | return string.replace( utils_includepattern, replace ); 28 | } 29 | 30 | Utils.last = function(array) { 31 | return array[array.length - 1]; 32 | } 33 | 34 | Utils.vec3Equal = function(a, b) { 35 | return a.x === b.x && a.y === b.y && a.z === b.z; 36 | } 37 | 38 | Utils.easings = { 39 | // no easing, no acceleration 40 | linear: function (t) { return t }, 41 | // accelerating from zero velocity 42 | easeInQuad: function (t) { return t*t }, 43 | // decelerating to zero velocity 44 | easeOutQuad: function (t) { return t*(2-t) }, 45 | // acceleration until halfway, then deceleration 46 | easeInOutQuad: function (t) { return t<.5 ? 2*t*t : -1+(4-2*t)*t }, 47 | // accelerating from zero velocity 48 | easeInCubic: function (t) { return t*t*t }, 49 | // decelerating to zero velocity 50 | easeOutCubic: function (t) { return (--t)*t*t+1 }, 51 | // acceleration until halfway, then deceleration 52 | easeInOutCubic: function (t) { return t<.5 ? 4*t*t*t : (t-1)*(2*t-2)*(2*t-2)+1 }, 53 | // accelerating from zero velocity 54 | easeInQuart: function (t) { return t*t*t*t }, 55 | // decelerating to zero velocity 56 | easeOutQuart: function (t) { return 1-(--t)*t*t*t }, 57 | // acceleration until halfway, then deceleration 58 | easeInOutQuart: function (t) { return t<.5 ? 8*t*t*t*t : 1-8*(--t)*t*t*t }, 59 | // accelerating from zero velocity 60 | easeInQuint: function (t) { return t*t*t*t*t }, 61 | // decelerating to zero velocity 62 | easeOutQuint: function (t) { return 1+(--t)*t*t*t*t }, 63 | // acceleration until halfway, then deceleration 64 | easeInOutQuint: function (t) { return t<.5 ? 16*t*t*t*t*t : 1+16*(--t)*t*t*t*t } 65 | } 66 | 67 | Utils.toCamelCase = function(str, splitter) { 68 | if(!splitter) splitter = " "; 69 | 70 | return str.split(splitter).map(function(word,index) { 71 | // If it is the first word make sure to lowercase all the chars. 72 | if(index == 0){ 73 | return word.toLowerCase(); 74 | } 75 | // If it is not the first word only upper case the first char and lowercase the rest. 76 | return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(); 77 | }).join(''); 78 | } 79 | 80 | Utils.removeAllChildren = function(element) { 81 | while (element.firstChild) { 82 | element.removeChild(element.firstChild); 83 | } 84 | } 85 | 86 | Utils.setAllAsUnactive = function(array) { 87 | for(let i = 0; i < array.length; i++) { 88 | let element = array[i]; 89 | element.classList.remove("active"); 90 | } 91 | } 92 | 93 | Utils.inchesToHeightString = function(value) { 94 | let feet = Math.floor(value / 12); 95 | let inches = value % 12; 96 | 97 | return "" + feet + "' " + inches + "''"; 98 | } 99 | 100 | Utils.getJSON = function(file, callback) { 101 | var rawFile = new XMLHttpRequest(); 102 | rawFile.overrideMimeType("application/json"); 103 | rawFile.open("GET", file, true); 104 | rawFile.onreadystatechange = function() { 105 | if (rawFile.readyState === 4 && rawFile.status == "200") { 106 | var jsonData = JSON.parse(rawFile.responseText); 107 | callback(jsonData); 108 | } 109 | }; 110 | rawFile.send(null); 111 | } 112 | 113 | export default Utils; -------------------------------------------------------------------------------- /Cornell box/shaders/atrous.js: -------------------------------------------------------------------------------- 1 | let atrous_vs = ` 2 | varying vec2 vUv; 3 | 4 | void main() { 5 | gl_Position = vec4(position, 1.0); 6 | vUv = uv; 7 | } 8 | `; 9 | 10 | let atrous_fs = `varying vec2 vUv; 11 | 12 | uniform sampler2D uRadiance; 13 | uniform sampler2D uPosition; 14 | uniform sampler2D uNormal; 15 | uniform sampler2D uMaterial; 16 | uniform sampler2D uHistoryAccum; 17 | uniform float uFilterHistoryModulation; 18 | uniform float uMaxFramesHistory; 19 | uniform float uStep; 20 | uniform vec2 uScreenSize; 21 | uniform float uC_phi; 22 | uniform float uN_phi; 23 | uniform float uP_phi; 24 | 25 | 26 | void main() { 27 | 28 | #ifdef atrous5x5 29 | float kernel[25]; 30 | kernel[20] = 0.00390625; 31 | kernel[21] = 0.015625; 32 | kernel[22] = 0.0234375; 33 | kernel[23] = 0.015625; 34 | kernel[24] = 0.00390625; 35 | kernel[15] = 0.015625; kernel[16] = 0.0625; kernel[17] = 0.09375; kernel[18] = 0.0625; kernel[19] = 0.015625; 36 | kernel[10] = 0.0234375; kernel[11] = 0.09375; kernel[12] = 0.140625; kernel[13] = 0.09375; kernel[14] = 0.0234375; 37 | kernel[5] = 0.015625; kernel[6] = 0.0625; kernel[7] = 0.09375; kernel[8] = 0.0625; kernel[9] = 0.015625; 38 | kernel[0] = 0.00390625; kernel[1] = 0.015625; kernel[2] = 0.0234375; kernel[3] = 0.015625; kernel[4] = 0.00390625; 39 | 40 | vec2 offs[25]; 41 | offs[20] = vec2(-2.0, +2.0); offs[21] = vec2(-1.0, +2.0); offs[22] = vec2(+0.0, +2.0); offs[23] = vec2(+1.0, +2.0); offs[24] = vec2(+2.0, +2.0); 42 | offs[15] = vec2(-2.0, +1.0); offs[16] = vec2(-1.0, +1.0); offs[17] = vec2(+0.0, +1.0); offs[18] = vec2(+1.0, +1.0); offs[19] = vec2(+2.0, +1.0); 43 | offs[10] = vec2(-2.0, +0.0); offs[11] = vec2(-1.0, +0.0); offs[12] = vec2(+0.0, +0.0); offs[13] = vec2(+1.0, +0.0); offs[14] = vec2(+2.0, +0.0); 44 | offs[5] = vec2(-2.0, -1.0); offs[6] = vec2(-1.0, -1.0); offs[7] = vec2(+0.0, -1.0); offs[8] = vec2(+1.0, -1.0); offs[9] = vec2(+2.0, -1.0); 45 | offs[0] = vec2(-2.0, -2.0); offs[1] = vec2(-1.0, -2.0); offs[2] = vec2(+0.0, -2.0); offs[3] = vec2(+1.0, -2.0); offs[4] = vec2(+2.0, -2.0); 46 | const int loopSteps = 25; 47 | #endif 48 | 49 | 50 | #ifdef atrous3x3 51 | float kernel[9]; 52 | kernel[6] = 0.0625; kernel[7] = 0.125; kernel[8] = 0.0625; 53 | kernel[3] = 0.125; kernel[4] = 0.25; kernel[5] = 0.125; 54 | kernel[0] = 0.0625; kernel[1] = 0.125; kernel[2] = 0.0625; 55 | 56 | vec2 offs[9]; 57 | offs[6] = vec2(-1.0, +1.0); offs[7] = vec2(+0.0, +1.0); offs[8] = vec2(+1.0, +1.0); 58 | offs[3] = vec2(-1.0, +0.0); offs[4] = vec2(+0.0, +0.0); offs[5] = vec2(+1.0, +0.0); 59 | offs[0] = vec2(-1.0, -1.0); offs[1] = vec2(+0.0, -1.0); offs[2] = vec2(+1.0, -1.0); 60 | const int loopSteps = 9; 61 | #endif 62 | 63 | 64 | float c_phi = uC_phi; 65 | float n_phi = uN_phi; 66 | float p_phi = uP_phi; 67 | float stepwidth = uStep; 68 | 69 | vec4 sum = vec4(0.0); 70 | vec2 step = vec2(1./uScreenSize.x, 1./uScreenSize.y); 71 | vec2 hstep = step * 0.0; 72 | vec4 cval = texture2D(uRadiance, vUv.st + hstep); 73 | vec4 nval = texture2D(uNormal, vUv.st + hstep); 74 | vec4 pval = texture2D(uPosition, vUv.st + hstep); 75 | 76 | 77 | float history = texture2D(uHistoryAccum, vUv.st + hstep).x; 78 | // here I'm multiplying the history by 0.5 because the 79 | // perceived variance in the samples that are exactly at maxFrameHistory 80 | // is almost 2x lower than the samples that are at maxFrameHistory * 2 81 | // in practice: if some fragments have been accumulated 40 times each 82 | // (with maxframehis. == 20) they will show a much reduced variance 83 | // compared to a set of fragments that have been accumulated for 20 frames. 84 | // So essentially when a frame is at "uMaxFramesHistory * 2", it will have the same 85 | // perceived variance of the frames that have accumulated much 86 | // longer than $uMaxFramesHistory 87 | float clampedHistory = min(history * 0.5, uMaxFramesHistory); 88 | stepwidth *= 1.0 - (1.0 - (uMaxFramesHistory - clampedHistory) / uMaxFramesHistory) * uFilterHistoryModulation; 89 | // stepwidth *= history >= uMaxFramesHistory * 4.0 ? 0.0 : 1.0; 90 | 91 | // // **************** mirror-like materials 92 | // vec4 material = texture2D(uMaterial, vUv.st + hstep); 93 | // if(material.w == 3.0) stepwidth *= 0.75; 94 | // // **************** mirror-like materials 95 | 96 | 97 | float cum_w = 0.0; 98 | for(int i = 0; i < loopSteps; i++) { 99 | vec2 uv = vUv.st + hstep + offs[i] * step * stepwidth; 100 | 101 | vec4 ctmp = texture2D(uRadiance, uv); 102 | vec4 t = cval - ctmp; 103 | float dist2 = dot(t,t); 104 | float c_w = min(exp(-(dist2)/c_phi), 1.0); 105 | 106 | vec4 ntmp = texture2D(uNormal, uv); 107 | t = nval - ntmp; 108 | dist2 = max(dot(t,t)/(stepwidth*stepwidth),0.0); 109 | float n_w = min(exp(-(dist2)/n_phi), 1.0); 110 | 111 | vec4 ptmp = texture2D(uPosition, uv); 112 | t = pval - ptmp; 113 | dist2 = dot(t,t); 114 | float p_w = min(exp(-(dist2)/p_phi), 1.0); 115 | 116 | 117 | float weight = c_w * n_w * p_w; 118 | sum += ctmp * weight * kernel[i]; 119 | 120 | cum_w += weight * kernel[i]; 121 | } 122 | 123 | vec4 color = sum / cum_w; 124 | 125 | gl_FragColor = color; 126 | } 127 | `; 128 | 129 | export { atrous_fs, atrous_vs }; -------------------------------------------------------------------------------- /Cornell box/shaders/radiance.js: -------------------------------------------------------------------------------- 1 | function makeSceneShaders(tot_triangles) { 2 | 3 | window.radiance_vs = ` 4 | varying vec2 vUv; 5 | 6 | void main() { 7 | gl_Position = vec4(position, 1.0); 8 | 9 | vUv = uv; 10 | } 11 | `; 12 | 13 | window.radiance_fs = ` 14 | uniform vec4 uScene[${tot_triangles * 3}]; 15 | // uniform vec3 uScene[6]; 16 | 17 | uniform vec3 uCameraPos; 18 | uniform vec3 uCameraTarget; 19 | uniform float uAspectRatio; 20 | uniform float uRadMult; 21 | uniform float uTime; 22 | uniform float uMirrorIndex; 23 | uniform vec4 uRandom; 24 | 25 | uniform sampler2D uPositionBuffer; 26 | 27 | varying vec2 vUv; 28 | 29 | struct intersectionResult 30 | { 31 | vec3 n; 32 | float t; 33 | bool hit; 34 | float meshIndex; 35 | }; 36 | 37 | 38 | #define PI 3.14159265359 39 | 40 | 41 | bool rayTriangleIntersect(vec3 O, vec3 D, vec3 A, vec3 B, vec3 C, inout float t, inout vec3 normal) { 42 | // Ray-triangle isect: 43 | vec3 E1=B-A; vec3 E2=C-A; 44 | 45 | vec3 N=cross(E1,E2); 46 | normal = normalize(N); 47 | 48 | float det = -dot(D,N); 49 | vec3 AO = O - A; 50 | vec3 DAO = cross(AO,D); 51 | 52 | float u = dot(E2,DAO)/det; 53 | float v = -dot(E1,DAO)/det; 54 | t = dot(AO,N)/det; 55 | 56 | return ( 57 | t > 0.0 && u > 0.0 && v > 0.0 && (u+v) < 1.0 58 | ); 59 | } 60 | 61 | // bool rayTriangleIntersect(vec3 ro, vec3 rd, vec3 v0, vec3 v1, vec3 v2, inout float t) { 62 | // float kEpsilon = 0.0001; 63 | 64 | // // return t; 65 | // vec3 v0v1 = v1 - v0; 66 | // vec3 v0v2 = v2 - v0; 67 | // vec3 pvec = cross(rd, v0v2); 68 | // float det = dot(v0v1, pvec); 69 | 70 | // if (abs(det) < kEpsilon) return false; 71 | 72 | // float invDet = 1.0 / det; 73 | 74 | // vec3 tvec = ro - v0; 75 | // float u = dot(tvec, pvec) * invDet; 76 | // if (u < 0.0 || u > 1.0) return false; 77 | 78 | // vec3 qvec = cross(tvec, v0v1); 79 | // float v = dot(rd, qvec) * invDet; 80 | // if (v < 0.0 || u + v > 1.0) return false; 81 | 82 | // t = dot(v0v2, qvec) * invDet; 83 | 84 | // return true; 85 | // } 86 | 87 | 88 | intersectionResult scene(vec3 ro, vec3 rd) { 89 | // vec3 triangles[6]; 90 | // triangles[0] = vec3(-1.0, 0.0, 0.0); 91 | // triangles[1] = vec3(+1.0, 0.0, 0.0); 92 | // triangles[2] = vec3( 0.0, 1.7, 0.0); 93 | 94 | // triangles[3] = vec3(-17.0, 0.0, 8.0); 95 | // triangles[4] = vec3(+17.0, 0.0, 8.0); 96 | // triangles[5] = vec3( 0.0, 0.0, -13.0); 97 | 98 | 99 | 100 | vec3 normal = vec3(0.0); 101 | float mint = 9999999999.0; 102 | bool hit = false; 103 | float meshIndex = 0.0; 104 | 105 | // for(int i = 0; i < 2; i++) { 106 | for(int i = 0; i < ${tot_triangles}; i++) { 107 | vec3 v0 = uScene[i*3 + 0].xyz; 108 | vec3 v1 = uScene[i*3 + 1].xyz; 109 | vec3 v2 = uScene[i*3 + 2].xyz; 110 | 111 | vec3 n = vec3(0.0); 112 | float t = 999999999.0; 113 | 114 | if(rayTriangleIntersect(ro, rd, v0, v1, v2, t, n)) { 115 | if(t < mint) { 116 | mint = t; 117 | hit = true; 118 | normal = n; 119 | meshIndex = uScene[i*3 + 0].w; 120 | } 121 | } 122 | } 123 | 124 | if(dot(normal, rd) > 0.0) { 125 | normal = -normal; 126 | } 127 | 128 | return intersectionResult( normal, mint, hit, meshIndex ); 129 | } 130 | 131 | // one out, three in 132 | float rand(vec3 p3) 133 | { 134 | p3 = fract(p3 * .1031); 135 | p3 += dot(p3, p3.yzx + 33.33); 136 | return fract((p3.x + p3.y) * p3.z); 137 | } 138 | 139 | // 3 out, 3 in... 140 | vec3 hash33(vec3 p3) 141 | { 142 | p3 = fract(p3 * vec3(.1031, .1030, .0973)); 143 | p3 += dot(p3, p3.yxz+33.33); 144 | return fract((p3.xxy + p3.yxx)*p3.zyx); 145 | 146 | } 147 | 148 | vec3 sampleDiffuseHemisphere(vec3 normal, vec3 pos) { 149 | float theta = 2.0 * PI * rand(pos * 100.0 + uRandom.x * 127.0); 150 | float phi = acos(2.0 * rand(pos * 100.0 + uRandom.y * 127.0) - 1.0); 151 | 152 | vec3 unitSphereSample = vec3( 153 | cos(theta) * sin(phi), 154 | sin(theta) * sin(phi), 155 | cos(phi) 156 | ); 157 | 158 | vec3 np = pos + normal + unitSphereSample; 159 | return normalize(np - pos); 160 | } 161 | 162 | vec3 sampleGlossyHemisphere(vec3 normal, vec3 pos, vec3 dir) { 163 | vec3 wout = reflect(dir, normal); 164 | 165 | float theta = 2.0 * PI * rand(pos * 100.0 + uRandom.x * 127.0); 166 | float phi = acos(2.0 * rand(pos * 100.0 + uRandom.y * 127.0) - 1.0); 167 | 168 | vec3 unitSphereSample = vec3( 169 | cos(theta) * sin(phi), 170 | sin(theta) * sin(phi), 171 | cos(phi) 172 | ); 173 | 174 | vec3 np = pos + wout + unitSphereSample * 0.001; 175 | return normalize(np - pos); 176 | } 177 | 178 | void main() { 179 | vec3 radiance = vec3(0.0); 180 | vec2 ndcuv = (vUv * 2.0 - 1.0) * vec2(uAspectRatio, 1.0); 181 | 182 | // "height normalizer" 183 | float hn = tan(/* IN RADIANTI, NON IN GRADI! */ (PI * 45.0 * 0.5) / 180.0); 184 | ndcuv *= hn; 185 | 186 | 187 | vec3 ro = uCameraPos; 188 | 189 | vec3 w = normalize(uCameraTarget - uCameraPos); 190 | vec3 u = normalize(cross(w, vec3(0.0, 1.0, 0.0))); 191 | vec3 v = normalize(cross(u, w)); 192 | 193 | vec3 d = normalize(vec3(ndcuv, 1.0)); 194 | vec3 nd = normalize( u * d.x + v * d.y + w * d.z ); 195 | 196 | vec3 rd = nd; 197 | 198 | 199 | vec3 posBuff = texture2D(uPositionBuffer, vUv).xyz; 200 | // why posBuff minus rd ? 201 | // remember: the wall's front-faces are culled! 202 | // so the first position that you "see" in the positionBuffer, 203 | // is already far into the scene! 204 | ro = posBuff - rd * 0.01; 205 | 206 | if(posBuff == vec3(0.0)) { 207 | gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0); 208 | return; 209 | } 210 | 211 | 212 | 213 | 214 | vec3 mult = vec3(1.0); 215 | for(int i = 0; i < 4; i++) { 216 | intersectionResult ir = scene(ro, rd); 217 | 218 | if(ir.hit) { 219 | // radiance = vec3(ir.n); 220 | vec3 albedo = vec3(1.0); 221 | 222 | ro = ro + rd * (ir.t - 0.001); 223 | 224 | float cos; 225 | 226 | // if(ir.meshIndex > 2.7 && ir.meshIndex < 3.2) { 227 | if(ir.meshIndex > uMirrorIndex - 0.2 && ir.meshIndex < uMirrorIndex + 0.2) { 228 | rd = sampleGlossyHemisphere(ir.n, ro, rd); 229 | // albedo = vec3(0.86, 0.86, 0.86); 230 | albedo = vec3(1.0, 1.0, 1.0); 231 | cos = 1.0; 232 | } else { 233 | rd = sampleDiffuseHemisphere(ir.n, ro); 234 | cos = dot(rd, ir.n); 235 | } 236 | 237 | if(ir.meshIndex > 14.0) { 238 | // radiance += vec3(6.0, 5.5, 4.5) * mult; 239 | radiance += vec3(6.0) * mult; 240 | } 241 | if(ir.meshIndex == 1.0) { 242 | // albedo = vec3(1.0, 0.3, 0.15); 243 | albedo = vec3(1.0, 0.3, 0.15); 244 | albedo = vec3(1.0, 0.15, 0.05); 245 | } 246 | if(ir.meshIndex == 2.0) { 247 | // albedo = vec3(0.15, 1.0, 0.3); 248 | albedo = vec3(0.6, 1.0, 0.15); 249 | albedo = vec3(0.3, 1.0, 0.08); 250 | } 251 | 252 | 253 | mult *= albedo; 254 | // sometimes the dot is negative (no idea why) so we need to make sure and guard this operation with max(...) 255 | mult *= max(cos, 0.0); 256 | } 257 | } 258 | 259 | 260 | gl_FragColor = vec4(radiance * uRadMult, 1.0); 261 | } 262 | `; 263 | } 264 | 265 | export { makeSceneShaders }; -------------------------------------------------------------------------------- /Cornell box/dependencies/RGBELoader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Nikos M. / https://github.com/foo123/ 3 | */ 4 | 5 | import { 6 | DataTextureLoader, 7 | FloatType, 8 | HalfFloatType, 9 | LinearEncoding, 10 | LinearFilter, 11 | NearestFilter, 12 | RGBEEncoding, 13 | RGBEFormat, 14 | RGBFormat, 15 | UnsignedByteType 16 | } from "./three.module.js"; 17 | 18 | // https://github.com/mrdoob/three.js/issues/5552 19 | // http://en.wikipedia.org/wiki/RGBE_image_format 20 | 21 | var RGBELoader = function ( manager ) { 22 | 23 | DataTextureLoader.call( this, manager ); 24 | 25 | this.type = UnsignedByteType; 26 | 27 | }; 28 | 29 | RGBELoader.prototype = Object.assign( Object.create( DataTextureLoader.prototype ), { 30 | 31 | constructor: RGBELoader, 32 | 33 | // adapted from http://www.graphics.cornell.edu/~bjw/rgbe.html 34 | 35 | parse: function ( buffer ) { 36 | 37 | var 38 | /* return codes for rgbe routines */ 39 | //RGBE_RETURN_SUCCESS = 0, 40 | RGBE_RETURN_FAILURE = - 1, 41 | 42 | /* default error routine. change this to change error handling */ 43 | rgbe_read_error = 1, 44 | rgbe_write_error = 2, 45 | rgbe_format_error = 3, 46 | rgbe_memory_error = 4, 47 | rgbe_error = function ( rgbe_error_code, msg ) { 48 | 49 | switch ( rgbe_error_code ) { 50 | 51 | case rgbe_read_error: console.error( "RGBELoader Read Error: " + ( msg || '' ) ); 52 | break; 53 | case rgbe_write_error: console.error( "RGBELoader Write Error: " + ( msg || '' ) ); 54 | break; 55 | case rgbe_format_error: console.error( "RGBELoader Bad File Format: " + ( msg || '' ) ); 56 | break; 57 | default: 58 | case rgbe_memory_error: console.error( "RGBELoader: Error: " + ( msg || '' ) ); 59 | 60 | } 61 | return RGBE_RETURN_FAILURE; 62 | 63 | }, 64 | 65 | /* offsets to red, green, and blue components in a data (float) pixel */ 66 | //RGBE_DATA_RED = 0, 67 | //RGBE_DATA_GREEN = 1, 68 | //RGBE_DATA_BLUE = 2, 69 | 70 | /* number of floats per pixel, use 4 since stored in rgba image format */ 71 | //RGBE_DATA_SIZE = 4, 72 | 73 | /* flags indicating which fields in an rgbe_header_info are valid */ 74 | RGBE_VALID_PROGRAMTYPE = 1, 75 | RGBE_VALID_FORMAT = 2, 76 | RGBE_VALID_DIMENSIONS = 4, 77 | 78 | NEWLINE = "\n", 79 | 80 | fgets = function ( buffer, lineLimit, consume ) { 81 | 82 | lineLimit = ! lineLimit ? 1024 : lineLimit; 83 | var p = buffer.pos, 84 | i = - 1, len = 0, s = '', chunkSize = 128, 85 | chunk = String.fromCharCode.apply( null, new Uint16Array( buffer.subarray( p, p + chunkSize ) ) ) 86 | ; 87 | while ( ( 0 > ( i = chunk.indexOf( NEWLINE ) ) ) && ( len < lineLimit ) && ( p < buffer.byteLength ) ) { 88 | 89 | s += chunk; len += chunk.length; 90 | p += chunkSize; 91 | chunk += String.fromCharCode.apply( null, new Uint16Array( buffer.subarray( p, p + chunkSize ) ) ); 92 | 93 | } 94 | 95 | if ( - 1 < i ) { 96 | 97 | /*for (i=l-1; i>=0; i--) { 98 | byteCode = m.charCodeAt(i); 99 | if (byteCode > 0x7f && byteCode <= 0x7ff) byteLen++; 100 | else if (byteCode > 0x7ff && byteCode <= 0xffff) byteLen += 2; 101 | if (byteCode >= 0xDC00 && byteCode <= 0xDFFF) i--; //trail surrogate 102 | }*/ 103 | if ( false !== consume ) buffer.pos += len + i + 1; 104 | return s + chunk.slice( 0, i ); 105 | 106 | } 107 | return false; 108 | 109 | }, 110 | 111 | /* minimal header reading. modify if you want to parse more information */ 112 | RGBE_ReadHeader = function ( buffer ) { 113 | 114 | var line, match, 115 | 116 | // regexes to parse header info fields 117 | magic_token_re = /^#\?(\S+)$/, 118 | gamma_re = /^\s*GAMMA\s*=\s*(\d+(\.\d+)?)\s*$/, 119 | exposure_re = /^\s*EXPOSURE\s*=\s*(\d+(\.\d+)?)\s*$/, 120 | format_re = /^\s*FORMAT=(\S+)\s*$/, 121 | dimensions_re = /^\s*\-Y\s+(\d+)\s+\+X\s+(\d+)\s*$/, 122 | 123 | // RGBE format header struct 124 | header = { 125 | 126 | valid: 0, /* indicate which fields are valid */ 127 | 128 | string: '', /* the actual header string */ 129 | 130 | comments: '', /* comments found in header */ 131 | 132 | programtype: 'RGBE', /* listed at beginning of file to identify it after "#?". defaults to "RGBE" */ 133 | 134 | format: '', /* RGBE format, default 32-bit_rle_rgbe */ 135 | 136 | gamma: 1.0, /* image has already been gamma corrected with given gamma. defaults to 1.0 (no correction) */ 137 | 138 | exposure: 1.0, /* a value of 1.0 in an image corresponds to watts/steradian/m^2. defaults to 1.0 */ 139 | 140 | width: 0, height: 0 /* image dimensions, width/height */ 141 | 142 | }; 143 | 144 | if ( buffer.pos >= buffer.byteLength || ! ( line = fgets( buffer ) ) ) { 145 | 146 | return rgbe_error( rgbe_read_error, "no header found" ); 147 | 148 | } 149 | /* if you want to require the magic token then uncomment the next line */ 150 | if ( ! ( match = line.match( magic_token_re ) ) ) { 151 | 152 | return rgbe_error( rgbe_format_error, "bad initial token" ); 153 | 154 | } 155 | header.valid |= RGBE_VALID_PROGRAMTYPE; 156 | header.programtype = match[ 1 ]; 157 | header.string += line + "\n"; 158 | 159 | while ( true ) { 160 | 161 | line = fgets( buffer ); 162 | if ( false === line ) break; 163 | header.string += line + "\n"; 164 | 165 | if ( '#' === line.charAt( 0 ) ) { 166 | 167 | header.comments += line + "\n"; 168 | continue; // comment line 169 | 170 | } 171 | 172 | if ( match = line.match( gamma_re ) ) { 173 | 174 | header.gamma = parseFloat( match[ 1 ], 10 ); 175 | 176 | } 177 | if ( match = line.match( exposure_re ) ) { 178 | 179 | header.exposure = parseFloat( match[ 1 ], 10 ); 180 | 181 | } 182 | if ( match = line.match( format_re ) ) { 183 | 184 | header.valid |= RGBE_VALID_FORMAT; 185 | header.format = match[ 1 ];//'32-bit_rle_rgbe'; 186 | 187 | } 188 | if ( match = line.match( dimensions_re ) ) { 189 | 190 | header.valid |= RGBE_VALID_DIMENSIONS; 191 | header.height = parseInt( match[ 1 ], 10 ); 192 | header.width = parseInt( match[ 2 ], 10 ); 193 | 194 | } 195 | 196 | if ( ( header.valid & RGBE_VALID_FORMAT ) && ( header.valid & RGBE_VALID_DIMENSIONS ) ) break; 197 | 198 | } 199 | 200 | if ( ! ( header.valid & RGBE_VALID_FORMAT ) ) { 201 | 202 | return rgbe_error( rgbe_format_error, "missing format specifier" ); 203 | 204 | } 205 | if ( ! ( header.valid & RGBE_VALID_DIMENSIONS ) ) { 206 | 207 | return rgbe_error( rgbe_format_error, "missing image size specifier" ); 208 | 209 | } 210 | 211 | return header; 212 | 213 | }, 214 | 215 | RGBE_ReadPixels_RLE = function ( buffer, w, h ) { 216 | 217 | var data_rgba, offset, pos, count, byteValue, 218 | scanline_buffer, ptr, ptr_end, i, l, off, isEncodedRun, 219 | scanline_width = w, num_scanlines = h, rgbeStart 220 | ; 221 | 222 | if ( 223 | // run length encoding is not allowed so read flat 224 | ( ( scanline_width < 8 ) || ( scanline_width > 0x7fff ) ) || 225 | // this file is not run length encoded 226 | ( ( 2 !== buffer[ 0 ] ) || ( 2 !== buffer[ 1 ] ) || ( buffer[ 2 ] & 0x80 ) ) 227 | ) { 228 | 229 | // return the flat buffer 230 | return new Uint8Array( buffer ); 231 | 232 | } 233 | 234 | if ( scanline_width !== ( ( buffer[ 2 ] << 8 ) | buffer[ 3 ] ) ) { 235 | 236 | return rgbe_error( rgbe_format_error, "wrong scanline width" ); 237 | 238 | } 239 | 240 | data_rgba = new Uint8Array( 4 * w * h ); 241 | 242 | if ( ! data_rgba || ! data_rgba.length ) { 243 | 244 | return rgbe_error( rgbe_memory_error, "unable to allocate buffer space" ); 245 | 246 | } 247 | 248 | offset = 0; pos = 0; ptr_end = 4 * scanline_width; 249 | rgbeStart = new Uint8Array( 4 ); 250 | scanline_buffer = new Uint8Array( ptr_end ); 251 | 252 | // read in each successive scanline 253 | while ( ( num_scanlines > 0 ) && ( pos < buffer.byteLength ) ) { 254 | 255 | if ( pos + 4 > buffer.byteLength ) { 256 | 257 | return rgbe_error( rgbe_read_error ); 258 | 259 | } 260 | 261 | rgbeStart[ 0 ] = buffer[ pos ++ ]; 262 | rgbeStart[ 1 ] = buffer[ pos ++ ]; 263 | rgbeStart[ 2 ] = buffer[ pos ++ ]; 264 | rgbeStart[ 3 ] = buffer[ pos ++ ]; 265 | 266 | if ( ( 2 != rgbeStart[ 0 ] ) || ( 2 != rgbeStart[ 1 ] ) || ( ( ( rgbeStart[ 2 ] << 8 ) | rgbeStart[ 3 ] ) != scanline_width ) ) { 267 | 268 | return rgbe_error( rgbe_format_error, "bad rgbe scanline format" ); 269 | 270 | } 271 | 272 | // read each of the four channels for the scanline into the buffer 273 | // first red, then green, then blue, then exponent 274 | ptr = 0; 275 | while ( ( ptr < ptr_end ) && ( pos < buffer.byteLength ) ) { 276 | 277 | count = buffer[ pos ++ ]; 278 | isEncodedRun = count > 128; 279 | if ( isEncodedRun ) count -= 128; 280 | 281 | if ( ( 0 === count ) || ( ptr + count > ptr_end ) ) { 282 | 283 | return rgbe_error( rgbe_format_error, "bad scanline data" ); 284 | 285 | } 286 | 287 | if ( isEncodedRun ) { 288 | 289 | // a (encoded) run of the same value 290 | byteValue = buffer[ pos ++ ]; 291 | for ( i = 0; i < count; i ++ ) { 292 | 293 | scanline_buffer[ ptr ++ ] = byteValue; 294 | 295 | } 296 | //ptr += count; 297 | 298 | } else { 299 | 300 | // a literal-run 301 | scanline_buffer.set( buffer.subarray( pos, pos + count ), ptr ); 302 | ptr += count; pos += count; 303 | 304 | } 305 | 306 | } 307 | 308 | 309 | // now convert data from buffer into rgba 310 | // first red, then green, then blue, then exponent (alpha) 311 | l = scanline_width; //scanline_buffer.byteLength; 312 | for ( i = 0; i < l; i ++ ) { 313 | 314 | off = 0; 315 | data_rgba[ offset ] = scanline_buffer[ i + off ]; 316 | off += scanline_width; //1; 317 | data_rgba[ offset + 1 ] = scanline_buffer[ i + off ]; 318 | off += scanline_width; //1; 319 | data_rgba[ offset + 2 ] = scanline_buffer[ i + off ]; 320 | off += scanline_width; //1; 321 | data_rgba[ offset + 3 ] = scanline_buffer[ i + off ]; 322 | offset += 4; 323 | 324 | } 325 | 326 | num_scanlines --; 327 | 328 | } 329 | 330 | return data_rgba; 331 | 332 | }; 333 | 334 | var RGBEByteToRGBFloat = function ( sourceArray, sourceOffset, destArray, destOffset ) { 335 | 336 | var e = sourceArray[ sourceOffset + 3 ]; 337 | var scale = Math.pow( 2.0, e - 128.0 ) / 255.0; 338 | 339 | destArray[ destOffset + 0 ] = sourceArray[ sourceOffset + 0 ] * scale; 340 | destArray[ destOffset + 1 ] = sourceArray[ sourceOffset + 1 ] * scale; 341 | destArray[ destOffset + 2 ] = sourceArray[ sourceOffset + 2 ] * scale; 342 | 343 | }; 344 | 345 | var RGBEByteToRGBHalf = ( function () { 346 | 347 | // Source: http://gamedev.stackexchange.com/questions/17326/conversion-of-a-number-from-single-precision-floating-point-representation-to-a/17410#17410 348 | 349 | var floatView = new Float32Array( 1 ); 350 | var int32View = new Int32Array( floatView.buffer ); 351 | 352 | /* This method is faster than the OpenEXR implementation (very often 353 | * used, eg. in Ogre), with the additional benefit of rounding, inspired 354 | * by James Tursa?s half-precision code. */ 355 | function toHalf( val ) { 356 | 357 | floatView[ 0 ] = val; 358 | var x = int32View[ 0 ]; 359 | 360 | var bits = ( x >> 16 ) & 0x8000; /* Get the sign */ 361 | var m = ( x >> 12 ) & 0x07ff; /* Keep one extra bit for rounding */ 362 | var e = ( x >> 23 ) & 0xff; /* Using int is faster here */ 363 | 364 | /* If zero, or denormal, or exponent underflows too much for a denormal 365 | * half, return signed zero. */ 366 | if ( e < 103 ) return bits; 367 | 368 | /* If NaN, return NaN. If Inf or exponent overflow, return Inf. */ 369 | if ( e > 142 ) { 370 | 371 | bits |= 0x7c00; 372 | /* If exponent was 0xff and one mantissa bit was set, it means NaN, 373 | * not Inf, so make sure we set one mantissa bit too. */ 374 | bits |= ( ( e == 255 ) ? 0 : 1 ) && ( x & 0x007fffff ); 375 | return bits; 376 | 377 | } 378 | 379 | /* If exponent underflows but not too much, return a denormal */ 380 | if ( e < 113 ) { 381 | 382 | m |= 0x0800; 383 | /* Extra rounding may overflow and set mantissa to 0 and exponent 384 | * to 1, which is OK. */ 385 | bits |= ( m >> ( 114 - e ) ) + ( ( m >> ( 113 - e ) ) & 1 ); 386 | return bits; 387 | 388 | } 389 | 390 | bits |= ( ( e - 112 ) << 10 ) | ( m >> 1 ); 391 | /* Extra rounding. An overflow will set mantissa to 0 and increment 392 | * the exponent, which is OK. */ 393 | bits += m & 1; 394 | return bits; 395 | 396 | } 397 | 398 | return function ( sourceArray, sourceOffset, destArray, destOffset ) { 399 | 400 | var e = sourceArray[ sourceOffset + 3 ]; 401 | var scale = Math.pow( 2.0, e - 128.0 ) / 255.0; 402 | 403 | destArray[ destOffset + 0 ] = toHalf( sourceArray[ sourceOffset + 0 ] * scale ); 404 | destArray[ destOffset + 1 ] = toHalf( sourceArray[ sourceOffset + 1 ] * scale ); 405 | destArray[ destOffset + 2 ] = toHalf( sourceArray[ sourceOffset + 2 ] * scale ); 406 | 407 | }; 408 | 409 | } )(); 410 | 411 | var byteArray = new Uint8Array( buffer ); 412 | byteArray.pos = 0; 413 | var rgbe_header_info = RGBE_ReadHeader( byteArray ); 414 | 415 | if ( RGBE_RETURN_FAILURE !== rgbe_header_info ) { 416 | 417 | var w = rgbe_header_info.width, 418 | h = rgbe_header_info.height, 419 | image_rgba_data = RGBE_ReadPixels_RLE( byteArray.subarray( byteArray.pos ), w, h ); 420 | 421 | if ( RGBE_RETURN_FAILURE !== image_rgba_data ) { 422 | 423 | switch ( this.type ) { 424 | 425 | case UnsignedByteType: 426 | 427 | var data = image_rgba_data; 428 | var format = RGBEFormat; // handled as THREE.RGBAFormat in shaders 429 | var type = UnsignedByteType; 430 | break; 431 | 432 | case FloatType: 433 | 434 | var numElements = ( image_rgba_data.length / 4 ) * 3; 435 | var floatArray = new Float32Array( numElements ); 436 | 437 | for ( var j = 0; j < numElements; j ++ ) { 438 | 439 | RGBEByteToRGBFloat( image_rgba_data, j * 4, floatArray, j * 3 ); 440 | 441 | } 442 | 443 | var data = floatArray; 444 | var format = RGBFormat; 445 | var type = FloatType; 446 | break; 447 | 448 | case HalfFloatType: 449 | 450 | var numElements = ( image_rgba_data.length / 4 ) * 3; 451 | var halfArray = new Uint16Array( numElements ); 452 | 453 | for ( var j = 0; j < numElements; j ++ ) { 454 | 455 | RGBEByteToRGBHalf( image_rgba_data, j * 4, halfArray, j * 3 ); 456 | 457 | } 458 | 459 | var data = halfArray; 460 | var format = RGBFormat; 461 | var type = HalfFloatType; 462 | break; 463 | 464 | default: 465 | 466 | console.error( 'THREE.RGBELoader: unsupported type: ', this.type ); 467 | break; 468 | 469 | } 470 | 471 | return { 472 | width: w, height: h, 473 | data: data, 474 | header: rgbe_header_info.string, 475 | gamma: rgbe_header_info.gamma, 476 | exposure: rgbe_header_info.exposure, 477 | format: format, 478 | type: type 479 | }; 480 | 481 | } 482 | 483 | } 484 | 485 | return null; 486 | 487 | }, 488 | 489 | setDataType: function ( value ) { 490 | 491 | this.type = value; 492 | return this; 493 | 494 | }, 495 | 496 | load: function ( url, onLoad, onProgress, onError ) { 497 | 498 | function onLoadCallback( texture, texData ) { 499 | 500 | switch ( texture.type ) { 501 | 502 | case UnsignedByteType: 503 | 504 | texture.encoding = RGBEEncoding; 505 | texture.minFilter = NearestFilter; 506 | texture.magFilter = NearestFilter; 507 | texture.generateMipmaps = false; 508 | texture.flipY = true; 509 | break; 510 | 511 | case FloatType: 512 | 513 | texture.encoding = LinearEncoding; 514 | texture.minFilter = LinearFilter; 515 | texture.magFilter = LinearFilter; 516 | texture.generateMipmaps = false; 517 | texture.flipY = true; 518 | break; 519 | 520 | case HalfFloatType: 521 | 522 | texture.encoding = LinearEncoding; 523 | texture.minFilter = LinearFilter; 524 | texture.magFilter = LinearFilter; 525 | texture.generateMipmaps = false; 526 | texture.flipY = true; 527 | break; 528 | 529 | } 530 | 531 | if ( onLoad ) onLoad( texture, texData ); 532 | 533 | } 534 | 535 | return DataTextureLoader.prototype.load.call( this, url, onLoadCallback, onProgress, onError ); 536 | 537 | } 538 | 539 | } ); 540 | 541 | export { RGBELoader }; 542 | -------------------------------------------------------------------------------- /Cornell box/dependencies/DRACOLoader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Don McCurdy / https://www.donmccurdy.com 3 | */ 4 | 5 | import { 6 | BufferAttribute, 7 | BufferGeometry, 8 | FileLoader, 9 | Loader 10 | } from "./three.module.js"; 11 | 12 | var DRACOLoader = function ( manager ) { 13 | 14 | Loader.call( this, manager ); 15 | 16 | this.decoderPath = ''; 17 | this.decoderConfig = {}; 18 | this.decoderBinary = null; 19 | this.decoderPending = null; 20 | 21 | this.workerLimit = 4; 22 | this.workerPool = []; 23 | this.workerNextTaskID = 1; 24 | this.workerSourceURL = ''; 25 | 26 | this.defaultAttributeIDs = { 27 | position: 'POSITION', 28 | normal: 'NORMAL', 29 | color: 'COLOR', 30 | uv: 'TEX_COORD' 31 | }; 32 | this.defaultAttributeTypes = { 33 | position: 'Float32Array', 34 | normal: 'Float32Array', 35 | color: 'Float32Array', 36 | uv: 'Float32Array' 37 | }; 38 | 39 | }; 40 | 41 | DRACOLoader.prototype = Object.assign( Object.create( Loader.prototype ), { 42 | 43 | constructor: DRACOLoader, 44 | 45 | setDecoderPath: function ( path ) { 46 | 47 | this.decoderPath = path; 48 | 49 | return this; 50 | 51 | }, 52 | 53 | setDecoderConfig: function ( config ) { 54 | 55 | this.decoderConfig = config; 56 | 57 | return this; 58 | 59 | }, 60 | 61 | setWorkerLimit: function ( workerLimit ) { 62 | 63 | this.workerLimit = workerLimit; 64 | 65 | return this; 66 | 67 | }, 68 | 69 | /** @deprecated */ 70 | setVerbosity: function () { 71 | 72 | console.warn( 'THREE.DRACOLoader: The .setVerbosity() method has been removed.' ); 73 | 74 | }, 75 | 76 | /** @deprecated */ 77 | setDrawMode: function () { 78 | 79 | console.warn( 'THREE.DRACOLoader: The .setDrawMode() method has been removed.' ); 80 | 81 | }, 82 | 83 | /** @deprecated */ 84 | setSkipDequantization: function () { 85 | 86 | console.warn( 'THREE.DRACOLoader: The .setSkipDequantization() method has been removed.' ); 87 | 88 | }, 89 | 90 | load: function ( url, onLoad, onProgress, onError ) { 91 | 92 | var loader = new FileLoader( this.manager ); 93 | 94 | loader.setPath( this.path ); 95 | loader.setResponseType( 'arraybuffer' ); 96 | 97 | if ( this.crossOrigin === 'use-credentials' ) { 98 | 99 | loader.setWithCredentials( true ); 100 | 101 | } 102 | 103 | loader.load( url, ( buffer ) => { 104 | 105 | var taskConfig = { 106 | attributeIDs: this.defaultAttributeIDs, 107 | attributeTypes: this.defaultAttributeTypes, 108 | useUniqueIDs: false 109 | }; 110 | 111 | this.decodeGeometry( buffer, taskConfig ) 112 | .then( onLoad ) 113 | .catch( onError ); 114 | 115 | }, onProgress, onError ); 116 | 117 | }, 118 | 119 | /** @deprecated Kept for backward-compatibility with previous DRACOLoader versions. */ 120 | decodeDracoFile: function ( buffer, callback, attributeIDs, attributeTypes ) { 121 | 122 | var taskConfig = { 123 | attributeIDs: attributeIDs || this.defaultAttributeIDs, 124 | attributeTypes: attributeTypes || this.defaultAttributeTypes, 125 | useUniqueIDs: !! attributeIDs 126 | }; 127 | 128 | this.decodeGeometry( buffer, taskConfig ).then( callback ); 129 | 130 | }, 131 | 132 | decodeGeometry: function ( buffer, taskConfig ) { 133 | 134 | // TODO: For backward-compatibility, support 'attributeTypes' objects containing 135 | // references (rather than names) to typed array constructors. These must be 136 | // serialized before sending them to the worker. 137 | for ( var attribute in taskConfig.attributeTypes ) { 138 | 139 | var type = taskConfig.attributeTypes[ attribute ]; 140 | 141 | if ( type.BYTES_PER_ELEMENT !== undefined ) { 142 | 143 | taskConfig.attributeTypes[ attribute ] = type.name; 144 | 145 | } 146 | 147 | } 148 | 149 | // 150 | 151 | var taskKey = JSON.stringify( taskConfig ); 152 | 153 | // Check for an existing task using this buffer. A transferred buffer cannot be transferred 154 | // again from this thread. 155 | if ( DRACOLoader.taskCache.has( buffer ) ) { 156 | 157 | var cachedTask = DRACOLoader.taskCache.get( buffer ); 158 | 159 | if ( cachedTask.key === taskKey ) { 160 | 161 | return cachedTask.promise; 162 | 163 | } else if ( buffer.byteLength === 0 ) { 164 | 165 | // Technically, it would be possible to wait for the previous task to complete, 166 | // transfer the buffer back, and decode again with the second configuration. That 167 | // is complex, and I don't know of any reason to decode a Draco buffer twice in 168 | // different ways, so this is left unimplemented. 169 | throw new Error( 170 | 171 | 'THREE.DRACOLoader: Unable to re-decode a buffer with different ' + 172 | 'settings. Buffer has already been transferred.' 173 | 174 | ); 175 | 176 | } 177 | 178 | } 179 | 180 | // 181 | 182 | var worker; 183 | var taskID = this.workerNextTaskID ++; 184 | var taskCost = buffer.byteLength; 185 | 186 | // Obtain a worker and assign a task, and construct a geometry instance 187 | // when the task completes. 188 | var geometryPending = this._getWorker( taskID, taskCost ) 189 | .then( ( _worker ) => { 190 | 191 | worker = _worker; 192 | 193 | return new Promise( ( resolve, reject ) => { 194 | 195 | worker._callbacks[ taskID ] = { resolve, reject }; 196 | 197 | worker.postMessage( { type: 'decode', id: taskID, taskConfig, buffer }, [ buffer ] ); 198 | 199 | // this.debug(); 200 | 201 | } ); 202 | 203 | } ) 204 | .then( ( message ) => this._createGeometry( message.geometry ) ); 205 | 206 | // Remove task from the task list. 207 | geometryPending 208 | .finally( () => { 209 | 210 | if ( worker && taskID ) { 211 | 212 | this._releaseTask( worker, taskID ); 213 | 214 | // this.debug(); 215 | 216 | } 217 | 218 | } ); 219 | 220 | // Cache the task result. 221 | DRACOLoader.taskCache.set( buffer, { 222 | 223 | key: taskKey, 224 | promise: geometryPending 225 | 226 | } ); 227 | 228 | return geometryPending; 229 | 230 | }, 231 | 232 | _createGeometry: function ( geometryData ) { 233 | 234 | var geometry = new BufferGeometry(); 235 | 236 | if ( geometryData.index ) { 237 | 238 | geometry.setIndex( new BufferAttribute( geometryData.index.array, 1 ) ); 239 | 240 | } 241 | 242 | for ( var i = 0; i < geometryData.attributes.length; i ++ ) { 243 | 244 | var attribute = geometryData.attributes[ i ]; 245 | var name = attribute.name; 246 | var array = attribute.array; 247 | var itemSize = attribute.itemSize; 248 | 249 | geometry.setAttribute( name, new BufferAttribute( array, itemSize ) ); 250 | 251 | } 252 | 253 | return geometry; 254 | 255 | }, 256 | 257 | _loadLibrary: function ( url, responseType ) { 258 | 259 | var loader = new FileLoader( this.manager ); 260 | loader.setPath( this.decoderPath ); 261 | loader.setResponseType( responseType ); 262 | 263 | return new Promise( ( resolve, reject ) => { 264 | 265 | loader.load( url, resolve, undefined, reject ); 266 | 267 | } ); 268 | 269 | }, 270 | 271 | preload: function () { 272 | 273 | this._initDecoder(); 274 | 275 | return this; 276 | 277 | }, 278 | 279 | _initDecoder: function () { 280 | 281 | if ( this.decoderPending ) return this.decoderPending; 282 | 283 | var useJS = typeof WebAssembly !== 'object' || this.decoderConfig.type === 'js'; 284 | var librariesPending = []; 285 | 286 | if ( useJS ) { 287 | 288 | librariesPending.push( this._loadLibrary( 'draco_decoder.js', 'text' ) ); 289 | 290 | } else { 291 | 292 | librariesPending.push( this._loadLibrary( 'draco_wasm_wrapper.js', 'text' ) ); 293 | librariesPending.push( this._loadLibrary( 'draco_decoder.wasm', 'arraybuffer' ) ); 294 | 295 | } 296 | 297 | this.decoderPending = Promise.all( librariesPending ) 298 | .then( ( libraries ) => { 299 | 300 | var jsContent = libraries[ 0 ]; 301 | 302 | if ( ! useJS ) { 303 | 304 | this.decoderConfig.wasmBinary = libraries[ 1 ]; 305 | 306 | } 307 | 308 | var fn = DRACOLoader.DRACOWorker.toString(); 309 | 310 | var body = [ 311 | '/* draco decoder */', 312 | jsContent, 313 | '', 314 | '/* worker */', 315 | fn.substring( fn.indexOf( '{' ) + 1, fn.lastIndexOf( '}' ) ) 316 | ].join( '\n' ); 317 | 318 | this.workerSourceURL = URL.createObjectURL( new Blob( [ body ] ) ); 319 | 320 | } ); 321 | 322 | return this.decoderPending; 323 | 324 | }, 325 | 326 | _getWorker: function ( taskID, taskCost ) { 327 | 328 | return this._initDecoder().then( () => { 329 | 330 | if ( this.workerPool.length < this.workerLimit ) { 331 | 332 | var worker = new Worker( this.workerSourceURL ); 333 | 334 | worker._callbacks = {}; 335 | worker._taskCosts = {}; 336 | worker._taskLoad = 0; 337 | 338 | worker.postMessage( { type: 'init', decoderConfig: this.decoderConfig } ); 339 | 340 | worker.onmessage = function ( e ) { 341 | 342 | var message = e.data; 343 | 344 | switch ( message.type ) { 345 | 346 | case 'decode': 347 | worker._callbacks[ message.id ].resolve( message ); 348 | break; 349 | 350 | case 'error': 351 | worker._callbacks[ message.id ].reject( message ); 352 | break; 353 | 354 | default: 355 | console.error( 'THREE.DRACOLoader: Unexpected message, "' + message.type + '"' ); 356 | 357 | } 358 | 359 | }; 360 | 361 | this.workerPool.push( worker ); 362 | 363 | } else { 364 | 365 | this.workerPool.sort( function ( a, b ) { 366 | 367 | return a._taskLoad > b._taskLoad ? - 1 : 1; 368 | 369 | } ); 370 | 371 | } 372 | 373 | var worker = this.workerPool[ this.workerPool.length - 1 ]; 374 | worker._taskCosts[ taskID ] = taskCost; 375 | worker._taskLoad += taskCost; 376 | return worker; 377 | 378 | } ); 379 | 380 | }, 381 | 382 | _releaseTask: function ( worker, taskID ) { 383 | 384 | worker._taskLoad -= worker._taskCosts[ taskID ]; 385 | delete worker._callbacks[ taskID ]; 386 | delete worker._taskCosts[ taskID ]; 387 | 388 | }, 389 | 390 | debug: function () { 391 | 392 | console.log( 'Task load: ', this.workerPool.map( ( worker ) => worker._taskLoad ) ); 393 | 394 | }, 395 | 396 | dispose: function () { 397 | 398 | for ( var i = 0; i < this.workerPool.length; ++ i ) { 399 | 400 | this.workerPool[ i ].terminate(); 401 | 402 | } 403 | 404 | this.workerPool.length = 0; 405 | 406 | return this; 407 | 408 | } 409 | 410 | } ); 411 | 412 | /* WEB WORKER */ 413 | 414 | DRACOLoader.DRACOWorker = function () { 415 | 416 | var decoderConfig; 417 | var decoderPending; 418 | 419 | onmessage = function ( e ) { 420 | 421 | var message = e.data; 422 | 423 | switch ( message.type ) { 424 | 425 | case 'init': 426 | decoderConfig = message.decoderConfig; 427 | decoderPending = new Promise( function ( resolve/*, reject*/ ) { 428 | 429 | decoderConfig.onModuleLoaded = function ( draco ) { 430 | 431 | // Module is Promise-like. Wrap before resolving to avoid loop. 432 | resolve( { draco: draco } ); 433 | 434 | }; 435 | 436 | DracoDecoderModule( decoderConfig ); 437 | 438 | } ); 439 | break; 440 | 441 | case 'decode': 442 | var buffer = message.buffer; 443 | var taskConfig = message.taskConfig; 444 | decoderPending.then( ( module ) => { 445 | 446 | var draco = module.draco; 447 | var decoder = new draco.Decoder(); 448 | var decoderBuffer = new draco.DecoderBuffer(); 449 | decoderBuffer.Init( new Int8Array( buffer ), buffer.byteLength ); 450 | 451 | try { 452 | 453 | var geometry = decodeGeometry( draco, decoder, decoderBuffer, taskConfig ); 454 | 455 | var buffers = geometry.attributes.map( ( attr ) => attr.array.buffer ); 456 | 457 | if ( geometry.index ) buffers.push( geometry.index.array.buffer ); 458 | 459 | self.postMessage( { type: 'decode', id: message.id, geometry }, buffers ); 460 | 461 | } catch ( error ) { 462 | 463 | console.error( error ); 464 | 465 | self.postMessage( { type: 'error', id: message.id, error: error.message } ); 466 | 467 | } finally { 468 | 469 | draco.destroy( decoderBuffer ); 470 | draco.destroy( decoder ); 471 | 472 | } 473 | 474 | } ); 475 | break; 476 | 477 | } 478 | 479 | }; 480 | 481 | function decodeGeometry( draco, decoder, decoderBuffer, taskConfig ) { 482 | 483 | var attributeIDs = taskConfig.attributeIDs; 484 | var attributeTypes = taskConfig.attributeTypes; 485 | 486 | var dracoGeometry; 487 | var decodingStatus; 488 | 489 | var geometryType = decoder.GetEncodedGeometryType( decoderBuffer ); 490 | 491 | if ( geometryType === draco.TRIANGULAR_MESH ) { 492 | 493 | dracoGeometry = new draco.Mesh(); 494 | decodingStatus = decoder.DecodeBufferToMesh( decoderBuffer, dracoGeometry ); 495 | 496 | } else if ( geometryType === draco.POINT_CLOUD ) { 497 | 498 | dracoGeometry = new draco.PointCloud(); 499 | decodingStatus = decoder.DecodeBufferToPointCloud( decoderBuffer, dracoGeometry ); 500 | 501 | } else { 502 | 503 | throw new Error( 'THREE.DRACOLoader: Unexpected geometry type.' ); 504 | 505 | } 506 | 507 | if ( ! decodingStatus.ok() || dracoGeometry.ptr === 0 ) { 508 | 509 | throw new Error( 'THREE.DRACOLoader: Decoding failed: ' + decodingStatus.error_msg() ); 510 | 511 | } 512 | 513 | var geometry = { index: null, attributes: [] }; 514 | 515 | // Gather all vertex attributes. 516 | for ( var attributeName in attributeIDs ) { 517 | 518 | var attributeType = self[ attributeTypes[ attributeName ] ]; 519 | 520 | var attribute; 521 | var attributeID; 522 | 523 | // A Draco file may be created with default vertex attributes, whose attribute IDs 524 | // are mapped 1:1 from their semantic name (POSITION, NORMAL, ...). Alternatively, 525 | // a Draco file may contain a custom set of attributes, identified by known unique 526 | // IDs. glTF files always do the latter, and `.drc` files typically do the former. 527 | if ( taskConfig.useUniqueIDs ) { 528 | 529 | attributeID = attributeIDs[ attributeName ]; 530 | attribute = decoder.GetAttributeByUniqueId( dracoGeometry, attributeID ); 531 | 532 | } else { 533 | 534 | attributeID = decoder.GetAttributeId( dracoGeometry, draco[ attributeIDs[ attributeName ] ] ); 535 | 536 | if ( attributeID === - 1 ) continue; 537 | 538 | attribute = decoder.GetAttribute( dracoGeometry, attributeID ); 539 | 540 | } 541 | 542 | geometry.attributes.push( decodeAttribute( draco, decoder, dracoGeometry, attributeName, attributeType, attribute ) ); 543 | 544 | } 545 | 546 | // Add index. 547 | if ( geometryType === draco.TRIANGULAR_MESH ) { 548 | 549 | // Generate mesh faces. 550 | var numFaces = dracoGeometry.num_faces(); 551 | var numIndices = numFaces * 3; 552 | var index = new Uint32Array( numIndices ); 553 | var indexArray = new draco.DracoInt32Array(); 554 | 555 | for ( var i = 0; i < numFaces; ++ i ) { 556 | 557 | decoder.GetFaceFromMesh( dracoGeometry, i, indexArray ); 558 | 559 | for ( var j = 0; j < 3; ++ j ) { 560 | 561 | index[ i * 3 + j ] = indexArray.GetValue( j ); 562 | 563 | } 564 | 565 | } 566 | 567 | geometry.index = { array: index, itemSize: 1 }; 568 | 569 | draco.destroy( indexArray ); 570 | 571 | } 572 | 573 | draco.destroy( dracoGeometry ); 574 | 575 | return geometry; 576 | 577 | } 578 | 579 | function decodeAttribute( draco, decoder, dracoGeometry, attributeName, attributeType, attribute ) { 580 | 581 | var numComponents = attribute.num_components(); 582 | var numPoints = dracoGeometry.num_points(); 583 | var numValues = numPoints * numComponents; 584 | var dracoArray; 585 | 586 | var array; 587 | 588 | switch ( attributeType ) { 589 | 590 | case Float32Array: 591 | dracoArray = new draco.DracoFloat32Array(); 592 | decoder.GetAttributeFloatForAllPoints( dracoGeometry, attribute, dracoArray ); 593 | array = new Float32Array( numValues ); 594 | break; 595 | 596 | case Int8Array: 597 | dracoArray = new draco.DracoInt8Array(); 598 | decoder.GetAttributeInt8ForAllPoints( dracoGeometry, attribute, dracoArray ); 599 | array = new Int8Array( numValues ); 600 | break; 601 | 602 | case Int16Array: 603 | dracoArray = new draco.DracoInt16Array(); 604 | decoder.GetAttributeInt16ForAllPoints( dracoGeometry, attribute, dracoArray ); 605 | array = new Int16Array( numValues ); 606 | break; 607 | 608 | case Int32Array: 609 | dracoArray = new draco.DracoInt32Array(); 610 | decoder.GetAttributeInt32ForAllPoints( dracoGeometry, attribute, dracoArray ); 611 | array = new Int32Array( numValues ); 612 | break; 613 | 614 | case Uint8Array: 615 | dracoArray = new draco.DracoUInt8Array(); 616 | decoder.GetAttributeUInt8ForAllPoints( dracoGeometry, attribute, dracoArray ); 617 | array = new Uint8Array( numValues ); 618 | break; 619 | 620 | case Uint16Array: 621 | dracoArray = new draco.DracoUInt16Array(); 622 | decoder.GetAttributeUInt16ForAllPoints( dracoGeometry, attribute, dracoArray ); 623 | array = new Uint16Array( numValues ); 624 | break; 625 | 626 | case Uint32Array: 627 | dracoArray = new draco.DracoUInt32Array(); 628 | decoder.GetAttributeUInt32ForAllPoints( dracoGeometry, attribute, dracoArray ); 629 | array = new Uint32Array( numValues ); 630 | break; 631 | 632 | default: 633 | throw new Error( 'THREE.DRACOLoader: Unexpected attribute type.' ); 634 | 635 | } 636 | 637 | for ( var i = 0; i < numValues; i ++ ) { 638 | 639 | array[ i ] = dracoArray.GetValue( i ); 640 | 641 | } 642 | 643 | draco.destroy( dracoArray ); 644 | 645 | return { 646 | name: attributeName, 647 | array: array, 648 | itemSize: numComponents 649 | }; 650 | 651 | } 652 | 653 | }; 654 | 655 | DRACOLoader.taskCache = new WeakMap(); 656 | 657 | /** Deprecated static methods */ 658 | 659 | /** @deprecated */ 660 | DRACOLoader.setDecoderPath = function () { 661 | 662 | console.warn( 'THREE.DRACOLoader: The .setDecoderPath() method has been removed. Use instance methods.' ); 663 | 664 | }; 665 | 666 | /** @deprecated */ 667 | DRACOLoader.setDecoderConfig = function () { 668 | 669 | console.warn( 'THREE.DRACOLoader: The .setDecoderConfig() method has been removed. Use instance methods.' ); 670 | 671 | }; 672 | 673 | /** @deprecated */ 674 | DRACOLoader.releaseDecoderModule = function () { 675 | 676 | console.warn( 'THREE.DRACOLoader: The .releaseDecoderModule() method has been removed. Use instance methods.' ); 677 | 678 | }; 679 | 680 | /** @deprecated */ 681 | DRACOLoader.getDecoderModule = function () { 682 | 683 | console.warn( 'THREE.DRACOLoader: The .getDecoderModule() method has been removed. Use instance methods.' ); 684 | 685 | }; 686 | 687 | export { DRACOLoader }; 688 | -------------------------------------------------------------------------------- /Cornell box/dependencies/orbitControls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author qiao / https://github.com/qiao 3 | * @author mrdoob / http://mrdoob.com 4 | * @author alteredq / http://alteredqualia.com/ 5 | * @author WestLangley / http://github.com/WestLangley 6 | * @author erich666 / http://erichaines.com 7 | * @author ScieCode / http://github.com/sciecode 8 | */ 9 | 10 | import { 11 | EventDispatcher, 12 | MOUSE, 13 | Quaternion, 14 | Spherical, 15 | TOUCH, 16 | Vector2, 17 | Vector3 18 | } from "./three.module.js"; 19 | 20 | // This set of controls performs orbiting, dollying (zooming), and panning. 21 | // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). 22 | // 23 | // Orbit - left mouse / touch: one-finger move 24 | // Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish 25 | // Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move 26 | 27 | var OrbitControls = function ( object, domElement ) { 28 | 29 | this.object = object; 30 | 31 | this.domElement = ( domElement !== undefined ) ? domElement : document; 32 | 33 | // Set to false to disable this control 34 | this.enabled = true; 35 | 36 | // "target" sets the location of focus, where the object orbits around 37 | this.target = new Vector3(); 38 | 39 | // How far you can dolly in and out ( PerspectiveCamera only ) 40 | this.minDistance = 0; 41 | this.maxDistance = Infinity; 42 | 43 | // How far you can zoom in and out ( OrthographicCamera only ) 44 | this.minZoom = 0; 45 | this.maxZoom = Infinity; 46 | 47 | // How far you can orbit vertically, upper and lower limits. 48 | // Range is 0 to Math.PI radians. 49 | this.minPolarAngle = 0; // radians 50 | this.maxPolarAngle = Math.PI; // radians 51 | 52 | // How far you can orbit horizontally, upper and lower limits. 53 | // If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ]. 54 | this.minAzimuthAngle = - Infinity; // radians 55 | this.maxAzimuthAngle = Infinity; // radians 56 | 57 | // Set to true to enable damping (inertia) 58 | // If damping is enabled, you must call controls.update() in your animation loop 59 | this.enableDamping = false; 60 | this.dampingFactor = 0.05; 61 | 62 | // This option actually enables dollying in and out; left as "zoom" for backwards compatibility. 63 | // Set to false to disable zooming 64 | this.enableZoom = true; 65 | this.zoomSpeed = 1.0; 66 | 67 | // Set to false to disable rotating 68 | this.enableRotate = true; 69 | this.rotateSpeed = 1.0; 70 | 71 | // Set to false to disable panning 72 | this.enablePan = true; 73 | this.panSpeed = 1.0; 74 | this.screenSpacePanning = false; // if true, pan in screen-space 75 | this.keyPanSpeed = 7.0; // pixels moved per arrow key push 76 | 77 | // Set to true to automatically rotate around the target 78 | // If auto-rotate is enabled, you must call controls.update() in your animation loop 79 | this.autoRotate = false; 80 | this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60 81 | 82 | // Set to false to disable use of the keys 83 | this.enableKeys = true; 84 | 85 | // The four arrow keys 86 | this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 }; 87 | 88 | // Mouse buttons 89 | this.mouseButtons = { LEFT: MOUSE.ROTATE, MIDDLE: MOUSE.DOLLY, RIGHT: MOUSE.PAN }; 90 | 91 | // Touch fingers 92 | this.touches = { ONE: TOUCH.ROTATE, TWO: TOUCH.DOLLY_PAN }; 93 | 94 | // for reset 95 | this.target0 = this.target.clone(); 96 | this.position0 = this.object.position.clone(); 97 | this.zoom0 = this.object.zoom; 98 | 99 | 100 | this.lastViewMatrixInverse = null; 101 | 102 | // 103 | // public methods 104 | // 105 | 106 | this.getPolarAngle = function () { 107 | 108 | return spherical.phi; 109 | 110 | }; 111 | 112 | this.getAzimuthalAngle = function () { 113 | 114 | return spherical.theta; 115 | 116 | }; 117 | 118 | this.saveState = function () { 119 | 120 | scope.target0.copy( scope.target ); 121 | scope.position0.copy( scope.object.position ); 122 | scope.zoom0 = scope.object.zoom; 123 | 124 | }; 125 | 126 | this.reset = function () { 127 | 128 | scope.target.copy( scope.target0 ); 129 | scope.object.position.copy( scope.position0 ); 130 | scope.object.zoom = scope.zoom0; 131 | 132 | scope.object.updateProjectionMatrix(); 133 | scope.dispatchEvent( changeEvent ); 134 | 135 | scope.update(); 136 | 137 | state = STATE.NONE; 138 | 139 | }; 140 | 141 | // this method is exposed, but perhaps it would be better if we can make it private... 142 | this.update = function () { 143 | 144 | var offset = new Vector3(); 145 | 146 | // so camera.up is the orbit axis 147 | var quat = new Quaternion().setFromUnitVectors( object.up, new Vector3( 0, 1, 0 ) ); 148 | var quatInverse = quat.clone().inverse(); 149 | 150 | var lastPosition = new Vector3(); 151 | var lastQuaternion = new Quaternion(); 152 | 153 | return function update() { 154 | 155 | this.lastViewMatrixInverse = scope.object.matrixWorldInverse.clone(); 156 | 157 | 158 | var position = scope.object.position; 159 | 160 | offset.copy( position ).sub( scope.target ); 161 | 162 | // rotate offset to "y-axis-is-up" space 163 | offset.applyQuaternion( quat ); 164 | 165 | // angle from z-axis around y-axis 166 | spherical.setFromVector3( offset ); 167 | 168 | if ( scope.autoRotate && state === STATE.NONE ) { 169 | 170 | rotateLeft( getAutoRotationAngle() ); 171 | 172 | } 173 | 174 | if ( scope.enableDamping ) { 175 | 176 | spherical.theta += sphericalDelta.theta * scope.dampingFactor; 177 | spherical.phi += sphericalDelta.phi * scope.dampingFactor; 178 | 179 | } else { 180 | 181 | spherical.theta += sphericalDelta.theta; 182 | spherical.phi += sphericalDelta.phi; 183 | 184 | } 185 | 186 | // restrict theta to be between desired limits 187 | spherical.theta = Math.max( scope.minAzimuthAngle, Math.min( scope.maxAzimuthAngle, spherical.theta ) ); 188 | 189 | // restrict phi to be between desired limits 190 | spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) ); 191 | 192 | spherical.makeSafe(); 193 | 194 | 195 | if( scope.enableDamping ) { 196 | scale += scaleDelta * 4 * scope.dampingFactor; 197 | } 198 | 199 | 200 | spherical.radius *= scale; 201 | 202 | // restrict radius to be between desired limits 203 | spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) ); 204 | 205 | // move target to panned location 206 | 207 | if ( scope.enableDamping === true ) { 208 | 209 | scope.target.addScaledVector( panOffset, scope.dampingFactor ); 210 | 211 | } else { 212 | 213 | scope.target.add( panOffset ); 214 | 215 | } 216 | 217 | offset.setFromSpherical( spherical ); 218 | 219 | // rotate offset back to "camera-up-vector-is-up" space 220 | offset.applyQuaternion( quatInverse ); 221 | 222 | position.copy( scope.target ).add( offset ); 223 | 224 | scope.object.lookAt( scope.target ); 225 | 226 | if ( scope.enableDamping === true ) { 227 | 228 | sphericalDelta.theta *= ( 1 - scope.dampingFactor ); 229 | sphericalDelta.phi *= ( 1 - scope.dampingFactor ); 230 | 231 | panOffset.multiplyScalar( 1 - scope.dampingFactor ); 232 | 233 | scaleDelta *= ( 1 - scope.dampingFactor ); 234 | 235 | } else { 236 | 237 | sphericalDelta.set( 0, 0, 0 ); 238 | 239 | panOffset.set( 0, 0, 0 ); 240 | 241 | scaleDelta = 0; 242 | 243 | } 244 | 245 | scale = 1; 246 | 247 | // update condition is: 248 | // min(camera displacement, camera rotation in radians)^2 > EPS 249 | // using small-angle approximation cos(x/2) = 1 - x^2 / 8 250 | 251 | if ( zoomChanged || 252 | lastPosition.distanceToSquared( scope.object.position ) > EPS || 253 | 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) { 254 | 255 | scope.dispatchEvent( changeEvent ); 256 | 257 | lastPosition.copy( scope.object.position ); 258 | lastQuaternion.copy( scope.object.quaternion ); 259 | zoomChanged = false; 260 | 261 | return true; 262 | 263 | } 264 | 265 | return false; 266 | 267 | }; 268 | 269 | }(); 270 | 271 | this.dispose = function () { 272 | 273 | scope.domElement.removeEventListener( 'contextmenu', onContextMenu, false ); 274 | scope.domElement.removeEventListener( 'mousedown', onMouseDown, false ); 275 | scope.domElement.removeEventListener( 'wheel', onMouseWheel, false ); 276 | 277 | scope.domElement.removeEventListener( 'touchstart', onTouchStart, false ); 278 | scope.domElement.removeEventListener( 'touchend', onTouchEnd, false ); 279 | scope.domElement.removeEventListener( 'touchmove', onTouchMove, false ); 280 | 281 | document.removeEventListener( 'mousemove', onMouseMove, false ); 282 | document.removeEventListener( 'mouseup', onMouseUp, false ); 283 | 284 | window.removeEventListener( 'keydown', onKeyDown, false ); 285 | 286 | //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here? 287 | 288 | }; 289 | 290 | // 291 | // internals 292 | // 293 | 294 | var scope = this; 295 | 296 | var changeEvent = { type: 'change' }; 297 | var startEvent = { type: 'start' }; 298 | var endEvent = { type: 'end' }; 299 | var zoomEvent = { type: 'zoom' }; 300 | 301 | var STATE = { 302 | NONE: - 1, 303 | ROTATE: 0, 304 | DOLLY: 1, 305 | PAN: 2, 306 | TOUCH_ROTATE: 3, 307 | TOUCH_PAN: 4, 308 | TOUCH_DOLLY_PAN: 5, 309 | TOUCH_DOLLY_ROTATE: 6 310 | }; 311 | 312 | var state = STATE.NONE; 313 | 314 | var EPS = 0.000001; 315 | 316 | // current position in spherical coordinates 317 | var spherical = new Spherical(); 318 | var sphericalDelta = new Spherical(); 319 | 320 | var scale = 1; 321 | var scaleDelta = 1; 322 | var panOffset = new Vector3(); 323 | var zoomChanged = false; 324 | 325 | var rotateStart = new Vector2(); 326 | var rotateEnd = new Vector2(); 327 | var rotateDelta = new Vector2(); 328 | 329 | var panStart = new Vector2(); 330 | var panEnd = new Vector2(); 331 | var panDelta = new Vector2(); 332 | 333 | var dollyStart = new Vector2(); 334 | var dollyEnd = new Vector2(); 335 | var dollyDelta = new Vector2(); 336 | 337 | function getAutoRotationAngle() { 338 | 339 | return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; 340 | 341 | } 342 | 343 | function getZoomScale() { 344 | 345 | return Math.pow( 0.95, scope.zoomSpeed ); 346 | 347 | } 348 | 349 | function rotateLeft( angle ) { 350 | 351 | sphericalDelta.theta -= angle; 352 | 353 | } 354 | 355 | function rotateUp( angle ) { 356 | 357 | sphericalDelta.phi -= angle; 358 | 359 | } 360 | 361 | var panLeft = function () { 362 | 363 | var v = new Vector3(); 364 | 365 | return function panLeft( distance, objectMatrix ) { 366 | 367 | v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix 368 | v.multiplyScalar( - distance ); 369 | 370 | panOffset.add( v ); 371 | 372 | }; 373 | 374 | }(); 375 | 376 | var panUp = function () { 377 | 378 | var v = new Vector3(); 379 | 380 | return function panUp( distance, objectMatrix ) { 381 | 382 | if ( scope.screenSpacePanning === true ) { 383 | 384 | v.setFromMatrixColumn( objectMatrix, 1 ); 385 | 386 | } else { 387 | 388 | v.setFromMatrixColumn( objectMatrix, 0 ); 389 | v.crossVectors( scope.object.up, v ); 390 | 391 | } 392 | 393 | v.multiplyScalar( distance ); 394 | 395 | panOffset.add( v ); 396 | 397 | }; 398 | 399 | }(); 400 | 401 | // deltaX and deltaY are in pixels; right and down are positive 402 | var pan = function () { 403 | 404 | var offset = new Vector3(); 405 | 406 | return function pan( deltaX, deltaY ) { 407 | 408 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 409 | 410 | if ( scope.object.isPerspectiveCamera ) { 411 | 412 | // perspective 413 | var position = scope.object.position; 414 | offset.copy( position ).sub( scope.target ); 415 | var targetDistance = offset.length(); 416 | 417 | // half of the fov is center to top of screen 418 | targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); 419 | 420 | // we use only clientHeight here so aspect ratio does not distort speed 421 | panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix ); 422 | panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix ); 423 | 424 | } else if ( scope.object.isOrthographicCamera ) { 425 | 426 | // orthographic 427 | panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix ); 428 | panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix ); 429 | 430 | } else { 431 | 432 | // camera neither orthographic nor perspective 433 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); 434 | scope.enablePan = false; 435 | 436 | } 437 | 438 | }; 439 | 440 | }(); 441 | 442 | function dollyIn( dollyScale ) { 443 | 444 | if ( scope.object.isPerspectiveCamera ) { 445 | 446 | // scale /= dollyScale; 447 | scaleDelta = scale - scale * dollyScale; 448 | 449 | } else if ( scope.object.isOrthographicCamera ) { 450 | 451 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) ); 452 | scope.object.updateProjectionMatrix(); 453 | zoomChanged = true; 454 | 455 | } else { 456 | 457 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 458 | scope.enableZoom = false; 459 | 460 | } 461 | 462 | } 463 | 464 | function dollyOut( dollyScale ) { 465 | 466 | if ( scope.object.isPerspectiveCamera ) { 467 | 468 | // scale *= dollyScale; 469 | scaleDelta = scale - scale / dollyScale; 470 | 471 | } else if ( scope.object.isOrthographicCamera ) { 472 | 473 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) ); 474 | scope.object.updateProjectionMatrix(); 475 | zoomChanged = true; 476 | 477 | } else { 478 | 479 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 480 | scope.enableZoom = false; 481 | 482 | } 483 | 484 | } 485 | 486 | // 487 | // event callbacks - update the object state 488 | // 489 | 490 | function handleMouseDownRotate( event ) { 491 | 492 | rotateStart.set( event.clientX, event.clientY ); 493 | 494 | } 495 | 496 | function handleMouseDownDolly( event ) { 497 | 498 | dollyStart.set( event.clientX, event.clientY ); 499 | 500 | } 501 | 502 | function handleMouseDownPan( event ) { 503 | 504 | panStart.set( event.clientX, event.clientY ); 505 | 506 | } 507 | 508 | function handleMouseMoveRotate( event ) { 509 | 510 | rotateEnd.set( event.clientX, event.clientY ); 511 | 512 | rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); 513 | 514 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 515 | 516 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height 517 | 518 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); 519 | 520 | rotateStart.copy( rotateEnd ); 521 | 522 | // scope.update(); 523 | 524 | } 525 | 526 | function handleMouseMoveDolly( event ) { 527 | 528 | dollyEnd.set( event.clientX, event.clientY ); 529 | 530 | dollyDelta.subVectors( dollyEnd, dollyStart ); 531 | 532 | if ( dollyDelta.y > 0 ) { 533 | 534 | dollyIn( getZoomScale() ); 535 | 536 | } else if ( dollyDelta.y < 0 ) { 537 | 538 | dollyOut( getZoomScale() ); 539 | 540 | } 541 | 542 | dollyStart.copy( dollyEnd ); 543 | 544 | // scope.update(); 545 | 546 | } 547 | 548 | function handleMouseMovePan( event ) { 549 | 550 | panEnd.set( event.clientX, event.clientY ); 551 | 552 | panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); 553 | 554 | pan( panDelta.x, panDelta.y ); 555 | 556 | panStart.copy( panEnd ); 557 | 558 | // scope.update(); 559 | 560 | } 561 | 562 | function handleMouseUp( /*event*/ ) { 563 | 564 | // no-op 565 | 566 | } 567 | 568 | function handleMouseWheel( event ) { 569 | 570 | if ( event.deltaY < 0 ) { 571 | 572 | dollyOut( getZoomScale() ); 573 | 574 | } else if ( event.deltaY > 0 ) { 575 | 576 | dollyIn( getZoomScale() ); 577 | 578 | } 579 | 580 | // scope.update(); 581 | 582 | } 583 | 584 | function handleKeyDown( event ) { 585 | 586 | var needsUpdate = false; 587 | 588 | switch ( event.keyCode ) { 589 | 590 | case scope.keys.UP: 591 | pan( 0, scope.keyPanSpeed ); 592 | needsUpdate = true; 593 | break; 594 | 595 | case scope.keys.BOTTOM: 596 | pan( 0, - scope.keyPanSpeed ); 597 | needsUpdate = true; 598 | break; 599 | 600 | case scope.keys.LEFT: 601 | pan( scope.keyPanSpeed, 0 ); 602 | needsUpdate = true; 603 | break; 604 | 605 | case scope.keys.RIGHT: 606 | pan( - scope.keyPanSpeed, 0 ); 607 | needsUpdate = true; 608 | break; 609 | 610 | } 611 | 612 | if ( needsUpdate ) { 613 | 614 | // prevent the browser from scrolling on cursor keys 615 | event.preventDefault(); 616 | 617 | scope.update(); 618 | 619 | } 620 | 621 | 622 | } 623 | 624 | function handleTouchStartRotate( event ) { 625 | 626 | if ( event.touches.length == 1 ) { 627 | 628 | rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 629 | 630 | } else { 631 | 632 | var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); 633 | var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); 634 | 635 | rotateStart.set( x, y ); 636 | 637 | } 638 | 639 | } 640 | 641 | function handleTouchStartPan( event ) { 642 | 643 | if ( event.touches.length == 1 ) { 644 | 645 | panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 646 | 647 | } else { 648 | 649 | var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); 650 | var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); 651 | 652 | panStart.set( x, y ); 653 | 654 | } 655 | 656 | } 657 | 658 | function handleTouchStartDolly( event ) { 659 | 660 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 661 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 662 | 663 | var distance = Math.sqrt( dx * dx + dy * dy ); 664 | 665 | dollyStart.set( 0, distance ); 666 | 667 | } 668 | 669 | function handleTouchStartDollyPan( event ) { 670 | 671 | if ( scope.enableZoom ) handleTouchStartDolly( event ); 672 | 673 | if ( scope.enablePan ) handleTouchStartPan( event ); 674 | 675 | } 676 | 677 | function handleTouchStartDollyRotate( event ) { 678 | 679 | if ( scope.enableZoom ) handleTouchStartDolly( event ); 680 | 681 | if ( scope.enableRotate ) handleTouchStartRotate( event ); 682 | 683 | } 684 | 685 | function handleTouchMoveRotate( event ) { 686 | 687 | if ( event.touches.length == 1 ) { 688 | 689 | rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 690 | 691 | } else { 692 | 693 | var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); 694 | var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); 695 | 696 | rotateEnd.set( x, y ); 697 | 698 | } 699 | 700 | rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); 701 | 702 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 703 | 704 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height 705 | 706 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); 707 | 708 | rotateStart.copy( rotateEnd ); 709 | 710 | } 711 | 712 | function handleTouchMovePan( event ) { 713 | 714 | if ( event.touches.length == 1 ) { 715 | 716 | panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 717 | 718 | } else { 719 | 720 | var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); 721 | var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); 722 | 723 | panEnd.set( x, y ); 724 | 725 | } 726 | 727 | panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); 728 | 729 | pan( panDelta.x, panDelta.y ); 730 | 731 | panStart.copy( panEnd ); 732 | 733 | } 734 | 735 | function handleTouchMoveDolly( event ) { 736 | 737 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 738 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 739 | 740 | var distance = Math.sqrt( dx * dx + dy * dy ); 741 | 742 | dollyEnd.set( 0, distance ); 743 | 744 | dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) ); 745 | 746 | dollyIn( dollyDelta.y ); 747 | 748 | dollyStart.copy( dollyEnd ); 749 | 750 | } 751 | 752 | function handleTouchMoveDollyPan( event ) { 753 | 754 | if ( scope.enableZoom ) handleTouchMoveDolly( event ); 755 | 756 | if ( scope.enablePan ) handleTouchMovePan( event ); 757 | 758 | } 759 | 760 | function handleTouchMoveDollyRotate( event ) { 761 | 762 | if ( scope.enableZoom ) handleTouchMoveDolly( event ); 763 | 764 | if ( scope.enableRotate ) handleTouchMoveRotate( event ); 765 | 766 | } 767 | 768 | function handleTouchEnd( /*event*/ ) { 769 | 770 | // no-op 771 | 772 | } 773 | 774 | // 775 | // event handlers - FSM: listen for events and reset state 776 | // 777 | 778 | function onMouseDown( event ) { 779 | 780 | if ( scope.enabled === false ) return; 781 | 782 | // Prevent the browser from scrolling. 783 | 784 | event.preventDefault(); 785 | 786 | // Manually set the focus since calling preventDefault above 787 | // prevents the browser from setting it automatically. 788 | 789 | scope.domElement.focus ? scope.domElement.focus() : window.focus(); 790 | 791 | switch ( event.button ) { 792 | 793 | case 0: 794 | 795 | switch ( scope.mouseButtons.LEFT ) { 796 | 797 | case MOUSE.ROTATE: 798 | 799 | if ( event.ctrlKey || event.metaKey || event.shiftKey ) { 800 | 801 | if ( scope.enablePan === false ) return; 802 | 803 | handleMouseDownPan( event ); 804 | 805 | state = STATE.PAN; 806 | 807 | } else { 808 | 809 | if ( scope.enableRotate === false ) return; 810 | 811 | handleMouseDownRotate( event ); 812 | 813 | state = STATE.ROTATE; 814 | 815 | } 816 | 817 | break; 818 | 819 | case MOUSE.PAN: 820 | 821 | if ( event.ctrlKey || event.metaKey || event.shiftKey ) { 822 | 823 | if ( scope.enableRotate === false ) return; 824 | 825 | handleMouseDownRotate( event ); 826 | 827 | state = STATE.ROTATE; 828 | 829 | } else { 830 | 831 | if ( scope.enablePan === false ) return; 832 | 833 | handleMouseDownPan( event ); 834 | 835 | state = STATE.PAN; 836 | 837 | } 838 | 839 | break; 840 | 841 | default: 842 | 843 | state = STATE.NONE; 844 | 845 | } 846 | 847 | break; 848 | 849 | 850 | case 1: 851 | 852 | switch ( scope.mouseButtons.MIDDLE ) { 853 | 854 | case MOUSE.DOLLY: 855 | 856 | if ( scope.enableZoom === false ) return; 857 | 858 | handleMouseDownDolly( event ); 859 | 860 | state = STATE.DOLLY; 861 | 862 | break; 863 | 864 | 865 | default: 866 | 867 | state = STATE.NONE; 868 | 869 | } 870 | 871 | break; 872 | 873 | case 2: 874 | 875 | switch ( scope.mouseButtons.RIGHT ) { 876 | 877 | case MOUSE.ROTATE: 878 | 879 | if ( scope.enableRotate === false ) return; 880 | 881 | handleMouseDownRotate( event ); 882 | 883 | state = STATE.ROTATE; 884 | 885 | break; 886 | 887 | case MOUSE.PAN: 888 | 889 | if ( scope.enablePan === false ) return; 890 | 891 | handleMouseDownPan( event ); 892 | 893 | state = STATE.PAN; 894 | 895 | break; 896 | 897 | default: 898 | 899 | state = STATE.NONE; 900 | 901 | } 902 | 903 | break; 904 | 905 | } 906 | 907 | if ( state !== STATE.NONE ) { 908 | 909 | document.addEventListener( 'mousemove', onMouseMove, false ); 910 | document.addEventListener( 'mouseup', onMouseUp, false ); 911 | 912 | scope.dispatchEvent( startEvent ); 913 | 914 | } 915 | 916 | } 917 | 918 | function onMouseMove( event ) { 919 | 920 | if ( scope.enabled === false ) return; 921 | 922 | event.preventDefault(); 923 | 924 | switch ( state ) { 925 | 926 | case STATE.ROTATE: 927 | 928 | if ( scope.enableRotate === false ) return; 929 | 930 | handleMouseMoveRotate( event ); 931 | 932 | break; 933 | 934 | case STATE.DOLLY: 935 | 936 | if ( scope.enableZoom === false ) return; 937 | 938 | handleMouseMoveDolly( event ); 939 | 940 | break; 941 | 942 | case STATE.PAN: 943 | 944 | if ( scope.enablePan === false ) return; 945 | 946 | handleMouseMovePan( event ); 947 | 948 | break; 949 | 950 | } 951 | 952 | } 953 | 954 | function onMouseUp( event ) { 955 | 956 | if ( scope.enabled === false ) return; 957 | 958 | handleMouseUp( event ); 959 | 960 | document.removeEventListener( 'mousemove', onMouseMove, false ); 961 | document.removeEventListener( 'mouseup', onMouseUp, false ); 962 | 963 | scope.dispatchEvent( endEvent ); 964 | 965 | state = STATE.NONE; 966 | 967 | } 968 | 969 | function onMouseWheel( event ) { 970 | 971 | if ( scope.enabled === false || scope.enableZoom === false || ( state !== STATE.NONE && state !== STATE.ROTATE ) ) return; 972 | 973 | event.preventDefault(); 974 | event.stopPropagation(); 975 | 976 | scope.dispatchEvent( startEvent ); 977 | scope.dispatchEvent( zoomEvent ); 978 | 979 | handleMouseWheel( event ); 980 | 981 | scope.dispatchEvent( endEvent ); 982 | 983 | } 984 | 985 | function onKeyDown( event ) { 986 | 987 | if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return; 988 | 989 | handleKeyDown( event ); 990 | 991 | } 992 | 993 | function onTouchStart( event ) { 994 | 995 | if ( scope.enabled === false ) return; 996 | 997 | event.preventDefault(); 998 | 999 | switch ( event.touches.length ) { 1000 | 1001 | case 1: 1002 | 1003 | switch ( scope.touches.ONE ) { 1004 | 1005 | case TOUCH.ROTATE: 1006 | 1007 | if ( scope.enableRotate === false ) return; 1008 | 1009 | handleTouchStartRotate( event ); 1010 | 1011 | state = STATE.TOUCH_ROTATE; 1012 | 1013 | break; 1014 | 1015 | case TOUCH.PAN: 1016 | 1017 | if ( scope.enablePan === false ) return; 1018 | 1019 | handleTouchStartPan( event ); 1020 | 1021 | state = STATE.TOUCH_PAN; 1022 | 1023 | break; 1024 | 1025 | default: 1026 | 1027 | state = STATE.NONE; 1028 | 1029 | } 1030 | 1031 | break; 1032 | 1033 | case 2: 1034 | 1035 | switch ( scope.touches.TWO ) { 1036 | 1037 | case TOUCH.DOLLY_PAN: 1038 | 1039 | if ( scope.enableZoom === false && scope.enablePan === false ) return; 1040 | 1041 | handleTouchStartDollyPan( event ); 1042 | 1043 | state = STATE.TOUCH_DOLLY_PAN; 1044 | 1045 | break; 1046 | 1047 | case TOUCH.DOLLY_ROTATE: 1048 | 1049 | if ( scope.enableZoom === false && scope.enableRotate === false ) return; 1050 | 1051 | handleTouchStartDollyRotate( event ); 1052 | 1053 | state = STATE.TOUCH_DOLLY_ROTATE; 1054 | 1055 | break; 1056 | 1057 | default: 1058 | 1059 | state = STATE.NONE; 1060 | 1061 | } 1062 | 1063 | break; 1064 | 1065 | default: 1066 | 1067 | state = STATE.NONE; 1068 | 1069 | } 1070 | 1071 | if ( state !== STATE.NONE ) { 1072 | 1073 | scope.dispatchEvent( startEvent ); 1074 | 1075 | } 1076 | 1077 | } 1078 | 1079 | function onTouchMove( event ) { 1080 | 1081 | if ( scope.enabled === false ) return; 1082 | 1083 | event.preventDefault(); 1084 | event.stopPropagation(); 1085 | 1086 | switch ( state ) { 1087 | 1088 | case STATE.TOUCH_ROTATE: 1089 | 1090 | if ( scope.enableRotate === false ) return; 1091 | 1092 | handleTouchMoveRotate( event ); 1093 | 1094 | // scope.update(); 1095 | 1096 | break; 1097 | 1098 | case STATE.TOUCH_PAN: 1099 | 1100 | if ( scope.enablePan === false ) return; 1101 | 1102 | handleTouchMovePan( event ); 1103 | 1104 | // scope.update(); 1105 | 1106 | break; 1107 | 1108 | case STATE.TOUCH_DOLLY_PAN: 1109 | 1110 | if ( scope.enableZoom === false && scope.enablePan === false ) return; 1111 | 1112 | handleTouchMoveDollyPan( event ); 1113 | 1114 | // scope.update(); 1115 | 1116 | break; 1117 | 1118 | case STATE.TOUCH_DOLLY_ROTATE: 1119 | 1120 | if ( scope.enableZoom === false && scope.enableRotate === false ) return; 1121 | 1122 | handleTouchMoveDollyRotate( event ); 1123 | 1124 | // scope.update(); 1125 | 1126 | break; 1127 | 1128 | default: 1129 | 1130 | state = STATE.NONE; 1131 | 1132 | } 1133 | 1134 | } 1135 | 1136 | function onTouchEnd( event ) { 1137 | 1138 | if ( scope.enabled === false ) return; 1139 | 1140 | handleTouchEnd( event ); 1141 | 1142 | scope.dispatchEvent( endEvent ); 1143 | 1144 | state = STATE.NONE; 1145 | 1146 | } 1147 | 1148 | function onContextMenu( event ) { 1149 | 1150 | if ( scope.enabled === false ) return; 1151 | 1152 | event.preventDefault(); 1153 | 1154 | } 1155 | 1156 | // 1157 | 1158 | scope.domElement.addEventListener( 'contextmenu', onContextMenu, false ); 1159 | 1160 | scope.domElement.addEventListener( 'mousedown', onMouseDown, false ); 1161 | scope.domElement.addEventListener( 'wheel', onMouseWheel, false ); 1162 | 1163 | scope.domElement.addEventListener( 'touchstart', onTouchStart, false ); 1164 | scope.domElement.addEventListener( 'touchend', onTouchEnd, false ); 1165 | scope.domElement.addEventListener( 'touchmove', onTouchMove, false ); 1166 | 1167 | window.addEventListener( 'keydown', onKeyDown, false ); 1168 | 1169 | // force an update at start 1170 | 1171 | this.update(); 1172 | 1173 | }; 1174 | 1175 | OrbitControls.prototype = Object.create( EventDispatcher.prototype ); 1176 | OrbitControls.prototype.constructor = OrbitControls; 1177 | 1178 | 1179 | // This set of controls performs orbiting, dollying (zooming), and panning. 1180 | // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). 1181 | // This is very similar to OrbitControls, another set of touch behavior 1182 | // 1183 | // Orbit - right mouse, or left mouse + ctrl/meta/shiftKey / touch: two-finger rotate 1184 | // Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish 1185 | // Pan - left mouse, or arrow keys / touch: one-finger move 1186 | 1187 | var MapControls = function ( object, domElement ) { 1188 | 1189 | OrbitControls.call( this, object, domElement ); 1190 | 1191 | this.mouseButtons.LEFT = MOUSE.PAN; 1192 | this.mouseButtons.RIGHT = MOUSE.ROTATE; 1193 | 1194 | this.touches.ONE = TOUCH.PAN; 1195 | this.touches.TWO = TOUCH.DOLLY_ROTATE; 1196 | 1197 | }; 1198 | 1199 | MapControls.prototype = Object.create( EventDispatcher.prototype ); 1200 | MapControls.prototype.constructor = MapControls; 1201 | 1202 | export { OrbitControls, MapControls }; 1203 | -------------------------------------------------------------------------------- /Cornell box/main.js: -------------------------------------------------------------------------------- 1 | import * as THREE from "./dependencies/three.module.js"; 2 | import { OrbitControls } from "./dependencies/orbitControls.js"; 3 | import { position_fs, position_vs } from "./shaders/position.js"; 4 | import { material_fs, material_vs } from "./shaders/material.js"; 5 | import { normal_fs, normal_vs } from "./shaders/normals.js"; 6 | import { display_fs, display_vs } from "./shaders/display.js"; 7 | import { makeSceneShaders } from "./shaders/radiance.js"; 8 | import { atrous_fs, atrous_vs } from "./shaders/atrous.js"; 9 | import { momentMove_fs, momentMove_vs } from "./shaders/momentMove.js"; 10 | import { historyTest_fs, historyTest_vs, historyAccum_fs, historyAccum_vs } from "./shaders/history.js"; 11 | import { radianceAccum_fs, radianceAccum_vs } from "./shaders/radianceAccum.js"; 12 | import * as dat from './dependencies/dat.gui.js'; 13 | 14 | 15 | window.addEventListener("load", init); 16 | 17 | let scene; 18 | let displayScene; 19 | let camera; 20 | let controls; 21 | let renderer; 22 | let pmremGenerator; 23 | let hdrCubeRenderTarget; 24 | let HDRtexture; 25 | 26 | let positionRT; 27 | let normalRT; 28 | let radianceRT; 29 | let atrousRT; 30 | let momentMoveRT; 31 | let historyRT; 32 | let materialRT; 33 | 34 | let positionBufferMaterial; 35 | let materialBufferMaterial; 36 | let normalBufferMaterial; 37 | let radianceBufferMaterial; 38 | let momentBufferMaterial; 39 | let historyTestMaterial; 40 | let historyAccumMaterial; 41 | let radianceAccumMaterial; 42 | let atrousMaterial; 43 | 44 | let displayQuadMesh; 45 | let mesh; 46 | 47 | let kpress; 48 | let lpress; 49 | let opress; 50 | let ppress; 51 | let npress; 52 | let mpress; 53 | 54 | 55 | function init() { 56 | // let w = new THREE.Vector3(0, 0, -1); 57 | // let u = w.clone().cross(new THREE.Vector3(0, 1, 0)); 58 | // let v = u.clone().cross(w); 59 | // console.log(u); 60 | // console.log(v); 61 | // return; 62 | 63 | renderer = new THREE.WebGLRenderer({ antialias: false }); 64 | renderer.setSize( window.innerWidth, window.innerHeight ); 65 | renderer.toneMapping = THREE.ACESFilmicToneMapping; 66 | renderer.toneMappingExposure = 0.8; 67 | renderer.outputEncoding = THREE.sRGBEncoding; 68 | renderer.autoClear = false; 69 | document.body.appendChild( renderer.domElement ); 70 | 71 | scene = new THREE.Scene(); 72 | displayScene = new THREE.Scene(); 73 | camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 1000 ); 74 | 75 | controls = new OrbitControls( camera, renderer.domElement ); 76 | controls.enableDamping = true; 77 | controls.dampingFactor = 0.0875; 78 | controls.enablePan = true; 79 | controls.panSpeed = 1.0; 80 | controls.screenSpacePanning = true; 81 | 82 | //controls.update() must be called after any manual changes to the camera's transform 83 | camera.position.set( 0, 1, 18 ); 84 | controls.target.set( 0, 0, 0 ); 85 | controls.update(); 86 | 87 | 88 | 89 | let geom = createGeometry(0); 90 | 91 | 92 | 93 | positionRT = new THREE.WebGLRenderTarget(innerWidth, innerHeight, { 94 | magFilter: THREE.NearestFilter, 95 | minFilter: THREE.NearestFilter, 96 | type: THREE.FloatType, 97 | stencilBuffer: false, 98 | }); 99 | 100 | normalRT = new THREE.WebGLRenderTarget(innerWidth, innerHeight, { 101 | magFilter: THREE.NearestFilter, 102 | minFilter: THREE.NearestFilter, 103 | type: THREE.FloatType, 104 | stencilBuffer: false, 105 | }); 106 | 107 | momentMoveRT = new THREE.WebGLRenderTarget(innerWidth, innerHeight, { 108 | magFilter: THREE.NearestFilter, 109 | minFilter: THREE.NearestFilter, 110 | type: THREE.FloatType, 111 | stencilBuffer: false, 112 | }); 113 | 114 | materialRT = new THREE.WebGLRenderTarget(innerWidth, innerHeight, { 115 | magFilter: THREE.NearestFilter, 116 | minFilter: THREE.NearestFilter, 117 | type: THREE.FloatType, 118 | stencilBuffer: false, 119 | }); 120 | 121 | 122 | atrousRT = createDoubleFBO(innerWidth, innerHeight, THREE.NearestFilter); 123 | historyRT = createTripleFBO(innerWidth, innerHeight, THREE.NearestFilter); 124 | radianceRT = createTripleFBO(innerWidth, innerHeight, THREE.NearestFilter); 125 | 126 | 127 | 128 | positionBufferMaterial = new THREE.ShaderMaterial({ 129 | fragmentShader: position_fs, 130 | vertexShader: position_vs, 131 | side: THREE.DoubleSide, 132 | }); 133 | 134 | normalBufferMaterial = new THREE.ShaderMaterial({ 135 | uniforms: { 136 | "uCameraPos": { value: camera.position }, 137 | }, 138 | fragmentShader: normal_fs, 139 | vertexShader: normal_vs, 140 | side: THREE.DoubleSide, 141 | }); 142 | 143 | materialBufferMaterial = new THREE.ShaderMaterial({ 144 | fragmentShader: material_fs, 145 | vertexShader: material_vs, 146 | side: THREE.DoubleSide, 147 | }); 148 | 149 | momentBufferMaterial = new THREE.ShaderMaterial({ 150 | uniforms: { 151 | "uOldModelViewMatrix": { value: new THREE.Matrix4() }, 152 | }, 153 | fragmentShader: momentMove_fs, 154 | vertexShader: momentMove_vs, 155 | side: THREE.DoubleSide, 156 | }); 157 | 158 | radianceBufferMaterial = new THREE.ShaderMaterial({ 159 | uniforms: { 160 | "uScene": { value: geom.uniform }, 161 | 162 | "uRadMult": { value: 1 }, 163 | "uCameraPos": { value: camera.position }, 164 | "uCameraTarget": { value: controls.target }, 165 | "uAspectRatio": { value: innerWidth / innerHeight }, 166 | "uRandom": { value: new THREE.Vector4(0, 0, 0, 0) }, 167 | "uTime": { value: 0 }, 168 | 169 | "uMirrorIndex": { value: 1 }, 170 | 171 | "uPositionBuffer": { type: "t", value: positionRT.texture }, 172 | }, 173 | transparent: true, 174 | blending: THREE.CustomBlending, 175 | blendEquation: THREE.AddEquation, 176 | blendSrc: THREE.OneFactor, 177 | blendDst: THREE.OneFactor, 178 | 179 | fragmentShader: radiance_fs, 180 | vertexShader: radiance_vs, 181 | side: THREE.DoubleSide, 182 | }); 183 | 184 | atrousMaterial = new THREE.ShaderMaterial({ 185 | defines: { 186 | "atrous3x3": true, 187 | }, 188 | uniforms: { 189 | "uRadiance": { type: "t", value: radianceRT.rt3.texture }, 190 | "uNormal": { type: "t", value: normalRT.texture }, 191 | "uPosition": { type: "t", value: positionRT.texture }, 192 | "uMaterial": { type: "t", value: materialRT.texture }, 193 | "uHistoryAccum": { type: "t", value: historyRT.rt3.texture }, 194 | "uFilterHistoryModulation": { value: 0 }, 195 | "uMaxFramesHistory": { value: 0 }, 196 | "uStep": { value: 1.0 }, 197 | "uScreenSize": { value: new THREE.Vector2(innerWidth, innerHeight) }, 198 | "uC_phi": { value: 0.0 }, 199 | "uN_phi": { value: 0.0 }, 200 | "uP_phi": { value: 0.0 }, 201 | }, 202 | fragmentShader: atrous_fs, 203 | vertexShader: atrous_vs, 204 | side: THREE.DoubleSide, 205 | }); 206 | 207 | historyTestMaterial = new THREE.ShaderMaterial({ 208 | uniforms: { 209 | "uNormalBuffer": { type: "t", value: normalRT.texture }, 210 | "uPositionBuffer": { type: "t", value: positionRT.texture }, 211 | "uMomentMove": { type: "t", value: momentMoveRT.texture }, 212 | "uCameraPos": { type: "t", value: camera.position }, 213 | "uInvScreenSize": { value: new THREE.Vector2(1 / innerWidth, 1 / innerHeight) }, 214 | }, 215 | fragmentShader: historyTest_fs, 216 | vertexShader: historyTest_vs, 217 | side: THREE.DoubleSide, 218 | }); 219 | 220 | 221 | historyAccumMaterial = new THREE.ShaderMaterial({ 222 | uniforms: { 223 | "uHistoryTest": { type: "t", value: null }, 224 | "uHistoryAccum": { type: "t", value: null }, 225 | "uMomentMove": { type: "t", value: momentMoveRT.texture }, 226 | }, 227 | fragmentShader: historyAccum_fs, 228 | vertexShader: historyAccum_vs, 229 | side: THREE.DoubleSide, 230 | }); 231 | 232 | radianceAccumMaterial = new THREE.ShaderMaterial({ 233 | uniforms: { 234 | "uCurrentRadiance": { type: "t", value: null }, 235 | "uAccumulatedRadiance": { type: "t", value: null }, 236 | "uHistoryBuffer": { type: "t", value: null }, 237 | "uMomentMoveBuffer": { type: "t", value: null }, 238 | "uMaxFramesHistory": { type: "t", value: null }, 239 | }, 240 | fragmentShader: radianceAccum_fs, 241 | vertexShader: radianceAccum_vs, 242 | side: THREE.DoubleSide, 243 | }); 244 | 245 | window.displayMaterial = new THREE.ShaderMaterial({ 246 | uniforms: { 247 | "uTexture": { type: "t", value: radianceRT.rt3.texture }, 248 | }, 249 | fragmentShader: display_fs, 250 | vertexShader: display_vs, 251 | side: THREE.DoubleSide, 252 | }); 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | // pmremGenerator = new THREE.PMREMGenerator( renderer, "1.44" ); 261 | // // pmremGenerator.compileEquirectangularShader(); 262 | 263 | 264 | // let hdrEquiTexture; 265 | // new RGBELoader() 266 | // .setDataType( THREE.UnsignedByteType ) // alt: FloatType, HalfFloatType 267 | // .load( "envmaps/env.hdr", function ( texture, textureData ) { 268 | 269 | // HDRtexture = texture; 270 | // // hdrEquiTexture = texture; 271 | // hdrCubeRenderTarget = pmremGenerator.fromEquirectangular( texture ); 272 | 273 | // onDownload(); 274 | // }); 275 | 276 | var material = new THREE.MeshBasicMaterial( { color: 0xffffff } ); 277 | mesh = new THREE.Mesh( geom.geometry, material ); 278 | mesh.geometryCULL = geom.geometryCULL; 279 | 280 | scene.add(mesh); 281 | 282 | 283 | 284 | window.addEventListener("keydown", (e) => { 285 | if(e.key == "k") kpress = true; 286 | if(e.key == "l") lpress = true; 287 | if(e.key == "p") ppress = true; 288 | if(e.key == "o") opress = true; 289 | if(e.key == "m") mpress = true; 290 | if(e.key == "n") npress = true; 291 | 292 | }); 293 | window.addEventListener("keyup", (e) => { 294 | if(e.key == "k") kpress = false; 295 | if(e.key == "l") lpress = false; 296 | if(e.key == "p") ppress = false; 297 | if(e.key == "o") opress = false; 298 | if(e.key == "m") mpress = false; 299 | if(e.key == "n") npress = false; 300 | }); 301 | 302 | 303 | 304 | displayQuadMesh = new THREE.Mesh(new THREE.PlaneBufferGeometry(2,2), displayMaterial); 305 | displayScene.add(displayQuadMesh); 306 | 307 | 308 | initGUI(); 309 | animate(0); 310 | } 311 | 312 | function createDoubleFBO(w, h, filtering) { 313 | let rt1 = new THREE.WebGLRenderTarget(w, h, { 314 | type: THREE.FloatType, 315 | minFilter: filtering || THREE.LinearFilter, 316 | magFilter: filtering || THREE.LinearFilter, 317 | wrapS: THREE.ClampToEdgeWrapping, 318 | wrapT: THREE.ClampToEdgeWrapping, 319 | format: THREE.RGBAFormat, 320 | stencilBuffer: false, 321 | anisotropy: 1, 322 | }); 323 | 324 | let rt2 = new THREE.WebGLRenderTarget(w, h, { 325 | type: THREE.FloatType, 326 | minFilter: filtering || THREE.LinearFilter, 327 | magFilter: filtering || THREE.LinearFilter, 328 | wrapS: THREE.ClampToEdgeWrapping, 329 | wrapT: THREE.ClampToEdgeWrapping, 330 | format: THREE.RGBAFormat, 331 | stencilBuffer: false, 332 | anisotropy: 1, 333 | }); 334 | 335 | return { 336 | read: rt1, 337 | write: rt2, 338 | swap: function() { 339 | let temp = this.read; 340 | this.read = this.write; 341 | this.write = temp; 342 | } 343 | }; 344 | } 345 | 346 | function createTripleFBO(w, h, filtering) { 347 | let rt1 = new THREE.WebGLRenderTarget(w, h, { 348 | type: THREE.FloatType, 349 | minFilter: filtering || THREE.LinearFilter, 350 | magFilter: filtering || THREE.LinearFilter, 351 | wrapS: THREE.ClampToEdgeWrapping, 352 | wrapT: THREE.ClampToEdgeWrapping, 353 | format: THREE.RGBAFormat, 354 | stencilBuffer: false, 355 | anisotropy: 1, 356 | }); 357 | 358 | let rt2 = new THREE.WebGLRenderTarget(w, h, { 359 | type: THREE.FloatType, 360 | minFilter: filtering || THREE.LinearFilter, 361 | magFilter: filtering || THREE.LinearFilter, 362 | wrapS: THREE.ClampToEdgeWrapping, 363 | wrapT: THREE.ClampToEdgeWrapping, 364 | format: THREE.RGBAFormat, 365 | stencilBuffer: false, 366 | anisotropy: 1, 367 | }); 368 | 369 | let rt3 = new THREE.WebGLRenderTarget(w, h, { 370 | type: THREE.FloatType, 371 | minFilter: filtering || THREE.LinearFilter, 372 | magFilter: filtering || THREE.LinearFilter, 373 | wrapS: THREE.ClampToEdgeWrapping, 374 | wrapT: THREE.ClampToEdgeWrapping, 375 | format: THREE.RGBAFormat, 376 | stencilBuffer: false, 377 | anisotropy: 1, 378 | }); 379 | 380 | return { 381 | rt1: rt1, 382 | rt2: rt2, 383 | rt3: rt3, 384 | swap_rt2_rt3: function() { 385 | let temp = this.rt2; 386 | this.rt2 = this.rt3; 387 | this.rt3 = temp; 388 | } 389 | }; 390 | } 391 | 392 | let prevMeshGeometryCull; 393 | function animate(now) { 394 | requestAnimationFrame( animate ); 395 | 396 | now *= 0.001; 397 | 398 | 399 | // HAI DOVUTO DISABILITARE SCOPE.UPDATE() DURANTE IL MOUSEMOVE/MOUSEDOWN ETC 400 | // DENTRO LO SCRIPT ORBITCONTROLS.js, 401 | // ALTRIMENTI controls.lastViewMatrixInverse SAREBBE STATA UGUALE ALLA 402 | // CURRENT MATRIX (mentre cliccavi e facevi drag), FACENDO SBALLARE I CALCOLI 403 | // DELL'HISTORYTEST. 404 | // NON HAI DISABILITATO SCOPE.UPDATE() NELL'HANDLING DEI TOUCH-EVENT, QUINDI 405 | // QUESTO PROGETTO 406 | // NON FUNZIONERA' SUI MOBILES FINCHE' NON RIMUOVI SCOPE.UPDATE() ANCHE DA LI' 407 | controls.update(); 408 | 409 | 410 | 411 | let newgeo = createGeometry(now); 412 | let oldgeometryCULL = mesh.geometryCULL; 413 | let oldgeometry = mesh.geometry; 414 | mesh.geometry = newgeo.geometry; 415 | mesh.geometryCULL = newgeo.geometryCULL; 416 | radianceBufferMaterial.uniforms.uScene.value = newgeo.uniform; 417 | radianceBufferMaterial.uniforms.uScene.needsUpdate = true; 418 | radianceBufferMaterial.uniforms.needsUpdate = true; 419 | radianceBufferMaterial.needsUpdate = true; 420 | 421 | 422 | 423 | // we need to create moment buffers BEFORE we update normal/position RTs 424 | // **************** create moment buffers 425 | // are you surprised I'm using the matrixWorldInverse? then think more.. 426 | // are you surprised I'm using the matrixWorldInverse? then think more.. 427 | let oldCameraMatrix = controls.lastViewMatrixInverse; 428 | let mgCULL = createMomentGeometry(newgeo.geometryCULL, oldgeometryCULL); 429 | let mg = createMomentGeometry(newgeo.geometry, oldgeometry); 430 | 431 | momentBufferMaterial.uniforms.uOldModelViewMatrix.value = oldCameraMatrix; 432 | momentBufferMaterial.uniforms.uOldModelViewMatrix.needsUpdate = true; 433 | momentBufferMaterial.uniforms.needsUpdate = true; 434 | momentBufferMaterial.needsUpdate = true; 435 | momentBufferMaterial.side = THREE.FrontSide; 436 | renderer.setRenderTarget(momentMoveRT); 437 | mesh.material = momentBufferMaterial; 438 | renderer.clear(); 439 | mesh.geometry = mgCULL; 440 | renderer.render( scene, camera ); 441 | 442 | renderer.setRenderTarget(momentMoveRT); 443 | mesh.geometry = mg; 444 | momentBufferMaterial.side = THREE.DoubleSide; 445 | renderer.render( scene, camera ); 446 | // reassign the new geometry after we're done here... 447 | mesh.geometry = newgeo.geometry; 448 | // **************** create moment buffers - END 449 | 450 | 451 | 452 | // ************** create history buffer 453 | // on rt1 we add the success vs unsuccess buffer (either +1 or -1) 454 | renderer.setRenderTarget(historyRT.rt1); 455 | renderer.clear(); 456 | mesh.material = historyTestMaterial; 457 | mesh.geometry = newgeo.geometryCULL; 458 | historyTestMaterial.uniforms.uCameraPos.value = camera.position; 459 | historyTestMaterial.side = THREE.FrontSide; 460 | renderer.render( scene, camera ); 461 | 462 | renderer.setRenderTarget(historyRT.rt1); 463 | historyTestMaterial.side = THREE.DoubleSide; 464 | mesh.geometry = newgeo.geometry; 465 | renderer.render( scene, camera ); 466 | 467 | 468 | 469 | historyRT.swap_rt2_rt3(); 470 | // rt2 now holds the previously accumulated values 471 | // rt3 updates the old accumulated values with the new buffer on rt1 472 | renderer.setRenderTarget(historyRT.rt3); 473 | renderer.clear(); 474 | displayQuadMesh.material = historyAccumMaterial; 475 | historyAccumMaterial.uniforms.uHistoryTest.value = historyRT.rt1.texture; 476 | historyAccumMaterial.uniforms.uHistoryAccum.value = historyRT.rt2.texture; 477 | renderer.render( displayScene, camera ); 478 | // ************** create history buffer - END 479 | 480 | 481 | 482 | 483 | // **************** creating buffers 484 | renderer.setRenderTarget(positionRT); 485 | mesh.material = positionBufferMaterial; 486 | positionBufferMaterial.side = THREE.FrontSide; 487 | mesh.geometry = newgeo.geometryCULL; 488 | renderer.clear(); 489 | renderer.render( scene, camera ); 490 | 491 | renderer.setRenderTarget(positionRT); 492 | positionBufferMaterial.side = THREE.DoubleSide; 493 | mesh.geometry = newgeo.geometry; 494 | renderer.render( scene, camera ); 495 | 496 | 497 | 498 | renderer.setRenderTarget(materialRT); 499 | mesh.material = materialBufferMaterial; 500 | mesh.material.side = THREE.FrontSide; 501 | mesh.geometry = newgeo.geometryCULL; 502 | renderer.clear(); 503 | renderer.render( scene, camera ); 504 | 505 | renderer.setRenderTarget(materialRT); 506 | mesh.material.side = THREE.DoubleSide; 507 | mesh.geometry = newgeo.geometry; 508 | renderer.render( scene, camera ); 509 | 510 | 511 | 512 | renderer.setRenderTarget(normalRT); 513 | mesh.material = normalBufferMaterial; 514 | mesh.material.side = THREE.FrontSide; 515 | mesh.geometry = newgeo.geometryCULL; 516 | normalBufferMaterial.uniforms.uCameraPos.value = camera.position; 517 | renderer.clear(); 518 | renderer.render( scene, camera ); 519 | 520 | renderer.setRenderTarget(normalRT); 521 | mesh.geometry = newgeo.geometry; 522 | mesh.material.side = THREE.DoubleSide; 523 | renderer.render( scene, camera ); 524 | 525 | 526 | renderer.setRenderTarget(radianceRT.rt1); 527 | renderer.clear(); 528 | for(let i = 0; i < controller.spp; i++) { 529 | renderer.setRenderTarget(radianceRT.rt1); 530 | radianceBufferMaterial.uniforms.uRadMult.value = 1 / (controller.spp); 531 | radianceBufferMaterial.uniforms.uCameraPos.value = camera.position; 532 | radianceBufferMaterial.uniforms.uCameraTarget.value = controls.target; 533 | radianceBufferMaterial.uniforms.uRandom.value = new THREE.Vector4(Math.random(), Math.random(), Math.random(), Math.random()); 534 | radianceBufferMaterial.uniforms.uTime.value = now; 535 | radianceBufferMaterial.uniforms.uMirrorIndex.value = controller.mirrorIndex; 536 | displayQuadMesh.material = radianceBufferMaterial; 537 | renderer.render(displayScene, camera ); 538 | } 539 | // ************** accumulating radiance 540 | radianceRT.swap_rt2_rt3(); 541 | 542 | renderer.setRenderTarget(radianceRT.rt3); 543 | renderer.clear(); 544 | displayQuadMesh.material = radianceAccumMaterial; 545 | radianceAccumMaterial.uniforms.uCurrentRadiance.value = radianceRT.rt1.texture; 546 | radianceAccumMaterial.uniforms.uAccumulatedRadiance.value = radianceRT.rt2.texture; 547 | radianceAccumMaterial.uniforms.uHistoryBuffer.value = historyRT.rt3.texture; 548 | radianceAccumMaterial.uniforms.uMomentMoveBuffer.value = momentMoveRT.texture; 549 | radianceAccumMaterial.uniforms.uMaxFramesHistory.value = controller.maxFramesHistory; 550 | renderer.render(displayScene, camera ); 551 | // ************** accumulating radiance - END 552 | 553 | 554 | // **************** creating buffers - END 555 | 556 | 557 | 558 | 559 | // **************** atrous 560 | atrousMaterial.uniforms.uN_phi.value = controller.n_phi; 561 | atrousMaterial.uniforms.uP_phi.value = controller.p_phi; 562 | atrousMaterial.uniforms.uC_phi.value = controller.c_phi; 563 | 564 | renderer.setRenderTarget(atrousRT.write); 565 | atrousMaterial.uniforms.uRadiance.value = radianceRT.rt3.texture; 566 | atrousMaterial.uniforms.uHistoryAccum.value = historyRT.rt3.texture; 567 | atrousMaterial.uniforms.uMaxFramesHistory.value = controller.maxFramesHistory; 568 | atrousMaterial.uniforms.uFilterHistoryModulation.value = controller.filterHistoryModulation; 569 | atrousMaterial.uniforms.uStep.value = 1.0; 570 | displayQuadMesh.material = atrousMaterial; 571 | renderer.clear(); 572 | renderer.render(displayScene, camera ); 573 | 574 | for(let i = 0; i < Math.floor(controller.iterations); i++) { 575 | atrousRT.swap(); 576 | 577 | renderer.setRenderTarget(atrousRT.write); 578 | atrousMaterial.uniforms.uRadiance.value = atrousRT.read.texture; 579 | atrousMaterial.uniforms.uStep.value *= controller.stepMultiplier; 580 | atrousMaterial.uniforms.uC_phi.value *= controller.c_phiMultPerIt; 581 | displayQuadMesh.material = atrousMaterial; 582 | renderer.clear(); 583 | renderer.render(displayScene, camera ); 584 | } 585 | atrousRT.swap(); 586 | // **************** atrous - END 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | renderer.setRenderTarget(null); 595 | displayQuadMesh.material = displayMaterial; 596 | // displayQuadMesh.material.uniforms.uTexture.value = radianceRT.rt3.texture; 597 | displayQuadMesh.material.uniforms.uTexture.value = atrousRT.write.texture; 598 | // if(kpress) { 599 | // // displayQuadMesh.material.uniforms.uTexture.value = momentMoveRT.texture; 600 | // // displayQuadMesh.material.uniforms.uTexture.value = normalRT.texture; 601 | // // displayQuadMesh.material.uniforms.uTexture.value = historyRT.rt1.texture; 602 | // // displayQuadMesh.material.uniforms.uTexture.value = historyRT.rt3.texture; 603 | // displayQuadMesh.material.uniforms.uTexture.value = radianceRT.rt3.texture; 604 | // } 605 | 606 | if(kpress) displayQuadMesh.material.uniforms.uTexture.value = radianceRT.rt3.texture; 607 | if(lpress) displayQuadMesh.material.uniforms.uTexture.value = normalRT.texture; 608 | if(opress) displayQuadMesh.material.uniforms.uTexture.value = positionRT.texture; 609 | if(ppress) displayQuadMesh.material.uniforms.uTexture.value = historyRT.rt3.texture; 610 | if(npress) displayQuadMesh.material.uniforms.uTexture.value = momentMoveRT.texture; 611 | if(mpress) displayQuadMesh.material.uniforms.uTexture.value = radianceRT.rt1.texture; 612 | 613 | renderer.clear(); 614 | renderer.render(displayScene, camera); 615 | } 616 | 617 | let controller; 618 | function initGUI() { 619 | 620 | var gui = new dat.GUI(); 621 | 622 | var GUIcontroller = function() { 623 | this.c_phi = 105; 624 | this.n_phi = 0.01; 625 | this.p_phi = 1; 626 | 627 | this.c_phiMultPerIt = 0.34; 628 | 629 | this.stepMultiplier = 1.5; 630 | this.iterations = 10; 631 | 632 | this.atrous5x5 = false; 633 | 634 | this.maxFramesHistory = 10; 635 | this.filterHistoryModulation = 0.54; 636 | this.spp = 3; 637 | this.mirrorIndex = 1; 638 | 639 | this.lowQuality = function() { 640 | this.spp = 1; 641 | this.iterations = 7; 642 | this.filterHistoryModulation = 0.37; 643 | this.stepMultiplier = 1.7; 644 | this.atrous5x5 = false; 645 | this.maxFramesHistory = 10; 646 | this.c_phiMultPerIt = 0.34; 647 | this.c_phi = 105; 648 | this.n_phi = 0.01; 649 | this.p_phi = 1; 650 | 651 | this.updateGUI(); 652 | }; 653 | 654 | this.mediumQuality = function() { 655 | this.spp = 2; 656 | this.iterations = 8; 657 | this.c_phiMultPerIt = 0.36; 658 | this.filterHistoryModulation = 0.42; 659 | this.stepMultiplier = 1.6; 660 | this.c_phi = 105; 661 | this.n_phi = 0.01; 662 | this.p_phi = 1; 663 | this.maxFramesHistory = 10; 664 | this.atrous5x5 = false; 665 | 666 | this.updateGUI(); 667 | }; 668 | 669 | this.highQuality = function() { 670 | this.c_phi = 105; 671 | this.n_phi = 0.01; 672 | this.p_phi = 1; 673 | 674 | this.c_phiMultPerIt = 0.34; 675 | 676 | this.stepMultiplier = 1.5; 677 | this.iterations = 10; 678 | 679 | this.atrous5x5 = false; 680 | 681 | this.maxFramesHistory = 10; 682 | this.filterHistoryModulation = 0.54; 683 | this.spp = 3; 684 | 685 | this.updateGUI(); 686 | } 687 | 688 | this.veryHighQuality = function() { 689 | this.c_phi = 105; 690 | this.n_phi = 0.01; 691 | this.p_phi = 1; 692 | 693 | this.c_phiMultPerIt = 0.34; 694 | 695 | this.stepMultiplier = 1.47; 696 | this.iterations = 10; 697 | 698 | this.atrous5x5 = false; 699 | 700 | this.maxFramesHistory = 7; 701 | this.filterHistoryModulation = 0.35; 702 | this.spp = 5; 703 | 704 | this.updateGUI(); 705 | } 706 | 707 | this.updateGUI = function() { 708 | for(let folder in gui.__folders) { 709 | if(!gui.__folders.hasOwnProperty(folder)) continue; 710 | 711 | for(let j = 0; j < gui.__folders[folder].__controllers.length; j++) { 712 | let property = gui.__folders[folder].__controllers[j].property; 713 | 714 | if(controller.hasOwnProperty(property)) { 715 | gui.__folders[folder].__controllers[j].setValue(controller[property]); 716 | } 717 | } 718 | } 719 | }; 720 | 721 | }; 722 | 723 | controller = new GUIcontroller(); 724 | 725 | 726 | var wff = gui.addFolder('Wavelet Filter'); 727 | var ptf = gui.addFolder('Path Tracer'); 728 | var rpf = gui.addFolder('Reprojection Params'); 729 | var qpf = gui.addFolder('Quality Presets'); 730 | 731 | wff.add(controller, 'c_phi', 0, 200).onChange(function(value) { 732 | atrousMaterial.uniforms.uC_phi.value = value; 733 | }); 734 | wff.add(controller, 'n_phi', 0.01, 30).onChange(function(value) { 735 | atrousMaterial.uniforms.uN_phi.value = value; 736 | }); 737 | wff.add(controller, 'p_phi', 0, 30).onChange(function(value) { 738 | atrousMaterial.uniforms.uP_phi.value = value; 739 | }); 740 | wff.add(controller, 'c_phiMultPerIt', 0, 1); 741 | wff.add(controller, 'stepMultiplier', 0, 5); 742 | wff.add(controller, 'iterations', 0, 10).step(1); 743 | wff.add(controller, 'atrous5x5').onChange(function(value) { 744 | if(value) { 745 | atrousMaterial.defines = { 746 | "atrous5x5": true, 747 | }; 748 | } else { 749 | atrousMaterial.defines = { 750 | "atrous3x3": true, 751 | }; 752 | } 753 | 754 | atrousMaterial.needsUpdate = true; 755 | }); 756 | 757 | ptf.add(controller, 'spp', 1, 10).step(1); 758 | ptf.add(controller, 'mirrorIndex', 1, 4).step(1); 759 | 760 | rpf.add(controller, 'maxFramesHistory', 0, 100).step(1); 761 | rpf.add(controller, 'filterHistoryModulation', 0, 1); 762 | 763 | qpf.add(controller, 'lowQuality'); 764 | qpf.add(controller, 'mediumQuality'); 765 | qpf.add(controller, 'highQuality'); 766 | qpf.add(controller, 'veryHighQuality'); 767 | 768 | 769 | 770 | wff.open(); 771 | ptf.open(); 772 | rpf.open(); 773 | qpf.open(); 774 | } 775 | 776 | 777 | let addt = 18; 778 | let tot_triangles = 14 + addt; 779 | makeSceneShaders(tot_triangles); 780 | // let addt = 0; 781 | let randBuffer = []; 782 | let randBufferTransl = []; 783 | for(let i = 0; i < addt; i++) { 784 | let index = 0; //Math.floor(Math.random() * 4); 785 | 786 | randBuffer.push(Math.random() * 2 - 1, Math.random(), Math.random() * 2 - 1, index); 787 | randBuffer.push(Math.random() * 2 - 1, Math.random(), Math.random() * 2 - 1, index); 788 | randBuffer.push(Math.random() * 2 - 1, Math.random(), Math.random() * 2 - 1, index); 789 | } 790 | for(let i = 0; i < addt; i++) { 791 | randBufferTransl.push( 792 | Math.random() * 2 - 1, 793 | Math.random() * 2 - 1, 794 | Math.random() * 2 - 1 795 | ); 796 | } 797 | function createGeometry(time) { 798 | var geometryCULL = new THREE.BufferGeometry(); 799 | // create a simple square shape. We duplicate the top left and bottom right 800 | // vertices because each vertex needs to appear once per triangle. 801 | var verticesCULL = [ 802 | -5, -5, -5, 1, 803 | -5, +5, -5, 1, 804 | -5, -5, +5, 1, 805 | 806 | -5, +5, +5, 1, 807 | -5, -5, +5, 1, 808 | -5, +5, -5, 1, 809 | 810 | +5, -5, +5, 2, 811 | +5, +5, -5, 2, 812 | +5, -5, -5, 2, 813 | 814 | +5, +5, +5, 2, 815 | +5, +5, -5, 2, 816 | +5, -5, +5, 2, 817 | 818 | 819 | +5, -5, -5, 0, 820 | -5, -5, -5, 0, 821 | -5, -5, +5, 0, 822 | 823 | +5, -5, +5, 0, 824 | +5, -5, -5, 0, 825 | -5, -5, +5, 0, 826 | 827 | +5, +5, -5, 0, 828 | -5, +5, +5, 0, 829 | -5, +5, -5, 0, 830 | 831 | +5, +5, +5, 0, 832 | -5, +5, +5, 0, 833 | +5, +5, -5, 0, 834 | 835 | +5, -5, -5, 3, 836 | -5, +5, -5, 3, 837 | -5, -5, -5, 3, 838 | 839 | -5, +5, -5, 3, 840 | +5, -5, -5, 3, 841 | +5, +5, -5, 3, 842 | 843 | +5, -5, +5, 0, 844 | -5, -5, +5, 0, 845 | -5, +5, +5, 0, 846 | 847 | -5, +5, +5, 0, 848 | +5, +5, +5, 0, 849 | +5, -5, +5, 0, 850 | 851 | // light source, will be back-culled 852 | +3.85, +4.9, -3.85, 15, 853 | -3.85, +4.9, +3.85, 15, 854 | -3.85, +4.9, -3.85, 15, 855 | 856 | +3.85, +4.9, +3.85, 15, 857 | -3.85, +4.9, +3.85, 15, 858 | +3.85, +4.9, -3.85, 15, 859 | ]; 860 | 861 | 862 | verticesCULL = new Float32Array(verticesCULL); 863 | // threejs's vertices wont need the "index" property at the 4th position 864 | let threeVerticesCULL = []; 865 | for(let i = 0; i < verticesCULL.length; i+=4) { 866 | threeVerticesCULL.push(verticesCULL[i+0]); 867 | threeVerticesCULL.push(verticesCULL[i+1]); 868 | threeVerticesCULL.push(verticesCULL[i+2]); 869 | } 870 | threeVerticesCULL = new Float32Array(threeVerticesCULL); 871 | 872 | var normalsCULL = []; 873 | for(let i = 0; i < threeVerticesCULL.length; i+=9) { 874 | let v1 = new THREE.Vector3(threeVerticesCULL[i], threeVerticesCULL[i+1], threeVerticesCULL[i+2]); 875 | let v2 = new THREE.Vector3(threeVerticesCULL[i+3], threeVerticesCULL[i+4], threeVerticesCULL[i+5]); 876 | let v3 = new THREE.Vector3(threeVerticesCULL[i+6], threeVerticesCULL[i+7], threeVerticesCULL[i+8]); 877 | 878 | let v2mv1 = v2.clone().sub(v1); 879 | let v3mv1 = v3.clone().sub(v1); 880 | 881 | let n = v2mv1.cross(v3mv1).normalize(); 882 | normalsCULL.push(n.x, n.y, n.z); 883 | normalsCULL.push(n.x, n.y, n.z); 884 | normalsCULL.push(n.x, n.y, n.z); 885 | } 886 | normalsCULL = new Float32Array(normalsCULL); 887 | 888 | // itemSize = 3 because there are 3 values (components) per vertex 889 | geometryCULL.setAttribute( 'position', new THREE.BufferAttribute( threeVerticesCULL, 3 ) ); 890 | geometryCULL.setAttribute( 'aMaterial', new THREE.BufferAttribute( verticesCULL, 4 ) ); 891 | geometryCULL.setAttribute( 'normal', new THREE.BufferAttribute( normalsCULL, 3 ) ); 892 | 893 | 894 | 895 | 896 | 897 | 898 | 899 | 900 | // NON-culled geometry 901 | var geometry = new THREE.BufferGeometry(); 902 | // create a simple square shape. We duplicate the top left and bottom right 903 | // vertices because each vertex needs to appear once per triangle. 904 | var vertices = [ 905 | 906 | ]; 907 | for(let i = 0; i < addt; i++) { 908 | let scale = 4; 909 | let transScale = 1; 910 | let yOffs = -3; 911 | 912 | 913 | vertices.push(randBuffer[i*12 + 0] * scale + randBufferTransl[i * 3 + 0] * transScale); 914 | vertices.push(randBuffer[i*12 + 1] * scale + randBufferTransl[i * 3 + 1] * transScale + yOffs + Math.sin(time + randBuffer[i*12 + 1] * 149.8776)); 915 | vertices.push(randBuffer[i*12 + 2] * scale + randBufferTransl[i * 3 + 2] * transScale); 916 | vertices.push(randBuffer[i*12 + 3]); 917 | 918 | vertices.push(randBuffer[i*12 + 4] * scale + randBufferTransl[i * 3 + 0] * transScale); 919 | vertices.push(randBuffer[i*12 + 5] * scale + randBufferTransl[i * 3 + 1] * transScale + yOffs + Math.sin(time + randBuffer[i*12 + 5] * 149.8776)); 920 | vertices.push(randBuffer[i*12 + 6] * scale + randBufferTransl[i * 3 + 2] * transScale); 921 | vertices.push(randBuffer[i*12 + 7]); 922 | 923 | vertices.push(randBuffer[i*12 + 8] * scale + randBufferTransl[i * 3 + 0] * transScale); 924 | vertices.push(randBuffer[i*12 + 9] * scale + randBufferTransl[i * 3 + 1] * transScale + yOffs + Math.sin(time + randBuffer[i*12 + 9] * 149.8776)); 925 | vertices.push(randBuffer[i*12 + 10] * scale + randBufferTransl[i * 3 + 2] * transScale); 926 | vertices.push(randBuffer[i*12 + 11]); 927 | 928 | // vertices[(i+2) * 9 + 1] = vertices[(i+2)*9 + 1] + Math.sin(time + randBuffer[(i+2)*9] + 1.2); 929 | // vertices[(i+2) * 9 + 4] = vertices[(i+2)*9 + 4] + Math.sin(time + randBuffer[(i+2)*9] + 3.3); 930 | // vertices[(i+2) * 9 + 7] = vertices[(i+2)*9 + 7] + Math.sin(time + randBuffer[(i+2)*9] + 23.3); 931 | } 932 | vertices = new Float32Array(vertices); 933 | // threejs's vertices wont need the "index" property at the 4th position 934 | let threeVertices = []; 935 | for(let i = 0; i < vertices.length; i+=4) { 936 | threeVertices.push(vertices[i+0]); 937 | threeVertices.push(vertices[i+1]); 938 | threeVertices.push(vertices[i+2]); 939 | } 940 | threeVertices = new Float32Array(threeVertices); 941 | 942 | var normals = []; 943 | for(let i = 0; i < threeVertices.length; i+=9) { 944 | let v1 = new THREE.Vector3(threeVertices[i], threeVertices[i+1], threeVertices[i+2]); 945 | let v2 = new THREE.Vector3(threeVertices[i+3], threeVertices[i+4], threeVertices[i+5]); 946 | let v3 = new THREE.Vector3(threeVertices[i+6], threeVertices[i+7], threeVertices[i+8]); 947 | 948 | let v2mv1 = v2.clone().sub(v1); 949 | let v3mv1 = v3.clone().sub(v1); 950 | 951 | let n = v2mv1.cross(v3mv1).normalize(); 952 | normals.push(n.x, n.y, n.z); 953 | normals.push(n.x, n.y, n.z); 954 | normals.push(n.x, n.y, n.z); 955 | } 956 | normals = new Float32Array(normals); 957 | 958 | // itemSize = 3 because there are 3 values (components) per vertex 959 | geometry.setAttribute( 'position', new THREE.BufferAttribute( threeVertices, 3 ) ); 960 | geometry.setAttribute( 'aMaterial', new THREE.BufferAttribute( vertices, 4 ) ); 961 | geometry.setAttribute( 'normal', new THREE.BufferAttribute( normals, 3 ) ); 962 | 963 | 964 | 965 | 966 | 967 | 968 | 969 | 970 | 971 | 972 | 973 | 974 | 975 | 976 | let uniformGeometry = []; 977 | for(let i = 0; i < verticesCULL.length; i+=4) { 978 | uniformGeometry.push(new THREE.Vector4(verticesCULL[i], verticesCULL[i+1], verticesCULL[i+2], verticesCULL[i+3])); 979 | } 980 | for(let i = 0; i < vertices.length; i+=4) { 981 | uniformGeometry.push(new THREE.Vector4(vertices[i], vertices[i+1], vertices[i+2], vertices[i+3])); 982 | } 983 | 984 | return { 985 | uniform: uniformGeometry, 986 | geometryCULL: geometryCULL, 987 | geometry: geometry 988 | }; 989 | } 990 | 991 | function createMomentGeometry(newgeo, oldgeo) { 992 | var geometry = new THREE.BufferGeometry(); 993 | geometry.setAttribute( 'position', new THREE.BufferAttribute( newgeo.attributes.position.array, 3 )); 994 | geometry.setAttribute( 'oldPosition', new THREE.BufferAttribute( oldgeo.attributes.position.array, 3 )); 995 | 996 | return geometry; 997 | } --------------------------------------------------------------------------------