【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现在的模型矩阵

相关推荐
NoneCoder19 分钟前
CSS系列(18)-- 工程化实践详解
前端·css
請你喝杯Java1 小时前
Mac软件清单(前后端开发环境搭建)
前端·后端·macos·软件
稀土君1 小时前
角逐20万奖金!这里有一份完整的豆包MarsCode AI编程挑战赛参赛指南!
前端·人工智能·豆包marscode
大王棒棒的1 小时前
不到300行代码实现包含多级表头、自定义单元格渲染、顺滑滚动以及虚拟列表等功能的超级表格
前端·javascript
杨荧1 小时前
【开源免费】基于Vue和SpringBoot的渔具租赁系统(附论文)
前端·javascript·jvm·vue.js·spring boot·spring cloud·开源
用户3623786912591 小时前
【面试题】说说你对发布订阅、观察者模式的理解?区别?_消息订阅与发布面试题
前端
想退休的搬砖人2 小时前
前端水印功能(svg,canvas文字水印,canvas图片水印)
开发语言·前端
王同学JavaNotes2 小时前
ES6 语法:强大特性全解析
前端·es6
GISer_Jing2 小时前
前端面试题目 (Node.JS-Express框架)[二]
前端·面试·node.js·express
别发呆了吧2 小时前
前端面试准备问题2
前端·经验分享