终端着色器,技术储备指南

WebGL技术储备指南

2015/12/22 · HTML5 · 1
评论 ·
WebGL

初稿出处: 天猫前端团队(FED)-
叶斋   

亚洲必赢官网 1

WebGL 是 HTML 5 草案的一部分,可以使得 Canvas 渲染三维场景。WebGL
就算还未有广泛应用,但极具潜力和想象空间。本文是自身就学 WebGL
时梳理知识系统的产物,花点时间整理出来与大家享用。

WebGL 是 HTML 5 草案的一有些,能够使得 Canvas 渲染三维场景。WebGL
纵然还未有广泛应用,但极具潜力和想象空间。本文是自我读书 WebGL
时梳理知识系统的产物,花点时间整理出来与大家享受。

WebGL 是 HTML 5 草案的一有些,可以使得 Canvas 渲染三维场景。WebGL
即使还未有广泛应用,但极具潜力和设想空间。本文是本人上学 WebGL
时梳理知识系统的产物,花点时间整理出来与大家分享。

着色器只好用在OpenGLES 2.X以上等可编程管道里,而在OpenGLES
1.X是不可以使用的。

示例

WebGL 很酷,有以下 demos 为证:

查找奥兹国
赛车游戏
泛舟的男孩(Goo
Engine Demo)

示例

WebGL 很酷,有以下 demos 为证:

找寻奥兹国
赛车游戏
泛舟的男孩(Goo
Engine Demo)

示例

管线,Pipeline,显卡执行的、从几何体到最终渲染图像的、数据传输处理总括的历程

本文的靶子

本文的预期读者是:不熟习图形学,熟稔前端,希望通晓或系统学习 WebGL
的同室。

正文不是 WebGL 的概述性小说,也不是全部详细的 WebGL
教程。本文只盼望变成一篇供 WebGL 初学者使用的总纲。

终端着色器,技术储备指南。本文的对象

正文的预料读者是:不熟谙图形学,熟练前端,希望明白或体系学习 WebGL
的同室。

正文不是 WebGL 的概述性小说,也不是全部详细的 WebGL
教程。本文只愿意变成一篇供 WebGL 初学者使用的纲要。

WebGL 很酷,有以下 demos 为证:

OpenGLES1.X中它是定位管道,整体式封闭的,中间的各道工艺按一定的流程顺序走。如图所示:

Canvas

了然 Canvas 的同室都了解,Canvas 绘图先要获取绘图上下文:

JavaScript

var context = canvas.getContext(‘2d’);

1
var context = canvas.getContext(‘2d’);

context上调用各类函数绘制图形,比如:

JavaScript

// 绘制左上角为(0,0),右下角为(50, 50)的矩形 context.fillRect(0, 0, 50,
50);

1
2
// 绘制左上角为(0,0),右下角为(50, 50)的矩形
context.fillRect(0, 0, 50, 50);

WebGL 同样要求取得绘图上下文:

JavaScript

var gl = canvas.getContext(‘webgl’); // 或 experimental-webgl

1
var gl = canvas.getContext(‘webgl’); // 或 experimental-webgl

只是接下去,如若想画一个矩形的话,就没这么不难了。实际上,Canvas
是浏览器封装好的一个制图环境,在事实上展开绘图操作时,浏览器如故须要调用
OpenGL API。而 WebGL API 大概就是 OpenGL API 未经封装,直接套了一层壳。

Canvas 的越来越多文化,可以参考:

  • JS
    权威指南的
    21.4 节或 JS
    高级程序设计中的
    15 章
  • W3CSchool
  • 阮一峰的 Canvas
    教程

Canvas

深谙 Canvas 的校友都晓得,Canvas 绘图先要获取绘图上下文:

var context = canvas.getContext('2d');

context上调用各样函数绘制图形,比如:

// 绘制左上角为(0,0),右下角为(50, 50)的矩形
context.fillRect(0, 0, 50, 50);

WebGL 同样须要取得绘图上下文:

var gl = canvas.getContext('webgl'); // 或 experimental-webgl

而是接下去,假使想画一个矩形的话,就没这么不难了。实际上,Canvas
是浏览器封装好的一个制图环境,在实际开展绘图操作时,浏览器依然须求调用
OpenGL API。而 WebGL API 大约就是 OpenGL API 未经封装,直接套了一层壳。

Canvas 的更加多学问,可以参照:

  • JS
    权威指南的
    21.4 节或 JS
    高级程序设计中的
    15 章
  • W3CSchool
  • 阮一峰的 Canvas
    教程

检索奥兹国

亚洲必赢官网 2

矩阵变换

三维模型,从文件中读出来,到绘制在 Canvas 中,经历了往往坐标变换。

设若有一个最不难易行的模子:三角形,多个终端分别为(-1,-1,0),(1,-1,0),(0,1,0)。那多少个数据是从文件中读出来的,是三角形开始河的坐标(局地坐标)。如下图所示,右手坐标系。

亚洲必赢官网 3

模型常常不会放在场景的原点,假使三角形的原点位于(0,0,-1)处,没有转动或缩放,三个终端分别为(-1,-1,-1),(1,-1,-1),(0,1,-1),即世界坐标。

亚洲必赢官网 4

绘制三维场景必须指定一个观望者,假使观望者位于(0,0,1)处而且看向三角形,那么七个顶峰相对于观看者的坐标为(-1,-1,-2),(1,-1,-2),(0,1,-2),即视图坐标。

亚洲必赢官网 5

观察者的眸子是一个点(那是看破投影的前提),水平视角和垂直视角都是90度,视野范围(目力所及)为[0,2]在Z轴上,观望者可以看出的区域是一个四棱台体。

亚洲必赢官网 6

将四棱台体映射为正规立方(CCV,焦点为原点,边长为2,边与坐标轴平行)。顶点在
CCV 中的坐标,离它最后在 Canvas 中的坐标已经很相近了,如若把 CCV
的前表面看成 Canvas,那么最后三角形就画在图中青色三角形的地点。

亚洲必赢官网 7

上述变换是用矩阵来拓展的。

有的坐标 –(模型变换)-> 世界坐标 –(视图变换)-> 视图坐标
–(投影变换)–> CCV 坐标。

以(0,1,0)为例,它的齐次向量为(0,0,1,1),上述变换的象征经过可以是:

亚洲必赢官网 8

上边多个矩阵依次是看破投影矩阵,视图矩阵,模型矩阵。三个矩阵的值分别取决于:观望者的眼光和视野距离,观望者在世界中的状态(地点和取向),模型在世界中的状态(地点和自由化)。计算的结果是(0,1,1,2),化成齐次坐标是(0,0.5,0.5,1),就是其一点在CCV中的坐标,那么(0,0.5)就是在Canvas中的坐标(认为
Canvas 中央为原点,长宽都为2)。

上边出现的(0,0,1,1)是(0,0,1)的齐次向量。齐次向量(x,y,z,w)可以代表三维向量(x,y,z)参加矩阵运算,通俗地说,w
分量为 1 时表示地方,w 分量为 0 时表示位移。

WebGL 没有提供任何有关上述变换的机制,开发者须求亲自总括顶点的 CCV
坐标。

至于坐标变换的越来越多内容,可以参见:

  • 电脑图形学中的5-7章
  • 转换矩阵@维基百科
  • 透视投影详解

相比较复杂的是模型变换中的绕任意轴旋转(平日用四元数生成矩阵)和投影变换(上面的例证都没收涉及到)。

至于绕任意轴旋转和四元数,可以参见:

  • 四元数@维基百科
  • 一个鬼子对四元数公式的求证

至于齐次向量的愈多内容,可以参见。

  • 微机图形学的5.2节
  • 齐次坐标@维基百科

矩阵变换

三维模型,从文件中读出来,到绘制在 Canvas 中,经历了累累坐标变换。

假使有一个最简易的模型:三角形,多少个极点分别为(-1,-1,0),(1,-1,0),(0,1,0)。那三个数据是从文件中读出来的,是三角形最开始的坐标(局部坐标)。如下图所示,右手坐标系。

亚洲必赢官网 9

模型经常不会放在场景的原点,如若三角形的原点位于(0,0,-1)处,没有转动或缩放,七个终端分别为(-1,-1,-1),(1,-1,-1),(0,1,-1),即世界坐标。

亚洲必赢官网 10

绘图三维场景必须指定一个寓目者,即使观看者位于(0,0,1)处而且看向三角形,那么多少个顶峰相对于观看者的坐标为(-1,-1,-2),(1,-1,-2),(0,1,-2),即视图坐标。

亚洲必赢官网 11

观望者的肉眼是一个点(那是看破投影的前提),水平视角和垂直视角都是90度,视野范围(目力所及)为[0,2]在Z轴上,观看者能够见到的区域是一个四棱台体。

亚洲必赢官网 12

