Three.js TSL BitcountNode:着色器位计数节点 countOneBits / countLeadingZeros / countTrailingZeros 详解
Three.js TSL BitcountNode着色器位计数节点 countOneBits / countLeadingZeros / countTrailingZeros 详解【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.jsBitcountNode 是 Three.js TSLThree Shading Language节点图中的一个数学节点用于在着色器中执行“位计数”运算统计数值中连续 0 位或 1 位的数量。本文将基于 API 文档 docs/pages/BitcountNode.html.md 与核心实现 src/nodes/math/BitcountNode.js完整覆盖其构造函数参数、类型体系、三大位计数算法的着色器级实现原理、单元测试的期望行为以及它在仓库内 SSGI 后处理中的真实用法。什么是 BitcountNode继承关系与 API 骨架按 API 文档的描述BitcountNode 表示一个对一段着色器数据执行位计数运算的节点This node represents an operation that counts the bits of a piece of shader data。其继承链为EventDispatcher → Node → TempNode → MathNode → BitcountNode即它最终派生自 src/nodes/math/MathNode.js 中的MathNode属于 TSL 数学节点家族的一部分并通过 src/nodes/Nodes.js 统一导出export { default as BitcountNode } from ./math/BitcountNode.js。构造函数文档定义的构造签名为new BitcountNode( method : countTrailingZeros | countLeadingZeros | countOneBits, aNode : Node )参数含义与源码 src/nodes/math/BitcountNode.js#L26-L39 完全一致method方法名只能是三个字符串常量之一countTrailingZeros统计从最低有效位LSB起连续 0 位的个数即最低 1 位的下标countLeadingZeros统计从最高有效位MSB起连续 0 位的个数countOneBits统计数值中置位为 1的位总数即 popcount。aNode第一个也是唯一的输入节点即被计数的值。三个字符串常量在类上以静态 getter 形式暴露src/nodes/math/BitcountNode.js#L397-L413可作为类型安全的方式引用BitcountNode.COUNT_TRAILING_ZEROS // countTrailingZeros BitcountNode.COUNT_LEADING_ZEROS // countLeadingZeros BitcountNode.COUNT_ONE_BITS // countOneBits类型标识属性.isBitcountNode : booleanreadonly文档标注的默认值为true用于类型测试。源码中在构造函数内通过this.isBitcountNode true赋值src/nodes/math/BitcountNode.js#L37与 TSL 中其他节点的isXxxNode惯例一致方便在节点图遍历中做instanceof之外的轻量类型判断。三个 TSL 入口函数countTrailingZeros / countLeadingZeros / countOneBits实际开发中几乎不会直接new BitcountNode(...)而是使用文件末尾通过nodeProxyIntent派生出的三个 TSL 函数src/nodes/math/BitcountNode.js#L419-L453它们都调用setParameterLength( 1 )即只接受一个输入参数import { countOneBits, countLeadingZeros, countTrailingZeros, uint } from three/tsl; // 统计 0xF011110000b中为 1 的位结果为 4 const ones countOneBits( uint( 0xF0 ) ); // 统计 81000b的尾随零结果为 3 const tz countTrailingZeros( uint( 8 ) ); // 统计 0xF0 的前导零结果为 24 const lz countLeadingZeros( uint( 0xF0 ) );三个函数的官方 JSDoc 语义与 docs/TSL.md 所属的 TSL 文档体系一致函数语义返回类型countTrailingZeros( x )输入值从最低有效位起连续 0 位的数量等价于最低 1 位的下标与输入同类型countLeadingZeros( x )输入值从最高有效位起连续 0 位的数量与输入同类型countOneBits( x )输入值中置位1的位总数与输入同类型需要特别注意源码 JSDoc 中标注这三个函数Can only be used with WebGPURenderer and a WebGPU backend面向WebGPURenderer与 WebGPU 后端设计。但从setup()的源码结构看节点在非 WebGPU 后端上并没有直接失败而是动态生成等价的 GLSL 函数来仿真这三个运算见下文这一回退路径在测试文件的注释中也被明确称为 WebGL polyfilltest/unit/addons/tsl/TSLBitOps.tests.js#L68-L70。因此可以推断WebGPU 路径使用硬件内建函数WebGL 路径使用运行时生成的位运算仿真。setup() 运行时生成机制函数注册缓存与按类型分发位计数节点的编译逻辑集中在setup( builder )方法中src/nodes/math/BitcountNode.js#L321-L395。其工作流程可拆解为四步后端分流if ( renderer.backend.isWebGPUBackend )命中时直接return super.setup( builder )把运算交给 WGSL 内建函数count_ones、count_one_bits等不生成任何额外着色器代码src/nodes/math/BitcountNode.js#L327-L333。推导输入类型通过 builder 获取inputType完整向量类型、elementType标量元素类型如int/uint与typeLength分量数。两级函数注册缓存模块级对象registeredBitcountFunctions以两种 key 缓存已生成的函数避免重复生成基础分量函数${method}_base_${elementType}例如countOneBits_base_uint、countOneBits_base_int由_createOneBitsBaseLayout/_createLeadingZerosBaseLayout/_createTrailingZerosBaseLayout分别创建面向完整输入类型的主函数${method}_${inputType}例如countOneBits_uvec3由_createMainLayout创建。 也就是说同一method会按元素类型生成基础实现再按向量长度生成向量包装缓存后跨节点复用。输出封装最终生成一个Fn包裹层调用主函数处理aNode并返回结果src/nodes/math/BitcountNode.js#L385-L393。输入/输出类型矩阵_returnDataNode( inputType )src/nodes/math/BitcountNode.js#L63-L117实现了“同类型进、同类型出”的映射输入类型输出类型uint/intuint/intuvec2/uvec3/uvec4uvec2/uvec3/uvec4ivec2/ivec3/ivec4ivec2/ivec3/ivec4向量输入走分量级处理_createMainLayoutsrc/nodes/math/BitcountNode.js#L282-L319在typeLength 1时直接返回标量基础函数结果否则依次对x、y、z、w分量调用基础函数并组装回向量——即位计数对向量的语义是逐分量component-wise的。对于int输入_resolveElementTypesrc/nodes/math/BitcountNode.js#L49-L61会先通过bitcast( inputNode, uint )把有符号整数按位重解释为无符号整数再计数避免符号位干扰移位运算uint输入则原样直通。三大 GLSL 位计数算法的源码解读WebGL 回退路径非 WebGPU 后端下节点会为每种元素类型动态生成一个Fn着色器函数其算法实现是理解位计数节点最有价值的部分。countOneBits经典 popcount 位压缩_createOneBitsBaseLayoutsrc/nodes/math/BitcountNode.js#L242-L269实现了教科书式的 32 位 popcount分三步把 32 个比特的 1 逐级压缩到低 8 位// 第 1 步相邻 2 位分组求和掩码 0101… v.assign( v.sub( v.shiftRight( uint( 1 ) ).bitAnd( uint( 0x55555555 ) ) ) ); // 第 2 步4 位一组求和掩码 0011… v.assign( v.bitAnd( uint( 0x33333333 ) ).add( v.shiftRight( uint( 2 ) ).bitAnd( uint( 0x33333333 ) ) ) ); // 第 3 步8 位一组求和后乘 0x1010101 把低字节和提升到最高字节右移 24 位取出 const numBits v.add( v.shiftRight( uint( 4 ) ) ).bitAnd( uint( 0xF0F0F0F ) ) .mul( uint( 0x1010101 ) ).shiftRight( uint( 24 ) );三步之后最高字节即为 0–32 的置位数整段运算只有加减、移位和位与无任何分支适合 GPU 并行执行。countLeadingZeros16/8/4/2/1 二分式扫描_createLeadingZerosBaseLayoutsrc/nodes/math/BitcountNode.js#L170-L232采用经典的“对半排除”法用一个累加器n和一个工作副本v// 输入为 0 时约定返回 32 If( value.equal( uint( 0 ) ), () { return uint( 32 ); } ); If( v.shiftRight( 16 ).equal( 0 ) ) { n.addAssign( 16 ); v.shiftLeftAssign( 16 ); } If( v.shiftRight( 24 ).equal( 0 ) ) { n.addAssign( 8 ); v.shiftLeftAssign( 8 ); } If( v.shiftRight( 28 ).equal( 0 ) ) { n.addAssign( 4 ); v.shiftLeftAssign( 4 ); } If( v.shiftRight( 30 ).equal( 0 ) ) { n.addAssign( 2 ); v.shiftLeftAssign( 2 ); } If( v.shiftRight( 31 ).equal( 0 ) ) { n.addAssign( 1 ); }含义是若右移 16 位后为 0说明高位 16 位全零累加 16 位并把v左移 16 位重新对齐随后以 8、4、2、1 位粒度重复该判断。最坏 5 次比较即可定位前导零个数时间复杂度与位宽对数级相当。countTrailingZeros借浮点指数域直接读出 2 的幂次_createTrailingZerosBaseLayoutsrc/nodes/math/BitcountNode.js#L127-L160是最巧妙的一段它利用v -v只保留最低置位这一性质再把结果借道float的指数域读出来If( value.equal( uint( 0 ) ), () { return uint( 32 ); } ); const f float( v.bitAnd( negate( v ) ) ); // 最低置位对应的 2 的幂 const uintBits floatBitsToUint( f ); // 按位重解释为 uint const numTrailingZeros ( uintBits.shiftRight( 23 ) ).sub( 127 ); // 取指数域 - 127原理若最低置位在第k位则v -v等于2^k而 IEEE-754 单精度浮点2^k的位模式高 8 位正是偏置指数k 127因此右移 23 位再减 127 就得到k。这里依赖的floatBitsToUint是 src/nodes/math/BitcastNode.js#L136 中定义的BitcastNode便捷封装new BitcastNode( value, uint, float )即纯粹的位模式重解释不改变任何位。零值的统一约定两个“零计数”函数对 0 输入都显式特判返回uint( 32 )前导零32 位全零即 32 个前导零尾随零无最低置位时约定为 32。这一约定在单元测试中被明确验证见下节使用这些函数时必须牢记零输入不是“未定义行为”而是固定返回 32。单元测试用独立参照系验证 GPU 行为仓库中的 GPU 端测试 test/unit/addons/tsl/TSLBitOps.tests.js 为三个函数给出了可复现的期望值且每个参照都刻意独立于被测节点本身import { uint, countLeadingZeros, countOneBits, countTrailingZeros } from three/tsl;countOneBits与一段纯 JS 手写 popcount 循环对比——countOneBits( uint( 0 ) )期望 0countOneBits( uint( 0xF0 ) )期望 4countOneBits( uint( 0xFFFFFFFF ) )期望 32test/unit/addons/tsl/TSLBitOps.tests.js#L31-L47countLeadingZeros以 JS 内建的Math.clz32()为参照——countLeadingZeros( uint( 0 ) )期望 32、( uint( 1 ) )期望 31、( uint( 0xF0 ) )期望 24、( uint( 0xFFFFFFFF ) )期望 0test/unit/addons/tsl/TSLBitOps.tests.js#L49-L61countTrailingZeros与手写尾随零循环对比——countTrailingZeros( uint( 0 ) )期望 32、( uint( 8 ) )期望 3、( uint( 0xF0 ) )期望 4、( uint( 1 ) )期望 0test/unit/addons/tsl/TSLBitOps.tests.js#L63-L85。这些用例同时验证了两类事实算法数值正确性以及“0 输入返回 32”的边界约定在真实 GPU 上成立。仓库中的真实应用SSGI 的遮挡位域统计BitcountNode 并不是一个孤立的实验性节点examples/jsm/tsl/display/SSGINode.jsSSGI 屏幕空间全局光照 TSL 实现就依赖countOneBits完成核心逻辑。SSGI 用一个 32 位位域globalOccludedBitfield记录某个切片内哪些角度区间已被遮挡每一位代表一条采样射线对应的方位角区间// 新增被遮挡区间后统计本次新遮挡了多少个区间 globalOccludedBitfield.assign( globalOccludedBitfield.bitOr( currentOccludedBitfield ) ); const numOccludedZones countOneBits( currentOccludedBitfield ); // L521 // 环境光遮蔽度 被遮挡区间数 / 总采样数 ao.addAssign( float( countOneBits( globalOccludedBitfield ) ).div( float( MAX_RAY ) ) ); // L626见 examples/jsm/tsl/display/SSGINode.js#L521 与 examples/jsm/tsl/display/SSGINode.js#L626这个案例很好地展示了countOneBits的典型工程价值GPU 上统计“32 位中有多少个 1”如果靠循环逐位判断代价高昂而位压缩 popcount 只有常数次算术/位运算且对 32 条采样的遮挡状态是逐像素并行计算的。类似的countTrailingZeros/countLeadingZeros在 GPU 端可用于确定位索引、构造前缀和、位域编码/解码等场景。使用要点小结入口优先使用three/tsl导出的countOneBits/countLeadingZeros/countTrailingZeros函数new BitcountNode( method, aNode )与其等价method 取值可引用静态常量BitcountNode.COUNT_ONE_BITS等src/nodes/math/BitcountNode.js#L397-L413。类型输入支持uint/int及 2/3/4 分量向量输出与输入同类型向量语义为逐分量int输入内部先bitcast为uint再计数。后端行为WebGPU 后端下编译为 WGSL 内建函数零额外代码生成其他后端由节点按${method}_${type}缓存键动态生成 GLSL 仿真函数其中countTrailingZeros借助floatBitsToUintsrc/nodes/math/BitcastNode.js的浮点指数域技巧实现。边界约定countLeadingZeros(0)与countTrailingZeros(0)均约定返回 32。类型测试.isBitcountNode truereadonly可用于节点图中的类型判断。参考文档docs/pages/BitcountNode.html.md核心实现src/nodes/math/BitcountNode.js行为验证test/unit/addons/tsl/TSLBitOps.tests.js真实用例examples/jsm/tsl/display/SSGINode.js。【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考