Поиск по базе сайта:
Оглавление: Введение. Каркас Opengl программы icon

Оглавление: Введение. Каркас Opengl программы




Скачати 259.86 Kb.
НазваОглавление: Введение. Каркас Opengl программы
Дата конвертації15.11.2012
Розмір259.86 Kb.
ТипДокументи

OpenGL для начинающих разработчиков игр.


Посвящается Игорю Тарасову


Оглавление:


1. Введение. Каркас OpenGL программы.

2. Перемещение объектов. Анимация.

3. Клавиатура. Мышь.

4. Рисование разных примитивов. Сложные объекты. Нормали. Дисплейные списки.

5. Материалы. Освещение. Масштабирование.

6. Текстуры.

7. Загрузка объектов из файла.

8. Вывод текста.

9. Эффекты.

10. Приятные и полезные вещи: многооконные приложения, пользовательский интерфейс, создание курсора мыши.

11. OpenGL+DirectX=...

12. Делаем настоящие игры.


Приложение 1. Установка всех необходимых компонентов и библиотек.


1.


Наверное, вам уже давно хочется создать свою трехмерную виртуальность. Хоть какую-нибудь, но свою. В принципе, можно использовать что-нибудь вроде DarkBasic или вообще, купить себе движок Q3A, и программирование ограничится скриптовым языком. Но это все для ламеров, мы-то к ним не относимся ;-). В качестве простого решения я предлагаю OpenGL и эту книжку. Почему OpenGL а не DirectX? Дело в том, что OpenGL работает как конечный автомат, и в этом смысле он гораздо проще в изучении, чем Direct3D, полностью построенный на классах и интерфейсах. OpenGL – кросс-платформенная графическая библиотека. На какой платформе вы бы не программировали, на Windows, Linux, Unix, Macintosh, весь код относящися собственно к OpenGL был бы совершенно одинаков и отличалось бы только 200-300 строк относящихся к конкретной системе. OpenGL – расширяем. Значит, если в лаборатории Nvidia или Ati открыли какую-то новейшую сверхтехнологию, то Microsoft выпускает новую версию DirectX, а SGI просто добавляет в спецификацию OpenGL новое расширение. Стандарт OpenGL почти не изменился с 1996 года, и не изменится кардинально до выхода OpenGL2. Все, что вы напишите сейчас, пользуясь OpenGL, будет работать и компилироваться через год-два, и будет точно таким же, как вы его создали сегодня. OpenGL не зависит от языка и среды программирования и мы можем писать наши программы на Fortran, Basic, Pascal, C, C++, etc. Я лично предпочитаю Ms Visual С++ 7/Windows, и работоспособность всех примеров из предлагаемой нашему любознательному читателю книги проверена именно в этой среде.




Итак, приняв решение о выборе OpenGL в качестве базового трехмерного движка, бежим в магазин покупать какую-нибуть современную среду разработки, желательно MSVC++7, т.к. далее порядок действий далее я буду описывать для неё.


Также нам понадобится библиотека Glut, которую можно скачать с моего сайта (OpenGL3D.fatal.ru) (см. Прил. 1).


Запустив MSVC++7 вы увидите перед собой примерно такое:

.

1 – Solution Explorer. Здесь в виде дерева показаны все файлы, имеющиеся в проекте. Пока что наш проект пуст и окно девственно чисто.


2 – Dynamic Help. Если вы почти/совсем не знаете С++, советую чаше сюда заглядывать. При установленной MSDN collection просто наведите курсор на непонянтый код и получите подробную справку.


3 – Task list. Вобще-то это органайзер, но сюда выводятся все сообщения и предупреждения об ошибках компиляции и сборки.


4 – Собственно рабочая область с закладками. Пока что она там только одна – Visual Studio start page.


Теперь, пощелкав из любопытства в разные места, приступаем к делу.

1.Щелкайте File->New->Project и в появившемся окне справа вверху щелкните Win32 Project. Наделите проект именем, например “OpenGL1” и щелкните OK.


2. Затем выберите Application Settings и затем Console Application. Щелкните Finish и удалите из Solution Explorer (1) все файлы, кроме OpenGL1.cpp. (Правый клик по файлу -> Remove).


3. В Solution Explorer правой кнопкой мыши щелкните по надписи OpenGL1 и выберите “Properties”. Появится окно настроек проекта. В нем выберите папку “Linker”, а в ней – “Input”. Справа выберите “Additional Dependencies”.


4. Впишите туда “opengl32.lib glu32.lib glut32.lib”.


5. В папке “C++” выберите “Precompiled header” и сотрите все из второго поля.


Все! Мы готовы к нормальной работе. Теперь посмотрим (дважды кликнув по OpenGL1.cpp) что же нам сгенерировал MSVC++:


// OpenGL1.cpp : Defines the entry point for the console application.

//


#include "stdafx.h"


int _tmain(int argc, _TCHAR* argv[])

{

return 0;

}


Ерунда, правда? ;-). Удаляем все это и спокойно пишем:


#include

#include

#include

#include "glut.h"


Эти четыре строки подключают все необходимые библиотеки, такие как OpenGL от Microsoft, Glu и Glut который должен быть в каталоге с программой. Они необходимы для любой программы, использующей OpenGL.


void resize(int width,int height)

{

glViewport(0,0,width,height);

glMatrixMode( GL_PROJECTION );

glLoadIdentity();

gluPerspective(45.0, width/height, 0.1,10);

gluLookAt( 0,0,5, 0,0,0, 0,1,0 );

glMatrixMode( GL_MODELVIEW );

}


Это – функция изменения размера окна программы. Она будет вызываться каждый раз, когда у окна программы будет изменен размер, а также при создании окна. Команда gluPerspective устанавливает угол зрения (у нас – 45.0, первый параметр), степень перспективы (второй параметр), переднюю и заднюю плоскости отсечения. Все предметы, находящиеся за плоскостью отсечения вырезаются, так что с этими параметрами надо быть осторожней. Команда gluLookAt задает положение наблюдателя (первые три параметра) и точку, в которую он смотрит (следующие три).


void display(void)

{

glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

// ...

glutSwapBuffers();

}


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


void main()

{

glutInitWindowPosition(50, 50);

glutInitWindowSize(640, 480);

glutInitDisplayMode( GLUT_RGB | GLUT_DEPTH | GLUT_DOUBLE );

glutCreateWindow("OpenGL1 Window");

glutIdleFunc(display);

glutDisplayFunc(display);

glutReshapeFunc(resize);


glEnable(GL_DEPTH_TEST);

glEnable(GL_COLOR_MATERIAL);

glEnable(GL_LIGHTING);

glEnable(GL_LIGHT0);

glutMainLoop();

}


