这里,咱们将向大家演示WebGL的一些奢侈阐明和根本应用,即便你后续应用第三方3D渲染引擎进行绘制,这里的基本概念仍旧是十分无益的,或者说是必要的。
在演示和阐明的时候,咱们抉择基于image3D.js来作为依赖库,但因为其奢侈的语法简直和原生WebGL是统一的,因而咱们认为这不是一个蹩脚的抉择(如果间接应用原生,代码会变得过于冗余,不好阐明)。
在后续的阐明中,你都无需查阅别的文档,当然,如果你想晓得的更具体,也能够间接拜访image3D.js 文档进行查阅。
绘制流程
个别最通用的绘制流程大抵如下:
筹备好着色器
→数据写入缓冲区并实现调配
→调用绘制办法进行绘制
不晓得你是否能够了解下面每个步骤是在干什么,咱们上面将通过一个逐步丰盛的例子来进行解释。
你能够提前看看咱们最终要绘制的成果:
这是二元函数 : y=x2+ z2的图像。
你能够点击此处进行查看运行成果。
着色器
绘制的第一步,就是筹备好两个着色器:顶点着色器
和片段着色器
。前者用于形容绘制的图形的点的地位,后者用于形容每个点的色彩。
可能这样说你会无奈了解,其实简略的说就是:咱们在绘图的时候,会一次性的把数据都传递给GPU,传递给GPU的数据须要一些"整顿"后再应用,而着色器就是驻留在GPU上的这段"整顿程序"。
咱们传递的数据是什么?不就是点的地位和点的色彩吗。所以,着色器就分为了顶点着色器和片段着色器(有时候也叫片元着色器),前者解决点,后者解决色彩。
所以,让咱们先看看这里的顶点着色器的具体代码:
attribute vec4 a_position;attribute vec4 a_color;varying vec4 v_color;void main(){ gl_Position = a_position; v_color = a_color;}
内置变量gl_Position就是绘图最终接管的点的数据,而咱们定义的变量a_position好比一个管道,咱们后续能够给这个变量赋值,也就间接的给gl_Position赋值了(也就是点的地位)。
那v_color是什么?你能够了解,绘图的时候,是以点为主的,每个点的色彩,须要借助点的地位来设置,而v_color就是地位到色彩的桥梁。还是间接看看片段着色器:
precision mediump float;varying vec4 v_color;void main(){ gl_FragColor=v_color;}
同样的存在一个内置变量,这里叫gl_FragColor, 其接管了来自顶点着色器的v_color。
我不晓得你是否了解了下面的行为,不过你可能也感觉到了,点的地位和色彩如何解决曾经筹备好了,后续咱们只须要借助a_position和a_color就能够设置数据了。
3D对象
在传递数据前,咱们先就基于这两个着色器创立3D对象,这一步非常简单,间接看代码:
var image3d = new image3D(document.getElementsByTagName('canvas')[0], { "vertex-shader": vsCode, "fragment-shader": fsCode, depth: true});
后续的所有操作,包含传递绘制和绘制等,间接调用这个对象上的接口就能够了。
特地阐明:vsCode和fsCode就是下面两个着色器的代码,是字符串。
传递数据
终于,能够给GPU传递数据了,所以,咱们先来筹备好数据:
var points = [];/** 具体的写法你能够间接看最终的代码,获取的思路大略就是: 三个点拼接成一个三角形, 每个点由6个数据组成,前3个示意点的地位,后3个示意点的色彩, 而一个个三角形拼接成最终的图形。*/// 因而,点的个数就是var num = points.length / 6;
这些三角形如何确定的?对xoz面,范畴是-1 ~ 1,切割成一个个正方形,而后斜切一下就能够了:
对于y值,由 y=x2+ z2计算获取。
数据筹备好了,间接设置即可:
image3d.Buffer().write(new Float32Array(points)).use('a_position', 3, 6, 0).use('a_color', 3, 6, 3);
数据写入缓冲区,而后调配给a_position和a_color即可。
绘制
因为是三角形,一共num个,间接执行绘制办法即可:
image3d.Painter().drawTriangle(0, num);
变换
尽管下面的话,图形应该曾经进去了,不过,咱们看最终的例子如同始终在旋转,那旋转成果是如何进去的?
聪慧的你肯定想到了顶点着色器,是的,咱们只须要批改一下顶点着色器中内置变量gl_Position接管的值,让其都和一个矩阵相乘就能够了。着色器中对应代码批改:
gl_Position=u_matrix * a_position;
后续,咱们不停的从新绘制,而在绘制前,都传递一个新的矩阵即可:
// 创立相机对象var camera = image3d.Camera({ size: 2}).rotateBody(0.9, 0, 1, 0).rotateBody(0.3, 1, 0, 0, 0, 0, 1).moveBody(0.5, 0, -1, 0);setInterval(function () { // 每次从新绘制前,都围绕射线(0,0,0)→ (0,1,0)旋转弧度0.05 camera.rotateBody(0.05, 0, 1, 0); // 传递照相机 image3d.setUniformMatrix("u_matrix", camera.value() ); // 绘制 painter.drawTriangle(0, num);}, 30);
更多
如果说,咱们心愿再加上光照、投影,或者说,可不可以给图形贴上“皮肤”等,怎么写,是不是显而易见了。
是的,思路都非常简单,通过批改两个着色器最终获取的变量值即可,如何批改?那你就能够借助一系列的数学计算和着色器的内置函数了。
残缺代码
<!DOCTYPE html><html lang="zh-cn"><head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <script src="https://unpkg.com/image3d@3"></script> <style> body { margin: 0; text-align: center; overflow: hidden; } </style> <!-- 顶点着色器 --> <script type='x-shader/x-vertex' id='vs'> attribute vec4 a_position; uniform mat4 u_matrix; attribute vec4 a_color; varying vec4 v_color; void main(){ gl_Position=u_matrix * a_position; v_color=a_color; } </script> <!-- 片段着色器 --> <script type='x-shader/x-fragment' id='fs'> precision mediump float; varying vec4 v_color; void main(){ gl_FragColor=v_color; } </script></head><body> <canvas width='300' height='300'>十分道歉,您的浏览器不反对canvas!</canvas> <script> // 创立3D对象并配置好画布和着色器 var image3d = new image3D(document.getElementsByTagName('canvas')[0], { "vertex-shader": document.getElementById("vs").innerText, "fragment-shader": document.getElementById("fs").innerText, depth: true }); /** * 许多的点,三个点拼接成一个三角形, * 每个点由6个数据组成,前3个示意点的地位,后3个示意点的色彩 * 而一个个三角形拼接成最终的图形 */ var points = []; // 一个依据值来确定色彩的办法 var valToColor = function (val) { return [val * 0.5, 0.5, 1 - val * 0.5]; } var dist = 0.02; var color, val; // y=x2+z2; for (var x = -1; x < 1; x += dist) { for (var z = -1; z <= 1; z += dist) { // 左上三角形 val = x * x + z * z; color = valToColor(val); points.push(x, val, z, color[0], color[1], color[2]); val = (x + dist) * (x + dist) + z * z; points.push(x + dist, val, z, color[0], color[1], color[2]); val = x * x + (z + dist) * (z + dist); points.push(x, val, z + dist, color[0], color[1], color[2]); // 右下三角形 val = (x + dist) * (x + dist) + z * z; color = valToColor(val); points.push(x + dist, val, z, color[0], color[1], color[2]); val = x * x + (z + dist) * (z + dist); points.push(x, val, z + dist, color[0], color[1], color[2]); val = (x + dist) * (x + dist) + (z + dist) * (z + dist); points.push(x + dist, val, z + dist, color[0], color[1], color[2]); } } // 点的个数 var num = points.length / 6; // 点的坐标 image3d.Buffer().write(new Float32Array(points)).use('a_position', 3, 6, 0).use('a_color', 3, 6, 3); var painter = image3d.Painter(); // 创立相机对象 var camera = image3d.Camera({ size: 2 }).rotateBody(0.9, 0, 1, 0).rotateBody(0.3, 1, 0, 0, 0, 0, 1).moveBody(0.5, 0, -1, 0); setInterval(function () { camera.rotateBody(0.05, 0, 1, 0); // 传递照相机 image3d.setUniformMatrix("u_matrix", camera.value() ); // 绘制 painter.drawTriangle(0, num); }, 30); </script></body></html>