OpenGL и Delphi на практике

Издательский Дом “КОМИЗДАТ”

Любая теория хороша, если она может быть реализована на Delphi :-). Поэтому предлагаю не откладывая в долгий ящик написать первую программу на OpenGL – а потом, окрылившись успехом, вернуться к теории и как следует проштудировать все книги и сайты по сабжу, чтобы уж стать настоящими монстрами трехмерного моделирования.

Для начала придется проделать подготовительную работу:

Настроить формат пикселей с учетом отображаемой информации;

Создать контекст OpenGL и подготовить сам движок OpenGL к работе.

Формат пикселей удобно вынести в отдельную процедуру, которую мы оформим следующим образом:

Procedure SetDCPixelFormat (dc: HDC);

Var pfd: TPixelFormatDescriptor;

NPixelFormat: Integer;

Begin

FillChar (pfd, SizeOf (pfd),0);

With pfd do

Begin

NSize:= sizeof (pfd);

NVersion:= 1;

DwFlags:= PFD_DRAW_TO_WINDOW or

PFD_SUPPORT_OPENGL or

PFD_DOUBLEBUFFER;

IPixelType:= PFD_TYPE_RGBA;

CColorBits:= 16;

CDepthBits:= 64;

ILayerType:= PFD_MAIN_PLANE;

End;

NPixelFormat:=ChoosePixelFormat (DC,@pfd);

SetPixelFormat (DC, nPixelFormat,@pfd);

End;

Здесь при заполнении структуры TPixelFormatDescriptor мы задаем параметры будущего графического отображения, в том числе количество цветовых бит, а также тип пикселей (iPixelType). Мы также задаем флаги, которые, как видно из названия, указывают, что наша программа будет поддерживать OpenGL, а также что мы будем рисовать в окне и использовать двойную буферизацию (параметр, необходимый для воспроизведения движущихся объектов).

Далее посредством вызова ChoosePixelFormat система выбирает подходящий формат пикселя – и мы присваиваем его (через SetPixelFormat) нашему окну.

Теперь нужно инициализировать контекст самого OpenGL посредством функций, содержащихся в модуле Windows, и произвести дополнительную настройку движка:

Procedure TForm1.FormCreate (Sender: TObject);

Begin

H:=Handle;

DC:=GetDC (H);

SetDCPixelFormat (DC);

RC:=wglCreateContext (DC);

WglMakeCurrent (DC, RC);

GlClearColor (0.6,0.6,0.6,1.0);

GlMatrixMode (GL_PROJECTION);

GlLoadIdentity;

GlFrustum (-1,1,-1,1,2,20);

GlMatrixMode (GL_MODELVIEW);

GlLoadIdentity;

GlTranslatef (0.0,-1.0,-6.0);

BeginPaint;

End;

Как видим, сначала мы задали для нашей графики необходимый формат пикселей. Теперь при помощи функции wglCreateContext создаем OpenGL-контекст, а впоследствии делаем его текущим контекстом. Далее, используя уже универсальные функции**, произведем настройку “мира”, который будем создавать. Для этого через glClearColor очистим контекст и заполним ее 60-процентным черным цветом. Далее выберем матрицу проекций, которая определяет, как будут проецироваться трехмерные объекты на плоскость экрана (в оконные координаты) и через glLoadIdentity установим единичную матрицу и зададим границы плана в “мировых координатах” при помощи вызова glFrustum. После чего загрузим модельно видовую матрицу и произведем ее смещение (glTranslatef).

Что будем рисовать

Конечно, можно было нарисовать простую пирамиду или же куб. Но мы сделаем большее – нарисуем “признание в любви”** (рис. 1). Специально для этого методом “научного перебора” была разработана модель, описывающая соответствующую кривую:

Остается только перевести ее с языка математики на нормальный человеческий.

Прорисовка сцены

Подготовку сцены начнем с подключения разных дополнительных функций, без которых дальнейшая работа невозможна. Эти функции прописаны в методе BeginPaint, а также в методе FormResize (чтобы при изменении размера формы соответственно менялся размер объекта). Для этого используем функцию glEnable с соответствующими параметрами.

Далее в FormPaint используем подготовленные заранее методы DrawFace и DrawElement (см. листинг ниже) для отрисовки упомянутого объекта. А для придания ему еще большей “жары” используем возможности OpenGL по освещению сцены.

Итог

С точки зрения сложности освоения OpenGL сопоставим с другими подобными библиотеками. Так что с одной стороны нет разницы, в чем разбираться и что изучать. Но с точки зрения разумного подхода любой проект трехмерной графики должен как минимум поддерживать OpenGL в качестве одной из опций. Ведь серьезные вещи считаются и визуализируются, как правило, под Unix/IRIX/Linux/FreeBSD, и в то же время было бы неправильно игнорировать пользователей Windows. Так что OpenGL как раз и является тем универсальным языком и общим знаменателем, позволяющим вашим приложениям свободно мигрировать с одной платформы на другую.

