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

WebGL技术储备指南

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

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

澳门新莆京娱乐 1

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

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

技术储备指南,WebGL技术储备指南。一、前言

一、前言

示例

WebGL 很酷,有以下 demos 为证:

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

示例

      今日持续第陆章的求学内容,开头攻读复合变换的文化。

      算是到了第4章了,貌似发轫越来越复杂了。

正文的指标

正文的意料读者是:面生图形学,熟谙前端,希望领会或连串学习 WebGL
的同桌。

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

WebGL 很酷,有以下 demos 为证:

 

 

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

不过接下去,即使想画3个矩形的话,就没那样简单了。实际上,Canvas
是浏览器封装好的三个绘制环境,在实质上进行绘图操作时,浏览器依旧须求调用
OpenGL API。而 WebGL API 大致正是 OpenGL API 未经封装,直接套了一层壳。

Canvas 的越多学问,能够参照:

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

探寻奥兹国

二、正文

二、正文

矩阵变换

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

要是有一个最简便的模子:三角形,两极分化分别为(-1,-1,0),(1,-1,0),(0,1,0)。那八个数据是从文件中读出来的,是三角形最伊始的坐标(局地坐标)。如下图所示,右手坐标系。

澳门新莆京娱乐 2

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

澳门新莆京娱乐 3

绘图三维场景必须钦点几个观望者,假诺观望者位于(0,0,1)处而且看向三角形,那么四个极端相对于阅览者的坐标为(-1,-1,-2),(1,-1,-2),(0,1,-2),即视图坐标。

澳门新莆京娱乐 4

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

澳门新莆京娱乐 5

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

澳门新莆京娱乐 6

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

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

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

澳门新莆京娱乐 7

上面七个矩阵依次是看破投影矩阵,视图矩阵,模型矩阵。四个矩阵的值分别取决于:阅览者的观点和视野距离,观看者在世界中的状态(地方和取向),模型在世界中的状态(地方和自由化)。总计的结果是(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章
  • 更换矩阵@维基百科
  • 透视投影详解

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

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

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

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

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

超级跑车游戏

    
 **
Example1: 复合变换
**

       
**
Example1**:使用1个缓冲区去赋值多少个顶峰数据(蕴含坐标及点大小)

着色器和光栅化

在 WebGL
中,开发者是透过着色器来完毕上述变换的。着色器是运作在显卡中的程序,以
GLSL 语言编写,开发者要求将着色器的源码以字符串的款型传给 WebGL
上下文的连锁函数。

着色器有二种,顶点着色器和片元(像素)着色器,它们成对出现。顶点着色器职务是收到顶点的部分坐标,输出
CCV 坐标。CCV
坐标经过光栅化,转化为逐像素的数据,传给片元着色器。片元着色器的义务是规定各个片元的颜色。

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

澳门新莆京娱乐 8

着色器功用很多,上述只是基本作用。大部分炫酷的效用都以信赖着色器的。如若你对着色器完全没有概念,可以试着明亮下一节
hello world 程序中的着色器再回想一下本节。

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

  • GLSL@维基百科
  • WebGL@MSDN

泛舟的男孩(Goo
EngineDemo)

      
在书中,笔者为大家封装了一套用于转移的矩阵对象:Matrix4对象。它富含以下两种方法:

function initVertexBuffers(gl) {
  var verticesSizes = new Float32Array([
     0.0,  0.5,  10.0,  
    -0.5, -0.5,  20.0,  
     0.5, -0.5,  30.0   
  ]);
  var n = 3; 
  var vertexSizeBuffer = gl.createBuffer();  
  if (!vertexSizeBuffer) {
    console.log('Failed to create the buffer object');
    return -1;
  }

  gl.bindBuffer(gl.ARRAY_BUFFER, vertexSizeBuffer);
  gl.bufferData(gl.ARRAY_BUFFER, verticesSizes, gl.STATIC_DRAW);

  var FSIZE = verticesSizes.BYTES_PER_ELEMENT;
  var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
  if (a_Position < 0) {
    console.log('Failed to get the storage location of a_Position');
    return -1;
  }
  gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, FSIZE * 3, 0);
  gl.enableVertexAttribArray(a_Position);  


  var a_PointSize = gl.getAttribLocation(gl.program, 'a_PointSize');
  if(a_PointSize < 0) {
    console.log('Failed to get the storage location of a_PointSize');
    return -1;
  }
  gl.vertexAttribPointer(a_PointSize, 1, gl.FLOAT, false, FSIZE * 3, FSIZE * 2);
  gl.enableVertexAttribArray(a_PointSize);  
  gl.bindBuffer(gl.ARRAY_BUFFER, null);

  return n;
}

