OpenGL.ES在Android上的简单实践:3-曲棍球(顶点归一化、增加颜色)

OpenGL.ES在Android上的简单实践:3-曲棍球(顶点归一化 、增加颜色)

 

1、顶点归一化

承接上 简单实践系列文章:2。   运行程序后,大家看见了什么,是不是如下图? what the fxxk?!

 

以上这个问题详细原因很复杂,随着文章深入,答案自然就会迎刃而解;目前,我们来认识一件事,无论是x坐标还是y坐标,OpenGL都会把屏幕映射到 [-1,1]的范围内。这就意味着屏幕的左边对应x轴的-1,而屏幕的右边对应+1,;屏幕的底边会对应y轴的-1,而屏幕的定边就对应+1

不管屏幕是什么形状和大小,这个坐标范围都是一样的,如果我们需要在屏幕上显示任何东西,都需要在这个范围内绘制它们。让我们回归到tableVerticesWithTriangles,重新定义这些坐标点。

float[] tableVerticesWithTriangles = {
            // 第一个三角形
            -0.5f, -0.5f,
            0.5f, 0.5f,
            -0.5f, 0.5f,
            // 第二个三角形
            -0.5f, -0.5f,
            0.5f, -0.5f,
            0.5f, 0.5f,
            // 中间的分界线
            -0.5f, 0f,
            0.5f, 0f,
            // 两个木槌的质点位置
            0f, -0.25f,
            0f, 0.25f
    };

这次又看到什么呢,是不是如下图所示?

这看起来比之前的好多了,但是木槌位置的质点呢?在OpenGL看来,对于点来说,是需要指定在屏幕上所显示的点的大小的。我们修改顶点着色器simple_vertex_shader.glsl 文件,如下:

attribute vec4 a_Position;

void main()
{
    gl_Position = a_Position;
    gl_PointSize = 10.0; //指定点的大小为10.0
}

通过给另外一个特殊的输出变量gl_PointSize赋值,我们告诉OpenGL这些点的大小应该是10。你可能会懊恼,怎么又蹦出了一个特殊变量啊,究竟有多少个这些特殊变量啊。 其实我之前刚开始学的时候也一脸懵逼,随后慢慢的就开始接受了,并整理了一堆shader的内置变量/常量/函数,详细请参考这里面的 “一些基本的glsl概念”。

这次运行程序,效果应该不差了吧。看看是不是下面这个状态:

 

 

现在看起来有模有样了,但还是单调了点了,离我们想要的效果还差多着呢~是时候为我们的曲棍球增添色彩了。


2、顶点间完成平滑着色

之前我们了解到uniform里用单一的颜色绘制顶点片段,其实OpenGL还允许我们平滑地混合一条直线或一个三角形的表面上,每个顶点的颜色值。现在计划使用这种平滑着色,使得桌子中心表现得更加明亮,而其他边缘显得比较暗淡,这就好像一盏灯挂在桌子中间的上方一样。然而,在做这些之前,我们需要更新桌子的顶点结构。

现在我们是用两个三角形绘制桌子的,我们怎样才能让中间显得更明亮呢?我们需要在中间位置加入这个点,这样,就可以在桌子的中间和边缘之间混合颜色。所以,我们以下图的形式更新桌子顶点结构吧。

让我们按照上图更新tableVerticesWithTriangles的顶点数据,并修改onDrawFrame的代码

float[] tableVerticesWithTriangles = {
             第一个三角形
            //-0.5f, -0.5f,
            //0.5f, 0.5f,
            //-0.5f, 0.5f,
             第二个三角形
            //-0.5f, -0.5f,
            //0.5f, -0.5f,
            //0.5f, 0.5f,
            // 三角扇
             0,     0,
            -0.5f, -0.5f,
             0.5f, -0.5f,
             0.5f,  0.5f,
            -0.5f,  0.5f,
            -0.5f, -0.5f,
            // 中间的分界线
            -0.5f, 0f,
            0.5f, 0f,
            // 两个木槌的质点位置
            0f, -0.25f,
            0f, 0.25f
    };
    @Override
    public void onDrawFrame(GL10 gl10) {
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);

        //GLES20.glUniform4f(uColorLocation, 1.0f,1.0f,1.0f, 1.0f);
        //GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 6);
        GLES20.glUniform4f(uColorLocation, 1.0f,1.0f,1.0f, 1.0f);
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_FAN, 0, 6);
        
        GLES20.glUniform4f(uColorLocation, 1.0f,0.0f,0.0f, 1.0f);
        GLES20.glDrawArrays(GLES20.GL_LINES, 6, 2);

        GLES20.glUniform4f(uColorLocation, 0.0f,0.0f,1.0f, 1.0f);
        GLES20.glDrawArrays(GLES20.GL_POINTS, 8, 1);
        GLES20.glUniform4f(uColorLocation, 0.0f,1.0f,0.0f, 1.0f);
        GLES20.glDrawArrays(GLES20.GL_POINTS, 9, 1);
    }

