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

Подробное объяснение и пример кода рисования 3D-графики с использованием OpenGL ES в Android

OpenGL ES является подмножеством OpenGL API для трёхмерной графики, предназначенного для嵌入式 устройств, таких как мобильные телефоны, PDAs и игровые консоли. Ophone в настоящее время поддерживает 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-окна спроектированы на основе знакомых интерфейсов OpenGL для Microsoft Windows (WGL) и UNIX (GLX), и они очень близки друг к другу. Состояние графического管线 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 и локальной системой окон. Локальная система окон означает X-окно системы GNU/Linux или Quartz в Mac OS X. Перед тем как 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);
// Во время инициализации EGLDisplay также получают номер версии
int[] version = new int[2];
egl.eglInitialize(dpy, version);

Каждый EGLDisplay перед использованием требует инициализации. Во время инициализации EGLDisplay можно получить версию реализации EGL в системе. Через версию можно разумно использовать соответствующие API OpenGL ES, чтобы написать программы с хорошей совместимостью, чтобы адаптироваться к более многим устройствам и обеспечить максимальную移植ность. Прототип функции инициализации:
boolean eglInitialize(EGLDisplay display, int[] major_minor)

Внутри display является эффективным экземпляром EGLDisplay. По завершении вызова функции, major_minor будет присвоен текущей версии EGL. Например, EGL1.0, major_minor[0] равен 1, major_minor[1] равен 0. EGLSurface включает в себя все informacje о поверхности рендеринга 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 основаны на точках. Точки также могут использоваться для обозначения位置的 источника света, объекта. Обычно мы используем набор плавающих точек для представления точки. Например, четыре вершины прямоугольника можно представить следующим образом:

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() - это получение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);

Рендеринг

После explanations вышеупомянутых концепций, теперь нужно приступить к самой важной работе - рендеринг. Рендеринг преобразует координаты объектов в изображение в буфере кадра. Изображение и координаты вершин имеют тесную связь. Эта связь даётся через режим рисования. Часто используемые режимы рисования - 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: рисует группу линий,相连 от первого вершины до последнего, vertices n и n+1 определяют line n, всего рисуется N-1 line.


GL_LINE_LOOP: рисует группу линий,相连 от первого вершины до последнего, затем последний vertex снова相连 с первым. Vertices n и n+1 определяют line n, затем последний line是由 vertices N и 1 определен, всего рисуется N line.


GL_TRIANGLES: каждый набор из трех頂ок рассматривается как отдельный треугольник. Vertices 3n-2, 3n-1 и 3n определяют n-й треугольник, всего рисуется N/3 треугольников.

GL_TRIANGLE_STRIP: рисует группу相连ых треугольников. Для нечетного n, vertices n, n+1 и n+2 определяют n-й треугольник; для четного n, vertices n+1, n и n+2 определяют n-й треугольник, всего рисуется N-2 треугольника.


GL_TRIANGLE_FAN: рисует группу相连ых треугольников. Треугольник определяется первым頂ком и следующим за ним頂ками. Vertices 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 хранит вершины

Индекс точки.

Пример использования

Используя изложенное выше, давайте предоставим программу для рисования 3D сферы на Ophone. Эффект выглядит следующим образом:


Рис. 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();
}

Обобщение:

Эта статья介绍了 основные концепции и методы рисования графиков с использованием OpenGL ES в Ophone. В OpenGL ES есть много других материалов, таких как текстуры, освещение и материалы, смешение, туман, маски, отражение, загрузка 3D моделей и т.д. С помощью функций OpenGL ES можно рисовать разнообразные графические приложения и интерфейсы игр.

Основной учебник
Рекомендуется к просмотру