一 将三个变换集合成为一个矩阵
平移、旋转、缩放的变换还受先后顺序的影响,执行顺序不同,则得到的结果也不同。
用一个矩阵来代表三个变换(原理就不放在实践篇了,可自行扒原理)
var m3 = {
translation: function (tx, ty) {
return [
1, 0, 0,
0, 1, 0,
tx, ty, 1
]
},
rotation: function (angleInRadians) {
var c = Math.cos(angleInRadians);
var s = Math.sin(angleInRadians);
return [
c, -s, 0,
s, c, 0,
0, 0, 1
]
},
scaling: function (sx, sy) {
return [
sx, 0, 0,
0, sy, 0,
0, 0, 1
]
},
multiply: function (a, b) {
var a00 = a[0 * 3 + 0];
var a01 = a[0 * 3 + 1];
var a02 = a[0 * 3 + 2];
var a10 = a[1 * 3 + 0];
var a11 = a[1 * 3 + 1];
var a12 = a[1 * 3 + 2];
var a20 = a[2 * 3 + 0];
var a21 = a[2 * 3 + 1];
var a22 = a[2 * 3 + 2];
var b00 = b[0 * 3 + 0];
var b01 = b[0 * 3 + 1];
var b02 = b[0 * 3 + 2];
var b10 = b[1 * 3 + 0];
var b11 = b[1 * 3 + 1];
var b12 = b[1 * 3 + 2];
var b20 = b[2 * 3 + 0];
var b21 = b[2 * 3 + 1];
var b22 = b[2 * 3 + 2];
return [
b00 * a00 + b01 * a10 + b02 * a20,
b00 * a01 + b01 * a11 + b02 * a21,
b00 * a02 + b01 * a12 + b02 * a22,
b10 * a00 + b11 * a10 + b12 * a20,
b10 * a01 + b11 * a11 + b12 * a21,
b10 * a02 + b11 * a12 + b12 * a22,
b20 * a00 + b21 * a10 + b22 * a20,
b20 * a01 + b21 * a11 + b22 * a21,
b20 * a02 + b21 * a12 + b22 * a22,
];
},
}
当变换都集合在一个矩阵里,那么着色器的代码就变成了这样:
<script id="vertex-shader" type="x-shader/x-vertex">
attribute vec2 a_position;
uniform vec2 u_resolution;
uniform mat3 u_matrix;
void main(){
vec2 position = (u_matrix * vec3(a_position,1)).xy;
vec2 zeroToOne = position / u_resolution;
vec2 zeroToTwo = zeroToOne * 2.0;
vec2 clipSpace = zeroToTwo - 1.0;
gl_Position = vec4(clipSpace * vec2(1,-1),0,1);
}
</script>
设置矩阵变换的参数:
var translationMatrix = m3.translation(translations[0], translations[1]);
var rotationMatrix = m3.rotation(angleInRadians);
var scaleMatrix = m3.scaling(scale[0], scale[1]);
var matrix = m3.multiply(translationMatrix, rotationMatrix);
matrix = m3.multiply(matrix, scaleMatrix);
webgl.uniformMatrix3fv(matrixUniformLocation, false, matrix);
矩阵变换与之前分别做的三个变换的最终结果是相同的。
如果要改变变换顺序(上边那个是先缩放再旋转再平移的),那么只需要改变一下矩阵运算即可(下边这个是先平移再旋转再缩放),用滑块呈现的不同效果就是,上边的旋转中心在F的左上角,那下边这个旋转中心就是在F外的某一点(大概是[100,150]),缩放中心也如此,平移倒是没有像旋转跟缩放那么大的变化,:
var matrix = m3.multiply(scaleMatrix, rotationMatrix);
matrix = m3.multiply(matrix, translationMatrix);
二 改变“F”的原点
按照原来的变换顺序,F是绕着它的左上角进行旋转和缩放的,而我们可以通过矩阵变换来改变旋转的中心
//将原点移动到“F”中心
var moveOriginMatrix = m3.translation(-50, -75);
var matrix = m3.multiply(translationMatrix, rotationMatrix);
matrix = m3.multiply(matrix, scaleMatrix);
matrix = m3.multiply(matrix, moveOriginMatrix);
三 投影矩阵
也就是,简化屏幕像素坐标转换成裁剪空间坐标的着色器代码。
首先我们看一下转换的步骤:
1.屏幕像素坐标转换到 0-1;(缩放)
2.再将0-1转换成0-2;(缩放)
3.最后转换成裁剪空间坐标 [-1,1];(平移)
4.翻转Y轴。(缩放)
即,假设一个屏幕坐标为(x,y)的点P,要将其转换成为裁剪空间坐标,即需要:
1. x / u_resolution.x , y / u_resolution.y
2. (x / u_resolution.x) * 2 , (y / u_resolution.y) * 2
3. (x / u_resolution.x) * 2 - 1 , (y / u_resolution.y) * 2 - 1
4. (x / u_resolution.x) * 2 - 1 , -(y / u_resolution.y) * 2 + 1
因此,构成的投影矩阵如下:
var m3 = {
projection: function (width, height) {
return [
2 / width, 0, 0,
0, -2 / height, 0,
-1, 1, 1
]
},
...
}
简化顶点着色器:
<script id="vertex-shader" type="x-shader/x-vertex">
attribute vec2 a_position;
uniform mat3 u_matrix;
void main(){
gl_Position = vec4(u_matrix * vec3(a_position,1).xy,0,1);
}
</script>
设置矩阵变换:
var projectionMatrix = m3.projection(webgl.canvas.clientWidth, webgl.canvas.clientHeight);
//执行顺序一般从后往前
var matrix = m3.multiply(projectionMatrix, translationMatrix);
matrix = m3.multiply(matrix, rotationMatrix);
matrix = m3.multiply(matrix, scaleMatrix);
四 层次变换
构造五个“F”,每一个F相对于前一个F进行变换。
1.构造五个“F”,那就是要对“F”进行复制,就要用到单位矩阵
var m3 = {
identity: function () {
return [
1, 0, 0,
0, 1, 0,
0, 0, 1
]
},
2.后一个“F”是基于前一个F做变换:
var matrix = m3.identity();
matrix = m3.multiply(matrix, projectionMatrix);
for (var i = 0; i < 5; i++) {
matrix = m3.multiply(matrix, translationMatrix);
matrix = m3.multiply(matrix, rotationMatrix);
matrix = m3.multiply(matrix, scaleMatrix);
webgl.uniformMatrix3fv(matrixUniformLocation, false, matrix);
webgl.drawArrays(webgl.TRIANGLES, 0, 18)
}
结果如下:
PS:旋转时旋转中心在第一个F的左上角。平移跟缩放应该都是基于前一个F进行的变换。
五 执行顺序的两种解释(自我理解,详细的还是扒原理吧)
1. 从后往前的解释:以物体为中心,物体在进行矩阵变换
2. 从前往后的解释:以画布(坐标)为中心,是画布在进行矩阵变换,类似于你在一个框定的视口内(手机屏幕)玩手机里的某个图形(平移,放大)
六 clientWidth, clientHeight以及width,height
大多数情况下,有关于画布的大小使用canvas.width和canvas.height;但要计算长宽比时一般利用canvas.clientWidth和canvas.clientHeight,比如上述投影矩阵projection中传递进去的参数。
七 矩阵变换库
不管二维三维,矩阵变换一般都可引入外部库进行函数的调用,只用了解它们的调用方式就好了,在使用过程中也要注意它们的执行顺序。