WebGL第十课:搞一搞 vertex shader(4) 旋转

772 阅读3分钟
本文标题: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>

在浏览器里直接打开这个页面,效果如下:

10-1.png
更新完的代码,将原始数据变成了三角形的。 这是为了便于观察。
然后增加了一个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>


注意一下,里面 拉伸 旋转 平移 的顺序。

  1. 拉伸
  2. 旋转
  3. 平移

这个顺序的安排,还是基于业务的目的,用一句朴实无华的句子来说明:
因为我们就是想 先拉伸,再旋转,最后平移

当我平移到某一个地方的时候,然后再去旋转,此时旋转的效果好像与定义有所出入

看下图:

10-2.gif

很明显,在我们滑动 rotate 的时候,所有的点,并不是绕着原点也就是canvas中心在转, 而是绕着这个三角形的直角顶点在转。

这是为什么呢?

解析:其实很简单,一定要坚守这三个操作的顺序,你就明白一切了。

  • 首先进行的是scale操作,我们将这一步操作之后的结果显示出来:

10-3.png
符合我们的预期,两个方向的坐标数值都变成0.5倍

  • 再进行旋转操作,接着给出效果:

10-4.gif
这时候就很明显了,确实是绕着canvas中心在转的。

  • 转完了之后,才会进行平移操作。所以给人的假象就是:平移完了,旋转的中心变成其他点

  • 而真相就是:先是绕着中心转,转完了,再平移的!!!




    正文结束,下面是答疑
    
    小瓜瓜说:我们什么时候才能画一些完整的东西,我不想看见点了,我想画别的。
    • 答:点才是核心东西,其他的东西,比如说三角形啥的,其实就是插值得来的,你暂时在脑子里对这些点进行插值吧。

    小瓜瓜说:下一次可还是画点吗?
    • 答:下一次课反正不会画三角,你信我,🙃。
    小丫丫说:别人家的课程里面,都好鲜艳啊,五彩斑斓的。
    • 答:五彩斑斓的,看花眼了都。。。。。。