动画与 Canvas 图形
使用 requestAnimationFrame
早期定时动画
早期动画就是使用 setInterval()来控制动画的执行
无论是 setInterval()还是 setTimeout()都是不能保证时间精度的。作为第二个参数的延时只能保证何时将代码添加到任务队列,不能保证添加到队列就会立即执行
时间间隔的问题
浏览器自身计时器会让这个问题雪上加霜,浏览器的计时器精度不足毫秒,以下是浏览器几个计时器精度情况:
IE8 及更早版本的计时器精度为 15.625 秒
IE9 及更晚版本的计时器精度为 4 毫秒
Firefox 和 Safar 的计时器精度为约 10 毫秒
Chrome 的计时器精度为 4 毫秒
即使将时间间隔设到最优,也免不了只能得到近似结果
requestAnimationFrame
该方法接收一个参数,该参数是一个要在重绘屏幕前调用的函数;这个函数就是修改 DOM 样式以反映下一次重绘有什么变化的地方;为了实现循环,可以把多个 requestAnimationFrame()调用串联起来:
function update() { var div = document.getElementById('status'); div.style.width = (parseInt(div.style.width, 10) + 5 + '%'; if (div.style.left != 100%) { requestAnimationFrame(update); }}requestAnimationFrame(update);
在传给该函数的函数中可以接收一个参数,此参数是一个 DOMHighResTimeStamp 的实例(比如 performance.now()返回的值),表示下次重绘的时间
该函数其实把重绘任务安排在了未来一个已知时间点上,而且通过这个参数告诉了开发者,开发者可以根据这个参数优化动画
cancelAnimationFrame
requestAnimationFrame()也返回一个 ID,可以通过 cancelAnimationFrame()来取消重绘任务
通过 requestAnimationFrame 节流
支持这个方法的浏览器实际上会暴露作为钩子的回调队列(钩子 hook ,就是浏览器再执行下一次重绘之前的一个点);这个回调队列是一个可修改的函数列表,包含在重绘之前的调用函数,每次调用 requestAnimationFrame()函数都会在队列上推入一个回调函数,队列的长度没有限制
这个回调队列的行为不一定和动画有关,不过,每次递归的向队列中加入回调函数,可以保证每次重绘最多只调用一次回调函数,这是一个非常好的节流工具
function test() {
console.log("hello");
}
window.addEventListener("scroll", () => {
window.requestAnimationFrame(test);
}); //这样会把每次回调执行集中在钩子,但不会过滤掉多余的调用,所以可以定义一个标志变量作为开关let flag = false;function test() { console.log('hello'); flag = false;}window.addEventListener('scroll', () => { if (!flag) { flag = true; window.requestAnimationFrame(test); }})//但是重绘是频繁的操作,所以这个还不能算真正节流,更好的办法是配合使用一个计时器来限制操作执行的频率let flag = true;function test() { console.log('hello');}window.addEventListener('scroll', () => { if (flag) { flag = false; window.requestAnimationFrame(test); window.setTimeout(() => flag = true, 50); }})
基本的画布功能
创建 canvas 元素时至少要设置其 width 和 height 属性,这样才能告诉浏览器在多大面积上绘图;在标签内的内容是后备数据,以防浏览器会不支持 canvas 元素时显示
元素可以像其他元素一样在 DOM 节点上设置属性,整个元素还可以通过 CSS 添加样式,并且元素在添加样式或实际绘图内容前是不可见的
要在画布上绘制图形,首相要取得绘图上下文,使用 getContext()方法可以获取绘图上下文的引用,对于平面图形还需要给这个方法传入参数 2d
let drawing = document.getElementById("drawing"); //确保浏览器支持canvasif (drawing.getContext) { let context = drawing.getContext('2d');}
可以使用 toDataURL 方法导出 canvas 元素上的图像,这个方法接收一个参数:要生成的图像的 MIME 类型
let drawing = document.getElementById("drawing"); //确保浏览器支持canvasif (drawing.getContext) { //获得图像数据URI let imgURI = drawing.toDataURL("image/png"); //显示图片 let image = document.createElement("img"); image.src = imgURI; document.body.appendChild(image);}//较新版本的浏览器还支持“image/jpeg”进行JPEG编码
如果画布的图像是从其它域绘制过来的,toDataURL()方法就会抛出错误,因为跨域问题
2D 绘图上下文
该上下文提供了绘制 2D 图形的方法,包括矩形、弧形和路径;2D 上下文坐标原点(0,0)在 canvas 元素的左上角,x 轴坐标向右增长,y 轴坐标向下增长,width 和 height 表示两个方向上像素的最大值
填充和描边
可以在 2D 上下文设置基本的绘画操作:填充和描边,fillStyle 和 strokeStyle;这两个属性可以接收 CSS 色彩的任意格式(名称、rgb
、渐变、图案等)
设置完成后,所有的描边和填充都会使用这两种样式,除非再次修改
let context = drawing.getContext("2d");
context.strokeStyle = "red";
context.fillStyle = "#0000ff";
绘制矩形
矩形是唯一一个可以直接在 2D 绘图上下文中绘制的形状,相关方法有三个:fillRect()、strokeRect()、clearRect(),这些方法都接收四个参数:矩形 x 坐标、矩形 y 坐标、矩形宽度、矩形高度,单位都为像素
fillRect 方法用于在画布上绘制并填充矩形;strokeRect 方法用于绘制矩形描边轮廓;clearRect 方法可以擦除画布中某个矩形区域
描边的宽度由 lineWidth 属性控制,可以是任意整数值;lineCap 属性控制线条端点形状:butt(平头)、round(出圆头)、square(出方头);lineJoin 控制线条交点的形状:round(圆转)、bevel(取平)、miter(出尖)
绘制路径
绘制路径首先要先调用 beginPath()方法表示要开始绘制的新路径,然后调用以下方法来绘制路径:
arc(x, y, radius, startAngle, endAngle, counterclockwise):以坐标 x,y 为圆心,以 radius 为半径绘制一条弧线,起始角度为 startAngle,结束角度为 endAngle,counterclockwise 表示是否逆时针计算起始角度和结束角度(默认顺时针)
arcTo(x1, y1, x2, y2, radius):以半径 radius,经由(x1, y1)绘制一条从上一点到(x2, y2)的弧线
bezierCurveTo(c1x, c1y, c2x, c2y, x, y):以(c1x, c1y)和(c2x, c2y)为控制点,绘制一条从上一点到(x, y)的弧线(三次贝塞尔曲线)
lineTo(x, y):绘制一条从上一点到(x, y)的直线
moveTo(x, y):不绘制线条,把绘制光标移动到(x, y)
quadraticCurveTo(cx, cy, x, y):以(cx, cy)为控制点,绘制一条从上一点到(x, y)的弧线(二次贝塞尔曲线)
rect(x, y, width, height):以给定宽度和高度在坐标点(x, y)绘制一个矩形
创建路径后可以使用 closePath()方法绘制一条返回起点的线。如果路径已经完成,可以使用 fillStyle 属性并调用 fill()方法来填充路径,也可以指定 strokeStyle 属性并调用 stroke()方法来描画路径,还可以调用 clip()方法基于已有路径创建一个新剪切区域
isPointInPath(x, y)方法可以检测指定点是否在路径上,可以在关闭路径前随时调用
绘制文本
可以通过 fillText()和 strokeText()方法绘制文本,这两个方法都接收四个参数:要绘制的字符串、x 坐标、y 坐标、可选的最大像素宽度
该方法绘制的结果都取决于以下三个属性:
font:以 CSS 语法指定的字体样式、大小、字体族等,比如“10px Arial”
textAlign:指定文本的对齐方式,start、end、left、right、center
textBaseLine:指定文本的基线,top、hanging、middle、alphabetic、ideographic、bottom
measureText()方法接受一个参数,即要绘制的文本,然后返回一个 TextMetrics 对象
变换
上下文变换可以操作绘制在画布上的图像,以下方法可用于改变绘制上下文的变换矩阵:
rotate(angle):围绕原点把图像旋转 angle 弧度
scale(scaleX, scaleY):通过在 x 轴乘以 scaleX、在 y 轴乘以 scaleY 来缩放图像,默认值都是 1.0
translate(x, y):把原点移动到(x, y),这个操作执行后,原点坐标就会从(0, 0)变成(x, y)
transform(m1_1, m1_2, m2_1, m2_2, dx, dy):像下面这样通过矩阵乘法直接修改矩阵
m1_1 m1_2 dx
m2_1 m2_2 dy
0 0 1
setTransform(m1_1, m1_2, m2_1, m2_2, dx, dy):把矩阵重置为默认值,再以传入的参数调用 transform()
如果想记录当前属性和变化状态,可以调用 save()方法,调用后,这一时刻所有的设置会被放到一个暂存栈中。保存后可以继续修改上下文,需要恢复时,可以调用 restore()方法,这个方法会从栈区中取出之前保存的设置并恢复(save 只保存上下文设置和变换,不保存内容)
绘制图像
可以通过 drawing()方法绘制图像,该方法接收三组不同参数:
要绘制的图像,x 坐标,y 坐标
要绘制的图像,x 坐标,y 坐标,目标宽度,目标高度
要绘制的图像,源图像 x 坐标,源图像 y 坐标,源图像宽度、源图像高度、目标区域 x 坐标、目标区域 y 坐标、目标区域宽度、目标区域高度
这里要绘制的图像可以是 HTML 的 img 元素,还可以是另一个 canvas 元素
如果绘制的图像来自其它域而非当前页面,则不能获取其数据,此时使用 toDataURL()会报错
阴影
可以为已有的形状或路径生成阴影,使用以下方法:
shadowColor:CSS 颜色值,默认黑色
shadowOffsetX:阴影相对于形状或路径的 x 坐标偏移量,默认为 0
shadowOffsetY:阴影相对于形状或路径的 y 坐标偏移量,默认为 0
shadowBlur:像素,表示模糊量,默认为 0
这些属性都可以通过 context 对象读写
渐变
渐变通过 CanvasGradient 实例表示,调用上下文的 createLinearGradient()创建线性渐变,这个方法接收四个参数:起点 x 坐标、起点 y 坐标、终点 x 坐标、终点 y 坐标;该方法返回一个 CanvasGradient 对象并返回实例
然后使用 addColorStop()方法为渐变指定色标,这个方法接收两个参数:色标位置、CSS 颜色值(色标通过 0-1 范围内的值表示,0 代表第一种颜色,1 代表最后一种颜色)
let gradient = context.createLinearGradient(30, 30, 70, 70);
gradient.addColorStop(0, "white");
gradient.addColorStop(1, "black");
context.fillStyle = "#0000ff";
context.fillRect(10, 10, 50, 50);
context.fillStyle = gradient;
context.fillRect(30, 30, 50, 50);
径向渐变(放射性渐变)使用 createRadialGradient()方法来创建,该方法接收六个参数:起点圆心 x 坐标、起点圆心 y 坐标、起点圆心半径、终点圆心 x 坐标、终点圆心 y 坐标、终点圆心半径
图案
图案是用于填充和描画图形的重复图像,通过 createPattern()方法创建新图案,传入两个参数:源图案、表示该如何重复图像的字符串(与 CSS 的 background-repeat 属性值一样:repeat、repeat-x、repeat-y、no-repeat)
和渐变一样,图案的起点实际上是画布的原点(0, 0);将填充样式设置为图案,表示在指定位置而不是开始绘制的位置显示图案
源图案可以是:HTML 的 img 元素、video 元素、另一个 canvas 元素
图像数据
2D 上下文可以使用 getImageData()方法获取原始图像数据,方法接收四个参数:左上角像素 x 坐标、左上角像素 y 坐标、像素宽度,像素高度;返回一个 ImageData 实例,每个实例都包含三个属性:width、height、data,data 是包含图像原始像素信息的数组
每个像素在 data 数组中都由四个值表示,分别代表红、绿、蓝和透明度值;例如第一个像素信息包含在第 0-3 个值中:
let data = imageData.data;
red = data[0];
green = data[1];
blue = data[2];
alpha = data[3];
这样四个四个一组表示取得的所有像素的信息
处理好的信息可以通过 putImageData()方法,把图像数据再绘制到画布上context.putImageData(imageData, 0, 0);
合成
2D 上下文中绘制的所有内容都会应用两个属性:globalAlpha 和 globalCompositionOperation
globalAlpha 是一个在 0-1 范围的值(包括 0 和 1),用于指定所有内容的透明度;修改只会影响后面的绘制
globalCompositionOperation 表示新绘制的形状如何与已有的形状融合,有下列值:
source-over:默认值,新图形在原有图形上
source-in:新图形只绘制与原有图形重叠的部分,其余部分全部透明
source-out:新图形只绘制出不与原有图形重叠的部分,其余部分全部透明
source-atop:新图形只绘制出与原有图形重叠的部分,原有图形不受影响
destination-over:新图形绘制在原有图形下面,重叠部分只有原图形透明像素下的部分可见
destination-in:新图形绘制在原有图形下面,画布上只剩下二者重叠的部分,其余部分完全透明
destination-out:新图形与原有图形重叠的部分完全透明,原图形其余部分不受影响
destination-atop:新图形绘制在原有图形下面,原有图形与新图形不重叠部分完全透明
lighter:新图形与原有图形重叠部分的像素值相加,使该部分变亮
copy:新图形将擦除并完全取代原有图形
xor:新图形与原有图形重叠部分执行”异或“计算
通过给 globalCompositionOperation 属性赋上面的值来规定合成方式;这个值在不同浏览器上有差异,用之前先了解
WebGL
WebGL 是画布 3D 上下文,它不是 W3C 标准,而是 Khronos Group 的标准,很多 WebGL 的概念可以从 OpenGL 照搬过来,可以先去熟悉 OpenGL ES2.0
WebGL 上下文
在完全支持的浏览器中,WebGL 2.0 上下文的名字叫”webgl2“,WebGL 1.0 上下文的名字叫”webgl1“,如果浏览器不支持 WebGL 则会返回 null
let drawing = document.getElementById('drawing');if (drawing.getContext) { let gl = drawing.getContext('webgl'); if (gl) { //... }}
WebGL 基础
可以在取得 WebGL 上下文时指定一些选项,通过一个参数对象传入
alpha:布尔值,表示是否为上下文创建透明通道缓冲区,默认 true
depth:布尔值,表示是否使用 16 位深缓冲区,默认 true
stencil:布尔值,表示是否使用 8 位模板缓冲区,默认 false
antialias:布尔值,表示是否使用默认机制执行抗锯齿操作,默认 true
premultipliedAlpha:布尔值,表示缓冲区是否预乘透明度值,默认为 true
preserveDrawingBuffer:布尔值,表示绘图完成后是否保留绘图缓冲区,默认 false
let gl = drawing.getContext('webgl', { alpha: false });
某些浏览器可能会在创建 WebGL 上下文时抛出错误,所以可以把这个方法包装在 try/catch 中
1、常量
在 OpenGL 中有各种以 GL_开头的常量,假设”GL_COl“,但是在 WebGL 中需要这样访问:gl.COl
;这种方式支持大部分 OpenGL 常量
2、方法命名
OpenGL 中很多方法会包含相关数据类型信息,通常通过后缀来体现;数字(1-4)优先,数据类型(”i“——整数,”f“——浮点数)在后;比如:gl.uniform4f();//表示需要四个浮点数参数 gl.uniform3i();//表示需要三个整数参数
还有方法接收数组,用“v”表示,例如:gl.uniform3iv();//表示接收一个包含三个值的数组参数
3、准备绘图
准备使用 WebGL 上下文之前,通常要先指定一种实心颜色清除 canvas;为此要调用 clearColor()方法并传入 4 个参数:红色值、绿色值、蓝色值、透明度值(都必须是 0-1 范围内的值)
4、视口与坐标
绘图前还需要定义 WebGL 视口。默认情况,视口使用整个 canvas 区域。可以调用 viewport()方法并传入视口相对于 canvas 元素的 x、y 坐标及宽度和高度;这个方法的 x、y 坐标原点(0, 0)表示 canvas 的左下角
在视口中,坐标原点(0, 0)是视口的中心点,左下角是(-1, -1),右上角是(1, 1);如果绘图中坐标超出了,则超出部分不显示,会被剪切
5、缓冲区
在 js 中,顶点信息保存在定型数组中,要使用这些信息,必须把他们先转换为 WebGL 缓冲区
创建缓冲区要调用 gl.createBuffer()方法,并使用 gl.bindBuffer()方法将缓冲区绑定到 WebGL 上下文
let buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_buffer, new Float32Array([0, 0.5, 1]), gl.STATIC_DRAW);
调用 gl.bindBuffer()将 buffer 设置为上下文的当前缓冲区,之后所有的缓冲区操作都在 buffer 上直接执行;所以 gl.bufferData()虽然没有包含对 buffer 的引用,但是也是在它上面执行的
上面最后一行代码使用了一个 Float32Array(通常保存了所有顶点信息)初始化了 buffer;如果要输出缓冲区内容,使用 drawElement()方法并传入 gl.ELEMENT_ARRAY_BUFFER
gl.bufferData()方法的最后一个参数表示如何使用缓冲区,可以是以下常量值:
gl.STATIC_DRAW:数据加载一次,可以在多次绘制时使用
gl.STREAM_DRAW:数据加载一次,只能在几次绘制中使用
gl.DYNAMIC_DRAW:数据可以重复更改,在多次绘制中使用
除非是很有经验的 OpenGL 程序员,否则我们需要对大多数缓冲区使用 gl.STATIC_DRAW
缓冲区会一直驻留在内存中,直到页面卸载;如果不需要缓冲区,最好调用 gl.deleteBuffer()方法释放内存:gl.deleteBuffer(buffer);
6、错误
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.NO_ERROR
7、着色器
着色器时 OpenGL 中另一个概念,WebGL 中有两种着色器:顶点着色器、片段着色器
着色器用于把 3D 顶点转换为可以渲染的 2D 顶点,它是用 GLSL(OpenGL Shading Language)语言写的
编写着色器
每个着色器都有一个 main()方法,在绘制期间重复运行,给着色器传递数据的方法有两种:attribute 和 uniform,attribute 用于将顶点传入顶点着色器,uniform 用于将常量值传入任何着色器;这两个值是在 main 函数外部定义的,后面紧跟数据类型和变量名
attribute vec2 aVertexPosition;void main() { gl_Position = vec4(aVertexPosition, 0.0, 1.0);}
定义了一个名为 aVertexPosition 的 attribute,这个 attribute 是一个包含两项的数组,代表 x、y 坐标;即使只传两个坐标,顶点着色器返回值也会包含四个元素,存在变量 gl_Position 中
片段着色器通过 uniform 传入数据
uniform vec4 uColor;void main() { gl_FragColor = uColor;}
着色器必须返回一个值,保存在 gl_FragColor 中,这个值表示绘制时使用的颜色
OpenGl 着色器语言比示例要复杂,这里只是简单介绍
创建着色器程序
浏览器并不能理解原生 GLSL 代码,所以因此 GLSL 代码字符串必须经过编译并链接到一个着色器程序中
为了便于使用,通常使用带有自定义 type 属性的 script 元素把着色器代码包含在网页中,如果 type 无效则浏览器不会解析 script 其中的内容,但是我们还是可以读写其中的字符串(type="x-webgl/x-vertex-shader"或 type="x-webgl/x-fragment-shader")
通过 text 提取 script 元素中的内容,然后创建 shader 对象,需要调用 gl.createShader()方法,并传入想创建的着色器类型(gl.VERTEX_SHADER 或 gl.FRAGMENT_SHADER),然后调用 gl.shaderSource(shader 对象, GLSL 代码)方法把 GLSL 代码应用到着色器,再调用 gl.compileShader(shader 对象)编译着色器
接着把这两个对象链接到着色程序:
let program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
第一行创建一个程序,然后 attachShader()添加着色器,调用 linkProgram()将两个着色器链接到变量 program 中,最后就可以通过 useProgram()方法让 WebGL 上下文使用这个程序了:
gl.useProgram(program);
调用这个函数后,所有的后续绘制操作都会使用这个程序
给着色器传值
前面定义的着色器都需要传入一个值,才能完成工作;对于 uniform,可以调用 gl.getUniformLocation()方法,返回一个对象,表示 uniform 在内存中的位置,然后使用它来赋值,如:
let uColor = gl.getUniformLocation(program, "uColor");
gl.uniform4fv(uColor, [0, 0, 0, 1]);
attribute 也一样,如:
let aVertexPosition = gl.getAttributeLocation(program, "aVertexPosition");
gl.enableVertexAttribArray(aVertexPosition);
gl.vertexAttribPointer(aVertexPosition, itemSize, gl.FLOAT, false, 0, 0);
最后创建了一个指向调用 gl.bindBuffer()指定的缓冲区的指针,并保存在 aVertexPosition 中,可以在后面由顶点着色器使用
调试着色器和程序
着色操作可能失败,而且是静默失败,必须手动通过 WebGL 上下文获取着色器或程序信息
通过 gl.getShaderParameter()方法取得编译后的状态,如果着色器编译成功则会返回 true,否则返回 false,此时可以调用 gl.getShaderInfoLog()并传入着色器取得错误,返回一个字符串,表示问题所在(它可以用于顶点着色器或者片段着色器)
通过 gl.getShaderParameter()方法还可以检测链接阶段(相关检测查看红宝书 p575)
还有 getProgramParameter()(与 getShaderParameter()方法一样返回 true 或 false),同样也有 getProgramInfoLog()方法
GLSL 100 升级到 GLSL 300
WebGL 2 主要是升级到了 GLSL 3.00 ES 着色器,这个升级有很多新着色器功能暴露,要使用这个升级版着色器,着色器第一行代码必须是:#version 300 es
这个升级包括了一些语法变化:
顶点 attribute 变量要使用 in 而不是 attribute 关键字
使用 varying 关键字为顶点或片段着色器声明的变量,现在必须根据相应的着色器行为改写为使用 in 或 out
预定义的输出变量 gl_FragColor 没有了,片段着色器必须为颜色输出声明自己的 out 变量
纹理查找函数 texture2D 和 textureCube 统一成了一个 texture 函数
8、绘图
WebGl 绘图只能绘制三种形状:点、线、三角形
WebGL 绘图要使用:drawArrays()和 drawElements()方法,前者使用数组缓冲区,后者则操作元素数组缓冲区
这两个函数第一个参数都表示要绘制图形的常量:
gl.POINTS:将每个顶点当成一个点来绘制
gl.LINES:将数组作为一系列顶点,在这些顶点间绘制直线,每个顶点是起点也是终点,因此数组中的顶点必须是偶数个才能开始绘制
gl.LINE_LOOP:将数组作为一系列顶点,从第一个顶点链接第二个顶点,依次链接直到最后一个顶点链接第一个顶点
gl.LINE_STRIP:和上一个常量差不多,只是不会从最后一个顶点链接第一个顶点
gl.TRIANGLES:将数组作为一系列顶点,在这些顶点间绘制三角形(不特殊指定的话,每个三角形都分开绘制,不共享顶点)
gl.TRIANGLES_STRIP:与上一个常量相似,只不过前三个顶点之后的顶点会作为第三个顶点和前两个顶点组成三角形
gl.TRIANGLES_FAN:与上一个常量相似,只不过前三个顶点后的顶点会作为第三个顶点与前面的顶点和第一个顶点构成三角形
gl.drawArrays()第二个参数是数组缓冲区的起点索引,第三个参数是数组缓冲区包含的顶点集合数量
9、纹理
WebGL 纹理可以使用 DOM 中的图片,使用 gl.createTexture()创建新的纹理,然后再将图片绑定到这个纹理;图片加载后才能使用纹理
gl.pixelStorei()设置了像素储存格式
图片纹理必须跟当前页面同源
例子查看红宝书 p578
10、读取像素
可以通过 readPixels()方法与 OpenGL 中方法有相同的参数,只不过最后一个参数必须是定型数组
该方法参数:x、y 坐标、宽度、高度、图像格式、类型、定型数组,前四个参数用于指定要读取像素位置,图像格式基本总是 gl.RGBA,类型参数指的是要存储在定型数组中的数据类型(gl.UNSIGNED_BYTE——Uint8Array,gl.UNSIGNED_SHORT_5_6_5/gl.UNSIGNED_SHORT_4_4_4_4/gl.UNSIGNED_SHORT_5_5_5_1——Uint16Array)
例子查看红宝书 p578
WebGL1 与 WebGL2
WebGL1 代码几乎与 WebGL2 完全兼容
相关对比查看红宝书 p579