程序

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

澳门新莆京娱乐 9

释疑几点(假若以前不精晓 WebGL ,多半会对上面包车型地铁代码质疑,无碍):

  1. 字符串 VSHADER_SOURCE 和 FSHADER_SOU途胜CE
    是极限着色器和片元着色器的源码。能够将着色器精晓为有一定输入和出口格式的次序。开发者需求事先编写好着色器,再依据一定格式着色器发送绘图命令。
  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.A途乐RAY_BUFFETucson实行的。在 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.TTiggoIANGLES
    内定绘制三角形,也足以改变参数绘制点、折线等等。

关于 ArrayBuffer 的详细消息,能够参见:

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

关于 gl.T奥迪Q5IANGLES
等任何绘制格局,能够参照上边那张图或那篇博文。

澳门新莆京娱乐 10

本文的靶子

  1. Matrix4.setIdentity():
    将Matrix4实例化为单位矩阵
  2. Matrix4.setTranslate(x,y,z):
    将Matrix4实例设置为平移变换矩阵,在x轴平移距离为x,在y轴平移距离为y,在z轴平移距离为z;

  3. Matrix4.setScale(x,y,z):
    将Matrix4实例设置为缩放变换矩阵,缩放因子分别为x,y,z;

  4. Matrix4.setRotate(angle,x,y,z):
    将Matrix4实例设置为旋转变换矩阵,角度为angle,旋转轴为(x,y,z);
  5. Matrix4.translate(x,y,z):
    将Matrix4实例本身乘以二个平移变换矩阵;
  6. Matrix4.rototate(angle,x,y,z):
    将Matrix4实例自己乘以四个旋转变换矩阵;
  7. Matrix4.scale(x,y,z): 将Matrix4实例本人乘以1个缩放变换矩阵;
  8. Matrix4.set(m): 将Matrix4设置为m;
  9. Matrix4.elements:
    类型化数组包含了Matrix4实例的矩阵成分;

    var modelMatrix = new Matrix4();
    modelMatrix.setRotate(ANGLE,0,0,1);
    modelMatrix.translate(Tx,0,0);

    … …

    gl.uniformMatrix4fv(u_ModelMatrix,false,modelMatrix.elements);

        

深度检查和测试

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

澳门新莆京娱乐 11

澳门新莆京娱乐 12

终点着色器给出了 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 的这套逻辑,对通晓蒙版(前面会说到)有部分声援。

正文的预期读者是:面生图形学,熟识前端,希望精晓或体系学习 WebGL
的同校。

 

         
Example2
:使用varying变量从巅峰着色器传输颜色音信给片元着色器

顶点索引

gl.drawArrays()是遵照顶点的逐条绘制的,而
gl.drawElements()能够令着色器以二个索引数组为顺序绘制顶点。比如以此例子。

澳门新莆京娱乐 13

此间画了八个三角形,但只用了 四个极端,有三个极限被多少个三角共用。这时急需建立索引数组,数组的种种成分表示顶点的索引值。将数组填充至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);

本文不是 WebGL 的概述性作品,也不是完整详细的 WebGL
教程。本文只希望成为一篇供 WebGL 初学者使用的纲要。

         Example2:
动画

var VSHADER_SOURCE =
  'attribute vec4 a_Position;\n' +
  'attribute vec4 a_Color;\n' +  //attribute变量
  'varying vec4 v_Color;\n' +   // varying变量
  'void main() {\n' +
  '  gl_Position = a_Position;\n' +
  '  gl_PointSize = 10.0;\n' +
  '  v_Color = a_Color;\n' +  // 将attribute变量赋给varying变量
  '}\n';


var FSHADER_SOURCE =

  '#ifdef GL_ES\n' +
  'precision mediump float;\n' + 
  '#endif GL_ES\n' +

  'varying vec4 v_Color;\n' +    //同名varying变量
  'void main() {\n' +
  '  gl_FragColor = v_Color;\n' + //!!!!!
  '}\n';

