【ThreeJs原理解析】第2期 | 旋转、平移、缩放实现原理

前言

在Threejs中,旋转、平移、缩放一个物体的方法相信大家已经耳熟能详,具体操作如下,

  • 平移:调用mesh的translate(X/Y/Z)方法
  • 旋转:调用mesh的rotate(X/Y/Z)方法
  • 缩放:修改mesh的scale属性
js 复制代码
// 创建场景
const scene = new THREE.Scene();
// 创建相机
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);

// 创建渲染器
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// 创建几何体
const geometry = new THREE.BoxGeometry();
// 创建材质
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
// 创建网格
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);

// 平移
cube.translateX(0.01);
cube.translateY(0.01);
cube.translateZ(0.01);
// 旋转
cube.rotateX(0.01);
cube.rotateY(0.01);
cube.rotateZ(0.01);
// 缩放
cube.scale.set(2,2,2)

// 渲染
renderer.render(scene, camera);

那么这些Api操作是如何实现的呢?如果我需要同时平移、旋转、和缩放能否一步操作就可以解决呢?

这些问题的答案其实总结下来就俩个字矩阵,本质上是矩阵运算,接下来我们来分析一下具体的实现原理。

平移

数学原理

假定初始坐标为(x,y,z,1), 那么平移操作就是初始矩阵*模型矩阵(modelMatrix), 这个模型矩阵本质上是平移矩阵,旋转矩阵和缩放矩阵的复合矩阵,第四列的a,b,c分别代表沿着x,y,z的平移量

WebGL fragmentShader

让顶点的x移动0.1,y移动0.2,z移动0.3

js 复制代码
attribute vec4 a_Position;
//列主序平移矩阵
mat4 m4=mat4(
    1.0, 0.0, 0.0,0.0,
    0.0, 1.0, 0.0,0.0,
    0.0, 0.0, 1.0,0.0,
    0.1, 0.2, 0.3,1.0
);
void main(){
   gl_Position = m4*a_Position;
}

ThreeJs 源码解析

js 复制代码
cube.translate(1,2,3);//沿着x轴方向平移1,y轴方向平移2,z轴方向平3

第一步

js 复制代码
// 源码地址:https://github.com/mrdoob/three.js/blob/dev/src/core/BufferGeometry.js
const _m1 = /*@__PURE__*/ new Matrix4();
.......
translate( x, y, z ) {
    // translate geometry
    _m1.makeTranslation( x, y, z );
    this.applyMatrix4( _m1 );
    return this;
}
  1. m1 :该物体当前模型矩阵(对该物体的平移,旋转,缩放都会修改这个矩阵)
  2. 调用模型矩阵的makeTranslation方法,修改模型矩阵
js 复制代码
// 源码地址 https://github.com/mrdoob/three.js/blob/dev/src/math/Matrix4.js
makeTranslation( x, y, z ) {
    if ( x.isVector3 ) {
        this.set(
            1, 0, 0, x.x,
            0, 1, 0, x.y,
            0, 0, 1, x.z,
            0, 0, 0, 1
        );
    } else {
        this.set(
            1, 0, 0, x,
            0, 1, 0, y,
            0, 0, 1, z,
            0, 0, 0, 1
        );
    }
    return this;
}

这边会有一个判断,makeTranslation支持传入一个Vector3向量,这个我们暂时不用关心,继续往下看,大家有没有发现一个规律,这个set的右侧x,y,z和我上述说的数学原理中的模型矩阵是对应关系的,这就说明 平移的本质就是对矩阵变换

第二步 调用applyMatrix4 即 上文的this.applyMatrix4( _m1 );

js 复制代码
// 源码地址:https://github.com/mrdoob/three.js/blob/dev/src/core/BufferGeometry.js
applyMatrix4( matrix ) {
    const position = this.attributes.position;
    if ( position !== undefined ) {
        position.applyMatrix4( matrix );
        position.needsUpdate = true;
    }
    ......
}

applyMatrix4方法也很好理解,就是将之前模型矩阵和现在平移后所得的矩阵相乘,得出此次平移后最终的模型矩阵。

旋转

旋转和平移相似,唯一不同就是旋转矩阵和平移矩阵的在模型矩阵中的位置不同

数学原理

一个物体沿着x,y,z 轴分别旋转α, β, γ 度数, 可以看出该矩阵和平移矩阵的区别就是,旋转矩阵是左上角的3*3,平移矩阵是最右侧那一列

WebGL fragmentShader

物体沿z轴旋转60度

glsl 复制代码
attribute vec4 a_Position;
float angle = radians(60.0);
float cosB = cos(angle);
float sinB = sin(angle);
mat4 m4 = mat4 (
   cosB, sinB, 0.0, 0.0,
   -sinB, cosB, 0.0, 0.0,
   0.0, 0.0, 1.0, 0.0,
   0.0, 0.0, 0.0, 1.0
);
void main(){
   gl_Position = m4*a_Position;
}

ThreeJs 源码解析

js 复制代码
// 旋转
cube.rotateZ(Math.PI/4);//沿着z轴旋转π/4

第一步