这里引入三角扇GL_TRIANGLE_FAN标志,从示意图我想大家都能弄懂什么是三角扇了。 一个三角扇形以一个中心顶点作为开始,使用相邻的两个顶点创建第一个三角形,接下来的每个顶点都会创建一个三角形,围绕起始的中心点按扇形展开。为了使这个扇形闭合,我们只需要在最后重复第二个点即可。
之后我们glDrawArrays选择绘制GL_TRIANGLE_FAN,从位标0开始,共6个点。   这时候看看运行状态吧。

 

通过在桌子中心增加一个额外的点,我们已经更新了桌子的数据结构,现在我们可以给每个点加入一个颜色属性。让我们把整个数据的数组更新如下:

float[] tableVerticesWithTriangles = {
            //  X, Y,        R, G, B
            // 三角扇形
        0,     0,     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,
        // 中间的分界线
       -0.5f,    0f,     1f,  0f,  0f,
        0.5f,    0f,     1f,  0f,  0f,
        // 两个木槌的质点位置
        0f,    -0.25f,    0f,  0f,  1f,
        0f,     0.25f,    1f,  0f,  0f,
    };

下一步就是从着色器中去掉uniform定义的颜色,并用一个属性替换它。接下来会更新Java代码以体现这段新的着色器代码。

attribute vec4 a_Position;
attribute vec4 a_Color;

varying vec4 v_Color;

void main()
{
    v_Color = a_Color;
    gl_Position = a_Position;
    gl_PointSize = 20.0;
}

我们加入了一个新的属性a_Color,也加入了一个叫做v_Color的新的varying。 可能会问“varying究竟是什么呀?”还记得我们说过我们需要在一个三角形平面上让颜色产生变化(vary)么? 就是通过这个被称为varying的特殊的变量类型实现的。为了更好地理解一个varying是做什么的,让我回顾一下图元光栅化的过程。
当OpenGL构建一条直线的时候,它会用两个顶点构成这条直线,并为这条直线生成片段;当OpenGL构建一个三角形的时候,它会同样使用三个顶点构建这个三角形。然后,对于每一个被生成的片段,片段着色器都会被执行一次。

varying是一个特殊的变量类型,它把给它的那些值进行混合,并把这些混合后的值发送给片段着色器。如果顶点0的a_Color是红色,且顶点1的a_Color是绿色,然后,通过把a_Color赋值給v_Color,来告诉OpenGL我们需要每个片段都接收一个混合的颜色。接近顶点0的片段,混合后的颜色显得更红,而接近顶点1的片段,颜色就会越绿。

 

接下来,我们把varying也加入到片段着色器。修改simple_fragment_shader.glsl,修改如下:

precision mediump float;

//uniform vec4 u_Color;
varying vec4 v_Color;

void main()
{
    gl_FragColor = v_Color;
}

我们用varying变量v_Color替换了原来的uniform。如果那个片段属于一个三角形,那OpenGL就会用构成那个三角形的三个顶点计算其混合的颜色。

