WebGL基础介绍

谈起WebGL可能有一些人比较陌生,实际上WebGL是一种3D绘图标准,这种绘图技术标准允许把JavaScript和OpenGL ES 2.0结合在一起,通过增加OpenGL ES 2.0的一个JavaScript绑定,WebGL可以为HTML5 Canvas提供硬件3D加速渲染,这样Web开发人员就可以借助系统显卡来在浏览器里更流畅地展示3D场景和模型了,还能创建复杂的导航和数据视觉化。显然,WebGL技术标准免去了开发网页专用渲染插件的麻烦,可被用于创建具有复杂3D结构的网站页面,甚至可以用来设计3D网页游戏等等。

此链接可以查看你的游览器是否支持WebGL以及支持的版本。
检测浏览器是否支持WebGL

看WebGL的背景实际上是JavaScript操作一些OpenGL接口,也就意味着,可能会编写一部分GLSL ES 2.0的代码,没错,你猜对了,WebGL只是绑定了一层,内部的一些核心内容,如着色器,材质,灯光等都是需要借助GLSL ES语法来操作的.

基于WebGL周边也衍生了众多的第三方库,如开发应用类的Three.js,开发游戏类的Egert.js等,都大大的降低了学习WebGL的成本,但是本着有问题解决问题,没问题制造问题在解决问题的程序猿态度,还是觉得应该稍微了解一下WebGL一些基本的概念,以便能更好的去理解不同框架带来的便捷以及优势。一些简单的效果其实无需多引入一个体积可观的三方库来实现。如下图的效果:

波浪预览

接下来先简单介绍一下使用到的知识要点。

创建webGL对象

不同浏览器生命WebGL对象方式有所区别,虽然大部分浏览器都支持experimental-webgl,而且以后会变成webgl,所以创建时做一下兼容处理

var canvas = document.getElementById("glcanvas");
gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");

着色器

WebGL依赖一种新的称为着色器(shader)的绘图机制。着色器提供了灵活且强大的绘制二维或三维图形的方法,所有WebGL必须使用它。着色器不仅强大,而且更复杂,仅仅通过一条简单的绘图指令是不能操作它的。

WebGL需要两种着色器

  • 顶点着色器(Vertex shader):顶点着色器是用来描述顶点特性(如位置、颜色等)的程序。顶点(Vertex)是指二维或三维空间的一个点,比如二维或三维空间线与线之间的交叉点或者端点。
  • 片元着色器(Fragment shader):进行逐片元处理过程(如光照等)的程序。片元(fragment)是一个WebGL的术语,你可以将其理解成像素。

着色器语言使用的是GLSL ES语言,所以在javascript需要将之存放在字符串中,等待调用编译

创建顶点着色器:
var VSHADER_SOURCE =
'void main() {\n' +
' gl_Position = vec4(0.0, 0.0, 0.0, 1.0);\n' +
' gl_PointSize = 10.0;\n' +
'}\n';

创建片元着色器:
var FSHADER_SOURCE =
'void main() {\n' +
' gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n' +
'}\n';

浏览器的整个过程如下:

WebGL渲染过程

着色器中包含几个内置变量:gl_Position, gl_PointSize, gl_FragColor

着色器语言中涉及到vec4的数据类型,此数据类型是一个思维浮点数组,所以其值不可以是整形如(1,1,1,1),正确应为:(1.0,1.0,1.0,1.0)

  • gl_Position: 为一种vec4类型的变量,且必须被赋值。四维坐标矢量,我们称之为齐次坐标,即(x,y,z,w)等价于三维左边(x/w,y/w,z/w),w相当于深度,没有特殊要求设置为1.0即可。

  • gl_PointSize:表示顶点的尺寸,也是浮点数,为非必填项,如果不填则默认显示为1.0。

  • gl_FragColor:该变量为片元着色器唯一的内置变量,表示其颜色,也是一个vec4类型变量,分别代表(R,G,B,A),不过颜色范围是从0.0-1.0对应Javascript中的#00-#FF

有了着色器我们就可以着手去绘制图像了,既然绘制3D图形,必然会有对应的三维坐标系,WebGL采用右手坐标系,如图所示:

坐标系

使用着色器

让我们来看看如何把着色器代码编译并且使用起来

