webgl基础2-颜色、纹理
# 颜色
# 使用varying变量完成颜色的内插值
使用过程是在顶点着色器中定义 varying vec4 vColor;
varying表示可以变换的,把同样的变量名称定义到片元着色器中,varying vec4 vColor;
varying变量的作用是从顶点着色器向片元着色器中传递数据;
从顶点到片元进行传递数据。
// 创建着色器源码
const VERTEX_SHADER_SOURCE = `
attribute vec4 aPosition;
attribute vec4 aColor;
varying vec4 vColor;
void main() {
vColor = aColor;
gl_Position = aPosition;
}
`; // 顶点着色器
const FRAGMENT_SHADER_SOURCE = `
// 定义float类型的精度
precision lowp float;
varying vec4 vColor;
void main() {
gl_FragColor = vColor;
}
`; // 片元着色器
const points = new Float32Array([
0.0, 0.5, 1.0, 0.0, 0.0,
-0.5, -0.5, 0.0, 1.0, 0.0,
0.5, -0.5, 0.0, 0.0, 1.0,
])
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
用varying变量为三个顶点指定三个不同颜色,三角形表面上的这些颜色值都是webgl系统通过内插计算出来。
# 使用getAttribLocation获取color
上边代码已经定义好了顶点和片元着色器,之后可以获取到定义的变量值;
getAttribLocation
方法获取到定义在着色器的值
const aPosition = gl.getAttribLocation(program, 'aPosition');
const aColor = gl.getAttribLocation(program, 'aColor');
2
# vertexAttribPointer和enableVertexAttribArray给着色器变量赋值
获取到着色器变量值后,就可以给获取到的变量进行赋值
const points = new Float32Array([
-0.8, -0.8, 1.0, 0.0, 0.0,
0.8, -0.8, 0.0, 1.0, 0.0,
0.0, 0.8, 0.0, 0.0, 1.0,
])
const FSIZE = points.BYTES_PER_ELEMENT;
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
// 设置bufferData; 创建的 points 赋值给缓冲数据对象
gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW);
gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, FSIZE * 5, 0);
gl.enableVertexAttribArray(aPosition)
gl.vertexAttribPointer(aColor, 3, gl.FLOAT, false, FSIZE * 5, FSIZE * 2);
gl.enableVertexAttribArray(aColor)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 自定义颜色示例
首先在着色器代码中定义变量
获取着色器中的变量,并设置颜色值 jcode (opens new window)
# webgl颜色渲染的整体流程
- 顶点坐标:首先通过缓存数据,定义顶点数据
- 图元装配:将独立的顶点坐标装配成几何图形,图形的类别由 gl.drawArrays函数 第一个参数决定
- 光栅化:将装配好的图形,转为片元;会进行一些优化,比如背面剔除、位于可视区外图形的裁剪等
- 图形绘制:绘制到浏览器上
# 纹理
为了更逼真的模拟现实场景,需要给图形添加图片纹理。三维场景中的纹理映射,添加到图形的指定位置。
完成纹理映射需要以下4步:
- 准备好纹理图像
- 为几何图形配置纹理映射方式
- 加载纹理图像,进行配置,让其在webgl中使用
- 在片元着色器中将相应的纹素从纹理中抽取出来,并将纹素的颜色赋给片元。
# 准备几何图形
首先创建基础的矩形
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
* {
margin: 0;
padding: 0;
}
canvas {
margin: 50px auto 0;
display: block;
background: yellow;
}
</style>
</head>
<body>
<canvas id="canvas" width="400" height="400">
此浏览器不支持canvas
</canvas>
</body>
</html>
<script>
function initShader(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE) {
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(vertexShader, VERTEX_SHADER_SOURCE) // 指定顶点着色器的源码
gl.shaderSource(fragmentShader, FRAGMENT_SHADER_SOURCE) // 指定片元着色器的源码
// 编译着色器
gl.compileShader(vertexShader)
gl.compileShader(fragmentShader)
// 创建一个程序对象
const program = gl.createProgram();
gl.attachShader(program, vertexShader)
gl.attachShader(program, fragmentShader)
gl.linkProgram(program)
gl.useProgram(program)
return program;
}
const ctx = document.getElementById('canvas')
const gl = ctx.getContext('webgl')
// 创建着色器源码
const VERTEX_SHADER_SOURCE = `
// 只传递顶点数据
attribute vec4 aPosition;
void main() {
gl_Position = aPosition; // vec4(0.0,0.0,0.0,1.0)
}
`; // 顶点着色器
const FRAGMENT_SHADER_SOURCE = `
precision lowp float;
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
`; // 片元着色器
const program = initShader(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE)
const aPosition = gl.getAttribLocation(program, 'aPosition');
const points = new Float32Array([
-0.5, 0.5,
-0.5, -0.5,
0.5, 0.5,
0.5, -0.5,
])
const buffer = gl.createBuffer();
const BYTES = points.BYTES_PER_ELEMENT;
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW);
gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, BYTES * 2, 0);
gl.enableVertexAttribArray(aPosition)
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
</script>
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
展示效果
# 纹理坐标映射
纹理坐标是纹理图像上的坐标,通过纹理坐标可以在纹理图像上获取纹素颜色。webgl系统中的纹理坐标系统是二维的,为了将纹理坐标和图像屏幕坐标系统x和y坐标区分开,webgl使用s和t命名纹理坐标。
纹理图像四个角的坐标为左下(0.0, 0.0) ,右下(1.0,0.0),右上(1.0,1.0) ,左上(0.0,1.0)。纹理坐标和通用,坐标值和图像自身的尺寸无关,不管是128还是256,右上角坐标都是(1.0,1.0)
接下来需要转换图像的纹理坐标与几何形体坐标之间映射关系。
将纹理坐标的(0.0, 1.0)映射到顶点坐标(-0.5, -0.5, 0.0)上,将纹理坐标(1.0,1.0)映射到顶点坐标(0.5, 0.5, 0.0)上。依此了推,建立对应坐标映射关系。
将顶点坐标和纹理坐标写入同一个缓冲区中,定义数组points,成对记录每个顶点的顶点坐标和纹理坐标。
然后将顶点坐标和纹理坐标写入缓冲区对象,将其中的顶点坐标分配给aPosition变量并启用。
接着获取aTex变量的存储位置,将缓冲区的纹理坐标分配给该变量,并启用。
const uSampler = gl.getUniformLocation(program, 'uSampler');
调用gl.getUniformLocation(program, 'uSampler');从片元着色器中获取uniform变量uSampler的存储位置,用该变量来接收纹理对象。
# 创建纹理对象
纹理对象用于存储纹理图像的数据
const texture = gl.createTexture();
调用gl.createTexure()函数,将在webgl系统中创建一个纹理对象。gl.TEXTURE0
到gl.TEXTURE7
是管理纹理图像的8个纹理单元。每个都与gl.TEXTURE_2D相关联,而后者是绑定纹理时的纹理目标。
# 图像Y轴翻转
使用图像前,必须对它进行Y轴翻转,因为图像的y轴正方向是屏幕左上角向下,和webgl坐标系统Y轴相反
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1)
pixelStorei函数的使用:
ggl.pixelStorei(pname, param)
- pname可以是
gl.UNPACK_FLIP_Y_WEBGL
(对图像进行Y轴翻转,默认为false)或者gl.UNPACK_PERMULTIPLY__ALPHA_WEBGL
(将图像RGB颜色值得每一个分量乘以A,默认为false) - param: 指定非0或0,必须为整数。
# 激活并绑定纹理对象
webgl通过纹理单元机制来同时使用多个纹理。每个纹理单元用一个单元编号老管理一张纹理图像。即使程序只有一张纹理图像,也要指定一个纹理单元。
webgl系统至少支持8个纹理单元。
在使用纹理单元之前,需要调用gl.activeTexture()来激活它。
gl.activeTexture(gl.TEXTURE0);
接下来进行绑定纹理对象,webgl支持2中类型的纹理类型
- gl.TEXTURE_2D: 二维纹理
- gl.TEXTURE_CUBE_MAP: 立方体纹理
gl.bindTexture(gl.TEXTURE_2D, texture);
# 配置纹理对象
# 处理缩放和平铺的逻辑
配置纹理对象参数,用来设置纹理坐标获取纹素颜色、按照哪种方式重复填充纹理。使用函数gl.texParameteri()
gl.texParameteri(target, pname, param)
target: 参数为gl.TEXTURE_2D或者gl.TEXTURE_CUBE_MAP;以下示例全部使用gl.TEXTURE_2D
pname: 可以指定4个纹理参数
- 放大方法
gl.TEXTURE_MAG_FILTER
: 定义当纹理的绘制范围比纹理本身更大,如何获取纹素颜色。
- 缩小方法
gl.TEXTURE_MIN_FILTER
:定义当纹理的绘制范围比纹理本身更小,如何获取纹素颜色。 - 水平填充方法:
gl.TEXTURE_WRAP_S
:表示如何对纹理图像的左右两侧区域进行填充 - 垂直填充方法:
gl.TEXTURE_WRAP_T
:表示如何对纹理图像的上下两侧区域进行填充
- 放大方法
放大或缩小可以赋值的参数
- gl.NEAREST: 使用原纹理上距离映射新像素中心最近的那个像素颜色值,作为新像素值
- gl.LINEAR: 使用距离新像素中心最近的四个像素的颜色值得加权平均,作为新像素值。
水平或上下平铺可以赋值的参数
- gl.REPEAT: 平铺式重复纹理
- gl.MIRRORED_REPEAT: 镜像对称式的重复纹理
- gl.CLAMP_TO_EDGE: 使用纹理图像边缘值
// 处理放大缩小的逻辑
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
// 横向 纵向 平铺的方式
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
2
3
4
5
6
7
# 将纹理图像分配给纹理对象
使用gl.texImage2D()方法将纹理图像分配给纹理对象,同时该函数还允许配置WEBGL系统关于图像的一些特性。
gl.texImage2D(target, level, internalformat, format, type, image)
- target: gl.TEXTURE_2D或者gl.TEXTURE_CUBE_MAP
- level: 传入0
- internalformat: 图像的内部格式
- format:纹理数据格式,必须使用和internalformat相同值
- type: 纹理数据的类型
- image: 包含纹理图像的Image对象
// 配置纹理图像
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, img);
2
# 将纹理单元传递给片元着色器
将纹理图像传入Webgl系统,就必须将其传入片元着色器,并映射到图形的表面上。
必须将片元着色器中表示纹理对象的uniform变量声明为一种特殊的、专用的纹理对象的数据类型。程序中使用二维纹理gl.TEXTURE_2D, 所以该uniform变量的类型设为sampler2D;
- sampler2D:绑定到gl.TEXTURE_2D上的纹理数据类型
- samplerCube: 绑定到gl.TEXTURE_CUBE_MAP上的纹理数据类型
const FRAGMENT_SHADER_SOURCE = `
precision lowp float;
uniform sampler2D uSampler;
varying vec2 vTex;
void main() {
gl_FragColor = texture2D(uSampler, vTex);
}
`;
gl.uniform1i(uSampler, 0);
2
3
4
5
6
7
8
9
10
11
# 完整示例代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
* {
margin: 0;
padding: 0;
}
canvas {
margin: 50px auto 0;
display: block;
background: rgb(182, 182, 181);
}
</style>
</head>
<body>
<canvas id="canvas" width="400" height="400">
此浏览器不支持canvas
</canvas>
</body>
</html>
<script>
function initShader(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE) {
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(vertexShader, VERTEX_SHADER_SOURCE) // 指定顶点着色器的源码
gl.shaderSource(fragmentShader, FRAGMENT_SHADER_SOURCE) // 指定片元着色器的源码
// 编译着色器
gl.compileShader(vertexShader)
gl.compileShader(fragmentShader)
// 创建一个程序对象
const program = gl.createProgram();
gl.attachShader(program, vertexShader)
gl.attachShader(program, fragmentShader)
gl.linkProgram(program)
gl.useProgram(program)
return program;
}
const ctx = document.getElementById('canvas')
const gl = ctx.getContext('webgl')
// 创建着色器源码
const VERTEX_SHADER_SOURCE = `
attribute vec4 aPosition;
attribute vec4 aTex;
varying vec2 vTex;
void main() {
gl_Position = aPosition; // vec4(0.0,0.0,0.0,1.0)
vTex = vec2(aTex.x, aTex.y);
}
`; // 顶点着色器
const FRAGMENT_SHADER_SOURCE = `
precision lowp float;
uniform sampler2D uSampler;
varying vec2 vTex;
void main() {
gl_FragColor = texture2D(uSampler, vTex);
}
`; // 片元着色器
const program = initShader(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE)
const aPosition = gl.getAttribLocation(program, 'aPosition');
const aTex = gl.getAttribLocation(program, 'aTex');
const uSampler = gl.getUniformLocation(program, 'uSampler');
// 顶点坐标和 纹理坐标
const points = new Float32Array([
-0.5, 0.5, 0.0, 1.0,
-0.5, -0.5, 0.0, 0.0,
0.5, 0.5, 1.0, 1.0,
0.5, -0.5, 1.0, 0.0,
])
// 使用vertexAttribPointer给aPosition赋值
const buffer = gl.createBuffer();
const BYTES = points.BYTES_PER_ELEMENT;
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW);
gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, BYTES * 4, 0);
gl.enableVertexAttribArray(aPosition);
gl.vertexAttribPointer(aTex, 2, gl.FLOAT, false, BYTES * 4, BYTES * 2);
gl.enableVertexAttribArray(aTex)
const img = new Image();
img.onload = function () {
// 创建纹理对象
const texture = gl.createTexture();
// 翻转 图片 Y轴
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1)
// 开启一个纹理单元
gl.activeTexture(gl.TEXTURE0);
// 绑定纹理对象
gl.bindTexture(gl.TEXTURE_2D, texture);
// 处理放大缩小的逻辑
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
// 横向 纵向 平铺的方式
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
// 配置纹理图像
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, img);
gl.uniform1i(uSampler, 0);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
}
img.src = '../assets/sky.png'
</script>
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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# 纹理的叠加
将多个图像添加到图形上,多重纹理的融合。
将天空图片和半透明的gif图片相叠加融合。
相对于一张纹理图像,使用两张纹理图像需要定义2个纹理对象,然后将2个纹理对象的值进行相乘,获取最终的颜色效果。
const FRAGMENT_SHADER_SOURCE = `
precision lowp float;
uniform sampler2D uSampler;
uniform sampler2D uSampler1;
varying vec2 vTex;
void main() {
vec4 c1 = texture2D(uSampler, vTex);
vec4 c2 = texture2D(uSampler1, vTex);
gl_FragColor = c1 * c2;
}
`;
2
3
4
5
6
7
8
9
10
11
12
13
在片元着色器中定义第4行、第9行和11行代码。
# 完整示例代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
* {
margin: 0;
padding: 0;
}
canvas {
margin: 50px auto 0;
display: block;
background: rgb(61, 61, 61);
}
</style>
</head>
<body>
<canvas id="canvas" width="400" height="400">
此浏览器不支持canvas
</canvas>
</body>
</html>
<script>
function initShader(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE) {
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(vertexShader, VERTEX_SHADER_SOURCE) // 指定顶点着色器的源码
gl.shaderSource(fragmentShader, FRAGMENT_SHADER_SOURCE) // 指定片元着色器的源码
// 编译着色器
gl.compileShader(vertexShader)
gl.compileShader(fragmentShader)
// 创建一个程序对象
const program = gl.createProgram();
gl.attachShader(program, vertexShader)
gl.attachShader(program, fragmentShader)
gl.linkProgram(program)
gl.useProgram(program)
return program;
}
const ctx = document.getElementById('canvas')
const gl = ctx.getContext('webgl')
// 创建着色器源码
const VERTEX_SHADER_SOURCE = `
// 只传递顶点数据
attribute vec4 aPosition;
attribute vec4 aTex;
varying vec2 vTex;
void main() {
gl_Position = aPosition; // vec4(0.0,0.0,0.0,1.0)
vTex = vec2(aTex.x, aTex.y);
}
`; // 顶点着色器
const FRAGMENT_SHADER_SOURCE = `
precision lowp float;
uniform sampler2D uSampler;
uniform sampler2D uSampler1;
varying vec2 vTex;
void main() {
vec4 c1 = texture2D(uSampler, vTex);
vec4 c2 = texture2D(uSampler1, vTex);
gl_FragColor = c1 * c2;
}
`; // 片元着色器
const program = initShader(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE)
const aPosition = gl.getAttribLocation(program, 'aPosition');
const aTex = gl.getAttribLocation(program, 'aTex');
const uSampler = gl.getUniformLocation(program, 'uSampler');
const uSampler1 = gl.getUniformLocation(program, 'uSampler1');
const points = new Float32Array([
-0.5, 0.5, 0.0, 1.0,
-0.5, -0.5, 0.0, 0.0,
0.5, 0.5, 1.0, 1.0,
0.5, -0.5, 1.0, 0.0,
])
const buffer = gl.createBuffer();
const BYTES = points.BYTES_PER_ELEMENT;
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW);
gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, BYTES * 4, 0);
gl.enableVertexAttribArray(aPosition)
gl.vertexAttribPointer(aTex, 2, gl.FLOAT, false, BYTES * 4, BYTES * 2);
gl.enableVertexAttribArray(aTex)
function getImage(url, location, index) {
return new Promise(resolve => {
const img = new Image();
img.onload = function () {
// 创建纹理对象
const texture = gl.createTexture();
// 翻转 图片 Y轴
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1)
// 开启一个纹理单元
gl.activeTexture(gl[`TEXTURE${index}`]);
// 绑定纹理对象
gl.bindTexture(gl.TEXTURE_2D, texture);
// 处理放大缩小的逻辑
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
// 横向 纵向 平铺的方式
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
// 配置纹理图像
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, img);
gl.uniform1i(location, index);
resolve();
}
img.src = url;
})
}
Promise.all([getImage('../assets/sky.jpg', uSampler, 0), getImage('../assets/circle.gif', uSampler1, 1)]).then(() => {
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
})
</script>
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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
最后展示效果
内容参考 《webgl编程指南》 更多资源文档和代码下载地址 (opens new window):