目标:掌握利用矩阵位移,旋转,缩放基本图形
准备
引入cuon-matrix.js矩阵库
基本概念
- Matrix4类 cuon-matrix.js中为我们提供了Matrix4类,Matrix4对象(实例)表示一个4*4的矩阵,该对象内部使用类型化数组Floated32Array来存储矩阵的元素
- 旋转
为描述一个旋转,你必须指明1.旋转轴(图形将围绕旋转轴旋转); 2.旋转方向(方向:顺时针或逆时针); 3.旋转角度(图形旋转经过的角度)。
在旋转中,关于“逆时针”的约定是:如果β是正值,观察者在Z轴正半轴某处,视线沿着Z轴负方向进行观察,那个看到的物体就是逆时针的,这种情况又可称为正旋转。我们也可以使用右手来确定旋转方向:右手握拳,大拇指伸直并指向旋转轴正方向,那么右手其余几个手指就指明了旋转的方向,因此正方向又可以称为右手法则旋转。本文默认使用右手法则旋转
本文旋转中这样描述旋转:绕Z轴,逆时针旋转β角度; - 必须掌握的方法
Matrix4对象所支持的方法和属性
方法和属性名称 | 描述 |
---|---|
Matrix4.setIdentity() | 将Matrix4实例初始化为单位阵 |
Matrix4.setTranslate(x,y,z) | 将Matrix4实例设置为平移变换矩阵,在x轴上平移的距离为x,在y轴上平移的距离为y,在z轴上平移的距离为z |
Matrix4.setRotate(angle,x,y,z) | 将Matrix4实例设置为旋转变换矩阵,旋转的角度为angle,旋转轴为(x,y,z),旋转轴(x,y,z)无需归一化 |
Matrux4,setScale(x,y,z) | 将Matrix4实例设置为缩放变换矩阵,在三个轴上的缩放因子分别为x,y,z |
Matrix4.translate(x,y,z) | 将Matrix4实例乘以一个平移变换矩阵(该平移矩阵在x轴上平移的距离为x,在y轴上平移的距离为y,在z轴上平移的距离为z),所得的结果还存储在Matrix4中 |
Matrix4.rotate(angle,x,y,z) | 将Matrix4实例乘以一个旋转变换矩阵(该旋转矩阵旋转角度为angle,旋转轴为(x,y,z),旋转轴(x,y,z)无需归一化),所得的结果还是存储在Matrix4中 |
Matrix4.scale(x,y,z) | 将Matrix4实例乘以一个缩放变换矩阵(该缩放矩阵在三个轴上的缩放因子分别为x,y,z),所得的结果还存储在Matrix4中。 |
Matrix4.set(m) | 将Matrix4实例设置为m,m必须也是一个Matrix4实例 |
Matrix4.elements | 类型化数组(Float32Array)包含了Matrix4实例的矩阵元素 |
上边的表格中,Matrix4对象有两种方法,一种方法名称中含有前缀set,这一种会根据参数计算出变换矩阵,然后将矩阵写入自身中,另一种不含set,这种会先根据参数计算出变换矩阵,然后将自身与刚刚计算得到的变换矩阵相乘,然后把最终得到的结果写入Matrix4对象中
单位阵在矩阵乘法中的行为,就像数字1在乘法中的行为一样,将一个矩阵乘以单位阵,得到的结果和原矩阵完全相同。在单位阵中,对角线上的元素为1.0,其余元素为0.0
- 变换等式
旋转后 的坐标 = 旋转矩阵 * 原始坐标(矢量)
缩放后 的坐标 = 缩放矩阵 * 原始坐标(矢量)
平移后 的坐标 = 平移矩阵 * 原始坐标(矢量)
平移后旋转 的坐标 = 旋转矩阵 * 平移后的坐标(矢量)
= 旋转矩阵 * (平移矩阵 * 原始坐标)
= (旋转矩阵 * 平移矩阵)* 原始坐标 - 新坐标 = 变换矩阵 * 原始坐标
gl_Position = u_xformMatrix * a_Position;
一个模型可能经过了多次变换,将这些变换全部复合成一个等效的变换,就得到了模型变换,模型变换的矩阵称为模型矩阵。上式中(旋转矩阵*平移矩阵)就称为模型矩阵
方法
gl.uniformMatrix4fv(location, transpose, array)
将array表示的4*4矩阵分配给location指定的uniform变量。transpose在WebGL中必须指定为false
示例程序
// 示例一(利用矩阵方法平移旋转三角形)
var VSHADER_SOURCE = `
attribute vec4 a_Position;\n
uniform mat4 u_ModelMatrix;\n
void main(){\n
gl_Position = u_ModelMatrix * a_Position;\n
}\n
`
var FSHADER_SOURCE = `
void main(){\n
gl_FragColor = vec4(1.0,0.0,0.0,1.0);\n
}
`
var ANGLE = 60.0;// 旋转角度
var Tx = 0.5;//平移
function main () {
var canvas = document.getElementById('webgl');
var gl = getWebGLContext(canvas);
gl.clearColor(0.0,0.0,0.0,1.0);
if(!initShaders(gl,VSHADER_SOURCE,FSHADER_SOURCE)) {
return;
}
var n = initVertexBuffers(gl);
gl.clear(gl.COLOR_BUFFER_BIT);// 清空缓存区
gl.drawArrays(gl.LINE_LOOP,0,n);// 绘制三角形
}
main();
function initVertexBuffers(gl) {
var vertices = new Float32Array([0.0,0.5,-0.5,-0.5,0.5,-0.5]); // 三个顶点坐标数据
var n = 3; //顶点个数为3
var vertexBuffer = gl.createBuffer();
if (!vertexBuffer) {
console.log('Failed to create the buffer object');
return -1;
}
// 将顶点数据写入缓存区
gl.bindBuffer(gl.ARRAY_BUFFER,vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER,vertices,gl.STATIC_DRAW);
var a_Position = gl.getAttribLocation(gl.program,'a_Position');
gl.vertexAttribPointer(a_Position,2,gl.FLOAT,false,0,0);
gl.enableVertexAttribArray(a_Position);
//模型矩阵
var modelMatrix = new Matrix4();
/* 先平移后旋转
modelMatrix.setRotate(ANGLE,0,0,1);
modelMatrix.translate(Tx,0,0);
*/
/* 先旋转后平移 */
modelMatrix.setTranslate(Tx,0,0);
modelMatrix.rotate(ANGLE,0,0,1);
// 将模型矩阵传给attribute变量
var u_ModelMatrix = gl.getUniformLocation(gl.program,'u_ModelMatrix');
gl.uniformMatrix4fv(u_ModelMatrix,false,modelMatrix.elements);
return n;
}
//示例二(分别平移、旋转、缩放三角形,自己手写矩阵,仅供参考)
var VSHADER_SOURCE = `
attribute vec4 a_Position;\n
uniform mat4 u_xformMatrix;\n
void main(){\n
gl_Position = u_xformMatrix * a_Position;\n
}\n
`
var FSHADER_SOURCE = `
void main(){\n
gl_FragColor = vec4(1.0,0.0,0.0,1.0);\n
}
`
var ANGLE = 90.0;
function main () {
var canvas = document.getElementById('webgl');
var gl = getWebGLContext(canvas);
gl.clearColor(0.0,0.0,0.0,1.0);
if(!initShaders(gl,VSHADER_SOURCE,FSHADER_SOURCE)) {
return;
}
var n = initVertexBuffers(gl);
gl.clear(gl.COLOR_BUFFER_BIT);// 清空缓存区
gl.drawArrays(gl.LINE_LOOP,0,n); // 绘制三角形
}
main();
function initVertexBuffers(gl) {
var vertices = new Float32Array([0.0,0.5,-0.5,-0.5,0.5,-0.5]); // 三个顶点的坐标数据
var n = 3;// 顶点个数为3
var vertexBuffer = gl.createBuffer();
if (!vertexBuffer) {
console.log('Failed to create the buffer object');
return -1;
}
// 将顶点数据写入缓存区
gl.bindBuffer(gl.ARRAY_BUFFER,vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER,vertices,gl.STATIC_DRAW);
var a_Position = gl.getAttribLocation(gl.program,'a_Position');
gl.vertexAttribPointer(a_Position,2,gl.FLOAT,false,0,0);
gl.enableVertexAttribArray(a_Position);
/* 旋转矩阵
var radian = Math.PI * ANGLE / 180.0;
var cosB = Math.cos(radian);
var sinB = Math.sin(radian);
var xformMatrix = new Float32Array([
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
])
*/
/* 平移矩阵
var Tx = 0.5,Ty = 0.5,Tz = 0.5;
var xformMatrix = new Float32Array([
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
Tx, Ty, Tz, 1.0
])
*/
// 缩放矩阵
var Sx = 1.0, Sy = 1.5, Sz = 1.0;
var xformMatrix = new Float32Array([
Sx, 0.0, 0.0, 0.0,
0.0, Sy, 0.0, 0.0,
0.0, 0.0, Sz, 0.0,
0.0, 0.0, 0.0, 1.0
])
/* 无任何变换的矩阵
var xformMatrix = new Float32Array([
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.0, 0.0, 0.0, 1.0
]);
*/
// 将变换矩阵写入传给attribute变量
var u_xformMatrix = gl.getUniformLocation(gl.program,'u_xformMatrix');
gl.uniformMatrix4fv(u_xformMatrix,false,xformMatrix);
return n;
}
详解矩阵【矩阵的算法是表达式算法的另一种表达方式】
矩阵和矢量的乘法
矢量就是由多个分量组成的对象,如顶点的坐标(0, 0.5, 1);
矩阵和矢量的乘法可以写成如下形式
可见将矩阵和矢量相乘,就可以获得一个新的矢量。注意矩阵的乘法不符合交换律,A*B不等于B*A
上式中的矩阵为3*3矩阵,矩阵右侧是一个由x,y,z组成的矢量(表示点的坐标)。注意:只有在矩阵的列数和矢量的行数相等时,才可以将两者相乘
矩阵和等式的转换
假设点p的坐标为(x, y, z, 1),平移之后的点p’ 的坐标为(x’, y’, z’, 1);
如下表示:
矩阵的元素都是等式中的系数。平移矩阵必须为4*4矩阵,因为平移是加上一个常量,第四列均为常量。
一旦你熟悉这种矩阵表示法,进行变换就变得非常简单了。如果你不熟悉,你应当花点时间好好的理解它,变换矩阵的概念在三维图形学中非常重要
平移矩阵
缩放矩阵
旋转矩阵
旋转的数学表达式推导:
利用三角函数两角和公式,可得:
旋转矩阵为3*3矩阵,平移矩阵是4*4矩阵,为使他们阶数相同,比较如下等式:
JavaScript表示矩阵
JavaScript并没有专门表示矩阵的类型,你需要使用类型化数组Float32Array 存储矩阵的每个元素,但矩阵是二维的,其元素按照行和列进行排列,而数组是一维的,其元素只能排成一行。我们可以按照两种方式在数组中存储矩阵元素:按行主序(row major order)和按列主序(column major order)
WebGL和OpenGL一样,矩阵元素是按列主序存储在数组中的[a, e, i, m, b, f, j, n, c, …].