OpenGL Tutorial/ko

From Lazarus wiki
Revision as of 05:16, 29 November 2008 by Heennavi (talk | contribs) (Using display lists)

Template:OpenGL 튜토리얼

OpenGL은 이기종간 이식가능하고 상호 작용이 가능한 2D 및 3D 그래픽 어플리케이션을 개발할 수 있는 훌륭한 환경이다. 1992년 처음 소개된 이래 OpenGL은 산업계에서 가장 폭넓게 이용되며 2D와 3D 그래픽 어플리케이션 인터페이스(API)를 제공하며 다양한 컴퓨터 플랫폼에서 수 많은 어플리케이션이 있다. OpenGL은 렌더링, 텍스쳐 매핑, 특수효과 및 다른 강력한 시각화 함수등의 다양한 셋을 삽입하여 혁신적이고 빠른 어플리케이션의 개발을 가능하게 하였다. 개발자는 모든 인기있는 데스크톱과 워크스테이션 플랫폼에서 다양한 어플리케이션 개발의 성공을 보장받기 위해 OpenGL의 능력을 저울질하게 된다.

OpenGL에 관한 더 많은 정보는 이곳에서 찾을 수 있다.

GLUT

GLUT(glutony의 glu와 발음이 비슷)는 OpenGL Utility Toolkit으로,윈도우 시스템과 독립적인 툴킷으로 OpenGL 프로그램 작서에 사용된다. 이것은 OpenGL을 위한 간단한 윈도우 어플리케이션 프로그래밍 인터페이스(API)를 구현한다. GLUT는 이식 가능한 API를 제공하여 모든 PC와 워크스테이션 플랫폼에 동작하는 단일 OpenGL 프로그램을 작성할 수 있게 한다.

GLUT에 관한 더 자세한 정보는 여기에 있다.

많은 OS는 GLUT를 설치하고 있으나 당신이 구글에서 쉽게 찾을 수 없다면 윈도우즈 바이너리를 www.xmission.com에서 다운로드 할 수 있다.

LCL

라자루스 컴포넌트 라이브러리는 OpenGL과 함께 하용할 수 있다. 라자루스는 OpenGl 컨텍스트를 가진 LCL 컨트롤을 가진 TOpenGLControl 을 포함하고 있다. 라자루스 패키지 LazOpenGLContext는 lazarus/components/opengl/lazopenglcontext.lpk에서 찾을 수 있다. 예제는 lazarus/examples/openglcontrol/openglcontrol_demo.lpi에서 찾을 수 있다.

LCL / GLUT

GLUT와 LCL은 어제 이용하나?

  • 내 맘대로 무언가를 그리려고 한다면 GLUT가 더 낫다.
  • 보통의 어플리케이션에서는 LCL이 더 낫다. 예를 들어 3D 에디터는 몇개의 OpenGL 윈도우를 필요로 하며 나머지는 보통의 버튼, 콤보박스, 윈도우 모달 윈도우 등을 사용하는 보통의 어플리케이션이다.

OpenGL 파트는 매우 유사하다. GLUT는 윈도우 환경에서 dll을 필요로 하며 LCL은 박스의 바깥에서 실행되지만 LCL 실행 파일은 더 커진다.

첫번째 GLUT 프로그램 작성

GLUT를 이용하기 위해서는 반드시 먼저 초기화가 필요하다.이것은 glutInit 함수를 사용하여 수행한다. 이 함수는 명령어 라인을 파싱하여 메인 윈도우를 위해 파라메터를 설정하지만, C/C++ 스타일로 입력되어야 한다. ParamCount와 ParamStr을 C/C++와 유사한 명령어 파라메터로 변환하는 각자의 함수를 작성할 수가 있다.

<delphi>

procedure glutInitPascal(ParseCmdLine: Boolean); 
var
  Cmd: array of PChar;
  CmdCount, I: Integer;
begin
  if ParseCmdLine then
    CmdCount := ParamCount + 1
  else
    CmdCount := 1;
  SetLength(Cmd, CmdCount);
  for I := 0 to CmdCount - 1 do
    Cmd[I] := PChar(ParamStr(I));
  glutInit(@CmdCount, @Cmd);
end;

</delphi>

본질적으로, 프로그래머는 배열을 생성하고 ParamStr에 있는 문자열로 배열을 채워야 한다. 이 함수는 어떤 것이 gluInit에 전달되는지를 컨트롤하는 파라메터(명령어 라인 전체 혹은 실행파일 이름 만)를 취한다.

할일: glutInit(@argc, @argv);가 충분하다면.

glutInit에 더 많은 정보: http://www.opengl.org/resources/libraries/glut/spec3/node10.html

다음으로는 메인 윈도우를 생성할 필요가 있다. glutInitDisplayMode를 사용하여 메인윈도우의 디스플레이 모드를 설정한다. 이것은 플래그의 조합으로 이루어진 단 한개의 파라메터만을 취한다. 보통 GLUT_DOUBLE 나 GLUT_RGB, GLUT_DEPTH가 필요한 전부이다.

glutInitDisplayMode에 관한 더 많은 정보: http://www.opengl.org/resources/libraries/glut/spec3/node12.html

윈도우의 위치와 크기는 glutInitWindowPositionglutInitWindowSize를 사용하여 조절할 수가 있다. 이것들은 2개의 파라메터를 취한다. 전자는 Y와 Y 좌표를, 후자는 폭(width)과 높이(height)이다. glutGet을 사용하여 스크린의 크기와 윈도우의 중심을 알아낼 수 있다.

lutInitWindowPosition, glutInitWindowSize 및 glutGet에 관한 더 자세한 것은: http://www.opengl.org/resources/libraries/glut/spec3/node11.html http://www.opengl.org/documentation/specs/glut/spec3/node70.html

마지막으로 윈도우는 glutCreateWindow함수를 사용하여 생성하면 된다. 이것은 윈도우를 생성하고 파라메터를 통해 윈도우의 캡션을 설정한다. 그 결과 윈도우의 핸들을 반환한다. 이 핸들은 이것을 필요로 하는 다른 함수에서 사용할 수 있다.

glutCreateWindow에 관한 더 자세한 정보는: http://www.opengl.org/resources/libraries/glut/spec3/node16.html

작성한 프로그램이 메인 루프에 들어가기 전에 몇몇 콜백을 설정해야만 한다. 윈도우를 그리고, 크기를 변경하고 키보드 입력을 얻는데 콜백이 필요하다. 이 콜백들은 glutDisplayFunc, glutReshapeFuncglutKeyboardFunc을 이용하여 설정한다.

콜백을 설정하는 더 자세한 정보는: http://www.opengl.org/resources/libraries/glut/spec3/node45.html#SECTION00080000000000000000

당신의 그리기 함수는 다름과 같은 모습을 띨것이다:

procedure DrawGLScene; cdecl;
begin
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
  glutSwapBuffers;
end;

이것은 윈도우를 배경색상으로 지우고 zbuffer를 리셋한다(zbuffer에 대해 걱정하지 마라... 뒤에 더 자세히 다룰 것이다).

당신의 리사이즈 함수는 다음과 같을 것이다:

