English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Esempio dettagliato di disegno di grafici 3D con OpenGL ES per la sviluppo Android

OpenGL ES è una sottoselezione dell'API OpenGL per grafica 3D, progettata per dispositivi embedded come telefoni cellulari, PDA e console di gioco. Ophone supporta attualmente OpenGL ES 1.0, che si basa sullo standard OpenGL 1.3, e OpenGL ES 1.1, che si basa sullo standard OpenGL 1.5. Questo articolo introduce i passaggi di base per disegnare grafica utilizzando 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 OS 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);
// Ottenere il numero di versione durante l'inizializzazione di EGLDisplay
int[] version = new int[2];
egl.eglInitialize(dpy, version);

Ogni EGLDisplay deve essere inizializzato prima dell'uso. Durante l'inizializzazione di EGLDisplay, è possibile ottenere il numero di versione dell'implementazione EGL del sistema. Utilizzando il numero di versione, è possibile scrivere programmi compatibili in modo ragionevole, per adattarsi a più dispositivi e fornire la massima portabilità. L'原型 della funzione di inizializzazione:
boolean eglInitialize(EGLDisplay display, int[] major_minor)

Il display è un'istanza valida di EGLDisplay. Al completamento della chiamata di funzione, major_minor verrà assegnato al numero di versione corrente di EGL. Ad esempio EGL1.0, major_minor[0] è 1, major_minor[1] è 0. EGLSurface contiene tutte le informazioni relative alla superficie di rendering EGL. Ci sono due metodi per consultare le informazioni di configurazione di EGLSurface: uno è consultare tutte le informazioni di configurazione e scegliere una più adatta; l'altro è specificare le informazioni di configurazione e ottenere il miglior risultato di corrispondenza fornito dal sistema. Generalmente si utilizza il secondo metodo. L'utente specifica le configurazioni desiderate tramite configSpec, e la funzione eglChooseConfig restituisce una lista dei migliori configurazioni. Successivamente, utilizzando le Configurations acquisite, si chiama eglCreateContext per creare un contesto di rendering, che restituisce una struttura EGLContext. La creazione della superficie di rendering EGLSurface avviene tramite la funzione eglCreateWindowSurface. Un'applicazione può creare più EGLContext. eglMakeCurrent è il processo di associare un contesto di rendering a una superficie di rendering. Le funzioni di ricerca eglGetCurrentContext, eglGetCurrentDisplay e eglGetCurrentSurface vengono utilizzate rispettivamente per ottenere il contesto di rendering attuale del sistema, il gestore di display e la superficie di rendering. Infine, il metodo statico getGL di EGLContext ottiene l'interfaccia di programmazione OpenGL ES. Il seguente frammento di codice riassume il contenuto sopra descritto.

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();

Punti per la costruzione di grafici 3D

Il punto è la base per la costruzione di modelli 3D. I calcoli interni di OpenGL ES si basano sui punti. I punti possono anche rappresentare la posizione della fonte di luce, la posizione degli oggetti. Di solito si utilizza un insieme di numeri a virgola mobile per rappresentare i punti. Ad esempio, i 4 vertici di un quadrato possono essere rappresentati come:

float vertices[] = {
-1.0f, 1.0f, 0.0f, // in alto a sinistra
-1.0f, -1.0f, 0.0f, // in basso a sinistra
1.0f, -1.0f, 0.0f, // in basso a destra
1.0f, 1.0f, 0.0f, // in alto a destra
};

Per migliorare le prestazioni, è necessario memorizzare l'array di valori a virgola mobile in un buffer di byte. Ecco le seguenti operazioni:

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

ByteOrder.nativeOrder() è usato per ottenere l'ordine dei byte del sistema. OpenGL ES ha funzioni per operare sulla pipeline di rendering grafica, che per impostazione predefinita sono disabilitate. È possibile abilitare e disabilitare queste funzioni con glEnableClientState e glDisableClientState.

// Specifica l'array di vertex da abilitare
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
// Spiega il tipo di array abilitato e il buffer di byte, il tipo è GL_FLOAT
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer);
// Chiude l'array di vertici quando non è più necessario
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);

Lato

Un lato è una linea che collega due punti, è l'edge di una faccia poligonale.

Poligono

Un poligono è un anello chiuso composto da lati. Nei poligoni di OpenGL ES devono essere poligoni convessi, ovvero in ogni punto interno del poligono, se si collegano due punti, la linea che li unisce deve rimanere all'interno del poligono. Quando si disegna un poligono è necessario specificare la direzione di rendering, che può essere orizzontale o verticale. La direzione determina la direzione del poligono, ossia il lato anteriore e il retro. Evitare di rendere le parti nascoste può migliorare significativamente le prestazioni del programma. La funzione glFrontFace definisce la direzione di rendering dei vertici.

// Imposta la direzione CCW come "fronte", CCW significa CounterClockWise, orizzontale
glFrontFace(GL_CCW);
// Imposta la direzione CW come "fronte", CW significa ClockWise, orizzontale
glFrontFace(GL_CW);

Rendering

