OpenGL в C++ Builder. Основы

  1. Введение
  2. Инициализация
  3. Примитивы

Введение

Все, наверное, понимают, что для работы с 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_POINTS
GL_LINES Каждая пара вершин задаёт отрезок GL_LINES
GL_LINE_STRIP Вершины задают ломаную линию GL_LINE_STRIP
GL_LINE_LOOP Аналогично предыдущему, но последняя точка соединяется с первой GL_LINE_LOOP
GL_TRIANGLES Каждые 3 вершины задают треугольник GL_TRIANGLES
GL_TRIANGLE_STRIP Рисует треугольники с общей стороной GL_TRIANGLE_STRIP
GL_TRIANGLE_FAN Рисует треугольники с общей стороной GL_TRIANGLE_FAN
GL_QUADS Каждые 4 вершины задают четырёхугольник GL_QUADS
GL_QUAD_STRIP Рисует четырёхугольники с общей стороной GL_QUAD_STRIP
GL_POLYGON Вершины задают выпуклый многоугольник GL_POLYGON

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

На этом пока всё.

Добавить комментарий