- Create class "player"
This commit is contained in:
Kum1ta
2024-08-18 01:48:50 +02:00
parent a265843b82
commit 44ad413120
1218 changed files with 638609 additions and 0 deletions

220
site/real_game/node_modules/three/src/nodes/Nodes.js generated vendored Normal file
View File

@ -0,0 +1,220 @@
// @TODO: We can simplify "export { default as SomeNode, other, exports } from '...'" to just "export * from '...'" if we will use only named exports
// this will also solve issues like "import TempNode from '../core/Node.js'"
// constants
export * from './core/constants.js';
// core
export { default as AssignNode, assign } from './core/AssignNode.js';
export { default as AttributeNode, attribute } from './core/AttributeNode.js';
export { default as BypassNode, bypass } from './core/BypassNode.js';
export { default as CacheNode, cache } from './core/CacheNode.js';
export { default as ConstNode } from './core/ConstNode.js';
export { default as ContextNode, context, label } from './core/ContextNode.js';
export { default as IndexNode, vertexIndex, instanceIndex, drawIndex } from './core/IndexNode.js';
export { default as LightingModel } from './core/LightingModel.js';
export { default as Node, addNodeClass, createNodeFromType } from './core/Node.js';
export { default as VarNode, temp } from './core/VarNode.js';
export { default as NodeAttribute } from './core/NodeAttribute.js';
export { default as NodeBuilder } from './core/NodeBuilder.js';
export { default as NodeCache } from './core/NodeCache.js';
export { default as NodeCode } from './core/NodeCode.js';
export { default as NodeFrame } from './core/NodeFrame.js';
export { default as NodeFunctionInput } from './core/NodeFunctionInput.js';
export { default as NodeKeywords } from './core/NodeKeywords.js';
export { default as NodeUniform } from './core/NodeUniform.js';
export { default as NodeVar } from './core/NodeVar.js';
export { default as NodeVarying } from './core/NodeVarying.js';
export { default as ParameterNode, parameter } from './core/ParameterNode.js';
export { default as PropertyNode, property, varyingProperty, output, diffuseColor, emissive, roughness, metalness, clearcoat, clearcoatRoughness, sheen, sheenRoughness, iridescence, iridescenceIOR, iridescenceThickness, specularColor, shininess, dashSize, gapSize, pointWidth, alphaT, anisotropy, anisotropyB, anisotropyT } from './core/PropertyNode.js';
export { default as StackNode, stack } from './core/StackNode.js';
export { default as TempNode } from './core/TempNode.js';
export { default as UniformGroupNode, uniformGroup, objectGroup, renderGroup, frameGroup } from './core/UniformGroupNode.js';
export { default as UniformNode, uniform } from './core/UniformNode.js';
export { default as VaryingNode, varying } from './core/VaryingNode.js';
export { default as OutputStructNode, outputStruct } from './core/OutputStructNode.js';
export { default as MRTNode, mrt } from './core/MRTNode.js';
import * as NodeUtils from './core/NodeUtils.js';
export { NodeUtils };
// math
export { default as MathNode, PI, PI2, EPSILON, INFINITY, radians, degrees, exp, exp2, log, log2, sqrt, inverseSqrt, floor, ceil, normalize, fract, sin, cos, tan, asin, acos, atan, abs, sign, length, lengthSq, negate, oneMinus, dFdx, dFdy, round, reciprocal, trunc, fwidth, bitcast, atan2, min, max, mod, step, reflect, distance, difference, dot, cross, pow, pow2, pow3, pow4, transformDirection, mix, clamp, saturate, refract, smoothstep, faceForward, cbrt, transpose, all, any, equals, rand } from './math/MathNode.js';
export { default as OperatorNode, add, sub, mul, div, remainder, equal, lessThan, greaterThan, lessThanEqual, greaterThanEqual, and, or, not, xor, bitAnd, bitNot, bitOr, bitXor, shiftLeft, shiftRight } from './math/OperatorNode.js';
export { default as CondNode, cond } from './math/CondNode.js';
export { default as HashNode, hash } from './math/HashNode.js';
// math utils
export { parabola, gain, pcurve, sinc } from './math/MathUtils.js';
export { triNoise3D } from './math/TriNoise3D.js';
// utils
export { default as ArrayElementNode } from './utils/ArrayElementNode.js';
export { default as ConvertNode } from './utils/ConvertNode.js';
export { default as DiscardNode, discard, Return } from './utils/DiscardNode.js';
export { default as EquirectUVNode, equirectUV } from './utils/EquirectUVNode.js';
export { default as FunctionOverloadingNode, overloadingFn } from './utils/FunctionOverloadingNode.js';
export { default as JoinNode } from './utils/JoinNode.js';
export { default as LoopNode, loop, Continue, Break } from './utils/LoopNode.js';
export { default as MatcapUVNode, matcapUV } from './utils/MatcapUVNode.js';
export { default as MaxMipLevelNode, maxMipLevel } from './utils/MaxMipLevelNode.js';
export { default as OscNode, oscSine, oscSquare, oscTriangle, oscSawtooth } from './utils/OscNode.js';
export { default as PackingNode, directionToColor, colorToDirection } from './utils/PackingNode.js';
export { default as RemapNode, remap, remapClamp } from './utils/RemapNode.js';
export { default as RotateUVNode, rotateUV } from './utils/RotateUVNode.js';
export { default as RotateNode, rotate } from './utils/RotateNode.js';
export { default as SetNode } from './utils/SetNode.js';
export { default as SplitNode } from './utils/SplitNode.js';
export { default as SpriteSheetUVNode, spritesheetUV } from './utils/SpriteSheetUVNode.js';
export { default as StorageArrayElementNode } from './utils/StorageArrayElementNode.js';
export { default as TimerNode, timerLocal, timerGlobal, timerDelta, frameId } from './utils/TimerNode.js';
export { default as TriplanarTexturesNode, triplanarTextures, triplanarTexture } from './utils/TriplanarTexturesNode.js';
export { default as ReflectorNode, reflector } from './utils/ReflectorNode.js';
export { default as RTTNode, rtt } from './utils/RTTNode.js';
// shadernode
export * from './shadernode/ShaderNode.js';
// accessors
export { TBNViewMatrix, parallaxDirection, parallaxUV, transformedBentNormalView } from './accessors/AccessorsUtils.js';
export { default as UniformsNode, uniforms } from './accessors/UniformsNode.js';
export * from './accessors/BitangentNode.js';
export { default as BufferAttributeNode, bufferAttribute, dynamicBufferAttribute, instancedBufferAttribute, instancedDynamicBufferAttribute } from './accessors/BufferAttributeNode.js';
export { default as BufferNode, buffer } from './accessors/BufferNode.js';
export * from './accessors/CameraNode.js';
export { default as VertexColorNode, vertexColor } from './accessors/VertexColorNode.js';
export { default as CubeTextureNode, cubeTexture } from './accessors/CubeTextureNode.js';
export { default as InstanceNode, instance } from './accessors/InstanceNode.js';
export { default as BatchNode, batch } from './accessors/BatchNode.js';
export { default as MaterialNode, materialAlphaTest, materialColor, materialShininess, materialEmissive, materialOpacity, materialSpecular, materialSpecularStrength, materialReflectivity, materialRoughness, materialMetalness, materialNormal, materialClearcoat, materialClearcoatRoughness, materialClearcoatNormal, materialRotation, materialSheen, materialSheenRoughness, materialIridescence, materialIridescenceIOR, materialIridescenceThickness, materialLineScale, materialLineDashSize, materialLineGapSize, materialLineWidth, materialLineDashOffset, materialPointWidth, materialAnisotropy, materialAnisotropyVector, materialDispersion, materialLightMap, materialAOMap } from './accessors/MaterialNode.js';
export { default as MaterialReferenceNode, materialReference } from './accessors/MaterialReferenceNode.js';
export { default as RendererReferenceNode, rendererReference } from './accessors/RendererReferenceNode.js';
export { default as MorphNode, morphReference } from './accessors/MorphNode.js';
export { default as TextureBicubicNode, textureBicubic } from './accessors/TextureBicubicNode.js';
export { default as ModelNode, modelDirection, modelViewMatrix, modelNormalMatrix, modelWorldMatrix, modelPosition, modelViewPosition, modelScale, modelWorldMatrixInverse } from './accessors/ModelNode.js';
export { default as ModelViewProjectionNode, modelViewProjection } from './accessors/ModelViewProjectionNode.js';
export * from './accessors/NormalNode.js';
export { default as Object3DNode, objectDirection, objectViewMatrix, objectNormalMatrix, objectWorldMatrix, objectPosition, objectScale, objectViewPosition } from './accessors/Object3DNode.js';
export { default as PointUVNode, pointUV } from './accessors/PointUVNode.js';
export * from './accessors/PositionNode.js';
export { default as ReferenceNode, reference, referenceBuffer } from './accessors/ReferenceNode.js';
export * from './accessors/ReflectVectorNode.js';
export { default as SkinningNode, skinning, skinningReference } from './accessors/SkinningNode.js';
export { default as SceneNode, backgroundBlurriness, backgroundIntensity } from './accessors/SceneNode.js';
export { default as StorageBufferNode, storage, storageObject } from './accessors/StorageBufferNode.js';
export * from './accessors/TangentNode.js';
export { default as TextureNode, texture, textureLoad, /*textureLevel,*/ sampler } from './accessors/TextureNode.js';
export { default as TextureSizeNode, textureSize } from './accessors/TextureSizeNode.js';
export { default as StorageTextureNode, storageTexture, textureStore } from './accessors/StorageTextureNode.js';
export { default as Texture3DNode, texture3D } from './accessors/Texture3DNode.js';
export * from './accessors/UVNode.js';
export { default as UserDataNode, userData } from './accessors/UserDataNode.js';
// display
export { default as BlendModeNode, burn, dodge, overlay, screen } from './display/BlendModeNode.js';
export { default as BumpMapNode, bumpMap } from './display/BumpMapNode.js';
export { default as ColorAdjustmentNode, saturation, vibrance, hue, luminance, threshold } from './display/ColorAdjustmentNode.js';
export { default as ColorSpaceNode, linearToColorSpace, colorSpaceToLinear, linearTosRGB, sRGBToLinear } from './display/ColorSpaceNode.js';
export { default as FrontFacingNode, frontFacing, faceDirection } from './display/FrontFacingNode.js';
export { default as NormalMapNode, normalMap } from './display/NormalMapNode.js';
export { default as PosterizeNode, posterize } from './display/PosterizeNode.js';
export { default as ToneMappingNode, toneMapping } from './display/ToneMappingNode.js';
export { default as ViewportNode, viewport, viewportCoordinate, viewportResolution, viewportTopLeft, viewportBottomLeft, viewportTopRight, viewportBottomRight } from './display/ViewportNode.js';
export { default as ViewportTextureNode, viewportTexture, viewportMipTexture } from './display/ViewportTextureNode.js';
export { default as ViewportSharedTextureNode, viewportSharedTexture } from './display/ViewportSharedTextureNode.js';
export { default as ViewportDepthTextureNode, viewportDepthTexture } from './display/ViewportDepthTextureNode.js';
export { default as ViewportDepthNode, viewZToOrthographicDepth, orthographicDepthToViewZ, viewZToPerspectiveDepth, perspectiveDepthToViewZ, depth, linearDepth, viewportLinearDepth } from './display/ViewportDepthNode.js';
export { default as GaussianBlurNode, gaussianBlur } from './display/GaussianBlurNode.js';
export { default as AfterImageNode, afterImage } from './display/AfterImageNode.js';
export { default as AnamorphicNode, anamorphic } from './display/AnamorphicNode.js';
export { default as SobelOperatorNode, sobel } from './display/SobelOperatorNode.js';
export { default as DepthOfFieldNode, dof } from './display/DepthOfFieldNode.js';
export { default as DotScreenNode, dotScreen } from './display/DotScreenNode.js';
export { default as RGBShiftNode, rgbShift } from './display/RGBShiftNode.js';
export { default as FilmNode, film } from './display/FilmNode.js';
export { default as Lut3DNode, lut3D } from './display/Lut3DNode.js';
export { default as GTAONode, ao } from './display/GTAONode.js';
export { default as DenoiseNode, denoise } from './display/DenoiseNode.js';
export { default as FXAANode, fxaa } from './display/FXAANode.js';
export { default as BloomNode, bloom } from './display/BloomNode.js';
export { default as TransitionNode, transition } from './display/TransitionNode.js';
export { default as RenderOutputNode, renderOutput } from './display/RenderOutputNode.js';
export { default as PixelationPassNode, pixelationPass } from './display/PixelationPassNode.js';
export { default as PassNode, pass, passTexture, depthPass } from './display/PassNode.js';
// code
export { default as ExpressionNode, expression } from './code/ExpressionNode.js';
export { default as CodeNode, code, js, wgsl, glsl } from './code/CodeNode.js';
export { default as FunctionCallNode, call } from './code/FunctionCallNode.js';
export { default as FunctionNode, wgslFn, glslFn } from './code/FunctionNode.js';
export { default as ScriptableNode, scriptable, global } from './code/ScriptableNode.js';
export { default as ScriptableValueNode, scriptableValue } from './code/ScriptableValueNode.js';
// fog
export { default as FogNode, fog } from './fog/FogNode.js';
export { default as FogRangeNode, rangeFog } from './fog/FogRangeNode.js';
export { default as FogExp2Node, densityFog } from './fog/FogExp2Node.js';
// geometry
export { default as RangeNode, range } from './geometry/RangeNode.js';
// gpgpu
export { default as ComputeNode, compute } from './gpgpu/ComputeNode.js';
// lighting
export { default as LightNode, lightTargetDirection } from './lighting/LightNode.js';
export { default as PointLightNode } from './lighting/PointLightNode.js';
export { default as DirectionalLightNode } from './lighting/DirectionalLightNode.js';
export { default as RectAreaLightNode } from './lighting/RectAreaLightNode.js';
export { default as SpotLightNode } from './lighting/SpotLightNode.js';
export { default as IESSpotLightNode } from './lighting/IESSpotLightNode.js';
export { default as AmbientLightNode } from './lighting/AmbientLightNode.js';
export { default as LightsNode, lights, lightsNode, addLightNode } from './lighting/LightsNode.js';
export { default as LightingNode /* @TODO: lighting (abstract), light */ } from './lighting/LightingNode.js';
export { default as LightingContextNode, lightingContext } from './lighting/LightingContextNode.js';
export { default as HemisphereLightNode } from './lighting/HemisphereLightNode.js';
export { default as EnvironmentNode } from './lighting/EnvironmentNode.js';
export { default as BasicEnvironmentNode } from './lighting/BasicEnvironmentNode.js';
export { default as IrradianceNode } from './lighting/IrradianceNode.js';
export { default as AONode } from './lighting/AONode.js';
export { default as AnalyticLightNode } from './lighting/AnalyticLightNode.js';
// pmrem
export { default as PMREMNode, pmremTexture } from './pmrem/PMREMNode.js';
export * from './pmrem/PMREMUtils.js';
// procedural
export { default as CheckerNode, checker } from './procedural/CheckerNode.js';
// loaders
export { default as NodeLoader } from './loaders/NodeLoader.js';
export { default as NodeObjectLoader } from './loaders/NodeObjectLoader.js';
export { default as NodeMaterialLoader } from './loaders/NodeMaterialLoader.js';
// parsers
export { default as GLSLNodeParser } from './parsers/GLSLNodeParser.js'; // @TODO: Move to jsm/renderers/webgl.
// materials
export * from './materials/Materials.js';
// materialX
export * from './materialx/MaterialXNodes.js';
// functions
export { default as BRDF_GGX } from './functions/BSDF/BRDF_GGX.js';
export { default as BRDF_Lambert } from './functions/BSDF/BRDF_Lambert.js';
export { default as D_GGX } from './functions/BSDF/D_GGX.js';
export { default as DFGApprox } from './functions/BSDF/DFGApprox.js';
export { default as F_Schlick } from './functions/BSDF/F_Schlick.js';
export { default as Schlick_to_F0 } from './functions/BSDF/Schlick_to_F0.js';
export { default as V_GGX_SmithCorrelated } from './functions/BSDF/V_GGX_SmithCorrelated.js';
export { getDistanceAttenuation } from './lighting/LightUtils.js';
export { default as getGeometryRoughness } from './functions/material/getGeometryRoughness.js';
export { default as getRoughness } from './functions/material/getRoughness.js';
export { default as PhongLightingModel } from './functions/PhongLightingModel.js';
export { default as PhysicalLightingModel } from './functions/PhysicalLightingModel.js';

View File

@ -0,0 +1,25 @@
import { bitangentView } from './BitangentNode.js';
import { normalView, transformedNormalView } from './NormalNode.js';
import { tangentView } from './TangentNode.js';
import { mat3 } from '../shadernode/ShaderNode.js';
import { mix } from '../math/MathNode.js';
import { anisotropy, anisotropyB, roughness } from '../core/PropertyNode.js';
import { positionViewDirection } from './PositionNode.js';
export const TBNViewMatrix = mat3( tangentView, bitangentView, normalView );
export const parallaxDirection = positionViewDirection.mul( TBNViewMatrix )/*.normalize()*/;
export const parallaxUV = ( uv, scale ) => uv.sub( parallaxDirection.mul( scale ) );
export const transformedBentNormalView = ( () => {
// https://google.github.io/filament/Filament.md.html#lighting/imagebasedlights/anisotropy
let bentNormal = anisotropyB.cross( positionViewDirection );
bentNormal = bentNormal.cross( anisotropyB ).normalize();
bentNormal = mix( bentNormal, transformedNormalView, anisotropy.mul( roughness.oneMinus() ).oneMinus().pow2().pow2() ).normalize();
return bentNormal;
} )();

View File

@ -0,0 +1,96 @@
import Node, { addNodeClass } from '../core/Node.js';
import { normalLocal } from './NormalNode.js';
import { positionLocal } from './PositionNode.js';
import { nodeProxy, vec3, mat3, mat4, int, ivec2, float, tslFn } from '../shadernode/ShaderNode.js';
import { textureLoad } from './TextureNode.js';
import { textureSize } from './TextureSizeNode.js';
import { tangentLocal } from './TangentNode.js';
import { instanceIndex, drawIndex } from '../core/IndexNode.js';
class BatchNode extends Node {
constructor( batchMesh ) {
super( 'void' );
this.batchMesh = batchMesh;
this.instanceColorNode = null;
this.batchingIdNode = null;
}
setup( builder ) {
// POSITION
if ( this.batchingIdNode === null ) {
if ( builder.getDrawIndex() === null ) {
this.batchingIdNode = instanceIndex;
} else {
this.batchingIdNode = drawIndex;
}
}
const getIndirectIndex = tslFn( ( [ id ] ) => {
const size = textureSize( textureLoad( this.batchMesh._indirectTexture ), 0 );
const x = int( id ).remainder( int( size ) );
const y = int( id ).div( int( size ) );
return textureLoad( this.batchMesh._indirectTexture, ivec2( x, y ) ).x;
} ).setLayout( {
name: 'getIndirectIndex',
type: 'uint',
inputs: [
{ name: 'id', type: 'int' }
]
} );
const matriceTexture = this.batchMesh._matricesTexture;
const size = textureSize( textureLoad( matriceTexture ), 0 );
const j = float( getIndirectIndex( int( this.batchingIdNode ) ) ).mul( 4 ).toVar();
const x = int( j.mod( size ) );
const y = int( j ).div( int( size ) );
const batchingMatrix = mat4(
textureLoad( matriceTexture, ivec2( x, y ) ),
textureLoad( matriceTexture, ivec2( x.add( 1 ), y ) ),
textureLoad( matriceTexture, ivec2( x.add( 2 ), y ) ),
textureLoad( matriceTexture, ivec2( x.add( 3 ), y ) )
);
const bm = mat3( batchingMatrix );
positionLocal.assign( batchingMatrix.mul( positionLocal ) );
const transformedNormal = normalLocal.div( vec3( bm[ 0 ].dot( bm[ 0 ] ), bm[ 1 ].dot( bm[ 1 ] ), bm[ 2 ].dot( bm[ 2 ] ) ) );
const batchingNormal = bm.mul( transformedNormal ).xyz;
normalLocal.assign( batchingNormal );
if ( builder.hasGeometryAttribute( 'tangent' ) ) {
tangentLocal.mulAssign( bm );
}
}
}
export default BatchNode;
export const batch = nodeProxy( BatchNode );
addNodeClass( 'batch', BatchNode );

View File

@ -0,0 +1,13 @@
import { varying } from '../core/VaryingNode.js';
import { cameraViewMatrix } from './CameraNode.js';
import { normalGeometry, normalLocal, normalView, normalWorld, transformedNormalView } from './NormalNode.js';
import { tangentGeometry, tangentLocal, tangentView, tangentWorld, transformedTangentView } from './TangentNode.js';
const getBitangent = ( crossNormalTangent ) => crossNormalTangent.mul( tangentGeometry.w ).xyz;
export const bitangentGeometry = /*#__PURE__*/ varying( getBitangent( normalGeometry.cross( tangentGeometry ) ), 'v_bitangentGeometry' ).normalize().toVar( 'bitangentGeometry' );
export const bitangentLocal = /*#__PURE__*/ varying( getBitangent( normalLocal.cross( tangentLocal ) ), 'v_bitangentLocal' ).normalize().toVar( 'bitangentLocal' );
export const bitangentView = /*#__PURE__*/ varying( getBitangent( normalView.cross( tangentView ) ), 'v_bitangentView' ).normalize().toVar( 'bitangentView' );
export const bitangentWorld = /*#__PURE__*/ varying( getBitangent( normalWorld.cross( tangentWorld ) ), 'v_bitangentWorld' ).normalize().toVar( 'bitangentWorld' );
export const transformedBitangentView = /*#__PURE__*/ getBitangent( transformedNormalView.cross( transformedTangentView ) ).normalize().toVar( 'transformedBitangentView' );
export const transformedBitangentWorld = /*#__PURE__*/ transformedBitangentView.transformDirection( cameraViewMatrix ).normalize().toVar( 'transformedBitangentWorld' );

View File

@ -0,0 +1,162 @@
import InputNode from '../core/InputNode.js';
import { addNodeClass } from '../core/Node.js';
import { varying } from '../core/VaryingNode.js';
import { nodeObject, addNodeElement } from '../shadernode/ShaderNode.js';
import { InterleavedBufferAttribute } from '../../core/InterleavedBufferAttribute.js';
import { InterleavedBuffer } from '../../core/InterleavedBuffer.js';
import { StaticDrawUsage, DynamicDrawUsage } from '../../constants.js';
class BufferAttributeNode extends InputNode {
constructor( value, bufferType = null, bufferStride = 0, bufferOffset = 0 ) {
super( value, bufferType );
this.isBufferNode = true;
this.bufferType = bufferType;
this.bufferStride = bufferStride;
this.bufferOffset = bufferOffset;
this.usage = StaticDrawUsage;
this.instanced = false;
this.attribute = null;
this.global = true;
if ( value && value.isBufferAttribute === true ) {
this.attribute = value;
this.usage = value.usage;
this.instanced = value.isInstancedBufferAttribute;
}
}
getHash( builder ) {
if ( this.bufferStride === 0 && this.bufferOffset === 0 ) {
let bufferData = builder.globalCache.getData( this.value );
if ( bufferData === undefined ) {
bufferData = {
node: this
};
builder.globalCache.setData( this.value, bufferData );
}
return bufferData.node.uuid;
}
return this.uuid;
}
getNodeType( builder ) {
if ( this.bufferType === null ) {
this.bufferType = builder.getTypeFromAttribute( this.attribute );
}
return this.bufferType;
}
setup( builder ) {
if ( this.attribute !== null ) return;
const type = this.getNodeType( builder );
const array = this.value;
const itemSize = builder.getTypeLength( type );
const stride = this.bufferStride || itemSize;
const offset = this.bufferOffset;
const buffer = array.isInterleavedBuffer === true ? array : new InterleavedBuffer( array, stride );
const bufferAttribute = new InterleavedBufferAttribute( buffer, itemSize, offset );
buffer.setUsage( this.usage );
this.attribute = bufferAttribute;
this.attribute.isInstancedBufferAttribute = this.instanced; // @TODO: Add a possible: InstancedInterleavedBufferAttribute
}
generate( builder ) {
const nodeType = this.getNodeType( builder );
const nodeAttribute = builder.getBufferAttributeFromNode( this, nodeType );
const propertyName = builder.getPropertyName( nodeAttribute );
let output = null;
if ( builder.shaderStage === 'vertex' || builder.shaderStage === 'compute' ) {
this.name = propertyName;
output = propertyName;
} else {
const nodeVarying = varying( this );
output = nodeVarying.build( builder, nodeType );
}
return output;
}
getInputType( /*builder*/ ) {
return 'bufferAttribute';
}
setUsage( value ) {
this.usage = value;
if ( this.attribute && this.attribute.isBufferAttribute === true ) {
this.attribute.usage = value;
}
return this;
}
setInstanced( value ) {
this.instanced = value;
return this;
}
}
export default BufferAttributeNode;
export const bufferAttribute = ( array, type, stride, offset ) => nodeObject( new BufferAttributeNode( array, type, stride, offset ) );
export const dynamicBufferAttribute = ( array, type, stride, offset ) => bufferAttribute( array, type, stride, offset ).setUsage( DynamicDrawUsage );
export const instancedBufferAttribute = ( array, type, stride, offset ) => bufferAttribute( array, type, stride, offset ).setInstanced( true );
export const instancedDynamicBufferAttribute = ( array, type, stride, offset ) => dynamicBufferAttribute( array, type, stride, offset ).setInstanced( true );
addNodeElement( 'toAttribute', ( bufferNode ) => bufferAttribute( bufferNode.value ) );
addNodeClass( 'BufferAttributeNode', BufferAttributeNode );

View File

@ -0,0 +1,36 @@
import UniformNode from '../core/UniformNode.js';
import { addNodeClass } from '../core/Node.js';
import { nodeObject } from '../shadernode/ShaderNode.js';
class BufferNode extends UniformNode {
constructor( value, bufferType, bufferCount = 0 ) {
super( value, bufferType );
this.isBufferNode = true;
this.bufferType = bufferType;
this.bufferCount = bufferCount;
}
getElementType( builder ) {
return this.getNodeType( builder );
}
getInputType( /*builder*/ ) {
return 'buffer';
}
}
export default BufferNode;
export const buffer = ( value, type, count ) => nodeObject( new BufferNode( value, type, count ) );
addNodeClass( 'BufferNode', BufferNode );

View File

@ -0,0 +1,19 @@
import { uniform } from '../core/UniformNode.js';
import { sharedUniformGroup } from '../core/UniformGroupNode.js';
import { Vector3 } from '../../math/Vector3.js';
const cameraGroup = /*#__PURE__*/ sharedUniformGroup( 'camera' ).onRenderUpdate( () => {
cameraGroup.needsUpdate = true;
} );
export const cameraNear = /*#__PURE__*/ uniform( 'float' ).label( 'cameraNear' ).setGroup( cameraGroup ).onRenderUpdate( ( { camera } ) => camera.near );
export const cameraFar = /*#__PURE__*/ uniform( 'float' ).label( 'cameraFar' ).setGroup( cameraGroup ).onRenderUpdate( ( { camera } ) => camera.far );
export const cameraLogDepth = /*#__PURE__*/ uniform( 'float' ).label( 'cameraLogDepth' ).setGroup( cameraGroup ).onRenderUpdate( ( { camera } ) => 2.0 / ( Math.log( camera.far + 1.0 ) / Math.LN2 ) );
export const cameraProjectionMatrix = /*#__PURE__*/ uniform( 'mat4' ).label( 'cameraProjectionMatrix' ).setGroup( cameraGroup ).onRenderUpdate( ( { camera } ) => camera.projectionMatrix );
export const cameraProjectionMatrixInverse = /*#__PURE__*/ uniform( 'mat4' ).label( 'cameraProjectionMatrixInverse' ).setGroup( cameraGroup ).onRenderUpdate( ( { camera } ) => camera.projectionMatrixInverse );
export const cameraViewMatrix = /*#__PURE__*/ uniform( 'mat4' ).label( 'cameraViewMatrix' ).setGroup( cameraGroup ).onRenderUpdate( ( { camera } ) => camera.matrixWorldInverse );
export const cameraWorldMatrix = /*#__PURE__*/ uniform( 'mat4' ).label( 'cameraWorldMatrix' ).setGroup( cameraGroup ).onRenderUpdate( ( { camera } ) => camera.matrixWorld );
export const cameraNormalMatrix = /*#__PURE__*/ uniform( 'mat3' ).label( 'cameraNormalMatrix' ).setGroup( cameraGroup ).onRenderUpdate( ( { camera } ) => camera.normalMatrix );
export const cameraPosition = /*#__PURE__*/ uniform( new Vector3() ).label( 'cameraPosition' ).setGroup( cameraGroup ).onRenderUpdate( ( { camera }, self ) => self.value.setFromMatrixPosition( camera.matrixWorld ) );

View File

@ -0,0 +1,145 @@
import Node from '../core/Node.js';
import { nodeObject } from '../shadernode/ShaderNode.js';
import { positionView } from './PositionNode.js';
import { diffuseColor, property } from '../core/PropertyNode.js';
import { tslFn } from '../shadernode/ShaderNode.js';
import { loop } from '../utils/LoopNode.js';
import { smoothstep } from '../math/MathNode.js';
import { uniforms } from './UniformsNode.js';
class ClippingNode extends Node {
constructor( scope = ClippingNode.DEFAULT ) {
super();
this.scope = scope;
}
setup( builder ) {
super.setup( builder );
const clippingContext = builder.clippingContext;
const { localClipIntersection, localClippingCount, globalClippingCount } = clippingContext;
const numClippingPlanes = globalClippingCount + localClippingCount;
const numUnionClippingPlanes = localClipIntersection ? numClippingPlanes - localClippingCount : numClippingPlanes;
if ( this.scope === ClippingNode.ALPHA_TO_COVERAGE ) {
return this.setupAlphaToCoverage( clippingContext.planes, numClippingPlanes, numUnionClippingPlanes );
} else {
return this.setupDefault( clippingContext.planes, numClippingPlanes, numUnionClippingPlanes );
}
}
setupAlphaToCoverage( planes, numClippingPlanes, numUnionClippingPlanes ) {
return tslFn( () => {
const clippingPlanes = uniforms( planes );
const distanceToPlane = property( 'float', 'distanceToPlane' );
const distanceGradient = property( 'float', 'distanceToGradient' );
const clipOpacity = property( 'float', 'clipOpacity' );
clipOpacity.assign( 1 );
let plane;
loop( numUnionClippingPlanes, ( { i } ) => {
plane = clippingPlanes.element( i );
distanceToPlane.assign( positionView.dot( plane.xyz ).negate().add( plane.w ) );
distanceGradient.assign( distanceToPlane.fwidth().div( 2.0 ) );
clipOpacity.mulAssign( smoothstep( distanceGradient.negate(), distanceGradient, distanceToPlane ) );
clipOpacity.equal( 0.0 ).discard();
} );
if ( numUnionClippingPlanes < numClippingPlanes ) {
const unionClipOpacity = property( 'float', 'unionclipOpacity' );
unionClipOpacity.assign( 1 );
loop( { start: numUnionClippingPlanes, end: numClippingPlanes }, ( { i } ) => {
plane = clippingPlanes.element( i );
distanceToPlane.assign( positionView.dot( plane.xyz ).negate().add( plane.w ) );
distanceGradient.assign( distanceToPlane.fwidth().div( 2.0 ) );
unionClipOpacity.mulAssign( smoothstep( distanceGradient.negate(), distanceGradient, distanceToPlane ).oneMinus() );
} );
clipOpacity.mulAssign( unionClipOpacity.oneMinus() );
}
diffuseColor.a.mulAssign( clipOpacity );
diffuseColor.a.equal( 0.0 ).discard();
} )();
}
setupDefault( planes, numClippingPlanes, numUnionClippingPlanes ) {
return tslFn( () => {
const clippingPlanes = uniforms( planes );
let plane;
loop( numUnionClippingPlanes, ( { i } ) => {
plane = clippingPlanes.element( i );
positionView.dot( plane.xyz ).greaterThan( plane.w ).discard();
} );
if ( numUnionClippingPlanes < numClippingPlanes ) {
const clipped = property( 'bool', 'clipped' );
clipped.assign( true );
loop( { start: numUnionClippingPlanes, end: numClippingPlanes }, ( { i } ) => {
plane = clippingPlanes.element( i );
clipped.assign( positionView.dot( plane.xyz ).greaterThan( plane.w ).and( clipped ) );
} );
clipped.discard();
}
} )();
}
}
ClippingNode.ALPHA_TO_COVERAGE = 'alphaToCoverage';
ClippingNode.DEFAULT = 'default';
export default ClippingNode;
export const clipping = () => nodeObject( new ClippingNode() );
export const clippingAlpha = () => nodeObject( new ClippingNode( ClippingNode.ALPHA_TO_COVERAGE ) );

View File

@ -0,0 +1,78 @@
import TextureNode from './TextureNode.js';
import { reflectVector, refractVector } from './ReflectVectorNode.js';
import { addNodeClass } from '../core/Node.js';
import { addNodeElement, nodeProxy, vec3 } from '../shadernode/ShaderNode.js';
import { CubeReflectionMapping, CubeRefractionMapping, WebGPUCoordinateSystem } from '../../constants.js';
class CubeTextureNode extends TextureNode {
constructor( value, uvNode = null, levelNode = null, biasNode = null ) {
super( value, uvNode, levelNode, biasNode );
this.isCubeTextureNode = true;
}
getInputType( /*builder*/ ) {
return 'cubeTexture';
}
getDefaultUV() {
const texture = this.value;
if ( texture.mapping === CubeReflectionMapping ) {
return reflectVector;
} else if ( texture.mapping === CubeRefractionMapping ) {
return refractVector;
} else {
console.error( 'THREE.CubeTextureNode: Mapping "%s" not supported.', texture.mapping );
return vec3( 0, 0, 0 );
}
}
setUpdateMatrix( /*updateMatrix*/ ) { } // Ignore .updateMatrix for CubeTextureNode
setupUV( builder, uvNode ) {
const texture = this.value;
if ( builder.renderer.coordinateSystem === WebGPUCoordinateSystem || ! texture.isRenderTargetTexture ) {
return vec3( uvNode.x.negate(), uvNode.yz );
} else {
return uvNode;
}
}
generateUV( builder, cubeUV ) {
return cubeUV.build( builder, 'vec3' );
}
}
export default CubeTextureNode;
export const cubeTexture = nodeProxy( CubeTextureNode );
addNodeElement( 'cubeTexture', cubeTexture );
addNodeClass( 'CubeTextureNode', CubeTextureNode );

