一个完整的示例代码


来自于《OpenGL ES 2.0 Programming Guide》,画一个三角形。

Hello_Triangle.c

完整的示例代码,参见 Google Code

本书主页参见 OpenGL ES 2.0 Programming Guide

代码解释


初始化

OpenGL ES 2.0 所有接口参考手册:链接

1. 创建并编译 Vertex shader 和 Fragment shader

GLuint glCreateShader(GLenum shaderType);

void glShaderSource(GLuint shader,
                    GLsizei count,
                    const GLchar * const *string,
                    const GLint *length);

void glCompileShader(GLuint shader);

void glGetShaderiv( GLuint shader,
                    GLenum pname,
                    GLint *params);

void glDeleteShader(GLuint shader);

示例中创建了两个简单的shader,其源码分别是:

Vertex shader:

attribute vec4 vPosition; 
void main()                  
{                            
    gl_Position = vPosition;  
}  

Fragment shader:

precision mediump float;
void main()                                 
{                                            
    gl_FragColor = vec4 ( 1.0, 0.0, 0.0, 1.0 );
}  

2. 创建 Program object 并连接两个 shader

GLuint glCreateProgram( void);

void glAttachShader(GLuint program,
                    GLuint shader);

3. 链接 Program object

void glLinkProgram( GLuint program);

void glGetProgramiv(GLuint program,
                    GLenum pname,
                    GLint *params);

void glDeleteProgram(GLuint program);

绘图

初始化工作完毕,主要完成了:

  • 创建两个shader并编译
  • 创建一个程序对象并链接,并将两个shader与之关联起来

由于初始化过程中,调用了:

// Bind vPosition to attribute 0   
glBindAttribLocation ( programObject, 0, "vPosition" );

使得索引0处的属性与 Vertex shader 的 vPosition 输入变量关联起来了。

其后,调用:

// Load the vertex data
glVertexAttribPointer ( 0, 3, GL_FLOAT, GL_FALSE, 0, vVertices );
glEnableVertexAttribArray ( 0 );

glDrawArrays ( GL_TRIANGLES, 0, 3 );

将 vVertices 与第0个索引的属性关联起来,也就是与 vPosition 关联起来。

更多


这里产生出几个问题:

  • glBindAttribLocation 的使用,如果改变索引数值或者改变变量字符串名称,会发生什么?
  • 如何把上面示例中的 Fragment shader 中的 gl_FragColor 改成像顶点那样,由程序输入?
  • 如何画多边形?
  • 如何画圆形、椭圆形?

第一个问题:

在 shader 源码中,可以定义多个 attribute,实际上这些 attribute 的顺序跟其在源码中定义的先后顺序是没有什么关系的。这才需要绑定,使得我们可以用固定的索引值来访问这些属性。

第二个问题:

方案一

Vertex shader 改成:

attribute vec4 aFragColor;   
attribute vec4 vPosition;    
varying vec4 vFragColor;     
void main()                  
{                           
    gl_Position = vPosition;  
    vFragColor = aFragColor;  
} 

Fragment shader 改成:

precision mediump float;
varying vec4  vFragColor;
void main()                                 
{                                           
    gl_FragColor = vFragColor;                 
}  

绘图函数改成:

void Draw ( ESContext *esContext )
{
    UserData *userData = esContext->userData;
    GLfloat vVertices[] = {0.0f,  0.5f, 0.0f, 
                       -0.5f, -0.5f, 0.0f,
                        0.5f, -0.5f, 0.0f};
    GLfloat vColors[] = {1.0, 0.0, 0.0, 1.0,
                    1.0, 0.0, 0.0, 1.0,
                    1.0, 0.0, 0.0, 1.0};

    // Set the viewport
    glViewport ( 0, 0, esContext->width, esContext->height );

    // Clear the color buffer
    glClear ( GL_COLOR_BUFFER_BIT );

    // Use the program object
    glUseProgram ( userData->programObject );

    // Load the vertex data
    glVertexAttribPointer ( 0, 3, GL_FLOAT, GL_FALSE, 0, vVertices );
    glEnableVertexAttribArray ( 0 );

    glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, vColors);
    glEnableVertexAttribArray(1);

    glDrawArrays ( GL_TRIANGLES, 0, 3 );
}

初始化函数的改动:

// Bind vPosition to attribute 0   
glBindAttribLocation (programObject, 0, "vPosition" );
glBindAttribLocation(programObject, 1, "aFragColor");

可以看到:

  1. 我们在 Vertex shader 和 Fragment shader 加入了属性 vFragColor 和 aFragColor。
  2. 在初始化时,调用 glBindAttribLocation 将 aFragColor 绑定到第1个索引上。
  3. 在绘图时,调用 glVertexAttribPointer 并在第一个参数传入索引值1,最后一个参数传入颜色值数值 vColors,这样就终于将 vColors 和 aFragColor 关联起来了。

这里又有个小问题:如果我们把三个顶点的颜色值设成不一样的,会发生什么?

答案是,OpenGL 流水线会进行插值运算,这个发生在流水线的「光栅化」阶段。因此,我们将会看到,绘制出来的图形以三个顶点为出发点,发生颜色的渐变。这是插值运算的结果。因为我们只指定了三个顶点的颜色,而实际绘制的命令是三角形,这中间的所包围的所有像素点,颜色值怎么办?就是用插值运算,根据这三个顶点的颜色来平均。

opengles_color

方案二

从上面看到,假定三角形是纯色的,而我们却提供了一个 vColors 数组,数组的三个颜色值是一样的,这样显得很累赘。有没有更加简洁的方法呢?

答案是有。我们可以使用被称为「constant vertex data」的属性,也就是 aFragColor 将变成所有顶点所共享,而非每个顶点各自的属性。要达到这个目的,shader 部分不用修改,只需修改 Draw 函数:

void Draw ( ESContext *esContext )
{
    UserData *userData = esContext->userData;
    GLfloat vVertices[] = {
                            0.0f, 0.5f, 0.0f,
                            -0.5f, -0.5f, 0.0f,
                             0.5f, -0.5f, 0.0f};

    GLfloat vColors[] = {1.0f, 0.0f, 0.0f, 1.0f};

    // Set the viewport
    glViewport ( 0, 0, esContext->width, esContext->height );

    // Clear the color buffer
    glClear ( GL_COLOR_BUFFER_BIT );

    // Use the program object
    glUseProgram ( userData->programObject );

    // Load the vertex data
    glVertexAttribPointer ( 0, 3, GL_FLOAT, GL_FALSE, 0, vVertices );
    glEnableVertexAttribArray ( 0 );

    glVertexAttrib4fv(1, vColors);

    glDrawArrays ( GL_TRIANGLES, 0, 3 );
}

问题的关键是,我们调用了:

glVertexAttrib4fv(1, vColors);

glVertexAttrib* — specify the value of a generic vertex attribute

后两个问题留待下篇解决。