来一点题外话
5G距离大家越来越近,今年在拉斯维加斯CES 2019的每个人都在谈论它将要做什么。如果你认为5G带来的只是下载视频更快,上网更加流畅,那你就错了。
5G可以带给我们的远不止这些。在5G时代,你眼前的一切都可以连接在一起,水杯、汽车、空调、电视机、农作物……真正实现了万物互联互通。
5G具有超高速率、超大连接、超低时延三大特性,通信速率会比4G高出10-100倍,5G生态圈中的云计算、AI、无人机、VR和大视频都会同步发展。
可以说,5G将引领一场新的网络革命。
5G对前端工程来说可能也是一场技术改革,过去和现在所经历的互联网繁荣都是4G对3G的颠覆。
对于前端工程师来说
- 相比4G,5G快的不只是速度
- 新的交互场景,新的交互形式
- 对安全,可靠等的要求越来越高
继移动互联网之后,物联网的发展将带来新的应用场景,包括智能家居,可穿戴设备等领域将带来大量的前端开发需求。前端将不限于传统的PC屏幕和各种尺寸的手机屏幕,这意味着前端工程师的战场将更加多样化,复杂化。
新的场景必然带来交互方式的改变,不论是传统的PC还是新出现的手机和pad设备,都是基于数遍或者触摸等接触式的操作,新的场景可能带来声音,动作等新的交互方式,也变得更加复杂。
而要做到这些,我们现有的技术可能不能全部满足我们想要实现的场景。就这样,我就踏上了学习WebGL的慢慢长路上。
WebGL概述
WebGL,是一项用来网页上绘制和渲染复杂三维图形,并允许用户与之进行交互的技术。WebGL技术结合了HTML5
和JavaScript
,允许开发者在网页上创建和渲染三维图形。
WebGl的起源
在个人计算机上使用最广泛的两种三维图形渲染技术是Direct3D
和OpenGL
。
Direact3D
是微软DireactX
技术的一部分,主要用于Windows平台。而OpenGL由于其开放和免费的特性,在多平台上都有广泛的使用。而我们WebGL也是源于继承OpenGL的。
OpenGL支持一项非常重要的特性,即可编程着色器方法。该特性被OpenGL2.0
继承,并成为了WebGL1.0
标准的核心部分。
着色器方法
着色器方法或称着色器,使用一种类似于C的编程语言实现金梅的视觉效果。编写着色器的语言有称为着色器语言,OpenGL ES 2.0
基于OpenGL着色器语言(GLSL),因此后者又称为OpenGl着色器语言(GLSL ES
),WebGL基于OpenGL ES 2.0
也使用GLSL ES编写着色器
WebGL的优势
现在我们可以使用css
或canvas
标签在网页上绘制二维图形,以呈现更丰富的内容,WebGL则走的更远,它允许JavaScript在网页上显示和操作三维图形。有了WebGL的帮助,开发三位的客户界面,运行三维的网页游戏,对互联网上的海量数据进行三维可视化都成为了可能。虽然,WebGL强大到令人惊叹,但是使用这项技术进行开发却很简单。
- 你只需要一个文本编辑器和浏览器。
- 你可以充分利用浏览器的功能。
webGL程序的结构

