2
0
Эх сурвалжийг харах

Nodes: Tangent and Bitangent (#24672)

* Added TangentNode

* Added BitangentNode

* NormalNode: cleanup

* Added optional value for .getConst()

* Added .hasGeometryAttribute()

* AttributeNode: use dummy value if not the geometry does not have the attribute

* Added tangent, bitangent and TBN properties

* Added tangent support

* update example to use tangent

* fix typo
sunag 2 жил өмнө
parent
commit
a9c2a501d7

+ 6 - 0
examples/jsm/nodes/Nodes.js

@@ -26,6 +26,7 @@ import VarNode from './core/VarNode.js';
 import VaryingNode from './core/VaryingNode.js';
 import VaryingNode from './core/VaryingNode.js';
 
 
 // accessors
 // accessors
+import BitangentNode from './accessors/BitangentNode.js';
 import BufferNode from './accessors/BufferNode.js';
 import BufferNode from './accessors/BufferNode.js';
 import CameraNode from './accessors/CameraNode.js';
 import CameraNode from './accessors/CameraNode.js';
 import CubeTextureNode from './accessors/CubeTextureNode.js';
 import CubeTextureNode from './accessors/CubeTextureNode.js';
@@ -41,6 +42,7 @@ import PositionNode from './accessors/PositionNode.js';
 import ReferenceNode from './accessors/ReferenceNode.js';
 import ReferenceNode from './accessors/ReferenceNode.js';
 import ReflectVectorNode from './accessors/ReflectVectorNode.js';
 import ReflectVectorNode from './accessors/ReflectVectorNode.js';
 import SkinningNode from './accessors/SkinningNode.js';
 import SkinningNode from './accessors/SkinningNode.js';
+import TangentNode from './accessors/TangentNode.js';
 import TextureNode from './accessors/TextureNode.js';
 import TextureNode from './accessors/TextureNode.js';
 import UVNode from './accessors/UVNode.js';
 import UVNode from './accessors/UVNode.js';
 import UserDataNode from './accessors/UserDataNode.js';
 import UserDataNode from './accessors/UserDataNode.js';
@@ -150,6 +152,7 @@ const nodeLib = {
 	ComputeNode,
 	ComputeNode,
 
 
 	// accessors
 	// accessors
+	BitangentNode,
 	BufferNode,
 	BufferNode,
 	CameraNode,
 	CameraNode,
 	CubeTextureNode,
 	CubeTextureNode,
@@ -165,6 +168,7 @@ const nodeLib = {
 	ReferenceNode,
 	ReferenceNode,
 	ReflectVectorNode,
 	ReflectVectorNode,
 	SkinningNode,
 	SkinningNode,
+	TangentNode,
 	TextureNode,
 	TextureNode,
 	UVNode,
 	UVNode,
 	UserDataNode,
 	UserDataNode,
@@ -264,6 +268,7 @@ export {
 	ComputeNode,
 	ComputeNode,
 
 
 	// accessors
 	// accessors
+	BitangentNode,
 	BufferNode,
 	BufferNode,
 	CameraNode,
 	CameraNode,
 	CubeTextureNode,
 	CubeTextureNode,
@@ -279,6 +284,7 @@ export {
 	ReferenceNode,
 	ReferenceNode,
 	ReflectVectorNode,
 	ReflectVectorNode,
 	SkinningNode,
 	SkinningNode,
+	TangentNode,
 	TextureNode,
 	TextureNode,
 	UVNode,
 	UVNode,
 	UserDataNode,
 	UserDataNode,

+ 62 - 0
examples/jsm/nodes/accessors/BitangentNode.js

@@ -0,0 +1,62 @@
+import Node from '../core/Node.js';
+import VaryingNode from '../core/VaryingNode.js';
+import OperatorNode from '../math/OperatorNode.js';
+import MathNode from '../math/MathNode.js';
+import SplitNode from '../utils/SplitNode.js';
+import NormalNode from './NormalNode.js';
+import TangentNode from './TangentNode.js';
+
+class BitangentNode extends Node {
+
+	static GEOMETRY = 'geometry';
+	static LOCAL = 'local';
+	static VIEW = 'view';
+	static WORLD = 'world';
+
+	constructor( scope = BitangentNode.LOCAL ) {
+
+		super( 'vec3' );
+
+		this.scope = scope;
+
+	}
+
+	getHash( /*builder*/ ) {
+
+		return `bitangent-${this.scope}`;
+
+	}
+
+	generate( builder ) {
+
+		const scope = this.scope;
+
+		const crossNormalTangent = new MathNode( MathNode.CROSS, new NormalNode( scope ), new TangentNode( scope ) );
+		const tangentW = new SplitNode( new TangentNode( TangentNode.GEOMETRY ), 'w' );
+		const vertexNode = new SplitNode( new OperatorNode( '*', crossNormalTangent, tangentW ), 'xyz' );
+
+		const outputNode = new MathNode( MathNode.NORMALIZE, new VaryingNode( vertexNode ) );
+
+		return outputNode.build( builder, this.getNodeType( builder ) );
+
+	}
+
+	serialize( data ) {
+
+		super.serialize( data );
+
+		data.scope = this.scope;
+
+	}
+
+	deserialize( data ) {
+
+		super.deserialize( data );
+
+		this.scope = data.scope;
+
+	}
+
+}
+
+export default BitangentNode;

+ 6 - 6
examples/jsm/nodes/accessors/NormalNode.js

@@ -10,8 +10,8 @@ class NormalNode extends Node {
 
 
 	static GEOMETRY = 'geometry';
 	static GEOMETRY = 'geometry';
 	static LOCAL = 'local';
 	static LOCAL = 'local';
-	static WORLD = 'world';
 	static VIEW = 'view';
 	static VIEW = 'view';
+	static WORLD = 'world';
 
 
 	constructor( scope = NormalNode.LOCAL ) {
 	constructor( scope = NormalNode.LOCAL ) {
 
 
@@ -43,18 +43,18 @@ class NormalNode extends Node {
 
 
 		} else if ( scope === NormalNode.VIEW ) {
 		} else if ( scope === NormalNode.VIEW ) {
 
 
-			const vertexNormalNode = new OperatorNode( '*', new ModelNode( ModelNode.NORMAL_MATRIX ), new NormalNode( NormalNode.LOCAL ) );
-			outputNode = new MathNode( MathNode.NORMALIZE, new VaryingNode( vertexNormalNode ) );
+			const vertexNode = new OperatorNode( '*', new ModelNode( ModelNode.NORMAL_MATRIX ), new NormalNode( NormalNode.LOCAL ) );
+			outputNode = new MathNode( MathNode.NORMALIZE, new VaryingNode( vertexNode ) );
 
 
 		} else if ( scope === NormalNode.WORLD ) {
 		} else if ( scope === NormalNode.WORLD ) {
 
 
 			// To use INVERSE_TRANSFORM_DIRECTION only inverse the param order like this: MathNode( ..., Vector, Matrix );
 			// To use INVERSE_TRANSFORM_DIRECTION only inverse the param order like this: MathNode( ..., Vector, Matrix );
-			const vertexNormalNode = new MathNode( MathNode.TRANSFORM_DIRECTION, new NormalNode( NormalNode.VIEW ), new CameraNode( CameraNode.VIEW_MATRIX ) );
-			outputNode = new MathNode( MathNode.NORMALIZE, new VaryingNode( vertexNormalNode ) );
+			const vertexNode = new MathNode( MathNode.TRANSFORM_DIRECTION, new NormalNode( NormalNode.VIEW ), new CameraNode( CameraNode.VIEW_MATRIX ) );
+			outputNode = new MathNode( MathNode.NORMALIZE, new VaryingNode( vertexNode ) );
 
 
 		}
 		}
 
 
-		return outputNode.build( builder );
+		return outputNode.build( builder, this.getNodeType( builder ) );
 
 
 	}
 	}
 
 

+ 7 - 0
examples/jsm/nodes/accessors/SkinningNode.js

@@ -7,6 +7,7 @@ import {
 	uniform,
 	uniform,
 	positionLocal,
 	positionLocal,
 	normalLocal,
 	normalLocal,
+	tangentLocal,
 	assign,
 	assign,
 	element,
 	element,
 	add,
 	add,
@@ -56,6 +57,12 @@ const Skinning = new ShaderNode( ( inputs, builder ) => {
 	assign( positionLocal, skinPosition ).build( builder );
 	assign( positionLocal, skinPosition ).build( builder );
 	assign( normalLocal, skinNormal ).build( builder );
 	assign( normalLocal, skinNormal ).build( builder );
 
 
+	if ( builder.hasGeometryAttribute( 'tangent' ) ) {
+
+		assign( tangentLocal, skinNormal ).build( builder );
+
+	}
+
 } );
 } );
 
 
 class SkinningNode extends Node {
 class SkinningNode extends Node {

+ 95 - 0
examples/jsm/nodes/accessors/TangentNode.js

@@ -0,0 +1,95 @@
+import Node from '../core/Node.js';
+import AttributeNode from '../core/AttributeNode.js';
+import VaryingNode from '../core/VaryingNode.js';
+import ModelNode from '../accessors/ModelNode.js';
+import CameraNode from '../accessors/CameraNode.js';
+import OperatorNode from '../math/OperatorNode.js';
+import MathNode from '../math/MathNode.js';
+import SplitNode from '../utils/SplitNode.js';
+
+class TangentNode extends Node {
+
+	static GEOMETRY = 'geometry';
+	static LOCAL = 'local';
+	static VIEW = 'view';
+	static WORLD = 'world';
+
+	constructor( scope = TangentNode.LOCAL ) {
+
+		super();
+
+		this.scope = scope;
+
+	}
+
+	getHash( /*builder*/ ) {
+
+		return `tangent-${this.scope}`;
+
+	}
+
+	getNodeType() {
+
+		const scope = this.scope;
+
+		if ( scope === TangentNode.GEOMETRY ) {
+
+			return 'vec4';
+
+		}
+
+		return 'vec3';
+
+	}
+
+
+	generate( builder ) {
+
+		const scope = this.scope;
+
+		let outputNode = null;
+
+		if ( scope === TangentNode.GEOMETRY ) {
+
+			outputNode = new AttributeNode( 'tangent', 'vec4' );
+
+		} else if ( scope === TangentNode.LOCAL ) {
+
+			outputNode = new VaryingNode( new SplitNode( new TangentNode( TangentNode.GEOMETRY ), 'xyz' ) );
+
+		} else if ( scope === TangentNode.VIEW ) {
+
+			const vertexNode = new SplitNode( new OperatorNode( '*', new ModelNode( ModelNode.VIEW_MATRIX ), new TangentNode( TangentNode.LOCAL ) ), 'xyz' );
+
+			outputNode = new MathNode( MathNode.NORMALIZE, new VaryingNode( vertexNode ) );
+
+		} else if ( scope === TangentNode.WORLD ) {
+
+			const vertexNode = new MathNode( MathNode.TRANSFORM_DIRECTION, new TangentNode( TangentNode.VIEW ), new CameraNode( CameraNode.VIEW_MATRIX ) );
+			outputNode = new MathNode( MathNode.NORMALIZE, new VaryingNode( vertexNode ) );
+
+		}
+
+		return outputNode.build( builder, this.getNodeType( builder ) );
+
+	}
+
+	serialize( data ) {
+
+		super.serialize( data );
+
+		data.scope = this.scope;
+
+	}
+
+	deserialize( data ) {
+
+		super.deserialize( data );
+
+		this.scope = data.scope;
+
+	}
+
+}
+
+export default TangentNode;

+ 31 - 8
examples/jsm/nodes/core/AttributeNode.js

@@ -19,14 +19,23 @@ class AttributeNode extends Node {
 
 
 	getNodeType( builder ) {
 	getNodeType( builder ) {
 
 
+		const attributeName = this.getAttributeName( builder );
+
 		let nodeType = super.getNodeType( builder );
 		let nodeType = super.getNodeType( builder );
 
 
 		if ( nodeType === null ) {
 		if ( nodeType === null ) {
 
 
-			const attributeName = this.getAttributeName( builder );
-			const attribute = builder.geometry.getAttribute( attributeName );
+			if ( builder.hasGeometryAttribute( attributeName ) ) {
+
+				const attribute = builder.geometry.getAttribute( attributeName );
+
+				nodeType = builder.getTypeFromLength( attribute.itemSize );
 
 
-			nodeType = builder.getTypeFromLength( attribute.itemSize );
+			} else {
+
+				nodeType = 'float';
+
+			}
 
 
 		}
 		}
 
 
@@ -50,17 +59,31 @@ class AttributeNode extends Node {
 
 
 	generate( builder ) {
 	generate( builder ) {
 
 
-		const attribute = builder.getAttribute( this.getAttributeName( builder ), this.getNodeType( builder ) );
+		const attributeName = this.getAttributeName( builder );
+		const nodeType = this.getNodeType( builder );
+		const geometryAttribute = builder.hasGeometryAttribute( attributeName );
+
+		if ( geometryAttribute === true ) {
+
+			const nodeAttribute = builder.getAttribute( attributeName, nodeType );
+
+			if ( builder.isShaderStage( 'vertex' ) ) {
+
+				return nodeAttribute.name;
+
+			} else {
+
+				const nodeVarying = new VaryingNode( this );
 
 
-		if ( builder.isShaderStage( 'vertex' ) ) {
+				return nodeVarying.build( builder, nodeAttribute.type );
 
 
-			return attribute.name;
+			}
 
 
 		} else {
 		} else {
 
 
-			const nodeVarying = new VaryingNode( this );
+			console.warn( `Attribute "${ attributeName }" not found.` );
 
 
-			return nodeVarying.build( builder, attribute.type );
+			return builder.getConst( nodeType );
 
 
 		}
 		}
 
 

+ 19 - 2
examples/jsm/nodes/core/NodeBuilder.js

@@ -6,7 +6,7 @@ import NodeCode from './NodeCode.js';
 import NodeKeywords from './NodeKeywords.js';
 import NodeKeywords from './NodeKeywords.js';
 import { NodeUpdateType } from './constants.js';
 import { NodeUpdateType } from './constants.js';
 
 
-import { REVISION, LinearEncoding } from 'three';
+import { REVISION, LinearEncoding, Color, Vector2, Vector3, Vector4 } from 'three';
 
 
 export const defaultShaderStages = [ 'fragment', 'vertex' ];
 export const defaultShaderStages = [ 'fragment', 'vertex' ];
 export const shaderStages = [ ...defaultShaderStages, 'compute' ];
 export const shaderStages = [ ...defaultShaderStages, 'compute' ];
@@ -206,7 +206,18 @@ class NodeBuilder {
 	}
 	}
 
 
 	// @TODO: rename to .generateConst()
 	// @TODO: rename to .generateConst()
-	getConst( type, value ) {
+	getConst( type, value = null ) {
+
+		if ( value === null ) {
+
+			if ( type === 'float' || type === 'int' || type === 'uint' ) value = 0;
+			else if ( type === 'bool' ) value = false;
+			else if ( type === 'color' ) value = new Color();
+			else if ( type === 'vec2' ) value = new Vector2();
+			else if ( type === 'vec3' ) value = new Vector3();
+			else if ( type === 'vec4' ) value = new Vector4();
+
+		}
 
 
 		if ( type === 'float' ) return toFloat( value );
 		if ( type === 'float' ) return toFloat( value );
 		if ( type === 'int' ) return `${ Math.round( value ) }`;
 		if ( type === 'int' ) return `${ Math.round( value ) }`;
@@ -254,6 +265,12 @@ class NodeBuilder {
 
 
 	}
 	}
 
 
+	hasGeometryAttribute( name ) {
+
+		return this.geometry?.getAttribute( name ) !== undefined;
+
+	}
+
 	getAttribute( name, type ) {
 	getAttribute( name, type ) {
 
 
 		const attributes = this.attributes;
 		const attributes = this.attributes;

+ 19 - 12
examples/jsm/nodes/display/NormalMapNode.js

@@ -1,6 +1,5 @@
 import TempNode from '../core/TempNode.js';
 import TempNode from '../core/TempNode.js';
-import ModelNode from '../accessors/ModelNode.js';
-import { ShaderNode, positionView, normalView, uv, vec3, add, sub, mul, dFdx, dFdy, cross, max, dot, normalize, inversesqrt, faceDirection } from '../shadernode/ShaderNodeBaseElements.js';
+import { ShaderNode, positionView, normalView, uv, vec3, add, sub, mul, dFdx, dFdy, cross, max, dot, normalize, inversesqrt, faceDirection, modelNormalMatrix, TBNViewMatrix } from '../shadernode/ShaderNodeBaseElements.js';
 
 
 import { TangentSpaceNormalMap, ObjectSpaceNormalMap } from 'three';
 import { TangentSpaceNormalMap, ObjectSpaceNormalMap } from 'three';
 
 
@@ -44,7 +43,7 @@ class NormalMapNode extends TempNode {
 
 
 	}
 	}
 
 
-	construct() {
+	construct( builder ) {
 
 
 		const { normalMapType, scaleNode } = this;
 		const { normalMapType, scaleNode } = this;
 
 
@@ -62,18 +61,26 @@ class NormalMapNode extends TempNode {
 
 
 		if ( normalMapType === ObjectSpaceNormalMap ) {
 		if ( normalMapType === ObjectSpaceNormalMap ) {
 
 
-			const vertexNormalNode = mul( new ModelNode( ModelNode.NORMAL_MATRIX ), normalMap );
-
-			outputNode = normalize( vertexNormalNode );
+			outputNode = normalize( mul( modelNormalMatrix, normalMap ) );
 
 
 		} else if ( normalMapType === TangentSpaceNormalMap ) {
 		} else if ( normalMapType === TangentSpaceNormalMap ) {
 
 
-			outputNode = perturbNormal2ArbNode.call( {
-				eye_pos: positionView,
-				surf_norm: normalView,
-				mapN: normalMap,
-				uv: uv()
-			} );
+			const tangent = builder.hasGeometryAttribute( 'tangent' );
+
+			if ( tangent === true ) {
+
+				outputNode = normalize( mul( TBNViewMatrix, normalMap ) );
+
+			} else {
+
+				outputNode = perturbNormal2ArbNode.call( {
+					eye_pos: positionView,
+					surf_norm: normalView,
+					mapN: normalMap,
+					uv: uv()
+				} );
+
+			}
 
 
 		}
 		}
 
 

+ 77 - 60
examples/jsm/nodes/shadernode/ShaderNodeBaseElements.js

@@ -14,6 +14,7 @@ import VarNode from '../core/VarNode.js';
 import VaryingNode from '../core/VaryingNode.js';
 import VaryingNode from '../core/VaryingNode.js';
 
 
 // accessors
 // accessors
+import BitangentNode from '../accessors/BitangentNode.js';
 import BufferNode from '../accessors/BufferNode.js';
 import BufferNode from '../accessors/BufferNode.js';
 import CameraNode from '../accessors/CameraNode.js';
 import CameraNode from '../accessors/CameraNode.js';
 import MaterialNode from '../accessors/MaterialNode.js';
 import MaterialNode from '../accessors/MaterialNode.js';
@@ -25,6 +26,7 @@ import PointUVNode from '../accessors/PointUVNode.js';
 import PositionNode from '../accessors/PositionNode.js';
 import PositionNode from '../accessors/PositionNode.js';
 import ReferenceNode from '../accessors/ReferenceNode.js';
 import ReferenceNode from '../accessors/ReferenceNode.js';
 import StorageBufferNode from '../accessors/StorageBufferNode.js';
 import StorageBufferNode from '../accessors/StorageBufferNode.js';
+import TangentNode from '../accessors/TangentNode.js';
 import TextureNode from '../accessors/TextureNode.js';
 import TextureNode from '../accessors/TextureNode.js';
 import UserDataNode from '../accessors/UserDataNode.js';
 import UserDataNode from '../accessors/UserDataNode.js';
 import UVNode from '../accessors/UVNode.js';
 import UVNode from '../accessors/UVNode.js';
@@ -124,65 +126,6 @@ export const label = nodeProxy( VarNode );
 export const temp = label;
 export const temp = label;
 export const varying = nodeProxy( VaryingNode );
 export const varying = nodeProxy( VaryingNode );
 
 
-// accesors
-
-export const buffer = ( value, nodeOrType, count ) => nodeObject( new BufferNode( value, getConstNodeType( nodeOrType ), count ) );
-export const storage = ( value, nodeOrType, count ) => nodeObject( new StorageBufferNode( value, getConstNodeType( nodeOrType ), count ) );
-
-export const cameraProjectionMatrix = nodeImmutable( CameraNode, CameraNode.PROJECTION_MATRIX );
-export const cameraViewMatrix = nodeImmutable( CameraNode, CameraNode.VIEW_MATRIX );
-export const cameraNormalMatrix = nodeImmutable( CameraNode, CameraNode.NORMAL_MATRIX );
-export const cameraWorldMatrix = nodeImmutable( CameraNode, CameraNode.WORLD_MATRIX );
-export const cameraPosition = nodeImmutable( CameraNode, CameraNode.POSITION );
-
-export const materialAlphaTest = nodeImmutable( MaterialNode, MaterialNode.ALPHA_TEST );
-export const materialColor = nodeImmutable( MaterialNode, MaterialNode.COLOR );
-export const materialEmissive = nodeImmutable( MaterialNode, MaterialNode.EMISSIVE );
-export const materialOpacity = nodeImmutable( MaterialNode, MaterialNode.OPACITY );
-//export const materialSpecular = nodeImmutable( MaterialNode, MaterialNode.SPECULAR );
-export const materialRoughness = nodeImmutable( MaterialNode, MaterialNode.ROUGHNESS );
-export const materialMetalness = nodeImmutable( MaterialNode, MaterialNode.METALNESS );
-export const materialRotation = nodeImmutable( MaterialNode, MaterialNode.ROTATION );
-
-export const diffuseColor = nodeImmutable( PropertyNode, 'DiffuseColor', 'vec4' );
-export const roughness = nodeImmutable( PropertyNode, 'Roughness', 'float' );
-export const metalness = nodeImmutable( PropertyNode, 'Metalness', 'float' );
-export const alphaTest = nodeImmutable( PropertyNode, 'AlphaTest', 'float' );
-export const specularColor = nodeImmutable( PropertyNode, 'SpecularColor', 'color' );
-
-export const reference = ( name, nodeOrType, object ) => nodeObject( new ReferenceNode( name, getConstNodeType( nodeOrType ), object ) );
-export const materialReference = ( name, nodeOrType, material ) => nodeObject( new MaterialReferenceNode( name, getConstNodeType( nodeOrType ), material ) );
-export const userData = ( name, inputType, userData ) => nodeObject( new UserDataNode( name, inputType, userData ) );
-
-export const modelViewProjection = nodeProxy( ModelViewProjectionNode );
-
-export const normalGeometry = nodeImmutable( NormalNode, NormalNode.GEOMETRY );
-export const normalLocal = nodeImmutable( NormalNode, NormalNode.LOCAL );
-export const normalWorld = nodeImmutable( NormalNode, NormalNode.WORLD );
-export const normalView = nodeImmutable( NormalNode, NormalNode.VIEW );
-export const transformedNormalView = nodeImmutable( VarNode, normalView, 'TransformedNormalView' );
-
-export const modelViewMatrix = nodeImmutable( ModelNode, ModelNode.VIEW_MATRIX );
-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 modelViewPosition = nodeImmutable( ModelNode, ModelNode.VIEW_POSITION );
-
-export const positionGeometry = nodeImmutable( PositionNode, PositionNode.GEOMETRY );
-export const positionLocal = nodeImmutable( PositionNode, PositionNode.LOCAL );
-export const positionWorld = nodeImmutable( PositionNode, PositionNode.WORLD );
-export const positionView = nodeImmutable( PositionNode, PositionNode.VIEW );
-export const positionViewDirection = nodeImmutable( PositionNode, PositionNode.VIEW_DIRECTION );
-
-export const texture = nodeProxy( TextureNode );
-export const sampler = ( texture ) => nodeObject( new ConvertNode( texture.isNode === true ? texture : new TextureNode( texture ), 'sampler' ) );
-export const uv = ( ...params ) => nodeObject( new UVNode( ...params ) );
-export const pointUV = nodeImmutable( PointUVNode );
-
-// gpgpu
-
-export const compute = ( node, count, workgroupSize ) => nodeObject( new ComputeNode( nodeObject( node ), count, workgroupSize ) );
-
 // math
 // math
 
 
 export const EPSILON = float( 1e-6 );
 export const EPSILON = float( 1e-6 );
@@ -258,6 +201,80 @@ export const refract = nodeProxy( MathNode, MathNode.REFRACT );
 export const smoothstep = nodeProxy( MathNode, MathNode.SMOOTHSTEP );
 export const smoothstep = nodeProxy( MathNode, MathNode.SMOOTHSTEP );
 export const faceforward = nodeProxy( MathNode, MathNode.FACEFORWARD );
 export const faceforward = nodeProxy( MathNode, MathNode.FACEFORWARD );
 
 
+// accessors
+
+export const buffer = ( value, nodeOrType, count ) => nodeObject( new BufferNode( value, getConstNodeType( nodeOrType ), count ) );
+export const storage = ( value, nodeOrType, count ) => nodeObject( new StorageBufferNode( value, getConstNodeType( nodeOrType ), count ) );
+
+export const cameraProjectionMatrix = nodeImmutable( CameraNode, CameraNode.PROJECTION_MATRIX );
+export const cameraViewMatrix = nodeImmutable( CameraNode, CameraNode.VIEW_MATRIX );
+export const cameraNormalMatrix = nodeImmutable( CameraNode, CameraNode.NORMAL_MATRIX );
+export const cameraWorldMatrix = nodeImmutable( CameraNode, CameraNode.WORLD_MATRIX );
+export const cameraPosition = nodeImmutable( CameraNode, CameraNode.POSITION );
+
+export const materialAlphaTest = nodeImmutable( MaterialNode, MaterialNode.ALPHA_TEST );
+export const materialColor = nodeImmutable( MaterialNode, MaterialNode.COLOR );
+export const materialEmissive = nodeImmutable( MaterialNode, MaterialNode.EMISSIVE );
+export const materialOpacity = nodeImmutable( MaterialNode, MaterialNode.OPACITY );
+//export const materialSpecular = nodeImmutable( MaterialNode, MaterialNode.SPECULAR );
+export const materialRoughness = nodeImmutable( MaterialNode, MaterialNode.ROUGHNESS );
+export const materialMetalness = nodeImmutable( MaterialNode, MaterialNode.METALNESS );
+export const materialRotation = nodeImmutable( MaterialNode, MaterialNode.ROTATION );
+
+export const diffuseColor = nodeImmutable( PropertyNode, 'DiffuseColor', 'vec4' );
+export const roughness = nodeImmutable( PropertyNode, 'Roughness', 'float' );
+export const metalness = nodeImmutable( PropertyNode, 'Metalness', 'float' );
+export const alphaTest = nodeImmutable( PropertyNode, 'AlphaTest', 'float' );
+export const specularColor = nodeImmutable( PropertyNode, 'SpecularColor', 'color' );
+
+export const reference = ( name, nodeOrType, object ) => nodeObject( new ReferenceNode( name, getConstNodeType( nodeOrType ), object ) );
+export const materialReference = ( name, nodeOrType, material ) => nodeObject( new MaterialReferenceNode( name, getConstNodeType( nodeOrType ), material ) );
+export const userData = ( name, inputType, userData ) => nodeObject( new UserDataNode( name, inputType, userData ) );
+
+export const modelViewProjection = nodeProxy( ModelViewProjectionNode );
+
+export const normalGeometry = nodeImmutable( NormalNode, NormalNode.GEOMETRY );
+export const normalLocal = nodeImmutable( NormalNode, NormalNode.LOCAL );
+export const normalView = nodeImmutable( NormalNode, NormalNode.VIEW );
+export const normalWorld = nodeImmutable( NormalNode, NormalNode.WORLD );
+export const transformedNormalView = nodeImmutable( VarNode, normalView, 'TransformedNormalView' );
+export const transformedNormalWorld = normalize( transformDirection( transformedNormalView, cameraViewMatrix ) );
+
+export const tangentGeometry = nodeImmutable( TangentNode, TangentNode.GEOMETRY );
+export const tangentLocal = nodeImmutable( TangentNode, TangentNode.LOCAL );
+export const tangentView = nodeImmutable( TangentNode, TangentNode.VIEW );
+export const tangentWorld = nodeImmutable( TangentNode, TangentNode.WORLD );
+export const transformedTangentView = nodeImmutable( VarNode, tangentView, 'TransformedTangentView' );
+export const transformedTangentWorld = normalize( transformDirection( transformedTangentView, cameraViewMatrix ) );
+
+export const bitangentGeometry = nodeImmutable( BitangentNode, BitangentNode.GEOMETRY );
+export const bitangentLocal = nodeImmutable( BitangentNode, BitangentNode.LOCAL );
+export const bitangentView = nodeImmutable( BitangentNode, BitangentNode.VIEW );
+export const bitangentWorld = nodeImmutable( BitangentNode, BitangentNode.WORLD );
+export const transformedBitangentView = normalize( mul( cross( transformedNormalView, transformedTangentView ), tangentGeometry.w ) );
+export const transformedBitangentWorld = normalize( transformDirection( transformedBitangentView, cameraViewMatrix ) );
+
+export const modelViewMatrix = nodeImmutable( ModelNode, ModelNode.VIEW_MATRIX );
+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 modelViewPosition = nodeImmutable( ModelNode, ModelNode.VIEW_POSITION );
+
+export const positionGeometry = nodeImmutable( PositionNode, PositionNode.GEOMETRY );
+export const positionLocal = nodeImmutable( PositionNode, PositionNode.LOCAL );
+export const positionWorld = nodeImmutable( PositionNode, PositionNode.WORLD );
+export const positionView = nodeImmutable( PositionNode, PositionNode.VIEW );
+export const positionViewDirection = nodeImmutable( PositionNode, PositionNode.VIEW_DIRECTION );
+
+export const texture = nodeProxy( TextureNode );
+export const sampler = ( texture ) => nodeObject( new ConvertNode( texture.isNode === true ? texture : new TextureNode( texture ), 'sampler' ) );
+export const uv = ( ...params ) => nodeObject( new UVNode( ...params ) );
+export const pointUV = nodeImmutable( PointUVNode );
+
+// gpgpu
+
+export const compute = ( node, count, workgroupSize ) => nodeObject( new ComputeNode( nodeObject( node ), count, workgroupSize ) );
+
 // display
 // display
 
 
 export const frontFacing = nodeImmutable( FrontFacingNode );
 export const frontFacing = nodeImmutable( FrontFacingNode );
@@ -274,4 +291,4 @@ export const element = nodeProxy( ArrayElementNode );
 
 
 export const difference = ( a, b ) => nodeObject( abs( sub( a, b ) ) );
 export const difference = ( a, b ) => nodeObject( abs( sub( a, b ) ) );
 export const dotNV = clamp( dot( transformedNormalView, positionViewDirection ) );
 export const dotNV = clamp( dot( transformedNormalView, positionViewDirection ) );
-export const transformedNormalWorld = normalize( transformDirection( transformedNormalView, cameraViewMatrix ) );
+export const TBNViewMatrix = mat3( tangentView, bitangentView, normalView );

+ 11 - 0
examples/webgpu_loader_gltf.html

@@ -83,6 +83,17 @@
 					//const light = new THREE.PointLight( 0xffffff );
 					//const light = new THREE.PointLight( 0xffffff );
 					//camera.add( light );
 					//camera.add( light );
 
 
+					const mesh = gltf.scene.children[ 0 ];
+					const nodeMaterial = Nodes.NodeMaterial.fromMaterial( mesh.material );
+
+					nodeMaterial.normalNode = Nodes.normalMap( Nodes.texture( nodeMaterial.normalMap ) );
+					nodeMaterial.normalMap = null; // ignore non-node normalMap material
+
+					// optional: use tangent to compute normalMap
+					mesh.geometry.computeTangents();
+
+					mesh.material = nodeMaterial;
+
 					scene.add( gltf.scene );
 					scene.add( gltf.scene );
 
 
 					render();
 					render();