three.js中的矩阵变换(模型视图投影变换)

article/2025/9/15 5:17:48

文章目录

  • 1. 概述
  • 2. 基本变换
    • 2.1. 矩阵运算
    • 2.2. 模型变换矩阵
      • 2.2.1. 平移矩阵
      • 2.2.2. 旋转矩阵
        • 2.2.2.1. 绕X轴旋转矩阵
        • 2.2.2.2. 绕Y轴旋转矩阵
        • 2.2.2.3. 绕Z轴旋转矩阵
    • 2.3. 投影变换矩阵
    • 2.4. 视图变换矩阵
  • 3. 着色器变换
    • 3.1. 代码
    • 3.2. 解析
  • 4. 其他

1. 概述

我在《WebGL简易教程(五):图形变换(模型、视图、投影变换)》这篇博文里详细讲解了OpenGL\WebGL关于绘制场景的图形变换过程,并推导了相应的模型变换矩阵、视图变换矩阵以及投影变换矩阵。这里我就通过three.js这个图形引擎,验证一下其推导是否正确,顺便学习下three.js是如何进行图形变换的。

2. 基本变换

2.1. 矩阵运算

three.js已经提供了向量类和矩阵类,定义并且查看一个4阶矩阵类:

var m = new THREE.Matrix4();
m.set(11, 12, 13, 14,21, 22, 23, 24,31, 32, 33, 34,41, 42, 43, 44);
console.log(m);

输出结果:
imglink1

说明THREE.Matrix4内部是列主序存储的,而我们理论描述的矩阵都为行主序。

2.2. 模型变换矩阵

在场景中新建一个平面:

// create the ground plane
var planeGeometry = new THREE.PlaneGeometry(60, 20);
var planeMaterial = new THREE.MeshBasicMaterial({color: 0xAAAAAA
});
var plane = new THREE.Mesh(planeGeometry, planeMaterial);// add the plane to the scene
scene.add(plane);

three.js中场景节点的基类都是Object3D,Object3D包含了3种矩阵对象:

  1. Object3D.matrix: 相对于其父对象的局部模型变换矩阵。
  2. Object3D.matrixWorld: 对象的全局模型变换矩阵。如果对象没有父对象,则与Object3D.matrix相同。
  3. Object3D.modelViewMatrix: 表示对象相对于相机坐标系的变换。也就是matrixWorld左乘相机的matrixWorldInverse。

2.2.1. 平移矩阵

平移这个mesh:

plane.position.set(15, 8, -10);

根据推导得到平移矩阵为:
[ 1 0 0 T x 0 1 0 T y 0 0 1 T z 0 0 0 1 ] \left[ \begin{matrix} 1 & 0 & 0 & Tx\\ 0 & 1 & 0 & Ty\\ 0 & 0 & 1 & Tz\\ 0 & 0 & 0 & 1 \end{matrix} \right] 100001000010TxTyTz1
输出这个Mesh:
imglink2

2.2.2. 旋转矩阵

2.2.2.1. 绕X轴旋转矩阵

绕X轴旋转:

plane.rotation.x = THREE.Math.degToRad(30);

对应的旋转矩阵:
[ 1 0 0 0 0 c o s β − s i n β 0 0 s i n β c o s β 0 0 0 0 1 ] \left[ \begin{matrix} 1 & 0 & 0 & 0\\ 0 & cosβ & -sinβ & 0\\ 0 & sinβ & cosβ & 0\\ 0 & 0 & 0 & 1 \end{matrix} \right] 10000cosβsinβ00sinβcosβ00001

输出信息:
imglink3

2.2.2.2. 绕Y轴旋转矩阵

绕Y轴旋转:

plane.rotation.y = THREE.Math.degToRad(30);

对应的旋转矩阵:
[ c o s β 0 s i n β 0 0 1 0 0 − s i n β 0 c o s β 0 0 0 0 1 ] \left[ \begin{matrix} cosβ & 0 & sinβ & 0\\ 0 & 1 & 0 & 0\\ -sinβ & 0 & cosβ & 0\\ 0 & 0 & 0 & 1 \end{matrix} \right] cosβ0sinβ00100sinβ0cosβ00001

输出信息:
imglink4

2.2.2.3. 绕Z轴旋转矩阵

绕Z轴旋转:

plane.rotation.z = THREE.Math.degToRad(30);

