Урок 1. Отображение полигонов и трансформации.

Урок 1. Отображение полигонов и трансформации.

Источник: MGDC
Автор: Ребрин Виталий Владимирович (Vito)

Трехмерный мир все больше входит в нашу жизнь. В
играх на РС, он фактически стал стандартом – какая же сегодня игра не
3D? Даже операционные системы все больше используют трехмерные
представления. Почему же такой успех?

Ответ очевиден – мы живем в трехмерном пространстве
и представление виртуального мира, подобно нашему, воспринимается
несоизмеримо лучше.Человек может полностью погрузиться в этот мир.Не
отстают от времени и мобильные платформы. Все больше игр выпускается в
3D.

Когда я начал осваивать 3D библиотеку от SUN, то
просто переложил несколько уроков от NeHe. После чего, я решил, что
будет интересно сделать подобное для Java.

В этом уроке будут рассмотрены самые простые
реализации в 3D графике.На кого рассчитан цикл уроков? Подразумевается,
что вы в большей или меньшей степени, но владеете Java. Имеете
представление о компьютерной графике, но до сих пор не сталкивались с
3D графикой. Впрочем, и для специалистов в этой области, подобные уроки
будут интересны, так как позволят быстрее понять особенности реализации.

Image

Но с учетом того, что материал рассчитан все же на
начинающих, будут вводиться и объясняться принципы работы 3D. Конечно,
невозможно, в этом кратком материале охватить все и читателю будет
необходимо более подробно ознакомиться с заинтересовавшими его
аспектами. Но все же я постараюсь максимально емко объяснить
основополагающие вещи.

Итак, приступим.

В этом уроке будет рассмотрено отображение
полигонов, работа с материалом, освещение, перспективная проекция,
трансформации (вращение, перемещение).

Создадим новый проект. Я использую IDE NetBeans
5.5.1.
Назовем его Lesson1. Добавим новый мидлет и класс, которые назовем
соответственно - MidletMain, Lesson1.Проведем изменения в исходном коде
Листинги приведены ниже.

/*
* MidletMain.java
*
* Created on 18 Август 2007 г., 0:06
*/

import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
import java.util.*;
/**
*
* @author vito
* @version
*/
public class MidletMain extends MIDlet {
static MidletMain instance;
Lesson1 displayable = new Lesson1();
Timer iTimer = new Timer();

public void startApp() {
Display.getDisplay(this).setCurrent(displayable);
iTimer.schedule( new MyTimerTask(), 0, 40 );
}

public MidletMain() {
this.instance = this;
}
public void pauseApp() {
}

public void destroyApp(boolean unconditional) {
}

public static void quitApp() {
instance.destroyApp(true);
instance.notifyDestroyed();
instance = null;
}

/** * Our timer task for providing animation. */
class MyTimerTask extends TimerTask {
public void run()
{
if( displayable != null )
{
displayable.repaint();
}
}
}
}

Пожалуй единственное на что нужно обратить внимание, это класс
MyTimerTask. Его задача перерисовывать экран через заданный интервал
времени.