js 复制代码
// 源码地址:https://github.com/mrdoob/three.js/blob/dev/src/core/BufferGeometry.js
const _m1 = /*@__PURE__*/ new Matrix4();
.......
rotateZ( angle ) {
    // rotate geometry around world z-axis
    _m1.makeRotationZ( angle );
    this.applyMatrix4( _m1 );

    return this;
}
  1. m1 :该物体当前模型矩阵(对该物体的平移,旋转,缩放都会修改这个矩阵)
  2. 调用模型矩阵的makeRotationZ方法,修改模型矩阵
js 复制代码
// 源码地址 https://github.com/mrdoob/three.js/blob/dev/src/math/Matrix4.js
makeRotationZ( theta ) {
    const c = Math.cos( theta ), s = Math.sin( theta );
    this.set(
        c, - s, 0, 0,
        s, c, 0, 0,
        0, 0, 1, 0,
        0, 0, 0, 1
    );
    return this;
}

makeRotationZ中set本质就是上面数学原理中的构造沿着z轴旋转的旋转矩阵

PS: 需要注意的点是在threejs矩阵的存储方式默认是列优先,而在我们大学线代中的矩阵是行优先。就是将我数学原理中的矩阵的行看成three中的列即可

第二步 调用applyMatrix4 即 上文的this.applyMatrix4( _m1 );

js 复制代码
// 源码地址:https://github.com/mrdoob/three.js/blob/dev/src/core/BufferGeometry.js
applyMatrix4( matrix ) {
    const position = this.attributes.position;
    if ( position !== undefined ) {
        position.applyMatrix4( matrix );
        position.needsUpdate = true;
    }
    ......
}

原理同上,将此次旋转所得的矩阵和之前的模型矩阵相乘,得出现在的模型矩阵

缩放

缩放也同上,唯一不同就是缩放矩阵

数学原理

下面公式表示沿着x,y,z轴分别缩放a,b,c倍

WebGL fragmentShader

x轴向缩放2,y轴向缩放3,z轴向缩放4

glsl 复制代码
attribute vec4 a_Position;
// 声明缩放矩阵
mat4 m4 = mat4(
   2.0, 0.0, 0.0,0.0,
   0.0, 3.0, 0.0,0.0,
   0.0, 0.0, 4.0,0.0,
   0.0, 0.0, 0.0,1.0
);
void main(){
   gl_Position = m4*a_Position;
}

ThreeJs 源码解析

js 复制代码
// 缩放
cube.scale.set(1, 2, 3) // x轴缩放1倍,y轴缩放2倍,z轴缩放3倍

第一步

js 复制代码
// 源码地址:https://github.com/mrdoob/three.js/blob/dev/src/core/BufferGeometry.js
const _m1 = /*@__PURE__*/ new Matrix4();
.......
scale( x, y, z ) {
    // scale geometry
    _m1.makeScale( x, y, z );
    this.applyMatrix4( _m1 );

    return this;
}
  1. m1 :该物体当前模型矩阵(对该物体的平移,旋转,缩放都会修改这个矩阵)
  2. 调用模型矩阵的makeRotationZ方法,修改模型矩阵
js 复制代码
// 源码地址 https://github.com/mrdoob/three.js/blob/dev/src/math/Matrix4.js
makeScale( x, y, z ) {
    this.set(
        x, 0, 0, 0,
        0, y, 0, 0,
        0, 0, z, 0,
        0, 0, 0, 1
    );

    return this;
}

makeScale中set本质就是上面数学原理中的构造缩放矩阵

第二步 调用applyMatrix4 即 上文的this.applyMatrix4( _m1 );

js 复制代码
// 源码地址:https://github.com/mrdoob/three.js/blob/dev/src/core/BufferGeometry.js
applyMatrix4( matrix ) {
    const position = this.attributes.position;
    if ( position !== undefined ) {
        position.applyMatrix4( matrix );
        position.needsUpdate = true;
    }
    ......
}

原理同上,将此次缩放所得的矩阵和之前的模型矩阵相乘,得出现在的模型矩阵

总结

现在我在前言中提出的问题其实就迎刃而解了

【如果我需要同时平移、旋转、和缩放能否一步操作就可以解决呢?】

答: 构造平移,旋转,缩放的复合矩阵;再乘以该mesh现在的模型矩阵

相关推荐
前端大卫27 分钟前
Vue3 + Element-Plus 自定义虚拟表格滚动实现方案【附源码】
前端
却尘43 分钟前
Next.js 请求最佳实践 - vercel 2026一月发布指南
前端·react.js·next.js
ccnocare44 分钟前
浅浅看一下设计模式
前端
Lee川1 小时前
🎬 从标签到屏幕:揭秘现代网页构建与适配之道
前端·面试
Ticnix1 小时前
ECharts初始化、销毁、resize 适配组件封装(含完整封装代码)
前端·echarts
纯爱掌门人1 小时前
终焉轮回里,藏着 AI 与人类的答案
前端·人工智能·aigc
twl1 小时前
OpenClaw 深度技术解析
前端
崔庆才丨静觅1 小时前
比官方便宜一半以上!Grok API 申请及使用
前端
星光不问赶路人2 小时前
vue3使用jsx语法详解
前端·vue.js
天蓝色的鱼鱼2 小时前
shadcn/ui,给你一个真正可控的UI组件库
前端