WebGL简单的图形变换!

957 阅读5分钟

前言

上一篇文章已经看到了“女神”的样子了,难道不想让它动一下?

我的意思是说让我们绘制的三角形进行一些变换,别想歪👀年纪轻轻脑子里都在想些森莫❓

之前的《客官,进来看看图形的几何变换》一文从理论上讲解并推导了图形变换的过程,那么本文将结合理论实操一波😃

⚡️闪现

难道不疑惑游戏中的闪现如何实现吗?这次让我们用三角形做一个“阉割版闪现”,实现效果为:当按下F键时,三角形闪现到canvas的右上方(闪现键是F我还是问的室友:P)🤩首先要明确一点,闪现“技能”对应的是我们图形变换中的“平移”这一行为。在上学时就学过,计算一个点平移后的坐标其实就是矢量相加的操作:

将点A移动至点A'后:\overrightarrow{OA'} = \overrightarrow{OA} + \overrightarrow{AA'}

所以转换成我们的代码就是:

const VertexShader = `
  attribute vec4 a_Position;
  uniform vec4 u_Translation;
  void main() {
  	gl_Position = u_Translation + a_Position;
  }
`;
// ...
function main () {
  // ...
  const a_Position = gl.getAttribLocation(gl.program, 'a_Position');
  const u_Translation = gl.getUniformLocation(gl.program, 'u_Translation');
  gl.vertexAttrib3f(a_Position, 0.0, 0.0, 0.0);
  gl.uniform4f(u_Translation, Tx, Ty, Tz, 0.0);	// Tx, Ty, Tz 为在三个分量上的平移距离
  // ...
}
// ...

这样三角形就出现在了canvas的右上角:)然后在添加键盘的监听事件⌨️

window.addEventListener('keydown', function(ev) {
  if (ev.keyCode === 70) {
    const u_Translation = gl.getUniformLocation(gl.program, 'u_Translation');	// (1)
    gl.uniform4f(u_Translation, Tx, Ty, Tz, 0.0);	// (2)

    gl.clear(gl.COLOR_BUFFER_BIT);
    gl.drawArrays(gl.TRIANGLES, 0, n);
  }
}, true);
// 记得删去上一步中在main()中添加的(1)和(2)

这样当我们按下F时,三角形就会出现在我们的右上角。

💃和Donna跳一支探戈

如果你不知道Donna是谁,那就去看一下《闻香识女人》吧:P Donna接受你的邀请之后,你要确定以下三件事情:

  1. 旋转轴(Donna 绕你哪个胳膊转呢?);
  2. 旋转方向(Donna 是按照哪个方向转呢?);
  3. 旋转角度。

首先声明一下,在WebGL中默认是右手旋转法则,所谓右手旋转法则就是:右手握拳,大拇指伸直并指向旋转轴的正方向,其余几个手指就指明了旋转的正方向(多么熟悉的手法)👍🏻图形变换一文中我们推导出了一个点旋转θ角后的坐标:

\left\{	\begin{array}{c}		x' = xcosθ - ysinθ	\\		y' = xsinθ + ycosθ	\\	\end{array}\right.

那么我们就开始让 Donna 沿正方向(逆时针)旋转45度吧。首先让我们把角度转换成弧度制,并计算出其sincos

const Angle = Math.PI * 45.0 / 180.0;
const Sin = Math.sin(Angle);
const Cos = Math.cos(Angle);

然后,修改顶点着色器:

const VertexShader = `
  attribute vec4 a_Position;
  uniform float u_Sin, u_Cos;
  void main() {
    gl_Position.x = a_Position.x * u_Cos - a_Position.y * u_Sin;
    gl_Position.y = a_Position.x * u_Sin + a_Position.y * u_Cos;
    gl_Position.z = a_Position.z;
    gl_Position.w = 1.0;
  }
`;

最后,给uniform变量传值:

var u_Sin = gl.getUniformLocation(gl.program, 'u_Sin');
var u_Cos = gl.getUniformLocation(gl.program, 'u_Cos');

gl.uniform1f(u_Sin, Sin);
gl.uniform1f(u_Cos, Cos);

然后我们就能在浏览器看到旋转后的三角形了,但是怎么变形了呢?因为我们的画布并不是一个正方形,并且我们使用的坐标是相对于画布而言的[-1.0, 1.0]的形式,所以三角形顶点的坐标与原点是相对的,而不是绝对的距离坐标原点的距离。那么我们就先把画布改成400px x 400px的一个正方形吧:)

