three.js Uniform 全面解析:ShaderMaterial 全局着色器变量的声明、更新与底层实现
three.js Uniform 全面解析ShaderMaterial 全局着色器变量的声明、更新与底层实现【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js本篇指南围绕 three.js 的Uniform类展开系统讲解全局着色器变量这一概念的声明方式、构造函数与全部属性方法、在ShaderMaterial中的两种写法以及在渲染循环中动态更新 uniform 的标准流程。文章还结合仓库源码src/core/Uniform.js、src/core/UniformsGroup.js、src/renderers/common/Uniform.js与真实示例examples/webgl_shader.html深入剖析 uniform 在 WebGLRenderer / WebGPURenderer 两套渲染管线下的底层差异帮助读者从会写进阶到懂原理。Uniform 是什么着色器的全局变量通道在 three.js 中Uniform表示一个全局着色器变量global shader variable。它由 CPU 侧的 JavaScript 应用声明并赋值在每次渲染时被传递给 GPU 上的 shader program供顶点着色器与片元着色器读取。从源码注释与官方文档docs/pages/Uniform.html.md看Uniform有两个关键使用约束它只能在ShaderMaterial的上下文中使用即通过material.uniforms声明因此它只支持WebGLRenderer。这一点在 src/core/Uniform.js 的类注释中明确写明若改用 WebGPU 渲染管线则要走 WebGPU 语义下的另一套 uniform 封装详见后文WebGPU 与 TSL 体系的演进。最小代码示例import * as THREE from three; const uniforms { time: { value: 1.0 }, resolution: new THREE.Uniform( new THREE.Vector2() ) };上例演示了ShaderMaterial声明 uniform 时支持的两种等价写法写法示例说明按值字面量对象time: { value: 1.0 }直接提供{ value: ... }结构是最常见、最简洁的写法按对象显式实例resolution: new Uniform( new Vector2() )通过new Uniform( value )构造实例语义更明确便于后续类型判断与复用两种写法最终都会被 three.js 内部的统一机制处理成相同的上传路径见 src/renderers/webgl/WebGLUniforms.js 的upload/seqWithValue逻辑渲染器遍历 material 上的 uniforms 字典读取每个条目对应的.value并上传给着色器。如果 uniforms 内部各属性通过字面量对象书写渲染器会自动为其创建对应的Uniform包装。构造函数与核心属性构造函数new Uniform( value : any )value参数即 uniform 的取值可以是任意 JavaScript 值——数字、向量、矩阵、颜色、纹理等。构造函数的核心实现非常精简见 src/core/Uniform.jsconstructor( value ) { this.value value; }也就是说Uniform本质上是把一个任意类型的值包装进一个带.value属性的容器对象中。属性表属性类型含义.valueanyuniform 的值。文档中出现两次同名条目指同一个属性可在声明时赋初值也可在运行时修改.boundarynumber用于按STD140 布局构建 uniform buffer派生数据类型的uniform 会将该属性设为与数据类型对应的字节边界值.itemSizenumber条目尺寸分量数派生 uniform 会按数据类型设置.indexnumber由UniformsGroup设置标记该 uniform 在 uniform 数组中的索引位置.offsetnumber由UniformsGroup设置标记该 uniform 在 uniform buffer 中的起始位置.namestringuniform 的名称在 WebGPU 体系下存在WebGL 体系中 WebGLUniforms 以 name 作键管理需要强调的是.boundary、.itemSize、.index、.offset这四项不是由core/Uniform.js中的类本身在构造时写入的。仓库中存在两套 Uniform 实现src/core/Uniform.js文档所描述的面向ShaderMaterial/WebGLRenderer的类仅含value与clone()src/renderers/common/Uniform.jsWebGPU/通用渲染器使用的抽象基类boundary默认 0、itemSize默认 0、offset默认 0、index默认 -1、name都在其构造函数中被初始化。因此可以推断文档中关于.boundary/.itemSize/.index/.offset的描述实际对应渲染器内部WebGPU 路径派生 uniform 类型的布局计算体系例如NumberUniform的boundary 4, itemSize 1Matrix4Uniform的boundary 64, itemSize 16见 src/renderers/common/Uniform.js 与 L364-L366。这些布局常量正是 WebGL2 UBO / WebGPU uniform buffer 依据 STD140 规则做内存对齐的关键。方法与操作.clone() : Uniform返回一个新Uniform值从当前实例复制而来。若值本身带有clone()方法则该值也会被一并深拷贝。对应源码见 src/core/Uniform.jsclone() { return new Uniform( this.value.clone undefined ? this.value : this.value.clone() ); }这一行为可被单元测试验证test/unit/src/core/Uniform.tests.js 分别测试了简单值数字23与复杂值new Vector3(1,2,3)的克隆结果简单值直接共享原值Vector3 等带clone()的值则得到内容相等的新对象。.setValue( value : any )设置 uniform 的值等价于uniform.value value。该方法定义在 WebGPU/通用路径的基类 src/renderers/common/Uniform.js 中setValue( value ) { this.value value; }.getValue() : any返回 uniform 的当前值getValue() { return this.value; }在 WebGL 路径下core/Uniform.js类本身没有显式定义setValue/getValue渲染器直接读写.value属性即可。可以理解为.value才是整个 uniform 体系的数据中枢方法只是便捷封装。在渲染循环中动态更新 uniform一个可运行示例uniform 的典型使用场景是每帧变化的时间、分辨率、相机参数等。参考仓库示例 examples/webgl_shader.html完整流程如下第一步声明 uniforms 字典并创建 ShaderMaterialconst uniforms { time: { value: 1.0 } }; const material new THREE.ShaderMaterial( { uniforms: uniforms, vertexShader: document.getElementById( vertexShader ).textContent, fragmentShader: document.getElementById( fragmentShader ).textContent } ); const mesh new THREE.Mesh( geometry, material ); scene.add( mesh );示例片段位于 examples/webgl_shader.html#L99-L112。也可以在渲染器与几何体准备完毕后通过new THREE.Uniform( value )方式显式构造uniforms: { time: { value: 1.0 }, resolution: new THREE.Uniform( new THREE.Vector2( window.innerWidth, window.innerHeight ) ) }第二步在 animate 循环中写入新值function animate() { uniforms[ time ].value performance.now() / 1000; renderer.render( scene, camera ); }见 examples/webgl_shader.html#L132-L137。这里直接在animate中更新uniforms[time].valuethree.js 会在每次渲染前自动把发生过变化的 uniform 值同步到 GPU无需手动调用任何上传 API。第三步在着色器 GLSL 代码中读取uniform float time; void main() { // 使用 time 驱动颜色 / 顶点位置的动画…… }uniform 的名字必须在 GLSL 中与 JS 声明的 key 一一对应否则该 uniform 会被标记为optional而跳过上传WebGLUniforms 的setOptional机制。实用建议尽量减少每帧新建对象将 Vector3/Color/Matrix4 等作为模块级或外层对象缓存每帧只改动其内部分量可减少垃圾回收压力名字保持唯一且一致避免在多个 material 间混用同名不同意的 uniform 造成排查困难注意值类型匹配JS 侧使用数字GLSL 侧应为float/uniform float使用THREE.Color对应vec3THREE.Matrix4对应mat4three.js 会按类型选择正确的上传函数如setValueV1f、setValueV3f、setValueV4f见 src/renderers/webgl/WebGLUniforms.js。UniformsGroup将多个 uniform 打包为统一缓冲与Uniform紧密相关的是UniformsGroupsrc/core/UniformsGroup.js。它继承自EventDispatcher用于把多个 uniform 聚合为一组渲染器可将整组作为单个 UBOUniform Buffer Object上传减少 draw call 与状态切换开销。import { UniformsGroup, Uniform } from three; const group new UniformsGroup(); group.setName( MyGroup ); group.add( new Uniform( 1.0 ) ); group.add( new Uniform( new THREE.Vector3() ) );其主要 API 包括add( uniform )、remove( uniform )、setName( name )、setUsage( value )默认StaticDrawUsage可配 Dynamic/Stream 等 GL 用法常量、dispose()派发dispose事件释放 GPU 资源以及copy(source)/clone()见 src/core/UniformsGroup.js。从源码结构看UniformsGroup在渲染器内部负责为组内每个 uniform 计算offset与index——这正是前文属性表中.offset/.index注释由 UniformsGroup 设置的来源。当场景使用大量共享 uniform 的参数如骨骼动画矩阵、通用材质参数时用UniformsGroup聚合是 WebGLRenderer 下较为高效的编程方式。WebGPU 与 TSL 体系的演进另一套 Uniform 栈前文提到ShaderMaterial Uniform仅面向 WebGL。在 WebGPU 渲染管线中仓库维护了一套独立但同名的通用层实现src/renderers/common/Uniform.js抽象基类与按类型派生的NumberUniform、Vector2Uniform、Vector3Uniform、Vector4Uniform、ColorUniform、Matrix2Uniform、Matrix3Uniform、Matrix4Uniform各自预置符合 STD140 对齐规则的boundary与itemSizesrc/renderers/common/UniformBuffer.js与src/renderers/common/UniformsGroup.js管理 uniform 数据的连续内存缓冲节点体系TSL方面src/nodes/core/UniformNode.js将 uniform 提升为可编程节点配合src/nodes/core/UniformGroupNode.js控制其在 uniform buffer 中的分组归属供WebGPURenderer使用。因此当开发者需要同时兼容 WebGL 与 WebGPU或使用 three.js 的 TSLThree Shading Language编程式着色方案时应优先选择对应 WebGPU 体系的 uniform 抽象而不是文档中所述仅限 WebGL 的core/Uniform。从代码结构判断仓库正逐步将 uniform 逻辑收敛到src/renderers/common/通用层而src/core/Uniform.js保持向后兼容的轻量职责。小结Uniform是 three.js 中沟通 CPU 与 GPU 的一座桥用uniforms { name: { value: v } }或new THREE.Uniform( value )声明全局着色器变量并配合ShaderMaterial使用仅 WebGLRenderer 支持通过.value的读写或setValue/getValue封装在渲染循环中驱动动画深入底层时需区分 WebGL 的core/Uniform与 WebGPU 的renderers/common/Uniform二者的boundary/itemSize/offset/index等布局属性服务于 STD140 对齐的 uniform buffer 构建大批量共享参数可借助UniformsGroup打包为 UBO 提升性能。围绕这一机制可以继续研读仓库中对应的 ShaderMaterial 文档、UniformsGroup 文档以及单元测试 test/unit/src/core/Uniform.tests.js 与 test/unit/src/core/UniformsGroup.tests.js 深化理解。【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考