既然已经更新了着色器,我们也需要在Renderer类中更新java代码,以便我们传递新的颜色属性给顶点着色器的a_Color。

      private static final int BYTES_PER_FLOAT = 4;
    private static final int POSITION_COMPONENT_COUNT = 2;
    private static final int COLOR_COMPONENT_COUNT = 3;
    private static final int STRIDE = (POSITION_COMPONENT_COUNT + COLOR_COMPONENT_COUNT) * BYTES_PER_FLOAT;

      //private static final String U_COLOR = "u_Color";
    //private int uColorLocation;
    private static final String A_POSITION = "a_Position";
    private int aPositionLocation;
    private static final String A_COLOR = "a_Color";
    private int aColorLocation;

      @Override
    public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
        GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
        int programId = ShaderHelper.buildProgram(vertexShaderSource, fragmentShaderSource);
        GLES20.glUseProgram(programId);
        // uColorLocation = GLES20.glGetUniformLocation(programId, U_COLOR);
        aPositionLocation = GLES20.glGetAttribLocation(programId, A_POSITION);
        aColorLocation = GLES20.glGetAttribLocation(programId, A_COLOR);


        vertexData.position(0);
        GLES20.glVertexAttribPointer(aPositionLocation, POSITION_COMPONENT_COUNT,
                GLES20.GL_FLOAT, false, STRIDE, vertexData);
        GLES20.glEnableVertexAttribArray(aPositionLocation);


        vertexData.position(2);
        GLES20.glVertexAttribPointer(aColorLocation, COLOR_COMPONENT_COUNT,
                GLES20.GL_FLOAT, false, STRIDE, vertexData);
        GLES20.glEnableVertexAttribArray(aColorLocation);
    }

这些代码比较重要,因此让我们花点时间仔细地理解每一行代码:

        1、首先,我们可以去掉那些旧的常量和与u_Color相关的变量。 然后增加一个名为“STRIDE”的特殊常量,因为我们现在在同一个数据数组里面既有位置又有颜色属性,OpenGL不能再假定下一个位置是紧跟着前一个位置的。一旦OpenGL读入了一个顶点的位置,如果它想读入下一个顶点的位置,它不得不跳过当前顶点的颜色数据。我们要用那个跨距(stride)告诉OpenGL每个位置之间有多少个字节,这样它就知道需要跳过多少了。

        2、我们把vertexData的位置设为POSITION_COMPONENT_COUNT,这个值被设为2。为什么执行这一步呢?这是因为,当OpenGL开始读入颜色属性时,我们要它从第一个颜色属性开始,而不是第一个位置属性。当需要跳过第一个位置时,我们就要把位置分量的大小计算在内;把那个位置设置为POSITION_COMPONENT_COUNT,缓冲区的指针就被调整到第一个颜色属性的位置了。

        3、接下来,我们调用glVertexAttribPointer把颜色数据和着色器的a_Color关联起来。那个跨距告诉OpenGL每个颜色之间有多少个字节,这样,当需要读入所有顶点的颜色时,它就知道要读取下一个顶点的颜色需要跳过多少个字节了。跨距以字节为单位是非常重要的。 尽管OpenGL中的一种颜色有四个分量(红、绿、蓝和透明度),我们并不必要指定所有分量,OpenGL会用默认值替换属性中未指定值的分量。前三个分量默认为0,第四个分量被设为1。

        4、最好,就像前面讲过的位置属性一样,我们要使能颜色属性数组。

 

 

我们就剩下最后一件事情了,更新onDrawFrame。既然我们已经把顶点数据和a_Color关联起来了,只需要调用glDrawArrays即可,OpenGL会自动从vertexData数据里读入颜色属性。

    @Override
    public void onDrawFrame(GL10 gl10) {
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);

        //GLES20.glUniform4f(uColorLocation, 1.0f,1.0f,1.0f, 1.0f);
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_FAN, 0, 6);

        //GLES20.glUniform4f(uColorLocation, 1.0f,0.0f,0.0f, 1.0f);
        GLES20.glDrawArrays(GLES20.GL_LINES, 6, 2);

        //GLES20.glUniform4f(uColorLocation, 0.0f,0.0f,1.0f, 1.0f);
        GLES20.glDrawArrays(GLES20.GL_POINTS, 8, 1);
        //GLES20.glUniform4f(uColorLocation, 0.0f,1.0f,0.0f, 1.0f);
        GLES20.glDrawArrays(GLES20.GL_POINTS, 9, 1);
    }

看看是不是以下效果吧。

 

 

小结:

        因为我们已经有一个基本的框架,给每一个顶点增加颜色并不太困难。为此,我们给顶点数据和顶点着色器增加了一个新的属性,并且告诉OpenGL如何使用跨距读入数据。接着我们学习了如何使用一个varying在三角形平面上进行插值。

        一个需要记住的重要内容:当传递属性数据时,我们要确保给分量计算和跨距(字节位单位)传递正确的值。如果它们错了,我们最终可能会看到一个混乱的屏幕。

 

 

 

 

 

 

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mr_Zzr

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值