webgl 着色器

webgl 着色器

要使用 WebGL 进行绘图就必须使用着色器。
WebGL 需要两种着色器。

  • 顶点着色器(Vertex shader)∶顶点着色器是用来描述顶点特性(如位置、颜色等)
的程序。顶点(vertex)是指二维或三维空间中的一个点,比如二维或三维图形的端点或交点。
  • 片元着色器(Fragment shader)∶进行逐片元处理过程如光照(的程序。片元(fragment)是一个WebGL术语,你可以将其理解为像素(图像的单元)。

顶点着色器

顶点着色器工作流程

示例代码

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>


<script id="vertexShader" type="x-shader/x-vertex">
attribute vec3 a_position;
void main(){
gl_Position = vec4(a_position,1.0);
gl_PointSize = 10.0;
}
</script>

<script id="fragmentShader" type="x-shader/x-fragment">
void main(){
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
</script>

</head>
<body>

<canvas id='glcanvas' width='600' height='600'></canvas>


<script>
var canvas = document.getElementById('glcanvas');
var gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');


//顶点着色器源码
var vertexShaderSource = document.getElementById('vertexShader').innerText;

//片元着色器源码
var fragShaderSource = document.getElementById('fragmentShader').innerText;

//创建顶点着色器对象
var vShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vShader, vertexShaderSource); //引入顶点着色器源代码
gl.compileShader(vShader); ////编译顶点着色器

//创建片元着色器对象
var fShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fShader, fragShaderSource); //引入片元着色器源代码
gl.compileShader(fShader); //编译片元着色器


var shaderProgram = gl.createProgram(); //创建程序对象program

//附着顶点着色器和片元着色器到program
gl.attachShader(shaderProgram, vShader);
gl.attachShader(shaderProgram, fShader);

gl.linkProgram(shaderProgram); // 链接program
gl.useProgram(shaderProgram); // 使用program


var vertices = [
0.0, 0.5,
-0.5, -0.5,
0.5, -0.5
];


var vertex_buffer = gl.createBuffer(); // 创建一个空的缓冲区对象来存储顶点缓冲区
gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer); // 绑定缓冲区对象
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW); // 将顶点数据传递到缓冲区


//清空画板
gl.clearColor(0.0, 0.0, 0.0, 1.0); //设置清空颜色缓冲时的颜色
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);



var a_position = gl.getAttribLocation(shaderProgram, "a_position");

//这里是设置获取数据的规则 第二个参数3 表示每个点定点有3个数据(对应vec3)
// 顶点着色器 会执行3次
gl.vertexAttribPointer(a_position, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(a_position); //激活属性。不激活的属性是不会被使用的


//开始画图
//3 表示要画 3 个点
gl.drawArrays(gl.POINTS, 0, 3)
</script>

</body>
</html>

image-20210923113523143

一、在缓冲区对象中保存顶点数据

1
2
3
4
5
6
7
8
9
10
var vertices = [
0.0, 0.5,
-0.5, -0.5,
0.5, -0.5
];


var vertex_buffer = gl.createBuffer(); // 创建一个空的缓冲区对象来存储顶点缓冲区
gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer); // 绑定缓冲区对象
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW); // 将顶点数据传递到缓冲区

二、将缓冲区对象分配给 shader中的 attribute 变量

1
2
3
4
5
6
var a_position = gl.getAttribLocation(shaderProgram, "a_position");