着色器代码需要载入到一个程序中,webgl使用此程序才能调用着色器。
var program = gl.createProgram();
// 创建顶点着色器
var vShader = gl.createShader(gl.VERTEX_SHADER);
//创建片元着色器
var fShader = gl.createShader(gl.FRAGMENT_SHADER);
//shader容器与着色器绑定
gl.shaderSource(vShader, VSHADER_SOURCE);
gl.shaderSource(fShader, FSHADER_SOURCE);
//将GLSE语言编译成浏览器可用代码
gl.compileShader(vShader);
gl.compileShader(fShader);
//将着色器添加到程序上
gl.attachShader(program, vShader);
gl.attachShader(program, fShader);
//链接程序,在链接操作执行以后,可以任意修改shader的源代码,
对shader重新编译不会影响整个程序,除非重新链接程序
gl.linkProgram(program);
//加载并使用链接好的程序
gl.useProgram(program);

让我们尝试绘制一个点

gl.clearColor(0.0,0.0,0.0,1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.POINTS, 0 ,1);

我们来看一看最终结果,果然出来了一个点

绘制单点的结果

程序创建完之后,我们需要需要对着色器进行动态控制才能达到我们所需要的功能。

首先让我来介绍2个变量,我们需要借助这2个变量搭建的桥梁才能使JavaScript与GLSL ES之间进行沟通。

  • attribute: 用于顶点点着色器(Vertex Shader)传值时使用。
  • uniform:可用于顶点着色器(Vertex Shader)与片元着色器(Fragment Shader)使用。

将顶点动态化

先在顶点着色器代码中,将对应的vec4的固定值变成变量
var VSHADER_SOURCE =
'attribute vec4 a_Position;\n' +
'void main() {\n' +
' gl_Position = a_Position;\n' +
' gl_PointSize = 10.0;\n' +
'}\n';

位置参数使用了attribute变量来承载。这样WebGL对象就可以获取到对应的存储位置,就可以去动态改变GLSL变量了。

使用WebGL来获取对应参数的存储地址地址
//返回对应的地址信息
var aPosition = gl.getAttribLocation(gl.program, 'a_Position');
//判断地址是否获取成功
if(aPosition < 0) {
console.log('没有获取到对应position');
}
然后给变量赋值
gl.vertexAttrib3f(aPosition, 1.0, 1.0, 0.0);
//或者使用Float32Array来传参
var p = new Float32Array([1.0, 1.0, 1.0]);
gl.vertexAttrib3fv(aPosition, p);

注意:vertexAttrib3fv这个函数是典型的GLSL语法命名规范,
vertexAttrib函数功能,
3:对应需要传3个参数,或者是几维向量,
f:表示参数是float类型,
v:表示传如的为一个vector变量。

也就是说对应设置顶点着色器的函数有一下几种功能,参考文档

void gl.vertexAttrib1f(index, v0);
void gl.vertexAttrib2f(index, v0, v1);
void gl.vertexAttrib3f(index, v0, v1, v2);
void gl.vertexAttrib4f(index, v0, v1, v2, v3);
void gl.vertexAttrib1fv(index, value);
void gl.vertexAttrib2fv(index, value);
void gl.vertexAttrib3fv(index, value);
void gl.vertexAttrib4fv(index, value);

同样操作可以如下修改PointSize:
//着色器中添加变量
var VSHADER_SOURCE =
'attribute vec4 a_Position;\n' +
'attribute float a_PointSize;\n' +
'void main() {\n' +
' gl_Position = a_Position;\n' +
' gl_PointSize = a_PointSize;\n' +
'}\n';
var aPointSize = gl.getAttribLocation(gl.program, 'a_PointSize');
gl.vertexAttrib1f(aPointSize, 10.0);

片元着色器编程

对片元着色器变成需要使用uniform变量来承载。
var FSHADER_SOURCE =
'precision mediump float;\n'+
'uniform vec4 vColor;\n'+
'void main() {\n' +
' gl_FragColor = vColor;\n' + // Set the point color
'}\n';
获取片元着色器变量地址
var vColor = gl.getUniformLocation(gl.program, 'vColor');
给变量赋值
gl.uniform4f(vColor, 1.0, 0.0, 0.0, 1.0);
//或使用Float32Array来传参
var color = new Float32Array([1.0, 0.0, 0.0, 1.0]);
gl.uniform4fv(vColor,color)
注意:uniform3fv这个函数是典型的GLSL语法命名规范,
uniform3fv函数功能,
3:对应需要传3个参数,或者是几维向量,
f:表示参数是float类型,
u:表示参数是Uint32Array类型,
i:表示参数是integer类型,
ui:表示参数是unsigned integer类型,
v:表示传如的为一个vector变量。