然而,因为通常GLSL ES
是(以字符串的形式)在Javascript中编写的,实际上WebGL程序也只需要用到JavaScript文件。虽然,WebGL让网页更复杂了,但它仍然保持着于传统的网页相同的机构。
WebGL入门
最短的WebGL程序:清空绘图区
function main(){
//获取canvas元素
var canvas = document.getElementById('webgl')
//获取WebGL绘图上下文
var gl = getWebGLcontext(canvas)
if(!gl){
console.log("没有获取到WebGl")
return
}
//指定清空canvas的颜色
gl.clearColor(0.0,0.0,0.0,1.0)
//清空canvas
gl.clear(gl.COLOR_BUFFER_BIT)
我们来归纳一下,WebGL的工作流程。
- 获取Canvas元素
- 获取WebGl绘图的上下文
- 设置背景色
- 清空canvas
其实,熟悉canvas绘制流程的朋友都应该会觉得眼熟。事实上,的确也差不多。
获取canvas元素
我们首先会去获取canvas元素,并将其保存在canvas变量里。
为WebGL获取绘图的上下文
var gl = getWebGLContext(canvas)
使用变量canvas来获取WebGL绘图的上下文。这个函数是WebGL编程有用的辅助函数之一。
getWebGLContext(element,[,debug]) | |
---|---|
element | 指定canvas元素 |
debug | 默认为false,如果设置为true,javascript中发生的错误显示在控制台,注意:在调试结束后关闭它,否则会影响性能 |
设置canvas的背景色
gl.clearColor(red,green,blue,alpha) 指定绘图区域的背景色
gl.clearColor(red,green,blue,alpha) | |
---|---|
red | 指定红色值(从0,0到1.0) |
green | 指定绿色值(从0,0到1.0) |
blue | 指定绿色值(从0,0到1.0) |
alpha | 指定透明度值(从0,0到1.0) |
如果任何值小于0.0或者大于1.0,那么就会分别截断为0.0或者1.0
在这里我们会发现第一个不同,就是颜色的分量值,一般我们都会用到rgba来表示颜色,它的颜色取值范围0-255之间,但是在WebGL中,它继承了OpenGL,所以它也遵循了OpenGL的颜色分量的取值,即从0.0到1.0。
第二个不同就是一旦指定了背景颜色,颜色就会贮存在WebGL系统中,在一下调用gl.clearColor()方法前不会改变。换句话来说,如果将来你什么时候还想用同一个颜色在清空一次绘图区,没必要在指定一次背景色。
清空canvas
最后,你就调用gl.clear()函数,用上面定义好的背景色清空(即填充背景,擦除已经绘制的内容)绘图区域。
注意:函数的参数是gl.COLOR_BUFFER_BIT
,而不是表示绘图区于的canvas。这是因为WebGL中的gl.clear()方法来自OpenGL,它基于多基本缓存区模型。清空绘图区域,实际上是在清空颜色缓冲区(color buffer),传递的参数COLOR_BUFFER_BIT
就是在告诉WebGL我们在操作颜色缓冲区。
gl.clearC(buffer)将指定缓冲区设定为预定的值,像例子所说,如果是像清空颜色缓冲区,那么将使用gl.clearColor定义的颜色。
gl.clearC(buffer) | |
---|---|
buffer | gl.COLOR_BUFFER_BIT 指定颜色缓存/gl.DEPTH_BUFFER_BIT 指定深度缓存 /gl。STENCIL_BUFFER_BIT 指定模版缓存 |
绘制一个点

接下来我们将画一个点,交互是随着我们的鼠标点击而出现,在不通的WebGL象限里出现的颜色也会不同。
这个例子很完善的展示了Javascript与WebGL之间是怎么交流沟通的,第一眼会很难看懂,可以试着理解理解。
//顶点着色器
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' + // uniform変数
'void main() {\n' +
' gl_FragColor = u_FragColor;\n' +
'}\n';
//初始化着色器
if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
console.log('着色器初始化失败');
return;
}
function main(){
//获取canvas
var canvas = document.getElementById('webgl')
//获取webgl上下文
var gl = getWebGLContext(canvas)
//if(!gl){
console.log('没有获取到WebGL')
return
}
//获取a_position变量的储存位置
var a_position = gl.getAttribLocation(gl.program,'a_position')
if (a_Position < 0) {
console.log('没有找到a_Position储存地址');
return;
}
//获取u_FragColor变量的存储的位置
var u_FragColor = gl.getUniformLocation(gl.program,'u_FragColor')
if (!u_FragColor) {
console.log('没有找到u_FragColor储存的地址');
return;
}
//注册鼠标点击时的事件响应函数
canvas.onmousedown = function(ev){
click(ev,gl,canvas,a_Position,u_FragColor)
}
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT)
}
var g_points=[]
var g_colors=[]
function click(ev,gl,canvas,a_Position,u_FragColor){
var x = ev.clientX;//鼠标点击处的X坐标
var y = ev.clientY;//鼠标点击处的Y坐标
var rect = ev.targetgetBoundingClientRect()
//调整到webGL的坐标系
x = ((x-rect.left ) - canvas.width/2) / (canvas.width/2)
y = ((x-rect.top ) - canvas.height/2) / (canvas.height/2)
//将坐标储存到g_points中
g_points.push([x,y])
//将点的颜色储存到g_colors中
if(x>=0.0 && y>= 0.0){ //第一象限
g_color.push([1.0,0.0,0.0,1.0]) //红色
}else if(){
g_color.push([0.0,1.0,0.0,1.0]) //绿色
}else{
g_color.push([1.0,1.0,1.0,1.0]) //白色
}
//清空canvas
gl.clear(gl.COLOR_BUFFER_BIT)
var len = g_points.length
for( let i = 0 ; i<len ; i++){
var xy = g_points[i]
var rgba = g_colors[i]
//将点的位置传输到a_position中
gl.vertxAttrib3f(a_Position,xy[0],xy[0],0.0)
//将点的颜色传输到u_FragColor变量中
gl.uniform4f(u_FragColor,rgba[0],rgba[1],raba[2].rgba[3])
gl.drawArrays(gl.POINTS,0,1)
}
}
着色器
函数的一开始,就定义了2个着色器,我们之前说过着色器是WebGL里最重要的一部分,我们要用WebGL绘图就必要要使用着色器。在代码中你会很明显的发现,着色器程序是以 字符串 的形式嵌入在Javascript中的。
在这里,我们用到了2个着色器
- 顶点着色器
- 片元着色器

