three
Version:
JavaScript 3D library
371 lines (277 loc) • 9.45 kB
JavaScript
import { nodeObject } from '../tsl/TSLCore.js';
import TextureNode from '../accessors/TextureNode.js';
import { NodeUpdateType } from '../core/constants.js';
import { uv } from '../accessors/UV.js';
import { context } from '../core/ContextNode.js';
import NodeMaterial from '../../materials/nodes/NodeMaterial.js';
import QuadMesh from '../../renderers/common/QuadMesh.js';
import { RenderTarget } from '../../core/RenderTarget.js';
import { Vector2 } from '../../math/Vector2.js';
import { HalfFloatType } from '../../constants.js';
import { error } from '../../utils.js';
import { resetRendererState, restoreRendererState } from '../../renderers/common/RendererUtils.js';
const _size = /*@__PURE__*/ new Vector2();
/**
* `RTTNode` takes another node and uses it with a `QuadMesh` to render into a texture (RTT).
* This module is especially relevant in context of post processing where certain nodes require
* texture input for their effects. With the helper function `convertToTexture()` which is based
* on this module, the node system can automatically ensure texture input if required.
*
* @augments TextureNode
*/
class RTTNode extends TextureNode {
static get type() {
return 'RTTNode';
}
/**
* Constructs a new RTT node.
*
* @param {Node} node - The node to render a texture with.
* @param {?number} [width=null] - The width of the internal render target. If no width is applied, the render target is automatically resized.
* @param {?number} [height=null] - The height of the internal render target.
* @param {Object} [options={}] - The options for the internal render target.
* @param {number} [options.type=HalfFloatType] - The texture type.
* @param {boolean} [options.autoUpdate=true] - Whether the texture should automatically be updated or not.
* @param {number} [options.resolutionScale=1] - The resolution scale.
*/
constructor( node, width = null, height = null, options = {} ) {
const {
autoUpdate = true,
resolutionScale = 1
} = options;
const renderTarget = new RenderTarget( width ?? 1, height ?? 1, { type: HalfFloatType, ...options } );
super( renderTarget.texture, uv() );
/**
* This flag can be used for type testing.
*
* @type {boolean}
* @readonly
* @default true
*/
this.isRTTNode = true;
/**
* The node to render a texture with.
*
* @type {Node}
*/
this.node = node;
/**
* The width of the internal render target.
* If not width is applied, the render target is automatically resized.
*
* @type {?number}
* @default null
*/
this.width = width;
/**
* The height of the internal render target.
*
* @type {?number}
* @default null
*/
this.height = height;
/**
* The render target
*
* @type {RenderTarget}
*/
this.renderTarget = renderTarget;
/**
* Whether the texture requires an update or not.
*
* @type {boolean}
* @default true
*/
this.textureNeedsUpdate = true;
/**
* Whether the texture should automatically be updated or not.
*
* @type {boolean}
* @default true
*/
this.autoUpdate = autoUpdate;
/**
* The resolution scale
*
* @private
* @type {number}
* @default 1
*/
this._resolutionScale = resolutionScale;
/**
* The internal quad mesh for RTT.
*
* @private
* @type {QuadMesh}
*/
this._quadMesh = new QuadMesh( new NodeMaterial() );
/**
* The renderer state saved and restored around the RTT render.
* Kept per instance because nested RTT nodes must not share it.
*
* @private
* @type {Object}
*/
this._rendererState = {};
/**
* The `updateBeforeType` is set to `NodeUpdateType.FRAME` since the node updates
* the texture once per frame in its {@link RTTNode#updateBefore} method.
*
* @type {string}
* @default 'frame'
*/
this.updateBeforeType = NodeUpdateType.FRAME;
}
/**
* Whether the internal render target should automatically be resized or not.
*
* @type {boolean}
* @readonly
* @default true
*/
get autoResize() {
return this.width === null;
}
setup( builder ) {
this._quadMesh.material.contextNode = context( builder.getSharedContext() );
this._quadMesh.material.fragmentNode = this.node;
this._quadMesh.material.name = 'RTT';
this._quadMesh.material.needsUpdate = true;
return super.setup( builder );
}
/**
* Sets the size of the internal render target.
*
* @param {number} width - The width to set.
* @param {number} height - The height to set.
*/
setSize( width, height ) {
const effectiveWidth = Math.floor( width * this._resolutionScale );
const effectiveHeight = Math.floor( height * this._resolutionScale );
this.renderTarget.setSize( effectiveWidth, effectiveHeight );
this.textureNeedsUpdate = true;
}
/**
* Sets the resolution scale.
* The resolution scale is a factor that is multiplied with the renderer's width and height.
*
* @param {number} resolutionScale - The resolution scale to set. A value of `1` means full resolution.
* @returns {RTTNode} A reference to this node.
*/
setResolutionScale( resolutionScale ) {
this._resolutionScale = resolutionScale;
if ( this.autoResize === false ) {
this.setSize( this.width, this.height );
}
return this;
}
/**
* Gets the resolution scale.
*
* @returns {number} The resolution scale.
*/
getResolutionScale() {
return this._resolutionScale;
}
/**
* Overwritten since the value is defined by the internal render target.
*
* @param {Texture} value - The texture value.
*/
set value( value ) {
if ( this.renderTarget && value !== this.renderTarget.texture ) {
error( 'TSL: "rtt()" does not allow overwriting the value.' );
}
}
/**
* The texture of the internal render target.
*
* @type {Texture}
*/
get value() {
return this.renderTarget ? this.renderTarget.texture : null;
}
/**
* Renders the node's output into the internal render target before the main render pass.
* Handles automatic resizing of the render target when `autoResize` is enabled,
* and skips rendering if neither `textureNeedsUpdate` nor `autoUpdate` is true.
*
* @param {NodeFrame} frame - The current node frame, providing access to the renderer and other frame data.
*/
updateBefore( frame ) {
const { renderer } = frame;
if ( this.textureNeedsUpdate === false && this.autoUpdate === false ) return;
this.textureNeedsUpdate = false;
//
if ( this.autoResize === true ) {
const size = renderer.getDrawingBufferSize( _size );
const effectiveWidth = Math.floor( size.width * this._resolutionScale );
const effectiveHeight = Math.floor( size.height * this._resolutionScale );
if ( effectiveWidth !== this.renderTarget.width || effectiveHeight !== this.renderTarget.height ) {
this.renderTarget.setSize( effectiveWidth, effectiveHeight );
this.textureNeedsUpdate = true;
}
}
//
let name = 'RTT';
const callName = this.name || this.node.name;
if ( callName ) {
name = callName + ' [ ' + name + ' ]';
}
this._quadMesh.name = name;
//
resetRendererState( renderer, this._rendererState );
renderer.setRenderTarget( this.renderTarget );
this._quadMesh.render( renderer );
restoreRendererState( renderer, this._rendererState );
}
clone() {
const newNode = new TextureNode( this.value, this.uvNode, this.levelNode );
newNode.sampler = this.sampler;
newNode.referenceNode = this;
return newNode;
}
/**
* Frees internal resources. Should be called when the node is no longer in use.
*/
dispose() {
this.renderTarget.dispose();
this._quadMesh.material.dispose();
super.dispose();
}
}
export default RTTNode;
/**
* TSL function for creating a RTT node.
*
* @tsl
* @function
* @param {Node} node - The node to render a texture with.
* @param {?number} [width=null] - The width of the internal render target. If no width is applied, the render target is automatically resized.
* @param {?number} [height=null] - The height of the internal render target.
* @param {Object} [options={}] - The options for the internal render target.
* @param {number} [options.type=HalfFloatType] - The texture type.
* @param {boolean} [options.autoUpdate=true] - Whether the texture should automatically be updated or not.
* @param {number} [options.resolutionScale=1] - The resolution scale.
* @returns {RTTNode}
*/
export const rtt = ( node, ...params ) => new RTTNode( nodeObject( node ), ...params );
/**
* TSL function for converting nodes to textures nodes.
*
* @tsl
* @function
* @param {Node} node - The node to render a texture with.
* @param {?number} [width=null] - The width of the internal render target. If no width is applied, the render target is automatically resized.
* @param {?number} [height=null] - The height of the internal render target.
* @param {Object} [options={}] - The options for the internal render target.
* @param {number} [options.type=HalfFloatType] - The texture type.
* @param {boolean} [options.autoUpdate=true] - Whether the texture should automatically be updated or not.
* @param {number} [options.resolutionScale=1] - The resolution scale.
* @returns {RTTNode}
*/
export const convertToTexture = ( node, ...params ) => {
if ( node.isSampleNode || node.isTextureNode ) return node;
if ( node.isPassNode ) return node.getTextureNode();
return rtt( node, ...params );
};