Intel 的 Multi-OS Engine - OpenGL 示例





5.00/5 (1投票)
本文介绍 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 创建和操作图形:GLSurfaceView 和 GLSurfaceView.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 指针的方式。您将在其他地方看到类似的差异。
通用数据
现在是时候描述其他通用部分了。我们的应用程序使用以下通用数据。
- Geometry
public class Geometry { public static final float[] VERTICES = new float[] { … }; public static final byte[] INDICES = { … }; }
- 着色器(作为 String 常量)
public class Shaders { public static final String VERTEXT_SHADER = " ... "; public static final String FRAGMENT_SHADER = " ... "; }
- 参数(背景颜色、旋转速度、不同的相机相关值)
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();
}
GLKTextureLoader 和 GLKTextureInfo 类不会为您管理 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 模拟器并排调试两个目标。