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

相关推荐
lilu88888881 小时前
AI代码生成器赋能房地产:ScriptEcho如何革新VR/AR房产浏览体验
前端·人工智能·ar·vr
LCG元1 小时前
Vue.js组件开发-实现对视频预览
前端·vue.js·音视频
阿芯爱编程1 小时前
vue3 react区别
前端·react.js·前端框架
烛.照1032 小时前
Nginx部署的前端项目刷新404问题
运维·前端·nginx
YoloMari2 小时前
组件中的emit
前端·javascript·vue.js·微信小程序·uni-app
浪浪山小白兔2 小时前
HTML5 Web Worker 的使用与实践
前端·html·html5
疯狂小料3 小时前
React 路由导航与传参详解
前端·react.js·前端框架
追光少年33224 小时前
Learning Vue 读书笔记 Chapter 2
前端·javascript·vue.js·vue3
前端熊猫4 小时前
JavaScript 的 Promise 对象和 Promise.all 方法的使用
开发语言·前端·javascript
iOS阿玮4 小时前
速领🧧!iOS研究院专属「红包封面」来了,第二弹。
前端