View File

@ -0,0 +1,140 @@
import Node, { addNodeClass } from '../core/Node.js';
import { varyingProperty } from '../core/PropertyNode.js';
import { instancedBufferAttribute, instancedDynamicBufferAttribute } from './BufferAttributeNode.js';
import { normalLocal } from './NormalNode.js';
import { positionLocal } from './PositionNode.js';
import { nodeProxy, vec3, mat3, mat4 } from '../shadernode/ShaderNode.js';
import { NodeUpdateType } from '../core/constants.js';
import { buffer } from '../accessors/BufferNode.js';
import { instanceIndex } from '../core/IndexNode.js';
import { InstancedInterleavedBuffer } from '../../core/InstancedInterleavedBuffer.js';
import { InstancedBufferAttribute } from '../../core/InstancedBufferAttribute.js';
import { DynamicDrawUsage } from '../../constants.js';
class InstanceNode extends Node {
constructor( instanceMesh ) {
super( 'void' );
this.instanceMesh = instanceMesh;
this.instanceMatrixNode = null;
this.instanceColorNode = null;
this.updateType = NodeUpdateType.FRAME;
this.buffer = null;
this.bufferColor = null;
}
setup( /*builder*/ ) {
let instanceMatrixNode = this.instanceMatrixNode;
let instanceColorNode = this.instanceColorNode;
const instanceMesh = this.instanceMesh;
if ( instanceMatrixNode === null ) {
const instanceAttribute = instanceMesh.instanceMatrix;
// Both WebGPU and WebGL backends have UBO max limited to 64kb. Matrix count number bigger than 1000 ( 16 * 4 * 1000 = 64kb ) will fallback to attribute.
if ( instanceMesh.count <= 1000 ) {
instanceMatrixNode = buffer( instanceAttribute.array, 'mat4', instanceMesh.count ).element( instanceIndex );
} else {
const buffer = new InstancedInterleavedBuffer( instanceAttribute.array, 16, 1 );
this.buffer = buffer;
const bufferFn = instanceAttribute.usage === DynamicDrawUsage ? instancedDynamicBufferAttribute : instancedBufferAttribute;
const instanceBuffers = [
// F.Signature -> bufferAttribute( array, type, stride, offset )
bufferFn( buffer, 'vec4', 16, 0 ),
bufferFn( buffer, 'vec4', 16, 4 ),
bufferFn( buffer, 'vec4', 16, 8 ),
bufferFn( buffer, 'vec4', 16, 12 )
];
instanceMatrixNode = mat4( ...instanceBuffers );
}
this.instanceMatrixNode = instanceMatrixNode;
}
const instanceColorAttribute = instanceMesh.instanceColor;
if ( instanceColorAttribute && instanceColorNode === null ) {
const buffer = new InstancedBufferAttribute( instanceColorAttribute.array, 3 );
const bufferFn = instanceColorAttribute.usage === DynamicDrawUsage ? instancedDynamicBufferAttribute : instancedBufferAttribute;
this.bufferColor = buffer;
instanceColorNode = vec3( bufferFn( buffer, 'vec3', 3, 0 ) );
this.instanceColorNode = instanceColorNode;
}
// POSITION
const instancePosition = instanceMatrixNode.mul( positionLocal ).xyz;
// NORMAL
const m = mat3( instanceMatrixNode );
const transformedNormal = normalLocal.div( vec3( m[ 0 ].dot( m[ 0 ] ), m[ 1 ].dot( m[ 1 ] ), m[ 2 ].dot( m[ 2 ] ) ) );
const instanceNormal = m.mul( transformedNormal ).xyz;
// ASSIGNS
positionLocal.assign( instancePosition );
normalLocal.assign( instanceNormal );
// COLOR
if ( this.instanceColorNode !== null ) {
varyingProperty( 'vec3', 'vInstanceColor' ).assign( this.instanceColorNode );
}
}
update( /*frame*/ ) {
if ( this.instanceMesh.instanceMatrix.usage !== DynamicDrawUsage && this.buffer != null && this.instanceMesh.instanceMatrix.version !== this.buffer.version ) {
this.buffer.version = this.instanceMesh.instanceMatrix.version;
}
if ( this.instanceMesh.instanceColor && this.instanceMesh.instanceColor.usage !== DynamicDrawUsage && this.bufferColor != null && this.instanceMesh.instanceColor.version !== this.bufferColor.version ) {
this.bufferColor.version = this.instanceMesh.instanceColor.version;
}
}
}
export default InstanceNode;
export const instance = nodeProxy( InstanceNode );
addNodeClass( 'InstanceNode', InstanceNode );

View File

@ -0,0 +1,21 @@
import MaterialNode from './MaterialNode.js';
import { addNodeClass } from '../core/Node.js';
import { nodeImmutable } from '../shadernode/ShaderNode.js';
class InstancedPointsMaterialNode extends MaterialNode {
setup( /*builder*/ ) {
return this.getFloat( this.scope );
}
}
InstancedPointsMaterialNode.POINT_WIDTH = 'pointWidth';
export default InstancedPointsMaterialNode;
export const materialPointWidth = nodeImmutable( InstancedPointsMaterialNode, InstancedPointsMaterialNode.POINT_WIDTH );
addNodeClass( 'InstancedPointsMaterialNode', InstancedPointsMaterialNode );

View File

@ -0,0 +1,440 @@
import Node, { addNodeClass } from '../core/Node.js';
import { reference } from './ReferenceNode.js';
import { materialReference } from './MaterialReferenceNode.js';
import { normalView } from './NormalNode.js';
import { nodeImmutable, float, vec2, mat2 } from '../shadernode/ShaderNode.js';
import { uniform } from '../core/UniformNode.js';
import { Vector2 } from '../../math/Vector2.js';
const _propertyCache = new Map();
class MaterialNode extends Node {
constructor( scope ) {
super();
this.scope = scope;
}
getCache( property, type ) {
let node = _propertyCache.get( property );
if ( node === undefined ) {
node = materialReference( property, type );
_propertyCache.set( property, node );
}
return node;
}
getFloat( property ) {
return this.getCache( property, 'float' );
}
getColor( property ) {
return this.getCache( property, 'color' );
}
getTexture( property ) {
return this.getCache( property === 'map' ? 'map' : property + 'Map', 'texture' );
}
setup( builder ) {
const material = builder.context.material;
const scope = this.scope;
let node = null;
if ( scope === MaterialNode.COLOR ) {
const colorNode = this.getColor( scope );
if ( material.map && material.map.isTexture === true ) {
node = colorNode.mul( this.getTexture( 'map' ) );
} else {
node = colorNode;
}
} else if ( scope === MaterialNode.OPACITY ) {
const opacityNode = this.getFloat( scope );
if ( material.alphaMap && material.alphaMap.isTexture === true ) {
node = opacityNode.mul( this.getTexture( 'alpha' ) );
} else {
node = opacityNode;
}
} else if ( scope === MaterialNode.SPECULAR_STRENGTH ) {
if ( material.specularMap && material.specularMap.isTexture === true ) {
node = this.getTexture( 'specular' ).r;
} else {
node = float( 1 );
}
} else if ( scope === MaterialNode.SPECULAR_INTENSITY ) {
const specularIntensity = this.getFloat( scope );
if ( material.specularMap ) {
node = specularIntensity.mul( this.getTexture( scope ).a );
} else {
node = specularIntensity;
}
} else if ( scope === MaterialNode.SPECULAR_COLOR ) {
const specularColorNode = this.getColor( scope );
if ( material.specularColorMap && material.specularColorMap.isTexture === true ) {
node = specularColorNode.mul( this.getTexture( scope ).rgb );
} else {
node = specularColorNode;
}
} else if ( scope === MaterialNode.ROUGHNESS ) { // TODO: cleanup similar branches
const roughnessNode = this.getFloat( scope );
if ( material.roughnessMap && material.roughnessMap.isTexture === true ) {
node = roughnessNode.mul( this.getTexture( scope ).g );
} else {
node = roughnessNode;
}
} else if ( scope === MaterialNode.METALNESS ) {
const metalnessNode = this.getFloat( scope );
if ( material.metalnessMap && material.metalnessMap.isTexture === true ) {
node = metalnessNode.mul( this.getTexture( scope ).b );
} else {
node = metalnessNode;
}
} else if ( scope === MaterialNode.EMISSIVE ) {
const emissiveIntensityNode = this.getFloat( 'emissiveIntensity' );
const emissiveNode = this.getColor( scope ).mul( emissiveIntensityNode );
if ( material.emissiveMap && material.emissiveMap.isTexture === true ) {
node = emissiveNode.mul( this.getTexture( scope ) );
} else {
node = emissiveNode;
}
} else if ( scope === MaterialNode.NORMAL ) {
if ( material.normalMap ) {
node = this.getTexture( 'normal' ).normalMap( this.getCache( 'normalScale', 'vec2' ) );
} else if ( material.bumpMap ) {
node = this.getTexture( 'bump' ).r.bumpMap( this.getFloat( 'bumpScale' ) );
} else {
node = normalView;
}
} else if ( scope === MaterialNode.CLEARCOAT ) {
const clearcoatNode = this.getFloat( scope );
if ( material.clearcoatMap && material.clearcoatMap.isTexture === true ) {
node = clearcoatNode.mul( this.getTexture( scope ).r );
} else {
node = clearcoatNode;
}
} else if ( scope === MaterialNode.CLEARCOAT_ROUGHNESS ) {
const clearcoatRoughnessNode = this.getFloat( scope );
if ( material.clearcoatRoughnessMap && material.clearcoatRoughnessMap.isTexture === true ) {
node = clearcoatRoughnessNode.mul( this.getTexture( scope ).r );
} else {
node = clearcoatRoughnessNode;
}
} else if ( scope === MaterialNode.CLEARCOAT_NORMAL ) {
if ( material.clearcoatNormalMap ) {
node = this.getTexture( scope ).normalMap( this.getCache( scope + 'Scale', 'vec2' ) );
} else {
node = normalView;
}
} else if ( scope === MaterialNode.SHEEN ) {
const sheenNode = this.getColor( 'sheenColor' ).mul( this.getFloat( 'sheen' ) ); // Move this mul() to CPU
if ( material.sheenColorMap && material.sheenColorMap.isTexture === true ) {
node = sheenNode.mul( this.getTexture( 'sheenColor' ).rgb );
} else {
node = sheenNode;
}
} else if ( scope === MaterialNode.SHEEN_ROUGHNESS ) {
const sheenRoughnessNode = this.getFloat( scope );
if ( material.sheenRoughnessMap && material.sheenRoughnessMap.isTexture === true ) {
node = sheenRoughnessNode.mul( this.getTexture( scope ).a );
} else {
node = sheenRoughnessNode;
}
node = node.clamp( 0.07, 1.0 );
} else if ( scope === MaterialNode.ANISOTROPY ) {
if ( material.anisotropyMap && material.anisotropyMap.isTexture === true ) {
const anisotropyPolar = this.getTexture( scope );
const anisotropyMat = mat2( materialAnisotropyVector.x, materialAnisotropyVector.y, materialAnisotropyVector.y.negate(), materialAnisotropyVector.x );
node = anisotropyMat.mul( anisotropyPolar.rg.mul( 2.0 ).sub( vec2( 1.0 ) ).normalize().mul( anisotropyPolar.b ) );
} else {
node = materialAnisotropyVector;
}
} else if ( scope === MaterialNode.IRIDESCENCE_THICKNESS ) {
const iridescenceThicknessMaximum = reference( '1', 'float', material.iridescenceThicknessRange );
if ( material.iridescenceThicknessMap ) {
const iridescenceThicknessMinimum = reference( '0', 'float', material.iridescenceThicknessRange );
node = iridescenceThicknessMaximum.sub( iridescenceThicknessMinimum ).mul( this.getTexture( scope ).g ).add( iridescenceThicknessMinimum );
} else {
node = iridescenceThicknessMaximum;
}
} else if ( scope === MaterialNode.TRANSMISSION ) {
const transmissionNode = this.getFloat( scope );
if ( material.transmissionMap ) {
node = transmissionNode.mul( this.getTexture( scope ).r );
} else {
node = transmissionNode;
}
} else if ( scope === MaterialNode.THICKNESS ) {
const thicknessNode = this.getFloat( scope );
if ( material.thicknessMap ) {
node = thicknessNode.mul( this.getTexture( scope ).g );
} else {
node = thicknessNode;
}
} else if ( scope === MaterialNode.IOR ) {
node = this.getFloat( scope );
} else if ( scope === MaterialNode.REFRACTION_RATIO ) {
node = this.getFloat( scope );
} else if ( scope === MaterialNode.LIGHT_MAP ) {
node = this.getTexture( scope ).rgb.mul( this.getFloat( 'lightMapIntensity' ) );
} else if ( scope === MaterialNode.AO_MAP ) {
node = this.getTexture( scope ).r.sub( 1.0 ).mul( this.getFloat( 'aoMapIntensity' ) ).add( 1.0 );
} else {
const outputType = this.getNodeType( builder );
node = this.getCache( scope, outputType );
}
return node;
}
}
MaterialNode.ALPHA_TEST = 'alphaTest';
MaterialNode.COLOR = 'color';
MaterialNode.OPACITY = 'opacity';
MaterialNode.SHININESS = 'shininess';
MaterialNode.SPECULAR = 'specular';
MaterialNode.SPECULAR_STRENGTH = 'specularStrength';
MaterialNode.SPECULAR_INTENSITY = 'specularIntensity';
MaterialNode.SPECULAR_COLOR = 'specularColor';
MaterialNode.REFLECTIVITY = 'reflectivity';
MaterialNode.ROUGHNESS = 'roughness';
MaterialNode.METALNESS = 'metalness';
MaterialNode.NORMAL = 'normal';
MaterialNode.CLEARCOAT = 'clearcoat';
MaterialNode.CLEARCOAT_ROUGHNESS = 'clearcoatRoughness';
MaterialNode.CLEARCOAT_NORMAL = 'clearcoatNormal';
MaterialNode.EMISSIVE = 'emissive';
MaterialNode.ROTATION = 'rotation';
MaterialNode.SHEEN = 'sheen';
MaterialNode.SHEEN_ROUGHNESS = 'sheenRoughness';
MaterialNode.ANISOTROPY = 'anisotropy';
MaterialNode.IRIDESCENCE = 'iridescence';
MaterialNode.IRIDESCENCE_IOR = 'iridescenceIOR';
MaterialNode.IRIDESCENCE_THICKNESS = 'iridescenceThickness';
MaterialNode.IOR = 'ior';
MaterialNode.TRANSMISSION = 'transmission';
MaterialNode.THICKNESS = 'thickness';
MaterialNode.ATTENUATION_DISTANCE = 'attenuationDistance';
MaterialNode.ATTENUATION_COLOR = 'attenuationColor';
MaterialNode.LINE_SCALE = 'scale';
MaterialNode.LINE_DASH_SIZE = 'dashSize';
MaterialNode.LINE_GAP_SIZE = 'gapSize';
MaterialNode.LINE_WIDTH = 'linewidth';
MaterialNode.LINE_DASH_OFFSET = 'dashOffset';
MaterialNode.POINT_WIDTH = 'pointWidth';
MaterialNode.DISPERSION = 'dispersion';
MaterialNode.LIGHT_MAP = 'light';
MaterialNode.AO_MAP = 'ao';
MaterialNode.REFRACTION_RATIO = 'refractionRatio';
export default MaterialNode;
export const materialAlphaTest = nodeImmutable( MaterialNode, MaterialNode.ALPHA_TEST );
export const materialColor = nodeImmutable( MaterialNode, MaterialNode.COLOR );
export const materialShininess = nodeImmutable( MaterialNode, MaterialNode.SHININESS );
export const materialEmissive = nodeImmutable( MaterialNode, MaterialNode.EMISSIVE );
export const materialOpacity = nodeImmutable( MaterialNode, MaterialNode.OPACITY );
export const materialSpecular = nodeImmutable( MaterialNode, MaterialNode.SPECULAR );
export const materialSpecularIntensity = nodeImmutable( MaterialNode, MaterialNode.SPECULAR_INTENSITY );
export const materialSpecularColor = nodeImmutable( MaterialNode, MaterialNode.SPECULAR_COLOR );
export const materialSpecularStrength = nodeImmutable( MaterialNode, MaterialNode.SPECULAR_STRENGTH );
export const materialReflectivity = nodeImmutable( MaterialNode, MaterialNode.REFLECTIVITY );
export const materialRoughness = nodeImmutable( MaterialNode, MaterialNode.ROUGHNESS );
export const materialMetalness = nodeImmutable( MaterialNode, MaterialNode.METALNESS );
export const materialNormal = nodeImmutable( MaterialNode, MaterialNode.NORMAL );
export const materialClearcoat = nodeImmutable( MaterialNode, MaterialNode.CLEARCOAT );
export const materialClearcoatRoughness = nodeImmutable( MaterialNode, MaterialNode.CLEARCOAT_ROUGHNESS );
export const materialClearcoatNormal = nodeImmutable( MaterialNode, MaterialNode.CLEARCOAT_NORMAL );
export const materialRotation = nodeImmutable( MaterialNode, MaterialNode.ROTATION );
export const materialSheen = nodeImmutable( MaterialNode, MaterialNode.SHEEN );
export const materialSheenRoughness = nodeImmutable( MaterialNode, MaterialNode.SHEEN_ROUGHNESS );
export const materialAnisotropy = nodeImmutable( MaterialNode, MaterialNode.ANISOTROPY );
export const materialIridescence = nodeImmutable( MaterialNode, MaterialNode.IRIDESCENCE );
export const materialIridescenceIOR = nodeImmutable( MaterialNode, MaterialNode.IRIDESCENCE_IOR );
export const materialIridescenceThickness = nodeImmutable( MaterialNode, MaterialNode.IRIDESCENCE_THICKNESS );
export const materialTransmission = nodeImmutable( MaterialNode, MaterialNode.TRANSMISSION );
export const materialThickness = nodeImmutable( MaterialNode, MaterialNode.THICKNESS );
export const materialIOR = nodeImmutable( MaterialNode, MaterialNode.IOR );
export const materialAttenuationDistance = nodeImmutable( MaterialNode, MaterialNode.ATTENUATION_DISTANCE );
export const materialAttenuationColor = nodeImmutable( MaterialNode, MaterialNode.ATTENUATION_COLOR );
export const materialLineScale = nodeImmutable( MaterialNode, MaterialNode.LINE_SCALE );
export const materialLineDashSize = nodeImmutable( MaterialNode, MaterialNode.LINE_DASH_SIZE );
export const materialLineGapSize = nodeImmutable( MaterialNode, MaterialNode.LINE_GAP_SIZE );
export const materialLineWidth = nodeImmutable( MaterialNode, MaterialNode.LINE_WIDTH );
export const materialLineDashOffset = nodeImmutable( MaterialNode, MaterialNode.LINE_DASH_OFFSET );
export const materialPointWidth = nodeImmutable( MaterialNode, MaterialNode.POINT_WIDTH );
export const materialDispersion = nodeImmutable( MaterialNode, MaterialNode.DISPERSION );
export const materialLightMap = nodeImmutable( MaterialNode, MaterialNode.LIGHT_MAP );
export const materialAOMap = nodeImmutable( MaterialNode, MaterialNode.AO_MAP );
export const materialRefractionRatio = nodeImmutable( MaterialNode, MaterialNode.REFRACTION_RATIO );
export const materialAnisotropyVector = uniform( new Vector2() ).onReference( function ( frame ) {
return frame.material;
} ).onRenderUpdate( function ( { material } ) {
this.value.set( material.anisotropy * Math.cos( material.anisotropyRotation ), material.anisotropy * Math.sin( material.anisotropyRotation ) );
} );
addNodeClass( 'MaterialNode', MaterialNode );

View File

@ -0,0 +1,41 @@
import ReferenceNode from './ReferenceNode.js';
//import { renderGroup } from '../core/UniformGroupNode.js';
//import { NodeUpdateType } from '../core/constants.js';
import { addNodeClass } from '../core/Node.js';
import { nodeObject } from '../shadernode/ShaderNode.js';
class MaterialReferenceNode extends ReferenceNode {
constructor( property, inputType, material = null ) {
super( property, inputType, material );
this.material = material;
//this.updateType = NodeUpdateType.RENDER;
}
/*setNodeType( node ) {
super.setNodeType( node );
this.node.groupNode = renderGroup;
}*/
updateReference( state ) {
this.reference = this.material !== null ? this.material : state.material;
return this.reference;
}
}
export default MaterialReferenceNode;
export const materialReference = ( name, type, material ) => nodeObject( new MaterialReferenceNode( name, type, material ) );
addNodeClass( 'MaterialReferenceNode', MaterialReferenceNode );

View File

@ -0,0 +1,37 @@
import Object3DNode from './Object3DNode.js';
import { addNodeClass } from '../core/Node.js';
import { nodeImmutable } from '../shadernode/ShaderNode.js';
import { uniform } from '../core/UniformNode.js';
import { Matrix4 } from '../../math/Matrix4.js';
class ModelNode extends Object3DNode {
constructor( scope = ModelNode.VIEW_MATRIX ) {
super( scope );
}
update( frame ) {
this.object3d = frame.object;
super.update( frame );
}
}
export default ModelNode;
export const modelDirection = nodeImmutable( ModelNode, ModelNode.DIRECTION );
export const modelViewMatrix = nodeImmutable( ModelNode, ModelNode.VIEW_MATRIX ).label( 'modelViewMatrix' ).temp( 'ModelViewMatrix' );
export const modelNormalMatrix = nodeImmutable( ModelNode, ModelNode.NORMAL_MATRIX );
export const modelWorldMatrix = nodeImmutable( ModelNode, ModelNode.WORLD_MATRIX );
export const modelPosition = nodeImmutable( ModelNode, ModelNode.POSITION );
export const modelScale = nodeImmutable( ModelNode, ModelNode.SCALE );
export const modelViewPosition = nodeImmutable( ModelNode, ModelNode.VIEW_POSITION );
export const modelWorldMatrixInverse = uniform( new Matrix4() ).onObjectUpdate( ( { object }, self ) => self.value.copy( object.matrixWorld ).invert() );
addNodeClass( 'ModelNode', ModelNode );

View File

@ -0,0 +1,39 @@
import { addNodeClass } from '../core/Node.js';
import TempNode from '../core/TempNode.js';
import { cameraProjectionMatrix } from './CameraNode.js';
import { modelViewMatrix } from './ModelNode.js';
import { positionLocal } from './PositionNode.js';
import { nodeProxy } from '../shadernode/ShaderNode.js';
import { varying } from '../core/VaryingNode.js';
class ModelViewProjectionNode extends TempNode {
constructor( positionNode = null ) {
super( 'vec4' );
this.positionNode = positionNode;
}
setup( builder ) {
if ( builder.shaderStage === 'fragment' ) {
return varying( builder.context.mvp );
}
const position = this.positionNode || positionLocal;
return cameraProjectionMatrix.mul( modelViewMatrix ).mul( position );
}
}
export default ModelViewProjectionNode;
export const modelViewProjection = nodeProxy( ModelViewProjectionNode );
addNodeClass( 'ModelViewProjectionNode', ModelViewProjectionNode );

View File

@ -0,0 +1,259 @@
import Node, { addNodeClass } from '../core/Node.js';
import { NodeUpdateType } from '../core/constants.js';
import { float, nodeProxy, tslFn } from '../shadernode/ShaderNode.js';
import { uniform } from '../core/UniformNode.js';
import { reference } from './ReferenceNode.js';
import { positionLocal } from './PositionNode.js';
import { normalLocal } from './NormalNode.js';
import { textureLoad } from './TextureNode.js';
import { instanceIndex, vertexIndex } from '../core/IndexNode.js';
import { ivec2, int } from '../shadernode/ShaderNode.js';
import { loop } from '../utils/LoopNode.js';
import { DataArrayTexture } from '../../textures/DataArrayTexture.js';
import { Vector2 } from '../../math/Vector2.js';
import { Vector4 } from '../../math/Vector4.js';
import { FloatType } from '../../constants.js';
const _morphTextures = new WeakMap();
const _morphVec4 = /*@__PURE__*/ new Vector4();
const getMorph = tslFn( ( { bufferMap, influence, stride, width, depth, offset } ) => {
const texelIndex = int( vertexIndex ).mul( stride ).add( offset );
const y = texelIndex.div( width );
const x = texelIndex.sub( y.mul( width ) );
const bufferAttrib = textureLoad( bufferMap, ivec2( x, y ) ).depth( depth );
return bufferAttrib.mul( influence );
} );
function getEntry( geometry ) {
const hasMorphPosition = geometry.morphAttributes.position !== undefined;
const hasMorphNormals = geometry.morphAttributes.normal !== undefined;
const hasMorphColors = geometry.morphAttributes.color !== undefined;
// instead of using attributes, the WebGL 2 code path encodes morph targets
// into an array of data textures. Each layer represents a single morph target.
const morphAttribute = geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color;
const morphTargetsCount = ( morphAttribute !== undefined ) ? morphAttribute.length : 0;
let entry = _morphTextures.get( geometry );
if ( entry === undefined || entry.count !== morphTargetsCount ) {
if ( entry !== undefined ) entry.texture.dispose();
const morphTargets = geometry.morphAttributes.position || [];
const morphNormals = geometry.morphAttributes.normal || [];
const morphColors = geometry.morphAttributes.color || [];
let vertexDataCount = 0;
if ( hasMorphPosition === true ) vertexDataCount = 1;
if ( hasMorphNormals === true ) vertexDataCount = 2;
if ( hasMorphColors === true ) vertexDataCount = 3;
let width = geometry.attributes.position.count * vertexDataCount;
let height = 1;
const maxTextureSize = 4096; // @TODO: Use 'capabilities.maxTextureSize'
if ( width > maxTextureSize ) {
height = Math.ceil( width / maxTextureSize );
width = maxTextureSize;
}
const buffer = new Float32Array( width * height * 4 * morphTargetsCount );
const bufferTexture = new DataArrayTexture( buffer, width, height, morphTargetsCount );
bufferTexture.type = FloatType;
bufferTexture.needsUpdate = true;
// fill buffer
const vertexDataStride = vertexDataCount * 4;
for ( let i = 0; i < morphTargetsCount; i ++ ) {
const morphTarget = morphTargets[ i ];
const morphNormal = morphNormals[ i ];
const morphColor = morphColors[ i ];
const offset = width * height * 4 * i;
for ( let j = 0; j < morphTarget.count; j ++ ) {
const stride = j * vertexDataStride;
if ( hasMorphPosition === true ) {
_morphVec4.fromBufferAttribute( morphTarget, j );
buffer[ offset + stride + 0 ] = _morphVec4.x;
buffer[ offset + stride + 1 ] = _morphVec4.y;
buffer[ offset + stride + 2 ] = _morphVec4.z;
buffer[ offset + stride + 3 ] = 0;
}
if ( hasMorphNormals === true ) {
_morphVec4.fromBufferAttribute( morphNormal, j );
buffer[ offset + stride + 4 ] = _morphVec4.x;
buffer[ offset + stride + 5 ] = _morphVec4.y;
buffer[ offset + stride + 6 ] = _morphVec4.z;
buffer[ offset + stride + 7 ] = 0;
}
if ( hasMorphColors === true ) {
_morphVec4.fromBufferAttribute( morphColor, j );
buffer[ offset + stride + 8 ] = _morphVec4.x;
buffer[ offset + stride + 9 ] = _morphVec4.y;
buffer[ offset + stride + 10 ] = _morphVec4.z;
buffer[ offset + stride + 11 ] = ( morphColor.itemSize === 4 ) ? _morphVec4.w : 1;
}
}
}
entry = {
count: morphTargetsCount,
texture: bufferTexture,
stride: vertexDataCount,
size: new Vector2( width, height )
};
_morphTextures.set( geometry, entry );
function disposeTexture() {
bufferTexture.dispose();
_morphTextures.delete( geometry );
geometry.removeEventListener( 'dispose', disposeTexture );
}
geometry.addEventListener( 'dispose', disposeTexture );
}
return entry;
}
class MorphNode extends Node {
constructor( mesh ) {
super( 'void' );
this.mesh = mesh;
this.morphBaseInfluence = uniform( 1 );
this.updateType = NodeUpdateType.OBJECT;
}
setup( builder ) {
const { geometry } = builder;
const hasMorphPosition = geometry.morphAttributes.position !== undefined;
const hasMorphNormals = geometry.morphAttributes.normal !== undefined;
const morphAttribute = geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color;
const morphTargetsCount = ( morphAttribute !== undefined ) ? morphAttribute.length : 0;
// nodes
const { texture: bufferMap, stride, size } = getEntry( geometry );
if ( hasMorphPosition === true ) positionLocal.mulAssign( this.morphBaseInfluence );
if ( hasMorphNormals === true ) normalLocal.mulAssign( this.morphBaseInfluence );
const width = int( size.width );
loop( morphTargetsCount, ( { i } ) => {
const influence = float( 0 ).toVar();
if ( this.mesh.count > 1 && ( this.mesh.morphTexture !== null && this.mesh.morphTexture !== undefined ) ) {
influence.assign( textureLoad( this.mesh.morphTexture, ivec2( int( i ).add( 1 ), int( instanceIndex ) ) ).r );
} else {
influence.assign( reference( 'morphTargetInfluences', 'float' ).element( i ).toVar() );
}
if ( hasMorphPosition === true ) {
positionLocal.addAssign( getMorph( {
bufferMap,
influence,
stride,
width,
depth: i,
offset: int( 0 )
} ) );
}
if ( hasMorphNormals === true ) {
normalLocal.addAssign( getMorph( {
bufferMap,
influence,
stride,
width,
depth: i,
offset: int( 1 )
} ) );
}
} );
}
update() {
const morphBaseInfluence = this.morphBaseInfluence;
if ( this.mesh.geometry.morphTargetsRelative ) {
morphBaseInfluence.value = 1;
} else {
morphBaseInfluence.value = 1 - this.mesh.morphTargetInfluences.reduce( ( a, b ) => a + b, 0 );
}
}
}
export default MorphNode;
export const morphReference = nodeProxy( MorphNode );
addNodeClass( 'MorphNode', MorphNode );

View File

@ -0,0 +1,14 @@
import { attribute } from '../core/AttributeNode.js';
import { varying } from '../core/VaryingNode.js';
import { property } from '../core/PropertyNode.js';
import { cameraViewMatrix } from './CameraNode.js';
import { modelNormalMatrix } from './ModelNode.js';
import { vec3 } from '../shadernode/ShaderNode.js';
export const normalGeometry = /*#__PURE__*/ attribute( 'normal', 'vec3', vec3( 0, 1, 0 ) );
export const normalLocal = /*#__PURE__*/ normalGeometry.toVar( 'normalLocal' );
export const normalView = /*#__PURE__*/ varying( modelNormalMatrix.mul( normalLocal ), 'v_normalView' ).normalize().toVar( 'normalView' );
export const normalWorld = /*#__PURE__*/ varying( normalView.transformDirection( cameraViewMatrix ), 'v_normalWorld' ).normalize().toVar( 'normalWorld' );
export const transformedNormalView = /*#__PURE__*/ property( 'vec3', 'transformedNormalView' );
export const transformedNormalWorld = /*#__PURE__*/ transformedNormalView.transformDirection( cameraViewMatrix ).normalize().toVar( 'transformedNormalWorld' );
export const transformedClearcoatNormalView = /*#__PURE__*/ property( 'vec3', 'transformedClearcoatNormalView' );

View File

@ -0,0 +1,150 @@
import Node, { addNodeClass } from '../core/Node.js';
import { NodeUpdateType } from '../core/constants.js';
import UniformNode from '../core/UniformNode.js';
import { nodeProxy } from '../shadernode/ShaderNode.js';
import { Vector3 } from '../../math/Vector3.js';
class Object3DNode extends Node {
constructor( scope = Object3DNode.VIEW_MATRIX, object3d = null ) {
super();
this.scope = scope;
this.object3d = object3d;
this.updateType = NodeUpdateType.OBJECT;
this._uniformNode = new UniformNode( null );
}
getNodeType() {
const scope = this.scope;
if ( scope === Object3DNode.WORLD_MATRIX || scope === Object3DNode.VIEW_MATRIX ) {
return 'mat4';
} else if ( scope === Object3DNode.NORMAL_MATRIX ) {
return 'mat3';
} else if ( scope === Object3DNode.POSITION || scope === Object3DNode.VIEW_POSITION || scope === Object3DNode.DIRECTION || scope === Object3DNode.SCALE ) {
return 'vec3';
}
}
update( frame ) {
const object = this.object3d;
const uniformNode = this._uniformNode;
const scope = this.scope;
if ( scope === Object3DNode.VIEW_MATRIX ) {
uniformNode.value = object.modelViewMatrix;
} else if ( scope === Object3DNode.NORMAL_MATRIX ) {
uniformNode.value = object.normalMatrix;
} else if ( scope === Object3DNode.WORLD_MATRIX ) {
uniformNode.value = object.matrixWorld;
} else if ( scope === Object3DNode.POSITION ) {
uniformNode.value = uniformNode.value || new Vector3();
uniformNode.value.setFromMatrixPosition( object.matrixWorld );
} else if ( scope === Object3DNode.SCALE ) {
uniformNode.value = uniformNode.value || new Vector3();
uniformNode.value.setFromMatrixScale( object.matrixWorld );
} else if ( scope === Object3DNode.DIRECTION ) {
uniformNode.value = uniformNode.value || new Vector3();
object.getWorldDirection( uniformNode.value );
} else if ( scope === Object3DNode.VIEW_POSITION ) {
const camera = frame.camera;
uniformNode.value = uniformNode.value || new Vector3();
uniformNode.value.setFromMatrixPosition( object.matrixWorld );
uniformNode.value.applyMatrix4( camera.matrixWorldInverse );
}
}
generate( builder ) {
const scope = this.scope;
if ( scope === Object3DNode.WORLD_MATRIX || scope === Object3DNode.VIEW_MATRIX ) {
this._uniformNode.nodeType = 'mat4';
} else if ( scope === Object3DNode.NORMAL_MATRIX ) {
this._uniformNode.nodeType = 'mat3';
} else if ( scope === Object3DNode.POSITION || scope === Object3DNode.VIEW_POSITION || scope === Object3DNode.DIRECTION || scope === Object3DNode.SCALE ) {
this._uniformNode.nodeType = 'vec3';
}
return this._uniformNode.build( builder );
}
serialize( data ) {
super.serialize( data );
data.scope = this.scope;
}
deserialize( data ) {
super.deserialize( data );
this.scope = data.scope;
}
}
Object3DNode.VIEW_MATRIX = 'viewMatrix';
Object3DNode.NORMAL_MATRIX = 'normalMatrix';
Object3DNode.WORLD_MATRIX = 'worldMatrix';
Object3DNode.POSITION = 'position';
Object3DNode.SCALE = 'scale';
Object3DNode.VIEW_POSITION = 'viewPosition';
Object3DNode.DIRECTION = 'direction';
export default Object3DNode;
export const objectDirection = nodeProxy( Object3DNode, Object3DNode.DIRECTION );
export const objectViewMatrix = nodeProxy( Object3DNode, Object3DNode.VIEW_MATRIX );
export const objectNormalMatrix = nodeProxy( Object3DNode, Object3DNode.NORMAL_MATRIX );
export const objectWorldMatrix = nodeProxy( Object3DNode, Object3DNode.WORLD_MATRIX );
export const objectPosition = nodeProxy( Object3DNode, Object3DNode.POSITION );
export const objectScale = nodeProxy( Object3DNode, Object3DNode.SCALE );
export const objectViewPosition = nodeProxy( Object3DNode, Object3DNode.VIEW_POSITION );
addNodeClass( 'Object3DNode', Object3DNode );

