Spaces:
Running
Running
import { RenderTarget, Vector2, TempNode, NodeUpdateType, QuadMesh, RendererUtils, NodeMaterial } from 'three/webgpu'; | |
import { convertToTexture, nodeObject, Fn, passTexture, uv, vec2, vec3, vec4, max, float, sub, int, Loop, fract, pow, distance } from 'three/tsl'; | |
/** @module LensflareNode **/ | |
const _quadMesh = /*@__PURE__*/ new QuadMesh(); | |
const _size = /*@__PURE__*/ new Vector2(); | |
let _rendererState; | |
/** | |
* Post processing node for adding a bloom-based lens flare effect. This effect | |
* requires that you extract the bloom of the scene via a bloom pass first. | |
* | |
* References: | |
* - {@link https://john-chapman-graphics.blogspot.com/2013/02/pseudo-lens-flare.html}. | |
* - {@link https://john-chapman.github.io/2017/11/05/pseudo-lens-flare.html}. | |
* | |
* @augments TempNode | |
*/ | |
class LensflareNode extends TempNode { | |
static get type() { | |
return 'LensflareNode'; | |
} | |
/** | |
* Constructs a new lens flare node. | |
* | |
* @param {TextureNode} textureNode - The texture node that represents the scene's bloom. | |
* @param {Object} params - The parameter object for configuring the effect. | |
* @param {Node<vec3> | Color} [params.ghostTint=vec3(1, 1, 1)] - Defines the tint of the flare/ghosts. | |
* @param {Node<float> | Number} [params.threshold=float(0.5)] - Controls the size and strength of the effect. A higher threshold results in smaller flares. | |
* @param {Node<float> | Number} [params.ghostSamples=float(4)] - Represents the number of flares/ghosts per bright spot which pivot around the center. | |
* @param {Node<float> | Number} [params.ghostSpacing=float(0.25)] - Defines the spacing of the flares/ghosts. | |
* @param {Node<float> | Number} [params.ghostAttenuationFactor=float(25)] - Defines the attenuation factor of flares/ghosts. | |
* @param {Number} [params.downSampleRatio=4] - Defines how downsampling since the effect is usually not rendered at full resolution. | |
*/ | |
constructor( textureNode, params = {} ) { | |
super( 'vec4' ); | |
/** | |
* The texture node that represents the scene's bloom. | |
* | |
* @type {TextureNode} | |
*/ | |
this.textureNode = textureNode; | |
const { | |
ghostTint = vec3( 1, 1, 1 ), | |
threshold = float( 0.5 ), | |
ghostSamples = float( 4 ), | |
ghostSpacing = float( 0.25 ), | |
ghostAttenuationFactor = float( 25 ), | |
downSampleRatio = 4 | |
} = params; | |
/** | |
* Defines the tint of the flare/ghosts. | |
* | |
* @type {Node<vec3>} | |
*/ | |
this.ghostTintNode = nodeObject( ghostTint ); | |
/** | |
* Controls the size and strength of the effect. A higher threshold results in smaller flares. | |
* | |
* @type {Node<float>} | |
*/ | |
this.thresholdNode = nodeObject( threshold ); | |
/** | |
* Represents the number of flares/ghosts per bright spot which pivot around the center. | |
* | |
* @type {Node<float>} | |
*/ | |
this.ghostSamplesNode = nodeObject( ghostSamples ); | |
/** | |
* Defines the spacing of the flares/ghosts. | |
* | |
* @type {Node<float>} | |
*/ | |
this.ghostSpacingNode = nodeObject( ghostSpacing ); | |
/** | |
* Defines the attenuation factor of flares/ghosts. | |
* | |
* @type {Node<float>} | |
*/ | |
this.ghostAttenuationFactorNode = nodeObject( ghostAttenuationFactor ); | |
/** | |
* Defines how downsampling since the effect is usually not rendered at full resolution. | |
* | |
* @type {Number} | |
*/ | |
this.downSampleRatio = downSampleRatio; | |
/** | |
* The `updateBeforeType` is set to `NodeUpdateType.FRAME` since the node renders | |
* its effect once per frame in `updateBefore()`. | |
* | |
* @type {String} | |
* @default 'frame' | |
*/ | |
this.updateBeforeType = NodeUpdateType.FRAME; | |
/** | |
* The internal render target of the effect. | |
* | |
* @private | |
* @type {RenderTarget} | |
*/ | |
this._renderTarget = new RenderTarget( 1, 1, { depthBuffer: false } ); | |
this._renderTarget.texture.name = 'LensflareNode'; | |
/** | |
* The node material that holds the effect's TSL code. | |
* | |
* @private | |
* @type {NodeMaterial} | |
*/ | |
this._material = new NodeMaterial(); | |
this._material.name = 'LensflareNode'; | |
/** | |
* The result of the effect is represented as a separate texture node. | |
* | |
* @private | |
* @type {PassTextureNode} | |
*/ | |
this._textureNode = passTexture( this, this._renderTarget.texture ); | |
} | |
/** | |
* Returns the result of the effect as a texture node. | |
* | |
* @return {PassTextureNode} A texture node that represents the result of the effect. | |
*/ | |
getTextureNode() { | |
return this._textureNode; | |
} | |
/** | |
* Sets the size of the effect. | |
* | |
* @param {Number} width - The width of the effect. | |
* @param {Number} height - The height of the effect. | |
*/ | |
setSize( width, height ) { | |
const resx = Math.round( width / this.downSampleRatio ); | |
const resy = Math.round( height / this.downSampleRatio ); | |
this._renderTarget.setSize( resx, resy ); | |
} | |
/** | |
* This method is used to render the effect once per frame. | |
* | |
* @param {NodeFrame} frame - The current node frame. | |
*/ | |
updateBefore( frame ) { | |
const { renderer } = frame; | |
const size = renderer.getDrawingBufferSize( _size ); | |
this.setSize( size.width, size.height ); | |
_rendererState = RendererUtils.resetRendererState( renderer, _rendererState ); | |
_quadMesh.material = this._material; | |
// clear | |
renderer.setMRT( null ); | |
// lensflare | |
renderer.setRenderTarget( this._renderTarget ); | |
_quadMesh.render( renderer ); | |
// restore | |
RendererUtils.restoreRendererState( renderer, _rendererState ); | |
} | |
/** | |
* This method is used to setup the effect's TSL code. | |
* | |
* @param {NodeBuilder} builder - The current node builder. | |
* @return {PassTextureNode} | |
*/ | |
setup( builder ) { | |
const lensflare = Fn( () => { | |
// flip uvs so lens flare pivot around the image center | |
const texCoord = uv().oneMinus().toVar(); | |
// ghosts are positioned along this vector | |
const ghostVec = sub( vec2( 0.5 ), texCoord ).mul( this.ghostSpacingNode ).toVar(); | |
// sample ghosts | |
const result = vec4().toVar(); | |
Loop( { start: int( 0 ), end: int( this.ghostSamplesNode ), type: 'int', condition: '<' }, ( { i } ) => { | |
// use fract() to ensure that the texture coordinates wrap around | |
const sampleUv = fract( texCoord.add( ghostVec.mul( float( i ) ) ) ).toVar(); | |
// reduce contributions from samples at the screen edge | |
const d = distance( sampleUv, vec2( 0.5 ) ); | |
const weight = pow( d.oneMinus(), this.ghostAttenuationFactorNode ); | |
// accumulate | |
let sample = this.textureNode.sample( sampleUv ).rgb; | |
sample = max( sample.sub( this.thresholdNode ), vec3( 0 ) ).mul( this.ghostTintNode ); | |
result.addAssign( sample.mul( weight ) ); | |
} ); | |
return result; | |
} ); | |
this._material.fragmentNode = lensflare().context( builder.getSharedContext() ); | |
this._material.needsUpdate = true; | |
return this._textureNode; | |
} | |
/** | |
* Frees internal resources. This method should be called | |
* when the effect is no longer required. | |
*/ | |
dispose() { | |
this._renderTarget.dispose(); | |
this._material.dispose(); | |
} | |
} | |
export default LensflareNode; | |
/** | |
* TSL function for creating a bloom-based lens flare effect. | |
* | |
* @function | |
* @param {TextureNode} node - The node that represents the scene's bloom. | |
* @param {Object} params - The parameter object for configuring the effect. | |
* @param {Node<vec3> | Color} [params.ghostTint=vec3(1, 1, 1)] - Defines the tint of the flare/ghosts. | |
* @param {Node<float> | Number} [params.threshold=float(0.5)] - Controls the size and strength of the effect. A higher threshold results in smaller flares. | |
* @param {Node<float> | Number} [params.ghostSamples=float(4)] - Represents the number of flares/ghosts per bright spot which pivot around the center. | |
* @param {Node<float> | Number} [params.ghostSpacing=float(0.25)] - Defines the spacing of the flares/ghosts. | |
* @param {Node<float> | Number} [params.ghostAttenuationFactor=float(25)] - Defines the attenuation factor of flares/ghosts. | |
* @param {Number} [params.downSampleRatio=4] - Defines how downsampling since the effect is usually not rendered at full resolution. | |
* @returns {LensflareNode} | |
*/ | |
export const lensflare = ( node, params ) => nodeObject( new LensflareNode( convertToTexture( node ), params ) ); | |