function initVertexBuffers(gl) {
  var verticesColors = new Float32Array([
    // 顶点坐标 与 颜色
     0.0,  0.5,  1.0,  0.0,  0.0, 
    -0.5, -0.5,  0.0,  1.0,  0.0, 
     0.5, -0.5,  0.0,  0.0,  1.0, 
  ]);
  var n = 3; 
  var vertexColorBuffer = gl.createBuffer();  
  if (!vertexColorBuffer) {
    console.log('Failed to create the buffer object');
    return false;
  }

  gl.bindBuffer(gl.ARRAY_BUFFER, vertexColorBuffer);
  gl.bufferData(gl.ARRAY_BUFFER, verticesColors, gl.STATIC_DRAW);

  var FSIZE = verticesColors.BYTES_PER_ELEMENT;
  var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
  if (a_Position < 0) {
    console.log('Failed to get the storage location of a_Position');
    return -1;
  }
  gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, FSIZE * 5, 0);
  gl.enableVertexAttribArray(a_Position);  
  var a_Color = gl.getAttribLocation(gl.program, 'a_Color');
  if(a_Color < 0) {
    console.log('Failed to get the storage location of a_Color');
    return -1;
  }
  gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, FSIZE * 5, FSIZE * 2);
  gl.enableVertexAttribArray(a_Color);  

  return n;
}

纹理

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

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

比如本条例子。

澳门新莆京娱乐 14

纹理对象和缓冲区指标很接近:使用 gl 的 API 函数创制,须要绑定至常量
gl.A猎豹CS6RAY_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);
};

Canvas

 requestAnimationFrame(func):
请求浏览器在以后某时刻回调函数func以成功重绘。我们理应在回调函数最终再度发起该请求。

 

混合与蒙版

透明效果是用混合机制形成的。混合机制与深度检查和测试类似,也发出在打算向有个别已填写的像素填充颜色时。深度检查和测试通过相比z值来规定像素的水彩,而掺杂机制会将二种颜色混合。比如这么些事例。

澳门新莆京娱乐 15

掺杂的一一是安分守纪绘制的一一实行的,要是绘制的各类有变化,混合的结果常常也不比。借使模型既有非透明表面又有透明表面,绘制透明表面时打开蒙版,其目标是锁定深度缓冲区,因为半透明物体前面包车型大巴实体依旧得以观望的,假设不这么做,半透明物体前边的物体将会被深度检查和测试机制排除。

翻开混合的代码如下。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
  ]);

明白 Canvas 的同室都知情,Canvas 绘图先要获取绘图上下文:

var tick = function() {
    currentAngle = animate(currentAngle);  // Update the rotation angle
    draw(gl, n, currentAngle, modelMatrix, u_ModelMatrix);   // Draw the triangle
    requestAnimationFrame(tick, canvas); // Request that the browser calls tick
};
tick();

      
Example3
:纹理(将图片的纹路赋给webgl对象)

浏览器的WebGL系统

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

澳门新莆京娱乐 16

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

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

 

var VSHADER_SOURCE =
  'attribute vec4 a_Position;\n' +
  'attribute vec2 a_TexCoord;\n' +  // 声明一个attribute变量
  'varying vec2 v_TexCoord;\n' +    // 声明一个varying变量
  'void main() {\n' +
  '  gl_Position = a_Position;\n' +
  '  v_TexCoord = a_TexCoord;\n' +  // attribute变量赋给varying变量
  '}\n';

var FSHADER_SOURCE =
  '#ifdef GL_ES\n' +
  'precision mediump float;\n' +
  '#endif\n' +

  'uniform sampler2D u_Sampler;\n' +
  'varying vec2 v_TexCoord;\n' +
  'void main() {\n' +

  // texture2D(sampler2D sampler, vec2 coord)
  // (纹理单元编号,纹理坐标) 这里是赋值的关键
  '  gl_FragColor = texture2D(u_Sampler, v_TexCoord);\n' + 
  '}\n';

function main() {  
  var canvas = document.getElementById('webgl');
  var gl = getWebGLContext(canvas);
  if (!gl) {
    console.log('Failed to get the rendering context for WebGL');
    return;
  }
  if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
    console.log('Failed to intialize shaders.');
    return;
  }

  // 设置顶点缓存
  var n = initVertexBuffers(gl);
  if (n < 0) {
    console.log('Failed to set the vertex information');
    return;
  }

  gl.clearColor(0.0, 0.0, 0.0, 1.0);

  // 设置纹理
  if (!initTextures(gl, n)) {
    console.log('Failed to intialize the texture.');
    return;
  }
}

