WebGL矩阵变换

目录

变换矩阵:旋转

变换矩阵:平移

4×4的旋转矩阵 

示例代码:

gl.uniformMatrix4fv()规范

 平移:相同的策略

 变换矩阵:缩放


 

变换矩阵:旋转

对于简单的变换,你可以使用数学表达式来实现。但是当情形逐渐变得复杂时,你很快就会发现利用表达式运算实际上相当繁琐。比如,下图显示了一个“旋转后平移”的过程,如果使用数学表达式,我们就需要两种变换的等式叠加,获得一个新的等式,然后在顶点着色器中实现。

WebGL矩阵变换_第1张图片

 但是如果这样做,每次都需要进行一次新的变换,我们就需要重新求取一个新的等式,然后实现一个新的着色器,这当然很不科学。好在我们可以使用另一个数学工具——变换矩阵来完成这项工作。变换矩阵非常适合操作计算机图形。

如下图所示,矩阵是一个矩形的二维数组,数字按照行(水平方向)和列(垂直方向)排列,数字两侧的方括号表示这些数字是一个整体(一个矩阵)。我们将使用矩阵来表示前面的计算过程。

WebGL矩阵变换_第2张图片

在解释如何使用变换矩阵来替代数学表达式之前,你需要理解矩阵和矢量的乘法。矢量就是由多个分量组成的对象,比如顶点的坐标(0.0,0.5,1.0)。

矩阵和矢量的乘法可以写成如下等式一的形式(虽然乘号“×”通常被忽略不写,但是为了强调,本书中我们总是明确地将这个符号写出来)。可见,将矩阵(中间)和矢量(右边)相乘,就获得了一个新的矢量(左边)。注意矩阵的乘法不符合交换律,也就是说,A×B和B×A并不相等。

WebGL矩阵变换_第3张图片

上式中的这个矩阵具有3行3列,因此又被称为3×3矩阵。矩阵右侧是一个由x、y、z组成的矢量(为了与矢量相乘,矢量被写成列的形式,其仍然表示点的坐标)。矢量具有3个分量,因此被称为三维矢量。再次说明,数字两侧的方括号表示这些数字是一个整体(一个矢量)。

在本例中,矩阵与矢量相乘得到的新矢量,其三个分量为x'、y'、z',其值如下等式二所示。注意,只有在矩阵的列数与矢量的行数相等时,才可以将两者相乘。 

        x'=ax+by+cz

        y'=dx+ey+fz

        z'=gx+hy+iz

 现在,为了理解矩阵是如何代替数学表达式的,下面将矩阵等式与数学表达式(如下等式三,即WebGL非矩阵变换_山楂树の的博客-CSDN博客 中的等式R4)进行比较。

等式三:

        x' = x cosβ - y sinβ

        y' = x sinβ + y cosβ

        z' = z

与比较关于x'的表达式进行比较:

        x'=ax+by+cz

        x'=x cos β-y sin β

这样的话,如果设a=cosβ,b=-sinβ,c=0,那么这两个等式就完全相同了。再来看一下y':

        y'=dx+ey+fz

        y'=x sin β+y cos β

这样的话,设d=sinβ,e=cosβ,f=0,两个等式也就完全相同了。最后的关于z'的等式更简单,设g=0,h=0,i=1即可。

 接下来,将这些结果代入到等式一中,得到等式四:

WebGL矩阵变换_第4张图片

 

这个矩阵就被称为变换矩阵(transformation matrix),因为它将右侧的矢量(x,y,z)“变换”为了左侧的矢量(x',y',z')。上面这个变换矩阵进行的变换是一次旋转,所以这个矩阵又可以被称为旋转矩阵

可以看到,等式三中矩阵的元素都是等式二中的系数。一旦你熟悉这种矩阵表示法,进行变换就变得非常简单了。变换矩阵的概念在三维图形学中非常重要。

变换矩阵在三维计算机图形学中应用得如此广泛,以致于着色器本身就实现了矩阵和矢量相乘的功能。但是,在我们修改着色器代码以采用矩阵之前,先来快速浏览一遍(除了旋转矩阵的)其他几种变换矩阵。

变换矩阵:平移

显然,如果我们使用变换矩阵来表示旋转变换,我们就也应该使用它来表示其他变换,比如平移。比较一下等式二和平移的数学表达式,如下所示:

WebGL矩阵变换_第5张图片

 这里第二个等式的右侧有常量项Tx,第一个等式中没有,这意味着我们无法通过使用一个3×3的矩阵来表示平移。为了解决这个问题,我们可以使用一个4×4的矩阵,以及具有第4个分量(通常被设为1.0)的矢量。也就是说,我们假设点p的坐标为(x,y,z,1),平移之后的点p'的坐标为(x',y',z',1),如等式等式五所示:

WebGL矩阵变换_第6张图片

该矩阵的乘法的结果如下等式六:

x'=ax+by+cz +d

y'=ex+fy+gz+h

z'=ix+jy+kz + l

1=mx+ny+oz+ p

根据最后一个式子1=mx+ny+oz+p,很容易求算出系数m=0,n=0,o=0,p=1。这些方程都有常数项d、h、l和p,看上去比较适合平移等式(因为平移等式也有常数项)。平移等式如下所示,我们将它与等式六进行比较:

x'=x+Tx

y'=y+Ty

z'=z+Tz

比较x',可知a=1,b=0,c=0,d=Tx;类似地,比较y',可知e=0,f=1,g=0,h=Ty;比较z',可知i=0,j=0,k=1,l=Tz。这样,你就可以写出表示平移的矩阵,又称为平移矩阵,如等式七所示:

WebGL矩阵变换_第7张图片

4×4的旋转矩阵 

至此,我们已经成功地创建了一个旋转矩阵和一个平移矩阵,这两个矩阵的作用与此前示例程序中的数学表达式的作用是一样的,那就是计算变换后的顶点坐标。在“先旋转再平移”的情形下,我们需要将两个矩阵组合起来(你应该记得,这也是我们使用矩阵的初衷),然而旋转矩阵(3×3矩阵)与平移矩阵(4×4矩阵)的阶数不同。我们不能把两个阶数不一样的矩阵组合起来,所以得使用某种手段,使这两个矩阵的阶数一致。

将旋转矩阵从一个3×3矩阵转变为一个4×4矩阵,只需要将等式三等式六比较一下即可。 

        x'=x cos β-y sin β

        y'=x sin β+y cos β

        z'=z


        x'=ax+by+cz+d

        y'=ex+fy+gz+h

        z'=ix+jy+kz+l

        1=mx+ny+oz+p

例如,当你通过比较x'=x cosβ- y sinβ与x'=ax+by+cz+d时,可知a=cosβ,b=-sinβ,c=0,d=0。以此类推,求得y'和z'等式中的系数,最终得到4×4的旋转矩阵,如等式八所示: 

WebGL矩阵变换_第8张图片

这样,我们就可以使用相同阶数(4×4)的矩阵来表示平移和旋转,实现了最初的目标! 

示例代码:

 在创建了4×4的旋转矩阵之后,我们使用旋转矩阵来重写之前的示例程序,令三角形绕Z轴逆时针旋转90度。例如下显示了本例的代码,其运行结果与 WebGL非矩阵变换_山楂树の的博客-CSDN博客 的旋转实例完全一致。

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' +
  '}\n';

var ANGLE = 90.0;
// var tx = 0.5, ty = 0.5, tz=0  平移矩阵所用

function main() {
  var canvas = document.getElementById('webgl');

  var gl = getWebGLContext(canvas);
  if (!gl) {
    console.log('Failed to get the rendering context for WebGL');
    return;
  }

  if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
    console.log('Failed to intialize shaders.');
    return;
  }
 
  var n = initVertexBuffers(gl);
  if (n < 0) {
    console.log('Failed to set the positions of the vertices');
    return;
  }

  var radian = Math.PI * ANGLE / 180.0; // Convert to radians
  var cosB = Math.cos(radian), 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 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 u_xformMatrix = gl.getUniformLocation(gl.program, 'u_xformMatrix');
  if (!u_xformMatrix) {
    console.log('Failed to get the storage location of u_xformMatrix');
    return;
  }
  gl.uniformMatrix4fv(u_xformMatrix, false, xformMatrix);

  gl.clearColor(0, 0, 0, 1);

  gl.clear(gl.COLOR_BUFFER_BIT);

  gl.drawArrays(gl.TRIANGLES, 0, n);
}

