WebGL编程指南学习,界明城,2022-3-1
1. 只是简单的迁移罢了——在JavaScript中调用WebGL
在JavaScript中调用WebGL,类似于在C++中调用OpenGL,也类似在Java中调用NDK,是一种类似客户端-服务器的请求-返回两层机制。API就起到了通信的作用。因此,难点在于:
- 如何在JavaScript中获取WebGL传来的数据,主要是指WebGL里的变量(属性)及相关内容;
- 如何将JavaScript中的数据传到WebGL
1.1 着色器与JavaScript的数据“通信”
1.1.1 两种方式
- attribute变量:与顶点相关的数据(类似变量)
- uniform变量:对所有顶点(或与顶点无关)的数据(类似常量)
attribute来自GLSL,用来从外部向顶点着色器传输数据,只有顶点着色器能使用它
1.1.2 attribute变量“通信”程序范式
- 顶点着色器中声明attribute变量
- JavaScript中向顶点着色器传输数据
- 获取attribute变量的存储位置(从顶点着色器中获取位置,GLSL中变量可以看做存储在一个表里。每个变量长度不同,但是紧密排列。变量名代表该变量的起始位置,类似指针)
- 将定点位置传输给attribute变量(基于API,细节隐藏了)
顶点着色器
attribute vec4 a_Position;
void main(){
gl_Position = a_Position;
}
JavaScript
// 获取attribute变量的存储位置
var a_Position = gl.getAttributeLocation(gl.program, 'a_Position');
if (a_Position < 0){
console.log('找不到a_Position的位置');
return;
}
// 将顶点位置传输给attribute变量
gl.vertexAttrib3f(a_Position, 0.0, 0.0, 0.0);
- 着色器里
vec4
变量类型是指长度为4的向量gl.getAttributeLocation
获取变量位置,如果返回的是负值,说明该变量不在那个表里;gl.vertexAttrib3f
传输数据,这里3f是可变的,与输入数据类型对应,意思是长度为3的float
1.1.3 uniform变量“通信”程序范式
与attribute类似,但是uniform既可以在顶点着色器,也可以在片元着色器中使用
GLSL ES中,attribute只能指定为float,但是uniform可以是任意类型
- 着色器中声明uniform变量
- JavaScript向着色器传输数据
以片元着色器为例(uniform变量在所有着色器中共享)
片元着色器
uniform vec4 u_FragColor;
void main(){
gl_FragColor = u_FragColor;
}
JavaScript
// 获取u_FragColor变量的存储地址
var u_FragColor = gl.getUniformLocation(gl.program, 'u_FragColor');
if (!u_FragColor){
console.log('找不到u_FragColor变量');
return;
}
// 给uniform变量赋值
gl.uniform4f(u_FragColor, value);
- 和attribute的不同在于,
getUniformLocation
失败返回的不是-1而是NULL(历史问题)
1.2 着色器与JavaScript的”大规模通信“
WebGL提供了一种缓冲区机制(buffer),通过创建缓冲区对象,它可以一次性地向着色器传入大批量数据。缓冲区对象是”WebGL系统“(”服务器“)中的一块内存
例如:以缓冲区向顶点着色器传输数据,替代了gl.vertexAttrib3f
function main(){
...
// 设置顶点位置
var n = initVertexBuffers(gl);
if (n < 0){
console.log('设置顶点位置失败');
return;
}
...
// 绘制多个点
gl.drawArrays(gl.POINTS, 0, n);
}
- n是返回的顶点数量,如果返回负值,说明没能把数据写入顶点着色器
- 需要显式告诉WebGL系统,缓冲区里有多少个顶点的数据(n),以及绘制多少个(不一定全绘制出来)
1.2.1 如何创建缓冲区对象(Buffer Object)
- 创建缓冲区对象(gl.createBuffer)
- 绑定缓冲区对象(gl.bindBuffer)
- 将数据写入缓冲区对象(gl.bufferdata)
- 将缓冲区对象分配给一个attribute变量(gl.vertexAttribPointer)
- 开启(激活)attribute变量(gl.enableVertexAttribArray)
1,2,5 是OpenGL的传统艺能(仪式感),创建、绑定、激活;
3 是把数据写入缓冲区;
4 是把数据从缓冲区传入顶点着色器
function initVertexBuffers(gl){
// 待写入的数据
var vertices = new Float32Array...
var n = 3; // 点的个数
// 创建缓冲区对象
var vertexBuffer = gl.createBuffer();
if (!vertexBuffer){
...
return;
}
// 绑定缓冲区对象
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
// 向缓冲区对象中写入数据
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
// 获取attribute位置
var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
...
// 将缓冲区对象分配给a_Position变量
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);
// 连接a_Position变量与分配给它的缓冲区对象
gl.enableVertexAttribArray(a_Position);
}
说明及槽点
- 创建缓冲区对象/删除缓冲区对象
gl.createBuffer(),gl.deleteBuffer(buffer)
- 绑定缓冲区指的是把缓冲区对象绑定到WebGL系统中已经存在的目标(target)上
- 向缓冲区对象写数据
gl.bufferData
我们不能直接向缓冲区写数据,而只能向”目标“写数据。所以写数据前,必须先绑定。gl.bufferData(target, data, usage)
。代码里声明的变量vertexBuffer
其实就是个工具人。这是OpenGL的特色,所有这些通过create返回值,其实就是个”索引“,不是数据,也不是实例(指针) gl.vertexAttribPointer(location, size, type, normalized, stride, offset)
其实可以理解为”预分配“,就是说,将有”多么大,什么样类型的,数据间隔多少,存储位置偏移“的数据要赋值给你了!所以这里用了一个Pointer,其实就是说要赋给你那个数据的指针- 那这个数据从哪儿来?从某个target来,这里就是从gl.ARRAY_BUFFER来,所以要激活一下
gl.enableVertexAttribArray(a_Pisition)
这次是”实分配“
一般地,向某个attribute传数据只能传一次,所以通过缓冲区对象传数据和通过gl.vertexAttrib
传数据是互斥的
1.3 基本图形的绘制
最后再回头看一下gl.drawArrays()
gl.drawArrays()
可以绘制不同的基本图形,包括点、线、线条、回路、三角形、三角带、三角扇等