OpenGl:www.opengl.orgweb
WebGL:www.learningwebgl.comcanvas
WebGL是针对Canvas的3D上下文;OpenGL等是3D图形语言;数组
类型化数组也是数组,只不过其元素被设置为特定类型的值。浏览器
ArrayBuffer
类型和byteLength
属性类型化数组的核心就是一个名为函数
ArrayBuffer
的类型。每一个ArrayBuffer对象表示的只是内存中指定的字节数,但不会指定这些字节用于保存什么类型的数据。经过ArrayBuffer能作的,就是为了未来使用而分配必定数量的字节。性能
如:webgl
var buffer = new ArrayBuffer(20); //在内存中分配20B
属性ui
byteLength
返回它包含的字节数调试
如:code
var buffer = new ArrayBuffer(20); console.log(buffer.byteLength); //20
DataView
数组缓冲器视图使用ArrayBuffer(数组缓冲器类型)的一种特别的方式就是用它来建立数组缓冲器视图。其中,最多见的视图是
DataView
,经过它能够选择ArrayBuffer中的一小段字节。为此,可在建立DataView实例的时候传入一个ArrayBuffer、一个可选的字节偏移量(从该字节开始选择)和一个可选的要选择的字节数。
如:
var view = new DataView(buffer); //新的视图 var view = new DataView(buffer, 6); //开始于字节6的新视图 var view = new DataView(buffer, 6, 9); //开始于字节6,结束于字节9的新视图
DataView
的属性byteOffset
和byteLength
DataView对象会把字节偏移量以及字符长度信息保存在
byteOffset
byteLength
两个属性中:
var view = new DataView(buffer, 6, 9); //开始于字节6,结束于字节9的新视图 console.log(view.byteOffset); //6 字节偏移量为6 console.log(view.byteLength); //9 字节长度为9
buffer属性也能够取得数组缓冲器;
getter
和setter
读写方法读取和写入DataView的时候,要根据实际操做的数据类型,选择相应的
getter
setter
以下,列出了DataView支持的数据类型以及相应的读写方法:
getter:
getInt8(byteOffset)
方法: 在相对于视图开始处的指定字节偏移量位置处获取 Int8 值。
getUint8(byteOffset)
方法 (DataView): 在相对于视图开始处的指定字节偏移量位置处获取 Uint8 值。
getInt16(byteOffset,littleEndian)
方法 (DataView): 在相对于视图开始处的指定字节偏移量位置处获取 Int16 值。
getUint16(byteOffset,littleEndian)
方法 (DataView): 在相对于视图开始处的指定字节偏移量位置处获取 Uint16 值。
getInt32(byteOffset,littleEndian)
方法 (DataView): 在相对于视图开始处的指定字节偏移量位置处获取 Int32 值。
getUint32(byteOffset,littleEndian)
方法 (DataView): 在相对于视图开始处的指定字节偏移量位置处获取 Uint32 值。
getFloat32(byteOffset,littleEndian)
方法 (DataView): 在相对于视图开始处的指定字节偏移量位置处获取 Float32 值。
getFloat64(byteOffset,littleEndian)
方法 (DataView): 在相对于视图开始处的指定字节偏移量位置处获取 Float64 值。
setter:
setInt8(byteOffset,value)
方法 (DataView): 在相对于视图开始处的指定字节偏移量位置处存储 Int8 值。
setUint8(byteOffset,value)
方法 (DataView): 在相对于视图开始处的指定字节偏移量位置处存储 Uint8 值。
setInt16(byteOffset,value,littleEndian)
方法 (DataView): 在相对于视图开始处的指定字节偏移量位置处存储 Int16 值。
setUint16(byteOffset,value,littleEndian)
方法 (DataView): 在相对于视图开始处的指定字节偏移量位置处存储 Uint16 值。
setInt32(byteOffset,value,littleEndian)
方法 (DataView): 在相对于视图开始处的指定字节偏移量位置处存储 Int32 值。
setUint32(byteOffset,value,littleEndian)
方法 (DataView): 在相对于视图开始处的指定字节偏移量位置处存储 Uint32 值。
setFloat32(byteOffset,value,littleEndian)
方法 (DataView): 在相对于视图开始处的指定字节偏移量位置处存储 Float32 值。
setFloat64(byteOffset,value,littleEndian)
方法 (DataView): 在相对于视图开始处的指定字节偏移量位置处存储 Float64 值。
如:
var buffer = new ArrayBuffer(20); var view = new DataView(buffer); view.setUint16(0,25); //0000000000001001 var value = view.getUint8(0); //00000000 console.log(value); //0
类型化视图在读写数组缓冲器中更加便利:
类型化视图通常也被称为类型化数组,由于它们除了元素必须是某种特定的数据类型外,与常规的数组无异。类型化视图也分几种,并且它们都继承了DataView。
Int8Array
:表示8为二补整数。
Uint8Array
:表示8位无符号整数。
Int16Array
:表示16位二补整数。
Uint16Array
:表示16位无符号整数。
Int32Array
:表示32为二补整数。
Uint32Array
:表示32位无符号整数。
Float32Array
:表示32位IEEE浮点值。
Float64Array
:表示64位IEEE浮点值。
须要三个参数,只有第一个是必须的:ArrayBuffer对象、字节偏移量、要包含的字节数,如:
var buffer = new ArrayBuffer(20); var int8s = new Int8Array(buffer);
注意:20B的ArrayBuffer能够保存20个Int8Array或Uint8Array,或者10个Int16Array或Uint16Array,或者5个Int32Array或Uint32Array或Float32Array,或者2个Float64Array。
var buffer = new ArrayBuffer(20); var int8s = new Int8Array(buffer); //建立一个新数组,使用整个缓冲器 var int16s = new Int16Array(buffer, 9); //只使用从字节9开始的缓冲器 var uint16s = new Uint16Array(buffer, 9, 10); //只使用从字节9到字节10的缓冲器
可以指定缓冲器中可用的字节段,意味着能在同一个缓冲器中保存不一样类型的数值,以下面的代码就是在缓冲器的开头保存8位整数,而在其余字节中保存16位整数:
var buffer = new ArrayBuffer(30); //缓冲器中有30个字节 var int8s = new Int8Array(buffer, 0, 10); //前面10个字节存储10个8位整数 var int16s = new Int16Array(buffer, 10, 10); //后面还有20个字节,2个字节存储1个16位整数,因此只能存储10个
另外,每一个视图构造函数都有一个名为
BYTES_PER_ELEMENT
表示类型化数组的每一个元素须要多少字节:
console.log(Float64Array.BYTES_PER_ELEMENT) //8
这样就能够利用这个属性来辅助初始化:
var buffer = new ArrayBuffer(20); var int8s = new Int8Array(buffer, 0, 10 * Int8Array.BYTES_PER_ELEMENT); var int16s = new Int16Array(buffer, int8s.byteOffset + int8s.byteLength, (10 / Int16Array.BYTES_PER_ELEMENT));
另外,还能够不用首先建立ArrayBuffer对象,只要传入但愿数组保存的元素数,相应的构造函数就能够自动建立一个包含足够字节数的ArrayBuffer对象:
var int16s = new Int16Array(10); //建立一个数组保存10个16位整数(10字节) var int32s = new Int32Array(1); //建立一个数组保存1个32位整数(4字节)
另外还能够把常规数组转换为类型化视图:
var int8s = new Int8Array([1,2,3,4]); var view = new DataView(int8s.buffer); console.log(int8s.toString()); //1234 console.log(view.byteLength); //4
对类型化视图的迭代:
for (var i = 0; i < int8s.length; i++) { console.log(int8s[i]); };
也可使用方括号语法为类型化视图的元素赋值:
var uint16s = new Uint16Array(10); uint16s[0] = 65537; console.log(uint16s[0]); //1
另外能够经过
subarray()
方法基于底层数组缓冲器的子集建立一个新视图,接收两个参数:开始元素的索引,可选的结束元素的索引:
如:
var uint16s = new Uint16Array(10), sub = uint16s.subarray(2, 5);
目前,在支持的浏览器中,WebGL的名字叫作“experimental-webgl”,这是由于WebGL规范仍然未制定完成。制定完成后,这个上下文的名字就会变成简单的“webgl”。若是浏览器不支持WebGL,那么取得该上下文时会返回null。
var drawing = document.getElementById("drawing"); if (drawing.getContext) { var gl = drawing.getContext("experimental-webgl"); if (gl) { //[...] } }
经过给getContext()传递第二个参数,能够为WebGL上下文设置一些选项。这个参数自己是一个对象,能够包含下列属性:
* `alpha`:值为true,表示为上下文建立一个Alpha通道缓冲区;默认值为true; * `depth`:值为true,表示可使用16位深缓冲区;默认值为true; * `stencil`:值为true,表示可使用8位模板缓冲区;默认值为false; * `antialias`:值为true,表示将使用默认机制执行抗锯齿操做;默认值为true。 * `premultipliedAlpha`:值为true,表示绘图缓冲区有预乘Alpha值;默认为true; * `preserveDrawingBuffer`:值为true;表示在绘图完成后保留绘图缓冲区;默认值为false。
传递这个选项对象的方式以下:
var drawing = document.getElementById("drawing"); if (drawing.getContext) { var gl = drawing.getContext("experimental-webgl", { alpha: false }); if (gl) { //[...] } }
大多数状况下不用开启,由于可能影响到性能,并且默认值通常都能知足咱们需求。
若是getContext()没法建立WebGL上下文,浏览器可能会报错。因此应该把它封装到try-catch块中:
var drawing = document.getElementById("drawing"); if (drawing.getContext) { try { var gl = drawing.getContext("experimental-webgl"); } catch (e) {} if (gl) { //[...] } }
在WebGL中,保存在上下文对象中的这些常量都没有GL_前缀。
方法名的后缀会包含参数个数(1到4),和接收的数据类型(f为浮点数,i为整数),如:gl.uniform4f()意味着要接收4个浮点数;另外还有不少方法接收数组参数而非一个个单独的参数,这样的方法中名字包含字母v,如:gl.uniform3iv()能够接收一个包含3个值的整数数组。
在实际操做WebGL上下文以前,通常都要使用某种实色清除canvas元素,为绘图作好准备。为此,首先必须使用:
clearColor()
方法来指定要使用的颜色值,这个方法接收4个参数:红、绿、蓝和透明度。每一个参数必须是一个0到1之间的数值,表示每种份量在最终颜色中的强度。
如:
var drawing = document.getElementById("drawing"); if (drawing.getContext) { try { var gl = drawing.getContext("experimental-webgl"); } catch (e) {} if (gl) { gl.clearColor(0,0,0,1); //把清理缓冲区的值设置为黑色 gl.clear(gl.COLOR_BUFFER_BIT); //调用clear方法,传入参数gl.COLOR_BUFFER_BIT告诉WebGL使用以前定义的颜色来填充相应区域。 } }
开始绘图以前,一般要先定义WebGL的视口(viewport)。默认状况下,视口可使用整个canavs区域。要改变视口大小,能够调用
viewport()
方法并传入4个参数:(视口相对于canvas元素的)x、y坐标、宽度和高度。
视口坐标的原点(0,0)在canvas元素的左下角,x轴和y轴的正方向分别是向右和向上,能够定义为(width-1,height-1)。
如:
var drawing = document.getElementById("drawing"); if (drawing.getContext) { try { var gl = drawing.getContext("experimental-webgl"); } catch (e) {} if (gl) { gl.clearColor(0, 0, 0, 1); gl.clear(gl.COLOR_BUFFER_BIT); // gl.viewport(0, 0, drawing.width / 2, drawing.height / 2); //视口在画布的左下角四分之一区域 gl.viewport(drawing.width / 2, 0, drawing.width / 2, drawing.height / 2); //视口在画布的右下角四分之一区域 } }
视口内部的坐标系与定义视口的坐标系也不同。在视口内部,坐标原点(0,0)是视口的中心点,所以视口左下角坐标为(-1,-1),而右上角坐标为(1,1)。
顶点信息保存在JavaScript的类型化数组中,使用以前必须转换到WebGL的缓冲区。要建立缓冲区,能够调用
gl.createBuffer()
,而后使用
gl.bindBuffer()
绑定到WebGL上下文。这两步作完之后,就能够用数据来填充缓冲区了。
如:
var drawing = document.getElementById("drawing"); if (drawing.getContext) { try { var gl = drawing.getContext("experimental-webgl"); } catch (e) {} if (gl) { gl.clearColor(0, 0, 0, 1); gl.clear(gl.COLOR_BUFFER_BIT); gl.viewport(drawing.width / 2, 0, drawing.width / 2, drawing.height / 2); var buffer = gl.createBuffer(); //建立缓冲区 gl.bindBuffer(gl.ARRAY_BUFFER, buffer); //绑定到上下文 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0, 0.5, 1]), gl.STATIC_DRAW); //使用Float32Array中的数据初始化buffer } }
gl.bufferData()
最后一个参数主要有:
gl.STATIC_DRAW
:数据只加载一次,在屡次绘图中使用;
gl.STREAM_DRAW
:数据只加载一次,在几回绘图中使用;
gl.DYNAMIC_DRAW
:数据动态改变,在屡次绘图中使用;
通常来讲gl.STATIC_DRAW
够用了;
在包含缓冲区的页面重载以前,缓冲区始终保留在内存中。若是你不想要某个缓冲区了,能够直接调用
gl.deleteBuffer()
释放内存。
JavaScript与WebGL之间的一个最大区别在于,WebGL操做通常不会抛出错误。为了知道是否有错误发生,必须在调用某个可能出错的方法后,手工调用
gl.getError()
方法。这个方法返回一个表示错误类型的常量。
可能的错误常量以下:
gl.NO_ERROR
:上一次操做没有发生错误(值为0)。
gl.INVALID_ENUM
:应该给方法传入WebGL常量,但却传错了参数。
gl.INVALID_VALUE
:在须要无符号数的地方传入了负值。
gl.INVALID_OPERATION
:在当前状态下不能完成操做。
gl.OUT_OF_MEMORY
:没有足够的内存完成操做。
gl.CONTEXT_LOST_WEBGL
:因为外部事件(如设备断电)干扰丢失了当前WebGL的上下文。
若是发生了多个错误,须要反复调用gl.getError()直到返回gl.NO_ERROR:
var errorCode = gl.getError(); while (errorCode) { console.log(errorCode); errorCode = gl.getError(); }
着色器(shader)是OpenGL 中的另外一个概念。WebGL中有两种着色器:定点着色器和片断(或像素)着色器。顶点着色器用于将3D顶点转换为须要渲染的2D点。片断着色器用于准确计算要绘制的每一个像素的颜色。WebGL的着色器是使用GLSL(OpenGL Shading Language,OpenGL着色器)写的,GLSL是一种与C和JavaScript彻底不一样的语言。
GLSL是一种类C语言,专门用于编写OpenGL着色器。由于WebGL是OpenGL ES 2.0的实现,因此OpenGL中使用的着色器能够直接在WebGL中使用。
每一个着色器都有一个
main()
方法,该方法在绘图期间会重复执行。
为着色器传递数据的方式有两种:
Attribute
和Uniform
。经过Attribute能够向顶点着色器传入顶点信息,经过Uniform能够向任何着色器传入常量值。
Attribute和Uniform在main()方法外部定义,分别使用关键字attribute和uniform。
如Attribute顶点着色器:
void main() { gl_Position = vec4(aVertexPosition, 0.0, 1.0); }
又如Uniform片断着色器:
void main() { gl_FragColor = uColor; }
浏览器不能理解GLSL程序,所以必须准备好字符串形式的GLSL程序,以便编译并连接到着色器程序。
前面定义的着色器必须接收一个值才能工做。为了给着色器传入这个值,必须先找到要接收这个值的变量。
与着色器的其余操做同样,着色器操做也可能会失败,并且也是静默失败。若是你想找到着色器或程序执行中是否发生了错误,必须亲自询问WebGL上下文。
WebGL只能绘制三种形状:点、线和三角。其余全部形状都是由这三种基本形状合成以后,再绘制到三维空间中的。执行绘图操做要调用gl.drawArrays()或gl.drawElements()方法,前者用于数组缓冲区,后置用于元素数组缓冲区。
WebGL的纹理可使用DOM中的图像。要建立一个新纹理,能够调用gl.createTexture(),而后再将一副图像绑定到该纹理。若是图像还没有加载到内存中,可能须要建立一个Image对象的实例,以便动态加载图像。图像加载完成以前,纹理不会初始化,所以,必须在load事件触发后才能设置纹理。
与2D上下文相似,经过WebGL上下文也能读取像素值。读取像素值的方法readPixels()与OpenGL中的同名方法只有一点不一样,即最后一个参数必须是类型化数组。像素信息是从帧缓冲区读取的,而后保存在类型化数组中。readPixels()方法的参数有:x、y、宽度、高度、图像格式、数据类型和类型化数组。前4个参数指定读取哪一个区域中的像素。图像格式参数几乎老是gl.RGBA。数据类型用于指定保存在类型化数组中的数据类型,但有如下限制。
若是类型是gl.UNSIGNED_BYTE,则类型化数组必须是Unit8Array。
若是类型是gl.UNSIGNED_SHORT_5_6_五、gl.UNSIGNED_SHORT_4_4_4_四、或gl.UNSIGNED_SHORT_5_5_5_1,则类型化数组必须是Unit16Array。
15.3.3 支持
Firefox4+和Chrome都实现了WebGL API。Safari5.1也实现了WebGL,但默认是禁用的。