将四棱台体映射为正式立方(CCV,焦点为原点,边长为2,边与坐标轴平行)。顶点在
CCV 中的坐标,离它说到底在 Canvas 中的坐标已经很接近了,即使把 CCV
的前表面看成 Canvas,那么最后三角形就画在图中粉红色三角形的职位。

亚洲必赢官网 13

上述变换是用矩阵来拓展的。

一对坐标 –(模型变换)-> 世界坐标 –(视图变换)-> 视图坐标
–(投影变换)–> CCV 坐标。

以(0,1,0)为例,它的齐次向量为(0,0,1,1),上述变换的表示经过可以是:

亚洲必赢官网 14

下面八个矩阵依次是看破投影矩阵,视图矩阵,模型矩阵。多少个矩阵的值分别取决于:观看者的眼光和视野距离,阅览者在世界中的状态(地点和可行性),模型在世界中的状态(地点和取向)。计算的结果是(0,1,1,2),化成齐次坐标是(0,0.5,0.5,1),就是其一点在CCV中的坐标,那么(0,0.5)就是在Canvas中的坐标(认为
Canvas 大旨为原点,长宽都为2)。

地方出现的(0,0,1,1)是(0,0,1)的齐次向量。齐次向量(x,y,z,w)能够代表三维向量(x,y,z)参预矩阵运算,通俗地说,w
分量为 1 时表示地方,w 分量为 0 时表示位移。

WebGL 没有提供其他关于上述变换的体制,开发者须要亲自计算顶点的 CCV
坐标。

关于坐标变换的愈多内容,可以参照:

  • 微机图形学中的5-7章
  • 改换矩阵@维基百科
  • 透视投影详解

比较复杂的是模型变换中的绕任意轴旋转(常常用四元数生成矩阵)和投影变换(上面的事例都没收涉及到)。

关于绕任意轴旋转和四元数,可以参见:

  • 四元数@维基百科
  • 一个老外对四元数公式的表明

关于齐次向量的更多内容,可以参见。

  • 统计机图形学的5.2节
  • 齐次坐标@维基百科

赛车游戏

从上图可以看到,那么些工艺顺序是定点的,整个经过又分为:处理顶点,处理片元,验证片元信息并存入内存

着色器和光栅化

在 WebGL
中,开发者是由此着色器来形成上述变换的。着色器是运行在显卡中的程序,以
GLSL 语言编写,开发者需求将着色器的源码以字符串的花样传给 WebGL
上下文的有关函数。

着色器有三种,顶点着色器和片元(像素)着色器,它们成对出现。顶点着色器义务是收取顶点的有些坐标,输出
CCV 坐标。CCV
坐标经过光栅化,转化为逐像素的数码,传给片元着色器。片元着色器的义务是确定每个片元的颜料。

终端着色器接收的是 attribute 变量,是逐顶点的数额。顶点着色器输出
varying 变量,也是逐顶点的。逐顶点的 varying
变量数据通过光栅化,成为逐片元的 varying
变量数据,输入片元着色器,片元着色器输出的结果就会显得在 Canvas 上。

亚洲必赢官网 15

着色器作用很多,上述只是基本成效。一大半炫酷的职能都是依靠着色器的。假若您对着色器完全没有概念,能够试着明亮下一节
hello world 程序中的着色器再回看一下本节。

有关更加多着色器的文化,可以参考:

  • GLSL@维基百科
  • WebGL@MSDN

着色器和光栅化

在 WebGL
中,开发者是透过着色器来已毕上述变换的。着色器是运作在显卡中的程序,以
GLSL 语言编写,开发者须求将着色器的源码以字符串的样式传给 WebGL
上下文的相干函数。

着色器有二种,顶点着色器和片元(像素)着色器,它们成对出现。顶点着色器职分是接受顶点的片段坐标,输出
CCV 坐标。CCV
坐标经过光栅化,转化为逐像素的多少,传给片元着色器。片元着色器的天职是规定每个片元的水彩。

极限着色器接收的是 attribute 变量,是逐顶点的多寡。顶点着色器输出
varying 变量,也是逐顶点的。逐顶点的 varying
变量数据经过光栅化,成为逐片元的 varying
变量数据,输入片元着色器,片元着色器输出的结果就会显得在 Canvas 上。

亚洲必赢官网 16

着色器功效很多,上述只是基本功效。半数以上炫酷的机能都是依靠着色器的。要是你对着色器完全没有概念,可以试着明亮下一节
hello world 程序中的着色器再回想一下本节。

至于越多着色器的学识,可以参见:

  • GLSL@维基百科
  • WebGL@MSDN

泛舟的男孩(Goo
EngineDemo)

Rasterizer:光栅化处理,当顶点处理完,会提交rasterizer来拓展光栅化处理,结果会吧顶点的坐标音信转换成能在显示屏展现的像素信息,即片元(Fragments)

程序

这一节解释绘制上述场景(三角形)的 WebGL
程序。点本条链接,查看源代码,试图精通一下。那段代码出自WebGL
Programming
Guide,我作了部分修改以适应本文内容。如若一切正常,你看到的应当是下面那样:

亚洲必赢官网 17

表达几点(倘诺从前不打听 WebGL ,多半会对上面的代码思疑,无碍):

  1. 字符串 VSHADER_SOURCE 和 FSHADER_SOURCE
    是终端着色器和片元着色器的源码。可以将着色器驾驭为有固定输入和输出格式的先后。开发者须求事先编写好着色器,再按照一定格式着色器发送绘图命令。
  2. Part2 将着色器源码编译为 program
    对象:先分别编译顶点着色器和片元着色器,然后连接两者。如若编译源码错误,不会报
    JS 错误,但足以因此任何
    API(如gl.getShaderInfo等)获取编译状态新闻(成功与否,如若出错的错误新闻)。
JavaScript

// 顶点着色器 var vshader = gl.createShader(gl.VERTEX\_SHADER);
gl.shaderSource(vshader, VSHADER\_SOURCE);
gl.compileShader(vshader); // 同样新建 fshader var program =
gl.createProgram(); gl.attachShader(program, vshader);
gl.attachShader(program, fshader); gl.linkProgram(program);

<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<tbody>
<tr class="odd">
<td><div class="crayon-nums-content" style="font-size: 13px !important; line-height: 15px !important;">
<div class="crayon-num" data-line="crayon-5b8f14b3a671c960813930-1">
1
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f14b3a671c960813930-2">
2
</div>
<div class="crayon-num" data-line="crayon-5b8f14b3a671c960813930-3">
3
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f14b3a671c960813930-4">
4
</div>
<div class="crayon-num" data-line="crayon-5b8f14b3a671c960813930-5">
5
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f14b3a671c960813930-6">
6
</div>
<div class="crayon-num" data-line="crayon-5b8f14b3a671c960813930-7">
7
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f14b3a671c960813930-8">
8
</div>
<div class="crayon-num" data-line="crayon-5b8f14b3a671c960813930-9">
9
</div>
</div></td>
<td><div class="crayon-pre" style="font-size: 13px !important; line-height: 15px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;">
<div id="crayon-5b8f14b3a671c960813930-1" class="crayon-line">
// 顶点着色器
</div>
<div id="crayon-5b8f14b3a671c960813930-2" class="crayon-line crayon-striped-line">
var vshader = gl.createShader(gl.VERTEX_SHADER);
</div>
<div id="crayon-5b8f14b3a671c960813930-3" class="crayon-line">
gl.shaderSource(vshader, VSHADER_SOURCE);
</div>
<div id="crayon-5b8f14b3a671c960813930-4" class="crayon-line crayon-striped-line">
gl.compileShader(vshader);
</div>
<div id="crayon-5b8f14b3a671c960813930-5" class="crayon-line">
// 同样新建 fshader
</div>
<div id="crayon-5b8f14b3a671c960813930-6" class="crayon-line crayon-striped-line">
var program = gl.createProgram();
</div>
<div id="crayon-5b8f14b3a671c960813930-7" class="crayon-line">
gl.attachShader(program, vshader);
</div>
<div id="crayon-5b8f14b3a671c960813930-8" class="crayon-line crayon-striped-line">
gl.attachShader(program, fshader);
</div>
<div id="crayon-5b8f14b3a671c960813930-9" class="crayon-line">
gl.linkProgram(program);
</div>
</div></td>
</tr>
</tbody>
</table>
  1. program
    对象急需指定使用它,才得以向着色器传数据并绘制。复杂的顺序常常有五个program 对 象,(绘制每一帧时)通过切换 program
    对象绘制场景中的差异作用。
JavaScript

gl.useProgram(program);

