|
|
@@ -1,5 +1,6 @@
|
|
|
-import { RenderTarget, Vector2, RendererUtils, QuadMesh, TempNode, NodeMaterial, NodeUpdateType, HalfFloatType } from 'three/webgpu';
|
|
|
-import { reference, viewZToPerspectiveDepth, logarithmicDepthToViewZ, getScreenPosition, getViewPosition, sqrt, 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 { HalfFloatType, RenderTarget, Vector2, RendererUtils, QuadMesh, TempNode, NodeMaterial, NodeUpdateType, LinearFilter, LinearMipmapLinearFilter } from 'three/webgpu';
|
|
|
+import { texture, reference, viewZToPerspectiveDepth, logarithmicDepthToViewZ, getScreenPosition, getViewPosition, sqrt, 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();
|
|
|
const _size = /*@__PURE__*/ new Vector2();
|
|
|
@@ -27,10 +28,11 @@ class SSRNode extends TempNode {
|
|
|
* @param {Node<vec4>} colorNode - The node that represents the beauty pass.
|
|
|
* @param {Node<float>} depthNode - A node that represents the beauty pass's depth.
|
|
|
* @param {Node<vec3>} normalNode - A node that represents the beauty pass's normals.
|
|
|
- * @param {Node<float>} metalnessNode - A node that represents the beauty pass's metalness.
|
|
|
+ * @param {Node<float>} metalnessRoughnessNode - A node that represents the beauty pass's metalness and roughness.
|
|
|
* @param {Camera} camera - The camera the scene is rendered with.
|
|
|
+ * @param {boolean} [blurred=false] - Whether the SSR reflections should be blurred or not.
|
|
|
*/
|
|
|
- constructor( colorNode, depthNode, normalNode, metalnessNode, camera ) {
|
|
|
+ constructor( colorNode, depthNode, normalNode, metalnessRoughnessNode, camera, blurred = false ) {
|
|
|
|
|
|
super( 'vec4' );
|
|
|
|
|
|
@@ -60,7 +62,7 @@ class SSRNode extends TempNode {
|
|
|
*
|
|
|
* @type {Node<float>}
|
|
|
*/
|
|
|
- this.metalnessNode = metalnessNode;
|
|
|
+ this.metalnessRoughnessNode = metalnessRoughnessNode;
|
|
|
|
|
|
/**
|
|
|
* The camera the scene is rendered with.
|
|
|
@@ -89,15 +91,6 @@ class SSRNode extends TempNode {
|
|
|
*/
|
|
|
this.updateBeforeType = NodeUpdateType.FRAME;
|
|
|
|
|
|
- /**
|
|
|
- * The render target the SSR is rendered into.
|
|
|
- *
|
|
|
- * @private
|
|
|
- * @type {RenderTarget}
|
|
|
- */
|
|
|
- this._ssrRenderTarget = new RenderTarget( 1, 1, { depthBuffer: false, type: HalfFloatType } );
|
|
|
- this._ssrRenderTarget.texture.name = 'SSRNode.SSR';
|
|
|
-
|
|
|
/**
|
|
|
* Controls how far a fragment can reflect. Increasing this value result in more
|
|
|
* computational overhead but also increases the reflection distance.
|
|
|
@@ -133,6 +126,32 @@ class SSRNode extends TempNode {
|
|
|
*/
|
|
|
this.quality = uniform( 0.5 );
|
|
|
|
|
|
+ /**
|
|
|
+ * The quality of the blur. Must be an integer in the range `[1,3]`.
|
|
|
+ *
|
|
|
+ * @type {UniformNode<int>}
|
|
|
+ */
|
|
|
+ this.blurQuality = uniform( 2 );
|
|
|
+
|
|
|
+ /**
|
|
|
+ * The spread of the blur. Automatically set when generating mips.
|
|
|
+ *
|
|
|
+ * @private
|
|
|
+ * @type {UniformNode<int>}
|
|
|
+ */
|
|
|
+ this._blurSpread = uniform( 1 );
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Whether the SSR reflections should be blurred or not. Blurring is a costly
|
|
|
+ * operation so turn it off if you encounter performance issues on certain
|
|
|
+ * devices.
|
|
|
+ *
|
|
|
+ * @private
|
|
|
+ * @type {boolean}
|
|
|
+ * @default false
|
|
|
+ */
|
|
|
+ this._blurred = blurred;
|
|
|
+
|
|
|
/**
|
|
|
* Represents the projection matrix of the scene's camera.
|
|
|
*
|
|
|
@@ -181,14 +200,51 @@ class SSRNode extends TempNode {
|
|
|
*/
|
|
|
this._resolution = uniform( new Vector2() );
|
|
|
|
|
|
+ /**
|
|
|
+ * The render target the SSR is rendered into.
|
|
|
+ *
|
|
|
+ * @private
|
|
|
+ * @type {RenderTarget}
|
|
|
+ */
|
|
|
+ this._ssrRenderTarget = new RenderTarget( 1, 1, { depthBuffer: false, type: HalfFloatType } );
|
|
|
+ this._ssrRenderTarget.texture.name = 'SSRNode.SSR';
|
|
|
+
|
|
|
+ /**
|
|
|
+ * The render target for the blurred SSR reflections.
|
|
|
+ *
|
|
|
+ * @private
|
|
|
+ * @type {RenderTarget}
|
|
|
+ */
|
|
|
+ this._blurRenderTarget = new RenderTarget( 1, 1, { depthBuffer: false, type: HalfFloatType, minFilter: LinearMipmapLinearFilter, magFilter: LinearFilter } );
|
|
|
+ this._blurRenderTarget.texture.name = 'SSRNode.Blur';
|
|
|
+ this._blurRenderTarget.texture.mipmaps.push( {}, {}, {}, {}, {} );
|
|
|
+
|
|
|
/**
|
|
|
* The material that is used to render the effect.
|
|
|
*
|
|
|
* @private
|
|
|
* @type {NodeMaterial}
|
|
|
*/
|
|
|
- this._material = new NodeMaterial();
|
|
|
- this._material.name = 'SSRNode.SSR';
|
|
|
+ this._ssrMaterial = new NodeMaterial();
|
|
|
+ this._ssrMaterial.name = 'SSRNode.SSR';
|
|
|
+
|
|
|
+ /**
|
|
|
+ * The blur material.
|
|
|
+ *
|
|
|
+ * @private
|
|
|
+ * @type {NodeMaterial}
|
|
|
+ */
|
|
|
+ this._blurMaterial = new NodeMaterial();
|
|
|
+ this._blurMaterial.name = 'SSRNode.Blur';
|
|
|
+
|
|
|
+ /**
|
|
|
+ * The copy material.
|
|
|
+ *
|
|
|
+ * @private
|
|
|
+ * @type {NodeMaterial}
|
|
|
+ */
|
|
|
+ this._copyMaterial = new NodeMaterial();
|
|
|
+ this._copyMaterial.name = 'SSRNode.Copy';
|
|
|
|
|
|
/**
|
|
|
* The result of the effect is represented as a separate texture node.
|
|
|
@@ -198,6 +254,17 @@ class SSRNode extends TempNode {
|
|
|
*/
|
|
|
this._textureNode = passTexture( this, this._ssrRenderTarget.texture );
|
|
|
|
|
|
+ const mips = this._blurRenderTarget.texture.mipmaps.length - 1;
|
|
|
+ const lod = this.metalnessRoughnessNode.g.mul( mips ).clamp( 0, mips );
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Holds the blurred SSR reflections.
|
|
|
+ *
|
|
|
+ * @private
|
|
|
+ * @type {PassTextureNode}
|
|
|
+ */
|
|
|
+ this._blurredTextureNode = passTexture( this, this._blurRenderTarget.texture ).level( lod );
|
|
|
+
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -207,7 +274,7 @@ class SSRNode extends TempNode {
|
|
|
*/
|
|
|
getTextureNode() {
|
|
|
|
|
|
- return this._textureNode;
|
|
|
+ return this._blurred ? this._blurredTextureNode : this._textureNode;
|
|
|
|
|
|
}
|
|
|
|
|
|
@@ -224,6 +291,7 @@ class SSRNode extends TempNode {
|
|
|
|
|
|
this._resolution.value.set( width, height );
|
|
|
this._ssrRenderTarget.setSize( width, height );
|
|
|
+ this._blurRenderTarget.setSize( width, height );
|
|
|
|
|
|
}
|
|
|
|
|
|
@@ -238,9 +306,12 @@ class SSRNode extends TempNode {
|
|
|
|
|
|
_rendererState = RendererUtils.resetRendererState( renderer, _rendererState );
|
|
|
|
|
|
+ const ssrRenderTarget = this._ssrRenderTarget;
|
|
|
+ const blurRenderTarget = this._blurRenderTarget;
|
|
|
+
|
|
|
const size = renderer.getDrawingBufferSize( _size );
|
|
|
|
|
|
- _quadMesh.material = this._material;
|
|
|
+ _quadMesh.material = this._ssrMaterial;
|
|
|
|
|
|
this.setSize( size.width, size.height );
|
|
|
|
|
|
@@ -251,9 +322,27 @@ class SSRNode extends TempNode {
|
|
|
|
|
|
// ssr
|
|
|
|
|
|
- renderer.setRenderTarget( this._ssrRenderTarget );
|
|
|
+ renderer.setRenderTarget( ssrRenderTarget );
|
|
|
_quadMesh.render( renderer );
|
|
|
|
|
|
+ // blur (optional)
|
|
|
+
|
|
|
+ if ( this._blurred === true ) {
|
|
|
+
|
|
|
+ // blur mips but leave the base mip unblurred
|
|
|
+
|
|
|
+ for ( let i = 0; i < blurRenderTarget.texture.mipmaps.length; i ++ ) {
|
|
|
+
|
|
|
+ _quadMesh.material = ( i === 0 ) ? this._copyMaterial : this._blurMaterial;
|
|
|
+
|
|
|
+ this._blurSpread.value = i;
|
|
|
+ renderer.setRenderTarget( blurRenderTarget, 0, i );
|
|
|
+ _quadMesh.render( renderer );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
// restore
|
|
|
|
|
|
RendererUtils.restoreRendererState( renderer, _rendererState );
|
|
|
@@ -328,7 +417,7 @@ class SSRNode extends TempNode {
|
|
|
|
|
|
const ssr = Fn( () => {
|
|
|
|
|
|
- const metalness = this.metalnessNode.sample( uvNode ).r;
|
|
|
+ const metalness = this.metalnessRoughnessNode.sample( uvNode ).r;
|
|
|
|
|
|
// fragments with no metalness do not reflect their environment
|
|
|
metalness.equal( 0.0 ).discard();
|
|
|
@@ -487,12 +576,22 @@ class SSRNode extends TempNode {
|
|
|
|
|
|
} );
|
|
|
|
|
|
- this._material.fragmentNode = ssr().context( builder.getSharedContext() );
|
|
|
- this._material.needsUpdate = true;
|
|
|
+ this._ssrMaterial.fragmentNode = ssr().context( builder.getSharedContext() );
|
|
|
+ this._ssrMaterial.needsUpdate = true;
|
|
|
+
|
|
|
+ // below materials are used for blurring
|
|
|
+
|
|
|
+ const reflectionBuffer = texture( this._ssrRenderTarget.texture );
|
|
|
+
|
|
|
+ this._blurMaterial.fragmentNode = boxBlur( reflectionBuffer, { size: this.blurQuality, separation: this._blurSpread } );
|
|
|
+ this._blurMaterial.needsUpdate = true;
|
|
|
+
|
|
|
+ this._copyMaterial.fragmentNode = reflectionBuffer;
|
|
|
+ this._copyMaterial.needsUpdate = true;
|
|
|
|
|
|
//
|
|
|
|
|
|
- return this._textureNode;
|
|
|
+ return this.getTextureNode();
|
|
|
|
|
|
}
|
|
|
|
|
|
@@ -503,8 +602,11 @@ class SSRNode extends TempNode {
|
|
|
dispose() {
|
|
|
|
|
|
this._ssrRenderTarget.dispose();
|
|
|
+ this._blurRenderTarget.dispose();
|
|
|
|
|
|
- this._material.dispose();
|
|
|
+ this._ssrMaterial.dispose();
|
|
|
+ this._blurMaterial.dispose();
|
|
|
+ this._copyMaterial.dispose();
|
|
|
|
|
|
}
|
|
|
|
|
|
@@ -520,8 +622,9 @@ export default SSRNode;
|
|
|
* @param {Node<vec4>} colorNode - The node that represents the beauty pass.
|
|
|
* @param {Node<float>} depthNode - A node that represents the beauty pass's depth.
|
|
|
* @param {Node<vec3>} normalNode - A node that represents the beauty pass's normals.
|
|
|
- * @param {Node<float>} metalnessNode - A node that represents the beauty pass's metalness.
|
|
|
+ * @param {Node<float>} metalnessRoughnessNode - A node that represents the beauty pass's metalness and roughness.
|
|
|
* @param {Camera} camera - The camera the scene is rendered with.
|
|
|
+ * @param {boolean} [blurred=false] - Whether the SSR reflections should be blurred or not.
|
|
|
* @returns {SSRNode}
|
|
|
*/
|
|
|
-export const ssr = ( colorNode, depthNode, normalNode, metalnessNode, camera ) => nodeObject( new SSRNode( nodeObject( colorNode ), nodeObject( depthNode ), nodeObject( normalNode ), nodeObject( metalnessNode ), camera ) );
|
|
|
+export const ssr = ( colorNode, depthNode, normalNode, metalnessRoughnessNode, camera, blurred ) => nodeObject( new SSRNode( nodeObject( colorNode ), nodeObject( depthNode ), nodeObject( normalNode ), nodeObject( metalnessRoughnessNode ), camera, blurred ) );
|