@hology/core 0.0.233 → 0.0.235

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.
Files changed (150) hide show
  1. package/dist/csm.d.ts +2 -2
  2. package/dist/data/surface-definition.js +1 -1
  3. package/dist/effects/sequence/sequence-action.d.ts +4 -4
  4. package/dist/effects/sequence/sequence-animation-retiming.d.ts +1 -1
  5. package/dist/effects/sequence/sequence-data.d.ts +3 -2
  6. package/dist/effects/sequence/sequence-definitions.d.ts +2 -2
  7. package/dist/effects/sequence/sequence-event.d.ts +1 -1
  8. package/dist/effects/sequence/sequence-locator.d.ts +1 -1
  9. package/dist/effects/sequence/sequence-ops.d.ts +1 -1
  10. package/dist/effects/sequence/sequence-player.d.ts +8 -7
  11. package/dist/effects/sequence/sequence-transform.d.ts +1 -1
  12. package/dist/effects/sequence/sequence-value-lane.d.ts +2 -2
  13. package/dist/effects/vfx/behaviours.d.ts +2 -1
  14. package/dist/effects/vfx/initializsers.d.ts +2 -1
  15. package/dist/effects/vfx/rates.d.ts +1 -1
  16. package/dist/effects/vfx/trail-renderer.d.ts +1 -1
  17. package/dist/effects/vfx/vfx-asset.d.ts +4 -3
  18. package/dist/effects/vfx/vfx-binding-runtime.d.ts +2 -2
  19. package/dist/effects/vfx/vfx-collision-behaviour.d.ts +3 -2
  20. package/dist/effects/vfx/vfx-defs.d.ts +5 -3
  21. package/dist/effects/vfx/vfx-input-runtime.d.ts +1 -1
  22. package/dist/effects/vfx/vfx-materializer.d.ts +7 -7
  23. package/dist/effects/vfx/vfx-param.d.ts +3 -3
  24. package/dist/effects/vfx/vfx-renderers.d.ts +4 -3
  25. package/dist/effects/vfx/vfx-renderers.js +1 -1
  26. package/dist/effects/vfx/vfx-service.d.ts +1 -1
  27. package/dist/effects/vfx/vfx-service.js +1 -1
  28. package/dist/game-component/game-component.d.ts +1 -1
  29. package/dist/gameplay/actors/builtin/components/character/character-movement.js +1 -1
  30. package/dist/gameplay/actors/builtin/components/editor-sprite-component.d.ts +1 -1
  31. package/dist/gameplay/actors/builtin/components/editor-sprite-component.js +1 -1
  32. package/dist/gameplay/actors/builtin/components/volume-editor-component.d.ts +1 -1
  33. package/dist/gameplay/actors/builtin/components/volume-editor-component.js +1 -1
  34. package/dist/gameplay/actors/builtin/navmesh-actor.d.ts +1 -1
  35. package/dist/gameplay/actors/builtin/navmesh-actor.js +1 -1
  36. package/dist/gameplay/actors/builtin/post-process-volume-actor.d.ts +1 -1
  37. package/dist/gameplay/actors/builtin/post-process-volume-actor.js +1 -1
  38. package/dist/gameplay/actors/component.d.ts +2 -2
  39. package/dist/gameplay/actors/controller/actor-controller.d.ts +1 -1
  40. package/dist/gameplay/actors/controller/actor-controller.js +1 -1
  41. package/dist/gameplay/actors/internal/component-init.d.ts +1 -1
  42. package/dist/gameplay/actors/internal/container-map.d.ts +2 -2
  43. package/dist/gameplay/ai/behavior-tree/move.d.ts +3 -3
  44. package/dist/gameplay/ai/behavior-tree/move.js +1 -1
  45. package/dist/gameplay/ai/build-tile.d.ts +2 -1
  46. package/dist/gameplay/ai/dynamic-tiled-navmesh.d.ts +4 -3
  47. package/dist/gameplay/ai/navigation.js +1 -1
  48. package/dist/gameplay/animation/anim-sm.d.ts +1 -1
  49. package/dist/gameplay/animation/retarget.d.ts +2 -1
  50. package/dist/gameplay/animation/root-motion.d.ts +2 -1
  51. package/dist/gameplay/event-graph/runtime-assets.d.ts +1 -1
  52. package/dist/gameplay/initiate.d.ts +5 -5
  53. package/dist/gameplay/inject.d.ts +1 -1
  54. package/dist/gameplay/net/browser/index.d.ts +3 -2
  55. package/dist/gameplay/net/browser/index.js +1 -1
  56. package/dist/gameplay/net/net-session.d.ts +2 -2
  57. package/dist/gameplay/services/asset-loader.js +1 -1
  58. package/dist/gameplay/services/game-time-scheduler.d.ts +1 -1
  59. package/dist/gameplay/services/physics/abstract-physics-system.d.ts +7 -7
  60. package/dist/gameplay/services/pointer-events.d.ts +2 -2
  61. package/dist/gameplay/services/pointer-events.js +1 -1
  62. package/dist/gameplay/services/world.d.ts +1 -1
  63. package/dist/gameplay/services/world.js +1 -1
  64. package/dist/index.d.ts +3 -3
  65. package/dist/index.js +1 -1
  66. package/dist/rendering/cluster-generator.d.ts +2 -1
  67. package/dist/rendering/fog/fog-volume-actor.d.ts +1 -1
  68. package/dist/rendering/fog/fog-volume-actor.js +1 -1
  69. package/dist/rendering/fog/fog-volume-object.d.ts +2 -1
  70. package/dist/rendering/fog/volumetric-fog-pass.d.ts +4 -2
  71. package/dist/rendering/fog/volumetric-fog-pass.js +1 -1
  72. package/dist/rendering/light-probes/light-volume-capture.d.ts +2 -1
  73. package/dist/rendering/motion-vector-node.d.ts +8 -0
  74. package/dist/rendering/motion-vector-node.js +4 -0
  75. package/dist/rendering/node-material-webgpu-resolver.d.ts +2 -1
  76. package/dist/rendering/post-process-effect.d.ts +1 -1
  77. package/dist/rendering/webgpu-experimental-backend.d.ts +23 -3
  78. package/dist/rendering/webgpu-experimental-backend.js +1 -1
  79. package/dist/rendering.d.ts +14 -2
  80. package/dist/rendering.js +1 -1
  81. package/dist/scene/asset-resource-loader.d.ts +5 -4
  82. package/dist/scene/assets-provider.d.ts +2 -2
  83. package/dist/scene/batched-mesh-2.d.ts +1 -1
  84. package/dist/scene/bootstrap.d.ts +8 -7
  85. package/dist/scene/collision/collision-shape-import.d.ts +3 -3
  86. package/dist/scene/collision/collision-shape.d.ts +2 -1
  87. package/dist/scene/custom-param-deserialize.d.ts +1 -1
  88. package/dist/scene/custom-param-runtime-types.d.ts +2 -1
  89. package/dist/scene/landscape/landscape-manager.d.ts +6 -6
  90. package/dist/scene/landscape/landscape-manager.js +1 -1
  91. package/dist/scene/landscape/utils.d.ts +2 -2
  92. package/dist/scene/materializer.js +1 -1
  93. package/dist/scene/materials/grass-foliage.d.ts +2 -2
  94. package/dist/scene/materials/grass.d.ts +1 -1
  95. package/dist/scene/materials/utils/material-painting.d.ts +2 -1
  96. package/dist/scene/materials/water.d.ts +1 -1
  97. package/dist/scene/model.d.ts +1 -1
  98. package/dist/scene/objects/prefab.d.ts +3 -3
  99. package/dist/scene/objects/shapes.d.ts +4 -2
  100. package/dist/scene/runtime-asset-service.d.ts +2 -2
  101. package/dist/scene/runtime-backend-service.d.ts +2 -2
  102. package/dist/scene/runtime-bundled-backend-service.d.ts +3 -3
  103. package/dist/scene/scatter/painted-scatter-manager.d.ts +1 -1
  104. package/dist/scene/scatter/surface-scatter-manager.d.ts +4 -4
  105. package/dist/scene/scene-data-service.d.ts +1 -1
  106. package/dist/scene/surface-query.d.ts +2 -2
  107. package/dist/scene/surface-query.js +1 -1
  108. package/dist/shader/builtin/decal-unlit-shader.js +1 -1
  109. package/dist/shader/builtin/index.d.ts +1 -1
  110. package/dist/shader/decal-shader.d.ts +3 -2
  111. package/dist/shader/graph/compiler.d.ts +1 -1
  112. package/dist/shader/parameter.d.ts +8 -8
  113. package/dist/shader/post-process-shader.d.ts +2 -1
  114. package/dist/shader/shader.d.ts +6 -5
  115. package/dist/shader/trail-shader.d.ts +3 -2
  116. package/dist/shader/trail-shader.js +1 -1
  117. package/dist/shader/uv-nodes.d.ts +2 -2
  118. package/dist/shader-nodes/bulge.d.ts +1 -1
  119. package/dist/shader-nodes/curve-sample.d.ts +2 -2
  120. package/dist/shader-nodes/depth.d.ts +3 -3
  121. package/dist/shader-nodes/dither.d.ts +1 -1
  122. package/dist/shader-nodes/effects.d.ts +1 -1
  123. package/dist/shader-nodes/glsl-node.d.ts +1 -1
  124. package/dist/shader-nodes/glsl-node.js +1 -1
  125. package/dist/shader-nodes/gradient-sample.d.ts +1 -1
  126. package/dist/shader-nodes/landscape.d.ts +1 -1
  127. package/dist/shader-nodes/layers.d.ts +1 -1
  128. package/dist/shader-nodes/material-flags.d.ts +1 -1
  129. package/dist/shader-nodes/material-flags.js +1 -1
  130. package/dist/shader-nodes/particle.d.ts +1 -1
  131. package/dist/shader-nodes/particle.js +1 -1
  132. package/dist/shader-nodes/pom.d.ts +2 -1
  133. package/dist/shader-nodes/pom.js +1 -1
  134. package/dist/shader-nodes/scene-sample.d.ts +2 -1
  135. package/dist/shader-nodes/shapes.d.ts +1 -1
  136. package/dist/shader-nodes/texture-sequence.d.ts +1 -1
  137. package/dist/shader-nodes/time.d.ts +1 -1
  138. package/dist/shader-nodes/voronoi.d.ts +1 -1
  139. package/dist/shader-nodes/wgsl-node.d.ts +1 -1
  140. package/dist/shader-nodes/wgsl-node.js +1 -1
  141. package/dist/test/parameter-definition.test.js +1 -1
  142. package/dist/utils/materials.d.ts +1 -1
  143. package/dist/utils/mesh.d.ts +1 -1
  144. package/dist/utils/obb-utils.d.ts +2 -1
  145. package/dist/utils/three/cleanup.d.ts +1 -1
  146. package/dist/utils/three/depth-pass.d.ts +1 -1
  147. package/dist/utils/three/traverse.d.ts +2 -1
  148. package/dist/worker/index.js +1 -1
  149. package/package.json +2 -1
  150. package/tsconfig.tsbuildinfo +1 -1
