Android开发 OpenGL ES绘制3D 图形实例详解

OpenGL ES是 OpenGL三维图形API 的子集,针对手机、PDA和游戏主机等嵌入式设备而设计。 Ophone目前支持OpenGL ES 1.0 ,OpenGL ES 1.0 是以 OpenGL 1.3 规范为基础的,OpenGL ES 1.1 是以 OpenGL 1.5 规范为基础的。本文主要介绍利用OpenGL ES绘制图形方面的基本步骤。

本文内容由三部分构成。首先通过EGL获得OpenGL ES的编程接口;其次介绍构建3D程序的基本概念;最后是一个应用程序示例。

OpenGL ES 本质上是一个图形渲染管线的状态机,而 EGL 则是用于监控这些状态以及维护帧缓冲和其他渲染面的外部层。图1 是一个典型的 EGL 系统布局图。EGL 视窗设计是基于人们熟悉的用于 Microsoft Windows ( WGL )和 UNIX ( GLX )上的 OpenGL 的 Native 接口,与后者比较接近。 OpenGL ES 图形管线的状态被存储于 EGL 管理的一个上下文中。帧缓冲和其他绘制渲染面通过 EGL API 创建、管理和销毁。 EGL 同时也控制和提供了对设备显示和可能的设备渲染配置的访问。

图1

OpenGL ES 需要一个渲染上下文和渲染面。渲染上下文中存储OpenGL ES的状态信息,渲染面用于图元的绘制。编写OpenGL ES之前需要EGL的操作有:

查询设备可以支持的显示句柄,并初始化。

创建渲染面,绘制OpenGL ES图形。

创建渲染上下文。EGL需要创建OpenGL ES渲染上下文用于关联到某个渲染面。

Ophone中EGL包括4个类,分别是EGLDisplay:显示句柄、EGLConfig:配置类;EGLContext:渲染上下文;的类和EGLSurface:可渲染的视图类。

EGL可以认为成OpenGL ES和本地窗口系统之间的中间层。 本地窗口系统指GNU/Linux上X窗口系统,或者Mac OX X's Quartz等。在EGL确定渲染面的类型前,EGL需要和底层的窗口系统进行通讯。因为在不同的操作系统上的窗口系统的不同,EGL提供一个透明窗口类型,即EGLDisplay。它抽象了各种窗口系统。所以首先要创建、初始化一个EGLDisplay对象。

// EGLContext的静态方法getEGL获得EGL实例
EGL10 egl = (EGL10)EGLContext.getEGL();
//创建EGLDisplay, EGL_DEFAULT_DISPLAY获得缺省的本地窗口系统类型
EGLDisplay dpy = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
//初始化EGLDispla的同时获得版本号
int[] version = new int[2];
egl.eglInitialize(dpy, version);

每个 EGLDisplay 在使用前都需要初始化。初始化 EGLDisplay 的同时能够得到系统中 EGL 的实现版本号。通过版本号,合理运用相应OpenGL ES API,可以编写兼容性良好的程序,以适应更多的设备以及提供最大限度的移植性。初始化函数原型:
boolean eglInitialize(EGLDisplay display, int[] major_minor)

其中的display是一个有效的 EGLDisplay实例。函数调用完成时, major_minor将被赋予当前 EGL 版本号。比如 EGL1.0 , major_minor[0]为1,major_minor[1]为0。EGLSurface包含了EGL渲染面相关的所有信息。查询 EGLSurface配置信息有两种方法,一是查询所有的配置信息,从中选择一个最为适合的;二是指定好配置信息,由系统给出最佳匹配结果。一般采用第二种方法。用户通过configSpec指定出希望获得的配置,函数eglChooseConfig通过参数Configs返回最佳的配置列表。之后利用已获得的Configs,调用eglCreateContext创建一个渲染上下文,该函数返回EGLContext结构。渲染面EGLSurface的创建通过函数eglCreateWindowSurface完成。一个应用程序可以创建多个EGLContext。 eglMakeCurrent就是将某个渲染上下文绑定到渲染面。查询函数 eglGetCurrentContext, eglGetCurrentDisplay和eglGetCurrentSurface 分别用于获得当前系统的渲染上下文、显示句柄和渲染面。最后EGLContext的静态方法getGL获得OpenGL ES的编程接口。下面的程序片段总结了上述内容。

