这是我参与8月更文挑战的第8天,活动详情查看:8月更文挑战
本文标题:WebGL第二十二课:2D拉伸旋转平移实战| 8月更文挑战
复制代码
引子
前面用了很多次课,讲了矩阵在我们这里的应用。我们这次课就来搞一搞2D情况下的实战。
写一个生成模型的js文件,单独出去
所有的东西放在一个文件里写,过于看不清了。所以我们准备新建一个 models_point.js
来存放点的生成方面的代码。之所以加一个后缀point
,是因为,这个文件里生成的东西,都是要用gl.POINTS
这个画法来画。后面画三角形的时候,再新建一个文件 models_tri.js
来专门搞三角形的东西。
在页面同级目录下,新建一个 models_point.js 文件,填入以下代码:
function GetQuad_Point(xcenter, ycenter, length) {
let xMin = xcenter - length / 2;
let yMin = ycenter - length / 2;
let step = length / 20;
//
let res = [];
let xidx = 0;
let yidx = 0;
for (xidx = 0; xidx != 20; xidx++) {
for (yidx = 0; yidx != 20; yidx++) {
res.push(
xMin + xidx * step,
yMin + yidx * step,
);
}
}
return res;
}
复制代码
上面的代码没啥东西,就是生成一堆点而已。
先给出整体代码
<!doctype html>
<html>
<head>
<style>
canvas {
border: 1px solid #000000;
}
</style>
<script type="text/javascript" src="https://juejin.cn/post/models_point.js"></script>
</head>
<body>
<p>
<b>scale value:</b>
<input id="scalex" type="range" min="-2" max="2" value="1" step="0.1" oninput="updatefunc()" />
<b id="scalevaluex">0</b>
<input id="scaley" type="range" min="-2" max="2" value="1" step="0.1" oninput="updatefunc()" />
<b id="scalevaluey">0</b>
</p>
<p>
<b>offset value:</b>
<input id="offsetx" type="range" min="-1" max="1" value="0" step="0.1" oninput="updatefunc()" />
<b id="offsetvaluex">0</b>
<input id="offsety" type="range" min="-1" max="1" value="0" step="0.1" oninput="updatefunc()" />
<b id="offsetvaluey">0</b>
</p>
<p>
<b>rotate value:</b>
<input id="rotate" type="range" min="0" max="6.28" value="0" step="0.01" oninput="updatefunc()" />
<b id="rotatevalue">0</b>
</p>
<canvas id="point" style="width:300px; height:300px">
</canvas>
<script id="vertex_shader" type="myshader">
// Vertex Shader
precision mediump int;
precision mediump float;
uniform float u_x_scale;
uniform float u_x_offset;
uniform float u_y_scale;
uniform float u_y_offset;
uniform float u_rotate;
attribute vec2 a_PointVertex;
void main() {
gl_Position = vec4(a_PointVertex, 0.0, 1.0);
gl_Position.x *= u_x_scale;
gl_Position.y *= u_y_scale;
float rx = gl_Position.x * cos(u_rotate) - gl_Position.y * sin(u_rotate);
float ry = gl_Position.x * sin(u_rotate) + gl_Position.y * cos(u_rotate);
gl_Position.x = rx + u_x_offset;
gl_Position.y = ry + u_y_offset;
gl_PointSize = 3.0;
}
</script>
<script id="fragment_shader" type="myshader">
// Fragment shader
precision mediump int;
precision mediump float;
void main() {
gl_FragColor = vec4(0, 0, 0, 1.0);
}
</script>
<script type="text/javascript">
var pointCanvas = document.getElementById('point'); // 我们的纸
var gl = pointCanvas.getContext('webgl', { preserveDrawingBuffer: true }); // 我们的笔
var pointData = GetQuad_Point(0, 0, 1);
var pointCount = pointData.length / 2;
var pointArray = new Float32Array(pointData);
var buffer_id;
buffer_id = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer_id);
gl.bufferData(gl.ARRAY_BUFFER, pointArray, gl.STATIC_DRAW);
//
var vertex_shader_code = document.getElementById('vertex_shader').textContent;
console.log(vertex_shader_code);
var vertex_shader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertex_shader, vertex_shader_code);
gl.compileShader(vertex_shader);
//
var fragment_shader_code = document.getElementById('fragment_shader').textContent;
var fragment_shader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragment_shader, fragment_shader_code);
gl.compileShader(fragment_shader);
//
var program = gl.createProgram();
gl.attachShader(program, vertex_shader);
gl.attachShader(program, fragment_shader);
gl.linkProgram(program);
gl.useProgram(program);
//
var a_PointVertex = gl.getAttribLocation(program, 'a_PointVertex');
gl.vertexAttribPointer(a_PointVertex, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(a_PointVertex);
//
</script>
<script>
var u_x_offset_loc = gl.getUniformLocation(program, "u_x_offset");
var u_x_scale_loc = gl.getUniformLocation(program, "u_x_scale");
var u_y_offset_loc = gl.getUniformLocation(program, "u_y_offset");
var u_y_scale_loc = gl.getUniformLocation(program, "u_y_scale");
var u_rotate_loc = gl.getUniformLocation(program, "u_rotate");
var scaleDomX = document.getElementById("scalex");
var scaleValueDomX = document.getElementById("scalevaluex");
var scaleDomY = document.getElementById("scaley");
var scaleValueDomY = document.getElementById("scalevaluey");
var offsetDomX = document.getElementById("offsetx");
var offsetValueDomX = document.getElementById("offsetvaluex");
var offsetDomY = document.getElementById("offsety");
var offsetValueDomY = document.getElementById("offsetvaluey");
var rotateDom = document.getElementById("rotate");
var rotateValueDom = document.getElementById("rotatevalue");
function updatefunc() {
gl.uniform1f(u_x_scale_loc, parseFloat(scaleDomX.value));
gl.uniform1f(u_y_scale_loc, parseFloat(scaleDomY.value));
gl.uniform1f(u_x_offset_loc, parseFloat(offsetDomX.value));
gl.uniform1f(u_y_offset_loc, parseFloat(offsetDomY.value));
gl.uniform1f(u_rotate_loc, parseFloat(rotateDom.value));
scaleValueDomX.innerText = scaleDomX.value;
offsetValueDomX.innerText = offsetDomX.value;
scaleValueDomY.innerText = scaleDomY.value;
offsetValueDomY.innerText = offsetDomY.value;
rotateValueDom.innerText = rotateDom.value;
gl.enable(gl.CULL_FACE);
gl.enable(gl.DEPTH_TEST);
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.drawArrays(gl.POINTS, 0, pointCount);
}
updatefunc();
</script>
</script>
</body>
</html>
复制代码
注意上面的这一句:
<script type="text/javascript" src="https://juejin.cn/post/models_point.js"></script>
要引用一下我们刚才写的models_point.js
文件。
现在的运行效果就是很正常的,可以拉伸,旋转, 平移。
对 vertex_shader 进行矩阵化改造
我们知道,拉伸,旋转,平移,这三个操作,可以揉到一个矩阵里去。
这样的话, 我们就不用搞那么多 uniform 变量了。
只需要一个 uniform 变量,变量类型是矩阵类型,就足矣。
uniform float u_x_scale;
uniform float u_x_offset;
uniform float u_y_scale;
uniform float u_y_offset;
uniform float u_rotate;
复制代码
上面这些变量用一个矩阵变量代替,就可以搞定
例如下面这样:
uniform mat3 u_all;
复制代码
我们上面的uniform
变量u_all
,类型是 mat3
,就是 matrix 3
, 就是 3 * 3
的矩阵。
为什么需要 3 * 3 的矩阵,前面在提及位移
的时候,已经解释过了,不明白的小伙伴直接到前面去找一下。
我们先顺一下思路:
- 将我们传入的点的(x,y) 二维变成三维,第三维写死1。–>(x,y,1)
- 将 u_all * (x,y,1)
- 得到的结果,只拿前两个
好,根据上面的思路,vertex_shader
代码如下:
<script id="vertex_shader" type="myshader">
// Vertex Shader
precision mediump int;
precision mediump float;
uniform mat3 u_all;
attribute vec2 a_PointVertex;
void main() {
vec3 coord = u_all * vec3(a_PointVertex, 1.0);
gl_Position = vec4(coord.x, coord.y, 0.0, 1.0);
gl_PointSize = 3.0;
}
</script
复制代码
看一下,是不是舒服太多了,一下少了一堆东西。
对 js 进行矩阵化改造
传入的uniform变成了一个mat3,自然的,外部js代码,也要进行相应的改造。
假设 拉伸系数是a b, 旋转弧度是 α, 位移 是 A B。
那么最终的矩阵:
根据这个矩阵,定义一个函数:
function genMat3ForGL(a, b, alpha, A, B) {
let mat3 = [
a * Math.cos(alpha), a * Math.sin(alpha), 0,
-b * Math.sin(alpha), b * Math.cos(alpha), 0,
A, B, 1,
];
return new Float32Array(mat3);
}
复制代码
注意一下, 上面构造这个矩阵的时候,我们是一列一列的来填入的,这又是将矩阵
看做向量数组
的一个佐证。
我们先前的传入uniform变量如下:
gl.uniform1f(u_x_scale_loc, parseFloat(scaleDomX.value));
gl.uniform1f(u_y_scale_loc, parseFloat(scaleDomY.value));
gl.uniform1f(u_x_offset_loc, parseFloat(offsetDomX.value));
gl.uniform1f(u_y_offset_loc, parseFloat(offsetDomY.value));
gl.uniform1f(u_rotate_loc, parseFloat(rotateDom.value));
复制代码
需要传五个变量进去,那么矩阵化之后:
gl.uniformMatrix3fv(u_all_loc, false, genMat3ForGL(scaleDomX.value,
scaleDomY.value, rotateDom.value, offsetDomX.value, offsetDomY.value
));
复制代码
只需要使用上面新定义的函数,将这些变量揉到一个矩阵里去,就大功告成了。
同理,在js代码里,还要预先获取,这个mat3 uniform 变量的位置:
var u_all_loc = gl.getUniformLocation(program, "u_all");
复制代码
改造之后的整体代码,不给出了,诸位请自行对代码进行修修补补:最终运行的效果如下:
总结
本次课主要实战了一下,如何将拉伸旋转平移这三个操作,用矩阵代替,并且真正实战到代码里。
我们可以看出,用矩阵代替之后,代码量少了,简洁了很多。
正文结束,下面是答疑
复制代码
小丫丫说:文中有一个没有推导的东西,那就是,拉伸->旋转->平移 的整体矩阵。
- 答:上次课讲了 拉伸,旋转 的综合矩阵:
我们一样,先把这个矩阵升一个维度:
为什么升到这样呢:
因为
* 的结果,就是我们想要的。
然后 将这个矩阵左乘 位移矩阵:
*
=