procedure ReSizeGLScene(Width, Height: Integer); cdecl;
begin
  if Height = 0 then
    Height := 1;

  glViewport(0, 0, Width, Height);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity;
  gluPerspective(45, Width / Height, 0.1, 1000);

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity;
end;

이 코드에서, 윈도우의 어느 곳에 그리며, 원하는 값을 행렬(matrix함수는 뒤에서 설명한다)에 설정할지를 OpenGL에게 알려주어야 한다.

키보드 입력은 다음 콜백을 검증한다.

procedure GLKeyboard(Key: Byte; X, Y: Longint); cdecl;
begin
  if Key = 27 then
    Halt(0);
end;

이 함수는 프로그램에서 ESC 키가 눌리면 종료하도록 명령한다. GLUT는 이벤트 구동방식이므로 프로그램을 종료하는 유일한 방법은 당신의 콜백함수 중의 하나의 내부에서 Halt를 콜하는 것이다. 만약 다른 방법으로 윈도우를 종료한다면, 윈도우는 사라지지만 프로그램은 메인 루틴에서 무한정 루프를 계속 돌 것이다.

메인 루프를 시작하기 위해 glutMainLoop를 콜해야 한다. 이것은 무한 루프에 들어가 프로그램의 모든 콜백함수들을 호출하게 된다

프로그램의 메인 파트는 다음과 같은 모습을 띨 것이다:

const 
  AppWidth = 640; 
  AppHeight = 480; 

procedure InitializeGL; 
begin 
  glClearColor(0.18, 0.20, 0.66, 0); 
end; 

var 
  ScreenWidth, ScreenHeight: Integer; 
begin 
  glutInitPascal(True); 
  glutInitDisplayMode(GLUT_DOUBLE or GLUT_RGB or GLUT_DEPTH); 
  glutInitWindowSize(AppWidth, AppHeight); 
  ScreenWidth := glutGet(GLUT_SCREEN_WIDTH); 
  ScreenHeight := glutGet(GLUT_SCREEN_HEIGHT); 
  glutInitWindowPosition((ScreenWidth - AppWidth) div 2,
    (ScreenHeight - AppHeight) div 2); 
  glutCreateWindow('OpenGL Tutorial 1'); 

  InitializeGL; 

  glutDisplayFunc(@DrawGLScene); 
  glutReshapeFunc(@ReSizeGLScene); 
  glutKeyboardFunc(@GLKeyboard); 

  glutMainLoop; 
end.

다음 튜토리얼은 간단한 도형을 그리는 몇몇 코드를 더할 것이다.

소스코드나 리눅스/윈도우즈 실행 파일은 다음에서 다운로드 할 수가 있다 Lazarus CCR SourceForge.

간단한 도형 그리기

Note:이어지는 부분은 거의 OpenGL에 관한 것이므로 GLUT와 LCL에서 실행하여야 한다. 'glu' 접두어를 통해 GLUT에 관련된 함수라는 것을 알 수가 있다.

이 시점에서 단지 몇 라인의 코드만을 추가 할 것이며 몇몇 OpenGL 함수에 관한 설명에 촛점을 맞출 것이다.

다음 코드에 대해 설명할 것이다.

  .
  .
  .
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity;
  gluPerspective(45, Width / Height, 0.1, 1000);

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity;
end;

glMatrixMode 함수를 사용하여 변경하고자 하는 매트릭스(행렬)를 선택 할 수가 있다. OpenGL은 3개의 매트릭스를 이용하여 작업을 수행한다:

GL_MODELVIEW: 이것은 버텍스를 모델 스페이스로 이동할 때 사용한다.

GL_PROJECTION: 이것은 최종 픽셀위치를 위해 3d 좌표를 2d 좌표로 변환하는데 사용한다.

GL_TEXTURE: 이것은 텍스쳐 좌표를 변경할 떄 사용한다.

변화시키고자 하는 매트릭스를 선택했으면, 매트릭스 값을 변화시키는 함수를 호출하여야 한다. glLoadIdentity는 매트릭스를 리셋하므로 버텍스 위치에 영향을 미치지 못한다. 거의 대부분의 매트릭스 함수가 현재 매트릭스를 생성된 매트릭스와 곱하므로, 때때로 이 함수로 매트릭스를 지워주어야 한다.

perspective 매트릭스를 설정하기 위해 gluPerspective 함수를 사용할 수 있다. 네개의 파라메터는 시야(field of view), 가로세로비율(aspect ratio), 근접(near) 및 far 평면(plane)을 나타낸다. 이렇게 간단하다.

이제 모델 매트릭스를 변경할 것이며 identity만 설정하면 된다.

좋다... 그리고 이제 첫번째 도형을 그리는 코드이다.:

procedure DrawGLScene; cdecl;
begin
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);

  glLoadIdentity;
  glTranslatef(0, 0, -5);

  glBegin(GL_TRIANGLES);
    glColor3f(1, 0, 0);
    glVertex3f(-1, -1, 0);

    glColor3f(0, 1, 0);
    glVertex3f(1, -1, 0);

    glColor3f(0, 0, 1);
    glVertex3f(0, 1, 0);
  glEnd;

  glutSwapBuffers;
end;

우리는 이미 glClear 함수를 사용하였다. 이것은 버퍼를 리셋하기만 한다. 우리는 다음 두 함수는를 건너뛰고 도형을 그리는 곳으로 향할 것이다.

glBegin는 드로잉 블록의 시작을 표시한다. 이 함수 뒤에 버텍스를 입력을 시작할 수 있다. 파라메터는 드로잉 할 때 버텍스가 어떻게 이용되는 지를 설명해 준다:

GL_POINTS: 각 버텍스를 단일 포인트로 취급한다. 버텍스 n은 포인트 n으로 정의한다. N개의 포인트가 그려진다.

GL_LINES: 각 버텍스 쌍을 독립적인 라인 세그컨트로 취급한다. 버텍스 2n-1과 2n은 라인 n을 정의한다. n/2개의 라인이 그려진다.

GL_LINE_STRIP: 첫번째 버텍스에서 마지막까지 라인세그먼트의 연결된 그룹을 그린다. n-1개의 라인이 그려진다.

GL_LINE_LOOP: 첫번째 버텍스에서 마지막까지 라인세그먼트의 연결된 그룹을 그린다. 그리고 첫번째 버텍스로 그린다. n과 n+1째 버텍스는 라인 n을 정의한다. 그러나 마지막 라인은 n과 1로 정의된다. n개의 라인이 그려진다.

GL_TRIANGLES: 각 세개의 버텍스를 독립된 삼각형으로 취급한다. 버텍스 3n-2, 3n-1 및 3n은 삼각형 n을 정의한다. n/3개의 삼각형이 그려진다.

GL_TRIANGLE_STRIP: 삼각형이 연결된 그룹을 그린다. 한개의 삼각형은 각 버텍스가 앞 두 버텍스와 함께 정의된다. 홀수 n에서 버텍스 n, n+1, n+2는 삼각형 n을 정의한다. 짝수 n에서, 버텍스 n+1, n 및 n+2는 삼각형 n을 정의한다. n-2개의 삼각형이 그려진다.