对应的旋转矩阵:
[ c o s β − s i n β 0 0 s i n β c o s β 0 0 0 0 1 0 0 0 0 1 ] \left[ \begin{matrix} cosβ & -sinβ & 0 & 0\\ sinβ & cosβ & 0 & 0\\ 0 & 0 & 1 & 0\\ 0 & 0 & 0 & 1 \end{matrix} \right] cosβsinβ00sinβcosβ0000100001

输出信息:
imglink5

2.3. 投影变换矩阵

在场景中新建一个Camera:

var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);

这里创建了一个透视投影的相机,一般建立的都是对称的透视投影,推导的透视投影矩阵为:
P = [ 1 a s p e c t ∗ t a n ⁡ ( f o v y 2 ) 0 0 0 0 1 t a n ⁡ ( f o v y 2 ) 0 0 0 0 f + n n − f 2 f n n − f 0 0 − 1 0 ] P= \left[ \begin{matrix} \frac{1}{aspect*tan⁡(\frac{fovy}{2})} & 0 & 0 & 0 \\ 0 & \frac{1}{tan⁡(\frac{fovy}{2})} & 0 & 0 \\ 0 & 0 & \frac{f+n}{n-f} & \frac{2fn}{n-f} \\ 0 & 0 & -1 & 0 \\ \end{matrix} \right] P=aspecttan(2fovy)10000tan(2fovy)10000nff+n100nf2fn0

为了验证其推导是否正确,输出这个camera,查看projectionMatrix,也就是透视投影矩阵:
imglink6

2.4. 视图变换矩阵

通过Camera可以设置视图矩阵:

camera.position.set(0, 0, 100);   //相机的位置
camera.up.set(0, 1, 0);         //相机以哪个方向为上方
camera.lookAt(new THREE.Vector3(1, 2, 3));          //相机看向哪个坐标

根据《WebGL简易教程(五):图形变换(模型、视图、投影变换)》中的描述,可以通过three.js的矩阵运算来推导其视图矩阵:

var eye = new THREE.Vector3(0, 0, 100);
var up = new THREE.Vector3(0, 1, 0);
var at = new THREE.Vector3(1, 2, 3);var N = new THREE.Vector3();
N.subVectors(eye, at); 
N.normalize();
var U = new THREE.Vector3();
U.crossVectors(up, N);
U.normalize();
var V = new THREE.Vector3();
V.crossVectors(N, U);
V.normalize();var R = new THREE.Matrix4();
R.set(U.x, U.y, U.z, 0,V.x, V.y, V.z, 0,N.x, N.y, N.z, 0,0, 0, 0, 1);  var T = new THREE.Matrix4(); 
T.set(1, 0, 0, -eye.x,0, 1, 0, -eye.y,0, 0, 1, -eye.z,0, 0, 0, 1);  var V = new THREE.Matrix4();
V.multiplyMatrices(R, T);   
console.log(V); 

其推导公式如下:
V = R − 1 T − 1 = [ U x U y U z 0 V x V y V z 0 N x N y N z 0 0 0 0 1 ] ∗ [ 1 0 0 − T x 0 1 0 − T y 0 0 1 − T z 0 0 0 1 ] = [ U x U y U z − U ⋅ T V x V y V z − V ⋅ T N x N y N z − N ⋅ T 0 0 0 1 ] V=R^{-1} T^{-1}= \left[ \begin{matrix} Ux & Uy & Uz & 0 \\ Vx & Vy & Vz & 0 \\ Nx & Ny & Nz & 0 \\ 0 & 0 & 0 & 1 \\ \end{matrix} \right] * \left[ \begin{matrix} 1 & 0 & 0 & -Tx \\ 0 & 1 & 0 & -Ty\\ 0 & 0 & 1 & -Tz\\ 0 & 0 & 0 & 1\\ \end{matrix} \right] = \left[ \begin{matrix} Ux & Uy & Uz & -U·T \\ Vx & Vy & Vz & -V·T \\ Nx & Ny & Nz & -N·T \\ 0 & 0 & 0 & 1 \\ \end{matrix} \right] V=R1T1=UxVxNx0UyVyNy0UzVzNz00001100001000010TxTyTz1=UxVxNx0UyVyNy0UzVzNz0UTVTNT1

最后输出它们的矩阵值:
imglink7
imglink8

两者的计算结果基本时一致的。需要注意的是Camera中表达视图矩阵的成员变量是Camera.matrixWorldInverse。它的逻辑应该是视图矩阵与模型矩阵互为逆矩阵,模型矩阵也可以称为世界矩阵,那么世界矩阵的逆矩阵就是视图矩阵了。