function initVertexBuffers(gl) {
  var verticesTexCoords = new Float32Array([
    //webgl顶点坐标, 纹理坐标相应点
    -0.5,  0.5,   0.0, 1.0,
    -0.5, -0.5,   0.0, 0.0,
     0.5,  0.5,   1.0, 1.0,
     0.5, -0.5,   1.0, 0.0,
  ]);
  var n = 4; 
  // 创建缓存区对象
  var vertexTexCoordBuffer = gl.createBuffer();
  if (!vertexTexCoordBuffer) {
    console.log('Failed to create the buffer object');
    return -1;
  }

  gl.bindBuffer(gl.ARRAY_BUFFER, vertexTexCoordBuffer);
  gl.bufferData(gl.ARRAY_BUFFER, verticesTexCoords, gl.STATIC_DRAW);

  var FSIZE = verticesTexCoords.BYTES_PER_ELEMENT;
  var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
  if (a_Position < 0) {
    console.log('Failed to get the storage location of a_Position');
    return -1;
  }
  gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, FSIZE * 4, 0);
  gl.enableVertexAttribArray(a_Position);  


  // 将纹理坐标分配给该存储位置并开启
  var a_TexCoord = gl.getAttribLocation(gl.program, 'a_TexCoord');
  if (a_TexCoord < 0) {
    console.log('Failed to get the storage location of a_TexCoord');
    return -1;
  }  
  gl.vertexAttribPointer(a_TexCoord, 2, gl.FLOAT, false, FSIZE * 4, FSIZE * 2);
  gl.enableVertexAttribArray(a_TexCoord);  

  return n;
}

function initTextures(gl, n) {
 // Step1:设置纹理对象
  var texture = gl.createTexture();   
  if (!texture) {
    console.log('Failed to create the texture object');
    return false;
  }

  // Step2: 获取u_Sampler(取样器)存储位置
  var u_Sampler = gl.getUniformLocation(gl.program, 'u_Sampler');
  if (!u_Sampler) {console.log('Failed to get the storage location of u_Sampler');
    return false;
  }

  // Step3: 创建图片对象
  var image = new Image();  
  if (!image) {console.log('Failed to create the image object');
    return false;
  }

  image.onload = function(){ loadTexture(gl, n, texture, u_Sampler, image); };
  image.src = '../resources/sky.jpg';
  return true;
}

function loadTexture(gl, n, texture, u_Sampler, image) {
  // Step1:对图像进行y轴反转
  gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);  

  // Step2: 开启0号纹理单元(textunit0~7)
  gl.activeTexture(gl.TEXTURE0);

  // Step3: 绑定纹理对象(target,texture)   
  // target可以是:gl.TEXTURE或gl.TEXTURE_CUBE_MAP
  gl.bindTexture(gl.TEXTURE_2D, texture);

  // Step4: 设置纹理参数(target,pname,param)
  // gl.TEXTURE_MAG_FILTER (纹理放大) 默认值: gl.LINEAR
  // gl.TEXTURE_MIN_FILTER (纹理缩小) 默认值: gl.NEAREST_MIPMAP_LINEAR
  // gl.TEXTURE_WRAP_S (纹理水平填充)  默认值: gl.REPEAT(平铺式) 
  //                                         gl.MIRRORED_REPEAT (镜像对称)
  //                                         gl.CLAMP_TO_EDGE (使用纹理图像边缘值)
  // gl.TEXTURE_WRAP_T (纹理垂直填充)  默认值: gl.REPEAT
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);

  // Step5:配置纹理图片(target,level,internalformat,format,type,image)
  // level: 0
  // internalformat:图像的内部格式
  // format: 纹理数据的格式,必须与internalformat一致
  // type: 纹理数据的类型
  // image:包含纹理的图像的image对象
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image); 

  // Step6:将0号纹理传递至取样器
  gl.uniform1i(u_Sampler, 0);

  gl.clear(gl.COLOR_BUFFER_BIT);  
  gl.drawArrays(gl.TRIANGLE_STRIP, 0, n); 
}

光照

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

只然则有颜色的,模型也是有颜色的。在光照下,最后物体展现的水彩是双方联手功效的结果。