GL_TRIANGLE_FAN: 삼각형이 연결된 그룹을 그린다. 한개의 삼각형은 각 버텍스가 앞 두 버텍스와 함께 정의된다. 버텍스 1, n+1 및 n+2는 삼각형 n으로 정의한다. n-2개의 삼각형이 그려진다.

GL_QUADS: 각 네개의 버텍스를 독립된 사각형으로 취급한다. 버텍스 4n-3, 4n-2, 4n-1 및 4n은 사각형을 정의한다. n/4개의 사각형이 그려진다.

GL_QUAD_STRIP: 사각형이 연결된 그룹을 그린다. 한개의 사각형은 각 쌍의 버텍스가 앞의 한쌍과 함께 정의된다. 버텍스 2n-1, 2n, 2n+2 및 2n+1 는 사각형 n을 정의한다. n/2-1개의 사각형이 그려진다. 스트립 데이타 중의 어떤 버텍스가 사각형을 그리는데 사용되는지의 순서가 독립적인 데이타 함께사용 될 때는 다르다.

GL_POLYGON: 한개의 볼록 다각형을 그린다. 버텍스 1에서 n은 이 다각형을 정의한다.

SimpleShapePic1.jpg

한개의 삼각형을 그릴 수 있으며, 이를 위해 TRIANGLES 플래그가 트릭으로 사용된다. glVertex3f 함수는 그리고자 하는 버텍스의 위치를 정의한다. 더 많은 glVertex* 함수가 있다. 유일한 차이점은 필요로 하는 파라메터의 수와 타입이다.예를 들면... glVertex2i 는 두개의 파라메터 정수형 (x 및y)를 취한다. glVertex3f 는 거의 항상 당신이 필요로 하는 것일 것이다.

glVertex 전에 색상, material, 텍스쳐...를 설정할 수 있다. 간단하게 하기 위해 이 튜토리얼에서는 각 버텍스에 색상을 부여할 것이다. 색상은 glColor3f함수를 사용하여 설정한다. glColor 는 glVertex 같이 다른 셋의 파라메터를 취할 수 있다.

코드를 통해 보았듯이 모든 버텍스에서 Z는 0으로 설정되어 있었다. near plane을 0.1로 설정하였으므로 삼각형은 보이지 않을 것이다. 그것은 시작 부분에서 건너뛴 두 함수가 점프한 곳이 그곳이기 때문이다. glLoadIdentity 함수가 매트릭스를 리셋한다는 것을 이미 알고 있다. glTranslatef는 삼각형을 X, Y 및 Z값 만큼 이동시킨다. Z를 -5로 설정하였기 때문에(마이너스 Z는 카메라로 부터 멀리 떨어져 있는 것이다) 모든 버텍스는 우리가 볼 수 있는 뷰에서 5유닛 만큼 후퇴해 있다.

마지막에서 glEnd함수를 호출하여 드로잉을 종료한다. 원한다면 이제 새로운 glBegin 함수와 함께 또 다른 블록을 시작할 수가 있다.

소스코드, 리눅스 실행파일, 또는 윈도우즈 실행파일은 다음에서 다운로드 받을 수 있다: Lazarus CCR SourceForge.

디스플레이 리스트 사용하기

가끔 한 장면(scene)에서 몇몇 오브젝트를 여러 번 그려야 할 때가 있을 것이다. OpenGL은 더 빠르게 그릴 수 있는 display lists를 빌드할 수 있다. 디스플레이 리스트를 생성하는 것은 매우 쉽다.... 이전의 튜토리얼 처럼 버텍스들을 그리기만 하고 glNewListglEndList 호출로 묶어주기만 하면 된다.

const
  LIST_OBJECT = 1;

procedure CreateList;
begin
  glNewList(LIST_OBJECT, GL_COMPILE);
    glBegin(GL_TRIANGLE_FAN);
      glColor3f(1, 0, 0);
      glVertex3f(0, 0.5, 0);

      glColor3f(1, 1, 0);
      glVertex3f(-0.5, -0.5, 0.5);

      glColor3f(1, 1, 1);
      glVertex3f(0.5, -0.5, 0.5);

      glColor3f(0, 1, 1);
      glVertex3f(0.5, -0.5, -0.5);

      glColor3f(0, 0, 1);
      glVertex3f(-0.5, -0.5, -0.5);

      glColor3f(0, 1, 0);
      glVertex3f(-0.5, -0.5, 0.5);
    glEnd;

    glBegin(GL_QUADS);
      glColor3f(1, 1, 0);
      glVertex3f(-0.5, -0.5, 0.5);

      glColor3f(1, 1, 1);
      glVertex3f(0.5, -0.5, 0.5);

      glColor3f(0, 1, 1);
      glVertex3f(0.5, -0.5, -0.5);

      glColor3f(0, 0, 1);
      glVertex3f(-0.5, -0.5, -0.5);

      glColor3f(0, 1, 0);
      glVertex3f(-0.5, -0.5, 0.5);
    glEnd;
  glEndList;
end;

glNewList 는 새로운 리스트를 생성하고 모든 그리기 함수는 glEndList를 호출하기 전까지는 기록이 된다. glNewList 함수에 대한 첫번째 파라메터는 리스트 ID이다. 각 리스트는 각각의 ID에 의해 정의된다. 만약 리스트가 기존의 ID로 생성되면 이전의 기록은 지워질 것이다. 만약 두번째 파라메터가 GL_COMPILE이면 모든 드로잉 함수는 바로 기록이 될 것이지만, GL_COMPILE_AND_EXECUTE라면 자동적으로 기록되고 실행될 것이다.

glIsList 함수는 디스플레이 함수 사용에 도움을 준다. 이것은 어떤 리스트 ID가 이미 데이터로 사용되고 있는 지를 알려준다. 다른 유용한 함수는 glGenLists이다. 이것은 다수의 텅빈 디스플레이 리스트르 생성할 것이다. 원하는 디스플레이 리스트 수를 넘기면 첫번째의 ID를 얻을 수 있을것이다. n 리스트를 원하면 r ID를 받을 것이고 생성된 리스트는: r, r+1, r+2,..., r+n-1 이 된다.

생성된 모든 리스트는 제거해야만 한다. 프로그램을 종료하기 전에 실행하여야 한다:

procedure GLKeyboard(Key: Byte; X, Y: Longint); cdecl;
begin
  if Key = 27 then
  begin
    glDeleteLists(LIST_OBJECT, 1);
    Halt(0);
  end;
end;

glDeleteLists는 2개의 파라메터 즉, 삭제하려고하는 디스플레이 리스트의 ID 와 리스트의 수를 필요로 한다. 만약 ID가 r 이고 삭제하려는 리스트의 수가 n이라면 삭제되는 리스트는: r, r+1, r+2,..., r+n-1 이다.

이제 디스플레이 리스트를 생성하고 삭제하는 방법을 알았다... 이것들을 어떻게 그리는 지 알아보자:

procedure DrawGLScene; cdecl;
begin
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);

  glLoadIdentity;
  glTranslatef(-2, 0, -5);
  glRotatef(40, 1, 0, 1);
  glCallList(LIST_OBJECT);

  glLoadIdentity;
  glTranslatef(1, -2, -10);
  glRotatef(62, 0, 1, 0);
  glCallList(LIST_OBJECT);

  glLoadIdentity;
  glTranslatef(-4, 0.5, -15);
  glRotatef(200, 1, 0, 0);
  glCallList(LIST_OBJECT);

  glutSwapBuffers;
end;
DisplayListsPic1.jpg

glCallList를 사용하면 당 한개의 디스플레이 리스트를 그릴 수 있다. 이 튜토리얼에서, 디스플레이 리스트를 그리기 전에, 모델 매트릭스를 변경하고 다른 위치에 오브넥트를 그려야 한다.

가끔은 한번에 여러 개의 리스트를 그리고 싶을 것이다. glCallLists 함수를 사용한다면 가능하다. 이것은 그리고자 하는 리스트의 수, 디스플레이 리스트 ID를 가진 배열의 타입 및 디스플레이 리스트 ID를 가진 배열을 받는다. 리스트의 타입은 다음 중의 하나이다:

GL_BYTE: 리스트는 -128에서 127까지의 부호있는 바이트의 배열로 취급한다.

GL_UNSIGNED_BYTE: 리스트는 0에서 255까지의 부호없는 바이트의 배열로 취급한다.

GL_SHORT: 리스트는 -32768에서 32767까지의 부호있는 두 바이트 정수의 배열로 취급한다.

GL_UNSIGNED_SHORT: 리스트는 0에서 65535까지의 부호없는 두 바이트 정수의 배열로 취급한다.

GL_INT: 리스트는 부호있는 네자리 정수의 배열로 취급한다.

GL_UNSIGNED_INT: 리스트는 부호없는 네자리 정수의 배열로 취급한다.

GL_FLOAT: 리스트는 네자리 부동소수점 배열로 취급한다.

GL_2_BYTES: 리스트는 부호없는 바이트의 배열로 취급한다. 각 바이트 쌍은 단일한 디스플레이 리스트 ID를 지정한다. 각 쌍의 값은 부호없는 첫번째 바이트 값과 부호없는 두번째 바이트 값의 합을 256회 계산한다.

GL_3_BYTES: 리스트는 부호없는 바이트의 배열로 취급한다. 각 바이트 세 개쌍(triplet)는 단일한 디스플레이 리스트 ID를 지정한다. 세 개쌍의 값은 단일한 디스플레이 리스트 ID를 지정한다. 세 개쌍의 값은 65536회 계산한다. 부호없는 첫번째 바이트 값과 부호없는 두번째 바이트 값의 합을 256회 계산하고 세번째 바이트 값을 더한다.

GL_4_BYTES: 리스트는 부호없는 바이트의 배열로 취급한다. 각 바이트 네 개쌍(triplet)는 단일한 디스플레이 리스트 ID를 지정한다. 네 개쌍의 값은 단일한 디스플레이 리스트 ID를 지정한다. 네 개쌍의 값은 16777216회 계산한다. 부호없는 첫번째 바이트 값과 부호없는 두번째 바이트 값의 합을 65535회 계산하고 세번째 바이트 값을 더하고 256회 계산 한 후 네번째 부호없는 바이트를 더한다.

다음 튜토리얼은 작은 행성계를 만드는 방법을 보여 줄 것이다. 매트릭스 및 초당 프레임에 얽매이지 않는 움직이는 장면(scene)을 만드는 방법에 대하여 이야기 할 것이다.

소스, 리눅스 실행파일과 윈도우즈 실행파일은 다음에서 다운로드 할 수 있다 Lazarus CCR SourceForge.

Full screen animation

Entering full screen mode is easy with GLUT. Let's change main part of the program:

const
  FSMode = '800x600:32@75';

begin
  glutInitPascal(False);
  glutInitDisplayMode(GLUT_DOUBLE or GLUT_RGB or GLUT_DEPTH);
  glutGameModeString(FSMode);
  glutEnterGameMode;
  glutSetCursor(GLUT_CURSOR_NONE);

  InitializeGL;

  glutDisplayFunc(@DrawGLScene);
  glutReshapeFunc(@ReSizeGLScene);
  glutKeyboardFunc(@GLKeyboard);
  glutIdleFunc(@DrawGLScene);

  glutMainLoop;
end.

Since we don't want GLUT to parse command line this time we call glutInitPascal with False parameter. As you can see, there is no code for window creation. GLUT have glutEnterGameMode that create full screen window. To specify what kind of full screen mode you want, you call glutGameModeString function which takes string that defines mode you like. Format of that string is:

[width "x" height][":" bpp]["@" hertz]

In FSMode string we declared that full screen mode should be 800x600, with 32bit pallete and 75Hz refresh. It is possible to skip one of the group. If you omit size, GLUT will try to use current one or first smaller that can work. That policy is used and for other parameters.

Usually in full screen mode cursor is not visible. To hide cursor you use glutSetCursor function. It takes only one parameter which describes cursor you would like to see:

GLUT_CURSOR_RIGHT_ARROW
GLUT_CURSOR_LEFT_ARROW
GLUT_CURSOR_INFO
GLUT_CURSOR_DESTROY
GLUT_CURSOR_HELP
GLUT_CURSOR_CYCLE
GLUT_CURSOR_SPRAY
GLUT_CURSOR_WAIT
GLUT_CURSOR_TEXT
GLUT_CURSOR_CROSSHAIR
GLUT_CURSOR_UP_DOWN
GLUT_CURSOR_LEFT_RIGHT
GLUT_CURSOR_TOP_SIDE
GLUT_CURSOR_BOTTOM_SIDE
GLUT_CURSOR_LEFT_SIDE
GLUT_CURSOR_RIGHT_SIDE
GLUT_CURSOR_TOP_LEFT_CORNER
GLUT_CURSOR_TOP_RIGHT_CORNER
GLUT_CURSOR_BOTTOM_RIGHT_CORNER
GLUT_CURSOR_BOTTOM_LEFT_CORNER
GLUT_CURSOR_FULL_CROSSHAIR
GLUT_CURSOR_NONE
GLUT_CURSOR_INHERIT

glutIdleFunc defines callback function that you want to be called every time you program has no messages to process. Since we just want to render new frame if there is nothing to do, just set idle function to DrawGLScene. Some other tutorials show that idle function should send refresh message insted of drawing, but that way I have 50-100 frames less than using method I described.

Now, let's look at the program termination where you need to exit full screen mode:

procedure GLKeyboard(Key: Byte; X, Y: Longint); cdecl;
begin
  if Key = 27 then
  begin
    glutLeaveGameMode;
    Halt(0);
  end;
end;

As you can see, all you need to do is to call glutLeaveGameMode.

Now, we'll introduce some new matrix functions. First, let's change ReSizeGLScene function:

procedure ReSizeGLScene(Width, Height: Integer); cdecl;
begin
  .
  .
  .
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity;
  gluLookAt(0, 20, 25, 0, 0, 0, 0, 1, 0);