这张图是从执行JavaScript程序到浏览器中显示结果的过程:首先运行Javascript程序,调用了WebbGL的相关方法,然后顶点着色器和片元着色器就会运行,在颜色缓冲区内进行绘制,这时就清空了绘图区,最后,颜色缓冲区中的内容就会自动在浏览器的canvas上显示出来。
顶点着色器
顶点着色器是用来描述顶点特性(如位置,颜色)的程序。顶点是指二维或三维空间的一个点。比如二维或三维图形的端点或交点。
//顶点着色器
var VSHADER_SOURCE =
'attribute vec4 a_Position;\n' +
'void main() {\n' +
' gl_Position = a_Position;\n' +
' gl_PointSize = 10.0;\n' +
'}\n';
这里用字符串写在Javascript里的语言就是我们说的OpenGL着色器语言,也就是说GLSL ES 登台了~
因为着色器程序必须预先处理成单个字符串的形式,所以我们用+
号把它们串联起来,每行以/n
结束,这不是必要的,只是为了检查代码错误的时候就报出错误的行号,方便我们进行调试,如果你不喜欢,不写也是可以的。
attribute
关键词attribute
被称为储存限定符,它表示接下来的变量(在我们这里例子中是a_Position)是一个attribute变量。attribute
变量必须声明成全局变量,数据将从着色器的外部传给该变量。(这里也就是说我们可以利用attribute
变量来传输我们需要值给着色器)
变量声明格式如下
<储存限定符> <类型> <变量名>
attribute vec4 a_Position
vec4
变量名 | 描述 |
---|---|
vec4 gl_Position | 表示顶点位置 |
float gl_PointSize | 表示点的尺寸(像素) |
注意:gl_Position变量必须被赋值,否则着色器是无法工作的,相反g_PonintSize并不是必须的,如果你不赋值,默认就是1.0.
类型 | 描述 |
---|---|
vec4 | 表示由4个浮点数组成的矢量 |
float | 便是浮点数 |
你现在应该发现了,Javascript
和GSSL ES
对变量定义的不同,Javascript
是弱类型的语言,而GSSL ES
是强类型的语言,也就是说在开发的过程中,我们必须明确明确指出某个变量是什么类型。
这里还需要注意的是,如果向某类型的变量赋一个不同类型的值,就会出错。
片元着色器
进行逐片元处理过程(光照等)的程序。片元是WebGL的一个术语,也可以理解称为像素。
// 片元着色器
var FSHADER_SOURCE =
'precision mediump float;\n' +
'uniform vec4 u_FragColor;\n' + // uniform変数
'void main() {\n' +
' gl_FragColor = u_FragColor;\n' +
'}\n';
uniform变量
我们前面知道了attribute变量用来定义顶点着色器变量的。很遗憾,在片元着色器中,我们需要使用uniform变量或者你还可以使用varying变量。
<储存限定符> <类型> <变量名>
uniform vec4 u_FragColor
这里需要一笔带过的的precision mediump float
使用精度限定词垃圾指定变量的范围(最大值与最小值)和精度,本例为中等精度。
初始化着色器
//初始化着色器
if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
console.log('着色器初始化失败');
return;
}
这里的initShaders
是WebbGL编程指南中内置的一个辅助函数,帮我们初始化字符串形式的着色器。
initShaders(gl,vshader,fshader) | |
---|---|
gl | 指定渲染上下文 |
vshager | 指定顶点着色器程序代码(字符串) |
fshader | 指定片元着色器代码(字符串) |
在初始化着色器之前,顶点着色器和片元着色器都是空白的,我们需要将字符串的形式着色器从Javascript
传给WebGL
系统,并建立着色器。
注意:着色器运行在WebGL系统中,而不是Javascript程序中。
这里你必须清楚,WebGL程序包括运行在浏览器中的Javascript和运行在WebGL系统的着色器程序这俩部分
获取attribute变量和uniform变量的存储地址
为什么要获取变量地址?
WebGL对着色器进行解析,辨识出着色器具有的attribute
变量和uniform
变量,每个变量都具有一个储存地址,以便通过储存地址向变量传输数据。比如,当你想向顶点着色器多的a_Position变量传输数据时,首先需要向WebGL系统请求该变量的储存地址。
我们使用gl.getAttribuLocation()
和gl.getUniformLocation()
来获取地址。
//获取a_position变量的储存位置
var a_position = gl.getAttribLocation(gl.program,'a_position')
if (a_Position < 0) {
console.log('没有找到a_Position储存地址');
return;
}
//获取u_FragColor变量的存储的位置
var u_FragColor = gl.getUniformLocation(gl.program,'u_FragColor')
if (!u_FragColor) {
console.log('没有找到u_FragColor储存的地址');
return;
}
gl.getAttribuLocation(program,name) | |
---|---|
program | 指定包含顶点着色器或片元着色器程序对象 |
name | 指定想要获取其储存地址的attribute变量的名称 |
gl.getUniformLocation(program,name) | |
---|---|
gl | 指定渲染上下文 |
program | 指定包含顶点着色器或片元着色器程序对象 |
name | 指定想要获取其储存地址的uniform变量的名称 |
注意:第一个参数是一个程序对象,包含顶点着色器或者片元着色器。现在我们只需要理解gl.program
做为参数即可。
向attribute变量和uniform变量赋值
一旦将attibute
和uniform
变量的储存地址保存在javascriot变量a_Position和u_FragColor中,下面就需要使用该变量来向着色器传入值。我们将使用gl.vertexArrib3f()
和gl.uniform4f()
gl.vertexArrib3f(location,v0,v1,v2) | |
---|---|
location | 将要修改的attribute变量的地址 |
v0 | 指定填充attribute变量第一个分量值 |
v1 | 指定填充attribute变量第二个分量值 |
v2 | 指定填充attribute变量第三个分量值 |
我们将数据(v0,v1,v2)传输给由location参数指定的attribute变量。这里的v0,v1,v2,及点的x,y,z坐标值,都是浮点数。在这里你会发现,我之前说的a_Position是一个vec4类型,但是在这里只穿了3个值,当你忽略掉的第4个值会帮你自动不全为1.0。
gl.uniform4f(location,v0,v1,v2,v3) | |
---|---|
location | 将要修改的uniform变量的地址 |
v0 | 指定填充uniform变量第一个分量值 |
v1 | 指定填充uniform变量第二个分量值 |
v2 | 指定填充uniform变量第三个分量值 |
v3 | 指定填充uniform变量第四个分量值 |
我们将数据(v0,v1,v2,v3)传输给由location参数指定的unifrom变量。
到这里,用到的WebGL的方法都已经讲完了,也完成了JavaScript与WebGL之间绘制一个点所有的通信。其他的逻辑其实都是在写Javascript的代码了。
通过鼠标点击绘点
//注册鼠标点击事件响应函数
canvas.onmousedown = function(ev){ click(ev, gl, canvas, a_Position, u_FragColor) };
//响应鼠标点击事件
var g_points=[]
var g_colors=[]
function click(ev,gl,canvas,a_Position,u_FragColor){
var x = ev.clientX;//鼠标点击处的X坐标
var y = ev.clientY;//鼠标点击处的Y坐标
var rect = ev.targetgetBoundingClientRect()
//调整到webGL的坐标系
x = ((x-rect.left ) - canvas.width/2) / (canvas.width/2)
y = ((x-rect.top ) - canvas.height/2) / (canvas.height/2)
//将坐标储存到g_points中
g_points.push([x,y])
//将点的颜色储存到g_colors中
if(x>=0.0 && y>= 0.0){ //第一象限
g_color.push([1.0,0.0,0.0,1.0]) //红色
}else if(){
g_color.push([0.0,1.0,0.0,1.0]) //绿色
}else{
g_color.push([1.0,1.0,1.0,1.0]) //白色
}
//清空canvas
gl.clear(gl.COLOR_BUFFER_BIT)
var len = g_points.length
for( let i = 0 ; i<len ; i++){
var xy = g_colors[i]
var rgba = g_colors[i]
//将点的位置传输到a_position中
gl.vertxAttrib3f(a_Position,xy[0],xy[0],0.0)
//将点的颜色传输到u_FragColor变量中
gl.uniform4f(u_FragColor,rgba[0],rgba[1],raba[2].rgba[3])
gl.drawArrays(gl.POINTS,0,1)
}
}
在这里主要完成了:
- 获取到鼠标点击的位置并储存在一个数组中。
- 清空canvas
- 根据数组汇总的给个元素,在相应的位置绘制点。
鼠标点击时,我们能获取到ev
,该对象能获取到ev.clientX
和ev.clientY
。但是我们并不能直接去用这个点的坐标。因为:
- 鼠标点击位置的坐标是在"浏览器客户区"的坐标,而不是在canvas中的。
- cannvas的坐标系又和WebGL的坐标系,其原点位置和Y轴的正方向都不一样。

