资讯专栏INFORMATION COLUMN

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

whidy / 1740人阅读

摘要:中有两种着色器定点着色器和片段或像素着色器。顶点着色器用于将顶点转换为需要渲染的点。的着色器是使用,着色器写的,是一种与和完全不同的语言。为着色器传递数据的方式有两种和。通过可以向顶点着色器传入顶点信息,通过可以向任何着色器传入常量值。

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,但默认是禁用的。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/78634.html

相关文章

  • SegmentFault 技术周刊 Vol.35 - WebGL:打开网页看大片

    摘要:在文末,我会附上一个可加载的模型方便学习中文艺术字渲染用原生可以很容易地绘制文字,但是原生提供的文字效果美化功能十分有限。 showImg(https://segmentfault.com/img/bVWYnb?w=900&h=385); WebGL 可以说是 HTML5 技术生态链中最为令人振奋的标准之一,它把 Web 带入了 3D 的时代。 初识 WebGL 先通过几个使用 Web...

    objc94 评论0 收藏0
  • JavaScript Canvas——“2D上下文”注意要点

    摘要:源图像之外的目标图像部分不会被显示。使用异或操作对源图像与目标图像进行组合。如将第二个图片放在第一个图片下方检查兼容性绘制原始图像 Canvas支持基本绘图能力的2D上下文,以及基于WebGL的3D上下文 基本用法 canvas元素:定义画布 getContext()方法:定义2D、3D上下文 toDataURL()方法:生成图片格式获取URL链接(支持image/png;有浏览器也...

    skinner 评论0 收藏0
  • WebGL 初探

    摘要:下面是顶点及片元着色器程序,用字符串表示,它将直接运行在浏览器之上。最后,使用将和着色器连接。创建着色器对象创建编程对象赋值已创建的着色器对象连接编程对象检查链接结果链接程序失败使用可用着色器程序这一步主要使用方法告诉使用程序。 该文章于一天前发表在 github,若有问题可提至 github。 目前,我们有很多方案可以快速的接触到 WebGL 并绘制复杂的图形,但最后发现我们忽视了很...

    whjin 评论0 收藏0
  • 前端动画调研-V1

    摘要:支持动画状态的,在动画开始,执行中,结束时提供回调函数支持动画可以自定义贝塞尔曲线任何包含数值的属性都可以设置动画仓库文档演示功能介绍一定程度上,也是一个动画库,适用所有的属性,并且实现的能更方便的实现帧动画,替代复杂的定义方式。 动画调研-V1 前言:动画从用途上可以分为两种,一种是展示型的动画,类似于一张GIF图,或者一段视频,另一种就是交互性的动画。这两种都有具体的应用场景,比如...

    ddongjian0000 评论0 收藏0
  • WebGL2系列之从WebGL1迁移到WebGL2

    摘要:比如顶点数组对象,在中,是一个扩展对象,而在直接使用。在中,使用代码如下告诉用户没有此扩展或者使用其他方式而在中,代码如下有关的相关功能,将会在以后的章节中介绍。后面的章节,会陆续介绍相关的特性。 WebGL2几乎100%兼容WebGL1,需要注意的是,这里说的几乎,也就是说,也是存在一些细微的差别的,因此原本WebGL1的代码迁移到WebGL2,应该不会有遇到太多的困难;不过WebG...

    Youngdze 评论0 收藏0

发表评论

0条评论

whidy

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<