顶点着色器对应函数,参考文档

void gl.uniform1ui(location, v0);
void gl.uniform2ui(location, v0, v1);
void gl.uniform3ui(location, v0, v1, v2);
void gl.uniform4ui(location, v0, v1, v2, v3);
void gl.uniform1fv(location, data, optional srcOffset, optional srcLength);
void gl.uniform2fv(location, data, optional srcOffset, optional srcLength);
void gl.uniform3fv(location, data, optional srcOffset, optional srcLength);
void gl.uniform4fv(location, data, optional srcOffset, optional srcLength);
void gl.uniform1iv(location, data, optional srcOffset, optional srcLength);
void gl.uniform2iv(location, data, optional srcOffset, optional srcLength);
void gl.uniform3iv(location, data, optional srcOffset, optional srcLength);
void gl.uniform4iv(location, data, optional srcOffset, optional srcLength);
void gl.uniform1uiv(location, data, optional srcOffset, optional srcLength);
void gl.uniform2uiv(location, data, optional srcOffset, optional srcLength);
void gl.uniform3uiv(location, data, optional srcOffset, optional srcLength);
void gl.uniform4uiv(location, data, optional srcOffset, optional srcLength);

着色器中的代码precision mediump float;表示的意思是着色器中配置的float对象会占用中等尺寸内存。
具体包含的尺寸:

  • highp for vertex positions,
  • mediump for texture coordinates,
  • lowp for colors.

如果不设置此参数会报错:


yanglei8.jpg

我们可以绘制自定义的点了,接下来我们就可以尝试绘制大批量点来达到波浪的基础效果,但是之前的操作都是针对一个点的,如何可以同时绘制多个订点呢,如果你的回答是循环数据,BINGGO,没错这样你的确是可以达到这个目的,但是不是我们接下来要讲的,因为在3D绘制的时候是会经常出现大批量点、线、面的绘制的,所以WebGL提供了一种承载机制来达到传递多点的能力,说了这么多,也让我们来看看它到底是什么吧

缓存区对象

之前的方式可以通过循环来绘制多个点,一次需要绘制多个点,需要同时传递进去多个点的数据。刚好,在WebGL中提供了一种机制:缓存区对象(buffer data),缓存区对象可以同时向着色器传递多个顶点坐标。缓存区是WebGL中的一块内存区域,我们可以向里面存放大量顶点坐标数据,可随时供着色器使用。

使用缓存区步骤

  • 创建缓存区对象(gl.createBuffer())
  • 绑定缓存区对象(gl.bindBuffer())
  • 将数据写入缓存区对象(gl.bufferData())
  • 将缓存区对象分配给一个attribute变量(gl.vertexAttribPointer())
  • 开启attribute变量(gl.enableVertexAttribArray())

首先,我们仍然需要创建WebGL对象、片元着色器以及顶点着色器,具体创建的步骤以及原理,可参考之前的教程。具体代码实现如下:
当创建好WebGL之后,可以通过着色器中的attrbute或者uniform对象来传递需要动态修改或设置的的变量。
接下来我们需要进行缓冲区的操作:
首先,需要创建一个缓冲区来承载大量顶点的坐标
//创建缓存区
var vertexBuffer = gl.createBuffer();
if(!vertexBuffer) {
log('创建缓存区失败。');
return -1;
}
//将创建的缓存区对象绑定到target表示的目标上
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
//开辟存储空间,向绑定在target上的缓存区对象中写入数据
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
//获取着色器中的变量值
var a_position = gl.getAttribLocation(gl.program, 'a_p');
//将缓存区对象绑定到着色器变量中
gl.vertexAttribPointer(a_position, 3, gl.FLOAT, false, 0, 0);
// 启用缓存区
gl.enableVertexAttribArray(a_position);
// 绘制缓存区中画的多个顶点
gl.drawArrays(gl.POINTS, 0 , array);

看完了绘制过程,让我们来拆解一下具体内容:

首先,我们要在茫茫内存中申请一个区域来放置缓存区对象的内容,但是我们无法直接放置缓存对象进入内存中,否则会无法识别对应的数据类型,从而无法达到存取自如的境界,那我们就需要将数据的类型告知内存,bingBuffer就是为解决此问题诞生的,函数会在内存中申请一部分区域,并且通过target来制定数据类型,也就是说,缓存区是需要放置在target表示的类型部分去存储。

gl.bindBuffer(target, buffer)

target: 指定存储缓存区的目标类型

  • gl.ARRAY_BUFFER : 指缓存区中包含了顶点的数据
  • gl.ELEMENT_ARRAY_BUFFER : 指缓存区中包含了顶点数据的索引值

buffer: 自己创建的缓存区对象

接下来,我们需要做的是填充刚刚申请的缓存区,我们需要使用一个符合GLSL语法的数据格式,Javascript中可用Float32Array类型来创建支持GLSL的数据。使用bufferData函数将数据放入缓存区内。

gl.bufferData(target, size, usage)

target: 同上
size: 为多个顶点坐标的集合数组
usage: 表示程序将如何使用缓存区中的数据

  • gl.STATIC_DRAW : 只会向缓存区对象中写入一次数据,但需要绘制很多次
  • gl.STREAM_DRAW : 只会向缓存区对象中写入一次数据,然后绘制若干次
  • gl.DYNAMIC_DRAW : 会想缓存区对象中多次写入数据,并绘制很多次

缓存区中已经存储了多个顶点坐标,接下来我们需要将此数据运用到对应的着色器上,才能真正的绘制出来可视化图像,如何传递呢?首先我们需要在着色器中建立一个attribute类型的变量以方便我们操作,着色器中的对象,着色器中存在对象之后,我们可以使用Javascript中getAttribLocation函数获取着色器中的attribute类型变量,并且通过vertexAttribPointer将其赋值改变,从而达到改变图像呈现。

gl.getAttribLocation(program,name)

param: webgl之前创建的进程
name: 变量名称

gl.vertexAttribPointer(name, size, type, normalized, stride, offset)

name: 指定要赋值的attribute变量位置
size: 指定每个顶点数据的分量个数(1或4)
type: 指定传入的数据格式

  • gl.BYTE: 字节型, 取值范围[-128, 127]
  • gl.SHORT: 短整型,取值范围[-32768, 32767]
  • gl.UNSIGNED_BYTE: 无符号字节型,取值范围[0, 255]
  • gl.UNSIGNED_SHORT: 无符号短整型, 取值范围[0, 65535]
  • gl.FLOAT: 浮点型

normalized: 表明是否将非浮点数的数据归入到[0, 1]或[-1, 1]区间
stride: 指定相邻2个顶点间的字节数,默认为0
offset: 指定缓存区对象中的偏移量,设置为0即可

如为2,则
new Float32Array([
    1.0, 1.0,
    1.0,1.0
])
代表2个顶点
如为4,则
new Float32Array([
    1.0, 1.0, 1.0,1.0
])
代表1个顶点

现在缓存区已经存在多个顶点数据,接下来我们来启用携带缓存区数据的attribute变量,使用enableVertexAttribArray来启用对应变量。

gl.enableVertexAttribArray(name)

name: 待启动的变量指针,也就是名称
所有的缓存区操作步骤我们都已经完成,那么接下来我们可以绘制出缓存区中的多个顶点

gl.drawArrays(mode, first, count)

mode: 需要绘制的图像形状

  • gl.POINTS: 绘制一个点。
  • gl.LINE_STRIP: 绘制一条直线到下一个顶点。
  • gl.LINE_LOOP: 绘制一条首尾相连的线。
  • gl.LINES: 绘制一条线。
  • gl.TRIANGLES: 绘制一个三角形。
    first: 绘制的开始点
    count: 需要绘制的图形个数
    看看屏幕吧,是不是出来了好多点,接下来我们就可以来绘制对应的波浪图了。
    接下来会讲解如何绘制波浪图。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,012评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,589评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,819评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,652评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,954评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,381评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,687评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,404评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,082评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,355评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,880评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,249评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,864评论 3 232
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,007评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,760评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,394评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,281评论 2 259

推荐阅读更多精彩内容