[转]Android 3D 编程:HelloArrow(用 OpenGL ES 2.0 实现)

本文来自:http://www.cnblogs.com/bpasser/archive/2011/10/19/2217411.html

本文为转载,版权为原作者所有!

运行时检测OpenGL ES版本

对此Android文档中并没有提及,但是<NDK>/sampels/hello-gl2 示例工程中透露出了一些蛛丝马迹,从中我们可以归纳出,要进行OpenGL ES 2.0的渲染,需要有2个步骤:

(1)创建OpenGL ES 2.0 的 Rendering Context

EGL 创建 Context 的函数是

EGLContext eglCreateContext(EGLDisplay display,
    EGLConfig config,
    EGLContext share_context,
    EGLint const * attrib_list)

最后一个参数我们一般会给一个NULL值。根据 EGL 1.0 规范,最后一个参数不使用,但是EGL实现可能扩展此参数用于特定目的。Android 的 EGL 正是在此处引入一个值为 0x3098 的属性,用于指定 OpenGL ES 的版本。OpenGL ES 2.0 的版本值为 2

(2)获取 OpenGL ES 2.0 的 EGL Config

对于

EGLBoolean eglChooseConfig(EGLDisplay display,
    EGLint const * attrib_list,
    EGLConfig * configs,
    EGLint config_size,
    EGLint * num_config)

的 attrib_list 参数,Android 引入了一个 EGL10.EGL_RENDERABLE_TYPE=0x3040 的属性,EGL 1.0 规范中并未规定此属性。要进行OpenGL ES 2.0 的渲染,需指定此属性值为 4

根据以上两点,我们可以在运行时首先初始化 EGL 支持 OpenGL ES 2.0,如果失败,则回退到 OpenGL ES 1.x,如下:

public class EGLHelper {

    public static final int OPENGL_ES_VERSION_1x = 1;
    public static final int OPENGL_ES_VERSION_2 = 2;
    private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
    private static final int EGL_OPENGL_ES2_BIT = 4;

    private EGL10 egl;
    private EGLDisplay eglDisplay;
    private EGLConfig eglConfig;
    private EGLContext eglContext;
    private EGLSurface eglSurface;

    public EGLHelper() {
    }

    public boolean initialize(SurfaceHolder holder, int glesVersion) {

        if (OPENGL_ES_VERSION_1x != glesVersion && OPENGL_ES_VERSION_2 != glesVersion) {
            throw new IllegalArgumentException("GL ES version has to be one of " + OPENGL_ES_VERSION_1x + " and "
                    + OPENGL_ES_VERSION_2);
        }

        // ...

        // Choose an EGLConfig
        int[] attrList;
        if (OPENGL_ES_VERSION_1x == glesVersion) {
            attrList = new int[] { //
            EGL10.EGL_SURFACE_TYPE, EGL10.EGL_WINDOW_BIT, //
                    EGL10.EGL_RED_SIZE, 8, //
                    EGL10.EGL_GREEN_SIZE, 8, //
                    EGL10.EGL_BLUE_SIZE, 8, //
                    EGL10.EGL_DEPTH_SIZE, 16, //
                    EGL10.EGL_NONE //
            };
        } else {
            attrList = new int[] { //
            EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, //
                    EGL10.EGL_SURFACE_TYPE, EGL10.EGL_WINDOW_BIT, //
                    EGL10.EGL_RED_SIZE, 8, //
                    EGL10.EGL_GREEN_SIZE, 8, //
                    EGL10.EGL_BLUE_SIZE, 8, //
                    EGL10.EGL_DEPTH_SIZE, 16, //
                    EGL10.EGL_NONE //
            };
        }
        EGLConfig[] configOut = new EGLConfig[1];
        int[] configNumOut = new int[1];
        if (egl.eglChooseConfig(eglDisplay, attrList, configOut, 1, configNumOut) && 1 == configNumOut[0]) {
            eglConfig = configOut[0];
        } else {
            // ...
            return false;
        }

        // Create rendering context
        int[] contextAttrs;
        if (OPENGL_ES_VERSION_1x == glesVersion) {
            contextAttrs = null;
        } else {
            contextAttrs = new int[] { EGL_CONTEXT_CLIENT_VERSION, OPENGL_ES_VERSION_2, //
                    EGL10.EGL_NONE };
        }
        eglContext = egl.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT, contextAttrs);
        if (null == eglContext || EGL10.EGL_NO_CONTEXT == eglContext) {
            // ...
            return false;
        }

        // ...

        return true;
    }

    public void destroy() {
        // ...
    }
}