<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<tbody>
<tr class="odd">
<td><div class="crayon-nums-content" style="font-size: 13px !important; line-height: 15px !important;">
<div class="crayon-num" data-line="crayon-5b8f14b3a6720232020477-1">
1
</div>
</div></td>
<td><div class="crayon-pre" style="font-size: 13px !important; line-height: 15px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;">
<div id="crayon-5b8f14b3a6720232020477-1" class="crayon-line">
gl.useProgram(program);
</div>
</div></td>
</tr>
</tbody>
</table>
  1. Part3 向正在使用的着色器传入数据,蕴涵逐顶点的 attribute
    变量和全局的 uniform 变量。向着色器传入数据必须利用
    ArrayBuffer,而不是健康的 JS 数组。
JavaScript

var varray = new Float32Array(\[-1, -1, 0, 1, -1, 0, 0, 1, 0\])

<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<tbody>
<tr class="odd">
<td><div class="crayon-nums-content" style="font-size: 13px !important; line-height: 15px !important;">
<div class="crayon-num" data-line="crayon-5b8f14b3a6723482450329-1">
1
</div>
</div></td>
<td><div class="crayon-pre" style="font-size: 13px !important; line-height: 15px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;">
<div id="crayon-5b8f14b3a6723482450329-1" class="crayon-line">
var varray = new Float32Array([-1, -1, 0, 1, -1, 0, 0, 1, 0])
</div>
</div></td>
</tr>
</tbody>
</table>
  1. WebGL API 对 ArrayBuffer
    的操作(填充缓冲区,传入着色器,绘制等)都是因而 gl.ARRAY_BUFFER
    举办的。在 WebGL 系统中又很多好像的事态。
JavaScript

// 只有将 vbuffer 绑定到 gl.ARRAY\_BUFFER,才可以填充数据
gl.bindBuffer(gl.ARRAY\_BUFFER, vbuffer); // 这里的意思是,向“绑定到
gl.ARRAY\_BUFFER”的缓冲区中填充数据 gl.bufferData(gl.ARRAY\_BUFFER,
varray, gl.STATIC\_DRAW); // 获取 a\_Position
变量在着色器程序中的位置,参考顶点着色器源码 var aloc =
gl.getAttribLocation(program, 'a\_Position'); // 将 gl.ARRAY\_BUFFER
中的数据传入 aloc 表示的变量,即 a\_Position
gl.vertexAttribPointer(aloc, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(aloc);

<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<tbody>
<tr class="odd">
<td><div class="crayon-nums-content" style="font-size: 13px !important; line-height: 15px !important;">
<div class="crayon-num" data-line="crayon-5b8f14b3a6727492492738-1">
1
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f14b3a6727492492738-2">
2
</div>
<div class="crayon-num" data-line="crayon-5b8f14b3a6727492492738-3">
3
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f14b3a6727492492738-4">
4
</div>
<div class="crayon-num" data-line="crayon-5b8f14b3a6727492492738-5">
5
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f14b3a6727492492738-6">
6
</div>
<div class="crayon-num" data-line="crayon-5b8f14b3a6727492492738-7">
7
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f14b3a6727492492738-8">
8
</div>
<div class="crayon-num" data-line="crayon-5b8f14b3a6727492492738-9">
9
</div>
</div></td>
<td><div class="crayon-pre" style="font-size: 13px !important; line-height: 15px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;">
<div id="crayon-5b8f14b3a6727492492738-1" class="crayon-line">
// 只有将 vbuffer 绑定到 gl.ARRAY_BUFFER,才可以填充数据
</div>
<div id="crayon-5b8f14b3a6727492492738-2" class="crayon-line crayon-striped-line">
gl.bindBuffer(gl.ARRAY_BUFFER, vbuffer);
</div>
<div id="crayon-5b8f14b3a6727492492738-3" class="crayon-line">
// 这里的意思是,向“绑定到 gl.ARRAY_BUFFER”的缓冲区中填充数据
</div>
<div id="crayon-5b8f14b3a6727492492738-4" class="crayon-line crayon-striped-line">
gl.bufferData(gl.ARRAY_BUFFER, varray, gl.STATIC_DRAW);
</div>
<div id="crayon-5b8f14b3a6727492492738-5" class="crayon-line">
// 获取 a_Position 变量在着色器程序中的位置,参考顶点着色器源码
</div>
<div id="crayon-5b8f14b3a6727492492738-6" class="crayon-line crayon-striped-line">
var aloc = gl.getAttribLocation(program, 'a_Position');
</div>
<div id="crayon-5b8f14b3a6727492492738-7" class="crayon-line">
// 将 gl.ARRAY_BUFFER 中的数据传入 aloc 表示的变量,即 a_Position
</div>
<div id="crayon-5b8f14b3a6727492492738-8" class="crayon-line crayon-striped-line">
gl.vertexAttribPointer(aloc, 3, gl.FLOAT, false, 0, 0);
</div>
<div id="crayon-5b8f14b3a6727492492738-9" class="crayon-line">
gl.enableVertexAttribArray(aloc);
</div>
</div></td>
</tr>
</tbody>
</table>
  1. 向着色器传入矩阵时,是按列存储的。可以相比较一下 mmatrix
    和矩阵变换一节中的模型矩阵(第 3 个)。
  2. 极限着色器计算出的 gl_Position 就是 CCV
    中的坐标,比如最上面的终极(红色)的 gl_Position
    化成齐次坐标就是(0,0.5,0.5,1)。
  3. 向终极着色器传入的只是多个终端的水彩值,而三角形表面的水彩渐变是由那五个颜色值内插出的。光栅化不仅会对
    gl_Position 举行,还会对 varying 变量插值。
  4. gl.drawArrays()方法使得缓冲区进行绘图,gl.TRIANGLES
    指定绘制三角形,也可以变动参数绘制点、折线等等。

关于 ArrayBuffer 的详细音信,能够参见:

  • ArrayBuffer@MDN
  • 阮一峰的 ArrayBuffer
    介绍
  • 张鑫旭的 ArrayBuffer
    介绍

至于 gl.TRIANGLES
等其余绘制格局,可以参见上边那张图或那篇博文。

亚洲必赢官网 18

程序

这一节解释绘制上述情景(三角形)的 WebGL
程序。点其一链接,查看源代码,试图精通一下。那段代码出自WebGL
Programming
Guide,我作了有的修改以适应本文内容。要是一切正常,你看看的应该是上面那样:

亚洲必赢官网 19

解释几点(要是从前不领悟 WebGL ,多半会对上面的代码疑忌,无碍):

  1. 字符串 VSHADER_SOURCE 和 FSHADER_SOURCE
    是终极着色器和片元着色器的源码。可以将着色器明白为有定点输入和出口格式的次第。开发者须要事先编写好着色器,再根据一定格式着色器发送绘图命令。

  2. Part2 将着色器源码编译为 program
    对象:先分别编译顶点着色器和片元着色器,然后连接两者。如若编译源码错误,不会报
    JS 错误,但足以由此任何
    API(如gl.getShaderInfo等)获取编译状态音讯(成功与否,假如出错的错误音信)。

    // 顶点着色器
    var vshader = gl.createShader(gl.VERTEX_SHADER);
    gl.shaderSource(vshader, VSHADER_SOURCE);
    gl.compileShader(vshader);
    // 同样新建 fshader
    var program = gl.createProgram();
    gl.attachShader(program, vshader);
    gl.attachShader(program, fshader);
    gl.linkProgram(program);
    
  3. program
    对象必要指定使用它,才方可向着色器传数据并绘制。复杂的程序日常有多个program 对 象,(绘制每一帧时)通过切换 program
    对象绘制场景中的差别效用。

    gl.useProgram(program);
    
  4. Part3 向正在选取的着色器传入数据,包括逐顶点的 attribute
    变量和大局的 uniform 变量。向着色器传入数据必须使用
    ArrayBuffer,而不是健康的 JS 数组。

    var varray = new Float32Array([-1, -1, 0, 1, -1, 0, 0, 1, 0])
    
  5. WebGL API 对 ArrayBuffer
    的操作(填充缓冲区,传入着色器,绘制等)都是经过 gl.ARRAY_BUFFER
    举行的。在 WebGL 系统中又很多接近的动静。

    // 只有将 vbuffer 绑定到 gl.ARRAY_BUFFER,才可以填充数据
    gl.bindBuffer(gl.ARRAY_BUFFER, vbuffer);
    // 这里的意思是,向“绑定到 gl.ARRAY_BUFFER”的缓冲区中填充数据
    gl.bufferData(gl.ARRAY_BUFFER, varray, gl.STATIC_DRAW);
    // 获取 a_Position 变量在着色器程序中的位置,参考顶点着色器源码
    var aloc = gl.getAttribLocation(program, 'a_Position');
    // 将 gl.ARRAY_BUFFER 中的数据传入 aloc 表示的变量,即 a_Position
    gl.vertexAttribPointer(aloc, 3, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(aloc);
    
  6. 向着色器传入矩阵时,是按列存储的。可以相比较一下 mmatrix
    和矩阵变换一节中的模型矩阵(第 3 个)。

  7. 顶点着色器总计出的 gl_Position 就是 CCV
    中的坐标,比如最上面的终点(灰色)的 gl_Position
    化成齐次坐标就是(0,0.5,0.5,1)。

  8. 向终极着色器传入的只是三个极点的水彩值,而三角形表面的水彩渐变是由那多个颜色值内插出的。光栅化不仅会对
    gl_Position 进行,还会对 varying 变量插值。

  9. gl.drawArrays()方法使得缓冲区进行绘图,gl.TRIANGLES
    指定绘制三角形,也得以更改参数绘制点、折线等等。

有关 ArrayBuffer 的详细新闻,可以参考:

  • ArrayBuffer@MDN
  • 阮一峰的 ArrayBuffer
    介绍
  • 张鑫旭的 ArrayBuffer
    介绍

关于 gl.TRIANGLES
等其他绘制格局,可以参照上边那张图或那篇博文。

亚洲必赢官网 20

正文的目的

生成片元后,接下去就是对fragments片元的各个声明,即过滤掉无用的片元,裁剪掉不在视野内的片元,最终把有效片元存储入内存中。

深度检测

当五个外表重叠时,后面的模型会遮掩前面的模子。比如本条例子,绘制了五个交叉的三角(
varray 和 carray 的长短变为 18,gl.drawArrays 最终一个参数变为
6)。为了不难,这些事例去掉了矩阵变换进程,直接向着色器传入 CCV 坐标。

亚洲必赢官网 21

亚洲必赢官网 22

极限着色器给出了 6 个极端的 gl_Position ,经过光栅化,片元着色器得到了
2X 个片元(假如 X 为每个三角形的像素个数),每个片元都离散的 x,y
坐标值,还有 z 值。x,y 坐标就是三角形在 Canvas
上的坐标,但假使有多个颇具同等 x,y 坐标的片元同时出现,那么 WebGL
就会取 z 坐标值较小的可怜片元。

在深度检测此前,必须在绘制前拉开一个常量。否则,WebGL 就会听从在 varray
中定义的顺序绘制了,前边的会覆盖前边的。

JavaScript

gl.enable(gl.DEPTH_TEST);

1
gl.enable(gl.DEPTH_TEST);

实则,WebGL 的逻辑是如此的:依次拍卖片元,若是渲染缓冲区(那里就是
Canvas
了)的不得了与目前片元对应的像素还尚未绘制时,就把片元的水彩画到渲染缓冲区对应像素里,同时把片元的
z
值缓存在另一个深度缓冲区的同等地点;固然当前缓冲区的相应像素已经绘制过了,就去查看深度缓冲区中对应地方的
z 值,若是当前片元 z 值小,就重绘,否则就屏弃当前片元。

WebGL 的那套逻辑,对了然蒙版(前面会说到)有一对扶持。

深度检测

当七个外表重叠时,前边的模型会遮掩前边的模子。比如以此例子,绘制了八个交叉的三角(
varray 和 carray 的长度变为 18,gl.drawArrays 最终一个参数变为
6)。为了不难,这么些例子去掉了矩阵变换过程,直接向着色器传入 CCV 坐标。

亚洲必赢官网 23

亚洲必赢官网 24

极端着色器给出了 6 个极点的 gl_Position ,经过光栅化,片元着色器得到了
2X 个片元(即使 X 为每个三角形的像素个数),每个片元都离散的 x,y
坐标值,还有 z 值。x,y 坐标就是三角形在 Canvas
上的坐标,但假如有四个有着相同 x,y 坐标的片元同时出现,那么 WebGL
就会取 z 坐标值较小的可怜片元。

在深度检测以前,必须在绘制前拉开一个常量。否则,WebGL 就会根据在 varray
中定义的顺序绘制了,前边的会覆盖前面的。

gl.enable(gl.DEPTH_TEST);

实则,WebGL 的逻辑是这么的:依次拍卖片元,即使渲染缓冲区(那里就是
Canvas
了)的不得了与当下片元对应的像素还从未绘制时,就把片元的颜料画到渲染缓冲区对应像素里,同时把片元的
z
值缓存在另一个深度缓冲区的同一地点;若是当前缓冲区的应和像素已经绘制过了,就去查看深度缓冲区中对应地方的
z 值,若是当前片元 z 值小,就重绘,否则就屏弃当前片元。

WebGL 的那套逻辑,对了然蒙版(后边会说到)有一对帮扶。

本文的料想读者是:不谙习图形学,熟习前端,希望通晓或系列学习 WebGL
的同班。

光栅化处理进度,就是把矢量图转化成像素点的历程。大家屏幕上显示的镜头都是由像素结合,而三维物体都是点线面构成的。要让点线面变成能在显示屏上彰显的像素,就必要Rasterizer这一个进程。

顶点索引

gl.drawArrays()是规行矩步顶点的次第绘制的,而
gl.drawElements()可以令着色器以一个索引数组为顺序绘制顶点。比如这么些例子。

亚洲必赢官网 25

此间画了三个三角形,但只用了 5
个极点,有一个巅峰被多少个三角形共用。那时要求建立索引数组,数组的每个元素表示顶点的索引值。将数组填充至gl.ELEMENT_ARRAY,然后调用
gl.drawElements()。

JavaScript

var iarray = new Uint8Array([0,1,2,2,3,4]); var ibuffer =
gl.createBuffer(gl.ARRAY_BUFFER, ibuffer);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, iarray, gl.STATIC_DRAW);

1
2
3
4
var iarray = new Uint8Array([0,1,2,2,3,4]);
var ibuffer = gl.createBuffer(gl.ARRAY_BUFFER, ibuffer);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, iarray, gl.STATIC_DRAW);

