65.9K
CodeProject 正在变化。 阅读更多。
Home

Intel 的 Multi-OS Engine - OpenGL 示例

2016年1月14日

CPOL

5分钟阅读

viewsIcon

12383

本文介绍 Multi-OS Engine 中的 OpenGLBox 示例。

Intel® Developer Zone 提供跨平台应用开发工具和操作指南、平台和技术信息、代码示例以及同行专业知识,帮助开发者创新并取得成功。加入我们的社区,获取 Android物联网Intel® RealSense™ 技术Windows 的相关内容,下载工具,获取开发套件,与志同道合的开发者分享想法,并参加黑客松、竞赛、路演和本地活动。

本文介绍 Multi-OS Engine 中的 OpenGLBox 示例。这是一个真正意义上的跨平台示例。在下面的截图上,您可以看到相同的基于 Java 的应用程序在 iOS 和 Android 上看起来和行为方式都相同。不同平台的应用程序共享数据和部分源代码。这是如何实现的?请继续向下滚动!

每个 OpenGL 程序员至少都做过一次这个小程序:旋转的 3D 立方体。如果您不熟悉 3D 图形编程,也不要害怕。本示例将帮助您从零开始。如果您已经精通 OpenGL,本文将帮助您专注于 Android 和 iOS 开发的特定功能。让我们开始吧。

我们的几何图形有 6 个面,正如立方体应有的那样,以及 24 个顶点。为什么会有这么多顶点?每个面都具有 Multi-OS Engine 徽标的纹理。因此,我们需要将 8 个实际顶点中的每个顶点细分为 3 个具有不同纹理坐标的新顶点。

我们的场景中没有光照效果。因此,我们可以使用非常简单的着色器。顶点着色器仅变换几何图形并将纹理坐标传递给片段着色器。然后,片段着色器执行简单的纹理映射。

平台特定代码

Multi-OS Engine 不会不必要地隐藏平台特定代码。这对于移植和调试应用程序非常有用。每个平台的类都可以拥有自己的功能。

iOS
package com.intel.inde.moe.samples.openglbox.ios;

class Main extends NSObject implements UIApplicationDelegate
class OpenGLBoxController extends GLKViewController implements GLKViewDelegate
class ShaderProgram extends ShaderProgramBase

GLKit 框架提供了函数和类,可以减少创建新的基于着色器的应用程序的工作量。

GLKViewController

GLKViewController 类提供了所有标准的视图控制器功能,但还实现了 OpenGL ES 渲染循环。一个GLKViewController 对象与一个GLKView 对象协同工作,以在视图中显示动画帧。

GLKView

GLKView 类通过提供一个 OpenGL ES 感知的视图的默认实现,简化了创建 OpenGL ES 应用程序所需的工作。一个GLKView 会代表您的应用程序直接管理帧缓冲对象;当需要更新内容时,您的应用程序只需绘制到帧缓冲中。

GLKViewDelegate

实现GLKViewDelegate 协议的对象可以设置为GLKView 对象委托。委托允许您的应用程序为GLKView 对象提供绘图方法,而无需继承GLKView 类。

Android
package com.intel.inde.moe.samples.openglbox.android;

class OpenGLBoxActivity extends Activity
class SurfaceView extends GLSurfaceView
class BoxRenderer implements GLSurfaceView.Renderer
class ShaderProgram extends ShaderProgramBase

Android 框架中有两个基本类可以让您使用 OpenGL ES API 创建和操作图形:GLSurfaceViewGLSurfaceView.Renderer。要在 Android 应用程序中使用 OpenGL,需要了解如何在 Activity 中实现这些类。

GLSurfaceView

此类是一个View,您可以在其中使用 OpenGL API 调用绘制和操作对象,其功能类似于 SurfaceView。您可以通过创建 GLSurfaceView 实例并向其添加 Renderer 来使用此类。但是,如果您想捕获触摸屏事件,则应扩展 GLSurfaceView 类来实现响应触摸事件的触摸监听器 监听触摸事件

GLSurfaceView.Renderer

此接口定义了在 GLSurfaceView 中绘制图形所需的方法。您必须将此接口的实现提供为一个单独的类,并使用 GLSurfaceView.setRenderer() 将其附加到您的 GLSurfaceView 实例。

通用代码

OpenGL 代码在两个平台上的代码相似。这使我们能够将接口甚至抽象类提取到项目的公共部分。例如,处理着色器的类必须继承抽象基类 `ShaderProgramBase`