Это – самая главная функция, которая исполняется в самом начале. Первая команда задает положение окна, вторая его размер. Третья команда устанавливает различные режимы вывода (Rgb цветовое пространство, с буфером глубины (веть у нас 3D), двойная буфферизация.

Четвертая команда создает окно с названием “Opengl1 Window”

Пятая и шестая команды указывают, что в случае неактивности и во всех остальных случаях нужно исполнять функцию display.


Следующие четыре комманды относятся к OpenGL. C помощью них включается: тест буфера глубины, необходимый для трехмерных объектов, цветные материалы, иначе все объекты будут серые, освещение и первый источник света. Вообще, с помощью glEnable можно многое включить, а с помощью glDisable выключить то, что включили.


И, наконец, последняя команда запускает цикл, в котором будет все время повторяться функция display.


Вот мы и закончили обзор простейшей программы на основе OpenGL.


Нажмите Ctrl+F5, щелкните Yes и наслаждайтесь…


Да, к сожалению, пока ничего не видно. Но стоит в функции display вместо строки “// ...” вставить, например “glutSolidTeapot(1.0);” и мы тут же увидим значительную разницу! Одной командой мы создали чайник! Впечетляет? Давайте оценим затраты времени. В OpenGL, чтобы нарисовать чайник, нам понадобилось 43 строки кода, в Direct3D нам бы понадобилось написать 320 строк, а при написании собственного движка… Это уж сами считайте.

Правда, чайник некрасивый – белый, да и фон так себе… Теперь встравляем еще две комманды:


glClearColor(0.3,0.2,0.6,1.0);


glColor3f(0.0,0.0,1.0);


перед рисованием чайника и получаем новый результат: на сиреневом фоне – синий чайник!

Команда glClearColor заливает фон цветом, который определяют три первых параметра (четвертый – фиктивный): соотношение красного, зеленого и синего цветов [0;1]. glColor3f задает текущий цвет, параметры те же: соотношение красного, зеленого, синего [0;1]. Вообще, многие комманды в OpenGL содержат всякие суффиксы, которые означают количество и тип аргументов, например 3f означает 3 float. Ниже дана таблица, для лучшего понимания команд OpenGL:


Суффикс, тип, размер

Значение

B (GLbyte), 1

UB (GLubyte), 1

Байт

Беззнаковый байт

F (GLfloat), 4

Значение с плавающей точкой

S (GLshort), 2

Короткое целое

US (GLushort), 2

Беззнаковое короткое целое

I (GLint), 4

Целое

UI (GLuint), 4

Беззнаковое целое

D (GLdouble), 8

Дробное с двойной точностью

V

Массив из нескольких значений


Например: 3ubv означает, что представлены три параметра типа беззнаковый байт, выраженные массивом.

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


Команды GLUT также имеют свои аффиксы, то есть кроме таких команд, как glutSolidTeapot, которая рисует чайник со сплошной заливкой, есть, скажем, и команда glutWireTeapot, которая рисует “проволочный” чайниик.


Упражнения.


  1. Модифицируйте всю программу на свой вкус и цвет. Измените размер окна, его заголовок, различные настройки.

  2. Кроме glutSolidTeapot, в библиотеке GLUT содержится еше множество различных команд для рисования объектов, например:


glutSolidCube(float size);

glutSolidDodecahedron();

glutSolidOctahedron();

glutSolidThetrahedron();

glutSolidIcosahedron();


Измените команду glutSolidTeapot на одну из этих и поупражняйтесь в рисовании геометрических тел. Также попробуйте применить аффикс “wire”. Примечание: размер рекомендую ставить от 0.1 до 2, помните, что наш чайник-гигант был размером только в 1.


2.


Конечно же, у вас сразу возникло много вопросов. Действительно, очень странно, что чайник и другие объекты всегда рисуются в центре, а функции, их рисующие не имеют никаких параметров насчет координат. Все дело в том, что все объекты всегда рисуются в точке (0,0,0), а когда необходимо передвинуть или повернуть объект, мы поворачиваем или переносим систему координат относительно текущей. Но что же делать, если мы хотим один объект передвинуть, а другой оставить на месте? Ведь если мы передвинем один объект, а потом нарисуем другой, то другой объект тоже передвинется. Значит, надо все время запоминать на сколько мы сдвинулись и возвращаться назад. Слава богу, в OpenGL включены команды glPushMatrix() / glPopMatrix(), которые запоминают текущее “положение вещей” (PushMatrix) а потом восстанавливают то, что запомнили. Эти команды работают со стеком, поэтому их можно использовать вложенно. В OpenGL своя собственная система координат: ось X направлена вправо, ось Y – вверх, а ось Z – к наблюдателю. Да, и конечно, команды для поворота и переноса:


glRotatef(int angle, float x, y, z) – поворачивает систему координат на угол angle отпосительно вектора, который определен x, y и z. Я не рекомендую задавать x, y или z отличными от нуля и единицы, т.к. результат подобной операции не определен.


glTranslatef(float x, y, z) – перенос системы координат на x, y, z относительно текущей.


Посмотрите, например, на такой фрагмент кода:


glTranslatef(-0.4,0,0);

glutSolidTeapot(0.15);

glTranslatef(-0.4,0,0);

glutSolidTeapot(0.17);

glTranslatef(-0.4,0,0);

glutSolidTeapot(0.19);


Если добавить его в программу без дополнительных комманд, то вместо трех чайников, построенных в ряд по X в левую стовону, в первую секунду мы может, ещё что-нибуть увидим, а вот во вторую секунду – уже ничего. Чайники убегут влево с неимоверной быстротой. Почему? Ведь при прорисовке кадра система координат не возвращается в изначальное положение. И мы каждый кадр сдвигаемся влево на 1.2. Как это предотвратить? Очень просто:


glPushMatrix();

glTranslatef(-0.4,0,0);

glutSolidTeapot(0.15);

glTranslatef(-0.4,0,0);

glutSolidTeapot(0.17);

glTranslatef(-0.4,0,0);

glutSolidTeapot(0.19);

glPopMatrix();


Здесь команда glPushMatrix() запоминает текущее положение (0,0,0), потом рисуются чайники, каждый с относительным сдвигом в 0.2. Таким образом, третий чайник сдвинут на 0.6 относительно “абсолютного нуля” (первоначального положения). Я лично вам рекомендую обрамлять в комманды glPushMatrix()/glPopMatrix() все объекты независимо движущиеся, и всегда переносить и поворачивать их относительно “абсолютного нуля”. Иначе вы очень быстро запутаетесь, что относительно чего передвигалось. То же самое относится и к повороту, только тут запутаться проще:


glPushMatrix();

glTranslatef(-0.4,0,0);

glRotatef(-90,1,0,0);

glutSolidTeapot(0.15);

glTranslatef(-0.4,0,0);

glRotatef(90,1,0,0);

glutSolidTeapot(0.19);

glPopMatrix();


Что мы ожидаем увидеть? Конечно, два чайника, один лежит на одном боку, а другой на другом. А получаем: один лежит действительно на боку, а вот другой почему-то не сдвинулся с места. Не пугайтесь, здесь магии нет, просто первый glRotatef повернул всю систему координат, а второй повернул ее обратно. Правильный код:


glPushMatrix();

glTranslatef(-0.4,0,0);

glPushMatrix();

glRotatef(-90,1,0,0);

glutSolidTeapot(0.15);

glPopMatrix();

glTranslatef(-0.4,0,0);

glPushMatrix();

glRotatef(90,1,0,0);

glutSolidTeapot(0.19);

glPopMatrix();

glPopMatrix();


Как видвите, glPopMatrix()/glPushMatrix могут быть вложенными.

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


Раз уж мы умеем вращать и перемещфть объекты, то неплохо было бы делать это динамически, анимированно. Для создания анимации в OpenGL существует множество разных способов, самый простой из них – просто выполнять анимацию при каждой прорисовке кадра, например:


int r=0;


// ...


void display(void)

{

glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

glClearColor(0.0,0.0,0.0,1.0);


glColor3f(0.0,0.0,0.8);


glPushMatrix();

glRotatef(r,1,1,0);

glutSolidTeapot(0.7);

glPopMatrix();


r++;

if (r>=360) {r=0;};


glutSwapBuffers();

}


Но у этого подхода есть один, но очень существенный недотсаток: скорость вращения чайника будет зависеть от производительности компьютера. Т.е. на одном компьютере чайник обернется вокруг двух своих осей за 1 сек., а на другом – за 10 мин. Решение этой проблемы заключается в установке мультимедийного таймера. Он может выполнять функцию с интервалом до 1 мс (0.001 сек). Мультимедийный таймер очень точен и всегда стабилен.


uint timeSetEvent(UINT uDelay, UINT uResolution, LPTIMECALLBACK fptc, DWORD_PTR dwUser, UINT fuEvent) – устанавливает таймер и возвращает его идентификатор. Первый параметр – интервал таймера в миллисекундах, второй – количество миллисекунд, ограничивающих обработку каждого тика, т.е. точность. Если значение задано нулём, обработка таймера происходит с максимально возможной точностью. Третий параметр – процедура обработки тика, четвертый – значение, передаваемое этой процедуре. Последний параметр фиктивен – для установки переодического, а не одноразового таймера его надо задавать равным TIME_PERIODIC.


Вот пример его использования: (здесь я привел весь исходный код, и во избежание ошибок новые строки выделены).


#include

#include

#include

#include "glut.h"

#include


int r=0;

GLuint timerId;

// ...


void CALLBACK TimerProc(GLuint uTimerId, GLuint uMessage,

DWORD_PTR dwUser, DWORD_PTR dw1, DWORD_PTR dw2)

{

r++;

if (r>=360) {r=0;};

}


void resize(int width,int height)

{

glViewport(0,0,width,height);

glMatrixMode( GL_PROJECTION );

glLoadIdentity();

gluPerspective(45.0, width/height, 0.1,100);

gluLookAt( 0,0,5, 0,0,0, 0,1,0 );

glMatrixMode( GL_MODELVIEW );

}


void display(void)

{

glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

glClearColor(0.0,0.0,0.0,1.0);


glColor3f(0.0,0.0,0.8);


glPushMatrix();

glRotatef(r,1,1,0);

glutSolidTeapot(0.7);

glPopMatrix();

glutSwapBuffers();

}


void main()

{

glutInitWindowPosition(50, 50);

glutInitWindowSize(640, 480);

glutInitDisplayMode( GLUT_RGB | GLUT_DEPTH | GLUT_DOUBLE );

glutCreateWindow("OpenGL1 Window");

glutIdleFunc(display);

glutDisplayFunc(display);

glutReshapeFunc(resize);

glEnable(GL_DEPTH_TEST);

glEnable(GL_COLOR_MATERIAL);

glEnable(GL_LIGHTING);

glEnable(GL_LIGHT0);

timerId=timeSetEvent(10, 0, TimerProc, 0, TIME_PERIODIC);


glutMainLoop();


timeKillEvent(timerId);

}


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


Упражнения.



  1. Пусть по экрану носится чайник, отталкиваясь от стенок

  2. Теперь чайник должен носиться, вращаясь при этом вокруг своей оси (Подсказка: мы должны сначала переместить систему координат, а потом уже, относительно новых координат, поворачиваем его).

  3. Сделайте программу “парад объектов”, где будет изображено много различных объектов сразу в виде таблицы. Добавьте им различные скороти вращения



3.


Итак, все вроде знаем: объекты какие-то умеем рисовать, анимацию делать, на 3D-игры вроде похоже. Но главный недостаток всех наших программ виден сразу: никакой реакции на действия пользователя. Даже alt+F4 почему-то не работает. Не бойтесь, здесь вас тоже ничего сложного не ожидает, все просто как… Ну, как все остальное.


glutKeyboardFunc(void (*func)(unsigned char key, int x, int y)) – устанавливает обработчик с клавиатуры. Параметр – функция обработки вида: void keyfunc(unsigned char key, int x, int y), где key – клавиша, x, y – оконные координаты мыши.


Вот пример:


#include

#include

#include

#include "glut.h"


int r=0;


// ...


void keyboard(unsigned char key, int x, int y)

{

switch (key) {

case 'a': r++; break;

case 'z': r--; break;

}

}


void resize(int width,int height)

{

glViewport(0,0,width,height);

glMatrixMode( GL_PROJECTION );

glLoadIdentity();

gluPerspective(45.0, width/height, 0.1,100);

gluLookAt( 0,0,5, 0,0,0, 0,1,0 );

glMatrixMode( GL_MODELVIEW );

}


void display(void)

{

glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

glClearColor(0.0,0.0,0.0,1.0);


glColor3f(0.0,0.0,0.8);


glPushMatrix();

glRotatef(r,1,1,0);

glutSolidTeapot(0.7);

glPopMatrix();

glutSwapBuffers();

}


void main()

{

glutInitWindowPosition(50, 50);

glutInitWindowSize(640, 480);

glutInitDisplayMode( GLUT_RGB | GLUT_DEPTH | GLUT_DOUBLE );

glutCreateWindow("OpenGL1 Window");

glutIdleFunc(display);

glutDisplayFunc(display);

glutReshapeFunc(resize);

glutKeyboardFunc(keyboard);


glEnable(GL_DEPTH_TEST);

glEnable(GL_COLOR_MATERIAL);

glEnable(GL_LIGHTING);

glEnable(GL_LIGHT0);


glutMainLoop();


}


Вот и все! При нажатии клавиш ‘a’ и ‘z’ (только на английской раскладке клавиатуры) чайник будет вращаться в одну или в другую сторону, в зависимости от нажатой клавиши. Но glutKeyboardFunc не обрабатывает такие клавиши, как: стрелки, Fx, insert и многие другие. Для этих клавиш существует функция


glutSpecialFunc(void (*func)(unsigned char key, int x, int y)) – все то же самое, что и glutKeyboardFunc, но вместо кода клавиши передается специфический идентификатор, который может быть:


GLUT_KEY_Fx – Fx.

GLUT_KEY_LEFT – Стрелка влево.

GLUT_KEY_UP – Стрелка вверх.

GLUT_KEY_RIGHT – Стрелка вправо.

GLUT_KEY_DOWN – Стрелка вниз.

GLUT_KEY_PAGE_UP – Page up.

GLUT_KEY_PAGE_DOWN – Page down.

GLUT_KEY_HOME – Home.

GLUT_KEY_END – End.

GLUT_KEY_INSERT – Insert.


Примечание: delete, backspace, enter, escape передаются в функцию glutKeyboardFunc.


Все, с клавиатурой вроде разобрались. Да, конечно же, совсем забыл:


int glutGetModifiers() – возвращает, нажаты ли Alt, Control, Shift. Возвращаемые значения:


GLUT_ACTIVE_SHIFT – если нажат Shift (или Caps Lock включен).

GLUT_ACTIVE_CTRL - если нажат Control.

GLUT_ACTIVE_ALT - если нажат Alt.


А теперь мышка:


glutMouseFunc(void (*func)(int button, int state, int x, int y)) – устанавливает обработчик клика мышки. Параметр – функция, где button:


GLUT_LEFT_BUTTON – левая кнопка.

GLUT_MIDDLE_BUTTON – средняя кнопка.

GLUT_RIGHT_BUTTON – правая кнопка.


State может быть:


GLUT_UP – кнопка отпущена.

GLUT_DOWN – кнопка нажата.


X, Y – координаты мыши в окне.


Еще есть функция


glutMotionFunc(void (*func)(int x, int y)) – устанавливает обработчик, вызываемый при нажатой левой кнопке и перемещении мышки. Параметр – функция, где x,y – оконные координаты мыши.


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


#include

#include

#include

#include "glut.h"


int rx=0;

int ry=0;


Переменные для поворота по x и по y.


int oX=-1;

int oY=-1;


Предыдущие значения этих переменных. Сейчас они равны недопустимому значению, т.к. пользователь еще ни разу ни кликнул по окну программы.


void mouserot(int x, int y)

{

if (oX>(-1))

{

ry += x-oX;

rx += y-oY;

}

oX=x;

oY=y;

}


Собственно функция разворота. Если мы можем получить предыдущее положение, то к rY прибавляем отличие старой и новой координаты X а к rX – отличие страрого Y от нового. Заключительный шаг: сохраняем текущую позицию в качетве старой. Заметьте, координаты несколько запутаны, но это сделано специально для большей удобности вращения.


void resize(int width,int height)

{

glViewport(0,0,width,height);

glMatrixMode( GL_PROJECTION );

glLoadIdentity();

gluPerspective(45.0, width/height, 0.1,100);

gluLookAt( 0,0,5, 0,0,0, 0,1,0 );

glMatrixMode( GL_MODELVIEW );

}


void display(void)

{

glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

glClearColor(0.0,0.0,0.0,1.0);


glColor3f(0.0,0.0,0.8);


glPushMatrix();

glRotatef(rx,1,0,0);

glRotatef(ry,0,1,0);


glutSolidTeapot(0.7);

glPopMatrix();

glutSwapBuffers();

}


void main()

{

glutInitWindowPosition(50, 50);

glutInitWindowSize(640, 480);

glutInitDisplayMode( GLUT_RGB | GLUT_DEPTH | GLUT_DOUBLE );

glutCreateWindow("OpenGL1 Window");

glutIdleFunc(display);

glutDisplayFunc(display);

glutReshapeFunc(resize);


glutMotionFunc(mouserot);


glEnable(GL_DEPTH_TEST);

glEnable(GL_COLOR_MATERIAL);

glEnable(GL_LIGHTING);

glEnable(GL_LIGHT0);


glutMainLoop();

}


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


Упражнения



  1. Напишите программу, которая при событиях, поступающих с клавиатуры выдавала бы подробную информацию на стандартный вывод (второе окно вашей программы, запись информации туда осуществляется с помощью printf)

  2. Сделайте то же самое и для мыши, но при этом записывайте все данные в log-файл.

  3. Напишите программу “просмотр объектов”, в которой можно будет выбирать, перемещать, изменять, вращать объект с помощью мыши и клавиатуры.



4.


Как же рисуются все остальные объекты? Или с помощью OpenGL нам только чайники да тетраэдры придется рисовать? Конечно нет, в OpenGL существует множество комманд, облегчающих рендеринг таких объектов, как диск, сфера, цилиндр, конус, тор (кольцо). И конечно же, линия, точка, треугольник, полигон.


glutSolidSphere(GLdouble radius, GLint slices, GLint stacks) – рисует сферу. Первый параметр – радиус сферы. Второй – количество делений вокруг оси Z (как линии широты на глобусе). Третий – кол-во делений вдоль оси Z (как линии долготы).


glutSolidCone(GLdouble base, GLdouble height, GLint slices, GLint stacks) – рисует конус. Первый параметр – радиус базы, второй – высота. Третий и четвертый – см. glutSolidSphere.


glutSolidTorus(GLdouble innerRadius, GLdouble outerRadius, GLint sides, GLint rings) – рисует тор. Первый параметр – общий радиус, второй – толщина. Третий – кол-во сторон на каждом круговом делении. Четвертый – круговые деления.


Естественно, чем больше параметры slices, stacks, sides, rings, тем более качетвенная получается фигура. Уменьшая эти параметры у сферы можно добиться очень интересных эффектов, например сфера со slices=40, stacks=4 выглядит достаточно странно (для сферы, а не для клетки). (Она изображена на рисунке).


Да ну, зачем нам эти дурацкие торы да конусы. Нам бы попроще чего, треугольник, например. В OpenGL для всех примитивов, строящихся по точкам существуют три унифицированные комманды: glBegin, glVertex и glEnd. Перед тем, как нарисовать объект, мы вызываем glBegin c параметром, обозначающим тип объекта, он может быть:


GL_POINTS – точки

GL_LINES – линии (каждые две вершины образуют линию)

GL_LINE_STRIP – вершины последовательно соединянются линиями

GL_LINE_LOOP – то же, что GL_LINE_STRIP, но последняя вершина соединяется с первой.

GL_TRIANGLES – треугольники (каждые три вершины образуют треугольник)

GL_TRIANGLE_STRIP – связанная группа треугольников. Три первых вершины образуют первый треугольник, с первой по четвертую – второй, и т.д.

GL_TRIANGLE_FAN – первая вершина – общая для всех треугольников.

GL_QUADS – четырехугольники (каждые четыре вершины образуют четырехугольник)

GL_QUAD_STRIP – связанные четырехугольники. Первый четырехугольник – вершины 1,2,3,4, второй – 2,3,4,5. И т.д.

GL_POLYGON – выпуклый многоугольник (каждые скобки glBegin/glEnd – один многоугольник).


Потом идут команды glVertex, которые указывают координаты вершин. Эта команда имеет множество суффиксов, также как glColor,например, для обозначения точки в трехмерном пространстве, заданной массивом из float, комманда будет такой: glVertex3fv.


После того, как мы нарисовали объект, надо завершить рисование командой glEnd.


Также есть комманды:


glLineWidth(float size) – задает ширину линий, равную size.

glPointSize(float size) – задает величину точек, равную size.

glPolygonmode(int origin, int mode) – задает режим рисования полигонов (треугольников, четырехугольников, etc.) Первый параметр может принимать значения:


GL_FRONT – для лицевой стороны многоугольников

GL_BACK – для задней стороны

GL_FRONT_AND_BACK – для обоих сторон


Второй параметр может быть:


GL_POINT – точками

GL_LINE – линиями

GL_FILL – сплошной заливкой.


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


glBegin(GL_TRIANGLES);

glColor3f(0.0,0.0,0.8);

glVertex3f(-2,-2,0);

glColor3f(0.8,0.0,0.0);

glVertex3f(2,2,0);

glColor3f(0.0,0.8,0.0);

glVertex3f(-2,2,0);

glEnd();


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


Отключать/включать этот эффект можно вызвав команду glShadeModel c аргументом GL_FLAT/GL_SMOOTH.


С помощью glEnable/glDisable(GL_POLYGON_SMOOTH (или GL_LINE_SMOOTH)) можно включить/выключить сглаживание полигонов или линий.


Внимание: многие команды недоступны внутри скобок glBegin/glEnd.


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


void calcnorm(float v1[3], float v2[3], float v3[3], float &nx, float &ny, float &nz)

{

float wrki, vx1, vy1, vz1, vx2, vy2, vz2;


vx1 = v1[0] - v2[0];

vy1 = v1[1] - v2[1];

vz1 = v1[2] - v2[2];


vx2 = v2[0] - v3[0];

vy2 = v2[1] - v3[1];

vz2 = v2[2] - v3[2];


// Вектор-перпендикуляр к центру.

nx = vy1 * vz2 - vz1 * vy2;

ny = vz1 * vx2 - vx1 * vz2;

nz = vx1 * vy2 - vy1 * vx2;


// Чтоб он был длины 1...

wrki = sqrt(nx*nx + ny*ny + nz*nz);

if (wrki==0) {wrki=1;};


nx/=wrki;

ny/=wrki;

nz/=wrki;

}


Первые три паравметра – массивы из координат точек треугольника. (Первый элемент – x, второй – y, третий – z). Следующие три – ссылки на переменные с нормалями. Примечание: создатели GLUT уже позаботились о нас, и все объекты из этой библиотеки рисуются с просчитанными нормалями.


А если мы нарисовали много объектов сразу и хотим их повторять? Нам что ли каждый раз повторять одни и те же команды? Или, например, загрузили мы объект из файла, нарисовали его в процессе загрузки, а потом приходится хранить огромные объемы памяти, для того, что бы можно было нарисовать этот объект в любой момент. К счастью, в OpenGL присутствует механизм “дисплейных списков” (display lists), с помощью которых можно запомнить почти неограниченное количество команд и потом воспроизвети их одним махом.


Для создания дисплейных списков служит команда glNewList. Первый ее аргумент обозначает имя списка (не совсем имя. Все “имена” в OpenGL являются числами типа GLuint), второй всегда должен быть GL_COMPILE. Далее следуют команды, которые, собственно и образуют список. (Ими могут быть любые команды OpenGL). Заканчивает список команда glEndList(). Чтобы вызвать список (выполнить все его команды) служит команда glCallList(GLuint name), которая вызывает список с именем name. Если непонятно, какое имя придать списку (ведь имя уже может использоваться), можно вызвать команду GLuint glGenLists(GLint range), которая возвращает подходящее незанятое имя для range списков. (О вызове нескольких списков сразу мы поговорим позже). Например:


#include

#include

#include

#include "glut.h"


int rx=0;

int ry=0;


int oX=-1;

int oY=-1;


int triangle;

int cube;

int scene;

// ...


Имена различных списков: для треугольника, для куба и всей сцены.


void prepare()

{

triangle=glGenLists(1);

cube =glGenLists(1);

scene =glGenLists(1);


Даем каждому из них уникальное имя.


glNewList(triangle, GL_COMPILE);

glPushMatrix();

glBegin(GL_TRIANGLES);

glColor3f(1,0,0);

glVertex3f(2,2,0.1);

glColor3f(0,1,0);

glVertex3f(-2,-2,-0.1);

glVertex3f(2,-2,-0.1);

glEnd();

glPopMatrix();

glEndList();


glNewList(cube, GL_COMPILE);

glPushMatrix();

glRotatef(30,1,1,0);

glColor3f(0.3,0.3,0.7);

glutSolidCube(1.3);

glPopMatrix();

glEndList();

glNewList(scene, GL_COMPILE);

glCallList(triangle);

glCallList(cube);

glEndList();

}


Как видите, списки могут быть и вложенные.


void resize(int width,int height)

{

glViewport(0,0,width,height);

glMatrixMode( GL_PROJECTION );

glLoadIdentity();

gluPerspective(45.0, height/height, 0.1,100);

gluLookAt( 0,0,5, 0,0,0, 0,1,0 );

glMatrixMode( GL_MODELVIEW );

}


void display(void)

{

glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

glClearColor(0.0,0.0,0.0,1.0);


glCallList(scene);


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


glutSwapBuffers();

}


void main()

{

glutInitWindowPosition(50, 50);

glutInitWindowSize(640, 480);

glutInitDisplayMode( GLUT_RGB | GLUT_DEPTH | GLUT_DOUBLE );

glutCreateWindow("OpenGL1 Window");

glutIdleFunc(display);

glutDisplayFunc(display);

glutReshapeFunc(resize);


glEnable(GL_DEPTH_TEST);

glEnable(GL_COLOR_MATERIAL);

glEnable(GL_LIGHTING);

glEnable(GL_LIGHT0);


prepare();


Подготавливаем списки.


glutMainLoop();

}


Я рекомендую всегда использовать в качетве имен списков не числа, а переменные.


Упражнения.


  1. Нарисуйте что-нибудь интересное, например снеговика или автомобиль. Теперь ваши фантазии не ограничены. Пусть ваши мечты о трехмерной графике станут реальностью!



5.


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


glLightf[v](GLenum light, GLenum name, GLfloat *params) – устанавливает какой-то параметр c именем name для источника света light (их может быть максимум 8, различные источники света задаются константами GL_LIGHT0 ... GL_LIGHT7). Параметров существует масса:


GL_POSITION – расположение источника света (Четвертый элемент здесь показывает, учитывать ли удаление объекта от источника света. Он должен быть равен 1. Первые три элемента – x,y,z.).

GL_SPOT_DIRECTION – направление источника света.

GL_AMBIENT – фоновая составляющая (цвет). Говоря короче, цвет, который поглощается объектами

GL_DIFFUSE – диффузная составляющая (цвет). Цвет, отражаемый объектами.

GL_SPECULAR – отраженный цвет. Т.е тот цвет, которым изображаются блики на поверхности объектов.


На рисунке изображен чайник, параметры источника света у которого таковы (Чайник все еше синий! Поэтому цвета сильно изменяются):


float lPos[4] = {1.0,1.0,2.0,1.0};

float lDir[3] = {0.0,0.0,1.0};


float lAmb[4] = {0.5,0.0,0.0, 1.0}; // Темно-красный цвет

float lDiff[4] = {0.0,0.5,0.0, 1.0}; // Темно-зеленый цвет

float lSpec[4] = {1.0,1.0,1.0, 1.0}; // белый цвет


glLightfv(GL_LIGHT0, GL_POSITION, lPos);

glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, lDir);