@@ -1,4 +1,4 @@
1
- import*as e from"three";import{Matrix4 as t,PerspectiveCamera as n,PointLight as i,Vector3 as o,WebGLRenderTarget as a}from"three";import{FullScreenQuad as r,Pass as s}from"three/examples/jsm/Addons.js";import{ambientLightName as l}from"../../scene/sky.js";import{FogVolumeObject as c}from"./fog-volume-object";const f=new o;export class VolumetricFogPass extends s{constructor(n){super(),this.resolution=n,this.fsQuad=new r(null),this.downSampleRatio=4,this._oldClearColor=new e.Color,this.oldClearAlpha=1,this.volumesCache=new WeakMap,this._viewProjection=new t,this._invViewProjection=new t;const i={minFilter:e.LinearFilter,magFilter:e.LinearFilter,format:e.RGBAFormat,depthBuffer:!1,stencilBuffer:!1,type:e.HalfFloatType},s=Math.round(this.resolution.x/this.downSampleRatio),l=Math.round(this.resolution.y/this.downSampleRatio);this.renderTargetFog=new a(s,l,i),this.renderTargetFog.texture.generateMipmaps=!1,this.renderTargetBlur=new a(s,l,i),this.renderTargetBlur.texture.generateMipmaps=!1,this.material=new e.ShaderMaterial({defines:{PERSPECTIVE_CAMERA:1,NUM_FOG_VOLUMES:0,MAX_POINT_LIGHTS:8,MAX_SPOT_LIGHTS:2,VOLUMETRIC:1,SHADOW_FAR:100},uniforms:{tDepth:{value:null},tDiffuse:{value:null},cameraPos:{value:new o},cameraMatrix:{value:new t},invProjection:{value:new t},fogVolumes:{value:[]},stepCount:{value:30},cameraNear:{value:null},cameraFar:{value:null},pointLights:{value:new Array(8).fill(null).map(()=>({color:new o(0,0,0),distance:0,position:new o}))},activePointLightCount:{value:0},spotLights:{value:new Array(2).fill(null).map(()=>({position:new o,direction:new o,color:new o,distance:0,decay:0,coneCos:0,penumbraCos:0}))},activeSpotLightCount:{value:0},directionalLight:{value:{direction:new o(0,-1,1).normalize(),color:new o(1,1,1)}},ambientLight:{value:new o(1,1,1).multiplyScalar(1.35)},directionalShadowMatrix:{value:new t},directionalShadowMap:{value:null},depthMappingParams:{value:new o}},vertexShader:"\n varying vec2 vUv;\n void main() {\n vUv = uv;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);\n }\n ",fragmentShader:"\n #define MAX_STEP_COUNT 64\n\n struct IncidentLight {\n vec3 color;\n vec3 direction;\n bool visible;\n };\n\n #if MAX_POINT_LIGHTS > 0\n struct PointLight {\n\t\t\tvec3 position;\n vec3 color;\n float distance;\n\t\t};\n #endif\n\n #if MAX_SPOT_LIGHTS > 0\n struct SpotLight {\n vec3 position;\n vec3 direction;\n vec3 color;\n float distance;\n float decay;\n float coneCos;\n float penumbraCos;\n };\n uniform SpotLight spotLights[ MAX_SPOT_LIGHTS ];\n uniform int activeSpotLightCount;\n float getSpotAttenuation( const in float coneCosine, const in float penumbraCosine, const in float angleCosine ) {\n return smoothstep( coneCosine, penumbraCosine, angleCosine );\n }\n #ifndef saturate\n #define saturate( a ) clamp( a, 0.0, 1.0 )\n #endif\n float pow2( const in float x ) { return x*x; }\n vec3 pow2( const in vec3 x ) { return x*x; }\n float pow4( const in float x ) { float x2 = x*x; return x2*x2; }\n float getDistanceAttenuation( const in float lightDistance, const in float cutoffDistance, const in float decayExponent ) {\n // based upon Frostbite 3 Moving to Physically-based Rendering\n // page 32, equation 26: E[window1]\n // https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf\n float distanceFalloff = 1.0 / max( pow( lightDistance, decayExponent ), 0.01 );\n if ( cutoffDistance > 0.0 ) {\n distanceFalloff *= pow2( saturate( 1.0 - pow4( lightDistance / cutoffDistance ) ) );\n }\n return distanceFalloff;\n }\n void getSpotLightInfo( const in SpotLight spotLight, const in vec3 geometryPosition, out IncidentLight light ) {\n vec3 lVector = spotLight.position - geometryPosition;\n light.direction = normalize( lVector );\n float angleCos = dot( light.direction, spotLight.direction );\n float spotAttenuation = getSpotAttenuation( spotLight.coneCos, spotLight.penumbraCos, angleCos );\n if ( spotAttenuation > 0.0 ) {\n float lightDistance = length( lVector );\n light.color = spotLight.color * spotAttenuation;\n light.color *= getDistanceAttenuation( lightDistance, spotLight.distance, spotLight.decay );\n light.visible = ( light.color != vec3( 0.0 ) );\n } else {\n light.color = vec3( 0.0 );\n light.visible = false;\n }\n }\n vec3 calcSpotLight(vec3 p, vec3 rayDir, float g, SpotLight spotLight) {\n IncidentLight directLight;\n getSpotLightInfo(spotLight, p, directLight);\n return directLight.color * 3.14;\n }\n #endif\n\n struct DirectionalLight {\n vec3 color; // pre multiplied with intensity\n vec3 direction; // should point *from the light*, normalized\n };\n\n struct FogVolume {\n vec3 sphereCenter;\n float sphereRadius;\n float baseDensity;\n float heightFalloff;\n float heightOffset;\n float scatteringDistribution;\n float startDistance;\n vec3 albedo;\n vec3 emissive; // Self-illumination color\n float ambientIntensity; // Multiplier for ambient light contribution\n float scatteringMode; // 0 = volumetric (occludes), 1 = lightShaft (god rays)\n };\n\n varying vec2 vUv;\n uniform sampler2D tDiffuse;\n uniform vec3 cameraPos;\n\n uniform float stepCount;\n uniform float nearMinDistance;\n\n uniform mat4 cameraMatrix;\n uniform mat4 invProjection;\n uniform sampler2D tDepth;\n\t\tuniform float cameraNear;\n\t\tuniform float cameraFar;\n \n uniform DirectionalLight directionalLight;\n\n uniform mat4 directionalShadowMatrix;\n uniform sampler2D directionalShadowMap;\n\n uniform vec3 ambientLight;\n uniform vec3 depthMappingParams;\n\n #if NUM_FOG_VOLUMES > 0\n uniform FogVolume fogVolumes[ NUM_FOG_VOLUMES ];\n #endif\n #if MAX_POINT_LIGHTS > 0\n uniform PointLight pointLights[ MAX_POINT_LIGHTS ];\n uniform int activePointLightCount;\n #endif\n\n #include <packing>\n\n // Ray-sphere intersection\n bool intersectSphere(vec3 rayOrigin, vec3 rayDir, vec3 sphereCenter, float sphereRadius, out float t0, out float t1) {\n // Vector from ray origin to sphere center\n vec3 L = rayOrigin - sphereCenter;\n float a = dot(rayDir, rayDir); // should be 1 if rayDir is normalized\n float b = 2.0 * dot(rayDir, L);\n float c = dot(L, L) - sphereRadius * sphereRadius;\n \n float discriminant = b * b - 4.0 * a * c;\n if (discriminant < 0.0) {\n return false; // no intersection\n }\n \n float sqrtD = sqrt(discriminant);\n t0 = (-b - sqrtD) / (2.0 * a);\n t1 = (-b + sqrtD) / (2.0 * a);\n \n // Make sure t0 <= t1\n if (t0 > t1) {\n float tmp = t0;\n t0 = t1;\n t1 = tmp;\n }\n return true;\n }\n\n float getLinearDepth( const in vec2 uv ) {\n\n float fragCoordZ = texture2D( tDepth, uv ).x;\n return perspectiveDepthToViewZ( fragCoordZ, cameraNear, cameraFar );\n\n\t\t}\n\n float linearizeDepth(float depth) {\n float z = depth * 2.0 - 1.0; // back to NDC (-1..1)\n return (2.0 * cameraNear * cameraFar) / (cameraFar + cameraNear - z * (cameraFar - cameraNear));\n }\n\n float getSliceAtDepth(float depth) {\n return log2(max(1.0, depth * depthMappingParams.x + depthMappingParams.y)) * depthMappingParams.z;\n }\n\n float getDepthAtSlice(float slice) {\n return (exp2(slice / depthMappingParams.z) - depthMappingParams.y) / depthMappingParams.x;\n }\n\n\n\n float texture2DCompare( sampler2D depths, vec2 uv, float compare ) {\n return step( compare, unpackRGBAToDepth( texture2D( depths, uv ) ) );\n }\n\n vec3 calcDirectionalLight(vec3 p, vec3 rayDir, float g, DirectionalLight dirLight, out float outInShadow) {\n vec3 toLight = dirLight.direction; // direction from sample point to light\n float cosTheta = dot(toLight, -rayDir); // angle between view ray and light\n cosTheta = clamp(cosTheta, -1.0, 1.0); // keep it safe\n\n // phase function (Henyey-Greenstein or isotropic)\n float denom = 1.0 + g*g - 2.0*g*cosTheta;\n float phase = (1.0 - g*g) / (4.0 * 3.14159265 * pow(denom, 1.5));\n phase *= 3.0 * 3.14159265; // scale to total energy\n\n vec4 shadowCoord = directionalShadowMatrix * vec4(p, 1.0);\n shadowCoord.xyz /= shadowCoord.w; // perspective divide\n\n float bias = 0.0; \n outInShadow = texture2DCompare(directionalShadowMap, shadowCoord.xy, shadowCoord.z - bias);\n\n return dirLight.color * phase * outInShadow;\n }\n\n vec3 calcPointLight(vec3 p, vec3 rayDir, float g, PointLight pointLight) {\n vec3 lightPos = pointLight.position;\n vec3 lightColor = pointLight.color;\n float lightRadius = pointLight.distance;\n\n vec3 L = normalize(lightPos - p);\n float lightDist = length(lightPos - p);\n \n float attenuation = clamp(1.0 - lightDist / lightRadius, 0.0, 1.0);\n attenuation *= attenuation; // smoother falloff\n attenuation /= (1.0 + lightDist*lightDist); // inverse-square style\n\n attenuation *= 4.0 * 3.14159; // Manually scaled to match point light intensity\n // float attenuation = 1.0 / (4.0 * 3.14159 * lightDist * lightDist);\n\n\n float cosTheta = dot(L, -rayDir); // angle between light and view ray\n float phase = (1.0 - g*g) / pow(1.0 + g*g - 2.0*g*cosTheta, 1.5);\n phase *= 0.25 / 3.14159; // normalize\n\n // Intensity is part of color\n vec3 light = lightColor * attenuation * phase;\n return light;\n }\n\n float random01(vec2 p) {\n return fract(sin(dot(p, vec2(41.0, 289.0))) * 45758.5453);\n }\n\n float hash(vec3 p) { return fract(sin(dot(p, vec3(12.9898,78.233,37.719))) * 43758.5453); }\n\n // Compute perceived brightness of a color (luminosity, Rec. 709)\n float luminosity(vec3 color) {\n return dot(color, vec3(0.2126, 0.7152, 0.0722));\n }\n \n void main() {\n // reconstruct ray in world space\n vec2 ndc = vUv * 2.0 - 1.0;\n vec4 nearPoint = invProjection * vec4(ndc, -1.0, 1.0);\n nearPoint /= nearPoint.w;\n vec4 farPoint = invProjection * vec4(ndc, 1.0, 1.0);\n farPoint /= farPoint.w;\n\n vec3 rayOrigin = cameraPos;\n vec3 rayDir = normalize(farPoint.xyz - cameraPos);\n\n float sceneDepth = -getLinearDepth(vUv); // in view space\n\n // vec4 scenePosView = invProjection * vec4(ndc, sceneDepth * 2.0 - 1.0, 1.0);\n // scenePosView /= scenePosView.w;\n\n // distance from camera to surface\n // float sceneDist = length(scenePosView.xyz - cameraPosition);\n\n float transmittance = 1.0;\n vec3 inscatter = vec3(0.0);\n float random = random01(vUv);\n\n\n #if NUM_FOG_VOLUMES > 0\n for (int v = 0; v < NUM_FOG_VOLUMES; v++) {\n FogVolume volume = fogVolumes[v];\n vec3 sphereCenter = volume.sphereCenter;\n float sphereRadius = volume.sphereRadius;\n float heightFalloff = volume.heightFalloff;\n float heightOffset = volume.heightOffset;\n float baseDensity = volume.baseDensity;\n float startDistance = volume.startDistance;\n float g = volume.scatteringDistribution;\n bool isLightShaft = volume.scatteringMode > 0.5;\n\n float t0, t1;\n if (!intersectSphere(rayOrigin, rayDir, sphereCenter, sphereRadius, t0, t1) || t1 <= 0.0) {\n continue;\n }\n // if camera inside sphere\n t0 = max(t0, cameraNear);\n float t_end = min(t1, sceneDepth);\n if (t_end <= t0) continue;\n\n float sliceStart = getSliceAtDepth(t0);\n float sliceEnd = getSliceAtDepth(t_end);\n float sliceRange = sliceEnd - sliceStart;\n \n // Determine number of steps based on distance, but capped at MAX_STEP_COUNT\n float maxStepSize = 0.3;\n int desiredSteps = int(ceil((t_end - t0) / maxStepSize));\n int numSteps = clamp(desiredSteps, 1, MAX_STEP_COUNT);\n float sliceStep = sliceRange / float(numSteps);\n\n float jitter = random;\n\n for (int i = 0; i < MAX_STEP_COUNT; i++) {\n if (i >= numSteps) break;\n\n float s0 = sliceStart + float(i) * sliceStep;\n float s1 = sliceStart + float(i + 1) * sliceStep;\n \n float tSampleT0 = getDepthAtSlice(s0);\n float tSampleT1 = getDepthAtSlice(s1);\n float t = getDepthAtSlice(s0 + jitter * sliceStep);\n \n float stepSize = tSampleT1 - tSampleT0;\n \n if (t > sceneDepth) break;\n\n vec3 p = rayOrigin + rayDir * t;\n\n float geomDelta = sceneDepth - t; // how far fog sample is from hitting geometry\n float fade = clamp(geomDelta, 0.0, 1.0); \n // float fade = clamp((sceneDepth - t) / 0.1, 0.0, 1.0);\n\n float closeFade = smoothstep(0.0, 0.3, (t - startDistance) / t);\n float edgeFade = clamp(sphereRadius - length(p - sphereCenter), 0.0, 1.0);\n\n // density with exponential height falloff\n float h = p.y - (sphereCenter.y + heightOffset);\n float heightFactor = clamp(exp(-h * heightFalloff), 0.0, 1.0);\n float density = baseDensity * heightFactor * fade * closeFade * edgeFade;\n\n vec3 light = vec3(0.0);\n float inDirectLight = 1.0; // 1.0 = in light, 0.0 = in shadow\n \n #if VOLUMETRIC == 1\n // Skip if we're too far away for directional light shadows\n // Or the entire volume will show instead of just the shafts\n if (isLightShaft && t > float(SHADOW_FAR)) {\n continue;\n }\n // Get directional light first - this also gives us the shadow value for god rays\n light += calcDirectionalLight(p, rayDir, g, directionalLight, inDirectLight);\n \n // For god rays mode, skip everything else if we're in shadow\n // For atmospheric mode, always include all lights\n if (!isLightShaft || inDirectLight > 0.5) {\n #if MAX_POINT_LIGHTS > 0\n for (int pi = 0; pi < activePointLightCount; pi++) {\n light += calcPointLight(p, rayDir, g, pointLights[pi]);\n } \n #endif\n\n #if MAX_SPOT_LIGHTS > 0\n for (int pi = 0; pi < activeSpotLightCount; pi++) {\n light += calcSpotLight(p, rayDir, g, spotLights[pi]);\n } \n #endif\n\n // Ambient and emissive only for atmospheric mode, not god rays\n if (!isLightShaft) {\n light += ambientLight * volume.ambientIntensity;\n light += volume.emissive;\n }\n }\n\n #else\n light = vec3(1.0);\n #endif\n\n light *= volume.albedo;\n \n if (!isLightShaft || inDirectLight > 0.5) {\n float extinction = exp(-density * stepSize);\n inscatter += light * (1.0 - extinction) * transmittance;\n transmittance *= extinction;\n }\n }\n }\n #endif\n\n float alpha = 1.0 - transmittance;\n\n gl_FragColor = vec4(inscatter, alpha);\n }\n "}),this.material.onBeforeCompile=e=>{},this.materialBlur=new e.ShaderMaterial({uniforms:{tInput:{value:null},tDepth:{value:null},texelSize:{value:new e.Vector2(1,1).divideScalar(1e3)},direction:{value:new e.Vector2(1,0)},depthSigma:{value:2},blurSigma:{value:1},cameraNear:{value:.1},cameraFar:{value:1e3}},vertexShader:"\n varying vec2 vUv;\n void main() {\n vUv = uv;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n }\n ",fragmentShader:"\n uniform sampler2D tInput;\n uniform sampler2D tDepth;\n uniform vec2 texelSize;\n uniform vec2 direction;\n uniform float depthSigma;\n uniform float blurSigma;\n uniform float cameraNear;\n uniform float cameraFar;\n varying vec2 vUv;\n\n #include <packing>\n\n float getLinearDepth( const in vec2 uv ) {\n float fragCoordZ = texture2D( tDepth, uv ).x;\n return perspectiveDepthToViewZ( fragCoordZ, cameraNear, cameraFar );\n }\n\n void main() {\n float centerDepth = getLinearDepth(vUv);\n vec4 center = texture2D(tInput, vUv);\n vec4 centerColor = center;\n\n vec4 sum = vec4(0.0);\n float wsum = 0.0;\n\n for (int i = -5; i <= 5; i++) {\n vec2 offset = direction * float(i) * texelSize;\n vec2 uv = vUv + offset;\n\n vec4 sampleColor = texture2D(tInput, uv);\n float sampleDepth = getLinearDepth(uv);\n\n float spatialWeight = exp(-0.5 * (float(i) * float(i)) / (blurSigma * blurSigma));\n float depthDiff = abs(sampleDepth - centerDepth);\n float depthWeight = exp(-0.5 * (depthDiff * depthDiff) / (depthSigma * depthSigma));\n\n float w = spatialWeight * depthWeight;\n\n sum += sampleColor * w;\n wsum += w;\n }\n\n gl_FragColor = sum / (wsum + 0.0001);\n }\n ",transparent:!0}),this.materialComposite=new e.ShaderMaterial({uniforms:{tDiffuse:{value:null},tFog:{value:null},tDepth:{value:null},fogResolution:{value:new e.Vector2}},vertexShader:"\n varying vec2 vUv;\n void main() {\n vUv = uv;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n }\n ",fragmentShader:"\n uniform sampler2D tDiffuse;\n uniform sampler2D tFog;\n uniform sampler2D tDepth;\n uniform vec2 fogResolution;\n varying vec2 vUv;\n\n #include <packing>\n\n void main() {\n vec2 fogTexelSize = 1.0 / fogResolution;\n vec2 fogUv = vUv * fogResolution - vec2(0.5);\n vec2 uv00 = (floor(fogUv) + vec2(0.5)) * fogTexelSize;\n vec2 uv10 = uv00 + vec2(fogTexelSize.x, 0.0);\n vec2 uv01 = uv00 + vec2(0.0, fogTexelSize.y);\n vec2 uv11 = uv00 + vec2(fogTexelSize.x, fogTexelSize.y);\n\n float d00 = texture2D(tDepth, uv00).x;\n float d10 = texture2D(tDepth, uv10).x;\n float d01 = texture2D(tDepth, uv01).x;\n float d11 = texture2D(tDepth, uv11).x;\n\n float dCenter = texture2D(tDepth, vUv).x;\n\n float diff00 = abs(dCenter - d00);\n float diff10 = abs(dCenter - d10);\n float diff01 = abs(dCenter - d01);\n float diff11 = abs(dCenter - d11);\n\n float minDiff = min(min(diff00, diff10), min(diff01, diff11));\n\n vec2 closestUv = uv00;\n if (minDiff == diff10) closestUv = uv10;\n else if (minDiff == diff01) closestUv = uv01;\n else if (minDiff == diff11) closestUv = uv11;\n\n vec4 sceneColor = texture2D(tDiffuse, vUv);\n \n // Depth-aware upscaling:\n // By default use hardware bilinear, but near depth edges, snap to the closest depth texel.\n // This avoids leaking fog across sharp silhouettes while keeping smooth areas artifact-free.\n float maxDiff = max(max(diff00, diff10), max(diff01, diff11));\n vec2 sampleUv = vUv;\n if (maxDiff > 0.001) {\n sampleUv = closestUv;\n }\n\n vec4 fogColor = texture2D(tFog, sampleUv);\n\n // Composite: fogColor.a is the fog alpha, blend over scene\n // vec3 color = mix(sceneColor.rgb, fogColor.rgb, fogColor.a);\n // Multiplying with fogColor.a again helps to avoid dark borders around the fog\n gl_FragColor = vec4(fogColor.rgb + (1.0 - fogColor.a * fogColor.a) * sceneColor.rgb, 1.0);\n }\n ",transparent:!0}),this.materialComposite.uniforms.fogResolution.value.set(s,l)}render(e,t,n){e.getClearColor(this._oldClearColor),this.oldClearAlpha=e.getClearAlpha();const i=e.autoClear;e.autoClear=!1,e.setClearColor(16777215,0),this.fsQuad.material=this.material,e.setRenderTarget(this.renderTargetFog),e.clear(),this.fsQuad.render(e),this.materialBlur.uniforms.tInput.value=this.renderTargetFog.texture,this.materialBlur.uniforms.direction.value=h,this.fsQuad.material=this.materialBlur,e.setRenderTarget(this.renderTargetBlur),e.clear(),this.fsQuad.render(e),this.materialBlur.uniforms.tInput.value=this.renderTargetBlur.texture,this.materialBlur.uniforms.direction.value=u,e.setRenderTarget(this.renderTargetFog),e.clear(),this.fsQuad.render(e),this.materialComposite.uniforms.tDiffuse.value=n.texture,this.materialComposite.uniforms.tFog.value=this.renderTargetFog.texture,this.fsQuad.material=this.materialComposite,this.renderToScreen?(e.setRenderTarget(null),this.fsQuad.render(e)):(e.setRenderTarget(t),this.clear&&e.clear(),this.fsQuad.render(e)),e.setClearColor(this._oldClearColor,this.oldClearAlpha),e.autoClear=i}setSize(e,t){let n=Math.round(e/this.downSampleRatio),i=Math.round(t/this.downSampleRatio);this.renderTargetFog.setSize(n,i),this.renderTargetBlur.setSize(n,i),this.materialBlur.uniforms.texelSize.value.set(1/n,1/i),this.materialComposite.uniforms.fogResolution.value.set(n,i)}set volumetric(e){this.material.defines.VOLUMETRIC=e?1:0}get volumetric(){return!!this.material.defines.VOLUMETRIC}getVolumeUniforms(e){let t=this.volumesCache.get(e);return null==t&&(t={albedo:new o,baseDensity:e.baseDensity,heightFalloff:e.heightFalloff,heightOffset:e.heightOffset,startDistance:e.startDistance??3,scatteringDistribution:e.scatteringDistribution,sphereRadius:1,sphereCenter:new o,emissive:new o,ambientIntensity:e.ambientIntensity??1,scatteringMode:"lightShaft"===e.scatteringMode?1:0},this.volumesCache.set(e,t)),e.object.getWorldPosition(t.sphereCenter),e.object.getWorldScale(f),t.sphereRadius=5*f.x,t.albedo.set(e.albedo.r,e.albedo.g,e.albedo.b),t.baseDensity=e.baseDensity,t.heightFalloff=e.heightFalloff,t.heightOffset=e.heightOffset,t.scatteringDistribution=e.scatteringDistribution,t.startDistance=e.startDistance,t.emissive.set(e.emissive.r,e.emissive.g,e.emissive.b),t.ambientIntensity=e.ambientIntensity??1,t.scatteringMode="lightShaft"===e.scatteringMode?1:0,t}getPointLightUniforms(e){const t={position:new o,color:new o,distance:0};return e.getWorldPosition(t.position),t.color.set(e.color.r,e.color.g,e.color.b).multiplyScalar(e.intensity*(e.userData?.volumetricIntensity??1)),t.distance=void 0!==e.distance&&e.distance>0?e.distance:1,t}getSpotLightUniforms(e){const t={position:new o,direction:new o,color:new o,distance:0,coneCos:0,penumbraCos:0,decay:1};return e.getWorldPosition(t.position),t.direction.setFromMatrixPosition(e.matrixWorld),f.setFromMatrixPosition(e.target.matrixWorld),t.direction.sub(f),t.color.set(e.color.r,e.color.g,e.color.b).multiplyScalar(e.intensity*(e.userData?.volumetricIntensity??1)),t.distance=void 0!==e.distance&&e.distance>0?e.distance:0,t.coneCos=Math.cos(e.angle),t.penumbraCos=Math.cos(e.angle*(1-e.penumbra)),t.decay=void 0!==e.decay?e.decay:1,t}updateArrayUniforms(t){const n=this.material.uniforms.fogVolumes.value;let o=0;const a=this.material.uniforms.pointLights.value;let r=0;const s=this.material.uniforms.spotLights.value;let l=0;t.traverseVisible(t=>{if(t instanceof c){const e=t.volume,i=this.getVolumeUniforms(e);n[o]=i,o++}else if(t instanceof i){if(r<a.length){if(0===t.userData.volumetricIntensity)return;const e=this.getPointLightUniforms(t);a[r].position.copy(e.position),a[r].color.copy(e.color),a[r].distance=e.distance,r++}}else if(t instanceof e.SpotLight&&l<s.length){if(0===t.userData.volumetricIntensity)return;const e=this.getSpotLightUniforms(t);s[l].position.copy(e.position),s[l].direction.copy(e.direction),s[l].color.copy(e.color),s[l].distance=e.distance,s[l].coneCos=e.coneCos,s[l].penumbraCos=e.penumbraCos,s[l].decay=e.decay,l++}}),this.material.uniforms.activePointLightCount.value=r,this.material.uniforms.activeSpotLightCount.value=l,this.material.defines.NUM_FOG_VOLUMES!==o&&(this.material.needsUpdate=!0,this.material.uniformsNeedUpdate=!0),this.material.defines.NUM_FOG_VOLUMES=o}get uniforms(){return this.material.uniforms}update(e,t,i,a){if(this.updateArrayUniforms(a),this.enabled=0!=this.material.defines.NUM_FOG_VOLUMES,!this.enabled)return;null==this.uniforms.cameraPosition&&(this.uniforms.cameraPos={value:new o}),this.uniforms.cameraPos.value.copy(e.position),this._viewProjection.multiplyMatrices(e.projectionMatrix,e.matrixWorldInverse),this.uniforms.invProjection.value.copy(this._invViewProjection.copy(this._viewProjection).invert()),this.uniforms.tDepth.value=t.depthTexture,this.materialBlur.uniforms.tDepth.value=t.depthTexture,this.materialComposite.uniforms.tDepth.value=t.depthTexture,e instanceof n&&(this.uniforms.cameraNear.value=e.near,this.uniforms.cameraFar.value=e.far,this.materialBlur.uniforms.cameraNear.value=e.near,this.materialBlur.uniforms.cameraFar.value=e.far),this.uniforms.directionalLight.value.direction=i.lightDirection,this.uniforms.directionalLight.value.color.set(1,1,1).multiplyScalar(i.lightIntensity);const r=i.lights[i.lights.length-1];r&&r.shadow&&r.castShadow&&r.visible&&(this.uniforms.directionalShadowMap.value=r.shadow.map?.texture,this.uniforms.directionalShadowMatrix.value=r.shadow.matrix),r&&!r.visible?this.uniforms.directionalLight.value.color.set(0,0,0):this.uniforms.directionalLight.value.color.set(r.color.r,r.color.g,r.color.b).multiplyScalar(r.intensity*(r.userData?.volumetricIntensity??1));const s=a.children.find(e=>e.name===l);if(null!=s&&s.visible?this.uniforms.ambientLight.value.set(s.color.r,s.color.g,s.color.b).multiplyScalar(s.intensity*(s.userData?.volumetricIntensity??1)):this.uniforms.ambientLight.value.set(0,0,0),e instanceof n){const t=4.05,n=this.material.uniforms.stepCount.value;this.calculateDepthMappingParams(e.near,e.far,t,n,this.uniforms.depthMappingParams.value)}}calculateDepthMappingParams(e,t,n,i,o){const a=Math.max(e,.01),r=t,s=n,l=(r-a*Math.pow(2,i/s))/(r-a),c=(1-l)/a;o.set(c,l,s)}}const h=new e.Vector2(1,0),u=new e.Vector2(0,1);/*
1
+ import*as e from"three";import{Matrix4 as t,PerspectiveCamera as n,PointLight as i,Vector3 as o,WebGLRenderTarget as a}from"three";import{FullScreenQuad as r,Pass as s}from"three/examples/jsm/Addons.js";import{ambientLightName as l}from"../../scene/sky.js";import{FogVolumeObject as c}from"./fog-volume-object.js";const f=new o;export class VolumetricFogPass extends s{constructor(n){super(),this.resolution=n,this.fsQuad=new r(null),this.downSampleRatio=4,this._oldClearColor=new e.Color,this.oldClearAlpha=1,this.volumesCache=new WeakMap,this._viewProjection=new t,this._invViewProjection=new t;const i={minFilter:e.LinearFilter,magFilter:e.LinearFilter,format:e.RGBAFormat,depthBuffer:!1,stencilBuffer:!1,type:e.HalfFloatType},s=Math.round(this.resolution.x/this.downSampleRatio),l=Math.round(this.resolution.y/this.downSampleRatio);this.renderTargetFog=new a(s,l,i),this.renderTargetFog.texture.generateMipmaps=!1,this.renderTargetBlur=new a(s,l,i),this.renderTargetBlur.texture.generateMipmaps=!1,this.material=new e.ShaderMaterial({defines:{PERSPECTIVE_CAMERA:1,NUM_FOG_VOLUMES:0,MAX_POINT_LIGHTS:8,MAX_SPOT_LIGHTS:2,VOLUMETRIC:1,SHADOW_FAR:100},uniforms:{tDepth:{value:null},tDiffuse:{value:null},cameraPos:{value:new o},cameraMatrix:{value:new t},invProjection:{value:new t},fogVolumes:{value:[]},stepCount:{value:30},cameraNear:{value:null},cameraFar:{value:null},pointLights:{value:new Array(8).fill(null).map(()=>({color:new o(0,0,0),distance:0,position:new o}))},activePointLightCount:{value:0},spotLights:{value:new Array(2).fill(null).map(()=>({position:new o,direction:new o,color:new o,distance:0,decay:0,coneCos:0,penumbraCos:0}))},activeSpotLightCount:{value:0},directionalLight:{value:{direction:new o(0,-1,1).normalize(),color:new o(1,1,1)}},ambientLight:{value:new o(1,1,1).multiplyScalar(1.35)},directionalShadowMatrix:{value:new t},directionalShadowMap:{value:null},depthMappingParams:{value:new o}},vertexShader:"\n varying vec2 vUv;\n void main() {\n vUv = uv;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);\n }\n ",fragmentShader:"\n #define MAX_STEP_COUNT 64\n\n struct IncidentLight {\n vec3 color;\n vec3 direction;\n bool visible;\n };\n\n #if MAX_POINT_LIGHTS > 0\n struct PointLight {\n\t\t\tvec3 position;\n vec3 color;\n float distance;\n\t\t};\n #endif\n\n #if MAX_SPOT_LIGHTS > 0\n struct SpotLight {\n vec3 position;\n vec3 direction;\n vec3 color;\n float distance;\n float decay;\n float coneCos;\n float penumbraCos;\n };\n uniform SpotLight spotLights[ MAX_SPOT_LIGHTS ];\n uniform int activeSpotLightCount;\n float getSpotAttenuation( const in float coneCosine, const in float penumbraCosine, const in float angleCosine ) {\n return smoothstep( coneCosine, penumbraCosine, angleCosine );\n }\n #ifndef saturate\n #define saturate( a ) clamp( a, 0.0, 1.0 )\n #endif\n float pow2( const in float x ) { return x*x; }\n vec3 pow2( const in vec3 x ) { return x*x; }\n float pow4( const in float x ) { float x2 = x*x; return x2*x2; }\n float getDistanceAttenuation( const in float lightDistance, const in float cutoffDistance, const in float decayExponent ) {\n // based upon Frostbite 3 Moving to Physically-based Rendering\n // page 32, equation 26: E[window1]\n // https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf\n float distanceFalloff = 1.0 / max( pow( lightDistance, decayExponent ), 0.01 );\n if ( cutoffDistance > 0.0 ) {\n distanceFalloff *= pow2( saturate( 1.0 - pow4( lightDistance / cutoffDistance ) ) );\n }\n return distanceFalloff;\n }\n void getSpotLightInfo( const in SpotLight spotLight, const in vec3 geometryPosition, out IncidentLight light ) {\n vec3 lVector = spotLight.position - geometryPosition;\n light.direction = normalize( lVector );\n float angleCos = dot( light.direction, spotLight.direction );\n float spotAttenuation = getSpotAttenuation( spotLight.coneCos, spotLight.penumbraCos, angleCos );\n if ( spotAttenuation > 0.0 ) {\n float lightDistance = length( lVector );\n light.color = spotLight.color * spotAttenuation;\n light.color *= getDistanceAttenuation( lightDistance, spotLight.distance, spotLight.decay );\n light.visible = ( light.color != vec3( 0.0 ) );\n } else {\n light.color = vec3( 0.0 );\n light.visible = false;\n }\n }\n vec3 calcSpotLight(vec3 p, vec3 rayDir, float g, SpotLight spotLight) {\n IncidentLight directLight;\n getSpotLightInfo(spotLight, p, directLight);\n return directLight.color * 3.14;\n }\n #endif\n\n struct DirectionalLight {\n vec3 color; // pre multiplied with intensity\n vec3 direction; // should point *from the light*, normalized\n };\n\n struct FogVolume {\n vec3 sphereCenter;\n float sphereRadius;\n float baseDensity;\n float heightFalloff;\n float heightOffset;\n float scatteringDistribution;\n float startDistance;\n vec3 albedo;\n vec3 emissive; // Self-illumination color\n float ambientIntensity; // Multiplier for ambient light contribution\n float scatteringMode; // 0 = volumetric (occludes), 1 = lightShaft (god rays)\n };\n\n varying vec2 vUv;\n uniform sampler2D tDiffuse;\n uniform vec3 cameraPos;\n\n uniform float stepCount;\n uniform float nearMinDistance;\n\n uniform mat4 cameraMatrix;\n uniform mat4 invProjection;\n uniform sampler2D tDepth;\n\t\tuniform float cameraNear;\n\t\tuniform float cameraFar;\n \n uniform DirectionalLight directionalLight;\n\n uniform mat4 directionalShadowMatrix;\n uniform sampler2D directionalShadowMap;\n\n uniform vec3 ambientLight;\n uniform vec3 depthMappingParams;\n\n #if NUM_FOG_VOLUMES > 0\n uniform FogVolume fogVolumes[ NUM_FOG_VOLUMES ];\n #endif\n #if MAX_POINT_LIGHTS > 0\n uniform PointLight pointLights[ MAX_POINT_LIGHTS ];\n uniform int activePointLightCount;\n #endif\n\n #include <packing>\n\n // Ray-sphere intersection\n bool intersectSphere(vec3 rayOrigin, vec3 rayDir, vec3 sphereCenter, float sphereRadius, out float t0, out float t1) {\n // Vector from ray origin to sphere center\n vec3 L = rayOrigin - sphereCenter;\n float a = dot(rayDir, rayDir); // should be 1 if rayDir is normalized\n float b = 2.0 * dot(rayDir, L);\n float c = dot(L, L) - sphereRadius * sphereRadius;\n \n float discriminant = b * b - 4.0 * a * c;\n if (discriminant < 0.0) {\n return false; // no intersection\n }\n \n float sqrtD = sqrt(discriminant);\n t0 = (-b - sqrtD) / (2.0 * a);\n t1 = (-b + sqrtD) / (2.0 * a);\n \n // Make sure t0 <= t1\n if (t0 > t1) {\n float tmp = t0;\n t0 = t1;\n t1 = tmp;\n }\n return true;\n }\n\n float getLinearDepth( const in vec2 uv ) {\n\n float fragCoordZ = texture2D( tDepth, uv ).x;\n return perspectiveDepthToViewZ( fragCoordZ, cameraNear, cameraFar );\n\n\t\t}\n\n float linearizeDepth(float depth) {\n float z = depth * 2.0 - 1.0; // back to NDC (-1..1)\n return (2.0 * cameraNear * cameraFar) / (cameraFar + cameraNear - z * (cameraFar - cameraNear));\n }\n\n float getSliceAtDepth(float depth) {\n return log2(max(1.0, depth * depthMappingParams.x + depthMappingParams.y)) * depthMappingParams.z;\n }\n\n float getDepthAtSlice(float slice) {\n return (exp2(slice / depthMappingParams.z) - depthMappingParams.y) / depthMappingParams.x;\n }\n\n\n\n float texture2DCompare( sampler2D depths, vec2 uv, float compare ) {\n return step( compare, unpackRGBAToDepth( texture2D( depths, uv ) ) );\n }\n\n vec3 calcDirectionalLight(vec3 p, vec3 rayDir, float g, DirectionalLight dirLight, out float outInShadow) {\n vec3 toLight = dirLight.direction; // direction from sample point to light\n float cosTheta = dot(toLight, -rayDir); // angle between view ray and light\n cosTheta = clamp(cosTheta, -1.0, 1.0); // keep it safe\n\n // phase function (Henyey-Greenstein or isotropic)\n float denom = 1.0 + g*g - 2.0*g*cosTheta;\n float phase = (1.0 - g*g) / (4.0 * 3.14159265 * pow(denom, 1.5));\n phase *= 3.0 * 3.14159265; // scale to total energy\n\n vec4 shadowCoord = directionalShadowMatrix * vec4(p, 1.0);\n shadowCoord.xyz /= shadowCoord.w; // perspective divide\n\n float bias = 0.0; \n outInShadow = texture2DCompare(directionalShadowMap, shadowCoord.xy, shadowCoord.z - bias);\n\n return dirLight.color * phase * outInShadow;\n }\n\n vec3 calcPointLight(vec3 p, vec3 rayDir, float g, PointLight pointLight) {\n vec3 lightPos = pointLight.position;\n vec3 lightColor = pointLight.color;\n float lightRadius = pointLight.distance;\n\n vec3 L = normalize(lightPos - p);\n float lightDist = length(lightPos - p);\n \n float attenuation = clamp(1.0 - lightDist / lightRadius, 0.0, 1.0);\n attenuation *= attenuation; // smoother falloff\n attenuation /= (1.0 + lightDist*lightDist); // inverse-square style\n\n attenuation *= 4.0 * 3.14159; // Manually scaled to match point light intensity\n // float attenuation = 1.0 / (4.0 * 3.14159 * lightDist * lightDist);\n\n\n float cosTheta = dot(L, -rayDir); // angle between light and view ray\n float phase = (1.0 - g*g) / pow(1.0 + g*g - 2.0*g*cosTheta, 1.5);\n phase *= 0.25 / 3.14159; // normalize\n\n // Intensity is part of color\n vec3 light = lightColor * attenuation * phase;\n return light;\n }\n\n float random01(vec2 p) {\n return fract(sin(dot(p, vec2(41.0, 289.0))) * 45758.5453);\n }\n\n float hash(vec3 p) { return fract(sin(dot(p, vec3(12.9898,78.233,37.719))) * 43758.5453); }\n\n // Compute perceived brightness of a color (luminosity, Rec. 709)\n float luminosity(vec3 color) {\n return dot(color, vec3(0.2126, 0.7152, 0.0722));\n }\n \n void main() {\n // reconstruct ray in world space\n vec2 ndc = vUv * 2.0 - 1.0;\n vec4 nearPoint = invProjection * vec4(ndc, -1.0, 1.0);\n nearPoint /= nearPoint.w;\n vec4 farPoint = invProjection * vec4(ndc, 1.0, 1.0);\n farPoint /= farPoint.w;\n\n vec3 rayOrigin = cameraPos;\n vec3 rayDir = normalize(farPoint.xyz - cameraPos);\n\n float sceneDepth = -getLinearDepth(vUv); // in view space\n\n // vec4 scenePosView = invProjection * vec4(ndc, sceneDepth * 2.0 - 1.0, 1.0);\n // scenePosView /= scenePosView.w;\n\n // distance from camera to surface\n // float sceneDist = length(scenePosView.xyz - cameraPosition);\n\n float transmittance = 1.0;\n vec3 inscatter = vec3(0.0);\n float random = random01(vUv);\n\n\n #if NUM_FOG_VOLUMES > 0\n for (int v = 0; v < NUM_FOG_VOLUMES; v++) {\n FogVolume volume = fogVolumes[v];\n vec3 sphereCenter = volume.sphereCenter;\n float sphereRadius = volume.sphereRadius;\n float heightFalloff = volume.heightFalloff;\n float heightOffset = volume.heightOffset;\n float baseDensity = volume.baseDensity;\n float startDistance = volume.startDistance;\n float g = volume.scatteringDistribution;\n bool isLightShaft = volume.scatteringMode > 0.5;\n\n float t0, t1;\n if (!intersectSphere(rayOrigin, rayDir, sphereCenter, sphereRadius, t0, t1) || t1 <= 0.0) {\n continue;\n }\n // if camera inside sphere\n t0 = max(t0, cameraNear);\n float t_end = min(t1, sceneDepth);\n if (t_end <= t0) continue;\n\n float sliceStart = getSliceAtDepth(t0);\n float sliceEnd = getSliceAtDepth(t_end);\n float sliceRange = sliceEnd - sliceStart;\n \n // Determine number of steps based on distance, but capped at MAX_STEP_COUNT\n float maxStepSize = 0.3;\n int desiredSteps = int(ceil((t_end - t0) / maxStepSize));\n int numSteps = clamp(desiredSteps, 1, MAX_STEP_COUNT);\n float sliceStep = sliceRange / float(numSteps);\n\n float jitter = random;\n\n for (int i = 0; i < MAX_STEP_COUNT; i++) {\n if (i >= numSteps) break;\n\n float s0 = sliceStart + float(i) * sliceStep;\n float s1 = sliceStart + float(i + 1) * sliceStep;\n \n float tSampleT0 = getDepthAtSlice(s0);\n float tSampleT1 = getDepthAtSlice(s1);\n float t = getDepthAtSlice(s0 + jitter * sliceStep);\n \n float stepSize = tSampleT1 - tSampleT0;\n \n if (t > sceneDepth) break;\n\n vec3 p = rayOrigin + rayDir * t;\n\n float geomDelta = sceneDepth - t; // how far fog sample is from hitting geometry\n float fade = clamp(geomDelta, 0.0, 1.0); \n // float fade = clamp((sceneDepth - t) / 0.1, 0.0, 1.0);\n\n float closeFade = smoothstep(0.0, 0.3, (t - startDistance) / t);\n float edgeFade = clamp(sphereRadius - length(p - sphereCenter), 0.0, 1.0);\n\n // density with exponential height falloff\n float h = p.y - (sphereCenter.y + heightOffset);\n float heightFactor = clamp(exp(-h * heightFalloff), 0.0, 1.0);\n float density = baseDensity * heightFactor * fade * closeFade * edgeFade;\n\n vec3 light = vec3(0.0);\n float inDirectLight = 1.0; // 1.0 = in light, 0.0 = in shadow\n \n #if VOLUMETRIC == 1\n // Skip if we're too far away for directional light shadows\n // Or the entire volume will show instead of just the shafts\n if (isLightShaft && t > float(SHADOW_FAR)) {\n continue;\n }\n // Get directional light first - this also gives us the shadow value for god rays\n light += calcDirectionalLight(p, rayDir, g, directionalLight, inDirectLight);\n \n // For god rays mode, skip everything else if we're in shadow\n // For atmospheric mode, always include all lights\n if (!isLightShaft || inDirectLight > 0.5) {\n #if MAX_POINT_LIGHTS > 0\n for (int pi = 0; pi < activePointLightCount; pi++) {\n light += calcPointLight(p, rayDir, g, pointLights[pi]);\n } \n #endif\n\n #if MAX_SPOT_LIGHTS > 0\n for (int pi = 0; pi < activeSpotLightCount; pi++) {\n light += calcSpotLight(p, rayDir, g, spotLights[pi]);\n } \n #endif\n\n // Ambient and emissive only for atmospheric mode, not god rays\n if (!isLightShaft) {\n light += ambientLight * volume.ambientIntensity;\n light += volume.emissive;\n }\n }\n\n #else\n light = vec3(1.0);\n #endif\n\n light *= volume.albedo;\n \n if (!isLightShaft || inDirectLight > 0.5) {\n float extinction = exp(-density * stepSize);\n inscatter += light * (1.0 - extinction) * transmittance;\n transmittance *= extinction;\n }\n }\n }\n #endif\n\n float alpha = 1.0 - transmittance;\n\n gl_FragColor = vec4(inscatter, alpha);\n }\n "}),this.material.onBeforeCompile=e=>{},this.materialBlur=new e.ShaderMaterial({uniforms:{tInput:{value:null},tDepth:{value:null},texelSize:{value:new e.Vector2(1,1).divideScalar(1e3)},direction:{value:new e.Vector2(1,0)},depthSigma:{value:2},blurSigma:{value:1},cameraNear:{value:.1},cameraFar:{value:1e3}},vertexShader:"\n varying vec2 vUv;\n void main() {\n vUv = uv;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n }\n ",fragmentShader:"\n uniform sampler2D tInput;\n uniform sampler2D tDepth;\n uniform vec2 texelSize;\n uniform vec2 direction;\n uniform float depthSigma;\n uniform float blurSigma;\n uniform float cameraNear;\n uniform float cameraFar;\n varying vec2 vUv;\n\n #include <packing>\n\n float getLinearDepth( const in vec2 uv ) {\n float fragCoordZ = texture2D( tDepth, uv ).x;\n return perspectiveDepthToViewZ( fragCoordZ, cameraNear, cameraFar );\n }\n\n void main() {\n float centerDepth = getLinearDepth(vUv);\n vec4 center = texture2D(tInput, vUv);\n vec4 centerColor = center;\n\n vec4 sum = vec4(0.0);\n float wsum = 0.0;\n\n for (int i = -5; i <= 5; i++) {\n vec2 offset = direction * float(i) * texelSize;\n vec2 uv = vUv + offset;\n\n vec4 sampleColor = texture2D(tInput, uv);\n float sampleDepth = getLinearDepth(uv);\n\n float spatialWeight = exp(-0.5 * (float(i) * float(i)) / (blurSigma * blurSigma));\n float depthDiff = abs(sampleDepth - centerDepth);\n float depthWeight = exp(-0.5 * (depthDiff * depthDiff) / (depthSigma * depthSigma));\n\n float w = spatialWeight * depthWeight;\n\n sum += sampleColor * w;\n wsum += w;\n }\n\n gl_FragColor = sum / (wsum + 0.0001);\n }\n ",transparent:!0}),this.materialComposite=new e.ShaderMaterial({uniforms:{tDiffuse:{value:null},tFog:{value:null},tDepth:{value:null},fogResolution:{value:new e.Vector2}},vertexShader:"\n varying vec2 vUv;\n void main() {\n vUv = uv;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n }\n ",fragmentShader:"\n uniform sampler2D tDiffuse;\n uniform sampler2D tFog;\n uniform sampler2D tDepth;\n uniform vec2 fogResolution;\n varying vec2 vUv;\n\n #include <packing>\n\n void main() {\n vec2 fogTexelSize = 1.0 / fogResolution;\n vec2 fogUv = vUv * fogResolution - vec2(0.5);\n vec2 uv00 = (floor(fogUv) + vec2(0.5)) * fogTexelSize;\n vec2 uv10 = uv00 + vec2(fogTexelSize.x, 0.0);\n vec2 uv01 = uv00 + vec2(0.0, fogTexelSize.y);\n vec2 uv11 = uv00 + vec2(fogTexelSize.x, fogTexelSize.y);\n\n float d00 = texture2D(tDepth, uv00).x;\n float d10 = texture2D(tDepth, uv10).x;\n float d01 = texture2D(tDepth, uv01).x;\n float d11 = texture2D(tDepth, uv11).x;\n\n float dCenter = texture2D(tDepth, vUv).x;\n\n float diff00 = abs(dCenter - d00);\n float diff10 = abs(dCenter - d10);\n float diff01 = abs(dCenter - d01);\n float diff11 = abs(dCenter - d11);\n\n float minDiff = min(min(diff00, diff10), min(diff01, diff11));\n\n vec2 closestUv = uv00;\n if (minDiff == diff10) closestUv = uv10;\n else if (minDiff == diff01) closestUv = uv01;\n else if (minDiff == diff11) closestUv = uv11;\n\n vec4 sceneColor = texture2D(tDiffuse, vUv);\n \n // Depth-aware upscaling:\n // By default use hardware bilinear, but near depth edges, snap to the closest depth texel.\n // This avoids leaking fog across sharp silhouettes while keeping smooth areas artifact-free.\n float maxDiff = max(max(diff00, diff10), max(diff01, diff11));\n vec2 sampleUv = vUv;\n if (maxDiff > 0.001) {\n sampleUv = closestUv;\n }\n\n vec4 fogColor = texture2D(tFog, sampleUv);\n\n // Composite: fogColor.a is the fog alpha, blend over scene\n // vec3 color = mix(sceneColor.rgb, fogColor.rgb, fogColor.a);\n // Multiplying with fogColor.a again helps to avoid dark borders around the fog\n gl_FragColor = vec4(fogColor.rgb + (1.0 - fogColor.a * fogColor.a) * sceneColor.rgb, 1.0);\n }\n ",transparent:!0}),this.materialComposite.uniforms.fogResolution.value.set(s,l)}render(e,t,n){e.getClearColor(this._oldClearColor),this.oldClearAlpha=e.getClearAlpha();const i=e.autoClear;e.autoClear=!1,e.setClearColor(16777215,0),this.fsQuad.material=this.material,e.setRenderTarget(this.renderTargetFog),e.clear(),this.fsQuad.render(e),this.materialBlur.uniforms.tInput.value=this.renderTargetFog.texture,this.materialBlur.uniforms.direction.value=h,this.fsQuad.material=this.materialBlur,e.setRenderTarget(this.renderTargetBlur),e.clear(),this.fsQuad.render(e),this.materialBlur.uniforms.tInput.value=this.renderTargetBlur.texture,this.materialBlur.uniforms.direction.value=u,e.setRenderTarget(this.renderTargetFog),e.clear(),this.fsQuad.render(e),this.materialComposite.uniforms.tDiffuse.value=n.texture,this.materialComposite.uniforms.tFog.value=this.renderTargetFog.texture,this.fsQuad.material=this.materialComposite,this.renderToScreen?(e.setRenderTarget(null),this.fsQuad.render(e)):(e.setRenderTarget(t),this.clear&&e.clear(),this.fsQuad.render(e)),e.setClearColor(this._oldClearColor,this.oldClearAlpha),e.autoClear=i}setSize(e,t){let n=Math.round(e/this.downSampleRatio),i=Math.round(t/this.downSampleRatio);this.renderTargetFog.setSize(n,i),this.renderTargetBlur.setSize(n,i),this.materialBlur.uniforms.texelSize.value.set(1/n,1/i),this.materialComposite.uniforms.fogResolution.value.set(n,i)}set volumetric(e){this.material.defines.VOLUMETRIC=e?1:0}get volumetric(){return!!this.material.defines.VOLUMETRIC}getVolumeUniforms(e){let t=this.volumesCache.get(e);return null==t&&(t={albedo:new o,baseDensity:e.baseDensity,heightFalloff:e.heightFalloff,heightOffset:e.heightOffset,startDistance:e.startDistance??3,scatteringDistribution:e.scatteringDistribution,sphereRadius:1,sphereCenter:new o,emissive:new o,ambientIntensity:e.ambientIntensity??1,scatteringMode:"lightShaft"===e.scatteringMode?1:0},this.volumesCache.set(e,t)),e.object.getWorldPosition(t.sphereCenter),e.object.getWorldScale(f),t.sphereRadius=5*f.x,t.albedo.set(e.albedo.r,e.albedo.g,e.albedo.b),t.baseDensity=e.baseDensity,t.heightFalloff=e.heightFalloff,t.heightOffset=e.heightOffset,t.scatteringDistribution=e.scatteringDistribution,t.startDistance=e.startDistance,t.emissive.set(e.emissive.r,e.emissive.g,e.emissive.b),t.ambientIntensity=e.ambientIntensity??1,t.scatteringMode="lightShaft"===e.scatteringMode?1:0,t}getPointLightUniforms(e){const t={position:new o,color:new o,distance:0};return e.getWorldPosition(t.position),t.color.set(e.color.r,e.color.g,e.color.b).multiplyScalar(e.intensity*(e.userData?.volumetricIntensity??1)),t.distance=void 0!==e.distance&&e.distance>0?e.distance:1,t}getSpotLightUniforms(e){const t={position:new o,direction:new o,color:new o,distance:0,coneCos:0,penumbraCos:0,decay:1};return e.getWorldPosition(t.position),t.direction.setFromMatrixPosition(e.matrixWorld),f.setFromMatrixPosition(e.target.matrixWorld),t.direction.sub(f),t.color.set(e.color.r,e.color.g,e.color.b).multiplyScalar(e.intensity*(e.userData?.volumetricIntensity??1)),t.distance=void 0!==e.distance&&e.distance>0?e.distance:0,t.coneCos=Math.cos(e.angle),t.penumbraCos=Math.cos(e.angle*(1-e.penumbra)),t.decay=void 0!==e.decay?e.decay:1,t}updateArrayUniforms(t){const n=this.material.uniforms.fogVolumes.value;let o=0;const a=this.material.uniforms.pointLights.value;let r=0;const s=this.material.uniforms.spotLights.value;let l=0;t.traverseVisible(t=>{if(t instanceof c){const e=t.volume,i=this.getVolumeUniforms(e);n[o]=i,o++}else if(t instanceof i){if(r<a.length){if(0===t.userData.volumetricIntensity)return;const e=this.getPointLightUniforms(t);a[r].position.copy(e.position),a[r].color.copy(e.color),a[r].distance=e.distance,r++}}else if(t instanceof e.SpotLight&&l<s.length){if(0===t.userData.volumetricIntensity)return;const e=this.getSpotLightUniforms(t);s[l].position.copy(e.position),s[l].direction.copy(e.direction),s[l].color.copy(e.color),s[l].distance=e.distance,s[l].coneCos=e.coneCos,s[l].penumbraCos=e.penumbraCos,s[l].decay=e.decay,l++}}),this.material.uniforms.activePointLightCount.value=r,this.material.uniforms.activeSpotLightCount.value=l,this.material.defines.NUM_FOG_VOLUMES!==o&&(this.material.needsUpdate=!0,this.material.uniformsNeedUpdate=!0),this.material.defines.NUM_FOG_VOLUMES=o}get uniforms(){return this.material.uniforms}update(e,t,i,a){if(this.updateArrayUniforms(a),this.enabled=0!=this.material.defines.NUM_FOG_VOLUMES,!this.enabled)return;null==this.uniforms.cameraPosition&&(this.uniforms.cameraPos={value:new o}),this.uniforms.cameraPos.value.copy(e.position),this._viewProjection.multiplyMatrices(e.projectionMatrix,e.matrixWorldInverse),this.uniforms.invProjection.value.copy(this._invViewProjection.copy(this._viewProjection).invert()),this.uniforms.tDepth.value=t.depthTexture,this.materialBlur.uniforms.tDepth.value=t.depthTexture,this.materialComposite.uniforms.tDepth.value=t.depthTexture,e instanceof n&&(this.uniforms.cameraNear.value=e.near,this.uniforms.cameraFar.value=e.far,this.materialBlur.uniforms.cameraNear.value=e.near,this.materialBlur.uniforms.cameraFar.value=e.far),this.uniforms.directionalLight.value.direction=i.lightDirection,this.uniforms.directionalLight.value.color.set(1,1,1).multiplyScalar(i.lightIntensity);const r=i.lights[i.lights.length-1];r&&r.shadow&&r.castShadow&&r.visible&&(this.uniforms.directionalShadowMap.value=r.shadow.map?.texture,this.uniforms.directionalShadowMatrix.value=r.shadow.matrix),r&&!r.visible?this.uniforms.directionalLight.value.color.set(0,0,0):this.uniforms.directionalLight.value.color.set(r.color.r,r.color.g,r.color.b).multiplyScalar(r.intensity*(r.userData?.volumetricIntensity??1));const s=a.children.find(e=>e.name===l);if(null!=s&&s.visible?this.uniforms.ambientLight.value.set(s.color.r,s.color.g,s.color.b).multiplyScalar(s.intensity*(s.userData?.volumetricIntensity??1)):this.uniforms.ambientLight.value.set(0,0,0),e instanceof n){const t=4.05,n=this.material.uniforms.stepCount.value;this.calculateDepthMappingParams(e.near,e.far,t,n,this.uniforms.depthMappingParams.value)}}calculateDepthMappingParams(e,t,n,i,o){const a=Math.max(e,.01),r=t,s=n,l=(r-a*Math.pow(2,i/s))/(r-a),c=(1-l)/a;o.set(c,l,s)}}const h=new e.Vector2(1,0),u=new e.Vector2(0,1);/*
2
2
  * Copyright (©) 2026 Hology Interactive AB. All rights reserved.
3
3
  * See the LICENSE.md file for details.
4
4
  */
