webgl封装画简易图像

我们新建一个 html 文件然后使用 chrome 浏览器来绘制简易图像。

最终生成的调用方式如下:

  glUnlink();
      glInit({
        vertexShaderCode: `
            attribute vec2 a_pos;
            uniform vec2 resol;
            void main(){
              // 坐标转换
              gl_Position = vec4((a_pos / resol * 2.0 - 1.0) * vec2(1,-1), 0 , 1);
            }
        `,
        fragmentShaderCode: `
            void main(){
                gl_FragColor = vec4(0,1,0,1);
            }
        `,
        varArr: { a_pos: [2012020100100100100120] },
        uniformArr: { resol: [600400] },
      });
      glDrawArrays("TRIANGLE_FAN"04);

这样我们只需要专心处理 shader 和把 shader 要的数据交待一下就能看到效果,方便我们快速测试和学习 webgl 。

这里将以像素为单位的 canvas 坐标系转换成 [-1,1] 的投影区间,坐标原点在左上角,因为所有 css 的定位坐标系 y 轴也是向下的,因此如果要转换成投影矩阵正常的 y 轴向上的坐标系,需要 乘以 vec2(1,-1)。

 

第一步:新建一个 canvas 元素,获取 webgl 的运行上下文,这里没做各种兼容处理,准备导出要使用的方法

<body>
    <canvas width="600" height="400" id="cs"></canvas>
    <script>
      const { glInitglDrawArraysglUnlink } = (() => {
        const canvas = document.getElementById("cs");
        const gl = canvas.getContext("experimental-webgl");
        
        let vertexShader = "";
        let fragmentShader = "";
        
        // 重置画布
        let _program = null;
        let getProgram = () => {
          if (!_program) {
            _program = gl.createProgram();
          }
          return _program;
        };
        let delProgram = () => {
          if (_program) {
            gl.deleteProgram(_program);
          }
          _program = null;
        };

 

第二步:shader 中变量访问地址和内存地址的绑定

        // 给 顶点shader 的 vec2 变量绑定  顶点信息buffer 的内存地址并规定读取方式
        const injectV2Attribute = (varConfig=> {
          for (let varname in varConfig) {
            const buffer = gl.createBuffer();
            gl.bindBuffer(gl.ARRAY_BUFFERbuffer);
            gl.bufferData(
              gl.ARRAY_BUFFER,
              new Float32Array(varConfig[varname]),
              gl.STATIC_DRAW
            );
            // 拿到固定的shader变量内存地址
            const varPosition = gl.getAttribLocation(getProgram(), varname);
            // 允许shader读buffer
            gl.enableVertexAttribArray(varPosition);
            // 规定读取buffer的方式
            gl.vertexAttribPointer(varPosition2gl.FLOATfalse00);
          }
        };
 
        // 给 顶点shader 的 vec2 常量 找到 内存中此值的地址
        const injectV2Uniform = (uniformConfig=> {
          for (let uname in uniformConfig) {
            const varPosition = gl.getUniformLocation(getProgram(), uname);
            gl.uniform2f(varPosition, ...uniformConfig[uname]);
          }
        };

这里学习下 gl.vertexAttribPointer 用来规定读取数据的方式:

参数1:变量地址

参数2:顶点组成部分,这里因为是画二维图像,因此给 shader x 和 y 坐标就行,z 轴坐标为 0,最后摄像机距离默认 1 表示比例

参数3:Float32Array 和 gl.FLOAT 遥相呼应,使用 4 字节存储

参数4:可以简单理解 true 的话顶点存储占更多字节数,精度高,false 可能会只用 单字节 存值,比如颜色这种上限 255 的,具体还是跟传入的实际数据有关

参数5:每隔多个个存储单位取下一个值,0 相当于值是紧密排列

参数6:取值偏移,结合参数5 可以精确间隔取值

 

第三步:通用初始化封装

        // 编译shader,报错则打印日志
        function createShader(glsourceCodetype) {
          var shader = gl.createShader(type);
          gl.shaderSource(shadersourceCode);
          gl.compileShader(shader);
 
          if (!gl.getShaderParameter(shadergl.COMPILE_STATUS)) {
            var info = gl.getShaderInfoLog(shader);
            throw "Could not compile WebGL program. \n\n" + info;
          }
          return shader;
        }
 
        return {
          glInit({ vertexShaderCodefragmentShaderCodevarArruniformArr }) {
            // shader
            vertexShader = createShader(glvertexShaderCodegl.VERTEX_SHADER);
            fragmentShader = createShader(
              gl,
              fragmentShaderCode,
              gl.FRAGMENT_SHADER
            );
 
            // 启动流程
            gl.attachShader(getProgram(), vertexShader);
            gl.attachShader(getProgram(), fragmentShader);
            gl.linkProgram(getProgram());
            gl.useProgram(getProgram());
 
            // 日志
            if (!gl.getProgramParameter(getProgram(), gl.LINK_STATUS)) {
              var info = gl.getProgramInfoLog(getProgram());
              throw "Could not compile WebGL program. \n\n" + info;
            }
 
            varArr && injectV2Attribute(varArr);
            uniformArr && injectV2Uniform(uniformArr);
          },
          glDrawArrays(...args) {
            gl.drawArrays(gl[args[0]], ...args.slice(1));
          },
          glUnlink() {
            delProgram();
          },
        };
      })();

这里学习下 gl.drawArrays 的第一个参数连三角形时的模式,共有三种:

第一种 gl.TRIANGLES:即每 3 个点画一个三角网格

后面两种画网格方式可以减少需要存储顶点数量,量级从 3*N 变为 N + 2。

第二种 gl.TRIANGLE_STRIP:strip 翻译过来就是彩带,因为三角形连成的形状是长条状

第三种 gl.TRIANGLE_FAN:fan 翻译过来就是扇子,因为三角形连成形状近似蒲扇

 

有了上面的简单封装画三角网格就会方便很多!

posted on 2021-02-25 16:24  Lowki  阅读(316)  评论(0编辑  收藏  举报