[WebGL] 更加灵活的描点实现

之前的代码实现了描绘一个巨大的、正方形的、红色的点,但是,稍有常识的人都能看出,那个点的颜色、位置、大小,都是无法在运行时改变的,这就比较尴尬了……

毫无疑问,Web 前端的编程语言是 ECMAScript,然而顶点着色器和片元着色器的控制用的却是 GLSL ES。为了能动态的修改着色器代码,我们就需要在着色器代码里添加需要动态编辑的变量,并且通过已有的方法来通过 JavaScript 获取并修改他们的值。

与之前相同,我们需要一个具有 canvas 的网页,其 ID 为 webgl。此处 HTML 代码可以直接参考之前的。同样我们也需要获取 WebGL 上下文,这些代码也是和之前一致的:

	var canvas = document.getElementById('webgl');
	var gl = getWebGLContext(canvas);

	if (!gl) {
		console.log('Error on getting context of WebGL');
		return;
	}

我们需要在着色器代码中添加相应的变量。其中,顶点着色器需要用 attribute 修饰符,是一个四维向量,而片元着色器则是使用的 uniform 修饰符,同样也是一个四维向量。片元着色器里的第一行 precision mediump float; 指明了数据精度:

	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 =
		'precision mediump float;\n' + 
		'uniform vec4 u_FragColor;\n' + 
		'void main() {\n' +
		'    gl_FragColor = u_FragColor;\n' +
		'}\n';

初始化这两个着色器。其实这里的工作不是调用一个函数这么简单。initShaders 函数是我使用的参考书中的库提供的,稍微看了下代码,似乎涉及了 shader 的编译与链接,还有程序对象的生成等内容。按照书本的意思,这些细节都会在后面详细说明,这里我也不去关注他们了。

	if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
		console.log('Error on initialize shaders.');
		return;
	}

嗯……接着就是使用 WebGL 提供的 getAttribLocation 函数和 getUniformLocation 函数,来获得那两个变量的位置。当获得位置出现错误时,函数将返回一个负值:

	var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
	if (a_Position < 0) {
		console.log('Error on getting the storage location of a_Position');
		return;
	}
	var u_FragColor = gl.getUniformLocation(gl.program, 'u_FragColor');
	if (u_FragColor < 0) {
		console.log('Error on getting the storage location of u_FragColor');
		return;
	}

这样下来,我们就可以直接的调用 gl.vertexAttrib3f 方法和 gl.uniform4f 方法,来动态的修改两个着色器的内容。为了体现出动态,做一个点到哪儿画到哪儿的页面,无疑是最简明的了。

因为涉及到了多个点的存储,我们需要新增加一个变量来保存已经单击过的点的位置,在 app.js 最开头增加一行:

	var points = new Array();

然后再写一个函数,来描绘 points 数组内的所有的点。其中,右上、左上、左下、右下的点的颜色依次为红、绿、蓝、白。

	function drawPoints() {
		gl.clearColor(0, 0 , 0, 1);
		gl.clear(gl.COLOR_BUFFER_BIT);
		for (var i = 0; i != points.length; ++i) {
			gl.vertexAttrib3f(a_Position, points[i].x, points[i].y, 0.0);
			if (points[i].x > 0 && points[i].y > 0)
				gl.uniform4f(u_FragColor, 1, 0, 0, 1);
			else if (points[i].x < 0 && points[i].y < 0)
				gl.uniform4f(u_FragColor, 0, 0, 1, 1);
			else if (points[i].x < 0)
				gl.uniform4f(u_FragColor, 0, 1, 0, 1);
			else
				gl.uniform4f(u_FragColor, 1, 1, 1, 1);
			gl.drawArrays(gl.POINT, 0, 1);
		}
	};

点的位置来源可以通过增加一个对 canvas 的 click 事件的监听,将获得的信息转换后 push 到 points 中。这里需要注意的就是 HTML 坐标与 GL 坐标的转换。说起来我还没说 WebGL 的默认坐标系呢(躺

想了想感觉还是没啥好说的,随手糊两张图应该就都能理解了……吧

名稱未設定 1

请叫我灵魂画师

然后就是代码了,理解了坐标系后转换应该也没啥难度了吧 pup

	canvas.onmousedown = function (e) => {
		var mx = e.clientX;
		var my = e.clientY;
		var rect = e.target.getBoundingClientRect();
		
		points.push({
			x: (2 * (mx - rect.left) - canvas.height) / canvas.height,
			y: (canvas.width - 2 * (my - rect.top)) / canvas.width
		});
		drawPoints();
	}

这样我们就做到了一个稍复杂但是更加灵活的描点网页了(

webgl2