向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, 然后把点依次绘制出来。