Learn OpenGLES:颜色渐变

这一贴,是继上一贴的补充。主要介绍,Shader的顶点属性数组(即顶点有多个属性)的情况,以及Shader是如何对顶点渲染颜色的。
我们将以简单的 长方形为例, 并在最后讨论当手机横竖屏时,长方形显示适配的问题。

在上一节中,我们讲了如何对三角形进行上色。 它的关键点很简单,就是通过在fragment shader里定义一个uniform变量(unifrom是图像管线中的全局变量,在图形渲染中,其值不会被改变)。在图形管线中,越是前面的shader越涉及到顶点的操作。 在光栅化之前,经过一些坐标变换和灯光的颜色操作(T&L), 着色器通过生成新的顶点,完成对顶点属性的赋值,这种赋值是通过顶点插值完成的。 如下图所示,

Learn OpenGLES:颜色渐变_第1张图片

假如在左边的顶点定义为红色(rgb: 1, 0, 0),右边顶点定义为白色(rgb: 1, 1, 1)。 那么, 左右顶点之间的顶点的颜色就会是一个线性插值满足如下等时关系:

1
Color_point = rightColor *x + (1-x)*leftColor;
其中, x表示中间的点的位置,这里我们假定左右点的距离为1。
在光栅化之后,空间中的这些点被映射到屏幕上,fragment其实就相当于像素, 关于这些像素的位置、颜色信息其实都存在相应的buffer中,然后经过fragment shader和 blending,最终会将具有颜色的像素显示在屏幕上。 如果,我们在fragment shader中定义了uniform变量,将其赋值给gl_FragColor输出, 那么,它将刷新每个fragment的颜色。如果,我们想让每个fragment都具有自己不同的颜色,通过在vertex shader中定义每个顶点的颜色属性,可以简单的 实现这一目的。

修改simple_vertex_shader.glsl:
1
2
3
4
5
6
7
8
9
10
attribute vec4 a_Position;
attribute vec4 a_Color;
         
varying vec4 v_Color;
         
void main()
{
    v_Color = a_Color;
    gl_Position = a_Position;
}
simple_frag_shader.glsl:
1
2
3
4
5
6
7
precision mediump float;
varying vec4 v_Color;
         
void main()
{
    gl_FragColor = v_Color;
}
相应的我们也需要修改MyGLRenderer.java里的顶点数据:
1
2
3
4
5
6
float[] tableVerticesWithTriangles = {
               //X, Y, Z ,    R,    G,    B
               -0.5f, -0.5f, 1.0f, 0.0f, 0.0f,
                 0f,  0.5f,  0.0f, 1.0f, 0.0f,
               0.5f, -0.5f,   0.0f, 0.0f, 1.0f// Triangle 1
       };
那么,这个时候,vertex shader中每个顶点就要多个属性了。 那么shader有是如何找到上面的顶点数据呢? 
Learn OpenGLES:颜色渐变_第2张图片
通过上图我们知道,shader中的顶点属性与我们输入的顶点数据之间的关系,vertex shader中定义的顶点属性是相应的放在一个顶点数组中,每个属性都分别指向GPU的一段内存。通过以下语句我,可以知道shader中的顶点属性的这种指向关系。
在onSurfaceCreated最后添加:
1
2
3
4
5
6
7
8
9
10
aColorLocation = glGetAttribLocation(program, A_COLOR);
aPositionLocation = glGetAttribLocation(program, A_POSITION);
         
vertexData.position(0);  // 指向顶点位置数据起始的位置
glVertexAttribPointer(aPositionLocation, NUM_POSITION_COMPONENT, GL_FLOAT, false, STRIDE, vertexData);
glEnableVertexAttribArray(aPositionLocation);
         
vertexData.position(NUM_POSITION_COMPONENT);  // 指向顶点颜色数据起始的位置
glVertexAttribPointer(aColorLocation, NUM_COLOR_COMPONENT, GL_FLOAT, false, STRIDE, vertexData);
glEnableVertexAttribArray(aColorLocation);

即通过vertexData的position 操作修改的数据的起始位置,即指向不同的内存段,而Stride参数表示一个顶点所有属性占用的内存。还需记住的一点是,我们输入的顶点数据在内存中是连续存放的

接着,在MyGLRenderer.java类中定义如下私有变量:

1
2
3
4
5
6
7
private static final int NUM_POSITION_COMPONENT = 2;
private static final int NUM_COLOR_COMPONENT = 3;
         
