JavaScript Canvas——“WebGL”的注意要点

OpenGl:www.opengl.org

WebGL:www.learningwebgl.com

WebGL是针对Canvas的3D上下文;OpenGL等是3D图形语言;

类型化数组

类型化数组也是数组,只不过其元素被设置为特定类型的值。

数组缓冲器ArrayBuffer类型和byteLength属性

类型化数组的核心就是一个名为

  • ArrayBuffer的类型。每个ArrayBuffer对象表示的只是内存中指定的字节数,但不会指定这些字节用于保存什么类型的数据。通过ArrayBuffer能做的,就是为了将来使用而分配一定数量的字节。

如:

var buffer = new ArrayBuffer(20); //在内存中分配20B

属性

  • byteLength 返回它包含的字节数

如:

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的属性byteOffsetbyteLength

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属性也可以取得数组缓冲器;

gettersetter读写方法

读取和写入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上下文

目前,在支持的浏览器中,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()方法,该方法在绘图期间会重复执行。

为着色器传递数据的方式有两种:

  • AttributeUniform。通过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_5、gl.UNSIGNED_SHORT_4_4_4_4、或gl.UNSIGNED_SHORT_5_5_5_1,则类型化数组必须是Unit16Array。

15.3.3 支持

   Firefox4+和Chrome都实现了WebGL API。Safari5.1也实现了WebGL,但默认是禁用的。

你可能感兴趣的:(webgl,javascript)