金玉锦绣光照的章程是:将光照的多寡(点光源的职责,平行光的势头,以及光的颜料和强度)作为
uniform 变量传入着色器中,将物体表面每种顶点处的法线作为 attribute
变量传入着色器,遵守光照规则,修订最终片元突显的颜料。

光照又分为逐顶点的和逐片元的,两者的区分是,将法线光线交角因素位居顶点着色器中考虑可能放在片元着色器中考虑。逐片元光照更是跃然纸上,三个十分的例证是:

澳门新莆京娱乐 17

那会儿,点光源在离开贰个外部较近处,表面核心 A
处较亮,四周较暗。然而在逐顶点光照下,表面包车型客车颜色(的熏陶因子)是由顶点内插出来的,所以表面宗旨也会相比暗。而逐片元光照直接运用片元的岗位和法线总计与点光源的交角,由此表面中心会相比较亮。

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

出于浏览器执行Tick()的年华是不可控的,大家需求让三角匀速的团团转,那么就须求控制时间:

 

复杂模型

复杂模型或许有囊括子模型,子模型恐怕与父模型有相对运动。比如开着雨刮器的轿车,雨刮器的世界坐标是受父模型小车,和笔者的图景共同决定的。若要总括雨刮器某顶点的岗位,供给用雨刮器相对小车的模子矩阵乘北小车的模子矩阵,再乘以顶点的部分坐标。

复杂模型可能有成都百货上千外表,只怕各类表面使用的着色器就不一样。平时将模型拆解为组,使用相同着色器的外表为一组,先绘制同一组中的内容,然后切换着色器。每趟切换着色器都要重新将缓冲区中的数据分配给着色器中相应变量。

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

var g_last = Date.now();
function animate(angle) {
  // Calculate the elapsed time
  var now = Date.now();
  var elapsed = now - g_last;
  g_last = now;
  // Update the current rotation angle (adjusted by the elapsed time)
  var newAngle = angle + ANGLE_STEP * (elapsed / 1000.0);
  return newAngle %= 360;
}

三、结尾

动画

动画的原理正是高效地擦除和重绘。常用的法门是闻名海外的
requestAnimationFrame
。素不相识的同窗,可以参照正美的牵线。

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

 

      上述代码均来自《WebGL编制程序指南》。

WebGL库

现阶段最风靡的 WebGL 库是
ThreeJS,很有力,官网,代码。

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

三、结尾

 

调节工具

正如早熟的 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本。那本书真的万分可相信,网上种种学Corey很多没说掌握的东西,那本书说得很了然,而且还提供了一份很完整的API文档。翻译那本书的历程也使自己收益匪浅。借使有同学愿意系统学一下
WebGL
的,提出购买一本(文青建议买英文版)。

1 赞 2 收藏 1
评论

澳门新莆京娱乐 18

而是接下去,假设想画2个矩形的话,就没这样简单了。实际上,Canvas
是浏览器封装好的一个制图环境,在实际上海展览中心开绘图操作时,浏览器依旧供给调用
OpenGL API。而 WebGL API 大约正是 OpenGL API 未经封装,直接套了一层壳。

Canvas 的越来越多知识,能够参见:

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

W3CSchool

阮一峰的 Canvas
教程

矩阵变换

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

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

澳门新莆京娱乐 19

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

澳门新莆京娱乐 20

绘图三维场景必须钦点2个旁观者,要是阅览者位于(0,0,1)处而且看向三角形,那么多少个终端相对于观望者的坐标为(-1,-1,-2),(1,-1,-2),(0,1,-2),即视图坐标。

澳门新莆京娱乐 21

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

澳门新莆京娱乐 22

将四棱台体映射为规范立方(CCV,主旨为原点,边长为2,边与坐标轴平行)。顶点在
CCV 中的坐标,离它最后在 Canvas 中的坐标已经很类似了,倘使把 CCV
的前表面看成 Canvas,那么最终三角形就画在图中橙褐三角形的地方。

澳门新莆京娱乐 23

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

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

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

澳门新莆京娱乐 24

上面多个矩阵依次是看破投影矩阵,视图矩阵,模型矩阵。四个矩阵的值分别取决于:观看者的见解和视野距离,观望者在世界中的状态(地方和动向),模型在世界中的状态(位置和方向)。计算的结果是(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章

转移矩阵@维基百科

透视投影详解

相关文章