Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 30 additions & 49 deletions examples/jsm/tsl/display/SSRNode.js
Original file line number Diff line number Diff line change
@@ -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();
Expand Down Expand Up @@ -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.
*
Expand All @@ -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;

}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -469,31 +481,28 @@ 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();

// 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();
const ySpan = yLen.div( totalStep ).toVar();

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();
Expand All @@ -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, () => {

Expand Down Expand Up @@ -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();

} );

Expand All @@ -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;
Expand All @@ -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();
Expand Down Expand Up @@ -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 ) );
Binary file modified examples/screenshots/webgpu_postprocessing_ssr.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified examples/screenshots/webgpu_skinning_points.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion examples/webgpu_compute_reduce.html
Original file line number Diff line number Diff line change
Expand Up @@ -677,7 +677,7 @@ <h3 id="panel-title" style="flex: 0 0 auto;">Subgroup Reduction Explanation</h3>

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
Expand Down
4 changes: 2 additions & 2 deletions examples/webgpu_lights_phong.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@
<a href="https://threejs.org/" target="_blank" rel="noopener" class="logo-link"></a>

<div class="title-wrapper">
<a href="https://threejs.org/" target="_blank" rel="noopener">three.js</a><span>Phong</span>
<a href="https://threejs.org/" target="_blank" rel="noopener">three.js</a><span>Phong Material</span>
</div>

<small>
<b style="color:red">Left: Red lights</b> - <b>Center: All lights</b> - <b style="color:blue">Right: blue light</b>
Selective lights with Phong materials.
</small>
</div>

Expand Down
1 change: 1 addition & 0 deletions examples/webgpu_skinning_points.html
Original file line number Diff line number Diff line change
Expand Up @@ -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( () => {

Expand Down
28 changes: 28 additions & 0 deletions src/loaders/Cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ const Cache = {

if ( this.enabled === false ) return;

if ( isBlobURL( key ) ) return;

// log( 'Cache', 'Adding key:', key );

this.files[ key ] = file;
Expand All @@ -52,6 +54,8 @@ const Cache = {

if ( this.enabled === false ) return;

if ( isBlobURL( key ) ) return;

// log( 'Cache', 'Checking key:', key );

return this.files[ key ];
Expand Down Expand Up @@ -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 };
1 change: 1 addition & 0 deletions test/e2e/puppeteer.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const exceptionList = [

// Needs investigation
'physics_rapier_instancing',
'physics_jolt_instancing',
'webgl_shadowmap',
'webgl_postprocessing_dof2',
'webgl_postprocessing_glitch',
Expand Down
Loading