Все, наверное, понимают, что для работы с 2D и 3D графикой очень приятно пользоваться готовыми библиотеками. Они позволяют программисту избавиться от кучи адовых вычислений, типа обсчёта поворотов, освещений, пересечений, наложений и прочей мути. Все вышеперечисленные «умения» в таком случае реализуются в библиотеке, а нам остаётся только с помощью специальных стандартных функций сообщать ей, что, где и как нужно нарисовать или повернуть, откуда смотрит камера, где расположены источники света и тому подобные вещи. Короче, красота и минимум напряжения, — мы только описываем модель, а библиотека сама всё обсчитывает и рисует.
Одна из самых популярных подобных библиотек — это OpenGL (Open Graphics Library — открытая графическая библиотека). О ней сегодня и пойдёт речь, а точнее о том, как пользоваться этой библиотекой в С++ Builder.
Итак, открываем C++ Builder (у меня 6.0), создаём новый проект и начинаем.
Первое, что нам понадобится — это добавить в заголовочный файл проекта (unit1.h) файлы, содержащие прототипы основных функций OpenGL (чтобы мы ими вообще могли пользоваться):
#include <GL/gl.h> // описание непосредственно библиотеки OpenGL #include <GL/glu.h> // описание дополнительной библиотеки со всякими полезностями |
Далее нам понадобится функция установки формата пикселей. Мы могли бы и не оформлять этот код отдельной функцией, но обычно делают именно так.
Описываем её в заголовочном файле (unit1.h):
bool bSetupPixelFormat(HDC hdc); |
И добавляем код (в файле unit1.cpp):
bool bSetupPixelFormat(HDC hdc) { PIXELFORMATDESCRIPTOR pfd, *ppfd; int pixelformat; ppfd = &pfd; ppfd->nSize = sizeof(PIXELFORMATDESCRIPTOR); ppfd->nVersion = 1; ppfd->dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; ppfd->dwLayerMask = PFD_MAIN_PLANE; ppfd->iPixelType = PFD_TYPE_RGBA; // формат указания цвета // (значение PFD_TYPE_RGBA означает, что цвет указывается // четырьмя параметрами RGBA - красный, зленный, синий и альфа) ppfd->cColorBits = 16; // глубина цвета ppfd->cDepthBits = 16; // размер буфера глубины (Z-Buffer) ppfd->cAccumBits = 0; ppfd->cStencilBits = 0; // размер буфера трафарета (0 - не используется) if ((pixelformat = ChoosePixelFormat(hdc, ppfd)) == 0) // функция ChoosePixelFormat подбирает формат пикселей, максимально // удовлетворяющий нашим требованиям, и возвращает его дескриптор { MessageBox(NULL, "ChoosePixelFormat failed", "Error", MB_OK); return FALSE; } if (SetPixelFormat(hdc, pixelformat, ppfd) == FALSE) // функция SetPixelFormat устанавливает выбранный // формат пикселей в контексте устройства (dc) { MessageBox(NULL, "SetPixelFormat failed", "Error", MB_OK); return FALSE; } return TRUE; } |
Переходим к настройке. Рисовать будем прямо на форме, во весь её размер. А при изменении размера рисунок будем масштабировать.
В заголовочном файле, в объявлении класса формы, в секции private объявляем две новые переменные:
HGLRC ghRC; // указатель на контекст воспроизведения (Rendering Context) HDC ghDC; // дескриптор устройства (указатель на окно, в котором будем рисовать) |
В обработчике события OnCreate (функция FormCreate) прописываем следующее:
ghDC = GetDC(Handle); if (!bSetupPixelFormat(ghDC)) Close(); // устанавливаем формат пикселей ghRC = wglCreateContext(ghDC); // эта и следующая функция нужны для wglMakeCurrent(ghDC, ghRC); // создания контекста воспроизведения (Rendering Context) glClearColor(0.85, 0.85, 0.85, 0.0); // устанавливаем цвет, которым будет заполняться экран при // очищении (4 параметра соответствуют выбранной нами при // установке пикселей палитре RGBA) FormResize(Sender); // здесь делаем тоже самое, что и при изменении // размеров формы, поэтому так // в OpenGL всё включается/выключается (разрешается/запрещается) процедурами glEnable и glDisable glEnable(GL_COLOR_MATERIAL); // разрешаем давать нашим объектам какой-то цвет glEnable(GL_DEPTH_TEST); // разрешаем тест глубины, чтобы изображение было объёмным glEnable(GL_LIGHTING); // разрешаем освещение glEnable(GL_LIGHT0); // включаем "лампочку №0" float p[4]={3,3,3,1}, d[3]={-1,-1,-3}; glLightfv(GL_LIGHT0,GL_POSITION,p); // устанавливаем свойства "лампочки №0": позицию glLightfv(GL_LIGHT0,GL_SPOT_DIRECTION,d); // и направление света |
А в обработчике события OnResize (функция FormResize) пишем такой код:
// устанавливаем область вывода - область, в которую OpenGL будет // выводить изображение (в нашем случае - вся форма) glViewport( 0, 0, Width, Height ); // устанавливаем режим матрицы видового преобразования // если вы меняете тип проецирования, положение или направление // камеры, то параметр должен быть GL_PROJECTION // после того, как вы завершили изменения, вызовите эту // процедуру с параметром GL_MODELVIEW glMatrixMode( GL_PROJECTION ); // заменяем текущую матрицу видового преобразования glLoadIdentity(); // устанавливаем режим ортогонального (прямоугольного) проецирования; // это значит, что изображение будет рисоваться как в изометрии // 6 параметров типа GLdouble (или просто double): left, right, bottom, // top, near, far определяют координаты соответственно левой, правой, // нижней, верхней, ближней и дальней плоскостей отсечения, // т.е. всё, что окажется за этими пределами, рисоваться не будет (на // самом деле эта процедура просто устанавливает масштабы координатных осей) // для того чтобы установить перспективное проецирование, используются // процедуры glFrustum и gluPerspective glOrtho(-5,5, -5,5, 2,12); // устанавливаем параметры камеры: первая тройка - её координаты, // вторая - вектор направления, третья - направление оси Y gluLookAt(5,5,5, 0,0,0, 0,0,1); glMatrixMode( GL_MODELVIEW ); |
Пропишем так же, что делать при закрытии формы, — в обработчик события OnClose (функция FormClose) вставим такой код:
// удаляем контекст воспроизведения и освобождаем окно, в котором рисовали if(ghRC) { wglMakeCurrent(ghDC,0); wglDeleteContext(ghRC); } if(ghDC) ReleaseDC(Handle, ghDC); |
Осталось добавить только процедуру рисования. Сначала опишем её в заголовочном файле (unit1.h), в объявлении класса формы, в секции private:
void Draw(); |
А затем напишем её код (в файле unit1.cpp):
void TForm1::Draw() { glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); // очищаем буфер кадра // рисуем, крутим, вертим SwapBuffers(ghDC); // выводим всё на экран } |
Вот и всё. Теперь нужно только научиться рисовать. Рисовать в OpenGL можно с помощью примитивов. Базовых примитивов 5 штук: точка, линия, треугольник, четырёхугольник и многоугольник. Всё остальное рисуется с помощью этих примитивов. Но спокойно, никто не будет вас заставлять рисовать, например, сферу из треугольников, — многие сложные объекты из примитивов умеет рисовать дополнительная библиотека glu.h (ага, это как раз то, зачем мы её подключали).
Однако, вернёмся к примитивам. Как я уже сказал, — всего их 5. При этом их существует 10 разных типов. Всё дело в том, что некоторые примитивы могут быть нескольких типов, отличающихся способом построения. Например, бывают просто треугольники, а бывают треугольники с общими рёбрами (позже вы увидите, в чём отличие) и тому подобное.
Все примитивы рисуются по одному шаблону:
glBegin(Type); // указываем тип примитива, который будем рисовать glVertex3d(x,y,z); // первая вершина ... // промежуточные вершины glVertex3d(x,y,z); // последняя вершина glEnd; // закончили рисовать примитив |
Внутри этой же конструкции можно указать цвет точек и линий, командой:
glColor3d(r,g,b) |
В таблице ниже указаны все типы базовых примитивов и показано, как именно эти типы примитивы работают:
GL_POINTS | Каждая вершина задает отдельную точку | ![]() |
GL_LINES | Каждая пара вершин задаёт отрезок | ![]() |
GL_LINE_STRIP | Вершины задают ломаную линию | ![]() |
GL_LINE_LOOP | Аналогично предыдущему, но последняя точка соединяется с первой | ![]() |
GL_TRIANGLES | Каждые 3 вершины задают треугольник | ![]() |
GL_TRIANGLE_STRIP | Рисует треугольники с общей стороной | ![]() |
GL_TRIANGLE_FAN | Рисует треугольники с общей стороной | ![]() |
GL_QUADS | Каждые 4 вершины задают четырёхугольник | ![]() |
GL_QUAD_STRIP | Рисует четырёхугольники с общей стороной | ![]() |
GL_POLYGON | Вершины задают выпуклый многоугольник | ![]() |
Если вершины фигуры обходятся на экране против часовой стрелки, то считается, что эта фигура обращена к нам лицевой стороной, а если по часовой, то она обращена к нам обратной стороной. Впрочем, специальными командами эту настройку можно изменить.
На этом пока всё.