//这里是设置获取数据的规则 第二个参数3 表示每个点定点有3个数据(对应vec3)
// 顶点着色器 会执行3次
gl.vertexAttribPointer(a_position, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(a_position); //激活属性。不激活的属性是不会被使用的

gl.vertexAttribPointer(a_position, 2, gl.FLOAT, false, 0, 0); 表示每次取 缓冲区的两个值传给 顶点着色器的 a_position 变量

三、开始绘制图形

gl.drawArrays(gl.POINTS, 0, 3)

image-20210923144840282

由于在绘制单个的点,第1个参数 mode 是g1.POINTs

设置第2个参数frst为0,表示从缓冲区中的第1个坐标开始画起

设置第3个参数count为3,表示准备绘制3个点

image-20210923124705234

多个缓存对象

示例代码:

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
<html>
<head>
<meta charset="utf-8">
<title></title>


<script id="vertexShader" type="x-shader/x-vertex">
attribute vec3 a_position;
attribute float a_pointSize;
void main(){
gl_Position = vec4(a_position,1.0);
gl_PointSize = a_pointSize;
}
</script>

<script id="fragmentShader" type="x-shader/x-fragment">
void main(){
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
</script>

</head>
<body>

<canvas id='glcanvas' width='600' height='600'></canvas>


<script>
var canvas = document.getElementById('glcanvas');
var gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');


//顶点着色器源码
var vertexShaderSource = document.getElementById('vertexShader').innerText;

//片元着色器源码
var fragShaderSource = document.getElementById('fragmentShader').innerText;

//创建顶点着色器对象
var vShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vShader, vertexShaderSource); //引入顶点着色器源代码
gl.compileShader(vShader); ////编译顶点着色器

//创建片元着色器对象
var fShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fShader, fragShaderSource); //引入片元着色器源代码
gl.compileShader(fShader); //编译片元着色器


var shaderProgram = gl.createProgram(); //创建程序对象program

//附着顶点着色器和片元着色器到program
gl.attachShader(shaderProgram, vShader);
gl.attachShader(shaderProgram, fShader);

gl.linkProgram(shaderProgram); // 链接program
gl.useProgram(shaderProgram); // 使用program



//清空画板
gl.clearColor(0.0, 0.0, 0.0, 1.0); //设置清空颜色缓冲时的颜色
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);


var vertices = new Float32Array([
0.0, 0.5, -0.5, -0.5, 0.5, -0.5
]);
var n = 3;

var sizes = new Float32Array([
10.0, 20.0, 30.0 // Point sizes
]);



var vertex_buffer = gl.createBuffer(); // 创建一个空的缓冲区对象来存储顶点缓冲区
gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer); // 绑定缓冲区对象
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW); // 将顶点数据传递到缓冲区

//绑定 a_position
var a_position = gl.getAttribLocation(shaderProgram, "a_position");
gl.vertexAttribPointer(a_position, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(a_position);



var size_buffer = gl.createBuffer(); // 创建一个空的缓冲区对象来存储尺寸信息
gl.bindBuffer(gl.ARRAY_BUFFER, size_buffer); // 绑定缓冲区对象
gl.bufferData(gl.ARRAY_BUFFER, sizes, gl.STATIC_DRAW); // 将顶点数据传递到缓冲区


//绑定 a_pointSize
var a_pointSize = gl.getAttribLocation(shaderProgram, "a_pointSize");
gl.vertexAttribPointer(a_pointSize, 1, gl.FLOAT, false, 0 , 0);
gl.enableVertexAttribArray(a_pointSize);


//开始画图
//3 表示要画 3 个点
gl.drawArrays(gl.POINTS, 0, 3)
</script>

</body>
</html>

上述代码比较容易理解,就是分别创建了一个缓存来存储顶点数据 和 一个缓存来存储 顶点的尺寸信息

image-20210923170000162

一个缓冲区存多个数据

可以通过设置 gl.vertexAttribPointer( ) 的 stride ( 步进) 和 offset ( 偏移 ) 参数
 ,把多个要传到 attribute 的数据放到同一个 缓冲区。比如,可以将顶点的坐标和尺寸数据按照如下方式交错组织。

1
2
3
4
5
6
var verticesSizes = new Float32Array([
// Coordinate and size of points
0.0, 0.5, 10.0, // the 1st point
-0.5, -0.5, 20.0, // the 2nd point
0.5, -0.5, 30.0 // the 3rd point
]);

示例代码:

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
<html>
<head>
<meta charset="utf-8">
<title></title>


<script id="vertexShader" type="x-shader/x-vertex">
attribute vec3 a_position;
attribute float a_pointSize;
void main(){
gl_Position = vec4(a_position,1.0);
gl_PointSize = a_pointSize;
}
</script>

<script id="fragmentShader" type="x-shader/x-fragment">
void main(){
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
</script>

</head>
<body>

<canvas id='glcanvas' width='600' height='600'></canvas>


<script>
var canvas = document.getElementById('glcanvas');
var gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');


//顶点着色器源码
var vertexShaderSource = document.getElementById('vertexShader').innerText;

//片元着色器源码
var fragShaderSource = document.getElementById('fragmentShader').innerText;

//创建顶点着色器对象
var vShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vShader, vertexShaderSource); //引入顶点着色器源代码
gl.compileShader(vShader); ////编译顶点着色器

//创建片元着色器对象
var fShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fShader, fragShaderSource); //引入片元着色器源代码
gl.compileShader(fShader); //编译片元着色器


