OpenGL. От детского сада до гуру. Вступление. Итак, начну серию своих «мирных» статей по CG(computer graphics). Здесь я попытаюсь рассказать вам о программировании под OpenGL и DirectX, о WinAPI и двойной буферизации, о физике твердых тел(Rigid Body) и построении высоко фотореалистичных изображений методом трассировки лучей(Ray Tracing). Надеюсь, это станет интересным для многих, а некоторые и вовсе найдут в себе таланты CG`шников. Для затравки я решил начать с цикла по программированию на С под OpenGL. Хоть приведенный код будет и на С(С++ в данном курсе использовать не обязательно, так что не буду придумывать сложности, мне-то все равно на чем писать, но многих может отпугнуть. Поклонникам ООП, для вас следующие статьи, стоит, например, лишь сказать, что Ray Tracing считается классической и лучшей задачей на С++), но я постараюсь снабжать его максимально понятными и подробными комментариями, всвязи с чем код будет абсолютно портируемым хоть на Delphi, хоть на Python, хоть на Haskell. «Цикл» будет состоять из следующих частей: Часть 1. Базовый макет GLUT-приложения Простейшие OpenGL программы с использованием OpenGL Utility Toolkit для любой операционной системы. Часть 2. Освещение и тени Создание источников света, физические коэффициенты, тени. Часть 3. Рисование полигонов. Текстурирование и прозрачность Рисование собственных фигур. Наложение текстур. Создание прозрачности и полупрозрачности. Часть 4. Мультитекстурирование. OpenGL Shaders. OpenGL Shader Language Наложение нескольких текстур. Инициализация шейдеров. Работа с шейдерами. Язык программирования GLSL. Часть 5. Пример базового макета системы анимации на WinAPI, двойной буферизации и OpenGL «Взрослое» программирование под OpenGL. Пишем без GLUT под Windows. Часть 6. Пример базового макета системы анимации на Qt4 и OpenGL «Взрослое» программирование под OpenGL. Пишем без GLUT кроссплатформенные приложения с использование Qt4. Часть 7. Загрузка моделей из редактора Maya Работать со стандартными моделями и полигонами не интересно. Так что завершим весь цикл загрузкой моделей из популярного редактора 3D графики Maya. Часть 1. Базовый макет GLUT-приложения Начинать, разумеется стоит с самых основ. А потому эту статью я посвящу самой простой части OpenGL.- GLUT или OpenGL Utility Toolkit. Но начнем разбираться по порядку. OpenGL (Open Graphics Library — открытая графическая библиотека) — спецификация, определяющая независимый от языка программирования кросс-платформенный программный интерфейс для написания приложений, использующих двумерную и трехмерную компьютерную графику. (с) Википедия OpenGL работает по принципу клиент-серверной системы, где в роли клиента выступает оформленная программа с использованием специального API, а в роли сервера обрабатывающая OpenGL часть графического процессора(GPU) видеокарты. Нашей задачей будет на протяжении цикла научиться пользоваться API для реализации любой проблемы, а так же понимать, на что идут ресурсы нашей видеокарты и как свести их трату к минимуму.. Вплоть до частей 5 и 6 мы будем пользоваться кроссплатформенной библиотекой GLUT. Это позволит нам не совершать дополнительных инициализаций и писать простой, понятный и кроссплатформенный код. Практически, 99% всех задач можно реализовать с использованием GLUT. Почему же лучше писать на «чистом» API и как это делать мы разберем уже в 5 и 6 частях, когда откажемся от его использования. Пока же начнем разбираться, с чем нам работать ближайшее время. Перед тем, как начнем писать программу, скажу, что GLUT а так же все необходимые библиотеки и файлы заголовков берутся с сайта _http://opengl.org. Итак, после этого вступления начнем писать программу. PHP: /* Подключаем необходимые файл заголовков. Зачем нам stdio, stdlib и time - дальше */ #include <GL/glut.h> #include <stdio.h> #include <stdlib.h> #include <time.h> /* Объявляем главную функцию. Использование аргументов крайне желательно */ int main( int argc, char *argv[] ) { /* Инициализация GLUT API от параметров командной строки. */ glutInit(&argc,argv); /* Инициализация системы анимации * GLUT_DOUBLE — включение двойного буфера(подробнее в следующих статьях, сейчас отмечу, * что это избавит нас от мерцания изображения) * GLUT_RGB — Red, Green, Blue гамма, без комментариев * GLUT_DEPTH — использование окна с буфером глубины для реалистичной объемности */ glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH); /* Тут все элементарно — выставляем размеры окна. Сначала ширину, потом высоту. */ glutInitWindowSize(512, 512); /* Создание окна. Параметр — заголовок окна. Для недоуменных гуру коддинга напоминаю, что мы идем от простого к сложному */ glutCreateWindow("My window"); /* Устанавливаем цвет очистки экрана. R, G, B, Alpha. Впоследствии, когда вам нужно будет инициализировать многие вещи, такие как свет, шейдера и проч, где-то здесь будет вызываться ваша функция инициализации всего этого */ glClearColor(0,0,0,0); /* Функция обработки изменения размеров окна. Параметр — указатель на функцию обработки */ glutReshapeFunc(Reshape); /* Функция вывода сцены на экран. Параметр — указатель на функцию обработки */ glutDisplayFunc(Display); /* Функция «простоя» системы. Параметр — указатель на функцию обработки */ glutIdleFunc(Idle); /* Функция обработки клавиатуры. Параметр — указатель на функцию обработки */ glutKeyboardFunc(Keyboard); /* Запускает работу GLUT */ glutMainLoop(); /* Выход в систему с кодом 0, т.е. все супер */ return 0; } Думаю, тут все предельно просто и ясно. Такая инициализация будет в большинстве ваших GLUT-программ. Для полной ясности рассмотрим описанные функции. Я использовал лишь обязательные и те, по аналогии с которыми можно понять работу с другими, о которых вы уже узнаете подробнее из манов, или которые мы будем использовать в следующих статьях. Первая функция, которая нам встретилась — Reshape, с нее и начнем. PHP: /* Функция принимает 2 параметра — новую ширину и новую высоту окна */ void Reshape( int W, int H ) { /* Задаем область просмотра в виде прямоугольника с диагонально противоположными вершинами (x1;y1) и (x2;y2). В нашем случае (0;0) и (W;H) */ glViewport(0, 0, W, H); /* Задаем матрицу проекционной */ glMatrixMode(GL_PROJECTION); /* Переходим на установленную проекционную матрицу */ glLoadIdentity(); /* Как известно, существуют 2 вида проекции: ортогональная и перспективная. В ортогональной лучи идут параллельными, в перспективе сходятся в некой точке. Мы будем использовать матрицу перспективы, как более удобную. Параметры — угол обзора, коэффициент отношения ширины к высоте и, так называемые, zNear и zFar или границы отсечения изображения, т.е. Изображение будет показываться в промежутке от zNear до zFar */ gluPerspective(60, (double)W / H, 1, 500); /* Откуда и куда смотрим. Первые 3 аргумента — точка, куда направлена камера, следующие 3 — точка расположения камеры, последние 3 — вектор «на макушку», т.е. задающий вектор ориентированный на верх камеры */ gluLookAt(0, 0, 15, 0, 0, 0, 0, 1, 0); /* Устанавливаем «видовую» матрицу и переходим на нее */ glMatrixMode(GL_MODELVIEW); glLoadIdentity(); } Следующая функция, которая очевидно должна быть рассмотрена — это сама функция Display. PHP: /* Функция не принимает аргументов. Тут все просто */ void Display( void ) { /* Перед рисованием нового кадра очищаем буферы цвета и глубины */ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); /* Вызываем функцию непосредственно отрисовки, ее опишем в последнюю очередь */ Draw(); /* Так как мы используем двойной буфер нам надо скинуть информацию на экран */ glutSwapBuffers(); } Далее рассмотрим функцию Idle, выполняющуюся во время простоя программы. Давайте заведем еще какую-нибудь глобальную переменную static double SyncTime и будем с помощью этой функции делать синхронизацию по времени. PHP: /* Как и Display, функция ничего не получает */ void Idle( void ) { /* Заведем пару переменных для просчета времени */ long Time; static long OldTime = -1; /* При первом запуске стартуем таймер */ if(OldTime == -1) OldTime = clock(); /* Считаем число «клоков» на данное время с момента запуска программы */ Time = clock() - OldTime; /* В SyncTime записываем уже секунды */ SyncTime = (double)Time/CLOCKS_PER_SEC; /* Перерисовываем кадр */ glutPostRedisplay(); } Последнее, что мы разберем перед самой прорисовкой кадра, это обработка клавиатуры. Тут даже ничего комментировать не стану, все и так предельно ясно. Напомню только, что 27 — это код ESC. Абсолютно аналогично будет строится обработка мыши и прочих устройств ввода. PHP: void Keyboard( unsigned char Ch, int Mx, int My ) { if (Ch == 27) exit(0); } И последнее на сегодня. Нарисуем что-нибудь в нашей сцене. Предлагаю сделать это зеленым тором в wireframe. Почему wire? Т.к. Мы пока не сделали освещения, залитая фигура будет смотреться не эстетично и не красиво. Почему тор? Просто мне нравится эта фигура, да еще она и есть среди стандартных GLUT фигур. Отрисовывать свои фигуры начнем со следующей статьи. А пока разбираем несложную функцию Draw. PHP: /* Ничего не получаем */ void Draw( void ) { /* Сохраняем текущую матрицу преобразований в стек */ glPushMatrix(); /* Устанавливаем цвет всех последующих объектов на зеленый. D в конце функции означает, что будут использоваться double числа. Есть еще вариант с float. Цвета задаются дробными числами в отрезке [0..1] */ glColor3d(0,1,0); /* Поворачиваем сцену на 30*SyncTime градусов вокруг вектора (0;1;0) */ glRotated(30*SyncTime, 0, 1, 0); /* Рисуем тор. Параметры: размер внутреннего кольца, размер внешнего кольца и 2 параметра полигонизации, т.е. качества отрисовки */ glutWireTorus(2, 5, 50, 50); /* Восстанавливаем сохраненную матрицу из стека */ glPopMatrix(); } На этом, думаю, моя первая статья из цикла OpenGL будет завершена. Надеюсь, вам она покажется интересной, а меня за нее сильно не будут пинать. Ждите новых статей из цикла OpenGL и других тем CG. Творите! Удачи!
Часть 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. Надеюсь, многие найдут ее для себя интересной и будут ожидать следующей, разумеется, еще более интересная статья, в которой мы начнем создавать собственные объекты из полигонов, текстурировать их и делать полупрозрачными. До новых статей. Всем спасибо за внимание, а я пойду, наконец, спать
Ты зря смешиваешь GL и GLU/GLUT. Я бы не стал начинать с изучения glutWireTorus и glutInitWindowSize. Самое интересное - написание оберток под все это дело. Классов и шаблонных класов для управления моделями, рендером, графикой и т.д. А так *****то. Наконецто б***ь эти все быдлохакеры-быдлокодеры сраные почитают, увидят незнакомые буковки и спросят : "ОЯЕБАЛ!! А ГДЕ ТУТ UnhookSST( ________ __)" ... Молодец, держи плюсик!! гыгы
Мой глаз больше радуют статьи о процессе крекинга нефти, например; графика меня не интересует, я в ней не силён и т. д. Но возникает закономерный вопрос: почему статьи не в соответствующем разделе? Выглядят они вполне серьёзно и достойно.
Часть 3. Рисование полигонов. Текстурирование и прозрачность 6 часов сна, гран-при Санкт-Петербурга по ЧГК, и вновь я мучаю ноут OpenOffice`ом. Большое спасибо за отзывы. Очень приятно удивили и порадовали комментарии. Еще раз спасибо! По поводу замечания KEZ`а. Возможно, при описании WinAPI и Qt, где-то там еще немного коснусь С++ для создания классов управления элементами OpenGL. Этого я вообще делать не планировал, но, возможно, это будет правильно. Я вообще хотел сделать этот курс вводным именно в технологию OpenGL, а его практическое использование уже оставить на потом в таких проектах, как Физика Твердых Тел(Rigid Body) и прочие. Но, если все же сочту полезным, то включу что-то и в этот цикл. Он разрабатывается прямо у вас на глазах. Извиняюсь, что задержал выход этой части дольше, чем обычно. Просто для полной спецификации всех функций, замазывания хвостов и продуманного изложения потребовалось больше времени, чем я ожидал. Итак, поехали. Начнем с несложного, но необходимого материала — построения своих объектов. Рисование «руками» в OpenGL начинается с функции glBegin( int Mode ), где Mode указывает режим рисования и может принимать следующие значения: Code: GL_POINTS — рисование точек GL_LINES — рисование линий между каждой парой точек GL_LINES_STRIP — рисование непрерывной ломанной GL_LINE_LOOP — рисование замкнутой непрерывной ломанной GL_POLYGON — рисование n-угольного полигона GL_QUADS — рисование четырехугольников по 4 точкам GL_QUAD_STRIP — рисование четырехугольников с общими сторонами GL_TRIANGLES — рисование треугольников GL_TRIANGLE_FAN — рисование треугольников с общей вершиной(первой заданной) GL_TRIANGLE_STRIP — рисование треугольников с общими сторонами Заканчивается рисование соответственно функцией glEnd( void ). Теперь о самих функциях рисования. Опишу их семантику. PHP: /* Рисование точек в плоскости. Параметры z тут неизменно равен нулю, w равен 1 */ void glVertex2d( double x, double y ); /* Рисование точек в 3D пространстве. W тут устанавливается равный 1 */ void glVertex3d( double x, double y, double z ); /* Рисование точек в 3D пространстве с использованием всех координат и делителя */ void glVertex4d( double x, double y, double z, double w ); Этого хватит для рисования любой фигуры. Для примера приведу пример функции, рисующей куб. Ее вызов можно подставить на место вызова нашего многострадального тора и посмотреть результат. PHP: void DrawCube( void ) { /* Начинаем отрисовку четырехугольников */ glBegin(GL_QUADS); /* Рисуем 4 граничные точки нижней грани в часовом(или против часового — главное не в рандомном) порядке */ glVertex3d(0,0,0); glVertex3d(0,0,-1); glVertex3d(1,0,-1); glVertex3d(1,0,0); /* Аналогично рисуем остальные грани */ glVertex3d(0,1,0); glVertex3d(0,1,-1); glVertex3d(1,1,-1); glVertex3d(1,1,0); glVertex3d(0,0,0); glVertex3d(0,1,0); glVertex3d(0,1,-1); glVertex3d(0,0,-1); glVertex3d(1,0,0); glVertex3d(1,0,-1); glVertex3d(1,1,-1); glVertex3d(1,1,0); glVertex3d(0,0,0); glVertex3d(0,1,0); glVertex3d(1,1,0); glVertex3d(1,0,0); glVertex3d(0,0,-1); glVertex3d(0,1,-1); glVertex3d(1,1,-1); glVertex3d(1,0,-1); /* Заканчиваем отрисовку */ glEnd(); } Реализовано в функцию это было для удобства многократного использования. Так же модифицируем функцию обработки клавиатуры до следующего вида: PHP: void Keyboard( unsigned char Ch, int Mx, int My ) { if (Ch == 27) exit(0); /* Используем функцию glPolygonMode, задающую отображение полигонов. Параметры: тип поверхности(GL_FRONT, GL_BACK или GL_FRONT_AND_BACK) и вид их отображения(GL_LINE — wireframe, т.е. рисование ребер, GL_FILL — полная заливка и GL_POINT — вершины). Стоит отметить, что GL_FILL логичнее отрисовывать с первым параметром GL_FRONT чтоб не рисовать лишние все равно не видные поверхности. */ if (Ch == 'w') glPolygonMode(GL_FRONT_AND_BACK,GL_LINE); if (Ch == 'f') glPolygonMode(GL_FRONT_AND_BACK,GL_FILL); if (Ch == 'p') glPolygonMode(GL_FRONT_AND_BACK,GL_POINT); } Так же обговорю еще несколько функций, о которых, наверное, следовало бы рассказать еще раньше. В первую очередь, это функции аффинных преобразований или(для тех кто еще учится в школе) геометрического движения, т.е. преобразования плоскости, сохраняющих расстояния между любыми двумя точками. Итак, эти функции: PHP: /* C этой функцией мы уже сталкивались. Поворот на угол angle вокруг вектора (x;y;z) */ void glRotated( double angle, double x, double y, double z ); /* То же, но для float чисел. Кстати говоря, видеокарта умеет работать лишь с float`ами, так что в итоге все числа все равно будут преобразованы к нему, так что решайте, стоит ли вообще пользоваться double. Мне так почему-то привычнее, хоть это и ест больше памяти. */ void glRotatef( float angle, float x, float y, float z ); /* Параллельный перенос на вектор (x;y;z) */ void glTranslated( double x, double y, double z ); void glTranslatef( float x, float y, float z ); /* Не движение, т.к. не сохраняет расстояние, но из той же серии — масштабирование по трем коэффициентам x, y и z для каждого измерения */ void glScaled( double x, double y, double z ); void glScalef( float x, float y, float z ); Хочется напомнить, что все эти функции — это всего лишь тензорное исчисление. А именно, преобразование матрицы. Для ручного задания новой матрицы используется функция glLoadMatrixd( double *matrix ), где matrix — массив double из 16 элементов, поскольку в OpenGL используются матрицы 4х4. Разумеется, вы уже догадались что так же существует функция glLoadMatrixf( float *matrix ). Так же вы можете умножить текущую матрицу на вашу с помощью функций glMultMatrixd( double *matrix ) и glMultMatrixf( float *matrix ). Тут параметр matrix задается так же, как и в LoadMatrix. Теперь, вы понимаете, что задав один маленький квадратик мы можем рисовать любой параллелепипед, перемещать его в любое место, растягивать, сжимать, сдвигать(математическое понятие Shift путем умножения матриц) и так далее. Итак, мы можем нарисовать любой объект в любой точке нашего виртуального пространства. Так же хочу отметить еще функции установки параметров «кисти» рисования. А именно: PHP: /* Все просто — устанавливаем размер точек */ void glPointSize( float size ); /* Еще проще — толщина линий */ void glLineWidth( float width ); /* Способ рисования линий. Требует включения(glEnable) GL_LINE_STIPPLE. Функция устанавливает метод штриховки линий. Параметры: factor — множитель битов в маске, т.е. число использования бита до перехода к следующему. Дефолтное значение — 1, принимает целое число в отрезке [1;256](Нафига для этого использовать int — х.з.[I] *вольное примечание[/I]). pattern — битовая маска(16битное целое число), по которой определяют отображаемые фрагменты линии */ void glLineStipple( int factor, unsigned short pattern ); /* Практически то же, но для полигонов. Задание штриховки полигонов по маске. Передается массив из 1024 элементов для создания маски рисования 32х32. Требует включения GL_POLYGON_STIPPLE */ void glPolygonStipple( unsigned char *mask ); Таким образом, мы можем менять не только параметры матрицы отображения, но и способов рисования, а потому наравне с функциями сохранения/восстановления матриц из стека(то, что это стек очень важно: помним про принцип LIFO — last in first out, т.е. последний вошедший выходит первым) существуют функции glPushAttrib и glPopAttrib для сохранения/восстановления параметров рисования. В отличие от работы с матрицами в glPushAttrib мы должны передать параметр GL_ALL_ATTRIB_BITS для сохранения всех параметров или один из следующих параметров(объяснения приводить не буду — то, что реально используется, и так ясно из предыдущего материала): GL_ACCUM_BUFFER_BIT, GL_COLOR_BUFFER_BIT, GL_CURRENT_BIT, GL_DEPTH_BUFFER_BIT, GL_ENABLE_BIT, GL_EVAL_BIT, GL_FOG_BIT, GL_HINT_BIT, GL_LIGHTING_BIT, GL_LINE_BIT, GL_LIST_BIT, GL_PIXEL_MODEL_BIT, GL_POINT_BIT, GL_POLYGON_BIT, GL_POLYGON_STIPPLE_BIT, GL_SCISSOR_BIT, GL_STENCIL_BUFFER_BIT, GL_TEXTURE_BIT, GL_TRANSFORM_BIT, GL_VIEWPORT_BIT. Итак, мы уже почти вплотную стоим рядом с текстурированием. Но, если вы запускали измененные проекты, то могли заметить кривость освещения, а потому последнее маленькое замечание. Дело в том, что когда мы рисовали GLUT фигуры(если вы заинтересовались материалом и рисовали не только тор, но сферу, куб и прочие объекты), то нормали у нас задавались автоматом. Но в данном случае мы все рисовали сами, а потому никаких нормалей нет. Конечно, можно положиться на glEnable(GL_AUTO_NORMAL), но я бы не стал этого делать. В этой ситуации к нам на помощь приходят функции задачи нормалей. А конкретно: PHP: /* Задаем нормаль вектором (nx;ny;nz) */ void glNormal3d( double nx, double ny, double nz ); void glNormal3f( float nx, float ny, float nz ); Так же в функцию нормали и точки можно задавать вектора(структура с необходимым количеством double или float полей). В таком случае, как это обычно бывает, в конце названия имени функции будет так же присутствовать буква «v». Короче, извращаться можно как угодно. Реально, вы сами определите, что вам удобнее и будете этим пользоваться, забыв про остальное. Итак, все старые хвосты подправлены, все новые объекты построены. Теперь займемся украшательством, а именно натянем текстуры и посмотрим на прозрачность. В учебных целях я буду использовать не общепринятые форматы BMP, JPG и проч, т.к. писать функции загрузки их в память — это тема отдельной статьи, причем в случае JPG даже не одной. Использовать мы будем некие вымышленные форматы файлов, в которых первые 2 байта — ширина изображения, следующие 2 байта — высота, а далее идет описание каждой точки 3 байтам(r,g,b) для 24-битных изображений и 4 байтами(r,g,b,alpha) для 32-битных изображений. Форматы эти взяты не «с потолка». Примерно в таком виде хранятся изображения после загрузки в оперативной памяти. В учебных целях это самое то для быстрого и не нудного объяснения. В случае ваших проектов, вы, конечно, сможете использовать более популярные форматы загружая их в память и используя их абсолютно аналогично. Для начала напишем функции загрузки в память этих текстур. PHP: /* Возвращаем 1 хорошо и 0, если нет. Принимаем имя файла с текстурой */ int LoadTexture( char *FileName ) { FILE *F; int W = 0, H = 0; unsigned char *Image; /* Открываем файл */ if ((F = fopen(FileName, "rb")) == NULL) return 0; /* Считываем ширину и высоту изображения */ fread(&W, 2, 1, F); fread(&H, 2, 1, F); /* Выделяем память под изображение. Если память не выделилась, закрываем файл и выходим с ошибкой */ if ((Image = malloc(sizeof(unsigned char)*3*W*H)) == NULL) { fclose(F); return 0; } /* Считываем изображение в память */ fread(Image, 3, W * H, F); /* Устанавливаем параметры текстуры. Первый параметр — тип текстуры. В данном случае, GL_TEXTURE_2D, т.е. двухмерной текстуры(тут может быть одно из значений: GL_TEXTURE_2D или GL_TEXTURE_1D). Далее следует параметр текстуры и его значение. В первых 2 случаях мы устанавливаем, что делать с текстурой, если она больше объекта(GL_TEXTURE_MAG_FILTER) и меньше объекта(GL_TEXTURE_MIN_FILTER). Значение GL_LINEAR_MIPMAP_LINEAR означает, что текстура должна быть подогнана с максимальным соответствием и фильтрацией(так же существуют значения GL_NEAREST, GL_LINEAR, GL_NEAREST_MIPMAP_NEAREST, GL_LINEAR_MIPMAP_NEAREST, GL_NEAREST_MIPMAP_LINEAR. Тут все стоит понимать из расчета NEAREST — быстро, но не точное соответствие текстуре, а LINEAR — максимальное соответствие изображению на текстуре с помощью линейной интерполяции). В следующих же двух вызовах glTexParameteri мы задаем параметры S и T текстурных координат(почему-то их так принято называть). GL_CLAMP делает выравнивание по краям, GL_REPEAT размножает текстуру по поверхности. Разумеется, так же существуют функции glTexParameterf, glTexParameterfv и др. */ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); /* Строим 2D текстуру с помощью специальной функции glBuild2DMipmaps. Параметры: тип текстуры(по моим спецификациям всегда имеет значение GL_TEXTURE_2D), байты на точку(так же есть продефайненные значения, смысл которых ясен из названий: GL_ALPHA, GL_ALPHA4, GL_ALPHA8, GL_ALPHA12, GL_ALPHA16, GL_LUMINANCE, GL_LUMINANCE4, GL_LUMINANCE8, GL_LUMINANCE12, GL_LUMINANCE16, GL_LUMINANCE_ALPHA, GL_LUMINANCE4_ALPHA4, GL_LUMINANCE6_ALPHA2, GL_LUMINANCE8_ALPHA8, GL_LUMINANCE12_ALPHA4, GL_LUMINANCE12_ALPHA12, GL_LUMINANCE16_ALPHA16, GL_INTENSITY, GL_INTENSITY4, GL_INTENSITY8, GL_INTENSITY12, GL_INTENSITY16, GL_RGB, GL_R3_G3_B2, GL_RGB4, GL_RGB5, GL_RGB8, GL_RGB10, GL_RGB12, GL_RGB16, GL_RGBA, GL_RGBA2, GL_RGBA4, GL_RGB5_A1, GL_RGBA8, GL_RGB10_A2, GL_RGBA12, GL_RGBA16), ширина и высота изображения, спецификация описания точек, т.е. как их читают(в нашем случае порядок, например, обратный Blue, Green, Red(такой порядок был выбран по аналогии с BMP). Так же могут использоваться следующие значения: GL_COLOR_INDEX, GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA, GL_RGB, GL_RGBA, GL_LUMINANCE, GL_LUMINANCE_ALPHA), далее идет тип данных(напомню, что unsigned char так же принято называть BYTE. Дополнительные возможные значения: GL_UNSIGNED_BYTE, GL_BYTE, GL_BITMAP, GL_UNSIGNED_SHORT, GL_SHORT, GL_UNSIGNED_INT, GL_INT, GL_FLOAT), и, наконец, само изображение */ gluBuild2DMipmaps(GL_TEXTURE_2D, 3, W, H, GL_BGR_EXT, GL_UNSIGNED_BYTE, Image); /* Очищаем память и закрываем файл */ free(Image); fclose(F); /* Возвращаемся с сообщением об удаче */ return 1; } Аналогично можно загружать текстуры, использующие Alpha-канал. PHP: int LoadTextureA( char *FileName ) { FILE *F; int W = 0, H = 0; unsigned char *Image; if ((F = fopen(FileName, "rb")) == NULL) return 0; fread(&W, 2, 1, F); fread(&H, 2, 1, F); if ((Image = malloc(sizeof(unsigned char)*4*W*H)) == NULL) { fclose(F); return 0; } fread(Image, 4, W * H, F); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); gluBuild2DMipmaps(GL_TEXTURE_2D, 4, W, H, GL_BGRA_EXT, GL_UNSIGNED_BYTE, Image); free(Image); fclose(F); return 1; } Если вы не хотите писать свою загрузку BMP текстур, то взять готовый модуль можно отсюда: _http://local.wasp.uwa.edu.au/~pbourke/dataformats/bmp/ В Windows же, если я не ошибаюсь, даже есть стандартная функция, но это как-то не по-джедайски. Итак, у нас есть функции загрузки. Осталось разобраться, как ее использовать. Для этого нам надо будет добавить инициализацию текстур и собственно их рисование. Начнем с инициализации. Для того добавим глобальный массив int Textures[MAX_TEX], где MAX_TEX — максимально число текстур, которое вы планируете использовать(без фанатизма, не делайте его особо большим). Далее в многострадальную функцию MyInit добавим несколько строк: PHP: /* Инициализируем Textures как массив MAX_TEX текстур */ glGenTextures(MAX_TEX, Textures); /* Следующая пара действий проводится для каждой выбранной вами текстуры, где i=0..MAX_TEX-1. С помощью функции glBindTexture мы переклчаемся на необходимую текстуру. Т.е. все последующие действия будут происходить с текстурой в Textures[i]. Парметры: тип текстуры и, собственно, само место «куда отщелкать» текущее положение. Далее с помощью самодельной функции LoadTexture или LoadTextureA(или еще какой-нибудь вашей функции), мы грузим саму текстуру из файла. */ glBindTexture(GL_TEXTURE_2D, Textures[i]); LoadTexture(«/full/path/to/texture»); Теперь на счет рисования самой текстуры по объекту. Для рисования текстуры требуется: 1) Включить режим рисования текстуры GL_TEXTURE_2D(как всегда, с помощью функции glEnable). 2) Переключиться на необходимую текстуру с помощью функции glBindTexture 3) Нарисовать объект, расставляя текстурные координаты 4) После рисования объекта с этой текстурой отключить режим текстурирования(glDisable(GL_TEXTURE_2D)) Тут все ясно, кроме расставления текстурных координат. В GLUT-моделях они, разумеется, уже расставлены, но нам требуется рисоваться собственные модели. Тут к нам на помощь приходят функции glTexCoord1d, glTexCoord2d, glTexCoord3d, glTexCoord4d и, соответственно, из производные, как и во всех GL-функциях. В качестве параметром они получают текстурные координаты заданного типа в количестве 1,2,3 или 4 соответственно. Для иллюстрации создания текстурированной модели с нормалями нарисуем простейший квадратик и затекстурируем его. Комментарии тут излишни. PHP: void DrawRectangle( void ) { glBegin(GL_QUADS); glNormal3d(0, 0, 1); glTexCoord2f(0, 0); glVertex3d(0,0,0); glTexCoord2f(0, 1); glVertex3dv(0,1,0); glTexCoord2f(1, 1); glVertex3dv(1,1,0); glTexCoord2f(1, 0); glVertex3dv(1,0,0); glEnd(); } И, напоследок, пара слов о прозрачности. Разделяют два вида прозрачности: прозрачность по маске и прозрачность с Alpha-каналом. Прозрачность с маской мы можем реализовать уже с помощью описанной ранее функции glPolygonStipple. Приведу пример рисования объекта в таком режиме. Комментарии излишни — все это вы уже знаете. PHP: static unsigned long Mask[] = { 0xAAAAAAAA, 0x55555555, 0xAAAAAAAA, 0x55555555, 0xAAAAAAAA, 0x55555555, 0xAAAAAAAA, 0x55555555, 0xAAAAAAAA, 0x55555555, 0xAAAAAAAA, 0x55555555, 0xAAAAAAAA, 0x55555555, 0xAAAAAAAA, 0x55555555, 0xAAAAAAAA, 0x55555555, 0xAAAAAAAA, 0x55555555, 0xAAAAAAAA, 0x55555555, 0xAAAAAAAA, 0x55555555, 0xAAAAAAAA, 0x55555555, 0xAAAAAAAA, 0x55555555, 0xAAAAAAAA, 0x55555555, 0xAAAAAAAA, 0x55555555, }; glPushMatrix(); glEnable(GL_POLYGON_STIPPLE); glPolygonStipple(Mask); /* Рисуем полупрозрачные объекты */ glDisable(GL_POLYGON_STIPPLE); glPopMatrix(); А теперь рассмотрим вариант с Alpha-каналом. PHP: /* Загружаем текстуру с Alpha-каналом */ glBindTexture(GL_TEXTURE_2D, 1); LoadTextureA(«path_to_texture_with_alpha»); /* Рисуем все цвета текстуры с множителями цветов 1 и Alpha-прозрачностью 50% */ glColor4d(1, 1, 1, 0.5); /* Включаем опцию смешивания(бленидинга) */ glEnable(GL_BLEND); /* Включаем поддержку «нерисования» каких-либо частей полигонов. С помощью функции glCullFace можно определять какие именно. Возможные параметры нам уже хорошо знакомы: GL_FRONT, GL_BACK и GL_FRONT_AND_BACK */ glEnable(GL_CULL_FACE); /* Определяем смешивание для компонентов. В данном случае Alpha-канал. */ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUX_SRC_ALPHA); /* Рисуем полупрозрачные объекты */ /* Выключаем подключенные опции */ glDisable(GL_CULL_FACE); glDisable(GL_BLEND); Наконец, с облегчением вам сообщаю, что очередная статья цикла окончена. По-моему, она была информативна. Надеюсь, вы посчитаете так же. Во всяком случае, следящим за этим циклом явно будет чем заняться перед следующей честью. Итак, до встречи в шейдерах!
Классно. Прекрасно подходит для начального изучения opengl и glut. Написано все понятно и поймет каждый
Да.. чё вы наезжаете на чувака.. не знаю... лично мне статьи понравились... Так держать!. Главное то что он их сам писал, а не упёр у кого то.