private static final int STRIDE = (NUM_POSITION_COMPONENT+NUM_COLOR_COMPONENT)*BYTES_PER_FLOAT;
         
private static final String A_COLOR = "a_Color";
private int aColorLocation;

最后,修改onDrawFrame:
1
2
3
4
5
6
public void onDrawFrame(GL10 gl) {
      glClear(GL_COLOR_BUFFER_BIT);
         
      // glUniform4f(uColorLocation, 1.0f, 0.0f, 1.0f, 1.0f);
       glDrawArrays(GL_TRIANGLES, 03);
   }
我们来看一下输出效果:

Learn OpenGLES:颜色渐变_第3张图片


由上面的步骤,我们可以总结一下写opengl的流程: 定义需求--》在shader中添加相应的属性和算法--》在onSurfaceCreated里添加获取shader里相应属性的位置信息和数据--》在onDrawFrame里完成相应的操作。

下面,我们将实现一个小例子: 
这个小例子实现的是一个中间发光的桌面。 我们将先实现画一个长方形,然后,通过颜色渐变完成发光效果。由于glDrawArrays的图形单元中,只接受点,线, 三角形等基本的图形,因此如果我们想画一个长方形,只需画两个三角形即可。

按照之前总结的流程, 首先修改shader。因为我们还只是用到两个顶点属性,不用其它操作,因此无需更改。 然后, 更改顶点数据: 用两个三角形画一个长方形的顺序如下:
Learn OpenGLES:颜色渐变_第4张图片
即按照 1-2-3  3-4-2画两个三角形即可。 但是根据需要,我们需要在长方形的中间高亮。 考虑到之前的颜色渐变,我们应该画如下的图:

Learn OpenGLES:颜色渐变_第5张图片
即按照0-1-2, 0-2-3, 0-3-4, 0-4-1, 然后为每个顶点赋予不同的颜色, 0点的颜色最亮即可。 但是一次画这么多三角形需要重复定义每个顶点数据,很费劲。好在opengl为我们提供了GL_TRIANGLE_FAN,这样的基本画法, 这样我们只需按照0-1-2-3-4-1定义顶点数据即可。如下,

1
2
3
4
5
6
7
// X,     Y,    R,    G,   B
  0f,     0f,   1.0f, 1.0f, 1.0f,
 -0.5f, -0.75f, 0.7f, 0.7f, 0.7f,
 0.5f, -0.75f,  0.7f, 0.7f, 0.7f,
 0.5f, 0.75f,   0.7f, 0.7f, 0.7f,
 -0.5f, 0.75f,  0.7f, 0.7f, 0.7f,
-0.5f, -0.75f,  0.7f, 0.7f, 0.7f,
然后再onDrawFrame里修改glDrawArrays

1
glDrawArrays(GL_TRIANGLE_FAN, 06);
这次,我们横竖屏的显示这一结果

Learn OpenGLES:颜色渐变_第6张图片

可以发现: 竖屏有些拉长了, 横屏还好一点。这是因为我们定义的长方形 长宽比为1.5:1,而我手机的 长宽比 1.7777:1, 我们已经知道我们所定义的点都会在X, Y轴为(-1, 1)的范围内, 这种设备归一化坐标不受屏幕分辨率的影响。但是glViewPort会考虑屏幕分辨率重新把点映射会屏幕坐标,如果我们保持宽(X轴)是1的情况下, 那么在竖屏状态下,它的高(Y轴)就拉伸了1.77倍。 因此,我们需要对顶点进行操作,即使得Y轴坐标能够缩小1.77倍, 然后再经过设备归一化,之后经过glViewPort就正常了。

我们可以利用一个被称为正交投影的矩阵实现这一步。 百度百科是如此定义正交投影的: 投影线垂直于投影面的投影属于正交投影 ,也称为平行投影。即这种变换不会产生近大远小的感觉,非常适合于2D的图像变换。

到这里,我们需要接触一些矢量, 矩阵的概念。 OpenGL经常会与这两个东西打交道。在解决手机横竖屏适配问题之前,也是为以后进入3D世界打下基础。下面几节,我会主要介绍这两个概念以及一些关于坐标系转换及OpenGL最终如何成像的问题。


————————————————————————————————————

ARVR技术交流群:129340649

欢迎加入!


你可能感兴趣的:(OpenGLES)