WebGL 之点的动态绘制

《从画一个点入手学习使用 WebGL》中,我们使用 webgl 在 canvas 画布的中心位置绘制了个点,但这个点是固定不动的,想改变它的位置还得去顶点着色器源码里手动改变 gl_Position 的值;另外这个点的颜色也是写死的,想改变颜色就得手动去改 gl_FragColor 的值,这样的代码显然不够灵活。本篇文章的任务,就是基于之前的代码,进一步研究在 webgl 中如何去动态地修改点的位置、大小与颜色。

attribute 变量

所谓动态绘制点,其实是让决定点位置的内置变量 gl_Position 的值可以通过变量传递:

const vsSource = `
  attribute vec4 aPosition;
  void main() {
    // 点的坐标
    gl_Position = aPosition;
    // ...
  }
`
  • aPosition 就是变量的名称,因为是 attribute 变量,所以我习惯用 a 开头。 GLSL 是大小写敏感的,其变量名声明规则同 js 类似,不能以数字开头,可以包含数字、字母和下划线,但不能是关键字或保留字,另外不能以 gl_webgl__webgl_ 开头,避免与 webgl 的一些内置变量冲突;
  • 因为 GLSL 是强类型语言,所以变量名前的 vec4 指定了变量的类型为具有 4 个浮点数元素的矢量;
  • attribute 为存储限定符(storage qualifier),至于什么是存储限定符,我就举个例子 —— 在 GLSL 中也有 const,而 const 也是存储限定符,表示声明的是一个常量,定义之后不能改变。 attribute 变量传递的都是顶点相关的数据,只能在顶点着色器中被访问,并且必须声明成全局变量,所以注意它的定义位置是位于 main 函数外部的。

给 attribute 变量赋值

现在如果运行代码,会看到画布中央还是有一个点:

WebGL 之点的动态绘制

这是因为 aPosition 被赋予了一个默认值 vec4(0.0, 0.0, 0.0, 1.0),如果我们要设置 aPosition 的值,就要先使用 gl.getAttribLocation() 去得到它的内存地址:

// 获取 aPosition 的内存地址
const aPosition = gl.getAttribLocation(program, 'aPosition')

gl.getAttribLocation() 传递的第 2 个参数就是要获取的属性名称,所以要加上引号;而第 1 个参数为包含了 aPosition 属性的 program,所以其定义位置需要在我们得到 program 之后。

获取到了 aPosition 的存储地址,就可以使用 gl.vertexAttrib4f() 来为顶点 attibute 变量赋值:

gl.vertexAttrib4f(aPosition, 1.0, 0.0, 0.0, 1.0)

gl.vertexAttrib4f() 的第 1 个参数就是要赋值的 attibute 变量的地址,后面 4 个参数为 attibute 变量的各分量值。当我设置第 1 个分量,此处也就是 x 为 1.0 时,得到的效果如下:

WebGL 之点的动态绘制

至此,我们使用 js 代码给 aPosition 赋值完毕,其实就是通过 attibute 变量实现了 js 代码与顶点着色器之间的数据传递。我们通过 attribute 变量实现了对gl_Position 的修改,进而改变了点的位置,自然也能通过 attribute 变量修改 gl_PointSize 来改变点的大小。那么我们要如何通过变量修改点的颜色呢?答案就是接下去介绍的 uniform 变量。

uniform 变量

uniform 变量在顶点着色器或片元着色器中都能够访问,是只读类型的变量,用来存储影响所有顶点的数据,其声明格式同 attribute 变量相同:

const fsSource = `
  precision mediump float;
  uniform vec4 uColor;
    void main() {
      // 点的颜色
      gl_FragColor = uColor;
    }
`

precision mediump float; 修改着色器的默认浮点数精度为中精度,进行精度限定可以提升运行效率,减少内存开支。在顶点着色器中,有默认将浮点数精度设置为高精度(highp),而在片元着色器中则没有。uColor 的类型为 vec4,是具有 4 个浮点数元素的矢量,所以需要指定精度。

给 uniform 变量赋值

给 uniform 变量赋值的过程与给 attribute 变量赋值类似,只不过是使用的方法名不同而已 —— 先使用 gl.getUniformLocation()获取 uniform 变量的内存地址,再使用 gl.uniform4f() 去给 uniform 变量赋值:

// 获取 uColor 的内存地址
const uColor = gl.getUniformLocation(program, 'uColor')
// 设置 uColor 的值
gl.uniform4f(uColor, 0.0, 1.0, 0.0, 1.0)

注意,虽然 uniform 变量可以在顶点着色器中被访问,但是不能用 uniform 变量传递顶点数据,因为 uniform 变量在一次渲染过程中是一致的(不变的),而不同顶点的坐标是不同的。

实现点的平移动画

接下来,我们能做的事就多了,比如可以让这个点动起来,并且不断地改变颜色:

let x = -1
r = 0
function animation() {
  x += 0.01
  if (x > 1) x = -1
  r += 0.01
  if (r > 1) r = 0
  // 改变点的位置
  gl.vertexAttrib4f(aPosition, x, 0.0, 0.0, 1.0)
  // 改变点的颜色
  gl.uniform4f(uColor, r, 1.0, 0.0, 1.0)
  // 改变画布颜色
  gl.clear(gl.COLOR_BUFFER_BIT)
  // 执行绘制
  gl.drawArrays(gl.POINTS, 0, 1)
  requestAnimationFrame(animation)
}
animation()

使用 requestAnimationFrame 来实现的动画效果如下:

webgl 同步绘图原理

此处能形成一个点从左到右平移的动画,而不是生成一系列 x 坐标相差 0.01 的点的原因,是因为 gl.drawArrays 在绘制图像时是同步的。而 requestAnimationFrame 是个宏任务,每次异步执行时画布都会被重置,上次画的点和背景色就都没了。所以在 animation 中执行绘制前我都调用了一次 gl.clear(gl.COLOR_BUFFER_BIT) 使得背景重新变为红色,并且我们最终也只是能看到最新绘制的这个点。

WebGL 之点的动态绘制
WebGL 之点的动态绘制

原文链接:https://juejin.cn/post/7334141485029015561 作者:亦黑迷失

(0)
上一篇 2024年2月14日 下午4:00
下一篇 2024年2月14日 下午4:11

相关推荐

发表回复

登录后才能评论