@@ -2,165 +2,66 @@ import ShadowNode from './ShadowNode.js';
22import { uniform } from '../core/UniformNode.js' ;
33import { float , vec2 , If , Fn , nodeObject } from '../tsl/TSLBase.js' ;
44import { reference } from '../accessors/ReferenceNode.js' ;
5- import { texture } from '../accessors/TextureNode.js' ;
6- import { max , abs , sign } from '../math/MathNode.js' ;
7- import { sub , div } from '../math/OperatorNode.js' ;
5+ import { cubeTexture } from '../accessors/CubeTextureNode.js' ;
86import { renderGroup } from '../core/UniformGroupNode.js' ;
97import { Matrix4 } from '../../math/Matrix4.js' ;
10- import { Vector2 } from '../../math/Vector2.js' ;
118import { Vector3 } from '../../math/Vector3.js' ;
12- import { Vector4 } from '../../math/Vector4.js' ;
139import { Color } from '../../math/Color.js' ;
14- import { BasicShadowMap } from '../../constants.js' ;
10+ import { BasicShadowMap , LessCompare , WebGPUCoordinateSystem } from '../../constants.js' ;
11+ import { CubeDepthTexture } from '../../textures/CubeDepthTexture.js' ;
1512
1613const _clearColor = /*@__PURE__ */ new Color ( ) ;
1714const _projScreenMatrix = /*@__PURE__ */ new Matrix4 ( ) ;
1815const _lightPositionWorld = /*@__PURE__ */ new Vector3 ( ) ;
1916const _lookTarget = /*@__PURE__ */ new Vector3 ( ) ;
2017
21- // These viewports map a cube-map onto a 2D texture with the
22- // following orientation:
23- //
24- // xzXZ
25- // y Y
26- //
27- // X - Positive x direction
28- // x - Negative x direction
29- // Y - Positive y direction
30- // y - Negative y direction
31- // Z - Positive z direction
32- // z - Negative z direction
33-
34- const _frameExtents = /*@__PURE__ */ new Vector2 ( 4 , 2 ) ;
35-
36- const _viewports = [
37- // positive X
38- /*@__PURE__ */ new Vector4 ( 2 , 1 , 1 , 1 ) ,
39- // negative X
40- /*@__PURE__ */ new Vector4 ( 0 , 1 , 1 , 1 ) ,
41- // positive Z
42- /*@__PURE__ */ new Vector4 ( 3 , 1 , 1 , 1 ) ,
43- // negative Z
44- /*@__PURE__ */ new Vector4 ( 1 , 1 , 1 , 1 ) ,
45- // positive Y
46- /*@__PURE__ */ new Vector4 ( 3 , 0 , 1 , 1 ) ,
47- // negative Y
48- /*@__PURE__ */ new Vector4 ( 1 , 0 , 1 , 1 )
18+ // Cube map face directions and up vectors for point light shadows
19+ // Face order: +X, -X, +Y, -Y, +Z, -Z
20+ // WebGPU coordinate system - Y faces swapped to match texture sampling convention
21+ const _cubeDirectionsWebGPU = [
22+ /*@__PURE__ */ new Vector3 ( 1 , 0 , 0 ) , /*@__PURE__ */ new Vector3 ( - 1 , 0 , 0 ) , /*@__PURE__ */ new Vector3 ( 0 , - 1 , 0 ) ,
23+ /*@__PURE__ */ new Vector3 ( 0 , 1 , 0 ) , /*@__PURE__ */ new Vector3 ( 0 , 0 , 1 ) , /*@__PURE__ */ new Vector3 ( 0 , 0 , - 1 )
4924] ;
5025
51- const _cubeDirections = [
52- /*@__PURE__ */ new Vector3 ( 1 , 0 , 0 ) , /*@__PURE__ */ new Vector3 ( - 1 , 0 , 0 ) , /*@__PURE__ */ new Vector3 ( 0 , 0 , 1 ) ,
53- /*@__PURE__ */ new Vector3 ( 0 , 0 , - 1 ) , /*@__PURE__ */ new Vector3 ( 0 , 1 , 0 ) , /*@__PURE__ */ new Vector3 ( 0 , - 1 , 0 )
26+ const _cubeUpsWebGPU = [
27+ /*@__PURE__ */ new Vector3 ( 0 , - 1 , 0 ) , /*@__PURE__ */ new Vector3 ( 0 , - 1 , 0 ) , /*@__PURE__ */ new Vector3 ( 0 , 0 , - 1 ) ,
28+ /*@__PURE__ */ new Vector3 ( 0 , 0 , 1 ) , /*@__PURE__ */ new Vector3 ( 0 , - 1 , 0 ) , /*@__PURE__ */ new Vector3 ( 0 , - 1 , 0 )
5429] ;
5530
56- const _cubeUps = [
57- /*@__PURE__ */ new Vector3 ( 0 , 1 , 0 ) , /*@__PURE__ */ new Vector3 ( 0 , 1 , 0 ) , /*@__PURE__ */ new Vector3 ( 0 , 1 , 0 ) ,
58- /*@__PURE__ */ new Vector3 ( 0 , 1 , 0 ) , /*@__PURE__ */ new Vector3 ( 0 , 0 , 1 ) , /*@__PURE__ */ new Vector3 ( 0 , 0 , - 1 )
31+ // WebGL coordinate system - standard OpenGL convention
32+ const _cubeDirectionsWebGL = [
33+ /*@__PURE__ */ new Vector3 ( 1 , 0 , 0 ) , /*@__PURE__ */ new Vector3 ( - 1 , 0 , 0 ) , /*@__PURE__ */ new Vector3 ( 0 , 1 , 0 ) ,
34+ /*@__PURE__ */ new Vector3 ( 0 , - 1 , 0 ) , /*@__PURE__ */ new Vector3 ( 0 , 0 , 1 ) , /*@__PURE__ */ new Vector3 ( 0 , 0 , - 1 )
5935] ;
6036
61- // cubeToUV() maps a 3D direction vector suitable for cube texture mapping to a 2D
62- // vector suitable for 2D texture mapping. This code uses the following layout for the
63- // 2D texture:
64- //
65- // xzXZ
66- // y Y
67- //
68- // Y - Positive y direction
69- // y - Negative y direction
70- // X - Positive x direction
71- // x - Negative x direction
72- // Z - Positive z direction
73- // z - Negative z direction
74- //
75- // Source and test bed:
76- // https://gist.github.com/tschw/da10c43c467ce8afd0c4
77-
78- export const cubeToUV = /*@__PURE__ */ Fn ( ( [ pos , texelSizeY ] ) => {
79-
80- const v = pos . toVar ( ) ;
81-
82- // Number of texels to avoid at the edge of each square
83-
84- const absV = abs ( v ) ;
85-
86- // Intersect unit cube
87-
88- const scaleToCube = div ( 1.0 , max ( absV . x , max ( absV . y , absV . z ) ) ) ;
89- absV . mulAssign ( scaleToCube ) ;
90-
91- // Apply scale to avoid seams
92-
93- // two texels less per square (one texel will do for NEAREST)
94- v . mulAssign ( scaleToCube . mul ( texelSizeY . mul ( 2 ) . oneMinus ( ) ) ) ;
95-
96- // Unwrap
97-
98- // space: -1 ... 1 range for each square
99- //
100- // #X## dim := ( 4 , 2 )
101- // # # center := ( 1 , 1 )
102-
103- const planar = vec2 ( v . xy ) . toVar ( ) ;
104-
105- const almostATexel = texelSizeY . mul ( 1.5 ) ;
106- const almostOne = almostATexel . oneMinus ( ) ;
107-
108- If ( absV . z . greaterThanEqual ( almostOne ) , ( ) => {
109-
110- If ( v . z . greaterThan ( 0.0 ) , ( ) => {
111-
112- planar . x . assign ( sub ( 4.0 , v . x ) ) ;
113-
114- } ) ;
115-
116- } ) . ElseIf ( absV . x . greaterThanEqual ( almostOne ) , ( ) => {
117-
118- const signX = sign ( v . x ) ;
119- planar . x . assign ( v . z . mul ( signX ) . add ( signX . mul ( 2.0 ) ) ) ;
120-
121- } ) . ElseIf ( absV . y . greaterThanEqual ( almostOne ) , ( ) => {
122-
123- const signY = sign ( v . y ) ;
124- planar . x . assign ( v . x . add ( signY . mul ( 2.0 ) ) . add ( 2.0 ) ) ;
125- planar . y . assign ( v . z . mul ( signY ) . sub ( 2.0 ) ) ;
126-
127- } ) ;
128-
129- // Transform to UV space
130-
131- // scale := 0.5 / dim
132- // translate := ( center + 0.5 ) / dim
133- return vec2 ( 0.125 , 0.25 ) . mul ( planar ) . add ( vec2 ( 0.375 , 0.75 ) ) . flipY ( ) ;
134-
135- } ) . setLayout ( {
136- name : 'cubeToUV' ,
137- type : 'vec2' ,
138- inputs : [
139- { name : 'pos' , type : 'vec3' } ,
140- { name : 'texelSizeY' , type : 'float' }
141- ]
142- } ) ;
37+ const _cubeUpsWebGL = [
38+ /*@__PURE__ */ new Vector3 ( 0 , - 1 , 0 ) , /*@__PURE__ */ new Vector3 ( 0 , - 1 , 0 ) , /*@__PURE__ */ new Vector3 ( 0 , 0 , 1 ) ,
39+ /*@__PURE__ */ new Vector3 ( 0 , 0 , - 1 ) , /*@__PURE__ */ new Vector3 ( 0 , - 1 , 0 ) , /*@__PURE__ */ new Vector3 ( 0 , - 1 , 0 )
40+ ] ;
14341
144- export const BasicPointShadowFilter = /*@__PURE__ */ Fn ( ( { depthTexture, bd3D, dp, texelSize } ) => {
42+ export const BasicPointShadowFilter = /*@__PURE__ */ Fn ( ( { depthTexture, bd3D, dp } ) => {
14543
146- return texture ( depthTexture , cubeToUV ( bd3D , texelSize . y ) ) . compare ( dp ) ;
44+ return cubeTexture ( depthTexture , bd3D ) . compare ( dp ) ;
14745
14846} ) ;
14947
150- export const PointShadowFilter = /*@__PURE__ */ Fn ( ( { depthTexture, bd3D, dp, texelSize , shadow } ) => {
48+ export const PointShadowFilter = /*@__PURE__ */ Fn ( ( { depthTexture, bd3D, dp, shadow } ) => {
15149
15250 const radius = reference ( 'radius' , 'float' , shadow ) . setGroup ( renderGroup ) ;
153- const offset = vec2 ( - 1.0 , 1.0 ) . mul ( radius ) . mul ( texelSize . y ) ;
154-
155- return texture ( depthTexture , cubeToUV ( bd3D . add ( offset . xyy ) , texelSize . y ) ) . compare ( dp )
156- . add ( texture ( depthTexture , cubeToUV ( bd3D . add ( offset . yyy ) , texelSize . y ) ) . compare ( dp ) )
157- . add ( texture ( depthTexture , cubeToUV ( bd3D . add ( offset . xyx ) , texelSize . y ) ) . compare ( dp ) )
158- . add ( texture ( depthTexture , cubeToUV ( bd3D . add ( offset . yyx ) , texelSize . y ) ) . compare ( dp ) )
159- . add ( texture ( depthTexture , cubeToUV ( bd3D , texelSize . y ) ) . compare ( dp ) )
160- . add ( texture ( depthTexture , cubeToUV ( bd3D . add ( offset . xxy ) , texelSize . y ) ) . compare ( dp ) )
161- . add ( texture ( depthTexture , cubeToUV ( bd3D . add ( offset . yxy ) , texelSize . y ) ) . compare ( dp ) )
162- . add ( texture ( depthTexture , cubeToUV ( bd3D . add ( offset . xxx ) , texelSize . y ) ) . compare ( dp ) )
163- . add ( texture ( depthTexture , cubeToUV ( bd3D . add ( offset . yxx ) , texelSize . y ) ) . compare ( dp ) )
51+ const mapSize = reference ( 'mapSize' , 'vec2' , shadow ) . setGroup ( renderGroup ) ;
52+
53+ const texelSize = float ( 1 ) . div ( mapSize . x ) ;
54+ const offset = vec2 ( - 1.0 , 1.0 ) . mul ( radius ) . mul ( texelSize ) ;
55+
56+ return cubeTexture ( depthTexture , bd3D . add ( offset . xyy ) ) . compare ( dp )
57+ . add ( cubeTexture ( depthTexture , bd3D . add ( offset . yyy ) ) . compare ( dp ) )
58+ . add ( cubeTexture ( depthTexture , bd3D . add ( offset . xyx ) ) . compare ( dp ) )
59+ . add ( cubeTexture ( depthTexture , bd3D . add ( offset . yyx ) ) . compare ( dp ) )
60+ . add ( cubeTexture ( depthTexture , bd3D ) . compare ( dp ) )
61+ . add ( cubeTexture ( depthTexture , bd3D . add ( offset . xxy ) ) . compare ( dp ) )
62+ . add ( cubeTexture ( depthTexture , bd3D . add ( offset . yxy ) ) . compare ( dp ) )
63+ . add ( cubeTexture ( depthTexture , bd3D . add ( offset . xxx ) ) . compare ( dp ) )
64+ . add ( cubeTexture ( depthTexture , bd3D . add ( offset . yxx ) ) . compare ( dp ) )
16465 . mul ( 1.0 / 9.0 ) ;
16566
16667} ) ;
@@ -175,7 +76,6 @@ const pointShadowFilter = /*@__PURE__*/ Fn( ( { filterFn, depthTexture, shadowCo
17576 const cameraNearLocal = uniform ( 'float' ) . setGroup ( renderGroup ) . onRenderUpdate ( ( ) => shadow . camera . near ) ;
17677 const cameraFarLocal = uniform ( 'float' ) . setGroup ( renderGroup ) . onRenderUpdate ( ( ) => shadow . camera . far ) ;
17778 const bias = reference ( 'bias' , 'float' , shadow ) . setGroup ( renderGroup ) ;
178- const mapSize = uniform ( shadow . mapSize ) . setGroup ( renderGroup ) ;
17979
18080 const result = float ( 1.0 ) . toVar ( ) ;
18181
@@ -185,23 +85,18 @@ const pointShadowFilter = /*@__PURE__*/ Fn( ( { filterFn, depthTexture, shadowCo
18585 const dp = lightToPositionLength . sub ( cameraNearLocal ) . div ( cameraFarLocal . sub ( cameraNearLocal ) ) . toVar ( ) ; // need to clamp?
18686 dp . addAssign ( bias ) ;
18787
188- // bd3D = base direction 3D
88+ // bd3D = base direction 3D (direction from light to fragment)
18989 const bd3D = lightToPosition . normalize ( ) ;
190- const texelSize = vec2 ( 1.0 ) . div ( mapSize . mul ( vec2 ( 4.0 , 2.0 ) ) ) ;
19190
192- // percentage-closer filtering
193- result . assign ( filterFn ( { depthTexture, bd3D, dp, texelSize , shadow } ) ) ;
91+ // percentage-closer filtering using cube texture sampling
92+ result . assign ( filterFn ( { depthTexture, bd3D, dp, shadow } ) ) ;
19493
19594 } ) ;
19695
19796 return result ;
19897
19998} ) ;
20099
201- const _viewport = /*@__PURE__ */ new Vector4 ( ) ;
202- const _viewportSize = /*@__PURE__ */ new Vector2 ( ) ;
203- const _shadowMapSize = /*@__PURE__ */ new Vector2 ( ) ;
204-
205100
206101/**
207102 * Represents the shadow implementation for point light nodes.
@@ -261,15 +156,35 @@ class PointShadowNode extends ShadowNode {
261156 * @param {NodeBuilder } builder - A reference to the current node builder.
262157 * @param {Object } inputs - A configuration object that defines the shadow filtering.
263158 * @param {Function } inputs.filterFn - This function defines the filtering type of the shadow map e.g. PCF.
264- * @param {Texture } inputs.shadowTexture - A reference to the shadow map's texture.
265- * @param {DepthTexture } inputs.depthTexture - A reference to the shadow map's texture data.
159+ * @param {DepthTexture } inputs.depthTexture - A reference to the shadow map's depth texture.
266160 * @param {Node<vec3> } inputs.shadowCoord - Shadow coordinates which are used to sample from the shadow map.
267161 * @param {LightShadow } inputs.shadow - The light shadow.
268162 * @return {Node<float> } The result node of the shadow filtering.
269163 */
270- setupShadowFilter ( builder , { filterFn, shadowTexture, depthTexture, shadowCoord, shadow } ) {
164+ setupShadowFilter ( builder , { filterFn, depthTexture, shadowCoord, shadow } ) {
165+
166+ return pointShadowFilter ( { filterFn, depthTexture, shadowCoord, shadow } ) ;
167+
168+ }
169+
170+ /**
171+ * Overwrites the default implementation to create a CubeRenderTarget with CubeDepthTexture.
172+ *
173+ * @param {LightShadow } shadow - The light shadow object.
174+ * @param {NodeBuilder } builder - A reference to the current node builder.
175+ * @return {Object } An object containing the shadow map and depth texture.
176+ */
177+ setupRenderTarget ( shadow , builder ) {
178+
179+ const depthTexture = new CubeDepthTexture ( shadow . mapSize . width ) ;
180+ depthTexture . name = 'PointShadowDepthTexture' ;
181+ depthTexture . compareFunction = LessCompare ;
271182
272- return pointShadowFilter ( { filterFn, shadowTexture, depthTexture, shadowCoord, shadow } ) ;
183+ const shadowMap = builder . createCubeRenderTarget ( shadow . mapSize . width ) ;
184+ shadowMap . texture . name = 'PointShadowMap' ;
185+ shadowMap . depthTexture = depthTexture ;
186+
187+ return { shadowMap, depthTexture } ;
273188
274189 }
275190
@@ -287,12 +202,12 @@ class PointShadowNode extends ShadowNode {
287202 const camera = shadow . camera ;
288203 const shadowMatrix = shadow . matrix ;
289204
290- _shadowMapSize . copy ( shadow . mapSize ) ;
291- _shadowMapSize . multiply ( _frameExtents ) ;
292-
293- shadowMap . setSize ( _shadowMapSize . width , _shadowMapSize . height ) ;
205+ // Select cube directions/ups based on coordinate system
206+ const isWebGPU = renderer . coordinateSystem === WebGPUCoordinateSystem ;
207+ const cubeDirections = isWebGPU ? _cubeDirectionsWebGPU : _cubeDirectionsWebGL ;
208+ const cubeUps = isWebGPU ? _cubeUpsWebGPU : _cubeUpsWebGL ;
294209
295- _viewportSize . copy ( shadow . mapSize ) ;
210+ shadowMap . setSize ( shadow . mapSize . width , shadow . mapSize . width ) ;
296211
297212 //
298213
@@ -303,23 +218,13 @@ class PointShadowNode extends ShadowNode {
303218
304219 renderer . autoClear = false ;
305220 renderer . setClearColor ( shadow . clearColor , shadow . clearAlpha ) ;
306- renderer . clear ( ) ;
307-
308- for ( let vp = 0 ; vp < 6 ; vp ++ ) {
309-
310- const viewport = _viewports [ vp ] ;
311-
312- const x = _viewportSize . x * viewport . x ;
313- const y = _shadowMapSize . y - _viewportSize . y - ( _viewportSize . y * viewport . y ) ;
314221
315- _viewport . set (
316- x ,
317- y ,
318- _viewportSize . x * viewport . z ,
319- _viewportSize . y * viewport . w
320- ) ;
222+ // Render each cube face
223+ for ( let face = 0 ; face < 6 ; face ++ ) {
321224
322- shadowMap . viewport . copy ( _viewport ) ;
225+ // Set render target to the specific cube face
226+ renderer . setRenderTarget ( shadowMap , face ) ;
227+ renderer . clear ( ) ;
323228
324229 // Update shadow camera matrices for this face
325230
@@ -336,8 +241,8 @@ class PointShadowNode extends ShadowNode {
336241 camera . position . copy ( _lightPositionWorld ) ;
337242
338243 _lookTarget . copy ( camera . position ) ;
339- _lookTarget . add ( _cubeDirections [ vp ] ) ;
340- camera . up . copy ( _cubeUps [ vp ] ) ;
244+ _lookTarget . add ( cubeDirections [ face ] ) ;
245+ camera . up . copy ( cubeUps [ face ] ) ;
341246 camera . lookAt ( _lookTarget ) ;
342247 camera . updateMatrixWorld ( ) ;
343248
@@ -350,7 +255,7 @@ class PointShadowNode extends ShadowNode {
350255
351256 const currentSceneName = scene . name ;
352257
353- scene . name = `Point Light Shadow [ ${ light . name || 'ID: ' + light . id } ] - Face ${ vp + 1 } ` ;
258+ scene . name = `Point Light Shadow [ ${ light . name || 'ID: ' + light . id } ] - Face ${ face + 1 } ` ;
354259
355260 renderer . render ( scene , camera ) ;
356261
0 commit comments