varying变量和颜色插值
前面课程讲解过一系列顶点通过顶点着色器逐顶点处理后,再经过图元装配、光栅化环节会得到原始未定义颜色的片元,然后经过片元着色器逐片元添加颜色,会得到一副图像。在片元着色器程序中编写代码
gl_FragColor = vec4(1.0,0.0,0.0,1.0)
就表示所有经过光栅化生成的没定义颜色的片元经过片元着色器处理后所有片元赋值红色得到一个个红色像素值。
如果希望不同的片元定义不同的颜色怎么编写代码,比如立方体每个面的颜色不同,具体点说比如执行代码
gl.drawElements(gl.TRIANGLES, 0, gl.UNSIGNED_BYTE, 6);
,
表示利用6个顶点,每三个顶点绘制一个三角形面,如果我希望两个三角面的颜色不同,如何实现,在片元着色器程序中编写代码
gl_FragColor = vec4(1.0,0.0,0.0,1.0)
肯定不行。
这就需要学习新的顶点数据,前面课程引入了顶点位置数据,本节课来讲解顶点颜色数据,以及顶点颜色使用关键字varying实现的颜色插值计算。
颜色线性插值
执行下面的代码你可以发现一个渐变色直线,由蓝色经过逐渐变为红色。它的实现过程很简单,前面的课程讲过定义两个顶点,绘制模式使用gl.LINES,连接两点形成一条直线,在片元着色器中再添加颜色。
下面的程序是分别定义两个顶点颜色的RGBA值对应两个顶点的位置坐标值,也就是说一个点一个位置数据一个颜色数据。点1为蓝色(0,0,1),点2位红色(1,0,0),R和B的值随着像素位置线性变化,R从0到1,B从1到0,
绿色成分G从0到0渐变,相当于没变化。相邻像素之间的同一种单色成分的差值是定值,所有像素的同一种单色成分是一个等差数列,给出两个点的像素值,GPU自动内插出两点之间所有像素的值,这个过程是像素的线性插值过程,
着色器程序
下面的顶点着色器和前面的相比就是增加了顶点颜色数据的处理,第13行代码使用关键字attribute声明了一个变量,用来接收第40行的顶点位置数据;第14行代码同样是使用关键字attribute声明了一个变量,
用来接收第41行的顶点颜色RGB值数据,同门的数据类型都是vec4,从这里也可以看出GLSL ES语言关键字attribute的作用就是声明一个可以接收顶面坐标、顶点颜色等等点数据的变量。
顶点着色器程序的使用关键字varying声明了一个v_color变量,这里你可能会想为什么不使用attribute关键字而是varying。使用attribute关键字声明的顶点坐标数据经过第17行处理,
经过装配、光栅化后得到一系列未定义颜色的片元,执行第18行代码
v_color = a_color;
,就是把使用attribute关键字声明的变量a_color赋值给varying关键字声明的变量v_color,
着色处处理器会利用原始的两个颜色数据进行插值计算,计算出每一个片元对应的RGBA值。然后执行片元着色器中的第25行代码
gl_FragColor = v_color;
,把插值计算出的每一个片元对应的颜色值赋值给片元,到这里你就应该明白如何实现给一个立方体的不同平面赋值不同的颜色,6个点可以绘制2个三角面,你如果把前三个点的颜色设置为红色,后三个点设置为蓝色,那么你可以得到一个红色三角面,一个蓝色三角面,如果三个点颜色不同插值计算后会得到一个彩色三角形。
注意:片元着色器程序的第23行都是使用关键字varying声明了一个v_color变量,和顶点着色器中第14行代码形式一样,都是
varying vec4 v_color;
。这很好理解,两个独立的着色器你可以理解为两个具有串联关系的CPU,这样声明就是为了把顶点着色器插值计算后得到的颜色值v_color传递给渲染流水线中处于顶点着色器后面的片元着色器。到此为止你可以学习到片元的着色可以使用
gl_FragColor = vec4(R,G,B,A);
这种方式,也可以先通过attribute声明一个接收顶点颜色数据的变量,然后赋值给varying声明的变量,GPU会知道把顶点颜色数据进行插值计算,执行语句
gl_FragColor = v_color;
就可以完成把所有插值颜色赋值给对应片元。
注意:第22行代码是为了定义片元着色器中的所有浮点型float数据的精度,在着色器语言中lowp是精度限定字表示低精度。计算机资源有限,设置数据精度是为了提高执行效率。除了片元着色器中的浮点类型float数据,其它所有类型数据浏览器都有默认的精度,因此要设定片元着色器中浮点类型float数据精度,否则会报错,初学WebGL可以先不深入学习着色器语言,知道一个大概,先把WebGL的API对应的GPU渲染管线搞明白,再去详细研究着色器语言的语法。
11
// 顶点着色器源码
12
var
vertexShaderSource =
''
+
13
'attribute vec4 apos;'
+
//顶点坐标变量
14
'attribute vec4 a_color;'
+
//attribute声明顶点颜色变量
15
'varying vec4 v_color;'
+
//varying声明顶点颜色插值后变量
16
'void main(){'
+
17
'gl_Position = apos;'
+
//顶点坐标apos赋值给内置变量gl_Position
18
'v_color = a_color;'
+
//顶点颜色插值计算
19
'}'
;
20
// 片元着色器源码
21
var
fragShaderSource =
''
+
22
'precision lowp float;'
+
//所有float类型数据的精度是lowp
23
'varying vec4 v_color;'
+
//接收顶点着色器中v_color数据
24
'void main(){'
+
25
' gl_FragColor = v_color;'
+
//插值后颜色数据赋值给对应的片元
26
'}'
;
获取顶点变量
程序执行完第65行的初始化函数initShader()后,会返回一个program对象,该对象包含顶点着色器中attribute关键字声明的顶点位置、颜色变量。调用getAttribLocation()方法,把program对象作为方法第一个参数,位置变量apos或颜色变量a_color字符串形式作为第二个参数可以返回对应顶点数据变量的位置。
30
/**
31
从program对象获取顶点位置变量apos、颜色变量a_color
32
**/
33
var
aposLocation = gl.getAttribLocation(program
,
'apos'
)
;
34
var
a_color = gl.getAttribLocation(program
,
'a_color'
)
;
设置顶点颜色、位置数据
通过第42~57行代码把数据传递给顶点着色器颜色和位置变量,位置数据只设置了x和y,z未设定默认为1,颜色数据设置了RGB,没有设置A,默认为1,注意第48行代码和第56行代码传递数据方式的差异。
36
/**
37
创建顶点位置数据数组data,存储两个顶点(-0.5,0.5、(0.5,0.5)
38
创建顶点颜色数组colorData,存储两个顶点对应RGB颜色值(0,0,1)、(1,0,0)
39
**/
40
var
data=
new
Float32Array([-
0.5
,
0.5
,
0.5
,
0.5
])
;
41
var
colorData =
new
Float32Array([
0
,
0
,
1
,
1
,
0
,
0
])
;
创建缓冲区顶点
顶点颜色数据和顶点位置数据的数据类型一样,所以创建缓冲区、传入数据的方式基本一样。
42
/**
43
创建缓冲区colorBuffer,传入顶点颜色数据colorData
44
**/
45
var
colorBuffer=gl.createBuffer()
;
46
gl.bindBuffer(gl.ARRAY_BUFFER
,
colorBuffer)
;
47
gl.bufferData(gl.ARRAY_BUFFER
,
colorData
,
gl.STATIC_DRAW)
;
48
gl.vertexAttribPointer(a_color
,
3
,
gl.FLOAT
,
false
,
0
,
0
)
;
49
gl.enableVertexAttribArray(a_color)
;
50
/**
51
创建缓冲区buffer,传入顶点位置数据data
52
**/
53
var
buffer=gl.createBuffer()
;
54
gl.bindBuffer(gl.ARRAY_BUFFER
,
buffer)
;
55
gl.bufferData(gl.ARRAY_BUFFER
,
data
,
gl.STATIC_DRAW)
;
56
gl.vertexAttribPointer(aposLocation
,
2
,
gl.FLOAT
,
false
,
0
,
0
)
;
57
gl.enableVertexAttribArray(aposLocation)
;
课后练习案例
1.创建一个彩色三角形
在原来的基础上更改两处代码即可,两个顶点改为三个顶点,绘制模式从直线改为三角形,绘制点数从两个改为3个,这里你会发现,写一个WebGL程序好像很复杂很麻烦,但是改代码实现一个新的效果却不是太麻烦。这主要是程序要同时在CPU和GPU两个硬件中执行,需要一些CPU和GPU通信的Javascript API,除了CPU执行的Javascript语言,还有GPU执行的着色器程序,着色器程序又有多个不同的处理单元,还要分别编写着色器程序,只要你花一定时间把GPU的整个流水线搞明白一切问题迎刃而解,可以利用Javascript语言封装原生的WebGL API,实现代码复用。
创建顶点位置数据数组data,存储3个顶点(-0.5,0.5、(0.5,0.5)、(0.5,-0.5)
创建顶点颜色数组colorData,存储3个顶点对应RGB颜色值(1,0,0)、(0,1,0)、(0,0,1)
var
data=
new
Float32Array([-
0.5
,
0.5
,
0.5
,
0.5
,
0.5
,
-
0.5
])
;
var
colorData =
new
Float32Array([
1
,
0
,
0
,
0
,
1
,
0
,
0
,
0
,
1
])
;
/**执行绘制命令**/
gl.drawArrays(gl.TRIANGLES
,
0
,
3
)
;
2.两个单色三角面(不同颜色)
创建顶点位置数据数组data,存储6个顶点
创建顶点颜色数组colorData,存储6个顶点对应RGB颜色值
var
data=
new
Float32Array([
-
0.5
,
0.5
,
0.5
,
0.5
,
0.5
,
-
0.5
,
//第一个三角形的三个点
-
0.5
,
0.5
,
0.5
,
-
0.5
,
-
0.5
,
-
0.5
//第二个三角形的三个点
var
colorData =
new
Float32Array([
1
,
0
,
0
,
1
,
0
,
0
,
1
,
0
,
0
,
//三个红色点
0
,
0
,
1
,
0
,
0
,
1
,
0
,
0
,
1
//三个蓝色点
3.颜色插值(顶点位置、颜色使用一个缓冲区存储)
把顶点位置数据、顶点颜色数据存储在同一个数组中,然后存入一个缓冲区中。在数组中的顶点颜色、位置坐标数据要交叉排列,使用方法vertexAttribPointer()才能合理选择数据,交叉排列正是因为vertexAttribPointer()方法5个参数的特点决定的。
创建顶点位置数据数组data,存储两个顶点(-0.5,0.5、(0.5,0.5)
存储两个顶点对应RGB颜色值(0,0,1)、(1,0,0)
var
data=
new
Float32Array([
-
0.5
,
0.5
,
0
,
0
,
1
,
0.5
,
0.5
,
1
,
0
,
0
创建缓冲区buffer,传入顶点颜色、位置数据data
var
buffer=gl.createBuffer()
;
gl.bindBuffer(gl.ARRAY_BUFFER
,
buffer)
;
gl.bufferData(gl.ARRAY_BUFFER
,
data
,
gl.STATIC_DRAW)
;
//4表示data数组一个元素占据的字节数
//倒数第二个参数4*5表示每5个元素是一个选择单元
//第2个参数2表示从5元素组成的一个选择单元中选择前2个作为顶点位置数据
gl.vertexAttribPointer(aposLocation
,
2
,
gl.FLOAT
,
false
,
4
*
5
,
0
)
;
//最后一个参数4*2表示5元素组成的一个选择单元中偏移2个元素
//第2个参数3表示从5元素组成的一个选择单元中选择后三个作为顶点颜色数据
gl.vertexAttribPointer(a_color
,
3
,
gl.FLOAT
,
false
,
4
*
5
,
4
*
2
)
;
gl.enableVertexAttribArray(aposLocation)
;
gl.enableVertexAttribArray(a_color)
;