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

栏目: 编程工具 · 发布时间: 5年前

内容简介:本节内容来自于小册WebGL 入门与实践。上节带领大家学习了基本三角形图元的绘制过程,以及如何使用本节通过一个鼠标每点击三次便会绘制一个渐变三角形的示例,带大家深入理解缓冲区的用法,最终效果如下图所示:

本节内容来自于小册WebGL 入门与实践。

上节带领大家学习了基本三角形图元的绘制过程,以及如何使用 缓冲区 向着色器传递多个数据,但上节只演示了往着色器传递 坐标 这一种数据,本节通过绘制渐变三角形,讲解一下如何通过缓冲区向着色器传递多种数据。

目标

本节通过一个鼠标每点击三次便会绘制一个渐变三角形的示例,带大家深入理解缓冲区的用法,最终效果如下图所示:

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

通过本节学习,你将会掌握如下内容:

  • 顶点数据在 buffer 中的排布方式。
  • 切换 buffer 时, bindBuffer 的重要性。
  • 使用多个 buffer 读取多种顶点数据。
  • 使用单个 buffer 读取多种顶点数据。
  • 如何实现渐变效果。

渐变三角形

上节我们实现的是单色三角形,通过在片元着色器中定义一个 uniform 变量,接收 JavaScript 传递过去的颜色值来实现。那渐变三角形的处理与单色三角形有何不同呢?

渐变三角形颜色不单一,在顶点与顶点之间进行颜色的渐变过渡,这就要求我们的顶点信息除了包含 坐标 ,还要包含 颜色 。这样在顶点着色器之后,GPU 根据每个顶点的颜色对顶点与顶点之间的颜色进行插值,自动填补顶点之间像素的颜色,于是形成了渐变三角形。

那既然我们需要为每个顶点传递坐标信息和颜色信息,因此需要在顶点着色器中额外增加一个 attribute 变量 a_Color ,用来接收顶点的颜色,同时还需要在顶点着色器和片元着色器中定义一个 varying 类型的变量 v_Color ,用来传递顶点颜色信息。

着色器

  • 依然从顶点着色器开始,顶点着色器新增一个 attribute 变量,用来接收顶点颜色。
//设置浮点数精度为中等精度。
    precision mediump float;
    //接收顶点坐标 (x, y)
    attribute vec2 a_Position;
    //接收浏览器窗口尺寸(width, height)
    attribute vec2 a_Screen_Size;
    //接收 JavaScript 传递的顶点颜色
    attribute vec4 a_Color;
    //传往片元着色器的颜色。
    varying vec4 v_Color;
    void main(){
      vec2 position = (a_Position / a_Screen_Size) * 2.0 - 1.0;
      position = position * vec2(1.0,-1.0);
      gl_Position = vec4(position, 0, 1);
      v_Color = a_Color;
    }

复制代码
  • 片元着色器

片元着色器新增一个 varying 变量 v_Color ,用来接收插值后的颜色。

//设置浮点数精度为中等。
    precision mediump float;
    //接收 JavaScript 传过来的颜色值(rgba)。
    varying vec4 v_Color;
    void main(){
      vec4 color = v_Color / vec4(255, 255, 255, 1);
      gl_FragColor = color;
   }
复制代码

我们的着色器部分还是和之前一样简单,只是在顶点着色器中增加了顶点颜色这一变量。

接下来我们用 JavaScript 向着色器传递数据。

JavaScript 部分

用缓冲区向着色器传递数据有两种方式:

  • 利用一个缓冲区传递多种数据。
  • 另一种是利用多个缓冲区传递多个数据。

上节绘制三角形的时候我们给顶点着色器传递的只是坐标信息,并且只用了一个 buffer ,本节示例,我们除了传递顶点的坐标数据,还要传递顶点颜色。 按照正常思路,我们可以创建两个 buffer ,其中一个 buffer 传递坐标,另外一个 buffer 传递颜色。

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

创建两个 buffer ,将 a_PositionpositionBuffer 绑定, a_ColorcolorBuffer 绑定,然后设置各自读取 buffer 的方式。

