├── AssetManager.js ├── BlueNoise.js ├── EffectCompositer.js ├── EffectShader.js ├── HorizontalBlurShader.js ├── N8GIPass.js ├── PoissionBlur.js ├── ROADMAP.md ├── VerticalBlurShader.js ├── VoxelColorShader.js ├── VoxelModule.js ├── bluenoise.png ├── coi-serviceworker.js ├── draco ├── draco_decoder.wasm └── draco_wasm_wrapper.js ├── index.html ├── main.js ├── package-lock.json ├── package.json ├── panel.js ├── proxy-headers.json ├── skybox ├── Box_Back.bmp ├── Box_Bottom.bmp ├── Box_Front.bmp ├── Box_Left.bmp ├── Box_Right.bmp └── Box_Top.bmp ├── sponza_cd.glb ├── stats.js ├── statsVoxel.js ├── utils.js └── voxel-worker.js /AssetManager.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'https://cdn.skypack.dev/three@0.150.0'; 2 | import { 3 | GLTFLoader 4 | } from 'https://unpkg.com/three@0.150.0/examples/jsm/loaders/GLTFLoader.js'; 5 | const AssetManager = {}; 6 | AssetManager.gltfLoader = new GLTFLoader(); 7 | AssetManager.audioLoader = new THREE.AudioLoader(); 8 | AssetManager.loadGLTFAsync = (url) => { 9 | return new Promise((resolve, reject) => { 10 | AssetManager.gltfLoader.load(url, obj => { 11 | resolve(obj); 12 | }) 13 | }); 14 | } 15 | 16 | AssetManager.loadAudioAsync = (url) => { 17 | return new Promise((resolve, reject) => { 18 | AssetManager.audioLoader.load(url, (buffer) => { 19 | resolve(buffer); 20 | }); 21 | }) 22 | } 23 | 24 | AssetManager.loadTextureAsync = (url) => { 25 | return new Promise((resolve, reject) => { 26 | THREE.ImageUtils.loadTexture(url, null, (tex) => { 27 | resolve(tex); 28 | }) 29 | }) 30 | } 31 | AssetManager.loadAll = (promiseArr, element, message) => { 32 | let count = promiseArr.length; 33 | let results = []; 34 | element.innerHTML = `${message} (${promiseArr.length - count} / ${promiseArr.length})...` 35 | return new Promise((resolve, reject) => { 36 | promiseArr.forEach((promise, i) => { 37 | promise.then(result => { 38 | results[i] = result; 39 | count--; 40 | element.innerHTML = `${message} (${promiseArr.length - count} / ${promiseArr.length})...` 41 | if (count === 0) { 42 | resolve(results); 43 | } 44 | }) 45 | }) 46 | }); 47 | } 48 | export { AssetManager }; -------------------------------------------------------------------------------- /EffectCompositer.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'https://unpkg.com/three@0.162.0/build/three.module.min.js'; 2 | 3 | const EffectCompositer = { 4 | uniforms: { 5 | sceneDiffuse: { value: null }, 6 | sceneDepth: { value: null }, 7 | sceneAlbedo: { value: null }, 8 | sceneAO: { value: null }, 9 | voxelTexture: { value: null }, 10 | tDiffuse: { value: null }, 11 | tSpecular: { value: null }, 12 | giStrengthMultiplier: { value: 1.0 }, 13 | specularStrengthMultiplier: { value: 1.0 }, 14 | giOnly: { value: false }, 15 | aoEnabled: { value: true }, 16 | background: { value: null }, 17 | viewMatrixInv: { value: new THREE.Matrix4() }, 18 | projectionMatrixInv: { value: new THREE.Matrix4() }, 19 | cameraPos: { value: new THREE.Vector3() } 20 | }, 21 | vertexShader: ` 22 | varying vec2 vUv; 23 | void main() { 24 | vUv = uv; 25 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 26 | } 27 | `, 28 | fragmentShader: ` 29 | uniform sampler2D sceneDiffuse; 30 | uniform sampler2D sceneDepth; 31 | uniform sampler2D sceneAlbedo; 32 | uniform sampler2D sceneAO; 33 | uniform sampler2D tDiffuse; 34 | uniform sampler2D tSpecular; 35 | uniform sampler2D voxelTexture; 36 | uniform samplerCube background; 37 | uniform mat4 viewMatrixInv; 38 | uniform mat4 projectionMatrixInv; 39 | uniform vec3 cameraPos; 40 | uniform float giStrengthMultiplier; 41 | uniform float specularStrengthMultiplier; 42 | uniform bool aoEnabled; 43 | uniform bool giOnly; 44 | varying vec2 vUv; 45 | vec3 getWorldPos(float depth, vec2 coord) { 46 | float z = depth * 2.0 - 1.0; 47 | vec4 clipSpacePosition = vec4(coord * 2.0 - 1.0, z, 1.0); 48 | vec4 viewSpacePosition = projectionMatrixInv * clipSpacePosition; 49 | // Perspective division 50 | viewSpacePosition /= viewSpacePosition.w; 51 | vec4 worldSpacePosition = viewMatrixInv * viewSpacePosition; 52 | return worldSpacePosition.xyz; 53 | } 54 | void main() { 55 | vec4 diffuse = texture2D(sceneDiffuse, vUv); 56 | float depth = texture2D(sceneDepth, vUv).r; 57 | vec4 ao =texture2D(sceneAO, vUv); 58 | if (depth == 1.0) { 59 | gl_FragColor = textureCube(background, normalize(getWorldPos(depth, vUv) - cameraPos)); 60 | return; 61 | } 62 | if (!aoEnabled) { 63 | ao = vec4(1.0); 64 | } 65 | vec4 albedo = texture2D(sceneAlbedo, vUv); 66 | vec4 denoised = texture2D(tDiffuse, vUv); 67 | vec4 specular = texture2D(tSpecular, vUv); 68 | float giStrength = giStrengthMultiplier; 69 | float specularStrength = specularStrengthMultiplier; 70 | gl_FragColor = vec4((diffuse.rgb + denoised.rgb * albedo.rgb * giStrength + specular.rgb * albedo.rgb * specularStrength) * ao.rgb, 1.0); 71 | if (giOnly) { 72 | gl_FragColor = vec4(giStrength * denoised.rgb * ao.rgb, 1.0); 73 | } 74 | 75 | } 76 | ` 77 | }; 78 | export { EffectCompositer }; -------------------------------------------------------------------------------- /EffectShader.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'https://unpkg.com/three@0.162.0/build/three.module.min.js'; 2 | const EffectShader = { 3 | 4 | uniforms: { 5 | 6 | 'sceneDiffuse': { value: null }, 7 | 'sceneDepth': { value: null }, 8 | 'sceneNormal': { value: null }, 9 | 'sceneAlbedo': { value: null }, 10 | 'bluenoise': { value: null }, 11 | 'skybox': { value: null }, 12 | 'projMat': { value: new THREE.Matrix4() }, 13 | 'viewMat': { value: new THREE.Matrix4() }, 14 | 'projectionMatrixInv': { value: new THREE.Matrix4() }, 15 | 'viewMatrixInv': { value: new THREE.Matrix4() }, 16 | 'cameraPos': { value: new THREE.Vector3() }, 17 | 'resolution': { value: new THREE.Vector2() }, 18 | 'time': { value: 0.0 }, 19 | 'voxelTexture': { value: null }, 20 | 'voxelColor1': { value: null }, 21 | 'voxelColor2': { value: null }, 22 | 'voxelColorTextureSize': { value: 0 }, 23 | 'boxSize': { value: new THREE.Vector3(1, 1, 1) }, 24 | 'boxCenter': { value: new THREE.Vector3(0, 0, 0) }, 25 | 'voxelAmount': { value: new THREE.Vector3(1, 1, 1) }, 26 | 'debugVoxels': { value: false }, 27 | 'roughness': { value: 1.0 }, 28 | 'samples': { value: 1.0 }, 29 | 'sceneMaterial': { value: null } 30 | }, 31 | 32 | vertexShader: /* glsl */ ` 33 | varying vec2 vUv; 34 | void main() { 35 | vUv = uv; 36 | gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); 37 | }`, 38 | 39 | fragmentShader: /* glsl */ ` 40 | #include 41 | layout(location = 1) out vec4 specular; 42 | uniform highp sampler2D sceneDiffuse; 43 | uniform highp sampler2D sceneDepth; 44 | uniform highp sampler2D sceneAlbedo; 45 | uniform highp sampler2D sceneNormal; 46 | uniform highp sampler2D sceneMaterial; 47 | uniform highp sampler2D bluenoise; 48 | uniform highp isampler3D voxelTexture; 49 | uniform highp samplerCube skybox; 50 | uniform highp usampler2D voxelColor1; 51 | uniform highp usampler2D voxelColor2; 52 | uniform int voxelColorTextureSize; 53 | uniform mat4 projMat; 54 | uniform mat4 viewMat; 55 | uniform highp mat4 projectionMatrixInv; 56 | uniform highp mat4 viewMatrixInv; 57 | uniform vec3 voxelAmount; 58 | uniform vec3 cameraPos; 59 | uniform vec2 resolution; 60 | uniform float time; 61 | uniform float roughness; 62 | uniform bool debugVoxels; 63 | uniform float samples; 64 | uniform vec3 boxSize; 65 | uniform vec3 boxCenter; 66 | varying vec2 vUv; 67 | struct Ray { 68 | highp vec3 origin; 69 | highp vec3 direction; 70 | }; 71 | struct RayHit { 72 | highp vec3 normal; 73 | highp ivec3 voxelPos; 74 | bool hit; 75 | }; 76 | 77 | Ray createRay(vec3 origin, vec3 direction) { 78 | Ray ray; 79 | ray.origin = origin; 80 | ray.direction = direction; 81 | return ray; 82 | } 83 | Ray createCameraRay(vec2 uv) { 84 | vec3 origin = (viewMatrixInv * vec4(0.0, 0.0, 0.0, 1.0)).xyz; 85 | vec3 direction = (projectionMatrixInv * vec4(uv, 0.0, 1.0)).xyz; 86 | direction = (viewMatrixInv * vec4(direction, 0.0)).xyz; 87 | direction = normalize(direction); 88 | return createRay(origin, direction); 89 | } 90 | vec2 rayBoxDist(vec3 boundsMin, vec3 boundsMax, vec3 rayOrigin, vec3 rayDir) { 91 | vec3 t0 = (boundsMin - rayOrigin) / rayDir; 92 | vec3 t1 = (boundsMax - rayOrigin) / rayDir; 93 | vec3 tmin = min(t0, t1); 94 | vec3 tmax = max(t0, t1); 95 | 96 | float distA = max(max(tmin.x, tmin.y), tmin.z); 97 | float distB = min(tmax.x, min(tmax.y, tmax.z)); 98 | 99 | float distToBox = max(0.0, distA); 100 | float distInsideBox = max(0.0, distB - distToBox); 101 | return vec2(distToBox, distInsideBox); 102 | } 103 | RayHit voxelCast(vec3 startPos, Ray ray, float dist) { 104 | vec3 deltaDist = abs(vec3(length(ray.direction)) / ray.direction); 105 | ivec3 rayStep = ivec3(sign(ray.direction)); 106 | ivec3 voxelPos = ivec3(floor(startPos)); 107 | ivec3 endPos = ivec3(floor(startPos + ray.direction * dist)); 108 | vec3 sideDist = (sign(ray.direction) * (vec3(voxelPos) - startPos) + (sign(ray.direction) * 0.5) + 0.5) * deltaDist; 109 | bvec3 mask; 110 | vec3 cushion = 1.0 / voxelAmount; 111 | vec3 invVoxelAmount = 1.0 / voxelAmount; 112 | bool hit = false; 113 | ivec3 minBound = ivec3(0); 114 | ivec3 maxBound = ivec3(voxelAmount) - 1; 115 | int maxSteps = 116 | (abs(endPos.x - voxelPos.x) + 117 | abs(endPos.y - voxelPos.y) + 118 | abs(endPos.z - voxelPos.z)); 119 | 120 | for(int i = 0; i < maxSteps; i++) { 121 | int voxel = texelFetch(voxelTexture, voxelPos, 0).r; 122 | if (voxel >= 0) { 123 | hit = true; 124 | break; 125 | } 126 | mask = lessThanEqual(sideDist.xyz, min(sideDist.yzx, sideDist.zxy)); 127 | sideDist += vec3(mask) * deltaDist; 128 | voxelPos += ivec3(mask) * rayStep; 129 | } 130 | if (hit) { 131 | 132 | vec3 normal = vec3(0.0); 133 | if (mask.x) { 134 | normal = vec3(-sign(rayStep.x), 0.0, 0.0); 135 | } else if (mask.y) { 136 | normal = vec3(0.0, -sign(rayStep.y), 0.0); 137 | } else { 138 | normal = vec3(0.0, 0.0, -sign(rayStep.z)); 139 | } 140 | 141 | return RayHit(normal, (voxelPos), true); 142 | } else { 143 | return RayHit(vec3(-1.0), ivec3(-1), false); 144 | } 145 | } 146 | vec3 toVoxelSpace(vec3 pos) { 147 | pos -= boxCenter; 148 | pos += boxSize / 2.0; 149 | pos *= voxelAmount / boxSize; 150 | return pos; 151 | } 152 | vec3 toWorldSpace(vec3 pos) { 153 | pos *= boxSize / voxelAmount; 154 | pos -= boxSize / 2.0; 155 | pos += boxCenter; 156 | return pos; 157 | } 158 | uint quantizeToBits(float num, float m, float bitsPowTwo) { 159 | num = clamp(num, 0.0, m); 160 | return uint(bitsPowTwo * sqrt(num / m)); 161 | } 162 | float unquantizeToBits(uint num, float m, float bitsPowTwo) { 163 | float t = float(num) / bitsPowTwo; 164 | return (t * t * m); 165 | } 166 | uint packThreeBytes(vec3 light) { 167 | float maxNum = 10.0; 168 | float bitsPowTwo = 1023.0; 169 | uint r = quantizeToBits(light.r, maxNum, bitsPowTwo); 170 | uint g = quantizeToBits(light.g, maxNum, bitsPowTwo); 171 | uint b = quantizeToBits(light.b, maxNum, bitsPowTwo); 172 | 173 | return r << 20 | g << 10 | b; 174 | } 175 | vec3 unpackRGB(uint packedInt) { 176 | float maxNum = 10.0; 177 | float bitsPowTwo = 1023.0; 178 | float r = unquantizeToBits(packedInt >> 20u, maxNum, bitsPowTwo); 179 | float g = unquantizeToBits((packedInt >> 10u) & 1023u, maxNum, bitsPowTwo); 180 | float b = unquantizeToBits(packedInt & 1023u, maxNum, bitsPowTwo); 181 | return vec3(r, g, b); 182 | 183 | } 184 | vec3 getVoxelColor(ivec3 voxelPos, vec3 accessDir) { 185 | ivec3 VOXEL_AMOUNT = ivec3(voxelAmount); 186 | int index = voxelPos.x + voxelPos.y * VOXEL_AMOUNT.x + voxelPos.z * VOXEL_AMOUNT.x * VOXEL_AMOUNT.y; 187 | int sampleY = index / voxelColorTextureSize; 188 | int sampleX = index - sampleY * voxelColorTextureSize; 189 | uvec4 color = texelFetch(voxelColor1, ivec2(sampleX, sampleY), 0); 190 | uvec4 color2 = texelFetch(voxelColor2, ivec2(sampleX, sampleY), 0); 191 | vec3[6] cardinals = vec3[6]( 192 | vec3(1.0, 0.0, 0.0), 193 | vec3(-1.0, 0.0, 0.0), 194 | vec3(0.0, 1.0, 0.0), 195 | vec3(0.0, -1.0, 0.0), 196 | vec3(0.0, 0.0, 1.0), 197 | vec3(0.0, 0.0, -1.0) 198 | ); 199 | vec3[6] accumulatedLight; 200 | accumulatedLight[0] = unpackRGB(color.r).rgb; 201 | accumulatedLight[1] = unpackRGB(color.g).rgb; 202 | accumulatedLight[2] = unpackRGB(color.b).rgb; 203 | accumulatedLight[3] = unpackRGB(color2.r).rgb; 204 | accumulatedLight[4] = unpackRGB(color2.g).rgb; 205 | accumulatedLight[5] = unpackRGB(color2.b).rgb; 206 | 207 | vec3 accumulatedColor = vec3(0.0); 208 | float w = 0.0; 209 | vec3 dotAccessDir = -accessDir; 210 | for (int i = 0; i < 6; i++) { 211 | float dotProduct = max(dot(cardinals[i], dotAccessDir), 0.0); 212 | accumulatedColor += accumulatedLight[i] * dotProduct; 213 | } 214 | return accumulatedColor; 215 | } 216 | vec3 voxelIntersectPos(vec3 voxel, Ray ray) { 217 | vec3 hitPos = toWorldSpace(voxel); 218 | vec2 voxelIntersectData = rayBoxDist(floor(hitPos), hitPos + vec3(1.0, 1.0, 1.0), ray.origin, ray.direction); 219 | vec3 intersectPos = ray.origin + ray.direction * voxelIntersectData.x; 220 | return intersectPos; 221 | } 222 | RayHit raycast(Ray ray) { 223 | vec3 startPos = toVoxelSpace(ray.origin); 224 | vec3 voxelSpaceDir = ray.direction; 225 | vec3 voxelRatioResults = voxelAmount / boxSize; 226 | voxelSpaceDir *= voxelRatioResults; 227 | voxelSpaceDir = normalize(voxelSpaceDir); 228 | vec2 voxelBoxDist = rayBoxDist(vec3(0.0), voxelAmount, startPos, voxelSpaceDir); 229 | float distToBox = voxelBoxDist.x + 0.0001; 230 | float distInsideBox = voxelBoxDist.y; 231 | startPos += distToBox * voxelSpaceDir; 232 | RayHit result = voxelCast(startPos, Ray( 233 | startPos, 234 | voxelSpaceDir 235 | ), distInsideBox); 236 | return result; 237 | 238 | 239 | } 240 | 241 | 242 | 243 | vec3 getWorldPos(float depth, vec2 coord) { 244 | float z = depth * 2.0 - 1.0; 245 | vec4 clipSpacePosition = vec4(coord * 2.0 - 1.0, z, 1.0); 246 | vec4 viewSpacePosition = projectionMatrixInv * clipSpacePosition; 247 | // Perspective division 248 | vec4 worldSpacePosition = viewSpacePosition; 249 | worldSpacePosition.xyz /= worldSpacePosition.w; 250 | return worldSpacePosition.xyz; 251 | } 252 | vec3 computeNormal(vec3 worldPos, vec2 vUv) { 253 | ivec2 p = ivec2(vUv * resolution); 254 | float c0 = texelFetch(sceneDepth, p, 0).x; 255 | float l2 = texelFetch(sceneDepth, p - ivec2(2, 0), 0).x; 256 | float l1 = texelFetch(sceneDepth, p - ivec2(1, 0), 0).x; 257 | float r1 = texelFetch(sceneDepth, p + ivec2(1, 0), 0).x; 258 | float r2 = texelFetch(sceneDepth, p + ivec2(2, 0), 0).x; 259 | float b2 = texelFetch(sceneDepth, p - ivec2(0, 2), 0).x; 260 | float b1 = texelFetch(sceneDepth, p - ivec2(0, 1), 0).x; 261 | float t1 = texelFetch(sceneDepth, p + ivec2(0, 1), 0).x; 262 | float t2 = texelFetch(sceneDepth, p + ivec2(0, 2), 0).x; 263 | 264 | float dl = abs((2.0 * l1 - l2) - c0); 265 | float dr = abs((2.0 * r1 - r2) - c0); 266 | float db = abs((2.0 * b1 - b2) - c0); 267 | float dt = abs((2.0 * t1 - t2) - c0); 268 | 269 | vec3 ce = getWorldPos(c0, vUv).xyz; 270 | 271 | vec3 dpdx = (dl < dr) ? ce - getWorldPos(l1, (vUv - vec2(1.0 / resolution.x, 0.0))).xyz 272 | : -ce + getWorldPos(r1, (vUv + vec2(1.0 / resolution.x, 0.0))).xyz; 273 | vec3 dpdy = (db < dt) ? ce - getWorldPos(b1, (vUv - vec2(0.0, 1.0 / resolution.y))).xyz 274 | : -ce + getWorldPos(t1, (vUv + vec2(0.0, 1.0 / resolution.y))).xyz; 275 | 276 | return normalize(cross(dpdx, dpdy)); 277 | } 278 | 279 | 280 | vec4 getSampleDir(vec3 diskInfo, vec3 normal, vec3 viewDir, vec3 worldPos, float roughness, float metalness) { 281 | float f0 = 0.04 + 0.96 * metalness; 282 | float schlick = f0 + (1.0 - f0) * pow(1.0 - dot(-viewDir, normal), 5.0); 283 | if (diskInfo.b > schlick || debugVoxels) { 284 | 285 | vec3 reflectedDir; 286 | diskInfo.r = sqrt(diskInfo.r); 287 | 288 | vec2 diskPos = vec2( 289 | diskInfo.r * cos(diskInfo.g * 2.0 * 3.14159), 290 | diskInfo.r * sin(diskInfo.g * 2.0 * 3.14159) 291 | ); 292 | vec3 hemisphereDir = normalize(vec3(diskPos, sqrt(1.0 - dot(diskPos, diskPos)))); 293 | 294 | vec3 helper = vec3(1.0, 0.0, 0.0); 295 | if (abs(dot(helper, normal)) > 0.99) { 296 | helper = vec3(0.0, 1.0, 0.0); 297 | } 298 | vec3 tangent = normalize(cross(normal, helper)); 299 | vec3 bitangent = cross(normal, tangent); 300 | mat3 TBN = mat3(tangent, bitangent, normal); 301 | reflectedDir = (TBN * hemisphereDir); 302 | return vec4(reflectedDir, 0.0); 303 | } else { 304 | float alpha = roughness * roughness; 305 | float theta = atan(sqrt( 306 | alpha * alpha * diskInfo.r / (1.0 - diskInfo.r) 307 | )); 308 | float phi = diskInfo.g * 2.0 * 3.14159; 309 | vec3 hemisphereDir = vec3( 310 | sin(theta) * cos(phi), 311 | sin(theta) * sin(phi), 312 | cos(theta) 313 | ); 314 | vec3 helper = vec3(1.0, 0.0, 0.0); 315 | if (abs(dot(helper, normal)) > 0.99) { 316 | helper = vec3(0.0, 1.0, 0.0); 317 | } 318 | vec3 tangent = normalize(cross(normal, helper)); 319 | vec3 bitangent = cross(normal, tangent); 320 | mat3 TBN = mat3(tangent, bitangent, normal); 321 | vec3 hNormal = normalize(TBN * hemisphereDir); 322 | vec3 reflectedDir = reflect(viewDir, hNormal); 323 | return vec4(reflectedDir, 1.0); 324 | } 325 | 326 | } 327 | vec4 takeSample( 328 | vec3 cameraPos, 329 | vec3 worldPos, 330 | vec3 normal, 331 | vec3 viewDir, 332 | vec3 diskInfo, 333 | float roughness, 334 | float metalness 335 | ) { 336 | vec4 sampleData = getSampleDir(diskInfo, normal, viewDir, worldPos, roughness, metalness); 337 | vec3 reflectedDir = sampleData.rgb; 338 | vec3 voxelRatioResults = voxelAmount / boxSize; 339 | float voxelRatioMax = 1.0 / max(max(voxelRatioResults.x, voxelRatioResults.y), voxelRatioResults.z); 340 | 341 | Ray ray; 342 | ray.origin = debugVoxels ? cameraPos : worldPos + normal * voxelRatioMax + reflectedDir * voxelRatioMax; 343 | ray.direction = debugVoxels ? viewDir : reflectedDir; 344 | RayHit hit = raycast(ray); 345 | vec3 reflectedColor = vec3(0.0); 346 | if (hit.hit) { 347 | reflectedColor = getVoxelColor(hit.voxelPos, -hit.normal); 348 | } else { 349 | reflectedColor = textureLod(skybox, ray.direction, 9.0 * roughness).rgb / 3.14159; 350 | } 351 | return vec4(reflectedColor, sampleData.a); 352 | } 353 | void main() { 354 | if (texture2D(sceneDepth, vUv).r == 1.0) { 355 | gl_FragColor = texture(sceneDiffuse, vUv); 356 | return; 357 | } 358 | float depth = texture(sceneDepth, vUv).r; 359 | vec3 worldPos = (viewMatrixInv * vec4(getWorldPos(depth, vUv), 1.0)).xyz; 360 | vec3 normal = (viewMatrixInv * vec4( 361 | texture2D(sceneNormal, vUv).rgb, 362 | 0.0)).xyz; 363 | 364 | 365 | 366 | vec3 viewDir = normalize(worldPos - cameraPos); 367 | 368 | vec3 initialSample = texture2D( 369 | bluenoise, 370 | gl_FragCoord.xy / vec2(textureSize(bluenoise, 0).xy) 371 | ).rgb; 372 | vec3 harmoniousNumbers = vec3( 373 | 1.618033988749895, 374 | 1.324717957244746, 375 | 1.220744084605759 376 | ); 377 | // vec3 reflectedColor = vec3(0.0); 378 | vec3 diffuseColor = vec3(0.0); 379 | vec3 specularColor = vec3(0.0); 380 | float diffuseSamples = 0.0; 381 | float specularSamples = 0.0; 382 | vec4 matData = texture2D(sceneMaterial, vUv); 383 | float r = matData.g; 384 | float m = matData.r; 385 | for(float i = 0.0; i < samples; i++) { 386 | vec3 s = fract(initialSample + i * harmoniousNumbers); 387 | //reflectedColor += takeSample(cameraPos, worldPos, normal, viewDir, s, r, m); 388 | vec4 sampleData = takeSample(cameraPos, worldPos, normal, viewDir, s, r, m); 389 | vec3 reflCol = sampleData.rgb; 390 | if (sampleData.a == 0.0) { 391 | diffuseColor += reflCol; 392 | diffuseSamples += 1.0; 393 | } 394 | if (sampleData.a == 1.0) { 395 | specularColor += reflCol; 396 | specularSamples += 1.0; 397 | } 398 | } 399 | // reflectedColor /= samples; 400 | if (diffuseSamples > 0.0) { 401 | diffuseColor /= diffuseSamples; 402 | } 403 | if (specularSamples > 0.0) { 404 | specularColor /= specularSamples; 405 | } 406 | 407 | gl_FragColor = vec4(diffuseColor, 1.0); 408 | specular = vec4(specularColor, 1.0); 409 | }` 410 | 411 | }; 412 | 413 | export { EffectShader }; -------------------------------------------------------------------------------- /HorizontalBlurShader.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'https://unpkg.com/three@0.162.0/build/three.module.min.js'; 2 | 3 | const HorizontalBlurShader = { 4 | 5 | uniforms: { 6 | 7 | 'tDiffuse': { value: null }, 8 | 'sceneDepth': { value: null }, 9 | 'sceneMaterial': { value: null }, 10 | 'blurSharp': { value: 0 }, 11 | 'depthBias': { value: 1.0 }, 12 | 'near': { value: 0 }, 13 | 'far': { value: 0 }, 14 | 'h': { value: 1.0 / 512.0 }, 15 | 'resolution': { value: new THREE.Vector2() }, 16 | 'blurThreshold': { value: 0.25 }, 17 | 'normalTexture': { value: null }, 18 | 'projectionMatrixInv': { value: new THREE.Matrix4() }, 19 | 'viewMatrixInv': { value: new THREE.Matrix4() }, 20 | "tSpecular": { value: null } 21 | 22 | }, 23 | 24 | vertexShader: /* glsl */ ` 25 | varying vec2 vUv; 26 | void main() { 27 | vUv = uv; 28 | gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); 29 | }`, 30 | 31 | fragmentShader: /* glsl */ ` 32 | layout(location = 1) out vec4 specular; 33 | uniform sampler2D tDiffuse; 34 | uniform sampler2D tSpecular; 35 | uniform sampler2D sceneDepth; 36 | uniform sampler2D sceneMaterial; 37 | uniform sampler2D normalTexture; 38 | uniform float blurSharp; 39 | uniform float h; 40 | uniform float near; 41 | uniform float far; 42 | uniform vec2 resolution; 43 | uniform float blurThreshold; 44 | uniform float depthBias; 45 | uniform mat4 projectionMatrixInv; 46 | uniform mat4 viewMatrixInv; 47 | varying vec2 vUv; 48 | 49 | float sdPlane( vec3 p, vec3 n, float h ) 50 | { 51 | // n must be normalized 52 | return dot(p,n) + h; 53 | } 54 | vec3 getWorldPos(float depth, vec2 coord) { 55 | float z = depth * 2.0 - 1.0; 56 | vec4 clipSpacePosition = vec4(coord * 2.0 - 1.0, z, 1.0); 57 | vec4 viewSpacePosition = projectionMatrixInv * clipSpacePosition; 58 | // Perspective division 59 | viewSpacePosition /= viewSpacePosition.w; 60 | vec4 worldSpacePosition = viewMatrixInv * viewSpacePosition; 61 | return worldSpacePosition.xyz; 62 | } 63 | float depthFalloff(vec2 uv, vec3 norm, float c) { 64 | vec3 uvPos = getWorldPos(texture2D(sceneDepth, uv).x, uv); 65 | return exp(-1.0 * depthBias * abs(sdPlane(uvPos, norm, c))); 66 | } 67 | float colorFalloff(vec2 uv, vec3 color) { 68 | vec3 color2 = texture2D(tDiffuse, uv).rgb; 69 | return exp(-0.5 * length(color - color2)); 70 | } 71 | 72 | 73 | vec3 computeNormal(vec3 worldPos) { 74 | vec2 downUv = vUv + vec2(0.0, 1.0 / resolution.y); 75 | vec3 downPos = getWorldPos(texture2D(sceneDepth, downUv).x, downUv).xyz; 76 | vec2 rightUv = vUv + vec2(1.0 / resolution.x, 0.0);; 77 | vec3 rightPos = getWorldPos(texture2D(sceneDepth, rightUv).x, rightUv).xyz; 78 | vec2 upUv = vUv - vec2(0.0, 1.0 / resolution.y); 79 | vec3 upPos = getWorldPos(texture2D(sceneDepth, upUv).x, upUv).xyz; 80 | vec2 leftUv = vUv - vec2(1.0 / resolution.x, 0.0); 81 | vec3 leftPos = getWorldPos(texture2D(sceneDepth, leftUv).x, leftUv).xyz; 82 | int hChoice; 83 | int vChoice; 84 | if (length(leftPos - worldPos) < length(rightPos - worldPos)) { 85 | hChoice = 0; 86 | } else { 87 | hChoice = 1; 88 | } 89 | if (length(upPos - worldPos) < length(downPos - worldPos)) { 90 | vChoice = 0; 91 | } else { 92 | vChoice = 1; 93 | } 94 | vec3 hVec; 95 | vec3 vVec; 96 | if (hChoice == 0 && vChoice == 0) { 97 | hVec = leftPos - worldPos; 98 | vVec = upPos - worldPos; 99 | } else if (hChoice == 0 && vChoice == 1) { 100 | hVec = leftPos - worldPos; 101 | vVec = worldPos - downPos; 102 | } else if (hChoice == 1 && vChoice == 1) { 103 | hVec = rightPos - worldPos; 104 | vVec = downPos - worldPos; 105 | } else if (hChoice == 1 && vChoice == 0) { 106 | hVec = rightPos - worldPos; 107 | vVec = worldPos - upPos; 108 | } 109 | return normalize(cross(hVec, vVec)); 110 | } 111 | void main() { 112 | float[9] weights = float[9](0.051, 0.0918, 0.12245, 0.1531, 0.1633, 0.1531, 0.12245, 0.0918, 0.051); 113 | float d = texture2D(sceneDepth, vUv).x; 114 | if (d == 1.0) { 115 | gl_FragColor = texture2D(tDiffuse, vUv); 116 | return; 117 | } 118 | vec3 uvWorldPos = getWorldPos(d, vUv); 119 | vec3 normal = (viewMatrixInv * vec4(texture2D(normalTexture, vUv).rgb, 0.0)).xyz; 120 | vec4 matData = texture2D(sceneMaterial, vUv); 121 | float metalness = matData.r; 122 | float roughness = matData.g; 123 | float radius = h / resolution.x; //max(h * (1.0 - d) * (-blurSharp * pow(b - 0.5, 2.0) + 1.0), blurThreshold / resolution.x); 124 | vec3 planeNormal = normal; 125 | float planeConstant = -dot(uvWorldPos, normal); 126 | vec3 diffuseSum = vec3( 0.0 ); 127 | float weightSum = 0.0; 128 | for(float i = -4.0; i <= 4.0; i++) { 129 | vec2 sampleUv = vec2( vUv.x + i * radius, vUv.y ); 130 | vec2 clipRangeCheck = step(vec2(0.0),sampleUv.xy) * step(sampleUv.xy, vec2(1.0)); 131 | float w = weights[int(i + 4.0)] * depthFalloff(sampleUv, planeNormal, planeConstant) * clipRangeCheck.x * clipRangeCheck.y; 132 | diffuseSum += texture2D( tDiffuse, sampleUv).rgb * w ; 133 | weightSum += w; 134 | } 135 | diffuseSum /= weightSum; 136 | radius *= clamp(sqrt(roughness), 0.1 * (1.0-metalness), 1.0); 137 | vec3 specularSum = vec3( 0.0 ); 138 | weightSum = 0.0; 139 | for(float i = -4.0; i <= 4.0; i++) { 140 | vec2 sampleUv = vec2( vUv.x + i * radius, vUv.y ); 141 | vec2 clipRangeCheck = step(vec2(0.0),sampleUv.xy) * step(sampleUv.xy, vec2(1.0)); 142 | float w = weights[int(i + 4.0)] * depthFalloff(sampleUv, planeNormal, planeConstant) * clipRangeCheck.x * clipRangeCheck.y; 143 | specularSum += texture2D( tSpecular, sampleUv).rgb * w ; 144 | weightSum += w; 145 | } 146 | specularSum /= weightSum; 147 | 148 | 149 | gl_FragColor = vec4(diffuseSum, 1.0); 150 | specular = vec4(specularSum, 1.0); 151 | }` 152 | 153 | }; 154 | export { HorizontalBlurShader }; -------------------------------------------------------------------------------- /N8GIPass.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | import { Pass, FullScreenQuad } from "three/examples/jsm/postprocessing/Pass.js"; 3 | import { EffectShader } from './EffectShader.js'; 4 | import { EffectCompositer } from './EffectCompositer.js'; 5 | import BlueNoise from './BlueNoise.js'; 6 | import { VerticalBlurShader } from './VerticalBlurShader.js'; 7 | import { HorizontalBlurShader } from './HorizontalBlurShader.js'; 8 | import { VoxelModule } from './VoxelModule.js'; 9 | import { N8AOPass } from "https://unpkg.com/n8ao@latest/dist/N8AO.js"; 10 | import { packRGBToUint32, createBufferTexture, imageToDataTexture, createGBufferSplit } from './utils.js'; 11 | const bluenoiseBits = Uint8Array.from(atob(BlueNoise), c => c.charCodeAt(0)); 12 | 13 | class N8GIPass extends Pass { 14 | constructor(scene, camera, renderer, width = 512, height = 512) { 15 | super(); 16 | this.width = width; 17 | this.height = height; 18 | 19 | this.clear = true; 20 | 21 | this.camera = camera; 22 | this.scene = scene; 23 | this.configuration = new Proxy({ 24 | voxelsOnly: false, 25 | giOnly: false, 26 | denoise: true, 27 | denoiseStrength: 1.0, 28 | roughness: 1.0, 29 | giStrength: 1.0, 30 | specularStrength: 1.0, 31 | useSimpleEnvmap: false, 32 | samples: 1, 33 | aoEnabled: true 34 | }, { 35 | set: (target, propName, value) => { 36 | const oldProp = target[propName]; 37 | target[propName] = value; 38 | return true; 39 | }, 40 | get: (target, propName) => { 41 | return target[propName]; 42 | } 43 | }); 44 | /* 45 | const box = new THREE.Box3().setFromObject(sponza, true); 46 | box.min = box.min.floor().addScalar(-2); 47 | box.max = box.max.ceil().addScalar(2); 48 | const size = box.getSize(new THREE.Vector3()).floor(); 49 | const center = box.getCenter(new THREE.Vector3()); 50 | 51 | const VOXEL_AMOUNT = size.clone().multiplyScalar(0.5).floor(); //.addScalar(2); 52 | let VOXEL_RATIO_MAX = (new THREE.Vector3(VOXEL_AMOUNT.x / size.x, VOXEL_AMOUNT.y / size.y, VOXEL_AMOUNT.z / size.z)); 53 | VOXEL_RATIO_MAX = Math.max(VOXEL_RATIO_MAX.x, VOXEL_RATIO_MAX.y, VOXEL_RATIO_MAX.z); 54 | 55 | const voxelCenter = center.clone(); 56 | const voxelSize = size.clone(); 57 | 58 | const voxelModule = new VoxelModule({ 59 | scene, 60 | renderer, 61 | VOXEL_AMOUNT, 62 | voxelCenter, 63 | voxelSize, 64 | workers: navigator.hardwareConcurrency, 65 | }); 66 | 67 | await voxelModule.init(); 68 | */ 69 | const box = new THREE.Box3().setFromObject(scene, true); 70 | box.min = box.min.floor().addScalar(-2); 71 | box.max = box.max.ceil().addScalar(2); 72 | const size = box.getSize(new THREE.Vector3()).floor(); 73 | const center = box.getCenter(new THREE.Vector3()); 74 | 75 | this.VOXEL_AMOUNT = size.clone().multiplyScalar(0.5).floor(); //.addScalar(2); 76 | const VOXEL_AMOUNT = this.VOXEL_AMOUNT; 77 | let VOXEL_RATIO_MAX = (new THREE.Vector3(VOXEL_AMOUNT.x / size.x, VOXEL_AMOUNT.y / size.y, VOXEL_AMOUNT.z / size.z)); 78 | this.VOXEL_RATIO_MAX = Math.max(VOXEL_RATIO_MAX.x, VOXEL_RATIO_MAX.y, VOXEL_RATIO_MAX.z); 79 | 80 | this.voxelCenter = center.clone(); 81 | this.voxelSize = size.clone(); 82 | 83 | this.voxelModule = new VoxelModule({ 84 | scene, 85 | renderer, 86 | VOXEL_AMOUNT: this.VOXEL_AMOUNT, 87 | voxelCenter: this.voxelCenter, 88 | voxelSize: this.voxelSize, 89 | workers: navigator.hardwareConcurrency, 90 | }); 91 | 92 | this.voxelModule.init(); 93 | 94 | this.uniformsCapture = new THREE.Mesh(new THREE.BufferGeometry(), new THREE.ShaderMaterial({ 95 | lights: true, 96 | uniforms: { 97 | ...THREE.UniformsLib.lights, 98 | }, 99 | vertexShader: /*glsl*/ ` 100 | void main() { 101 | gl_Position = vec4(position, 1.0); 102 | } 103 | `, 104 | fragmentShader: /*glsl*/ ` 105 | #include 106 | #include 107 | void main() { 108 | gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0); 109 | } 110 | ` 111 | })); 112 | scene.add(this.uniformsCapture); 113 | /* this.defaultTexture = createGBufferSplit(width, height); 114 | this.normalTexture = createGBufferSplit(width, height); 115 | this.albedoTexture = createGBufferSplit(width, height);*/ 116 | this.gbuffer = new THREE.WebGLRenderTarget(width, height, { 117 | count: 4, 118 | format: THREE.RGBAFormat, 119 | type: THREE.HalfFloatType, 120 | minFilter: THREE.NearestFilter, 121 | magFilter: THREE.NearestFilter, 122 | }); 123 | this.gbuffer.depthTexture = new THREE.DepthTexture(width, height, THREE.UnsignedIntType); 124 | this.meshNormalMaterial = new THREE.MeshNormalMaterial(); 125 | this.bluenoise = new THREE.DataTexture( 126 | bluenoiseBits, 127 | 128, 128 | 128 129 | ); 130 | this.bluenoise.wrapS = THREE.RepeatWrapping; 131 | this.bluenoise.wrapT = THREE.RepeatWrapping; 132 | this.bluenoise.minFilter = THREE.NearestFilter; 133 | this.bluenoise.magFilter = THREE.NearestFilter; 134 | this.bluenoise.needsUpdate = true; 135 | 136 | this.writeTargetInternal = new THREE.WebGLRenderTarget(this.width, this.height, { 137 | minFilter: THREE.LinearFilter, 138 | magFilter: THREE.LinearFilter, 139 | depthBuffer: false, 140 | format: 'RGB', 141 | type: THREE.FloatType, 142 | internalFormat: 'R11F_G11F_B10F', 143 | count: 2 144 | }); 145 | this.readTargetInternal = new THREE.WebGLRenderTarget(this.width, this.height, { 146 | minFilter: THREE.LinearFilter, 147 | magFilter: THREE.LinearFilter, 148 | depthBuffer: false, 149 | format: 'RGB', 150 | type: THREE.FloatType, 151 | internalFormat: 'R11F_G11F_B10F', 152 | count: 2 153 | }); 154 | this.giTarget = new THREE.WebGLRenderTarget(this.width, this.height, { 155 | minFilter: THREE.LinearFilter, 156 | magFilter: THREE.LinearFilter, 157 | format: 'RGB', 158 | type: THREE.FloatType, 159 | internalFormat: 'R11F_G11F_B10F' 160 | }); 161 | 162 | this.effectQuad = new FullScreenQuad(new THREE.ShaderMaterial(EffectShader)); 163 | /* const blurs = []; 164 | for (let i = 0; i < 3; i++) { 165 | const hblur = new ShaderPass(HorizontalBlurShader); 166 | const vblur = new ShaderPass(VerticalBlurShader); 167 | const blurSize = 2.0; 168 | hblur.uniforms.h.value = blurSize; 169 | vblur.uniforms.v.value = blurSize; 170 | blurs.push([hblur, vblur]); 171 | }*/ 172 | 173 | 174 | this.horizontalQuad = new FullScreenQuad(new THREE.ShaderMaterial(HorizontalBlurShader)); 175 | this.verticalQuad = new FullScreenQuad(new THREE.ShaderMaterial(VerticalBlurShader)); 176 | this.effectCompositer = new FullScreenQuad(new THREE.ShaderMaterial(EffectCompositer)); 177 | this.copyQuad = new FullScreenQuad(new THREE.ShaderMaterial({ 178 | uniforms: { 179 | tDiffuse: { value: null } 180 | }, 181 | vertexShader: ` 182 | varying vec2 vUv; 183 | void main() { 184 | vUv = uv; 185 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 186 | } 187 | `, 188 | fragmentShader: ` 189 | uniform sampler2D tDiffuse; 190 | varying vec2 vUv; 191 | void main() { 192 | gl_FragColor = texture2D(tDiffuse, vUv); 193 | } 194 | ` 195 | })); 196 | this.voxelModule.updateVoxels(); 197 | this.n8aopass = new N8AOPass(scene, camera, this.width, this.height); 198 | this.n8aopass.configuration.autoRenderBeauty = false; 199 | this.n8aopass.beautyRenderTarget = this.gbuffer; 200 | this.n8aopass.configuration.gammaCorrection = false; 201 | // n8aopass.configuration.aoSamples = 64; 202 | // n8aopass.configuration.denoiseRadius = 3; 203 | // n8aopass.configuration.aoRadius = 10; 204 | this.n8aopass.setDisplayMode("AO"); 205 | this.n8aoRenderTarget = new THREE.WebGLRenderTarget(this.width, this.height, { 206 | minFilter: THREE.LinearFilter, 207 | magFilter: THREE.LinearFilter, 208 | format: 'RGB', 209 | type: THREE.FloatType, 210 | internalFormat: 'R11F_G11F_B10F' 211 | }); 212 | this.objectGBufferMaterials = new Map(); 213 | 214 | this.voxelModule.children.forEach(child => { 215 | const newGBufferMaterial = child.material.clone(); 216 | /* if (newGBufferMaterial.onBeforeCompile) { 217 | let oldHook = newGBufferMaterial.onBeforeCompile;*/ 218 | let oldHook = newGBufferMaterial.onBeforeCompile || function() {}; 219 | newGBufferMaterial.onBeforeCompile = (shader) => { 220 | oldHook(shader); 221 | shader.fragmentShader = "layout(location = 1) out vec4 gNormal;\nlayout(location = 2) out vec4 gAlbedo;\nlayout(location = 3) out vec4 gMaterial;\n" + shader.fragmentShader; 222 | shader.fragmentShader = shader.fragmentShader.replace( 223 | "#include ", 224 | ` 225 | #include 226 | gNormal = vec4(normal, 1.0); 227 | gAlbedo = vec4(diffuseColor); 228 | gMaterial = vec4(metalnessFactor, roughnessFactor, 0.0, 0.0); 229 | `); 230 | }; 231 | 232 | // child.material = newGBufferMaterial; 233 | 234 | this.objectGBufferMaterials.set(child, newGBufferMaterial); 235 | child.material = new Proxy(child.material, { 236 | set: (target, propName, value) => { 237 | /*const oldProp = target[propName]; 238 | target[propName] = value; 239 | if (propName === "envMapIntensity") { 240 | newGBufferMaterial.envMapIntensity = value; 241 | }*/ 242 | target[propName] = value; 243 | newGBufferMaterial[propName] = value; 244 | return true; 245 | }, 246 | get: (target, propName) => { 247 | return target[propName]; 248 | } 249 | }); 250 | }); 251 | 252 | const lights = []; 253 | scene.traverse(obj => { 254 | if (obj.isLight) { 255 | lights.push(obj); 256 | } 257 | }); 258 | 259 | 260 | } 261 | setSize(width, height) { 262 | this.width = width; 263 | this.height = height; 264 | /* this.defaultTexture.setSize(width, height); 265 | this.normalTexture.setSize(width, height); 266 | this.albedoTexture.setSize(width, height);*/ 267 | this.gbuffer.setSize(width, height); 268 | this.giTarget.setSize(width, height); 269 | this.writeTargetInternal.setSize(width, height); 270 | this.readTargetInternal.setSize(width, height); 271 | this.n8aoRenderTarget.setSize(width, height); 272 | this.n8aopass.setSize(width, height); 273 | } 274 | render(renderer, writeBuffer, readBuffer, deltaTime, maskActive) { 275 | if (!this.voxelModule.updating) { 276 | this.voxelModule.updateVoxels(); 277 | } 278 | this.voxelModule.voxelColorShader.material.uniforms['sceneTex'].value = this.giTarget.texture; 279 | this.voxelModule.voxelColorShader.material.uniforms['sceneDepth'].value = this.gbuffer.depthTexture; 280 | this.voxelModule.voxelColorShader.material.uniforms['projMat'].value = this.camera.projectionMatrix; 281 | this.voxelModule.voxelColorShader.material.uniforms['viewMat'].value = this.camera.matrixWorldInverse; 282 | this.voxelModule.voxelColorShader.material.uniforms['projectionMatrixInv'].value = this.camera.projectionMatrixInverse; 283 | this.voxelModule.voxelColorShader.material.uniforms['viewMatrixInv'].value = this.camera.matrixWorld; 284 | this.voxelModule.updateMaterialDataTexture(); 285 | 286 | this.voxelModule.update(); 287 | this.scene.updateMatrixWorld(); 288 | renderer.shadowMap.needsUpdate = true; 289 | renderer.setRenderTarget(this.gbuffer); 290 | renderer.clear(); 291 | const oldBackground = this.scene.background; 292 | this.scene.background = null; 293 | const oldMaterials = new Map(); 294 | this.voxelModule.children.forEach(child => { 295 | oldMaterials.set(child, child.material); 296 | child.material = this.objectGBufferMaterials.get(child); 297 | }); 298 | renderer.render(this.scene, this.camera); 299 | this.scene.background = oldBackground; 300 | this.voxelModule.children.forEach(child => { 301 | child.material = oldMaterials.get(child); 302 | }); 303 | 304 | 305 | this.n8aopass.render( 306 | renderer, 307 | this.n8aoRenderTarget, 308 | null, 309 | 0, 310 | false 311 | ); 312 | 313 | const uniforms = this.uniformsCapture.material.uniforms; 314 | uniforms.directionalLights.value.forEach(light => { 315 | light.direction.applyMatrix4(new THREE.Matrix4().extractRotation( 316 | this.camera.matrixWorld 317 | )); 318 | }); 319 | uniforms.pointLights.value.forEach(pointLight => { 320 | // console.log(pointLight.position); 321 | pointLight.position.applyMatrix4(this.camera.matrixWorld); 322 | }); 323 | uniforms.spotLights.value.forEach(spotLight => { 324 | //console.log(spotLight); 325 | spotLight.position.applyMatrix4(this.camera.matrixWorld); 326 | spotLight.direction.applyMatrix4(new THREE.Matrix4().extractRotation( 327 | this.camera.matrixWorld 328 | )); 329 | }); 330 | Object.keys(uniforms).forEach(key => { 331 | this.voxelModule.setUniform(key, uniforms[key].value); 332 | }); 333 | this.camera.updateMatrixWorld(); 334 | this.effectQuad.material.uniforms["sceneDiffuse"].value = this.gbuffer.textures[0]; 335 | this.effectQuad.material.uniforms["sceneDepth"].value = this.gbuffer.depthTexture; 336 | this.effectQuad.material.uniforms["sceneNormal"].value = this.gbuffer.textures[1]; 337 | this.effectQuad.material.uniforms["sceneAlbedo"].value = this.gbuffer.textures[2]; 338 | this.effectQuad.material.uniforms["sceneMaterial"].value = this.gbuffer.textures[3]; 339 | this.effectQuad.material.uniforms["bluenoise"].value = this.bluenoise; 340 | this.effectQuad.material.uniforms["skybox"].value = this.scene.background; 341 | this.effectQuad.material.uniforms["voxelTexture"].value = this.voxelModule.getIndexTexture(); 342 | this.effectQuad.material.uniforms["voxelColor1"].value = this.voxelModule.getVoxelRenderTarget().textures[0]; 343 | this.effectQuad.material.uniforms["voxelColor2"].value = this.voxelModule.getVoxelRenderTarget().textures[1]; 344 | this.effectQuad.material.uniforms["voxelColorTextureSize"].value = this.voxelModule.getVoxelRenderTargetSize(); 345 | this.effectQuad.material.uniforms["projMat"].value = this.camera.projectionMatrix; 346 | this.effectQuad.material.uniforms["viewMat"].value = this.camera.matrixWorldInverse; 347 | this.effectQuad.material.uniforms["projectionMatrixInv"].value = this.camera.projectionMatrixInverse; 348 | this.effectQuad.material.uniforms["viewMatrixInv"].value = this.camera.matrixWorld; 349 | this.effectQuad.material.uniforms["cameraPos"].value = this.camera.getWorldPosition(new THREE.Vector3()); 350 | this.effectQuad.material.uniforms['resolution'].value = new THREE.Vector2(this.width, this.height); 351 | this.effectQuad.material.uniforms['time'].value = performance.now() / 1000; 352 | this.effectQuad.material.uniforms['boxSize'].value = this.voxelSize; 353 | this.effectQuad.material.uniforms['boxCenter'].value = this.voxelCenter; 354 | this.effectQuad.material.uniforms['roughness'].value = this.configuration.roughness; 355 | this.effectQuad.material.uniforms['voxelAmount'].value = this.VOXEL_AMOUNT; 356 | this.effectQuad.material.uniforms['debugVoxels'].value = this.configuration.voxelsOnly; 357 | this.effectQuad.material.uniforms['samples'].value = this.configuration.samples; 358 | 359 | if (this.configuration.voxelsOnly) { 360 | renderer.setRenderTarget( 361 | this.renderToScreen ? null : 362 | writeBuffer 363 | ); 364 | this.effectQuad.render(renderer); 365 | return; 366 | } 367 | 368 | renderer.setRenderTarget(this.writeTargetInternal); 369 | this.effectQuad.render(renderer); 370 | 371 | this.horizontalQuad.material.uniforms["sceneDepth"].value = this.gbuffer.depthTexture; 372 | this.horizontalQuad.material.uniforms["sceneMaterial"].value = this.gbuffer.textures[3]; 373 | this.horizontalQuad.material.uniforms["normalTexture"].value = this.gbuffer.textures[1]; 374 | this.horizontalQuad.material.uniforms["resolution"].value = new THREE.Vector2(this.width, this.height); 375 | this.horizontalQuad.material.uniforms["projectionMatrixInv"].value = this.camera.projectionMatrixInverse; 376 | this.horizontalQuad.material.uniforms["viewMatrixInv"].value = this.camera.matrixWorld; 377 | this.verticalQuad.material.uniforms["sceneDepth"].value = this.gbuffer.depthTexture; 378 | this.verticalQuad.material.uniforms["sceneMaterial"].value = this.gbuffer.textures[3]; 379 | this.verticalQuad.material.uniforms["normalTexture"].value = this.gbuffer.textures[1]; 380 | this.verticalQuad.material.uniforms["resolution"].value = new THREE.Vector2(this.width, this.height); 381 | this.verticalQuad.material.uniforms["projectionMatrixInv"].value = this.camera.projectionMatrixInverse; 382 | this.verticalQuad.material.uniforms["viewMatrixInv"].value = this.camera.matrixWorld; 383 | if (!this.configuration.voxelsOnly && this.configuration.denoise) { 384 | const blurnums = [16, 4, 1]; 385 | for (let i = 0; i < blurnums.length; i++) { 386 | // if (i % 2 == 0) { 387 | [this.writeTargetInternal, this.readTargetInternal] = [this.readTargetInternal, this.writeTargetInternal]; 388 | this.horizontalQuad.material.uniforms["h"].value = blurnums[i] * this.configuration.denoiseStrength; 389 | this.horizontalQuad.material.uniforms["tDiffuse"].value = this.readTargetInternal.textures[0]; 390 | this.horizontalQuad.material.uniforms["tSpecular"].value = this.readTargetInternal.textures[1]; 391 | renderer.setRenderTarget(this.writeTargetInternal); 392 | this.horizontalQuad.render(renderer); 393 | [this.writeTargetInternal, this.readTargetInternal] = [this.readTargetInternal, this.writeTargetInternal]; 394 | this.verticalQuad.material.uniforms["v"].value = blurnums[i] * this.configuration.denoiseStrength; 395 | this.verticalQuad.material.uniforms["tDiffuse"].value = this.readTargetInternal.textures[0]; 396 | this.verticalQuad.material.uniforms["tSpecular"].value = this.readTargetInternal.textures[1]; 397 | renderer.setRenderTarget(this.writeTargetInternal); 398 | this.verticalQuad.render(renderer); 399 | } 400 | } 401 | this.effectCompositer.material.uniforms["cameraPos"].value = this.camera.getWorldPosition(new THREE.Vector3()); 402 | this.effectCompositer.material.uniforms["viewMatrixInv"].value = this.camera.matrixWorld; 403 | this.effectCompositer.material.uniforms["projectionMatrixInv"].value = this.camera.projectionMatrixInverse; 404 | this.effectCompositer.material.uniforms["sceneDiffuse"].value = this.gbuffer.textures[0]; 405 | this.effectCompositer.material.uniforms["sceneAlbedo"].value = this.gbuffer.textures[2]; 406 | this.effectCompositer.material.uniforms["sceneDepth"].value = this.gbuffer.depthTexture; 407 | this.effectCompositer.material.uniforms["sceneAO"].value = this.n8aoRenderTarget.texture; 408 | this.effectCompositer.material.uniforms["tDiffuse"].value = this.writeTargetInternal.textures[0]; 409 | this.effectCompositer.material.uniforms["tSpecular"].value = this.writeTargetInternal.textures[1]; 410 | this.effectCompositer.material.uniforms["voxelTexture"].value = this.voxelModule.getIndexTexture(); 411 | this.effectCompositer.material.uniforms["giStrengthMultiplier"].value = this.configuration.giStrength * (!this.configuration.useSimpleEnvmap); 412 | this.effectCompositer.material.uniforms["specularStrengthMultiplier"].value = this.configuration.specularStrength * (!this.configuration.useSimpleEnvmap); 413 | this.effectCompositer.material.uniforms["giOnly"].value = this.configuration.giOnly; 414 | this.effectCompositer.material.uniforms["background"].value = this.scene.background; 415 | this.effectCompositer.material.uniforms["aoEnabled"].value = this.configuration.aoEnabled; 416 | // renderer.setRenderTarget(this.giTarget); 417 | // this.effectCompositer.render(renderer); 418 | /* renderer.setRenderTarget(this.giTarget); 419 | this.copyQuad.material.uniforms["tDiffuse"].value = this.writeTargetInternal.texture; 420 | this.copyQuad.render(renderer);*/ 421 | renderer.setRenderTarget(this.renderToScreen ? null : writeBuffer); 422 | this.effectCompositer.render(renderer); 423 | 424 | 425 | 426 | 427 | } 428 | 429 | } 430 | 431 | export { N8GIPass }; -------------------------------------------------------------------------------- /PoissionBlur.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'https://unpkg.com/three@0.162.0/build/three.module.min.js'; 2 | const PoissionBlur = { 3 | uniforms: { 4 | 5 | 'sceneDiffuse': { value: null }, 6 | 'sceneDepth': { value: null }, 7 | 'sceneNormal': { value: null }, 8 | 'tDiffuse': { value: null }, 9 | 'projMat': { value: new THREE.Matrix4() }, 10 | 'viewMat': { value: new THREE.Matrix4() }, 11 | 'projectionMatrixInv': { value: new THREE.Matrix4() }, 12 | 'viewMatrixInv': { value: new THREE.Matrix4() }, 13 | 'cameraPos': { value: new THREE.Vector3() }, 14 | 'resolution': { value: new THREE.Vector2() }, 15 | 'time': { value: 0.0 }, 16 | 'r': { value: 5.0 }, 17 | 'blueNoise': { value: null }, 18 | 'radius': { value: 12.0 }, 19 | 'worldRadius': { value: 5.0 }, 20 | 'index': { value: 0.0 }, 21 | "poissonDisk": { value: [] }, 22 | "distanceFalloff": { value: 1.0 }, 23 | 'near': { value: 0.1 }, 24 | 'far': { value: 1000.0 }, 25 | 'logDepth': { value: false }, 26 | 'screenSpaceRadius': { value: false } 27 | }, 28 | depthWrite: false, 29 | depthTest: false, 30 | 31 | vertexShader: /* glsl */ ` 32 | varying vec2 vUv; 33 | void main() { 34 | vUv = uv; 35 | gl_Position = vec4(position, 1.0); 36 | }`, 37 | fragmentShader: /* glsl */ ` 38 | uniform sampler2D sceneDiffuse; 39 | uniform highp sampler2D sceneDepth; 40 | uniform sampler2D sceneNormal; 41 | uniform sampler2D tDiffuse; 42 | uniform sampler2D blueNoise; 43 | uniform mat4 projectionMatrixInv; 44 | uniform mat4 viewMatrixInv; 45 | uniform vec2 resolution; 46 | uniform float r; 47 | uniform float radius; 48 | uniform float worldRadius; 49 | uniform float index; 50 | uniform float near; 51 | uniform float far; 52 | uniform float distanceFalloff; 53 | uniform bool logDepth; 54 | uniform bool screenSpaceRadius; 55 | varying vec2 vUv; 56 | 57 | highp float linearize_depth(highp float d, highp float zNear,highp float zFar) 58 | { 59 | highp float z_n = 2.0 * d - 1.0; 60 | return 2.0 * zNear * zFar / (zFar + zNear - z_n * (zFar - zNear)); 61 | } 62 | highp float linearize_depth_log(highp float d, highp float nearZ,highp float farZ) { 63 | float depth = pow(2.0, d * log2(farZ + 1.0)) - 1.0; 64 | float a = farZ / (farZ - nearZ); 65 | float b = farZ * nearZ / (nearZ - farZ); 66 | float linDepth = a + b / depth; 67 | return linearize_depth(linDepth, nearZ, farZ); 68 | } 69 | highp float linearize_depth_ortho(highp float d, highp float nearZ, highp float farZ) { 70 | return nearZ + (farZ - nearZ) * d; 71 | } 72 | vec3 getWorldPosLog(vec3 posS) { 73 | vec2 uv = posS.xy; 74 | float z = posS.z; 75 | float nearZ =near; 76 | float farZ = far; 77 | float depth = pow(2.0, z * log2(farZ + 1.0)) - 1.0; 78 | float a = farZ / (farZ - nearZ); 79 | float b = farZ * nearZ / (nearZ - farZ); 80 | float linDepth = a + b / depth; 81 | vec4 clipVec = vec4(uv, linDepth, 1.0) * 2.0 - 1.0; 82 | vec4 wpos = projectionMatrixInv * clipVec; 83 | return wpos.xyz / wpos.w; 84 | } 85 | vec3 getWorldPos(float depth, vec2 coord) { 86 | #ifdef LOGDEPTH 87 | return getWorldPosLog(vec3(coord, depth)); 88 | #endif 89 | 90 | float z = depth * 2.0 - 1.0; 91 | vec4 clipSpacePosition = vec4(coord * 2.0 - 1.0, z, 1.0); 92 | vec4 viewSpacePosition = projectionMatrixInv * clipSpacePosition; 93 | // Perspective division 94 | vec4 worldSpacePosition = viewSpacePosition; 95 | worldSpacePosition.xyz /= worldSpacePosition.w; 96 | return worldSpacePosition.xyz; 97 | } 98 | #include 99 | #define NUM_SAMPLES 4 100 | uniform vec2 poissonDisk[NUM_SAMPLES]; 101 | void main() { 102 | const float pi = 3.14159; 103 | vec2 texelSize = vec2(1.0 / resolution.x, 1.0 / resolution.y); 104 | vec2 uv = vUv; 105 | vec4 data = texture2D(tDiffuse, vUv); 106 | vec3 illumination = data.rgb; 107 | vec3 baseIllumination = data.rgb; 108 | /*if (vUv.x < 1.0) { 109 | gl_FragColor = vec4(baseIllumination, 1.0); 110 | return; 111 | }*/ 112 | vec3 normal = texture2D(sceneNormal, vUv).xyz * 2.0 - 1.0; 113 | float count = 1.0; 114 | float d = texture2D(sceneDepth, vUv).x; 115 | if (d == 1.0) { 116 | gl_FragColor = data; 117 | return; 118 | } 119 | vec3 worldPos = getWorldPos(d, vUv); 120 | float size = radius; 121 | float angle; 122 | if (index == 0.0) { 123 | angle = texture2D(blueNoise, gl_FragCoord.xy / 1024.0).x * PI2; 124 | } else if (index == 1.0) { 125 | angle = texture2D(blueNoise, gl_FragCoord.xy / 1024.0).y * PI2; 126 | } else if (index == 2.0) { 127 | angle = texture2D(blueNoise, gl_FragCoord.xy / 1024.0).z * PI2; 128 | } else { 129 | angle = texture2D(blueNoise, gl_FragCoord.xy / 1024.0).w * PI2; 130 | } 131 | 132 | mat2 rotationMatrix = mat2(cos(angle), -sin(angle), sin(angle), cos(angle)); 133 | float radiusToUse = screenSpaceRadius ? distance( 134 | worldPos, 135 | getWorldPos(d, vUv + 136 | vec2(worldRadius, 0.0) / resolution) 137 | ) : worldRadius; 138 | float distanceFalloffToUse =screenSpaceRadius ? 139 | radiusToUse * distanceFalloff 140 | : distanceFalloff; 141 | 142 | 143 | for(int i = 0; i < NUM_SAMPLES; i++) { 144 | vec2 offset = (rotationMatrix * poissonDisk[i]) * texelSize * size; 145 | vec4 dataSample = texture2D(tDiffuse, uv + offset); 146 | vec3 illuminationSample = dataSample.rgb; 147 | vec3 normalSample = texture2D(sceneNormal, uv + offset).xyz * 2.0 - 1.0; 148 | float dSample = texture2D(sceneDepth, uv + offset).x; 149 | vec3 worldPosSample = getWorldPos(dSample, uv + offset); 150 | float tangentPlaneDist = abs(dot(worldPos - worldPosSample, normal)); 151 | float rangeCheck = dSample == 1.0 ? 0.0 :exp(-1.0 * tangentPlaneDist * (1.0 / distanceFalloffToUse)) * max(dot(normal, normalSample), 0.0);//(1.732 - length(abs(illuminationSample - baseIllumination))); 152 | baseIllumination += illuminationSample * rangeCheck; 153 | count += rangeCheck; 154 | } 155 | if (count > 0.0) { 156 | baseIllumination /= count; 157 | } 158 | gl_FragColor = vec4(baseIllumination, 1.0); 159 | } 160 | ` 161 | 162 | } 163 | export { PoissionBlur }; -------------------------------------------------------------------------------- /ROADMAP.md: -------------------------------------------------------------------------------- 1 | ### Key: 2 | ⚫️ - Conceptually very difficult to implement 3 | 🔴 - Hard to Implement 4 | 🟠 - Moderately Difficult to Implement 5 | 🟡 - Tedious to Implement 6 | 🟢 - Easy to Implement 7 | ### Short Term ( 1-2 Weeks ) 8 | - Orthographic Camera 🟢 9 | - Logarithmic Depth Buffer (Ortho + Perspective) 🟢 10 | - More test scenes (Godot Bistro) 🟢 11 | - Support multiple directional lights 🟡 12 | - Support point lights 🟡 13 | - Support spot lights 🟠 14 | - Support shadow mapping on all light types 🟠 15 | ### Medium Term ( 2-4 Weeks ) 16 | - Proper GBuffer solution to extract material data and prevent having to do many draw calls of the scene (requires overhauling a significant portion of THREE.JS rendering pipeline) 🔴 17 | - Seperate out rendering of material properties, normals, metalness, etc. - Encode individual material maps into gbuffer when overriding shader (VERY difficult) - Shader swapping might be necessary 🔴 18 | - Potentially hijack internal shader of each material to create duplicate that writes to the G-Buffer 19 | - Sample according to BSDFs of materials for GI, rather than simple cosine-weighted hemisphere (allows for voxel reflections!!) 🟠 20 | ### Other Goals 21 | - General optimizations to denoising pipeline 🟡 22 | - Attempt the implementation of SVOs or some similar structure to accelerate tracing (initial attempts were not successful - it took longer to generate the acceleration structure than it did to trace the scene) 🔴 23 | - Allow moving around the volume being voxelized - to support GI in open worlds 🔴 24 | - Potentially have multiple cascades of voxels (the poor CPU!) ⚫️ 25 | ### Known Bugs 26 | - Startup voxelization is inconsistent (investigate) 🟡 -------------------------------------------------------------------------------- /VerticalBlurShader.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'https://unpkg.com/three@0.162.0/build/three.module.min.js'; 2 | 3 | const VerticalBlurShader = { 4 | 5 | uniforms: { 6 | 7 | 'tDiffuse': { value: null }, 8 | 'sceneDepth': { value: null }, 9 | 'blurSharp': { value: 0 }, 10 | 'depthBias': { value: 1.0 }, 11 | 'sceneMaterial': { value: null }, 12 | 'near': { value: 0 }, 13 | 'far': { value: 0 }, 14 | 'v': { value: 1.0 / 512.0 }, 15 | 'resolution': { value: new THREE.Vector2() }, 16 | 'blurThreshold': { value: 0.25 }, 17 | 'normalTexture': { value: null }, 18 | 'projectionMatrixInv': { value: new THREE.Matrix4() }, 19 | 'viewMatrixInv': { value: new THREE.Matrix4() }, 20 | "tSpecular": { value: null } 21 | 22 | 23 | }, 24 | 25 | vertexShader: /* glsl */ ` 26 | varying vec2 vUv; 27 | void main() { 28 | vUv = uv; 29 | gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); 30 | }`, 31 | 32 | fragmentShader: /* glsl */ ` 33 | layout(location = 1) out vec4 specular; 34 | uniform sampler2D tDiffuse; 35 | uniform sampler2D tSpecular; 36 | uniform sampler2D sceneDepth; 37 | uniform sampler2D sceneMaterial; 38 | uniform sampler2D normalTexture; 39 | uniform float blurSharp; 40 | uniform float v; 41 | uniform float near; 42 | uniform float far; 43 | uniform vec2 resolution; 44 | uniform float blurThreshold; 45 | uniform float depthBias; 46 | uniform mat4 projectionMatrixInv; 47 | uniform mat4 viewMatrixInv; 48 | varying vec2 vUv; 49 | 50 | float sdPlane( vec3 p, vec3 n, float h ) 51 | { 52 | // n must be normalized 53 | return dot(p,n) + h; 54 | } 55 | vec3 getWorldPos(float depth, vec2 coord) { 56 | float z = depth * 2.0 - 1.0; 57 | vec4 clipSpacePosition = vec4(coord * 2.0 - 1.0, z, 1.0); 58 | vec4 viewSpacePosition = projectionMatrixInv * clipSpacePosition; 59 | // Perspective division 60 | viewSpacePosition /= viewSpacePosition.w; 61 | vec4 worldSpacePosition = viewMatrixInv * viewSpacePosition; 62 | return worldSpacePosition.xyz; 63 | } 64 | float depthFalloff(vec2 uv, vec3 norm, float c) { 65 | vec3 uvPos = getWorldPos(texture2D(sceneDepth, uv).x, uv); 66 | return exp(-1.0 * depthBias * abs(sdPlane(uvPos, norm, c))); 67 | } 68 | float colorFalloff(vec2 uv, vec3 color) { 69 | vec3 color2 = texture2D(tDiffuse, uv).rgb; 70 | return exp(-0.5 * length(color - color2)); 71 | } 72 | vec3 computeNormal(vec3 worldPos) { 73 | vec2 downUv = vUv + vec2(0.0, 1.0 / resolution.y); 74 | vec3 downPos = getWorldPos(texture2D(sceneDepth, downUv).x, downUv).xyz; 75 | vec2 rightUv = vUv + vec2(1.0 / resolution.x, 0.0);; 76 | vec3 rightPos = getWorldPos(texture2D(sceneDepth, rightUv).x, rightUv).xyz; 77 | vec2 upUv = vUv - vec2(0.0, 1.0 / resolution.y); 78 | vec3 upPos = getWorldPos(texture2D(sceneDepth, upUv).x, upUv).xyz; 79 | vec2 leftUv = vUv - vec2(1.0 / resolution.x, 0.0); 80 | vec3 leftPos = getWorldPos(texture2D(sceneDepth, leftUv).x, leftUv).xyz; 81 | int hChoice; 82 | int vChoice; 83 | if (length(leftPos - worldPos) < length(rightPos - worldPos)) { 84 | hChoice = 0; 85 | } else { 86 | hChoice = 1; 87 | } 88 | if (length(upPos - worldPos) < length(downPos - worldPos)) { 89 | vChoice = 0; 90 | } else { 91 | vChoice = 1; 92 | } 93 | vec3 hVec; 94 | vec3 vVec; 95 | if (hChoice == 0 && vChoice == 0) { 96 | hVec = leftPos - worldPos; 97 | vVec = upPos - worldPos; 98 | } else if (hChoice == 0 && vChoice == 1) { 99 | hVec = leftPos - worldPos; 100 | vVec = worldPos - downPos; 101 | } else if (hChoice == 1 && vChoice == 1) { 102 | hVec = rightPos - worldPos; 103 | vVec = downPos - worldPos; 104 | } else if (hChoice == 1 && vChoice == 0) { 105 | hVec = rightPos - worldPos; 106 | vVec = worldPos - upPos; 107 | } 108 | return normalize(cross(hVec, vVec)); 109 | } 110 | void main() { 111 | float[9] weights = float[9](0.051, 0.0918, 0.12245, 0.1531, 0.1633, 0.1531, 0.12245, 0.0918, 0.051); 112 | float d = texture2D(sceneDepth, vUv).x; 113 | if (d == 1.0) { 114 | gl_FragColor = texture2D(tDiffuse, vUv); 115 | return; 116 | } 117 | vec3 uvWorldPos = getWorldPos(d, vUv); 118 | vec3 normal = (viewMatrixInv * (vec4(texture2D(normalTexture, vUv).rgb, 0.0))).xyz; 119 | vec4 matData = texture2D(sceneMaterial, vUv); 120 | float metalness = matData.r; 121 | float roughness = matData.g; 122 | float radius = v / resolution.y; //max(h * (1.0 - d) * (-blurSharp * pow(b - 0.5, 2.0) + 1.0), blurThreshold / resolution.x); 123 | vec3 planeNormal = normal; 124 | float planeConstant = -dot(uvWorldPos, normal); 125 | vec3 diffuseSum = vec3( 0.0 ); 126 | float weightSum = 0.0; 127 | for(float i = -4.0; i <= 4.0; i++) { 128 | vec2 sampleUv = vec2( vUv.x, vUv.y + i * radius ); 129 | vec2 clipRangeCheck = step(vec2(0.0),sampleUv.xy) * step(sampleUv.xy, vec2(1.0)); 130 | float w = weights[int(i + 4.0)] * depthFalloff(sampleUv, planeNormal, planeConstant) * clipRangeCheck.x * clipRangeCheck.y; 131 | diffuseSum += texture2D( tDiffuse, sampleUv).rgb * w ; 132 | weightSum += w; 133 | } 134 | diffuseSum /= weightSum; 135 | radius *= clamp(sqrt(roughness), 0.1 * (1.0-metalness), 1.0); 136 | vec3 specularSum = vec3( 0.0 ); 137 | weightSum = 0.0; 138 | for(float i = -4.0; i <= 4.0; i++) { 139 | vec2 sampleUv = vec2( vUv.x, vUv.y + i * radius ); 140 | vec2 clipRangeCheck = step(vec2(0.0),sampleUv.xy) * step(sampleUv.xy, vec2(1.0)); 141 | float w = weights[int(i + 4.0)] * depthFalloff(sampleUv, planeNormal, planeConstant) * clipRangeCheck.x * clipRangeCheck.y; 142 | specularSum += texture2D( tSpecular, sampleUv).rgb * w ; 143 | weightSum += w; 144 | } 145 | specularSum /= weightSum; 146 | 147 | 148 | gl_FragColor = vec4(diffuseSum, 1.0); 149 | specular = vec4(specularSum, 1.0); 150 | }` 151 | 152 | }; 153 | 154 | export { VerticalBlurShader }; -------------------------------------------------------------------------------- /VoxelColorShader.js: -------------------------------------------------------------------------------- 1 | const VoxelColorShader = { 2 | vertexShader: ` 3 | varying vec3 vNormal; 4 | void main() { 5 | vNormal = normal; 6 | gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); 7 | } 8 | `, 9 | vertexShader: /*glsl*/ ` 10 | in vec2 uv; 11 | in vec3 position; 12 | out vec2 vUv; 13 | void main() { 14 | vUv = uv; 15 | gl_Position = vec4(position, 1.0); 16 | } 17 | `, 18 | fragmentShader: /*glsl*/ ` 19 | #define varying in 20 | #define texture2D texture 21 | precision highp float; 22 | uniform mat4 viewMatrix; 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #if defined( USE_SHADOWMAP ) && ( NUM_DIR_LIGHT_SHADOWS > 0 ) 32 | uniform mat4 directionalShadowMatrix[ NUM_DIR_LIGHT_SHADOWS ]; 33 | #endif 34 | #if ( NUM_SPOT_LIGHT_SHADOWS > 0 ) 35 | uniform mat4 spotLightMatrix[ NUM_SPOT_LIGHT_SHADOWS ]; 36 | #endif 37 | uniform float time; 38 | uniform sampler2D sceneTex; 39 | uniform sampler2D sceneDepth; 40 | uniform mat4 projMat; 41 | uniform mat4 viewMat; 42 | uniform mat4 projectionMatrixInv; 43 | uniform mat4 viewMatrixInv; 44 | vec3 getWorldPos(float depth, vec2 coord) { 45 | float z = depth * 2.0 - 1.0; 46 | vec4 clipSpacePosition = vec4(coord * 2.0 - 1.0, z, 1.0); 47 | vec4 viewSpacePosition = projectionMatrixInv * clipSpacePosition; 48 | // Perspective division 49 | vec4 worldSpacePosition = viewSpacePosition; 50 | worldSpacePosition.xyz /= worldSpacePosition.w; 51 | worldSpacePosition = (viewMatrixInv * vec4(worldSpacePosition.xyz, 1.0)); 52 | return worldSpacePosition.xyz; 53 | } 54 | uniform highp isampler3D voxelTex; 55 | uniform highp sampler2D posTex; 56 | uniform highp sampler2D normalTex; 57 | uniform highp sampler2D uvTex; 58 | uniform highp isampler2D meshIndexTex; 59 | uniform highp usampler2D materialDataTexture; 60 | uniform highp sampler2D meshMatrixTex; 61 | uniform highp samplerCube environment; 62 | uniform int textureSize; 63 | uniform int posSize; 64 | uniform highp sampler2DArray mapAtlas; 65 | uniform float mapSize; 66 | uniform ivec3 VOXEL_AMOUNT; 67 | uniform vec3 boxCenter; 68 | uniform vec3 boxSize; 69 | layout(location = 0) out uvec4 pcColor1; 70 | layout(location = 1) out uvec4 pcColor2; 71 | 72 | in vec2 vUv; 73 | float dot2( in vec3 v ) { return dot(v,v); } 74 | float maxcomp( in vec2 v ) { return max(v.x,v.y); } 75 | precision highp isampler2D; 76 | 77 | vec4 sample1Dim( sampler2D s, int index, int size ) { 78 | int y = index / size; 79 | int x = index - y * size; 80 | return texelFetch(s, ivec2(x, y), 0); 81 | } 82 | ivec4 sample1Dimi( isampler2D s, int index, int size ) { 83 | int y = index / size; 84 | int x = index - y * size; 85 | return texelFetch(s, ivec2(x, y), 0); 86 | } 87 | vec3 closestTriangle( in vec3 v0, in vec3 v1, in vec3 v2, in vec3 p ) 88 | { 89 | vec3 v10 = v1 - v0; vec3 p0 = p - v0; 90 | vec3 v21 = v2 - v1; vec3 p1 = p - v1; 91 | vec3 v02 = v0 - v2; vec3 p2 = p - v2; 92 | vec3 nor = cross( v10, v02 ); 93 | vec3 q = cross( nor, p0 ); 94 | float d = 1.0/dot2(nor); 95 | float u = d*dot( q, v02 ); 96 | float v = d*dot( q, v10 ); 97 | float w = 1.0-u-v; 98 | 99 | if( u<0.0 ) { w = clamp( dot(p2,v02)/dot2(v02), 0.0, 1.0 ); u = 0.0; v = 1.0-w; } 100 | else if( v<0.0 ) { u = clamp( dot(p0,v10)/dot2(v10), 0.0, 1.0 ); v = 0.0; w = 1.0-u; } 101 | else if( w<0.0 ) { v = clamp( dot(p1,v21)/dot2(v21), 0.0, 1.0 ); w = 0.0; u = 1.0-v; } 102 | 103 | return u*v1 + v*v2 + w*v0; 104 | } 105 | 106 | vec3 bary( in vec3 v0, in vec3 v1, in vec3 v2, in vec3 p ) { 107 | vec3 normal = cross(v1 - v0, v2 - v0); 108 | float area = dot(cross(v1 - v0, v2 - v0), normal); 109 | 110 | vec3 pv0 = v0 - p; 111 | vec3 pv1 = v1 - p; 112 | vec3 pv2 = v2 - p; 113 | 114 | vec3 asub = vec3(dot(cross(pv1, pv2), normal), 115 | dot(cross(pv2, pv0), normal), 116 | dot(cross(pv0, pv1), normal)); 117 | return abs(asub) / vec3(abs(area)).xxx; 118 | } 119 | 120 | vec3 toWorldSpace(vec3 pos) { 121 | pos *= boxSize / vec3(VOXEL_AMOUNT); 122 | pos -= boxSize / 2.0; 123 | pos += boxCenter; 124 | return pos; 125 | } 126 | vec3 toVoxelSpace(vec3 pos) { 127 | pos -= boxCenter; 128 | pos += boxSize / 2.0; 129 | pos *= vec3(VOXEL_AMOUNT) / boxSize; 130 | return pos; 131 | } 132 | uint quantizeToBits(float num, float m, float bitsPowTwo) { 133 | num = clamp(num, 0.0, m); 134 | return uint(bitsPowTwo * sqrt(num / m)); 135 | } 136 | uint packThreeBytes(vec3 light) { 137 | float maxNum = 10.0; 138 | float bitsPowTwo = 1023.0; 139 | uint r = quantizeToBits(light.r, maxNum, bitsPowTwo); 140 | uint g = quantizeToBits(light.g, maxNum, bitsPowTwo); 141 | uint b = quantizeToBits(light.b, maxNum, bitsPowTwo); 142 | 143 | return r << 20 | g << 10 | b; 144 | } 145 | float unquantizeToBits(uint num, float m, float bitsPowTwo) { 146 | float t = float(num) / bitsPowTwo; 147 | return (t * t * m); 148 | } 149 | vec3 unpackRGB(uint packedInt) { 150 | float maxNum = 10.0; 151 | float bitsPowTwo = 1023.0; 152 | float r = unquantizeToBits(packedInt >> 20u, maxNum, bitsPowTwo); 153 | float g = unquantizeToBits((packedInt >> 10u) & 1023u, maxNum, bitsPowTwo); 154 | float b = unquantizeToBits(packedInt & 1023u, maxNum, bitsPowTwo); 155 | return vec3(r, g, b); 156 | 157 | } 158 | float hash(float n) { 159 | return fract(sin(n) * 43758.5453123); 160 | } 161 | 162 | vec3 randomColor(float seed) { 163 | float r = hash(seed); 164 | float g = hash(seed + 1.0); 165 | float b = hash(seed + 2.0); 166 | return vec3(r, g, b); 167 | } 168 | 169 | 170 | void main() { 171 | int index = int(gl_FragCoord.y) * textureSize + int(gl_FragCoord.x); 172 | int voxelZ = index / (VOXEL_AMOUNT.x * VOXEL_AMOUNT.y); 173 | int voxelY = (index - voxelZ * VOXEL_AMOUNT.x * VOXEL_AMOUNT.y) / VOXEL_AMOUNT.x; 174 | int voxelX = index - voxelZ * VOXEL_AMOUNT.x * VOXEL_AMOUNT.y - voxelY * VOXEL_AMOUNT.x; 175 | int sampledIndex = texelFetch(voxelTex, ivec3(voxelX, voxelY, voxelZ), 0).r; 176 | if (sampledIndex < 0) { 177 | pcColor1 = uvec4(0, 0, 0, 0); 178 | pcColor2 = uvec4(0, 0, 0, 0); 179 | } else { 180 | int meshIndex = sample1Dimi(meshIndexTex, sampledIndex * 3, posSize).r; 181 | mat4 worldMatrix; 182 | worldMatrix = (mat4( 183 | texelFetch(meshMatrixTex, ivec2(0, meshIndex), 0), 184 | texelFetch(meshMatrixTex, ivec2(1, meshIndex), 0), 185 | texelFetch(meshMatrixTex, ivec2(2, meshIndex), 0), 186 | texelFetch(meshMatrixTex, ivec2(3, meshIndex), 0) 187 | )) ; 188 | // worldMatrix = [meshIndex]; 189 | 190 | // Get y rotation of world matrix 191 | 192 | 193 | // Compute normal matrix by normalizing the rotation part of the world matrix 194 | mat3 normalMatrix = transpose(mat3(inverse(worldMatrix))); 195 | vec3 posA =(worldMatrix * vec4(sample1Dim(posTex, sampledIndex * 3, posSize).xyz, 1.0)).xyz; 196 | vec3 posB = (worldMatrix * vec4(sample1Dim(posTex, sampledIndex * 3 + 1, posSize).xyz, 1.0)).xyz; 197 | vec3 posC = (worldMatrix * vec4(sample1Dim(posTex, sampledIndex * 3 + 2, posSize).xyz, 1.0)).xyz; 198 | // Get barycoords 199 | vec3 worldPos = closestTriangle(posA, posB, posC, toWorldSpace(vec3(voxelX, voxelY, voxelZ) + vec3(0.5))); 200 | vec3 baryCoords = bary(posA, posB, posC, worldPos); 201 | 202 | // Get normals 203 | vec3 normalA = normalMatrix * sample1Dim(normalTex, sampledIndex * 3, posSize).xyz; 204 | vec3 normalB = normalMatrix * sample1Dim(normalTex, sampledIndex * 3 + 1, posSize).xyz; 205 | vec4 normalCInitial = sample1Dim(normalTex, sampledIndex * 3 + 2, posSize); 206 | vec3 normalC = normalMatrix * normalCInitial.xyz; 207 | int materialIndex = int(normalCInitial.w); 208 | vec3 interpolatedNormal = normalize(normalA * baryCoords.x + normalB * baryCoords.y + normalC * baryCoords.z); 209 | vec2 uvA = sample1Dim(uvTex, sampledIndex * 3, posSize).xy; 210 | vec2 uvB = sample1Dim(uvTex, sampledIndex * 3 + 1, posSize).xy; 211 | vec2 uvC = sample1Dim(uvTex, sampledIndex * 3 + 2, posSize).xy; 212 | vec2 interpolatedUV = uvA * baryCoords.x + uvB * baryCoords.y + uvC * baryCoords.z; 213 | 214 | 215 | float uvSpanX = max(max(uvA.x, uvB.x), uvC.x) - min(min(uvA.x, uvB.x), uvC.x); 216 | float uvSpanY = max(max(uvA.y, uvB.y), uvC.y) - min(min(uvA.y, uvB.y), uvC.y); 217 | 218 | vec3 voxelRatio = vec3(VOXEL_AMOUNT) / boxSize; 219 | float areaOfTriangle = 0.5 * length(cross(posB - posA, posC - posA)) * max(voxelRatio.x, max(voxelRatio.y, voxelRatio.z)); 220 | float xChange = 1024.0 * uvSpanX * (1.0 / sqrt(areaOfTriangle)); 221 | float yChange = 1024.0 * uvSpanY * (1.0 / sqrt(areaOfTriangle)); 222 | float mipLevel = log2( 223 | max( 224 | xChange, 225 | yChange 226 | ) / 4.0 227 | ); 228 | 229 | // Get texture 230 | vec2 uv = vec2(interpolatedUV.x, interpolatedUV.y); 231 | uvec4 materialData = texelFetch(materialDataTexture, ivec2(materialIndex, 0), 0); 232 | vec3 mrn = unpackRGB(materialData.b); 233 | float metalness = mrn.r; 234 | float roughness = mrn.g; 235 | float mapIndex = float(materialData.w); 236 | vec3 color = unpackRGB( 237 | materialData.g 238 | ); 239 | vec3 emissive = unpackRGB( 240 | materialData.r 241 | );//materials[materialIndex].emissive; 242 | vec4 sampledTexel = textureLod(mapAtlas, vec3(uv, mapIndex), mipLevel); 243 | /*vec3 accumulatedLight = vec3(0.0); 244 | vec3 accumulatedLightBack = vec3(0.0);*/ 245 | vec3[6] cardinals = vec3[6]( 246 | vec3(1.0, 0.0, 0.0), 247 | vec3(-1.0, 0.0, 0.0), 248 | vec3(0.0, 1.0, 0.0), 249 | vec3(0.0, -1.0, 0.0), 250 | vec3(0.0, 0.0, 1.0), 251 | vec3(0.0, 0.0, -1.0) 252 | ); 253 | vec3[6] accumulatedLight = vec3[6]( 254 | vec3(0.0), 255 | vec3(0.0), 256 | vec3(0.0), 257 | vec3(0.0), 258 | vec3(0.0), 259 | vec3(0.0) 260 | ); 261 | 262 | #if ( NUM_DIR_LIGHTS > 0 ) 263 | vec3 lightDirection; 264 | vec4 shadowCoord; 265 | float s, shadow; 266 | #pragma unroll_loop_start 267 | for(int i = 0; i < NUM_DIR_LIGHTS; i++) { 268 | 269 | lightDirection = directionalLights[ i ].direction; 270 | 271 | //float incidentLight = max(dot( interpolatedNormal, lightDirection ), 0.0); 272 | // float incidentLightBack = max(dot( -interpolatedNormal, lightDirection ), 0.0); 273 | s = 1.0; 274 | #if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS ) 275 | shadowCoord = directionalShadowMatrix[i] * vec4(worldPos, 1.0); 276 | shadow = getShadow(directionalShadowMap[i], directionalLightShadows[i].shadowMapSize, directionalLightShadows[i].shadowBias, directionalLightShadows[i].shadowRadius, shadowCoord); 277 | s *= shadow; 278 | #endif 279 | if (s > 0.0) { 280 | for (int j = 0; j < 6; j++) { 281 | accumulatedLight[j] += (directionalLights[ i ].color / 3.14159) * s * sampledTexel.rgb * color * (1.0 - metalness) * max(dot(cardinals[j], lightDirection), 0.0); 282 | } 283 | } 284 | } 285 | #pragma unroll_loop_end 286 | #endif 287 | 288 | #if (NUM_POINT_LIGHTS > 0) 289 | PointLight pointLight; 290 | IncidentLight directLight; 291 | #if defined( USE_SHADOWMAP ) && NUM_POINT_LIGHT_SHADOWS > 0 292 | PointLightShadow pointLightShadow; 293 | #endif 294 | #pragma unroll_loop_start 295 | for ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) { 296 | 297 | pointLight = pointLights[ i ]; 298 | 299 | getPointLightInfo( pointLight, worldPos, directLight ); 300 | 301 | #if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_POINT_LIGHT_SHADOWS ) 302 | pointLightShadow = pointLightShadows[ i ]; 303 | directLight.color *= ( directLight.visible ) ? getPointShadow( pointShadowMap[ i ], pointLightShadow.shadowMapSize, pointLightShadow.shadowBias, pointLightShadow.shadowRadius, vec4( 304 | worldPos - pointLight.position , 1.0 305 | ), pointLightShadow.shadowCameraNear, pointLightShadow.shadowCameraFar ) : 1.0; 306 | #endif 307 | 308 | for (int j = 0; j < 6; j++) { 309 | accumulatedLight[j] += (directLight.color / 3.14159) * sampledTexel.rgb * color * (1.0 - metalness) * max(dot(cardinals[j], directLight.direction), 0.0); 310 | } 311 | } 312 | #pragma unroll_loop_end 313 | #endif 314 | 315 | 316 | #if (NUM_SPOT_LIGHTS > 0) 317 | SpotLight spotLight; 318 | IncidentLight directLight; 319 | #if defined( USE_SHADOWMAP ) && NUM_SPOT_LIGHT_SHADOWS > 0 320 | SpotLightShadow spotLightShadow; 321 | #endif 322 | #pragma unroll_loop_start 323 | for ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) { 324 | 325 | spotLight = spotLights[ i ]; 326 | 327 | getSpotLightInfo( spotLight, worldPos, directLight ); 328 | 329 | #if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS ) 330 | spotLightShadow = spotLightShadows[ i ]; 331 | directLight.color *= ( directLight.visible ) ? getShadow( spotShadowMap[ i ], spotLightShadow.shadowMapSize, spotLightShadow.shadowBias, spotLightShadow.shadowRadius, spotLightMatrix[i] * vec4(worldPos, 1.0) ) : 1.0; 332 | #endif 333 | for (int j = 0; j < 6; j++) { 334 | accumulatedLight[j] += (directLight.color / 3.14159) * sampledTexel.rgb * color * (1.0 - metalness) * max(dot(cardinals[j], directLight.direction), 0.0); 335 | } 336 | } 337 | #pragma unroll_loop_end 338 | #endif 339 | 340 | /* accumulatedLight += emissive; 341 | accumulatedLightBack += emissive;*/ 342 | for(int j = 0; j < 6; j++) { 343 | accumulatedLight[j] += emissive; 344 | } 345 | 346 | 347 | 348 | 349 | 350 | vec3 center = (posA + posB + posC) / 3.0; 351 | /* pcColor1 = 352 | uvec4( 353 | packThreeBytes(vec3( 354 | accumulatedLight 355 | //randomColor(100.0*float(meshIndex)) 356 | )), 357 | packThreeBytes(vec3( 358 | accumulatedLightBack //randomColor(100.0*float(meshIndex)) 359 | )), 360 | packThreeBytes(interpolatedNormal * 0.5 + 0.5) 361 | , 362 | 1); 363 | pcColor2 = uvec4(0, 0, 0, 0);*/ 364 | pcColor1 = uvec4( 365 | packThreeBytes(accumulatedLight[0]), 366 | packThreeBytes(accumulatedLight[1]), 367 | packThreeBytes(accumulatedLight[2]), 368 | 1 369 | ); 370 | pcColor2 = uvec4( 371 | packThreeBytes(accumulatedLight[3]), 372 | packThreeBytes(accumulatedLight[4]), 373 | packThreeBytes(accumulatedLight[5]), 374 | 1 375 | ); 376 | //vec4(unpackRGBAToDepth(vec4(accumulatedLight, 1.0)), unpackRGBAToDepth(vec4(accumulatedLightBack, 1.0)), unpackRGBAToDepth(vec4(0.5 + 0.5 * interpolatedNormal, 1.0)), 1.0); 377 | 378 | 379 | } 380 | } 381 | ` 382 | } 383 | export { VoxelColorShader }; -------------------------------------------------------------------------------- /VoxelModule.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'https://unpkg.com/three@0.162.0/build/three.module.min.js'; 2 | import { FullScreenQuad } from "https://unpkg.com/three/examples/jsm/postprocessing/Pass.js"; 3 | import { VoxelColorShader } from './VoxelColorShader.js'; 4 | import { packRGBToUint32, createBufferTexture, imageToDataTexture, createGBufferSplit, computeSplits } from './utils.js'; 5 | 6 | export class VoxelModule { 7 | constructor({ scene, renderer, VOXEL_AMOUNT, voxelCenter, voxelSize, workers }) { 8 | this.scene = scene; 9 | this.renderer = renderer; 10 | this.VOXEL_AMOUNT = VOXEL_AMOUNT; 11 | this.voxelCenter = voxelCenter; 12 | this.voxelSize = voxelSize; 13 | this.workers = []; 14 | this.workerCount = workers; 15 | this.onBeforeVoxelization = function() {}; 16 | this.onAfterVoxelization = function() {}; 17 | } 18 | 19 | init() { 20 | this.children = []; 21 | this.scene.traverse((child) => { 22 | if (child.isMesh && child.geometry.index) { 23 | child.updateMatrixWorld(true); 24 | this.children.push(child); 25 | } 26 | }); 27 | 28 | 29 | this.meshMatrixData = new Float32Array(new SharedArrayBuffer(this.children.length * 4 * 4 * 4)); 30 | this.meshMatrixTex = new THREE.DataTexture(this.meshMatrixData, 4, this.children.length, THREE.RGBAFormat, THREE.FloatType); 31 | this.meshMatrixTex.needsUpdate = true; 32 | this.indexArray = new Int32Array(new SharedArrayBuffer(this.VOXEL_AMOUNT.z * this.VOXEL_AMOUNT.y * this.VOXEL_AMOUNT.x * 4)); 33 | this.voxelRenderTargetSize = Math.ceil(Math.sqrt(this.VOXEL_AMOUNT.z * this.VOXEL_AMOUNT.y * this.VOXEL_AMOUNT.x)); 34 | this.voxelRenderTarget = new THREE.WebGLRenderTarget(this.voxelRenderTargetSize, this.voxelRenderTargetSize, { 35 | minFilter: THREE.NearestFilter, 36 | magFilter: THREE.NearestFilter, 37 | format: THREE.RGBAIntegerFormat, 38 | type: THREE.UnsignedIntType, 39 | internalFormat: "RGBA32UI", 40 | count: 2 41 | }); 42 | 43 | this.indexTex = new THREE.Data3DTexture(this.indexArray, this.VOXEL_AMOUNT.x, this.VOXEL_AMOUNT.y, this.VOXEL_AMOUNT.z); 44 | this.indexTex.format = THREE.RedIntegerFormat; 45 | this.indexTex.type = THREE.IntType; 46 | this.indexTex.colorSpace = THREE.NoColorSpace; 47 | this.indexTex.minFilter = THREE.NearestFilter; 48 | this.indexTex.maxFilter = THREE.NearestFilter; 49 | 50 | let total = 0; 51 | this.children.forEach((child) => { 52 | total += child.geometry.index.array.length; 53 | }); 54 | this.MAX_POINTS = total; 55 | const MAX_POINTS = this.MAX_POINTS; 56 | 57 | const posTexSize = Math.ceil(Math.sqrt(MAX_POINTS)); 58 | const meshIndexBuffer = new Int32Array(posTexSize * posTexSize); 59 | const meshIndexTex = new THREE.DataTexture(meshIndexBuffer, posTexSize, posTexSize, THREE.RedIntegerFormat, THREE.IntType); 60 | meshIndexTex.minFilter = THREE.NearestFilter; 61 | meshIndexTex.maxFilter = THREE.NearestFilter; 62 | meshIndexTex.needsUpdate = true; 63 | 64 | const materials = []; 65 | /* scene.traverse((child) => { 66 | if (child.isMesh && child.geometry.index) { 67 | if (materials.indexOf(child.material) === -1) { 68 | materials.push(child.material); 69 | } 70 | child.materialIndex = materials.indexOf(child.material); 71 | } 72 | });*/ 73 | this.children.forEach((child) => { 74 | if (materials.indexOf(child.material) === -1) { 75 | materials.push(child.material); 76 | } 77 | child.materialIndex = materials.indexOf(child.material); 78 | }); 79 | 80 | /* let maps = [ 81 | await new THREE.TextureLoader().loadAsync('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAA1JREFUGFdj+P///38ACfsD/QVDRcoAAAAASUVORK5CYII='), 82 | ];*/ 83 | // Use data texture 84 | let maps = [ 85 | new THREE.DataTexture(new Uint8Array([255, 255, 255, 255]), 1, 1, THREE.RGBAFormat, THREE.UnsignedByteType) 86 | ] 87 | for (let i = 0; i < materials.length; i++) { 88 | if (materials[i].map) { 89 | maps.push(materials[i].map); 90 | materials[i].mapIndex = maps.length - 1; 91 | } else { 92 | materials[i].mapIndex = 0; 93 | } 94 | } 95 | 96 | const materialInfoBuffer = new Uint32Array(materials.length * 4); 97 | this.materials = materials; 98 | this.materialInfoBuffer = materialInfoBuffer; 99 | for (let i = 0; i < materials.length; i++) { 100 | materialInfoBuffer[i * 4] = packRGBToUint32(new THREE.Vector3(materials[i].emissive.r, materials[i].emissive.g, materials[i].emissive.b)); 101 | materialInfoBuffer[i * 4 + 1] = packRGBToUint32(new THREE.Vector3(materials[i].color.r, materials[i].color.g, materials[i].color.b)); 102 | materialInfoBuffer[i * 4 + 2] = packRGBToUint32(new THREE.Vector3( 103 | materials[i].metalnessMap ? 0 : materials[i].metalness, 104 | materials[i].roughnessMap ? 0 : materials[i].roughness, 105 | 0.0)); 106 | materialInfoBuffer[i * 4 + 3] = materials[i].mapIndex; 107 | } 108 | 109 | const materialDataTexture = new THREE.DataTexture(materialInfoBuffer, materials.length, 1); 110 | materialDataTexture.minFilter = THREE.NearestFilter; 111 | materialDataTexture.maxFilter = THREE.NearestFilter; 112 | materialDataTexture.format = THREE.RGBAIntegerFormat; 113 | materialDataTexture.type = THREE.UnsignedIntType; 114 | materialDataTexture.internalFormat = "RGBA32UI"; 115 | materialDataTexture.needsUpdate = true; 116 | this.materialDataTexture = materialDataTexture; 117 | 118 | const TARGET_SIZE_X = 1024; 119 | const TARGET_SIZE_Y = 1024; 120 | maps = maps.map((map, i) => { 121 | if (i === 0) { 122 | const blank = new ImageData(TARGET_SIZE_X, TARGET_SIZE_Y); 123 | for (let i = 0; i < blank.data.length; i++) { 124 | blank.data[i] = 255; 125 | } 126 | return blank; 127 | } 128 | return imageToDataTexture(map, TARGET_SIZE_X, TARGET_SIZE_Y); 129 | }); 130 | 131 | const mapAtlasArray = new Uint8Array(TARGET_SIZE_X * TARGET_SIZE_Y * maps.length * 4); 132 | for (let i = 0; i < maps.length; i++) { 133 | mapAtlasArray.set(maps[i].data, i * TARGET_SIZE_X * TARGET_SIZE_Y * 4); 134 | } 135 | 136 | const mapAtlas = new THREE.DataArrayTexture(mapAtlasArray, TARGET_SIZE_X, TARGET_SIZE_Y, maps.length); 137 | mapAtlas.format = THREE.RGBAFormat; 138 | mapAtlas.type = THREE.UnsignedByteType; 139 | mapAtlas.minFilter = THREE.LinearMipMapLinearFilter; 140 | mapAtlas.magFilter = THREE.LinearFilter; 141 | mapAtlas.wrapS = THREE.RepeatWrapping; 142 | mapAtlas.wrapT = THREE.RepeatWrapping; 143 | mapAtlas.colorSpace = "srgb"; 144 | mapAtlas.generateMipmaps = true; 145 | mapAtlas.needsUpdate = true; 146 | this.mapAtlas = mapAtlas; 147 | this.posTexSize = Math.ceil(Math.sqrt(this.MAX_POINTS)); 148 | this.posBufferAux = new Float32Array(new SharedArrayBuffer(this.posTexSize * this.posTexSize * 4 * 4)); 149 | [this.posBufferTex, this.posTex] = createBufferTexture(this.posTexSize); 150 | [this.normalAuxBuffer, this.normalTex] = createBufferTexture(this.posTexSize); 151 | [this.uvAuxBuffer, this.uvTex] = createBufferTexture(this.posTexSize); 152 | 153 | this.meshIndexBuffer = new Int32Array(this.posTexSize * this.posTexSize); 154 | this.meshIndexTex = new THREE.DataTexture(this.meshIndexBuffer, this.posTexSize, this.posTexSize, THREE.RedIntegerFormat, THREE.IntType); 155 | this.meshIndexTex.minFilter = THREE.NearestFilter; 156 | this.meshIndexTex.maxFilter = THREE.NearestFilter; 157 | this.meshIndexTex.needsUpdate = true; 158 | 159 | for (let i = 0; i < this.workerCount; i++) { 160 | const worker = new Worker('./voxel-worker.js', { type: "module" }); 161 | this.workers.push(worker); 162 | } 163 | 164 | let posCount = 0; 165 | let uvCount = 0; 166 | let indexCount = 0; 167 | let normalCount = 0; 168 | for (let i = 0; i < this.children.length; i++) { 169 | const child = this.children[i]; 170 | const positions = child.geometry.attributes.position.array; 171 | const uvs = child.geometry.attributes.uv.array; 172 | const indices = child.geometry.index.array; 173 | const normals = child.geometry.attributes.normal.array; 174 | const iLen = indices.length; 175 | child.meshIndex = i; 176 | 177 | for (let j = 0; j < iLen; j++) { 178 | const i = indices[j]; 179 | this.posBufferTex[posCount++] = positions[i * 3]; 180 | this.posBufferTex[posCount++] = positions[i * 3 + 1]; 181 | this.posBufferTex[posCount++] = positions[i * 3 + 2]; 182 | this.posBufferTex[posCount++] = 0; 183 | this.uvAuxBuffer[uvCount++] = uvs[i * 2]; 184 | this.uvAuxBuffer[uvCount++] = uvs[i * 2 + 1]; 185 | this.uvAuxBuffer[uvCount++] = 0.0; 186 | this.uvAuxBuffer[uvCount++] = 0.0; 187 | this.meshIndexBuffer[indexCount++] = child.meshIndex; 188 | this.normalAuxBuffer[normalCount++] = normals[i * 3]; 189 | this.normalAuxBuffer[normalCount++] = normals[i * 3 + 1]; 190 | this.normalAuxBuffer[normalCount++] = normals[i * 3 + 2]; 191 | this.normalAuxBuffer[normalCount++] = child.materialIndex; 192 | } 193 | } 194 | 195 | this.posTex.needsUpdate = true; 196 | this.normalTex.needsUpdate = true; 197 | this.uvTex.needsUpdate = true; 198 | this.meshIndexTex.needsUpdate = true; 199 | 200 | this.meshIndexData = []; 201 | let sum = 0; 202 | for (let i = 0; i < this.children.length; i++) { 203 | this.meshIndexData[i] = sum; 204 | sum += this.children[i].geometry.index.array.length; 205 | } 206 | 207 | this.meshIndexSplits = []; 208 | const splitSize = Math.ceil(sum / this.workerCount); 209 | for (let i = 0; i < this.workerCount; i++) { 210 | this.meshIndexSplits[i] = this.meshIndexData.findIndex((value) => value >= splitSize * (i + 1)); 211 | } 212 | this.meshIndexSplits[this.workerCount - 1] = this.children.length; 213 | 214 | for (let i = 0; i < this.workerCount; i++) { 215 | const worker = this.workers[i]; 216 | const startIndex = i === 0 ? 0 : this.meshIndexSplits[i - 1]; 217 | const endIndex = this.meshIndexSplits[i]; 218 | for (let j = startIndex; j < endIndex; j++) { 219 | worker.postMessage({ 220 | type: "add", 221 | data: { 222 | id: j, 223 | position: this.children[j].geometry.attributes.position.array, 224 | index: this.children[j].geometry.index.array, 225 | } 226 | }); 227 | } 228 | } 229 | let s = 0; 230 | for (let i = 0; i < this.children.length; i++) { 231 | const child = this.children[i]; 232 | child.meshIndex = i; 233 | s += child.geometry.index.array.length; 234 | } 235 | this.posSum = s; 236 | 237 | this.firstVoxelization = true; 238 | this.sahSplits = []; 239 | 240 | let vs = VoxelColorShader.vertexShader; 241 | let fs = VoxelColorShader.fragmentShader; 242 | const lights = []; 243 | this.scene.traverse((child) => { 244 | if (child.isLight) { 245 | lights.push(child); 246 | } 247 | }); 248 | fs = fs.replace(`#include `, THREE.ShaderChunk.lights_pars_begin); 249 | fs = fs.replace(`#include `, THREE.ShaderChunk.shadowmap_pars_fragment); 250 | if (this.renderer.shadowMap.enabled) { 251 | fs = "#define USE_SHADOWMAP\n" + fs; 252 | } 253 | if (this.renderer.shadowMap.type === THREE.PCFShadowMap) { 254 | fs = "#define SHADOWMAP_TYPE_PCF\n" + fs; 255 | } else if (this.renderer.shadowMap.type === THREE.PCFSoftShadowMap) { 256 | fs = "#define SHADOWMAP_TYPE_PCF_SOFT\n" + fs; 257 | } else if (this.renderer.shadowMap.type === THREE.VSMShadowMap) { 258 | fs = "#define SHADOWMAP_TYPE_VSM\n" + fs; 259 | } 260 | fs = fs.replace(/NUM_DIR_LIGHTS/g, lights.filter((light) => light.isDirectionalLight).length); 261 | fs = fs.replace(/NUM_DIR_LIGHT_SHADOWS/g, lights.filter((light) => light.isDirectionalLight && light.castShadow).length); 262 | fs = fs.replace(/NUM_POINT_LIGHTS/g, lights.filter((light) => light.isPointLight).length); 263 | fs = fs.replace(/NUM_POINT_LIGHT_SHADOWS/g, lights.filter((light) => light.isPointLight && light.castShadow).length); 264 | fs = fs.replace(/NUM_SPOT_LIGHTS/g, lights.filter((light) => light.isSpotLight).length); 265 | fs = fs.replace(/NUM_SPOT_LIGHT_SHADOWS/g, lights.filter((light) => light.isSpotLight && light.castShadow).length); 266 | this.voxelColorShader = new FullScreenQuad(new THREE.RawShaderMaterial({ 267 | lights: false, 268 | uniforms: { 269 | ...THREE.UniformsLib.lights, 270 | voxelTex: { value: this.indexTex }, 271 | posTex: { value: this.posTex }, 272 | normalTex: { value: this.normalTex }, 273 | uvTex: { value: this.uvTex }, 274 | textureSize: { value: this.voxelRenderTargetSize }, 275 | posSize: { value: this.posTexSize }, 276 | VOXEL_AMOUNT: { value: this.VOXEL_AMOUNT }, 277 | boxCenter: { value: this.voxelCenter }, 278 | boxSize: { value: this.voxelSize }, 279 | mapAtlas: { value: this.mapAtlas }, 280 | environment: { value: null }, 281 | meshIndexTex: { value: this.meshIndexTex }, 282 | mapSize: { value: 1024 }, 283 | materialDataTexture: { value: this.materialDataTexture }, 284 | meshMatrixTex: { value: this.meshMatrixTex }, 285 | time: { value: 0.0 }, 286 | sceneTex: { value: null }, 287 | sceneDepth: { value: null }, 288 | viewMat: { value: new THREE.Matrix4() }, 289 | projMat: { value: new THREE.Matrix4() }, 290 | viewMatrixInv: { value: new THREE.Matrix4() }, 291 | projectionMatrixInv: { value: new THREE.Matrix4() }, 292 | }, 293 | vertexShader: vs, 294 | fragmentShader: fs, 295 | glslVersion: THREE.GLSL3 296 | })); 297 | 298 | //this.voxelColorShader._mesh.add( 299 | 300 | /* this.voxelColorShader.material.onBeforeCompile = (shader) => { 301 | console.log(shader.fragmentShader); 302 | }*/ 303 | this.updating = false; 304 | 305 | } 306 | 307 | update() { 308 | this.voxelColorShader.material.uniforms["time"].value = performance.now() / 1000; 309 | } 310 | 311 | setUniform(key, value) { 312 | this.voxelColorShader.material.uniforms[key].value = value; 313 | } 314 | 315 | async updateVoxels() { 316 | this.onBeforeVoxelization(); 317 | this.updating = true; 318 | this.indexArray.fill(-1); 319 | for (let i = 0; i < this.children.length; i++) { 320 | const child = this.children[i]; 321 | child.updateWorldMatrix(false, false); 322 | const transform = child.matrixWorld; 323 | transform.toArray(this.meshMatrixData, i * 16); 324 | } 325 | 326 | const positionPromises = this.workers.map((worker, i) => new Promise((resolve, reject) => { 327 | worker.onmessage = (e) => { 328 | resolve(); 329 | }; 330 | const startIndex = i === 0 ? 0 : this.meshIndexSplits[i - 1]; 331 | const endIndex = this.meshIndexSplits[i]; 332 | worker.postMessage({ 333 | type: "transform", 334 | data: { 335 | meshMatrixData: this.meshMatrixData, 336 | posBufferAux: this.posBufferAux, 337 | startIndex: this.meshIndexData[startIndex] * 4, 338 | startMesh: startIndex, 339 | endMesh: endIndex, 340 | } 341 | }); 342 | })); 343 | 344 | await Promise.all(positionPromises); 345 | const posBufferCount = 4 * this.posSum; 346 | const posArray = this.posBufferAux.slice(0, posBufferCount); 347 | 348 | if (this.firstVoxelization) { 349 | this.sahSplits = computeSplits({ 350 | posArray, 351 | posBufferCount, 352 | sum: this.posSum, 353 | workerCount: this.workerCount, 354 | VOXEL_RATIO_MAX: Math.max( 355 | this.VOXEL_AMOUNT.x / this.voxelSize.x, 356 | this.VOXEL_AMOUNT.y / this.voxelSize.y, 357 | this.VOXEL_AMOUNT.z / this.voxelSize.z 358 | ) 359 | }); 360 | this.firstVoxelization = false; 361 | } 362 | 363 | const pLen = posArray.length; 364 | const workerIndexLength = Math.ceil(pLen / this.workerCount / 12) * 12; 365 | const promises = this.workers.map((worker, i) => new Promise((resolve, reject) => { 366 | worker.onmessage = (e) => { 367 | resolve(); 368 | }; 369 | const startIndex = i === 0 ? 0 : this.sahSplits[i - 1] * 12; 370 | const endIndex = this.sahSplits[i] * 12; 371 | worker.postMessage({ 372 | type: "voxelize", 373 | data: { 374 | posArray: posArray.slice(startIndex, endIndex), 375 | voxelCenter: this.voxelCenter, 376 | voxelSize: this.voxelSize, 377 | VOXEL_AMOUNT: this.VOXEL_AMOUNT, 378 | indexArray: this.indexArray, 379 | indexOffset: startIndex / 12, 380 | } 381 | }); 382 | })); 383 | 384 | await Promise.all(promises); 385 | this.indexTex.needsUpdate = true; 386 | this.meshMatrixTex.needsUpdate = true; 387 | 388 | this.renderer.setRenderTarget(this.voxelRenderTarget); 389 | this.renderer.clear(); 390 | this.voxelColorShader.render(this.renderer); 391 | 392 | this.onAfterVoxelization(); 393 | this.updating = false; 394 | 395 | } 396 | 397 | getIndexTexture() { 398 | return this.indexTex; 399 | } 400 | 401 | getVoxelRenderTarget() { 402 | return this.voxelRenderTarget; 403 | } 404 | 405 | getVoxelRenderTargetSize() { 406 | return this.voxelRenderTargetSize; 407 | } 408 | setUniform(key, value) { 409 | this.voxelColorShader.material.uniforms[key].value = value; 410 | } 411 | updateMaterialDataTexture() { 412 | const materials = this.materials; 413 | const materialInfoBuffer = this.materialInfoBuffer; 414 | for (let i = 0; i < materials.length; i++) { 415 | materialInfoBuffer[i * 4] = packRGBToUint32(new THREE.Vector3(materials[i].emissive.r, materials[i].emissive.g, materials[i].emissive.b)); 416 | materialInfoBuffer[i * 4 + 1] = packRGBToUint32(new THREE.Vector3(materials[i].color.r, materials[i].color.g, materials[i].color.b)); 417 | materialInfoBuffer[i * 4 + 2] = packRGBToUint32(new THREE.Vector3( 418 | materials[i].metalnessMap ? 0 : materials[i].metalness, 419 | materials[i].roughnessMap ? 0 : materials[i].roughness, 420 | 0.0)); 421 | materialInfoBuffer[i * 4 + 3] = materials[i].mapIndex; 422 | } 423 | this.materialDataTexture.needsUpdate = true; 424 | } 425 | } -------------------------------------------------------------------------------- /bluenoise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/N8python/N8GI/0afc845566f272c5bcf6ec7a4317d7083f7df2d1/bluenoise.png -------------------------------------------------------------------------------- /coi-serviceworker.js: -------------------------------------------------------------------------------- 1 | let coepCredentialless = false; 2 | if (typeof window === 'undefined') { 3 | self.addEventListener("install", () => self.skipWaiting()); 4 | self.addEventListener("activate", (event) => event.waitUntil(self.clients.claim())); 5 | 6 | self.addEventListener("message", (ev) => { 7 | if (!ev.data) { 8 | return; 9 | } else if (ev.data.type === "deregister") { 10 | self.registration 11 | .unregister() 12 | .then(() => { 13 | return self.clients.matchAll(); 14 | }) 15 | .then(clients => { 16 | clients.forEach((client) => client.navigate(client.url)); 17 | }); 18 | } else if (ev.data.type === "coepCredentialless") { 19 | coepCredentialless = ev.data.value; 20 | } 21 | }); 22 | 23 | self.addEventListener("fetch", function(event) { 24 | const r = event.request; 25 | if (r.cache === "only-if-cached" && r.mode !== "same-origin") { 26 | return; 27 | } 28 | 29 | const request = (coepCredentialless && r.mode === "no-cors") ? 30 | new Request(r, { 31 | credentials: "omit", 32 | }) : 33 | r; 34 | event.respondWith( 35 | fetch(request) 36 | .then((response) => { 37 | if (response.status === 0) { 38 | return response; 39 | } 40 | 41 | const newHeaders = new Headers(response.headers); 42 | newHeaders.set("Cross-Origin-Embedder-Policy", 43 | coepCredentialless ? "credentialless" : "require-corp" 44 | ); 45 | if (!coepCredentialless) { 46 | newHeaders.set("Cross-Origin-Resource-Policy", "cross-origin"); 47 | } 48 | newHeaders.set("Cross-Origin-Opener-Policy", "same-origin"); 49 | 50 | return new Response(response.body, { 51 | status: response.status, 52 | statusText: response.statusText, 53 | headers: newHeaders, 54 | }); 55 | }) 56 | .catch((e) => console.error(e)) 57 | ); 58 | }); 59 | 60 | } else { 61 | (() => { 62 | // You can customize the behavior of this script through a global `coi` variable. 63 | const coi = { 64 | shouldRegister: () => true, 65 | shouldDeregister: () => false, 66 | coepCredentialless: () => (window.chrome !== undefined || window.netscape !== undefined), 67 | doReload: () => window.location.reload(), 68 | quiet: false, 69 | ...window.coi 70 | }; 71 | 72 | const n = navigator; 73 | 74 | if (n.serviceWorker && n.serviceWorker.controller) { 75 | n.serviceWorker.controller.postMessage({ 76 | type: "coepCredentialless", 77 | value: coi.coepCredentialless(), 78 | }); 79 | 80 | if (coi.shouldDeregister()) { 81 | n.serviceWorker.controller.postMessage({ type: "deregister" }); 82 | } 83 | } 84 | 85 | // If we're already coi: do nothing. Perhaps it's due to this script doing its job, or COOP/COEP are 86 | // already set from the origin server. Also if the browser has no notion of crossOriginIsolated, just give up here. 87 | if (window.crossOriginIsolated !== false || !coi.shouldRegister()) return; 88 | 89 | if (!window.isSecureContext) { 90 | !coi.quiet && console.log("COOP/COEP Service Worker not registered, a secure context is required."); 91 | return; 92 | } 93 | 94 | // In some environments (e.g. Chrome incognito mode) this won't be available 95 | if (n.serviceWorker) { 96 | n.serviceWorker.register(window.document.currentScript.src).then( 97 | (registration) => { 98 | !coi.quiet && console.log("COOP/COEP Service Worker registered", registration.scope); 99 | 100 | registration.addEventListener("updatefound", () => { 101 | !coi.quiet && console.log("Reloading page to make use of updated COOP/COEP Service Worker."); 102 | coi.doReload(); 103 | }); 104 | 105 | // If the registration is active, but it's not controlling the page 106 | if (registration.active && !n.serviceWorker.controller) { 107 | !coi.quiet && console.log("Reloading page to make use of COOP/COEP Service Worker."); 108 | coi.doReload(); 109 | } 110 | }, 111 | (err) => { 112 | !coi.quiet && console.error("COOP/COEP Service Worker failed to register:", err); 113 | } 114 | ); 115 | } 116 | })(); 117 | } -------------------------------------------------------------------------------- /draco/draco_decoder.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/N8python/N8GI/0afc845566f272c5bcf6ec7a4317d7083f7df2d1/draco/draco_decoder.wasm -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Triangle Voxelization 9 | 15 | 16 | 17 | 18 | 19 | 20 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'https://unpkg.com/three@0.162.0/build/three.module.min.js'; 2 | import { EffectComposer } from 'https://unpkg.com/three@0.162.0/examples/jsm/postprocessing/EffectComposer.js'; 3 | import { ShaderPass } from 'https://unpkg.com/three@0.162.0/examples/jsm/postprocessing/ShaderPass.js'; 4 | import { SMAAPass } from 'https://unpkg.com/three@0.162.0/examples/jsm/postprocessing/SMAAPass.js'; 5 | import { GammaCorrectionShader } from 'https://unpkg.com/three@0.162.0/examples/jsm/shaders/GammaCorrectionShader.js'; 6 | import { OrbitControls } from 'https://unpkg.com/three@0.162.0/examples/jsm/controls/OrbitControls.js'; 7 | import { GLTFLoader } from 'https://unpkg.com/three@0.162.0/examples/jsm/loaders/GLTFLoader.js'; 8 | import { DRACOLoader } from 'https://unpkg.com/three@0.162.0/examples/jsm/loaders/DRACOLoader.js'; 9 | import { GUI } from 'https://unpkg.com/three@0.162.0/examples/jsm/libs/lil-gui.module.min.js'; 10 | import { Stats } from "./stats.js"; 11 | import SimpleStats from "./statsVoxel.js"; 12 | import { N8GIPass } from './N8GIPass.js'; 13 | 14 | async function main() { 15 | let clientWidth = window.innerWidth; 16 | let clientHeight = window.innerHeight; 17 | const scene = new THREE.Scene(); 18 | const camera = new THREE.PerspectiveCamera(75, clientWidth / clientHeight, 0.1, 1000); 19 | camera.position.set(50, 75, 50); 20 | 21 | const renderer = new THREE.WebGLRenderer(); 22 | THREE.Texture.DEFAULT_ANISOTROPY = renderer.capabilities.getMaxAnisotropy(); 23 | renderer.setSize(clientWidth, clientHeight); 24 | 25 | document.body.appendChild(renderer.domElement); 26 | renderer.shadowMap.enabled = true; 27 | renderer.shadowMap.autoUpdate = false; 28 | renderer.shadowMap.type = THREE.VSMShadowMap; 29 | const controls = new OrbitControls(camera, renderer.domElement); 30 | controls.target.set(0, 25, 0); 31 | const stats = new Stats({ 32 | logsPerSecond: 20, 33 | samplesLog: 100, 34 | samplesGraph: 10, 35 | mode: 2, 36 | 37 | }); 38 | stats.init(renderer); 39 | document.body.appendChild(stats.dom); 40 | const voxelStats = new SimpleStats(); 41 | voxelStats.showPanel(1); 42 | document.body.appendChild(voxelStats.dom); 43 | 44 | const environment = new THREE.CubeTextureLoader().load([ 45 | "skybox/Box_Right.bmp", 46 | "skybox/Box_Left.bmp", 47 | "skybox/Box_Top.bmp", 48 | "skybox/Box_Bottom.bmp", 49 | "skybox/Box_Front.bmp", 50 | "skybox/Box_Back.bmp" 51 | ]); 52 | environment.colorSpace = THREE.SRGBColorSpace; 53 | scene.background = environment; 54 | 55 | const directionalLight = new THREE.DirectionalLight(0xffffff, 5.0); 56 | directionalLight.position.set(-150, 450, -150); 57 | directionalLight.castShadow = true; 58 | directionalLight.shadow.radius = 4; 59 | directionalLight.shadow.mapSize.width = 2048; 60 | directionalLight.shadow.mapSize.height = 2048; 61 | directionalLight.shadow.camera.left = -200; 62 | directionalLight.shadow.camera.right = 200; 63 | directionalLight.shadow.camera.top = 200; 64 | directionalLight.shadow.camera.bottom = -200; 65 | directionalLight.shadow.camera.near = 0.1; 66 | directionalLight.shadow.camera.far = 500; 67 | directionalLight.shadow.bias = -0.0001; 68 | 69 | scene.add(directionalLight); 70 | const loader = new GLTFLoader(); 71 | const dracoLoader = new DRACOLoader(); 72 | dracoLoader.setDecoderPath("./draco/"); 73 | loader.setDRACOLoader(dracoLoader); 74 | 75 | const sponza = (await loader.loadAsync('./sponza_cd.glb')).scene; 76 | 77 | const tKnot = new THREE.Mesh( 78 | new THREE.TorusKnotGeometry(5, 1.5, 200, 32), 79 | new THREE.MeshStandardMaterial({ 80 | emissive: new THREE.Color(1, 1, 0), 81 | color: new THREE.Color(0, 0, 0), 82 | metalness: 0.0, 83 | roughness: 1.0 84 | }) 85 | ); 86 | tKnot.position.y = 50; 87 | tKnot.position.z = 35; 88 | tKnot.castShadow = true; 89 | tKnot.receiveShadow = true; 90 | scene.add(tKnot); 91 | 92 | const tKnot2 = new THREE.Mesh( 93 | new THREE.TorusKnotGeometry(5, 1.5, 200, 32), 94 | new THREE.MeshStandardMaterial({ 95 | emissive: new THREE.Color(1, 0, 1), 96 | color: new THREE.Color(0, 0, 0), 97 | metalness: 0.0, 98 | roughness: 1.0 99 | }) 100 | ); 101 | tKnot2.position.y = 15; 102 | tKnot2.position.z = -35; 103 | tKnot2.castShadow = true; 104 | tKnot2.receiveShadow = true; 105 | scene.add(tKnot2); 106 | 107 | const tKnot3 = new THREE.Mesh( 108 | new THREE.TorusKnotGeometry(5, 1.5, 200, 32), 109 | new THREE.MeshStandardMaterial({ 110 | emissive: new THREE.Color(1, 0, 0), 111 | color: new THREE.Color(0, 0, 0), 112 | metalness: 0.0, 113 | roughness: 1.0 114 | }) 115 | ); 116 | tKnot3.position.y = 15; 117 | tKnot3.position.x = 70; 118 | tKnot3.castShadow = true; 119 | tKnot3.receiveShadow = true; 120 | scene.add(tKnot3); 121 | 122 | const tKnot4 = new THREE.Mesh( 123 | new THREE.TorusKnotGeometry(5, 1.5, 200, 32), 124 | new THREE.MeshStandardMaterial({ 125 | color: new THREE.Color(0, 0, 1), 126 | metalness: 0.0, 127 | roughness: 1.0 128 | }) 129 | ); 130 | tKnot4.position.y = 15; 131 | tKnot4.position.x = -70; 132 | tKnot4.castShadow = true; 133 | tKnot4.receiveShadow = true; 134 | scene.add(tKnot4); 135 | 136 | const metalSphere = new THREE.Mesh( 137 | new THREE.SphereGeometry(10, 32, 32), 138 | new THREE.MeshStandardMaterial({ 139 | color: new THREE.Color(1, 1, 1), 140 | metalness: 1.0, 141 | roughness: 0.0 142 | }) 143 | ); 144 | metalSphere.position.y = 10; 145 | scene.add(metalSphere); 146 | 147 | sponza.scale.set(10, 10, 10); 148 | scene.add(sponza); 149 | 150 | scene.traverse((child) => { 151 | if (child.isMesh) { 152 | child.castShadow = true; 153 | child.receiveShadow = true; 154 | child.material.dithering = true; 155 | child.material.envMap = environment; 156 | child.material.envMapIntensity = 0.0; 157 | /* child.material.metalness = 0.5; 158 | child.material.metalnessMap = null; 159 | child.material.roughness = 0.0;*/ 160 | } 161 | }); 162 | 163 | window.addEventListener('resize', () => { 164 | clientWidth = window.innerWidth; 165 | clientHeight = window.innerHeight; 166 | // main.js (continued) 167 | camera.aspect = clientWidth / clientHeight; 168 | camera.updateProjectionMatrix(); 169 | 170 | renderer.setSize(clientWidth, clientHeight); 171 | composer.setSize(clientWidth, clientHeight); 172 | }); 173 | 174 | 175 | const composer = new EffectComposer(renderer, new THREE.WebGLRenderTarget(clientWidth, clientHeight, { 176 | minFilter: THREE.LinearFilter, 177 | magFilter: THREE.LinearFilter, 178 | format: 'RGB', 179 | type: THREE.FloatType, 180 | internalFormat: 'R11F_G11F_B10F' 181 | })); 182 | 183 | 184 | const smaaPass = new SMAAPass(clientWidth, clientHeight); 185 | const n8giPass = new N8GIPass(scene, camera, renderer, clientWidth, clientHeight); 186 | n8giPass.voxelModule.onBeforeVoxelization = function() { 187 | voxelStats.begin(); 188 | } 189 | n8giPass.voxelModule.onAfterVoxelization = function() { 190 | voxelStats.end(); 191 | } 192 | composer.addPass(n8giPass); 193 | composer.addPass(new ShaderPass(GammaCorrectionShader)); 194 | composer.addPass(smaaPass); 195 | 196 | const gui = new GUI(); 197 | 198 | const effectController = { 199 | voxelsOnly: false, 200 | giOnly: false, 201 | denoise: true, 202 | denoiseStrength: 1.0, 203 | roughness: 0.0, 204 | metalness: 1.0, 205 | giStrength: Math.PI, 206 | specularStrength: 1.0, 207 | useSimpleEnvmap: false, 208 | samples: 1, 209 | aoSamples: 16, 210 | aoRadius: 5, 211 | denoiseRadius: 12, 212 | aoEnabled: true, 213 | aoIntensity: 5, 214 | envLeak: 0 215 | } 216 | 217 | 218 | gui.add(effectController, "useSimpleEnvmap").onChange((value) => { 219 | if (!value) { 220 | scene.traverse((child) => { 221 | if (child.isMesh) { 222 | child.material.envMapIntensity = effectController.envLeak; 223 | } 224 | }); 225 | } else { 226 | scene.traverse((child) => { 227 | if (child.isMesh) { 228 | child.material.envMapIntensity = 1.0; 229 | } 230 | }); 231 | } 232 | }); 233 | gui.add(effectController, "envLeak", 0, 1, 0.01).onChange((value) => { 234 | if (!effectController.useSimpleEnvmap) { 235 | scene.traverse((child) => { 236 | if (child.isMesh) { 237 | child.material.envMapIntensity = value; 238 | } 239 | }); 240 | } 241 | }); 242 | 243 | gui.add(effectController, "giOnly"); 244 | gui.add(effectController, "voxelsOnly"); 245 | gui.add(effectController, "samples", 1, 16, 1); 246 | gui.add(effectController, "denoiseStrength", 0.0, 1.0); 247 | gui.add(effectController, "giStrength", 0.0, Math.PI); 248 | gui.add(effectController, "specularStrength", 0.0, Math.PI); 249 | gui.add(effectController, "roughness", 0.0, 1.0); 250 | gui.add(effectController, "metalness", 0.0, 1.0); 251 | gui.add(effectController, "aoSamples", 1, 64, 1); 252 | gui.add(effectController, "aoRadius", 1, 10, 1); 253 | gui.add(effectController, "denoiseRadius", 1, 12, 1); 254 | gui.add(effectController, "aoIntensity", 0.0, 10.0); 255 | gui.add(effectController, "aoEnabled"); 256 | 257 | function animate() { 258 | metalSphere.material.roughness = effectController.roughness; 259 | metalSphere.material.metalness = effectController.metalness; 260 | tKnot.rotation.y += 0.01; 261 | tKnot.rotation.x += 0.01; 262 | tKnot.position.x = Math.sin(performance.now() / 1000) * 75; 263 | tKnot2.rotation.y += 0.01; 264 | tKnot2.rotation.x += 0.01; 265 | tKnot2.position.x = Math.sin(performance.now() / 1000) * -75; 266 | tKnot3.rotation.y += 0.01; 267 | tKnot3.rotation.x += 0.01; 268 | tKnot4.rotation.y += 0.01; 269 | tKnot4.rotation.x += 0.01; 270 | tKnot3.material.emissive = new THREE.Color().setHSL( 271 | 0.5 * Math.sin(performance.now() / 2500) + 0.5, 272 | 1, 273 | 0.5 274 | ); 275 | n8giPass.configuration.samples = effectController.samples; 276 | n8giPass.configuration.giOnly = effectController.giOnly; 277 | n8giPass.configuration.voxelsOnly = effectController.voxelsOnly; 278 | n8giPass.configuration.denoise = effectController.denoise; 279 | n8giPass.configuration.denoiseStrength = effectController.denoiseStrength; 280 | n8giPass.configuration.roughness = effectController.roughness; 281 | n8giPass.configuration.giStrength = effectController.giStrength; 282 | n8giPass.configuration.specularStrength = effectController.specularStrength; 283 | n8giPass.configuration.useSimpleEnvmap = effectController.useSimpleEnvmap; 284 | n8giPass.configuration.aoEnabled = effectController.aoEnabled; 285 | n8giPass.n8aopass.configuration.aoEnabled = effectController.aoEnabled; 286 | n8giPass.n8aopass.configuration.aoSamples = effectController.aoSamples; 287 | n8giPass.n8aopass.configuration.aoRadius = effectController.aoRadius; 288 | n8giPass.n8aopass.configuration.denoiseRadius = effectController.denoiseRadius; 289 | n8giPass.n8aopass.configuration.intensity = effectController.aoIntensity; 290 | 291 | 292 | directionalLight.position.set(-60 * Math.cos(performance.now() / 1000), 293 | 200, -60 * Math.sin(performance.now() / 1000) 294 | ); 295 | composer.render(); 296 | 297 | controls.update(); 298 | stats.update(); 299 | requestAnimationFrame(animate); 300 | } 301 | 302 | requestAnimationFrame(animate); 303 | } 304 | 305 | main(); -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "trianglevoxelization", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "trianglevoxelization", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "express": "^4.18.2" 13 | } 14 | }, 15 | "node_modules/accepts": { 16 | "version": "1.3.8", 17 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", 18 | "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", 19 | "dependencies": { 20 | "mime-types": "~2.1.34", 21 | "negotiator": "0.6.3" 22 | }, 23 | "engines": { 24 | "node": ">= 0.6" 25 | } 26 | }, 27 | "node_modules/array-flatten": { 28 | "version": "1.1.1", 29 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 30 | "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" 31 | }, 32 | "node_modules/body-parser": { 33 | "version": "1.20.1", 34 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", 35 | "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", 36 | "dependencies": { 37 | "bytes": "3.1.2", 38 | "content-type": "~1.0.4", 39 | "debug": "2.6.9", 40 | "depd": "2.0.0", 41 | "destroy": "1.2.0", 42 | "http-errors": "2.0.0", 43 | "iconv-lite": "0.4.24", 44 | "on-finished": "2.4.1", 45 | "qs": "6.11.0", 46 | "raw-body": "2.5.1", 47 | "type-is": "~1.6.18", 48 | "unpipe": "1.0.0" 49 | }, 50 | "engines": { 51 | "node": ">= 0.8", 52 | "npm": "1.2.8000 || >= 1.4.16" 53 | } 54 | }, 55 | "node_modules/bytes": { 56 | "version": "3.1.2", 57 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 58 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", 59 | "engines": { 60 | "node": ">= 0.8" 61 | } 62 | }, 63 | "node_modules/call-bind": { 64 | "version": "1.0.2", 65 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", 66 | "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", 67 | "dependencies": { 68 | "function-bind": "^1.1.1", 69 | "get-intrinsic": "^1.0.2" 70 | }, 71 | "funding": { 72 | "url": "https://github.com/sponsors/ljharb" 73 | } 74 | }, 75 | "node_modules/content-disposition": { 76 | "version": "0.5.4", 77 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", 78 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", 79 | "dependencies": { 80 | "safe-buffer": "5.2.1" 81 | }, 82 | "engines": { 83 | "node": ">= 0.6" 84 | } 85 | }, 86 | "node_modules/content-type": { 87 | "version": "1.0.5", 88 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", 89 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", 90 | "engines": { 91 | "node": ">= 0.6" 92 | } 93 | }, 94 | "node_modules/cookie": { 95 | "version": "0.5.0", 96 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", 97 | "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", 98 | "engines": { 99 | "node": ">= 0.6" 100 | } 101 | }, 102 | "node_modules/cookie-signature": { 103 | "version": "1.0.6", 104 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 105 | "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" 106 | }, 107 | "node_modules/debug": { 108 | "version": "2.6.9", 109 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 110 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 111 | "dependencies": { 112 | "ms": "2.0.0" 113 | } 114 | }, 115 | "node_modules/depd": { 116 | "version": "2.0.0", 117 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 118 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", 119 | "engines": { 120 | "node": ">= 0.8" 121 | } 122 | }, 123 | "node_modules/destroy": { 124 | "version": "1.2.0", 125 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", 126 | "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", 127 | "engines": { 128 | "node": ">= 0.8", 129 | "npm": "1.2.8000 || >= 1.4.16" 130 | } 131 | }, 132 | "node_modules/ee-first": { 133 | "version": "1.1.1", 134 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 135 | "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" 136 | }, 137 | "node_modules/encodeurl": { 138 | "version": "1.0.2", 139 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 140 | "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", 141 | "engines": { 142 | "node": ">= 0.8" 143 | } 144 | }, 145 | "node_modules/escape-html": { 146 | "version": "1.0.3", 147 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 148 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" 149 | }, 150 | "node_modules/etag": { 151 | "version": "1.8.1", 152 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 153 | "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", 154 | "engines": { 155 | "node": ">= 0.6" 156 | } 157 | }, 158 | "node_modules/express": { 159 | "version": "4.18.2", 160 | "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", 161 | "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", 162 | "dependencies": { 163 | "accepts": "~1.3.8", 164 | "array-flatten": "1.1.1", 165 | "body-parser": "1.20.1", 166 | "content-disposition": "0.5.4", 167 | "content-type": "~1.0.4", 168 | "cookie": "0.5.0", 169 | "cookie-signature": "1.0.6", 170 | "debug": "2.6.9", 171 | "depd": "2.0.0", 172 | "encodeurl": "~1.0.2", 173 | "escape-html": "~1.0.3", 174 | "etag": "~1.8.1", 175 | "finalhandler": "1.2.0", 176 | "fresh": "0.5.2", 177 | "http-errors": "2.0.0", 178 | "merge-descriptors": "1.0.1", 179 | "methods": "~1.1.2", 180 | "on-finished": "2.4.1", 181 | "parseurl": "~1.3.3", 182 | "path-to-regexp": "0.1.7", 183 | "proxy-addr": "~2.0.7", 184 | "qs": "6.11.0", 185 | "range-parser": "~1.2.1", 186 | "safe-buffer": "5.2.1", 187 | "send": "0.18.0", 188 | "serve-static": "1.15.0", 189 | "setprototypeof": "1.2.0", 190 | "statuses": "2.0.1", 191 | "type-is": "~1.6.18", 192 | "utils-merge": "1.0.1", 193 | "vary": "~1.1.2" 194 | }, 195 | "engines": { 196 | "node": ">= 0.10.0" 197 | } 198 | }, 199 | "node_modules/finalhandler": { 200 | "version": "1.2.0", 201 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", 202 | "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", 203 | "dependencies": { 204 | "debug": "2.6.9", 205 | "encodeurl": "~1.0.2", 206 | "escape-html": "~1.0.3", 207 | "on-finished": "2.4.1", 208 | "parseurl": "~1.3.3", 209 | "statuses": "2.0.1", 210 | "unpipe": "~1.0.0" 211 | }, 212 | "engines": { 213 | "node": ">= 0.8" 214 | } 215 | }, 216 | "node_modules/forwarded": { 217 | "version": "0.2.0", 218 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 219 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", 220 | "engines": { 221 | "node": ">= 0.6" 222 | } 223 | }, 224 | "node_modules/fresh": { 225 | "version": "0.5.2", 226 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 227 | "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", 228 | "engines": { 229 | "node": ">= 0.6" 230 | } 231 | }, 232 | "node_modules/function-bind": { 233 | "version": "1.1.1", 234 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 235 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" 236 | }, 237 | "node_modules/get-intrinsic": { 238 | "version": "1.2.1", 239 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", 240 | "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", 241 | "dependencies": { 242 | "function-bind": "^1.1.1", 243 | "has": "^1.0.3", 244 | "has-proto": "^1.0.1", 245 | "has-symbols": "^1.0.3" 246 | }, 247 | "funding": { 248 | "url": "https://github.com/sponsors/ljharb" 249 | } 250 | }, 251 | "node_modules/has": { 252 | "version": "1.0.3", 253 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 254 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 255 | "dependencies": { 256 | "function-bind": "^1.1.1" 257 | }, 258 | "engines": { 259 | "node": ">= 0.4.0" 260 | } 261 | }, 262 | "node_modules/has-proto": { 263 | "version": "1.0.1", 264 | "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", 265 | "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", 266 | "engines": { 267 | "node": ">= 0.4" 268 | }, 269 | "funding": { 270 | "url": "https://github.com/sponsors/ljharb" 271 | } 272 | }, 273 | "node_modules/has-symbols": { 274 | "version": "1.0.3", 275 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", 276 | "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", 277 | "engines": { 278 | "node": ">= 0.4" 279 | }, 280 | "funding": { 281 | "url": "https://github.com/sponsors/ljharb" 282 | } 283 | }, 284 | "node_modules/http-errors": { 285 | "version": "2.0.0", 286 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 287 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 288 | "dependencies": { 289 | "depd": "2.0.0", 290 | "inherits": "2.0.4", 291 | "setprototypeof": "1.2.0", 292 | "statuses": "2.0.1", 293 | "toidentifier": "1.0.1" 294 | }, 295 | "engines": { 296 | "node": ">= 0.8" 297 | } 298 | }, 299 | "node_modules/iconv-lite": { 300 | "version": "0.4.24", 301 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 302 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 303 | "dependencies": { 304 | "safer-buffer": ">= 2.1.2 < 3" 305 | }, 306 | "engines": { 307 | "node": ">=0.10.0" 308 | } 309 | }, 310 | "node_modules/inherits": { 311 | "version": "2.0.4", 312 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 313 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 314 | }, 315 | "node_modules/ipaddr.js": { 316 | "version": "1.9.1", 317 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 318 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", 319 | "engines": { 320 | "node": ">= 0.10" 321 | } 322 | }, 323 | "node_modules/media-typer": { 324 | "version": "0.3.0", 325 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 326 | "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", 327 | "engines": { 328 | "node": ">= 0.6" 329 | } 330 | }, 331 | "node_modules/merge-descriptors": { 332 | "version": "1.0.1", 333 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 334 | "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" 335 | }, 336 | "node_modules/methods": { 337 | "version": "1.1.2", 338 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 339 | "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", 340 | "engines": { 341 | "node": ">= 0.6" 342 | } 343 | }, 344 | "node_modules/mime": { 345 | "version": "1.6.0", 346 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 347 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", 348 | "bin": { 349 | "mime": "cli.js" 350 | }, 351 | "engines": { 352 | "node": ">=4" 353 | } 354 | }, 355 | "node_modules/mime-db": { 356 | "version": "1.52.0", 357 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 358 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 359 | "engines": { 360 | "node": ">= 0.6" 361 | } 362 | }, 363 | "node_modules/mime-types": { 364 | "version": "2.1.35", 365 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 366 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 367 | "dependencies": { 368 | "mime-db": "1.52.0" 369 | }, 370 | "engines": { 371 | "node": ">= 0.6" 372 | } 373 | }, 374 | "node_modules/ms": { 375 | "version": "2.0.0", 376 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 377 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" 378 | }, 379 | "node_modules/negotiator": { 380 | "version": "0.6.3", 381 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", 382 | "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", 383 | "engines": { 384 | "node": ">= 0.6" 385 | } 386 | }, 387 | "node_modules/object-inspect": { 388 | "version": "1.12.3", 389 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", 390 | "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", 391 | "funding": { 392 | "url": "https://github.com/sponsors/ljharb" 393 | } 394 | }, 395 | "node_modules/on-finished": { 396 | "version": "2.4.1", 397 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", 398 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", 399 | "dependencies": { 400 | "ee-first": "1.1.1" 401 | }, 402 | "engines": { 403 | "node": ">= 0.8" 404 | } 405 | }, 406 | "node_modules/parseurl": { 407 | "version": "1.3.3", 408 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 409 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", 410 | "engines": { 411 | "node": ">= 0.8" 412 | } 413 | }, 414 | "node_modules/path-to-regexp": { 415 | "version": "0.1.7", 416 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 417 | "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" 418 | }, 419 | "node_modules/proxy-addr": { 420 | "version": "2.0.7", 421 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 422 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 423 | "dependencies": { 424 | "forwarded": "0.2.0", 425 | "ipaddr.js": "1.9.1" 426 | }, 427 | "engines": { 428 | "node": ">= 0.10" 429 | } 430 | }, 431 | "node_modules/qs": { 432 | "version": "6.11.0", 433 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", 434 | "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", 435 | "dependencies": { 436 | "side-channel": "^1.0.4" 437 | }, 438 | "engines": { 439 | "node": ">=0.6" 440 | }, 441 | "funding": { 442 | "url": "https://github.com/sponsors/ljharb" 443 | } 444 | }, 445 | "node_modules/range-parser": { 446 | "version": "1.2.1", 447 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 448 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", 449 | "engines": { 450 | "node": ">= 0.6" 451 | } 452 | }, 453 | "node_modules/raw-body": { 454 | "version": "2.5.1", 455 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", 456 | "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", 457 | "dependencies": { 458 | "bytes": "3.1.2", 459 | "http-errors": "2.0.0", 460 | "iconv-lite": "0.4.24", 461 | "unpipe": "1.0.0" 462 | }, 463 | "engines": { 464 | "node": ">= 0.8" 465 | } 466 | }, 467 | "node_modules/safe-buffer": { 468 | "version": "5.2.1", 469 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 470 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 471 | "funding": [ 472 | { 473 | "type": "github", 474 | "url": "https://github.com/sponsors/feross" 475 | }, 476 | { 477 | "type": "patreon", 478 | "url": "https://www.patreon.com/feross" 479 | }, 480 | { 481 | "type": "consulting", 482 | "url": "https://feross.org/support" 483 | } 484 | ] 485 | }, 486 | "node_modules/safer-buffer": { 487 | "version": "2.1.2", 488 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 489 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 490 | }, 491 | "node_modules/send": { 492 | "version": "0.18.0", 493 | "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", 494 | "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", 495 | "dependencies": { 496 | "debug": "2.6.9", 497 | "depd": "2.0.0", 498 | "destroy": "1.2.0", 499 | "encodeurl": "~1.0.2", 500 | "escape-html": "~1.0.3", 501 | "etag": "~1.8.1", 502 | "fresh": "0.5.2", 503 | "http-errors": "2.0.0", 504 | "mime": "1.6.0", 505 | "ms": "2.1.3", 506 | "on-finished": "2.4.1", 507 | "range-parser": "~1.2.1", 508 | "statuses": "2.0.1" 509 | }, 510 | "engines": { 511 | "node": ">= 0.8.0" 512 | } 513 | }, 514 | "node_modules/send/node_modules/ms": { 515 | "version": "2.1.3", 516 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 517 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 518 | }, 519 | "node_modules/serve-static": { 520 | "version": "1.15.0", 521 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", 522 | "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", 523 | "dependencies": { 524 | "encodeurl": "~1.0.2", 525 | "escape-html": "~1.0.3", 526 | "parseurl": "~1.3.3", 527 | "send": "0.18.0" 528 | }, 529 | "engines": { 530 | "node": ">= 0.8.0" 531 | } 532 | }, 533 | "node_modules/setprototypeof": { 534 | "version": "1.2.0", 535 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 536 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" 537 | }, 538 | "node_modules/side-channel": { 539 | "version": "1.0.4", 540 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", 541 | "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", 542 | "dependencies": { 543 | "call-bind": "^1.0.0", 544 | "get-intrinsic": "^1.0.2", 545 | "object-inspect": "^1.9.0" 546 | }, 547 | "funding": { 548 | "url": "https://github.com/sponsors/ljharb" 549 | } 550 | }, 551 | "node_modules/statuses": { 552 | "version": "2.0.1", 553 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 554 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", 555 | "engines": { 556 | "node": ">= 0.8" 557 | } 558 | }, 559 | "node_modules/toidentifier": { 560 | "version": "1.0.1", 561 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 562 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", 563 | "engines": { 564 | "node": ">=0.6" 565 | } 566 | }, 567 | "node_modules/type-is": { 568 | "version": "1.6.18", 569 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 570 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 571 | "dependencies": { 572 | "media-typer": "0.3.0", 573 | "mime-types": "~2.1.24" 574 | }, 575 | "engines": { 576 | "node": ">= 0.6" 577 | } 578 | }, 579 | "node_modules/unpipe": { 580 | "version": "1.0.0", 581 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 582 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", 583 | "engines": { 584 | "node": ">= 0.8" 585 | } 586 | }, 587 | "node_modules/utils-merge": { 588 | "version": "1.0.1", 589 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 590 | "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", 591 | "engines": { 592 | "node": ">= 0.4.0" 593 | } 594 | }, 595 | "node_modules/vary": { 596 | "version": "1.1.2", 597 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 598 | "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", 599 | "engines": { 600 | "node": ">= 0.8" 601 | } 602 | } 603 | } 604 | } 605 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "trianglevoxelization", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "main.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "express": "^4.18.2" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /panel.js: -------------------------------------------------------------------------------- 1 | class Panel { 2 | constructor(name, fg, bg) { 3 | this.name = name; 4 | this.fg = fg; 5 | this.bg = bg; 6 | this.PR = Math.round(window.devicePixelRatio || 1); 7 | this.WIDTH = 90 * this.PR; 8 | this.HEIGHT = 48 * this.PR; 9 | this.TEXT_X = 3 * this.PR; 10 | this.TEXT_Y = 2 * this.PR; 11 | this.GRAPH_X = 3 * this.PR; 12 | this.GRAPH_Y = 15 * this.PR; 13 | this.GRAPH_WIDTH = 84 * this.PR; 14 | this.GRAPH_HEIGHT = 30 * this.PR; 15 | this.canvas = document.createElement("canvas"); 16 | this.canvas.width = 90 * this.PR; 17 | this.canvas.height = 48 * this.PR; 18 | this.canvas.style.width = "90px"; 19 | this.canvas.style.position = "absolute"; 20 | this.canvas.style.height = "48px"; 21 | this.canvas.style.cssText = "width:90px;height:48px"; 22 | this.context = this.canvas.getContext("2d"); 23 | if (this.context) { 24 | this.context.font = "bold " + 9 * this.PR + "px Helvetica,Arial,sans-serif"; 25 | this.context.textBaseline = "top"; 26 | this.context.fillStyle = this.bg; 27 | this.context.fillRect(0, 0, this.WIDTH, this.HEIGHT); 28 | this.context.fillStyle = this.fg; 29 | this.context.fillText(this.name, this.TEXT_X, this.TEXT_Y); 30 | this.context.fillRect(this.GRAPH_X, this.GRAPH_Y, this.GRAPH_WIDTH, this.GRAPH_HEIGHT); 31 | this.context.fillStyle = this.bg; 32 | this.context.globalAlpha = 0.9; 33 | this.context.fillRect(this.GRAPH_X, this.GRAPH_Y, this.GRAPH_WIDTH, this.GRAPH_HEIGHT); 34 | } 35 | } 36 | update(value, valueGraph, maxValue, maxGraph, decimals = 0) { 37 | let min = Infinity, 38 | max = 0; 39 | if (!this.context) 40 | return; 41 | min = Math.min(min, value); 42 | max = Math.max(maxValue, value); 43 | maxGraph = Math.max(maxGraph, valueGraph); 44 | this.context.fillStyle = this.bg; 45 | this.context.globalAlpha = 1; 46 | this.context.fillRect(0, 0, this.WIDTH, this.GRAPH_Y); 47 | this.context.fillStyle = this.fg; 48 | this.context.fillText(value.toFixed(decimals) + " " + this.name + " (" + min.toFixed(decimals) + "-" + parseFloat(max.toFixed(decimals)) + ")", this.TEXT_X, this.TEXT_Y); 49 | this.context.drawImage(this.canvas, this.GRAPH_X + this.PR, this.GRAPH_Y, this.GRAPH_WIDTH - this.PR, this.GRAPH_HEIGHT, this.GRAPH_X, this.GRAPH_Y, this.GRAPH_WIDTH - this.PR, this.GRAPH_HEIGHT); 50 | this.context.fillRect(this.GRAPH_X + this.GRAPH_WIDTH - this.PR, this.GRAPH_Y, this.PR, this.GRAPH_HEIGHT); 51 | this.context.fillStyle = this.bg; 52 | this.context.globalAlpha = 0.9; 53 | this.context.fillRect(this.GRAPH_X + this.GRAPH_WIDTH - this.PR, this.GRAPH_Y, this.PR, (1 - valueGraph / maxGraph) * this.GRAPH_HEIGHT); 54 | } 55 | } 56 | 57 | ; 58 | export { 59 | Panel as 60 | default 61 | }; -------------------------------------------------------------------------------- /proxy-headers.json: -------------------------------------------------------------------------------- 1 | { 2 | "Cross-Origin-Opener-Policy": "same-origin", 3 | "Cross-Origin-Embedder-Policy": "require-corp" 4 | } -------------------------------------------------------------------------------- /skybox/Box_Back.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/N8python/N8GI/0afc845566f272c5bcf6ec7a4317d7083f7df2d1/skybox/Box_Back.bmp -------------------------------------------------------------------------------- /skybox/Box_Bottom.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/N8python/N8GI/0afc845566f272c5bcf6ec7a4317d7083f7df2d1/skybox/Box_Bottom.bmp -------------------------------------------------------------------------------- /skybox/Box_Front.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/N8python/N8GI/0afc845566f272c5bcf6ec7a4317d7083f7df2d1/skybox/Box_Front.bmp -------------------------------------------------------------------------------- /skybox/Box_Left.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/N8python/N8GI/0afc845566f272c5bcf6ec7a4317d7083f7df2d1/skybox/Box_Left.bmp -------------------------------------------------------------------------------- /skybox/Box_Right.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/N8python/N8GI/0afc845566f272c5bcf6ec7a4317d7083f7df2d1/skybox/Box_Right.bmp -------------------------------------------------------------------------------- /skybox/Box_Top.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/N8python/N8GI/0afc845566f272c5bcf6ec7a4317d7083f7df2d1/skybox/Box_Top.bmp -------------------------------------------------------------------------------- /sponza_cd.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/N8python/N8GI/0afc845566f272c5bcf6ec7a4317d7083f7df2d1/sponza_cd.glb -------------------------------------------------------------------------------- /stats.js: -------------------------------------------------------------------------------- 1 | import Panel from "./panel.js?module"; 2 | const _Stats = class _Stats2 { 3 | constructor({ logsPerSecond = 20, samplesLog = 100, samplesGraph = 10, precision = 2, minimal = false, horizontal = true, mode = 0 } = {}) { 4 | this.totalCpuDuration = 0; 5 | this.totalGpuDuration = 0; 6 | this.totalGpuDurationCompute = 0; 7 | this.totalFps = 0; 8 | this.activeQuery = null; 9 | this.gpuQueries = []; 10 | this.renderCount = 0; 11 | this.mode = mode; 12 | this.horizontal = horizontal; 13 | this.dom = document.createElement("div"); 14 | this.dom.style.cssText = "position:fixed;top:0;left:0;opacity:0.9;z-index:10000;"; 15 | if (minimal) { 16 | this.dom.style.cssText += "cursor:pointer"; 17 | } 18 | this.gl = null; 19 | this.query = null; 20 | this.isRunningCPUProfiling = false; 21 | this.minimal = minimal; 22 | this.beginTime = (performance || Date).now(); 23 | this.prevTime = this.beginTime; 24 | this.prevCpuTime = this.beginTime; 25 | this.frames = 0; 26 | this.renderCount = 0; 27 | this.threeRendererPatched = false; 28 | this.averageCpu = { 29 | logs: [], 30 | graph: [] 31 | }; 32 | 33 | this.averageGpu = { 34 | logs: [], 35 | graph: [] 36 | }; 37 | 38 | this.averageGpuCompute = { 39 | logs: [], 40 | graph: [] 41 | }; 42 | 43 | this.queryCreated = false; 44 | this.fpsPanel = this.addPanel(new _Stats2.Panel("FPS", "#0ff", "#002"), 0); 45 | this.msPanel = this.addPanel(new _Stats2.Panel("CPU", "#0f0", "#020"), 1); 46 | this.gpuPanel = null; 47 | this.gpuPanelCompute = null; 48 | this.samplesLog = samplesLog; 49 | this.samplesGraph = samplesGraph; 50 | this.precision = precision; 51 | this.logsPerSecond = logsPerSecond; 52 | if (this.minimal) { 53 | this.dom.addEventListener("click", event => { 54 | event.preventDefault(); 55 | this.showPanel(++this.mode % this.dom.children.length); 56 | }, false); 57 | this.mode = mode; 58 | this.showPanel(this.mode); 59 | } else { 60 | window.addEventListener("resize", () => { 61 | this.resizePanel(this.fpsPanel, 0); 62 | this.resizePanel(this.msPanel, 1); 63 | if (this.gpuPanel) { 64 | this.resizePanel(this.gpuPanel, 2); 65 | } 66 | if (this.gpuPanelCompute) { 67 | this.resizePanel(this.gpuPanelCompute, 3); 68 | } 69 | }); 70 | } 71 | } 72 | patchThreeRenderer(renderer) { 73 | const originalRenderMethod = renderer.render; 74 | const statsInstance = this; 75 | renderer.render = function(scene, camera) { 76 | statsInstance.begin(); 77 | originalRenderMethod.call(this, scene, camera); 78 | statsInstance.end(); 79 | }; 80 | this.threeRendererPatched = true; 81 | } 82 | resizePanel(panel, offset) { 83 | panel.canvas.style.position = "absolute"; 84 | if (this.minimal) { 85 | panel.canvas.style.display = "none"; 86 | } else { 87 | panel.canvas.style.display = "block"; 88 | if (this.horizontal) { 89 | panel.canvas.style.top = "0px"; 90 | panel.canvas.style.left = offset * panel.WIDTH / panel.PR + "px"; 91 | } else { 92 | panel.canvas.style.left = "0px"; 93 | panel.canvas.style.top = offset * panel.HEIGHT / panel.PR + "px"; 94 | } 95 | } 96 | } 97 | addPanel(panel, offset) { 98 | if (panel.canvas) { 99 | this.dom.appendChild(panel.canvas); 100 | this.resizePanel(panel, offset); 101 | } 102 | return panel; 103 | } 104 | showPanel(id) { 105 | for (let i = 0; i < this.dom.children.length; i++) { 106 | const child = this.dom.children[i]; 107 | child.style.display = i === id ? "block" : "none"; 108 | } 109 | this.mode = id; 110 | } 111 | async init(canvasOrGL) { 112 | if (!canvasOrGL) { 113 | console.error('Stats: The "canvas" parameter is undefined.'); 114 | return; 115 | } 116 | if (canvasOrGL.isWebGLRenderer && !this.threeRendererPatched) { 117 | const canvas = canvasOrGL; 118 | this.patchThreeRenderer(canvas); 119 | this.gl = canvas.getContext(); 120 | } else if (!this.gl && canvasOrGL instanceof WebGL2RenderingContext) { 121 | this.gl = canvasOrGL; 122 | } 123 | if (canvasOrGL.isWebGPURenderer) { 124 | canvasOrGL.backend.trackTimestamp = true; 125 | if (await canvasOrGL.hasFeatureAsync("timestamp-query")) { 126 | this.gpuPanel = this.addPanel(new _Stats2.Panel("GPU", "#ff0", "#220"), 2); 127 | this.gpuPanelCompute = this.addPanel(new _Stats2.Panel("CPT", "#e1e1e1", "#212121"), 3); 128 | this.info = canvasOrGL.info; 129 | } 130 | return; 131 | } else if (!this.gl && canvasOrGL instanceof HTMLCanvasElement || canvasOrGL instanceof OffscreenCanvas) { 132 | this.gl = canvasOrGL.getContext("webgl2"); 133 | if (!this.gl) { 134 | console.error("Stats: Unable to obtain WebGL2 context."); 135 | return; 136 | } 137 | } else if (!this.gl) { 138 | console.error("Stats: Invalid input type. Expected WebGL2RenderingContext, HTMLCanvasElement, or OffscreenCanvas."); 139 | return; 140 | } 141 | this.ext = this.gl.getExtension("EXT_disjoint_timer_query_webgl2"); 142 | if (this.ext) { 143 | this.gpuPanel = this.addPanel(new _Stats2.Panel("GPU", "#ff0", "#220"), 2); 144 | } 145 | } 146 | begin() { 147 | if (!this.isRunningCPUProfiling) { 148 | this.beginProfiling("cpu-started"); 149 | } 150 | if (!this.gl || !this.ext) 151 | return; 152 | if (this.gl && this.ext) { 153 | if (this.activeQuery) { 154 | this.gl.endQuery(this.ext.TIME_ELAPSED_EXT); 155 | } 156 | this.activeQuery = this.gl.createQuery(); 157 | if (this.activeQuery !== null) { 158 | this.gl.beginQuery(this.ext.TIME_ELAPSED_EXT, this.activeQuery); 159 | } 160 | } 161 | } 162 | end() { 163 | this.renderCount++; 164 | if (this.gl && this.ext && this.activeQuery) { 165 | this.gl.endQuery(this.ext.TIME_ELAPSED_EXT); 166 | this.gpuQueries.push({ query: this.activeQuery, start: performance.now() }); 167 | this.activeQuery = null; 168 | } 169 | } 170 | processGpuQueries() { 171 | if (!this.gl || !this.ext) 172 | return; 173 | this.totalGpuDuration = 0; 174 | // let minStart = Infinity; 175 | // let maxEnd = -Infinity; 176 | let queriesToResolve = []; 177 | this.gpuQueries.forEach((queryInfo, index) => { 178 | if (this.gl) { 179 | const available = this.gl.getQueryParameter(queryInfo.query, this.gl.QUERY_RESULT_AVAILABLE); 180 | const disjoint = this.gl.getParameter(this.ext.GPU_DISJOINT_EXT); 181 | if (available && !disjoint) { 182 | const elapsed = this.gl.getQueryParameter(queryInfo.query, this.gl.QUERY_RESULT); 183 | const duration = elapsed * 1e-3; 184 | this.gl.deleteQuery(queryInfo.query); 185 | this.gpuQueries.splice(index, 1); 186 | queriesToResolve.push({ 187 | start: queryInfo.start, 188 | end: queryInfo.start + duration 189 | }); 190 | } 191 | } 192 | }); 193 | queriesToResolve.sort((a, b) => a.start - b.start); 194 | let merged = []; 195 | for (let i = 0; i < queriesToResolve.length; i++) { 196 | let current = queriesToResolve[i]; 197 | if (merged.length === 0) { 198 | merged.push(current); 199 | } else { 200 | let last = merged[merged.length - 1]; 201 | if (current.start <= last.end) { 202 | last.end = Math.max(last.end, current.end); 203 | } else { 204 | merged.push(current); 205 | } 206 | } 207 | } 208 | let total = 0; 209 | for (let i = 0; i < merged.length; i++) { 210 | total += merged[i].end - merged[i].start; 211 | } 212 | this.totalGpuDuration = total * 1e-3; 213 | } 214 | update() { 215 | if (this.info === void 0) { 216 | this.processGpuQueries(); 217 | } else { 218 | this.totalGpuDuration = this.info.render.timestamp; 219 | this.totalGpuDurationCompute = this.info.compute.timestamp; 220 | this.addToAverage(this.totalGpuDurationCompute, this.averageGpuCompute); 221 | } 222 | this.endProfiling("cpu-started", "cpu-finished", "cpu-duration"); 223 | this.addToAverage(this.totalCpuDuration, this.averageCpu); 224 | this.addToAverage(this.totalGpuDuration, this.averageGpu); 225 | this.renderCount = 0; 226 | if (this.totalCpuDuration === 0) { 227 | this.beginProfiling("cpu-started"); 228 | } 229 | this.totalCpuDuration = 0; 230 | this.totalFps = 0; 231 | this.beginTime = this.endInternal(); 232 | } 233 | endInternal() { 234 | this.frames++; 235 | const time = (performance || Date).now(); 236 | if (time >= this.prevCpuTime + 1e3 / this.logsPerSecond) { 237 | this.updatePanel(this.msPanel, this.averageCpu); 238 | this.updatePanel(this.gpuPanel, this.averageGpu); 239 | if (this.gpuPanelCompute) { 240 | this.updatePanel(this.gpuPanelCompute, this.averageGpuCompute); 241 | } 242 | this.prevCpuTime = time; 243 | } 244 | if (time >= this.prevTime + 1e3) { 245 | const fps = this.frames * 1e3 / (time - this.prevTime); 246 | this.fpsPanel.update(fps, fps, 100, 100, 0); 247 | this.prevTime = time; 248 | this.frames = 0; 249 | } 250 | return time; 251 | } 252 | addToAverage(value, averageArray) { 253 | averageArray.logs.push(value); 254 | if (averageArray.logs.length > this.samplesLog) { 255 | averageArray.logs.shift(); 256 | } 257 | averageArray.graph.push(value); 258 | if (averageArray.graph.length > this.samplesGraph) { 259 | averageArray.graph.shift(); 260 | } 261 | } 262 | beginProfiling(marker) { 263 | if (window.performance) { 264 | window.performance.mark(marker); 265 | this.isRunningCPUProfiling = true; 266 | } 267 | } 268 | endProfiling(startMarker, endMarker, measureName) { 269 | if (window.performance && endMarker && this.isRunningCPUProfiling) { 270 | window.performance.mark(endMarker); 271 | const cpuMeasure = performance.measure(measureName, startMarker, endMarker); 272 | this.totalCpuDuration += cpuMeasure.duration; 273 | this.isRunningCPUProfiling = false; 274 | } 275 | } 276 | updatePanel(panel, averageArray) { 277 | if (averageArray.logs.length > 0) { 278 | let sumLog = 0; 279 | let max = 0.01; 280 | for (let i = 0; i < averageArray.logs.length; i++) { 281 | sumLog += averageArray.logs[i]; 282 | if (averageArray.logs[i] > max) { 283 | max = averageArray.logs[i]; 284 | } 285 | } 286 | let sumGraph = 0; 287 | let maxGraph = 0.01; 288 | for (let i = 0; i < averageArray.graph.length; i++) { 289 | sumGraph += averageArray.graph[i]; 290 | if (averageArray.graph[i] > maxGraph) { 291 | maxGraph = averageArray.graph[i]; 292 | } 293 | } 294 | if (panel) { 295 | panel.update(sumLog / Math.min(averageArray.logs.length, this.samplesLog), sumGraph / Math.min(averageArray.graph.length, this.samplesGraph), max, maxGraph, this.precision); 296 | } 297 | } 298 | } 299 | get domElement() { 300 | return this.dom; 301 | } 302 | get container() { 303 | console.warn("Stats: Deprecated! this.container as been replaced to this.dom "); 304 | return this.dom; 305 | } 306 | }; 307 | 308 | _Stats.Panel = Panel; 309 | let Stats = _Stats; 310 | export { 311 | Stats 312 | }; -------------------------------------------------------------------------------- /statsVoxel.js: -------------------------------------------------------------------------------- 1 | var Stats = function() { 2 | 3 | var mode = 0; 4 | 5 | var container = document.createElement('div'); 6 | container.style.cssText = 'position:fixed;top:0;left:0;cursor:pointer;opacity:0.9;z-index:10000;left:270px;'; 7 | 8 | container.addEventListener('click', function(event) { 9 | 10 | event.preventDefault(); 11 | // showPanel(++mode % container.children.length); 12 | 13 | }, false); 14 | 15 | // 16 | 17 | function addPanel(panel) { 18 | 19 | container.appendChild(panel.dom); 20 | return panel; 21 | 22 | } 23 | 24 | function showPanel(id) { 25 | 26 | for (var i = 0; i < container.children.length; i++) { 27 | 28 | container.children[i].style.display = i === id ? 'block' : 'none'; 29 | 30 | } 31 | 32 | mode = id; 33 | 34 | } 35 | 36 | // 37 | 38 | var beginTime = (performance || Date).now(), 39 | prevTime = beginTime, 40 | frames = 0; 41 | 42 | var fpsPanel = addPanel(new Stats.Panel('FPS', '#0ff', '#002')); 43 | var msPanel = addPanel(new Stats.Panel('VMS', '#f0f', '#202')); 44 | 45 | if (self.performance && self.performance.memory) { 46 | 47 | var memPanel = addPanel(new Stats.Panel('MB', '#f08', '#201')); 48 | 49 | } 50 | 51 | showPanel(0); 52 | 53 | return { 54 | 55 | REVISION: 16, 56 | 57 | dom: container, 58 | 59 | addPanel: addPanel, 60 | showPanel: showPanel, 61 | 62 | begin: function() { 63 | 64 | beginTime = (performance || Date).now(); 65 | 66 | }, 67 | 68 | end: function() { 69 | 70 | frames++; 71 | 72 | var time = (performance || Date).now(); 73 | 74 | msPanel.update(time - beginTime, 200); 75 | 76 | if (time >= prevTime + 1000) { 77 | 78 | fpsPanel.update((frames * 1000) / (time - prevTime), 100); 79 | 80 | prevTime = time; 81 | frames = 0; 82 | 83 | if (memPanel) { 84 | 85 | var memory = performance.memory; 86 | memPanel.update(memory.usedJSHeapSize / 1048576, memory.jsHeapSizeLimit / 1048576); 87 | 88 | } 89 | 90 | } 91 | 92 | return time; 93 | 94 | }, 95 | 96 | update: function() { 97 | 98 | beginTime = this.end(); 99 | 100 | }, 101 | 102 | // Backwards Compatibility 103 | 104 | domElement: container, 105 | setMode: showPanel 106 | 107 | }; 108 | 109 | }; 110 | 111 | Stats.Panel = function(name, fg, bg) { 112 | 113 | var min = Infinity, 114 | max = 0, 115 | round = (x) => Math.round(x * 100) / 100 116 | var PR = round(window.devicePixelRatio || 1); 117 | 118 | var WIDTH = 90 * PR, 119 | HEIGHT = 48 * PR, 120 | TEXT_X = 3 * PR, 121 | TEXT_Y = 2 * PR, 122 | GRAPH_X = 3 * PR, 123 | GRAPH_Y = 15 * PR, 124 | GRAPH_WIDTH = 84 * PR, 125 | GRAPH_HEIGHT = 30 * PR; 126 | 127 | var canvas = document.createElement('canvas'); 128 | canvas.width = WIDTH; 129 | canvas.height = HEIGHT; 130 | canvas.style.cssText = 'width:90px;height:48px'; 131 | 132 | var context = canvas.getContext('2d'); 133 | context.font = 'bold ' + (9 * PR) + 'px Helvetica,Arial,sans-serif'; 134 | context.textBaseline = 'top'; 135 | 136 | context.fillStyle = bg; 137 | context.fillRect(0, 0, WIDTH, HEIGHT); 138 | 139 | context.fillStyle = fg; 140 | context.fillText(name, TEXT_X, TEXT_Y); 141 | context.fillRect(GRAPH_X, GRAPH_Y, GRAPH_WIDTH, GRAPH_HEIGHT); 142 | 143 | context.fillStyle = bg; 144 | context.globalAlpha = 0.9; 145 | context.fillRect(GRAPH_X, GRAPH_Y, GRAPH_WIDTH, GRAPH_HEIGHT); 146 | let runningVal = 0; 147 | return { 148 | 149 | dom: canvas, 150 | 151 | update: function(value, maxValue) { 152 | 153 | min = Math.min(min, value); 154 | max = Math.max(max, value); 155 | min = 0.9 * min + 0.1 * value; 156 | max = 0.9 * max + 0.1 * value; 157 | if (runningVal == 0) { 158 | runningVal = value; 159 | } else { 160 | runningVal = 0.9 * runningVal + 0.1 * value; 161 | } 162 | 163 | context.fillStyle = bg; 164 | context.globalAlpha = 1; 165 | context.fillRect(0, 0, WIDTH, GRAPH_Y); 166 | context.fillStyle = fg; 167 | context.fillText(round(runningVal) + ' ' + name + ' (' + Math.round(min) + '-' + Math.round(max) + ')', TEXT_X, TEXT_Y); 168 | 169 | context.drawImage(canvas, GRAPH_X + PR, GRAPH_Y, GRAPH_WIDTH - PR, GRAPH_HEIGHT, GRAPH_X, GRAPH_Y, GRAPH_WIDTH - PR, GRAPH_HEIGHT); 170 | 171 | context.fillRect(GRAPH_X + GRAPH_WIDTH - PR, GRAPH_Y, PR, GRAPH_HEIGHT); 172 | 173 | context.fillStyle = bg; 174 | context.globalAlpha = 0.9; 175 | context.fillRect(GRAPH_X + GRAPH_WIDTH - PR, GRAPH_Y, PR, round((1 - (value / max)) * GRAPH_HEIGHT)); 176 | 177 | } 178 | 179 | }; 180 | 181 | }; 182 | 183 | export default Stats; -------------------------------------------------------------------------------- /utils.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'https://unpkg.com/three@0.162.0/build/three.module.min.js'; 2 | 3 | /*const packRGBToUint32 = (v) => { 4 | const r = Math.floor(v.x * 255.0); 5 | const g = Math.floor(v.y * 255.0); 6 | const b = Math.floor(v.z * 255.0); 7 | return (r << 16) | (g << 8) | b; 8 | };*/ 9 | 10 | /* 11 | uint quantizeToBits(float num, float m, float bitsPowTwo) { 12 | num = clamp(num, 0.0, m); 13 | return uint(bitsPowTwo * sqrt(num / m)); 14 | } 15 | uint packThreeBytes(vec3 light) { 16 | float maxNum = 10.0; 17 | float bitsPowTwo = 1023.0; 18 | uint r = quantizeToBits(light.r, maxNum, bitsPowTwo); 19 | uint g = quantizeToBits(light.g, maxNum, bitsPowTwo); 20 | uint b = quantizeToBits(light.b, maxNum, bitsPowTwo); 21 | 22 | return r << 20 | g << 10 | b; 23 | } 24 | */ 25 | 26 | const quantizeToBits = (num, m, bitsPowTwo) => { 27 | num = Math.min(Math.max(num, 0.0), m); 28 | return Math.floor(bitsPowTwo * Math.sqrt(num / m)); 29 | } 30 | const packRGBToUint32 = (v) => { 31 | const maxNum = 10.0; 32 | const bitsPowTwo = 1023.0; 33 | const r = quantizeToBits(v.x, maxNum, bitsPowTwo); 34 | const g = quantizeToBits(v.y, maxNum, bitsPowTwo); 35 | const b = quantizeToBits(v.z, maxNum, bitsPowTwo); 36 | return (r << 20) | (g << 10) | b; 37 | } 38 | const createBufferTexture = (size) => { 39 | const buffer = new Float32Array(size * size * 4); 40 | const tex = new THREE.DataTexture(buffer, size, size, THREE.RGBAFormat, THREE.FloatType); 41 | tex.minFilter = THREE.NearestFilter; 42 | tex.maxFilter = THREE.NearestFilter; 43 | tex.needsUpdate = true; 44 | return [buffer, tex]; 45 | }; 46 | const createGBufferSplit = (width, height) => { 47 | const texture = new THREE.WebGLRenderTarget(width, height, { 48 | minFilter: THREE.LinearFilter, 49 | magFilter: THREE.NearestFilter, 50 | format: THREE.RGBAFormat, 51 | type: THREE.HalfFloatType 52 | }); 53 | texture.depthTexture = new THREE.DepthTexture(width, height, THREE.UnsignedIntType); 54 | return texture; 55 | } 56 | const imageToDataTexture = (map, TARGET_SIZE_X, TARGET_SIZE_Y) => { 57 | const canvas = document.createElement('canvas'); 58 | canvas.width = TARGET_SIZE_X; 59 | canvas.height = TARGET_SIZE_Y; 60 | const ctx = canvas.getContext('2d'); 61 | ctx.drawImage(map.image, 0, 0, TARGET_SIZE_X, TARGET_SIZE_Y); 62 | return ctx.getImageData(0, 0, TARGET_SIZE_X, TARGET_SIZE_Y); 63 | } 64 | 65 | function computeSplits({ posArray, posBufferCount, sum, workerCount, VOXEL_RATIO_MAX }) { 66 | let triangleSurfaceAreas = new Float32Array(sum / 3); 67 | let sahSum = 0; 68 | for (let i = 0; i < posBufferCount; i += 12) { 69 | const x1 = posArray[i]; 70 | const y1 = posArray[i + 1]; 71 | const z1 = posArray[i + 2]; 72 | const x2 = posArray[i + 4]; 73 | const y2 = posArray[i + 5]; 74 | const z2 = posArray[i + 6]; 75 | const x3 = posArray[i + 8]; 76 | const y3 = posArray[i + 9]; 77 | const z3 = posArray[i + 10]; 78 | const ABx = x2 - x1, 79 | ABy = y2 - y1, 80 | ABz = z2 - z1; 81 | const ACx = x3 - x1, 82 | ACy = y3 - y1, 83 | ACz = z3 - z1; 84 | 85 | // Cross product components 86 | const crossX = ABy * ACz - ABz * ACy; 87 | const crossY = ABz * ACx - ABx * ACz; 88 | const crossZ = ABx * ACy - ABy * ACx; 89 | // Area of the triangle plus a constant factor to account for the cost of rasterizing small triangles 90 | const area = 0.5 * Math.sqrt(crossX ** 2 + crossY ** 2 + crossZ ** 2) * VOXEL_RATIO_MAX + 10.0; 91 | sahSum += area; 92 | triangleSurfaceAreas[i / 12] = sahSum; 93 | } 94 | // Compute splits 95 | const sahSplitSize = sahSum / workerCount; 96 | let sahSplits = new Int32Array(workerCount); 97 | for (let i = 0; i < workerCount; i++) { 98 | sahSplits[i] = triangleSurfaceAreas.findIndex((value) => value >= sahSplitSize * (i + 1)); 99 | } 100 | sahSplits[workerCount - 1] = sum / 3; 101 | return sahSplits; 102 | } 103 | export { packRGBToUint32, createBufferTexture, imageToDataTexture, computeSplits, createGBufferSplit }; -------------------------------------------------------------------------------- /voxel-worker.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'https://unpkg.com/three@0.162.0/build/three.module.min.js'; 2 | const renderTriangle = (ax, ay, az, bx, by, bz, cx, cy, cz, voxelAmountX, voxelAmountY, voxelAmountZ, indexArray, i) => { 3 | const voxelAmountXY = voxelAmountX * voxelAmountY; 4 | 5 | const flooredAx = Math.floor(ax); 6 | const flooredAy = Math.floor(ay); 7 | const flooredAz = Math.floor(az); 8 | const flooredBx = Math.floor(bx); 9 | const flooredBy = Math.floor(by); 10 | const flooredBz = Math.floor(bz); 11 | const flooredCx = Math.floor(cx); 12 | const flooredCy = Math.floor(cy); 13 | const flooredCz = Math.floor(cz); 14 | let xmin = Math.max(Math.floor(Math.min(ax, bx, cx)), 0); 15 | let xmax = Math.min(Math.ceil(Math.max(ax, bx, cx)), voxelAmountX - 1); 16 | let ymin = Math.max(Math.floor(Math.min(ay, by, cy)), 0); 17 | let ymax = Math.min(Math.ceil(Math.max(ay, by, cy)), voxelAmountY - 1); 18 | let zmin = Math.max(Math.floor(Math.min(az, bz, cz)), 0); 19 | let zmax = Math.min(Math.ceil(Math.max(az, bz, cz)), voxelAmountZ - 1); 20 | 21 | if (flooredAx === flooredBx && flooredAx === flooredCx && 22 | flooredAy === flooredBy && flooredAy === flooredCy && 23 | flooredAz === flooredBz && flooredAz === flooredCz) { 24 | 25 | const index = (flooredAz * voxelAmountXY + flooredAy * voxelAmountX + flooredAx); 26 | if (i > indexArray[index]) { 27 | indexArray[index] = i; 28 | } 29 | return; 30 | } 31 | let Ax = az; 32 | let Ay = ay; 33 | let Adepth = ax; 34 | let Bx = bz; 35 | let By = by; 36 | let Bdepth = bx; 37 | let Cx = cz; 38 | let Cy = cy; 39 | let Cdepth = cx; 40 | 41 | let invDenom = 1.0 / ((By - Cy) * (Ax - Cx) + (Cx - Bx) * (Ay - Cy)); 42 | if (Number.isFinite(invDenom)) { 43 | const b1y = (By - Cy); 44 | const b1x = (Cx - Bx); 45 | const b2y = (Cy - Ay); 46 | const b2x = (Ax - Cx); 47 | let minX = zmin; 48 | let maxX = zmax; 49 | let minY = ymin; 50 | let maxY = ymax; 51 | const b1yinvDenom = b1y * invDenom; 52 | const b1xinvDenom = b1x * invDenom; 53 | const b2yinvDenom = b2y * invDenom; 54 | const b2xinvDenom = b2x * invDenom; 55 | const b1yinvDenomPlusb2yinvDenom = -(b1yinvDenom + b2yinvDenom); 56 | const w1A = -Cx * b1yinvDenom - Cy * b1xinvDenom; 57 | const w2A = -Cx * b2yinvDenom - Cy * b2xinvDenom; 58 | for (let y = minY; y <= maxY; y++) { 59 | let weight1 = w1A + b1yinvDenom * minX + b1xinvDenom * y; 60 | let weight2 = w2A + b2yinvDenom * minX + b2xinvDenom * y; 61 | let weight3 = 1 - weight1 - weight2; 62 | for (let x = minX; x <= maxX; x++) { 63 | weight1 += b1yinvDenom; 64 | weight2 += b2yinvDenom; 65 | weight3 += b1yinvDenomPlusb2yinvDenom; 66 | if (weight1 < 0 || weight2 < 0 || weight3 < 0) { 67 | continue; 68 | } 69 | const depth = weight1 * Adepth + weight2 * Bdepth + weight3 * Cdepth; 70 | if (depth >= 0 && depth < voxelAmountX) { 71 | const index = x * voxelAmountXY + y * voxelAmountX + Math.floor(depth); 72 | if (i > indexArray[index]) { 73 | indexArray[index] = i; 74 | } 75 | } 76 | 77 | } 78 | } 79 | } 80 | Ax = ax; 81 | Ay = az; 82 | Bx = bx; 83 | By = bz; 84 | Cx = cx; 85 | Cy = cz; 86 | Adepth = ay; 87 | Bdepth = by; 88 | Cdepth = cy; 89 | invDenom = 1.0 / ((By - Cy) * (Ax - Cx) + (Cx - Bx) * (Ay - Cy)); 90 | if (Number.isFinite(invDenom)) { 91 | const b1y = (By - Cy); 92 | const b1x = (Cx - Bx); 93 | const b2y = (Cy - Ay); 94 | const b2x = (Ax - Cx); 95 | let minX = xmin; 96 | let maxX = xmax; 97 | let minY = zmin; 98 | let maxY = zmax; 99 | const b1yinvDenom = b1y * invDenom; 100 | const b1xinvDenom = b1x * invDenom; 101 | const b2yinvDenom = b2y * invDenom; 102 | const b2xinvDenom = b2x * invDenom; 103 | const b1yinvDenomPlusb2yinvDenom = -(b1yinvDenom + b2yinvDenom); 104 | const w1A = -Cx * b1yinvDenom - Cy * b1xinvDenom; 105 | const w2A = -Cx * b2yinvDenom - Cy * b2xinvDenom; 106 | for (let y = minY; y <= maxY; y++) { 107 | let weight1 = w1A + b1yinvDenom * minX + b1xinvDenom * y; 108 | let weight2 = w2A + b2yinvDenom * minX + b2xinvDenom * y; 109 | let weight3 = 1 - weight1 - weight2; 110 | for (let x = minX; x <= maxX; x++) { 111 | weight1 += b1yinvDenom; 112 | weight2 += b2yinvDenom; 113 | weight3 += b1yinvDenomPlusb2yinvDenom; 114 | if (weight1 < 0 || weight2 < 0 || weight3 < 0) { 115 | continue; 116 | } 117 | const depth = weight1 * Adepth + weight2 * Bdepth + weight3 * Cdepth; 118 | if (depth >= 0 && depth < voxelAmountY) { 119 | const index = y * voxelAmountXY + Math.floor(depth) * voxelAmountX + x; 120 | if (i > indexArray[index]) { 121 | indexArray[index] = i; 122 | } 123 | 124 | } 125 | 126 | } 127 | } 128 | } 129 | Ax = ax; 130 | Ay = ay; 131 | Bx = bx; 132 | By = by; 133 | Cx = cx; 134 | Cy = cy; 135 | Adepth = az; 136 | Bdepth = bz; 137 | Cdepth = cz; 138 | invDenom = 1.0 / ((By - Cy) * (Ax - Cx) + (Cx - Bx) * (Ay - Cy)); 139 | if (Number.isFinite(invDenom)) { 140 | const b1y = (By - Cy); 141 | const b1x = (Cx - Bx); 142 | const b2y = (Cy - Ay); 143 | const b2x = (Ax - Cx); 144 | let minX = xmin; 145 | let maxX = xmax; 146 | let minY = ymin; 147 | let maxY = ymax; 148 | const b1yinvDenom = b1y * invDenom; 149 | const b1xinvDenom = b1x * invDenom; 150 | const b2yinvDenom = b2y * invDenom; 151 | const b2xinvDenom = b2x * invDenom; 152 | const b1yinvDenomPlusb2yinvDenom = -(b1yinvDenom + b2yinvDenom); 153 | const w1A = -Cx * b1yinvDenom - Cy * b1xinvDenom; 154 | const w2A = -Cx * b2yinvDenom - Cy * b2xinvDenom; 155 | for (let y = minY; y <= maxY; y++) { 156 | let weight1 = w1A + b1yinvDenom * minX + b1xinvDenom * y; 157 | let weight2 = w2A + b2yinvDenom * minX + b2xinvDenom * y; 158 | let weight3 = 1 - weight1 - weight2; 159 | for (let x = minX; x <= maxX; x++) { 160 | weight1 += b1yinvDenom; 161 | weight2 += b2yinvDenom; 162 | weight3 += b1yinvDenomPlusb2yinvDenom; 163 | if (weight1 < 0 || weight2 < 0 || weight3 < 0) { 164 | continue; 165 | } 166 | const depth = weight1 * Adepth + weight2 * Bdepth + weight3 * Cdepth; 167 | if (depth >= 0 && depth < voxelAmountZ) { 168 | const index = Math.floor(depth) * voxelAmountXY + y * voxelAmountX + x; 169 | if (i > indexArray[index]) { 170 | indexArray[index] = i; 171 | } 172 | } 173 | } 174 | } 175 | } 176 | } 177 | const positionMap = new Map(); 178 | const indexMap = new Map(); 179 | self.onmessage = async(event) => { 180 | const { type, data } = event.data; 181 | if (type === "add") { 182 | const { id, position, index } = data; 183 | positionMap.set(id, position); 184 | indexMap.set(id, index); 185 | } 186 | if (type === "transform") { 187 | const { meshMatrixData, posBufferAux, startIndex, startMesh, endMesh } = data; 188 | 189 | let posBufferCount = startIndex; 190 | for (let i = startMesh; i < endMesh; i++) { 191 | const id = i; 192 | const positions = positionMap.get(id); 193 | const indices = indexMap.get(id); 194 | const matrix = meshMatrixData.slice(id * 16, id * 16 + 16); 195 | const e0 = matrix[0]; 196 | const e1 = matrix[1]; 197 | const e2 = matrix[2]; 198 | const e4 = matrix[4]; 199 | const e5 = matrix[5]; 200 | const e6 = matrix[6]; 201 | const e8 = matrix[8]; 202 | const e9 = matrix[9]; 203 | const e10 = matrix[10]; 204 | const e12 = matrix[12]; 205 | const e13 = matrix[13]; 206 | const e14 = matrix[14]; 207 | for (let j = 0, iLen = indices.length; j < iLen; j++) { 208 | const index = indices[j] * 3; 209 | const x = positions[index]; 210 | const y = positions[index + 1]; 211 | const z = positions[index + 2]; 212 | 213 | posBufferAux[posBufferCount++] = x * e0 + y * e4 + z * e8 + e12; 214 | posBufferAux[posBufferCount++] = x * e1 + y * e5 + z * e9 + e13; 215 | posBufferAux[posBufferCount++] = x * e2 + y * e6 + z * e10 + e14; 216 | posBufferAux[posBufferCount++] = 1.0; 217 | } 218 | } 219 | 220 | self.postMessage({ type: "transform", data: { posBufferCount: posBufferCount } }); 221 | } 222 | if (type === "voxelize") { 223 | const voxelCenter = new THREE.Vector3(data.voxelCenter.x, data.voxelCenter.y, data.voxelCenter.z); 224 | const voxelSize = new THREE.Vector3(data.voxelSize.x, data.voxelSize.y, data.voxelSize.z); 225 | const VOXEL_AMOUNT = new THREE.Vector3(data.VOXEL_AMOUNT.x, data.VOXEL_AMOUNT.y, data.VOXEL_AMOUNT.z); 226 | const halfSize = voxelSize.clone().multiplyScalar(0.5); 227 | const voxelRatio = VOXEL_AMOUNT.clone().divide(voxelSize); 228 | const halfSizeMinusVoxelCenter = halfSize.clone().sub(voxelCenter); 229 | 230 | const posArray = data.posArray; 231 | //const iLen = indices.length; 232 | const voxelAmountX = VOXEL_AMOUNT.x; 233 | const voxelAmountY = VOXEL_AMOUNT.y; 234 | const voxelAmountXY = voxelAmountX * voxelAmountY; 235 | const voxelAmountZ = VOXEL_AMOUNT.z; 236 | const halfSizeMinusVoxelCenterX = halfSizeMinusVoxelCenter.x; 237 | const halfSizeMinusVoxelCenterY = halfSizeMinusVoxelCenter.y; 238 | const halfSizeMinusVoxelCenterZ = halfSizeMinusVoxelCenter.z; 239 | const voxelRatioX = voxelRatio.x; 240 | const voxelRatioY = voxelRatio.y; 241 | const voxelRatioZ = voxelRatio.z; 242 | const halfSizeMinusVoxelCenterXTimesVoxelRatioX = halfSizeMinusVoxelCenterX * voxelRatioX; 243 | const halfSizeMinusVoxelCenterYTimesVoxelRatioY = halfSizeMinusVoxelCenterY * voxelRatioY; 244 | const halfSizeMinusVoxelCenterZTimesVoxelRatioZ = halfSizeMinusVoxelCenterZ * voxelRatioZ; 245 | const indexArray = data.indexArray; 246 | const indexOffset = data.indexOffset; 247 | const pLength = posArray.length; 248 | for (let i = 0, j = indexOffset; i < pLength; i += 12) { 249 | renderTriangle(posArray[i] * voxelRatioX + halfSizeMinusVoxelCenterXTimesVoxelRatioX, 250 | posArray[i + 1] * voxelRatioY + halfSizeMinusVoxelCenterYTimesVoxelRatioY, 251 | posArray[i + 2] * voxelRatioZ + halfSizeMinusVoxelCenterZTimesVoxelRatioZ, 252 | posArray[i + 4] * voxelRatioX + halfSizeMinusVoxelCenterXTimesVoxelRatioX, 253 | posArray[i + 5] * voxelRatioY + halfSizeMinusVoxelCenterYTimesVoxelRatioY, 254 | posArray[i + 6] * voxelRatioZ + halfSizeMinusVoxelCenterZTimesVoxelRatioZ, 255 | posArray[i + 8] * voxelRatioX + halfSizeMinusVoxelCenterXTimesVoxelRatioX, 256 | posArray[i + 9] * voxelRatioY + halfSizeMinusVoxelCenterYTimesVoxelRatioY, 257 | posArray[i + 10] * voxelRatioZ + halfSizeMinusVoxelCenterZTimesVoxelRatioZ, 258 | voxelAmountX, 259 | voxelAmountY, 260 | voxelAmountZ, 261 | indexArray, 262 | j++ 263 | ); 264 | } 265 | self.postMessage({}); 266 | } 267 | 268 | 269 | } --------------------------------------------------------------------------------