webgl|《前端图形学从入门到放弃》001 画一个三角形

这是面向前端的图形学课程。希望用浅显的语言为大家解释清图形学的一些概念。我们使用的前端最容易webgl,虽然不免还是要接触GLSL,别打我,这已经是最简单的!

我们开始第一节,矩阵....不还是画个三角形吧!
webgl是什么? 说人话webgl就是个工具,可以拿来画图的,它依赖于canvas,在canvas上你可以获取2d的上下文,也可以获取webgl的上下文。类似宝可梦新手村可以选蒜头王八也可以选黄皮耗子
webgl|《前端图形学从入门到放弃》001 画一个三角形
文章图片

所以第一步我们就创建一个canvas,并获取webgl上下文

开始擦滑板咯!小画家 这样我们就获取了canvas的掌控权,canvas相当于一块画布,在画图之前我们得像保证画面是干净的。
gl提供了两个api来做这件事:gl.clearColorgl.clear
gl.clearColor接受(r,g,b,a)的颜色数据,相当于给画布选一个底色;
gl.clear用来清除buffer,至于什么是buffer我们后面会讲。这里只需要知道gl在没次绘图时都可以记录颜色和深度两个buffer,再次绘制时需要清除!
顶点着色器和片段着色器 着色器是足以熄灭你学习webgl的热情的魔鬼。但理解之后你也会觉得豁然开朗!首先三维空间的物体不管多复杂都是一些集合体,所谓点动成线,线动成面,面动成体。
所以点是描述空间物体的最小单元。
webgl|《前端图形学从入门到放弃》001 画一个三角形
文章图片

顶点着色器就是用来处理我们传入的顶点的,在下节课我们将使用顶点着色器对三角形进行旋转,缩放,平移等操作。
而与之相对的是片段着色器,它主要处理围成图形的上色工作。
这并不是明确的定义,但有助于你的理解:顶点着色器提供裁剪空间坐标值片断着色器提供颜色值
着色器的语言与js不同是一种类c语言,就是大学计算机学的那种需要main函数的语言。所以作色器的语法总结起来就是
// 各种变量 balabala void main() { //各种操作 balabala }

上文我们说过可以把变量传入着色器,由于作色器现在我们来说下常见的变量
  • Attributes(属性):属性用来指明怎么从缓冲中获取所需数据并将它提供给顶点着色器。 例如你可能在缓冲中用三个32位的浮点型数据存储一个位置值。
  • buffer(缓冲):缓冲是发送到GPU的一些二进制数据序列,通常情况下缓冲数据包括位置,法向量,纹理坐标,顶点颜色值等。 你可以存储任何数据。
  • Uniforms(全局变量):全局变量在着色程序运行前赋值,在运行过程中全局有效。
  • Textures(纹理):纹理是一个数据序列,可以在着色程序运行中随意读取其中的数据。大多数情况我们不会自己写材质,就直接用纹理了。
  • Varyings(可变量):可变量是一种顶点着色器给片断着色器传值的方式,依照渲染的图元是点, 线还是三角形,顶点着色器中设置的可变量会在片断着色器运行中获取不同的插值。
下面我们来写一个最简单的顶点作色器
【webgl|《前端图形学从入门到放弃》001 画一个三角形】gl接受一个字符串的作色器代码,你可以使用数组或任意形式提供这个字符串,我个人通常写在一个script标签中:

这里我们定义了两个属性vertPositionvertColor用来存储传入的位置与颜色信息,可变量 fragColor会继续传递给片段着色器使用。
内置变量gl_Position的值是四维向量vec4(x,y,z,1.0),前三个参数表示顶点的xyz坐标值,第四个参数是浮点数1.0
由于案例中我们只需要绘制一个平面内的三角形,所以z值被我们设为0。
与之相对我们也能写出片段着色器:

使用作色器 webgl让初学者窝火的另一个原因是,他的编程逻辑很像底层语言,而非javascript,没有很多封装。做一件小事往往要执行多个方法。例如我们下面要使用作色器,写的这段语法:
// 我们先创建两个着色器 var vertexShader = gl.createShader(gl.VERTEX_SHADER); var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); // 这两个详单于作色器的容器,但还未被填充内容。所以想在需要获取我们前面写好的顶点作色器和片段作色器 var vertexShaderText = document.querySelector("#vertex-shader-2d").text; var fragmentShaderText = document.querySelector("#fragment-shader-2d").text; // 之后,我们要把两者关联起来 gl.shaderSource(vertexShader, vertexShaderText); gl.shaderSource(fragmentShader, fragmentShaderText); // 最后再编译着色器,才能供我们使用! gl.compileShader(vertexShader); gl.compileShader(fragmentShader);