View File

@ -0,0 +1,26 @@
import Node, { addNodeClass } from '../core/Node.js';
import { nodeImmutable } from '../shadernode/ShaderNode.js';
class PointUVNode extends Node {
constructor() {
super( 'vec2' );
this.isPointUVNode = true;
}
generate( /*builder*/ ) {
return 'vec2( gl_PointCoord.x, 1.0 - gl_PointCoord.y )';
}
}
export default PointUVNode;
export const pointUV = nodeImmutable( PointUVNode );
addNodeClass( 'PointUVNode', PointUVNode );

View File

@ -0,0 +1,10 @@
import { attribute } from '../core/AttributeNode.js';
import { varying } from '../core/VaryingNode.js';
import { modelWorldMatrix, modelViewMatrix } from './ModelNode.js';
export const positionGeometry = /*#__PURE__*/ attribute( 'position', 'vec3' );
export const positionLocal = /*#__PURE__*/ positionGeometry.toVar( 'positionLocal' );
export const positionWorld = /*#__PURE__*/ varying( modelWorldMatrix.mul( positionLocal ).xyz, 'v_positionWorld' );
export const positionWorldDirection = /*#__PURE__*/ varying( positionLocal.transformDirection( modelWorldMatrix ), 'v_positionWorldDirection' ).normalize().toVar( 'positionWorldDirection' );
export const positionView = /*#__PURE__*/ varying( modelViewMatrix.mul( positionLocal ).xyz, 'v_positionView' );
export const positionViewDirection = /*#__PURE__*/ varying( positionView.negate(), 'v_positionViewDirection' ).normalize().toVar( 'positionViewDirection' );

View File

@ -0,0 +1,166 @@
import Node, { addNodeClass } from '../core/Node.js';
import { NodeUpdateType } from '../core/constants.js';
import { uniform } from '../core/UniformNode.js';
import { texture } from './TextureNode.js';
import { buffer } from './BufferNode.js';
import { nodeObject } from '../shadernode/ShaderNode.js';
import { uniforms } from './UniformsNode.js';
import ArrayElementNode from '../utils/ArrayElementNode.js';
class ReferenceElementNode extends ArrayElementNode {
constructor( referenceNode, indexNode ) {
super( referenceNode, indexNode );
this.referenceNode = referenceNode;
this.isReferenceElementNode = true;
}
getNodeType() {
return this.referenceNode.uniformType;
}
generate( builder ) {
const snippet = super.generate( builder );
const arrayType = this.referenceNode.getNodeType();
const elementType = this.getNodeType();
return builder.format( snippet, arrayType, elementType );
}
}
class ReferenceNode extends Node {
constructor( property, uniformType, object = null, count = null ) {
super();
this.property = property;
this.uniformType = uniformType;
this.object = object;
this.count = count;
this.properties = property.split( '.' );
this.reference = object;
this.node = null;
this.updateType = NodeUpdateType.OBJECT;
}
element( indexNode ) {
return nodeObject( new ReferenceElementNode( this, nodeObject( indexNode ) ) );
}
setNodeType( uniformType ) {
let node = null;
if ( this.count !== null ) {
node = buffer( null, uniformType, this.count );
} else if ( Array.isArray( this.getValueFromReference() ) ) {
node = uniforms( null, uniformType );
} else if ( uniformType === 'texture' ) {
node = texture( null );
} else {
node = uniform( null, uniformType );
}
this.node = node;
}
getNodeType( builder ) {
if ( this.node === null ) {
this.updateValue();
}
return this.node.getNodeType( builder );
}
getValueFromReference( object = this.reference ) {
const { properties } = this;
let value = object[ properties[ 0 ] ];
for ( let i = 1; i < properties.length; i ++ ) {
value = value[ properties[ i ] ];
}
return value;
}
updateReference( state ) {
this.reference = this.object !== null ? this.object : state.object;
return this.reference;
}
setup() {
this.updateValue();
return this.node;
}
update( /*frame*/ ) {
this.updateValue();
}
updateValue() {
if ( this.node === null ) this.setNodeType( this.uniformType );
const value = this.getValueFromReference();
if ( Array.isArray( value ) ) {
this.node.array = value;
} else {
this.node.value = value;
}
}
}
export default ReferenceNode;
export const reference = ( name, type, object ) => nodeObject( new ReferenceNode( name, type, object ) );
export const referenceBuffer = ( name, type, count, object ) => nodeObject( new ReferenceNode( name, type, object, count ) );
addNodeClass( 'ReferenceNode', ReferenceNode );

View File

@ -0,0 +1,10 @@
import { cameraViewMatrix } from './CameraNode.js';
import { transformedNormalView } from './NormalNode.js';
import { positionViewDirection } from './PositionNode.js';
import { materialRefractionRatio } from './MaterialNode.js';
export const reflectView = /*#__PURE__*/ positionViewDirection.negate().reflect( transformedNormalView );
export const refractView = /*#__PURE__*/ positionViewDirection.negate().refract( transformedNormalView, materialRefractionRatio );
export const reflectVector = /*#__PURE__*/ reflectView.transformDirection( cameraViewMatrix ).toVar( 'reflectVector' );
export const refractVector = /*#__PURE__*/ refractView.transformDirection( cameraViewMatrix ).toVar( 'reflectVector' );

View File

@ -0,0 +1,29 @@
import ReferenceNode from './ReferenceNode.js';
import { addNodeClass } from '../core/Node.js';
import { nodeObject } from '../shadernode/ShaderNode.js';
class RendererReferenceNode extends ReferenceNode {
constructor( property, inputType, renderer = null ) {
super( property, inputType, renderer );
this.renderer = renderer;
}
updateReference( state ) {
this.reference = this.renderer !== null ? this.renderer : state.renderer;
return this.reference;
}
}
export default RendererReferenceNode;
export const rendererReference = ( name, type, renderer ) => nodeObject( new RendererReferenceNode( name, type, renderer ) );
addNodeClass( 'RendererReferenceNode', RendererReferenceNode );

View File

@ -0,0 +1,52 @@
import Node from '../core/Node.js';
import { addNodeClass } from '../core/Node.js';
import { nodeImmutable } from '../shadernode/ShaderNode.js';
import { reference } from './ReferenceNode.js';
class SceneNode extends Node {
constructor( scope = SceneNode.BACKGROUND_BLURRINESS, scene = null ) {
super();
this.scope = scope;
this.scene = scene;
}
setup( builder ) {
const scope = this.scope;
const scene = this.scene !== null ? this.scene : builder.scene;
let output;
if ( scope === SceneNode.BACKGROUND_BLURRINESS ) {
output = reference( 'backgroundBlurriness', 'float', scene );
} else if ( scope === SceneNode.BACKGROUND_INTENSITY ) {
output = reference( 'backgroundIntensity', 'float', scene );
} else {
console.error( 'THREE.SceneNode: Unknown scope:', scope );
}
return output;
}
}
SceneNode.BACKGROUND_BLURRINESS = 'backgroundBlurriness';
SceneNode.BACKGROUND_INTENSITY = 'backgroundIntensity';
export default SceneNode;
export const backgroundBlurriness = nodeImmutable( SceneNode, SceneNode.BACKGROUND_BLURRINESS );
export const backgroundIntensity = nodeImmutable( SceneNode, SceneNode.BACKGROUND_INTENSITY );
addNodeClass( 'SceneNode', SceneNode );

View File

@ -0,0 +1,124 @@
import Node, { addNodeClass } from '../core/Node.js';
import { NodeUpdateType } from '../core/constants.js';
import { nodeObject } from '../shadernode/ShaderNode.js';
import { attribute } from '../core/AttributeNode.js';
import { reference, referenceBuffer } from './ReferenceNode.js';
import { add } from '../math/OperatorNode.js';
import { normalLocal } from './NormalNode.js';
import { positionLocal } from './PositionNode.js';
import { tangentLocal } from './TangentNode.js';
import { uniform } from '../core/UniformNode.js';
import { buffer } from './BufferNode.js';
class SkinningNode extends Node {
constructor( skinnedMesh, useReference = false ) {
super( 'void' );
this.skinnedMesh = skinnedMesh;
this.useReference = useReference;
this.updateType = NodeUpdateType.OBJECT;
//
this.skinIndexNode = attribute( 'skinIndex', 'uvec4' );
this.skinWeightNode = attribute( 'skinWeight', 'vec4' );
let bindMatrixNode, bindMatrixInverseNode, boneMatricesNode;
if ( useReference ) {
bindMatrixNode = reference( 'bindMatrix', 'mat4' );
bindMatrixInverseNode = reference( 'bindMatrixInverse', 'mat4' );
boneMatricesNode = referenceBuffer( 'skeleton.boneMatrices', 'mat4', skinnedMesh.skeleton.bones.length );
} else {
bindMatrixNode = uniform( skinnedMesh.bindMatrix, 'mat4' );
bindMatrixInverseNode = uniform( skinnedMesh.bindMatrixInverse, 'mat4' );
boneMatricesNode = buffer( skinnedMesh.skeleton.boneMatrices, 'mat4', skinnedMesh.skeleton.bones.length );
}
this.bindMatrixNode = bindMatrixNode;
this.bindMatrixInverseNode = bindMatrixInverseNode;
this.boneMatricesNode = boneMatricesNode;
}
setup( builder ) {
const { skinIndexNode, skinWeightNode, bindMatrixNode, bindMatrixInverseNode, boneMatricesNode } = this;
const boneMatX = boneMatricesNode.element( skinIndexNode.x );
const boneMatY = boneMatricesNode.element( skinIndexNode.y );
const boneMatZ = boneMatricesNode.element( skinIndexNode.z );
const boneMatW = boneMatricesNode.element( skinIndexNode.w );
// POSITION
const skinVertex = bindMatrixNode.mul( positionLocal );
const skinned = add(
boneMatX.mul( skinWeightNode.x ).mul( skinVertex ),
boneMatY.mul( skinWeightNode.y ).mul( skinVertex ),
boneMatZ.mul( skinWeightNode.z ).mul( skinVertex ),
boneMatW.mul( skinWeightNode.w ).mul( skinVertex )
);
const skinPosition = bindMatrixInverseNode.mul( skinned ).xyz;
// NORMAL
let skinMatrix = add(
skinWeightNode.x.mul( boneMatX ),
skinWeightNode.y.mul( boneMatY ),
skinWeightNode.z.mul( boneMatZ ),
skinWeightNode.w.mul( boneMatW )
);
skinMatrix = bindMatrixInverseNode.mul( skinMatrix ).mul( bindMatrixNode );
const skinNormal = skinMatrix.transformDirection( normalLocal ).xyz;
// ASSIGNS
positionLocal.assign( skinPosition );
normalLocal.assign( skinNormal );
if ( builder.hasGeometryAttribute( 'tangent' ) ) {
tangentLocal.assign( skinNormal );
}
}
generate( builder, output ) {
if ( output !== 'void' ) {
return positionLocal.build( builder, output );
}
}
update( frame ) {
const object = this.useReference ? frame.object : this.skinnedMesh;
object.skeleton.update();
}
}
export default SkinningNode;
export const skinning = ( skinnedMesh ) => nodeObject( new SkinningNode( skinnedMesh ) );
export const skinningReference = ( skinnedMesh ) => nodeObject( new SkinningNode( skinnedMesh, true ) );
addNodeClass( 'SkinningNode', SkinningNode );

View File

@ -0,0 +1,130 @@
import BufferNode from './BufferNode.js';
import { bufferAttribute } from './BufferAttributeNode.js';
import { addNodeClass } from '../core/Node.js';
import { nodeObject } from '../shadernode/ShaderNode.js';
import { varying } from '../core/VaryingNode.js';
import { storageElement } from '../utils/StorageArrayElementNode.js';
import { GPUBufferBindingType } from '../../renderers/webgpu/utils/WebGPUConstants.js';
class StorageBufferNode extends BufferNode {
constructor( value, bufferType, bufferCount = 0 ) {
super( value, bufferType, bufferCount );
this.isStorageBufferNode = true;
this.access = GPUBufferBindingType.Storage;
this.bufferObject = false;
this.bufferCount = bufferCount;
this._attribute = null;
this._varying = null;
this.global = true;
if ( value.isStorageBufferAttribute !== true && value.isStorageInstancedBufferAttribute !== true ) {
// TOOD: Improve it, possibly adding a new property to the BufferAttribute to identify it as a storage buffer read-only attribute in Renderer
if ( value.isInstancedBufferAttribute ) value.isStorageInstancedBufferAttribute = true;
else value.isStorageBufferAttribute = true;
}
}
getHash( builder ) {
if ( this.bufferCount === 0 ) {
let bufferData = builder.globalCache.getData( this.value );
if ( bufferData === undefined ) {
bufferData = {
node: this
};
builder.globalCache.setData( this.value, bufferData );
}
return bufferData.node.uuid;
}
return this.uuid;
}
getInputType( /*builder*/ ) {
return 'storageBuffer';
}
element( indexNode ) {
return storageElement( this, indexNode );
}
setBufferObject( value ) {
this.bufferObject = value;
return this;
}
setAccess( value ) {
this.access = value;
return this;
}
toReadOnly() {
return this.setAccess( GPUBufferBindingType.ReadOnlyStorage );
}
generate( builder ) {
if ( builder.isAvailable( 'storageBuffer' ) ) {
return super.generate( builder );
}
const nodeType = this.getNodeType( builder );
if ( this._attribute === null ) {
this._attribute = bufferAttribute( this.value );
this._varying = varying( this._attribute );
}
const output = this._varying.build( builder, nodeType );
builder.registerTransform( output, this._attribute );
return output;
}
}
export default StorageBufferNode;
// Read-Write Storage
export const storage = ( value, type, count ) => nodeObject( new StorageBufferNode( value, type, count ) );
export const storageObject = ( value, type, count ) => nodeObject( new StorageBufferNode( value, type, count ).setBufferObject( true ) );
addNodeClass( 'StorageBufferNode', StorageBufferNode );

View File

@ -0,0 +1,104 @@
import { addNodeClass } from '../core/Node.js';
import TextureNode from './TextureNode.js';
import { nodeProxy } from '../shadernode/ShaderNode.js';
import { GPUStorageTextureAccess } from '../../renderers/webgpu/utils/WebGPUConstants.js';
class StorageTextureNode extends TextureNode {
constructor( value, uvNode, storeNode = null ) {
super( value, uvNode );
this.storeNode = storeNode;
this.isStorageTextureNode = true;
this.access = GPUStorageTextureAccess.WriteOnly;
}
getInputType( /*builder*/ ) {
return 'storageTexture';
}
setup( builder ) {
super.setup( builder );
const properties = builder.getNodeProperties( this );
properties.storeNode = this.storeNode;
}
setAccess( value ) {
this.access = value;
return this;
}
generate( builder, output ) {
let snippet;
if ( this.storeNode !== null ) {
snippet = this.generateStore( builder );
} else {
snippet = super.generate( builder, output );
}
return snippet;
}
toReadOnly() {
return this.setAccess( GPUStorageTextureAccess.ReadOnly );
}
toWriteOnly() {
return this.setAccess( GPUStorageTextureAccess.WriteOnly );
}
generateStore( builder ) {
const properties = builder.getNodeProperties( this );
const { uvNode, storeNode } = properties;
const textureProperty = super.generate( builder, 'property' );
const uvSnippet = uvNode.build( builder, 'uvec2' );
const storeSnippet = storeNode.build( builder, 'vec4' );
const snippet = builder.generateTextureStore( builder, textureProperty, uvSnippet, storeSnippet );
builder.addLineFlowCode( snippet );
}
}
export default StorageTextureNode;
export const storageTexture = nodeProxy( StorageTextureNode );
export const textureStore = ( value, uvNode, storeNode ) => {
const node = storageTexture( value, uvNode, storeNode );
if ( storeNode !== null ) node.append();
return node;
};
addNodeClass( 'StorageTextureNode', StorageTextureNode );

View File

@ -0,0 +1,23 @@
import { attribute } from '../core/AttributeNode.js';
import { varying } from '../core/VaryingNode.js';
import { cameraViewMatrix } from './CameraNode.js';
import { modelViewMatrix } from './ModelNode.js';
import { tslFn, vec4 } from '../shadernode/ShaderNode.js';
export const tangentGeometry = /*#__PURE__*/ tslFn( ( stack, builder ) => {
if ( builder.geometry.hasAttribute( 'tangent' ) === false ) {
builder.geometry.computeTangents();
}
return attribute( 'tangent', 'vec4' );
} )();
export const tangentLocal = /*#__PURE__*/ tangentGeometry.xyz.toVar( 'tangentLocal' );
export const tangentView = /*#__PURE__*/ varying( modelViewMatrix.mul( vec4( tangentLocal, 0 ) ).xyz, 'v_tangentView' ).normalize().toVar( 'tangentView' );
export const tangentWorld = /*#__PURE__*/ varying( tangentView.transformDirection( cameraViewMatrix ), 'v_tangentWorld' ).normalize().toVar( 'tangentWorld' );
export const transformedTangentView = /*#__PURE__*/ tangentView.toVar( 'transformedTangentView' );
export const transformedTangentWorld = /*#__PURE__*/ transformedTangentView.transformDirection( cameraViewMatrix ).normalize().toVar( 'transformedTangentWorld' );

View File

@ -0,0 +1,100 @@
import TextureNode from './TextureNode.js';
import { addNodeClass } from '../core/Node.js';
import { nodeProxy, vec3, tslFn, If } from '../shadernode/ShaderNode.js';
const normal = tslFn( ( { texture, uv } ) => {
const epsilon = 0.0001;
const ret = vec3().temp();
If( uv.x.lessThan( epsilon ), () => {
ret.assign( vec3( 1, 0, 0 ) );
} ).elseif( uv.y.lessThan( epsilon ), () => {
ret.assign( vec3( 0, 1, 0 ) );
} ).elseif( uv.z.lessThan( epsilon ), () => {
ret.assign( vec3( 0, 0, 1 ) );
} ).elseif( uv.x.greaterThan( 1 - epsilon ), () => {
ret.assign( vec3( - 1, 0, 0 ) );
} ).elseif( uv.y.greaterThan( 1 - epsilon ), () => {
ret.assign( vec3( 0, - 1, 0 ) );
} ).elseif( uv.z.greaterThan( 1 - epsilon ), () => {
ret.assign( vec3( 0, 0, - 1 ) );
} ).else( () => {
const step = 0.01;
const x = texture.uv( uv.add( vec3( - step, 0.0, 0.0 ) ) ).r.sub( texture.uv( uv.add( vec3( step, 0.0, 0.0 ) ) ).r );
const y = texture.uv( uv.add( vec3( 0.0, - step, 0.0 ) ) ).r.sub( texture.uv( uv.add( vec3( 0.0, step, 0.0 ) ) ).r );
const z = texture.uv( uv.add( vec3( 0.0, 0.0, - step ) ) ).r.sub( texture.uv( uv.add( vec3( 0.0, 0.0, step ) ) ).r );
ret.assign( vec3( x, y, z ) );
} );
return ret.normalize();
} );
class Texture3DNode extends TextureNode {
constructor( value, uvNode = null, levelNode = null ) {
super( value, uvNode, levelNode );
this.isTexture3DNode = true;
}
getInputType( /*builder*/ ) {
return 'texture3D';
}
getDefaultUV() {
return vec3( 0.5, 0.5, 0.5 );
}
setUpdateMatrix( /*updateMatrix*/ ) { } // Ignore .updateMatrix for 3d TextureNode
setupUV( builder, uvNode ) {
return uvNode;
}
generateUV( builder, uvNode ) {
return uvNode.build( builder, 'vec3' );
}
normal( uvNode ) {
return normal( { texture: this, uv: uvNode } );
}
}
export default Texture3DNode;
export const texture3D = nodeProxy( Texture3DNode );
addNodeClass( 'Texture3DNode', Texture3DNode );

View File

@ -0,0 +1,94 @@
import TempNode from '../core/TempNode.js';
import { addNodeClass } from '../core/Node.js';
import { add, mul, div } from '../math/OperatorNode.js';
import { floor, ceil, fract, pow } from '../math/MathNode.js';
import { nodeProxy, addNodeElement, float, vec2, vec4, int } from '../shadernode/ShaderNode.js';
// Mipped Bicubic Texture Filtering by N8
// https://www.shadertoy.com/view/Dl2SDW
const bC = 1.0 / 6.0;
const w0 = ( a ) => mul( bC, mul( a, mul( a, a.negate().add( 3.0 ) ).sub( 3.0 ) ).add( 1.0 ) );
const w1 = ( a ) => mul( bC, mul( a, mul( a, mul( 3.0, a ).sub( 6.0 ) ) ).add( 4.0 ) );
const w2 = ( a ) => mul( bC, mul( a, mul( a, mul( - 3.0, a ).add( 3.0 ) ).add( 3.0 ) ).add( 1.0 ) );
const w3 = ( a ) => mul( bC, pow( a, 3 ) );
const g0 = ( a ) => w0( a ).add( w1( a ) );
const g1 = ( a ) => w2( a ).add( w3( a ) );
// h0 and h1 are the two offset functions
const h0 = ( a ) => add( - 1.0, w1( a ).div( w0( a ).add( w1( a ) ) ) );
const h1 = ( a ) => add( 1.0, w3( a ).div( w2( a ).add( w3( a ) ) ) );
const bicubic = ( textureNode, texelSize, lod ) => {
const uv = textureNode.uvNode;
const uvScaled = mul( uv, texelSize.zw ).add( 0.5 );
const iuv = floor( uvScaled );
const fuv = fract( uvScaled );
const g0x = g0( fuv.x );
const g1x = g1( fuv.x );
const h0x = h0( fuv.x );
const h1x = h1( fuv.x );
const h0y = h0( fuv.y );
const h1y = h1( fuv.y );
const p0 = vec2( iuv.x.add( h0x ), iuv.y.add( h0y ) ).sub( 0.5 ).mul( texelSize.xy );
const p1 = vec2( iuv.x.add( h1x ), iuv.y.add( h0y ) ).sub( 0.5 ).mul( texelSize.xy );
const p2 = vec2( iuv.x.add( h0x ), iuv.y.add( h1y ) ).sub( 0.5 ).mul( texelSize.xy );
const p3 = vec2( iuv.x.add( h1x ), iuv.y.add( h1y ) ).sub( 0.5 ).mul( texelSize.xy );
const a = g0( fuv.y ).mul( add( g0x.mul( textureNode.uv( p0 ).level( lod ) ), g1x.mul( textureNode.uv( p1 ).level( lod ) ) ) );
const b = g1( fuv.y ).mul( add( g0x.mul( textureNode.uv( p2 ).level( lod ) ), g1x.mul( textureNode.uv( p3 ).level( lod ) ) ) );
return a.add( b );
};
const textureBicubicMethod = ( textureNode, lodNode ) => {
const fLodSize = vec2( textureNode.size( int( lodNode ) ) );
const cLodSize = vec2( textureNode.size( int( lodNode.add( 1.0 ) ) ) );
const fLodSizeInv = div( 1.0, fLodSize );
const cLodSizeInv = div( 1.0, cLodSize );
const fSample = bicubic( textureNode, vec4( fLodSizeInv, fLodSize ), floor( lodNode ) );
const cSample = bicubic( textureNode, vec4( cLodSizeInv, cLodSize ), ceil( lodNode ) );
return fract( lodNode ).mix( fSample, cSample );
};
class TextureBicubicNode extends TempNode {
constructor( textureNode, blurNode = float( 3 ) ) {
super( 'vec4' );
this.textureNode = textureNode;
this.blurNode = blurNode;
}
setup() {
return textureBicubicMethod( this.textureNode, this.blurNode );
}
}
export default TextureBicubicNode;
export const textureBicubic = nodeProxy( TextureBicubicNode );
addNodeElement( 'bicubic', textureBicubic );
addNodeClass( 'TextureBicubicNode', TextureBicubicNode );

View File

@ -0,0 +1,445 @@
import UniformNode, { uniform } from '../core/UniformNode.js';
import { uv } from './UVNode.js';
import { textureSize } from './TextureSizeNode.js';
import { colorSpaceToLinear } from '../display/ColorSpaceNode.js';
import { expression } from '../code/ExpressionNode.js';
import { addNodeClass } from '../core/Node.js';
import { maxMipLevel } from '../utils/MaxMipLevelNode.js';
import { addNodeElement, nodeProxy, vec3, nodeObject } from '../shadernode/ShaderNode.js';
import { NodeUpdateType } from '../core/constants.js';
import { IntType, UnsignedIntType } from '../../constants.js';
class TextureNode extends UniformNode {
constructor( value, uvNode = null, levelNode = null, biasNode = null ) {
super( value );
this.isTextureNode = true;
this.uvNode = uvNode;
this.levelNode = levelNode;
this.biasNode = biasNode;
this.compareNode = null;
this.depthNode = null;
this.gradNode = null;
this.sampler = true;
this.updateMatrix = false;
this.updateType = NodeUpdateType.NONE;
this.referenceNode = null;
this._value = value;
this._matrixUniform = null;
this.setUpdateMatrix( uvNode === null );
}
set value( value ) {
if ( this.referenceNode ) {
this.referenceNode.value = value;
} else {
this._value = value;
}
}
get value() {
return this.referenceNode ? this.referenceNode.value : this._value;
}
getUniformHash( /*builder*/ ) {
return this.value.uuid;
}
getNodeType( /*builder*/ ) {
if ( this.value.isDepthTexture === true ) return 'float';
if ( this.value.type === UnsignedIntType ) {
return 'uvec4';
} else if ( this.value.type === IntType ) {
return 'ivec4';
}
return 'vec4';
}
getInputType( /*builder*/ ) {
return 'texture';
}
getDefaultUV() {
return uv( this.value.channel );
}
updateReference( /*state*/ ) {
return this.value;
}
getTransformedUV( uvNode ) {
if ( this._matrixUniform === null ) this._matrixUniform = uniform( this.value.matrix );
return this._matrixUniform.mul( vec3( uvNode, 1 ) ).xy;
}
setUpdateMatrix( value ) {
this.updateMatrix = value;
this.updateType = value ? NodeUpdateType.FRAME : NodeUpdateType.NONE;
return this;
}
setupUV( builder, uvNode ) {
const texture = this.value;
if ( builder.isFlipY() && ( texture.isRenderTargetTexture === true || texture.isFramebufferTexture === true || texture.isDepthTexture === true ) ) {
uvNode = uvNode.setY( uvNode.y.oneMinus() );
}
return uvNode;
}
setup( builder ) {
const properties = builder.getNodeProperties( this );
properties.referenceNode = this.referenceNode;
//
let uvNode = this.uvNode;
if ( ( uvNode === null || builder.context.forceUVContext === true ) && builder.context.getUV ) {
uvNode = builder.context.getUV( this );
}
if ( ! uvNode ) uvNode = this.getDefaultUV();
if ( this.updateMatrix === true ) {
uvNode = this.getTransformedUV( uvNode );
}
uvNode = this.setupUV( builder, uvNode );
//
let levelNode = this.levelNode;
if ( levelNode === null && builder.context.getTextureLevel ) {
levelNode = builder.context.getTextureLevel( this );
}
//
properties.uvNode = uvNode;
properties.levelNode = levelNode;
properties.biasNode = this.biasNode;
properties.compareNode = this.compareNode;
properties.gradNode = this.gradNode;
properties.depthNode = this.depthNode;
}
generateUV( builder, uvNode ) {
return uvNode.build( builder, this.sampler === true ? 'vec2' : 'ivec2' );
}
generateSnippet( builder, textureProperty, uvSnippet, levelSnippet, biasSnippet, depthSnippet, compareSnippet, gradSnippet ) {
const texture = this.value;
let snippet;
if ( levelSnippet ) {
snippet = builder.generateTextureLevel( texture, textureProperty, uvSnippet, levelSnippet, depthSnippet );
} else if ( biasSnippet ) {
snippet = builder.generateTextureBias( texture, textureProperty, uvSnippet, biasSnippet, depthSnippet );
} else if ( gradSnippet ) {
snippet = builder.generateTextureGrad( texture, textureProperty, uvSnippet, gradSnippet, depthSnippet );
} else if ( compareSnippet ) {
snippet = builder.generateTextureCompare( texture, textureProperty, uvSnippet, compareSnippet, depthSnippet );
} else if ( this.sampler === false ) {
snippet = builder.generateTextureLoad( texture, textureProperty, uvSnippet, depthSnippet );
} else {
snippet = builder.generateTexture( texture, textureProperty, uvSnippet, depthSnippet );
}
return snippet;
}
generate( builder, output ) {
const properties = builder.getNodeProperties( this );
const texture = this.value;
if ( ! texture || texture.isTexture !== true ) {
throw new Error( 'TextureNode: Need a three.js texture.' );
}
const textureProperty = super.generate( builder, 'property' );
if ( output === 'sampler' ) {
return textureProperty + '_sampler';
} else if ( builder.isReference( output ) ) {
return textureProperty;
} else {
const nodeData = builder.getDataFromNode( this );
let propertyName = nodeData.propertyName;
if ( propertyName === undefined ) {
const { uvNode, levelNode, biasNode, compareNode, depthNode, gradNode } = properties;
const uvSnippet = this.generateUV( builder, uvNode );
const levelSnippet = levelNode ? levelNode.build( builder, 'float' ) : null;
const biasSnippet = biasNode ? biasNode.build( builder, 'float' ) : null;
const depthSnippet = depthNode ? depthNode.build( builder, 'int' ) : null;
const compareSnippet = compareNode ? compareNode.build( builder, 'float' ) : null;
const gradSnippet = gradNode ? [ gradNode[ 0 ].build( builder, 'vec2' ), gradNode[ 1 ].build( builder, 'vec2' ) ] : null;
const nodeVar = builder.getVarFromNode( this );
propertyName = builder.getPropertyName( nodeVar );
const snippet = this.generateSnippet( builder, textureProperty, uvSnippet, levelSnippet, biasSnippet, depthSnippet, compareSnippet, gradSnippet );
builder.addLineFlowCode( `${propertyName} = ${snippet}` );
nodeData.snippet = snippet;
nodeData.propertyName = propertyName;
}
let snippet = propertyName;
const nodeType = this.getNodeType( builder );
if ( builder.needsColorSpaceToLinear( texture ) ) {
snippet = colorSpaceToLinear( expression( snippet, nodeType ), texture.colorSpace ).setup( builder ).build( builder, nodeType );
}
return builder.format( snippet, nodeType, output );
}
}
setSampler( value ) {
this.sampler = value;
return this;
}
getSampler() {
return this.sampler;
}
// @TODO: Move to TSL
uv( uvNode ) {
const textureNode = this.clone();
textureNode.uvNode = nodeObject( uvNode );
textureNode.referenceNode = this;
return nodeObject( textureNode );
}
blur( amountNode ) {
const textureNode = this.clone();
textureNode.biasNode = nodeObject( amountNode ).mul( maxMipLevel( textureNode ) );
textureNode.referenceNode = this;
return nodeObject( textureNode );
}
level( levelNode ) {
const textureNode = this.clone();
textureNode.levelNode = nodeObject( levelNode );
textureNode.referenceNode = this;
return nodeObject( textureNode );
}
size( levelNode ) {
return textureSize( this, levelNode );
}
bias( biasNode ) {
const textureNode = this.clone();
textureNode.biasNode = nodeObject( biasNode );
textureNode.referenceNode = this;
return nodeObject( textureNode );
}
compare( compareNode ) {
const textureNode = this.clone();
textureNode.compareNode = nodeObject( compareNode );
textureNode.referenceNode = this;
return nodeObject( textureNode );
}
grad( gradNodeX, gradNodeY ) {
const textureNode = this.clone();
textureNode.gradNode = [ nodeObject( gradNodeX ), nodeObject( gradNodeY ) ];
textureNode.referenceNode = this;
return nodeObject( textureNode );
}
depth( depthNode ) {
const textureNode = this.clone();
textureNode.depthNode = nodeObject( depthNode );
textureNode.referenceNode = this;
return nodeObject( textureNode );
}
// --
serialize( data ) {
super.serialize( data );
data.value = this.value.toJSON( data.meta ).uuid;
data.sampler = this.sampler;
data.updateMatrix = this.updateMatrix;
data.updateType = this.updateType;
}
deserialize( data ) {
super.deserialize( data );
this.value = data.meta.textures[ data.value ];
this.sampler = data.sampler;
this.updateMatrix = data.updateMatrix;
this.updateType = data.updateType;
}
update() {
const texture = this.value;
const matrixUniform = this._matrixUniform;
if ( matrixUniform !== null ) matrixUniform.value = texture.matrix;
if ( texture.matrixAutoUpdate === true ) {
texture.updateMatrix();
}
}
clone() {
const newNode = new this.constructor( this.value, this.uvNode, this.levelNode, this.biasNode );
newNode.sampler = this.sampler;
return newNode;
}
}
export default TextureNode;
export const texture = nodeProxy( TextureNode );
export const textureLoad = ( ...params ) => texture( ...params ).setSampler( false );
//export const textureLevel = ( value, uv, level ) => texture( value, uv ).level( level );
export const sampler = ( aTexture ) => ( aTexture.isNode === true ? aTexture : texture( aTexture ) ).convert( 'sampler' );
addNodeElement( 'texture', texture );
//addNodeElement( 'textureLevel', textureLevel );
addNodeClass( 'TextureNode', TextureNode );

