WebGL学习系列-使用缓冲区对象画多个点

Posted 那个天真的人

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了WebGL学习系列-使用缓冲区对象画多个点相关的知识,希望对你有一定的参考价值。

前言

一般而言,我们需要绘制的点的数量非常的多,所以不可能像第一个程序一样一个点一个点绘制,WebGL提供了缓冲区对象,用于处理绘制多个点的数据问题。

预览效果图

坐标系

为了更好的理解点的位置,我们需要知道canvas以及WebGL的坐标系。

canvas坐标系如下所示:

WebGL坐标系如下所示:

可以看到,WebGL的坐标系跟canvas坐标系是不一样的,原点位置不一样,定义也不太一样,canvas是以像素值为坐标值的,在WebGL当中,不管依托的canvas宽高大小,它四个顶点的坐标值都是不变的,而且取中心点为原点。

向量

上一小节,我们使用 vec4(0.0, 0.0, 0.0, 1.0); 来表示点的位置,vec4表示有4个数值的向量,当把vec4赋值给顶点位置的时候,前三个参数分别表示xyz坐标,那为何还有第四个参数呢?其实,在图形学中,通常使用称为齐次坐标的形式来表示坐标,如(x,y,z,w),它相当于 (x/w,y/w,z/w),主要是为了便于计算(后续章节将会逐步体会到)。所以现在你知道为何我们定义第4个参数为1.0了吧。

此外,我们使用 vec4(1.0, 0.0, 0.0, 1.0) 来表示红色,四个参数对应RGBA四个分量值,只不过以往我们使用0~255来表示RGB三个分量,在WebGL我们把它归一化了,其范围值为0.0~1.0。

着色器变量和缓冲区对象

还记得咱们画一个点的时候 ,在顶点着色器代码中,固化了顶点的值,现在,我们想要画多个点,难道要创建好多个program和好多个顶点着色器么?显然不可能这么麻烦。为了解决这个问题,WebGL提供了缓冲区对象,使得我们可以使用一个数组来连续存放多个顶点的数据,然后使用drawArrays一次性画出来,这时候,我们要引入着色器变量才行。

先来看一下本次使用的顶点着色器代码:

// 顶点着色器代码(决定顶点在哪里,大小)
var VSHADER_SOURCE = 
  'attribute vec4 a_Position;\\n' +
  'void main() \\n' +
  '  gl_Position = a_Position;\\n' + // 设置顶点的位置
  '  gl_PointSize = 10.0;\\n' +     // 设置顶点的大小
  '\\n';

可以看到 attribute vec4 a_Position; 这一行代码,它表示定义一个着色器变量,类型为vec4,变量名字为a_Position,然后在main方法中直接把 a_Position 赋值给gl_Position。

让我们再强调一次,每执行一次顶点着色器代码,只可以获取一个顶点的信息,为了获取多个不同的顶点信息,每调用一次顶点着色器代码的时候,a_Position必须是要不一样的,也就是动态值,结合缓冲区技术我们便可以实现 。

我相信,你依然会有疑惑这一切是怎么做到的,先来看看缓冲区使用示意图:

不要被吓到了,我们一步步来看,首先记得我们的目标是要利用同一个顶点着色器来绘制多个顶点,本例我们绘制三个顶点。

1、准备顶点数据,我们要绘制三个顶点,所以需要3个顶点的数据,如下:

// Float32Array 是用于存放 float 类型的数组,每一个float数据占用4个字节
var vertices = new Float32Array([
        0.0, 0.5,   -0.5, -0.5,   0.5, -0.5
      ]);

注意,由于目前我们使用的坐标z值为1,w=1,所以只定义x和y坐标就可以了,以上就是三个顶点位置的定义。

2、创建一个缓冲区对象

// 创建缓冲区对象非常的简单
var vertexBuffer = context.createBuffer();

3、把缓冲区对象绑定到 ARRAY_BUFFER 目标上
我想看到这里很多人会有疑问,啥是目标对象呀?回头看看第1和第2这两个步骤,我们准备好了顶点数据以及 一个空的缓冲区对象,那么怎么把顶点数据放入到缓冲区对象呢?WebGL没有直接的方法可以做到,只能够借助一个中间代理,这就是绑定的由来。我们暂时把空的缓冲区对象绑定到 ARRAY_BUFFER 这个目标上(目标有多种类型,以后会学习到),绑定后,就相当于在 ARRAY_BUFFER 这个目标上,当前代理的工作对象是新建的缓冲区对象。

// 绑定缓冲区对象到  ARRAY_BUFFER  目标上
context.bindBuffer(context.ARRAY_BUFFER, vertexBuffer);

4、为了把顶点数据填充到缓冲区对象中,我们把顶点数据开放给 ARRAY_BUFFER 目标代理的对象使用(这时代理对象就是新建的缓冲区对象),执行完之后,缓冲区对象就有顶点数据了。

// 把顶点数据开放给  ARRAY_BUFFER  目标使用,间接把数据注入缓冲区对象中
context.bufferData(context.ARRAY_BUFFER, vertices, context.STATIC_DRAW);

5、到此,缓冲区对象有数据了,而且有多个顶点数据,接着,我们获取顶点着色器中的顶点变量:a_Position

// 获取顶点着色器中顶点的位置变量
var a_Position = context.getAttribLocation(context.program, 'a_Position');

6、我们的目的是要 a_Position 能够动态从缓冲区对象中获取数据,所以我们还要设置获取的规则,比如,数据的类型,每个顶点占用多少个变量,这样才会正确处理缓冲区中的数值而不会乱来。