/*

* Lesson1.java
*
* Created on 18 Август 2007 г., 0:06
*
* To change this template, choose Tools | Template Manager
* and open the template in the editor.
*/
import javax.microedition.lcdui.*;
import javax.microedition.m3g.*;
/**
*
* @author vito
*/
public class Lesson1 extends Canvas {

private Graphics3D iG3D;// объект 3Д
private Camera iCamera; // камера
private Light iLight; // освещение
private float iAngle = 0.0f; // угол вращения
private Transform iTransform = new Transform();// объект трансформации
(аращение перемещение)
private Background iBackground = new Background(); // задний фон
private CompositingMode asd = new CompositingMode();// класс для композиции
private VertexBuffer iVb, iVb1; // позиции вершин, нормали, цвет, текстурные
координаты
IndexBuffer iIb,iIb1; // индексы для iVB, они формируют отображемые
треугольники (так называемая - triangle strips)
Appearance iAppearance; // здесь мы храним данные о материале, текстуре, композиции и т.д.
Material iMaterial = new Material();// материал

/** Creates a new instance of Lesson1 */
public Lesson1() {
// set up this Displayable to listen to command events
setCommandListener(new CommandListener()
{ public void commandAction(Command c, Displayable d)
{ if (c.getCommandType() == Command.EXIT) { // exit the MIDlet
// exit the MIDlet
MidletMain.quitApp();
}
}
});
try {
init();
}
catch(Exception e) {
e.printStackTrace();
}

}

private void init() throws Exception {
// add the Exit command
addCommand(new Command("Exit", Command.EXIT, 1));

setFullScreenMode(true);// полноэкранный режим

// Получаем описатель объекта 3Д
iG3D = Graphics3D.getInstance();
// Создаем камеру
// Устанавливаем перспетивную прекцию
iCamera = new Camera();
iCamera.setPerspective( 60.0f, // угол обзора
(float)getWidth()/ (float)getHeight(), // Боковые грани определяются с учетом
дисбаланса двух размеров окна
1.0f, // передняя грань отсечения
1000.0f ); // задняя грань отсечения

// создаем освещение
iLight = new Light();
iLight.setColor(0xffffff); // белый свет
// iLight.setIntensity(1.25f); // интенсивность
// инициализируем массив вершин для объекта (квадрат)
// 1 * * * * * 0
// * * *
// * * *
// * * *
// 3 * * * * * 2
short[] vert = { 1, 1, 0, -1, 1, 0, 1,-1, 0, -1,-1, 0};// массив вершин-
квадрат
// Создаем массив вершин для данного объекта (квадрат).
VertexArray vertArray = new VertexArray(vert.length / 3, 3, 2);
vertArray.set(0, vert.length/3, vert);

short[] vert1 = { -1, -1, 0, 0, 1, 0, 1, -1, 0};// массив вершин -треугольник
// Создаем массив вершин для данного объекта(треугольник).
VertexArray vertArray1 = new VertexArray(vert1.length / 3, 3, 2);
vertArray1.set(0, vert1.length/3, vert1);

byte[ ] norm = { 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1};// массив нормалей

// Создаем массив вершин для данного объекта(квадрат).
VertexArray normArray = new VertexArray(norm.length / 3, 3, 1);
normArray.set(0, norm.length/3, norm);

byte[ ] norm1 = { 0, 0, 1, 0, 0, 1, 0, 0, 1};// массив нормалей

// Создаем массив вершин для данного объекта(треугольник).
VertexArray normArray1 = new VertexArray(norm1.length / 3, 3, 1);
normArray.set(0, norm1.length/3, norm1);

// Длина полосы трегольников
int[ ] stripLen = { 4 };
int[ ] stripLen1 = { 3 };

byte[ ] Col =
{ -1, 0, 0, //красный
0, -1, 0,//зеленый
0, 0, -1,
-1, 0, -1}; // массив цветов

byte[ ] Col1 =
{ -1, 0, -1, //
0, -1, -1,//
-1, 0, 0}; // массив цветов

VertexArray Cols = new VertexArray(Col.length/3, 3, 1);
Cols.set(0, Col.length/3, Col);

VertexArray Cols1 = new VertexArray(Col1.length/3, 3, 1);
Cols1.set(0, Col1.length/3, Col1);

// Создаем объект VertexBuffer
VertexBuffer vb = iVb = new VertexBuffer();
VertexBuffer vb1 = iVb1 = new VertexBuffer();
vb.setPositions(vertArray, 1.0f, null); // unit scale, zero bias
vb.setNormals(normArray);
vb.setColors(Cols);
// vb.setDefaultColor(0xFF00FF00); // white

vb1.setPositions(vertArray1, 1.0f, null); // unit scale, zero bias
vb1.setNormals(normArray1);
vb1.setColors(Cols1);
// vb1.setDefaultColor(0xFF00FF00); // white

// Создаем массив индексов вершин
iIb = new TriangleStripArray( 0, stripLen );

iIb1 = new TriangleStripArray( 0, stripLen1 );

// create the appearance
iAppearance = new Appearance();
//iAppearance.setMaterial(iMaterial);
//iMaterial.setVertexColorTrackingEnable(true);
//iMaterial.setColor(Material.DIFFUSE, 0xFFFFFFFF); // white
//iMaterial.setColor(Material.SPECULAR, 0xFFFFFFFF); // white
//iMaterial.setColor(Material.EMISSIVE, 0xFFFFFFFF); // white
//iMaterial.setColor(Material.AMBIENT, 0xFFFFFFFF); // white
//iMaterial.setShininess(100.0f);
iBackground.setColor(0x0); // set the background color

iAppearance.setCompositingMode(asd);
asd.setDepthTestEnable(false);// отключаем тест глубины
PolygonMode p = new PolygonMode(); // Режим отображения полигонов
iAppearance.setPolygonMode(p);
p.setPerspectiveCorrectionEnable(true);// включаем коррекцию перспективы
p.setCulling(PolygonMode.CULL_NONE);// будем рисовать невидимые грани
}

protected void paint(Graphics g) {

// начинаем рисовать
iG3D.bindTarget(g);
// очищаем буфер цвета и буфер глубины
iG3D.clear(iBackground);
// Устанавливаем камеру
Transform transform = new Transform();
transform.postTranslate(0.0f, 0.0f, 6.0f);
iG3D.setCamera(iCamera, transform);

// добавляем освещение
iG3D.resetLights();
iG3D.addLight(iLight, transform);

// изменяем угол поворота
iAngle += 2.0f;
iTransform.setIdentity(); // устанавливаем еденичную матрицу
iTransform.postTranslate(-1.5f,0.0f,0.0f); // сдвигаемся влево по оси Х
iTransform.postRotate(iAngle, // вращение в градусах
1.0f, 0.0f, 0.0f); // вркруг какой оси вращаем (x,y,z)
iG3D.render(iVb, iIb, iAppearance, iTransform);// рисуем сцену с заданными параметрами (квадрат)

iTransform.setIdentity();
iTransform.postTranslate(1.5f,0.0f,0.0f);// смещаемся вправо по оси Х
iTransform.postRotate(iAngle/2, // врещаем треугольник помедленнее
0.0f, 1.0f, 0.0f);
iG3D.render(iVb1, iIb1, iAppearance, iTransform); //рисуем тругольник
// Освобождаем контекст (закончили отрисовку)
iG3D.releaseTarget();

repaint(); // просим обновить экран
}

}