请谨记:程序中如果有多个 buffer 的时候,在切换 buffer 进行操作时,一定要通过调用 gl.bindBuffer 将要操作的 buffer 绑定到 gl.ARRAY_BUFFER 上,这样才能正确地操作 buffer 。您可以将 bindBuffer 理解为一个状态机, bindBuffer 之后的对 buffer 的一些操作,都是基于最近一次绑定的 buffer 来进行的。

以下 buffer 的操作需要在绑定 buffer 之后进行:

  • gl.bufferData:传递数据。
  • gl.vertexAttribPointer:设置属性读取 buffer 的方式。

方式一:多个 buffer 传递

我们使用一个 buffer 传递坐标信息,另一个 buffer 传递颜色信息。

// 创建 坐标信息 buffer
var positionBuffer = gl.createBuffer();
// 将当前 buffer 设置为 postionBuffer,接下来对 buffer 的操作都是针对 positionBuffer 了。
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// 设置 a_Position 变量读取 positionBuffer 缓冲区的方式。
var size = 2;
var type = gl.FLOAT;
var normalize = false;
var stride = 0;
var offset = 0;
gl.vertexAttribPointer(
      a_Position, size, type, normalize, stride, offset);
      
// 创建 颜色信息 buffer
var colorBuffer = gl.createBuffer();
// 将当前 buffer 设置为 postionBuffer,接下来对 buffer 的操作都是针对 positionBuffer 了。
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
// 设置 a_Position 变量读取 positionBuffer 缓冲区的方式。
var size = 4;
var type = gl.FLOAT;
var normalize = false;
var stride = 0;
var offset = 0;
gl.vertexAttribPointer(
      a_Color, size, type, normalize, stride, offset);

复制代码

gl.vertexAttribPointer( a_Color, size, type, normalize, stride, offset)。这个方法比较重要,上节已经向大家详细介绍了,如果还不太明白的,可以再次回顾下上节内容。

我们发现,上面代码对 buffer 的操作有些冗余,我们还是提取出一个方法 createBuffer 放到 webgl-helper.js ,减少重复编码,之后我们对 buffer 的一系列调用只需要如下两句就可以了:

var positionBuffer = createBuffer(gl, a_Position, { size: 2});
var colorBuffer = createBuffer(gl, a_Color, { size: 4});

复制代码

假如我们顶点坐标数组中有四个顶点 8 个元素【30, 30, 30, 40, 40, 30, 20, 0】,顶点着色器中的 a_Position 属性在读取顶点坐标信息时,以 2 个元素为一组进行读取:

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

又假如我们顶点颜色数组中有两个顶点 8 个元素 【244, 230, 100, 1, 125, 30, 206, 1】,那么顶点着色器中的 a_Color 属性在读取顶点颜色信息时,以 4 个元素(r, g, b, a)为一组进行读取,如下图所示。

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

以多少元素作为一个顶点信息进行读取的设置,是在调用 gl.vertexAttribPointer 时设置的 size 参数值。

言归正传,接下来我们为 canvas 添加点击事件:

canvas.addEventListener('click', e => {
    var x = e.pageX;
    var y = e.pageY;
    positions.push(x, y);
    //随机一种颜色
    var color = randomColor();
    //将随机颜色的 rgba 值添加到顶点的颜色数组中。
    colors.push(color.r, color.g, color.b, color.a);
    //顶点的数量是 3 的整数倍时,执行绘制操作。
    if (positions.length % 6 == 0) {
        gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.DYNAMIC_DRAW);
        gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.DYNAMIC_DRAW);
        render(gl);
    }
})
复制代码

万事俱备,只欠绘制:

function render(gl) {
      //用设置的清空画布颜色清空画布。
      gl.clear(gl.COLOR_BUFFER_BIT);
      if (positions.length <= 0) {
        return;
      }
      //绘制图元设置为三角形。
      var primitiveType = gl.TRIANGLES;
      //因为我们要绘制三个点,所以执行三次顶点绘制操作。
      gl.drawArrays(primitiveType, 0, positions.length / 2);
    }
复制代码

至此,三角形的渐变效果就实现啦。

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

另一种思路:使用 1 个 buffer 同时传递坐标和颜色信息

常规思路使用多个 buffer 传递多种数据(坐标和颜色),我们再演示另外一种思路:使用 1 个 buffer 同时传递多种数据。

着色器部分的代码和上面的一样,无需改动,改动的主要部分是 JavaScript 程序。