然后:

            eglHelper = new EGLHelper();
            if (eglHelper.initialize(getHolder(), EGLHelper.OPENGL_ES_VERSION_2)) {
                // Create RenderingEngine2
            } else if (eglHelper.initialize(getHolder(), EGLHelper.OPENGL_ES_VERSION_1x)) {
                // Create RenderingEngine1
            } else {
                // ...
            }

Shader

OpenGL ES 2.0 引入了 Shader 的概念。Shader 就是交给 OpenGL 去执行的一段程序,用 OpenGL Shading Language(GLSL)编写。Shader 分为 Vertex Shader 和 Fragement Shader 两类,Vertex Shader 用于处理通过 glDrawArrays() 提交给 OpenGL 的顶点数据,Fragement Shader 用于计算顶点的颜色

目前我所知的就是这么多了。本篇 HellowArrow 程序所用的 Vertex Shader 和 Fragement Shader 我直接从原书复制。Vertex Shader 源代码文件 Shade.vert 内容为

const char* VERTEX_SHADER =STRINGIFY(

attribute vec4 Position;
attribute vec4 SourceColor;
varying vec4 DestinationColor;
uniform mat4 Projection;
uniform mat4 Modelview;

void main(void)
{
    DestinationColor = SourceColor;
    gl_Position = Projection * Modelview * Position;
}

);

很怪异的语法,后面再学习。Fragement Shader 源文件 Shader.frag 的内容为:

const char * FRAG_SHADER=STRINGIFY(

varying lowp vec4 DestinationColor;
void main(void)
{
    gl_FragColor = DestinationColor;
}

);

这两个 Shader 源文件其实是两个 STRINGIFY 宏的扩展(或者说调用吧),GLSL 作为宏变量 。STRINGIFY 宏定义在 RenderingEngine2.c 中,并且用#include 将这两个文件的内容包含进来:

#define STRINGIFY(A) #A
#include "Shader.vert"
#include "Shader.frag"

注意上面 STRINGIFY 宏定义中“#A”前缀的 # 符号。# 是一个 c 预处理符,它将宏变量转换为字符串字面值,例如:

#define AS_STRING(A) #A
const char * str = AS_STRING(this is a c string!);
const char * str_qt = AS_STRING("this is a quoted c string!");

等效于

const char * str = "this is a c string!";
const char * str_qt = "\"this is a quoted c string!\"";

因此,前面的 RenderingEngine2.c 代码片断定义了 VERTEX_SHADER 和 FRAG_SHADER 两个字符串变量,字符串内容分别为 Vertex Shade 和 Fragement Shader 代码。这两段代码会在运行时提交给 OpenGL 编译并执行

RenderingEngine2.c

RenderingEngine2.c 对应于前一篇中的 RenderingEngine1.c,OpenGL 绘图操作(包括动画)都在这个文件中实现,只不过这次用 OpenGL ES 2.0 而不是 OpenGL ES 1.x 来实现。下面我只列出 RenderingEngine2.c 与 RenderingEngine1.c 中不同的地方

initialize()

与 RenderingEngine1.c 的 initialize() 函数一样,主要功能还是设置 Viewport 和正交投影矩阵,代码为:

static GLuint simpleProgram;

void initialize(int width, int height) {
 
    glViewport(0, 0, width, height);

    simpleProgram = buildProgram(VERTEX_SHADER, FRAG_SHADER);
    glUseProgram(simpleProgram);

    // Initialize the projection matrix.
    applyOrtho(2, 3); 
}

glViewport() 函数在前一篇讲过了,它设置 OpenGL 绘图的范围

接下来出现了 OpenGL ES 2.0 的新东西。OpenGL ES 2.0 将几个 Shader 链接成一个单元,称为 Program(程序),我们这里用一个 buildProgram() 函数创建一个 Program,它由前面定义的 Vertex Shader 和 Fragement Shader 构成。buildProgram() 函数的代码是:

static GLuint buildProgram(const char* vertexShaderSource,
        const char* fragmentShaderSource) {
    GLuint vertexShader = buildShader(vertexShaderSource, GL_VERTEX_SHADER);
    GLuint fragmentShader = buildShader(fragmentShaderSource, GL_FRAGMENT_SHADER);
    GLuint programHandle = glCreateProgram();
    glAttachShader(programHandle, vertexShader);
    glAttachShader(programHandle, fragmentShader);
    glLinkProgram(programHandle);
    GLint linkSuccess;
    glGetProgramiv(programHandle, GL_LINK_STATUS, &linkSuccess);
    if (linkSuccess == GL_FALSE) {
        // ...
    }
    return programHandle;
}