end;

gluLookAt create matrix that will define from where are you look to objects. First 3 parameters are X, Y and Z coordinate of position of camera. Next 3 parameters are X, Y and Z coordinate of point where camera look at, and last 3 parameters defines "up" vector (where is "up" for the camera). Usually, up is positive y axis.

OK, let's draw now. Since you set matrix with gluLookAt that should be used with all objects, you can't just use glLoadIdentity to reset matrix for next object... you'll save previous matrix state and restore it after object is drawn:

procedure DrawGLScene; cdecl;
var
  T: Single;
begin
  T := glutGet(GLUT_ELAPSED_TIME) / 1000;
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);

  glPushMatrix;
    glRotatef(5 * T, 0, 1, 0);
    glColor3f(1, 1, 0);
    glutWireSphere(2, 20, 20);
  glPopMatrix;

  glPushMatrix;
    glRotatef(90 * T, 0, 1, 0);
    glTranslatef(5, 0, 0);
    glRotatef(40 * T, 0, 1, 0);
    glColor3f(1, 0, 0);
    glutWireSphere(0.6, 10, 10);
  glPopMatrix;

  glPushMatrix;
    glRotatef(60 * T, 0, 1, 0);
    glTranslatef(-3, 0, 9);
    glRotatef(50 * T, 0, 1, 0);
    glColor3f(0, 1, 0);
    glutWireSphere(1, 16, 16);

    glPushMatrix;
      glRotatef(360 * T, 0, 1, 0);
      glTranslatef(-1.7, 0, 0);
      glRotatef(50 * T, 0, 1, 0);
      glColor3f(0, 0, 1);
      glutWireSphere(0.4, 10, 10);
    glPopMatrix;

  glPopMatrix;

  glutSwapBuffers;
end;
FullScreenAnimationPic1.jpg

glPushMatrix i glPopMatrix are used to save and restore matrix state. As you can see, we save matrix state, then change matrix in order to draw object in right place, and then restore old matrix state.

You may wonder what is T variable for. Well, it is used to determen animation speed. Every change that depends on time is multiplied with T. That way animation speed is constant on every frame rate. glutGet function with GLUT_ELAPSED_TIME parameter returns time in milliseconds from glutInit is called. By dividing that value with 1000, we get time in seconds.

glRotatef function create rotation matrix. First parameter is angle in degrees, and last 3 parameters defines axis around which rotation will be done. Since you multiplied angle with T, object will be rotated by that angle in exactly 1 second.

Download source code, linux executable or windows executable from Lazarus CCR SourceForge.

Light

This tutorial will introduce some light to the scene. You'll make rotating cube and one light which will add some realism to the scene, but first let's make some utility unit.

For now it will have only basic functions to help us getting current and delta (time that elapsed from one render to other render call) times and for calculating frames per second.

unit utils;

{$mode objfpc}{$H+}

interface

uses
  glut;

function GetTotalTime: Single;
function GetDeltaTime: Single;
procedure FrameRendered(Count: Integer = 1);
function GetFPS: Single;

implementation

var
  OldTime: Integer = 0;
  FPSTime: Integer = 0;
  FPSCount: Integer = 0;

function GetTotalTime: Single;
begin
  Result := glutGet(GLUT_ELAPSED_TIME) / 1000;
end;

function GetDeltaTime: Single;
var
  NewTime: Integer;
begin
  NewTime := glutGet(GLUT_ELAPSED_TIME);
  Result := (NewTime - OldTime) / 1000;
  OldTime := NewTime;
end;

procedure FrameRendered(Count: Integer);
begin
  Inc(FPSCount, Count);
end;

function GetFPS: Single;
var
  NewTime: Integer;
begin
  NewTime := glutGet(GLUT_ELAPSED_TIME);

  Result := FPSCount / ((NewTime - FPSTime) / 1000);

  FPSTime := NewTime;
  FPSCount := 0;
end;

end.

As you can see, there is nothing complicated in this unit. Time is simply saved betwen calls and difference is returned. FrameRendered should be called every time you draw scene so function can calculate FPS.

Now, let's have fun with lights.

OpenGL have several types of light... ambient, diffuse, point, spot, specular and emissive light.

Ambient light is something like Sun. When sun rays pass through the window of a room they hit the walls and are reflected and scattered into all different directions which averagely brightens up the whole room. All vertices are lit with ambient light.

Diffuse light can be represented as parallel light rays comming from far away. They will lit only vertices that are oriented towards the light source.

Point light lights all around it. It is like a fire ball, it send light rays all around it and lights vertices that are oriented towards light source and that are close enough.

Spot light is like light from flashlight. It is simply a point light source with a small light cone radius. All vertices that falls inside of cone and are close enough are lit.

Just like Diffuse light, Specular light is a directional type of light. It comes from one particular direction. The difference between the two is that specular light reflects off the surface in a sharp and uniform way. The rendering of specular light relies on the angle between the viewer and the light source. From the viewer’s standpoint specular light creates a highlighted area on the surface of the viewed object known as specular highlight or specular reflection.

Emissive light is a little different than any other previously explained light components. This light comes out of object you draw but don't lit other objects in nearby.

For simplicity we'll use only diffuse light in this tutorial. Later on, some other lights may appear in tutorials :)

Let's see how to enable light in scene:

const
  DiffuseLight: array[0..3] of GLfloat = (0.8, 0.8, 0.8, 1);

  glEnable(GL_LIGHTING);
  glLightfv(GL_LIGHT0, GL_DIFFUSE, DiffuseLight);
  glEnable(GL_LIGHT0);

As you see, we enable lighting in OpenGL so lights affect scene you are rendering. Light parameters are set with glLightfv function. It takes 3 parameters... one for light number you want to change (OpenGL suports up to 8 lights), next tells OpenGL what light parameter to change, and the last one is new parameter for light. You'll set just diffuse color for light in this tutorial. After that, you can enable light and there will be light in the scene... but... that is not all.

More about glLightfv: http://www.opengl.org//documentation/specs/man_pages/hardcopy/GL/html/gl/light.html

If you want to use lights you can't just set color for vertex... you must set material for vertices. Let's setup material for drawing:

glEnable(GL_COLOR_MATERIAL);
glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
LightPic1.jpg

You expected something more complicated, do you? :) Well, this code allows us to use glColor function to set material to vertices. By using glEnable function and GL_COLOR_MATERIAL flag, you can define what material properties will glColor change. glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE) tells OpenGL that glColor changes ambient and diffuse material. We'll discus materials more in later tutorials.

One more thing that is important when using lights... every vertex must have normal associated with it. Normal is used to find the direction of vertex so light can be calculated properly. You'll use GLUT function to draw cube and it provides normals for us, so this time we'll just walk by normals.

After all those setting ups, light will shine up your cube :)

Part of the text is copied from The OpenGL Light Bible

Download source code, linux executable or windows executable from Lazarus CCR SourceForge.

Bitmap fonts

Games and programs usually need to write some text on screen. GLUT provides several functions for drawing chars that are platform independent.

First, we'll show how to use default bitmap fonts. Almost all code additions will be made to utils.pas unit.

