WebGL技术储备指南,技术储备指南

by admin on 2019年2月12日

WebGL技术储备指南

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

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

必发88 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 节或 WebGL技术储备指南,技术储备指南。JS
    高级程序设计中的
    15 章
  • W3CSchool
  • 阮一峰的 Canvas
    教程

招来奥兹国

必发88 2

矩阵变换

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

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

必发88 3

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

必发88 4

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

必发88 5

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

必发88 6

将四棱台体映射为规范立方(CCV,中央为原点,边长为2,边与坐标轴平行)。顶点在
CCV 中的坐标,离它最后在 Canvas 中的坐标已经很类似了,假诺把 CCV
的前表面看成 Canvas,那么最后三角形就画在图中紫色三角形的岗位。

必发88 7

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

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

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

必发88 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)。这五个数据是从文件中读出来的,是三角形最开首的坐标(局地坐标)。如下图所示,右手坐标系。

必发88 9

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

必发88 10

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

必发88 11

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

必发88 12

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

必发88 13

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

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

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

必发88 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 上。

必发88 15

着色器功用很多,上述只是基本效用。半数以上炫酷的成效都是借助着色器的。倘使你对着色器完全没有概念,可以试着明亮下一节
hello world 程序中的着色器再回首一下本节。

有关越来越多着色器的知识,能够参照:

  • GLSL@维基百科
  • WebGL@MSDN

着色器和光栅化

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

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

终极着色器接收的是 attribute 变量,是逐顶点的数额。顶点着色器输出
varying 变量,也是逐顶点的。逐顶点的 varying
变量数据经过光栅化,成为逐片元的 varying
变量数据,输入片元着色器,片元着色器输出的结果就会浮以往 Canvas 上。

必发88 16

着色器功效很多,上述只是基本效能。一大半炫酷的出力都以依靠着色器的。倘若您对着色器完全没有概念,可以试着明亮下一节
hello world 程序中的着色器再回顾一下本节。

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

  • GLSL@维基百科
  • WebGL@MSDN

泛舟的男孩(Goo
EngineDemo)

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

程序

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

必发88 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. WebGL技术储备指南,技术储备指南。向着色器传入矩阵时,是按列存储的。可以相比较一下 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
等任何绘制格局,可以参照上面那张图或那篇博文。

必发88 18

程序

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

必发88 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
等其他绘制形式,可以参照下边那张图或那篇博文。

必发88 20

本文的目标

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

纵深检测

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

必发88 21

必发88 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 坐标。

必发88 23

必发88 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()可以令着色器以一个索引数组为顺序绘制顶点。比如那么些事例。

必发88 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()可以令着色器以一个索引数组为顺序绘制顶点。比如那几个事例。

必发88 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,)的方框形中),然后传入纹理对象。片元着色器得到的是对应片元的内插后的纹理坐标,就应用这些纹理坐标去纹理对象上取颜色,再画到片元上。内插后的纹路坐标很只怕不正好对应纹理上的某部像素,而是在多少个像素之间(因为日常的图纸纹理也是离散),那时只怕会通过周围多少个像素的加权平均算出该像素的值(具体有多少种不相同形式,可以参考)。

比如其一事例。

必发88 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,)的方框形中),然后传入纹理对象。片元着色器拿到的是对应片元的内插后的纹路坐标,就动用那么些纹理坐标去纹理对象上取颜色,再画到片元上。内插后的纹路坐标很或者不凑巧对应纹理上的某部像素,而是在多少个像素之间(因为寻常的图片纹理也是离散),那时或者会由此周围多少个像素的加权平均算出该像素的值(具体有多少种不一样方法,可以参见)。

比如以此事例。

必发88 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值来规定像素的颜色,而掺杂机制会将几种颜色混合。比如那个事例。

必发88 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值来确定像素的颜色,而掺杂机制会将三种颜色混合。比如其一例子。

必发88 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 绘图先要获取绘图上下文:

必发88 31

浏览器的WebGL系统

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

必发88 32

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

浏览器的WebGL系统

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

必发88 33

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

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

VertexShader:顶点着色器

光照

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

只可是有颜色的,模型也是有颜色的。在光照下,最终物体显示的颜料是双边一起功能的结果。

已毕光照的章程是:将光照的数量(点光源的职位,平行光的大势,以及光的颜色和强度)作为
uniform 变量传入着色器中,将物体表面每种顶点处的法线作为 attribute
变量传入着色器,遵守光照规则,修订最后片元突显的颜色。

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

必发88 34

那时,点光源在距离一个外表较近处,表面宗旨 A
处较亮,四周较暗。然则在逐顶点光照下,表面的颜料(的影响因子)是由顶点内插出来的,所以表面中心也会相比较暗。而逐片元光照直接使用片元的职位和法线计算与点光源的交角,因而表面中心会比较亮。

光照

WebGL 没有为光照提供任何内置的点子,需要开发者在着色器中落成光照算法。

只然而有颜色的,模型也是有颜色的。在光照下,最后物体彰显的颜色是双方联手成效的结果。

达成光照的点子是:将光照的多寡(点光源的地点,平行光的倾向,以及光的颜料和强度)作为
uniform 变量传入着色器中,将物体表面各种顶点处的法线作为 attribute
变量传入着色器,遵守光照规则,修订最后片元突显的颜料。