View File

@ -0,0 +1,35 @@
import Node from '../core/Node.js';
import { addNodeClass } from '../core/Node.js';
import { addNodeElement, nodeProxy } from '../shadernode/ShaderNode.js';
class TextureSizeNode extends Node {
constructor( textureNode, levelNode = null ) {
super( 'uvec2' );
this.isTextureSizeNode = true;
this.textureNode = textureNode;
this.levelNode = levelNode;
}
generate( builder, output ) {
const textureProperty = this.textureNode.build( builder, 'property' );
const levelNode = this.levelNode.build( builder, 'int' );
return builder.format( `${ builder.getMethod( 'textureDimensions' ) }( ${ textureProperty }, ${ levelNode } )`, this.getNodeType( builder ), output );
}
}
export default TextureSizeNode;
export const textureSize = nodeProxy( TextureSizeNode );
addNodeElement( 'textureSize', textureSize );
addNodeClass( 'TextureSizeNode', TextureSizeNode );

View File

@ -0,0 +1,3 @@
import { attribute } from '../core/AttributeNode.js';
export const uv = ( index ) => attribute( 'uv' + ( index > 0 ? index : '' ), 'vec2' );

View File

@ -0,0 +1,146 @@
import { addNodeClass } from '../core/Node.js';
import { nodeObject } from '../shadernode/ShaderNode.js';
import { NodeUpdateType } from '../core/constants.js';
import { getValueType } from '../core/NodeUtils.js';
import ArrayElementNode from '../utils/ArrayElementNode.js';
import BufferNode from './BufferNode.js';
class UniformsElementNode extends ArrayElementNode {
constructor( arrayBuffer, indexNode ) {
super( arrayBuffer, indexNode );
this.isArrayBufferElementNode = true;
}
getNodeType( builder ) {
return this.node.getElementType( builder );
}
generate( builder ) {
const snippet = super.generate( builder );
const type = this.getNodeType();
return builder.format( snippet, 'vec4', type );
}
}
class UniformsNode extends BufferNode {
constructor( value, elementType = null ) {
super( null, 'vec4' );
this.array = value;
this.elementType = elementType;
this._elementType = null;
this._elementLength = 0;
this.updateType = NodeUpdateType.RENDER;
this.isArrayBufferNode = true;
}
getElementType() {
return this.elementType || this._elementType;
}
getElementLength() {
return this._elementLength;
}
update( /*frame*/ ) {
const { array, value } = this;
const elementLength = this.getElementLength();
const elementType = this.getElementType();
if ( elementLength === 1 ) {
for ( let i = 0; i < array.length; i ++ ) {
const index = i * 4;
value[ index ] = array[ i ];
}
} else if ( elementType === 'color' ) {
for ( let i = 0; i < array.length; i ++ ) {
const index = i * 4;
const vector = array[ i ];
value[ index ] = vector.r;
value[ index + 1 ] = vector.g;
value[ index + 2 ] = vector.b || 0;
//value[ index + 3 ] = vector.a || 0;
}
} else {
for ( let i = 0; i < array.length; i ++ ) {
const index = i * 4;
const vector = array[ i ];
value[ index ] = vector.x;
value[ index + 1 ] = vector.y;
value[ index + 2 ] = vector.z || 0;
value[ index + 3 ] = vector.w || 0;
}
}
}
setup( builder ) {
const length = this.array.length;
this._elementType = this.elementType === null ? getValueType( this.array[ 0 ] ) : this.elementType;
this._elementLength = builder.getTypeLength( this._elementType );
let arrayType = Float32Array;
if ( this._elementType.charAt( 0 ) === 'i' ) arrayType = Int32Array;
else if ( this._elementType.charAt( 0 ) === 'u' ) arrayType = Uint32Array;
this.value = new arrayType( length * 4 );
this.bufferCount = length;
this.bufferType = builder.changeComponentType( 'vec4', builder.getComponentType( this._elementType ) );
return super.setup( builder );
}
element( indexNode ) {
return nodeObject( new UniformsElementNode( this, nodeObject( indexNode ) ) );
}
}
export default UniformsNode;
export const uniforms = ( values, nodeType ) => nodeObject( new UniformsNode( values, nodeType ) );
addNodeClass( 'UniformsNode', UniformsNode );

View File

@ -0,0 +1,29 @@
import ReferenceNode from './ReferenceNode.js';
import { addNodeClass } from '../core/Node.js';
import { nodeObject } from '../shadernode/ShaderNode.js';
class UserDataNode extends ReferenceNode {
constructor( property, inputType, userData = null ) {
super( property, inputType, userData );
this.userData = userData;
}
update( frame ) {
this.reference = this.userData !== null ? this.userData : frame.object.userData;
super.update( frame );
}
}
export default UserDataNode;
export const userData = ( name, inputType, userData ) => nodeObject( new UserDataNode( name, inputType, userData ) );
addNodeClass( 'UserDataNode', UserDataNode );

View File

@ -0,0 +1,71 @@
import { addNodeClass } from '../core/Node.js';
import AttributeNode from '../core/AttributeNode.js';
import { nodeObject } from '../shadernode/ShaderNode.js';
import { Vector4 } from '../../math/Vector4.js';
class VertexColorNode extends AttributeNode {
constructor( index = 0 ) {
super( null, 'vec4' );
this.isVertexColorNode = true;
this.index = index;
}
getAttributeName( /*builder*/ ) {
const index = this.index;
return 'color' + ( index > 0 ? index : '' );
}
generate( builder ) {
const attributeName = this.getAttributeName( builder );
const geometryAttribute = builder.hasGeometryAttribute( attributeName );
let result;
if ( geometryAttribute === true ) {
result = super.generate( builder );
} else {
// Vertex color fallback should be white
result = builder.generateConst( this.nodeType, new Vector4( 1, 1, 1, 1 ) );
}
return result;
}
serialize( data ) {
super.serialize( data );
data.index = this.index;
}
deserialize( data ) {
super.deserialize( data );
this.index = data.index;
}
}
export default VertexColorNode;
export const vertexColor = ( ...params ) => nodeObject( new VertexColorNode( ...params ) );
addNodeClass( 'VertexColorNode', VertexColorNode );

View File

@ -0,0 +1,84 @@
import Node, { addNodeClass } from '../core/Node.js';
import { nodeProxy } from '../shadernode/ShaderNode.js';
class CodeNode extends Node {
constructor( code = '', includes = [], language = '' ) {
super( 'code' );
this.isCodeNode = true;
this.code = code;
this.language = language;
this.includes = includes;
}
isGlobal() {
return true;
}
setIncludes( includes ) {
this.includes = includes;
return this;
}
getIncludes( /*builder*/ ) {
return this.includes;
}
generate( builder ) {
const includes = this.getIncludes( builder );
for ( const include of includes ) {
include.build( builder );
}
const nodeCode = builder.getCodeFromNode( this, this.getNodeType( builder ) );
nodeCode.code = this.code;
return nodeCode.code;
}
serialize( data ) {
super.serialize( data );
data.code = this.code;
data.language = this.language;
}
deserialize( data ) {
super.deserialize( data );
this.code = data.code;
this.language = data.language;
}
}
export default CodeNode;
export const code = nodeProxy( CodeNode );
export const js = ( src, includes ) => code( src, includes, 'js' );
export const wgsl = ( src, includes ) => code( src, includes, 'wgsl' );
export const glsl = ( src, includes ) => code( src, includes, 'glsl' );
addNodeClass( 'CodeNode', CodeNode );

View File

@ -0,0 +1,37 @@
import Node, { addNodeClass } from '../core/Node.js';
import { nodeProxy } from '../shadernode/ShaderNode.js';
class ExpressionNode extends Node {
constructor( snippet = '', nodeType = 'void' ) {
super( nodeType );
this.snippet = snippet;
}
generate( builder, output ) {
const type = this.getNodeType( builder );
const snippet = this.snippet;
if ( type === 'void' ) {
builder.addLineFlowCode( snippet );
} else {
return builder.format( `( ${ snippet } )`, type, output );
}
}
}
export default ExpressionNode;
export const expression = nodeProxy( ExpressionNode );
addNodeClass( 'ExpressionNode', ExpressionNode );

View File

@ -0,0 +1,96 @@
import TempNode from '../core/TempNode.js';
import { addNodeClass } from '../core/Node.js';
import { addNodeElement, nodeArray, nodeObject, nodeObjects } from '../shadernode/ShaderNode.js';
class FunctionCallNode extends TempNode {
constructor( functionNode = null, parameters = {} ) {
super();
this.functionNode = functionNode;
this.parameters = parameters;
}
setParameters( parameters ) {
this.parameters = parameters;
return this;
}
getParameters() {
return this.parameters;
}
getNodeType( builder ) {
return this.functionNode.getNodeType( builder );
}
generate( builder ) {
const params = [];
const functionNode = this.functionNode;
const inputs = functionNode.getInputs( builder );
const parameters = this.parameters;
if ( Array.isArray( parameters ) ) {
for ( let i = 0; i < parameters.length; i ++ ) {
const inputNode = inputs[ i ];
const node = parameters[ i ];
params.push( node.build( builder, inputNode.type ) );
}
} else {
for ( const inputNode of inputs ) {
const node = parameters[ inputNode.name ];
if ( node !== undefined ) {
params.push( node.build( builder, inputNode.type ) );
} else {
throw new Error( `FunctionCallNode: Input '${inputNode.name}' not found in FunctionNode.` );
}
}
}
const functionName = functionNode.build( builder, 'property' );
return `${functionName}( ${params.join( ', ' )} )`;
}
}
export default FunctionCallNode;
export const call = ( func, ...params ) => {
params = params.length > 1 || ( params[ 0 ] && params[ 0 ].isNode === true ) ? nodeArray( params ) : nodeObjects( params[ 0 ] );
return nodeObject( new FunctionCallNode( nodeObject( func ), params ) );
};
addNodeElement( 'call', call );
addNodeClass( 'FunctionCallNode', FunctionCallNode );

View File

@ -0,0 +1,130 @@
import CodeNode from './CodeNode.js';
import { addNodeClass } from '../core/Node.js';
import { nodeObject } from '../shadernode/ShaderNode.js';
class FunctionNode extends CodeNode {
constructor( code = '', includes = [], language = '' ) {
super( code, includes, language );
this.keywords = {};
}
getNodeType( builder ) {
return this.getNodeFunction( builder ).type;
}
getInputs( builder ) {
return this.getNodeFunction( builder ).inputs;
}
getNodeFunction( builder ) {
const nodeData = builder.getDataFromNode( this );
let nodeFunction = nodeData.nodeFunction;
if ( nodeFunction === undefined ) {
nodeFunction = builder.parser.parseFunction( this.code );
nodeData.nodeFunction = nodeFunction;
}
return nodeFunction;
}
generate( builder, output ) {
super.generate( builder );
const nodeFunction = this.getNodeFunction( builder );
const name = nodeFunction.name;
const type = nodeFunction.type;
const nodeCode = builder.getCodeFromNode( this, type );
if ( name !== '' ) {
// use a custom property name
nodeCode.name = name;
}
const propertyName = builder.getPropertyName( nodeCode );
let code = this.getNodeFunction( builder ).getCode( propertyName );
const keywords = this.keywords;
const keywordsProperties = Object.keys( keywords );
if ( keywordsProperties.length > 0 ) {
for ( const property of keywordsProperties ) {
const propertyRegExp = new RegExp( `\\b${property}\\b`, 'g' );
const nodeProperty = keywords[ property ].build( builder, 'property' );
code = code.replace( propertyRegExp, nodeProperty );
}
}
nodeCode.code = code + '\n';
if ( output === 'property' ) {
return propertyName;
} else {
return builder.format( `${ propertyName }()`, type, output );
}
}
}
export default FunctionNode;
const nativeFn = ( code, includes = [], language = '' ) => {
for ( let i = 0; i < includes.length; i ++ ) {
const include = includes[ i ];
// TSL Function: glslFn, wgslFn
if ( typeof include === 'function' ) {
includes[ i ] = include.functionNode;
}
}
const functionNode = nodeObject( new FunctionNode( code, includes, language ) );
const fn = ( ...params ) => functionNode.call( ...params );
fn.functionNode = functionNode;
return fn;
};
export const glslFn = ( code, includes ) => nativeFn( code, includes, 'glsl' );
export const wgslFn = ( code, includes ) => nativeFn( code, includes, 'wgsl' );
addNodeClass( 'FunctionNode', FunctionNode );

View File

@ -0,0 +1,502 @@
import Node, { addNodeClass } from '../core/Node.js';
import { scriptableValue } from './ScriptableValueNode.js';
import { addNodeElement, nodeProxy, float } from '../shadernode/ShaderNode.js';
class Resources extends Map {
get( key, callback = null, ...params ) {
if ( this.has( key ) ) return super.get( key );
if ( callback !== null ) {
const value = callback( ...params );
this.set( key, value );
return value;
}
}
}
class Parameters {
constructor( scriptableNode ) {
this.scriptableNode = scriptableNode;
}
get parameters() {
return this.scriptableNode.parameters;
}
get layout() {
return this.scriptableNode.getLayout();
}
getInputLayout( id ) {
return this.scriptableNode.getInputLayout( id );
}
get( name ) {
const param = this.parameters[ name ];
const value = param ? param.getValue() : null;
return value;
}
}
export const global = new Resources();
class ScriptableNode extends Node {
constructor( codeNode = null, parameters = {} ) {
super();
this.codeNode = codeNode;
this.parameters = parameters;
this._local = new Resources();
this._output = scriptableValue();
this._outputs = {};
this._source = this.source;
this._method = null;
this._object = null;
this._value = null;
this._needsOutputUpdate = true;
this.onRefresh = this.onRefresh.bind( this );
this.isScriptableNode = true;
}
get source() {
return this.codeNode ? this.codeNode.code : '';
}
setLocal( name, value ) {
return this._local.set( name, value );
}
getLocal( name ) {
return this._local.get( name );
}
onRefresh() {
this._refresh();
}
getInputLayout( id ) {
for ( const element of this.getLayout() ) {
if ( element.inputType && ( element.id === id || element.name === id ) ) {
return element;
}
}
}
getOutputLayout( id ) {
for ( const element of this.getLayout() ) {
if ( element.outputType && ( element.id === id || element.name === id ) ) {
return element;
}
}
}
setOutput( name, value ) {
const outputs = this._outputs;
if ( outputs[ name ] === undefined ) {
outputs[ name ] = scriptableValue( value );
} else {
outputs[ name ].value = value;
}
return this;
}
getOutput( name ) {
return this._outputs[ name ];
}
getParameter( name ) {
return this.parameters[ name ];
}
setParameter( name, value ) {
const parameters = this.parameters;
if ( value && value.isScriptableNode ) {
this.deleteParameter( name );
parameters[ name ] = value;
parameters[ name ].getDefaultOutput().events.addEventListener( 'refresh', this.onRefresh );
} else if ( value && value.isScriptableValueNode ) {
this.deleteParameter( name );
parameters[ name ] = value;
parameters[ name ].events.addEventListener( 'refresh', this.onRefresh );
} else if ( parameters[ name ] === undefined ) {
parameters[ name ] = scriptableValue( value );
parameters[ name ].events.addEventListener( 'refresh', this.onRefresh );
} else {
parameters[ name ].value = value;
}
return this;
}
getValue() {
return this.getDefaultOutput().getValue();
}
deleteParameter( name ) {
let valueNode = this.parameters[ name ];
if ( valueNode ) {
if ( valueNode.isScriptableNode ) valueNode = valueNode.getDefaultOutput();
valueNode.events.removeEventListener( 'refresh', this.onRefresh );
}
return this;
}
clearParameters() {
for ( const name of Object.keys( this.parameters ) ) {
this.deleteParameter( name );
}
this.needsUpdate = true;
return this;
}
call( name, ...params ) {
const object = this.getObject();
const method = object[ name ];
if ( typeof method === 'function' ) {
return method( ...params );
}
}
async callAsync( name, ...params ) {
const object = this.getObject();
const method = object[ name ];
if ( typeof method === 'function' ) {
return method.constructor.name === 'AsyncFunction' ? await method( ...params ) : method( ...params );
}
}
getNodeType( builder ) {
return this.getDefaultOutputNode().getNodeType( builder );
}
refresh( output = null ) {
if ( output !== null ) {
this.getOutput( output ).refresh();
} else {
this._refresh();
}
}
getObject() {
if ( this.needsUpdate ) this.dispose();
if ( this._object !== null ) return this._object;
//
const refresh = () => this.refresh();
const setOutput = ( id, value ) => this.setOutput( id, value );
const parameters = new Parameters( this );
const THREE = global.get( 'THREE' );
const TSL = global.get( 'TSL' );
const method = this.getMethod( this.codeNode );
const params = [ parameters, this._local, global, refresh, setOutput, THREE, TSL ];
this._object = method( ...params );
const layout = this._object.layout;
if ( layout ) {
if ( layout.cache === false ) {
this._local.clear();
}
// default output
this._output.outputType = layout.outputType || null;
if ( Array.isArray( layout.elements ) ) {
for ( const element of layout.elements ) {
const id = element.id || element.name;
if ( element.inputType ) {
if ( this.getParameter( id ) === undefined ) this.setParameter( id, null );
this.getParameter( id ).inputType = element.inputType;
}
if ( element.outputType ) {
if ( this.getOutput( id ) === undefined ) this.setOutput( id, null );
this.getOutput( id ).outputType = element.outputType;
}
}
}
}
return this._object;
}
deserialize( data ) {
super.deserialize( data );
for ( const name in this.parameters ) {
let valueNode = this.parameters[ name ];
if ( valueNode.isScriptableNode ) valueNode = valueNode.getDefaultOutput();
valueNode.events.addEventListener( 'refresh', this.onRefresh );
}
}
getLayout() {
return this.getObject().layout;
}
getDefaultOutputNode() {
const output = this.getDefaultOutput().value;
if ( output && output.isNode ) {
return output;
}
return float();
}
getDefaultOutput() {
return this._exec()._output;
}
getMethod() {
if ( this.needsUpdate ) this.dispose();
if ( this._method !== null ) return this._method;
//
const parametersProps = [ 'parameters', 'local', 'global', 'refresh', 'setOutput', 'THREE', 'TSL' ];
const interfaceProps = [ 'layout', 'init', 'main', 'dispose' ];
const properties = interfaceProps.join( ', ' );
const declarations = 'var ' + properties + '; var output = {};\n';
const returns = '\nreturn { ...output, ' + properties + ' };';
const code = declarations + this.codeNode.code + returns;
//
this._method = new Function( ...parametersProps, code );
return this._method;
}
dispose() {
if ( this._method === null ) return;
if ( this._object && typeof this._object.dispose === 'function' ) {
this._object.dispose();
}
this._method = null;
this._object = null;
this._source = null;
this._value = null;
this._needsOutputUpdate = true;
this._output.value = null;
this._outputs = {};
}
setup() {
return this.getDefaultOutputNode();
}
getCacheKey( force ) {
const cacheKey = [ this.source, this.getDefaultOutputNode().getCacheKey( force ) ];
for ( const param in this.parameters ) {
cacheKey.push( this.parameters[ param ].getCacheKey( force ) );
}
return cacheKey.join( ',' );
}
set needsUpdate( value ) {
if ( value === true ) this.dispose();
}
get needsUpdate() {
return this.source !== this._source;
}
_exec() {
if ( this.codeNode === null ) return this;
if ( this._needsOutputUpdate === true ) {
this._value = this.call( 'main' );
this._needsOutputUpdate = false;
}
this._output.value = this._value;
return this;
}
_refresh() {
this.needsUpdate = true;
this._exec();
this._output.refresh();
}
}
export default ScriptableNode;
export const scriptable = nodeProxy( ScriptableNode );
addNodeElement( 'scriptable', scriptable );
addNodeClass( 'ScriptableNode', ScriptableNode );

View File

@ -0,0 +1,168 @@
import Node, { addNodeClass } from '../core/Node.js';
import { arrayBufferToBase64, base64ToArrayBuffer } from '../core/NodeUtils.js';
import { addNodeElement, nodeProxy, float } from '../shadernode/ShaderNode.js';
import { EventDispatcher } from '../../core/EventDispatcher.js';
class ScriptableValueNode extends Node {
constructor( value = null ) {
super();
this._value = value;
this._cache = null;
this.inputType = null;
this.outpuType = null;
this.events = new EventDispatcher();
this.isScriptableValueNode = true;
}
get isScriptableOutputNode() {
return this.outputType !== null;
}
set value( val ) {
if ( this._value === val ) return;
if ( this._cache && this.inputType === 'URL' && this.value.value instanceof ArrayBuffer ) {
URL.revokeObjectURL( this._cache );
this._cache = null;
}
this._value = val;
this.events.dispatchEvent( { type: 'change' } );
this.refresh();
}
get value() {
return this._value;
}
refresh() {
this.events.dispatchEvent( { type: 'refresh' } );
}
getValue() {
const value = this.value;
if ( value && this._cache === null && this.inputType === 'URL' && value.value instanceof ArrayBuffer ) {
this._cache = URL.createObjectURL( new Blob( [ value.value ] ) );
} else if ( value && value.value !== null && value.value !== undefined && (
( ( this.inputType === 'URL' || this.inputType === 'String' ) && typeof value.value === 'string' ) ||
( this.inputType === 'Number' && typeof value.value === 'number' ) ||
( this.inputType === 'Vector2' && value.value.isVector2 ) ||
( this.inputType === 'Vector3' && value.value.isVector3 ) ||
( this.inputType === 'Vector4' && value.value.isVector4 ) ||
( this.inputType === 'Color' && value.value.isColor ) ||
( this.inputType === 'Matrix3' && value.value.isMatrix3 ) ||
( this.inputType === 'Matrix4' && value.value.isMatrix4 )
) ) {
return value.value;
}
return this._cache || value;
}
getNodeType( builder ) {
return this.value && this.value.isNode ? this.value.getNodeType( builder ) : 'float';
}
setup() {
return this.value && this.value.isNode ? this.value : float();
}
serialize( data ) {
super.serialize( data );
if ( this.value !== null ) {
if ( this.inputType === 'ArrayBuffer' ) {
data.value = arrayBufferToBase64( this.value );
} else {
data.value = this.value ? this.value.toJSON( data.meta ).uuid : null;
}
} else {
data.value = null;
}
data.inputType = this.inputType;
data.outputType = this.outputType;
}
deserialize( data ) {
super.deserialize( data );
let value = null;
if ( data.value !== null ) {
if ( data.inputType === 'ArrayBuffer' ) {
value = base64ToArrayBuffer( data.value );
} else if ( data.inputType === 'Texture' ) {
value = data.meta.textures[ data.value ];
} else {
value = data.meta.nodes[ data.value ] || null;
}
}
this.value = value;
this.inputType = data.inputType;
this.outputType = data.outputType;
}
}
export default ScriptableValueNode;
export const scriptableValue = nodeProxy( ScriptableValueNode );
addNodeElement( 'scriptableValue', scriptableValue );
addNodeClass( 'ScriptableValueNode', ScriptableValueNode );

View File

@ -0,0 +1,128 @@
import { addNodeClass } from '../core/Node.js';
import TempNode from '../core/TempNode.js';
import { addNodeElement, nodeProxy } from '../shadernode/ShaderNode.js';
import { vectorComponents } from '../core/constants.js';
class AssignNode extends TempNode {
constructor( targetNode, sourceNode ) {
super();
this.targetNode = targetNode;
this.sourceNode = sourceNode;
}
hasDependencies() {
return false;
}
getNodeType( builder, output ) {
return output !== 'void' ? this.targetNode.getNodeType( builder ) : 'void';
}
needsSplitAssign( builder ) {
const { targetNode } = this;
if ( builder.isAvailable( 'swizzleAssign' ) === false && targetNode.isSplitNode && targetNode.components.length > 1 ) {
const targetLength = builder.getTypeLength( targetNode.node.getNodeType( builder ) );
const assignDiferentVector = vectorComponents.join( '' ).slice( 0, targetLength ) !== targetNode.components;
return assignDiferentVector;
}
return false;
}
generate( builder, output ) {
const { targetNode, sourceNode } = this;
const needsSplitAssign = this.needsSplitAssign( builder );
const targetType = targetNode.getNodeType( builder );
const target = targetNode.context( { assign: true } ).build( builder );
const source = sourceNode.build( builder, targetType );
const sourceType = sourceNode.getNodeType( builder );
const nodeData = builder.getDataFromNode( this );
//
let snippet;
if ( nodeData.initialized === true ) {
if ( output !== 'void' ) {
snippet = target;
}
} else if ( needsSplitAssign ) {
const sourceVar = builder.getVarFromNode( this, null, targetType );
const sourceProperty = builder.getPropertyName( sourceVar );
builder.addLineFlowCode( `${ sourceProperty } = ${ source }` );
const targetRoot = targetNode.node.context( { assign: true } ).build( builder );
for ( let i = 0; i < targetNode.components.length; i ++ ) {
const component = targetNode.components[ i ];
builder.addLineFlowCode( `${ targetRoot }.${ component } = ${ sourceProperty }[ ${ i } ]` );
}
if ( output !== 'void' ) {
snippet = target;
}
} else {
snippet = `${ target } = ${ source }`;
if ( output === 'void' || sourceType === 'void' ) {
builder.addLineFlowCode( snippet );
if ( output !== 'void' ) {
snippet = target;
}
}
}
nodeData.initialized = true;
return builder.format( snippet, targetType, output );
}
}
export default AssignNode;
export const assign = nodeProxy( AssignNode );
addNodeClass( 'AssignNode', AssignNode );
addNodeElement( 'assign', assign );

View File

@ -0,0 +1,134 @@
import Node, { addNodeClass } from './Node.js';
import { varying } from './VaryingNode.js';
import { nodeObject } from '../shadernode/ShaderNode.js';
class AttributeNode extends Node {
constructor( attributeName, nodeType = null, defaultNode = null ) {
super( nodeType );
this.defaultNode = defaultNode;
this.global = true;
this._attributeName = attributeName;
}
getHash( builder ) {
return this.getAttributeName( builder );
}
getNodeType( builder ) {
let nodeType = super.getNodeType( builder );
if ( nodeType === null ) {
const attributeName = this.getAttributeName( builder );
if ( builder.hasGeometryAttribute( attributeName ) ) {
const attribute = builder.geometry.getAttribute( attributeName );
nodeType = builder.getTypeFromAttribute( attribute );
} else {
nodeType = 'float';
}
}
return nodeType;
}
setAttributeName( attributeName ) {
this._attributeName = attributeName;
return this;
}
getAttributeName( /*builder*/ ) {
return this._attributeName;
}
generate( builder ) {
const attributeName = this.getAttributeName( builder );
const nodeType = this.getNodeType( builder );
const geometryAttribute = builder.hasGeometryAttribute( attributeName );
if ( geometryAttribute === true ) {
const attribute = builder.geometry.getAttribute( attributeName );
const attributeType = builder.getTypeFromAttribute( attribute );
const nodeAttribute = builder.getAttribute( attributeName, attributeType );
if ( builder.shaderStage === 'vertex' ) {
return builder.format( nodeAttribute.name, attributeType, nodeType );
} else {
const nodeVarying = varying( this );
return nodeVarying.build( builder, nodeType );
}
} else {
console.warn( `AttributeNode: Vertex attribute "${ attributeName }" not found on geometry.` );
const { defaultNode } = this;
if ( defaultNode !== null ) {
return defaultNode.build( builder, nodeType );
} else {
return builder.generateConst( nodeType );
}
}
}
serialize( data ) {
super.serialize( data );
data.global = this.global;
data._attributeName = this._attributeName;
}
deserialize( data ) {
super.deserialize( data );
this.global = data.global;
this._attributeName = data._attributeName;
}
}
export default AttributeNode;
export const attribute = ( name, nodeType, defaultNode ) => nodeObject( new AttributeNode( name, nodeType, nodeObject( defaultNode ) ) );
addNodeClass( 'AttributeNode', AttributeNode );

View File

@ -0,0 +1,45 @@
import Node, { addNodeClass } from './Node.js';
import { addNodeElement, nodeProxy } from '../shadernode/ShaderNode.js';
class BypassNode extends Node {
constructor( returnNode, callNode ) {
super();
this.isBypassNode = true;
this.outputNode = returnNode;
this.callNode = callNode;
}
getNodeType( builder ) {
return this.outputNode.getNodeType( builder );
}
generate( builder ) {
const snippet = this.callNode.build( builder, 'void' );
if ( snippet !== '' ) {
builder.addLineFlowCode( snippet );
}
return this.outputNode.build( builder );
}
}
export default BypassNode;
export const bypass = nodeProxy( BypassNode );
addNodeElement( 'bypass', bypass );
addNodeClass( 'BypassNode', BypassNode );

View File

@ -0,0 +1,46 @@
import Node, { addNodeClass } from './Node.js';
import { addNodeElement, nodeObject } from '../shadernode/ShaderNode.js';
class CacheNode extends Node {
constructor( node, parent = true ) {
super();
this.node = node;
this.parent = parent;
this.isCacheNode = true;
}
getNodeType( builder ) {
return this.node.getNodeType( builder );
}
build( builder, ...params ) {
const previousCache = builder.getCache();
const cache = builder.getCacheFromNode( this, parent );
builder.setCache( cache );
const data = this.node.build( builder, ...params );
builder.setCache( previousCache );
return data;
}
}
export default CacheNode;
export const cache = ( node, ...params ) => nodeObject( new CacheNode( nodeObject( node ), ...params ) );
addNodeElement( 'cache', cache );
addNodeClass( 'CacheNode', CacheNode );

View File

@ -0,0 +1,32 @@
import InputNode from './InputNode.js';
import { addNodeClass } from './Node.js';
class ConstNode extends InputNode {
constructor( value, nodeType = null ) {
super( value, nodeType );
this.isConstNode = true;
}
generateConst( builder ) {
return builder.generateConst( this.getNodeType( builder ), this.value );
}
generate( builder, output ) {
const type = this.getNodeType( builder );
return builder.format( this.generateConst( builder ), type, output );
}
}
export default ConstNode;
addNodeClass( 'ConstNode', ConstNode );

View File

@ -0,0 +1,67 @@
import Node, { addNodeClass } from './Node.js';
import { addNodeElement, nodeProxy } from '../shadernode/ShaderNode.js';
class ContextNode extends Node {
constructor( node, context = {} ) {
super();
this.isContextNode = true;
this.node = node;
this.context = context;
}
getNodeType( builder ) {
return this.node.getNodeType( builder );
}
analyze( builder ) {
this.node.build( builder );
}
setup( builder ) {
const previousContext = builder.getContext();
builder.setContext( { ...builder.context, ...this.context } );
const node = this.node.build( builder );
builder.setContext( previousContext );
return node;
}
generate( builder, output ) {
const previousContext = builder.getContext();
builder.setContext( { ...builder.context, ...this.context } );
const snippet = this.node.build( builder, output );
builder.setContext( previousContext );
return snippet;
}
}
export default ContextNode;
export const context = nodeProxy( ContextNode );
export const label = ( node, name ) => context( node, { label: name } );
addNodeElement( 'context', context );
addNodeElement( 'label', label );
addNodeClass( 'ContextNode', ContextNode );

View File

@ -0,0 +1,72 @@
import Node, { addNodeClass } from './Node.js';
import { varying } from './VaryingNode.js';
import { nodeImmutable } from '../shadernode/ShaderNode.js';
class IndexNode extends Node {
constructor( scope ) {
super( 'uint' );
this.scope = scope;
this.isInstanceIndexNode = true;
}
generate( builder ) {
const nodeType = this.getNodeType( builder );
const scope = this.scope;
let propertyName;
if ( scope === IndexNode.VERTEX ) {
propertyName = builder.getVertexIndex();
} else if ( scope === IndexNode.INSTANCE ) {
propertyName = builder.getInstanceIndex();
} else if ( scope === IndexNode.DRAW ) {
propertyName = builder.getDrawIndex();
} else {
throw new Error( 'THREE.IndexNode: Unknown scope: ' + scope );
}
let output;
if ( builder.shaderStage === 'vertex' || builder.shaderStage === 'compute' ) {
output = propertyName;
} else {
const nodeVarying = varying( this );
output = nodeVarying.build( builder, nodeType );
}
return output;
}
}
IndexNode.VERTEX = 'vertex';
IndexNode.INSTANCE = 'instance';
IndexNode.DRAW = 'draw';
export default IndexNode;
export const vertexIndex = nodeImmutable( IndexNode, IndexNode.VERTEX );
export const instanceIndex = nodeImmutable( IndexNode, IndexNode.INSTANCE );
export const drawIndex = nodeImmutable( IndexNode, IndexNode.DRAW );
addNodeClass( 'IndexNode', IndexNode );

View File

