three
Version:
JavaScript 3D library
252 lines (176 loc) • 6.2 kB
JavaScript
import Node from './Node.js';
import { NodeShaderStage } from './constants.js';
import { addMethodChaining, nodeProxy } from '../tsl/TSLCore.js';
import { subBuild } from './SubBuildNode.js';
/**
* Class for representing shader varyings as nodes. Varyings are create from
* existing nodes like the following:
*
* ```js
* const positionLocal = positionGeometry.toVarying( 'vPositionLocal' );
* ```
*
* @augments Node
*/
class VaryingNode extends Node {
static get type() {
return 'VaryingNode';
}
/**
* Constructs a new varying node.
*
* @param {Node} node - The node for which a varying should be created.
* @param {?string} name - The name of the varying in the shader.
*/
constructor( node, name = null ) {
super();
/**
* The node for which a varying should be created.
*
* @type {Node}
*/
this.node = subBuild( node, 'VERTEX' );
/**
* The name of the varying in the shader. If no name is defined,
* the node system auto-generates one.
*
* @type {?string}
* @default null
*/
this.name = name;
/**
* This flag can be used for type testing.
*
* @type {boolean}
* @readonly
* @default true
*/
this.isVaryingNode = true;
/**
* The interpolation type of the varying data.
*
* @type {?string}
* @default null
*/
this.interpolationType = null;
/**
* The interpolation sampling type of varying data.
*
* @type {?string}
* @default null
*/
this.interpolationSampling = null;
/**
* This flag is used for global cache.
*
* @type {boolean}
* @default true
*/
this.global = true;
}
isCacheable( /*builder*/ ) {
return false;
}
/**
* Defines the interpolation type of the varying.
*
* @param {string} type - The interpolation type.
* @param {?string} sampling - The interpolation sampling type
* @return {VaryingNode} A reference to this node.
*/
setInterpolation( type, sampling = null ) {
this.interpolationType = type;
this.interpolationSampling = sampling;
return this;
}
getHash( builder ) {
return this.name || super.getHash( builder );
}
generateNodeType( builder ) {
// VaryingNode is auto type
return this.node.getNodeType( builder );
}
/**
* This method performs the setup of a varying node with the current node builder.
*
* @param {NodeBuilder} builder - The current node builder.
* @return {NodeVarying} The node varying from the node builder.
*/
setupVarying( builder ) {
const properties = builder.getNodeProperties( this );
let varying = properties.varying;
if ( varying === undefined ) {
const name = this.name;
const type = this.getNodeType( builder );
const interpolationType = this.interpolationType;
const interpolationSampling = this.interpolationSampling;
properties.varying = varying = builder.getVaryingFromNode( this, name, type, interpolationType, interpolationSampling );
properties.node = subBuild( this.node, 'VERTEX' );
}
// this property can be used to check if the varying can be optimized for a variable
varying.needsInterpolation || ( varying.needsInterpolation = ( builder.shaderStage === 'fragment' ) );
return varying;
}
setup( builder ) {
// the context can evaluate varyings directly in the fragment stage, e.g. if the surface is reconstructed per fragment.
// indices and attributes create their own varyings in the fragment stage, so their varyings are kept unless the
// context provides the attribute
if ( builder.context.inlineVaryings === true && builder.shaderStage === 'fragment' ) {
let node = this.node;
while ( node.isSubBuildNode === true ) node = node.node;
if ( node.isIndexNode !== true && ( node.isAttributeNode !== true || node.getContextAttribute( builder ) ) ) return this.node;
}
this.setupVarying( builder );
builder.flowNodeFromShaderStage( NodeShaderStage.VERTEX, this.node );
}
analyze( builder ) {
// TODO: Node.build() could pass through the output node of setup() directly, so overrides of analyze() and generate() don't need to handle it twice
if ( builder.getNodeProperties( this ).outputNode ) return super.analyze( builder );
this.setupVarying( builder );
builder.flowNodeFromShaderStage( NodeShaderStage.VERTEX, this.node );
}
generate( builder ) {
const { outputNode } = builder.getNodeProperties( this );
if ( outputNode ) return outputNode.build( builder, this.getNodeType( builder ) );
const propertyKey = builder.getSubBuildProperty( 'property', builder.currentStack );
const properties = builder.getNodeProperties( this );
const varying = this.setupVarying( builder );
// The vertex assignment is emitted once per block, from the fragment stage it is emitted outside of any block.
const flowBlock = builder.shaderStage === NodeShaderStage.VERTEX ? builder.flowBlock : null;
if ( properties[ propertyKey ] !== flowBlock ) {
const type = this.getNodeType( builder );
const propertyName = builder.getPropertyName( varying, NodeShaderStage.VERTEX );
if ( builder.shaderStage === NodeShaderStage.VERTEX ) {
const snippet = properties.node.build( builder, type );
builder.addLineFlowCode( `${ propertyName } = ${ snippet }`, this );
} else {
// force node run in vertex stage
builder.flowNodeFromShaderStage( NodeShaderStage.VERTEX, properties.node, type, propertyName );
}
properties[ propertyKey ] = flowBlock;
}
return builder.getPropertyName( varying );
}
}
export default VaryingNode;
/**
* TSL function for creating a varying node.
*
* @tsl
* @function
* @param {Node} node - The node for which a varying should be created.
* @param {?string} name - The name of the varying in the shader.
* @returns {VaryingNode}
*/
export const varying = /*@__PURE__*/ nodeProxy( VaryingNode ).setParameterLength( 1, 2 );
/**
* Computes a node in the vertex stage.
*
* @tsl
* @function
* @param {Node} node - The node which should be executed in the vertex stage.
* @returns {VaryingNode}
*/
export const vertexStage = ( node ) => varying( node );
addMethodChaining( 'toVarying', varying );
addMethodChaining( 'toVertexStage', vertexStage );