Since text will be drawn in 2D, we'll need to know width and height of viewport... so, we'll write two functions for that:

function glGetViewportWidth: Integer;
var
  Rect: array[0..3] of Integer;
begin
  glGetIntegerv(GL_VIEWPORT, @Rect);
  Result := Rect[2] - Rect[0];
end;

function glGetViewportHeight: Integer;
var
  Rect: array[0..3] of Integer;
begin
  glGetIntegerv(GL_VIEWPORT, @Rect);
  Result := Rect[3] - Rect[1];
end;

We just get left/right, top/bottom and calculate width/height by subtracting them.

There must be functions for entering and leaving 2D mode:

procedure glEnter2D;
begin
  glMatrixMode(GL_PROJECTION);
  glPushMatrix;
  glLoadIdentity;
  gluOrtho2D(0, glGetViewportWidth, 0, glGetViewportHeight);

  glMatrixMode(GL_MODELVIEW);
  glPushMatrix;
  glLoadIdentity;

  glDisable(GL_DEPTH_TEST);
end;

procedure glLeave2D;
begin
  glMatrixMode(GL_PROJECTION);
  glPopMatrix;
  glMatrixMode(GL_MODELVIEW);
  glPopMatrix;

  glEnable(GL_DEPTH_TEST);
end;

When entering 2D mode, we save current matrices and set 2D matrix using gluOrtho2D function. This way if we draw some thing on 100, 100 it will be drawn on exactly 100 pixels from left edge of window, and 100 pixels form bottom edge (positive Y is up). Also, we disable ZBuffer. This way text won't alter ZBuffer.

Leaving 2D mode just returns old matrices and enable ZBuffer.

Now, we can create function for text drawing:

procedure glWrite(X, Y: GLfloat; Font: Pointer; Text: String);
var
  I: Integer;
begin
  glRasterPos2f(X, Y);
  for I := 1 to Length(Text) do
    glutBitmapCharacter(Font, Integer(Text[I]));
end;

glutBitmapCharacter can draw only one character of selected font. First parameter is desired font (GLUT_BITMAP_9_BY_15, GLUT_BITMAP_8_BY_13, GLUT_BITMAP_TIMES_ROMAN_10, GLUT_BITMAP_TIMES_ROMAN_24, GLUT_BITMAP_HELVETICA_10, GLUT_BITMAP_HELVETICA_12 or GLUT_BITMAP_HELVETICA_18) and other one is character.

Character will be drawn at current raster position. To set desired raster position we call glRasterPos function. glRasterPos can handle different number and types of parameters just like glVertex function. Coordinate specified is transformed by model and projection matrix to get 2D coordinate where new raster position will be. Since we entered 2D mode, X and Y coordinates are actual 2D coordinates where drawing will occur.

This new functions will make text drawing very easy:

procedure DrawGLScene; cdecl;
begin
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);

  glLoadIdentity;
  glTranslatef(0, 0, -5);
  glRotatef(GetTotalTime * 10, 0, 0.5, 0.5);

  glColor3f(1, 0, 0);
  glutSolidCube(2);

  glEnter2D;

  glColor3f(0.2, 0.8 + 0.2 * Sin(GetTotalTime * 5), 0);
  glWrite(20, glGetViewportHeight - 20, GLUT_BITMAP_8_BY_13,
    Format('OpenGL Tutorial :: Bitmap Fonts :: FPS - %.2f FPS', [FPS]));

  glColor3f(1, 1, 1);
  glWrite(50, glGetViewportHeight - 60, GLUT_BITMAP_9_BY_15, 'GLUT_BITMAP_9_BY_15');
  glWrite(50, glGetViewportHeight - 90, GLUT_BITMAP_8_BY_13, 'GLUT_BITMAP_8_BY_13');
  glWrite(50, glGetViewportHeight - 120, GLUT_BITMAP_TIMES_ROMAN_10, 'GLUT_BITMAP_TIMES_ROMAN_10');
  glWrite(50, glGetViewportHeight - 150, GLUT_BITMAP_TIMES_ROMAN_24, 'GLUT_BITMAP_TIMES_ROMAN_24');
  glWrite(50, glGetViewportHeight - 180, GLUT_BITMAP_HELVETICA_10, 'GLUT_BITMAP_HELVETICA_10');
  glWrite(50, glGetViewportHeight - 210, GLUT_BITMAP_HELVETICA_12, 'GLUT_BITMAP_HELVETICA_12');
  glWrite(50, glGetViewportHeight - 240, GLUT_BITMAP_HELVETICA_18, 'GLUT_BITMAP_HELVETICA_18');

  glColor3f(0.5, 0.5, 1);
  glWrite(
    glGetViewportWidth - glutBitmapLength(GLUT_BITMAP_9_BY_15, LazText) - 5,
    10, GLUT_BITMAP_9_BY_15, LazText);

  glLeave2D;

  glutSwapBuffers;

  FrameRendered;
end;
BitmapFontsPic1.jpg

We draw red cube and rotate it, and some text to show how various bitmap fonts look like. glutBitmapLength function is used to find width of string so it could be aligned to right. Code can easily be altered to center text.

Note: See how cube looks without light.

Download source code, linux executable or windows executable from Lazarus CCR SourceForge.

Textures

It's time to use textures :)

This tutorial will show how to draw textured polygons and how to blend textures using multipass technic. Since OpenGL has no builtin mechanism for loading textures, we'll use external library: Vampyre Imaging Library. We'll use just OpenGL helper functions, but you may find this lib handy for some other things to.

Let's get started... we'll create display list for drawing textured rectangle:

procedure CreateList;
begin
  glNewList(LIST_OBJECT, GL_COMPILE);
    glBegin(GL_QUADS);
      glTexCoord2f(1, 0);
      glVertex3f( 2, 2, 0);
      glTexCoord2f(0, 0);
      glVertex3f(-2, 2, 0);
      glTexCoord2f(0, 1);
      glVertex3f(-2,-2, 0);
      glTexCoord2f(1, 1);
      glVertex3f( 2,-2, 0);
    glEnd;
  glEndList;
end;

Notice glTexCoord functions. They are used to specify which part of texture is assigned to vertex. Coordinates defined in this functions are from 0 to 1 (values greater than 1 are allowed but can generate different results). 0 is first pixel and 1 is last pixel. So, 0.5 will be right in the middle of texture.

Texture loading is extremely easy with Vampyre Imaging Library:

var
  Tex1, Tex2: GLuint;

procedure InitializeGL;
begin
  glClearColor(0, 0, 0, 0);
  Tex1 := LoadGLTextureFromFile('ashwood.bmp');
  Tex2 := LoadGLTextureFromFile('Flare.bmp');
  glEnable(GL_TEXTURE_2D);
end;

LoadGLTextureFromFile loads texture from file and returns it's ID. When texture is loaded it is allready setup for rendering. Last line just enables 2D textures.

To draw textured polygon you have to bind texture and setup texture coordinations (texture coordinations are set in display list in this tutorial):

  ...
  glLoadIdentity;
  glTranslatef(-5, 0, -15);
  glBindTexture(GL_TEXTURE_2D, Tex1);
  glCallList(LIST_OBJECT);
  ...