@ -0,0 +1,83 @@
import Node, { addNodeClass } from './Node.js';
import { getValueType, getValueFromType, arrayBufferToBase64 } from './NodeUtils.js';
class InputNode extends Node {
constructor( value, nodeType = null ) {
super( nodeType );
this.isInputNode = true;
this.value = value;
this.precision = null;
}
getNodeType( /*builder*/ ) {
if ( this.nodeType === null ) {
return getValueType( this.value );
}
return this.nodeType;
}
getInputType( builder ) {
return this.getNodeType( builder );
}
setPrecision( precision ) {
this.precision = precision;
return this;
}
serialize( data ) {
super.serialize( data );
data.value = this.value;
if ( this.value && this.value.toArray ) data.value = this.value.toArray();
data.valueType = getValueType( this.value );
data.nodeType = this.nodeType;
if ( data.valueType === 'ArrayBuffer' ) data.value = arrayBufferToBase64( data.value );
data.precision = this.precision;
}
deserialize( data ) {
super.deserialize( data );
this.nodeType = data.nodeType;
this.value = Array.isArray( data.value ) ? getValueFromType( data.valueType, ...data.value ) : data.value;
this.precision = data.precision || null;
if ( this.value && this.value.fromArray ) this.value = this.value.fromArray( data.value );
}
generate( /*builder, output*/ ) {
console.warn( 'Abstract function.' );
}
}
export default InputNode;
addNodeClass( 'InputNode', InputNode );

View File

@ -0,0 +1,17 @@
class LightingModel {
start( /*input, stack, builder*/ ) { }
finish( /*input, stack, builder*/ ) { }
direct( /*input, stack, builder*/ ) { }
directRectArea( /*input, stack, builder*/ ) {}
indirect( /*input, stack, builder*/ ) { }
ambientOcclusion( /*input, stack, builder*/ ) { }
}
export default LightingModel;

View File

@ -0,0 +1,76 @@
import { addNodeClass } from './Node.js';
import OutputStructNode from './OutputStructNode.js';
import { nodeProxy, vec4 } from '../shadernode/ShaderNode.js';
function getTextureIndex( textures, name ) {
for ( let i = 0; i < textures.length; i ++ ) {
if ( textures[ i ].name === name ) {
return i;
}
}
return - 1;
}
class MRTNode extends OutputStructNode {
constructor( outputNodes ) {
super();
this.outputNodes = outputNodes;
this.isMRTNode = true;
}
getNode( name ) {
return this.outputNodes[ name ];
}
merge( mrtNode ) {
const outputs = { ...this.outputNodes, ...mrtNode.outputNodes };
return mrt( outputs );
}
setup( builder ) {
const outputNodes = this.outputNodes;
const mrt = builder.renderer.getRenderTarget();
const members = [];
const textures = mrt.textures;
for ( const name in outputNodes ) {
const index = getTextureIndex( textures, name );
members[ index ] = vec4( outputNodes[ name ] );
}
this.members = members;
return super.setup( builder );
}
}
export default MRTNode;
export const mrt = nodeProxy( MRTNode );
addNodeClass( 'MRTNode', MRTNode );

View File

@ -0,0 +1,571 @@
import { NodeUpdateType } from './constants.js';
import { getNodeChildren, getCacheKey } from './NodeUtils.js';
import { EventDispatcher } from '../../core/EventDispatcher.js';
import { MathUtils } from '../../math/MathUtils.js';
const NodeClasses = new Map();
let _nodeId = 0;
class Node extends EventDispatcher {
constructor( nodeType = null ) {
super();
this.nodeType = nodeType;
this.updateType = NodeUpdateType.NONE;
this.updateBeforeType = NodeUpdateType.NONE;
this.updateAfterType = NodeUpdateType.NONE;
this.uuid = MathUtils.generateUUID();
this.version = 0;
this._cacheKey = null;
this._cacheKeyVersion = 0;
this.global = false;
this.isNode = true;
Object.defineProperty( this, 'id', { value: _nodeId ++ } );
}
set needsUpdate( value ) {
if ( value === true ) {
this.version ++;
}
}
get type() {
return this.constructor.type;
}
onUpdate( callback, updateType ) {
this.updateType = updateType;
this.update = callback.bind( this.getSelf() );
return this;
}
onFrameUpdate( callback ) {
return this.onUpdate( callback, NodeUpdateType.FRAME );
}
onRenderUpdate( callback ) {
return this.onUpdate( callback, NodeUpdateType.RENDER );
}
onObjectUpdate( callback ) {
return this.onUpdate( callback, NodeUpdateType.OBJECT );
}
onReference( callback ) {
this.updateReference = callback.bind( this.getSelf() );
return this;
}
getSelf() {
// Returns non-node object.
return this.self || this;
}
updateReference( /*state*/ ) {
return this;
}
isGlobal( /*builder*/ ) {
return this.global;
}
* getChildren() {
for ( const { childNode } of getNodeChildren( this ) ) {
yield childNode;
}
}
dispose() {
this.dispatchEvent( { type: 'dispose' } );
}
traverse( callback ) {
callback( this );
for ( const childNode of this.getChildren() ) {
childNode.traverse( callback );
}
}
getCacheKey( force = false ) {
force = force || this.version !== this._cacheKeyVersion;
if ( force === true || this._cacheKey === null ) {
this._cacheKey = getCacheKey( this, force );
this._cacheKeyVersion = this.version;
}
return this._cacheKey;
}
getHash( /*builder*/ ) {
return this.uuid;
}
getUpdateType() {
return this.updateType;
}
getUpdateBeforeType() {
return this.updateBeforeType;
}
getUpdateAfterType() {
return this.updateAfterType;
}
getElementType( builder ) {
const type = this.getNodeType( builder );
const elementType = builder.getElementType( type );
return elementType;
}
getNodeType( builder ) {
const nodeProperties = builder.getNodeProperties( this );
if ( nodeProperties.outputNode ) {
return nodeProperties.outputNode.getNodeType( builder );
}
return this.nodeType;
}
getShared( builder ) {
const hash = this.getHash( builder );
const nodeFromHash = builder.getNodeFromHash( hash );
return nodeFromHash || this;
}
setup( builder ) {
const nodeProperties = builder.getNodeProperties( this );
let index = 0;
for ( const childNode of this.getChildren() ) {
nodeProperties[ 'node' + index ++ ] = childNode;
}
// return a outputNode if exists
return null;
}
increaseUsage( builder ) {
const nodeData = builder.getDataFromNode( this );
nodeData.usageCount = nodeData.usageCount === undefined ? 1 : nodeData.usageCount + 1;
return nodeData.usageCount;
}
analyze( builder ) {
const usageCount = this.increaseUsage( builder );
if ( usageCount === 1 ) {
// node flow children
const nodeProperties = builder.getNodeProperties( this );
for ( const childNode of Object.values( nodeProperties ) ) {
if ( childNode && childNode.isNode === true ) {
childNode.build( builder );
}
}
}
}
generate( builder, output ) {
const { outputNode } = builder.getNodeProperties( this );
if ( outputNode && outputNode.isNode === true ) {
return outputNode.build( builder, output );
}
}
updateBefore( /*frame*/ ) {
console.warn( 'Abstract function.' );
}
updateAfter( /*frame*/ ) {
console.warn( 'Abstract function.' );
}
update( /*frame*/ ) {
console.warn( 'Abstract function.' );
}
build( builder, output = null ) {
const refNode = this.getShared( builder );
if ( this !== refNode ) {
return refNode.build( builder, output );
}
builder.addNode( this );
builder.addChain( this );
/* Build stages expected results:
- "setup" -> Node
- "analyze" -> null
- "generate" -> String
*/
let result = null;
const buildStage = builder.getBuildStage();
if ( buildStage === 'setup' ) {
this.updateReference( builder );
const properties = builder.getNodeProperties( this );
if ( properties.initialized !== true ) {
const stackNodesBeforeSetup = builder.stack.nodes.length;
properties.initialized = true;
properties.outputNode = this.setup( builder );
if ( properties.outputNode !== null && builder.stack.nodes.length !== stackNodesBeforeSetup ) {
properties.outputNode = builder.stack;
}
for ( const childNode of Object.values( properties ) ) {
if ( childNode && childNode.isNode === true ) {
childNode.build( builder );
}
}
}
} else if ( buildStage === 'analyze' ) {
this.analyze( builder );
} else if ( buildStage === 'generate' ) {
const isGenerateOnce = this.generate.length === 1;
if ( isGenerateOnce ) {
const type = this.getNodeType( builder );
const nodeData = builder.getDataFromNode( this );
result = nodeData.snippet;
if ( result === undefined ) {
result = this.generate( builder ) || '';
nodeData.snippet = result;
}
result = builder.format( result, type, output );
} else {
result = this.generate( builder, output ) || '';
}
}
builder.removeChain( this );
return result;
}
getSerializeChildren() {
return getNodeChildren( this );
}
serialize( json ) {
const nodeChildren = this.getSerializeChildren();
const inputNodes = {};
for ( const { property, index, childNode } of nodeChildren ) {
if ( index !== undefined ) {
if ( inputNodes[ property ] === undefined ) {
inputNodes[ property ] = Number.isInteger( index ) ? [] : {};
}
inputNodes[ property ][ index ] = childNode.toJSON( json.meta ).uuid;
} else {
inputNodes[ property ] = childNode.toJSON( json.meta ).uuid;
}
}
if ( Object.keys( inputNodes ).length > 0 ) {
json.inputNodes = inputNodes;
}
}
deserialize( json ) {
if ( json.inputNodes !== undefined ) {
const nodes = json.meta.nodes;
for ( const property in json.inputNodes ) {
if ( Array.isArray( json.inputNodes[ property ] ) ) {
const inputArray = [];
for ( const uuid of json.inputNodes[ property ] ) {
inputArray.push( nodes[ uuid ] );
}
this[ property ] = inputArray;
} else if ( typeof json.inputNodes[ property ] === 'object' ) {
const inputObject = {};
for ( const subProperty in json.inputNodes[ property ] ) {
const uuid = json.inputNodes[ property ][ subProperty ];
inputObject[ subProperty ] = nodes[ uuid ];
}
this[ property ] = inputObject;
} else {
const uuid = json.inputNodes[ property ];
this[ property ] = nodes[ uuid ];
}
}
}
}
toJSON( meta ) {
const { uuid, type } = this;
const isRoot = ( meta === undefined || typeof meta === 'string' );
if ( isRoot ) {
meta = {
textures: {},
images: {},
nodes: {}
};
}
// serialize
let data = meta.nodes[ uuid ];
if ( data === undefined ) {
data = {
uuid,
type,
meta,
metadata: {
version: 4.6,
type: 'Node',
generator: 'Node.toJSON'
}
};
if ( isRoot !== true ) meta.nodes[ data.uuid ] = data;
this.serialize( data );
delete data.meta;
}
// TODO: Copied from Object3D.toJSON
function extractFromCache( cache ) {
const values = [];
for ( const key in cache ) {
const data = cache[ key ];
delete data.metadata;
values.push( data );
}
return values;
}
if ( isRoot ) {
const textures = extractFromCache( meta.textures );
const images = extractFromCache( meta.images );
const nodes = extractFromCache( meta.nodes );
if ( textures.length > 0 ) data.textures = textures;
if ( images.length > 0 ) data.images = images;
if ( nodes.length > 0 ) data.nodes = nodes;
}
return data;
}
}
export default Node;
export function addNodeClass( type, nodeClass ) {
if ( typeof nodeClass !== 'function' || ! type ) throw new Error( `Node class ${ type } is not a class` );
if ( NodeClasses.has( type ) ) {
console.warn( `Redefinition of node class ${ type }` );
return;
}
NodeClasses.set( type, nodeClass );
nodeClass.type = type;
}
export function createNodeFromType( type ) {
const Class = NodeClasses.get( type );
if ( Class !== undefined ) {
return new Class();
}
}

View File

@ -0,0 +1,15 @@
class NodeAttribute {
constructor( name, type, node = null ) {
this.isNodeAttribute = true;
this.name = name;
this.type = type;
this.node = node;
}
}
export default NodeAttribute;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,36 @@
let id = 0;
class NodeCache {
constructor( parent = null ) {
this.id = id ++;
this.nodesData = new WeakMap();
this.parent = parent;
}
getData( node ) {
let data = this.nodesData.get( node );
if ( data === undefined && this.parent !== null ) {
data = this.parent.getData( node );
}
return data;
}
setData( node, data ) {
this.nodesData.set( node, data );
}
}
export default NodeCache;

View File

@ -0,0 +1,15 @@
class NodeCode {
constructor( name, type, code = '' ) {
this.name = name;
this.type = type;
this.code = code;
Object.defineProperty( this, 'isNodeCode', { value: true } );
}
}
export default NodeCode;

View File

@ -0,0 +1,185 @@
import { NodeUpdateType } from './constants.js';
class NodeFrame {
constructor() {
this.time = 0;
this.deltaTime = 0;
this.frameId = 0;
this.renderId = 0;
this.startTime = null;
this.updateMap = new WeakMap();
this.updateBeforeMap = new WeakMap();
this.updateAfterMap = new WeakMap();
this.renderer = null;
this.material = null;
this.camera = null;
this.object = null;
this.scene = null;
}
_getMaps( referenceMap, nodeRef ) {
let maps = referenceMap.get( nodeRef );
if ( maps === undefined ) {
maps = {
renderMap: new WeakMap(),
frameMap: new WeakMap()
};
referenceMap.set( nodeRef, maps );
}
return maps;
}
updateBeforeNode( node ) {
const updateType = node.getUpdateBeforeType();
const reference = node.updateReference( this );
if ( updateType === NodeUpdateType.FRAME ) {
const { frameMap } = this._getMaps( this.updateBeforeMap, reference );
if ( frameMap.get( reference ) !== this.frameId ) {
if ( node.updateBefore( this ) !== false ) {
frameMap.set( reference, this.frameId );
}
}
} else if ( updateType === NodeUpdateType.RENDER ) {
const { renderMap } = this._getMaps( this.updateBeforeMap, reference );
if ( renderMap.get( reference ) !== this.renderId ) {
if ( node.updateBefore( this ) !== false ) {
renderMap.set( reference, this.renderId );
}
}
} else if ( updateType === NodeUpdateType.OBJECT ) {
node.updateBefore( this );
}
}
updateAfterNode( node ) {
const updateType = node.getUpdateAfterType();
const reference = node.updateReference( this );
if ( updateType === NodeUpdateType.FRAME ) {
const { frameMap } = this._getMaps( this.updateAfterMap, reference );
if ( frameMap.get( reference ) !== this.frameId ) {
if ( node.updateAfter( this ) !== false ) {
frameMap.set( reference, this.frameId );
}
}
} else if ( updateType === NodeUpdateType.RENDER ) {
const { renderMap } = this._getMaps( this.updateAfterMap, reference );
if ( renderMap.get( reference ) !== this.renderId ) {
if ( node.updateAfter( this ) !== false ) {
renderMap.set( reference, this.renderId );
}
}
} else if ( updateType === NodeUpdateType.OBJECT ) {
node.updateAfter( this );
}
}
updateNode( node ) {
const updateType = node.getUpdateType();
const reference = node.updateReference( this );
if ( updateType === NodeUpdateType.FRAME ) {
const { frameMap } = this._getMaps( this.updateMap, reference );
if ( frameMap.get( reference ) !== this.frameId ) {
if ( node.update( this ) !== false ) {
frameMap.set( reference, this.frameId );
}
}
} else if ( updateType === NodeUpdateType.RENDER ) {
const { renderMap } = this._getMaps( this.updateMap, reference );
if ( renderMap.get( reference ) !== this.renderId ) {
if ( node.update( this ) !== false ) {
renderMap.set( reference, this.renderId );
}
}
} else if ( updateType === NodeUpdateType.OBJECT ) {
node.update( this );
}
}
update() {
this.frameId ++;
if ( this.lastTime === undefined ) this.lastTime = performance.now();
this.deltaTime = ( performance.now() - this.lastTime ) / 1000;
this.lastTime = performance.now();
this.time += this.deltaTime;
}
}
export default NodeFrame;

View File

@ -0,0 +1,22 @@
class NodeFunction {
constructor( type, inputs, name = '', precision = '' ) {
this.type = type;
this.inputs = inputs;
this.name = name;
this.precision = precision;
}
getCode( /*name = this.name*/ ) {
console.warn( 'Abstract function.' );
}
}
NodeFunction.isNodeFunction = true;
export default NodeFunction;

View File

@ -0,0 +1,17 @@
class NodeFunctionInput {
constructor( type, name, count = null, qualifier = '', isConst = false ) {
this.type = type;
this.name = name;
this.count = count;
this.qualifier = qualifier;
this.isConst = isConst;
}
}
NodeFunctionInput.isNodeFunctionInput = true;
export default NodeFunctionInput;

View File

@ -0,0 +1,80 @@
class NodeKeywords {
constructor() {
this.keywords = [];
this.nodes = {};
this.keywordsCallback = {};
}
getNode( name ) {
let node = this.nodes[ name ];
if ( node === undefined && this.keywordsCallback[ name ] !== undefined ) {
node = this.keywordsCallback[ name ]( name );
this.nodes[ name ] = node;
}
return node;
}
addKeyword( name, callback ) {
this.keywords.push( name );
this.keywordsCallback[ name ] = callback;
return this;
}
parse( code ) {
const keywordNames = this.keywords;
const regExp = new RegExp( `\\b${keywordNames.join( '\\b|\\b' )}\\b`, 'g' );
const codeKeywords = code.match( regExp );
const keywordNodes = [];
if ( codeKeywords !== null ) {
for ( const keyword of codeKeywords ) {
const node = this.getNode( keyword );
if ( node !== undefined && keywordNodes.indexOf( node ) === - 1 ) {
keywordNodes.push( node );
}
}
}
return keywordNodes;
}
include( builder, code ) {
const keywordNodes = this.parse( code );
for ( const keywordNode of keywordNodes ) {
keywordNode.build( builder );
}
}
}
export default NodeKeywords;

View File

@ -0,0 +1,11 @@
class NodeParser {
parseFunction( /*source*/ ) {
console.warn( 'Abstract function.' );
}
}
export default NodeParser;

View File

@ -0,0 +1,39 @@
class NodeUniform {
constructor( name, type, node ) {
this.isNodeUniform = true;
this.name = name;
this.type = type;
this.node = node.getSelf();
}
get value() {
return this.node.value;
}
set value( val ) {
this.node.value = val;
}
get id() {
return this.node.id;
}
get groupNode() {
return this.node.groupNode;
}
}
export default NodeUniform;

View File

@ -0,0 +1,215 @@
import { Color } from '../../math/Color.js';
import { Matrix3 } from '../../math/Matrix3.js';
import { Matrix4 } from '../../math/Matrix4.js';
import { Vector2 } from '../../math/Vector2.js';
import { Vector3 } from '../../math/Vector3.js';
import { Vector4 } from '../../math/Vector4.js';
export function getCacheKey( object, force = false ) {
let cacheKey = '{';
if ( object.isNode === true ) {
cacheKey += object.id;
}
for ( const { property, childNode } of getNodeChildren( object ) ) {
cacheKey += ',' + property.slice( 0, - 4 ) + ':' + childNode.getCacheKey( force );
}
cacheKey += '}';
return cacheKey;
}
export function* getNodeChildren( node, toJSON = false ) {
for ( const property in node ) {
// Ignore private properties.
if ( property.startsWith( '_' ) === true ) continue;
const object = node[ property ];
if ( Array.isArray( object ) === true ) {
for ( let i = 0; i < object.length; i ++ ) {
const child = object[ i ];
if ( child && ( child.isNode === true || toJSON && typeof child.toJSON === 'function' ) ) {
yield { property, index: i, childNode: child };
}
}
} else if ( object && object.isNode === true ) {
yield { property, childNode: object };
} else if ( typeof object === 'object' ) {
for ( const subProperty in object ) {
const child = object[ subProperty ];
if ( child && ( child.isNode === true || toJSON && typeof child.toJSON === 'function' ) ) {
yield { property, index: subProperty, childNode: child };
}
}
}
}
}
export function getValueType( value ) {
if ( value === undefined || value === null ) return null;
const typeOf = typeof value;
if ( value.isNode === true ) {
return 'node';
} else if ( typeOf === 'number' ) {
return 'float';
} else if ( typeOf === 'boolean' ) {
return 'bool';
} else if ( typeOf === 'string' ) {
return 'string';
} else if ( typeOf === 'function' ) {
return 'shader';
} else if ( value.isVector2 === true ) {
return 'vec2';
} else if ( value.isVector3 === true ) {
return 'vec3';
} else if ( value.isVector4 === true ) {
return 'vec4';
} else if ( value.isMatrix3 === true ) {
return 'mat3';
} else if ( value.isMatrix4 === true ) {
return 'mat4';
} else if ( value.isColor === true ) {
return 'color';
} else if ( value instanceof ArrayBuffer ) {
return 'ArrayBuffer';
}
return null;
}
export function getValueFromType( type, ...params ) {
const last4 = type ? type.slice( - 4 ) : undefined;
if ( params.length === 1 ) { // ensure same behaviour as in NodeBuilder.format()
if ( last4 === 'vec2' ) params = [ params[ 0 ], params[ 0 ] ];
else if ( last4 === 'vec3' ) params = [ params[ 0 ], params[ 0 ], params[ 0 ] ];
else if ( last4 === 'vec4' ) params = [ params[ 0 ], params[ 0 ], params[ 0 ], params[ 0 ] ];
}
if ( type === 'color' ) {
return new Color( ...params );
} else if ( last4 === 'vec2' ) {
return new Vector2( ...params );
} else if ( last4 === 'vec3' ) {
return new Vector3( ...params );
} else if ( last4 === 'vec4' ) {
return new Vector4( ...params );
} else if ( last4 === 'mat3' ) {
return new Matrix3( ...params );
} else if ( last4 === 'mat4' ) {
return new Matrix4( ...params );
} else if ( type === 'bool' ) {
return params[ 0 ] || false;
} else if ( ( type === 'float' ) || ( type === 'int' ) || ( type === 'uint' ) ) {
return params[ 0 ] || 0;
} else if ( type === 'string' ) {
return params[ 0 ] || '';
} else if ( type === 'ArrayBuffer' ) {
return base64ToArrayBuffer( params[ 0 ] );
}
return null;
}
export function arrayBufferToBase64( arrayBuffer ) {
let chars = '';
const array = new Uint8Array( arrayBuffer );
for ( let i = 0; i < array.length; i ++ ) {
chars += String.fromCharCode( array[ i ] );
}
return btoa( chars );
}
export function base64ToArrayBuffer( base64 ) {
return Uint8Array.from( atob( base64 ), c => c.charCodeAt( 0 ) ).buffer;
}

View File

@ -0,0 +1,14 @@
class NodeVar {
constructor( name, type ) {
this.isNodeVar = true;
this.name = name;
this.type = type;
}
}
export default NodeVar;

View File

@ -0,0 +1,17 @@
import NodeVar from './NodeVar.js';
class NodeVarying extends NodeVar {
constructor( name, type ) {
super( name, type );
this.needsInterpolation = false;
this.isNodeVarying = true;
}
}
export default NodeVarying;

View File

@ -0,0 +1,59 @@
import Node, { addNodeClass } from './Node.js';
import StructTypeNode from './StructTypeNode.js';
import { nodeProxy } from '../shadernode/ShaderNode.js';
class OutputStructNode extends Node {
constructor( ...members ) {
super();
this.members = members;
this.isOutputStructNode = true;
}
setup( builder ) {
super.setup( builder );
const members = this.members;
const types = [];
for ( let i = 0; i < members.length; i ++ ) {
types.push( members[ i ].getNodeType( builder ) );
}
this.nodeType = builder.getStructTypeFromNode( new StructTypeNode( types ) ).name;
}
generate( builder, output ) {
const propertyName = builder.getOutputStructName();
const members = this.members;
const structPrefix = propertyName !== '' ? propertyName + '.' : '';
for ( let i = 0; i < members.length; i ++ ) {
const snippet = members[ i ].build( builder, output );
builder.addLineFlowCode( `${ structPrefix }m${ i } = ${ snippet }` );
}
return propertyName;
}
}
export default OutputStructNode;
export const outputStruct = nodeProxy( OutputStructNode );
addNodeClass( 'OutputStructNode', OutputStructNode );

View File

@ -0,0 +1,33 @@
import { addNodeClass } from './Node.js';
import { nodeObject } from '../shadernode/ShaderNode.js';
import PropertyNode from './PropertyNode.js';
class ParameterNode extends PropertyNode {
constructor( nodeType, name = null ) {
super( nodeType, name );
this.isParameterNode = true;
}
getHash() {
return this.uuid;
}
generate() {
return this.name;
}
}
export default ParameterNode;
export const parameter = ( type, name ) => nodeObject( new ParameterNode( type, name ) );
addNodeClass( 'ParameterNode', ParameterNode );

View File

@ -0,0 +1,84 @@
import Node, { addNodeClass } from './Node.js';
import { nodeImmutable, nodeObject } from '../shadernode/ShaderNode.js';
class PropertyNode extends Node {
constructor( nodeType, name = null, varying = false ) {
super( nodeType );
this.name = name;
this.varying = varying;
this.isPropertyNode = true;
}
getHash( builder ) {
return this.name || super.getHash( builder );
}
isGlobal( /*builder*/ ) {
return true;
}
generate( builder ) {
let nodeVar;
if ( this.varying === true ) {
nodeVar = builder.getVaryingFromNode( this, this.name );
nodeVar.needsInterpolation = true;
} else {
nodeVar = builder.getVarFromNode( this, this.name );
}
return builder.getPropertyName( nodeVar );
}
}
export default PropertyNode;
export const property = ( type, name ) => nodeObject( new PropertyNode( type, name ) );
export const varyingProperty = ( type, name ) => nodeObject( new PropertyNode( type, name, true ) );
export const diffuseColor = nodeImmutable( PropertyNode, 'vec4', 'DiffuseColor' );
export const emissive = nodeImmutable( PropertyNode, 'vec3', 'EmissiveColor' );
export const roughness = nodeImmutable( PropertyNode, 'float', 'Roughness' );
export const metalness = nodeImmutable( PropertyNode, 'float', 'Metalness' );
export const clearcoat = nodeImmutable( PropertyNode, 'float', 'Clearcoat' );
export const clearcoatRoughness = nodeImmutable( PropertyNode, 'float', 'ClearcoatRoughness' );
export const sheen = nodeImmutable( PropertyNode, 'vec3', 'Sheen' );
export const sheenRoughness = nodeImmutable( PropertyNode, 'float', 'SheenRoughness' );
export const iridescence = nodeImmutable( PropertyNode, 'float', 'Iridescence' );
export const iridescenceIOR = nodeImmutable( PropertyNode, 'float', 'IridescenceIOR' );
export const iridescenceThickness = nodeImmutable( PropertyNode, 'float', 'IridescenceThickness' );
export const alphaT = nodeImmutable( PropertyNode, 'float', 'AlphaT' );
export const anisotropy = nodeImmutable( PropertyNode, 'float', 'Anisotropy' );
export const anisotropyT = nodeImmutable( PropertyNode, 'vec3', 'AnisotropyT' );
export const anisotropyB = nodeImmutable( PropertyNode, 'vec3', 'AnisotropyB' );
export const specularColor = nodeImmutable( PropertyNode, 'color', 'SpecularColor' );
export const specularF90 = nodeImmutable( PropertyNode, 'float', 'SpecularF90' );
export const shininess = nodeImmutable( PropertyNode, 'float', 'Shininess' );
export const output = nodeImmutable( PropertyNode, 'vec4', 'Output' );
export const dashSize = nodeImmutable( PropertyNode, 'float', 'dashSize' );
export const gapSize = nodeImmutable( PropertyNode, 'float', 'gapSize' );
export const pointWidth = nodeImmutable( PropertyNode, 'float', 'pointWidth' );
export const ior = nodeImmutable( PropertyNode, 'float', 'IOR' );
export const transmission = nodeImmutable( PropertyNode, 'float', 'Transmission' );
export const thickness = nodeImmutable( PropertyNode, 'float', 'Thickness' );
export const attenuationDistance = nodeImmutable( PropertyNode, 'float', 'AttenuationDistance' );
export const attenuationColor = nodeImmutable( PropertyNode, 'color', 'AttenuationColor' );
export const dispersion = nodeImmutable( PropertyNode, 'float', 'Dispersion' );
addNodeClass( 'PropertyNode', PropertyNode );

View File

@ -0,0 +1,89 @@
import Node, { addNodeClass } from './Node.js';
import { cond } from '../math/CondNode.js';
import { ShaderNode, nodeProxy, getCurrentStack, setCurrentStack } from '../shadernode/ShaderNode.js';
class StackNode extends Node {
constructor( parent = null ) {
super();
this.nodes = [];
this.outputNode = null;
this.parent = parent;
this._currentCond = null;
this.isStackNode = true;
}
getNodeType( builder ) {
return this.outputNode ? this.outputNode.getNodeType( builder ) : 'void';
}
add( node ) {
this.nodes.push( node );
return this;
}
if( boolNode, method ) {
const methodNode = new ShaderNode( method );
this._currentCond = cond( boolNode, methodNode );
return this.add( this._currentCond );
}
elseif( boolNode, method ) {
const methodNode = new ShaderNode( method );
const ifNode = cond( boolNode, methodNode );
this._currentCond.elseNode = ifNode;
this._currentCond = ifNode;
return this;
}
else( method ) {
this._currentCond.elseNode = new ShaderNode( method );
return this;
}
build( builder, ...params ) {
const previousStack = getCurrentStack();
setCurrentStack( this );
for ( const node of this.nodes ) {
node.build( builder, 'void' );
}
setCurrentStack( previousStack );
return this.outputNode ? this.outputNode.build( builder, ...params ) : super.build( builder, ...params );
}
}
export default StackNode;
export const stack = nodeProxy( StackNode );
addNodeClass( 'StackNode', StackNode );

View File

@ -0,0 +1,24 @@
import Node, { addNodeClass } from './Node.js';
class StructTypeNode extends Node {
constructor( types ) {
super();
this.types = types;
this.isStructTypeNode = true;
}
getMemberTypes() {
return this.types;
}
}
export default StructTypeNode;
addNodeClass( 'StructTypeNode', StructTypeNode );

View File

@ -0,0 +1,58 @@
import Node, { addNodeClass } from './Node.js';
class TempNode extends Node {
constructor( type ) {
super( type );
this.isTempNode = true;
}
hasDependencies( builder ) {
return builder.getDataFromNode( this ).usageCount > 1;
}
build( builder, output ) {
const buildStage = builder.getBuildStage();
if ( buildStage === 'generate' ) {
const type = builder.getVectorType( this.getNodeType( builder, output ) );
const nodeData = builder.getDataFromNode( this );
if ( nodeData.propertyName !== undefined ) {
return builder.format( nodeData.propertyName, type, output );
} else if ( type !== 'void' && output !== 'void' && this.hasDependencies( builder ) ) {
const snippet = super.build( builder, type );
const nodeVar = builder.getVarFromNode( this, null, type );
const propertyName = builder.getPropertyName( nodeVar );
builder.addLineFlowCode( `${propertyName} = ${snippet}` );
nodeData.snippet = snippet;
nodeData.propertyName = propertyName;
return builder.format( nodeData.propertyName, type, output );
}
}
return super.build( builder, output );
}
}
export default TempNode;
addNodeClass( 'TempNode', TempNode );

View File

@ -0,0 +1,13 @@
class UniformGroup {
constructor( name ) {
this.name = name;
this.isUniformGroup = true;
}
}
export default UniformGroup;

View File

@ -0,0 +1,56 @@
import Node from './Node.js';
import { addNodeClass } from './Node.js';
class UniformGroupNode extends Node {
constructor( name, shared = false ) {
super( 'string' );
this.name = name;
this.version = 0;
this.shared = shared;
this.isUniformGroup = true;
}
set needsUpdate( value ) {
if ( value === true ) this.version ++;
}
serialize( data ) {
super.serialize( data );
data.name = this.name;
data.version = this.version;
data.shared = this.shared;
}
deserialize( data ) {
super.deserialize( data );
this.name = data.name;
this.version = data.version;
this.shared = data.shared;
}
}
export const uniformGroup = ( name ) => new UniformGroupNode( name );
export const sharedUniformGroup = ( name ) => new UniformGroupNode( name, true );
export const frameGroup = sharedUniformGroup( 'frame' );
export const renderGroup = sharedUniformGroup( 'render' );
export const objectGroup = uniformGroup( 'object' );
export default UniformGroupNode;
addNodeClass( 'UniformGroupNode', UniformGroupNode );

View File

@ -0,0 +1,109 @@
import InputNode from './InputNode.js';
import { objectGroup } from './UniformGroupNode.js';
import { addNodeClass } from './Node.js';
import { nodeObject, getConstNodeType } from '../shadernode/ShaderNode.js';
class UniformNode extends InputNode {
constructor( value, nodeType = null ) {
super( value, nodeType );
this.isUniformNode = true;
this.name = '';
this.groupNode = objectGroup;
}
label( name ) {
this.name = name;
return this;
}
setGroup( group ) {
this.groupNode = group;
return this;
}
getGroup() {
return this.groupNode;
}
getUniformHash( builder ) {
return this.getHash( builder );
}
onUpdate( callback, updateType ) {
const self = this.getSelf();
callback = callback.bind( self );
return super.onUpdate( ( frame ) => {
const value = callback( frame, self );
if ( value !== undefined ) {
this.value = value;
}
}, updateType );
}
generate( builder, output ) {
const type = this.getNodeType( builder );
const hash = this.getUniformHash( builder );
let sharedNode = builder.getNodeFromHash( hash );
if ( sharedNode === undefined ) {
builder.setHashNode( this, hash );
sharedNode = this;
}
const sharedNodeType = sharedNode.getInputType( builder );
const nodeUniform = builder.getUniformFromNode( sharedNode, sharedNodeType, builder.shaderStage, this.name || builder.context.label );
const propertyName = builder.getPropertyName( nodeUniform );
if ( builder.context.label !== undefined ) delete builder.context.label;
return builder.format( propertyName, type, output );
}
}
export default UniformNode;
export const uniform = ( arg1, arg2 ) => {
const nodeType = getConstNodeType( arg2 || arg1 );
// @TODO: get ConstNode from .traverse() in the future
const value = ( arg1 && arg1.isNode === true ) ? ( arg1.node && arg1.node.value ) || arg1.value : arg1;
return nodeObject( new UniformNode( value, nodeType ) );
};
addNodeClass( 'UniformNode', UniformNode );

View File

