diff --git a/examples/jsm/helpers/LightProbeHelper.js b/examples/jsm/helpers/LightProbeHelper.js index e6da80faa45945..1f9e375a11d370 100644 --- a/examples/jsm/helpers/LightProbeHelper.js +++ b/examples/jsm/helpers/LightProbeHelper.js @@ -58,11 +58,12 @@ class LightProbeHelper extends Mesh { #define RECIPROCAL_PI 0.318309886 - vec3 inverseTransformDirection( in vec3 normal, in mat4 matrix ) { - // matrix is assumed to be orthogonal + vec3 transformNormalByInverseViewMatrix( in vec3 normal, in mat4 viewMatrix ) { - return normalize( ( vec4( normal, 0.0 ) * matrix ).xyz ); + // upper-left 3x3 of view matrix is assumed to be orthogonal + + return normalize( ( vec4( normal, 0.0 ) * viewMatrix ).xyz ); } @@ -101,7 +102,7 @@ class LightProbeHelper extends Mesh { vec3 normal = normalize( vNormal ); - vec3 worldNormal = inverseTransformDirection( normal, viewMatrix ); + vec3 worldNormal = transformNormalByInverseViewMatrix( normal, viewMatrix ); vec3 irradiance = shGetIrradianceAt( worldNormal, sh ); diff --git a/examples/jsm/loaders/SVGLoader.js b/examples/jsm/loaders/SVGLoader.js index 4bae8dee0d0b14..756efffb1fd162 100644 --- a/examples/jsm/loaders/SVGLoader.js +++ b/examples/jsm/loaders/SVGLoader.js @@ -2008,442 +2008,206 @@ class SVGLoader extends Loader { */ static createShapes( shapePath ) { - const BIGNUMBER = 999999999; - - const IntersectionLocationType = { - ORIGIN: 0, - DESTINATION: 1, - BETWEEN: 2, - LEFT: 3, - RIGHT: 4, - BEHIND: 5, - BEYOND: 6 - }; - - const classifyResult = { - loc: IntersectionLocationType.ORIGIN, - t: 0 - }; - - function findEdgeIntersection( a0, a1, b0, b1 ) { - - const x1 = a0.x; - const x2 = a1.x; - const x3 = b0.x; - const x4 = b1.x; - const y1 = a0.y; - const y2 = a1.y; - const y3 = b0.y; - const y4 = b1.y; - const nom1 = ( x4 - x3 ) * ( y1 - y3 ) - ( y4 - y3 ) * ( x1 - x3 ); - const nom2 = ( x2 - x1 ) * ( y1 - y3 ) - ( y2 - y1 ) * ( x1 - x3 ); - const denom = ( y4 - y3 ) * ( x2 - x1 ) - ( x4 - x3 ) * ( y2 - y1 ); - const t1 = nom1 / denom; - const t2 = nom2 / denom; - - if ( ( ( denom === 0 ) && ( nom1 !== 0 ) ) || ( t1 <= 0 ) || ( t1 >= 1 ) || ( t2 < 0 ) || ( t2 > 1 ) ) { - - //1. lines are parallel or edges don't intersect - - return null; - - } else if ( ( nom1 === 0 ) && ( denom === 0 ) ) { - - //2. lines are colinear - - //check if endpoints of edge2 (b0-b1) lies on edge1 (a0-a1) - for ( let i = 0; i < 2; i ++ ) { - - classifyPoint( i === 0 ? b0 : b1, a0, a1 ); - //find position of this endpoints relatively to edge1 - if ( classifyResult.loc == IntersectionLocationType.ORIGIN ) { - - const point = ( i === 0 ? b0 : b1 ); - return { x: point.x, y: point.y, t: classifyResult.t }; - - } else if ( classifyResult.loc == IntersectionLocationType.BETWEEN ) { - - const x = + ( ( x1 + classifyResult.t * ( x2 - x1 ) ).toPrecision( 10 ) ); - const y = + ( ( y1 + classifyResult.t * ( y2 - y1 ) ).toPrecision( 10 ) ); - return { x: x, y: y, t: classifyResult.t, }; - - } - - } - - return null; + // Point-in-polygon test using the even-odd ray-casting rule. Valid for + // simple (non self-intersecting) polygons. + function pointInPolygon( p, polygon ) { - } else { - - //3. edges intersect - - for ( let i = 0; i < 2; i ++ ) { + let inside = false; + const n = polygon.length; - classifyPoint( i === 0 ? b0 : b1, a0, a1 ); + for ( let i = 0, j = n - 1; i < n; j = i ++ ) { - if ( classifyResult.loc == IntersectionLocationType.ORIGIN ) { + const a = polygon[ i ]; + const b = polygon[ j ]; - const point = ( i === 0 ? b0 : b1 ); - return { x: point.x, y: point.y, t: classifyResult.t }; + if ( ( a.y > p.y ) !== ( b.y > p.y ) && + p.x < ( b.x - a.x ) * ( p.y - a.y ) / ( b.y - a.y ) + a.x ) { - } + inside = ! inside; } - const x = + ( ( x1 + t1 * ( x2 - x1 ) ).toPrecision( 10 ) ); - const y = + ( ( y1 + t1 * ( y2 - y1 ) ).toPrecision( 10 ) ); - return { x: x, y: y, t: t1 }; - - } - - } - - function classifyPoint( p, edgeStart, edgeEnd ) { - - const ax = edgeEnd.x - edgeStart.x; - const ay = edgeEnd.y - edgeStart.y; - const bx = p.x - edgeStart.x; - const by = p.y - edgeStart.y; - const sa = ax * by - bx * ay; - - if ( ( p.x === edgeStart.x ) && ( p.y === edgeStart.y ) ) { - - classifyResult.loc = IntersectionLocationType.ORIGIN; - classifyResult.t = 0; - return; - } - if ( ( p.x === edgeEnd.x ) && ( p.y === edgeEnd.y ) ) { + return inside; - classifyResult.loc = IntersectionLocationType.DESTINATION; - classifyResult.t = 1; - return; - - } - - if ( sa < - Number.EPSILON ) { - - classifyResult.loc = IntersectionLocationType.LEFT; - return; - - } + } - if ( sa > Number.EPSILON ) { + // Returns a point guaranteed to be strictly inside the given simple + // polygon. First tries the bounding-box center; if that falls outside + // the polygon, casts a horizontal ray at the center's y and picks the + // midpoint between the first two sorted intercepts. + // + // Port of paper.js' Path#getInteriorPoint() + // https://github.com/paperjs/paper.js/blob/develop/src/path/PathItem.Boolean.js + function getInteriorPoint( polygon, boundingBox ) { - classifyResult.loc = IntersectionLocationType.RIGHT; - return; + const point = boundingBox.getCenter( new Vector2() ); + if ( pointInPolygon( point, polygon ) ) return point; - } + const y = point.y; + const intercepts = []; + const n = polygon.length; - if ( ( ( ax * bx ) < 0 ) || ( ( ay * by ) < 0 ) ) { + for ( let i = 0; i < n; i ++ ) { - classifyResult.loc = IntersectionLocationType.BEHIND; - return; + const a = polygon[ i ]; + const b = polygon[ ( i + 1 ) % n ]; - } + // Half-open crossing rule — counts each vertex exactly once and + // skips horizontal edges. + if ( ( a.y > y ) !== ( b.y > y ) ) { - if ( ( Math.sqrt( ax * ax + ay * ay ) ) < ( Math.sqrt( bx * bx + by * by ) ) ) { + const x = a.x + ( y - a.y ) * ( b.x - a.x ) / ( b.y - a.y ); + intercepts.push( x ); - classifyResult.loc = IntersectionLocationType.BEYOND; - return; + } } - let t; - - if ( ax !== 0 ) { - - t = bx / ax; - - } else { + if ( intercepts.length > 1 ) { - t = by / ay; + intercepts.sort( ( a, b ) => a - b ); + point.x = ( intercepts[ 0 ] + intercepts[ 1 ] ) / 2; } - classifyResult.loc = IntersectionLocationType.BETWEEN; - classifyResult.t = t; + return point; } - function getIntersections( path1, path2 ) { - - const intersectionsRaw = []; - const intersections = []; - - for ( let index = 1; index < path1.length; index ++ ) { - - const path1EdgeStart = path1[ index - 1 ]; - const path1EdgeEnd = path1[ index ]; - - for ( let index2 = 1; index2 < path2.length; index2 ++ ) { - - const path2EdgeStart = path2[ index2 - 1 ]; - const path2EdgeEnd = path2[ index2 ]; + // Resolve fill-rule. SVG defaults to 'nonzero'. + let fillRule = ( shapePath.userData && shapePath.userData.style && shapePath.userData.style.fillRule ) || 'nonzero'; - const intersection = findEdgeIntersection( path1EdgeStart, path1EdgeEnd, path2EdgeStart, path2EdgeEnd ); + if ( fillRule !== 'nonzero' && fillRule !== 'evenodd' ) { - if ( intersection !== null && intersectionsRaw.find( i => i.t <= intersection.t + Number.EPSILON && i.t >= intersection.t - Number.EPSILON ) === undefined ) { - - intersectionsRaw.push( intersection ); - intersections.push( new Vector2( intersection.x, intersection.y ) ); - - } - - } - - } - - return intersections; + console.warn( 'THREE.SVGLoader: fill-rule "' + fillRule + '" is not supported, falling back to "nonzero".' ); + fillRule = 'nonzero'; } - function getScanlineIntersections( scanline, boundingBox, paths ) { - - const center = new Vector2(); - boundingBox.getCenter( center ); - - const allIntersections = []; - - paths.forEach( path => { - - // check if the center of the bounding box is in the bounding box of the paths. - // this is a pruning method to limit the search of intersections in paths that can't envelop of the current path. - // if a path envelops another path. The center of that other path, has to be inside the bounding box of the enveloping path. - if ( path.boundingBox.containsPoint( center ) ) { - - const intersections = getIntersections( scanline, path.points ); - - intersections.forEach( p => { - - allIntersections.push( { identifier: path.identifier, isCW: path.isCW, point: p } ); - - } ); - - } - - } ); - - allIntersections.sort( ( i1, i2 ) => { - - return i1.point.x - i2.point.x; - + // Predicate that decides whether a winding number falls inside the fill + // region, per the SVG fill-rule spec. Works for negative windings too, + // because JavaScript's bitwise AND preserves odd/even under two's + // complement. + const isInside = fillRule === 'nonzero' + ? ( w => w !== 0 ) + : ( w => ( w & 1 ) !== 0 ); + + // Build an entry per usable subpath. Self-winding follows the standard + // convention used by ShapeUtils: counter-clockwise (signed area > 0) + // contributes +1 to the winding number at an interior point, + // clockwise contributes -1. + const entries = []; + + for ( const subPath of shapePath.subPaths ) { + + const points = subPath.getPoints(); + if ( points.length < 3 ) continue; + + const area = ShapeUtils.area( points ); + if ( area === 0 ) continue; + + const boundingBox = new Box2(); + for ( let i = 0; i < points.length; i ++ ) boundingBox.expandByPoint( points[ i ] ); + + entries.push( { + subPath: subPath, + points: points, + boundingBox: boundingBox, + interiorPoint: getInteriorPoint( points, boundingBox ), + absArea: Math.abs( area ), + winding: area < 0 ? - 1 : 1, + container: null, + exclude: false, + role: null } ); - return allIntersections; - } - function isHoleTo( simplePath, allPaths, scanlineMinX, scanlineMaxX, _fillRule ) { - - if ( _fillRule === null || _fillRule === undefined || _fillRule === '' ) { - - _fillRule = 'nonzero'; + // Sort by area descending. This guarantees that any subpath that could + // contain `entries[i]` is located at a smaller index and has already + // been processed when it's entries[i]'s turn. Port of paper.js' + // reorientPaths() algorithm. + entries.sort( ( a, b ) => b.absArea - a.absArea ); - } - - const centerBoundingBox = new Vector2(); - simplePath.boundingBox.getCenter( centerBoundingBox ); - - const scanline = [ new Vector2( scanlineMinX, centerBoundingBox.y ), new Vector2( scanlineMaxX, centerBoundingBox.y ) ]; - - const scanlineIntersections = getScanlineIntersections( scanline, simplePath.boundingBox, allPaths ); - - scanlineIntersections.sort( ( i1, i2 ) => { - - return i1.point.x - i2.point.x; - - } ); - - const baseIntersections = []; - const otherIntersections = []; - - scanlineIntersections.forEach( i => { - - if ( i.identifier === simplePath.identifier ) { - - baseIntersections.push( i ); - - } else { - - otherIntersections.push( i ); - - } - - } ); - - const firstXOfPath = baseIntersections[ 0 ].point.x; - - // build up the path hierarchy - const stack = []; - let i = 0; - - while ( i < otherIntersections.length && otherIntersections[ i ].point.x < firstXOfPath ) { - - if ( stack.length > 0 && stack[ stack.length - 1 ] === otherIntersections[ i ].identifier ) { - - stack.pop(); + // Walk already-processed entries from closest-in-size to largest, + // stopping at the innermost container. Accumulate the container's + // cumulative winding into this entry's winding so that the final value + // equals the winding number at this entry's interior point. + // + // A subpath only contributes to the fill boundary when crossing it + // actually flips the "insideness" per the fill rule; otherwise it's a + // redundant overlap and gets excluded to avoid double-counting. + for ( let i = 0; i < entries.length; i ++ ) { - } else { + const entry = entries[ i ]; + let containerWinding = 0; - stack.push( otherIntersections[ i ].identifier ); + for ( let j = i - 1; j >= 0; j -- ) { - } + const candidate = entries[ j ]; + if ( ! candidate.boundingBox.containsPoint( entry.interiorPoint ) ) continue; + if ( ! pointInPolygon( entry.interiorPoint, candidate.points ) ) continue; - i ++; + entry.container = candidate.exclude ? candidate.container : candidate; + containerWinding = candidate.winding; + entry.winding += containerWinding; + break; } - stack.push( simplePath.identifier ); - - if ( _fillRule === 'evenodd' ) { - - const isHole = stack.length % 2 === 0 ? true : false; - const isHoleFor = stack[ stack.length - 2 ]; - - return { identifier: simplePath.identifier, isHole: isHole, for: isHoleFor }; - - } else if ( _fillRule === 'nonzero' ) { - - // check if path is a hole by counting the amount of paths with alternating rotations it has to cross. - let isHole = true; - let isHoleFor = null; - let lastCWValue = null; - - for ( let i = 0; i < stack.length; i ++ ) { - - const identifier = stack[ i ]; - if ( isHole ) { - - lastCWValue = allPaths[ identifier ].isCW; - isHole = false; - isHoleFor = identifier; - - } else if ( lastCWValue !== allPaths[ identifier ].isCW ) { - - lastCWValue = allPaths[ identifier ].isCW; - isHole = true; - - } - - } - - return { identifier: simplePath.identifier, isHole: isHole, for: isHoleFor }; - - } else { + if ( isInside( entry.winding ) === isInside( containerWinding ) ) { - console.warn( 'fill-rule: "' + _fillRule + '" is currently not implemented.' ); + entry.exclude = true; } } - // check for self intersecting paths - // TODO - - // check intersecting paths - // TODO - - // prepare paths for hole detection - let scanlineMinX = BIGNUMBER; - let scanlineMaxX = - BIGNUMBER; - - let simplePaths = shapePath.subPaths.map( p => { - - const points = p.getPoints(); - let maxY = - BIGNUMBER; - let minY = BIGNUMBER; - let maxX = - BIGNUMBER; - let minX = BIGNUMBER; - - //points.forEach(p => p.y *= -1); - - for ( let i = 0; i < points.length; i ++ ) { - - const p = points[ i ]; - - if ( p.y > maxY ) { - - maxY = p.y; - - } - - if ( p.y < minY ) { - - minY = p.y; - - } - - if ( p.x > maxX ) { + // Classify retained entries. An entry is an outer shape if it has no + // container or if its container is itself a hole (a solid nested inside + // a hole becomes a new top-level shape); otherwise it's a hole in its + // container. Entries were already sorted outermost-first, so each + // container's role is known by the time we look at it. + for ( const entry of entries ) { - maxX = p.x; - - } - - if ( p.x < minX ) { - - minX = p.x; - - } - - } - - // - if ( scanlineMaxX <= maxX ) { - - scanlineMaxX = maxX + 1; - - } - - if ( scanlineMinX >= minX ) { - - scanlineMinX = minX - 1; - - } - - return { curves: p.curves, points: points, isCW: ShapeUtils.isClockWise( points ), identifier: - 1, boundingBox: new Box2( new Vector2( minX, minY ), new Vector2( maxX, maxY ) ) }; - - } ); - - simplePaths = simplePaths.filter( sp => sp.points.length > 1 ); - - for ( let identifier = 0; identifier < simplePaths.length; identifier ++ ) { - - simplePaths[ identifier ].identifier = identifier; + if ( entry.exclude ) continue; + entry.role = ( entry.container === null || entry.container.role === 'hole' ) ? 'outer' : 'hole'; } - // check if path is solid or a hole - const isAHole = simplePaths.map( p => isHoleTo( p, simplePaths, scanlineMinX, scanlineMaxX, ( shapePath.userData ? shapePath.userData.style.fillRule : undefined ) ) ); + // Build Shapes for outers first, then attach holes to their container's + // Shape. + const shapes = []; + const shapeByEntry = new Map(); + for ( const entry of entries ) { - const shapesToReturn = []; - simplePaths.forEach( p => { + if ( entry.exclude || entry.role !== 'outer' ) continue; - const amIAHole = isAHole[ p.identifier ]; + const shape = new Shape(); + shape.curves = entry.subPath.curves; + shapes.push( shape ); + shapeByEntry.set( entry, shape ); - if ( ! amIAHole.isHole ) { + } - const shape = new Shape(); - shape.curves = p.curves; - const holes = isAHole.filter( h => h.isHole && h.for === p.identifier ); - holes.forEach( h => { + for ( const entry of entries ) { - const hole = simplePaths[ h.identifier ]; - const path = new Path(); - path.curves = hole.curves; - shape.holes.push( path ); + if ( entry.exclude || entry.role !== 'hole' ) continue; - } ); - shapesToReturn.push( shape ); + const shape = shapeByEntry.get( entry.container ); + if ( ! shape ) continue; - } + const hole = new Path(); + hole.curves = entry.subPath.curves; + shape.holes.push( hole ); - } ); + } - return shapesToReturn; + return shapes; } diff --git a/examples/models/svg/emoji.svg b/examples/models/svg/emoji.svg new file mode 100644 index 00000000000000..5dd464e1b87a24 --- /dev/null +++ b/examples/models/svg/emoji.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/webgl_loader_svg.html b/examples/webgl_loader_svg.html index 187ae68689274c..34d96963246c93 100644 --- a/examples/webgl_loader_svg.html +++ b/examples/webgl_loader_svg.html @@ -120,6 +120,7 @@ 'singlePointTest2': 'models/svg/singlePointTest2.svg', 'singlePointTest3': 'models/svg/singlePointTest3.svg', 'emptyPath': 'models/svg/emptyPath.svg', + 'emoji': 'models/svg/emoji.svg', } ).name( 'SVG File' ).onChange( update ); diff --git a/src/nodes/display/PassNode.js b/src/nodes/display/PassNode.js index a75f1fd4f9f0cb..8f43867fec0d29 100644 --- a/src/nodes/display/PassNode.js +++ b/src/nodes/display/PassNode.js @@ -251,14 +251,21 @@ class PassNode extends TempNode { */ this._height = 1; - const depthTexture = new DepthTexture(); - depthTexture.isRenderTargetTexture = true; - //depthTexture.type = FloatType; - depthTexture.name = 'depth'; - const renderTarget = new RenderTarget( this._width * this._pixelRatio, this._height * this._pixelRatio, { type: HalfFloatType, ...options, } ); renderTarget.texture.name = 'output'; - renderTarget.depthTexture = depthTexture; + + let depthTexture = null; + + if ( this.scope === PassNode.DEPTH || options.depthBuffer !== false ) { + + depthTexture = new DepthTexture(); + depthTexture.isRenderTargetTexture = true; + //depthTexture.type = FloatType; + depthTexture.name = 'depth'; + + renderTarget.depthTexture = depthTexture; + + } /** * The pass's render target. @@ -310,13 +317,18 @@ class PassNode extends TempNode { * A dictionary holding the internal result textures. * * @private - * @type {Object} + * @type {{ output: Texture, depth?: DepthTexture }} */ this._textures = { - output: renderTarget.texture, - depth: depthTexture + output: renderTarget.texture }; + if ( depthTexture !== null ) { + + this._textures.depth = depthTexture; + + } + /** * A dictionary holding the internal texture nodes. * @@ -567,6 +579,12 @@ class PassNode extends TempNode { if ( texture === undefined ) { + if ( name === 'depth' ) { + + throw new Error( 'THREE.PassNode: Depth texture is not available for this pass.' ); + + } + const refTexture = this.renderTarget.texture; texture = refTexture.clone(); @@ -757,7 +775,7 @@ class PassNode extends TempNode { this.renderTarget.texture.type = renderer.getOutputBufferType(); - if ( renderer.reversedDepthBuffer === true ) { + if ( renderer.reversedDepthBuffer === true && this.renderTarget.depthTexture !== null ) { this.renderTarget.depthTexture.type = FloatType; diff --git a/src/renderers/shaders/ShaderChunk/common.glsl.js b/src/renderers/shaders/ShaderChunk/common.glsl.js index 8c452c97bb87c9..771d504a8ef5e3 100644 --- a/src/renderers/shaders/ShaderChunk/common.glsl.js +++ b/src/renderers/shaders/ShaderChunk/common.glsl.js @@ -64,12 +64,21 @@ vec3 transformDirection( in vec3 dir, in mat4 matrix ) { } -vec3 inverseTransformDirection( in vec3 dir, in mat4 matrix ) { +#define inverseTransformDirection transformNormalByInverseViewMatrix // keeping this for backwards compatibility - // dir can be either a direction vector or a normal vector - // upper-left 3x3 of matrix is assumed to be orthogonal +vec3 transformNormalByInverseViewMatrix( in vec3 normal, in mat4 viewMatrix ) { - return normalize( ( vec4( dir, 0.0 ) * matrix ).xyz ); + // upper-left 3x3 of view matrix is assumed to be orthogonal + + return normalize( ( vec4( normal, 0.0 ) * viewMatrix ).xyz ); + +} + +vec3 transformDirectionByInverseViewMatrix( in vec3 dir, in mat4 viewMatrix ) { + + // upper-left 3x3 of view matrix is assumed to be orthogonal + + return normalize( ( vec4( dir, 0.0 ) * viewMatrix ).xyz ); } diff --git a/src/renderers/shaders/ShaderChunk/envmap_fragment.glsl.js b/src/renderers/shaders/ShaderChunk/envmap_fragment.glsl.js index fd753723ac337f..d06111180167fe 100644 --- a/src/renderers/shaders/ShaderChunk/envmap_fragment.glsl.js +++ b/src/renderers/shaders/ShaderChunk/envmap_fragment.glsl.js @@ -16,7 +16,7 @@ export default /* glsl */` } // Transforming Normal Vectors with the Inverse Transformation - vec3 worldNormal = inverseTransformDirection( normal, viewMatrix ); + vec3 worldNormal = transformNormalByInverseViewMatrix( normal, viewMatrix ); #ifdef ENVMAP_MODE_REFLECTION diff --git a/src/renderers/shaders/ShaderChunk/envmap_physical_pars_fragment.glsl.js b/src/renderers/shaders/ShaderChunk/envmap_physical_pars_fragment.glsl.js index 1fb80e4d41c98a..39d62f0f50bfeb 100644 --- a/src/renderers/shaders/ShaderChunk/envmap_physical_pars_fragment.glsl.js +++ b/src/renderers/shaders/ShaderChunk/envmap_physical_pars_fragment.glsl.js @@ -5,7 +5,7 @@ export default /* glsl */` #ifdef ENVMAP_TYPE_CUBE_UV - vec3 worldNormal = inverseTransformDirection( normal, viewMatrix ); + vec3 worldNormal = transformNormalByInverseViewMatrix( normal, viewMatrix ); vec4 envMapColor = textureCubeUV( envMap, envMapRotation * worldNormal, 1.0 ); @@ -28,7 +28,7 @@ export default /* glsl */` // Mixing the reflection with the normal is more accurate and keeps rough objects from gathering light from behind their tangent plane. reflectVec = normalize( mix( reflectVec, normal, pow4( roughness ) ) ); - reflectVec = inverseTransformDirection( reflectVec, viewMatrix ); + reflectVec = transformDirectionByInverseViewMatrix( reflectVec, viewMatrix ); vec4 envMapColor = textureCubeUV( envMap, envMapRotation * reflectVec, roughness ); diff --git a/src/renderers/shaders/ShaderChunk/envmap_vertex.glsl.js b/src/renderers/shaders/ShaderChunk/envmap_vertex.glsl.js index 537c94f1bbde6e..14a9df4d8b2f45 100644 --- a/src/renderers/shaders/ShaderChunk/envmap_vertex.glsl.js +++ b/src/renderers/shaders/ShaderChunk/envmap_vertex.glsl.js @@ -19,7 +19,7 @@ export default /* glsl */` } - vec3 worldNormal = inverseTransformDirection( transformedNormal, viewMatrix ); + vec3 worldNormal = transformNormalByInverseViewMatrix( transformedNormal, viewMatrix ); #ifdef ENVMAP_MODE_REFLECTION diff --git a/src/renderers/shaders/ShaderChunk/lights_pars_begin.glsl.js b/src/renderers/shaders/ShaderChunk/lights_pars_begin.glsl.js index e691f32387d381..a335fc2809df13 100644 --- a/src/renderers/shaders/ShaderChunk/lights_pars_begin.glsl.js +++ b/src/renderers/shaders/ShaderChunk/lights_pars_begin.glsl.js @@ -37,7 +37,7 @@ vec3 shGetIrradianceAt( in vec3 normal, in vec3 shCoefficients[ 9 ] ) { vec3 getLightProbeIrradiance( const in vec3 lightProbe[ 9 ], const in vec3 normal ) { - vec3 worldNormal = inverseTransformDirection( normal, viewMatrix ); + vec3 worldNormal = transformNormalByInverseViewMatrix( normal, viewMatrix ); vec3 irradiance = shGetIrradianceAt( worldNormal, lightProbe ); diff --git a/src/renderers/shaders/ShaderChunk/shadowmap_vertex.glsl.js b/src/renderers/shaders/ShaderChunk/shadowmap_vertex.glsl.js index 081e4812d80807..b0d1a1556d103d 100644 --- a/src/renderers/shaders/ShaderChunk/shadowmap_vertex.glsl.js +++ b/src/renderers/shaders/ShaderChunk/shadowmap_vertex.glsl.js @@ -6,7 +6,7 @@ export default /* glsl */` // Offsetting the position used for querying occlusion along the world normal can be used to reduce shadow acne. - vec3 shadowWorldNormal = inverseTransformDirection( transformedNormal, viewMatrix ); + vec3 shadowWorldNormal = transformNormalByInverseViewMatrix( transformedNormal, viewMatrix ); #else diff --git a/src/renderers/shaders/ShaderChunk/transmission_fragment.glsl.js b/src/renderers/shaders/ShaderChunk/transmission_fragment.glsl.js index 1bbf86c20a3adb..e77c9f8c757743 100644 --- a/src/renderers/shaders/ShaderChunk/transmission_fragment.glsl.js +++ b/src/renderers/shaders/ShaderChunk/transmission_fragment.glsl.js @@ -21,7 +21,7 @@ export default /* glsl */` vec3 pos = vWorldPosition; vec3 v = normalize( cameraPosition - pos ); - vec3 n = inverseTransformDirection( normal, viewMatrix ); + vec3 n = transformNormalByInverseViewMatrix( normal, viewMatrix ); vec4 transmitted = getIBLVolumeRefraction( n, v, material.roughness, material.diffuseContribution, material.specularColorBlended, material.specularF90, diff --git a/test/e2e/puppeteer.js b/test/e2e/puppeteer.js index 5523b4f040d849..1c0388fc3250ac 100644 --- a/test/e2e/puppeteer.js +++ b/test/e2e/puppeteer.js @@ -39,6 +39,7 @@ const exceptionList = [ 'webgpu_storage_buffer', 'webgpu_tsl_editor', 'webxr_vr_video', + 'webgpu_tsl_transpiler', // Need more time to render 'css3d_mixed',