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学习系列-使用缓冲区对象画多个点的主要内容,如果未能解决你的问题,请参考以下文章