EGL10 egl = (EGL10)EGLContext.getEGL();
EGLDisplay dpy = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); int[] version = new int[2];
egl.eglInitialize(dpy, version);
int[] configSpec = {
EGL10.EGL_RED_SIZE, 5,
EGL10.EGL_GREEN_SIZE, 6,
EGL10.EGL_BLUE_SIZE, 5,
EGL10.EGL_DEPTH_SIZE, 16,
EGL10.EGL_NONE
};
EGLConfig[] configs = new EGLConfig[1];
int[] num_config = new int[1];
egl.eglChooseConfig(dpy, configSpec, configs, 1, num_config);
EGLConfig config = configs[0];
EGLContext context = egl.eglCreateContext(dpy, config,
EGL10.EGL_NO_CONTEXT, null);
EGLSurface surface = egl.eglCreateWindowSurface(dpy, config,
sHolder, null);
egl.eglMakeCurrent(dpy, surface, surface, context);
GL10 gl = (GL10)context.getGL();

构建3D图形的点

点是构建3D模型的基础。 OpenGL ES的内部计算是基于点的。 用点也可以表示光源的位置,物体的位置。一般我们用一组浮点数来表示点。 例如一个正方形的4个顶点可表示为:

float vertices[] = {
-1.0f, 1.0f, 0.0f, //左上
-1.0f, -1.0f, 0.0f, //左下
1.0f, -1.0f, 0.0f, //右下
1.0f, 1.0f, 0.0f, //右上
};

为了提高性能, 需要将浮点数组存入一个字节缓冲中。 所以有了下面的操作:

ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4);
vbb.order(ByteOrder.nativeOrder());
FloatBuffer vertexBuffer = vbb.asFloatBuffer();
vertexBuffer.put(vertices);
vertexBuffer.position(0);

其中ByteOrder.nativeOrder()是获取本机字节顺序。OpenGL ES有操作图形渲染管线的函数,在默认情况下这些函数功能的使用状态是处于关闭的。 启用和关闭这些函数可以用glEnableClientState、glDisableClientState来完成。

// 指定需要启用定点数组
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
// 说明启用数组的类型和字节缓冲,类型为GL_FLOAT
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer);
// 不再需要时,关闭顶点数组
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);

边是连接两个点的一条线,是多边形面的边缘。

多边形

多边形是由边构成的单闭合环。 OpenGL ES中的多边形必须是凸多边形,即在多边形的内部任意取两点, 如果连接这两个点的线段都在多变的内部,这个多边形就是凸多边形。 绘制多边形时需要指定渲染的方向, 分为顺时针和逆时针。 因为方向决定了多边形的朝向, 即正面和背面。 避免渲染那些被遮挡的部分可以了有效提高程序性能。 函数glFrontFace定义了渲染顶点的方向。

// 设置CCW方向为“正面”,CCW即CounterClockWise,逆时针
glFrontFace(GL_CCW);
// 设置CW方向为“正面”,CW即ClockWise,顺时针
glFrontFace(GL_CW);

渲染

有了以上的概念讲解后,现在要进行最主要的工作—渲染。渲染是把物体坐标所指定的图元转化成帧缓冲区中的图像。图像和顶点坐标有着密切的关系。这个关系通过绘制模式给出。常用到得绘制模式有GL_POINTS、GL_LINE_STRIP、