内心戏:three.js真乃好发明!
这么还不能用,program上场! 写完上面的代码,其实着色器还是个半成品!
WebGL在电脑的GPU中运行。因此你需要使用能够在GPU上运行的代码。这样的代码需要提供成对的方法。每对方法中一个叫顶点着色器,另一个叫片断着色器,这些事你已经知道了!
而把他两组合起来就是program!
var program = gl.createProgram(); // 我们需要先创建program // 再把之前申明好的作色器附加到program上 gl.attachShader(program, vertexShader); gl.attachShader(program, fragmentShader); // 然后我们将这两个着色器 _link_(链接)到一个 _program_(着色程序) gl.linkProgram(program); // 检查 WebGLProgram 程序是否链接成功的同时还会检查其是否能在当前的 WebGL 中使用。 gl.validateProgram(program);

这样所有的前置工作都已经做好了,下面可以开始画三角形了。心累
创建buffer 前面我们说过在webgl中顶点和颜色信息被存在buffer上。
这里我们就偷懒把他们写在一个数组里:
var triangleVertices = [ // X, Y,R, G, B -0.5, -0.5,1.0, 0.0, 0.0, -0.5, 0.5,1.0, 1.0, 0.0, 0.5, -0.5,1.0, 1.0, 0.0, ];

上文我们知道着色器需要调用createShader方法创建,buffer自然如法炮制:
var triangleVertexBufferObject = gl.createBuffer(); // 绑定buffer数据类型 gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexBufferObject); // 关联变量与数据 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(triangleVertices), gl.STATIC_DRAW); // 虽然我们拥有了顶点和颜色数据的数组,但无法直接用。 // WebGL需要强类型数据,所以使用new Float32Array(triangleVertices)创建了32位浮点型数据序列 // 最后一个参数gl.STATIC_DRAW是提示WebGL我们将怎么使用这些数据。

还是没用! 但此时buffer上的数据和着色器上的数据还未对应,因此需要吧着色器的属性取出。
var positionAttribLocation = gl.getAttribLocation(program, 'vertPosition'); var colorAttribLocation = gl.getAttribLocation(program, 'vertColor'); // 并从triangleVertices中复制数据到序列中,然后gl.bufferData复制这些数据到GPU的Buffer对象上。 // 由于我们的顶点数据和颜色数据写在了一起,我们需要告诉webgl哪些是顶点哪些是颜色 gl.vertexAttribPointer( positionAttribLocation, // 上文获取的本地属性 2, // 几个值能表示这个属性,这里是坐标只需要2个值x,y gl.FLOAT, // 类型 gl.FALSE, 5 * Float32Array.BYTES_PER_ELEMENT, // 步幅 0 // 起始点 ); gl.vertexAttribPointer( colorAttribLocation, 3, // 这里是颜色需要3个值r,g,b gl.FLOAT, gl.FALSE, 5 * Float32Array.BYTES_PER_ELEMENT, 2 * Float32Array.BYTES_PER_ELEMENT ); // 启动属性gl.enableVertexAttribArray(positionAttribLocation); gl.enableVertexAttribArray(colorAttribLocation); gl.useProgram(program); // gl.useProgram就与之前讲到的gl.bindBuffer相似,设置当前使用的着色程序。

绘制 至此我们就大功告成了,最后一步就是绘制,由于我们使用的是三角形面,一个三角形仅仅需要3个顶点,所以最终执行
gl.drawArrays(gl.TRIANGLES, 0, 3);

大功告成
webgl|《前端图形学从入门到放弃》001 画一个三角形
文章图片

var triangleVertices = [ // X, Y,R, G, B -0.5, -0.5,1.0, 0.0, 0.0, -0.5, 0.5,1.0, .0, 0.0, 0.5, -0.5,1.0, .0, 0.0,];

webgl|《前端图形学从入门到放弃》001 画一个三角形
文章图片

如果想要绘制正方形,只要多加一组点即可。把triangleVertices改为boxVertices
var boxVertices = [ // X, Y,R, G, B -0.5, -0.5,1.0, 0.0, 0.0, -0.5, 0.5,1.0, 1.0, 0.0, 0.5, -0.5,1.0, 1.0, 0.0, -0.5, 0.5,1.0, 1.0, 0.0, 0.5, 0.5,0.0, 1.0, 0.0, 0.5, -0.5,1.0, 1.0, 0.0 ];

但此时绘制的点就不再是3个,而是6个,因此修改绘制方法为:
gl.drawArrays(gl.TRIANGLES, 0, 6);
下期预告
这期作为一个热身,熟悉一下webgl的api,下期我们将开启一个比较硬核的话题矩阵

    推荐阅读