three-environment-shadows 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,3 +1,7 @@
1
+ <a href="https://github.com/mjurczyk/three-environment-shadows" target="_blank">
2
+ <img width="100%" src="https://github.com/user-attachments/assets/0e4dec8a-fcde-4fbb-9d53-1f7ffd9d6d5c" title="Three Environment Shadows" />
3
+ </a>
4
+
1
5
  # three-environment-shadows
2
6
 
3
7
  Use environment as a source of light and _shadows_ in your Three.js scene.
package/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { E as EnvironmentShadowsBase } from './shared.js';
2
2
  import { BufferGeometry, Float32BufferAttribute, Mesh, OrthographicCamera, FloatType, HalfFloatType, MeshLambertMaterial, DoubleSide, BackSide, CustomBlending, AddEquation, OneFactor, ShaderChunk, Vector3, ShaderMaterial } from 'three';
3
3
 
4
- const FACE_LINE="float faceDirection = gl_FrontFacing ? 1.0 : - 1.0;",SHADOW_COORD="vDirectionalShadowCoord[ i ]",JITTER_PARS="\n uniform float bakeNoise; // in shadow map texels\n uniform float bakeSeed; // changes every sample\n uniform float bakeMapTexel; // 1 / shadow map size\n\n float bakeHash( vec2 p ) {\n vec3 p3 = fract( vec3( p.xyx ) * 0.1031 );\n p3 += dot( p3, p3.yzx + 33.33 );\n return fract( ( p3.x + p3.y ) * p3.z );\n }\n\n vec4 bakeShadowJitter() {\n vec2 seed = gl_FragCoord.xy + vec2( bakeSeed * 17.31, bakeSeed * 5.79 );\n vec2 offset = vec2( bakeHash( seed ), bakeHash( seed.yx + 11.7 ) ) * 2.0 - 1.0;\n return vec4( offset * bakeNoise * bakeMapTexel, 0.0, 0.0 );\n }\n",FINALIZE_FRAGMENT="\n precision highp float;\n\n uniform sampler2D uSum;\n uniform ivec2 uSize;\n uniform float uScale;\n\n #define DILATE 4\n\n void main() {\n ivec2 pixel = ivec2( gl_FragCoord.xy );\n vec4 sum = texelFetch( uSum, pixel, 0 );\n\n if ( sum.a > 0.0 ) {\n gl_FragColor = vec4( sum.rgb * uScale / sum.a, 1.0 );\n return;\n }\n\n for ( int r = 1; r <= DILATE; r ++ ) {\n for ( int y = - r; y <= r; y ++ ) {\n for ( int x = - r; x <= r; x ++ ) {\n if ( abs( x ) != r && abs( y ) != r ) continue;\n\n vec4 s = texelFetch( uSum, clamp( pixel + ivec2( x, y ), ivec2( 0 ), uSize - 1 ), 0 );\n\n if ( s.a > 0.0 ) {\n gl_FragColor = vec4( s.rgb * uScale / s.a, 1.0 );\n return;\n }\n }\n }\n }\n\n gl_FragColor = vec4( 0.0 );\n }\n",DISPLAY_VERTEX_PARS="\n attribute vec2 lightmapUvA;\n attribute vec2 lightmapUvB;\n varying vec2 vLmA;\n varying vec2 vLmB;\n varying vec2 vLmHas;\n",DISPLAY_VERTEX="\n vLmA = lightmapUvA;\n vLmB = lightmapUvB;\n vLmHas = vec2( step( 1e-6, lightmapUvA.x ), step( 1e-6, lightmapUvB.x ) );\n",DISPLAY_FRAGMENT_PARS="\n varying vec2 vLmA;\n varying vec2 vLmB;\n varying vec2 vLmHas;\n uniform sampler2D lmA;\n uniform sampler2D lmB;\n uniform float lmFrontIsA;\n uniform float lmBlend;\n uniform float lmValid;\n uniform float lmSpecularOcclusion;\n",DISPLAY_FRAGMENT="\n {\n vec3 lmUnoccluded = iblIrradiance;\n vec3 lmValueA = vLmHas.x > 0.5 ? texture2D( lmA, vLmA ).rgb : lmUnoccluded;\n vec3 lmValueB = vLmHas.y > 0.5 ? texture2D( lmB, vLmB ).rgb : lmUnoccluded;\n vec3 lmFront = lmFrontIsA > 0.5 ? lmValueA : lmValueB;\n vec3 lmPrevious = lmFrontIsA > 0.5 ? lmValueB : lmValueA;\n vec3 lmIrradiance = lmValid > 0.5 ? mix( lmPrevious, lmFront, lmBlend ) : lmUnoccluded;\n\n vec3 lmWeights = vec3( 0.2126, 0.7152, 0.0722 );\n float lmVisibility = clamp( dot( lmIrradiance, lmWeights ) / max( 1e-6, dot( lmUnoccluded, lmWeights ) ), 0.0, 1.0 );\n\n iblIrradiance = lmIrradiance;\n radiance *= mix( 1.0, lmVisibility, lmSpecularOcclusion );\n }\n",quadGeometry=new BufferGeometry;quadGeometry.setAttribute("position",new Float32BufferAttribute([-1,3,0,-1,-1,0,3,-1,0],3));const quad=new Mesh(quadGeometry);quad.frustumCulled=!1;const quadCamera=new OrthographicCamera;const glslBackend={getSumType:e=>null!==e.getContext().getExtension("EXT_float_blend")?FloatType:HalfFloatType,createBakeMaterial(){const e={bakeNoise:{value:0},bakeSeed:{value:0},bakeMapTexel:{value:1/512}},n=new MeshLambertMaterial({color:16777215,side:DoubleSide,shadowSide:BackSide,forceSinglePass:!0,blending:CustomBlending,blendEquation:AddEquation,blendSrc:OneFactor,blendDst:OneFactor,blendSrcAlpha:OneFactor,blendDstAlpha:OneFactor,depthTest:!1,depthWrite:!1});return n.onBeforeCompile=n=>{const a=ShaderChunk.normal_fragment_begin,r=ShaderChunk.lights_fragment_begin;if(!a.includes(FACE_LINE)||!r.includes(SHADOW_COORD))throw new Error("EnvironmentShadows: Unsupported three.js shader chunks.");Object.assign(n.uniforms,e),n.vertexShader=n.vertexShader.replace("void main() {","attribute vec2 lightmapUvBake;\nvoid main() {").replace("#include <fog_vertex>","#include <fog_vertex>\ngl_Position = vec4( lightmapUvBake * 2.0 - 1.0, 0.0, 1.0 );"),n.fragmentShader=n.fragmentShader.replace("void main() {",`${JITTER_PARS}\nvoid main() {`).replace("#include <normal_fragment_begin>",a.replace(FACE_LINE,"float faceDirection = 1.0;")).replace("#include <lights_fragment_begin>",r.split(SHADOW_COORD).join(`( ${SHADOW_COORD} + bakeShadowJitter() )`));},n.customProgramCacheKey=()=>"environment-shadows-bake",{material:n,uniforms:{noise:e.bakeNoise,seed:e.bakeSeed,mapTexel:e.bakeMapTexel,shadowExtent:{value:1},lightDirection:{value:new Vector3}}}},createFinalize(e,n){const a=new ShaderMaterial({vertexShader:"void main() { gl_Position = vec4( position.xy, 0.0, 1.0 ); }",fragmentShader:FINALIZE_FRAGMENT,uniforms:{uSum:{value:e},uSize:{value:{x:n,y:n}},uScale:{value:1}},depthTest:!1,depthWrite:!1});return {material:a,uniforms:{scale:a.uniforms.uScale},dispose:()=>a.dispose()}},renderQuad(e,n){quad.material=n,e.render(quad,quadCamera);},createDisplay(){const e={lmA:{value:null},lmB:{value:null},lmFrontIsA:{value:1},lmBlend:{value:1},lmValid:{value:0},lmSpecularOcclusion:{value:1}},n=new WeakMap;return {uniforms:{a:e.lmA,b:e.lmB,frontIsA:e.lmFrontIsA,blend:e.lmBlend,valid:e.lmValid,specularOcclusion:e.lmSpecularOcclusion},refresh(){},patch(a){if(n.has(a))return;const r=a.onBeforeCompile,l=a.customProgramCacheKey;n.set(a,{onBeforeCompile:r,customProgramCacheKey:l}),a.onBeforeCompile=function(n,a){r.call(this,n,a),n.fragmentShader.includes("#include <lights_fragment_maps>")&&(Object.assign(n.uniforms,e),n.vertexShader=n.vertexShader.replace("void main() {",`${DISPLAY_VERTEX_PARS}\nvoid main() {`).replace("#include <fog_vertex>",`#include <fog_vertex>\n${DISPLAY_VERTEX}`),n.fragmentShader=n.fragmentShader.replace("void main() {",`${DISPLAY_FRAGMENT_PARS}\nvoid main() {`).replace("#include <lights_fragment_maps>",`#include <lights_fragment_maps>\n${DISPLAY_FRAGMENT}`));},a.customProgramCacheKey=function(){return `${l.call(this)}|environment-shadows`},a.dispose(),a.needsUpdate=!0;},unpatch(e){const a=n.get(e);a&&(e.onBeforeCompile=a.onBeforeCompile,e.customProgramCacheKey=a.customProgramCacheKey,n.delete(e),e.dispose(),e.needsUpdate=!0);}}}};
4
+ const FACE_LINE="float faceDirection = gl_FrontFacing ? 1.0 : - 1.0;",SHADOW_COORD="vDirectionalShadowCoord[ i ]",JITTER_PARS="\n uniform float bakeNoise; // in shadow map texels\n uniform float bakeSeed; // changes every sample\n uniform float bakeMapTexel; // 1 / shadow map size\n\n float bakeHash( vec2 p ) {\n vec3 p3 = fract( vec3( p.xyx ) * 0.1031 );\n p3 += dot( p3, p3.yzx + 33.33 );\n return fract( ( p3.x + p3.y ) * p3.z );\n }\n\n vec4 bakeShadowJitter() {\n vec2 seed = gl_FragCoord.xy + vec2( bakeSeed * 17.31, bakeSeed * 5.79 );\n vec2 offset = vec2( bakeHash( seed ), bakeHash( seed.yx + 11.7 ) ) * 2.0 - 1.0;\n return vec4( offset * bakeNoise * bakeMapTexel, 0.0, 0.0 );\n }\n",FINALIZE_FRAGMENT="\n precision highp float;\n\n uniform sampler2D uSum;\n uniform ivec2 uSize;\n uniform float uScale;\n\n #define DILATE 4\n\n void main() {\n ivec2 pixel = ivec2( gl_FragCoord.xy );\n vec4 sum = texelFetch( uSum, pixel, 0 );\n\n if ( sum.a > 0.0 ) {\n gl_FragColor = vec4( sum.rgb * uScale / sum.a, 1.0 );\n return;\n }\n\n for ( int r = 1; r <= DILATE; r ++ ) {\n for ( int y = - r; y <= r; y ++ ) {\n for ( int x = - r; x <= r; x ++ ) {\n if ( abs( x ) != r && abs( y ) != r ) continue;\n\n vec4 s = texelFetch( uSum, clamp( pixel + ivec2( x, y ), ivec2( 0 ), uSize - 1 ), 0 );\n\n if ( s.a > 0.0 ) {\n gl_FragColor = vec4( s.rgb * uScale / s.a, 1.0 );\n return;\n }\n }\n }\n }\n\n gl_FragColor = vec4( 0.0 );\n }\n",DISPLAY_VERTEX_PARS="\n attribute vec2 lightmapUvA;\n attribute vec2 lightmapUvB;\n varying vec2 vLmA;\n varying vec2 vLmB;\n varying vec2 vLmHas;\n",DISPLAY_VERTEX="\n vLmA = lightmapUvA;\n vLmB = lightmapUvB;\n vLmHas = vec2( step( 1e-6, lightmapUvA.x ), step( 1e-6, lightmapUvB.x ) );\n",DISPLAY_FRAGMENT_PARS="\n varying vec2 vLmA;\n varying vec2 vLmB;\n varying vec2 vLmHas;\n uniform sampler2D lmA;\n uniform sampler2D lmB;\n uniform float lmFrontIsA;\n uniform float lmBlend;\n uniform float lmValid;\n uniform float lmSpecularOcclusion;\n",DISPLAY_FRAGMENT="\n {\n vec3 lmUnoccluded = iblIrradiance;\n\n // NOTE The lightmap is baked at intensity 1. envMapIntensity is material.envMapIntensity or\n // scene.environmentIntensity, the same as for the environment diffuse. The unoccluded light has it already.\n #ifdef USE_ENVMAP\n float lmIntensity = envMapIntensity;\n #else\n float lmIntensity = 1.0;\n #endif\n\n vec3 lmValueA = vLmHas.x > 0.5 ? texture2D( lmA, vLmA ).rgb * lmIntensity : lmUnoccluded;\n vec3 lmValueB = vLmHas.y > 0.5 ? texture2D( lmB, vLmB ).rgb * lmIntensity : lmUnoccluded;\n vec3 lmFront = lmFrontIsA > 0.5 ? lmValueA : lmValueB;\n vec3 lmPrevious = lmFrontIsA > 0.5 ? lmValueB : lmValueA;\n vec3 lmIrradiance = lmValid > 0.5 ? mix( lmPrevious, lmFront, lmBlend ) : lmUnoccluded;\n\n vec3 lmWeights = vec3( 0.2126, 0.7152, 0.0722 );\n float lmVisibility = clamp( dot( lmIrradiance, lmWeights ) / max( 1e-6, dot( lmUnoccluded, lmWeights ) ), 0.0, 1.0 );\n\n iblIrradiance = lmIrradiance;\n radiance *= mix( 1.0, lmVisibility, lmSpecularOcclusion );\n }\n",quadGeometry=new BufferGeometry;quadGeometry.setAttribute("position",new Float32BufferAttribute([-1,3,0,-1,-1,0,3,-1,0],3));const quad=new Mesh(quadGeometry);quad.frustumCulled=!1;const quadCamera=new OrthographicCamera;const glslBackend={getSumType:e=>null!==e.getContext().getExtension("EXT_float_blend")?FloatType:HalfFloatType,createBakeMaterial(){const e={bakeNoise:{value:0},bakeSeed:{value:0},bakeMapTexel:{value:1/512}},n=new MeshLambertMaterial({color:16777215,side:DoubleSide,shadowSide:BackSide,forceSinglePass:!0,blending:CustomBlending,blendEquation:AddEquation,blendSrc:OneFactor,blendDst:OneFactor,blendSrcAlpha:OneFactor,blendDstAlpha:OneFactor,depthTest:!1,depthWrite:!1});return n.onBeforeCompile=n=>{const a=ShaderChunk.normal_fragment_begin,r=ShaderChunk.lights_fragment_begin;if(!a.includes(FACE_LINE)||!r.includes(SHADOW_COORD))throw new Error("EnvironmentShadows: Unsupported three.js shader chunks.");Object.assign(n.uniforms,e),n.vertexShader=n.vertexShader.replace("void main() {","attribute vec2 lightmapUvBake;\nvoid main() {").replace("#include <fog_vertex>","#include <fog_vertex>\ngl_Position = vec4( lightmapUvBake * 2.0 - 1.0, 0.0, 1.0 );"),n.fragmentShader=n.fragmentShader.replace("void main() {",`${JITTER_PARS}\nvoid main() {`).replace("#include <normal_fragment_begin>",a.replace(FACE_LINE,"float faceDirection = 1.0;")).replace("#include <lights_fragment_begin>",r.split(SHADOW_COORD).join(`( ${SHADOW_COORD} + bakeShadowJitter() )`));},n.customProgramCacheKey=()=>"environment-shadows-bake",{material:n,uniforms:{noise:e.bakeNoise,seed:e.bakeSeed,mapTexel:e.bakeMapTexel,shadowExtent:{value:1},lightDirection:{value:new Vector3}}}},createFinalize(e,n){const a=new ShaderMaterial({vertexShader:"void main() { gl_Position = vec4( position.xy, 0.0, 1.0 ); }",fragmentShader:FINALIZE_FRAGMENT,uniforms:{uSum:{value:e},uSize:{value:{x:n,y:n}},uScale:{value:1}},depthTest:!1,depthWrite:!1});return {material:a,uniforms:{scale:a.uniforms.uScale},dispose:()=>a.dispose()}},renderQuad(e,n){quad.material=n,e.render(quad,quadCamera);},createDisplay(){const e={lmA:{value:null},lmB:{value:null},lmFrontIsA:{value:1},lmBlend:{value:1},lmValid:{value:0},lmSpecularOcclusion:{value:1}},n=new WeakMap;return {uniforms:{a:e.lmA,b:e.lmB,frontIsA:e.lmFrontIsA,blend:e.lmBlend,valid:e.lmValid,specularOcclusion:e.lmSpecularOcclusion},refresh(){},patch(a){if(n.has(a))return;const r=a.onBeforeCompile,l=a.customProgramCacheKey;n.set(a,{onBeforeCompile:r,customProgramCacheKey:l}),a.onBeforeCompile=function(n,a){r.call(this,n,a),n.fragmentShader.includes("#include <lights_fragment_maps>")&&(Object.assign(n.uniforms,e),n.vertexShader=n.vertexShader.replace("void main() {",`${DISPLAY_VERTEX_PARS}\nvoid main() {`).replace("#include <fog_vertex>",`#include <fog_vertex>\n${DISPLAY_VERTEX}`),n.fragmentShader=n.fragmentShader.replace("void main() {",`${DISPLAY_FRAGMENT_PARS}\nvoid main() {`).replace("#include <lights_fragment_maps>",`#include <lights_fragment_maps>\n${DISPLAY_FRAGMENT}`));},a.customProgramCacheKey=function(){return `${l.call(this)}|environment-shadows`},a.dispose(),a.needsUpdate=!0;},unpatch(e){const a=n.get(e);a&&(e.onBeforeCompile=a.onBeforeCompile,e.customProgramCacheKey=a.customProgramCacheKey,n.delete(e),e.dispose(),e.needsUpdate=!0);}}}};
5
5
 
