向attribute变量赋值
接上一节代码, 一旦将attribute变量存储地址保存在js变量a_Position中, 下面就需要使用该变量来想着色器传入值。我们使用gl.vertexAttrib3f()函数来完成这一步

下面是该函数的规范
gl.vertexAttrib3f(location, v0, v1,v2)
| 参数 | location | 指定要修改的attribute变量的存储位置 |
|---|---|---|
| v0 | 指定填充attribute变量第一个分量的值 | |
| v1 | 指定填充attribute变量第二个分量的值 | |
| v2 | 指定填充attribute变量第三个分量的值 | |
| 返回值 | 无 | |
| 错误 | INVALID_OPERATION | 没有当前的program对象 |
| INVALID_VALUE | location大于等于attribute变量的最大数目 |
该函数第一个参数是attribute变量的存储地址,即gl.getAttribLocation()的返回值。第二三四个参数是三个浮点型数值。即点的x, y, z坐标值。函数被调用后,这三个值被一起传给顶点着色器中的a_Position变量。
gl.vertexAttrib3f()是一系列同族函数中的一个,该系列函数的任务就是从js向顶点着色器中的attribute变量传值。
通过鼠标点击绘点
效果如图

<!doctype html><html><head><meta charset="UTF-8"><meta name="viewport"content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>Document</title></head><body onload="main()"><canvas id="canvas" height="400" width="400">你的浏览器不支持WebGL,请更换新的浏览器</canvas></body><script src="../lib/webgl-util.js" ></script><script src="../lib/webgl-debug.js" ></script><script src="../lib/cuon-utils.js" ></script><script>//顶点着色器程序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";function main() {//首先获取到canvas的dom对象var canvas = document.getElementById("canvas");//获取到WebGL的上下文var gl = getWebGLContext(canvas);//不支持WebGL的浏览器将打印一个错误,并结束代码运行if (!gl) {console.log("浏览器不支持WebGL");return;}//初始化着色器if(!initShaders(gl,VSHADER_SOURCE,FSHADER_SOURCE)){console.log("初始化着色器失败");return;}// 获取attribute变量的存储位置var a_Position = gl.getAttribLocation(gl.program, 'a_Position');// 将顶点位置传输给attribute变量// gl.vertexAttrib3f(a_Position, 0.0, 0.0, 0.0);// 注册鼠标点击事件响应函数canvas.onmousedown = function(ev) {click(ev, gl, canvas, a_Position);}//指定一个覆盖(清空)canvas的颜色gl.clearColor(0.0, 0.0, 0.0, 1.0);//执行清空gl.clear(gl.COLOR_BUFFER_BIT);// //绘制一个点// gl.drawArrays(gl.POINTS,0,1);}var g_points = [];function click(ev, gl, canvas, a_Position) {var x = ev.clientX;var y = ev.clientY;var rect = ev.target.getBoundingClientRect();x = ((x - rect.left) - canvas.width/2)/(canvas.height/2);y = (canvas.height/2 - (y-rect.top)) / (canvas.width/2);// 将坐标存储到g_points数组中g_points.push(x);g_points.push(y);gl.clear(gl.COLOR_BUFFER_BIT);var len = g_points.length;for (let i = 0; i < len; i+=2) {gl.vertexAttrib3f(a_Position, g_points[i], g_points[i+1], 0.0);gl.drawArrays(gl.POINTS,0,1);}}</script></html>
看完上面一段吧啦吧啦一堆代码之后,最终就会实现我们想要的效果。 可以发现,80%的代码都是前面写过的代码,只有后面做了一些调整。 下面就来分析下原理
注册事件响应函数
获取了webGL上下文,初始化了着色器,获取了attribute变量的存储地址 53-74行。 这些流程与前面的demo一致。 主要区别是后面定义和注册了click函数。
事件响应函数能够响应用户在网页上的操作。关于如何注册一个事件响应函数这里就不多说了,是js的基础知识。
响应鼠标点击事件
来看下click函数到底做了什么
- 获取鼠标点击的位置,并存储在一个数组中
- 清空canvas
- 根据数组的每个元素,在相应的位置绘制点
鼠标点击的位置信息存储在ev对象中,我们可以直接获取到点击坐标。但是,由于以下两个原因,不能直接使用这两个坐标值
- 鼠标点击的位置是在浏览器客户区中的坐标(client area中), 而不是在canvas中的
- canvas的坐标系与webGL的坐标系其原点位置和y轴的正方向位置也不一样。

回顾这张图
我们需要先把鼠标坐标转为canvas坐标,然后再转为WebGL坐标

这里就是坐标转换代码
首先, 获取canvas在浏览器中的坐标, rect.left, rect.top是canvas原点在浏览器中的坐标。
x - rect.left和y - rect.top就是把鼠标点击坐标转为canvas坐标。
接下来, canvas坐标转为WebGL坐标。 做这一步我们需要知道canvas的中心点坐标。 canvas.height和canvas.width获取canvas的高和宽 而中心点坐标就是(canvas.height/2, canvas.width/2)
然后利用((x - rect.left) - canvas.width/2) 和 (canvas.height/2 - (y-rect.top))将canvas原点平移到中心点
因为webGL坐标系区间是从-1到1 所以x坐除以 canvas.height/2 y轴坐标除以canvas.height/2 将canvas坐标映射到webGL坐标。
每一次鼠标点击的时候都会将坐标存储在g_points中。WebGL使用的是颜色缓冲区。系统中的绘制操作实际上是在颜色缓冲区中进行绘制的,绘制结束后系统将缓冲区中的内容显示在屏幕上,然后颜色缓冲区就会被重置。其中的内容会丢失。因此需要把每次的点击坐标都记录下来,鼠标每次点击之后,程序都重新绘制了所有的点。
代码中我们清空了canvas, 然后把点依次绘制出来。