一阵操作,索然无味😐感觉没什么特别的呢?有没有发现,我们到现在还没有用过变换矩阵呢?在这个例子中我们绕z轴旋转了45度,那么参考之前说过的绕z轴旋转的变换矩阵应该是:

\left[  \matrix {    x'  \\    y'  \\    z'  \\    1  }\right]=\left[  \matrix {    cos45° & -sin45° & 0 & 0 \\    sin45° & cos45° & 0 & 0  \\    0 & 0 & 1 & 0 \\    0 & 0 & 0 & 1  }\right]\left[  \matrix {    x \\    y \\    z \\    1  }\right]=R_z(\theta)\left[  \matrix {    x \\    y \\    z \\    1  }\right]

那就让我们来定义一下我们的变换矩阵吧:

const RotationMat = new Float32Array([
  Cos, -Sin, 0, 0,
  Sin, Cos, 0, 0,
  0.0, 0.0, 1.0, 0.0,
  0.0, 0.0, 0.0, 1.0,
]);

并且在顶点着色器中定义一个mat4类型的uniform变量接收变换矩阵:

let VSHADER_SOURCE = `
  attribute vec4 a_Position;
  uniform mat4 u_RotationMat;
  void main () {
    gl_Position = u_RotationMat * a_Position;	// 注意矩阵乘法的顺序!!!
  }
`;

将变换矩阵传递给顶点着色器:

var u_RotationMat = gl.getUniformLocation(gl.program, 'u_RotationMat');
gl.uniformMatrix4fv(u_RotationMat, false, RotationMat);

来看看效果:

又有问题了!三角形怎么顺时针旋转了45度呢?其实矩阵中存储元素分为按行主序按列主序两种,WebGLOpenGL一样,矩阵的元素是按列主序存储在数组中的。所以我们就需要改一下我们RotationMat中的元素顺序了:

const RotationMat = new Float32Array([
  Cos, Sin, 0, 0,
  -Sin, Cos, 0, 0,
  0.0, 0.0, 1.0, 0.0,
  0.0, 0.0, 0.0, 1.0,
]);

然后再看一下浏览器,就会发现三角形的旋转正常了,可以和 Donna 快乐的跳探戈了💃

🐵金箍棒

西游记中孙悟空从东海龙宫取定海神针时有一场景是,孙悟空对着定海神针说“大,大,大”,随后定海神针就开始越来越大。这对应到我们的图形变换中,就是所谓的缩放

缩放的变换矩阵:

\left[	\matrix {		s_x & 0 & 0 & 0	\\		0 & s_y & 0 & 0	\\		0 & 0 & s_z & 0	\\		0 & 0 & 0 & 1	\\	}\right]

同理,我们在顶点着色器中新建一个mat4类型的uniform变量接收缩放变换的矩阵,并且在JavaScript中将变换矩阵传到顶点着色器中。代码与上面旋转相似,故不赘述。

但还是要注意矩阵的相乘顺序!!!

结束语

平移、缩放和旋转的操作就说这么多,对于复合变换来说,其实就是三种变换的组合,将变换矩阵按顺序相乘即可:)

做一个小小的回顾,目前已经讲了三角形的绘制,以及变换。那么是否就可以写一些简单的动画了呢?比如规律性移动轨迹、键盘控制图形的大小、位置的操作。提示:动画使用 requestAnimationFrame

欢迎大家关注公众号:Refactor,后续会出更多好玩并且有用的文章分享给大家,感谢阅读~