three.js Vector4 四维向量完全解析:API 用法、源码原理与渲染管线应用
【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js
本文基于 three.js 仓库中的官方 API 参考文档 Vector4 文档页 展开,并结合 Vector4 源码 与 Vector4 单元测试 进行源码级验证。
Vector4是 three.js 数学核心库中表示四维向量的类。与三维向量Vector3相比,Vector4多出的w分量使其既能承载齐次坐标(在Matrix4变换下保留透视语义),也能在渲染管线中表达矩形视口与裁剪区域((x, y, width, height))。无论你在做自定义着色器后处理、解析缓冲几何数据,还是理解 WebGL 渲染器的视口管理,Vector4都是绕不开的基础工具。
读完本文,你将完整掌握Vector4的构造函数、全部公开方法、每个方法的底层公式与边界行为,并通过单元测试与渲染器调用链看清它在 three.js 内部的真实用法。
一、什么是 4D 向量:Vector4的定位与典型用途
Vector4表示一个有序四元组数字(x, y, z, w)。在 three.js 中它最常被用来表达以下三种含义:
- 4D 空间中的点:具有四个独立坐标的点;
- 4D 空间中的方向与长度:此时“长度”指从原点
(0, 0, 0, 0)到(x, y, z, w)的欧几里得直线距离,方向同样以原点为参照; - 任意有序四元组:例如视口/裁剪矩形
(x, y, width, height),或 RGBA 颜色分量。
值得特别注意的是,w分量在图形学中的经典角色是齐次坐标。three.js 为构造函数的w提供了与其他分量不同的默认值1(而x/y/z默认为0),这一设计正是为了与齐次坐标的惯例保持一致——这一点从 构造函数实现 可以直观确认。
另外,从源码可见Vector4实现了迭代器协议,Symbol.iterator 依次产出x, y, z, w,因此你可以用for...of、数组解构等方式直接遍历向量分量:
const v = new THREE.Vector4( 1, 2, 3, 4 ); for ( const c of v ) console.log( c ); // 依次输出 1 2 3 4 const [ x, y, z, w ] = v; // 解构出四个分量二、构造与类型标识
构造函数签名
new Vector4( x : number, y : number, z : number, w : number )
各参数说明如下:
| 参数 | 含义 | 默认值 |
|---|---|---|
x | 向量的 x 分量 | 0 |
y | 向量的 y 分量 | 0 |
z | 向量的 z 分量 | 0 |
w | 向量的 w 分量 | 1 |
参数全部可选。根据 单元测试,new Vector4()会得到(0, 0, 0, 1),而传入四个常数后四个分量一一对应相等。
官方代码示例:
const a = new THREE.Vector4( 0, 1, 0, 0 ); // 不传参数时将被初始化为 (0, 0, 0, 1) const b = new THREE.Vector4(); const d = a.dot( b );类型标识isVector4
在类的静态初始化块中,three.js 会为原型设置isVector4 = true(见 Vector4.js)。这是一个贯穿整个 three.js 的类型标记惯例:渲染器在很多地方并不依赖instanceof,而是通过类似x.isVector4、v.isVector3的鸭子类型判断来决定处理分支。
例如在 WebGLRenderer 的视口设置方法 中,就存在if ( x.isVector4 ) { ... }的分支:传入参数若是Vector4,则直接按(x, y, z, w)解析为视口的(x, y, width, height)。这一点下文会继续展开。
三、分量读写:别名、索引与解构
常规分量属性
.x、.y、.z、.w分别是四个分量的直接属性。
width/height别名
.width:z的别名(访问器 getter/setter);.height:w的别名。
两者的实现见 Vector4.js。这意味着当你把Vector4用作视口或矩形时,既可以用v.z/v.w,也可以用语义更清晰的v.width/v.height读写同一块存储:
const vp = new THREE.Vector4( 0, 0, 800, 600 ); console.log( vp.z ); // 800 console.log( vp.width ); // 800,等价于 vp.z vp.height = 720; // 等价于 vp.w = 720组件索引读写
.getComponent( index : number ) : number:返回指定索引的分量,0→x、1→y、2→z、3→w;.setComponent( index : number, value : number ) : Vector4:按索引写入分量。
注意索引越界时会抛出异常。源码中getComponent/setComponent的default分支统一抛出'THREE.Vector4: index is out of range: ' + index(见 Vector4.js)。
单项 setter
.setX( x )、.setY( y )、.setZ( z )、.setW( w )分别只更新对应分量,全部返回this,便于链式调用。
四、构造式的赋值方法
.set( x, y, z, w ) : Vector4
一次性设置全部四个分量,返回自身。
.setScalar( scalar ) : Vector4
把四个分量全部设为同一个标量值。常用于构造四维“常量向量”,例如给四通道数据赋初值。
.copy( v : Vector3 | Vector4 ) : Vector4
从另一个向量拷贝分量。关键细节:该方法允许传入Vector3,当v.w为undefined时自动补写1(见 Vector4.js)。因此把一个三维向量拷贝进四维向量时,w会被安全初始化为齐次坐标所需的1,不会出现NaN。对应的 copy 单元测试 还验证了它是“深拷贝”——修改源向量不会影响目标。
.clone() : Vector4
返回一个复制当前分量的全新Vector4实例。
五、向量与标量运算
Vector4的运算方法与Vector3保持一致,分为两类:in-place 修改自身(多数方法)与把两个操作数的结果写入自身(addVectors/subVectors/lerpVectors等)。所有方法统一返回this,方便链式调用。
加法
| 方法 | 行为 | 源码位置 |
|---|---|---|
.add( v : Vector4 ) : Vector4 | 逐分量加上v | Vector4.js |
.addScalar( s : number ) : Vector4 | 每个分量都加s | Vector4.js |
.addScaledVector( v : Vector4, s : number ) : Vector4 | 每个分量加上v对应分量与s的积(即this += v * s) | Vector4.js |
.addVectors( a : Vector4, b : Vector4 ) : Vector4 | 计算a + b并写入自身 | Vector4.js |
其中addScaledVector是物理积分、重心迭代等算法中最常用的原子操作之一,因为它只用一次方法调用就完成了“缩放 + 累加”,且不产生临时对象。
减法
| 方法 | 行为 | 源码位置 |
|---|---|---|
.sub( v : Vector4 ) : Vector4 | 逐分量减去v | Vector4.js |
.subScalar( s : number ) : Vector4 | 每个分量都减s | Vector4.js |
.subVectors( a : Vector4, b : Vector4 ) : Vector4 | 计算a - b并写入自身 | Vector4.js |
减法在图形学中最直接的语义是求方向:B.subVectors(end, start)得到从start指向end的向量。
乘法与除法
.multiply( v : Vector4 ) : Vector4:与向量逐分量相乘(Hadamard 积)。.multiplyScalar( scalar ) : Vector4:所有分量乘以同一标量。.divide( v : Vector4 ) : Vector4:逐分量除以v的对应分量。.divideScalar( scalar : number ) : Vector4:所有分量除以同一标量。
关于.divideScalar有一个值得指出的实现细节:它并非直接做除法,而是委托给this.multiplyScalar( 1 / scalar )(见 Vector4.js)。也就是说,它不包含除零保护,传入0会产生Infinity/NaN。真正需要“安全归一化”的场景应改用.normalize()——后者内置了零长度保护。
六、矩阵变换与四元数轴角:Vector4的“进阶”用法
.applyMatrix4( m : Matrix4 ) : Vector4
用 4x4 矩阵变换本向量。Matrix4元素按列主序存储在e[0..15]中,其数学本质是:
x' = m11·x + m21·y + m31·z + m41·w y' = m12·x + m22·y + m32·z + m42·w z' = m13·x + m23·y + m33·z + m43·w w' = m14·x + m24·y + m34·z + m44·w逐行实现见 Vector4.js。这正是齐次坐标的用法:当w = 1时,w分量让矩阵的平移项(e[12]、e[13]、e[14],即第四列前三行)得以参与变换;当w = 0时则代表纯方向向量,平移被忽略。因此用Vector4变换齐次坐标时,务必把w设为1(这正是构造函数默认值的由来),而变换“方向”时则应设为0。
.setFromMatrixPosition( m : Matrix4 ) : Vector4
把矩阵的位移项读入向量:x = e[12]、y = e[13]、z = e[14]、w = e[15](见 Vector4.js)。它对e[15]的读取通常得到1,因此可以直接取出变换矩阵的平移量。
这一方法在 three.js 内部有真实的调用场景:在 WebGLRenderer 的投影矩阵代码路径 中,渲染器用_vector4.setFromMatrixPosition( object.matrixWorld )取出对象世界矩阵的平移作为深度排序依据;在 LightShadow.js 中同样用它取出光源世界坐标。
.setAxisAngleFromQuaternion( q : Quaternion ) : Vector4
把一个归一化的四元数q转换成语义为“轴-角”的向量:x/y/z存旋转轴,w存旋转角(弧度)。实现见 Vector4.js:当旋转角接近0或π(即sqrt(1 - w²) < 0.0001)时,退化处理为固定轴(1, 0, 0)。
.setAxisAngleFromRotationMatrix( m : Matrix4 ) : Vector4
同上,但输入是纯旋转 4x4 矩阵(左上 3x3 无缩放)。源码在 Vector4.js 中实现了完备的轴-角提取算法:先用反对称差判断是否存在奇异性,再分别处理单位矩阵(角为 0)、180° 旋转,以及一般情况下的acos角度求解,且内置了0.01与0.1两个容差阈值用于容纳浮点舍入误差。
const q = new THREE.Quaternion().setFromEuler( new THREE.Euler( 0, Math.PI / 3, 0 ) ); const axisAngle = new THREE.Vector4().setAxisAngleFromQuaternion( q ); // axisAngle.xyz ≈ (0, 1, 0),axisAngle.w ≈ Math.PI / 3七、度量:点积与各种“长度”
.dot( v : Vector4 ) : number
计算两个向量的点积:
x·v.x + y·v.y + z·v.z + w·v.w实现见 Vector4.js。这是本类最常用的运算之一:v.dot(v)等于lengthSq();两个单位向量点积等于夹角余弦;符号可判断方向性。
.length() : number与.lengthSq() : number
length()返回从原点到(x, y, z, w)的欧几里得长度,即Math.sqrt(x² + y² + z² + w²)(Vector4.js);lengthSq()返回长度的平方(Vector4.js)。
官方文档特别提示:当你需要比较多个向量的大小时,应当用lengthSq()而非length(),因为省去了一次Math.sqrt,计算上更高效且判定结果不变。
.manhattanLength() : number
返回曼哈顿长度:|x| + |y| + |z| + |w|(见 Vector4.js)。适用于网格化空间中“只能沿轴移动”的距离度量。
八、方向保持:归一化与长度设定
.normalize() : Vector4
将向量化为单位向量(长度为 1,方向不变)。其实现与divideScalar不同,具有零向量保护:
normalize() { return this.divideScalar( this.length() || 1 ); }即当长度为0时以1作为除数,避免产生NaN(见 Vector4.js)。在大量实时渲染代码中,这条“零长度安全”保证让开发者可以放心地对可能退化的向量调用normalize。
.setLength( length : number ) : Vector4
先把向量归一化,再缩放到指定的新长度(Vector4.js),等价于“保持方向、重设长度”。实现为normalize().multiplyScalar( length )。
九、夹取(Clamp)三兄弟
Vector4提供三种粒度的夹取方法,全部基于MathUtils中导出的clamp函数(见 Vector4.js):
| 方法 | 夹取对象 | 行为 | 源码位置 |
|---|---|---|---|
.clamp( min : Vector4, max : Vector4 ) : Vector4 | 每个分量 | 逐分量夹在min与max的对应分量之间,前提是min/max各分量满足min < max | Vector4.js |
.clampScalar( minVal : number, maxVal : number ) : Vector4 | 每个分量 | 所有分量统一夹在[minVal, maxVal]区间内 | Vector4.js |
.clampLength( min : number, max : number ) : Vector4 | 向量的长度 | 若当前长度超出区间,则按比例缩放向量,使长度落在[min, max],方向不变 | Vector4.js |
其中clampLength的实现同样具备零长度保护:divideScalar( length || 1 )先归一化(长度 0 时按 1 处理),再乘以夹取后的长度。典型应用是限制速度向量的大小:只压长度、不改方向。
十、线性插值(Lerp)系列
| 方法 | 语义 | 源码位置 |
|---|---|---|
.lerp( v : Vector4, alpha : number ) : Vector4 | 在this与v之间插值,结果写回自身:alpha = 0时为本向量,alpha = 1时为v | Vector4.js |
.lerpVectors( v1 : Vector4, v2 : Vector4, alpha : number ) : Vector4 | 计算v1到v2之间的插值并写入自身:alpha = 0得v1,alpha = 1得v2 | Vector4.js |
alpha是沿直线的比例因子,通常取闭区间[0, 1],但源码并不限制越界——取区间外的值即为外推。两者都是逐分量执行a + (b - a) * alpha。lerpVectors不会读自身旧值,因此适合作为临时缓冲反复复用。
const a = new THREE.Vector4( 0, 0, 0, 1 ); const b = new THREE.Vector4( 1, 1, 1, 1 ); const mid = new THREE.Vector4().lerpVectors( a, b, 0.5 ); // mid === (0.5, 0.5, 0.5, 1)十一、取整三件套与取反
| 方法 | 行为 | 等价于 |
|---|---|---|
.floor() : Vector4 | 各分量向下取整 | Math.floor |
.ceil() : Vector4 | 各分量向上取整 | Math.ceil |
.round() : Vector4 | 各分量四舍五入 | Math.round |
.roundToZero() : Vector4 | 各分量向零取整(负数向上、正数向下) | Math.trunc |
四个方法分别见 Vector4.js,常用于将连续坐标离散化到栅格。注意roundToZero与floor的差异只在负数上体现:-1.7经floor变-2,经roundToZero变-1。
.negate() : Vector4:分量全部取相反数(Vector4.js)。
十二、相等性判断
.equals( v : Vector4 ) : boolean
逐个分量用严格相等===比较x/y/z/w,全部相等才返回true(见 Vector4.js)。注意这是精确比较,不做任何容差处理;涉及浮点运算结果之间的比较时,应先对差值做长度判断(如delta.lengthSq() < eps),单元测试中也从math-constants导入了专门的eps常量用于此类容差断言。
十三、与数组及缓冲属性互转
.fromArray( array : Array.<number>, offset : number = 0 ) : Vector4
按x = array[offset]、y = array[offset+1]、z = array[offset+2]、w = array[offset+3]读取(Vector4.js)。
.toArray( array : Array.<number> = [], offset : number = 0 ) : Array.<number>
把四个分量顺序写入目标数组并返回该数组;未提供array时新建数组(Vector4.js)。
这两个方法与 BufferGeometry 的 attribute 数据布局完全一致,是 CPU 端向几何数据填充/读取四分量数据的标准通道。对应测试见 Vector4.tests.js。
.fromBufferAttribute( attribute : BufferAttribute, index : number ) : Vector4
从 GPU 缓冲属性中读取第index个元素的四个分量,内部调用attribute.getX/getY/getZ/getW(Vector4.js)。这是从几何体缓冲中高效抽取四维数据(例如 RGBA 顶点色、自定义四分量 attribute)的标准方法。
十四、随机向量
.random() : Vector4
四个分量分别赋值为[0, 1)区间(含 0 不含 1)的伪随机数(Vector4.js)。随机粒子系统、程序化初值等场景可直接使用。
十五、渲染管线中的真实应用:视口与裁剪矩形
Vector4在 three.js 内部最主要的工程用途,是把“四元组(x, y, width, height)”编码成单一对象,用于描述视口(viewport)与裁剪区域(scissor)。
在 WebGLRenderer 内部状态管理 中,渲染器持有_viewport = new Vector4( 0, 0, _width, _height )与_scissor作为缓存对象;而setViewport/setScissor公开方法的参数类型都接受Vector4 | number,通过x.isVector4做类型分派(见 WebGLRenderer.js)。换句话说,用户代码传入的Vector4会直接按列(x, y, z, w)语义被解析为(x, y, width, height)传给底层gl.viewport。
在阴影渲染一侧,LightShadow 的默认视口集合 同样以new Vector4( 0, 0, 1, 1 )表示规范化矩形(全图一个阴影视口)。
这也是Vector4提供.width/.height别名的根本原因:当它作为“矩形”使用时,z读作宽度、w读作高度,代码可读性显著提升。
十六、从单元测试看契约与边界行为
Vector4的单元测试位于 test/unit/src/math/Vector4.tests.js(QUnit 组织,全文约 650 行),覆盖了本文介绍的全部公开方法,可用于快速验证行为契约:
- 实例化与类型:
new Vector4()默认为(0, 0, 0, 1),isVector4为真(L17-L39); - 矩阵变换与位移提取:
applyMatrix4多组平移/缩放矩阵断言与setFromMatrixPosition验证(L181-L245); - 数组互转:
fromArray的默认/带偏移读取,toArray的默认新建与带偏移写入(L378-L415); - 标量运算:
multiplyScalar/divideScalar的正负标量验证(L533-L556)。
这些测试与测试工具常量math-constants.js(提供x/y/z/w样本与容差eps)共同保证了Vector4的数学行为与文档描述一致。
十七、小结:方法速查总表
| 分类 | 方法 |
|---|---|
| 构造 | new Vector4(x, y, z, w)(默认0,0,0,1) |
| 赋值 | set、setScalar、copy、clone、setX/Y/Z/W、setComponent、getComponent |
| 加法 | add、addScalar、addVectors、addScaledVector |
| 减法 | sub、subScalar、subVectors |
| 乘法/除法 | multiply、multiplyScalar、divide、divideScalar |
| 矩阵/轴角 | applyMatrix4、setFromMatrixPosition、setAxisAngleFromQuaternion、setAxisAngleFromRotationMatrix |
| 度量 | dot、length、lengthSq、manhattanLength |
| 方向控制 | normalize、setLength |
| 夹取 | clamp、clampScalar、clampLength |
| 插值 | lerp、lerpVectors |
| 取整 | floor、ceil、round、roundToZero、negate |
| 判断/转换 | equals、fromArray、toArray、fromBufferAttribute、random |
| 协议 | isVector4标记、[Symbol.iterator](产出 x/y/z/w) |
多数方法都返回向量自身,天然支持链式调用,例如v.copy(a).addScaledVector(b, 0.5).clampLength(0, 10)。建议把Vector4与同目录下的 Vector3 文档(若需三维运算)、Matrix4、Quaternion结合使用:四维向量的矩阵与轴角转换能力正是它们在数学上协同工作的接口所在。深入阅读源码可直接查看 src/math/Vector4.js,需要更多示例可翻看 src/math 目录下同族类的实现风格。
【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考