这时候,就需要会计算坐标系的转换。
首先,获取canvas在浏览器客户区的坐标,rect.left
和rect.top
。这样rect.left-x
和y-rect.top
就是浏览器坐标系中(x,y)换算到了canvas坐标系。
接下来,我们要将canvas坐标系换算到WebGL坐标系,需要知道canvas的中心点的位置,通过canvas.width
和canvas.height
求出(canvas.width/2,canvas.height/2)
然后,在(x-rect.left) - canvas.width/2, canvas.height/2 - (y-rect.top))
将canvas的坐标原平移到中心点。
接着,canvas的坐标x轴坐标区间为(0,canvas.width)
,y轴坐标区间(0,canvas.height)
。而WebGL的坐标系是-1.0到1.0,所以,最后我们要将x坐标除以canvas.width/2
,将y坐标除以canvas.height/2
。将canvas坐标映射到WebGL坐标。
x = ((x-rect.left ) - canvas.width/2) / (canvas.width/2)
y = ((x-rect.top ) - canvas.height/2) / (canvas.height/2)
后续的操作就是每次点击回去判断点在WebGl坐标系中哪一个象限,然后放到g_colors
数组中,循环遍历g_colors
和g_colors
,依次绘制出点的位置和颜色。

至此,终于学会了如何利用WebGL绘制简单的点图形了,撒花~~~

最后,这篇文章我学习了一个星期,整理了一个星期,看起来可能很枯燥,但是如果你也和我一样想学WebGL,我希望你能看完,还能找出我写错的地方,然后共同进步~ 哈哈哈哈哈哈哈 学习WebGL任重道远,接下来我会做一些更好玩的例子分享给大家~flag先立起来,满满填~ 拜了个拜~
