UNPKG

three

Version:

JavaScript 3D library

234 lines (166 loc) 6 kB
import { output, renderOutput, uniform } from '../../nodes/TSL.js'; import { ColorManagement } from '../../math/ColorManagement.js'; import { NoToneMapping } from '../../constants.js'; import RenderPipeline from './RenderPipeline.js'; /** * An alternative render pipeline that applies output processing directly in * material shaders. This avoids the intermediate framebuffer and output pass * used by {@link Renderer}, but changes blending and is not compatible with * materials that sample the framebuffer, such as transmissive materials. * * ```js * const renderPipeline = new DirectRenderPipeline( renderer ); * renderPipeline.render( scene, camera ); * ``` * * Note: This module can only be used with `WebGPURenderer`. * * @augments RenderPipeline */ class DirectRenderPipeline extends RenderPipeline { /** * Constructs a direct render pipeline. * * @param {Renderer} renderer - A reference to the renderer. * @param {Node<vec4>} outputNode - An optional output node. */ constructor( renderer, outputNode = output ) { super( renderer, outputNode ); /** * This flag can be used for type testing. * * @type {boolean} * @readonly * @default true */ this.isDirectRenderPipeline = true; /** * The context node used to apply output processing in material shaders. * * @private * @type {?ContextNode} * @default null */ this._contextNode = null; /** * The renderer context node wrapped by this pipeline. * * @private * @type {?ContextNode} * @default null */ this._rendererContextNode = null; /** * Cached node representations of solid scene backgrounds. * * @private * @type {WeakMap<Color, UniformNode>} */ this._backgroundNodes = new WeakMap(); } /** * Renders the scene with output processing applied directly in material shaders. * * @param {Object3D} scene - The scene or object to render. * @param {Camera} camera - The camera. */ render( scene, camera ) { const renderer = this.renderer; this._update(); const backgroundNode = this._getBackgroundNode( scene ); const currentBackgroundNode = backgroundNode !== null ? scene.backgroundNode : null; const currentContextNode = renderer.contextNode; const outputRenderTarget = renderer.getOutputRenderTarget(); const useDirectXRRenderTarget = outputRenderTarget !== null && renderer.xr.isPresenting === true && renderer.backend.isWebGPUBackend === true && renderer.isOutputTarget === true; let samples; let depthBuffer; let toneMapping; let outputColorSpace; if ( backgroundNode !== null ) scene.backgroundNode = backgroundNode; renderer.contextNode = this._contextNode; if ( useDirectXRRenderTarget ) { samples = outputRenderTarget.samples; depthBuffer = outputRenderTarget.depthBuffer; outputRenderTarget.samples = renderer.samples; outputRenderTarget.depthBuffer = true; } try { for ( const callback of this._contextData.onBeforePipelineCallbacks ) callback(); toneMapping = renderer.toneMapping; outputColorSpace = renderer.outputColorSpace; renderer.toneMapping = NoToneMapping; renderer.outputColorSpace = ColorManagement.workingColorSpace; if ( renderer.xr.enabled ) { renderer.xr.renderLayers(); } renderer.render( scene, camera ); } finally { if ( toneMapping !== undefined ) renderer.toneMapping = toneMapping; if ( outputColorSpace !== undefined ) renderer.outputColorSpace = outputColorSpace; if ( backgroundNode !== null && scene.backgroundNode === backgroundNode ) scene.backgroundNode = currentBackgroundNode; if ( renderer.contextNode === this._contextNode ) renderer.contextNode = currentContextNode; if ( useDirectXRRenderTarget ) { outputRenderTarget.samples = samples; outputRenderTarget.depthBuffer = depthBuffer; } for ( const callback of this._contextData.onAfterPipelineCallbacks ) callback(); } } /** * Returns a node representation of a solid scene background so it receives * the same inline output processing as material fragments. * * @private * @param {Object3D} scene - The scene or object to render. * @return {?UniformNode} The background node. */ _getBackgroundNode( scene ) { if ( scene.isScene !== true || scene.backgroundNode != null || scene.background?.isColor !== true ) return null; const environmentBlendMode = this.renderer.xr.getEnvironmentBlendMode(); if ( environmentBlendMode === 'additive' || environmentBlendMode === 'alpha-blend' ) return null; let backgroundNode = this._backgroundNodes.get( scene.background ); if ( backgroundNode === undefined ) { backgroundNode = uniform( scene.background ); this._backgroundNodes.set( scene.background, backgroundNode ); } return backgroundNode; } /** * Updates the state of the module. * * @private */ _update() { if ( this._rendererContextNode !== this.renderer.contextNode ) { this._rendererContextNode = this.renderer.contextNode; this.needsUpdate = true; } super._update(); } /** * Updates the context used to process material output directly. * * @private */ _updateContext() { super._updateContext(); const pipelineOutputNode = this.outputNode; const toneMapping = this._toneMapping; const outputColorSpace = this._outputColorSpace; const outputColorTransform = this.outputColorTransform; this._contextNode = this._rendererContextNode.context( { ...this._contextData, getOutput: ( materialOutputNode, builder ) => { const renderer = builder.renderer; const renderTarget = renderer.getRenderTarget(); if ( renderer.isOutputTarget === false && renderTarget?._hasExternalTextures !== true ) return materialOutputNode; output.assign( materialOutputNode ); return outputColorTransform === true ? renderOutput( pipelineOutputNode, toneMapping, outputColorSpace ) : pipelineOutputNode; } } ); } } export default DirectRenderPipeline;