在 《从画一个点入手学习使用 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 变量赋值
现在如果运行代码,会看到画布中央还是有一个点:
这是因为 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
时,得到的效果如下:
至此,我们使用 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)
使得背景重新变为红色,并且我们最终也只是能看到最新绘制的这个点。
原文链接:https://juejin.cn/post/7334141485029015561 作者:亦黑迷失