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

相关推荐
麻花201311 分钟前
WPF学习之路,控件的只读、是否可以、是否可见属性控制
服务器·前端·学习
.54811 分钟前
提取双栏pdf的文字时 输出文件顺序混乱
前端·pdf
jyl_sh19 分钟前
WebKit(适用2024年11月份版本)
前端·浏览器·客户端·webkit
zhanghaisong_20151 小时前
Caused by: org.attoparser.ParseException:
前端·javascript·html·thymeleaf
Eric_见嘉1 小时前
真的能无限试(白)用(嫖)cursor 吗?
前端·visual studio code
DK七七2 小时前
多端校园圈子论坛小程序,多个学校同时代理,校园小程序分展示后台管理源码
开发语言·前端·微信小程序·小程序·php
老赵的博客2 小时前
QSS 设置bug
前端·bug·音视频
Chikaoya2 小时前
项目中用户数据获取遇到bug
前端·typescript·vue·bug
南城夏季2 小时前
蓝领招聘二期笔记
前端·javascript·笔记
Huazie2 小时前
来花个几分钟,轻松掌握 Hexo Diversity 主题配置内容
前端·javascript·hexo