WebGL第七课:搞一搞 vertex shader(1)

1,105 阅读3分钟
本文标题:WebGL第七课:搞一搞 vertex shader(1)

在接下来的几次课里,我们将通过几个实例,来熟悉 vertex shader 的各种用法。

  1. 我们将用 1000 个点,来绘制我们的图形。
  2. 我们的代码模板给出如下:

<!doctype html>
<html>

<head>
    <style>
        canvas {
            border: 1px solid #000000;
        }
    </style>

</head>

<body>
    <input id="slider" type="range" min="0" max="100" value="50" step="1" οnchange="sliderfunc()" />

    <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_offset;
        attribute vec2 a_PointVertex;
        
        void main() {
          gl_Position = vec4(a_PointVertex, 0.0, 1.0);
          gl_Position.y = sin(gl_Position.x * 3.14);
          if ( fract(gl_Position.x * 100.0) < 0.5)
          {
            gl_Position.y = gl_Position.x;
          }
          gl_Position.x = gl_Position.x + u_x_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 = -500; idx <= 500; idx++) {
            pointCount++;
            pointData.push(idx / 500);
            pointData.push(0);
        }
        //
        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);
        //
        // gl.drawArrays(gl.POINTS, 0, pointCount);
    </script>
    <script>
        var u_x_offset_loc = gl.getUniformLocation(program, "u_x_offset");

        var sliderDom = document.getElementById("slider");
        function sliderfunc() {
            console.log("___________________", u_x_offset_loc, typeof sliderDom.value, sliderDom.value);
            gl.uniform1f(u_x_offset_loc, parseFloat(sliderDom.value) / 100);
            gl.clearColor(0, 0, 0, 0);
            gl.clear(gl.COLOR_BUFFER_BIT);
            gl.drawArrays(gl.POINTS, 0, pointCount);
        }
        sliderDom.addEventListener("change", sliderfunc);
    </script>
    </script>
</body>

</html>
  1. 我们的重点在上述代码的 这一部分, 也就是 vertex shader 部分 :
    <script id="vertex_shader" type="myshader">
        // Vertex Shader
        precision mediump int;
        precision mediump float;
        
        attribute vec2 a_PointVertex;
        
        void main() {
          gl_Position = vec4(a_PointVertex, 0.0, 1.0);
          gl_PointSize = 3.0;
        }
    </script>

第一个例子: 绘制 sin 正弦图像
  • 我们准备的数据点有1000个,这些点的横坐标,从 -1 到 1
  • 纵坐标都是0

那么按到里讲,绘制的东西是一根横的直线,在屏幕中间
我们在浏览器里打开这个页面,结果也确实如此:

7-1.png
根据 正弦函数式子:
y = sin (x)

我们改动我们的 vertex shader, 如下:

    <script id="vertex_shader" type="myshader">
        // Vertex Shader
        precision mediump int;
        precision mediump float;
        
        attribute vec2 a_PointVertex;
        
        void main() {
          gl_Position = vec4(a_PointVertex, 0.0, 1.0);
          gl_Position.y = sin(gl_Position.x);
          gl_PointSize = 3.0;
        }
    </script>

刷新一下页面,结果如下图:

7-2.png

我们发现上图不怎么完美,因为并没有完整显示一个周期之内的正弦
原因就是因为我们的 x 的范围是 -1---1 之间,这不够一个周期的
一个周期应该是 -π --- +π 之间。
那这样来变一下代码:

    <script id="vertex_shader" type="myshader">
        // Vertex Shader
        precision mediump int;
        precision mediump float;
        
        attribute vec2 a_PointVertex;
        
        void main() {
          gl_Position = vec4(a_PointVertex, 0.0, 1.0);
          gl_Position.y = sin(gl_Position.x * 3.14);
          gl_PointSize = 3.0;
        }
    </script>

刷新一下页面,结果如下,这样就好看多了:

7-3.png
太棒了,我们可以用webgl来绘制函数图形了,多好玩啊~

我要画两个周期的 sin 图像:

    <script id="vertex_shader" type="myshader">
        // Vertex Shader
        precision mediump int;
        precision mediump float;
        
        attribute vec2 a_PointVertex;
        
        void main() {
          gl_Position = vec4(a_PointVertex, 0.0, 1.0);
          gl_Position.y = sin(gl_Position.x * 3.14 * 2.0);
          gl_PointSize = 3.0;
        }
    </script>

是的,我们成功了:

7-4.png

接下来我们来搞一些奇怪的东西,比如说,我们的代码怎么改动,能绘制下面的图:

7-6.png

我们发现,在图的左半边,好像图形往中间压缩了一半。
线索:

  1. 图的左半边:(x<0)
  2. 压缩一半 :( y = y * 0.5)
    我们将上面的线索,更换成相应的代码如下:
<script id="vertex_shader" type="myshader">
      // Vertex Shader
      precision mediump int;
      precision mediump float;
      
      attribute vec2 a_PointVertex;
      
      void main() {
        gl_Position = vec4(a_PointVertex, 0.0, 1.0);
        gl_Position.y = sin(gl_Position.x * 3.14 * 2.0);
        if (gl_Position.x < 0.0)
        {
          gl_Position.y = gl_Position.y * 0.5;
        }
        gl_PointSize = 3.0;
      }
  </script>

还是两个周期的图像,不过,代码里写了,如果 x<0 , y 就变成一半,很容易理解!

第二个例子: 同时绘制 一条直线sin 图像

就像下图这样:

7-7.png
有读者脑子里就在想了,简单:
我在外面绘制两次,就是用 gl.drawArrays,绘制两次图像,事先准备好两个 vertex shader
结果是:行,你这么搞是可以的,我不会说任何反对的话,但是我再加一个限制,

  1. 只准有一个vertex shader,

  2. 只准用一次gl.drawArrays

  3. 而且不准改外面的生成数据的代码,我们只能改 vertex shader 代码!!!

思路: 我们有1000个点,我们交叉绘制上面两个图形不就行了吗!!!
比如说,奇数下标的绘制sin图像,偶数下标的绘制直线

问题来了,我们能在vertex shader 里获取下标吗????? 显然不能。
我们只能在x本身上下手,我们列一下最左边的几个x:
-1.000, -0.998, -0.996, -0.994, -0.992, -0.990 ……
我们将上面的数全部乘以 100就会得到下面的数:
-100.0, -99.8, -99.6, -99.4, -99.2, -99
我们再把小数部分保留,去掉整数部分:
0, 0.8, 0.6, 0.4, 0.2 , 0
我们发现,除了0之外,两个小于0.5的,两个大于0.5
那我们就按照这个来交叉,代码如下:

<script id="vertex_shader" type="myshader">
        // Vertex Shader
        precision mediump int;
        precision mediump float;
        
        attribute vec2 a_PointVertex;
        
        void main() {
          gl_Position = vec4(a_PointVertex, 0.0, 1.0);
          gl_Position.y = sin(gl_Position.x * 3.14);
          if ( fract(gl_Position.x * 100.0) < 0.5)
          {
            gl_Position.y = gl_Position.x;
          }
          gl_PointSize = 3.0;
        }
    </script>

刷新页面,图像出来了!!成功!!




正文结束,下面是答疑
小瓜瓜说:天真热,瓜真好吃,不过图形也是真好看。。。。。。