В предыдущих статьях нам удалось отобразить треугольники. Как отобразить квадрат тогда? Квадрат состоит из 2 треугольников.
Следующая диаграмма показывает вам это:
Площадь из треугольников |
Здесь есть интересная вещь, которую стоит отметить. Квадрат ABDC вместо обычного ABCD . Почему это? Из-за того, что OpenGL соединяет треугольники вместе.
То, что вы видите здесь, это полоса треугольника . Треугольная полоса — это серия соединенных треугольников, в нашем случае 2 треугольника.
OpenGL рисует следующую треугольную полосу (которая представляет собой квадрат), используя вершины в следующем порядке:
Треугольник 1: V1 -> V2 -> V3
Треугольник 2: V3 -> V2 -> V4
Полоса треугольника |
Он рисует первый треугольник, используя вершины по порядку, затем берет последнюю вершину из предыдущего треугольника и использует последнюю сторону треугольника в качестве основы для нового треугольника.
Это также имеет свои преимущества: мы удаляем избыточные данные из памяти.
Возьмите проект из предыдущей статьи и создайте новый класс с именем Square .
Если вы сравните класс Square с классом Triangle , вы заметите только одно отличие:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
package net.obviam.opengl; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; import javax.microedition.khronos.opengles.GL10; public class Square { private FloatBuffer vertexBuffer; // buffer holding the vertices private float vertices[] = { - 1 .0f, - 1 .0f, 0 .0f, // V1 - bottom left - 1 .0f, 1 .0f, 0 .0f, // V2 - top left 1 .0f, - 1 .0f, 0 .0f, // V3 - bottom right 1 .0f, 1 .0f, 0 .0f // V4 - top right }; public Square() { // a float has 4 bytes so we allocate for each coordinate 4 bytes ByteBuffer vertexByteBuffer = ByteBuffer.allocateDirect(vertices.length * 4 ); vertexByteBuffer.order(ByteOrder.nativeOrder()); // allocates the memory from the byte buffer vertexBuffer = vertexByteBuffer.asFloatBuffer(); // fill the vertexBuffer with the vertices vertexBuffer.put(vertices); // set the cursor position to the beginning of the buffer vertexBuffer.position( 0 ); } /** The draw method for the square with the GL context */ public void draw(GL10 gl) { gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); // set the colour for the square gl.glColor4f( 0 .0f, 1 .0f, 0 .0f, 0 .5f); // Point to our vertex buffer gl.glVertexPointer( 3 , GL10.GL_FLOAT, 0 , vertexBuffer); // Draw the vertices as triangle strip gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0 , vertices.length / 3 ); //Disable the client state before leaving gl.glDisableClientState(GL10.GL_VERTEX_ARRAY); } } |
Разница в выделенных строках (13-18). Правильно, мы добавили еще одну вершину в массив вершин .
Теперь измените GlRenderer, чтобы вместо Треугольника мы использовали Квадрат .
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
package net.obviam.opengl; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; import android.opengl.GLU; import android.opengl.GLSurfaceView.Renderer; public class GlRenderer implements Renderer { private Square square; // the square /** Constructor to set the handed over context */ public GlRenderer() { this .square = new Square(); } @Override public void onDrawFrame(GL10 gl) { // clear Screen and Depth Buffer gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); // Reset the Modelview Matrix gl.glLoadIdentity(); // Drawing gl.glTranslatef( 0 .0f, 0 .0f, - 5 .0f); // move 5 units INTO the screen // is the same as moving the camera 5 units away square.draw(gl); // Draw the triangle } @Override public void onSurfaceChanged(GL10 gl, int width, int height) { if (height == 0 ) { //Prevent A Divide By Zero By height = 1 ; //Making Height Equal One } gl.glViewport( 0 , 0 , width, height); //Reset The Current Viewport gl.glMatrixMode(GL10.GL_PROJECTION); //Select The Projection Matrix gl.glLoadIdentity(); //Reset The Projection Matrix //Calculate The Aspect Ratio Of The Window GLU.gluPerspective(gl, 45 .0f, ( float )width / ( float )height, 0 .1f, 100 .0f); gl.glMatrixMode(GL10.GL_MODELVIEW); //Select The Modelview Matrix gl.glLoadIdentity(); //Reset The Modelview Matrix } @Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { } } |
Выполнение этого приведет к следующему результату:
Треугольная полоса, образующая квадрат |
Рассматривая это, метод draw () в классе Square должен иметь смысл.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
public void draw(GL10 gl) { gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); // set the colour for the square gl.glColor4f( 0 .0f, 1 .0f, 0 .0f, 0 .5f); // Point to our vertex buffer gl.glVertexPointer( 3 , GL10.GL_FLOAT, 0 , vertexBuffer); // Draw the vertices as triangle strip gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0 , vertices.length / 3 ); //Disable the client state before leaving gl.glDisableClientState(GL10.GL_VERTEX_ARRAY); } |
Сначала мы разрешаем OpenGL использовать массив вершин для рендеринга. Наш массив вершин содержит вершины для нашего квадрата.
gl.glVertexPointer (строка 5) сообщает рендереру opengl, откуда брать вершины и какого они типа.
Первый параметр сообщает, сколько координат используется для вершины. Мы используем 3 (x, y, z). Второй параметр говорит, что значения имеют тип float .
Третий параметр — это смещение между вершинами в массиве. Это называется рознь . У нас плотно упакованный массив, поэтому он равен 0 .
Наконец, последний параметр сообщает, где находятся вершины. Конечно, это наш буфер vertexBuffer .
gl.glDrawArrays в строке 11 говорит OpenGL нарисовать примитив. Что за примитив? Тот, который указан в первом параметре: GL10.GL_TRIANGLE_STRIP . Он берет вершины из ранее установленного буфера вершин и следует правилам полос треугольника, описанным ранее.
Второй параметр указывает начальный индекс для вершин в массиве.
Третий параметр сообщает OpenGL, сколько вершин использовать для многоугольника, которые должны быть визуализированы. Поскольку в предыдущем выражении ( gl.glVertexPointer ) мы указали, что 3 координаты определяют вершину, мы предоставим длину нашего массива вершин, разделенную на 3. В массиве 9 элементов, определяющих 3 вершины.
glDisableClientState (GL10.GL_VERTEX_ARRAY) отключает состояние рендеринга из массива, содержащего вершины.
Представьте, что glEnableClientState и glDisableClientState начинаются… заканчиваются в программе. Мы в основном вводим подпрограммы в рендерер OpenGL. После того, как мы ввели подпрограмму, мы устанавливаем переменные (буфер вершин, цвет и т. Д.) И выполняем другие подпрограммы (рисуем вершины). После того, как мы закончим, мы выходим из подпрограммы. Мы работаем в изоляции внутри рендера.
Убедитесь, что вы запустили приложение на этом этапе и поняли, что происходит.
Создание текстуры
Теперь самое интересное. Давайте загрузим изображение и создадим текстуру. Текстура это изображение.
Чтобы узнать, как вы можете загрузить изображения в вашем приложении для Android, ознакомьтесь с этой статьей .
Мы будем работать с классом Square, так как мы хотим применить текстуру к квадрату.
Нам нужно загрузить изображение, сообщить рендереру opengl, что мы хотим использовать его в качестве текстуры, и, наконец, мы сообщим рендереру, где именно на нашем примитиве (квадрате) отобразить его.
Думайте об этом, как будто вы кладете фольгу на окно или стену. Я предоставляю вам фольгу, содержащую изображение размера окна, и советую вам накрыть ею окно, чтобы левый верхний угол фольги находился в верхнем левом углу окна. Вот и все, давайте приступим к работе.
OpenGL использует вершины, чтобы определить, куда поместить материал. Поэтому нам нужно создать массив для изображения. Но на этот раз это будет 2D, поскольку растровое изображение похоже на лист бумаги, плоскую плоскость.
Добавьте массив координат для текстуры.
1
2
3
4
5
6
7
8
|
private FloatBuffer textureBuffer; // buffer holding the texture coordinates private float texture[] = { // Mapping coordinates for the vertices 0 .0f, 1 .0f, // top left (V2) 0 .0f, 0 .0f, // bottom left (V1) 1 .0f, 1 .0f, // top right (V4) 1 .0f, 0 .0f // bottom right (V3) }; |
Нам нужно создать textureBuffer аналогично vertexBuffer . Это происходит в конструкторе, и мы просто повторно используем byteBuffer . Проверьте новый конструктор:
01
02
03
04
05
06
07
08
09
10
11
12
13
|
public Square() { ByteBuffer byteBuffer = ByteBuffer.allocateDirect(vertices.length * 4 ); byteBuffer.order(ByteOrder.nativeOrder()); vertexBuffer = byteBuffer.asFloatBuffer(); vertexBuffer.put(vertices); vertexBuffer.position( 0 ); byteBuffer = ByteBuffer.allocateDirect(texture.length * 4 ); byteBuffer.order(ByteOrder.nativeOrder()); textureBuffer = byteBuffer.asFloatBuffer(); textureBuffer.put(texture); textureBuffer.position( 0 ); } |
Мы добавим важный метод в класс Square . Метод loadGLTexture . Это будет вызвано от средства визуализации, когда это запускается. Это происходит в методе onSurfaceCreated . Это загрузит изображение с диска и свяжет его с текстурой в репозитории OpenGL. Он будет в основном назначать внутренний идентификатор для обработанного изображения и будет использоваться API OpenGL для идентификации его среди других текстур.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
/** The texture pointer */ private int [] textures = new int [ 1 ]; public void loadGLTexture(GL10 gl, Context context) { // loading texture Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.android); // generate one texture pointer gl.glGenTextures( 1 , textures, 0 ); // ...and bind it to our array gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[ 0 ]); // create nearest filtered texture gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST); gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR); // Use Android GLUtils to specify a two-dimensional texture image from our bitmap GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0 , bitmap, 0 ); // Clean up bitmap.recycle(); } |
Нам нужен массив указателей текстуры. Здесь OpenGL будет хранить имена текстур, которые мы будем использовать в нашем приложении. Поскольку у нас есть только одно изображение, мы создадим массив размером 1.
Строка 06 загружает растровое изображение Android, которое было ранее скопировано в каталог / res / drawable-mdpi , поэтому идентификатор уже создан.
Примечание об этом растровом изображении. Рекомендуется быть квадратным. Это очень помогает при масштабировании. Поэтому убедитесь, что ваши растровые изображения для текстур являются квадратами (6 × 6, 12 × 12, 128 × 128 и т. Д.). Если не квадрат, убедитесь, что ширина и высота являются степенями 2 (2, 4, 8, 16, 32,…). Вы можете иметь растровое изображение 128 × 512, и оно идеально подходит для использования и оптимизировано.
Строка 10 генерирует имена для текстур. В нашем случае генерирует одно имя и сохраняет его в массиве текстур . Даже если он говорит имя , он на самом деле генерирует int . Немного смущает, но так оно и есть.
Строка 12 связывает текстуру с новым именем (texture [0]). Это означает, что все, что использует текстуры в этой подпрограмме, будет использовать связанную текстуру. Практически активирует текстуру. Связанная текстура — это активная текстура. Если бы у нас было несколько текстур и несколько квадратов, чтобы использовать их, нам пришлось бы привязать (активировать) соответствующие текстуры для каждого квадрата непосредственно перед тем, как они использовались для их активации.
Строки 15 и 16 устанавливают некоторые фильтры, которые будут использоваться для текстуры. Мы только что сообщили OpenGL, какие типы фильтров использовать, когда нужно уменьшить или расширить текстуру, чтобы покрыть квадрат. Мы выбрали несколько основных алгоритмов для масштабирования изображения. Не нужно беспокоиться об этом сейчас.
В строке 19 мы используем утилиты Android, чтобы указать изображение текстуры 2D для нашего растрового изображения. Он создает изображение (текстуру) внутри в своем родном формате на основе нашего растрового изображения.
в строке 22 мы освобождаем память. Это не следует забывать, так как память на устройстве очень ограничена, а изображения большие.
Теперь посмотрим, как был изменен метод draw () .
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
public void draw(GL10 gl) { // bind the previously generated texture gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[ 0 ]); // Point to our buffers gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY); // Set the face rotation gl.glFrontFace(GL10.GL_CW); // Point to our vertex buffer gl.glVertexPointer( 3 , GL10.GL_FLOAT, 0 , vertexBuffer); gl.glTexCoordPointer( 2 , GL10.GL_FLOAT, 0 , textureBuffer); // Draw the vertices as triangle strip gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0 , vertices.length / 3 ); //Disable the client state before leaving gl.glDisableClientState(GL10.GL_VERTEX_ARRAY); gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY); } } |
Это не большая модификация из предыдущей статьи. Дополнения документированы и делают следующее:
Строка 03 связывает (активирует) текстуру с именем (целочисленным идентификатором), хранящимся в текстурах [0] .
Строка 07 включает отображение текстуры в текущем контексте OpenGL.
Строка 14 предоставляет контекст OpenGL с координатами текстуры.
После рисования примитива с текстурами мы отключаем отображение текстур вместе с примитивом рендеринга.
Важно — UV Mapping
Если вы внимательно посмотрите, порядок вершин в массиве координат отображения текстуры не соответствует порядку, представленному в массиве координат вершины квадрата.
Здесь очень хорошее объяснение координат наложения текстуры: http://iphonedevelopment.blogspot.com/2009/05/opengl-es-from-ground-up-part-6_25.html .
Я постараюсь объяснить это быстро, хотя. Изучите следующую диаграмму.
Квадратные и текстурные координаты |
Квадрат состоит из 2 треугольников, вершины расположены в следующем порядке.
1 — внизу слева
2 — внизу справа
3 — верхний левый
4 — вверху справа
Обратите внимание на путь против часовой стрелки.
Координаты текстуры будут в следующем порядке: 1 -> 3 -> 2 -> 4
Просто запомните это отображение и поверните его, если вы начинаете свою форму из другого угла. Чтобы прочитать об УФ-картографировании, зайдите в статью в Википедии или поищите в сети.
В заключительной части, чтобы сделать эту работу, нам нужно предоставить контекст для нашего рендерера, чтобы мы могли загрузить текстуру при запуске.
Метод onSurfaceCreated будет выглядеть следующим образом.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
public void onSurfaceCreated(GL10 gl, EGLConfig config) { // Load the texture for the square square.loadGLTexture(gl, this .context); gl.glEnable(GL10.GL_TEXTURE_2D); //Enable Texture Mapping ( NEW ) gl.glShadeModel(GL10.GL_SMOOTH); //Enable Smooth Shading gl.glClearColor( 0 .0f, 0 .0f, 0 .0f, 0 .5f); //Black Background gl.glClearDepthf( 1 .0f); //Depth Buffer Setup gl.glEnable(GL10.GL_DEPTH_TEST); //Enables Depth Testing gl.glDepthFunc(GL10.GL_LEQUAL); //The Type Of Depth Testing To Do //Really Nice Perspective Calculations gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST); } |
Строка 03 загружает текстуру. Остальные строки просто настраивают рендерер с некоторыми значениями. Вам не нужно беспокоиться о них сейчас.
Вам нужно будет предоставить контекст приложения для объекта Square , потому что сам объект загружает текстуру и должен знать путь к растровому изображению.
Просто предоставьте контекст для средства визуализации в методе OnCreate действия Run ( glSurfaceView.setRenderer (новый GlRenderer (this)); ), и все готово.
Убедитесь, что визуализатор имеет контекст, объявленный и установленный через конструктор.
Выдержка из класса GlRendered .
01
02
03
04
05
06
07
08
09
10
|
private Square square; // the square private Context context; /** Constructor to set the handed over context */ public GlRenderer(Context context) { this .context = context; // initialise the square this .square = new Square(); } |
Если вы запустите код, вы увидите квадрат с красивым андроидом, положенным поверх него.
Квадрат с текстурой Android |
Загрузите исходный код и проект здесь (obviam.opengl.p03.tgz).
Ссылка: наложение текстур — OpenGL Android (отображение изображений с использованием OpenGL и квадратов) от нашего партнера по JCG Тамаса Яно из блога « Против зерна ».
- Введение в разработку игр для Android Введение
- Разработка игр для Android — Идея игры
- Разработка игр для Android — Создать проект
- Разработка игр для Android — базовая игровая архитектура
- Разработка игр для Android — основной игровой цикл
- Разработка игр для Android — Отображение изображений с Android
- Разработка игр для Android — перемещение изображений на экране
- Разработка игр для Android — The Game Loop
- Разработка игр для Android — Измерение FPS
- Разработка игр для Android — Sprite Animation
- Разработка игр для Android — Particle Explosion
- Разработка игр для Android — Дизайн игровых объектов — Стратегия
- Разработка игр для Android — Использование растровых шрифтов
- Разработка игр для Android — переход с Canvas на OpenGL ES
- Разработка игр для Android — отображение графических элементов (примитивов) с помощью OpenGL ES
- Разработка игр для Android — Дизайн игровых сущностей — Государственный паттерн
- Серия игр для Android