@ -0,0 +1,56 @@
import Node, { addNodeClass } from './Node.js';
import { addNodeElement, nodeProxy } from '../shadernode/ShaderNode.js';
class VarNode extends Node {
constructor( node, name = null ) {
super();
this.node = node;
this.name = name;
this.global = true;
this.isVarNode = true;
}
getHash( builder ) {
return this.name || super.getHash( builder );
}
getNodeType( builder ) {
return this.node.getNodeType( builder );
}
generate( builder ) {
const { node, name } = this;
const nodeVar = builder.getVarFromNode( this, name, builder.getVectorType( this.getNodeType( builder ) ) );
const propertyName = builder.getPropertyName( nodeVar );
const snippet = node.build( builder, nodeVar.type );
builder.addLineFlowCode( `${propertyName} = ${snippet}` );
return propertyName;
}
}
export default VarNode;
export const temp = nodeProxy( VarNode );
addNodeElement( 'temp', temp ); // @TODO: Will be removed in the future
addNodeElement( 'toVar', ( ...params ) => temp( ...params ).append() );
addNodeClass( 'VarNode', VarNode );

View File

@ -0,0 +1,104 @@
import Node, { addNodeClass } from './Node.js';
import { NodeShaderStage } from './constants.js';
import { addNodeElement, nodeProxy } from '../shadernode/ShaderNode.js';
class VaryingNode extends Node {
constructor( node, name = null ) {
super();
this.node = node;
this.name = name;
this.isVaryingNode = true;
}
isGlobal() {
return true;
}
getHash( builder ) {
return this.name || super.getHash( builder );
}
getNodeType( builder ) {
// VaryingNode is auto type
return this.node.getNodeType( builder );
}
setupVarying( builder ) {
const properties = builder.getNodeProperties( this );
let varying = properties.varying;
if ( varying === undefined ) {
const name = this.name;
const type = this.getNodeType( builder );
properties.varying = varying = builder.getVaryingFromNode( this, name, type );
properties.node = this.node;
}
// 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 ) {
this.setupVarying( builder );
}
analyze( builder ) {
this.setupVarying( builder );
return this.node.analyze( builder );
}
generate( builder ) {
const properties = builder.getNodeProperties( this );
const varying = this.setupVarying( builder );
if ( properties.propertyName === undefined ) {
const type = this.getNodeType( builder );
const propertyName = builder.getPropertyName( varying, NodeShaderStage.VERTEX );
// force node run in vertex stage
builder.flowNodeFromShaderStage( NodeShaderStage.VERTEX, this.node, type, propertyName );
properties.propertyName = propertyName;
}
return builder.getPropertyName( varying );
}
}
export default VaryingNode;
export const varying = nodeProxy( VaryingNode );
addNodeElement( 'varying', varying );
addNodeClass( 'VaryingNode', VaryingNode );

View File

@ -0,0 +1,28 @@
export const NodeShaderStage = {
VERTEX: 'vertex',
FRAGMENT: 'fragment'
};
export const NodeUpdateType = {
NONE: 'none',
FRAME: 'frame',
RENDER: 'render',
OBJECT: 'object'
};
export const NodeType = {
BOOLEAN: 'bool',
INTEGER: 'int',
FLOAT: 'float',
VECTOR2: 'vec2',
VECTOR3: 'vec3',
VECTOR4: 'vec4',
MATRIX2: 'mat2',
MATRIX3: 'mat3',
MATRIX4: 'mat4'
};
export const defaultShaderStages = [ 'fragment', 'vertex' ];
export const defaultBuildStages = [ 'setup', 'analyze', 'generate' ];
export const shaderStages = [ ...defaultShaderStages, 'compute' ];
export const vectorComponents = [ 'x', 'y', 'z', 'w' ];

View File

@ -0,0 +1,152 @@
import TempNode from '../core/TempNode.js';
import { nodeObject, addNodeElement, tslFn, float, vec4 } from '../shadernode/ShaderNode.js';
import { NodeUpdateType } from '../core/constants.js';
import { uv } from '../accessors/UVNode.js';
import { texture } from '../accessors/TextureNode.js';
import { passTexture } from './PassNode.js';
import { uniform } from '../core/UniformNode.js';
import { sign, max } from '../math/MathNode.js';
import QuadMesh from '../../renderers/common/QuadMesh.js';
import { Vector2 } from '../../math/Vector2.js';
import { RenderTarget } from '../../core/RenderTarget.js';
const _size = /*@__PURE__*/ new Vector2();
const _quadMeshComp = /*@__PURE__*/ new QuadMesh();
class AfterImageNode extends TempNode {
constructor( textureNode, damp = 0.96 ) {
super( textureNode );
this.textureNode = textureNode;
this.textureNodeOld = texture();
this.damp = uniform( damp );
this._compRT = new RenderTarget();
this._compRT.texture.name = 'AfterImageNode.comp';
this._oldRT = new RenderTarget();
this._oldRT.texture.name = 'AfterImageNode.old';
this._textureNode = passTexture( this, this._compRT.texture );
this.updateBeforeType = NodeUpdateType.RENDER;
}
getTextureNode() {
return this._textureNode;
}
setSize( width, height ) {
this._compRT.setSize( width, height );
this._oldRT.setSize( width, height );
}
updateBefore( frame ) {
const { renderer } = frame;
const textureNode = this.textureNode;
const map = textureNode.value;
const textureType = map.type;
this._compRT.texture.type = textureType;
this._oldRT.texture.type = textureType;
renderer.getDrawingBufferSize( _size );
this.setSize( _size.x, _size.y );
const currentRenderTarget = renderer.getRenderTarget();
const currentTexture = textureNode.value;
this.textureNodeOld.value = this._oldRT.texture;
// comp
renderer.setRenderTarget( this._compRT );
_quadMeshComp.render( renderer );
// Swap the textures
const temp = this._oldRT;
this._oldRT = this._compRT;
this._compRT = temp;
renderer.setRenderTarget( currentRenderTarget );
textureNode.value = currentTexture;
}
setup( builder ) {
const textureNode = this.textureNode;
const textureNodeOld = this.textureNodeOld;
//
const uvNode = textureNode.uvNode || uv();
textureNodeOld.uvNode = uvNode;
const sampleTexture = ( uv ) => textureNode.uv( uv );
const when_gt = tslFn( ( [ x_immutable, y_immutable ] ) => {
const y = float( y_immutable ).toVar();
const x = vec4( x_immutable ).toVar();
return max( sign( x.sub( y ) ), 0.0 );
} );
const afterImg = tslFn( () => {
const texelOld = vec4( textureNodeOld );
const texelNew = vec4( sampleTexture( uvNode ) );
texelOld.mulAssign( this.damp.mul( when_gt( texelOld, 0.1 ) ) );
return max( texelNew, texelOld );
} );
//
const materialComposed = this._materialComposed || ( this._materialComposed = builder.createNodeMaterial() );
materialComposed.fragmentNode = afterImg();
_quadMeshComp.material = materialComposed;
//
const properties = builder.getNodeProperties( this );
properties.textureNode = textureNode;
//
return this._textureNode;
}
dispose() {
this._compRT.dispose();
this._oldRT.dispose();
}
}
export const afterImage = ( node, damp ) => nodeObject( new AfterImageNode( nodeObject( node ).toTexture(), damp ) );
addNodeElement( 'afterImage', afterImage );
export default AfterImageNode;

View File

@ -0,0 +1,145 @@
import TempNode from '../core/TempNode.js';
import { nodeObject, addNodeElement, tslFn, float, vec2, vec3 } from '../shadernode/ShaderNode.js';
import { loop } from '../utils/LoopNode.js';
import { uniform } from '../core/UniformNode.js';
import { NodeUpdateType } from '../core/constants.js';
import { threshold } from './ColorAdjustmentNode.js';
import { uv } from '../accessors/UVNode.js';
import { passTexture } from './PassNode.js';
import QuadMesh from '../../renderers/common/QuadMesh.js';
import { Vector2 } from '../../math/Vector2.js';
import { RenderTarget } from '../../core/RenderTarget.js';
const _quadMesh = /*@__PURE__*/ new QuadMesh();
class AnamorphicNode extends TempNode {
constructor( textureNode, tresholdNode, scaleNode, samples ) {
super( 'vec4' );
this.textureNode = textureNode;
this.tresholdNode = tresholdNode;
this.scaleNode = scaleNode;
this.colorNode = vec3( 0.1, 0.0, 1.0 );
this.samples = samples;
this.resolution = new Vector2( 1, 1 );
this._renderTarget = new RenderTarget();
this._renderTarget.texture.name = 'anamorphic';
this._invSize = uniform( new Vector2() );
this._textureNode = passTexture( this, this._renderTarget.texture );
this.updateBeforeType = NodeUpdateType.RENDER;
}
getTextureNode() {
return this._textureNode;
}
setSize( width, height ) {
this._invSize.value.set( 1 / width, 1 / height );
width = Math.max( Math.round( width * this.resolution.x ), 1 );
height = Math.max( Math.round( height * this.resolution.y ), 1 );
this._renderTarget.setSize( width, height );
}
updateBefore( frame ) {
const { renderer } = frame;
const textureNode = this.textureNode;
const map = textureNode.value;
this._renderTarget.texture.type = map.type;
const currentRenderTarget = renderer.getRenderTarget();
const currentTexture = textureNode.value;
_quadMesh.material = this._material;
this.setSize( map.image.width, map.image.height );
// render
renderer.setRenderTarget( this._renderTarget );
_quadMesh.render( renderer );
// restore
renderer.setRenderTarget( currentRenderTarget );
textureNode.value = currentTexture;
}
setup( builder ) {
const textureNode = this.textureNode;
const uvNode = textureNode.uvNode || uv();
const sampleTexture = ( uv ) => textureNode.uv( uv );
const anamorph = tslFn( () => {
const samples = this.samples;
const halfSamples = Math.floor( samples / 2 );
const total = vec3( 0 ).toVar();
loop( { start: - halfSamples, end: halfSamples }, ( { i } ) => {
const softness = float( i ).abs().div( halfSamples ).oneMinus();
const uv = vec2( uvNode.x.add( this._invSize.x.mul( i ).mul( this.scaleNode ) ), uvNode.y );
const color = sampleTexture( uv );
const pass = threshold( color, this.tresholdNode ).mul( softness );
total.addAssign( pass );
} );
return total.mul( this.colorNode );
} );
//
const material = this._material || ( this._material = builder.createNodeMaterial() );
material.fragmentNode = anamorph();
//
const properties = builder.getNodeProperties( this );
properties.textureNode = textureNode;
//
return this._textureNode;
}
dispose() {
this._renderTarget.dispose();
}
}
export const anamorphic = ( node, threshold = .9, scale = 3, samples = 32 ) => nodeObject( new AnamorphicNode( nodeObject( node ).toTexture(), nodeObject( threshold ), nodeObject( scale ), samples ) );
addNodeElement( 'anamorphic', anamorphic );
export default AnamorphicNode;

View File

@ -0,0 +1,128 @@
import TempNode from '../core/TempNode.js';
import { /*mix, step,*/ EPSILON } from '../math/MathNode.js';
import { addNodeClass } from '../core/Node.js';
import { addNodeElement, tslFn, nodeProxy, vec3 } from '../shadernode/ShaderNode.js';
export const BurnNode = tslFn( ( { base, blend } ) => {
const fn = ( c ) => blend[ c ].lessThan( EPSILON ).cond( blend[ c ], base[ c ].oneMinus().div( blend[ c ] ).oneMinus().max( 0 ) );
return vec3( fn( 'x' ), fn( 'y' ), fn( 'z' ) );
} ).setLayout( {
name: 'burnColor',
type: 'vec3',
inputs: [
{ name: 'base', type: 'vec3' },
{ name: 'blend', type: 'vec3' }
]
} );
export const DodgeNode = tslFn( ( { base, blend } ) => {
const fn = ( c ) => blend[ c ].equal( 1.0 ).cond( blend[ c ], base[ c ].div( blend[ c ].oneMinus() ).max( 0 ) );
return vec3( fn( 'x' ), fn( 'y' ), fn( 'z' ) );
} ).setLayout( {
name: 'dodgeColor',
type: 'vec3',
inputs: [
{ name: 'base', type: 'vec3' },
{ name: 'blend', type: 'vec3' }
]
} );
export const ScreenNode = tslFn( ( { base, blend } ) => {
const fn = ( c ) => base[ c ].oneMinus().mul( blend[ c ].oneMinus() ).oneMinus();
return vec3( fn( 'x' ), fn( 'y' ), fn( 'z' ) );
} ).setLayout( {
name: 'screenColor',
type: 'vec3',
inputs: [
{ name: 'base', type: 'vec3' },
{ name: 'blend', type: 'vec3' }
]
} );
export const OverlayNode = tslFn( ( { base, blend } ) => {
const fn = ( c ) => base[ c ].lessThan( 0.5 ).cond( base[ c ].mul( blend[ c ], 2.0 ), base[ c ].oneMinus().mul( blend[ c ].oneMinus() ).oneMinus() );
//const fn = ( c ) => mix( base[ c ].oneMinus().mul( blend[ c ].oneMinus() ).oneMinus(), base[ c ].mul( blend[ c ], 2.0 ), step( base[ c ], 0.5 ) );
return vec3( fn( 'x' ), fn( 'y' ), fn( 'z' ) );
} ).setLayout( {
name: 'overlayColor',
type: 'vec3',
inputs: [
{ name: 'base', type: 'vec3' },
{ name: 'blend', type: 'vec3' }
]
} );
class BlendModeNode extends TempNode {
constructor( blendMode, baseNode, blendNode ) {
super();
this.blendMode = blendMode;
this.baseNode = baseNode;
this.blendNode = blendNode;
}
setup() {
const { blendMode, baseNode, blendNode } = this;
const params = { base: baseNode, blend: blendNode };
let outputNode = null;
if ( blendMode === BlendModeNode.BURN ) {
outputNode = BurnNode( params );
} else if ( blendMode === BlendModeNode.DODGE ) {
outputNode = DodgeNode( params );
} else if ( blendMode === BlendModeNode.SCREEN ) {
outputNode = ScreenNode( params );
} else if ( blendMode === BlendModeNode.OVERLAY ) {
outputNode = OverlayNode( params );
}
return outputNode;
}
}
BlendModeNode.BURN = 'burn';
BlendModeNode.DODGE = 'dodge';
BlendModeNode.SCREEN = 'screen';
BlendModeNode.OVERLAY = 'overlay';
export default BlendModeNode;
export const burn = nodeProxy( BlendModeNode, BlendModeNode.BURN );
export const dodge = nodeProxy( BlendModeNode, BlendModeNode.DODGE );
export const overlay = nodeProxy( BlendModeNode, BlendModeNode.OVERLAY );
export const screen = nodeProxy( BlendModeNode, BlendModeNode.SCREEN );
addNodeElement( 'burn', burn );
addNodeElement( 'dodge', dodge );
addNodeElement( 'overlay', overlay );
addNodeElement( 'screen', screen );
addNodeClass( 'BlendModeNode', BlendModeNode );

View File

@ -0,0 +1,333 @@
import TempNode from '../core/TempNode.js';
import { addNodeElement, tslFn, nodeObject, float, vec4, int } from '../shadernode/ShaderNode.js';
import { mix, smoothstep } from '../math/MathNode.js';
import { luminance } from './ColorAdjustmentNode.js';
import { uniform } from '../core/UniformNode.js';
import { uniforms } from '../accessors/UniformsNode.js';
import { uv } from '../accessors/UVNode.js';
import { Color } from '../../math/Color.js';
import { passTexture } from './PassNode.js';
import { RenderTarget } from '../../core/RenderTarget.js';
import { HalfFloatType } from '../../constants.js';
import { NodeUpdateType } from '../core/constants.js';
import { Vector2 } from '../../math/Vector2.js';
import { loop } from '../utils/LoopNode.js';
import { add } from '../math/OperatorNode.js';
import QuadMesh from '../../renderers/common/QuadMesh.js';
import { texture } from '../accessors/TextureNode.js';
import { Vector3 } from '../../math/Vector3.js';
const _quadMesh = /*@__PURE__*/ new QuadMesh();
const _clearColor = /*@__PURE__*/ new Color( 0, 0, 0 );
const _currentClearColor = /*@__PURE__*/ new Color();
const _size = /*@__PURE__*/ new Vector2();
const _BlurDirectionX = /*@__PURE__*/ new Vector2( 1.0, 0.0 );
const _BlurDirectionY = /*@__PURE__*/ new Vector2( 0.0, 1.0 );
class BloomNode extends TempNode {
constructor( inputNode, strength = 1, radius = 0, threshold = 0 ) {
super();
this.inputNode = inputNode;
this.strength = uniform( strength );
this.radius = uniform( radius );
this.threshold = uniform( threshold );
this.smoothWidth = uniform( 0.01 );
//
this._renderTargetsHorizontal = [];
this._renderTargetsVertical = [];
this._nMips = 5;
// render targets
this._renderTargetBright = new RenderTarget( 1, 1, { type: HalfFloatType } );
this._renderTargetBright.texture.name = 'UnrealBloomPass.bright';
this._renderTargetBright.texture.generateMipmaps = false;
for ( let i = 0; i < this._nMips; i ++ ) {
const renderTargetHorizontal = new RenderTarget( 1, 1, { type: HalfFloatType } );
renderTargetHorizontal.texture.name = 'UnrealBloomPass.h' + i;
renderTargetHorizontal.texture.generateMipmaps = false;
this._renderTargetsHorizontal.push( renderTargetHorizontal );
const renderTargetVertical = new RenderTarget( 1, 1, { type: HalfFloatType } );
renderTargetVertical.texture.name = 'UnrealBloomPass.v' + i;
renderTargetVertical.texture.generateMipmaps = false;
this._renderTargetsVertical.push( renderTargetVertical );
}
// materials
this._compositeMaterial = null;
this._highPassFilterMaterial = null;
this._separableBlurMaterials = [];
// pass and texture nodes
this._textureNodeBright = texture( this._renderTargetBright.texture );
this._textureNodeBlur0 = texture( this._renderTargetsVertical[ 0 ].texture );
this._textureNodeBlur1 = texture( this._renderTargetsVertical[ 1 ].texture );
this._textureNodeBlur2 = texture( this._renderTargetsVertical[ 2 ].texture );
this._textureNodeBlur3 = texture( this._renderTargetsVertical[ 3 ].texture );
this._textureNodeBlur4 = texture( this._renderTargetsVertical[ 4 ].texture );
this._textureOutput = passTexture( this, this._renderTargetsHorizontal[ 0 ].texture );
this.updateBeforeType = NodeUpdateType.FRAME;
}
getTextureNode() {
return this._textureOutput;
}
setSize( width, height ) {
let resx = Math.round( width / 2 );
let resy = Math.round( height / 2 );
this._renderTargetBright.setSize( resx, resy );
for ( let i = 0; i < this._nMips; i ++ ) {
this._renderTargetsHorizontal[ i ].setSize( resx, resy );
this._renderTargetsVertical[ i ].setSize( resx, resy );
this._separableBlurMaterials[ i ].invSize.value.set( 1 / resx, 1 / resy );
resx = Math.round( resx / 2 );
resy = Math.round( resy / 2 );
}
}
updateBefore( frame ) {
const { renderer } = frame;
const size = renderer.getDrawingBufferSize( _size );
this.setSize( size.width, size.height );
const currentRenderTarget = renderer.getRenderTarget();
const currentMRT = renderer.getMRT();
renderer.getClearColor( _currentClearColor );
const currentClearAlpha = renderer.getClearAlpha();
this.setSize( size.width, size.height );
renderer.setMRT( null );
renderer.setClearColor( _clearColor, 0 );
// 1. Extract Bright Areas
renderer.setRenderTarget( this._renderTargetBright );
_quadMesh.material = this._highPassFilterMaterial;
_quadMesh.render( renderer );
// 2. Blur All the mips progressively
let inputRenderTarget = this._renderTargetBright;
for ( let i = 0; i < this._nMips; i ++ ) {
_quadMesh.material = this._separableBlurMaterials[ i ];
this._separableBlurMaterials[ i ].colorTexture.value = inputRenderTarget.texture;
this._separableBlurMaterials[ i ].direction.value = _BlurDirectionX;
renderer.setRenderTarget( this._renderTargetsHorizontal[ i ] );
renderer.clear();
_quadMesh.render( renderer );
this._separableBlurMaterials[ i ].colorTexture.value = this._renderTargetsHorizontal[ i ].texture;
this._separableBlurMaterials[ i ].direction.value = _BlurDirectionY;
renderer.setRenderTarget( this._renderTargetsVertical[ i ] );
renderer.clear();
_quadMesh.render( renderer );
inputRenderTarget = this._renderTargetsVertical[ i ];
}
// 3. Composite All the mips
renderer.setRenderTarget( this._renderTargetsHorizontal[ 0 ] );
renderer.clear();
_quadMesh.material = this._compositeMaterial;
_quadMesh.render( renderer );
// restore
renderer.setRenderTarget( currentRenderTarget );
renderer.setMRT( currentMRT );
renderer.setClearColor( _currentClearColor, currentClearAlpha );
}
setup( builder ) {
// luminosity high pass material
const luminosityHighPass = tslFn( () => {
const texel = this.inputNode;
const v = luminance( texel.rgb );
const alpha = smoothstep( this.threshold, this.threshold.add( this.smoothWidth ), v );
return mix( vec4( 0 ), texel, alpha );
} );
this._highPassFilterMaterial = this._highPassFilterMaterial || builder.createNodeMaterial();
this._highPassFilterMaterial.fragmentNode = luminosityHighPass().context( builder.getSharedContext() );
this._highPassFilterMaterial.needsUpdate = true;
// gaussian blur materials
const kernelSizeArray = [ 3, 5, 7, 9, 11 ];
for ( let i = 0; i < this._nMips; i ++ ) {
this._separableBlurMaterials.push( this._getSeperableBlurMaterial( builder, kernelSizeArray[ i ] ) );
}
// composite material
const bloomFactors = uniforms( [ 1.0, 0.8, 0.6, 0.4, 0.2 ] );
const bloomTintColors = uniforms( [ new Vector3( 1, 1, 1 ), new Vector3( 1, 1, 1 ), new Vector3( 1, 1, 1 ), new Vector3( 1, 1, 1 ), new Vector3( 1, 1, 1 ) ] );
const lerpBloomFactor = tslFn( ( [ factor, radius ] ) => {
const mirrorFactor = float( 1.2 ).sub( factor );
return mix( factor, mirrorFactor, radius );
} ).setLayout( {
name: 'lerpBloomFactor',
type: 'float',
inputs: [
{ name: 'factor', type: 'float' },
{ name: 'radius', type: 'float' },
]
} );
const compositePass = tslFn( () => {
const color0 = lerpBloomFactor( bloomFactors.element( 0 ), this.radius ).mul( vec4( bloomTintColors.element( 0 ), 1.0 ) ).mul( this._textureNodeBlur0 );
const color1 = lerpBloomFactor( bloomFactors.element( 1 ), this.radius ).mul( vec4( bloomTintColors.element( 1 ), 1.0 ) ).mul( this._textureNodeBlur1 );
const color2 = lerpBloomFactor( bloomFactors.element( 2 ), this.radius ).mul( vec4( bloomTintColors.element( 2 ), 1.0 ) ).mul( this._textureNodeBlur2 );
const color3 = lerpBloomFactor( bloomFactors.element( 3 ), this.radius ).mul( vec4( bloomTintColors.element( 3 ), 1.0 ) ).mul( this._textureNodeBlur3 );
const color4 = lerpBloomFactor( bloomFactors.element( 4 ), this.radius ).mul( vec4( bloomTintColors.element( 4 ), 1.0 ) ).mul( this._textureNodeBlur4 );
const sum = color0.add( color1 ).add( color2 ).add( color3 ).add( color4 );
return sum.mul( this.strength );
} );
this._compositeMaterial = this._compositeMaterial || builder.createNodeMaterial();
this._compositeMaterial.fragmentNode = compositePass().context( builder.getSharedContext() );
this._compositeMaterial.needsUpdate = true;
//
return this._textureOutput;
}
dispose() {
for ( let i = 0; i < this._renderTargetsHorizontal.length; i ++ ) {
this._renderTargetsHorizontal[ i ].dispose();
}
for ( let i = 0; i < this._renderTargetsVertical.length; i ++ ) {
this._renderTargetsVertical[ i ].dispose();
}
this._renderTargetBright.dispose();
}
_getSeperableBlurMaterial( builder, kernelRadius ) {
const coefficients = [];
for ( let i = 0; i < kernelRadius; i ++ ) {
coefficients.push( 0.39894 * Math.exp( - 0.5 * i * i / ( kernelRadius * kernelRadius ) ) / kernelRadius );
}
//
const colorTexture = texture();
const gaussianCoefficients = uniforms( coefficients );
const invSize = uniform( new Vector2() );
const direction = uniform( new Vector2( 0.5, 0.5 ) );
const uvNode = uv();
const sampleTexel = ( uv ) => colorTexture.uv( uv );
const seperableBlurPass = tslFn( () => {
const weightSum = gaussianCoefficients.element( 0 ).toVar();
const diffuseSum = sampleTexel( uvNode ).rgb.mul( weightSum ).toVar();
loop( { start: int( 1 ), end: int( kernelRadius ), type: 'int', condition: '<' }, ( { i } ) => {
const x = float( i );
const w = gaussianCoefficients.element( i );
const uvOffset = direction.mul( invSize ).mul( x );
const sample1 = sampleTexel( uvNode.add( uvOffset ) ).rgb;
const sample2 = sampleTexel( uvNode.sub( uvOffset ) ).rgb;
diffuseSum.addAssign( add( sample1, sample2 ).mul( w ) );
weightSum.addAssign( float( 2.0 ).mul( w ) );
} );
return vec4( diffuseSum.div( weightSum ), 1.0 );
} );
const seperableBlurMaterial = builder.createNodeMaterial();
seperableBlurMaterial.fragmentNode = seperableBlurPass().context( builder.getSharedContext() );
seperableBlurMaterial.needsUpdate = true;
// uniforms
seperableBlurMaterial.colorTexture = colorTexture;
seperableBlurMaterial.direction = direction;
seperableBlurMaterial.invSize = invSize;
return seperableBlurMaterial;
}
}
export const bloom = ( node, strength, radius, threshold ) => nodeObject( new BloomNode( nodeObject( node ), strength, radius, threshold ) );
addNodeElement( 'bloom', bloom );
export default BloomNode;

View File

@ -0,0 +1,80 @@
import TempNode from '../core/TempNode.js';
import { addNodeClass } from '../core/Node.js';
import { uv } from '../accessors/UVNode.js';
import { normalView } from '../accessors/NormalNode.js';
import { positionView } from '../accessors/PositionNode.js';
import { faceDirection } from './FrontFacingNode.js';
import { addNodeElement, tslFn, nodeProxy, float, vec2 } from '../shadernode/ShaderNode.js';
// Bump Mapping Unparametrized Surfaces on the GPU by Morten S. Mikkelsen
// https://mmikk.github.io/papers3d/mm_sfgrad_bump.pdf
const dHdxy_fwd = tslFn( ( { textureNode, bumpScale } ) => {
// It's used to preserve the same TextureNode instance
const sampleTexture = ( callback ) => textureNode.cache().context( { getUV: ( texNode ) => callback( texNode.uvNode || uv() ), forceUVContext: true } );
const Hll = float( sampleTexture( ( uvNode ) => uvNode ) );
return vec2(
float( sampleTexture( ( uvNode ) => uvNode.add( uvNode.dFdx() ) ) ).sub( Hll ),
float( sampleTexture( ( uvNode ) => uvNode.add( uvNode.dFdy() ) ) ).sub( Hll )
).mul( bumpScale );
} );
// Evaluate the derivative of the height w.r.t. screen-space using forward differencing (listing 2)
const perturbNormalArb = tslFn( ( inputs ) => {
const { surf_pos, surf_norm, dHdxy } = inputs;
// normalize is done to ensure that the bump map looks the same regardless of the texture's scale
const vSigmaX = surf_pos.dFdx().normalize();
const vSigmaY = surf_pos.dFdy().normalize();
const vN = surf_norm; // normalized
const R1 = vSigmaY.cross( vN );
const R2 = vN.cross( vSigmaX );
const fDet = vSigmaX.dot( R1 ).mul( faceDirection );
const vGrad = fDet.sign().mul( dHdxy.x.mul( R1 ).add( dHdxy.y.mul( R2 ) ) );
return fDet.abs().mul( surf_norm ).sub( vGrad ).normalize();
} );
class BumpMapNode extends TempNode {
constructor( textureNode, scaleNode = null ) {
super( 'vec3' );
this.textureNode = textureNode;
this.scaleNode = scaleNode;
}
setup() {
const bumpScale = this.scaleNode !== null ? this.scaleNode : 1;
const dHdxy = dHdxy_fwd( { textureNode: this.textureNode, bumpScale } );
return perturbNormalArb( {
surf_pos: positionView,
surf_norm: normalView,
dHdxy
} );
}
}
export default BumpMapNode;
export const bumpMap = nodeProxy( BumpMapNode );
addNodeElement( 'bumpMap', bumpMap );
addNodeClass( 'BumpMapNode', BumpMapNode );

View File

@ -0,0 +1,104 @@
import TempNode from '../core/TempNode.js';
import { dot, mix } from '../math/MathNode.js';
import { add } from '../math/OperatorNode.js';
import { addNodeClass } from '../core/Node.js';
import { addNodeElement, tslFn, nodeProxy, float, vec3 } from '../shadernode/ShaderNode.js';
import { ColorManagement } from '../../math/ColorManagement.js';
import { Vector3 } from '../../math/Vector3.js';
const saturationNode = tslFn( ( { color, adjustment } ) => {
return adjustment.mix( luminance( color.rgb ), color.rgb );
} );
const vibranceNode = tslFn( ( { color, adjustment } ) => {
const average = add( color.r, color.g, color.b ).div( 3.0 );
const mx = color.r.max( color.g.max( color.b ) );
const amt = mx.sub( average ).mul( adjustment ).mul( - 3.0 );
return mix( color.rgb, mx, amt );
} );
const hueNode = tslFn( ( { color, adjustment } ) => {
const k = vec3( 0.57735, 0.57735, 0.57735 );
const cosAngle = adjustment.cos();
return vec3( color.rgb.mul( cosAngle ).add( k.cross( color.rgb ).mul( adjustment.sin() ).add( k.mul( dot( k, color.rgb ).mul( cosAngle.oneMinus() ) ) ) ) );
} );
class ColorAdjustmentNode extends TempNode {
constructor( method, colorNode, adjustmentNode = float( 1 ) ) {
super( 'vec3' );
this.method = method;
this.colorNode = colorNode;
this.adjustmentNode = adjustmentNode;
}
setup() {
const { method, colorNode, adjustmentNode } = this;
const callParams = { color: colorNode, adjustment: adjustmentNode };
let outputNode = null;
if ( method === ColorAdjustmentNode.SATURATION ) {
outputNode = saturationNode( callParams );
} else if ( method === ColorAdjustmentNode.VIBRANCE ) {
outputNode = vibranceNode( callParams );
} else if ( method === ColorAdjustmentNode.HUE ) {
outputNode = hueNode( callParams );
} else {
console.error( `${ this.type }: Method "${ this.method }" not supported!` );
}
return outputNode;
}
}
ColorAdjustmentNode.SATURATION = 'saturation';
ColorAdjustmentNode.VIBRANCE = 'vibrance';
ColorAdjustmentNode.HUE = 'hue';
export default ColorAdjustmentNode;
export const saturation = nodeProxy( ColorAdjustmentNode, ColorAdjustmentNode.SATURATION );
export const vibrance = nodeProxy( ColorAdjustmentNode, ColorAdjustmentNode.VIBRANCE );
export const hue = nodeProxy( ColorAdjustmentNode, ColorAdjustmentNode.HUE );
const _luminanceCoefficients = /*#__PURE__*/ new Vector3();
export const luminance = (
color,
luminanceCoefficients = vec3( ... ColorManagement.getLuminanceCoefficients( _luminanceCoefficients ) )
) => dot( color, luminanceCoefficients );
export const threshold = ( color, threshold ) => mix( vec3( 0.0 ), color, luminance( color ).sub( threshold ).max( 0 ) );
addNodeElement( 'saturation', saturation );
addNodeElement( 'vibrance', vibrance );
addNodeElement( 'hue', hue );
addNodeElement( 'threshold', threshold );
addNodeClass( 'ColorAdjustmentNode', ColorAdjustmentNode );

View File

@ -0,0 +1,108 @@
import TempNode from '../core/TempNode.js';
import { mix } from '../math/MathNode.js';
import { addNodeClass } from '../core/Node.js';
import { addNodeElement, tslFn, nodeObject, nodeProxy, vec4 } from '../shadernode/ShaderNode.js';
import { LinearSRGBColorSpace, SRGBColorSpace } from '../../constants.js';
const sRGBToLinearShader = tslFn( ( inputs ) => {
const { value } = inputs;
const { rgb } = value;
const a = rgb.mul( 0.9478672986 ).add( 0.0521327014 ).pow( 2.4 );
const b = rgb.mul( 0.0773993808 );
const factor = rgb.lessThanEqual( 0.04045 );
const rgbResult = mix( a, b, factor );
return vec4( rgbResult, value.a );
} );
const LinearTosRGBShader = tslFn( ( inputs ) => {
const { value } = inputs;
const { rgb } = value;
const a = rgb.pow( 0.41666 ).mul( 1.055 ).sub( 0.055 );
const b = rgb.mul( 12.92 );
const factor = rgb.lessThanEqual( 0.0031308 );
const rgbResult = mix( a, b, factor );
return vec4( rgbResult, value.a );
} );
const getColorSpaceMethod = ( colorSpace ) => {
let method = null;
if ( colorSpace === LinearSRGBColorSpace ) {
method = 'Linear';
} else if ( colorSpace === SRGBColorSpace ) {
method = 'sRGB';
}
return method;
};
const getMethod = ( source, target ) => {
return getColorSpaceMethod( source ) + 'To' + getColorSpaceMethod( target );
};
class ColorSpaceNode extends TempNode {
constructor( method, node ) {
super( 'vec4' );
this.method = method;
this.node = node;
}
setup() {
const { method, node } = this;
if ( method === ColorSpaceNode.LINEAR_TO_LINEAR )
return node;
return Methods[ method ]( { value: node } );
}
}
ColorSpaceNode.LINEAR_TO_LINEAR = 'LinearToLinear';
ColorSpaceNode.LINEAR_TO_sRGB = 'LinearTosRGB';
ColorSpaceNode.sRGB_TO_LINEAR = 'sRGBToLinear';
const Methods = {
[ ColorSpaceNode.LINEAR_TO_sRGB ]: LinearTosRGBShader,
[ ColorSpaceNode.sRGB_TO_LINEAR ]: sRGBToLinearShader
};
export default ColorSpaceNode;
export const linearToColorSpace = ( node, colorSpace ) => nodeObject( new ColorSpaceNode( getMethod( LinearSRGBColorSpace, colorSpace ), nodeObject( node ) ) );
export const colorSpaceToLinear = ( node, colorSpace ) => nodeObject( new ColorSpaceNode( getMethod( colorSpace, LinearSRGBColorSpace ), nodeObject( node ) ) );
export const linearTosRGB = nodeProxy( ColorSpaceNode, ColorSpaceNode.LINEAR_TO_sRGB );
export const sRGBToLinear = nodeProxy( ColorSpaceNode, ColorSpaceNode.sRGB_TO_LINEAR );
addNodeElement( 'linearTosRGB', linearTosRGB );
addNodeElement( 'sRGBToLinear', sRGBToLinear );
addNodeElement( 'linearToColorSpace', linearToColorSpace );
addNodeElement( 'colorSpaceToLinear', colorSpaceToLinear );
addNodeClass( 'ColorSpaceNode', ColorSpaceNode );