buildShader() 是自定义函数,用 GLSL 创建 Shader,稍后再看这个函数。其他的几个 glXxx() 函数,从函数名就能看出它的作用

buildShader() 代码为:

static GLuint buildShader(const char* source, GLenum shaderType) {
    GLuint shaderHandle = glCreateShader(shaderType);
    glShaderSource(shaderHandle, 1, &source, 0);
    glCompileShader(shaderHandle);
    GLint compileSuccess;
    glGetShaderiv(shaderHandle, GL_COMPILE_STATUS, &compileSuccess);
    if (compileSuccess == GL_FALSE) {
        // ...
    }
    return shaderHandle;
}

很直观的过程:创建、指定GLSL代码、编译

前一篇中用 OpenGL ES 1.0 设置正交投影的过程是:

    glMatrixMode(GL_PROJECTION);
    // ...
    glOrthof(-maxX, maxX, -maxY, maxY, -1.0f, 1.0f);
    glMatrixMode(GL_MODELVIEW);

但现在通过自定义 applyOrtho() 函数来设置OpenGL ES 的正交投影矩阵:

static void applyOrtho(float maxX, float maxY) {
    float a = 1.0f / maxX;
    float b = 1.0f / maxY;
    float ortho[16] = {//
            a, 0, 0, 0, //
            0, b, 0, 0, //
            0, 0, -1, 0,//
            0, 0, 0, 1 //
            };
    GLint projectionUniform = glGetUniformLocation(simpleProgram,
            "Projection");
    glUniformMatrix4fv(projectionUniform, 1, 0, &ortho[0]);
}

太奥妙了,直接上矩阵了,不懂。后面再慢慢淆吧:)

render()

render() 又是一段看不懂的代码啊:

void render() {
    glClearColor(0.5f, 0.5f, 0.5f, 1);
    glClear(GL_COLOR_BUFFER_BIT);

    applyRotation(-currentDegree);

    GLuint positionSlot = glGetAttribLocation(simpleProgram, "Position");
    GLuint colorSlot = glGetAttribLocation(simpleProgram, "SourceColor");
    glEnableVertexAttribArray(positionSlot);
    glEnableVertexAttribArray(colorSlot);

    GLsizei stride = sizeof(struct Vertex);
    const GLvoid* pCoords = vertices[0].position;
    const GLvoid* pColors = vertices[0].color;
    glVertexAttribPointer(positionSlot, 2, GL_FLOAT, GL_FALSE, stride, pCoords);
    glVertexAttribPointer(colorSlot, 4, GL_FLOAT, GL_FALSE, stride, pColors);

    GLsizei vertexCount = sizeof(vertices) / sizeof(struct Vertex);
    glDrawArrays(GL_TRIANGLES, 0, vertexCount);

    glDisableVertexAttribArray(positionSlot);
    glDisableVertexAttribArray(colorSlot); 
}

glClearColor()、glClear() 清屏

旋转图形,原来 OpenGL ES 1.0 是 glRotatef(),现在通过自定义函数 applyRotate():

static void applyRotation(float degrees) {
    float radians = degrees * 3.14159f / 180.0f;
    float s = sin(radians);
    float c = cos(radians);
    float zRotation[16] = { //
            c, s, 0, 0, //
            -s, c, 0, 0,//
            0, 0, 1, 0,//
            0, 0, 0, 1//
            };
    GLint modelviewUniform = glGetUniformLocation(simpleProgram, "Modelview");
    glUniformMatrix4fv(modelviewUniform, 1, 0, &zRotation[0]);
}

跟前面 initialize() 里面的 applyOrtho() 一样,直接用矩阵运算。。。无论如何,applyRotate() 将图形旋转了 -currentDegree,这个和前一篇最终结果是一样的

接下来又不同了。OpenGL ES 1.0 通过 glVertexPointer()、glColorPointer() 函数将顶点坐标、颜色分量数组的地址提交给OpenGL,现在对应的函数是 两个 glXxxAttribPointer(),它们的第1个参数似乎和前面的 Vertex Shader 联系上了。。。大概的流程是一致的。。。先这样吧

updateAnimation()、onRotate()

这两个函数只是更新 desiredDegree 和 currentDegree 这2个状态变量,与 RenderingEngine1.c 中的一模一样

本篇完

本篇用OpenGL ES 2.0 重新实现了 RenderingEngine 接口。对 Shader、GLSL 以及使用 2.0 与 1.0 进行3D渲染之间的区别留下了第一印象,但是,未知和疑问也更多了。路漫漫其修远兮。。。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值