فهرست منبع

TSL: Introduce `.toInspector()`, `.before()` and node `Viewer` for `Inspector` (#31928)

* CanvasTarget: Remove `.samples` ( move to Renderer )

* Node: Introduce `before()`

* TSL: Introduce `.toInspector()`

* ShadowNode: Add `.toInspector()`

* Inspector: Add `Viewer` for nodes

* Update examples

* cleanup

* add inspector for backdrop water example

* updates

* update

* cleanup
sunag 5 ماه پیش
والد
کامیت
48328154c9

+ 68 - 4
examples/jsm/inspector/Inspector.js

@@ -4,9 +4,11 @@ import { Profiler } from './ui/Profiler.js';
 import { Performance } from './tabs/Performance.js';
 import { Console } from './tabs/Console.js';
 import { Parameters } from './tabs/Parameters.js';
-import { setText, ease } from './ui/utils.js';
+import { Viewer } from './tabs/Viewer.js';
+import { setText, ease, splitPath, splitCamelCase } from './ui/utils.js';
 
-import { setConsoleFunction, REVISION } from 'three/webgpu';
+import { QuadMesh, NodeMaterial, CanvasTarget, setConsoleFunction, REVISION, NoToneMapping } from 'three/webgpu';
+import { renderOutput, vec3, vec4 } from 'three/tsl';
 
 const EASE_FACTOR = 0.1;
 
@@ -24,6 +26,9 @@ class Inspector extends RendererInspector {
 		parameters.hide();
 		profiler.addTab( parameters );
 
+		const viewer = new Viewer();
+		profiler.addTab( viewer );
+
 		const performance = new Performance();
 		profiler.addTab( performance );
 
@@ -38,10 +43,12 @@ class Inspector extends RendererInspector {
 		this.softDeltaTime = 0;
 
 		this.statsData = new Map();
+		this.canvasNodes = new Map();
 		this.profiler = profiler;
 		this.performance = performance;
 		this.console = console;
 		this.parameters = parameters;
+		this.viewer = viewer;
 		this.once = {};
 
 		this.displayCycle = {
@@ -247,6 +254,63 @@ class Inspector extends RendererInspector {
 
 	}
 
+	getCanvasDataByNode( node ) {
+
+		let canvasData = this.canvasNodes.get( node );
+
+		if ( canvasData === undefined ) {
+
+			const renderer = this.getRenderer();
+
+			const canvas = document.createElement( 'canvas' );
+
+			const canvasTarget = new CanvasTarget( canvas );
+			canvasTarget.setPixelRatio( window.devicePixelRatio );
+			canvasTarget.setSize( 140, 140 );
+
+			const id = node.id;
+
+			const { path, name } = splitPath( splitCamelCase( node.getName() || '(unnamed)' ) );
+
+			let output = vec4( vec3( node ), 1 );
+			output = renderOutput( output, NoToneMapping, renderer.outputColorSpace );
+			output = output.context( { inspector: true } );
+
+			const material = new NodeMaterial();
+			material.outputNode = output;
+
+			const quad = new QuadMesh( material );
+			quad.name = 'Inspector - ' + name;
+
+			canvasData = {
+				id,
+				name,
+				path,
+				node,
+				quad,
+				canvasTarget,
+				material
+			};
+
+			this.canvasNodes.set( node, canvasData );
+
+		}
+
+		return canvasData;
+
+	}
+
+	resolveViewer() {
+
+		const nodes = this.currentNodes;
+
+		const renderer = this.getRenderer();
+		const canvasDataList = nodes.map( node => this.getCanvasDataByNode( node ) );
+
+		this.viewer.update( renderer, canvasDataList );
+
+	}
+
 	resolveFrame( frame ) {
 
 		const nextFrame = this.getFrameById( frame.frameId + 1 );
@@ -278,7 +342,7 @@ class Inspector extends RendererInspector {
 
 			// Frame desync, probably due to async GPU timing.
 
-			return;
+			frame.miscellaneous = 0;
 
 		}
 
@@ -298,7 +362,7 @@ class Inspector extends RendererInspector {
 
 		if ( this.displayCycle.text.needsUpdate ) {
 
-			setText( 'fps-counter', this.fps.toFixed() );
+			setText( 'fps-counter', this.softFPS.toFixed() );
 
 			this.performance.updateText( this, frame );
 

+ 12 - 0
examples/jsm/inspector/RendererInspector.js

@@ -73,6 +73,7 @@ export class RendererInspector extends InspectorBase {
 
 		this.currentFrame = null;
 		this.currentRender = null;
+		this.currentNodes = null;
 
 		this.frames = [];
 		this.framesLib = {};
@@ -89,6 +90,7 @@ export class RendererInspector extends InspectorBase {
 
 		this.currentFrame = this._createFrame();
 		this.currentRender = this.currentFrame;
+		this.currentNodes = [];
 
 	}
 
@@ -104,6 +106,7 @@ export class RendererInspector extends InspectorBase {
 
 		this.currentFrame = null;
 		this.currentRender = null;
+		this.currentNodes = null;
 
 		this._lastFinishTime = now;
 
@@ -138,6 +141,8 @@ export class RendererInspector extends InspectorBase {
 
 	}
 
+	resolveViewer() { }
+
 	resolveFrame( /*frame*/ ) { }
 
 	async resolveTimestamp() {
@@ -266,12 +271,19 @@ export class RendererInspector extends InspectorBase {
 
 		if ( this.isAvailable ) {
 
+			this.resolveViewer();
 			this.resolveTimestamp();
 
 		}
 
 	}
 
+	inspect( node ) {
+
+		this.currentNodes.push( node );
+
+	}
+
 	beginCompute( uid, computeNode ) {
 
 		const frame = this.getFrame();

+ 3 - 2
examples/jsm/inspector/tabs/Performance.js

@@ -57,7 +57,7 @@ class Performance extends Tab {
 		const frameStats = new Item( 'Frame Stats', createValueSpan(), createValueSpan(), createValueSpan() );
 		perfList.add( frameStats );
 
-		const miscellaneous = new Item( 'Miscellaneous / Idle', createValueSpan(), createValueSpan(), createValueSpan() );
+		const miscellaneous = new Item( 'Miscellaneous & Idle', createValueSpan(), createValueSpan(), createValueSpan() );
 		miscellaneous.domElement.firstChild.style.backgroundColor = '#00ff0b1a';
 		miscellaneous.domElement.firstChild.classList.add( 'no-hover' );
 		frameStats.add( miscellaneous );
@@ -235,7 +235,7 @@ class Performance extends Tab {
 
 		//
 
-		setText( 'graph-fps-counter', inspector.fps.toFixed() + ' FPS' );
+		setText( 'graph-fps-counter', inspector.softFPS.toFixed() + ' FPS' );
 
 		//
 
@@ -248,6 +248,7 @@ class Performance extends Tab {
 		setText( this.miscellaneous.data[ 1 ], frame.miscellaneous.toFixed( 2 ) );
 		setText( this.miscellaneous.data[ 2 ], '-' );
 		setText( this.miscellaneous.data[ 3 ], frame.miscellaneous.toFixed( 2 ) );
+
 		//
 
 		this.currentItem = null;

+ 166 - 0
examples/jsm/inspector/tabs/Viewer.js

@@ -0,0 +1,166 @@
+import { Tab } from '../ui/Tab.js';
+import { List } from '../ui/List.js';
+import { Item } from '../ui/Item.js';
+
+import { RendererUtils, NoToneMapping, LinearSRGBColorSpace } from 'three/webgpu';
+
+class Viewer extends Tab {
+
+	constructor() {
+
+		super( 'Viewer' );
+
+		const nodeList = new List( 'Viewer', 'Name' );
+		nodeList.setGridStyle( '150px minmax(200px, 2fr)' );
+		nodeList.domElement.style.minWidth = '600px';
+
+		const scrollWrapper = document.createElement( 'div' );
+		scrollWrapper.className = 'list-scroll-wrapper';
+		scrollWrapper.appendChild( nodeList.domElement );
+		this.content.appendChild( scrollWrapper );
+
+		const nodes = new Item( 'Nodes' );
+		nodeList.add( nodes );
+
+		//
+
+		this.itemLibrary = new Map();
+		this.folderLibrary = new Map();
+		this.currentDataList = [];
+		this.nodeList = nodeList;
+		this.nodes = nodes;
+
+	}
+
+	getFolder( name ) {
+
+		let folder = this.folderLibrary.get( name );
+
+		if ( folder === undefined ) {
+
+			folder = new Item( name );
+
+			this.folderLibrary.set( name, folder );
+			this.nodeList.add( folder );
+
+		}
+
+		return folder;
+
+	}
+
+	addNodeItem( canvasData ) {
+
+		let item = this.itemLibrary.get( canvasData.id );
+
+		if ( item === undefined ) {
+
+			const name = canvasData.name;
+			const domElement = canvasData.canvasTarget.domElement;
+
+			item = new Item( domElement, name );
+			item.itemRow.children[ 1 ].style[ 'justify-content' ] = 'flex-start';
+			this.itemLibrary.set( canvasData.id, item );
+
+		}
+
+		return item;
+
+	}
+
+	update( renderer, canvasDataList ) {
+
+		if ( ! this.isActive ) return;
+
+		//
+
+		const previousDataList = [ ...this.currentDataList ];
+
+		// remove old
+
+		for ( const canvasData of previousDataList ) {
+
+			if ( this.itemLibrary.has( canvasData.id ) && canvasDataList.indexOf( canvasData ) === - 1 ) {
+
+				const item = this.itemLibrary.get( canvasData.id );
+				const parent = item.parent;
+
+				parent.remove( item );
+
+				if ( this.folderLibrary.has( parent.data[ 0 ] ) && parent.children.length === 0 ) {
+
+					parent.parent.remove( parent );
+
+					this.folderLibrary.delete( parent.data[ 0 ] );
+
+				}
+
+				this.itemLibrary.delete( canvasData.id );
+
+			}
+
+		}
+
+		//
+
+		const indexes = {};
+
+		for ( const canvasData of canvasDataList ) {
+
+			const item = this.addNodeItem( canvasData );
+			const previousCanvasTarget = renderer.getCanvasTarget();
+
+			const path = canvasData.path;
+
+			if ( path ) {
+
+				const folder = this.getFolder( path );
+
+				if ( indexes[ path ] === undefined ) {
+
+					indexes[ path ] = 0;
+
+				}
+
+				if ( folder.parent === null || item.parent !== folder || folder.children.indexOf( item ) !== indexes[ path ] ) {
+
+					folder.add( item );
+
+				}
+
+				indexes[ path ] ++;
+
+			} else {
+
+				if ( ! item.parent ) {
+
+					this.nodes.add( item );
+
+				}
+
+			}
+
+			this.currentDataList = canvasDataList;
+
+			//
+
+			const state = RendererUtils.resetRendererState( renderer );
+
+			renderer.toneMapping = NoToneMapping;
+			renderer.outputColorSpace = LinearSRGBColorSpace;
+
+			renderer.setCanvasTarget( canvasData.canvasTarget );
+
+			canvasData.quad.render( renderer );
+
+			renderer.setCanvasTarget( previousCanvasTarget );
+
+			RendererUtils.restoreRendererState( renderer, state );
+
+		}
+
+	}
+
+}
+
+export { Viewer };

+ 1 - 1
examples/jsm/inspector/ui/Style.js

@@ -348,7 +348,7 @@ export class Style {
 
 .item-toggler {
 	display: inline-block;
-	width: 1.5em;
+	margin-right: 0.8em;
 	text-align: left;
 }
 

+ 3 - 0
examples/jsm/inspector/ui/Tab.js

@@ -11,6 +11,7 @@ export class Tab {
 		this.content.id = `${this.id}-content`;
 		this.content.className = 'profiler-content';
 
+		this.isActive = false;
 		this.isVisible = true;
 
 	}
@@ -20,6 +21,8 @@ export class Tab {
 		this.button.classList.toggle( 'active', isActive );
 		this.content.classList.toggle( 'active', isActive );
 
+		this.isActive = isActive;
+
 	}
 
 	show() {

+ 26 - 0
examples/jsm/inspector/ui/utils.js

@@ -40,3 +40,29 @@ export function getText( element ) {
 	return el ? el.textContent : null;
 
 }
+
+export function splitPath( fullPath ) {
+
+	const lastSlash = fullPath.lastIndexOf( '/' );
+
+	if ( lastSlash === - 1 ) {
+
+		return {
+			path: '',
+			name: fullPath.trim()
+		};
+
+	}
+
+	const path = fullPath.substring( 0, lastSlash ).trim();
+	const name = fullPath.substring( lastSlash + 1 ).trim();
+
+	return { path, name };
+
+}
+
+export function splitCamelCase( str ) {
+
+	return str.replace( /([a-z0-9])([A-Z])/g, '$1 $2' ).trim();
+
+}

+ 7 - 7
examples/webgpu_backdrop_water.html

@@ -150,10 +150,10 @@
 
 				// linearDepth() returns the linear depth of the mesh
 				const depth = linearDepth();
-				const depthWater = viewportLinearDepth.sub( depth );
+				const depthWater = viewportLinearDepth.sub( depth ).toInspector( 'Water / Depth', ( node ) => node.oneMinus() );
 				const depthEffect = depthWater.remapClamp( - .002, .04 );
 
-				const refractionUV = screenUV.add( vec2( 0, waterIntensity.mul( .1 ) ) );
+				const refractionUV = screenUV.add( vec2( 0, waterIntensity.mul( .1 ) ) ).toInspector( 'Water / Refraction UV' );
 
 				// linearDepth( viewportDepthTexture( uv ) ) return the linear depth of the scene
 				const depthTestForRefraction = linearDepth( viewportDepthTexture( refractionUV ) ).sub( depth );
@@ -162,10 +162,10 @@
 
 				const finalUV = depthTestForRefraction.lessThan( 0 ).select( screenUV, refractionUV );
 
-				const viewportTexture = viewportSharedTexture( finalUV );
+				const viewportTexture = viewportSharedTexture( finalUV ).toInspector( 'Water / Viewport Texture + Refraction UV' );
 
 				const waterMaterial = new THREE.MeshBasicNodeMaterial();
-				waterMaterial.colorNode = waterColor;
+				waterMaterial.colorNode = waterColor.toInspector( 'Water / Color' );
 				waterMaterial.backdropNode = depthEffect.mix( viewportSharedTexture(), viewportTexture.mul( depthRefraction.mix( 1, waterColor ) ) );
 				waterMaterial.backdropAlphaNode = depthRefraction.oneMinus();
 				waterMaterial.transparent = true;
@@ -224,12 +224,12 @@
 				const scenePassColor = scenePass.getTextureNode();
 				const scenePassDepth = scenePass.getLinearDepthNode().remapClamp( .3, .5 );
 
-				const waterMask = objectPosition( camera ).y.greaterThan( screenUV.y.sub( .5 ).mul( camera.near ) );
+				const waterMask = objectPosition( camera ).y.greaterThan( screenUV.y.sub( .5 ).mul( camera.near ) ).toInspector( 'Post-Processing / Water Mask' );
 
 				const scenePassColorBlurred = gaussianBlur( scenePassColor );
-				scenePassColorBlurred.directionNode = waterMask.select( scenePassDepth, scenePass.getLinearDepthNode().mul( 5 ) );
+				scenePassColorBlurred.directionNode = waterMask.select( scenePassDepth, scenePass.getLinearDepthNode().mul( 5 ) ).toInspector( 'Post-Processing / Blur Strength [ Depth ]', ( node ) => node.toFloat() );
 
-				const vignette = screenUV.distance( .5 ).mul( 1.35 ).clamp().oneMinus();
+				const vignette = screenUV.distance( .5 ).mul( 1.35 ).clamp().oneMinus().toInspector( 'Post-Processing / Vignette' );
 
 				postProcessing = new THREE.PostProcessing( renderer );
 				postProcessing.outputNode = waterMask.select( scenePassColorBlurred, scenePassColorBlurred.mul( color( 0x74ccf4 ) ).mul( vignette ) );

+ 12 - 9
examples/webgpu_postprocessing_ssgi.html

@@ -57,10 +57,8 @@
 				renderer.setSize( window.innerWidth, window.innerHeight );
 				renderer.setAnimationLoop( animate );
 				renderer.shadowMap.enabled = true;
-				document.body.appendChild( renderer.domElement );
-
 				renderer.inspector = new Inspector();
-				document.body.appendChild( renderer.inspector.domElement );
+				document.body.appendChild( renderer.domElement );
 
 				//
 
@@ -84,10 +82,15 @@
 				} ) );
 
 				const scenePassColor = scenePass.getTextureNode( 'output' );
-				const scenePassDiffuse = scenePass.getTextureNode( 'diffuseColor' );
-				const scenePassDepth = scenePass.getTextureNode( 'depth' );
-				const scenePassNormal = scenePass.getTextureNode( 'normal' );
-				const scenePassVelocity = scenePass.getTextureNode( 'velocity' );
+				const scenePassDiffuse = scenePass.getTextureNode( 'diffuseColor' ).toInspector( 'Diffuse Color' );
+				const scenePassDepth = scenePass.getTextureNode( 'depth' ).toInspector( 'Depth', () => {
+
+					return scenePass.getLinearDepthNode();
+
+				} );
+
+				const scenePassNormal = scenePass.getTextureNode( 'normal' ).toInspector( 'Normal' );
+				const scenePassVelocity = scenePass.getTextureNode( 'velocity' ).toInspector( 'Velocity' );
 
 				// bandwidth optimization
 
@@ -111,8 +114,8 @@
 
 				// composite
 
-				const gi = giPass.rgb;
-				const ao = giPass.a;
+				const gi = giPass.rgb.toInspector( 'SSGI' );
+				const ao = giPass.a.toInspector( 'AO' );
 
 				const compositePass = vec4( add( scenePassColor.rgb.mul( ao ), ( scenePassDiffuse.rgb.mul( gi ) ) ), scenePassColor.a );
 				compositePass.name = 'Composite';

+ 7 - 3
examples/webgpu_volume_caustics.html

@@ -270,8 +270,11 @@
 
 				// Scene Pass
 
-				const scenePass = pass( scene, camera );
+				const scenePass = pass( scene, camera ).toInspector();
+				scenePass.name = 'Scene';
+
 				const sceneDepth = scenePass.getTextureNode( 'depth' );
+				sceneDepth.name = 'Scene Depth';
 
 				// Material - Apply occlusion depth of volumetric lighting based on the scene depth
 
@@ -279,14 +282,15 @@
 
 				// Volumetric Lighting Pass
 
-				const volumetricPass = pass( scene, camera, { depthBuffer: false } );
+				const volumetricPass = pass( scene, camera, { depthBuffer: false, samples: 0 } ).toInspector( 'Volumetric Lighting / Raw' );
 				volumetricPass.name = 'Volumetric Lighting';
 				volumetricPass.setLayers( volumetricLayer );
 				volumetricPass.setResolutionScale( .5 );
 
 				// Compose and Denoise
 
-				const bloomPass = bloom( volumetricPass, 1, 1, 0 );
+				const bloomPass = bloom( volumetricPass, 1, 1, 0 ).toInspector( 'Volumetric Lighting / Mip-Chain Gaussian Blur' );
+				bloomPass.name = 'Bloom';
 
 				const scenePassColor = scenePass.add( bloomPass.mul( volumetricLightingIntensity ) );
 

+ 120 - 0
src/nodes/core/InspectorNode.js

@@ -0,0 +1,120 @@
+import Node from './Node.js';
+import { addMethodChaining, nodeObject } from '../tsl/TSLCore.js';
+import { NodeUpdateType } from './constants.js';
+
+/**
+ * InspectorNode is a wrapper node that allows inspection of node values during rendering.
+ * It can be used to debug or analyze node outputs in the rendering pipeline.
+ *
+ * @augments Node
+ */
+class InspectorNode extends Node {
+
+	/**
+	 * Returns the type of the node.
+	 *
+	 * @returns {string}
+	 */
+	static get type() {
+
+		return 'InspectorNode';
+
+	}
+
+	/**
+	 * Creates an InspectorNode.
+	 *
+	 * @param {Node} node - The node to inspect.
+	 * @param {string} [name=''] - Optional name for the inspector node.
+	 * @param {Function|null} [callback=null] - Optional callback to modify the node during setup.
+	 */
+	constructor( node, name = '', callback = null ) {
+
+		super();
+
+		this.node = node;
+		this.name = name;
+		this.callback = callback;
+
+		this.updateType = NodeUpdateType.FRAME;
+
+		this.isInspectorNode = true;
+
+	}
+
+	/**
+	 * Returns the name of the inspector node.
+	 *
+	 * @returns {string}
+	 */
+	getName() {
+
+		return this.name || this.node.name;
+
+	}
+
+	/**
+	 * Updates the inspector node, allowing inspection of the wrapped node.
+	 *
+	 * @param {NodeFrame} frame - A reference to the current node frame.
+	 */
+	update( frame ) {
+
+		frame.renderer.inspector.inspect( this );
+
+	}
+
+	/**
+	 * Returns the type of the wrapped node.
+	 *
+	 * @param {NodeBuilder} builder - The node builder.
+	 * @returns {string}
+	 */
+	getNodeType( builder ) {
+
+		return this.node.getNodeType( builder );
+
+	}
+
+	/**
+	 * Sets up the inspector node.
+	 *
+	 * @param {NodeBuilder} builder - The node builder.
+	 * @returns {Node} The setup node.
+	 */
+	setup( builder ) {
+
+		let node = this.node;
+
+		if ( builder.context.inspector === true && this.callback !== null ) {
+
+			node = this.callback( node );
+
+		}
+
+		return node;
+
+	}
+
+}
+
+export default InspectorNode;
+
+/**
+ * Creates an inspector node to wrap around a given node for inspection purposes.
+ *
+ * @tsl
+ * @param {Node} node - The node to inspect.
+ * @param {string} [name=''] - Optional name for the inspector node.
+ * @param {Function|null} [callback=null] - Optional callback to modify the node during setup.
+ * @returns {Node} The inspector node.
+ */
+export function inspector( node, name = '', callback = null ) {
+
+	node = nodeObject( node );
+
+	return node.before( new InspectorNode( node, name, callback ) );
+
+}
+
+addMethodChaining( 'toInspector', inspector );

+ 30 - 0
src/nodes/core/Node.js

@@ -120,6 +120,8 @@ class Node extends EventDispatcher {
 
 		// private
 
+		this._beforeNodes = null;
+
 		/**
 		 * The cache key of this node.
 		 *
@@ -615,6 +617,16 @@ class Node extends EventDispatcher {
 
 	}
 
+	before( node ) {
+
+		if ( this._beforeNodes === null ) this._beforeNodes = [];
+
+		this._beforeNodes.push( node );
+
+		return this;
+
+	}
+
 	/**
 	 * This method performs the build of a node. The behavior and return value depend on the current build stage:
 	 * - **setup**: Prepares the node and its children for the build process. This process can also create new nodes. Returns the node itself or a variant.
@@ -637,6 +649,24 @@ class Node extends EventDispatcher {
 
 		//
 
+		if ( this._beforeNodes !== null ) {
+
+			const currentBeforeNodes = this._beforeNodes;
+
+			this._beforeNodes = null;
+
+			for ( const beforeNode of currentBeforeNodes ) {
+
+				beforeNode.build( builder, output );
+
+			}
+
+			this._beforeNodes = currentBeforeNodes;
+
+		}
+
+		//
+
 		const nodeData = builder.getDataFromNode( this );
 		nodeData.buildStages = nodeData.buildStages || {};
 		nodeData.buildStages[ builder.buildStage ] = true;

+ 16 - 2
src/nodes/lighting/ShadowNode.js

@@ -1,7 +1,7 @@
 import ShadowBaseNode, { shadowPositionWorld } from './ShadowBaseNode.js';
 import { float, vec2, vec3, int, Fn, nodeObject } from '../tsl/TSLBase.js';
 import { reference } from '../accessors/ReferenceNode.js';
-import { texture } from '../accessors/TextureNode.js';
+import { texture, textureLoad } from '../accessors/TextureNode.js';
 import { normalWorld } from '../accessors/Normal.js';
 import { mix, sqrt } from '../math/MathNode.js';
 import { add } from '../math/OperatorNode.js';
@@ -19,6 +19,8 @@ import { getDataFromObject } from '../core/NodeUtils.js';
 import { getShadowMaterial, BasicShadowFilter, PCFShadowFilter, PCFSoftShadowFilter, VSMShadowFilter } from './ShadowFilterNode.js';
 import ChainMap from '../../renderers/common/ChainMap.js';
 import { warn } from '../../utils.js';
+import { textureSize } from '../accessors/TextureSizeNode.js';
+import { uv } from '../accessors/UV.js';
 
 //
 
@@ -515,7 +517,19 @@ class ShadowNode extends ShadowBaseNode {
 		this.shadowMap = shadowMap;
 		this.shadow.map = shadowMap;
 
-		return shadowOutput;
+		// Shadow Output + Inspector
+
+		const inspectName = `${ this.light.type } Shadow [ ${ this.light.name || 'ID: ' + this.light.id } ]`;
+
+		return shadowOutput.toInspector( `${ inspectName } / Color`, () => {
+
+			return texture( this.shadowMap.texture );
+
+		} ).toInspector( `${ inspectName } / Depth`, () => {
+
+			return textureLoad( this.shadowMap.depthTexture, uv().mul( textureSize( texture( this.shadowMap.depthTexture ) ) ) ).x.oneMinus();
+
+		} );
 
 	}
 

+ 1 - 0
src/nodes/tsl/TSLBase.js

@@ -27,6 +27,7 @@ export * from '../utils/Discard.js'; // Discard(), Return()
 export * from '../display/RenderOutputNode.js'; // .renderOutput()
 export * from '../utils/DebugNode.js'; // debug()
 export * from '../core/SubBuildNode.js'; // subBuild()
+export * from '../core/InspectorNode.js'; // inspector(), .toInspector()
 
 export function addNodeElement( name/*, nodeElement*/ ) {
 

+ 1 - 37
src/renderers/common/CanvasTarget.js

@@ -10,30 +10,15 @@ import { DepthTexture } from '../../textures/DepthTexture.js';
  */
 class CanvasTarget extends EventDispatcher {
 
-	/**
-	 * CanvasTarget options.
-	 *
-	 * @typedef {Object} CanvasTarget~Options
-	 * @property {boolean} [antialias=false] - Whether MSAA as the default anti-aliasing should be enabled or not.
-	 * @property {number} [samples=0] - When `antialias` is `true`, `4` samples are used by default. This parameter can set to any other integer value than 0
-	 * to overwrite the default.
-	 */
-
 	/**
 	 * Constructs a new CanvasTarget.
 	 *
 	 * @param {HTMLCanvasElement|OffscreenCanvas} domElement - The canvas element to render to.
-	 * @param {Object} [parameters={}] - The parameters.
 	 */
-	constructor( domElement, parameters = {} ) {
+	constructor( domElement ) {
 
 		super();
 
-		const {
-			antialias = false,
-			samples = 0
-		} = parameters;
-
 		/**
 		 * A reference to the canvas element the renderer is drawing to.
 		 * This value of this property will automatically be created by
@@ -92,15 +77,6 @@ class CanvasTarget extends EventDispatcher {
 		 */
 		this._scissorTest = false;
 
-		/**
-		 * The number of MSAA samples.
-		 *
-		 * @private
-		 * @type {number}
-		 * @default 0
-		 */
-		this._samples = samples || ( antialias === true ) ? 4 : 0;
-
 		/**
 		 * The color texture of the default framebuffer.
 		 *
@@ -117,18 +93,6 @@ class CanvasTarget extends EventDispatcher {
 
 	}
 
-	/**
-	 * The number of samples used for multi-sample anti-aliasing (MSAA).
-	 *
-	 * @type {number}
-	 * @default 0
-	 */
-	get samples() {
-
-		return this._samples;
-
-	}
-
 	/**
 	 * Returns the pixel ratio.
 	 *

+ 7 - 0
src/renderers/common/InspectorBase.js

@@ -78,6 +78,13 @@ class InspectorBase {
 	 */
 	finish() { }
 
+	/**
+	 * Inspects a node.
+	 *
+	 * @param {Node} node - The node to inspect.
+	 */
+	inspect( /*node*/ ) { }
+
 	/**
 	 * When a compute operation is performed.
 	 *

+ 12 - 3
src/renderers/common/Renderer.js

@@ -254,6 +254,15 @@ class Renderer {
 
 		// internals
 
+		/**
+		 * The number of MSAA samples.
+		 *
+		 * @private
+		 * @type {number}
+		 * @default 0
+		 */
+		this._samples = samples || ( antialias === true ) ? 4 : 0;
+
 		/**
 		 * OnCanvasTargetResize callback function.
 		 *
@@ -268,7 +277,7 @@ class Renderer {
 		 * @private
 		 * @type {CanvasTarget}
 		 */
-		this._canvasTarget = new CanvasTarget( backend.getDomElement(), { antialias, samples } );
+		this._canvasTarget = new CanvasTarget( backend.getDomElement() );
 		this._canvasTarget.addEventListener( 'resize', this._onCanvasTargetResize );
 		this._canvasTarget.isDefaultCanvasTarget = true;
 
@@ -2172,7 +2181,7 @@ class Renderer {
 	 */
 	get samples() {
 
-		return this._canvasTarget.samples;
+		return this._samples;
 
 	}
 
@@ -2187,7 +2196,7 @@ class Renderer {
 	 */
 	get currentSamples() {
 
-		let samples = this.samples;
+		let samples = this._samples;
 
 		if ( this._renderTarget !== null ) {
 

粤ICP备19079148号