顶点索引

gl.drawArrays()是听从顶点的顺序绘制的,而
gl.drawElements()可以令着色器以一个索引数组为顺序绘制顶点。比如其一例子。

亚洲必赢官网 26

此处画了八个三角,但只用了 5
个极点,有一个极端被八个三角形共用。那时急需树立索引数组,数组的每个元素表示顶点的索引值。将数组填充至gl.ELEMENT_ARRAY,然后调用
gl.drawElements()。

var iarray = new Uint8Array([0,1,2,2,3,4]);
var ibuffer = gl.createBuffer(gl.ARRAY_BUFFER, ibuffer);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, iarray, gl.STATIC_DRAW);

正文不是 WebGL 的概述性作品,也不是一体化详细的 WebGL
教程。本文只愿意变成一篇供 WebGL 初学者使用的纲要。

OpenGLES2.X可编程管道,由两VertexShader(顶点着色器)、FragmentsShader(片元着色器)组成,分别对应上图中的Coordinates
和Texture等红色块

纹理

attribute
变量不仅能够传递顶点的坐标,还足以传递其余任何逐顶点的多少。比如
HelloTriangle 程序把单个顶点的颜料传入了 a_Color,片元着色器收到
v_Color 后一向赋给 gl_FragmentColor,就决定了颜色。

attribute
变量还是可以辅助绘制纹理。绘制纹理的基本原理是,为每个终端指定一个纹理坐标(在(0,0)与(1,1,)的四方形中),然后传入纹理对象。片元着色器得到的是对应片元的内插后的纹路坐标,就选取那一个纹理坐标去纹理对象上取颜色,再画到片元上。内插后的纹路坐标很可能不正好对应纹理上的某部像素,而是在多少个像素之间(因为普通的图形纹理也是离散),那时可能会透过周围多少个像素的加权平均算出该像素的值(具体有若干种不一致格局,可以参考)。

比如这些例子。

亚洲必赢官网 27

纹理对象和缓冲区目标很类似:使用 gl 的 API 函数创立,需求绑定至常量
gl.ARRAY_BUFFER 和 gl.TEXTURE_2D
,都通过常量对象向其中填入图像和数据。分化的是,纹理对象在绑定时还亟需激活一个纹理单元(此处的gl.TEXTURE0),而
WebGL 系统帮衬的纹理单元个数是很单薄的(一般为 8 个)。

JavaScript

var texture = gl.createTexture();
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);
gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE,
textureImage); var sloc = gl.getUniformLocation(program, ‘u_Sampler’);
gl.uniform1i(sloc, 0);

1
2
3
4
5
6
7
8
var texture = gl.createTexture();
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, textureImage);
var sloc = gl.getUniformLocation(program, ‘u_Sampler’);
gl.uniform1i(sloc, 0);

片元着色器内评释了 sampler2D 类型的 uniform
变量,通过texture2D函数取样。

JavaScript

precision mediump float; uniform sampler2D u_Sampler; varying vec2
v_TexCoord; void main() { gl_FragColor = texture2D(u_Sampler,
v_TexCoord); };

1
2
3
4
5
6
precision mediump float;
uniform sampler2D u_Sampler;
varying vec2 v_TexCoord;
void main() {
  gl_FragColor = texture2D(u_Sampler, v_TexCoord);
};