//  设置变量获取数据规则
//  第二个参数2表示每个顶点只有两个数据
//  后面两个参数用于控制数组包括多种数据内容的情况,在本例中都为0,后面用到再详细解释
context.vertexAttribPointer(a_Position, 2, context.FLOAT, false, 0, 0);

7、最后一步,当然是允许我们的顶点变量能够从缓冲区对象中取数了,使用enableVertexAttribArray方法表示变量将从 ARRAY_BUFFER目标代理的缓冲区中获取数据。

context.enableVertexAttribArray(a_Position);

一切都准备好了,最后我们还是使用 drawArrays接口来画图。

// 画n个点,本例中是3个点,第二个参数表示从索引为0的顶点开始绘制,绘制总数为n个
context.drawArrays(context.POINTS, 0, n);

小结

为了能够利用drawArrays一次性绘制多个点,我们使用了缓冲区对象,同时使用了着色器变量,利用WebGL提供的内置机制,在执行 context.drawArrays(context.POINTS, 0, n);的时候,会循环n次调用我们的顶点着色器代码,每一次执行顶点着色器代码时,底层会自动根据我们之前的相关配置,动态从缓冲区中取顶点的数据,然后填充到a_Position变量中。这下子,就算要画1000个点,我们只需要定义好点的位置,就可以一下子绘制出来了,这便是缓冲区对象的魅力所在。

本篇内容需要好好消化一下,理解每获取一个顶点的数据,就会执行一次顶点着色器代码至关重要,这是底层的一个执行的机制,包括片元着色器也是一样的,后面自定义颜色再细讲。

源码

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>第一个WebGL程序-画一个点</title>
  </head>
  <body onload="main()">
    <canvas id="webgl" width="400" height="400">
        您的浏览器不支持canvas,建议使用Chrome浏览器
    </canvas>
    <script>    
    // 主程序入口
    function main()
        var canvas = document.getElementById('webgl');
        var context = createWebGLContext(canvas);
        var program = createProgram(context , VSHADER_SOURCE , FSHADER_SOURCE);
        context.program = program;
        context.useProgram(program);

        var n = initVertexBuffers(context);

        // 每一次重绘时的背景色
        context.clearColor(0.0, 0.0, 0.0, 1.0);

        // 清除 <canvas>
        context.clear(context.COLOR_BUFFER_BIT);

        // 画n个点
        context.drawArrays(context.POINTS, 0, n);
    
    function initVertexBuffers(context) 
      var vertices = new Float32Array([
        0.0, 0.5,   -0.5, -0.5,   0.5, -0.5
      ]);
      // 画三个点
      var n = 3; 

      // 创建一个缓存对象,用于存放顶点数据
      var vertexBuffer = context.createBuffer();
      // 绑定缓存对象
      context.bindBuffer(context.ARRAY_BUFFER, vertexBuffer);
      // 把数据写到缓冲对象中
      context.bufferData(context.ARRAY_BUFFER, vertices, context.STATIC_DRAW);
      // 获取顶点着色器代码中的顶点变量
      var a_Position = context.getAttribLocation(context.program, 'a_Position');
      // 设置变量获取数据规则
      context.vertexAttribPointer(a_Position, 2, context.FLOAT, false, 0, 0);
      // 允许变量从 ARRAY_BUFFER目标上绑定的缓冲区对象获取数据
      context.enableVertexAttribArray(a_Position);

      return n;
    
    // 获取WebGL上下文
    function createWebGLContext(canvas)
        var names = ["experimental-webgl", "webgl" , "webkit-3d", "moz-webgl"];
        var webglContext = null;
        for (var i = 0; i < names.length; i++) 
            try 
                webglContext = canvas.getContext(names[i]);
                if(webglContext)
                    break;
                
             catch(e) 

        
        return webglContext;
    
    // 创建一个program(相当于着色器的上下文)
    function createProgram(context, vshader, fshader)
        var vertexShader = loadShader(context, context.VERTEX_SHADER, vshader);
        var fragmentShader = loadShader(context, context.FRAGMENT_SHADER, fshader);
        var program = context.createProgram();
        context.attachShader(program, vertexShader);
        context.attachShader(program, fragmentShader);

        context.linkProgram(program);
        return program;
       
    function loadShader(context, type, source)
        var shader = context.createShader(type);
        context.shaderSource(shader, source);
        context.compileShader(shader);
        return shader;
    

    // 顶点着色器代码(决定顶在哪里,大小)
    var VSHADER_SOURCE = 
      'attribute vec4 a_Position;\\n' +
      'void main() \\n' +
      '  gl_Position = a_Position;\\n' + // 设置顶点的位置
      '  gl_PointSize = 10.0;\\n' +      // 设置顶点的大小
      '\\n';

    // 片元着色器代码(给像素上色)
    var FSHADER_SOURCE =
      'void main() \\n' +
      '  gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\\n' + // 设置顶点的颜色
      '\\n';
    </script>
  </body>
</html>

源码下载

点击下载源码(使用缓冲区对象画多个点)

参考

<<WebGL编程指南>>

以上是关于WebGL学习系列-使用缓冲区对象画多个点的主要内容,如果未能解决你的问题,请参考以下文章

WebGL学习系列-多边形绘制原理

WebGl通过缓冲区绘制多个点

WebGL学习系列-第一个程序

WebGL 3D 入门系列 --- 绘制渐变三角形:深入理解缓冲区

webgl 缓冲区

#yyds干货盘点#愚公系列2022年09月 微信小程序-WebGL画正方形