首先,我们依然是创建 buffer ,只不过这次是创建一个 buffer

var buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
复制代码

创建完 buffer ,接下来设置读取 buffer 的方式,我们有两个属性 a_Positiona_Color ,由于我们只有一个 buffer ,该 buffer 中既存储坐标信息,又存储颜色信息,所以两个属性需要读取同一个 buffer

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

我们可以看到,一个顶点信息占用 6 个元素,前两个元素代表坐标信息,后四个元素代表颜色信息,所以在下面设置属性读取 buffer 方式时, a_Colora_Position 的设置会有不同:

  • a_Position:坐标信息占用 2 个元素,故 size 设置为 2。 坐标信息是从第一个元素开始读取,偏移值为 0 ,所以 offset 设置为 0.

  • a_Color:由于 color 信息占用 4 个元素,所以 size 设置为 4 。 color 信息是在坐标信息之后,偏移两个元素所占的字节(2 * 4 = 8)。所以,offset 设置为 8。

  • stride:代表一个顶点信息所占用的字节数,我们的示例,一个顶点占用 6 个元素,每个元素占用 4 字节,所以,stride = 4 * 6 = 24 个字节。

gl.vertexAttribPointer(
      a_Color, 4, gl.FLOAT, false, 24, 8);
      
gl.vertexAttribPointer(
      a_Position, 2, gl.FLOAT, false, 24, 0);

复制代码

canvas 的点击事件也有所不同,一个顶点占用 6 个元素,三个顶点组成一个三角形,所以我们的 positions 的元素数量必须是 18 的整数倍,才能组成一个三角形:

canvas.addEventListener('click', e => {
      var x = e.pageX;
      var y = e.pageY;
      positions.push(x);
      positions.push(y);
      //随机出一种颜色
      var color = randomColor();
      //将随机颜色的 rgba 值添加到顶点的颜色数组中。
      positions.push(color.r, color.g, color.b, color.a);
      //顶点的数量是 18 的整数倍时,执行绘制操作。
      if (positions.length % 18 == 0) {
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
        render(gl);
      }
    })
复制代码

实现效果和上面操作多缓冲区的方式一样,但是单缓冲区不仅减少了缓冲区的数量,而且减少了传递数据的次数以及复杂度。

回顾

至此,我们对缓冲区的讲解就结束了,本节所讲知识点和上节基本类似,不同点在于用单个缓冲区传递多类数据时, gl.vertexAttribPointer 各个参数如何设置,理解这点对我们以后编程十分有用,希望大家课下多多练习,深刻理解它的用法。

到目前为止,我们掌握了三角形的绘制方法,接下来学习怎样用三角形构建其他图形。

下一节我们将从简单平面开始:先用三角形构建一个矩形。

小册

这一系列的内容来自于小册 WebGL 3D 入门与实践,如果大家对进阶知识感兴趣,可以到小册中去学习: 小册:WebGL 3D 入门与实践

小册内容除了包含 WebGL 相关的基础练习,还包括 3D 图形概念与相关数学的原理与推导,旨在帮助大家建立图形学的技术轮廓。这部分图形学知识独立于 WebGL,除了可以适用于 WebGL,还适用于 OpenGL 等。

当然,本小册主要目的还是帮助 Web 前端同学学习 3D 技术,除了介绍适用 WebGL 实现 3D 效果以外,还对 CSS3 中的 3D 技术相关属性进行了深入剖析,并演示了与数学库的结合使用,希望能够让前端同学不仅仅局限于一般的二维平面开发,也能够在 3D 开发上更近一步~

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

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

数字乌托邦

数字乌托邦

尼古拉斯•卡尔 / 姜忠伟 / 中信前沿出版社 / 2018-5 / 69.00

当下,技术与我们的关系变得越来越紧密不可分割,特别是智能手机等设备的出现,带给整个人类社会一场彻底的变革。的确,智能手机上的各种应用程序让我们的工作生活无比便利:社交媒体让我们能够和他人实时保持联络并传输信息,不再受时间、地点的限制;搜索引擎通过精准的算法将我们所需要的信息整合推送至屏幕上,让我们毫不费力就看到自己想要的;地图软件为我们的出行提供了更多路线选择,甚至可以使用语音导航,帮助我们顺利到......一起来看看 《数字乌托邦》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具