В классе Lesson1 необходимо обратить внимание на переменные, относящиеся к пространству имен m3g.*.
Назначение этих переменных будет объясняться далее.
Теперь проект можно запустить и насладиться полученным результатом.
А теперь (уже надоело смотреть?), рассмотрим подробнее инициализацию (функция init)

// Получаем описатель объекта 3Д
iG3D = Graphics3D.getInstance();
// Создаем камеру
// Устанавливаем перспетивную прекцию
iCamera = new Camera();
iCamera.setPerspective( 60.0f, // угол обзора
(float)getWidth()/ (float)getHeight(), // Боковые грани определяются с учетом дисбаланса двух размеров окна
1.0f, // передняя грань отсечения
1000.0f ); // задняя грань отсечения

На этом моменте необходимо остановится подробнее.
Рассмотрим виды проекции.
Существуют два вида проекции трехмерных изображений на экран.

1. Перспективная.

Image



Для обозначения видимого объема используется термин frustum. Он имеет латинское происхождение и примерно означает
«отломанная часть, кусок».
Frustum задается шестью плоскими границами типа (min, max) для каждой из трех пространственных координат. В перспективном
режиме просмотра frustum — это усеченная пирамида, направленная на наблюдателя из глубины экрана. Все детали сцены, которые
попадают внутрь этого объема, видны, а те, что выходят за него, — отсекаются.

2. Ортографическая (или параллельная проекция).

Image


Она не учитывает перспективу, то есть при увеличении (удалении) координаты Z объекта от точки, в которой располагается
наблюдатель, размеры объектов и углы между ними не изменяются, что напоминает плоские проекции объекта.
В нашем случае использована перспективная проекция. И в дальнейшем мы будем использовать исключительно перспективную проекцию.
* Итак, объекты в трехмерном мире существуют в некоей усеченной(в
частном случае) пирамиде, размеры которой мы задаем функцией
setPerspective.
На освещении, подробно останавливаться не будем. Оно будет рассмотрено
в следующем уроке. Нужно только отметить, что освещение создано, задан
его цвет, интенсивность и положение.