纹理

attribute
变量不仅可以传递顶点的坐标,还能传递其他任何逐顶点的数目。比如
HelloTriangle 程序把单个顶点的水彩传入了 a_Color,片元着色器收到
v_Color 后平昔赋给 gl_FragmentColor,就控制了颜色。

attribute
变量还足以帮助绘制纹理。绘制纹理的基本原理是,为各种终端指定一个纹理坐标(在(0,0)与(1,1,)的方框形中),然后传入纹理对象。片元着色器获得的是对应片元的内插后的纹路坐标,就采用这些纹理坐标去纹理对象上取颜色,再画到片元上。内插后的纹路坐标很可能不正好对应纹理上的某部像素,而是在多少个像素之间(因为普通的图样纹理也是离散),那时可能会透过周围多少个像素的加权平均算出该像素的值(具体有若干种分化措施,可以参考)。

比如本条例子。

亚洲必赢官网 28

纹理对象和缓冲区目的很接近:使用 gl 的 API 函数创制,须要绑定至常量
gl.ARRAY_BUFFER 和 gl.TEXTURE_2D
,都因而常量对象向里面填入图像和多少。不相同的是,纹理对象在绑定时还亟需激活一个纹理单元(此处的gl.TEXTURE0),而
WebGL 系统援助的纹理单元个数是很有限的(一般为 8 个)。

var texture = gl.createTexture();
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, textureImage);
var sloc = gl.getUniformLocation(program, 'u_Sampler');
gl.uniform1i(sloc, 0);

片元着色器内注解了 sampler2D 类型的 uniform
变量,通过texture2D函数取样。

precision mediump float;
uniform sampler2D u_Sampler;
varying vec2 v_TexCoord;
void main() {
  gl_FragColor = texture2D(u_Sampler, v_TexCoord);
};

Canvas

OpenGLES2.0可渲染管道图:

掺杂与蒙版

晶莹剔透效果是用混合机制成功的。混合机制与深度检测类似,也时有暴发在准备向某个已填写的像素填充颜色时。深度检测通过比较z值来确定像素的水彩,而掺杂机制会将两种颜色混合。比如其一事例。

亚洲必赢官网 29

混合的种种是循途守辙绘制的逐条举办的,若是绘制的依次有变化,混合的结果平时也不比。固然模型既有非透明表面又有透明表面,绘制透明表面时打开蒙版,其目的是锁定深度缓冲区,因为半透明物体后边的物体仍然得以见见的,倘诺不这么做,半透明物体前面的物体将会被深度检测机制排除。

敞开混合的代码如下。gl.blendFunc办法指定了混合的点子,这里的意趣是,使用源(待混合)颜色的
α 值乘以源颜色,加上 1-[源颜色的 α]乘以目的颜色。

JavaScript

gl.enable(gl.BLEND); gl.blendFunc(gl.SRC_ALPHA,
gl.ONE_MINUS_SRC_ALPHA);

1
2
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);

所谓 α 值,就是颜色的第 4 个轻重。

JavaScript

var carray = new Float32Array([ 1,0,0,0.7,1,0,0,0.7,1,0,0,0.7,
0,0,1,0.4,0,0,1,0.4,0,0,1,0.4 ]);

1
2
3
4
var carray = new Float32Array([
  1,0,0,0.7,1,0,0,0.7,1,0,0,0.7,
  0,0,1,0.4,0,0,1,0.4,0,0,1,0.4
  ]);

掺杂与蒙版

晶莹剔透效果是用混合机制成功的。混合机制与深度检测类似,也发生在打算向某个已填写的像素填充颜色时。深度检测通过相比较z值来确定像素的颜色,而掺杂机制会将二种颜色混合。比如以此事例。

亚洲必赢官网 30

混合的次第是安分守己绘制的次第举办的,如若绘制的顺序有转变,混合的结果日常也不比。倘诺模型既有非透明表面又有透明表面,绘制透明表面时打开蒙版,其目的是锁定深度缓冲区,因为半透明物体后边的实体依然得以见见的,假如不那样做,半透明物体前面的物体将会被深度检测机制排除。

翻开混合的代码如下。gl.blendFunc办法指定了交集的法子,那里的趣味是,使用源(待混合)颜色的
α 值乘以源颜色,加上 1-[源颜色的 α]乘以目的颜色。

gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);

所谓 α 值,就是颜色的第 4 个轻重。

var carray = new Float32Array([
  1,0,0,0.7,1,0,0,0.7,1,0,0,0.7,
  0,0,1,0.4,0,0,1,0.4,0,0,1,0.4
  ]);

深谙 Canvas 的校友都通晓,Canvas 绘图先要获取绘图上下文:

亚洲必赢官网 31

浏览器的WebGL系统

WebGL 系统依次组成部分在既定规则下相互协作。稍作梳理如下。

亚洲必赢官网 32

那张图相比自由,箭头上的文字表示
API,箭头方向大概表现了数码的流淌方向,不必深究。

浏览器的WebGL系统

WebGL 系统依次组成部分在既定规则下互相合作。稍作梳理如下。

亚洲必赢官网 33

这张图比较轻易,箭头上的文字表示
API,箭头方向大致表现了数量的流淌方向,不必深究。

var context = canvas.getContext(‘2d’);

VertexShader:顶点着色器

光照

WebGL 没有为光照提供任何内置的办法,须要开发者在着色器中贯彻光照算法。

只不过有颜色的,模型也是有颜色的。在光照下,最终物体突显的颜料是两者一起成效的结果。

贯彻光照的办法是:将光照的多寡(点光源的岗位,平行光的动向,以及光的颜色和强度)作为
uniform 变量传入着色器中,将物体表面每个顶点处的法线作为 attribute
变量传入着色器,听从光照规则,修订最后片元突显的颜色。

光照又分为逐顶点的和逐片元的,两者的界别是,将法线光线交角因素位居顶点着色器中考虑或者放在片元着色器中考虑。逐片元光照更是有声有色,一个极其的事例是:

亚洲必赢官网 34

那会儿,点光源在离开一个表面较近处,表面主题 A
处较亮,四周较暗。然而在逐顶点光照下,表面的颜色(的震慑因子)是由顶点内插出来的,所以表面要旨也会相比暗。而逐片元光照直接行使片元的职位和法线计算与点光源的交角,由此表面中心会相比亮。

光照

WebGL 没有为光照提供任何内置的不二法门,须求开发者在着色器中贯彻光照算法。

只但是有颜色的,模型也是有颜色的。在光照下,最后物体突显的颜料是相互一起功能的结果。

兑现光照的不二法门是:将光照的数码(点光源的地方,平行光的取向,以及光的颜色和强度)作为
uniform 变量传入着色器中,将物体表面每个顶点处的法线作为 attribute
变量传入着色器,遵从光照规则,修订最后片元显示的颜色。

光照又分为逐顶点的和逐片元的,两者的分别是,将法线光线交角因素位居顶点着色器中考虑依旧放在片元着色器中考虑。逐片元光照更是有声有色,一个不过的事例是:

亚洲必赢官网 35

此刻,点光源在相距一个外表较近处,表面中心 A
处较亮,四周较暗。不过在逐顶点光照下,表面的颜料(的熏陶因子)是由顶点内插出来的,所以表面中心也会相比暗。而逐片元光照直接运用片元的岗位和法线总括与点光源的交角,因而表面大旨会相比亮。

在context上调用各个函数绘制图形,比如:

终端着色器输入包涵:

复杂模型

复杂模型可能有囊括子模型,子模型可能与父模型有相对运动。比如开着雨刮器的小车,雨刮器的世界坐标是受父模型汽车,和本身的动静共同决定的。若要统计雨刮器某顶点的地点,需求用雨刮器相对小车的模型矩阵乘上小车的模型矩阵,再乘以顶点的局地坐标。

复杂模型可能有好多外表,可能每个表面使用的着色器就差异。寻常将模型拆解为组,使用相同着色器的表面为一组,先绘制同一组中的内容,然后切换着色器。每一回切换着色器都要重新将缓冲区中的数据分配给着色器中相应变量。

复杂模型

复杂模型可能有包蕴子模型,子模型可能与父模型有绝对运动。比如开着雨刮器的汽车,雨刮器的世界坐标是受父模型小车,和自我的事态共同决定的。若要计算雨刮器某顶点的义务,要求用雨刮器相对小车的模型矩阵乘上小车的模子矩阵,再乘以顶点的有些坐标。

复杂模型可能有不少外部,可能每个表面使用的着色器就差异。平常将模型拆解为组,使用同样着色器的外表为一组,先绘制同一组中的内容,然后切换着色器。每趟切换着色器都要重复将缓冲区中的数据分配给着色器中相应变量。

// 绘制左上角为(0,0),右下角为(50, 50)的矩形