GL_LINE_LOOP、GL_LINES、 GL_TRIANGLES、GL_TRIANGLE_STRIP、GL_TRIANGLE_FAN。下面分别介绍:
GL_POINTS:把每一个顶点作为一个点进行处理,顶点n即定义了点n,共绘制n个点。

GL_LINES:把每一个顶点作为一个独立的线段,顶点2n-1和2n之间共定义了n个线段,总共绘制N/2条线段。,如果N为奇数,则忽略最后一个顶点。

GL_LINE_STRIP:绘制从第一个顶点到最后一个顶点依次相连的一组线段,第n和n+1个顶点定义了线段n,总共绘制N-1条线段。

GL_LINE_LOOP:绘制从定义第一个顶点到最后一个顶点依次相连的一组线段,然后最后一个顶点与第一个顶点相连。第n和n+1个顶点定义了线段n,然后最后一个线段是由顶点N和1之间定义,总共绘制N条线段。

GL_TRIANGLES:把每三个顶点作为一个独立的三角形。顶点3n-2,3n-1和3n定义了第n个三角形,总共绘制N/3个三角形。

GL_TRIANGLE_STRIP:绘制一组相连的三角形。对于奇数点n,顶点n,n+1和n+2定义了第n个三角形;对于偶数n,顶点n+1,n和n+2定义了第n个三角形,总共绘制N-2个三角形。

GL_TRIANGLE_FAN:绘制一组相连的三角形。三角形是由第一个顶点及其后给定的顶点所确定。顶点1,n+1和n+2定义了第n个三角形,总共绘制N-2个三角形。

绘制函数:

void glDrawArrays(int mode, int first, int count)
void glDrawElements(int mode, int count, int type, Buffer indices)

glDrawArrays创建一个几何图元序列,使用每个被的数组中从first开始,到first + count – 1结束的数组元素, mode为绘制模式。

glDrawElements使用count个元素定义一个图元序列,type是indices数组中的数据类型,mode为绘制模式,indices数组存储顶

点的索引值。

应用举例

利用上面讲解的内容给出一个Ophone上绘制一个3D球形的程序。效果图如下:

图2 球形示例

主要的绘制程序:

static private FloatBuffer vertex;//顶点对应的字节缓冲
static private FloatBuffer normal;//法向量对应的字节缓冲
float[] lightPos = new float[] {10.0f, 10.0f, 10.0f, 1.0f };//光源的坐标
private static final int STEP = 24;//
private static final float RADIUS = 1.0f;//半径
protected void init(GL10 gl) {
gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);//设置背景颜色
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, lightPos, 0);
gl.glEnable(GL10.GL_LIGHTING);//启用光照
gl.glEnable(GL10.GL_LIGHT0); //打开光源
gl.glClearDepthf(1.0f);//设置深度缓存
gl.glDepthFunc(GL10.GL_LEQUAL);//设置深度缓存比较函数,GL_LEQUAL表示新的像素的深度缓存值小于等于当前像素的深度缓存值时通过深度测试
gl.glEnable(GL10.GL_DEPTH_TEST);//启用深度缓存
gl.glEnable(GL10.GL_CULL_FACE);
gl.glShadeModel(GL10.GL_SMOOTH);//设置阴影模式GL_SMOOTH
}
protected void drawFrame(GL10 gl) {
gl.glClear(GL10.GL_COLOR_BUFFER_BIT |
GL10.GL_DEPTH_BUFFER_BIT);
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
GLU.gluLookAt(gl, 0, 0, 7f, 0f, 0f, 0f, 0f, 1.0f, 0.0f);//
drawSphere(gl, RADIUS, STEP, STEP); //绘制球形
}
public static void gluLookAt (GL10 gl, float eyeX, float eyeY, float eyeZ, float centerX, float centerY, float centerZ, float upX, float upY, float upZ)