View File

@ -0,0 +1,198 @@
import TempNode from '../core/TempNode.js';
import { uv } from '../accessors/UVNode.js';
import { addNodeElement, tslFn, nodeObject, float, int, vec2, vec3, vec4, mat2, If } from '../shadernode/ShaderNode.js';
import { NodeUpdateType } from '../core/constants.js';
import { uniform } from '../core/UniformNode.js';
import { uniforms } from '../accessors/UniformsNode.js';
import { abs, dot, sin, cos, PI, pow, max } from '../math/MathNode.js';
import { loop } from '../utils/LoopNode.js';
import { luminance } from './ColorAdjustmentNode.js';
import { textureSize } from '../accessors/TextureSizeNode.js';
import { Vector2 } from '../../math/Vector2.js';
import { Vector3 } from '../../math/Vector3.js';
class DenoiseNode extends TempNode {
constructor( textureNode, depthNode, normalNode, noiseNode, camera ) {
super();
this.textureNode = textureNode;
this.depthNode = depthNode;
this.normalNode = normalNode;
this.noiseNode = noiseNode;
this.cameraProjectionMatrixInverse = uniform( camera.projectionMatrixInverse );
this.lumaPhi = uniform( 5 );
this.depthPhi = uniform( 5 );
this.normalPhi = uniform( 5 );
this.radius = uniform( 5 );
this.index = uniform( 0 );
this._resolution = uniform( new Vector2() );
this._sampleVectors = uniforms( generatePdSamplePointInitializer( 16, 2, 1 ) );
this.updateBeforeType = NodeUpdateType.RENDER;
}
updateBefore() {
const map = this.textureNode.value;
this._resolution.value.set( map.image.width, map.image.height );
}
setup() {
const uvNode = uv();
const sampleTexture = ( uv ) => this.textureNode.uv( uv );
const sampleDepth = ( uv ) => this.depthNode.uv( uv ).x;
const sampleNormal = ( uv ) => this.normalNode.uv( uv );
const sampleNoise = ( uv ) => this.noiseNode.uv( uv );
const getViewPosition = tslFn( ( [ screenPosition, depth ] ) => {
screenPosition = vec2( screenPosition.x, screenPosition.y.oneMinus() ).mul( 2.0 ).sub( 1.0 );
const clipSpacePosition = vec4( vec3( screenPosition, depth ), 1.0 );
const viewSpacePosition = vec4( this.cameraProjectionMatrixInverse.mul( clipSpacePosition ) );
return viewSpacePosition.xyz.div( viewSpacePosition.w );
} );
const denoiseSample = tslFn( ( [ center, viewNormal, viewPosition, sampleUv ] ) => {
const texel = sampleTexture( sampleUv );
const depth = sampleDepth( sampleUv );
const normal = sampleNormal( sampleUv ).rgb.normalize();
const neighborColor = texel.rgb;
const viewPos = getViewPosition( sampleUv, depth );
const normalDiff = dot( viewNormal, normal ).toVar();
const normalSimilarity = pow( max( normalDiff, 0 ), this.normalPhi ).toVar();
const lumaDiff = abs( luminance( neighborColor ).sub( luminance( center ) ) ).toVar();
const lumaSimilarity = max( float( 1.0 ).sub( lumaDiff.div( this.lumaPhi ) ), 0 ).toVar();
const depthDiff = abs( dot( viewPosition.sub( viewPos ), viewNormal ) ).toVar();
const depthSimilarity = max( float( 1.0 ).sub( depthDiff.div( this.depthPhi ) ), 0 );
const w = lumaSimilarity.mul( depthSimilarity ).mul( normalSimilarity );
return vec4( neighborColor.mul( w ), w );
} );
const denoise = tslFn( ( [ uvNode ] ) => {
const depth = sampleDepth( uvNode );
const viewNormal = sampleNormal( uvNode ).rgb.normalize();
const texel = sampleTexture( uvNode );
If( depth.greaterThanEqual( 1.0 ).or( dot( viewNormal, viewNormal ).equal( 0.0 ) ), () => {
return texel;
} );
const center = vec3( texel.rgb );
const viewPosition = getViewPosition( uvNode, depth );
const noiseResolution = textureSize( this.noiseNode, 0 );
let noiseUv = vec2( uvNode.x, uvNode.y.oneMinus() );
noiseUv = noiseUv.mul( this._resolution.div( noiseResolution ) );
const noiseTexel = sampleNoise( noiseUv );
const x = sin( noiseTexel.element( this.index.mod( 4 ).mul( 2 ).mul( PI ) ) );
const y = cos( noiseTexel.element( this.index.mod( 4 ).mul( 2 ).mul( PI ) ) );
const noiseVec = vec2( x, y );
const rotationMatrix = mat2( noiseVec.x, noiseVec.y.negate(), noiseVec.x, noiseVec.y );
const totalWeight = float( 1.0 ).toVar();
const denoised = vec3( texel.rgb ).toVar();
loop( { start: int( 0 ), end: int( 16 ), type: 'int', condition: '<' }, ( { i } ) => {
const sampleDir = this._sampleVectors.element( i ).toVar();
const offset = rotationMatrix.mul( sampleDir.xy.mul( float( 1.0 ).add( sampleDir.z.mul( this.radius.sub( 1 ) ) ) ) ).div( this._resolution ).toVar();
const sampleUv = uvNode.add( offset ).toVar();
const result = denoiseSample( center, viewNormal, viewPosition, sampleUv );
denoised.addAssign( result.xyz );
totalWeight.addAssign( result.w );
} );
If( totalWeight.greaterThan( float( 0 ) ), () => {
denoised.divAssign( totalWeight );
} );
return vec4( denoised, texel.a );
} ).setLayout( {
name: 'denoise',
type: 'vec4',
inputs: [
{ name: 'uv', type: 'vec2' }
]
} );
const output = tslFn( () => {
return denoise( uvNode );
} );
const outputNode = output();
return outputNode;
}
}
function generatePdSamplePointInitializer( samples, rings, radiusExponent ) {
const poissonDisk = generateDenoiseSamples( samples, rings, radiusExponent );
const array = [];
for ( let i = 0; i < samples; i ++ ) {
const sample = poissonDisk[ i ];
array.push( sample );
}
return array;
}
function generateDenoiseSamples( numSamples, numRings, radiusExponent ) {
const samples = [];
for ( let i = 0; i < numSamples; i ++ ) {
const angle = 2 * Math.PI * numRings * i / numSamples;
const radius = Math.pow( i / ( numSamples - 1 ), radiusExponent );
samples.push( new Vector3( Math.cos( angle ), Math.sin( angle ), radius ) );
}
return samples;
}
export const denoise = ( node, depthNode, normalNode, noiseNode, camera ) => nodeObject( new DenoiseNode( nodeObject( node ).toTexture(), nodeObject( depthNode ), nodeObject( normalNode ), nodeObject( noiseNode ), camera ) );
addNodeElement( 'denoise', denoise );
export default DenoiseNode;

View File

@ -0,0 +1,119 @@
import TempNode from '../core/TempNode.js';
import { uv } from '../accessors/UVNode.js';
import { addNodeElement, tslFn, nodeObject, vec2, vec4 } from '../shadernode/ShaderNode.js';
import { NodeUpdateType } from '../core/constants.js';
import { uniform } from '../core/UniformNode.js';
import { clamp } from '../math/MathNode.js';
class DepthOfFieldNode extends TempNode {
constructor( textureNode, viewZNode, focusNode, apertureNode, maxblurNode ) {
super();
this.textureNode = textureNode;
this.viewZNode = viewZNode;
this.focusNode = focusNode;
this.apertureNode = apertureNode;
this.maxblurNode = maxblurNode;
this._aspect = uniform( 0 );
this.updateBeforeType = NodeUpdateType.RENDER;
}
updateBefore() {
const map = this.textureNode.value;
this._aspect.value = map.image.width / map.image.height;
}
setup() {
const textureNode = this.textureNode;
const uvNode = textureNode.uvNode || uv();
const sampleTexture = ( uv ) => textureNode.uv( uv );
const dof = tslFn( () => {
const aspectcorrect = vec2( 1.0, this._aspect );
const factor = this.focusNode.add( this.viewZNode );
const dofblur = vec2( clamp( factor.mul( this.apertureNode ), this.maxblurNode.negate(), this.maxblurNode ) );
const dofblur9 = dofblur.mul( 0.9 );
const dofblur7 = dofblur.mul( 0.7 );
const dofblur4 = dofblur.mul( 0.4 );
let col = vec4( 0.0 );
col = col.add( sampleTexture( uvNode ) );
col = col.add( sampleTexture( uvNode.add( vec2( 0.0, 0.4 ).mul( aspectcorrect ).mul( dofblur ) ) ) );
col = col.add( sampleTexture( uvNode.add( vec2( 0.15, 0.37 ).mul( aspectcorrect ).mul( dofblur ) ) ) );
col = col.add( sampleTexture( uvNode.add( vec2( 0.29, 0.29 ).mul( aspectcorrect ).mul( dofblur ) ) ) );
col = col.add( sampleTexture( uvNode.add( vec2( - 0.37, 0.15 ).mul( aspectcorrect ).mul( dofblur ) ) ) );
col = col.add( sampleTexture( uvNode.add( vec2( 0.40, 0.0 ).mul( aspectcorrect ).mul( dofblur ) ) ) );
col = col.add( sampleTexture( uvNode.add( vec2( 0.37, - 0.15 ).mul( aspectcorrect ).mul( dofblur ) ) ) );
col = col.add( sampleTexture( uvNode.add( vec2( 0.29, - 0.29 ).mul( aspectcorrect ).mul( dofblur ) ) ) );
col = col.add( sampleTexture( uvNode.add( vec2( - 0.15, - 0.37 ).mul( aspectcorrect ).mul( dofblur ) ) ) );
col = col.add( sampleTexture( uvNode.add( vec2( 0.0, - 0.4 ).mul( aspectcorrect ).mul( dofblur ) ) ) );
col = col.add( sampleTexture( uvNode.add( vec2( - 0.15, 0.37 ).mul( aspectcorrect ).mul( dofblur ) ) ) );
col = col.add( sampleTexture( uvNode.add( vec2( - 0.29, 0.29 ).mul( aspectcorrect ).mul( dofblur ) ) ) );
col = col.add( sampleTexture( uvNode.add( vec2( 0.37, 0.15 ).mul( aspectcorrect ).mul( dofblur ) ) ) );
col = col.add( sampleTexture( uvNode.add( vec2( - 0.4, 0.0 ).mul( aspectcorrect ).mul( dofblur ) ) ) );
col = col.add( sampleTexture( uvNode.add( vec2( - 0.37, - 0.15 ).mul( aspectcorrect ).mul( dofblur ) ) ) );
col = col.add( sampleTexture( uvNode.add( vec2( - 0.29, - 0.29 ).mul( aspectcorrect ).mul( dofblur ) ) ) );
col = col.add( sampleTexture( uvNode.add( vec2( 0.15, - 0.37 ).mul( aspectcorrect ).mul( dofblur ) ) ) );
col = col.add( sampleTexture( uvNode.add( vec2( 0.15, 0.37 ).mul( aspectcorrect ).mul( dofblur9 ) ) ) );
col = col.add( sampleTexture( uvNode.add( vec2( - 0.37, 0.15 ).mul( aspectcorrect ).mul( dofblur9 ) ) ) );
col = col.add( sampleTexture( uvNode.add( vec2( 0.37, - 0.15 ).mul( aspectcorrect ).mul( dofblur9 ) ) ) );
col = col.add( sampleTexture( uvNode.add( vec2( - 0.15, - 0.37 ).mul( aspectcorrect ).mul( dofblur9 ) ) ) );
col = col.add( sampleTexture( uvNode.add( vec2( - 0.15, 0.37 ).mul( aspectcorrect ).mul( dofblur9 ) ) ) );
col = col.add( sampleTexture( uvNode.add( vec2( 0.37, 0.15 ).mul( aspectcorrect ).mul( dofblur9 ) ) ) );
col = col.add( sampleTexture( uvNode.add( vec2( - 0.37, - 0.15 ).mul( aspectcorrect ).mul( dofblur9 ) ) ) );
col = col.add( sampleTexture( uvNode.add( vec2( 0.15, - 0.37 ).mul( aspectcorrect ).mul( dofblur9 ) ) ) );
col = col.add( sampleTexture( uvNode.add( vec2( 0.29, 0.29 ).mul( aspectcorrect ).mul( dofblur7 ) ) ) );
col = col.add( sampleTexture( uvNode.add( vec2( 0.40, 0.0 ).mul( aspectcorrect ).mul( dofblur7 ) ) ) );
col = col.add( sampleTexture( uvNode.add( vec2( 0.29, - 0.29 ).mul( aspectcorrect ).mul( dofblur7 ) ) ) );
col = col.add( sampleTexture( uvNode.add( vec2( 0.0, - 0.4 ).mul( aspectcorrect ).mul( dofblur7 ) ) ) );
col = col.add( sampleTexture( uvNode.add( vec2( - 0.29, 0.29 ).mul( aspectcorrect ).mul( dofblur7 ) ) ) );
col = col.add( sampleTexture( uvNode.add( vec2( - 0.4, 0.0 ).mul( aspectcorrect ).mul( dofblur7 ) ) ) );
col = col.add( sampleTexture( uvNode.add( vec2( - 0.29, - 0.29 ).mul( aspectcorrect ).mul( dofblur7 ) ) ) );
col = col.add( sampleTexture( uvNode.add( vec2( 0.0, 0.4 ).mul( aspectcorrect ).mul( dofblur7 ) ) ) );
col = col.add( sampleTexture( uvNode.add( vec2( 0.29, 0.29 ).mul( aspectcorrect ).mul( dofblur4 ) ) ) );
col = col.add( sampleTexture( uvNode.add( vec2( 0.4, 0.0 ).mul( aspectcorrect ).mul( dofblur4 ) ) ) );
col = col.add( sampleTexture( uvNode.add( vec2( 0.29, - 0.29 ).mul( aspectcorrect ).mul( dofblur4 ) ) ) );
col = col.add( sampleTexture( uvNode.add( vec2( 0.0, - 0.4 ).mul( aspectcorrect ).mul( dofblur4 ) ) ) );
col = col.add( sampleTexture( uvNode.add( vec2( - 0.29, 0.29 ).mul( aspectcorrect ).mul( dofblur4 ) ) ) );
col = col.add( sampleTexture( uvNode.add( vec2( - 0.4, 0.0 ).mul( aspectcorrect ).mul( dofblur4 ) ) ) );
col = col.add( sampleTexture( uvNode.add( vec2( - 0.29, - 0.29 ).mul( aspectcorrect ).mul( dofblur4 ) ) ) );
col = col.add( sampleTexture( uvNode.add( vec2( 0.0, 0.4 ).mul( aspectcorrect ).mul( dofblur4 ) ) ) );
col = col.div( 41 );
col.a = 1;
return vec4( col );
} );
const outputNode = dof();
return outputNode;
}
}
export const dof = ( node, viewZNode, focus = 1, aperture = 0.025, maxblur = 1 ) => nodeObject( new DepthOfFieldNode( nodeObject( node ).toTexture(), nodeObject( viewZNode ), nodeObject( focus ), nodeObject( aperture ), nodeObject( maxblur ) ) );
addNodeElement( 'dof', dof );
export default DepthOfFieldNode;

View File

@ -0,0 +1,75 @@
import TempNode from '../core/TempNode.js';
import { nodeObject, addNodeElement, tslFn, vec2, vec3, vec4 } from '../shadernode/ShaderNode.js';
import { uniform } from '../core/UniformNode.js';
import { NodeUpdateType } from '../core/constants.js';
import { uv } from '../accessors/UVNode.js';
import { sin, cos } from '../math/MathNode.js';
import { add } from '../math/OperatorNode.js';
import { Vector2 } from '../../math/Vector2.js';
class DotScreenNode extends TempNode {
constructor( inputNode, center = new Vector2( 0.5, 0.5 ), angle = 1.57, scale = 1 ) {
super( 'vec4' );
this.inputNode = inputNode;
this.center = uniform( center );
this.angle = uniform( angle );
this.scale = uniform( scale );
this._size = uniform( new Vector2() );
this.updateBeforeType = NodeUpdateType.RENDER;
}
updateBefore( frame ) {
const { renderer } = frame;
renderer.getDrawingBufferSize( this._size.value );
}
setup() {
const inputNode = this.inputNode;
const pattern = tslFn( () => {
const s = sin( this.angle );
const c = cos( this.angle );
const tex = uv().mul( this._size ).sub( this.center );
const point = vec2( c.mul( tex.x ).sub( s.mul( tex.y ) ), s.mul( tex.x ).add( c.mul( tex.y ) ) ).mul( this.scale );
return sin( point.x ).mul( sin( point.y ) ).mul( 4 );
} );
const dotScreen = tslFn( () => {
const color = inputNode;
const average = add( color.r, color.g, color.b ).div( 3 );
return vec4( vec3( average.mul( 10 ).sub( 5 ).add( pattern() ) ), color.a );
} );
const outputNode = dotScreen();
return outputNode;
}
}
export const dotScreen = ( node, center, angle, scale ) => nodeObject( new DotScreenNode( nodeObject( node ), center, angle, scale ) );
addNodeElement( 'dotScreen', dotScreen );
export default DotScreenNode;

View File

@ -0,0 +1,327 @@
import TempNode from '../core/TempNode.js';
import { uv } from '../accessors/UVNode.js';
import { addNodeElement, tslFn, nodeObject, float, vec2, vec4, int, If } from '../shadernode/ShaderNode.js';
import { NodeUpdateType } from '../core/constants.js';
import { uniform } from '../core/UniformNode.js';
import { abs, max, min, mix, pow } from '../math/MathNode.js';
import { sub } from '../math/OperatorNode.js';
import { loop, Break } from '../utils/LoopNode.js';
import { Vector2 } from '../../math/Vector2.js';
class FXAANode extends TempNode {
constructor( textureNode ) {
super();
this.textureNode = textureNode;
this.updateBeforeType = NodeUpdateType.RENDER;
this._invSize = uniform( new Vector2() );
}
updateBefore() {
const map = this.textureNode.value;
this._invSize.value.set( 1 / map.image.width, 1 / map.image.height );
}
setup() {
const textureNode = this.textureNode.bias( - 100 );
const uvNode = textureNode.uvNode || uv();
// FXAA 3.11 implementation by NVIDIA, ported to WebGL by Agost Biro (biro@archilogic.com)
//----------------------------------------------------------------------------------
// File: es3-kepler\FXAA\assets\shaders/FXAA_DefaultES.frag
// SDK Version: v3.00
// Email: gameworks@nvidia.com
// Site: http://developer.nvidia.com/
//
// Copyright (c) 2014-2015, NVIDIA CORPORATION. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of NVIDIA CORPORATION nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
//----------------------------------------------------------------------------------
const FxaaTexTop = ( p ) => textureNode.uv( p );
const FxaaTexOff = ( p, o, r ) => textureNode.uv( p.add( o.mul( r ) ) );
const NUM_SAMPLES = int( 5 );
const contrast = tslFn( ( [ a_immutable, b_immutable ] ) => {
// assumes colors have premultipliedAlpha, so that the calculated color contrast is scaled by alpha
const b = vec4( b_immutable ).toVar();
const a = vec4( a_immutable ).toVar();
const diff = vec4( abs( a.sub( b ) ) ).toVar();
return max( max( max( diff.r, diff.g ), diff.b ), diff.a );
} );
// FXAA3 QUALITY - PC
const FxaaPixelShader = tslFn( ( [ uv, fxaaQualityRcpFrame, fxaaQualityEdgeThreshold, fxaaQualityinvEdgeThreshold ] ) => {
const rgbaM = FxaaTexTop( uv ).toVar();
const rgbaS = FxaaTexOff( uv, vec2( 0.0, - 1.0 ), fxaaQualityRcpFrame.xy ).toVar();
const rgbaE = FxaaTexOff( uv, vec2( 1.0, 0.0 ), fxaaQualityRcpFrame.xy ).toVar();
const rgbaN = FxaaTexOff( uv, vec2( 0.0, 1.0 ), fxaaQualityRcpFrame.xy ).toVar();
const rgbaW = FxaaTexOff( uv, vec2( - 1.0, 0.0 ), fxaaQualityRcpFrame.xy ).toVar();
// . S .
// W M E
// . N .
const contrastN = contrast( rgbaM, rgbaN ).toVar();
const contrastS = contrast( rgbaM, rgbaS ).toVar();
const contrastE = contrast( rgbaM, rgbaE ).toVar();
const contrastW = contrast( rgbaM, rgbaW ).toVar();
const maxValue = max( contrastN, max( contrastS, max( contrastE, contrastW ) ) ).toVar();
// . 0 .
// 0 0 0
// . 0 .
If( maxValue.lessThan( fxaaQualityEdgeThreshold ), () => {
return rgbaM; // assuming define FXAA_DISCARD is always 0
} );
//
const relativeVContrast = sub( contrastN.add( contrastS ), ( contrastE.add( contrastW ) ) ).toVar();
relativeVContrast.mulAssign( fxaaQualityinvEdgeThreshold );
// 45 deg edge detection and corners of objects, aka V/H contrast is too similar
If( abs( relativeVContrast ).lessThan( 0.3 ), () => {
// locate the edge
const x = contrastE.greaterThan( contrastW ).cond( 1, - 1 ).toVar();
const y = contrastS.greaterThan( contrastN ).cond( 1, - 1 ).toVar();
const dirToEdge = vec2( x, y ).toVar();
// . 2 . . 1 .
// 1 0 2 ~= 0 0 1
// . 1 . . 0 .
// tap 2 pixels and see which ones are "outside" the edge, to
// determine if the edge is vertical or horizontal
const rgbaAlongH = FxaaTexOff( uv, vec2( dirToEdge.x, dirToEdge.y ), fxaaQualityRcpFrame.xy );
const matchAlongH = contrast( rgbaM, rgbaAlongH ).toVar();
// . 1 .
// 0 0 1
// . 0 H
const rgbaAlongV = FxaaTexOff( uv, vec2( dirToEdge.x.negate(), dirToEdge.y.negate() ), fxaaQualityRcpFrame.xy );
const matchAlongV = contrast( rgbaM, rgbaAlongV ).toVar();
// V 1 .
// 0 0 1
// . 0 .
relativeVContrast.assign( matchAlongV.sub( matchAlongH ) );
relativeVContrast.mulAssign( fxaaQualityinvEdgeThreshold );
If( abs( relativeVContrast ).lessThan( 0.3 ), () => { // 45 deg edge
// 1 1 .
// 0 0 1
// . 0 1
// do a simple blur
const sum = rgbaN.add( rgbaS ).add( rgbaE ).add( rgbaW );
return mix( rgbaM, sum.mul( 0.25 ), 0.4 );
} );
} );
const offNP = vec2().toVar();
If( relativeVContrast.lessThanEqual( 0 ), () => {
rgbaN.assign( rgbaW );
rgbaS.assign( rgbaE );
// . 0 . 1
// 1 0 1 -> 0
// . 0 . 1
offNP.x.assign( 0 );
offNP.y.assign( fxaaQualityRcpFrame.y );
} ).else( () => {
offNP.x.assign( fxaaQualityRcpFrame.x );
offNP.y.assign( 0 );
} );
const mn = contrast( rgbaM, rgbaN ).toVar();
const ms = contrast( rgbaM, rgbaS ).toVar();
If( mn.lessThanEqual( ms ), () => {
rgbaN.assign( rgbaS );
} );
const doneN = int( 0 ).toVar();
const doneP = int( 0 ).toVar();
const nDist = float( 0 ).toVar();
const pDist = float( 0 ).toVar();
const posN = vec2( uv ).toVar();
const posP = vec2( uv ).toVar();
const iterationsUsedN = int( 0 ).toVar();
const iterationsUsedP = int( 0 ).toVar();
loop( NUM_SAMPLES, ( { i } ) => {
const increment = i.add( 1 ).toVar();
If( doneN.equal( 0 ), () => {
nDist.addAssign( increment );
posN.assign( uv.add( offNP.mul( nDist ) ) );
const rgbaEndN = FxaaTexTop( posN.xy );
const nm = contrast( rgbaEndN, rgbaM ).toVar();
const nn = contrast( rgbaEndN, rgbaN ).toVar();
If( nm.greaterThan( nn ), () => {
doneN.assign( 1 );
} );
iterationsUsedN.assign( i );
} );
If( doneP.equal( 0 ), () => {
pDist.addAssign( increment );
posP.assign( uv.sub( offNP.mul( pDist ) ) );
const rgbaEndP = FxaaTexTop( posP.xy );
const pm = contrast( rgbaEndP, rgbaM ).toVar();
const pn = contrast( rgbaEndP, rgbaN ).toVar();
If( pm.greaterThan( pn ), () => {
doneP.assign( 1 );
} );
iterationsUsedP.assign( i );
} );
If( doneN.equal( 1 ).or( doneP.equal( 1 ) ), () => {
Break();
} );
} );
If( doneN.equal( 0 ).and( doneP.equal( 0 ) ), () => {
return rgbaM; // failed to find end of edge
} );
const distN = float( 1 ).toVar();
const distP = float( 1 ).toVar();
If( doneN.equal( 1 ), () => {
distN.assign( float( iterationsUsedN ).div( float( NUM_SAMPLES.sub( 1 ) ) ) );
} );
If( doneP.equal( 1 ), () => {
distP.assign( float( iterationsUsedP ).div( float( NUM_SAMPLES.sub( 1 ) ) ) );
} );
const dist = min( distN, distP );
// hacky way of reduces blurriness of mostly diagonal edges
// but reduces AA quality
dist.assign( pow( dist, 0.5 ) );
dist.assign( float( 1 ).sub( dist ) );
return mix( rgbaM, rgbaN, dist.mul( 0.5 ) );
} ).setLayout( {
name: 'FxaaPixelShader',
type: 'vec4',
inputs: [
{ name: 'uv', type: 'vec2' },
{ name: 'fxaaQualityRcpFrame', type: 'vec2' },
{ name: 'fxaaQualityEdgeThreshold', type: 'float' },
{ name: 'fxaaQualityinvEdgeThreshold', type: 'float' },
]
} );
const fxaa = tslFn( () => {
const edgeDetectionQuality = float( 0.2 );
const invEdgeDetectionQuality = float( 1 ).div( edgeDetectionQuality );
return FxaaPixelShader( uvNode, this._invSize, edgeDetectionQuality, invEdgeDetectionQuality );
} );
const outputNode = fxaa();
return outputNode;
}
}
export const fxaa = ( node ) => nodeObject( new FXAANode( nodeObject( node ).toTexture() ) );
addNodeElement( 'fxaa', fxaa );
export default FXAANode;

View File

@ -0,0 +1,52 @@
import TempNode from '../core/TempNode.js';
import { uv } from '../accessors/UVNode.js';
import { addNodeElement, tslFn, nodeProxy, vec4 } from '../shadernode/ShaderNode.js';
import { mix, fract, clamp, rand } from '../math/MathNode.js';
import { timerLocal } from '../utils/TimerNode.js';
class FilmNode extends TempNode {
constructor( inputNode, intensityNode = null, uvNode = null ) {
super();
this.inputNode = inputNode;
this.intensityNode = intensityNode;
this.uvNode = uvNode;
}
setup() {
const uvNode = this.uvNode || uv();
const film = tslFn( () => {
const base = this.inputNode.rgb;
const noise = rand( fract( uvNode.add( timerLocal() ) ) );
let color = base.add( base.mul( clamp( noise.add( 0.1 ), 0, 1 ) ) );
if ( this.intensityNode !== null ) {
color = mix( base, color, this.intensityNode );
}
return vec4( color, this.inputNode.a );
} );
const outputNode = film();
return outputNode;
}
}
export const film = nodeProxy( FilmNode );
addNodeElement( 'film', film );
export default FilmNode;

View File

@ -0,0 +1,41 @@
import Node, { addNodeClass } from '../core/Node.js';
import { nodeImmutable, float } from '../shadernode/ShaderNode.js';
import { BackSide, WebGLCoordinateSystem } from '../../constants.js';
class FrontFacingNode extends Node {
constructor() {
super( 'bool' );
this.isFrontFacingNode = true;
}
generate( builder ) {
const { renderer, material } = builder;
if ( renderer.coordinateSystem === WebGLCoordinateSystem ) {
if ( material.side === BackSide ) {
return 'false';
}
}
return builder.getFrontFacing();
}
}
export default FrontFacingNode;
export const frontFacing = nodeImmutable( FrontFacingNode );
export const faceDirection = float( frontFacing ).mul( 2.0 ).sub( 1.0 );
addNodeClass( 'FrontFacingNode', FrontFacingNode );

View File