package com.intel.inde.moe.samples.openglbox.common;

public abstract class ShaderProgramBase {

    protected static int INVALID_VALUE = -1;
    protected int programHandle = INVALID_VALUE;
    protected HashMap<String, Integer> attributes = new HashMap<String, Integer>();

    public abstract void create(String vertexCode, String fragmentCode);
    public abstract void use();
    public abstract void unUse();
    public abstract int getAttributeLocation(String attribute);
    public abstract int getUniformLocation(String uniform);

    protected abstract int loadShader(int type, String shaderCode);

    public int getProgramHandle() {
        return programHandle;
    }
}

现在,让我们深入研究 `ShaderProgramBase.create()` 方法的平台特定实现。

iOS

package com.intel.inde.moe.samples.openglbox.ios;
...

public class ShaderProgram extends ShaderProgramBase {
...

    @Override
    public void create(String vertexCode, String fragmentCode) {
        ...

        programHandle = OpenGLES.glCreateProgram();
        OpenGLES.glAttachShader(programHandle, vertexShader);
        OpenGLES.glAttachShader(programHandle, fragmentShader);
        OpenGLES.glLinkProgram(programHandle);
    }
}

Android

package com.intel.inde.moe.samples.openglbox.android;
...

public class ShaderProgram extends ShaderProgramBase {
...

    @Override
    public void create(String vertexCode, String fragmentCode) {
        ...

        programHandle = GLES20.glCreateProgram();
        GLES20.glAttachShader(programHandle, vertexShader);
        GLES20.glAttachShader(programHandle, fragmentShader);
        GLES20.glLinkProgram(programHandle);
    }
}

正如您所见,这些代码片段看起来非常相似。可能看起来代码仅在前缀上有所不同 - **OpenGLES** 和 **GLES20**。不太正确,但差不离。仔细查看 `loadShader()` 方法。

iOS

package com.intel.inde.moe.samples.openglbox.ios;
...

public class ShaderProgram extends ShaderProgramBase {
...

    @Override
    protected int loadShader(int type, String shaderCode) {
        ...

        IntPtr status = PtrFactory.newIntReference();
        OpenGLES.glGetShaderiv(shader, ES2.GL_COMPILE_STATUS, status);
        if (status.getValue() == 0) {
            BytePtr info = PtrFactory.newWeakByteArray(256);
            OpenGLES.glGetShaderInfoLog(shader, 256, PtrFactory.newWeakIntReference(0), info);
            OpenGLES.glDeleteShader(shader);
            throw new IllegalArgumentException("Shader compilation failed with: " + info.toASCIIString());
        }
        ...
    }
}

Android

package com.intel.inde.moe.samples.openglbox.android;
...

public class ShaderProgram extends ShaderProgramBase {
...

    @Override
    protected int loadShader(int type, String shaderCode) {
        ...

        int[] status = new int[1];
        GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, status, 0);
        if (status[0] == 0) {
            String info = GLES20.glGetShaderInfoLog(shader);
            GLES20.glDeleteShader(shader);
            throw new IllegalArgumentException("Shader compilation failed with: " + info);
        }
        ...
    }
}

这段代码的区别在于我们传递 OpenGL 指针的方式。您将在其他地方看到类似的差异。

通用数据

现在是时候描述其他通用部分了。我们的应用程序使用以下通用数据。

  1. Geometry
    public class Geometry {
        public static final float[] VERTICES = new float[] { … };
        public static final byte[] INDICES = { … };
    }
  2. 着色器(作为 String 常量)
    public class Shaders {
        public static final String VERTEXT_SHADER = " ... ";
        public static final String FRAGMENT_SHADER = " ... ";
    }
  3. 参数(背景颜色、旋转速度、不同的相机相关值)
    public class Parameters {
        ...
        public static final float DEGREES_PER_SECOND = 90.0f;
        ...
    }

所有这些常量都是不言自明的。它们代表了通用的 3D 数据模型和操作逻辑。在更大的项目中,通用数据将更多。

3D 数学

OpenGL ES 2.0 及更高版本不提供创建或指定变换矩阵的内置函数。相反,可编程着色器提供顶点变换,您可以使用通用 uniform 变量指定着色器输入。

iOS

GLKit 框架包含一个全面的向量和矩阵类型及函数的库,该库针对 iOS 硬件上的高性能进行了优化。

