更新于 

绘制一个点

HelloPoint1

着色器(shader)

如果要在绘图区域中心绘制一个红点,
理论上只需要两步:

  1. 指定点的颜色
  2. 指定点的位置和大小
1
2
gl.drawColor(1.0, 0.0, 0.0, 1.0);
gl.drawPoint(0, 0, 0, 10);

不幸的是,WebGL依赖一种称为着色器(shader)的绘图机制,
shader强大且复杂,仅用一条简单绘图命令无法操作。

HelloPoint1 Code
点绘效果
点绘效果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="./HelloPoint1.js"></script>
<script src="../examples/lib/webgl-debug.js"></script>
<script src="../examples/lib/webgl-utils.js"></script>
<script src="../examples/lib/cuon-utils.js"></script>
</head>
<body onload="main()">
<canvas id="webgl" width="400" height="400"></canvas>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 顶点着色器程序
var VSHADER_SOURCE =
`
void main(){
gl_Position = vec4(0.0,0.0,0.0,1.0); //设置坐标
gl_PointSize = 10.0; //设置尺寸
}
`;
//片源着色器程序
var FSHADER_SOURCE =
`
void main(){
gl_FragColor = vec4(1.0, 0.0 ,0.0 ,1.0); //设置颜色
}
`;
function main() {
// 获取canvas元素
var canvas = document.getElementById('webgl');
// 获取WebGL绘图上下文
var gl = getWebGLContext(canvas);
if (!gl) {
console.log('绘图上下文获取失败');
return;
}
// 初始化着色器
if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
console.log('着色器初始化失败');
return;
}
// 设置画板背景色
gl.clearColor(0.0, 0.0, 0.0, 1.0);
// 清空画板
gl.clear(gl.COLOR_BUFFER_BIT);
// 绘制点
gl.drawArrays(gl.POINTS, 0, 1);
}

什么是着色器?

What is shader?
Vertex shader
  • 描述范围: 顶点特性(如位置、颜色等)
  • 顶点(vertex): 二维或三维空间中的一个点(如端点、交点等)
Fragment shader
  • 描述范围: 进行逐片元处理过程(如光照等)
  • 片元(fragment): 类似于像素(图像的单元)

shader程序以String的形式嵌入在JavaScript文件中。

Browser执行JavaScript的绘制流程
Browser执行JavaScript的绘制流程
简化版流程
简化版流程
使用shader的WebGL程序的结构
Vertex shader program
1
2
3
4
5
6
7
var VSHADER_SOURCE =
`
void main(){
gl_Position = vec4(0.0,0.0,0.0,1.0);
gl_PointSize = 10.0;
}
`;
Fragment shader program
1
2
3
4
5
6
var FSHADER_SOURCE =
`
void main(){
gl_FragColor = vec4(1.0,0.0,0.0,1.0);
}
`;
Main program
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function main() { 
let canvas = document.getElementById('webgl');
let gl = getWebGLContext(canvas);
if (!gl) {
console.log('获取上下文失败');
return;
}
if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
console.log('着色器初始化失败');
return;
}
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.POINTS, 0, 1);
}
步骤分解
1.获取canvas元素
1
let canvas = document.getElementById('webgl');
2.获取WebGL绘图上下文
1
let gl = getWebGLContext(canvas);
3.初始化着色器
1
initShaders(gl,VSHADER_SOURCE,FSHADER_SOURCE);
initShaders

该函数被定义在 cuon.util.js 中。

initShaders()的行为
initShaders()的行为
上图有几点需要重点注意的:
  • 着色器运行在WebGL系统中,而不是JavaScript系统中。
  • 片元着色器接收到的是经过光栅化处理后的片元值。
  • WebGL程序包括运行于Browser的JS运行于WebGL的shader两个部分
4.设置canvas背景色
1
gl.clearColor(0.0,0.0,0.0,1.0);
5.清除canvas
1
gl.clear(gl.COLOR_BUFFER_BIT);
6.绘图
1
gl.drawArrays(gl.POINTS,0,1)
初始化shader后如何绘图?

要画一个点,你需要3个关键信息:

  • 位置
  • 尺寸
  • 颜色
顶点着色器

shader程序本身必须包含一个 main 函数。

注意:gl_Position必须赋值,gl_PointSize可选,默认值为1.0

顶点着色器的内置变量
顶点着色器的内置变量
顶点着色器内置变量对应的GLES数据类型
顶点着色器内置变量对应的GLES数据类型
float

表示浮点数,因此必须使用浮点形式的数字进行赋值。
这样写就会出错:

1
gl_PointSize = 10;
vec4

矢量(vector),表示由4个浮点数组成的矢量,
shader提供了内置函数 vec4() 创建vec4类型的变量。

齐次坐标

有4个分量组成的矢量被称为齐次坐标

片元着色器