3. 着色器变换

可以通过给着色器传值来验证计算的模型视图投影矩阵(以下称MVP矩阵)是否正确。对于一个任何事情都不做的着色器来说:

vertexShader: ` void main() { gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );      }`
,fragmentShader: `       void main() {    gl_FragColor = vec4(0.556, 0.0, 0.0, 1.0)                   }`

projectionMatrix和modelViewMatrix分别是three.js中内置的投影矩阵和模型视图矩阵。那么可以做一个简单的验证工作,将计算得到的MVP矩阵传入到着色器中,代替这两个矩阵,如果最终得到的值是正确的,那么就说明计算的MVP矩阵是正确的。

3.1. 代码

实例代码如下:

<!DOCTYPE html>
<html><head><title>Example 01.01 - Basic skeleton</title><meta charset="UTF-8" /><script type="text/javascript" charset="UTF-8" src="../three/three.js"></script><script type="text/javascript" charset="UTF-8" src="../three/controls/TrackballControls.js"></script><script type="text/javascript" charset="UTF-8" src="../three/libs/stats.min.js"></script><script type="text/javascript" charset="UTF-8" src="../three/libs/util.js"></script><script type="text/javascript" src="MatrixDemo.js"></script><link rel="stylesheet" href="../css/default.css">
</head><body><!-- Div which will hold the Output --><div id="webgl-output"></div><!-- Javascript code that runs our Three.js examples --><script type="text/javascript">(function () {// contains the code for the exampleinit();})();</script>
</body></html>
'use strict';THREE.StretchShader = {uniforms: {   "sw" : {type:'b', value : false},"mvpMatrix" : {type:'m4',value:new THREE.Matrix4()}    },// vertexShader: `    uniform mat4 mvpMatrix;uniform bool sw;void main() { if(sw) {gl_Position = mvpMatrix * vec4( position, 1.0 );  }else{gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); }       }`,//fragmentShader: `   uniform bool sw; void main() {    if(sw) {gl_FragColor = vec4(0.556, 0.0, 0.0, 1.0); }else {gl_FragColor = vec4(0.556, 0.8945, 0.9296, 1.0); }                    }`
};function init() {//console.log("Using Three.js version: " + THREE.REVISION);   // create a scene, that will hold all our elements such as objects, cameras and lights.var scene = new THREE.Scene();// create a camera, which defines where we're looking at.var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);// position and point the camera to the center of the scenecamera.position.set(0, 0, 100);   //相机的位置camera.up.set(0, 1, 0);         //相机以哪个方向为上方camera.lookAt(new THREE.Vector3(1, 2, 3));          //相机看向哪个坐标// create a render and set the sizevar renderer = new THREE.WebGLRenderer();renderer.setClearColor(new THREE.Color(0x000000));renderer.setSize(window.innerWidth, window.innerHeight);// add the output of the renderer to the html elementdocument.getElementById("webgl-output").appendChild(renderer.domElement);// create the ground planevar planeGeometry = new THREE.PlaneGeometry(60, 20);// var planeMaterial = new THREE.MeshBasicMaterial({//     color: 0xAAAAAA// });var planeMaterial = new THREE.ShaderMaterial({uniforms: THREE.StretchShader.uniforms,vertexShader: THREE.StretchShader.vertexShader,fragmentShader: THREE.StretchShader.fragmentShader});var plane = new THREE.Mesh(planeGeometry, planeMaterial);// add the plane to the scenescene.add(plane);// rotate and position the plane    plane.position.set(15, 8, -10);plane.rotation.x = THREE.Math.degToRad(30);plane.rotation.y = THREE.Math.degToRad(45);plane.rotation.z = THREE.Math.degToRad(60);render();var farmeCount = 0;function render() {    var mvpMatrix = new THREE.Matrix4(); mvpMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);    mvpMatrix.multiplyMatrices(mvpMatrix, plane.matrixWorld);   THREE.StretchShader.uniforms.mvpMatrix.value = mvpMatrix; if(farmeCount % 60 === 0){THREE.StretchShader.uniforms.sw.value = !THREE.StretchShader.uniforms.sw.value;}          farmeCount = requestAnimationFrame(render);renderer.render(scene, camera);}}

3.2. 解析

这段代码的意思是,给着色器传入了计算好的MVP矩阵变量mvpMatrix,以及一个开关变量sw。开关变量会每60帧变一次,如果为假,会使用内置的projectionMatrix和modelViewMatrix来计算顶点值,此时场景中的物体颜色会显示为蓝色;如果开关变量为真,则会使用传入的计算好的mvpMatrix计算顶点值,此时场景中的物体颜色会显示为红色。运行截图如下:
imglink9

可以看到场景中的物体的颜色在红色与蓝色之间来回切换,且物体位置没有任何变化,说明我们计算的MVP矩阵是正确的。

4. 其他

在使用JS的console.log()进行打印camera对象的时候,会发现如果不调用render()的话(或者单步调式),其内部的matrix相关的成员变量仍然是初始化的值,得不到想要的结果。而console.log()可以认为是异步的,调用render()之后,就可以得到正确的camera对象了。


http://chatgpt.dhexx.cn/article/gGr4vahE.shtml

相关文章

矩阵变换及其数学原理

矩阵变换及其数学原理 矩阵变换及其数学原理引子各种变换 平移矩阵缩放矩阵旋转变换 引子 推荐这篇文章线性代数的本质&#xff0c;这篇文章挺不错的&#xff0c;揭示了矩阵和向量的内涵。首先概要性的提一下 向量刻画的是线性空间中的对象。矩阵刻画的是向量在线性空间中的运…

计算机图形学之矩阵变换的深度理解

对于图形学来说&#xff0c;矩阵计算不可避免&#xff0c;既直观又方便。而如果线性代数学的不透彻的话&#xff0c;那么基本上是做不到应用的&#xff0c;这里推荐看一下3Blue1Brown的线性代数的视频&#xff0c;可以对矩阵计算有深刻的认识。 之后就是应用阶段&#xff0c;我…

图像处理-矩阵变换

Android中通过矩阵来处理图像问题是非常常见的。 图像中的每一个像素点都是一个颜色矩阵分量&#xff0c;然后我们让这两个矩阵相乘就能得到一个新的矩阵&#xff08;新的颜色矩阵分量&#xff09;&#xff0c;这就是矩阵变换对图像中的每一个点的处理&#xff0c;使得对整个图…

shader中的常用矩阵变换

unity shader 矩阵学习 矩阵运算法则&#xff08;1&#xff09;矩阵和标量的乘法&#xff08;2&#xff09;矩阵和矩阵的乘法矩阵相乘的条件 和 结果的行数和列数 变换的基本概念(1)变换(2)线性变换(3)平移变换(4)仿射变换&#xff08;5&#xff09;齐次坐标 2D 矩阵变换&#…

【Matlab】矩阵变换与矩阵求值

矩阵变换与矩阵求值 对角矩阵&#xff1a;只有对角线上有非零元素的矩阵。 数量矩阵&#xff1a;对角线上的元素相等的对角矩阵。 单位矩阵&#xff1a;对角线上的元素都为1的对角矩阵。 diag函数 提取矩阵的对角线元素 diag(A)&#xff1a;提取矩阵A主对角线元素&#x…

闫令琪图形学入门笔记(矩阵变换篇)

整个坐标变化过程贯穿管线渲染&#xff0c;它与光栅化、着色一同构成GPU完整的渲染过程。所以理解三维世界的坐标与矩阵变换是首要的学习内容 1.1 向量的点乘与叉乘 点乘 向量的点乘可以求得一个数&#xff0c;利用点乘可以进一步计算两向量的夹角大小&#xff0c;或者一个…

点云矩阵变换

点云矩阵变换 变换矩阵工作原理 : |-------> 变换矩阵列| 1 0 0 x | \| 0 1 0 y | }-> 左边是一个3阶的单位阵(无旋转)| 0 0 1 z | /| 0 0 0 1 | -> 这一行用不到 (这一行保持 0,0,0,1)要进行点云旋转&#xff0c;需要对3阶矩阵进行赋值 如何赋值参考&#xf…

OpenGL矩阵变换

参考内容&#xff1a; 1. 这次&#xff0c;彻底搞懂 OpenGL 矩阵转换 2. Article - World, View and Projection Transformation Matrices 模型变换的基本流程图&#xff1a; 1. 模型变换 模型变换解决的是&#xff0c;把物体在世界坐标系下的位置拆分成平移、缩放、旋转的…

MATLAB矩阵变换

目录 对角阵与三角阵 1、对角阵 &#xff08;1&#xff09;提取矩阵的对角线元素 (2)构造对角阵 2、三角阵 (1)上三角阵 (2)下三角阵 矩阵的转置与旋转 1、矩阵的转置 2、矩阵的旋转 3、矩阵的左右翻转 4、矩阵的上下翻转 矩阵的逆与伪逆 1、矩阵的逆 2、矩阵的伪…

Cesium 矩阵变换

在Cesium和其他三维开发中中经常用到矩阵变换。比如将一个物体移动、缩放、平移都可以用变换矩阵来计算。 再比如将三维场景中的物体转换为屏幕上显示的二维图形&#xff0c;需要用到透视投影&#xff08;perspective projection&#xff09;矩阵。 变换&#xff08;tansform…

浅谈矩阵变换——Matrix

矩阵变换在图形学上经常用到。基本的常用矩阵变换操作包括平移、缩放、旋转、斜切。 每种变换都对应一个变换矩阵&#xff0c;通过矩阵乘法&#xff0c;可以把多个变换矩阵相乘得到复合变换矩阵。 矩阵乘法不支持交换律&#xff0c;因此不同的变换顺序得到的变换矩阵也是不相同…

变换矩阵

1、变换矩阵 变换矩阵可以分解为缩放&#xff0c;旋转&#xff0c;平移矩阵的乘积&#xff1a; M T * R * S - 右手坐标系 当均匀缩放时&#xff0c;旋转和缩放可以交换顺序 缩放和平移不可以交换顺序 2、子坐标系与父坐标系 由在父坐标系中的坐标位置P&#xff0c;和三…

矩阵基础与变换

矩阵基础 矩阵的基本概念 由 m n 个数aij排成的m行n列的数表称为m行n列的矩阵&#xff0c;简称m n矩阵&#xff08;引用百度百科&#xff09;。记作&#xff1a; 这mn 个数称为矩阵A的元素&#xff0c;简称为元&#xff0c;数aij位于矩阵A的第i行第j列。 矩阵的加法 同型…

2.2 矩阵变换

写在前面&#xff1a;作者本人是纯纯的菜鸟&#xff0c;学习的内容来自于 中国大学MOOC 中南大学 《科学计算与MATLAB语言》&#xff0c;欢迎各位大佬或新手在这里和平讨论&#xff0c;如果我有错误请各位不吝赐教&#xff0c;提前感谢各位捧场&#xff01; 何为矩阵变化&#…

线性代数学习笔记——第七十二讲——共轭矩阵

1. 本讲内容概要 2. 复数及其性质及复数的运算 3. 复共轭及模 4. 共轭矩阵的定义及性质

共轭矩阵 正定矩阵

https://blog.csdn.net/know9163/article/details/80551764

共轭 、 共轭转置、共轭矩阵、酉矩阵、正定矩阵、半正定矩阵

共轭复数 实数部分相同而虚数部分互为相反数的两个复数。 矩阵的共轭转置 把矩阵转置后&#xff0c;再把每一个数换成它的共轭复数。 自共轭矩阵 矩阵中每一个第i 行第j 列的元素都与第j 行第i 列的元素的共轭相等。 酉矩阵 AH 是A 的共轭转置 A叫做酉矩阵 正定矩阵 半正…

matlab-线性代数 对矩阵取共轭(不用函数)

由上图所示&#xff0c;可以得知&#xff1a;a是求矩阵的共轭转置&#xff0c;而a.是求矩阵的共轭。在数学中&#xff0c;共轭转置表示为在矩阵的右上角加上H。 如果矩阵是实数矩阵&#xff0c;那么a和a.的结果一样&#xff0c;都是求矩阵的转置&#xff0c;如下图所示&#xf…

我的矩阵学习

摘自矩阵&#xff08;数学术语&#xff09;_百度百科 定义 由 m n 个数aij排成的m行n列的数表称为m行n列的矩阵&#xff0c;简称m n矩阵。记作&#xff1a; 这mn 个数称为矩阵A的元素&#xff0c;简称为元&#xff0c;数aij位于矩阵A的第i行第j列&#xff0c;称为矩阵A的(i…

矩阵的共轭

The conjugate of a matrix&#xff08;矩阵的共轭&#xff09;&#xff1a;矩阵元素中有复数&#xff08;complex number&#xff09;&#xff0c;将矩阵中的复数求共轭&#xff0c;实数不变。矩阵的共轭记为。如&#xff1a;