着色器程序——描述顶点上实施操作的极端着色器程序源代码或者可执行文件

动画

动画片的规律就是很快地擦除和重绘。常用的法子是大名鼎鼎的
requestAnimationFrame
。不熟稔的校友,可以参考正美的介绍。

动画

卡通的法则就是快捷地擦除和重绘。常用的艺术是有名的
requestAnimationFrame
。不熟习的校友,可以参见正美的介绍。

context.fillRect(0, 0, 50, 50);

终点着色器输入(或性质)——用极端数组提供的每个终端的数码

WebGL库

此时此刻最盛行的 WebGL 库是
ThreeJS,很有力,官网,代码。

WebGL库

眼前最盛行的 WebGL 库是
ThreeJS,很强劲,官网,代码。

WebGL 同样须求取得绘图上下文:

联合变量(uniform)——顶点(或部分)着色器使用的不变多少

调节工具

正如早熟的 WebGL 调试工具是WebGL
Inspector。

调剂工具

相比较成熟的 WebGL 调试工具是WebGL
Inspector。

var gl = canvas.getContext(‘webgl’); // 或 experimental-webgl

采样器——代表顶点着色器使用纹理的 特殊统一变量类型

网络资源和本本

英文的有关 WebGL 的资源有许多,包含:

  • learning webgl
  • WebGL@MDN
  • WebGL Cheat
    Sheet

国内最早的 WebGL 教程是由郝稼力翻译的,放在 hiwebgl 上,目前 hiwebgl
已经关闭,但教程还是能在这里找到。郝稼力最近营业着Lao3D。

境内曾经出版的 WebGL 书籍有:

  • WebGL入门指南:其实是一本讲
    ThreeJS 的书
  • WebGL高级编程:还不错的一本
  • WebGL编程指南:极度可相信的周全教程

最终再混合一点私货吧。读书期间自己曾花了小3个月岁月翻译了一本WebGL的书,也就是地方的第
3
本。那本书真的十分可信赖,网上种种学科里很多没说通晓的东西,那本书说得很清楚,而且还提供了一份很完整的API文档。翻译那本书的进度也使我受益匪浅。即使有同学愿意系统学一下
WebGL
的,提议购买一本(文青提议买英文版)。

1 赞 2 收藏 1
评论

亚洲必赢官网 36

网络资源和书籍

英文的关于 WebGL 的资源有不少,包括:

  • learning webgl
  • WebGL@MDN
  • WebGL Cheat
    Sheet

境内最早的 WebGL 教程是由郝稼力翻译的,放在 hiwebgl 上,如今 hiwebgl
已经关闭,但教程还足以在这里找到。郝稼力近期运营着Lao3D。

国内已经出版的 WebGL 书籍有:

  • WebGL入门指南:其实是一本讲
    ThreeJS 的书
  • WebGL高级编程:还不错的一本
  • WebGL编程指南:相当可信赖的健全教程

而是接下去,要是想画一个矩形的话,就没那样简单了。实际上,Canvas
是浏览器封装好的一个绘制环境,在其实进行绘图操作时,浏览器仍然必要调用
OpenGL API。而 WebGL API 大约就是 OpenGL API 未经封装,直接套了一层壳。

顶点着色器的输出在OpenGLES2.0称作可变变量(varying),但在OpenGLES3.0中改名为巅峰着色器输出变量。

Canvas 的越多学问,可以参考:

在光栅化阶段,为每个生成的部分总括顶点着色器输出值,并作为输入传递给部分着色器。

JS
权威指南的
21.4 节或JS
高级程序设计中的
15 章

插值:光栅器对从终端着色器传递的变量举行插值

W3CSchool

为了在显示屏上的确显示,必须将顶点着色器vs的出口变量设置为gl_Position,gl_Position是一个保留着顶点齐次坐标的4维向量。ZYZ分量被W分量分割(称作视角分割)并且XYZ分量上跨越单位化盒子([-1,
1])的有些会被裁剪掉。最后的结果会被撤换来屏幕坐标系然后三角形(或任何图元类型)被光栅器生成对应的像素。

阮一峰的 Canvas
教程

OpenGLES3.0新增了一个功力——变换反馈,使顶点着色器输出可以接纳性地写入一个输出缓冲区(除了传递给一部分着色器之外,也可用那种传递替代)

矩阵变换

极端着色器的输入和输出如下图所示:

三维模型,从文件中读出来,到绘制在 Canvas 中,经历了往往坐标变换。

亚洲必赢官网 37

一旦有一个最简便易行的模型:三角形,三个终端分别为(-1,-1,0),(1,-1,0),(0,1,0)。那多个数据是从文件中读出来的,是三角形最早先的坐标(局地坐标)。如下图所示,右手坐标系。

先看看剧本:

亚洲必赢官网 38

private final String mVertexShader =

模型常常不会放在场景的原点,假诺三角形的原点位于(0,0,-1)处,没有转动或缩放,五个极点分别为(-1,-1,-1),(1,-1,-1),(0,1,-1),即世界坐标。

“uniform mat4 uMVPMatrix;\n” +

亚洲必赢官网 39

“attribute vec4 aPosition;\n” +

制图三维场景必须指定一个观察者,假使观望者位于(0,0,1)处而且看向三角形,那么多个极点相对于观看者的坐标为(-1,-1,-2),(1,-1,-2),(0,1,-2),即视图坐标。

“attribute vec4 a_color;\n” +

亚洲必赢官网 40

“attribute vec2 aTextureCoord;\n” +

阅览者的眸子是一个点(那是看破投影的前提),水平视角和垂直视角都是90度,视野范围(目力所及)为[0,2]在Z轴上,寓目者可以看出的区域是一个四棱台体。

“varying vec2 vTextureCoord;\n” +

亚洲必赢官网 41

“out vec4 v_color;\n”

将四棱台体映射为业内立方(CCV,大旨为原点,边长为2,边与坐标轴平行)。顶点在
CCV 中的坐标,离它说到底在 Canvas 中的坐标已经很类似了,如若把 CCV
的前表面看成 Canvas,那么最后三角形就画在图中藏蓝色三角形的职位。

“void main() {\n” +

亚洲必赢官网 42

” gl_Position = uMVPMatrix * aPosition;\n” +

上述变换是用矩阵来开展的。

” vTextureCoord = aTextureCoord;\n” +

局部坐标 –(模型变换)-> 世界坐标 –(视图变换)-> 视图坐标
–(投影变换)–> CCV 坐标。

“ v_color = a_color;\n”

以(0,1,0)为例,它的齐次向量为(0,0,1,1),上述变换的代表经过可以是:

“}\n”;

亚洲必赢官网 43

private final String mFragmentShader =

地点两个矩阵依次是看破投影矩阵,视图矩阵,模型矩阵。四个矩阵的值分别取决于:观看者的观点和视野距离,观看者在世界中的状态(地方和取向),模型在世界中的状态(地点和自由化)。总括的结果是(0,1,1,2),化成齐次坐标是(0,0.5,0.5,1),就是其一点在CCV中的坐标,那么(0,0.5)就是在Canvas中的坐标(认为
Canvas 中央为原点,长宽都为2)。

“precision mediump float;\n” +

地点出现的(0,0,1,1)是(0,0,1)的齐次向量。齐次向量(x,y,z,w)可以代表三维向量(x,y,z)加入矩阵运算,通俗地说,w
分量为 1 时表示地方,w 分量为 0 时表示位移。

“varying vec2 vTextureCoord;\n” +

WebGL 没有提供任何关于上述变换的体制,开发者要求亲自统计顶点的 CCV
坐标。

“uniform sampler2D sTexture;\n” +

至于坐标变换的越来越多内容,可以参见:

“void main() {\n” +

电脑图形学中的5-7章

“gl_FragColor = texture2D(sTexture, vTextureCoord);\n” +

转换矩阵@维基百科

“}\n”;

透视投影详解

内部脚本语句关键字:

比较复杂的是模型变换中的绕任意轴旋转(平常用四元数生成矩阵)和投影变换(上面的事例都没收涉及到)。

attribute:使用终端数组封装每个终端的数据,一般用于每个终端都各不一样的变量,如顶点地点、颜色等

有关绕任意轴旋转和四元数,可以参考:

uniform:顶点着色器使用的常量数据,不可以被着色器修改,一般用来对相同组顶点组成的单个3D物体中有所终端都有同样的变量,如当前光源地方

四元数@维基百科

sampler:那是可选的,一种新鲜的uniform,表示顶点着色器使用的纹理

一个老外对四元数公式的验证

mat4:表示4×4浮点数矩阵,该变量存储了整合模型视图和投影矩阵

有关齐次向量的更加多内容,可以参考。

vec4:表示包蕴了4个浮点数的向量

微机图形学的5.2节

varying:用于从终端着色器传递到片元或FragmentsShader传递到下一步的出口变量