6
6
  class EnvironmentShadows extends EnvironmentShadowsBase{constructor(s,n,o={}){super(s,n,o,glslBackend);}}
7
7
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "three-environment-shadows",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "description": "Soft, colored shadows from HDR / EXR / UltraHDR environment maps in Three.js (WebGL and WebGPU).",
5
5
  "type": "module",
6
6
  "main": "./index.js",
package/webgpu.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import { E as EnvironmentShadowsBase } from './shared.js';
2
2
  import { FloatType, HalfFloatType, Vector3, DoubleSide, CustomBlending, AddEquation, OneFactor, BackSide, Texture } from 'three';
3
3
  import { QuadMesh, MeshLambertNodeMaterial, NodeMaterial, LightingNode } from 'three/webgpu';
4
- import { uniform, attribute, vec4, float, cameraViewMatrix, modelNormalMatrix, normalLocal, select, vec3, cross, screenCoordinate, hash, positionWorld, Fn, ivec2, textureLoad, If, texture, renderGroup, mix, clamp, dot, max } from 'three/tsl';
4
+ import { uniform, attribute, vec4, float, cameraViewMatrix, modelNormalMatrix, normalLocal, select, vec3, cross, screenCoordinate, hash, positionWorld, Fn, ivec2, textureLoad, If, texture, renderGroup, reference, mix, clamp, dot, max } from 'three/tsl';
5
5
 
