【零基础学WebGL】矩阵变换 您所在的位置:网站首页 向量相乘的坐标表示 【零基础学WebGL】矩阵变换

【零基础学WebGL】矩阵变换

2023-12-23 17:18| 来源: 网络整理| 查看: 265

矩阵变量

GLSL ES语言中,通过关键字mat2、mat3、mat4声明矩阵,并提供内置函数mat2()、mat3()、mat4()构造矩阵。

支持三种方式初始化矩阵:

从第一列开始,逐列指定元素的值。这里与,高数中习惯按照逐行书写矩阵不同。 只传入一个值,作为对角矩阵的对角线值; 传入多个向量;

如下代码初始化2x2单位矩阵:

// 按照列优先,逐列指定每个元素 mat2 aMat = mat2( 1.0, 0, // 第一列 0, 1.0 // 第二列 ); // 构造对角矩阵,值是对角线的值 mat bMat = mat2(1.0); vec2 aVec = vec2(1.0, 0); vec2 bVec = vec2(0, 1.0); // 使用向量构建矩阵 mat cMat = mat2(aVec, bVec);

可以向二维数组一样,使用索引下标访问元素。

mat2 aMat = mat2( 1.0, 0, // 第一列 0, 1.0 // 第二列 ); vec2 aVec = mat2[0]; // 访问第一列的向量,[1.0, 0] vec2 bVec = mat2[0][1]; // 访问第一列第二行,0 mat2[1] = vec2(1.0, 0); // 给第二列重新赋值 矩阵运算 矩阵与数字加减

矩阵与数字的加减,也就是矩阵的每个元素与数字进行加减。示例如下:

mat2 aMat = mat2( 1.0, 0, // 第一列 0, 1.0 // 第二列 ); // 矩阵aMat加1之后, 得到新的矩阵 // [ // 2.0, 1, // 第一列 // 1, 2.0 // 第二列 // ] // mat2 bMat = aMat + 1; 矩阵与数字相乘

与矩阵与数字加减类似,矩阵与数字相乘,就是矩阵每个元素与数字相乘。示例如下:

mat2 aMat = mat2( 1.0, 0, // 第一列 0, 1.0 // 第二列 ); // 矩阵aMat乘以2之后, 得到新的矩阵 // [ // 2.0, 0, // 第一列 // 0, 2.0 // 第二列 // ] // mat2 bMat = aMat*2; 矩阵与矩阵相乘

回顾下线性代数关于矩阵相乘的知识。假设有A、B两个矩阵,有如下定义:

相乘的前提条件是,A矩阵的列数需等于B矩阵的行数; 矩阵相乘不遵守交换律,也就是说A⋅B ≠ B⋅A; 矩阵相乘遵守结合律,A.B.C = A.(B.C); A⋅B 也是一个矩阵,乘积矩阵的行数等于A矩阵,结果矩阵的列数等于B矩阵; 乘积矩阵的第n行,第m列的结果等于,A矩阵的第n行与B矩阵的第m列对应值的乘积之和。

具象地举例,A矩阵是3行3列,B矩阵是3行3列,乘积矩阵的第2行第2列的计算过程如下:

c5的计算结果 c5 = a4b2 + a5b5 + a6*b8c5 = a4b2 + a5b5 + a6*b8

下面用GLSL表达如上矩阵相乘:

mat2 aMat = mat2( a1, a4, a7, a2, a5, a8, a3, a6, a9 ); mat2 bMat = mat2( b1, b4, b7, b2, b5, b8, b3, b6, b9, ); // cMat = mat2( // c1, c4, c7, // c2, c5, c8, // c3, c6, c9 // ); mat2 cMat = aMat * bMat; 空间变换

向量可以用来表示坐标、颜色等信息。WebGL可以用过vec2、vec3、vec4关键字声明向量。

vec2 aVec2 = vec2(1.0, 1.0); vec3 aVec3 = vec3(1.0, 1.0, 1.0); vec4 aVec4 = vec4(1.0, 1.0, 1.0, 1.0);

矩阵可以与向量相乘,计算过程和矩阵与矩阵相乘是一致的,因为向量可以看成多行单列矩阵。下图演示

3x3的矩阵与具有三个分量的向量相乘,结果也是一个向量。

下面用GLSL表达矩阵与向量相乘:

mat2 aMat = mat2( a1, a4, a7, a2, a5, a8, a3, a6, a9 ); vec3 aVec = vec3(b1, b4, b7); // cMat = mat2( // c1, c4, c7, // c2, c5, c8, // c3, c6, c9 // ); mat2 cMat = aMat * bMat;

矩阵和向量相乘可以表达几何变换过程。矩阵与向量相乘,除了可以表示平移、缩放、旋转这三种基本几何变换,还可以表示组合变换。

平移

点p坐标是(x,y),经过x方向移动tx,y方向移动ty之后,新的坐标p1是(x + tx,y + ty)。平移矩阵是:

matrix = [ 1, 0, 0, // 第一列 0, 1, 0, // 第二列 tx, ty, 1 // 第三列 ]

为了满足矩阵相乘的条件,给原坐标p额外添加一个分量,变成(x,y,1)。矩阵相乘的过程如下图:

缩放

点p坐标是(x,y),经过x方向缩放sx,y方向缩放sy之后,新的坐标p1是(x * sx,y * sy)。缩放矩阵是:

matrix = [ sx, 0, 0, // 第一列 0, sy, 0, // 第二列 0, 0, 1 // 第三列 ]

缩放变换过程,可以使用矩阵相乘进行如下表示:

旋转

点p坐标是(x,y),旋转弧度a之后,新的坐标p1是(xcos(a) + ysin(a),x*-sin(a) + y*cos(a))。缩放矩阵是:

matrix = [ cos(a), -sin(a), 0, sin(a), cos(a), 0, 0, 0, 1 ]

旋转变换过程,可以使用矩阵相乘进行如下表示:

组合

实际运用中,往往同时多种几何变换。假设坐标向量是D,平移矩阵是A、缩放矩阵是B、旋转矩阵C,那么坐标D经过平移、缩放、和旋转后的坐标向量如何表示了?

由于矩阵相乘遵循结合律,因此(C.(B.(A.D))) = (C.B.A).D,也就是,可以先把变换矩阵相乘,然后再与坐标向量相乘。这样的好处是,无论经过多少次变换,只需要向webgl传送一个组合矩阵,即使变换规则发生改变,着色器代码也无需变更。

如下代码,声明类Matrix3,提供了二维矩阵变换常用方法。我们先在js端计算组合矩阵combinationMatrix,然后通过uniformMatrix3fv传递给着色器代码。我们可以任意修改组合变换过程,着色器代码不需要做修改。

const vertextSource = ` attribute vec2 a_positon; uniform mat3 u_matrix; void main(void) { gl_Position = vec4((u_matrix * vec3(a_position, 1)).xy, 0, 1); } `; class Matrix3 { /** * 列优先计算矩阵相乘 * @param left 左矩阵,3x3 * @param right 右矩阵,3x3 */ static multiply(left: Array, right: Array) { const combination = []; for (let i = 0; i < 3; i++) { // 列 const rightColumn = right.slice(i * 3, i * 3 + 3); for (let j = 0; j < 3; j++) { // 行 const leftColumn = [left[j], left[j + 3], left[j + 6]]; const result = leftColumn.reduce((sum, leftItem, index) => sum + leftItem * rightColumn[index], 0) combination.push(result); } } return combination; } static identity() { return [ 1, 0, 0, 0, 1, 0, 0, 0, 1, ]; } static translation(matrix: Array, tx: number, ty: number) { return Matrix3.multiply( matrix, [ 1, 0, 0, 0, 1, 0, tx, ty, 1, ] ); } static rotation(matrix: Array, angleInRadians: number) { var c = Math.cos(angleInRadians); var s = Math.sin(angleInRadians); return Matrix3.multiply( matrix, [ c,-s, 0, s, c, 0, 0, 0, 1, ] ); } static scaling(matrix: Array, sx: number, sy: number) { return Matrix3.multiply( matrix, [ sx, 0, 0, 0, sy, 0, 0, 0, 1, ] ); } }; const rotatedMatrix = Matrix3.rotation(Matrix3.identity(), Math.PI / 2); const scaledMatrix = Matrix3.scaling(rotatedMatrix, 2, 1); const translatedMatrix =Matrix3.translation(scaledMatrix, 10, 10); const uMatrix = gl.getUniformLocation(program, 'u_matrix'); gl.uniformMatrix3fv(uMatrix, false, translatedMatrix); 坐标系统变换

最后,我们举个实际的运用示例,感受下矩阵变换的意义。

在之前【零基础学WebGL】绘制图片,我们提到WebGL着色器代码的坐标系统是规范化设备坐标系,原点在画布中心,x轴方向从左到右,y轴方向从下向上,范围都是从-1到1。

但是,人类还是习惯使用屏幕坐标系,原点在画布左上角,x长度是画布宽度,y长度是画布高度 ,单位是px。

比如,我们想在宽300,高200的画布上绘制一个左上角坐标是(10,10),宽度是40的矩形。那么坐标改如何变换了?

首先,我们需求得屏幕系统转换到归一化的设备坐标系的变换矩阵。假设画布宽度width,高度height,变换过程如下:

屏幕坐标系统,经过x轴缩放1/width,y轴缩放1/height,范围变成0-1。用矩阵表示为:

[ 1 / width, 0, 0, 0, 1 / height, 0, 0, 0, 1, ];

然后,将坐标系放大2倍,使得xy范围变成0-2。用矩阵表示为:

[ 2, 0, 0, 0, 2, 0, 0, 0, 1, ];

然后,将x轴向反方向平移1个单位,y轴向反方向平移1个单位。用矩阵表示为:

[ 1, 0, -1, 0, 1, -1, 0, 0, 1, ];

最后,对y轴进行翻转,实际上也是缩放变换。x轴不变,y轴缩放-1。用矩阵表示为:

[ 1, 0, 0, 0, -1, 0, 0, 0, 1, ];

我们可以把上述变换过程,使用一个组合矩阵表达。

class Matrix3 { ... static projection(canvasWidth: number, canvasHeight: number) { const flipY = Matrix3.scale(Matrix3.identity(), 1, -1); // 翻转Y轴 const moveMinusOne = Matrix3.translate(flipY, -1, -1); // XY向反方向移动1个单位 const scaleDouble = Matrix3.scale(moveMinusOne, 2, 2); // XY放大两倍 const scaleCanvas = Matrix3.scale(scaleDouble, 1/ canvasWidth, 1/ canvasHeight); // 按照画布比例缩放 return scaleCanvas; } };

进一步,我们可以封装一个绘制矩形的方法。

const drawRectangle = (x: number, y: number, width: number, height: number) => { const positionBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); const postionData = [ x, y, x + width, y, x, y + height, x, y + height, x + width, y, x + width, y + height ]; gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(postionData), gl.STATIC_DRAW); const aPosition = gl.getAttribLocation(program, 'a_position'); gl.enableVertexAttribArray(aPosition); gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, 0, 0); const uMatrix = gl.getUniformLocation(program, 'u_matrix'); const projectionMatrix = Matrix3.projection(gl.canvas.width, gl.canvas.height) gl.uniformMatrix3fv(uMatrix, false, projectionMatrix); gl.drawArrays(gl.TRIANGLES, 0, 6); }


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有