diff --git a/examples/jsm/tsl/display/SSRNode.js b/examples/jsm/tsl/display/SSRNode.js index 4d734f6c3e5c2e..172a05dc144b41 100644 --- a/examples/jsm/tsl/display/SSRNode.js +++ b/examples/jsm/tsl/display/SSRNode.js @@ -1,5 +1,5 @@ import { HalfFloatType, RenderTarget, Vector2, RendererUtils, QuadMesh, TempNode, NodeMaterial, NodeUpdateType, LinearFilter, LinearMipmapLinearFilter } from 'three/webgpu'; -import { texture, reference, viewZToPerspectiveDepth, logarithmicDepthToViewZ, getScreenPosition, getViewPosition, mul, div, cross, float, Break, Loop, int, max, abs, sub, If, dot, reflect, normalize, screenCoordinate, nodeObject, Fn, passTexture, uv, uniform, perspectiveDepthToViewZ, orthographicDepthToViewZ, vec2, vec3, vec4 } from 'three/tsl'; +import { texture, reference, viewZToPerspectiveDepth, logarithmicDepthToViewZ, getScreenPosition, getViewPosition, mul, div, cross, float, Continue, Break, Loop, int, max, abs, sub, If, dot, reflect, normalize, screenCoordinate, nodeObject, Fn, passTexture, uv, uniform, perspectiveDepthToViewZ, orthographicDepthToViewZ, vec2, vec3, vec4 } from 'three/tsl'; import { boxBlur } from './boxBlur.js'; const _quadMesh = /*@__PURE__*/ new QuadMesh(); @@ -262,7 +262,6 @@ class SSRNode extends TempNode { this._copyMaterial = new NodeMaterial(); this._copyMaterial.name = 'SSRNode.Copy'; - /** * The result of the effect is represented as a separate texture node. * @@ -271,13 +270,25 @@ class SSRNode extends TempNode { */ this._textureNode = passTexture( this, this._ssrRenderTarget.texture ); + let blurredTextureNode = null; + + if ( this.roughnessNode !== null ) { + + const mips = this._blurRenderTarget.texture.mipmaps.length - 1; + const r = float( this.roughnessNode ); + const lod = r.mul( r ).mul( mips ).clamp( 0, mips ); + + blurredTextureNode = passTexture( this, this._blurRenderTarget.texture ).level( lod ); + + } + /** * Holds the blurred SSR reflections. * * @private - * @type {?Node} + * @type {?PassTextureNode} */ - this._blurredTextureNode = null; + this._blurredTextureNode = blurredTextureNode; } @@ -389,6 +400,7 @@ class SSRNode extends TempNode { // https://en.wikipedia.org/wiki/Plane_(geometry) // http://paulbourke.net/geometry/pointlineplane/ + // planeNormal is already normalized, so denominator is 1 const d = mul( planeNormal.x, planePoint.x ).add( mul( planeNormal.y, planePoint.y ) ).add( mul( planeNormal.z, planePoint.z ) ).negate().toVar(); const distance = mul( planeNormal.x, point.x ).add( mul( planeNormal.y, point.y ) ).add( mul( planeNormal.z, point.z ) ).add( d ); return distance; @@ -469,6 +481,9 @@ class SSRNode extends TempNode { // below variables are used to control the raymarching process + // total length of the ray + const totalLen = d1.sub( d0 ).length().toVar(); + // offset in x and y direction const xLen = d1.x.sub( d0.x ).toVar(); const yLen = d1.y.sub( d0.y ).toVar(); @@ -476,9 +491,7 @@ class SSRNode extends TempNode { // determine the larger delta // The larger difference will help to determine how much to travel in the X and Y direction each iteration and // how many iterations are needed to travel the entire ray - // scale step count by distance - distant surfaces need less precision (0.5 to 1.0 multiplier) - const distanceFactor = float( 1 ).sub( depth.mul( 0.5 ) ).clamp( 0.5, 1 ); - const totalStep = int( max( abs( xLen ), abs( yLen ) ).mul( this.quality.clamp() ).mul( distanceFactor ) ).toConst(); + const totalStep = int( max( abs( xLen ), abs( yLen ) ).mul( this.quality.clamp() ) ).toConst(); // step sizes in the x and y directions const xSpan = xLen.div( totalStep ).toVar(); @@ -486,14 +499,10 @@ class SSRNode extends TempNode { const output = vec4( 0 ).toVar(); - // incremental interpolation factor - const sStep = float( 1 ).div( float( totalStep ) ); - const s = sStep.toVar(); // start at sStep since loop starts at i=1 - // the actual ray marching loop // starting from d0, the code gradually travels along the ray and looks for an intersection with the geometry. // it does not exceed d1 (the maximum ray extend) - Loop( { start: int( 1 ), end: totalStep }, ( { i } ) => { + Loop( totalStep, ( { i } ) => { // advance on the ray by computing a new position in screen coordinates const xy = vec2( d0.x.add( xSpan.mul( float( i ) ) ), d0.y.add( ySpan.mul( float( i ) ) ) ).toVar(); @@ -512,6 +521,9 @@ class SSRNode extends TempNode { const viewReflectRayZ = float( 0 ).toVar(); + // normalized distance between the current position xy and the starting point d0 + const s = xy.sub( d0 ).length().div( totalLen ); + // depending on the camera type, we now compute the z-coordinate of the reflected ray at the current step in view space If( this._isPerspectiveCamera, () => { @@ -547,9 +559,9 @@ class SSRNode extends TempNode { If( dot( viewReflectDir, vN ).greaterThanEqual( 0 ), () => { - // the reflected ray is hitting a backface (normal pointing away from ray), - // treat as opaque surface that blocks the ray - Break(); + // the reflected ray is pointing towards the same side as the fragment's normal (current ray position), + // which means it wouldn't reflect off the surface. The loop continues to the next step for the next ray sample. + Continue(); } ); @@ -575,19 +587,15 @@ class SSRNode extends TempNode { const fresnelCoe = div( dot( viewIncidentDir, viewReflectDir ).add( 1 ), 2 ); op.mulAssign( fresnelCoe ); - // output: RGB = color * opacity (premultiplied), A = normalized distance + // output const reflectColor = this.colorNode.sample( uvNode ); - const normalizedDistance = distance.div( this.maxDistance ).clamp( 0, 1 ); - output.assign( vec4( reflectColor.rgb.mul( op ), normalizedDistance ) ); + output.assign( vec4( reflectColor.rgb.mul( op ), 1 ) ); Break(); } ); } ); - // advance interpolation factor - s.addAssign( sStep ); - } ); return output; @@ -607,33 +615,6 @@ class SSRNode extends TempNode { this._copyMaterial.fragmentNode = reflectionBuffer; this._copyMaterial.needsUpdate = true; - // distance-aware blur sampling - - if ( this.roughnessNode !== null ) { - - const blurBuffer = texture( this._blurRenderTarget.texture ); - const mips = this._blurRenderTarget.texture.mipmaps.length - 1; - - // sample distance from unblurred SSR alpha and use roughness² * distance for LOD - // roughness² matches GGX microfacet distribution behavior - const distanceAwareSample = Fn( () => { - - // get distance from the unblurred SSR texture's alpha channel - const reflectionDistance = reflectionBuffer.sample( uvNode ).a; - const r = float( this.roughnessNode ); - // squared roughness for more physically accurate falloff (GGX-like) - const lod = r.mul( r ).mul( reflectionDistance ).mul( mips ).clamp( 0, mips ); - const blurred = blurBuffer.sample( uvNode ).level( lod ); - - // output: RGB is premultiplied color, keep alpha as distance for potential further use - return blurred; - - } ); - - this._blurredTextureNode = distanceAwareSample(); - - } - // return this.getTextureNode(); @@ -672,4 +653,4 @@ export default SSRNode; * @param {?Camera} [camera=null] - The camera the scene is rendered with. * @returns {SSRNode} */ -export const ssr = ( colorNode, depthNode, normalNode, metalnessNode, roughnessNode = null, camera = null ) => nodeObject( new SSRNode( nodeObject( colorNode ), nodeObject( depthNode ), nodeObject( normalNode ), nodeObject( metalnessNode ), nodeObject( roughnessNode ), camera ) ); +export const ssr = ( colorNode, depthNode, normalNode, metalnessNode, roughnessNode = null, camera = null ) => nodeObject( new SSRNode( nodeObject( colorNode ), nodeObject( depthNode ), nodeObject( normalNode ), nodeObject( metalnessNode ), nodeObject( roughnessNode ), camera ) ); \ No newline at end of file diff --git a/examples/screenshots/webgpu_postprocessing_ssr.jpg b/examples/screenshots/webgpu_postprocessing_ssr.jpg index 14688a8c3f0272..c6756672594a05 100644 Binary files a/examples/screenshots/webgpu_postprocessing_ssr.jpg and b/examples/screenshots/webgpu_postprocessing_ssr.jpg differ diff --git a/examples/screenshots/webgpu_skinning_points.jpg b/examples/screenshots/webgpu_skinning_points.jpg index 004c541c892e04..62a405c22932c9 100644 Binary files a/examples/screenshots/webgpu_skinning_points.jpg and b/examples/screenshots/webgpu_skinning_points.jpg differ diff --git a/examples/webgpu_compute_reduce.html b/examples/webgpu_compute_reduce.html index 2c61c238dad8ac..cd2038c64698b5 100644 --- a/examples/webgpu_compute_reduce.html +++ b/examples/webgpu_compute_reduce.html @@ -677,7 +677,7 @@

Subgroup Reduction Explanation

const fnDef = Fn( () => { - const inputSize = uint( inputBuffer.bufferCount.length ); + const inputSize = uint( inputBuffer.bufferCount ); const rowOffset = workgroupId.x.mul( rowSize ); // If the current rows elements exceed the bounds of the input diff --git a/examples/webgpu_lights_phong.html b/examples/webgpu_lights_phong.html index 56f71116dfda0a..2184bfb197c1df 100644 --- a/examples/webgpu_lights_phong.html +++ b/examples/webgpu_lights_phong.html @@ -12,11 +12,11 @@
- three.jsPhong + three.jsPhong Material
- Left: Red lights - Center: All lights - Right: blue light + Selective lights with Phong materials. diff --git a/examples/webgpu_skinning_points.html b/examples/webgpu_skinning_points.html index 02c4ad1cd28a07..c756cf23847bab 100644 --- a/examples/webgpu_skinning_points.html +++ b/examples/webgpu_skinning_points.html @@ -85,6 +85,7 @@ materialPoints.opacityNode = shapeCircle(); materialPoints.sizeNode = pointSpeedAttribute.length().exp().min( 5 ).mul( 5 ).add( 1 ); materialPoints.sizeAttenuation = false; + materialPoints.alphaTest = 0.5; const updateSkinningPoints = Fn( () => { diff --git a/src/loaders/Cache.js b/src/loaders/Cache.js index e3a65d0c2f923a..9a8d58effbb0d5 100644 --- a/src/loaders/Cache.js +++ b/src/loaders/Cache.js @@ -35,6 +35,8 @@ const Cache = { if ( this.enabled === false ) return; + if ( isBlobURL( key ) ) return; + // log( 'Cache', 'Adding key:', key ); this.files[ key ] = file; @@ -52,6 +54,8 @@ const Cache = { if ( this.enabled === false ) return; + if ( isBlobURL( key ) ) return; + // log( 'Cache', 'Checking key:', key ); return this.files[ key ]; @@ -83,5 +87,29 @@ const Cache = { }; +/** + * Returns true if the given cache key contains the blob: scheme. + * + * @private + * @param {string} key - The cache key. + * @return {boolean} Whether the given cache key contains the blob: scheme or not. + */ +function isBlobURL( key ) { + + try { + + const urlString = key.slice( key.indexOf( ':' ) + 1 ); // remove type identifier + + const url = new URL( urlString ); + return url.protocol === 'blob:'; + + } catch ( e ) { + + // If the string is not a valid URL, it throws an error + return false; + + } + +} export { Cache }; diff --git a/test/e2e/puppeteer.js b/test/e2e/puppeteer.js index a4c621a1fe1e81..b2aff0101c0114 100644 --- a/test/e2e/puppeteer.js +++ b/test/e2e/puppeteer.js @@ -19,6 +19,7 @@ const exceptionList = [ // Needs investigation 'physics_rapier_instancing', + 'physics_jolt_instancing', 'webgl_shadowmap', 'webgl_postprocessing_dof2', 'webgl_postprocessing_glitch',