WebGL学习系列-基本图形变换

前言
经过前面的学习,我们已经可以绘制基本的图形了。本小节将介绍基本的图形变换,介绍在WebGL中,如何对基本的图形进行平移、旋转和缩放。

平移
在前面的小节中,我们已经绘制过一个三角形,那时候,它看起来是这样的:

WebGL学习系列-基本图形变换

我们知道,在WebGL中,要绘制一个基本的图形,我们只需要指定顶点的位置、大小和颜色,然后调用drawArrays接口进行绘制即可。现在,我们想要实现对三角形进行一个平移,比如,把它移到右上角的地方,那如何实现呢?

其实仔细想想,你会发现,我们要移动一个三角形,只需要移动它的三个顶点即可,然后WebGL将会自动在新的顶点位置把三角形绘制出来。

其原理示意图如下:

WebGL学习系列-基本图形变换

为了进一步说明在程序中是如何实现平移的,我们先来看下顶点着色器代码:

// 顶点着色器代码(决定顶点的位置、大小、颜色)
var VSHADER_SOURCE = 
  'attribute vec4 a_Position;\n' +
  'uniform vec4 u_Translation;\n' +
  'void main() {\n' +
  '  gl_Position = a_Position + u_Translation;\n' + // 设置顶点的位置
  '  gl_PointSize = 10.0;\n' +      // 设置顶点的大小
  '}\n';

主要看到两个地方有所变动 : 
1、添加了 uniform vec4 u_Translation 定义 
2、顶点的位置使用两个 vec4 变量进行相加, gl_Position = a_Position + u_Translation

我们以前曾经使用过 attribute变量,用于直接表示顶点的位置,那时我们知道每个顶点的位置都不一样,而现在,为了对三角形进行平移,实际上是对三个顶点都进行了相同的平移,也就是说,平移的量对所有顶点是一致的,这样所有顶点共享的变量在WebGL中使用uniform 关键字进行表示。

既然定义了 uniform 变量,那么就需要对它进行赋值,对它的赋值跟对 attribute变量进行赋值的流程是完全一样的。

// 三角形顶点的平移量
var Tx = 0.5, Ty = 0.5, Tz = 0.0;
var u_Translation = context.getUniformLocation(context.program,'u_Translation');
context.uniform4f(u_Translation, Tx, Ty, Tz, 0.0);

我们定义了 Tx,Ty,Tz 表示顶点在x、y、z 轴上的位移,最后使用 uniform4f 方法对 u_Translation变量进行了赋值,这里有个地方要注意,方法 uniform4f 最后一个参数我们设置为了0.0,因为我们要确保两个vec4变量相加后,第四个参数为1。a_Position 变量第四个参数已经设置为1,所以u_Translation第四个参数只能设置为0。

最后,我们来看下平移后的效果图: 

WebGL学习系列-基本图形变换
旋转
旋转看起来比平移要复杂一些,在考虑旋转的时候,我们要考虑三个点:

绕着哪个轴旋转
旋转的方向(顺时针还是逆时针)
旋转的角度
在这里,我们以绕着Z轴逆时针旋转来推导

WebGL学习系列-基本图形变换

由上图可以看到,当一个点p(x,y,z)p(x,y,z)沿着Z轴逆时针旋转到p′(x′,y′,z′)p′(x′,y′,z′)时,可以得到p点满足如下等式: 
x=r∗cosαy=r∗sinα
x=r∗cos⁡αy=r∗sin⁡α

同理,p′p′也满足如下等式: 
x′=r∗cos(α+β)=r∗(cosα⋅cosβ−sinα⋅sinβ)y′=r∗sin(α+β)=r∗(sinα⋅cosβ+cosα⋅sinβ)
x′=r∗cos⁡(α+β)=r∗(cosα⋅cosβ−sin⁡α⋅sin⁡β)y′=r∗sin⁡(α+β)=r∗(sin⁡α⋅cos⁡β+cos⁡α⋅sin⁡β)
根据pp和p′p′的坐标等式,可以得到p′p′的新的坐标表示: 
x′=xcosβ−ysinβy′=xsinβ+ycosβz′=z
x′=xcos⁡β−ysin⁡βy′=xsin⁡β+ycos⁡βz′=z
有了以上旋转后的点的坐标公式,我们便可以编写顶点着色器代码了:

// 顶点着色器代码(决定顶点的位置、大小、颜色)
var VSHADER_SOURCE = 
  'attribute vec4 a_Position;\n' +
  'uniform float u_CosB, u_SinB;\n' +
  'void main() {\n' +
     'gl_Position.x = a_Position.x * u_CosB - a_Position.y * u_SinB;\n' +
     'gl_Position.y = a_Position.x * u_SinB + a_Position.y * u_CosB;\n' +
     'gl_Position.z = a_Position.z;\n' +
     'gl_Position.w = 1.0;\n' +
     'gl_PointSize = 10.0;\n' +      // 设置顶点的大小
  '}\n';

注意,我们可以通过 . 符号来访问一个vec4变量的x、y、z、w 四个分量值 ,绕着Z轴旋转,只需要计算x和y坐标值即可。

对于u_CosB和u_SinB,由于对三角形三个顶点而言,旋转的角度是一样的,所以变量应该是所有顶点共享,使用uniform进行声明,其赋值如下:

// 旋转的角度
var ANGLE = 30;
var radian = Math.PI * ANGLE / 180.0; //转为弧度
var cosB = Math.cos(radian);
var sinB = Math.sin(radian);

var u_CosB = context.getUniformLocation(context.program, 'u_CosB');
var u_SinB = context.getUniformLocation(context.program, 'u_SinB');
context.uniform1f(u_CosB, cosB);
context.uniform1f(u_SinB, sinB);

这段代码很简单,相信都应该明白了。

绕着Z轴逆序旋转30度的效果图: 

WebGL学习系列-基本图形变换
如果想要的是顺时针的旋转,把ANGLE设置为负数即可,大家可以拿源码改改试试,这就是三角函数的精妙之处。

以上推导的是绕着Z轴进行旋转,其实绕着x轴或者y轴也可以用类似的方式进行推导,大家有兴趣可以试着推导一下。

缩放
缩放其实是比较简单的,我们来看缩放示意图:

WebGL学习系列-基本图形变换

点p(x,y,z)p(x,y,z) 缩放后,在三个轴的缩放因子为:SxSx、SySy、SzSz,则缩放后,点p′(x′,y′,z′)p′(x′,y′,z′)的坐标如下:

x′=x∗Sxy′=y∗Syz′=z∗Sz
x′=x∗Sxy′=y∗Syz′=z∗Sz
然后我们来看下顶点着色器的代码:

// 顶点着色器代码(决定顶点的位置、大小、颜色)
var VSHADER_SOURCE = 
  'attribute vec4 a_Position;\n' +
  'uniform float a_Scale;\n' +
  'void main() {\n' +
  '  gl_Position.x = a_Position.x * a_Scale;\n' + 
  '  gl_Position.y = a_Position.y * a_Scale;\n' + 
  '  gl_Position.z = a_Position.z * a_Scale;\n' + 
  '  gl_Position.w = 1.0;\n' + 
  '  gl_PointSize = 10.0;\n' +      // 设置顶点的大小
  '}\n';

我们定义了一个uniform变量a_Scale,所有顶点都使用相同的缩放,然后对顶点的x、y、z值分别进行坐标变换。a_Scale的初始化也非常的简单。

// 三角形顶点的缩放
var scale = 0.5;
var u_Scale = context.getUniformLocation(context.program, 'a_Scale');
context.uniform1f(u_Scale, scale);

缩放后的三角形效果图:

WebGL学习系列-基本图形变换

小结
三角形的基础变换是非常重要的知识,还是要花点时间理解一下,目前使用的是简单的数学知识进行推导,其实,在图形学中,真正强大的是用矩阵进行计算,后面将会详细解说。