var shaderProgram = gl.createProgram(); //创建程序对象program

//附着顶点着色器和片元着色器到program
gl.attachShader(shaderProgram, vShader);
gl.attachShader(shaderProgram, fShader);

gl.linkProgram(shaderProgram); // 链接program
gl.useProgram(shaderProgram); // 使用program


var verticesSizes = new Float32Array([
// Coordinate and size of points
0.0, 0.5, 10.0, // the 1st point
-0.5, -0.5, 20.0, // the 2nd point
0.5, -0.5, 30.0 // the 3rd point
]);


var vertex_buffer = gl.createBuffer(); // 创建一个空的缓冲区对象来存储顶点缓冲区
gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer); // 绑定缓冲区对象
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(verticesSizes), gl.STATIC_DRAW); // 将顶点数据传递到缓冲区


//清空画板
gl.clearColor(0.0, 0.0, 0.0, 1.0); //设置清空颜色缓冲时的颜色
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

var FSIZE = verticesSizes.BYTES_PER_ELEMENT;


//绑定 a_position
var a_position = gl.getAttribLocation(shaderProgram, "a_position");
gl.vertexAttribPointer(a_position, 2, gl.FLOAT, false, FSIZE * 3, 0);
gl.enableVertexAttribArray(a_position);

//绑定 a_pointSize
var a_pointSize = gl.getAttribLocation(shaderProgram, "a_pointSize");
gl.vertexAttribPointer(a_pointSize, 1, gl.FLOAT, false, FSIZE * 3, FSIZE * 2);
gl.enableVertexAttribArray(a_pointSize);


//开始画图
//3 表示要画 3 个点
gl.drawArrays(gl.POINTS, 0, 3)
</script>

</body>
</html>

绑定 a_positiona_pointSize 的核心代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var verticesSizes = new Float32Array([
// Coordinate and size of points
0.0, 0.5, 10.0, // the 1st point
-0.5, -0.5, 20.0, // the 2nd point
0.5, -0.5, 30.0 // the 3rd point
]);

var FSIZE = verticesSizes.BYTES_PER_ELEMENT; // 代表了强类型数组中每个元素所占用的字节数

//绑定 a_position
var a_position = gl.getAttribLocation(shaderProgram, "a_position");
gl.vertexAttribPointer(a_position, 2, gl.FLOAT, false, FSIZE * 3, 0);

//绑定 a_pointSize
var a_pointSize = gl.getAttribLocation(shaderProgram, "a_pointSize");
gl.vertexAttribPointer(a_pointSize, 1, gl.FLOAT, false, FSIZE * 3, FSIZE * 2);

gl.getAttribLocation 的参数如下:

image-20210923160601162

在前面的示例程序中,缓冲区只含有一一种数据,即顶页点的坐标,所以将stride 和 offset 设置为0 即可。然而,在本例中,当缓冲区中有了多种数据(比如此例中的顶点坐标和顶点尺寸)时,我们就需要考虑 stride 和 offset 的值

image-20210923161509040

如图所示:

stride 指定相邻两个顶点间的字节数 。每一个顶点有3个数据值(两个坐标数据和一个尺寸数据),默认为 0
 。因此stride 应该设置为每项数据大小的三倍,即3×FSIZE(F1loat32Array中每个元素所占的字节数 )。


offset 表示当前考虑的数据项距离首个元素的距离(单位是字节),即偏移参数。在verticessizes数组中,表示图形端点的坐标数据是前面两个,所以offset 应当为0 , gl.vertexAttribPointer(a_position, 2, gl.FLOAT, false, FSIZE * 3, 0); 表示图形端点尺寸的是第三个数,所以offset 应当为 FSIZE * 2gl.vertexAttribPointer(a_pointSize, 1, gl.FLOAT, false, FSIZE * 3, FSIZE * 2);

image-20210923164209146


webgl 着色器
http://example.com/2024/05/23/webgl/webgl 着色器/
Author
John Doe
Posted on
May 23, 2024
Licensed under