4.3 增加新的颜色属性(Adding a New Color Attribute)
我们已经通过加入中心点更新了我们桌面顶点结构,现在可以对每个顶点增加一个颜色属性了,现在更新到到整个顶点数组,如下代码所示:
//AirHockey2/src/com/airhockey/android/AirHockeyRenderer.java
float[] tableVerticesWithTriangles = {
// Order of coordinates: X, Y, R, G, B
// Triangle Fan
0f, 0f, 1f, 1f, 1f,
-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, 1f, 0f, 0f,
// Mallets
0f, -0.25f, 0f, 0f, 1f,
0f, 0.25f, 1f, 0f, 0f
};
正如代码所表达那样,我们增加了三个数据到顶点坐标中,这些数据分别代码RGB三色分量值,分别代表相应的顶点坐标颜色。
Tips:使用Android的Color类转换颜色(Converting Colors Using Android's Color Class)
当我使用float类型属性的时候,我们需要把所指定的颜色值规范化到[0, 1]的范围内,1代表该颜色通道取最大值。或许 这样指定颜色值会让你感觉不知道当前的颜色会是什么样的,但是你可以使用Android提供的Color类来进行相应转换,下面是一些类似例子:
float red = Color.red(Color.GREEN) / 255f;
float green = Color.green(Color.GREEN) / 255f;
float blue = Color.blue(Color.GREEN) / 255f;
我们也可以像下面这样使用:
int parsedColor = Color.parseColor("#0099CC");
float red = Color.red(parsedColor) / 255f;
float green = Color.green(parsedColor) / 255f;
float blue = Color.blue(parsedColor) / 255f;
方法Color.red(), Color.green()及 Color.blue()返回的值在0到255之间,因此只需要把它们除以255就能转换到OpenGL颜色空间。
4.3.1 为着色器添加颜色属性 (Adding the Color Attribute to the Shaders)
下一步是把顶点着色器中uniform类型的颜色变量去掉,并且换成attribute类型变量。稍后我们将会更新java代码以作出相应的调整。
打开simple_vertex_shader.glsl并更新相应的着色器代码,如下所示:
//AirHockey2/res/raw/simple_vertex_shader.glsl
attribute vec4 a_Position;
attribute vec4 a_Color;
varying vec4 v_Color;
void main() {
v_Color = a_Color;
gl_Position = a_Position;
gl_PointSize = 10.0;
}
我们新增了一个属性类型的变量a_Color以及一个varying类型的变量v_Color,到底什么叫varying?这有什么作用呢?请记住,当你想要实现在一个三角形的表面实现颜色渐变的时候你就可以用上varying类型变量了。
正如我们前面所学习的一样,当OpenGL需要绘制一条线的时候,它会用两个顶点来产生绘制所需要的片元。当绘制三角形的时候它也会做同样的工作,然后片元着色器程序会针对每个片元运行一次。
一个varying类型的变量可以混合我们传递给它的颜色值然后再给片元着色器;比如对于上面的代码,假如a_Color是红色的且为顶点0的颜色,在顶点1是绿色的,这里通过把a_Color赋值给v_Color,这样OpenGL就会在执行片元着色器的时候使用混合后的颜色。在越靠近顶点0处,颜色越是红色,在越靠近顶点1处,颜色越是绿色。
在我们学习颜色是如何进行之前,我们也在相应的片元着色器里面加入varying类型的变量,更新simple_fragment_shader.glsl中的代码,代码如下:
//AirHockey2/res/raw/simple_fragment_shader.glsl
precision mediump float;
varying vec4 v_Color;
void main() {
gl_FragColor = v_Color;
}
我们把之前uniform类型的变量替换成了这里的varying类型变量v_Color。假如片元属于直线,那OpenGL就会使用直线两个端点的颜色进行颜色混合,假如片元属于三角形,那OpenGL就会使用三角形的三个端点的颜色进行颜色混合。
现在已经更新了着色器,我们也需要更新java代码把颜色值传递给顶点着色器的a_Color变量,不过在这之前我们先来学习下OpenGL是如何在两个顶点之前进行颜色混合的。
4.3.2 每一个varying类型的变量是如何进行颜色混合的?(How Does a Varying Get Blended at Each Fragment?)
现在我们知道可以使用varying类型的变量进行颜色混合,其实不仅仅是颜色,我们可以把任何值传递给varying类型变量,然后OpenGL就会自动的使用相关值进行一个插值,这样每一个片元都会得到一个不同的值,这个差值内部使用的是线性差值算法进行的。现在我们先从简单的情况说起,比如直线。
1)直线的线性混合(Linear Interpolation Along a Line)
现在假如我们有两个顶点,一个是红色,一个是绿色,那么他们混合的效果将会像下面图示一样:
在直线的左边,直线的颜色基本是红色,当向右移动的时候红色的分量越来越少,到中间的时候这些颜色介于红色和绿色之间,当越来越接近右边的时候,颜色越来越绿。
从这里我们看出这里的颜色有一个线性变化,因为左顶点是红色的,右顶点是绿色的,那么左边的颜色应该是100%的红色,中间应该是50%的红色,最右边应该是0%的红色。如下图所示:
同样的对于绿色也是一样,因为左顶点是红色的,右顶点是绿色的,因此左顶点应该是0%的绿,中间应该是50%的绿色,右边应该是100%的绿色,如下图所示:
一旦我们把上面两种情况合在一起,那么将会得到一个混合的效果,如下图所示:
这是颜色线性混合的一种大致表示,每一种颜色分量的强度取决于当前片元与所含颜色端点的距离。
如何计算混合后的颜色呢?首先得到顶点0和顶点1的颜色,然后计算当前片元的距离比,距离比是一个0到1之间的一个值,0代表左边的顶点,1代表右边的顶点,当我们从左边到右边移动的时候,距离比就会线性的从0到1进行变化,下面是距离比的一个直观展示:
你可以使用如下的公式对颜色进行实际的线性插值计算最终的混合颜色:
blended_value = (vertex_0_value * (100% – distance_ratio)) + (vertex_1_value * distance_ratio)
这个计算规则会针对每个分量都进行计算,假如是对颜色进行混合的话,这个计算会对RGBA分别进行计算,然后把计算的结果合成一个颜色。
下面是一些颜色混合计算的例子:
position | Distance ratio | Equation |
Far Left | 0% | (vertex_0_value * (1 - distance_ratio)) + (vertex_1_value * distance_ratio) = ((1, 0, 0) * (100% – 0%)) + ((0, 1, 0) * 0%) =((1, 0, 0) * 100%) = (1, 0, 0) (red) |
One-quarter along the line | 25% | (vertex_0_value * (1 – distance_ratio)) + (vertex_1_value * distance_ratio) = ((1, 0, 0) * (100% – 25%)) + ((0, 1, 0) * 25%) = ((1, 0, 0) * 75%) + ((0, 1, 0) * 25%) = (0.75, 0, 0) + (0, 0.25, 0) =(0.75, 0.25, 0) (mostly red) |
Middle of the line | 50% | (vertex_0_value * (1 – distance_ratio)) + (vertex_1_value * distance_ratio) = ((1, 0, 0) * (100% – 50%)) + ((0, 1, 0) * 50%) =((1, 0, 0) * 50%) + ((0, 1, 0) * 50%) = (0.5, 0, 0) + (0, 0.5, 0) =(0.5, 0.5, 0) (half red, half green) |
Three-quarters along the line | 75% | (vertex_0_value * (1 – distance_ratio)) + (vertex_1_value * distance_ratio) = ((1, 0, 0) * (100% – 75%)) + ((0, 1, 0) * 75%) =((1, 0, 0) * 25%) + ((0, 1, 0) * 75%) =(0.25, 0, 0) + (0, 0.75, 0) =(0.25, 0.75, 0) (mostly green) |
Far right | 100% | (vertex_0_value * (1 – distance_ratio)) + (vertex_1_value * distance_ratio) =((1, 0, 0) * (100% – 100%)) + ((0, 1, 0) * 100%) = ((1, 0, 0) * 0%) + ((0, 1, 0) * 100%) =(0, 1, 0) (green) |
请注意,所有分量的比例加起来刚好是100%,假如R分量是100%则G分量是0%,假如R分量50%则G分量是50%。
使用varying类型的变量,我们可以混合任何两种颜色,当然不仅限于颜色,我们也可以对其它的属性值进行插值混合计算。
现在我们已经知道线性插值如何对一条直线的颜色进行线性混合,现在我们来看看对于三角形又是怎样的呢?
2) 对三角形表面进行混合(Blending Across the Surface of a Triangle)
当仅仅只是对两个顶点进行混合的时候,我们知道每一个颜色分量的强度都会从100%到0%进行线性变化,然后把两个插值过的颜色合在一起产生一个新的最终颜色。
当对三角形的表面进行颜色混合的时候其实也是同样的思想,只是此时有三个点需要处理,请看下图:
这个三角形的三个顶点分别对应三种颜色,上顶点是蓝绿色,左顶点是洋红色,右顶点是黄色,现在分别针对每个顶点进行混合,结果如下:
就像线性混合一样,当越靠近顶点的时候颜色分量越强,越远离的时候颜色分量越弱,这里我们也使用占比进行颜色混合的计算,只是这次我们使用的是面积而不是距离比,如下图所示:
在上面三角形的内部每一个点,都可以通过连接三角形的三个顶点来得到三个内部三角形,内部三角形的面积占比代表了当前点相应颜色的权重分配。比如,黄色分量的强度由黄色顶点对面的内部三角形的面积占比决定。当该点越接近黄色顶点的时候,相应的面积占比越大,那么相应顶点黄色分量的权重越大。
就是线性混合一样,这些权重总和也是100%,我们可以使用如下的计算公式得到三角形内部每个顶点的颜色:
blended_value = (vertex_0_value * vertex_0_weight) + (vertex_1_value * vertex_1_weight) + (vertex_2_value * (100% – vertex_0_weight – vertex_1_weight))
对该公式的理解也与线性混合一样,只是这里有三个顶点,所以也不需要具体的例子进行说明了,思想都是一样的。
现在我们已经知道了顶点是如何进行混合的,下一节将会使用颜色属性混合进行桌面绘制。