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

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

Источник: MGDC
Автор: Ребрин Виталий (verussoft.com)

В данном уроке будет рассмотрено освещение. Это
пожалуй, один из сложнейших аспектов 3D, рассматривается зачастую либо
слишком просто или наоборот, рассматриваются очень сложные аспекты.
Надеюсь, что мне удалось найти компромиссное решение.

Начиная с этого урока, все аспекты 3D будут
рассматриваться в разрезе OGL, как классической библиотеки. Далее код с
пояснениями будет портироваться под другие 3Д библиотеки.

Начинаем работать

 

Image

Исходный код проекта для S60 можно скачать отсюда.

Для UIQ3 отсюда.

Несколько слов о OGL EX. К сожалению в этой
библиотеке не реализовано многое. Вы не встретите там блоков отрисовки,
и пожалуй самое главное дисплейных списков. Эти списки – отличный
инструмент оптимизации. Но как написано в документации, их реализация
слишком сложна.

К чему все это? Работа с эмулятором слишком
медленная. Поэтому лучше разрабатывать графическую часть на «родной
платформе». Существуют эмуляторы для РС, но они поддерживают старые
версии EX. Поэтому я рекомендую использовать имеющуюся у вас
библиотеку, учитывая ограничения.
То же самое относится и к DX.

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


static GLfloat angle= 3. * atan(1.)/2.5;
// Две характерные токи
static GLfloat V = cos(angle);
static GLfloat W = sin(angle);

Далее мы создадим два массива нормалей:
normp – грубо заданные нормали.
normf – точно рассчитанные нормали.

И массив вершин – vert. Который содержит 180
значащих значений типа GLfloat, которые описывают 60 точек (180/3) в 3D
и 20 треугольных плоскостей из котрых состоит икосаэдр (60/3).

Запустим проект с грубо заданными нормалями.


glEnableClientState(GL_NORMAL_ARRAY);
//glNormalPointer(GL_FLOAT,0,normf);
glNormalPointer(GL_FLOAT,0,normp);


И посмотрим на результат.

Image

Изменим массив нормалей.


glEnableClientState(GL_NORMAL_ARRAY);
glNormalPointer(GL_FLOAT,0,normf);
//glNormalPointer(GL_FLOAT,0,normp);


Image

Теперь проанализируем результаты.

Несколько важных моментов:

  • Не только направление нормали, но ее модуль (длина) влияют на степень освещенности.
  • Цвета вершин полигонов, влияют на цвет самого полигона (интерполируются - glShadeModel( GL_SMOOTH );).

Чтобы избавится от этого влияния (если это
необходимо), нормали масштабируют (или нормируют) т.е. делают ее длину
равной 1, оставляя неизменным направление. В нашем случае, это делает
OGL - glEnable( GL_NORMALIZE );.

Итак, что же произошло?

В первом случае, мы задали массив нормалей грубо,
как если бы наша фигура являлась сферой. И как мы видим, грани
«смазаны», фигура действительно стала напоминать сферу.

Но такой подход неверен, мы можем и должны точно задать нормали к треугольной плоскости.

Произведение двух векторов, a и b определяется как
вектор n перпендикулярный к плоскости в которой лежат исходные вектора
и рассчитывается по формулам.


void getNorm(GLfloat v1[3], GLfloat v2[3], GLfloat out[3])
{

out[0] = v1[1]*v2[2] - v1[2]*v2[1]; // normal calculate
out[1] = v1[2]*v2[0] - v1[0]*v2[2] ;
out[2] =v1[0]*v2[1] - v1[1]*v2[0];

}

Основные моменты рассмотрены. Я думаю теперь понятно, насколько важную роль играет освещение и нормали в 3D.

Пройдемся по коду.

Инициализация.


glEnable(GL_DEPTH_TEST);// Enables Depth Testing

// Включаем тест глубины.

Он необходим, для сортировки полигонов. Говоря проще, полигоны отрисовываются в порядке удаленности.


glEnable(GL_CULL_FACE);// отсечение невидимых граней.

Это повышает производительность. Мы не рисуем то что не видно.

glEnable(GL_LIGHTING);// включаем освещение

// Set up lamp.
glEnable( GL_LIGHT0 );
//glLightfv( GL_LIGHT0, GL_DIFFUSE, lightDiffuseLamp );
//glLightfv( GL_LIGHT0, GL_AMBIENT, lightAmbientLamp );
//glLightfv( GL_LIGHT0, GL_SPECULAR, lightDiffuseLamp );
//glLightfv( GL_LIGHT0, GL_POSITION, lightPositionLamp );

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


glEnable(GL_COLOR_MATERIAL); // учитываем цвет материала.

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

Аналогичное относится к материалу.


// Set duck material
glMaterialfv( GL_FRONT_AND_BACK, GL_DIFFUSE, objDiffuseDuck );
glMaterialfv( GL_FRONT_AND_BACK, GL_AMBIENT, objAmbientDuck );
glMaterialfv( GL_FRONT_AND_BACK, GL_SPECULAR, objSpecularDuck );
glMaterialfv( GL_FRONT_AND_BACK, GL_EMISSION, objEmissionDuck );
glMaterialx( GL_FRONT_AND_BACK, GL_SHININESS, 10 << 16 );

Он так – же может излучать, отражать свет и т.д. Все
эти параметры можно менять и динамически, как и модуль нормали. Так
создаются различные эффекты и строятся модели динамического освещения.

Java 3D.

Исходный код проекта можно скачать отсюда.

Пожалуй начнем, с того, что эта реализация отлично
оптимизирована под мобильные платформы, поэтому массивы вершин мы можем
задавать только с помощью целых чисел. Это конечно правильно, но во
многом неудобно. Поэтому «укрупним» масштаб, чтобы после приведения не
получить нули.


float V = 5.8778524f;
float W = 8.0901700f;// две характерные точки.

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


// Создаем объект VertexBuffer
VertexBuffer vb = iVb = new VertexBuffer();
VertexBuffer vb1 = iVb1 = new VertexBuffer();
vb.setPositions(vertArray, 1.0f, null);
vb.setNormals(normArray);
vb.setDefaultColor(0x00FF0000);

vb1.setPositions(vertArray, 1.0f, null);
vb1.setNormals(normArray1);
vb1.setDefaultColor(0x00FF0000);

Этот подход похож на реализацию в DX. И как было
сказано ранее, реализация open GL EX не поддерживает списков, которые
позволяют сформировать массивы заранее.

Интересно будет сравнить FPS. И это мы сделаем в
уроке 4(эффект флага), когда пересчитывать координаты придется делать
динамически.
Далее все аналогично уроку 1.

В функции paint меняем значения.


//iG3D.render(iVb, iIb, iAppearance, iTransform);// рисуем икосаэдр - грубый расчет нормалей
iG3D.render(iVb1, iIb, iAppearance, iTransform); //рисуем икосаэдр - точный расчет нормалей

Заключение.

Реализация для DX и Windows Mobile будет опубликована как дополнение.
Вопросы и обсуждения на форуме mgdc.ru или verussoft.com.