three-openvdb 0.0.0-stage → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -2
- package/index.cjs.js +134 -0
- package/index.js +67 -0
- package/package.json +41 -4
package/README.md
CHANGED
|
@@ -1,3 +1,27 @@
|
|
|
1
|
-
#
|
|
1
|
+
# three-openvdb
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
three.js rendering of OpenVDB volumes, built on `openvdb`: `VDBLoader`, `FrameVolume`, `VDBAnimationLoader`, `FogVolume`, `LevelSet`, `Points`, `Bbox`, volume materials and `MeshVolume`.
|
|
4
|
+
|
|
5
|
+
Streamed `.wvdb` files, made from `.vdb` files with the `wvdb` CLI (npm package `webvdb`):
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
import { FrameVolume, WVDBLoader, WVDBAnimationLoader } from 'three-openvdb';
|
|
9
|
+
|
|
10
|
+
// NOTE One file. LOD 0 shows at once, each next LOD replaces it while the download continues.
|
|
11
|
+
new WVDBLoader().load('./explosion.wvdb', (volumeData) => {
|
|
12
|
+
const grid = volumeData.grids.density;
|
|
13
|
+
const volume = new FrameVolume(grid.data, { resolution: grid.resolution, bbox: grid.bbox });
|
|
14
|
+
|
|
15
|
+
volumeData.attach(volume);
|
|
16
|
+
scene.add(volume);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// NOTE A sequence. LOD 0 of all frames loads first, so the animation plays early.
|
|
20
|
+
const animation = new WVDBAnimationLoader()
|
|
21
|
+
.setOptions({ emissiveGrid: 'flames', emissiveScale: 6.0 })
|
|
22
|
+
.load(urls, () => animation.play());
|
|
23
|
+
|
|
24
|
+
scene.add(animation);
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
See the main README of the repository for the API, and `src/docs/wvdb/README.md` for the format.
|
package/index.cjs.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var Three = require('three');
|
|
6
|
+
var openvdb = require('openvdb');
|
|
7
|
+
var BufferGeometryUtils = require('three/examples/jsm/utils/BufferGeometryUtils.js');
|
|
8
|
+
|
|
9
|
+
function _interopNamespace(e) {
|
|
10
|
+
if (e && e.__esModule) return e;
|
|
11
|
+
var n = Object.create(null);
|
|
12
|
+
if (e) {
|
|
13
|
+
Object.keys(e).forEach(function (k) {
|
|
14
|
+
if (k !== 'default') {
|
|
15
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
16
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
17
|
+
enumerable: true,
|
|
18
|
+
get: function () { return e[k]; }
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
n["default"] = e;
|
|
24
|
+
return Object.freeze(n);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
var Three__namespace = /*#__PURE__*/_interopNamespace(Three);
|
|
28
|
+
var BufferGeometryUtils__namespace = /*#__PURE__*/_interopNamespace(BufferGeometryUtils);
|
|
29
|
+
|
|
30
|
+
class VDBLoader extends Three__namespace.Loader{constructor(e){super(e);}load(e,o,r,a){openvdb.loadVDB(e).then((e=>{o(e);})).catch((e=>{a(e);}));}}
|
|
31
|
+
|
|
32
|
+
const Bbox=Three__namespace.Box3;
|
|
33
|
+
|
|
34
|
+
const sampleColors=[16711680,65280,255];class VolumeToBbox extends Three__namespace.Group{processes=[];constructor(e,{color:o,opacity:t}={}){let r;super(),r=e instanceof Array?e:void 0!==e.grids?Object.values(e.grids):[e];const s=new Three__namespace.Object3D,a=new Bbox;Object.values(r).forEach(((e,r)=>{const c=new Three__namespace.InstancedMesh(new Three__namespace.BoxGeometry(1,1,1),new Three__namespace.MeshBasicMaterial({wireframe:!0,color:o||sampleColors[r],transparent:!0,opacity:t}),e.leavesCount);let n=0;const i=o=>{o.isLeaf()&&(a.set(...e.getWorldBbox(o)),a.getCenter(s.position),a.getSize(s.scale),s.updateMatrix(),c.setMatrixAt(n++,s.matrix)),o.childMask?.countOn()>0&&o.childMask.forEachOn((({offset:e})=>{i(o.table[e]);}));};e.root.table.forEach((e=>i(e))),this.add(c);}));}}
|
|
35
|
+
|
|
36
|
+
const lights={useAmbientLights:32,usePointLights:16,useDirectionalLights:8,useSpotLights:4,useHemisphereLights:2,useEnvironment:1};
|
|
37
|
+
|
|
38
|
+
const vector2=new Three__namespace.Vector2,clearColor$1=new Three__namespace.Color;let depthRenderTarget,depthMaterial,depthBuffer,depthView,depthPacking=0,cachedFrame=-1,cachedCamera=null;class MeshDistanceMaterial extends Three__namespace.MeshBasicMaterial{name="MeshDistanceMaterial";customProgramCacheKey=()=>"MeshDistanceMaterial";_uniforms={};constructor(e){super(e),this._uniforms=e._uniforms||{},this.onBeforeCompile=e=>{Object.entries(this._uniforms).forEach((([r,a])=>{e.uniforms[r]=a;}));const r="\n #define DISTANCE\n\n varying mat4 mModelMatrix;\n varying mat4 mViewMatrix;\n varying mat4 mProjectionMatrix;\n varying mat4 mModelViewMatrix;\n ";e.vertexShader=e.vertexShader.replace("#include <common>",`\n ${r}\n\n out vec3 vPosition;\n\n #include <common>\n `).replace("#include <worldpos_vertex>","\n #include <worldpos_vertex>\n\n vPosition = worldPosition.xyz;\n\n mModelMatrix = modelMatrix;\n mViewMatrix = viewMatrix;\n mProjectionMatrix = projectionMatrix;\n mModelViewMatrix = modelViewMatrix;\n "),e.fragmentShader=e.fragmentShader.replace("#include <common>",`\n precision highp float;\n\n in vec3 vPosition;\n\n uniform float cameraNear;\n uniform float cameraFar;\n\n ${r}\n\n #include <packing>\n #include <common>\n `).replace("#include <opaque_fragment>","\n float dist = length(vPosition - cameraPosition);\n dist = (dist - cameraNear) / (cameraFar - cameraNear);\n dist = saturate(dist);\n \n gl_FragColor = packDepthToRGBA(dist);\n ").replace("#include <tonemapping_fragment>","").replace("#include <colorspace_fragment>","").replace("#include <fog_fragment>","").replace("#include <premultiplied_alpha_fragment>","").replace("#include <dithering_fragment>","");};}}const getDepthUniforms=()=>(depthRenderTarget||(depthRenderTarget=new Three__namespace.WebGLRenderTarget,depthRenderTarget.texture.minFilter=Three__namespace.NearestFilter,depthRenderTarget.texture.magFilter=Three__namespace.NearestFilter,depthRenderTarget.texture.generateMipmaps=!1,depthRenderTarget.depthTexture=new Three__namespace.DepthTexture,depthBuffer=depthRenderTarget.texture,depthPacking=1,depthMaterial=new MeshDistanceMaterial({_uniforms:{cameraPosition:{value:new Three__namespace.Vector3(0,0,0)},cameraNear:{value:1},cameraFar:{value:1}}}),depthView=new Three__namespace.Vector4(.1,1,1,1)),{depthScreenMap:{value:depthBuffer},depthPacking:{value:depthPacking},depthView:{value:depthView}});const updateFrameDepth=(e,r,a,t,n)=>{if(!depthBuffer)return;n._uniforms.cameraPosition.value.setFromMatrixPosition(a.matrixWorld),n._uniforms.cameraNear.value=a.near,n._uniforms.cameraFar.value=a.far;if((e?.info?.render?.frame||0)===cachedFrame&&a===cachedCamera)return;depthMaterial._uniforms.cameraPosition.value.setFromMatrixPosition(a.matrixWorld),depthMaterial._uniforms.cameraNear.value=a.near,depthMaterial._uniforms.cameraFar.value=a.far,e.getSize(vector2),depthRenderTarget.setSize(vector2.x,vector2.y),depthView.x=a.near,depthView.y=a.far,depthView.z=vector2.x,depthView.w=vector2.y;const i=[];r.traverseVisible((e=>{(e instanceof VolumeToFog||e.isFogVolume)&&(e.visible=!1,i.push(e));}));const o=r.background,l=e.getClearAlpha();e.getClearColor(clearColor$1),r.background=null,r.overrideMaterial=depthMaterial,e.setClearColor(16777215,1),e.setRenderTarget(depthRenderTarget),e.render(r,a),e.setRenderTarget(null),e.setClearColor(clearColor$1,l),r.overrideMaterial=null,r.background=o,i.forEach((e=>{e.visible=!0;})),cachedCamera=a,cachedFrame=e?.info?.render?.frame||0;};
|
|
39
|
+
|
|
40
|
+
class VolumeBasicMaterial extends Three__namespace.MeshStandardMaterial{name="VolumeBasicMaterial";isVolumetricFogMaterial=!0;customProgramCacheKey=()=>openvdb.getUuid(this);_uniforms={baseColor:{value:new Three__namespace.Color(16777215)},scatterColor:{value:new Three__namespace.Color(0)},densityMap3D:{value:null},emissiveMap3D:{value:null},baseColorMap3D:{value:null},maskMap3D:{value:null},steps:{value:100},absorbance:{value:1},roughness:{value:.5},densityScale:{value:1},densityCutoff:{value:0},resolution:{value:100},offset3D:{value:new Three__namespace.Vector3(0,0,0)},wrap3D:{value:Three__namespace.ClampToEdgeWrapping},lights:{value:lights.useDirectionalLights|lights.usePointLights|lights.useSpotLights|lights.useHemisphereLights|lights.useEnvironment},cameraPosition:{value:new Three__namespace.Vector3(0,0,0)},cameraNear:{value:1},cameraFar:{value:1}};set baseColor(e){this._uniforms.baseColor.value.set(e);}get baseColor(){return this._uniforms.baseColor.value}set scatterColor(e){this._uniforms.scatterColor.value.set(e);}get scatterColor(){return this._uniforms.scatterColor.value}set densityMap3D(e){this._uniforms.densityMap3D.value=e,e.offset3D=this._uniforms.offset3D.value;}get densityMap3D(){return this._uniforms.densityMap3D.value}set emissiveMap3D(e){this._uniforms.emissiveMap3D.value=e,e.offset3D=this._uniforms.offset3D.value,this.needsUpdate=!0;}get emissiveMap3D(){return this._uniforms.emissiveMap3D.value}set maskMap3D(e){this._uniforms.maskMap3D.value=e,this.needsUpdate=!0;}get maskMap3D(){return this._uniforms.maskMap3D.value}set baseColorMap3D(e){this._uniforms.baseColorMap3D.value=e,e.offset3D=this._uniforms.offset3D.value,this.needsUpdate=!0;}get baseColorMap3D(){return this._uniforms.baseColorMap3D.value}set steps(e){this._uniforms.steps.value=e;}get steps(){return this._uniforms.steps.value}set absorbance(e){this._uniforms.absorbance.value=e;}get absorbance(){return this._uniforms.absorbance.value}set roughness(e){this._uniforms&&(this._uniforms.roughness.value=e);}get roughness(){return this._uniforms?this._uniforms.roughness.value:.5}set resolution(e){this._uniforms.resolution.value=e;}get resolution(){return this._uniforms.resolution.value}set offset3D(e){this._uniforms.offset3D.value.copy(e);}get offset3D(){return this._uniforms.offset3D.value}set wrap3D(e){this._uniforms.wrap3D.value=e,this.needsUpdate=!0;}get wrap3D(){return this._uniforms.wrap3D.value}set densityScale(e){this._uniforms.densityScale.value=e;}get densityScale(){return this._uniforms.densityScale.value}set densityCutoff(e){this._uniforms.densityCutoff.value=e;}get densityCutoff(){return this._uniforms.densityCutoff.value}set lights(e){this._uniforms.lights.value=e,this.needsUpdate=!0;}get lights(){return this._uniforms.lights.value}set useDirectionalLights(e=!0){e?this._uniforms.lights.value|=lights.useDirectionalLights:this._uniforms.lights.value&=~lights.useDirectionalLights,this.needsUpdate=!0;}get useDirectionalLights(){return 0!=(this._uniforms.lights.value&lights.useDirectionalLights)}set usePointLights(e=!0){e?this._uniforms.lights.value|=lights.usePointLights:this._uniforms.lights.value&=~lights.usePointLights,this.needsUpdate=!0;}get usePointLights(){return 0!=(this._uniforms.lights.value&lights.usePointLights)}set useHemisphereLights(e=!0){e?this._uniforms.lights.value|=lights.useHemisphereLights:this._uniforms.lights.value&=~lights.useHemisphereLights,this.needsUpdate=!0;}get useHemisphereLights(){return 0!=(this._uniforms.lights.value&lights.useHemisphereLights)}set useSpotLights(e=!0){e?this._uniforms.lights.value|=lights.useSpotLights:this._uniforms.lights.value&=~lights.useSpotLights,this.needsUpdate=!0;}get useSpotLights(){return 0!=(this._uniforms.lights.value&lights.useSpotLights)}set useEnvironment(e=!0){e?this._uniforms.lights.value|=lights.useEnvironment:this._uniforms.lights.value&=~lights.useEnvironment,this.needsUpdate=!0;}get useEnvironment(){return 0!=(this._uniforms.lights.value&lights.useEnvironment)}constructor(e={}){super(),this.side=Three__namespace.BackSide,this.depthWrite=!1,this.depthTest=!1,this.transparent=!0,Object.keys(this._uniforms).forEach((n=>{void 0!==e[n]&&null!==e[n]&&(this[n]=e[n],e[n]instanceof Three__namespace.Texture&&(e[n].offset3D=this._uniforms.offset3D.value));})),this.onBeforeCompile=(n,t)=>{const i=getDepthUniforms();Object.entries({...this._uniforms,...i}).forEach((([e,t])=>{n.uniforms[e]=t;}));const o=`\n #define VOLUME_BBOX_SPAN 0.5\n ${e.emissiveMap3D?"#define USE_EMISSIVE_GRID 1":""}\n ${e.baseColorMap3D?"#define USE_BASE_COLOR_GRID 1":""}\n ${e.maskMap3D?"#define USE_MASK_GRID 1":""}\n\n #define VOLUME_USE_ENVIRONMENT ${Number(this.useEnvironment)} != 0\n #define VOLUME_USE_HEMI_LIGHTS ${Number(this.useHemisphereLights)} != 0\n #define VOLUME_USE_POINT_LIGHTS ${Number(this.usePointLights)} != 0\n #define VOLUME_USE_DIR_LIGHTS ${Number(this.useDirectionalLights)} != 0\n #define VOLUME_USE_SPOT_LIGHTS ${Number(this.useSpotLights)} != 0\n `,a="\n varying mat4 mModelMatrix;\n varying mat4 mViewMatrix;\n varying mat4 mProjectionMatrix;\n varying mat4 mModelViewMatrix;\n varying mat4 mInverseModelViewMatrix;\n varying mat3 mInverseNormalMatrix;\n ";n.vertexShader=n.vertexShader.replace("#include <common>",`\n ${a}\n\n out vec3 vOrigin;\n out vec3 vDirection;\n out vec3 vPosition;\n\n #include <common>\n `).replace("#include <worldpos_vertex>","\n #include <worldpos_vertex>\n\n vOrigin = vec3(inverse(modelMatrix) * vec4(cameraPosition, 1.0)).xyz;\n vDirection = position - vOrigin;\n vPosition = position;\n\n mModelMatrix = modelMatrix;\n mViewMatrix = viewMatrix;\n mProjectionMatrix = projectionMatrix;\n mModelViewMatrix = modelViewMatrix;\n mInverseModelViewMatrix = inverse(modelViewMatrix);\n mInverseNormalMatrix = inverse(normalMatrix);\n ");const r=`\n vec2 getVolumeBbox(vec3 vPointOfReference) {\n const vec3 vBoxMin = vec3(-0.5);\n const vec3 vBoxMax = vec3(0.5);\n vec3 vInvPointOfReference = 1.0 / vPointOfReference;\n \n vec3 vMinRange = (-vOrigin - VOLUME_BBOX_SPAN) * vInvPointOfReference;\n vec3 vMaxRange = (-vOrigin + VOLUME_BBOX_SPAN) * vInvPointOfReference;\n \n vec3 vMin = min(vMinRange, vMaxRange);\n vec3 vMax = max(vMinRange, vMaxRange);\n\n return vec2(\n max(vMin.x, max(vMin.y, vMin.z)),\n min(vMax.x, min(vMax.y, vMax.z))\n );\n }\n\n vec3 radiation = vec3(0.);\n vec3 getBlackBodyRadiation(float temperature) {\n if (temperature == 0.) {\n return vec3(0.);\n }\n\n #ifndef USE_EMISSIVE_GRID\n return vec3(0.);\n #endif\n\n // NOTE Blackbody radiation source - https://www.shadertoy.com/view/4tdGWM\n float temperatureScaled = temperature * 1000.;\n\n radiation.r += 1. / (exp(19E3 * 1. / temperatureScaled) - 1.);\n radiation.g += 3.375 / (exp(19E3 * 1.5 / temperatureScaled) - 1.);\n radiation.b += 8. / (exp(19E3 * 2. / temperatureScaled) - 1.);\n\n return (radiation / max(radiation.r,max(radiation.g,radiation.b)));\n }\n\n float loopUV(float x) {\n if (abs(mod(floor(x), 2.)) == 1.) {\n return ceil(x) - x;\n } else {\n return x - floor(x);\n }\n }\n\n vec3 mapTextureSample0(vec3 position){\n vec3 uv = position + VOLUME_BBOX_SPAN;\n\n ${this.wrap3D===Three__namespace.RepeatWrapping?"return mod(uv, 1.);":""}\n ${0===this.wrap3D||this.wrap3D===Three__namespace.ClampToEdgeWrapping?"// NOTE Return UV":""}\n ${this.wrap3D===Three__namespace.MirroredRepeatWrapping?"\n uv.x = loopUV(uv.x);\n uv.y = loopUV(uv.y);\n uv.z = loopUV(uv.z);\n ":""}\n\n return uv;\n }\n\n vec3 mapTextureSample(vec3 position, vec3 offset){\n vec3 uv = position + VOLUME_BBOX_SPAN + offset;\n\n ${this.wrap3D===Three__namespace.RepeatWrapping?"return mod(uv, 1.);":""}\n ${0===this.wrap3D||this.wrap3D===Three__namespace.ClampToEdgeWrapping?"// NOTE Return UV":""}\n ${this.wrap3D===Three__namespace.MirroredRepeatWrapping?"\n uv.x = loopUV(uv.x);\n uv.y = loopUV(uv.y);\n uv.z = loopUV(uv.z);\n ":""}\n\n return uv;\n }\n\n float blendSample(float value) {\n return value * densityScale;\n \n // float fSmoothingThreshold = 0.1;\n // float fSmoothingBlend = 0.1;\n\n // return smoothstep(\n // fSmoothingThreshold - fSmoothingBlend,\n // fSmoothingThreshold + fSmoothingBlend,\n // value\n // ) * densityScale;\n }\n\n float clampedTexture(sampler3D source, vec3 position) {\n ${0===this.wrap3D||this.wrap3D===Three__namespace.ClampToEdgeWrapping?"\n vec3 vAbs = abs(position);\n\n if (max(vAbs.x, max(vAbs.y, vAbs.z)) > .5) {\n return 0.;\n }\n ":""}\n\n return texture(source, mapTextureSample(position, offset3D)).r;\n }\n\n float clampedMaskTexture(sampler3D source, vec3 position) {\n ${0===this.wrap3D||this.wrap3D===Three__namespace.ClampToEdgeWrapping?"\n vec3 vAbs = abs(position);\n\n if (max(vAbs.x, max(vAbs.y, vAbs.z)) > .5) {\n return 0.;\n }\n ":""}\n\n return texture(source, mapTextureSample0(position)).r;\n }\n\n vec3 clampedTextureRGB(sampler3D source, vec3 position) {\n return texture(source, mapTextureSample(position, offset3D)).rgb;\n }\n `;n.fragmentShader=n.fragmentShader.replace("#include <uv_pars_fragment>","// NOTE Override UV calculations").replace("#include <packing>","").replace("#include <common>",`\n precision highp float;\n precision highp sampler2D;\n precision highp sampler3D;\n\n in vec3 vOrigin;\n in vec3 vDirection;\n in vec3 vPosition;\n\n uniform sampler3D densityMap3D;\n uniform sampler3D emissiveMap3D;\n uniform sampler3D baseColorMap3D;\n uniform sampler3D maskMap3D;\n uniform vec3 offset3D;\n uniform int wrap3D;\n\n uniform float steps;\n uniform float absorbance;\n uniform float densityScale;\n uniform float densityCutoff;\n uniform vec3 baseColor;\n uniform vec3 scatterColor;\n uniform float resolution;\n\n uniform float cameraNear;\n uniform float cameraFar;\n uniform sampler2D depthScreenMap;\n uniform vec4 depthView;\n\n ${o}\n ${a}\n\n #include <common>\n #include <packing>\n #include <uv_pars_fragment>\n\n ${r}\n `).replace("#include <lights_fragment_begin>","// NOTE Override light calculations").replace("#include <lights_fragment_maps>","// NOTE Override light calculations").replace("#include <lights_fragment_end>","// NOTE Override light calculations").replace("#include <opaque_fragment>","\n vec3 vWorld = -vViewPosition;\n\n vec3 vRayDirection = normalize(vDirection);\n vec2 vBounds = getVolumeBbox(vRayDirection);\n vec2 vMaxBounds = getVolumeBbox(vOrigin);\n \n if (vBounds.x > vBounds.y) {\n discard;\n }\n\n vBounds.x = max(vBounds.x, 0.0);\n vMaxBounds.x = max(vMaxBounds.x, 0.0);\n\n // Volume movement\n\n vec3 vPoint = vOrigin + vBounds.x * vRayDirection;\n vec3 vPointStep = 1.0 / abs(vRayDirection);\n float delta = min(vPointStep.x, min(vPointStep.y, vPointStep.z)) / steps;\n vec3 vDirectionDeltaStep = vRayDirection * delta;\n\n // Density calculations\n\n float density = 0.0;\n vec3 albedo = vec3(0.);\n vec3 emissive = vec3(0.);\n float volumeSample;\n float emissiveSample;\n float maskSample = 1.;\n float noiseSample;\n float noiseFactor;\n vec3 fTest;\n\n // Light calculations\n \n vec3 lightAlbedo = vec3(0.);\n vec3 vLightProbe;\n vec3 lightDirection;\n vec3 vLightStep;\n vec3 vEnvMapScatter = vec3(0.0);\n float lightSample;\n float lightAbsorbance;\n float lightDistance;\n float stepAccumulation;\n\n // Utils\n\n float absorbanceDensityRatio = min(absorbance, densityScale); // NOTE When reducing densityScale, automatically reduce absorbance\n float inverseAbsorbance = 1.0 / absorbanceDensityRatio;\n float inverseDensityScale = 1.0 / densityScale;\n float eInverseAbsorbance = exp(-1. - inverseAbsorbance); // NOTE Wrong, but looks kinda better than exp(-inverseAbsorbance)\n float eDensityAbsorbance = exp(-absorbanceDensityRatio * delta);\n float eInverseDensityScale = exp(-1. - inverseDensityScale);\n\n // Depth Testing\n\n vec2 screenUV = gl_FragCoord.xy / depthView.zw;\n float worldDepth = unpackRGBAToDepth(texture2D(depthScreenMap, screenUV));\n float fogDepth = 0.;\n vec3 vProjPoint;\n\n \n float lightMarchLimit = resolution + 2.;\n int iLightMarchLimit = int(lightMarchLimit);\n\n vec3 vUnit = (mModelMatrix * vec4(1., 0., 0., 0.)).xyz;\n float vUnitLength = length(vUnit);\n\n vec3 iblIrradiance = vec3(0.0);\n \n\n vec3 lastNonSolidPoint = vec3(vPoint);\n\n for (float i = vBounds.x; i < vBounds.y; i += delta) {\n volumeSample = clampedTexture(densityMap3D, vPoint);\n \n #ifdef USE_MASK_GRID\n maskSample = clampedMaskTexture(maskMap3D, vPoint);\n #endif\n\n density += blendSample(volumeSample) * eDensityAbsorbance * maskSample;\n\n #ifdef USE_EMISSIVE_GRID\n emissiveSample = clampedTexture(emissiveMap3D, vPoint);\n emissive = max(emissive, density * vec3(emissiveSample));\n #endif\n\n if (density < 1.) {\n lastNonSolidPoint = vPoint;\n }\n\n if (density >= 1.) {\n break;\n }\n\n vProjPoint = (mModelMatrix * vec4(vPoint, 1.0)).xyz;\n fogDepth = length(vProjPoint - cameraPosition);\n fogDepth = (fogDepth - cameraNear) / (cameraFar - cameraNear);\n fogDepth = saturate(fogDepth);\n \n if (worldDepth < fogDepth) {\n break;\n }\n\n vPoint += vDirectionDeltaStep;\n }\n\n density = clamp(density, 0.0, 1.0);\n\n vPoint = lastNonSolidPoint;\n\n if (density <= densityCutoff) {\n discard;\n }\n\n if (density > 0.) {\n\n \n vec3 baseColorSample = baseColor.rgb;\n\n #ifdef USE_BASE_COLOR_GRID\n baseColorSample *= clampedTextureRGB(baseColorMap3D, vPoint);\n #endif\n\n albedo += ambientLightColor;\n \n\n #if VOLUME_USE_ENVIRONMENT\n \n vec3 viewDir = isOrthographic ? vec3(0.0, 0.0, 1.0) : normalize(vViewPosition);\n vec3 vUVCoords = normalize(lastNonSolidPoint * mInverseNormalMatrix).xyz;\n\n #if defined( USE_ENVMAP )\n vEnvMapScatter = getIBLIrradiance(vUVCoords) * RECIPROCAL_PI;\n\n albedo += density * getIBLRadiance(viewDir, vUVCoords, roughness) * RECIPROCAL_PI;\n #endif\n \n #endif\n\n #if NUM_HEMI_LIGHTS > 0 && VOLUME_USE_HEMI_LIGHTS\n \n lightAlbedo = vec3(0.);\n vec3 textureProbe;\n\n float absorbanceUp;\n float absorbanceDown;\n float stepAccumulationUp;\n float stepAccumulationDown;\n\n for (int lightIndex = 0; lightIndex < NUM_HEMI_LIGHTS; lightIndex++) {\n\n HemisphereLight hemiLight = hemisphereLights[lightIndex];\n vLightProbe = vec3(vPoint);\n\n absorbanceUp = 0.0;\n absorbanceDown = 0.0;\n\n stepAccumulationUp = 0.;\n stepAccumulationDown = 0.;\n\n lightDirection = -normalize((vec4(hemiLight.direction, 1.) * viewMatrix).xyz);\n vLightStep = (lightDirection * VOLUME_BBOX_SPAN) / lightMarchLimit;\n\n for (int lightMarch = 1; lightMarch < int(lightMarchLimit / 2.); lightMarch++) {\n if (absorbanceUp < 1.) {\n textureProbe = vLightProbe - float(lightMarch) * vLightStep;\n\n #ifdef USE_MASK_GRID\n maskSample = clampedMaskTexture(maskMap3D, textureProbe);\n #endif\n\n lightSample = clampedTexture(densityMap3D, textureProbe);\n\n absorbanceUp += lightSample * eInverseAbsorbance;\n stepAccumulationUp += 1.;\n }\n \n if (absorbanceDown < 1.) {\n textureProbe = vLightProbe + float(lightMarch) * vLightStep;\n\n #ifdef USE_MASK_GRID\n maskSample = clampedMaskTexture(maskMap3D, textureProbe);\n #endif\n\n lightSample = clampedTexture(densityMap3D, textureProbe);\n\n absorbanceDown += blendSample(lightSample) * eInverseAbsorbance;\n stepAccumulationDown += 1.;\n }\n }\n\n absorbanceUp = min(1., absorbanceUp);\n absorbanceDown = min(1., absorbanceDown);\n\n lightAlbedo += mix(\n (1. - absorbanceUp) * hemiLight.skyColor,\n (1. - absorbanceDown) * hemiLight.groundColor,\n 0.5\n );\n }\n\n lightAlbedo *= density;\n albedo += lightAlbedo * baseColor.rgb * RECIPROCAL_PI;\n \n #endif\n\n #if NUM_POINT_LIGHTS > 0 && VOLUME_USE_POINT_LIGHTS\n \n PointLight pointLight;\n lightAlbedo = vec3(0.);\n \n for (int lightIndex = 0; lightIndex < NUM_POINT_LIGHTS; lightIndex++) {\n pointLight = pointLights[lightIndex];\n\n vLightProbe = vec3(vPoint);\n lightAbsorbance = 0.;\n stepAccumulation = 1.;\n\n lightDirection = vPoint - (mInverseModelViewMatrix * vec4(pointLight.position, 1.)).xyz;\n lightDistance = length(lightDirection) * vUnitLength;\n lightDirection = normalize(lightDirection);\n vLightStep = (lightDirection * VOLUME_BBOX_SPAN) / lightMarchLimit;\n\n for (int lightMarch = 0; lightMarch < iLightMarchLimit; lightMarch++) {\n vLightProbe -= vLightStep;\n stepAccumulation += 1.;\n\n #ifdef USE_MASK_GRID\n maskSample = clampedMaskTexture(maskMap3D, vLightProbe);\n #endif\n \n lightSample = clampedTexture(densityMap3D, vLightProbe) * sqrt(lightDistance);\n \n lightAbsorbance += blendSample(lightSample) * eInverseAbsorbance;\n\n if (lightAbsorbance >= 1.) {\n lightAbsorbance = 1.;\n break;\n }\n }\n\n lightAlbedo += (1. - lightAbsorbance) * pointLight.color;\n }\n\n lightAlbedo *= density;\n albedo += lightAlbedo * baseColor.rgb * RECIPROCAL_PI;\n \n #endif\n\n #if NUM_DIR_LIGHTS > 0 && VOLUME_USE_DIR_LIGHTS\n \n DirectionalLight directionalLight;\n lightAlbedo = vec3(0.);\n\n for (int lightIndex = 0; lightIndex < NUM_DIR_LIGHTS; lightIndex++) {\n directionalLight = directionalLights[lightIndex];\n\n vLightProbe = vec3(vPoint);\n lightAbsorbance = 0.0;\n stepAccumulation = 1.;\n\n lightDirection = -normalize((vec4(directionalLight.direction, 1.) * viewMatrix).xyz);\n vLightStep = (lightDirection * VOLUME_BBOX_SPAN) / lightMarchLimit;\n\n for (int lightMarch = 0; lightMarch < iLightMarchLimit; lightMarch++) {\n vLightProbe -= vLightStep;\n stepAccumulation += 1.;\n\n #ifdef USE_MASK_GRID\n maskSample = clampedMaskTexture(maskMap3D, vLightProbe);\n #endif\n \n float lightSample = clampedTexture(densityMap3D, vLightProbe);\n\n lightAbsorbance += blendSample(lightSample) * eInverseAbsorbance;\n\n if (lightAbsorbance >= 1.) {\n lightAbsorbance = 1.;\n break;\n }\n }\n\n lightAlbedo += (1. - lightAbsorbance) * directionalLight.color;\n }\n\n lightAlbedo *= density;\n albedo += lightAlbedo * baseColor.rgb * RECIPROCAL_PI;\n \n #endif\n\n #if NUM_SPOT_LIGHTS > 0 && VOLUME_USE_SPOT_LIGHTS\n \n SpotLight spotLight;\n lightAlbedo = vec3(0.);\n\n float angleCos;\n float spotAttenuation;\n \n for (int lightIndex = 0; lightIndex < NUM_SPOT_LIGHTS; lightIndex++) {\n spotLight = spotLights[lightIndex];\n\n vLightProbe = vec3(vPoint);\n lightAbsorbance = 0.0;\n stepAccumulation = 1.;\n\n lightDirection = vPoint - (mInverseModelViewMatrix * vec4(spotLight.position, 1.)).xyz;\n lightDistance = length(lightDirection) * vUnitLength;\n lightDirection = normalize(lightDirection);\n vLightStep = (lightDirection * VOLUME_BBOX_SPAN) / lightMarchLimit;\n\n angleCos = dot(normalize(-lightDirection), normalize(mInverseNormalMatrix * spotLight.direction));\n spotAttenuation = smoothstep(spotLight.coneCos, spotLight.penumbraCos, angleCos);\n\n if (spotAttenuation > 0.) {\n for (int lightMarch = 0; lightMarch < iLightMarchLimit; lightMarch++) {\n vLightProbe -= vLightStep;\n stepAccumulation += 1.;\n\n #ifdef USE_MASK_GRID\n maskSample = clampedMaskTexture(maskMap3D, vLightProbe);\n #endif\n \n lightSample = clampedTexture(densityMap3D, vLightProbe);\n \n lightAbsorbance += blendSample(lightSample) * eInverseAbsorbance;\n\n if (lightAbsorbance >= 1.) {\n lightAbsorbance = 1.;\n break;\n }\n }\n\n lightAlbedo += (1. - lightAbsorbance) * spotAttenuation * spotLight.color;\n }\n }\n\n lightAlbedo *= density;\n albedo += lightAlbedo * baseColor.rgb * RECIPROCAL_PI;\n \n #endif\n\n albedo *= baseColorSample;\n }\n \n emissive = getBlackBodyRadiation(emissive.r);\n albedo += emissive;\n\n outgoingLight.rgb = max(max(scatterColor, vEnvMapScatter), albedo);\n diffuseColor.a = saturate(density) * opacity;\n\n #include <opaque_fragment>\n ");};}}
|
|
41
|
+
|
|
42
|
+
const getDensity$1=(t,e)=>t.isLevelSet?.()?t.isActive(e)?1:0:Three.MathUtils.clamp(t.getValue(e),0,1);
|
|
43
|
+
|
|
44
|
+
const textureTypes={uint8:"uint8",half:"half",float:"float"};const textureTypeMap={[textureTypes.uint8]:Three__namespace.UnsignedByteType,[textureTypes.half]:Three__namespace.HalfFloatType,[textureTypes.float]:Three__namespace.FloatType};const getTextureScale=e=>e===textureTypes.half||e===textureTypes.float?1:255;const createTextureData=(e,t)=>e===textureTypes.half?new Uint16Array(t):e===textureTypes.float?new Float32Array(t):new Uint8Array(t);const readTexel=(e,t)=>e instanceof Uint16Array?Three__namespace.DataUtils.fromHalfFloat(e[t]):e[t];const writeTexel=(e,t,r)=>{e[t]=e instanceof Uint16Array?Three__namespace.DataUtils.toHalfFloat(r):r;};const createScalarTexture3D=(e,t,r)=>{const a=new Three__namespace.Data3DTexture(e,t,t,t);return a.format=Three__namespace.RedFormat,a.type=textureTypeMap[r]||Three__namespace.UnsignedByteType,a.minFilter=Three__namespace.LinearFilter,a.magFilter=Three__namespace.LinearFilter,a.unpackAlignment=1,a.needsUpdate=!0,a};
|
|
45
|
+
|
|
46
|
+
const textureBuilds={sample:"sample",leaves:"leaves"};const textureFilters={nearest:"nearest",trilinear:"trilinear",max:"max"};function leafTextureKernel(){const e=new Float32Array(1),t=new Uint32Array(e.buffer),n=n=>{e[0]=n;const r=t[0],o=r>>>16&32768;let l=(r>>>23&255)-127+15,s=8388607&r;return l>=31?31744|o:l<=0?l<-10?o:(s=(8388608|s)>>1-l,o|s+4096>>13):(s+=4096,8388608&s&&(s=0,l++),l>=31?31744|o:o|l<<10|s>>13)};return {create:({resolution:e,start:t,step:r,filter:o,background:l,leafLog2Dim:s})=>{const a=e*e,i=a*e,u="trilinear"===o,c="max"===o,f=1<<s,d=1<<3*s,h=Math.ceil(d/32);let p=null,g=null,x=0;const m=e=>p?p[x*h+(e>>5)]>>>(31&e)&1:Math.min(Math.max(g[x*d+e],0),1),y=new Float32Array(i),k=u?new Float32Array(i):null;u||0===l||y.fill(l);const w=[0,1,2].map((()=>({texels:new Int32Array(2*e+8),voxels:new Int32Array(2*e+8),weights:new Float32Array(2*e+8),count:0,scatter:new Int32Array(f)}))),M=(n,o,l)=>{const s=w[n],a=t[n],i=r[n],c=o+l;let f=(o-1-a)/i,d=(c+1-a)/i;f>d&&([f,d]=[d,f]);const h=Math.max(0,Math.floor(f)),p=Math.min(e-1,Math.ceil(d));let g=0;for(let e=h;e<=p;e++){const t=a+e*i;if(u){const n=Math.floor(t),r=t-n;n>=o&&n<c&&(s.texels[g]=e,s.voxels[g]=n-o,s.weights[g]=1-r,g++),n+1>=o&&n+1<c&&(s.texels[g]=e,s.voxels[g]=n+1-o,s.weights[g]=r,g++);}else {const n=Math.round(t);n>=o&&n<c&&(s.texels[g]=e,s.voxels[g]=n-o,s.weights[g]=1,g++);}}s.count=g;},v=(n,o)=>{const l=Math.round((o-t[n])/r[n]);return l>=0&&l<e?l:-1},b=(e,t,n)=>{u?(y[e]+=t*n,k[e]+=n):y[e]=c?Math.max(y[e],t):t;},C=(n,o,s,i,u)=>{if(u===l)return;M(0,n,i),M(1,o,i),M(2,s,i);const[f,d,h]=w;for(let t=0;t<h.count;t++)for(let n=0;n<d.count;n++){const r=d.texels[n]*e+h.texels[t]*a,o=d.weights[n]*h.weights[t];for(let e=0;e<f.count;e++)b(f.texels[e]+r,u,f.weights[e]*o);}if(!c)return;const p=[n,o,s].map(((n,o)=>{const l=Math.round((n-t[o])/r[o]),s=Math.round((n+i-1-t[o])/r[o]);return [Math.max(0,Math.min(l,s)),Math.min(e-1,Math.max(l,s))]}));for(let t=p[2][0];t<=p[2][1];t++)for(let n=p[1][0];n<=p[1][1];n++)for(let r=p[0][0];r<=p[0][1];r++){const o=r+n*e+t*a;y[o]=Math.max(y[o],u);}},L=(t,n,r)=>{M(0,t,f),M(1,n,f),M(2,r,f);const[o,i,u]=w,h=2*s;for(let t=0;t<u.count;t++)for(let n=0;n<i.count;n++){const r=i.texels[n]*e+u.texels[t]*a,c=i.voxels[n]<<s|u.voxels[t],f=i.weights[n]*u.weights[t];for(let e=0;e<o.count;e++){const t=m(o.voxels[e]<<h|c);t!==l&&b(o.texels[e]+r,t,o.weights[e]*f);}}if(c){[t,n,r].forEach(((e,t)=>{for(let n=0;n<f;n++)w[t].scatter[n]=v(t,e+n);}));for(let t=0;t<d;t++){const n=m(t),r=o.scatter[t>>h],c=i.scatter[t>>s&f-1],d=u.scatter[t&f-1];if(n!==l&&-1!==r&&-1!==c&&-1!==d){const t=r+c*e+d*a;y[t]=Math.max(y[t],n);}}}};return {addChunk:e=>{const{leafCount:t,origins:n,tileCount:r,tiles:o}=e;for(p=e.leafWords,g=e.leafValues,x=0;x<t;x++)L(n[3*x],n[3*x+1],n[3*x+2]);for(let e=0;e<r;e++){const t=5*e;C(o[t],o[t+1],o[t+2],o[t+3],o[t+4]);}p=null,g=null;},finish:({textureType:t,radius:r})=>{let o=y;if(u)for(let e=0;e<i;e++)o[e]+=Math.max(0,1-k[e])*l;if(r&&(o=((t,n)=>{const r=new Float32Array(i);for(let o=0;o<e;o++)for(let l=0;l<e;l++)for(let s=0;s<e;s++){const i=s+l*e+o*a,u=t[i];if(0!==u)for(let t=-n;t<n;t++)for(let c=-n;c<n;c++)for(let f=-n;f<n;f++){if(s+t<0||s+t>=e||l+c<0||l+c>=e||o+f<0||o+f>=e)continue;const d=i+t+c*e+f*a,h=Math.max(0,Math.min(1,1-Math.hypot(t,c,f)/(n/2)));r[d]=Math.min(r[d]+h*u,1);}}return r})(o,r)),"float"===t)return o;if("half"===t){const e=new Uint16Array(i);for(let t=0;t<i;t++)e[t]=n(o[t]);return e}const s=new Uint8Array(i);for(let e=0;e<i;e++)s[e]=Math.min(255*o[e],255);return s}}}}}const workerSource$1=`\nconst kernel = (${leafTextureKernel.toString()})();\nlet build = null;\n\nself.onmessage = ({ data }) => {\n if (data.type === 'create') {\n build = kernel.create(data.config);\n } else if (data.type === 'chunk') {\n build.addChunk(data.chunk);\n self.postMessage({ type: 'chunk' });\n } else if (data.type === 'finish') {\n const result = build.finish(data.options);\n build = null;\n self.postMessage({ type: 'result', result }, [result.buffer]);\n }\n};\n`;const canBuildFromLeaves=e=>e instanceof openvdb.GridDescriptor&&!!e.root&&"number"==typeof e.root.background;const getDensity=(e,t,n)=>n?t?1:0:Math.min(Math.max(e,0),1);function*packChunks(e,t,n){const r=e.isLevelSet(),o=1<<3*t,l=Math.ceil(o/32),s=getDensity(e.root.background,!1,r);let a;const i=()=>{a={leafCount:0,origins:new Int32Array(3072),leafValues:r?null:new Float32Array(1024*o),leafWords:r?new Uint32Array(1024*l):null,tileCount:0,tiles:new Float64Array(5120)};},u=()=>1024===a.leafCount||1024===a.tileCount,c=(e,t,s,i)=>{const u=a.leafCount++;a.origins[3*u]=t,a.origins[3*u+1]=s,a.origins[3*u+2]=i,r?a.leafWords.set(e.valueMask.words,u*l):e.values.length===o?a.leafValues.set(e.values,u*o):a.leafValues.fill(n,u*o,(u+1)*o);},f=(e,t,n,o,l,i)=>{const u=getDensity(l,i,r);if(u===s)return;const c=5*a.tileCount++;a.tiles[c]=e,a.tiles[c+1]=t,a.tiles[c+2]=n,a.tiles[c+3]=o,a.tiles[c+4]=u;};function*d(t,n,o,l){const s=t.log2dim,h=t.total-t.log2dim,p=1<<h,g=(1<<s)-1,x=t.childMask.words,m=t.valueMask.words;for(let y=0;y<x.length;y++){const k=x[y];let w=k|(r?m[y]&~k:~k);for(;0!==w;){const r=31-Math.clz32(w&-w),x=y<<5|r;if(w&=w-1,k>>>r&1){const e=t.table[x],r=n+e.origin.x,s=o+e.origin.y,a=l+e.origin.z;e.isLeaf()?c(e,r,s,a):yield*d(e,r,s,a);}else f(n+((x>>2*s&g)<<h),o+((x>>s&g)<<h),l+((x&g)<<h),p,t.values[x]??e.root.background,1==(m[y]>>>r&1));u()&&(yield a,i());}}}i();for(const t of e.root.table){const{x:e,y:n,z:r}=t.origin;t.isInternalNode()?yield*d(t,e,n,r):(t.isLeaf()?c(t,e,n,r):f(e,n,r,t.dim,t.value,t.active),u()&&(yield a,i()));}(a.leafCount||a.tileCount)&&(yield a);}const getChunkTransfer=e=>[e.origins,e.leafValues,e.leafWords,e.tiles].filter(Boolean).map((e=>e.buffer)),createWorkerBackend$1=()=>{if("undefined"==typeof Worker||"undefined"==typeof Blob||"undefined"==typeof URL)return null;const e=URL.createObjectURL(new Blob([workerSource$1],{type:"text/javascript"})),t=new Worker(e),n=[],r=(e,r=[])=>new Promise(((o,l)=>{n.push({resolve:o,reject:l}),t.postMessage(e,r);}));return t.onmessage=({data:e})=>{n.shift()?.resolve(e.result);},t.onerror=e=>{e.preventDefault(),n.splice(0).forEach((({reject:t})=>t(e.message||"Worker error")));},{create:e=>t.postMessage({type:"create",config:e}),addChunk:e=>r({type:"chunk",chunk:e},getChunkTransfer(e)),finish:e=>r({type:"finish",options:e}),dispose:()=>{t.terminate(),URL.revokeObjectURL(e),n.splice(0).forEach((({resolve:e})=>e(null)));}}},createLocalBackend$1=()=>{const e=leafTextureKernel();let t=null,n=performance.now();const r=e=>performance.now()-n<12?Promise.resolve().then(e):new Promise((t=>setTimeout((()=>{n=performance.now(),t(e());}),0)));return {create:n=>{t=e.create(n);},addChunk:e=>r((()=>t?.addChunk(e))),finish:e=>r((()=>t?.finish(e)??null)),dispose:()=>{t=null;}}};const buildLeafTexture=({grid:e,resolution:t,start:n,step:r,filter:o=textureFilters.nearest,textureType:l,radius:s,onProgress:a})=>{const{leafLog2Dim:i}=e.treeLayout,u=e.isLevelSet(),c=e.leavesCount*(1<<3*i);let f=!1,d=null;const h=async h=>{d=h(),d.create({resolution:t,start:[n.x,n.y,n.z],step:[r.x,r.y,r.z],filter:o,background:getDensity(e.root.background,!1,u),leafLog2Dim:i});const p=[];let g=0;for(const t of packChunks(e,i,e.root.background)){if(f)return null;g+=t.leafCount<<3*i,p.push(d.addChunk(t)),p.length>=2&&await p.shift(),a&&a({convertedVoxels:g,totalVoxels:c});}return await Promise.all(p),f?null:d.finish({textureType:l,radius:s})};return {promise:h((()=>createWorkerBackend$1()||createLocalBackend$1())).catch((e=>f?null:(console.warn("buildLeafTexture","Worker failed, building on the main thread.",{error:e}),d?.dispose(),h(createLocalBackend$1)))).finally((()=>{d?.dispose();})),cancel:()=>{f=!0,d?.dispose();}}};
|
|
47
|
+
|
|
48
|
+
const getTextureMapping=(e,t)=>{const r=new Three.Vector3,s=new Three.Vector3,a=new Three.Vector3,o=new Three.Vector3,i=new Bbox;return i.set(...e.getPreciseWorldBbox()),i.getCenter(r),i.getSize(s),a.copy(s).multiplyScalar(1/t),e.transform.applyInverseJacobian(a),o.copy(r).sub(s.clone().multiplyScalar(.5)),e.transform.applyInverseTransformMap(o),o.add(a.clone().multiplyScalar(.5)),{center:r,size:s,start:o,step:a}};class VolumeToFog extends Three__namespace.Group{processes=[];leafBuilds=[];materials=[];isFogVolume=!0;constructor(e,t,r,s){super();const{resolution:a,progressive:o,emissiveGrid:i,baseColorGrid:n,maskGrid:l,radius:c,textureBuild:u=textureBuilds.sample,textureFilter:d=textureFilters.nearest,textureType:p=textureTypes.uint8}=t,m=getTextureScale(p);let h,x,f,T,y,w,g;if(this.frustumCulled=!1,h=e instanceof Array?e:void 0!==e.grids?Object.values(e.grids):[e],i)if(h=h.filter((e=>e!==i)),i instanceof Uint8Array)x=createScalarTexture3D(i,a,textureTypes.uint8);else {const e=createTextureData(p,Math.pow(a,3));x=createScalarTexture3D(e,a,p),f=(t,r,s)=>{const a=void 0!==s?s:getDensity$1(i,r)*m;return writeTexel(e,t,a),a};}if(n){if(n instanceof Uint8Array)T=new Three__namespace.Data3DTexture(n,a,a,a);else {const e=new Uint8Array(4*Math.pow(a,3));T=new Three__namespace.Data3DTexture(e,a,a,a);const t=new Three__namespace.Color;y=(r,s,a)=>{if(a)return e[4*r+0]=255*a.r,e[4*r+1]=255*a.g,e[4*r+2]=255*a.b,void(e[4*r+3]=255);const o=n.getValue(s);return o instanceof Three__namespace.Color?t.set(gridValue):o instanceof openvdb.Vector3||o instanceof Three__namespace.Vector3?t.setRGB(o.x,o.y,o.z):t.setRGB(1,0,1),e[4*r+0]=255*t.r,e[4*r+1]=255*t.g,e[4*r+2]=255*t.b,e[4*r+3]=255,{r:t.r,g:t.g,b:t.b,a:t.a}};}T.format=Three__namespace.RGBAFormat,T.minFilter=Three__namespace.LinearFilter,T.magFilter=Three__namespace.LinearFilter,T.unpackAlignment=1,T.needsUpdate=!0;}if(l)if(l instanceof Uint8Array)w=createScalarTexture3D(l,a,textureTypes.uint8);else {const e=createTextureData(p,Math.pow(a,3));w=createScalarTexture3D(e,a,p),g=(t,r,s)=>{const a=void 0!==s?s:getDensity$1(l,r)*m;return writeTexel(e,t,a),a};}let D=0,v=0;const M=h.length,V=M*Math.pow(a,3),B=e=>!e||e instanceof Uint8Array||canBuildFromLeaves(e),F=u===textureBuilds.leaves&&h.every((e=>!(e instanceof openvdb.GridDescriptor)||canBuildFromLeaves(e)))&&B(i)&&B(l)&&(!n||n instanceof Uint8Array);u!==textureBuilds.leaves||F||console.warn("VolumeToFog","textureBuild","Primitive or vector grids need the sample build. Using the sample build.");const b=[],U=[];let G=!1;const A=()=>{s&&s({convertedVoxels:U.reduce(((e,t)=>e+t.convertedVoxels),0),totalVoxels:U.reduce(((e,t)=>e+t.totalVoxels),0),convertedGrids:D,totalGrids:M});},S=(e,t,r,s)=>{const o={convertedVoxels:0,totalVoxels:0};U.push(o);const i=buildLeafTexture({grid:e,resolution:a,start:t.start,step:t.step,filter:d,textureType:p,radius:s,onProgress:({convertedVoxels:e,totalVoxels:t})=>{o.convertedVoxels=e,o.totalVoxels=t,A();}});return this.leafBuilds.push(i),i.promise.then((e=>!!e&&(r.image.data=e,r.needsUpdate=!0,!0))).catch((e=>(console.error("VolumeToFog","buildLeafTexture",{error:e}),!1)))};h.reverse().forEach((async(e,n)=>{if(e instanceof Uint8Array){const r=new Three__namespace.Data3DTexture(e,a,a,a);r.format=Three__namespace.RedFormat,r.minFilter=Three__namespace.LinearFilter,r.magFilter=Three__namespace.LinearFilter,r.unpackAlignment=1,r.needsUpdate=!0;const s=new Three__namespace.SphereGeometry(1),o=new VolumeBasicMaterial({...t,emissiveMap3D:x,densityMap3D:r,baseColorMap3D:T,maskMap3D:w}),i=new Three__namespace.Mesh(s,o);return i.frustumCulled=!1,i.onBeforeRender=updateFrameDepth,this.materials.push(o),void this.add(i)}if(!(e instanceof openvdb.GridDescriptor))return;const u=createTextureData(p,Math.pow(a,3)),d=createScalarTexture3D(u,a,p),h=new Three__namespace.SphereGeometry(1),B=new VolumeBasicMaterial({...t,emissiveMap3D:x,densityMap3D:d,baseColorMap3D:T,maskMap3D:w}),U=new Three__namespace.Mesh(h,B);if(U.frustumCulled=!1,U.onBeforeRender=updateFrameDepth,this.materials.push(B),F){const t=getTextureMapping(e,a);return U.position.copy(t.center),U.scale.copy(t.size),o&&this.add(U),b.push(S(e,t,d,c).then((e=>{e&&(D++,o||this.add(U),A());}))),void(G||(G=!0,!i||i instanceof Uint8Array||b.push(S(i,t,x,0)),!l||l instanceof Uint8Array||b.push(S(l,t,w,0))))}const L=a,C=Math.pow(L,2);o&&this.add(U),await(t=>new Promise((a=>{const i=Math.pow(t,2),l=Math.pow(t,3),{center:p,size:h,start:B,step:F}=getTextureMapping(e,t);U.position.copy(p),U.scale.copy(h);let b=0,G=0,A=0;let S=function*(){for(let r=0;r<l;r++){const r=b+G*t+A*i,s=getDensity$1(e,B),a=f&&f(r,B),n=y&&y(r,B),l=g&&g(r,B),p=c;if(p)for(let e=-p;e<p;e++)for(let o=-p;o<p;o++)for(let i=-p;i<p;i++){if(b+e<0||b+e>=t||G+o<0||G+o>=t||A+i<0||A+i>=t)continue;const d=r+e+o*L+i*C,p=Math.max(0,Math.min(1,1-Math.hypot(e,o,i)/(c/2))),h=p*s*m;writeTexel(u,d,readTexel(u,d)+h),writeTexel(u,d,Math.min(readTexel(u,d),m)),f&&f(d,null,a*p*m),y&&y(d,null,n*p),g&&g(d,null,l*p*m);}else {const e=s*m;writeTexel(u,r,readTexel(u,r)+e),writeTexel(u,r,Math.min(readTexel(u,r),m));}if(v++,o&&(d.needsUpdate=!0,x&&(x.needsUpdate=!0),T&&(T.needsUpdate=!0),w&&(w.needsUpdate=!0)),A>=t){x.needsUpdate=!0,T.needsUpdate=!0,w.needsUpdate=!0;break}b++,B.x+=F.x,b>=t&&(b=0,B.x-=F.x*t,G++,B.y+=F.y),G>=t&&(G=0,B.y-=F.y*t,A++,B.z+=F.z),yield;}}();const z=()=>{let e=window.performance.now();for(;window.performance.now()-e<16&&S;){const{done:e}=S.next();s&&s({convertedVoxels:v,totalVoxels:V,convertedGrids:D,totalGrids:M}),e&&(D++,S=null,delete this.processes[n],a(),D===M&&(o||this.add(U),r&&r()));}S&&this.processes&&(this.processes[n]=setTimeout(z,0));};z();})))(a);})),F&&Promise.all(b).then((()=>{this.processes&&r&&r();}));}dispose(){this.leafBuilds.forEach((e=>e.cancel())),this.leafBuilds=[],this.processes&&(this.processes.forEach(((e,t)=>{cancelAnimationFrame(e),delete this.processes[t];})),this.processes=null);}}
|
|
49
|
+
|
|
50
|
+
const triangleLUT=[[],[0,8,3],[0,1,9],[1,8,3,9,8,1],[1,2,10],[0,8,3,1,2,10],[9,2,10,0,2,9],[2,8,3,2,10,8,10,9,8],[3,11,2],[0,11,2,8,11,0],[1,9,0,2,3,11],[1,11,2,1,9,11,9,8,11],[3,10,1,11,10,3],[0,10,1,0,8,10,8,11,10],[3,9,0,3,11,9,11,10,9],[9,8,10,10,8,11],[4,7,8],[4,3,0,7,3,4],[0,1,9,8,4,7],[4,1,9,4,7,1,7,3,1],[1,2,10,8,4,7],[3,4,7,3,0,4,1,2,10],[9,2,10,9,0,2,8,4,7],[2,10,9,2,9,7,2,7,3,7,9,4],[8,4,7,3,11,2],[11,4,7,11,2,4,2,0,4],[9,0,1,8,4,7,2,3,11],[4,7,11,9,4,11,9,11,2,9,2,1],[3,10,1,3,11,10,7,8,4],[1,11,10,1,4,11,1,0,4,7,11,4],[4,7,8,9,0,11,9,11,10,11,0,3],[4,7,11,4,11,9,9,11,10],[9,5,4],[9,5,4,0,8,3],[0,5,4,1,5,0],[8,5,4,8,3,5,3,1,5],[1,2,10,9,5,4],[3,0,8,1,2,10,4,9,5],[5,2,10,5,4,2,4,0,2],[2,10,5,3,2,5,3,5,4,3,4,8],[9,5,4,2,3,11],[0,11,2,0,8,11,4,9,5],[0,5,4,0,1,5,2,3,11],[2,1,5,2,5,8,2,8,11,4,8,5],[10,3,11,10,1,3,9,5,4],[4,9,5,0,8,1,8,10,1,8,11,10],[5,4,0,5,0,11,5,11,10,11,0,3],[5,4,8,5,8,10,10,8,11],[9,7,8,5,7,9],[9,3,0,9,5,3,5,7,3],[0,7,8,0,1,7,1,5,7],[1,5,3,3,5,7],[9,7,8,9,5,7,10,1,2],[10,1,2,9,5,0,5,3,0,5,7,3],[8,0,2,8,2,5,8,5,7,10,5,2],[2,10,5,2,5,3,3,5,7],[7,9,5,7,8,9,3,11,2],[9,5,7,9,7,2,9,2,0,2,7,11],[2,3,11,0,1,8,1,7,8,1,5,7],[11,2,1,11,1,7,7,1,5],[9,5,8,8,5,7,10,1,3,10,3,11],[5,7,0,5,0,9,7,11,0,1,0,10,11,10,0],[11,10,0,11,0,3,10,5,0,8,0,7,5,7,0],[11,10,5,7,11,5],[10,6,5],[0,8,3,5,10,6],[9,0,1,5,10,6],[1,8,3,1,9,8,5,10,6],[1,6,5,2,6,1],[1,6,5,1,2,6,3,0,8],[9,6,5,9,0,6,0,2,6],[5,9,8,5,8,2,5,2,6,3,2,8],[2,3,11,10,6,5],[11,0,8,11,2,0,10,6,5],[0,1,9,2,3,11,5,10,6],[5,10,6,1,9,2,9,11,2,9,8,11],[6,3,11,6,5,3,5,1,3],[0,8,11,0,11,5,0,5,1,5,11,6],[3,11,6,0,3,6,0,6,5,0,5,9],[6,5,9,6,9,11,11,9,8],[5,10,6,4,7,8],[4,3,0,4,7,3,6,5,10],[1,9,0,5,10,6,8,4,7],[10,6,5,1,9,7,1,7,3,7,9,4],[6,1,2,6,5,1,4,7,8],[1,2,5,5,2,6,3,0,4,3,4,7],[8,4,7,9,0,5,0,6,5,0,2,6],[7,3,9,7,9,4,3,2,9,5,9,6,2,6,9],[3,11,2,7,8,4,10,6,5],[5,10,6,4,7,2,4,2,0,2,7,11],[0,1,9,4,7,8,2,3,11,5,10,6],[9,2,1,9,11,2,9,4,11,7,11,4,5,10,6],[8,4,7,3,11,5,3,5,1,5,11,6],[5,1,11,5,11,6,1,0,11,7,11,4,0,4,11],[0,5,9,0,6,5,0,3,6,11,6,3,8,4,7],[6,5,9,6,9,11,4,7,9,7,11,9],[10,4,9,6,4,10],[4,10,6,4,9,10,0,8,3],[10,0,1,10,6,0,6,4,0],[8,3,1,8,1,6,8,6,4,6,1,10],[1,4,9,1,2,4,2,6,4],[3,0,8,1,2,9,2,4,9,2,6,4],[0,2,4,4,2,6],[8,3,2,8,2,4,4,2,6],[10,4,9,10,6,4,11,2,3],[0,8,2,2,8,11,4,9,10,4,10,6],[3,11,2,0,1,6,0,6,4,6,1,10],[6,4,1,6,1,10,4,8,1,2,1,11,8,11,1],[9,6,4,9,3,6,9,1,3,11,6,3],[8,11,1,8,1,0,11,6,1,9,1,4,6,4,1],[3,11,6,3,6,0,0,6,4],[6,4,8,11,6,8],[7,10,6,7,8,10,8,9,10],[0,7,3,0,10,7,0,9,10,6,7,10],[10,6,7,1,10,7,1,7,8,1,8,0],[10,6,7,10,7,1,1,7,3],[1,2,6,1,6,8,1,8,9,8,6,7],[2,6,9,2,9,1,6,7,9,0,9,3,7,3,9],[7,8,0,7,0,6,6,0,2],[7,3,2,6,7,2],[2,3,11,10,6,8,10,8,9,8,6,7],[2,0,7,2,7,11,0,9,7,6,7,10,9,10,7],[1,8,0,1,7,8,1,10,7,6,7,10,2,3,11],[11,2,1,11,1,7,10,6,1,6,7,1],[8,9,6,8,6,7,9,1,6,11,6,3,1,3,6],[0,9,1,11,6,7],[7,8,0,7,0,6,3,11,0,11,6,0],[7,11,6],[7,6,11],[3,0,8,11,7,6],[0,1,9,11,7,6],[8,1,9,8,3,1,11,7,6],[10,1,2,6,11,7],[1,2,10,3,0,8,6,11,7],[2,9,0,2,10,9,6,11,7],[6,11,7,2,10,3,10,8,3,10,9,8],[7,2,3,6,2,7],[7,0,8,7,6,0,6,2,0],[2,7,6,2,3,7,0,1,9],[1,6,2,1,8,6,1,9,8,8,7,6],[10,7,6,10,1,7,1,3,7],[10,7,6,1,7,10,1,8,7,1,0,8],[0,3,7,0,7,10,0,10,9,6,10,7],[7,6,10,7,10,8,8,10,9],[6,8,4,11,8,6],[3,6,11,3,0,6,0,4,6],[8,6,11,8,4,6,9,0,1],[9,4,6,9,6,3,9,3,1,11,3,6],[6,8,4,6,11,8,2,10,1],[1,2,10,3,0,11,0,6,11,0,4,6],[4,11,8,4,6,11,0,2,9,2,10,9],[10,9,3,10,3,2,9,4,3,11,3,6,4,6,3],[8,2,3,8,4,2,4,6,2],[0,4,2,4,6,2],[1,9,0,2,3,4,2,4,6,4,3,8],[1,9,4,1,4,2,2,4,6],[8,1,3,8,6,1,8,4,6,6,10,1],[10,1,0,10,0,6,6,0,4],[4,6,3,4,3,8,6,10,3,0,3,9,10,9,3],[10,9,4,6,10,4],[4,9,5,7,6,11],[0,8,3,4,9,5,11,7,6],[5,0,1,5,4,0,7,6,11],[11,7,6,8,3,4,3,5,4,3,1,5],[9,5,4,10,1,2,7,6,11],[6,11,7,1,2,10,0,8,3,4,9,5],[7,6,11,5,4,10,4,2,10,4,0,2],[3,4,8,3,5,4,3,2,5,10,5,2,11,7,6],[7,2,3,7,6,2,5,4,9],[9,5,4,0,8,6,0,6,2,6,8,7],[3,6,2,3,7,6,1,5,0,5,4,0],[6,2,8,6,8,7,2,1,8,4,8,5,1,5,8],[9,5,4,10,1,6,1,7,6,1,3,7],[1,6,10,1,7,6,1,0,7,8,7,0,9,5,4],[4,0,10,4,10,5,0,3,10,6,10,7,3,7,10],[7,6,10,7,10,8,5,4,10,4,8,10],[6,9,5,6,11,9,11,8,9],[3,6,11,0,6,3,0,5,6,0,9,5],[0,11,8,0,5,11,0,1,5,5,6,11],[6,11,3,6,3,5,5,3,1],[1,2,10,9,5,11,9,11,8,11,5,6],[0,11,3,0,6,11,0,9,6,5,6,9,1,2,10],[11,8,5,11,5,6,8,0,5,10,5,2,0,2,5],[6,11,3,6,3,5,2,10,3,10,5,3],[5,8,9,5,2,8,5,6,2,3,8,2],[9,5,6,9,6,0,0,6,2],[1,5,8,1,8,0,5,6,8,3,8,2,6,2,8],[1,5,6,2,1,6],[1,3,6,1,6,10,3,8,6,5,6,9,8,9,6],[10,1,0,10,0,6,9,5,0,5,6,0],[0,3,8,5,6,10],[10,5,6],[11,5,10,7,5,11],[11,5,10,11,7,5,8,3,0],[5,11,7,5,10,11,1,9,0],[10,7,5,10,11,7,9,8,1,8,3,1],[11,1,2,11,7,1,7,5,1],[0,8,3,1,2,7,1,7,5,7,2,11],[9,7,5,9,2,7,9,0,2,2,11,7],[7,5,2,7,2,11,5,9,2,3,2,8,9,8,2],[2,5,10,2,3,5,3,7,5],[8,2,0,8,5,2,8,7,5,10,2,5],[9,0,1,5,10,3,5,3,7,3,10,2],[9,8,2,9,2,1,8,7,2,10,2,5,7,5,2],[1,3,5,3,7,5],[0,8,7,0,7,1,1,7,5],[9,0,3,9,3,5,5,3,7],[9,8,7,5,9,7],[5,8,4,5,10,8,10,11,8],[5,0,4,5,11,0,5,10,11,11,3,0],[0,1,9,8,4,10,8,10,11,10,4,5],[10,11,4,10,4,5,11,3,4,9,4,1,3,1,4],[2,5,1,2,8,5,2,11,8,4,5,8],[0,4,11,0,11,3,4,5,11,2,11,1,5,1,11],[0,2,5,0,5,9,2,11,5,4,5,8,11,8,5],[9,4,5,2,11,3],[2,5,10,3,5,2,3,4,5,3,8,4],[5,10,2,5,2,4,4,2,0],[3,10,2,3,5,10,3,8,5,4,5,8,0,1,9],[5,10,2,5,2,4,1,9,2,9,4,2],[8,4,5,8,5,3,3,5,1],[0,4,5,1,0,5],[8,4,5,8,5,3,9,0,5,0,3,5],[9,4,5],[4,11,7,4,9,11,9,10,11],[0,8,3,4,9,7,9,11,7,9,10,11],[1,10,11,1,11,4,1,4,0,7,4,11],[3,1,4,3,4,8,1,10,4,7,4,11,10,11,4],[4,11,7,9,11,4,9,2,11,9,1,2],[9,7,4,9,11,7,9,1,11,2,11,1,0,8,3],[11,7,4,11,4,2,2,4,0],[11,7,4,11,4,2,8,3,4,3,2,4],[2,9,10,2,7,9,2,3,7,7,4,9],[9,10,7,9,7,4,10,2,7,8,7,0,2,0,7],[3,7,10,3,10,2,7,4,10,1,10,0,4,0,10],[1,10,2,8,7,4],[4,9,1,4,1,7,7,1,3],[4,9,1,4,1,7,0,8,1,8,7,1],[4,0,3,7,4,3],[4,8,7],[9,10,8,10,11,8],[3,0,9,3,9,11,11,9,10],[0,1,10,0,10,8,8,10,11],[3,1,10,11,3,10],[1,2,11,1,11,9,9,11,8],[3,0,9,3,9,11,1,2,9,2,11,9],[0,2,11,8,0,11],[3,2,11],[2,3,8,2,8,10,10,8,9],[9,10,2,0,9,2],[2,3,8,2,8,10,0,1,8,1,10,8],[1,10,2],[1,3,8,9,1,8],[0,9,1],[0,3,8],[]];class VolumeToLevelSet extends Three__namespace.Group{processes=[];constructor(e,t,{resolution:r,progressive:o},s,n){let a;super(),a=e instanceof Array?e:void 0!==e.grids?Object.values(e.grids):[e];let i=0;const c=a.length,l=c*Math.pow(r,3);a.forEach(((e,a)=>{const y=new Three__namespace.BufferGeometry,p=[],h=[],m=[],g=new Three__namespace.Mesh(y,t),u=1/r,d=(Math.pow(r,3)),f=new Three.Vector3(0,0,0),w=new Three.Vector3(0,0,0),x=new Three.Vector3(0,0,0),b=new Bbox,T=new Three.Vector3;b.set(...e.getPreciseWorldBbox()),b.getCenter(g.position),b.getSize(g.scale),g.scale.multiplyScalar(1/r),b.getCenter(f),b.getSize(w),x.copy(w).multiplyScalar(u),e.transform.applyInverseJacobian(x),f.sub(w.clone().multiplyScalar(.5)),e.transform.applyInverseTransformMap(f),f.add(x.clone().multiplyScalar(.5));let D=0,z=0,A=0;let M=function*(){for(let t=0;t<d;t++){if(A>=r)return;D++,f.x+=x.x,D>=r&&(D=0,f.x-=x.x*r,z++,f.y+=x.y),z>=r&&(z=0,f.y-=x.y*r,A++,f.z+=x.z);const t=[];T.copy(f),t[0]=getDensity$1(e,T),T.copy(f),T.x+=x.x,t[1]=getDensity$1(e,T),T.copy(f),T.x+=x.x,T.z+=x.z,t[2]=getDensity$1(e,T),T.copy(f),T.z+=x.z,t[3]=getDensity$1(e,T),T.copy(f),T.y+=x.y,t[4]=getDensity$1(e,T),T.copy(f),T.x+=x.x,T.y+=x.y,t[5]=getDensity$1(e,T),T.copy(f),T.x+=x.x,T.y+=x.y,T.z+=x.z,t[6]=getDensity$1(e,T),T.copy(f),T.y+=x.y,T.z+=x.z,t[7]=getDensity$1(e,T);let o=0;t[0]&&(o|=1),t[1]&&(o|=2),t[2]&&(o|=4),t[3]&&(o|=8),t[4]&&(o|=16),t[5]&&(o|=32),t[6]&&(o|=64),t[7]&&(o|=128);const s=triangleLUT[o]||s[1],n=[[.5,0,0],[1,0,.5],[.5,0,1],[0,0,.5],[.5,1,0],[1,1,.5],[.5,1,1],[0,1,.5],[0,.5,0],[1,.5,0],[1,.5,1],[0,.5,1]];if(t.filter((e=>0!==e)).length){const e={r:Math.random(),g:Math.random(),b:Math.random()};s.forEach((t=>{const o=n[t];p.push(D-r/2+o[0],z-r/2+o[1],A-r/2+o[2]),m.push(e.r,e.g,e.b);const s=new Three__namespace.Vector3(Math.random(),Math.random(),Math.random()).normalize();h.push(s.x,s.y,s.z);}));}yield;}}();const U=()=>{M&&(this.processes[a]=requestAnimationFrame(U));let e=window.performance.now();for(;window.performance.now()-e<16&&M;){const{done:e}=M.next();if(n&&n({convertedVoxels:0,totalVoxels:l,convertedGrids:i,totalGrids:c}),e&&(i++,M=null,delete this.processes[a],i===c)){if(!o){const e=new Three__namespace.BufferAttribute(new Float32Array(p),3);e.setUsage(Three__namespace.DynamicDrawUsage),g.geometry.setAttribute("position",e);const t=new Three__namespace.BufferAttribute(new Float32Array(h),3);t.setUsage(Three__namespace.DynamicDrawUsage),g.geometry.setAttribute("normal",t);const r=new Three__namespace.BufferAttribute(new Float32Array(m),3);r.setUsage(Three__namespace.DynamicDrawUsage),g.geometry.setAttribute("color",r),this.add(g);}s&&s();}}};U(),o&&this.add(g);}));}dispose(){this.processes&&(this.processes.forEach(((e,t)=>{cancelAnimationFrame(e),delete this.processes[t];})),this.processes=null);}}
|
|
51
|
+
|
|
52
|
+
class VolumeToPoints extends Three__namespace.Group{processes=[];constructor(e,t,{resolution:r,progressive:o},s,n){let i;super(),i=e instanceof Array?e:void 0!==e.grids?Object.values(e.grids):[e];let l=0;const a=i.length,c=a*Math.pow(r,3);i.forEach(((e,i)=>{const p=new Three__namespace.BufferGeometry,h=[],m=new Three__namespace.Points(p,new Three__namespace.PointsMaterial({color:16711935,size:1,...t||{}})),d=1/r,u=(Math.pow(r,3)),f=new Three.Vector3(0,0,0),w=new Three.Vector3(0,0,0),y=new Three.Vector3(0,0,0),x=new Bbox;x.set(...e.getPreciseWorldBbox()),x.getCenter(m.position),x.getSize(m.scale),m.scale.multiplyScalar(1/r),x.getCenter(f),x.getSize(w),y.copy(w).multiplyScalar(d),e.transform.applyInverseJacobian(y),f.sub(w.clone().multiplyScalar(.5)),e.transform.applyInverseTransformMap(f),f.add(y.clone().multiplyScalar(.5));let g=0,b=0,T=0;let v=function*(){for(let t=0;t<u;t++){if(T>=r)return;g++,f.x+=y.x,g>=r&&(g=0,f.x-=y.x*r,b++,f.y+=y.y),b>=r&&(b=0,f.y-=y.y*r,T++,f.z+=y.z);getDensity$1(e,f)&&(h.push(g-r/2,b-r/2,T-r/2),p.setAttribute("position",new Three__namespace.BufferAttribute(new Float32Array(h),3))),yield;}}();const V=()=>{v&&(this.processes[i]=requestAnimationFrame(V));let e=window.performance.now();for(;window.performance.now()-e<16&&v;){const{done:e}=v.next();n&&n({convertedVoxels:0,totalVoxels:c,convertedGrids:l,totalGrids:a}),e&&(l++,v=null,delete this.processes[i],l===a&&(o||this.add(m),s&&s()));}};V(),o&&this.add(m);}));}dispose(){this.processes&&(this.processes.forEach(((e,t)=>{cancelAnimationFrame(e),delete this.processes[t];})),this.processes=null);}}
|
|
53
|
+
|
|
54
|
+
class MeshVolume extends openvdb.GridDescriptor{static halfFloatGridPrefix=openvdb.GridDescriptor.halfFloatGridPrefix;saveAsHalfFloat=!1;leavesCount=0;uniqueName;gridName;gridType;mesh=new Three__namespace.Object3D;constructor(e){super(),this.readGrid();const t=e.clone(),r=[],i=new Three__namespace.Vector3,s=new Three__namespace.Quaternion;t.position.setScalar(0,0,0),t.scale.setScalar(1),t.quaternion.identity(),t.updateMatrix(),t.updateMatrixWorld(),t.updateWorldMatrix(),t.traverse((e=>{if(e.geometry){const t=e.geometry.clone();e.getWorldQuaternion(s),t.applyQuaternion(s),e.getWorldScale(i),t.scale(i.x,i.y,i.z),e.getWorldPosition(i),t.translate(i.x,i.y,i.z),t.attributes={position:t.attributes.position,normal:t.attributes.normal},r.push(t);}}));const a=BufferGeometryUtils__namespace.mergeVertices(BufferGeometryUtils__namespace.mergeGeometries(r,!1));this.mesh=new Three__namespace.Mesh(a,new Three__namespace.MeshBasicMaterial({side:Three__namespace.DoubleSide})),this.cacheMeshGeometry();}readGrid(){this.readGridHeader(),this.readGridTransform();}readGridHeader(){this.uniqueName=`MeshVolume#${openvdb.getUuid()}`,this.gridName=this.uniqueName.split("#")[0],this.gridType="fog volume";}readCompression(){}readMetadata(){}getGridValueType(){return openvdb.floatType}getGridClass(){return "fog volume"}readGridTransform(){this.transform=new openvdb.GridTransform;}readTopology(){}getWorldBbox(){return this.getLocalBbox()}getLocalBbox(){return [new openvdb.Vector3(-.5,-.5,-.5),new openvdb.Vector3(.5,.5,.5)]}getPreciseWorldBbox(){return this.getWorldBbox()}bbox=null;center=null;size=null;target=new Three__namespace.Vector3(0,0,0);raycaster=new Three__namespace.Raycaster;rayDirection=new Three__namespace.Vector3(1,0,0);cacheMeshGeometry(){this.bbox=new Three__namespace.Box3,this.bbox.expandByObject(this.mesh),this.center=new Three__namespace.Vector3,this.size=new Three__namespace.Vector3,this.bbox.getCenter(this.center),this.bbox.getSize(this.size);const e=Math.max(this.size.x,this.size.y,this.size.z),t=(new Three__namespace.Vector3).copy(this.size).divideScalar(e);this.size.divide(t),this.size.addScalar(.1),this.raycaster.near=0,this.raycaster.far=1e3;}getValue(e){this.target.copy(e).multiply(this.size).add(this.center),this.rayDirection.copy(e).normalize(),this.raycaster.set(this.target,this.rayDirection);const t=this.raycaster.intersectObjects([this.mesh],!0);return 0===t.length||t.length%2==0?0:1}getLeafAt(e){return null}}
|
|
55
|
+
|
|
56
|
+
class VolumeNormalMaterial extends Three__namespace.MeshStandardMaterial{name="VolumeNormalMaterial";isVolumetricFogMaterial=!0;customProgramCacheKey=()=>openvdb.getUuid(this);_uniforms={baseColor:{value:new Three__namespace.Color(16777215)},scatterColor:{value:new Three__namespace.Color(0)},densityMap3D:{value:null},emissiveMap3D:{value:null},baseColorMap3D:{value:null},maskMap3D:{value:null},steps:{value:100},absorbance:{value:1},densityScale:{value:1},resolution:{value:100},offset3D:{value:new Three__namespace.Vector3(0,0,0)},wrap3D:{value:Three__namespace.ClampToEdgeWrapping},lights:{value:lights.useDirectionalLights|lights.usePointLights|lights.useSpotLights|lights.useHemisphereLights|lights.useEnvironment},cameraPosition:{value:new Three__namespace.Vector3(0,0,0)},cameraNear:{value:1},cameraFar:{value:1}};set baseColor(e){this._uniforms.baseColor.value.set(e);}get baseColor(){return this._uniforms.baseColor.value}set scatterColor(e){this._uniforms.scatterColor.value.set(e);}get scatterColor(){return this._uniforms.scatterColor.value}set densityMap3D(e){this._uniforms.densityMap3D.value=e,e.offset3D=this._uniforms.offset3D.value;}get densityMap3D(){return this._uniforms.densityMap3D.value}set emissiveMap3D(e){this._uniforms.emissiveMap3D.value=e,e.offset3D=this._uniforms.offset3D.value,this.needsUpdate=!0;}get emissiveMap3D(){return this._uniforms.emissiveMap3D.value}set maskMap3D(e){this._uniforms.maskMap3D.value=e,this.needsUpdate=!0;}get maskMap3D(){return this._uniforms.maskMap3D.value}set baseColorMap3D(e){this._uniforms.baseColorMap3D.value=e,e.offset3D=this._uniforms.offset3D.value,this.needsUpdate=!0;}get baseColorMap3D(){return this._uniforms.baseColorMap3D.value}set steps(e){this._uniforms.steps.value=e;}get steps(){return this._uniforms.steps.value}set absorbance(e){this._uniforms.absorbance.value=e;}get absorbance(){return this._uniforms.absorbance.value}set resolution(e){this._uniforms.resolution.value=e;}get resolution(){return this._uniforms.resolution.value}set offset3D(e){this._uniforms.offset3D.value.copy(e);}get offset3D(){return this._uniforms.offset3D.value}set wrap3D(e){this._uniforms.wrap3D.value=e,this.needsUpdate=!0;}get wrap3D(){return this._uniforms.wrap3D.value}set densityScale(e){this._uniforms.densityScale.value=e;}get densityScale(){return this._uniforms.densityScale.value}set lights(e){this._uniforms.lights.value=e,this.needsUpdate=!0;}get lights(){return this._uniforms.lights.value}set useDirectionalLights(e=!0){e?this._uniforms.lights.value|=lights.useDirectionalLights:this._uniforms.lights.value&=~lights.useDirectionalLights,this.needsUpdate=!0;}get useDirectionalLights(){return 0!=(this._uniforms.lights.value&lights.useDirectionalLights)}set usePointLights(e=!0){e?this._uniforms.lights.value|=lights.usePointLights:this._uniforms.lights.value&=~lights.usePointLights,this.needsUpdate=!0;}get usePointLights(){return 0!=(this._uniforms.lights.value&lights.usePointLights)}set useHemisphereLights(e=!0){e?this._uniforms.lights.value|=lights.useHemisphereLights:this._uniforms.lights.value&=~lights.useHemisphereLights,this.needsUpdate=!0;}get useHemisphereLights(){return 0!=(this._uniforms.lights.value&lights.useHemisphereLights)}set useSpotLights(e=!0){e?this._uniforms.lights.value|=lights.useSpotLights:this._uniforms.lights.value&=~lights.useSpotLights,this.needsUpdate=!0;}get useSpotLights(){return 0!=(this._uniforms.lights.value&lights.useSpotLights)}set useEnvironment(e=!0){e?this._uniforms.lights.value|=lights.useEnvironment:this._uniforms.lights.value&=~lights.useEnvironment,this.needsUpdate=!0;}get useEnvironment(){return 0!=(this._uniforms.lights.value&lights.useEnvironment)}constructor(e={}){super(),this.side=Three__namespace.BackSide,this.depthWrite=!1,this.depthTest=!1,this.transparent=!0,Object.keys(this._uniforms).forEach((n=>{void 0!==e[n]&&null!==e[n]&&(this[n]=e[n],e[n]instanceof Three__namespace.Texture&&(e[n].offset3D=this._uniforms.offset3D.value));})),this.onBeforeCompile=(n,i)=>{const t=getDepthUniforms();Object.entries({...this._uniforms,...t}).forEach((([e,i])=>{n.uniforms[e]=i;}));const o=`\n #define VOLUME_BBOX_SPAN 0.5\n ${e.emissiveMap3D?"#define USE_EMISSIVE_GRID 1":""}\n ${e.baseColorMap3D?"#define USE_BASE_COLOR_GRID 1":""}\n ${e.maskMap3D?"#define USE_MASK_GRID 1":""}\n\n #define VOLUME_USE_ENVIRONMENT ${Number(this.useEnvironment)} != 0\n #define VOLUME_USE_HEMI_LIGHTS ${Number(this.useHemisphereLights)} != 0\n #define VOLUME_USE_POINT_LIGHTS ${Number(this.usePointLights)} != 0\n #define VOLUME_USE_DIR_LIGHTS ${Number(this.useDirectionalLights)} != 0\n #define VOLUME_USE_SPOT_LIGHTS ${Number(this.useSpotLights)} != 0\n `,a="\n varying mat4 mModelMatrix;\n varying mat4 mViewMatrix;\n varying mat4 mProjectionMatrix;\n varying mat4 mModelViewMatrix;\n varying mat4 mInverseModelViewMatrix;\n varying mat3 mInverseNormalMatrix;\n ";n.vertexShader=n.vertexShader.replace("#include <common>",`\n ${a}\n\n out vec3 vOrigin;\n out vec3 vDirection;\n out vec3 vPosition;\n\n #include <common>\n `).replace("#include <worldpos_vertex>","\n #include <worldpos_vertex>\n\n vOrigin = vec3(inverse(modelMatrix) * vec4(cameraPosition, 1.0)).xyz;\n vDirection = position - vOrigin;\n vPosition = position;\n\n mModelMatrix = modelMatrix;\n mViewMatrix = viewMatrix;\n mProjectionMatrix = projectionMatrix;\n mModelViewMatrix = modelViewMatrix;\n mInverseModelViewMatrix = inverse(modelViewMatrix);\n mInverseNormalMatrix = inverse(normalMatrix);\n ");const r=`\n vec2 getVolumeBbox(vec3 vPointOfReference) {\n const vec3 vBoxMin = vec3(-0.5);\n const vec3 vBoxMax = vec3(0.5);\n vec3 vInvPointOfReference = 1.0 / vPointOfReference;\n \n vec3 vMinRange = (-vOrigin - VOLUME_BBOX_SPAN) * vInvPointOfReference;\n vec3 vMaxRange = (-vOrigin + VOLUME_BBOX_SPAN) * vInvPointOfReference;\n \n vec3 vMin = min(vMinRange, vMaxRange);\n vec3 vMax = max(vMinRange, vMaxRange);\n\n return vec2(\n max(vMin.x, max(vMin.y, vMin.z)),\n min(vMax.x, min(vMax.y, vMax.z))\n );\n }\n\n vec3 radiation = vec3(0.);\n vec3 getBlackBodyRadiation(float temperature) {\n if (temperature == 0.) {\n return vec3(0.);\n }\n\n #ifndef USE_EMISSIVE_GRID\n return vec3(0.);\n #endif\n\n // NOTE Blackbody radiation source - https://www.shadertoy.com/view/4tdGWM\n float temperatureScaled = temperature * 1000.;\n\n radiation.r += 1. / (exp(19E3 * 1. / temperatureScaled) - 1.);\n radiation.g += 3.375 / (exp(19E3 * 1.5 / temperatureScaled) - 1.);\n radiation.b += 8. / (exp(19E3 * 2. / temperatureScaled) - 1.);\n\n return (radiation / max(radiation.r,max(radiation.g,radiation.b)));\n }\n\n float loopUV(float x) {\n if (abs(mod(floor(x), 2.)) == 1.) {\n return ceil(x) - x;\n } else {\n return x - floor(x);\n }\n }\n\n vec3 mapTextureSample0(vec3 position){\n vec3 uv = position + VOLUME_BBOX_SPAN;\n\n ${this.wrap3D===Three__namespace.RepeatWrapping?"return mod(uv, 1.);":""}\n ${0===this.wrap3D||this.wrap3D===Three__namespace.ClampToEdgeWrapping?"// NOTE Return UV":""}\n ${this.wrap3D===Three__namespace.MirroredRepeatWrapping?"\n uv.x = loopUV(uv.x);\n uv.y = loopUV(uv.y);\n uv.z = loopUV(uv.z);\n ":""}\n\n return uv;\n }\n\n vec3 mapTextureSample(vec3 position, vec3 offset){\n vec3 uv = position + VOLUME_BBOX_SPAN + offset;\n\n ${this.wrap3D===Three__namespace.RepeatWrapping?"return mod(uv, 1.);":""}\n ${0===this.wrap3D||this.wrap3D===Three__namespace.ClampToEdgeWrapping?"// NOTE Return UV":""}\n ${this.wrap3D===Three__namespace.MirroredRepeatWrapping?"\n uv.x = loopUV(uv.x);\n uv.y = loopUV(uv.y);\n uv.z = loopUV(uv.z);\n ":""}\n\n return uv;\n }\n\n float blendSample(float value) {\n float fSmoothingThreshold = 0.25;\n float fSmoothingBlend = 0.25;\n\n return smoothstep(\n fSmoothingThreshold - fSmoothingBlend,\n fSmoothingThreshold + fSmoothingBlend,\n value\n ) * densityScale;\n }\n\n float clampedTexture(sampler3D source, vec3 position) {\n ${0===this.wrap3D||this.wrap3D===Three__namespace.ClampToEdgeWrapping?"\n vec3 vAbs = abs(position);\n\n if (max(vAbs.x, max(vAbs.y, vAbs.z)) > .5) {\n return 0.;\n }\n ":""}\n\n return texture(source, mapTextureSample(position, offset3D)).r;\n }\n\n float clampedMaskTexture(sampler3D source, vec3 position) {\n ${0===this.wrap3D||this.wrap3D===Three__namespace.ClampToEdgeWrapping?"\n vec3 vAbs = abs(position);\n\n if (max(vAbs.x, max(vAbs.y, vAbs.z)) > .5) {\n return 0.;\n }\n ":""}\n\n return texture(source, mapTextureSample0(position)).r;\n }\n\n vec3 clampedTextureRGB(sampler3D source, vec3 position) {\n return texture(source, mapTextureSample(position, offset3D)).rgb;\n }\n `;n.fragmentShader=n.fragmentShader.replace("#include <uv_pars_fragment>","// NOTE Override UV calculations").replace("#include <packing>","").replace("#include <common>",`\n precision highp float;\n precision highp sampler2D;\n precision highp sampler3D;\n\n in vec3 vOrigin;\n in vec3 vDirection;\n in vec3 vPosition;\n\n uniform sampler3D densityMap3D;\n uniform sampler3D emissiveMap3D;\n uniform sampler3D baseColorMap3D;\n uniform sampler3D maskMap3D;\n uniform vec3 offset3D;\n uniform int wrap3D;\n\n uniform float steps;\n uniform float absorbance;\n uniform float densityScale;\n uniform vec3 baseColor;\n uniform vec3 scatterColor;\n uniform float resolution;\n\n uniform float cameraNear;\n uniform float cameraFar;\n uniform sampler2D depthScreenMap;\n uniform vec4 depthView;\n\n ${o}\n ${a}\n\n #include <common>\n #include <packing>\n #include <uv_pars_fragment>\n\n ${r}\n `).replace("#include <lights_fragment_begin>","// NOTE Override light calculations").replace("#include <lights_fragment_maps>","// NOTE Override light calculations").replace("#include <lights_fragment_end>","// NOTE Override light calculations").replace("#include <opaque_fragment>","\n vec3 vWorld = -vViewPosition;\n\n vec3 vRayDirection = normalize(vDirection);\n vec2 vBounds = getVolumeBbox(vRayDirection);\n vec2 vMaxBounds = getVolumeBbox(vOrigin);\n \n if (vBounds.x > vBounds.y) {\n discard;\n }\n\n vBounds.x = max(vBounds.x, 0.0);\n vMaxBounds.x = max(vMaxBounds.x, 0.0);\n\n // Volume movement\n\n vec3 vPoint = vOrigin + vBounds.x * vRayDirection;\n vec3 vPointStep = 1.0 / abs(vRayDirection);\n float delta = min(vPointStep.x, min(vPointStep.y, vPointStep.z)) / steps;\n vec3 vDirectionDeltaStep = vRayDirection * delta;\n\n // Density calculations\n\n float density = 0.0;\n vec3 albedo = vec3(0.);\n vec3 emissive = vec3(0.);\n float volumeSample;\n float emissiveSample;\n float maskSample = 1.;\n float noiseSample;\n float noiseFactor;\n vec3 fTest;\n\n // Light calculations\n \n vec3 lightAlbedo = vec3(0.);\n vec3 vLightProbe;\n vec3 lightDirection;\n vec3 vLightStep;\n vec3 vEnvMapScatter = vec3(0.0);\n float lightSample;\n float lightAbsorbance;\n float lightDistance;\n float stepAccumulation;\n\n // Utils\n\n float absorbanceDensityRatio = min(absorbance, densityScale); // NOTE When reducing densityScale, automatically reduce absorbance\n float inverseAbsorbance = 1.0 / absorbanceDensityRatio;\n float inverseDensityScale = 1.0 / densityScale;\n float eInverseAbsorbance = exp(-1. - inverseAbsorbance); // NOTE Wrong, but looks kinda better than exp(-inverseAbsorbance)\n float eDensityAbsorbance = exp(-absorbanceDensityRatio * delta);\n float eInverseDensityScale = exp(-1. - inverseDensityScale);\n\n // Depth Testing\n\n vec2 screenUV = gl_FragCoord.xy / depthView.zw;\n float worldDepth = unpackRGBAToDepth(texture2D(depthScreenMap, screenUV));\n float fogDepth = 0.;\n vec3 vProjPoint;\n\n \n float lightMarchLimit = resolution + 2.;\n int iLightMarchLimit = int(lightMarchLimit);\n\n vec3 vUnit = (mModelMatrix * vec4(1., 0., 0., 0.)).xyz;\n float vUnitLength = length(vUnit);\n\n vec3 iblIrradiance = vec3(0.0);\n \n\n vec3 lastNonSolidPoint = vec3(vPoint);\n\n for (float i = vBounds.x; i < vBounds.y; i += delta) {\n volumeSample = clampedTexture(densityMap3D, vPoint);\n \n #ifdef USE_MASK_GRID\n maskSample = clampedMaskTexture(maskMap3D, vPoint);\n #endif\n\n density += blendSample(volumeSample) * eDensityAbsorbance * maskSample;\n\n #ifdef USE_EMISSIVE_GRID\n emissiveSample = clampedTexture(emissiveMap3D, vPoint);\n emissive = max(emissive, density * vec3(emissiveSample));\n #endif\n\n if (density < 1.) {\n lastNonSolidPoint = vPoint;\n }\n\n if (density >= 1.) {\n break;\n }\n\n vProjPoint = (mModelMatrix * vec4(vPoint, 1.0)).xyz;\n fogDepth = length(vProjPoint - cameraPosition);\n fogDepth = (fogDepth - cameraNear) / (cameraFar - cameraNear);\n fogDepth = saturate(fogDepth);\n \n if (worldDepth < fogDepth) {\n break;\n }\n\n vPoint += vDirectionDeltaStep;\n }\n\n density = clamp(density, 0.0, 1.0);\n\n vPoint = lastNonSolidPoint;\n\n if (density > 0.) {\n\n \n vec3 baseColorSample = baseColor.rgb;\n\n #ifdef USE_BASE_COLOR_GRID\n baseColorSample *= clampedTextureRGB(baseColorMap3D, vPoint);\n #endif\n\n albedo += ambientLightColor;\n \n\n #if VOLUME_USE_ENVIRONMENT\n \n vec3 viewDir = isOrthographic ? vec3(0.0, 0.0, 1.0) : normalize(vViewPosition);\n vec3 vUVCoords = normalize(lastNonSolidPoint * mInverseNormalMatrix).xyz;\n\n #if defined( USE_ENVMAP )\n vEnvMapScatter = getIBLIrradiance(vUVCoords) * RECIPROCAL_PI;\n\n albedo += density * getIBLRadiance(viewDir, vUVCoords, .5) * RECIPROCAL_PI;\n #endif\n \n #endif\n\n #if NUM_HEMI_LIGHTS > 0 && VOLUME_USE_HEMI_LIGHTS\n \n lightAlbedo = vec3(0.);\n vec3 textureProbe;\n\n float absorbanceUp;\n float absorbanceDown;\n float stepAccumulationUp;\n float stepAccumulationDown;\n\n for (int lightIndex = 0; lightIndex < NUM_HEMI_LIGHTS; lightIndex++) {\n\n HemisphereLight hemiLight = hemisphereLights[lightIndex];\n vLightProbe = vec3(vPoint);\n\n absorbanceUp = 0.0;\n absorbanceDown = 0.0;\n\n stepAccumulationUp = 0.;\n stepAccumulationDown = 0.;\n\n lightDirection = -normalize((vec4(hemiLight.direction, 1.) * viewMatrix).xyz);\n vLightStep = (lightDirection * VOLUME_BBOX_SPAN) / lightMarchLimit;\n\n for (int lightMarch = 1; lightMarch < int(lightMarchLimit / 2.); lightMarch++) {\n if (absorbanceUp < 1.) {\n textureProbe = vLightProbe - float(lightMarch) * vLightStep;\n lightSample = clampedTexture(densityMap3D, textureProbe);\n\n absorbanceUp += lightSample * eInverseAbsorbance;\n stepAccumulationUp += 1.;\n }\n \n if (absorbanceDown < 1.) {\n textureProbe = vLightProbe + float(lightMarch) * vLightStep;\n lightSample = clampedTexture(densityMap3D, textureProbe);\n\n absorbanceDown += blendSample(lightSample) * eInverseAbsorbance;\n stepAccumulationDown += 1.;\n }\n }\n\n absorbanceUp = min(1., absorbanceUp);\n absorbanceDown = min(1., absorbanceDown);\n\n lightAlbedo += mix(\n (1. - absorbanceUp) * hemiLight.skyColor,\n (1. - absorbanceDown) * hemiLight.groundColor,\n 0.5\n );\n }\n\n lightAlbedo *= density;\n albedo += lightAlbedo * baseColor.rgb * RECIPROCAL_PI;\n \n #endif\n\n #if NUM_POINT_LIGHTS > 0 && VOLUME_USE_POINT_LIGHTS\n \n PointLight pointLight;\n lightAlbedo = vec3(0.);\n \n for (int lightIndex = 0; lightIndex < NUM_POINT_LIGHTS; lightIndex++) {\n pointLight = pointLights[lightIndex];\n\n vLightProbe = vec3(vPoint);\n lightAbsorbance = 0.;\n stepAccumulation = 1.;\n\n lightDirection = vPoint - (mInverseModelViewMatrix * vec4(pointLight.position, 1.)).xyz;\n lightDistance = length(lightDirection) * vUnitLength;\n lightDirection = normalize(lightDirection);\n vLightStep = (lightDirection * VOLUME_BBOX_SPAN) / lightMarchLimit;\n\n for (int lightMarch = 0; lightMarch < iLightMarchLimit; lightMarch++) {\n vLightProbe -= vLightStep;\n stepAccumulation += 1.;\n \n lightSample = clampedTexture(densityMap3D, vLightProbe);\n \n lightAbsorbance += blendSample(lightSample) * eInverseAbsorbance;\n\n if (lightAbsorbance >= 1.) {\n lightAbsorbance = 1.;\n break;\n }\n }\n\n lightAlbedo += (1. - lightAbsorbance) * pointLight.color;\n }\n\n lightAlbedo *= density;\n albedo += lightAlbedo * baseColor.rgb * RECIPROCAL_PI;\n \n #endif\n\n #if NUM_DIR_LIGHTS > 0 && VOLUME_USE_DIR_LIGHTS\n \n DirectionalLight directionalLight;\n lightAlbedo = vec3(0.);\n\n for (int lightIndex = 0; lightIndex < NUM_DIR_LIGHTS; lightIndex++) {\n directionalLight = directionalLights[lightIndex];\n\n vLightProbe = vec3(vPoint);\n lightAbsorbance = 0.0;\n stepAccumulation = 1.;\n\n lightDirection = -normalize((vec4(directionalLight.direction, 1.) * viewMatrix).xyz);\n vLightStep = (lightDirection * VOLUME_BBOX_SPAN) / lightMarchLimit;\n\n for (int lightMarch = 0; lightMarch < iLightMarchLimit; lightMarch++) {\n vLightProbe -= vLightStep;\n stepAccumulation += 1.;\n \n float lightSample = clampedTexture(densityMap3D, vLightProbe);\n\n lightAbsorbance += blendSample(lightSample) * eInverseAbsorbance;\n\n if (lightAbsorbance >= 1.) {\n lightAbsorbance = 1.;\n break;\n }\n }\n\n lightAlbedo += (1. - lightAbsorbance) * directionalLight.color;\n }\n\n lightAlbedo *= density;\n albedo += lightAlbedo * baseColor.rgb * RECIPROCAL_PI;\n \n #endif\n\n #if NUM_SPOT_LIGHTS > 0 && VOLUME_USE_SPOT_LIGHTS\n \n SpotLight spotLight;\n lightAlbedo = vec3(0.);\n\n float angleCos;\n float spotAttenuation;\n \n for (int lightIndex = 0; lightIndex < NUM_SPOT_LIGHTS; lightIndex++) {\n spotLight = spotLights[lightIndex];\n\n vLightProbe = vec3(vPoint);\n lightAbsorbance = 0.0;\n stepAccumulation = 1.;\n\n lightDirection = vPoint - (mInverseModelViewMatrix * vec4(spotLight.position, 1.)).xyz;\n lightDistance = length(lightDirection) * vUnitLength;\n lightDirection = normalize(lightDirection);\n vLightStep = (lightDirection * VOLUME_BBOX_SPAN) / lightMarchLimit;\n\n angleCos = dot(normalize(-lightDirection), normalize(mInverseNormalMatrix * spotLight.direction));\n spotAttenuation = smoothstep(spotLight.coneCos, spotLight.penumbraCos, angleCos);\n\n if (spotAttenuation > 0.) {\n for (int lightMarch = 0; lightMarch < iLightMarchLimit; lightMarch++) {\n vLightProbe -= vLightStep;\n stepAccumulation += 1.;\n \n lightSample = clampedTexture(densityMap3D, vLightProbe);\n \n lightAbsorbance += blendSample(lightSample) * eInverseAbsorbance;\n\n if (lightAbsorbance >= 1.) {\n lightAbsorbance = 1.;\n break;\n }\n }\n\n lightAlbedo += (1. - lightAbsorbance) * spotAttenuation * spotLight.color;\n }\n }\n\n lightAlbedo *= density;\n albedo += lightAlbedo * baseColor.rgb * RECIPROCAL_PI;\n \n #endif\n\n albedo *= baseColorSample;\n }\n \n emissive = getBlackBodyRadiation(emissive.r);\n albedo += emissive;\n\n outgoingLight.rgb = vPoint.xyz;\n diffuseColor.a = saturate(density * opacity);\n\n if (density <= 0.) {\n discard;\n }\n\n #include <opaque_fragment>\n ");};}}
|
|
57
|
+
|
|
58
|
+
const froxelPars="\n uniform vec3 froxelGrid;\n uniform float froxelColumns;\n uniform vec2 froxelRange;\n uniform mat4 froxelProjectionInverse;\n uniform int froxelSubSteps;\n\n float frameSliceDepth(float t) {\n return froxelRange.x * pow(froxelRange.y / froxelRange.x, t);\n }\n\n vec3 frameGetViewPosition(vec2 uv, float viewDepth) {\n vec4 nearPoint = froxelProjectionInverse * vec4(uv * 2.0 - 1.0, -1.0, 1.0);\n vec4 farPoint = froxelProjectionInverse * vec4(uv * 2.0 - 1.0, 1.0, 1.0);\n nearPoint /= nearPoint.w;\n farPoint /= farPoint.w;\n\n float t = (viewDepth + nearPoint.z) / (nearPoint.z - farPoint.z);\n\n return mix(nearPoint.xyz, farPoint.xyz, t);\n }\n";const noisePars="\n uniform float frameJitter;\n\n // NOTE Returns 0.5 when jitter is off (frameJitter is negative)\n float frameNoise(vec2 pixel) {\n if (frameJitter < 0.0) {\n return 0.5;\n }\n\n float noise = fract(52.9829189 * fract(dot(pixel, vec2(0.06711056, 0.00583715))));\n\n return fract(noise + frameJitter);\n }\n";const volumePars=`\n #define FRAME_WRAP_REPEAT ${Three__namespace.RepeatWrapping}\n #define FRAME_WRAP_MIRROR ${Three__namespace.MirroredRepeatWrapping}\n\n uniform mat4 frameViewToLocal;\n uniform mat4 frameLocalToView;\n uniform mat4 frameWorldToLocal;\n uniform float frameWorldScale;\n\n uniform highp sampler3D frameDensityMap;\n uniform highp sampler3D frameEmissiveMap;\n uniform highp sampler3D frameBaseColorMap;\n uniform highp sampler3D frameMaskMap;\n uniform bool frameUseEmissiveMap;\n uniform bool frameUseBaseColorMap;\n uniform bool frameUseMaskMap;\n\n uniform vec3 frameBaseColor;\n uniform vec3 frameScatterColor;\n uniform float frameExtinction;\n uniform float frameDensityScale;\n uniform float frameDensityCutoff;\n uniform float frameAbsorbance;\n uniform float frameOpacity;\n uniform float frameEmissiveIntensity;\n uniform vec2 frameTemperatureRange;\n uniform vec3 frameOffset3D;\n uniform int frameWrap3D;\n\n float frameMirrorAxis(float value) {\n float period = mod(value, 2.0);\n\n return period > 1.0 ? 2.0 - period : period;\n }\n\n vec3 frameWrapUv(vec3 uv) {\n if (frameWrap3D == FRAME_WRAP_REPEAT) {\n return fract(uv);\n }\n\n if (frameWrap3D == FRAME_WRAP_MIRROR) {\n return vec3(frameMirrorAxis(uv.x), frameMirrorAxis(uv.y), frameMirrorAxis(uv.z));\n }\n\n return uv;\n }\n\n // NOTE Mip level for a footprint of the given size in local units of the longest box side\n float frameLod(float localSize) {\n vec3 texels = vec3(textureSize(frameDensityMap, 0));\n\n return max(0.0, log2(localSize * max(texels.x, max(texels.y, texels.z))));\n }\n\n // NOTE Returns extinction for each world unit\n float frameSampleExtinction(vec3 localPosition, float lod) {\n vec3 uv = frameWrapUv(localPosition + 0.5 + frameOffset3D);\n float value = textureLod(frameDensityMap, uv, lod).r;\n\n if (frameUseMaskMap) {\n value *= textureLod(frameMaskMap, localPosition + 0.5, lod).r;\n }\n\n value = value > frameDensityCutoff ? value : 0.0;\n\n return value * frameDensityScale * frameExtinction / frameWorldScale;\n }\n\n bool frameInsideBox(vec3 localPosition) {\n vec3 localAbs = abs(localPosition);\n\n return max(localAbs.x, max(localAbs.y, localAbs.z)) <= 0.5;\n }\n\n float frameBoxExit(vec3 origin, vec3 direction) {\n vec3 inverseDirection = 1.0 / direction;\n vec3 tMin = (vec3(-0.5) - origin) * inverseDirection;\n vec3 tMax = (vec3(0.5) - origin) * inverseDirection;\n vec3 tFar = max(tMin, tMax);\n\n return max(0.0, min(tFar.x, min(tFar.y, tFar.z)));\n }\n\n // NOTE Returns the part of the segment from start to end (0.0 to 1.0) that is in the box\n vec2 frameBoxSegment(vec3 start, vec3 end) {\n vec3 inverseDirection = 1.0 / (end - start);\n vec3 tMin = (vec3(-0.5) - start) * inverseDirection;\n vec3 tMax = (vec3(0.5) - start) * inverseDirection;\n vec3 tNear = min(tMin, tMax);\n vec3 tFar = max(tMin, tMax);\n\n return vec2(\n max(0.0, max(tNear.x, max(tNear.y, tNear.z))),\n min(1.0, min(tFar.x, min(tFar.y, tFar.z)))\n );\n }\n\n vec3 frameSampleAlbedo(vec3 localPosition, float lod) {\n vec3 albedo = frameBaseColor;\n\n if (frameUseBaseColorMap) {\n albedo *= textureLod(frameBaseColorMap, frameWrapUv(localPosition + 0.5 + frameOffset3D), lod).rgb;\n }\n\n return albedo;\n }\n\n // NOTE Blackbody color, temperature in Kelvin. Source - https://www.shadertoy.com/view/4tdGWM\n vec3 frameBlackBody(float temperature) {\n if (temperature <= 0.0) {\n return vec3(0.0);\n }\n\n vec3 radiation = vec3(\n 1.0 / (exp(19E3 * 1.0 / temperature) - 1.0),\n 3.375 / (exp(19E3 * 1.5 / temperature) - 1.0),\n 8.0 / (exp(19E3 * 2.0 / temperature) - 1.0)\n );\n\n float peak = max(radiation.r, max(radiation.g, radiation.b));\n\n return peak > 0.0 ? radiation / peak : vec3(0.0);\n }\n\n vec3 frameSampleEmission(vec3 localPosition, float lod) {\n if (!frameUseEmissiveMap) {\n return vec3(0.0);\n }\n\n float value = textureLod(frameEmissiveMap, frameWrapUv(localPosition + 0.5 + frameOffset3D), lod).r;\n float temperature = mix(frameTemperatureRange.x, frameTemperatureRange.y, value);\n\n // NOTE Emitted power increases with temperature^4 (Stefan-Boltzmann). Range top gives 1.0.\n float power = pow(temperature / max(frameTemperatureRange.y, 1.0), 4.0);\n\n return frameBlackBody(temperature) * power * frameEmissiveIntensity;\n }\n`;const frameMaxShadowCascades=4;const gridPars="\n #define FRAME_MAX_CASCADES 4\n\n uniform vec3 frameGridMin[FRAME_MAX_CASCADES];\n uniform vec3 frameGridSize[FRAME_MAX_CASCADES];\n uniform vec3 frameGridVoxel[FRAME_MAX_CASCADES];\n uniform int frameGridCount;\n uniform float frameGridResolution;\n uniform float frameGridColumns;\n uniform vec2 frameGridAtlasSize;\n\n vec3 frameGridUvw(int cascade, vec3 worldPosition) {\n return (worldPosition - frameGridMin[cascade]) / frameGridSize[cascade];\n }\n\n // NOTE Half a voxel inside the cascade. Then the linear filter does not read the clamped edge.\n bool frameInsideGrid(vec3 gridUvw) {\n float inset = 0.5 / frameGridResolution;\n\n return all(greaterThanEqual(gridUvw, vec3(inset))) && all(lessThanEqual(gridUvw, vec3(1.0 - inset)));\n }\n\n // NOTE Finest cascade from the first cascade that contains the position, or -1\n int frameFindCascade(vec3 worldPosition, int firstCascade) {\n for (int i = 0; i < FRAME_MAX_CASCADES; i++) {\n if (i >= frameGridCount) {\n break;\n }\n\n if (i >= firstCascade && frameInsideGrid(frameGridUvw(i, worldPosition))) {\n return i;\n }\n }\n\n return -1;\n }\n\n // NOTE Hardware bilinear filter in the slice, manual linear filter between slices\n vec4 frameSampleGrid(sampler2D atlas, int cascade, vec3 gridUvw) {\n float resolution = frameGridResolution;\n float z = gridUvw.z * resolution - 0.5;\n float z0 = clamp(floor(z), 0.0, resolution - 1.0);\n float z1 = min(z0 + 1.0, resolution - 1.0);\n float blend = clamp(z - z0, 0.0, 1.0);\n vec2 xy = clamp(gridUvw.xy * resolution, vec2(0.5), vec2(resolution - 0.5));\n\n float tileOffset = float(cascade) * resolution;\n float tile0Index = tileOffset + z0;\n float tile1Index = tileOffset + z1;\n vec2 tile0 = vec2(mod(tile0Index, frameGridColumns), floor(tile0Index / frameGridColumns));\n vec2 tile1 = vec2(mod(tile1Index, frameGridColumns), floor(tile1Index / frameGridColumns));\n\n vec4 sample0 = texture(atlas, (tile0 * resolution + xy) / frameGridAtlasSize);\n vec4 sample1 = texture(atlas, (tile1 * resolution + xy) / frameGridAtlasSize);\n\n return mix(sample0, sample1, blend);\n }\n\n // NOTE Transmittance of the shadow slots at a world position. 1.0 outside the grid.\n void frameSampleShadows(\n sampler2D map0,\n sampler2D map1,\n vec3 worldPosition,\n out vec4 shadow0,\n out vec4 shadow1\n ) {\n shadow0 = vec4(1.0);\n shadow1 = vec4(1.0);\n\n int cascade = frameFindCascade(worldPosition, 0);\n\n if (cascade < 0) {\n return;\n }\n\n vec3 gridUvw = frameGridUvw(cascade, worldPosition);\n\n shadow0 = frameSampleGrid(map0, cascade, gridUvw);\n shadow1 = frameSampleGrid(map1, cascade, gridUvw);\n\n // NOTE Blend to the next cascade near the sides of this cascade. Then cascade borders are not visible.\n vec3 sideDistance = min(gridUvw, 1.0 - gridUvw);\n float blend = 1.0 - smoothstep(0.0, 0.1, min(sideDistance.x, min(sideDistance.y, sideDistance.z)));\n int nextCascade = blend > 0.0 ? frameFindCascade(worldPosition, cascade + 1) : -1;\n\n if (nextCascade >= 0) {\n vec3 nextUvw = frameGridUvw(nextCascade, worldPosition);\n\n shadow0 = mix(shadow0, frameSampleGrid(map0, nextCascade, nextUvw), blend);\n shadow1 = mix(shadow1, frameSampleGrid(map1, nextCascade, nextUvw), blend);\n }\n }\n\n // NOTE Cascade and grid position of an atlas pixel\n int frameGetGridCell(vec2 fragCoord, out vec3 gridUvw) {\n float resolution = frameGridResolution;\n vec2 tile = floor(fragCoord / resolution);\n float tileIndex = tile.y * frameGridColumns + tile.x;\n float cascade = floor(tileIndex / resolution);\n float z = tileIndex - cascade * resolution;\n\n gridUvw = vec3(fragCoord - tile * resolution, z + 0.5) / resolution;\n\n return int(cascade);\n }\n";const tileQuadVertexShader="\n uniform vec4 frameRect;\n uniform float frameSliceStart;\n uniform vec2 frameTileSize;\n uniform float frameTileColumns;\n uniform vec2 frameTargetSize;\n\n void main() {\n float slice = frameSliceStart + float(gl_InstanceID);\n vec2 tile = vec2(mod(slice, frameTileColumns), floor(slice / frameTileColumns));\n vec2 cell = mix(frameRect.xy, frameRect.zw, position.xy * 0.5 + 0.5);\n\n gl_Position = vec4((tile * frameTileSize + cell) / frameTargetSize * 2.0 - 1.0, 0.0, 1.0);\n }\n";const createVolumeUniforms=()=>({frameViewToLocal:{value:new Three__namespace.Matrix4},frameLocalToView:{value:new Three__namespace.Matrix4},frameWorldToLocal:{value:new Three__namespace.Matrix4},frameWorldScale:{value:1},frameDensityMap:{value:null},frameEmissiveMap:{value:null},frameBaseColorMap:{value:null},frameMaskMap:{value:null},frameUseEmissiveMap:{value:!1},frameUseBaseColorMap:{value:!1},frameUseMaskMap:{value:!1},frameBaseColor:{value:new Three__namespace.Color(16777215)},frameScatterColor:{value:new Three__namespace.Color(0)},frameExtinction:{value:100},frameDensityScale:{value:1},frameDensityCutoff:{value:0},frameAbsorbance:{value:1},frameOpacity:{value:1},frameEmissiveIntensity:{value:1},frameTemperatureRange:{value:new Three__namespace.Vector2(0,6500)},frameOffset3D:{value:new Three__namespace.Vector3},frameWrap3D:{value:Three__namespace.ClampToEdgeWrapping}});const setVolumeUniforms=(e,a,n)=>{e.frameViewToLocal.value.copy(n.viewToLocal),e.frameLocalToView.value.copy(n.localToView),e.frameWorldToLocal.value.copy(n.worldToLocal),e.frameWorldScale.value=n.matrixWorld.getMaxScaleOnAxis(),e.frameDensityMap.value=n.densityMap3D,e.frameEmissiveMap.value=n.emissiveMap3D,e.frameBaseColorMap.value=n.baseColorMap3D,e.frameMaskMap.value=n.maskMap3D,e.frameUseEmissiveMap.value=!!n.emissiveMap3D,e.frameUseBaseColorMap.value=!!n.baseColorMap3D,e.frameUseMaskMap.value=!!n.maskMap3D,e.frameBaseColor.value.copy(a.baseColor),e.frameScatterColor.value.copy(a.scatterColor),e.frameExtinction.value=a.extinction,e.frameDensityScale.value=a.densityScale,e.frameDensityCutoff.value=a.densityCutoff,e.frameAbsorbance.value=a.absorbance,e.frameOpacity.value=a.opacity,e.frameEmissiveIntensity.value=a.emissiveIntensity,e.frameTemperatureRange.value.copy(a.temperatureRange),e.frameOffset3D.value.copy(a.offset3D),e.frameWrap3D.value=a.wrap3D;};
|
|
59
|
+
|
|
60
|
+
const frameLights={useAmbientLights:32,usePointLights:16,useDirectionalLights:8,useSpotLights:4,useHemisphereLights:2,useEnvironment:1};const vertexShader$4="\n void main() {\n gl_Position = vec4(position.xy, 0.0, 1.0);\n }\n",fragmentShader$6=`\n precision highp float;\n precision highp sampler3D;\n\n #define FRAME_LIGHT_AMBIENT ${frameLights.useAmbientLights}\n #define FRAME_LIGHT_POINT ${frameLights.usePointLights}\n #define FRAME_LIGHT_DIR ${frameLights.useDirectionalLights}\n #define FRAME_LIGHT_SPOT ${frameLights.useSpotLights}\n #define FRAME_LIGHT_HEMI ${frameLights.useHemisphereLights}\n #define FRAME_LIGHT_ENV ${frameLights.useEnvironment}\n\n uniform int frameLights;\n uniform int frameShadowSteps;\n\n ${froxelPars}\n ${volumePars}\n ${noisePars}\n\n #include <common>\n #include <lights_pars_begin>\n #include <envmap_common_pars_fragment>\n #include <cube_uv_reflection_fragment>\n #include <envmap_physical_pars_fragment>\n\n // NOTE Shadow march inside the source volume only. Other volumes do not cast shadows.\n float frameTransmittance(vec3 origin, vec3 localDirection, float maxLocalDistance, float lod) {\n if (frameAbsorbance <= 0.0 || frameShadowSteps <= 0) {\n return 1.0;\n }\n\n vec3 direction = normalize(localDirection);\n float marchDistance = min(frameBoxExit(origin, direction), maxLocalDistance);\n\n if (marchDistance <= 0.0) {\n return 1.0;\n }\n\n float localStep = marchDistance / float(frameShadowSteps);\n float stepLod = max(lod, frameLod(localStep));\n float worldStep = localStep * length(mat3(frameLocalToView) * direction);\n float stepScale = worldStep * frameAbsorbance;\n float opticalDepth = 0.0;\n vec3 probe = origin + direction * localStep * frameNoise(gl_FragCoord.xy + vec2(17.0, 31.0));\n\n for (int i = 0; i < frameShadowSteps; i++) {\n opticalDepth += frameSampleExtinction(probe, stepLod) * stepScale;\n\n if (opticalDepth > 8.0) {\n break;\n }\n\n probe += direction * localStep;\n }\n\n return exp(-opticalDepth);\n }\n\n void main() {\n vec2 tile = floor(gl_FragCoord.xy / froxelGrid.xy);\n float slice = tile.y * froxelColumns + tile.x;\n\n if (slice >= froxelGrid.z) {\n discard;\n }\n\n vec2 uv = (gl_FragCoord.xy - tile * froxelGrid.xy) / froxelGrid.xy;\n float sliceNear = frameSliceDepth(slice / froxelGrid.z);\n float sliceFar = frameSliceDepth((slice + 1.0) / froxelGrid.z);\n vec3 viewNear = frameGetViewPosition(uv, sliceNear);\n vec3 viewFar = frameGetViewPosition(uv, sliceFar);\n vec3 localNear = (frameViewToLocal * vec4(viewNear, 1.0)).xyz;\n vec3 localFar = (frameViewToLocal * vec4(viewFar, 1.0)).xyz;\n\n vec2 boxSegment = frameBoxSegment(localNear, localFar);\n\n if (boxSegment.x >= boxSegment.y) {\n discard;\n }\n\n // NOTE Pick the mip level from the froxel width or the sub-step length, the larger one\n float subStepLength = length(viewFar - viewNear) / float(froxelSubSteps);\n float sliceMiddle = (sliceNear + sliceFar) * 0.5;\n float froxelWidth = length(\n frameGetViewPosition(uv + vec2(1.0 / froxelGrid.x, 0.0), sliceMiddle) -\n frameGetViewPosition(uv, sliceMiddle)\n );\n float lod = frameLod(max(froxelWidth, subStepLength) / frameWorldScale);\n\n // NOTE Pre-integrate the slice along the view ray. Light the visibility-weighted mean position, not the froxel center.\n float transmittance = 1.0;\n float visibleWeight = 0.0;\n float visibleTSum = 0.0;\n float jitter = frameNoise(gl_FragCoord.xy);\n\n for (int i = 0; i < froxelSubSteps; i++) {\n float t = (float(i) + jitter) / float(froxelSubSteps);\n\n if (t < boxSegment.x || t > boxSegment.y) {\n continue;\n }\n\n vec3 probe = mix(localNear, localFar, t);\n float opticalDepth = frameSampleExtinction(probe, lod) * frameOpacity * subStepLength;\n float stepTransmittance = exp(-opticalDepth);\n float weight = transmittance * (1.0 - stepTransmittance);\n\n visibleWeight += weight;\n visibleTSum += weight * t;\n transmittance *= stepTransmittance;\n }\n\n float alpha = 1.0 - transmittance;\n\n if (visibleWeight <= 0.0 || alpha <= 1e-5) {\n discard;\n }\n\n vec3 localPosition = mix(localNear, localFar, visibleTSum / visibleWeight);\n vec3 viewPosition = (frameLocalToView * vec4(localPosition, 1.0)).xyz;\n\n mat3 viewToLocalDirection = mat3(frameViewToLocal);\n\n vec3 outwardNormal = length(localPosition) > 1e-4\n ? normalize(mat3(frameLocalToView) * localPosition)\n : normalize((viewMatrix * vec4(0.0, 1.0, 0.0, 0.0)).xyz);\n\n vec3 irradiance = vec3(0.0);\n\n if ((frameLights & FRAME_LIGHT_AMBIENT) != 0) {\n irradiance += getAmbientLightIrradiance(ambientLightColor);\n\n #if defined( USE_LIGHT_PROBES )\n irradiance += getLightProbeIrradiance(lightProbe, outwardNormal);\n #endif\n }\n\n #if defined( USE_ENVMAP )\n if ((frameLights & FRAME_LIGHT_ENV) != 0) {\n float envTransmittance = frameTransmittance(localPosition, viewToLocalDirection * outwardNormal, 1e6, lod);\n\n irradiance += getIBLIrradiance(outwardNormal) * envTransmittance;\n }\n #endif\n\n #if NUM_HEMI_LIGHTS > 0\n if ((frameLights & FRAME_LIGHT_HEMI) != 0) {\n for (int i = 0; i < NUM_HEMI_LIGHTS; i++) {\n HemisphereLight hemiLight = hemisphereLights[i];\n vec3 upDirection = viewToLocalDirection * hemiLight.direction;\n\n irradiance += 0.5 * hemiLight.skyColor * frameTransmittance(localPosition, upDirection, 1e6, lod);\n irradiance += 0.5 * hemiLight.groundColor * frameTransmittance(localPosition, -upDirection, 1e6, lod);\n }\n }\n #endif\n\n #if NUM_DIR_LIGHTS > 0\n if ((frameLights & FRAME_LIGHT_DIR) != 0) {\n for (int i = 0; i < NUM_DIR_LIGHTS; i++) {\n DirectionalLight directionalLight = directionalLights[i];\n\n irradiance += directionalLight.color * frameTransmittance(\n localPosition,\n viewToLocalDirection * directionalLight.direction,\n 1e6,\n lod\n );\n }\n }\n #endif\n\n // NOTE Point and spot lights have no distance falloff. This is the same as FogVolume.\n\n #if NUM_POINT_LIGHTS > 0\n if ((frameLights & FRAME_LIGHT_POINT) != 0) {\n for (int i = 0; i < NUM_POINT_LIGHTS; i++) {\n PointLight pointLight = pointLights[i];\n vec3 toLight = (frameViewToLocal * vec4(pointLight.position, 1.0)).xyz - localPosition;\n\n irradiance += pointLight.color * frameTransmittance(localPosition, toLight, length(toLight), lod);\n }\n }\n #endif\n\n #if NUM_SPOT_LIGHTS > 0\n if ((frameLights & FRAME_LIGHT_SPOT) != 0) {\n for (int i = 0; i < NUM_SPOT_LIGHTS; i++) {\n SpotLight spotLight = spotLights[i];\n float angleCos = dot(normalize(spotLight.position - viewPosition), spotLight.direction);\n float spotAttenuation = getSpotAttenuation(spotLight.coneCos, spotLight.penumbraCos, angleCos);\n\n if (spotAttenuation > 0.0) {\n vec3 toLight = (frameViewToLocal * vec4(spotLight.position, 1.0)).xyz - localPosition;\n\n irradiance += spotLight.color * spotAttenuation * frameTransmittance(localPosition, toLight, length(toLight), lod);\n }\n }\n }\n #endif\n\n vec3 albedo = frameSampleAlbedo(localPosition, lod);\n vec3 radiance = max(frameScatterColor, albedo * irradiance * RECIPROCAL_PI);\n\n radiance += frameSampleEmission(localPosition, lod);\n\n // NOTE Premultiplied slice color and alpha. Linear filtering of these values gives transparent edges.\n gl_FragColor = vec4(radiance * alpha, alpha);\n }\n`;class FrameInjectMaterial extends Three__namespace.MeshStandardMaterial{name="FrameInjectMaterial";customProgramCacheKey=()=>"FrameInjectMaterial";constructor(e){super(),this.frameUniforms={...e,...createVolumeUniforms(),frameLights:{value:0},frameShadowSteps:{value:32}},this.transparent=!0,this.depthTest=!1,this.depthWrite=!1,this.side=Three__namespace.DoubleSide,this.blending=Three__namespace.CustomBlending,this.blendEquation=Three__namespace.AddEquation,this.blendSrc=Three__namespace.OneFactor,this.blendDst=Three__namespace.OneMinusSrcAlphaFactor,this.blendSrcAlpha=Three__namespace.OneFactor,this.blendDstAlpha=Three__namespace.OneMinusSrcAlphaFactor,this.onBeforeCompile=e=>{Object.assign(e.uniforms,this.frameUniforms),e.vertexShader=vertexShader$4,e.fragmentShader=fragmentShader$6;};}}
|
|
61
|
+
|
|
62
|
+
const vertexShader$3="\n varying vec2 vUv;\n\n void main() {\n vUv = position.xy * 0.5 + 0.5;\n\n gl_Position = vec4(position.xy, 0.0, 1.0);\n }\n",fragmentShader$5="\n precision highp float;\n\n #define FRAME_MAX_SLICES 1024\n\n uniform sampler2D froxelAtlas;\n uniform vec2 froxelAtlasSize;\n uniform vec3 froxelGrid;\n uniform float froxelColumns;\n uniform vec2 froxelRange;\n\n uniform sampler2D frameDepthMap;\n uniform float frameCameraNear;\n uniform float frameCameraFar;\n\n varying vec2 vUv;\n\n #include <common>\n #include <packing>\n\n float frameSliceDepth(float t) {\n return froxelRange.x * pow(froxelRange.y / froxelRange.x, t);\n }\n\n float frameSceneDepth() {\n float depth = texture2D(frameDepthMap, vUv).x;\n\n #ifdef USE_REVERSED_DEPTH_BUFFER\n bool isEmpty = depth <= 0.0;\n #else\n bool isEmpty = depth >= 1.0;\n #endif\n\n if (isEmpty) {\n return 1e20;\n }\n\n return -(isOrthographic\n ? orthographicDepthToViewZ(depth, frameCameraNear, frameCameraFar)\n : perspectiveDepthToViewZ(depth, frameCameraNear, frameCameraFar));\n }\n\n void main() {\n float sceneDepth = frameSceneDepth();\n\n vec2 froxelUv = clamp(vUv * froxelGrid.xy, vec2(0.5), froxelGrid.xy - 0.5);\n int sliceCount = int(froxelGrid.z);\n\n vec3 radiance = vec3(0.0);\n float transmittance = 1.0;\n float sliceStart = froxelRange.x;\n\n for (int i = 0; i < FRAME_MAX_SLICES; i++) {\n if (i >= sliceCount || sliceStart >= sceneDepth || transmittance < 0.001) {\n break;\n }\n\n float sliceEnd = frameSliceDepth(float(i + 1) / froxelGrid.z);\n float coverage = (min(sliceEnd, sceneDepth) - sliceStart) / (sliceEnd - sliceStart);\n\n vec2 tile = vec2(mod(float(i), froxelColumns), floor(float(i) / froxelColumns));\n vec4 froxel = texture2D(froxelAtlas, (tile * froxelGrid.xy + froxelUv) / froxelAtlasSize);\n\n float froxelAlpha = clamp(froxel.a, 0.0, 1.0);\n\n // NOTE Froxels store premultiplied slice color and alpha. Scene depth can cut the last slice.\n if (froxelAlpha > 0.0) {\n float sliceAlpha = coverage < 1.0 ? 1.0 - pow(1.0 - froxelAlpha, coverage) : froxelAlpha;\n\n radiance += transmittance * froxel.rgb * (sliceAlpha / froxelAlpha);\n transmittance *= 1.0 - sliceAlpha;\n }\n\n sliceStart = sliceEnd;\n }\n\n float alpha = 1.0 - transmittance;\n\n if (alpha <= 0.0) {\n discard;\n }\n\n gl_FragColor = vec4(radiance / alpha, alpha);\n\n #include <tonemapping_fragment>\n #include <colorspace_fragment>\n #include <premultiplied_alpha_fragment>\n }\n";class FrameIntegrateMaterial extends Three__namespace.ShaderMaterial{name="FrameIntegrateMaterial";constructor(e){super({uniforms:{...e,froxelAtlas:{value:null},froxelAtlasSize:{value:new Three__namespace.Vector2(1,1)},frameDepthMap:{value:null},frameCameraNear:{value:.1},frameCameraFar:{value:1e3}},vertexShader:vertexShader$3,fragmentShader:fragmentShader$5,transparent:!0,depthTest:!1,depthWrite:!1,premultipliedAlpha:!0});}}
|
|
63
|
+
|
|
64
|
+
const fragmentShader$4=`\n precision highp float;\n\n layout(location = 0) out highp vec4 frameDensityAlbedo;\n layout(location = 1) out highp vec4 frameDensityEmission;\n layout(location = 2) out highp vec4 frameDensityShadow0;\n layout(location = 3) out highp vec4 frameDensityShadow1;\n\n ${froxelPars}\n ${volumePars}\n ${gridPars}\n ${noisePars}\n\n uniform sampler2D frameTransmittanceMap0;\n uniform sampler2D frameTransmittanceMap1;\n uniform bool frameUseShadows;\n uniform mat4 frameCameraMatrixWorld;\n\n void main() {\n vec2 tile = floor(gl_FragCoord.xy / froxelGrid.xy);\n float slice = tile.y * froxelColumns + tile.x;\n vec2 uv = (gl_FragCoord.xy - tile * froxelGrid.xy) / froxelGrid.xy;\n\n float sliceNear = frameSliceDepth(slice / froxelGrid.z);\n float sliceFar = frameSliceDepth((slice + 1.0) / froxelGrid.z);\n vec3 viewNear = frameGetViewPosition(uv, sliceNear);\n vec3 viewFar = frameGetViewPosition(uv, sliceFar);\n vec3 localNear = (frameViewToLocal * vec4(viewNear, 1.0)).xyz;\n vec3 localFar = (frameViewToLocal * vec4(viewFar, 1.0)).xyz;\n\n vec2 boxSegment = frameBoxSegment(localNear, localFar);\n\n if (boxSegment.x >= boxSegment.y) {\n discard;\n }\n\n // NOTE Pick the mip level from the froxel width or the sub-step length, the larger one\n float subStepLength = length(viewFar - viewNear) / float(froxelSubSteps);\n float sliceMiddle = (sliceNear + sliceFar) * 0.5;\n float froxelWidth = length(\n frameGetViewPosition(uv + vec2(1.0 / froxelGrid.x, 0.0), sliceMiddle) -\n frameGetViewPosition(uv, sliceMiddle)\n );\n float lod = frameLod(max(froxelWidth, subStepLength) / frameWorldScale);\n\n // NOTE Mean extinction of the slice. The visibility-weighted mean position is the light position in the lighting pass.\n float extinctionSum = 0.0;\n float transmittance = 1.0;\n float visibleWeight = 0.0;\n float visibleTSum = 0.0;\n float jitter = frameNoise(gl_FragCoord.xy);\n\n for (int i = 0; i < froxelSubSteps; i++) {\n float t = (float(i) + jitter) / float(froxelSubSteps);\n\n if (t < boxSegment.x || t > boxSegment.y) {\n continue;\n }\n\n float extinction = frameSampleExtinction(mix(localNear, localFar, t), lod) * frameOpacity;\n float stepTransmittance = exp(-extinction * subStepLength);\n float weight = transmittance * (1.0 - stepTransmittance);\n\n visibleWeight += weight;\n visibleTSum += weight * t;\n extinctionSum += extinction;\n transmittance *= stepTransmittance;\n }\n\n float extinction = extinctionSum / float(froxelSubSteps);\n\n if (extinction <= 0.0) {\n discard;\n }\n\n float visibleT = visibleWeight > 0.0 ? visibleTSum / visibleWeight : 0.5;\n vec3 visiblePosition = mix(localNear, localFar, visibleT);\n vec3 albedo = frameSampleAlbedo(visiblePosition, lod);\n vec3 emission = frameSampleEmission(visiblePosition, lod) + frameScatterColor;\n\n // NOTE Shadows at the visible position of this volume, not of other volumes in the froxel\n vec4 shadow0 = vec4(1.0);\n vec4 shadow1 = vec4(1.0);\n\n if (frameUseShadows) {\n vec3 viewPosition = mix(viewNear, viewFar, visibleT);\n vec3 worldPosition = (frameCameraMatrixWorld * vec4(viewPosition, 1.0)).xyz;\n\n frameSampleShadows(\n frameTransmittanceMap0,\n frameTransmittanceMap1,\n worldPosition,\n shadow0,\n shadow1\n );\n }\n\n // NOTE Values are weighted by extinction. Additive blending then gives extinction-weighted means.\n frameDensityAlbedo = vec4(albedo * extinction, extinction);\n frameDensityEmission = vec4(emission * extinction, visibleT * extinction);\n frameDensityShadow0 = shadow0 * extinction;\n frameDensityShadow1 = shadow1 * extinction;\n }\n`;class FrameDensityMaterial extends Three__namespace.ShaderMaterial{name="FrameDensityMaterial";constructor(e){super({glslVersion:Three__namespace.GLSL3,uniforms:{...e,...createVolumeUniforms(),frameRect:{value:new Three__namespace.Vector4},frameSliceStart:{value:0}},vertexShader:tileQuadVertexShader,fragmentShader:fragmentShader$4,depthTest:!1,depthWrite:!1,transparent:!0,blending:Three__namespace.CustomBlending,blendEquation:Three__namespace.AddEquation,blendSrc:Three__namespace.OneFactor,blendDst:Three__namespace.OneFactor,blendSrcAlpha:Three__namespace.OneFactor,blendDstAlpha:Three__namespace.OneFactor});}}
|
|
65
|
+
|
|
66
|
+
const fragmentShader$3=`\n precision highp float;\n\n layout(location = 0) out highp vec4 frameShadowDensity;\n\n ${volumePars}\n ${gridPars}\n\n void main() {\n vec3 gridUvw;\n int cascade = frameGetGridCell(gl_FragCoord.xy, gridUvw);\n\n if (cascade >= frameGridCount) {\n discard;\n }\n\n vec3 worldPosition = frameGridMin[cascade] + gridUvw * frameGridSize[cascade];\n vec3 localPosition = (frameWorldToLocal * vec4(worldPosition, 1.0)).xyz;\n\n if (!frameInsideBox(localPosition)) {\n discard;\n }\n\n vec3 voxel = frameGridVoxel[cascade];\n float voxelSize = max(voxel.x, max(voxel.y, voxel.z));\n float lod = frameLod(voxelSize / frameWorldScale);\n\n // NOTE Absorbance scales the shadow of this volume, not its look from the camera\n float extinction = frameSampleExtinction(localPosition, lod) * frameAbsorbance;\n\n if (extinction <= 0.0) {\n discard;\n }\n\n frameShadowDensity = vec4(extinction, 0.0, 0.0, 0.0);\n }\n`;class FrameShadowDensityMaterial extends Three__namespace.ShaderMaterial{name="FrameShadowDensityMaterial";constructor(e){super({glslVersion:Three__namespace.GLSL3,uniforms:{...e,...createVolumeUniforms(),frameRect:{value:new Three__namespace.Vector4},frameSliceStart:{value:0}},vertexShader:tileQuadVertexShader,fragmentShader:fragmentShader$3,depthTest:!1,depthWrite:!1,transparent:!0,blending:Three__namespace.CustomBlending,blendEquation:Three__namespace.AddEquation,blendSrc:Three__namespace.OneFactor,blendDst:Three__namespace.OneFactor,blendSrcAlpha:Three__namespace.OneFactor,blendDstAlpha:Three__namespace.OneFactor});}}
|
|
67
|
+
|
|
68
|
+
const frameShadowLightSlots=6;const vertexShader$2="\n void main() {\n gl_Position = vec4(position.xy, 0.0, 1.0);\n }\n",fragmentShader$2=`\n precision highp float;\n\n #define FRAME_SHADOW_SLOTS 6\n #define FRAME_MAX_SHADOW_STEPS 1024\n\n layout(location = 0) out highp vec4 frameTransmittance0;\n layout(location = 1) out highp vec4 frameTransmittance1;\n\n ${gridPars}\n ${noisePars}\n\n uniform sampler2D frameShadowDensityMap;\n uniform vec3 frameSkyDirection;\n uniform vec4 frameShadowLights[FRAME_SHADOW_SLOTS];\n uniform int frameShadowLightCount;\n\n float frameGridExtinction(vec3 worldPosition) {\n int cascade = frameFindCascade(worldPosition, 0);\n\n if (cascade < 0) {\n return 0.0;\n }\n\n return frameSampleGrid(frameShadowDensityMap, cascade, frameGridUvw(cascade, worldPosition)).r;\n }\n\n // NOTE Each step moves one voxel of the finest cascade at the probe, on the fastest axis of the direction.\n // The march goes to coarser cascades when it leaves a fine cascade.\n float frameMarch(vec3 origin, vec3 direction, float maxDistance, float jitter) {\n float opticalDepth = 0.0;\n float traveled = 0.0;\n vec3 probe = origin;\n\n for (int i = 0; i < FRAME_MAX_SHADOW_STEPS; i++) {\n int cascade = frameFindCascade(probe, 0);\n\n if (cascade < 0 || traveled >= maxDistance || opticalDepth > 6.0) {\n break;\n }\n\n vec3 voxelsPerUnit = abs(direction) / frameGridVoxel[cascade];\n float stepLength = 1.0 / max(voxelsPerUnit.x, max(voxelsPerUnit.y, voxelsPerUnit.z));\n\n stepLength = min(stepLength, maxDistance - traveled);\n\n opticalDepth += frameGridExtinction(probe + direction * stepLength * jitter) * stepLength;\n probe += direction * stepLength;\n traveled += stepLength;\n }\n\n return exp(-opticalDepth);\n }\n\n float frameLightTransmittance(int slot, vec3 worldPosition, float jitter) {\n if (slot >= frameShadowLightCount) {\n return 1.0;\n }\n\n vec4 light = frameShadowLights[slot];\n\n // NOTE w is 1.0 for a light direction, 2.0 for a light position\n if (light.w < 1.5) {\n return frameMarch(worldPosition, normalize(light.xyz), 1e20, jitter);\n }\n\n vec3 toLight = light.xyz - worldPosition;\n\n return frameMarch(worldPosition, normalize(toLight), length(toLight), jitter);\n }\n\n void main() {\n vec3 gridUvw;\n int cascade = frameGetGridCell(gl_FragCoord.xy, gridUvw);\n\n if (cascade >= frameGridCount) {\n frameTransmittance0 = vec4(1.0);\n frameTransmittance1 = vec4(1.0);\n\n return;\n }\n\n vec3 worldPosition = frameGridMin[cascade] + gridUvw * frameGridSize[cascade];\n\n // NOTE Skip voxels with no extinction near them. The lighting pass does not read them.\n float nearExtinction = 0.0;\n\n for (int i = 0; i < 8; i++) {\n vec3 corner = vec3(float(i & 1), float((i >> 1) & 1), float((i >> 2) & 1)) - 0.5;\n\n nearExtinction += frameGridExtinction(worldPosition + corner * frameGridVoxel[cascade]);\n }\n\n if (nearExtinction <= 0.0) {\n frameTransmittance0 = vec4(1.0);\n frameTransmittance1 = vec4(1.0);\n\n return;\n }\n\n float jitter = frameNoise(gl_FragCoord.xy);\n\n frameTransmittance0 = vec4(\n frameMarch(worldPosition, frameSkyDirection, 1e20, jitter),\n frameMarch(worldPosition, -frameSkyDirection, 1e20, jitter),\n frameLightTransmittance(0, worldPosition, jitter),\n frameLightTransmittance(1, worldPosition, jitter)\n );\n\n frameTransmittance1 = vec4(\n frameLightTransmittance(2, worldPosition, jitter),\n frameLightTransmittance(3, worldPosition, jitter),\n frameLightTransmittance(4, worldPosition, jitter),\n frameLightTransmittance(5, worldPosition, jitter)\n );\n }\n`;class FrameTransmittanceMaterial extends Three__namespace.ShaderMaterial{name="FrameTransmittanceMaterial";constructor(e){super({glslVersion:Three__namespace.GLSL3,uniforms:{...e,frameShadowDensityMap:{value:null},frameSkyDirection:{value:new Three__namespace.Vector3(0,1,0)},frameShadowLights:{value:Array.from({length:6},(()=>new Three__namespace.Vector4))},frameShadowLightCount:{value:0}},vertexShader:vertexShader$2,fragmentShader:fragmentShader$2,depthTest:!1,depthWrite:!1,blending:Three__namespace.NoBlending});}}
|
|
69
|
+
|
|
70
|
+
const vertexShader$1="\n void main() {\n gl_Position = vec4(position.xy, 0.0, 1.0);\n }\n",fragmentShader$1=`\n precision highp float;\n\n ${froxelPars}\n\n uniform sampler2D frameDensityAlbedoMap;\n uniform sampler2D frameDensityEmissionMap;\n uniform sampler2D frameDensityShadowMap0;\n uniform sampler2D frameDensityShadowMap1;\n uniform vec3 frameSkyDirection;\n uniform int frameShadowLightCount;\n uniform int frameShadowPointOffset;\n uniform int frameShadowSpotOffset;\n\n #include <common>\n #include <lights_pars_begin>\n #include <envmap_common_pars_fragment>\n #include <cube_uv_reflection_fragment>\n #include <envmap_physical_pars_fragment>\n\n float frameShadowSlot(int slot, vec4 shadow0, vec4 shadow1) {\n if (slot >= frameShadowLightCount) {\n return 1.0;\n }\n\n if (slot == 0) return shadow0.z;\n if (slot == 1) return shadow0.w;\n if (slot == 2) return shadow1.x;\n if (slot == 3) return shadow1.y;\n if (slot == 4) return shadow1.z;\n if (slot == 5) return shadow1.w;\n\n return 1.0;\n }\n\n void main() {\n ivec2 texel = ivec2(gl_FragCoord.xy);\n vec4 densityAlbedo = texelFetch(frameDensityAlbedoMap, texel, 0);\n float extinction = densityAlbedo.a;\n\n if (extinction <= 0.0) {\n discard;\n }\n\n vec4 densityEmission = texelFetch(frameDensityEmissionMap, texel, 0);\n\n vec2 tile = floor(gl_FragCoord.xy / froxelGrid.xy);\n float slice = tile.y * froxelColumns + tile.x;\n vec2 uv = (gl_FragCoord.xy - tile * froxelGrid.xy) / froxelGrid.xy;\n\n float sliceNear = frameSliceDepth(slice / froxelGrid.z);\n float sliceFar = frameSliceDepth((slice + 1.0) / froxelGrid.z);\n vec3 viewNear = frameGetViewPosition(uv, sliceNear);\n vec3 viewFar = frameGetViewPosition(uv, sliceFar);\n\n // NOTE Spot light angles use the extinction-weighted visible position of all volumes\n float visibleT = clamp(densityEmission.a / extinction, 0.0, 1.0);\n vec3 viewPosition = mix(viewNear, viewFar, visibleT);\n\n // NOTE Extinction-weighted mean transmittance. Each volume sampled its own visible position.\n vec4 shadow0 = texelFetch(frameDensityShadowMap0, texel, 0) / extinction;\n vec4 shadow1 = texelFetch(frameDensityShadowMap1, texel, 0) / extinction;\n\n vec3 skyDirection = normalize(mat3(viewMatrix) * frameSkyDirection);\n vec3 irradiance = getAmbientLightIrradiance(ambientLightColor);\n\n #if defined( USE_LIGHT_PROBES )\n irradiance += 0.5 * getLightProbeIrradiance(lightProbe, skyDirection) * shadow0.x;\n irradiance += 0.5 * getLightProbeIrradiance(lightProbe, -skyDirection) * shadow0.y;\n #endif\n\n #if defined( USE_ENVMAP )\n irradiance += 0.5 * getIBLIrradiance(skyDirection) * shadow0.x;\n irradiance += 0.5 * getIBLIrradiance(-skyDirection) * shadow0.y;\n #endif\n\n #if NUM_HEMI_LIGHTS > 0\n for (int i = 0; i < NUM_HEMI_LIGHTS; i++) {\n HemisphereLight hemiLight = hemisphereLights[i];\n\n irradiance += 0.5 * hemiLight.skyColor * shadow0.x;\n irradiance += 0.5 * hemiLight.groundColor * shadow0.y;\n }\n #endif\n\n #if NUM_DIR_LIGHTS > 0\n for (int i = 0; i < NUM_DIR_LIGHTS; i++) {\n irradiance += directionalLights[i].color * frameShadowSlot(i, shadow0, shadow1);\n }\n #endif\n\n // NOTE Point and spot lights have no distance falloff. This is the same as FogVolume.\n\n #if NUM_POINT_LIGHTS > 0\n for (int i = 0; i < NUM_POINT_LIGHTS; i++) {\n irradiance += pointLights[i].color * frameShadowSlot(frameShadowPointOffset + i, shadow0, shadow1);\n }\n #endif\n\n #if NUM_SPOT_LIGHTS > 0\n for (int i = 0; i < NUM_SPOT_LIGHTS; i++) {\n SpotLight spotLight = spotLights[i];\n float angleCos = dot(normalize(spotLight.position - viewPosition), spotLight.direction);\n float spotAttenuation = getSpotAttenuation(spotLight.coneCos, spotLight.penumbraCos, angleCos);\n\n irradiance += spotLight.color * spotAttenuation * frameShadowSlot(frameShadowSpotOffset + i, shadow0, shadow1);\n }\n #endif\n\n vec3 albedo = densityAlbedo.rgb / extinction;\n vec3 emission = densityEmission.rgb / extinction;\n vec3 radiance = albedo * irradiance * RECIPROCAL_PI + emission;\n\n float alpha = 1.0 - exp(-extinction * length(viewFar - viewNear));\n\n gl_FragColor = vec4(radiance * alpha, alpha);\n }\n`;class FrameLightingMaterial extends Three__namespace.MeshStandardMaterial{name="FrameLightingMaterial";customProgramCacheKey=()=>"FrameLightingMaterial";constructor(e){super(),this.frameUniforms={...e,frameDensityAlbedoMap:{value:null},frameDensityEmissionMap:{value:null},frameDensityShadowMap0:{value:null},frameDensityShadowMap1:{value:null},frameSkyDirection:{value:new Three__namespace.Vector3(0,1,0)},frameShadowLightCount:{value:0},frameShadowPointOffset:{value:0},frameShadowSpotOffset:{value:0}},this.depthTest=!1,this.depthWrite=!1,this.side=Three__namespace.DoubleSide,this.blending=Three__namespace.NoBlending,this.onBeforeCompile=e=>{Object.assign(e.uniforms,this.frameUniforms),e.vertexShader=vertexShader$1,e.fragmentShader=fragmentShader$1;};}}
|
|
71
|
+
|
|
72
|
+
const vertexShader="\n void main() {\n gl_Position = vec4(position.xy, 0.0, 1.0);\n }\n",fragmentShader=`\n precision highp float;\n\n layout(location = 0) out highp vec4 frameResolved;\n\n ${froxelPars}\n\n uniform sampler2D frameCurrentMap;\n uniform sampler2D frameHistoryMap;\n uniform vec2 frameAtlasSize;\n uniform bool frameUseHistory;\n uniform float frameTemporalBlend;\n uniform mat4 frameCameraMatrixWorld;\n uniform mat4 framePreviousViewMatrix;\n uniform mat4 framePreviousProjectionMatrix;\n uniform vec2 framePreviousRange;\n\n vec2 frameGetTileOrigin(float slice) {\n return vec2(mod(slice, froxelColumns), floor(slice / froxelColumns)) * froxelGrid.xy;\n }\n\n // NOTE Bilinear filter in the slice. Half a cell inside the tile, then the filter does not read other tiles.\n vec4 frameSampleHistory(float slice, vec2 uv) {\n vec2 cell = clamp(uv * froxelGrid.xy, vec2(0.5), froxelGrid.xy - 0.5);\n\n return texture(frameHistoryMap, (frameGetTileOrigin(slice) + cell) / frameAtlasSize);\n }\n\n void main() {\n ivec2 texel = ivec2(gl_FragCoord.xy);\n vec4 current = texelFetch(frameCurrentMap, texel, 0);\n\n vec2 tile = floor(gl_FragCoord.xy / froxelGrid.xy);\n float slice = tile.y * froxelColumns + tile.x;\n\n if (!frameUseHistory || slice >= froxelGrid.z) {\n frameResolved = current;\n\n return;\n }\n\n // NOTE Reproject the froxel center into the froxel grid of the previous frame\n vec2 cell = gl_FragCoord.xy - tile * froxelGrid.xy;\n vec2 uv = cell / froxelGrid.xy;\n float viewDepth = frameSliceDepth((slice + 0.5) / froxelGrid.z);\n vec4 worldPosition = frameCameraMatrixWorld * vec4(frameGetViewPosition(uv, viewDepth), 1.0);\n vec4 previousView = framePreviousViewMatrix * worldPosition;\n vec4 previousClip = framePreviousProjectionMatrix * previousView;\n\n if (previousClip.w <= 0.0) {\n frameResolved = current;\n\n return;\n }\n\n vec2 previousUv = previousClip.xy / previousClip.w * 0.5 + 0.5;\n float previousDepth = -previousView.z;\n float previousT = log(max(previousDepth, 1e-6) / framePreviousRange.x) / log(framePreviousRange.y / framePreviousRange.x);\n float previousSlice = previousT * froxelGrid.z - 0.5;\n\n bool outside = any(lessThan(previousUv, vec2(0.0))) || any(greaterThan(previousUv, vec2(1.0)));\n\n if (outside || previousSlice < -0.5 || previousSlice > froxelGrid.z - 0.5) {\n frameResolved = current;\n\n return;\n }\n\n float slice0 = clamp(floor(previousSlice), 0.0, froxelGrid.z - 1.0);\n float slice1 = min(slice0 + 1.0, froxelGrid.z - 1.0);\n vec4 history = mix(\n frameSampleHistory(slice0, previousUv),\n frameSampleHistory(slice1, previousUv),\n clamp(previousSlice - slice0, 0.0, 1.0)\n );\n\n // NOTE Clamp the history to the current neighbors. This limits ghosting of moving volumes.\n vec4 neighborMin = current;\n vec4 neighborMax = current;\n vec2 tileOrigin = tile * froxelGrid.xy;\n vec2 cellMax = froxelGrid.xy - 1.0;\n\n vec2 neighborCells[4];\n neighborCells[0] = vec2(max(cell.x - 1.0, 0.5), cell.y);\n neighborCells[1] = vec2(min(cell.x + 1.0, cellMax.x + 0.5), cell.y);\n neighborCells[2] = vec2(cell.x, max(cell.y - 1.0, 0.5));\n neighborCells[3] = vec2(cell.x, min(cell.y + 1.0, cellMax.y + 0.5));\n\n for (int i = 0; i < 4; i++) {\n vec4 neighbor = texelFetch(frameCurrentMap, ivec2(tileOrigin + neighborCells[i]), 0);\n\n neighborMin = min(neighborMin, neighbor);\n neighborMax = max(neighborMax, neighbor);\n }\n\n for (int i = -1; i <= 1; i += 2) {\n float neighborSlice = slice + float(i);\n\n if (neighborSlice >= 0.0 && neighborSlice < froxelGrid.z) {\n vec4 neighbor = texelFetch(frameCurrentMap, ivec2(frameGetTileOrigin(neighborSlice) + cell), 0);\n\n neighborMin = min(neighborMin, neighbor);\n neighborMax = max(neighborMax, neighbor);\n }\n }\n\n frameResolved = mix(clamp(history, neighborMin, neighborMax), current, frameTemporalBlend);\n }\n`;class FrameTemporalMaterial extends Three__namespace.ShaderMaterial{name="FrameTemporalMaterial";constructor(e){super({glslVersion:Three__namespace.GLSL3,uniforms:{...e,frameCurrentMap:{value:null},frameHistoryMap:{value:null},frameAtlasSize:{value:new Three__namespace.Vector2(1,1)},frameUseHistory:{value:!1},frameTemporalBlend:{value:.1},frameCameraMatrixWorld:{value:new Three__namespace.Matrix4},framePreviousViewMatrix:{value:new Three__namespace.Matrix4},framePreviousProjectionMatrix:{value:new Three__namespace.Matrix4},framePreviousRange:{value:new Three__namespace.Vector2(.1,1)}},vertexShader:vertexShader,fragmentShader:fragmentShader,depthTest:!1,depthWrite:!1,blending:Three__namespace.NoBlending});}}
|
|
73
|
+
|
|
74
|
+
const unitBox=new Three__namespace.Box3(new Three__namespace.Vector3(-.5,-.5,-.5),new Three__namespace.Vector3(.5,.5,.5)),viewBox=new Three__namespace.Box3,casterBox=new Three__namespace.Box3,cascadeBox=new Three__namespace.Box3,frustum=new Three__namespace.Frustum,projectionScreenMatrix=new Three__namespace.Matrix4,clipMatrix=new Three__namespace.Matrix4,viewport=new Three__namespace.Vector4,corner=new Three__namespace.Vector4,clearColor=new Three__namespace.Color,vector3=new Three__namespace.Vector3,lightProxyFactories={AmbientLight:()=>new Three__namespace.AmbientLight,HemisphereLight:()=>new Three__namespace.HemisphereLight,DirectionalLight:()=>new Three__namespace.DirectionalLight,PointLight:()=>new Three__namespace.PointLight,SpotLight:()=>new Three__namespace.SpotLight,LightProbe:()=>new Three__namespace.LightProbe};const createFullscreenGeometry=()=>{const e=new Three__namespace.BufferGeometry;return e.setAttribute("position",new Three__namespace.Float32BufferAttribute([-1,-1,0,3,-1,0,-1,3,0],3)),e};const tileQuadPosition=new Three__namespace.Float32BufferAttribute([-1,-1,0,1,-1,0,1,1,0,-1,-1,0,1,1,0,-1,1,0],3),createRenderTarget=(e,t,r={})=>new Three__namespace.WebGLRenderTarget(e,t,{type:Three__namespace.HalfFloatType,format:Three__namespace.RGBAFormat,minFilter:Three__namespace.LinearFilter,magFilter:Three__namespace.LinearFilter,generateMipmaps:!1,depthBuffer:!1,...r}),createScene=()=>{const e=new Three__namespace.Scene;return e.matrixWorldAutoUpdate=!1,e},getAtlasLayout=(e,t,r)=>{const a=Math.ceil(Math.sqrt(r*t/e));return {columns:a,width:e*a,height:t*Math.ceil(r/a)}};class FrameRenderer{settings={width:512,height:512,depth:24,subSteps:32,near:.1,far:1/0,jitter:!1,temporal:!0,temporalBlend:.2,lighting:"frame",shadowResolution:64,shadowCascades:3,shadowDistance:512};froxelUniforms={froxelGrid:{value:new Three__namespace.Vector3(1,1,1)},froxelColumns:{value:1},froxelRange:{value:new Three__namespace.Vector2(.1,1)},froxelProjectionInverse:{value:new Three__namespace.Matrix4},froxelSubSteps:{value:6},frameJitter:{value:.5}};froxelTileUniforms={frameTileSize:{value:new Three__namespace.Vector2(1,1)},frameTileColumns:{value:1},frameTargetSize:{value:new Three__namespace.Vector2(1,1)}};gridUniforms={frameGridMin:{value:Array.from({length:frameMaxShadowCascades},(()=>new Three__namespace.Vector3))},frameGridSize:{value:Array.from({length:frameMaxShadowCascades},(()=>new Three__namespace.Vector3))},frameGridVoxel:{value:Array.from({length:frameMaxShadowCascades},(()=>new Three__namespace.Vector3))},frameGridCount:{value:0},frameGridResolution:{value:1},frameGridColumns:{value:1},frameGridAtlasSize:{value:new Three__namespace.Vector2(1,1)},frameJitter:this.froxelUniforms.frameJitter};gridTileUniforms={frameTileSize:{value:new Three__namespace.Vector2(1,1)},frameTileColumns:{value:1},frameTargetSize:{value:new Three__namespace.Vector2(1,1)}};densityShadowUniforms={frameTransmittanceMap0:{value:null},frameTransmittanceMap1:{value:null},frameUseShadows:{value:!1},frameCameraMatrixWorld:{value:new Three__namespace.Matrix4}};injectMaterial=new FrameInjectMaterial(this.froxelUniforms);integrateMaterial=new FrameIntegrateMaterial(this.froxelUniforms);lightingMaterial=new FrameLightingMaterial(this.froxelUniforms);transmittanceMaterial=new FrameTransmittanceMaterial(this.gridUniforms);depthMaterial=new Three__namespace.MeshBasicMaterial({colorWrite:!1,side:Three__namespace.DoubleSide});injectScene=createScene();injectMesh=new Three__namespace.Mesh(createFullscreenGeometry(),this.injectMaterial);lightingMesh=new Three__namespace.Mesh(createFullscreenGeometry(),this.lightingMaterial);lightProxies={};densityScene=createScene();densityPool=[];shadowScene=createScene();shadowPool=[];transmittanceScene=createScene();transmittanceMesh=new Three__namespace.Mesh(createFullscreenGeometry(),this.transmittanceMaterial);temporalMaterial=new FrameTemporalMaterial(this.froxelUniforms);temporalScene=createScene();temporalMesh=new Three__namespace.Mesh(createFullscreenGeometry(),this.temporalMaterial);historyTargets=[null,null];historyIndex=0;historyValid=!1;historyCamera=null;previousViewMatrix=new Three__namespace.Matrix4;previousProjectionMatrix=new Three__namespace.Matrix4;previousRange=new Three__namespace.Vector2(.1,1);jitterIndex=0;atlasTarget=null;densityTarget=null;shadowDensityTarget=null;transmittanceTarget=null;depthTarget=null;cachedFrame=-1;cachedCamera=null;constructor(){[this.injectMesh,this.lightingMesh,this.transmittanceMesh,this.temporalMesh].forEach((e=>{e.frustumCulled=!1;})),this.temporalScene.add(this.temporalMesh),this.injectScene.add(this.injectMesh,this.lightingMesh),this.transmittanceScene.add(this.transmittanceMesh);}render(e,t,r,a){const i=e.info.render.frame;if(t.overrideMaterial)return void a.geometry.setDrawRange(0,0);if(i===this.cachedFrame&&r===this.cachedCamera)return void a.geometry.setDrawRange(0,0);const{volumes:s,casters:o}=this.collectVolumes(t,r);s.length>0&&this.renderFrame(e,t,r,s,o),this.cachedFrame=e.info.render.frame,this.cachedCamera=r,a.geometry.setDrawRange(0,s.length>0?1/0:0);}isFrameLighting(){return "frame"===this.settings.lighting}collectVolumes(e,t){const{settings:r}=this,a=[],i=[],s=this.isFrameLighting();projectionScreenMatrix.multiplyMatrices(t.projectionMatrix,t.matrixWorldInverse),frustum.setFromProjectionMatrix(projectionScreenMatrix);let o=1/0,n=0;return e.traverseVisible((e=>{e.isFrameVolume&&e.proxy.layers.test(t.layers)&&e.volumes.forEach((r=>{r.ready&&(r.matrixWorld.multiplyMatrices(e.matrixWorld,r.matrix),r.worldToLocal.copy(r.matrixWorld).invert(),r.worldBox.copy(unitBox).applyMatrix4(r.matrixWorld),s&&i.push({frameVolume:e,volume:r}),frustum.intersectsBox(r.worldBox)&&(r.localToView.multiplyMatrices(t.matrixWorldInverse,r.matrixWorld),r.viewToLocal.copy(r.localToView).invert(),viewBox.copy(unitBox).applyMatrix4(r.localToView),r.viewNear=-viewBox.max.z,r.viewFar=-viewBox.min.z,o=Math.min(o,r.viewNear),n=Math.max(n,r.viewFar),a.push({frameVolume:e,volume:r})));}));})),o=Math.max(o,r.near,t.near),n=Math.min(n,r.far,t.far),0===a.length||n<=o?{volumes:[],casters:[]}:(this.froxelUniforms.froxelRange.value.set(o,Math.max(n,1.0001*o)),{volumes:a,casters:i})}getSlice(e){const t=this.froxelUniforms.froxelRange.value;return this.froxelUniforms.froxelGrid.value.z*Math.log(Math.max(e,t.x)/t.x)/Math.log(t.y/t.x)}getSliceRange(e){const t=this.froxelUniforms.froxelGrid.value.z,r=Math.max(0,Math.floor(this.getSlice(e.viewNear)));return {first:r,count:Math.min(t-1,Math.ceil(this.getSlice(e.viewFar)))-r+1}}updateTargets(e){const{width:t,height:r,depth:a,subSteps:i,shadowResolution:s,shadowCascades:o}=this.settings,n=getAtlasLayout(t,r,a);if(!this.atlasTarget||this.atlasTarget.width!==n.width||this.atlasTarget.height!==n.height){const{maxTextureSize:t}=e.capabilities;(n.width>t||n.height>t)&&console.warn("FrameRenderer: froxel atlas is larger than maxTextureSize. Decrease width, height or depth."),this.atlasTarget&&this.atlasTarget.dispose(),this.atlasTarget=createRenderTarget(n.width,n.height),this.historyValid=!1;}if(this.froxelUniforms.froxelGrid.value.set(t,r,a),this.froxelUniforms.froxelColumns.value=n.columns,this.froxelUniforms.froxelSubSteps.value=Math.max(1,Math.round(i)),this.froxelTileUniforms.frameTileSize.value.set(t,r),this.froxelTileUniforms.frameTileColumns.value=n.columns,this.froxelTileUniforms.frameTargetSize.value.set(n.width,n.height),this.integrateMaterial.uniforms.froxelAtlas.value=this.atlasTarget.texture,this.settings.temporal&&this.historyTargets.forEach(((e,t)=>{e&&e.width===n.width&&e.height===n.height||(e&&e.dispose(),this.historyTargets[t]=createRenderTarget(n.width,n.height),this.historyValid=!1);})),this.integrateMaterial.uniforms.froxelAtlasSize.value.set(n.width,n.height),this.isFrameLighting()){this.densityTarget&&this.densityTarget.width===n.width&&this.densityTarget.height===n.height||(this.densityTarget&&this.densityTarget.dispose(),this.densityTarget=createRenderTarget(n.width,n.height,{count:4,minFilter:Three__namespace.NearestFilter,magFilter:Three__namespace.NearestFilter}));const e=Math.max(2,Math.round(s)),t=this.getCascadeCount(),r=getAtlasLayout(e,e,e*t);this.shadowDensityTarget&&this.shadowDensityTarget.width===r.width&&this.shadowDensityTarget.height===r.height||(this.shadowDensityTarget&&(this.shadowDensityTarget.dispose(),this.transmittanceTarget.dispose()),this.shadowDensityTarget=createRenderTarget(r.width,r.height,{format:Three__namespace.RedFormat}),this.transmittanceTarget=createRenderTarget(r.width,r.height,{count:2})),this.gridUniforms.frameGridResolution.value=e,this.gridUniforms.frameGridColumns.value=r.columns,this.gridUniforms.frameGridAtlasSize.value.set(r.width,r.height),this.gridTileUniforms.frameTileSize.value.set(e,e),this.gridTileUniforms.frameTileColumns.value=r.columns,this.gridTileUniforms.frameTargetSize.value.set(r.width,r.height);}e.getCurrentViewport(viewport);const h=Math.max(1,viewport.z),l=Math.max(1,viewport.w);this.depthTarget?this.depthTarget.width===h&&this.depthTarget.height===l||this.depthTarget.setSize(h,l):this.depthTarget=new Three__namespace.WebGLRenderTarget(h,l,{depthBuffer:!0,depthTexture:new Three__namespace.DepthTexture(h,l)}),this.integrateMaterial.uniforms.frameDepthMap.value=this.depthTarget.depthTexture;}renderFrame(e,t,r,a,i){const s=e.getRenderTarget(),o=e.getActiveCubeFace(),n=e.getActiveMipmapLevel(),h=e.autoClear,l=e.getClearAlpha(),m=e.shadowMap.autoUpdate,c=e.shadowMap.needsUpdate;e.getClearColor(clearColor),this.updateTargets(e),this.froxelUniforms.froxelProjectionInverse.value.copy(r.projectionMatrixInverse),this.integrateMaterial.uniforms.frameCameraNear.value=r.near,this.integrateMaterial.uniforms.frameCameraFar.value=r.far,e.autoClear=!1,e.shadowMap.autoUpdate=!1,e.shadowMap.needsUpdate=!1,e.setClearColor(0,0),this.froxelUniforms.frameJitter.value=this.settings.jitter?.618034*this.jitterIndex%1:-1,this.jitterIndex=(this.jitterIndex+1)%1024,this.renderDepth(e,t,r),this.isFrameLighting()?this.renderFrameLighting(e,t,r,a,i):this.renderInject(e,t,r,a),this.settings.temporal?this.renderTemporal(e,r):this.historyValid=!1,e.shadowMap.autoUpdate=m,e.shadowMap.needsUpdate=c,e.autoClear=h,e.setClearColor(clearColor,l),e.setRenderTarget(s,o,n);}renderTemporal(e,t){const r=this.temporalMaterial.uniforms,a=this.historyTargets[this.historyIndex],i=this.historyTargets[1-this.historyIndex];r.frameCurrentMap.value=this.atlasTarget.texture,r.frameHistoryMap.value=a.texture,r.frameAtlasSize.value.set(this.atlasTarget.width,this.atlasTarget.height),r.frameUseHistory.value=this.historyValid&&this.historyCamera===t,r.frameTemporalBlend.value=Math.min(1,Math.max(.01,this.settings.temporalBlend)),r.frameCameraMatrixWorld.value.copy(t.matrixWorld),r.framePreviousViewMatrix.value.copy(this.previousViewMatrix),r.framePreviousProjectionMatrix.value.copy(this.previousProjectionMatrix),r.framePreviousRange.value.copy(this.previousRange),this.temporalMesh.layers.mask=t.layers.mask,e.setRenderTarget(i),e.render(this.temporalScene,t),this.integrateMaterial.uniforms.froxelAtlas.value=i.texture,this.historyIndex=1-this.historyIndex,this.historyValid=!0,this.historyCamera=t,this.previousViewMatrix.copy(t.matrixWorldInverse),this.previousProjectionMatrix.copy(t.projectionMatrix),this.previousRange.copy(this.froxelUniforms.froxelRange.value);}renderDepth(e,t,r){const a=[];t.traverseVisible((e=>{if(!e.material)return;(Array.isArray(e.material)?e.material:[e.material]).every((e=>!1===e.depthWrite))&&a.push(e);})),a.forEach((e=>{e.visible=!1;}));const i=t.background,s=t.overrideMaterial;t.background=null,t.overrideMaterial=this.depthMaterial,e.setRenderTarget(this.depthTarget),e.clear(!1,!0,!1),e.render(t,r),t.overrideMaterial=s,t.background=i,a.forEach((e=>{e.visible=!0;}));}prepareLightScene(e,t,r){const{injectScene:a}=this;return a.environment=e.environment,a.environmentIntensity=e.environmentIntensity,a.environmentRotation.copy(e.environmentRotation),this.injectMesh.visible=r===this.injectMesh,this.lightingMesh.visible=r===this.lightingMesh,r.layers.mask=t.layers.mask,this.updateLightProxies(e,t)}renderInject(e,t,r,a){const{atlasTarget:i,injectScene:s}=this,{froxelGrid:o,froxelColumns:n}=this.froxelUniforms,h=this.injectMaterial.frameUniforms;this.prepareLightScene(t,r,this.injectMesh),i.scissorTest=!1,e.setRenderTarget(i),e.clear(!0,!1,!1);const l=n.value,m=o.value.y;a.forEach((({frameVolume:t,volume:a})=>{setVolumeUniforms(h,t,a),h.frameLights.value=t.lights,h.frameShadowSteps.value=t.shadowSteps;const{first:o,count:n}=this.getSliceRange(a),c=Math.floor(o/l),d=Math.floor((o+n-1)/l);i.scissor.set(0,c*m,i.width,(d-c+1)*m),i.scissorTest=!0,e.setRenderTarget(i),e.render(s,r);})),i.scissorTest=!1;}renderFrameLighting(e,t,r,a,i){const s=this.prepareLightScene(t,r,this.lightingMesh),o=i.length>0&&this.updateGrid(i,r);if(o){const t=[];for(let e=0;e<this.gridUniforms.frameGridCount.value;e++)i.forEach((r=>t.push({...r,cascade:e})));this.updatePool(this.shadowPool,this.shadowScene,t,r,((e,{frameVolume:t,volume:r,cascade:a})=>{const i=this.getGridRange(r,a);return !!i&&(setVolumeUniforms(e.material.uniforms,t,r),e.material.uniforms.frameRect.value.copy(i.rect),e.material.uniforms.frameSliceStart.value=i.first,e.geometry.instanceCount=i.count,!0)}),(()=>new FrameShadowDensityMaterial({...this.gridUniforms,...this.gridTileUniforms}))),e.setRenderTarget(this.shadowDensityTarget),e.clear(!0,!1,!1),e.render(this.shadowScene,r);const a=this.transmittanceMaterial.uniforms;a.frameShadowDensityMap.value=this.shadowDensityTarget.texture,a.frameSkyDirection.value.copy(s.skyDirection),a.frameShadowLightCount.value=s.shadowLights.length,s.shadowLights.forEach(((e,t)=>{a.frameShadowLights.value[t].copy(e);})),this.transmittanceMesh.layers.mask=r.layers.mask,e.setRenderTarget(this.transmittanceTarget),e.render(this.transmittanceScene,r);}const{densityShadowUniforms:n}=this;n.frameUseShadows.value=o,n.frameTransmittanceMap0.value=this.transmittanceTarget.textures[0],n.frameTransmittanceMap1.value=this.transmittanceTarget.textures[1],n.frameCameraMatrixWorld.value.copy(r.matrixWorld),this.updatePool(this.densityPool,this.densityScene,a,r,((e,{frameVolume:t,volume:a})=>{const i=this.getFroxelRect(a,r);if(!i)return !1;const{first:s,count:o}=this.getSliceRange(a);return setVolumeUniforms(e.material.uniforms,t,a),e.material.uniforms.frameRect.value.copy(i),e.material.uniforms.frameSliceStart.value=s,e.geometry.instanceCount=o,!0}),(()=>new FrameDensityMaterial({...this.froxelUniforms,...this.froxelTileUniforms,...this.gridUniforms,...this.densityShadowUniforms}))),e.setRenderTarget(this.densityTarget),e.clear(!0,!1,!1),e.render(this.densityScene,r);const h=this.lightingMaterial.frameUniforms;h.frameDensityAlbedoMap.value=this.densityTarget.textures[0],h.frameDensityEmissionMap.value=this.densityTarget.textures[1],h.frameDensityShadowMap0.value=this.densityTarget.textures[2],h.frameDensityShadowMap1.value=this.densityTarget.textures[3],h.frameSkyDirection.value.copy(s.skyDirection),h.frameShadowLightCount.value=s.shadowLights.length,h.frameShadowPointOffset.value=s.pointOffset,h.frameShadowSpotOffset.value=s.spotOffset,e.setRenderTarget(this.atlasTarget),e.clear(!0,!1,!1),e.render(this.injectScene,r);}updatePool(e,t,r,a,i,s){let o=0;r.forEach((r=>{if(!e[o]){const r=new Three__namespace.InstancedBufferGeometry;r.setAttribute("position",tileQuadPosition);const a=s(),i=new Three__namespace.Mesh(r,a);i.frustumCulled=!1,i.matrixAutoUpdate=!1,t.add(i),e[o]={mesh:i,material:a,geometry:r};}const n=e[o];i(n,r)&&(n.mesh.visible=!0,n.mesh.layers.mask=a.layers.mask,o++);}));for(let t=o;t<e.length;t++)e[t].mesh.visible=!1;}getFroxelRect(e,t){const{x:r,y:a}=this.froxelUniforms.froxelGrid.value;let i=1/0,s=1/0,o=-1/0,n=-1/0,h=!1;clipMatrix.multiplyMatrices(t.projectionMatrix,e.localToView);for(let e=0;e<8&&!h;e++)corner.set(1&e?.5:-.5,2&e?.5:-.5,4&e?.5:-.5,1).applyMatrix4(clipMatrix),corner.w<=1e-6?h=!0:(i=Math.min(i,corner.x/corner.w),s=Math.min(s,corner.y/corner.w),o=Math.max(o,corner.x/corner.w),n=Math.max(n,corner.y/corner.w));if(h)return new Three__namespace.Vector4(0,0,r,a);const l=Math.max(0,Math.floor((.5*i+.5)*r)-1),m=Math.max(0,Math.floor((.5*s+.5)*a)-1),c=Math.min(r,Math.ceil((.5*o+.5)*r)+1),d=Math.min(a,Math.ceil((.5*n+.5)*a)+1);return c<=l||d<=m?null:new Three__namespace.Vector4(l,m,c,d)}getCascadeCount(){return Math.min(frameMaxShadowCascades,Math.max(1,Math.round(this.settings.shadowCascades)))}updateGrid(e,t){const r=this.gridUniforms.frameGridResolution.value,a=this.getCascadeCount(),{frameGridMin:i,frameGridSize:s,frameGridVoxel:o,frameGridCount:n}=this.gridUniforms;if(casterBox.makeEmpty(),e.forEach((({volume:e})=>casterBox.union(e.worldBox))),casterBox.isEmpty())return !1;vector3.setFromMatrixPosition(t.matrixWorld);const h=Math.min(this.settings.shadowDistance,.5*Math.max(casterBox.max.x-casterBox.min.x,casterBox.max.y-casterBox.min.y,casterBox.max.z-casterBox.min.z));let l=0;for(let e=0;e<a;e++){if(e===a-1)cascadeBox.copy(casterBox);else {const t=h/Math.pow(2,a-1-e);cascadeBox.min.copy(vector3).subScalar(t),cascadeBox.max.copy(vector3).addScalar(t),cascadeBox.intersect(casterBox);}cascadeBox.isEmpty()||e<a-1&&cascadeBox.equals(casterBox)||(["x","y","z"].forEach((e=>{const t=Math.max(cascadeBox.max[e]-cascadeBox.min[e],1e-6),a=Math.pow(2,Math.ceil(Math.log2(t/(r-1))));o.value[l][e]=a,i.value[l][e]=Math.floor(cascadeBox.min[e]/a)*a,s.value[l][e]=a*r;})),l++);}return n.value=l,l>0}getGridRange(e,t){const r=this.gridUniforms.frameGridResolution.value,a=this.gridUniforms.frameGridMin.value[t],i=this.gridUniforms.frameGridVoxel.value[t],{min:s,max:o}=e.worldBox,n=(e,t,s)=>Math.min(r,Math.max(0,s((e-a[t])/i[t]))),h=n(s.x,"x",Math.floor),l=n(s.y,"y",Math.floor),m=n(s.z,"z",Math.floor),c=n(o.x,"x",Math.ceil),d=n(o.y,"y",Math.ceil),u=n(o.z,"z",Math.ceil);return c<=h||d<=l||u<=m?null:{rect:new Three__namespace.Vector4(h,l,c,d),first:t*r+m,count:u-m}}updateLightProxies(e,t){const r={},a=[],i=[],s=[],o=new Three__namespace.Vector3(0,1,0);let n=!1;return e.traverseVisible((e=>{if(!e.isLight||!lightProxyFactories[e.type]||!e.layers.test(t.layers))return;const h=r[e.type]||0,l=this.getLightProxy(e.type,h);r[e.type]=h+1,l.visible=!0,l.layers.mask=t.layers.mask,l.color.copy(e.color),l.intensity=e.intensity,l.matrixWorld.copy(e.matrixWorld),e.target&&l.target.matrixWorld.copy(e.target.matrixWorld),(e.isPointLight||e.isSpotLight)&&(l.distance=e.distance,l.decay=e.decay),e.isSpotLight&&(l.angle=e.angle,l.penumbra=e.penumbra),e.isHemisphereLight&&(l.groundColor.copy(e.groundColor),n||(o.setFromMatrixPosition(e.matrixWorld),n=o.lengthSq()>0)),e.isLightProbe&&l.sh.copy(e.sh);const m=(new Three__namespace.Vector3).setFromMatrixPosition(e.matrixWorld);if(e.isDirectionalLight){const t=(new Three__namespace.Vector3).setFromMatrixPosition(e.target.matrixWorld);a.push((new Three__namespace.Vector4).set(...m.sub(t).normalize().toArray(),1));}else e.isSpotLight?s.push((new Three__namespace.Vector4).set(...m.toArray(),2)):e.isPointLight&&i.push((new Three__namespace.Vector4).set(...m.toArray(),2));})),Object.entries(this.lightProxies).forEach((([e,t])=>{t.forEach(((t,a)=>{a>=(r[e]||0)&&(t.visible=!1);}));})),n||o.set(0,1,0),{skyDirection:o.normalize(),shadowLights:[...a,...i,...s].slice(0,frameShadowLightSlots),pointOffset:a.length,spotOffset:a.length+i.length}}getLightProxy(e,t){const r=this.lightProxies[e]||(this.lightProxies[e]=[]);if(!r[t]){const a=lightProxyFactories[e]();a.matrixAutoUpdate=!1,a.matrixWorldAutoUpdate=!1,a.castShadow=!1,a.target&&(a.target.matrixAutoUpdate=!1,a.target.matrixWorldAutoUpdate=!1),r[t]=a,this.injectScene.add(a);}return r[t]}dispose(){this.historyTargets.forEach((e=>e&&e.dispose())),this.historyTargets=[null,null],this.historyValid=!1,["atlasTarget","densityTarget","shadowDensityTarget","transmittanceTarget"].forEach((e=>{this[e]&&(this[e].dispose(),this[e]=null);})),this.depthTarget&&(this.depthTarget.depthTexture.dispose(),this.depthTarget.dispose(),this.depthTarget=null),[...this.densityPool,...this.shadowPool].forEach((({mesh:e,material:t,geometry:r})=>{e.parent.remove(e),t.dispose(),r.dispose();})),this.densityPool=[],this.shadowPool=[],[this.injectMaterial,this.integrateMaterial,this.lightingMaterial,this.transmittanceMaterial,this.temporalMaterial,this.depthMaterial].forEach((e=>e.dispose())),[this.injectMesh,this.lightingMesh,this.transmittanceMesh].forEach((e=>e.geometry.dispose())),this.cachedFrame=-1,this.cachedCamera=null;}}const frameRenderer=new FrameRenderer;
|
|
75
|
+
|
|
76
|
+
const allLights=Object.values(frameLights).reduce(((e,r)=>e|r),0),createTexture3D=(e,r,t)=>{const s=new Three__namespace.Data3DTexture(e,r.x,r.y,r.z);return s.format=t,s.minFilter=Three__namespace.LinearMipmapLinearFilter,s.magFilter=Three__namespace.LinearFilter,s.generateMipmaps=!0,s.unpackAlignment=1,s.needsUpdate=!0,s},sampleDensity=(e,r,t=1)=>e.isLevelSet?.()?e.isActive(r)?1:0:Three__namespace.MathUtils.clamp(e.getValue(r)/t,0,1),sampleColor=(e,r,t)=>{const s=e.getValue(r);return s instanceof Three__namespace.Color?t.copy(s):s instanceof openvdb.Vector3||s instanceof Three__namespace.Vector3?t.setRGB(s.x,s.y,s.z):t.setRGB(1,0,1),t};class FrameVolume extends Three__namespace.Group{isFrameVolume=!0;volumes=[];process=null;constructor(e,r={},t,s){super();const{resolution:o=100,progressive:a=!1,emissiveGrid:i,baseColorGrid:n,maskGrid:l,baseColor:c=16777215,scatterColor:m=0,extinction:p=100,densityScale:h=1,densityCutoff:d=0,absorbance:y=1,opacity:u=1,emissiveIntensity:f=1,emissiveScale:T=1,temperatureRange:w=[0,6500],offset3D:x=new Three__namespace.Vector3,wrap3D:v=Three__namespace.ClampToEdgeWrapping,lights:D=allLights,shadowSteps:M=32,bbox:g}=r;let C;this.baseColor=new Three__namespace.Color(c),this.scatterColor=new Three__namespace.Color(m),this.extinction=p,this.densityScale=h,this.densityCutoff=d,this.absorbance=y,this.opacity=u,this.emissiveIntensity=f,this.emissiveScale=T,this.temperatureRange=(new Three__namespace.Vector2).fromArray(w),this.offset3D=(new Three__namespace.Vector3).copy(x),this.wrap3D=v,this.lights=D,this.shadowSteps=M,this.proxy=new Three__namespace.Mesh(createFullscreenGeometry(),frameRenderer.integrateMaterial),this.proxy.frustumCulled=!1,this.proxy.renderOrder=1e6,this.proxy.onBeforeRender=(e,r,t)=>{frameRenderer.render(e,r,t,this.proxy);},this.add(this.proxy),C=e instanceof Array?e:void 0!==e.grids?Object.values(e.grids):[e],C=C.filter((e=>e!==i&&e!==n&&e!==l&&(e instanceof Uint8Array||e instanceof openvdb.GridDescriptor)));const b=[],V=[i,n,l].some((e=>e instanceof Uint8Array));C.forEach((e=>{const r={densityMap3D:null,emissiveMap3D:null,baseColorMap3D:null,maskMap3D:null,matrix:new Three__namespace.Matrix4,matrixWorld:new Three__namespace.Matrix4,localToView:new Three__namespace.Matrix4,viewToLocal:new Three__namespace.Matrix4,worldToLocal:new Three__namespace.Matrix4,worldBox:new Three__namespace.Box3,viewNear:0,viewFar:0,ready:!1},t=new Three__namespace.Box3(new Three__namespace.Vector3(-.5,-.5,-.5),new Three__namespace.Vector3(.5,.5,.5));g?t.copy(g):e instanceof openvdb.GridDescriptor&&t.set(...e.getPreciseWorldBbox());const s=t.getCenter(new Three__namespace.Vector3),c=t.getSize(new Three__namespace.Vector3).max(new Three__namespace.Vector3(1e-6,1e-6,1e-6));r.matrix.compose(s,new Three__namespace.Quaternion,c);const m=e instanceof Uint8Array||V?new Three__namespace.Vector3(o,o,o):c.clone().multiplyScalar(o/Math.max(c.x,c.y,c.z)).round().max(new Three__namespace.Vector3(1,1,1)),p=m.x*m.y*m.z;r.densityMap3D=createTexture3D(e instanceof Uint8Array?e:new Uint8Array(p),m,Three__namespace.RedFormat);const h={};i&&(h.emissive=i,r.emissiveMap3D=createTexture3D(i instanceof Uint8Array?i:new Uint8Array(p),m,Three__namespace.RedFormat)),n&&(h.baseColor=n,r.baseColorMap3D=createTexture3D(n instanceof Uint8Array?n:new Uint8Array(4*p),m,Three__namespace.RGBAFormat)),l&&(h.mask=l,r.maskMap3D=createTexture3D(l instanceof Uint8Array?l:new Uint8Array(p),m,Three__namespace.RedFormat)),r.ready=a,this.volumes.push(r),b.push({volume:r,voxelCount:p,task:this.convert(r,e,h,s,c,m)});}));let A=0,G=0,F=0;const U=b.length,k=b.reduce(((e,{voxelCount:r})=>e+r),0),R=()=>{const e=window.performance.now(),r=b[A];for(;r&&window.performance.now()-e<12;){const{value:e,done:t}=r.task.next();if(t)break;G+=e,F+=e;}if(r){const e=F>=r.voxelCount;(a||e)&&(o=r.volume,["densityMap3D","emissiveMap3D","baseColorMap3D","maskMap3D"].forEach((e=>{o[e]&&(o[e].needsUpdate=!0);}))),e&&(r.volume.ready=!0,A++,F=0);}var o;s&&s({convertedVoxels:G,totalVoxels:k,convertedGrids:A,totalGrids:U}),A<U?this.process=setTimeout(R,0):(this.process=null,t&&t());};R();}*convert(e,r,t,s,o,a){const{x:i,y:n,z:l}=a;if(r instanceof Uint8Array)return void(yield i*n*l);const c=e.densityMap3D.image.data,m=t.emissive instanceof openvdb.GridDescriptor&&e.emissiveMap3D.image.data,p=t.baseColor instanceof openvdb.GridDescriptor&&e.baseColorMap3D.image.data,h=t.mask instanceof openvdb.GridDescriptor&&e.maskMap3D.image.data,d=new Three__namespace.Vector3,y=new Three__namespace.Vector3,u=new Three__namespace.Color,f=e=>e.transform.applyInverseTransformMap(y.copy(d));for(let e=0;e<l;e++)for(let a=0;a<n;a++){for(let y=0;y<i;y++){const T=y+a*i+e*i*n;d.set((y+.5)/i-.5,(a+.5)/n-.5,(e+.5)/l-.5).multiply(o).add(s),c[T]=255*sampleDensity(r,f(r)),m&&(m[T]=255*sampleDensity(t.emissive,f(t.emissive),this.emissiveScale)),h&&(h[T]=255*sampleDensity(t.mask,f(t.mask))),p&&(sampleColor(t.baseColor,f(t.baseColor),u),p[4*T+0]=255*u.r,p[4*T+1]=255*u.g,p[4*T+2]=255*u.b,p[4*T+3]=255);}yield i;}}dispose(){this.process&&(clearTimeout(this.process),this.process=null),this.volumes.forEach((e=>{["densityMap3D","emissiveMap3D","baseColorMap3D","maskMap3D"].forEach((r=>{e[r]&&e[r].dispose();}));})),this.volumes=[],this.proxy.geometry.dispose();}}
|
|
77
|
+
|
|
78
|
+
const sharedParams=["baseColor","scatterColor","extinction","densityScale","densityCutoff","absorbance","opacity","emissiveIntensity","temperatureRange","wrap3D","lights","shadowSteps"],applyParam=(e,t,s)=>{e[t]instanceof Three__namespace.Color?e[t].set(s):e[t]instanceof Three__namespace.Vector2?e[t].fromArray(s):e[t]=s;};class FrameVolumeAnimation extends Three__namespace.Group{isFrameVolumeAnimation=!0;frames=[];loadedFrames=0;totalFrames=0;bbox=new Three__namespace.Box3;fps=24;loop=!0;playing=!1;disposed=!1;params={};currentFrame=0;time=0;get frame(){return this.currentFrame}set frame(e){this.currentFrame=Three__namespace.MathUtils.clamp(Math.round(e),0,Math.max(0,this.frames.length-1)),this.frames.forEach(((e,t)=>{e.visible=t===this.currentFrame;}));}addFrame(e){Object.entries(this.params).forEach((([t,s])=>applyParam(e,t,s))),e.visible=this.frames.length===this.currentFrame,this.frames.push(e),this.add(e);}play(){this.playing=!0;}pause(){this.playing=!1;}stop(){this.playing=!1,this.time=0,this.frame=0;}update(e){const t=this.loadedFrames;if(!this.playing||t<2||t<this.totalFrames)return;this.time+=e*this.fps;const s=Math.floor(this.time);if(s<1)return;this.time-=s;let a=this.currentFrame+s;a>=t&&(this.loop?a%=t:(a=t-1,this.playing=!1)),this.frame=a;}dispose(){this.disposed=!0,this.playing=!1,this.frames.forEach((e=>{this.remove(e),e.dispose();})),this.frames=[],this.loadedFrames=0;}}sharedParams.forEach((e=>{Object.defineProperty(FrameVolumeAnimation.prototype,e,{get(){return e in this.params?this.params[e]:this.frames[0]?.[e]},set(t){this.params[e]=t,this.frames.forEach((s=>applyParam(s,e,t)));}});}));
|
|
79
|
+
|
|
80
|
+
const gridOptions$1=["emissiveGrid","baseColorGrid","maskGrid"],findGrid=(e,o)=>Object.values(e.grids).find((e=>e.gridName===o)),isEmptyGrid=e=>{const{file_voxel_count:o,file_bbox_min:r,file_bbox_max:t}=e.metadata||{};return !(!o||0!==Number(o.value))||!!r&&!!t&&(r.value.x>t.value.x||r.value.y>t.value.y||r.value.z>t.value.z)};class VDBAnimationLoader extends Three__namespace.Loader{options={};constructor(e){super(e);}setOptions(e){return this.options={...e},this}load(e,o,r,t){const i=new FrameVolumeAnimation;return i.totalFrames=e.length,this.loadFrames(i,e,r).then((()=>{o&&!i.disposed&&o(i);})).catch((e=>{t?t(e):console.error(e);})),i}async loadFrames(e,o,r){const t=[];for(const r of o)if(t.push(await openvdb.loadVDB(r)),e.disposed)return;const i=t.map((e=>{const o={...this.options};return gridOptions$1.forEach((r=>{"string"==typeof o[r]&&(o[r]=findGrid(e,o[r]));})),o}));t.forEach(((o,r)=>{const t=gridOptions$1.map((e=>i[r][e]));Object.values(o.grids).filter((e=>e instanceof openvdb.GridDescriptor&&!t.includes(e)&&!isEmptyGrid(e))).forEach((o=>{e.bbox.union((new Three__namespace.Box3).set(...o.getPreciseWorldBbox()));}));}));for(let o=0;o<t.length;o++){if(await new Promise((r=>{e.addFrame(new FrameVolume(t[o],{...i[o],bbox:e.bbox},r));})),t[o]=null,e.disposed)return;e.loadedFrames++,r&&r({loadedFrames:e.loadedFrames,totalFrames:e.totalFrames});}}}
|
|
81
|
+
|
|
82
|
+
function wvdbKernel(e,t){const n=512,r={},a={},s={},l=e=>255*Math.min(Math.max(e,0),1)|0,o=(e,t)=>{if(1===t.length)return e.fill(t[0]);for(let n=0;n<e.length;n+=t.length)e.set(t,n);return e},i=(e,[t,r,a],s,l,o=1)=>{const i=Math.ceil(t/8),c=Math.ceil(r/8);for(let d=0;d<s.length;d++){const f=s[d],h=f%i*8,g=Math.floor(f/i)%c*8,u=8*Math.floor(f/(i*c));let p=d*n;for(let n=0;n<8;n++)for(let s=0;s<8;s++){const i=(g+s)*t+(u+n)*t*r,c=g+s<r&&u+n<a;for(let n=0;n<8;n++,p++)if(c&&h+n<t){const t=(h+n+i)*o,r=p*o;for(let n=0;n<o;n++)e[t+n]=l[r+n];}}}},c=e=>e.backgroundTexel.length;return {fillTexels:o,create:({readerId:e,config:t})=>{r[e]=t;},release:({readerId:e})=>{delete r[e],[a,s].forEach((t=>Object.keys(t).filter((t=>t.startsWith(`${e}:`))).forEach((e=>delete t[e]))));},chunk:async({readerId:d,gridIndex:f,lod:h,kind:g,firstIndex:u,bytes:p})=>{const m=r[d];if(!m)return null;const w=m.grids[f],y=w.lods[h],M=`${d}:${f}`,k=`${M}:${h}`,x=c(w),[b,I,A]=y.dimensions,T=await(async e=>{const t=new Blob([e]).stream().pipeThrough(new DecompressionStream("deflate"));return new Uint8Array(await new Response(t).arrayBuffer())})(p);a[k]||(a[k]={data:o(new Uint8Array(b*I*A*x),w.backgroundTexel),pagesLeft:y.pages,mask:null});const U=a[k];if("mask"===g)U.mask=t(T);else {const t=s[M];t&&U.pagesLeft===y.pages&&(((e,[t,n,r],a,[s,l,o],i)=>{const c=t*n,d=(e,t)=>{const n=new Int32Array(e),r=new Int32Array(e),a=new Float32Array(e);for(let s=0;s<e;s++){const l=Math.min(Math.max((s+.5)/e*t-.5,0),t-1);n[s]=Math.floor(l),r[s]=Math.min(n[s]+1,t-1),a[s]=l-n[s];}return {lower:n,upper:r,weight:a}},f=d(s,t),h=d(l,n),g=d(o,r);let u=0;for(let n=0;n<o;n++){const r=g.lower[n]*c,o=g.upper[n]*c,d=g.weight[n];for(let n=0;n<l;n++){const l=h.lower[n]*t,c=h.upper[n]*t,g=h.weight[n];for(let t=0;t<s;t++){const n=f.lower[t],s=f.upper[t],h=f.weight[t];for(let t=0;t<i;t++){const f=n=>e[n*i+t],p=f(n+l+r)+(f(s+l+r)-f(n+l+r))*h,m=f(n+c+r)+(f(s+c+r)-f(n+c+r))*h,w=f(n+l+o)+(f(s+l+o)-f(n+l+o))*h,y=p+(m-p)*g,M=w+(f(n+c+o)+(f(s+c+o)-f(n+c+o))*h-w)*g;a[u++]=y+(M-y)*d+.5;}}}}})(t.data,t.dimensions,U.data,y.dimensions,x),delete s[M],U.mask&&((e,t,r)=>{const a=c(t),s=o(new Uint8Array(n*a),t.backgroundTexel),l=Math.ceil(r[0]/8)*Math.ceil(r[1]/8)*Math.ceil(r[2]/8);for(let t=0;t<l;t++)e.mask[t>>>5]&1<<(31&t)||i(e.data,r,[t],s,a);})(U,w,y.dimensions));const{indices:r,values:a}=e(T,{layout:y.layout,bits:m.bits,firstIndex:u,channels:w.channels});i(U.data,y.dimensions,r,((e,t,{class:r,background:a,channels:s,scale:o})=>{if(3===s){const r=new Uint8Array(t*n*4);for(let a=0;a<t;a++){const t=a*n*3,s=a*n*4;for(let a=0;a<n;a++){for(let o=0;o<3;o++)r[s+4*a+o]=l(e[t+o*n+a]);r[s+4*a+3]=255;}}return r}const i=new Uint8Array(e.length),c="level set"===r;for(let t=0;t<e.length;t++)i[t]=c?Math.abs(e[t])<a?255:0:l(e[t]/o);return i})(a,r.length,w),x),U.pagesLeft--;}const $=0===U.pagesLeft;if(!$&&("mask"===g||1===y.pages))return null;if($){delete a[k];const e=w.lods[h+1];e&&e.pages>1&&(s[M]={data:U.data.slice(),dimensions:y.dimensions});}return {type:"texture",gridIndex:f,lod:h,complete:$,data:$?U.data:U.data.slice()}}}}
|
|
83
|
+
|
|
84
|
+
const{fillTexels:fillTexels}=wvdbKernel(openvdb.decodeWVDBPage,openvdb.decodeWVDBMask),workerSource=`\nconst kernel = (${wvdbKernel.toString()})(\n (${openvdb.decodeWVDBPage.toString()}),\n (${openvdb.decodeWVDBMask.toString()})\n);\nlet queue = Promise.resolve();\n\nself.onmessage = ({ data }) => {\n queue = queue.then(async () => {\n try {\n const result = await kernel[data.type](data.payload);\n\n self.postMessage({ result }, result && result.data ? [result.data.buffer] : []);\n } catch (error) {\n self.postMessage({ error: String((error && error.message) || error) });\n }\n });\n};\n`,createWorkerBackend=e=>{const t=new Worker(e),a=[];return t.onmessage=({data:e})=>{const t=a.shift();e.error?t?.reject(e.error):t?.resolve(e.result);},t.onerror=e=>{e.preventDefault(),a.splice(0).forEach((({reject:t})=>t(e.message||"Worker error")));},{request:(e,s,r=[])=>new Promise(((n,d)=>{a.push({resolve:n,reject:d}),t.postMessage({type:e,payload:s},r);}))}},createLocalBackend=()=>{const e=wvdbKernel(openvdb.decodeWVDBPage,openvdb.decodeWVDBMask);let t=Promise.resolve();return {request:(a,s)=>{const r=t.then((()=>e[a](s)));return t=r.catch((()=>null)),r}}};let backends=null,nextReaderId=0;const getBackend=e=>{if(!backends)if("undefined"!=typeof Worker&&"undefined"!=typeof Blob&&"undefined"!=typeof URL){const e=URL.createObjectURL(new Blob([workerSource],{type:"text/javascript"})),t="undefined"!=typeof navigator&&navigator.hardwareConcurrency||2;backends=Array.from({length:Math.min(Math.max(t-1,1),4)},(()=>createWorkerBackend(e)));}else backends=[createLocalBackend()];return backends[e%backends.length]},toByte=e=>255*Math.min(Math.max(e,0),1)|0,getBackgroundTexel=({class:e,background:t,channels:a=1},s)=>3===a?[...t.map(toByte),255]:["level set"===e?0:toByte(t/s)];class WVDBReader{url=null;header=null;grids={};finalLod=0;loadedLod=-1;bytesLoaded=0;bytesTotal=0;done=!1;cancelled=!1;dataStart=0;streamReader=null;constructor({maxLod:e=1/0,scales:t={}}={}){this.maxLod=e,this.scales=t,this.readerId=nextReaderId++,this.backend=getBackend(this.readerId);}async load(e,{onHeader:t,onUpdate:a,untilLod:s=1/0}={}){return this.url=e,await this.read(s,{onHeader:t,onUpdate:a}),this}async loadMore(e,{onUpdate:t}={}){if(!this.header)throw new Error("Call load() first.");return await this.read(e,{onUpdate:t}),this}async read(e,{onHeader:t,onUpdate:a}){if(this.cancelled||this.header&&this.loadedLod>=Math.min(e,this.finalLod))return;let s=null,r=0,n={};if(this.header){s=this.getChunks(e),r=this.dataStart+s[0].offset;const t=this.dataStart+Math.max(...s.map((({offset:e,length:t})=>e+t)));n={Range:`bytes=${r}-${t-1}`};}const d=await fetch(this.url,{headers:n}).catch((e=>{if(n.Range)return fetch(this.url);throw e}));if(!d.ok)throw new Error(`Could not load ${this.url} (${d.status}).`);if(!d.body)throw new Error("Streams are not supported.");const o=206===d.status?r:0;this.streamReader=d.body.getReader();let l=new Uint8Array(0),i=0,h=0;const c=[],u=e=>{if(i+e.length>l.length){const t=new Uint8Array(Math.max(2*l.length,i+e.length));t.set(l.subarray(0,i)),l=t;}l.set(e,i),i+=e.length;},g=()=>{for(;h<s.length;){const e=s[h],t=this.dataStart+e.offset-o;if(t+e.length>i)break;h++;const r=l.slice(t,t+e.length),n=this.backend.request("chunk",{readerId:this.readerId,gridIndex:e.gridIndex,lod:e.lod,kind:e.kind,firstIndex:e.firstIndex,bytes:r},[r.buffer]);c.push(n.then((t=>{this.cancelled||this.apply(e,t,a);})));}};for(;!this.cancelled;){const{done:r,value:n}=await this.streamReader.read();if(r||this.cancelled)break;if(u(n),this.bytesLoaded+=n.length,!this.header){const r=openvdb.readWVDBHeader(l.subarray(0,i));if(!r)continue;this.header=r.header,this.dataStart=r.dataStart,this.prepare(),s=this.getChunks(e),t&&t(this),s.length||(this.done=!0,this.loadedLod=this.finalLod,a&&a({reader:this,grid:null,lod:-1,lodComplete:!0,done:!0}));}if(g(),h===s.length){this.streamReader.cancel().catch((()=>{}));break}}if(!this.cancelled&&(!s||h<s.length))throw new Error("The WVDB file is incomplete.");await Promise.all(c),s.length&&(this.loadedLod=Math.max(...s.map((({lod:e})=>e)))),this.done=this.loadedLod>=this.finalLod,this.done&&this.release();}getChunks(e){return openvdb.getWVDBChunks(this.header,Math.min(e,this.finalLod)).filter((({lod:e})=>e>this.loadedLod))}prepare(){const{grids:e,bake:t}=this.header,a=({name:e})=>this.scales[e]||1;this.finalLod=Math.min(Math.max(Math.floor(this.maxLod),0),t.lods-1),this.bytesTotal=this.dataStart+openvdb.getWVDBChunks(this.header,this.finalLod).reduce(((e,{offset:t,length:a})=>Math.max(e,t+a)),0),e.forEach((e=>{const t=getBackgroundTexel(e,a(e));this.grids[e.name]={name:e.name,class:e.class,channels:e.channels||1,bbox:e.bbox,texelSize:t.length,dimensions:[1,1,1],data:fillTexels(new Uint8Array(t.length),t),lod:-1,chunksLeft:e.lods.map((({mask:e,pages:t})=>t.length+(e?1:0)))};})),this.backend.request("create",{readerId:this.readerId,config:{bits:t.bits,grids:e.map((e=>({class:e.class,background:e.background,channels:e.channels||1,scale:a(e),backgroundTexel:getBackgroundTexel(e,a(e)),lods:e.lods.map((({dimensions:e,mask:t,pages:a})=>({dimensions:e,layout:t?openvdb.wvdbLayouts.sparse:openvdb.wvdbLayouts.dense,pages:a.length})))})))}});}apply(e,t,a){const s=this.grids[this.header.grids[e.gridIndex].name],{lod:r}=e;s.chunksLeft[r]--,t&&(s.data=t.data,s.dimensions=this.header.grids[e.gridIndex].lods[r].dimensions,s.lod=r);const n=0===s.chunksLeft[r];a&&(t||n)&&a({reader:this,grid:s,lod:r,lodComplete:n,done:this.isComplete()});}isComplete(){return Object.values(this.grids).every((({chunksLeft:e})=>e.slice(0,this.finalLod+1).every((e=>0===e))))}release(){this.backend.request("release",{readerId:this.readerId}).catch((()=>{}));}cancel(){this.cancelled||(this.cancelled=!0,this.streamReader?.cancel().catch((()=>{})),this.release());}}
|
|
85
|
+
|
|
86
|
+
const textureKeys=["densityMap3D","emissiveMap3D","baseColorMap3D","maskMap3D"],updateTexture=(e,{data:t,dimensions:[s,r,a]})=>{const{image:d}=e,o=d.width!==s||d.height!==r||d.depth!==a;e.image={data:t,width:s,height:r,depth:a},o&&e.dispose(),e.needsUpdate=!0;};class WVDBVolumeData{grids={};bindings=[];constructor(e){this.reader=e;}readGrids(){Object.values(this.reader.grids).forEach((e=>{const{name:t,class:s,channels:r,bbox:a}=e;this.grids[t]={name:t,isLevelSet:"level set"===s,isColor:3===r,bbox:a?new Three__namespace.Box3(new Three__namespace.Vector3(...a[0]),new Three__namespace.Vector3(...a[1])):new Three__namespace.Box3,get data(){return e.data},get dimensions(){return e.dimensions},get resolution(){return e.dimensions[0]},get lod(){return e.lod},source:e};}));}get lod(){return Math.min(...Object.values(this.reader.grids).map((({lod:e})=>e)))}get lods(){return this.reader.finalLod+1}get done(){return this.reader.done}attach(e){const t=Object.values(this.grids);return [...e.volumes||[],...e.materials||[]].forEach((s=>{textureKeys.forEach((r=>{const a=s[r],d=a&&t.find((({data:e})=>e===a.image?.data));d&&this.bindings.push({object:e,texture:a,grid:d.source});}));})),e}detach(e){this.bindings=this.bindings.filter((t=>t.object!==e));}updateTextures(){this.bindings.forEach((({texture:e,grid:t})=>{e.image.data!==t.data&&updateTexture(e,t);}));}dispose(){this.reader.cancel(),this.bindings=[];}}class WVDBLoader extends Three__namespace.Loader{maxLod=1/0;scales={};constructor(e){super(e);}setMaxLod(e){return this.maxLod=e,this}setGridScales(e){return this.scales={...e},this}load(e,t,s,r){const a=new WVDBReader({maxLod:this.maxLod,scales:this.scales}),d=new WVDBVolumeData(a);let o=!1;return a.load(e,{onHeader:()=>d.readGrids(),onUpdate:e=>{d.updateTextures(),!o&&d.lod>=0&&(o=!0,t&&t(d)),o&&s&&s({lod:e.lod,lods:d.lods,lodComplete:e.lodComplete,done:e.done,bytesLoaded:a.bytesLoaded,bytesTotal:a.bytesTotal});}}).catch((e=>{a.cancelled||(r?r(e):console.error(e));})),d}}
|
|
87
|
+
|
|
88
|
+
const gridOptions=["emissiveGrid","baseColorGrid","maskGrid"];class WVDBAnimationLoader extends Three__namespace.Loader{options={};maxLod=1/0;concurrency=6;constructor(e){super(e);}setOptions(e){return this.options={...e},this}setMaxLod(e){return this.maxLod=e,this}setConcurrency(e){return this.concurrency=Math.max(1,e),this}load(e,o,a,t){const r=new FrameVolumeAnimation;return r.totalFrames=e.length,this.loadFrames(r,e,o,a).catch((e=>{r.disposed||(t?t(e):console.error(e));})),r}async loadFrames(e,o,a,t){const{emissiveGrid:r,emissiveScale:n=1}=this.options,i="string"==typeof r?{[r]:n}:{},s=o.map((()=>new WVDBReader({maxLod:this.maxLod,scales:i}))),m=s.map((e=>new WVDBVolumeData(e))),d=new Array(o.length);let l=0;const c=[],u=()=>(e.disposed&&s.forEach((e=>e.cancel())),e.disposed),h=e=>{c[e]=(c[e]||0)+1,t&&t({lod:e,loadedFrames:c[e],totalFrames:o.length});},p=async e=>{let a=0;await Promise.all(Array.from({length:Math.min(this.concurrency,o.length)},(async()=>{for(;a<o.length&&!u();)await e(a++);})));},f=e=>()=>{u()||m[e].updateTextures();};if(await p((async a=>{await s[a].load(o[a],{untilLod:0,onHeader:()=>m[a].readGrids(),onUpdate:f(a)}),u()||(d[a]=this.createFrame(m[a],e),(()=>{for(;l<d.length&&d[l];)e.addFrame(d[l]),l++,e.loadedFrames=l;})(),h(0));})),u())return;a&&a(e);const y=Math.max(0,...s.map((({finalLod:e})=>e)));for(let e=1;e<=y;e++)if(await p((async o=>{s[o].finalLod<e||(await s[o].loadMore(e,{onUpdate:f(o)}),u()||h(e));})),u())return}createFrame(e,o){const{grids:a}=e,t={...this.options};gridOptions.forEach((e=>{const o="string"==typeof t[e]?a[t[e]]:null;delete t[e],o&&(t[e]=o);}));const r=gridOptions.map((e=>t[e])).filter(Boolean),n=a.density&&!r.includes(a.density)&&a.density||Object.values(a).find((e=>!e.isColor&&!r.includes(e)));return n?(gridOptions.forEach((e=>{t[e]&&(t[e]=t[e].data);})),n.bbox.isEmpty()||o.bbox.union(n.bbox),e.attach(new FrameVolume(n.data,{...t,resolution:n.resolution,bbox:n.bbox}))):new FrameVolume(new Uint8Array(1),{resolution:1})}}
|
|
89
|
+
|
|
90
|
+
Object.defineProperty(exports, 'CloudVolume', {
|
|
91
|
+
enumerable: true,
|
|
92
|
+
get: function () { return openvdb.CloudVolume; }
|
|
93
|
+
});
|
|
94
|
+
Object.defineProperty(exports, 'CubeVolume', {
|
|
95
|
+
enumerable: true,
|
|
96
|
+
get: function () { return openvdb.CubeVolume; }
|
|
97
|
+
});
|
|
98
|
+
Object.defineProperty(exports, 'ParametricVolume', {
|
|
99
|
+
enumerable: true,
|
|
100
|
+
get: function () { return openvdb.ParametricVolume; }
|
|
101
|
+
});
|
|
102
|
+
Object.defineProperty(exports, 'SphereVolume', {
|
|
103
|
+
enumerable: true,
|
|
104
|
+
get: function () { return openvdb.SphereVolume; }
|
|
105
|
+
});
|
|
106
|
+
Object.defineProperty(exports, 'perlin3Noise', {
|
|
107
|
+
enumerable: true,
|
|
108
|
+
get: function () { return openvdb.perlin3Noise; }
|
|
109
|
+
});
|
|
110
|
+
Object.defineProperty(exports, 'simplex3Noise', {
|
|
111
|
+
enumerable: true,
|
|
112
|
+
get: function () { return openvdb.simplex3Noise; }
|
|
113
|
+
});
|
|
114
|
+
Object.defineProperty(exports, 'worley3Noise', {
|
|
115
|
+
enumerable: true,
|
|
116
|
+
get: function () { return openvdb.worley3Noise; }
|
|
117
|
+
});
|
|
118
|
+
exports.Bbox = VolumeToBbox;
|
|
119
|
+
exports.FogVolume = VolumeToFog;
|
|
120
|
+
exports.FrameVolume = FrameVolume;
|
|
121
|
+
exports.FrameVolumeAnimation = FrameVolumeAnimation;
|
|
122
|
+
exports.LevelSet = VolumeToLevelSet;
|
|
123
|
+
exports.MeshVolume = MeshVolume;
|
|
124
|
+
exports.Points = VolumeToPoints;
|
|
125
|
+
exports.VDBAnimationLoader = VDBAnimationLoader;
|
|
126
|
+
exports.VDBLoader = VDBLoader;
|
|
127
|
+
exports.VolumeBasicMaterial = VolumeBasicMaterial;
|
|
128
|
+
exports.VolumeNormalMaterial = VolumeNormalMaterial;
|
|
129
|
+
exports.WVDBAnimationLoader = WVDBAnimationLoader;
|
|
130
|
+
exports.WVDBLoader = WVDBLoader;
|
|
131
|
+
exports.WVDBReader = WVDBReader;
|
|
132
|
+
exports.frameLights = frameLights;
|
|
133
|
+
exports.frameRenderer = frameRenderer;
|
|
134
|
+
exports.lights = lights;
|
package/index.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import * as Three from 'three';
|
|
2
|
+
import { MathUtils, Vector3 } from 'three';
|
|
3
|
+
import { loadVDB, getUuid, GridDescriptor, Vector3 as Vector3$1, floatType, GridTransform, decodeWVDBPage, decodeWVDBMask, readWVDBHeader, getWVDBChunks, wvdbLayouts } from 'openvdb';
|
|
4
|
+
export { CloudVolume, CubeVolume, ParametricVolume, SphereVolume, perlin3Noise, simplex3Noise, worley3Noise } from 'openvdb';
|
|
5
|
+
import * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils.js';
|
|
6
|
+
|
|
7
|
+
class VDBLoader extends Three.Loader{constructor(e){super(e);}load(e,o,r,a){loadVDB(e).then((e=>{o(e);})).catch((e=>{a(e);}));}}
|
|
8
|
+
|
|
9
|
+
const Bbox=Three.Box3;
|
|
10
|
+
|
|
11
|
+
const sampleColors=[16711680,65280,255];class VolumeToBbox extends Three.Group{processes=[];constructor(e,{color:o,opacity:t}={}){let r;super(),r=e instanceof Array?e:void 0!==e.grids?Object.values(e.grids):[e];const s=new Three.Object3D,a=new Bbox;Object.values(r).forEach(((e,r)=>{const c=new Three.InstancedMesh(new Three.BoxGeometry(1,1,1),new Three.MeshBasicMaterial({wireframe:!0,color:o||sampleColors[r],transparent:!0,opacity:t}),e.leavesCount);let n=0;const i=o=>{o.isLeaf()&&(a.set(...e.getWorldBbox(o)),a.getCenter(s.position),a.getSize(s.scale),s.updateMatrix(),c.setMatrixAt(n++,s.matrix)),o.childMask?.countOn()>0&&o.childMask.forEachOn((({offset:e})=>{i(o.table[e]);}));};e.root.table.forEach((e=>i(e))),this.add(c);}));}}
|
|
12
|
+
|
|
13
|
+
const lights={useAmbientLights:32,usePointLights:16,useDirectionalLights:8,useSpotLights:4,useHemisphereLights:2,useEnvironment:1};
|
|
14
|
+
|
|
15
|
+
const vector2=new Three.Vector2,clearColor$1=new Three.Color;let depthRenderTarget,depthMaterial,depthBuffer,depthView,depthPacking=0,cachedFrame=-1,cachedCamera=null;class MeshDistanceMaterial extends Three.MeshBasicMaterial{name="MeshDistanceMaterial";customProgramCacheKey=()=>"MeshDistanceMaterial";_uniforms={};constructor(e){super(e),this._uniforms=e._uniforms||{},this.onBeforeCompile=e=>{Object.entries(this._uniforms).forEach((([r,a])=>{e.uniforms[r]=a;}));const r="\n #define DISTANCE\n\n varying mat4 mModelMatrix;\n varying mat4 mViewMatrix;\n varying mat4 mProjectionMatrix;\n varying mat4 mModelViewMatrix;\n ";e.vertexShader=e.vertexShader.replace("#include <common>",`\n ${r}\n\n out vec3 vPosition;\n\n #include <common>\n `).replace("#include <worldpos_vertex>","\n #include <worldpos_vertex>\n\n vPosition = worldPosition.xyz;\n\n mModelMatrix = modelMatrix;\n mViewMatrix = viewMatrix;\n mProjectionMatrix = projectionMatrix;\n mModelViewMatrix = modelViewMatrix;\n "),e.fragmentShader=e.fragmentShader.replace("#include <common>",`\n precision highp float;\n\n in vec3 vPosition;\n\n uniform float cameraNear;\n uniform float cameraFar;\n\n ${r}\n\n #include <packing>\n #include <common>\n `).replace("#include <opaque_fragment>","\n float dist = length(vPosition - cameraPosition);\n dist = (dist - cameraNear) / (cameraFar - cameraNear);\n dist = saturate(dist);\n \n gl_FragColor = packDepthToRGBA(dist);\n ").replace("#include <tonemapping_fragment>","").replace("#include <colorspace_fragment>","").replace("#include <fog_fragment>","").replace("#include <premultiplied_alpha_fragment>","").replace("#include <dithering_fragment>","");};}}const getDepthUniforms=()=>(depthRenderTarget||(depthRenderTarget=new Three.WebGLRenderTarget,depthRenderTarget.texture.minFilter=Three.NearestFilter,depthRenderTarget.texture.magFilter=Three.NearestFilter,depthRenderTarget.texture.generateMipmaps=!1,depthRenderTarget.depthTexture=new Three.DepthTexture,depthBuffer=depthRenderTarget.texture,depthPacking=1,depthMaterial=new MeshDistanceMaterial({_uniforms:{cameraPosition:{value:new Three.Vector3(0,0,0)},cameraNear:{value:1},cameraFar:{value:1}}}),depthView=new Three.Vector4(.1,1,1,1)),{depthScreenMap:{value:depthBuffer},depthPacking:{value:depthPacking},depthView:{value:depthView}});const updateFrameDepth=(e,r,a,t,n)=>{if(!depthBuffer)return;n._uniforms.cameraPosition.value.setFromMatrixPosition(a.matrixWorld),n._uniforms.cameraNear.value=a.near,n._uniforms.cameraFar.value=a.far;if((e?.info?.render?.frame||0)===cachedFrame&&a===cachedCamera)return;depthMaterial._uniforms.cameraPosition.value.setFromMatrixPosition(a.matrixWorld),depthMaterial._uniforms.cameraNear.value=a.near,depthMaterial._uniforms.cameraFar.value=a.far,e.getSize(vector2),depthRenderTarget.setSize(vector2.x,vector2.y),depthView.x=a.near,depthView.y=a.far,depthView.z=vector2.x,depthView.w=vector2.y;const i=[];r.traverseVisible((e=>{(e instanceof VolumeToFog||e.isFogVolume)&&(e.visible=!1,i.push(e));}));const o=r.background,l=e.getClearAlpha();e.getClearColor(clearColor$1),r.background=null,r.overrideMaterial=depthMaterial,e.setClearColor(16777215,1),e.setRenderTarget(depthRenderTarget),e.render(r,a),e.setRenderTarget(null),e.setClearColor(clearColor$1,l),r.overrideMaterial=null,r.background=o,i.forEach((e=>{e.visible=!0;})),cachedCamera=a,cachedFrame=e?.info?.render?.frame||0;};
|
|
16
|
+
|
|
17
|
+
class VolumeBasicMaterial extends Three.MeshStandardMaterial{name="VolumeBasicMaterial";isVolumetricFogMaterial=!0;customProgramCacheKey=()=>getUuid(this);_uniforms={baseColor:{value:new Three.Color(16777215)},scatterColor:{value:new Three.Color(0)},densityMap3D:{value:null},emissiveMap3D:{value:null},baseColorMap3D:{value:null},maskMap3D:{value:null},steps:{value:100},absorbance:{value:1},roughness:{value:.5},densityScale:{value:1},densityCutoff:{value:0},resolution:{value:100},offset3D:{value:new Three.Vector3(0,0,0)},wrap3D:{value:Three.ClampToEdgeWrapping},lights:{value:lights.useDirectionalLights|lights.usePointLights|lights.useSpotLights|lights.useHemisphereLights|lights.useEnvironment},cameraPosition:{value:new Three.Vector3(0,0,0)},cameraNear:{value:1},cameraFar:{value:1}};set baseColor(e){this._uniforms.baseColor.value.set(e);}get baseColor(){return this._uniforms.baseColor.value}set scatterColor(e){this._uniforms.scatterColor.value.set(e);}get scatterColor(){return this._uniforms.scatterColor.value}set densityMap3D(e){this._uniforms.densityMap3D.value=e,e.offset3D=this._uniforms.offset3D.value;}get densityMap3D(){return this._uniforms.densityMap3D.value}set emissiveMap3D(e){this._uniforms.emissiveMap3D.value=e,e.offset3D=this._uniforms.offset3D.value,this.needsUpdate=!0;}get emissiveMap3D(){return this._uniforms.emissiveMap3D.value}set maskMap3D(e){this._uniforms.maskMap3D.value=e,this.needsUpdate=!0;}get maskMap3D(){return this._uniforms.maskMap3D.value}set baseColorMap3D(e){this._uniforms.baseColorMap3D.value=e,e.offset3D=this._uniforms.offset3D.value,this.needsUpdate=!0;}get baseColorMap3D(){return this._uniforms.baseColorMap3D.value}set steps(e){this._uniforms.steps.value=e;}get steps(){return this._uniforms.steps.value}set absorbance(e){this._uniforms.absorbance.value=e;}get absorbance(){return this._uniforms.absorbance.value}set roughness(e){this._uniforms&&(this._uniforms.roughness.value=e);}get roughness(){return this._uniforms?this._uniforms.roughness.value:.5}set resolution(e){this._uniforms.resolution.value=e;}get resolution(){return this._uniforms.resolution.value}set offset3D(e){this._uniforms.offset3D.value.copy(e);}get offset3D(){return this._uniforms.offset3D.value}set wrap3D(e){this._uniforms.wrap3D.value=e,this.needsUpdate=!0;}get wrap3D(){return this._uniforms.wrap3D.value}set densityScale(e){this._uniforms.densityScale.value=e;}get densityScale(){return this._uniforms.densityScale.value}set densityCutoff(e){this._uniforms.densityCutoff.value=e;}get densityCutoff(){return this._uniforms.densityCutoff.value}set lights(e){this._uniforms.lights.value=e,this.needsUpdate=!0;}get lights(){return this._uniforms.lights.value}set useDirectionalLights(e=!0){e?this._uniforms.lights.value|=lights.useDirectionalLights:this._uniforms.lights.value&=~lights.useDirectionalLights,this.needsUpdate=!0;}get useDirectionalLights(){return 0!=(this._uniforms.lights.value&lights.useDirectionalLights)}set usePointLights(e=!0){e?this._uniforms.lights.value|=lights.usePointLights:this._uniforms.lights.value&=~lights.usePointLights,this.needsUpdate=!0;}get usePointLights(){return 0!=(this._uniforms.lights.value&lights.usePointLights)}set useHemisphereLights(e=!0){e?this._uniforms.lights.value|=lights.useHemisphereLights:this._uniforms.lights.value&=~lights.useHemisphereLights,this.needsUpdate=!0;}get useHemisphereLights(){return 0!=(this._uniforms.lights.value&lights.useHemisphereLights)}set useSpotLights(e=!0){e?this._uniforms.lights.value|=lights.useSpotLights:this._uniforms.lights.value&=~lights.useSpotLights,this.needsUpdate=!0;}get useSpotLights(){return 0!=(this._uniforms.lights.value&lights.useSpotLights)}set useEnvironment(e=!0){e?this._uniforms.lights.value|=lights.useEnvironment:this._uniforms.lights.value&=~lights.useEnvironment,this.needsUpdate=!0;}get useEnvironment(){return 0!=(this._uniforms.lights.value&lights.useEnvironment)}constructor(e={}){super(),this.side=Three.BackSide,this.depthWrite=!1,this.depthTest=!1,this.transparent=!0,Object.keys(this._uniforms).forEach((n=>{void 0!==e[n]&&null!==e[n]&&(this[n]=e[n],e[n]instanceof Three.Texture&&(e[n].offset3D=this._uniforms.offset3D.value));})),this.onBeforeCompile=(n,t)=>{const i=getDepthUniforms();Object.entries({...this._uniforms,...i}).forEach((([e,t])=>{n.uniforms[e]=t;}));const o=`\n #define VOLUME_BBOX_SPAN 0.5\n ${e.emissiveMap3D?"#define USE_EMISSIVE_GRID 1":""}\n ${e.baseColorMap3D?"#define USE_BASE_COLOR_GRID 1":""}\n ${e.maskMap3D?"#define USE_MASK_GRID 1":""}\n\n #define VOLUME_USE_ENVIRONMENT ${Number(this.useEnvironment)} != 0\n #define VOLUME_USE_HEMI_LIGHTS ${Number(this.useHemisphereLights)} != 0\n #define VOLUME_USE_POINT_LIGHTS ${Number(this.usePointLights)} != 0\n #define VOLUME_USE_DIR_LIGHTS ${Number(this.useDirectionalLights)} != 0\n #define VOLUME_USE_SPOT_LIGHTS ${Number(this.useSpotLights)} != 0\n `,a="\n varying mat4 mModelMatrix;\n varying mat4 mViewMatrix;\n varying mat4 mProjectionMatrix;\n varying mat4 mModelViewMatrix;\n varying mat4 mInverseModelViewMatrix;\n varying mat3 mInverseNormalMatrix;\n ";n.vertexShader=n.vertexShader.replace("#include <common>",`\n ${a}\n\n out vec3 vOrigin;\n out vec3 vDirection;\n out vec3 vPosition;\n\n #include <common>\n `).replace("#include <worldpos_vertex>","\n #include <worldpos_vertex>\n\n vOrigin = vec3(inverse(modelMatrix) * vec4(cameraPosition, 1.0)).xyz;\n vDirection = position - vOrigin;\n vPosition = position;\n\n mModelMatrix = modelMatrix;\n mViewMatrix = viewMatrix;\n mProjectionMatrix = projectionMatrix;\n mModelViewMatrix = modelViewMatrix;\n mInverseModelViewMatrix = inverse(modelViewMatrix);\n mInverseNormalMatrix = inverse(normalMatrix);\n ");const r=`\n vec2 getVolumeBbox(vec3 vPointOfReference) {\n const vec3 vBoxMin = vec3(-0.5);\n const vec3 vBoxMax = vec3(0.5);\n vec3 vInvPointOfReference = 1.0 / vPointOfReference;\n \n vec3 vMinRange = (-vOrigin - VOLUME_BBOX_SPAN) * vInvPointOfReference;\n vec3 vMaxRange = (-vOrigin + VOLUME_BBOX_SPAN) * vInvPointOfReference;\n \n vec3 vMin = min(vMinRange, vMaxRange);\n vec3 vMax = max(vMinRange, vMaxRange);\n\n return vec2(\n max(vMin.x, max(vMin.y, vMin.z)),\n min(vMax.x, min(vMax.y, vMax.z))\n );\n }\n\n vec3 radiation = vec3(0.);\n vec3 getBlackBodyRadiation(float temperature) {\n if (temperature == 0.) {\n return vec3(0.);\n }\n\n #ifndef USE_EMISSIVE_GRID\n return vec3(0.);\n #endif\n\n // NOTE Blackbody radiation source - https://www.shadertoy.com/view/4tdGWM\n float temperatureScaled = temperature * 1000.;\n\n radiation.r += 1. / (exp(19E3 * 1. / temperatureScaled) - 1.);\n radiation.g += 3.375 / (exp(19E3 * 1.5 / temperatureScaled) - 1.);\n radiation.b += 8. / (exp(19E3 * 2. / temperatureScaled) - 1.);\n\n return (radiation / max(radiation.r,max(radiation.g,radiation.b)));\n }\n\n float loopUV(float x) {\n if (abs(mod(floor(x), 2.)) == 1.) {\n return ceil(x) - x;\n } else {\n return x - floor(x);\n }\n }\n\n vec3 mapTextureSample0(vec3 position){\n vec3 uv = position + VOLUME_BBOX_SPAN;\n\n ${this.wrap3D===Three.RepeatWrapping?"return mod(uv, 1.);":""}\n ${0===this.wrap3D||this.wrap3D===Three.ClampToEdgeWrapping?"// NOTE Return UV":""}\n ${this.wrap3D===Three.MirroredRepeatWrapping?"\n uv.x = loopUV(uv.x);\n uv.y = loopUV(uv.y);\n uv.z = loopUV(uv.z);\n ":""}\n\n return uv;\n }\n\n vec3 mapTextureSample(vec3 position, vec3 offset){\n vec3 uv = position + VOLUME_BBOX_SPAN + offset;\n\n ${this.wrap3D===Three.RepeatWrapping?"return mod(uv, 1.);":""}\n ${0===this.wrap3D||this.wrap3D===Three.ClampToEdgeWrapping?"// NOTE Return UV":""}\n ${this.wrap3D===Three.MirroredRepeatWrapping?"\n uv.x = loopUV(uv.x);\n uv.y = loopUV(uv.y);\n uv.z = loopUV(uv.z);\n ":""}\n\n return uv;\n }\n\n float blendSample(float value) {\n return value * densityScale;\n \n // float fSmoothingThreshold = 0.1;\n // float fSmoothingBlend = 0.1;\n\n // return smoothstep(\n // fSmoothingThreshold - fSmoothingBlend,\n // fSmoothingThreshold + fSmoothingBlend,\n // value\n // ) * densityScale;\n }\n\n float clampedTexture(sampler3D source, vec3 position) {\n ${0===this.wrap3D||this.wrap3D===Three.ClampToEdgeWrapping?"\n vec3 vAbs = abs(position);\n\n if (max(vAbs.x, max(vAbs.y, vAbs.z)) > .5) {\n return 0.;\n }\n ":""}\n\n return texture(source, mapTextureSample(position, offset3D)).r;\n }\n\n float clampedMaskTexture(sampler3D source, vec3 position) {\n ${0===this.wrap3D||this.wrap3D===Three.ClampToEdgeWrapping?"\n vec3 vAbs = abs(position);\n\n if (max(vAbs.x, max(vAbs.y, vAbs.z)) > .5) {\n return 0.;\n }\n ":""}\n\n return texture(source, mapTextureSample0(position)).r;\n }\n\n vec3 clampedTextureRGB(sampler3D source, vec3 position) {\n return texture(source, mapTextureSample(position, offset3D)).rgb;\n }\n `;n.fragmentShader=n.fragmentShader.replace("#include <uv_pars_fragment>","// NOTE Override UV calculations").replace("#include <packing>","").replace("#include <common>",`\n precision highp float;\n precision highp sampler2D;\n precision highp sampler3D;\n\n in vec3 vOrigin;\n in vec3 vDirection;\n in vec3 vPosition;\n\n uniform sampler3D densityMap3D;\n uniform sampler3D emissiveMap3D;\n uniform sampler3D baseColorMap3D;\n uniform sampler3D maskMap3D;\n uniform vec3 offset3D;\n uniform int wrap3D;\n\n uniform float steps;\n uniform float absorbance;\n uniform float densityScale;\n uniform float densityCutoff;\n uniform vec3 baseColor;\n uniform vec3 scatterColor;\n uniform float resolution;\n\n uniform float cameraNear;\n uniform float cameraFar;\n uniform sampler2D depthScreenMap;\n uniform vec4 depthView;\n\n ${o}\n ${a}\n\n #include <common>\n #include <packing>\n #include <uv_pars_fragment>\n\n ${r}\n `).replace("#include <lights_fragment_begin>","// NOTE Override light calculations").replace("#include <lights_fragment_maps>","// NOTE Override light calculations").replace("#include <lights_fragment_end>","// NOTE Override light calculations").replace("#include <opaque_fragment>","\n vec3 vWorld = -vViewPosition;\n\n vec3 vRayDirection = normalize(vDirection);\n vec2 vBounds = getVolumeBbox(vRayDirection);\n vec2 vMaxBounds = getVolumeBbox(vOrigin);\n \n if (vBounds.x > vBounds.y) {\n discard;\n }\n\n vBounds.x = max(vBounds.x, 0.0);\n vMaxBounds.x = max(vMaxBounds.x, 0.0);\n\n // Volume movement\n\n vec3 vPoint = vOrigin + vBounds.x * vRayDirection;\n vec3 vPointStep = 1.0 / abs(vRayDirection);\n float delta = min(vPointStep.x, min(vPointStep.y, vPointStep.z)) / steps;\n vec3 vDirectionDeltaStep = vRayDirection * delta;\n\n // Density calculations\n\n float density = 0.0;\n vec3 albedo = vec3(0.);\n vec3 emissive = vec3(0.);\n float volumeSample;\n float emissiveSample;\n float maskSample = 1.;\n float noiseSample;\n float noiseFactor;\n vec3 fTest;\n\n // Light calculations\n \n vec3 lightAlbedo = vec3(0.);\n vec3 vLightProbe;\n vec3 lightDirection;\n vec3 vLightStep;\n vec3 vEnvMapScatter = vec3(0.0);\n float lightSample;\n float lightAbsorbance;\n float lightDistance;\n float stepAccumulation;\n\n // Utils\n\n float absorbanceDensityRatio = min(absorbance, densityScale); // NOTE When reducing densityScale, automatically reduce absorbance\n float inverseAbsorbance = 1.0 / absorbanceDensityRatio;\n float inverseDensityScale = 1.0 / densityScale;\n float eInverseAbsorbance = exp(-1. - inverseAbsorbance); // NOTE Wrong, but looks kinda better than exp(-inverseAbsorbance)\n float eDensityAbsorbance = exp(-absorbanceDensityRatio * delta);\n float eInverseDensityScale = exp(-1. - inverseDensityScale);\n\n // Depth Testing\n\n vec2 screenUV = gl_FragCoord.xy / depthView.zw;\n float worldDepth = unpackRGBAToDepth(texture2D(depthScreenMap, screenUV));\n float fogDepth = 0.;\n vec3 vProjPoint;\n\n \n float lightMarchLimit = resolution + 2.;\n int iLightMarchLimit = int(lightMarchLimit);\n\n vec3 vUnit = (mModelMatrix * vec4(1., 0., 0., 0.)).xyz;\n float vUnitLength = length(vUnit);\n\n vec3 iblIrradiance = vec3(0.0);\n \n\n vec3 lastNonSolidPoint = vec3(vPoint);\n\n for (float i = vBounds.x; i < vBounds.y; i += delta) {\n volumeSample = clampedTexture(densityMap3D, vPoint);\n \n #ifdef USE_MASK_GRID\n maskSample = clampedMaskTexture(maskMap3D, vPoint);\n #endif\n\n density += blendSample(volumeSample) * eDensityAbsorbance * maskSample;\n\n #ifdef USE_EMISSIVE_GRID\n emissiveSample = clampedTexture(emissiveMap3D, vPoint);\n emissive = max(emissive, density * vec3(emissiveSample));\n #endif\n\n if (density < 1.) {\n lastNonSolidPoint = vPoint;\n }\n\n if (density >= 1.) {\n break;\n }\n\n vProjPoint = (mModelMatrix * vec4(vPoint, 1.0)).xyz;\n fogDepth = length(vProjPoint - cameraPosition);\n fogDepth = (fogDepth - cameraNear) / (cameraFar - cameraNear);\n fogDepth = saturate(fogDepth);\n \n if (worldDepth < fogDepth) {\n break;\n }\n\n vPoint += vDirectionDeltaStep;\n }\n\n density = clamp(density, 0.0, 1.0);\n\n vPoint = lastNonSolidPoint;\n\n if (density <= densityCutoff) {\n discard;\n }\n\n if (density > 0.) {\n\n \n vec3 baseColorSample = baseColor.rgb;\n\n #ifdef USE_BASE_COLOR_GRID\n baseColorSample *= clampedTextureRGB(baseColorMap3D, vPoint);\n #endif\n\n albedo += ambientLightColor;\n \n\n #if VOLUME_USE_ENVIRONMENT\n \n vec3 viewDir = isOrthographic ? vec3(0.0, 0.0, 1.0) : normalize(vViewPosition);\n vec3 vUVCoords = normalize(lastNonSolidPoint * mInverseNormalMatrix).xyz;\n\n #if defined( USE_ENVMAP )\n vEnvMapScatter = getIBLIrradiance(vUVCoords) * RECIPROCAL_PI;\n\n albedo += density * getIBLRadiance(viewDir, vUVCoords, roughness) * RECIPROCAL_PI;\n #endif\n \n #endif\n\n #if NUM_HEMI_LIGHTS > 0 && VOLUME_USE_HEMI_LIGHTS\n \n lightAlbedo = vec3(0.);\n vec3 textureProbe;\n\n float absorbanceUp;\n float absorbanceDown;\n float stepAccumulationUp;\n float stepAccumulationDown;\n\n for (int lightIndex = 0; lightIndex < NUM_HEMI_LIGHTS; lightIndex++) {\n\n HemisphereLight hemiLight = hemisphereLights[lightIndex];\n vLightProbe = vec3(vPoint);\n\n absorbanceUp = 0.0;\n absorbanceDown = 0.0;\n\n stepAccumulationUp = 0.;\n stepAccumulationDown = 0.;\n\n lightDirection = -normalize((vec4(hemiLight.direction, 1.) * viewMatrix).xyz);\n vLightStep = (lightDirection * VOLUME_BBOX_SPAN) / lightMarchLimit;\n\n for (int lightMarch = 1; lightMarch < int(lightMarchLimit / 2.); lightMarch++) {\n if (absorbanceUp < 1.) {\n textureProbe = vLightProbe - float(lightMarch) * vLightStep;\n\n #ifdef USE_MASK_GRID\n maskSample = clampedMaskTexture(maskMap3D, textureProbe);\n #endif\n\n lightSample = clampedTexture(densityMap3D, textureProbe);\n\n absorbanceUp += lightSample * eInverseAbsorbance;\n stepAccumulationUp += 1.;\n }\n \n if (absorbanceDown < 1.) {\n textureProbe = vLightProbe + float(lightMarch) * vLightStep;\n\n #ifdef USE_MASK_GRID\n maskSample = clampedMaskTexture(maskMap3D, textureProbe);\n #endif\n\n lightSample = clampedTexture(densityMap3D, textureProbe);\n\n absorbanceDown += blendSample(lightSample) * eInverseAbsorbance;\n stepAccumulationDown += 1.;\n }\n }\n\n absorbanceUp = min(1., absorbanceUp);\n absorbanceDown = min(1., absorbanceDown);\n\n lightAlbedo += mix(\n (1. - absorbanceUp) * hemiLight.skyColor,\n (1. - absorbanceDown) * hemiLight.groundColor,\n 0.5\n );\n }\n\n lightAlbedo *= density;\n albedo += lightAlbedo * baseColor.rgb * RECIPROCAL_PI;\n \n #endif\n\n #if NUM_POINT_LIGHTS > 0 && VOLUME_USE_POINT_LIGHTS\n \n PointLight pointLight;\n lightAlbedo = vec3(0.);\n \n for (int lightIndex = 0; lightIndex < NUM_POINT_LIGHTS; lightIndex++) {\n pointLight = pointLights[lightIndex];\n\n vLightProbe = vec3(vPoint);\n lightAbsorbance = 0.;\n stepAccumulation = 1.;\n\n lightDirection = vPoint - (mInverseModelViewMatrix * vec4(pointLight.position, 1.)).xyz;\n lightDistance = length(lightDirection) * vUnitLength;\n lightDirection = normalize(lightDirection);\n vLightStep = (lightDirection * VOLUME_BBOX_SPAN) / lightMarchLimit;\n\n for (int lightMarch = 0; lightMarch < iLightMarchLimit; lightMarch++) {\n vLightProbe -= vLightStep;\n stepAccumulation += 1.;\n\n #ifdef USE_MASK_GRID\n maskSample = clampedMaskTexture(maskMap3D, vLightProbe);\n #endif\n \n lightSample = clampedTexture(densityMap3D, vLightProbe) * sqrt(lightDistance);\n \n lightAbsorbance += blendSample(lightSample) * eInverseAbsorbance;\n\n if (lightAbsorbance >= 1.) {\n lightAbsorbance = 1.;\n break;\n }\n }\n\n lightAlbedo += (1. - lightAbsorbance) * pointLight.color;\n }\n\n lightAlbedo *= density;\n albedo += lightAlbedo * baseColor.rgb * RECIPROCAL_PI;\n \n #endif\n\n #if NUM_DIR_LIGHTS > 0 && VOLUME_USE_DIR_LIGHTS\n \n DirectionalLight directionalLight;\n lightAlbedo = vec3(0.);\n\n for (int lightIndex = 0; lightIndex < NUM_DIR_LIGHTS; lightIndex++) {\n directionalLight = directionalLights[lightIndex];\n\n vLightProbe = vec3(vPoint);\n lightAbsorbance = 0.0;\n stepAccumulation = 1.;\n\n lightDirection = -normalize((vec4(directionalLight.direction, 1.) * viewMatrix).xyz);\n vLightStep = (lightDirection * VOLUME_BBOX_SPAN) / lightMarchLimit;\n\n for (int lightMarch = 0; lightMarch < iLightMarchLimit; lightMarch++) {\n vLightProbe -= vLightStep;\n stepAccumulation += 1.;\n\n #ifdef USE_MASK_GRID\n maskSample = clampedMaskTexture(maskMap3D, vLightProbe);\n #endif\n \n float lightSample = clampedTexture(densityMap3D, vLightProbe);\n\n lightAbsorbance += blendSample(lightSample) * eInverseAbsorbance;\n\n if (lightAbsorbance >= 1.) {\n lightAbsorbance = 1.;\n break;\n }\n }\n\n lightAlbedo += (1. - lightAbsorbance) * directionalLight.color;\n }\n\n lightAlbedo *= density;\n albedo += lightAlbedo * baseColor.rgb * RECIPROCAL_PI;\n \n #endif\n\n #if NUM_SPOT_LIGHTS > 0 && VOLUME_USE_SPOT_LIGHTS\n \n SpotLight spotLight;\n lightAlbedo = vec3(0.);\n\n float angleCos;\n float spotAttenuation;\n \n for (int lightIndex = 0; lightIndex < NUM_SPOT_LIGHTS; lightIndex++) {\n spotLight = spotLights[lightIndex];\n\n vLightProbe = vec3(vPoint);\n lightAbsorbance = 0.0;\n stepAccumulation = 1.;\n\n lightDirection = vPoint - (mInverseModelViewMatrix * vec4(spotLight.position, 1.)).xyz;\n lightDistance = length(lightDirection) * vUnitLength;\n lightDirection = normalize(lightDirection);\n vLightStep = (lightDirection * VOLUME_BBOX_SPAN) / lightMarchLimit;\n\n angleCos = dot(normalize(-lightDirection), normalize(mInverseNormalMatrix * spotLight.direction));\n spotAttenuation = smoothstep(spotLight.coneCos, spotLight.penumbraCos, angleCos);\n\n if (spotAttenuation > 0.) {\n for (int lightMarch = 0; lightMarch < iLightMarchLimit; lightMarch++) {\n vLightProbe -= vLightStep;\n stepAccumulation += 1.;\n\n #ifdef USE_MASK_GRID\n maskSample = clampedMaskTexture(maskMap3D, vLightProbe);\n #endif\n \n lightSample = clampedTexture(densityMap3D, vLightProbe);\n \n lightAbsorbance += blendSample(lightSample) * eInverseAbsorbance;\n\n if (lightAbsorbance >= 1.) {\n lightAbsorbance = 1.;\n break;\n }\n }\n\n lightAlbedo += (1. - lightAbsorbance) * spotAttenuation * spotLight.color;\n }\n }\n\n lightAlbedo *= density;\n albedo += lightAlbedo * baseColor.rgb * RECIPROCAL_PI;\n \n #endif\n\n albedo *= baseColorSample;\n }\n \n emissive = getBlackBodyRadiation(emissive.r);\n albedo += emissive;\n\n outgoingLight.rgb = max(max(scatterColor, vEnvMapScatter), albedo);\n diffuseColor.a = saturate(density) * opacity;\n\n #include <opaque_fragment>\n ");};}}
|
|
18
|
+
|
|
19
|
+
const getDensity$1=(t,e)=>t.isLevelSet?.()?t.isActive(e)?1:0:MathUtils.clamp(t.getValue(e),0,1);
|
|
20
|
+
|
|
21
|
+
const textureTypes={uint8:"uint8",half:"half",float:"float"};const textureTypeMap={[textureTypes.uint8]:Three.UnsignedByteType,[textureTypes.half]:Three.HalfFloatType,[textureTypes.float]:Three.FloatType};const getTextureScale=e=>e===textureTypes.half||e===textureTypes.float?1:255;const createTextureData=(e,t)=>e===textureTypes.half?new Uint16Array(t):e===textureTypes.float?new Float32Array(t):new Uint8Array(t);const readTexel=(e,t)=>e instanceof Uint16Array?Three.DataUtils.fromHalfFloat(e[t]):e[t];const writeTexel=(e,t,r)=>{e[t]=e instanceof Uint16Array?Three.DataUtils.toHalfFloat(r):r;};const createScalarTexture3D=(e,t,r)=>{const a=new Three.Data3DTexture(e,t,t,t);return a.format=Three.RedFormat,a.type=textureTypeMap[r]||Three.UnsignedByteType,a.minFilter=Three.LinearFilter,a.magFilter=Three.LinearFilter,a.unpackAlignment=1,a.needsUpdate=!0,a};
|
|
22
|
+
|
|
23
|
+
const textureBuilds={sample:"sample",leaves:"leaves"};const textureFilters={nearest:"nearest",trilinear:"trilinear",max:"max"};function leafTextureKernel(){const e=new Float32Array(1),t=new Uint32Array(e.buffer),n=n=>{e[0]=n;const r=t[0],o=r>>>16&32768;let l=(r>>>23&255)-127+15,s=8388607&r;return l>=31?31744|o:l<=0?l<-10?o:(s=(8388608|s)>>1-l,o|s+4096>>13):(s+=4096,8388608&s&&(s=0,l++),l>=31?31744|o:o|l<<10|s>>13)};return {create:({resolution:e,start:t,step:r,filter:o,background:l,leafLog2Dim:s})=>{const a=e*e,i=a*e,u="trilinear"===o,c="max"===o,f=1<<s,d=1<<3*s,h=Math.ceil(d/32);let p=null,g=null,x=0;const m=e=>p?p[x*h+(e>>5)]>>>(31&e)&1:Math.min(Math.max(g[x*d+e],0),1),y=new Float32Array(i),k=u?new Float32Array(i):null;u||0===l||y.fill(l);const w=[0,1,2].map((()=>({texels:new Int32Array(2*e+8),voxels:new Int32Array(2*e+8),weights:new Float32Array(2*e+8),count:0,scatter:new Int32Array(f)}))),M=(n,o,l)=>{const s=w[n],a=t[n],i=r[n],c=o+l;let f=(o-1-a)/i,d=(c+1-a)/i;f>d&&([f,d]=[d,f]);const h=Math.max(0,Math.floor(f)),p=Math.min(e-1,Math.ceil(d));let g=0;for(let e=h;e<=p;e++){const t=a+e*i;if(u){const n=Math.floor(t),r=t-n;n>=o&&n<c&&(s.texels[g]=e,s.voxels[g]=n-o,s.weights[g]=1-r,g++),n+1>=o&&n+1<c&&(s.texels[g]=e,s.voxels[g]=n+1-o,s.weights[g]=r,g++);}else {const n=Math.round(t);n>=o&&n<c&&(s.texels[g]=e,s.voxels[g]=n-o,s.weights[g]=1,g++);}}s.count=g;},v=(n,o)=>{const l=Math.round((o-t[n])/r[n]);return l>=0&&l<e?l:-1},b=(e,t,n)=>{u?(y[e]+=t*n,k[e]+=n):y[e]=c?Math.max(y[e],t):t;},C=(n,o,s,i,u)=>{if(u===l)return;M(0,n,i),M(1,o,i),M(2,s,i);const[f,d,h]=w;for(let t=0;t<h.count;t++)for(let n=0;n<d.count;n++){const r=d.texels[n]*e+h.texels[t]*a,o=d.weights[n]*h.weights[t];for(let e=0;e<f.count;e++)b(f.texels[e]+r,u,f.weights[e]*o);}if(!c)return;const p=[n,o,s].map(((n,o)=>{const l=Math.round((n-t[o])/r[o]),s=Math.round((n+i-1-t[o])/r[o]);return [Math.max(0,Math.min(l,s)),Math.min(e-1,Math.max(l,s))]}));for(let t=p[2][0];t<=p[2][1];t++)for(let n=p[1][0];n<=p[1][1];n++)for(let r=p[0][0];r<=p[0][1];r++){const o=r+n*e+t*a;y[o]=Math.max(y[o],u);}},L=(t,n,r)=>{M(0,t,f),M(1,n,f),M(2,r,f);const[o,i,u]=w,h=2*s;for(let t=0;t<u.count;t++)for(let n=0;n<i.count;n++){const r=i.texels[n]*e+u.texels[t]*a,c=i.voxels[n]<<s|u.voxels[t],f=i.weights[n]*u.weights[t];for(let e=0;e<o.count;e++){const t=m(o.voxels[e]<<h|c);t!==l&&b(o.texels[e]+r,t,o.weights[e]*f);}}if(c){[t,n,r].forEach(((e,t)=>{for(let n=0;n<f;n++)w[t].scatter[n]=v(t,e+n);}));for(let t=0;t<d;t++){const n=m(t),r=o.scatter[t>>h],c=i.scatter[t>>s&f-1],d=u.scatter[t&f-1];if(n!==l&&-1!==r&&-1!==c&&-1!==d){const t=r+c*e+d*a;y[t]=Math.max(y[t],n);}}}};return {addChunk:e=>{const{leafCount:t,origins:n,tileCount:r,tiles:o}=e;for(p=e.leafWords,g=e.leafValues,x=0;x<t;x++)L(n[3*x],n[3*x+1],n[3*x+2]);for(let e=0;e<r;e++){const t=5*e;C(o[t],o[t+1],o[t+2],o[t+3],o[t+4]);}p=null,g=null;},finish:({textureType:t,radius:r})=>{let o=y;if(u)for(let e=0;e<i;e++)o[e]+=Math.max(0,1-k[e])*l;if(r&&(o=((t,n)=>{const r=new Float32Array(i);for(let o=0;o<e;o++)for(let l=0;l<e;l++)for(let s=0;s<e;s++){const i=s+l*e+o*a,u=t[i];if(0!==u)for(let t=-n;t<n;t++)for(let c=-n;c<n;c++)for(let f=-n;f<n;f++){if(s+t<0||s+t>=e||l+c<0||l+c>=e||o+f<0||o+f>=e)continue;const d=i+t+c*e+f*a,h=Math.max(0,Math.min(1,1-Math.hypot(t,c,f)/(n/2)));r[d]=Math.min(r[d]+h*u,1);}}return r})(o,r)),"float"===t)return o;if("half"===t){const e=new Uint16Array(i);for(let t=0;t<i;t++)e[t]=n(o[t]);return e}const s=new Uint8Array(i);for(let e=0;e<i;e++)s[e]=Math.min(255*o[e],255);return s}}}}}const workerSource$1=`\nconst kernel = (${leafTextureKernel.toString()})();\nlet build = null;\n\nself.onmessage = ({ data }) => {\n if (data.type === 'create') {\n build = kernel.create(data.config);\n } else if (data.type === 'chunk') {\n build.addChunk(data.chunk);\n self.postMessage({ type: 'chunk' });\n } else if (data.type === 'finish') {\n const result = build.finish(data.options);\n build = null;\n self.postMessage({ type: 'result', result }, [result.buffer]);\n }\n};\n`;const canBuildFromLeaves=e=>e instanceof GridDescriptor&&!!e.root&&"number"==typeof e.root.background;const getDensity=(e,t,n)=>n?t?1:0:Math.min(Math.max(e,0),1);function*packChunks(e,t,n){const r=e.isLevelSet(),o=1<<3*t,l=Math.ceil(o/32),s=getDensity(e.root.background,!1,r);let a;const i=()=>{a={leafCount:0,origins:new Int32Array(3072),leafValues:r?null:new Float32Array(1024*o),leafWords:r?new Uint32Array(1024*l):null,tileCount:0,tiles:new Float64Array(5120)};},u=()=>1024===a.leafCount||1024===a.tileCount,c=(e,t,s,i)=>{const u=a.leafCount++;a.origins[3*u]=t,a.origins[3*u+1]=s,a.origins[3*u+2]=i,r?a.leafWords.set(e.valueMask.words,u*l):e.values.length===o?a.leafValues.set(e.values,u*o):a.leafValues.fill(n,u*o,(u+1)*o);},f=(e,t,n,o,l,i)=>{const u=getDensity(l,i,r);if(u===s)return;const c=5*a.tileCount++;a.tiles[c]=e,a.tiles[c+1]=t,a.tiles[c+2]=n,a.tiles[c+3]=o,a.tiles[c+4]=u;};function*d(t,n,o,l){const s=t.log2dim,h=t.total-t.log2dim,p=1<<h,g=(1<<s)-1,x=t.childMask.words,m=t.valueMask.words;for(let y=0;y<x.length;y++){const k=x[y];let w=k|(r?m[y]&~k:~k);for(;0!==w;){const r=31-Math.clz32(w&-w),x=y<<5|r;if(w&=w-1,k>>>r&1){const e=t.table[x],r=n+e.origin.x,s=o+e.origin.y,a=l+e.origin.z;e.isLeaf()?c(e,r,s,a):yield*d(e,r,s,a);}else f(n+((x>>2*s&g)<<h),o+((x>>s&g)<<h),l+((x&g)<<h),p,t.values[x]??e.root.background,1==(m[y]>>>r&1));u()&&(yield a,i());}}}i();for(const t of e.root.table){const{x:e,y:n,z:r}=t.origin;t.isInternalNode()?yield*d(t,e,n,r):(t.isLeaf()?c(t,e,n,r):f(e,n,r,t.dim,t.value,t.active),u()&&(yield a,i()));}(a.leafCount||a.tileCount)&&(yield a);}const getChunkTransfer=e=>[e.origins,e.leafValues,e.leafWords,e.tiles].filter(Boolean).map((e=>e.buffer)),createWorkerBackend$1=()=>{if("undefined"==typeof Worker||"undefined"==typeof Blob||"undefined"==typeof URL)return null;const e=URL.createObjectURL(new Blob([workerSource$1],{type:"text/javascript"})),t=new Worker(e),n=[],r=(e,r=[])=>new Promise(((o,l)=>{n.push({resolve:o,reject:l}),t.postMessage(e,r);}));return t.onmessage=({data:e})=>{n.shift()?.resolve(e.result);},t.onerror=e=>{e.preventDefault(),n.splice(0).forEach((({reject:t})=>t(e.message||"Worker error")));},{create:e=>t.postMessage({type:"create",config:e}),addChunk:e=>r({type:"chunk",chunk:e},getChunkTransfer(e)),finish:e=>r({type:"finish",options:e}),dispose:()=>{t.terminate(),URL.revokeObjectURL(e),n.splice(0).forEach((({resolve:e})=>e(null)));}}},createLocalBackend$1=()=>{const e=leafTextureKernel();let t=null,n=performance.now();const r=e=>performance.now()-n<12?Promise.resolve().then(e):new Promise((t=>setTimeout((()=>{n=performance.now(),t(e());}),0)));return {create:n=>{t=e.create(n);},addChunk:e=>r((()=>t?.addChunk(e))),finish:e=>r((()=>t?.finish(e)??null)),dispose:()=>{t=null;}}};const buildLeafTexture=({grid:e,resolution:t,start:n,step:r,filter:o=textureFilters.nearest,textureType:l,radius:s,onProgress:a})=>{const{leafLog2Dim:i}=e.treeLayout,u=e.isLevelSet(),c=e.leavesCount*(1<<3*i);let f=!1,d=null;const h=async h=>{d=h(),d.create({resolution:t,start:[n.x,n.y,n.z],step:[r.x,r.y,r.z],filter:o,background:getDensity(e.root.background,!1,u),leafLog2Dim:i});const p=[];let g=0;for(const t of packChunks(e,i,e.root.background)){if(f)return null;g+=t.leafCount<<3*i,p.push(d.addChunk(t)),p.length>=2&&await p.shift(),a&&a({convertedVoxels:g,totalVoxels:c});}return await Promise.all(p),f?null:d.finish({textureType:l,radius:s})};return {promise:h((()=>createWorkerBackend$1()||createLocalBackend$1())).catch((e=>f?null:(console.warn("buildLeafTexture","Worker failed, building on the main thread.",{error:e}),d?.dispose(),h(createLocalBackend$1)))).finally((()=>{d?.dispose();})),cancel:()=>{f=!0,d?.dispose();}}};
|
|
24
|
+
|
|
25
|
+
const getTextureMapping=(e,t)=>{const r=new Vector3,s=new Vector3,a=new Vector3,o=new Vector3,i=new Bbox;return i.set(...e.getPreciseWorldBbox()),i.getCenter(r),i.getSize(s),a.copy(s).multiplyScalar(1/t),e.transform.applyInverseJacobian(a),o.copy(r).sub(s.clone().multiplyScalar(.5)),e.transform.applyInverseTransformMap(o),o.add(a.clone().multiplyScalar(.5)),{center:r,size:s,start:o,step:a}};class VolumeToFog extends Three.Group{processes=[];leafBuilds=[];materials=[];isFogVolume=!0;constructor(e,t,r,s){super();const{resolution:a,progressive:o,emissiveGrid:i,baseColorGrid:n,maskGrid:l,radius:c,textureBuild:u=textureBuilds.sample,textureFilter:d=textureFilters.nearest,textureType:p=textureTypes.uint8}=t,m=getTextureScale(p);let h,x,f,T,y,w,g;if(this.frustumCulled=!1,h=e instanceof Array?e:void 0!==e.grids?Object.values(e.grids):[e],i)if(h=h.filter((e=>e!==i)),i instanceof Uint8Array)x=createScalarTexture3D(i,a,textureTypes.uint8);else {const e=createTextureData(p,Math.pow(a,3));x=createScalarTexture3D(e,a,p),f=(t,r,s)=>{const a=void 0!==s?s:getDensity$1(i,r)*m;return writeTexel(e,t,a),a};}if(n){if(n instanceof Uint8Array)T=new Three.Data3DTexture(n,a,a,a);else {const e=new Uint8Array(4*Math.pow(a,3));T=new Three.Data3DTexture(e,a,a,a);const t=new Three.Color;y=(r,s,a)=>{if(a)return e[4*r+0]=255*a.r,e[4*r+1]=255*a.g,e[4*r+2]=255*a.b,void(e[4*r+3]=255);const o=n.getValue(s);return o instanceof Three.Color?t.set(gridValue):o instanceof Vector3$1||o instanceof Three.Vector3?t.setRGB(o.x,o.y,o.z):t.setRGB(1,0,1),e[4*r+0]=255*t.r,e[4*r+1]=255*t.g,e[4*r+2]=255*t.b,e[4*r+3]=255,{r:t.r,g:t.g,b:t.b,a:t.a}};}T.format=Three.RGBAFormat,T.minFilter=Three.LinearFilter,T.magFilter=Three.LinearFilter,T.unpackAlignment=1,T.needsUpdate=!0;}if(l)if(l instanceof Uint8Array)w=createScalarTexture3D(l,a,textureTypes.uint8);else {const e=createTextureData(p,Math.pow(a,3));w=createScalarTexture3D(e,a,p),g=(t,r,s)=>{const a=void 0!==s?s:getDensity$1(l,r)*m;return writeTexel(e,t,a),a};}let D=0,v=0;const M=h.length,V=M*Math.pow(a,3),B=e=>!e||e instanceof Uint8Array||canBuildFromLeaves(e),F=u===textureBuilds.leaves&&h.every((e=>!(e instanceof GridDescriptor)||canBuildFromLeaves(e)))&&B(i)&&B(l)&&(!n||n instanceof Uint8Array);u!==textureBuilds.leaves||F||console.warn("VolumeToFog","textureBuild","Primitive or vector grids need the sample build. Using the sample build.");const b=[],U=[];let G=!1;const A=()=>{s&&s({convertedVoxels:U.reduce(((e,t)=>e+t.convertedVoxels),0),totalVoxels:U.reduce(((e,t)=>e+t.totalVoxels),0),convertedGrids:D,totalGrids:M});},S=(e,t,r,s)=>{const o={convertedVoxels:0,totalVoxels:0};U.push(o);const i=buildLeafTexture({grid:e,resolution:a,start:t.start,step:t.step,filter:d,textureType:p,radius:s,onProgress:({convertedVoxels:e,totalVoxels:t})=>{o.convertedVoxels=e,o.totalVoxels=t,A();}});return this.leafBuilds.push(i),i.promise.then((e=>!!e&&(r.image.data=e,r.needsUpdate=!0,!0))).catch((e=>(console.error("VolumeToFog","buildLeafTexture",{error:e}),!1)))};h.reverse().forEach((async(e,n)=>{if(e instanceof Uint8Array){const r=new Three.Data3DTexture(e,a,a,a);r.format=Three.RedFormat,r.minFilter=Three.LinearFilter,r.magFilter=Three.LinearFilter,r.unpackAlignment=1,r.needsUpdate=!0;const s=new Three.SphereGeometry(1),o=new VolumeBasicMaterial({...t,emissiveMap3D:x,densityMap3D:r,baseColorMap3D:T,maskMap3D:w}),i=new Three.Mesh(s,o);return i.frustumCulled=!1,i.onBeforeRender=updateFrameDepth,this.materials.push(o),void this.add(i)}if(!(e instanceof GridDescriptor))return;const u=createTextureData(p,Math.pow(a,3)),d=createScalarTexture3D(u,a,p),h=new Three.SphereGeometry(1),B=new VolumeBasicMaterial({...t,emissiveMap3D:x,densityMap3D:d,baseColorMap3D:T,maskMap3D:w}),U=new Three.Mesh(h,B);if(U.frustumCulled=!1,U.onBeforeRender=updateFrameDepth,this.materials.push(B),F){const t=getTextureMapping(e,a);return U.position.copy(t.center),U.scale.copy(t.size),o&&this.add(U),b.push(S(e,t,d,c).then((e=>{e&&(D++,o||this.add(U),A());}))),void(G||(G=!0,!i||i instanceof Uint8Array||b.push(S(i,t,x,0)),!l||l instanceof Uint8Array||b.push(S(l,t,w,0))))}const L=a,C=Math.pow(L,2);o&&this.add(U),await(t=>new Promise((a=>{const i=Math.pow(t,2),l=Math.pow(t,3),{center:p,size:h,start:B,step:F}=getTextureMapping(e,t);U.position.copy(p),U.scale.copy(h);let b=0,G=0,A=0;let S=function*(){for(let r=0;r<l;r++){const r=b+G*t+A*i,s=getDensity$1(e,B),a=f&&f(r,B),n=y&&y(r,B),l=g&&g(r,B),p=c;if(p)for(let e=-p;e<p;e++)for(let o=-p;o<p;o++)for(let i=-p;i<p;i++){if(b+e<0||b+e>=t||G+o<0||G+o>=t||A+i<0||A+i>=t)continue;const d=r+e+o*L+i*C,p=Math.max(0,Math.min(1,1-Math.hypot(e,o,i)/(c/2))),h=p*s*m;writeTexel(u,d,readTexel(u,d)+h),writeTexel(u,d,Math.min(readTexel(u,d),m)),f&&f(d,null,a*p*m),y&&y(d,null,n*p),g&&g(d,null,l*p*m);}else {const e=s*m;writeTexel(u,r,readTexel(u,r)+e),writeTexel(u,r,Math.min(readTexel(u,r),m));}if(v++,o&&(d.needsUpdate=!0,x&&(x.needsUpdate=!0),T&&(T.needsUpdate=!0),w&&(w.needsUpdate=!0)),A>=t){x.needsUpdate=!0,T.needsUpdate=!0,w.needsUpdate=!0;break}b++,B.x+=F.x,b>=t&&(b=0,B.x-=F.x*t,G++,B.y+=F.y),G>=t&&(G=0,B.y-=F.y*t,A++,B.z+=F.z),yield;}}();const z=()=>{let e=window.performance.now();for(;window.performance.now()-e<16&&S;){const{done:e}=S.next();s&&s({convertedVoxels:v,totalVoxels:V,convertedGrids:D,totalGrids:M}),e&&(D++,S=null,delete this.processes[n],a(),D===M&&(o||this.add(U),r&&r()));}S&&this.processes&&(this.processes[n]=setTimeout(z,0));};z();})))(a);})),F&&Promise.all(b).then((()=>{this.processes&&r&&r();}));}dispose(){this.leafBuilds.forEach((e=>e.cancel())),this.leafBuilds=[],this.processes&&(this.processes.forEach(((e,t)=>{cancelAnimationFrame(e),delete this.processes[t];})),this.processes=null);}}
|
|
26
|
+
|
|
27
|
+
const triangleLUT=[[],[0,8,3],[0,1,9],[1,8,3,9,8,1],[1,2,10],[0,8,3,1,2,10],[9,2,10,0,2,9],[2,8,3,2,10,8,10,9,8],[3,11,2],[0,11,2,8,11,0],[1,9,0,2,3,11],[1,11,2,1,9,11,9,8,11],[3,10,1,11,10,3],[0,10,1,0,8,10,8,11,10],[3,9,0,3,11,9,11,10,9],[9,8,10,10,8,11],[4,7,8],[4,3,0,7,3,4],[0,1,9,8,4,7],[4,1,9,4,7,1,7,3,1],[1,2,10,8,4,7],[3,4,7,3,0,4,1,2,10],[9,2,10,9,0,2,8,4,7],[2,10,9,2,9,7,2,7,3,7,9,4],[8,4,7,3,11,2],[11,4,7,11,2,4,2,0,4],[9,0,1,8,4,7,2,3,11],[4,7,11,9,4,11,9,11,2,9,2,1],[3,10,1,3,11,10,7,8,4],[1,11,10,1,4,11,1,0,4,7,11,4],[4,7,8,9,0,11,9,11,10,11,0,3],[4,7,11,4,11,9,9,11,10],[9,5,4],[9,5,4,0,8,3],[0,5,4,1,5,0],[8,5,4,8,3,5,3,1,5],[1,2,10,9,5,4],[3,0,8,1,2,10,4,9,5],[5,2,10,5,4,2,4,0,2],[2,10,5,3,2,5,3,5,4,3,4,8],[9,5,4,2,3,11],[0,11,2,0,8,11,4,9,5],[0,5,4,0,1,5,2,3,11],[2,1,5,2,5,8,2,8,11,4,8,5],[10,3,11,10,1,3,9,5,4],[4,9,5,0,8,1,8,10,1,8,11,10],[5,4,0,5,0,11,5,11,10,11,0,3],[5,4,8,5,8,10,10,8,11],[9,7,8,5,7,9],[9,3,0,9,5,3,5,7,3],[0,7,8,0,1,7,1,5,7],[1,5,3,3,5,7],[9,7,8,9,5,7,10,1,2],[10,1,2,9,5,0,5,3,0,5,7,3],[8,0,2,8,2,5,8,5,7,10,5,2],[2,10,5,2,5,3,3,5,7],[7,9,5,7,8,9,3,11,2],[9,5,7,9,7,2,9,2,0,2,7,11],[2,3,11,0,1,8,1,7,8,1,5,7],[11,2,1,11,1,7,7,1,5],[9,5,8,8,5,7,10,1,3,10,3,11],[5,7,0,5,0,9,7,11,0,1,0,10,11,10,0],[11,10,0,11,0,3,10,5,0,8,0,7,5,7,0],[11,10,5,7,11,5],[10,6,5],[0,8,3,5,10,6],[9,0,1,5,10,6],[1,8,3,1,9,8,5,10,6],[1,6,5,2,6,1],[1,6,5,1,2,6,3,0,8],[9,6,5,9,0,6,0,2,6],[5,9,8,5,8,2,5,2,6,3,2,8],[2,3,11,10,6,5],[11,0,8,11,2,0,10,6,5],[0,1,9,2,3,11,5,10,6],[5,10,6,1,9,2,9,11,2,9,8,11],[6,3,11,6,5,3,5,1,3],[0,8,11,0,11,5,0,5,1,5,11,6],[3,11,6,0,3,6,0,6,5,0,5,9],[6,5,9,6,9,11,11,9,8],[5,10,6,4,7,8],[4,3,0,4,7,3,6,5,10],[1,9,0,5,10,6,8,4,7],[10,6,5,1,9,7,1,7,3,7,9,4],[6,1,2,6,5,1,4,7,8],[1,2,5,5,2,6,3,0,4,3,4,7],[8,4,7,9,0,5,0,6,5,0,2,6],[7,3,9,7,9,4,3,2,9,5,9,6,2,6,9],[3,11,2,7,8,4,10,6,5],[5,10,6,4,7,2,4,2,0,2,7,11],[0,1,9,4,7,8,2,3,11,5,10,6],[9,2,1,9,11,2,9,4,11,7,11,4,5,10,6],[8,4,7,3,11,5,3,5,1,5,11,6],[5,1,11,5,11,6,1,0,11,7,11,4,0,4,11],[0,5,9,0,6,5,0,3,6,11,6,3,8,4,7],[6,5,9,6,9,11,4,7,9,7,11,9],[10,4,9,6,4,10],[4,10,6,4,9,10,0,8,3],[10,0,1,10,6,0,6,4,0],[8,3,1,8,1,6,8,6,4,6,1,10],[1,4,9,1,2,4,2,6,4],[3,0,8,1,2,9,2,4,9,2,6,4],[0,2,4,4,2,6],[8,3,2,8,2,4,4,2,6],[10,4,9,10,6,4,11,2,3],[0,8,2,2,8,11,4,9,10,4,10,6],[3,11,2,0,1,6,0,6,4,6,1,10],[6,4,1,6,1,10,4,8,1,2,1,11,8,11,1],[9,6,4,9,3,6,9,1,3,11,6,3],[8,11,1,8,1,0,11,6,1,9,1,4,6,4,1],[3,11,6,3,6,0,0,6,4],[6,4,8,11,6,8],[7,10,6,7,8,10,8,9,10],[0,7,3,0,10,7,0,9,10,6,7,10],[10,6,7,1,10,7,1,7,8,1,8,0],[10,6,7,10,7,1,1,7,3],[1,2,6,1,6,8,1,8,9,8,6,7],[2,6,9,2,9,1,6,7,9,0,9,3,7,3,9],[7,8,0,7,0,6,6,0,2],[7,3,2,6,7,2],[2,3,11,10,6,8,10,8,9,8,6,7],[2,0,7,2,7,11,0,9,7,6,7,10,9,10,7],[1,8,0,1,7,8,1,10,7,6,7,10,2,3,11],[11,2,1,11,1,7,10,6,1,6,7,1],[8,9,6,8,6,7,9,1,6,11,6,3,1,3,6],[0,9,1,11,6,7],[7,8,0,7,0,6,3,11,0,11,6,0],[7,11,6],[7,6,11],[3,0,8,11,7,6],[0,1,9,11,7,6],[8,1,9,8,3,1,11,7,6],[10,1,2,6,11,7],[1,2,10,3,0,8,6,11,7],[2,9,0,2,10,9,6,11,7],[6,11,7,2,10,3,10,8,3,10,9,8],[7,2,3,6,2,7],[7,0,8,7,6,0,6,2,0],[2,7,6,2,3,7,0,1,9],[1,6,2,1,8,6,1,9,8,8,7,6],[10,7,6,10,1,7,1,3,7],[10,7,6,1,7,10,1,8,7,1,0,8],[0,3,7,0,7,10,0,10,9,6,10,7],[7,6,10,7,10,8,8,10,9],[6,8,4,11,8,6],[3,6,11,3,0,6,0,4,6],[8,6,11,8,4,6,9,0,1],[9,4,6,9,6,3,9,3,1,11,3,6],[6,8,4,6,11,8,2,10,1],[1,2,10,3,0,11,0,6,11,0,4,6],[4,11,8,4,6,11,0,2,9,2,10,9],[10,9,3,10,3,2,9,4,3,11,3,6,4,6,3],[8,2,3,8,4,2,4,6,2],[0,4,2,4,6,2],[1,9,0,2,3,4,2,4,6,4,3,8],[1,9,4,1,4,2,2,4,6],[8,1,3,8,6,1,8,4,6,6,10,1],[10,1,0,10,0,6,6,0,4],[4,6,3,4,3,8,6,10,3,0,3,9,10,9,3],[10,9,4,6,10,4],[4,9,5,7,6,11],[0,8,3,4,9,5,11,7,6],[5,0,1,5,4,0,7,6,11],[11,7,6,8,3,4,3,5,4,3,1,5],[9,5,4,10,1,2,7,6,11],[6,11,7,1,2,10,0,8,3,4,9,5],[7,6,11,5,4,10,4,2,10,4,0,2],[3,4,8,3,5,4,3,2,5,10,5,2,11,7,6],[7,2,3,7,6,2,5,4,9],[9,5,4,0,8,6,0,6,2,6,8,7],[3,6,2,3,7,6,1,5,0,5,4,0],[6,2,8,6,8,7,2,1,8,4,8,5,1,5,8],[9,5,4,10,1,6,1,7,6,1,3,7],[1,6,10,1,7,6,1,0,7,8,7,0,9,5,4],[4,0,10,4,10,5,0,3,10,6,10,7,3,7,10],[7,6,10,7,10,8,5,4,10,4,8,10],[6,9,5,6,11,9,11,8,9],[3,6,11,0,6,3,0,5,6,0,9,5],[0,11,8,0,5,11,0,1,5,5,6,11],[6,11,3,6,3,5,5,3,1],[1,2,10,9,5,11,9,11,8,11,5,6],[0,11,3,0,6,11,0,9,6,5,6,9,1,2,10],[11,8,5,11,5,6,8,0,5,10,5,2,0,2,5],[6,11,3,6,3,5,2,10,3,10,5,3],[5,8,9,5,2,8,5,6,2,3,8,2],[9,5,6,9,6,0,0,6,2],[1,5,8,1,8,0,5,6,8,3,8,2,6,2,8],[1,5,6,2,1,6],[1,3,6,1,6,10,3,8,6,5,6,9,8,9,6],[10,1,0,10,0,6,9,5,0,5,6,0],[0,3,8,5,6,10],[10,5,6],[11,5,10,7,5,11],[11,5,10,11,7,5,8,3,0],[5,11,7,5,10,11,1,9,0],[10,7,5,10,11,7,9,8,1,8,3,1],[11,1,2,11,7,1,7,5,1],[0,8,3,1,2,7,1,7,5,7,2,11],[9,7,5,9,2,7,9,0,2,2,11,7],[7,5,2,7,2,11,5,9,2,3,2,8,9,8,2],[2,5,10,2,3,5,3,7,5],[8,2,0,8,5,2,8,7,5,10,2,5],[9,0,1,5,10,3,5,3,7,3,10,2],[9,8,2,9,2,1,8,7,2,10,2,5,7,5,2],[1,3,5,3,7,5],[0,8,7,0,7,1,1,7,5],[9,0,3,9,3,5,5,3,7],[9,8,7,5,9,7],[5,8,4,5,10,8,10,11,8],[5,0,4,5,11,0,5,10,11,11,3,0],[0,1,9,8,4,10,8,10,11,10,4,5],[10,11,4,10,4,5,11,3,4,9,4,1,3,1,4],[2,5,1,2,8,5,2,11,8,4,5,8],[0,4,11,0,11,3,4,5,11,2,11,1,5,1,11],[0,2,5,0,5,9,2,11,5,4,5,8,11,8,5],[9,4,5,2,11,3],[2,5,10,3,5,2,3,4,5,3,8,4],[5,10,2,5,2,4,4,2,0],[3,10,2,3,5,10,3,8,5,4,5,8,0,1,9],[5,10,2,5,2,4,1,9,2,9,4,2],[8,4,5,8,5,3,3,5,1],[0,4,5,1,0,5],[8,4,5,8,5,3,9,0,5,0,3,5],[9,4,5],[4,11,7,4,9,11,9,10,11],[0,8,3,4,9,7,9,11,7,9,10,11],[1,10,11,1,11,4,1,4,0,7,4,11],[3,1,4,3,4,8,1,10,4,7,4,11,10,11,4],[4,11,7,9,11,4,9,2,11,9,1,2],[9,7,4,9,11,7,9,1,11,2,11,1,0,8,3],[11,7,4,11,4,2,2,4,0],[11,7,4,11,4,2,8,3,4,3,2,4],[2,9,10,2,7,9,2,3,7,7,4,9],[9,10,7,9,7,4,10,2,7,8,7,0,2,0,7],[3,7,10,3,10,2,7,4,10,1,10,0,4,0,10],[1,10,2,8,7,4],[4,9,1,4,1,7,7,1,3],[4,9,1,4,1,7,0,8,1,8,7,1],[4,0,3,7,4,3],[4,8,7],[9,10,8,10,11,8],[3,0,9,3,9,11,11,9,10],[0,1,10,0,10,8,8,10,11],[3,1,10,11,3,10],[1,2,11,1,11,9,9,11,8],[3,0,9,3,9,11,1,2,9,2,11,9],[0,2,11,8,0,11],[3,2,11],[2,3,8,2,8,10,10,8,9],[9,10,2,0,9,2],[2,3,8,2,8,10,0,1,8,1,10,8],[1,10,2],[1,3,8,9,1,8],[0,9,1],[0,3,8],[]];class VolumeToLevelSet extends Three.Group{processes=[];constructor(e,t,{resolution:r,progressive:o},s,n){let a;super(),a=e instanceof Array?e:void 0!==e.grids?Object.values(e.grids):[e];let i=0;const c=a.length,l=c*Math.pow(r,3);a.forEach(((e,a)=>{const y=new Three.BufferGeometry,p=[],h=[],m=[],g=new Three.Mesh(y,t),u=1/r,d=(Math.pow(r,3)),f=new Vector3(0,0,0),w=new Vector3(0,0,0),x=new Vector3(0,0,0),b=new Bbox,T=new Vector3;b.set(...e.getPreciseWorldBbox()),b.getCenter(g.position),b.getSize(g.scale),g.scale.multiplyScalar(1/r),b.getCenter(f),b.getSize(w),x.copy(w).multiplyScalar(u),e.transform.applyInverseJacobian(x),f.sub(w.clone().multiplyScalar(.5)),e.transform.applyInverseTransformMap(f),f.add(x.clone().multiplyScalar(.5));let D=0,z=0,A=0;let M=function*(){for(let t=0;t<d;t++){if(A>=r)return;D++,f.x+=x.x,D>=r&&(D=0,f.x-=x.x*r,z++,f.y+=x.y),z>=r&&(z=0,f.y-=x.y*r,A++,f.z+=x.z);const t=[];T.copy(f),t[0]=getDensity$1(e,T),T.copy(f),T.x+=x.x,t[1]=getDensity$1(e,T),T.copy(f),T.x+=x.x,T.z+=x.z,t[2]=getDensity$1(e,T),T.copy(f),T.z+=x.z,t[3]=getDensity$1(e,T),T.copy(f),T.y+=x.y,t[4]=getDensity$1(e,T),T.copy(f),T.x+=x.x,T.y+=x.y,t[5]=getDensity$1(e,T),T.copy(f),T.x+=x.x,T.y+=x.y,T.z+=x.z,t[6]=getDensity$1(e,T),T.copy(f),T.y+=x.y,T.z+=x.z,t[7]=getDensity$1(e,T);let o=0;t[0]&&(o|=1),t[1]&&(o|=2),t[2]&&(o|=4),t[3]&&(o|=8),t[4]&&(o|=16),t[5]&&(o|=32),t[6]&&(o|=64),t[7]&&(o|=128);const s=triangleLUT[o]||s[1],n=[[.5,0,0],[1,0,.5],[.5,0,1],[0,0,.5],[.5,1,0],[1,1,.5],[.5,1,1],[0,1,.5],[0,.5,0],[1,.5,0],[1,.5,1],[0,.5,1]];if(t.filter((e=>0!==e)).length){const e={r:Math.random(),g:Math.random(),b:Math.random()};s.forEach((t=>{const o=n[t];p.push(D-r/2+o[0],z-r/2+o[1],A-r/2+o[2]),m.push(e.r,e.g,e.b);const s=new Three.Vector3(Math.random(),Math.random(),Math.random()).normalize();h.push(s.x,s.y,s.z);}));}yield;}}();const U=()=>{M&&(this.processes[a]=requestAnimationFrame(U));let e=window.performance.now();for(;window.performance.now()-e<16&&M;){const{done:e}=M.next();if(n&&n({convertedVoxels:0,totalVoxels:l,convertedGrids:i,totalGrids:c}),e&&(i++,M=null,delete this.processes[a],i===c)){if(!o){const e=new Three.BufferAttribute(new Float32Array(p),3);e.setUsage(Three.DynamicDrawUsage),g.geometry.setAttribute("position",e);const t=new Three.BufferAttribute(new Float32Array(h),3);t.setUsage(Three.DynamicDrawUsage),g.geometry.setAttribute("normal",t);const r=new Three.BufferAttribute(new Float32Array(m),3);r.setUsage(Three.DynamicDrawUsage),g.geometry.setAttribute("color",r),this.add(g);}s&&s();}}};U(),o&&this.add(g);}));}dispose(){this.processes&&(this.processes.forEach(((e,t)=>{cancelAnimationFrame(e),delete this.processes[t];})),this.processes=null);}}
|
|
28
|
+
|
|
29
|
+
class VolumeToPoints extends Three.Group{processes=[];constructor(e,t,{resolution:r,progressive:o},s,n){let i;super(),i=e instanceof Array?e:void 0!==e.grids?Object.values(e.grids):[e];let l=0;const a=i.length,c=a*Math.pow(r,3);i.forEach(((e,i)=>{const p=new Three.BufferGeometry,h=[],m=new Three.Points(p,new Three.PointsMaterial({color:16711935,size:1,...t||{}})),d=1/r,u=(Math.pow(r,3)),f=new Vector3(0,0,0),w=new Vector3(0,0,0),y=new Vector3(0,0,0),x=new Bbox;x.set(...e.getPreciseWorldBbox()),x.getCenter(m.position),x.getSize(m.scale),m.scale.multiplyScalar(1/r),x.getCenter(f),x.getSize(w),y.copy(w).multiplyScalar(d),e.transform.applyInverseJacobian(y),f.sub(w.clone().multiplyScalar(.5)),e.transform.applyInverseTransformMap(f),f.add(y.clone().multiplyScalar(.5));let g=0,b=0,T=0;let v=function*(){for(let t=0;t<u;t++){if(T>=r)return;g++,f.x+=y.x,g>=r&&(g=0,f.x-=y.x*r,b++,f.y+=y.y),b>=r&&(b=0,f.y-=y.y*r,T++,f.z+=y.z);getDensity$1(e,f)&&(h.push(g-r/2,b-r/2,T-r/2),p.setAttribute("position",new Three.BufferAttribute(new Float32Array(h),3))),yield;}}();const V=()=>{v&&(this.processes[i]=requestAnimationFrame(V));let e=window.performance.now();for(;window.performance.now()-e<16&&v;){const{done:e}=v.next();n&&n({convertedVoxels:0,totalVoxels:c,convertedGrids:l,totalGrids:a}),e&&(l++,v=null,delete this.processes[i],l===a&&(o||this.add(m),s&&s()));}};V(),o&&this.add(m);}));}dispose(){this.processes&&(this.processes.forEach(((e,t)=>{cancelAnimationFrame(e),delete this.processes[t];})),this.processes=null);}}
|
|
30
|
+
|
|
31
|
+
class MeshVolume extends GridDescriptor{static halfFloatGridPrefix=GridDescriptor.halfFloatGridPrefix;saveAsHalfFloat=!1;leavesCount=0;uniqueName;gridName;gridType;mesh=new Three.Object3D;constructor(e){super(),this.readGrid();const t=e.clone(),r=[],i=new Three.Vector3,s=new Three.Quaternion;t.position.setScalar(0,0,0),t.scale.setScalar(1),t.quaternion.identity(),t.updateMatrix(),t.updateMatrixWorld(),t.updateWorldMatrix(),t.traverse((e=>{if(e.geometry){const t=e.geometry.clone();e.getWorldQuaternion(s),t.applyQuaternion(s),e.getWorldScale(i),t.scale(i.x,i.y,i.z),e.getWorldPosition(i),t.translate(i.x,i.y,i.z),t.attributes={position:t.attributes.position,normal:t.attributes.normal},r.push(t);}}));const a=BufferGeometryUtils.mergeVertices(BufferGeometryUtils.mergeGeometries(r,!1));this.mesh=new Three.Mesh(a,new Three.MeshBasicMaterial({side:Three.DoubleSide})),this.cacheMeshGeometry();}readGrid(){this.readGridHeader(),this.readGridTransform();}readGridHeader(){this.uniqueName=`MeshVolume#${getUuid()}`,this.gridName=this.uniqueName.split("#")[0],this.gridType="fog volume";}readCompression(){}readMetadata(){}getGridValueType(){return floatType}getGridClass(){return "fog volume"}readGridTransform(){this.transform=new GridTransform;}readTopology(){}getWorldBbox(){return this.getLocalBbox()}getLocalBbox(){return [new Vector3$1(-.5,-.5,-.5),new Vector3$1(.5,.5,.5)]}getPreciseWorldBbox(){return this.getWorldBbox()}bbox=null;center=null;size=null;target=new Three.Vector3(0,0,0);raycaster=new Three.Raycaster;rayDirection=new Three.Vector3(1,0,0);cacheMeshGeometry(){this.bbox=new Three.Box3,this.bbox.expandByObject(this.mesh),this.center=new Three.Vector3,this.size=new Three.Vector3,this.bbox.getCenter(this.center),this.bbox.getSize(this.size);const e=Math.max(this.size.x,this.size.y,this.size.z),t=(new Three.Vector3).copy(this.size).divideScalar(e);this.size.divide(t),this.size.addScalar(.1),this.raycaster.near=0,this.raycaster.far=1e3;}getValue(e){this.target.copy(e).multiply(this.size).add(this.center),this.rayDirection.copy(e).normalize(),this.raycaster.set(this.target,this.rayDirection);const t=this.raycaster.intersectObjects([this.mesh],!0);return 0===t.length||t.length%2==0?0:1}getLeafAt(e){return null}}
|
|
32
|
+
|
|
33
|
+
class VolumeNormalMaterial extends Three.MeshStandardMaterial{name="VolumeNormalMaterial";isVolumetricFogMaterial=!0;customProgramCacheKey=()=>getUuid(this);_uniforms={baseColor:{value:new Three.Color(16777215)},scatterColor:{value:new Three.Color(0)},densityMap3D:{value:null},emissiveMap3D:{value:null},baseColorMap3D:{value:null},maskMap3D:{value:null},steps:{value:100},absorbance:{value:1},densityScale:{value:1},resolution:{value:100},offset3D:{value:new Three.Vector3(0,0,0)},wrap3D:{value:Three.ClampToEdgeWrapping},lights:{value:lights.useDirectionalLights|lights.usePointLights|lights.useSpotLights|lights.useHemisphereLights|lights.useEnvironment},cameraPosition:{value:new Three.Vector3(0,0,0)},cameraNear:{value:1},cameraFar:{value:1}};set baseColor(e){this._uniforms.baseColor.value.set(e);}get baseColor(){return this._uniforms.baseColor.value}set scatterColor(e){this._uniforms.scatterColor.value.set(e);}get scatterColor(){return this._uniforms.scatterColor.value}set densityMap3D(e){this._uniforms.densityMap3D.value=e,e.offset3D=this._uniforms.offset3D.value;}get densityMap3D(){return this._uniforms.densityMap3D.value}set emissiveMap3D(e){this._uniforms.emissiveMap3D.value=e,e.offset3D=this._uniforms.offset3D.value,this.needsUpdate=!0;}get emissiveMap3D(){return this._uniforms.emissiveMap3D.value}set maskMap3D(e){this._uniforms.maskMap3D.value=e,this.needsUpdate=!0;}get maskMap3D(){return this._uniforms.maskMap3D.value}set baseColorMap3D(e){this._uniforms.baseColorMap3D.value=e,e.offset3D=this._uniforms.offset3D.value,this.needsUpdate=!0;}get baseColorMap3D(){return this._uniforms.baseColorMap3D.value}set steps(e){this._uniforms.steps.value=e;}get steps(){return this._uniforms.steps.value}set absorbance(e){this._uniforms.absorbance.value=e;}get absorbance(){return this._uniforms.absorbance.value}set resolution(e){this._uniforms.resolution.value=e;}get resolution(){return this._uniforms.resolution.value}set offset3D(e){this._uniforms.offset3D.value.copy(e);}get offset3D(){return this._uniforms.offset3D.value}set wrap3D(e){this._uniforms.wrap3D.value=e,this.needsUpdate=!0;}get wrap3D(){return this._uniforms.wrap3D.value}set densityScale(e){this._uniforms.densityScale.value=e;}get densityScale(){return this._uniforms.densityScale.value}set lights(e){this._uniforms.lights.value=e,this.needsUpdate=!0;}get lights(){return this._uniforms.lights.value}set useDirectionalLights(e=!0){e?this._uniforms.lights.value|=lights.useDirectionalLights:this._uniforms.lights.value&=~lights.useDirectionalLights,this.needsUpdate=!0;}get useDirectionalLights(){return 0!=(this._uniforms.lights.value&lights.useDirectionalLights)}set usePointLights(e=!0){e?this._uniforms.lights.value|=lights.usePointLights:this._uniforms.lights.value&=~lights.usePointLights,this.needsUpdate=!0;}get usePointLights(){return 0!=(this._uniforms.lights.value&lights.usePointLights)}set useHemisphereLights(e=!0){e?this._uniforms.lights.value|=lights.useHemisphereLights:this._uniforms.lights.value&=~lights.useHemisphereLights,this.needsUpdate=!0;}get useHemisphereLights(){return 0!=(this._uniforms.lights.value&lights.useHemisphereLights)}set useSpotLights(e=!0){e?this._uniforms.lights.value|=lights.useSpotLights:this._uniforms.lights.value&=~lights.useSpotLights,this.needsUpdate=!0;}get useSpotLights(){return 0!=(this._uniforms.lights.value&lights.useSpotLights)}set useEnvironment(e=!0){e?this._uniforms.lights.value|=lights.useEnvironment:this._uniforms.lights.value&=~lights.useEnvironment,this.needsUpdate=!0;}get useEnvironment(){return 0!=(this._uniforms.lights.value&lights.useEnvironment)}constructor(e={}){super(),this.side=Three.BackSide,this.depthWrite=!1,this.depthTest=!1,this.transparent=!0,Object.keys(this._uniforms).forEach((n=>{void 0!==e[n]&&null!==e[n]&&(this[n]=e[n],e[n]instanceof Three.Texture&&(e[n].offset3D=this._uniforms.offset3D.value));})),this.onBeforeCompile=(n,i)=>{const t=getDepthUniforms();Object.entries({...this._uniforms,...t}).forEach((([e,i])=>{n.uniforms[e]=i;}));const o=`\n #define VOLUME_BBOX_SPAN 0.5\n ${e.emissiveMap3D?"#define USE_EMISSIVE_GRID 1":""}\n ${e.baseColorMap3D?"#define USE_BASE_COLOR_GRID 1":""}\n ${e.maskMap3D?"#define USE_MASK_GRID 1":""}\n\n #define VOLUME_USE_ENVIRONMENT ${Number(this.useEnvironment)} != 0\n #define VOLUME_USE_HEMI_LIGHTS ${Number(this.useHemisphereLights)} != 0\n #define VOLUME_USE_POINT_LIGHTS ${Number(this.usePointLights)} != 0\n #define VOLUME_USE_DIR_LIGHTS ${Number(this.useDirectionalLights)} != 0\n #define VOLUME_USE_SPOT_LIGHTS ${Number(this.useSpotLights)} != 0\n `,a="\n varying mat4 mModelMatrix;\n varying mat4 mViewMatrix;\n varying mat4 mProjectionMatrix;\n varying mat4 mModelViewMatrix;\n varying mat4 mInverseModelViewMatrix;\n varying mat3 mInverseNormalMatrix;\n ";n.vertexShader=n.vertexShader.replace("#include <common>",`\n ${a}\n\n out vec3 vOrigin;\n out vec3 vDirection;\n out vec3 vPosition;\n\n #include <common>\n `).replace("#include <worldpos_vertex>","\n #include <worldpos_vertex>\n\n vOrigin = vec3(inverse(modelMatrix) * vec4(cameraPosition, 1.0)).xyz;\n vDirection = position - vOrigin;\n vPosition = position;\n\n mModelMatrix = modelMatrix;\n mViewMatrix = viewMatrix;\n mProjectionMatrix = projectionMatrix;\n mModelViewMatrix = modelViewMatrix;\n mInverseModelViewMatrix = inverse(modelViewMatrix);\n mInverseNormalMatrix = inverse(normalMatrix);\n ");const r=`\n vec2 getVolumeBbox(vec3 vPointOfReference) {\n const vec3 vBoxMin = vec3(-0.5);\n const vec3 vBoxMax = vec3(0.5);\n vec3 vInvPointOfReference = 1.0 / vPointOfReference;\n \n vec3 vMinRange = (-vOrigin - VOLUME_BBOX_SPAN) * vInvPointOfReference;\n vec3 vMaxRange = (-vOrigin + VOLUME_BBOX_SPAN) * vInvPointOfReference;\n \n vec3 vMin = min(vMinRange, vMaxRange);\n vec3 vMax = max(vMinRange, vMaxRange);\n\n return vec2(\n max(vMin.x, max(vMin.y, vMin.z)),\n min(vMax.x, min(vMax.y, vMax.z))\n );\n }\n\n vec3 radiation = vec3(0.);\n vec3 getBlackBodyRadiation(float temperature) {\n if (temperature == 0.) {\n return vec3(0.);\n }\n\n #ifndef USE_EMISSIVE_GRID\n return vec3(0.);\n #endif\n\n // NOTE Blackbody radiation source - https://www.shadertoy.com/view/4tdGWM\n float temperatureScaled = temperature * 1000.;\n\n radiation.r += 1. / (exp(19E3 * 1. / temperatureScaled) - 1.);\n radiation.g += 3.375 / (exp(19E3 * 1.5 / temperatureScaled) - 1.);\n radiation.b += 8. / (exp(19E3 * 2. / temperatureScaled) - 1.);\n\n return (radiation / max(radiation.r,max(radiation.g,radiation.b)));\n }\n\n float loopUV(float x) {\n if (abs(mod(floor(x), 2.)) == 1.) {\n return ceil(x) - x;\n } else {\n return x - floor(x);\n }\n }\n\n vec3 mapTextureSample0(vec3 position){\n vec3 uv = position + VOLUME_BBOX_SPAN;\n\n ${this.wrap3D===Three.RepeatWrapping?"return mod(uv, 1.);":""}\n ${0===this.wrap3D||this.wrap3D===Three.ClampToEdgeWrapping?"// NOTE Return UV":""}\n ${this.wrap3D===Three.MirroredRepeatWrapping?"\n uv.x = loopUV(uv.x);\n uv.y = loopUV(uv.y);\n uv.z = loopUV(uv.z);\n ":""}\n\n return uv;\n }\n\n vec3 mapTextureSample(vec3 position, vec3 offset){\n vec3 uv = position + VOLUME_BBOX_SPAN + offset;\n\n ${this.wrap3D===Three.RepeatWrapping?"return mod(uv, 1.);":""}\n ${0===this.wrap3D||this.wrap3D===Three.ClampToEdgeWrapping?"// NOTE Return UV":""}\n ${this.wrap3D===Three.MirroredRepeatWrapping?"\n uv.x = loopUV(uv.x);\n uv.y = loopUV(uv.y);\n uv.z = loopUV(uv.z);\n ":""}\n\n return uv;\n }\n\n float blendSample(float value) {\n float fSmoothingThreshold = 0.25;\n float fSmoothingBlend = 0.25;\n\n return smoothstep(\n fSmoothingThreshold - fSmoothingBlend,\n fSmoothingThreshold + fSmoothingBlend,\n value\n ) * densityScale;\n }\n\n float clampedTexture(sampler3D source, vec3 position) {\n ${0===this.wrap3D||this.wrap3D===Three.ClampToEdgeWrapping?"\n vec3 vAbs = abs(position);\n\n if (max(vAbs.x, max(vAbs.y, vAbs.z)) > .5) {\n return 0.;\n }\n ":""}\n\n return texture(source, mapTextureSample(position, offset3D)).r;\n }\n\n float clampedMaskTexture(sampler3D source, vec3 position) {\n ${0===this.wrap3D||this.wrap3D===Three.ClampToEdgeWrapping?"\n vec3 vAbs = abs(position);\n\n if (max(vAbs.x, max(vAbs.y, vAbs.z)) > .5) {\n return 0.;\n }\n ":""}\n\n return texture(source, mapTextureSample0(position)).r;\n }\n\n vec3 clampedTextureRGB(sampler3D source, vec3 position) {\n return texture(source, mapTextureSample(position, offset3D)).rgb;\n }\n `;n.fragmentShader=n.fragmentShader.replace("#include <uv_pars_fragment>","// NOTE Override UV calculations").replace("#include <packing>","").replace("#include <common>",`\n precision highp float;\n precision highp sampler2D;\n precision highp sampler3D;\n\n in vec3 vOrigin;\n in vec3 vDirection;\n in vec3 vPosition;\n\n uniform sampler3D densityMap3D;\n uniform sampler3D emissiveMap3D;\n uniform sampler3D baseColorMap3D;\n uniform sampler3D maskMap3D;\n uniform vec3 offset3D;\n uniform int wrap3D;\n\n uniform float steps;\n uniform float absorbance;\n uniform float densityScale;\n uniform vec3 baseColor;\n uniform vec3 scatterColor;\n uniform float resolution;\n\n uniform float cameraNear;\n uniform float cameraFar;\n uniform sampler2D depthScreenMap;\n uniform vec4 depthView;\n\n ${o}\n ${a}\n\n #include <common>\n #include <packing>\n #include <uv_pars_fragment>\n\n ${r}\n `).replace("#include <lights_fragment_begin>","// NOTE Override light calculations").replace("#include <lights_fragment_maps>","// NOTE Override light calculations").replace("#include <lights_fragment_end>","// NOTE Override light calculations").replace("#include <opaque_fragment>","\n vec3 vWorld = -vViewPosition;\n\n vec3 vRayDirection = normalize(vDirection);\n vec2 vBounds = getVolumeBbox(vRayDirection);\n vec2 vMaxBounds = getVolumeBbox(vOrigin);\n \n if (vBounds.x > vBounds.y) {\n discard;\n }\n\n vBounds.x = max(vBounds.x, 0.0);\n vMaxBounds.x = max(vMaxBounds.x, 0.0);\n\n // Volume movement\n\n vec3 vPoint = vOrigin + vBounds.x * vRayDirection;\n vec3 vPointStep = 1.0 / abs(vRayDirection);\n float delta = min(vPointStep.x, min(vPointStep.y, vPointStep.z)) / steps;\n vec3 vDirectionDeltaStep = vRayDirection * delta;\n\n // Density calculations\n\n float density = 0.0;\n vec3 albedo = vec3(0.);\n vec3 emissive = vec3(0.);\n float volumeSample;\n float emissiveSample;\n float maskSample = 1.;\n float noiseSample;\n float noiseFactor;\n vec3 fTest;\n\n // Light calculations\n \n vec3 lightAlbedo = vec3(0.);\n vec3 vLightProbe;\n vec3 lightDirection;\n vec3 vLightStep;\n vec3 vEnvMapScatter = vec3(0.0);\n float lightSample;\n float lightAbsorbance;\n float lightDistance;\n float stepAccumulation;\n\n // Utils\n\n float absorbanceDensityRatio = min(absorbance, densityScale); // NOTE When reducing densityScale, automatically reduce absorbance\n float inverseAbsorbance = 1.0 / absorbanceDensityRatio;\n float inverseDensityScale = 1.0 / densityScale;\n float eInverseAbsorbance = exp(-1. - inverseAbsorbance); // NOTE Wrong, but looks kinda better than exp(-inverseAbsorbance)\n float eDensityAbsorbance = exp(-absorbanceDensityRatio * delta);\n float eInverseDensityScale = exp(-1. - inverseDensityScale);\n\n // Depth Testing\n\n vec2 screenUV = gl_FragCoord.xy / depthView.zw;\n float worldDepth = unpackRGBAToDepth(texture2D(depthScreenMap, screenUV));\n float fogDepth = 0.;\n vec3 vProjPoint;\n\n \n float lightMarchLimit = resolution + 2.;\n int iLightMarchLimit = int(lightMarchLimit);\n\n vec3 vUnit = (mModelMatrix * vec4(1., 0., 0., 0.)).xyz;\n float vUnitLength = length(vUnit);\n\n vec3 iblIrradiance = vec3(0.0);\n \n\n vec3 lastNonSolidPoint = vec3(vPoint);\n\n for (float i = vBounds.x; i < vBounds.y; i += delta) {\n volumeSample = clampedTexture(densityMap3D, vPoint);\n \n #ifdef USE_MASK_GRID\n maskSample = clampedMaskTexture(maskMap3D, vPoint);\n #endif\n\n density += blendSample(volumeSample) * eDensityAbsorbance * maskSample;\n\n #ifdef USE_EMISSIVE_GRID\n emissiveSample = clampedTexture(emissiveMap3D, vPoint);\n emissive = max(emissive, density * vec3(emissiveSample));\n #endif\n\n if (density < 1.) {\n lastNonSolidPoint = vPoint;\n }\n\n if (density >= 1.) {\n break;\n }\n\n vProjPoint = (mModelMatrix * vec4(vPoint, 1.0)).xyz;\n fogDepth = length(vProjPoint - cameraPosition);\n fogDepth = (fogDepth - cameraNear) / (cameraFar - cameraNear);\n fogDepth = saturate(fogDepth);\n \n if (worldDepth < fogDepth) {\n break;\n }\n\n vPoint += vDirectionDeltaStep;\n }\n\n density = clamp(density, 0.0, 1.0);\n\n vPoint = lastNonSolidPoint;\n\n if (density > 0.) {\n\n \n vec3 baseColorSample = baseColor.rgb;\n\n #ifdef USE_BASE_COLOR_GRID\n baseColorSample *= clampedTextureRGB(baseColorMap3D, vPoint);\n #endif\n\n albedo += ambientLightColor;\n \n\n #if VOLUME_USE_ENVIRONMENT\n \n vec3 viewDir = isOrthographic ? vec3(0.0, 0.0, 1.0) : normalize(vViewPosition);\n vec3 vUVCoords = normalize(lastNonSolidPoint * mInverseNormalMatrix).xyz;\n\n #if defined( USE_ENVMAP )\n vEnvMapScatter = getIBLIrradiance(vUVCoords) * RECIPROCAL_PI;\n\n albedo += density * getIBLRadiance(viewDir, vUVCoords, .5) * RECIPROCAL_PI;\n #endif\n \n #endif\n\n #if NUM_HEMI_LIGHTS > 0 && VOLUME_USE_HEMI_LIGHTS\n \n lightAlbedo = vec3(0.);\n vec3 textureProbe;\n\n float absorbanceUp;\n float absorbanceDown;\n float stepAccumulationUp;\n float stepAccumulationDown;\n\n for (int lightIndex = 0; lightIndex < NUM_HEMI_LIGHTS; lightIndex++) {\n\n HemisphereLight hemiLight = hemisphereLights[lightIndex];\n vLightProbe = vec3(vPoint);\n\n absorbanceUp = 0.0;\n absorbanceDown = 0.0;\n\n stepAccumulationUp = 0.;\n stepAccumulationDown = 0.;\n\n lightDirection = -normalize((vec4(hemiLight.direction, 1.) * viewMatrix).xyz);\n vLightStep = (lightDirection * VOLUME_BBOX_SPAN) / lightMarchLimit;\n\n for (int lightMarch = 1; lightMarch < int(lightMarchLimit / 2.); lightMarch++) {\n if (absorbanceUp < 1.) {\n textureProbe = vLightProbe - float(lightMarch) * vLightStep;\n lightSample = clampedTexture(densityMap3D, textureProbe);\n\n absorbanceUp += lightSample * eInverseAbsorbance;\n stepAccumulationUp += 1.;\n }\n \n if (absorbanceDown < 1.) {\n textureProbe = vLightProbe + float(lightMarch) * vLightStep;\n lightSample = clampedTexture(densityMap3D, textureProbe);\n\n absorbanceDown += blendSample(lightSample) * eInverseAbsorbance;\n stepAccumulationDown += 1.;\n }\n }\n\n absorbanceUp = min(1., absorbanceUp);\n absorbanceDown = min(1., absorbanceDown);\n\n lightAlbedo += mix(\n (1. - absorbanceUp) * hemiLight.skyColor,\n (1. - absorbanceDown) * hemiLight.groundColor,\n 0.5\n );\n }\n\n lightAlbedo *= density;\n albedo += lightAlbedo * baseColor.rgb * RECIPROCAL_PI;\n \n #endif\n\n #if NUM_POINT_LIGHTS > 0 && VOLUME_USE_POINT_LIGHTS\n \n PointLight pointLight;\n lightAlbedo = vec3(0.);\n \n for (int lightIndex = 0; lightIndex < NUM_POINT_LIGHTS; lightIndex++) {\n pointLight = pointLights[lightIndex];\n\n vLightProbe = vec3(vPoint);\n lightAbsorbance = 0.;\n stepAccumulation = 1.;\n\n lightDirection = vPoint - (mInverseModelViewMatrix * vec4(pointLight.position, 1.)).xyz;\n lightDistance = length(lightDirection) * vUnitLength;\n lightDirection = normalize(lightDirection);\n vLightStep = (lightDirection * VOLUME_BBOX_SPAN) / lightMarchLimit;\n\n for (int lightMarch = 0; lightMarch < iLightMarchLimit; lightMarch++) {\n vLightProbe -= vLightStep;\n stepAccumulation += 1.;\n \n lightSample = clampedTexture(densityMap3D, vLightProbe);\n \n lightAbsorbance += blendSample(lightSample) * eInverseAbsorbance;\n\n if (lightAbsorbance >= 1.) {\n lightAbsorbance = 1.;\n break;\n }\n }\n\n lightAlbedo += (1. - lightAbsorbance) * pointLight.color;\n }\n\n lightAlbedo *= density;\n albedo += lightAlbedo * baseColor.rgb * RECIPROCAL_PI;\n \n #endif\n\n #if NUM_DIR_LIGHTS > 0 && VOLUME_USE_DIR_LIGHTS\n \n DirectionalLight directionalLight;\n lightAlbedo = vec3(0.);\n\n for (int lightIndex = 0; lightIndex < NUM_DIR_LIGHTS; lightIndex++) {\n directionalLight = directionalLights[lightIndex];\n\n vLightProbe = vec3(vPoint);\n lightAbsorbance = 0.0;\n stepAccumulation = 1.;\n\n lightDirection = -normalize((vec4(directionalLight.direction, 1.) * viewMatrix).xyz);\n vLightStep = (lightDirection * VOLUME_BBOX_SPAN) / lightMarchLimit;\n\n for (int lightMarch = 0; lightMarch < iLightMarchLimit; lightMarch++) {\n vLightProbe -= vLightStep;\n stepAccumulation += 1.;\n \n float lightSample = clampedTexture(densityMap3D, vLightProbe);\n\n lightAbsorbance += blendSample(lightSample) * eInverseAbsorbance;\n\n if (lightAbsorbance >= 1.) {\n lightAbsorbance = 1.;\n break;\n }\n }\n\n lightAlbedo += (1. - lightAbsorbance) * directionalLight.color;\n }\n\n lightAlbedo *= density;\n albedo += lightAlbedo * baseColor.rgb * RECIPROCAL_PI;\n \n #endif\n\n #if NUM_SPOT_LIGHTS > 0 && VOLUME_USE_SPOT_LIGHTS\n \n SpotLight spotLight;\n lightAlbedo = vec3(0.);\n\n float angleCos;\n float spotAttenuation;\n \n for (int lightIndex = 0; lightIndex < NUM_SPOT_LIGHTS; lightIndex++) {\n spotLight = spotLights[lightIndex];\n\n vLightProbe = vec3(vPoint);\n lightAbsorbance = 0.0;\n stepAccumulation = 1.;\n\n lightDirection = vPoint - (mInverseModelViewMatrix * vec4(spotLight.position, 1.)).xyz;\n lightDistance = length(lightDirection) * vUnitLength;\n lightDirection = normalize(lightDirection);\n vLightStep = (lightDirection * VOLUME_BBOX_SPAN) / lightMarchLimit;\n\n angleCos = dot(normalize(-lightDirection), normalize(mInverseNormalMatrix * spotLight.direction));\n spotAttenuation = smoothstep(spotLight.coneCos, spotLight.penumbraCos, angleCos);\n\n if (spotAttenuation > 0.) {\n for (int lightMarch = 0; lightMarch < iLightMarchLimit; lightMarch++) {\n vLightProbe -= vLightStep;\n stepAccumulation += 1.;\n \n lightSample = clampedTexture(densityMap3D, vLightProbe);\n \n lightAbsorbance += blendSample(lightSample) * eInverseAbsorbance;\n\n if (lightAbsorbance >= 1.) {\n lightAbsorbance = 1.;\n break;\n }\n }\n\n lightAlbedo += (1. - lightAbsorbance) * spotAttenuation * spotLight.color;\n }\n }\n\n lightAlbedo *= density;\n albedo += lightAlbedo * baseColor.rgb * RECIPROCAL_PI;\n \n #endif\n\n albedo *= baseColorSample;\n }\n \n emissive = getBlackBodyRadiation(emissive.r);\n albedo += emissive;\n\n outgoingLight.rgb = vPoint.xyz;\n diffuseColor.a = saturate(density * opacity);\n\n if (density <= 0.) {\n discard;\n }\n\n #include <opaque_fragment>\n ");};}}
|
|
34
|
+
|
|
35
|
+
const froxelPars="\n uniform vec3 froxelGrid;\n uniform float froxelColumns;\n uniform vec2 froxelRange;\n uniform mat4 froxelProjectionInverse;\n uniform int froxelSubSteps;\n\n float frameSliceDepth(float t) {\n return froxelRange.x * pow(froxelRange.y / froxelRange.x, t);\n }\n\n vec3 frameGetViewPosition(vec2 uv, float viewDepth) {\n vec4 nearPoint = froxelProjectionInverse * vec4(uv * 2.0 - 1.0, -1.0, 1.0);\n vec4 farPoint = froxelProjectionInverse * vec4(uv * 2.0 - 1.0, 1.0, 1.0);\n nearPoint /= nearPoint.w;\n farPoint /= farPoint.w;\n\n float t = (viewDepth + nearPoint.z) / (nearPoint.z - farPoint.z);\n\n return mix(nearPoint.xyz, farPoint.xyz, t);\n }\n";const noisePars="\n uniform float frameJitter;\n\n // NOTE Returns 0.5 when jitter is off (frameJitter is negative)\n float frameNoise(vec2 pixel) {\n if (frameJitter < 0.0) {\n return 0.5;\n }\n\n float noise = fract(52.9829189 * fract(dot(pixel, vec2(0.06711056, 0.00583715))));\n\n return fract(noise + frameJitter);\n }\n";const volumePars=`\n #define FRAME_WRAP_REPEAT ${Three.RepeatWrapping}\n #define FRAME_WRAP_MIRROR ${Three.MirroredRepeatWrapping}\n\n uniform mat4 frameViewToLocal;\n uniform mat4 frameLocalToView;\n uniform mat4 frameWorldToLocal;\n uniform float frameWorldScale;\n\n uniform highp sampler3D frameDensityMap;\n uniform highp sampler3D frameEmissiveMap;\n uniform highp sampler3D frameBaseColorMap;\n uniform highp sampler3D frameMaskMap;\n uniform bool frameUseEmissiveMap;\n uniform bool frameUseBaseColorMap;\n uniform bool frameUseMaskMap;\n\n uniform vec3 frameBaseColor;\n uniform vec3 frameScatterColor;\n uniform float frameExtinction;\n uniform float frameDensityScale;\n uniform float frameDensityCutoff;\n uniform float frameAbsorbance;\n uniform float frameOpacity;\n uniform float frameEmissiveIntensity;\n uniform vec2 frameTemperatureRange;\n uniform vec3 frameOffset3D;\n uniform int frameWrap3D;\n\n float frameMirrorAxis(float value) {\n float period = mod(value, 2.0);\n\n return period > 1.0 ? 2.0 - period : period;\n }\n\n vec3 frameWrapUv(vec3 uv) {\n if (frameWrap3D == FRAME_WRAP_REPEAT) {\n return fract(uv);\n }\n\n if (frameWrap3D == FRAME_WRAP_MIRROR) {\n return vec3(frameMirrorAxis(uv.x), frameMirrorAxis(uv.y), frameMirrorAxis(uv.z));\n }\n\n return uv;\n }\n\n // NOTE Mip level for a footprint of the given size in local units of the longest box side\n float frameLod(float localSize) {\n vec3 texels = vec3(textureSize(frameDensityMap, 0));\n\n return max(0.0, log2(localSize * max(texels.x, max(texels.y, texels.z))));\n }\n\n // NOTE Returns extinction for each world unit\n float frameSampleExtinction(vec3 localPosition, float lod) {\n vec3 uv = frameWrapUv(localPosition + 0.5 + frameOffset3D);\n float value = textureLod(frameDensityMap, uv, lod).r;\n\n if (frameUseMaskMap) {\n value *= textureLod(frameMaskMap, localPosition + 0.5, lod).r;\n }\n\n value = value > frameDensityCutoff ? value : 0.0;\n\n return value * frameDensityScale * frameExtinction / frameWorldScale;\n }\n\n bool frameInsideBox(vec3 localPosition) {\n vec3 localAbs = abs(localPosition);\n\n return max(localAbs.x, max(localAbs.y, localAbs.z)) <= 0.5;\n }\n\n float frameBoxExit(vec3 origin, vec3 direction) {\n vec3 inverseDirection = 1.0 / direction;\n vec3 tMin = (vec3(-0.5) - origin) * inverseDirection;\n vec3 tMax = (vec3(0.5) - origin) * inverseDirection;\n vec3 tFar = max(tMin, tMax);\n\n return max(0.0, min(tFar.x, min(tFar.y, tFar.z)));\n }\n\n // NOTE Returns the part of the segment from start to end (0.0 to 1.0) that is in the box\n vec2 frameBoxSegment(vec3 start, vec3 end) {\n vec3 inverseDirection = 1.0 / (end - start);\n vec3 tMin = (vec3(-0.5) - start) * inverseDirection;\n vec3 tMax = (vec3(0.5) - start) * inverseDirection;\n vec3 tNear = min(tMin, tMax);\n vec3 tFar = max(tMin, tMax);\n\n return vec2(\n max(0.0, max(tNear.x, max(tNear.y, tNear.z))),\n min(1.0, min(tFar.x, min(tFar.y, tFar.z)))\n );\n }\n\n vec3 frameSampleAlbedo(vec3 localPosition, float lod) {\n vec3 albedo = frameBaseColor;\n\n if (frameUseBaseColorMap) {\n albedo *= textureLod(frameBaseColorMap, frameWrapUv(localPosition + 0.5 + frameOffset3D), lod).rgb;\n }\n\n return albedo;\n }\n\n // NOTE Blackbody color, temperature in Kelvin. Source - https://www.shadertoy.com/view/4tdGWM\n vec3 frameBlackBody(float temperature) {\n if (temperature <= 0.0) {\n return vec3(0.0);\n }\n\n vec3 radiation = vec3(\n 1.0 / (exp(19E3 * 1.0 / temperature) - 1.0),\n 3.375 / (exp(19E3 * 1.5 / temperature) - 1.0),\n 8.0 / (exp(19E3 * 2.0 / temperature) - 1.0)\n );\n\n float peak = max(radiation.r, max(radiation.g, radiation.b));\n\n return peak > 0.0 ? radiation / peak : vec3(0.0);\n }\n\n vec3 frameSampleEmission(vec3 localPosition, float lod) {\n if (!frameUseEmissiveMap) {\n return vec3(0.0);\n }\n\n float value = textureLod(frameEmissiveMap, frameWrapUv(localPosition + 0.5 + frameOffset3D), lod).r;\n float temperature = mix(frameTemperatureRange.x, frameTemperatureRange.y, value);\n\n // NOTE Emitted power increases with temperature^4 (Stefan-Boltzmann). Range top gives 1.0.\n float power = pow(temperature / max(frameTemperatureRange.y, 1.0), 4.0);\n\n return frameBlackBody(temperature) * power * frameEmissiveIntensity;\n }\n`;const frameMaxShadowCascades=4;const gridPars="\n #define FRAME_MAX_CASCADES 4\n\n uniform vec3 frameGridMin[FRAME_MAX_CASCADES];\n uniform vec3 frameGridSize[FRAME_MAX_CASCADES];\n uniform vec3 frameGridVoxel[FRAME_MAX_CASCADES];\n uniform int frameGridCount;\n uniform float frameGridResolution;\n uniform float frameGridColumns;\n uniform vec2 frameGridAtlasSize;\n\n vec3 frameGridUvw(int cascade, vec3 worldPosition) {\n return (worldPosition - frameGridMin[cascade]) / frameGridSize[cascade];\n }\n\n // NOTE Half a voxel inside the cascade. Then the linear filter does not read the clamped edge.\n bool frameInsideGrid(vec3 gridUvw) {\n float inset = 0.5 / frameGridResolution;\n\n return all(greaterThanEqual(gridUvw, vec3(inset))) && all(lessThanEqual(gridUvw, vec3(1.0 - inset)));\n }\n\n // NOTE Finest cascade from the first cascade that contains the position, or -1\n int frameFindCascade(vec3 worldPosition, int firstCascade) {\n for (int i = 0; i < FRAME_MAX_CASCADES; i++) {\n if (i >= frameGridCount) {\n break;\n }\n\n if (i >= firstCascade && frameInsideGrid(frameGridUvw(i, worldPosition))) {\n return i;\n }\n }\n\n return -1;\n }\n\n // NOTE Hardware bilinear filter in the slice, manual linear filter between slices\n vec4 frameSampleGrid(sampler2D atlas, int cascade, vec3 gridUvw) {\n float resolution = frameGridResolution;\n float z = gridUvw.z * resolution - 0.5;\n float z0 = clamp(floor(z), 0.0, resolution - 1.0);\n float z1 = min(z0 + 1.0, resolution - 1.0);\n float blend = clamp(z - z0, 0.0, 1.0);\n vec2 xy = clamp(gridUvw.xy * resolution, vec2(0.5), vec2(resolution - 0.5));\n\n float tileOffset = float(cascade) * resolution;\n float tile0Index = tileOffset + z0;\n float tile1Index = tileOffset + z1;\n vec2 tile0 = vec2(mod(tile0Index, frameGridColumns), floor(tile0Index / frameGridColumns));\n vec2 tile1 = vec2(mod(tile1Index, frameGridColumns), floor(tile1Index / frameGridColumns));\n\n vec4 sample0 = texture(atlas, (tile0 * resolution + xy) / frameGridAtlasSize);\n vec4 sample1 = texture(atlas, (tile1 * resolution + xy) / frameGridAtlasSize);\n\n return mix(sample0, sample1, blend);\n }\n\n // NOTE Transmittance of the shadow slots at a world position. 1.0 outside the grid.\n void frameSampleShadows(\n sampler2D map0,\n sampler2D map1,\n vec3 worldPosition,\n out vec4 shadow0,\n out vec4 shadow1\n ) {\n shadow0 = vec4(1.0);\n shadow1 = vec4(1.0);\n\n int cascade = frameFindCascade(worldPosition, 0);\n\n if (cascade < 0) {\n return;\n }\n\n vec3 gridUvw = frameGridUvw(cascade, worldPosition);\n\n shadow0 = frameSampleGrid(map0, cascade, gridUvw);\n shadow1 = frameSampleGrid(map1, cascade, gridUvw);\n\n // NOTE Blend to the next cascade near the sides of this cascade. Then cascade borders are not visible.\n vec3 sideDistance = min(gridUvw, 1.0 - gridUvw);\n float blend = 1.0 - smoothstep(0.0, 0.1, min(sideDistance.x, min(sideDistance.y, sideDistance.z)));\n int nextCascade = blend > 0.0 ? frameFindCascade(worldPosition, cascade + 1) : -1;\n\n if (nextCascade >= 0) {\n vec3 nextUvw = frameGridUvw(nextCascade, worldPosition);\n\n shadow0 = mix(shadow0, frameSampleGrid(map0, nextCascade, nextUvw), blend);\n shadow1 = mix(shadow1, frameSampleGrid(map1, nextCascade, nextUvw), blend);\n }\n }\n\n // NOTE Cascade and grid position of an atlas pixel\n int frameGetGridCell(vec2 fragCoord, out vec3 gridUvw) {\n float resolution = frameGridResolution;\n vec2 tile = floor(fragCoord / resolution);\n float tileIndex = tile.y * frameGridColumns + tile.x;\n float cascade = floor(tileIndex / resolution);\n float z = tileIndex - cascade * resolution;\n\n gridUvw = vec3(fragCoord - tile * resolution, z + 0.5) / resolution;\n\n return int(cascade);\n }\n";const tileQuadVertexShader="\n uniform vec4 frameRect;\n uniform float frameSliceStart;\n uniform vec2 frameTileSize;\n uniform float frameTileColumns;\n uniform vec2 frameTargetSize;\n\n void main() {\n float slice = frameSliceStart + float(gl_InstanceID);\n vec2 tile = vec2(mod(slice, frameTileColumns), floor(slice / frameTileColumns));\n vec2 cell = mix(frameRect.xy, frameRect.zw, position.xy * 0.5 + 0.5);\n\n gl_Position = vec4((tile * frameTileSize + cell) / frameTargetSize * 2.0 - 1.0, 0.0, 1.0);\n }\n";const createVolumeUniforms=()=>({frameViewToLocal:{value:new Three.Matrix4},frameLocalToView:{value:new Three.Matrix4},frameWorldToLocal:{value:new Three.Matrix4},frameWorldScale:{value:1},frameDensityMap:{value:null},frameEmissiveMap:{value:null},frameBaseColorMap:{value:null},frameMaskMap:{value:null},frameUseEmissiveMap:{value:!1},frameUseBaseColorMap:{value:!1},frameUseMaskMap:{value:!1},frameBaseColor:{value:new Three.Color(16777215)},frameScatterColor:{value:new Three.Color(0)},frameExtinction:{value:100},frameDensityScale:{value:1},frameDensityCutoff:{value:0},frameAbsorbance:{value:1},frameOpacity:{value:1},frameEmissiveIntensity:{value:1},frameTemperatureRange:{value:new Three.Vector2(0,6500)},frameOffset3D:{value:new Three.Vector3},frameWrap3D:{value:Three.ClampToEdgeWrapping}});const setVolumeUniforms=(e,a,n)=>{e.frameViewToLocal.value.copy(n.viewToLocal),e.frameLocalToView.value.copy(n.localToView),e.frameWorldToLocal.value.copy(n.worldToLocal),e.frameWorldScale.value=n.matrixWorld.getMaxScaleOnAxis(),e.frameDensityMap.value=n.densityMap3D,e.frameEmissiveMap.value=n.emissiveMap3D,e.frameBaseColorMap.value=n.baseColorMap3D,e.frameMaskMap.value=n.maskMap3D,e.frameUseEmissiveMap.value=!!n.emissiveMap3D,e.frameUseBaseColorMap.value=!!n.baseColorMap3D,e.frameUseMaskMap.value=!!n.maskMap3D,e.frameBaseColor.value.copy(a.baseColor),e.frameScatterColor.value.copy(a.scatterColor),e.frameExtinction.value=a.extinction,e.frameDensityScale.value=a.densityScale,e.frameDensityCutoff.value=a.densityCutoff,e.frameAbsorbance.value=a.absorbance,e.frameOpacity.value=a.opacity,e.frameEmissiveIntensity.value=a.emissiveIntensity,e.frameTemperatureRange.value.copy(a.temperatureRange),e.frameOffset3D.value.copy(a.offset3D),e.frameWrap3D.value=a.wrap3D;};
|
|
36
|
+
|
|
37
|
+
const frameLights={useAmbientLights:32,usePointLights:16,useDirectionalLights:8,useSpotLights:4,useHemisphereLights:2,useEnvironment:1};const vertexShader$4="\n void main() {\n gl_Position = vec4(position.xy, 0.0, 1.0);\n }\n",fragmentShader$6=`\n precision highp float;\n precision highp sampler3D;\n\n #define FRAME_LIGHT_AMBIENT ${frameLights.useAmbientLights}\n #define FRAME_LIGHT_POINT ${frameLights.usePointLights}\n #define FRAME_LIGHT_DIR ${frameLights.useDirectionalLights}\n #define FRAME_LIGHT_SPOT ${frameLights.useSpotLights}\n #define FRAME_LIGHT_HEMI ${frameLights.useHemisphereLights}\n #define FRAME_LIGHT_ENV ${frameLights.useEnvironment}\n\n uniform int frameLights;\n uniform int frameShadowSteps;\n\n ${froxelPars}\n ${volumePars}\n ${noisePars}\n\n #include <common>\n #include <lights_pars_begin>\n #include <envmap_common_pars_fragment>\n #include <cube_uv_reflection_fragment>\n #include <envmap_physical_pars_fragment>\n\n // NOTE Shadow march inside the source volume only. Other volumes do not cast shadows.\n float frameTransmittance(vec3 origin, vec3 localDirection, float maxLocalDistance, float lod) {\n if (frameAbsorbance <= 0.0 || frameShadowSteps <= 0) {\n return 1.0;\n }\n\n vec3 direction = normalize(localDirection);\n float marchDistance = min(frameBoxExit(origin, direction), maxLocalDistance);\n\n if (marchDistance <= 0.0) {\n return 1.0;\n }\n\n float localStep = marchDistance / float(frameShadowSteps);\n float stepLod = max(lod, frameLod(localStep));\n float worldStep = localStep * length(mat3(frameLocalToView) * direction);\n float stepScale = worldStep * frameAbsorbance;\n float opticalDepth = 0.0;\n vec3 probe = origin + direction * localStep * frameNoise(gl_FragCoord.xy + vec2(17.0, 31.0));\n\n for (int i = 0; i < frameShadowSteps; i++) {\n opticalDepth += frameSampleExtinction(probe, stepLod) * stepScale;\n\n if (opticalDepth > 8.0) {\n break;\n }\n\n probe += direction * localStep;\n }\n\n return exp(-opticalDepth);\n }\n\n void main() {\n vec2 tile = floor(gl_FragCoord.xy / froxelGrid.xy);\n float slice = tile.y * froxelColumns + tile.x;\n\n if (slice >= froxelGrid.z) {\n discard;\n }\n\n vec2 uv = (gl_FragCoord.xy - tile * froxelGrid.xy) / froxelGrid.xy;\n float sliceNear = frameSliceDepth(slice / froxelGrid.z);\n float sliceFar = frameSliceDepth((slice + 1.0) / froxelGrid.z);\n vec3 viewNear = frameGetViewPosition(uv, sliceNear);\n vec3 viewFar = frameGetViewPosition(uv, sliceFar);\n vec3 localNear = (frameViewToLocal * vec4(viewNear, 1.0)).xyz;\n vec3 localFar = (frameViewToLocal * vec4(viewFar, 1.0)).xyz;\n\n vec2 boxSegment = frameBoxSegment(localNear, localFar);\n\n if (boxSegment.x >= boxSegment.y) {\n discard;\n }\n\n // NOTE Pick the mip level from the froxel width or the sub-step length, the larger one\n float subStepLength = length(viewFar - viewNear) / float(froxelSubSteps);\n float sliceMiddle = (sliceNear + sliceFar) * 0.5;\n float froxelWidth = length(\n frameGetViewPosition(uv + vec2(1.0 / froxelGrid.x, 0.0), sliceMiddle) -\n frameGetViewPosition(uv, sliceMiddle)\n );\n float lod = frameLod(max(froxelWidth, subStepLength) / frameWorldScale);\n\n // NOTE Pre-integrate the slice along the view ray. Light the visibility-weighted mean position, not the froxel center.\n float transmittance = 1.0;\n float visibleWeight = 0.0;\n float visibleTSum = 0.0;\n float jitter = frameNoise(gl_FragCoord.xy);\n\n for (int i = 0; i < froxelSubSteps; i++) {\n float t = (float(i) + jitter) / float(froxelSubSteps);\n\n if (t < boxSegment.x || t > boxSegment.y) {\n continue;\n }\n\n vec3 probe = mix(localNear, localFar, t);\n float opticalDepth = frameSampleExtinction(probe, lod) * frameOpacity * subStepLength;\n float stepTransmittance = exp(-opticalDepth);\n float weight = transmittance * (1.0 - stepTransmittance);\n\n visibleWeight += weight;\n visibleTSum += weight * t;\n transmittance *= stepTransmittance;\n }\n\n float alpha = 1.0 - transmittance;\n\n if (visibleWeight <= 0.0 || alpha <= 1e-5) {\n discard;\n }\n\n vec3 localPosition = mix(localNear, localFar, visibleTSum / visibleWeight);\n vec3 viewPosition = (frameLocalToView * vec4(localPosition, 1.0)).xyz;\n\n mat3 viewToLocalDirection = mat3(frameViewToLocal);\n\n vec3 outwardNormal = length(localPosition) > 1e-4\n ? normalize(mat3(frameLocalToView) * localPosition)\n : normalize((viewMatrix * vec4(0.0, 1.0, 0.0, 0.0)).xyz);\n\n vec3 irradiance = vec3(0.0);\n\n if ((frameLights & FRAME_LIGHT_AMBIENT) != 0) {\n irradiance += getAmbientLightIrradiance(ambientLightColor);\n\n #if defined( USE_LIGHT_PROBES )\n irradiance += getLightProbeIrradiance(lightProbe, outwardNormal);\n #endif\n }\n\n #if defined( USE_ENVMAP )\n if ((frameLights & FRAME_LIGHT_ENV) != 0) {\n float envTransmittance = frameTransmittance(localPosition, viewToLocalDirection * outwardNormal, 1e6, lod);\n\n irradiance += getIBLIrradiance(outwardNormal) * envTransmittance;\n }\n #endif\n\n #if NUM_HEMI_LIGHTS > 0\n if ((frameLights & FRAME_LIGHT_HEMI) != 0) {\n for (int i = 0; i < NUM_HEMI_LIGHTS; i++) {\n HemisphereLight hemiLight = hemisphereLights[i];\n vec3 upDirection = viewToLocalDirection * hemiLight.direction;\n\n irradiance += 0.5 * hemiLight.skyColor * frameTransmittance(localPosition, upDirection, 1e6, lod);\n irradiance += 0.5 * hemiLight.groundColor * frameTransmittance(localPosition, -upDirection, 1e6, lod);\n }\n }\n #endif\n\n #if NUM_DIR_LIGHTS > 0\n if ((frameLights & FRAME_LIGHT_DIR) != 0) {\n for (int i = 0; i < NUM_DIR_LIGHTS; i++) {\n DirectionalLight directionalLight = directionalLights[i];\n\n irradiance += directionalLight.color * frameTransmittance(\n localPosition,\n viewToLocalDirection * directionalLight.direction,\n 1e6,\n lod\n );\n }\n }\n #endif\n\n // NOTE Point and spot lights have no distance falloff. This is the same as FogVolume.\n\n #if NUM_POINT_LIGHTS > 0\n if ((frameLights & FRAME_LIGHT_POINT) != 0) {\n for (int i = 0; i < NUM_POINT_LIGHTS; i++) {\n PointLight pointLight = pointLights[i];\n vec3 toLight = (frameViewToLocal * vec4(pointLight.position, 1.0)).xyz - localPosition;\n\n irradiance += pointLight.color * frameTransmittance(localPosition, toLight, length(toLight), lod);\n }\n }\n #endif\n\n #if NUM_SPOT_LIGHTS > 0\n if ((frameLights & FRAME_LIGHT_SPOT) != 0) {\n for (int i = 0; i < NUM_SPOT_LIGHTS; i++) {\n SpotLight spotLight = spotLights[i];\n float angleCos = dot(normalize(spotLight.position - viewPosition), spotLight.direction);\n float spotAttenuation = getSpotAttenuation(spotLight.coneCos, spotLight.penumbraCos, angleCos);\n\n if (spotAttenuation > 0.0) {\n vec3 toLight = (frameViewToLocal * vec4(spotLight.position, 1.0)).xyz - localPosition;\n\n irradiance += spotLight.color * spotAttenuation * frameTransmittance(localPosition, toLight, length(toLight), lod);\n }\n }\n }\n #endif\n\n vec3 albedo = frameSampleAlbedo(localPosition, lod);\n vec3 radiance = max(frameScatterColor, albedo * irradiance * RECIPROCAL_PI);\n\n radiance += frameSampleEmission(localPosition, lod);\n\n // NOTE Premultiplied slice color and alpha. Linear filtering of these values gives transparent edges.\n gl_FragColor = vec4(radiance * alpha, alpha);\n }\n`;class FrameInjectMaterial extends Three.MeshStandardMaterial{name="FrameInjectMaterial";customProgramCacheKey=()=>"FrameInjectMaterial";constructor(e){super(),this.frameUniforms={...e,...createVolumeUniforms(),frameLights:{value:0},frameShadowSteps:{value:32}},this.transparent=!0,this.depthTest=!1,this.depthWrite=!1,this.side=Three.DoubleSide,this.blending=Three.CustomBlending,this.blendEquation=Three.AddEquation,this.blendSrc=Three.OneFactor,this.blendDst=Three.OneMinusSrcAlphaFactor,this.blendSrcAlpha=Three.OneFactor,this.blendDstAlpha=Three.OneMinusSrcAlphaFactor,this.onBeforeCompile=e=>{Object.assign(e.uniforms,this.frameUniforms),e.vertexShader=vertexShader$4,e.fragmentShader=fragmentShader$6;};}}
|
|
38
|
+
|
|
39
|
+
const vertexShader$3="\n varying vec2 vUv;\n\n void main() {\n vUv = position.xy * 0.5 + 0.5;\n\n gl_Position = vec4(position.xy, 0.0, 1.0);\n }\n",fragmentShader$5="\n precision highp float;\n\n #define FRAME_MAX_SLICES 1024\n\n uniform sampler2D froxelAtlas;\n uniform vec2 froxelAtlasSize;\n uniform vec3 froxelGrid;\n uniform float froxelColumns;\n uniform vec2 froxelRange;\n\n uniform sampler2D frameDepthMap;\n uniform float frameCameraNear;\n uniform float frameCameraFar;\n\n varying vec2 vUv;\n\n #include <common>\n #include <packing>\n\n float frameSliceDepth(float t) {\n return froxelRange.x * pow(froxelRange.y / froxelRange.x, t);\n }\n\n float frameSceneDepth() {\n float depth = texture2D(frameDepthMap, vUv).x;\n\n #ifdef USE_REVERSED_DEPTH_BUFFER\n bool isEmpty = depth <= 0.0;\n #else\n bool isEmpty = depth >= 1.0;\n #endif\n\n if (isEmpty) {\n return 1e20;\n }\n\n return -(isOrthographic\n ? orthographicDepthToViewZ(depth, frameCameraNear, frameCameraFar)\n : perspectiveDepthToViewZ(depth, frameCameraNear, frameCameraFar));\n }\n\n void main() {\n float sceneDepth = frameSceneDepth();\n\n vec2 froxelUv = clamp(vUv * froxelGrid.xy, vec2(0.5), froxelGrid.xy - 0.5);\n int sliceCount = int(froxelGrid.z);\n\n vec3 radiance = vec3(0.0);\n float transmittance = 1.0;\n float sliceStart = froxelRange.x;\n\n for (int i = 0; i < FRAME_MAX_SLICES; i++) {\n if (i >= sliceCount || sliceStart >= sceneDepth || transmittance < 0.001) {\n break;\n }\n\n float sliceEnd = frameSliceDepth(float(i + 1) / froxelGrid.z);\n float coverage = (min(sliceEnd, sceneDepth) - sliceStart) / (sliceEnd - sliceStart);\n\n vec2 tile = vec2(mod(float(i), froxelColumns), floor(float(i) / froxelColumns));\n vec4 froxel = texture2D(froxelAtlas, (tile * froxelGrid.xy + froxelUv) / froxelAtlasSize);\n\n float froxelAlpha = clamp(froxel.a, 0.0, 1.0);\n\n // NOTE Froxels store premultiplied slice color and alpha. Scene depth can cut the last slice.\n if (froxelAlpha > 0.0) {\n float sliceAlpha = coverage < 1.0 ? 1.0 - pow(1.0 - froxelAlpha, coverage) : froxelAlpha;\n\n radiance += transmittance * froxel.rgb * (sliceAlpha / froxelAlpha);\n transmittance *= 1.0 - sliceAlpha;\n }\n\n sliceStart = sliceEnd;\n }\n\n float alpha = 1.0 - transmittance;\n\n if (alpha <= 0.0) {\n discard;\n }\n\n gl_FragColor = vec4(radiance / alpha, alpha);\n\n #include <tonemapping_fragment>\n #include <colorspace_fragment>\n #include <premultiplied_alpha_fragment>\n }\n";class FrameIntegrateMaterial extends Three.ShaderMaterial{name="FrameIntegrateMaterial";constructor(e){super({uniforms:{...e,froxelAtlas:{value:null},froxelAtlasSize:{value:new Three.Vector2(1,1)},frameDepthMap:{value:null},frameCameraNear:{value:.1},frameCameraFar:{value:1e3}},vertexShader:vertexShader$3,fragmentShader:fragmentShader$5,transparent:!0,depthTest:!1,depthWrite:!1,premultipliedAlpha:!0});}}
|
|
40
|
+
|
|
41
|
+
const fragmentShader$4=`\n precision highp float;\n\n layout(location = 0) out highp vec4 frameDensityAlbedo;\n layout(location = 1) out highp vec4 frameDensityEmission;\n layout(location = 2) out highp vec4 frameDensityShadow0;\n layout(location = 3) out highp vec4 frameDensityShadow1;\n\n ${froxelPars}\n ${volumePars}\n ${gridPars}\n ${noisePars}\n\n uniform sampler2D frameTransmittanceMap0;\n uniform sampler2D frameTransmittanceMap1;\n uniform bool frameUseShadows;\n uniform mat4 frameCameraMatrixWorld;\n\n void main() {\n vec2 tile = floor(gl_FragCoord.xy / froxelGrid.xy);\n float slice = tile.y * froxelColumns + tile.x;\n vec2 uv = (gl_FragCoord.xy - tile * froxelGrid.xy) / froxelGrid.xy;\n\n float sliceNear = frameSliceDepth(slice / froxelGrid.z);\n float sliceFar = frameSliceDepth((slice + 1.0) / froxelGrid.z);\n vec3 viewNear = frameGetViewPosition(uv, sliceNear);\n vec3 viewFar = frameGetViewPosition(uv, sliceFar);\n vec3 localNear = (frameViewToLocal * vec4(viewNear, 1.0)).xyz;\n vec3 localFar = (frameViewToLocal * vec4(viewFar, 1.0)).xyz;\n\n vec2 boxSegment = frameBoxSegment(localNear, localFar);\n\n if (boxSegment.x >= boxSegment.y) {\n discard;\n }\n\n // NOTE Pick the mip level from the froxel width or the sub-step length, the larger one\n float subStepLength = length(viewFar - viewNear) / float(froxelSubSteps);\n float sliceMiddle = (sliceNear + sliceFar) * 0.5;\n float froxelWidth = length(\n frameGetViewPosition(uv + vec2(1.0 / froxelGrid.x, 0.0), sliceMiddle) -\n frameGetViewPosition(uv, sliceMiddle)\n );\n float lod = frameLod(max(froxelWidth, subStepLength) / frameWorldScale);\n\n // NOTE Mean extinction of the slice. The visibility-weighted mean position is the light position in the lighting pass.\n float extinctionSum = 0.0;\n float transmittance = 1.0;\n float visibleWeight = 0.0;\n float visibleTSum = 0.0;\n float jitter = frameNoise(gl_FragCoord.xy);\n\n for (int i = 0; i < froxelSubSteps; i++) {\n float t = (float(i) + jitter) / float(froxelSubSteps);\n\n if (t < boxSegment.x || t > boxSegment.y) {\n continue;\n }\n\n float extinction = frameSampleExtinction(mix(localNear, localFar, t), lod) * frameOpacity;\n float stepTransmittance = exp(-extinction * subStepLength);\n float weight = transmittance * (1.0 - stepTransmittance);\n\n visibleWeight += weight;\n visibleTSum += weight * t;\n extinctionSum += extinction;\n transmittance *= stepTransmittance;\n }\n\n float extinction = extinctionSum / float(froxelSubSteps);\n\n if (extinction <= 0.0) {\n discard;\n }\n\n float visibleT = visibleWeight > 0.0 ? visibleTSum / visibleWeight : 0.5;\n vec3 visiblePosition = mix(localNear, localFar, visibleT);\n vec3 albedo = frameSampleAlbedo(visiblePosition, lod);\n vec3 emission = frameSampleEmission(visiblePosition, lod) + frameScatterColor;\n\n // NOTE Shadows at the visible position of this volume, not of other volumes in the froxel\n vec4 shadow0 = vec4(1.0);\n vec4 shadow1 = vec4(1.0);\n\n if (frameUseShadows) {\n vec3 viewPosition = mix(viewNear, viewFar, visibleT);\n vec3 worldPosition = (frameCameraMatrixWorld * vec4(viewPosition, 1.0)).xyz;\n\n frameSampleShadows(\n frameTransmittanceMap0,\n frameTransmittanceMap1,\n worldPosition,\n shadow0,\n shadow1\n );\n }\n\n // NOTE Values are weighted by extinction. Additive blending then gives extinction-weighted means.\n frameDensityAlbedo = vec4(albedo * extinction, extinction);\n frameDensityEmission = vec4(emission * extinction, visibleT * extinction);\n frameDensityShadow0 = shadow0 * extinction;\n frameDensityShadow1 = shadow1 * extinction;\n }\n`;class FrameDensityMaterial extends Three.ShaderMaterial{name="FrameDensityMaterial";constructor(e){super({glslVersion:Three.GLSL3,uniforms:{...e,...createVolumeUniforms(),frameRect:{value:new Three.Vector4},frameSliceStart:{value:0}},vertexShader:tileQuadVertexShader,fragmentShader:fragmentShader$4,depthTest:!1,depthWrite:!1,transparent:!0,blending:Three.CustomBlending,blendEquation:Three.AddEquation,blendSrc:Three.OneFactor,blendDst:Three.OneFactor,blendSrcAlpha:Three.OneFactor,blendDstAlpha:Three.OneFactor});}}
|
|
42
|
+
|
|
43
|
+
const fragmentShader$3=`\n precision highp float;\n\n layout(location = 0) out highp vec4 frameShadowDensity;\n\n ${volumePars}\n ${gridPars}\n\n void main() {\n vec3 gridUvw;\n int cascade = frameGetGridCell(gl_FragCoord.xy, gridUvw);\n\n if (cascade >= frameGridCount) {\n discard;\n }\n\n vec3 worldPosition = frameGridMin[cascade] + gridUvw * frameGridSize[cascade];\n vec3 localPosition = (frameWorldToLocal * vec4(worldPosition, 1.0)).xyz;\n\n if (!frameInsideBox(localPosition)) {\n discard;\n }\n\n vec3 voxel = frameGridVoxel[cascade];\n float voxelSize = max(voxel.x, max(voxel.y, voxel.z));\n float lod = frameLod(voxelSize / frameWorldScale);\n\n // NOTE Absorbance scales the shadow of this volume, not its look from the camera\n float extinction = frameSampleExtinction(localPosition, lod) * frameAbsorbance;\n\n if (extinction <= 0.0) {\n discard;\n }\n\n frameShadowDensity = vec4(extinction, 0.0, 0.0, 0.0);\n }\n`;class FrameShadowDensityMaterial extends Three.ShaderMaterial{name="FrameShadowDensityMaterial";constructor(e){super({glslVersion:Three.GLSL3,uniforms:{...e,...createVolumeUniforms(),frameRect:{value:new Three.Vector4},frameSliceStart:{value:0}},vertexShader:tileQuadVertexShader,fragmentShader:fragmentShader$3,depthTest:!1,depthWrite:!1,transparent:!0,blending:Three.CustomBlending,blendEquation:Three.AddEquation,blendSrc:Three.OneFactor,blendDst:Three.OneFactor,blendSrcAlpha:Three.OneFactor,blendDstAlpha:Three.OneFactor});}}
|
|
44
|
+
|
|
45
|
+
const frameShadowLightSlots=6;const vertexShader$2="\n void main() {\n gl_Position = vec4(position.xy, 0.0, 1.0);\n }\n",fragmentShader$2=`\n precision highp float;\n\n #define FRAME_SHADOW_SLOTS 6\n #define FRAME_MAX_SHADOW_STEPS 1024\n\n layout(location = 0) out highp vec4 frameTransmittance0;\n layout(location = 1) out highp vec4 frameTransmittance1;\n\n ${gridPars}\n ${noisePars}\n\n uniform sampler2D frameShadowDensityMap;\n uniform vec3 frameSkyDirection;\n uniform vec4 frameShadowLights[FRAME_SHADOW_SLOTS];\n uniform int frameShadowLightCount;\n\n float frameGridExtinction(vec3 worldPosition) {\n int cascade = frameFindCascade(worldPosition, 0);\n\n if (cascade < 0) {\n return 0.0;\n }\n\n return frameSampleGrid(frameShadowDensityMap, cascade, frameGridUvw(cascade, worldPosition)).r;\n }\n\n // NOTE Each step moves one voxel of the finest cascade at the probe, on the fastest axis of the direction.\n // The march goes to coarser cascades when it leaves a fine cascade.\n float frameMarch(vec3 origin, vec3 direction, float maxDistance, float jitter) {\n float opticalDepth = 0.0;\n float traveled = 0.0;\n vec3 probe = origin;\n\n for (int i = 0; i < FRAME_MAX_SHADOW_STEPS; i++) {\n int cascade = frameFindCascade(probe, 0);\n\n if (cascade < 0 || traveled >= maxDistance || opticalDepth > 6.0) {\n break;\n }\n\n vec3 voxelsPerUnit = abs(direction) / frameGridVoxel[cascade];\n float stepLength = 1.0 / max(voxelsPerUnit.x, max(voxelsPerUnit.y, voxelsPerUnit.z));\n\n stepLength = min(stepLength, maxDistance - traveled);\n\n opticalDepth += frameGridExtinction(probe + direction * stepLength * jitter) * stepLength;\n probe += direction * stepLength;\n traveled += stepLength;\n }\n\n return exp(-opticalDepth);\n }\n\n float frameLightTransmittance(int slot, vec3 worldPosition, float jitter) {\n if (slot >= frameShadowLightCount) {\n return 1.0;\n }\n\n vec4 light = frameShadowLights[slot];\n\n // NOTE w is 1.0 for a light direction, 2.0 for a light position\n if (light.w < 1.5) {\n return frameMarch(worldPosition, normalize(light.xyz), 1e20, jitter);\n }\n\n vec3 toLight = light.xyz - worldPosition;\n\n return frameMarch(worldPosition, normalize(toLight), length(toLight), jitter);\n }\n\n void main() {\n vec3 gridUvw;\n int cascade = frameGetGridCell(gl_FragCoord.xy, gridUvw);\n\n if (cascade >= frameGridCount) {\n frameTransmittance0 = vec4(1.0);\n frameTransmittance1 = vec4(1.0);\n\n return;\n }\n\n vec3 worldPosition = frameGridMin[cascade] + gridUvw * frameGridSize[cascade];\n\n // NOTE Skip voxels with no extinction near them. The lighting pass does not read them.\n float nearExtinction = 0.0;\n\n for (int i = 0; i < 8; i++) {\n vec3 corner = vec3(float(i & 1), float((i >> 1) & 1), float((i >> 2) & 1)) - 0.5;\n\n nearExtinction += frameGridExtinction(worldPosition + corner * frameGridVoxel[cascade]);\n }\n\n if (nearExtinction <= 0.0) {\n frameTransmittance0 = vec4(1.0);\n frameTransmittance1 = vec4(1.0);\n\n return;\n }\n\n float jitter = frameNoise(gl_FragCoord.xy);\n\n frameTransmittance0 = vec4(\n frameMarch(worldPosition, frameSkyDirection, 1e20, jitter),\n frameMarch(worldPosition, -frameSkyDirection, 1e20, jitter),\n frameLightTransmittance(0, worldPosition, jitter),\n frameLightTransmittance(1, worldPosition, jitter)\n );\n\n frameTransmittance1 = vec4(\n frameLightTransmittance(2, worldPosition, jitter),\n frameLightTransmittance(3, worldPosition, jitter),\n frameLightTransmittance(4, worldPosition, jitter),\n frameLightTransmittance(5, worldPosition, jitter)\n );\n }\n`;class FrameTransmittanceMaterial extends Three.ShaderMaterial{name="FrameTransmittanceMaterial";constructor(e){super({glslVersion:Three.GLSL3,uniforms:{...e,frameShadowDensityMap:{value:null},frameSkyDirection:{value:new Three.Vector3(0,1,0)},frameShadowLights:{value:Array.from({length:6},(()=>new Three.Vector4))},frameShadowLightCount:{value:0}},vertexShader:vertexShader$2,fragmentShader:fragmentShader$2,depthTest:!1,depthWrite:!1,blending:Three.NoBlending});}}
|
|
46
|
+
|
|
47
|
+
const vertexShader$1="\n void main() {\n gl_Position = vec4(position.xy, 0.0, 1.0);\n }\n",fragmentShader$1=`\n precision highp float;\n\n ${froxelPars}\n\n uniform sampler2D frameDensityAlbedoMap;\n uniform sampler2D frameDensityEmissionMap;\n uniform sampler2D frameDensityShadowMap0;\n uniform sampler2D frameDensityShadowMap1;\n uniform vec3 frameSkyDirection;\n uniform int frameShadowLightCount;\n uniform int frameShadowPointOffset;\n uniform int frameShadowSpotOffset;\n\n #include <common>\n #include <lights_pars_begin>\n #include <envmap_common_pars_fragment>\n #include <cube_uv_reflection_fragment>\n #include <envmap_physical_pars_fragment>\n\n float frameShadowSlot(int slot, vec4 shadow0, vec4 shadow1) {\n if (slot >= frameShadowLightCount) {\n return 1.0;\n }\n\n if (slot == 0) return shadow0.z;\n if (slot == 1) return shadow0.w;\n if (slot == 2) return shadow1.x;\n if (slot == 3) return shadow1.y;\n if (slot == 4) return shadow1.z;\n if (slot == 5) return shadow1.w;\n\n return 1.0;\n }\n\n void main() {\n ivec2 texel = ivec2(gl_FragCoord.xy);\n vec4 densityAlbedo = texelFetch(frameDensityAlbedoMap, texel, 0);\n float extinction = densityAlbedo.a;\n\n if (extinction <= 0.0) {\n discard;\n }\n\n vec4 densityEmission = texelFetch(frameDensityEmissionMap, texel, 0);\n\n vec2 tile = floor(gl_FragCoord.xy / froxelGrid.xy);\n float slice = tile.y * froxelColumns + tile.x;\n vec2 uv = (gl_FragCoord.xy - tile * froxelGrid.xy) / froxelGrid.xy;\n\n float sliceNear = frameSliceDepth(slice / froxelGrid.z);\n float sliceFar = frameSliceDepth((slice + 1.0) / froxelGrid.z);\n vec3 viewNear = frameGetViewPosition(uv, sliceNear);\n vec3 viewFar = frameGetViewPosition(uv, sliceFar);\n\n // NOTE Spot light angles use the extinction-weighted visible position of all volumes\n float visibleT = clamp(densityEmission.a / extinction, 0.0, 1.0);\n vec3 viewPosition = mix(viewNear, viewFar, visibleT);\n\n // NOTE Extinction-weighted mean transmittance. Each volume sampled its own visible position.\n vec4 shadow0 = texelFetch(frameDensityShadowMap0, texel, 0) / extinction;\n vec4 shadow1 = texelFetch(frameDensityShadowMap1, texel, 0) / extinction;\n\n vec3 skyDirection = normalize(mat3(viewMatrix) * frameSkyDirection);\n vec3 irradiance = getAmbientLightIrradiance(ambientLightColor);\n\n #if defined( USE_LIGHT_PROBES )\n irradiance += 0.5 * getLightProbeIrradiance(lightProbe, skyDirection) * shadow0.x;\n irradiance += 0.5 * getLightProbeIrradiance(lightProbe, -skyDirection) * shadow0.y;\n #endif\n\n #if defined( USE_ENVMAP )\n irradiance += 0.5 * getIBLIrradiance(skyDirection) * shadow0.x;\n irradiance += 0.5 * getIBLIrradiance(-skyDirection) * shadow0.y;\n #endif\n\n #if NUM_HEMI_LIGHTS > 0\n for (int i = 0; i < NUM_HEMI_LIGHTS; i++) {\n HemisphereLight hemiLight = hemisphereLights[i];\n\n irradiance += 0.5 * hemiLight.skyColor * shadow0.x;\n irradiance += 0.5 * hemiLight.groundColor * shadow0.y;\n }\n #endif\n\n #if NUM_DIR_LIGHTS > 0\n for (int i = 0; i < NUM_DIR_LIGHTS; i++) {\n irradiance += directionalLights[i].color * frameShadowSlot(i, shadow0, shadow1);\n }\n #endif\n\n // NOTE Point and spot lights have no distance falloff. This is the same as FogVolume.\n\n #if NUM_POINT_LIGHTS > 0\n for (int i = 0; i < NUM_POINT_LIGHTS; i++) {\n irradiance += pointLights[i].color * frameShadowSlot(frameShadowPointOffset + i, shadow0, shadow1);\n }\n #endif\n\n #if NUM_SPOT_LIGHTS > 0\n for (int i = 0; i < NUM_SPOT_LIGHTS; i++) {\n SpotLight spotLight = spotLights[i];\n float angleCos = dot(normalize(spotLight.position - viewPosition), spotLight.direction);\n float spotAttenuation = getSpotAttenuation(spotLight.coneCos, spotLight.penumbraCos, angleCos);\n\n irradiance += spotLight.color * spotAttenuation * frameShadowSlot(frameShadowSpotOffset + i, shadow0, shadow1);\n }\n #endif\n\n vec3 albedo = densityAlbedo.rgb / extinction;\n vec3 emission = densityEmission.rgb / extinction;\n vec3 radiance = albedo * irradiance * RECIPROCAL_PI + emission;\n\n float alpha = 1.0 - exp(-extinction * length(viewFar - viewNear));\n\n gl_FragColor = vec4(radiance * alpha, alpha);\n }\n`;class FrameLightingMaterial extends Three.MeshStandardMaterial{name="FrameLightingMaterial";customProgramCacheKey=()=>"FrameLightingMaterial";constructor(e){super(),this.frameUniforms={...e,frameDensityAlbedoMap:{value:null},frameDensityEmissionMap:{value:null},frameDensityShadowMap0:{value:null},frameDensityShadowMap1:{value:null},frameSkyDirection:{value:new Three.Vector3(0,1,0)},frameShadowLightCount:{value:0},frameShadowPointOffset:{value:0},frameShadowSpotOffset:{value:0}},this.depthTest=!1,this.depthWrite=!1,this.side=Three.DoubleSide,this.blending=Three.NoBlending,this.onBeforeCompile=e=>{Object.assign(e.uniforms,this.frameUniforms),e.vertexShader=vertexShader$1,e.fragmentShader=fragmentShader$1;};}}
|
|
48
|
+
|
|
49
|
+
const vertexShader="\n void main() {\n gl_Position = vec4(position.xy, 0.0, 1.0);\n }\n",fragmentShader=`\n precision highp float;\n\n layout(location = 0) out highp vec4 frameResolved;\n\n ${froxelPars}\n\n uniform sampler2D frameCurrentMap;\n uniform sampler2D frameHistoryMap;\n uniform vec2 frameAtlasSize;\n uniform bool frameUseHistory;\n uniform float frameTemporalBlend;\n uniform mat4 frameCameraMatrixWorld;\n uniform mat4 framePreviousViewMatrix;\n uniform mat4 framePreviousProjectionMatrix;\n uniform vec2 framePreviousRange;\n\n vec2 frameGetTileOrigin(float slice) {\n return vec2(mod(slice, froxelColumns), floor(slice / froxelColumns)) * froxelGrid.xy;\n }\n\n // NOTE Bilinear filter in the slice. Half a cell inside the tile, then the filter does not read other tiles.\n vec4 frameSampleHistory(float slice, vec2 uv) {\n vec2 cell = clamp(uv * froxelGrid.xy, vec2(0.5), froxelGrid.xy - 0.5);\n\n return texture(frameHistoryMap, (frameGetTileOrigin(slice) + cell) / frameAtlasSize);\n }\n\n void main() {\n ivec2 texel = ivec2(gl_FragCoord.xy);\n vec4 current = texelFetch(frameCurrentMap, texel, 0);\n\n vec2 tile = floor(gl_FragCoord.xy / froxelGrid.xy);\n float slice = tile.y * froxelColumns + tile.x;\n\n if (!frameUseHistory || slice >= froxelGrid.z) {\n frameResolved = current;\n\n return;\n }\n\n // NOTE Reproject the froxel center into the froxel grid of the previous frame\n vec2 cell = gl_FragCoord.xy - tile * froxelGrid.xy;\n vec2 uv = cell / froxelGrid.xy;\n float viewDepth = frameSliceDepth((slice + 0.5) / froxelGrid.z);\n vec4 worldPosition = frameCameraMatrixWorld * vec4(frameGetViewPosition(uv, viewDepth), 1.0);\n vec4 previousView = framePreviousViewMatrix * worldPosition;\n vec4 previousClip = framePreviousProjectionMatrix * previousView;\n\n if (previousClip.w <= 0.0) {\n frameResolved = current;\n\n return;\n }\n\n vec2 previousUv = previousClip.xy / previousClip.w * 0.5 + 0.5;\n float previousDepth = -previousView.z;\n float previousT = log(max(previousDepth, 1e-6) / framePreviousRange.x) / log(framePreviousRange.y / framePreviousRange.x);\n float previousSlice = previousT * froxelGrid.z - 0.5;\n\n bool outside = any(lessThan(previousUv, vec2(0.0))) || any(greaterThan(previousUv, vec2(1.0)));\n\n if (outside || previousSlice < -0.5 || previousSlice > froxelGrid.z - 0.5) {\n frameResolved = current;\n\n return;\n }\n\n float slice0 = clamp(floor(previousSlice), 0.0, froxelGrid.z - 1.0);\n float slice1 = min(slice0 + 1.0, froxelGrid.z - 1.0);\n vec4 history = mix(\n frameSampleHistory(slice0, previousUv),\n frameSampleHistory(slice1, previousUv),\n clamp(previousSlice - slice0, 0.0, 1.0)\n );\n\n // NOTE Clamp the history to the current neighbors. This limits ghosting of moving volumes.\n vec4 neighborMin = current;\n vec4 neighborMax = current;\n vec2 tileOrigin = tile * froxelGrid.xy;\n vec2 cellMax = froxelGrid.xy - 1.0;\n\n vec2 neighborCells[4];\n neighborCells[0] = vec2(max(cell.x - 1.0, 0.5), cell.y);\n neighborCells[1] = vec2(min(cell.x + 1.0, cellMax.x + 0.5), cell.y);\n neighborCells[2] = vec2(cell.x, max(cell.y - 1.0, 0.5));\n neighborCells[3] = vec2(cell.x, min(cell.y + 1.0, cellMax.y + 0.5));\n\n for (int i = 0; i < 4; i++) {\n vec4 neighbor = texelFetch(frameCurrentMap, ivec2(tileOrigin + neighborCells[i]), 0);\n\n neighborMin = min(neighborMin, neighbor);\n neighborMax = max(neighborMax, neighbor);\n }\n\n for (int i = -1; i <= 1; i += 2) {\n float neighborSlice = slice + float(i);\n\n if (neighborSlice >= 0.0 && neighborSlice < froxelGrid.z) {\n vec4 neighbor = texelFetch(frameCurrentMap, ivec2(frameGetTileOrigin(neighborSlice) + cell), 0);\n\n neighborMin = min(neighborMin, neighbor);\n neighborMax = max(neighborMax, neighbor);\n }\n }\n\n frameResolved = mix(clamp(history, neighborMin, neighborMax), current, frameTemporalBlend);\n }\n`;class FrameTemporalMaterial extends Three.ShaderMaterial{name="FrameTemporalMaterial";constructor(e){super({glslVersion:Three.GLSL3,uniforms:{...e,frameCurrentMap:{value:null},frameHistoryMap:{value:null},frameAtlasSize:{value:new Three.Vector2(1,1)},frameUseHistory:{value:!1},frameTemporalBlend:{value:.1},frameCameraMatrixWorld:{value:new Three.Matrix4},framePreviousViewMatrix:{value:new Three.Matrix4},framePreviousProjectionMatrix:{value:new Three.Matrix4},framePreviousRange:{value:new Three.Vector2(.1,1)}},vertexShader:vertexShader,fragmentShader:fragmentShader,depthTest:!1,depthWrite:!1,blending:Three.NoBlending});}}
|
|
50
|
+
|
|
51
|
+
const unitBox=new Three.Box3(new Three.Vector3(-.5,-.5,-.5),new Three.Vector3(.5,.5,.5)),viewBox=new Three.Box3,casterBox=new Three.Box3,cascadeBox=new Three.Box3,frustum=new Three.Frustum,projectionScreenMatrix=new Three.Matrix4,clipMatrix=new Three.Matrix4,viewport=new Three.Vector4,corner=new Three.Vector4,clearColor=new Three.Color,vector3=new Three.Vector3,lightProxyFactories={AmbientLight:()=>new Three.AmbientLight,HemisphereLight:()=>new Three.HemisphereLight,DirectionalLight:()=>new Three.DirectionalLight,PointLight:()=>new Three.PointLight,SpotLight:()=>new Three.SpotLight,LightProbe:()=>new Three.LightProbe};const createFullscreenGeometry=()=>{const e=new Three.BufferGeometry;return e.setAttribute("position",new Three.Float32BufferAttribute([-1,-1,0,3,-1,0,-1,3,0],3)),e};const tileQuadPosition=new Three.Float32BufferAttribute([-1,-1,0,1,-1,0,1,1,0,-1,-1,0,1,1,0,-1,1,0],3),createRenderTarget=(e,t,r={})=>new Three.WebGLRenderTarget(e,t,{type:Three.HalfFloatType,format:Three.RGBAFormat,minFilter:Three.LinearFilter,magFilter:Three.LinearFilter,generateMipmaps:!1,depthBuffer:!1,...r}),createScene=()=>{const e=new Three.Scene;return e.matrixWorldAutoUpdate=!1,e},getAtlasLayout=(e,t,r)=>{const a=Math.ceil(Math.sqrt(r*t/e));return {columns:a,width:e*a,height:t*Math.ceil(r/a)}};class FrameRenderer{settings={width:512,height:512,depth:24,subSteps:32,near:.1,far:1/0,jitter:!1,temporal:!0,temporalBlend:.2,lighting:"frame",shadowResolution:64,shadowCascades:3,shadowDistance:512};froxelUniforms={froxelGrid:{value:new Three.Vector3(1,1,1)},froxelColumns:{value:1},froxelRange:{value:new Three.Vector2(.1,1)},froxelProjectionInverse:{value:new Three.Matrix4},froxelSubSteps:{value:6},frameJitter:{value:.5}};froxelTileUniforms={frameTileSize:{value:new Three.Vector2(1,1)},frameTileColumns:{value:1},frameTargetSize:{value:new Three.Vector2(1,1)}};gridUniforms={frameGridMin:{value:Array.from({length:frameMaxShadowCascades},(()=>new Three.Vector3))},frameGridSize:{value:Array.from({length:frameMaxShadowCascades},(()=>new Three.Vector3))},frameGridVoxel:{value:Array.from({length:frameMaxShadowCascades},(()=>new Three.Vector3))},frameGridCount:{value:0},frameGridResolution:{value:1},frameGridColumns:{value:1},frameGridAtlasSize:{value:new Three.Vector2(1,1)},frameJitter:this.froxelUniforms.frameJitter};gridTileUniforms={frameTileSize:{value:new Three.Vector2(1,1)},frameTileColumns:{value:1},frameTargetSize:{value:new Three.Vector2(1,1)}};densityShadowUniforms={frameTransmittanceMap0:{value:null},frameTransmittanceMap1:{value:null},frameUseShadows:{value:!1},frameCameraMatrixWorld:{value:new Three.Matrix4}};injectMaterial=new FrameInjectMaterial(this.froxelUniforms);integrateMaterial=new FrameIntegrateMaterial(this.froxelUniforms);lightingMaterial=new FrameLightingMaterial(this.froxelUniforms);transmittanceMaterial=new FrameTransmittanceMaterial(this.gridUniforms);depthMaterial=new Three.MeshBasicMaterial({colorWrite:!1,side:Three.DoubleSide});injectScene=createScene();injectMesh=new Three.Mesh(createFullscreenGeometry(),this.injectMaterial);lightingMesh=new Three.Mesh(createFullscreenGeometry(),this.lightingMaterial);lightProxies={};densityScene=createScene();densityPool=[];shadowScene=createScene();shadowPool=[];transmittanceScene=createScene();transmittanceMesh=new Three.Mesh(createFullscreenGeometry(),this.transmittanceMaterial);temporalMaterial=new FrameTemporalMaterial(this.froxelUniforms);temporalScene=createScene();temporalMesh=new Three.Mesh(createFullscreenGeometry(),this.temporalMaterial);historyTargets=[null,null];historyIndex=0;historyValid=!1;historyCamera=null;previousViewMatrix=new Three.Matrix4;previousProjectionMatrix=new Three.Matrix4;previousRange=new Three.Vector2(.1,1);jitterIndex=0;atlasTarget=null;densityTarget=null;shadowDensityTarget=null;transmittanceTarget=null;depthTarget=null;cachedFrame=-1;cachedCamera=null;constructor(){[this.injectMesh,this.lightingMesh,this.transmittanceMesh,this.temporalMesh].forEach((e=>{e.frustumCulled=!1;})),this.temporalScene.add(this.temporalMesh),this.injectScene.add(this.injectMesh,this.lightingMesh),this.transmittanceScene.add(this.transmittanceMesh);}render(e,t,r,a){const i=e.info.render.frame;if(t.overrideMaterial)return void a.geometry.setDrawRange(0,0);if(i===this.cachedFrame&&r===this.cachedCamera)return void a.geometry.setDrawRange(0,0);const{volumes:s,casters:o}=this.collectVolumes(t,r);s.length>0&&this.renderFrame(e,t,r,s,o),this.cachedFrame=e.info.render.frame,this.cachedCamera=r,a.geometry.setDrawRange(0,s.length>0?1/0:0);}isFrameLighting(){return "frame"===this.settings.lighting}collectVolumes(e,t){const{settings:r}=this,a=[],i=[],s=this.isFrameLighting();projectionScreenMatrix.multiplyMatrices(t.projectionMatrix,t.matrixWorldInverse),frustum.setFromProjectionMatrix(projectionScreenMatrix);let o=1/0,n=0;return e.traverseVisible((e=>{e.isFrameVolume&&e.proxy.layers.test(t.layers)&&e.volumes.forEach((r=>{r.ready&&(r.matrixWorld.multiplyMatrices(e.matrixWorld,r.matrix),r.worldToLocal.copy(r.matrixWorld).invert(),r.worldBox.copy(unitBox).applyMatrix4(r.matrixWorld),s&&i.push({frameVolume:e,volume:r}),frustum.intersectsBox(r.worldBox)&&(r.localToView.multiplyMatrices(t.matrixWorldInverse,r.matrixWorld),r.viewToLocal.copy(r.localToView).invert(),viewBox.copy(unitBox).applyMatrix4(r.localToView),r.viewNear=-viewBox.max.z,r.viewFar=-viewBox.min.z,o=Math.min(o,r.viewNear),n=Math.max(n,r.viewFar),a.push({frameVolume:e,volume:r})));}));})),o=Math.max(o,r.near,t.near),n=Math.min(n,r.far,t.far),0===a.length||n<=o?{volumes:[],casters:[]}:(this.froxelUniforms.froxelRange.value.set(o,Math.max(n,1.0001*o)),{volumes:a,casters:i})}getSlice(e){const t=this.froxelUniforms.froxelRange.value;return this.froxelUniforms.froxelGrid.value.z*Math.log(Math.max(e,t.x)/t.x)/Math.log(t.y/t.x)}getSliceRange(e){const t=this.froxelUniforms.froxelGrid.value.z,r=Math.max(0,Math.floor(this.getSlice(e.viewNear)));return {first:r,count:Math.min(t-1,Math.ceil(this.getSlice(e.viewFar)))-r+1}}updateTargets(e){const{width:t,height:r,depth:a,subSteps:i,shadowResolution:s,shadowCascades:o}=this.settings,n=getAtlasLayout(t,r,a);if(!this.atlasTarget||this.atlasTarget.width!==n.width||this.atlasTarget.height!==n.height){const{maxTextureSize:t}=e.capabilities;(n.width>t||n.height>t)&&console.warn("FrameRenderer: froxel atlas is larger than maxTextureSize. Decrease width, height or depth."),this.atlasTarget&&this.atlasTarget.dispose(),this.atlasTarget=createRenderTarget(n.width,n.height),this.historyValid=!1;}if(this.froxelUniforms.froxelGrid.value.set(t,r,a),this.froxelUniforms.froxelColumns.value=n.columns,this.froxelUniforms.froxelSubSteps.value=Math.max(1,Math.round(i)),this.froxelTileUniforms.frameTileSize.value.set(t,r),this.froxelTileUniforms.frameTileColumns.value=n.columns,this.froxelTileUniforms.frameTargetSize.value.set(n.width,n.height),this.integrateMaterial.uniforms.froxelAtlas.value=this.atlasTarget.texture,this.settings.temporal&&this.historyTargets.forEach(((e,t)=>{e&&e.width===n.width&&e.height===n.height||(e&&e.dispose(),this.historyTargets[t]=createRenderTarget(n.width,n.height),this.historyValid=!1);})),this.integrateMaterial.uniforms.froxelAtlasSize.value.set(n.width,n.height),this.isFrameLighting()){this.densityTarget&&this.densityTarget.width===n.width&&this.densityTarget.height===n.height||(this.densityTarget&&this.densityTarget.dispose(),this.densityTarget=createRenderTarget(n.width,n.height,{count:4,minFilter:Three.NearestFilter,magFilter:Three.NearestFilter}));const e=Math.max(2,Math.round(s)),t=this.getCascadeCount(),r=getAtlasLayout(e,e,e*t);this.shadowDensityTarget&&this.shadowDensityTarget.width===r.width&&this.shadowDensityTarget.height===r.height||(this.shadowDensityTarget&&(this.shadowDensityTarget.dispose(),this.transmittanceTarget.dispose()),this.shadowDensityTarget=createRenderTarget(r.width,r.height,{format:Three.RedFormat}),this.transmittanceTarget=createRenderTarget(r.width,r.height,{count:2})),this.gridUniforms.frameGridResolution.value=e,this.gridUniforms.frameGridColumns.value=r.columns,this.gridUniforms.frameGridAtlasSize.value.set(r.width,r.height),this.gridTileUniforms.frameTileSize.value.set(e,e),this.gridTileUniforms.frameTileColumns.value=r.columns,this.gridTileUniforms.frameTargetSize.value.set(r.width,r.height);}e.getCurrentViewport(viewport);const h=Math.max(1,viewport.z),l=Math.max(1,viewport.w);this.depthTarget?this.depthTarget.width===h&&this.depthTarget.height===l||this.depthTarget.setSize(h,l):this.depthTarget=new Three.WebGLRenderTarget(h,l,{depthBuffer:!0,depthTexture:new Three.DepthTexture(h,l)}),this.integrateMaterial.uniforms.frameDepthMap.value=this.depthTarget.depthTexture;}renderFrame(e,t,r,a,i){const s=e.getRenderTarget(),o=e.getActiveCubeFace(),n=e.getActiveMipmapLevel(),h=e.autoClear,l=e.getClearAlpha(),m=e.shadowMap.autoUpdate,c=e.shadowMap.needsUpdate;e.getClearColor(clearColor),this.updateTargets(e),this.froxelUniforms.froxelProjectionInverse.value.copy(r.projectionMatrixInverse),this.integrateMaterial.uniforms.frameCameraNear.value=r.near,this.integrateMaterial.uniforms.frameCameraFar.value=r.far,e.autoClear=!1,e.shadowMap.autoUpdate=!1,e.shadowMap.needsUpdate=!1,e.setClearColor(0,0),this.froxelUniforms.frameJitter.value=this.settings.jitter?.618034*this.jitterIndex%1:-1,this.jitterIndex=(this.jitterIndex+1)%1024,this.renderDepth(e,t,r),this.isFrameLighting()?this.renderFrameLighting(e,t,r,a,i):this.renderInject(e,t,r,a),this.settings.temporal?this.renderTemporal(e,r):this.historyValid=!1,e.shadowMap.autoUpdate=m,e.shadowMap.needsUpdate=c,e.autoClear=h,e.setClearColor(clearColor,l),e.setRenderTarget(s,o,n);}renderTemporal(e,t){const r=this.temporalMaterial.uniforms,a=this.historyTargets[this.historyIndex],i=this.historyTargets[1-this.historyIndex];r.frameCurrentMap.value=this.atlasTarget.texture,r.frameHistoryMap.value=a.texture,r.frameAtlasSize.value.set(this.atlasTarget.width,this.atlasTarget.height),r.frameUseHistory.value=this.historyValid&&this.historyCamera===t,r.frameTemporalBlend.value=Math.min(1,Math.max(.01,this.settings.temporalBlend)),r.frameCameraMatrixWorld.value.copy(t.matrixWorld),r.framePreviousViewMatrix.value.copy(this.previousViewMatrix),r.framePreviousProjectionMatrix.value.copy(this.previousProjectionMatrix),r.framePreviousRange.value.copy(this.previousRange),this.temporalMesh.layers.mask=t.layers.mask,e.setRenderTarget(i),e.render(this.temporalScene,t),this.integrateMaterial.uniforms.froxelAtlas.value=i.texture,this.historyIndex=1-this.historyIndex,this.historyValid=!0,this.historyCamera=t,this.previousViewMatrix.copy(t.matrixWorldInverse),this.previousProjectionMatrix.copy(t.projectionMatrix),this.previousRange.copy(this.froxelUniforms.froxelRange.value);}renderDepth(e,t,r){const a=[];t.traverseVisible((e=>{if(!e.material)return;(Array.isArray(e.material)?e.material:[e.material]).every((e=>!1===e.depthWrite))&&a.push(e);})),a.forEach((e=>{e.visible=!1;}));const i=t.background,s=t.overrideMaterial;t.background=null,t.overrideMaterial=this.depthMaterial,e.setRenderTarget(this.depthTarget),e.clear(!1,!0,!1),e.render(t,r),t.overrideMaterial=s,t.background=i,a.forEach((e=>{e.visible=!0;}));}prepareLightScene(e,t,r){const{injectScene:a}=this;return a.environment=e.environment,a.environmentIntensity=e.environmentIntensity,a.environmentRotation.copy(e.environmentRotation),this.injectMesh.visible=r===this.injectMesh,this.lightingMesh.visible=r===this.lightingMesh,r.layers.mask=t.layers.mask,this.updateLightProxies(e,t)}renderInject(e,t,r,a){const{atlasTarget:i,injectScene:s}=this,{froxelGrid:o,froxelColumns:n}=this.froxelUniforms,h=this.injectMaterial.frameUniforms;this.prepareLightScene(t,r,this.injectMesh),i.scissorTest=!1,e.setRenderTarget(i),e.clear(!0,!1,!1);const l=n.value,m=o.value.y;a.forEach((({frameVolume:t,volume:a})=>{setVolumeUniforms(h,t,a),h.frameLights.value=t.lights,h.frameShadowSteps.value=t.shadowSteps;const{first:o,count:n}=this.getSliceRange(a),c=Math.floor(o/l),d=Math.floor((o+n-1)/l);i.scissor.set(0,c*m,i.width,(d-c+1)*m),i.scissorTest=!0,e.setRenderTarget(i),e.render(s,r);})),i.scissorTest=!1;}renderFrameLighting(e,t,r,a,i){const s=this.prepareLightScene(t,r,this.lightingMesh),o=i.length>0&&this.updateGrid(i,r);if(o){const t=[];for(let e=0;e<this.gridUniforms.frameGridCount.value;e++)i.forEach((r=>t.push({...r,cascade:e})));this.updatePool(this.shadowPool,this.shadowScene,t,r,((e,{frameVolume:t,volume:r,cascade:a})=>{const i=this.getGridRange(r,a);return !!i&&(setVolumeUniforms(e.material.uniforms,t,r),e.material.uniforms.frameRect.value.copy(i.rect),e.material.uniforms.frameSliceStart.value=i.first,e.geometry.instanceCount=i.count,!0)}),(()=>new FrameShadowDensityMaterial({...this.gridUniforms,...this.gridTileUniforms}))),e.setRenderTarget(this.shadowDensityTarget),e.clear(!0,!1,!1),e.render(this.shadowScene,r);const a=this.transmittanceMaterial.uniforms;a.frameShadowDensityMap.value=this.shadowDensityTarget.texture,a.frameSkyDirection.value.copy(s.skyDirection),a.frameShadowLightCount.value=s.shadowLights.length,s.shadowLights.forEach(((e,t)=>{a.frameShadowLights.value[t].copy(e);})),this.transmittanceMesh.layers.mask=r.layers.mask,e.setRenderTarget(this.transmittanceTarget),e.render(this.transmittanceScene,r);}const{densityShadowUniforms:n}=this;n.frameUseShadows.value=o,n.frameTransmittanceMap0.value=this.transmittanceTarget.textures[0],n.frameTransmittanceMap1.value=this.transmittanceTarget.textures[1],n.frameCameraMatrixWorld.value.copy(r.matrixWorld),this.updatePool(this.densityPool,this.densityScene,a,r,((e,{frameVolume:t,volume:a})=>{const i=this.getFroxelRect(a,r);if(!i)return !1;const{first:s,count:o}=this.getSliceRange(a);return setVolumeUniforms(e.material.uniforms,t,a),e.material.uniforms.frameRect.value.copy(i),e.material.uniforms.frameSliceStart.value=s,e.geometry.instanceCount=o,!0}),(()=>new FrameDensityMaterial({...this.froxelUniforms,...this.froxelTileUniforms,...this.gridUniforms,...this.densityShadowUniforms}))),e.setRenderTarget(this.densityTarget),e.clear(!0,!1,!1),e.render(this.densityScene,r);const h=this.lightingMaterial.frameUniforms;h.frameDensityAlbedoMap.value=this.densityTarget.textures[0],h.frameDensityEmissionMap.value=this.densityTarget.textures[1],h.frameDensityShadowMap0.value=this.densityTarget.textures[2],h.frameDensityShadowMap1.value=this.densityTarget.textures[3],h.frameSkyDirection.value.copy(s.skyDirection),h.frameShadowLightCount.value=s.shadowLights.length,h.frameShadowPointOffset.value=s.pointOffset,h.frameShadowSpotOffset.value=s.spotOffset,e.setRenderTarget(this.atlasTarget),e.clear(!0,!1,!1),e.render(this.injectScene,r);}updatePool(e,t,r,a,i,s){let o=0;r.forEach((r=>{if(!e[o]){const r=new Three.InstancedBufferGeometry;r.setAttribute("position",tileQuadPosition);const a=s(),i=new Three.Mesh(r,a);i.frustumCulled=!1,i.matrixAutoUpdate=!1,t.add(i),e[o]={mesh:i,material:a,geometry:r};}const n=e[o];i(n,r)&&(n.mesh.visible=!0,n.mesh.layers.mask=a.layers.mask,o++);}));for(let t=o;t<e.length;t++)e[t].mesh.visible=!1;}getFroxelRect(e,t){const{x:r,y:a}=this.froxelUniforms.froxelGrid.value;let i=1/0,s=1/0,o=-1/0,n=-1/0,h=!1;clipMatrix.multiplyMatrices(t.projectionMatrix,e.localToView);for(let e=0;e<8&&!h;e++)corner.set(1&e?.5:-.5,2&e?.5:-.5,4&e?.5:-.5,1).applyMatrix4(clipMatrix),corner.w<=1e-6?h=!0:(i=Math.min(i,corner.x/corner.w),s=Math.min(s,corner.y/corner.w),o=Math.max(o,corner.x/corner.w),n=Math.max(n,corner.y/corner.w));if(h)return new Three.Vector4(0,0,r,a);const l=Math.max(0,Math.floor((.5*i+.5)*r)-1),m=Math.max(0,Math.floor((.5*s+.5)*a)-1),c=Math.min(r,Math.ceil((.5*o+.5)*r)+1),d=Math.min(a,Math.ceil((.5*n+.5)*a)+1);return c<=l||d<=m?null:new Three.Vector4(l,m,c,d)}getCascadeCount(){return Math.min(frameMaxShadowCascades,Math.max(1,Math.round(this.settings.shadowCascades)))}updateGrid(e,t){const r=this.gridUniforms.frameGridResolution.value,a=this.getCascadeCount(),{frameGridMin:i,frameGridSize:s,frameGridVoxel:o,frameGridCount:n}=this.gridUniforms;if(casterBox.makeEmpty(),e.forEach((({volume:e})=>casterBox.union(e.worldBox))),casterBox.isEmpty())return !1;vector3.setFromMatrixPosition(t.matrixWorld);const h=Math.min(this.settings.shadowDistance,.5*Math.max(casterBox.max.x-casterBox.min.x,casterBox.max.y-casterBox.min.y,casterBox.max.z-casterBox.min.z));let l=0;for(let e=0;e<a;e++){if(e===a-1)cascadeBox.copy(casterBox);else {const t=h/Math.pow(2,a-1-e);cascadeBox.min.copy(vector3).subScalar(t),cascadeBox.max.copy(vector3).addScalar(t),cascadeBox.intersect(casterBox);}cascadeBox.isEmpty()||e<a-1&&cascadeBox.equals(casterBox)||(["x","y","z"].forEach((e=>{const t=Math.max(cascadeBox.max[e]-cascadeBox.min[e],1e-6),a=Math.pow(2,Math.ceil(Math.log2(t/(r-1))));o.value[l][e]=a,i.value[l][e]=Math.floor(cascadeBox.min[e]/a)*a,s.value[l][e]=a*r;})),l++);}return n.value=l,l>0}getGridRange(e,t){const r=this.gridUniforms.frameGridResolution.value,a=this.gridUniforms.frameGridMin.value[t],i=this.gridUniforms.frameGridVoxel.value[t],{min:s,max:o}=e.worldBox,n=(e,t,s)=>Math.min(r,Math.max(0,s((e-a[t])/i[t]))),h=n(s.x,"x",Math.floor),l=n(s.y,"y",Math.floor),m=n(s.z,"z",Math.floor),c=n(o.x,"x",Math.ceil),d=n(o.y,"y",Math.ceil),u=n(o.z,"z",Math.ceil);return c<=h||d<=l||u<=m?null:{rect:new Three.Vector4(h,l,c,d),first:t*r+m,count:u-m}}updateLightProxies(e,t){const r={},a=[],i=[],s=[],o=new Three.Vector3(0,1,0);let n=!1;return e.traverseVisible((e=>{if(!e.isLight||!lightProxyFactories[e.type]||!e.layers.test(t.layers))return;const h=r[e.type]||0,l=this.getLightProxy(e.type,h);r[e.type]=h+1,l.visible=!0,l.layers.mask=t.layers.mask,l.color.copy(e.color),l.intensity=e.intensity,l.matrixWorld.copy(e.matrixWorld),e.target&&l.target.matrixWorld.copy(e.target.matrixWorld),(e.isPointLight||e.isSpotLight)&&(l.distance=e.distance,l.decay=e.decay),e.isSpotLight&&(l.angle=e.angle,l.penumbra=e.penumbra),e.isHemisphereLight&&(l.groundColor.copy(e.groundColor),n||(o.setFromMatrixPosition(e.matrixWorld),n=o.lengthSq()>0)),e.isLightProbe&&l.sh.copy(e.sh);const m=(new Three.Vector3).setFromMatrixPosition(e.matrixWorld);if(e.isDirectionalLight){const t=(new Three.Vector3).setFromMatrixPosition(e.target.matrixWorld);a.push((new Three.Vector4).set(...m.sub(t).normalize().toArray(),1));}else e.isSpotLight?s.push((new Three.Vector4).set(...m.toArray(),2)):e.isPointLight&&i.push((new Three.Vector4).set(...m.toArray(),2));})),Object.entries(this.lightProxies).forEach((([e,t])=>{t.forEach(((t,a)=>{a>=(r[e]||0)&&(t.visible=!1);}));})),n||o.set(0,1,0),{skyDirection:o.normalize(),shadowLights:[...a,...i,...s].slice(0,frameShadowLightSlots),pointOffset:a.length,spotOffset:a.length+i.length}}getLightProxy(e,t){const r=this.lightProxies[e]||(this.lightProxies[e]=[]);if(!r[t]){const a=lightProxyFactories[e]();a.matrixAutoUpdate=!1,a.matrixWorldAutoUpdate=!1,a.castShadow=!1,a.target&&(a.target.matrixAutoUpdate=!1,a.target.matrixWorldAutoUpdate=!1),r[t]=a,this.injectScene.add(a);}return r[t]}dispose(){this.historyTargets.forEach((e=>e&&e.dispose())),this.historyTargets=[null,null],this.historyValid=!1,["atlasTarget","densityTarget","shadowDensityTarget","transmittanceTarget"].forEach((e=>{this[e]&&(this[e].dispose(),this[e]=null);})),this.depthTarget&&(this.depthTarget.depthTexture.dispose(),this.depthTarget.dispose(),this.depthTarget=null),[...this.densityPool,...this.shadowPool].forEach((({mesh:e,material:t,geometry:r})=>{e.parent.remove(e),t.dispose(),r.dispose();})),this.densityPool=[],this.shadowPool=[],[this.injectMaterial,this.integrateMaterial,this.lightingMaterial,this.transmittanceMaterial,this.temporalMaterial,this.depthMaterial].forEach((e=>e.dispose())),[this.injectMesh,this.lightingMesh,this.transmittanceMesh].forEach((e=>e.geometry.dispose())),this.cachedFrame=-1,this.cachedCamera=null;}}const frameRenderer=new FrameRenderer;
|
|
52
|
+
|
|
53
|
+
const allLights=Object.values(frameLights).reduce(((e,r)=>e|r),0),createTexture3D=(e,r,t)=>{const s=new Three.Data3DTexture(e,r.x,r.y,r.z);return s.format=t,s.minFilter=Three.LinearMipmapLinearFilter,s.magFilter=Three.LinearFilter,s.generateMipmaps=!0,s.unpackAlignment=1,s.needsUpdate=!0,s},sampleDensity=(e,r,t=1)=>e.isLevelSet?.()?e.isActive(r)?1:0:Three.MathUtils.clamp(e.getValue(r)/t,0,1),sampleColor=(e,r,t)=>{const s=e.getValue(r);return s instanceof Three.Color?t.copy(s):s instanceof Vector3$1||s instanceof Three.Vector3?t.setRGB(s.x,s.y,s.z):t.setRGB(1,0,1),t};class FrameVolume extends Three.Group{isFrameVolume=!0;volumes=[];process=null;constructor(e,r={},t,s){super();const{resolution:o=100,progressive:a=!1,emissiveGrid:i,baseColorGrid:n,maskGrid:l,baseColor:c=16777215,scatterColor:m=0,extinction:p=100,densityScale:h=1,densityCutoff:d=0,absorbance:y=1,opacity:u=1,emissiveIntensity:f=1,emissiveScale:T=1,temperatureRange:w=[0,6500],offset3D:x=new Three.Vector3,wrap3D:v=Three.ClampToEdgeWrapping,lights:D=allLights,shadowSteps:M=32,bbox:g}=r;let C;this.baseColor=new Three.Color(c),this.scatterColor=new Three.Color(m),this.extinction=p,this.densityScale=h,this.densityCutoff=d,this.absorbance=y,this.opacity=u,this.emissiveIntensity=f,this.emissiveScale=T,this.temperatureRange=(new Three.Vector2).fromArray(w),this.offset3D=(new Three.Vector3).copy(x),this.wrap3D=v,this.lights=D,this.shadowSteps=M,this.proxy=new Three.Mesh(createFullscreenGeometry(),frameRenderer.integrateMaterial),this.proxy.frustumCulled=!1,this.proxy.renderOrder=1e6,this.proxy.onBeforeRender=(e,r,t)=>{frameRenderer.render(e,r,t,this.proxy);},this.add(this.proxy),C=e instanceof Array?e:void 0!==e.grids?Object.values(e.grids):[e],C=C.filter((e=>e!==i&&e!==n&&e!==l&&(e instanceof Uint8Array||e instanceof GridDescriptor)));const b=[],V=[i,n,l].some((e=>e instanceof Uint8Array));C.forEach((e=>{const r={densityMap3D:null,emissiveMap3D:null,baseColorMap3D:null,maskMap3D:null,matrix:new Three.Matrix4,matrixWorld:new Three.Matrix4,localToView:new Three.Matrix4,viewToLocal:new Three.Matrix4,worldToLocal:new Three.Matrix4,worldBox:new Three.Box3,viewNear:0,viewFar:0,ready:!1},t=new Three.Box3(new Three.Vector3(-.5,-.5,-.5),new Three.Vector3(.5,.5,.5));g?t.copy(g):e instanceof GridDescriptor&&t.set(...e.getPreciseWorldBbox());const s=t.getCenter(new Three.Vector3),c=t.getSize(new Three.Vector3).max(new Three.Vector3(1e-6,1e-6,1e-6));r.matrix.compose(s,new Three.Quaternion,c);const m=e instanceof Uint8Array||V?new Three.Vector3(o,o,o):c.clone().multiplyScalar(o/Math.max(c.x,c.y,c.z)).round().max(new Three.Vector3(1,1,1)),p=m.x*m.y*m.z;r.densityMap3D=createTexture3D(e instanceof Uint8Array?e:new Uint8Array(p),m,Three.RedFormat);const h={};i&&(h.emissive=i,r.emissiveMap3D=createTexture3D(i instanceof Uint8Array?i:new Uint8Array(p),m,Three.RedFormat)),n&&(h.baseColor=n,r.baseColorMap3D=createTexture3D(n instanceof Uint8Array?n:new Uint8Array(4*p),m,Three.RGBAFormat)),l&&(h.mask=l,r.maskMap3D=createTexture3D(l instanceof Uint8Array?l:new Uint8Array(p),m,Three.RedFormat)),r.ready=a,this.volumes.push(r),b.push({volume:r,voxelCount:p,task:this.convert(r,e,h,s,c,m)});}));let A=0,G=0,F=0;const U=b.length,k=b.reduce(((e,{voxelCount:r})=>e+r),0),R=()=>{const e=window.performance.now(),r=b[A];for(;r&&window.performance.now()-e<12;){const{value:e,done:t}=r.task.next();if(t)break;G+=e,F+=e;}if(r){const e=F>=r.voxelCount;(a||e)&&(o=r.volume,["densityMap3D","emissiveMap3D","baseColorMap3D","maskMap3D"].forEach((e=>{o[e]&&(o[e].needsUpdate=!0);}))),e&&(r.volume.ready=!0,A++,F=0);}var o;s&&s({convertedVoxels:G,totalVoxels:k,convertedGrids:A,totalGrids:U}),A<U?this.process=setTimeout(R,0):(this.process=null,t&&t());};R();}*convert(e,r,t,s,o,a){const{x:i,y:n,z:l}=a;if(r instanceof Uint8Array)return void(yield i*n*l);const c=e.densityMap3D.image.data,m=t.emissive instanceof GridDescriptor&&e.emissiveMap3D.image.data,p=t.baseColor instanceof GridDescriptor&&e.baseColorMap3D.image.data,h=t.mask instanceof GridDescriptor&&e.maskMap3D.image.data,d=new Three.Vector3,y=new Three.Vector3,u=new Three.Color,f=e=>e.transform.applyInverseTransformMap(y.copy(d));for(let e=0;e<l;e++)for(let a=0;a<n;a++){for(let y=0;y<i;y++){const T=y+a*i+e*i*n;d.set((y+.5)/i-.5,(a+.5)/n-.5,(e+.5)/l-.5).multiply(o).add(s),c[T]=255*sampleDensity(r,f(r)),m&&(m[T]=255*sampleDensity(t.emissive,f(t.emissive),this.emissiveScale)),h&&(h[T]=255*sampleDensity(t.mask,f(t.mask))),p&&(sampleColor(t.baseColor,f(t.baseColor),u),p[4*T+0]=255*u.r,p[4*T+1]=255*u.g,p[4*T+2]=255*u.b,p[4*T+3]=255);}yield i;}}dispose(){this.process&&(clearTimeout(this.process),this.process=null),this.volumes.forEach((e=>{["densityMap3D","emissiveMap3D","baseColorMap3D","maskMap3D"].forEach((r=>{e[r]&&e[r].dispose();}));})),this.volumes=[],this.proxy.geometry.dispose();}}
|
|
54
|
+
|
|
55
|
+
const sharedParams=["baseColor","scatterColor","extinction","densityScale","densityCutoff","absorbance","opacity","emissiveIntensity","temperatureRange","wrap3D","lights","shadowSteps"],applyParam=(e,t,s)=>{e[t]instanceof Three.Color?e[t].set(s):e[t]instanceof Three.Vector2?e[t].fromArray(s):e[t]=s;};class FrameVolumeAnimation extends Three.Group{isFrameVolumeAnimation=!0;frames=[];loadedFrames=0;totalFrames=0;bbox=new Three.Box3;fps=24;loop=!0;playing=!1;disposed=!1;params={};currentFrame=0;time=0;get frame(){return this.currentFrame}set frame(e){this.currentFrame=Three.MathUtils.clamp(Math.round(e),0,Math.max(0,this.frames.length-1)),this.frames.forEach(((e,t)=>{e.visible=t===this.currentFrame;}));}addFrame(e){Object.entries(this.params).forEach((([t,s])=>applyParam(e,t,s))),e.visible=this.frames.length===this.currentFrame,this.frames.push(e),this.add(e);}play(){this.playing=!0;}pause(){this.playing=!1;}stop(){this.playing=!1,this.time=0,this.frame=0;}update(e){const t=this.loadedFrames;if(!this.playing||t<2||t<this.totalFrames)return;this.time+=e*this.fps;const s=Math.floor(this.time);if(s<1)return;this.time-=s;let a=this.currentFrame+s;a>=t&&(this.loop?a%=t:(a=t-1,this.playing=!1)),this.frame=a;}dispose(){this.disposed=!0,this.playing=!1,this.frames.forEach((e=>{this.remove(e),e.dispose();})),this.frames=[],this.loadedFrames=0;}}sharedParams.forEach((e=>{Object.defineProperty(FrameVolumeAnimation.prototype,e,{get(){return e in this.params?this.params[e]:this.frames[0]?.[e]},set(t){this.params[e]=t,this.frames.forEach((s=>applyParam(s,e,t)));}});}));
|
|
56
|
+
|
|
57
|
+
const gridOptions$1=["emissiveGrid","baseColorGrid","maskGrid"],findGrid=(e,o)=>Object.values(e.grids).find((e=>e.gridName===o)),isEmptyGrid=e=>{const{file_voxel_count:o,file_bbox_min:r,file_bbox_max:t}=e.metadata||{};return !(!o||0!==Number(o.value))||!!r&&!!t&&(r.value.x>t.value.x||r.value.y>t.value.y||r.value.z>t.value.z)};class VDBAnimationLoader extends Three.Loader{options={};constructor(e){super(e);}setOptions(e){return this.options={...e},this}load(e,o,r,t){const i=new FrameVolumeAnimation;return i.totalFrames=e.length,this.loadFrames(i,e,r).then((()=>{o&&!i.disposed&&o(i);})).catch((e=>{t?t(e):console.error(e);})),i}async loadFrames(e,o,r){const t=[];for(const r of o)if(t.push(await loadVDB(r)),e.disposed)return;const i=t.map((e=>{const o={...this.options};return gridOptions$1.forEach((r=>{"string"==typeof o[r]&&(o[r]=findGrid(e,o[r]));})),o}));t.forEach(((o,r)=>{const t=gridOptions$1.map((e=>i[r][e]));Object.values(o.grids).filter((e=>e instanceof GridDescriptor&&!t.includes(e)&&!isEmptyGrid(e))).forEach((o=>{e.bbox.union((new Three.Box3).set(...o.getPreciseWorldBbox()));}));}));for(let o=0;o<t.length;o++){if(await new Promise((r=>{e.addFrame(new FrameVolume(t[o],{...i[o],bbox:e.bbox},r));})),t[o]=null,e.disposed)return;e.loadedFrames++,r&&r({loadedFrames:e.loadedFrames,totalFrames:e.totalFrames});}}}
|
|
58
|
+
|
|
59
|
+
function wvdbKernel(e,t){const n=512,r={},a={},s={},l=e=>255*Math.min(Math.max(e,0),1)|0,o=(e,t)=>{if(1===t.length)return e.fill(t[0]);for(let n=0;n<e.length;n+=t.length)e.set(t,n);return e},i=(e,[t,r,a],s,l,o=1)=>{const i=Math.ceil(t/8),c=Math.ceil(r/8);for(let d=0;d<s.length;d++){const f=s[d],h=f%i*8,g=Math.floor(f/i)%c*8,u=8*Math.floor(f/(i*c));let p=d*n;for(let n=0;n<8;n++)for(let s=0;s<8;s++){const i=(g+s)*t+(u+n)*t*r,c=g+s<r&&u+n<a;for(let n=0;n<8;n++,p++)if(c&&h+n<t){const t=(h+n+i)*o,r=p*o;for(let n=0;n<o;n++)e[t+n]=l[r+n];}}}},c=e=>e.backgroundTexel.length;return {fillTexels:o,create:({readerId:e,config:t})=>{r[e]=t;},release:({readerId:e})=>{delete r[e],[a,s].forEach((t=>Object.keys(t).filter((t=>t.startsWith(`${e}:`))).forEach((e=>delete t[e]))));},chunk:async({readerId:d,gridIndex:f,lod:h,kind:g,firstIndex:u,bytes:p})=>{const m=r[d];if(!m)return null;const w=m.grids[f],y=w.lods[h],M=`${d}:${f}`,k=`${M}:${h}`,x=c(w),[b,I,A]=y.dimensions,T=await(async e=>{const t=new Blob([e]).stream().pipeThrough(new DecompressionStream("deflate"));return new Uint8Array(await new Response(t).arrayBuffer())})(p);a[k]||(a[k]={data:o(new Uint8Array(b*I*A*x),w.backgroundTexel),pagesLeft:y.pages,mask:null});const U=a[k];if("mask"===g)U.mask=t(T);else {const t=s[M];t&&U.pagesLeft===y.pages&&(((e,[t,n,r],a,[s,l,o],i)=>{const c=t*n,d=(e,t)=>{const n=new Int32Array(e),r=new Int32Array(e),a=new Float32Array(e);for(let s=0;s<e;s++){const l=Math.min(Math.max((s+.5)/e*t-.5,0),t-1);n[s]=Math.floor(l),r[s]=Math.min(n[s]+1,t-1),a[s]=l-n[s];}return {lower:n,upper:r,weight:a}},f=d(s,t),h=d(l,n),g=d(o,r);let u=0;for(let n=0;n<o;n++){const r=g.lower[n]*c,o=g.upper[n]*c,d=g.weight[n];for(let n=0;n<l;n++){const l=h.lower[n]*t,c=h.upper[n]*t,g=h.weight[n];for(let t=0;t<s;t++){const n=f.lower[t],s=f.upper[t],h=f.weight[t];for(let t=0;t<i;t++){const f=n=>e[n*i+t],p=f(n+l+r)+(f(s+l+r)-f(n+l+r))*h,m=f(n+c+r)+(f(s+c+r)-f(n+c+r))*h,w=f(n+l+o)+(f(s+l+o)-f(n+l+o))*h,y=p+(m-p)*g,M=w+(f(n+c+o)+(f(s+c+o)-f(n+c+o))*h-w)*g;a[u++]=y+(M-y)*d+.5;}}}}})(t.data,t.dimensions,U.data,y.dimensions,x),delete s[M],U.mask&&((e,t,r)=>{const a=c(t),s=o(new Uint8Array(n*a),t.backgroundTexel),l=Math.ceil(r[0]/8)*Math.ceil(r[1]/8)*Math.ceil(r[2]/8);for(let t=0;t<l;t++)e.mask[t>>>5]&1<<(31&t)||i(e.data,r,[t],s,a);})(U,w,y.dimensions));const{indices:r,values:a}=e(T,{layout:y.layout,bits:m.bits,firstIndex:u,channels:w.channels});i(U.data,y.dimensions,r,((e,t,{class:r,background:a,channels:s,scale:o})=>{if(3===s){const r=new Uint8Array(t*n*4);for(let a=0;a<t;a++){const t=a*n*3,s=a*n*4;for(let a=0;a<n;a++){for(let o=0;o<3;o++)r[s+4*a+o]=l(e[t+o*n+a]);r[s+4*a+3]=255;}}return r}const i=new Uint8Array(e.length),c="level set"===r;for(let t=0;t<e.length;t++)i[t]=c?Math.abs(e[t])<a?255:0:l(e[t]/o);return i})(a,r.length,w),x),U.pagesLeft--;}const $=0===U.pagesLeft;if(!$&&("mask"===g||1===y.pages))return null;if($){delete a[k];const e=w.lods[h+1];e&&e.pages>1&&(s[M]={data:U.data.slice(),dimensions:y.dimensions});}return {type:"texture",gridIndex:f,lod:h,complete:$,data:$?U.data:U.data.slice()}}}}
|
|
60
|
+
|
|
61
|
+
const{fillTexels:fillTexels}=wvdbKernel(decodeWVDBPage,decodeWVDBMask),workerSource=`\nconst kernel = (${wvdbKernel.toString()})(\n (${decodeWVDBPage.toString()}),\n (${decodeWVDBMask.toString()})\n);\nlet queue = Promise.resolve();\n\nself.onmessage = ({ data }) => {\n queue = queue.then(async () => {\n try {\n const result = await kernel[data.type](data.payload);\n\n self.postMessage({ result }, result && result.data ? [result.data.buffer] : []);\n } catch (error) {\n self.postMessage({ error: String((error && error.message) || error) });\n }\n });\n};\n`,createWorkerBackend=e=>{const t=new Worker(e),a=[];return t.onmessage=({data:e})=>{const t=a.shift();e.error?t?.reject(e.error):t?.resolve(e.result);},t.onerror=e=>{e.preventDefault(),a.splice(0).forEach((({reject:t})=>t(e.message||"Worker error")));},{request:(e,s,r=[])=>new Promise(((n,d)=>{a.push({resolve:n,reject:d}),t.postMessage({type:e,payload:s},r);}))}},createLocalBackend=()=>{const e=wvdbKernel(decodeWVDBPage,decodeWVDBMask);let t=Promise.resolve();return {request:(a,s)=>{const r=t.then((()=>e[a](s)));return t=r.catch((()=>null)),r}}};let backends=null,nextReaderId=0;const getBackend=e=>{if(!backends)if("undefined"!=typeof Worker&&"undefined"!=typeof Blob&&"undefined"!=typeof URL){const e=URL.createObjectURL(new Blob([workerSource],{type:"text/javascript"})),t="undefined"!=typeof navigator&&navigator.hardwareConcurrency||2;backends=Array.from({length:Math.min(Math.max(t-1,1),4)},(()=>createWorkerBackend(e)));}else backends=[createLocalBackend()];return backends[e%backends.length]},toByte=e=>255*Math.min(Math.max(e,0),1)|0,getBackgroundTexel=({class:e,background:t,channels:a=1},s)=>3===a?[...t.map(toByte),255]:["level set"===e?0:toByte(t/s)];class WVDBReader{url=null;header=null;grids={};finalLod=0;loadedLod=-1;bytesLoaded=0;bytesTotal=0;done=!1;cancelled=!1;dataStart=0;streamReader=null;constructor({maxLod:e=1/0,scales:t={}}={}){this.maxLod=e,this.scales=t,this.readerId=nextReaderId++,this.backend=getBackend(this.readerId);}async load(e,{onHeader:t,onUpdate:a,untilLod:s=1/0}={}){return this.url=e,await this.read(s,{onHeader:t,onUpdate:a}),this}async loadMore(e,{onUpdate:t}={}){if(!this.header)throw new Error("Call load() first.");return await this.read(e,{onUpdate:t}),this}async read(e,{onHeader:t,onUpdate:a}){if(this.cancelled||this.header&&this.loadedLod>=Math.min(e,this.finalLod))return;let s=null,r=0,n={};if(this.header){s=this.getChunks(e),r=this.dataStart+s[0].offset;const t=this.dataStart+Math.max(...s.map((({offset:e,length:t})=>e+t)));n={Range:`bytes=${r}-${t-1}`};}const d=await fetch(this.url,{headers:n}).catch((e=>{if(n.Range)return fetch(this.url);throw e}));if(!d.ok)throw new Error(`Could not load ${this.url} (${d.status}).`);if(!d.body)throw new Error("Streams are not supported.");const o=206===d.status?r:0;this.streamReader=d.body.getReader();let l=new Uint8Array(0),i=0,h=0;const c=[],u=e=>{if(i+e.length>l.length){const t=new Uint8Array(Math.max(2*l.length,i+e.length));t.set(l.subarray(0,i)),l=t;}l.set(e,i),i+=e.length;},g=()=>{for(;h<s.length;){const e=s[h],t=this.dataStart+e.offset-o;if(t+e.length>i)break;h++;const r=l.slice(t,t+e.length),n=this.backend.request("chunk",{readerId:this.readerId,gridIndex:e.gridIndex,lod:e.lod,kind:e.kind,firstIndex:e.firstIndex,bytes:r},[r.buffer]);c.push(n.then((t=>{this.cancelled||this.apply(e,t,a);})));}};for(;!this.cancelled;){const{done:r,value:n}=await this.streamReader.read();if(r||this.cancelled)break;if(u(n),this.bytesLoaded+=n.length,!this.header){const r=readWVDBHeader(l.subarray(0,i));if(!r)continue;this.header=r.header,this.dataStart=r.dataStart,this.prepare(),s=this.getChunks(e),t&&t(this),s.length||(this.done=!0,this.loadedLod=this.finalLod,a&&a({reader:this,grid:null,lod:-1,lodComplete:!0,done:!0}));}if(g(),h===s.length){this.streamReader.cancel().catch((()=>{}));break}}if(!this.cancelled&&(!s||h<s.length))throw new Error("The WVDB file is incomplete.");await Promise.all(c),s.length&&(this.loadedLod=Math.max(...s.map((({lod:e})=>e)))),this.done=this.loadedLod>=this.finalLod,this.done&&this.release();}getChunks(e){return getWVDBChunks(this.header,Math.min(e,this.finalLod)).filter((({lod:e})=>e>this.loadedLod))}prepare(){const{grids:e,bake:t}=this.header,a=({name:e})=>this.scales[e]||1;this.finalLod=Math.min(Math.max(Math.floor(this.maxLod),0),t.lods-1),this.bytesTotal=this.dataStart+getWVDBChunks(this.header,this.finalLod).reduce(((e,{offset:t,length:a})=>Math.max(e,t+a)),0),e.forEach((e=>{const t=getBackgroundTexel(e,a(e));this.grids[e.name]={name:e.name,class:e.class,channels:e.channels||1,bbox:e.bbox,texelSize:t.length,dimensions:[1,1,1],data:fillTexels(new Uint8Array(t.length),t),lod:-1,chunksLeft:e.lods.map((({mask:e,pages:t})=>t.length+(e?1:0)))};})),this.backend.request("create",{readerId:this.readerId,config:{bits:t.bits,grids:e.map((e=>({class:e.class,background:e.background,channels:e.channels||1,scale:a(e),backgroundTexel:getBackgroundTexel(e,a(e)),lods:e.lods.map((({dimensions:e,mask:t,pages:a})=>({dimensions:e,layout:t?wvdbLayouts.sparse:wvdbLayouts.dense,pages:a.length})))})))}});}apply(e,t,a){const s=this.grids[this.header.grids[e.gridIndex].name],{lod:r}=e;s.chunksLeft[r]--,t&&(s.data=t.data,s.dimensions=this.header.grids[e.gridIndex].lods[r].dimensions,s.lod=r);const n=0===s.chunksLeft[r];a&&(t||n)&&a({reader:this,grid:s,lod:r,lodComplete:n,done:this.isComplete()});}isComplete(){return Object.values(this.grids).every((({chunksLeft:e})=>e.slice(0,this.finalLod+1).every((e=>0===e))))}release(){this.backend.request("release",{readerId:this.readerId}).catch((()=>{}));}cancel(){this.cancelled||(this.cancelled=!0,this.streamReader?.cancel().catch((()=>{})),this.release());}}
|
|
62
|
+
|
|
63
|
+
const textureKeys=["densityMap3D","emissiveMap3D","baseColorMap3D","maskMap3D"],updateTexture=(e,{data:t,dimensions:[s,r,a]})=>{const{image:d}=e,o=d.width!==s||d.height!==r||d.depth!==a;e.image={data:t,width:s,height:r,depth:a},o&&e.dispose(),e.needsUpdate=!0;};class WVDBVolumeData{grids={};bindings=[];constructor(e){this.reader=e;}readGrids(){Object.values(this.reader.grids).forEach((e=>{const{name:t,class:s,channels:r,bbox:a}=e;this.grids[t]={name:t,isLevelSet:"level set"===s,isColor:3===r,bbox:a?new Three.Box3(new Three.Vector3(...a[0]),new Three.Vector3(...a[1])):new Three.Box3,get data(){return e.data},get dimensions(){return e.dimensions},get resolution(){return e.dimensions[0]},get lod(){return e.lod},source:e};}));}get lod(){return Math.min(...Object.values(this.reader.grids).map((({lod:e})=>e)))}get lods(){return this.reader.finalLod+1}get done(){return this.reader.done}attach(e){const t=Object.values(this.grids);return [...e.volumes||[],...e.materials||[]].forEach((s=>{textureKeys.forEach((r=>{const a=s[r],d=a&&t.find((({data:e})=>e===a.image?.data));d&&this.bindings.push({object:e,texture:a,grid:d.source});}));})),e}detach(e){this.bindings=this.bindings.filter((t=>t.object!==e));}updateTextures(){this.bindings.forEach((({texture:e,grid:t})=>{e.image.data!==t.data&&updateTexture(e,t);}));}dispose(){this.reader.cancel(),this.bindings=[];}}class WVDBLoader extends Three.Loader{maxLod=1/0;scales={};constructor(e){super(e);}setMaxLod(e){return this.maxLod=e,this}setGridScales(e){return this.scales={...e},this}load(e,t,s,r){const a=new WVDBReader({maxLod:this.maxLod,scales:this.scales}),d=new WVDBVolumeData(a);let o=!1;return a.load(e,{onHeader:()=>d.readGrids(),onUpdate:e=>{d.updateTextures(),!o&&d.lod>=0&&(o=!0,t&&t(d)),o&&s&&s({lod:e.lod,lods:d.lods,lodComplete:e.lodComplete,done:e.done,bytesLoaded:a.bytesLoaded,bytesTotal:a.bytesTotal});}}).catch((e=>{a.cancelled||(r?r(e):console.error(e));})),d}}
|
|
64
|
+
|
|
65
|
+
const gridOptions=["emissiveGrid","baseColorGrid","maskGrid"];class WVDBAnimationLoader extends Three.Loader{options={};maxLod=1/0;concurrency=6;constructor(e){super(e);}setOptions(e){return this.options={...e},this}setMaxLod(e){return this.maxLod=e,this}setConcurrency(e){return this.concurrency=Math.max(1,e),this}load(e,o,a,t){const r=new FrameVolumeAnimation;return r.totalFrames=e.length,this.loadFrames(r,e,o,a).catch((e=>{r.disposed||(t?t(e):console.error(e));})),r}async loadFrames(e,o,a,t){const{emissiveGrid:r,emissiveScale:n=1}=this.options,i="string"==typeof r?{[r]:n}:{},s=o.map((()=>new WVDBReader({maxLod:this.maxLod,scales:i}))),m=s.map((e=>new WVDBVolumeData(e))),d=new Array(o.length);let l=0;const c=[],u=()=>(e.disposed&&s.forEach((e=>e.cancel())),e.disposed),h=e=>{c[e]=(c[e]||0)+1,t&&t({lod:e,loadedFrames:c[e],totalFrames:o.length});},p=async e=>{let a=0;await Promise.all(Array.from({length:Math.min(this.concurrency,o.length)},(async()=>{for(;a<o.length&&!u();)await e(a++);})));},f=e=>()=>{u()||m[e].updateTextures();};if(await p((async a=>{await s[a].load(o[a],{untilLod:0,onHeader:()=>m[a].readGrids(),onUpdate:f(a)}),u()||(d[a]=this.createFrame(m[a],e),(()=>{for(;l<d.length&&d[l];)e.addFrame(d[l]),l++,e.loadedFrames=l;})(),h(0));})),u())return;a&&a(e);const y=Math.max(0,...s.map((({finalLod:e})=>e)));for(let e=1;e<=y;e++)if(await p((async o=>{s[o].finalLod<e||(await s[o].loadMore(e,{onUpdate:f(o)}),u()||h(e));})),u())return}createFrame(e,o){const{grids:a}=e,t={...this.options};gridOptions.forEach((e=>{const o="string"==typeof t[e]?a[t[e]]:null;delete t[e],o&&(t[e]=o);}));const r=gridOptions.map((e=>t[e])).filter(Boolean),n=a.density&&!r.includes(a.density)&&a.density||Object.values(a).find((e=>!e.isColor&&!r.includes(e)));return n?(gridOptions.forEach((e=>{t[e]&&(t[e]=t[e].data);})),n.bbox.isEmpty()||o.bbox.union(n.bbox),e.attach(new FrameVolume(n.data,{...t,resolution:n.resolution,bbox:n.bbox}))):new FrameVolume(new Uint8Array(1),{resolution:1})}}
|
|
66
|
+
|
|
67
|
+
export { VolumeToBbox as Bbox, VolumeToFog as FogVolume, FrameVolume, FrameVolumeAnimation, VolumeToLevelSet as LevelSet, MeshVolume, VolumeToPoints as Points, VDBAnimationLoader, VDBLoader, VolumeBasicMaterial, VolumeNormalMaterial, WVDBAnimationLoader, WVDBLoader, WVDBReader, frameLights, frameRenderer, lights };
|
package/package.json
CHANGED
|
@@ -1,6 +1,43 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "three-openvdb",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.5.1",
|
|
4
|
+
"description": "three.js rendering of OpenVDB volumes: loaders, fog and frame volumes, materials.",
|
|
5
|
+
"main": "./index.cjs.js",
|
|
6
|
+
"module": "./index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./index.js",
|
|
10
|
+
"require": "./index.cjs.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"scripts": {},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"openvdb": "^0.5.1"
|
|
16
|
+
},
|
|
17
|
+
"peerDependencies": {
|
|
18
|
+
"three": ">=0.186.0"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"openvdb",
|
|
22
|
+
"vdb",
|
|
23
|
+
"threejs",
|
|
24
|
+
"webgl",
|
|
25
|
+
"volume",
|
|
26
|
+
"fog"
|
|
27
|
+
],
|
|
28
|
+
"author": {
|
|
29
|
+
"name": "mjurczyk",
|
|
30
|
+
"url": "https://discourse.threejs.org/u/mjurczyk"
|
|
31
|
+
},
|
|
32
|
+
"contributors": [
|
|
33
|
+
{
|
|
34
|
+
"name": "mjurczyk",
|
|
35
|
+
"url": "https://discourse.threejs.org/u/mjurczyk"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"name": "notchris",
|
|
39
|
+
"url": "https://discourse.threejs.org/u/notchris"
|
|
40
|
+
}
|
|
41
|
+
],
|
|
42
|
+
"license": "MIT"
|
|
43
|
+
}
|