three.js TSL SetNode向量组件回写操作 setXYZW / setRGBA / setSTPQ 的底层节点实现【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js本文围绕 three.js 官方文档中SetNode的 API 说明展开并结合 src/nodes/utils/SetNode.js 与 src/nodes/tsl/TSLCore.js 的源码讲清SetNode在 TSLThree Shading Language节点系统中的角色它如何支撑你在节点对象上调用setR()、setRGB()、setSTPQ()等组件回写方法以及在 Shader 生成阶段如何拼装出「仅替换指定分量、其余分量保持不变」的 GLSL 构造表达式。读完本文你能掌握SetNode的构造参数、三个实例属性、generateNodeType()/generate()的实现细节以及 TSL 中xyzw / rgba / stpq三套 swizzle 命名是如何统一映射到该节点的。定位与继承链SetNode属于 TSL 核心工具节点位于src/nodes/utils/下官方文档明确说明它通常不用于应用层代码而是用来实现节点对象上所有setXYZW()、setRGBA()和setSTPQ()方法调用的底层机制。从 docs/pages/SetNode.html.md 给出的继承关系看EventDispatcher → Node → TempNode → SetNode即SetNode继承自TempNode源码位于 src/nodes/core/TempNode.js。TempNode的核心能力是缓存管理当同一个节点在一次构建中被多处引用时它会把计算结果写入一个临时变量避免重复求值。在 src/nodes/core/TempNode.js 的build()中可以看到当构建阶段为generate且hasDependencies( builder )即usageCount 1时会为该节点分配临时变量并把结果行加入代码流。这意味着作为TempNode子类的SetNode其结果在被多次引用时同样会自动落入临时变量这对后面「回写后的向量被多处复用」的场景很关键。使用方式set 系列方法的统一入口在 TSL 代码里你几乎不会手动new SetNode(...)而是直接对任意节点调用setXxx()方法。文档给出的标准示例是materialLine.colorNode color( 0, 0, 0 ).setR( float( 1 ) );含义很直接在颜色vec3( 0, 0, 0 )的基础上把红色分量替换为float( 1 )等价于生成vec3( 1, 0, 0 )。这些方法并不是逐个手写的而是在 src/nodes/tsl/TSLCore.js 中通过setProtoSwizzle()动态挂到Node.prototype上// src/nodes/tsl/TSLCore.js节选 const parseSwizzle ( props ) props.replace( /r|s/g, x ).replace( /g|t/g, y ).replace( /b|p/g, z ).replace( /a|q/g, w ); const parseSwizzleAndSort ( props ) parseSwizzle( props ).split( ).sort().join( ); // ... Node.prototype[ set propUpper ] Node.prototype[ set altAUpper ] Node.prototype[ set altBUpper ] function ( value ) { const swizzle parseSwizzleAndSort( property ); return new SetNode( this, swizzle, nodeObject( value ) ); };注册过程由 src/nodes/tsl/TSLCore.js 中的四层嵌套循环完成覆盖 1 到 4 个分量的所有组合三套命名xyzw、rgba、stp q一一对应。因此以下写法全部可用最终都创建同一个SetNode单分量setX()/setR()/setS()双分量setXY()/setRG()/setST()三分量setXYZ()/setRGB()/setSTP()四分量setXYZW()/setRGBA()/setSTPQ()两个关键处理命名统一parseSwizzle把r/g/b/a与s/t/p/q全部归一化为x/y/z/wSetNode内部只认xyzw一套分量名排序parseSwizzleAndSort会把分量排序后再传入例如setGR()最终components为gr → x,y 排序后为 xy。结合下面generate()的实现这也解释了为什么components应当是连续且有序的分量串——源码按「命中首分量后连续跳过分量数」的方式消费它。另外注意nodeObject( value )传入的值会被自动转换为节点数字、常量、普通节点均被接受这是 TSL 的通用参数包装行为见 src/nodes/tsl/TSLCore.js 的ShaderNodeObject。构造函数与实例属性按照 docs/pages/SetNode.html.md 的 API 定义SetNode的构造签名为new SetNode( sourceNode : Node, components : string, targetNode : Node )对应 src/nodes/utils/SetNode.js 的实现三个实例属性含义如下属性类型含义sourceNodeNode被更新的节点即setXxx()调用所在的节点输出的整体类型由它推断componentsstring需要被替换的分量串如x、st、xyzw经 swizzle 解析后统一为xyzw子串targetNodeNode写入新值所用的节点value node其类型决定components对应分量的写入值类型静态属性SetNode.type固定返回字符串SetNode用于节点类型标识src/nodes/utils/SetNode.js。generateNodeType结果类型继承自 sourceNode文档中特别指出generateNodeType( builder )被覆写因为节点类型是从sourceNode推断的。源码印证了这一点// src/nodes/utils/SetNode.js generateNodeType( builder ) { return this.sourceNode.getNodeType( builder ); }也就是说SetNode自身不声明类型vec3节点调用setR()后仍是vec3vec4节点调用setST()后仍是vec4。这与直觉一致「只替换部分分量」不改变向量维度。这也是它覆写父类TempNode#generateNodeType的原因见 TempNode.html。generate()如何拼出「部分分量替换」的 GLSLgenerate( builder )是SetNode生成 Shader 片段的核心完整逻辑见 src/nodes/utils/SetNode.js。可以分四步理解第一步确定三种类型const sourceType this.getNodeType( builder ); const componentType builder.getComponentType( targetNode.getNodeType( builder ) ); const targetType builder.getTypeFromLength( components.length, componentType );sourceTypesourceNode的类型如vec3componentTypetargetNode类型对应的标量分量类型如floattargetType按components.length组装出的目标类型例如对vec3执行setXY( vec2 )时targetType就是vec2——即写入值本身可以是向量分量个数必须与components长度匹配。第二步构建两个片段const targetSnippet targetNode.build( builder, targetType ); const sourceSnippet sourceNode.build( builder, sourceType );注意sourceNode是以完整类型构建的后续要逐个分量拆出保留项。第三步逐分量遍历、条件替换const length builder.getTypeLength( sourceType ); const snippetValues []; for ( let i 0; i length; i ) { const component vectorComponents[ i ]; if ( component components[ 0 ] ) { snippetValues.push( targetSnippet ); i components.length - 1; } else { snippetValues.push( sourceSnippet . component ); } }vectorComponents常量定义在 src/nodes/core/constants.js[ x, y, z, w ]。循环按x, y, z, w顺序遍历原向量的每个分量当遍历到components的首个分量时把整个targetSnippet写入该位置并把下标一次性跳过分量个数i components.length - 1其余位置则保留sourceSnippet.x这类原分量引用。这里再次体现了components必须「连续、有序」的假设——跳步逻辑只对这种形态的 swizzle 串成立。第四步按源类型重新构造return ${ builder.getType( sourceType ) }( ${ snippetValues.join( , ) } );最终以源类型作为构造函数输出。以color( 0, 0, 0 ).setR( float( 1 ) )为例sourceType为vec3、components为x生成逻辑等价于vec3( 1, src.y, src.z )形式的表达式而vec4节点执行setST( vec2( 0.5, 0.5 ) )则等价于vec4( x, y, v.x, v.y, src.z, src.w )式的分量重组其中v为targetNode构建出的片段。小结从 API 到 Shader 的完整链路把文档描述与源码串联起来一条setXxx()调用的完整链路是应用代码node.setR( value )命中TSLCore动态注册的 prototype 方法src/nodes/tsl/TSLCore.jsswizzle 串经解析排序后创建new SetNode( this, x, nodeObject( value ) )构建期generateNodeType()继承sourceNode类型保证结果维度不变src/nodes/utils/SetNode.js;generate()按源向量分量顺序拼装「替换目标片段 保留原分量」的构造表达式并按TempNode的缓存策略在多引用场景下落入临时变量src/nodes/core/TempNode.js。如果你想在自己的节点类中扩展类似「局部修改」的操作直接参照 src/nodes/utils/SetNode.js 与同目录下的FlipNode、SplitNode、JoinNode等工具节点即可SplitNode恰好是SetNode的逆向操作——前者读分量后者写分量两者共同构成 TSL 中向量分量访问的对称设计。相关文档页面可继续参考 docs/pages/TempNode.html.md 与 docs/pages/SetNode.html.md。【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