Листинг программы

Листинг

========

Unit MainForm;

Interface

Uses

Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,

Dialogs, OpenGL, StdCtrls, ExtCtrls;

Type

TForm1 = class(TForm)

Timer1: TTimer;

Label1: TLabel;

Label2: TLabel;

Label3: TLabel;

Label4: TLabel;

Procedure FormCreate(Sender: TObject);

Procedure FormDestroy(Sender: TObject);

Procedure Timer1Timer(Sender: TObject);

Procedure FormPaint(Sender: TObject);

Procedure FormResize(Sender: TObject);

Private

RC:HGLRC;

DC:HDC;

H:THandle;

Procedure BeginPaint;

{ Private declarations }

Public

{ Public declarations }

End;

Var

Form1: TForm1;

Const mat1_dif:Array[0..2] of Single = (0.8,0.8,0.0);

Const mat1_amb:Array[0..2] of Single = (0.2,0.2,0.2);

Const mat1_spec:Array[0..2] of Single = (0.6,0.6,0.6);

Const mat1_shininess = 0.5*128;

Procedure DrawElement(A, b,R0,r1:Single);

Procedure DrawFace(A, R:Single;Normal:Boolean);

Implementation

Procedure SetDCPixelFormat(dc:HDC);

Var pfd:TPixelFormatDescriptor;

NPixelFormat:Integer;

Begin

FillChar(pfd, SizeOf(pfd),0);

With pfd do

Begin

NSize := sizeof(pfd);

NVersion := 1;

DwFlags := PFD_DRAW_TO_WINDOW or

PFD_SUPPORT_OPENGL or

PFD_DOUBLEBUFFER;

IPixelType:= PFD_TYPE_RGBA;

CColorBits:= 16;

CDepthBits:= 64;

ILayerType:= PFD_MAIN_PLANE;

End;

NPixelFormat:=ChoosePixelFormat(DC,@pfd);

SetPixelFormat(DC, nPixelFormat,@pfd);

End;

Procedure TForm1.BeginPaint;

Begin

GlEnable(GL_LIGHTING);

GlEnable(GL_LIGHT0);

GlEnable(GL_DEPTH_TEST);

GlEnable(GL_NORMALIZE);

GlEnable(GL_COLOR_MATERIAL);

Timer1.enabled:=true;

End;

{$R *.dfm}

Procedure TForm1.FormCreate(Sender: TObject);

Begin

H:=Handle;

DC:=GetDC(H);

SetDCPixelFormat(DC);

RC:=wglCreateContext(DC);

WglMakeCurrent(DC, RC);

GlClearColor(0.6,0.6,0.6,1.0);

GlMatrixMode(GL_PROJECTION);

GlLoadIdentity;

GlFrustum(-1,1,-1,1,2,20);

GlMatrixMode(GL_MODELVIEW);

GlLoadIdentity;

GlTranslatef(0.0,-1.0,-6.0);

BeginPaint;

End;

Procedure TForm1.FormDestroy(Sender: TObject);

Begin

WglMakeCurrent(0,0);

WglDeleteContext(RC);

ReleaseDC(H, DC);

DeleteDC(DC);

End;

Procedure TForm1.Timer1Timer(Sender: TObject);

Begin

GlRotatef(4.0,0.0,1.0,0.0);

SwapBuffers(DC);

InvalidateRect(H, nil, False);

End;

Procedure DrawElement(a, b,r0,r1:Single);

Var x1b, y1b:Single;

X1e, y1e:Single;

X0b, y0b:Single;

X0e, y0e:Single;

T0,t1:Single;

Dt:single;

Begin

T0:=-3;t1:=3;

Dt:=0.06;

While t0<=t1 do

Begin

X0b:=a*sin(t0)*sin(t0)*sin(t0)*sin(t0)*cos(t0);

Y0b:=a*abs(sin(t0)*cos(t0));

X0e:=a*sin(t0+dt)*sin(t0+dt)*sin(t0+dt)*sin(t0+dt)*cos(t0+dt);

Y0e:=a*abs(sin(t0+dt)*cos(t0+dt));

X1b:=b*sin(t0)*sin(t0)*sin(t0)*sin(t0)*cos(t0);

Y1b:=b*abs(sin(t0)*cos(t0));

X1e:=b*sin(t0+dt)*sin(t0+dt)*sin(t0+dt)*sin(t0+dt)*cos(t0+dt);