glBindTexture function is used to select texture. When you draw polygins they will have selected texture on them. It's that easy :)

So, using one texture is easy... but how to blend two textures. Basicly you draw polygon once with one texture, setup blending parameters, and draw polygon once more time with other texture. You can blend houndreds of textures this way. Let's see how code for this looks:

  ...
  glLoadIdentity;
  glTranslatef(5, 0, -15);
  glBindTexture(GL_TEXTURE_2D, Tex1);
  glCallList(LIST_OBJECT);

  glEnable(GL_BLEND);
  glBlendFunc(GL_ZERO, GL_SRC_COLOR);
  glLoadIdentity;
  glTranslatef(5, 0, -15);
  glBindTexture(GL_TEXTURE_2D, Tex2);
  glCallList(LIST_OBJECT);
  glDisable(GL_BLEND);
...

As you can see, polygon is drawn first time like we allready know. Before second drawing we enable blending by calling glEnable(GL_BLEND). Blending means that finall pixel color is calculated like this:

DrawingColor * SRCBLEND + BackgroundColor * DESTBLEND

SRCBLEND and DESTBLEND are defined using glBlendFunc function. In this tutorial we set SRCBLEND to GL_ZERO (zero) and DESTBLENT to GL_SRC_COLOR (DrawingColor) and finall color is then:

DrawingColor * 0 + BackgroundColor * DrawingColor
TexturesPic1.jpg

This means that background will get darker when you draw with dark colors... when you draw with white color, background color will not be changed. The result will look like this

Next time, we'll use extensions to show how to use singlepass multitexturing.

Download source code, linux executable or windows executable from Lazarus CCR SourceForge.

Multitexturing (extensions)

When youknow multipass multi texturing, singlepass is very easy. Texturing is separated in stages. First stage setup and draw first texture, second stage draws another one and so on. All you have to do is to setup texture stages and to render object.

Let's see how code looks like:

procedure InitializeGL;
begin
  Load_GL_ARB_multitexture;
  glClearColor(0, 0, 0, 0);
  Tex1 := LoadGLTextureFromFile('Lazarus.bmp');
  Tex2 := LoadGLTextureFromFile('Mask.bmp');
  glActiveTextureARB(GL_TEXTURE0_ARB);
  glEnable(GL_TEXTURE_2D);
  glBindTexture(GL_TEXTURE_2D, Tex1);
  glActiveTextureARB(GL_TEXTURE1_ARB);
  glEnable(GL_TEXTURE_2D);
  glBindTexture(GL_TEXTURE_2D, Tex2);
end;

First we need load OpenGL extension that will allow us to use multitexture functions. Load_GL_ARB_multitexture will try to load those extensions and will return TRUE if operation was successful.

To select texture stage you want to work on, use glActiveTextureARB function. It takes only one parameter that define which stage you want. After that all texture functions (enabling, disabling, binding, creating...) will affect that stage.

Since we setup every thing in initialization function, all we have to do is to draw object:

procedure DrawGLScene; cdecl;
begin
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);

  glLoadIdentity;
  glTranslatef(0, 0, -5);

  glBegin(GL_QUADS);
    glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 1, 0);
    glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 1, 0);
    glVertex3f(2.516, 2, 0);
    glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 0, 0);
    glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 0, 0);
    glVertex3f(-2.516, 2, 0);
    glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 0, 1);
    glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 0, 1);
    glVertex3f(-2.516,-2, 0);
    glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 1, 1);
    glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 1, 1);
    glVertex3f(2.516,-2, 0);
  glEnd;

  glutSwapBuffers;
end;
MultitexturePic1.jpg

As you can see, difference is only in defining texture coordinations. We now use glMultiTexCoord2fARB function that takes texture stage and texture coordinations. Every thing else is unchanged.

Today almost all graphic cards supports at least 2 texture stages. Using singlepass multitexturing is faster than multipass version since you draw objects only once. If hardware supports singlepass multitexturing (Load_GL_ARB_multitexture returns TRUE) use it.

Download source code, linux executable or windows executable from Lazarus CCR SourceForge.

Render to texture

This one will be short. OpenGL can capture current scene to texture so you can use it for texturing other objects (TV screen, mirror or some thing else). Well just render scene to texture and apply it to rotating plane.

First, we must create empty texture which well use to capture scene:

procedure SetupRenderTexture;
var
  Data: Pointer;
begin
  GetMem(Data, 256*256*3);
  glGenTextures(1, @RenderTexture);
  glBindTexture(GL_TEXTURE_2D, RenderTexture);
  glTexImage2D(GL_TEXTURE_2D, 0, 3, 256, 256, 0, GL_RGB, GL_UNSIGNED_BYTE, Data);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  FreeMem(Data);
end;

Buffer for 256*256 RGB image is created and it is used to setup 2D texture.

Main part is in drawing function:

procedure DrawGLScene; cdecl;
var
  TotalTime: Single;
begin
  glClearColor(0, 0, 0, 0);
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
  glEnable(GL_LIGHTING);
  glDisable(GL_TEXTURE_2D);
  glViewport(0, 0, 256, 256);

  TotalTime := GetTotalTime;

  glLoadIdentity;
  glTranslatef(0, 0, -5);
  glRotatef(50 * TotalTime, 1, 0, 0);
  glRotatef(100 * TotalTime, 0, 1, 0);
  glRotatef(50 * TotalTime, 0, 0, 1);

  glColor3f(1, 1, 1);
  glutSolidCube(2);

  glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, 256, 256, 0);

  glClearColor(0.18, 0.20, 0.66, 0);
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
  glDisable(GL_LIGHTING);
  glEnable(GL_TEXTURE_2D);
  glViewport(0, 0, AppWidth, AppHeight);

  glLoadIdentity;
  glTranslatef(0, 0, -7);
  glRotatef(20 * TotalTime, 1, 0, 0);
  glRotatef(50 * TotalTime, 0, 1, 0);

  glBegin(GL_QUADS);
    glTexCoord2f(1, 0);
    glVertex3f(2, 2, 0);
    glTexCoord2f(0, 0);
    glVertex3f(-2, 2, 0);
    glTexCoord2f(0, 1);
    glVertex3f(-2,-2, 0);
    glTexCoord2f(1, 1);
    glVertex3f(2,-2, 0);
  glEnd;

  glutSwapBuffers;
end;
RenderToTexturePic1.jpg

First, everything is setup for scene that will be captured. Viewport is reduced to 256*256 so it will fit into texture and scene is drawn. glCopyTexImage2D is used to capture scene to currently selected texture.

When we have scene captured to texture, everything can be cleared again, viewport can be returned to original size and final scene is drawn using previous scene as texture.

P.S. Captured texture can be saved using SaveGLTextureToFile function from Vampyre Imaging Library.

Download source code, linux executable or windows executable from Lazarus CCR SourceForge.

Vertex array

OpenGL is capable of rendering primitives using data that is stored in buffers insted of calling glVertex. Buffers can be used to define vertex and texture coordinates, and colors (index and RGBA), normals and edge flags.