viewMatrix = GLKit.GLKMatrix4MakeLookAt(
    0.0f, 0.0f, Parameters.EYE_Z, // eye
    0.0f, 0.0f, 0.0f, // center
    0.0f, 1.0f, 0.0f // up-vector
);
viewMatrix = GLKit.GLKMatrix4Rotate(viewMatrix,
    GLKit.GLKMathDegreesToRadians(Parameters.PITCH), 1, 0, 0);

Android

android.opengl.Matrix 提供数学实用程序。这些方法操作存储在 float 数组中的 OpenGL ES 格式矩阵和向量。

Matrix.setLookAtM(viewMatrix, 0,
    0.0f, 0.0f, Parameters.EYE_Z, // eye
    0.0f, 0.0f, 0.0f, // center
    0.0f, 1.0f, 0.0f // up-vector
);
Matrix.rotateM(viewMatrix, 0, Parameters.PITCH, 1, 0, 0);

但是,您可以自己实现基本的数学类,并增加通用代码的重用性。

加载纹理

iOS

GLKTextureLoader 类简化了加载纹理数据的操作。 GLKTextureLoader 类可以加载 Image I/O 框架支持的大多数图像格式的二维或立方体贴图纹理。在 iOS 上,它还可以加载 pvrtc 格式压缩的纹理。它可以同步或异步加载数据。

public static int loadGLTexture(String name, String extension) {
    NSMutableDictionary options = NSMutableDictionary.alloc().init();
    options.put(NSNumber.numberWithBool(true), GLKit.GLKTextureLoaderOriginBottomLeft());

    String path = NSBundle.mainBundle().pathForResourceOfType(name, extension);
    GLKTextureInfo info = GLKTextureLoader.textureWithContentsOfFileOptionsError(path, options, null);
    if (info == null) {
        System.out.println("Error loading file: " + name + "." + extension);
        return -1;
    }

    return info.name();
}

GLKTextureLoaderGLKTextureInfo 类不会为您管理 OpenGL 纹理。一旦纹理返回给您的应用程序,您就对其负责。这意味着在您的应用程序使用完 OpenGL 纹理后,必须通过调用 glDeleteTextures() 函数来显式取消分配它。

Android

无论好坏,我们都是手动实现的。

public static int loadGLTexture(Bitmap bitmap) {
    int[] textureIDs = new int[1];
    GLES20.glGenTextures(1, textureIDs, 0);

    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureIDs[0]);

    // Create Nearest Filtered Texture
    GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,
            GLES20.GL_LINEAR);
    GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER,
            GLES20.GL_LINEAR);

    GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT);
    GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT);

    // Use the Android GLUtils to specify a two-dimensional texture image from our bitmap
    GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);

    return textureIDs[0];
}

当然,您始终可以为这类 OpenGL 密集型代码编写自己的包装器。我们没有给自己设定这样的任务。

时序

iOS

方法 `timeSinceLastUpdate()` 返回自视图控制器上次调用委托的 `update()` 方法以来经过的时间量。

public void update() {
    rotation += Parameters.DEGREES_PER_SECOND * timeSinceLastUpdate();
    modelMatrix = GLKit.GLKMatrix4Identity();
    modelMatrix = GLKit.GLKMatrix4Rotate(modelMatrix, GLKit.GLKMathDegreesToRadians(rotation), 0, 1, 0);
}

Android

Android 上没有这样的方法。所以我们必须手动重新创建它。

private long lastTime;

public void onSurfaceCreated(GL10 unused, EGLConfig config) {
    …
    lastTime = System.nanoTime();
}

public void onDrawFrame(GL10 unused) {
    update();
    drawBox();
}

double timeSinceLastUpdate() {
    long time = System.nanoTime();
    double delta = (double) (time - lastTime) / 1000000000;
    lastTime = time;
    return delta;
}

private void update() {
    if (isPaused)
        return;
    rotation += Parameters.DEGREES_PER_SECOND * timeSinceLastUpdate();
    Matrix.setIdentityM(modelMatrix, 0);
    Matrix.rotateM(modelMatrix, 0, rotation, 0, 1, 0);
}

摘要

在 iOS 上使用 Java 进行 OpenGL 开发与在 Android 上进行开发非常相似,并具有许多优势。

  • OpenGL ES 是一个基于 C 的 API,iOS 和 Android 都支持它,具有很高的可移植性。
  • 熟悉的 Java 语法。
  • 使用强大的 iOS 模拟器和 Android 模拟器并排调试两个目标。
© . All rights reserved.