齐次坐标@维基百科

uMVPMatrix * aPosition:通过4×4
的变换地点后,输出给gl_Position,gl_Position是极限着色器内置的出口变量。

着色器和光栅化

gl_FragColor:片元着色器内置的输出变量

在 WebGL
中,开发者是经过着色器来形成上述变换的。着色器是运行在显卡中的程序,以
GLSL 语言编写,开发者须求将着色器的源码以字符串的款型传给 WebGL
上下文的相关函数。

PrimitiveAssembly:图元装配

着色器有二种,顶点着色器和片元(像素)着色器,它们成对出现。顶点着色器任务是接到顶点的局部坐标,输出
CCV 坐标。CCV
坐标经过光栅化,转化为逐像素的数目,传给片元着色器。片元着色器的职务是确定每个片元的水彩。

图元即图形,在OpenGL有多少个基本图元:点、线、三角形,其余的扑朔迷离图元都是按照那几个基本图元来绘制而成。

极端着色器接收的是 attribute 变量,是逐顶点的多寡。顶点着色器输出
varying 变量,也是逐顶点的。逐顶点的 varying
变量数据通过光栅化,成为逐片元的 varying
变量数据,输入片元着色器,片元着色器输出的结果就会来得在 Canvas 上。

在图元装配阶段,这些通过顶点着色器(VertexShader)处理过的巅峰数组或缓冲区的数据(VertexArrays/BufferObjects),被组装到一个个单身的几何图形中(点,线,三角形)

亚洲必赢官网 44

对装配好的没个图元,都不可以不确保它在世界坐标系中,而对于不在世界坐标系中的图元,就亟须开展裁剪,使其处于在世界坐标系中才能流到下一道工序(光栅化处理)

着色器作用很多,上述只是基本功效。大部分炫酷的效益都是看重着色器的。若是您对着色器完全没有定义,可以试着明亮下一节
hello world 程序中的着色器再回顾一下本节。

那边还有一个刨除操作(Cull),前提是其一效应的开关是打开的:GLES20.glEnable(GLES20.GL_CULL_FACE);
剔除的是图元的背影,阴影,背面等。

有关越多着色器的学问,可以参照:

Rasterization:光栅化

GLSL@维基百科

光栅化阶段绘制对应的图元(点、线、三角形),将图元转化为一组二维数组的长河,然后传递给一部分着色器处理。这么些二维数组代表屏幕上制图的像素

WebGL@MSDN

亚洲必赢官网 45

程序

(PS:上图中的点天使光栅化应该是点光栅化)

这一节解释绘制上述场景(三角形)的 WebGL
程序。点本条链接,查看源代码,试图精晓一下。那段代码出自WebGL
Programming
Guide,我作了有的修改以适应本文内容。如若一切正常,你看到的相应是下面那样:

FragmentShader:片元着色器

亚洲必赢官网 46

片元着色器首若是对光栅化处理后转移的片元逐个进行处理。接收顶点着色器输出的值,需求传入的数额,以及它经过变换矩阵后输出值存储地点。

分解几点(假若从前不打听 WebGL ,多半会对上面的代码猜忌,无碍):

着色器程序——描述片元所举行的片元着色器程序源代码

字符串 VSHADER_SOURCE 和 FSHADER_SOURCE
是极端着色器和片元着色器的源码。可以将着色器了然为有稳定输入和输出格式的先后。开发者须要事先编写好着色器,再根据一定格式着色器发送绘图命令。

输入变量——光栅器对终端着色器插值后的输出值

Part2 将着色器源码编译为 program
对象:先分别编译顶点着色器和片元着色器,然后连接两者。若是编译源码错误,不会报
JS 错误,但足以因而任何
API(如gl.getShaderInfo等)获取编译状态音讯(成功与否,如若出错的错误新闻)。

合并变量——片元(或极端)着色器使用的不变的多少

// 顶点着色器

采样器——代表片元着色器所用纹理的一种奇特的统一变量类型

var vshader = gl.createShader(gl.VERTEX_SHADER);

片元着色器输入和出口关系如下图所示:

gl.shaderSource(vshader, VSHADER_SOURCE);

亚洲必赢官网 47

gl.compileShader(vshader);

因为光栅化处理后,图元只是在显示器上有了像素,却绝非展开颜色处理,依旧看不到事物。

// 同样新建 fshader

为此FragmentsShader首要的功用是告诉GPU怎么样处哈苏照、阴影、遮挡、环境等,然后将结果输出到gl_FragColor变量中

var program = gl.createProgram();

FragmentsShader只输出一个颜色值——gl_FragColor,是片元着色器内置的输出变量

gl.attachShader(program, vshader);

片元着色器脚本示例:

gl.attachShader(program, fshader);

#version 300 es

gl.linkProgram(program);

precision mediump float; // 设置精度限定符

program
对象须求指定使用它,才得以向着色器传数据并绘制。复杂的程序寻常有三个program 对 象,(绘制每一帧时)通过切换 program 对象绘制场景中的分裂作用。

in vec4 v_color;

gl.useProgram(program);

out vec4 fragColor;

Part3 向正在选择的着色器传入数据,包括逐顶点的 attribute 变量和大局的
uniform 变量。向着色器传入数据必须利用 ArrayBuffer,而不是正规的 JS
数组。

void main()

var varray = new Float32Array([-1, -1, 0, 1, -1, 0, 0, 1, 0])

{

WebGL API 对 ArrayBuffer 的操作(填充缓冲区,传入着色器,绘制等)都是因此gl.ARRAY_BUFFER 举行的。在 WebGL 系统中又很多类似的气象。

fragColor = v_color;

// 只有将 vbuffer 绑定到 gl.ARRAY_BUFFER,才方可填充数据

gl_FragColor = fragColor;

gl.bindBuffer(gl.ARRAY_BUFFER, vbuffer);

}

// 那里的意趣是,向“绑定到 gl.ARRAY_BUFFER”的缓冲区中填充数据

光栅化阶段生成的水彩、深度、模板和显示屏坐标地点(Xw,
Yw)将会成为逐片元操作阶段的输入值

gl.bufferData(gl.ARRAY_BUFFER, varray, gl.STATIC_DRAW);

Pre-Fragment Operations:逐片元操作阶段

// 获取 a_Position 变量在着色器程序中的地方,参考顶点着色器源码

在片元着色器对片元进行汇总的处理,并最终为片元生成一个颜料值,并储存在gl_FragColor变量后,接下去就是逐个对片元进行部分列的测试。

var aloc = gl.getAttribLocation(program, ‘a_Position’);

光栅化处理时,它由于时把顶点从社会风气坐标系转换来显示屏坐标系,因而在光栅处理后,每个片元在显示屏上都有个坐标(Xw,
Yw)。且存储在了帧缓冲区(FrameBuffer),

// 将 gl.ARRAY_BUFFER 中的数据传入 aloc 表示的变量,即 a_Position

概括片元着色器也是对(Xw, Yw)那几个坐标的片元进行拍卖

gl.vertexAttribPointer(aloc, 3, gl.FLOAT, false, 0, 0);

亚洲必赢官网 48

gl.enableVertexAttribArray(aloc);

Pixel ownership test——像素归属测试,它控制FrameBuffer中某个(Xw,
Yw)地点的像素是不是属于当前Context

向着色器传入矩阵时,是按列存储的。可以比较一下 mmatrix
和矩阵变换一节中的模型矩阵(第 3 个)。

Scissor test——裁剪测试,决定一个职位(Xw,
Yw)的片元是或不是位于裁剪举行内,如若不在,则被撇下

极限着色器统计出的 gl_Position 就是 CCV
中的坐标,比如最上边的终端(黑色)的 gl_Position
化成齐次坐标就是(0,0.5,0.5,1)。

Stencil test/Depth
test——模版和深度测试,传入片元的沙盘和纵深值,决定是或不是放任片

向终极着色器传入的只是八个极点的颜料值,而三角形表面的颜色渐变是由那多少个颜色值内插出的。光栅化不仅会对
gl_Position 举行,还会对 varying 变量插值。

Blending——混合,将FragmentShader
新暴发的片元颜色值和FrameBuffer中某个地方(Xw,
Yw)的片元存储的颜色值实行混合

gl.drawArrays()方法使得缓冲区进行绘图,gl.TRIANGLES
指定绘制三角形,也得以变动参数绘制点、折线等等。

Dithering——抖动,对可用颜色较少的体系,能够就义分辨率为代价,通过颜色值的振动来充实可用颜色值。抖动操作和硬件相关,OpenGL允许程序员所有的操作就唯有打开或关闭都懂操作。默许情状下震动是激活的

有关 ArrayBuffer 的详细新闻,可以参照:

在逐片元操作阶段的最后,片元要么被放任,要么将颜色、深度、模板值写入到帧缓冲区(Xw,
Yw)地方,写入的值取决于启用的写入掩码