// инициализируем массив вершин для объекта (квадрат)
// 1 * * * * * 0
// * * *
// * * *
// * * *
// 3 * * * * * 2
short[ ] vert = { 1, 1, 0, -1, 1, 0, 1,-1, 0, -1,-1, 0};// массив вершин- квадрат
// Создаем массив вершин для данного объекта (квадрат).
VertexArray vertArray = new VertexArray(vert.length / 3, 3, 2);
vertArray.set(0, vert.length/3, vert);

Данный код описывает рисуемый нами квадрат. Массив vert – содержит
вершины квадрата, которые описываются тремя координатами (не забываем
что мы находимся в трехмерном пространстве) – X,Y,Z.
Массив vert можно представить в таком виде.

short[ ] vert = { 1, 1, 0,// X Y Z
-1, 1, 0,
1,-1, 0,
-1,-1, 0};// массив вершин- квадрат

Мы имеем четыре точки, которые описывают квадрат. Но в листинге квадрат разделен диагональю. Что это значит?
На самом деле, точек шесть и наш квадрат состоит из двух треугольников.
* В трехмерной графике для отображения поверхности используется
треугольник. Часто встречается вопрос – почему? Треугольник –
поверхность, которую можно описать минимальным количеством точек.

Куда же делись еще две точки? Если мы посмотрим на схему, то точки 1 и
2 повторяются. Имеет ли смысл рисовать два раза одну и ту же точку?
Конечно - нет. Даже в этом простом примере оптимизация очевидна. А
когда фигура состоит из сотен или тысяч точек?

Каким образом это происходит? За счет индексирования вершин.

// Длина полосы треугольников
int[ ] stripLen = { 4 };

Мы просим нарисовать полосу, состоящую из треугольников, которая содержит четыре значащие вершины.
И объявляем массив индексов.

// Создаем массив индексов вершин

iIb = new TriangleStripArray( 0, stripLen );

TriangleStripArray - имеет два конструктора.
В первом индексы задаются явно.
В нашем случае аргумент определяет первый индекс (первую вершину) с которой начинается отрисовка.
Мы можем задать его явно. Например так.

int [ ]iNd ={0,1,2,3};
iIb = new TriangleStripArray( iNd, stripLen );

* Использование индексов - обязательная практика.

В примере существует еще один массив вершин, который описывает
треугольник. Читателю рекомендуется самому понять, как реализован
треугольник. * Подобное будет часто практиковаться в уроках и носит
смысл самостоятельных заданий.
Далее мы задаем массив нормалей и цветов.
Вкратце расскажу о нормалях (подробно урок 2). Текстурные координаты будут рассмотрены в уроке 3.
Что из себя представляет нормаль?
Нормаль – это вектор, который задает направление и интенсивность
освещенности ( подробно урок 2). Каждой вершине соответствует нормаль.
* Важно помнить, что не только направление, но длина вектора влияет на степень освещенности.

Для примера, раскомментируем строчки.

iAppearance.setMaterial(iMaterial);
iMaterial.setVertexColorTrackingEnable(true);
iMaterial.setColor(Material.DIFFUSE, 0xFFFFFFFF); // white

Запустим проект.
А потом закомментируем строчку.

//vb.setNormals(normArray);

Изображение исчезнет. Для света нет никаких параметров.
Читателю рекомендуется поэкспериментировать, с освещением и материалом, пробуя разные варианты.

В массиве цветов нет ничего сложного. Поэтому опишем его кратко.
Он состоит из трех составляющих R,G,B и задет цвет для каждой вершины.
При желании мы можем задать цвет для всех вершин функцией - vb.setDefaultColor(0xFF00FF00). Параметры - ARGB составляющие цвета.
Наконец мы создаем VertexBuffer и устанавливаем для него позиции вершин, нормали и цвет.

VertexBuffer vb = iVb = new VertexBuffer();
vb.setPositions(vertArray, 1.0f, null);
vb.setNormals(normArray);
vb.setColors(Cols);

asd.setDepthTestEnable(false);// отключаем тест глубины

В данном случае он нам не нужен. Суть его заключается в том, что
объекты отрисовываются (сортировался) с учетом удаленности (координаты
Z).

p.setPerspectiveCorrectionEnable(true);// включаем коррекцию перспективы

Включим коррекцию изображения в перспективной
проекции. Если на данном примере искажения не очень заметны, то на
следующих, они будут очень сильные. Впрочем, существуют изображения,
когда искажением можно пренебречь, чтобы увеличить производительность.

