zoukankan      html  css  js  c++  java
  • JavaScript WebGL 绘制一条直线

    目录

    引子

    接着 WebGL 基础概念,做一个绘制直线的简单示例。

    主要参考以下两篇文章:

    绘制一条线

    下面不会对每个使用的函数进行详细的解释,个人比较喜欢先对整体逻辑有个感觉,实际使用时再按需去查资料。

    创建 WebGL 上下文

    基础概念中有提过是通过 Canvas 元素使用 WebGL :

      <canvas id="demo" width="300" height="200"></canvas>
    
      const canvasObj = document.querySelector("#demo");
      const glContext = canvasObj.getContext("webgl");
    
      if (!glContext) {
        alert("浏览器不支持 WebGL");
        return;
      }
    

    接着准备顶点数据。

    准备顶点数据并缓冲

    在 WebGL 中所有实物都是在 3D 空间中,绘制一条线需要两个顶点,每个顶点都有一个 3D 坐标:

    let vertices = [
        -0.5, -0.5, 0.0,
        0.5, -0.5, 0.0
      ];
    

    缓冲有多种类型,顶点缓冲对象的类型是 gl.ARRAY_BUFFER

      /**
       * 设置缓冲
       * @param {*} gl WebGL 上下文
       * @param {*} vertexData 顶点数据
       */
      function setBuffers(gl, vertexData) {
        // 创建空白的缓冲对象
        const buffer = gl.createBuffer();
        // 绑定目标
        gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
        // WebGL 不支持直接使用 JavaScript 原始数组类型,需要转换
        const dataFormat = new Float32Array(vertexData);
        // 初始化数据存储
        gl.bufferData(gl.ARRAY_BUFFER, dataFormat, gl.STATIC_DRAW);
      },
    

    bufferData 方法会把数据复制到当前绑定缓冲对象,该方法提供了管理给定数据的参数:

    • STATIC_DRAW : 缓冲区的内容可能经常使用,不会经常更改。
    • DYNAMIC_DRAW : 缓冲区的内容可能经常被使用,并且经常更改。
    • STREAM_DRAW : 缓冲区的内容可能不会经常使用。

    直线的数据不会改变,每次渲染都保持不变,所以这里使用的类型是 STATIC_DRAW 。现在已经把顶点数据储存在显卡的内存中,接着开始准备顶点着色器。

    顶点着色器

    顶点着色器需要用 GLSL ES 语言编写,在前端书写形式有两种:

    • script 标签包裹,使用时像获取 DOM 对象一样。
    • 纯字符串。
    <script id="shader" type="x-shader/x-vertex">
      attribute vec3 vertexPos;
      void main(void){
        gl_Position = vec4(vertexPos, 1);
      }
    </script>
    
    <script>
      const shader = document.getElementById('shader').innerHTML,
    </script>
    

    每个顶点都有一个 3D 坐标,创建了一个 vec3 类型输入变量 vertexPosvec3 表示三元组浮点数向量。

    main 是入口函数,gl_Position 是着色器内置的变量,GLSL 中一个变量最多 4 个分量,最后一个分量是用在透视除法上。gl_Position 设置的值会成为该顶点着色器的输出。这里请回想一下基础概念中提到的状态机。

    下面是纯字符形式:

      /**
       * 创建顶点着色器
       * @param {*} gl WebGL 上下文
       */
      function createVertexShader(gl) {
        // 顶点着色器 glsl 代码
        const source = `
          attribute vec3 vertexPos;
          void main(void){
            gl_Position = vec4(vertexPos, 1);
          }
        `;
    
        // 创建着色器
        const shader = gl.createShader(gl.VERTEX_SHADER);
    
        // 设置顶点着色器代码
        gl.shaderSource(shader, source);
    
        // 编译
        gl.compileShader(shader);
    
        // 判断是否编译成功
        if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
          alert("编译着色器报错: " + gl.getShaderInfoLog(shader));
          gl.deleteShader(shader);
          return null;
        }
    
        return shader;
      }
    

    为了让 WebGL 使用该着色器,必须在运行时动态编译它的源代码。

    1. createShader 函数创建类型为 gl.VERTEX_SHADER 的着色器对象;
    2. compileShader 函数进行编译。

    接着准备片段着色器。

    片段着色器

    片段着色器也是用 GLSL ES 语言编写。片段着色器所做的是计算像素最后的颜色输出,这里直接简化指定输出白色。gl_FragColor 是内置变量,表示颜色,4 个分量分别对应 R、G、B、A。

      /**
       * 创建片段着色器
       * @param {*} gl WebGL 上下文
       */
      function createFragmentShader(gl) {
        // 片段着色器 glsl 代码
        const source = `
          void main(void){
            gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
          }
        `;
    
        // 创建着色器
        const shader = gl.createShader(gl.FRAGMENT_SHADER);
    
        // 设置片段着色器代码
        gl.shaderSource(shader, source);
    
        // 编译
        gl.compileShader(shader);
    
        // 判断是否编译成功
        if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
          alert("编译着色器报错: " + gl.getShaderInfoLog(shader));
          gl.deleteShader(shader);
          return null;
        }
    
        return shader;
      },
    

    两个着色器都准备好后,需要进行链接合并才能使用。

    着色器程序

    着色器程序对象是多个着色器合并之后并最终链接完成的版本。当链接着色器至一个程序的时候,它会把每个着色器的输出链接到下个着色器的输入。当输出和输入不匹配的时候,会得到一个连接错误。

    当需要激活这个着色器的时候,把该对象作为参数调用 useProgram 函数。

      /**
       * 初始化着色器程序
       * @param {*} gl WebGL 上下文
       * @param {*} vertexShader 顶点着色器
       * @param {*} fragmentShader 片段着色器
       */
      function initShaderProgram(gl, vertexShader, fragmentShader) {
        // 创建着色器对象
        const shaderProgram = gl.createProgram();
        // 添加着色器
        gl.attachShader(shaderProgram, vertexShader);
        gl.attachShader(shaderProgram, fragmentShader);
        // 多个着色器合并链接
        gl.linkProgram(shaderProgram);
        // 创建是否成功检查
        if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
          alert("无法初始化着色器程序: " + gl.getProgramInfoLog(shaderProgram));
          return null;
        }
    
        return shaderProgram;
      }
    

    目前为止,已经把输入顶点数据发送给了 GPU ,并指示了 GPU 如何在顶点和片段着色器中处理它。最后就剩下绘制了。

    绘制

    • vertexAttribPointer 函数告诉 WebGL 如何解释顶点数据;
    • enableVertexAttribArray 函数启用顶点属性,顶点属性默认是禁用的;
    • useProgram 函数激活着色器;
    • drawArrays 函数进行绘制,第一个参数是绘制的图元的类型,绘制的是直线,所以是 gl.LINE_STRIP
      /**
       * 初始化着色器程序
       * @param {*} gl WebGL 上下文
       * @param {*} shaderProgram 着色器程序对象
       */
      function draw(gl, shaderProgram) {
        // 获取对应数据索引
        const vertexPos = gl.getAttribLocation(shaderProgram, "vertexPos");
        // 解析顶点数据
        gl.vertexAttribPointer(vertexPos, 3, gl.FLOAT, false, 0, 0);
        // 启用顶点属性,顶点属性默认是禁用的。
        gl.enableVertexAttribArray(vertexPos);
        // 激活着色器
        gl.useProgram(shaderProgram);
        // 绘制
        gl.drawArrays(gl.LINE_STRIP, 0, 2);
      }
    

    示例

    这是示例,整体的逻辑大概是这样的:

      const canvasObj = document.querySelector("#demo");
      const glContext = canvasObj.getContext("webgl");
      let vertices = [-0.5, -0.5, 0.0, 0.5, -0.5, 0.0]; // 顶点数据
    
      setBuffers(glContext, vertices); // 缓冲数据
      const vertexShader = createVertexShader(glContext); // 顶点着色器
      const fragmentShader = createFragmentShader(glContext); // 片段着色器
      const shaderProgram = initShaderProgram(
        glContext,
        vertexShader,
        fragmentShader
      ); // 着色器程序对象
      draw(glContext, shaderProgram); // 绘制
    

    这里面涉及很多的方法和变量,一开始的时候真的懵,多看几次亲自敲下代码后会慢慢习惯。

    接下来会对期间产生的一些疑问进行总结,见 JavaScript WebGL 基础疑惑点

    参考资料

  • 相关阅读:
    Unity Shader 之 uv动画
    c++源文件后缀名问题
    Unity Shader 之 透明效果
    正则表达式
    Unity Shader基础
    Unity Shader 之 渲染流水线
    2017/11/22 Leetcode 日记
    2017/11/21 Leetcode 日记
    2017/11/13 Leetcode 日记
    2017/11/20 Leetcode 日记
  • 原文地址:https://www.cnblogs.com/thyshare/p/15648214.html
Copyright © 2011-2022 走看看