Хождение по мукам
или
Юзаем OpenGL
Ну вот, доигрались. Теперь и в Дельфи стали писать игрушки, и причем достаточно неплохие. Похоже орда паскалистов скоро начнет вытеснять(если уже не вытеснила) программеров на С++. Ну это все мелочи жизни, и хороший программер одинаково легко может писать и на си и на дельфи, а статья эта о работе с OpenGL в Дельфи.
Считается, что программировать 3D сложно и требует специальных математических знаний. Это не совсем так. Да, действительно нужно знать хотя бы азы высшей математики, но освоить эти азы можно за 15 минут. А вот без чего здесь не обойтись, так это без объемного мышления. Если ты всегда мог легко представить как будет выглядеть пирамида стоящая на сфере, то все в порядке. Я знаю нескольких людей, которые совсем не могут представить объекты в пространстве. С этим ничего не поделать, абстрактное мышление есть не у всех.
В этой статье я не буду вдаваться в теорию. Кого интересует теория, читайте книжки. Я просто подробно прокоментирую один из примеров OpenGL приложения на Дельфи.
И так, приступим.
Что нам понадобится:
Комп с 3D-акселератором – 1 штука.(лучше NV не ниже TNT2)
Delphi любой версии, установленный – штука.
Внимания – четочку.
Терпение – много J.
Запускаем Дельфи.(в моем случае это шестая версия). Дельфи сразу создаст новый проект.
Теперь по порядку:
Сначала нам нужно подключить в юзесах(uses) OpenGL.(просто допиши там opengl).
Теперь добавим необходимые переменные. В public класса Tform1 добавим:
DC:HDC; // это ссылка на контекст устройства
HRC:HGLRC; // ссылка на контекст воспроизведения
В var добавим
переменную k типа integer
Теперь добавим процедуру SetDCPixelFormat , она нам понадобится для задания формата пикселя. Вот как она выглядит:
Procedure
SetDCPixelFormat(hdc:HDC);
var
pfd:TPixelFormatDescriptor;
nPixelFormat: Integer;
begin
FillChar(pfd,SizeOf(pfd),0);
pfd.dwFlags := PFD_DRAW_TO_WINDOW or PFD_SUPPORT_OPENGL or PFD_DOUBLEBUFFER;
nPixelFormat:=ChoosePixelFormat(hdc,@pfd);
SetPixelFormat(hdc,nPixelFormat,@pfd);
end;
Эту процедуру я целиком взял из книги М.Краснова «OpenGL. Графика в проектах Delphi»
Единственное на что здесь стоит обратить внимание, так это на задание флагов:
PFD_DRAW_TO_WINDOW // комментарии излишни
PFD_SUPPORT_OPENGL // включаем хардварную поддержку OpenGL
и самое главное
PFD_DOUBLEBUFFER // двойной буфер нужен нам для правильной смены кадров.(отрисовка кадра в памяти и быстрая замена им текущего)
Теперь перейдем к заданию стандартных процедур работы оконного приложения.
В событии OnCreate создадим процедуру по умолчанию, и приведем ее к следующему виду:
procedure
TForm1.FormCreate(Sender: TObject);
begin
DC:=GetDC(handle); //
получаем ссылку на контекст устройства
SetDCPixelFormat(DC); //
устанавливаем формат пикселя
hrc:=
wglCreateContext(DC); // контекст воспроизведения
wglMakeCurrent(DC,HRC); // устанавливаем текущий контекст
glEnable(GL_DEPTH_TEST); //включаем
тест глубины (для правельного отобродения объектов по оси Z)
glEnable (GL_COLOR_MATERIAL); //
тут все понятно
glEnable(GL_LIGHTING); //
разрешаем освещение
glEnable(GL_LIGHT0); //
Включаем источник света за именем GL_LIGHT0(все настройки по умолчанию)
glEnable(GL_NORMALIZE); // нужно для нормализации векторов нормалей(я
их криво задал, OGL сам пересчитает)
glNewList(1,GL_COMPILE); // очень
важный момент. Подготовка экранных списков. Чуть позже об этом подробнее
glBegin(GL_TRIANGLES); // открываем блок рисования (в донном случае
треугольников)
glNormal3f( 0, -1,
0); //
объявляем нормаль. Чуть ниже подробнее
glVertex3f(-0.5,-0.5,-0.5); //
1я вершина
glVertex3f( 0,-0.5,
0.5); // 2я вершина
glVertex3f( 0.5,-0.5,-0.5); //
3я вершина
glNormal3f(-0.5, 0.5, 0.5); //Следующая
нормаль для следующего треугольника
glVertex3f(-0.5,-0.5,-0.5);
glVertex3f( 0,-0.5, 0.5);
glVertex3f( 0, 0.5,
0);
glNormal3f( 0.5, 0.5, 0.5);
glVertex3f( 0,-0.5, 0.5);
glVertex3f( 0.5,-0.5,-0.5);
glVertex3f( 0, 0.5,
0);
glNormal3f( 0, 0.5,-0.5);
glVertex3f(-0.5,-0.5,-0.5);
glVertex3f( 0, 0.5,
0);
glVertex3f( 0.5,-0.5,-0.5);
glEnd;
glEndList;
end;
Ну вот теперь по подробнее. Нормаль это вектор, как правило, перпендикулярный к поверхности. Но в 3D нормаль может быть и не перпендикулярной. Это связано с тем, что нормали используются для расчета освещенности полигона. Посмотрим на иллюстрации:


На рисунке конус, у которого нормали строго перпендикулярны к полигонам. Такие нормали назаваются фасетными(faset).
А теперь посмотрите на тот же конус, но с не перпендикулярными нормалями:


Так модель выглядит сглаженной. И такой тип нормалей называется гладкими.(SmoothNormals) Как видно из иллюстраций, нормали присваиваются вершинам, а не полигону. И запись вида:
glNormal3f(
0.5, 0.5, 0.5);
glVertex3f( 0,-0.5, 0.5);
glVertex3f(
0.5,-0.5,-0.5);
glVertex3f( 0, 0.5,
0);
говорит о присвоении всем вершинам одного треугольника одной и той же нормали.
Как рассчитывать нормали я говорить не буду, это можно прочесть в любой книге по 3D.
Теперь о экранных списках(или дисплейных). Экранные списки представляют собой последовательность команд, запоминаемых для последующего вызова. Они подобны подпрограммам и являются удобным средством для кодирования больших систем со сложной иерархией. Использование экранных списков приводит к более быстрой работе приложения и повышает читабельность кода. Формат работы экранных списков таков:
glNewList(1,GL_COMPILE);
первый
параметр это идентификатор экранного списка(сколько максимально может быть
экранных списков я не знаю, это в доке не написано)
второй
параметр это режим создания экранного списка. В данном случае это режим
компиляции.
////////////
……
все
что угодно
///////////
glEndList; //
конец списка.
Теперь в событии OnResize созданим еще одну процедурку.
procedure
TForm1.FormResize(Sender: TObject);
begin
glViewport(0, 0, ClientWidth, ClientHeight ); // зона видимости(в данном случае все окно)
glMatrixMode(GL_PROJECTION); //
Делаем текущей матрицу проекции
glLoadIdentity; //
загружаем единичную матрицу
gluPerspective(30,ClientWidth/ClientHeight,1,35); // задаем перспективу
glMatrixMode(GL_MODELVIEW); // матрица вида
glLoadIdentity;
end;
Если что то не понятно, то читайте книгу все того же М.Красного.
И последняя, самая главная процедура, подвяжем ее на системное событие WM_PAINT.
Для этого в public класса Tform1 создадим процедуру следующего вида:
procedure
Render(var Msg: TWMPaint); message WM_PAINT;
Несложно догодаться что эта процедура отвечает за отрисовку сцены:
procedure
TForm1.Render(var Msg: TWMPaint);
begin
k:=k+1; //
для анимации
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT); //
Очищаем буфер глубины и содержимое фрейма
glPushMatrix(); //
сохраняем состояние матрицы
glTranslatef (0, 0, -4); //
немного сдвигаем по оси Z(глубина)
glDisable(GL_LIGHTING); //
Отключам освещение, это нужно для правильного отоброжения осей.
glBegin(GL_LINES); //
это блок отрисовки осей
glColor3f(1,0,0); //
задание цвета RGB
glVertex3f(-1,-1,0);
glVertex3f(-0.5,-1,0);
glColor3f(0,1,0);
glVertex3f(-1,-1,0);
glVertex3f(-1,-0.5,0);
glColor3f(0,0,1);
glVertex3f(-1,-1,0);
glVertex3f(-1,-1,-1);
glEnd;
glEnable(GL_LIGHTING); //
Включаем освещение
glRotate (k,0,1,0); //
поворот на угол k вокруг вектора 0,1,0
glRotate (k,1,0,0);
glRotate (k,0,0,1);
glColor3f(0,1,0);
glCallList(1); //
вызываем заранее созданный экранный список
glRotate (180,1,0,0);
glTranslatef (-0.5,0.5,0.25);
glColor3f(1,0,0);
glCallList(1);
glTranslatef (1,0,0);
glColor3f(0,0,1);
glCallList(1);
glPopMatrix(); //
восстанавливаем состояние матрицы
SwapBuffers(DC); //
отображаем на экран наше художество
end;
Теперь, после компиляции должно получится что то похожее на это:

Если что то не получилось, то качай исходник.
Вывод из запоя за 1 день Звоните: вывод из запоя.