前言
WebGL(Web Graphics Library),是一项用来在网页上绘制和渲染复杂三维图形(3D图形),并允许用户与之进行交互的技术。
WebGL的起源:
WebGl 根植于 OpenGL, 是由 OpenGL 的一个特殊版本 OpenGL ES 中派生出来的。
如下图所示:从2.0版本开始,OpenGL支持了 可编程着色器(programmable shader functions)。该特性被 OpenGL ES 2.0 所继承,并成为了 WebGL 1.0 标准的核心部分。
(该图源于《WebGL编程指南》)
着色器方法(着色器) 使用一种类似于C的编程语言实现了精美的视觉效果。编写着色器的语言称为着色器语言(shading language) 。OpenGL ES 2.0 基于 OpenGL 着色器语言(GLSL),因此后者又被称为 OpenGL ES 着色器语言(GLSL ES)。 WebGL 基于 OpenGL ES 2.0,也使用 GLSL ES 编写着色器。
OpenGL 规范的更新和标准化由 Khronos 组织负责。2009 年, Khronos 建立了 WebGL 工作小组,开始基于 OpenGL ES 着手建立 WebGL 规范, 并且于 2011 年发布了 WebGL 规范的第一个版本。
WebGL 程序的结构
(该图源于《WebGL编程指南》)
一、HTML Canvas
WebGL 采用 HTML5 中新引入的 <canvas>
元素(标签),它定义了网页上的绘图区域。如果没有 WebGL ,JavaScript 只能在 <canvas>
上绘制二维图形, 有了 WebGL 就可以绘制三维图形。那么什么是 <canvas>
元素呢?
1.1 Canvas 简介
<canvas>
是一个可以使用脚本语言(通常为JavaScript)来绘制图形的 HTML 元素。
<canvas>
标签通常会设置两个属性:width
和 height
, 用于设置画布的宽高。如果不设置的话,画布默认宽度为 300px, 高度为 150px。
当然<canvas>
元素也可以像其他块级元素一样设置 margin
、border
、background
等属性。
<canvas id="gl-canvas" width="640" height="480">
Your browser not support canvas
</canvas>
当浏览器不支持 Canvas 时,浏览器会渲染<canvas>
标签中包含的内容
1.2 Canvas 的基本使用
通常使用JavaScript在Canvas中绘制图形分为以下几步:
- 获取 canvas 元素
- 获取渲染上下文
- 在渲染上下文中调用相应的绘图函数,以绘制二维图形。
代码示例如下:
假设HTML页面上定义了如下<canvas>
标签:
<canvas id="test" width="640" height="480">
Your browser not support canvas
</canvas>
JavaScript脚本中使用Canvas绘制图形如下:
function draw() {
// 获取 canvas 元素
const canvas = document.getElementById('test');
// 判断浏览器是否支持
if (canvas.getContext) {
// 获取绘制图形的渲染上下文
const ctx = canvas.getContext('2d');
// 绘制图形
}
}
1.3 第一个 WebGL 程序
// 从这里开始
function main() {
const canvas = document.querySelector("#glcanvas");
// 初始化 WebGL 上下文
const gl = canvas.getContext("webgl");
// 确认 WebGL 支持性
if (!gl) {
alert("无法初始化 WebGL,你的浏览器、操作系统或硬件等可能不支持 WebGL。");
return;
}
// 使用完全不透明的黑色清除所有图像
gl.clearColor(0.0, 0.0, 0.0, 1.0);
// 用上面指定的颜色清除缓冲区
gl.clear(gl.COLOR_BUFFER_BIT);
}
gl.clearColor(red, greeen, blue, alpha)
:指定绘图区域的背景色(rgba值为 0-1.0 )gl.clear(buffer)
: 将指定缓冲区设置为预定的值,buffer的值可以为
gl.COLOR_BUFFER_BIT(颜色缓存)
gl.DEPTH_BUFFER_BIT(深度缓冲区)
gl.STENCIL_BUFFER_BIT(模板缓冲区)
二、着色器
前言中我们说到,要使用WebGl进行绘图就必须使用着色器。在代码中,着色器程序是以字符串的形式嵌入在JavaScript文件中的。
那么着色器是什么呢?
着色器是使用OpenGL ES 着色语言(GLSL)编写的程序,它携带着绘制形状的顶点信息以及构造绘制在屏幕上像素的所需数据。即:负责记录像素点的位置和颜色。
WebGL 中通常需要两种着色器:
- 顶点着色器(Vertex shader):顶点着色器是用来描述顶点特性(位置等)的程序。顶点(vertex)是指二维或三维空间中的一个点。例如:二维或三维图形的端点或交点。
- 每次渲染一个形状时,顶点着色器会在形状中的每个顶点运行。它的工作是将输入顶点从原始坐标系转换到 WebGL 使用的缩放空间 (clipspace) 坐标系,其中每个轴的坐标范围从-1.0 到 1.0,并且不考虑纵横比,实际尺寸或任何其他因素。
- 顶点着色器需要对顶点坐标进行必要的转换,在每个顶点基础上进行其他调整或计算,然后通过将其保存在由 GLSL 提供的特殊变量(我们称为 gl_Position)中来返回变换后的顶点
- 片元着色器(Fragment shader):进行逐片元处理过程的程序。(片元是WebGL的术语,可以理解为像素)
- 在顶点着色器处理完图形的顶点后,会被要绘制的每个图形的每个像素点调用一次。它的职责是确定像素的颜色,通过指定应用到像素的纹理元素(也就是图形纹理中的像素),获取纹理元素的颜色,然后将适当的光照应用于颜色。之后颜色存储在特殊变量 gl_FragColor 中,返回到 WebGL 层。该颜色将最终绘制到屏幕上图形对应像素的对应位置。
程序执行的流程大概如下:首先运行JavaScript程序,调用 WEbGL 相关方法,然后顶点着色器和片元着色器会执行,在颜色缓冲区内进行绘制,这时就清空了绘图区,最后,颜色缓冲区的内容会自动在浏览器的<canvas>
上显现出来。
2.1 顶点着色器
顶点着色器指定点的位置和尺寸。例如:
// 顶点着色器程序
var VSHADER_SOURCE =
'void main() {\n' +
' gl_Position = vec4(0.0, 0.0, 0.0, 1.0);\n' +
' gl_PointSize = 10.0; \n' +
'}\n';
如上述代码,顶点着色器的内置变量有gl_Position
和gl_PointSize
,其中gl_Position
变量必须被赋值,否则着色器无法正常工作。gl_PointSize
不是必须的,如果未赋值,会取默认值1.0.
变量 | 类型 | 描述 |
---|---|---|
gl_Position | vec4 (由四个浮点数组成的矢量) | 表示顶点位置 |
gl_PointSize | float (浮点数) | 表示点的尺寸(像素数) |
其中gl_Position
为齐次坐标,齐次坐标 (x, y, z, w) 等价于三维坐标 (x/w, y/w, z/w)。
注意:变量类型要保持一致,否则会出错
2.2 片元着色器
片段着色器控制点的颜色。
var FSHADER_SOURCE =
'void main() {\n' +
' gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n' +
'}\n';
变量 | 类型 | 描述 |
---|---|---|
gl_FragColor | vec4 (由四个浮点数组成的矢量) | 指定片元颜色(RGBA格式) |
2.3 初始化着色器
初始化着色器一般分为以下几个步骤:
- 创建着色器对象(
gl.createShader()
) - 向着色器对象中填充着色器程序的源代码(
gl.shaderSource()
) - 编译着色器(
gl.compileShader()
) - 创建程序对象(
gl.createProgram()
) - 为程序对象分配着色器(
gl.attachShader()
) - 连接程序对象(
gl.linkProgram()
) - 使用程序对象(
gl.useProgram()
)
具体代码实现如下:
function initShader(gl, vSource, fsSource) {
const program = createShaderProgram(gl, vsSource, fsSource);
if (!program) {
return false;
}
gl.useProgram(program);
gl.program = program;
return true;
}
function createShaderProgram(gl, vSource, fsSource) {
// 创建着色器对象
const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource);
const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource);
// 创建着色器程序
const shaderProgram = gl.createProgram();
// 为程序对象分配顶点着色器和片元着色器
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
// 连接着色器
gl.linkProgram(shaderProgram);
// 连接检查
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
console.log('Unable to initialize the shader program' + gl.getProgramInfoLog(shaderProgram));
return null;
}
return shaderProgram;
}
// 创建指定类型着色器,上传 source 源码并编译
function loadShader(gl, type, source) {
// 创建着色器对象
const shader = gl.createShader(type);
// 设置着色器源代码
gl.shaderSource(source);
// 编译着色器(向着色器对象传入源代码后,需要对其编译才能使用)
gl.compileShader(shader);
// 检查着色器的编译状态
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.log('An error occurred compiling the shaders: '+ gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return null;
}
return shader;
}
下面简单介绍一下其中使用的函数:
(1)gl.createShader(type) —— 创建着色器对象
创建由 type 指定的着色器
- 参数:
- type:指定创建着色器的类型
- gl.VERTEX_SHADER:顶点着色器
- gl.FRAGMENT_SHADER: 片元着色器
- type:指定创建着色器的类型
- 返回值:创建的着色器 或 null
(2)gl.deleteShader(shader) —— 删除着色器对象
删除 shader 指定的着色器对象
- 参数:
- shader:待删除的着色器对象
(3)gl.shaderSource(shader, source) —— 指定着色器对象的代码
将source 指定的字符串形式的代码传入 shader 指定的着色器。如果之前已经向 shader 传入过代码了,旧的代码会被替换。
- 参数:
- shader:指定需要传入代码的着色器对象
- source:指定字符串形式的代码
(4)gl.compileShader(shader) —— 编译着色器
编译shader 指定的着色器中的源代码
- 参数:
- shader:待编译的着色器
(5)gl.getShaderParameter(shader, pname) —— 获取着色器参数信息
获取 shader 指定的着色器, pname 指定的参数信息
- 参数:
- shader:指定待获取参数的着色器
- pname: 指定待获取参数的类型, 例如:gl.SHADER_TYPE、 gl.DELETE_STATUS、 gl.COMPILE_STATUS
- 返回值:
- gl.SHADER_TYPE:着色器类型(gl.VERTEX_SHADER 或 gl.FRAGMENT_SHADER)
- gl.DELETE_STATUS:着色器是否被删除成功
- gl.COMPILE_STATUS:着色器是否被编译成功
(6)gl.getShaderInfoLog(shader) —— 着色器编译错误日志
获取 shader 指定的着色器的信息日志
-
参数:
- shader: 指定待获取信息日志的着色器
-
返回值:
- 包含日志信息的字符串
- null:没有编译错误 (7)gl.createProgram() —— 创建程序对象
-
返回值:
- 新创建的程序对象
- null:创建失败
(8)gl.deleteProgram(program) —— 删除程序对象
删除 program 指定的程序对象,如果该程序对象正在被使用,则不立即删除,而是等它不再被使用后删除。
- *参数:
- program:指定待删除的程序对象
(9)gl.attachShader(program, shader) —— 为程序对象分配着色器对象
将shader指定的着色器对象分配给program指定的程序对象
- 参数
- program:指定程序对象
- shader:指定着色器对象
(10)gl.detachShader(program, shader)
取消 shader 指定的着色器对象对 program 指定的程序对象的分配。
(11)gl.linkProgram(program) —— 连接程序对象
连接 program 指定的程序对象中的着色器
(12)gl.getProgramParameter(program, pname) —— 获取程序对象参数信息
获取 program 指定的程序对象中, pname 指定的参数信息
- 参数:
- program:指定程序对象
- pname: 指定待获取参数的类型,
- 返回值:
- gl.DELETE_STATUS:程序是否被删除
- gl.LINK_STATUS:程序是否已经成功连接
- gl.VALIDATE_STATUS:程序是否已经通过验证
- gl.ATTACHED_SHADERS:已分配给程序的着色器数量
- gl.ACTIVE_ATTRIBUTEA:顶点着色器中 attribute 变量的数量
- gl.ACTIVE_NUIFORMS:程序中 uniform 变量的数量
(13)gl.getProgramInfoLog(program) —— 程序对象编译错误日志
获取 program 指定的程序对象的信息日志
-
参数:
- program: 指定待获取信息日志的程序
-
返回值:
- 包含日志信息的字符串
- null:没有编译错误
(14)gl.useProgram(program) —— 告知 WebGL 系统所使用的程序对象
告知 WebGL 系统绘制时使用 program 指定的程序对象
2.4 绘制操作
建立了着色器之后,就要进行绘制操作。通常使用 gl.drawArrays()
来进行绘制。
drawArrays(mode, first, count)
- 参数:
mode
:指定绘制方式。- gl.POINTS
- gl.LINES
- gl.LINE_STRIP
- gl.LINE_LOOP
- gl.TRIANGLES
- gl.TRIANGLE_STRIP
- gl.TRIANGLE_FAN
first
: 指定从哪个顶点开始绘制(整数)count
:指定绘制需要用到多少个顶点(整数)
介绍了这些,接下来就用这些知识在中心处绘制一个红色的顶点,代码如下所示:
// 顶点着色器程序
const VSHADER_SOURCE =
'void main() {\n' +
' gl_Position = vec4(0.0, 0.0, 0.0, 1.0);\n' + // 设置顶点坐标
' gl_PointSize = 10.0;\n' + // 设置顶点大小
'}\n';
const FSHADER_SOURCE =
'void main() {\n' +
' gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n' + // 设置颜色
'}\n';
function main() {
// 获取canvas元素
const canvas = document.getElementById('gl');
// 获取WebGL绘图上下文
const gl = canvas.getContext('webgl');
// 确认WebGL支持性
if (!gl) {
console.log('浏览器不支持WebGL');
return;
}
// 初始化着色器
if(!initShader(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
console.log('初始化着色器失败!');
return;
}
// 设置canvas背景色
gl.clearColor(0.0, 0.0, 0.0, 1.0);
// 情况canvas
gl.clear(gl.COLOR_BUFFER_BIT);
// 绘制一个点
gl.drawArrays(gl.POINTS, 0, 1);
}
main();
三、WebGL 坐标系统
WebGL 处理的是三维图形,所以使用的是三维坐标系统(笛卡尔坐标系),具有X轴、Y轴、Z轴。
- X轴是水平的(正方向向右)
- Y轴是垂直的(正方向向上)
- Z轴是垂直于屏幕的(正方向向外)
WebGL 的坐标系和 <canvas>
绘图区的坐标系不同:
<canvas>
的中心点:(0.0, 0.0, 0.0)<canvas>
的上边缘和下边缘:(0.0, 1.0, 0.0) 和 (0.0, -1.0, 0.0)<canvas>
的左边缘和右边缘:(-1.0, 0.0, 0.0) 和 (1.0, 0.0, 0.0)
四、attribute 变量 和 uniform 变量
前面介绍了 WebGL 绘图的基本流程,但是从二中的示例代码中可以看出,用于设置顶点位置大小以及颜色着色器语言(GLSL ES)都是以字符串的形式注册到着色器中的,可以理解为硬编码。但是这样程序缺乏可扩展性,且与JavaScript之间的交互性差。那么接下来就谈谈如何在JavaScript与着色器之间传递数据。
想要达到这一目的,通常使用的是 attribute 变量 和 uniform 变量。具体使用哪个,取决于数据本身:
- attribute 变量传输的是那些与顶点相关的数据
- uniform 变量传输的是那些对所有顶点都相同(或与顶点无关)的数据。
4.1 attribute 变量
首先我们来对上述例子做下修改,我们通过 attribute 变量来设置顶点的位置:
- 顶点着色器中声明 attribute 变量
- 将 attribute 变量赋值给 gl_Position
- JavaScript中向 attribute 变量传输数据
下面仅展示修改的代码:
const VSHADER_SOURCE =
'attribute vec4 a_Position;\n' + // 声明 attribute 变量
'void main() {\n' +
' gl_Position = a_Position;\n' + // 设置顶点坐标
' gl_PointSize = 10.0;\n' + // 设置顶点大小
'}\n';
...
function main() {
...
// 获取 attribute 变量的存储位置
const a_Position = gl.getAttribLocation(gl.program, 'a_Position');
// 判断是否获取成功
if (a_Position < 0) {
console.log('获取 a_Position 的存储位置失败!');
return;
}
// 将顶点位置传输给 attribute 变量
gl.vertexAttrib3f(a_Position, 0.0, 0.0, 0.0);
...
}
(1)attribute 关键字
'attribute vec4 a_Position;\n'
中的关键字 attribute
被称为存储限定符(storage qualifier),它表示接下来的变量是一个 attribute 变量。
- attribute 变量必须声明成全局变量,数据将从着色器外部传给该变量。
- attribute 变量必须按以下格式声明:
<存储限定符> <类型> <变量名>
, 例如:
attribute vec4 a_Position
(2)获取 attribute 变量的存储位置
每个 attribute 变量都具有一个存储地址,以便通过存储地址向变量传输数据。所以想要想顶点着色器的 attribute 变量传递数据时,首先需要向 WebGL 系统请求变量的存储地址。使用 gl.getAttribLocation()
来获取变量的地址。
gl.getAttribLocation(program, name)
: 获取由 name 参数指定的 attribute
- 参数:
program
:指定包含顶点着色器和片元着色器的程序对象name
:指定想要获取其存储地址的 attribute 变量的名称
- 返回值:
- >= 0:attribute 的变量地址
- < 0:指定的 attribute 变量不存在,或者其命名具有 gl_ 或 webgl_ 前缀。
(3)向 attribute 变量赋值
获取了 attribute 变量的存储地址后,那么接下来就可以使用 gl.vertexAttrib3f()
来向变量传值了。
gl.vertexAttrib3f(location, v0, v1, v2)
:将数据 (v0, v1, v2) 传给由 location 参数指定的 attribute 变量。
- 参数:
- location:指定将要修改的 attribute 变量的存储位置
- v0:指定填充 attribute 变量第一个分量的值
- v1:指定填充 attribute 变量第二个分量的值
- v2:指定填充 attribute 变量第三个分量的值
- 返回值:无
当然,向 attribute 变量传值不止gl.vertexAttrib3f()
一个函数,还有如下函数:
gl.vertexAttrib1f(location, v0) // 填充第 1 个分量,第 2,3 分量为0.0, 第 4 分量为1.0
gl.vertexAttrib2f(location, v0, v1) // 填充第 1,2 个分量,第 2 分量为0.0, 第 4 分量为1.0
gl.vertexAttrib3f(location, v0, v1, v2) // 填充第 1,2,3 分量, 第 4 分量为 1.0
gl.vertexAttrib4f(location, v0, v1, v2, v3) // 填充第 1,2,3,4 分量,
从函数命名的角度来看,以gl.vertexAttrib3f()
为例,其中:
- 3:表示参数个数
- f: 表示参数类型(f:浮点型, i:整型)
小结
如开头的例子:
- 首先我们在着色器程序中声明 attribute 变量
a_Position
- 然后通过
gl.getAttribLocation()
获取 attribute 变量的存储地址 - 最后使用
gl.vertexAttrib3f()
向变量赋值。
如此就是实现了在JavaScript中动态设置顶点位置,而不是静态的写在顶点着色器中。一般 attribute 变量的使用也都大致如此。
4.2 uniform 变量
4.1 中我们知道了如何在JavaScript中向顶点着色器的 attribute 变量传递数据,但是只有 attribute 变量只能用于顶点着色器,那片元着色器咋办呢。这时候就要使用 uniform 变量了。(注:也可以使用 varying 变量,这里暂不介绍)
uniform 变量的使用方式与 attribute 变量类似,如下代码:
// 顶点着色器程序
...
const FSHADER_SOURCE =
'precision mediump float;\n' + // 精度限定词,用于指定变量的范围和精度
'uniform vec4 u_FragColor;\n'+
'void main() {\n' +
' gl_FragColor = u_FragColor;\n' + // 设置颜色
'}\n';
function main() {
...
// 获取 uniform 变量的存储位置
const u_FragColor = gl.getUniformLocation(gl.program, 'u_FragColor');
// 判断是否获取成功
if (!u_FragColor) {
console.log('获取 u_FragColor 的存储位置失败!');
return;
}
// 将片元颜色传递给 uniform 变量
gl.uniform4f(u_FragColor, 1.0, 0.0, 0.0, 1.0);
// 设置canvas背景色
gl.clearColor(0.0, 0.0, 0.0, 1.0);
// 情况canvas
gl.clear(gl.COLOR_BUFFER_BIT);
// 绘制一个点
gl.drawArrays(gl.POINTS, 0, 1);
}
(1)uniform 关键字
与 attribute 变量相同,uniform 变量也需要满足以下条件
- uniform 变量必须声明成全局变量,数据将从着色器外部传给该变量。
- uniform 变量必须按以下格式声明:
<存储限定符> <类型> <变量名>
, 例如:
uniform vec4 u_FragColor
(2)获取 uniform 变量的存储位置
gl.getUniformLocation(program, name)
: 获取由 name 参数指定的 attribute
- 参数:
program
:指定包含顶点着色器和片元着色器的程序对象name
:指定想要获取其存储地址的 uniform 变量的名称
- 返回值:
- not-null:uniform 的变量地址
- null:指定的 uniform 变量不存在,或者其命名具有 gl_ 或 webgl_ 前缀。
(3)向 uniform 变量赋值
获取了 uniform 变量的存储地址后,那么接下来就可以使用 gl.uniform4f()
来向变量传值了。
gl.uniform4f(location, v0, v1, v2, v3)
:将数据 (v0, v1, v2, v3) 传给由 location 参数指定的 uniform 变量。
- 参数:
- location:指定将要修改的 uniform 变量的存储位置
- v0:指定填充 uniform 变量第一个分量的值
- v1:指定填充 uniform 变量第二个分量的值
- v2:指定填充 uniform 变量第三个分量的值
- v3:指定填充 uniform 变量第四个分量的值
- 返回值:无
当然,向 uniform 变量传值不止gl.uniform4f()
一个函数,还有如下函数:
gl.uniform1f(location, v0) // 填充第 1 个分量,第 2,3 分量为0.0, 第 4 分量为1.0
gl.uniform2f(location, v0, v1) // 填充第 1,2 个分量,第 2 分量为0.0, 第 4 分量为1.0
gl.uniform3f(location, v0, v1, v2) // 填充第 1,2,3 分量, 第 4 分量为 1.0
gl.uniform4f(location, v0, v1, v2, v3) // 填充第 1,2,3,4 分量,
五、缓冲区对象(buffer object)
前面的例子都是绘制一个点,但是如果要绘制多个点怎么办呢?最简单,那就是循环,每次循环传入一个点的位置,然后使用drawArray去绘制。但是对于三角形、矩形等由多个顶点组成的图形,需要一次性将图形的所有顶点全部传入顶点着色器,才能把图形绘制出来。
对此,WebGL提供了一种很方便的机制,即缓冲区对象(buffer object),它可以一次性地向着色器传入多个顶点的数据。
缓冲区对象是WebGL系统中的一块内存区域,可以一次性向缓冲区对象中填充大量的顶点数据,然后将这些数据保存在其中,供顶点着色器使用。
我们先看一个例子,然后再具体介绍缓冲区对象的使用:
// 初始化顶点缓冲区
function initVertexBuffer(gl) {
const vertices = new Float32Array([0.0, 0.5, -0.5, -0.5, 0.5, -0.5]);
const n = 3; // 点的个数
// 创建缓冲区对象
const vertexBuffer =gl.createBuffer();
if (!vertexBuffer) {
console.log('创建缓冲区对象失败!');
return -1;
}
// 将缓冲区对象绑定到目标
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
// 向缓冲区写入数据
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
// 获取 attribute 变量的存储位置
const a_Position = gl.getAttribLocation(gl.program, 'a_Position');
// 判断是否获取成功
if (a_Position < 0) {
console.log('获取 a_Position 的存储位置失败!');
return -1;
}
// 将缓冲区对象分配给 a_Position 变量
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);
// 连接 a_Position 变量与分配给它的缓冲区对象
gl.enableVertexAttribArray(a_Position);
return n;
}
// 顶点着色器程序
const VSHADER_SOURCE =
'attribute vec4 a_Position;\n' +
'void main() {\n' +
' gl_Position = a_Position;\n' + // 设置顶点坐标
' gl_PointSize = 10.0;\n' + // 设置顶点大小
'}\n';
const FSHADER_SOURCE =
'void main() {\n' +
' gl_FragColor = vec4(1.0, 0.0, 0.0, 0.0);\n' + // 设置颜色
'}\n';
function main() {
// 获取canvas元素
const canvas = document.getElementById('gl');
// 获取WebGL绘图上下文
const gl = canvas.getContext('webgl');
// 确认WebGL支持性
if (!gl) {
console.log('浏览器不支持WebGL');
return;
}
// 初始化着色器
if(!initShader(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
console.log('初始化着色器失败!');
return;
}
// 设置顶点位置
const n = initVertexBuffer(gl);
if (n < 0) {
console.log('设置顶点位置失败!');
return;
}
// 设置canvas背景色
gl.clearColor(0.0, 0.0, 0.0, 1.0);
// 情况canvas
gl.clear(gl.COLOR_BUFFER_BIT);
// 绘制一个点
gl.drawArrays(gl.POINTS, 0, n);
}
如上图所示,我们通过缓冲区对象,实现了一次性绘制三个点。接下来就详细了解下缓冲区对象的使用。
5.1 缓冲区对象的使用
通常缓冲器对象的使用分为以下几个步骤:
- 创建缓冲区对象(
gl.createBuffer()
) - 绑定缓冲区对象(
gl.bindBuffer()
) - 将数据写入缓冲区对象(
gl.bufferData()
) - 将缓冲区对象分配给一个 attribute 变量(
gl.vertexAttribPointer()
) - 开启 attribute 变量(
gl.enableVertexAttrtibArray()
)
(1)创建缓冲区
要使用缓冲区对象,首先必须要创建一个缓冲区对象。
gl.createBuffer()
:创建缓冲区对象
- 返回值:
- 非 null:新创建的缓冲区对象
- null:创建缓冲区对象失败
gl.deleteBuffer(buffer)
:删除参数 buffer 表示的缓冲区对象
- 参数:
buffer
:待删除的缓冲区对象
- 返回值:无
(2)绑定缓冲区
创建好缓冲区对象后,就是将缓冲区对象绑定到WebGL 系统中已经存在的目标(target)上,这个目标表示缓冲区对象的用途。
gl.bindBuffer(target, buffer)
:允许使用buffer表示的缓冲区对象并将其绑定到target表示的目标上。
- 参数:
target
:gl.ARRAY_BUFFER
:表示缓冲区对象中包含了顶点的数据。gl.ELEMENT_ARRAY_BUFFER
:表示缓冲区对象中包含了顶点的索引值
buffer
:指定之前由gl.createBuffer()
创建的缓冲区对象。如果为 null,则禁用对target 的绑定
- 返回值:无
(3)向缓冲区对象写入数据
绑定好之后,就是分配空间并写入数据。
gl.bufferData(target, data, usage)
:开辟存储空间,向绑定在 target 上的缓冲区对象中写入数据data.
- 参数:
target
:gl.ARRAY_BUFFER
或gl.ELEMENT_ARRAY_BUFFER
data
:写入缓冲区对象的数据(类型化数值)usage
:表示程序将如何使用存储在缓冲区对象中的数据(该参数用于帮助WebGL优化操作,即使传入错误的值,也不会中止程序,但是会降低程序的效率)gl.STATIC_DRAM
:只会向缓冲区对象中写入一次数据,但需要绘制很多次。gl.STREAM_DRAM
:只会向缓冲区对象中写入一次数据,然后绘制若干次。gl.DYNAMIC_DRAM
:会向缓冲区对象中多次写入数据,并绘制很多次。
- 返回值:无
由于 GLSL ES 是由C实现的,所以向缓冲区传递数据时,需要注意数据类型。对此,JavaScript中提供了类型化数组
数据类型 | 每个元素所占字节数 | C语言中对应的数据类型 |
---|---|---|
Int8Array | 1 | 8位整型数(signed char) |
UInt8Array | 1 | 8位无符号整型数(unsigned char) |
Int16Array | 2 | 16位整型数(signed short) |
UInt16Array | 2 | 16位无符号整型数(unsigned short) |
Int32Array | 24 | 32位整型数(signed int) |
UInt32Array | 4 | 32位无符号整型数(unsigned int) |
Float32Array | 4 | 单精度32位浮点数(float) |
Float64Array | 8 | 双精度64位浮点数(double) |
具体相关属性和介绍可见TypeArray —— MDN
(4)将缓冲区对象分配给 attribute 变量
缓冲区对象数据填充完后,紧接着就是分配给与 attribute 变量了。
gl.vertexAttribPointer(location, size, type, normalized, stride, offset)
:将绑定到 gl.ARRAY_BUFFER 缓冲区对象分配给由 location 指定的 attribute 变量。
- 参数:
location
:指定待分配 attribute 变量的存储位置size
:指定缓冲区中每个顶点的分量个数(1到4)。若size与attribute变量需要的分量数小,确实分量将按照与 gl.vertexAttrib[1234]f() 相同的规则补全。type
:数据格式gl.UNSIGNED_BYTE
:无符号字节,Uint8Arraygl.SHORT
:短整型,Int16ARraygl.UNSIGNED_SHORT
:无符号短整型,Uint16Arraygl.INT
:整型,Int32Arraygl.UNSINGED_INT
:无符号整型,Uint32Arraygl.FLOAT
:浮点型,FLoat32Array
normalize
:传入 true 或 false,表明是否将非浮点型的数据归一化到[0, 1] 或 [-1, 1]区间。stride
:指定相邻两个顶点间的字节数,默认位0。offset
:指定缓冲区对象中的偏移量(以字节为单位),即 attribute 变量从缓冲区中的何处开始存储。如果是从起始位置开始的,offset设为0。
- 返回值:无
(5)开启 attribute 变量
将整个缓冲区对象分配给 attribute 变量后,需要开启 attribute 变量,使缓冲区对 attribute 变量的分配生效。只有执行了这个函数后,缓冲区对象和attribute对象之间的连接才真正连接起来。
注意:开启 attribute 变量后,就不能使用
gl.vertexAttrib[1234]f()
向其传递数据了,除非显示关闭该 attribute 变量。
gl.enableVertexArray(location)
:开启 location 指定的 attribute 变量。
- 参数:
location
:指定 attribute 变量的存储位置
- 返回值:无
gl.disableVertexArray(location)
:关闭 location 指定的 attribute 变量。
- 参数:
location
:指定 attribute 变量的存储位置
- 返回值:无
小结
- WebGL(Web 图形库)是一个 JavaScript API,可在任何兼容的 Web 浏览器中渲染高性能的交互式 3D 和 2D 图形,而无需使用插件。WebGL 通过引入一个与 OpenGL ES 2.0 非常一致的 API 来做到这一点,该 API 可以在 HTML5
<canvas>
元素中使用。这种一致性使 API 可以利用用户设备提供的硬件图形加速。 - WebGL中着色器分为顶点着色器和片元着色器,初始化着色器一般分为以下几个步骤:
-
- 创建着色器对象(
gl.createShader()
)
- 创建着色器对象(
-
- 向着色器对象中填充着色器程序的源代码(
gl.shaderSource()
)
- 向着色器对象中填充着色器程序的源代码(
-
- 编译着色器(
gl.compileShader()
)
- 编译着色器(
-
- 创建程序对象(
gl.createProgram()
)
- 创建程序对象(
-
- 为程序对象分配着色器(
gl.attachShader()
)
- 为程序对象分配着色器(
-
- 连接程序对象(
gl.linkProgram()
)
- 连接程序对象(
-
- 使用程序对象(
gl.useProgram()
)
- 使用程序对象(
-
- WebGL 中的坐标系是笛卡尔坐标系,以
<canvas>
不同 - 可使用
drawArrays()
方法绘制顶点 - 可使用 attribute 变量 和 uniform 变量 在 JavaScript 中动态传递值给着色器。
以上就是对WebGL的一个简单介绍,以及WebGL绘图的基本流程,供大家参考。
最后提供一下着色器编程语言(GLSL ES)的语法规则www.khronos.org/files/openg…
参考:
[1] 《WebGL 编程指南》