片元就是显示在屏幕上的一个像素。
gl_FragColor 是片元着色器唯一的内置变量。

绘制操作

drawArrays() 可以用来绘制各种图形。

1
gl.drawArrays(gl.POINTS,0,1);
  • 第一个参数(gl.POINTS):表示绘制单点
  • 第二个参数(0):从第1个顶点开始绘制
  • 第三个参数(1):表示仅绘制1个点

一旦Vertex_shader执行完毕,Fragment_shader就开始执行

WebGL坐标系统

WebGL使用的是三维坐标系统(笛卡尔坐标系)

WebGL坐标系
WebGL坐标系
右手坐标系

WebGL默认使用右手坐标系,
但实际上,WebGL本身不是左手也不是右手坐标系,要复杂得多。

右手坐标系
右手坐标系
坐标映射

需要将WebGL的坐标系映射到canvas绘图区中。
坐标对应关系如下:

WebGL坐标canvas坐标
( 0.0, 0.0, 0.0)中心点
(-1.0, 0.0, 0.0)左边缘
( 1.0, 0.0, 0.0)右边缘
( 0.0, -1.0, 0.0)下边缘
( 0.0, 1.0, 0.0)上边缘
WebGL和canvas坐标映射关系
WebGL和canvas坐标映射关系

HelloPoint2

对比版本1和版本2
  • 版本1: 点位硬编码在Vertex Shader中
  • 版本2: 点位坐标从JS传到Shader程序中
attribute与uniform

要实现从JavaScript程序向Vertex_Shader传输位置信息,
有两种方式可以实现(使用哪一个取决于需传输的数据本身):

attribute变量

传输的是那些与顶点相关的数据。

uniform变量

传输的是那些与顶点无关的数据。

使用attribute变量

attribute变量实际上是一种 GLSL ES 变量,
功能就是被用来 从外部向Vertex_Shader内部传输数据

使用attribute需要经过以下步骤:
  1. 声明: 在Vertex_Shader中声明attribute变量。
  2. 赋值: 将attribute变量赋值给gl_Position变量。
  3. 传输: 向attribute变量传输数据。
HelloPoint2 Code
HelloPoint2运行结果
HelloPoint2运行结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="../examples/lib/webgl-debug.js"></script>
<script src="../examples/lib/webgl-utils.js"></script>
<script src="../examples/lib/cuon-utils.js"></script>
<script src="./HelloPoint2.js"></script>
</head>
<body onload="main()">
<canvas id="webgl" width="400" height="400"></canvas>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// 顶点着色器
var VSHADER_SOURCE =
`
attribute vec4 a_Position;
void main(){
gl_Position = a_Position;
gl_PointSize = 10.0;
}
`;
// 片源着色器
var FSHADER_SOURCE =
`
void main(){
gl_FragColor = vec4(0.0,1.0,0.0,1.0);
}
`;
function main() {
// 获取canvas元素
var canvas = document.getElementById('webgl');

// 获取WebGL上下文
var gl = getWebGLContext(canvas);
if (!gl) {
console.log('获取上下文失败');
return;
}

// 初始化着色器
if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
console.log('初始化着色器失败');
return;
}

// 获取attribute变量的存储位置
var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
if (a_Position < 0) {
console.log('Failed to get the storage location of a_Position');
return;
}

// 将顶点位置传输给attribute变量
gl.vertexAttrib3f(a_Position, 0.0, 0.0, 0.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);
}

attribute使用步骤详解
步骤一:声明

attribute又称 存储限定符 (storage qualifier)。
attribute变量必须声明成全局变量
attribute变量声明格式如下:

<存储限定符> <类型> <变量名>

attribute变量声明格式
attribute变量声明格式
步骤二:获取

每个变量都具有一个存储地址
以便通过存储地址向变量传输数据

getAttribLocation()

用于获取attribute变量的地址。
其接收2个参数:

参数1:程序对象(program object)

只要引用gl.program即可,
包括了Vertex_Shader和Frag_Shader,
initShader() 函数创建,
因此必须在initShader函数调用之后访问。

参数2:attribute变量的存储地址

步骤三:赋值

如果对于vec4类型的变量通过vertexAttrib3f传参,
自然会省略掉1个参数,则默认为1.0

gl.vertexAttrib3f()同族函数

gl.vertextAttrib[1-4]f有一系列函数,
专门负责从JavaScript向Vertex_Shader中的attribute变量传参。

gl.vertextAttrib[1-4]f也提供矢量版本gl.vertextAttrib[1-4]fv

  • 名字以v结尾
  • 接受类型化数组参数
1
2
var position = new Float32Array([1.0, 2.0, 3.0, 1.0]);
gl.vertexAttrib4fv(a_Position, position);
WebGL函数命名规范

OpenGL ES 2.0一致。
由三个部分组成:

  • 基础函数名
  • 参数个数
  • 参数类型