Y1e:=b*abs(sin(t0+dt)*cos(t0+dt));

GlBegin(GL_TRIANGLE_STRIP);

GlNormal((x0b+x1e)/2,(y0b+y1e)/2,(r1+r0)/2);

GlVertex3f(x0b, y0b, r0);

GlVertex3f(x0e, y0e, r0);

GlVertex3f(x1e, y1e, r1);

GlVertex3f(x1b, y1b, r1);

GlEnd;

T0:=t0+dt;

End;

End;

Procedure DrawFace(A, R:Single;Normal:Boolean);

Var x, y:single; t0,t1,dt:Single;

Begin

T0:=-3;t1:=3;

Dt:=0.06;

GlBegin(GL_POLYGON);

While t0<=t1 do

Begin

X:=a*sin(t0)*sin(t0)*sin(t0)*sin(t0)*cos(t0);

Y:=a*abs(sin(t0)*cos(t0));

GlVertex3F(x, y,r);

T0:=t0+dt;

End;

T0:=0;

X:=a*sin(t0)*sin(t0)*sin(t0)*sin(t0)*cos(t0);

Y:=a*abs(sin(t0)*cos(t0));

If Normal then glNormal3f(x, y,-r) else glNormal3f(x, y,r);

GlEnd;

End;

Procedure TForm1.FormPaint(Sender: TObject);

Var m, n:single;dm:Single;a:Single;df:Single;

Begin

A:=25;

Df:=10;

GlClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);

GlColor(1.0,0.0,0.0,0.0);

GlMaterialfv(GL_FRONT, GL_AMBIENT,@mat1_amb);

GlMaterialfv(GL_FRONT, GL_DIFFUSE,@mat1_dif);

GlMaterialfv(GL_FRONT, GL_SPECULAR,@mat1_spec);

GlMaterialf(GL_FRONT, GL_SHININESS, mat1_shininess);

M:=-1;n:=1;dm:=0.5;

While m<=n do

Begin

DrawElement(Sqrt(a-m*m),Sqrt(a-(m+dm)*(m+dm)),m/df,(m+dm)/df);

M:=m+dm;

End;

DrawFace(Sqrt(a-(m)*(m)),(m)/df, True);

M:=-1;

DrawFace(Sqrt(a-(m)*(m)),(m)/df, True);

End;

Procedure TForm1.FormResize(Sender: TObject);

Const lm:Array[0..3] of Single = (0.5,0.5,0.5,1.0);

Const

Light_ambient:array[0..3] of glfloat = (0.0,0.0,0.0,1.0);

Light_diffuse:array[0..3] of glfloat = (1.0,1.0,1.0,1.0);

Light_specular:array[0..3] of glfloat = (2.0,2.0,2.0,1.0);

Light_position:array[0..3] of glfloat = (2.0,1.0,3.0,1.0);

Light_emission:array[0..3] of glfloat = (1.0,1.0,1.0,1.0);

Light_spotdirection:array[0..3] of glfloat = (1.0,1.0,1.0,1.0);

Begin

WglMakeCurrent(0,0);

WglDeleteContext(RC);

ReleaseDC(H, DC);

DC:=GetDC(H);

SetDCPixelFormat(DC);

RC:=wglCreateContext(DC);

WglMakeCurrent(DC, RC);

GlClearColor(0.6,0.6,0.6,0.0);

GlMatrixMode(GL_PROJECTION);

GlLoadIdentity;

GlFrustum(-1,1,-1,1,2,20);

GlMatrixMode(GL_MODELVIEW);

GlLoadIdentity;

GlTranslatef(0.0,-1.0,-6.0);

GlLightModel(GL_LIGHT_MODEL_LOCAL_VIEWER, Ord(True));

GlLightModelfv(GL_LIGHT_MODEL_AMBIENT,@lm);

GlLightfv(GL_LIGHT0,GL_AMBIENT,@light_ambient);

GlLightfv(GL_LIGHT0,GL_DIFFUSE,@light_diffuse);

GlLightfv(GL_LIGHT0,GL_SPECULAR,@light_specular);

GlLightfv(GL_LIGHT0,GL_POSITION,@light_position);

GlLightf(GL_LIGHT0,GL_SPOT_EXPONENT,8);

GlLightf(GL_LIGHT0,GL_SPOT_CUTOFF,170);

GlLightfv(GL_LIGHT0,GL_SPOT_DIRECTION,@light_spotdirection);

GlEnable(GL_LIGHTING);

GlEnable(GL_LIGHT0);

GlEnable(GL_DEPTH_TEST);

GlEnable(GL_NORMALIZE);

GlEnable(GL_COLOR_MATERIAL);

End;

End.


OpenGL и Delphi на практике