之前的代码实现了描绘一个巨大的、正方形的、红色的点,但是,稍有常识的人都能看出,那个点的颜色、位置、大小,都是无法在运行时改变的,这就比较尴尬了……
毫无疑问,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 的默认坐标系呢(躺
想了想感觉还是没啥好说的,随手糊两张图应该就都能理解了……吧
请叫我灵魂画师
然后就是代码了,理解了坐标系后转换应该也没啥难度了吧 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(); }
这样我们就做到了一个稍复杂但是更加灵活的描点网页了(