它共接受三组坐标,分别为eye、 center和up。eye表示我们眼睛在"世界坐标系"中的位置,center表示眼睛"看"的那个点的坐标,up坐标表示观察者本身的方向,如果将观察点比喻成我们的眼睛,那么这个up则表示我们是正立还是倒立异或某一个角度在看,这里是正立方式,所以是{0,1,0}。

private static void drawSphere(GL10 gl, float radius,
int stacks, int slices) {
vertex=allocateFloatBuffer( 4* 6 * stacks * (slices+1) );
normal=allocateFloatBuffer( 4* 6 * stacks * (slices+1) );
int i, j, triangles;
float slicestep, stackstep;
stackstep = ((float)Math.PI) / stacks;
slicestep = 2.0f * ((float)Math.PI) / slices;
for (i = 0; i < stacks; ++i)
{
float a = i * stackstep;
float b = a + stackstep;
float s0 = (float)Math.sin(a);
float s1 = (float)Math.sin(b);
float c0 = (float)Math.cos(a);
float c1 = (float)Math.cos(b);
float nv;
for (j = 0; j <= slices; ++j)
{
float c = j * slicestep;
float x = (float)Math.cos(c);
float y = (float)Math.sin(c);
nv=x * s0;
normal.put(nv);
vertex.put( nv * radius);
nv=y * s0;
normal.put(nv);
vertex.put( nv * radius);
nv=c0;
normal.put(nv);
vertex.put( nv * radius);
nv=x * s1;
normal.put(nv);
vertex.put( nv * radius);
nv=y * s1;
normal.put(nv);
vertex.put( nv * radius);
nv=c1;
normal.put(nv);
vertex.put( nv * radius);
}
}
normal.position(0);
vertex.position(0);
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertex);
gl.glNormalPointer(GL10.GL_FLOAT, 0, normal);
gl.glEnableClientState (GL10.GL_VERTEX_ARRAY);
gl.glEnableClientState (GL10.GL_NORMAL_ARRAY);
triangles = (slices + 1) * 2;
for(i = 0; i < stacks; i++)
gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP,
i * triangles, triangles);
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
gl.glDisableClientState(GL10.GL_NORMAL_ARRAY);
}
private static FloatBuffer allocateFloatBuffer(int capacity){
ByteBuffer vbb = ByteBuffer.allocateDirect(capacity);
vbb.order(ByteOrder.nativeOrder());
return vbb.asFloatBuffer();
}

总结:

本文介绍了Ophone中利用OpenGL ES绘制图形的基本概念和方法。OpenGL ES中还有很多其他内容,诸如纹理、光照和材质、混合、雾、蒙板、反射、3D模型的加载等。利用OpenGL ES函数可以绘制丰富的图形应用和游戏界面。

(0)