glLightfv(GL_LIGHT0, GL_AMBIENT, lAmb);

glLightfv(GL_LIGHT0, GL_DIFFUSE, lDiff);

glLightfv(GL_LIGHT0, GL_SPECULAR, lSpec);


Рисунок наглядно демонстрирует смысл параметров GL_AMBIENT, GL_DIFFUSE и GL_SPECULAR. Впрочем, просто добавив этот код в функцию main, красивых бликов мы не увидим, т.к. не заданы свойства отражающей способности материала (и материал сейчас ничего не отражает). Для материала доступны те же самые параметры, что и для источника света, только изменяются они с помощью команды glMaterialf(GLenum face, GLenum pname, GLfloat param). Face может быть GL_FRONT(для лицевых сторон), GL_BACK (для задних) или GL_FRONT_AND_BACK (для всех сразу). Pname – те же самые GL_AMBIENT, GL_DIFFUSE, GL_SPECULAR плюс GL_EMISSION и GL_SHININESS. GL_EMISSION – цвет, который излучает материал. Например, ели мы хотим сделать солнце, вокруг которого вертятся какие-нибудь планеты, код рисования солнца будет такой:


Приложение 1. Установка всех необходимых компонентов и библиотек.



  1. Скачайте с моего сайта OpenGL3D.fatal.ru из раздела “разное” библиотеку GLUT.

  2. Скопируйте файл glut32.dll в каталог с вашей программой.

  3. Скопируйте файлы glut32.dll и glut.dll в каталог \windows\system\

  4. Скопируйте glut.lib и glut32.lib в каталог_где_установлен_MSVS7\ Vc7\lib\

  5. Скопируйте glut.h в каталог с вашей программой и в каталог_где_установлен_MSVS7\Vc7\include\



OpenGL.dll (Версию OpenGL от SGI), необходимую для компиляции некоторых примеров, можно скачать с моего сайта (раздел “разное”).




Схожі:




База даних захищена авторським правом ©lib.exdat.com
При копіюванні матеріалу обов'язкове зазначення активного посилання відкритою для індексації.
звернутися до адміністрації