function initVertexBuffers(gl) {
  var vertices = new Float32Array([
    0, 0.5,   -0.5, -0.5,   0.5, -0.5
  ]);
  var n = 3; // The number of vertices

  var vertexBuffer = gl.createBuffer();
  if (!vertexBuffer) {
    console.log('Failed to create the buffer object');
    return false;
  }

  gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
  gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

  var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
  if (a_Position < 0) {
    console.log('Failed to get the storage location of a_Position');
    return -1;
  }
  gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);

  gl.enableVertexAttribArray(a_Position);

  return n;
}

首先来看看顶点着色器:

WebGL矩阵变换_第9张图片

u_xformMatrix变量表示等式八中的旋转矩阵,a_Position变量表示顶点的坐标(即等式八中右侧的矢量),二者相乘得到变换后的顶点坐标,与等式八中相同。 

示例程序中,你可以在一行代码中完成矢量相加的运算(gl_Position=a_Position+u_Translate)。同样,你也可以在一行代码中完成矩阵与矢量相乘的运算(gl_Position=u_xformMatrix * a_Position)。这时因为着色器内置了常用的矢量和矩阵运算功能,这种强大特性正是专为三维计算机图形学而设计的。

由于变换矩阵是4×4的,GLSL ES需要知道每个变量的类型,所以我们将u_xformMatrix定义为mat4类型。如你所料,mat4类型的变量就是4×4的矩阵。

JavaScript按照等式八计算旋转矩阵,然后将其传给u_xformMatrix。

WebGL矩阵变换_第10张图片

 这段代码首先计算了90度的正弦值和余弦值这两个值需要被用来构建旋转矩阵;之后创建了Float32Array类型的xformMatrix变量表示旋转矩阵。与GLSL ES不同,JavaScript并没有专门表示矩阵的类型,所以你需要使用类型化数组Float32Array。我们在数组中存储矩阵的每个元素,但问题是:矩阵是二维的,其元素按照行和列进行排列,而数组是一维的,其元素只是排成一行。这里,我们可以按照两种方式在数组中存储矩阵元素:按行主序(row major oder)和按列主序(column major order),如下图所示。

WebGL矩阵变换_第11张图片

WebGL和OpenGL一样,矩阵元素是按列主序存储在数组中的。比如,图3.27所示的矩阵存储在数组中就是这样的:[a, e, i, m, b, f, j, n, c, g, k, o, d, h, l, p] 。本例中,旋转矩阵也是按照这样的顺序存储在Float32Array类型的数组中的。

最后,我们使用gl.uniformMatrix4fv()函数,将刚刚生成的数组传给u_xformMatrix变量。注意,函数名的最后一个字母是v,表示它可以向着色器传输多个数据值。

gl.uniformMatrix4fv()规范

WebGL矩阵变换_第12张图片

 平移:相同的策略

如你所见,4×4的矩阵不仅可以用来表示平移,也可以用来表示旋转。不管是平移还是旋转,你都使用如下形式来进行矩阵和矢量的运算以完成变换:<新坐标>=<变换矩阵> * <旧坐标>,比如在着色器中:

这意味着,如果我们改变数组xformMatrix中的元素,使之成为一个平移矩阵,那么就可以实现平移操作,其效果就和之前使用数学表达式进行的平移操作一样。 

因此,修改4x4旋转矩阵代码,将旋转角度修改为与平移相关的变量:

我们还需重写创建矩阵的代码,记住,矩阵是按列主序存储的。虽然xformMatrix现在是一个平移矩阵了,但我们仍使用这个变量名。因为对于着色器而言,旋转矩阵和平移矩阵其实是一回事。最后,你不会用到ANGLE变量,把与旋转相关的代码注释掉: 

WebGL矩阵变换_第13张图片

 变换矩阵:缩放

最后,我们来学习缩放变换矩阵。仍然假设最初的点p,经过缩放操作之后变成了p'。

WebGL矩阵变换_第14张图片

假设在三个方向X轴,Y轴,Z轴的缩放因子S 

x'=S× x

y'=Sy y

z'=Sz z

将上式与等式六作比较,可知缩放操作的变换矩阵: 

WebGL矩阵变换_第15张图片

和之前的例子一样,我们只要将缩放矩阵传给xformMatrix变量,就可以直接使用4x4旋转矩阵中的着色器对三角形进行缩放操作了。下面这个示例程序会将三角形在垂直方向上拉伸到1.5倍,如图所示。 

WebGL矩阵变换_第16张图片

 WebGL矩阵变换_第17张图片

 

你可能感兴趣的:(WebGL,webgl,矩阵,线性代数)