varying 变量和颜色插值

优质
小牛编辑
130浏览
2023-12-01

前面课程讲解过一系列顶点通过顶点着色器逐顶点处理后,再经过图元装配、光栅化环节会得到原始未定义颜色的片元,然后经过片元着色器逐片元添加颜色,会得到一副图像。在片元着色器程序中编写代码 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实现的颜色插值计算。

插值计算

线条插值效果

线条插值

三角形插值效果

三角形插值

颜色线性插值

执行本节课0.颜色插值.html源码你可以发现渲染效果是一个渐变色直线,由蓝色经过逐渐变为红色。它的实现过程很简单,前面的课程讲过定义两个顶点,绘制模式使用gl.LINES,连接两点形成一条直线,在片元着色器中再添加颜色。 下面的程序是分别定义两个顶点颜色的RGBA值对应两个顶点的位置坐标值,也就是说一个点一个位置数据一个颜色数据。点1为蓝色(0,0,1),点2位红色(1,0,0),R和B的值随着像素位置线性变化,R从0到1,B从1到0, 绿色成分G从0到0渐变,相当于没变化。相邻像素之间的同一种单色成分的差值是定值,所有像素的同一种单色成分是一个等差数列,给出两个点的像素值,GPU自动内插出两点之间所有像素的值,这个过程是像素的线性插值过程。

顶点着色器代码

//attribute声明vec4类型变量apos
attribute vec4 apos;
// attribute声明顶点颜色变量
attribute vec4 a_color;
//varying声明顶点颜色插值后变量
varying vec4 v_color;
void main() {
  // 顶点坐标apos赋值给内置变量gl_Position
  gl_Position = apos;
  //顶点颜色插值计算
  v_color = a_color;
}

片元着色器代码

// 所有float类型数据的精度是lowp
precision lowp float;
// 接收顶点着色器中v_color数据
varying vec4 v_color;
void main() {
  // 插值后颜色数据赋值给对应的片元
  gl_FragColor = v_color;
}

本节课顶点着色器和前面课程案例源码的相比就是增加了顶点颜色数据的处理,attribute vec4 apos;使用关键字attribute声明了一个表示顶点位置坐标数据的变量apos,用来接收类型数据var data=new Float32Array([-0.5,0.5,0.5,0.5]);包含的顶点位置数据;attribute vec4 a_color;同样是使用关键字attribute声明了一个表示顶点颜色数据的变量a_color, 用来接收类型数组var colorData = new Float32Array([0,0,1,1,0,0]);包含的顶点颜色RGB值数据,顶点位置变量apos和顶点颜色变量a_color的数据类型同样都是四维向量vec4,从这里也可以看出GLSL ES语言的关键字attribute的作用就是声明一个可以接收顶点位置坐标、顶点颜色、顶点法向量等顶点数据的变量。

//attribute声明vec4类型变量apos
attribute vec4 apos;
// attribute声明顶点颜色变量
attribute vec4 a_color;

顶点着色器程序使用关键字varying声明了一个v_color变量,这里你可能会想为什么不使用attribute关键字而是varying。使用attribute关键字声明的顶点位置坐标数据执行gl_Position = apos;经过装配、光栅化后得到一系列未定义颜色的片元,执行代码v_color = a_color;,就是把使用attribute关键字声明的变量a_color赋值给varying关键字声明的变量v_color, 着色处理器会利用原始的两个颜色数据进行插值计算,计算出每一个片元对应的RGBA值。然后执行片元着色器中代码gl_FragColor = v_color;,把插值计算出的每一个片元的颜色值赋值给对应片元,到这里你就应该明白如何实现给一个立方体的不同平面赋值不同的颜色,6个点可以绘制2个三角面,你如果把前三个点的颜色设置为红色,后三个点设置为蓝色,那么你可以得到一个红色三角面,一个蓝色三角面,如果三个点颜色不同插值计算后会得到一个彩色三角形,这里说的彩色三角形效果可以查看本节课的案例源码。

//varying声明顶点颜色插值后变量
varying vec4 v_color;

