美文网首页
WebGL-学习笔记(三)

WebGL-学习笔记(三)

作者: Patrick浩 | 来源:发表于2018-03-25 23:23 被阅读0次
    image.png

    在利用缓冲区并在学会利用 mode绘制图形动画以后,继续研究二维图形的颜色渲染以及纹理操作

    1. 颜色渲染

    1.1 利用步进和偏移拆分缓冲区数据

    利用缓冲区可以一次存储点的多个信息,继续利用这一特性,并在利用vertexAttribPointer()函数的时候,利用上strideoffset两个参数:

        function main() {
            const canvas = document.querySelector('#glCanvas');
            const gl = canvas.getContext('webgl');
            // 着色器程序
            const VSHADER_SOURCE = `
                attribute vec4 a_Position;
                attribute float a_PointSize;
                void main() {
                    gl_Position = a_Position;
                    gl_PointSize = a_PointSize;
                }
            `;
            const FSHADER_SOURCE = `
                precision mediump float;
                uniform vec4 u_FragColor;
                void main() {
                    gl_FragColor = u_FragColor;
                }
            `
            let program = init(gl, VSHADER_SOURCE, FSHADER_SOURCE);
            let a_Position = gl.getAttribLocation(program, 'a_Position');
            if (a_Position < 0) {
                console.log('Cant find the position');
                return;
            }
            let a_PointSize = gl.getAttribLocation(program, 'a_PointSize');
            if (a_PointSize < 0) {
                console.log('Cant find the pointsize');
                return;
            }
            gl.vertexAttrib1f(a_PointSize, 10.0);
            initPoint(gl, a_Position, a_PointSize);
            let u_FragColor = gl.getUniformLocation(program, 'u_FragColor');
            gl.uniform4f(u_FragColor, 1.0, 0.0, 0.0, 1.0);
    
            gl.clearColor(0.0, 0.0, 0.0, 1.0);
            gl.clear(gl.COLOR_BUFFER_BIT);
            gl.drawArrays(gl.POINTS, 0, 3);
        }
        function initPoint(gl, a_Position, a_PointSize) {
            // 将点的坐标和尺寸信息放入一个类型化数组中
            let pointData = new Float32Array([
                -0.5, -0.5, 10.0,
                0, 0.5, 20.0,
                0.5, -0.5, 30.0
            ]);
            // 利用类型化数组提供的内部属性,获取到元素的尺寸
            let FSIZE = pointData.BYTES_PER_ELEMENT;
            let buffer = gl.createBuffer();
            gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
            gl.bufferData(gl.ARRAY_BUFFER, pointData, gl.STATIC_DRAW);
            // 设置第五个参数stride为3*FSIZE(每两个点之间有三个元素,也就是每三个数据进行一次拆分)
            gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 3*FSIZE, 0);
            gl.enableVertexAttribArray(a_Position);
            // 设置第六个参数offset偏移,将每组数据的第三个元素设置给a_PointSize属性
            gl.vertexAttribPointer(a_PointSize, 1, gl.FLOAT, false, 3*FSIZE, 2*FSIZE);
            gl.enableVertexAttribArray(a_PointSize);
        }
    

    主要改变在于:

    1.利用了类型化数组的BYTE_PER_ELEMENT属性,获取了每个元素的大小

    1. 利用vertexAttribPointer()的第五个参数,指定两个相邻顶点间的字节数,或者理解为每个点的信息由多少个元素组成。
    2. 利用vertexAttribPointer()的第六个参数,指定缓冲区对象中每次读取时的偏移。

    当然,要完成以上的操作,也可以将点的位置信息和尺寸信息进行分开放置:

    function initPoint(gl, a_Position, a_PointSize) {
            // 将类型化数组增加点的尺寸的参数
            let pointData = new Float32Array([
                -0.5, -0.5,
                0, 0.5,
                0.5, -0.5
            ]);
            let pointSizeData = new Float32Array([10.0, 20.0, 30.0]);
            // 设置点的位置信息
            let buffer1 = gl.createBuffer();
            gl.bindBuffer(gl.ARRAY_BUFFER, buffer1);
            gl.bufferData(gl.ARRAY_BUFFER, pointData, gl.STATIC_DRAW);
            gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);
            gl.enableVertexAttribArray(a_Position);
            // 设置点的尺寸信息
            let buffer2 = gl.createBuffer();
            gl.bindBuffer(gl.ARRAY_BUFFER, buffer2);
            gl.bufferData(gl.ARRAY_BUFFER, pointSizeData, gl.STATIC_DRAW);
            gl.vertexAttribPointer(a_PointSize, 1, gl.FLOAT, false, 0, 0);
            gl.enableVertexAttribArray(a_PointSize);
    }
    

    但是很明显操作多余而且复杂了

    1.2 利用varying变量进行插值操作

    想要从Vertex ShaderFragment Shader进行数据传递,需要用到varying声明的变量,为什么利用从Vertex ShaderFragment Shader传递数据?
    由于Fragment Shader中是只能使用uniform变量的,unifrom信息是和顶点信息不相关联的,如果要单独对某个顶点的颜色进行设置,是没办法的,所以为了给每个顶点设置颜色,就要从Vertex Shader传递数据到Fragment Shader了。
    如果绘制一个三角形,并将顶点颜色传递给Fragment Shader

    // 修改着色器程序,增加varying
    const VSHADER_SOURCE = `
        attribute vec4 a_Position;
        attribute vec4 a_Color;
        varying vec4 v_Color;
        void main() {
            gl_Position = a_Position;
            v_Color = a_Color;
        }
    `;
    const FSHADER_SOURCE = `
         precision mediump float;
         varying vec4 v_Color;
         void main() {
            gl_FragColor = v_Color;
          }
    `
    // 修改点的数据信息加载
    function initPoint(gl, a_Position, a_Color) {
        // 将点的坐标信息和颜色信息放到同一个类型化数组中
        let pointData = new Float32Array([
            -0.5, -0.5, 1.0, 0.0, 0.0,
            0, 0.5, 0.0, 1.0, 0.0,
            0.5, -0.5, 0.0, 0.0, 1.0
        ]);
        // 利用类型化数组提供的方法,获取到元素的尺寸
        let FSIZE = pointData.BYTES_PER_ELEMENT;
        let buffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
        gl.bufferData(gl.ARRAY_BUFFER, pointData, gl.STATIC_DRAW);
        gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 5*FSIZE, 0);
        gl.enableVertexAttribArray(a_Position);
        gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, 5*FSIZE, 2*FSIZE);
        gl.enableVertexAttribArray(a_Color);
    }
    

    varying变量的作用其实不仅是进行数值的传递,如果将图形绘制的mode修改为gl.TRANGLES,可以发现绘制的三角形颜色是渐变的,造成这一结果是varying的作用。
    继续深入着色器过程,Vertex ShaderFragment Shader要进行两步操作:

    1. 图形装配:将Vertex Shader的顶点信息,根据drawArraymode配置在对应的几何图形顶点处
    2. 光栅化:将装配好的图形分割为一个一个的片元(像素点),因此片元的数目就是像素点

    varying在赋值颜色给顶点的过程当中,会自动计算从一个顶点到另一个顶点变化的插值,并将每一个片元染成对应计算后的颜色,所以才看到一个从顶点颜色渐变的三角形,这一过程也可以被称为内插。

    2. 纹理添加

    除了使用单独的颜色渲染外,也可以直接将图片贴到几何图形的表面,该图片也就是纹理。图片是由像素点组成的,每个像素点就是纹素,是使用了RGB/RGBA的编码块
    WebGL的给图形添加纹理很简单,但是要解决三个问题

    2.1 浏览器跨域

    由于WebGL的图片请求机制,所以如果直接请求本地图片的时候会出现跨域问题,为了使得开发和调试的方便,可以使用以下方式临时关闭浏览器的跨域验证,以Chrome为例:
    Windows:

    "C:\Program Files\Google\Chrome\Application\chrome.exe" --args --disable-web-security

    MAC:

    open -a "Google Chrome" --args --disable-web-security --user-data-dir

    2.2 纹理坐标和图片坐标

    WebGL中的纹理坐标Y轴正方向是朝上,其中坐标的四个角为左下(0.0, 0.0)右下(1.0, 0.0)右上(1.0, 1.0)左上(0.0, 1.0)
    图片坐标和WebGL坐标的Y轴方向是相反的,正方向朝下,所以在进行纹理映射的时候,需要将坐标轴进行反转。

    2.3 纹理添加

    进行纹理映射最主要的有以下五步操作,以绘制一个矩形并贴图为例:

    2.3.1. 将顶点坐标和纹理坐标都加入到类型化数组中

    // 每一行前两个数据为顶点坐标,后两个数据为纹理坐标
    let pointData = new Float32Array([
        -0.5, 0.5, 0.0, 1.0,
        -0.5, -0.5, 0.0, 0.0,
        0.5, 0.5, 1.0, 1.0,
        0.5, -0.5, 1.0, 0.0
    ]);
    
    2.3.2 创建纹理对象和图片加载

    // 创建image对象和纹理对象
    let texture = gl.createTexture();
    let image = new Image();
    // 给image执行onload方法的时候进行纹理渲染
    image.onload = function() { initTexture() };
    // 加载纹理图片
    image.src = 'xxx.png';
    

    PS:图片的加载过程是异步!!!

    2.3.3 设置纹理

    有6个主要的步骤,但是实际上去掉第2和6步也是可以运行的,目前对机制还不甚了解,之后会持续关注

    // 1. y轴反转,因为图像坐标系y轴向下,而webgl坐标系y轴向上
    gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);
    // 2. 开启纹理单元(0号),一共有8个
    gl.activeTexture(gl.TEXTURE0);
    // 3. 绑定纹理对象
    gl.bindTexture(gl.TEXTURE_2D, texture);
    // 4. 配置纹理参数(对于非2的幂次尺寸的图片,需要设置图片拉伸方式)
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
    // 如果图片尺寸为2的幂次,则可以使用以下方式
    // gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
    // 5. 将图像分配给纹理对象
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image);
    // 6. 将0号纹理传递给着色器中的取样器变量
    gl.uniform1i(u_Sampler, 0);
    
    2.3.4 修改着色器程序

    // 增加纹理坐标的数据接收
    const VSHADER_SOURCE = `
      attribute vec4 a_Position;
      attribute vec2 a_TexCoord;
      varying vec2 v_TexCoord;
      uniform mat4 u_Matrix;
      void main() {
        gl_Position = u_Matrix * a_Position;
        v_TexCoord = a_TexCoord;
      }
    `;
    // 给片元着色器增加 simlper2D的属性以及纹理坐标数据
    const FSHADER_SOURCE = `
      precision mediump float;
      uniform sampler2D u_Sampler;
      varying vec2 v_TexCoord;
      void main() {
        gl_FragColor = texture2D(u_Sampler, v_TexCoord);
      }
     `
    

    PS:一定要注意其中关于图片的选择,2.3.3 中关于配置纹理参数,一定要优先确定图片的长和宽是否为2的幂次,从而来决定使用哪种方案进行纹理的匹配,否则将导致纹理解析错误:

    It maybe non-power-of-2 and have incompatible texture filtering.

    当然目前在测试的过程当中,还存在一个浏览器警告提示没有解决

    RENDER WARNING: there is no texture bound to the unit 0

    目前虽然有警告,但是纹理渲染并没有问题,而且根据描述来说,我已经对纹理单元0进行了利用了,所以目前还不知道导致的原因,之后如果解决会在评论区补充原因说明。

    4. 总结

    基本上来说二维图形的绘制,渲染就大致这些内容,不过仍然存在很多细节需要自己去尝试摸索,接下来继续=学习进入真正的WebGL发挥实力的三维图形的领域!

    5. 参考

    《WebGL编程指南》

    相关文章

      网友评论

          本文标题:WebGL-学习笔记(三)

          本文链接:https://www.haomeiwen.com/subject/zkekcftx.html