@@ -1,4 +1,5 @@
1
- import { Data3DTexture, Object3D, Vector3, WebGLRenderer } from "three";
1
+ import type { Object3D, WebGLRenderer } from "three";
2
+ import { Data3DTexture, Vector3 } from "three";
2
3
  export declare class LightVolume {
3
4
  shTexture: Data3DTexture;
4
5
  gridResolution: Vector3;
@@ -0,0 +1,8 @@
1
+ import type { NodeShaderMaterial } from 'three-shader-graph';
2
+ import { Vec4Node } from 'three-shader-graph';
3
+ /**
4
+ * Creates the renderer-owned third MRT output without coupling the generic
5
+ * node-material compiler to Hology's G-buffer layout.
6
+ */
7
+ export declare function createMotionVectorOutput(material: NodeShaderMaterial): Vec4Node;
8
+ //# sourceMappingURL=motion-vector-node.d.ts.map
@@ -0,0 +1,4 @@
1
+ import{isWgslCompiler as e,isWgslFragmentCompiler as n,vec4 as i,Vec4Node as t}from"three-shader-graph";export function createMotionVectorOutput(e){const n=e,t=e.nodeMaterialDefinition.position;if(null!=t&&null==n.outputPreviousPosition)return i(0,0,1,1);const r=t??new o("current",n.outputPreviousTransform??null),u=null==t?new o("previous",n.outputPreviousTransform??null):n.outputPreviousPosition;return new s(r,u)}class o extends t{constructor(e,n){super(),this.frame=e,this.previousTransform=n}compile(i){if(!e(i)||n(i))throw new Error("Renderer motion clip positions require a WGSL vertex graph.");const t=`rendererMotionClip${i.variable()}`,o=i.getRendererVertexPosition();if("current"===this.frame)return{chunk:`\n#ifdef RENDERER_MOTION_VECTORS\n#ifdef USE_SKINNING\n let ${t} = rendererMotionCurrentClip(\n rendererSkinPosition(input.position, input.skinIndex, input.skinWeight),\n input.instanceIndex\n );\n#else\n let ${t} = rendererMotionCurrentClip(${o}, input.instanceIndex);\n#endif\n#else\n let ${t} = vec4<f32>(0.0);\n#endif`,out:t};return{chunk:`\n#ifdef RENDERER_MOTION_VECTORS\n#ifdef USE_SKINNING\n let ${t} = rendererMotionPreviousClip(\n rendererSkinPosition(input.position, input.skinIndex, input.skinWeight),\n rendererPreviousSkinPosition(input.position, input.skinIndex, input.skinWeight),\n input.instanceIndex\n );\n#else\n let ${t} = rendererMotionPreviousClip(\n ${o},\n ${null==this.previousTransform?o:`((${i.get(this.previousTransform)}) * vec4<f32>(input.position, 1.0)).xyz`},\n input.instanceIndex\n );\n#endif\n#else\n let ${t} = vec4<f32>(0.0);\n#endif`,out:t}}}class r extends t{compile(i){if(!e(i)||n(i))throw new Error("Renderer motion parameters require a WGSL vertex graph.");const t=`rendererMotionParams${i.variable()}`;return{chunk:`\n#ifdef RENDERER_MOTION_VECTORS\n let ${t} = rendererMotionBlurParams(input.instanceIndex);\n#else\n let ${t} = vec4<f32>(0.0);\n#endif`,out:t}}}class s extends t{constructor(e,n){super(),this.currentClip=e,this.previousClip=n,this.motionParams=new r}compile(e){if(!n(e))throw new Error("Motion-vector output requires the WGSL fragment compiler.");const i=e.defineVarying("vec4",this.currentClip).out,t=e.defineVarying("vec4",this.previousClip).out,o=e.defineVarying("vec4",this.motionParams).out,r=e.variable(),s=`motionVector${r}`;return{chunk:`\n let motionSafeCurrentW${r} = select(${i}.w, 1.0, abs(${i}.w) < 1e-6);\n let motionSafePreviousW${r} = select(${t}.w, 1.0, abs(${t}.w) < 1e-6);\n let motionCurrentNdc${r} = ${i}.xy / motionSafeCurrentW${r};\n let motionPreviousNdc${r} = ${t}.xy / motionSafePreviousW${r};\n // Previous -> current velocity in UV units. WebGPU NDC is Y-up while\n // render-target UV is Y-down, hence the negative Y scale.\n var motionVelocityUv${r} =\n (motionCurrentNdc${r} - motionPreviousNdc${r}) * vec2<f32>(0.5, -0.5);\n let motionFinite${r} =\n all(motionVelocityUv${r} == motionVelocityUv${r}) &&\n all(abs(motionVelocityUv${r}) < vec2<f32>(65504.0));\n motionVelocityUv${r} =\n select(vec2<f32>(0.0), motionVelocityUv${r}, motionFinite${r});\n let motionMode${r} = clamp(${o}.x, 0.0, 3.0);\n let motionScale${r} = clamp(${o}.y, 0.0, 8.0);\n let ${s} = vec4<f32>(\n motionVelocityUv${r},\n motionMode${r},\n motionScale${r}\n );`,out:s}}}/*
2
+ * Copyright (©) 2026 Hology Interactive AB. All rights reserved.
3
+ * See the LICENSE.md file for details.
4
+ */
@@ -1,5 +1,6 @@
1
1
  import * as THREE from 'three';
2
- import { NodeShaderMaterial, type CompiledNodeWebGpuFullscreen, type WebGpuMaterialUniform, type WebGpuMaterialUniformValue, type WebGpuMaterialVertexAttribute, type WgslGraphTextureBinding, Vec4Node, Vec3Node } from 'three-shader-graph';
2
+ import type { Vec4Node, Vec3Node } from 'three-shader-graph';
3
+ import { NodeShaderMaterial, type CompiledNodeWebGpuFullscreen, type WebGpuMaterialUniform, type WebGpuMaterialUniformValue, type WebGpuMaterialVertexAttribute, type WgslGraphTextureBinding } from 'three-shader-graph';
3
4
  type ResolvedWebGpuMaterial = THREE.Material & {
4
5
  readonly vertexShader: string;
5
6
  readonly fragmentShader: string;
@@ -1,4 +1,4 @@
1
- import { ShaderMaterial, Texture, Vector2, WebGLRenderTarget, WebGLRenderer } from 'three';
1
+ import type { ShaderMaterial, Texture, Vector2, WebGLRenderTarget, WebGLRenderer } from 'three';
2
2
  import { Pass } from 'three/examples/jsm/Addons.js';
3
3
  export declare const postProcessEffectStages: readonly ["beforeFog", "beforeDepthOfField", "beforeColorAdjustment", "beforeOutline", "beforeAntiAliasing", "beforeLut", "beforeOutput"];
4
4
  export type PostProcessEffectStage = typeof postProcessEffectStages[number];
@@ -2,11 +2,18 @@ import * as THREE from 'three';
2
2
  import { NodeMaterialWebGpuResolver } from './node-material-webgpu-resolver.js';
3
3
  import type { WorldEnvironmentState } from './environment.js';
4
4
  import type { PostProcessEffectStage } from './post-process-effect.js';
5
- import type { BloomOptions, ColorGradingOptions, DepthOfFieldOptions, FXAAOptions, GTAOOptions, OutlineOptions, OutlineInstanceSelection, SSROptions, RendererFrameProfile, SMAAOptions, TemporalAAOptions, VolumetricFogOptions } from '@hology/webgpu-renderer';
6
- export type { RendererFrameProfile } from '@hology/webgpu-renderer';
5
+ import type { BloomOptions, ColorGradingOptions, DepthOfFieldOptions, FXAAOptions, GTAOOptions, GTAOQuality, OutlineOptions, OutlineInstanceSelection, MotionBlurOptions, MotionBlurOverride, MotionBlurOverrideHandle, SSROptions, RendererFrameProfile, SMAAOptions, TemporalAAOptions, VolumetricFogOptions, VolumetricFogQuality } from '@hology/webgpu-renderer';
6
+ export type { RendererFrameProfile, GTAOOptions, GTAOQuality, MotionBlurOptions, MotionBlurOverride, MotionBlurOverrideHandle, VolumetricFogOptions, VolumetricFogQuality } from '@hology/webgpu-renderer';
7
7
  type SupportedCamera = THREE.PerspectiveCamera | THREE.OrthographicCamera;
8
+ export type AntiAliasingMode = 'off' | 'fxaa' | 'smaa' | 'taa';
8
9
  export interface ExperimentalWebGpuPostProcessingOptions {
9
10
  readonly bloom?: false | BloomOptions;
11
+ /**
12
+ * Selects one mutually exclusive antialiasing method. When present, all
13
+ * variants are kept available for inexpensive runtime switching.
14
+ * Defaults to the legacy behavior (TAA unless explicitly disabled/replaced).
15
+ */
16
+ readonly antiAliasing?: AntiAliasingMode;
10
17
  /** Optional low-cost, stateless antialiasing pass. Enable with `fxaa: {}`. */
11
18
  readonly fxaa?: false | FXAAOptions;
12
19
  /**
@@ -18,6 +25,8 @@ export interface ExperimentalWebGpuPostProcessingOptions {
18
25
  /** Screen-space reflections using the opaque color, depth, normal and roughness buffers. */
19
26
  readonly ssr?: false | SSROptions;
20
27
  readonly taa?: false | TemporalAAOptions;
28
+ /** HDR motion blur using opaque MRT velocity. Disabled by default. */
29
+ readonly motionBlur?: false | MotionBlurOptions;
21
30
  /** Compute-driven froxel fog. Existing FogVolume actors are discovered automatically. */
22
31
  readonly volumetricFog?: false | VolumetricFogOptions;
23
32
  /** Final HDR color grading and tone mapping. PostProcessVolumes update it at runtime. */
@@ -70,12 +79,15 @@ export declare class ExperimentalWebGpuBackend {
70
79
  private readonly gtao;
71
80
  private readonly ssr;
72
81
  private readonly taa;
82
+ private readonly motionBlur;
73
83
  private readonly volumetricFog;
74
84
  private readonly colorGrading;
75
85
  private readonly depthOfField;
76
86
  private readonly outline;
77
87
  private readonly postProcessEffects;
88
+ private readonly antiAliasingState;
78
89
  readonly canvas: HTMLCanvasElement;
90
+ private renderScale;
79
91
  private pmremGenerator;
80
92
  private readonly pmremResults;
81
93
  private readonly ownedPmremResults;
@@ -83,7 +95,7 @@ export declare class ExperimentalWebGpuBackend {
83
95
  private constructor();
84
96
  static create(postProcessing?: ExperimentalWebGpuPostProcessingOptions): Promise<ExperimentalWebGpuBackend>;
85
97
  static supportsCamera(camera: THREE.Camera): camera is SupportedCamera;
86
- setSize(width: number, height: number, pixelRatio: number): void;
98
+ setSize(width: number, height: number, presentationPixelRatio: number, renderScale?: number): void;
87
99
  initTexture(texture: THREE.Texture): void;
88
100
  setPostProcessEffects(effects: readonly ExperimentalWebGpuPostProcessEffect[]): void;
89
101
  setEnableOutlines(enabled: boolean): void;
@@ -103,6 +115,14 @@ export declare class ExperimentalWebGpuBackend {
103
115
  compile(scene: THREE.Scene, camera: SupportedCamera): Promise<void>;
104
116
  createStaticDrawSet(root: THREE.Object3D, label: string): ExperimentalWebGpuStaticDrawSet | null;
105
117
  render(scene: THREE.Scene, camera: SupportedCamera, timeSeconds: number): void;
118
+ setMotionBlurOptions(options: MotionBlurOptions): void;
119
+ setGTAOOptions(options: GTAOOptions): void;
120
+ setAntiAliasingMode(mode: AntiAliasingMode): void;
121
+ setGTAOQuality(quality: GTAOQuality): void;
122
+ setVolumetricFogOptions(options: VolumetricFogOptions): void;
123
+ setVolumetricFogQuality(quality: VolumetricFogQuality): void;
124
+ pushMotionBlurOverride(override: MotionBlurOverride): MotionBlurOverrideHandle | null;
125
+ invalidateObjectMotion(object: THREE.Object3D): void;
106
126
  getLastFrameProfile(): RendererFrameProfile | null;
107
127
  setPostProcessState(state: ExperimentalWebGpuPostProcessState | null): void;
108
128
  setProfilingEnabled(enabled: boolean): void;
@@ -1,4 +1,4 @@
1
- import*as e from"three";import{NodeMaterialWebGpuResolver as t}from"./node-material-webgpu-resolver.js";import{float as n,varyingTransformed as i,vec4 as r}from"three-shader-graph";import{NodeShaderMaterial as o}from"../shader-nodes/index.js";import{sceneMapUniformName as s}from"../shader-nodes/scene-sample.js";import{MATERIAL_FLAG_ALPHA_TEST as a,MATERIAL_FLAG_RECEIVE_DECALS as l}from"../shader-nodes/material-flags.js";export class ExperimentalWebGpuBackend{constructor(e,t,n,i,r,o,s,a,l,d,c,u,h,p,m,g){this.renderer=e,this.fallbackMaterial=t,this.nodeMaterialResolver=n,this.gBufferFallbackSource=i,this.bloom=r,this.fxaa=o,this.smaa=s,this.gtao=a,this.ssr=l,this.taa=d,this.volumetricFog=c,this.colorGrading=u,this.depthOfField=h,this.outline=p,this.postProcessEffects=m,this.pmremGenerator=null,this.pmremResults=new WeakMap,this.ownedPmremResults=new Set,this.warnedUnsupportedPostProcessStages=new Set,this.canvas=g}static async create(u={}){const h=await import("@hology/webgpu-renderer"),p=document.createElement("canvas");p.dataset.hologyRenderer="webgpu-experimental";const m=new h.WGSLShaderMaterial({vertexShader:d,fragmentShader:c});m.side=h.DoubleSide;const g=new t(h,(e,t)=>"opaque"===t?[e.outputColor.xyz.rgba(n(e.alphaTest>0?a:0).add(!1===e.userData.receiveDecals?0:l)),r((e.outputNormal??i.normal).multiplyScalar(.5).addScalar(.5),e.outputRoughness??n(1))]:void 0),f=new e.MeshStandardMaterial({color:8688803,roughness:1});if(null==g.resolve(f,"opaque"))throw m.dispose(),f.dispose(),new Error("Failed to compile the WebGPU G-buffer fallback material.");const P=!1===u.gtao?null:new h.GTAOEffect({normalSpace:"view",resolutionScale:.5,...u.gtao??{}}),S=!1===u.ssr||null==u.ssr?null:new h.SSREffect({normalSpace:"view",resolutionScale:.5,...u.ssr??{}}),x=!1===u.bloom?null:new h.BloomEffect({threshold:1,strength:.9,radius:.2,...u.bloom??{}}),w=!1===u.fxaa||null==u.fxaa?null:new h.FXAAEffect(u.fxaa),b=!1===u.smaa||null==u.smaa?null:new h.SMAAEffect(u.smaa),v=!1===u.taa||null!=u.smaa&&!1!==u.smaa&&null==u.taa?null:new h.TemporalAAEffect({renderScale:.9,...u.taa??{}}),C=u.volumetricFog||void 0,L=!1===u.volumetricFog?null:new h.VolumetricFogEffect(void 0,{froxelPixelSize:8,maxDistance:64,depthSlices:64,integrationSubsteps:2,temporalJitter:!0,spatialJitter:!0,scatteringIntensity:Math.PI,...C,blur:!1!==C?.blur&&{depthAwareUpsample:!0,...C?.blur}}),y=!1===u.colorGrading?null:new h.ColorGradingEffect({enabled:!1,...u.colorGrading??{}}),F=!1===u.depthOfField?null:new h.DepthOfFieldEffect({enabled:!1,...u.depthOfField??{}}),M=!1===u.outline?null:new h.OutlineEffect({enabled:!1,...u.outline??{}}),G=[],O=new WeakMap,D=new Set,R=(e,t,n)=>{let i=n;const r=G.filter(e=>e.enabled&&e.stage===t).sort((e,t)=>e.priority-t.priority||e.id-t.id);for(const t of r){if(!(t.material instanceof o)){const e=`${t.id}:material`;D.has(e)||(D.add(e),console.warn(`[Hology WebGPU experiment] Skipping custom post-process effect ${t.id}: only NodeShaderMaterial effects are supported.`));continue}try{let n=O.get(t.material);null!=n&&n.version===t.material.version||(n={version:t.material.version,shader:g.compileFullscreen(t.material)},O.set(t.material,n));const r=e.createColorTarget(`hologyCustomPostProcess${t.id}`);e.addNodeFullscreenPass({name:`HologyCustomPostProcess${t.id}`,shader:n.shader,inputOverrides:{[s]:i},outputs:r}),i=r}catch(e){const n=`${t.id}:compile:${t.material.version}`;D.has(n)||(D.add(n),console.warn(`[Hology WebGPU experiment] Skipping custom post-process effect ${t.id}: ${e instanceof Error?e.message:String(e)}`,e))}}return i},E=new h.WebGpuRenderer({canvas:p,fallbackMaterial:m,materialResolver:(e,t)=>g.resolve(e,t),invokeObjectRenderCallbacks:!1,hiZOcclusionCulling:!0,renderGraphProfiling:!1,sampleCount:1,multiDrawIndirect:!1});E.setRenderScale(v?.renderScale??1),E.setRenderGraphBuilder((e,t)=>{const n=new h.FrameGraphBuilder(t),i=n.createColorTarget("hologyNormalRoughness",{format:"rgba16float"});n.addDirectionalShadowPass(),n.addPointShadowPass(),n.addSpotShadowPass(),n.addDepthPrepass(),n.addHiZPasses(),n.addVisibilityPass(),n.addMaterialPass({name:"OpaqueGBufferPass",outputs:[n.sceneColor,i],drawStage:"opaque",loadDepth:!0}),n.addHiZHistoryPass("OpaqueGBufferPass");let r=n.sceneColor;if(null!=P){const e=P.addPasses(n,{depth:n.mainDepth,normal:i});r=P.addCompositePass(n,r,e)}null!=S&&(r=S.addPasses(n,{sceneColor:r,depth:n.mainDepth,normalRoughness:i})),n.addSceneTextureCopyPass(r),n.addLatePass(r),r=R(n,"beforeFog",r),null!=L&&(r=L.addPasses(n,{sceneColor:r,depth:n.mainDepth,volumeObjects:t.fogVolumeObjects,lights:t.extractedLights})),null!=x&&(r=x.addPass(n,r)),r=R(n,"beforeOutline",r),null!=M&&(r=M.addPass(n,r)),r=R(n,"beforeDepthOfField",r),null!=F&&(r=F.addPass(n,r,n.mainDepth)),r=R(n,"beforeColorAdjustment",r),null!=y&&(r=y.addPass(n,r)),r=R(n,"beforeAntiAliasing",r);let o=v?.addPass(n,r,n.mainDepth)??r;null!=b?o=b.addPass(n,o):null!=w&&(o=w.addPass(n,o)),o=R(n,"beforeOutput",o),n.present(o)});try{return await E.initialize(),new ExperimentalWebGpuBackend(E,m,g,f,x,w,b,P,S,v,L,y,F,M,G,p)}catch(e){throw E.destroy(),m.dispose(),g.dispose(),f.dispose(),P?.dispose(),b?.dispose(),L?.dispose(),M?.dispose(),e}}static supportsCamera(t){return t instanceof e.PerspectiveCamera||t instanceof e.OrthographicCamera}setSize(e,t,n){this.renderer.setSize(e,t,n)}initTexture(e){this.renderer.initTexture(e)}setPostProcessEffects(e){this.postProcessEffects.splice(0,this.postProcessEffects.length,...e);for(const t of e){if("beforeLut"!==t.stage)continue;const e=`${t.id}:${t.stage}`;this.warnedUnsupportedPostProcessStages.has(e)||(this.warnedUnsupportedPostProcessStages.add(e),console.warn(`[Hology WebGPU experiment] Skipping custom post-process effect ${t.id}: stage "${t.stage}" has no native WebGPU anchor.`))}}setEnableOutlines(e){null!=this.outline&&(this.outline.enabled=e)}setDynamicBatchingEnabled(e){this.renderer.setDynamicBatchingEnabled(e)}setSelectedObjects(e){this.outline?.setSelectedObjects(e)}setSelectedObjectInstances(e){this.outline?.setSelectedInstances(e)}setCascadedShadowRanges(e,t=[]){this.renderer.setCascadedShadowRanges(e,t),this.volumetricFog?.setCascadedDirectionalLights(t)}setWorldEnvironment(t){if(null==t)return void this.nodeMaterialResolver.setWorldEnvironment(null);let n=t.source;if(n.mapping!==e.CubeUVReflectionMapping){let e=this.pmremResults.get(n);null==e&&(this.pmremGenerator??(this.pmremGenerator=this.renderer.createPMREMGenerator()),e=this.pmremGenerator.fromEquirectangular(n),this.pmremResults.set(n,e),this.ownedPmremResults.add(e)),n=e.texture}this.nodeMaterialResolver.setWorldEnvironment({texture:n,intensity:t.intensity})}resetViewHistory(){this.renderer.resetViewHistory(),this.taa?.reset()}async compile(e,t){await this.renderer.compileAsync(e,t)}createStaticDrawSet(e,t){this.renderer.setHiZOcclusionCullingEnabled(!0);const n=this.renderer.createStaticDrawSet(e,{label:t,onUnsupported:"skip"});return 0===n.drawCount?(n.dispose(),null):n}render(e,t,n){this.volumetricFog?.setScene(e),this.taa?.prepareCamera(t,this.canvas.width,this.canvas.height);try{this.renderer.render(e,t,n)}finally{this.taa?.finishCamera(t)}}getLastFrameProfile(){return this.renderer.getLastFrameProfile()}setPostProcessState(e){null!=this.colorGrading&&(Object.assign(this.colorGrading.options,e??{enabled:!1}),this.colorGrading.options.enabled=!0===e?.enabled),null!=this.depthOfField&&(this.depthOfField.options.enabled=null!=e?.depthFocus||null!=e?.depthAperture||null!=e?.depthMaxBlur,this.depthOfField.options.focus=e?.depthFocus,this.depthOfField.options.aperture=e?.depthAperture,this.depthOfField.options.maxBlur=e?.depthMaxBlur)}setProfilingEnabled(e){this.renderer.setRenderGraphProfilingEnabled(e)}resetTemporalHistory(){this.taa?.reset()}inspect(e,t){return inspectWebGpuScene(e,t,this.nodeMaterialResolver)}destroy(){this.nodeMaterialResolver.dispose(),this.gBufferFallbackSource.dispose(),this.gtao?.dispose(),this.smaa?.dispose(),this.volumetricFog?.dispose();for(const e of this.ownedPmremResults)e.dispose();this.ownedPmremResults.clear(),this.renderer.destroy(),this.fallbackMaterial.dispose()}}export function inspectWebGpuScene(e,t,n){const i={meshes:0,skinnedMeshes:0,instancedMeshes:0,batchedMeshes:0,sprites:0,points:0,lines:0,missingPositionGeometry:0,transparentMaterials:0,shaderMaterialsReplaced:0,materialsReplaced:0,unsupportedCamera:ExperimentalWebGpuBackend.supportsCamera(t)?0:1};return e.traverse(e=>{if(e.isSprite)return void i.sprites++;if(e.isPoints)return void i.points++;if(e.isLine)return void i.lines++;if(!e.isMesh)return;const t=e;t.isBatchedMesh?i.batchedMeshes++:t.isInstancedMesh?i.instancedMeshes++:t.isSkinnedMesh?i.skinnedMeshes++:i.meshes++,null==t.geometry?.getAttribute("position")&&i.missingPositionGeometry++;const r=Array.isArray(t.material)?t.material:[t.material];for(const e of r){if(null==e)continue;e.transparent&&i.transparentMaterials++;const t=!0===e.isWGSLShaderMaterial||!0===e.isLineMaterial,r=!t&&!0===n?.canResolve(e);t||r||(i.materialsReplaced++,e.isShaderMaterial&&i.shaderMaterialsReplaced++)}}),i}export function formatWebGpuSceneCapabilitySummary(e){return`[Hology WebGPU experiment] ${JSON.stringify(e)}`}const d="\nstruct VertexInput {\n @location(0) position: vec3<f32>,\n @location(1) normal: vec3<f32>,\n @location(2) uv: vec2<f32>,\n @location(3) color: vec4<f32>,\n#ifdef USE_SKINNING\n @location(4) skinIndex: vec4<u32>,\n @location(5) skinWeight: vec4<f32>,\n#endif\n @builtin(instance_index) instanceIndex: u32,\n};\n\nstruct VertexOutput {\n @builtin(position) position: vec4<f32>,\n @location(0) worldPosition: vec3<f32>,\n @location(1) worldNormal: vec3<f32>,\n @location(2) color: vec4<f32>,\n};\n\n@vertex\nfn vsMain(input: VertexInput) -> VertexOutput {\n var output: VertexOutput;\n#ifdef USE_SKINNING\n let localPosition = rendererSkinPosition(input.position, input.skinIndex, input.skinWeight);\n let localNormal = rendererSkinNormal(input.normal, input.skinIndex, input.skinWeight);\n#else\n let localPosition = input.position;\n let localNormal = input.normal;\n#endif\n let world = rendererWorldPosition(localPosition, input.instanceIndex);\n output.position = rendererClipPosition(localPosition, input.instanceIndex);\n output.worldPosition = world.xyz;\n output.worldNormal = rendererWorldNormal(localNormal, input.instanceIndex);\n output.color = input.color * rendererObjectColor(input.instanceIndex);\n return output;\n}\n",c="\nstruct VertexOutput {\n @builtin(position) position: vec4<f32>,\n @location(0) worldPosition: vec3<f32>,\n @location(1) worldNormal: vec3<f32>,\n @location(2) color: vec4<f32>,\n};\n\nfn distanceAttenuation(distanceToLight: f32, range: f32) -> f32 {\n let linear = max(0.0, 1.0 - distanceToLight / max(range, 0.001));\n return linear * linear;\n}\n\n@fragment\nfn fsMain(input: VertexOutput) -> @location(0) vec4<f32> {\n let normal = normalize(input.worldNormal);\n let normalTint = normal * 0.5 + vec3<f32>(0.5);\n let base = mix(vec3<f32>(0.52, 0.58, 0.64), normalTint, 0.22) * input.color.rgb;\n var lighting = rendererIndirectDiffuse(normal) + vec3<f32>(0.08);\n var directionalLighting = vec3<f32>(0.0);\n let directionalCount = rendererDirectionalLightCount();\n\n for (var index: u32 = 0u; index < directionalCount; index = index + 1u) {\n let lightDirection = rendererDirectionalLightDirectionForLight(index);\n let shadow = rendererSampleDirectionalShadowForLightWithNormal(index, input.worldPosition, normal);\n directionalLighting += rendererDirectionalLightColorForLight(index) * max(dot(normal, lightDirection), 0.0) * shadow;\n }\n if (directionalCount > 0u) {\n lighting += directionalLighting / f32(directionalCount);\n }\n\n for (var index: u32 = 0u; index < rendererPointLightCount(); index = index + 1u) {\n let lightVector = rendererPointLightPositionForLight(index) - input.worldPosition;\n let distanceToLight = length(lightVector);\n let lightDirection = normalize(lightVector);\n let shadow = rendererSamplePointShadowForLightWithNormal(index, input.worldPosition, normal);\n lighting += rendererPointLightColorForLight(index) * max(dot(normal, lightDirection), 0.0) * distanceAttenuation(distanceToLight, rendererPointLightRangeForLight(index)) * shadow;\n }\n\n for (var index: u32 = 0u; index < rendererSpotLightCount(); index = index + 1u) {\n let lightVector = rendererSpotLightPositionForLight(index) - input.worldPosition;\n let distanceToLight = length(lightVector);\n let lightDirection = normalize(lightVector);\n let angleCos = dot(-lightDirection, rendererSpotLightDirectionForLight(index));\n let cone = smoothstep(rendererSpotLightConeCosForLight(index), rendererSpotLightPenumbraCosForLight(index), angleCos);\n let shadow = rendererSampleSpotShadowForLightWithNormal(index, input.worldPosition, normal);\n lighting += rendererSpotLightColorForLight(index) * max(dot(normal, lightDirection), 0.0) * cone * distanceAttenuation(distanceToLight, rendererSpotLightRangeForLight(index)) * shadow;\n }\n\n return vec4<f32>(base * max(lighting, vec3<f32>(0.08)), input.color.a);\n}\n";/*
1
+ import*as e from"three";import{NodeMaterialWebGpuResolver as t}from"./node-material-webgpu-resolver.js";import{float as i,varyingTransformed as n,vec4 as r}from"three-shader-graph";import{NodeShaderMaterial as o}from"../shader-nodes/index.js";import{sceneMapUniformName as s}from"../shader-nodes/scene-sample.js";import{MATERIAL_FLAG_ALPHA_TEST as a,MATERIAL_FLAG_RECEIVE_DECALS as l}from"../shader-nodes/material-flags.js";import{createMotionVectorOutput as d}from"./motion-vector-node.js";export class ExperimentalWebGpuBackend{constructor(e,t,i,n,r,o,s,a,l,d,c,h,u,p,m,g,f,v){this.renderer=e,this.fallbackMaterial=t,this.nodeMaterialResolver=i,this.gBufferFallbackSource=n,this.bloom=r,this.fxaa=o,this.smaa=s,this.gtao=a,this.ssr=l,this.taa=d,this.motionBlur=c,this.volumetricFog=h,this.colorGrading=u,this.depthOfField=p,this.outline=m,this.postProcessEffects=g,this.antiAliasingState=f,this.renderScale=1,this.pmremGenerator=null,this.pmremResults=new WeakMap,this.ownedPmremResults=new Set,this.warnedUnsupportedPostProcessStages=new Set,this.canvas=v}static async create(u={}){const p=await import("@hology/webgpu-renderer"),m=document.createElement("canvas");m.dataset.hologyRenderer="webgpu-experimental";const g=new p.WGSLShaderMaterial({vertexShader:c,fragmentShader:h});g.side=p.DoubleSide;const f=p,v=null!=u.motionBlur&&!1!==u.motionBlur,S=!1!==u.gtao&&(null==u.gtao||!1!==u.gtao.temporal),P=!1!==u.volumetricFog&&(null==u.volumetricFog||!1!==u.volumetricFog.temporal),w=null!=(x=u).antiAliasing?x.antiAliasing:null!=x.taa&&!1!==x.taa?"taa":null!=x.smaa&&!1!==x.smaa?"smaa":null!=x.fxaa&&!1!==x.fxaa?"fxaa":!1===x.taa?"off":"taa";var x;const b=null!=u.antiAliasing,y=v||S||P||(b||"taa"===w),F=new t(f,(e,t)=>"opaque"===t?[e.outputColor.xyz.rgba(i(e.alphaTest>0?a:0).add(!1===e.userData.receiveDecals?0:l)),r((e.outputNormal??n.normal).multiplyScalar(.5).addScalar(.5),e.outputRoughness??i(1)),...y?[d(e)]:[]]:void 0),M=new e.MeshStandardMaterial({color:8688803,roughness:1});if(null==F.resolve(M,"opaque"))throw g.dispose(),M.dispose(),new Error("Failed to compile the WebGPU G-buffer fallback material.");const O=!1===u.gtao?null:new p.GTAOEffect({normalSpace:"view",...p.GTAO_PRESETS.high,...u.gtao??{}}),C=!1===u.ssr||null==u.ssr?null:new p.SSREffect({normalSpace:"view",resolutionScale:.5,...u.ssr??{}}),L=!1===u.bloom?null:new p.BloomEffect({threshold:1,strength:.9,radius:.2,...u.bloom??{}}),E=b||!1!==u.fxaa&&"fxaa"===w?new p.FXAAEffect(!1===u.fxaa?{}:u.fxaa):null,G=b||!1!==u.smaa&&"smaa"===w?new p.SMAAEffect(!1===u.smaa?{}:u.smaa):null,R=b||!1!==u.taa&&"taa"===w?new p.TemporalAAEffect({renderScale:1,...!1===u.taa?{}:u.taa}):null,B={mode:w},D=!1===u.motionBlur||null==u.motionBlur?null:new p.MotionBlurEffect(u.motionBlur),A=u.volumetricFog||void 0,T=!1===u.volumetricFog?null:new p.VolumetricFogEffect(void 0,{...p.VOLUMETRIC_FOG_PRESETS.high,maxDistance:64,spatialJitter:!0,scatteringIntensity:Math.PI,...A,temporal:!1!==A?.temporal&&{...p.VOLUMETRIC_FOG_PRESETS.high.temporal,...A?.temporal},blur:!1!==A?.blur&&{...!1===p.VOLUMETRIC_FOG_PRESETS.high.blur?{}:p.VOLUMETRIC_FOG_PRESETS.high.blur,depthAwareUpsample:!0,...A?.blur}}),V=!1===u.colorGrading?null:new p.ColorGradingEffect({enabled:!1,...u.colorGrading??{}}),W=!1===u.depthOfField?null:new p.DepthOfFieldEffect({enabled:!1,...u.depthOfField??{}}),k=!1===u.outline?null:new p.OutlineEffect({enabled:!1,...u.outline??{}}),I=[],N=new WeakMap,U=new Set,j=(e,t,i)=>{let n=i;const r=I.filter(e=>e.enabled&&e.stage===t).sort((e,t)=>e.priority-t.priority||e.id-t.id);for(const t of r){if(!(t.material instanceof o)){const e=`${t.id}:material`;U.has(e)||(U.add(e),console.warn(`[Hology WebGPU experiment] Skipping custom post-process effect ${t.id}: only NodeShaderMaterial effects are supported.`));continue}try{let i=N.get(t.material);null!=i&&i.version===t.material.version||(i={version:t.material.version,shader:F.compileFullscreen(t.material)},N.set(t.material,i));const r=e.createColorTarget(`hologyCustomPostProcess${t.id}`);e.addNodeFullscreenPass({name:`HologyCustomPostProcess${t.id}`,shader:i.shader,inputOverrides:{[s]:n},outputs:r}),n=r}catch(e){const i=`${t.id}:compile:${t.material.version}`;U.has(i)||(U.add(i),console.warn(`[Hology WebGPU experiment] Skipping custom post-process effect ${t.id}: ${e instanceof Error?e.message:String(e)}`,e))}}return n},H=new p.WebGpuRenderer({canvas:m,fallbackMaterial:g,materialResolver:(e,t)=>F.resolve(e,t),invokeObjectRenderCallbacks:!1,hiZOcclusionCulling:!0,renderGraphProfiling:!1,sampleCount:1,multiDrawIndirect:!1});H.setRenderScale("taa"===w?R?.renderScale??1:1),H.setRenderGraphBuilder((e,t)=>{const i=new p.FrameGraphBuilder(t),n=i.createColorTarget("hologyNormalRoughness",{format:"rgba16float"}),r=y?i.createColorTarget("hologyMotionVectors",{format:"rgba16float"}):null;i.addDirectionalShadowPass(),i.addPointShadowPass(),i.addSpotShadowPass(),i.addDepthPrepass(),i.addHiZPasses(),i.addVisibilityPass(),i.addMaterialPass({name:"OpaqueGBufferPass",outputs:null==r?[i.sceneColor,n]:[i.sceneColor,n,r],drawStage:"opaque",loadDepth:!0}),i.addHiZHistoryPass("OpaqueGBufferPass");let o=i.sceneColor;if(O?.enabled){const e=O.addPasses(i,{depth:i.mainDepth,normal:n,velocity:r??void 0});o=O.addCompositePass(i,o,e)}null!=C&&(o=C.addPasses(i,{sceneColor:o,depth:i.mainDepth,normalRoughness:n})),i.addSceneTextureCopyPass(o),i.addLatePass(o),o=j(i,"beforeFog",o),null!=T&&(o=T.addPasses(i,{sceneColor:o,depth:i.mainDepth,velocity:r??void 0,volumeObjects:t.fogVolumeObjects,lights:t.extractedLights})),null!=D&&null!=r&&(o=D.addPass(i,o,r,i.mainDepth)),null!=L&&(o=L.addPass(i,o)),o=j(i,"beforeOutline",o),null!=k&&(o=k.addPass(i,o)),o=j(i,"beforeDepthOfField",o),null!=W&&(o=W.addPass(i,o,i.mainDepth)),o=j(i,"beforeColorAdjustment",o),null!=V&&(o=V.addPass(i,o)),o=j(i,"beforeAntiAliasing",o);let s="taa"===B.mode&&null!=R?R.addPass(i,o,i.mainDepth,r??void 0):o;"smaa"===B.mode&&null!=G?s=G.addPass(i,s):"fxaa"===B.mode&&null!=E&&(s=E.addPass(i,s)),s=j(i,"beforeOutput",s),i.present(s)});try{return await H.initialize(),new ExperimentalWebGpuBackend(H,g,F,M,L,E,G,O,C,R,D,T,V,W,k,I,B,m)}catch(e){throw H.destroy(),g.dispose(),F.dispose(),M.dispose(),O?.dispose(),G?.dispose(),D?.dispose(),T?.dispose(),k?.dispose(),e}}static supportsCamera(t){return t instanceof e.PerspectiveCamera||t instanceof e.OrthographicCamera}setSize(e,t,i,n=1){const r=this.canvas.width,o=this.canvas.height,s=Math.min(1,Math.max(.25,n)),a=s!==this.renderScale;this.renderScale=s,this.renderer.setRenderScale(s),this.taa?.setRenderScale(s),this.renderer.setSize(e,t,i),(a||this.canvas.width!==r||this.canvas.height!==o)&&this.resetViewHistory()}initTexture(e){this.renderer.initTexture(e)}setPostProcessEffects(e){this.postProcessEffects.splice(0,this.postProcessEffects.length,...e);for(const t of e){if("beforeLut"!==t.stage)continue;const e=`${t.id}:${t.stage}`;this.warnedUnsupportedPostProcessStages.has(e)||(this.warnedUnsupportedPostProcessStages.add(e),console.warn(`[Hology WebGPU experiment] Skipping custom post-process effect ${t.id}: stage "${t.stage}" has no native WebGPU anchor.`))}}setEnableOutlines(e){null!=this.outline&&(this.outline.enabled=e)}setDynamicBatchingEnabled(e){this.renderer.setDynamicBatchingEnabled(e)}setSelectedObjects(e){this.outline?.setSelectedObjects(e)}setSelectedObjectInstances(e){this.outline?.setSelectedInstances(e)}setCascadedShadowRanges(e,t=[]){this.renderer.setCascadedShadowRanges(e,t),this.volumetricFog?.setCascadedDirectionalLights(t)}setWorldEnvironment(t){if(null==t)return void this.nodeMaterialResolver.setWorldEnvironment(null);let i=t.source;if(i.mapping!==e.CubeUVReflectionMapping){let e=this.pmremResults.get(i);null==e&&(this.pmremGenerator??(this.pmremGenerator=this.renderer.createPMREMGenerator()),e=this.pmremGenerator.fromEquirectangular(i),this.pmremResults.set(i,e),this.ownedPmremResults.add(e)),i=e.texture}this.nodeMaterialResolver.setWorldEnvironment({texture:i,intensity:t.intensity})}resetViewHistory(){this.renderer.resetViewHistory(),this.taa?.reset(),this.motionBlur?.reset(),this.gtao?.reset(),this.volumetricFog?.reset()}async compile(e,t){await this.renderer.compileAsync(e,t)}createStaticDrawSet(e,t){this.renderer.setHiZOcclusionCullingEnabled(!0);const i=this.renderer.createStaticDrawSet(e,{label:t,onUnsupported:"skip"});return 0===i.drawCount?(i.dispose(),null):i}render(e,t,i){const n="taa"===this.antiAliasingState.mode?this.taa:null;this.volumetricFog?.setScene(e),this.renderer.setMotionVectorProjection(t.projectionMatrix),this.motionBlur?.prepareCamera(t,this.canvas.width,this.canvas.height,i),this.motionBlur?.requiresRendererHistoryReset()&&(this.renderer.resetViewHistory(),this.gtao?.reset(),this.volumetricFog?.reset()),n?.prepareCamera(t,this.canvas.width,this.canvas.height),this.motionBlur?.setJitterUv(n?.currentJitterUv.x??0,n?.currentJitterUv.y??0);let r=!1;try{this.renderer.render(e,t,i),r=!0}finally{n?.finishCamera(t),r&&this.motionBlur?.commitCamera()}}setMotionBlurOptions(e){null!=this.motionBlur&&Object.assign(this.motionBlur.options,e)}setGTAOOptions(e){this.gtao?.setOptions(e)}setAntiAliasingMode(e){e!==this.antiAliasingState.mode&&("taa"===e&&null==this.taa||"smaa"===e&&null==this.smaa||"fxaa"===e&&null==this.fxaa?console.warn(`[Hology WebGPU experiment] Anti-aliasing mode "${e}" was not initialized. Set webgpuPostProcessing.antiAliasing in the initial rendering configuration to enable runtime switching.`):(this.antiAliasingState.mode=e,this.resetTemporalHistory()))}setGTAOQuality(e){this.gtao?.applyPreset(e)}setVolumetricFogOptions(e){this.volumetricFog?.setOptions(e)}setVolumetricFogQuality(e){this.volumetricFog?.applyPreset(e)}pushMotionBlurOverride(e){return this.motionBlur?.pushOverride(e)??null}invalidateObjectMotion(e){this.renderer.invalidateObjectMotion(e)}getLastFrameProfile(){return this.renderer.getLastFrameProfile()}setPostProcessState(e){null!=this.colorGrading&&(Object.assign(this.colorGrading.options,e??{enabled:!1}),this.colorGrading.options.enabled=!0===e?.enabled),null!=this.depthOfField&&(this.depthOfField.options.enabled=null!=e?.depthFocus||null!=e?.depthAperture||null!=e?.depthMaxBlur,this.depthOfField.options.focus=e?.depthFocus,this.depthOfField.options.aperture=e?.depthAperture,this.depthOfField.options.maxBlur=e?.depthMaxBlur)}setProfilingEnabled(e){this.renderer.setRenderGraphProfilingEnabled(e)}resetTemporalHistory(){this.taa?.reset(),this.motionBlur?.reset(),this.gtao?.reset(),this.volumetricFog?.reset(),this.renderer.resetViewHistory()}inspect(e,t){return inspectWebGpuScene(e,t,this.nodeMaterialResolver)}destroy(){this.nodeMaterialResolver.dispose(),this.gBufferFallbackSource.dispose(),this.gtao?.dispose(),this.smaa?.dispose(),this.motionBlur?.dispose(),this.volumetricFog?.dispose();for(const e of this.ownedPmremResults)e.dispose();this.ownedPmremResults.clear(),this.renderer.destroy(),this.fallbackMaterial.dispose()}}export function inspectWebGpuScene(e,t,i){const n={meshes:0,skinnedMeshes:0,instancedMeshes:0,batchedMeshes:0,sprites:0,points:0,lines:0,missingPositionGeometry:0,transparentMaterials:0,shaderMaterialsReplaced:0,materialsReplaced:0,unsupportedCamera:ExperimentalWebGpuBackend.supportsCamera(t)?0:1};return e.traverse(e=>{if(e.isSprite)return void n.sprites++;if(e.isPoints)return void n.points++;if(e.isLine)return void n.lines++;if(!e.isMesh)return;const t=e;t.isBatchedMesh?n.batchedMeshes++:t.isInstancedMesh?n.instancedMeshes++:t.isSkinnedMesh?n.skinnedMeshes++:n.meshes++,null==t.geometry?.getAttribute("position")&&n.missingPositionGeometry++;const r=Array.isArray(t.material)?t.material:[t.material];for(const e of r){if(null==e)continue;e.transparent&&n.transparentMaterials++;const t=!0===e.isWGSLShaderMaterial||!0===e.isLineMaterial,r=!t&&!0===i?.canResolve(e);t||r||(n.materialsReplaced++,e.isShaderMaterial&&n.shaderMaterialsReplaced++)}}),n}export function formatWebGpuSceneCapabilitySummary(e){return`[Hology WebGPU experiment] ${JSON.stringify(e)}`}const c="\nstruct VertexInput {\n @location(0) position: vec3<f32>,\n @location(1) normal: vec3<f32>,\n @location(2) uv: vec2<f32>,\n @location(3) color: vec4<f32>,\n#ifdef USE_SKINNING\n @location(4) skinIndex: vec4<u32>,\n @location(5) skinWeight: vec4<f32>,\n#endif\n @builtin(instance_index) instanceIndex: u32,\n};\n\nstruct VertexOutput {\n @builtin(position) position: vec4<f32>,\n @location(0) worldPosition: vec3<f32>,\n @location(1) worldNormal: vec3<f32>,\n @location(2) color: vec4<f32>,\n};\n\n@vertex\nfn vsMain(input: VertexInput) -> VertexOutput {\n var output: VertexOutput;\n#ifdef USE_SKINNING\n let localPosition = rendererSkinPosition(input.position, input.skinIndex, input.skinWeight);\n let localNormal = rendererSkinNormal(input.normal, input.skinIndex, input.skinWeight);\n#else\n let localPosition = input.position;\n let localNormal = input.normal;\n#endif\n let world = rendererWorldPosition(localPosition, input.instanceIndex);\n output.position = rendererClipPosition(localPosition, input.instanceIndex);\n output.worldPosition = world.xyz;\n output.worldNormal = rendererWorldNormal(localNormal, input.instanceIndex);\n output.color = input.color * rendererObjectColor(input.instanceIndex);\n return output;\n}\n",h="\nstruct VertexOutput {\n @builtin(position) position: vec4<f32>,\n @location(0) worldPosition: vec3<f32>,\n @location(1) worldNormal: vec3<f32>,\n @location(2) color: vec4<f32>,\n};\n\nfn distanceAttenuation(distanceToLight: f32, range: f32) -> f32 {\n let linear = max(0.0, 1.0 - distanceToLight / max(range, 0.001));\n return linear * linear;\n}\n\n@fragment\nfn fsMain(input: VertexOutput) -> @location(0) vec4<f32> {\n let normal = normalize(input.worldNormal);\n let normalTint = normal * 0.5 + vec3<f32>(0.5);\n let base = mix(vec3<f32>(0.52, 0.58, 0.64), normalTint, 0.22) * input.color.rgb;\n var lighting = rendererIndirectDiffuse(normal) + vec3<f32>(0.08);\n var directionalLighting = vec3<f32>(0.0);\n let directionalCount = rendererDirectionalLightCount();\n\n for (var index: u32 = 0u; index < directionalCount; index = index + 1u) {\n let lightDirection = rendererDirectionalLightDirectionForLight(index);\n let shadow = rendererSampleDirectionalShadowForLightWithNormal(index, input.worldPosition, normal);\n directionalLighting += rendererDirectionalLightColorForLight(index) * max(dot(normal, lightDirection), 0.0) * shadow;\n }\n if (directionalCount > 0u) {\n lighting += directionalLighting / f32(directionalCount);\n }\n\n for (var index: u32 = 0u; index < rendererPointLightCount(); index = index + 1u) {\n let lightVector = rendererPointLightPositionForLight(index) - input.worldPosition;\n let distanceToLight = length(lightVector);\n let lightDirection = normalize(lightVector);\n let shadow = rendererSamplePointShadowForLightWithNormal(index, input.worldPosition, normal);\n lighting += rendererPointLightColorForLight(index) * max(dot(normal, lightDirection), 0.0) * distanceAttenuation(distanceToLight, rendererPointLightRangeForLight(index)) * shadow;\n }\n\n for (var index: u32 = 0u; index < rendererSpotLightCount(); index = index + 1u) {\n let lightVector = rendererSpotLightPositionForLight(index) - input.worldPosition;\n let distanceToLight = length(lightVector);\n let lightDirection = normalize(lightVector);\n let angleCos = dot(-lightDirection, rendererSpotLightDirectionForLight(index));\n let cone = smoothstep(rendererSpotLightConeCosForLight(index), rendererSpotLightPenumbraCosForLight(index), angleCos);\n let shadow = rendererSampleSpotShadowForLightWithNormal(index, input.worldPosition, normal);\n lighting += rendererSpotLightColorForLight(index) * max(dot(normal, lightDirection), 0.0) * cone * distanceAttenuation(distanceToLight, rendererSpotLightRangeForLight(index)) * shadow;\n }\n\n return vec4<f32>(base * max(lighting, vec3<f32>(0.08)), input.color.a);\n}\n";/*
2
2
  * Copyright (©) 2026 Hology Interactive AB. All rights reserved.
3
3
  * See the LICENSE.md file for details.
4
4
  */
@@ -11,11 +11,13 @@ import { Cluster } from "./rendering/cluster-generator.js";
11
11
  import { LightVolume } from "./rendering/light-probes/light-volume-capture.js";
12
12
  import { type PostProcessMaterialSource } from "./shader/post-process-shader.js";
13
13
  import { type UpscalingOptions } from "./rendering/upscaling-pass.js";
14
- import { type ExperimentalWebGpuPostProcessingOptions } from './rendering/webgpu-experimental-backend.js';
14
+ import { type AntiAliasingMode, type ExperimentalWebGpuPostProcessingOptions, type GTAOOptions, type GTAOQuality, type MotionBlurOptions, type MotionBlurOverride, type MotionBlurOverrideHandle, type VolumetricFogOptions, type VolumetricFogQuality } from './rendering/webgpu-experimental-backend.js';
15
15
  import type { WorldEnvironmentState } from './rendering/environment.js';
16
16
  export type { UpscalingOptions } from "./rendering/upscaling-pass.js";
17
- export type { ExperimentalWebGpuPostProcessingOptions } from './rendering/webgpu-experimental-backend.js';
17
+ export type { AntiAliasingMode, ExperimentalWebGpuPostProcessingOptions } from './rendering/webgpu-experimental-backend.js';
18
+ export type { GTAOOptions, GTAOQuality, MotionBlurOptions, MotionBlurOverride, MotionBlurOverrideHandle, VolumetricFogOptions, VolumetricFogQuality } from './rendering/webgpu-experimental-backend.js';
18
19
  export type RenderingBackend = 'webgl' | 'webgpu-experimental';
20
+ export type MotionBlurObjectMode = 'disabled' | 'cameraOnly' | 'objectAndCamera' | 'forceObjectBlur';
19
21
  export type DynamicResolutionOptions = {
20
22
  enabled?: boolean;
21
23
  /**
@@ -240,6 +242,16 @@ export declare class RenderingView {
240
242
  setCamera(camera: Camera): void;
241
243
  setSelectedObjects(obj: Object3D[]): void;
242
244
  setExperimentalWebGpuDynamicBatchingEnabled(enabled: boolean): void;
245
+ setMotionBlurOptions(options: MotionBlurOptions): void;
246
+ setGTAOOptions(options: GTAOOptions): void;
247
+ setAntiAliasingMode(mode: AntiAliasingMode): void;
248
+ setGTAOQuality(quality: GTAOQuality): void;
249
+ setVolumetricFogOptions(options: VolumetricFogOptions): void;
250
+ setVolumetricFogQuality(quality: VolumetricFogQuality): void;
251
+ pushMotionBlurOverride(override: MotionBlurOverride): MotionBlurOverrideHandle | null;
252
+ setObjectMotionBlur(object: Object3D, mode: MotionBlurObjectMode, scale?: number): void;
253
+ /** Call after a teleport or discontinuous animation reset. */
254
+ invalidateObjectMotion(object: Object3D): void;
243
255
  /**
244
256
  * Select individual InstancedMesh instances or BatchedMesh geometry ids for
245
257
  * WebGPU outlines. The legacy WebGL OutlinePass still selects whole objects.