OpenGL ES入门教程(三)之为平面桌子添加渐变色
- 前言
- 零、OpenGL ES实现混合色的原理
- 一、修改绘制的桌子结构
- 1. 三角形扇介绍
- 2. 基于三角形扇结构绘制平面桌子
- 二、为每个顶点添加颜色属性
- 三、修改着色器
- 1. 顶点着色器
- 2. 片段这色器
- 四、绘制具有混合颜色的平面桌子
- 1. 计算跨度
- 2. 关联顶点位置数据和颜色数据
- 3. 绘制图形
- 五、完整示例代码下载
前言
上一篇文章我们讲解了OpenGL ES如何绘制一个平面桌子,本文在其基础上继续讲解如何使绘制的平面桌子具有混合色,效果类似在桌子中心上面吊一盏灯,越靠近桌子中心颜色越亮白,越远离桌子中心颜色越暗灰。如果是OpenGL ES小白,在阅读本篇文章之前一定要搞懂上篇文章OpenGL ES入门教程(二)之绘制一个平面桌子
零、OpenGL ES实现混合色的原理
首先混合色指的是将两种及以上的颜色进行混合而得出的其它颜色。之前的文章中我们讲过OpenGL只有点,线,三角形三种基本图元,其余图元都是有这三种基本图元构成。很明显只有线和三角形这两种图元有实现混合色的意义,混合色最常见的实现方式就是对颜色进行线性插值,OpenGL实现混合色的原理也是如此。
-
一条直线实现混合色,只需要给定直线两个顶点的颜色,线其余的颜色都是由这两个顶点线性插值而得。混合色结果取决于线上的位置与两个顶点之间的距离,线上的位置越接近哪个顶点,那混合色就越倾向于哪个顶点的颜色。直线上各个位置通过线性插值计算的混合色的公式如下:
可以看到distance_ratio的值越大,vertex_1_value所占的比重就越大,而对应vertex_0_value所占的比重就越小,即distance_ratio的值越大,颜色越接近于vertex_1_value的颜色。 -
三角形平面实现混合色,只需要给定三角形三个顶点的颜色,三角形内部其余的颜色都是由这三个顶点线性插值而得。对于三角形内任何给定的点,从那个点向每个顶点所对应的点画一条直线就可以生成三个内部三角形。这三个内部三角形的面积的比例决定了那个点上每种颜色的权重。如下图所示三角形,那个点上黄色的强度就取决于黄色顶点相对的那个内部三角形的面积。距离黄色顶点越近的点,它相对的三角形就越大,在那个点的片段就越显黄。
三角形内每个点的混合色线性插值的计算公式如下:
说到底,就是最普通的线性插值,只是插值插的是颜色值而已,与我们上学时数学课上的线性插值一样(z=ax+by),相信稍微思考下就明白啦~,而且下面的混合色实现也不需要我们写公式,OpenGL都封装好了。
一、修改绘制的桌子结构
我们要实现效果类似在桌子中心上面吊一盏灯,越靠近桌子中心颜色越亮白,越远离桌子中心颜色越暗灰。那我们就要在桌子的中心放置一个顶点,且该处顶点颜色是纯白色,因此,长方形平面桌子应该有如下图所示的4个三角形组成,而上一篇文章中我们实现的平面桌面是由两个三角形组成的,因此我们应该先修改桌面的结构。
1. 三角形扇介绍
一个三角形扇以一个中心顶点作为起始,使用相邻的两个顶点创建第一个三角形,接下来的每个顶点都会创建一个三角形,围绕起始的中心点按扇形展开。为了使这个扇形闭合,我们只需要在最后重复第二个点。
三角形扇完全可以实现我们上面所提到的由4个三角形构成的长方形,该三角形扇包含的顶点如下图所示,一共由5个顶点组成。
2. 基于三角形扇结构绘制平面桌子
- 上面提到三角形扇需要5个顶点,同时由于为了使得三角形扇闭合,我们需要重复第二个顶点使其成为最后一个顶点,因此,平面桌子的坐标数据修改如下:
float[] tableVerticesWithTriangles = {/**无论是x还是y坐标,OpenGL都会把屏幕映射到[-1,1]的范围内。即屏幕的左边对应x轴的-1,右边对应+1;屏幕的底边对应y轴的-1,顶边对应+1*/// Triangle Fan0f, 0f,//三角形扇的中心点坐标-0.5f, -0.5f,//三角形扇第二个顶点坐标0.5f, -0.5f,0.5f, 0.5f,-0.5f, 0.5f,-0.5f, -0.5f,//三角形扇必须封闭,因此这个就是第二个顶点坐标// Line 1-0.5f, 0f,0.5f, 0f,// Mallets0f, -0.25f,0f, 0.25f};
- 调用glDrawArrays方法,第一个参数选择GL_TRIANGLE_FAN绘制三角形扇即可,平面桌子的绘制代码如下
@Overridepublic void onDrawFrame(GL10 gl) {glClear(GL_COLOR_BUFFER_BIT);/*绘制桌子*///更新着色器中u_Color的值(蓝色)glUniform4f(uColorLocation, 0.0f, 0.0f, 1.0f, 1.0f);//参数1:绘制三角形扇;参数2:从顶点数组的开头开始读顶点;参数3:读取6个顶点//之前glVertexAttribPointer告诉过OpenGL每个顶点的位置包含两个浮点分量,因此OpenGL会使用vertexData中如下12个浮点数glDrawArrays(GL_TRIANGLE_FAN, 0, 6);/*绘制分割线*///更新u_Color的值(红色)glUniform4f(uColorLocation, 1.0f, 0.0f, 0.0f, 1.0f);//参数1:绘制直线; 参数2:从顶点数组的第6个顶点之后(即第7个顶点)开始读取;参数3:读取两个顶点glDrawArrays(GL_LINES, 6, 2);/*绘制两个木槌*///更新u_Color的值(黑色)glUniform4f(uColorLocation, 0.0f, 0.0f, 0.0f, 1.0f);glDrawArrays(GL_POINTS, 8, 1);//更新u_Color的值(绿色)glUniform4f(uColorLocation, 0.0f, 1.0f, 0.0f, 1.0f);glDrawArrays(GL_POINTS, 9, 1);}
- 此时,运行代码结果和上一篇文章绘制的平面桌子效果一致,只是长方形的结构由两个三角形组成变成了由4个三角形组成而已。
二、为每个顶点添加颜色属性
我们之前在数组中记录了每个顶点的位置坐标数据,同理,我们也可以再定义一个数组,对应记录每个顶点的颜色数据。但为了数据处理更加高效,我们不用另外再定义一个数组存储颜色数据,完全可以把顶点的位置数据和颜色数据存储在同一个数组中。包含顶点位置坐标数据和颜色数据的数组如下,每个顶点包含x,y两个位置坐标数据,紧邻其后是r,g,b三个颜色分量数据。
float[] tableVerticesWithTriangles = {/**无论是x还是y坐标,OpenGL都会把屏幕映射到[-1,1]的范围内。即屏幕的左边对应x轴的-1,右边对应+1;屏幕的底边对应y轴的-1,顶边对应+1*/// Triangle Fan0f, 0f, 1f, 1f, 1f,//前两位是三角形扇的中心点坐标,后面三位是代表rgb的颜色分量(此处代表的颜色是白色)-0.5f, -0.5f, 0.7f, 0.7f, 0.7f,//三角形扇第二个顶点坐标0.5f, -0.5f, 0.7f, 0.7f, 0.7f,0.5f, 0.5f, 0.7f, 0.7f, 0.7f,-0.5f, 0.5f, 0.7f, 0.7f, 0.7f,-0.5f, -0.5f, 0.7f, 0.7f, 0.7f,//三角形扇必须封闭,因此这个就是第二个顶点坐标// Line 1-0.5f, 0f, 1f, 0f, 0f,//顶点颜色是红色0.5f, 0f, 0f, 1f, 0f,//顶点颜色是绿色// Mallets0f, -0.25f, 0f, 0f, 1f,//顶点颜色是蓝色0f, 0.25f, 1f, 0f, 0f//顶点颜色是红色};
三、修改着色器
1. 顶点着色器
顶点带有颜色属性的,顶点着色器代码如下。
加入了a_Color颜色属性和v_Color混合颜色属性,a_Color存储顶点的颜色,v_Color用于实现颜色的线性插值,为片段生成混合色。可以理解为咱们在第零章讲解的混合色原理,OpenGL通过varying这种特殊标记实现了。
/*
attribute:属性数据的特定标识
vec4:一种包含4个分量的向量数据类型(x,y,z,w)a_Position中x,y,z代表顶点的三维位置坐标,w是一个特殊坐标,后面会讲解
a_Position:变量名称,该名称后面OpenGL的glGetAttribLocation方法要用到,如果修改后面就要一起修改
*/
attribute vec4 a_Position;//代表位置的属性数据(x,y,z,w)
attribute vec4 a_Color;//代表颜色的属性数据(r,g,b,a)/*
varying:一种特殊的变量类型,它把给它的哪些值进行混合,并把这些混合的值发送给片段着色器。
其实所谓混合,就是通过线性插值方法生成片段中除了顶点之外的其它颜色。
例如生成直线的片段是由两个顶点构成,顶点0的a_Color是红色,顶点1的a_Color是绿色,通过将a_Color赋值给varying这种特殊类型的变量,
就可以使得越靠近顶点0的直线片段颜色越红,越靠近顶点1的直线片段颜色越绿。
*/
varying vec4 v_Color;//和C语言类似,main函数是着色器的入口函数
void main()
{//将颜色数据赋值给varying类型的变量v_Color = a_Color;//gl_Position :OpenGL特定的变量名,用于存储我们定义的顶点数据gl_Position = a_Position;//gl_PointSize:OpenGL特定的变量名,用于存储点的大小gl_PointSize = 10.0;
}
2. 片段这色器
为片段生成混合颜色的片段着色器代码如下。
将uniform标识的一个片段只有一个颜色的属性修改为varying标识的混合颜色属性,这个属性与顶点着色器定义的varying属性相对应,属性名必须一致。
//OpenGL定义float数据类型的精度(lowp;mediump;highp),就像java代码中浮点型选择float类型还是double类型。
//精度是以性能为代价的,这里选择mediump
precision mediump float;varying vec4 v_Color;//通过线性插值顶点颜色,形成混合颜色(这个参数名必须与顶点着色器中的参数名一致)//和C语言类似,main函数是着色器的入口函数
void main()
{gl_FragColor = v_Color;
}
四、绘制具有混合颜色的平面桌子
1. 计算跨度
上一篇文章中,我们讲解到通过glVertexAttribPointer方法将顶点位置与着色器进行关联,该方法中有一个跨度参数stride,我们直接赋值为0,并没有对其进行详细讲解,因为那时数组中只存储了顶点位置坐标数据,不包含颜色属性数据,因此OpenGL只需要一个顶点数据挨着一个顶点数据的读取就能得到每个顶点的正确位置坐标数据。
//获取顶点着色器中(attribute)位置属性aPositionLocation = glGetAttribLocation(program, A_POSITION);//告诉OpenGL从vertexData中读取a_Position的数据vertexData.position(0);//将缓冲区数据中的指针指向第一个数据,即从第一个数据开始读/*public static void glVertexAttribPointer(int indx,int size,int type,boolean normalized,int stride,java.nio.Buffer ptr)将着色器中的位置属性与本地顶点数据相关联。aPositionLocation:着色器中定义的位置属性POSITION_COMPONENT_COUNT:每次从本地数组中读取两个数据(即x,y代表一个顶点坐标)GL_FLOAT:OpenGL采用的数据类型,因为我们定义的是浮点数数组,所以采用GL_FLOATvertexData:要关联的本地数据列表*/glVertexAttribPointer(aPositionLocation, POSITION_COMPONENT_COUNT, GL_FLOAT,false, 0, vertexData);
但是,现在我们的每个顶点不仅存储了位置坐标数据,同时存储了颜色属性数据,因此,第一个顶点的位置坐标数据和第二个顶点的位置坐标数据之间多个三个颜色属性数据,为了正常的获得每个顶点的位置坐标数据,我们需要把中间的颜色属性数据跨过去,所以,才有了跨度stride这个参数。因为,现在我们每个顶点位置坐标数据直接插入了三个颜色属性数据,因此下一个顶点的位置坐标数据需要跨过上一个顶点中的两个位置坐标数据和3个颜色属性数据,因此跨距stride计算方式如下(跨距的单位是字节):
//每个float由4个字节组成private static final int BYTES_PER_FLOAT = 4;//每个顶点前两个浮点数代表位置:x,yprivate static final int POSITION_COMPONENT_COUNT = 2;//每个顶点后三个浮点数代表颜色:r,g,bprivate static final int COLOR_COMPONENT_COUNT = 3;//STRIDE代表每个顶点位置之间间隔多少个字节(因为现在顶点位置和顶点颜色数据混在一起,只有明确跨度才能查找到每个顶点的位置)private static final int STRIDE =(POSITION_COMPONENT_COUNT + COLOR_COMPONENT_COUNT) * BYTES_PER_FLOAT;
2. 关联顶点位置数据和颜色数据
通过glVertexAttribPointer函数将数组中存储的顶点数据与着色器中的属性相关联
//获取顶点着色器中代表顶点颜色的属性aColorLocation = glGetAttribLocation(program, A_COLOR);//获取顶点着色器中代表顶点位置的属性(attribute)aPositionLocation = glGetAttribLocation(program, A_POSITION);//告诉OpenGL从vertexData中读取a_Position的数据vertexData.position(0);//将缓冲区数据中的指针指向第一个顶点的位置数据开始的位置glVertexAttribPointer(aPositionLocation, POSITION_COMPONENT_COUNT, GL_FLOAT,false, STRIDE, vertexData);//使能顶点数组glEnableVertexAttribArray(aPositionLocation);//告诉OpenGL从vertexData中读取a_Color的数据vertexData.position(POSITION_COMPONENT_COUNT); //将缓冲区中的指针指向第一个顶点的颜色数据开始的位置glVertexAttribPointer(aColorLocation, COLOR_COMPONENT_COUNT, GL_FLOAT,false, STRIDE, vertexData);//使能顶点数组glEnableVertexAttribArray(aColorLocation);
3. 绘制图形
现在每个顶点都有了自己的颜色,并且采用varying实现片段的混合色,因此,不要再调用glUniform4f方法为片段设置颜色了,每个片段的颜色都会由顶点插值而得。
@Overridepublic void onDrawFrame(GL10 gl) {glClear(GL_COLOR_BUFFER_BIT);/*绘制桌子*///参数1:绘制三角形扇;参数2:从顶点数组的开头开始读顶点;参数3:读取6个顶点//之前glVertexAttribPointer告诉过OpenGL每个顶点的位置包含两个浮点分量,因此OpenGL会使用vertexData中12个浮点数glDrawArrays(GL_TRIANGLE_FAN, 0, 6);/*绘制分割线*///参数1:绘制直线; 参数2:从顶点数组的第6个顶点之后(即第7个顶点)开始读取;参数3:读取两个顶点glDrawArrays(GL_LINES, 6, 2);glLineWidth(10); //设置线宽,不设置线宽,无法体现线的渐变颜色/*绘制两个木槌*/glDrawArrays(GL_POINTS, 8, 1);glDrawArrays(GL_POINTS, 9, 1);}
运行代码,绘制图形效果如下图所示
五、完整示例代码下载
完整的代码都上传到了Gitee仓库
OpenGL_ES_DEMO中,仓库代码具有详细的提交记录,博友可以执行git命令:
git reset --hard f5081f05963ed48b4d149d4f9bc8476ee81caac8,切到本篇文章所讲解内容的完整示例代码。