注意:片元着色器程序和顶点着色器程序都通过varying关键字声明一个颜色变量v_color,即varying vec4 v_color;。这很好理解,两个独立的着色器单元你可以理解为两个具有串联关系的处理器,这样声明就是为了把顶点着色器插值计算后得到的颜色值v_color传递给渲染流水线中处于顶点着色器后面的片元着色器。到此为止你可以学习到片元的着色可以使用gl_FragColor = vec4(R,G,B,A);这种方式,也可以先通过attribute关键字声明一个接收顶点颜色数据的变量a_color,然后赋值给varying声明的变量v_color,GPU会知道把顶点颜色数据进行插值计算,执行语句gl_FragColor = v_color;就可以完成把所有插值颜色赋值给对应片元。

注意:precision lowp float;是为了定义片元着色器中的所有浮点型float数据的精度,在着色器语言中lowp是精度限定字表示低精度。计算机资源有限,设置数据精度是为了提高执行效率。除了片元着色器中的浮点类型float数据,其它所有类型数据浏览器都有默认的精度,因此要设定片元着色器中浮点类型float数据精度,否则会报错,初学WebGL可以先不深入学习着色器语言,知道一个大概,先把WebGL的API对应的GPU渲染管线搞明白,再去详细研究着色器语言的语法。关于WebGL着色器精度的介绍,可以查看第二章2.12 precision着色器计算精度

获取顶点变量

程序执行完初始化函数initShader()后,会返回一个程序对象program,该对象包含顶点着色器中attribute关键字声明的顶点位置、顶点颜色变量。调用WebGL APIgl.getAttribLocation(),可以把program对象作为该API第一个参数,位置变量apos或颜色变量a_color字符串形式作为第二个参数可以返回对应顶点数据变量的位置,获取了着色器中变量的位置就可以把javascript中对应数据传递过去。

//获取顶点着色器的位置变量apos
var aposLocation = gl.getAttribLocation(program,'apos');
var a_color = gl.getAttribLocation(program,'a_color');

设置顶点颜色、位置数据

通过第63~75行代码中gl.createBuffer()gl.bufferData()等WebGL API把数据传递给顶点着色器颜色和位置变量,顶点位置数据只设置了x和y,z未设定默认为1.0,颜色数据设置了RGB,没有设置透明度分量A,默认为1.0,注意gl.vertexAttribPointer(a_color,3,gl.FLOAT,false,0,0);gl.vertexAttribPointer(aposLocation,2,gl.FLOAT,false,0,0);传递数据方式的差异。

/**
 创建顶点位置数据数组data,存储两个顶点(-0.5,0.5、(0.5,0.5)
 创建顶点颜色数组colorData,存储两个顶点对应RGB颜色值(0,0,1)、(1,0,0)
 **/
var data=new Float32Array([-0.5,0.5,0.5,0.5]);
var colorData = new Float32Array([0,0,1,1,0,0]);

创建顶点缓冲区、传递数据

顶点颜色数据和顶点位置数据的数据类型一样,所以创建缓冲区、传入数据的方式基本一样。

/**
 创建缓冲区colorBuffer,传入顶点颜色数据colorData
 **/
var colorBuffer=gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER,colorBuffer);
gl.bufferData(gl.ARRAY_BUFFER,colorData,gl.STATIC_DRAW);
gl.vertexAttribPointer(a_color,3,gl.FLOAT,false,0,0);
gl.enableVertexAttribArray(a_color);
/**
 创建缓冲区buffer,传入顶点位置数据data
 **/
var buffer=gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER,buffer);
gl.bufferData(gl.ARRAY_BUFFER,data,gl.STATIC_DRAW);
gl.vertexAttribPointer(aposLocation,2,gl.FLOAT,false,0,0);
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.颜色插值(顶点位置、颜色使用一个缓冲区存储)

把顶点位置数据、顶点颜色数据存储在同一个数组中,然后存入一个缓冲区中。在数组中的顶点颜色、位置坐标数据要交叉排列,使用方法gl.vertexAttribPointer()才能合理选择数据,交叉排列正是因为gl.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);