p.setCulling(PolygonMode.CULL_NONE);// будем рисовать невидимые грани

Если этого не сделать, то задние грани фигур, которые считаются невидимыми, при повороте отрисованы не будут.
Еще необходимо заметить, что по умолчанию, интерполяция цветов включена.
Что бы его отключить, сделаем следующее.

p.setShading(PolygonMode.SHADE_FLAT);

Итак класс CompositingMode, отвечает за режим представления 3Д сцены.
Класс PolygonMode, отвечает за режим отрисовки полигонов.
А класс Appearance содержит информацию о материале, композиции, режиме
отображения полигонов и т.д. либо заданную по умолчанию, либо ту,
которую мы задали для соответствующих объектов.
На данном этапе, читатель должен иметь представление о том, как
описываются фигуры в трехмерном представлении. И важность использования
индексов.

Рассмотри далее функцию paint.
То что здесь важно– трансформации.

Transform transform = new Transform();
transform.postTranslate(0.0f, 0.0f, 6.0f);
iG3D.setCamera(iCamera, transform);

Создаем новый объект – Transform.
Функция postTranslate (X , Y , Z) – отвечает за перемещение объекта.
Заданные нами параметры означают, что мы отодвинули камеру от сцены на
6 единиц. Положительная ось Z направлена на смотрящего.
Подобную трансформацию можно описать как – «мы удалили предмет от
глаз».
Трансформации подробно будут рассмотрены в уроке 3.

Далее мы устанавливаем единичную матрицу.

iTransform.setIdentity(); // устанавливаем еденичную матрицу

На данном этапе это можно рассмотреть как – «обнуление результатов предыдущих трансформаций».
postRotate – отвечает за вращение вдоль определенной оси.

Что же мы сделали?

iTransform.setIdentity(); // устанавливаем еденичную матрицу
Transform.postTranslate(-1.5f,0.0f,0.0f); // сдвигаемся влево по оси Х
iTransform.postRotate(iAngle, // вращение в градусах
1.0f, 0.0f, 0.0f); // вркруг какой оси вращаем (x,y,z)
iG3D.render(iVb, iIb, iAppearance, iTransform);// рисуем сцену с заданными параметрами (квадрат)

iTransform.setIdentity();
iTransform.postTranslate(1.5f,0.0f,0.0f);// смещаемся вправо по оси Х
iTransform.postRotate(iAngle/2, // врещаем треугольник помедленнее
0.0f, 1.0f, 0.0f);
iG3D.render(iVb1, iIb1, iAppearance, iTransform); //рисуем тругольник

1. Обнулили результаты.
2. Сдвинули квадрат влево.
3. Повернули вокруг оси X.
4. Нарисовали квадрат.
5. Обнулили результаты.
6. Сдвинули треугольник влево.
7. Повернули вокруг оси Y.
8. Нарисовали треугольник.

Функция releaseTarget() заканчивает блок рисования и освобождает графический контекст устройства.

И в конце мы просим перерисовать экран. Далее будет рассмотрена
двойная буферизация. Подобный подход к выводу изображения позволяет
добиться плавной анимации.
Первый урок завершен. Его целью было дать общее, но с другой стороны
достаточно конкретное представление о работе с 3Д графикой. Были
затронуты многие аспекты, но не все объяснено. Я придерживаюсь правила
– давать максимально возможное количество информации, чтобы читатель
знал, с чем ему еще предстоит столкнуться. И уже имел общее
представление о вопросе.
Вопросы, пожелания, комментарии не просто приветствуются, а необходимы.

И напоследок. Как вы думаете – не слишком много, чтобы нарисовать два примитива?

Следующие уроки.

Урок 2. Работа с материалом и освещением.


Image

Урок 3. Наложение текстур, алфа – блендинг и перемещение в 3Д.


Image

Урок 4. Эффект флага в 3Д.


Image

Урок 5. Трансформации в 3Д.


Image

Урок 6. Загрузка моделей и анимация.


Image

Необходимая документация.

Mobile 3D Graphics API
Technical Specification
Version 1.1
June 22, 2005

Читать продолжение "Реализация для Symbian 9 S60 (Nokia) и UIQ3 (Sony Ericsson) под open GL EX на С++"