1

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 <android/log.h>

#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>

#include <stdio.h>
#include <stdlib.h>

#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

运行脚本,将可以看一个绿色的三角形,并且背景在不断变化。

图片描述


RichardXG
337 声望19 粉丝

引用和评论

0 条评论