In this tutorial well use only vertex and color buffers, and we'll show non-indexed and indexed drawing. Non-indexed mode draws buffers as streams. Indexed mode will draw buffer elements in order that is defined in index buffer. But enough talking... let's start coding.

First, let's define some types and constants:

type
  TVertex3f = record
    X, Y, Z: Single;
  end;

  TColor3f = record
   R, G, B: Single;
  end;

  VertexBuffer: array [0..5] of TVertex3f = (
    (X : 1; Y : 1; Z : 0),
    (X : -1; Y : 1; Z : 0),
    (X : -1; Y : -1; Z : 0),
    (X : 1; Y : 1; Z : 0),
    (X : -1; Y : -1; Z : 0),
    (X : 1; Y : -1; Z : 0)
  );
  ColorBuffer: array [0..5] of TColor3f = (
    (R : 1; G : 0; B : 1),
    (R : 0; G : 0; B : 1),
    (R : 0; G : 1; B : 0),
    (R : 1; G : 0; B : 1),
    (R : 0; G : 1; B : 0),
    (R : 1; G : 1; B : 0)
  );

We have two buffers. One for vertex coordinates and one for vertex colors. This 6 vertices defines 2 triangles that forms rectangle.

Drawing primitives using buffers is easy:

  glEnableClientState(GL_VERTEX_ARRAY);
  glEnableClientState(GL_COLOR_ARRAY);
  glVertexPointer(3, GL_FLOAT, 0, @VertexBuffer[0]);
  glColorPointer(3, GL_FLOAT, 0, @ColorBuffer[0]);

  glDrawArrays(GL_TRIANGLES, 0, Length(VertexBuffer));

  glDisableClientState(GL_VERTEX_ARRAY);
  glDisableClientState(GL_COLOR_ARRAY);

First we enable buffers we want to use using glEnableClientState function. Than we can select buffers we want to use. Every buffer type has own function for selecting (glColorPointer, glEdgeFlagPointer, glIndexPointer, glNormalPointer, glTexCoordPointer, glVertexPointer). First parameter in those functions defines how many numbers every element contains. For example, let's take vertex buffer. If this parameter is 2 than OpenGL expects that every element in buffer contains x and y coordinate. If this parameter is, for example, 4, than every element should contains x, y, z and w coordinate. Next parameter defines what type of data element contains (GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT, GL_INT, GL_UNSIGNED_INT, GL_FLOAT or GL_DOUBLE). Next one defines how many bytes are between each element. This way you can have buffer that contains vertex coordinates and some custom data. For arbitrary data type, this parameter can be calculated like this:

type
  TBufferData = record
    DataBefore: TDataBefore;
    Vertex: TVertex;
    DataAfter: TDataAfter;
  end;

Bytes between elements = SizeOf(TDataBefore) + SizeOf(TDataAfter)

Last parameter if pointer to the begginig of buffer.

When buffers are selected we can draw them using glDrawArrays functions. All enabled buffers are used to draw primitives. What kind of polygons are being generated is defined in first parameter (same as in glBegin function). Next two defines subset of buffer which is used for drawing (start and count).

When buffers are not needed you can disable them.

To demonstrate indexed mode, I made some simple mesh class that can load vertex, color and index data from external files:

type
  TMesh = class
  private
    FVertices: array of TVertex3f;
    FColors: array of TColor3f;
    FIndices: array of Integer;
    procedure FreeBuffers;
  public
    constructor Create;
    destructor Destroy; override;
    procedure LoadMesh(FileName: String);
    procedure DrawMesh;
  end;

FVertices will contain data about vertices, FColors data about color and FIndices data about indices when external file is loaded.

First we'll write some code that deals with creation and destruction of class:

procedure TMesh.FreeBuffers;
begin
  FVertices := nil;
  FColors := nil;
  FIndices := nil;
end;

constructor TMesh.Create;
begin
  FreeBuffers;
end;

destructor TMesh.Destroy;
begin
  FreeBuffers;
  inherited Destroy;
end;

File that will contain mesh data is simple text file. First row will contain number of vertices and indices separated by space character. After that row will come rows for every vertex and color. X, Y, Z, R, G and B all separated by space character. In the end, there will be rows for indices... every index number is written in its own row... so, for one triangle, data file will look like this:

3 3
-1 -1 0 1 1 1
1 -1 0 1 1 1
0 1 0 1 1 1
0
1
2

This means that there is 3 vertices and 3 indices defined in file. First vrtex is at -1, -1, 0 and has color 1, 1, 1 and so on. Indices defines that order in which vertices are drawn (in this case vertices are drawn in the same order as they are defined).

Code for loading this data will loke like this:

procedure TMesh.LoadMesh(FileName: String);
var
  MeshFile: TextFile;
  VertexCount, IndexCount: Integer;
  iV, iI: Integer;
begin
  FreeBuffers;

  AssignFile(MeshFile, FileName);
  Reset(MeshFile);

  ReadLn(MeshFile, VertexCount, IndexCount);

  SetLength(FVertices, VertexCount);
  SetLength(FColors, VertexCount);
  SetLength(FIndices, IndexCount);

  for iV := 0 to VertexCount - 1 do
    ReadLn(MeshFile,
      FVertices[iV].X, FVertices[iV].Y, FVertices[iV].Z,
      FColors[iV].R, FColors[iV].G, FColors[iV].B);

  for iI := 0 to IndexCount - 1 do
    ReadLn(MeshFile, FIndices[iI]);

  CloseFile(MeshFile);
end;

After loading data, we have everything for drawing:

procedure TMesh.DrawMesh;
begin
  glEnableClientState(GL_VERTEX_ARRAY);
  glEnableClientState(GL_COLOR_ARRAY);
  glVertexPointer(3, GL_FLOAT, 0, @FVertices[0]);
  glColorPointer(3, GL_FLOAT, 0, @FColors[0]);

  glDrawElements(GL_TRIANGLES, Length(FIndices), GL_UNSIGNED_INT, @FIndices[0]);

  glDisableClientState(GL_VERTEX_ARRAY);
  glDisableClientState(GL_COLOR_ARRAY);
end;

As you can see, allmost everything is the same as for non-indexed drawing, except function that actually draw polygons. In this case we use glDrawElements function. For this one we specify what kind of polygons we want, how many indices are in index buffer, type of data in index buffer and pointer to the beginning of index buffer.

VertexArrayPic1.jpg

Full source code comes with mesh data file that this class can use to generates rectangle that is identical with one that is drawn using non-indexed mode. Mesh data file looks like this:

4 6
1 1 0 1 0 1
-1 1 0 0 0 1
-1 -1 0 0 1 0
1 -1 0 1 1 0
0
1
2
0
2
3

As you can see, there is data for only 4 vertices and 6 indices. So, first triangle is defined by vertices 0, 1 and 2, and the seccond one by vertices 0, 2 and 3. By using indexed mode we don't have to duplicate vertices.

Download source code, linux executable or windows executable from Lazarus CCR SourceForge.


Creating bindings for C libraries