光照又分为逐顶点的和逐片元的,两者的分别是,将法线光线交角因素位居顶点着色器中考虑可能放在片元着色器中考虑。逐片元光照更是活龙活现,一个相当的例证是:

必发88 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编程指南:卓绝可相信的周密教程

最后再混合一点私货吧。读书时期我曾花了小六个月时间翻译了一本WebGL的书,相当于地点的第
3
本。那本书真的非凡可信,网上各样课程里很多没说了然的东西,那本书说得很明白,而且还提供了一份很完整的API文档。翻译那本书的历程也使本人收益匪浅。若是有同学愿意系统学一下
WebGL
的,指出购买一本(文青指出买英文版)。

1 赞 2 收藏 1
评论

必发88 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 中,经历了频仍坐标变换。

必发88 37

如若有一个最简单易行的模子:三角形,三个顶峰分别为(-1,-1,0),(1,-1,0),(0,1,0)。这一个数据是从文件中读出来的,是三角形最早先的坐标(局部坐标)。如下图所示,右手坐标系。

先看看剧本:

必发88 38

private final String mVertexShader =

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

“uniform mat4 uMVPMatrix;\n” +

必发88 39

“attribute vec4 aPosition;\n” +

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

“attribute vec4 a_color;\n” +

必发88 40

“attribute vec2 aTextureCoord;\n” +

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

“varying vec2 vTextureCoord;\n” +

必发88 41

“out vec4 v_color;\n”

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

“void main() {\n” +

必发88 42

” gl_Position = uMVPMatrix * aPosition;\n” +

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

” vTextureCoord = aTextureCoord;\n” +

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

“ v_color = a_color;\n”

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

“}\n”;

必发88 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),被组装到一个个独自的几何图形中(点,线,三角形)

必发88 44

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

着色器功效很多,上述只是基本功效。大部分炫酷的意义都以借助着色器的。即便你对着色器完全没有概念,可以试着明亮下一节
hello world 程序中的着色器再回首一下本节。

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

有关越来越多着色器的知识,可以参考:

Rasterization:光栅化

GLSL@维基百科

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

WebGL@MSDN

必发88 45

程序

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

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

FragmentShader:片元着色器

必发88 46

片元着色重视假诺对光栅化处理后变更的片元每一个举行拍卖。接收顶点着色器输出的值,须要传入的多少,以及它通过变换矩阵后输出值存储地点。

演讲几点(若是从前不打听 WebGL ,多半会对下边的代码怀疑,无碍):

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

字符串 VSHADER_SOURCE 和 FSHADER_SOURCE
是终极着色器和片元着色器的源码。能够将着色器了然为有定点输入和输出格式的次序。开发者必要事先编写好着色器,再依照一定格式着色器发送绘图命令。

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

Part2 将着色器源码编译为 program
对象:先分别编译顶点着色器和片元着色器,然后连接两者。即便编译源码错误,不会报
JS 错误,但可以透过另外API(如gl.getShaderInfo等)获取编译状态音信(成功与否,尽管出错的错误信息)。

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

// 顶点着色器

采样器——代表片元着色器所用纹理的一种非凡的联结变量类型

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

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

gl.shaderSource(vshader, VSHADER_SOURCE);

必发88 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);

必发88 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
等其余绘制格局,可以参考上面那张图或那篇博文。

必发88 49

纵深检测

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

必发88 50

必发88 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()可以令着色器以一个索引数组为顺序绘制顶点。比如其一例子。

必发88 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,)的方框形中),然后传入纹理对象。片元着色器得到的是对应片元的内插后的纹理坐标,就利用那一个纹理坐标去纹理对象上取颜色,再画到片元上。内插后的纹路坐标很只怕不正好对应纹理上的某部像素,而是在多少个像素之间(因为寻常的图纸纹理也是离散),那时只怕会通过周围多少个像素的加权平均算出该像素的值(具体有多少种差别措施,可以参照)。

比如以此事例。

必发88 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);

};

必发88,掺杂与蒙版

透明效果是用混合机制落成的。混合机制与深度检测类似,也暴发在试图向某个已填写的像素填充颜色时。深度检测通过相比较z值来规定像素的颜色,而掺杂机制会将二种颜色混合。比如本条例子。

必发88 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 系统依次组成部分在既定规则下互相合营。稍作梳理如下。

必发88 55

那张图相比随便,箭头上的文字表示
API,箭头方向大概表现了数额的流动方向,不必深究。

光照

WebGL 没有为光照提供任何内置的点子,须求开发者在着色器中落到实处光照算法。

只可是有颜色的,模型也是有颜色的。在光照下,最后物体显示的颜色是双边联手成效的结果。

兑现光照的办法是:将光照的数量(点光源的义务,平行光的来头,以及光的水彩和强度)作为
uniform 变量传入着色器中,将物体表面每一个顶点处的法线作为 attribute
变量传入着色器,听从光照规则,修订最后片元突显的水彩。

光照又分为逐顶点的和逐片元的,两者的区分是,将法线光线交角因素位居顶点着色器中考虑依旧放在片元着色器中考虑。逐片元光照更是绘影绘声,一个极端的例子是:

必发88 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地图