OpenGL ES 2.0 系列一
闲言少叙,先从一段最简单的示例开始吧,这样可以直观一点。
// Hello_Triangle.c
//
// 这是一段绘制三角形的代码,使用了最最简单的顶点着色器和片元着色器,
// 目的在于演示一下 OpenGL ES 2.0 的渲染流程
#include <stdlib.h>
// 这里引入了需要用到的工具方法库,其与具体渲染流程关系不大,可以先不必纠结
// 工具方法均为esXXX()格式
#include "Common/esUtil.h"
typedef struct
{
// programObject 引用
GLuint programObject;
} UserData;
// 创建shader
GLuint LoadShader(GLenum type, const char *shaderSrc)
{
GLuint shader;
GLint compiled;
// 生成一个shader引用
shader = glCreateShader(type);
if (shader == 0)
return 0;
// 载入shader的源代码
glShaderSource(shader, 1, &shaderSrc, NULL);
// 编译shader
glCompileShader(shader);
// 看看shader的编译有没有哪里报错
glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
// 这个if语句块中的内容可以暂时忽略,主要是获取OpenGL的一些状态信息
if (!compiled)
{
GLint infoLen = 0;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
if (infoLen > 1)
{
char* infoLog = malloc(sizeof(char) * infoLen);
glGetShaderInfoLog(shader, infoLen, NULL, infoLog);
esLogMessage("Error compiling shader:\n%s\n", infoLog);
free(infoLog);
}
glDeleteShader(shader);
return 0;
}
return shader;
}
///
// 初始化shader
//
int Init(ESContext *esContext)
{
UserData *userData = esContext->userData;
GLbyte vShaderStr[] =
"attribute vec4 vPosition; \n"
"void main() \n"
"{ \n"
" gl_Position = vPosition; \n"
"} \n";
GLbyte fShaderStr[] =
"precision mediump float;\n"\
"void main() \n"
"{ \n"
" gl_FragColor = vec4 ( 1.0, 0.0, 0.0, 1.0 );\n"
"} \n";
GLuint vertexShader;
GLuint fragmentShader;
GLuint programObject;
GLint linked;
// 调用上面载入 shader 的方法
vertexShader = LoadShader(GL_VERTEX_SHADER, vShaderStr);
fragmentShader = LoadShader(GL_FRAGMENT_SHADER, fShaderStr);
// 创建一个 program 引用
programObject = glCreateProgram();
if (programObject == 0)
return 0;
// 分别将 vertex/fragment shader 添加到 program 中
glAttachShader(programObject, vertexShader);
glAttachShader(programObject, fragmentShader);
// 绑定 vPosition 到 attribute 0
glBindAttribLocation(programObject, 0, "vPosition");
// 链接 program
glLinkProgram(programObject);
// 看看有没出错
glGetProgramiv(programObject, GL_LINK_STATUS, &linked);
if (!linked)
{
GLint infoLen = 0;
glGetProgramiv(programObject, GL_INFO_LOG_LENGTH, &infoLen);
if (infoLen > 1)
{
char* infoLog = malloc(sizeof(char) * infoLen);
glGetProgramInfoLog(programObject, infoLen, NULL, infoLog);
esLogMessage("Error linking program:\n%s\n", infoLog);
free(infoLog);
}
glDeleteProgram(programObject);
return FALSE;
}
// 存储一下 program 引用
userData->programObject = programObject;
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
return TRUE;
}
///
// 利用上面的 shader 来进行三角形的绘制
//
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 };
// 设置 viewport
glViewport(0, 0, esContext->width, esContext->height);
// 清一下颜色 buffer
glClear(GL_COLOR_BUFFER_BIT);
// 这里告诉 OpenGL 你要使用这个 program 来进行后续的绘制
glUseProgram(userData->programObject);
// 将三角形的顶点信息传递到 vertex shader 中
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, vVertices);
glEnableVertexAttribArray(0);
glDrawArrays(GL_TRIANGLES, 0, 3);
eglSwapBuffers(esContext->eglDisplay, esContext->eglSurface);
}
int main(int argc, char *argv[])
{
ESContext esContext;
UserData userData;
esInitContext(&esContext);
esContext.userData = &userData;
// 创建一个用于绘制的窗口
esCreateWindow(&esContext, "Hello Triangle", 320, 240, ES_WINDOW_RGB);
if (!Init(&esContext))
return 0;
esRegisterDrawFunc(&esContext, Draw);
esMainLoop(&esContext);
}
上面的这段代码展示了如何绘制一个三角形,功能很基础。但是,好像步骤可不少呢,下面结合这个三角形的示例,来介绍一下OpenGL ES 2.0的渲染流程。
上面这个图片很清晰地展示了每个图形是怎样渲染出来的。其中灰色方框中”Vertex Shader”和”Fragment Shader”就是我们可以对整个渲染过程进行干预的最主要场所。
GLfloat vVertices[] = { 0.0f, 0.5f, 0.0f,
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f };
这段代码就是图中的Vertex Arrays,为最初始的原料,三角形的三个顶点坐标。
然后就是准备好Vertex Shader:
GLbyte vShaderStr[] =
"attribute vec4 vPosition; \n"
"void main() \n"
"{ \n"
" gl_Position = vPosition; \n"
"} \n";
这一小段代码就是我们的顶点着色器,它有一个attribute修饰的vec4类型变量vPosition,很明显这就是用来存放顶点坐标的。
shader = glCreateShader(type);
glShaderSource(shader, 1, &shaderSrc, NULL);
glCompileShader(shader);
每个 shader 都需要经过这几个步骤,在编译成功之后进行接下来的处理
// 创建一个 program 引用
programObject = glCreateProgram();
// 分别将 vertex/fragment shader 添加到 program 中
glAttachShader(programObject, vertexShader);
glAttachShader(programObject, fragmentShader);
// 链接 program
glLinkProgram(programObject);
这样,最终有了一个可以在渲染中使用的 program ,由 vertex shader 和 program shader 编译最终生成的。
接下来要做的就是将需要渲染的原料(顶点数组)传入到 shader 中,让它来进行处理。
// 绑定 vPosition 到 attribute 0
glBindAttribLocation(programObject, 0, "vPosition");
// 这里告诉 OpenGL 你要使用这个 program 来进行后续的绘制
glUseProgram(userData->programObject);
// 将三角形的顶点信息传递到 vertex shader 中
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, vVertices);
glEnableVertexAttribArray(0);
这样,顶点数组 vVertices 就被传递到 vertex shader 中了,被赋给了 vPosition 这个变量。在 vertex shader 中我们并没有对顶点做什么特殊的处理,而是把它赋值给gl_Position = vPosition;
gl_position
这个变量是 vertex shader 中自带的变量之一,它会自动被传递到渲染管线的下一个流程。
Primitive Assembly 和 Rasterization 阶段简要说来就是进行图形的组装,根据顶点来生成图形,同时进行一些坐标的变换。这两个阶段也可以通过 OpenGL 的API来进行一些简单的设置,已达到不同的效果。
我们没有使用纹理,接下来就到了 Fragment Shader 了。
GLbyte fShaderStr[] =
"precision mediump float;\n"\
"void main() \n"
"{ \n"
" gl_FragColor = vec4 ( 1.0, 0.0, 0.0, 1.0 );\n"
"} \n";
顶点着色器中的代码是以顶点为单位进行处理的,片元处理器则是对每个片元(可暂时理解为需要绘制的每个像素)进行处理,也就是有多少个像素,main()
中的代码就会执行多少次。在其中,我们可以对每个片元进行任何想做的操作,比如改变其颜色、位置等等来达到想要的效果。在我们三角形的示例中仅仅是简单地为每个片元设置了颜色
gl_FragColor = vec4 ( 1.0, 0.0, 0.0, 1.0 );
其中 gl_FragColor
是 Fragment Shader 内置的变量,它用来存储每个片元的颜色信息,然后传递到渲染管线的下一个流程里。当然,同时传递到下一个流程的还有片元的其它额外信息。
Per-Fragment Operations 包括了下面这些操作,都是针对每个片元进行的
同样 OpenGL 提供了一些 API 可以对这些步骤进行设置来达到不同的效果。
FrameBuffer 就是最终的渲染结果了,只要把它显示到屏幕上就可以了。