Dopo aver spiegato i concetti sopra menzionati, ora è necessario procedere con il lavoro più importante: la rendering. La rendering trasforma gli elementi di coordinate degli oggetti in immagini nel buffer di frame. Le immagini e le coordinate dei vertici sono strettamente correlate. Questa relazione è fornita dallo stile di disegno. Modelli di disegno comuni includono GL_POINTS, GL_LINE_STRIP,

GL_LINE_LOOP, GL_LINES, GL_TRIANGLES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN. Di seguito è riportata una descrizione dettagliata di ciascuno di essi:
GL_POINTS: Ogni vertice è trattato come un punto, il vertice n definisce il punto n, e vengono disegnati n punti in totale.

GL_LINES: Ogni vertice è considerato come un segmento di linea indipendente, tra il vertice 2n-1 e 2n sono definiti n segmenti, per un totale di N/2 segmenti da disegnare. Se N è un numero dispari, l'ultimo vertice viene ignorato.

GL_LINE_STRIP: Disegna un gruppo di segmenti connessi da definire dal primo vertice all'ultimo vertice in successione, il n-esimo e il n+1-esimo vertice definiscono il segmento n, vengono disegnati in totale N-1 segmenti.


GL_LINE_LOOP: Disegna un gruppo di segmenti connessi da definire dal primo vertice all'ultimo vertice in successione, quindi ilultimo vertice è connesso al primo vertice. Il n-esimo e il n+1-esimo vertice definiscono il segmento n, quindi l'ultimo segmento è definito dai vertici N e 1, vengono disegnati in totale N segmenti.


GL_TRIANGLES: Considera ogni tre vertici come un triangolo indipendente. I vertici 3n-2, 3n-1 e 3n definiscono il n-esimo triangolo, vengono disegnati in totale N/3 triangoli.

GL_TRIANGLE_STRIP: Disegna un gruppo di triangoli connessi. Per il punto n dispari, i vertici n, n+1 e n+2 definiscono il n-esimo triangolo; per il punto n pari, i vertici n+1, n e n+2 definiscono il n-esimo triangolo, vengono disegnati in totale N-2 triangoli.


GL_TRIANGLE_FAN: Disegna un gruppo di triangoli connessi. I triangoli sono determinati dal primo vertice e dai vertici successivi forniti. Il vertice 1, n+1 e n+2 definiscono il n-esimo triangolo, vengono disegnati in totale N-2 triangoli.

Funzione di disegno:

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

glDrawArrays crea una sequenza di elementi geometrici, utilizzando gli elementi dell'array da first a first + count - 1, mode è il modo di disegno.

glDrawElements utilizza count elementi per definire una sequenza di elementi, type è il tipo dei dati nell'array indices, mode è il modo di disegno, l'array indices memorizza i vertici

Il valore dell'indice del punto.

Esempio di applicazione

Utilizzando il contenuto spiegato sopra, forniamo un programma per disegnare una sfera 3D su un Ophone. L'effetto visivo è come segue:


Figura 2: Esempio sferico

Il principale programma di disegno:

static private FloatBuffer vertex;//Buffer di byte corrispondente ai vertici
static private FloatBuffer normal;//Buffer di byte corrispondente alla normale
float[] lightPos = new float[] {10.0f, 10.0f, 10.0f, 1.0f };//La coordinate della sorgente di luce
private static final int STEP = 24; //}}
private static final float RADIUS = 1.0f; // raggio
protected void init(GL10 gl) {
gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // imposta il colore di sfondo
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, lightPos, 0);
gl.glEnable(GL10.GL_LIGHTING); // abilita l'illuminazione
gl.glEnable(GL10.GL_LIGHT0); // accendi la luce
gl.glClearDepthf(1.0f); // imposta il buffer di profondità
gl.glDepthFunc(GL10.GL_LEQUAL); // imposta la funzione di confronto del buffer di profondità, GL_LEQUAL indica che il valore di profondità del nuovo pixel è inferiore o uguale al valore di profondità del pixel corrente quando passa il test di profondità
gl.glEnable(GL10.GL_DEPTH_TEST); // abilita il buffer di profondità
gl.glEnable(GL10.GL_CULL_FACE);
gl.glShadeModel(GL10.GL_SMOOTH); // imposta il modello di ombreggiatura 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); // disegna la sfera
}
public static void gluLookAt (GL10 gl, float eyeX, float eyeY, float eyeZ, float centerX, float centerY, float centerZ, float upX, float upY, float upZ)

Esso accetta tre gruppi di coordinate, rispettivamente eye, center e up. Eye rappresenta la posizione dell'occhio nel sistema di coordinate "mondo", center rappresenta la posizione del punto che l'occhio "vede", e l'orientamento della coordinate up rappresenta la direzione dell'osservatore. Se si paragona il punto di osservazione all'occhio, l'up rappresenta se siamo in posizione eretta, inversa o in un certain angolo di osservazione, in questo caso è in posizione eretta, quindi è {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();
}

Sommario:

Questo articolo introduce i concetti di base e i metodi per disegnare grafici utilizzando OpenGL ES in Ophone. In OpenGL ES ci sono molte altre funzionalità, come le texture, la luce e i materiali, la mescolanza, la nebbia, i mascheroni, i riflessi, il caricamento dei modelli 3D, ecc. Utilizzando le funzioni OpenGL ES è possibile disegnare applicazioni grafiche ricche e interfacce di gioco.

Ti potrebbe interessare