ArrayBuffer@MDN

写入掩码可以更精致地控制写入的颜色、深度、模板值。

阮一峰的 ArrayBuffer
介绍

备考:Alpha测试和逻辑操作不再是逐片元操作的一片段,这七个阶段存在于OpenGL2.0盒OpenGL1.x中。

张鑫旭的 ArrayBuffer
介绍

至于 gl.TRIANGLES
等任何绘制格局,可以参见上边那张图或那篇博文。

亚洲必赢官网 49

纵深检测

当五个外表重叠时,前边的模型会遮掩前面的模型。比如以此事例,绘制了三个交叉的三角(
varray 和 carray 的长短变为 18,gl.drawArrays 最终一个参数变为
6)。为了不难,这几个例子去掉了矩阵变换进程,直接向着色器传入 CCV 坐标。

亚洲必赢官网 50

亚洲必赢官网 51

终点着色器给出了 6 个终端的 gl_Position ,经过光栅化,片元着色器得到了
2X 个片元(如若 X 为每个三角形的像素个数),每个片元都离散的 x,y
坐标值,还有 z 值。x,y 坐标就是三角形在 Canvas
上的坐标,但只要有五个拥有相同 x,y 坐标的片元同时出现,那么 WebGL
就会取 z 坐标值较小的那个片元。

在深度检测往日,必须在绘制前拉开一个常量。否则,WebGL 就会依据在 varray
中定义的次第绘制了,后边的会覆盖前边的。

gl.enable(gl.DEPTH_TEST);

其实,WebGL 的逻辑是这么的:依次拍卖片元,假若渲染缓冲区(这里就是
Canvas
了)的要命与当前片元对应的像素还从未绘制时,就把片元的颜色画到渲染缓冲区对应像素里,同时把片元的
z
值缓存在另一个纵深缓冲区的等同地方;若是当前缓冲区的呼应像素已经绘制过了,就去查看深度缓冲区中对应地点的
z 值,要是当前片元 z 值小,就重绘,否则就遗弃当前片元。

WebGL 的那套逻辑,对精通蒙版(前面会说到)有部分增援。

顶点索引

gl.drawArrays()是安分守纪顶点的顺序绘制的,而
gl.drawElements()可以令着色器以一个索引数组为顺序绘制顶点。比如其一例子。

亚洲必赢官网 52

这里画了七个三角形,但只用了 5
个极点,有一个极端被四个三角共用。那时要求建立索引数组,数组的各种元素表示顶点的索引值。将数组填充至gl.ELEMENT_ARRAY,然后调用
gl.drawElements()。

var iarray = new Uint8Array([0,1,2,2,3,4]);

var ibuffer = gl.createBuffer(gl.ARRAY_BUFFER, ibuffer);

gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibuffer);

gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, iarray, gl.STATIC_DRAW);

纹理

attribute
变量不仅能够传递顶点的坐标,还足以传递其余任何逐顶点的数据。比如
HelloTriangle 程序把单个顶点的颜色传入了 a_Color,片元着色器收到
v_Color 后一向赋给 gl_FragmentColor,就控制了颜色。

attribute
变量仍可以协理绘制纹理。绘制纹理的基本原理是,为每个终端指定一个纹理坐标(在(0,0)与(1,1,)的四方形中),然后传入纹理对象。片元着色器得到的是对应片元的内插后的纹理坐标,就拔取那几个纹理坐标去纹理对象上取颜色,再画到片元上。内插后的纹理坐标很可能不正好对应纹理上的某个像素,而是在多少个像素之间(因为一般而言的图纸纹理也是离散),那时可能会通过周围多少个像素的加权平均算出该像素的值(具体有好多种不一样形式,可以参考)。

比如以此事例。

亚洲必赢官网 53

纹理对象和缓冲区目的很类似:使用 gl 的 API 函数创立,必要绑定至常量
gl.ARRAY_BUFFER 和 gl.TEXTURE_2D
,都经过常量对象向其中填入图像和数码。分歧的是,纹理对象在绑定时还必要激活一个纹理单元(此处的gl.TEXTURE0),而
WebGL 系统辅助的纹理单元个数是很单薄的(一般为 8 个)。

var texture = gl.createTexture();

gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);

gl.activeTexture(gl.TEXTURE0);

gl.bindTexture(gl.TEXTURE_2D, texture);

gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);

gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE,
textureImage);

var sloc = gl.getUniformLocation(program, ‘u_Sampler’);

gl.uniform1i(sloc, 0);

片元着色器内注明了 sampler2D 类型的 uniform
变量,通过texture2D函数取样。

precision mediump float;

uniform sampler2D u_Sampler;

varying vec2 v_TexCoord;

void main() {

  gl_FragColor = texture2D(u_Sampler, v_TexCoord);

};

混合与蒙版

透明效果是用混合机制形成的。混合机制与深度检测类似,也发生在打算向某个已填写的像素填充颜色时。深度检测通过相比z值来规定像素的水彩,而掺杂机制会将两种颜色混合。比如其一例子。

亚洲必赢官网 54

错落的一一是按照绘制的各类举行的,尽管绘制的逐条有生成,混合的结果平常也不比。若是模型既有非透明表面又有晶莹剔透表面,绘制透明表面时打开蒙版,其目标是锁定深度缓冲区,因为半晶莹剔透物体后边的实体依旧得以看出的,假使不那样做,半透明物体前边的实体将会被深度检测机制排除。

敞开混合的代码如下。gl.blendFunc方法指定了混合的法子,那里的意思是,使用源(待混合)颜色的
α 值乘以源颜色,加上 1-[源颜色的 α]乘以目标颜色。

gl.enable(gl.BLEND);

gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);

所谓 α 值,就是颜色的第 4 个轻重。

var carray = new Float32Array([

  1,0,0,0.7,1,0,0,0.7,1,0,0,0.7,

  0,0,1,0.4,0,0,1,0.4,0,0,1,0.4

  ]);

浏览器的WebGL系统

WebGL 系统依次组成部分在既定规则下相互协作。稍作梳理如下。

亚洲必赢官网 55

那张图比较随意,箭头上的文字表示
API,箭头方向大致表现了多少的流动方向,不必深究。

光照

WebGL 没有为光照提供其他内置的不二法门,要求开发者在着色器中贯彻光照算法。

只不过有颜色的,模型也是有颜色的。在光照下,最终物体突显的水彩是双方一起成效的结果。

贯彻光照的艺术是:将光照的多寡(点光源的地方,平行光的大方向,以及光的颜料和强度)作为
uniform 变量传入着色器中,将物体表面每个顶点处的法线作为 attribute
变量传入着色器,服从光照规则,修订最后片元展现的颜料。

光照又分为逐顶点的和逐片元的,两者的分别是,将法线光线交角因素位居顶点着色器中考虑或者放在片元着色器中考虑。逐片元光照更是栩栩欲活,一个然而的事例是:

亚洲必赢官网 56

那儿,点光源在离开一个外部较近处,表面焦点 A
处较亮,四周较暗。不过在逐顶点光照下,表面的颜色(的熏陶因子)是由顶点内插出来的,所以表面大旨也会相比较暗。而逐片元光照直接运用片元的职位和法线统计与点光源的交角,由此表面中心会相比亮。

复杂模型

复杂模型可能有包含子模型,子模型可能与父模型有相对运动。比如开着雨刮器的汽车,雨刮器的世界坐标是受父模型小车,和本身的动静共同决定的。若要总结雨刮器某顶点的职分,必要用雨刮器相对小车的模型矩阵乘上小车的模型矩阵,再乘以顶点的一部分坐标。

复杂模型可能有很多外表,可能每个表面使用的着色器就不同。经常将模型拆解为组,使用相同着色器的表面为一组,先绘制同一组中的内容,然后切换着色器。每一趟切换着色器都要重复将缓冲区中的数据分配给着色器中相应变量。

动画

动画片的规律就是急迅地擦除和重绘。常用的主意是赫赫有名的
requestAnimationFrame
。不熟识的同学,可以参考正美的介绍。

WebGL库

近日最流行的 WebGL 库是
ThreeJS,很强大,官网,代码。

调剂工具

正如早熟的 WebGL 调试工具是WebGL
Inspector。

网络资源和书本

亚洲必赢官网 ,英文的有关 WebGL 的资源有广大,包涵:

learning
webgl

WebGL@MDN

WebGL Cheat
Sheet

国内最早的 WebGL 教程是由郝稼力翻译的,放在 hiwebgl 上,近年来 hiwebgl
已经关门,但教程还是可以在这里找到。郝稼力如今营业着Lao3D。

国内已经出版的 WebGL 书籍有:

WebGL入门指南:其实是一本讲
ThreeJS 的书

WebGL高级编程:还不易的一本

WebGL编程指南:至极可信赖的一揽子教程

网站地图xml地图