Hello GLES2
和ES1.0不同,ES2.0引入了可编程管线,如下图中,可编程阶段为VertexShader,FragmentShader顶点和片段着色器阶段。
顶点着色器被使用在传统的基于顶点的操作,例如位移矩阵、计算光照方程、产生贴图
坐标。顶点着色器被应用指定,应用于客户端的顶点转化。顶点着色器需要一个位置和颜色数据作为输入属性,输入位置数据是 4×4 的矩阵,输出是变换后的位置和颜色。片段着色器不需定义输出,这是因为片段着色器仅仅的输出是gl_FragColor。现在我们用上一节的Android入口渲染一个三角形。这里借用NDK包里的例子hello-gl2。看里面的lesson2的tag
宏文件
#ifndef _APPMACROS_H__#define _APPMACROS_H__#include#include #include #include #include #define LOG_TAG "GLES-Tutorial"#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE,LOG_TAG,__VA_ARGS__)#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)static void printGLString(const char *name, GLenum s) { const char *v = (const char *) glGetString(s); LOGI("GL %s = %s\n", name, v);}static void checkGlError(const char* op) { for (GLint error = glGetError(); error; error = glGetError()) { LOGI("after %s() glError (0x%x)\n", op, error); }}#endif
这里除了android里的Log外添加了gl2.h, gl2ext.h头文件,这两个文件可以在NDK包(platforms/android-19/)中找到,这里使用了android里的libGLESv2.so库。printGLString可以获取到OpenGL属性的状态,checkGlError可以检测OpenGL状态是否有错误。
使用OpenGLES2
先给Director添加成员变量:
GLProgram *_glProgram; // opengl状态机 GLuint _vPositionHandle; // 获取到的顶点位置属性
#include "Director.h"#include "AppMacros.h"// 顶点着色器static const char gVertexShader[] = "attribute vec4 vPosition;\n" "void main() {\n" " gl_Position = vPosition;\n" "}\n";// 片段着色器static const char gFragmentShader[] = "precision mediump float;\n" "void main() {\n" " gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);\n" "}\n";// 三角形顶点数据const GLfloat gTriangleVertices[] = { 0.0f, 0.5f, -0.5f, -0.5f, 0.5f, -0.5f };// ... 省略掉一些void Director::setFrameSize(float width, float height){ LOGI("Director::setFrameSize(%lf, %lf)", width, height); _fFrameWidth = width; _fFrameHeight = height; // 创建一个空源的OpenGL状态机 _glProgram = new GLProgram(); // 将着色器装载和编译 _glProgram->initWithVertexShaderByteArray(gVertexShader, gFragmentShader); // 链接着色器 _glProgram->link(); // 使用此状态机,着色器将能运用上 _glProgram->use(); // 获取到位置属性,以便用来绘制图形;顶点着色器里的vPosition属性 _vPositionHandle = _glProgram->getAttribLocation("vPosition"); // 设置OpenGL视口,一个2D的长方形区域(Android里GLSurfaceView窗体的大小) glViewport(0, 0, width, height); checkGlError("glViewport");}void Director::mainLoop(){ // 每一帧都让灰度值叠加 static float grey; grey += 0.01f; if (grey > 1.0f) { grey = 0.0f; } // 用颜色来填充清除深度和颜色缓冲区 glClearColor(grey, grey, grey, 1.0f); glClear( GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); // 将顶点位置属性获取到 glVertexAttribPointer(_vPositionHandle, 2, GL_FLOAT, GL_FALSE, 0, gTriangleVertices); checkGlError("glVertexAttribPointer"); glEnableVertexAttribArray(_vPositionHandle); // 并将三角形顶点设置进顶点矩阵 checkGlError("glEnableVertexAttribArray"); glDrawArrays(GL_TRIANGLES, 0, 3); // 绘制三角形图元 checkGlError("glDrawArrays");}
GLProgram是我包装了OpenGL的一些操作,便于使用:
class GLProgram{public: GLProgram(); ~GLProgram(); /* * init GLProgram with vertex shader array data and fragment shader array data */ bool initWithVertexShaderByteArray(const GLchar* vShaderByteArray, const GLchar* fShaderByteArray); /* * link shader */ bool link(); /* * use this opengl program */ void use(); /* * get location attrib */ GLuint getAttribLocation(const GLchar* attrib);private: GLuint loadShader(GLenum shaderType, const GLchar* shaderSrc); GLuint _uProgram; GLuint _uVertShader; GLuint _uFragShader;};
bool GLProgram::initWithVertexShaderByteArray(const GLchar* vShaderByteArray, const GLchar* fShaderByteArray){ _uVertShader = loadShader(GL_VERTEX_SHADER, vShaderByteArray); if (!_uVertShader) { return false; } _uFragShader = loadShader(GL_FRAGMENT_SHADER, fShaderByteArray); if (!_uFragShader) { return false; } _uProgram = glCreateProgram(); if (!_uProgram) { return false; } glAttachShader(_uProgram, _uVertShader); checkGlError("glAttachShader"); glAttachShader(_uProgram, _uFragShader); checkGlError("glAttachShader"); return true;}bool GLProgram::link(){ glLinkProgram(_uProgram); GLint linkStatus = GL_FALSE; glGetProgramiv(_uProgram, GL_LINK_STATUS, &linkStatus); if (linkStatus != GL_TRUE) { GLint bufLength = 0; glGetProgramiv(_uProgram, GL_INFO_LOG_LENGTH, &bufLength); if (bufLength) { char* buf = (char*) malloc(bufLength); if (buf) { glGetProgramInfoLog(_uProgram, bufLength, NULL, buf); LOGE("Could not link _uProgram:\n%s\n", buf); free(buf); } } glDeleteProgram(_uProgram); _uProgram = 0; return false; } return true;}void GLProgram::use(){ glUseProgram(_uProgram); checkGlError("glUseProgram");}GLuint GLProgram::loadShader(GLenum shaderType, const char* shaderSrc){ GLuint shader = glCreateShader(shaderType); if (shader) { glShaderSource(shader, 1, &shaderSrc, NULL); glCompileShader(shader); GLint compiled = 0; glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); // check compile information if (!compiled) { GLint infoLen = 0; glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen); if (infoLen) { char* infoLog = (char*) malloc(infoLen); if (infoLog) { glGetShaderInfoLog(shader, infoLen, NULL, infoLog); // seek infor log LOGE("Could not compile shader %d:\n%s\n", shaderType, infoLog); free(infoLog); } glDeleteShader(shader); shader = 0; } } } return shader;}GLuint GLProgram::getAttribLocation(const GLchar* attrib){ GLuint retAtt = glGetAttribLocation(_uProgram, attrib); checkGlError("glGetAttribLocation"); LOGI("glGetAttribLocation(\"%s\") = %d\n", attrib, retAtt); return retAtt;}
OpenGL ES提供了一套运行期动态编译的流程:
1.创建着色器:glCreateShader
2.指定着色器源代码字符串:glShaderSource
3.编译着色器:glCompileShader
4.创建着色器可执行程序:glCompileShader
5.向可执行程序中添加着色器:glAttachShader
6.链接可执行程序:glLinkProgram
Run
编译之前,需要修改下Android.mk
LOCAL_SRC_FILES := \com_richard_glestutorial_GLRenderer.cpp \../core/Director.cpp \../core/GLProgram.cpp LOCAL_C_INCLUDES := \$(LOCAL_PATH)
$ndk-build && ant debug && adb install -r bin/GlesTutorial-debug.apk
运行脚本,将可以看一个绿色的三角形,并且背景在不断变化。