相关推荐

  • Android编程绘制圆形图片的方法

    本文实例讲述了Android编程绘制圆形图片的方法.分享给大家供大家参考,具体如下: 效果图如下: 第一步:新建RoundView自定义控件继承View package com.rong.activity; import com.rong.test.R; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import android.grap

  • Android中使用ListView绘制自定义表格技巧分享

    先上一下可以实现的效果图  要实现的效果有几方面 1.列不固定:可以根据数据源的不同生成不同的列数 2.表格内容可以根据数据源的定义合并列 3.要填写的单元格可以选择自定义键盘还是系统键盘 奔着这三点,做了个简单的实现,把源码贴一下(因为该点是主界面中的一部分,不便于放整个Demo) 自定义适配器,CallBackInterface是自定义的回调接口,这里定义回调是因为数据输入时需要及时保存 复制代码 代码如下: public class SiteDetailViewAdapter extend

  • Android QQ登录界面绘制代码

    先看看效果图: 首先过程中碰到的几个问题: 1.对 EditText 进行自定义背景 2.运行时自动 EditText 自动获得焦点 3.在获得焦点时即清空 hint ,而不是输入后清空 4.清空按钮的出现时机(在得到焦点并且有输入内容时) ---  这些问题都有一一解决 --- 以下是代码: 布局 fragment_main(问题2) <!-- android:focusable="true" android:focusableInTouchMode="true&qu

  • Android开发中使用achartengine绘制各种图表的方法

    本文实例讲述了Android开发中使用achartengine绘制各种图表的方法.分享给大家供大家参考,具体如下: 1. ABarChart.java package com.anjoyo.achartengine; import java.util.Random; import org.achartengine.ChartFactory; import org.achartengine.chart.BarChart.Type; import org.achartengine.model.Cat

  • Android开发笔记之:在ImageView上绘制圆环的实现方法

    绘制圆环其实很简单,有大概以下三种思路. 这里先说网上提到的一种方法.思路是先绘制内圆,然后绘制圆环(圆环的宽度就是paint设置的paint.setStrokeWidth的宽度),最后绘制外圆.请看核心源码: 复制代码 代码如下: <SPAN xmlns="http://www.w3.org/1999/xhtml">package yan.guoqi.rectphoto;import android.content.Context;import android.graph

  • Android自定义View之继承TextView绘制背景

    本文实例为大家分享了TextView绘制背景的方法,供大家参考,具体内容如下 效果: 实现流程: 1.初始化:对画笔进行设置 mPaintIn = new Paint(); mPaintIn.setAntiAlias(true); mPaintIn.setDither(true); mPaintIn.setStyle(Paint.Style.FILL); mPaintIn.setColor(getResources().getColor(R.color.colorPrimary)); mPain

  • Android中使用achartengine生成图表的具体方法

    今天在做项目的时候用到了图表功能,记录下来 achartengine是google的一个开源项目,可以在https://code.google.com/p/achartengine/ 下载技术文档,jar包以及项目源代码 demo下载:https://code.google.com/p/achartengine/downloads/list 一.饼状图 新建工程,添加achartengine  jar包 PieChart.java 复制代码 代码如下: package com.meritit.f

  • Android百度地图实现搜索和定位及自定义图标绘制并点击时弹出泡泡

    一.问题描述 上一次我们使用百度地图实现基本的定位功能,接下来我们继续实现搜索和定位,并使用LocationOverlay绘制定位位置,同时展示如何使用自定义图标绘制并点击时弹出泡泡 如图所示: 二.编写MyApplication类 public class MyApplication extends Application { private static MyApplication mInstance = null; public boolean m_bKeyRight = true; pu

  • Android编程开发之在Canvas中利用Path绘制基本图形(圆形,矩形,椭圆,三角形等)

    本文实例讲述了Android编程开发之在Canvas中利用Path绘制基本图形的方法.分享给大家供大家参考,具体如下: 在Android中绘制基本的集合图形,本程序就是自定义一个View组件,程序重写该View组件的onDraw(Canvase)方法,然后在该Canvas上绘制大量的基本的集合图形. 直接上代码: 1.自定义的View组件代码: package com.infy.configuration; import android.content.Context; import andro

  • 解决Android SurfaceView绘制触摸轨迹闪烁问题的方法

    本文分享了解决SurfaceView触摸轨迹闪烁问题的方法,供大家参考,具体内容如下 第一种解决SurfaceView触摸轨迹闪烁问题的方法: 由于SurfaceView使用双缓存机制,两张画布轮流显示到屏幕上.那么,要存储触摸轨迹并避免两张画布内容不一致造成的闪烁问题,完全可以利用保存绘制过程并不断重新绘制的方法解决闪烁,而且这样还顺带解决了多次试验中偶尔出现的因为moveTo()函数不能读取到参数执行默认设置(参数设为上次的触摸点)而出现的断线连接闪烁问题,详细代码如下: package c

  • Android listView 绘制表格实例详解

    Android  listView 绘制表格 效果图: 二,创建步骤: 1,创建布局: activity_main中的布局: <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:orientation="vertical" android:

随机推荐