@ -0,0 +1,324 @@
import TempNode from '../core/TempNode.js';
import { texture } from '../accessors/TextureNode.js';
import { textureSize } from '../accessors/TextureSizeNode.js';
import { uv } from '../accessors/UVNode.js';
import { addNodeElement, nodeObject, tslFn, mat3, vec2, vec3, vec4, float, int, If } from '../shadernode/ShaderNode.js';
import { NodeUpdateType } from '../core/constants.js';
import { uniform } from '../core/UniformNode.js';
import { DataTexture } from '../../textures/DataTexture.js';
import { Vector2 } from '../../math/Vector2.js';
import { Vector3 } from '../../math/Vector3.js';
import { PI, cos, sin, pow, clamp, abs, max, mix, sqrt, acos, dot, normalize, cross } from '../math/MathNode.js';
import { div, mul, add, sub } from '../math/OperatorNode.js';
import { loop } from '../utils/LoopNode.js';
import { passTexture } from './PassNode.js';
import { RepeatWrapping } from '../../constants.js';
import QuadMesh from '../../renderers/common/QuadMesh.js';
import { RenderTarget } from '../../core/RenderTarget.js';
import { Color } from '../../math/Color.js';
const _quadMesh = /*@__PURE__*/ new QuadMesh();
const _currentClearColor = /*@__PURE__*/ new Color();
const _size = /*@__PURE__*/ new Vector2();
class GTAONode extends TempNode {
constructor( depthNode, normalNode, camera ) {
super();
this.depthNode = depthNode;
this.normalNode = normalNode;
this.radius = uniform( 0.25 );
this.resolution = uniform( new Vector2() );
this.thickness = uniform( 1 );
this.distanceExponent = uniform( 1 );
this.distanceFallOff = uniform( 1 );
this.scale = uniform( 1 );
this.noiseNode = texture( generateMagicSquareNoise() );
this.cameraProjectionMatrix = uniform( camera.projectionMatrix );
this.cameraProjectionMatrixInverse = uniform( camera.projectionMatrixInverse );
this.SAMPLES = uniform( 16 );
this._aoRenderTarget = new RenderTarget();
this._aoRenderTarget.texture.name = 'GTAONode.AO';
this._material = null;
this._textureNode = passTexture( this, this._aoRenderTarget.texture );
this.updateBeforeType = NodeUpdateType.FRAME;
}
getTextureNode() {
return this._textureNode;
}
setSize( width, height ) {
this.resolution.value.set( width, height );
this._aoRenderTarget.setSize( width, height );
}
updateBefore( frame ) {
const { renderer } = frame;
const size = renderer.getDrawingBufferSize( _size );
const currentRenderTarget = renderer.getRenderTarget();
const currentMRT = renderer.getMRT();
renderer.getClearColor( _currentClearColor );
const currentClearAlpha = renderer.getClearAlpha();
_quadMesh.material = this._material;
this.setSize( size.width, size.height );
// clear
renderer.setMRT( null );
renderer.setClearColor( 0xffffff, 1 );
// ao
renderer.setRenderTarget( this._aoRenderTarget );
_quadMesh.render( renderer );
// restore
renderer.setRenderTarget( currentRenderTarget );
renderer.setMRT( currentMRT );
renderer.setClearColor( _currentClearColor, currentClearAlpha );
}
setup( builder ) {
const uvNode = uv();
const sampleDepth = ( uv ) => this.depthNode.uv( uv ).x;
const sampleNoise = ( uv ) => this.noiseNode.uv( uv );
const getSceneUvAndDepth = tslFn( ( [ sampleViewPos ] )=> {
const sampleClipPos = this.cameraProjectionMatrix.mul( vec4( sampleViewPos, 1.0 ) );
let sampleUv = sampleClipPos.xy.div( sampleClipPos.w ).mul( 0.5 ).add( 0.5 ).toVar();
sampleUv = vec2( sampleUv.x, sampleUv.y.oneMinus() );
const sampleSceneDepth = sampleDepth( sampleUv );
return vec3( sampleUv, sampleSceneDepth );
} );
const getViewPosition = tslFn( ( [ screenPosition, depth ] ) => {
screenPosition = vec2( screenPosition.x, screenPosition.y.oneMinus() ).mul( 2.0 ).sub( 1.0 );
const clipSpacePosition = vec4( vec3( screenPosition, depth ), 1.0 );
const viewSpacePosition = vec4( this.cameraProjectionMatrixInverse.mul( clipSpacePosition ) );
return viewSpacePosition.xyz.div( viewSpacePosition.w );
} );
const ao = tslFn( () => {
const depth = sampleDepth( uvNode );
depth.greaterThanEqual( 1.0 ).discard();
const viewPosition = getViewPosition( uvNode, depth );
const viewNormal = this.normalNode.rgb.normalize();
const radiusToUse = this.radius;
const noiseResolution = textureSize( this.noiseNode, 0 );
let noiseUv = vec2( uvNode.x, uvNode.y.oneMinus() );
noiseUv = noiseUv.mul( this.resolution.div( noiseResolution ) );
const noiseTexel = sampleNoise( noiseUv );
const randomVec = noiseTexel.xyz.mul( 2.0 ).sub( 1.0 );
const tangent = vec3( randomVec.xy, 0.0 ).normalize();
const bitangent = vec3( tangent.y.mul( - 1.0 ), tangent.x, 0.0 );
const kernelMatrix = mat3( tangent, bitangent, vec3( 0.0, 0.0, 1.0 ) );
const DIRECTIONS = this.SAMPLES.lessThan( 30 ).cond( 3, 5 );
const STEPS = add( this.SAMPLES, DIRECTIONS.sub( 1 ) ).div( DIRECTIONS );
const ao = float( 0 ).toVar();
loop( { start: int( 0 ), end: DIRECTIONS, type: 'int', condition: '<' }, ( { i } ) => {
const angle = float( i ).div( float( DIRECTIONS ) ).mul( PI );
const sampleDir = vec4( cos( angle ), sin( angle ), 0., add( 0.5, mul( 0.5, noiseTexel.w ) ) );
sampleDir.xyz = normalize( kernelMatrix.mul( sampleDir.xyz ) );
const viewDir = normalize( viewPosition.xyz.negate() );
const sliceBitangent = normalize( cross( sampleDir.xyz, viewDir ) );
const sliceTangent = cross( sliceBitangent, viewDir );
const normalInSlice = normalize( viewNormal.sub( sliceBitangent.mul( dot( viewNormal, sliceBitangent ) ) ) );
const tangentToNormalInSlice = cross( normalInSlice, sliceBitangent );
const cosHorizons = vec2( dot( viewDir, tangentToNormalInSlice ), dot( viewDir, tangentToNormalInSlice.negate() ) ).toVar();
loop( { end: STEPS, type: 'int', name: 'j', condition: '<' }, ( { j } ) => {
const sampleViewOffset = sampleDir.xyz.mul( radiusToUse ).mul( sampleDir.w ).mul( pow( div( float( j ).add( 1.0 ), float( STEPS ) ), this.distanceExponent ) );
// x
const sampleSceneUvDepthX = getSceneUvAndDepth( viewPosition.add( sampleViewOffset ) );
const sampleSceneViewPositionX = getViewPosition( sampleSceneUvDepthX.xy, sampleSceneUvDepthX.z );
const viewDeltaX = sampleSceneViewPositionX.sub( viewPosition );
If( abs( viewDeltaX.z ).lessThan( this.thickness ), () => {
const sampleCosHorizon = dot( viewDir, normalize( viewDeltaX ) );
cosHorizons.x.addAssign( max( 0, mul( sampleCosHorizon.sub( cosHorizons.x ), mix( 1.0, float( 2.0 ).div( float( j ).add( 2 ) ), this.distanceFallOff ) ) ) );
} );
// y
const sampleSceneUvDepthY = getSceneUvAndDepth( viewPosition.sub( sampleViewOffset ) );
const sampleSceneViewPositionY = getViewPosition( sampleSceneUvDepthY.xy, sampleSceneUvDepthY.z );
const viewDeltaY = sampleSceneViewPositionY.sub( viewPosition );
If( abs( viewDeltaY.z ).lessThan( this.thickness ), () => {
const sampleCosHorizon = dot( viewDir, normalize( viewDeltaY ) );
cosHorizons.y.addAssign( max( 0, mul( sampleCosHorizon.sub( cosHorizons.y ), mix( 1.0, float( 2.0 ).div( float( j ).add( 2 ) ), this.distanceFallOff ) ) ) );
} );
} );
const sinHorizons = sqrt( sub( 1.0, cosHorizons.mul( cosHorizons ) ) );
const nx = dot( normalInSlice, sliceTangent );
const ny = dot( normalInSlice, viewDir );
const nxb = mul( 0.5, acos( cosHorizons.y ).sub( acos( cosHorizons.x ) ).add( sinHorizons.x.mul( cosHorizons.x ).sub( sinHorizons.y.mul( cosHorizons.y ) ) ) );
const nyb = mul( 0.5, sub( 2.0, cosHorizons.x.mul( cosHorizons.x ) ).sub( cosHorizons.y.mul( cosHorizons.y ) ) );
const occlusion = nx.mul( nxb ).add( ny.mul( nyb ) );
ao.addAssign( occlusion );
} );
ao.assign( clamp( ao.div( DIRECTIONS ), 0, 1 ) );
ao.assign( pow( ao, this.scale ) );
return vec4( vec3( ao ), 1.0 );
} );
const material = this._material || ( this._material = builder.createNodeMaterial() );
material.fragmentNode = ao().context( builder.getSharedContext() );
material.needsUpdate = true;
//
return this._textureNode;
}
dispose() {
this._aoRenderTarget.dispose();
}
}
function generateMagicSquareNoise( size = 5 ) {
const noiseSize = Math.floor( size ) % 2 === 0 ? Math.floor( size ) + 1 : Math.floor( size );
const magicSquare = generateMagicSquare( noiseSize );
const noiseSquareSize = magicSquare.length;
const data = new Uint8Array( noiseSquareSize * 4 );
for ( let inx = 0; inx < noiseSquareSize; ++ inx ) {
const iAng = magicSquare[ inx ];
const angle = ( 2 * Math.PI * iAng ) / noiseSquareSize;
const randomVec = new Vector3(
Math.cos( angle ),
Math.sin( angle ),
0
).normalize();
data[ inx * 4 ] = ( randomVec.x * 0.5 + 0.5 ) * 255;
data[ inx * 4 + 1 ] = ( randomVec.y * 0.5 + 0.5 ) * 255;
data[ inx * 4 + 2 ] = 127;
data[ inx * 4 + 3 ] = 255;
}
const noiseTexture = new DataTexture( data, noiseSize, noiseSize );
noiseTexture.wrapS = RepeatWrapping;
noiseTexture.wrapT = RepeatWrapping;
noiseTexture.needsUpdate = true;
return noiseTexture;
}
function generateMagicSquare( size ) {
const noiseSize = Math.floor( size ) % 2 === 0 ? Math.floor( size ) + 1 : Math.floor( size );
const noiseSquareSize = noiseSize * noiseSize;
const magicSquare = Array( noiseSquareSize ).fill( 0 );
let i = Math.floor( noiseSize / 2 );
let j = noiseSize - 1;
for ( let num = 1; num <= noiseSquareSize; ) {
if ( i === - 1 && j === noiseSize ) {
j = noiseSize - 2;
i = 0;
} else {
if ( j === noiseSize ) {
j = 0;
}
if ( i < 0 ) {
i = noiseSize - 1;
}
}
if ( magicSquare[ i * noiseSize + j ] !== 0 ) {
j -= 2;
i ++;
continue;
} else {
magicSquare[ i * noiseSize + j ] = num ++;
}
j ++;
i --;
}
return magicSquare;
}
export const ao = ( depthNode, normalNode, camera ) => nodeObject( new GTAONode( nodeObject( depthNode ), nodeObject( normalNode ), camera ) );
addNodeElement( 'ao', ao );
export default GTAONode;

View File

@ -0,0 +1,207 @@
import TempNode from '../core/TempNode.js';
import { nodeObject, addNodeElement, tslFn, float, vec2, vec4 } from '../shadernode/ShaderNode.js';
import { NodeUpdateType } from '../core/constants.js';
import { mul } from '../math/OperatorNode.js';
import { uv } from '../accessors/UVNode.js';
import { passTexture } from './PassNode.js';
import { uniform } from '../core/UniformNode.js';
import QuadMesh from '../../renderers/common/QuadMesh.js';
import { Vector2 } from '../../math/Vector2.js';
import { RenderTarget } from '../../core/RenderTarget.js';
// WebGPU: The use of a single QuadMesh for both gaussian blur passes results in a single RenderObject with a SampledTexture binding that
// alternates between source textures and triggers creation of new BindGroups and BindGroupLayouts every frame.
const _quadMesh1 = /*@__PURE__*/ new QuadMesh();
const _quadMesh2 = /*@__PURE__*/ new QuadMesh();
class GaussianBlurNode extends TempNode {
constructor( textureNode, directionNode = null, sigma = 2 ) {
super( 'vec4' );
this.textureNode = textureNode;
this.directionNode = directionNode;
this.sigma = sigma;
this._invSize = uniform( new Vector2() );
this._passDirection = uniform( new Vector2() );
this._horizontalRT = new RenderTarget();
this._horizontalRT.texture.name = 'GaussianBlurNode.horizontal';
this._verticalRT = new RenderTarget();
this._verticalRT.texture.name = 'GaussianBlurNode.vertical';
this._textureNode = passTexture( this, this._verticalRT.texture );
this.updateBeforeType = NodeUpdateType.RENDER;
this.resolution = new Vector2( 1, 1 );
}
setSize( width, height ) {
width = Math.max( Math.round( width * this.resolution.x ), 1 );
height = Math.max( Math.round( height * this.resolution.y ), 1 );
this._invSize.value.set( 1 / width, 1 / height );
this._horizontalRT.setSize( width, height );
this._verticalRT.setSize( width, height );
}
updateBefore( frame ) {
const { renderer } = frame;
const textureNode = this.textureNode;
const map = textureNode.value;
const currentRenderTarget = renderer.getRenderTarget();
const currentMRT = renderer.getMRT();
const currentTexture = textureNode.value;
_quadMesh1.material = this._material;
_quadMesh2.material = this._material;
this.setSize( map.image.width, map.image.height );
const textureType = map.type;
this._horizontalRT.texture.type = textureType;
this._verticalRT.texture.type = textureType;
// clear
renderer.setMRT( null );
// horizontal
renderer.setRenderTarget( this._horizontalRT );
this._passDirection.value.set( 1, 0 );
_quadMesh1.render( renderer );
// vertical
textureNode.value = this._horizontalRT.texture;
renderer.setRenderTarget( this._verticalRT );
this._passDirection.value.set( 0, 1 );
_quadMesh2.render( renderer );
// restore
renderer.setRenderTarget( currentRenderTarget );
renderer.setMRT( currentMRT );
textureNode.value = currentTexture;
}
getTextureNode() {
return this._textureNode;
}
setup( builder ) {
const textureNode = this.textureNode;
if ( textureNode.isTextureNode !== true ) {
console.error( 'GaussianBlurNode requires a TextureNode.' );
return vec4();
}
//
const uvNode = textureNode.uvNode || uv();
const directionNode = vec2( this.directionNode || 1 );
const sampleTexture = ( uv ) => textureNode.uv( uv );
const blur = tslFn( () => {
const kernelSize = 3 + ( 2 * this.sigma );
const gaussianCoefficients = this._getCoefficients( kernelSize );
const invSize = this._invSize;
const direction = directionNode.mul( this._passDirection );
const weightSum = float( gaussianCoefficients[ 0 ] ).toVar();
const diffuseSum = vec4( sampleTexture( uvNode ).mul( weightSum ) ).toVar();
for ( let i = 1; i < kernelSize; i ++ ) {
const x = float( i );
const w = float( gaussianCoefficients[ i ] );
const uvOffset = vec2( direction.mul( invSize.mul( x ) ) ).toVar();
const sample1 = vec4( sampleTexture( uvNode.add( uvOffset ) ) );
const sample2 = vec4( sampleTexture( uvNode.sub( uvOffset ) ) );
diffuseSum.addAssign( sample1.add( sample2 ).mul( w ) );
weightSum.addAssign( mul( 2.0, w ) );
}
return diffuseSum.div( weightSum );
} );
//
const material = this._material || ( this._material = builder.createNodeMaterial() );
material.fragmentNode = blur().context( builder.getSharedContext() );
material.needsUpdate = true;
//
const properties = builder.getNodeProperties( this );
properties.textureNode = textureNode;
//
return this._textureNode;
}
dispose() {
this._horizontalRT.dispose();
this._verticalRT.dispose();
}
_getCoefficients( kernelRadius ) {
const coefficients = [];
for ( let i = 0; i < kernelRadius; i ++ ) {
coefficients.push( 0.39894 * Math.exp( - 0.5 * i * i / ( kernelRadius * kernelRadius ) ) / kernelRadius );
}
return coefficients;
}
}
export const gaussianBlur = ( node, directionNode, sigma ) => nodeObject( new GaussianBlurNode( nodeObject( node ).toTexture(), directionNode, sigma ) );
addNodeElement( 'gaussianBlur', gaussianBlur );
export default GaussianBlurNode;

View File

@ -0,0 +1,53 @@
import TempNode from '../core/TempNode.js';
import { addNodeElement, tslFn, nodeObject, vec3, vec4, float } from '../shadernode/ShaderNode.js';
import { uniform } from '../core/UniformNode.js';
import { mix } from '../math/MathNode.js';
class Lut3DNode extends TempNode {
constructor( inputNode, lutNode, size, intensityNode ) {
super();
this.inputNode = inputNode;
this.lutNode = lutNode;
this.size = uniform( size );
this.intensityNode = intensityNode;
}
setup() {
const { inputNode, lutNode } = this;
const sampleLut = ( uv ) => lutNode.uv( uv );
const lut3D = tslFn( () => {
const base = inputNode;
// pull the sample in by half a pixel so the sample begins at the center of the edge pixels.
const pixelWidth = float( 1.0 ).div( this.size );
const halfPixelWidth = float( 0.5 ).div( this.size );
const uvw = vec3( halfPixelWidth ).add( base.rgb.mul( float( 1.0 ).sub( pixelWidth ) ) );
const lutValue = vec4( sampleLut( uvw ).rgb, base.a );
return vec4( mix( base, lutValue, this.intensityNode ) );
} );
const outputNode = lut3D();
return outputNode;
}
}
export const lut3D = ( node, lut, size, intensity ) => nodeObject( new Lut3DNode( nodeObject( node ), nodeObject( lut ), size, nodeObject( intensity ) ) );
addNodeElement( 'lut3D', lut3D );
export default Lut3DNode;

View File

@ -0,0 +1,106 @@
import TempNode from '../core/TempNode.js';
import { add } from '../math/OperatorNode.js';
import { modelNormalMatrix } from '../accessors/ModelNode.js';
import { normalView } from '../accessors/NormalNode.js';
import { positionView } from '../accessors/PositionNode.js';
import { TBNViewMatrix } from '../accessors/AccessorsUtils.js';
import { uv } from '../accessors/UVNode.js';
import { faceDirection } from './FrontFacingNode.js';
import { addNodeClass } from '../core/Node.js';
import { addNodeElement, tslFn, nodeProxy, vec3 } from '../shadernode/ShaderNode.js';
import { TangentSpaceNormalMap, ObjectSpaceNormalMap } from '../../constants.js';
// Normal Mapping Without Precomputed Tangents
// http://www.thetenthplanet.de/archives/1180
const perturbNormal2Arb = tslFn( ( inputs ) => {
const { eye_pos, surf_norm, mapN, uv } = inputs;
const q0 = eye_pos.dFdx();
const q1 = eye_pos.dFdy();
const st0 = uv.dFdx();
const st1 = uv.dFdy();
const N = surf_norm; // normalized
const q1perp = q1.cross( N );
const q0perp = N.cross( q0 );
const T = q1perp.mul( st0.x ).add( q0perp.mul( st1.x ) );
const B = q1perp.mul( st0.y ).add( q0perp.mul( st1.y ) );
const det = T.dot( T ).max( B.dot( B ) );
const scale = faceDirection.mul( det.inverseSqrt() );
return add( T.mul( mapN.x, scale ), B.mul( mapN.y, scale ), N.mul( mapN.z ) ).normalize();
} );
class NormalMapNode extends TempNode {
constructor( node, scaleNode = null ) {
super( 'vec3' );
this.node = node;
this.scaleNode = scaleNode;
this.normalMapType = TangentSpaceNormalMap;
}
setup( builder ) {
const { normalMapType, scaleNode } = this;
let normalMap = this.node.mul( 2.0 ).sub( 1.0 );
if ( scaleNode !== null ) {
normalMap = vec3( normalMap.xy.mul( scaleNode ), normalMap.z );
}
let outputNode = null;
if ( normalMapType === ObjectSpaceNormalMap ) {
outputNode = modelNormalMatrix.mul( normalMap ).normalize();
} else if ( normalMapType === TangentSpaceNormalMap ) {
const tangent = builder.hasGeometryAttribute( 'tangent' );
if ( tangent === true ) {
outputNode = TBNViewMatrix.mul( normalMap ).normalize();
} else {
outputNode = perturbNormal2Arb( {
eye_pos: positionView,
surf_norm: normalView,
mapN: normalMap,
uv: uv()
} );
}
}
return outputNode;
}
}
export default NormalMapNode;
export const normalMap = nodeProxy( NormalMapNode );
addNodeElement( 'normalMap', normalMap );
addNodeClass( 'NormalMapNode', NormalMapNode );

View File

@ -0,0 +1,291 @@
import { addNodeClass } from '../core/Node.js';
import TempNode from '../core/TempNode.js';
import { default as TextureNode/*, texture*/ } from '../accessors/TextureNode.js';
import { NodeUpdateType } from '../core/constants.js';
import { nodeObject } from '../shadernode/ShaderNode.js';
import { uniform } from '../core/UniformNode.js';
import { viewZToOrthographicDepth, perspectiveDepthToViewZ } from './ViewportDepthNode.js';
import { HalfFloatType/*, FloatType*/ } from '../../constants.js';
import { Vector2 } from '../../math/Vector2.js';
import { DepthTexture } from '../../textures/DepthTexture.js';
import { RenderTarget } from '../../core/RenderTarget.js';
const _size = /*@__PURE__*/ new Vector2();
class PassTextureNode extends TextureNode {
constructor( passNode, texture ) {
super( texture );
this.passNode = passNode;
this.setUpdateMatrix( false );
}
setup( builder ) {
this.passNode.build( builder );
return super.setup( builder );
}
clone() {
return new this.constructor( this.passNode, this.value );
}
}
class PassMultipleTextureNode extends PassTextureNode {
constructor( passNode, textureName ) {
super( passNode, null );
this.textureName = textureName;
}
setup( builder ) {
this.value = this.passNode.getTexture( this.textureName );
return super.setup( builder );
}
clone() {
return new this.constructor( this.passNode, this.textureName );
}
}
class PassNode extends TempNode {
constructor( scope, scene, camera, options = {} ) {
super( 'vec4' );
this.scope = scope;
this.scene = scene;
this.camera = camera;
this.options = options;
this._pixelRatio = 1;
this._width = 1;
this._height = 1;
const depthTexture = new DepthTexture();
depthTexture.isRenderTargetTexture = true;
//depthTexture.type = FloatType;
depthTexture.name = 'depth';
const renderTarget = new RenderTarget( this._width * this._pixelRatio, this._height * this._pixelRatio, { type: HalfFloatType, ...options, } );
renderTarget.texture.name = 'output';
renderTarget.depthTexture = depthTexture;
this.renderTarget = renderTarget;
this.updateBeforeType = NodeUpdateType.FRAME;
this._textures = {
output: renderTarget.texture,
depth: depthTexture
};
this._textureNodes = {};
this._linearDepthNodes = {};
this._viewZNodes = {};
this._cameraNear = uniform( 0 );
this._cameraFar = uniform( 0 );
this._mrt = null;
this.isPassNode = true;
}
setMRT( mrt ) {
this._mrt = mrt;
return this;
}
getMRT() {
return this._mrt;
}
isGlobal() {
return true;
}
getTexture( name ) {
let texture = this._textures[ name ];
if ( texture === undefined ) {
const refTexture = this.renderTarget.texture;
texture = refTexture.clone();
texture.isRenderTargetTexture = true;
texture.name = name;
this._textures[ name ] = texture;
this.renderTarget.textures.push( texture );
}
return texture;
}
getTextureNode( name = 'output' ) {
let textureNode = this._textureNodes[ name ];
if ( textureNode === undefined ) {
this._textureNodes[ name ] = textureNode = nodeObject( new PassMultipleTextureNode( this, name ) );
}
return textureNode;
}
getViewZNode( name = 'depth' ) {
let viewZNode = this._viewZNodes[ name ];
if ( viewZNode === undefined ) {
const cameraNear = this._cameraNear;
const cameraFar = this._cameraFar;
this._viewZNodes[ name ] = viewZNode = perspectiveDepthToViewZ( this.getTextureNode( name ), cameraNear, cameraFar );
}
return viewZNode;
}
getLinearDepthNode( name = 'depth' ) {
let linearDepthNode = this._linearDepthNodes[ name ];
if ( linearDepthNode === undefined ) {
const cameraNear = this._cameraNear;
const cameraFar = this._cameraFar;
const viewZNode = this.getViewZNode( name );
// TODO: just if ( builder.camera.isPerspectiveCamera )
this._linearDepthNodes[ name ] = linearDepthNode = viewZToOrthographicDepth( viewZNode, cameraNear, cameraFar );
}
return linearDepthNode;
}
setup( { renderer } ) {
this.renderTarget.samples = this.options.samples === undefined ? renderer.samples : this.options.samples;
// Disable MSAA for WebGL backend for now
if ( renderer.backend.isWebGLBackend === true ) {
this.renderTarget.samples = 0;
}
this.renderTarget.depthTexture.isMultisampleRenderTargetTexture = this.renderTarget.samples > 1;
return this.scope === PassNode.COLOR ? this.getTextureNode() : this.getLinearDepthNode();
}
updateBefore( frame ) {
const { renderer } = frame;
const { scene, camera } = this;
this._pixelRatio = renderer.getPixelRatio();
const size = renderer.getSize( _size );
this.setSize( size.width, size.height );
const currentRenderTarget = renderer.getRenderTarget();
const currentMRT = renderer.getMRT();
this._cameraNear.value = camera.near;
this._cameraFar.value = camera.far;
renderer.setRenderTarget( this.renderTarget );
renderer.setMRT( this._mrt );
renderer.render( scene, camera );
renderer.setRenderTarget( currentRenderTarget );
renderer.setMRT( currentMRT );
}
setSize( width, height ) {
this._width = width;
this._height = height;
const effectiveWidth = this._width * this._pixelRatio;
const effectiveHeight = this._height * this._pixelRatio;
this.renderTarget.setSize( effectiveWidth, effectiveHeight );
}
setPixelRatio( pixelRatio ) {
this._pixelRatio = pixelRatio;
this.setSize( this._width, this._height );
}
dispose() {
this.renderTarget.dispose();
}
}
PassNode.COLOR = 'color';
PassNode.DEPTH = 'depth';
export default PassNode;
export const pass = ( scene, camera, options ) => nodeObject( new PassNode( PassNode.COLOR, scene, camera, options ) );
export const passTexture = ( pass, texture ) => nodeObject( new PassTextureNode( pass, texture ) );
export const depthPass = ( scene, camera ) => nodeObject( new PassNode( PassNode.DEPTH, scene, camera ) );
addNodeClass( 'PassNode', PassNode );

View File

@ -0,0 +1,201 @@
import TempNode from '../core/TempNode.js';
import { uv } from '../accessors/UVNode.js';
import { addNodeElement, tslFn, nodeObject, vec2, vec3, float, If } from '../shadernode/ShaderNode.js';
import { NodeUpdateType } from '../core/constants.js';
import { uniform } from '../core/UniformNode.js';
import { dot, clamp, smoothstep, sign, step, floor } from '../math/MathNode.js';
import { Vector4 } from '../../math/Vector4.js';
import { output, property } from '../core/PropertyNode.js';
import PassNode from './PassNode.js';
import { mrt } from '../core/MRTNode.js';
import { normalView } from '../accessors/NormalNode.js';
import { NearestFilter } from '../../constants.js';
class PixelationNode extends TempNode {
constructor( textureNode, depthNode, normalNode, pixelSize, normalEdgeStrength, depthEdgeStrength ) {
super();
// Input textures
this.textureNode = textureNode;
this.depthNode = depthNode;
this.normalNode = normalNode;
// Input uniforms
this.pixelSize = pixelSize;
this.normalEdgeStrength = normalEdgeStrength;
this.depthEdgeStrength = depthEdgeStrength;
// Private uniforms
this._resolution = uniform( new Vector4() );
this.updateBeforeType = NodeUpdateType.RENDER;
}
updateBefore() {
const map = this.textureNode.value;
const width = map.image.width;
const height = map.image.height;
this._resolution.value.set( width, height, 1 / width, 1 / height );
}
setup() {
const { textureNode, depthNode, normalNode } = this;
const uvNodeTexture = textureNode.uvNode || uv();
const uvNodeDepth = depthNode.uvNode || uv();
const uvNodeNormal = normalNode.uvNode || uv();
const sampleTexture = () => textureNode.uv( uvNodeTexture );
const sampleDepth = ( x, y ) => depthNode.uv( uvNodeDepth.add( vec2( x, y ).mul( this._resolution.zw ) ) ).r;
const sampleNormal = ( x, y ) => normalNode.uv( uvNodeNormal.add( vec2( x, y ).mul( this._resolution.zw ) ) ).rgb.normalize();
const depthEdgeIndicator = ( depth ) => {
const diff = property( 'float', 'diff' );
diff.addAssign( clamp( sampleDepth( 1, 0 ).sub( depth ) ) );
diff.addAssign( clamp( sampleDepth( - 1, 0 ).sub( depth ) ) );
diff.addAssign( clamp( sampleDepth( 0, 1 ).sub( depth ) ) );
diff.addAssign( clamp( sampleDepth( 0, - 1 ).sub( depth ) ) );
return floor( smoothstep( 0.01, 0.02, diff ).mul( 2 ) ).div( 2 );
};
const neighborNormalEdgeIndicator = ( x, y, depth, normal ) => {
const depthDiff = sampleDepth( x, y ).sub( depth );
const neighborNormal = sampleNormal( x, y );
// Edge pixels should yield to faces who's normals are closer to the bias normal.
const normalEdgeBias = vec3( 1, 1, 1 ); // This should probably be a parameter.
const normalDiff = dot( normal.sub( neighborNormal ), normalEdgeBias );
const normalIndicator = clamp( smoothstep( - 0.01, 0.01, normalDiff ), 0.0, 1.0 );
// Only the shallower pixel should detect the normal edge.
const depthIndicator = clamp( sign( depthDiff.mul( .25 ).add( .0025 ) ), 0.0, 1.0 );
return float( 1.0 ).sub( dot( normal, neighborNormal ) ).mul( depthIndicator ).mul( normalIndicator );
};
const normalEdgeIndicator = ( depth, normal ) => {
const indicator = property( 'float', 'indicator' );
indicator.addAssign( neighborNormalEdgeIndicator( 0, - 1, depth, normal ) );
indicator.addAssign( neighborNormalEdgeIndicator( 0, 1, depth, normal ) );
indicator.addAssign( neighborNormalEdgeIndicator( - 1, 0, depth, normal ) );
indicator.addAssign( neighborNormalEdgeIndicator( 1, 0, depth, normal ) );
return step( 0.1, indicator );
};
const pixelation = tslFn( () => {
const texel = sampleTexture();
const depth = property( 'float', 'depth' );
const normal = property( 'vec3', 'normal' );
If( this.depthEdgeStrength.greaterThan( 0.0 ).or( this.normalEdgeStrength.greaterThan( 0.0 ) ), () => {
depth.assign( sampleDepth( 0, 0 ) );
normal.assign( sampleNormal( 0, 0 ) );
} );
const dei = property( 'float', 'dei' );
If( this.depthEdgeStrength.greaterThan( 0.0 ), () => {
dei.assign( depthEdgeIndicator( depth ) );
} );
const nei = property( 'float', 'nei' );
If( this.normalEdgeStrength.greaterThan( 0.0 ), () => {
nei.assign( normalEdgeIndicator( depth, normal ) );
} );
const strength = dei.greaterThan( 0 ).cond( float( 1.0 ).sub( dei.mul( this.depthEdgeStrength ) ), nei.mul( this.normalEdgeStrength ).add( 1 ) );
return texel.mul( strength );
} );
const outputNode = pixelation();
return outputNode;
}
}
const pixelation = ( node, depthNode, normalNode, pixelSize = 6, normalEdgeStrength = 0.3, depthEdgeStrength = 0.4 ) => nodeObject( new PixelationNode( nodeObject( node ).toTexture(), nodeObject( depthNode ).toTexture(), nodeObject( normalNode ).toTexture(), nodeObject( pixelSize ), nodeObject( normalEdgeStrength ), nodeObject( depthEdgeStrength ) ) );
addNodeElement( 'pixelation', pixelation );
class PixelationPassNode extends PassNode {
constructor( scene, camera, pixelSize = 6, normalEdgeStrength = 0.3, depthEdgeStrength = 0.4 ) {
super( 'color', scene, camera, { minFilter: NearestFilter, magFilter: NearestFilter } );
this.pixelSize = pixelSize;
this.normalEdgeStrength = normalEdgeStrength;
this.depthEdgeStrength = depthEdgeStrength;
this.isPixelationPassNode = true;
this._mrt = mrt( {
output: output,
normal: normalView
} );
}
setSize( width, height ) {
const pixelSize = this.pixelSize.value ? this.pixelSize.value : this.pixelSize;
const adjustedWidth = Math.floor( width / pixelSize );
const adjustedHeight = Math.floor( height / pixelSize );
super.setSize( adjustedWidth, adjustedHeight );
}
setup() {
const color = super.getTextureNode( 'output' );
const depth = super.getTextureNode( 'depth' );
const normal = super.getTextureNode( 'normal' );
return pixelation( color, depth, normal, this.pixelSize, this.normalEdgeStrength, this.depthEdgeStrength );
}
}
export const pixelationPass = ( scene, camera, pixelSize, normalEdgeStrength, depthEdgeStrength ) => nodeObject( new PixelationPassNode( scene, camera, pixelSize, normalEdgeStrength, depthEdgeStrength ) );
export default PixelationPassNode;

View File

@ -0,0 +1,32 @@
import TempNode from '../core/TempNode.js';
import { addNodeClass } from '../core/Node.js';
import { addNodeElement, nodeProxy } from '../shadernode/ShaderNode.js';
class PosterizeNode extends TempNode {
constructor( sourceNode, stepsNode ) {
super();
this.sourceNode = sourceNode;
this.stepsNode = stepsNode;
}
setup() {
const { sourceNode, stepsNode } = this;
return sourceNode.mul( stepsNode ).floor().div( stepsNode );
}
}
export default PosterizeNode;
export const posterize = nodeProxy( PosterizeNode );
addNodeElement( 'posterize', posterize );
addNodeClass( 'PosterizeNode', PosterizeNode );

View File

@ -0,0 +1,49 @@
import TempNode from '../core/TempNode.js';
import { nodeObject, addNodeElement, tslFn, vec2, vec4 } from '../shadernode/ShaderNode.js';
import { uniform } from '../core/UniformNode.js';
import { uv } from '../accessors/UVNode.js';
import { sin, cos } from '../math/MathNode.js';
class RGBShiftNode extends TempNode {
constructor( textureNode, amount = 0.005, angle = 0 ) {
super( 'vec4' );
this.textureNode = textureNode;
this.amount = uniform( amount );
this.angle = uniform( angle );
}
setup() {
const { textureNode } = this;
const uvNode = textureNode.uvNode || uv();
const sampleTexture = ( uv ) => textureNode.uv( uv );
const rgbShift = tslFn( () => {
const offset = vec2( cos( this.angle ), sin( this.angle ) ).mul( this.amount );
const cr = sampleTexture( uvNode.add( offset ) );
const cga = sampleTexture( uvNode );
const cb = sampleTexture( uvNode.sub( offset ) );
return vec4( cr.r, cga.g, cb.b, cga.a );
} );
return rgbShift();
}
}
export const rgbShift = ( node, amount, angle ) => nodeObject( new RGBShiftNode( nodeObject( node ).toTexture(), amount, angle ) );
addNodeElement( 'rgbShift', rgbShift );
export default RGBShiftNode;

View File

@ -0,0 +1,56 @@
import TempNode from '../core/TempNode.js';
import { addNodeClass } from '../core/Node.js';
import { addNodeElement, nodeObject } from '../shadernode/ShaderNode.js';
import { SRGBColorSpace, NoToneMapping } from '../../constants.js';
class RenderOutputNode extends TempNode {
constructor( colorNode, toneMapping, outputColorSpace ) {
super( 'vec4' );
this.colorNode = colorNode;
this.toneMapping = toneMapping;
this.outputColorSpace = outputColorSpace;
this.isRenderOutput = true;
}
setup( { context } ) {
let outputNode = this.colorNode || context.color;
// tone mapping
const toneMapping = this.toneMapping !== null ? this.toneMapping : context.toneMapping;
const outputColorSpace = this.outputColorSpace !== null ? this.outputColorSpace : context.outputColorSpace;
if ( toneMapping !== NoToneMapping ) {
outputNode = outputNode.toneMapping( toneMapping );
}
// output color space
if ( outputColorSpace === SRGBColorSpace ) {
outputNode = outputNode.linearToColorSpace( outputColorSpace );
}
return outputNode;
}
}
export default RenderOutputNode;
export const renderOutput = ( color, toneMapping = null, outputColorSpace = null ) => nodeObject( new RenderOutputNode( nodeObject( color ), toneMapping, outputColorSpace ) );
addNodeElement( 'renderOutput', renderOutput );
addNodeClass( 'RenderOutputNode', RenderOutputNode );

Some files were not shown because too many files have changed in this diff Show More