6
- class EnvironmentShadowsLightingNode extends LightingNode{static get type(){return "EnvironmentShadowsLightingNode"}constructor(e){super(),this.display=e;}setup(e){const{iblIrradiance:t,radiance:a}=e.context,r=this.display,n=vec3(t).toVar(),o=select(r.uvA.x.greaterThan(1e-6),r.sampleA.rgb,n),i=select(r.uvB.x.greaterThan(1e-6),r.sampleB.rgb,n),s=r.frontIsA.greaterThan(.5),d=select(s,o,i),l=select(s,i,o),u=select(r.valid.greaterThan(.5),mix(l,d,r.blend),n).toVar(),c=vec3(.2126,.7152,.0722),m=clamp(dot(u,c).div(max(dot(n,c),1e-6)),0,1);t.assign(u),a.mulAssign(mix(float(1),m,r.specularOcclusion));}}const quad=new QuadMesh,OFFSETS=[];for(let e=-4;e<=4;e++)for(let t=-4;t<=4;t++)0===t&&0===e||OFFSETS.push([t,e]);OFFSETS.sort((e,t)=>e[0]*e[0]+e[1]*e[1]-(t[0]*t[0]+t[1]*t[1]));const tslBackend={getSumType(e){const t=e.backend;return (t.isWebGLBackend?null!==t.gl.getExtension("EXT_float_blend"):e.hasFeature("float32-blendable"))?FloatType:HalfFloatType},createBakeMaterial(){const e={noise:uniform(0),seed:uniform(0),mapTexel:uniform(1/512),shadowExtent:uniform(1),lightDirection:uniform(new Vector3(0,1,0))},t=new MeshLambertNodeMaterial({color:16777215,side:DoubleSide,forceSinglePass:!0,blending:CustomBlending,blendEquation:AddEquation,blendSrc:OneFactor,blendDst:OneFactor,blendSrcAlpha:OneFactor,blendDstAlpha:OneFactor,depthTest:!1,depthWrite:!1});t.shadowSide=BackSide;const a=attribute("lightmapUvBake","vec2");if(t.vertexNode=vec4(a.x.mul(2).sub(1),float(1).sub(a.y.mul(2)),0,1),t.normalNode=cameraViewMatrix.mul(vec4(modelNormalMatrix.mul(normalLocal),0)).xyz.normalize(),"receivedShadowPositionNode"in t){const a=e.lightDirection.normalize(),r=select(a.y.abs().lessThan(.99),vec3(0,1,0),vec3(1,0,0)),n=cross(r,a).normalize(),o=cross(a,n),i=screenCoordinate.x.add(screenCoordinate.y.mul(4099)).add(e.seed.mul(7919)),s=n.mul(hash(i).mul(2).sub(1)).add(o.mul(hash(i.add(1337)).mul(2).sub(1))).mul(e.noise.mul(e.mapTexel).mul(e.shadowExtent));t.receivedShadowPositionNode=positionWorld.add(s);}return {material:t,uniforms:e}},createFinalize(e,t){const a=uniform(1),r=Fn(()=>{const r=ivec2(screenCoordinate.xy),n=vec4(0).toVar(),o=textureLoad(e,r);return If(o.a.greaterThan(0),()=>{n.assign(vec4(o.rgb.mul(a).div(o.a),1));}).Else(()=>{for(const[o,i]of OFFSETS)If(n.a.equal(0),()=>{const s=r.add(ivec2(o,i)).max(ivec2(0,0)).min(ivec2(t-1,t-1)),d=textureLoad(e,s);If(d.a.greaterThan(0),()=>{n.assign(vec4(d.rgb.mul(a).div(d.a),1));});});}),n}),n=new NodeMaterial;return n.fragmentNode=r(),n.depthTest=!1,n.depthWrite=!1,{material:n,uniforms:{scale:a},dispose:()=>n.dispose()}},beforeSample(e){e._nodes.nodeFrame.update();},renderQuad(e,t){quad.material=t,quad.render(e);},createDisplay(){const e=attribute("lightmapUvA","vec2"),t=attribute("lightmapUvB","vec2"),a={uvA:e,uvB:t,sampleA:texture(new Texture,e),sampleB:texture(new Texture,t),frontIsA:uniform(1).setGroup(renderGroup),blend:uniform(1).setGroup(renderGroup),valid:uniform(0).setGroup(renderGroup),specularOcclusion:uniform(1).setGroup(renderGroup)},r=new WeakSet,n=new Set;return {uniforms:{a:a.sampleA,b:a.sampleB,frontIsA:a.frontIsA,blend:a.blend,valid:a.valid,specularOcclusion:a.specularOcclusion},refresh(){for(const e of n)e.needsUpdate=!0;},patch(e){if(r.has(e))return;if(!e.isNodeMaterial)return void console.warn("EnvironmentShadows: WebGPURenderer needs node materials (e.g. MeshStandardNodeMaterial), skipped",e);const t=Object.getPrototypeOf(e);"function"==typeof t.setupMaterialLightings?e.setupMaterialLightings=function(e){const r=t.setupMaterialLightings.call(this,e);return r.push(new EnvironmentShadowsLightingNode(a)),r}:e.setupLights=function(e){const r=t.setupLights.call(this,e),n=new EnvironmentShadowsLightingNode(a);return e.renderer.lighting.createNode([...r.getLights(),n])},r.add(e),n.add(e),e.needsUpdate=!0;},unpatch(e){r.has(e)&&(delete e.setupMaterialLightings,delete e.setupLights,r.delete(e),n.delete(e),e.needsUpdate=!0);}}}};
6
+ class EnvironmentShadowsLightingNode extends LightingNode{static get type(){return "EnvironmentShadowsLightingNode"}constructor(e){super(),this.display=e;}setup(e){const{iblIrradiance:t,radiance:r}=e.context,a=this.display,n=vec3(t).toVar(),o=e.material.envMap?reference("envMapIntensity","float",e.material):reference("environmentIntensity","float",e.scene),i=select(a.uvA.x.greaterThan(1e-6),a.sampleA.rgb.mul(o),n),s=select(a.uvB.x.greaterThan(1e-6),a.sampleB.rgb.mul(o),n),l=a.frontIsA.greaterThan(.5),d=select(l,i,s),u=select(l,s,i),c=select(a.valid.greaterThan(.5),mix(u,d,a.blend),n).toVar(),m=vec3(.2126,.7152,.0722),p=clamp(dot(c,m).div(max(dot(n,m),1e-6)),0,1);t.assign(c),r.mulAssign(mix(float(1),p,a.specularOcclusion));}}const quad=new QuadMesh,OFFSETS=[];for(let e=-4;e<=4;e++)for(let t=-4;t<=4;t++)0===t&&0===e||OFFSETS.push([t,e]);OFFSETS.sort((e,t)=>e[0]*e[0]+e[1]*e[1]-(t[0]*t[0]+t[1]*t[1]));const tslBackend={getSumType(e){const t=e.backend;return (t.isWebGLBackend?null!==t.gl.getExtension("EXT_float_blend"):e.hasFeature("float32-blendable"))?FloatType:HalfFloatType},createBakeMaterial(){const e={noise:uniform(0),seed:uniform(0),mapTexel:uniform(1/512),shadowExtent:uniform(1),lightDirection:uniform(new Vector3(0,1,0))},t=new MeshLambertNodeMaterial({color:16777215,side:DoubleSide,forceSinglePass:!0,blending:CustomBlending,blendEquation:AddEquation,blendSrc:OneFactor,blendDst:OneFactor,blendSrcAlpha:OneFactor,blendDstAlpha:OneFactor,depthTest:!1,depthWrite:!1});t.shadowSide=BackSide;const r=attribute("lightmapUvBake","vec2");if(t.vertexNode=vec4(r.x.mul(2).sub(1),float(1).sub(r.y.mul(2)),0,1),t.normalNode=cameraViewMatrix.mul(vec4(modelNormalMatrix.mul(normalLocal),0)).xyz.normalize(),"receivedShadowPositionNode"in t){const r=e.lightDirection.normalize(),a=select(r.y.abs().lessThan(.99),vec3(0,1,0),vec3(1,0,0)),n=cross(a,r).normalize(),o=cross(r,n),i=screenCoordinate.x.add(screenCoordinate.y.mul(4099)).add(e.seed.mul(7919)),s=n.mul(hash(i).mul(2).sub(1)).add(o.mul(hash(i.add(1337)).mul(2).sub(1))).mul(e.noise.mul(e.mapTexel).mul(e.shadowExtent));t.receivedShadowPositionNode=positionWorld.add(s);}return {material:t,uniforms:e}},createFinalize(e,t){const r=uniform(1),a=Fn(()=>{const a=ivec2(screenCoordinate.xy),n=vec4(0).toVar(),o=textureLoad(e,a);return If(o.a.greaterThan(0),()=>{n.assign(vec4(o.rgb.mul(r).div(o.a),1));}).Else(()=>{for(const[o,i]of OFFSETS)If(n.a.equal(0),()=>{const s=a.add(ivec2(o,i)).max(ivec2(0,0)).min(ivec2(t-1,t-1)),l=textureLoad(e,s);If(l.a.greaterThan(0),()=>{n.assign(vec4(l.rgb.mul(r).div(l.a),1));});});}),n}),n=new NodeMaterial;return n.fragmentNode=a(),n.depthTest=!1,n.depthWrite=!1,{material:n,uniforms:{scale:r},dispose:()=>n.dispose()}},beforeSample(e){e._nodes.nodeFrame.update();},renderQuad(e,t){quad.material=t,quad.render(e);},createDisplay(){const e=attribute("lightmapUvA","vec2"),t=attribute("lightmapUvB","vec2"),r={uvA:e,uvB:t,sampleA:texture(new Texture,e),sampleB:texture(new Texture,t),frontIsA:uniform(1).setGroup(renderGroup),blend:uniform(1).setGroup(renderGroup),valid:uniform(0).setGroup(renderGroup),specularOcclusion:uniform(1).setGroup(renderGroup)},a=new WeakSet,n=new Set;return {uniforms:{a:r.sampleA,b:r.sampleB,frontIsA:r.frontIsA,blend:r.blend,valid:r.valid,specularOcclusion:r.specularOcclusion},refresh(){for(const e of n)e.needsUpdate=!0;},patch(e){if(a.has(e))return;if(!e.isNodeMaterial)return void console.warn("EnvironmentShadows: WebGPURenderer needs node materials (e.g. MeshStandardNodeMaterial), skipped",e);const t=Object.getPrototypeOf(e);"function"==typeof t.setupMaterialLightings?e.setupMaterialLightings=function(e){const a=t.setupMaterialLightings.call(this,e);return a.push(new EnvironmentShadowsLightingNode(r)),a}:e.setupLights=function(e){const a=t.setupLights.call(this,e),n=new EnvironmentShadowsLightingNode(r);return e.renderer.lighting.createNode([...a.getLights(),n])},a.add(e),n.add(e),e.needsUpdate=!0;},unpatch(e){a.has(e)&&(delete e.setupMaterialLightings,delete e.setupLights,a.delete(e),n.delete(e),e.needsUpdate=!0);}}}};
7
7
 
8
8
  class EnvironmentShadows extends EnvironmentShadowsBase{constructor(s,n,o={}){super(s,n,o,tslBackend);}}
9
9