本文标题:WebGL第十课:搞一搞 vertex shader(4) 旋转
上次课搞定了拉伸
和平移
两个操作,这一次搞定另一个也是最后一个比较重要的操作:旋转
。
旋转:
每一个点都绕着原点(0,0)
也就是canvas的中心
,逆时针
转动一个角度
。
讲解:
要点1: 依然是每一个点都进行同样的操作。
要点2: 一定是绕着原点(0,0), 记住这一点,这将使你不会把自己绕晕。
至于,绕其他点旋转,那是另外的事情。
要点3:为什么一定是逆时针,因为定义要准确。
你完全可以逆时针转动一个负角度,来达到顺时针转动的目的。
对于拉伸
和平移
,很容易得出数学式子,但是旋转不是这么明显。
具体的推导过程有很多种路子,这里不细推,直接给出结果如下:
一个平面点(x,y),绕着原点(0, 0)逆时针旋转 α(弧度),得到的点坐标是:
(x', y'):
x' = x * cos(α) - y * sin(α)
y' = x * sin(α) + y * cos(α)
根据以上的公式,然后将上次课中的拉伸
和平移
全部弄到一块去,整体代码如下:
<!doctype html>
<html>
<head>
<style>
canvas {
border: 1px solid #000000;
}
</style>
</head>
<body>
<p>
<b>scale value:</b>
<input id="scalex" type="range" min="-1" max="1" value="1" step="0.1" oninput="updatefunc()" />
<b id="scalevaluex">0</b>
<input id="scaley" type="range" min="-1" max="1" 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.0, 0.0, 1.0);
}
</script>
<script type="text/javascript">
var pointCanvas = document.getElementById('point'); // 我们的纸
var gl = pointCanvas.getContext('webgl', { preserveDrawingBuffer: true }); // 我们的笔
var pointCount = 0;
var pointData = [];
for (var idx = 0; idx <= 15; idx++) {
for (var idy = 0; idy <= 15 - idx; idy++) {
pointCount++;
pointData.push(idx / 15);
pointData.push(idy / 15);
}
}
//
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.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.POINTS, 0, pointCount);
}
updatefunc();
</script>
</script>
</body>
</html>
在浏览器里直接打开这个页面,效果如下:
更新完的代码,将原始数据变成了三角形的。 这是为了便于观察。
然后增加了一个uniform
变量 u_rotate
, 用来接收旋转值。注意这个值是弧度,[0-2*3.14] 范围。正好一圈,360°。
接着看一下 vertex shader
, 如下:
<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>
注意一下,里面 拉伸
旋转
平移
的顺序。
- 拉伸
- 旋转
- 平移
这个顺序的安排,还是基于业务的目的,用一句朴实无华的句子来说明:
因为我们就是想 先拉伸,再旋转,最后平移
。
当我平移到某一个地方的时候,然后再去旋转,此时旋转的效果好像与定义有所出入
看下图:
很明显,在我们滑动 rotate 的时候,所有的点,并不是绕着原点也就是canvas中心
在转, 而是绕着这个三角形的直角顶点在转。
这是为什么呢?
解析:其实很简单,一定要坚守这三个操作的顺序,你就明白一切了。
- 首先进行的是
scale
操作,我们将这一步操作之后的结果显示出来:
符合我们的预期,两个方向的坐标数值都变成0.5倍
。
- 再进行旋转操作,接着给出效果:
这时候就很明显了,确实是绕着canvas中心在转的。
-
转完了之后,才会进行平移操作。所以给人的假象就是:
平移完了,旋转的中心变成其他点。 -
而真相就是:
先是绕着中心转,转完了,再平移的
!!!
正文结束,下面是答疑
小瓜瓜说:我们什么时候才能画一些完整的东西,我不想看见点了,我想画别的。
-
答:点才是核心东西,其他的东西,比如说三角形啥的,其实就是
插值
得来的,你暂时在脑子里对这些点进行插值
吧。
小瓜瓜说:下一次可还是画点吗?
- 答:下一次课反正不会画三角,你信我,🙃。
小丫丫说:别人家的课程里面,都好鲜艳啊,五彩斑斓的。
- 答:五彩斑斓的,看花眼了都。。。。。。
-