|
Постоянный
Регистрация: 29.05.2006
Сообщений: 356
Провел на форуме: 1900547
Репутация:
576
|
|
Часть 2. Освещение и тени
Не прошло и двух суток с публикации на Ачате первой части цикла, как вновь моя Тошиба терпит запущенные GLUT проекты и OpenOffice одновременно. Да, вновь я за клавиатурой, чтобы продолжить свой курс.
Хочу сразу сказать спасибо MicRO и mr. p-s за то, что готовы были всегда прочитать предрелизные версии этих заметок, а так же всегда показывали свою заинтересованность и оказывали моральную поддержку. Спасибо вам, ребята, без вас может и не сел бы писать этот «свой взгляд на CG глазами ачатовца».
Порадовало, что первую часть статьи зашли почитать многие, жаль лишь, что не откомментировали, так что не знаю, вызвала ли она интерес к CG и достаточно ли приличным языком изъясняюсь. Однако, как бы оно ни было, продолжу свой небольшой курс.
Сегодня мы будем говорить о вещи, без которой в CG обойтись нельзя — об освещении и тенях. Мы рассмотрим базовые методы создания светотени в OpenGL, которые дадут достаточно красивый эффект, однако, тех красот, что реализованы в современных играх мы пока не добьемся — к этому нас подведет тема шейдеров, которым на сегодняшний делается все и вся. Но пойдем от простого к сложному.
На данный момент мы имеем некий исходник, модифицируя который мы и будем развивать наши знания. Для тех, кто знакомился со статьей чисто теоретически и ничего не компилировал, или же для тех, у кого эта компиляция почему-то не вышла, выкладываю его сразу весь, как он должен быть. Комментарии уберу, их мы уже видели в первой части.
PHP код:
#include <GL/glut.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
static double SyncTime;
void Draw( void )
{
glPushMatrix();
glColor3d(0,1,0);
glRotated(30*SyncTime, 0, 1, 0);
glutWireTorus(2, 5, 50, 50);
glPopMatrix();
}
void Reshape( int W, int H )
{
glViewport(0, 0, W, H);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(60, (double)W / H, 1, 500);
gluLookAt(0, 0, 15, 0, 0, 0, 0, 1, 0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void Idle( void )
{
long Time;
static long OldTime = -1;
if(OldTime == -1)
OldTime = clock();
Time = clock() - OldTime;
SyncTime = (double)Time/CLOCKS_PER_SEC;
glutPostRedisplay();
}
void Display( void )
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
Draw();
glutSwapBuffers();
}
void Keyboard( unsigned char Ch, int Mx, int My )
{
if (Ch == 27)
exit(0);
}
int main( int argc, char *argv[] )
{
glutInit(&argc,argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
glutInitWindowSize(512, 512);
glutCreateWindow("My window");
glClearColor(0,0,0,0);
glutReshapeFunc(Reshape);
glutDisplayFunc(Display);
glutIdleFunc(Idle);
glutKeyboardFunc(Keyboard);
glutMainLoop();
return 0;
}
Для начала создадим функцию инициализации собственных настроек и параметров. Я назвал ее MyInit. Вызов ее ставим в main вместо glClearColor, как я упоминал в первой части. У меня она не получает никаких параметров, т.к. Происходить там будет только инициализация. Вы, конечно, можете поступить по-своему, например, задавая координаты источника света с клавиатуры — фантазия безгранична, но мы рассмотрим простейший случай этой функции чисто для инициализации освещения.
PHP код:
/* Все просто — не получаем, не возвращаем */
void MyInit( void )
{
/* Первое, что сделаем, зададим в уже известную нам функцию цвета очистки */
glClearColor(0,0,0,0);
/* Задаем модель освещения. Ставим параметр сглаживания для мягких переходов цвета */
glShadeModel(GL_SMOOTH);
/* Инициализируем кучу всего. Обо всем по порядку:
* GL_DEPTH_TEST — проверка глубины, она необходима, чтоб объекты могли перекрывать друг друга
* GL_NORMALIZE — включить автонормирование всех нормалей
* GL_AUTO_NORMAL — включить режим автоматической генерации нормалей(позже мы от всего этого откажемся)
* GL_LIGHTING — собственно, включить освещение
* GL_LIGHT0 — включить нулевую лампу(для использования n ламп включаем их GL_LIGHTi, где i=0..n-1)
* GL_COLOR_MATERIAL — включаем выставление параметров материала по его цвету
*/
glEnable(GL_DEPTH_TEST);
glEnable(GL_NORMALIZE);
glEnable(GL_AUTO_NORMAL);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glEnable(GL_COLOR_MATERIAL);
/* Устанавливаем параметры освещения моделей, разрешая освещать по-разному разные части объектов */
glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
}
Теперь надо непосредственно разместить саму лампочку. Мы могли бы сделать ее прямо в MyInit, но будем рассматривать ее как объект, поскольку мы наверняка захотим ее вращать и вообще всячески изменять. А потому задавать ее позицию будем непосредственно в функции Draw. Чем сейчас и займемся. Я приведу такую модификацию, вы же поступите как вам будет угодно.
PHP код:
void Draw( void )
{
/* Задаем координаты вектора лампочки. В CG вектора принято задавать 4 компонентами x,y,z,w, где x,y,z — координаты вектора в пространстве, а w — делитель, которой позволяет задавать точки удаленные на бесконечность. В рамках нашего курса таких точек мы задавать не будем, а потому делитель логично положим равным 1 */
float Pos[4] = {0, 10, -10, 1};
/* Точка, куда будем светить. Принята давать стандартное расположение в пространстве, т.к. на бесконечность мы обычно не светим :) Специально обособленно определяю, т.к. этот параметр можно проигнорировать, тогда лампочка будет светить во все стороны. */
float Dir[4] = {0, 0, 0};
/* Сохраним матрицу преобразований */
glPushMatrix();
/* Проведем движение поворота в зависимости от времени */
glRotated(50*SyncTime, 0, 1, 0);
/* Зададим операцию над лампочкой. fv в конце функции показывают, что параметры будут типа float, и параметром функции будет некий вектор(vector). Функция принимает аргументы:
* GL_LIGHT0 — имя лампочки
* GL_POSITION — что будем задавать. В данном случае позицию лампочки.
* Pos — вектор размещения лампочки */
glLightfv(GL_LIGHT0, GL_POSITION, Pos);
/* Аналогично задаем, если надо, направление свечения */
glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, Dir);
/* Восстановим матрицу преобразований */
glPopMatrix();
/* Эта часть кода почти неизменна. Поясню только некоторые аспекты. */
glPushMatrix();
glColor3d(0,1,0);
/* Делаем поворот тора на статический угол для лучшего рассмотрения */
glRotated(30, 0, 1, 0);
/* Делаем тор залитым, а не wire, дабы видеть игру светотени */
glutSolidTorus(2, 5, 50, 50);
glPopMatrix();
}
Итак, мы увидим наше первое освещение. Я специально предложил сделать его в динамике чтоб посмотреть на тени. Вам, вероятно, они покажутся очень угловыми и некрасивыми. Это происходит от того, что освещение считается для каждого полигона, а их у нас маловато. Поправить это можно будет исправив glutSolidTorus, исправив значения 50 на, скажем 500. Теперь тени получаются очень симпатичными и аккуратными.
Но я бы не был извращенцем, окончи я на этом этапе свою статью. Хоть стандартное освещение OpenGL практически и не используется, но все же раскрою его немного более полно, чем создание однотонной «белой» неинтересной лампочки. Конечно, что-то все равно останется за кадром, но ни в одном учебнике, а уж тем более в серии статей рассказать абсолютно все нельзя. Потому немного вспомним физику и продолжим программирование того, что раскрою я. Прочее же несложно найти.
Характеристики освещения:
Ambient — стандартный оттенок объекта. Т.е. все неосвещенные объекты изначально не черные, а указанного оттенка серого.
Diffuse — рассеянный свет, распространенный в среде.
Specular — отраженный свет от объектов.
Emission — излучаемый свет.
Shininess — степень отражения света.
Теперь рассмотрим, как можно использовать эти параметры в OpenGL. А именно добавим 4 вектора(четвертичных, зачем, не знаю, ИМХО — тут тупая трата лишней памяти, но для каких-то темных индийских целей используются четвертичные вектора) для Амбиета, Диффузии, Отражения, Излучения и одну переменную для степени отражения. Из них первые 3 относятся к лампочкам, а так же они все применимы к материалам.
Задавать параметры лампочек мы уже умеем — это функции glLightfv и glLightf(использование этой функции мы не разбирали, но для задания специфических коэффициентов она используется для передачи float числа, а не вектора, как glLightfv). Параметры соответственно имя лампочки GL_LIGHTi, изменяемый параметр(в данном случае, Ambient — GL_AMBIENT, Diffuse — GL_DIFFUSE, Specular — GL_SPECULAR) и собственно сам передаваемый параметр. Инициализация может проходить в функции MyInit(причем, даже предпочтительно их задавать там) или непосредственно в Draw(но зачем ее захламлять, давайте там только рисовать).
Аналогично с материалами. Оставшиеся параметры GL_EMISSION, GL_SHININESS. Функции работы с материалами glMaterialfv и glMaterialf(по полной аналогии с glLightfv и glLightf). На абсолютной аналогии функция получает 3 параметра: тип поверхности(GL_FRONT — передняя, GL_BACK — задняя, GL_FRONT_AND_BACK — обе), устанавливаемый параметр и сам передаваемый параметр. Работает это, как glColor3d, для всех объектов, идущих после вызова, а потому, использовать эту функцию предпочтительно непосредственно в самом Draw для рисования не однотипных объектов из разных материалов.
Итак, мы разобрались со следующей частью великого и почти всемогущего OpenGL. Надеюсь, многие найдут ее для себя интересной и будут ожидать следующей, разумеется, еще более интересная статья, в которой мы начнем создавать собственные объекты из полигонов, текстурировать их и делать полупрозрачными. До новых статей. Всем спасибо за внимание, а я пойду, наконец, спать 
|