grafix3d.net

2003년 Grafix3D.NET 문서 정리

• 기타
• 수학 및 물리
• 렌더링 파이프라인
• OpenGL 기초 튜토리얼
• 프랙탈
• GLUT
• 빌보딩 기법
• 특수 효과
• G3DGL
• 이 문서에 대해서

기타

• OpenGL 확장 기능 목록
• DirectDraw 프로그래밍 튜토리얼
• DirectX 16비트 픽셀 설정 방법
• 고스트마우스만들기
• zlib 사용하기
• Using ASSERT(), VERIFY(), and TRACE() in non-MFC Applications
• C++ 템플릿 튜토리얼
• Inside the VCL : From Message to Event Part 1
• Inside the VCL : From Message to Event Part 2
• Inside the VCL : Window Creation Fundamentals
• Programming Games on the Windows Canvas
• Turtle Graphics(거북이 그래픽스)
• Using DirectX with C++Builder
• Set 클래스 사용하기
• 서브클래싱의 이해
• BCB 에 GLUT 설치하기
• CreateParams 맛보기
• C++ MySQL CLASS 받기 | 레퍼런스 | 예제
• 조해진님이 수정해 주신 C++ MySQL CLASS!! 받기 ( 이자리를 빌어 정말 감사합니다 ^-^ )
• PHP CLASS(`01~02) 받기 | 레퍼런스 | 예제

수학 및 물리

• Determinant 의 의미
• 수학 용어
• 고유값의 활용
• 2D 선형 변환
• 3D 선형 변환
• Coordinate Systems, Vectors, Planes FAQ
• The Matrix and Quaternions FAQ
• The Quadratic Curves, Circles and Ellipses FAQ

렌더링 파이프라인

• 렌더링 파이프라인의 이해에 필요한 배경 지식
• 뷰잉 파이프라인의 이해
• Lighting
• Rasterisation
• Improving Realism

OpenGL 기초 튜토리얼

• OpenGL 을 위한 윈도우 프레임웤 만들기 1 편
• OpenGL 을 위한 윈도우 프레임웤 만들기 2 편
• 점 그리기
• 선 그리기
• 다각형 그리기
• 색 표현하기
• 3차원 폴리곤 그리기
• 투영변환
• 이동, 회전, 크기 변환
• 빛, 재질 표현하기
• 텍스춰 매핑
• 튜토리얼을 끝맺으면서...

프랙탈

• Fractal 에 대해서
• 재귀적 프랙탈
• 칸토어 먼지
• 시어핀스키 양탄자
• 코호 곡선
• 코호 섬
• C 곡선
• 드래곤 곡선
• 이진 나무
• L-SYSTEM
• L-SYSTEM 을 활용한 프랙탈

GLUT

• glut 설정
• glut 초기화
• 창의 크기 변화에 따른 렌더링 처리 하기
• 애니메이션
• 키입력처리
• 키입력 예제 : 세상을 둘러 보아요~ :)
• 마우스처리
• 팝업메뉴에 대해서
• 서브(sub, 하위)메뉴
• 메뉴 수정하기
• 메뉴 교체하기
• 비트맵폰트
• 비트맵폰트와 직교투영
• 외곽선폰트
• 초당프레임수(FPS) 계산하기
• 게임모드에 대해서
• 보조창(subwindow)에 대해서
• 보조창의 크기변환 처리
• 여러개의 보조창에 렌더링하기

빌보딩 기법

• 빌보딩이란?
• 빠르고 쉬운 구형빌보딩
• 빠르고 쉬운 원통형빌보딩
• 빠르지만 쉽지 않은 방법
• 원통형빌보딩
• 구형빌보딩
• 객체는 어디에 있나요?
• 예제의 소스코드

특수 효과

• Diffusion Effect

G3DGL

• G3DGL 받기
• G3DGL 컴포넌트에 대해서
• 렌더링 처리 방법

이 문서에 대해서

• 문서 저작권

1. GRAFIX3D.NET 에서 제공하는 문서의 저작권은 원저자에게 있습니다.
2. 원저자의 동의 없이는 문서를 상업적으로 이용할 수 없습니다. 상업적으로 이용을 원할 경우에는 원저자에게 문의하시길 바랍니다.
3. 번역된 문서를 다른 싸이트에 게시할 때에는 원문출처의 링크와 번역자의 이름을 명시해 주세요.

• 문서 정리

Grafix3D.NET 을 만들 당시 1 년씩 꾸준히 학습하고 결과물을 만들어서 문서로 정리하자고 다짐했었습니다. 아직 2003년이 지나지는 않았지만 새로운 기반으로 Grafix3D.NET 을 만들기 위해서 지금까지 만들어온 문서를 정리했습니다. 막상 하고 보니 많은 것을 하지도 못 했습니다. 아둔한 머리로 부푼 욕망을 따라 잡는 것은 참 어려운 일이네요^^;;; 내년부터 Grafix3D.NET 은 DirectX 를 기반으로 꾸며 나갈 생각입니다. 아직 준비된 것은 하나도 없지만 헤헤^^;; 2004년도 말에는 이 것 보다 더 많은 문서가 정리되길 바랍니다.

• 죄송합니다!

제 부족한 영어 실력으로 상당히 많은 오역을 만들어 냈습니다. 이를 반성하는 마음에서 얼마전에 몇몇 문서를 다시 번역하기로 약속드렸는데... 못 지킬 것 같습니다. 에궁ㅡ_ㅡ;;; 정말 죄송해요. 앞으로 더 많이 공부해서 오역을 만들지 않도록 노력하겠습니다. 반성반성^-^;;;

• 광고 : Boost.or.kr

C++, STL , Boost 에 대한 글을 번역해서 싸이트를 만들려고 Boost.or.kr 도메인을 샀는데 혼자서는 도저히 불가능한 일이더군요. Grafix3D.NET 도 계속해서 관리해야하니.... 그래서 뜻이 있는 분께 도메인을 양도하고 싶습니다. 그리고 저는 관련 글을 번역하는 멤버로^^;; 계약은 3 년으로 해 두었습니다. 뜻이 있는 분은 제 이메일로 연락해 주세요!! ^-^

관리자 : 김성철( paranwave@korea.com )


빌보딩 튜토리얼

빌보딩 튜토리얼 #1

빌보딩 튜토리얼

소개

by Antonio

원문 : http://www.lighthouse3d.com/opengl/billboarding/index.php3?billInt

용어설명

  • 빌보드 ( Billboard ) : 사각텍스춰 및 두 개의 삼각텍스춰에 그려진 2D 이미지
  • 빌보딩 ( Billboarding ) : 빌보드를 항상 특정 방향이나 한 곳을 바라보도록 설정하는 기법. 대부분 카메라를 바라보도록 설정함

빌보딩은 객체가 특정 목표물(대부분 카메라가 됨)을 바라보도록 방향을 조정하는 기법입니다. 여기서 객체가 특정 목표물을 "바라본다" 라는 말은 여러가지 의미가 있습니다. 이 튜토리얼을 통해서 하나하나씩 알아봅시다.

이 빌보딩 기법은 게임이나 매우 많은 수의 폴리곤이 필요한 어플리케이션에서 꽤 많이 사용됩니다. 그래픽카드가 아무리 빨라도 그 점은 전혀 충분하지 않을 것입니다. 대부분의 게임들이 하드웨어의 이점을 얻어서 지금까지 만든 것 보다 더욱 더 복잡하고 자세한 폴리곤을 출력하려고 하니까요.( 사실 하드웨어 제조업자들은 게임 설계자들과 절대 끝나지 않는 공생관계를 유지합니다.) 대부분의 3D 그래픽스 프로그래머들과 게임 프로그래머들은 적당한 프레임비율(FPS)을 만들기 위해서 폴리곤 수를 조절하느라 끊임 없이 계속 되는 악전 고투를 벌이고 있습니다.

빌보딩을 사용하면 기하모양을 텍스춰로 교체하여 장면을 만드는데 필요한 폴리곤의 수를 줄일 수 있습니다. 흔히 볼 수 있는 예는 나무입니다. 얼마나 많은 수의 폴리곤이 있어야 봄철의 나무를 표현할 수 있을까요? 빌보딩을 사용하면 기하모양을 그리는 대신에 사각형(또는 두 개의 삼각형)에 그려진 텍스춰 한 개만 있으면 아름다운 나무를 그릴 수 있습니다. 빌보딩은 텍스춰가 항상 카메라를 바라보도록 텍스춰의 방향을 조정합니다. 사용자가 텍스춰를 위에서 바라봐서 착시가 깨지지 않는다면 사용자들은 나무가 실제로는 사각형에 그려진 평평한 텍스춰라는 것을 절대 알아채지 못합니다.

같은 텍스춰를 모든 방향에 대해서 사용하면 그림자가 틀려지는데 이 점을 조심해야합니다. 서로 다른 방향에서 바라보는 3D 입체 나무는 서로 다른 고정된 모양으로 보일 것입니다. 또는 적어도 빛의 움직임과 카메라가 서로 관계가 없는 것처럼 보일 것입니다. 하지만 2D 빌보드기법으로 표현한 나무가 모든 방향에 대해서 같은 텍스춰를 사용한다면 빛이 카메라와 같이 움직이는 것처럼 보일 것입니다. 이런 경우에 사용자가 그림자가 틀렸다는 것을 모를수도 있겠지만 눈치챘을 경우를 위해서 이 문제를 해결해야 합니다. 한가지 해결 방법은 서로 다른 방향에 맞는 여러개의 텍스춰를 사용하는 것입니다. 하지만, 엄청나게 많은 텍스춰를 사용하는 것이 아니라면, 팝핑이펙트(popping effect)가 텍스춰가 변할 때마다 나타날 것입니다. 블렌딩이 이 문제를 해결하는 방법이 될 수 있는데 이 것은 이 문서의 범위를 벗어나는 주제이므로 다루지 않을 것입니다.

빌보딩은 폴리곤 개수에 대한 고민 없이도 마치 3D 입체 객체인 것처럼 착시를 만들 때 사용할 수 있습니다. 하지만 빌보딩이 꼭 폴리곤 수를 줄이기 위해서만 사용되는 것은 아닙니다. 일반적으로 빌보딩 기법은 어떤 객체가 특정 목표물을 바라보도록 만들기 위해서 사용하는데 대부분은 카메라를 바라보도록 만듭니다. 하지만 다른 객체를 바라보게 하거나 단순하게는 3 차원 공간의 한 점(위치)을 바라보도록 만들 수도 있습니다.

이번 튜토리얼에서 알아볼 빌보딩은 폴리곤의 수를 줄이는데 사용하는 것이 아니라 특정 어플리케이션의 용도와 관련된 기능들을 만드는데 사용하는 것입니다. 예를 들어서, 적들이 항상 여러분을 쳐다보로록 만든다든지 축구선수가 항상 축구공을 보도록 만드는 것입니다.

빌보딩기법에는 원통빌보딩과 구형빌보딩 두 가지 기법이 있습니다. 구형빌보딩은 객체의 방향에 제한을 두지 않지만 원통빌보딩은 객체의 회전이 벡터에 제한되는데 보통 Y 축 양의 방향 벡터에 제한됩니다.

이 튜토리얼에서 빌보딩을 구현하는 여러 방법들을 알아볼 것입니다. 첫번째 방법은 아주 간단한 방법이지만 실제 빌보딩을 만드는 것은 아닙니다. 이 방법은 구현하기 아주 쉬운 속임수입니다. 그 다음으로 결과는 같지만 빠르기를 위해서 사용하는 쿼드(quad) 빌보드에 대해서 알아봅니다. 이 기법은 구현하기가 쉽지 않지만 쿼드 빌보드(그림을 사각형 텍스춰에 그린 것)를 많이 사용할 때 속도에서 큰 잇점을 얻을 수 있습니다.

마지막에 가서 진짜 빌보딩을 다루는데 이 방법은 빌보드가 카메라만 바라보도록 만들어 줄 것입니다. 이 기법을 사용하면 빌보드를 다른 객체나 공간 속의 한 점을 바라보도록 만들 수 있습니다. 그렇기 때문에 마지막에 가서 알아보는 빌보딩이 속임수 기법보다 더 일반적인 방법입니다.

이 튜토리얼을 통해서 여러분들이 많은 것들을 배워 가시길 바랍니다. 문제점이 있을 때는 이메일을 보내 주세요

안토니오

번역 : 김성철 ( paranwave@korea.com )


빌보딩 튜토리얼

빌보딩 튜토리얼 #2

빌보딩 튜토리얼

속임수 - 빠르고 쉬운 구형빌보딩

by Antonio

원문 : http://www.lighthouse3d.com/opengl/billboarding/index.php3?billCheat

용어설명

  • 빌보드 ( Billboard ) : 사각텍스춰 및 두 개의 삼각텍스춰에 그려진 2D 이미지
  • 빌보딩 ( Billboarding ) : 빌보드를 항상 특정 방향이나 한 곳을 바라보도록 설정하는 기법. 대부분 카메라를 바라보도록 설정함

이번 장에서 알아볼 빌보딩은 진짜 빌보딩이 아니라 거의 비슷하게 만들 수 있는 속임수입니다. 이런 속임수도 몇몇 어플리케이션에서는 아주 효과적인 방법으로 사용됩니다.

이 튜토리얼에서 다루는 소스코드는 GLUT 튜토리얼에서 만든 프로젝트에 코드를 조금 덧붙인 것입니다. 아직 GLUT 에 대해서 익숙하지 않다면 튜토리얼을 읽어 보길 바랍니다. 그래야 소스코드를 이해하는데 도움이 됩니다.

다음 그림은 데모의 스크린샷입니다. 눈사람과 빌보딩으로 그린 나무들을 볼 수 있습니다. 설명하기 어렵지만, 현재 나무들은 카메라를 보고 있지 않습니다. 대신에 나무들은 카메라가 바라보는 방향에 수직인 평면을 바라보고 있습니다. 이 역시 설명하기 어렵군요 :)

다음 그림을 보면 제가 무슨 말을 하는지 확실하게 알 수 있습니다. 맨 아래의 검은색 원은 카메라를 나타낸 것입니다. 카메라에서 출발하는 벡터가 카메라가 현재 바라보고 있는 방향입니다. 이 벡터가 카메라가 바라보는 방향에 수직인 평면의 방향을 정합니다. 화면에 있는 객체들인 O1..O3 이 모두 돌려져 있는데 이 것은 객체들의 앞면 벡터가 평면을 가리키기 위해서 입니다. 이 것은 화면 안에 있는 객체들이 지역좌표의 원점에 있고 그 객체들이 Z 축의 양의 방향을 바라보고 있다고 가정하는 것입니다.

이 효과를 만들기 위해서는 모델뷰행렬이 필요합니다. 이미 알고 있듯이 모델뷰행렬은 기하변환에 대한 정보를 보관하고 있습니다. 즉, 여러분들이 하는 회전변환, 크기변환, 이동변환에 대한 정보를 보관합니다. 이 행렬은 여러분들이 입력한 좌표인 월드(world)좌표를 카메라 좌표로 바꾸는 변환도 포함하고 있습니다.

맨 오른쪽 열에서 위 3개의 값들은 카메라의 위치와 방향에 상대적인 지역좌표 원점의 현재 위치를 나타냅니다. 위의 3x3 부분행렬은 크기변환과 회전변환을 포함하는 부분입니다. 이 부분행렬을 단위행렬로 설정하면 이 변환들이 사라지고 카메라가 바라보는 방향은 월드좌표의 Z 축과 일치하게 됩니다. 카메라의 상단과 우측 벡터도 월드좌표의 각 축과 일치하게 됩니다. 이렇게 해서 구형빌보딩과 비슷한 속임수 구형빌보딩이 만들어 지는 것입니다. 빌보드의 중심은 지역좌표의 원점입니다. 이 속임수 구형빌보딩으로 나무를 나타냈을 때, 사용자가 나무의 위쪽 혹은 아래쪽을 보면 나무의 모습이 앞 뒤로 길게 늘어져 보이게 되어 모양새가 약간 어색해집니다. 이 것은 빌보드가 카메라 위치의 맞은편에 있을 때 카메라의 방향에 의해서 빌보드의 방향이 결정되기 때문입니다.

실제로, 부분행렬을 단위행렬로 설정하는 것은 객체를 아무런 회전도 없이 그리기 위한 것입니다. 그래서 카메라가 항상 Z 축의 음의 방향으로 바라보고 있는 것처럼 그림이 그려집니다.

크기변환도 사라지는데, 모델뷰행렬이 바뀌기 전까지 이 속임수 구형빌보딩으로는 크기변환된 나무를 그리고 싶어도 그릴 수가 없습니다.

속임수 구형빌보딩을 만들기 위해서 우선 현재 모델뷰행렬을 저장합니다. 그러고 난 후에 모델뷰행렬을 얻어서 상단의 3x3 부분행렬을 단위행렬로 설정합니다. 이 행렬을 OpenGL 상태기계에 올려 놓고 나서 객체를 지역좌표의 원점에 그리면 됩니다. 끝으로 빌보드를 다 그렸으면 원래의 모델뷰행렬을 OpenGL 상태기계에 다시 올려 놓아야합니다.

float modelview[16];
int i,j;

// 현재 모델뷰행렬을 저장합니다.
glPushMatrix();
//
  현재 모델뷰행렬을 가져옵니다.
glGetFloatv(GL_MODELVIEW_MATRIX
  , modelview);
//
  모든 회전을 원래대로 돌려 놓습니다.
// 크기변환에 관한 모든 것이 사라지는
  것에 주의하세요.
for( i=0; i<3; i++ )
{
for(
  j=0; j<3; j++ )
{
if
  ( i==j )
modelview[i*4+j]
  = 1.0;
else
modelview[i*4+j]
  = 0.0;
}
}
//
  모델뷰행렬을 회전과 크기변환이 없는 행렬로 설정합니다.
glLoadMatrixf(modelview);
drawObject();
// 모델뷰행렬을 저장했던 것으로 다시 되돌려 놓습니다.
glPopMatrix();

위의 코드를 사용하면, 객체들은 카메라가 바라보는 방향에 수직인 평면을 바라보도록 알맞게 회전할 것입니다. 이 회전의 중심은 지역좌표의 원점이 됩니다.

위의 코드는 두 개의 함수로 나눌 수 있습니다. 하나는 그림을 그리기 전에 사용하는 함수로 모델뷰행렬을 알맞게 설정합니다. 두번째 함수는 그림을 다 그리고 나서 사용하는 함수로 현재 모델뷰행렬을 다시 원래대로 돌려 놓습니다.

void billboardCheatSphericalBegin()
{
float modelview[16];
int i,j;

//
  현재 모델뷰행렬을 저장합니다.
glPushMatrix();

//
  현재 모델뷰행렬을 가져옵니다.
glGetFloatv(GL_MODELVIEW_MATRIX
  , modelview);

//
  모든 회전을 원래대로 돌려 놓습니다.
// 크기변환에 관한 모든 것이 사라지는
  것에 주의하세요.
for( i=0; i<3; i++ )
for(
  j=0; j<3; j++ )
{
if
  ( i==j )
modelview[i*4+j]
  = 1.0;
else
modelview[i*4+j]
  = 0.0;
}

//
  모델뷰행렬을 회전과 크기변환이 없는 행렬로 설정합니다.
glLoadMatrixf(modelview);
}

void billboardEnd()
{
//
  모델뷰행렬을 저장했던 것으로 다시 되돌려 놓습니다.
glPopMatrix();
}

위의 두 함수를 사용하면 빌보드를 그리는 코드를 아래처럼 작성할 수 있습니다. :

billboardCheatSphericalBegin();
drawObject();
billboardEnd();

앞에서도 말했듯이, 여러분이 새로운 모델뷰행렬을 설정하면 크기변환은 사라집니다. 만약에 크기변환을 하여 객체를 그리고 싶다면 아래에 나오는 코드처럼 빌보딩을 설정하고 나서 크기를 변환하면 됩니다.

billboardCheatSphericalBegin();
glScalef(1,2,1);
drawObject();
billboardEnd();

번역 : 김성철 ( paranwave@korea.com )


빌보딩 튜토리얼

빌보딩 튜토리얼 #3

빌보딩 튜토리얼

속임수 - 원통형빌보딩

by Antonio

원문 : http://www.lighthouse3d.com/opengl/billboarding/index.php3?billCheat1

용어설명

  • 빌보드 ( Billboard ) : 사각텍스춰 및 두 개의 삼각텍스춰에 그려진 2D 이미지
  • 빌보딩 ( Billboarding ) : 빌보드를 항상 특정 방향이나 한 곳을 바라보도록 설정하는 기법. 대부분 카메라를 바라보도록 설정함

앞 장에서도 말했듯이, 모델뷰행렬은 지역좌표와 월드좌표간에 좌표 변경을 위해서 여러가지 변환들을 포함하고 있습니다. 그리고 이 행렬의 상단 3x3 부분행렬을 단위행렬로 바꿔서 속임수 구형빌보딩을 만들 수 있었습니다. 그럼 원통형빌보딩은 무엇이고 어떻게 만드는 것일까요?

지금부터 상단 3x3 부분행렬의 실제 구조가 무엇인지 자세하게 알아봅시다. 이 행렬은 세 개의 열을 가지고 있고 각각의 열마다 특별한 의미를 담고 있습니다. 첫번째 열은 카메라의 우측 벡터이고 두번째 열은 카메라의 상단 벡터이고 세번째 열은 카메라가 바라보는 곳을 나타내는 시점(lookAt) 벡터입니다. 이 세 개의 벡터가 서로 연관되어 카메라의 방향을 결정합니다. 그래서 상단의 3x3 부분행렬을 단위행렬로 설정하면 카메라의 좌표계가 월드 좌표계와 일치하게 됩니다.

속임수 원통형빌보딩을 만들려면, 카메라의 상단 벡터는 그대로 남겨두고 카메라의 우측 벡터와 시점 벡터를 월드 좌표계와 일치되도록 만들면 됩니다. 이 기법과 앞 장에서 살펴본 속임수 구형빌보딩의 차이점은 카메라가 위아래를 쳐다볼 때 빌보드가 안 움직인다는 것입니다. 대신에 이 빌보드는 카메라가 좌우를 볼때만 회전합니다.

이 원통형빌보딩도 나무를 그릴 때 사용할 수 있습니다. 이 기법을 사용하면 카메라가 위아래를 쳐다볼 때 나무가 앞뒤로 늘어나는 현상이 없어지는데 그것은 카메라의 상단 벡터가 고정되어 있기 때문입니다. 그래서 나무가 평평한 빌보드로 되어 있다는 점을 구형빌보딩 보다 더 잘 숨길 수 있습니다. 하지만 나무 위에 카메라가 있게 되면 착시가 깨지게 되고 이 경우 나무가 점점 작아져서 결국에는 아무것도 보이지 않게 됩니다. 하지만 이렇게 되지 않도록 만든다면 원통형빌보딩은 아주 좋은 선택이 될 것입니다.

다음은 속임수 원통형빌보딩의 코드입니다. 속임수 구형빌보딩의 코드와 거의 비슷합니다.

float modelview[16];
int i,j;

// 현재 모델뷰행렬을 저장합니다.
glPushMatrix();

// 현재 모델뷰행렬을 가져옵니다.
glGetFloatv(GL_MODELVIEW_MATRIX , modelview);

// 속임수 구형빌보딩기법의 코드와 다른점은
// 변수가 카메라의 상단벡터인 행렬의 두 번째 열을 건너뛰고
// 설정하는 것입니다.

for( i=0; i<3; i+=2 )
{
for( j=0; j<3; j++ )
{
if ( i==j )
modelview[i*4+j]
  = 1.0;
else
modelview[i*4+j]
  = 0.0;
}
}

// 카메라의 상단 벡터가 변경 되지 않은
// 모델뷰행렬을 설정합니다.
glLoadMatrixf(modelview);

drawObject();

// 모델뷰행렬을 저장했던 것으로 다시 되돌려 놓습니다.
glPopMatrix();

앞 장의 코드처럼 위의 코드도 두 개의 함수로 나눌 수 있습니다. 하나는 그림을 그리기 전에 사용하는 함수로 모델뷰행렬을 알맞게 설정합니다. 두번째 함수는 그림을 다 그리고 나서 사용하는 함수로 현재 모델뷰행렬을 다시 원래대로 돌려 놓습니다.

void billboardCheatCylindricalBegin()
{

float modelview[16];
int i,j;

// 현재 모델뷰행렬을 저장합니다.
glPushMatrix();

// 현재 모델뷰행렬을 가져옵니다.
glGetFloatv(GL_MODELVIEW_MATRIX , modelview);

for( i=0; i<3; i+=2 )
{
for( j=0; j<3; j++ )
{
if ( i==j
  )
modelview[i*4+j] = 1.0;
else
modelview[i*4+j] = 0.0;
}
}

// 카메라의 상단 벡터가 변경 되지 않은
// 모델뷰행렬을 설정합니다.
glLoadMatrixf(modelview);
}

void billboardEnd()
{
// 모델뷰행렬을 저장했던 것으로 다시 되돌려 놓습니다.
glPopMatrix();
}

위의 두 함수를 사용하면 빌보드를 그리는 코드를 아래와 같이 작성할 수 있습니다. :

billboardCheatCylindricalBegin();
drawObject();
billboardEnd();

다시 한번 말하지만, 크기변환을 조심해야합니다. 이 속임수 원통형빌보딩은 X 축과 Y 축에 대한 크기변환이 되지 않고 Z 축에 대한 크기변환만 됩니다. 제일 좋은 방법은 billboardCheatCylindricalBegin() 함수를 호출하고 나서 크기를 변환하는 것입니다.

번역 : 김성철 ( paranwave@korea.com )


빌보딩 튜토리얼

빌보딩 튜토리얼 #4

빌보딩 튜토리얼

속임수 - 빠르지만 쉽지 않은 방법

by Antonio

원문 : http://www.lighthouse3d.com/opengl/billboarding/index.php3?billCheat2

용어설명

  • 빌보드 ( Billboard ) : 사각텍스춰 및 두 개의 삼각텍스춰에 그려진 2D 이미지
  • 빌보딩 ( Billboarding ) : 빌보드를 항상 특정 방향이나 한 곳을 바라보도록 설정하는 기법. 대부분 카메라를 바라보도록 설정함

모델뷰행렬의 상단 3x3 부분행렬의 방향을 반대로 바꾸는 것만으로 속임수 빌보딩을 만들 수 있었습니다. 그리고 앞의 장들에서는 부분행렬이 포함하는 변환들이 사라지도록 해서 이 것을 만들었습니다.

이번 장에서 알아볼 방법은 빌보딩을 구현하는 또 다른 방법이며 아주 많이 사용하는 것이기도 합니다. 이 빌보딩은 OpenGL Game Programming 책에서도 다루는 방법입니다.

앞의 장들과는 다르게 모델뷰행렬을 변경하지 않고 대신에 빌보드의 정점들을 손수 변경해 주는 것입니다. 이 변환은 모델뷰행렬의 현재 방향을 효과적으로 반전시키고 앞의 장들과 같은 결과를 만들어 냅니다.

방향이 반전된 모델뷰행렬의 상단 벡터와 우측 벡터를 사용해서 빌보드의 정점을 정의합니다. M1 의 역행렬에서 이 벡터들을 얻을 수 있습니다.

다행스럽게도 직교행렬의 역행렬은 원래 행렬의 전치행렬입니다. 그럼 M1 은 직교행렬입니까? 반드시 그래야 합니다. 카메라를 움직이기 위해서 gluLookAt 함수를 사용하거나 카메라를 이동하고 회전만 한다면 M1 은 직교행렬입니다. 만약 그렇지 않고 모델뷰행렬을 뒤죽박죽 사용한다면 M1 은 직교행렬이 아닐 것입니다.

그럼 전치행렬은 무엇일까요? 행렬 A 의 전치행렬이 B 라면 AT = B 라고 적고 모든 원소에 대해서 aij = bji 일 때를 말합니다.

직교행렬을 무엇입니까? 정방행렬 Q 가 직교행렬이면 Q x QT = I 가 됩니다. 여기서 QT 는 Q 행렬의 전치행렬이고 I 는 단위행렬을 뜻합니다. 행과 열이 다른 원소를 서로 곱하면 0 이 나오는데 이 것은 벡터의 외적이 0 이라는 뜻입니다. 그리고 행과 열이 서로 같은 원소를 곱하면 1 이 나오는데 이 것은 벡터가 자신과 곱해져서 정규화되었다는 뜻입니다. 즉, 한 행렬에 그 행렬의 전치행렬을 곱했을 때 단위행렬이 되면 그 행렬을 직교행렬이라고 합니다. 이 방법을 사용해서 주어진 행렬이 직교행렬인지 아닌지를 판단할 수 있습니다.

M1 은 직교행렬이므로 M1 의 역행렬을 쉽게 얻을 수 있습니다. M1 역행렬의 첫번째 열은 우측 벡터이고 두번째 열은 상단 벡터입니다.

right = [a0,a4,a8]
up = [a1,a5,a9]

아래에 두 개의 쿼드빌보드 예제가 있는데 정점을 결정하는 방법을 보여줍니다. 아래 쿼드들의 각변의 길이는 같다고 가정합니다.

원점이 아랫변의 중심에 있는 첫번째 쿼드의 정점들을 구해보면 다음과 같습니다. ( 왼쪽 그림 )

a = center - right * (size * 0.5);
b = center + right * size * 0.5;
c = center + right * size * 0.5 + up * size;
d = center - right * size * 0.5 + up * size;

쿼드의 중심에 원점이 있는 오른쪽 그림의 쿼드는 정점들을 다음과 같이 정의됩니다. :

a = center - (right + up) * size;
b = center + (right - up) * size;
c = center + (right + up) * size;
d = center - (right - up) * size;

위의 방법들을 비교해 보면 매 프레임마다 모델뷰행렬은 한번만 얻으면 되지만 빌보드의 정점 모두를 손수 변환해야합니다. 전체적으로 이 방법이 쿼드빌보드( 단지 4 개의 정점만 있는 빌보드)에 가장 빠릅니다. 하지만 구현하기는 어렵습니다. 그리고 아주 많은 정점으로 이뤄진 객체를 빌보드로 만든다면 앞의 장들에서 살펴본 방법을 사용하여 더 좋은 결과를 만들 수 있습니다.

소스코드에 나오는 함수는 상단 벡터와 우측 벡터를 추출해냅니다.

void l3dBillboardGetUpRightVector(float *up, float *right);

인자 설명:
up - 3 개의 float 형 데이타를 담는 배열, 이 배열에 상단벡터의 값을 설정합니다.
right - 3 개의 float 형 데이타를 담는 배열, 이 배열에 오른쪽벡터의 값을 설정합니다.

소스코드는 다음과 같습니다.:

void l3dBillboardGetUpRightVector(float *up,float *right)
{
float modelview[16];

glGetFloatv(GL_MODELVIEW_MATRIX, modelview);

right[0] = modelview[0];
right[1] = modelview[4];
right[2] = modelview[8];

up[0] = modelview[1];
up[1] = modelview[5];
up[2] = modelview[9];
}

지금까지 살펴본 방법을 원통형으로 만들려면 상단 벡터를 [0, 1, 0] 으로 설정하면 됩니다. 그리고 오른쪽 벡터를 얻기 위해선 l3dBillboardGetRightVector 함수를 사용하면 됩니다.

번역 : 김성철 ( paranwave@korea.com )


빌보딩 튜토리얼

빌보딩 튜토리얼 #5

빌보딩 튜토리얼

원통형빌보딩

by Antonio

원문 : http://www.lighthouse3d.com/opengl/billboarding/index.php3?billCyl

용어설명

  • 빌보드 ( Billboard ) : 사각텍스춰 및 두 개의 삼각텍스춰에 그려진 2D 이미지
  • 빌보딩 ( Billboarding ) : 빌보드를 항상 특정 방향이나 한 곳을 바라보도록 설정하는 기법. 대부분 카메라를 바라보도록 설정함

진짜 원통형빌보딩도 속임수 기법처럼 축을 중심으로 한 회전을 막지만 객체가 바라보는 곳을 나타내는 시점 벡터는 항상 카메라의 방향, 카메라의 상단 벡터에 의해서 정의되는 평면, 지역좌표 원점의 중심으로 틀어져 있습니다. 아래의 그림을 보면 무엇을 말하는지 쉽게 알 수 있습니다.

위의 예에서 사용자가 주위를 돌아다니면 나무들은 사용자를 정확하게 바라볼 것입니다.

이번에 알아볼 방법을 이해하기 위해서는 객체가 월드 좌표에 있을 뿐만 아니라 바라보는 표적의 위치도 월드 좌표에 있다는 것을 알아야 합니다. 잘 알듯이 바라보는 표적은 대개 카메라의 위치입니다. 만약에 객체가 이동 또는 회전과 같은 기하변환을 통해서 월드 안에 놓여졌다면 월드 좌표로 객체 위치를 알아내는 것이 고된 일이 될 수 있습니다. 이에 대해서는 나중에 더 자세하게 알아보도록 합시다.(빌보딩 튜토리얼 #7)

객체들과 표적의 위치를 월드좌표로 알고 있다고 가정하면 빌보딩은 간단한 기하연산이 됩니다.

그 이유는 객체다 다음 벡터들을 가지고 있다고 가정하기 때문입니다.

  • 우측 벡터(right vector) = [1,0,0]
  • 상단 벡터(up vector) = [0,1,0]
  • 시점 벡터(lookAt vector) = [0,0,1]

실제로 이 것은 객체가 지역좌표의 원점에 그려지는 것과 객체가 Z 축 양의 방향을 따라 바라보는 것을 뜻합니다.

객체가 카메라를 바라보게 하려면 상단 벡터를 중심으로 회전해야합니다. 이 때 객체의 현재 시점 벡터와 객체에서 카메라까지 연결된 벡터 사이의 각만큼 회전합니다.

객체에서 카메라까지의 벡터는 다음과 같이 정의할 수 있습니다.

  • objToCam = CamPos - ObjPos

objToCamProj 벡터는 objToCam 벡터를 XZ 평면에 투영한 것입니다. 그래서 Y 요소가 0 으로 설정됩니다.

각을 알기 위해서 objToCamProj 를 정규화 하고 내적을 하면 시점(lookAt) 벡터와 objToCam 사이의 각의 코사인 값을 알 수 있습니다. 하지만 코사인 값만 아는 것만으로 충분하지 않습니다. 왜냐하면 cos(a) = cos(-a) 이기 때문입니다. 외적을 계산하면 확실한 각을 알 수 있습니다. 만약에 각이 양의 값이라면 외적 벡터는 상단 벡터와 같은 방향이고 각이 음의 값이라면 상단 벡터의 방향은 반대가 되어 회전을 반대로 할 수 있습니다.

다음은 objToCamProj 를 계산한 후 필요한 단계입니다.

  • 1. normalize(objToCamProj) ; objToCamProj 를 정규화한다.
  • 2. angleCosine = innerProduct(lookAt,objToCamProj) ; 시점(lookAt) 벡터와 objToCamProj 간의 내적으로 구해서 코사인각을 구한다.
  • 3. upAux = crossProduct(lookAt,objToCamProj) ; 시점(lookAt) 벡터와 objToCamProj 를 외적한다.
  • 4. glRotatef(acos(aux)*180/3.14,upAux[0], upAux[1], upAux[2]); 회전한다.

다음 함수는 빌보드를 설정하는 것입니다. 다음은 코드입니다. :

void billboardCylindricalBegin( float camX, float camY, float
  camZ, float objPosX, float objPosY, float objPosZ)
{
float lookAt[3], objToCamProj[3], upAux[3];
float modelview[16], angleCosine;

glPushMatrix();

// objToCamProj 는 지역좌표 원점에서 카메라까지
  잇는 벡터를
// 월드좌표로 나타낸 것으로 XZ 평면에 투영한 것입니다.
objToCamProj[0] = camX - objPosX ;
objToCamProj[1] = 0;
objToCamProj[2] = camZ - objPosZ ;

// 월드좌표 내의 객체의 시점(lookAt) 벡터입니다.
lookAt[0] = 0;
lookAt[1] = 0;
lookAt[2] = 1;

// 코사인값을 얻기 위해서 벡터를 정규화합니다.
mathsNormalize(objToCamProj);

// 각이 음인지 양인지 아는 것은 간단합니다.
// upAux 가 y 축의 양의 방향을 가리키는 벡터가 될 것이고
// 음의 값이라면 y 축의 음의 방향을 가리키는 벡터가 되어
// 반대로 회전을 합니다.
mathsCrossProduct(upAux, lookAt, objToCamProj);

// 각을 계산합니다.
angleCosine = mathsInnerProduct(lookAt, objToCamProj);

// 회전을 합니다. if 문은 안정성을 이유로 사용했습니다.
// lookAt 벡터와 objToCamProj 벡터가 너무 가까우면
// |angleCosine| 값이 1 보다 커질 수 있기때문에 정확함이 떨어집니다.
if ((angleCosine < 0.99990) && (angleCosine
  > -0.9999))
glRotatef(acos(angleCosine)*180/3.14,
  upAux[0], upAux[1], upAux[2]);
}

billboardEnd() 함수는 다음 예제처럼 객체를 그린 다음에 호출합니다.

billboardCylindricalBegin(cx, cy, cz, ox, oy, oz);
drawObject();
billboardEnd();

번역 : 김성철 ( paranwave@korea.com )


빌보딩 튜토리얼

빌보딩 튜토리얼 #6

빌보딩 튜토리얼

구형빌보딩

by Antonio

원문 : http://www.lighthouse3d.com/opengl/billboarding/index.php3?billSphe

용어설명

  • 빌보드 ( Billboard ) : 사각텍스춰 및 두 개의 삼각텍스춰에 그려진 2D 이미지
  • 빌보딩 ( Billboarding ) : 빌보드를 항상 특정 방향이나 한 곳을 바라보도록 설정하는 기법. 대부분 카메라를 바라보도록 설정함

구형빌보딩은 원통형 기법을 약간 확장한 것입니다. 원통형 기법으로 객체를 회전시킨 다음, 정확하게 카메라를 바라볼 때까지 객체를 기울입니다.

회전축은 우측 벡터입니다. 각에 대해 말하면, objToCamProj 와 objToCam 벡터를 내적해서 코사인 값을 얻을 수 있습니다.

다음 함수는 빌보드를 설정하는 것입니다. 다음은 코드입니다. :

void billboardSphericalBegin( float camX, float camY, float camZ,
  float objPosX, float objPosY, float objPosZ)
{
float lookAt[3],objToCamProj[3],upAux[3];
float modelview[16],angleCosine;

glPushMatrix();

// objToCamProj 는 지역좌표 원점에서 카메라까지
  잇는 벡터를
// 월드좌표로 나타낸 것으로 XZ 평면에 투영한 것입니다.
objToCamProj[0] = camX - objPosX ;
objToCamProj[1] = 0;
objToCamProj[2] = camZ - objPosZ ;

// 월드좌표 내의 객체의 시점(lookAt) 벡터입니다.
lookAt[0] = 0;
lookAt[1] = 0;
lookAt[2] = 1;

// 코사인값을 얻기 위해서 벡터를 정규화합니다.
mathsNormalize(objToCamProj);

// 각이 음인지 양인지 아는 것은 간단합니다.
// upAux 가 y 축의 양의 방향을 가리키는 벡터가 될 것이고
// 음의 값이라면 y 축의 음의 방향을 가리키는 벡터가 되어
// 반대로 회전을 합니다.
mathsCrossProduct(upAux, lookAt, objToCamProj);

// 각을 계산합니다.
angleCosine = mathsInnerProduct(lookAt,objToCamProj);

// 회전을 합니다. if 문은 안정성을 이유로 사용했습니다.
// lookAt 벡터와 objToCamProj 벡터가 너무 가까우면
// |angleCosine| 값이 1 보다 커질 수 있기때문에 정확함이 떨어집니다.
if ((angleCosine < 0.99990) && (angleCosine
  > -0.9999))
glRotatef(acos(angleCosine)*180/3.14,upAux[0],
  upAux[1], upAux[2]);

// 지금까지는 구형빌보딩과 똑 같습니다.
// 지금부터 두번째 회전을 수행하는 코드입니다.
// 객체를 기울여서 카메라를 정확하게 바라보도록 합니다.

// objToCam 은 지역좌표의 원점에서 카메라까지의
  거리를
// 월드좌표로 표현한 벡터입니다.
objToCam[0] = camX - objPosX;
objToCam[1] = camY - objPosY;
objToCam[2] = camZ - objPosZ;

// 코사인값을 얻기 위해서 정규화합니다.
mathsNormalize(objToCam);

// objToCamProj 와 objToCam
  사이의 각을 계산합니다.
// 즉, 시점 lookup 벡터를 위해 필요한 각을 계산하는 것입니다.
angleCosine = mathsInnerProduct(objToCamProj,objToCam);

// 객체를 기울입니다.
// 아래의 검사는 objToCam 와 objToCamProj 사이의 각이
// 아주 작을 때 생기는 불안정성을 막기 위한 것입니다.
if ((angleCosine < 0.99990) && (angleCosine
  > -0.9999))
if (objToCam[1] < 0)
glRotatef(acos(angleCosine)*180/3.14,1,0,0);
else
glRotatef(acos(angleCosine)*180/3.14,-1,0,0);
}

billboardEnd() 함수는 다음 예제처럼 객체를 그린 다음에 호출합니다.

billboardCylindricalBegin(cx, cy, cz, ox, oy, oz);
drawObject();
billboardEnd();

번역 : 김성철 ( paranwave@korea.com )


빌보딩 튜토리얼

빌보딩 튜토리얼 #7

빌보딩 튜토리얼

객체는 어디에 있나요?

by Antonio

원문 : http://www.lighthouse3d.com/opengl/billboarding/index.php3?billWhere

용어설명

  • 빌보드 ( Billboard ) : 사각텍스춰 및 두 개의 삼각텍스춰에 그려진 2D 이미지
  • 빌보딩 ( Billboarding ) : 빌보드를 항상 특정 방향이나 한 곳을 바라보도록 설정하는 기법. 대부분 카메라를 바라보도록 설정함

이동변환과 회전변환을 사용해서 객체를 월드 안에 놓을 때 그 객체의 월드좌표를 찾기가 점점 힘들어 집니다. 이번 장에선 객체의 월드좌표가 어디쯤인지를 찾는 간단한 방법에 대해서 알아보겠습니다. 우선 객체를 지역좌표의 원점에 그렸다고 가정합니다. 이 방법은 월드좌표에서 지역좌표의 원점이 어디인지 알려줍니다.

우선 모델뷰행렬을 다시 봐야합니다. 카메라 좌표계상에서 지역좌표의 원점의 위치는 아래 그림의 v 벡터입니다. 하지만 지역좌표의 원점의 위치는 카메라의 위치와 방향에 의존하기 때문에, 카메라의 위치에 저 위치만 더할 수 없습니다.

카메라의 방향을 반대로 만들기 위해서 필요한 것은 좌표계의 각 축을 월드좌표계와 일치하도록 만드는 것입니다. 그러면 카메라에 상대적인 객체의 위치와 카메라의 위치를 더할 수 있습니다. 이 합으로 월드좌표상의 객체의 위치를 알 수 있습니다.

카메라의 방향을 반대로 만들기 위해선 M1 의 역행렬을 사용해야 합니다. 앞에서 말했듯이, M1 의 역행렬은 전치행렬입니다.

따라서 월드좌표상의 객체의 위치는 아래의 식으로 구할 수 있습니다.

objPosWC = camPos + M1T * V

camPos 는 월드좌표상의 카메라의 위치이고 M1T 는 M1 의 전치행렬입니다.

번역 : 김성철 ( paranwave@korea.com )


빌보딩 튜토리얼

빌보딩 튜토리얼 #8

빌보딩 튜토리얼

소스코드

by Antonio

원문 : http://www.lighthouse3d.com/opengl/billboarding/index.php3?billSource

용어설명

  • 빌보드 ( Billboard ) : 사각텍스춰 및 두 개의 삼각텍스춰에 그려진 2D 이미지
  • 빌보딩 ( Billboarding ) : 빌보드를 항상 특정 방향이나 한 곳을 바라보도록 설정하는 기법. 대부분 카메라를 바라보도록 설정함

빌보딩 튜토리얼에서 제공하는 예제의 코드는 어떤 방식으로든 최적화 되지 않았습니다. 이 튜토리얼은 교육을 목적으로 만들었기 때문입니다. 하지만 예제에는 최적화가 필요한 부분이 여러군데 있을 것입니다. 제공하는 예제는 빌보딩 튜토리얼에서 다루었던 모든 빌보딩을 포함하여 만들었습니다.

화살표키를 사용하면 주변을 돌아다닐 수 있습니다. 나무는 아래가 중심인 빌보드로 표현했고 눈사람은 빌보드로 표현하지 않았습니다. 위쪽과 아래쪽을 보려면 F1 키와 F2 키를 누르세요.(각 빌보딩들의 차이점을 볼 수 있습니다.) 데모의 처음은 속임수 구형빌보딩으로 시작합니다. F5 키를 사용하면 각각의 빌보딩을 차례대로 바꿀 수 있습니다. 콘솔 윈도우에 나타나는 메세지는 현재 어떤 빌보딩을 사용하고 있는 알 수 있도록 해줍니다. 진짜 구형빌보딩이 사용될 때 나무를 넘어보도록 시도해 보길 바랍니다!

끝으로 버그를 발견하시면 저에게 이메일로 알려주세요.

[ 소스코드받기(VC6.0) ]

번역 : 김성철 ( paranwave@korea.com )


C++ 템플릿 튜토리얼

제네릭 프로그래밍 시리즈 문서

C++ 템플릿 튜토리얼

by Christopher Vickery

원문 : http://babbage.cs.qc.edu/STL_Docs/templates.htm

목 차

  • 소개
  • 클래스 템플릿
    • 클래스 템플릿의 구현
    • 클래스 템플릿의 멤버 함수 구현
    • 클래스 템플릿의 사용
  • 함수 템플릿
    • 함수 템플릿의 구현
    • 함수 템플릿의 사용
  • 템플릿의 인스턴스화
  • 클래스 템플릿 스페셜라이제이션
    • 클래스 템플릿의 부분 스페셜라이제이션
  • 함수 템플릿의 스페셜라이제이션
  • 템플릿 인자
  • 정적 멤버와 변수
  • 템플릿과 프렌드(friend)

소개 ▲

수 많은 C++ 프로그램들은 스택(stack), 큐(queue), 리스트(list)와 같은 공통 데이타 구조를 사용합니다. 현재 만들고 있는 프로그램에서 고객을 위한 큐와 메세지를 위한 큐가 필요할 지도 모릅니다. 고객을 위한 큐는 쉽게 만들 수 있습니다. 그 다음에 메세지를 위한 큐를 만들 때는 고객을 위한 큐를 만들 때 사용했던 코드를 다시 사용해서 구현합니다. 프로그램이 자꾸만 커지면서 주문을 위한 큐가 필요해졌습니다. 그럼 메시지를 위한 큐를 가져다가 주문을 위한 큐를 만들기 위해서 코드를 복사하고 붙이고 찾고 수정해야합니까? 큐의 구현만 바꾸는 쉬운 일인가요? 아닙니다. 이건 쉬운 일이 아닙니다. 코드는 갈수록 중복되어 쌓이게 됩니다. 그리고 필요한 모든 코드를 처음부터 끝까지 다시 만드는 것은 객체지향 방법론으로 봤을 때 아주 멍청한 짓입니다. 그럼 코드를 중복하지 않고 이 문제를 해결할 수 있다는 얘기인가요? 어떻게 하는 것인가요? 이 질문에 대한 답은 바로 템플릿을 사용하는 것입니다.

C++ 템플릿을 사용해서 타입 인자 T 를 갖는 제네릭한 Queue<T> 템플릿을 만들 수 있습니다. 여기서 T 는 실제 타입으로 바뀌게 됩니다. 예를 들어, Queue<Customers> 라고 하면 C++ 는 클래스 Queue<Customers> 를 만들어 냅니다. Queue 의 구현을 바꾸는 일은 아주 쉬워집니다. 템플릿 Queue<T> 의 구현을 한번 바꾸게 되면 Queue<Customers>, Queue<Messages>, 그리고 Queue<Orders> 에도 변환 내용이 그대로 반영됩니다.

템플릿은 어느 타입에나 필요한 벡터(vector), 스택(stack), 리스트(list), 큐(queue) 와 같은 것을 제네릭하게 만들 때 사용하면 아주 유용합니다. C++ 템플릿은 상속과 구성이 제공하는 객체 코드의 재사용 방법 대신에 소스 코드의 재사용 방법을 제공합니다.

C++ 에는 클래스 템플릿과 함수 템플릿, 두 가지 종류의 템플릿이 있습니다. : 모든 형을 다루기 위한 제네릭 함수를 만든다면 함수 템플릿을 사용하세요. 예를 들어, 모든 형에 대해서 사용할 수 있는 검색과 정렬 루틴을 함수 템플릿을 사용해 제네릭 함수로 만들 수 있습니다. 표준 템플릿 라이브러리(STL)의 제네릭 알고리즘은 모두 함수 템플릿으로 구현한 것입니다. 그리고 STL 의 컨테이너는 모두 클래스 템플릿으로 구현한 것입니다.

클래스 템플릿 ▲

클래스 템플릿의 구현 ▲

클래스 템플릿의 정의는 일반 클래스의 정의와 비슷합니다. 단 template(템플릿) 키워드가 선두에 온다는 점만 틀립니다. 아래에는 스택을 템플릿 클래스로 정의한 예제입니다.

template<class
  T>
class Stack
{
public:
Stack(int = 10);
~Stack() { delete [] stackPtr;
  }
int push(const
  T&);
int pop(T&);
int isEmpty() const
  { return top == -1; }
int isFull() const
  { return top == size - 1; }
private:
int size; //
  스택에 있는 원소의 갯수.
int top;
T* stackPtr;
};

T 는 타입 인자로 어떤 타입이나 다 가능합니다. 예를 들어, Stack<Token> 에서 Token 은 사용자가 만든 클래스입니다. class 란 키워드 때문에 클래스 타입만 넣을 수 있다고 생각할 수도 있습니다. 하지만 T 에는 클래스 타입만 가능한 것이 아닙니다. 예를 들어, Stack<int> 와 Stack<Message *> 를 하면 Message * 와 int 가 "클래스" 가 아니어도 인스턴스를 만들 수 있습니다.

클래스 템플릿의 멤버 함수 구현 ▲

템플릿 멤버 함수의 구현에는 일반 클래스의 멤버 함수를 구현하는 것과는 약간의 차이점이 있습니다. 클래스 템플릿의 멤버 함수의 선언과 정의는 모두 같은 헤더 파일 안에 있어야합니다.

//B.H
template<class t>
class b
{
public:
b() ;
~b() ;
} ; 		// B.CPP
#include "B.H"

template<class t>
b<t>::b()
{}

template<class t>
b<t>::~b()
{} 		//MAIN.CPP
#include "B.H"

void main()
{
b<int> bi ;
b<float> bf ;
}

B.cpp 파일을 컴파일하면, 컴파일러는 선언과 정의 모두에 대한 정보를 갖게 됩니다. 그리고 템플릿 클래스로 인스턴스를 만드는 것이 없기 때문에 템플릿 클래스를 만들지 않아도 됩니다. 하지만 컴파일러가 main.cpp 파일을 컴파일할 때는 template class B<int> 와 B<float> 두 개의 인스턴스를 만드는 곳이 있습니다. 이 시점에서 컴파일러는 선언에 대한 정의는 가지고 있지만 정의에 대한 정보는 가지고 있지 않기 때문에 컴파일 에러가 발생됩니다.

클래스 템플릿의 멤버 함수를 구현할 때도, template 키워드를 정의의 선두에 적어줘야합니다. 다음은 클래스 템플릿 Stack 의 완전한 구현 코드입니다. :

//stack.h
#pragma once
template<class T>
class Stack
{
public:
Stack(int = 10);
~Stack() { delete [] stackPtr;
  }
int push(const
  T&); // 스택에 원소를 집어 넣는다.
int pop(T&); //
  스택에서 원소를 꺼낸다.
int isEmpty() const
  { return top == -1; }
int isFull() const
  { return top == size - 1; }
private:
int size; //
  스택에 있는 원소의 갯수.
int top;
T* stackPtr;
};

// 기본 크기 10 으로 스택을 생성하는 생성자.
template<class
  T>
Stack<T>::Stack(int s)
{
size = (s > 0 && s < 1000) ? s : 10 ;
top = -1 ; // 스택을 초기화한다.
stackPtr = new T[size]
  ;
}

// 스택에 원소를 집어 넣는다.
template<class
  T>
int Stack<T>::push(const T& item)
{
if(!isFull())
{
stackPtr[++top] = item ;
return
  1 ; // push 성공
}
return 0 ; //
  push 실패
}

// 스택에서 원소를 꺼낸다.
template<class
  T>
int Stack<T>::pop(T& popValue)
{
if(!isEmpty())
{
popValue = stackPtr[top--] ;
return
  1 ; // pop 성공
}
return 0 ; //
  pop 실패
}

클래스 템플릿의 사용 ▲

클래스 템플릿을 사용하는 것은 아주 쉬운 일입니다. 타입 인자에 실제 타입을 주어 필요한 클래스를 만듭니다. 이 과정을 흔히 "클래스의 인스턴스와" 라고 합니다. 다음은 위에서 만든 Stack 클래스 템플릿을 사용하는 간단한 예제입니다.

#include <iostream>
#include "stack.h"
using namespace std ;
void main()
{
typedef Stack<float>
  FloatStack ;
typedef Stack<int>
  IntStack ;

FloatStack fs(5) ;
float f = 1.1 ;
cout << "Pushing elements onto fs" <<
  endl ;
while(fs.push(f))
{
cout << f << ' '
  ;
f += 1.1 ;
}
cout << endl << "Stack Full."
  << endl
<< endl <<
  "Popping elements from fs" << endl ;

while(fs.pop(f))
cout << f << ' '
  ;
cout << endl << "Stack Empty"
  << endl ;
cout << endl ;

IntStack is ;
int i = 1.1 ;
cout << "Pushing elements onto is" <<
  endl ;
while(is.push(i))
{
cout << i << ' '
  ;
i += 1 ;
}
cout << endl << "Stack Full" <<
  endl
<< endl <<
  "Popping elements from is" << endl ;

while(is.pop(i))
cout << i << ' '
  ;
cout << endl << "Stack Empty"
  << endl ;
}

실행 결과

Pushing elements onto fs
1.1 2.2 3.3 4.4 5.5
Stack Full.

Popping elements from fs
5.5 4.4 3.3 2.2 1.1
Stack Empty

Pushing elements onto is
1 2 3 4 5 6 7 8 9 10
Stack Full

Popping elements from is
10 9 8 7 6 5 4 3 2 1
Stack Empty

위의 예제에서 클래스 템플릿을 만들고, 예제 프로그램에서는 float 형 Stack 과 int 형 Stack 로 인스턴스화 했습니다. 템플릿 클래스가 인스턴스화 되면 그 타입의 객체를 여러개 만들어서 사용할 수 있습니다.( 예 fs 와 is )

템플릿 클래스를 인스턴스화 할 때는 typedef 를 사용하세요. 그러면 프로그램 전체에 걸쳐, typedef 로 만든 이름을 사용할 수 있습니다. 다음은 typedef 를 사용할 때 얻을 수 있는 두 가지 잇점입니다.

  • typedef 는 "템플릿의 템플릿" 을 사용할 때 아주 유용합니다. 예를 들어, int 형의 STL vector 를 인스턴스화 할 때 다음과 같이 사용할 수 있습니다. :
typedef vector<int, allocator<int> > INTVECTOR ;
  • 템플릿 정의가 바뀌면, 간단히 typedef 정의만 바꿔주면 됩니다. 예를 들어, 현재 STL vector 는 두번째 인자가 필요합니다.
typedef vector<int, allocator<int> > INTVECTOR ;
INTVECTOR vi1;

하지만 새로운 버전에서는 두번째 인자가 필요하지 않을 것입니다.(현재 이렇게 되었습니다.) 그래서 다음과 같이 수정할 수 있습니다.

typedef vector<int> INTVECTOR ;
INTVECTOR vi1;

typedef 가 없다면 얼마나 많은 변경을 해줘야 하는지 생각해 보세요~ :)

함수 템플릿 ▲

모든 타입에 대해서 똑같은 연산을 하려 할 때는 함수 템플릿을 사용하는 것이 좋습니다. 템플릿 함수 하나만 만들어 놓고, 타입을 인자로 주어 함수 템플릿을 호출하면, 컴파일러가 알아서 해당 타입에 맞게 함수의 코드를 만들어 줍니다. 그리곤 실행합니다. STL 알고리즘은 모두 함수 템플릿으로 만들어진 것입니다.

함수 템플릿의 구현 ▲

함수 템플릿은 일반 함수처럼 구현합니다. 단, template 키워드가 선두에 와야한다는 점만 다릅니다. 다음은 함수 템플릿의 예제입니다.

#include <iostream>
using namespace std ;
// 두 값 중 최대값을 반환한다.
template<class
  T>
T max(T a, T b)
{
return a > b ? a : b
  ;
}

함수 템플릿의 사용 ▲

함수 템플릿을 사용하는 것은 아주 쉽습니다. 일반 함수를 사용하는 것과 똑같습니다. max(10, 15) 와 같은 함수 템플릿의 인스턴스를 컴파일러가 보게 되면 컴파일러는 함수 max(int, int) 를 알아서 생성합니다. 이처럼 아래의 예제에서 컴파일러는 max(char, char) 와 max(float, float) 을 만들어 줍니다.

#include <iostream>
using namespace std ;
// 두 값 중 최대값을 반환한다.
template<class
  T>
T max(T a, T b)
{
return a > b ? a : b
  ;
}

void main()
{
cout << "max(10, 15) = " << max(10,
  15) << endl ;
cout << "max('k', 's') = " <<
  max('k', 's') << endl ;
cout << "max(10.1, 15.2) = " <<
  max(10.1, 15.2) << endl ;
}

실행 결과

max(10, 15) = 15
max('k', 's') = s
max(10.1, 15.2) = 15.2

템플릿의 인스턴스화 ▲

컴파일러가 템플릿으로부터 클래스, 함수, 정적 데이타 멤버를 생성하는 것을 "템플릿의 인스턴스화"라고 합니다.

  • 클래스 템플릿에서 클래스를 생성하는 것을 클래스 생성이라고 합니다.
  • 함수 템플릿에서 함수를 생성하는 것을 함수 생성이라고 합니다.
  • 템플릿의 정적 데이타 멤버에서 정적 데이타 멤버를 생성하는 것을 정적 데이타 멤버 생성이라고 합니다.

컴파일러는 암시적인 인스턴스화를 보았을 때 템플릿으로부터 클래스, 함수, 정적 데이타 멤버를 생성합니다.

  1. 아래의 예제는 클래스 템플릿의 암시적인 인스턴스화를 보여줍니다.

    2. template<class T>
    class Z
    {
    public:
    Z(){}
    ~Z(){}
    void f(){}
    void g(){}
    } ;

    int main()
    {
    Z<int> zi; // 암시적인 인스턴스화로 클래스 Z<int> 를 생성한다.
    Z<float> zf; // 암시적인 인스턴스화로 클래스 Z<float> 를 생성한다.
    return 0 ;
    }

  2. 아래의 예제는 클래스 템플릿의 멤버 함수인 Z<T>::f() 와 Z<T>::g() 를 사용하는 것입니다.

    template<class
            T>
class Z
{
public:
Z(){}
~Z(){}
void f(){}
void g(){}
} ;

    int main()
    {
    Z<int> zi ;// 암시적인 인스턴스화로 클래스 Z<int> 를 생성한다.
    zi.f() ; // 그리고 함수 Z<int>::f() 를 생성한다.
    Z<float> zf ; // 암시적인 인스턴스화로 클래스 Z<float> 를 생성한다.
    zf.g() ; //그리고 z<float>::g() 를 생성한다.
    return 0 ;
    }

  3. 아래의 예제는 클래스 템플릿의 명시적인 인스턴스화를 보여줍니다.

    template<class
            T>
class Z
{
public:
Z(){}
~Z(){}
void f(){}
void g(){}
} ;

    int main()
    {
    template class Z<int> ; // 클래스 Z<int> 의 명시적인 인스턴스화.
    template class Z<float> ; // 클래스 Z<float> 의 명시적인 인스턴스화.
    return 0 ;
    }

  4. 아래의 예제에서는 컴파일러가 어떤 클래스를 생성할까요? 대답은 "아무것도 생성하지 않는다"입니다.

    template<class
            T>
class Z
{
public:
Z(){}
~Z(){}
void f(){}
void g(){}
} ;

    int main()
    {
    Z<int>* p_zi; // 클래스 Z<int> 의 인스턴스화가 필요하지 않다.
    Z<float>* p_zf; // 클래스 Z<float> 의 인스턴스화가 필요하지 않다.
    return 0 ;
    }

  5. 아래의 예제는 함수 템플릿의 암시적인 인스턴스화를 보여줍니다.

    // 두 값 중 최대값을 반환한다.
template<class
            T>
T max(T a, T b)
{
return a >
            b ? a : b ;
}
void main()
{
int I ;
I = max(10, 15)
            ; // max(int, int) 의 암시적인 인스턴스화.
char c ;
c = max('k',
            's') ; // max(char, char) 의 암시적인 인스턴스화.
}

    컴파일러가 max(int, int) 와 max(char, char) 함수를 생성할 때는 함수 템플릿 max 의 정의를 사용해서 생성합니다.

  6. 아래의 예제는 함수 템플릿의 명시적인 인스턴스화를 보여줍니다.

    template<class T>
    void Test(T r_t)
    {
    }

    int main()
{
//
            Test(int) 의 명시적인 인스턴스화.
template void
            Test<int>(int)
            ;
return 0 ;
}

    주의 : Visual C++ 5.0 은 위의 문법을 지원하지 않습니다. 위의 예제를 컴파일하면 컴파일에러 C1001 이 발생합니다. Visual C++ 7.0 에서는 C2252 컴파일에러가 발생합니다.

    컴파일러가 Test(int) 함수를 생성할 때는 함수 템플릿 Test 의 정의를 사용해서 생성합니다.

  7. 클래스 템플릿의 인스턴스화를 할 때 해당 템플릿의 선언은 있지만 정의가 없는 것은 프로그램을 잘 못 작성한 것입니다. VC++5.0 와 VC++7.0 은 컴파일에러 C2079 를 발생합니다.

    template<class T> class X ;

    int main()
    {
    X<int> xi ; // 에러 C2079 : 'xi' 는 정의되지 않는 클래스 'X<int>' 를 사용합니다.
    return 0 ;
    }

  8. 클래스 템플릿의 가상 멤버 함수는 인스턴스화가 필요하지 않아도 수행됩니다. 아래의 예제를 보면 가상 함수 X<T>::Test() 는 필요하지 않지만 VC5.0 은 X<T>::Test 의 정의를 생성합니다.

    template<class T>
    class X
    {
    public:
    virtual void Test() {}
    };

    int main()
    {
    X<int> xi ; // X<int> 의 암시적인 인스턴스화
    return 0 ;
    }

    X<int>::Test 가 필요 없지만 컴파일러는 Test 를 생성합니다.

클래스 템플릿 스페셜라이제이션 ▲

생성된 템플릿 코드 대신에 특정 타입으로 정의한 템플릿 코드를 제공해서 사용하게 할 수 있습니다. 이 것을 템플릿 스페셜라이제이션(특정화)이라고 합니다. 다음 예제는 템플릿 스트림(stream) 클래스의 클래스 템플릿 스페셜라이제이션을 정의하는 예제입니다.

#include <iostream>
using namespace std ;

template<class
  T>
class stream
{
public:
void f() { cout <<
  "stream<T>::f()"<< endl ;}
};

template <>
class stream<char>
{
public:
void f() { cout <<
  "stream<char>::f()"<< endl ;}
};

int main()
{
stream<int>
  si ;
stream<char>
  sc ;

si.f() ;
sc.f() ;

return 0 ;
}

실행 결과

stream<T>::f()
stream<char>::f()

위의 예제에서 stream<char> 는 char 형을 위한 스트림(stream)을 정의하는데 사용되었습니다. 다른 스트림(stream)들은 클래스 템플릿으로 만들어진 코드로 처리됩니다.

클래스 템플릿의 부분 스페셜라이제이션 ▲

때로는 클래스의 인자 중 단 하나만 스페셜라이제이션을 할 경우가 있습니다. 예를 들면,

// 기본 템플릿 클래스
template<typename T1, typename T2>
class X
{
} ;

// 부분 스페셜라이제이션
template<typename T1>
class X<T1, int>
{
} ; //C2989 에러

int main()
{
// 기본 템플릿을 사용해서 인스턴스를 생성한다.
X<char, char> xcc ;

// 부분 스페셜라이제이션 템플릿을 사용해서 인스턴스를 생성한다.
X<char, int> xii ;

return 0 ;
}

부분 스페셜라이제이션의 템플릿 인자를 실제 템플릿 인자 목록으로부터 추론할 수 있다면 부분 스페셜라이제이션은 주어진 실제 템플릿 인자 목록과 연결됩니다.

주의 : Visual C++ 5.0 은 템플릿 클래스의 부분 스페셜라이제이션을 지원하지 않습니다. 위의 예제는 컴파일에러 C2989 가 발생합니다. C2989 는 템플릿 클래스가 이미 템플릿이 아닌 클래스로 정의 되어 있다는 에러 메세지입니다.

함수 템플릿의 스페셜라이제이션 ▲

생성된 템플릿 코드 대신에 특정 타입으로 정의한 템플릿 코드를 제공해서 사용하게 할 수 있습니다. 이 것을 템플릿 스페셜라이제이션(특정화)이라고 합니다. 다음 예제는 함수 템플릿의 스페셜라이제이션을 보여줍니다.

#include <iostream>
using namespace std ;

// max 함수는 T 타입인 두 값 중 최대값을 반환합니다.
// 이 때 T 타입은 > 연산자가 정의되어 있는 클래스나 데이타 형이 될 수 있습니다.
template<class
  T>
T max(T a, T b)
{
return a > b ? a : b
  ;
}

int main()
{
cout << "max(10,
  15) = " << max(10, 15) << endl ;
cout << "max('k',
  's') = " << max('k', 's') << endl ;
cout << "max(10.1,
  15.2) = " << max(10.1, 15.2) << endl ;
cout << "max(\"Aladdin\",
  \"Jasmine\") = " << max("Aladdin", "Jasmine")
  << endl ;
return 0 ;
}

실행 결과

max(10, 15) = 15
max('k', 's') = s
max(10.1, 15.2) = 15.2
max("Aladdin", "Jasmine") = Aladdin

실행 결과가 올바르게 나오질 않는군요! 왜 그렇죠? max("Aladdin", "Jasmine") 함수를 호출하면 컴파일러는 max(char*, char*) 코드를 생성합니다. 그럼 max 함수는 문자열의 주소의 크기를 비교하게 됩니다! 이런 경우를 위해서 특정 타입에 알맞은 구현을 해야합니다. 이 때 템플릿 스페셜라이제이션을 사요할 수 있습니다. 위의 예제는 스페셜라이제이션을 사용해서 아래처럼 다시 만들 수 있습니다.:

#include <iostream>
using namespace std ;

// max 함수는 두 값 중 최대값을 반환합니다.
template<class
  T>
T max(T a, T b)
{
return a > b ? a : b
  ;
}

// max 를 char * 에 대해서 스페셜라이제이션을 합니다.
template <>
char* max(char* a,
  char* b)
{
return strcmp(a, b) >
  0 ? a : b ;
}

int main()
{
cout << "max(10,
  15) = " << max(10, 15) << endl ;
cout << "max('k',
  's') = " << max('k', 's') << endl ;
cout << "max(10.1,
  15.2) = " << max(10.1, 15.2) << endl ;
cout << "max(\"Aladdin\",
  \"Jasmine\") = " << max("Aladdin", "Jasmine")
  << endl ;
return 0 ;
}

실행 결과

max(10, 15) = 15
max('k', 's') = s
max(10.1, 15.2) = 15.2
max("Aladdin", "Jasmine") = Jasmine

템플릿 인자 ▲

  1. C++ 템플릿을 사용해서 타입 인자 T 를 갖는 제네릭한 Queue<T> 템플릿을 만들 수 있습니다. 여기서 T 는 실제 타입으로 바뀝니다. 예를 들어, Queue<Customers> 라고 하면 C++ 은 클래스 Queue<Customers> 를 생성합니다.

    template <class T>
    class Stack
    {
    } ;

    위의 예에서 T 가 템플릿 인자입니다. 때로는 타입 인자로도 불립니다.

  2. C++ 은 기본 템플릿 인자를 정할 수 있습니다. 그래서 정의를 다음과 같이 할 수 있습니다. :

    template <class T = float, int elements = 100> Stack { ....} ;

    위처럼 정의하면 인스턴스 선언은 다음처럼 할 수 있습니다.

    Stack<> mostRecentSalesFigures ;

    컴파일을 할 때 mostRecentSalesFigures 이름의 템플릿 클래스는 float 형 원소 100 개를 갖는 스택으로 만들어집니다. 이 스택의 템플릿 클래스는 Stack<float, 100> 형이 될 것입니다.

    주의, C++ 은 비타입 템플릿 인자도 허용합니다. 템플릿 클래스 Stack 은 int 를 비타입 인자로 사용한 것입니다.

    어떤 특정 인자로 기본 템플릿 인자를 정하는 것은 함수의 기본 인자를 정하는 것과 똑 같습니다. 기본 인자가 선언되면 그 후에 나오는 모든 인자들도 기본값을 가져야합니다.

  3. 스페셜라이제이션의 선언 또는 정의에서는 기본 인자를 설정할 수 없습니다. 예를 들면,

    template <class T, int size>
    class Stack
    {
    } ;

    // 에러 C2989 : 'Stack<int, 10>' 템플릿 클래스는 이미 비템플릿 클래스로
    // 정의되어 있습니다.
    template <class T, int size = 10>
    class Stack<int, 10>
    {
    } ;

    int main()
{
Stack<float,10>
            si ;
return 0 ;
}

  4. 타입 인자는 템플릿 선언 범위 내에서 유일한 타입 이름으로 정의됩니다. 그래서 같은 범위 내에서 재선언할 수 없습니다. 예를 들면,

    template <class T, int size>
    class Stack
    {
    int T ; //에러 타입 인자가 재정의 되었다.
    void f()
    {
    char T ; //에러 타입 인자가 재정의 되었다.
    }
    } ;

    class A {} ;
int main()
{
Stack<A,10>
            si ;
return 0 ;
}

    주의 : VC++5.0 또는 SP1 가 설치된 컴파일러는 위의 예제에 대해서 아무런 에러도 보고하지 않습니다. 이 것은 타입 인자의 재정의를 에러로 정하지 않아서 입니다.

  5. 비타입 인자의 값은 할당 되거나 변경할 수 없습니다.

    template <class T, int size>
    class Stack
    {
    void f()
    {
    //에러 C2105: '++' 은 l-value 가 필요합니다.
    size++ ; //error change of template argument value //템플릿 인자의 값을 변경하는 것은 에러입니다.
    }
    } ;

     int main()
{
Stack<double,10>
            si ;
return 0 ;
}

  6. 인자의 선언 또는 타입 인자로 해석될 수 있는 템플릿 인자는 타입 인자로 받아들여집니다.

    class T {} ;
    int i ;

    template <class T, T i>
    void f(T t)
    {
    T t1 = i ; // 템플릿 인자 T 와 i
    ::T t2 = ::i ; // 전역 개체 T 와 i
    }

    int main()
{
f('s') ; //여기서
            C2783 에러 발생
return 0 ;
}

    위의 예제를 VC++5.0 과 SP1 에서 컴파일을 하면 컴파일 에러 C2783(i 를 템플릿 인자로 볼 수 없다.)이 발생합니다. 이 문제를 해결하려면 f('s') 를 f<char, 's'>('s') 로 호출하면 됩니다.

    class T {} ;
    int i ;

    template <class T, T i>
    void f(T t)
    {
    T t1 = i ; // 템플릿 인자 T 와 i
    ::T t2 = ::i ; // 전역 개체 T 와 i
    }

    int main()
    {
    f<char, 's'>('s') ; //문제 해결
    return 0 ;
    }

  7. 비타입 템플릿 인자는 부동소수점 형이 될 수 없습니다. 예를 들면,

    template <double d> class X ; // 'xd' 는 정의되지 않은 클래스 'X<1.e66>' 을 사용했습니다.
    //template <double* pd> class X ; //ok
    //template <double& rd> class X ; //ok

    int main()
{
X<1.0>
            xd ;
return 0 ;
}

정적 멤버와 변수 ▲

  1. 정적 변수나 정적 멤버 함수를 가지고 있는 템플릿으로부터 생성된 클래스나 함수는 각각 자기만의 정적 변수 또는 정적 멤버 함수를 가지게 됩니다.

  2. 함수의 정의 부분에 정적 변수를 가지고 있는 함수 템플릿이 인스턴스화 되면 인스턴스만의 정적 변수를 가지게 됩니다. 예를 들어,

    template <class T>
    class X
    {
    public:
    static T s ;
    } ;

    int main()
    {
    X<int> xi ;
    X<char*> xc ;
    }

    위의 예에서 X<int> 는 int 형의 정적 데이타 멤버 s 를 갖게 되고 X<char*> 는 char* 형의 정적 데이타 멤버 s 를 갖게 됩니다.

  3. 정적 멤버는 아래처럼 정의합니다.

    #include <iostream>
    using namespace std ;

    template <class T>
    class X
    {
    public:
    static T s ;
    } ;

    template <class T> T X<T>::s = 0 ;
    template <> int X<int>::s = 3 ;
    template <> char* X<char*>::s = "Hello" ;

    int main()
    {
    X<int> xi ;
    cout << "xi.s = " << xi.s << endl ;

         X<char*>
            xc ;
cout <<
            "xc.s = " << xc.s << endl ;

return 0 ;
}

    실행 결과

    xi.s = 10
    xc.s = Hello

  4. 각각의 함수 템플릿 인스턴스는 자신만의 정적 변수를 갖습니다. 예를 들어,

#include <iostream>
using namespace std ;

template <class T>
void f(T t)
{
static T s = 0;
s = t ;
cout << "s = " << s << endl ;
}

int main()
{
f(10) ;
f("Hello") ;

return 0 ;
}

실행 결과

s = 10
s = Hello

위의 예제에서 f<int>(int) 는 int 형의 정적 변수 s 를 갖게 되고 f<char*>(char*) 는 char* 형의 정적 변수 s 를 갖게 됩니다.

템플릿과 프렌드(friend) ▲

프렌드 관계는 클래스 템플릿과 전역 함수, 다른 클래스의 멤버 함수(템플릿 클래스도 가능) 아니면 클래스 전체와(템플릿 클래스도 가능) 맺어질 수 있습니다. 아래의 표는 서로 다른 종류의 클래스의 프렌드 선언을 나타낸 것입니다.

클래스 템플릿 | 클래스 템플릿 X 내에서 프렌드 선언 | 프렌드 관계 설정의 결과
template class <T> class X | friend void f1() ; | f1() 는 템플릿 X 의 모든 인스턴스오와 프렌드가 됩니다. 예를 들면, f1() 은 X<int>, X<A>그리고 X<Y>...와 프렌드입니다.
template class <T> class X | friend void f2(X<T>&) ; | 특정 타입인 T 가 예를 들어 float 이라면 makes f2(X<float>&) 은 오로지 class X<float> 와만 프렌드가 될 수 있습니다. f2(x<float>&) 는 class X<A> 와 프렌드가 될 수 없습니다.
template class <T> class X | friend A::f4() ; // A 는 사용자가 정의한 클래스로 멤버함수 f4(); 를 가지고 있습니다. | A::f4() 는 템플릿 X 의 모든 인스턴스와 프렌드가 됩니다. 예를 들면, A::f4() 는 X<int>, X<A>, 그리고 X<Y> ... 와 프렌드입니다.
template class <T> class X | friend C<T>::f5(X<T>&) ; // C 는 클래스 템플릿으로 멤버 함수 f5 를 가지고 있습니다. | 특정 타입인 T 가 예를 들어 float 이라면 C<float>::f5(X<float>&) 는 오로지 class X<float> 와만 프렌드가 될 수 있습니다. C<float>::f5(x<float>&) 는 class X<A> 와 프렌드가 될 수 없습니다.
template class <T> class X | friend class Y ; | 클래스 Y 의 모든 멤버 함수는 클래스 템플릿 X 로부터 생성되는 모든 템플릿 클래스와 플렌드입니다.
template class <T> class X | friend class Z<T> ; | 템플릿 클래스가 float 과 같은 특정 타입으로 인스턴스화 되면 class Z<float> 의 모든 멤버는 템플릿 클래스 X<float> 과 프렌드가 됩니다.

번역 : 김성철 ( paranwave@korea.com )


Inside the VCL

From Message to Event Part 1

by Damon Chandler

원문 : http://bcbcaq.bytamin-c.com/MessagesToEvents.html

1. 메시지란 무엇인가?

메세지는 윈도우즈가 어플리케이션에게 어떤 사건의 발생 여부를 알려주는 일종의 통지다. 마우스 클릭이나 윈도우의 크기 변경, 키보드의 키 눌림 같은 사건이 일어나면 윈도우즈는 어플리케이션에게 메세지를 보내 어떤 사건이 일어났는지를 알려준다. 윈도우즈는 이벤트 기반의 운영체제로 메세지를 중심으로 프레임웤(API)을 구성한다. 따라서 VCL 이건 MFC 이건간에 윈도우즈와 관련된 개발 프레임웤은 메세지 처리, 이벤트 처리를 중심으로 설계된다. 그러므로 VCL 을 처음 배우는 사람은 VCL 이 메세지와 이벤트를 내부적으로 어떻게 처리하는지 반드시 알아야한다.

2. 이벤트 어플리케이션

아래 목록 1.0 에 있는 코드는 “Hello, World” 를 출력하는 간단한 콘솔프로그램이다.

 목록 1.0

int main(int argc, char *argv[])
{
std::cout << "Hello World!" <<
  std::endl;
return 0;
}

이 프로그램은 단순히 문자를 출력하고 끝난다. 프로그램 내부에서 어떤 이벤트도 일어나지 않는다. 그렇다면 다음 프로그램을 살펴보자.

목록 1.1

int main(int argc, char* argv[])
{
while (getch() != 'Q')
{
std::cout << "Hello
  World!" << std::endl;
}
return 0;
}

목록 1.1 에 있는 프로그램은 ‘Q’ 문자가 입력 될 때까지 while 루프를 실행하며 “Hello World” 문자열을 출력한다. 여기에서 이벤트 프로그래밍의 아주 중요한 개념을 볼 수 있다. 즉 ‘Q’ 문자가 입력되는 것은 사건이고 이로 인해 발생되는 이벤트는 프로그램의 종료다. 따라서, 이벤트 프로그래밍은 행동과 반응을 나타내는 것이다. 윈도우즈를 예로 들어보면, 태스크바에 있는 ‘시작’ 버튼을 누르면 시작메뉴가 나타난다. 이 것은 시작 버튼을 누르는 행동과 시작메뉴를 보여주는 반응으로 이뤄진 것이다.

이제 윈도우즈 어플리케이션 개발 관점에서 생각해 보자.

목록 1.2

WINAPI APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR
  lpCmdLine, int nCmdShow)
{
//
// <윈도우(창) 생성 코드> ...
//

MSG Msg;
while (GetMessage(&Msg, NULL, 0, 0))
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
return 0;
}

윈도우즈 어플리케이션의 시작점(Entry Point)은 WinMain() 이란 함수다. 콘솔 프로그램의 main() 함수라고 생각하면 된다. 목록 1.2 는 WinMain() 함수의 구조를 보여주는데 자세히 보면 목록 1.1 의 main() 함수 구조와 매우 비슷하다는 것을 알 수 있다. 목록 1.1 에서 while 루프가 사용자로부터 ‘Q’ 문자를 입력 받는 것처럼, 목록 1.2 의 while 루프는 윈도우즈의 메세지큐에서 메세지를 가져온다. 이 메세지들은 키보드, 마우스, 기타 장치에서 발생한다. 목록 1.1 의 getch() 함수는 키보드버퍼에서 문자를 가져오는데 이와 비슷하게 목록 1.2 의 GetMessage() 함수는 메세지 큐에서 메세지를 가져 온다. 이 때 목록 1.1 에서 getch() 함수가 가져온 것이 ‘Q’ 문자가 아니면 조건식이 참(TRUE)이 되어 while 루프가 실행되듯이 목록 1.2 의 GetMessage() 함수가 가져온 것이 WM_QUIT 메세지가 아니면 GetMessage() 함수가 참(TRUE)을 반환해서 while 루프가 실행된다.

GetMessage() 함수가 가져오는 값은 메세지의 정보를 담고 있는 MSG 구조체이다. 이 구조체는 발생한 사건의 종류와 그 사건을 설명하는 추가적인 정보를 담고 있다. 예를 들어서, 마우스 클릭에 관한 MSG 구조체는 버튼이 눌렸을 때의 마우스 위치를 담고 있다. 아래는 MSG 구조체를 나타낸 것이다.

typedef struct tagMSG {
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
} MSG;

MSG 구조체의 각 데이타 멤버를 보면, hwnd 는 메세지를 받는 윈도우의 32비트 핸들이다. 윈도우는 폼만 말하는 것이 아니고 Win32 환경의 비주얼 객체들(폼, 대화상자, 버튼, 텍스트 상자 등)을 말한다. 이 비주얼 객체들은 모두 윈도우 핸들을 가지고 있다. message 는 메세지의 종류를 나타내는 상수값이다(WM_QUIT). 이 message 에 사용자가 만든 메세지 값을 설정할 수도 있다. WParam 은 메세지에 관련된 추가적인 정보를 담은 32비트값으로 주로 상수값으로 사용되지만, 메세지에 관련된 윈도우 핸들이나 식별 번호를 담기도 한다. LParam 도 역시 메세지에 관련된 추가적인 정보를 담은 32비트값으로, 메모리의 특정 데이타를 가리키는 포인터나 배열의 색인을 담는데 주로 사용된다. WParam 과 LParam 그리고 포인터는 모두 32비트 크기(32 비트 OS)이기 때문에 적절히 형 변환을 하면 서로 값을 교환할 수 있다. time 은 메세지가 발생한 시간이고 pt 는 메세지가 발생했을 때 마우스 커서가 있던 위치 좌표를 담고 있다.

3. 메세지 처리의 이해

메세지를 쉽게 이해하기 위해서 그림 1.1 의 폼을 만들어보자. 이 폼은 TEdit 와 TButton 으로 구성되어 있다.

목록 1.3

//----------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//----------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"

TForm1 *Form1;

//----------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//----------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject* Sender)
{
Edit1->Text = "Hello World!";
}
//----------------------------------------------------------------------

위의 목록 1.3 은 그림 1.1 의 어플리케이션을 구현한 코드다. 이 코드는 단순하게 TButton::OnClick 이벤트 코드만 있는데 이 이벤트는 TEdit 의 Text 속성을 “Hello World!” 라고 설정한다. 코드를 보면 알겠지만 버튼을 클릭하면 “Hello World!” 문자열이 에디트 박스에 출력된다. 이것은 버튼을 클릭하는 행동과 에디트 박스의 문자열이 바뀌는 반응으로 행동/반응의 예로 생각할 수 있다. 이 TButton::OnClick() 함수를 이벤트 핸들러라고 한다. 이 프로그램을 통해서 VCL 의 추상화 수준을 엿볼 수 있다. 코드를 보면 알다시피 버튼에 WM_LBUTTONDOWN 메세지를 전달한 적도 없고 에디트 박스에 WM_SETTEXT 메세지를 보낸 적도 없다. 하지만 모든 것이 잘 작동된다. 그럼 VCL 은 이것을 어떻게 하는 것일까? 즉, 내부적으로 어떻게 메세지를 주고 받는 것일까? 아래의 목록 1.4 는 이 질문에 대한 해답을 보여준다.

 목록 1.4

//----------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
//----------------------------------------------------------------------
USEFORM("Unit1.cpp", Form1);
USERES("Project1.res");
//----------------------------------------------------------------------
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
try
{
Application->Initialize();
Application->CreateForm(__classid(TForm1),
  &Form1);
Application->Run();
}
catch (Exception &exception)
{
Application->ShowException(&exception);
}
return 0;
}
//----------------------------------------------------------------------

목록 1.4 는 우리가 만든 프로젝트 파일의 소스 코드인데 WinMain() 함수를 가지고 있다. 이 WinMain() 함수는 목록 1.2 에서 봤던 WinMain() 함수와 똑같은 것이다. 차이점이 있다면 메세지 루프, 윈도우 생성 그리고 예외 처리 메카니즘을 TApplication 과 TWinControl 로 처리하는 점이다. VCL 을 처음 접해 본 사람들은 VCL 속에 어떤 마법이 있을거라고 생각 하는데 봐서 알겠지만 VCL 어플리케이션도 일반 윈도우즈 어플리케이션과 마찬가지로 윈도우를 생성하고 일반 메세지 루프를 사용한다.

다시 목록 1.3 의 코드로 돌아가서 TWinControl 에서 파생된 윈도우 컨트롤이 어떻게 메세지를 주고 받는지 알아보자. 목록 1.3 에서 버튼의 OnClick 이벤트 핸들러는 에디트 컨트롤의 Text 속성을 설정하는 코드로 되어 있다. API 를 사용해서 같은 내용을 구현하려면 아래의 목록 1.5 처럼 코드를 써 줘야한다.

목록 1.5

void __fastcall TForm1::Button1Click(TObject* Sender)
{
// Edit1->Text = "Hello
  World!";
SendMessage(Edit1->Handle, WM_SETTEXT, 0, reinterpret_cast<LPARAM>("Hello
  World!"));
}

SendMessage() API 함수를 사용해서 WM_SETTEXT 메세지를 에디트 컨트롤에 직접 전달한다. 하지만 이 코드는 TWinControl::Text 속성을 사용한 코드보다 이해하기 어렵고 형에 대한 안전성도 보장되지 않는다. 위의 SendMessage() 함수 구문은 TWinControl::SetText() 멤버 함수에 이미 구현 되어 있다. 이처럼 윈도우 속성을 설정하는 대부분의 경우가 TWinControl 의 멤버 함수로 만들어져 있다. 즉, 위처럼 SendMessage() API 함수를 직접 사용할 일이 거의 없다. 이제 TWinControl 의 Text 속성이 내부적으로 WM_SETTEXT 메세지를 보낸다는 것을 알았지만 어떻게 특정 메세지가 버튼의 OnClick 이벤트 핸들러와 연결되는지는 모른다.

메세지들이 윈도우에 전달 되면 특수 함수인 윈도우 프로시져가 호출된다. 예를 들어서, 목록 1.5 의 SendMessage() API 함수로 에디트 컨트롤에 WM_SETTEXT 메세지를 보내서 에디트 컨트롤의 문자열을 변경했었다. 이런 방식으로, 버튼을 클릭했을 때 버튼의 OnClick 이벤트 핸들러를 호출해서 WM_LBUTTONUP 메세지를 처리하는 것이다. TWinControl 클래스는 마우스 커서의 위치를 테스트하고 Button1Click 멤버 함수를 호출해서 메세지를 처리한다.

마우스의 왼쪽 버튼이 버튼 컨트롤 위에서 눌리면 윈도우즈는 WM_LBUTTONDOWN 메세지를 어플리케이션에게 보내고 어플리케이션의 메세지 루프안에 있는 DispatchMessage() 함수는 버튼의 윈도우 프로시져에게 이 메세지를 전달한다. 비슷하게, 마우스 왼쪽 버튼이 버튼 컨트롤 위에서 놓이면, 버튼 컨트롤의 윈도우 프로시져는 WM_LBUTTONUP 메세지를 받게 된다. 이 두 메세지가 마우스의 “클릭” 이벤트를 만들어내는데 대개 목록 1.6 의 코드처럼 처리된다.

목록 1.6

LRESULT CALLBACK ButtonWndProc(
HWND hwnd, // handle of window
UINT uMsg, // message identifier
WPARAM wParam, // "word" message parameter
LPARAM lParam) // "long" message parameter
{
switch (uMsg)
{
case WM_LBUTTONDOWN:
{
SetCapture(hwnd);
break;
}
case WM_LBUTTONUP:
{
ReleaseCapture();
SendMessage(hwndEdit, WM_SETTEXT, 0, reinterpret_cast<LPARAM>("Hello World!"));
break;
}
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return 0;
}

OnClick 이벤트 핸들러 함수를 사용하면 목록 1.6 의 윈도우 프로시져 코드처럼 지저분하게 코드를 작성하지 않아도 된다. 그리고 또 이벤트 핸들러는 단 하나의 이벤트만을 처리해야할 때, 아주 유용하게 사용할 수 있다.

목록 1.3 의 예제 프로그램에서 TButton 컨트롤에 포커스를 주고 오브젝트 인스펙터를 통해서 OnClick 이벤트를 만들어 보았다.

WM_LBUTTONUP 메세지가 OnClick 이벤트를 발생시키는 것을 이미 알고 있으므로, OnEnter 와 OnExit 이벤트를 알아보자. 윈도우 컨트롤이 키보드 또는 입력 장치의 포커스를 받았을 때, 윈도우즈 운영제체는 그 컨트롤의 윈도우 프로시져에 WM_SETFOCUS 메세지를 보낸다. 예제의 TButton 컨트롤을 선택하면 점선사각형이 버튼의 둘레에 그려지는데 이 것은 버튼이 현재 키보드 포커스를 가지고 있고 WM_SETFOCUS 메세지를 처리했다는 뜻이다. 버튼이 포커스를 잃었을 때 점선 사각형은 지워지게 되는데 이 것은 WM_KILLFOCUS 메세지가 처리됐다는 뜻이다. TWinControl 에서 파생된 클래스의 OnExit 와 OnEnter 이벤트 핸들러는 WM_KILLFOCUS 그리고 WM_SETFOCUS 메세지를 받았을 때 호출된다.

윈도우 프로시져는 비 멤버 함수로 윈도우 컨트롤에 전달된 모든 메세지들을 처리한다. 이 함수는 클래스의 멤버 함수(static 함수가 아닐 경우에)가 될 수 없는데, MainWndProc() 과 WndProc() 함수는 신기하게도 TWinControl 클래스의 멤버 함수로 되어 있다. VCL 은 이 마술을 부리는데 forms.pas 유닛에 정의되어 있는 MakeObjectInstance() 함수를 사용한다. 이 함수는 내부적으로 VirtualAlloc() API 함수를 사용해서 멤버 함수가 아닌 윈도우 프로시져를 TWinControl::MainWndProc() 멤버 함수와 연결시킨다. MainWndProc() 멤버 함수는 내부에서 WndProc() 멤버함수를 호출하고 이때 발생하는 모든 예외들은 TApplication::HandleException() 멤버 함수에 전달한다.

이런 점들을 통해서, VCL 이 C 언어 형식의 윈도우 메세지 처리 시스템을 객체지향 프레임웤으로 만들려는 시도를 엿볼 수 있다. 아래의 목록 1.7 코드는 오브젝트 파스칼로 된 TWinControl::MainWndProc() 멤버 함수를 C++ 언어로 간략하게 표현해 본 것이다.

목록 1.7

void __fastcall TWinControl::MainWndProc(TMessage& Message)
{
try
{
try
{
WndProc(Message);
}
catch (...)
{
//
// 자원
  해제, 메모리 반환과 같은 정리 루틴...
//
}

//
// 자원 해제, 메모리 반환과 같은 정리 루틴...
//
}
catch (...)
{
Application->HandleException(this);
}
}

MainWndProc() 멤버 함수는 VCL 의 TMessage 구조체를 사용해서 메세지를 전달한다. 이 구조체는 API 의 MSG 구조체와 비슷하지만, hwnd 와 time 그리고 pt 데이타 멤버가 없다.

#pragma pack(push, 1)
struct TMessage
{
unsigned long Msg;
union
{
struct
{
WORD
  WParamLo;
WORD
  WParamHi;
WORD
  LParamLo;
WORD
  LParamHi;
WORD
  ResultLo;
WORD
  ResultHi;
};
struct
{
long
  WParam;
long
  LParam;
long
  Result;
};
};
};
#pragma pack(pop)

TMessage 구조체 주위에 #pragma pack 이란 컴파일러 지시자를 사용했는데 이 것은 이 구조체를 1 바이트로 정렬하는 것이다. 즉, TMessage 구조체의 데이타 멤버와 MSG 구조체의 데이타 멤버가 서로 맞물려질 수 있도록 하는 것이다.

TWinControl::WndProc() 멤버 함수는 받은 메세지의 대부분을 선조 클래스의 기본 메세지 핸들러로 보낸다. TButton 을 예로 들면, VCL 로 구현한 이벤트는 별로 없고 대부분 표준 BUTTON 컨트롤의 이벤트 기능을 이용하는 것이다. 목록 1.8 의 코드는 TWinControl::WndProc() 함수를 C++ 언어로 표현해 본 것이다.

목록 1.8

void __fastcall TWinControl::WndProc(TMessage& Message)
{
switch (Message.Msg)
{
//
  ...
case //
  대부분의 마우스 메세지...
{
TWMMouse
  WMMouse;
WMMouse.Pos
  = PointToSmallPoint(Point(Message.WParamLo, Message.WParamHi));
WMMouse.XPos
  = Message.WParamLo;
WMMouse.YPos
  = Message.WParamHi;

if (IsControlMouseMsg(WMMouse))
{
if
  (WMMouse.Result == 0)
DefWndProc(Handle,
  Message.Msg, Message.WParam, Message.LParam);
return;
}
break;
}
case
  // 대부분의 키보드 메세지...
{
if (Dragging)
  return;
break;
}
//
  ...
}
TControl::WndProc(Message);
}

TWinControl::WndProc() 멤버 함수가 여러 기능을 처리하는 것처럼 보이지만 오로지 메세지만 처리한다. 주의할 점은 WM_MOUSEMOVE 부터 WM_MBUTTONDBLCLK 까지의 마우스 메세지를 윈도우 형식이 아닌 컨트롤을 위해서 특별하게 처리하는 것이다.

TWinControl::WndProc() 함수에서 마지막으로 호출하는 함수는 TControl::WndProc() 멤버 함수다. 이 함수는 TWinControl::WndProc() 함수에서 처리되지 않은 모든 메세지들을 처리한다. 목록 1.9 는 TControl::WndProc() 멤버 함수를 간략하게 나타낸 것이다.

목록 1.9

void __fastcall TControl::WndProc(TMessage& Message)
{
//
// (1) handle design-time mouse messages...디지인 타임때 발생하는
  마우스 메세지를 처리한다...
//

//
// (2) handle keyboard messages...키보드 메세지를 처리한다...
//

//
// (3) 실행할 때 발생하는 마우스 메세지를 처리한다...
//

// (a) handle double clicks...더블클릭을
  처리한다...
// (b) send hint messages for Application hints Application
  객체의 hints 에 hint 메세지를 보낸다.
// (c) support dragging and ControlStyle 드래깅(dragging)과
  컨트롤스타일(ControlStyle) 을 지원한다.
//

Dispatch(&Message);
}

TControl::WndProc() 멤버 함수의 일 중 가장 중요한 것은 ControlState 속성에 따라 메세지를 조사해서 TApplication 객체에게 윈도우 기능에 대한 정보를 알려주는 것이고 TContol::ControlStyle 속성에 따라서 메세지를 처리하는 것이다. 각각의 메세지에 대해서 Dispatch() 멤버 함수를 호출하여 이벤트 핸들러를 실행하거나(물론 해당 메세지에 대한 이벤트 핸들러가 있어야 한다) DefaultHandler() 함수를 호출하는데 이 함수는 내부적으로 CallWindowProc() API 함수를 사용해서 윈도우 컨트롤의 기본 윈도우 프로시져를 호출한다. 여기까지 오면 메세지의 처리가 끝나게 된다. 여기까지의 과정을 간략하게 그림으로 표현해보면 아래와 같다.

4. 메세지 처리 방법

VCL 에는 특정 메세지를 처리하는 여러가지의 방법이 있다. 각 방법의 차이점을 알아보자.

4-1 WndProc 멤버 함수 이용하기

TControl::WndProc() 멤버 함수는 가상 멤버 함수이기 때문에 이 함수를 재정의해서 윈도우 컨트롤에 전달되는 특정 메세지 또는 모든 메세지를 처리할 수 있다. 목록 1.10a 와 1.10b 는 WndProc() 함수를 사용하는 예제다.

목록 1.10a

//----------------------------------------------------------------------
#ifndef Unit1H
#define Unit1H
//----------------------------------------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <Forms.hpp>
//----------------------------------------------------------------------

class TForm1 : public TForm
{
private: // User declarations
void __fastcall DoMouseUp(TMessage& Msg);
protected:
virtual void __fastcall WndProc(TMessage& Msg);
public: // User declarations
__fastcall TForm1(TComponent* Owner);
};
//----------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//----------------------------------------------------------------------
#endif

목록 1.10b

//----------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//----------------------------------------------------------------------
#pragma resource "*.dfm"
TForm1 *Form1;
//----------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//----------------------------------------------------------------------
void __fastcall TForm1::WndProc(TMessage& Msg)
{
switch (Msg.Msg)
{
case WM_LBUTTONUP:
{
DoMouseUp(Msg);
break;
}
}
TForm::WndProc(Msg);
}
//----------------------------------------------------------------------
void __fastcall TForm1::DoMouseUp(TMessage& Msg)
{
int X = Msg.LParamLo;
int Y = Msg.LParamHi;
ShowMessage("mouse up at: " + IntToStr(X)
  + ", " + IntToStr(Y));
}
//----------------------------------------------------------------------

메세지 전달 과정 중에 WndProc() 함수는 MainWndProc() 함수 내부에서 가장 먼저 호출되기 때문에 메세지를 제일 먼저 처리할 수 있다. 따라서 메세지 전달 과정을 단순화할 수 있고 메세지 처리에 있어 강력한 방법이 되지만 단일 메세지만을 처리하기에 사용하기도 그렇고 또 처리해야할 메세지가 많아지면 코드가 지저분해지기 때문에 유지보수 하기가 어렵다.

4-2 Dispatch 멤버 함수를 이용하는 방법

목록 1.9 를 보면 TControl::WndProc() 함수 내부에서 Dispatch() 함수가 제일 마지막에 호출되는데 이 것은 메세지가 WndProc() 멤버 함수에서 처리된 다음에 Dispatch() 멤버 함수에서 처리된다는 것을 말한다. 이 함수는 TObject 클래스의 가상 함수로, 메세지와 관련된 이벤트 핸들러를 호출하거나 아니면 DefaultHandler() 멤버 함수를 호출한다. 목록 1.10c 와 1.10d 는 Dispatch() 멤버 함수로 메세지를 처리하는 것을 보여준다.

목록 1.10c

//----------------------------------------------------------------------
#ifndef Unit1H
#define Unit1H
//----------------------------------------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <Forms.hpp>
//----------------------------------------------------------------------

class TForm1 : public TForm
{
private: // User declarations
void __fastcall DoMouseUp(TMessage& Msg);
public: // User declarations
__fastcall TForm1(TComponent* Owner);
virtual void __fastcall Dispatch(void* Message);
};
//----------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//----------------------------------------------------------------------
#endif

목록 1.10d

//----------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//----------------------------------------------------------------------
#pragma resource "*.dfm"
TForm1 *Form1;
//----------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//----------------------------------------------------------------------
void __fastcall TForm1::Dispatch(void* Message)
{
TMessage* PMsg = static_cast<TMessage*>(Message);
switch (PMsg->Msg)
{
case WM_LBUTTONUP:
{
DoMouseUp(*PMsg);
break;
}
}
TForm::Dispatch(Message);
}
//----------------------------------------------------------------------
void __fastcall TForm1::DoMouseUp(TMessage& Msg)
{
int X = Msg.LParamLo;
int Y = Msg.LParamHi;
ShowMessage("mouse up at: " + IntToStr(X)
  + ", " + IntToStr(Y));
}
//----------------------------------------------------------------------

목록 1.10d 에 구현된 Dispatch() 함수는 목록 1.10b 의 WndProc() 함수와 똑같은 것이다. 두 함수의 구현 모두 하나의 메세지만 처리하지만, 처리할 메세지의 수가 증가하면 switch 구문을 이해하기가 점점 힘들어질 것이다.

4-3 메세지 맵 메크로 사용하기

VCL 은 윈도우즈 API 에 숨어 있는 수 많은 메세지들을 편하게 다룰 수 있도록 메세지 매크로를 제공한다. 이 매크로는 BEGIN_MESSAGE_MAP, VCL_MESSAGE_HANDLER, END_MESSAGE_MAP 매크로로 구성되어 있고 sysdefs.h 헤더 파일에 구현되어 있다. 목록 1.11 은 이 매크로를 보여준다.

#define BEGIN_MESSAGE_MAP \
virtual void __fastcall Dispatch(void *Message) \
{ \
switch (((PMessage)Message)->Msg) \
{
#define VCL_MESSAGE_HANDLER(msg,type,meth) \
case msg: \
meth(*((type *)Message)); \
break;
#define END_MESSAGE_MAP(base) \
default: \
base::Dispatch(Message); \
break; \
} \
}

위의 코드를 보면 매크로의 내부에서 Dispatch() 멤버 함수를 사용하는 것을 볼 수 있다. 즉, 메세지 맵 매크로는 Dispatch() 멤버 함수를 이용해서 메세지를 처리하는 방법이라고 볼 수 있다. 메세지 맵 매크로를 이용해서 목록 1.10 의 코드를 목록 1.12a 와 목록 1.12b 의 코드로 다시 만들어 보았다.

목록 1.12a

//----------------------------------------------------------------------
#ifndef Unit1H
#define Unit1H
//----------------------------------------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <Forms.hpp>
//----------------------------------------------------------------------

class TForm1 : public TForm
{
private: // User declarations
void __fastcall DoMouseUp(TMessage& Msg);
public: // User declarations
__fastcall TForm1(TComponent* Owner);

BEGIN_MESSAGE_MAP
VCL_MESSAGE_HANDLER(WM_LBUTTONUP, TMessage, DoMouseUp)
END_MESSAGE_MAP(TForm)
};
//----------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//----------------------------------------------------------------------
#endif

목록 1.12b

//----------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//----------------------------------------------------------------------
#pragma resource "*.dfm"
TForm1 *Form1;
//----------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//----------------------------------------------------------------------
void __fastcall TForm1::DoMouseUp(TMessage& Msg)
{
int X = Msg.LParamLo;
int Y = Msg.LParamHi;
ShowMessage("mouse up at: " + IntToStr(X)
  + ", " + IntToStr(Y));
}
//----------------------------------------------------------------------

5. 맺음말

지금까지 VCL 이 메세지를 처리하는 과정과 방법에 대해서 알아보았다. 글을 번역하기도 하고 나름대로 쓴 곳도 있는데 원문의 뜻을 잘 전달 할 수 있을지 조금 두렵다. 아무튼 이 글이 C++빌더에 입문하는 사람들에게 VCL 을 이해하는데 많은 도움이 되길 바란다. 다음 문서의 주제는 메세지 처리 과정의 Part 2 로 메세지와 이벤트의 연결 방법 그리고 서브클래싱에 대해서 알아볼 것이다.

6. 붙임말

VCL 의 컨트롤들을 보면 TLabel 과 같은 윈도우 형식이 아닌 컨트롤이 있다. 윈도우 형식이 아닌 컨트롤은 입력 장치의 포커스를 받지 않는 컨트롤을 말한다. 하지만 이러한 컨트롤의 이벤트 핸들러 목록을 보면 마우스에 대한 이벤트 핸들러 함수가 있다. 어떻게 포커스도 받지 못하는 함수가 마우스 이벤트를 처리할 수 있을까?

위에서 살펴 보았듯이 TControl 에 WndProc() 라는 멤버 함수가 있다. 윈도우 프로시져 함수는 메세지를 처리하는 함수다. 하지만 메세지를 전달하기 위해서는 윈도우 핸들이 필요하다. 그러나 TControl 은 윈도우 컨트롤이 아닌 모든 컨트롤을 표현하는데 WndProc() 함수가 TWinControl 클래스에 있지 않고 TControl 클래스에 존재한다. 아마 이상할 것이다. SendMessage() 와 같은 함수를 이용해서 메세지를 전달하려면 윈도우 핸들이 필요하다. 그래서 윈도우 핸들은 TWinControl 클래스부터 존재한다. 그래서 TWinControl 클래스에서 파생된 컨트롤들은 쉽게 메세지를 주고 받을 수 있다. 하지만 포커스를 갖지 않으면서 마우스 이벤트는 처리하는 컨트롤을 만들고 싶을 땐 어떻게 해야할까? 이를 위해 VCL 은 TControl 에서 파생된 모든 컨트롤들에게 메세지를 전달 할 수 있도록 Perform 함수를 제공한다. 이 함수가 있어서 윈도우 핸들이 없는 컨트롤 들도 마우스 이벤트를 처리할 수 있게 되는 것이다.

번역 : 김성철( paranwave@korea.com )


Inside the VCL

From Message to Event Part 2

by Damon Chandler

원문 : http://bcbcaq.bytamin-c.com/MessagesToEvents2.html

"From Messages to Events Part I" 문서에서 메세지 처리 멤버 함수와 특정 메세지가 처리되는 방법을 알아보았다. 지금부터는 메세지와 연결된 VCL의 이벤트가 어떻게 발생되는지 알아보자.

1. 메세지와 VCL 이벤트의 연결

이 주제를 다루기 위해서 앞에서 살펴본 그림 1.1 의 예제를 다시 살펴본다. 이 예제의 코드는 목록 1.13 에 있다.

목록 1.13

//----------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"
//----------------------------------------------------------------------
#pragma resource "*.dfm"
TForm1 *Form1;
//----------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//----------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
Edit1->Text = "Hello World!";
}
//----------------------------------------------------------------------

OnClick 멤버 함수는 WM_LBUTTONUP 메세지를 받으면 호출된다. 즉, TMessage 구조체의 Msg 멤버 데이타가 WM_LBUTTONUP 으로 설정된 것은 해당 컨트롤을 마우스 버튼으로 클릭했다는 것이다. 메세지 연결(mapping) 을 이용하면 TButton::OnClick 이벤트를 이용하지 않고 같은 기능을 구현할 수 있다. 그 방법은 WM_LBUTTONUP 메세지를 버튼의 윈도우 프로시져에 직접 보내는 것이다. 이 내용을 구현한 것은 목록 1.14a 와 1.14b 에 있고 그림 1.3 은 수정된 예제다.

목록 1.14a

//----------------------------------------------------------------------
#ifndef Unit1H
#define Unit1H
//----------------------------------------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
//----------------------------------------------------------------------
class TMyButton : public TButton
{
private:
void __fastcall WMLButtonUp(TMessage &Msg);
public:
__fastcall TMyButton(TComponent *Owner) : TButton(Owner)
{}
BEGIN_MESSAGE_MAP
VCL_MESSAGE_HANDLER(WM_LBUTTONUP, TMessage, WMLButtonUp)
END_MESSAGE_MAP(TButton)
};
//----------------------------------------------------------------------
class TForm1 : public TForm
{
__published: // IDE-managed Components
TEdit *Edit1;
private: // User declarations
TMyButton *MyButton1;
public: // User declarations
__fastcall TForm1(TComponent* Owner);
};
//----------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//----------------------------------------------------------------------
#endif

목록 1.14b

//----------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//----------------------------------------------------------------------
#pragma resource "*.dfm"
TForm1 *Form1;
//----------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
MyButton1 = new TMyButton(this);
MyButton1->Parent = this;
MyButton1->Caption = "MyButton1";
MyButton1->Left = Edit1->Left + Edit1->Width
  + 20;
MyButton1->Top = Edit1->Top;
}
//----------------------------------------------------------------------
void __fastcall TMyButton::WMLButtonUp(TMessage &Msg)
{
TButton::Dispatch(&Msg);
POINT MousePos = Point(Msg.LParamLo, Msg.LParamHi);
RECT R = static_cast<RECT>(ClientRect);
if (PtInRect(&R, MousePos))
{
Form1->Edit1->Text = "Hello
  World!";
}
}
//----------------------------------------------------------------------

목록 1.14 에 있는 코드는 처음에 보았던 예제와 다른 점이 있다. 우선, TButton 의 Button1 컨트롤을 제거하고 대신에 TButton 에서 파생된 컨트롤인 TMyButton 의 인스턴스 MyButton1 을 사용했다. MyButton1 의 생성은 폼의 생성자에서 이뤄졌다. TMyButton 클래스에서는 메세지 매핑 매크로를 사용했다 이 것은 목록 1.12a 의 예제와 다른게 WM_LBUTTONUP 메세지를 폼차원에서 연결한 것이 아니다. 이렇게 하는 이유는 WM_LBUTTONUP 메세지를 Form1 의 윈도우 프로시져가 아닌 MyButton1 의 윈도우 프로시져에 보내기 위한 것이다. 이 것은 TMyButton::Dispatch 가상 멤버 함수를 사용하는 것이지 TForm1::Dispatch 멤버 함수를 사용하는 것이 아니다.

MESSAGE_HANDLER 매크로 안에서, WM_LBUTTONUP 메세지와 WMLButtonUp 메세지 핸들러가 연결된다. 이 핸들러 안에서, 마우스 커서의 좌표를 TMessage 데이타 멤버 중 LParamLo 와 LParamHi 에서 가져오고, TControl::ClientRect 속성을 MyButton1 의 클라이언트 영역을 알기 위해서 사용한다. 그런 다음 마우스 커서가 이 클라이언트 영역안에서 눌려졌는지 알기 우해서 PtInRect API 함수를 이용해서 테스트한다.

메세지 핸들러 안에서 처음으로 TButton::Dispatch 멤버 함수를 호출했는데, 이 것은 메세지를 TButton 클래스에 전달해서 TButton 클래스가 이 메세지에 대해서 기본적인 작업을 처리하도록 한다. 이 예에서, TMyButton::OnClick 이벤트 핸들러가 할당되어 있다면 이벤트 핸들러가 발생되는데 그 이유는 TButton 클래스와 이 클래스이 조상 클래스가 WM_LBUTTONUP 메세지를 받기 때문이다. 하지만, 만약 TButton::Dispatch 멤버 함수를 WMLButtonUp 함수에서 호출하지 않으면 WM_LBUTTONUP 메세지가 TButton 클래스에 전달 되지 않기 때문에 OnClick 이벤트 핸들러는 절대로 호출되지 않는다. 이를 이용하면 WM_LBUTTONUP 메세지를 쉽게 막거나 트랩(trap) 할 수 있다. 수정된 메세지 핸들러가 목록 1.15 에 있다.

목록 1.15

void __fastcall TMyButton::WMLButtonUp(TMessage &Msg)
{
// TButton::Dispatch(&Msg);
POINT MousePos = Point(Msg.LParamLo, Msg.LParamHi);
RECT R = static_cast<RECT>(ClientRect);
if (PtInRect(&R, MousePos))
{
Form1->Edit1->Text = "Hello
  World!";
}
Msg.Result = 0;
}

목록 1.15 에서 TMessage 데이타 멤버인 Result 를 0 으로 설정했는데 이 것은 WM_LBUTTONUP 메세지에 대한 응답을 0 으로 만드는 것이다.(즉, 버튼의 윈도우 프로시져는 0 을 반환하게 된다.) 목록 1.6 에 있는 C 언어 형식의 윈도우 프로시져가 반환하는 데이타 형이 LRESULT 이고 대부분의 윈도우 메세지는 응답 값으로 0 을 필요로한다. 하지만 목록 1.14b 에 있는 WMLButtonUp 이벤트 핸들러에서는 Result 값을 설정하지 않았는데 이 것은 Result 값을 TButton 클래스 또는 TButton 의 조상클래스가 설장하기 때문이다. 사실, Result 데이타 멤버는 기본 윈도우 프로시져에서 0 으로 초기화 되기 때문에 목록 1.15 에서 처럼 0 으로 설정하는 것은 쓸모없는 짓이다. 아무튼 Result 의 값을 0 으로 설정하는 것은 관련 메세지가 트랩(다루어졌다) 되었다는 것을 말한다.

메세지 트랩이 첫 보기에는 쓸모 없어 보이지만 이미 존재하는 윈도우 컨트롤에 특별한 기능을 만들 때는 종종 필요하다. 예를 들어, 목록 1.15 에서 WM_LBUTTONUP 메세지 대신에 WM_LBUTTONDOWN 메세지를 트랩하면 버튼을 클릭했을 때 버튼이 눌려지는 모습이 나타나지 않게 할 수 있다.

여기서, WM_LBUTTONUP 메세지와 TControl::OnClick 이벤트가 어떻게 연결되는지 알 수 있다. 즉, TControl 클래스는 WM_LBUTTONUP 메세지를 목록 1.14 의 방법과 아주 비슷하게 다룬다. C++ 로 표현한 TControl::WMLButtonUp 멤버 함수가 목록 1.16 에 있다.

목록 1.16

void __fastcall TControl::WMLButtonUp(TWMLButtonUp &Message)
{
TComponent::Dispatch(&Message);
//
// mouse-capture-related stuff...
//
if (ControlState.Contains(csClicked))
{
ControlState = ControlState
  >> csClicked;
RECT R = static_cast<RECT>(ClientRect);
if (PtInRect(&R, SmallPointToPoint(Message.Pos)))
{
//
  will fire the OnClick event
// handler,
  if assigned
Click();
}
}
// will fire the OnMouseUp
  event handler, if assigned
DoMouseUp(Message, mbLeft);
}

많은 부분에서, TControl::WMLButtonUP 메세지 핸들러는 목록 1.14b 의 WMLButtonUP 메세지 핸들러와 비슷하다. 예를 들어서, PtInRect API 함수가 마우스 커서의 위치를 테스트하기 위해서 사용되었다. SmallPointToPoint VCL 함수는 Message 인자의 Pos 멤버 데이타에서 POINT 구조체 값을 구해준다. 여기서는 일반적인 TMessage 형 대신에 TControl 은 TWMLButtonUp 형을 인자로 사용해서 구현했다. TWMLButtonUp 의 Pos 멤버 데이타는 TMessage 의 LParamLo(LOWORD) 와 LParamHi(HIWORD) 의 멤버 데이타 값과 같은 값을 가지고 있다. 목록 1.12b 에서 봤던 것처럼, 이 멤버 데이타는 마우스 커서의 X 와 Y 좌표값을 나타낸다.

목록 1.16 의 TControl::WMLButtonUp 메세지 핸들러는 OnClick 과 OnMouseUp 이벤트를 호출한다. 이 이벤트 핸들러들이 할당되어 있다면, 마우스의 버튼이 클라이언트 영역 내에서 놓여졌을 때 발생된다. 이벤트 핸들러를 호출하는 작업은 TControl::Click 멤버 함수를 통해 이뤄진다. TControl::Click 함수가 목록 1.17 에 있다.

목록 1.17

void __fastcall TControl::Click()
{
if (FOnClick) FOnClick(this);
}

private 영역에 있는 FOnClick 멤버는 사용자정의의 OnClick 이벤트 핸들러를 가리키는 포인터이다. 여기서 FOnClick 은 TNotifyEvent 형이다.

typedef void __fastcall (__closure *TNotifyEvent)(System::TObject* Sender);

위의 형정의로, TControl::Click 멤버 함수가 사용자 정의의 OnClick 이벤트 핸들러를 직접 호출한다는 것을 볼 수 있다. 물론 사용자 정의의 이벤트 핸들러가 할당 되엉 있어야한다. 또한, 이벤트 핸들러의 인자롤 Sender 를 넘겨주기 때문에 각각의 "Sender" 에 대해 공통 작업을 수행할 수 있다. TNotifyEvent 는 이벤트 핸들러를 호출하는데 사용되는 함수 포인터 형일 뿐이다. 특별하게, 이 이벤트 핸들러 포인터는 이벤트의 종류나 관련된 정보에 따라서 달라 질 수가 있다. 예를 들어, OnMouseDown 과 OnMouseUp 이벤트들은 TMouseEvent 형이다.

typedef void __fastcall (__closure *TMouseEvent)(System::TObject* Sender, TMouseButton Button, Classes::TShiftState Shift, int X, int Y);

간단한 예제를 통해서, 윈도우즈 메세지와 VCL 이벤트간의 연결을 알아보았다. 또한, 각각의 단계를 거치면서 윈도우즈 운영체제와 VCL 이 어떻게 서로 통신을 하는지 알아보았다. 이 문서의 나머지 글을 보면서, TWinControl 의 대부분의 이벤트들이 특정 윈도우즈 메세지에서 발생한다는 것을 알게될 것이다. 나중에, 확장 기능을 위해서 VCL 이 어떻게 확장 메세지를 정의하고 사용하는지 알아볼 것이다. 지금은, 이 문서의 마지막 주제인 인스턴스 서브클래싱에 대해서 알아보자.

2. 인스턴스 서브클래싱

TMyButton 클래스의 구현을 살펴보고 난 지금, 특정 컨트롤에 보내어진 메세지를 다루기 위해서 컨트롤의 Dispatch 멤버 함수를 만들었던 것을 기억할 것이다. 예를 들어서, 버튼 컨트롤에서 WM_LBUTTONUP 메세지를 잡아내기 위해서, TMyButton 클래스를 새로 만들었었다. 하지만 TMyButton 클래스를 만들 필요가 없고 대신에 TButton::OnMouseUp 이벤트 핸들러를 사용하면 된다. 사실, 대부분의 경우에는 VCL 이벤트 핸들러만으로도 충분하다. 하지만, VCL 을 잠깐 사용해봐도 알겠지만, 모든 메세지들이 VCL 이벤트와 연결되어 있는 것은 아니다. 실제로, 윈도우즈 API 는 많고 많은 메세지들이 있고 이 메세지들을 직접 연결해야할 경우가 많다. 특정 메세지를 직접 연결하기 위해서 VCL 컴포넌트를 상속받아 새로운 컴포넌트 클래스를 만드는 것은 과욕이 될 수 있다. 특히 이 점은 프로젝트 내에서 오로지 하나의 인스턴스만 필요한 컴포넌트일 경우에 더 그렇다. 사실, 이러한 문제점을 해결하기 위해서 윈도우즈 운영체제는 '서브클래싱' 이란 기술을 제공한다. 이 서브클래싱 기술은 윈도우즈의 윈도우 프로시져를 직접 가로챌 수 있도록 해준다. VCL 은 이 개념을 확장해서 모든 TControl 의 파생 클래스에 서브클래싱을 지원하고 있다.( 비윈도우컨트롤도 포함한다.)

컨트롤의 윈도우 프로시져는 컨트롤 클래스가 운영체제에 등록될 때 설정된다. 모든 컨틀롤 클래스는 이 윈도우 프로시져 함수를 기본 윈도우 프로시져로 사용할 것이다. 이 함수는 TWinControl::DefaultHandler 멤버 함수 내에서 호출되는 CallWindowProc API 함수가 불러내는 윈도우 프로시져와 같은 윈도우 프로시져이다. 인스턴스 서브클래싱 기술을 사용해서, 특정 윈도우 인스턴스의 윈도우 프로시져를 바꿀 수 있다. 인스턴스 서브클래싱을 쉽게 이해할 수 있도록 간단한 예제를 살펴보자.

TMemo 컨트롤 인스턴스인 Memo1 을 가지고 있는 Form1 이라는 폼을 생각해보자. 그림 1.4 는 이 예제의 그림이다.

Memo 컨트롤을 사용하면 텍스트 파일을 쉽게 읽어올 수 있다. 이 단원에서는 Memo 컨트롤에 끌어다놓기(Drag and Drop) 기능을 만들 것이다. 끌어다놓기를 만드는 방법은 WM_DROPFILES 메세지를 처리하는 것인데 이 메세지는 사용자가 컨트롤에 파일을 끌어다 놓을 때 발생된다. TMemo 컨트롤이 지원하는 이벤트를 보면 WM_DROPFILES 메세지와 연결되는 OnDropFile() 이벤트핸들러가 없다는 것을 알 수 있다. WM_DROPFILES 메세지를 다루기위한 방법은 TMemo 에서 파생된 클래스를 만들고 메세지 연결 기술을 이용해서 WM_DROPFILES 메세지를 처리하는 것이다. 다른 방법은 TMemo 에서 파생된 클래스의 멤버함수 중 TWinControl::WndProc() 함수에서 이 메세지를 처리하는 것이다. 하지만, 우린 WM_DROPFILES 메세지 하나만 처리할 것이다. 이럴경우에는 인스턴스 서브클래싱을 사용하는 것이 편리한다. 인스턴스 서브클래싱은 TMemo 클래스의 인스턴스인 Memo1 의 윈도우 프로시져를 사용자가 만든 윈도우 프로시져로 바꾸는 기술이다. 간단한 예를 들어보면:

14시간 동안 계속 프로그래밍만 하던 존은 신선한 커피와 커피여과기를 사기위해서 차를 몰고 슈퍼마켓으로 갔다. 다시 집에 오는 길에, 존은 연료가 조금 밖에 없는 것을 보곤, 근처 주유소에 들렀다. 하지만 존은 최근에 신버전인 델파이를 사느라 모든 돈을 다 써버렸기 때문에 아주 싼 기름을 살수 밖에 없었다. 주유소를 나와서 한 20 마일 쯤 갔을 때, 차가 멈춰섰다. 존은 연료에 돌조각이 섞여 있다고 판다하곤 차를 고치려했지만 돌을 제거할 방법을 몰랐다. 하지만 잠시 후 존은 내화기로 연결된 연료선을 자르고 커피 여과기를 설치해서 연료에서 돌을 걸러낼 수 있었다. 존의 차는 시동이 걸렸고 무시히 집에 올 수 있었다.

존이 문제를 해결한 방법이 인스턴스 서브클래싱과 똑같은 방법이다. 연료를 윈도우가 보내는 메세지로 생각할 수 있고 내화기를 Memo 컨트롤로 생각할 수 있다. 그리고 WM_DROPFILES 메세지는 돌조각으로 생각할 수 있다. 존이 돌조각을 걸러내듯이, 우리도 WM_DROPFILES 메세지를 걸러내서 처리하는 것이다.

2-1. API 로 구현한 인스턴스 서브클래싱

우선 SetWindowLong API 함수를 이용해서 인스턴스 서브클래싱을 해보자. 이 함수에 GWL_WNDPROC 값을 설정하면, Memo1 컨트롤을 위한 새로운 윈도우 프로시져를 설정할 수 있다. SetWindowLong 함수는 Memo1 의 기본 윈도우 프로시져를 반환한다. 예제의 코드는 목록 1.18a 와 1.18b 에 있다.

목록 1.18a

//----------------------------------------------------------------------
#ifndef MemoDDH
#define MemoDDH
//----------------------------------------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
//----------------------------------------------------------------------
class TForm1 : public TForm
{
__published: // IDE-managed Components
TMemo *Memo1;
private: // User declarations
FARPROC Memo1DefaultWndProc;
void *MemberFunctionInstance;
void __fastcall Memo1WindowProc(TMessage &Msg);
void __fastcall HandleDroppedFile(TMessage &Msg);
public: // User declarations
__fastcall TForm1(TComponent* Owner);
};
//----------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//----------------------------------------------------------------------
#endif

목록 1.18b

//----------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include <shellapi.h>
#include "MemoDD.h"
//----------------------------------------------------------------------
#pragma resource "*.dfm"
TForm1 *Form1;
//----------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
// register Memo1 as a valid
  drop target
DragAcceptFiles(Memo1->Handle, true);
// create a non-member function
  pointer to the
// the TForm1::Memo1WindowProc
  member function
MemberFunctionInstance = MakeObjectInstance(Memo1WindowProc);
// subclass Memo1 via the
  SetWindowLong API function and
// store the default window procedure
Memo1DefaultWndProc = reinterpret_cast<FARPROC>(SetWindowLong(Memo1->Handle,
  GWL_WNDPROC,reinterpret_cast<LONG>(MemberFunctionInstance)));
}
//----------------------------------------------------------------------
void __fastcall TForm1::Memo1WindowProc(TMessage &Msg)
{
switch (Msg.Msg)
{
case WM_DROPFILES:
{
HandleDroppedFile(Msg);
Msg.Result
  = 0;
return;
}
case WM_DESTROY:
{
//
  remove the instance subclass Memo1
SetWindowLong(Memo1->Handle,
  GWL_WNDPROC,reinterpret_cast<LONG>(Memo1DefaultWndProc));
//
  free the memory for the function pointer
FreeObjectInstance(MemberFunctionInstance);
break;
}
}
// call the default window
  procedure
Msg.Result = CallWindowProc(Memo1DefaultWndProc, Memo1->Handle,Msg.Msg,
  Msg.WParam, Msg.LParam);
}
//----------------------------------------------------------------------
void __fastcall TForm1::HandleDroppedFile(TMessage &Msg)
{
// grab a handle to the drop
  object
HDROP HDrop = reinterpret_cast<HDROP>(Msg.WParam);
// find the number of files
  dropped
int num_files = DragQueryFile(HDrop, 0xFFFFFFFF, NULL,
  NULL);
if (num_files > 1)
{
ShowMessage("You have dropped
  too many files.");
return;
}
// extract the name and path
  of the dropped file
char filename[MAX_PATH];
DragQueryFile(HDrop, 0, filename, MAX_PATH);
try
{
//
  load the file
Memo1->Lines->LoadFromFile(filename);
}
catch (...)
{
//
  close the drop handle
DragFinish(HDrop);
}
// close the drop handle
DragFinish(HDrop);
}
//----------------------------------------------------------------------

위의 예제에서는 TMemo 에서 파생된 클래스를 만들지 않았다. 그대신에 Memo1 의 WM_DROPFILES 메세지를 TForm1 클래스에서 처리했다. TForm1 클래스는 private 영역에 FARPROC 자료형의 Memo1DefaultWndProc 멤버 변수를 가지고 있는데 이 것은 Memo1 의 기본 윈도우 프로시져의 주소를 담는다. 이 변수외에 일반 포인터형의 MemberFunctionInstance 멤버 변수가 있는데 이 것은 Memo1WindowProc 멤버 함수와 연결될 비 멤버 함수의 주소를 가지고 있다. 그리고 HandleDroppedFiles 멤버 함수가 있는데 이 것은 WM_DROPFILES 메세지를 처리해서 Memo 컨트롤에 파일을 불러온다.

폼의 생성자에서 DragAcceptFiles API 함수에 Memo 컨트롤의 핸들을 전달해서 호출했다. 이 함수는 Memo1 컨트롤을 끌어다놓기의 목표윈도우로 등록한다. 그 다음 비 멤버 함수와 Memo1 의 메세지를 처리하는 TForm1::Memo1WindowProc 멤버 함수를 연결하기 위해서 MakeObjectInstance VCL 함수를 호출했다. 윈도우 프로시져는 클래스의 멤버함수가 될 수 없기 때문에 이 과정이 필요하다. 앞에서도 다루었는데, TWinControl 클래스는 모든 윈도우 형식의 컨트롤에 이 과정을 수행한다. MakeObjectInstance 함수는 비멤버 함수의 포인터를 반환하는데 여기서는 SetWindowLong API 함수를 호출할 때 필요한 Memo1 의 새로운 윈도우 프로시져의 주소를 반환한다. SetWindowLong API 함수는 Memo1 의 기본 윈도우 프로시져의 주소를 반환하는데 이 값을 Memo1DefaultWndProc 멤버 변수에 저장했다. Memo 컨트롤이 메모리에서 제거하기 전에 서브클래싱을 해지해야 하는데 이 때 기본윈도우 프로시져의 주소값이 필요하다. 기본 윈도우 프로시져의 주소값은 CallWindowProc API 함수를 호출해서 얻을 수도 있다. 그래서 이 함수를 통해 처리하지 않는 모든 메세지를 기본 윈도우 프로시져에 전달할 수 있다.

TForm1::Memo1WindowProcedure 멤버 함수가 서브클래스의 프로시져다. Memo1 에 전달되는 모든 메세지들은 이 멤버 함수로 전달된다. 이런 방법으로 TForm1 클래스의 멤버 함수에서 Memo1 의 WM_DROPFILES 메세지를 다룰 수 있는 것이다. - TMemo 에서 파생된 클래스가 필요없다. 서브클래스 프로시져 함수 내에서 마지막으로 호출하는 함수는 CallWindowProc API 함수인데 처리하지 않는 모든 메세지를 Memo 컨트롤의 기본 윈도우 프로시져로 보내게 한다. 예를 들어, WM_PAINT 메세지를 트랩(기본윈도우프로시져로 보내지않으면)하면, Memo 컨트롤의 클라이언트 영역이 그려지지 않는다. 실제 결과는 스택오버플로우가 일어난다. 왜냐하면 어플리케이션의 메세지 큐에는 처리되지 않은 WM_PAINT 로 가득 채워지기 때문이다. 이 경우는 존이 연료선을 잘라서 커피 여과기를 설치한 것과 비슷하지만 양쪽 끝을 다시 연결하지 않은 것과 같다.

서브클래스 프로시져의 switch 블럭은 WM_DROPFUKES 와 WM_DESTROY 두 개의 메세지만 검사한다. WM_DESTROY 에서 인스턴스 서브클래싱을 제거한다. WM_DROPFILES 경우에는, HandleDroppedFile 멤버함수를 호출해서 Memo 컨트롤이 파일을 불러올 수 있도록 한다. 이 메세지는 트랩(기본윈도우프로시져로 전달되지 않는다)되었는데 TMemo 클래스의 기반 클래스인 EDIT 윈도우 표준 컨트롤은 WM_DROPFILES 메세지를 다룰 필요가 없기 때문이다. ( 메세지를 트랩해야할 지 말아야할지 모르겠다면, 그냥 트랩하지 않는 것이 좋은 방법이다. )

HandleDroppedFile 멤버 함수 내에서, Msg 인자의 WParam 멤버에서 드롭된 물체의 핸들을 얻는다. 그 다음 DragQueryFile API 함수를 호출해서 드롭된 파일의 갯수와 파일의 경로를 얻는다. 파일 내용을 TStrings::LoadFromFile 멤버 함수를 통해 Memo 컨트롤에 불러온다음에 드롭된 물체의 핸들을 DragFinish API 함수로 닫아버린다.

HandleDroppedFile 멤버함수의 구현을 자세하게 이해하는 것이 중요한 것이 아니라 특정 윈도우에 전달되는 메세지를 처리하는 방법이 여러개라는 것을 아는 것이 중요하다. 컨트롤이 단 하나느이 메세지만을 처리할 때 인스턴스 서브클래싱 기술을 사용하면 아주 좋다.

2-2. VCL 을 사용한 인스턴스 서브클래싱

VCL 을 사용하면 인스턴스 서브클래싱을 아주 쉽게 할 수 있다. TControl 클래스는 WindowProc 속성을 가지고 있는데 이 속성은 인스턴스 서블클래싱을 위해 사용할 수 있다. WindowProc 속성은 TWinControl::MainWndProc 멤버 함수를 다룰 때 봤었다. 이 속성은 TWndMethod 타입으로 TControl::WndProc 멤버 함수의 포인터다. WindowProc 속성과 WndProc 멤버 함수의 차이점은 WndProc 멤버 함수가 proctected 에 있기 때문에 TControl 클래스나 TControl 에서 파생된 클래스를 벗어나면 접근이 불가능하다는 것이다. 하지만, WindowProc 속성은 public 에 선언되어 있기 때문에 인스턴스 서브클래싱을 하기 위해서 사용할 수 있다. 목록 1.19a 와 1.19b 는 위에서 살펴본 예제를 TControl::WindowProc 속성을 이용해서 다시 만들어본 예제다.

목록 1.19a

//----------------------------------------------------------------------
#ifndef MemoDDH
#define MemoDDH
//----------------------------------------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
//----------------------------------------------------------------------
class TForm1 : public TForm
{
__published: // IDE-managed Components
TMemo *Memo1;
private: // User declarations
Controls::TWndMethod Memo1DefaultWindowProc;
void __fastcall Memo1WindowProc(TMessage &Msg);
void __fastcall HandleDroppedFile(TMessage &Msg);
public: // User declarations
__fastcall TForm1(TComponent* Owner);
};
//----------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//----------------------------------------------------------------------
#endif

목록 1.19b

//----------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include <shellapi.h>
#include "MemoDD.h"
//----------------------------------------------------------------------
#pragma resource "*.dfm"
TForm1 *Form1;
//----------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
// register Memo1 as a valid
  drop target
DragAcceptFiles(Memo1->Handle, true);
// store the address of the
  original window procedure
Memo1DefaultWindowProc = Memo1->WindowProc;
// replace the window procedure
  (i.e., subclass the Memo control)
Memo1->WindowProc = Memo1WindowProc;
}
//----------------------------------------------------------------------
void __fastcall TForm1::Memo1WindowProc(TMessage &Msg)
{
switch (Msg.Msg)
{
case WM_DROPFILES:
{
HandleDroppedFile(Msg);
Msg.Result
  = 0;
return;
}
case WM_DESTROY:
{
//
  remove the instance subclass Memo1
Memo1->WindowProc
  = Memo1DefaultWindowProc;
break;
}
}
// call the default window
  procedure
Memo1DefaultWindowProc(Msg);
}
//----------------------------------------------------------------------
void __fastcall TForm1::HandleDroppedFile(TMessage &Msg)
{
// grab a handle to the drop
  object
HDROP HDrop = reinterpret_cast<HDROP>(Msg.WParam);
// find the number of files
  dropped
int num_files = DragQueryFile(HDrop, 0xFFFFFFFF, NULL,
  NULL);
if (num_files > 1)
{
ShowMessage("You have dropped
  too many files.");
return;
}
// extract the name and path
  of the dropped file
char filename[MAX_PATH];
DragQueryFile(HDrop, 0, filename, MAX_PATH);
try
{
//
  load the file
Memo1->Lines->LoadFromFile(filename);
}
catch (...)
{
//
  close the drop handle
DragFinish(HDrop);
}
// close the drop handle
DragFinish(HDrop);
}
//----------------------------------------------------------------------

WindowProc 속성을 사용하면 SetWindowLong API 함수를 사용해서 했던 것보다 더 쉽게 인스턴스 서브클래싱을 할 수 있다. 게다가, WindowProc 속성은 TWndMethod 속성이기 때문에, MakeObjectInstance 함수를 사용할 필요가 없다. 그리고 또 WindowProc 속성을 사용하면 메세지들을 기본 프로시져에 전달히기 위해서 CallWindowPoc API 함수를 호출할 필요가 없다. 그 대신에 Memo1DefaultWindowProc 함수를 바로 호출하면 된다.

3. 적당한 메세지 처리방법 선택하기

윈도우에 전달되는 메세지를 처리하는 세가지 방법을 알아보았다. 대부분 BEGIN_MESSAGE_MAP, MESSAGE_HANDLER 그리고 END_MESSAGE_MAP 매크로를 통해서 Dispatch 함수를 사용한다. WndProc 멤버 함수는 메세지가 조상 클래스의 WndProc 멤버 함수에서 처리되기 전이나 또는 트랩해햐할 경우에 사용한다. 마지막으로 인스턴스 서브클래싱은 단 한개의 메세지만을 처리해야할 때 사용할 수 있는 방법이다.

WndProc 멤버 함수와 인스턴스 서브클래싱을 사용하면 BEGIN_MESSAGE_MAP, MESSAGE_HANDLER 그리고 END_MESSAGE_MAP 매크로를 사용할 때보다 얻을 수 있는 잇점이 있다. 그 잇점은 메세지 트래핑(가로채기)과 관련된 것이다. TControl::WndProc 함수 정의에서 마지막으로 호출하는 함수는 Dispatch 멤버 함수였다. 또한 메세지 매크로 맵은 Dispathc 함수를 사용하는 것이었다. 이 것을 사용하면, TControl::WndProc 멤버 함수가 메세지를 처리한 다음에 우리가 메세지를 처리하는 것이다. 하지만 인스턴스 서브클래싱이나 WndProc 함수를 사요하면 메세지를 WndProc 함수가 다루기 전이나 후에 메세지를 처리할 수 있게 된다. 이런 내용을 바탕으로 마지막 예제를 살펴보자.

TPanel 의 Panel1 과 TShape 의 Shape1 로 구성된 폼을 생각해보자. Shape 컨트롤의 부모는 Panel 이고 Shape 의 Align 속성은 alClient 다. Panel 은 TWinControl 에서 파생된 클래스지만 Shape 는 TGraphicControl 에서 파생된 거싱다. 목록 1.8 에서 살펴본 TWinControl::WndProc 함수의 구현을 생각해 보면 마우스 메세지를 처리할 것인지 자식 컨트롤에 전달할 것인지 결정하기 위해서 IsControlMouseMsg 함수를 호출한다. 이 경우에, Shape 컨트롤이 Panel 의 클라이언트 영역 모두를 덮고 있기 때문에 Panel 에 보내지는 모든 마우스 메세지가 바로 Shape 컨트롤에 전달될 것이다. 즉, Panel 의 마우스 이벤트가 발생되질 않는다. 그림 1.5 는 이 경우를 나타낸 것이다.

이런 경우라면 대게, Panel 의 마우스 관련 이벤트 대신에 Shape 의 마우스 관련 이벤트를 사용할 것이다. 하지만 TGraphicControl 에서 파생된 어떤 컨트롤 클래스는 마우스 관련 이벤트가 없는 경우도 있다, 이럴때, 해결할 수 있는 방법은 Panel 의 마우스 이벤트를 사용하는 것이다.

이 예제의 경우에, Panel 의 마우스 관련 이벤트를 사용하려면 Shape 의 Enable 속성을 false 로 설정하면 된다. 이 것은 IsControlMouseMsg 함수가 비활성 컨트롤에는 false 를 반환할 것이기 때문에 잘 동작할 것이다. 즉, IsControlMouseMsg 함수는 TWincotrol::ControlAtPos 멤버함수의 AllowDisabled 인자에 false 를 넘겨 주어 사용한다. 하지만, 이 방법은 완벽하지가 않은데, TGraphicsControl 에서 파생된 컨트롤이 비활성 되었을 때 자신을 다르게 그리기 때문이다.

또 다른 방법은 간단하게 TPanel 을 파생시켜서 파생된 클래스에서 메세지를 처리하는 것이다. 그러면 어떻게 메세지를 처리해야할까? WndProc 멤버함수를 사용해야할까 아니면 메세지 매핑 매크로를 사용해야할까? 답은 WndProc 멤버 함수를 사용하는 것이다. 왜그럴까? 메세지 매핑 매크로는 단순히 Dispatch 멤버 함수를 사용하는 것인데, Dispatch 멤버 함수는 TWinControl::WndProc 멤버 함수의 마지막부분에서 호출되기 때문이다. 지금 이 예제의경우를 보면 TWinControl::WndProc 멤버 함수는 모든 마우스 메세지들을 Shape 컨트롤에 보내기 때문에 Panel 의 Dispatch 멤버 함수는 절대 호출되지 않는다. 이 말은 마우스 관련 메세지들이 Shape 컨트롤에 보내기지 때문에 Panel 의 Dispatch 멤버 함수를 사용하는게 쓸모 없다는 것이다. 목록 1.20a 와 1.20b 는 이 내용을 구현해본 것이다.

목록 1.20a

//----------------------------------------------------------------------
#ifndef MyPanelUnitH
#define MyPanelUnitH
//----------------------------------------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include <ExtCtrls.hpp>
//----------------------------------------------------------------------
class TMyPanel : public TPanel
{
private:
void __fastcall WMMouseMove(TMessage &Msg);
public:
__fastcall TMyPanel(TComponent *Owner) : TPanel(Owner)
  {};
BEGIN_MESSAGE_MAP
VCL_MESSAGE_HANDLER(WM_MOUSEMOVE, TMessage, WMMouseMove)
END_MESSAGE_MAP(TPanel)
};
//----------------------------------------------------------------------
class TForm1 : public TForm
{
private: // User declarations
TMyPanel *MyPanel;
TShape *Shape1;
void __fastcall Shape1MouseMove(TObject *Sender, TShiftState
  Shift, int X, int Y);
public: // User declarations
__fastcall TForm1(TComponent* Owner);
};
//----------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//----------------------------------------------------------------------
#endif

 목록 1.20b

//----------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "MyPanelUnit.h"
//----------------------------------------------------------------------
#pragma resource "*.dfm"
TForm1 *Form1;
//----------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
MyPanel = new TMyPanel(this);
MyPanel->Parent = this;
MyPanel->Align = alClient;
MyPanel->BevelInner = bvNone;
MyPanel->BevelOuter = bvNone;
MyPanel->Caption = "Panel1";

Shape1 = new TShape(MyPanel);
Shape1->Parent = MyPanel;
Shape1->Align = alClient;
Shape1->Shape = stRoundRect;
Shape1->Brush->Style = bsCross;
Shape1->OnMouseMove = Shape1MouseMove;
}
//----------------------------------------------------------------------
void __fastcall TForm1::Shape1MouseMove(TObject *Sender, TShiftState Shift,
  int X, int Y)
{
Caption = "Shape Mouse Move: " + IntToStr(X)
  + ", " + IntToStr(Y);
}
//----------------------------------------------------------------------
//
// WM_MOUSEMOVE message handler ? WILL NEVER BE CALLED
//
void __fastcall TMyPanel::WMMouseMove(TMessage &Msg)
{
int X = Msg.LParamLo;
int Y = Msg.LParamHi;
Form1->Caption = "Panel Mouse Move: " +
  IntToStr(X) + ", " + IntToStr(Y);

TPanel::Dispatch(&Msg);
}
//----------------------------------------------------------------------

목록 1.20a 에서 Dispatch 멤버함수를 사용하기 위해서 메세지 매핑 매크로를 사용하여 TPanel 에서 파생된 클래스에서 WM_MOUSEMOVE 메세지를 처리했다. 하지만, 안타깝게도 목록 1.20b 에 있는 WMMouseMove 메세지 핸들러 함수는 절대 호출되지 않을 것이다. 즉, TPanel 의 파생클래스의 멤버 함수인 Dispatch 가 호출되지 않기 때문에 Form1 의 Caption 은 항상 "Shape Mouse Move:" 가 될 것이다.

메세지 매핑 매크로를 사용하는 대신에, 목록 1.21a 와 1.21b 처럼 Panel 의 WndProc 멤버 함수를 사용해 보자.

목록 1.21a

//----------------------------------------------------------------------
#ifndef MyPanelUnitH
#define MyPanelUnitH
//----------------------------------------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include <ExtCtrls.hpp>
//----------------------------------------------------------------------
class TMyPanel : public TPanel
{
private:
virtual void __fastcall WndProc(TMessage &Msg);
public:
__fastcall TMyPanel(TComponent *Owner) : TPanel(Owner)
  {};
};
//----------------------------------------------------------------------
class TForm1 : public TForm
{
private: // User declarations
TMyPanel *MyPanel;
TShape *Shape1;
void __fastcall Shape1MouseMove(TObject *Sender, TShiftState
  Shift, int X, int Y);
public: // User declarations
__fastcall TForm1(TComponent* Owner);
};
//----------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//----------------------------------------------------------------------
#endif

 목록 1.21b

//----------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "MyPanelUnit.h"
//----------------------------------------------------------------------
#pragma resource "*.dfm"
TForm1 *Form1;
//----------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
MyPanel = new TMyPanel(this);
MyPanel->Parent = this;
MyPanel->Align = alClient;
MyPanel->BevelInner = bvNone;
MyPanel->BevelOuter = bvNone;
MyPanel->Caption = "Panel1";

Shape1 = new TShape(MyPanel);
Shape1->Parent = MyPanel;
Shape1->Align = alClient;
Shape1->Shape = stRoundRect;
Shape1->Brush->Style = bsCross;
Shape1->OnMouseMove = Shape1MouseMove;
}
//----------------------------------------------------------------------
void __fastcall TForm1::Shape1MouseMove(TObject *Sender, TShiftState Shift,
  int X, int Y)
{
Caption = "Shape Mouse Move: " + IntToStr(X)
  + ", " + IntToStr(Y);
}
//----------------------------------------------------------------------
void __fastcall TMyPanel::WndProc(TMessage &Msg)
{
if (Msg.Msg == WM_MOUSEMOVE)
{
int X = Msg.LParamLo;
int Y = Msg.LParamHi;

Form1->Caption = "Panel
  Mouse Move: " + IntToStr(X) + ", " + IntToStr(Y);

//
  trap the message
Msg.Result = 0;
return;
}
TPanel::WndProc(Msg);
}
//----------------------------------------------------------------------

WndProc 멤버 함수를 사용해서, TPanel 에 전달된 메세지를 파생된 클래스가 조상 클래스보다 먼저 처리할 수 있다. 이 경우에, Form1 의 Caption 은 항상 "Panel Mouse Move: " 가 될 것이다. 이 예제에서는 WM_MOUSEMOVE 메세지를 조상 클래스의 WndProc 멤버 함수에 전달된 것처럼 하기 위해서 메세지를 트랩(trap)하였다. 이 예제의 경우에, 이 메세지 트랩이 필요한데, TWinControl::WndProc 멤버 함수가 마지막가서 Shape 컨트롤의 OnMouseMove 이벤트 핸들러를 호출하기 때문이다. 만약 Shape 의 이벤트 핸들러가 호출된다면 Form1 의 Caption 은 "Shape's Mouse Move: " 메세지로 바뀔 것이다. 이는 우리가 원하는 결과가 아니다.

이제 TPanel::WndProc 함수가 위치한 곳을 살펴보자. 이 함수는 맨 마지막에 위치해 있다. 만약 TPanel 의 WndProc 멤버 함수를 메세지 처리 블럭 위에서 호출했다면, Shape 의 OnMouseMove 이벤트 핸들러가 호출되고 Form1 의 Caption 은 "Shape's Mouse Move: " 가 된다. 그리고 나서 우리가 만든 메세지 처리 블럭이 실행되어, Form1 의 Caption 은 "Panel Mouse Move: " 가 된다. 이렇게 되면 Caption 은 깜빡깜빡 거릴 것이다.

마지막으로, 인스턴스 서브클래싱은 WndProc 멤버 함수를 이용해서 사용할 수 있다. 이 때는 조상 클래스의 WndProc 멤버 함수가 호출되기 전에 메세지를 가로채서 먼저 처리해야한다.

수 많은 경우에서, 메세지 처리를 조상 클래스이 WndProc 함수가 호출되기 전에 하느냐 아니면 후에 하느냐로 고민될 때가 있다. 이 고민의 답은 실제 메세지와 처리되어야하는 방법에 따라 결정될 것이다. 이 때는 "메세지가 조상 클래스의 WndProc 함수에서 처리되어야 하는가 아니면 처리할 필요가 없는가?" 라는 질문을 던져보자.

다음 강좌에서는 VCL 이 펼치는 윈도우 생성 마법에 대해서 알아볼 것이다. :]

번역 : 김성철( paranwave@korea.com )


Inside the VCL

Window Creation Fundamentals

by Damon Chandler

원문 : http://bcbcaq.bytamin-c.com/WindowCreationFundamentals.html

윈도우즈 API 레퍼런스에서 "윈도우(window)" 란 단어를 봤을 때, VCL 의 폼이 생각날 것이다. 실제로 폼은 어플리케이션에서 사용되는 윈도우의 한 종류이다. 곧 알게 되겠지만, TWinControl 에서 파생된 컨트롤들은 윈도우 형식의 컨트롤이다. 예를 들어서, Handle 속성은 TWinControl 클래스에서 가장 중요한 속성인데 이 것은 아무렇게 설계해서 그런 것이 아니라 오히려 윈도우 형식의 컨트롤임을 확실하게 나타내는 것이다. 이 핸들 속성은 윈도우의 실제 메모리 블럭을 나타내는 추상화 레이어로 사용되고 API 함수들은 이 핸들을 이용해서 윈도우의 실제 메모리 블럭을 다룰 수 있는 것이다. "From Message to Events" 의 문서에서 SendMessage API 함수를 사용했들 때도, 이 함수의 첫번째 인자에 메세지를 받을 윈도우의 핸들을 전달했었다. 사실, 윈도우 형식의 컨트롤을 다루는 거의 모든 API 함수들은 윈도우 핸들을 필요로한다. 하지만 VCL 을 사용하면 거의 대부분 이 윈도우 핸들(Handle) 속성을 사용할 필요가 없다. 그러나 VCL 이 모든 윈도우 조작 API 함수를 지원하는 것이 아니기 때문에 가끔 쓸일도 있다. :]

TWinControl 클래스는 윈도우즈 API 와 윈도우 형식의 모든 VCL 컨트롤을 연결시켜 주는 접작제가 된다. 그렇기 때문에, TWinControl 을 이해하기 위해서는, 먼저 API 관점에서 윈도우 생성 과정을 이해해야한다. "From Message to Event" 시리즈의 문서에서 메세지와 이벤트에 대해서 몇몇 윈도우즈 API 와 VCL 을 연결해 보았지만 이 문서에서는 TWinControl 클래스와 윈도우 생성 과정의 API 들을 연결시켜 볼 것이다.

1. 윈도우 생성

대부분의 어플리케이션들은 사용자와 정보를 서로 주고받기 위해서 윈도우즈 형식으로 만들어진다. 확실히, 윈도우라는 비주얼 오브젝트를 이용해서 사용자와 어플리케이션 간에 정보를 주고 받으면 정말 편리하다. 어떤 경우에는, 윈도우가 사용자의 입력을 받아 들이고, 어떤 때에는 정보를 알려주는데 사용된다. 이런 얘기는 그래픽컬 유저 인터페이스(GUI) 개잘자들에게는 모두다 알고 있는 따분한 옛 이야기일 뿐이지만 콘솔 또는 텍스트 기반의 어플리케이션만을 주로 만드는 개발자들에게는 감칠 맛 나는 얘기거리가 된다. 또한, VCL 은 윈도우 생성 과정의 대부분을 속에 감추고 있기 때문에, 숙련된 C++ 빌더 개발자들도 윈도우를 커스터마이즈 할 때 또는 문제를 해결 할 때 많은 어려움을 겪는다. 이런 이유로, 이 문서에서는 윈도우즈 API 로 윈도우를 생성하는 과정을 설명하고 나서, VCL 이 어떻게 윈도우 생성 과정을 포장해 놓았는지 하나하나씩 벗겨볼 것이다.

1-1. CreateWindow 함수

모든 윈도우 형식의 컨트롤들은 CreateWindow API 함수를 사용해서 만들 수 있다. API 의 구현은 모든 컨트롤들이 윈도우 형식의 컨트롤인데 비쥬얼한게 하나도 없다. 그래서 모든 컨트롤들을 코드를 작성해서 만들어야 한다. 버튼, 에디트 컨트롤 같은 간단한 컨트롤들을 만들기 위해서는 CreateWindow() 함수를 한번만 호출해 주면 되는데 이 컨트롤들은 윈도우즈에 미리 등록되어 있는 컨트롤이기 때문이다. 우선, CreateWindow 함수를 살펴보자.:

HWND CreateWindow(
LPCTSTR lpClassName,
LPCTSTR lpWindowName,
DWORD dwStyle,
int x,
int y,
int nWidth,
int nHeight,
HWND hWndParent,
HMENU hMenu,
HANDLE hInstance,
LPVOID lpParam
);

이 함수는 7개의 인자를 필요로하고, 새롭게 생성된 윈도우의 핸들을 반환한다. 첫 인자인 lpClassName 은 만들어질 윈도우 클래스의 이름(문자열)을 가리키는 포인터이다. 여기서 이 윈도우 클래스의 이름은 CreateWindow 함수가 호출되기 전에 윈도우즈 운영체제에 등록되어 있어야한다. 윈도우즈 운영체제는 CreateWindow() 함수에 전달되는 모든 윈도우 클래스의 이름을 알지 못하며 해당 클래스의 윈도우를 어떻게 만들어야하는지도 모른다. 사실, 윈도우즈 API 는 특정 종류의 컨트롤들을 운영체제에 미리 등록해 놓았기 때문에 CreateWindow() 함수만 호출해서 윈도우 컨트롤을 만들 수 있는 것이다. 이 특정 윈도우 컨트롤들을 표준컨트롤, 공통컨트롤이라고 한다.

1-2. 컨트롤 클래스의 등록

윈도우즈 API 는 운영체제에 미리 등록된 컨트롤 클래스를 제공하기 때문에 컨트롤 클래스를 다시 등록하지 않고 특정 컨트롤을 사용할 수 있다. 이 컨트롤들은 API 의 사용자 인터페이스 서비스의 일부분으로 윈도우즈 운영체제에 포함되어 있다. 버튼, 에디트 컨트롤(edit control), 스태틱 컨트롤(static control), 콤보박스(combo box), 목록 박스(list box) 와 같은 표준 컨트롤은 운영체제가 시작될 때 미리 등록된다. 예를 들어서, 버튼을 생성하려면 CreateWindow() 함수의 lpClassName 인자에 "BUTTON" 값을 넘겨주기만하면 된다. 표준컨트롤과 비슷한 공통컨트롤이 있는데 목록뷰(list view), 트리뷰(tree view), 헤더 컨트롤(header control), 탭 컨트롤(tab control), 툴바(toolbar) 컨트롤을 가지고 있다. 이 공통 컨트롤은 InitCommonControlEx() API 함수를 호출하면 운영체제에 등록된다. 그렇다면 사용자 컨트롤을 만들고 싶을 때는 어떻게 해야할까? 우선, 사용자 컨트롤의 클래스를 RegisterClass() API 함수를 사용해서 운영체제에 등록해야한다.

ATOM RegisterClass(const WNDCLASS *lpWndClass);

RegisterClass() 함수는 한개의 인자만 받는데, 윈도우 클래스의 속성을 정의하는 WNDCLASS 구조체의 포인터이다. WNDCLASS 구조체는 아래처럼 정의되어 있다.

typedef struct _WNDCLASS {
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HANDLE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCTSTR lpszMenuName;
LPCTSTR lpszClassName;
} WNDCLASS;

WNDCLASS 구조체의 멤버 데이타 중 몇개는 익숙할텐데, 예를 들어 lpfnWndProc 은 윈도우 프로시져의 포인터이고, hIcon과 hCursor 은 윈도우가 사용할 기본 아이콘과 마우스 커서의 핸들이다. hbrBackground 는 윈도우의 배경을 칠할때 사용하는 브러쉬의 핸들이고, 가장 중요한 마지막 데이타 멤버인 lpszClassName 은 윈도우의 클래스 이름을 정의한다.( 예: "TForm1" )

WNDCLASS 구조체가 알맞게 초기화 되면, 구조체의 주소를 RegisterClass() 함수의 인자로 전달한다. 이제, "TMyForm" 인 새로운 윈도우를 생성하는 예제를 살펴보자. 목록 2.0은 이 예제의 코드다.

목록 2.0

//----------------------------------------------------------------------
#include <windows.h>
//----------------------------------------------------------------------

// our new window class name
const char NewClassName[] = "TMyForm";
//----------------------------------------------------------------------

// main window procedure
LRESULT CALLBACK WndProc(HWND HWnd, unsigned int Msg, WPARAM WParam, LPARAM
  LParam)
{
// this window will actually
  do no special processing
// except terminate the application when destroyed
switch (Msg)
{
case WM_DESTROY:
{
//
  will place WM_QUIT in our message queue
// (strictly
  speaking, the QS_QUIT flag)
PostQuitMessage(0);
return
  0;
}
}
return DefWindowProc(HWnd, Msg, WParam, LParam);
}
//----------------------------------------------------------------------

int APIENTRY WinMain(HINSTANCE HInstance, HINSTANCE HPrevInstance, LPTSTR lpCmdLine,
  int nCmdShow)
{
WNDCLASS wc;
memset(&wc, 0, sizeof(WNDCLASS));

// initialize the WNDCLASS
  structure
wc.style = CS_VREDRAW | CS_HREDRAW | CS_DBLCLKS;
wc.lpfnWndProc = WndProc;
wc.hInstance = HInstance;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = GetStockObject(LTGRAY_BRUSH);
wc.lpszClassName = NewClassName;

// register our new class of window
if (RegisterClass(&wc))
{
//
  once registered, we are free to create the window
HWND HWnd = CreateWindow(NewClassName,
  "MyForm1",
WS_OVERLAPPEDWINDOW
  | WS_CAPTION | WS_VISIBLE,
CW_USEDEFAULT,
  CW_USEDEFAULT,
CW_USEDEFAULT,
  CW_USEDEFAULT,
NULL,
  NULL, HInstance, NULL);

if (HWnd)
{
//
  show the new window
ShowWindow(HWnd,
  nCmdShow);
UpdateWindow(HWnd);

//
  main message pump
MSG
  msg;

while
  (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
return 0;
}
//----------------------------------------------------------------------

목록 2.0 에 있는 WinMain 함수는 VCL 이 만들어 준 WinMain 함수( "From Message to Events" 문서에서 목록 1.4 의 소스코드 )와 아주 똑같지만 TMyForm 은 새로운 윈도우 클래스이고 미리 등록된 클래스가 아니기 때문에 기본 윈도우 프로시져를 만들어줘야 한다. 이 윈도우 프로시져에 TMyForm 윈도우의 행동과 속성을 정의하는데, 목록 2.0 에 정의된 윈도우 프로시져(WndProc) 는 DefWindowProc() API 함수만 호출한다. 이 말은 새로운 종류인 TMyFrom 윈도우가 표준 윈도우와 똑같이 행동한다는 것이다.

목록 2.0 에 정의된 WinMain 함수를 보면, 구현이 세단계로 되어 있다. 첫 단계는, WNDCLASS 구조체를 선언하고 초기화한다. 두번째 단계는, RegisterWindow() API 함수에 구조체의 주소를 전달해 호출한다. 마지막 세번째 단계는 클래스의 등록이 성공한 후에, CreateWindow() 함수에 새롭게 등록된 클래스의 이름을 lpClassName 인자에 전달해서 호출한다. 모든 윈도우 컨트롤을 만들기 위해서 이 세단계를 반드시 거쳐야한다.

확실하게, 캡슐화 된 것이 하나 없는 이 과정은 아주 귀찮고 버그가 발생하면 발견하기도 어렵다. 그러나 행복하게도, TWinControl 클래스가 이 과정의 전부를 캡슐화 했고 이에 대해서는 다음 섹션에서 알아볼 것이다. 우선은 CreateWindow() 함수에 대해서 더 알아보자.

HWND CreateWindow(
LPCTSTR lpClassName,
LPCTSTR lpWindowName,
DWORD dwStyle,
int x,
int y,
int nWidth,
int nHeight,
HWND hWndParent,
HMENU hMenu,
HANDLE hInstance,
LPVOID lpParam
);

두번째 인자인, lpWindowName 은 윈도우의 캡션을 나나태는 문자열값이다. C++ 빌더에서, 어플리케이션의 첫번재 폼은 lpWindowName 에 "Form1" 이란 문자열이 설정된다. dwStyle 인자는 여러가지 윈도우의 속성을 설정하기 위해서 사용한다. 예를 들어서, WS_OVERLAPPED 스타일은 표준 탑레벨(top-level) 윈도우를 만들 때 사용하느데 TForm 클래스가 기본 스타일로 사용된다. WS_POPUP 스타일은 캡션과 경계(border) 가 없는 윈도우를 만들어 준다. 이미 알고 있을지 모르지만, TForm::BorderStyle 속성은 윈도우의 여러가지 경계(border) 스타일을 만들 때 사용할 수 있다. 이 때 이 속성에 설정되는 값이 dwStyle 인자에 전달되는 것이다. 비슷하게, x, y, nWidth, nHeight 인자는 폼의 Top, Left, Width, Height 속성이 설정될 때 속성 값이 전달되는 것이다. 윈도우가 자식윈도우로 만들어 진다면, hWndParent 인자가 윈도우의 부모를 가리키기 위해 사용된다. 탑레벨(top-level) 윈도우에서 이 값은 데스크탑(바탕화면)의 핸들로 설정된다. 이 때, 데스크탑(바탕화면) 윈도우의 핸들은 hWndParent 에 NULL 값을 설정하거나 GetDesktopWindow() API 함수를 호출해서 얻을 수 있다. TFrom 클래스는 이 hWndParent 의 값으로 TApplication 윈도우의 핸들을 설정한다. hMenu 인자는 윈도우의 메인 메뉴의 핸들로 사용된다. hInstance 인자는 어플리케이션의 인스턴스 핸들로 전역 변수인 HInstance 를 통해서 설정할 수 있다. 마지막 인자인 lpParam 은 "사용자 정의"의 추가 정보를 저장할 때 사용한다.

지금까지 미리 등록된 클래스가 바탕이 아닌 새로운 윈도우 클래를 만들어 보았다. 지금부터는 운영체제에 미리 등록된 클래스를 바탕으로 새로운 윈도우 클래스를 만들어 주는 슈퍼클래싱에 대해서 알아보자. VCL 의 TButton 클래스는 사실 표준 컨트롤인 BUTTON 을 바탕으로 새로운 클래스로 만들어진 것이다. 이 경우에, TButton 과 TWinControl 클래스는 슈퍼클래싱을 수행한다. 마이크로소프트의 Spy++ 툴을 사용해서, TButton 의 인스턴스를 조사해 보면 클래스 이름이 "BUTTON" 이 아니라 "TButton" 으로 되어 있는 것을 확인할 수 있다.

1-3. 슈퍼클래싱

위에서 말했듯이, 슈퍼클래싱 기술은 미리 등록된 클래스를 바탕으로 새로운 윈도우 클래스를 만드는 것이다. 그냥 새롭게 윈도우 클래스를 만들 때와 비슷하게 슈퍼클래싱을 하기 위해서도 WNDCLASS 구조체를 선언하고 새로 만들 클래스의 특징으로 WNDCLASS 를 초기화해야한다. 차이점은 부모클래스로부터 WNDCLASS 의 정보를 얻기 위해서 GetClassInfo() API 함수를 먼저 호출한다는 것이다.

bool GetClassInfo(
HINSTANCE hInstance,
LPCTSTR lpClassName,
LPWNDCLASS lpWndClass
);

GetClassInfo() 함수에 WNDCLASS 변수를 전달해 호출하면, 부모클래스의 기본 윈도우 프로시져의 주소가 lpfnWndProc 데이타 멤버에 저장된다. 이 값은 슈퍼클래스(새롭게 만들어지는)의 윈도우 프로시져로 사용된다. 이 단계는 "From Messages to Events" 에서 원래의 윈도우 프로시져를 다른 윈도우 프로시져와 바꿨던 인스턴스 서브클래싱의 예제(목록 1.18)와 비슷하다. 서브클래싱에서는 교체된 윈도우 프로시져를 "서브클래스 프로시져"라고 했는데, 슈퍼클래싱에서는 새로운 윈도우 프로시져를 "슈퍼클래스 프로시져" 라고 한다. 기반 클래스의 윈도우 프로시져를 저장하고 나서, 슈퍼클래스 프로시져가 WNDCLASS 의 lpfnWndProc 멤버에 설정된다. 마지막으로 어플리케이션의 인스턴스 핸들(hInstance)을 hInstance 인자에 설정하고 lpClassName 인자에 새로운 클래스의 이름을 설정한다.

표준컨트롤인 BUTTON 을 슈퍼클래싱하는 예제를 살펴보며 알아보자. 전과정이 목록 2.1 에 있고, 이때 슈퍼클래스의 이름을 "TMyButton" 으로 했다.

목록 2.1

//-----------------------------------------------------------------------
#include <windows.h>
//-----------------------------------------------------------------------

// our new window class name
const char NewFormClassName[] = "TMyForm";

// our new button class name
const char NewButtonClassName[] = "TMyButton";

// will hold the address of the base (BUTTON)
// class's default window procedure
FARPROC ButtonDefWndProc = NULL;

//----------------------------------------------------------------------
// main window procedure
LRESULT CALLBACK FormWndProc(HWND HWnd, unsigned int Msg, WPARAM WParam, LPARAM
  LParam)
{
// this window will actually do
  no special processing
// except terminate the application when destroyed
switch (Msg)
{
case WM_DESTROY:
{
//
  will place WM_QUIT in our message queue
// (strictly
  speaking, the QS_QUIT flag)
PostQuitMessage(0);
return
  0;
}
}
return DefWindowProc(HWnd, Msg, WParam, LParam);
}

//----------------------------------------------------------------------
// button window procedure
LRESULT CALLBACK ButtonWndProc(HWND HWnd, unsigned int Msg, WPARAM WParam, LPARAM
  LParam)
{
//
// this window will actually do no special processing
//
// call the original BUTTON window procedure
return CallWindowProc(ButtonDefWndProc, HWnd, Msg, WParam,
  LParam);
}

//----------------------------------------------------------------------
int APIENTRY WinMain(HINSTANCE HInstance, HINSTANCE HPrevInstance, LPTSTR lpCmdLine,
  int nCmdShow)
{
WNDCLASS form_wc, button_wc;
memset(&form_wc, 0, sizeof(WNDCLASS));
memset(&button_wc, 0, sizeof(WNDCLASS));

// initialize the Form's WNDCLASS structure
form_wc.style = CS_VREDRAW
  | CS_HREDRAW | CS_DBLCLKS;
form_wc.lpfnWndProc = FormWndProc;
form_wc.hInstance = HInstance;
form_wc.hCursor = LoadCursor(NULL,
  IDC_ARROW);
form_wc.hbrBackground =
  GetStockObject(LTGRAY_BRUSH);
form_wc.lpszClassName =
  NewFormClassName;

// initialize a WNDCLASS structure
  for the "BUTTON"
// base class via the GetClassInfo API function
if (!GetClassInfo(NULL,
  "BUTTON", &button_wc)) return false;

// store the address of the original
  window procedure
ButtonDefWndProc = reinterpret_cast<FARPROC>(button_wc.lpfnWndProc);
// specify the address of the subclass
  procedure
button_wc.lpfnWndProc =
  ButtonWndProc;

// initialize the rest of our button's WNDCLASS
// structure for superclassing
button_wc.hInstance = HInstance;
button_wc.lpszClassName
  = NewButtonClassName;

// register our new class of window and button
if (RegisterClass(&form_wc)
  && RegisterClass(&button_wc))
{
// once registered,
  we are free to create the window
HWND HWnd = CreateWindow(NewFormClassName, "MyForm1",
WS_OVERLAPPEDWINDOW | WS_CAPTION | WS_VISIBLE,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, HInstance, NULL);

if (HWnd)
{
// create the new button
CreateWindow(NewButtonClassName,
  "MyButton1",
WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
10, 10, 200, 75,
HWnd, NULL, HInstance, NULL);

// show the new window
ShowWindow(HWnd,
  nCmdShow);
UpdateWindow(HWnd);

// main message pump
MSG msg;
while (GetMessage(&msg,
  NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
return 0;
}
//----------------------------------------------------------------------

목록 2.0 에서 본 구현과 비슷하게, 목록 2.1 에 있는 WinMain 함수의 구현은 우선 TMyForm 클래스를 위한 WNDCLASS 변수를 선언하고 초기화했다. 그러나 목록 2.1 에서는 새로운 버튼 클래스를 만들기 위해서 button_wc 란 WNDCLASS 변수를 하나더 선언했다. button_wc 의 데이타 멤버를 일일이 설정하는 대신에, GetClassInfo() API 함수를 호출했다. 이때, hInstance 인자에는 NULL 을 전달하고, 부모클래스의 클래스 이름을 나타내는 lpClassName 인자에는 "BUTTON" 을 전달했다. 마지막의 lpWndClass 인자에는 button_wc 의 주소값을 전달했다. hInstance 인자에 NULL 을 전달한 것은 BUTTON 클래스가 윈도우즈 운영체제에 이미 등록되어 있기 때문이다. GetClassInfo() 함수가 수행되고 나면, button_wc 는 BUTTON 클래스를 등록할 때 사용한 정보로 초기화된다. 위의 코드에서는 부모 클래스의 기본 윈도우 프로시져를 ButtonDefWndProc 변수에 저장했다. 그 다음 button_wc 의 hInstance 데이타 멤버를 우리가 만들고 있는 어플리케이션의 인스턴스 핸들로 바꿨다. 그리고 lpfnWndProc 값은 슈퍼클래스 프로시져 주소값으로 설정되었고 lpClassName 데이타 멤버는 새로운 클래스 이름인 "TMyButton" 으로 설정되었다. 두개의 새로운 클래스를 등록한 다음에, 새로운 TMyForm 윈도우를 만들고 CreateWindow() 함수를 사용해서 새로운 TMyButton 윈도우 컨트롤을 만들었다. TMyButton 컨트롤을 만들 때 CreateWindow() 함수에 WS_CHILD 와 BS_PUSHDOWN 스타일을 설정했다. 이 것은 BUTTON 클래스의 스타일인데, TMyButton 클래스의 부모 클래스가 BUTTON 클래스이기 때문에 BUTTON 클래스의 스타일을 그대로 쓸 수 있는 것이다. 설정한 스타일의 뜻은 TMyButton 컨트롤이 다른 윈도우(이 예제에서는 TMyForm)의 자식이라는 것이다. 자식 윈도우를 대개 "윈도우 컨트롤" 또는 간단하게 "컨트롤" 이라고 한다. 이 예제에서 TMyForm 이 TMyButton 클래싕 부모이기 때문에, TMyButton 를 만들 때 CreateWindow() API 함수의 hWndParent 의 인자에 TMyForm 윈도우의 핸들인 HWnd 값을 설정했다.

VCL 은 슈퍼클래싱을 완벽하게 지원한다. 즉, TWinControl 에서 파생된 컨트롤 중 존재하는 컨트롤 클래스를 확장한 컨트롤들은 모두 슈퍼클래싱을 사용하고 있는 것이다. 예를 들어서, TListBox 클래스는 표준 컨트롤 클래스인 LISTBOX 를 슈퍼클래싱 하고 있는 것이고 TListView 클래스는 공통 컨트롤인 WC_LISTVIEW 를 슈퍼클래싱 하는 것이다.

지금쯤 CreateWindow() API 함수에 매우 익숙해져 있을 것이다. 하지만 이 함수를 잊고 살아도 된다. 마찬가리로 RegisterWindow() 함수를 WNDCLASS 구조체를 초기화하는데 호출할 경우가 거의 없다. 이 함수들 없이도 슈퍼클래싱을 멋지게 할 수 있다. 왜냐하면, TWinControl 클래스가 이러한 작업(윈도우 생성, 슈퍼클래싱)의 모든 과정을 포함하고 있기 때문이다. TWinControl 클래스가 바로 VCL 프레임웤이 제공하는 추상화의 수준이다. 이러한 RAD(Rapid Application Development) 를 만들기 위한 캡슐화 때문에 C++ 빌더가 계속해서 강세를 보이고 있는 것이다. VCL 은 강력한 프레임웤과 사용하기 쉬운 점 외에도, 아주 뛰어난 설계, 윈도우즈 API 와의 유연성 등을 골고루 갖추고 있다. 게다가 곧 알게 되겠지만, VCL 은 절대로 로우레벨의 API 를 다루는 것을 제한하지 않는다.

II. TWinControl 클래스를 사용해서 윈도우 만들기

지금까지, CreateWindow() 와 RegisterClass() API 함수에 대해서 알아보았다. 알아본 것 중에 가장 중요한 것은, 윈도우 컨트롤을 만들기 위해서는 WNDCLASS 구조체를 만들고 초기화한 다음에 윈도우즈 운영체제에 등록해야 한다는 점이었다. 또한, 기존의 컨트롤 클래스를 슈퍼클래싱하는 단계를 살펴보았다. 그럼 이제 TWinControl 클래스가 이러한 작업을 가상 멤버 함수에 심어 놨는지 살펴보자. 이 번 단원에서는 TWinControl 클래스의 멤버 함수 중에서 윈도우 생성과 슈퍼클래싱에 필요한 멤버 함수들에 대해서 알아볼 것이다. 앞에서 API 를 이용해 구현해 봤기 때문에, TWinControl 로 이뤄지는 구현은 별로 어렵지 않다.

2-1. The CreateParams Function CreateParams 함수

자꾸 말해서 미안하지만, 사용자 정의의 윈도우 컨트롤을 만들기 위해서 첫번째로 해야할 것은 윈도우 클래스를 등록하는 것이었다. 특히, WNDCLASS 구조체를 알맞게 설정해야 하는데 이 일을 TWinControl::CreateParams() 멤버 함수를 사용해서 할 수 있다. 이 함수는 1 개의 인자만 받는데 TCreateParams 구조체 값이다.

struct TCreateParams
{
char *Caption;
long Style;
long ExStyle;
int X;
int Y;
int Width;
int Height;
HWND WndParent;
void *Param;
WNDCLASSA WindowClass;
char WinClassName[64];
};

이 구조체를 살펴보면, 윈도우를 생성할 때 필요한 모든 정보가 있다는 것을 알 수가 있다. 즉, TCreateParams 구조체는 이름대로 윈도우 "생성 인자(Creation Parameters)" 모두를 가지고 있는 것이다. 특히, 이 구조체는 다음 세단계에서 필요한 모든 정보를 가지고 있다. (1) WNDCLASS 설정 (2) RegisterWnd() (3) CreateWindow. 또한 TCreatePArams 구조체의 멤버들은 CreateWindow API 함수의 인자들과 서로 대응한다. TWinControl::CreateParams() 멤버 함수는 이 TCreateParams 구초체를 알맞게 설정한다. 목록 2.2 를 보면 C++ 로 표현된 TWinControl::CreateParams() 멤버 함수가 있다.

목록 2.2

void __fastcall TWinControl::CreateParams(TCreateParams &Params)
{
memset(&Params, 0, sizeof(TCreateParams));

//
// <snip> initialize:
// Params.Caption (with FText);
// Params.Style with WS_* flags (e.g., WS_CHILD)
// Params.X with FLeft;
// Params.Y with FTop;
// Params.Width with FWidth;
// Params.Height with FHeight;
// Params.WndParent = Parent->Handle;
//

Params.WindowClass.style
  = CS_VREDRAW | CS_HREDRAW | CS_DBLCLKS;
Params.WindowClass.lpfnWndProc
  = DefWindowProc;
Params.WindowClass.hInstance
  = HInstance;
Params.WindowClass.hCursor
  = LoadCursor(NULL, IDC_ARROW);
Params.WindowClass.hbrBackground
  = NULL;
strcpy(Params.WinClassName,
  AnsiString(ClassName()).c_str());
}

목록 2.2 에 정의된 TWinControl::CreateParams() 함수를 보면 TCreateParms 구조체의 대부분의 멤버를 초기화한다. 자세한 구현 사항은 TWinControl 에서 파생된 클래스들을 볼 때 살펴보기로 하고, 우선 두가지 중요한 점을 알아보자. 첫째, TCreateParams::WindowClass(WNDCLASS 형)구조체의 멤버인 lpfnWndProc 의 값이 DefWindowProc() API 함수의 주소로 설정되었는데 이 구현은 아무런 문제가 없다. 왜냐하면 DefWindowProc() 함수는 모든 윈도우의 윈도우 프로시져로 사용할 수 있기 때문이다. TWinControl 클래스는 이 주소값을 임시 값으로 사용하며, 현재 윈도우 클래스가 이미 등록되어 있는지 아닌지를 구분하기 위해서 사용한다. 둘째, WindowClass 의 lpszClassName 멤버가 전혀 초기화되지 않았다. 오히려 클래스의 이름이 TCreateParams::WinClassName 멤버에 저장되었다. 이 이유는 다음에 설명하고 우선, 이렇게 하는 것이 더 효율적이다라고 알아두자.

2-2. CreateWnd 함수

TCreateParams 구조체의 WindowClass 멤버를 설정한 후에 RegisterClass() API 함수로 등록해야 한다. 이 일을 CreateWnd() 멤버 함수가 수행한다. 이 함수내에서 윈도우 생성 메카니즘이 모두 수행된다. 즉. 윈도우 생성에 필요한 모든 TWinControl 클래스의 멤버 함수가 CreateWnd() 함수 내에서 호출된다. 목록 2.3 에 C++ 로 나타낸 TWinControl::CreateWnd() 멤버 함수가 있다.

목록 2.3

void __fastcall TWinControl::CreateWnd()
{
TCreateParams Params;
CreateParams(Params);

if (Params.WndParent ==
  NULL && (Params.Style & WS_CHILD))
{
throw EInvalidOperation(SParentRequired, ARRAYOFCONST((Name)));
}

FDefWndProc = Params.WindowClass.lpfnWndProc;

WNDCLASS dummyClass;
bool IsClassRegistered =
  GetClassInfo(
Params.WindowClass.hInstance,
Params.WinClassName,
&dummyClass
);

if (!IsClassRegistered ||
  TempClass.lpfnWndProc != reinterpret_cast<WNDPROC>(InitWndProc))
{
if (IsClassRegistered)
{
UnregisterClass(Params.WinClassName,
  Params.WindowClass.hInstance);
}

Params.WindowClass.lpfnWndProc = reinterpret_cast<WNDPROC>(InitWndProc);
Params.WindowClass.lpszClassName = Params.WinClassName;
}

CreateWindowHandle(Params);

// <snip> font and bounds
  stuff...
}

CreateWnd() 멤버 함수는 알맞게 설정된 TCreateParmas 구조체 값이 필요하기 때문에, 제일 먼저 TWinControl::CreateParams() 멤버 함수를 호출한다. 그 다음에 컨트롤이 다른 윈도우의 자식일 때 WndParent 멤버의 유효성을 살펴서 문제가 있다면 "Control <ControlName> has no Parent" 라는 예외를 일으킨다. 기본 윈도우 프로시져의 주소가 FDefWndProc 멤버( TWinControl 의 DefWndProc 속성을 통해서 접근 가능하다)에 저장되고 GetClassInfo() API 함수가 호출된다. 이 때 임시 WNDCLASS 변수를 사용하는데 이는 현재 클래스가 이미 등록된 클래스인지를 구분하기 위해서다. 만약에 클래스가 이미 등록된 것이 아니거나 등록된 클래스의 기본 윈도우 프로시져의 주소값이 InitWndProc() 함수의 반환값과 틀리면, 새로운 클래스가 등록된다. if 의 조건문이 참이 되면, 먼저 등록된 클래스를 먼저 UnregsterClass() API 함수를 사용해서 등록 해제한다. 그 다음, TWinControl::CreateWindowHandle() 멤버 함수에 Params 값을 전달해서 호출한다.

각각의 구현에 대해서 깊게 알려고 하지 말고 대충 무슨 일이 일어나지만 알아두길 바란다. 즉, CreateWnd() 멤버 함수의 주된 기증은 새로운 윈도우 클래스를 RegisterClass() API 함수를 이용해서 등록하는 것이다. Params 변수의 초기화는 CreateParams() 함수가 수행하고 실제 윈도우 생성은 CreateWindowHandle() 함수가 처리한다.

2-3. CreateWindowHandle 함수

앞에서 말했듯이, CreateWindowHandle() 멤버 함수가 CreateWindow() API 함수를 사용해서 실제 윈도우를 만든다. 사실, CreateWindowHandle() 함수는 CreateWindowEx() API 함수를 사용한다. 함수의 이름대로, 이 함수는 CreateWindow() 함수의 확장판으로 더 많은 윈도우 스타일(확장 윈도우 스타일)을 설정할 수 있다. 이는 TCreateParams 구조체의 ExStyle 데이타 멤버를 알맞게 설정하면 된다.

HWND CreateWindowEx(
DWORD dwExStyle,
LPCTSTR lpClassName,
LPCTSTR lpWindowName,
DWORD dwStyle,
int x,
int y,
int nWidth,
int nHeight,
HWND hWndParent,
HMENU hMenu,
HINSTANCE hInstance,
LPVOID lpParam
);

목록 2.4에 C++ 로 표현한 CreateWindowHandle() 함수가 있는데, CreateWindowEx() API 함수만 호출하는 것을 볼 수 있다.

목록 2.4

void __fastcall TWinControl::CreateWindowHandle(const TCreateParams &Params)
{
FHandle = CreateWindowEx(
Params.ExStyle,
Params.WinClassName,
Params.Caption,
Params.Style,
Params.X, Params.Y,
Params.Width, Params.Height,
Params.WndParent, NULL,
Params.WindowClass.hInstance,
Params.Param
);
}

목록 2.3 에 정의된 CreateWindowHandle() 함수를 살펴보면 실제 윈도우를 만든다는 것을 알 수 있다. 이 일은 간단하게 CreateWindowEx() API 함수를 호출해서 이뤄지고 이때, 초기화된 Params 의 멤버를 함수의 인자로 전달한다. CreateWindow() API 함수처럼 CreateWindowEx() 함수도 새롭게 만들어진 윈도우 핸들을 반환한다. 이 값은 FHandle 멤버에 저장되는데 TWinControl::Handle 속성을 통해서 접근할 수 있다.

지금까지 살펴본 세가지 멤버 함수인, CreateParams(), CreateWnd(), CreateWindowHandle() 로 TWinControl 클래스는 윈도우 생성 메카니즘을 제공한다. CreateParams() 함수는 TCreateParams 구조체를 알맞게 설정하고, CreateWnd() 함수는 새 윈도우 클래스를 등록하며 CreateWindowHandle() 함수는 실제 윈도우을 만든다. 이 세 단계가 새로운 윈도우 클래스를 만들기 위해서 필요하다. 사실, 이 세가지 멤버 함수는 하나의 함수로 합칠 수 있다. 하지만 앞에서도 얘기했듯이, TWinControl 에서 파생된 클래스 대부분이 기존의 윈도우 클래스를 바탕으로 만들어진 것이다. 우리는 이 것이 슈퍼클래싱이란 기술을 통해서 이뤄진다는 것을 알고 있다. 그럼 지금부터 TWinControl 클래스가 어떻게 슈퍼클래싱을 캡슐화했는지 알아보자.

2-4. TWinControl 클래스로 슈퍼클래싱하기

기존의 컨트롤 클래스를 슈퍼클래싱하기 위해서 피룡한 단계를 생각해보면 첫째, GetClassInfo() 함수를 사용해서 기존 윈도우 클래스의 정보를 WNDCLASS 구조체에 담아야한다. 둘째, 클래스의 기본 윈도우 프로시져 주소값을 저장하고 새롭게 만든 윈도우 프로시져 주소값으로 교체해야한다. 셋째, WNDCLASS 데이타 멤버인 hInstance 와 lpClassName 을 알맞게 바꿔야한다. 곧 알게 되겠지만, 이 세가지 단계는 TWinControl 의 멤버 함수 하나로 이뤄진다.

CreateParams() 멤버 함수는 TCreateParams::WindowClass 의 멤버인 hInstance 를 설정하는 것으로 슈퍼클래싱에도 사용된다. 목록 2.1 의 구현처럼, CreateParams() 멤버 함수는 어플리케이션의 인스턴스인 HInstance 를 hInstance 데이타 멤버에 설정한다. CreateWnd() 멤버 함수 또한 슈퍼클래싱에 사용되는데 기본 윈도우 프로시져를 FDefWndProc 멤버에 저장하며, 슈퍼클래스 프로시져를 lpfnWndProc 멤버에 설정하고, lpszClassName 멤버에 새로운 윈도우 클래스 이름을 설정한다. 그럼 남은 일이 무엇일까? 지금까지의 구현을 보면 기존 클래스의 정보를 TCreateParams::WindowClass 멤버에 설정하기 위해서 GetClassInfo() 함수를 호출하는 곳이 없다. 이 일은 TWinControl::CreateSubClass 멤버 함수가 수행한다.

함수 이름에 맞기 않게, CreateSubClass() 멤버 함수는 CreateParams() 또는 CreateWnd() 함수에서 하지 않은 슈퍼클래싱의 나머지 작업을 처리한다. 즉, CreateSubClass() 멤버 함수의 역할은 TCreateParams::WindowClass 데이타 멤버를 기존 윈도우 클래스의 정보로 설정하는 것이다. 목록 2.5 에 C++ 로 표현한 TWinControl::CreateSubClass() 멤버 함수의 코드가 있다.

목록 2.5

void __fastcall TWinControl::CreateSubClass(TCreateParams &Params, char
  *ControlClassName)
{
if (ControlClassName)
{
HINSTANCE SaveInstance = Params.WindowClass.hInstance;

if (!GetClassInfo(HInstance, ...) && !GetClassInfo(NULL, ...)
  && !GetClassInfo(MainInstance, ...))
{
GetClassInfo(Params.WindowClass.hInstance,
  ControlClassName, &Params.WindowClass);
}

Params.WindowClass.hInstance = SaveInstance;
}
}

CreateSubClass() 함수에 전달되는 ControlClassName 인자는 기반 클래스이 이름이다. 예를 들어서, 윈도우즈의 BUTTON 표준 컨트롤을 슈퍼클래싱을 하려면, ControlClassName 인자에 "BUTTON" 을 전달해야 한다. Params 인자는 CreateParams() 함수로 초기화한 TCreateParms 구조체 값이다. CreateParams() 함수는 CreateSubClass() 함수 이전에 호출되어야 한다. 만약, CreateParams() 함수가 CreateSUbClass() 함수보다 늦게 호출된다면, CreateParams() 함수가 CreateSubClass() 에 의해 초기화된 정보를 지워버리고 다시 쓸 것이다. 대개, CreateSubClass() 함수는 오버라이드(override)된 CreateParams() 함수 안에서 호출되는데, 부모 클래스의 CreateParams() 함수를 호출한다음에 CreateSubClass() 함수를 호출한다.

목록 2.5를 보면, CreateSubClass() 함수는 대부분 GetClassInfo() API 함수를 이용해서 TCreateParams::WindowClass 멤버를 설정하는 내용으로 되어 있다. CreateSubClass() 함수는 총 4 번에 걸쳐서 GetClassInfo() 함수를 호출하는데, 각각의 호출마다 hInstance 인자가 다르다. GetClassInfo() 함수에 전달된는 hInstance 값은 클래스가 등록되어 있는 어플리케이션의 인스턴스 핸들이다. CreateSubClass() 함수는 우선 전역 HInstance 변수를 사용해서 현재 어플리케이션에 기반 클래스가 등록되어 있는지 살펴본다. 첫번째 호출이 실패하면, hInstance 에 NULL 값을 주어 호출하는데, 이 것은 윈도우즈 운영체제에 기반 클래스가 등록되어 있는지를 살펴보는 것이다. 두번째 호출도 실패하면, hInstance 인자에 MainInstance 를 전달해 호출한다. MainInstance 는 DLL 이 사용될 경우 HInsatnce 와 다른 값을 가지게 된다. 세번에 걸친 호출이 모두 실패하면, 마지막 호출로, WNDCLASS::hInstance 데이타 멤버를 GetClassInfo() 함수에 전달해 호출한다. 이 경우에는 GetClassInfo() 함수를 위해서 WindowClass 데이타 멤버인 hInstance 에 어플리케이션의 핸들을 명시적으로 설정해서 CreateSubClass() 함수에 전달해야 한다.

슈퍼클래싱 작업을 여러 멤버함수로 쪼개 놓은 것이 비직관적일 수도 있다. 사실, CreateSubClass() 함수가 슈퍼클래싱 작업을 한꺼번에 처리하는 것이 더 논리적일 수도 있다. 곧 알게 되겠지만, 슈퍼클래싱 작업을 이렇게 나누는 것이 꼭 필요한데, TWinControl 클래스에서 파생된 컨트롤 중 기반 클래스를 슈퍼클래싱 하지 않는 것도 있기 때문이다. 예를 들면, TCustomGrid 클래스는 TWinControl 에서 파생된 것이지만, 기반 컨트롤 클래스가 없다. 그렇기 때문에, TCustomGrid::CreateParams() 멤버 함수의 구현을 보면 CreateSubClass() 함수를 호출하지 않는다. 하지만, TWinControl 그 자체는 일반(generic) 윈도우를 슈퍼클래싱한 것이다. 사실, TWinControl 은 작은 빈공간에, 경계(border)가 없는 자식 윈도우다. 그렇기 때문에 TWinControl::CreateParams() 구현에서 TCreateParams::Style 멤버에 WS_CHILD 스타일을 기본으로 사용한다.

이 멤버 함수들의 자세한 설명에 질려버릴 것 같다면, 잠시 쉬면서 정리해보자. 이 글의 주제는 CreateParams(), CreateWnd() 그리고 CreateWindowHandle() 멤버 함수가 새로운 클래스를 등록하고, 이 새로운 클래스의 윈도우를 만들어 준다는 것이다. 마찬가지로 CreateSubClass() 함수를 "슈퍼클래싱 유틸리티" 멤버 함수라고 생각하면 된다. 슈퍼클래싱을 확실하게 이해하기 위해서 이제 예제를 살펴보자. 이 예제는 목록 2.1 에서 만들어본 TMyButton 컨트롤을 TWinControl 클래스를 사용해서 만들어 본 것이다. 목록 2.6a 와 2.6b 에 전체 코드가 있다.

목록 2.6a

//----------------------------------------------------------------------
#ifndef SuperClassMyButtonH
#define SuperClassMyButtonH
//----------------------------------------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
//----------------------------------------------------------------------

class TMyButton : public TWinControl
{
protected:
virtual void __fastcall
  CreateParams(TCreateParams &Params);
public:
__fastcall TMyButton(TComponent
  *Owner) : TWinControl(Owner) {};
};
//----------------------------------------------------------------------

class TForm1 : public TForm
{
__published: // IDE-managed Components
private: // User declarations
TMyButton *MyButton;
Public: // User declarations
__fastcall TForm1(TComponent*
  Owner);
};
//----------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//----------------------------------------------------------------------
#endif

 목록 2.6b

//----------------------------------------------------------------------
#include <vcl\vcl.h>
#pragma hdrstop
#include "SuperClassMyButton.h"
//----------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//----------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
MyButton = new TMyButton(this);
MyButton->Parent = this;
MyButton->SetBounds(10,
  10, 200, 75);

// use SetWindowText to set the caption of the
// button since we have not published the
// TControl::Caption property
SetWindowText(MyButton->Handle,
  "MyButton1");
}
//----------------------------------------------------------------------
void __fastcall TMyButton::CreateParams(TCreateParams &Params)
{
// have the TWinControl::CreateParams
  member function
// perform its default initialization of Params
TWinControl::CreateParams(Params);

// superclass the Windows BUTTON Standard Control
CreateSubClass(Params, "BUTTON");
}
//----------------------------------------------------------------------

이 예제의 핵심은 TMyButton::CreateParams() 함수에 있다. 우선, 부모 클래스인 TWinControl 의 CreateParams() 함수를 호출해서 Params 인자를 알맞게 초기화한 다음 TWinControl::CreateSubClass() 멤버 함수에 초기화된 Params 값과 기반 컨트롤 클래스의 이름인 "BUTTON" 을 전달해서 호출했다.

이 예제는 TWinControl 클래스의 윈도우 생성 매카니즘을 자세히 살펴보는 일은 매우 어려웠지만, 멤버 함수를 사용하는 것은 매우 직관적인 것을 보여준다. 미리 정의된 컨트롤 클래스는 제한 되어 있기 때문에, CreateSubClass() 함수가 항상 필요한 것은 아니다.

3. 윈도우 생성의 커스터마이즈

클래스 설계 중 가장 중요한 것은 확장이 쉬워야 하는 것인데 이 것은 TWinControl 클래스의 아주 강한 면중 하나다. 혼합되어 사용할 수 있는 윈도우 스타일이 어마어마하지만, TWinControl 클래스는 이 모든 스타일을 아주 유연하게 다룰 수 있다. 미리 정의된 컨트롤은 몇개 안 되지만 각 컨트롤이 사용할 수 있는 스타일은 아주 많다. 예를 들어서 아래에 있는 스타일은 표준 컨트롤인 STATIC 이 사용하는 스타일이다.

SS_BITMAP, SS_BLACKFRAME, SS_BLACKRECT, SS_CENTER, SS_CENTERIMAGE, SS_ENHMETAFILE, SS_ETCHEDFRAME, SS_ETCHEDHORZ, SS_ETCHEDVERT, SS_GRAYFRAME, SS_GRAYRECT, SS_ICON, SS_LEFT, SS_LEFTNOWORDWRAP, SS_NOPREFIX, SS_NOTIFY, SS_OWNERDRAW, SS_REALSIZEIMAGE, SS_RIGHT, SS_RIGHTJUST, SS_ICON, SS_SIMPLE, SS_SUNKEN, SS_WHITEFRAME, SS_WHITERECT.

TWinControl 에서 파생된 클래스들은 CreateParams() 멤버 함수를 재정해서 윈도우 생성에 사용되는 여러 인자들을 설정한다. 예를 들어서, TCustomStaticText::CreateParams() 함수는 아주 많은 STATIC 컨트롤의 스타일을 다룬다. 목록 2.7 을 보자.

목록 2.7

void __fastcall TCustomStaticText::CreateParams(TCreateParams &Params)
{
TWinControl::CreateParams(Params);
CreateSubClass(Params, "STATIC");

Params.Style |= SS_NOTIFY;
switch (FAlignment)
{
case taLeftJustify:
{
Params.Style |= SS_LEFT;
break;
}
case taRightJustify:
{
Params.Style |= SS_RIGHT;
break;
}
case taCenter:
{
Params.Style |= SS_CENTER;
break;
}
}

// <snip> some right to left alignment support...

// BorderStyle...
switch (BorderStyle)
{
case sbsSingle:
{
Params.Style |= WS_BORDER;
break;
}
case sbsSunken:
{
Params.Style |= WS_BORDER;
break;
}
}
Params.WindowClass.style
  &= ~(CS_HREDRAW | CS_VREDRAW);
}

목록 2.7 에서 TCreateParams::Style 과 TCreateParams::WindowClass:style 데이타 멤버를 다루는 예를 볼 수 있다. 모든 커스터마이즈는 CreateParams() 멤버 함수 내에서 할 수 있다. 계속해서 윈도우 생성을 커스터마이즈하는 예제를 살펴보자.

3-1. Style 과 ExStyle 다루기

앞에서 말했듯이, CreateWindowEx() 함수에 전달되는 인자를 다뤄야할 때가 있다. 보통 dwStyle 과 dwExStyle 인자를 많이 다룬다. 하지만, CreateWindowEx() 함수를 직접 호출해야할 일이 없기 때문에, 어떻게 이 인자들을 다뤄야 하는가? TCreateParams 구조체의 데이타 멤버중 Style 과 ExStyle 가 CreateWindowEx() 함수의 dwStyle 과 dwExStyle 로 전달되기 때문에 TCreateParams 구조체의 Style 과 ExStyle 을 다루면 된다. 이 일은 대개 CreateParams 함수 내에서 이뤄진다.

떠있는(플롯팅) 도구바를 만들기 위해서 많이 사용하는 툴윈도우를 만들어보자. 툴윈도우는 TForm::BorderStyle 속성을 bsToolWindow 로 고치면 쉽게 만들 수 있지만, 이 예제에서는 이 것을 TForm::CreateParams() 멤버 함수에서 만들어 본다. 특히, 툴윈도우는 CreateWindowEx() 함수의 dwExStyle 인자에 WS_EX_TOOLWINDOW 를 설정해서 만들기 때문에, TCreateParams::ExStyle 를 알맞게 설정해줘야 한다. 목록 2.8a 와 2.8b 는 구현 코드다.

목록 2.8a

//----------------------------------------------------------------------
#ifndef ToolWindowH
#define ToolWindowH
//----------------------------------------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
//----------------------------------------------------------------------
class TForm1 : public TForm
{
__published: // IDE-managed Components
private: // User declarations
virtual void __fastcall
  CreateParams(TCreateParams &Params);
public: // User declarations
__fastcall TForm1(TComponent*
  Owner);
};
//----------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//----------------------------------------------------------------------
#endif

 목록 2.8b

//----------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "ToolWindow.h"
//----------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//----------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//----------------------------------------------------------------------
void __fastcall TForm1::CreateParams(TCreateParams &Params)
{
TForm::CreateParams(Params);
Params.ExStyle = Params.ExStyle
  | WS_EX_TOOLWINDOW;
}
//----------------------------------------------------------------------

목록 2.8b 에 있는 TForm1::CreateParams() 함수에서, 우선 부모클래스의 CreateParams() 멤버 함수를 호출했다. 그리고 나서 Params 변수의 ExStyle 데이타 멤버에 WS_EX_TOOLWINDOW 스타일로 설정했다. 사실, 이 것은 BorderStyle 속성이 bsToolWindow 로 설정될 때 수행하는 일과 같은 것이다. 이 예제는 BorderStyle 속성을 다루는 것으로 더 쉽게 만들 수 있기 때문에 더 좋은 예제를 만들어보자.

그림 2.0 과 같이 파일의 속성을 알려주는 간단한 폼을 생각해보자.

탐색기에서 폼으로 파일을 끌어다놓기(드래그앤드롭 drag and drop)가 되도록 만들 것이다. "From Message to Event" 에서 DragAcceptFiles() API 함수를 드롭 목표 윈도우를 정할 때 사용할 수 있었다. 사실, DragAcceptFiles() 함수를 사용하지 않고 목표 윈도우를 설정하는 다른 방법이 있는데, WS_EX_ACCEPTFILES 확장 윈도우 스타일을 설정하면 해당 윈도우가 드롭 목표 윈도우로 만들어진다. 위의 예제처럼, TCreateParms::ExStyle 만 간단히 수정하면 된다. 목록 2.9a 와 2.9b 는 구현 코드다.

목록 2.9a

//----------------------------------------------------------------------
#ifndef DragDropH
#define DragDropH
//----------------------------------------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include <ExtCtrls.hpp>
//----------------------------------------------------------------------
class TForm1 : public TForm
{
__published: // IDE-managed Components
TLabel *NameLabel;
TLabel *SizeLabel;
TLabel *DateLabel;
TBevel *Bevel1;
protected:
virtual void __fastcall
  CreateParams(TCreateParams &Params);
private: // User declarations
void __fastcall WMDropFiles(TMessage
  &Msg);
public: // User declarations
__fastcall TForm1(TComponent*
  Owner);

BEGIN_MESSAGE_MAP
MESSAGE_HANDLER(WM_DROPFILES,
  TMessage, WMDropFiles)
END_MESSAGE_MAP(TForm)
};
//----------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//----------------------------------------------------------------------
#endif

 목록 2.9b

//----------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include <memory>
#include "DragDrop.h"
//----------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//----------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//----------------------------------------------------------------------
void __fastcall TForm1::CreateParams(TCreateParams &Params)
{
TForm::CreateParams(Params);
Params.ExStyle = Params.ExStyle
  | WS_EX_ACCEPTFILES;
}
//----------------------------------------------------------------------
void __fastcall TForm1::WMDropFiles(TMessage &Msg)
{
// grab a handle to the drop object
HDROP HDrop = reinterpret_cast<HDROP>(Msg.WParam);
// find the number of files dropped
int num_files = DragQueryFile(HDrop,
  0xFFFFFFFF, NULL, NULL);
if (num_files > 1)
{
ShowMessage("You have dropped too many files.");
return;
}
// extract the name and path of
  the dropped file
char filename[MAX_PATH];
DragQueryFile(HDrop, 0,
  filename, MAX_PATH);
NameLabel->Caption =
  NameLabel->Caption + " " + filename;
std::auto_ptr<TFileStream>
  fs;
try
{
fs.reset(new TFileStream(filename, fmOpenRead));
// get the file
  information
SizeLabel->Caption = SizeLabel->Caption + " " + GetFileSize(reinterpret_cast<HANDLE>(fs->Handle),
  NULL);
DateLabel->Caption = DateLabel->Caption + " " + FileDateToDateTime(FileGetDate(fs->Handle));
}
catch (...)
{
// close the
  drop handle
DragFinish(HDrop);
}
// close the drop handle
DragFinish(HDrop);
Msg.Result = 0;
}
//----------------------------------------------------------------------

목록 2.8b 에 있는 TForm1::CreateParams() 함수 내에서, 우선 부모 클래스의 CreateParams() 함수를 호출한다음에 ExStyle 멤버에 WS_EX_ACCEPTFILES 를 추가했다. WMDropFiles 메세지 핸들러에서, 드롭된 파일의 크기를 얻기 위해서 GetFileSize() API 함수를 사용했고 파일이 마지막으로 수정된 날짜를 알기위해서 VCL 함수인 FileGetDate() 와 FileDateToDateTime() 함수를 사용했다.

Style 과 ExStyle 멤버는 자식 컨트롤을 만들 때 아주 유용하게 살사용할 수 있다. 예를 들어, TListBox 에서 수직 스크롤바를 없애려면 Style 데이타 멤버에서 WS_VSCROLL 을 제거하면 된다.

목록 2.10

//----------------------------------------------------------------------
#ifndef NoScrollListBoxH
#define NoScrollListBoxH
//----------------------------------------------------------------------
#include <StdCtrls.hpp>
//----------------------------------------------------------------------
class TMyListBox : public TListBox
{
protected:
virtual void __fastcall
  CreateParams(TCreateParams &Params)
{
TListBox::CreateParams(Params);
Params.Style = Params.Style & ~WS_VSCROLL;
}
public:
__fastcall TMyListBox(TComponent
  *Owner) : TListBox(Owner) {};
};
//----------------------------------------------------------------------
#endif

3-2. WindowClass 멤버 다루기

새로운 컨트롤 클래스의 윈도우를 만드는 첫번재 단계는 WNDCLASS 변수를 설정하는 것이었다. RegisterClass() API 함수에 전달되는 WNDCLASS 변수는 새로운 컨트롤 클래스의 속성을 설정하는데 사용할 수 있다. style 멤버는 클래스의 스타일을 정의하는데 사용된다. 예를 들어, 목록 2.0 에서 form_wc 의 style 멤버를 CS_VREDRAW, CS_HREDRAW, CS_DBLCLKS 클래스 스타일을 섞어서 설정했었다.

TCreateParams::WindowClass 데이타 멤버는 RegisterClass() 함수에 전달되는 WNDCLASS 구조체 변수다. 이 멤버는 CreateParams() 멤버 함수에서 설정되는데, Style 과 ExStyle 멤버처럼 WindowClass 멤버를 커스터마이즈할 수 있다.

기본 마우스 커서로 십자선 커서를 갖는 폼을 만들어보자. 이런 폼은 Cursor 속성을 수정해서 쉽게 만들수 있지만, 이 예제에서는 WindowClass 의 hCursor 데이타 멤버를 수저해서 만들어 본다.

//----------------------------------------------------------------------
#ifndef HelpCursorH
#define HelpCursorH
//----------------------------------------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <Forms.hpp>
#include <StdCtrls.hpp>
//----------------------------------------------------------------------
class TForm1 : public TForm
{
__published: // IDE-managed Components
protected:
virtual void __fastcall
  CreateParams(TCreateParams &Params)
{
TForm::CreateParams(Params);
Params.WindowClass.hCursor = LoadCursor(NULL, IDC_CROSS);
}
public: // User declarations
__fastcall TForm1(TComponent*
  Owner);
};
//----------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//----------------------------------------------------------------------
#endif

전과 같이, 우선 부모 클래스의 CreateParams() 멤버 함수로 Params 를 설정한다음에, LoadCursor() API 함수를 사용해서 hCursor 의 멤버에 십자형 마우스 커서를 설정했다.

WindowClass 멤버를 다루는 다른 예제를 살펴보자. 이 예제는 그림 2.1 처럼 윈도우의 "닫기" 버튼을 비활성 버튼으로 만든다.

WindowClass 데이타 멤버엣 CS_NOCLASS 클래스 스타일을 추가하면 된다.

목록 2.12

//----------------------------------------------------------------------
#ifndef NoCloseH
#define NoCloseH
//----------------------------------------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <Forms.hpp>
#include <StdCtrls.hpp>
//----------------------------------------------------------------------
class TForm1 : public TForm
{
__published: // IDE-managed Components
protected:
virtual void __fastcall
  CreateParams(TCreateParams &Params)
{
TForm::CreateParams(Params);
Params.WindowClass.style = Params.WindowClass.style | CS_NOCLOSE;
}
public: // User declarations
__fastcall TForm1(TComponent*
  Owner);
};
//----------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//----------------------------------------------------------------------
#endif

앞에서 살펴본 hCursor 멤버를 설정하는 예제처럼, WindowClass 의 style 데이타 멤버에 CS_NOCLOSE 클래스 스타일을 추가했다. 이 스타일은 타이틀바의 "닫기" 버튼을 비활성 버튼으로 만들고 시스템 메뉴에서 "닫기" 항목을 제거한다. 하지만, 태스바에 있는 어플리케이션 버튼위에서 마우스 오른쪽 버튼을 누르면 "닫기" 메뉴 항목이 있는 것을 볼 수 있다. 이 문제를 해결하는 방법을 알아보자.

3-3. WndParent 멤버 다루기

가장 많이 묻는 질문 중에 하나는, '어플리케이션 내의 폼들을 어떻게 테스크바 위에 버튼으로 나타내느냐' 이다. 기본적으로, VCL 어플리케이션에서 쓰이는 폼은 테스크바 버튼으로 나타나질 않는다. 우리가 보는 것은 TApplication 윈도우이다. 테스크바 버튼 위에서 마우스 오른쪽 버튼을 눌러보면, 표준 윈도우즈 어플리케이션과 VCL 어플리케이션과의 차이점을 볼 수 있다. 메모장의 팝업 메뉴는 그림 2.2 처럼 시스템 메뉴다.

VCL 어플리케이션은 다른 메뉴가 나온다. TApplication::CreateHandle() 멤버 함수가 그림 2.3 처럼 최대화(Maximize), 이동(Move), 크기 조정(Size) 옵션을 제거하기 때문이다.

그럼 왜 TApplication::CreateHandle() 멤버 함수는 최대화, 이동, 크기조정의 메뉴를 만들지 않는 것일까? 그것은 TApplication 의 윈도우가 최대화, 이동, 크기변환이 되면 안 되기 때문이다.

CreateWindowEx() 함수의 hWndParent 인자에 대해서 얘기할 때, 기본으로 이 값에 어플리케이션(TApplication) 윈도우의 핸들을 설정했었다. 그렇기 때문에 테스크바에서 폼의 버튼을 볼 수 없는 것이다. 앞의 예제처럼 CreateParams() 함수를 사용해서, TCreateParams::WndParent 멤버를 바탕화면(desktop) 윈도우의 핸들로 설정할 수 있다. 목록 2.13 은 구현 코드다.

목록 2.13

//----------------------------------------------------------------------
#ifndef AppWindow2H
#define AppWindow2H
//----------------------------------------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
//----------------------------------------------------------------------
class TForm2 : public TForm
{
__published: // IDE-managed Components
private: // User declarations
virtual void __fastcall
  CreateParams(TCreateParams &Params)
{
TForm::CreateParams(Params);
Params.WndParent = GetDesktopWindow(); // or
  NULL
}
public: // User declarations
__fastcall TForm2(TComponent*
  Owner);
};
//----------------------------------------------------------------------
extern PACKAGE TForm2 *Form2;
//----------------------------------------------------------------------
#endif

GetDesktopWindow() API 함수를 이용해서 바탕화면(desktop)의 윈도우 핸들을 가져온 다음에, 이 핸들 값을 Params 의 WndParent 데이타 멤버에 설정했다. 위의 구현으로, Form2 는 테스크바에 버튼으로 나타난다. 그리고 마우스 오른쪽 버튼을 눌러보면 시스템 메뉴가 나온다. 하지만, 어플리케이션(TApplication) 자체가 최소화될 때 Form2 는 최소화되지 않는다.

사실, 위의 방법은 Application 의 MainFrom( TApplication::MainForm) 을 뺀 모든 폼이 잘 동작한다. 하지만 왜 MainForm 에서 최소화 메뉴가 작동하지 않는지 알아보기 위해서 목록 2.13 의 코드를 TFrom1 클래스로 다시 만들어보자.

목록 2.14

//----------------------------------------------------------------------
#ifndef AppWindow2H
#define AppWindow2H
//----------------------------------------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
//----------------------------------------------------------------------
class TForm1 : public TForm
{
__published: // IDE-managed Components
private: // User declarations
virtual void __fastcall
  CreateParams(TCreateParams &Params)
{
TForm::CreateParams(Params);
Params.WndParent = GetDesktopWindow(); // or
  NULL
}
public: // User declarations
__fastcall TForm2(TComponent*
  Owner);
};
//----------------------------------------------------------------------
extern PACKAGE TForm2 *Form2;
//----------------------------------------------------------------------
#endif

이 예제를 실행시키면, 두가지 문제점을 발견할 것이다. 어플리케이션의 윈도우 버튼이 테스크바에 그대로 있고, Form1 의 새로운 버튼도 테스크바 위에 나타난다. 어플리케이션의 테스트바 버튼위에서 마우스 오른쪽 버튼을 눌러서 최소화 메뉴를 선택하면 Form1 은 최소화되지 않고 그래도 있는 문제를 볼 수 있다.

왜 그런지 알기 위해서는 WndParent 멤버에 어플리케이션 윈도우의 핸들이 사용된 것을 알아야한다. 그렇기 때문에 어플리케이션이 최소화되면 모든 폼이 최소화되는 것이다. 위의 예제를 보면, Form1 과 Form2 는 WndParent 의 값으로 더이상 어플리케이션의 윈도우 핸들을 사용하지 않기 때문에 어플리케이션이 최소화 될 때, 그대로 있는 것이다.

윈도우의 최소화 시스템 메뉴가 선택되면, 윈도우는 SC_MINIMIZE 명령을 wParam 인자에 넣어 WM_SYSCOMMAND 메세지를 보낸다. TCustomForm 클래스는 이 메세지를 WMSysCommand 메세지 핸들러에서 다루는데, 이 핸들러 안에서 메세지의 현재 인스턴스가 어플리케이션의 MainForm 인지 아닌지를 체크한다음에, 그렇다면 그 메세지를 어플리케이션 윈도우에 전달한다. 메세지를 받은 어플리케이션 윈도우는 TApplication::Minimize() 멤버 함수를 사용해서 메세지를 처리한다. 이렇게 하면 모든 폼들이 최소화되지만, WndParent 를 조작한 폼은 최소화되질 않는다.

이 문제를 해결하기 위해서, 메세지 매크로를 사용해야한다.( 즉, Dispatch() 함수를 사용하는 것 ) 그렇게하면, TCustomForm 클래스에 전달되던 WM_SYSCOMMAND 메세지를 우리가 만든 WMSysCommand 메세지 핸들러에서 가로챌 수 있다. 목록 2.15a 와 2.15b 에 구현 코드가 있다.

목록 2.15a

//----------------------------------------------------------------------
#ifndef AppWindowH
#define AppWindowH
//----------------------------------------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
//----------------------------------------------------------------------
class TForm1 : public TForm
{
__published: // IDE-managed Components
TButton *Button1;
void __fastcall FormShow(TObject
  *Sender);
void __fastcall Button1Click(TObject
  *Sender);
private: // User declarations
void __fastcall WMSysCommand(TMessage
  &Msg);
protected:
virtual void __fastcall
  CreateParams(TCreateParams &Params);
public: // User declarations
__fastcall TForm1(TComponent*
  Owner);
BEGIN_MESSAGE_MAP
MESSAGE_HANDLER(WM_SYSCOMMAND,
  TMessage, WMSysCommand)
END_MESSAGE_MAP(TForm)
};
//----------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//----------------------------------------------------------------------
#endif

목록 2.15b

//----------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "AppWindow.h"
#include "AppWindow2.h"
//----------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//----------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//----------------------------------------------------------------------
void __fastcall TForm1::CreateParams(TCreateParams &Params)
{
TForm::CreateParams(Params);
Params.WndParent = GetDesktopWindow();
}
//----------------------------------------------------------------------
void __fastcall TForm1::WMSysCommand(TMessage &Msg)
{
// trap the WM_SYSCOMMAND message
  only if the
// SC_MINIMIZE flag is specified
unsigned int op_code = Msg.WParam
  & 0xFFF0;
if (op_code == SC_MINIMIZE)
{
WindowState = wsMinimized;
Msg.Result = 0;
return;
}
TForm::Dispatch(&Msg);
}
//----------------------------------------------------------------------
void __fastcall TForm1::FormShow(TObject *Sender)
{
// hide the Application window
  (and its taskbar button)
ShowWindow(Application->Handle,
  SW_HIDE);
}
//----------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
// display Form2 (notice its own
  taskbar button)
Form2->Show();
}
//----------------------------------------------------------------------

목록 2.15a 에서 메세지 매크로를 사용했는데, 이 매크로는 Dispatch() 멤버 함수를 사용하는 것이다. 목록 2.15b 의 WM_SYSCOMMAND 메세지 핸들러인 WMSysCommand() 내에서, WParam 이 SC_MINIMIZE 인지 체크한다음에 맞다면, TCustomForm::WindowState 속성을 설정해서 Form1 을 최소화시키고 메세지를 트랩했다.

TCustomForm::OnShow 이벤트 핸들러를 사용했는데, ShowWindow() API 함수를 사용해서 어플리케이션 윈도우와 테스트바의 버튼을 보이지 않게 했다. 이렇게 해서 모든 문제를 해결했다.

Inside the VCL 시리즈 끝!

맺음말

남의 나라말을 우리말로 옮겨오는 것이 무척 어려운 일 같습니다. 제가 전문 번역가가 아니므로 번역의 상태가 고르지 않더라도 이해해 주시길 바랍니다.

번역 : 김성철( paranwave@korea.com )


OpenGL Demo

Diffusion Effect

by Sylvain Lefebvre

원문 : http://lefebvre.sylvain.free.fr/gltut/diffusion/

목표

이 튜토리얼은 WinAMP 의 플러그인 에서 볼 수 있는 "Geiss" 와 같은 멋진 효과를 만들기 위해서 필요한 알고리즘을 제공한다.

필요한 것

C 언어와 OpenGL 에 대해서 어느정도 알아야한다. 이 튜토리얼이 제공하는 예제를 만들 때에는 아주 쉬운 인터페이스를 가진 GLUT 를 사용할 것이다.

이론

이 효과는 확산(방안에서 연기가 퍼지는 듯한)을 시뮬레이션하는 것을 바탕으로 한다.

어떻게 작동하나?

렌더링 과정은 벡터장에 의해서 일그러진 텍스춰를 그리는 것으로 시작한다. 벡터장에 대해서는 나중에 설명하고 지금은 처음에 그린 것을 가지고 다음 렌더링 과정에서 또 다시 텍스춰로 사용한다고 생각한다. 일그러짐을 반복적으로 적용해서 움직임을 만들어 낸다. 예를 들어 보자. 아래에 선이 하나 있는데, 일그러트린 다음 왼쪽으로 전사시킨다. 여기서 전사란 글씨·글·그림 따위를 옮겨 베끼거나 촬영하거나 복사하는 것을 말한다. ( □ 은 흰색 픽셀이고 ■ 은 검은색 픽셀이다. )

□■■■■

이 과정을 반복하고, 이전 과정의 결과를 다음 과정에 사용하면 움직임이 만들어 진다.

□■■■■ -> ■□■■■ -> ■■□■■ -> ■■■□■ -> ■■■■□

여기에 이전 결과를 다시 사용하기 전에 감쇠효과를 추가하면 점점 작아지게 만들면서 사라지게끔 효과를 낼 수 있다.

□■■■■ -> ■□■■■ -> ■■.■■ -> ■■■■■

벡터장으로 텍스춰 일그러트리기

규칙적인 격자에 렌더링을 끝내고 격자의 각 칸에 OpenGL 의 사각형 텍스춰를 설정한다. 뒤틀림은 격자의 각각의 점에 정의한 벡터를 이용해서 만들 수 있다. 이 벡터는 텍스춰 매핑 때 텍스춰 좌표로 추가된다. 각 점의 변형은 OpenGL 에 의해서 자동으로 보간된다. 이 때문에 번지는 효과가 만들어진다.

이 그림들은 텍스춰를 일그러트리는데 사용하는 벡터장(녹색으로 그려진 선)을 보여준다.(클릭을 하면 큰 그림을 볼 수 있다.)

이 그림은 규칙적인 격자와 텍스춰 좌표를 섭동시키는데 사용하는 벡터를 나타내고 있다. 여기서 섭동은 역학계(力學系)에 있어서 주요한 힘의 작용으로 생기는 운동이, 다른 부차적인 힘의 영향으로 교란되는 일을 말한다.

여기서 중요한 점은 격자(사각형들과 텍스춰좌표)는 오로지 단 한번만 계산되고 ( 우리가 같은 움직임을 원하는 동안 ) 더이상 바뀌지 않는다. 격자는 변형 단계를 만드는데 사용된다. 여기서 움직임은 앞에서 설명했듯이 변형의 결과가 다음 단계의 전체 텍스춰에 다시 사용되서 만들어진다.

아직 잘 이해가 되지 않는다면 걱정하지 말고 C 코드에 있는 주석을 읽어본 후 다시 이 부분을 읽어보길 바란다.

실습

/* --------------------------------------------------------
  */
#include <GL/gl.h>
#include <stdio.h>
#include <GL/glut.h>
#include <time.h>
#include <sys/types.h>
#include <math.h>
#include <stdlib.h>
/* --------------------------------------------------------
  */
#define GRID_RES 10 //격자의 해상도(몇개의 사각형으로 나눌 것인가)
/* --------------------------------------------------------
  */
int g_iMainWindow; //GLUT 윈도우의 식별값 id
int g_iDeform=2; //현재 변형 정도
int g_iDrawLines=1; //무작위로 선을 그리겠습니까?
int g_iDrawCube=1; //와이어프레임으로 된 3차원 박스를 그리겠습니까?
int g_iFrame=0; //프레임 카운터
int g_iClear=1; //텍스춰를 깨끗이 지우겠습니까?
int g_iShowField=0; //벡터장을 그리겠습니까?
int g_iPaintBrush=0; //마우스커서아래 사각형을 그리겠습니까?
double g_dBrushX, g_dBrushY; //마우스 커서의 위치
GLuint g_uiTexId; //OpenGL 텍스춰 식별값 id
GLuint g_uiGridList; //격자를 그리는 디스플레이 리스트

GLUT 키보드 콜백 함수

키보드는 효과를 다루기 위해서 사용한다. 다음은 사용되는 키의 설명이다.

[SPACE] - 선을 그린다.
[+] - 변형을 변경한다.
[o] - 객체를 그린다.
[c] - 깨끗하게 지운다.
[f] - 벡터장을 그린다.
[q] - 프로그램을 종료한다.

void mainKeyboard(unsigned char key, int x, int y)
{
if (key == '+')
{
g_iDeform=(g_iDeform+1) % 5;
}
else if (key == ' ')
{
g_iDrawLines=1; //!g_iDrawLines;
}
else if (key == 'o')
{
g_iDrawCube=!g_iDrawCube;
}
else if (key == 'c')
{
g_iClear=1;
}
else if (key == 'f')
{
g_iShowField=!g_iShowField;
}
else if (key == 'q')
{
exit (0);
}
}

마우스 이벤트 GLUT 콜백 함수

마우스는 효과에 바로 색을 칠하기 위해서 사용된다. 사용자가 버튼을 누를 때마다, 마우스 커서 아래에서 그림이 그려지기 시작하고 눌렸던 버튼을 놓으면 그리기를 멈춘다.

void mainMouse(int btn, int state, int x, int y)
{
if (state == GLUT_DOWN)
{
g_iPaintBrush=1;
g_dBrushX=((double)x) / ((double)glutGet(GLUT_WINDOW_WIDTH));
g_dBrushY=((double)y) / ((double)glutGet(GLUT_WINDOW_HEIGHT));
}
else
{
g_iPaintBrush=0;
}
}

마우스 추적(마우스버튼을 누른채 마우스를 움직일 때) GLUT 콜백 함수

이 콜백 함수는 마우스 커서의 위치를 추적하기 위해서 사용한다.

void mainMotion(int x,int y)
{
g_dBrushX=((double)x) / ((double)glutGet(GLUT_WINDOW_WIDTH));
g_dBrushY=((double)y) / ((double)glutGet(GLUT_WINDOW_HEIGHT));
}

화면 크기 변경 GLUT 콜백 함수

윈도우의 크기가 512x512 를 넘지 않게 한다. 윈도우의 크기는 반드시 2 의 제곱승 값이어야한다. 이 것은 OpenGL 텍스춰의 크기가 2 의 제곱승 값으로 제한되어 있기 때문이다.

void mainReshape(int w,int h)
{
glutSetWindow(g_iMainWindow);
glViewport(0,0,w,h);
if (w != 512 || h != 512)
glutReshapeWindow(512,512);
}

각 격자의 정점을 뒤틀어서 텍스춰좌표 계산하기

우선, 텍스춰의 좌표는 뒤틀림이 없는 상태에서 계산된다. 그 다음 switch 구문에서 이 좌표들을 sin/cos 함수를 이용해서 섭동하게끔 만든다. 더 멋진 효과를 만들고 싶다면, 이 부분을 가지고 놀면 된다 !!

void genTexCoords(int time,int res,double ***tbl)
{
double x,y,c,s,d;
int i,j;

for (i=0;i<res;i++)
{
for (j=0;j<res;j++)
{
tbl[i][j][0]=((double)i)/((double)res-1.0);
tbl[i][j][1]=((double)j)/((double)res-1.0);
switch
  (g_iDeform)
{
case
  0:
tbl[i][j][0]+=0.01;
tbl[i][j][1]+=0.01;
break;
case
  1:
tbl[i][j][0]+=(i-res/2.0)*0.001;
tbl[i][j][1]+=(j-res/2.0)*0.001;
break;
case
  2:
tbl[i][j][0]*=cos((i+j-res)/50.0*M_PI);
tbl[i][j][1]*=cos((i-j)/100.0*M_PI);
break;
case
  3:
tbl[i][j][0]+=(i-res/2.0)*0.003;
tbl[i][j][1]+=(j-res/2.0)*0.003;

tbl[i][j][0]-=0.5;
tbl[i][j][1]-=0.5;
x=tbl[i][j][0];
c=cos(M_PI/32.0+(i-res/2)*M_PI/32.0);
s=sin(M_PI/32.0+(j-res/2)*M_PI/32.0);
tbl[i][j][0]=c*x-s*tbl[i][j][1];
tbl[i][j][1]=s*x+c*tbl[i][j][1];
tbl[i][j][0]+=0.5;
tbl[i][j][1]+=0.5;
break;
case
  4:
tbl[i][j][0]-=0.5;
tbl[i][j][1]-=0.5;
x=tbl[i][j][0];
d=sqrt((i-res/2.0)*(i-res/2.0)+(j-res/2.0)*(j-res/2.0));
c=cos((d)*M_PI/64.0);
s=sin((d)*M_PI/64.0);
tbl[i][j][0]=c*x-s*tbl[i][j][1];
tbl[i][j][1]=s*x+c*tbl[i][j][1];
tbl[i][j][0]+=0.5;
tbl[i][j][1]+=0.5;
break;
default:
break;
}
}
}
}

화면에 효과 없이 그리기

하얀색의 3차원 박스를 그린다. 다른 것을 그리고 싶다면 이부분에 코드를 추가하면 된다.

void noDiffuseDraw()
{
if (g_iDrawCube)
{
//
  와이어프레임 사각박스를 그린다.

//
  투영설정
glMatrixMode(GL_PROJECTION);
glLoadIdentity();

gluPerspective(90.0,1.0,1.0,100.0);
gluLookAt(7.0,7.0,7.0, 0.0,0.0,0.0,
  0.0,0.0,1.0);

glMatrixMode(GL_MODELVIEW);
glLoadIdentity();

//
  무작위로 움직인다.
glRotated(2.0*g_iFrame, 0.0,
  0.0, 1.0);
glTranslated(3.0*cos(g_iFrame/1800.0),
  2.0*sin(g_iFrame/1000.0), 0.0);
glRotated(10.0*g_iFrame, 0.0,
  1.0, 1.0);
glTranslated(sin(g_iFrame/1500.0),
  5.0*sin(g_iFrame/2000.0), 0.0);
glDisable(GL_TEXTURE_2D);
glLineWidth(3.0);
glColor3d(1.0,1.0,1.0);
glutWireCube(5.0);
}
}

효과를 적용해서 그리기

여기에서 그려지는 모든 것들은 효과가 적용된다. 무작위로 선과 3차원 박스 그리고 마우스 커서아래에 사각형을 그릴 것이다. 다른 것을 그리고 싶다면 코드를 추가하면 된다. 만약에 채도 효과를 내고 싶다면 (1.0, 0.0, 0.0) 과 같은 단순 RGB 색을 사용하지 말아야한다.

void diffuseDraw()
{
int i;
double x,y;

if (g_iDrawLines)
{
//
  무작위로 선을 그린다.
glDisable(GL_TEXTURE_2D);
glLineWidth(1.0+(rand()%3));
glEnable(GL_BLEND);
glBlendFunc(GL_ONE,GL_ONE);
glColor3d(1.0,0.6,0.7);
glBegin(GL_LINE_STRIP);
for
  (i=0;i<10;i++)
{
x=drand48();
y=drand48();
glVertex2d(x,y);
}
glEnd();
glDisable(GL_BLEND);
g_iDrawLines=0;
}

if (g_iPaintBrush)
{
//
  무작위로 선을 그린다.
glDisable(GL_TEXTURE_2D);
glLineWidth(1.0+(rand()%3));
glEnable(GL_BLEND);
glBlendFunc(GL_ONE,GL_ONE_MINUS_SRC_COLOR);
glColor3d(1.0,0.6,0.7);
glTranslated(g_dBrushX,1.0-g_dBrushY,0.0);
glBegin(GL_QUADS);
glVertex2d(-0.04,-0.04);
glVertex2d(0.04,-0.04);
glVertex2d(0.04,0.04);
glVertex2d(-0.04,0.04);
glEnd();
glDisable(GL_BLEND);
g_iDrawLines=0;
}

if (g_iDrawCube)
{
//
  3차원 와이어프레임 박스를 그린다

//
  투영설정
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(90.0,1.0,1.0,100.0);
gluLookAt(7.0,7.0,7.0, 0.0,0.0,0.0,
  0.0,0.0,1.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();

//
  무작위로 움직인다.
glRotated(2.0*g_iFrame,0.0,0.0,1.0);
glTranslated(3.0*cos(g_iFrame/1800.0),2.0*sin(g_iFrame/1000.0),0.0);
glRotated(10.0*g_iFrame,0.0,1.0,1.0);
glTranslated(sin(g_iFrame/1500.0),5.0*sin(g_iFrame/2000.0),0.0);

glDisable(GL_TEXTURE_2D);
glLineWidth(1.0+(rand()%3));
glEnable(GL_BLEND);
glBlendFunc(GL_ONE,GL_ONE);
glColor3d(0.1,0.1,0.6);
glutWireCube(5.0);
glDisable(GL_BLEND);
}
}

격자와 벡터장 그리기

특별한 것이 없다.

void drawVectorField(int res,double ***texCoords)
{
int i,j;
double u,v;

glPushAttrib(GL_ENABLE_BIT);

// 투영설정
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0.0,1.0,0.0,1.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();

// 그리기
glDisable(GL_TEXTURE_2D);
glLineWidth(1.0);
glBegin(GL_LINES);
glColor3d(0.0,1.0,0.0);
for (i=0;i<GRID_RES;i++)
{
for
  (j=0;j<GRID_RES;j++)
{
u=texCoords[i][j][0]-((double)i)/((double)res-1.0);
v=texCoords[i][j][1]-((double)j)/((double)res-1.0);
glVertex2d(((double)i)/((double)res-1.0),((double)j)/((double)res-1.0));
glVertex2d(((double)i)/((double)res-1.0)+u,((double)j)/((double)res-1.0)+v);
}
}
glEnd();
glColor3d(1.0,1.0,1.0);
glPolygonMode(GL_FRONT,GL_LINE);
glCallList(g_uiGridList);
glPolygonMode(GL_FRONT,GL_FILL);

glPopAttrib();
}

격자를 위한 디스플레이리스트 만들기

격자는 간단히 OpenGL 사각형으로 만들어진다. 텍스춰 좌표는 genTexCoords 함수를 통해서 생성된 텍스춰 테이블에서 읽는다.

void computeGrid(int res,double ***texCoords)
{
int i,j;

glNewList(g_uiGridList,GL_COMPILE);
glBegin(GL_QUADS);
for (i=0;i<GRID_RES;i++)
{
for
  (j=0;j<GRID_RES;j++)
{
glTexCoord2d(texCoords[i][j][0],texCoords[i][j][1]);
glVertex2d(((double)i)/((double)GRID_RES),((double)j)/((double)GRID_RES));
glTexCoord2d(texCoords[i+1][j][0],texCoords[i+1][j][1]);
glVertex2d(((double)i+1)/((double)GRID_RES),((double)j)/((double)GRID_RES));
glTexCoord2d(texCoords[i+1][j+1][0],texCoords[i+1][j+1][1]);
glVertex2d(((double)i+1)/((double)GRID_RES),((double)j+1)/((double)GRID_RES));
glTexCoord2d(texCoords[i][j+1][0],texCoords[i][j+1][1]);
glVertex2d(((double)i)/((double)GRID_RES),((double)j+1)/((double)GRID_RES));
}
}
glEnd();
glEndList();
}

주렌더링 함수.

위에서 만든 함수들을 모두 호출한다. 코드안에 주석을 달았다.

void mainRender()
{
static int deform=-1;
static double ***texCoords=NULL;
static int first=1;
int i,j;
GLint v[4];

가장 먼저 텍스춰
  좌표를 저장하는 테이블을 메모리에 할당해야한다.
if (texCoords == NULL)
{
//
  1차 할당
texCoords=new double **[GRID_RES+1];

//
  2차 할당
for (i=0;i<GRID_RES+1;i++)
texCoords[i]=new
  double *[GRID_RES+1];

//
  3차 할당
for (i=0;i<GRID_RES+1;i++)
for
  (j=0;j<GRID_RES+1;j++)
texCoords[i][j]=new
  double [2];
}

변환 정도가 바뀌었다면,
  텍스춰좌표를 다시 계산해줘야한다. 그리고 이 과정은 이 함수가 제일 처음 호출될 때에도 수행된다.
if (first || deform != g_iDeform)
{
genTexCoords(0,GRID_RES+1,(double
  ***)texCoords);
computeGrid(GRID_RES,(double
  ***)texCoords);
deform=g_iDeform;
}
// 주 윈도우에 그리도록 설정한다.
glutSetWindow(g_iMainWindow);
// 화면을 깨끗이 지운다.
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

이 함수가 처음
  호출되었을 땐, 텍스춰를 사용하기전에 텍스춰에 무엇인가를 그려야한다.
if (first)
{
//
  렌더링창의 화면을 텍스춰로 만든다.
glGetIntegerv(GL_VIEWPORT,v);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D,g_uiTexId);
glCopyTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,0,0,v[2],v[3],0);
glTexParameteri(GL_TEXTURE_2D,
  GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,
  GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,
  GL_TEXTURE_WRAP_S,GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D,
  GL_TEXTURE_WRAP_T,GL_CLAMP);
glTexEnvi(GL_TEXTURE_2D, GL_TEXTURE_ENV_MODE
  ,GL_MODULATE);
first=0;
}

// 투영설정
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0.0,1.0,0.0,1.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();

// 확산효과를 만드는 격자를 그린다.
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D,g_uiTexId);
glColor3d(0.98,0.98,0.98);

여기에서 OpenGL
  에 격자를 그릴 것을 요청한다. OpenGL 이 실행하는 이 코드는 glNewList 와 glEndList 함수 사이에 있는 코드다.
OpenGL 디스플레이리스트가
  빠르기 때문에 사용했다.
glCallList(g_uiGridList);

// 확산효과가 적용되도록 그림을 그린다.
diffuseDraw();

// 프레임수를 센다.
g_iFrame++;

// OpenGL 명령들을 실행한다.
glFlush();
if (g_iClear)
{
glClear(GL_COLOR_BUFFER_BIT
  | GL_DEPTH_BUFFER_BIT);
g_iClear=0;
}

렌더링창을 텍스춰로
  복사한다. 다음 번에 mainRender 함수는 이 텍스춰를 격자에 의해서 그려지는 텍스춰로 사용할 것이다.
이 것이 바로
  움직임을 만들어내는 것이다. 이 부분을 확실하게 이해했다면 다 한 것이나 다름없다!
glGetIntegerv(GL_VIEWPORT,v);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D,g_uiTexId);
glCopyTexSubImage2D(GL_TEXTURE_2D,0,0,0,0,0,v[2],v[3]);

렌더링창을 텍스춰로 복사한
  후에, 그려지는 모든 것을 효과 없이 나타낸다.
// 확산효과 없이 그린다.
noDiffuseDraw();

// 벡터장을 그리도록 설정되어 있다면 벡터장을
  그린다.
if (g_iShowField)
drawVectorField(GRID_RES+1,(double
  ***)texCoords);

// 전면버퍼와 후면버퍼를 교체한다.
glutSwapBuffers();
}

GLUT 휴면(Idle) 콜백 함수

이 함수는 시스템이 아무것도 수행하지 않을 때 호출되는 콜백함수다. 이 함수가 다시그리기를 요청하기에 딱 좋은 장소죠!

void idle( void )
{
glutSetWindow(g_iMainWindow);
glutPostRedisplay();
}

main 함수

GLUT 와 OpenGL 을 초기화한다.

main(int argc, char **argv)
{
int i;

// GLUT 초기화
glutInit(&argc, argv);
glutInitWindowSize(512, 512);
glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);

// 주윈도우
g_iMainWindow=glutCreateWindow("Plin");
glutMouseFunc(mainMouse);
glutMotionFunc(mainMotion);
glutKeyboardFunc(mainKeyboard);
glutDisplayFunc(mainRender);
glutReshapeFunc(mainReshape);
glutIdleFunc(idle);
glutSetWindow(g_iMainWindow);

// 초기화
glClearColor(0.0, 0.0, 0.0, 1.0);
glDisable(GL_LIGHTING);

glDisable(GL_DEPTH_TEST);
glEnable(GL_COLOR_MATERIAL);
glEnable(GL_CULL_FACE);

glGenTextures(1,&g_uiTexId);
g_uiGridList=glGenLists(1);

printf("[SPACE] - draw lines\n");
printf("[+] - change deformation\n");
printf("[o] - draw object\n");
printf("[c] - clear\n");
printf("[f] - show vector field\n");
printf("[q] - quit\n");

// 렛츠고~!!
glutMainLoop();
}

번역 : 김성철 ( paranwave@korea.com )
-+ 실행파일 및 소스코드 받기 +-


수학 용어

Mathematical Terms [ 수학 용어 ]

Mathematical Terms
용어 | 해설 | 용어 | 해설
acute angle | 예각 | equidistance | 등거리
acute triangle | 예각 삼각형 | equilateral triangle | 정삼각형
addition | 덧셈 | equivalent | 동치
adjacent angle | 인접각 | estimate | 평가, 계산
algebra | 대수 | even number | 짝수
alternate angle | 엇각 | exponent | 멱지수
altitude | 높이 | expression | 식
angle | 각 | factor | 인수
approximation | 근사값 | figure | 도형
arbitrary | 임의의 | formula | 공식
area | 면적 | fraction | 분수
arc | 호 | function | 함수
arithmetic | 산술 | geometry | 기하
associative law | 결합법칙 | greatest common factor | 최대 공약수
axis | 축 | group | 군
base | 기수 | height | 높이
bisect | 이등분하다 | hexagon | 6변형
consecutive | 연속의 | horizontal | 가로의
center | 중심 | hyperbola | 쌍곡선
center angle | 중심각 | hypotenuse | 직각삼각형의 사변
chord | 현 | identical equation | 항등식
circle | 원 | improper fraction | 가분수
circumference | 원주 | inequality | 부등식
combination | 조합 | inscribe | 내접하다
complementary angle | 여각 | integer | 정수
cone | 원뿔 | intercept | 선에따라 잘라내다
constant | 상수 | intersection | 교점
coordinate | 좌표 | inverse | 역
corresponding angle | 동위각 | involve | 누승하다
cube | 정육면체; 3승 | isosceles | 이등변 삼각형
cubic | 3차의 | leg | 변
cylinder | 원기둥 | length | 길이
decimal point | 소수점 | letter | 문자
demonstration | 증명 | like term | 동류항
denominator | 분모 | line | 선
denote | 표시하다 | linear | 직선의, 1차의
develop | 전개하다 | linear equation | 1차 방정식
diagonal | 대각선 | measurement of angle | 각의 측정
diagram | 도표 | mid point | 중점
diameter | 지름 | minuend | 피감수
difference | 차 | mixed number | 대분수
digit | 숫자 | multiplication | 곱
dimension | 차원 | natural number | 자연수
dividend | 피제수 | negative number | 음수
division | 나눗셈 | notation | 기수법
edge | 모서리 | number | 숫자
eliminate | 삭제하다 | numerator | 분자
ellipse | 타원 | obtuse angle | 둔각
equation | 방정식 | obtuse triangle | 둔각 삼각형
odd number | 홀수 | remainder | 나머지
origin | 원점 | regular polygon | 정다각형
operation | 계산, 연산 | revolution | 회전, 주기
parallelism | 평행 | rhombus | 마름모
parallemlogram | 평행4변형 | right triangle | 직삼각형
pentagon | 5각형 | root | 근
perpendicular | 수선, 수직면 | round off | 반올림
perimeter | 주변길이 | sector | 부채꼴
plane geometry | 평면 기하학 | segment | 선분
point | 점, 소수점 | semicircle | 반원
polygon | 다각형 | set theory | 집합론
polynomial | 다항식 | similar | 닮은꼴
positive number | 양수 | slash | 사선
power | 누승, 거듭제곱 | solution | 해법
practice | 실산 | specify | 상술하다
prime | 소수 | sphere | 구
prism | 삼각기둥 | square | 정사각형
probability | 확률 | state | 식으로 나타내다
product | 곱, 적 | statistics | 통계
proper fraction | 진분수 | straight angle | 평각
pyramid | 각뿔 | straight line | 직선
quadrilateral | 4변형 | subtraction | 뺄셈
quantity | 양 | surface area | 표면적
quotient | 몫 | symbol | 기호
radius | 반지름 | tangent | 접선
raise | 제곱하다 | tenths | 소수 첫째자리
reciprocal | 상반된, 역의 | term | 항
rectangle | 직사각형 | thousandths | 소수 세째자리
rectangular box | 직육면체 | transform | 변환하다
reduce | 약분하다 | transversal | 횡단선
trapezoid | 사다리꼴 | unknown | 미지수
triangle | 삼각형 | variable | 변수
union | 합집합 | vertex | 꼭지점
unit | '1'의 수 | vertical angle | 맞꼭지각
vertical line | 수직선 | volume | 부피
whole number | 정수 | width | 폭

그리스 문자 기호 및 발음 표기

문자 표기 | 발음 표기 | 문자 표기 | 발음 표기
 | Alpha (알파) |  | Nu (누)
 | Beta (베타) |  | Xi / Ksi (크사이)
 | Gamma (감마) |  | Omicron (오미크론)
 | Delta (델타) |  | Pi (파이)
 | Epsilon (입실론) |  | Rho (로오)
 | Zeta (제타) |  | Sigma (씨그마)
 | Eta (에타) |  | Tau (타우)
 | Theta (쎄타) |  | Upsilon (업실론)
 | Lota (이오타) |  | Phi (파이)
 | Kappa (카파) |  | Chi (카이)
 | Lambda (람다) |  | Psi (프사이)
 | Mu (뮤) |  | Omega (오메가)


3D 의 시작 - 기초 선형 대수

[3D 에 필요한 수학] Determinant 의 의미 - 김성철.EDin

[참고 자료]

  1. Mathematica 3.0 류재구 / 크라운출판사
  2. Mathematica tool

행렬의 행(Row) 와 열(Col umn) 로 이루어진 숫자의 집합이다. 아래는 3행 3열의 행렬이다.

행렬에는 여러가지 연산이 있다.

  1. matA + matB
  2. matA - matB
  3. matA * matB

지금 알고자 하는 것은 Determinant 가 어떤 의미를 가지는가이다.
우리가 가장 흔히 사용하는 행렬의 용도는 어떤 변환을 위한 목적이나, 복잡한 선형 시스템을 손쉽게 풀어내기 위한 수단으로 사용하는 것인데, 변환을 목적으로 사용할 경우에는 벡터의 내적에 관한 연산을 주로 사용하고 선형 시스템의 분석이나 해석을 목적으로는 역행렬과 Determinant 를 사용한다.

이 수식을 푸는데 편리한 방법은 무엇일까? 바로 행렬을 사용하는 것이다.

A Y = P

위의 선형식에서 Y벡터의 값만 구하면, 우리에게 주어진 연립방정식은 간단히 해결되는 것인데, 만약 A라는 행렬의 역행렬만 구할 수 있다면 다음과 같은 방법으로 간단히 Y 벡터의 값을 구할 수 있다.

Y = A-1 P

하지만 A 행렬의 역행렬이 존재하지 않을 경우에는 위의 방법을 사용할 수 없다. 주어진 행렬의 역행렬의 존재 여부는 고등학교 때 배웠던 ad-bc (2차행렬에 관해서) 가 생각난다. 이 값이 0이면 역행렬이 존재하지 않는 것이고 0 이 아니면 존재하는 것이다. 이것을 판별하는 것을 Determinant 라고 하는데 이는 무엇을 의미하는지 알아보자.

어떤 행렬의 Determinant 라고 하는 것은 그 행렬의 행벡터를 변으로 하는 입체(행벡터로 둘러싸인 공간)의 체적을 의미하는 것이다(2x2 행렬의 경우에는 두행벡터 사이의 면적을 의미한다).

역행렬이 존재하기 위해서는 그 행렬을 구성하는 모든 벡터(이 경우에는 행벡터)들이 서로 상호독립적인 관계에 있어야 한다. 3차원 공간의 벡터로 이루어진 3x3 행렬의 Det (Determinant) 이 0 이면 이 공간을 이루는 벡터들이 상호독립적이 아니라는 얘기로 n 차원공간을 이 행렬로 변환했을 때의 결과가 n 차원보다 작은 차원으로 찌그러진 결과가 나온다. 테팔이라는 다리미 선전에 나오는 고양이처럼 된다.

다음 벡터행렬은 역행렬이 존재하지 않는다. 즉 Det(matA) = 0 이다.

위에서 말한대로라면 3차원에서의 Det 은 가로*세로*높이 로 구해지는 3차원 공간의 체적이 될 것이다. 벡터로 풀어 쓰면 다음과 같이 된다

Det(matA) = matA[1행] 내적 ( matA[2행] 외적 matA[3행] ) // { 1, -1, 0 } . ( { -1,-1,0 } x { -1, 1, 0} )

두 벡터의 외적이라는 것은 이들 두 벡터를 각 변으로 하는 마름모꼴의 면적을 의미하는데, 이와 같은 외적을 구하게 되면 이들 두 벡터의 면적과 같은 크기이고 이 마름모 평면과 수직인 벡터가 구해진다, 다음에는 이 면적벡터(외적)와 행렬의 첫 번째 행벡터 사이의 내적을 구하게 되는데, 이 내적이라는 것은 결국 앞에서 구한 외적벡터와 matA 의 1행 { 1, -1, 0 } 이라는 벡터 사이의 각도를 의미하게 되는 것이다.

위와 같은 내용을 염두해 볼 때, 어떤 한 행렬의 외적이 0이 된다는 것은 결국 이들 각 행벡터로 둘러싸인 평면의 면적이 0이 되던지, 아니면 이 평면과 나머지 벡터 사이의 각도가 0이 되는 경우, 아니면 둘 다가 0 이 되는 경우를 생각해 볼 수 있다. 결국 그 행렬 내의 모든 행벡터들이 서로 상호 독립이냐 아니면 어느 하나라도 다른 벡터에 종속(즉, 두 벡터의 방향이 같거나 다르지만 같은 축에 포개져 있는 것)된 것이 있는가 하는 것이다. 따라서, 행렬의 Det 가 0 이라는 것은 이행렬의 열벡터들 중에 방향이 같거나 같은 직선상에 놓여 있으며 방향만 반대인 벡터가 존재한다는 것을 의미하는 것이다.

이를 그림으로 보면 더욱 확실하게 이해할 수 있다.

Det = 1 의 그림 A | Det = 0 의 그림 B

그림 B 의 경우에는 Det 이 0 으로 나타난 행렬의 그림이다. 예상대로 하나의 연장선상에 놓여 있는 두 개의 벡터(상호 독립이 아닌 벡터)가 존재한다는 것을 눈으로 확인할 수 있다. - 끝 -


[3D에 필요한 수학] 고유값의 활용

[3D 에 필요한 수학] 고유값의 활용 - 김성철.EDin

[참고 자료]

  1. Mathematica 3.0 류재구 / 크라운출판사
  2. Mathematica tool

벡터의 변형을 위해서 행렬을 사용하면 원하는 결과를 쉽게 도출할 수 있다. 하지만 우리가 '아무렇게나 생각해낸 행렬이 어떤 벡터를 어떻게 변환시킬까?' 를 생각해 본적이 있을 것이다. 물론 결과야 벡터를 행렬에 적용해 변환해 보면 알지만 그렇게 하지 않고 알 수 있는 방법이 없을까? 여기에 쓰이는 것이 바로 고유값으로 영어로는 Eigenvalue 라고 한다.

사실 고유치라는 것이 어디에 쓰이며, 이것의 실제적인 의미가 무엇인가하는 것은 뭐라고 한마디로 딱꼬집어 말하기 어렵다. 왜냐하면, 행렬이 사용되는 곳이라면 어디에서나 다 사용되고, 그 용도 또한 분야나 상황에 따라서 너무나 다양한 모습을 갖기 때문이다. 하지만 지금 우리에게 필요한 고유치는 위에서 말한 것과 같은 성질을 갖는 것이다.

수학적으로 고유값(Eigenvalue)를 정의하면 아래와 같다.

Aх = λх

위의 조건을 만족하는 λ (람다) 값을 이 행렬에 대한 고유치라고 한다. 위의 수식을 정리하면 아래와 같다.

Aх - λх = 0
(* x를 공통인수로 뽑아 낸다 *)
(A - λ)х = 0

(* 위에서 A는 행렬이고 λ 는 상수이므로, 이들의 차원을 서로 동일하게 만들기 위해서 λ 라는 스칼라에 A 와 같은 차원의 단위행렬(I)를 곱해준다. *)
(A - λI)х = 0

위의 식에서와 같이 좌변이 0 이 되려면, х 라는 벡터가 0 이거나 (A - λI) 부분이 0 이 되어야 하는데, 벡터 부분을 0으로 만드는 것은 현실적으로 별 의미가 없으므로, (A - λI) 를 0 으로 만드는 λ 을 찾으면 되는 것이다. 그런데, (A - λI) 는 하나의 행렬을 나타내므로 이 부분이 0 이라는 것은 이 행렬의 Determinent 가 0 이라는 것을 의미하므로, (A - λI) 이 0 이라는 것은 다음과 같은 조건을 만족한다는 것이다.

Det[ (A - λI) ] = 0

예를 들어 보면

matA = { {1,2}, {3,2} };
matI = { {1,0}, {0,1} };

Det[ matA - λ matI ] = 0
(* λ 에 대해서 풀면 *)
-4 - 3λ + λ2 = 0
{ λ = -1 } , { λ = 4 }

위에서 λ 의 값, Eigenvalue 를 구해 보았다. 이는 단순히 방정식을 풀어본 것이고 이를 바탕으로 행렬을 분석해 보자.

주어진 임의의 한 점(벡터)에 하나의 행렬을 곱하면, 그 결과 그 점은 다른 곳으로 이동을 하게 된다. 이와 같이 행렬을 사용해서 어느 한 점을 다른 점으로 이동시키는 것을 선형변환이라고 한다. 이 선형 변환에서 사용되는 행렬은 주어진 점을 여러가지로 변환을 시키게 되는데, 이 변환의 가장 대표적인 것이 확대, 축소, 회전이다. 그런데, 주어진 행렬이 있을 때 그 행렬이 주어진 벡터를 회전을 하게 될지, 축소/확대를 하게 될지를 어떻게 알 수가 있을까? 물론, 가장 간단한 방법은 그 행렬을 주어진 벡터에 직접 곱해서 그 결과를 보면 알 수 있을 것이다. 하지만, 매번 이러한 작업을 한다는 것은 여간 번거러운 일이 아니다. 그렇다면, 이처럼 직접적인 계산을 하지 않고 주어진 행렬만 보고서 그 행렬이 벡터를 회전시킬지 확대/축소를 하게 될지 알 수 있는 방법은 없을까? 이 질문에 대한 대답을 제공하는 것이 바로 고유치라고 하는 것이다.

0 <= х <= 1, 0 <= y <= 1 의 범위에서 0.05 간격을 갖는 점들의 그룹이 있다고 예를 들어보자.

ListPoints = { {x, y}, {x, 0, 1, 0.05}, {y, 0, 1, 0.05} };

이를 변환 시킬 행렬은 다음과 같다.

m = { {1,2}, {2,1} };

위 행렬의 정보를 구해보면 아래와 같은 결과가 나온다. (자세한 유도 과정은 위의 책을 참고.)

"Eigen Values are : {-1., 3.} (* 고유치 *)
"Rs of Eigen Values are : {1., 3.} (* 고유치의 절대값 *)
"Args of Eigen Values are : {180. Degree, 0.} (* 회전 각도 *)
"Eigen Vectors are : {{-1., 1.}, {1., 1.}} (* 고유 벡터 변환된 좌표축을 나타냄*)
"Determinant is :-3. (* 체적 또는 면적 *)

위의 결과를 해석해 보면, 먼저 Eigenvalue 값들이 모두 실수로 되어 있으므로, 이 행렬은 벡터를 회전시키는 기능은 없다는 것을 알 수 있으며(회전은 복소수 즉 실수 + 허수 로 나타난다), 이 값은 크기가 1과 3 이므로, 이 행렬은 주어진 벡터를 x 축으로 1배, y 축으로 3 배 확대한다는 것을 알 수 있는데, 이는 다른 말로 하면 원래 평면의 면적을 3 배로 확대한다는 의미이며, 이것은 Determinent 값의 크기(절대값)를 보고서도 알 수 있다. 그리고, 이 행렬의 고유벡터가 (-1, 1) 과 (1,1) 로 얻어졌으므로, 이 행렬에 의해 변환된 벡터의 기준 좌표축이 (-1,1) 과 (1,1)이라는 것을 알 수 있다. 이 것을 그림으로 나타내면 아래와 같다.

ListPoints
변환된 ListPoints

새로운 변환 행렬에 대해 정보를 구해보면 아래와 같다.

m = { {1,2}, {-2,1} };

해석을 해보면, 고유치가 복소수 값으로 나타났으므로, 이 행렬은 주어진 데이터들을 회전시키는 성질이 있다는 것을 알 수 있다. 그렇다면 얼마만큼 회전시키는가? 그 해답은 고유치로 얻어진 복소수의 편각을 구해 보면 알 수 있는데, 위에서는 수식으로 나타났는데 이를 풀면 63.4349 의 값이 나오므로, 이 행렬은 원래 데이터를 63.4349 도 만큼 회전시킨다는 것을 의미한다. 그리고, 이 고유치의 절대값이 2.23607 과 2.23607 로 얻어진 것으로 봐서, 이 행렬은 주어진 데이터를 x 축과 y 축으로 각각 2.23607 배 확대한다는 것을 알 수 있다. 그리고, 이 행렬의 Determinent 가 5 이므로, 이 행렬은 기존 평면의 면적을 5배로 확대한다는 것을 알 수 있다. 8번 변환 과정의 결과를 연속된 그림으로 살펴보면 아래와 같다. 변환 대상은 위와 마찬가지로 ListPoints 이다.

결과를 보면 알 수 있듯이, 이 행렬은 주어진 데이터를 회전시킴과 동시에 확대도 동시에 시키고 있다는 것을 눈으로 확인 할 수 있다.

고등학교 때 배운 회전 행렬을 이용하여 회전 변환을 하는 행렬을 만들어 보면 아래와 같다.

m = { { Cos[10 π/180], -Sin[10 π/180] }, { -Sin[10 π/180], Cos[10 π/180] } };

고유치의 값들이 복소수 형태로 얻어지는 것을 봐서 이 행렬은 회전변환의 성질이 있다는 것을 알 수 있고, 이 고유치의 편각의 크기가 10 이므로, 회전의 크기가 10 도라는 것을 알 수 있다. 그런데, Determinent 의 크기가 1 이므로, 이 행렬은 확대/축소의 기능은 없다는 것을 알 수 있다. 연속된 변환 과정을 그림을 통해 살펴 보자.

해석한 결과 대로 변환이 이루어 지고 있음을 볼 수 있다. 이 외의 고유치에 대한 내용( 통계 및 기타 )은 위의 참고 자료를 보길 바란다. - 끝 -


[3D에 필요한 수학] 2D 변환

[3D 에 필요한 수학 ] 2D 변환 - 김성철.EDin

[참고 자료]

  1. Mathematica 3.0 류재구 / 크라운출판사
  2. Mathematica tool

2D 선형변환에는 이동, 크기, 회전 변환 등이 있다. 행렬이라는 도구가 가장 자주 사용되는 곳을 말하라면, 뭐니뭐니 해도 선형 변환과 관련된 부분을 들 수 있을 것이다. 물론, 이 '선형변환' 이라고 하는 개념 그 자체도 자세히 풀어서 설명하자면 한이 없겠지만, 복잡한 개념은 일단은 접어두기로 하고, 우리가 일반적으로 선형변환이라고 할 때, 그 용어가 어떤 의미를 갖는지를 대충 요약해 보면 이렇다. 선형 시스템은 모두 일종의 행렬로 표시될 수 있다. 그리고, 변환이라고 하는 것은 주어진 대상을 다른 곳으로 옮기거나 그 모양을 바꾸는 것을 말한다. 따라서, 선형 변환이라고 하는 것을 거의 상식적인 수준에서 정의하면 '행렬을 사용해서 주어진 대상을 변환한다'는 의미가 되는 것이다. 이러한 선형 변환 과정을 가장 간단히 표현하면 다음과 같이 나타낼 수 있다.

Ax = y

위의 식에서 A는 하나의 행렬을 나타내고, x, y 는 하나의 벡터를 의미하는데, 이것은 x 라는 벡터가 A 라는 행렬에 의해서 y 라는 벡터로 변환된다는것을 의미한다. 다시 말하면, A는 어떤 대상을 변환시키는 장치의 역할을 하고, A 라는 장치에 의해서 x 는 변환되는 대상이 되고, 이러한 변환의 결과가 바로 y 가 되는 것이다.

아래는 선형 변환의 대상인 점들의 집합이다.

pts = { {-1,0,} , {-1,2}, {-0.5, 2 }, {-0.5,0.5}, {0.0, 0.5}, {0.0, 0.0},{-1, 0} };

아래의 그림은 위 점들을 선으로 서로 연결한 그림이다.

좌표점들을 연결해 보니 'L' 자 모양의 도형이 그려졌다. 위의 도형을 이용해서 2차원 선형변환을 실험한다.

ReflectY = { {-1, 0}, {0, 1} };

위의 행렬은 이름이 말해주듯이 도형을 Y 축을 중심으로 대칭 이동 변환 시킨다.

변환의 결과가 위 그림과 같다. 녹색의 도형이 변환된 결과이다. 예상했던대로 Y 축을 중심으로 대칭 이동이 되었다.

선형 변환에 대한 행렬을 사용할 때는 다음과 같은 의미를 생각하면서 보면 그 행렬의 기능을 대충 짐작할 수 있다. 예를 들어, 2차원 행렬의 경우에는 다음과 같은 전형적인 형식을 염두해 둔다.

이 전형과 앞에서 우리가 만든 행렬을 비교해 보면, 우리가 만든 행렬은 첫행은 y 부분이 0 이고 두 번째 행에서는 x 부분이 0 이라는 것을 알 수 있는데, 이것이 의미하는 바는 이 행렬은 주어진 대상을 회전시키는 역할은 하지 않고 x 와 y 방향으로 수축, 확대, 혹은 그 대로 보존하는 역할을 한다는 것을 의미한다. 그런데, x 와 y 부분에 설정되어 있는 숫자의 절대값이 모두 1 이므로, 이는 원래 대상을 늘이거나 줄이는 역할도 하지 않는다는 것을 의미한다. 그런데 자세히 보면 x1 부분의 값이 -1 로 되어 있는 것을 볼 수 있는데, 이 것은 주어진 대상을 y 축을 기준으로 대칭 이동시킨다는 것을 의미한다.

ReflectX = { {1, 0}, {0, -1} };

다른 부분은 그대로이고 y 부분에 해당되는 요소만 1 이 아니라 -1 로 바꾸어 놓았다. 결과는 어떻게 될까?

x 축의 대칭으로 이동한다는 것을 알 수 있다.

x, y 에 대한 값을 모두 -1 로 지정한다면, 결과는 어떻게 될까?

ReflectX = { {-1, 0}, {0, -1} };

위의 그림에서 알 수 있듯이 x, y 성분을 모두 -1 로 지정하게 되면, 그 대상은 원점(0, 0)에 대칭인 지점으로 이동하는 것을 알 수 있다.

그렇다면 도형을 y = x 를 중심으로 대칭 이동시키는 행렬은 어떻게 만들어야 할까? 수식을 보면 y 는 x 가 되고 x 는 y 가 됨을 알 수 있다. 따라서 행렬은 다음과 같이 만들어진다.

ReflectDiag = { {0, 1} , {1, 0} }

아래의 그림을 보면 y = x 의 직선은 그려져 있지 않지만 y = x 를 중심으로 대칭 이동한다는 것을 볼 수 있다.

다음 행렬은 도형을 어떻게 변환시킬까?

ReflectDiag = { {0, -1}, {1,0} };

그림을 보면 알 수 있듯이 y = x 에 대해 대칭이동 한 후 다시 y 축을 중심으로 대칭 이동 변환 시킨다.

이번에는 다음과 같은 행렬을 사용해 보자.

ReflectDiag = { {0,1}, {-1,0} };

그림을 보면 알 수 있듯이 y = x 에 대해 대칭이동 한 후 다시 x 축을 중심으로 대칭 이동 변환 시킨다.

대칭이동에 대한 마직막 예로 다음과 같은 행렬을 생각해 보자.

ReflectDiag = { {0,-1}, {-1,0} };

이젠 행렬만 봐도 어떻게 변환 될 것이지 감이 올 것이다. 이 행렬은 도형을 y = x 에 대칭 이동 변환 시킨 후 다시 y 축에 대칭 이동 변환 그리고 x 축에 대칭 이동 변환을 시킨다.
결과를 그림으로 확인해 보자.

위에서는 주로 원 대상의 크기는 그대로 둔 채, 여러 가지 방식으로 주어진 도형을 변형하는 방법에 대해서 알아보고, 이와 같은 역할을 하는 행렬의 성질에 대해서 살펴보았다. 다시 한번 앞에서 행한 내용을 요약하면, 행렬의 각 행을 구성하는 요소 중에서 어느 한 요소의 값만 절대값이 1 인 값으로 되어 있고, 나머지는 모두 0 으로 되어 있을 경우에는, 축소, 확대, 회전 변환은 하지 않고 대칭이동만 한다는 것이다.

이번에는 원래 대상의 크기를 변환시킬 수 있는 여러 가지 행렬의 성질을 알아보자.

ScaleX = { {0.5, 0} , {0, 1} };

위 행렬의 내용을 보면, 첫 번째 행(x 좌표를 변환하는 부분)의 x 부분이 0.5 로 되어 있고, 두 번째 행(y 좌표를 변환하는 부분)의 y 좌표가 1 로 지정되어 있고, 나머지는 모두 0 으로 되어 있으므로, 이 행렬은 주어진 도형을 x 축 방향으로 1/2 로 축소하고, y 축 방향으로는 그대로 놔둔다는 것을 알 수 있다.

결과 그림을 보기전에 앞장에서 배웠던 고유값 정보를 구해보자.

고유치의 값이 모두 실수가 나왔으므로, 이 행렬은 주어진 대상을 회전시키는 기능은 없다는 것을 알 수 있다. 고유 벡터를 보면 이 행렬의 좌표 또한 원래의 x, y 좌표축과 일치하는 것을 알 수 있다. 이 행렬의 Determinent 를 보면 0.5 값이 나왔다. 이 것이 의미하는 것은 무엇인가? 한마디로, 이 행렬은 원래의 도형의 면적을 1/2 로 축소한다는 것을 의미한다. 원래 도형의 면적을 1/2 로 하는 방법에는 수없이 많은 경우가 있을 수 있겠지만, 이 Determinent 값만으로는 그 구체적인 방법까지는 알 수 없으며, 단지 그 행렬이 원래 대상을 얼마나 늘이고 줄이는지에 대해서만 알 수 있을 뿐이다. 결과그림을 보면서 예상한 결과가 나오는 지 살펴보자.

위 그림에서 보듯이, 이 변환에 의해서 원래의 도형이 x 축 방향으로 1/2 로 축소된 것을 볼 수 있다.

다음 행렬을 보자.

ScaleY = { {1,0} , {0, 0.5} };

짐작하듯이 이 행렬은 원 대상을 y 축 방향에 대해서만 크기를 1/2 로 줄일 것이다.

ScaleXY = { {0.5, 0}, {0, 0.5} };

위의 행렬은 원 대상을 x, y 축 모두 1/2 크기로 줄일 것이다. 그렇다면 전체 면적의 크기는 얼마나 줄어들까? 조금만 생각해 보면 1/4 로 줄어든 다는 것을 알 수 있다.

지금까지는 주로 행렬의 대각 성분 값의 크기에 따라서, 주어진 도형이 얼마만큼 축소 또는 확대 대는가에 대한 예들을 다루어 보아는데, 이번에는 이 대각행렬의 부호에 따라서 어떠한 변환특성을 나타내는가를 알아 보자.

RefYScaleXY = { {-0.5, 0} , {0, 0.5} };

위의 행렬은 원 대상을 x, y 축 각각 1/2 씩 줄인다음 그 결과를 다시 y 축에 대칭하도록 이동한다.

RefXScaleXY = { {0.5, 0}, {0, -0.5} };

이 행렬은 딱 보면 어떻게 변환이 이루어 질지 알 수 있을 것이다.

이번에는 주어진 대상을 회전 시키는 회전 변환에 대해서 알아보자.

우선 다음 행렬과 고유값의 정보를 보자. 행렬을 보면 고등학교 때 무조건 외위던 회전 행렬이다.

Rot = { { Cos[π/3] , -Sin[π/3] }, { Sin[π/3] , Cos[π/3] } };

고유치가 실수가 아니라 복소수로 나오는 것으로 봐서 이 행렬은 회전 변환 행렬임을 알 수 있다. 회적각도는 π/3 이고 Determinent 가 1 인 것으로 봐서 크기에는 변환이 없음을 알 수 있다.

다음 행렬을 보자.

Rot = { { Cos[π/3] , Sin[π/3] }, { -Sin[π/3] , Cos[π/3] } };

Sin 성분만 부호를 바꾸었다. 결과를 보자.

시계방향으로 회전함을 볼 수 있다.

아래의 링크 자료는 2 차원 변환에 대한 모든 공식을 담고 있는 파일이다.

참고 파일 : 2D 선형변환 공식 - 끝 -


[3D 에 필요한 수학] 3D 선형 변환

[3D 에 필요한 수학] 3D 선형 변환 - 김성철.EDin

[참고 자료]

  1. Mathematica 3.0 류재구 / 크라운출판사
  2. Mathematica tool

수학적인 논리에 있어서는 2차원이건 3차원이건 근본적인 차이는 없다. 하지만, 실질적인 면에서 보면 3차원에서는 상당히 의미 있고 유용한 변환이 2차원에서는 그리 큰 의미를 갖지 못한다거나, 2차원에서는 유용하게 쓰이는 조작이 3차원에서는 별로 사용되지 않는 등의 차이가 있다.

3차원에서의 선형 변환도 2차원에서와 마찬가지로 다음과 같은 형식을 갖는다.

대상.변환행렬 = 변환결과

앞에서 다루었던 2차원의 경우에, 위의 식에 사용된 각 항목의 형식은 다음과 같았다.

앞에서 다룬 내용은 선형변환의 실질적인 용도보다는, 그 변환에 사용되는 수학적인 조작에 더 큰 의미를 두었기 때문에, 이와 같은 행렬과 벡터를 사용했지만, 실제로 그래픽을 전문으로 하는 프로그램이나 이론서에서는 다음과 같은 형식을 사용하는 것이 보통이다.

이 방법은 2차원 좌표 값에다 스케일(Scale)을 나타내는 새로운 요소를 첨가하여, 차원을 하나 더 늘이고, 변환행렬에도 역시 스케일 조절에 대한 항목을 첨가하여 차원을 하나 증가 시킨 다음, 변환 연산을 수행하고, 그렇게 해서 얻어지는 결과에서 첫 번째, 두 번째 요소들만 뽑아서 하나의 좌표쌍을 만들면 그것이 바로 변환결과가 되는 것이다. 이 방법을 이용하여 설명을 해 나갈 것이다. 방금 설명한 이 방법을 3차원 변환에 알맞게 수정해 보면 다음과 같다.

위의 변환 행렬에서 대문자 T 로 시작되는 부분은 주어진 대상을 이동시키는데 관여하는 것이고, S 로 시작되는 부분은 그 대상의 스케일을 변경하는데 관여하는 것이다.

다시 말하면, 변환 행렬의 차원을 하나 더 증가시킴으로써 대상의 확대/축소/회전뿐만 아니라 그 대상의 이동까지, 즉 선형변환에 대한 모든 조작을 하나의 행렬로 수행할 수 있게 되는 것이다.

다음은 변환에 이용되는 점(벡터)들의 집합이다.

pts = { {0, 0, 1}, {0, 1, 1}, {1, 1, 1}, {1, 0, 1}, {0, 0, 1}, {0, 0, 0}, {0, 1, 0}, {1, 1, 0}, {1, 0, 0}, {0, 0, 0} };

그리고 이 그림은 위의 점(벡터)들을 서로 선으로 연결한 그림이다.

위의 그림에서 보듯이, 조작하고자 하는 대상은 하나의 정육면체이다. 이 입체를 대상으로 여러 가지 종류의 변환을 수행애 보기로 하는데, 맨 먼저 하고자 하는 일은 주어진 대상을 기존 위치에서 적당한 폭으로 이동시키는 것이다. 이 변환을 수행하기 위해서는 먼저 변환의 대상이 되는 좌표쌍의 맨 마지막에 스케일을 의미하는 요소를 추가하고, 원하는 변환행렬과 내적을 시킨 다음 그 결과로부터 좌표쌍을 나타내는 수치값(결과 벡터의 처음 3요소값)을 뽑아내는 등의 여러 가지 단계가 필요하지만, 여기서는 그 과정은 생략한다.

다음은 사용할 변환행렬이다.

위에서 정의한 행렬의 Tx, Ty, Tz 부분에 어떤 값을 지정해 주면 x, y, z 축을 따라서 지정된 숫자만큼 대상이 이동하는 것이다.

위의 행렬의 의미는 pts 의 좌표들을 x 방향으로 -0.5 , y 방향으로 -0.5 , z 방향으로 -0.5 만큼 이동하라는 의미이다. 이 행렬을 이용하여 변환된 좌표들을 연결한 그림을 보면 다음과 같다.

->

그림에서와 같이 대상이 이동 되었음을 볼 수 있다.

이번에는 스케일을 조절할 수 있는 행렬을 만들어 보기로 하는데, 다음에서 보는 바와 같이 변환 행렬의 대각성분만 지정하면 된다.

그럼 이번에는 주어진 대상에 대한 스케일을 바꿔 보기로 하자. 다음 행렬은 주어진 대상을 x 축 방향으로 0.5 배, y 축 방향으로 1.5 배 z 축 방향으로 2.0 배 확대 축소한 것이다.

위 행렬을 적용하여 변환된 pts 를 그려보면 다음과 같다.

->

이번에는 주어진 대상을 정해진 좌표축을 기준으로 그 반대편 방향으로 이동시키는 변환을 생각해 보자. 간단히 말하면 주어진 점을 좌표축의 반대방향으로 이동시키는 것이다. 이 변환을 위한 행렬을 만들어 보면 다음과 같은데, 행렬의 형식만을 보면 위에서 다루었던 스케일 변환 함수와 그 모양이 같다는 것을 알 수 있을 것이다. 사실, 스케일 변환 행렬과 대칭 변환 행렬은 그 모양이 같으며, 다만 스케일 변환 행렬의 경우에는 대각행렬 값의 크기를 사용하는데 비해서 대칭변환 행렬은 대각성분의 부호를 사용한다는 것이 다를 뿐이다.

만약 주어진 대상을 z 축에 대칭되도록 하고자 할 경우에는 다음 예에서 보는 바와 같이 앞에서 만든 행렬의 z 성분의 값만을 -1 로 설정하면 되는 것이다.

다음은 위 행렬을 적용한 pts 의 결과 그림이다.

->

이번에는 회전 변환에 대해서 알아보자. 3차원 공간에서의 회전은 2차원 평면에서의 경우에 비하면 상당히 복잡한 양상을 갖는데, 그 중에서 가장 기본적인 형식만을 정리해 본다면 그것은 각 좌표축을 기준으로 회전시키는 것이다. 3차원 공간에서 하나의 좌표축을 기준으로 대상을 이동시킬 때에는 회전축이 되는 좌표축에 대한 값은 그대로 두고, 나머지 두 축의 값은 2차원 평면에서 회전변환을 하는 식을 그대로 이용하면 된다. 예를 들어, x축을 기준으로 대상을 회전시키고자 한다면, 다음 함수에서 보는 것과 같이 변환 행렬의 x 성분의 값은 1로 고정시켜 놓고, 나머지 두 축에 대해서는 2차원 평면에서의 회전 변환 공식을 그대로 써 주면 되는 것이다.

이 행렬을 이용하여 π/3 도만큼 회전 변환을 하면 다음 그림과 같이 된다.

->

x 축을 중심으로 회전된 모습을 볼 수 있다.

이번에는 y 축을 중심으로 회전시키기 위한 행렬을 만들어 보자. 예상하겠지만, y 축을 중심으로 회전하고자 할 경우에는 변환 행렬의 y 성분만 일정한 값으로 고정시켜 놓고 나머지 두 축을 대상으로 회전변환 공식을 적용하면 된다.

이번에는 y 축을 중심으로 π/3 도만큼 회전변환을 해 보자.

->

z 축을 방향으로 대상을 회전시키고자 할 때는, 변환 행렬의 z 성분은 그대로 두고 나머지 성분에 대해서만 2 차원 회전 변환공식을 적용시키면 된다.

아래의 그림은 위 행렬을 이용하여 z 축을 중심으로 π/3 만큼 회전 변환 된 결과이다.

->

3차원 공간상에 놓여 있는 물체의 경우에는 그 물체를 바라보는 지점이 어디냐에 따라서 보이는 모양이 다르게 나타난다. 이번에는 주어진 대상에 대한 투시도를 얻어내는 방법에 대해서 알아보자. 언뜻 생각하면 꽤나 어려울 것 같이 여겨지겠지만, 사실은 지금까지 했던 변환 중에서 가장 간단한 것이라 할 정도로 단순한 논리이다.

사용하는 논리는 이렇다. 만약, 주어진 대상을 위에서 내려다 본 모습을 그리고자 한다고 가정해 보자. 이것은 이 물체를 xy 평면으로 투영시킨 것과 마찬가지일 것이다. 마찬가지 논리로, 이 물체를 오른쪽에서 바라본 모습을 얻고자 할 경우에는, 그 물체를 xz 평면에 투영시킨 모습을 생각하면 될 것이고, 그 물체를 정면에서 바라본 모습을 얻고자 할 경우에는 그 물체를 yz 평면에 투영시킨 모습을 생각하면 될 것이다.

먼저 이 물체를 xy 평면에 투영시키는, 다시 말하면 이 물체를 위에서 바라본 모습을 얻어내는 행렬을 만들면 다음과 같다. 이제 어느 정도 이 변환 행렬이 익숙해 졌는지 모르겠지만, 변환행렬 내부를 자세히 들여다 보면 z 축에 대한 성분이 모두 0 으로 되어 있는 것을 알 수 있는데, 변환행렬을 이렇게 만듦으로써 이 변환행렬과 주어진 대상을 내적하게 되면 그 결과의 z 성분 값이 모두 0 이 되므로, 이는 그 대상을 xy 평면에 투영시킨 것과 같은 결과를 낳게 되는 것이다.

pts 좌표로 이루어진 육면체를 위의 행렬을 이용해 변환해 보면 다음과 같다.

->

이렇게 변환된 결과를 좌표상에 나타내 보면, 위에서 보는 것처럼 육면체가 xy 평면에 투영된 모습을 볼 수 있다.

이번에는 물체를 오른쪽에서 바라본 모습, 즉 그 물체를 yz 평면에 투영시키는 변환 행렬을 만들어 보기로 하는데, 그 원리는 전과 동일하다. 이러한 역할을 하는 행렬은 다음과 같은데, 변환행렬을 보면 변환행렬의 x 축에 대한 성분이 모두 0 으로 되어 있는 것을 볼 수 있다. 따라서, 이러한 행렬을 주어진 좌표점에 적용하게 되면 그 좌표의 x 성분 값이 0 으로 되는 것이다.

아래는 결과 그림이다. 육면체를 yz 평면에 압축시켜 놓은 모양이 나타난다.

->

이번에는 지금까지와 마찬가지 방법으로, 주어진 대상을 xz 평면으로 투영시키는 행렬을 만들어 보기로 하는데, 지금까지의 논리를 제대로 이해했다면 y 축 성분만 모두 0 으로 지정하면 될 것이라는 것을 알 수 있을 것이다.

아래는 결과 그림이다. 육면체가 xz 평면으로 투영되어 있다.

->

이번에는 먼저 주어진 대상을 y 축을 중심으로 π/3 각도만큼 회전 시킨 다음, 그 결과를 다시 xy 평면으로 투영하는 경우를 생각해 보자. 물론 한참 동안 머리를 쓰면 이러한 조작을 한번에 해주는 행렬을 만들 수도 있겠지만, 가장 간단한 방법은 지금까지 다루었던 변환행렬들을 차례대로 연속해서 적용하는 것이다.

[대상] 내적 [image] 내적 [image] = 결과

그림으로 보면 다음과 같다.

-> [image] ->

이번에는 육면체를 y 축을 기준으로 π/3 만큼 회전시킨 다음, 그 결과를 yz 평면으로 투영해 보기로 하자.

[대상] 내적 [image] 내적 [image] = 결과

그림으로 보면 다음과 같다.

-> [image] ->

마지막으로 육면체를 y 축을 중시으로 회전시킨 다음, 그 결과를 다시 xz 평면에 투영시켜보자.

[대상] 내적 [image] 내적 [image] = 결과

그림으로 보면 다음과 같다.

-> [image] ->

- 끝 -


프랙탈 그래픽스

Fractal 에 대해서

by 김성철 ( paranwave@korea.com )

참고 문헌 : C 로 배우는 그래픽 프로그래밍 [ 국형태, 신동준 공저 / 기전연구사 ] [ 구입하기 ]

미리 알림 : OpenGL 을 더욱 재미있게 익혀보는 과정으로 프랙탈 그래픽스를 선택했다. 프랙탈은 정말 신비하며 환상적이다. 교재를 선택하는데 있어서 위의 참고 문헌을 선택했는데 프로그래밍 초보자를 대상으로 프랙탈 및 컴퓨터 그래픽스에 대해서 아주 쉽고 친절하게 저술되어 있다. 하지만 이 책이 쓰여질 당시의 프로그래밍 환경은 MS-DOS 였기 때문에 관련 코드들이 모두 MS-DOS 용으로 작성되어 있다. 예제 코드를 제외하면 이 책의 내용이 너무 좋기 때문에 이 강좌에서는 내용은 이 책의 내용을 참고하며 예제 코드는 MS-Windows 용 OpenGL 코드로 변환해서 진행해 나간다. 다만 이 것이 문제가 될 시 이 강좌는 사라질 것이다.

프랙탈기하(fractal geometry)라고 불리는 현대 비유클리드기하가 1961년 IBM 수학자 만델브롯(B. Mandelbrot)에 의하여 소개되었다. 선은 1차원이고 평면이나 구면, 쌍곡면은 2차원, 우리가 살고 있는 공간은 3차원이다. 이 새로운 기하가 프랙탈이라고 불리는 이유는 이 기하의 모델들의 차원이 1.26 과 같이 정수가 아닌 분수(fraction)값을 갖기 때문이다. 프랙탈이란 한 모티프(motif)가 계속 작아지는 형태로 반복되는 그림을 말한다. 만델브롯은 해안선, 눈의 결정, 구름, 나뭇잎, 고사리, 산맥등의 일상적인 대상들이 프랙탈로 표현됨을 보였다. 직선 혹은 매끈한 곡선이나 곡면으로는 자연계에 존재하는 많은 복잡한 모양을 표현할 수 없어 새로운 기하가 탄생하게 되었다. 다음 그림은 모티프가 T 의 모양으로 주어진 프랙탈을 보여준다. 수선이 두 개의 수평한 선과 길이가 같도록 T 를 그리고, 양쪽 가지 끝에 크기가 처음의 반인 T 를 그린다. 이 과정을 계속 반복하면 다음의 프랙탈을 얻는다.


프랙탈 그래픽스

1. 재귀적 프랙탈

by 김성철 ( paranwave@korea.com )

참고 문헌 : C 로 배우는 그래픽 프로그래밍 [ 국형태, 신동준 공저 / 기전연구사 ] [ 구입하기 ]

미리 알림 : OpenGL 을 더욱 재미있게 익혀보는 과정으로 프랙탈 그래픽스를 선택했다. 프랙탈은 정말 신비하며 환상적이다. 교재를 선택하는데 있어서 위의 참고 문헌을 선택했는데 프로그래밍 초보자를 대상으로 프랙탈 및 컴퓨터 그래픽스에 대해서 아주 쉽고 친절하게 저술되어 있다. 하지만 이 책이 쓰여질 당시의 프로그래밍 환경은 MS-DOS 였기 때문에 관련 코드들이 모두 MS-DOS 용으로 작성되어 있다. 예제 코드를 제외하면 이 책의 내용이 너무 좋기 때문에 이 강좌에서는 내용은 이 책의 내용을 참고하며 예제 코드는 MS-Windows 용 OpenGL 코드로 변환해서 진행해 나간다. 다만 이 것이 문제가 될 시 이 강좌는 사라질 것이다.

프랙탈의 기본 속성은 자기유사성(Self-Similarity)과 순환성(Recursiveness)으로, 프랙탈의 순환성을 구현하는 가장 원시적인 방법은 함수의 재귀호출을 이용하는 방법이다. 이에 의한 프랙탈은 있는 그대로 따져 만들어진다.

함수의 재귀호출

함수 실행 중 자기자신을 다시 호출하는 것을 재귀호출(Recursive Call)이라 한다. 재귀호출을 순환호출이라고도 한다. 다으에 재귀호출 함수를 사용한 프로그램예를 표시하였다. 이 프로그램에서 RecursiveFunc() 함수는 재귀호출 방식으로 실행된다. 즉 RecursiveFunc() 내부에서 다시 RecursiveFunc() 를 실행시키고 있다.

void RecursiveFunc(int n)
{
if( n != 3 )
RecursiveFunc(n+1);

printf("%-3d", n);
}

int main(void)
{
RecursivFunc(0);
return 0;
}

재귀 호출의 자세한 내용은 참고 문헌을 참고하거나 기타 알고리즘 서적을 참고하길 바란다.

예제 프랙탈

다음 코드는 맛보기 프로그램을 표시한 것이다. RecursiveCircle() 함수, 코드는 불과 5 줄이다. 그런데 만들어내는 결과는 무시무시하다. 재귀호출 함수 인자의 '마법의 피리'에 의한 연출이다.

#include "lib\egl.h"
#include <math.h>

using namespace egl;

#define TORAD (0.01745f)

void DrawCircle(GLfloat x, GLfloat y, GLfloat r)
{
glBegin(GL_POINTS);
for(int i=0; i<360; ++i)
{
glVertex2f(x+(r*cos(i*TORAD)),
  y+(r*sin(i*TORAD)));
}
glEnd();
}

void RecursiveCircle(GLfloat x, GLfloat y, GLfloat r)
{
if(r>1.0f)
{
DrawCircle(x, y, r);
RecursiveCircle(x-r/2, y, r/2);
  /* x:r/2 감소, r:1/2감소 */
RecursiveCircle(x+r/2, y, r/2);
  /* x:r/2 증가, r:1/2감수 */
}
}

class RenderWindow : public Window
{
public:
virtual void RenderGLScene(void);
};

void RenderWindow::RenderGLScene(void)
{
Window::RenderGLScene();

glTranslatef(0.0f, 0.0f, -100.0f);
glColor3f(1.0f, 0.0f, 0.0f);
RecursiveCircle(0.0f, 0.0f, 30.0f);
}

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,
  int nShowCmd)
{
RenderWindow app;
if(!app.Create(FALSE))
return EXIT_FAILURE;
return app.Run();
}

RecursiveCircle() 함수가 어떻게 위의 그림을 그려내는지 가물가물하다면 참고 문헌을 참고한다. 아주 자세한 설명이 적혀 있다. ;] RecursiveCircle() 의 함수 내용을 조금씩 바꿔 보면서 결과를 보자.

void RecursiveCircle(GLfloat x, GLfloat y, GLfloat r)
{
if(r>1.0f)
{
DrawCircle(x, y, r);

RecursiveCircle(x-r/2, y, r/2);
RecursiveCircle(x+r/2, y, r/2);
RecursiveCircle(x, y-r/2, r/2);
RecursiveCircle(x, y+r/2, r/2);
}
}

void RecursiveCircle(GLfloat x, GLfloat y, GLfloat r)
{
if(r>8.0f)
{
DrawCircle(x, y, r/2);

RecursiveCircle(x-r/2, y, r/2);
RecursiveCircle(x+r/2, y, r/2);
RecursiveCircle(x, y-r/2, r/2);
RecursiveCircle(x, y+r/2, r/2);
}
}

void RecursiveCircle(GLfloat x, GLfloat y, GLfloat r)
{
if(r>1.0f)
{
if(r<=10.0f)
{
glColor3f(0.0f,
  0.5f, 0.0f);
}
else if(r<=30.0f)
{
glColor3f(1.0f,
  1.0f, 0.0f);
}
else if(r<=50.0f)
{
glColor3f(1.0f,
  0.0f, 1.0f);
}
DrawCircle(x, y, r);
RecursiveCircle(x-r/2, y, r*0.4);
RecursiveCircle(x+r/2, y, r*0.4);
RecursiveCircle(x, y-r/2, r*0.4);
RecursiveCircle(x, y+r/2, r*0.4);
}
}

SOURCE CODE ARCHIVES
DOWNLOAD : Visual C++ [ rwon.zip ]

BUILD TEST
Visual C++ 6.0 ServicePack 5


프랙탈 그래픽

2. 칸토어 먼지

by 김성철 ( paranwave@korea.com )

참고 문헌 : C 로 배우는 그래픽 프로그래밍 [ 국형태, 신동준 공저 / 기전연구사 ] [ 구입하기 ]

미리 알림 : OpenGL 을 더욱 재미있게 익혀보는 과정으로 프랙탈 그래픽스를 선택했다. 프랙탈은 정말 신비하며 환상적이다. 교재를 선택하는데 있어서 위의 참고 문헌을 선택했는데 프로그래밍 초보자를 대상으로 프랙탈 및 컴퓨터 그래픽스에 대해서 아주 쉽고 친절하게 저술되어 있다. 하지만 이 책이 쓰여질 당시의 프로그래밍 환경은 MS-DOS 였기 때문에 관련 코드들이 모두 MS-DOS 용으로 작성되어 있다. 예제 코드를 제외하면 이 책의 내용이 너무 좋기 때문에 이 강좌에서는 내용은 이 책의 내용을 참고하며 예제 코드는 MS-Windows 용 OpenGL 코드로 변환해서 진행해 나간다. 다만 이 것이 문제가 될 시 이 강좌는 사라질 것이다.

프랙탈의 본성은 '축소하여 자기자신을 복제' 하는 것으로 앞 장의 예제들도 이를 만족하고 있다. 흔히들 프랙탈은 1970년대 만델브로트에 의해 창시되었다고 하나 그 이전에도 여러 수학자들에 의해 프랙탈이 연구되었다. 칸토어 먼지(Georg Cantor. 1872), 코호 곡선(Helge von Koch, 1904), 시어핀스키 양탄자(Waclaw Sierpinski. 1910년대 후반) 등이 대표적인 예이다. 이들을 고전적 프랙탈(Classical Fractal)이라 한다. 이 장에서는 고전적 프랙탈 중 칸토어 먼지를 재귀 호출 함수를 사용하여 구현하는 방법을 설명한다.

칸토어 먼지와 칸토어 집합

고전적 프랙탈 가운데 가장 오래된 것중의 하나가 칸토어 먼지로 1872년에 독일의 수학자 칸토어에 의해 발표되었다.

위의 그림은 칸토어 먼지의 그림이다. 길이가 1인 직선을 준비한 후 가운데 1/3 을 지운다. 이 결과 좌우에 두 직선이 생기는데 이 두 직선에 대해서도 가운데 1/3 을 지운다. 이러한 작업을 무한히 반복한 결과 남아 있는 것들의 모임을 칸토어 먼지라고 부른다. 칸토어 먼지를 칸토어 집합(Contor Set)이라고도 한다. 칸토어 집합은 물리, 수학, 통계 등 여러 분야에서 널리 활용되고 있다.

알고리즘 분석

위 그림의 칸토어 먼지는 완벽한 순환적 구조이다. 이와 같은 순환적 구조의 경우에는 재귀 호출 함수를 사용하여 프로그램을 작성하는데, 앨고리즘을 분석할 때에는 재귀호출이 한번 수행된 후 얻어진 그림을 가지고 분석한다. -> 방향으로 칸토어 먼지를 그리는 것으로 하고 이 그림을 그리는 함수를 Cantor()라 하자.

  1. A를 그린다.
  2. A에서 조금 ->(오른)쪽으로 이동하여 L 을 1/3 로 줄여 B,C 를 그린다.
  3. L >1 인한 1, 2 번 과정을 재귀적으로 반복한다.

코드 작성

이 상의 내용을 코드로 작성해 보자. 우선 사각형을 그려주는 함수 DrawRectangle() 를 살펴보자.

void DrawRectangle(GLfloat x, GLfloat y, GLfloat length)
{
glPushMatrix();
glTranslatef(x, y, 0.0f); //지정
  위치로 이동
glScalef(1.0f, length, 1.0f);
  //Y축으로 length 만큼 확대
//넓이 1*length
  의 사각형
glBegin(GL_QUADS);
glColor3f(1.0f,
  0.5f, 1.0f);
glVertex3f(-1.0f,
  0.0f, 0.0f);
glVertex3f(
  0.0f, 0.0f, 0.0f);
glVertex3f(
  0.0f, 1.0f, 0.0f);
glVertex3f(-1.0f,
  1.0f, 0.0f);
glEnd();
glPopMatrix();
}

너비가 1 인 사각형을 그리되, glScalef() 함수를 이용해서 Y 축으로 length 길이만큼 확대하므로 결과적으로 1*length 의 너비를 갖는 사각형을 그리게 된다. 다음은 칸토어 먼지를 그려주는 Cantor() 함수를 살펴보자.

void Cantor(GLfloat x, GLfloat y, GLfloat length)
{
if(length > 0.1f) //0.1f
  보다 큰 사각형들을 그린다.
{
DrawRectangle(x, y, length);
  //A 를 그린다.
//각
  사각형의 간격은 1.5
Cantor(x+1.5f, y, length/3.0f);
  //B 를 그린다.
Cantor(x+1.5f, y+length*(2.0f/3.0f),
  length/3.0f); //C 를 그린다.
}
}

B 를 그릴 때 A length 의 1/3 크기의 사각형을 그린다. 그리고 C 를 그릴 때 Y 축에서 2/3 만큼 아래로 이동한 좌표에 A length 의 1/3 크기의 사각형을 그린다. length 가 0.1 보다 작아질 때까지 이를 계속해서 반복한다. 다음은 투영영역을 설정하는 OnSize() 의 함수다.

void RenderWindow::OnSize(WPARAM wParam, LPARAM lParam)
{
width = LOWORD(lParam);
height = HIWORD(lParam);

if (height == 0)
height = 1;

glViewport( 0, 0, width, height );

glMatrixMode(GL_PROJECTION);
glLoadIdentity();

width /= 5; //X축 5배 확대
height /= 5; //Y축 5배 확대

// {left, top}~{right,bottom}
  => {0,0}~{width/5, height/5}
glOrtho(0, width, height, 0, 1.0f, -1.0f);

glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}

윈도우에서 사용하는 좌표계와 동일한 좌표계를 만들고 있다. 칸토어 먼지는 이러한 좌표계를 사용하는 것이 구현하기 더 쉽기 때문에 그렇게 했다. 다음은 이를 좋합한 전체적인 코드와 결과 프로그램의 화면을 보자.

#include <windows.h>
#include "lib\egl.h"

using namespace egl;

void DrawRectangle(GLfloat x, GLfloat y, GLfloat length)
{
glPushMatrix();
//지정
  위치로 이동
glTranslatef(x, y, 0.0f);
//Y축으로
  length 만큼 확대
glScalef(1.0f, length, 1.0f);
//넓이
  1*length 의 사각형
glBegin(GL_QUADS);
glColor3f(0.0f,
  1.0f, 0.0f);
glVertex3f(-1.0f,
  0.0f, 0.0f);
glVertex3f(
  0.0f, 0.0f, 0.0f);
glVertex3f(
  0.0f, 1.0f, 0.0f);
glVertex3f(-1.0f,
  1.0f, 0.0f);
glEnd();
glPopMatrix();
}

void Cantor(GLfloat x, GLfloat y, GLfloat length)
{
if(length > 0.1f)
{
DrawRectangle(x, y, length);
  //A

//각
  사각형의 간격은 1.5
Cantor(x+1.5f, y, length/3.0f);
  //B
Cantor(x+1.5f, y+length*(2.0f/3.0f),
  length/3.0f); //C
}
}

class RenderWindow : public Window
{
private:
GLsizei width, height;
public:
virtual void RenderGLScene(void);
virtual void OnSize(WPARAM wParam, LPARAM lParam);
};

void RenderWindow::OnSize(WPARAM wParam, LPARAM lParam)
{
width = LOWORD(lParam);
height = HIWORD(lParam);

if (height == 0)
height = 1;

glViewport( 0, 0, width, height );

glMatrixMode(GL_PROJECTION);
glLoadIdentity();

width /= 5; //X축 5배 확대
height /= 5; //Y축 5배 확대

// {left, top}~{right,bottom}
  => {0,0}~{width, height}
glOrtho(0, width, height, 0, 1.0f, -1.0f);

glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}

void RenderWindow::RenderGLScene(void)
{
static float length=60.0f;
static float OriginX =0.0f;
static float OriginY =0.0f;

OriginX = (float)(width/2); //칸토어
  먼지가 그려질 X축 원점은 윈도우 너비의 1/2
OriginY = (float)(height/2)-(length*0.5); //칸토어
  먼지가 그려질 Y축 원점은 윈도우 높이의 1/2 - 막대기 길이의 1/2

Window::RenderGLScene();
Cantor(OriginX, OriginY, length);
}

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,
  int nShowCmd)
{
RenderWindow app;
if(!app.Create(FALSE,"Fractal Graphics - Cantor
  Set"))
return EXIT_FAILURE;
return app.Run();
}

SOURCE CODE ARCHIVES
DOWNLOAD : Visual C++ [ cantor.zip ]

BUILD TEST
Visual C++ 6.0 ServicePack 5


프랙탈 그래픽

3. 시어핀스키 양탄자

by 김성철 ( paranwave@korea.com )

참고 문헌 : C 로 배우는 그래픽 프로그래밍 [ 국형태, 신동준 공저 / 기전연구사 ] [ 구입하기 ]

미리 알림 : OpenGL 을 더욱 재미있게 익혀보는 과정으로 프랙탈 그래픽스를 선택했다. 프랙탈은 정말 신비하며 환상적이다. 교재를 선택하는데 있어서 위의 참고 문헌을 선택했는데 프로그래밍 초보자를 대상으로 프랙탈 및 컴퓨터 그래픽스에 대해서 아주 쉽고 친절하게 저술되어 있다. 하지만 이 책이 쓰여질 당시의 프로그래밍 환경은 MS-DOS 였기 때문에 관련 코드들이 모두 MS-DOS 용으로 작성되어 있다. 예제 코드를 제외하면 이 책의 내용이 너무 좋기 때문에 이 강좌에서는 내용은 이 책의 내용을 참고하며 예제 코드는 MS-Windows 용 OpenGL 코드로 변환해서 진행해 나간다. 다만 이 것이 문제가 될 시 이 강좌는 사라질 것이다.

시어핀스키 양탄자( Sierpinski Carpet )

역사로 따지면 칸토어 먼지(1872년) 다음은 코호 곡선(1904년) 순이나 코호 곡선에 대해서는 다음 편에서 알아보기로 하고, 코호 곡선 다음 순에 해당하는 시어핀스키 양탄자(1910년대 말)에 대해 먼저 설명한다. 다음은 시어핀스키 양탄자를 표시하였다.

(A)원형 | (B)변형

정사각형을 준비하여 각 변을 3등분해서 9개의 정사각형으로 분할한 후 가운데 사각형을 제거한다. 이어서는 제거된 사각형 둘레의 8개의 분할된 사각형에 대해 동일한 방법으로 가운데 정사각형을 제거한다. 이와 같은 작업을 다시 제거된 둘레에 남겨진 8개의 사각형에 대해 무한히 반복한다. 이 결과 사각형은 어떤 모양일 될까? 칸토어의 먼지처럼 무엇이 존재하기는 하나 그것들의 총면적은 0 이 되어 버린다. 양탄자는 존재하되 양탄자로 덮을 수 있는 면적은 없는 것이다. 아리달쏭하다. 아래의 그림은 이 과정을 나타낸 것이다.

(A)원형 그림이 시어핀스크 양탄자의 원형인데 재귀의 경우의 수가 8개(↑↓←→↖↗↙↘ 방향)나 되어 좌표 따지기가 번거롭다. (B)변형 그림과 같이 변형된 시어핀스키 양탄자를 가지고 설명을 진행해 나가기로 한다. 이 양탄자는 재귀의 경우의 수가 4개(↖↗↙↘ 방향)이다. 따라서 (A)원형 양탄자에 비해 반만 따지면 된다. 이 경우 시어핀스킨 양탄자 원형과 같은 방식으로 중앙의 사각형을 제거하면 단순한 모양이 되어 버린다. 따라서 이곳에서는 다음과 같은 방식으로 양탄자를 만들도록 한다. 이 방식으로 양탄자를 만들어 보면 (A)원형 시어핀스키 양탄자는 손쉽게 만들어 낼 수 있다.

  1. 중앙에 사각형을 그린다.
  2. ↖↗↙↘ 방향으로 가각 r 만큼 이동하여 이전에 그린 사각형보다 작은 사각형을 그린다.
  3. r 을 1/2 로 축소시켜 나가면서 r > 1 이면 1~2 를 재귀적으로 반복한다.

단, 이 방법으로 만들어진 양탄자는 무면적이 되지는 않는다. 이상의 내용을 코드로 표현해 보면 아래와 같다.

// 사각형을 그린다.
void DrawRectangle(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2)
{
glRectf(x1, y1, x2, y2);
}

//변형의 시어핀스키 양탄자를 그린다.
void SCarpet_b(GLfloat x, GLfloat y, GLfloat r)
{
if(r >= 1.0)
{
DrawRectangle(x-r/3,y-r/3,x+r/3,y+r/3);
  //한변의 길이가 2r/3 인 사각형을 그린다.

SCarpet_b(x-r, y+r, r/2.0f);
  //좌상 ↖
SCarpet_b(x+r, y+r, r/2.0f);
  //우상 ↗
SCarpet_b(x-r, y-r, r/2.0f);
  //좌하 ↙
SCarpet_b(x+r, y-r, r/2.0f);
  //우하 ↘
}
}

그렇다면 시어핀스키 양탄자의 원형을 그리는 방법은 어떻게 될까? 다음과 같이 하면 된다.

  1. 중앙에 사각형을 그린다.
  2. ↑↓←→↖↗↙↘ 방향으로 가각 r 만큼 이동하여 이전에 그린 사각형보다 작은 사각형을 그린다.
  3. r 을 1/2 로 축소시켜 나가면서 r > 1 이면 1~2 를 재귀적으로 반복한다.

변형과 달리 4가지의 방향에 대해서 더 그려주기만 하면 된다. 이를 코드로 표현해 보면 아래와 같다.

//원형의 시어핀스키 양탄자를 그린다.
void SCarpet_a(GLfloat x, GLfloat y, GLfloat r)
{
if(r >= 1.0f)
{
DrawRectangle(x-r/6, y-r/6, x+r/6, y+r/6); //한변의 길이가 2r/6 인 사각형을 그린다.

SCarpet_a(x-r/3, y-r/3, r/3); //좌하 ↙
SCarpet_a(x , y-r/3, r/3); //하 ↓
SCarpet_a(x+r/3, y-r/3, r/3); //우하 ↘
SCarpet_a(x-r/3, y , r/3); //좌 ←
SCarpet_a(x+r/3, y , r/3); //우 →
SCarpet_a(x-r/3, y+r/3, r/3); //좌상 ↖
SCarpet_a(x , y+r/3, r/3); //상 ↑
SCarpet_a(x+r/3, y+r/3, r/3); //우상 ↗
}
}

프로그램 결과

위의 그림은 시어핀스키 양탄자의 원형과 변형을 출력한 결과다. 사각형을 다른 모양의 기하로 바꿔보면 더욱더 재미있을 것이다. 아래는 이 프로그램의 전체 코드다.

#include "lib\egl.h"

using namespace egl;

// 사각형을 그린다.
void DrawRectangle(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2)
{
glRectf(x1, y1, x2, y2);
}

//변형의 시어핀스키 양탄자를 그린다.
void SCarpet_b(GLfloat x, GLfloat y, GLfloat r)
{
if(r >= 1.0)
{
DrawRectangle(x-r/3,y-r/3,x+r/3,y+r/3);
  //한변의 길이가 2r/3 인 사각형을 그린다.

SCarpet_b(x-r, y+r, r/2.0f);
  //좌상 ↖
SCarpet_b(x+r, y+r, r/2.0f);
  //우상 ↗
SCarpet_b(x-r, y-r, r/2.0f);
  //좌하 ↙
SCarpet_b(x+r, y-r, r/2.0f);
  //우하 ↘
}
}

//원형의 시어핀스키 양탄자를 그린다.
void SCarpet_a(GLfloat x, GLfloat y, GLfloat r)
{
if(r >= 1.0f)
{
DrawRectangle(x-r/6, y-r/6,
  x+r/6, y+r/6); //한변의 길이가 2r/6 인 사각형을 그린다.

SCarpet_a(x-r/3, y-r/3, r/3);
   //좌하 ↙
SCarpet_a(x    ,
  y-r/3, r/3); //하 ↓
SCarpet_a(x+r/3, y-r/3, r/3);
  //우하 ↘
SCarpet_a(x-r/3, y    ,
  r/3);  //좌 ←
SCarpet_a(x+r/3, y    ,
  r/3); //우 →
SCarpet_a(x-r/3, y+r/3, r/3);
  //좌상 ↖
SCarpet_a(x    ,
  y+r/3, r/3); //상 ↑
SCarpet_a(x+r/3, y+r/3, r/3);
  //우상 ↗
}
}

class RenderWindow : public Window
{
private:
bool mode;
public:
virtual void RenderGLScene(void);
virtual void OnCreate(WPARAM wParam, LPARAM lParam);
virtual void OnSize(WPARAM wParam, LPARAM lParam);
virtual void OnKeyDown(WPARAM wParam, LPARAM lParam);
};

void RenderWindow::OnCreate(WPARAM wParam, LPARAM lParam)
{
mode = true;
}

void RenderWindow::OnKeyDown(WPARAM wParam, LPARAM lParam)
{
if(wParam == VK_F1)
{
mode = !mode;
}
Window::OnKeyDown(wParam, lParam);
}

void RenderWindow::OnSize(WPARAM wParam, LPARAM lParam)
{
GLfloat width, height;

width = LOWORD(lParam);
height = HIWORD(lParam);

if (height == 0)
height = 1;
glViewport( 0, 0, width, height );

glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(54.0f, GLfloat(width)/GLfloat(height),
  1.0f, 1000.0f);

glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}

void RenderWindow::RenderGLScene(void)
{
Window::RenderGLScene();

if(mode)
{
//원형을
  그린다.
glTranslatef(0.0f, 0.0f, -70.0f);
SCarpet_a(0.0f, 0.0f, 50.0f);
}
else
{
//변형을
  그린다.
glTranslatef(0.0f, 0.0f, -400.0f);
SCarpet_b(0.0f, 0.0f, 80.0f);
}
}

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,
  int nShowCmd)
{
RenderWindow app;
if(!app.Create(FALSE, "Fractal - Sierpinski Carpet"))
return EXIT_FAILURE;
return app.Run();
}

SOURCE CODE ARCHIVES
DOWNLOAD : Visual C++ [ scarpet.zip ]

BUILD TEST
Visual C++ 6.0 ServicePack 5


프랙탈 그래픽

4. 코호 곡선

by 김성철 ( paranwave@korea.com )

참고 문헌 : C 로 배우는 그래픽 프로그래밍 [ 국형태, 신동준 공저 / 기전연구사 ] [ 구입하기 ]

미리 알림 : OpenGL 을 더욱 재미있게 익혀보는 과정으로 프랙탈 그래픽스를 선택했다. 프랙탈은 정말 신비하며 환상적이다. 교재를 선택하는데 있어서 위의 참고 문헌을 선택했는데 프로그래밍 초보자를 대상으로 프랙탈 및 컴퓨터 그래픽스에 대해서 아주 쉽고 친절하게 저술되어 있다. 하지만 이 책이 쓰여질 당시의 프로그래밍 환경은 MS-DOS 였기 때문에 관련 코드들이 모두 MS-DOS 용으로 작성되어 있다. 예제 코드를 제외하면 이 책의 내용이 너무 좋기 때문에 이 강좌에서는 내용은 이 책의 내용을 참고하며 예제 코드는 MS-Windows 용 OpenGL 코드로 변환해서 진행해 나간다. 다만 이 것이 문제가 될 시 이 강좌는 사라질 것이다.

거북이 그래픽(Turtle Graphic) 은 그래픽스의 여러 분야에서 두루 활용된다. 프랙탈 분야에서도 예외가 아니어서 많은 프랙탈이 거북이 그래픽을 응용하고 있다. 그 대표적인 예가 나중에 다룰 L-SYSTEM 이다. 거북이 그래픽스에 대해 궁금한 사항은 다음 링크된 참고 문서를 참고한다.

  • 참고 문서 : 거북이 그래픽스의 이해

이 문서에서는 거북이 그래픽스를 이용한 코호 곡선을 설명한다.

거북이 그래픽

코호 곡선에 대해서 자세히 알아보기 전에 우리가 사용할 거북이 그래픽 클래스에 대해서 알아보자. 거북이 그래픽은 모래사장 위에서 거북이가 지나간 자리에 자국이 남듯이 캔바스 위에서 전진과 회전만을 적용해 그림을 그리는 것이다. 이 거북이 그래픽 클래스에서는 전진(Forward) 와 회전을 지원하고 회전을 왼쪽 회전과 오른쪽 회전으로 나누어 작성했다. 다음은 이 거북이 그래픽의 클래스 전 코드와 간단한 예제를 실행한 모습이다. 물론 지금 작성한 거북이 그래픽 클래스는 OpenGL 상에서 올바르게 작동한다.

Header File

#ifndef _TURTLE_H_
#define _TURTLE_H_

#include <vector>

#define TORAD 0.01745f //각도를 라디안으로 바꾸기 위한 (3.14152/180)
  의 값

//거북이가 지나간 지점의 좌표를 기억하기 위한 자료
struct Point3D
{
float x, y, z;
Point3D() { x=y=z=0.0f; }
Point3D(const Point3D& pt) { x=pt.x; y=pt.y; z=pt.z;
  }
Point3D(float ax, float ay, float az) { x=ax; y=ay;
  z=az;}
};

using namespace std;

typedef vector<Point3D> plist; //Point3D 를 담는
  vector 컨테이너
typedef vector<Point3D>::iterator pi; //반복자

class Turtle
{
private:
plist PointList; //거북이가 지나간
  모든 지점을 저장할 컨테이너
bool IsUp; //현재 거북이가 들려있는지.
  즉, 현재 거북이가 들려 있다면 선은 그려지지 않는다.
float angle; //회전 각도
public:
Turtle(void); //거북이 생성
void SetStartPoint(float x, float y, float z);
  //거북이가 출발할 시작 위치 좌표
void Forward(float length); //앞으로
  몇 length 만큼 진행하는가? 진행 거리만큼 선을 그린다. 단 IsUp == false 일 때만.
void Left(float rotate_angle); //왼쪽으로
  rotate_angle 만큼 회전한다.
void Right(float rotate_angle); //오른쪽으로
  rotate_angle 만큼 회전한다.
void Up(void); //거북이를 든다.
void Down(void); //거북이를 내려
  놓는다.
void MarkPoint(void); //거북이가
  지나간 모든 지점을 점으로 표시한다.
};

#endif

Source File

#include <windows.h>
#include <gl\gl.h>
#include <math.h>
#include "turtle.h"

Turtle::Turtle(void)
{
PointList.clear();
Point3D startPoint(0.0f, 0.0f, 0.0f);
PointList.push_back(startPoint);
IsUp = false;
angle = 0.0f;
}

void Turtle::SetStartPoint(float x, float y, float z)
{
angle = 0.0f;
Point3D startPoint(x, y, z);
PointList.clear();
PointList.push_back(startPoint);
}

void Turtle::Forward(float length)
{
static unsigned int count = 0;
static float radian = 0.0f;
static Point3D pt;
count = PointList.size();
radian = angle*TORAD;
pt.x = (length*cos(radian))+PointList[count-1].x;
pt.y = (length*sin(radian))+PointList[count-1].y;
pt.z = (PointList[count-1].z);
if(!IsUp)
{
glBegin(GL_LINES);
glVertex3f(PointList[count-1].x,
  PointList[count-1].y, PointList[count-1].z);
glVertex3f(pt.x,
  pt.y, pt.z);
glEnd();
}
PointList.push_back(pt);
}

void Turtle::Left(float rotate_angle)
{
angle = (angle + rotate_angle);
angle = fmod(angle, 360.0f);
}

void Turtle::Right(float rotate_angle)
{
angle = (angle - rotate_angle);
angle = fmod(angle, 360.0f);
}

void Turtle::Up(void)
{
IsUp = true;
}

void Turtle::Down(void)
{
IsUp = false;
}

void Turtle::MarkPoint(void)
{
glPointSize(5.0f);
glColor3f(1.0f, 0.0f, 0.0f);
glBegin(GL_POINTS);
for(pi i = PointList.begin();
  i != PointList.end(); ++i)
{
glVertex3f((*i).x,
  (*i).y, (*i).z);
}
glEnd();
glColor3f(1.0f, 1.0f, 1.0f);
}

코호 곡선

1904년 코호에 의해 발표된 곡선이다. 외관은 곡선처럼 보이나 곡선내의 어떤 부분도 '구불어진 곡선'은 포함하고 있지 않고 직선의 연결로만 되어 있다. 선의 총 길이는 무한대이다. 발표 당시에는 전혀 주목을 받지 못하다가 70여년이 흘러 만델브로트에 의해 프랙탈이 등장한 후 화려하게 복권되어, 이제는 프랙탈에 관한 어떤 책에서나 빠짐없이 등장한다.

알고리즘

코호 곡선은 완벽한 재귀적 구조로 되어 있다. 기본 도형은 직선이다. 직선에서 시작하여 다음과 같은 알고리즘으로 그려지게 된다.

    1. 직선을 그린다.
    2. 왼쪽으로 60 도 회전하여 직선을 그린다.
    3. 오른쪽으로 120 도 회전하여 직선을 그린다.
    4. 왼쪽으로 60 도 회전하여 직선을 그린다.

재귀호출이 한번 반복될 때마다 차수를 1씩 줄이고(n -> n-1), 차수가 0 이 되면 선을 그리면 된다. 다음은 이 알고리즘을 코드로 표현한 것이다.

void Koch(int n)
{
if(n==0)
{
turtle.Forward(0.3f);
}
else
{
Koch(n-1);
turtle.Left(60); Koch(n-1);
turtle.Right(120); Koch(n-1);
turtle.Left(60); Koch(n-1);
}
}

차수 n=1 인 경우를 생각해 보자. 처음에는 n=1 이고 따라서 if...else { ... } 의 { ... } 부분이 실행되어 위에서 설명한 알고리즘대로 방향을 회전시키면서 Koch() 함수가 4차례 재귀호출된다. Koch(n-1) 은 Koch(1-1) 로 실행되고 이때에는 n=0 이므로 if(n==0) turtle.Forward() 함수가 실행되어 직선을 그리고 직선 끝으로 이동한다. else { ... } 의 4 개의 Koch() 함수가 실행되고나면 위에서 위의 그림 중 직선 옆의 가운데 그림처럼 선이 그려진다.

아래는 차수 7 의 코호 곡선을 그린 실행 결과와 전 코드이다.

#include "lib\egl.h"
#include "turtle.h"
using namespace egl;

Turtle turtle;

class RenderWindow : public Window
{
private:
public:
virtual void RenderGLScene(void);
};

void Koch(int n)
{
if(n==0)
{
turtle.Forward(0.3f);
}
else
{
Koch(n-1);
turtle.Left(60); Koch(n-1);
turtle.Right(120); Koch(n-1);
turtle.Left(60); Koch(n-1);
}
}

void RenderWindow::RenderGLScene(void)
{
Window::RenderGLScene();
glTranslatef(0.0f, 0.0f, -70.0f);
turtle.SetStartPoint(-35.0f, 0.0f, 0.0f);
Koch(7);
}

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,
  int nShowCmd)
{
RenderWindow app;
if(!app.Create(FALSE, "EDin's OpenGL - Koch Curve"))
return EXIT_FAILURE;
return app.Run();
}

SOURCE CODE ARCHIVES
DOWNLOAD : Visual C++ [ koch_curve.zip ]

BUILD TEST
Visual C++ 6.0 ServicePack 5


프랙탈 그래픽

5. 코호 섬

by 김성철 ( paranwave@korea.com )

참고 문헌 : C 로 배우는 그래픽 프로그래밍 [ 국형태, 신동준 공저 / 기전연구사 ] [ 구입하기 ]

미리 알림 : OpenGL 을 더욱 재미있게 익혀보는 과정으로 프랙탈 그래픽스를 선택했다. 프랙탈은 정말 신비하며 환상적이다. 교재를 선택하는데 있어서 위의 참고 문헌을 선택했는데 프로그래밍 초보자를 대상으로 프랙탈 및 컴퓨터 그래픽스에 대해서 아주 쉽고 친절하게 저술되어 있다. 하지만 이 책이 쓰여질 당시의 프로그래밍 환경은 MS-DOS 였기 때문에 관련 코드들이 모두 MS-DOS 용으로 작성되어 있다. 예제 코드를 제외하면 이 책의 내용이 너무 좋기 때문에 이 강좌에서는 내용은 이 책의 내용을 참고하며 예제 코드는 MS-Windows 용 OpenGL 코드로 변환해서 진행해 나간다. 다만 이 것이 문제가 될 시 이 강좌는 사라질 것이다.

코호 섬

코호 곡선으로 둘러 싸여진 폐곡선이 코호 섬이다. 코호 섬을 코호눈송이라고도 한다. 위 오른쪽은 차수가 5인 코호섬인데 차수가 무한대가 되면 어떻게 될까? 섬 외곽선의 길이가 무한대가 된다. 길이가 무한한 선이 유한한 면적을 둘러싸는 것이다. 무척이나 아리달쏭하다 ;] 시어핀스키 양탄자 및 가스켓이나 코호섬, 보통 사람들의 상식에는 벗어난다. 그래서 시어핀스키나 코호 모두 발표 당시에는 '맛이 조금 간 사람(?)' 으로 외면당했다. 이들을 구제해 준 사람이 만델브로트이고 이제는 그들은 시대를 앞서 갔던 사람으로 칭송받고 있다.

알고리즘

    1. 코호 곡선을 그린다.
    2. 오른쪽으로 120 도 회전하여 코호 곡선을 그린다.
    3. 오른쪽으로 120 도 회전하여 코호 곡선을 그린다.

위의 알고리즘을 코드로 표현하면 다음과 같다.

void RenderWindow::KochIsland(int n)
{
KochCurve(n);
turtle.Right(120); KochCurve(n);
turtle.Right(120); KochCurve(n);
}

아래는 차수 3 의 코호섬을 그린 결과와 전체 코드이다.

#include "lib\egl.h"
#include "turtle.h"

using namespace egl;

class RenderWindow : public Window
{
private:
Turtle turtle;
public:
void KochCurve(int n);
void KochIsland(int n);
virtual void RenderGLScene(void);
virtual void OnSize(WPARAM wParam, LPARAM lParam);
};

void RenderWindow::KochCurve(int n)
{
if(n==0)
{
turtle.Forward(1.0f);
}
else
{
KochCurve(n-1);
turtle.Left(60); KochCurve(n-1);
turtle.Right(120); KochCurve(n-1);
turtle.Left(60); KochCurve(n-1);
}
}

void RenderWindow::KochIsland(int n)
{
KochCurve(n);
turtle.Right(120); KochCurve(n);
turtle.Right(120); KochCurve(n);
}

void RenderWindow::RenderGLScene(void)
{
Window::RenderGLScene();

glTranslatef(0.0f, 0.0f, -35.0f);

turtle.SetStartPoint(-12.0f, 7.0f, 0.0f);
KochIsland(3);
}

void RenderWindow::OnSize(WPARAM wParam, LPARAM lParam)
{
GLsizei width = LOWORD(lParam);
GLsizei height = HIWORD(lParam);

if (height == 0)
height = 1;

glViewport( 0, 0, width, height );

glMatrixMode(GL_PROJECTION);
glLoadIdentity();

gluPerspective(54.0f, GLfloat(width)/GLfloat(height),
  1.0f, 1000.0f);

glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,
  int nShowCmd)
{
RenderWindow app;
if(!app.Create(FALSE, "Fractal - Koch Island"))
return EXIT_FAILURE;
return app.Run();
}

SOURCE CODE ARCHIVES
DOWNLOAD : Visual C++ [ KochIsland.zip ]

BUILD TEST
Visual C++ 6.0 ServicePack 5


프랙탈 그래픽

6. C 곡선

by 김성철 ( paranwave@korea.com )

참고 문헌 : C 로 배우는 그래픽 프로그래밍 [ 국형태, 신동준 공저 / 기전연구사 ] [ 구입하기 ]

미리 알림 : OpenGL 을 더욱 재미있게 익혀보는 과정으로 프랙탈 그래픽스를 선택했다. 프랙탈은 정말 신비하며 환상적이다. 교재를 선택하는데 있어서 위의 참고 문헌을 선택했는데 프로그래밍 초보자를 대상으로 프랙탈 및 컴퓨터 그래픽스에 대해서 아주 쉽고 친절하게 저술되어 있다. 하지만 이 책이 쓰여질 당시의 프로그래밍 환경은 MS-DOS 였기 때문에 관련 코드들이 모두 MS-DOS 용으로 작성되어 있다. 예제 코드를 제외하면 이 책의 내용이 너무 좋기 때문에 이 강좌에서는 내용은 이 책의 내용을 참고하며 예제 코드는 MS-Windows 용 OpenGL 코드로 변환해서 진행해 나간다. 다만 이 것이 문제가 될 시 이 강좌는 사라질 것이다.

C 곡선

아래와 같은 모양의 곡선을 C 곡선이라 한다. 모양이 영문자 'C' 와 닮았기 때문이다. 언뜻 아래와 같은 프랙탈을 만드는 것은 아주 어려울 것 같은 생각이 들 수도 있겠으나 거북이 그래픽과 재귀호출을 사용하면 믿어지지 않을 정도로 간단히 만들 수 있다.

알고리즘

  1. 왼쪽으로 45 도 회전하여 선을 그린다.
  2. 오른쪽으로 90 도 회전하여 선을 그린다.
  3. 왼쪽으로 45 도 회전하여 전진 방향을 원래대로 돌려놓는다.

위의 알고리즘을 코드로 표현하면 아래와 같다.

void RenderWindow::c_curve(int n)
{
if(n == 0)
{
turtle.Forward(1.0f);
}
else
{
turtle.Left(45); c_curve(n-1);
turtle.Right(90); c_curve(n-1);
turtle.Left(45);
}
}

C 곡선의 알고리즘은 단순하다. 왼쪽으로 45 도 돌고 전진하고 오른쪽으로 90 도 돌고 전진하고 왼쪽으로 45 도 돈다. 이 단순한 알고리즘이 재귀적으로 반복하여 위와 같은 그림이 그려진다니 믿어지지 않을 정도이다. 재귀호출의 마력이다. 아래는 C 곡선을 구현한 프로그램의 실행 결과와 전체 코드다. 코드 중에 C 곡선 함수 구현 중 glFlush() 와 SwapBuffer() 함수가 있는데 이는 재귀 함수의 수행이 너무 길어질 경우 결과를 보려면 많은 시간을 기다려야 한다. 하지마 위의 함수를 호출해 주면 프랙탈이 그려지는 과정을 바로바로 볼 수 있게 된다.

#include "lib\egl.h"
#include "turtle.h"

using namespace egl;

class RenderWindow : public Window
{
private:
Turtle turtle;
public:
void c_curve(int n);
virtual void RenderGLScene(void);
virtual void OnSize(WPARAM wParam, LPARAM lParam);
};

void RenderWindow::OnSize(WPARAM wParam, LPARAM lParam)
{
GLsizei width = LOWORD(lParam);
GLsizei height = HIWORD(lParam);

if (height == 0)
height = 1;

glViewport( 0, 0, width, height );

glMatrixMode(GL_PROJECTION);
glLoadIdentity();

gluPerspective(54.0f, GLfloat(width)/GLfloat(height),
  1.0f, 1000.0f);

glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}

void RenderWindow::c_curve(int n)
{
if(n == 0)
{
turtle.Forward(1.0f);
glFlush();
SwapBuffers(mDC);
}
else
{
turtle.Left(45); c_curve(n-1);
turtle.Right(90); c_curve(n-1);
turtle.Left(45);
}
}

void RenderWindow::RenderGLScene(void)
{
Window::RenderGLScene();

glTranslatef(0.0f, 0.0f, -400.0f);

turtle.SetStartPoint(-90.0f, 0.0f, 0.0f);
c_curve(15);
}

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,
  int nShowCmd)
{
RenderWindow app;
if(!app.Create(FALSE, "Fractal - C curve"))
return EXIT_FAILURE;
return app.Run();
}

SOURCE CODE ARCHIVES
DOWNLOAD : Visual C++ [ c_curve.zip ]

BUILD TEST
Visual C++ 6.0 ServicePack 5


프랙탈 그래픽

7. 드래곤 곡선

by 김성철 ( paranwave@korea.com )

참고 문헌 : C 로 배우는 그래픽 프로그래밍 [ 국형태, 신동준 공저 / 기전연구사 ] [ 구입하기 ]

미리 알림 : OpenGL 을 더욱 재미있게 익혀보는 과정으로 프랙탈 그래픽스를 선택했다. 프랙탈은 정말 신비하며 환상적이다. 교재를 선택하는데 있어서 위의 참고 문헌을 선택했는데 프로그래밍 초보자를 대상으로 프랙탈 및 컴퓨터 그래픽스에 대해서 아주 쉽고 친절하게 저술되어 있다. 하지만 이 책이 쓰여질 당시의 프로그래밍 환경은 MS-DOS 였기 때문에 관련 코드들이 모두 MS-DOS 용으로 작성되어 있다. 예제 코드를 제외하면 이 책의 내용이 너무 좋기 때문에 이 강좌에서는 내용은 이 책의 내용을 참고하며 예제 코드는 MS-Windows 용 OpenGL 코드로 변환해서 진행해 나간다. 다만 이 것이 문제가 될 시 이 강좌는 사라질 것이다.

드래곤 곡선

C 곡선의 알고리즘을 조금만 수정하면 아래와 같은 프랙탈을 얻을 수 있다. 모양이 대충 용 비슷하다. 그래서 이름이 드래곤 곡선이다. 아래의 그림은 각각의 차수가 10 과 15인 그림이다.

알고리즘

드래곤 곡선은 차수 1 의 C 곡선을 위, 아래로 번갈아 가면서 그리는 알고리즘을 사용한다.

드래곤 곡선 위를 그리는 알고리즘

  1. 왼쪽으로 45 도 회전한다.
  2. 드래곤 곡선의 윗부분의 루틴을 호출한다.
  3. 오른쪽으로 90도 회전한다.
  4. 드래곤 곡선을 아랫부분의 루틴을 호출한다.
  5. 왼쪽으로 45 도 회전한다.

드래곤 곡선 아래를 그리는 알고리즘

  1. 오른쪽으로 45 도 회전한다.
  2. 드래곤 곡선의 윗부분의 루틴을 호출한다.
  3. 왼쪽으로 90 도 회전한다.
  4. 드래곤 곡선의 아랫부분의 루틴을 호출한다.
  5. 오른쪽으로 45 도 회전한다.

아래는 위의 알고리즘을 코드로 표현한 것이다.

//드래곤 곡선의 윗부분을 그린다.
void DragonCurve_up(int n)
{
if(n==0)
{
turtle.Forward(1.0f);
}
else
{
turtle.Left(45); DragonCurve_up(n-1);
turtle.Right(90); DragonCurve_down(n-1);
turtle.Left(45);
}
}

//드래곤 곡선의 아랫부분을 그린다.
void DragonCurve_down(int n)
{
if(n==0)
{
turtle.Forward(1.0f);
}
else
{
turtle.Right(45); DragonCurve_up(n-1);
turtle.Left(90); DragonCurve_down(n-1);
turtle.Right(45);
}
}

드래곤 곡선은 양방향 재귀성이다. DragonCurve_up() 내에서 DragonCurve_down() 을 부르고, DragonCurve_up() 도 재귀적으로 돌고 DragonCurve_down() 도 재귀적으로 돈다. 아래는 드래곤 곡선을 구현한 프로그램 결과와 전체 코드이다. 색상이 들어가 있는 그림은 거북이가 이동한 점을 표시해 본 것이다.

#include "lib\egl.h"
#include "turtle.h"

using namespace egl;

class RenderWindow : public Window
{
private:
Turtle turtle;
public:
void DragonCurve_up(int n);
void DragonCurve_down(int n);
virtual void RenderGLScene(void);
virtual void OnSize(WPARAM wParam, LPARAM lParam);
};

void RenderWindow::DragonCurve_up(int n)
{
if(n==0)
{
turtle.Forward(1.0f);
}
else
{
turtle.Left(45); DragonCurve_up(n-1);
turtle.Right(90); DragonCurve_down(n-1);
turtle.Left(45);
}
}

void RenderWindow::DragonCurve_down(int n)
{
if(n==0)
{
turtle.Forward(1.0f);
}
else
{
turtle.Right(45); DragonCurve_up(n-1);
turtle.Left(90); DragonCurve_down(n-1);
turtle.Right(45);
}
}

void RenderWindow::OnSize(WPARAM wParam, LPARAM lParam)
{
GLsizei width = LOWORD(lParam);
GLsizei height = HIWORD(lParam);

if (height == 0)
height = 1;

glViewport( 0, 0, width, height );

glMatrixMode(GL_PROJECTION);
glLoadIdentity();

gluPerspective(54.0f, GLfloat(width)/GLfloat(height),
  1.0f, 1000.0f);

glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}

void RenderWindow::RenderGLScene(void)
{
Window::RenderGLScene();

glTranslatef(0.0f, 0.0f, -300.0f);

turtle.SetStartPoint(-35.0f, 0.0f, 0.0f);
DragonCurve_up(15);
turtle.MarkPoint();
}

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,
  int nShowCmd)
{
RenderWindow app;
if(!app.Create(FALSE, "Fractal - Dragon Curve",330,260))
return EXIT_FAILURE;
return app.Run();
}

SOURCE CODE ARCHIVES
DOWNLOAD : Visual C++ [ DragonCurve.zip ]

BUILD TEST
Visual C++ 6.0 ServicePack 5


프랙탈 그래픽

8. 이진 나무

by 김성철 ( paranwave@korea.com )

참고 문헌 : C 로 배우는 그래픽 프로그래밍 [ 국형태, 신동준 공저 / 기전연구사 ] [ 구입하기 ]

미리 알림 : OpenGL 을 더욱 재미있게 익혀보는 과정으로 프랙탈 그래픽스를 선택했다. 프랙탈은 정말 신비하며 환상적이다. 교재를 선택하는데 있어서 위의 참고 문헌을 선택했는데 프로그래밍 초보자를 대상으로 프랙탈 및 컴퓨터 그래픽스에 대해서 아주 쉽고 친절하게 저술되어 있다. 하지만 이 책이 쓰여질 당시의 프로그래밍 환경은 MS-DOS 였기 때문에 관련 코드들이 모두 MS-DOS 용으로 작성되어 있다. 예제 코드를 제외하면 이 책의 내용이 너무 좋기 때문에 이 강좌에서는 내용은 이 책의 내용을 참고하며 예제 코드는 MS-Windows 용 OpenGL 코드로 변환해서 진행해 나간다. 다만 이 것이 문제가 될 시 이 강좌는 사라질 것이다.

이진 나무

아래와 같은 하나의 줄기에 두개의 가지만 있는 나무 모양의 프랙탈을 이진 나무(Binary Tree)라 한다. 쉽게 짐작할 수 있듯이 많은 자연의 성장 과정이 위와 유사한 구조로 되어 있다. 나무와 하천의 흐름이 가장 쉽게 찾아 볼 수 있는 예이다.

알고리즘

위의 차수가 3 인 이진 나무를 유심히 관찰하자. 구조가 완벽한 재귀적(순환적) 구조이다. 줄기에서 가지를 쳐 나가는 작업을 0 차 가지(잎)에 도달할 때까지 재귀적으로 반복하면 된다. 기본적인 알고리즘은 잡혔다. 이어서는 어떻게 가지를 쳐 나갈 것인가를 결정한다.

  1. 길이가 length 인 a 가지를 그린다.
  2. 왼쪽으로 30 도 회전하여 길이가 length*0.8 인 b 가지를 그린다.
  3. 오른쪽으로 60 도 회전하여 길이가 length*0.8 인 c 가지를 그린다.
  4. 3 번 과정을 마친 후 왼쪽으로 30 도 회전하여 진행 방향을 원래대로 환원시킨다.

예를 들어 차수 n = 3 인 경우 위와 같은 처리를 n 을 1 씩 줄여 나가면서 n = 0 이 될때까지 재귀적으로 반복시키면 위의 오른쪽의 나무가 그려진다. 이 때 주의할 점은 예를 들어 b 가지를 그린 후에는 선을 그리는 시작 위치를 b 가지의 시작점인 CP 로 환원시켜야 한다는 점이다. 그래야 위 알고리즘은 c 가지를 정확히 그린다. 이를 위해서 거북이 그래픽 클래스에 현재 거북이의 위치를 저장하는 스택을 포함하였다. 아래 부분은 거북이 클래스에 새로 추가된 함수의 코드이다.

Header File

typedef stack<Point3D> pstack;

class Turtle
{
private:
...
pstack PointStack; //위치를 저장할 스택
...
public:
...
void MarkCurrentPoint(void); //현재의 위치에 점을 그린다.
void Push(void); //현재의 위치를 저장한다.
void Pop(void); //저장한 위치로 돌아간다.
};

Source File

Turtle::Turtle(void)
{
//스택 포인터 모두 제거
while(PointStack.size())
{
PointStack.pop();
}
...
}

void Turtle::SetStartPoint(float x, float y, float z)
{
//스택 포인터 모두 제거
while(PointStack.size())
{
PointStack.pop();
}
...
}

void Turtle::MarkCurrentPoint(void)
{
glPointSize(5.0f);
glBegin(GL_POINTS);
glColor3f(1.0f, 0.0f, 0.0f);
glVertex3f(PointList[PointList.size()-1].x,
  PointList[PointList.size()-1].y, PointList[PointList.size()-1].z);
glColor3f(1.0f, 1.0f, 1.0f);
glEnd();
}

void Turtle::Push(void)
{
int index = PointList.size()-1;
Point3D pt(PointList[index].x, PointList[index].y, PointList[index].z);

//포인트 리스트에 저장되어 있는 마지막 점을
  push 한다.
PointStack.push(pt);
}

void Turtle::Pop(void)
{
if(PointStack.size())
{
Point3D pt(PointStack.top());
PointStack.pop();
PointList.push_back(pt);
}
}

위의 알고리즘을 코드로 표현하면 아래와 같다.

void RenderWindow::BinaryTree(int n, float length)
{
turtle.Forward(length);
if(n>0)
{
turtle.Push(); //CP
  저장
turtle.Left(30); BinaryTree(n-1,
  length*0.8);
turtle.Pop(); //CP
  로 이동
turtle.Right(60); BinaryTree(n-1,
  length*0.8);
turtle.Left(30);
}
}

아래는 이진나무를 표현한 프로그램 결과와 전체 코드이다. 오른쪽 그림은 열매를 맺은 나무이다. n==0 일때 현재의 점을 표시하면 된다.

#include "lib\egl.h"
#include "turtle.h"

using namespace egl;

class RenderWindow : public Window
{
private:
Turtle turtle;
public:
void BinaryTree(int n, float length);
virtual void RenderGLScene(void);
};

void RenderWindow::BinaryTree(int n, float length)
{
turtle.Forward(length);

//열매를 그린다.
if(n==0)
{
turtle.MarkCurrentPoint();
}

if(n>0)
{
turtle.Push();
turtle.Left(30); BinaryTree(n-1,
  length*0.8);
turtle.Pop();
turtle.Right(60); BinaryTree(n-1,
  length*0.8);
turtle.Left(30);
}
}

void RenderWindow::RenderGLScene(void)
{
Window::RenderGLScene();

glTranslatef(0.0f, 0.0f, -7.0f);

turtle.SetStartPoint(0.0f, -2.0f, 0.0f);
turtle.Left(90);

BinaryTree(12, 1.0f);
}

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,
  int nShowCmd)
{
RenderWindow app;
if(!app.Create(FALSE, "Fractal - Binary Tree"))
return EXIT_FAILURE;
return app.Run();
}

SOURCE CODE ARCHIVES
DOWNLOAD : Visual C++ [ BinaryTree.zip ]

BUILD TEST
Visual C++ 6.0 ServicePack 5


프랙탈 그래픽

9. L-SYSTEM

by 김성철 ( paranwave@korea.com )

참고 문헌 : C 로 배우는 그래픽 프로그래밍 [ 국형태, 신동준 공저 / 기전연구사 ] [ 구입하기 ]

미리 알림 : OpenGL 을 더욱 재미있게 익혀보는 과정으로 프랙탈 그래픽스를 선택했다. 프랙탈은 정말 신비하며 환상적이다. 교재를 선택하는데 있어서 위의 참고 문헌을 선택했는데 프로그래밍 초보자를 대상으로 프랙탈 및 컴퓨터 그래픽스에 대해서 아주 쉽고 친절하게 저술되어 있다. 하지만 이 책이 쓰여질 당시의 프로그래밍 환경은 MS-DOS 였기 때문에 관련 코드들이 모두 MS-DOS 용으로 작성되어 있다. 예제 코드를 제외하면 이 책의 내용이 너무 좋기 때문에 이 강좌에서는 내용은 이 책의 내용을 참고하며 예제 코드는 MS-Windows 용 OpenGL 코드로 변환해서 진행해 나간다. 다만 이 것이 문제가 될 시 이 강좌는 사라질 것이다.

L-SYSTEM

정해진 규칙에 의해 특정 문자열을 반복하여 전개한 후 전개된 문자열에 의거하여 그림을 그리는 것을 L-시스템이라 한다. 1986년 린덴마이어(Lindenmayer)가 생태계의 성장 모형, 특히 나무의 분기 모형을 연구하는 목적으로 고안하였다.

L-SYSTEM 개요

(1) F, +, -

L-시스템에서는 거북이 그래픽과 문자열에 의거하여 프랙탈을 그리는데, 문자열의 문자 중 다음의 문자를 만나면 거북이 그래픽을 수행하여 선을 그리고 회전을 한다.

  • F : 일정한 길이의 선을 그리고 현재의 점을 선의 끝점으로 이동
  • + : 왼쪽 방향으로 angle 만큼 회전
  • - : 오른쪽 방향으로 angle 만큼 회전

(2) 변환규칙

아래와 같은 코호 곡선을 그리는 경우를 생각해 보자.

  1. 선을 그린다 ( F )
  2. 60 도 좌회전한다 ( + )
  3. 선을 그린다 ( F )
  4. 60 도 우회전한다 ( - )
  5. 60 도 우회전한다 ( - )
  6. 선을 그린다 ( F )
  7. 60 도 좌회전한다 ( + )
  8. 선을 그린다 ( F )

이에 해당하는 문자열은 다음과 같다.

1 차 변환 : F+F--F+F

위는 1 차원의 코호 곡선을 그린 것이다. 그렇다면 아래의 2 차원의 코호 곡선은 어떻게 그릴까? 1 차 코호 곡선을 그리는 규칙을 A라 한다면 A 는 다음과 같이 된다. A( F+F--F+F)

  1. A 를 그린다
  2. 60 도 좌회전한다 ( + )
  3. A 를 그린다
  4. 60 도 우회전한다 ( - )
  5. 60 도 우회전한다 ( - )
  6. A 를 그린다
  7. 60 도 좌회전한다 ( + )
  8. A 를 그린다.

이에 해당하는 문자열은 다음과 같다.

A+A--A+A

위의 규칙을 A -> F + F - - F + F 로 고쳐보면 다음과 같다.

2 차 변환 : F+F--F+F+F+F--F+F--F+F--F+F+F+F--F+F

이를 3 차 변환 해 본다면 아래와 같이 된다.

3차 변환 : F+F--F+F+F+F--F+F--F+F--F+F+F+F--F+F+F+F--F+F+F+F--F+F--F+F--F+F+F+F--F+F--F+F--F+F+F+F--F+F--F+F--F+F+F+F--F+F+F+F--F+F+F+F--F+F--F+F--F+F+F+F--F+F

이와 같이 F -> F+F--F+F 로 변환하는 규칙을 변환규칙이라고 한다. 이 변환 규칙을 3번 변환하면 위의 3차 변환이 나오게 된다.

(3) 공리

수학에서 공리는 가장 기본을 이룬다. 예를 들어 '삼각형 내각의 합은 180도이다' 가 공리의 예이다. 이러한 공리에서 많은 수학적 정리들이 만들어졌다. 즉 공리는 가장 기본적인 원칙이라고 할 수 있다. 이 공리는 마치 유전자와도 같다. 간단한 유전자가 생물체의 여러 가지 복잡한 성장을 제어한다. L-시스템에서도 공리는 이와 비슷한 개념을 갖는다. 다시 말하자면 L-시스템에서 공리는 복잡한 프랙탈안에 숨어 있는 유전자라고 할 수 있다.

L-SYSTEM 구현

지금부터 구현해볼 L-시스템은 다음과 같은 규칙을 갖는다.

  • F : 전진
  • + : 좌회전
  • - : 우회전
  • @ : 점을 그린다.(열매를 그린다)
  • [ : 현재 위치와 각도를 스택에 저장한다.
  • ] : 현재 위치와 각도를 스택의 최상위 아이템의 위치와 각도로 설정한다.
  • { : 현재 위치만을 스택에 저장한다.
  • } : 현재 위치를 스택의 최상위 아이템의 위치와 각도로 설정한다.
  • | : 왼쪽으로 180 도 회전한다.

이를 위해서 거북이 그래픽 클래스가 약간 변형되었다. 스택에 저장하는 내용과 Push() 함수에 새로운 인자가 설정된 것이다. 소스 코드를 보면 이해가 갈 것이다.

struct StackItem
{
Point3D cp;
float angle;
bool save_angle;
StackItem() { cp.x=cp.y=cp.z=0.0f; angle=0.0f; save_angle=false;
  }
StackItem(const Point3D& pt, float a, bool s)
{
cp.x = pt.x;
cp.y = pt.y;
cp.z = pt.z;
angle = a;
save_angle = s;
}
StackItem(const StackItem& i)
{
cp = i.cp;
angle = i.angle;
save_angle = i.save_angle;
}
};

typedef stack<StackItem> pstack;

class Turtle
{
...
void Push(bool save_angle=true);
void Pop(void);
...
};

void Turtle::Push(bool save_angle)
{
int index = PointList.size()-1;
Point3D pt(PointList[index].x, PointList[index].y, PointList[index].z);
//포인트 리스트에 저장되어 있는 마지막 점을
  push 한다.
// save_angle 이 true 이면 pop 수행시 저장된 각도가 현재 각도가 된다.
StackItem s(pt, angle, save_angle);
PointStack.push(s);
}

void Turtle::Pop(void)
{
if(PointStack.size())
{
StackItem s(PointStack.top());
PointStack.pop();
PointList.push_back(s.cp);
if(s.save_angle)
{
angle
  = s.angle;
}
}
}

이제 L 시스템을 구현해 보자. 아래는 소스 코드를 나타낸 것이다. 따로 설명은 하지 않고 주석을 달았다.

/*******************************************************************************
{ LSystem Class
{ written by Sungchul Kim
{ 2003.4.27
{
{ F : 전진
{ + : 왼쪽으로 회전
{ - : 오른쪽으로 회전
{ | : 왼쪽으로 180 도 회전
{ [ : 현재좌표, 현재 각도를 스택에저장
{ ] : 현재좌표, 현재 각도를 저장했던 좌표,각도로 설정
{ { : 현재좌표만 저장
{ } : 현재좌표만 저장했던 좌표로 설정
{ @ : 열매를 그린다.
*******************************************************************************/

#ifndef _LSYSTEM_H_
#define _LSYSTEM_H_

#pragma warning(disable : 4786)

#include <map>
#include <string>
#include "turtle.h"

using namespace std;

typedef map< char, string > trule_list;
typedef map< char, string >::iterator ai;

class LSystem
{
public:
string TotalAxiom; //최종 공리
trule_list TRuleList; //변환규칙 리스트
float angle; //회전각도
float length; //진행길이
float factor; //길이의 배율
Turtle *turtle; //거북이
public:
LSystem();

void SetAngle(float rotate_angle); //회전 각도를 설정한다.
void SetLength(float move_distance); //전진 거리를 설정한다.
void SetFactor(float magnification); //전진 비율을 설정한다. 최종 전진 거리는 length*factor 와 같다.

void SelectTurtle(Turtle *pturtle); //거북이를 선택한다.
void AddTransRule(char name, string rule); //변환 규칙을 생성, 추가한다.
void MakeAxiom(string axiom, int n); //공리를 만든다. n 차까지 공리 변환
void Draw(void); //공리에 의한 L-시스템을 그린다.
};

#endif

#include "LSystem.h"

LSystem::LSystem(void)
{
angle = 0.0f;
length = 1.0f;
factor = 1.0f;
turtle = NULL;
TotalAxiom = "";
TRuleList.clear();
}
void LSystem::SetAngle(float rotate_angle)
{
angle = rotate_angle;
}
void LSystem::SetLength(float move_distance)
{
length = move_distance;
}
void LSystem::SetFactor(float magnification)
{
factor = magnification;
}
void LSystem::SelectTurtle(Turtle *pturtle)
{
turtle = pturtle;
}
void LSystem::AddTransRule(char name, string rule)
{
TRuleList[name] = rule;
}
void LSystem::MakeAxiom(string axiom, int n)
{
ai fi;
string buffer;
TotalAxiom = axiom;
for(int x=0; x<n; ++x) //n차까지
  변환
{
buffer.erase(buffer.begin(),
  buffer.end());//공리를 담을 버퍼
for(int i=0; i<TotalAxiom.length();
  ++i)
{
switch(TotalAxiom[i])
{
case
  'F':
fi
  = TRuleList.find('F'); // F 에 대해서 정의된 변환 규칙이 있으면 그
  규칙을 버퍼에 넣고 아니면 그냥 F(전진) 자를 넣는다.
if(fi!=TRuleList.end())
{
buffer
  += fi->second;
}
else
{
buffer
  += "F";
}
break;
case
  '@':
buffer
  += "@";
break;
case
  '+':
buffer
  += "+";
break;
case
  '-':
buffer
  += "-";
break;
case
  '[':
buffer
  += "[";
break;
case
  ']':
buffer
  += "]";
break;
case
  '{':
buffer
  += "{";
break;
case
  '}':
buffer
  += "}";
break;
case
  '|':
buffer
  += "|";
break;
default:
  //위의 문자 이외의 문자는 해당 문자에 해당하는 변환규칙이 있으면 버퍼에 넣는다.
fi
  = TRuleList.find((char)(TotalAxiom[i]));
if(fi!=TRuleList.end())
{
buffer
  += fi->second;
}
break;
}
}
TotalAxiom = buffer; //버퍼를
  최종 공리에 넣는다.
}
}

void LSystem::Draw(void)
{
for(string::iterator i = TotalAxiom.begin(); i!=TotalAxiom.end();
  ++i)
{
switch((char)(*i))
{
case
  'F':
turtle->Forward(length*factor);
  //전진
break;
case
  '@':
turtle->MarkCurrentPoint();
  //열매그리기
break;
case
  '+':
turtle->Left(angle);
  //좌회전
break;
case
  '-':
turtle->Right(angle);
  //우회전
break;
case
  '|':
turtle->Left(180);
  //좌로 180 도 회전
break;
case
  '[':
turtle->Push(true);
  //위치, 각도 저장
break;
case
  ']':
case
  '}':
turtle->Pop();
  //위치 또느 각도 화원
break;
case
  '{':
turtle->Push(false);
  //위치 저장
break;
}
}
}

아래의 코드는 L-시스템을 사용하는 일련의 코드를 보여준다.

#include "lib\egl.h"
#include "LSystem.h"

using namespace egl;

class RenderWindow : public Window
{
private:
LSystem lsystem;
Turtle turtle;
public:
virtual BOOL InitGL(void);
virtual void RenderGLScene(void);
};

BOOL RenderWindow::InitGL(void)
{
Window::InitGL();

lsystem.SelectTurtle(&turtle); //거북이
  선택
lsystem.SetAngle(60.0f); //회전
  각도를 60 도로 설정
lsystem.SetLength(1.0f); //전진
  거리를 1.0 으로 설정
lsystem.SetFactor(1.0f); //전진
  비율을 1.0 으로 설정
lsystem.AddTransRule('F', "F+F--F+F"); //코흐
  곡선을 그리는 변환 규칙 F 생성 및 추가
lsystem.MakeAxiom("F", 3); //공리
  F 를 3 차에 걸쳐 변환

return TRUE;
}

void RenderWindow::RenderGLScene(void)
{
Window::RenderGLScene();
turtle.SetStartPoint(-13.0f, 0.0f, -30.0f); //거북이의
  시작위치를 정하고
lsystem.Draw(); //L-시스템에 의해
  생성된 프랙탈을 그린다.
}

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,
  int nShowCmd)
{
RenderWindow app;
if(!app.Create(FALSE, "EDin's OpenGL - Texture2D"))
return EXIT_FAILURE;
return app.Run();
}

아래는 실행 결과이다.

다음 강좌에서 L-시스템을 활용한 여러가지 프랙탈을 그려보도록 한다.

SOURCE CODE ARCHIVES
DOWNLOAD : Visual C++ [ LSystem.zip ]

BUILD TEST
Visual C++ 6.0 ServicePack 5


프랙탈 그래픽

10. L-SYSTEM 을 활용한 프랙탈

by 김성철 ( paranwave@korea.com )

참고 문헌 : C 로 배우는 그래픽 프로그래밍 [ 국형태, 신동준 공저 / 기전연구사 ] [ 구입하기 ]

미리 알림 : OpenGL 을 더욱 재미있게 익혀보는 과정으로 프랙탈 그래픽스를 선택했다. 프랙탈은 정말 신비하며 환상적이다. 교재를 선택하는데 있어서 위의 참고 문헌을 선택했는데 프로그래밍 초보자를 대상으로 프랙탈 및 컴퓨터 그래픽스에 대해서 아주 쉽고 친절하게 저술되어 있다. 하지만 이 책이 쓰여질 당시의 프로그래밍 환경은 MS-DOS 였기 때문에 관련 코드들이 모두 MS-DOS 용으로 작성되어 있다. 예제 코드를 제외하면 이 책의 내용이 너무 좋기 때문에 이 강좌에서는 내용은 이 책의 내용을 참고하며 예제 코드는 MS-Windows 용 OpenGL 코드로 변환해서 진행해 나간다. 다만 이 것이 문제가 될 시 이 강좌는 사라질 것이다.

L-시스템을 이용해서 여러가지 멋진 프랙탈을 그려보도록 하자. 다음과 같이 변환 규칙, 공리 를 말하면 그에 맞게 코드로 설정하면 된다.

변 환 : F -> F+F--F+F
공 리 : F

lsystem.AddTransRule('F', "F+F--F+F");
lsystem.MakeAxiom("F");

이 강좌에서는 따로 실행파일은 제공하지 않고 실행 결과의 그림과 변환 규칙, 공리만을 적는다. 자세한 내용은 참고 서적을 참고한다.

코호섬

변 환
 F -> F-F+F+FF-F-F+F
공 리
 F-F-F-F
각 도
 90 도
코 드
 lsystem.SelectTurtle(&turtle);
lsystem.SetAngle(90.0f);
lsystem.SetLength(1.0f);
lsystem.SetFactor(1.0f);
lsystem.AddTransRule('F', "F-F+F+FF-F-F+F");
lsystem.MakeAxiom("F-F-F-F", 3);

시어핀스키 사각형

변 환
 F -> FF-F-F-F-FF
공 리
 F-F-F-F
각 도
 90 도
코 드
 lsystem.SelectTurtle(&turtle);
lsystem.SetAngle(90.0f);
lsystem.SetLength(1.0f);
lsystem.SetFactor(1.0f);
lsystem.AddTransRule('F', "FF-F-F-F-FF");
lsystem.MakeAxiom("F-F-F-F", 3);

시어핀스키 가스켓

변 환
 F -> FF
변 환
 X -> ++FXF--FXF--FXF++
공 리
 FXF++FF++FF
각 도
 60 도
코 드
 lsystem.SelectTurtle(&turtle);
lsystem.SetAngle(60.0f);
lsystem.SetLength(1.0f);
lsystem.SetFactor(1.0f);
lsystem.AddTransRule('F', "FF");
lsystem.AddTransRule('X', "++FXF--FXF--FXF++");
lsystem.MakeAxiom("FXF++FF++FF", 4);

힐버트 곡선

변 환
 F -> F
변 환
 X -> +YF-XFX-FY+
변 환
 Y -> -XF+YFY+FX-
공 리
 X
각 도
 90 도
코 드
 lsystem.SelectTurtle(&turtle);
lsystem.SetAngle(90.0f);
lsystem.SetLength(1.0f);
lsystem.SetFactor(1.0f);
lsystem.AddTransRule('F', "F");
lsystem.AddTransRule('X', "+YF-XFX-FY+");
lsystem.AddTransRule('Y', "-XF+YFY+FX-");
lsystem.MakeAxiom("X", 4);

나무

변 환
 F -> FF+[+F-F-F]-[-F+F+F]
공 리
 F
각 도
 30 도
코 드
 lsystem.SelectTurtle(&turtle);
lsystem.SetAngle(30.0f);
lsystem.SetLength(1.0f);
lsystem.SetFactor(1.0f);
lsystem.AddTransRule('F', "FF+[+F-F-F]-[-F+F+F]");
lsystem.MakeAxiom("F", 4);

열매를 맺는 나무

변 환
 F -> FF+[+F-F-F@]-[-F+F+F@]
공 리
 F
각 도
 30 도
코 드
 lsystem.SelectTurtle(&turtle);
lsystem.SetAngle(30.0f);
lsystem.SetLength(1.0f);
lsystem.SetFactor(1.0f);
lsystem.AddTransRule('F', "FF+[+F-F-F@]-[-F+F+F@]");
lsystem.MakeAxiom("F", 4);

드래곤 곡선

변 환
 F -> F
변 환
 X -> X-YF-
변 환
 Y -> +FX+Y
공 리
 X
각 도
 90 도
코 드
 lsystem.SelectTurtle(&turtle);
lsystem.SetAngle(90.0f);
lsystem.SetLength(1.0f);
lsystem.SetFactor(1.0f);
lsystem.AddTransRule('F', "F");
lsystem.AddTransRule('X', "X-YF-");
lsystem.AddTransRule('Y', "+FX+Y");
lsystem.MakeAxiom("X", 10);

네모 곡선

변 환
 F -> FF-F-F-F-F-F+F
공 리
 F-F-F-F-
각 도
 90 도
코 드
 lsystem.SelectTurtle(&turtle);
lsystem.SetAngle(90.0f);
lsystem.SetLength(1.0f);
lsystem.SetFactor(1.0f);
lsystem.AddTransRule('F', "FF-F-F-F-F-F+F");
lsystem.MakeAxiom("F-F-F-F-", 3);

무궁화

변 환
 F -> F++F++F|F-F++F
공 리
 F++F++F++F++F
각 도
 36 도
코 드
 lsystem.SelectTurtle(&turtle);
lsystem.SetAngle(36.0f);
lsystem.SetLength(1.0f);
lsystem.SetFactor(1.0f);
lsystem.AddTransRule('F', "F++F++F|F-F++F");
lsystem.MakeAxiom("F++F++F++F++F", 3);

나무

변 환
 F -> F[+F[+F][-F]F][-F[+F][-F]F]F[+F][-F]F
공 리
 F
각 도
 30 도
코 드
 lsystem.SelectTurtle(&turtle);
lsystem.SetAngle(30.0f);
lsystem.SetLength(1.0f);
lsystem.SetFactor(1.0f);
lsystem.AddTransRule('F', "F[+F[+F][-F]F][-F[+F][-F]F]F[+F][-F]F");
lsystem.MakeAxiom("F", 3);

시어핀스키 곡선

변 환
 F -> F
변 환
 X -> YF+XF+Y
변 환
 Y -> XF-YF-X
공 리
 YF
각 도
 60 도
코 드
 lsystem.SelectTurtle(&turtle);
lsystem.SetAngle(60.0f);
lsystem.SetLength(1.0f);
lsystem.SetFactor(1.0f);
lsystem.AddTransRule('F', "F");
lsystem.AddTransRule('X', "YF+XF+Y");
lsystem.AddTransRule('Y', "XF-YF-X");
lsystem.MakeAxiom("YF", 6);

시어핀스키 삼각형

변 환
 F -> FXF
변 환
 X -> +FXF-FXF-FXF+
공 리
 XF
각 도
 120 도
코 드
 lsystem.SelectTurtle(&turtle);
lsystem.SetAngle(120.0f);
lsystem.SetLength(1.0f);
lsystem.SetFactor(1.0f);
lsystem.AddTransRule('F', "FXF");
lsystem.AddTransRule('X', "+FXF-FXF-FXF+");
lsystem.MakeAxiom("XF", 5);

눈송이

변 환
 F -> "" 아무런 문자로도 전재하지 않음
변 환
 L -> FL-FR--FR+FL++FLFL+FR-
변 환
 R -> +FL-FRFR--FR-FL++FL+FR
공 리
 FL
각 도
 60 도
코 드
 lsystem.SelectTurtle(&turtle);
lsystem.SetAngle(60.0f);
lsystem.SetLength(1.0f);
lsystem.SetFactor(1.0f);
lsystem.AddTransRule('F', "");
lsystem.AddTransRule('L', "FL-FR--FR+FL++FLFL+FR-");
lsystem.AddTransRule('R', "+FL-FRFR--FR-FL++FL+FR");
lsystem.MakeAxiom("FL", 3);

페아노 곡선

변 환
 F -> F
변 환
 X -> XFYFX-F-YFXFY+F+XFYFX
변 환
 Y -> YFXFY+F+XFYFX-F-YFXFY
공 리
 X
각 도
 90 도
코 드
 lsystem.SelectTurtle(&turtle);
lsystem.SetAngle(90.0f);
lsystem.SetLength(1.0f);
lsystem.SetFactor(1.0f);
lsystem.AddTransRule('F', "F");
lsystem.AddTransRule('X', "XFYFX-F-YFXFY+F+XFYFX");
lsystem.AddTransRule('Y', "YFXFY+F+XFYFX-F-YFXFY");
lsystem.MakeAxiom("X", 3);

갈대

변 환
 F -> FF+[F+FF-F]-[+F+F-F]
공 리
 F
각 도
 26 도
코 드
 lsystem.SelectTurtle(&turtle);
lsystem.SetAngle(26.0f);
lsystem.SetLength(1.0f);
lsystem.SetFactor(1.0f);
lsystem.AddTransRule('F', "FF+[F+FF-F]-[+F+F-F]");
lsystem.MakeAxiom("F", 4);

나무

변 환
 F -> FF+[+F-F-F]-[-FFF]
공 리
 F
각 도
 26 도
코 드
 lsystem.SelectTurtle(&turtle);
lsystem.SetAngle(26.0f);
lsystem.SetLength(1.0f);
lsystem.SetFactor(1.0f);
lsystem.AddTransRule('F', "FF+[+F-F-F]-[-FFF]");
lsystem.MakeAxiom("F", 4);

선인장

변 환
 F -> F
변 환
 S -> [++G][--G]TTS
변 환
 G -> +H[-G]L
변 환
 H -> -G[+HF]FL
변 환
 T -> TL
변 환
 L -> [-FFF][+FFF]F
공 리
 SLFFF
각 도
 18 도
코 드
 lsystem.SelectTurtle(&turtle);
lsystem.SetAngle(18.0f);
lsystem.SetLength(1.0f);
lsystem.SetFactor(1.0f);
lsystem.AddTransRule('F', "F");
lsystem.AddTransRule('S', "[++G][--G]TTS");
lsystem.AddTransRule('G', "+H[-G]L");
lsystem.AddTransRule('H', "-G[+HF]FL");
lsystem.AddTransRule('T', "TL");
lsystem.AddTransRule('L', "[-FFF][+FFF]F");
lsystem.MakeAxiom("SLFFF", 9);

이곳에서 설명한 L-시스템 이외에도 자신이 L-시스템을 만들어 볼 수 있다. L-시스템의 모형을 만들고 이에 대한 공리, 변환, 각도를 정해주면 된다. 자신이 설계한 L-시스템이 원하는 그림을 화면에 뿌려낼 때에는 한동안 무아지경에서 빠져 나오지 못할 것이다. ;] - 끝 -


BCB OpenGL 튜토리얼

G3DGL 컴포넌트에 대해서

by 김성철( paranwave@korea.com )

G3DGL 컴포넌트는 Borland C++ Builder(이하 BCB) 용 컴포넌트로 현재 Grafix3D.net 에서 진행하는 G3DGL 프로젝트의 결과물입니다. 이 컴포넌트는 BCB 로 OpenGL 용 어플리케이션을 개발할 때 좀 더 편하게 개발 할 수 있도록 하기 위해서 만든 것입니다. 아래는 현재 포함하고 있는 컴포넌트입니다.

  • TGLPanel : OpenGL 렌더링이 수행 되는 창입니다. 쉽게 그림을 그리는 도화지라고 생각하시면 됩니다.

계속해서 필요한 컴포넌트들을 추가할 계획입니다. 이 튜토리얼은 G3DGL 컴포넌트를 사용해서 OpenGL 을 학습하는 것이 목표입니다. 그 시작으로 이 번 장에서는 컴포넌트를 사용하여 화면이 텅 빈 OpenGL 어플리케이션을 만들어 보겠습니다.

우선 G3DGL 컴포넌트를 설치해야 합니다. 설치가 끝나면 컴포넌트 팔레트에 G3DGL 이란 탭이 생기고 그 아래에는 G3DGL 컴포넌트가 포함하고 있는 컴포넌트 아이콘이 만들어집니다.

가 TGLPanel 입니다. 그럼 새로운 프로젝트를 시작해서 아래 그림과 같이 TGLPanel 컴포넌트를 폼에 배치합니다.

컴포넌트를 배치 했으면 OpenGL 창을 만들기 위해서 필요한 설정을 해줘야합니다. OpenGL 창을 만들기 위해서는 PIXELFORMATDESCRIPTOR 를 설정하고 윈도우의 디바이스컨텍스트 와 OpenGL 의 렌더링컨텍스트를 서로 연결해 줘야합니다. 이를 오브젝트 인스펙터를 통해서 쉽게 원하는 설정을 만들 수 있습니다.

그럼 이제 예제를 만들어 봅시다. OpenGL 어플리케이션을 만들 때 필요한 과정은 다음과 같습니다.

  1. OpenGL 초기화
  2. 뷰폿 설정
  3. 투영 방법 및 투영 설정
  4. 렌더링 코드 작성

이를 위해 TGLPanel 에는 위의 과정을 처리할 수 있는 이벤트를 제공합니다. TGLPanel 의 오브젝트 인스펙터에서 Event 탭을 클릭하면 다음과 같이 TGLPanel 이 지원하는 이벤트 목록을 볼 수 있습니다.

위의 그림을 보면 OnInit 와 OnResize 그리고 OnRenderScene 이벤트가 있습니다. 이 이벤트에서 위의 과정을 알맞게 처리합니다.

  • OnInit 에서는 OpenGL 을 초기화합니다.
  • OnResize 에서는 뷰폿 및 투영을 설정합니다.
  • OnRenderScene 에서는 렌더링 코드를 작정합니다.

그럼 검은 배경에 텅빈 OpenGL 창을 만들기 위해서 각각의 이벤트 부분에 알맞은 코드를 작성해 봅시다.

우선 OnInit 코드로 여기에서는 화면을 지울 때 사용하는 색상을 설정합니다.

void __fastcall TForm1::GLPanel1Init(TObject *Sender)
{
glClearColor(0.0f, 0.0f, 0.0f, 1.0f); //배경을 지우는 색을 검은색으로 설정합니다.
}

다음은 뷰폿과 투영을 설정하는 OnResize 코드입니다.

void __fastcall TForm1::GLPanel1Resize(TObject
  *Sender)
{
if(GLPanel1->Height
  == 0) // 0 으로 나뉘는 것을 방지합니다.
{
GLPanel1->Height=1;
}

glViewport(0, 0, GLPanel1->Width, GLPanel1->Height);
  // 창 전체를 뷰폿으로 설정합니다.

glMatrixMode(GL_PROJECTION); //
  투영을 설정합니다.
glLoadIdentity(); // 투영행렬을
  초기화합니다.

// 투영을 원근투영으로 설정합니다.
gluPerspective(
45.0f,
(GLfloat)GLPanel1->Height/(GLfloat)GLPanel1->Width,
0.1f,
100.0f
);

glMatrixMode(GL_MODELVIEW); //현재
  행렬을 모델뷰행렬로 선택합니다.
glLoadIdentity();
}

항상 위처럼 창의 전체 영역을 뷰폿으로 설정하고 원근 투영에 가로세로의 비가 TglPanel 의 Width/Height 이라면 다음과 같이 코드를 작성할 수 있습니다.

void __fastcall TForm1::GLPanel1Resize(TObject
  *Sender)
{
GLPanel1->EntirePerspective(45.0f, 0.1f, 100.0f);
}

위의 EntirePerspective() 함수는 위에서 작성한 투영 설정 코드와 똑 같습니다. 단지 매번 해줘야하는 번거로움을 없애고자 함수로 만들었습니다. 함수 이름을 EntirePerspective 라고 지었는데 적절한 이름인지는 잘 모르겠네요 :)

이제 남은 것은 렌더링부분의 코드를 작성하는 것입니다. 이 것은 OnRenderScene 에 작성하는데 이 예제에서는 화면을 검은색으로 칠하기만 하면 됩니다.

void __fastcall TForm1::GLPanel1RenderScene(TObject *Sender)
{
glClear(GL_COLOR_BUFFER_BIT); // 컬러버퍼에 설정된 색으로 화면을 지운다.
}

이렇게 해서 아래의 그림처럼 만들고자 했던 예제를 만들었습니다.

위의 예제에서 TGLPanel 로 폼의 전체 영역을 덮고 싶다면 TGLPanel 의 속성 중 Align 속성을 alClient 로 설정하면 됩니다.

그럼 예제는 아래 그림처럼 됩니다.

TGLPanel 에는 여러가지의 프레임 스타일이 있습니다. BorderStyle 속성을 설정하면 됩니다. 아래는 각 스타일마다의 TGLPanel 을 나타낸 것입니다.

BorderStyle 이 bsNone 입니다.

BorderStyle 이 bsSingle 입니다.
BorderStyle 이 bsSizeToolWin 입니다. 크기 변경과 이동이 가능합니다.

BorderStyle 이 bsToolWindow 입니다. 크기변경은 불가능하고 이동은 가능합니다.
BorderStyle 이 bsDialog 입니다. 일반 윈도우와 똑 같습니다.

예제 받기 : tutorial01.zip
위의 예제는 Borland C++ Builder 6.0 에서 빌드하였습니다.
이 튜토리얼에 대한 문제점 및 질문이 있다면 포럼 및 이메일을 이용해 주세요.
- Grafix3D.NET -


BCB OpenGL 튜토리얼

렌더링을 처리하는 방법

by 김성철( paranwave@korea.com )

G3DGL 의 TGLPanel 은 기본적으로 렌더링 쓰레드를 사용해서 렌더링을 처리합니다. 하지만 렌더링 쓰레드를 사용하지 않고도 렌더링을 처리할 수 있습니다. 다음은 렌더링을 처리하는 방법을 목록으로 나타낸 것입니다.

  1. 렌더링 쓰레드를 사용한다.
  2. OnIdle 이벤트를 사용한다.
  3. OnPaint 이벤트를 사용한다.
  4. 타이머를 사용한다.

렌더링 쓰레드

렌더링 쓰레드의 사용 여부는 TGLPanel 의 UsingRenderThread 속성을 설정해서 정할 수 있습니다.

기본값은 true 로 설정되어 있습니다. 이렇게 되면 렌더링 처리 부분을 작성하지 않아도 됩니다. 프로그램이 실행될 때 자동으로 렌더링 쓰레드가 생성되어 OpenGL 의 버퍼를 관리해 줍니다. 렌더링 쓰레드 외의 방법을 사용해서 렌더링을 처리하려면 반드시 UsingRenderThread 속성을 false 로 설정해줘야 합니다.

OnIdle 이벤트

다음은 OnIlde 이벤트를 사용하는 것입니다. OnIdle 이벤트는 프로그램에 아무런 이벤트가 일어나지 않을 때 발생하는 것입니다. 이 이벤트는 TApplication 에 있는 이벤트로 직접 함수를 만들어서 연결해줘야 합니다. 하지만 컴포넌트 팔레트를 보면 Additional 팔레트에 ApplicationEvents 컴포넌트가 있습니다.

이 컴포넌트를 폼에 설치하고 이벤트 중에서 OnIdle 이벤트를 만듭니다.

그리고 다음과 같이 코드를 작성합니다.

// OnIdle 이벤트를 사용할 때
void __fastcall TForm1::GLPanel1RenderScene(TObject
  *Sender)
{
...
SwapBuffers(GLPanel1->hDC); //후면버퍼와
  전면버퍼를 교체한다.
}

void __fastcall TForm1::ApplicationEvents1Idle(TObject
  *Sender, bool &Done)
{
GLPanel1RenderScene(this);
   //렌더링 함수를 호출합니다.
Done = false; //반드시
  false 로 설정해야합니다.
}

OnPaint 이벤트

다음으로 OnPaint 이벤트를 사용하는 것인데 애니메이션이 필요 없는 그림을 그릴 때 사용하면 좋습니다. G3DGL 의 TGLPanel 에서는 UsingRenderThread 속성이 false 일 때 OnPaint 이벤트가 발생하면 자동으로 OnRenderScene 이 호출됩니다. 따라서 OnPaint 이벤트를 만들 필요가 없습니다. 그냥 OnRenderScene 이벤트 끝에 버퍼를 교체해주는 함수만 호출해 주면 됩니다.

// OnPaint 이벤트를 사용할 때
void __fastcall TForm1::GLPanel1RenderScene(TObject *Sender)
{
...
SwapBuffers(GLPanel1->hDC); //후면버퍼와 전면버퍼를 교체한다.
}

타이머

다음은 타이머를 사용하는 방법으로 간격을 두고 그림이 움직여야할 때 또는 느슨한 애니메이션이 필요할 때 사용합니다. 사용법은 타이머 컴포넌트를 폼에 설치한 후 타이머의 OnTimer 이벤트를 작성합니다. 이 때 타이머의 Interval 속성으로 업데이트 간격을 설정합니다.

void __fastcall TForm1::GLPanel1RenderScene(TObject
  *Sender)
{
...
SwapBuffers(GLPanel1->hDC); //후면버퍼와
  전면버퍼를 교체한다.
}

void __fastcall TGLForm::Timer1Timer(TObject *Sender)
{
GLPanel1->Invalidate(); //
  GLPanel1 의 클라이언트 영역을 무효화한다.
GLPanel1->Update(); //
  즉시 WM_PAINT 메세지가 처리 되도록 한다.
}


GLUT 튜토리얼

GLUT Tutorial

GLUT 설정

by Antonio

원문 : http://www.lighthouse3d.com/opengl/glut/index.php3?1

GLUT 는 OpenGL 을 위한 표준 유틸리티 툴킷입니다. 즉 OpenGL 용 어플리케이션의 개발을 편하게 할 수 있도록 도와주는 도구로 생각하면 됩니다. Mark J. Kilgard 씨가 GLUT 를 만들었는데, 그 이유는 특정 윈도우 시스템들을 알지 못해도 OpenGL 용 어플리케이션을 만들 수 있도록 하기 위해서 였죠. 너무나 고마운 일 아닙니까? Mark J. Kilgard 씨와 GLUT 에 고마워합시다~ :) GLUT 를 사용하면 X 윈도우 시스템이나 마이크로소프트의 윈도우 시스템에 대해서 배우지 않고도 OpenGL 용 어플리케이션을 만들 수 있습니다. Kilgard 씨가 X 윈도우용의 GLUT 를 만들었고 나중에 Nate Robins 씨가 마이크로소프트 윈도우즈용의 GLUT 를 만들었답니다. 우리 모두 이 두사람의 위대한 업적에 갈채를 보냅시다!

이 강좌는 GLUT 를 사용해서 어플리케이션을 만드는 방법을 설명합니다. 단, 예제의 코드를 가능한 간단하게 만들기 위해서 화려한 시각 효과 같은건 만들지 않겠습니다.

무엇이 필요한가요?

GLUT 를 이용해서 어플리케이션을 만들려면 우선 최신버전의 GLUT 가 필요해요. 당연한 얘기인가요 ;) 이 글을 쓸 때, GLUT 의 최신버전은 3.7 이었습니다. GLUT 배포판은 아주 많은 예제를 포함하고 있기 때문에 이 강좌를 다 보고 난 다음에 예제를 분석해 보는 것이 좋겠죠? 당연한 얘기입니다! :) GLUT 가 없거나 GLUT 에 대해서 궁금한 것이 있으면 GLUTs 웹페이지를 살펴보세요.

GLUT 를 사용해서 C 언어로 어플리케이션을 만들려면 3 개의 파일이 필요합니다. :

  • glut.h - 이 파일은 어플리케이션의 소스코드에 항상 포함해야하는 파일입니다. 이 파일은 대개 여러분이 사용하는 개발툴 시스템의 include 폴더 아래에 있는 gl 폴더에 들어있습니다.
  • glut.lib ( SGI 의 윈도우즈 버전 ) 과 glut32.lib ( 마이크로소프트 버전 ) - 이 파일은 glut 를 이용하는 어플리케이션이라면 반드시 링크해야 합니다. 대개 개발툴 시스템의 lib 폴더에 들어있죠.
  • glut.dll ( SGI 의 윈도우즈 버전 ) 또는 glut32.dll ( 마이크로소프트 버전 ) - OpenGL 을 사용하기 위해서 둘 중 하나만 선택하면 됩니다. 마이크로소프트 버전의 glut 를 사용하려면 glut32.dll 파일을 선택하세요. 이 파일은 운영체제의 system 폴더에 있어야합니다.

Visual C/C++ 6.0 환경 설정하기

Visual C/C++ 로 프로젝트를 만들려면 두가지를 설정해줘야 합니다. 하나는 콘솔 프로그램으로 만들 것인지 아니면 Win32 프로그램으로 만들 것인지 정해야합니다. 콘솔로 만들게 되면 어플리케이션은 두 개의 창을 갖게 됩니다. 하나는 콘솔창이고 다른 하나의 OpenGL 창이랍니다. Win32 를 선택했을 때 GLUT 를 사용하면 Win32 로 어플리케이션을 만들 때 만나게 되는 '프로그래머 혼란스럽게 하기' 란 장애물을 피해갈 수 있습니다. :) 이를 위해서 아래의 과정을 따라 하나만 바꿔주세요.

  1. 주메뉴의 Project->Settings 를 선택하세요.
  2. 대화상자에서 "Link" 탭을 선택하세요.
  3. 콤보박스의 "Category" 에서 "Output" 을 선택하세요.
  4. "Entry-point symbol" 에디트박스에 "mainCRTStartup" 이라고 입력하세요.

이미 만든 콘솔 프로젝트를 Win32 어플리케이션 프로젝트로 만들려면 아래의 과정을 따라 설정해주면 됩니다. 콘솔에서 Win32 로 바꾸는 것은 콘솔창을 만들지 않기 위해서겠죠?

  1. 위의 과정을 따라서 entry-point symbol 을 추가합니다.
  2. "Project Options" 에디트박스에서 "subsystem:console" 을 "subsystem:windows" 로 바꿔줍니다.

위의 과정을 일일이 다 해주기 귀찮다면 소스코드의 시작부분에 아래의 코드를 입력해주세요. 위의 과정과 똑같이 프로젝트를 설정해줍니다.

#pragma comment( linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"" )

프로젝트를 위의 과정에 따라 올바르게 설정했다면 콘솔창은 없고 OpenGL 창만 있는 어플리케이션이 만들어집니다. 두번째 설정은 GLUT 를 어플리케이션에 링크해주는 것인데 Visual C/C++ 을 사용한다면 아래의 과정을 따라 설정하면 됩니다.

  1. 주메뉴의 Project->Settings 를 선택하세요.
  2. 대화상자에서 "Link" 탭을 선택하세요.
  3. "Object/library modules" 에디트박스에 "opengl32.lib glut32.lib glu32.lib" 을 입력합니다.

주목 : glu32.lib 과 opengl32.lib 을 추가했습니다. 이 두개의 라이브러리 파일은 OpenGL 의 표준 라이브러리입니다. GLU 는 OpenGL 이 배포하는 표준 API 입니다.

모든 설정이 끝났나요? 잘 끝냈기를 바랍니다 :) 그럼 이제 GLUT 를 이용해서 어플리케이션을 만들어봅시다. 이 강좌에서 확실하지 않거나 궁금한 점이 있으면 연락해 주세요. 성실하게 답해드릴께요. 이런 강좌는 여러분들의 참여가 아주 중요하거든요.

이 강좌가 여러분들에게 도움이 되길 바랍니다. - Antonio -

윈도우즈용 GLUT 받기 : http://www.xmission.com/~nate/opengl.html
번역 : 김성철 ( paranwave@korea.com )


GLUT 튜토리얼

GLUT Tutorial

초기화

by Antonio

원문 : http://www.lighthouse3d.com/opengl/glut/index.php3?2

이번 장에선 glut 를 사용하는 어플리케이션의 main 함수를 만들어 볼거에요. main 함수는 필요한 초기화를 수행하고 이벤트 처리 루프를 시작한답니다. GLUT 에 있는 모든 함수들은 접두어로 "glut" 를 갖습니다. 그리고 초기화에 관련된 함수는 접두어로 "glutInit" 를 갖지요. glut 를 사용할 때 제일 먼저 호출해야할 함수가 바로 glutInit 함수입니다.

void glutInit(int *argc, char **argv);

인자 설명:
argc - main 함수에 전달되는 argc 변수의 포인터로 전혀 가공되지 않은 데이타입니다.
argv - main 함수에 전달되는 argv 변수의 포인터로 전혀 가공되지 않은 데이타입니다.

GLUT 가 초기화되고 나면, 창(Window)을 만들어야합니다. 우선 창의 위치를 설정해야하는데, 창의 왼쪽(Left), 위쪽(Top) 코너를 설정하는 걸 말합니다. 창의 위치를 설정하기 위해서 glutInitWindowPosition 함수를 사용하면 됩니다.

void glutInitWindowPosition(int x, int y);

인자 설명:
x - 화면에서 왼쪽으로 몇 픽셀 떨어져 있는지를 설정합니다. -1 값이 기본값인데, 이 값의 의미는 창이 나타날 때 윈도우즈가 알아서 위치를 결정하라는 뜻입니다. 기본값을 사용하지 않으려면 화면크기 내에서 알맞은 양수 값을 입력하세요.
y - 화면의 위쪽에서 몇 픽셀 떨어져 있는지를 설정합니다. 나머지는 x 인자의 설명과 동일합니다.

창의 위치를 설정할 때 입력하는 값들은 단지 윈도우 관리자에게 '위치가 이정도면 좋겠다' 라고 제안하는 것일 뿐입니다. 따라서 다른 위치에 창이 나타날 수 있습니다. 다음에는 창의 크기를 설정해야하는데 이 것은 glutInitWindowSize 함수를 사용하면 됩니다.

void glutInitWindowSize(int width, int height);

인자 설명:
width - 창의 너비
height - 창의 높이

마찬가지로 여기서 전달하는 크기값들도 역시 윈도우 관리자에게 "크기가 이정도면 적당하겠군!" 이라고 제안하는 것일 뿐입니다. 그리고 값을 설정할 때에는 음수값으로 설정하지 마세요!

여기까지 한 다음에는 glutInitDisplayMode 함수를 사용해서 "디스플레이모드" 를 설정해야합니다.

void glutInitDisplayMode(unsigned int mode)

인자 설명:
mode - "디스플레이모드" 의 종류값

mode 인자는 GLUT 라이브러리에 정의되어 있는 값들을 OR 비트 연산하여 합한 값인데, 색상(color) 모드와 버퍼의 수 , 버퍼의 종류를 설정해야합니다.

아래는 미리 정의되어 있는 색상 모델(color model)의 상수입니다.

  • GLUT_RGBA 또는 GLUT_RGB - RGBA 창을 만듭니다. 이 값은 glut 가 사용하는 기본 색상 모드입니다.
  • GLUT_INDEX - 인덱스 색상 모드로 설정합니다.

디스플레이 모드는 단일버퍼 창을 만들 것인지 아니면 이중버퍼 창을 만들 것인지 결정하는 것인데, 아래에 설정할 수 있는 상수가 있습니다.:

  • GLUT_SINGLE - 단일버퍼(single buffer) 창을 마든다.
  • GLUT_DOUBLE - 이중버퍼(double buffer) 창을 만든다. 부드러운 에니메이션을 만들 때 필요합니다.

그리고 여러분이 원한다면 특정 버퍼로 설정된 창을 만들 수 있습니다. 아래는 가장 많이 설정하는 버퍼입니다.:

  • GLUT_ACCUM - 어큐뮬레이션 버퍼( The accumulation buffer )
  • GLUT_STENCIL - 스텐실 버퍼 ( The stencil buffer )
  • GLUT_DEPTH - 깊이 버퍼 ( The depth buffer )

그럼, 단일 버퍼와 깊이 버퍼가 있고 RGB 색상 모드를 갖는 창을 만들려면 어떻게 해야할까요? 원하는 디스플레이 모드를 만들기 위해서 해당되는 상수들을 모두 OR 비트 연산해서 합쳐야합니다.

...
glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE | GLUT DEPTH);
...

위의 모든 과정을 수행한 다음에는 glutCreateWindow 함수를 사용해서 창을 만들어줍니다.

int glutCreateWindow(char *title);

인자 설명:
title - 창의 타이틀을 설정한다.

glutCreateWindow 함수가 반환하는 값은 창을 구별할 수 있는 정수값인데 나중에 필요할 때 사용할 수 있습니다. 물론 이 값을 담고있는 변수는 함수 외부에 있어야 되겠죠.

자 그럼 모든 초기화 과정을 수행하는 코드를 구경해 봅시다.

#include <gl/glut.h>
void main(int argc, char **argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DEPTH | GLUT_SINGLE | GLUT_RGBA);
glutInitWindowPosition(100,100);
glutInitWindowSize(320,320);
glutCreateWindow("3D Tech- GLUT Tutorial");
}

첫 줄을 보면 include 문이 있는데 GLUT 를 사용하기 위해서 반드시 필요합니다.

이 코드를 실행시켜 보면, OpenGL 창은 없고 검은색 바탕의 콘솔 윈도우만 볼 수 있습니다. 게다가 몇 초가 지나면 이 콘솔창 마저도 사라지죠. 그럼 해야할 일이 더 있는 건가요? 네 그렇습니다! 무엇을 그리기 위해서는 두 가지를 더 설정해 줘야합니다. 하나는 GLUT 에게 어떤 함수가 렌더링을 책임지고 수행하는지 알려주는 것입니다.

그럼 렌더링을 책임지는 함수를 만들어볼까요? 색상 버퍼를 깨끗이 지우고 삼각형 한개를 그리는 함수를 만들어보겠습니다.

...
void renderScene(void)
{
glClear(GL_COLOR_BUFFER_BIT);
glBegin(GL_TRIANGLES);
glVertex3f(-0.5,-0.5,0.0);
glVertex3f(0.5,0.0,0.0);
glVertex3f(0.0,0.5,0.0);
glEnd();
glFlush();
}

우리는 함수의 이름만 봐도 이 함수가 렌더링에 관련된 함수라는 것을 알 수 있지만 GLUT 는 모르기 때문에 GLUT 에게 어떤 함수가 렌더링을 처리하는데 사용될 것인지 알려줘야합니다. 렌더링 함수로 정해진 함수는 콜백 함수로 등록되어 렌더링 을 할 때면 언제든지 호출됩니다. 지금 예제는 GLUT 에게 창을 다시 그려야할 때 renderScene 함수를 사용하라고 알려주는 것입니다. GLUT 에는 다시 그리기가 필요할 때 사용하는 함수이름을 인자로 받아 설정하는 함수가 있습니다. 단 주의할 점은 이 함수의 인자로 전달된 함수가 창이 만들어질 때도 호출된다는 것입니다. 다음은 이 함수의 설명입니다.

void glutDisplayFunc(void (*func)(void));

인자 설명:
func - 다시 그리기가 필요할 때 호출되는 함수의 이름입니다. 단, NULL 을 전달하면 안됩니다.

설정해줘야 할 것 중 마지막 하나는 GLUT 로 어플리케이션의 이벤트 처리 루프를 설정하는 것입니다. GLUT 는 이를 위해서 이벤트 루프를 제공합니다. 이 루프는 다음 이벤트가 일어날 때까지 항상 기다리고 있습니다. 그 GLUT 함수는 glutMainLoop 함수이고 아래는 이 함수의 설명입니다.:

void glutMainLoop(void)

예제의 전체 코드가 아래에 있는데 OpenGL 의 함수인 glClear, glBegin, glVertex3f, glEnd 등을 사용하기 위해서 include 문을 추가했습니다.

이 코드를 실행하면 콘솔 창과 하얀색의 삼각형이 그려져 있는 OpenGL 창을 볼 수 있습니다.

#include <gl/glut.h>
#include<gl/gl.h>

void renderScene(void)
{
glClear(GL_COLOR_BUFFER_BIT);
glBegin(GL_TRIANGLES);
glVertex3f(-0.5,-0.5,0.0);
glVertex3f(0.5,0.0,0.0);
glVertex3f(0.0,0.5,0.0);
glEnd();
glFlush();
}

void main(int argc, char **argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DEPTH | GLUT_SINGLE | GLUT_RGBA);
glutInitWindowPosition(100,100);
glutInitWindowSize(320,320);
glutCreateWindow("3D Tech- GLUT Tutorial");
glutDisplayFunc(renderScene);
glutMainLoop();
}

번역 : 김성철 ( paranwave@korea.com )


GLUT 튜토리얼

GLUT Tutorial

창의 크기 변화를 위한 준비

by Antonio

원문 : http://www.lighthouse3d.com/opengl/glut/index.php3?3

VC 프로젝트 파일(glut0.zip)을 받아서 실행시켜 보세요. 두 개의 창이 보일거에요. 하나는 콘솔창이고 다른 하나는 OpenGL 창이죠. OpenGL 창을 너비와 맞지 않게 높이를 조절해 보세요. 삼각형이 일그러져서 보이죠? 이것은 우리가 투영값을 제대로 설정하지 않아서 그런 것이랍니다. 기본 투영값은 너비/높이의 비율로 1 이 설정되어 있고 이 비율에 따라서 그림을 그리죠. 그래서 비율이 변하게 되면 투영되는 그림이 일그러지는 것입니다. 그래서, 창의 너비와 높이가 변할 때마다 투영값을 다시 계산해줘야합니다.

GLUT 는 창의 크기가 변할 때 호출되는 함수를 설정할 수 있는 함수를 제공합니다. 즉, 투시값을 계산하는 콜백함수를 등록하는 것입니다. 게다가 이 함수는 창이 생성되어 초기화될 때도 호출되기 때문에 창이 정사각형이 아니어도 그림을 창의 너비와 높이의 비율에 맞춰 예쁘게 그릴 수 있습니다.

GLUT 는 glutReshapeFunc 함수를 사용해서 이 일을 처리합니다.

void glutReshapeFunc(void (*func)(int width, int height));

인자 설명:
func - 창의 크기가 변할 때 투영값을 계산할 함수의 이름입니다.

제일 먼저 해야할 것은 glutReshapeFunc 함수에 전달할 함수를 선언하고 정의하는 것이겠죠. 여기서 만드는 함수는 changeSize 함수로 윈도우 크기를 조절할 때 사용되는 함수입니다. main 함수 내에서 glutReshapeFunc 함수를 호출해서 changeSize 함수를 콜백함수로 추가합니다. :

void main(int argc, char **argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DEPTH | GLUT_SINGLE | GLUT_RGBA);
glutInitWindowPosition(100,100);
glutInitWindowSize(320,320);
glutCreateWindow("3D Tech- GLUT Tutorial");
glutDisplayFunc(renderScene);

//새롭게 추가된 코드
glutReshapeFunc(changeSize);

glutMainLoop();
}

자 이제 투영값을 계산하는 함수를 정의해 봅시다. glutReshapeFunc 를 보면 changeSize 함수는 두 개의 인자를 전달 받습니다. 하나는 창의 너비(width)이고 다른 하나는 창의 높이(height) 입니다. 여기서 말하는 창의 너비와 높이는 창의 프레임을 뺀 클라이언트 영역의 크기입니니다.

void changeSize(int w, int h)
{
//창이 아주 작을 때, 0 으로 나누는 것을
  예방합니다.
if(h == 0)
h = 1;
float ratio = 1.0* w / h;

//좌표계를 수정하기 전에 초기화합니다.
glMatrixMode(GL_PROJECTION);
glLoadIdentity();

//뷰포트를 창의 전체 크기로 설정합니다.
glViewport(0, 0, w, h);

//투시값을 설정합니다.
gluPerspective(45,ratio,1,1000);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(0.0,0.0,5.0, 0.0,0.0,-1.0, 0.0f,1.0f,0.0f);
}

여러개의 함수를 살펴보았는데 다 까먹기 전에 좀 더 깊게 살펴봅시다. 위의 코드를 보면 우선 창의 너비와 높이의 비율을 계산했는데, 여기서 중요한 것은 이 값을 항상 유효한 값으로 만들기 위해서 창의 높이 값이 0 이 안 되도록 만드는 것입니다. 컴퓨터에서 0 으로 나누는 것은 옳은 연산이 아니기 때문입니다.

그리고 현재 행렬을 투영행렬로 설정하는데 이 행렬로 관측 공간을 정의합니다. 그리고 투영행렬을 단위행렬로 초기화하고 나서 뷰포트(화면상의 화상 표시영역)를 glViewport 함수를 사용해서 창의 전체 클라이언트 영역으로 설정했습니다. 뷰포트가 무엇인지 궁금하면 다른 값을 넣어서 실험해보세요. 처음 두 개의 인자는 위쪽, 오른쪽 좌표값이고 나머지 두개는 왼쪽, 아래쪽 좌표값입니다. 이 때 설정하는 좌표값은 화면 좌표값이 아니라 창의 클라이언트 영역 좌표값입니다. 각각의 값을 바꿔서 실험해 보되 너비와 높이의 비율은 widht, height 변수를 이용해서 계산한 값을 사용하세요.

gluPerspective 함수는 OpenGL 의 또 다른 라이브러리입니다. OpenGL 유틸리티 라이브러리 또는 GLU 라고 부릅니다. GLU 는 OpenGL 구현을 도와주는 표준 컴포넌트입니다. gluPerspective 함수는 투영을 만드는데 필요한 값을 인자로 받습니다. 처음 것은 yz 평면의 시야각이고 두번째 것은 뷰포트의 너비와 높이의 비율입니다. 나머지 두 인자는 절단 평면의 near 와 far 를 설정하는 값입니다. 무엇이든지 near 값보다 가깝게 있거나 far 값보다 멀리 있으면 장면에서 잘려져 나갑니다. 이 값을 잘 못 설정하면 아무것도 못 볼 수 있으니 조심하세요!

마지막으로 설정하는 것은 카메라입니다. 우선 현재 행렬을 GL_MODELVIEW 로 설정했는데 모델뷰행렬에 카메라 설정과 모델링 변환을 수행할 수 있습니다. 카메라를 설정하기 전에 항상 모델뷰행렬을 단위행렬로 초기화해야합니다. 이 것은 예전에 사용한 카메라 설정이나 변환 효과가 적용되는 것을 없애는 것이죠. gluLookAt 함수를 사용하면 카메라의 위치와 카메라가 바라보는 방향을 아주 쉽게 설정할 수 있습니다. 카메라를 이렇게 설정하는 것은 원래 세 그룹으로 나뉘는 인자들을 설정해줘야하죠. 각각의 그룹은 세개의 float 형 값을 가집니다. 처음 세가지 값은 카메라의 위치이고 두번째는 카메라가 바라보는 방향이고 나머지 값은 카메라의 위쪽을 정의하는 값입니다. 보통 이 값에는 (0.0, 1.0, 0.0) 을 사용하는데 카메라가 기울지 않았다는 것을 말합니다. 카메라를 기울이고 싶다면 이 값을 조절하면 됩니다. 예를 들어서 모든 것이 뒤집혀서 보이게 할려면 (0.0, -1.0, 0.0) 으로 설정하면 됩니다.

VC 프로젝트 파일(glut1.zip) 을 받아서 창의 크기를 조절해 보면서 삼각형의 비율이 일정하게 유지되는지 살펴보세요. 그리고 새로 소개한 함수들의 인자를 바꿔가면서 실제 어떤 일이 일어나는 살펴보길 바랍니다.

번역 : 김성철 ( paranwave@korea.com )


GLUT 튜토리얼

GLUT Tutorial

애니메이션

by Antonio

원문 : http://www.lighthouse3d.com/opengl/glut/index.php3?4

지금까지 잘 따라오셨습니다. 우린 하얀색 삼각형이 있는 OpenGL 창을 만들어 보았지만 전혀 재미가 없었지요. 그렇지만 이제부터가 시작입니다.! 지금부터 삼각형을 살살 돌려보도록하죠 :)

우선 main 함수에 몇가지를 더 추가해줘야합니다. 첫째, GLUT 에 이중버퍼(double buffer)가 필요하다고 알려주는 것이죠. 이중버퍼는 부드러운 애니메이션을 만들기 위해서 꼭 필요한 것입니다. 이중버퍼는 후면버퍼(back buffer)에 그림을 다 그리고 나서 전면 버퍼(우리가 실제로 보는 장면, front buffer)로 교체하는데, 이렇게 해서 렌더링을 수행합니다. 이중버퍼는 장면의 깜빡임을 제거할 때도 사용합니다.

...
glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);
...

두번째로 설정해줘야할 것은 GLUT 에게 어플리케이션의 휴면(Idle) 시간에 렌더링 함수가 호출되도록 알려주는 것입니다. 이렇게 해주면 GLUT 가 계속해서 렌더링 함수를 호출하기 때문에 결과적으로 애니메이션이 만들어지는 것입니다. GLUT 는 glutIdleFunc 함수를 제공하는데 이 함수로 어플리케이션의 휴면(Idle) 시간에 호출될 콜백 함수를 등록할 수 있습니다.

void glutIdleFunc(void (*func)(void));

인자설명:
func - 어플리케이션의 휴면(Idle) 시간에 호출될 함수의 이름입니다.

우리가 만들 예제는 어플리케이션의 휴면(idle) 시간에 호출되는 함수를 renderScene 함수로 설정하고 깊이 검사가 가능하도록 설정합니다. OpenGL 은 깊이 검사를 알아서 설정해 주지 않기 때문에 깊이 검사를 설정하지 않으면 물체가 그려질 때 앞뒤 순서가 맞지 않게 그려집니다. 다행인 것은 깊이 검사를 설정하는게 쉽다는 거죠. 아래의 main 함수처럼 설정해주면 됩니다.

void main(int argc, char **argv)
{
glutInit(&argc, argv);

//이중버퍼를 설정한다.
glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);

glutInitWindowPosition(100,100);
glutInitWindowSize(320,320);
glutCreateWindow("3D Tech- GLUT Tutorial");
glutDisplayFunc(renderScene);

//휴면(idle)시간에 호출될 함수를 설정한다.
glutIdleFunc(renderScene);

glutReshapeFunc(changeSize);

//깊이 검사가 가능하도록 설정한다.
glEnable(GL_DEPTH_TEST);
glutMainLoop();
}

이제는 렌더링 함수를 고쳐봅시다. 우선 각을 나타내는 float 형의 변수를 선언하고 0.0 으로 설정합니다. 그런 다음에 renderScene 함수에서 필요에 따라 값을 더해줍니다.

float angle=0.0;

void renderScene(void)
{
// 깊이 버퍼를 깨끗이 지워야합니다.
// 깊이 버퍼가 꽉차있으면 아무것도 그려지지 않기 때문이죠.
// 아래는 깊이버퍼를 지워주는 코드입니다.
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

// 이전에 설정한 것들을 저장합니다.
// 여기서는 카메라의 설정을 저장합니다.
glPushMatrix();

// angle 변수값을 회전량으로 y 축(0,
  1, 0)을 중심으로 회전변환을 수행합니다.
glRotatef(angle,0.0,1.0,0.0);
glBegin(GL_TRIANGLES);
glVertex3f(-0.5,-0.5,0.0);
glVertex3f(0.5,0.0,0.0);
glVertex3f(0.0,0.5,0.0);
glEnd();

// 모델링 변환을 마치고나서
// 카메라설정을 원래대로 돌려놓습니다.
glPopMatrix();

// 위에서 그린것을 보여주기 위해서 버퍼를 교체합니다.
glutSwapBuffers();

// 마지막으로 angle 변수에 1 을 더해줍니다.
angle++;
}

glutSwapBuffers 함수는 후면버퍼에 앞서 그린 것들을 보여주기 위해서 후면버퍼와 전면버퍼를 교환해줍니다. 함수원형은 아래와 같아요.:

void glutSwapBuffers();

자 이제~ 회전하는 삼각형을 그리는 VC 프로젝트 파일(glut2.zip)을 받아서 실행해보세요. 정말 멋지지 않나요? 그렇지 않다구요? 이 강좌는 GLUT 를 배우는 것에 초점을 맞췄기에 화려한 예제가 없습니다. 그러니 불평하지 말아주세요 :)

번역 : 김성철 ( paranwave@korea.com )


GLUT 튜토리얼

GLUT Tutorial

키보드입력

by Antonio

원문 : http://www.lighthouse3d.com/opengl/glut/index.php3?5

GLUT 를 사용하면 일반키뿐만 아니라 특수키(F1 키나 화살표키)의 키보드입력을 감지하는 어플리케이션을 만들 수 있습니다. 이번 장에서는 키입력을 감지하는 방법과 GLUT 를 통해서 어떤 정보를 얻을 수 있는지 그리고 얻은 정보를 어떻게 다루는지 알아보겠습니다.

지금쯤이면 다아시겠지만, 어떤 이벤트를 처리하는 과정을 제어하려면 "내가 제공하는 어떤 함수가 그 이베트를 처리할 것이다" 라고 GLUT 에게 알려줘야합니다. 지금까지 우리는 "창을 다시 그릴 필요가 있을땐 이 함수를 사용하면 돼" "시스템이 휴면(Idle) 상태에 있을 때는 이 함수를 사용해" "창 크기가 변할 땐 이 함수를 사용하고~ 알았지~" 라고 GLUT 를 통해서 윈도우즈 운영체제 시스템에 알려준 것입니다.

키보드 이벤트를 처리할 때도 마찬가지로 같은 일을 해줘야합니다. GLUT 를 통해 윈도우 시스템에 "어떤 함수가 키가 입력되었을 때 필요한 처리를 할 것이다" 라고 알려줘야하죠. 이벤트가 발생했을 때 특정 함수가 실행되게끔 만드는 것을 "콜백함수의 등록" 이라고 합니다.

GLUT 는 키입력 키보드 이벤트를 처리하는 콜백함수를 등록하기 위해서 두개의 함수를 제공합니다. 처음 것은 glutKeyboardFunc 함수인데 일반키를 처리하는 함수를 윈도우 시스템에 알려줄 때 사용합니다. 일반키는 문자키, 숫자키 그리고 ASCII 코드로 표현되는 키들을 말합니다. 다음은 이 함수의 설명입니다.

void glutKeyboardFunc(void (*func) (unsigned char key, int x, int y));

인자 설명:
func - 일반키 입력 이벤트를 처리하는 함수의 이름. NULL 을 전달하면 GLUT 는 일반키 입력을 무시합니다.

glutKeyboardFunc 함수에 인자로 사용되는 함수는 세 개의 인자를 가지고 있습니다. 처음 것은 입력되는 키의 ASCII 코드를 나타내고 나머지 두 개의 인자들은 키가 눌렸을 때의 마우스 위치를 나타냅니다. 마우스 위치의 시작점은 창의 클라이언트 영역의 왼쪽-위(left-top) 코너입니다.

키입력을 처리하는 예를 들어보면, ESC(ESCAPE) 키가 눌렸을 때 어플리케이션이 끝나는 함수를 생각할 수 있겠죠. glutMainLoop 함수는 앞에서도 말했듯이 무한루프를 제공합니다. 즉, 절대 값을 반환하지 않고 계속해서 어플리케이션이 실행된다는 것이죠. 이 루프를 나가는 방법은 오직 exit 시스템 함수를 호출하는 길뿐입니다. 이렇게 하기 위해서 ECS 키가 눌렸을 때 exit 시스템 함수를 호출하도록 함수를 만듭니다. 따라서 ESC 키가 눌리면 어플리케이션은 종료되죠.(stdlib.h 파일을 포함하는 것을 잊지마세요). 다음은 지금까지 말한 것을 코드로 나타낸 것입니다.:

void processNormalKeys(unsigned char key, int x, int y)
{
if (key == 27)
exit(0);
}

glutKeyboardFunc 함수 인자의 모양대로 함수를 만들어 보았습니다. 만약 인자와 다르게 함수를 만들었다면 VC 가 컴파일 에러를 나불댈 거에요. 설마 이런걸 원하는 건 아니겠죠?

자~ 그럼 이제 특수키의 입력을 다뤄보죠. GLUT 는 glutSpecialFunc 함수를 제공하는데 이 함수를 사용해서 특수키 이벤트를 처리하는 함수를 등록할 수 있습니다. 다음은 이 함수의 설명입니다.

void glutSpecialFunc(void (*func) (int key, int x, int y));

Parameters: 인자 설명:
func - 특수키 입력 이벤트를 처리하는 함수의 이름. NULL 을 전달하면 GLUT 는 특수키 입력을 무시합니다.

특수키가 입력 되었을 때 삼각형의 색을 바꾸는 코드를 만들어보겠습니다. F1 키를 누르면 삼각형이 빨갛게 되고 F2 키를 누르면 녹색으로, F3 키를 누르면 파랗게 됩니다.

void processSpecialKeys(int key, int x, int y)
{
switch(key)
{
case GLUT_KEY_F1 :
red = 1.0;
green = 0.0;
blue = 0.0; break;
case GLUT_KEY_F2 :
red = 0.0;
green = 1.0;
blue = 0.0; break;
case GLUT_KEY_F3 :
red = 0.0;
green = 0.0;
blue = 1.0; break;
}
}

GLUT_KEY_* 는 glut.h 파일에 정의되어 있는 상수입니다. 정의되어 있는 모든 상수를 살펴볼까요?

GLUT_KEY_F1 | F1 키
GLUT_KEY_F2 | F2 키
GLUT_KEY_F3 | F3 키
GLUT_KEY_F4 | F4 키
GLUT_KEY_F5 | F5 키
GLUT_KEY_F6 | F6 키
GLUT_KEY_F7 | F7 키
GLUT_KEY_F8 | F8 키
GLUT_KEY_F9 | F9 키
GLUT_KEY_F10 | F10 키
GLUT_KEY_F11 | F11 키
GLUT_KEY_F12 | F12 키
GLUT_KEY_LEFT | 왼쪽 방향키(←)
GLUT_KEY_RIGHT | 오른쪽 방향키(→)
GLUT_KEY_UP | 위쪽 방향키(↑)
GLUT_KEY_DOWN | 아래쪽 방향키(↓)
GLUT_KEY_PAGE_UP | Page Up 키
GLUT_KEY_PAGE_DOWN | Page Down 키
GLUT_KEY_HOME | Home 키
GLUT_KEY_END | End 키
GLUT_KEY_INSERT | Insert 키

위에서 정의한 processSpecialKeys 코드를 컴파일하려면 red, green, blue 변수를 코드 시작부분에 선언해야합니다. 그리고 위의 코드대로 효과를 나타낼려면 렌더링을 수행하는 renderScene 함수를 고쳐야하겠죠.

...
// 모든 변수를 1.0 으로 초기화하는데
// 이렇게 하면 삼각형이 흰색으로 그려집니다.
float red=1.0, blue=1.0, green=1.0;

void renderScene(void)
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
glPushMatrix();
glRotatef(angle,0.0,1.0,0.0);
// 여기서 실제 색을 설정합니다.
// glColor 는 앞으로 그려질 물체들의 색을 결정합니다.
glColor3f(red,green,blue);

glBegin(GL_TRIANGLES);
glVertex3f(-0.5,-0.5,0.0);
glVertex3f(0.5,0.0,0.0);
glVertex3f(0.0,0.5,0.0);
glEnd();
glPopMatrix();

angle++;
glutSwapBuffers();
}

이제는 GLUT 에 키보드 이벤트를 처리하는 함수가 무엇인지를 말해줘야합니다. 즉, GLUT 의 glutKeyboardFunc 와 glutSpecialFunc 함수를 사용할 때란 거죠. 이 함수들은 어디서나 호출할 수 있습니다. 이 말은 키보드이벤트 함수가 아무때나 바뀔 수 있다는 얘기입니다. 하지만 대부분 이렇게 하지 않습니다. 그래서 우리는 main 함수에서 이 함수들을 호출하겠습니다. 아래에 main 함수가 있습니다. 키보드 처리부분이 추가되었네요.

void main(int argc, char **argv)
{
glutInit(&argc, argv);

// 이중버퍼를 설정합니다.
glutInitDisplayMode(GLUT_DEPTH|GLUT_DOUBLE|GLUT_RGBA);

glutInitWindowPosition(100,100);
glutInitWindowSize(320,320);
glutCreateWindow("3D Tech - GLUT Tutorial");
glutDisplayFunc(renderScene);
glutIdleFunc(renderScene);
glutReshapeFunc(changeSize);

// 새로 추가된 코드입니다.
glutKeyboardFunc(processNormalKeys);
glutSpecialFunc(processSpecialKeys);

// 깊이 검사를 수행합니다.
glEnable(GL_DEPTH_TEST);
glutMainLoop();
}

Visual C++ 로 만든 프로젝트파일을 받아서 실행해 보세요. ( glut3.zip )

CTRL, ALT 그리고 SHIFT 검사

가끔 변경키(modifier key)가 눌렸는지 알고 싶을 때가 있습니다. 변경키는 CTRL, ALT 또는 SHIFT 키를 말합니다. GLUT 는 변경키가 눌렸는지를 알려주는 함수를 제공합니다. 이 함수는 키보드나 마우스 입력 이벤트처리 함수 내에서 호출되어야합니다. 다음은 이 함수의 설명입니다.

int glutGetModifiers(void);

이 함수의 반환값은 아래 세개의 정의된 상수중 하나이거나 OR 비트 연산되어 합쳐진 값입니다. ( glut.h 에 정의되어 있습니다. )

  • GLUT_ACTIVE_SHIFT - SHIFT 키나 Caps Lock 키를 눌렀을 때 반환값에 설정됩니다. 만약 두개의 키가 같이 눌려 있다면 반환값에 이 상수가 설정되지 않습니다.
  • GLUT_ACTIVE_CTRL - CTRL 키를 눌렀을 때 반환값에 설정됩니다.
  • GLUT_ACTIVE_ALT - ALT 키를 눌렀을 때 반환값에 설정됩니다.

윈도우 시스템이 가끔 변경키를 가로챌때가 있는데, 이를 해결할 콜백함수가 없기 때문에 조심해야합니다. 그래서 변경키를 어떻게 다뤄야하는지 알아보기위해서 processNormalKeys 함수를 약간 고쳐봅시다. 여기서는 r 키를 누르면 red 변수가 0.0 으로 설정되고 ALT + r 을 누르면 1.0 으로 설정된다고 가정합니다. 다음 코드는 약간의 트릭입니다.:

void processNormalKeys(unsigned char key, int x, int y)
{
if (key == 27)
exit(0);
else if (key=='r')
{
int mod = glutGetModifiers();
if (mod == GLUT_ACTIVE_ALT)
red = 0.0;
else
red = 1.0;
}
}

위의 코드를 가지고 실행을 한다면, 'R' 키를 눌렀을 때는 아무런 일도 일어나지 않습니다. 그건 눌린 키가 'R' 이지 'r' 이 아니기 때문이죠. 그래서 소문자를 대문자로 바꾸기 위해서 또는 ASCII 코드로 정의된 어떤 것을 나타내기 위해서 SHIFT 변경키를 사용하면 안됩니다. SHIFT 키는 F1 과 같은 시스템키와만 사용할 수 있습니다.(위의 코드를 GLUT_ACTIVE_SHIFT 로 해놓고 실행하면 원하는 결과가 실행되지 않는다.)

void processNormalKeys(unsigned char key, int x, int y)
{
if (key == 27)
exit(0);
// 'R' 을 감지하는 것이라면 잘못된 코드입니다.
else if (key=='r')
{
int mod = glutGetModifiers();
if (mod == GLUT_ACTIVE_SHIFT)
...
}
}

마지막으로 알아볼 것은 CTRL+ALT+F1 을 어떻게 감지하느냐는 것입니다. 여기서는 두개의 변경키를 동시에 감지해야합니다. 이걸 위해서는 원하는 변경키 상수를 OR 비트 연산을 사용해야합니다. 다음 코드는 red 변수를 CTRL+ALT+F1 이 눌렸을 때 변경되도록 바꾼 것입니다.

void processSpecialKeys(int key, int x, int y)
{
int mod;
switch(key)
{
case GLUT_KEY_F1 :
mod = glutGetModifiers();
if (mod == (GLUT_ACTIVE_CTRL|GLUT_ACTIVE_ALT))
{
red = 1.0; green =
  0.0; blue = 0.0;
}
break;
case GLUT_KEY_F2 :
red = 0.0;
green = 1.0;
blue = 0.0; break;
case GLUT_KEY_F3 :
red = 0.0;
green = 0.0;
blue = 1.0; break;
}
}

이제 키보드와 마우스 이벤트 함수를 콜백함수로 등록해주세요. 이벤트 처리하기 위한 콜백함수를 등록하면 어플리케이션의 성능이 조금 떨어집니다. 왜냐하면 시스템이 항상 이벤트를 감시하고 있기 때문이죠. 그래서 키보드나 마우스 이벤트가 필요없을 때에는 콜백함수를 등록하지 말아주세요 :)

번역 : 김성철 ( paranwave@korea.com )


GLUT 튜토리얼

GLUT Tutorial

키입력 예제 : 세상을 둘러 보아요~ :)

by Antonio

원문 : http://www.lighthouse3d.com/opengl/glut/index.php3?6

키보드 입력을 다루는 재미있는 예제를 만들어봅시다. 이번 장에서는 눈사람이 서있는 작은 세상을 만드는 어플리케이션을 제작해보겠습니다. 그리고 방향키를 이용해서 그 세상속에서 카메라를 이리저리 움직여보는 것이죠! 왼쪽과 오른쪽키를 누르면 카메라가 Y 축을 중심으로 회전하게 만들고(XZ 평면상의 회전) 위쪽과 아래쪽키를 누르면 카메라가 현재 방향에서 앞뒤로 움직이게끔 만들어봅시다.

주석이 달린 코드가 아래에 쭈~욱 있습니다. 서둘지 말구요 우선 초기화부분부터 살펴보지요.

#include <math.h>
#include <gl\glut.h>
#include <gl\gl.h>
#include <gl\glu.h>
#include <stdlib.h>

static float angle=0.0,ratio;
static float x=0.0f,y=1.75f,z=5.0f;
static float lx=0.0f,ly=0.0f,lz=-1.0f;
static GLint snowman_display_list;

회전각을 계산할 때 수학관련 함수들이 필요하기 때문에 math.h 파일을 포함했습니다. 위에 선언된 변수를 보면 의미가 확실하지만 그래도 간단하게 설명해보겠습니다.:

  • angle: Y 축을 중심으로 회전할 때의 회전각입니다. 이 변수로 카메라를 회전할 수 있는거죠.
  • x,y,z: 카메라의 위치입니다.
  • lx,ly,lz: 시선의 방향을 나타내는 벡터입니다.
  • ratio: 창의 너비/높이의 비율입니다.
  • snowman_display_list: 한개의 눈사람을 그리는 디스플레이리스트의 인덱스입니다.

잠깐만! : 여러분들이 디스플레이리스트에 대해서 모른다면 무시하고 넘어가도 됩니다. 하지만 디스플레이리스트에 대해서 우선 알아야겠다 싶으면 다음 튜토리얼을 읽어보세요. :)

다음은 앞에서 살펴보았던 창의 크기변경을 다루는 함수입니다. 앞에서 만든 함수와 차이점이 있다면 gluLookAt 함수의 인자들이 상수값이 아니라 변수를 사용한다는 것입니다. 이 함수 때문에 앞장으로 가지 마세요. 간략하게 설명해드릴테니까요. gluLookAt 함수는 카메라 위치와 원점을 설정하는데 쉽고 직관적인 방법을 제공합니다. 이 함수는 세개의 그룹으로 나뉘는 인자들을 받습니다. 그리고 각 그룹의 인자들은 세개의 float 형 변수값으로 구성되어 있습니다. 처음 세개의 값들은 카메라의 위치를 나타내고 두번째의 값들은 우리가(카메라)가 보고있는 곳을 나타냅니다. 이 값은 우리의 시선 속 아무 위치나 될 수 있습니다. 마지막 값들은 카메라의 위쪽을 가리키는 벡터를 나타냅니다. 이 값을 (0.0, 1.0, 0.0) 으로 설정하면 카메라가 뒤집어지지 않은 것입니다. 카메라를 뒤집고 싶다면 이 값들을 조작하면 됩니다. 예를 들어서 세상을 뒤집어서 보고 싶다면 (0.0, -1.0, 0.0) 으로 이 값을 설정하면 됩니다.

앞에서도 말했듯이 x, y 그리고 z 는 카메라의 위치를 나타냅니다. 두번째 인자들의 값들은 시점을 나타내는데 시선을 나타내는 벡터에 카메라의 위치를 더해서 계산되는 값입니다.

  • 시점 = 시선벡터 + 카메라의 위치 ( Look At Point = Line Of Sight + Camera Position )

void changeSize(int w, int h)
{
// 창이 너무 작아졌을때 0 으로 나뉘는 것을
  방지합니다.
if(h == 0)
h = 1;

ratio = 1.0f * w / h;
// 좌표계를 수정하기 전에 초기화합니다.
glMatrixMode(GL_PROJECTION);
glLoadIdentity();

// 뷰포트를 창 전체크기로 설정합니다.
glViewport(0, 0, w, h);

// 절단 공간을 설정합니다.
gluPerspective(45,ratio,1,1000);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(x, y, z,
x +
  lx,y + ly,z + lz,
0.0f,1.0f,0.0f);
}

다은은 디스플레이리스트를 정의하고 그림을 그리는 렌더링함수를 살펴봅시다. 이 부분은 건너뛰고 다음 부분을 읽어도 됩니다.

void drawSnowMan()
{
glColor3f(1.0f, 1.0f, 1.0f);

// 몸을 그립니다.
glTranslatef(0.0f ,0.75f, 0.0f);
glutSolidSphere(0.75f,20,20);

// 머리를 그립니다.
glTranslatef(0.0f, 1.0f, 0.0f);
glutSolidSphere(0.25f,20,20);

// 두 눈을 그립니다.
glPushMatrix();
glColor3f(0.0f,0.0f,0.0f);
glTranslatef(0.05f, 0.10f, 0.18f);
glutSolidSphere(0.05f,10,10);
glTranslatef(-0.1f, 0.0f, 0.0f);
glutSolidSphere(0.05f,10,10);
glPopMatrix();

// 코를 그립니다.
glColor3f(1.0f, 0.5f , 0.5f);
glRotatef(0.0f,1.0f, 0.0f, 0.0f);
glutSolidCone(0.08f,0.5f,10,2);
}

GLuint createDL()
{
GLuint snowManDL;

// 디스플레이리스트를 생성하고 id 를 반환합니다.
snowManDL = glGenLists(1);

// 디스플레이리스트를 시작합니다.
glNewList(snowManDL,GL_COMPILE);

// 렌더링을 수행하는 함수를 호출합니다.
drawSnowMan();

// 디스플레이리스트를 끝마칩니다.
glEndList();

return(snowManDL);
}

void initScene()
{
glEnable(GL_DEPTH_TEST);
snowman_display_list = createDL();
}

void renderScene(void)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

// 지형을 그립니다.
glColor3f(0.9f, 0.9f, 0.9f);
glBegin(GL_QUADS);
glVertex3f(-100.0f, 0.0f, -100.0f);
glVertex3f(-100.0f, 0.0f, 100.0f);
glVertex3f( 100.0f, 0.0f, 100.0f);
glVertex3f( 100.0f, 0.0f, -100.0f);
glEnd();

// 36개의 눈사람을 그립니다.
for(int i = -3; i < 3; i++)
for(int j=-3; j < 3; j++)
{
glPushMatrix();
glTranslatef(i*10.0,0,j
  * 10.0);
glCallList(snowman_display_list);;
glPopMatrix();
}
glutSwapBuffers();
}

자 이제 특수키 이벤트를 다루는 함수를 만들어 봅시다. 왼쪽키와 오른쪽키는 카메라를 회전하는데 사용할 것입니다. 즉 시선을 정의하는 벡터를 변경하는 것이죠. 위쪽키와 아래쪽키는 현재 시선방향의 앞뒤로 움직이는데 사용할 것입니다.

void inputKey(int key, int x, int y)
{
switch (key)
{
case GLUT_KEY_LEFT :
angle -= 0.01f;
orientMe(angle);
break;
case GLUT_KEY_RIGHT :
angle +=0.01f;
orientMe(angle);
break;
case GLUT_KEY_UP :
moveMeFlat(1);
break;
case GLUT_KEY_DOWN :
moveMeFlat(-1);
break;
}
}

왼쪽 또는 오른쪽 키를 누르면 angle 변수의 값이 알맞게 설정되고 orientMe 함수가 새로운 값과 함께 호출됩니다. 이 함수는 카메라를 알맞게 회전시킵니다. moveMeFlat 함수는 카메라를 XZ 평면상에서 시선의 방향으로 움직이게 합니다. 인자는 카메라가 움직일 방향을 나타냅니다.

다음에 나오는 함수는 카메라를 회전시킵니다. 이 함수는 각도를 받아서 시선 벡터의 새로운 x 와 z 값을 계산합니다. 이 예제에서는 카메라가 XZ 평면상에서만 움직이기 때문에 시선벡터의 y 좌표는 수정할 필요가 없습니다. 새로운 lx 와 lz 는 XZ 평면상의 단위원으로 사상됩니다. 그러므로 각 ang 에 대해서 lx 와 lz 값은 다음처럼 구합니다.:

  • lx = sin(ang)
  • lz = cos(ang)

orientMe 함수를 살펴보면, 극좌표(ang, 1)를 유클리드 좌표로 변환하고 나중에 카메라의 새로운 방향을 설정합니다. 투영 행렬을 알맞게 설정하기 위해서 우선 이전에 적용된 변환 효과를 없애야 하기 때문에 투영 행렬을 단위 행렬로 초기화합니다. gluPerspective 함수로 원근 투시값을 설정하고 gluLookAt 함수로는 카메라의 방향을 설정합니다. 카메라의 위치가 아직 같기 때문에 카메라는 움직이지 않습니다.

void orientMe(float ang)
{
lx = sin(ang);
lz = -cos(ang);
glLoadIdentity();
gluLookAt(x, y, z,
x +
  lx,y + ly,z + lz,
0.0f,1.0f,0.0f);
}

다음 함수는 카메라를 움직이는데 사용합니다. 카메라를 시선방향으로 움직일 것이기 때문에 카메라는 항상 시선벡터상에 있습니다. 이렇게 하기 위해서 현재 위치에 시선 벡터를 더해줘야합니다. 다음과 같이 새로운 x 와 z 를 구합니다.:

  • x = x + direction(lx)*fraction
  • z = z + direction*(lz)*fraction

방향(direction)값이 -1 또는 1 이면 앞 뒤로 움직이는 것입니다. fraction 은 속도값으로 사용할 수 있습니다. lx 와 lz 가 단위 벡터(위에서 말했던 단위원 위의 한 점)이므로 fraction 이 상수이면 속도는 항상 같을 것입니다. fraction 을 증가시키면 카메라를 빠르게 움직일 수 있겠죠. 즉 각 프레임마다 더 멀리 움직이는 것입니다.

다음 단계는 orientMe 함수와 같습니다.

void moveMeFlat(int direction)
{
x = x + direction*(lx)*0.1;
z = z + direction*(lz)*0.1;
glLoadIdentity();
gluLookAt(x, y, z,
x +
  lx,y + ly,z + lz,
0.0f,1.0f,0.0f);
}

다음은 GLUT 를 사용하는 main 함수의 코드입니다.

int main(int argc, char **argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);
glutInitWindowPosition(100,100);
glutInitWindowSize(640,360);
glutCreateWindow("SnowMen from 3D-Tech");

initScene();

glutSpecialFunc(inputKey);

glutDisplayFunc(renderScene);
glutIdleFunc(renderScene);

glutReshapeFunc(changeSize);

glutMainLoop();

return(0);
}

VC 6.0 프로젝트 파일( glut51.zip ) 을 받아서 카메라 설정 함수들을 가지고 놀아보세요 :)

번역 : 김성철 ( paranwave@korea.com )


GLUT 튜토리얼

GLUT Tutorial

키입력 예제 : 세상을 둘러 보아요~ 2 탄:)

by Antonio

원문 : http://www.lighthouse3d.com/opengl/glut/index.php3?8

이번 장에서는 앞장에서 만든 예제에 키입력 고급 기법을 사용해서 수정해보겠습니다. 새로 추가한 코드에는 주석을 달았습니다.

초기화 부분에 deltaAngle 과 deltaMove 라는 두 개의 변수를 추가했습니다. 이 변수들은 각각 카메라의 회전과 이동을 제어합니다. 0 이 아닐 때에는 카메라가 어떤 행동을 취하고 0 일 때에는 카메라가 고정되어 있습니다. 이 두 변수는 카메라의 초기 상태를 고정 상태로 만들기 위해서 0 으로 초기화합니다.

#include <math.h>
#include <gl\glut.h>
#include <gl\gl.h>
#include <gl\glu.h>

float angle=0.0, deltaAngle = 0.0;
float ratio;
float x=0.0f,y=1.75f,z=5.0f;
float lx=0.0f,ly=0.0f,lz=-1.0f;
GLint snowman_display_list;
int deltaMove = 0;

void changeSize(int w, int h)
{
// 창이 너무 작아졌을때 0 으로 나뉘는 것을
  방지합니다.
if(h == 0)
h = 1;

ratio = 1.0f * w / h;
// 좌표계를 수정하기 전에 초기화합니다.
glMatrixMode(GL_PROJECTION);
glLoadIdentity();

// 뷰포트를 창 전체크기로 설정합니다.
glViewport(0, 0, w, h);

// 절단 공간을 설정합니다.
gluPerspective(45,ratio,1,1000);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(x, y, z,
x +
  lx,y + ly,z + lz,
0.0f,1.0f,0.0f);
}

void drawSnowMan()
{
glColor3f(1.0f, 1.0f, 1.0f);

// 몸을 그립니다.
glTranslatef(0.0f ,0.75f, 0.0f);
glutSolidSphere(0.75f,20,20);

// 머리를 그립니다.
glTranslatef(0.0f, 1.0f, 0.0f);
glutSolidSphere(0.25f,20,20);

// 두 눈을 그립니다.
glPushMatrix();
glColor3f(0.0f,0.0f,0.0f);
glTranslatef(0.05f, 0.10f, 0.18f);
glutSolidSphere(0.05f,10,10);
glTranslatef(-0.1f, 0.0f, 0.0f);
glutSolidSphere(0.05f,10,10);
glPopMatrix();

// 코를 그립니다.
glColor3f(1.0f, 0.5f , 0.5f);
glRotatef(0.0f,1.0f, 0.0f, 0.0f);
glutSolidCone(0.08f,0.5f,10,2);
}

GLuint createDL()
{
GLuint snowManDL;

// 디스플레이리스트를 생성하고 id 를 반환합니다.
snowManDL = glGenLists(1);

// 디스플레이리스트를 시작합니다.
glNewList(snowManDL,GL_COMPILE);

// 렌더링을 수행하는 함수를 호출합니다.
drawSnowMan();

// 디스플레이리스트를 끝마칩니다.
glEndList();

return(snowManDL);
}

void initScene()
{
glEnable(GL_DEPTH_TEST);
snowman_display_list = createDL();
}

void orientMe(float ang)
{
lx = sin(ang);
lz = -cos(ang);
glLoadIdentity();
gluLookAt(x, y, z,
x +
  lx,y + ly,z + lz,
0.0f,1.0f,0.0f);
}

void moveMeFlat(int i)
{
x = x + i*(lx)*0.1;
z = z + i*(lz)*0.1;
glLoadIdentity();
gluLookAt(x, y, z,
x +
  lx,y + ly,z + lz,
0.0f,1.0f,0.0f);
}

아래 함수에 추가된 코드가 있습니다. 이 함수는 각각의 카메라 액션을 뜻하는 두 개의 변수가 0 이 아닌지 검사하고 0 이 아니라면 카메라의 필요한 동작에 적절한 함수를 호출합니다.

void renderScene(void)
{
if (deltaMove)
moveMeFlat(deltaMove);
if (deltaAngle)
{
angle += deltaAngle;
orientMe(angle);
}

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

// 지형을 그립니다.
glColor3f(0.9f, 0.9f, 0.9f);
glBegin(GL_QUADS);
glVertex3f(-100.0f, 0.0f, -100.0f);
glVertex3f(-100.0f, 0.0f, 100.0f);
glVertex3f( 100.0f, 0.0f, 100.0f);
glVertex3f( 100.0f, 0.0f, -100.0f);
glEnd();

// 36개의 눈사람을 그립니다.
for(int i = -3; i < 3; i++)
for(int j=-3; j < 3; j++)
{
glPushMatrix();
glTranslatef(i*10.0,0,j
  * 10.0);
glCallList(snowman_display_list);;
glPopMatrix();
}
glutSwapBuffers();
}

다음에 나오는 함수들은 각각 특수키의 콜백함수로 일반키가 놓였을 때 호출되는 콜백함수로 등록합니다.

void pressKey(int key, int x, int y)
{
switch (key)
{
case GLUT_KEY_LEFT :
deltaAngle = -0.01f;
break;
case GLUT_KEY_RIGHT :
deltaAngle = 0.01f;
break;
case GLUT_KEY_UP :
deltaMove = 1;
break;
case GLUT_KEY_DOWN :
deltaMove = -1;
break;
}
}

void releaseKey(int key, int x, int y)
{
switch (key)
{
case GLUT_KEY_LEFT :
case GLUT_KEY_RIGHT :
deltaAngle = 0.0f;
break;
case GLUT_KEY_UP :
case GLUT_KEY_DOWN :
deltaMove = 0;
break;
}
}

main 함수에는 세 줄의 코드를 추가했습니다.:

반복되는 키입력을 정지하기 위해서 glutIgnoreKeyRepeat 함수를 0 이 아닌 값을 인자로 주어 호출했고 그 다음에 glutSpecialUpFunc 와 glutKeyboardUpFunc 함수로 콜백함수를 등록했습니다.

int main(int argc, char **argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);
glutInitWindowPosition(100,100);
glutInitWindowSize(640,360);
glutCreateWindow("SnowMen from 3D-Tech");

initScene();

glutIgnoreKeyRepeat(1);
glutKeyboardFunc(processNormalKeys);
glutSpecialFunc(pressKey);
glutSpecialUpFunc(releaseKey);

glutDisplayFunc(renderScene);
glutIdleFunc(renderScene);

glutReshapeFunc(changeSize);

glutMainLoop();

return(0);
}

VC 6.0 프로젝트 파일( glut53.zip ) 을 받아서 카메라 설정 함수들을 카메라 설정 함수들을 가지고 놀아보세요 :)

번역 : 김성철 ( paranwave@korea.com )


GLUT 튜토리얼

GLUT Tutorial

마우스처리

by Antonio

원문 : http://www.lighthouse3d.com/opengl/glut/index.php3?9

앞 장에서 우리는 GLUT 의 키보드 입력처리 기능을 사용해서 OpenGL 어플리케이션과 대화하는(상호작용)하는 방법을 배웠습니다. 이제 키보드 외에 입력도구로 많이 사용하는 마우스에 대해서 알아봅시다. GLUT 의 마우스 인터페이스는 마우스를 사용하는데 필요한 기능들을 많이 가지고 있습니다. 이 마우스 인터페이스를 사용하면 마우스 버튼의 클릭여부와 마우스의 움직임을 감지할 수 있습니다.

마우스 클릭 감지

키보드 입력처리에서 본 것처럼 GLUT 는 마우스 관련 이벤트를 처리하는데 콜백함수를 사용합니다. 물론 콜백함수를 등록해주는 함수가 있습니다. 마우스의 클릭 처리를 위한 콜백함수는 glutMouseFunc 함수로 등록하며 대부분 어플리케이션의 초기화부분에서 호출합니다. 다음은 이 함수의 설명입니다.:

void glutMouseFunc(void (*func)(int button, int state, int x, int y));

인자 설명:
func - 마우스클릭 이벤트를 처리하는 함수의 이름입니다.

위의 glutMouseFunc 함수를 보면 알 수 있듯이, 마우스의 클릭 이벤트를 처리하는 함수는 네 개의 인자를 갖습니다. 첫번째 인자는 어떤 버튼이 눌렸는지 혹은 놓여졌는지에 관한 것인데 다음 세가지의 값중에 한가지가 될 수 있습니다. :

  • GLUT_LEFT_BUTTON
  • GLUT_MIDDLE_BUTTON
  • GLUT_RIGHT_BUTTON

두 번째 인자는 콜백함수가 호출됐을 때 마우스 버튼의 상태를 말해줍니다. 즉, 이 값을 통해서 버튼이 눌렸는지 아니면 놓여져있는지를 알 수 있는 것이죠. 다음 값 중에 한가지가 될 수 있습니다. :

  • GLUT_DOWN
  • GLUT_UP

마우스 버튼의 상태가 GLUT_DOWN 일 때 콜백함수가 호출되면 어플리케이션은 "다음 마우스 버튼의 상태는 GLUT_UP 이 되겠지" 라고 가정합니다. 이 것은 마우스의 움직임이 창 밖에서 일어나고 있어도 마찬가지입니다. 그러나 어플리케이션이 glutMouseFunc 의 인자에 NULL 값을 주어 다시 호출하면 GLUT 가 마우스 버튼의 상태를 변경하는 것을 막을 수 있습니다.

나머지 두 개의 인자는 마우스의 좌표인데 OpenGL 창의 클라이언트 영역에 상대적인 좌표값입니다.

마우스 모션(motion, 움직임) 감지

GLUT 로 마우스 모션을 감지할 수 있습니다. GLUT 가 처리하는 마우스 모션에는 두가지 종류가 있는데 능동모션과 수동모션입니다. 능동모션은 마우스 버튼을 누른채 마우스를 움직이는 것이고 수동모션은 마우스 버튼을 누르지 않은채 마우스를 움직이는 것입니다. 어플리케이션이 마우스 모션을 계속해서 추적하면 모션 이벤트는 마우스가 움직일동안 매 프레임마다 발생됩니다.

다른 이벤트처리와 마찬가지로 마우스 모션 이벤트를 처리하려면 관련 콜백함수를 등록해야합니다. GLUT 는 두 개의 함수를 제공하는데 하나는 수동모션을 처리하는 함수이고 다른 하나는 능동모션을 처리하는 함수입니다.

다음은 이 함수들의 설명입니다. :

void glutMotionFunc(void (*func) (int x,int y));
void glutPassiveMotionFunc(void (*func) (int x, int y));

인자 설명:
func - 모션처리를 위한 함수의 이름입니다.

모션을 처리하는 함수의 인자인 x, y 는 마우스의 좌표인데 OpenGL 창의 클라이언트 영역에 상대적인 좌표값입니다.

마우스가 OpenGL 창에 내부에 있는지 아니면 외부에 있는지 판단하기

GLUT 를 사용하면 마우스가 OpenGL 창의 내부 있는지 아니면 외부에 있는지를 쉽게 알 수 있습니다. 두 경우를 처리하는 콜백함수를 등록할 때 glutEntryFunc 함수를 사용합니다. 다음은 이 함수의 설명입니다.:

void glutEntryfunc(void (*func)(int state));

인자 설명:
func - 이벤트를 처리하는 함수의 이름입니다.

이벤트를 처리하는 함수의 state 인자로 마우스가 OpenGL 창의 영역에 들어왔는지 아닌지를 알 수 있습니다. 이 인자에 사용할 수 있는 값은 두 개의 상수인데 GLUT 에 다음과 같이 정의되어 있습니다.:

  • GLUT_LEFT
  • GLUT_ENTERED

주의 : 마이크로소프트 윈도우즈에서 이 함수는 정확하게 동작하지 않는데 그 이유는 윈도우즈 운영체제가 마우스를 클릭했을 때 포커스를 변경하기 때문입니다. 마이크로소프트의 다른 도구들를 사용해서 이 설정을 임의대로 바꿀 수는 있지만 다른 사람들이 사용하는 마이크로소프트 윈도우즈는 대부분 표준 설정을 가지고 있을 것입니다. 그래서 마이크로소프트 윈도우즈를 사용한다면 마우스가 창의 내/외부에 있는지 알기 위해서 이 함수를 사용하지 않는게 나을듯 싶습니다.

모든걸 적용해서 예제를 만들어 볼까요?

제일 처음에 해야할 것은 마우스 이벤트 처리 함수들을 등록하는 것이므로 필요한 모든 콜백함수들를 등록하기 위해서 main 함수를 고쳐봅시다. 예제를 만들면서 놓치기 쉬운 것들은 자세하게 설명하고 아직 자세하게 배우지 않은 부분은 설명하지 않겠습니다.

void main(int argc, char **argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);
glutInitWindowPosition(100,100);
glutInitWindowSize(320,320);
glutCreateWindow("SnowMen");
glutDisplayFunc(renderScene);
glutIdleFunc(renderScene);
glutReshapeFunc(changeSize);

// 마우스 처리 콜백함수들을 등록합니다.
glutMouseFunc(processMouse);
glutMotionFunc(processMouseActiveMotion);
glutPassiveMotionFunc(processMousePassiveMotion);
glutEntryFunc(processMouseEntry);

glutMainLoop();
}

자~ 이제 좀 즐겨볼까요! :) 위에서 등록한 콜백함수들을 차례차례 정의해봅시다. 마우스 버튼을 누르고 ALT 키도 같이 눌렀을 때 삼각형의 색깔이 바뀌겠끔 만드는데, 이때 왼쪽 버튼을 누르면 삼각형이 빨간색으로, 가운데 버튼을 누르면 녹색으로, 왼쪽 버튼을 누르면 파란색으로 변하게 합니다. 다음은 이 일을 처리하는 processMouse 함수의 코드입니다. :

void processMouse(int button, int state, int x, int y)
{
specialKey = glutGetModifiers();

// 마우스 버튼과 ALT 키가 같이 눌리면~
if ((state == GLUT_DOWN) && (specialKey == GLUT_ACTIVE_ALT))
{
//
  왼쪽 버튼을 눌렀으면 삼각형을 빨간색으로 설정합니다.
if (button == GLUT_LEFT_BUTTON)
{
red
  = 1.0; green = 0.0; blue = 0.0;
}
//
  가운데 버튼을 눌렀으면 삼각형을 녹색으로 설정합니다.
else if (button == GLUT_MIDDLE_BUTTON)
{
red
  = 0.0; green = 1.0; blue = 0.0;
}
//
  오른쪽 버튼을 눌렀으면 삼각형을 파란색으로 설정합니다.
else
{
red
  = 0.0; green = 0.0; blue = 1.0;
}
}
}

이제는 미묘한 색을 선택하는 함수를 만들어봅시다. ALT 키는 누르지 않고 마우스 버튼만 눌렀을 때 파란색은 0.0 으로 설정하고 빨간색과 녹색은 마우스 위치(OpenGL 창의 클라이언트 영역 내)에 따라 다르게 설정합니다. 아래에 이 함수의 코드가 있습니다. :

void processMouseActiveMotion(int x, int y)
{
// ALT 키는 위 함수에서 검사합니다.
if (specialKey != GLUT_ACTIVE_ALT)
{
//
  마우스가 창의 내부에 있을 때
// 마우스 위치에 따라 red 변수를 설정합니다.
if (x < 0)
red
  = 0.0;
else if (x > width)
red
  = 1.0;
else
red
  = ((float) x)/height;

//
  마우스가 창의 내부에 있을 때
// 마우스 위치에 따라 green 변수를 설정합니다.
if (y < 0)
green
  = 0.0;
else if (y > width)
green
  = 1.0;
else
green
  = ((float) y)/height;

//
  blue 변수(파란색)은 0.0 으로 설정해서 파란색 요소를 제거합니다.
blue = 0.0;
}
}

이제 수동모션을 처리할 때군요. SHIFT 키가 눌렸을 때 마우스 위치에 따라서 X 축 중심으로 회전하게 만들어봅시다. 이렇게 하기 위해서 renderScene 함수를 고쳐야합니다. 다음은 새로운 renderScene 함수입니다. :

float angleX = 0.0;
...
void renderScene(void)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glPushMatrix();
glRotatef(angle,0.0,1.0,0.0);

// X 축
  회전을 위해 새로 추가한 코드입니다.
glRotatef(angleX,1.0,0.0,0.0);

glColor3f(red,green,blue);

glBegin(GL_TRIANGLES);
glVertex3f(-0.5,-0.5,0.0);
glVertex3f(0.5,0.0,0.0);
glVertex3f(0.0,0.5,0.0);
glEnd();
glPopMatrix();
angle++;
glutSwapBuffers();
}

수동모션 이벤트를 처리하는 함수는 마우스의 x 좌표에 따라서 angleX 의 값을 설정합니다.

void processMousePassiveMotion(int x, int y)
{
// X 축 중심으로 회전하게 만들려면 SHIFT 키를
  눌러야합니다.
if (specialKey != GLUT_ACTIVE_SHIFT)
{
//
  마우스 위치에 따라서 회전 각도를 설정합니다.
if (x < 0)
angleX
  = 0.0;
else if (x > width)
angleX
  = 180.0;
else
angleX
  = 180.0 * ((float) x)/height;
}
}

마지막으로 마우스가 창을 떠났을 때 에니메이션을 멈추게 만듭니다. 이를 위해서 renderScene 함수를 다시 조금만 수정합니다. :

// angle(각도)변수의 증가값으로 사용하는데 1.0 으로 초기화합니다.
float deltaAngle = 1.0;
...
void renderScene(void)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glPushMatrix();
glRotatef(angle,0.0,1.0,0.0);
glRotatef(angleX,1.0,0.0,0.0);
glColor3f(red,green,blue);

glBegin(GL_TRIANGLES);
glVertex3f(-0.5,-0.5,0.0);
glVertex3f(0.5,0.0,0.0);
glVertex3f(0.0,0.5,0.0);
glEnd();
glPopMatrix();
// 새로운 코드입니다.
// 이전 함수에서는 angle++ 이었습니다.
 angle+=deltaAngle;
glutSwapBuffers();
}

마지막으로 precessMouseEntry 함수입니다. 위에서 말했듯이, 이 함수는 마이크로소프트 윈도우즈에서 정확하게 작동하지 않습니다. 리눅스에서는 잘 동작하죠.

void processMouseEntry(int state)
{
if (state == GLUT_LEFT)
deltaAngle = 0.0;
else
deltaAngle = 1.0;
}

Visual C 프로젝트 파일( glut5.zip )을 받아서 자세하게 살펴보세요 :)

번역 : 김성철 ( paranwave@korea.com )


GLUT 튜토리얼

GLUT Tutorial

팝업메뉴의 소개

by Antonio

원문 : http://www.lighthouse3d.com/opengl/glut/index.php3?10

GLUT 는 팝업메뉴를 쉽게 만들 수 있도록 지원합니다. 윈도우즈 시스템에서 쉽게 볼 수 있는 팝업메뉴의 기능이 부족할 수 지만, GLUT 에서 팝업메뉴는 아주 중요한 역할을 합니다. 어플리케이션에 메뉴를 추가하는 것은 상호작용과 옵션을 선택하는데 키보드보다 더 쉬운 방법을 제공합니다. 왜냐구요? 기능에 해당되는 키를 외울 필요가 없기 때문이죠!

메뉴를 만들기 위해서 제일 처음에 해줘야할 것은 GLUT 의 glutCreateMenu 함수를 호출하는 것입니다. 다음은 이 함수의 설명입니다.:

int glutCreateMenu(void (*func)(int value));

인자 설명:
func - 만든 메뉴의 이벤트를 처리할 함수의 이름입니다.

이 함수가 반환하는 값은 메뉴를 구별할 때는 쓰는 아이디(identifier)입니다.

어플리케이션에 우리가 원하는만큼 메뉴를 만들 수 있고 각 메뉴를 위한 콜백함수가 정해져 있습니다. 하지만 하나의 콜백함수를 모든 메뉴의 콜백함수로 사용할 수도 있습니다. 다음에 해야할 것은 메뉴에 항목을 추가하는 것인데 GLUT 의 glutAddMenuEntry 함수를 사용합니다. 다음은 이 함수의 설명입니다.

void glutAddMenuEntry(char *name, int value);

인자 설명:
name - 메뉴에 나타나는 항목의 이름입니다.
value - 항목이 선택되었을 때 메뉴의 콜백함수에 전달되는 값입니다.

이 함수는 앞서 추가된 항목 바로 뒤에 새로운 항목을 추가합니다. 즉 메뉴의 제일 아래에 추가하는 것입니다. GLUT 에는 메뉴의 중간에 항목을 추가하는 함수는 없습니다. 왜냐하면 GLUT 가 순수 윈도우즈 API 의 기능을 대체하기 위해서 만들어진 것이 아니니까요. GLUT 는 데모를 쉽게 만들기 위해서 개발된 것이기에 데모 개발에 아주 좋은 솔루션입니다.

OK, so now you have a pop-up menu, but there's one last thing we must do: attach the menu to a mouse button, that is we must specify when the pop-up menu will appear. Using GLUT you can cause the menu to appear when a mouse button is pressed. The function to establish this relationship is glutAttachMenu.

위의 함수들을 사용해서 하나의 메뉴를 만들 수 있지만 마지막으로 해줘야할 일이 있습니다. 바로 메뉴를 마우스버튼에 연결하는 것인데 팝업메뉴가 나타날 때를 정하는 것입니다. GLUT 를 사용하면 마우스버튼이 눌렸을 때 메뉴를 나타나게 할 수 있습니다. 메뉴와 마우스버튼을 연결해 주는 함수는 glutAttachMenu 입니다. 아래는 이 함수의 설명입니다.

void glutAttachMenu(int button);

인자 설명:
button - 메뉴에 연결될 버튼을 나타내는 정수값입니다.

위의 함수에서 button 인자는 다음 세개의 값중에서 하나가 됩니다.:

  • GLUT_LEFT_BUTTON
  • GLUT_MIDDLE_BUTTON
  • GLUT_RIGHT_BUTTON

다음은 위의 모든 함수를 사용하는 예제입니다.

...
#define RED 1
#define GREEN 2
#define BLUE 3
#define WHITE 4
...
void createGLUTMenus()
{
int menu;
// 메뉴를 만들고 GLUT 에 processMenuEvents
  함수가
// 메뉴이벤트를 다룰 것이라고 알려줍니다.
menu = glutCreateMenu(processMenuEvents);

// 메뉴에 항목을 추가합니다.
glutAddMenuEntry("Red",RED);
glutAddMenuEntry("Blue",BLUE);
glutAddMenuEntry("Green",GREEN);
glutAddMenuEntry("White",WHITE);

// 마우스의 오른쪽버튼에 메뉴를 연결합니다.
glutAttachMenu(GLUT_RIGHT_BUTTON);
}

위에서 사용하는 RED, BLUE, GREEN 그리고 WHITE 는 반드시 정수값으로 정의되어 있어야합니다. 이 때 각 항목은 서로 다른 값으로 정의되어야하며 그렇지 않으면 항목별로 처리를 달리 만들수 없습니다.

Now we'll write the function to process the menu events. As you probably guessed by now, we're going to set the color of the triangle using our menu. Note that our function must be called processMenuEvents, thats the name we provided when we created the menu in the function above. Furthermore, looking at the syntax of glutCreateMenu we know that it will have a parameter representing the selected menu item.

이제 메뉴의 이벤트를 처리하는 함수를 만들어봅시다. 이미 짐작했겠지만 메뉴를 통해서 삼각형의 색깔을 바꿔보겠습니다. 우리가 만들 함수는 위에서 메뉴를 만들 때 정해준 "processMenuEvents" 여야합니다. 그리고 glutCreateMenu 함수를 봐서 알겠지만 이 함수는 선택된 메뉴 항목을 나타내는 아이디값을 인자로 받습니다.

void processMenuEvents(int option)
{
switch (option)
{
case RED :
red = 1.0;
green = 0.0;
blue = 0.0;
break;
case GREEN :
red = 0.0;
green = 1.0;
blue = 0.0;
break;
case BLUE :
red = 0.0;
green = 0.0;
blue = 1.0;
break;
case WHITE :
red = 1.0;
green = 1.0;
blue = 1.0;
break;
}
}

이제 남은 일은 main 함수에 위에서 만들었던 createGLUTMenus 함수를 호출하는 것입니다. 다은은 새로운 main 함수입니다.

void main(int argc, char **argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);
glutInitWindowPosition(100,100);
glutInitWindowSize(320,320);
glutCreateWindow("SnowMen");
glutDisplayFunc(renderScene);
glutIdleFunc(renderScene);
glutReshapeFunc(changeSize);

// 메뉴를 만드는 함수를 호출합니다.
createGLUTMenus();

glutMainLoop();
}

Visual C 프로젝트 파일( glut7.zip ) 을 받아서 자세히 살펴보세요 :)

팝업메뉴를 소개한 이번장을 끝내기 전에 두개의 함수를 더 알아봅시다. 처음 함수는 메뉴와 마우스버튼간에 연결을 해지할 때 사용하는 함수입니다. 위에서 우리는 glutAttachMenu 함수를 사용해서 메뉴를 마우스버튼에 연결했었습니다. 때론 이 연결을 끊어야할 때도 있겠죠. 즉 마우스버튼을 눌렀을 때 더이상 메뉴가 표시되지 않게 하는 것입니다. 이 일은 glutDetachMenu 함수를 사용하며 다음은 이 함수의 설명입니다.:

void glutDetachMenu(int button);

인자설명:
button - 메뉴와 연결을 해지할 버튼입니다.

button 인자는 glutAttachMenu 에서 사용한 값과 같아야합니다. 그래서 위의 예제에서 마우스버튼과의 연결을 해지할려면 다음처럼 해야합니다.

...
glutDetachMenu(GLUT_RIGHT_BUTTON);
...

이제 마지막으로, 메뉴가 사용하는 리소스를 반환하고 싶을 때는 메뉴를 제거하면 됩니다. GLUT 는 glutDestroyMenu 함수를 사용해서 메뉴를 제거합니다. 다음은 이 함수의 설명입니다.:

void glutDestroyMenu(int menuIdentifier);

인자설명:
menuIdentifier - 제거시킬 메뉴의 아이디값입니다. 이 값은 glutCreateMenu 함수가 반환한 값과 같은 값이어야합니다.

지금까지 GLUT 를 사용해서 메뉴를 만드는 방법에 대해서 알아보았습니다. 다음 장에서는 팝업메뉴의 특성에 대해서 더 자세하게 알아보겠습니다.

번역 : 김성철 ( paranwave@korea.com )


GLUT 튜토리얼

GLUT Tutorial

서브(sub, 하위)메뉴

by Antonio

원문 : http://www.lighthouse3d.com/opengl/glut/index.php3?11

앞장에서 우리는 간단한 메뉴를 만드는 방법과 메뉴항목을 선택했을 때 일어나는 이벤트를 처리하는 방법에 대해서 알아보았습니다. 이번 장에서는 서브메뉴를 추가하는 방법에 대해서 알아보겠습니다. 서브메뉴는 메뉴를 만들때 사용했던 것과 같은 함수로 만듭니다. 그래서 앞장에서 보았듯이 glutCreateMenu 함수를 사용해서 메뉴를 만들듯 서브메뉴를 만듭니다. 그런 다음에 glutAddSubMenu 함수를 사용해서 메뉴에 서브메뉴 항목을 추가하는 것입니다.

void glutAddSubMenu(char *entryName, int menuIndex);

인자설명:
entryName - 메뉴의 항목중에 서브메뉴의 이름입니다.
menuIndex - 서브메뉴의 인덱스값입니다. 이 값은 서브메뉴를 만들기 위해서 glutCreateMenu 를 호출했을 때 반환되는 값입니다.

이 함수는 메뉴의 끝에 항목을 추가합니다. 그리고 그 항목을 선택했을 때, 서브메뉴가 나타나게 됩니다. 다음에 나오는 코드는 위의 함수의 사용을 나타낸 코드입니다.

void createGLUTMenus()
{
int menu,submenu;
submenu = glutCreateMenu(processMenuEvents);
glutAddMenuEntry("Red",RED);
glutAddMenuEntry("Blue",BLUE);
glutAddMenuEntry("Green",GREEN);

menu = glutCreateMenu(processMenuEvents);
glutAddMenuEntry("White",WHITE);
glutAddSubMenu("RGB Menu",submenu);
glutAttachMenu(GLUT_RIGHT_BUTTON);
}

위의 코드를 실행해보면, 마우스의 오른쪽버튼을 눌렀을 때 두 개의 항목이 있는 메뉴가 나타납니다. 첫번째 항목은 "White" 고 두번째 항목은 "RGB Menu" 입니다. 여기서 "RGB Menu" 항목을 누르면 서브메뉴가 나타나게 되고 이 서브메뉴는 "Red", "Green" 그리고 "Blue" 세개의 메뉴항목을 가지고 있습니다.

이 예제에서는 메뉴와 서브메뉴의 이벤트를 처리하는 함수를 같게 설정해 놓았지만 꼭 이렇게 하지 않아도 됩니다. GLUT 는 이벤트처리부분이 각 메뉴마다 분리되어 있든지 아니면 합쳐져있든지 상관하지 않기 때문입니다. 이 프로젝트의 소스코드는 glut8.zip 파일에 있으므로 받아서 실행해 보고 자세하게 살펴보길 바랍니다.

번역 : 김성철 ( paranwave@korea.com )


GLUT 튜토리얼

GLUT Tutorial

메뉴 수정하기

by Antonio

원문 : http://www.lighthouse3d.com/opengl/glut/index.php3?12

메뉴항목을 비활성으로 바꿔야할 때가 있는데 GLUT 를 사용하면 메뉴항목을 쉽게 바꾸거나 삭제할 수 있습니다. 메뉴항목을 바꾸기 위해서 다음의 함수를 사용합니다. :

void glutChangeToMenuEntry(int entry, char *name, int value);

인자 설명:
entry - 메뉴항목의 인덱스입니다. 반드시 1 부터 전메뉴항목의 개수사이여야합니다.
name - 메뉴항목의 새로운 이름입니다.
value - 메뉴항목이 선택되었을 때 콜백함수로 반환되는 값입니다.

void glutChangeToSubMenu(int entry, char *name, int menu);

인자 설명:
entry - 메뉴항목의 인덱스입니다. 반드시 1 부터 전메뉴항목의 개수사이여야합니다.
name - 메뉴항목의 새로운 이름입니다.
menu - 메뉴의 인덱스입니다.

주의: 이 함수는 마이크로소프트 윈도우즈에서 사용하기에 약간 문제가 있습니다. 필자 생각에는 이식성 때문인 것 같습니다. 서브메뉴를 연달아 두번바꾸면 첫번째변환만 실행되고 나머지는 실행되지 않습니다.

메뉴항목을 지우기 위해서는 다음 함수를 사용하면 됩니다.

void glutRemoveMenuItem(int entry);

인자 설명:
entry - 메뉴항목의 인덱스입니다. 반드시 1 부터 전메뉴항목의 개수사이여야합니다.

마지막으로 메뉴항목의 개수는 아무때나 알 수 있습니다. glutGet 함수를 사용해서 현재 메뉴에 질의하면 됩니다. 다음 예제는 메뉴를 수정하는 것입니다.:

void processMenuEvents(int option)
{
red = 0.0;
green = 0.0;
blue = 0.0;
switch (option)
{
case RED :
red
  = 1.0;
break;
case GREEN :
green = 1.0;
break;
case BLUE :
blue = 1.0;
break;
case WHITE :
red = 1.0;
green
  = 1.0;
blue
  = 1.0;
break;
}
}

void processKeys(unsigned char c, int x, int y)
{
int num = glutGet(GLUT_MENU_NUM_ITEMS);
switch (c)
{
case 'a':
glutChangeToMenuEntry(1,"Blue",BLUE);
glutChangeToMenuEntry(3,"Red",RED);
break;
case 'b':
glutChangeToMenuEntry(3,"Blue",BLUE);
glutChangeToMenuEntry(1,"Red",RED);
break;
case 'c':
if (num
  > 3)
glutRemoveMenuItem(num);
break;
case 'd': if (num == 3)
glutAddMenuEntry("White",WHITE);
break;
}
glutSetMenu(menu);
}

void createGLUTMenus()
{
menu = glutCreateMenu(processMenuEvents);
glutAddMenuEntry("Red",RED);
glutAddMenuEntry("Green",GREEN);
glutAddMenuEntry("Blue",BLUE);
glutAddMenuEntry("White",WHITE);
glutAttachMenu(GLUT_RIGHT_BUTTON);
}

메뉴의 콜백함수에서 메뉴를 수정한 것이 아니라 키보드의 콜백함수에서 메뉴를 수정했습니다. 이 이유는 메뉴를 사용중일 때는 메뉴를 수정할 수 없기 때문입니다. 콜백함수가 끝나기 전까지는 메뉴가 사용되고 있는 것이기 때문에 메뉴의 구조를 메뉴의 콜백함수내에서 바꿀 수 없는 것입니다. 시험해보고 싶다면 Visual C 프로젝트 파일( glut81.zip ) 을 받아서 실행해보세요.

위에서도 말했지만, 메뉴가 사용중일 때에는 메뉴항목을 수정할 수 없습니다. 메뉴가 사용중이 아닐 때에만 메뉴를 수정할 수 있습니다. GLUT 에는 메뉴가 나타날 때와 사라질 때 호출되는 함수를 등록해주는 콜백함수가 있습니다. 이 함수는 glutMenuStatusFunc 로 다음은 이 함수의 설명입니다.

void glutMenuStatusFunc(void (*func)(int status, int x, int y);

인자 설명:
func - 콜백함수의 이름입니다.

이 함수는 main 함수에서 호출할 수 있습니다. 나중에 추가해봅시다. glutMenuStatusFunc 함수의 원형을 보면 콜백함수는 세개의 인자를 받습니다.

status - GLUT_MENU_IN_USE 또는 GLUT_MENU_NOT_IN_USE 값 중 하나입니다.
x - 윈도우 클라이언트 영역을 기준으로 메뉴의 왼쪽(left) 좌표입니다.
y - 윈도우 클라이언트 영역을 기준으로 메뉴의 위쪽(top) 좌표입니다.

다음 예제 함수는 메뉴가 사용중일 때와 아닐때를 가려 알맞게 flag 변수를 설정합니다.

void processMenuStatus(int status, int x, int y)
{
if (status == GLUT_MENU_IN_USE)
flag = 1;
else
flag = 0;
}

이제 이 flag 변수를 사용해서 키보드 이벤트 처리함수를 수정해봅시다.

void processKeys(unsigned char c, int x, int y)
{
if (!flag)
{
int num = glutGet(GLUT_MENU_NUM_ITEMS);
switch (c)
{
case
  'a':
glutChangeToMenuEntry(1,"Blue",BLUE);
glutChangeToMenuEntry(3,"Red",RED);
break;
case
  'b':
glutChangeToMenuEntry(3,"Blue",BLUE);
glutChangeToMenuEntry(1,"Red",RED);
break;
case
  'c':
if
  (num > 3)
glutRemoveMenuItem(num);
break;
case
  'd':
if (num == 3)
glutAddMenuEntry("White",WHITE);
break;
}
}
}

다음은 새로운 코드가 추가된 main 함수입니다.

void main(int argc, char **argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);
glutInitWindowPosition(100,100);
glutInitWindowSize(320,320);
glutCreateWindow("SnowMen");
glutDisplayFunc(renderScene);
glutIdleFunc(renderScene);
glutReshapeFunc(changeSize);

createGLUTMenus();

glutMenuStatusFunc(processMenuStatus);

glutKeyboardFunc(processKeys);

glutMainLoop();
}

소스코드 파일( glut9.zip ) 을 받아서 자세하게 살펴보세요.

번역 : 김성철 ( paranwave@korea.com )


GLUT 튜토리얼

GLUT Tutorial

메뉴 교체하기

by Antonio

원문 : http://www.lighthouse3d.com/opengl/glut/index.php3?13

GLUT 를 사용하면 어플리케이션 실행 중에 메뉴를 통채로 바꿀 수 있습니다. glutSetMenu 함수와 glutGetMenu 함수를 사용하면 됩니다. 다음은 이 함수들의 설명입니다.:

void glutSetMenu(int menu);

인자 설명:
menu - 만들어 놓은 메뉴의 인덱스입니다.

이 함수를 사용해서 메뉴를 교체할 수 있습니다.

다음은 glutGetMenu 함수의 설명입니다.:

int glutGetMenu(void);

이 함수는 현재 메뉴의 인덱스를 반환합니다. 다음 예제는 위의 두함수를 사용해서 만든 것인데 F1 키를 누르면 메뉴가 교체됩니다.

void processSpecialKeys(int c, int x, int y)
{
if (!flag)
{
if (c == GLUT_KEY_F1)
{
int
  x = glutGetMenu();
if (x
  == menu1)
glutSetMenu(menu2);
else
glutSetMenu(menu1);

//
  메뉴를 마우스버튼에 연결하는 것을 잊지마세요!!!
glutAttachMenu(GLUT_RIGHT_BUTTON);
}
}
}

void createGLUTMenus()
{
menu2 = glutCreateMenu(processMenuEvents);
glutAddMenuEntry("Blue",BLUE);
glutAddMenuEntry("Green",GREEN);
glutAddMenuEntry("Red",RED);

menu1 = glutCreateMenu(processMenuEvents);
glutAddMenuEntry("Red",RED);
glutAddMenuEntry("Green",GREEN);
glutAddMenuEntry("Blue",BLUE);
glutAttachMenu(GLUT_RIGHT_BUTTON);
}

Visual C 프로젝트 파일( glut10.zip )을 받아서 자세하게 살펴보세요 :)

번역 : 김성철 ( paranwave@korea.com )


GLUT 튜토리얼

GLUT Tutorial

비트맵폰트

by Antonio

원문 : http://www.lighthouse3d.com/opengl/glut/index.php3?bmpfont

비트맵폰트는 2D 폰트를 말합니다. 비트맵폰트를 3D 상에서 표현하지만 굵기가 없고 회전 및 크기변환을 할 수 없습니다. 오로지 이동만 할 수 있습니다. 그리고 이 폰트는 빌보드처럼 항상 관측자 쪽으로 향하고 있습니다. 이 점이 안 좋은 것 같지만 다르게 생각해보면 폰트의 방향에 대해서 신경 쓰지 않아도 되는 것입니다. 폰트는 항상 관측자 쪽으로 향해있으니까요.

이번 장에서는 비트맵형식의 폰트를 화면에 출력하는 GLUT 함수에 대해서 알아보겠습니다. 우선, glutBitmapCharacter 함수가 필요합니다. 다음은 이 함수의 설명입니다.:

void glutBitmapCharacter(void *font, int character)

인자 설명:
font - 사용할 폰트의 이름입니다.(아래에 사용가능한 값들이 있습니다.)
character - 렌더링할 대상입니다. 문자, 기호, 숫자 등등...

font 인자에 설정할 수 있는 값입니다.:

  • GLUT_BITMAP_8_BY_13
  • GLUT_BITMAP_9_BY_15
  • GLUT_BITMAP_TIMES_ROMAN_10
  • GLUT_BITMAP_TIMES_ROMAN_24
  • GLUT_BITMAP_HELVETICA_10
  • GLUT_BITMAP_HELVETICA_12
  • GLUT_BITMAP_HELVETICA_18

위의 폰트이름만 봐도 어떤 폰트가 쓰일 것인지 어느 정도 알 수 있겠지만 팝업메뉴를 통해서 모두 살펴볼 수 있도록 예제프로그램을 만들었으니 이 문서를 다 읽고 나서 살펴보세요.

다음 줄의 예제는 glutBitmapCharacter 함수를 호출해서 현재 레스터 위치에 한 개의 문자를 출력하는 것입니다.:

glutBitmapCharacter(GLUT_HELVETICA_18,'3');

다음은 레즈터 위치에 관한 것입니다. 레스터 위치는 OpenGL 라이브러리군 중 glRasterPos 함수로 설정할 수 있습니다. 다음은 이 함수의 설명입니다.:

void glRasterPos2f(float x, float y);
void glRasterPos3f(float x, float y, float z);

인자 설명:
x, y, z - 문자열이 출력될 지역 좌표값입니다.

glutBitmapCharacter 함수는 설정된 위치에 문자를 렌더링하고 현재 레스터 위치는 문자의 너비만큼 이동됩니다. 그래서 문자열을 렌더링하면, glutBitmapCharacter 함수가 연속해서 호출되어 원하는 출력을 만들어 냅니다. 다음 함수는 지정된 레스터 위치에서 시작하여 하나의 문자열을 출력하는 함수입니다. :

void renderBitmapCharacter(float x, float y, float z, void *font,
  char *string)
{
char *c;
glRasterPos3f(x, y,z);
for (c=string; *c != '\0'; c++)
{
glutBitmapCharacter(font, *c);
}
}

Visual C 프로젝트 파일( glutbmpfont.zip ) 을 받아서 실행해 보세요. 이 프로젝트는 "세상을 둘러 보아요~ 2 탄" 의 예제를 뼈대로 이용했습니다. 달라진 점은 렌더링을 처리하는 함수에서 renderBitmapCharacter 함수를 호출하는 것과 폰트를 선택을 위해서 팝업메뉴를 제공하는 것입니다.

번역 : 김성철 ( paranwave@korea.com )


GLUT 튜토리얼

GLUT Tutorial

비트맵폰트와 직교투영

by Antonio

원문 : http://www.lighthouse3d.com/opengl/glut/index.php3?bmpfontortho

2D 폰트인 비트맵폰트의 쓰임새는 대개 사용자에게 정보를 제공하는 것입니다. 예를 들면, 어플리케이션에서 초당 프레임수를 알려주기 위해서 비트맵폰트를 사용할 수 있습니다. 이러한 정보들은 사용자가 카메라를 이리저리 움직이더라도 화면의 고정된 위치에 있어야할 것입니다. 정보를 출력할 위치를 계산할 때 2D 직교투영을 사용하는 것이 원근(투시)투영을 사용하는 것보다 계산하기가 쉽습니다. 그것은 직교투영을 사용하면 픽셀단위로 위치를 정할 수 있기 때문입니다.

비트맵폰트로 글자를 출력할 때에는 우선 원근투영을 사용해서 장면을 그린 다음에, 투영방법을 직교투영으로 변환 후 문자열을 출력합니다. 문자열을 출력하고 나서는 투영방법을 이전투영방법으로 돌려놔야하는데(이 경우는 원근투영으로) 그래야만 다음 프레임때 장면을 제대로 그릴 수 있기 때문입니다.

다음에 나오는 코드는 위에서 설명한 것을 렌더링함수로 표현해본 것입니다.:

void renderScene()
{
// 장면을 렌더링하는데 필요한 모든 작업을 수행합니다.
....

setOrthographicProjection();
glPushMatrix();
glLoadIdentity();
renderBitmapString(5,30,GLUT_HELVETICA_18,"3D
  Tech");
glPopMatrix();
resetPerpectiveProjection();

glutSwapBuffers();
}

두개의 새로운 함수인 setOrthograpicProjection 과 resetPerpectiveProjection 을 볼 수 있습니다. 첫번재 함수는 카메라를 조작하기 위해서 행렬모드를 GL_PROJECTION 으로 바꾸는 것으로 시작해서 이전 환경변수(이 예제에서는 원근투영과 그 밖에 다른것들)를 저장하고 투영행렬을 glLoadIdentity() 함수를 사용해서 초기화합니다. 그 다음에 gluOrtho 함수를 사용해서 투영방법을 직교투영을 설정합니다.

setOrthographicProjection 함수는 x 축과 y 축의 범위를 정하는 것이 주된 목적입니다. y 축을 뒤집고 나서 원점을 좌측 위로 옮깁니다. 이렇게 만들면 화면좌표계상에 글씨를 쓰기가 쉽워집니다.

변수 w 과 h 는 다른 곳에서 계산됩니다.(소스코드에서 changeSize 함수를 참고하세요.)

void setOrthographicProjection()
{
// 행렬을 투영모드로 변경합니다.
glMatrixMode(GL_PROJECTION);
// 원근투영에 대한 환경변수를
// 가지고 있는 이전 행렬을 저장합니다.
glPushMatrix();
//
  행렬을 초기화합니다.
glLoadIdentity();
//
  2D 직교투영을 설정합니다.
gluOrtho2D(0, w, 0, h);
//
  y 축을 뒤집습니다.
glScalef(1, -1, 1);
//
  좌측아래의 원점을
// 좌측위로 옮깁니다.
glTranslatef(0, -h, 0);
glMatrixMode(GL_MODELVIEW);
}

resetPerspectiveProjection 함수는 아주 간단합니다. 전에 직교투영을 설정하기 전에 저장해 놓은 원근투영을 다시 돌려놓는 것인데, 행렬모드를 GL_PROJECTION 으로 바꾸고 행렬을 팝(pop)하여 예전 설정들을 다시 돌려놓고 다시 행렬모드를 GL_MODELVIEW 로 설정해 주는게 전부입니다.

void resetPerspectiveProjection()
{
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
}

위의 함수들을 사용하면 문자열을 다닥다닥 붙여서 출력합니다. 물론 빈칸(스페이스키)은 제외입니다. 문자열에서 빈칸이 있으면 빈칸이 출력되겠죠. 여백을 주기 위해서는 현재 레스터 위치마다 간격을 유지해야합니다. 즉 x 좌표 값에 약간씩 여백을 주는 것이죠. 레스터 위치에 간격을 유지하는 방법에는 두가지가 있습니다. 하나는 비트맵폰트 한 글자를 그린 다음에 간격을 계산하는 것이고 두번째는 OpenGL 의 상태기계에 현재 레스터 위치가 어디쯤 되느냐고 묻는 것입니다.

첫번째 방법을 사용하려면 문자의 치수(너비와, 높이)를 알아야됩니다. 높이는 폰트들 높이의 최고값인 상수이지만, 너비는 매우 가변적입니다. 이 점을 위해서 GLUT 에는 문자의 너비를 알려주는 함수가 있습니다. 그 함수는 glutBitmapWidth 이며 아래는 이 함수의 설명입니다.:

int glutBitmapWidth(void *font, int character);

인자 설명:
font - GLUT 에 정의 되어 있는 폰트 중 하나입니다. 설정할 수 있는 값은 앞장을 살펴보세요.
character - 너비를 알고 싶은 문자입니다.

예를들어서, 각 문자간에 일정한 간격을 두고 문자열을 출력해주는 함수를 만들고자 한다면 아래의 코드처럼 만들 수 있습니다. :

void renderSpacedBitmapString( float x, float
  y, int spacing, void *font, char *string)
{
char *c;
int x1=x;
for (c=string; *c != '\0'; c++)
{
glRasterPos2f(x1,y);
glutBitmapCharacter(font, *c);
x1 = x1 + glutBitmapWidth(font,*c)
  + spacing;
}
}

비슷한 예로, 문자마다 일정한 간격을 두어서 세로로 된 문자열을 출력하려면 아래의 코드처럼 만들 수 있습니다.:

void renderVerticalBitmapString( float x, float
  y, int bitmapHeight, void *font, char *string)
{
char *c;
int i;
for (c=string,i=0; *c != '\0'; i++,c++)
{
glRasterPos2f(x, y+bitmapHeight*i);
glutBitmapCharacter(font, *c);
}
}

bitmapHeight 변수는 쉽게 계산할 수가 있는데 그 것은 각 폰트의 높이 중 최고값을 알기 때문입니다.(소스코드를 참고하세요.)

GLUT 에는 아직 설명하지 않은 비트맵폰트를 위한 함수가 있는데 glutBitmapLength 함수입니다. 이 함수는 문자열의 총 픽셀길이를 계산해줍니다. 이 함수의 반환값은 문자열 내의 모든 문자들의 너비를 더한 값입니다. 다음은 이 함수의 설명입니다.:

int glutBitmapLength(void *font, char *string);

인자 설명:
font - GLUT 에 정의 되어 있는 폰트 중 하나입니다. 설정할 수 있는 값은 앞장을 살펴보세요.
string - 총 픽셀길이를 알고 싶은 문자열입니다.

위에서 살펴본 함수들의 사용법과 쓰임새는 예제( glutbmpfontortho.zip )를 참고하세요.

번역 : 김성철 ( paranwave@korea.com )


GLUT 튜토리얼

GLUT Tutorial

외곽선폰트

by Antonio

원문 : http://www.lighthouse3d.com/opengl/glut/index.php3?strokefont

외곽선폰트는 3D 입체폰트로 비트맵폰트와는 다르게 회전변환, 크기변환이 가능합니다. 물론 이동변환도 가능하죠.

이번 장에서는 외곽선폰트를 화면에 출력해주는 GLUT 의 함수에 대해서 알아보겠습니다. 간단하게 glutStrokeCharacter 함수 하나만 사용하면 됩니다. 다음은 이 함수의 설명입니다.:

void glutStrokeCharacter(void *font, int character)

인자 설명:
font - 사용하려는 폰트의 이름입니다.(설정할 수 있는 값은 아래를 참고하세요.)
character - 문자, 숫자, 기호 등등. 렌더링을 할 것입니다.

다음은 font 인자에 설정할 수 있는 값입니다.:

  • GLUT_STROKE_ROMAN
  • GLUT_STROKE_MONO_ROMAN (고정폭폰트: 104.76 단위 너비).

다음에 나오는 코드는 glutStrokeCharacter 함수를 사용하는 예로 현재 지역좌표위치에 문자 한개를 출력합니다. :

glutStrokeCharacter(GLUT_HELVETICA_18,'3');

비트맵폰트와는 다르게 외곽선폰트를 출력하는 위치는 다른 그래픽컬 프리미티브를 그리는 것처럼 정합니다. 즉, 이동, 회전, 크기변환을 사용하는 것입니다. 다음 함수는 지역월드좌표계의 특정 위치에서 문자열을 출력하는 함수입니다.

void renderBitmapString(float x, float y, float z, void *font,
  char *string)
{
char *c;
glPushMatrix();
glTranslatef(x, y,z);
for (c=string; *c != '\0'; c++)
{
glutStrokeCharacter(font,
  *c);
}
glPopMatrix();
}

주의: GLUT 는 외곽선폰트를 그릴 때 여러개의 선분을 이용하기 때문에 glLineWidth 함수를 사용해서 선분의 굵기를 조절할 수 있습니다. 이 함수는 선분의 굵기를 나타내는 float 형 값만 인자로 받습니다. Viasul C 프로젝트 파일( glutstrokefont.zip ) 을 받아서 실행해 보세요. 이 예제는 폰트를 선택할 수 있도록 팝업메뉴를 제공합니다.

GLUT 에는 비트맵폰트용으로 문자의 너비를 알려주는 함수가 있습니다. 비슷하게 외곽선폰트용으로 문자의 너비를 알려주는 함수 glutStrokeWidth 함수가 있습니다. 다음은 이 함수의 설명입니다.:

int glutStrokeWidth(void *font, int character);

인자 설명:
font - 사용하려는 폰트의 이름입니다.(설정할 수 있는 값은 위를 참고하세요.)
character - 너비를 알고 싶은 문자입니다.

번역 : 김성철 ( paranwave@korea.com )


GLUT 튜토리얼

GLUT Tutorial

초당프레임수(FPS) 계산하기

by Antonio

원문 : http://www.lighthouse3d.com/opengl/glut/index.php3?fps

우리가 만든 어플리케이션이 얼마의 빠르기로 실행되고 있는 것일까요? 종종 어플리케이션에 작은 변경을 가하지만 성능에는 어떻게 영향을 끼치는지 확실하게 알 수 없습니다. 즉, 그러한 변경사항들이 초당 장면이 그려지는 수인 FPS 에 얼만큼 영향을 줄까요? 이번 장에서는 FPS 를 계산하기 위해서 어떻게 GLUT 를 사용하는지 알아보겠습니다. 이번 장에서 알아보는 것은 성능을 측정하는 완벽한 방법이 아니라 하나의 참고사항일 뿐입니다.

GLUT 는 시스템의 여러 사항에 대해서 알 수 있도록 함수를 제공하는데, 이 함수로 알 수 있는 것 중 하나가 glutInit 함수가 호출되었을 때부터 누적된 밀리초입니다. 이 함수는 glutGet 으로 다음은 이 함수의 설명입니다.:

int glutGet(GLenum state);

인자 설명:
state - 알고 싶은 값의 이름입니다.

이 함수는 많은 용도로 사용될 수 있습니다. 예를 들면, 창의 좌표를 얻는다거나 또는 OpenGL 버퍼의 깊이를 얻는데 사용할 수 있습니다. 이번 장에서는 이 함수를 glutInit 함수가 호출되었을 때부터 누적된 밀리초를 얻는데 사용할 것입니다. 누적된 밀리초를 얻기 위해서는 state 인자에 GLUT_ELAPSED_TIME 을 넘겨줘야합니다.

int time;
time = glutGet(GLUT_ELAPSED_TIME);

그럼 지금부터 이 함수를 가지고 FPS 를 계산해 봅시다. 프레임속도는 매 프레임마다 다릅니다. 즉, 모든 프레임들이 각각 그려질 때마다 걸리는 시간이 다르다는 것입니다. 그 것은 운영체제에서 주어진 어플리이케이션 하나만 실행되고 있는 것이 아니기 때문입니다. 운영제체가 주어진 어플리케이션을 처리할 때에만 카메라가 움직일 것이고 이에 따라 렌더링되는 장면이 달라질 것입니다. 그래서 예제에서는 FPS 를 매 프레임마다 계산하지 않고 1 초 단위로 계산합니다.

FPS 를 계산하기 위해서 세 개의 변수 frame, time, timebase 를 선언하고 timebase 와 frame 변수는 0 으로 초기화합니다.

int frame=0, time, timebase=0;

이 변수의 용도를 알아보겠습니다. :

  • frame - 마지막으로 FPS 를 계산한 후부터 현재까지의 프레임수입니다.
  • time - 현재의 밀리초입니다.
  • timebase - 마지막으로 FPS 를 계산했을 때의 밀리초입니다.

다음 코드는 idle(휴면) 함수로 등록된 함수에 적을 코드입니다. 프레임수를 계산합니다. 자세한 것은 코드 아래에 있는 설명을 참고하세요.:

...
frame++;
time=glutGet(GLUT_ELAPSED_TIME);

if (time - timebase > 1000)
{
fps = frame*1000.0/(time-timebase));
timebase = time;
frame = 0;
}
...

frame 변수를 사용해서 프레임수를 증가하고 time 변수에 현재 시간을 담습니다. 그 다음 time 변수와 timebase 변수를 비교해서 1 초가 지났는지 검사합니다. 즉 time 변수에서 timebase 변수의 차가 1000 밀리초보다 크면 1 초가 지난 것입니다. 만약 1 초가 지나지 않았다면 FPS 를 계산하지 않고 1 초가 지났다면 FPS 를 계산합니다.

time 변수와 timebase 변수의 차는 마지막으로 FPS 를 계산한 후부터 경과된 밀리초단위의 시간을 알려줍니다. 밀리초단위의 경과된 시간으로 1000 을 나누면 초단위로 경과된 시간을 얻게됩니다. FPS 를 계산하기 위해서 남은 일은 초단위의 경과된 시간에 렌더링된 프레임수(마지막으로 FPS 를 계산한 후부터 누적되온 값)를 곱해주는 것입니다. 이 계산의 결과로 새로운 FPS 를 얻게 됩니다. 끝으로 timebase 변수를 현재 밀리초로 다시 설정하고 frame 변수는 0 으로 설정합니다.

어플리케이션이 처음 시작할 때는 timebase 값이 0 이기 때문에 1 초가 지난 다음에 처음으로 계산된 FPS 값을 볼 수 있습니다. 하지만 이 FPS 값은 오차가 큰데, 그 이유는 FPS 를 계산할 때 창이 초기화되는 시간까지 포함되어 있기 때문입니다. 조금만 테스트를 계속해 보면 이 값이 실제 FPS 보다 훨씬 작은 값이었단 걸 알 수 있습니다.

다음 코드처럼 해주면 FPS 를 창에 출력할 수 있습니다.

...
frame++;
time=glutGet(GLUT_ELAPSED_TIME);
if (time - timebase > 1000)
{
sprintf(s,"FPS:%4.2f",
frame*1000.0/(time-timebase));
timebase = time;
frame = 0;
}

glColor3f(0.0f,1.0f,1.0f);
glPushMatrix();
glLoadIdentity();
setOrthographicProjection();
renderBitmapString(30,35,(void
  *)font,s);
glPopMatrix();
resetPerspectiveProjection();
...

위의 코드 조각은 이전에 살펴본 '비트맵폰트와 직교투영' 장에서 다룬 것입니다. 그리고 이 장의 예제 파일 ( glutfps.zip ) 을 받아서 실행해보세요.

번역 : 김성철 ( paranwave@korea.com )


GLUT 튜토리얼

GLUT Tutorial

게임모드에 대해서

by Antonio

원문 : http://www.lighthouse3d.com/opengl/glut/index.php3?gameglut

GLUT 에 대해서 설명한 맨페이지(Linux 에 있는 도움말)에 따르면 GLUT 의 게임모드는 고성능의 풀스크린 렌더링이 가능하도록 설계되었다고 적혀있습니다. 하지만 어디에나 함정이 있기 마련이죠. GLUT 의 기능 중에서 팝업메뉴와 서브윈도우 기능은 좋은 성능을 내는데 걸림돌이 됩니다. 이번 장에서는 GLUT 가 제공하는 게임모드에 대해서 알아보겠습니다. 참고로 이 장의 설명들은 맨페이지나 GLUT 의 소스코드에서 거의 대부분 참고한 것입니다. 제가 이 글을 쓸 때만해도 GLUT 의 게임모드에 대해서 공식적인 문서가 없었고 다른 싸이트에서도 이 주제에 대한 튜토리얼이 없었습니다. 그래서 게임모드 주제에 대해서 쓴 이 글의 정확성을 100% 보장할 수는 없습니다. 저는 게임모드에 대해서 얼마의 식견을 가지고서 실행할 수 있는 데모를 만들었습니다. 하지만 여러 다른 하드웨어를 갖춘 시스템에서 충분히 테스트하지 못했기 때문에 예제에 애매한 점이나 잘못된 버그가 있을지도 모릅니다. 이 튜토리얼의 내용을 바탕으로 어플리케이션을 만들었을 때 이런 문제로 고생하고 있다면 문제에 대한 자세한 정보를 저에게 보내주세요. 그럼 제가 최선을 다해서 살펴보겠습니다. 이미 여러분들 중에 GLUT 의 게임모드에 대해서 잘 아시는 분이 있다면 잘 못된 부분을 수정할 수 있도록 이 글에서 틀린점을 지적해서 제 e-mail(antonio.ramires@lighthouse3d.com)로 보내주셨으면 좋겠습니다.

자~ 이제 약간의 참회도 했으니 이번 장을 시작해보겠습니다. 우선 제일 처음에 해야할 것은 게임모드를 위한 설정을 정의하는 것입니다. 즉 풀스크린으로 할 것인지 아닌지. 이런 설정의 정의는 스크린의 해상도, 픽셀 깊이, 리프레쉬율 등을 포함합니다. 이 말을 다르게 풀어보면, 우리가 원하는 해상도가 뭐든지간에 다 만들 수 있다는 얘기입니다.(물론 하드웨어의 지원 범위 내에서만 가능합니다.) 즉, 현재 해상도로만 풀스크린을 설정하지 않아도 되는 것입니다.

풀스크린을 설정하는 정의는 문자열로 만들어집니다. 다음은 이 문자열의 형식입니다.:

"WxH:Bpp@Rr"

형식 설명:
W - 픽셀 단위의 스크린의 너비입니다.
H - 픽셀 단위의 스크린의 높이입니다.
Bpp - 픽셀당 비트의 수입니다.
Rr - Hz 단위의 화면 리프레쉬율입니다.

다른 것을 알아보기 전에, 이 설정들은 단지 하드웨어에 요청하는 것일 뿐입니다. 만약에 특정 모드가 불가능하다면 그 설정은 무시됩니다.

다음은 특정 해상도를 설정하는 문자열의 예제입니다.

"800x600:32@100" - 800x600 해상도; 32bit 트루컬러; 100Hz; 의 스크린 설정문입니다.
"640x480:16@75" - 640x480 해상도; 16bit 하이컬러; 75Hz; 의 스크린 설정문입니다.

모든 조건(요소)들을 일일이 설정하는 것은 쪼금 괴로운 일입니다. 비록 우리가 스크린 해상도에 대해서 분명하게 알고 있더라도, 특별한 픽셀 깊이나 리프레쉬율을 일일이 설정하기에는 무리가 있을 때가 있습니다. 하지만 다행스러운 것은 우리가 일일이 모든 것을 설정해줄 필요가 없다는 것입니다. 우리는 특정 조건을 빼 놓고 설정할 수 있습니다. 빠진 조건은 GLUT 가 알아서 설정합니다. 다음에 나오는 예제들은 모두 풀스크린을 설정하는 문자열입니다.

"WxH"
"WxH:Bpp"
"WxH@Rr"
"WxH:Bpp@Rr"
"@Rr"
":Bpp"
"Bpp:@Rr"

GLUT 는 순서가 지켜진다면 어떤 조합이든지 다룰 수가 있습니다. 그래서 픽셀당 비트의 수 앞에 리프레쉬율을 설정하는 것은 허용되지 않습니다.

그럼 픽셀깊이와 리프레쉬율등은 고려하지 않고 화면 해상도만 설정하고 싶다면 다음과 같이 쓸 수 있을 것입니다.:

"800x600"

아니면 현재 해상도에 픽셀깊이만 32비트로 설정하고 싶다면 다음과 같이 쓸 수 있을 것입니다.

":32"

때로는 리프레쉬율만 바꾸고 싶을 때가 있겠죠.:

"@100"

위에서 든 몇가지 예제들이 풀스크린의 설정에 관해서 모든 것을 보여주진 않지만 모두 사용할 수 있는 설정문들입니다.

자~ 이제 게임모드로 들어갈 준비가 되었습니까? 그럼 우선 GLUT 로 풀스크린 설정을 요구해야합니다. 게임모드를 설정하는 GLUT 의 함수는 glutGameModeString 함수입니다. 다음은 이 함수의 설명입니다.:

void glutGameModeString(const char *string);

인자 설명:
string - 위에서 본 설정문처럼 알맞은 조건을 포함한 문자열입니다.

GLUT 는 glutGameModeString 함수에 전달된 인자의 유효성을 검사합니다. 비록 함수가 에러코드를 반환하지 않더라도, 설정모드가 유효한지 검사할 수 있습니다. GLUT 는 게임모드 설정문의 유효성을 검사할 수 있는 함수를 제공합니다. 다음은 이 함수의 설명입니다.

int glutGameModeGet(GLenum info);

인자 설명:
info - 원하는 정보입니다.

요청한 게임모드가 유효한지 검사하려면 GLUT 에 미리 정의되어 있는 상수 GLUT_GAME_MODE_POSSIBLE 을 info 인자에 전해줘야합니다. 이 경우, 반환값이 게임모드의 유효성을 뜻하는데 0 이 아니면 유효(가능)한 게임모드라는 뜻입니다. 하지만 맨페이지에 있는 GLUT 설명서를 보면 비록 게임모드가 유효하더라도 스크린 설정이 보증되지 않을 경우가 있다고합니다.

위 함수의 반환값이 0 이 아니라고 가정하면 glutEnterGameMode 함수로 게임모드에 진입할 수 있고 또는 적어도 진입을 시도할 수 있습니다. 이 함수가 실제로 스크린을 요구한 설정대로 게임모드를 만듭니다. 다음은 이 함수의 설명입니다.

void glutEnterGameMode(void);

800x600 해상도의 게임모드를 설정하는 GLUT 어플리케이션의 main 함수는 아래처럼 만들 수 있습니다.

int main(int argc, char **argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);

/*
glutInitWindowPosition(100,100);
glutInitWindowSize(640,360);
glutCreateWindow("SnowMen from 3D-Tech");
*/

// 창의 크기와 위치를 설정하는 위의 함수호출
  대신에
// 게임모드를 설정합니다
glutGameModeString("800x600:32");

// 풀스크린으로 진입합니다.
if (glutGameModeGet(GLUT_GAME_MODE_POSSIBLE))
glutEnterGameMode();
else
{
printf("The select mode
  is not available\n");
exit(1);
}

// 모든 콜백함수들을 등록하고
// 디스플레이리스트를 만듭니다.
init();

glutMainLoop();

return(0);
}

init 함수는 openGL 을 초기화를 수행하는데 필요한 모든 콜백함수들을 등록합니다. 예를 들어 아래와 같이 만들 수 있습니다.:

void init()
{
glutIgnoreKeyRepeat(1);
glutKeyboardFunc(processNormalKeys);
glutSpecialFunc(pressKey);
glutSpecialUpFunc(releaseKey);
glutDisplayFunc(renderScene);
glutIdleFunc(renderScene);
glutReshapeFunc(changeSize);
initScene();
}

어플리케이션을 실행하는 도중에 게임모드와 창모드를 왔다갔다 변환하고 싶을 경우가 있습니다. 다음에 나오는 코드를 사용하면 게임모드에서 창모드로 변환할 수 있습니다. 사용자가 F1 키를 누르면 게임모드로 변환되고 F6 키를 누르면 다시 창모드로 변환됩니다. 이 때 반드시 창의 속성을 다시 정의하고 콜백함수를 등록한 다음, 메인루프로 진입해야합니다.

예제 코드를 보기전에 게임모드를 창모드로 변환시켜주는 함수를 살펴봅시다.:

void glutLeaveGameMode(void);

이 함수는 모드를 변환시키는 특수키가 눌렸을 때 작동할 것입니다. 다음은 앞에서 말했던 동작들을 그대로 코드로 옮겨놓은 함수입니다.:

void pressKey(int key, int x, int y)
{
switch (key)
{
...
case GLUT_KEY_F1:
//
  해상도와, 색상깊이를 저으이합니다.
glutGameModeString("640x480:32");
//
  풀스크린으로 진입합니다.
if (glutGameModeGet(GLUT_GAME_MODE_POSSIBLE))
{
glutEnterGameMode();

//
  콜백함수들을 다시 등록합니다.
init();
}
break;
case GLUT_KEY_F6:
//
  기본창모드로 돌아갑니다.
glutLeaveGameMode();
break;
}
}

위의 함수에는 중요한 내용들이 많이 있습니다. glutEnterGameMode 함수로 게임모드에 진입했을 때 반드시 콜백함수들을 다시 등록해줘야합니다. 그리고 OpneGL 문맥을 다시 정의해줘야합니다. 게임모드는 새로운 창과 같은 것입니다. 이 말은 창모드의 콜백함수들이 게임모드에는 쓸모 없다는 것입니다. 그래서 콜백함수를 사용하려면 다시 등록해줘야 합니다. 거기에다가 OpenGL 문맥을 다시 정의해야합니다. 그리고 창모드의 디스플레이 리스트를 만들었다면 게임모드를 위한 디스플레이 리스트도 다시 만들어줘야합니다.

GLUT 는 아주 좋은 API 라서, 현재 처리하고 있는 일의 상황을 물어볼 수 있도록 프로그래머에게 방법을 제공합니다. 당연히 게임모드가 돌아가는 상황을 알 수 있도록 특별한 함수를 제공하는데 glutGameModeGet 이란 함수입니다. 이 함수의 원형은 이미 위에서 소개했었습니다. 그 때 이 함수의 인자로 GLUT_GAME_MODE_POSSIBLE 값을 전달했었습니다. 그럼 이제 인자에 전달할 수 있는 다른 값들에 대해서 알아봅시다.

glutGameModeGet 함수에 인자로 전달할 수 있는 값이 여러개가 있습니다. 이 값들은 게임모드 프로그래밍을 안전하게 하기 위해서 모두 필요합니다. 각각의 경우에 따라서 반환값이 어떻게 되는지 아래에 적어보았습니다.:

설정값 | 설명
GLUT_GAME_MODE_ACTIVE | 어플리케이션이 게임모드로 실행되고 있다면 0 이 아닌값을 반환하고 창모드로 실행되고 있다면 0 을 반환합니다.
GLUT_GAME_MODE_POSSIBLE | 위에서도 알아보았듯이 게임모드를 설정하는 설정문이 유효한지 검사합니다. 게임모드에 진입하기 전에 glutGameModeGet 함수로 설정문을 검사하는 것은 아주 좋은 생각입니다.
GLUT_GAME_MODE_DISPLAY_CHANGED | 위에서도 말했듯이 게임모드로 진입할 때 디스플레이모드가 실제로 변경되었는지 보증할 수 없습니다. 이 값을 사용해서 게임모드에 실제로 진입되었는지 알 수 있습니다. 이미 게임모드에 진입되어 있는 상태에서 이 값을 검사하면 설정들이 변경되었는 알 수 있습니다.
GLUT_GAME_MODE_WIDTH | 스크린의 너비를 반환합니다.
GLUT_GAME_MODE_HEIGHT | 스크린의 높이를 반환합니다.
GLUT_GAME_MODE_PIXEL_DEPTH | 현재모드의 픽셀당 비트의 수를 반환합니다.
GLUT_GAME_MODE_REFRESH | 실제 리프레쉬율을 반환합니다.

마지막 네 개의 설정값들은 게임모드가 실행되고 있을 때 사용할 수 있는 값입니다. 만약에 게임모드를 설정하는 설정문이 유효하지 않다면 glutGameModeGet 함수는 이 설정값들(마지막 네 개의 값)에 대해서 모두 -1 을 반환합니다. 이미 게임모드가 실행되고 있을 때에도 그렇습니다. 예를 들어서 주어진 어플리케이션이 640x480 의 게임모드에서 실행 중에 1600x1200 게임모드로 변경요청을 했는데 실제 하드웨어 능력과 맞지 않아서 유효하지 않은 요구가 된다면 GLUT 는 게임모드의 해상도를 변경하지 않고 640x480 해상도로 게임모드를 유지합니다. 그러나 이 때 높이값을 알아봤을 때 480 값이 반환되지 않고 -1 이 반환됩니다. 실제 높이가 480 이더라도 말이죠.

다음 코드는 glutGameModeGet 함수를 사용하는 예제입니다.

if (glutGameModeGet(GLUT_GAME_MODE_ACTIVE) == 0)
sprintf(currentMode,"Current Mode: Window");
else
sprintf( currentMode, "Current Mode: Game Mode %dx%d at %d hertz, %d bpp",
glutGameModeGet(GLUT_GAME_MODE_WIDTH),
glutGameModeGet(GLUT_GAME_MODE_HEIGHT),
glutGameModeGet(GLUT_GAME_MODE_REFRESH_RATE),
glutGameModeGet(GLUT_GAME_MODE_PIXEL_DEPTH)
);
}

여기까지가 게임모드에 관해서 제가 알고있는 것들입니다. 게임모드를 사용하는 예제를 VC 프로젝트 ( glutgm.zip ) 로 만들어 보았습니다. 자세하게 살펴보세요.

번역 : 김성철 ( paranwave@korea.com )


GLUT 튜토리얼

GLUT Tutorial

보조창(subwindow)에 대해서

by Antonio

원문 : http://www.lighthouse3d.com/opengl/glut/index.php3?subwin

GLUT 를 사용해서 보조창을 여러개 만들 수 있습니다. 즉, 메인창을 각기 다른 영역으로 나누어 각 창마다 OpenGL 문맥(Context)과 콜백함수들을 설정할 수 있습니다. 보조창을 만들려면 glutCreateSubWindow 함수를 사용해야 합니다. 다음은 이 함수의 설명입니다. :

int glutCreateSubWindow(int parentWindow, int x, int y, int width, int height);

인자 설명:
parentwindow - 부모창의 아이디(구분값)입니다.
x, y - 보조창의 왼쪽 위쪽 경계의 좌표입니다. 이 좌표는 부모창의 원점에 상대적인 좌표입니다.
width, height - 보조창의 크기입니다.

부모창의 아이디 값은 부모창을 만들 때 얻을 수 있습니다. 다음에 나오는 코드는 메인창과 보조창을 만드는 예제입니다. :

mainWindow = glutCreateWindow("SnowMen from 3D-Tech");
...
subWindow1 = glutCreateSubWindow(mainWindow, 10,10,100,100);

주어진 보조창이 또 다른 보조창의 부모창이 될 수 있습니다. GLUT 의 명세에 따르면 보조창을 컨테이너로 사용할 수 있다고 되어 있습니다.

위에서도 말했듯이 보조창은 자신만의 OpenGL 문맥을 갖습니다. 그래서 디스플레이 리스트를 사용하려면 디스플레이 리스트를 사용하는 모든 창과 보조창에 대해서 디스플레이 리스트를 만들어 줘야 합니다. 그리고 대부분의 콜백함수들도 마찬가지입니다.

적어도 각 창마다 렌더링을 수행하는 함수를 등록해 줘야 합니다. 등록은 glutDisplayFunc 를 사용합니다. 그리고 또, 각 창마다 커서와 마우스 이벤트를 처리하는 콜백함수들을 따로 등록해야 합니다. 그리고 여러개의 보조창을 사용하면 특정 보조창에 팝업메뉴를 연결할 수 있습니다. 그리고 주의해야할 것은 휴면(idle)함수는 단 한개만 존재한다는 것입니다.

다음 코드는 더욱 완벽하게 초기화를 수행하는 코드입니다.

int mainWindow, subWindow1,subWindow2,subWindow3;
...
int main(int argc, char **argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);
glutInitWindowPosition(100,100);
glutInitWindowSize(w,h);

mainWindow = glutCreateWindow("SnowMen from 3D-Tech");

//
  키보드 관련 설정입니다.
glutIgnoreKeyRepeat(1);
glutKeyboardFunc(processNormalKeys);
glutSpecialFunc(pressKey);
glutSpecialUpFunc(releaseKey);

//
  크기변경처리 함수입니다.
glutReshapeFunc(changeSize);

//
  렌더링과 휴지(idle)함수입니다.
glutDisplayFunc(renderScene);
glutIdleFunc(renderSceneAll);

//
  이 함수가 OpenGL 문맥을 만듭니다.
initScene();

subWindow1 = glutCreateSubWindow(mainWindow, border,border,
  w-2*border, h/2 - border*3/2);

//
  각 창마다 렌더링 함수를 등록해야합니다.
glutDisplayFunc(renderScene1);

//
  OpenGL 문맥을 다시 만들어야합니다.
initScene();

subWindow2 = glutCreateSubWindow(mainWindow, border,(h+border)/2,
  w/2-border*3/2, h/2 - border*3/2);

//
  각 창마다 렌더링 함수를 등록해야합니다.
glutDisplayFunc(renderScene2);

//
  OpenGL 문맥을 다시 만들어야합니다.
initScene();

subWindow1 = glutCreateSubWindow(mainWindow, (w+border)/2,(h+border)/2,
  w/2-border*3/2,h/2 - border*3/2);

// 각 창마다
  렌더링 함수를 등록해야합니다.
glutDisplayFunc(renderScene3);

//
  OpenGL 문맥을 다시 만들어야합니다.
initScene();

glutMainLoop();

return(0);
}

위의 코드는 세 개의 보조창을 만듭니다. 각각의 보조창들은 같은 장면을 서로 다른 시점으로 표현하기 위해서 사용됩니다. 제일 위에 있는 보조창은 자유롭게 움직이는 카메라를 표현하고, 아래 왼쪽에 있는 보조창은 위에서 본 것을 그리고 아래 오른쪽 보조창은 오른쪽에서 본 것을 표현합니다. 이 것을 그림으로 보면 다음과 같습니다.

메인창이든 보조창이든 창이 하나 만들어지면 그 창이 현재 선택된 창이 됩니다. 현재 창에 관련된 모든 콜백함수들이 등록됩니다. 물론 휴면(idle)함수는 다시 등록되지 않습니다. 앞에서도 말했듯이 휴면(idle)함수는 전체 어플리케이션에서 오로지 하나만 있어야합니다.

그래서 위의 어플이케이션에서 키보드 콜백함수는 오로지 메인창에만 등록되어 있습니다. 제 경험에 의하면 마이크로소프트의 윈도우즈 운영체제에서는 키보드 콜백함수를 메인창에 등록하지 않으면 키보드 이벤트를 잡지 못 합니다. 다른 리눅스나 SGI 그리고 Mac 을 사용하는 분들도 그런 경험이 있는지 궁금합니다.

위의 코드를 보면 모든 보조창들의 OpenGL 문맥을 만들려고 initScene 함수를 호출했습니다. 그러나 크기변경을 처리하는 reshape 함수는 한번만 등록했습니다. 그 이유는 한 개의 함수를 사용해서 모든 창들의 크기변경을 처리하기 때문입니다. 하지만 각각의 보조창마다 reshape 함수를 따로 등록하는 것도 가능합니다.

그리고 또 하나, 렌더링을 처리하는 콜백함수를 각각의 창을 생성할 때 꼭 등록해야합니다. 절대 렌더링 함수를 등록하는 작업과 휴면(idle)함수에 관한 내용에 대해서 실수하지마세요. 이 두 함수에 대한 내용과 차이점이 궁금하다면 앞 장에서 살펴본 " 애니메이션 " 을 참고하세요.

보조창이 더 이상 필요가 없다면 제거할 수 있습니다. 보조창을 제거하려면 glutDestroyWindow 함수를 사용합니다.

void glutDestroyWindow(int windowIdentifier)

인자 설명:
windowIdentifier - 창을 만들 때 반환된 아이디값입니다.

이 함수는 창을 제거합니다. 이 때 파괴되는 창이 포함하는 모든 보조창들도 제거됩니다. OpenGL 문맥은 창이 제거될 때 같이 제거됩니다.

번역 : 김성철 ( paranwave@korea.com )


GLUT 튜토리얼

GLUT Tutorial

보조창의 크기변환 처리

by Antonio

원문 : http://www.lighthouse3d.com/opengl/glut/index.php3?subwinreshape

창의 크기변환을 처리하는 콜백함수는 두 가지의 일을 처리합니다. 보조창들의 크기를 다시 설정해 주고 투영행렬을 다시 계산해 줍니다. 우리는 메인창에 어떤 지형도 그리지 않으므로 메인창의 투영행렬을 다시 계산하는 것은 하지 않겠습니다.

제일 처음 소개할 함수는 창의 크기와 위치를 다시 설정해 주는 함수입니다.

void glutPositionWindow(int x, int y);
void glutReshapeWindow(int width, int height);

인자 설명:
x,y - 창의 위쪽 왼쪽 경계좌표입니다.
width, height - 픽셀 단위의 창 크기입니다.

이 두 함수는 현재 창과 맞물려서 동작하기 때문에 제일 처음에 특정 창을 현재 창으로 설정해야합니다. 현재 창으로 설정하기 위해서는 창의 핸들을 가지고 glutSetWindow 함수를 호출해야합니다. 다음은 이 함수의 설명입니다.:

void glutSetWindow(int windowIdentifier);

인자 설명:
windowIdentifier - 창을 만들었들 때 반환된 식별값입니다.

그래서 보조창의 위치와 크기를 다시 설정하기 위해서 어떤 함수를 호출하기 전에 각각의 보조창들을 현재 창으로 설정해야합니다. 아래 나오는 코드조각은 크기변환 처리 함수를 나타낸 changeSize 함수입니다. 앞장에서 말했듯이 메인창을 위한 크기변환처리 함수만 만들었습니다. 이 것만으로 충분한데 그 것은 사용자가 메인창의 크기를 변경할 수 있기 때문입니다.

어떤 창이 현재 창인지 알아야할 때가 있다면 GLUT 의 glutGetWindow 함수를 사용하면 됩니다.

int glutGetWindow();

이 함수의 반환값은 현재 윈도우의 식별값입니다. 다시 크기변환함수에 대해서 알아봅시다.:

int w,h, border=6;
...
void changeSize(int w1,int h1)
{
if(h1 == 0)
h1 = 1;

// 나중을 위해서 이 값들을 보관합니다.
w = w1;
h = h1;

// subwindow1 을 현재창으로 설정합니다.
glutSetWindow(subWindow1);

// 현재창의 크기와 위치를 다시 설정합니다.
glutPositionWindow(border,border);
glutReshapeWindow(w-2*border, h/2 - border*3/2);

// 현재창의 투영행렬을 설정합니다.
changeSize2(w-2*border, h/2 - border*3/2);

// subwindow2 을 현재창으로 설정합니다.
glutSetWindow(subWindow2);

// 현재창의 크기와 위치를 다시 설정합니다.
glutPositionWindow(border,(h+border)/2);
glutReshapeWindow(w/2-border*3/2, h/2 - border*3/2);

// 현재창의 투영행렬을 설정합니다.
// 현재창의 너비와 높이에 맞게 인자를 설정해야합니다.
changeSize2(w/2-border*3/2,h/2 - border*3/2);

// subwindow3 을 현재창으로 설정합니다.
glutSetWindow(subWindow3);

// 현재창의 크기와 위치를 다시 설정합니다.
glutPositionWindow((w+border)/2,(h+border)/2);
glutReshapeWindow(w/2-border*3/2,h/2 - border*3/2);

// 현재창의 투영행렬을 설정합니다.
changeSize2(w/2-border*3/2,h/2 - border*3/2);
}

이제 남은 일은 changeSize2 함수를 정의하는 것으로 이 함수는 투영 행렬을 다시 계산합니다. 이미 위에서 현재윈도우를 보조윈도우 중 하나로 설정하면서 changeSize2 함수에 보조윈도우의 크기값을 그대로 전달해서 호출했기 때문에 이 함수는 평범한 크기변환 함수가 됩니다.

void changeSize2(int w1, int h1)
{
// 윈도우가 너무 작아졌을 때
// 0 으로 나누는 것을 막는 코드입니다.
ratio = 1.0f * w1 / h1;

// 수정하기 전에 좌표계를 초기화합니다.
glMatrixMode(GL_PROJECTION);
glLoadIdentity();

// 뷰포트를 전체 윈도우 크기로 설정합니다.
glViewport(0, 0, w1, h1);

// 절단 영역을 설정합니다.
gluPerspective(45,ratio,0.1,1000);
glMatrixMode(GL_MODELVIEW);
}

번역 : 김성철 ( paranwave@korea.com )


GLUT 튜토리얼

GLUT Tutorial

여러 보조창에 렌더링하기

by Antonio

원문 : http://www.lighthouse3d.com/opengl/glut/index.php3?subwinrender

시작하기 전에 우리가 정의한 콜백함수들을 기억해 봅시다.:

  • 휴면(idle) 함수 - renderSceneAll
  • 메인창의 렌러링함수 - renderScene
  • subwindow1 창의 렌더링함수 - renderScenesw1
  • subwindow2 창의 렌더링함수 - renderScenesw2
  • subwindow3 창의 렌더링함수 - renderScenesw3

각 창들의 렌더링 함수를 살펴보는 것으로 이번 장을 시작하겠습니다. 메인창은 보조창들로 가려져 있기 때문에 검은색으로만 렌더링합니다. 이 예제에서는 여러개의 창을 가지고 있기 때문에 적절한 창의 식별값으로 glutSetWindow 함수를 호출해야합니다. 그 다음 색상버퍼를 기본색인 검은색으로 깨끗이 지웁니다.

void renderScene()
{
glutSetWindow(mainWindow);
glClear(GL_COLOR_BUFFER_BIT);
glutSwapBuffers();
}

각각의 보조창을 위한 렌더링 함수를 만들어야하는데 이 예제에서는 모든 창에 그리는 지형은 같고 시점이나 카메라의 위치만 다릅니다.

지형을 그리는 함수는 renderScene2 함수입니다. 하지만 이 함수를 호출하기 전에 우리는 반드시 특정 보조창을 현재창으로 설정하고 MODELVIEW 행렬을 초기화하기 위해서 단위행렬로 만들고 카메라를 gluLookAt 함수를 사용해서 설정해야 합니다.

다음 변수는 아래에 있는 코드에 나오는데 각각의 의미를 알아봅시다. :

  • x,y,z : 월드내에서 현재 위치의 좌표입니다.
  • lx,ly,lz : 시선을 정의하는 벡터입니다.
  • deltaMove, deltaAngle, angle : 카메라 이동을 정의냅니다.

보조창에 관한 첫 장에서 우리는 세 개의 보조창을 만들었습니다. 그 세 개의 보조창은 같은 장면을 서로 다르게 투시합니다. 첫번째 보조창은 현재 시점으로 장면으로 투시하고 두번째는 장면을 위에서 보는 것으로 투시합니다. 그리고 세번째 보조창은 카메라처럼 현재 위치에서 오른쪽을 투시합니다.

다음 코드는 각각의 창들을 위한 렌더링 함수입니다. 이 코드는 앞에서 살펴본 코드를 확장한 것입니다. 이 코드에 대해서 더 깊이 알고 싶다면 " 키입력 예제 : 세상을 둘러 보아요~" , "비트맵폰트와 직교투영", "초당프레임수(FPS) 계산하기" 편을 다시 읽어보세요.

// main window
void renderScene()
{
glutSetWindow(mainWindow);
glClear(GL_COLOR_BUFFER_BIT);
glutSwapBuffers();
}

// subwindow 1 - 카메라 = 현재위치
void renderScenesw1()
{
glutSetWindow(subWindow1);
glLoadIdentity();
gluLookAt(x, y, z, x + lx,y + ly,z + lz, 0.0f,1.0f,0.0f);

renderScene2(subWindow1);
}

// subwindow 2 - 위에서 투시
void renderScenesw2()
{
glutSetWindow(subWindow2);
glLoadIdentity();
gluLookAt(x, y+15, z, x ,y - 1,z, lx,0,lz);
renderScene2(subWindow2);
}

// subwindow 3 - 오른쪽에서 투시
void renderScenesw3()
{
glutSetWindow(subWindow3);
glLoadIdentity();
gluLookAt(x-lz*10 , y, z+lx*10, x ,y ,z , 0.0f,1.0f,0.0f);
renderScene2(subWindow3);
}

이제 남은 것은 휴면(idle)함수를 만드는 것입니다. 우리가 만드는 예제에서 이 함수는 renderSceneAll 함수입니다. 이 함수는 deltaMove 와 deltaAngle 변수가 0 이 아닌지 검사해서 현재 위치값과 시선 벡터를 업데이트합니다.

각 보조창의 렌더링 함수를 모두 호출한 다음에 메인창의 렌더링 함수는 호출하지 않는데 그 이유는 메인창은 항상 검은색 배경이므로 변하는 것이 전혀 없기 때문입니다.

void renderSceneAll()
{
if (deltaMove)
moveMeFlat(deltaMove);
if (deltaAngle)
{
angle += deltaAngle;
orientMe(angle);
}
renderScenesw1();
renderScenesw2();
renderScenesw3();
}

VC 프로젝트 파일( glutsubwin.zip ) 을 받아서 자세하게 살펴보세요. 자 이제 GLUT 튜토리얼이 끝났습니다. 제안, 버그, 요구사항 등등 얼마든지 환영합니다. 이메일(antonio.ramires@lighthouse3d.com)을 보내주세요.

GLUT 튜토리얼 번역이 모두 끝났습니다.
하지만 한건 해냈다란 느낌보다는 한건 저질렀다라는 느낌이 드네요.
어색하기 짝이 없는 단어 설정과 word to word 의 번역-_-;;
다음에는 더 잘 할 수 있기를....

번역 : 김성철 ( paranwave@korea.com )


Using DirectX with C++Builder

Using DirectX with C++Builder http://www.geocities.com/foetsch/

Using DirectX with C++Builder
Translation : Sungchul Kim.

요약

이 것은 볼랜드 사용자들에게 DirectX SDK 의 사용법을 알려주기 위한 글입니다. 여기에서는 DirectX SDK 의 설치법부터 VCL 상에서 DirectDraw 와 Direct3D 의 사용법, SDK 를 컴파일 하는 방법, 순수 API 만으로 만드는 방법 등 모든 이슈들을 다루려합니다.

소개

DirectX and C++Builder Graphics 뉴스그룹에는, 두가지 질문들이 자주 올라오곤 합니다. "볼랜드 컴파일러에서 DirectX 를 사용할 수 있나요?", "DX 어플리케이션을 만들기 위해서 Visual C++ 를 구입해야만 합니까?"

8월 11일 금요일에, 마이크로소프트 직원이 그 질문에 답변을 달았는데 이런 내용이었죠. "물론 VC++ 을 사용하는게 더 안전하겠죠. 그 것을 사용하라고 권하고 싶군요. 왜냐하면 무엇보다도 MS 에서 그것(VC++)을 모든 곳에서 사용하기 때문이죠." (이게 적절한 답변인지는 잘 모르겠다...;-)

하지만, C++ 빌더에서 DX 어플리케이션을 만들 수 있는 것은 당근빠따 이두박근으로 가능합니다. 당신은 아마 이렇게 말할걸요. "그걸 하기 위해서 아무것도 설정할 필요가 없더군!" 공평하기 위해서, 마이크로소프트 직원은 그것(BCB를 포함한 다른 컴파일러에서 DX 를 사용할 수 있다는 것)을 숨기지 않았고 마이크로소프트는 Borland 사용자들이 다이렉트X SDK를 사용할 수 있도록 확실하게 만들어 놓았습니다. 무엇보다도, C++ 빌더는 (VC++보다 더) ANSI 표준에 충실한 Win32 개발환경을 제공하고 있습니다.사실, BCB 와 DX SDK 를 함께 사용할 때는 약간, 아주 쬐쬐쬐금 주의해야 할 사항들이 있지만 그러한 문제는 이 문서에서 해결해 줄 것입니다~ ;-)

DirectX SDK 받기와 설치하기

좋은 소식 : C++ 빌더는 DX 개발에 필요한 모든 헤더파일(*.h or *.hpp) 과 라이브러리 파일(*.lib or *.dll)들을 가지고 있습니다.
다른 소식 : 새로운 버전의 DirectX 는 여러분이 새로운 버전의 C++ 빌더를 사는 것보다 더 자주 발표됩니다.

항상 최신의 SDK를 얻기 위해서는 우선 마이크로소프트의 DirectX 개발자 센터에 가봐야합니다. 거기에는 항상 새로운 버전의 SDK 들이 나열되어 있고 여러분들은 그 SDK 를 다운로드하거나 아니면 CD-ROM 으로 주문을 할 수가 있습니다. 매번 신버전의 SDK 는 용량이 커지고 커져서 (이 글을 쓸때는 128MB! 였다.) 그냥 주문을 하는 편이 아마도 싸고 간편한 방법일 것입니다.

밧뜨! 그러나, 만약에 여러분들이 예제와 유틸리티 (자주 업데이트가 안 되는 것들) 가 필요 없다면, 여러분들은 도움말 파일(.chm), 헤더 파일(.h), 그리고 라이브러리들(.lib) 을 분별해서 받을 수 있습니다.

다음 순서는 DX SDK 를 사용하기 위한 IDE(통합개발환경)를 설정하는 방법입니다. CBuilder 폴더 안에 있는 "include" 와 "lib" 폴더에 DX SDK 를 설치하는 것은 그리 썩 좋은 생각도 아니고 그렇게 할 필요도 없습니다. 그보다는, DX SDK 는 다른 곳에 설치하고 프로젝트의 옵션을 바꾸어 주는 것이 좋습니다. C++ 빌더에서 실행되고 있는 열려있는 모든 프로젝트들을 닫아 주시고 "Project | Options" 에서 "Folders and Definitions" 탭을 선택해 주세요. 그런 다음 DX SDK 가 설치된 경로를 추가해줍니다.

include 경로에는 "<dxsdk>\include" 를, 그리고 libraries 경로에는 "<dxsdk>\lib\Borland" 를 추가해 주세요.

시선집중! : DX SDK 경로는 다른 경로들 보다 앞에 적는 것이 필요합니다. 예를 들면 DX SDK 경로를 추가하기 전이 "$(BCB)\include" 였다면 추가 후는 "c:\dxsdk\include ; $ ( BCB ) \include" 처럼 되어야합니다. dxsdk 는 여러분들이 DX SDK 를 설치한 폴더라고 가정합니다.

우리들은 곧 프로젝트 옵션으로 돌아갈 것이지만 그 보다 우선...

SDK 예제들을 빌드(Build)하기

SDK 는 컴파일이 된 예제와 같이 소스코드의 형태로도 예제 어플리케이션들을 제공하는데... 대체로, 여러분들은 그 예제들을 살펴 보고 만지작만지작 거리며 수정하고 싶을 것입니다. C++ 빌더에서 함 땡겨볼까요? 예제들을 컴파일해보도록 하죠. 다음의 순서대로 해 주시길 바랍니다.

  1. 예제 코드를 여러분이 원하는 폴더에다가 복사를 하세요.(예 : "C:\My Documents"). 이렇게 함으로써, 여러분들은 예제 코드를 망가트렸을 때 원본 소스를 보호할 수 있습니다. 따라서어~ 마음대로 지지고 볶아 보십시요~ ;-)

  2. C++빌더에서, "New" 버튼을 클릭한 다음. "Console Wizard" 를 선택한 후 VCL 을 제외한 "Window(GUI)" 어플리케이션을 생성합니다.(필요는 없지만 사용할 수는 있습니다).

    시선집중! : C++Builder의 최신 버젼에서는,"project source"를 지정할 수 있습니다. 이것을 위해 WinMain 함수(대체로 " winmain.cpp ")를 포함하는 파일을 선택하시고 원시 코드 형으로는 C++을 지정하세요.
  3. 여러분이 Console Wizard 에서 "project source" 를 지정하지 않았다면 WinMain 함수의 몸체(구현부분)는 제거되고 IDE 가 "extern" 으로 그 함수를 선언하는데, 아래처럼 됩니다.:

    extern WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow);

  4. 그 다음, "Add to Project" ( "Project | Add to Project...", Shift+F11) 버튼을 눌러서 winmain.cpp 를 링크하세요.

    시선집중! : 위처럼 하기 싫으면, 프로젝트 소스(Console Wizard 가 만들어준 "Unit1.cpp")에 winmain.cpp( 또는 WinMain 함수가 구현되어 있는 파일) 에서 WinMain 코드 부분을 복사&붙이기를 하시든가~ ;-)

  5. 예제 폴더에 있는 모든 .cpp(또는 .c) 파일들을 프로젝트에 추가하시고 .res 및 .rc 파일들도 추가해 주세요.

    시선집중! : 무조건 모든 파일들을 추가하지 말고, 해당 예제의 "makefile" 을 살펴서 그 예제가 어떤 파일들을 사용하는지 보세요. 어쩌면 다른 폴더에서 있는 파일들을 가져와야 할지도 모릅니다. 대개 <dxsdk>\samples\multimedia\d3dim\src\d3dframe 의 파일들이 필요한데, 이 파일들 또한 링크를 해 주어야합니다.

  6. DirectX 어플리케이션들은 DirectX 시스템 DLL 들로부터 함수를 가져와 사용합니다. 이러한 함수들은 .lib 파일들을 통해서 가져올 수 있습니다. DirectDraw 를 예를 들면, 여러분들은 ddraw.lib를 링크해야 하고, Direct3D 를 사용하기 위해서는 ddraw.lib 과 d3dim.lib 를 링크시켜야 합니다. 또한, 모든 DirectX 어플리케이션들은 dxguid.lib 을 필요로 한답니다. (DX SDK 도움말 파일에서 GUIDs, dxguid.lib 과 INITGUID 부분을 보세요)

    시선집중! : <dxsdk>\lib\Borland 폴더의 .lib 파일들을 사용하세요. 다른 폴더에 있는 것들은 "invalid OMF record" 에러를 일으킵니다. "COFF vs OMF" 이슈에 관한 더 자세한 정보를 얻고 싶다면 Borland 싸이트에 방문해 보세요.

  7. 빌드를 해 봤는데 "unresolved external" 과 같은 에러가 발생한다면, 그 예제가 필요로 하는 모든 파일(.lib 또는 .cpp 을 포함한)들을 제대로 링크했는지 살펴보세요. 그리고 "makefile" 을 보면서 다른 폴더의 파일이 필요한지도 살펴보세요. 또한 아래의 "The Direct3D Framework" 봐 주세요~!

시선집중! : 예제 중에서 "Direct3DX Utility Library" 예제들은 빌드할 수가 없는데 그것은 C++ 빌더에서 D3DX 를 전혀 사용할 수가 없기 때문입니다.(하지만, Direct3D 는 사용할 수 있습니다) "d3dx.lib" 은 static library(import library 와 비교되는) 입니다. 이 것이 의미하는 것은 DLL 파일이 없다는 것입니다. 누군가 마이크로소프트의 D3DX 소스 코드를 볼랜드 컴파일러의 OMF 의 라이브러리를 만들기 위해서 재 컴파일해야 하지만 마이크로소프트는 볼랜드에 책임이 있다고 그러고, 볼랜드는 마이크로소프트에 책임이 있다고 그러고 있습니다 그려 쩝. -_-;;

VCL Form 상에서 DirectDraw 와 Direct3D 사용하기

Visual Component Library(이하 VCL) 은 C++ 빌더의 가장 뛰어난 특징 중의 하나입니다. DirectX 어플리케이션에서 VCL 의 장점을 이용하지 못한다면 부끄러운 일이 될 것입니다. 사실, DirectDraw 와 Direct3D 의 기능들을 VCL 프로젝트들에 통합하는 것은 쉬운 일입니다. 다음 단계들을 실행해 보면서 이 것을 어떻게 하는지 익혀봅시다.(프로젝트 파일은 여기에서 ZIP 파일 포멧으로 받을 수 있습니다)

  1. 새로운 어플리케이션을 생성하세요.(어떻게 하는지 다들 아시죠? ;-)

  2. main form 을 더블-클릭해서 OnCreate 이벤트의 핸들러를 만든 다음에 다음 코드를 쓰세요.

    PostMessage(Handle, WM_INITDIRECT3D, 0, 0);

    시선집중! : WM_INITDIRECT3D 는 이미 정의 되어 있는 윈도우즈 메세지가 아닙니다. 이 메세지는 사용자가 정의한 메세지로 사용자 정의 함수에서 다루어야 할 것입니다. 이 메세지는 모든 폼 생성 메세지들이 처리되었다는 것과 폼이 Direct3D 초기화를 시도하기 전에 나타나는 것을 보장합니다.

  3. Unit1.h 에 "#define WM_INITDIRECT3D (WM_USER+101)" 을 추가하고 이 메세지를 핸들링하기 위해서 커스텀 메세지 핸들러를 TForm1 클래스가 선언된 곳에 다음과 같이 추가합니다.

    void __fastcall InitDirect3D(TMessage &Msg);
    BEGIN_MESSAGE_MAP
    VCL_MESSAGE_HANDLER(WM_INITDIRECT3D, TMessage, InitDirect3D)
    END_MESSAGE_MAP(TForm)

  4. 일반적인 Direct3D 초기화 코드를 InitDirect3D 함수에 추가하세요.

  5. InitDirect3D 의 끝에 아래의 호출을 추가하세요.

    PostMessage(Handle, WM_ENTERGAMELOOP, 0, 0);

  6. WM_ENTERGAMELOOP("#define WM_USER+102" 처럼 정의할 수 있겠죠) 메세지 핸들러로 "GameLoop" 를 등록합니다.

  7. 다음과 같이 GameLoop 를 구현합니다.

    while (!UserWantsToQuit)
    {
    Application->ProcessMessages();
    RenderScene();
    }

    시선집중! : 이 게임 루프는 가능한 빠른 프레임레이트(율)을 보장할 것입니다. 게임을 위해서, TTimer 는 좋은 선택이 아니고 좋지도 않습니다.(게임면에서 보면.) ProcessMessages 를 호출하는 것은 어플리케이션이 여전히(DX 를 돌린 상황에서도) 메세지들에 반응하도록 만듭니다. 이로써 일반적인 이벤트들은 실행되겠죠.

  8. RenderScene 을 구현합니다.

  9. "bool UserWantsToQuit" 를 TForm1 클래스에 추가합니다. 생성자에서 그 변수를 false 로 설정하고 여러분이 만든 OnCloseQuery 핸들러에서 true 로 설정합니다.

  10. 일반적인 Direct3D 셧다운(메모리정리) 코드를 OnDestroy 핸들러에 추가합니다.

  11. #include <ddraw.h> 와 #include <d3d.h> 를 적었는지, dxguid.lib, ddraw.lib 과 d3dim.lib 을 링크했는지 확인합니다.

이 것이 해야할 모든 것이랍니다. 이 것은 단지 DirectDraw 와 VCL 을 어떻게 함께 사용하는지를 보여주는 간단한 예제라는 것을 기억하세요. 여러분이 만드는 어플리케이션의 종류에 따라서 "game loop" 는 필요하지 않을 수도 있습니다.

VCL 의 장점 중의 하나가 윈도우의 생성, 메세지 디스페칭, 등등을 여러분 스스로 만들지 않아도 된다는 것이죠.(비록 이런 일이 안 어렵지만 말입니다.) 예를 들어, 윈도우 형식의 어플리케이션에서는WM_RESIZE 에 반응하게 만들고 이 때마다 DirectDraw 의 서페이스(표면)를 다시 만들어야 할 것입니다. VCL 을 사용하면, 전자처럼 하는 것 대신에 OnResize 이벤트 핸들러에서 이를 구현할 수 있습니다.(OnMove 핸들러가 없는 것을 주의하세요. 이 것이 필요하다면, 위에서 사용자 정의 메세지를 다루었던 것처럼 이 메세지를 다루어 주면 됩니다.)

풀스크린 어플리케이션을 위해서 폼의 프로퍼티(속성)를 바꾸면 이것저것 할 것 없이 바로 적용됩니다.: 단지 "BorderStyle" 을 "bsNone" 으로 설정해 주기만 하면 되죠. 그리고 또 예제 프로젝트에서는 비트맵을 서페이스(표면)에 그리기 위해서 TImage 를 사용하며 그 것(TImage)은 TForm1 클래스 멤버로 구현됩니다.

C++ 빌더로 순수 WinAPI 코드 작성하기

이 것은 DirectX 를 동작시키는 특정한 방법을 말하는 것이 아닙니다. 여러분이 해야할 것은 Console Wizard 를 다시 사용하는 것입니다. "Window(GUI)" 어플리케이션을 생성하고, DirectX 도움말에서 알려주는 예제코드를 적으면 됩니다.

여러분은 "Window(GUI)" 어플리케이션에서 non-visual VCL 컴포넌트들을 사용할 수 있습니다.(Wizard 다이얼로그에서 "include the VCL" 을 선택했다면 말이죠)

The Direct3D Framework

여기서는 Direct SDK 의 많은 예제들이 구축되는 환경에 대해서 얘기합니다. 프레임워크 소스 코드는 <dxsdk>\samples\multimedia\d3dim\d3dframe 에서 찾을 수 있습니다. 헤더파일들은 <dxsdk>\samples\multimedia\d3dim\include 에 있습니다. 여러분이 이 프레임워크(d3dframe, d3dutil, d3dmath, d3dtextr, 등)를 사용하는 예제들을 빌드하려든지 또는 여러분의 어플리케이션(d3dutil 그리고 d3dmath 와 매우 비슷한)에서 이 프레임워크를 사용하려면 여러분은 적당한 .cpp 파일들을 링크해 주어야 합니다.("Add to Project..." 메뉴를 이용하세요).
그리고 또 <dxsdk>\samples\multimedia\d3dim\include 의 경로를 추가해야 하고 위에서 했던 것처럼 프로젝트의 환경을 설정해 주여야합니다.

DirectX 의 코드를 컴파일하는 당신의 능력에 영향을 줄 수 있는 C++빌더와 Visual C++ 간의 차이점

D3DFrame 얘기해볼까요... 여러분은 d3dmath, d3dtextr, 그리고 이와 유사한 것들을 사용하려 할 때 하나 또는 두가지정도의 문제에 부딪힐 것입니다.

처음 것은 컴파일러가 정의되지 않은 함수에 대해서 궁시렁 댈 것입니다. "sinf", "cosf", "tanf", 등의 함수들이 대표적이죠. 이들은 수학함수("sin", "cos", "tan" 등)들의 특별한 버젼입니다.(float 을 리턴한는...) 이 함수들은 C++ 빌더에는 없지만 보편적인 수학 함수들을 사용해서 결과값을 안전하게 "float" 형으로 캐스팅할 수 있습니다.(마치 sinf, cosf, tanf 등을 에뮬레이션 하는 것처럼 말이죠). 다음의 정의를 프로젝트 옵션에 정의하는 것이 최고의 방법입니다.("Project | Options", "Folders and Definitions" 탭)

"cosf=(float)cos;sinf=(float)sin;acosf=(float)acos;asinf=(float)asin;
tanf=(float)tan;atanf=(float)atan;sqrtf=(float)sqrt"

아니면, unit 의 헤더 파일에 #define 를 추가하여 정의할 수도 있습니다.

시선집중! : float 으로 캐스트 하는 것은 Visual C++ 어플리케이션과 비교했을 때 정밀도가 전혀 떨어지지 않습니다.

두번째 차이점은(제가 알고 있는한, d3dtextr.cpp 만 해당됩니다.) 변수 범위와 관련된 것입니다. C++ 빌더에서는 다음 코드는 유효합니다.

for (int i=0; i < 1000; i++)
DoSomething(i);
for (int i=0; i < 2000; i++)
DoSomething2(i);

Visual C++ 에서 컴파일 했을 때는 "Redefinition of 'int i'" 라는 에러를 일으키며, 다음과 같이 해석합니다.

int i;
for (i=0; i < 1000; i++)
DoSomething(i);
int i; // of course, this is a redefinition!
for (i=0; i < 2000; i++)
...

ANSI C++ 표준에 따르면, C++ 빌더가 맞는 것이고 Visual C++ 가 잘 못된 것입니다. 하지만, d3dtextr 의 프로그래머들(아마도 DX SDK 의 다른 파일들도 만든)은 Visual C++ 경우처럼 루프카운터가 루프밖에 있는 것이 유효하다고 생각했나 봅니다.

for (int i=0; i < 1000; i++)
DoSomething(i);
for (i=0; i < 2000; i++) /* 'i' is undefined. It only exists in the first
    loop. */
DoSomething2(i);
return i; /* 'i' is undefined. It only exists in the first loop. */

C++빌더는 이 코드는 컴파일 하지 못할 것입니다. 해결법은 이런 루프를 사용하는 함수의 윗부분쯤에 "int i;" 로 바꾸는 것입니다.

int i;
...
for (i=0...

시선집중! : 또한, 여러분들은 "Project | Options" 에 있는 "Advanced compiler options" 에서 "MFC compatibility" 를 활성화함으로써 Visual C++ 처럼 사용할 수 있습니다.

그 밖에 무엇이 있을까나?

이 글이 C++ 빌더에서 DirectX 를 사용하는 것에 관한 여러분들의 모든 질문에 대답할 수 있기를 바랍니다. 질문이나, 제안, 추가할 사항등이 있다면, 저에게 메일을 주세요.

Code archive : bcbddraw.zip

번역자에게 궁금한 점이 있다면 메일을 보내주세요 ;-)


DirectDraw Programming Tutorial

DirectDraw Programming Tutorial GameDev.net

DirectDraw Programming Tutorial
by Lan Mader

Contents

Overview of DirectDraw

  1. DirectX API groups
  2. What is Direct Draw
  3. Relationship to WinG
  4. DirectDraw surfaces - how you access video memory
  5. The page flipping approach to animation
  6. DirectDraw and COM

DirectDraw Programming

  1. What you need to compile and link
  2. Initializing DirectDraw
  3. Creating the primary surface and setting up for page flipping
  4. Loading a bitmap into video memory
  5. Loading the palette
  6. Color Keying
  7. Composing the Scene
  8. Cleaning up

A Few Details

  1. Debugging DirectDraw
  2. Running Windowed
  3. Losing Surfaces
  4. Further Reading

Overview of DirectDraw

이 섹션에서는 DirectDraw 를 살펴보며, DirectDraw 구조를 이해하기 위한 개념을 설명합니다.

1. DirectX API groups

마이크로소프트의 DirectX API 들은 다음의 그룹들로 이루어져 있습니다:

  • DirectDraw - 비디오 메모리에 다이렉트로 접근할 수 있습니다.
  • DirectSound - 사운드 하드웨어 장치에 다이렉트로 접근할 수 있습니다.
  • DirectPlay - 네트워크로 연결된 멀티플레이 게이밍을 지원합니다.
  • DirectInput - 조이스틱과 같은 게임 입력 장치들을 지원합니다.

이들 모두는 프로그래머에게 하드웨어 장치에 다이렉트로 접근할 수 있는 API 를 제공하도록 설계되었습니다. 이 글은 DirectDraw 기능들을 사용하는 기술에 초점을 맞추어 여러분들이 예술적인 프로그램을 빠르게 만들 수 있도록 도와 줄 것입니다.

2. What is DirectDraw

DirectDraw 는 본질적으로 얘기하자면 비디오 메모리 관리자입니다. DirectDraw 의 가장 중요한 기능은 프로그래머에게 비디오 메모리에 다이렉트로 접근해 비트맵을 저장하고 다룰 수 있게 하는 것입니다. DirectDraw 는 비디오 메모리 안에 비트맵들을 블릿할 수 있는 비디오 하드웨어 블릿터의 유리한 점을 제공해줍니다. 비디오 하드웨어의 블릿터를 사용하여 비디오 메모리에서 비디오 메모리로 블릿하는 것은 시스템 메모리에서 비디오 메모리로 블릿하는 것보다 훨씬 빠릅니다. 이 것은 오늘 날의 비디오 메모리에 64비트 데이타 경로를 제공하는 64비트 비디오 카드에서 특히 잘 나타납니다. 또한, 하드웨어 블릿 기능은 CPU 에 상관없이 수행되는 지라 CPU 는 하던 일을 계속해서 할 수 있습니다. 게다가, DirectDraw 는 비디오 카드의 하드웨어 가속( 예를 들면 스프라이트와 z-버퍼링을 지원하는 하드웨어 )을 제공합니다. DirectDraw 1.0 은 현재 Windows 95 에서 작동되고 이번 여름에는 Windows NT 에서 가능하도록 만들어 질 것입니다.( 너무 늦은 번역이었군요 -_-;; )

3. Relationship to WinG

지금까지는 윈도우즈 운영체제에서 빠른 에니메이션을 위해서 "WinG" 또는 "CreateDIBSection" 을 사용했었습니다. 이 것은 프로그래머에게 시스템 메모리 안의 비트맵에 다이렉트 접근할 수 있게 해 주었으며 그로인해 비트맵을 그리는 최적화된 루틴을 사용할 수 있었습니다. "WinG" 는 비트맵을 그리는 모든 연산에서 "GDI" 를 사용하는 것보다 더 나았습니다. 그 때문에 "GDI" 는 최적화된 루틴을 사용하는 게임 프로그래머들에게는 항상 찬밥신세였습니다.

"WinG" 에 의한 그림들은 비트맵으로 이루어진 것으로, 시스템 메모리에서 비디오 메모리로 블릿이 되야 화면에 출력되었습니다. 이 기술은 "DirectDraw" 보다 빠르진 않은데 그 것은 시스템 메모리에서 비디오 메모리로 블릿하는 것이 비디오 메모리에서 비디오 메모리로 블릿하는 것보다 훨씬 느렸기 때문입니다. "DirectDraw" 와 "WinG" 기술 둘다 수 많은 복잡한 게임들 속에서 또는 비디오 메모리를 한정된 리소스로 사용하는 어플리케이션들 속에서 계속 공존할 것입니다.

WinG 의 소개

이전 윈도우즈 환경에서는 게임이나 고급 멀티미디어를 개발하기 위한 충분한 그래픽 운영 속도를 제공하지 못했다. 고작해야 카드놀이와 같은 아주 간단하고 속도에 관계없는 게임이나 스프레트시트 등에 적합했다. 때문에 많은 프로그래머들이 이전 DOS 환경을 고집하고 그 환경에서 동작하는 응용프로그램을 개발했다. 마이크로소프트는 이러한 상황의 대안으로 초기 윈도우즈 운영제체에 "WinG" 라는 솔루션을 내놓았지만, 이 또한 그래픽 프로그래머들의 만족을 사지 못 했다. 윈도우95에 이르러 마이크로소프트는 DirectX 라는 솔루션을 발표했다. < DirectX 7.0 온라인 도움말에서... >

4. DirectDraw surfaces - how you access video memory

DirectDraw 는, 가능한한 많은 비트맵들을 비디오 메모리에 출력하는 것이 목표입니다. DirectDraw 를 사용함으로써, 비디오 메모리의 모든 부분을 사용할 수 있습니다. 여러분들은 DirectDraw 를 여러 비트맵들, 그리고 프라이머리 그리고 오프스크린 비디오 버퍼를 저장하는데 사용할 수 있습니다. 비디오 메모리의 조각조각들 모두를 DirectDraw 에서는 표면("surface")으로 부릅니다. 우리가 스프라이트를 표현할 비트맵을 로드하고, 그 비트맵을 비디오 메모리에 저장할 때는, 우선 비디오 메모리의 조각인 표면("surface")을 만들고, 이 표면("surface")으로 비트맵을 블릿해야합니다. 그러면, 여러분들이 원하는 한, 이 비트맵들은 우리가 사용할 수 있게 끔 비디오 메모리에 존재하고 있답니다.

현재 스크린에 무엇인가를 보여주고 있는 비디오 메모리도 또한 표면("surface") 입니다, 이것을 주표면("primary surface")이라고 부릅니다. 이 표면("surface")은 현재 디스플레이 모드를 위한 거대한 메모리 조각입니다. 만약에 디스플레이 모드가 640x480 에 256 컬러라면(8 bits per pixel), 주표면("primary surface") 는 307,200 byte 의 비디오 메모리를 사용합니다. 여러분들은 대개 페이지 플립핑(뒤집기)을 위한 주표면과 똑같은 크기의 오프스크린 표면을 적어도 하나는 만들것입니다. 이 말의 의미는 여러분들은 시작시에 아무런 비트맵도 로딩하지 않았는데, 614,400 byte 의 디스플레이 메모리가 필요하다는 것입니다.

그래서, 빠른 DirectDraw 게임들을 위해서 하드웨어의 요구사항 중 가장 중요한 것은 많은 비디오 메모리입니다. 우리가 비디오 메모리를 다 사용했을 때는, DirectDraw 표면들은 시스템 메모리에 생성될 것입니다. 이 때, 이 표면들에서는 하드웨어 블릿의 모든 장점들이 무용지물 됩니다. 오늘날의 비디오 카드들은 적어도 1M 의 비디오 메모리를 가지고 있습니다, 하지만 1M 는 아주 작은 양입니다. 2M 정도가 DirectDraw 를 사용할 수 있는 최소한도 일 것입니다.

DirectDraw 는 런타임으로 비디오 메모리를 정하는 기능들을 제공하기 때문에, 어플리케이션은 비디오 메모리를 최적화하여 사용할 수 있습니다.

5. The page flipping approach to animation

하나의 장면은 비디오 메모리 버퍼(표면)로부터 비디오 메모리의 또다른 오프스크린 버퍼(표면)로 비트맵을 블릿하는 것으로 만들어집니다. 이렇게 된 장면은 다음에 현재의 주표면과 오프스크린의 표면을 플립함(뒤바꿈)으로써 우리에게 새로운 장면으로 보여지는 것입니다. 이 것은 페이지 플립핑으로 알려져 있으며 매우 빠르게 동작합니다. 페이지 플립은 빠른데 그 것은, 메모리 복사가 이루어 지는 것이 아니기 때문입니다. 그것은 비디오 카드의 레지스터(모니터에 보내는 신호로 사용될 비디오 메모리의 스캔 주소를 알려주는 레지스터)를 셋팅하는 간단한 것입니다. DirectDraw 는 하드웨어의 특정적인 이러한(비디오의 레지스터를 설정하는) 연산들로부터 프로그래머를 보호(프로그래머가 각각의 하드웨어마다 구현하지 않아도 된다.)하고, 이러한 일을 할 수 있게 Flip() API 함수를 제공합니다. 페이지 플립핑으로 에니메이션을 만들기 위해서는 다음과 같이 합니다.

  1. 오프스크린 표면에 장면을 구성합니다.
  2. 오프스크린의 표면을 현재 디스플레이 되고 있는 주 표면과 플립합니다.
  3. 주 표면은 지금 오프스크린 표면이 되었습니다.
  4. 1단계부터 다시 반복하세요.

페이지 플립핑을 위한 프레임 비율은 매우 높지만, 페이지 플립이 오직 수직 귀선 신호에서만 일어나기 때문에 이 것(프레임 비율)은 모니터의 리프레쉬 비율에 의해서 정해집니다. 이 말 뜻은 여러분들의 모니터가 72 Hz 의 리프레쉬 비율이라면, 이론적으로 초당 72 프레임까지 프레임비율을 올릴 수 있다는 것입니다. 수직 귀선을 기다리는 것은 좋은 것입니다. 왜냐하면 우리들는 DirectDraw 의 찢어지는 페이지 플립핑을 보지 않아도 되기 때문입니다. 찢어지는(Tearing) 것은 여러분이 스크린의 리프레쉬를 통해서 스크린에 스프라이트(작은 그림)를 옮길 때 목격되는 현상입니다. 그 스프라이트가 새로운 위치로 이동될 때 찢어지게 보입니다. 예를 들면, 이미지의 꼭대기의 반은 새로운 위치에 나타날 것이고, 반면 아래쪽의 반은 오른쪽 또는 왼쪽으로 옮겨진 채로 나타날 것입니다( 이미지가 깨지거나 또는 좌우로 찢어진 상태 ).

그래픽스 프로그래머들은 스프라이트의 최적화된 성능을 위해서 몇몇 복잡하고 미묘한 기법들을 개발했고, 이 기술들은 DirectDraw 어플리케이션 안에서 여전히 사용할 수 있습니다. 하지만, 이런한 기술을 배우는 것은 DirectDraw 를 이해하고 배우는데는 도움을 주지 않습니다. 그래서, 필자는 에니메이션을 위한 페이지 플립핑에 초점을 맞추어 이 글을 쓸 것입니다. 페이지 플립핑은 에니메이션에 다가가기 위한 가장 쉬운 방법일뿐만 아니라 DirectDraw 가 동작하는 방법을 배울 수 있는 가장 좋은 방법입니다.

6. DirectDraw and COM

DirectX 는 COM 을 사용하여 구현 되었습니다, DLL 안에서 COM 을 사용하는 것은 다형성과 버전닝을 포함하여 일반 DLL 로 부터 API 들을 노출 시키는 것보다 더 많은 장점이 있습니다. DirectDraw 프로그래머가 알아야할 가장 중요한 것은 다른 API 를 사용하는 것과 같이 C++ 이나 Object Pascal 어플리케이션 안에서 COM 객체들을 사용할줄 아는 것입니다.

여러분들은 COM 을 초기화하고, 또는 인터페이스를 얻기 위해서 QueryInterface 를 호출하는 것에 쩔쩔맬 필요가 없습니다. DirectDraw 헤더 파일은 여러 DirectX 객체들을 위해서 C++ 클래스들을 선언해 두었습니다. 이 클래스들의 인스턴스는 여러 생성 함수들을 호출해서 만들 수 있습니다. 그것은 여러 멤버 함수들을 배우는 것처럼 간단한 것입니다.

DirectDraw Programming

이 섹션은 DirectDraw 를 사용하는 방법을 설명하는 예제 코드를 보여줄 것입니다. 예제 코드는 완전히 기능적(설계등이 없는 코드)이지만, 가능한한 가려운 곳을 긁어주는 예제가 될 것입니다.

1. What you need to compile and link

제일 먼저, 여러분들은 Direct SDK 를 가지고 있어야 합니다. 이 것은 online MSDN 에서 받을 수 있습니다. SDK 는 세세한 도움말 파일과 아주 좋은 예제 프로그램들을 제공합니다.

DirectDraw 를 컴파일하고 링크하기 위해서는, DDRAW.DLL, DDRAW.H 가 필요하고 IMPLIB.EXE 로 DDRAW.DLL 을 임포트하기 위한 라이브러리 파일을 만들어야합니다.(현 버전에서는 다 있습니다.) DirectX 가 Win32 기술이기 때문에, 여러분들은 Win32 어플리케이션을 만들 수 있는 컴파일러가 필요합니다. Borland C++ 4.52 그리고 Borland C++ 5.0 은 DirectX 개발에 아주 좋은 환경을 제공합니다.

DirectX 기술을 구동시키기 위해서, 여러분들은 여러분들의 시스템에 DirectX 드라이버를 반드시 설치해야 합니다. 이 것은 게임 기술에 공통적으로 사용되기 때문에, 여러분들의 시스템에는 이미 DirectX 가 설치되어 있을 것입니다. "Windows/System" 디렉토리를 살펴서 이미 설치가 되어 있다면 DDRAW.DLL 을 복사하세요.(이 기술은 윈도우즈NT 의 3.X 버전대에서는 작동하지 않는 것을 기억하세요)

지금은, 단지 SETUP.EXE 만 누르면 모든 것이 해결되죠! ^_^

2. Initializing DirectDraw

DirectDraw 를 배우는 첫단계는 DirectDraw 를 초기화 시키는 방법입니다. 다음 코드에서 각각의 스텝을 자세히 설명하는 주석을 읽어주세요.

// globals (ugh)
LPDIRECTDRAW lpDD; // DirectDraw 객체는 DDRAW.H 안에 정의되어 있습니다.

/*
* DirectDraw 초기화 함수
* 설명:
* 1) DirectDraw 객체를 생성한다.
* 2) 협력 레벨(Cooperative level)을 설정한다.
* 3) 디스플레이 모드(Display mode)를 설정한다.
*
*/
bool DirectDrawInit(HWND hwnd)
{
HRESULT ddrval;

        /*
* 메인 DirectDraw 객체를 생성한다.
*
* 이 함수는 COM 을 안전하게 초기화 하고 DirectDraw 객체를
* 생성합니다.
*/
ddrval = DirectDrawCreate( NULL, &lpDD,
    NULL );
if( ddrval != DD_OK )
{
return(false);
}

/*
 * 협력 레벨은 스크린을 어느정도 컨트롤 할 것인지를 결정하는 것입니다.
 * 이 협력 레벨은 적어도 DDSCL_EXCLUSIVE 또는 DDSCL_NORMAL 이 되어야 합니다.
 *
 * DDSCL_EXCLUSIVE 는 비디오 모드를 바꿀 수 있게 해줍니다
 * ( 풀스크린 윈도우를 제공하는DDSCL_FULLSCREEN 플래그를 같이 설정해 주어야 합니다. )
* 이 DDSCL_EXCLUSIVE 모드는 선호되는 모드인데
 * 그것은 GDI 에 의존하지 않고 전체 스크린을 컨트롤 할 수 있기 때문입니다.
 *
 * DDSCL_NORMAL 은 일반 윈도우 상태에서 DirectDraw 를 사용할 수 있게 해 줍니다.
 */
ddrval = lpDD->SetCooperativeLevel( hwnd, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN );
if( ddrval != DD_OK )
{
lpDD->Release();
return(false);
}

        /*
* 비디오 모드를
    640x480x8 로 설정합니다.
* 우리가 위해서
    exclusive 모드를 설정했기 때문에 비디오 모드는 바뀔 것입니다.
*/
ddrval = lpDD->SetDisplayMode( 640,
    480, 8);
if( ddrval != DD_OK )
{
lpDD->Release();
return(false);
}

return(true);
}

우리는 지금 DirectDraw 를 초기화 했고 디스플레이 모드를 설정했습니다.

3. Creating the primary surface and setting up for page flipping

다음으로 우리는 백버퍼와 함께 주 표면을 생성합니다. 이 것은 DirectDraw 에서 복합체 표면이라고 불립니다. 백버퍼는 주 표면과 연결되어 있습니다. 프로그램의 끝에서 메모리를 반환 할 때, 우리는 주표면의 포인터로 Release 를 호출해주면 됩니다, 그러면 백버퍼는 자동적으로 메모리가 해제됩니다.

// globals (ugh)
LPDIRECTDRAWSURFACE lpDDSPrimary; // DirectDraw primary surface
LPDIRECTDRAWSURFACE lpDDSBack; // DirectDraw back surface

/*
* Create a Primary surface that is flippable, with an
* attached backbuffer
*/
bool CreatePrimarySurface()
{
DDSURFACEDESC ddsd;
DDSCAPS ddscaps;
HRESULT ddrval;

// Create the primary surface with 1 back buffer
memset( &ddsd, 0, sizeof(ddsd) );
ddsd.dwSize = sizeof( ddsd );

       ddsd.dwFlags = DDSD_CAPS
    | DDSD_BACKBUFFERCOUNT;
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE
    | DDSCAPS_FLIP | DDSCAPS_COMPLEX;
ddsd.dwBackBufferCount = 1;

       ddrval = lpDD->CreateSurface(
    &ddsd, &lpDDSPrimary, NULL );
if( ddrval != DD_OK )
{
lpDD->Release();
return(false);
}

       //
    Get the pointer to the back buffer
ddscaps.dwCaps = DDSCAPS_BACKBUFFER;
ddrval = lpDDSPrimary->GetAttachedSurface(&ddscaps,
    &lpDDSBack);
if( ddrval != DD_OK )
{
lpDDSPrimary->Release();
lpDD->Release();
return(false);
}

return true;
}

우리는 지금 더블버퍼링의 페이제 플립핑 환경을 만들었습니다.

4. Loading a bitmap into video memory

다음 순서는 게임을 위한 비트맵들을 로드하는 것입니다. 비디오 메모리가 충분하다면 모든 비트맵들은 비디오 메모리로 로드 될 것입니다. 만약 그렇지 않다면, CreateSurface 는 시스템 메모리에 표면을 만들것입니다. 어디의 메모리 표면이 되었던 간에 작업은 계속될 것이지만, 시스템 메모리에 만들어진 표면으로부터 블릿하는 것은 약간 느리게 동작될 것입니다.

/*
* Function to create an offscreen surface and load a bitmap from
* disk into it. The szBitmap field is the filename of the Bitmap.
*/
IDirectDrawSurface * DDLoadBitmap(IDirectDraw *pdd, LPCSTR szBitmap)
{
HBITMAP hbm;
BITMAP bm;
IDirectDrawSurface *pdds;

// LoadImage has some added functionality in Windows 95 that allows
// you to load a bitmap from a file on disk.
hbm = (HBITMAP)LoadImage(NULL, szBitmap, IMAGE_BITMAP, 0, 0,
LR_LOADFROMFILE|LR_CREATEDIBSECTION);

if (hbm == NULL)
return NULL;

GetObject(hbm, sizeof(bm), &bm); // get size of bitmap

/*
* create a DirectDrawSurface for this bitmap
* source to function CreateOffScreenSurface() follows immediately
*/
 pdds = CreateOffScreenSurface(pdd, bm.bmWidth, bm.bmHeight);
if (pdds) {
DDCopyBitmap(pdds, hbm, bm.bmWidth, bm.bmHeight);
}

DeleteObject(hbm);

return pdds;
}

/*
* Creates a DirectDraw Surface of a specified size
* This surface will be in video memory if enough is
* available, otherwise it will be created in system memory
*/
IDirectDrawSurface * CreateOffScreenSurface(IDirectDraw *pdd, int dx, int dy)
{
DDSURFACEDESC ddsd;
IDirectDrawSurface *pdds;

//
// create a DirectDrawSurface for this bitmap
//
ZeroMemory(&ddsd, sizeof(ddsd));
ddsd.dwSize = sizeof(ddsd);
ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT |DDSD_WIDTH;
ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
ddsd.dwWidth = dx;
ddsd.dwHeight = dy;

       if (pdd->CreateSurface(&ddsd,
    &pdds, NULL) != DD_OK)
{
return NULL;
} else {
return pdds;
}
}

/*
* This function copies a previously loaded bitmap into a DirectDraw surface.
* Notice that we can obtain a GDI style device context for a
* DirectDraw surface, with which to BitBlt the bitmap into the
* surface.
*/
HRESULT DDCopyBitmap(IDirectDrawSurface *pdds, HBITMAP hbm, int dx, int dy)
{
HDC hdcImage;
HDC hdc;
HRESULT hr;
HBITMAP hbmOld;

       //
// select bitmap into a memoryDC so we can
    use it.
//
hdcImage = CreateCompatibleDC(NULL);
hbmOld = (HBITMAP)SelectObject(hdcImage, hbm);

       if ((hr = pdds->GetDC(&hdc))
    == DD_OK)
{
BitBlt(hdc,
    0, 0, dx, dy, hdcImage, 0, 0, SRCCOPY);
pdds->ReleaseDC(hdc);
}

SelectObject(hdcImage, hbmOld);
DeleteDC(hdcImage);

return hr;
}

지금 우리는 DirectDraw 표면에 비트맵을 로드하였습니다. 우리는 이 표면을 나중에 장면을 구성할 때 사용할 것입니다.

5. Loading the palette

우리는 주표면을 위해서 팔레트를 설정할 필요가 있습니다. 게임을 위한 모든 비트맵들은 같은 팔레트로 만들어 졌을 것입니다. 여기에 주어진 비트맵을 위한 DirectDraw 팔레트를 만드는 함수가 있습니다.

/*
* Creates a DirectDraw palette for a given bitmap on disk.
* The parameter szBitmap is the file name of the bitmap.
*
*/
IDirectDrawPalette * DDLoadPalette(IDirectDraw *pdd, LPCSTR szBitmap)
{
IDirectDrawPalette* ddpal;
int i;
int n;
int fh;
PALETTEENTRY ape[256];

       if (szBitmap &&
    (fh = _lopen(szBitmap, OF_READ)) != -1)
{
BITMAPFILEHEADER
    bf;
BITMAPINFOHEADER
    bi;

_lread(fh, &bf, sizeof(bf));
_lread(fh, &bi, sizeof(bi));
_lread(fh, ape, sizeof(ape));
_lclose(fh);

if (bi.biSize != sizeof(BITMAPINFOHEADER))
n = 0;
else if (bi.biBitCount > 8)
n = 0;
else if (bi.biClrUsed == 0)
n = 1 << bi.biBitCount;
else
n = bi.biClrUsed;

            //
// a DIB color
    table has its colors stored BGR not RGB
// so flip
    them around.
//
for(i=0; i<n;
    i++ )
{
BYTE r = ape[i].peRed;
ape[i].peRed = ape[i].peBlue;
ape[i].peBlue = r;
}
}

if (pdd->CreatePalette(DDPCAPS_8BIT, ape,
    &ddpal, NULL) != DD_OK)
{
return NULL;
} else {
return ddpal;
}
}

다음에 우리는 이 팔레트를 주표면에 다음과 같이 설정해야 합니다.:

lpDDPal = DDLoadPalette(lpDD, szBitmap); // Call the function above to load the palette

if (lpDDPal)
lpDDSPrimary->SetPalette(lpDDPal);
    // this sets the palette for the primary surface

6. Color Keying

DirectDraw 는 블릿을 하는 동안에 투명 컬러를 사용하기 위한 것으로 "Color Key" 란 개념을 사용합니다. 이 것은, 여러분들이 비트맵의 특정 부분을 투명으로 나타낼 수 있게 해줍니다. 이 것은 백그라운드 표면에 스프라이트를 블릿팅할 때 사용할 수 있는 유용한 기술입니다. 컬러키는 소스 비트맵 또는 목적 비트맵의 팔레트 엔트리 범위에 있는 특정 값으로 블릿이 되는 동안에 복사가 되지 않을 것입니다. 전형적으로 여러분들은 소스비트맵의 투명 컬러를 위해서 0 또는 255 의 팔레트 엔트리를 설정할 것입니다. 그리고 투명해져야할 곳이 있으면 팔레트의 이 색깔을 사용해서 투명하게 만들며 비트맵을 구성할 것입니다.

// Set the color key for this bitmap.
//
// Whatever color is at entry 255 in the palette will be the
// transparent color for blits. This color is black unless
// you used the DDPCAPS_ALLOW256 flag when the palette was
// created

DDCOLORKEY ddck;
ddck.dwColorSpaceLowValue = 0xff;
ddck.dwColorSpaceHighValue = 0xff;

// lpDDSSomeBitmapSurface is a DirectDraw
    surface with a
// bitmap loaded into it. This needs to be done for each
// surface containing a bitmap.
lpDDSSomeBitmapSurface->SetColorKey( DDCKEY_SRCBLT, &ddck );

7. Composing the Scene

지금 우리는 게임, 시뮬레이션 또는 다른 그래픽스에 기초한 프로그램들을 만들 준비가 되었습니다. 간단히 해볼 만한 것은 백그라운드 비트맵을 백버퍼에 블릿하고, 그 다음 몇몇 스프라이트들을(또는 단순한 비트맵들) 백버퍼의 적당한 위치에 블릿합니다. 그다음 플립합니다. 이 순서들을 반복하면서 스프라이트들을 새로운 위치로 블릿팅합니다.

// This function should be called repeatedly during the idle time of your
// PeekMessage()loop.

void updateFrame( void )
{
RECT rcRect;
HRESULT ddrval;
int xpos, ypos;

// Blit the stuff for the next frame
SetRect(&rcRect, 0, 0, 640, 480);

// blit the background bitmap. This is a 640x480 bitmap,
// so it will fill the screen (remember, we set the video
// mode to 640x480x8.
// The parameter lpDDSOne is a hypothetical surface with this
// background bitmap loaded. LpDDSBack is our backbuffer.
ddrval = lpDDSBack->BltFast( 0, 0, lpDDSOne, &rcRect,
DDBLTFAST_NOCOLORKEY | DDBLTFAST_WAIT);

       if( ddrval != DD_OK )
{
return;
}
SetRect(&rcRect, 0, 0, 32, 32); // our
    fictitious sprite bitmap is 32x32 in size

       //
    xpos and ypos represent some appropriate location for the sprite.
// lpDDSMySprite is just a surface with the
    bitmap for our sprite.
ddrval = lpDDSBack->BltFast( xpos, ypos,
    lpDDSMySprite,
&rcRect, DDBLTFAST_SRCCOLORKEY | DDBLTFAST_WAIT
    );
if( ddrval != DD_OK )
{
return;
}

// Flip the surfaces
ddrval = lpDDSPrimary->Flip( NULL, DDFLIP_WAIT );
} /* updateFrame */

여기서 관심있게 봐야할 것은 두가지입니다. BltFast() 와 Filp() 을 호출할 때, 마지막 인자가 DDXX_WAIT 플래그를 포함하고 있습니다. 이 것은 이 기능들이 각각 비동기적이기 때문에 필요합니다. 이 함수들은 기능이 성공적으로 시작 되었을 때(하지만 아직 끝난 것은 아닌), 또는 에러가 발생해서 성공적으로 시작하지 못 했을 때 값을 반환합니다. 가장 평범한 에러는 이전 연산을 수행 완료한 표면이 현재 사용중 임을 나타내는 에러입니다. 여러분들은 DDXX_WAIT 플래그를 사용하여 DirectDraw 에게 표면을 사용중일 때, 또는 다른 에러가 발생할 때 다음 연산을 대기 시키라고 일러줍니다.

BltFast() 의 첫 함수 호출을 보면, 백그라운드에 블릿팅하는 것으로, DDBLTFAST_NOCOLORKEY 플래그를 사용하고 있습니다. 이 것은 BltFast 가 컬러키를 무시해서, 블릿의 성능을 개선할 수 있도록하는 것입니다. 이 말의 의미는 이 비트맵의 투명한 영역은 비투명으로 복사가 되어 버리지만, 백그라운드 비트맵에서는 어짜피 사용자의 눈에 보이지 않기 때문에 괜찮습니다.

Flip() 함수가 호출되었을 때, 비디오 메모리인 표면 객체들은 서로 스왑(바뀜)됩니다. 이 말의 의미는 주버퍼는 백버퍼가 되고 백퍼버가 주버퍼가 된다는 것입니다. 결과는 장면을 구성하기 위해서 백버퍼 포인터를 사용하면서 보존할 수 있다는 것이며, 그래서 버퍼가 어디에 있는지를 담은 정보를 유지할 걱정 없이, Flip() 함수를 호출할 수 있다는 것입니다.

8. Cleaning up

DirectX 를 깨끗이 정리하기 위해서, 중요한 것은 여러분이 만든 모든 DirectX 객체들에서 Release() 멤버 함수를 호출하는 것입니다. 다음과 같이 하세요.:

/*
* Call Release on all objects we created to clean up
*/
void finiObjects( void )
{
if( lpDD != NULL )
{
if( lpDDSPrimary
    != NULL )
{
lpDDSPrimary->Release();
lpDDSPrimary = NULL;
}

             if(
    lpDDSOne != NULL )
{
lpDDSOne->Release();
lpDDSOne = NULL;
}

             if(
    lpDDPal != NULL )
{
lpDDPal->Release();
lpDDPal = NULL;
}
lpDD->Release();
lpDD = NULL;
} /* finiObjects */

살아있는 DirectDraw 객체에서 release 함수를 호출할 때, 참조 카운트는 0으로 될 것이고, 객체는 소멸될 것입니다. 이 때에(객체가 소멸될 때)는, 스크린의 오리지날 모드로 다시 돌아옵니다.

A Few Details

1. Debugging DirectDraw

DirectDraw 어플리케이션이 풀스크린으로 동작중일 때에는, GDI 를 사용할 수 없습니다. 이 말의 의미는 여러분의 어플리케이션에서 브레이크포인트를 설정할 수 없으며, 브레이크포인터에 도달했을 때 디버거를 볼 수 없다는 것을 말합니다. 듀얼모니터,또는 리모트 디버깅 등의 디버거를 포함한 좋은 솔루션들이 몇 있지만, 간단하게 할 수 있는 것은 윈도우 모두에서 실행하면서 디버깅을 하고 나중에 풀스크린모드로 변환하는 것입니다. 윈도우 모드에서 잘 동작되는 것은 풀스크린에서도 잘 동작하니까 말이죠.

2. Running Windowed

여러분들이 윈도우 모드로 실행하고 싶을 때는 DirectDraw 의 초기화를 다르게 해야합니다. GDI 가 같이 사용되는 것이기 때문에, 디스플레이 모드들 바꾸는 것은 허용되지 않습니다. 지켜야할 가장 중요한 것은 주 표면을 생성할 시, 현재 보이는 그리고 GDI 가 사용하고 있는 디스플레이 메모리의 포인터를 얻는 것입니다. 여러분이 원한다면, 여러분들은 스크린의 아무곳에나 갈겨 쓸 수가 있습니다. 이렇기 때문에 여러분들은 윈도우 안에 그릴 때 매우 주의해야 합니다. DirectDraw 클립퍼를 사용하면 도움이 될 것입니다.

다음 함수는 DirectDraw 를 윈도우 모드로 초기화하고 페이지 플립핑을 사용하지 않습니다.

bool IsWindowed = false;

// function to initialize DirectDraw
    in windowed mode
bool DDGameInitWindowed(THIS_ HWND hwnd)
{
DDSURFACEDESC ddsd;
DDSCAPS ddscaps;
HRESULT ddrval;

IsWindowed = true;

       /*
* create the main DirectDraw object
*/
ddrval = DirectDrawCreate( NULL, &lpDD,
    NULL );
if( ddrval != DD_OK )
{
return(false);
}

       //
    using DDSCL_NORMAL means we will coexist with GDI
ddrval = lpDD->SetCooperativeLevel( hwnd,
    DDSCL_NORMAL );
if( ddrval != DD_OK )
{
lpDD->Release();
return(false);
}

       memset( &ddsd, 0,
    sizeof(ddsd) );
ddsd.dwSize = sizeof( ddsd );
ddsd.dwFlags = DDSD_CAPS;
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;

       //
    The primary surface is not a page flipping surface this time
ddrval = lpDD->CreateSurface( &ddsd,
    &lpDDSPrimary, NULL );
if( ddrval != DD_OK )
{
lpDD->Release();
return(false);
}

       //
    Create a clipper to ensure that our drawing stays inside our window
ddrval = lpDD->CreateClipper( 0, &lpClipper,
    NULL );
if( ddrval != DD_OK )
{
lpDDSPrimary->Release();
lpDD->Release();
return(false);
}

       //
    setting it to our hwnd gives the clipper the coordinates from our window
ddrval = lpClipper->SetHWnd( 0, hwnd );
if( ddrval != DD_OK )
{
lpClipper->
    Release();
lpDDSPrimary->Release();
lpDD->Release();
return(false);
}

       //
    attach the clipper to the primary surface
ddrval = lpDDSPrimary->SetClipper( lpClipper
    );
if( ddrval != DD_OK )
{
lpClipper->
    Release();
lpDDSPrimary->Release();
lpDD->Release();
return(false);
}

       memset( &ddsd, 0,
    sizeof(ddsd) );
ddsd.dwSize = sizeof( ddsd );
ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH;
ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
ddsd.dwWidth = 640;
ddsd.dwHeight = 480;

       //
    create the backbuffer separately
ddrval = lpDD->CreateSurface( &ddsd,
    &lpDDSBack, NULL );
if( ddrval != DD_OK )
{
lpClipper->
    Release();
lpDDSPrimary->Release();
lpDD->Release();
return(false);
}
return(true);
}

이 코드와 이전 코드간의 첫 차이점은 DirectDraw 를 SetCooperativeLevel() 을 DDSCL_NORMAL 플래그와 함께 호출하여 초기화 하는 것입니다. 이 말의 의미는 우리가 디스플레이를 완전히 제어할 수 없다는 것을 말합니다. 그러므로 그림으로부터 GDI 를 제거하지 못 합니다. 주 표면을 생성할 때, 복합 표면을 만들 수 없습니다. 그렇기 때문에 페이지 플립핑을 준비하지 못 합니다.

다음은, 윈도우를 위한 DirectDraw 클리퍼를 생성하고 그것을 주 표면에 붙이는 것입니다. DirectDraw 클리퍼는 클리핑을 위한 사각형의 리스트를 사용하는 객체입니다. 윈도우 핸들에 클리퍼를 설정함으로써, 우리들은 자르기 위한 사각형 영역을 사용하여 윈도우의 클라이언트 영역을 자르라고 암암리로 말합니다. 다음, 이 것을 주 표면에 설정합니다. 그리해서 윈도우의 클립핑된 클라이언트 영역에 블릿되도록 합니다.

우리는 백버퍼를 따로 생성하며, 이 때 반드시 크기를 정해야합니다.

우리가 다르게 다루어야할 코드는 Flip() 을 호출하는 곳이며, 이 대신에 블릿을 사용합니다. 여러분들은 다음과 같이 Flip 함수를 만들 수 있습니다.

bool MyFlip()
{
HRESULT ddrval;
RECT rcRectSrc;
RECT rcRectDest;
POINT p;

       //
    if we're windowed do the blit, else just Flip
if (IsWindowed)
{
//
    first we need to figure out where on the primary surface our window lives
p.x = 0; p.y
    = 0;
ClientToScreen(ddWnd,
    &p);
GetClientRect(ddWnd,
    &rcRectDest);
OffsetRect(&rcRectDest,
    p.x, p.y);
SetRect(&rcRectSrc,
    0, 0, 640, 480);
ddrval = lpDDSPrimary->Blt(
    &rcRectDest, lpDDSBack, &rcRectSrc, DDBLT_WAIT, NULL);
} else {
ddrval = lpDDSPrimary->Flip(
    NULL, DDFLIP_WAIT);
}

return (ddrval == DD_OK);
}

3. Losing Surfaces

위의 소스코드를 제외하고, 매우 중요한 것은 '만약 풀스크린 DirectDraw 어플리케이션에서 시스템의 다른 어플리케이션으로 바꾸었을 때 무엇이 발생할 것인가?' 라는 것입니다. 무엇보다도 먼저, 여러분들은 WM_ACTIVEAPP 메세지를 핸들링할 코드가 필요하며 여러분의 어플리케이션이 비활성화되었을 때 플래그를 설정해야합니다. PeekMessage 루프의 Idle 타임에, 이 플래그를 검사하여, 어플리케이션이 비활성화일때 업데이트하지 않도록 해야합니다.

하지만 진짜 이야깃꺼리는 다른 어플리케이션으로 변환되었을 때입니다, 여러분들은 DirectDraw 환경에서 벗어나고 어플리케이션이 활성화 되면서 GDI 를 사용할 것입니다. DirectDraw 에서 잘 잊혀지는 존재인 GDI 는 , 사용중인 DirectDraw 의 표면에 디스플레이 메모리 영역을 다시 쓸 것입니다. 이 말의 의미는 여러분들의 비트맵은 깨끗이 지워질 것이고 딸서 다시 비트맵을 로드해야 된다는 것입니다. 운좋게도, 표면상에 Flip() 또는 BltFlip() 등의 기능을 하는 함수를 호출할 때, DirectDraw 는 DDERR_SURFACELOST 라는 에러를 반환해서 여러분들이 표면을 잃어버렸음을 알려줍니다. 그래서, 여러분들의 코드는 이 에러를 검사하여야 하고 이 에러가 발생했을 때에는 표면으로 비트맵을 다시 로드해야합니다.

ddrval = lpDDSPrimary->Blt( &rcRectDest, lpDDSBack, &rcRectSrc,
    DDBLT_WAIT, NULL);
if( ddrval == DDERR_SURFACELOST )
{
ddrval = restoreAll();
}

ddrval = lpDDSPrimary->Flip( NULL, DDFLIP_WAIT);

if( ddrval == DDERR_SURFACELOST )
{
ddrval = restoreAll();
}
:
:

void restoreAll()
{
// for each
    surface your app has created you should do the following:
ddrval = lpMyDDSurface->Restore(); //
    this reattaches the video memory to the surface
if( ddrval == DD_OK )
{
lpTempDDS ->DDReLoadBitmap();
    // this will be the same as the function
    above
//
    that was originally used to load the bitmap
// with the exception that the
    surface is already
// created and ready to go
}
}

4. Further Reading

읽어봐야할 것중에서 최고를 뽑으라면, DirectX SDK 와 함게 오는 예제코드를 추천하겠습니다. 이 예제 코드들은 이 글에서 다루지 않았던 몇몇 내용들도 세세하게 다루고 있습니다. 전 아직 API 차원에서 DirectX 를 아주 좋게 설명한 책을 아직 발견하지 못했습니다. MSJ 는 DirectDraw 와 DirectSound 를 소개하는 아주 좋은 글을 썼지만, 그 글은 소개글일 뿐 매우 유익한 정보는 없더군요.

Discuss this article in the forums

번역이 매끄럽지 못한 점 죄송합니다.
이 글이 많은 도움이 되길 바랍니다. 번역은 하다보면 차츰 나아지겠죠^^;
저에게 궁금한 점이 있으시면 메일을 보내주세요.

Translate by Sungchul Kim.


16-Bit Pixels
GameDev.net

16-Bit Pixels

by Sam Christiansen

약간의 DirectX 기초에 대해서 배운 후, 필자는 16비트 표면(surfaces)들에 대해서, 그리고, 좀 특정적인, 픽셀 플롯(Plot) 루틴에 대해서 알아 보기로 했다. 인터넷을 조금 찾아 본 후, 16비트 픽셀의 기초를 이해하는데 도움이 되는 2개의 문서를 찾았다. 하나는 Angelic-Coders.com 의 Chris Barnes 가 쓴 문서로 "16Bit Pixel Plotting" 이다. 두번째 문서는 "16Bit with DirectDraw" 로 Hugh Hunkin 이 작성했다. 필자는 Mr. Barnes 와 Mr. Hunkin 에게, 그들의 문서에서 소개된 소스코드를 사용할 수 있도록 허가해 준 것에 대해 고마움을 표한다. 필자는 16비트 픽셀에 관한 새로운 글을 쓰기로 했는데 그 이유는 Mr. Barnes 와 Mr. Hunkin 이 작성한 문서에서 소개된 기술들을 합치면 더 쉽게 이해될 것이라 생각했기 때문이다.

픽셀 포멧

우선, 16비트 DirectDraw 표면에 대해서 몇 마디 해보자. 표면 각각의 16비트 픽셀은 red, green,blue 값을 가지고 있다. 16은 3으로 딱 나누어 떨어지지 않기 때문에, 픽셀 정보를 가지고 있기 위해 가장 공통된 방법인 5-6-5 포멧을 사용한다. 이 말의 뜻은 red 값이 처음 5비트에 담겨 있고, green 은 다음 6비트에, 그리고 blue 는 다음 5비트에 담겨있다. 5-6-5 포멧을 사용함으로써, 우리는 65,536 개의 서로 다은 색을(2^16) 사용할 수 있다. red 의 32 색조(shades), green 의 64 색조, blue 의 32 색조를 가진다. 5-6-5 포멧이 표준이라면 모든 것이 매우 쉽겠지만 다른 픽셀 포멧이 존재할 가능성을 항상 염두해 두어야 한다.

비트마스크와 비트 쉬프트에 관해서

여러분들은 이미 비트마스크와 쉬프팅에 관해서 알 것이다. 그렇다면, 이 섹션은 넘어가고 다음 섹션을 보길 바란다. 그렇지 않고 비트마스크가 혼동 되거나, 필자가 지금 무슨 말을 하는지 전혀 모르겠다면, 이 섹션을 읽어야한다. 비트마스크는 16비트 값에서 red,green,blue 의 부분값을 추출할 수 있도록하는 값이다. 우리는 두 개의 값에 OR(|) 또는 AND(&) 와 같은 논리 연산자를 이용해서 컬러 값으로 이루어진 새로운 값을 만들 수 있다. 예를 들면, 5-6-5 픽셀은 아래와 같이 결합될 수 있다.

red:    5 bits,    00011
green:  6 bits,    001011
blue:   5 bits,    00101

모두 합하면, 16비트 픽셀은 아래처럼 보인다.

0001 1001 0110 0101 ( 공백은 단지 읽기 쉽게 하기 위한 것이다. )

매우 혼잡하게 보인다. 우리는 색깔을 볼 수 있겠지만 어떻게 그것들(red,green,blue) 을 추출할 수 있을까? 여기서 우리의 비트마스크가 등장한다. red 는 5 비트이기 때문에, 우리는 아래의 2진수를 사용할 수 있다.

1111 1000 0000 0000

그리고 이 2진수를 논리 AND 연산해 5 비트 red 값을 뽑아낼 수 있다. 아래처럼 하면 된다.

0001 1001 0110 0101 &
1111 1000 0000 0000
------------------------------
0001 1000 0000 0000

지금 우리는 red 값만 가지고 있는 16 비트 값을 볼 수 있다. 하지만 이 값이 오른쪽에 있지 않다는 것에 주목하길 바란다. 우리가 지금 이 값을 읽는다면, 우리가 예상했던 1에서 31사이의 값이 아닐 것이다. 우리는 여기서 5 비트 값을 움직이기 위해 쉬프트( << 또는 >> ) 할 필요가 있다. 그래서 비트 값이 오른쪽에 위치할 수 있도록 한다. 여러분은 1 자의 오른쪽에 11 개의 0 이 있는 것을 볼 수 있다. 그래서, 우리는 오른쪽으로 11번 쉬프트한다. 여기 그 작업이 있다.

0001 1000 0000 0000 >> 11 =
0000 0000 0000 0011

지금 우리는 마침내 픽셀에 저장된 red 값을 구했다. 그리하여 우리는 이 값을 알맞게 읽을 수 있다. 여기에 우리가 했던 예제의 의사 코드가 있다.

// suppose "pixel" contains the value above
BYTE red;
red = ((pixel & 0xF800) >> 11);

지금 red 변수는 픽셀의 red 부분의 5 비트 값을 가지고 있을 것이다. 비트마스크를 보면, 0xF800 은 16 진수로 되어 있다. 이 것으로 우리는 모든 0 을 일일이 적지 않아도 된다.

중요한 것이 있다. 그것은 우리가 비트마스크와 쉬프트를 이용해서 픽셀을 디코드하고 엔코드 할 수 있다는 것이다. 이 두가지 연산의 예제 코드를 다음 섹션에서 볼 수 있을 것이다.

어떻게 픽셀 포멧을 찾을 수 있을까?

Chris Barnes 는 그의 문서에서 GetRGB16 함수를 사용할 것을 제안했다. 필자는 이 함수를 더 이해하기 쉽도록 수정했다. 기본적으로, 이 프로시져는 DirectDraw 표면을 입력 받고, 표면 정보를 얻기 위해서 DirectDraw 의 GetSurfaceDesc() 를 사용한다. 그리하여 이 함수는 우리에게 픽셀 포멧을 알려 준다. 중요한 것 : 픽셀의 구조가 red-green-blue 라는 가정하에 이 함수의 코드가 유효하다는 것을 알아야 한다. 주요 비디오 카드들은 RGB 포멧을 사용하는 반면, 다른 RGB 조합을 사용하는 비디오 카드도 있을 수 있다( 예 : BGR, GRB 등등 ) 여러분의 코드가 다른 컴퓨터에서도 100% 동작하길 원한다면, 픽셀 포멧에 따라 GetRGB16() 함수와 Plot_Pixel() 함수를 수정해야만 할 것이다.

여기에 필자의 GetRGB16 함수가 있다.:

// you need this struct for the getrgb16 proc
// note: you could even yank the bitmask variables out of this
// if you find you aren't using them in any functions of your own
typedef struct
{
DWORD dwRBitMask,
dwGBitMask,
dwBBitMask;
RGBQUAD Position; // RGBQUAD is a data structure included with MSVC++5.0
} RGB16;

BOOL GetRGB16 ( LPDIRECTDRAWSURFACE Surface, RGB16 *rgb16)
{
DDSURFACEDESC ddsd;
//get a surface
      description
ddsd.dwSize = sizeof( ddsd );
ddsd.dwFlags = DDSD_PIXELFORMAT;
if (Surface->GetSurfaceDesc ( &ddsd ) !=
      DD_OK )
return FALSE;

       //
      will increment r,g, or b for each 1 in it's bitmask, which will give
// us the number of bits used for each
      color
char r=0, g=0, b=0;
for(int n = 1; n<65536; N<<=1)
{
if(ddsd.ddpfPixelFormat.dwRBitMask
      & N)
++R;
if(ddsd.ddpfPixelFormat.dwRBitMask
      & N)
++G;
if(ddsd.ddpfPixelFormat.dwRBitMask
      & N)
++B;
}

// THIS IS THE PART THAT ASSUMES THE FORMAT IS RGB
RGB16->dwRBitMask = ddsd.ddpfPixelFormat.dwRBitMask;
rgb16->Position.rgbRed = g + b;

rgb16->dwGBitMask = ddsd.ddpfPixelFormat.dwGBitMask;
rgb16->Position.rgbGreen = b;

rgb16->dwBBitMask = ddsd.ddpfPixelFormat.dwBBitMask;
rgb16->Position.rgbBlue = 0;

return TRUE;
}//GetRBG16

여기에 우리의 포멧을 이용하는 Plot_Pixel 의 루틴이 있다.:

// Must have the surface locked,
      pass the surface description
// that gets returned from the lock call
void PlotRGB(int x, int y, int r, int g, int b, DDSURFACEDESC *ddsd, RGB16
      *rgb16)
{
WORD Pixel;
Pixel = (r << RGB16->Position.rgbRed) |
(g
      << RGB16->Position.rgbGreen) |
(b);
      // once again, assuming format is RGB

WORD *pixels = (WORD *)ddsd->lpSurface;
DWORD pitch = ddsd->dwWidth;
pixels[y*pitch + x] = Pixel;
}

이 Plot_Pixel 은 dwWidth 가 표면의 피치(pitch)와 같다고 가정한다. Mr. Barnes 가 사용한

DWORD pitch = ddsd->lPitch >> 1;

대신에

DWORD pitch = ddsd->dwWidth;

이 것은 대부분의 표면들에서 정확한 결과를 만들어 낸다. 필자가 이 예제를 생각하기에, dwWidth 가 이 예제에 더 알맞는 것 같다. 이 함수를 사용할 것이라면 기억해 두어라.

최적화

어떻게 이 함수의 속도를 올릴 수 있을까? 이 함수는 RGB 값을 받아서 하나의 16비트 값으로 만든다. 우리는 이미 16비트 값의 픽셀 포멧을 R-G-B 로 가정했다. 예를 들어, 파티클 엔진을 가정한다면, 찍어버린 픽셀들의 색을 바꿀 수 없다. 그래서 RGB 값은 Plot_Pixel 함수에 보내는 것보다 컬러값으로 한번에 엔코드하고 저장하는 것이 아마 더 좋을 것이다. 여기에, 그 함수가 있다.

// Must have the surface locked,
      pass the surface description
// that gets returned from the lock call
void PlotPixel(int x, int y, WORD RGB, DDSURFACEDESC *ddsd)
{
WORD *pixels = (WORD *)ddsd->lpSurface;
DWORD pitch = ddsd->dwWidth;
pixels[y*pitch + x] = RGB;
}

마지막으로 쉬운 최적화는 y*pitch 를 제거하는 것이다. 대두분, 우리는 pitch 를 정의한다. 우리가 우리의 어플리케이션이 640x480 에서 실행된다는 것을 알고 있다면, 그리고 단지 화면에만 쓸 것이라면 우리는 아래와 같이 pitch 테이블을 만들 수 있다. ytable 이 전역 변수라 가정하고, 우리는 간단하게 함수의 마지막 라인을 다음처럼 바꿀 수 있다.

int ytable[480];

for (int i=0;i<480;I++) YTABLE[I]="640" * I;

필자는 이 문서가 여러분이 가지고 있는 16 비트 픽셀에 관한 질문들에 명쾌한 해답이 되었으면 좋겠다. 필자는 이 코드들이 가장 빠르거나 가장 좋다고 생각하지 않는다. 필자는 단지 처음 배울 시에 사용하 수 있는 것을 제공한 것이다. 여기, 필자의 16 비트 라이브러리가 있다.: Pixel.h, Pixel.cpp 그리고 여기, 필자가 이 라이브러리를 사용해 만든 Simple program 이 있다. 이 것을 자유롭게 사용하고, 수정해 보길 바란다. 질문, 하고 싶은 말이 있거나 또는 이 문서를 여러분의 웹페이지에 게시 하고 싶을 때 필자에게 E-Mail 을 보내주길 바란다.

?1998 Sam Christiansen

번역 : 김성철
저에게 질문이 있다면 paranwave@korea.com 으로 메일을 보내주세요 ;)


Programming Games on the Windows Canvas Articles

Programming Games on the Windows Canvas
http://delphigamer.com

Programming Games on the Windows Canvas

Written by : Armand "The Lion" Postma

차례

1. Canvas 는 무엇일까?

2. 게임프로그래밍에서 Canvas 사용하기

3. 고효율의 리드로윙(redrawing)

4. 이미지 깜박임(the Flickering-Image) 문제 해결하기

5. 화면크기 반영하기

6. 풀스크린으로 실행하기

7. 더블버퍼링

8. 그리기

8.1 Canvas.Draw 메서드
8.2 사각영역 복사하기 (Copy Rectangle)

9. 투명화

9.1 Canvas.Draw 로 투명화 만들기
9.2 사각영역 복사(Copy Rectangle)로 투명화 만들기

10. 선들과 사각형들에 대해서

10.1 선들
10.2 사각형들
10.3 펜에 관해서

11. Kylix 게임프로그래머가 주의할 점

12. Canvas 게임 템플릿!

13. 연락하기

1. Canvas 는 무엇일까?

윈도우즈 캔바스(Canvas) 는 윈도우즈의 기본 그리기 표면이다. 윈도우즈의 모든 그래픽컬한 모습들은 캔바스를 통해서 그려진다. 모든 버튼, 모든 윈도우 그리고 음.. 그래, 이쁜 그림들 그리고 당신의 배경화면에 깔려 있는 유명한 젊은 여배우의 사진들까지도 캔바스를 통해 그려진다. 캔바스가 무엇인지 감이 오는가? 그것은 그리기판, 표면 윈도우들에 불과하다 그리고 프로그래머들이 캔바스로 그것들에 그릴 수가 있어 그래픽컬한 풍의 프로그램들을 만들 수 있다.

위의 단락에서 캔바스는 소프트웨어의 근사한 부분으로 말할 수 있을 것이다. 그러나 이 모든 것을 위한 밑바닥이 없다면 윈도우즈는 윈도우즈가 아닐 것이다. 그리고 여기에서 캔바스는 대단하지만 매우 느리다! 캔바스를 버튼들을 그리거나 그림을 그릴 때 사용하는 것은 전혀 문제가 되지 않는다. 하지만 우리가 캔바스를 다이나믹한 게임 프로그래밍 또는 에니메이션들을 보여주기 위해서 사용한다면 문제가 발생한다. 예를 들자면, 이미지 깜박임이나 매우 느린 게임 그래픽스들의 문제가 발생한다.

이 튜토리얼에서는 느린 캔바스를 어떻게 게임 프로그래밍과 애니메에션을 보여주는데 사용할 수 있는지를 설명할 것이다. 이 튜토리얼은 기적을 주진 않는다, 윈도우즈 캔바스를 사용하여 아주 크고 대단한 게임 엔진을 만들 수는 없다는 말이다. 캔바스는 이런 작업에는 알맞지가 않고 보드 게임이나 작은 아케이드 게임들에 적당하다.

2. 게임프로그래밍에서 Canvas 사용하기

전에 얘기했듯이 캔바스는 매우 느린 그리기 표면이다. 그래서 이 튜토리얼 전부분에 걸쳐 게임 프로그래밍에 알맞게, 메모리를 절약하는 트릭들과 캔바스 드로윙을 빠르게 수행하는 방법을 설명할 것이다. 필자는 많은 보드 게임들과 Windows NT4 머신에서 실행되는 게임들을 만들 때 윈도우즈 캔바스를 사용하곤 했다. 또한 캔바스는 당신의 어플리케이션에서 간단한 애니메이션을 구현하기에 매우 유용하다, 예를 들어 로고나 마이크로소프트 오피스에서 볼수 있는 어시스턴트를 얘기할 수 있다. 요약하면, 윈도우즈 캔바스는 "간단한 그래픽스"를 사용하는 게임(보드 게임이나 간단한 아케이드류의 게임들)이나 예쁜 어플리케이션을 만들 때 사용할 수 있는 아주 좋은 그리기 표면이다.

필자는 캔바스를 사용하길 좋아하는데, 그것은 내가 서드파티 도구의 도움 없이 프로그래밍 하길 좋아하고 내 스스로 필요한 것은 만들어 쓰길 선호하기 때문이다. 불행하게도, 필자의 실력이 썩 좋은 편은 아니지만 가능한한 Borland Delphi(우리는 BCB ;)가 지원하는 모든 것을 사용하여 항상 그 점을 보완하려고 노력한다.

OK, 이제 잡담은 그만하고 프로그래밍을 해보자! :)

3. 고효율의 리드로윙(redrawing)

알았든 몰랐든 간에, 윈도우즈는 Paint 이벤트 호출에 매우 빈번하게 자신의 윈도우들을 다시 그린다. 이 것은 매우 좋은 것이다, 이는 윈도우가 항상 우리가 원했던 바로 좋게 보여진다는 것을 확실시 해준다. 하지만, 우리가 폼위에 이미지를 그렸을 윈도우즈는 폼을 다시 그림으로써 우리의 이미지는 사라지게 된다. 예를 들어 우리가 폼위에 이미지를 그리고 폼을 최소화했을 때 그리고 폼을 다시 원래 크기로 돌려 놓았을 때, 이미지는 사라지고 없을 것이다. 이 것은 Paint 이벤트가 폼의 윈도우 상태가 변하면 호출되기 때문이다. 이 문제의 해결법은 쉽다, 우리가 가능한한 많이 화면을 다시 그려주면 된다. 그리고 또한 종종 스크린의 데이타를 업데이트하는 것도 우리들에게 도움이 된다. 그래서 문자가 움직였을 때 이 것은 매우 부드럽게 보일 것이다. DelphiX 나 DirectX 를 가지고 게임 프로그래밍을 해본 경험이 있는 프로그래머들에게 이 것은 새로운 것이 아니다. 그것은 출력되고 있는 이미지를 유지하기 위해 그리고 부드러운 움직임들을 얻기 위해서 DirectX 표면을 가능한한 많이 다시 그려야하기 때문이다. 그래서 우리는 화면을 고효율로 그려나가고 유지하기 위해서 하나의 루프가 필요하다. DelphiX 를 사용하지 않고 게임 루프를 만들기 위해서는 3가지의 방법이 있다.

  • 짧은 간격으로 TTimer 객체를 사용함으로써...
  • Paint 이벤트를 오버라이딩함으로써...
  • Application->OnIdle 이벤트를( Done 을 false 로 설정하고서 )사용함으로써...

필자는 게임을 만들때 TTimer 객체를 사용하는 것을 좋아하지 않는데 그 이유는 그 것이 또 다른 컴포넌트이고 컴포넌트들은 메모리를 사용하기 때문이다. 그리고 TTimer 는 작업을 다루기에 충분히 빠르지 않다고 생각한다. 폼의 Paint 이벤트를 오버라이딩 하는 것은 매우 빠르고 별도의 메모리를 사용하지 않는다. 하지만 필자는 그것을 전혀 사용하지 않는다. 왜냐하면 그것은 실제로 어플리케이션이 받는 모든 WM_PAINT 메세지를 캡쳐한다는 것이다. 이 것은 당신이 버튼을 폼위에 놓았을 때 그 것이 보이지 않을 것이다라는 의미다. "버튼을 다시 그려라"라고 말하는 WM_PAINT 메세지는 절대로 도착하지 않는다. 그것은 오버라이드한 Paint 이벤트가 모두 잡아채기 때문이다. 장면을 고효율로 업데이트하기 위한 가장 좋은 방법은 Application->OnIdle 이벤트를 사용하는 것이다. 이 때 Done 은 false 로 설정한다. 이 메서드를 사용하는 것은 메모리를 사용하지 않는다. 이 메서드를 사용하는 것은 매우 쉽다. 폼의 private 영역에 아래와 같이 새로운 이벤트-핸들러(프로시져) 를 선언하는 것이 전부다.

DELPHI
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
procedure GameLoop(Sender: TObject; var Done: Boolean);
public
{ Public declarations }
end;

BCB
class TForm1 : public TForm
{
public:
__fastcall TForm(TObject* Sender);
private:
void __fastcall GameLoop(TObject* Sender, bool& Done);
};

그 다음 새로운 TForm1::GameLoop 프로시져에 아래와 같이 추가해야 한다.:

DELPHI
Procedure TForm1.GameLoop(Sender: TObject; var Done: Boolean);
begin
Done := False;
// Game Stuff Goes here...
end;

BCB
void __fastcall TForm1::GameLoop(TObject* Sender, bool&
      Done)
{
Done = false;
}

만약 Kylix 를 사용한다면 "// Game Stuff Goes here..." 주석자리에 다음 코드를 입력해야 한다.:

DELPHI
Application.HandleMessage;

BCB
Application->HandleMessage();

마지막으로 우리는 Application.Idle 이벤트에 우리의 이벤트 핸들러를 설정해야 하는데 이것은 폼이 생성될 때 수행되도록 한다. 이를 위해서, 폼을 더블 클릭하면 델파이 또는 빌더는 TForm1::OnCreate 프로시져를 만들게 된다. 이 프로시져에 아래의 코드를 입력한다:

DELPHI
Application.OnIdle := Form1.GameLoop;

BCB
Application->OnIdle = Form1->GameLoop;

지금 우리는 OnIdle 이벤트에 우리의 이벤트 핸들러를 설정했다. 이렇게 함으로써 매번 이들-이벤트가 발생될 때마다 우리의 GameLoop 프로시져가 호출된다. 그리고 Done = false 로 설정하는 것은, 게임 루프의 이벤트가 무한정 발생되도록 하기 위해서다. 만약에 우리가 while 문의 무한 루프를 만듦으로써 같은 효과를 얻을 수 있지만 while 문을 사용하면 다른 모든 이벤트들이 무시될 것이고 어플리케이션은 오버플로우가 될 것이다. OnIdle 이벤트를 사용함으로써, 우리는 무한 while 루프를 사용하는 것과 같은 효과를 얻을 수 있을 뿐만 아니라 다른 이벤트들도 처리할 수 있으며 따라서 우리는 오버플로우를 걱정하지 않아도 된다!

4. 이미지 깜박임(the Flickering-Image) 문제 해결하기

우리가 캔바스로 그림을 그리려하고 그려진 이미지를 고효율로 다시 그리려한다면 우리는 또다른 거대한 문제에 봉착할 것이다.: 이미지 깜박임 문제( the flickering-image problem ). 캔바스는 이미지를 고효율로 다시 그리기에는 약간의 문제가 있다. 캔바스는 그자체가 다소 느리기 때문이다. 좋지 않은 것은 캔바스가 다시 그리기를 했을 때 실제 그리기는 다 끝나지 않는다는 것이다. 그래서 이미지가 여러줄로 겹쳐져서 그려지거나 또는 이미지의 반만이 그려지는 경우가 있다. 이 문제를 해결하려면, 우리는 우선 그 문제의 원인을 알아야한다. 이런 경우, 캔바스가 문제가 아니라 윈도우즈가 문제다.

윈도우즈는 항상 빠르고 좋게 보여지려한다. 그래서 그것의 폼들을 시작시나 우리가 스크린 여기저기로 움직였을 때 그들을 매번 다시 그림으로써 아주 보기 좋게 만든다. 우리는 전에 윈도우즈가 이를 위해서 Paint 이벤트를 사용하는 것을 본적이 있다. 하지만 윈도우즈는 Paint 이벤트가 폼을 다시 그리기에 충분하다고 생각하지 않고 EraseBackGround 라 불리는 이벤트를 사용한다. 이 이벤트는 폼의 표면을 항상 보기 좋게 그리려 한다. 하지만 우리의 경우에 이 이벤트는 필요없는 것이다. 왜냐하면 우리들은 장면들을 매 1/1000 초마다 다시 그리는 것을 원함으로써 EraseBackGroud 이벤트 또한 자주 호출되고 그것을 벗어나면 우리는 폼위에 장면을 다시 그리고 EraseBackGroud 이벤트가 또 호출되고 그것을 벗어나면 다시....그 결과로 이미지의 깜박임이 생긴다!

지금 우리는 문제와 해결책을 알고 있다: 윈도우즈가 우리의 폼의 배경을 지우는 것을 정지하면 된다! 폼의 Paint 이벤트를 오버라이드 하게 되면 우리의 폼에 전달되는 모든 Paint 메세지를 잡을 수 있다는 것을 전에 본적이 있다. 그래서 우리가 해야할 것은 모든 EraseBackGroud 이벤트를 잡는 이벤트 프로시져를 만들면 되는 것이다. 이것을 하기 위해서 우리는 새로운 이벤트 핸들러(프로시져) 를 폼의 private 영역에 정의한다.

DELPHI
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
procedure GameLoop(Sender: TObject; var Done: Boolean);
procedure StopFlicker(var Msg: TWMEraseBkgnd); message WM_ERASEBKGND;
public
{ Public declarations }
end;

BCB
class TForm1 : public TForm
{
public:
__fastcall TForm(TObject* Sender);
void __fastcall FormCreate(TObject* Sender);
private:
void __fastcall GameLoop(TObject* Sender, bool& Done);
void __fastcall StopFlicker(TWMEraseBkgnd& Msg);
BEGIN_MESSAGE_MAP
MESSAGE_HANDLER(WM_ERASEBKGND, TWMEraseBkgnd, StopFlicker)
END_MESSAGE_MAP(TForm)
};

다음 마지막으로 우리는 이벤트 핸들러에 다음과 같이 코드를 작성하면 된다:

DELPHI
Procedure TForm1.StopFlicker(var Msg: TWMEraseBkgnd);
begin
Msg.Result := 1;
end;

BCB
void __fastcall TForm1::StopFlicker(TWMEraseBkgnd& Msg)
{
Msg.Result = 1;
}

지금, 당신은 이미지 깜박임 문제를 해결했다!

5. 화면크기 반영하기

당신이 타일 배경을 원한다고 말한다면, 예를 들어 식탁처럼 보이는 나무 텍스쳐, 그리고 그 타일들이 128x128 픽셀을 사용한다면, 전화면을 꽉채워서 그릴려면 얼만큼의 타일들이 필요할까? 이 질문에는 화면의 폭과 높이를 알지 전까지 답할 수가 없다. 물론 당신은 쉽게 디스플레이 속성을 볼수가 있고 당신의 화면이 1024x768 픽셀의 해상도라는 것을 찾을 수 있다. 하지만 당신이 800x600 의 해상도로 설정한 친구에게 당신의 게임을 준다면 당신의 게임은 아주 안 좋게 보여질 것이다! 화면의 폭과 높이가 기계마다 매우 다양하기 때문에 그것을 아는 것이 쉬운 것이 아니다. 하지만 운 좋게도, 스크린의 해상도를 쉽게 찾는 방법이 있다!
Windows API 에는 우리에게 필요한 정보를 제공해주는 함수가 만들어져있다! 그 함수는 GetSystemMetrics 이다. 해상도를 찾기 위해서 우리는 그 함수를 두번 호출하면 된다. 한번은 화면의 폭을 찾는 것이고 또 한번은 화면의 높이를 찾는 것이다. 이 것은 아주 중요한! 정보이기 때문에 우리는 폼의 OnCreate 이벤트 안에서 이 함수를 호출한다. 그렇게 함으로써 우리는 폼이 만들어지자 마자 즉시 화면 크기 값을 참고 할 있다.

이를 위해서 우리는 VideoModeX 와 VideoModeY 라는 SmallInt( short int ) 데이타 타입의 2개의 변수를 가져야한다. 필자는 항상 이들을 구현 바로 위에서 찾을 수 있도록 변수 영역에 선언한다. 하지만 만약 당신이 다른 폼에서도 스크린크기 값들을 사용하길 원한다면, 이들을 폼의 public 영역에 선언할 수 있다. 이 변수들을 변수 영역에 선언하는 것은 다음과 같다.:

DELPHI
var
Form1: TForm1;
VideoModeX, VideoModeY : SmallInt;

BCB
class TForm1 : public TForm
{
public:
__fastcall TForm(TObject* Sender);
void __fastcall FormCreate(TObject* Sender);
short int VideoModeX;
short int VideoModeY;
private:
void __fastcall GameLoop(TObject* Sender, bool& Done);
void __fastcall StopFlicker(TWMEraseBkgnd& Msg);
BEGIN_MESSAGE_MAP
MESSAGE_HANDLER(WM_ERASEBKGND, TWMEraseBkgnd, StopFlicker)
END_MESSAGE_MAP(TForm)
};

우리가 폼을 클랙했을 때 우리는 폼의 OnCreate 이벤트에 있을 것이다. 그 다음, 우리는 아래와 같이 GetSystemMetrics 함수 호출을 프로시져에 추가한다.:

DELPHI
procedureTForm1.FormCreate(Sender: TObject);
begin
// Retrieve the screensizes...
VideoModeX := GetSystemMetrics(SM_CXSCREEN);
VideoModeY := GetSystemMetrics(SM_CYSCREEN);

// Assign the Gameloop event-procedure to the Application.OnIdle event
Application.OnIdle := Form1.GameLoop;
End;

BCB
void __fastcall TForm1::FormCreate(TObject* Sender)
{
//
      Retrieve the screensizes...
VideoModeX
      = GetSystemMetrics(SM_CXSCREEN);
VideoModeY
      = GetSystemMetrics(SM_CYSCREEN);

//
      Assign the Gameloop event-procedure to the Application.OnIdle event
Application->OnIdle
      = Form1->GameLoop;
}

지금 우리는 쉽게 대압을 찾았다. VideoModeX 가 1024 이고 VideoModeY 가 768 이라면 타일이 얼마나 필요할까?:
가로는 1024 / 128 = 8 개 타일이 화면을 채우기 위해서 필요하다.
세로는 768 / 128 = 6 개 타일이 화면을 채우기 위해서 필요하다.

6. 풀스크린으로 실행하기

우리가 가져온 화면 크기는 전체 화면의 크기다. 하지만 우리의 폼은 전체 화면을 덮고 있지 않다. 단지 한 부분만 가리고 있을 뿐이다. 물론 우리는 폼을 최대로 할 수 있다(최대화 버튼을 눌러서), 하지만 그래도 우리의 폼은 전체 화면 크기와 같지가 않다. 왜냐하면 타이틀바가 있기 때문이다!

폼을 전체 화면 크기와 같은 크기로 만드는 트릭이 있다. 우리는 폼의 border 를 Off 하고 폼을 최대화 한다. 이 방법은 폼이 전체 화면을 가릴 수 있게 할 것이다. 우리는 이 것을 OnCreate 이벤트에서 실행한다. OnCreate 이벤트 핸들러에 풀스크린 모드를 추가한 다음 이벤트 핸들러의 모습은 아래와 같을 것이다.

DELPHI
procedureTForm1.FormCreate(Sender: TObject);
begin
// Retrieve the screensizes...
VideoModeX := GetSystemMetrics(SM_CXSCREEN);
VideoModeY := GetSystemMetrics(SM_CYSCREEN);

Form1.BorderStyle := bsNone;
Form1.WindowState := wsMaximized;

// Assign the Gameloop event-procedure to the Application.OnIdle event
Application.OnIdle := Form1.GameLoop;
End;

BCB
void __fastcall TForm1::FormCreate(TObject* Sender)
{
// Retrieve the screensizes...
VideoModeX = GetSystemMetrics(SM_CXSCREEN);
VideoModeY = GetSystemMetrics(SM_CYSCREEN);

    Form1->BorderStyle
      = bsNone;
Form1->WindowState
      = wsMaximized;

//
      Assign the Gameloop event-procedure to the Application.OnIdle event
Application->OnIdle
      = Form1->GameLoop;
}

만약에 당신의 폼위에서 다른 어플리케이션이 실행되는 것을 막으려면, 아래의 코드를 이벤트 핸들러에 추가할 수 있다. 바로 Form1.WindowsState := wsMaximized 아래의 라인에:

DELPHI
Form1.FormStyle := fsStayOnTop;

BCB
Form1->FormStyle = fsStayOnTop;

이 코드는 우리의 폼이 항상 위에 있을 것이라는고 말해준다. 다른 어플리케이션이 실행되건 말건, 우리의 폼은 항상 위에 있게 된다!

타이틀바를 없앰으로써 문제가 발생하는데 그것은 우리의 어플리케이션을 종료할 수 없다는 것이다. 이 것은 키입력을 핸들링하는 이벤트 프로시져를 만듦으로써 쉽게 해결할수 있고 ESCAPE 키가 눌렸을 때 어플리케이션이 종료되게 하면 된다. 이 것은 Object Inspector 에서 Events 탭을 선택해서 할 수 있다. OnKeyDown 이벤트를 클릭하면, OnKeyDown 이벤트 핸들러를 만들게 된다. escape 에 프로그램을 종료시키는 몇 라인의 코드만 추가하는 것이 우리가 해야할 모든 것이다. OnKeyDown 이벤트 핸들러에 몇 라인을 추가한 다음의 모습은 아래와 같을 것이다.:

DELPHI
procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin { If the ESCAPE key has been pressed then close the program! }
If Key = VK_ESCAPE then
Begin
SendMessage(Handle, WM_CLOSE, 0, 0);
End;
end;

BCB
void __fastcall TForm1::FormKeyDown(TObject*
      Sender, Word& Key, TShiftState Shift)
{
if( Key == VK_ESCAPE )
{
SendMessage(Handle,
      WM_CLOSE, 0, 0);
}
}

지금 우리는 ESCAPE 키를 눌렀을 때 어플리케이션이 스스로 종료하도록 모든 것을 해 놓았다.

7. 더블버퍼링

더블버퍼링의 그리기 방법의 전문적인 용어로 종종 "오프스크린 그리기(offscreen drawing)" 이라고도 불린다. 이 이름은 우리가 화면에 그리지 않고 다른 어떤 곳에다 그린다는 것은 정확하게 더블버퍼링의 뜻을 말해준다! 우리가 더블 버퍼링을 할 때 우리는 화면에 그리지 않고 컴퓨터의 메모리에 그리게 된다. 컴퓨터의 메모리가 오늘날 가짱 빠른 매체이기 때문이다. 메모리에 그리는 것은 화면에 바로 그리는 것보다 전체적으로 훨씬 빠르다.

컴퓨터 게임의 장면을 그릴 때, 우리는 전체 장면을 만들기 위해서 많은 수의 작은 이미지들을 사용하게 된다. 사막의 타일 이미지, 자동차의 작은 이미지, 그리고 빌딩 이미지의 타일 이미지를 가지고 있다면... 우리는 사막의 타일 이미지들을 가지고 전체 장면을 채울 때, 사막으로 보이는 장면에 빌딩을 넣었을 때, 그것은 빌딩이 있는 사막으로 보일것이다. 플레이어는 차를 가지고 그 것을 운전해 사막 주위를 운전할 수 있도록 우리는 세개의 작은 이미지들로 장면을 만들 수 있을 것이다... 이 장면을 화면에 그렸을 때, 우리는 화면을 적어도 세번은 그려야할 것이고 그것의 메모리에 매번 세개의 이미지 중 하나를 채우게 될 것이다. 필자가 생각했을 때 이런 장면 그리기는 얼마간의 시간이 필요할 것이다...

자~ 지금 우리가 위에서 얘기한 예제를 세개의 작은 그림 대신에 하나의 큰 그림으로만 그릴 거라고 생각해 보자. 그것은 더욱더 빠르게 그려질 것이다! 이 것이 우리가 하려는 더블버퍼링의 기초다. 우리는 세개의 작은 이미지를 가져와 화면 대신에 메모리 안에서 전체 장면을 만든다. 우리가 장면을 만드는 것을 끝냈을 때, 우리는 화면에 전체 장면을 한번에 그린다. 이 그리기 방법은 더욱더 빠른 화면 업데이트와 더욱 부드러운 애니메이션을 만들고 깜박임 이미지 문제에 좋은 해결책이 된다.

지금 우리는 더블버퍼링이 무엇인지 알았다. 우리는 이 것을 델파이의 코드로 만들어야한다. 이 것을 하기 전에 우리는 우선 약간의 준비를 해야한다. 더블버퍼링을 위한 준비는 쉽다. 우리는 단지 우리가 장면을 메모리에 그릴 수 있도록 우리의 프로그램에 하나의 방법을 추가하기만 하면 된다. 델파이에서 이 것은 대부분 TBitmap 객체로 수행된다. 그래서 우리는 TBitmap 객체를 유닛의 var 영역에 선언한다. 이를 한 다음, var 영역은 아래와 같을 것이다.:

DELPHI
var
Form1: TForm1;
VideoModeX, VideoModeY : SmallInt;
OffScreen : TBitmap;

BCB
class TForm1 : public TForm
{
public:
__fastcall TForm(TObject* Sender);
void __fastcall FormCreate(TObject* Sender);
short int VideoModeX;
short int VideoModeY;
TBitmap* OffScreen;
private:
void __fastcall GameLoop(TObject* Sender, bool& Done);
void __fastcall StopFlicker(TWMEraseBkgnd& Msg);
BEGIN_MESSAGE_MAP
MESSAGE_HANDLER(WM_ERASEBKGND, TWMEraseBkgnd, StopFlicker)
END_MESSAGE_MAP(TForm)
};

우리는 TBitmap 객체를 사용하는데 그 이유는 자체 캔바스가 있기 때문이며, 그래서 우리는 폼 캔바스로 그리는 것과 같이 TBitmap 객체에 그릴 수가 있다. 하지만 우리가 OffScreen 객체에 그릴 수 있기 전에, 우리는 우선 그 객체를 생성해야한다. 우리가 지금 한 객체를 선언한 의미는 단지 객체가 사용할 메모리를 준비해둔 것이다. 우리는 폼이 생성된 다음 즉시 OffScreen 객체가 필요하기 때문에 우리는 OffScreen 객체의 생성을 역시 OnCreate 이벤트핸들러에 추가할 것이다. 지금 OnCreate 이벤트 핸들러는 다음과 같을 것이다.:

DELPHI
procedureTForm1.FormCreate(Sender: TObject);
begin
// Retrieve the screensizes...
VideoModeX := GetSystemMetrics(SM_CXSCREEN);
VideoModeY := GetSystemMetrics(SM_CYSCREEN);

Form1.BorderStyle := bsNone;
Form1.WindowState := wsMaximized;

OffScreen := TBitmap.Create;
OffScreen.Width := VideoModeX;
OffScreen.Height := VideoModeY;

// Assign the Gameloop event-procedure to the Application.OnIdle event
Application.OnIdle := Form1.GameLoop;
End;

BCB
void __fastcall TForm1::FormCreate(TObject* Sender)
{
// Retrieve the screensizes...
VideoModeX = GetSystemMetrics(SM_CXSCREEN);
VideoModeY = GetSystemMetrics(SM_CYSCREEN);

    Form1->BorderStyle
      = bsNone;
Form1->WindowState
      = wsMaximized;

OffScreen
      = new Graphics::TBitmap;
OffScreen->Width
      = VideoModeX;
OffScreen->Height
      = VideoModeY;

//
      Assign the Gameloop event-procedure to the Application.OnIdle event
Application->OnIdle
      = Form1->GameLoop;
}

위의 코드 구현에서 OffScreen 이미지 또한 폭과 높이를 우리의 스크린 크기와 같게 설정하는 것을 볼 수 있다. 우리는 우리가 오프스크린 이미지를 화면에 그릴 때 그 이미지가 전 화면을 덮을 수 있도록 하기 위해서 이렇게 한다. 만약 우리가 화면의 반 크기만을 설정했다면, 오프스크린의 이미지를 화면에 그렸을 때 우리는 단지 화면의 반만을 채우게 될 것이다. 우리가 오프스크린 이미지에 폭과 너비의 값을 설정하지 않았을 때, 우리는 화면에서 아무것도 볼 수 없을 것이다. 그것은 오프스크린 이미지의 크기가 0x0 이기 때문이다!
지금 우리는 더블버퍼링을 위해 필요한 것을 거의 끝내놓았다. 하나만 남겨두었는데 그것은 오프스크린 이미지를 화면에 그리는 일이다. 이 것은 GameLoop 에 단 한줄의 코드만 추가하면 할 수 있다. 이를 위해 우리는 API 호출을 사용할 것인데, 그것은 BitBlt API 다. 이 것은 캔바스에 큰 이미지를 그리는 가장 빠른 방법이고 우리는 이 것을 오프스크린 객체의 캔바스로부터 폼의 캔바스로 복사하는데 사용할 것이다. BitBlt 호출이 구현된 다음, GameLoop 는 아래처럼 될 것이다.

DELPHI
Procedure TForm1.GameLoop(Sender: TObject; var Done: Boolean);
Begin
Done := False;

// Game Stuff Goes here...

BitBlt(Canvas.Handle, 0, 0, VideoModeX, VideoModeY, OffScreen.Canvas.Handle, 0, 0, SRCCOPY);
End;

BCB
void __fastcall TForm1::GameLoop(TObject* Sender, bool&
      Done)
{
Done = false;

//
      Game Stuff Goes here...

BitBlt(Canvas->Handle,
      0, 0, VideoModeX, VideoModeY, OffScreen->Canvas->Handle, 0, 0, SRCCOPY);
}

8. 그리기

마지막으로, 이 챕터는 우리가 기다리고 기다리던, 그리기 챕터다! 하지만 우리가 그리기를 시작할 수 있기 전에 우리는 그릴 무엇인가가 필요하다. 캔바스와 함께 우리는 몇가지의 그리기 방법들을 가지고 있고 캔바스는 픽셀부터 전 이미지들까지 그리기를 지원한다. 시작하는 이들을 위해 필자는 이미지를 그리는 것부터 시작할 것이다. 그 이유는 이 것이 게임프로그래밍에서 가장 자주 사용되기 때문이다. 이미지를 그리기 위해서 우리는 TBitmap 객체에 이미지를 불러들여야한다. 우리는 물론 우리에게 필요한 각각의 이미지마다 TBitmap 객체를 만들수 있다. 그리고 CarImage 와 같이 객체에 이름을 줄 수 있다. 이 것은 우리가 2개 또는 3개의 이미지들을 사용하려고 할 때 매우 유용할 것이다. 하지만 게임 프로그래밍에서 우리는 대부분 더 많은 이미지들을 사용한다. 이런 경우에 우리는 TBitmap 객체의 배열을 만든다. 이 의미는 우리가 TBitmap 객체의 전체 더미를 관리할 수 있는 변수를 만들 수 있다는 것이다. 필자는 이 변수를 GameGraphics 라고 부를 것이다. 우리가 5개의 이미지가 필요하다면, 이 경우에 우리는 var 영역에 5개의 TBitmap 의 배열을 선언할 것이다. 만약에 우리가 var 영역에 5 개의 TBitmap 객체 배열을 선언했다면 그 것은 아래와 같이 보일 것이다.:

DELPHI
var
Form1: TForm1;
VideoModeX, VideoModeY : SmallInt;
OffScreen : TBitmap
GameGraphics: Array[0..4] of TBitmap;

BCB
class TForm1 : public TForm
{
public:
__fastcall TForm(TObject* Sender);
void __fastcall FormCreate(TObject* Sender);
short int VideoModeX;
short int VideoModeY;
TBitmap* OffScreen;
TBitmap* GameGraphics[5];
private:
void __fastcall GameLoop(TObject* Sender, bool& Done);
void __fastcall StopFlicker(TWMEraseBkgnd& Msg);
BEGIN_MESSAGE_MAP
MESSAGE_HANDLER(WM_ERASEBKGND, TWMEraseBkgnd, StopFlicker)
END_MESSAGE_MAP(TForm)
};

지금 우린느 GameGraphics 배열을 선언했다. 우리는 이미지들로 그것을 채워야할 것이다. 하지만 우리가 TBitmap 객체를 이미지로 채울 수 있기 전에, 우리는 우선 이미지들을 생성해야한다.(기억하길바란다. 우리는 단지 TBitmap 객체를 위해 메모리를 예약했을 뿐이다). 그래서 가장 좋은 방법은 하드드라이브에서 메모리로 이미지들을 불러오는 프로시져를 만드는 것이다. 우리는 이 프로시져를 폼의 public 영역에 선언하고 이 것을 LoadGameGraphics 라고 명명한다. 이 것을 선언한 다음 폼의 pubic 영역은 아래와 같을 것이다:

DELPHI
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
procedure GameLoop(Sender: TObject; var Done: Boolean);
procedure StopFlicker(var Msg: TWMEraseBkgnd); message WM_ERASEBKGND;
public
{ Public declarations }
procedure LoadGameGraphics;
end;

BCB
class TForm1 : public TForm
{
public:
__fastcall TForm(TObject* Sender);
void __fastcall FormCreate(TObject* Sender);
short int VideoModeX;
short int VideoModeY;
TBitmap* OffScreen;
TBitmap* GameGraphics[5];
void __fastcall LoadGameGraphics(void);
private:
void __fastcall GameLoop(TObject* Sender, bool& Done);
void __fastcall StopFlicker(TWMEraseBkgnd& Msg);
BEGIN_MESSAGE_MAP
MESSAGE_HANDLER(WM_ERASEBKGND, TWMEraseBkgnd, StopFlicker)
END_MESSAGE_MAP(TForm)
};

지금 우리는 LoadGameGraphics 프로시져를 선언했다. 우리는 실제로 폼의 어딘가에다가 프로시져를 써야한다. 우리가 이것을 했을 때, 프로시져는 아래와 같을 것이다.:

DELPHI
Procedure TForm1.LoadGameGraphics;
var
D : Integer;
Begin
For D := 0 to 4 do
GameGraphics[D] := TBitmap.Create;

   GameGraphics[0].LoadFromFile(ExtractFilePath(ParamStr(0))
      + 'image1.bmp');
GameGraphics[1].LoadFromFile(ExtractFilePath(ParamStr(0))
      + 'image2.bmp');
GameGraphics[2].LoadFromFile(ExtractFilePath(ParamStr(0))
      + 'image3.bmp');
GameGraphics[3].LoadFromFile(ExtractFilePath(ParamStr(0))
      + 'image4.bmp');
GameGraphics[4].LoadFromFile(ExtractFilePath(ParamStr(0))
      + 'image5.bmp');
End;

BCB
void __fastcall TForm1::LoadGameGraphics(void)
{
int
      d;
for(d
      = 0 ; d < 4 ; d++)
{
GameGraphics[d]
      = new Graphics::TBitmap;
}

GameGraphics[0]->LoadFromFile(ExtractFilePath(ParamStr(0))
      + "image1.bmp");
GameGraphics[1]->LoadFromFile(ExtractFilePath(ParamStr(0))
      + "image2.bmp");
GameGraphics[2]->LoadFromFile(ExtractFilePath(ParamStr(0))
      + "image3.bmp");
GameGraphics[3]->LoadFromFile(ExtractFilePath(ParamStr(0))
      + "image4.bmp");
GameGraphics[4]->LoadFromFile(ExtractFilePath(ParamStr(0))
      + "image5.bmp");
}

이 프로시져는 우선 for 루프에서 TBitmap 이미지들을 생성한 다음 어플리케이션과 같은 디렉토리에 있는 비트맵들로부터 이미지들을 불러들이고 있다. ExtractFilePath(ParamStr(0)) 함수 호출로 어플리케이션이 있는 디렉토리를 얻은 다음 이미지의 파일 이름을 붙인다. 물론 어플리케이션이 위친한 곳에 5개의 이미지들이 있어야하고 파일이름을 수정하거나 또는 LoadGameGraphics 프로시져안의 파일이름들을 여러분의 이미지 이름을 바꿔야 이 프로시져를 사용할 수 있을 것이다. 이제 남은 것은 어플리케이션이 시작할 때 OnCreate 이벤트 핸들러에서 이 프로시져를 호출하는 것이다. 이것을 적고난 다음 OnCreate 이벤트 핸들러는 아래와 같을 것이다.:

DELPHI
procedureTForm1.FormCreate(Sender: TObject);
begin
// Retrieve the screensizes...
VideoModeX := GetSystemMetrics(SM_CXSCREEN);
VideoModeY := GetSystemMetrics(SM_CYSCREEN);

Form1.BorderStyle := bsNone;
Form1.WindowState := wsMaximized;

OffScreen := TBitmap.Create;
OffScreen.Width := VideoModeX;
OffScreen.Height := VideoModeY;

LoadGameGraphics;

// Assign the Gameloop event-procedure to the Application.OnIdle event
Application.OnIdle := Form1.GameLoop;
End;

BCB
void __fastcall TForm1::FormCreate(TObject* Sender)
{
// Retrieve the screensizes...
VideoModeX = GetSystemMetrics(SM_CXSCREEN);
VideoModeY = GetSystemMetrics(SM_CYSCREEN);

    Form1->BorderStyle
      = bsNone;
Form1->WindowState
      = wsMaximized;

OffScreen
      = new Graphics::TBitmap;
OffScreen->Width
      = VideoModeX;
OffScreen->Height
      = VideoModeY;

LoadGameGraphics();

//
      Assign the Gameloop event-procedure to the Application.OnIdle event
Application->OnIdle
      = Form1->GameLoop;
}

GameGraphics 배열로 부터 화면에 이미지를 그리는 방법에는 2가지가 있다. 처음 것은(가장 쉬운 방법) 간단힌 Draw 함수만 호출해주면 되고 두번째 것은 CopyRect 호출하는 것이다. 지금, 필자는 간단한 몇몇 캔바스 그리기 튜토리얼들을 읽었는데 대부분 CopyRect 를 사용했다. CopyRect 는 매우 빠른 그리기 방법이고 캔바스에서 가장 많이 사용되지만, 필자는 그것을 몇번만 사용해봤고 그것의 속도에 매우 감명받았다. 낯설은가? 전현, CopyRect 는 비투명 이미지를 그리기에는 가장 빠른 메서드이다. 왜냐하면 그것은 단지 가장 빠르기만한 메서드이기 때문이다. 하지만 우리가 투명 이미지를 그리려할 때 필자는 Draw 메서드를 사용하라고 권고할 것이다. 필자는 얼마전에 투명 이미지를 그리기 위해 CopyRect 메서드를 사용하려고 시도해 보았지만 그것은 필자의 AMD XP 1800+ 에서 그리기에 속도가 저하되었다. 반면에 Draw 메서드는 펜티엄 400 MHz 에서 실행시켜보았지만 전혀 속도 저하를 발견할 수 없었다. 그래서 필자는 이 두가지 메서드를 설명할 것이다, 하지만 필자의 충고는: 투명 이미지를 사용할 것라면 Draw 메서드를 사용하고 비 투명 이미지를 사용할 것이라면 CopyRect 메서드를 사용하라는 것이다! 필자의 체스 게임에서 필자는 처음에 모든곳에 Draw 메서드를 사용했지만 후에 필자는 배경 및 체스판의 검고 흰 타일들을 그릴 때 CopyRect 를 사용했는데 400 MHz 머신에서도 게임의 속도 증가를 목격했다!

8.1 Canvas.Draw 메서드

이 것은 배우기에 가장 쉬운 메서드이고 단 몇줄로 설명할 수 있다. Draw 함수는 아래와 같다:

procedure Draw(X, Y: Integer; Graphic: TGraphic);

설명은 쉽다. X 값을 높게 설정하면 이미지는 오른쪽으로 움직일 것이고 Y 값을 높게 하면 이미지는 화면의 아래쪽으로 움직일 것이다. Graphic 에는 우리가 그리고자 하는 이미지를 전달한다. 예를들면:

Draw(0, 0, GameGraphics[2]); Draw(20, 5, GameGraphics[2]);

첫번째 예제는 GameGraphics 배열에서 3번째 이미지를 화면의 왼쪽-위 코너에 그리고 두번째 예제는 이 이미지를 오른쪽으로 20 픽셀 움직이고 아래로 5 픽셀 움직인다.

쉽지 않은가? 하지만 잊지 말아야할 것은 항상 Draw 프로시져를 GameLoop 에서 호출해야하고 항상 BitBlt 를 호출하기 전에 모든 그리기 작업을 해야한다. 그러지 않으면 이미지는 화면에 나타나지 않을 것이다!

8.2 사각영역 복사하기 (Copy Rectangle)

이 메서드는 이해하고 마스터하는데 많이 어렵다. 이 메서드를 사용하기 위해서 우리는 우선 OffScreen 캔바스에서 복사하길 원하는 사각영역의 변수를 선언한다. 그런 다음 다른 사각영역에 복사할 사각변수를 선언한다. 매우 복잡한 것 같지만... 그렇게 걱정하지 않아도 된다.

GameGraphics 배열의 2번째 이미지를 OffScreen 비트맵에 CopyRect 를 이용해 그리고 싶다면. 우선 2개의 사각영역을 선언한다. 하나는 SourceRect(데이타 타입은 TRect)이고 나머지는 DestRect(데이타 타입 TRect)이다. SourceRect 는 GameGraphics 배열의 두번째 이미지에서 영역을 "선택"하기 위해서 사용할 것이다. DestRect 는 OffScreen 상에 그려질 이미지의 위치를 잡는데 사용할 것이다. 만약 우리가 두번째 이미지를 OffScreen 이미지의 0, 0 위치에 그린다면 GameLoop 는 아래처럼 될 것이다.

DELPHI
Procedure TForm1.GameLoop(Sender: TObject; var Done: Boolean);
Begin
Done := False;

   //
      Game Stuff Goes here...
SourceRect := Rect(0, 0, GameGraphics[1].Width,
      GameGraphics[1].Height);
DestRect := Rect(0, 0, GameGraphics[1].Width,
      GameGraphics[1].Height);
OffScreen.Canvas.CopyMode := cmSrcCopy;
OffScreen.Canvas.CopyRect(DestRect, GameGraphics[1].Canvas,
      SourceRect);

BitBlt(Canvas.Handle, 0, 0, VideoModeX, VideoModeY, OffScreen.Canvas.Handle, 0, 0, SRCCOPY);
End;

BCB
void __fastcall TForm1::GameLoop(TObject* Sender, bool&
      Done)
{
Done = false;

//
      Game Stuff Goes here...
SourceRect
      = Rect(0, 0, GameGraphics[1].Width, GameGraphics[1].Height);
DestRect
      = Rect(0, 0, GameGraphics[1].Width, GameGraphics[1].Height);
OffScreen->Canvas->CopyMode
      = cmSrcCopy;
OffScreen->Canvas->CopyRect(DestRect,
      GameGraphics[1].Canvas, SourceRect);

BitBlt(Canvas->Handle,
      0, 0, VideoModeX, VideoModeY, OffScreen->Canvas->Handle, 0, 0, SRCCOPY);
}

오케이, 정말 간단하다. 초보자들을 위해서 무엇을 했는지 알아보자. 우리는 2개의 사각영역을 선언했다. 처음 SouceRect 는 단지 GameGraphics 배열의 2번째 이미지를 전부 선택하고 두번째는 OffScreen 이미지에 선택한 이미지가 위치할 영역을 선택한다. 이 경우, 두개의 사각영역은 OffScreen 이미지의 위쪽-왼쪽 코너에 이미지를 그리는 결과가 같을 것이다. 만약에 우리가 이미지를 20 픽셀 왼쪽으로 5픽실 아래로 DestRect 를 설정하면 아래처럼 될 것이다.:

DestRect := Rect(20, 5, 20 + GameGraphics[1].Width, 5 + GameGraphics[1].Height);

위에 코드가 수행되면 이 이미지는 왼쪽으로 20 픽셀 움직이고 아래쪽으로 5픽셀 내려갈 것이다, Draw 메서드와 비슷하지 않은가?

DestRect 아래 줄에는 아주 특별한 것이 있다. 이 코드로 우리는 CopyMode 를 선택한다. 우리가 하려는 모든 것은 이미지를 복사하는 것이기 때문에 그 것을 특별하게 봐야한다. 하지만 CopyRect 는 마스크인 반전 이미지를 만들어 그것을 할 수 있다. 하지만 우리는 단지 소스 이미지로부터 목표 이미지로 복사하는 것을 원하기 때문에 우리는 cmSrcCopy 를 사용한 것이다. 그리고 마지막으로 우리는 CopyRect 함수를 사용해서 이미지를 그린다.

Procedure CopyRect(Dest: TRect; Canvas: TCanvas; Source: TRect);

일단 어떻게 사각영역들을 생성하고 어떻게 대상을 선택하는 지를 이해하면 이 함수는 매우 논리적이다. 이전에 CopyRect 프로시져에 필요한 스스로 말한 사각영역을 이해하자. 대상 위치인 DestRect 를 제공하고, 그 다음 소스 캔바스를 제공하고 마지막으로 소스 사각영역(SoruceRect)을 제공하면 끝입니다.

물론 이 일을 모든 이미지마다 할 때 당신은 절실하게 짧은 시간에 하기를 원할 것이다. 그래서 필자는 Draw 메서드처럼 쉽게 사용할 수 있는 프로시져를 만들었다. 당신의 게임에서 이 프로시져를 사용하길 원한다면, 이 프로시져를 폼의 public 영역에 선언해야할 것이다. 이 것을 한 public 영역은 아래와 같을 것이다.

 DELPHI
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
private
{ Private
      declarations }
procedure GameLoop(Sender: TObject;
      var Done: Boolean);
procedure StopFlicker(var Msg:
      TWMEraseBkgnd); message WM_ERASEBKGND;
public
{
      Public declarations }
procedure LoadGameGraphics;
procedure DrawImage(X, Y : Integer;
      Bmp : TBitmap);
end;

BCB
class TForm1 : public TForm
{
public:
__fastcall TForm(TObject* Sender);
void __fastcall FormCreate(TObject* Sender);
short int VideoModeX;
short int VideoModeY;
TBitmap* OffScreen;
TBitmap* GameGraphics[5];
void __fastcall LoadGameGraphics(void);
void __fastcall DrawImage(int X, int Y, Graphics::TBitmap* Bmp);
private:
void __fastcall GameLoop(TObject* Sender, bool& Done);
void __fastcall StopFlicker(TWMEraseBkgnd& Msg);
BEGIN_MESSAGE_MAP
MESSAGE_HANDLER(WM_ERASEBKGND, TWMEraseBkgnd, StopFlicker)
END_MESSAGE_MAP(TForm)
};

실제 프로시져를 유닛파일에 추가해야한다. 프로시져는 아래와 같다.:

DELPHI
Procedure TForm1.DrawImage(X, Y : Integer; Bmp
      : TBitmap);
var SourceRect, DestRect : TRect;
Begin
SourceRect := Rect(0, 0, Bmp.Width, Bmp.Height);
DestRect := Rect(X, Y, X + Bmp.Width, Y + Bmp.Height);
OffScreen.Canvas.CopyMode := cmSrcCopy;
OffScreen.Canvas.CopyRect(DestRect, Bmp.Canvas,
      SourceRect);
End;

BCB
void __fastcall TForm1::DrawImage(int
      X, int Y, TBitmap* Bmp)
{
TRect SourceRect,
      DestRect;
SourceRect = Rect(0,
      0, Bmp->Width, Bmp->Height);
DestRect = (X,
      Y, X+Bmp->Width, Y+Bmp->Height);
OffScreen->Canvas->CopyMode
      = cmSrcCopy;
OffScreen->Canvas->CopyRect(DestRect,
      Bmp->Canvas, SourceRect);
}

이 프로시져는 Draw 메서드와 비슷하게 동작한다. GameGraphics 배열의 4번째 그림을 오른쪽으로 20 픽셀 아래로 5픽셀 이동시킨 위치에 그리고 싶다면 이 프로시져를 아래처럼 호출하면 된다:

DrawImage(20, 5, GameGraphics[3]);

GameLoop 에서 그리기 프로시져를 매번 호출해야 하고 이 것을 BitBlt 이전에 해줘야 하는 것을 잊지 말자!

9. 투명화

컴퓨터 그래픽스에서 가장 큰 문제는 비트맵은 항상 사각형이라는 것이다. 반면 때때로 우리는 원이나, 삼각형, 또는 또다른 모습의 그림들을 그리길 원한다. 게임 프로그래밍에서 이 문제는 투명화로 해결한다.

위에 보이는 이미지에서, 우리는 같은 그림을 두개 볼 수 있다. 왼쪽 이미지는 투명하지 않고 오른쪽 이미지는 투명하다. 오른쪽것이 더욱더 실감나게 보인다.

투명하게 만드는 방법은 캔바스의 두가지 그리기 방식에 따라 다르게 수행된다. 이전 챕터에서 말했듯이, CopyRect 를 이용한 투명화는 당신의 어플리케이션을 더 느리게 만든다. 하지만 그렇다 하더라도 필자는 이 메서드를 당신을 위해서 설명할 것이다. 하지만 필자는 우선 쉬운 Draw 메서드부터 설명할 것이다.

9.1 Canvas.Draw 로 투명화 만들기

Draw 메서드를 사용하여 화면에 이미지를 그리려할 때, 투명화는 실제로 매우 쉽게 사용할 수 있다. 이 방법은 단지 안 그려질 색깔만 설정하면 된다. 이 색깔은 보통 fuchsia 색깔을 투명색으로 사용하는데 그것은 그 색깔이 이미지에 잘 안쓰이기 때문에 그것이 이 투명화에 딱 알맞다!

오케이, 자 우리가 위헤서 그린 GameGraphics 배열의 4번째 이미지를 오프스크린에 투명으로 그리고 싶다면 아래의 코드처럼 해주면 된다:

DELPHI
GameGraphics[3].TransparentColor := clFuchsia;
GameGraphics[3].Transparent := True;
OffScreen.Draw(20, 5, GameGraphics[3]);

BCB
GameGraphics[3]->TransparentColor = clFuchsia;
GameGraphics[3]->Transparent = true;
OffScreen->Draw(20, 5, GameGraphics[3]);

물론 우리가 이미지를 그리려 할 때마다 아래의 코드를 설정해 줘야 하는 것은 아니다.:

GameGraphics[3]. TransparentColor := clFuchsia;
GameGraphics[3]. Transparent := True;

LoadGameGraphics 프로시져에서도 설정해 줄 수 있다. 이 속성은 한번 설정되면 설정이 계속 유지된다. 그래서 이 이미지는 Transparent 가 False 로 변할 때까지 투명하게 그려진다.

만약에 GameGraphics 배열의 모든 그림들을 투명하게 그려야한다면, LoadGameGraphics 프로시져에서 for 루프로 이를 설정하면 된다. 그렇게 한 LoadGameGraphics 프로시져는 아래와 같다.

DELPHI
Procedure TForm1.LoadGameGraphics;
var
D : Integer;
Begin
For D := 0 to 4 do
GameGraphics[D] := TBitmap.Create;

   GameGraphics[0].LoadFromFile(ExtractFilePath(ParamStr(0))
      + 'image1.bmp');
GameGraphics[1].LoadFromFile(ExtractFilePath(ParamStr(0))
      + 'image2.bmp');
GameGraphics[2].LoadFromFile(ExtractFilePath(ParamStr(0))
      + 'image3.bmp');
GameGraphics[3].LoadFromFile(ExtractFilePath(ParamStr(0))
      + 'image4.bmp');
GameGraphics[4].LoadFromFile(ExtractFilePath(ParamStr(0))
      + 'image5.bmp');

For D := 0 to 4 do
Begin
GameGraphics[D].TransparentColor
      := clFuchsia;
GameGraphics[D].Transparent :=
      True;
End;

End;

BCB
void __fastcall TForm1::LoadGameGraphics(void)
{
int
      d;
for(d
      = 0 ; d < 4 ; d++)
{
GameGraphics[d]
      = new Graphics::TBitmap;
}

GameGraphics[0]->LoadFromFile(ExtractFilePath(ParamStr(0))
      + "image1.bmp");
GameGraphics[1]->LoadFromFile(ExtractFilePath(ParamStr(0))
      + "image2.bmp");
GameGraphics[2]->LoadFromFile(ExtractFilePath(ParamStr(0))
      + "image3.bmp");
GameGraphics[3]->LoadFromFile(ExtractFilePath(ParamStr(0))
      + "image4.bmp");
GameGraphics[4]->LoadFromFile(ExtractFilePath(ParamStr(0))
      + "image5.bmp");

for(d
      = 0; d < 4; d++)
{
GameGraphics[d]->TransparentColor
      = clFuchsia;
GameGraphics[d]->Transparent
      = true;
}
}

9.2 사각영역 복사(Copy Rectangle)로 투명화 만들기

CopyRect 로 투명 이미지를 만드는 것은 많이 어렵다. 이 것은 실제로 투명이미지를 위해 색깔을 사용하지 않고, 마스크를 사용한다. 이 의미는 2개의 이미지가 필요하다는 것이다, 하지만 하나는 검은색과 흰색으로만 되어 있어야한다.

이 흑과 백색으로 이루어진 이미지를 마스크라 부르고 이 마스크의 하얀 부분이 그려지지 않는 부분이다. 오리지날 이미지는 마스크의 위에 그려진다, 하지만 오리지날 이니미지는 마스크의 검은 부분에 겹쳐진 부분만 그려진다. 이 방법을 마스킹이라고 부른다! 마스킹의 예제가 위에 있다. 오른쪽 이미지는 오리지날 이미지이고 왼쪽 이미지는 마스크다.

CopyRect 에 마스크를 사용하는 의미는 우리가 같은 영역에 두번 그려야한다는 것을 말한다, 하지만 서로 다른 이미지이고 서로 다른 CopyMode 를 가진다. GameGraphics 배열의 4번째 이미지를 오프스크린에 투명하게 그리고 싶다면 우리는 MaskGraphics 의 TBitmap 배열을 만들어야할 것이고 여기서 우리는 마스크를 찾아야할 것이다. CopyRect 를 사용하여 마스킹을 하는 것은 아래와 같다.:

DELPHI
SourceRect := Rect(0, 0, GameGraphics[3].Width, GameGraphics[3].Height);
DestRect := Rect(0, 0, GameGraphics[3].Width, GameGraphics[3].Height);

// Draw the mask…
OffScreen.Canvas.CopyMode := cmSrcAnd;
OffScreen.Canvas.CopyRect(DestRect, MaksGraphics[3].Canvas, SourceRect);

// Draw the original image over the mask…
OffScreen.Canvas.CopyMode := cmSrcCopy;
OffScreen.Canvas.CopyRect(DestRect, GameGraphics[3].Canvas, SourceRect);

BCB
SourceRect = Rect(0, 0, GameGraphics[3]->Width,
      GameGraphics[3]->Height);
DestRect = Rect(0, 0, GameGraphics[3]->Width, GameGraphics[3]->Height);

// Draw the mask…
OffScreen->Canvas->CopyMode = cmSrcAnd;
OffScreen->Canvas->CopyRect(DestRect, MaksGraphics[3]->Canvas,
      SourceRect);

// Draw the original image over
      the mask…
OffScreen->Canvas->CopyMode = cmSrcCopy;
OffScreen->Canvas->CopyRect(DestRect, GameGraphics[3]->Canvas,
      SourceRect);

실제로 바뀐 것은 cmSrcAnd CopyMode 를 사용하는 것뿐이다. 이 것은 CopyRect 에 우리가 마스크를 그릴 것이라는 것을 말해준다. 실제로 어렵지 않다, 하지만 전에 얘기했듯이 Draw 메소드가 투명이미지를 만들기에 더 좋고 CopyRect 는 매우 느리다.

10. 선들과 사각형들에 대해서

때때로 게임에서 평지나 간단한 사각형 또는 선이 필요할 것이다, 예를 들면 우리가 selector 를 만들고 싶을 때다. 물론 선이나, 원 또는 사각형의 간단한 비트맵들을 투명이미지로 만들어서 사용할 수도 있다. 그것은 실행은 되겠지만, 깊숙히 들여다 보면 그방법은 어플리케이션에서 TBitmap 객체들을 저장하기위해 메모리가 필요할 것이고 따라서 어플리케이션의 크기는 증가한다. 또다른 문제는 크기를 늘릴려할 때다, 만약 당신이 큰 사각형이 필요하다면, 이미지를 리사징할 수 있겠지만 이 것은 이미지의 질이 매우 안좋게 된다.

다행스럽게도, Canvas 에 폼함된 함수들을 사용함으로써 이 문제를 피할 수 있게 되었다. 이 함수들은 line, rectangle, ellipse, pixel 등등이다. 필자는 이 모든 함수를 설명하지 않을 것이다. 왜냐하면 서로서로 비슷하기 때문이다. 필자는 단지 line 과 rectangle 함수만 설명할 것이다. 왜냐하면 이들이 가장 자주 사용되기 때문이다.

10.1 선들

캔바스에서 선을 사용하는 것은 쉽다. 단지 시작점과 끝점만 설정해주면 된다. 만약 시작점을 정해주지 않으면, 선은 0,0 점에서 시작되거나 또는 마지막으로 그린 선의 끝점에서 시작된다. 그래서 선이 그려졌을 때, 시작점은 그려진 라인의 끝점으로 설정된다. 이 것은 매우 좋은 것이다, 왜냐하면 선으로 사각형을 만들 때, 단지 사각형의 다음 점만 지정해 주면 되기 때문이다! 선의 시작점은 MoveTo(x, y) 의 함수로 서정하고 LineTo(x, y) 로 선을 그린다. 그럼 점(50, 50) 에서 점(100, 50) 으로 선을 그리고 싶다면, 우리는 아래처럼 수행해야 된다.:

DELPHI
OffScreen.Canvas.MoveTo(50, 50);
OffScreen.Canvas.LineTo(100, 50);

BCB
OffScreen->Canvas->MoveTo(50,
      50);
OffScreen->Canvas->LineTo(100, 50);

이 것은 점(50, 50) 에서 점(100, 50) 까지 멋진 직선을 그릴 것이다. 지금 우리가 점(100, 50) 에서 점(100, 100) 으로 또 다른 선을 그리고 싶다면 아래의 코드를 추가하면 된다.:

DELPHI
OffScreen.Canvas.LineTo(100, 100);

BCB
OffScreen->Canvas->LineTo(100, 100);

시작점이 마지막으로 그려진 선의 끝점으로 설정되었기 때문에, 시작점은 이미 (100, 50) 으로 설정되어 있다 그래서 우리는 이 시작점을 지정하지 않았다! 선을 그리는게 쉽지 않은가!

10.2 사각형들

사각형들 또한 매우 쉽다, 선 그리기보다 더 쉽다. 왜냐하면 시작점을 정해주지 않아도 되기 때문이다. 우리는 단지 Rectangle 함수로 시작점과 끝점만 정의해 주면 된다! Rectangle 함수는 아래와 같다:

procedure Rectangle(X1, Y1, X2, Y2: Integer);

하나의 사각형을 그리고 싶다면 시작점 (X1, Y1) 과 끝점(X2, Y2) 를 정해주면 된다. 시작점이 (50, 50) 이고 폭 50 높이 50인 사각형을 그리고 싶다면 아래와 같이 Rectangle 함수를 사용해야할 것이다.:

OffScreen.Canvas.Rectangle(50, 50, 100, 100);

이 것이 다다! 캔바스에 그리는 것은 정말 재미있다. 그렇지 않은가? 꼭 기억해야할 것은 GameLoop 에서 BitBlt 함수보다 먼저 모든 그리기가 와야지 스크린에 이미지와 그린 것들을 볼 수 있다는 점이다.

10.3 펜에 관해서

오케이, 캔바스에 선 또는 사각형을 그릴 때 우리는 Pen 이라는 객체를 사용한다. 하지만 선들과 사각형들을 그리기 시작했을 때 그들의 색깔은 검은색이고 선은 매우 얇을 것이다. 게임을 만들 때 사각형 또는 선들을 사용하고 싶을 텐데 선을 두깨를 바꾸고 색도 검은 색이 아닌 다른 색을 설장하는 방법을 배운다. 이 것 또한 무지 쉽다는 것을 알아주길 바란다.

선 또는 사각형을 다른 색깔로 그리기 위해서 우리는 OffScreen.Canvas.Pen.Color 의 설정으로 펜의 색깔을 바꿔야한다. 선 또는 사각형을 두꺼운 선으로 그리기 위해서 우리는 OffScreen.Canvas.Pen.Width 를 설정해야한다. 예를 들어 빨간색을 가진 두꺼운 선(Width = 4)인 사각형을 그리고 싶다면 다음과 같이 하면 된다.:

DELPHI
OffScreen.Canvas.Pen.Color := clRed;
OffScreen.Canvas.Pen.Width := 4;
OffScreen.Canvas.Rectangle(50, 50, 100, 100);

BCB
OffScreen->Canvas->Pen->Color = clRed;
OffScreen->Canvas->Pen->Width = 4;
OffScreen->Canvas->Rectangle(50, 50, 100, 100);

하지만 색깔과 폭은 펜의 속성이라는 것을 기억해야 한다. 이 말의 의미는 그들이 다시 바뀌기 전까지 그 속성이 그대로 기억된다는 뜻이다. 그래서 우리가 위처럼 사각형을 그리고 난 뒤 선을 그리려 한다면, 선은 색은 빨강색이고 4의 굵기를 가지게 될 것이다. 다시 평범한 선을 그리고 싶다면, 우리는 평범한 색깔(검은색)과 얇은(width=1) 을 다시 설정해주어야 한다.

11. Kylix 게임프로그래머가 주의할 점

이 튜토리얼은 전부다 Windows 캔바스에 대해서 얘기하고 있다. 편리하게 캔바스는 Kylix 에서도 구현되어 있다. 그래서 Windows 캔바스처럼 사용할 수가 있다. 하지만 이 튜토리얼에 소개된 모든 트릭들을 Kylix 에서 사용할 수 있는 것은 아니다. 하지만 문제는 필자가 알고 있기론 StopFlicker 함수가 Kylix 에서는 잘 되지 않는 다는 것이다. 왜냐하면 윈도우즈 메세지가 사용되었고 Kylix 에서 풀스크린을 만들기위한 GetSystemMetrics 함수와 같은 것을 지원하는지 필자가 모르겠고 ExtractFilePath(ParamStr(0)) 으로 경로를 가지고 올 수 있는지도 필자는 잘 모르겠다.

풀스크린 문제는 폼의 크기를 참고해서 쉽게 해결할 수 있다. 하지만 이 것이 OnCreate 이벤트에서 수행된다면 문제가 될 것이다. 당신은 이 것을 다른 곳에서 수행해야 될 것이다. 풀 스크린으로 실행하는 것은 몇가지 문제를 일을킬 수 있다, 왜냐하면 X 윈도우의 환경 때문이다. 필자는 아직 Kylix 프로그래머를 만나지 못했기 때문에, 문제에 대한 진솔한 답변을 해줄 수 없지만 문제들은 아마 쉽게 해결될 것이다.

대부분의 트릭들은 더블 버퍼링과 게임루프에 관한 것이므로 대부분 잘 동작할 것이다!

12. Canvas 게임 템플릿!

델파이 캔바스로 게임을 만들 때마다 이 튜토리얼에 나온 사항들을 준비하는 것은 큰 핸디캡이 될 것이다. 그래서 필자는 이 튜토리얼에 Game Template 를 포함한다. 고효율의 그리기와 깜빡임 없는 이미지를 그리기 위한 모든 준비가 이 Game Template 에 이미 되어 있다. 그래서 이 것은 당신이 새로운 게임을 만들 때 시간을 절약해 줄 것이고 이 튜토리얼을 이해하는데 도움을 줄 것이다. ;)

>>> DELPHI Game Template <<< >>> BCB Game Template <<<

13. 연락하기

이 튜토리얼에서 필자의 실수를 발견했거나 또는 질문, 아이디어, 하고싶으말 그리고 또 단지 인사말이라도 보내고 싶다면, 이메일을 보내주기 바란다. 이메일 주소는 lion.tiger@home.nl 이다. 또는 Lion Productions 웹싸이트인 http://lionprod.f2o.org 에 접속하면 된다. 필자는 또 DelphiGames 메일링 리스트(delphigames@yahoogroups.com)과 Delphi@Elists 메일링 리스트에서 만나볼 수 있다.

THE END

Copyright ⓒ 2002 Lion Productions

편역 : 김성철
저에게 질문이 있다면 paranwave@korea.com 으로 메일을 주세요 ;)


ASSERT(), VERIFY() and TRACE() for non-MFC Applications
gamedev.net

Using ASSERT(), VERIFY(), and TRACE() in non-MFC Applications

by Gabriel Fleseriu

C++ 환경에서 게임을 개발하게 되었을 때, 몇몇 사람들은 MFC 사용을 선택합니다. MFC 에는 ASSERT(), VERIFY() 그리고 TRACE() 매크로들이 있는데 이들은 지금도 매우 유용합니다. 그래서 저는 윈도우즈 플랫폼에서 작동하는 모든 프로젝트를 위해서 이 매크로 코드를 작성하기로 마음먹었습니다.

약간 알아야할 것들:

  • ASSERT() 는 인자가 유효하다고 가정합니다, 그리고 만약 인자가 0이면, 실행을 멈춥니다, Release 모드에서, assert 는 아무것도 수행하지 않습니다.
  • VERIFY() 는 ASSERT() 와 매우 비슷합니다, 다만 VERIFY() 는 Release 모드에서도, 파라미터가 유효하다고 가정합니다.
  • ASSERT() 를 사용할 때에는 인자에 어떠한 함수 호출도 포함하면 안 됩니다. 만약에 함수호출을 포함한다면, VERIFY() 를 사용해야 합니다. 그래야만 함수 호출이 Release 모드에서도 수행될 것이기 때문입니다.
  • TRACE() 는 printf() 와 똑 닮은 것입니다. 하지만 TRACE() 의 출력은 디버그윈도우에 된다는 것이 다릅니다. Release 모드에서, TRACE() 도 마찬가지로 아무것도 수행하지 않습니다.
  • 3개의 매크로, 어느 것도 Release 모드에서 어떠한 실시간 패널티를 의미하진 않습니다. 이 매크로들은 Debug 와 Release 모드에서 미리 정의된 _DEBUG 매크로에 의해서 구분됩니다. 이 매크로는 Microsoft Visual C++ 의 특정적인 것으로, 당신이 몇몇 다른 컴파일러에서 이 매크로(TRACE, VERIFY, ASSERT) 를 사용하기 위해서는 반드시 이 _DEBUG 매크로를 정의해야합니다.
  • 볼랜드 C++ 빌더에서는 Event-Log 창에 TRACE() 메세지가 출력된다는 것을 기억하세요 ;)

Header File

#ifndef _BCBDEBUG_H_
#define _BCBDEBUG_H_

#include <windows.h>
#include <stdio.h>
#include <stdarg.h>
#pragma hdrstop

static HRESULT hr;
#define SAFE_RELEASE(x) { if(x) x->Release(); x=NULL;
    }
#define JIF(x) if(FAILED(hr=(x))) \
{ Msg(TEXT("FAILED(hr=0x%x) in ") TEXT(#x)
    TEXT("\n"), hr); return hr; }
#define LIF(x) if(FAILED(hr=(x))) \
{ Msg(TEXT("FAILED(hr=0x%x) in ") TEXT(#x)
    TEXT("\n"), hr); }

void Msg(char *szFormat, ...)
{
TCHAR szBuffer[512];

va_list pArgs;
va_start(pArgs, szFormat);
vsprintf(szBuffer, szFormat, pArgs);
va_end(pArgs);

MessageBox(NULL, szBuffer, "[DEBUG MESSAGE]", MB_OK);
}

#ifdef _DEBUG
void _ctrace(char
    *message, ...);
void _trace(char
    *message, ...);
#define ASSERT(x) {if(!(x)) _asm{int
    0x03}}
#define VERIFY(x) {if(!(x)) _asm{int
    0x03}}
#else
#define ASSERT(x)
#define VERIFY(x) x
#endif

#ifdef _DEBUG
#define CTrACE _ctrace
#else
inline void _ctrace(LPCSTR message,
    ...) { }
#define CTRACE 1 ? (void)0 : _ctrace
#endif

#ifdef _DEBUG
#define TRACE _trace
#else
inline void _trace(LPCTSTR message,
    ...) { }
#define TRACE 1 ? (void)0 : _trace
#endif

#ifdef _DEBUG
void _trace(char *message, ...)
{
char out[1024];
va_list body;
va_start(body, message);
vsprintf(out, message, body);
va_end(body);
OutputDebugString(out);
}

void _ctrace(char *message,
    ...)
{
//콘솔 TRACE
//후에 추가하겠습니다 ;-)
}
#endif

#endif //BCBDEBUG

편역 : 김성철
저에게 질문이 있다면 여기로 메일을 보내주세요 ;)


CreateParams 맛보기

CreatParams 맛보기

개요

CreatParams 함수는 윈도우 생성시 필요한 값들을 설정하는 기능을 가지고 있습니다. 이 함수는 TWinControl 클래스의 멤버 함수로 커스텀 컨포넌트를 만들시에 아주 많이 사용되는 함수입니다. 여기서는 CreateParams 함수의 간단한 사용 예제를 살펴보며, 후에 더욱더 자세하게 다룰 때가 있을 것입니다.

캡션바가 없으면서 사이즈(크기)를 조절할 수 있게 폼을 만들려면 어떻게 해야할까요? 오브젝트 인스펙터에서 속성 설정만으로는 이러한 폼을 만들 수 없습니다. 그러면 무엇을 하여야 하는 것일까?... 바로 CreateParams 라는 함수를 오버로딩하여 사용하는 것입니다. CreateParams 의 함수 안에서 새롭게 폼의 스타일을 지정해 주는 것으로 모든 것이 끝납니다. 다만 CreateParams 함수를 오버로딩할 때 주의할 점은 바로 부모클래스의 CreateParams 함수를 반드시 호출해 주어야하는 것입니다.

예제 코드를 보시죠!

예제

Unit1.h 파일

#ifndef Unit1H
#define Unit1H
//---------------------------------------------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
//---------------------------------------------------------------------------
class TForm1 : public TForm
{
__published: // IDE-managed Components
private: // User declarations
virtual void __fastcall CreateParams(TCreateParams&
    Params);
public: // User declarations
__fastcall TForm1(TComponent* Owner);
};
//---------------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//---------------------------------------------------------------------------
#endif

Unit1.cpp 파일

//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TForm1::CreateParams(TCreateParams& Params)
{
TForm::CreateParams(Params);
    //Note!
Params.Style = WS_THICKFRAME|WS_POPUP|WS_BORDER;
}

작성자 : 김성철
질문이 있다면 메일을 보내주세요 ;)


OpenGL 환경 설정

제 목 : C++빌더에서의 OpenGL 환경 설정
작성자 : 김성철
참고문헌 : OpenGL Super Bible.
포함자료 : 클릭하세요

윈도우즈에는 OpenGL 이 설치되어 있습니다. 하지만 C++빌더용 라이브러리는 설치되어 있지 않습니다.
그것은 사용자가 직접 설치해 주어야 하는데 설치법은 숨쉬기 보다 더 쉽습니다.

■ OpenGL 및 GLUT 설치하기

빌더에는 OpenGL 에 대한 헤더파일이 포함되어 있습니다.

(빌더의 설치경로가 이와 같다고 가정합니다))

빌더가 설치된 경로 아래 Include\Gl 의 폴더를 보시면 OpenGL 에 관련된 헤더파일이 있습니다.
하지만 GLUT 에 대한 헤더파일은 없습니다.
자 그럼 이제부터 GLUT 및 OpenGL 라이브러리를 설치해 보겠습니다.

우선 위의 포함자료를 받으시길 바랍니다. 포함자료에는 GLUT 의 라이브러리 및 헤더파일이 들어 있습니다.

● GLUT 설치하기

GLUT32.LIB 파일을 아래의 경로에 복사해 주세요

GLUT32.DLL 파일을 아래의 경로에 복사해 주세요

GLUT32.H 파일을 아래의 경로에 복사해 주세요

GLUT 의 설치가 모두 끝났습니다.
그럼 이제부터 빌더용 OpenGL 라이브러를 만들어 보겠습니다.

● C++빌더용 OpenGL 라이브러리 만들기

위의 경로에 가면 OpenGL32.DLL 및 GLU32.DLL 파일이 있습니다.
이 곳에서 implib 도구를 사용하여 빌더용 OpenGL 라이브러리를 만들 수 있습니다.

implib opengl32.lib opengl32.dll

implib glu32.lib glu32.dll

이렇게 만들어진 라이브러리(*.LIB) 파일들을 아래의 경로에 복사합니다

OpenGL 의 라이브러리 설치가 모두 끝났습니다. 그럼 이제 OpenGL 을 사용하는 간단한
프로그램을 만들어 보겠습니다.


Set 클래스 사용하기

개요

Set 클래스는 델파이에 있는 Set(집합) 자료형을 클래스로 구현한 것입니다. Set 은 집합 데이타를 다루기 위한 자료형입니다. 주의할 점은 Set 은 오직 순서를 지닌 값만을 가질 수 있다는 것입니다. 예를 들어, 문자형, 정수형, 열거형 타입. 가장 작은 값은 0 보다는 커야 하고 가장 큰 값은 255 보다는 작아야 합니다. BCB 의 Set 클래스는 C++ 의 템플릿 기능을 사용하여 구현되었습니다. 따라서 Set 클래스를 사용하기 위해서는 3가지의 값을 가정해야 합니다.

  1. 자료형 ( Type : 일반적으로 char 이거나 enum 타입 )
  2. 가장 작은 값
  3. 가장 큰 값

보통 BCB 에서 Set 클래스는 typedef 문과 같이 사용됩니다. 아래에 사용되는 예가 있습니다.

typedef Set<char, 'A', 'Z'> UPPERCASESet;
UPPERCASESet s;

Set 자료형이 중요한 이유는 매우 많이, 자주 사용되기 때문입니다. 지금 오브젝트 인스펙터를 살펴보세요. Font 라는 속성이 있습니다. 이 또한 Set 자료형을 사용하여 만들어진 속성입니다. 라벨 컴포넌트의 폰트 속성을 변경하기 위해서는 다음과 같이 합니다.

/* BCB 의 경우 */

Label->Font->Style = Label->Font->Style << fsBold; //fsBold 속성을 추가합니다.
Label->Font->Style = Label->Font->Style >> fsBold; //fsBold 속성을 제거합니다.
Label->Font->Style.Clear(); //모든 속성을 삭제합니다.

//-------------------------------------------------------------------------------------

{ 델파이의 경우 }

Label.Font.Style := Label.Font.Style + [fsBold]; //fsBold 속성을 추가합니다.
Label.Font.Style := Label.Font.Style - [fsBold]; //fsBold 속성을 제거합니다.
Label.Font.Style := []; //모든 속성을 삭제합니다

예제

폼의 볼더 스타일도 Set 을 사용한 속성입니다. 이를 동적으로 바꾸어 바로바로 실행 중인 폼에 적용되는 프로그램을 만들어 봅니다.

DFM 파일

object MainForm: TMainForm
Left = 278
Top = 201
Width = 308
Height = 304
Caption = 'C++ 빌더 BorderStyle 예제'
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'MS Sans Serif'
Font.Style = []
OldCreateOrder = False
PixelsPerInch = 96
TextHeight = 13
object gbBorderIcons: TGroupBox
Left = 16
Top = 9
Width = 129
Height = 256
Caption = 'BorderIcons'
TabOrder = 0
object cbSystemMenu: TCheckBox
Left = 16
Top = 24
Width = 97
Height = 17
Caption = 'biSystemMenu'
TabOrder = 0
OnClick = cbMinimizeClick
end
 object cbMinimize: TCheckBox
Left = 16
Top = 56
Width = 97
Height = 17
Caption = 'biMinimize'
TabOrder = 1
OnClick = cbMinimizeClick
end
 object cbMaximize: TCheckBox
Left = 16
Top = 96
Width = 97
Height = 17
Caption = 'biMaximize'
TabOrder = 2
OnClick = cbMinimizeClick
end
object cbHelp: TCheckBox
Left = 16
Top = 136
Width = 97
Height = 17
Caption = 'biHelp'
TabOrder = 3
OnClick = cbMinimizeClick
end
end
object rgBorderStyle: TRadioGroup
Left = 160
Top = 8
Width = 121
Height = 257
Caption = 'BorderStyle'
Items.Strings = (
'bsNone'
'bsSingle'
'bsSizable'
'bsDialog'
'bsToolWindow'
'bsSizeToolWin')
TabOrder = 1
OnClick = rgBorderStyleClick
end
end

tip! 이 내용을 그대로 복사하여 폼위에서 붙이기(Ctrl+V)를 해보세요. 마법이 일어납니다 ;)

Unit1.h 파일

//---------------------------------------------------------------------------
#ifndef Unit1H
#define Unit1H
//---------------------------------------------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include <ExtCtrls.hpp>
//---------------------------------------------------------------------------
class TMainForm : public TForm
{
__published: // IDE-managed Components
TGroupBox *gbBorderIcons;
TRadioGroup *rgBorderStyle;
TCheckBox *cbSystemMenu;
TCheckBox *cbMinimize;
TCheckBox *cbMaximize;
TCheckBox *cbHelp;
void __fastcall cbMinimizeClick(TObject
    *Sender);
void __fastcall rgBorderStyleClick(TObject
    *Sender);
private: // User declarations
public: // User declarations
__fastcall TMainForm(TComponent*
    Owner);
};
//---------------------------------------------------------------------------
extern PACKAGE TMainForm *MainForm;
//---------------------------------------------------------------------------
#endif

unit1.cpp 파일

//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TMainForm *MainForm;
//---------------------------------------------------------------------------
__fastcall TMainForm::TMainForm(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TMainForm::cbMinimizeClick(TObject *Sender)
{
TBorderIcons IconSet; //TBorderIcons
    는 집합형 자료형, IconSet 집합 자료 생성

IconSet.Clear(); //초기화

     if( cbSystemMenu->Checked
    )
{
IconSet = IconSet <<
    biSystemMenu; //속성 추가
}
if( cbMinimize->Checked )
{
IconSet = IconSet <<
    biMinimize; //속성 추가
}
if( cbMaximize->Checked )
{
IconSet = IconSet <<
    biMaximize; //속성 추가
}
if( cbHelp->Checked )
{
IconSet = IconSet <<
    biHelp; //속성 추가
}

BorderIcons = IconSet; //속성 설정

}
//---------------------------------------------------------------------------
void __fastcall TMainForm::rgBorderStyleClick(TObject *Sender)
{
BorderStyle = TBorderStyle(rgBorderStyle->ItemIndex);
}
//---------------------------------------------------------------------------

스크린샷

작성자 : 김성철
참고 문헌 : Delphi 5.0 Bible by Xavier Pacheco (류광 편역, 정보문화사)
질문이 있다면 메일을 보내주세요 ;)


서브클래싱의 이해

개요

서브클래싱이란 간단히 말해서, 메세지를 훔쳐오는 것입니다. 예를 들어, 숫자만 입력할 수 있는 에디트 컨트롤을 만들고자 한다면 우리는 'A' 라는 문자가 눌렸을 때, 이 정보(메세지)가 에디트 컨트롤에 전달되지 못하도록 설정해야합니다. VCL 이 제공하는 모든 윈도우 컨트롤들은 WndProc 이라는 윈도우 프로시져가 있습니다. 이 멤버 함수가 하는 일은 관련 컨트롤에 들어온 메세지들을 처리하는 것입니다. 그렇다면 'A' 라는 문자가 입력되었다라는 메세지는 에디트 컨트롤의 윈도우 프로시져로 전달될 것입니다. 하지만 우리는 이 메세지가 전달 되는 것을 원하지 않습니다. 어떻게 해결하면 좋을까요? 해결 방법은 간단합니다. 에디트 컨트롤의 윈도우 프로시져를 바꾸어주는 것입니다. 새롭게 만든 윈도우 프로시져에서는 'WM_CHAR' 메세지가 전달 되었을 때 'A' 문자나 기타 숫자가 아닌 데이타가 있다면 에디트 컨트롤로 전달하지 않도록 구현하면 그만입니다. 그러면 새로 만든 윈도우 프로시져와 에디트 컨트롤의 원래 프로시져를 바꾸어야 한다는 것인데요. 이 것은 SetWindowLong 이란 함수가 우리를 대신해서 해결해줍니다.

구현

DFM 파일 내용

object Form1: TForm1
object Edit1: TEdit
Left = 164
Top = 108
Width = 121
Height = 21
ImeName = '한국어(한글) (MS-IME98)'
TabOrder = 0
Text = 'Edit1'
end
object Button1: TButton
Left = 344
Top = 108
Width = 75
Height = 25
Caption = '서브클래싱'
TabOrder = 1
OnClick = Button1Click
end
object Button2: TButton
Left = 344
Top = 148
Width = 75
Height = 25
Caption = '해제'
TabOrder = 2
OnClick = Button2Click
end
end

tip! : 위의 내용 중 두꺼운 글씨체의 부분을 복사하여 폼위에서 붙이기(Ctrl + V) 를 해 보세요 ;)

에디터를 서브클래싱하는 예제 소스

#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
#ifdef STRICT
WNDPROC OldEditProc; //Edit1
  의 원래 프로시져를 담을 프로시져 포인터
#else
FARPROC OldEditProc;
#endif
LRESULT CALLBACK EditSubProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_KEYDOWN:
if(wParam
  == VK_RETURN)
{
MessageBox(hWnd,
  "엔터를 눌렀습니다", "Edit",MB_OK);
SetFocus(hWnd);
}
break;
case
  WM_CHAR:
if(
  !isdigit((int)wParam) ) return 0;
}
//나머지 기능은 원래 프로시져에게
  맡긴다.
return CallWindowProc(OldEditProc, hWnd, msg, wParam,
  lParam);
}
//---------------------------------------------------------------------------
//서브클래싱시작
void __fastcall TForm1::Button1Click(TObject *Sender)
{
//프로시져를 서로 바꾼다
OldEditProc =(FARPROC)SetWindowLong(Edit1->Handle, GWL_WNDPROC, (LONG)EditSubProc);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
//프로시져를 원래대로 해 놓는다
SetWindowLong(Edit1->Handle, GWL_WNDPROC, (LONG)OldEditProc);
}
//---------------------------------------------------------------------------

작성자 : 김성철
질문이 있다면 메일을 보내주세요 ;)


Turtle Garphics basic

Turtle Graphics
http://opengl.cafe2.net

Turtle Graphics

by 김성철

Turtle Graphics?

거북이 그래픽스는 전진과 회전의 두가지 동작으로만 그래픽을 그리는 것으로 그래픽스 프로그래밍의 가장 기초가 된다. 가장 기초가 되지만 재귀 호출과 함께 사용되면 너무나 신비한 그림을 만들어낸다. 아마 그 놀라움에 입을 다물지 못 할 것이다. 이번 장에서는 필자가 파이썬의 터틀그래픽스 라이브러리를 축약하여 BCB 용으로 만든 터틀그래픽스 라이브러리의 설치와 사용법을 알아보고 간단한 거북이 그래픽스를 해볼 것이다. 거북이 그래픽스를 활용하는 것은 프랙탈을 배울 때까지 우선 미루어 놓자.

BCB 거북이 그래픽 라이브러리 설치

거북이 그래픽 라이브러리 받기

필자가 만든 거북이 그래픽스 라이브러리를 받으면 위의 3가지 파일이 압축되어 있다. 압축을 푼 후 거북이 그래픽스를 할 프로젝트 디렉토리에 복사하길 바란다. 그런 후 아래처럼 프로젝트 메니져에 Turtle.lib 파일을 추가한다.

그런 다음 메인 폼의 유닛 파일에 다음과 같이 코드를 입력한다. 여기서 말하는 프로젝트는 그래픽스 템플릿 으로부터 파생된 프로젝트를 말한다.

header file
#include "Turtle.h"

class TMainForm : public TForm
{
...
Graphics::TBitmap* OffScreen;
INT16 VideoModeX, VideoModeY;
/**/Turtle turtle;
...
};

source file
void __fastcall TMainForm::FormCreate(TObject *Sender)
{
...

OffScreen = new Graphics::TBitmap();
OffScreen->Width = VideoModeX;
OffScreen->Height = VideoModeY;

     /**/turtle.AttachCanvas(OffScreen->Canvas);
/**/turtle.SetStartPoint(OffScreen->Width/2-15,
      OffScreen->Height/2-40);

...
}

이 것으로 거북이 그래픽스를 할 모든 준비가 끝났다. 위에서 turtle 은 거북이 그래픽스 라이브러리의 인스턴스로 우리와 즐겁게 놀 거북이이다 ;) turtle.AttachCanvas() 메서드는 거북이가 돌아다닐 모래사장을 정하는 것과 같다. 우리는 오프스크린 비트맵에 그릴 것이므로 그것의 캔바스를 거북이에게 설정했다. turtle.SetStartPoint() 메서드는 거북이가 출발할 위치를 설정한다. 폼의 중간에서 약간 좌상단 쯤 되는 위치다.

Turtle 클래스 메서드 소개

  • Turtle(void)
    거북이를 생성한다.
  • Turtle(TCanvas* Canvas)
    거북이를 생성하지만 생성시 거북이가 돌아다닐 캔바스를 설정할 수 있다.
  • void AttachCanvas(TCanvas* Canvas)
    거북이가 돌아다닐 캔바스를 설정한다.
  • TCanvas* GetCanvas(void)
    현재 거북이가 돌아다니고 있는 캔바스를 얻어 온다
  • void SetStartPoint(int x, int y)
    거북이가 출발할 시작 위치를 설정한다.
  • void Forward(double length)
    length 만큼 앞으로 전진한다.
  • void Backward(double length)
    length 만큼 뒤로 후진한다.
  • void Left(double angle)
    angle 만큼 왼쪽(반시계방향)으로 회전한다.
  • void Right(double angle)
    angle 만큼 오른쪽(시계방향)으로 회전한다.
  • void Up(void)
    거북이를 들어 올린다;) 즉 이 함수를 실행하면 Forward() 및 Backward() 를 해도 거북이가 지나간 흔적이 그려지지 않는다.
  • void Down(void)
    거북이를 내려 놓는다;) 즉 이 함수를 실행하면 Forward() 및 Backward() 를 하면 거북이가 지나간 흔적이 그려진다.
    거북이가 생성될 때 기본 모드는 Down() 으로 되어 있다.

즐거운 Turtle 그래픽스의 세계로 ;)

자~ 이제 거북이 그래픽스의 세계로 떠날 모든 준비가 끝난 것 같다. 그럼 즐겁게 거북이를 움직여 볼까? ;) 참고로, 여기에 나오는 코드는 모두 DrawGraphics() 의 메서드안에서 수행되는 것이다.

우선 다음 코드를 입력해보자.

turtle.Forward(100);
turtle.Right(360/3);
Sleep(50);
Flip();

result

위처럼 정 삼각형이 그려졌는가? 이 번에는 사각형을 그려보자.

turtle.Forward(100);
turtle.Right(360/4);
Sleep(50);
Flip();

result

거북이 그래픽스가 더욱더 재미 있다. 삼각형은 각이 3개인 도형이다. 따라서 360/3 을 하면 삼각형을 그리기 위해 필요한 회전각을 구할 수 있다. 사각형은 각이 4개인 도형이다. 역시 360/4 를 하면 사각형을 그리기 위한 회전각을 구할 수 있다. 그렇다면 원은 어떻게 그릴까? 예전에 그리스의 수학자 아르키메데스는 원의 넓이를 구하기 위해서 넓이를 구하고자 하는 원에 내접하는 사각형에서 하나하나 각을 늘려나가면서 이 각의 수를 무한대로 생각하여 원의 넓이를 구했다. 그렇다 원은 각이 무한개로 있는 도형으로 생각할 수 있다. 하지만 여기서는 360 개의 각이 있는 원을 그려보겠다. 360/360 = 1 로 회전각은 1이다.

turtle.Forward(1);
turtle.Right1(1);
Sleep(50);
Flip();

result

회전각이 작을 수록 더 이쁜 원이 그려질 것이다. 밤하늘에 무엇이 뜨는가? 별이 뜬다. 자 그럼 별을 그려볼까?

turtle.Forward(50);
turtle.Right(144);
Sleep(50);
Flip();

result

이제부터 단순한 반복이 낳는 아름다움을 살펴볼 것이다. 루프의 수와 회전각의 관계를 관심 있게 봐주길 바란다.

for(int t=0; t<6; t++) //DrawGraphics
      에서 그린다면 이 루프는 생략가능하다.
{
for(int i=0; i<6; i++)
{
turtle.Forward(40);
turtle.Right(60);
Sleep(50);
Flip();
}
turtle.Right(60);
}

result

60 * 6 = 360 임을 안다면 루프카운터와 회전각의 관계를 알 것이다. 각이 더 많고 회전각이 작은 도형들을 그려보자.

for(int i=0; i<12; i++)
{
turtle.Forward(25);
turtle.Right(30);
Sleep(50);
Flip();
}
turtle.Right(360/12);

result

참 신기하지 않은가? 그럼 이번에는 가장 밖에 있는 turtle.Right() 메서드에 임의의 값을 주어보자.

for(int i=0; i<12; i++)
{
turtle.Forward(25);
turtle.Right(30);
Sleep(50);
Flip();
}
turtle.Right(360/24);

result

turtle.Up() 과 turtle.Down() 메서드로 점선 도형 그리기

for(int i=0; i<5; i++)
{
turtle.Forward(5);
turtle.Up();
turtle.Forward(5);
turtle.Down();
Sleep(50);
Flip();
}
turtle.Right(360/4);

result

끝맺음

거북이 그래픽스를 이용해 간단한 도형그리기를 해 보았다. 느낌이 어떤까? 각의 수와 회전각등을 조절해서 여러 도형을 그려보길 바란다. 거북이 그래픽스는 여기서 끝나는 것이 아니다. 재귀호출을 만나면 거의 환상적인 모습으로 변신한다. 프랙탈은 후에 다루기로 하자! 필자가 아직 공부를 못 했다 ;) 그리고 위의 코드는 모두 다 DrawGraphics 메서드 안에서 수행됨을 말하며 루프문의 수행시간이 길어 진다면 루프의 안에 Application->ProcessMessages(); 를 넣어주자. 그래야 루프 수행 중에도 예제 어플리케이션을 제어할 수 있다.

저에게 질문이 있다면 paranwave@korea.com 으로 메일을 보내 주세요 ;)


GhostMouse 만들어 보기

스택 및 큐를 응용하여 고스트마우스 만들기 - 김성철( paranwave@korea.com )

98 년이면 프로그래밍에 막 입문했을 때구나.. 지금 보면 유치찬란에 극을 달리지만 그 때의 열정이 사라진 것 같아 내 자신에게 조금 서운하다. 귀엽게 봐 주길... ;]

소스 다운로드 프로그램받기[ 받기(소스포함) ]

스택은 하나의 자료 구조입니다. 우리가 배열을 말 할때 같은 형의 자료만 모을 수 있는 자료 구조 라고 말하듯 이 스택도 하나의 자료 구조입니다. 하지만 스택은 자신이 행위적 측면을 포함하는 자료 구조라는 것이 좀 다르죠. 여기서 행위적 측면이라는 것이 바로 알고리즘을 말합니다. 스택은 pop() 과 push() 라는 행위적 측면이 있습니다. pop()은 자료를 스택에서 하나 꺼내오는 것이고 push()는 자료를 스택에 하나 집어 넣는 것을 말합니다. 그리고 스택은 자료를 꺼내고 저장 하는데 방법이 있습니다. 바로 처음 들어온 자료가 제일 마지막에 나간다입니다. 바로 이것을 LIFO(Last In First Out) 구조라고 합니다.

위의 그림을 보면 4를 한번 push() 하고 다시 pop() 하였습니다. 여기서 중요한 것은 바로 top 이라는 것입니다. 이 top이라는 것은 스택의 꼭대기를 가리키고 있는 인덱스가 되겠습니다. 그래서 스택이 꽉 차면 더이상 push() 할 수 없으므로 top() 값을 조사하여 지금 스택이 Full 인지 아니면 Empty 인지 알아봅니다. 1차 예제는 배열을 사용하여 제한된 크기를 가지는 스택을 만들어 보겠습니다.

<예제 1 배열로 구현하는 스택>

#include <stdio.h>
#define MAX 10

int stack[10]; //우리가 사용할 스택 크기는 int형 변수를 10개 넣을 수
  있는 20바이트(16bit컴파일러 기준)
int top; //스택의 상단을 가리킬 변수

void init_stack(void)
{
top = -1; //top의 초기값을 -1로 세팅
}

int push(int t)
{
if( top >= MAX - 1) //스택이
  꽉 차있는지를 조사하고 있다
{
printf("\t\n Stack overflow.");
return -1;
}
stack[++top] = t; //인덱스를 먼저
  증가시키고 자료저장 top을 왜 -1로 했는지 아시겠죠?
return t;
}

int pop(void)
{
if(top < 0) //스택이 텅 비어
  있는지를 조사하고 있다
{
printf("\n Stack underflower.");
return -1;
}
return stack[top--]; //자료를
  꺼내고 인덱스 하나 감소
}

void print_stack(void) //스택의 현재 모습을 보여준다.
{
int i;
printf("\n Stack contents : top -----> Bottom\n");
for(i= top; i>=0; i--)
printf("%-6d",stack[i]);
}

void main(void)
{
int i;
init_stack();
printf("\nPush 5,4,7,8,2,1");
push(5);
push(4);
push(7);
push(8);
push(2);
push(1);
print_stack();
printf("\nPop");
i = pop();
print_stack();
printf("\n popping value is %d",i);
printf("\push 3, 2, 5, 7, 2");
push(3);
push(2);
push(5);
push(7);
push(2);
print_stack();
printf("\Now stack is full, push 3");
push(3);
print_stack();
printf("\nInitialize stack");
init_stack();
print_stack();
printf("\nNow stack is empty, pop");
i = pop();
print_stack();
printf("\n popping value is %d", i);
}

결과는 여기에 적지 않겠습니다. 직접 해보시길 바랍니다. ^^ 그래야 실력이...^^ 이 스택은 배열로 구현하였습니다. 그럼 이 스택은 좋은 스택이 될 수 있을까요? 대답은 아니오 입니다. 상황에 따라 그럴 수도 있겠지만 되부분 유용한 스택이 되질 못 합니다. 왜 그럴까요? 그것은 배열은 크기가 한 정되어 있어서 메모리 낭비를 초래할 수도 있습니다. 예를 들어 사용자는 3개의 자료만 저장하는 스택이 필요한데 설계자는 이 것을 모르고 10개의 자료를 저장하는 스택을 만들어 주었다고 생각해 봅시다. 그러면 7개의 자료를 저장하는 메모리가 낭비가 되는 것이겠죠. 그럼 때에 따라 스택의 크기가 달라 져야 하는 데 그렇게 할려면 어떻게 해야 할까요? 바로 동적인 메모리 할당입니다.(동적인 메모리 할당은 프로그램 수행 도중에 메모리를 잡는 것을 말합니다.)그리고 링크드 리스트라는 것을 사용해서 스택을 구현합니다. 다음에는 링크드리스트를 이용하여 스택을 구현해 보겠습니다.

링크드 리스트를 이용하여 스택을 구현하면 다음과 같은 이점이 있습니다.

  1. 현재 스택에 저장되어 있는 자료 만큼만 메모리를 잡아먹기 때문에 메모리가 절약된다.
  2. 메모리가 허용하는 한도에서 커질 수 있다.

연결리스트를 이용하는 스택의 모양은 아래의 그림과 같습니다.

< 예제 2. 링크드 리스트를 이용한 스택 >
#include <stdio.h>
#include <alloc.h>

//노드의 모양새(?)를 만들고 있다.
typedef struct tagNode
{
int data; //노드가 저장할 자료
struct tagNode *next; //다음
  노드를 가리킬 포인터 (이부분은 기초문법서적을 참고하세요^^)
}node;

node* head; //head 노드와 tail 노드를 선언하고 있다.
node* tail;

void init_stack(void) //노드를 초기화 해주는 함수
{
head = (node *)malloc(sizeof(node));
  //head와 tail노드를 메모리에 잡고 있다
tail = (node *)malloc(sizeof(node));
  //원래 head와 tail은 메모리에 잡을 필요가 없다
head->next = tail; //이들은
  자료를 갖지 않고 단지 가리킬 뿐이기 때문이다.
tail->next = tail; //하지만
  여기서는 이것이 더 쉽기 때문에 ^^....
}

//자료를 push 하는 함수
int push(int k)
{
node* temp; //노드를 추가시키기 위하여
  임시 노드를 만듦
if((temp = (node *)malloc(sizeof(node))) == NULL) //만약
  메모리가 없을 때에는 에러를 출력
{
printf("\n Out of Memory...");
return -1;
}
temp->data = k;
temp->next = head->next; //이부분을
  잘 이해하셔야 합니다.
head->next = temp; //이부분은
  노드를 링크드리스트에 추가시키는 부분입니다.
return k;
}

//자료를 pop하는 함수
int pop(void)
{
node* temp;
int i;
if(head->next == tail) //head의
  다음이 tail이면 링크드 리스트가 빈 것이므로 에러를 출력
{
printf("\n Stack underflow.");
return -1;
}
temp = head->next; //이부분을
  잘 이해하셔야 합니다.
i = temp->data; //이부분은
  링크드 리스트에서 노들르 지우는 것입니다.
head->next = temp->next;
free(temp);
return i;
}

//모든 노드를 지우는 함수
void clear_stack(void)
{
node* current; //현재 노드를 가리키는
  포인터
node* old; //이전 노드를 가리키는 포인터
current = head->next; //현재
  노드를 head의 다음으로 하고 있다
while( current != tail) //현재
  노드가 tail 일 때까지
{
old = current; //
  현재 노드를 가리키는 포인터를 예전 노드를 가리키는 포인터에 저장
current = current->next;
  //현재노드를 현재노드의 다음 노드로 옮김
free(old); //
  예전 노드 삭제
}
head->next = tail;
}

//모든 노드를 보여주는 함수
void print_stack(void)
{
node* temp;
temp = head->next;
printf("\n Stack contents : Top ----> Bottom\n");
while( temp != tail)
{
printf("%-6d",temp->data);
temp = temp->next;
}
}

void main(void)
{
int i;
init_stack();
printf("\n Push 5, 4, 7, 8, 2, 1");
push(5);
push(4);
push(7);
push(8);
push(2);
push(1);
print_stack();
printf("\n Pop");
i = pop();
print_stack();
printf("\n popping value is %d", i);
printf("\nPush 3, 2, 5, 7, 2");
push(3);
push(2);
push(5);
push(7);
push(2);
print_stack();
printf("\nPush 3");
push(3);
print_stack();
printf("\nInitialize stack");
clear_stack();
print_stack();
printf("\nNow stack is empty, pop");
i = pop();
print_stack();
printf("\n popping value is %d",i);
}

링크드 리스트를 이용한 스택을 구현해 봤습니다. 아주 좋아보이죠? 메모리의 제약도 거의 받지 않고 메모리가 무한대라면 스택을 무한대로 늘릴 수 있는 방법입니다. 하지만 이것의 문제가 아니라 티가 있다면 무엇일까요? 이 소스를 망치에 비유하자면 이렇습니다. 망치를 쓸 때마다 망치 자체를 조립한 다음에 망치를 써야 합니다. 하지만 망치가 필요할 때 우리는 그냥 망치를 쓰지 누가 망치를 그 때마다 조립해서 씁니까? 그럼 어떻게 해야할까요? 음... 스택을 객체로 정의하면 어떨까요? 나중에 다른 프로그램에서 필요할 때 스택 클래스만 갖다가 쓰면 되겠죠..^^ 그럼 망치가 필요할 때 바로 망치만 가져다 쓰듯이 우리도 스택이 필요할 때는 스택만 가져다 쓸 수 있습니다. 다음에는 스택을 클래스로 구현해 보겠습니다.

< 예제 3. 클래스화 한 스택 >

#include <stdio.h>

#ifndef STACK
#define STACK
typedef struct tagNode
{
int data;
tagNode* next;
}node;

class kStack
{
public:
//constructure | destructure
kStack();
~kStack();
//operation
int Pop(void);
int Push(int k);
void PrintStack(void);
void ClearStack(void);
private:
//attribute
node* head;
node* tail;
};

kStack::kStack() //생성자에서 스택링크드리스트의 head와 tail을 초기화
  하고 있습니다.
{
head = new node[1];
tail = new node[1];
head->next = tail;
tail->next = tail;
}

kStack::~kStack() //소멸자에서 head와 tail 에 해준 동적 메모리 할당을
  지우고 있습니다. *중요*
{
delete [] head;
delete [] tail;
}

int kStack::Push(int k)
{
//C++의 new 연산자가 더 편하고 좋기 때문에
  malloc 대신에 이것을 사용했습니다
node* temp = new node[1];
if(temp == NULL)
{
printf("\n Out of Memory...");
return -1;
}
temp->data = k;
temp->next = head->next;
head->next = temp;
return k;
}

int kStack::Pop(void)
{
node* temp;
int i;
if(head->next == tail)
{
printf("\n Stack underflow.");
return -1;
}
temp = head->next;
i = temp->data;
head->next = temp->next;
delete [] temp;
return i;
}

void kStack::PrintStack(void)
{
node* temp;
if(head->next == tail)
{
printf("\n Stack is Empty");
return;
}
temp = head->next;
printf("\n Stack contents : Top ----> Bottom\n");
while(temp != tail)
{
printf("%-6d",temp->data);
temp = temp->next;
}
}

void kStack::ClearStack(void)
{
node* current;
node* old;
if(head->next == tail)
{
printf("\n Stack is Empty
  do not clear");
return;
}
current = head->next;
while(current != tail)
{
old = current;
current = current->next;
delete [] old;
}
head->next = tail;
}

void main()
{
int i;
kStack stack;
printf("\n Push 5, 4, 7, 8, 2, 1");
stack.Push(5);
stack.Push(4);
stack.Push(7);
stack.Push(8);
stack.Push(2);
stack.Push(1);
stack.PrintStack();
printf("\n Pop");
i = stack.Pop();
stack.PrintStack();
printf("\n popping value is %d", i);
printf("\nPush 3, 2, 5, 7, 2");
stack.Push(3);
stack.Push(2);
stack.Push(5);
stack.Push(7);
stack.Push(2);
stack.PrintStack();
printf("\nPush 3");
stack.Push(3);
stack.PrintStack();
printf("\nInitialize stack");
stack.ClearStack();
stack.PrintStack();
printf("\nNow stack is empty, pop");
i = stack.Pop();
stack.PrintStack();
printf("\n popping value is %d",i);
}
#endif //STACK

어떻습니까? 클래스로 구현하니깐 의미가 더욱더 정확해 지지 않았습니까? 하지만 이 클래스에도 문제가 있습 니다. -_-;; 자꾸만 무슨 문제냐구요...? 왜 자꾸 따지냐구요? 그야 더 좋게 만들기 위해서지요 ^^;; 그럼 문제점을 말해 보겠습니다. 사실 이 스택은 자료를 int형의 자료 밖에 저장하지 못 합니다. 그런데 실제 프로그램에서는 그렇지 않거든요. 하나의 윈도우의 정보를 저장한다거나... 아니면 다른 여러가지 정보가 섞인 자료를 스택에 저 장 할 수도 있습니다. 그럼 그 때마다 그에 맞는 스택 클래스를 만들어 줘야 할까요???? 그렇다면 프로그래머들 은 스트레스 때문에 죽을 것입니다. 그 때 이용하는 것이 C++의 다형성이라는 것입니다. 여기서는 다형성의 극 치인 바로 template(템플릿) 을 사용하는 것입니다. 템플릿을 사용하여 클래스를 만들면 어떤 자료형이 들어와도 상관이 없겠죠? 그럼 다음에 템플릿으로 만든 스택을 올리겠습니다. ^^

#include <stdio.h>
#include <stdlib.h>

#ifndef TEMPLATE_STACK
#define TEMPLATE_STACK

template<class T>
class Stack
{
protected:
struct Node
{
Node* next;
T data;
};
private:
Node* head;
Node* tail;
public:
Stack();
~Stack();
void Push(T* d);
T* Pop(void);
T* Get(int index);
};
template<class T>
Stack::Stack()
{
head = new Node[1];
tail = new Node[1];
head->next = tail;
tail->next = tail;
}

template<class T>
Stack::~Stack()
{
delete [] head;
delete [] tail;
}

template<class T>
void Stack::Push(T *d)
{
Node* temp = new Node[1];
temp->data = *d;
temp->next = head->next;
head->next = temp;
}

template<class T>
T* Stack::Pop(void)
{
Node* temp;
T data2;
if(head->next == tail)
{
printf("\nStack is Empty");
exit(1);
}
temp = head->next;
data2 = temp->data;
head->next = temp->next;
delete [] temp;
return &data2;
}

template<class T>
T* Stack::Get(int index)
{
if(index == 0)
{
exit(1);
}
Node* temp;
temp = head;
for(int i=0; i < index; i++)
{
temp = temp->next;
}
return &(temp->data);
}
#endif

위의 소스를 보면 클래스 안에 구조체가 있습니다. 왜 그런 것일까요? 아래의 그림을 보세요.

이 그림을 보면 스택 객체안에 노드들이 들어 있습니다. 만약 이렇게 설계하지 않으면 우리가 노드에 넣을 자료형 에 반드시 그 자료형을 가리킬 수 있는 포인터가 있어야 합니다. 구조체로 예로 들면 .. 내가 POINT라는 구조체를 노드에 넣으려 할 때 반드시 POINT를 가리킬 수 있는 포인터를 선언해 주어야 한다는 말이죠 다음과 같이.. struct POINT { int x; int y; POINT* next; //POINT를 가리킬 수 있는 포인터 }; 하지만 우리가 저장하고 싶은 자료 형태를 만들 때만다 저것을 챙겨줘야 하면 좀 불편하겠죠... 그래서 클래스에 Node라는 구조체를 포함한 것입니다.^^ 이 stack의 사용법은 Stack<POINT> stack 이런 형식으로 선언해 주고요 POINT pt 자료를 Push 할 때는 ........ stack.Push(&pt); 이렇게 포인터를 넘겨 줍니다. 이렇게 해야 메모리 낭비가 없겠죠? 그리고 Pop()은 POINT 자료형의 포인터(주소)를 넘겨줍니다. 그래서 POINT* ptr = stack.Pop(); 이렇게 사용 해 줍니다. 그리고 몇 번째 자료를 알고 싶을 때 POINT* ptr = stack.Get(2); // 두번째 자료를 알고 싶을 때 ............ 이렇게 사용해 줍니다. ^^ 이렇게 함으로써 모든 자료형을 저정할 수 있는 스택이 만들어 졌습니다. ^^

이 번에는 큐를 이용하여 고스트 마우스라는 프로그램을 만들어 보겠습니다. 고스트 마 우스라는 프로그램은 마우스 움직임을 기억했다가 플레이를 누르면 마우스가 움직였던 대로 재생이 되는 프로그램 입니다.^^ 마우스가 거꾸로가 아닌 똑 같이 재생이 되니깐 큐가 필요하겠죠? 여기서 큐(Queue)란 스택과는 반대로 처음에 들어온 자료가 처음에 나가는 자료가 되겠습니다. 스택을 이해하셨다 면 이해가 잘 되실 것입니다. 우리가 고스트 마우스를 만들기 위해 저장해야할 정보는 마우스 좌표 X,Y 와 마우스 왼 쪽 누름 버튼 확인(True/False) 그리고 오른쪽 마우스 누름 버튼 확인(True/False) 입니다. 구조체로 표현하면....

struct MouseData
{
LONG x; LONG y;
BOOL IsLButtonDown;
BOOL IsRButtonDown;
};

이 되겠죠. ^^ 그럼 이 런 자료형도 저장할 수 있고 또 다른 자료형도 저장할 수 있는 템플리트 큐를 만들면 좋을 것 같습니다. 자 아래 템플리트로 만든 큐 소스를 뿌립니다. 링크드 리스트를 이용했지만 이중 링크드 리스트를 이용했습니다. 이중을 사용한 것은 큐가 입구와 출구가 나위어져 있기 때문에 이중 링크드 리스트를 사용하는 것이 표현하기에 편합니다. 단일로도 물론 억지로라면 구현할 수도 있습니다.. 하지만 왠만하면 정신건강을 위하여 ^^

#ifndef KQUEUE
#define KQUEUE

#ifndef NULL
#    define NULL 0
#endif

#include <stdio.h>

template<class T>
class Queue
{
protected:
struct Node
{
T data;
Node* next;
Node* prev;
};
public:
Queue();
virtual ~Queue();
//operation
void Push(T* d); //tail 노드
  앞에 새로운 노드를 추가
void Get(T* buffer,int index); //index
  순서에 있는 값을 얻어 올 수 있음
void RemoveAll(void); //모든
  노드를 지운다
void Remove(T* buffer); //head
  노드 다음 노드의 값을 얻고 그 노드를 삭제한다.
private:
Node* head;
Node* tail;
};
#endif //class Queue

//생성자에서 head와 tail을 생성하고 그들의 서로 연결한다.
template<class T>
Queue::Queue()
{
head = new Node[1];
tail = new Node[1];
head->prev = head;
head->next = tail;
tail->prev = head;
tail->next = tail;
}

//소멸자에서 head와 tail을 삭제한다.
template<class T>
Queue::~Queue()
{
delete [] head;
delete [] tail;
}

//Push 함수는 tail 앞에 새로운 노드를 만든다
template<class T>
void Queue::Push(T* d)
{
Node* insert = new Node[1];
if(insert == NULL)
{
printf("Error :: Push Function");
return;
}
insert->data = *d;
//tail 노드 앞에 노드를 추가하는 모습(?)
tail->prev->next = insert;
insert->prev = tail->prev;
tail->prev = insert;
insert->next = tail;
}

//index 에 3이라고 주면 3번째 노드의 값을 얻어온다.(만약 3번째 노드가 있다면..)
template<class T>
void Queue::Get(T* buffer,int index)
{
if(head->next == tail)
{
printf("\nError :: Get
  Function");
return;
}
Node* temp;
temp = head->next;
for(int i=0; i < index; i++)
{
temp = temp-> next;
}
*buffer = temp->data;
}

//모든 노드를 삭제한다.
template<class T>
void Queue::RemoveAll(void)
{
if(head->next == tail)
{
printf("Error :: RemoveAll");
return;
}
Node* current;
Node* old;
current = head->next;
while(current != tail)
{
old = current;
current = current->next;
delete [] old;
}
head->next = tail;
tail->prev = head;
}

//head 의 다음 노드의 값을 얻고 그 노드를 삭제한다.
template<class T>
void Queue::Remove(T* buffer)
{
if(head->next == tail)
{
printf("\nError :: Remove
  Function");
return;
}
Node* temp;
temp = head->next;
*buffer = temp->data;
//head 의 다음다음 노드와 연결하고 있다.
head->next = temp->next;
temp->next->prev = head;
tail->prev=tail->prev->prev;
delete [] temp;
}

struct POINT
{
int x;
int y;
};

void main(void)
{
POINT pt[5] = {1,1,2,2,3,3,4,4,5,5};
Queue queue;
for(int i=0; i<5; i++)
{
queue.Push(&pt[i]);
}
POINT temp;
// queue.RemoveAll();
for(int j=0; j<5; j++)
{
queue.Get(&temp,j);
printf("\n%d %d",temp.x,
  temp.y);
}
}


PHP CLASS (ver 1_2001_12_05) ARCHITECTURE
Made by  SUNGCHUL KIM.
EMAIL    paranwave@korea.com
         paranwave@hanmail.net
HOMEPAGE http://douner.pe.kr

CObject
 +--------------------+-----------+-------------+
 |                    |           |             |
 CDatabase            CUtil       CMail         CEnv
 |
 CMysql

 COMPLETE CLASS [2001 / 12 / 05]
 CObject
 CDatabase
 CMysql

 COMPLETE CLASS [2001 / 12 / 06]
 CUtil
 CEnv
 CMail

 clslib.php
 phpclass 는 한파일에 모든클래스를 인쿠르드하는 방법을 사용합니다.
 clslib.php 파일이 그 파일이며 새로 추가되는 파일이 있을 경우 clslib.php 에 추가합니다

PHP CLASS REFERENCE
작성자 : 김성철
연  락 : paranwave@korea.com
연  락 : paranwave@metarica.com
연  락 : http://douner.pe.kr

+-----------------+
| CObject         |
+-----------------+
설명 : 최상위 클래스
기능 : 예외처리기능
# bool Destroy($object)
# 리턴값 true/false
# 인  자 클래스의 객체
# 설  명 아직 동작하지 않습니다. eval 함수에 문제가 있는 것 같습니다.

# CObject($classname="CObject")
# 리턴값 void
# 인  자 클래스의 이름
# 설  명 클래스의 객체를 생성한다

# __CObject($Instance)
# 리턴값 void
# 인  자 클래스 객체의 인스턴스
# 설  명 클래스의 객체를 소멸시킨다

# string GetClassName(void)
# 리턴값 클래스의 이름
# 인  자 void
# 설  명 클래스의 이름을 반환한다.
# ex)
# $myObj = new CObject;
# echo $myObj->GetClassName(); // CObject 를 출력한다

# fixed GetInstanceId(void)
# 리턴값 각 클래스 인스턴스의 고유한 ID
# 인  자 void
# 설  명 클래스이름과 microtime 을 이용한 값을 반환한다
# ex)
# $myObj = new CObject;
# echo $myObj->GetInstanceId(); // CObject_123491098511234123 를 출력한다(출력결과는 다를 수 있다)

# false Exception($code)
# 리턴값 __HALT_ON 이 true 이면 함수는 스크립트를 종료하고 false 이면 false 를 리턴한다
# 인  자 각 예외상황을 나타내는 예외 코드
# 설  명 이 함수를 이용하기 전에 반드시 예외 코드를 정의해야 한다

+---------------+
|  CDatabase    |
+---------------+
설명 : 데이타베이스의 기본 설정과 기능을 제공하는 클래스
       단독으로 쓰이는 경우는 거의 없다.
       현재 버전에서는 이 클래스를 상속받은 CMysql 의 부모 클래스로 이용된다
기능 : 데이타베이스 환경 설정 및 기타 기능
# CDatabase($classname="CDatabase")
# 리턴값 : 없음
# 인  자 : 클래스의 이름
# 설  명 : CDatabase 의 객체를 생성한다

# __CDatabase($instance)
# 리턴값 : 없음
# 인  자 : 클래스의 인스턴스
# 설  명 : 객체를 소멸시킨다

# false Exception($code)
# 리턴값 __HALT_ON 이 true 이면 함수는 스크립트를 종료하고 false 이면 false 를 리턴한다
# 인  자 각 예외상황을 나타내는 예외 코드
# 설  명 이 함수를 이용하기 전에 반드시 예외 코드를 정의해야 한다

# void SetHostName($szHostName)
# 리턴값 : 없음
# 인  자 : Mysql 서버가 있는 곳의 주소
# 설  명 : Mysql 서버가 있는 곳의 주소를 설정한다
# ex)
# $mySQL = new CMysql;
# $mysQL->SetHostName("localhost"); or $mySQL->SetHostName("127.0.0.1");

# string GetHostName(void)
# 리턴값 : Mysql 서버가 있는 곳의 주소 문자열
# 인  자 : 없음
# 설  명 : Mysql 서버가 있는 곳의 주소 문자열을 반환한다
# ex)
# $mySQL = new CMysql;
# $mysQL->SetHostName("localhost"); or $mySQL->SetHostName("127.0.0.1");
# echo $mySQL->GetHostName(); // localhost 를 출력한다

# void SetUserName($szUserName)
# 리턴값 : 없음
# 인  자 : Mysql 서버에 로그인할 수 있는 사용자 ID
# 설  명 : Mysql 서버에 로그인할 수 있는 사용자 ID 를 설정한다
# ex)
# $mySQL = new CMysql;
# $mySQL->SetHostName("localhost"); or $mySQL->SetHostName("127.0.0.1");
# $mySQL->SetUserName("root");

# string GetUserName(void)
# 리턴값 :  Mysql 서버에 로그인할 수 있는 사용자 ID
# 인  자 : 없음
# 설  명 : Mysql 서버에 로그인할 수 있는 사용자 ID 를 출력한다
# ex)
# $mySQL = new CMysql;
# $mysQL->SetHostName("localhost"); or $mySQL->SetHostName("127.0.0.1");
# echo $mySQL->GetHostName(); // localhost 를 출력한다
# $mySQL->SetUserName("root");
# echo $mySQL->GetUserName(); // root 를 출력한다

# void SetPassword($szPassword)
# 리턴값 : 없음
# 인  자 : Mysql 서버에 로그인할 수 있는 사용자 비밀번호
# 설  명 : Mysql 서버에 로그인할 수 있는 사용자 비밀번호를 설정한다
# ex)
# $mySQL = new CMysql;
# $mysQL->SetHostName("localhost"); or $mySQL->SetHostName("127.0.0.1");
# echo $mySQL->GetHostName(); // localhost 를 출력한다
# $mySQL->SetUserName("root");
# echo $mySQL->GetUserName(); // root 를 출력한다
# $mySQL->SetPassword("1234");

# string GetPassword(void)
# 리턴값 : Mysql 서버에 로그인할 수 있는 사용자 비밀번호
# 인  자 : 없음
# 설  명 : Mysql 서버에 로그인할 수 있는 사용자 비밀번호를 반환한다
# ex)
# $mySQL = new CMysql;
# $mysQL->SetHostName("localhost"); or $mySQL->SetHostName("127.0.0.1");
# echo $mySQL->GetHostName(); // localhost 를 출력한다
# $mySQL->SetUserName("root");
# echo $mySQL->GetUserName(); // root 를 출력한다
# $mySQL->SetPassword("1234");
# echo $mySQL->GetPassword(); // 1234 를 출력한다

# void SetDatabaseName($szDatabaseName)
# 리턴값 : 없음
# 인  자 : 연결할 데이타베이스의 이름
# 설  명 : 연결할 데이타베이스의 이름을 설정한다
# ex)
# $mySQL = new CMysql;
# $mysQL->SetHostName("localhost"); or $mySQL->SetHostName("127.0.0.1");
# echo $mySQL->GetHostName(); // localhost 를 출력한다
# $mySQL->SetUserName("root");
# echo $mySQL->GetUserName(); // root 를 출력한다
# $mySQL->SetPassword("1234");
# echo $mySQL->GetPassword(); // 1234 를 출력한다
# $mySQL->SetDatabaseName("mysql");

# string GetDatabaseName(void)
# 리턴값 : 연결할 데이타베이스의 이름
# 인  자 : 없음
# 설  명 : 연결할 데이타베이스의 이름을 반환한다.
# ex)
# $mySQL = new CMysql;
# $mysQL->SetHostName("localhost"); or $mySQL->SetHostName("127.0.0.1");
# echo $mySQL->GetHostName(); // localhost 를 출력한다
# $mySQL->SetUserName("root");
# echo $mySQL->GetUserName(); // root 를 출력한다
# $mySQL->SetPassword("1234");
# echo $mySQL->GetPassword(); // 1234 를 출력한다
# $mySQL->SetDatabaseName("mysql");
# echo $mySQL->GetDatabaseName(); // mysql 을 출력한다

# bool IsSetHostName(void)
# 리턴값 : HostName 이 설정되어 있으면 true 아니면 false 를 리턴한다
# 인  자 : 없음
# 설  명 : HostName 이 설정되어 있으면 true 아니면 false 를 리턴한다
# ex)
# $mySQL = new CMysql;
# $mysQL->SetHostName("localhost"); or $mySQL->SetHostName("127.0.0.1");
# echo $mySQL->GetHostName(); // localhost 를 출력한다
# $mySQL->SetUserName("root");
# echo $mySQL->GetUserName(); // root 를 출력한다
# $mySQL->SetPassword("1234");
# echo $mySQL->GetPassword(); // 1234 를 출력한다
# $mySQL->SetDatabaseName("mysql");
# echo $mySQL->GetDatabaseName(); // mysql 을 출력한다
# if($mySQL->IsSetHostName()) echo "t"; else echo "f"; // t 를 출력한다

# bool IsSetUserName(void)
# 리턴값 : UserName 이 설정되어 있으면 true 아니면 false 를 리턴한다
# 인  자 : 없음
# 설  명 : UserName 이 설정되어 있으면 true 아니면 false 를 리턴한다
# ex)
# $mySQL = new CMysql;
# $mysQL->SetHostName("localhost"); or $mySQL->SetHostName("127.0.0.1");
# echo $mySQL->GetHostName(); // localhost 를 출력한다
# $mySQL->SetUserName("root");
# echo $mySQL->GetUserName(); // root 를 출력한다
# $mySQL->SetPassword("1234");
# echo $mySQL->GetPassword(); // 1234 를 출력한다
# $mySQL->SetDatabaseName("mysql");
# echo $mySQL->GetDatabaseName(); // mysql 을 출력한다
# if($mySQL->IsSetHostName()) echo "t"; else echo "f"; // t 를 출력한다
# if($mySQL->IsSetUserName()) echo "t"; else echo "f"; // t 를 출력한다

# bool IsSetPassword(void)
# 리턴값 : Password 이 설정되어 있으면 true 아니면 false 를 리턴한다
# 인  자 : 없음
# 설  명 : Password 이 설정되어 있으면 true 아니면 false 를 리턴한다
# ex)
# $mySQL = new CMysql;
# $mysQL->SetHostName("localhost"); or $mySQL->SetHostName("127.0.0.1");
# echo $mySQL->GetHostName(); // localhost 를 출력한다
# $mySQL->SetUserName("root");
# echo $mySQL->GetUserName(); // root 를 출력한다
# $mySQL->SetPassword("1234");
# echo $mySQL->GetPassword(); // 1234 를 출력한다
# $mySQL->SetDatabaseName("mysql");
# echo $mySQL->GetDatabaseName(); // mysql 을 출력한다
# if($mySQL->IsSetHostName()) echo "t"; else echo "f"; // t 를 출력한다
# if($mySQL->IsSetUserName()) echo "t"; else echo "f"; // t 를 출력한다
# if($mySQL->IsSetPassword()) echo "t"; else echo "f"; // t 를 출력한다

# bool IsSetDatabaseName(void)
# 리턴값 : DatabaseName 이 설정되어 있으면 true 아니면 false 를 리턴한다
# 인  자 : 없음
# 설  명 : DatabaseName 이 설정되어 있으면 true 아니면 false 를 리턴한다
# ex)
# $mySQL = new CMysql;
# $mysQL->SetHostName("localhost"); or $mySQL->SetHostName("127.0.0.1");
# echo $mySQL->GetHostName(); // localhost 를 출력한다
# $mySQL->SetUserName("root");
# echo $mySQL->GetUserName(); // root 를 출력한다
# $mySQL->SetPassword("1234");
# echo $mySQL->GetPassword(); // 1234 를 출력한다
# $mySQL->SetDatabaseName("mysql");
# echo $mySQL->GetDatabaseName(); // mysql 을 출력한다
# if($mySQL->IsSetHostName()) echo "t"; else echo "f"; // t 를 출력한다
# if($mySQL->IsSetUserName()) echo "t"; else echo "f"; // t 를 출력한다
# if($mySQL->IsSetPassword()) echo "t"; else echo "f"; // t 를 출력한다
# if($mySQL->IsSetDatabaseName()) echo "t"; else echo "f"; // t 를 출력한다

# void SelectDb($szDbName)
# 리턴값 : 없음
# 인  자 : 접속할 Database 이름
# 설  명 : SetDatabaseName 과 기능이 같음
# $mySQL = new CMysql;
# $mysQL->SetHostName("localhost"); or $mySQL->SetHostName("127.0.0.1");
# echo $mySQL->GetHostName(); // localhost 를 출력한다
# $mySQL->SetUserName("root");
# echo $mySQL->GetUserName(); // root 를 출력한다
# $mySQL->SetPassword("1234");
# echo $mySQL->GetPassword(); // 1234 를 출력한다
# $mySQL->SetDatabaseName("mysql");
# $mySQL->SelectDb("test");

# void SetAll($szHostName, $szUserName, $szPassword, $szDatabaseName="")
# 리턴값 : 없음
# 인  자 : Host 이름, 사용자 ID , 사용자 비밀번호, [데이타베이스이름(생략가능)]
# 설  명 : SetHostName(), SetUserName(), SetPassword(), SetDatabaseName() 의 기능을 한번에 설정한다
#         $szDatabaseName 은 생략가능으로 생략하면 나중에 SelectDb() 나 SetDatabaseName() 으로 설정해야 한다
# ex)
# $mySQL = new CMysql;
# $mySQL->SetAll("localhost", "root", "1234","mysql");
# or
# $mySQL->SetAll("localhost", "root", "1234");
# $mySQL->SelectDb("mysql") or $mySQL->SetDatabaseName("mysql");

# void SetResult($Result)
# 리턴값 : 없음
# 인  자 : 질의(query) 의 결과
# 설  명 : 질의의 결과를 설정한다. 클래스 내부 연산에서 많이 쓰임

# result GetResult(void)
# 리턴값 : 질의의 결과를 반환한다
# 인  자 : 없음
# 설  명 : 질의의 결과를 반환하며 클래스 내부 연산에서 많이 쓰임

# void SetConnection($objConnection)
# 리턴값 : 없음
# 인  자 : mysql_connect 로 연결된 연결 핸들
# 설  명 : mysql_connect 로 연결된 연결 핸들을 설정한다

# integer GetConnection(void)
# 리턴값 : mysql_connect 로 연결된 연결 핸들을 반환한다
# 인  자 : 없음
# 설  명 : mysql_connect 로 연결된 연결 핸들을 반환한다

# string GetQuery(void)
# 리턴값 : 설정된 질의문을 반환한다
# 인  자 : 없음
# 설  명 : 설정된 질의문을 반환한다

# void SetQuery($szQuery)
# 리턴값 : 없음
# 인  자 : 질의문
# 설  명 : 질의문을 설정한다

# bool IsSetQuery(void)
# 리턴값 : 질의문이 설정되어 있으면 true / 아니면 false 를 리턴한다
# 인  자 : 없음
# 설  명 : 질의문이 설정되어 있으면 true / 아니면 false 를 리턴한다

# bool IsConnected(void)
# 리턴값 : 현재 Mysql 서버와 연결이 되어 있으면 true / 아니면 false 를 리턴한다
# 인  자 : 없음
# 설  명 : 현재 Mysql 서버와 연결 유무를 알아본다

# int GetCurrentRecordNo(void)
# 리턴값 : 결과값 rows 에서 현재의 위치를 반환한다
# 인  자 : 없음
# 설  명 : 결과값 rows 에서 현재의 위치를 반환한다

# void SetCurrentRecordNo($No)
# 리턴값 : 없음
# 인  자 : 현재 row 행의 값
# 설  명 : 결과값 rows 에서 현재의 위치를 설정한다

# int GetTotalRecordNo(void)
# 리턴값 : 질의 결과로 얻은 rows 의 행 개수
# 인  자 : 없음
# 설  명 : 질의 결과로 얻은 rows 의 행 개수를 리턴한다

# void SetTotalRecordNo($No)
# 리턴값 : 없음
# 인  자 : 전체 행의 개수
# 설  명 : 질의 결과로 얻은 rows 의 전체행 개수를 설정한다

# int GetCurrentFieldNo(void)
# 리턴값 : 현재 필드의 포인터 위치
# 인  자 : 없음
# 설  명 :
# ex)
# +------+--------+--------+
# | Fi1  | Fi2    | Fi2    | <-Fields
# +------+--------+--------+
# |tt    | tt2    | tt3    | <-Row1 --+
# +------+--------+--------+          |-> Rows
# |ff    | ff2    | ff3    | <-Row2 --+
# +------+--------+--------+
# void SetCurrentFieldNo($No)
# 리턴값 : 없음
# 인  자 : 현재 필드의 포인터위치를 설정한다
# 설  명 :
# ex)
# +------+--------+--------+
# | Fi1  | Fi2    | Fi2    | <-Fields
# +------+--------+--------+
# |tt    | tt2    | tt3    | <-Row1 --+
# +------+--------+--------+          |-> Rows
# |ff    | ff2    | ff3    | <-Row2 --+
# +------+--------+--------+
#
# $mySQL->SetCurrentFieldNo(1); //을 하면 현재 필드의 포인터는 Fi2 를 가리킨다

# int GetTotalFieldNo(void)
# 리턴값 : 필드의 전체개수
# 인  자 : 없음
# 설  명 :
# 위의 테이블에서 GetTotalFieldNo() 는 3을 리턴한다

# void SetTotalFieldNo($No)
# 리턴값 : 없음
# 인  자 : 전체 필드의 개수
# 설  명 : 전체 필드의 개수를 설정한다

# bool fIsBOF(void)
# 리턴값 : 위의 테이블에서 필드의 포인터가 Fi1 이면 true / 아니면 false 를 리턴한다
# 인  자 : 없음
# 설  명 : 필드의 포인터가 처음인지 아닌지를 판별한다.

# bool fIsEOF(void)
# 리턴값 : 위의 테이블에서 필드의 포인터가 Fi3 이면 true / 아니면 false 를 리턴한다
# 인  자 : 없음
# 설  명 : 필드의 포인터가 끝인지 아닌지를 판별한다.

# bool IsBOF(void)
# 리턴값 : 위의 테이블에서 Rows의 포인터가  Row1 이면 true / 아니면 false 를 리턴한다
# 인  자 : 없음
# 설  명 : Rows의 포인터가 처음인지 아닌지를 판별한다.
# ex)
# +------+--------+--------+
# | Fi1  | Fi2    | Fi2    | <-Fields
# +------+--------+--------+
# |tt    | tt2    | tt3    | <-Row1 --+
# +------+--------+--------+          |-> Rows
# |ff    | ff2    | ff3    | <-Row2 --+
# +------+--------+--------+

# bool IsEOF(void)
# 리턴값 : 위의 테이블에서 Rows의 포인터가  Row2 이면 true / 아니면 false 를 리턴한다
# 인  자 : 없음
# 설  명 : 필드의 포인터가 끝인지 아닌지를 판별한다.
# ex)
# +------+--------+--------+
# | Fi1  | Fi2    | Fi2    | <-Fields
# +------+--------+--------+
# |tt    | tt2    | tt3    | <-Row1 --+
# +------+--------+--------+          |-> Rows
# |ff    | ff2    | ff3    | <-Row2 --+
# +------+--------+--------+

# void fSetBOF($bBOF)
# 리턴값 : 없음
# 인  자 : true / false
# 설  명 : 필드의 포인터가 처음인지 아닌지를 설정한다

# void fSetEOF($bEOF)
# 리턴값 : 없음
# 인  자 : true / false
# 설  명 : 필드의 포인터가 끝인지 아닌지를 설정한다

# void SetBOF($bBOF)
# 리턴값 : 없음
# 인  자 : true / false
# 설  명 : Rows의 포인터가 처음인지 아닌지를 설정한다

# void SetEOF($bEOF)
# 리턴값 : 없음
# 인  자 : true / false
# 설  명 : Rows의 포인터가 끝인지 아닌지를 설정한다

+-----------------+
| CMysql          |
+-----------------+
설명 : mysql 에 관련된 연산을 수행
기능

# CMysql($szClassName="CMysql")
# 반환값 : 없음
# 인  자 : 클래스 이름
# 설  명 : 생성자

# __CMysql($objInstance)
# 반환값 : 없음
# 인  자 : 객체 인스턴스
# 설  명 : 소멸자

# bool Connect($szHostName="", $szUserName="", $szPassword="", $szDatabaseName="")
# 반환값 : Mysql 연결이 되면 true / 안 되면 false
# 인  자 : SetHostName(), SetUserName(), SetPassword(), SetDatabaseName() 참고
# 설  명 : Mysql 에 연결한다.
          인자에 아무것도 넣지 않고 호출하면 $mySQL->Connect() 기존에 SetHostName(), SetUserName(), SetPassword()
      로 설정을 해야 한다.
      DatabaseName 은 설정하면 연결 후 Database 를 선택하며 DatabaseName 을 설정하지 않으면
      기존에 SetDatabaseName() 이 설정되어 있는지 살피고 되어 있으면 Database 를 선택하고 그렇지 않으면
      선택하지 않는다.
# ex)
# $mySQL = new CMysql;
# if($mySQL->Connect("localhost","root","1234")) { echo "t"; $mySQL->SelectDb("mysql"); } else { echo "f"; }

# bool Disconnect(void)
# 반환값 : Mysql 서버와 연결이 끊어지면 true 아니면 false
# 인  자 : 없음
# 설  명 : Mysql 서버와 연결이 끊는다

# bool Query($szQuery="")
# 반환값 : 쿼리가 성공적으로 이루어지면 true / 아니면 false
# 인  자 : 쿼리문
# 설  명 : Mysql 서버에 질의한다. SetQuery() 로 이전에 쿼리문을 설정했으면 인자를 전달하지 않아도 된다
# ex)
# $mySQL->SetQuery("select * from test");
# $mySQL->Query();
# or
# $mySQL->Query("select * from test");

# bool CreateDatabase($szDatabaseName)
# 반환값 : Database 가 만들어 지면 true / 아니면 false
# 인  자 : 만들 Database 의 이름
# 설  명 : Database 를 만든다

# bool DeleteDatabase($szDatabaseName)
# 반환값 : Database 가 지워지면 true / 아니면 false
# 인  자 : 지울 Database 이름
# 설  명 : Database 를 지운다

# result GetCurrentResult(void)
# 반환값 : 여러 Rows 중에서 현재 Rows 포인터가 가리키는 결과를 리턴한다
# 인  자 : 없음
# 설  명 : 여러 Rows 중에서 현재 Rows 포인터가 가리키는 결과를 리턴한다

# object/array FetchFields($iField=0, $GetAll=false)
# 반환값 : 지정된 Field 의 정보를 담은 객체를 반환한다
# 인  자 : $iField : Field의 포인터 위치,
#          $GetAll : 현재행의 모든 필드의 정보를 담은 객체의 배열을 얻을 것인가 말 것인가?
# 설  명 : 여러 Rows 중에서 현재 Rows 포인터가 가리키는 결과를 리턴한다
# 필드의 속성
# +------------------------+----------------------------------------------+
# |        속성             |                설명                          |
# +------------------------+----------------------------------------------+
# | blob                   | BLOB 필드이면 TRUE                            |
# +------------------------+----------------------------------------------+
# | max_length             | 필드의 최대 길이                               |
# +------------------------+----------------------------------------------+
# | multiple_key           | 고유하지 않은 키 필드이면 true                  |
# +------------------------+----------------------------------------------+
# | name                   | 필드 이름                                     |
# +------------------------+----------------------------------------------+
# | not_null               | 널 값을 허용하지 않으면 true                    |
# +------------------------+----------------------------------------------+
# | numuric                | 숫자 필드이면 true                             |
# +------------------------+----------------------------------------------+
# | primary_key            | 기본키 필드이면 true                           |
# +------------------------+----------------------------------------------+
# | table                  | 필드가 속한 테이블 이름                         |
# +------------------------+----------------------------------------------+
# | type                   | 필드의 데이타형                                |
# +------------------------+----------------------------------------------+
# | unique_key             | 고유 키 필드(필드가 항상 고유한 값을 가짐)        |
# +------------------------+----------------------------------------------+
# | unsigned               | 부호 없는 값을 가지는 필드이면 true              |
# +------------------------+----------------------------------------------+
# | zerofill               | 빈자리에 0을 채우는 필드이면 true                |
# +------------------------+----------------------------------------------+
#
# ex)
# +------+--------+--------+
# | Fi1  | Fi2    | Fi2    | <-Fields
# +------+--------+--------+
# |tt    | tt2    | tt3    | <-Row1 --+
# +------+--------+--------+          |-> Rows
# |ff    | ff2    | ff3    | <-Row2 --+
# +------+--------+--------+
#
# $objField1 = $mySQL->FetchFields(0); // Fi1 의 필드 속성을 담은 객체를 얻는다
# $objField2 = $mySQL->FetchFields(1); // Fi2 의 필드 속성을 담은 객체를 얻는다
# $arrObjFields = $mySQL->FetchFields(0,true); // Fi1,Fi2,Fi3 의 객체 정보를 담은 배열을 얻는다
# echo $arrObjFields[0]->name; //Fi1 을 출력한다
# for($i=0; $i<sizeof($arrObjFields); $i++)
# {
#     echo $arrObjFields[$i]->name;
# }

# array FetchLength(void)
# 반환값 : 현재행의 필드들의 최대 길이를 가지고 있는 배열
# 인  자 : 없음
# 설  명 : 현재행의 필드들의 최대 길이를 구한다
# ex)
# $lengths = $mySQL->FetchLengths();
# echo $length[0]; //Fi1 의 최대길이를 출력한다
# ( ### BUG ### ) 실행이 안된다.

# object FetchObject(void)
# 반환값 : 현재행의 결과 정보를 가지는 객체
# 인  자 : 없음
# 설  명 : 현재행의 결과 정보를 가지는 객체를 반환한다.
# ex)
# $objRow = $mySQL->FetchObject();
# echo $objRow->name;

# array FetchRow(void)
# 반환값 : 현재행의 결과 집합의 배열
# 인  자 : 없음
# 설  명 : 현재행의 결과 집합의 배열을 반환한다
# ex)
# $arrRow = $mySQL->FetchRow();
# for($i=0; $i<sizeof($arrRow); $i++)
# {
#     echo $arrRow[$i];
# }

# string/array FieldFlags($iField=0, $GetAll=false)
# 반환값 : 필드의 플래그에 대한 정보를 담은 문자열 또는 현재행의 전체 필드의 정보를 담음
#         문자열들을 담은 배열 ($GetAll 이 true 일 때)
# 인  자 : $iField : 필드의 포인트위치, $GetAll : 현재행의 전체 필드를 대상으로 할 것인가?
# 설  명 : $iField 인수로 지정된 필드의 플래그들에 대한 정보를 담은 문자열을 돌려준다.
#         문자열에 포함될 수 있는 플래그들은
#         auto_increment
#         binary
#         blob
#         enum
#         multiple_key
#         not_null
#         primary_key
#         timestamp
#         unique_key
#         unsigned
#         zerofill
# ex)
# $arrFlags = $mySQL->FieldFlags(0,true);
# for($i=0; $i<sizeof($arrFlags); $i++)
# {
#     echo $arrFlags[$i];
# }

# int/array FieldLen($iField=0, $GetAll=false)
# 반환값 : 지정된 필드의 최대길이(문자 개수) 또는 현재행의 전체 필드에 대한 필드의 최대길이의 배열
# 인  자 : $iField : 특정 필드의 포인터 위치, $GetAll : 현재행 전체에 대해 수행?
# 설  명 : 지정된 필드의 최대길이(문자 개수)를 반환한다
# ex)
# $arrFieldLen = $mySQL->FieldLen(0,true);
# for($i=0; $i<sizeof($arrFieldLen); $i++)
# {
#     echo $arrFieldLen[$i];
# }

# string/array FieldName($iField=0, $GetAll=false)
# 반환값 : $iField 로 지정된 필드의 이름 또는 현재행 전체 필드 이름의 배열
# 인  자 : $iField : 특정 필드의 포인터 위치, $GetAll : 현재행 전체에 대해 수행?
# 설  명 : 지정된 필드의 이름
# ex)
# $arrFieldName = $mySQL->FieldName(0,true);
# for($i=0;$i<sizeof($arrFieldName); $i++)
# {
#     echo $arrFieldName[$i];
# }
# or
# echo $mySQL->FieldName(3); //현재행의 4(0부터시작한다)번째 필드의 이름을 반환한다

# string/array FieldTable($iField=0, $GetAll=false)
# 반환값 : $iField 가 지정하는 필드가 속해있는 table 의 이름을 반환 또는 전체 필드에 대한 테이블이름 배열
# 인  자 : $iField : 특정 필드의 포인터 위치, $GetAll : 현재행 전체에 대해 수행?
# 설  명 : $iField 가 지정하는 필드가 속해있는 테이블이름을 반환한다.
# ex)
# $arrFieldTable = $mySQL->FieldTable(0,true);
# for($i=0; $i<sizeof($arrFieldTable); $i++)
# {
#	echo $arrFieldTable[$i];
# }

# string / array FieldType($iField=0, $GetAll=false)
# 반환값 : $iField 가 지정하는 필드의 데이타 타입 또는 현재 행 전체 필드의 데이타 타입 배열
# 인  자 : $iField : 특정 필드의 포인터 위치, $GetAll : 현재행 전체에 대해 수행?
# 설  명 : $iField 가 지정하는 필드의 데이타 타입을 반환한다.
# ex)
# $arrFieldDataType = $mySQL->FieldType(0,true);
# for($i=0; $i<sizeof($arrFieldDataType); $i++)
# {
#     echo $arrFieldDataType[$i];
# }

# array ListDbs(void)
# 반환값 : 현재 MySQL 서버의 모든 데이타베이스 목록 배열
# 인  자 : 없음
# 설  명 : 현재 MySQL 서버의 모든 데이타베이스 목록 배열을 반환한다
# ex)
# $arrListDbs =  $mySQL->ListDbs();
# for($i=0; $i<sizeof($arrListDbs); $i++)
# {
# 	echo $arrListDbs[$i];
# }

# void fFirst(void)
# 반환값 : 없음
# 인  자 : 없음
# 설  명 : 필드의 포인터 위치를 처음으로 설정한다
# ex)
# $mySQL->fFirst();
# while(!$mySQL->fIsEOF())
# {
#	$objField = $mySQL->FieldInfo();
#	echo $objField->name;
#	echo "<br>";
#	$mySQL->fNext();
# }
#  fFirst  fNext   fLast
#  [fIsBOF = true] [fIsEOF = true]
# +------+--------+--------+
# | Fi1  | Fi2    | Fi2    | <-Fields
# +------+--------+--------+
# |tt    | tt2    | tt3    | <-Row1 --+
# +------+--------+--------+          |-> Rows
# |ff    | ff2    | ff3    | <-Row2 --+
# +------+--------+--------+

# void fLast(void)
# 반환값 : 없음
# 인  자 : 없음
# 설  명 : 필드의 포인터 위치를 마지막으로 설정한다
# ex)
# $mySQL->fLast();
# while(!$mySQL->fIsBOF())
# {
#	$objField = $mySQL->FieldInfo();
#	echo $objField->name;
#	echo "<br>";
#	$mySQL->fPrev();
# }
#  fFirst  fNext   fLast
#  [fIsBOF = true] [fIsEOF = true]
# +------+--------+--------+
# | Fi1  | Fi2    | Fi2    | <-Fields
# +------+--------+--------+
# |tt    | tt2    | tt3    | <-Row1 --+
# +------+--------+--------+          |-> Rows
# |ff    | ff2    | ff3    | <-Row2 --+
# +------+--------+--------+

# void fNext(void)
# 반환값 : 없음
# 인  자 : 없음
# 설  명 : 필드의 포인터 위치를 현재에서 다음포인터로 설정한다
# ex)
# $mySQL->fFirst();
# while(!$mySQL->fIsEOF())
# {
#	$objField = $mySQL->FieldInfo();
#	echo $objField->name;
#	echo "<br>";
#	$mySQL->fNext();
# }
#  fFirst  fNext   fLast
#  [fIsBOF = true] [fIsEOF = true]
# +------+--------+--------+
# | Fi1  | Fi2    | Fi2    | <-Fields
# +------+--------+--------+
# |tt    | tt2    | tt3    | <-Row1 --+
# +------+--------+--------+          |-> Rows
# |ff    | ff2    | ff3    | <-Row2 --+
# +------+--------+--------+

# void fPrev(void)
# 반환값 : 없음
# 인  자 : 없음
# 설  명 : 필드의 포인터 위치를 현재 포인터 이전으로 설정한다
# ex)
# $mySQL->fLast();
# while(!$mySQL->fIsBOF())
# {
#	$objField = $mySQL->FieldInfo();
#	echo $objField->name;
#	echo "<br>";
#	$mySQL->fPrev();
# }
#  fFirst  fNext   fLast
#  [fIsBOF = true] [fIsEOF = true]
# +------+--------+--------+
# | Fi1  | Fi2    | Fi2    | <-Fields
# +------+--------+--------+
# |tt    | tt2    | tt3    | <-Row1 --+
# +------+--------+--------+          |-> Rows
# |ff    | ff2    | ff3    | <-Row2 --+
# +------+--------+--------+

# object FieldInfo(void)
# 반환값 : 필드의 정보를 담고 있는 객체
# 인  자 : 없음
# 설  명 : 현재 필트 포인터가 가리키고 있는 필드의 정보를 담는 객체를 반환한다
# ex)
# $mySQL->fLast();
# while(!$mySQL->fIsBOF())
# {
#	$objField = $mySQL->FieldInfo();
#	echo $objField->name;
#	echo "<br>";
#	$mySQL->fPrev();
# }
#  fFirst  fNext   fLast
#  [fIsBOF = true] [fIsEOF = true]
# +------+--------+--------+
# | Fi1  | Fi2    | Fi2    | <-Fields
# +------+--------+--------+
# |tt    | tt2    | tt3    | <-Row1 --+
# +------+--------+--------+          |-> Rows
# |ff    | ff2    | ff3    | <-Row2 --+
# +------+--------+--------+

# array FetchArray(void)
# 반환값 : 현재 질의 결과 집합을 연관 배열로 반환한다.
# 인 자 : 없음
# 설 명 :

# bool FreeResult(void)
# 반환값 : 결과값을 unset 시킨다 성공이면 true / 실패면 false
# 인  자 : 없음
# 설  명 :

# void First(void)
# 반환값 : 없음
# 인  자 : 없음
# 설  명 : Rows의 포인터 위치를 처음으로 설정한다
# ex)
# $mySQL->First();
# while(!$mySQL->IsEOF())
# {
#	echo $mySQL->Field("Fi1")."-";
#	echo $mySQL->Field("Fi2")."-";
#	echo $mySQL->Field("Fi3")."-";
#	$mySQL->Next();
#	echo "<br>";
# }
#  fFirst  fNext   fLast
#  [fIsBOF = true] [fIsEOF = true]
# +------+--------+--------+
# | Fi1  | Fi2    | Fi2    | <-Fields
# +------+--------+--------+
# |aa    | aa2    | aa3    |                    | First(), IsBOF()==true
# +------+--------+--------+                        ▼        ▲
# |tt    | tt2    | tt3    | <-Row1 --+         | Next(), Prev()
# +------+--------+--------+          |-> Rows      ▼        ▲
# |ff    | ff2    | ff3    | <-Row2 --+         | Last(), IsEOF()==true
# +------+--------+--------+

# void Last(void)
# 반환값 : 없음
# 인  자 : 없음
# 설  명 : Rows의 포인터 위치를 마지막으로 설정한다
# ex)
# $mySQL->Last();
# while(!$mySQL->IsBOF())
# {
#	echo $mySQL->Field("Fi1")."-";
#	echo $mySQL->Field("Fi2")."-";
#	echo $mySQL->Field("Fi3")."-";
#	$mySQL->Prev();
#	echo "<br>";
# }
#  fFirst  fNext   fLast
#  [fIsBOF = true] [fIsEOF = true]
# +------+--------+--------+
# | Fi1  | Fi2    | Fi2    | <-Fields
# +------+--------+--------+
# |aa    | aa2    | aa3    |                    | First(), IsBOF()==true
# +------+--------+--------+                        ▼        ▲
# |tt    | tt2    | tt3    | <-Row1 --+         | Next(), Prev()
# +------+--------+--------+          |-> Rows      ▼        ▲
# |ff    | ff2    | ff3    | <-Row2 --+         | Last(), IsEOF()==true
# +------+--------+--------+

# void Next(void)
# 반환값 : 없음
# 인  자 : 없음
# 설  명 : Rows의 포인터 위치를 현재의 다음으로 설정한다
# ex)
# $mySQL->First();
# while(!$mySQL->IsEOF())
# {
#	echo $mySQL->Field("Fi1")."-";
#	echo $mySQL->Field("Fi2")."-";
#	echo $mySQL->Field("Fi3)."-";
#	$mySQL->Next();
#	echo "<br>";
# }
#  fFirst  fNext   fLast
#  [fIsBOF = true] [fIsEOF = true]
# +------+--------+--------+
# | Fi1  | Fi2    | Fi2    | <-Fields
# +------+--------+--------+
# |aa    | aa2    | aa3    |                    | First(), IsBOF()==true
# +------+--------+--------+                        ▼        ▲
# |tt    | tt2    | tt3    | <-Row1 --+         | Next(), Prev()
# +------+--------+--------+          |-> Rows      ▼        ▲
# |ff    | ff2    | ff3    | <-Row2 --+         | Last(), IsEOF()==true
# +------+--------+--------+

# void Prev(void)
# 반환값 : 없음
# 인  자 : 없음
# 설  명 : Rows의 포인터 위치를 현재의 이전으로 설정한다
# ex)
# $mySQL->Last();
# while(!$mySQL->IsBOF())
# {
#	echo $mySQL->Field("Fi1")."-";
#	echo $mySQL->Field("Fi2")."-";
#	echo $mySQL->Field("Fi3)."-";
#	$mySQL->Prev();
#	echo "<br>";
# }
#  fFirst  fNext   fLast
#  [fIsBOF = true] [fIsEOF = true]
# +------+--------+--------+
# | Fi1  | Fi2    | Fi2    | <-Fields
# +------+--------+--------+
# |aa    | aa2    | aa3    |                    | First(), IsBOF()==true
# +------+--------+--------+                        ▼        ▲
# |tt    | tt2    | tt3    | <-Row1 --+         | Next(), Prev()
# +------+--------+--------+          |-> Rows      ▼        ▲
# |ff    | ff2    | ff3    | <-Row2 --+         | Last(), IsEOF()==true
# +------+--------+--------+

# void Field($szFieldName)
# 반환값 : 없음
# 인  자 : 없음
# 설  명 : 해당 필드의 값을 가져온다
# ex)
# $mySQL->Last();
# while(!$mySQL->IsBOF())
# {
#	echo $mySQL->Field("Fi1")."-";
#	echo $mySQL->Field("Fi2")."-";
#	echo $mySQL->Field("Fi3)."-";
#	$mySQL->Prev();
#	echo "<br>";
# }
#  fFirst  fNext   fLast
#  [fIsBOF = true] [fIsEOF = true]
# +------+--------+--------+
# | Fi1  | Fi2    | Fi2    | <-Fields
# +------+--------+--------+
# |aa    | aa2    | aa3    |                    | First(), IsBOF()==true
# +------+--------+--------+                        ▼        ▲
# |tt    | tt2    | tt3    | <-Row1 --+         | Next(), Prev()
# +------+--------+--------+          |-> Rows      ▼        ▲
# |ff    | ff2    | ff3    | <-Row2 --+         | Last(), IsEOF()==true
# +------+--------+--------+

# void SetValidResult($bIsValid);
# 인자 : 쿼리의 결과가 유효한가
# 반환값 : 없음
# 설 명 : 쿼리를 했을 경우 그것에 대한 결과가 유효한지를 설정합니다.

# bool IsValidResult(void);
# 인 자 : 없 음
# 반환값 : 쿼리의 결과가 유효하면 true 를 아니면 false 를 리턴합니다.
# 설  명 : select 쿼리를 제외하고 insert,update,delete 는 쿼리의 결과를 부울 값으로
#         리턴합니다. 이럴경우 $mySQL->Next() 같은 함수를 쓰면 결과가 없기 때문에
#         오류가 생기겠죠. 그것을 방지하기 위해서 만들었습니다.

# NO_HAVE_RESULT 예외 추가
# 사용자가 다음과 같은 쿼리를 했다고 생각해 봅시다.
# $mySQL->Query("select * from testmemo");
# 사용자는 데이타이 있는 줄 알고
# $mySQL->First();
# while(!$mySQL->IsEOF())
# {
#     $mySQL->Field("title");
#     $mySQL->Next();
# }
# 이런 코드를 썼을 경우 당연히 결과가 없기 때문에 에러가 발생합니다.
# 이 때 사용자가 위의 코드를 쓰기전에 결과가 유효한지를 살펴보고 하는 것이
# 좋겠죠. 만약에 사용자가 결과의 유효함을 체크하는 루틴을 빼먹었을 때를
# 대비한 예외 코드입니다.
# 위에서 아무러 데이타가 없을 경우
# [예외발생] NO_HAVE_RESULT 쿼리에 대한 결과가 없습니다.
# 라는 예외가 발생됩니다.

# 유효성을 체크하기 위해서
# if($mySQL->IsValidResult())
# {
#       $mySQL->First();
#       while(!$mySQL->IsEOF())
#       {
#            $mySQL->Field("title");
#            $mySQL->Next();
#       }
# }

#######################################################################
##       2001년 12월 05일자 REFERENCE 끝 헥헥 ㅡㅡ;;                   ##
######################################################################

#######################################################################
##       2001년 12월 06일자 REFERENCE 시작  ㅡㅡ;;                     ##
#######################################################################

+---------------+
|  CUtil        |
+---------------+
설명 : 웹프로그래밍시 필요한 여러 기타 기능들을 제공한다
기능 : 이메일주소의 유효성이나 홈페이지주소의 유효성 등등

# CUtil($szClassName="CUtil")
# 리턴값 : 없음
# 인  자 : 클래스 이름
# 설  명 : 생성자

# __CUtil($objInstance)
# 리턴값 : 없음
# 인  자 : 클래스 인스턴스
# 설  명 : 소멸자

# false Exception($exception)
# 리턴값 : __HALT_ON 이 true 이면 exit false 이면 false 를 리턴한다
# 인  자 : 예외 코드
# 설  명 : 이 함수를 사용하기 전에 예외코드를 정의해야 한다

# string GetMailPattern(void)
# 리턴값 : 이메일주소 유효성 판단에 사용되는 정규표현식을 반환한다
# 인  자 : 없음
# 설  명 : 이메일주소 유효성 판단에 사용되는 정규표현식을 반환한다

# string GetHttpPattern(void)
# 리턴값 : 웹주소 유효성 판단에 사용되는 정규표현식을 반환한다
# 인  자 : 없음
# 설  명 : 웹주소 유효성 판단에 사용되는 정규표현식을 반환한다

# string GetftpPattern(void)
# 리턴값 : ftp주소 유효성 판단에 사용되는 정규표현식을 반환한다
# 인  자 : 없음
# 설  명 : ftp주소 유효성 판단에 사용되는 정규표현식을 반환한다

# string GetTelnetPattern(void)
# 리턴값 : Telnet주소 유효성 판단에 사용되는 정규표현식을 반환한다
# 인  자 : 없음
# 설  명 : Telnet주소 유효성 판단에 사용되는 정규표현식을 반환한다

# void SetSpace($iSpaceLen=1)
# 리턴값 : 없음
# 인  자 : 공백수
# 설  명 : 공백수 많큼 &nbsp; 를 찍는다

# bool IsValidEmail($szEmailAddr)
# 리턴값 : 이메일주소 형식이 맞으면 true 틀리면 false
# 인  자 : 검사하고자 하는 이메일 주소
# 설  명 : 이메일주소의 형식을 검사한다

# bool IsValidHttp($szHttpAddr)
# 리턴값 : Htpp주소 형식이 맞으면 true 틀리면 false
# 인  자 : 검사하고자 하는 이메일 주소
# 설  명 : Htpp주소의 형식을 검사한다

# bool IsValidFtp($szFtpAddr)
# 리턴값 : Ftp주소 형식이 맞으면 true 틀리면 false
# 인  자 : 검사하고자 하는 이메일 주소
# 설  명 : Ftp주소의 형식을 검사한다

# bool IsValidTelnet($szTelnetAddr)
# 리턴값 : Telnet주소 형식이 맞으면 true 틀리면 false
# 인  자 : 검사하고자 하는 이메일 주소
# 설  명 : Telnet주소의 형식을 검사한다

# bool IsEmpty($szString)
# 리턴값 : 주어진 문자열이 공백으로 이루어져 있으면 true 아니면 false
# 인  자 : 검사하고자 하는 문자열
# 설  명 : 문자열이 공백으로만 이루어져 있는지 검사한다

# bool IsDigit($szChar)
# 리턴값 : 주어진 문자가 숫자인지 판단한다
# 인  자 : 검사하고자 하는 문자
# 설  명 : 문자가 숫자인지 검사한다

# bool IsAlpha($szChar)
# 리턴값 : 주어진 문자가 알파벳이면 true 아니면 false
# 인  자 : 검사하고자 하는 문자
# 설  명 : 문자가 알파벳인지 검사한다

# bool IsSpace($szChar)
# 리턴값 : 주어진 문자가 공백이면 true 아니면 false
# 인  자 : 검사하고자 하는 문자
# 설  명 : 문자가 공백인지 검사한다(' ' '\t' '\r' '\n' 은 공백으로 처리)

# bool IsWChar($szChar)
# 리턴값 : 주어진 문자가 와이드문자(2byte)인지 검사한다. 맞으면 true 아니면 false
# 인  자 : 검사하고자 하는 문자
# 설  명 : 문자가 2byte 문자인지 검사한다

# string GetUniqId($szValue)
# 리턴값 : 주어진 값으로 유일한 키를 만들어낸다
# 인  자 : 키를 만들 값
# 설  명 : 주어진 값으로 유일한 키를 만들어낸다

# void DoSleep($iSecond)
# 리턴값 : 없음
# 인  자 : 초단위의 수
# 설  명 : 지정한 초만큼 스크립트 실행을 멈춘다

# void DoUSleep($iMicroSecond)
# 리턴값 : 없음
# 인  자 : 밀리세컨드 수
# 설  명 : 지정한 밀리초만큼 스크립트 실행을 멈춘다

# void SyntaxString($szString)
# 리턴값 : 없음
# 인  자 : 문자열
# 설  명 : 주어진 문자열을 PHP 코드의 문법 하이라이팅으로 만든다

# void SyntaxFile($szFileName)
# 리턴값 : 없음
# 인  자 : 파일이름
# 설  명 : 주어진 파일을 PHP 문법 스타일의 하이라팅으로 만든다

# void ShowSource($szFileName)
# 리턴값 : 없음
# 인  자 : 파일이름
# 설  명 : 주어진 파일의 PHP 소스를 출력한다

# void GotoBack()
# 리턴값 : 없음
# 인  자 : 없음
# 설  명 : 현재 브라우져를 이전 페이지로 설정한다

# void GotoPage($url, $delay=0)
# 리턴값 : 없음
# 인  자 : 가고자 하는 url , 가기전에 delay 할 초
# 설  명 : 지정한 url 로 페이지를 설정한다

# void OpenerGotoPage($url)
# 리턴값 : 없음
# 인  자 : 가고자 하는 url
# 설  명 : 창을 연 부모창의 페이지를 지정한 url 로 설정한다
#          popup 창에서 많이 쓰인다

# void MoveWindow($left=0,$top=0,$width=0,$height=0,$mode="MW_ALL")
# 리턴값 : 없음
# 인  자 : 현재 윈도우의 위치를 옮긴고 사이즈를 조정한다.
#          MW_NOMOVE    : 윈도우를 움직이지 않는다
#          MW_NORESIZE  : 윈도우의 크기를 조정하지 않는다
#          MW_MOVE      : 윈도우를 움직인다
#          MW_ALL       : 윈도우를 움직이고 사이즈를 재조정한다
# 설  명 : 윈도우를 옮기거나 크기를 변경한다

# void MessageBox($szMsg)
# 리턴값 : 없음
# 인  자 : 메세지
# 설  명 : 주어진 메세지로 윈도우메세지박스를 나타낸다

# void HtmlMessageBox($szMsg,$szTitle="Error",$szOkLink="")
# 리턴값 : 없음
# 인  자 : $szMsg : 메세지
#          $szTitle : 메세지 박스 위에 나타날 타이틀
#          $szOkLink : 확인을 눌렀을 경우 가게될 페이지 url
#                      인자를 주지 않으면 확인을 눌렀을 경우 현재윈도우가 닫힌다

# void SetHMBoxAction($szAction)
# 리턴값 : 없음
# 인  자 : Html 메세지 박스의 페이지가 있는 url
# 설  명 : HtmlMessageBox 는 Html 페이지를 이용해서 메세지를 출력하기 때문에
#          HtmlMessageBox 의 페이지가 있는 곳을 알아야 한다
#          따로 설정할 경우 주어진 인자가 나타날 수 있도록 따로 PHP 코딩을 해주어야
#          한다

# string GetHMBoxAction(void)
# 리턴값 : 현재 설정되어있는 HtmlMessageBox 페이지의 위치 url 을 반환한다
# 인  자 : 없음
# 설  명 : 현재 설정되어있는 HtmlMessageBox 페이지의 위치 url 을 반환한다.

# string AutoLink($szHtml)
# 리턴값 : 자동링크가 설정된 html 문자열
# 인  자 : 자동링크를 설정할 html 문자열
# 설  명 : 웹주소,이메일주소 등을 자동으로 링크로 만들어 준다

# CUtil 예제
# $myUtil = new CUtil
# if($myUtil->IsValidEMail("paranwave@korea.com")) echo "t"; else "f"; // t를 출력
# if($myUtil->IsValidHttp("http://teambus.pe.kg")) echo "t"; else "f"; // t를 출력
# if($myUtil->IsValidHttp("teambus.pe.kg")) echo "t"; else "f"; // f를 출력
# if($myUtil->IsDigit("1")) echo "t"; else "f"; // t를 출력
# if($myUtil->IsDigit("a")) echo "t"; else "f"; // f를 출력
# if($myUtil->IsSpace(" ")) echo "t"; else "f"; // t를 출력
# if($myUtil->IsEmpty("")) echo "t"; else "f"; // t를 출력
# if($myUtil->IsEmpty("     ")) echo "t"; else "f"; // t를 출력
# if($myUtil->IsEmpty("  a  ")) echo "t"; else "f"; // f를 출력
# $myUTIL->SetHMBoxAction("./HtmlMessageBox.php");
# $myUtil->HtmlMessageBox("회원님!!!\n비밀번호가 일치하지 않습니다","회원정보오류","http://www.w.w/idpwcheck.php");
# $myUtil->SetSpace(5); //html 파일내에 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 를 출력한다
+---------------+
|  CMail        |
+---------------+
설명 : 메일을 보내는 클래스
기능 : 파일 첨부, html 메일 보내기

# CMail($szClassName="CMail")
# 생성자

# __CMail($objInstance)
# 소멸자

# void SetFrom($szEmailAddr, $szName="")
# 반환값 : 없음
# 인  자 : 이메일주소, 이름 이름이 있을 경우 김성철 <paranwave@korea.com> 형식이 된다
# 설  명 : 보낸 사람의 이메일 주소를 설정한다

# string GetForm(void)
# 반환값 : 보낸사람의 이메일 주소를 반환한다
# 인  자 : 없음
# 설  명 :

# void SetTo($szEmailAddr, $szName="")
# 반환값 : 없음
# 인  자 : 이메일주소, 이름 이름이 있을 경우 김성철 <paranwave@korea.com> 형식이 된다
# 설  명 : 받는 사람의 이메일 주소를 설정한다

# string GetTo(void)
# 반환값 : 받는사람의 이메일 주소를 반환한다
# 인  자 : 없음
# 설  명 :

# void SetTitle($szTitle)
# 반환값 : 없음
# 인  자 : 메일 제목
# 설  명 : 메일의 제목을 설정한다

# string GetTitle(void)
# 반환값 : 메일의 제목
# 인  자 : 없음
# 설  명 : 메일의 제목을 얻는다

# void SetBody($szBody)
# 반환값 : 없음
# 인  자 : 메일 본문 내용
# 설  명 : 메일의 본문을 설정한다

# string GetBody(void)
# 반환값 : 메일의 본문
# 인  자 : 없음
# 설  명 : 메일의 본문을 얻는다

# bool SetBodyFromFile($szFileName)
# 반환값 : 파일에서 읽어와 본문을 설정하면 true 아니면 false
# 인  자 : 파일 이름
# 설  명 : 파일을 읽어와 메일 본문을 설정한다

# void SetAttachFile($fFile, $fFileName, $fFileType, $fFileSize)
# 반환값 : 없음
# 인  자 : 파일링크, 파일이름, 파일타입, 파일사이즈
#         파일폼을 쓸 경우 파일폼의 이름이 userfile 이면
#         SetAttachFile($userfile, $userfile_name, $userfile_type, $userfile_size);
#         로 하면 된다
# 설  명

# bool IsAttachFile(void)
# 반환값 : 파일 첨부인가 true/false
# 인  자 : 없음
# 설  명 :

# bool Send(void)
# 반환값 : 메일 보내기가 성공하면 true 아니면 false
# 인  자 : 없음
# 설  명 : 메일을 보낸다

# 메일보내기예
# $myMail = new CMail;
# $myMail->SetTo("paranwave@korea.com","김성철");
# $myMail->SetFrom("paranwave@metarica.com", "나야");
# $myMail->SetTitle("안녕하세요^^");
# $myMail->SetBodyFromFile("./MailMessage.txt"); or $myMail->SetBody("하하하 방가방가");
# if($myMail->Send())
# {
#      echo "메일을 보냈습니다";
# }
# else
# {
#      echo "메일을 보내지 못했습니다";
# }

+---------------+
|  CEnv         |
+---------------+
설명 : 각종 환경변수 값을 얻거나 설정한다
기능 : 클라이언트의 IP 를 얻는다 등등

# CEnv($szClassName="CEnv")
# 설 명 : 생성자

# __CEnv($objInstance)
# 설 명 : 소멸자

# Exception($exception)
# 예외처리

# fixed GetEnv($szEnv)
# 반환값 : $szEnv 의 값에 따른 조사된 환경변수값
# 인  자 : 조사하고 하는 환경변수
# 설  명 : 자세한 사항은 getenv 함수 참고

# void SetEnv($szEnv)
# 반환값 : 없음
# 인  자 : 주어진 환경변수를 설정한다
# 설  명 : 주어진 환경변수를 설정한다

# string GetCliendIp(void)
# 반환값 : 서버에 접속되어있는 클라이언트 IP 주소
# 인 자 : 없음
# 설  명 : 서버에 접속되어있는 클라이언트 IP 주소

# string GetServerInfo(void)
# 반환값 : 웹서버의 이름과 버전
# 인 자 : 없음
# 설  명 : 웹서버의 이름과 버전을 얻는다

# string GetServerName(void)
# 반환값 : 웹서버의 이름 또는 IP 주소
# 인 자 : 없음
# 설  명 : 웹서버의 이름 또는 IP 주소를 얻는다

# string GetServerProtocol(void)
# 반환값 : 서버의 프로토콜, 보통 HTTP/1.0 이나 HTTP/1.1 에 해당
# 인  자 : 없음
# 설  명 : 서버의 프로토콜, 보통 HTTP/1.0 이나 HTTP/1.1 에 해당

# string GetCgiType(void)
# 반환값 : 서버의 CGI 타입과 계정레벨을 반환(CGI/1.1)
# 인  자 : 없음
# 설  명 : 서버의 CGI 타입과 계정레벨을 반환(CGI/1.1)

# string GetServerPort(void)
# 반환값 : 클라이언트 요청을 보내는 포트번호, 보통 80번
# 인  자 : 없음
# 설  명 : 클라이언트 요청을 보내는 포트번호, 보통 80번

# string GetRequestMethod(void)
# 반환값 : CGI에서 데이타를 전송하는 방법으로 GET이나 POST
# 인  자 : 없음
# 설  명 :

# string GetPathInfo(void)
# 반환값 : 스크립트에 대한 경로 정보
# 인  자 : 없음
# 설  명 :

# string GetPathTranslated(void)
# 반환값 : PATH_INFO 경로를 실제 물리적 경로로 표시한 것
# 인  자 : 없음
# 설  명 : 없음

# string GetScriptName(void)
# 반환값 : 현재 실행중인 스크립트의 파일 이름
# 인  자 : 없음
# 설  명 :

# string GetQueryString(void)
# 반환값 : GET 이나 POST 방식을 통해 전달된 값을 저장
# 인  자 : 없음
# 설  명 :

# string GetClientName(void)
# 반환값 : 스크립트 실행을 요청한 클라이언트 호스트 이름
# 인  자 : 없음
# 설  명 :

# string GetConetntLength(void)
# 반환값 : POST 방식을 통해 전달되는 입력 정보의 길이를 표시
# 인  자 : 없음
# 설  명 :

# string GetUserAgent(void)
# 반환값 : 스크립트 실행을 요청한 브라우저의 종류 및 버전을 표시
# 인  자 : 없음
# 설  명 :

# string GetLastModifyTime($szTimeFormat="Y/m/d H:i:s")
# 반환값 : 실행 스크립트파일이 최종적으로 수정된 시간을 반환
# 인  자 : 시간의 포멧
# 설  명 :

# void GetPhpInfo(void)
# 설  명 : phpinfo() 을 수행

# string GetPhpVersion(void)
# 설  명 : 현재의 php 버전을 반환


/* zlib 를 사용하는 예 */
/* 컴파일: gcc -Wall comptest.c -o comptest -lz */
/* -lz 는 libz.a 를 링크 한다(아마 /usr/local/lib 에 있다)*/

#include <stdio.h>
#include <stdlib.h>
#include <zlib.h>               /* /usr/local/include/zlib.h */

#define INBUFSIZ   1024         /* 입력 버퍼 사이즈(임의) */
#define OUTBUFSIZ  1024         /* 출력 버퍼 사이즈(임의) */

z_stream z;                     /* 라이브러리와 교환하기 위한 구조체 */

char inbuf[INBUFSIZ];           /* 입력 버퍼 */
char outbuf[OUTBUFSIZ];         /* 출력 버퍼 */
FILE *fin, *fout;               /* 입력·출력 파일 */

void do_compress(void)          /* 압축 */
{
    int count, flush, status;

    /* 모든 메모리 메니지먼트를 라이브러리에 맡긴다 */
    z.zalloc = Z_NULL;
    z.zfree = Z_NULL;
    z.opaque = Z_NULL;

    /* 초기화 */
    /* 제2 인수는 압축의도 합. 0~9 의 범위의 정수로, 0 은 무압축 */
    /* Z_DEFAULT_COMPRESSION (= 6)가 표준 */
    if (deflateInit(&z, Z_DEFAULT_COMPRESSION) ! = Z_OK) {
        fprintf(stderr, "deflateInit: %s\n", (z.msg) ?  z.msg : "???");
        exit(1);
    }

    z.avail_in = 0;             /* 입력 버퍼중의 데이터의 바이트수 */
    z.next_out = outbuf;        /* 출력 포인터 */
    z.avail_out = OUTBUFSIZ;    /* 출력 버퍼의 사이즈 */

    /* 통상은 deflate()의 제2 인수는 Z_NO_FLUSH 로 해 호출한다 */
    flush = Z_NO_FLUSH;

    while (1) {
        if (z.avail_in == 0) {  /* 입력이 다하면 */
            z.next_in = inbuf;  /* 입력 포인터를 입력 버퍼의 선두에 */
            z.avail_in = fread(inbuf, 1, INBUFSIZ, fin); /* 데이터를 읽어들인다 */

            /* 입력이 마지막에 되면(자) deflate()의 제2 인수는 Z_FINISH 로 한다 */
            if (z.avail_in < INBUFSIZ) flush = Z_FINISH;
        }
        status = deflate(&z, flush); /* 압축한다 */
        if (status == Z_STREAM_END) break; /* 완료 */
        if (status ! = Z_OK) {   /* 에러 */
            fprintf(stderr, "deflate: %s\n", (z.msg) ?  z.msg : "???");
            exit(1);
        }
        if (z.avail_out == 0) { /* 출력 버퍼가 다하면 */
            /* 정리해 써낸다 */
            if (fwrite(outbuf, 1, OUTBUFSIZ, fout) ! = OUTBUFSIZ) {
                fprintf(stderr, "Write error\n");
                exit(1);
            }
            z.next_out = outbuf; /* 출력 버퍼 잔량을 바탕으로 되돌린다 */
            z.avail_out = OUTBUFSIZ; /* 출력 포인터를 바탕으로 되돌린다 */
        }
    }

    /* 나머지를 토해낸다 */
    if ((count = OUTBUFSIZ - z.avail_out) ! = 0) {
        if (fwrite(outbuf, 1, count, fout) ! = count) {
            fprintf(stderr, "Write error\n");
            exit(1);
        }
    }

    /* 뒤처리 */
    if (deflateEnd(&z) ! = Z_OK) {
        fprintf(stderr, "deflateEnd: %s\n", (z.msg) ?  z.msg : "???");
        exit(1);
    }
}

void do_decompress(void)        /* 전개(복원) */
{
    int count, status;

    /* 모든 메모리 메니지먼트를 라이브러리에 맡긴다 */
    z.zalloc = Z_NULL;
    z.zfree = Z_NULL;
    z.opaque = Z_NULL;

    /* 초기화 */
    z.next_in = Z_NULL;
    z.avail_in = 0;
    if (inflateInit(&z) ! = Z_OK) {
        fprintf(stderr, "inflateInit: %s\n", (z.msg) ?  z.msg : "???");
        exit(1);
    }

    z.next_out = outbuf;        /* 출력 포인터 */
    z.avail_out = OUTBUFSIZ;    /* 출력 버퍼 잔량 */
    status = Z_OK;

    while (status ! = Z_STREAM_END) {
        if (z.avail_in == 0) {  /* 입력 잔량이 제로가 되면 */
            z.next_in = inbuf;  /* 입력 포인터를 바탕으로 되돌린다 */
            z.avail_in = fread(inbuf, 1, INBUFSIZ, fin); /* 데이터를 읽는다 */
        }
        status = inflate(&z, Z_NO_FLUSH); /* 전개 */
        if (status == Z_STREAM_END) break; /* 완료 */
        if (status ! = Z_OK) {   /* 에러 */
            fprintf(stderr, "inflate: %s\n", (z.msg) ?  z.msg : "???");
            exit(1);
        }
        if (z.avail_out == 0) { /* 출력 버퍼가 다하면 */
            /* 정리해 써낸다 */
            if (fwrite(outbuf, 1, OUTBUFSIZ, fout) ! = OUTBUFSIZ) {
                fprintf(stderr, "Write error\n");
                exit(1);
            }
            z.next_out = outbuf; /* 출력 포인터를 바탕으로 되돌린다 */
            z.avail_out = OUTBUFSIZ; /* 출력 버퍼 잔량을 바탕으로 되돌린다 */
        }
    }

    /* 나머지를 토해낸다 */
    if ((count = OUTBUFSIZ - z.avail_out) ! = 0) {
        if (fwrite(outbuf, 1, count, fout) ! = count) {
            fprintf(stderr, "Write error\n");
            exit(1);
        }
    }

    /* 뒤처리 */
    if (inflateEnd(&z) ! = Z_OK) {
        fprintf(stderr, "inflateEnd: %s\n", (z.msg) ?  z.msg : "???");
        exit(1);
    }
}

int main(int argc, char *argv[])
{
    int c;

    if (argc ! = 4) {
        fprintf(stderr, "Usage: comptest flag infile outfile\n");
        fprintf(stderr, "  flag: c=compress d=decompress\n");
        exit(0);
    }
    if (argv[1][0] == 'c') {
        c = 1;
    } else if (argv[1][0] == 'd') {
        c = 0;
    } else {
        fprintf(stderr, "Unknown flag: %s\n", argv[1]);
        exit(1);
    }
    if ((fin = fopen(argv[2], "r")) == NULL) {
        fprintf(stderr, "Can't open %s\n", argv[2]);
        exit(1);
    }
    if ((fout = fopen(argv[3], "w")) == NULL) {
        fprintf(stderr, "Can't open %s\n", argv[3]);
        exit(1);
    }
    if (c) do_compress(); else do_decompress();
    fclose(fin);
    fclose(fout);
    return 0;
}


/* zlib 를 사용하는 예 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <io.h>
#define _WINDOWS                 //
#include "zlib.h"

FILE *fin, *fout;               /* 입력·출력 파일 */
void gz_compress(const char* src_filename)          /* 압축 */
{
    z_stream z;                     /* 라이브러리와 교환하기 위한 구조체 */
    unsigned char* inbuf = NULL;
    unsigned char* outbuf = NULL;
    char dest_filename[256];
    unsigned int count, flush, status;
    unsigned long INBUFSIZE;
    unsigned long OUTBUFSIZE;
    //파일 네임 설정
    strcpy(dest_filename, src_filename);
    strcat(dest_filename,".zgz");
    fin = fopen(src_filename, "rb");
    if(fin == NULL)
    {
        printf("%s open failed\n", src_filename);
        exit(1);
    }
    fout = fopen(dest_filename, "wb");
    if(fout == NULL)
    {
        printf("%s open failed\n", dest_filename);
        exit(1);
    }

    //압축을 하기 위한 환경 설정
    INBUFSIZE = filelength(fileno(fin));
    OUTBUFSIZE = INBUFSIZE+1024;
    inbuf = (unsigned char *)malloc(INBUFSIZE);
    outbuf = (unsigned char *)malloc(OUTBUFSIZE);

    /* 모든 메모리 메니지먼트를 라이브러리에 맡긴다 */
    z.zalloc = Z_NULL;
    z.zfree = Z_NULL;
    z.opaque = Z_NULL;

    /* 초기화 */
    /* 제2 인수는 압축의도 합. 0~9 의 범위의 정수로, 0 은 무압축 */
    /* Z_DEFAULT_COMPRESSION (= 6)가 표준 */
    if (deflateInit(&z, Z_DEFAULT_COMPRESSION) != Z_OK) {
        fprintf(stderr, "deflateInit: %s\n", (z.msg) ?  z.msg : "???");
        exit(1);
    }

    z.avail_in = 0;             /* 입력 버퍼중의 데이터의 바이트수 */
    z.next_out = outbuf;        /* 출력 포인터 */
    z.avail_out = OUTBUFSIZE;    /* 출력 버퍼의 사이즈 */

    /* 통상은 deflate()의 제2 인수는 Z_NO_FLUSH 로 해 호출한다 */
    flush = Z_NO_FLUSH;

    while (1) {
        if (z.avail_in == 0) {  /* 입력이 다하면 */
            z.next_in = inbuf;  /* 입력 포인터를 입력 버퍼의 선두에 */
            z.avail_in = fread(inbuf, 1, INBUFSIZE, fin); /* 데이터를 읽어들인다 */

            /* 입력이 마지막에 되면(자) deflate()의 제2 인수는 Z_FINISH 로 한다 */
            if (z.avail_in < INBUFSIZE) flush = Z_FINISH;
        }
        status = deflate(&z, flush); /* 압축한다 */
        if (status == Z_STREAM_END) break; /* 완료 */
        if (status != Z_OK) {   /* 에러 */
            fprintf(stderr, "deflate: %s\n", (z.msg) ?  z.msg : "???");
            exit(1);
        }
        if (z.avail_out == 0) { /* 출력 버퍼가 다하면 */
            /* 정리해 써낸다 */
            if (fwrite(outbuf, 1, OUTBUFSIZE, fout) != OUTBUFSIZE) {
                fprintf(stderr, "Write error\n");
                exit(1);
            }
            z.next_out = outbuf; /* 출력 버퍼 잔량을 바탕으로 되돌린다 */
            z.avail_out = OUTBUFSIZE; /* 출력 포인터를 바탕으로 되돌린다 */
        }
    }

    /* 나머지를 토해낸다 */
    if ((count = OUTBUFSIZE - z.avail_out) != 0) {
        if (fwrite(outbuf, 1, count, fout) != count) {
            fprintf(stderr, "Write error\n");
            exit(1);
        }
    }

    /* 뒤처리 */
    if (deflateEnd(&z) != Z_OK) {
        fprintf(stderr, "deflateEnd: %s\n", (z.msg) ?  z.msg : "???");
        exit(1);
    }
    free(inbuf);
    free(outbuf);
    fclose(fin);
    fclose(fout);
}

void gz_decompress(const char* src_filename)        /* 전개(복원) */
{
    z_stream z;
    unsigned char* inbuf = NULL;
    unsigned char* outbuf = NULL;
    char dest_filename[256]={0,};
    unsigned int count, status;
    unsigned long INBUFSIZE;
    unsigned long OUTBUFSIZE;
    //파일 네임 설정
    int strl;
    strl = strlen(src_filename);
    strncpy(dest_filename,src_filename, strl-4);
    printf(dest_filename);
    fin = fopen(src_filename, "rb");
    if(fin == NULL)
    {
        printf("%s open failed\n", src_filename);
        exit(1);
    }
    fout = fopen(dest_filename, "wb");
    if(fout == NULL)
    {
        printf("%s open failed\n", dest_filename);
        exit(1);
    }

    //압축을 하기 위한 환경 설정
    INBUFSIZE = filelength(fileno(fin));
    OUTBUFSIZE = INBUFSIZE;
    inbuf = (unsigned char *)malloc(INBUFSIZE);
    outbuf = (unsigned char *)malloc(OUTBUFSIZE);

    /* 모든 메모리 메니지먼트를 라이브러리에 맡긴다 */
    z.zalloc = Z_NULL;
    z.zfree = Z_NULL;
    z.opaque = Z_NULL;

    /* 초기화 */
    z.next_in = Z_NULL;
    z.avail_in = 0;
    if (inflateInit(&z) != Z_OK) {
        fprintf(stderr, "inflateInit: %s\n", (z.msg) ?  z.msg : "???");
        exit(1);
    }

    z.next_out = outbuf;        /* 출력 포인터 */
    z.avail_out = OUTBUFSIZE;    /* 출력 버퍼 잔량 */
    status = Z_OK;

    while (status != Z_STREAM_END) {
        if (z.avail_in == 0) {  /* 입력 잔량이 제로가 되면 */
            z.next_in = inbuf;  /* 입력 포인터를 바탕으로 되돌린다 */
            z.avail_in = fread(inbuf, 1, INBUFSIZE, fin); /* 데이터를 읽는다 */
        }
        status = inflate(&z, Z_NO_FLUSH); /* 전개 */
        if (status == Z_STREAM_END) break; /* 완료 */
        if (status != Z_OK) {   /* 에러 */
            fprintf(stderr, "inflate: %s\n", (z.msg) ?  z.msg : "???");
            exit(1);
        }
        if (z.avail_out == 0) { /* 출력 버퍼가 다하면 */
            /* 정리해 써낸다 */
            if (fwrite(outbuf, 1, OUTBUFSIZE, fout) != OUTBUFSIZE) {
                fprintf(stderr, "Write error\n");
                exit(1);
            }
            z.next_out = outbuf; /* 출력 포인터를 바탕으로 되돌린다 */
            z.avail_out = OUTBUFSIZE; /* 출력 버퍼 잔량을 바탕으로 되돌린다 */
        }
    }

    /* 나머지를 토해낸다 */
    if ((count = OUTBUFSIZE - z.avail_out) != 0) {
        if (fwrite(outbuf, 1, count, fout) != count) {
            fprintf(stderr, "Write error\n");
            exit(1);
        }
    }

    /* 뒤처리 */
    if (inflateEnd(&z) != Z_OK) {
        fprintf(stderr, "inflateEnd: %s\n", (z.msg) ?  z.msg : "???");
        exit(1);
    }
}

int main(int argc, char *argv[])
{
    if (argc < 2 ) {
        printf("%s [c|d] in_filename\n", argv[0]);
        exit(0);
    }
    if(argv[1][0] == 'c')
    {
        gz_compress(argv[2]);
    }
    else if(argv[1][0] == 'd')
    {
        gz_decompress(argv[2]);
    }
    else
    {
        printf("%s [c|d] in_filename\n", argv[0]);
    }
    return 0;
}


#include <windows.h>
#include <memory.h>
#include <stdio.h>
#include <io.h>
#define _WINDOWS
#include "zlib.h"

void gz_compress(const char* src_filename)          /* 압축 */
{
    FILE *fin, *fout;               /* 입력·출력 파일 */
    z_stream z;                     /* 라이브러리와 교환하기 위한 구조체 */
    unsigned char* inbuf = NULL;
    unsigned char* outbuf = NULL;
    char dest_filename[256];
    unsigned int count, flush, status;
    unsigned long INBUFSIZE;
    unsigned long OUTBUFSIZE;
    char szAni[2] = { '-','+'};
    int iAniTab = 0;
    //파일 네임 설정
    strcpy(dest_filename, src_filename);
    strcat(dest_filename,".zgz");
    fin = fopen(src_filename, "rb");
    if(fin == NULL)
    {
        printf("%s open failed\n", src_filename);
        exit(1);
    }
    fout = fopen(dest_filename, "wb");
    if(fout == NULL)
    {
        printf("%s open failed\n", dest_filename);
        exit(1);
    }

    //압축을 하기 위한 환경 설정
    INBUFSIZE = filelength(fileno(fin));
    OUTBUFSIZE = INBUFSIZE+1024;
    inbuf = (unsigned char *)malloc(INBUFSIZE);
    outbuf = (unsigned char *)malloc(OUTBUFSIZE);

    /* 모든 메모리 메니지먼트를 라이브러리에 맡긴다 */
    z.zalloc = Z_NULL;
    z.zfree = Z_NULL;
    z.opaque = Z_NULL;

    /* 초기화 */
    /* 제2 인수는 압축의도 합. 0~9 의 범위의 정수로, 0 은 무압축 */
    /* Z_DEFAULT_COMPRESSION (= 6)가 표준 */
    if (deflateInit(&z, Z_DEFAULT_COMPRESSION) != Z_OK) {
        fprintf(stderr, "deflateInit: %s\n", (z.msg) ?  z.msg : "???");
        exit(1);
    }

    z.avail_in = 0;             /* 입력 버퍼중의 데이터의 바이트수 */
    z.next_out = outbuf;        /* 출력 포인터 */
    z.avail_out = OUTBUFSIZE;    /* 출력 버퍼의 사이즈 */

    /* 통상은 deflate()의 제2 인수는 Z_NO_FLUSH 로 해 호출한다 */
    flush = Z_NO_FLUSH;

    while (1) {
        if(iAniTab == 2)
        {
            iAniTab = 0;
        }
        else
        {
            printf("\r[ %s ] %c",src_filename, szAni[iAniTab]);
            iAniTab++;
        }
        if (z.avail_in == 0) {  /* 입력이 다하면 */
            z.next_in = inbuf;  /* 입력 포인터를 입력 버퍼의 선두에 */
            z.avail_in = fread(inbuf, 1, INBUFSIZE, fin); /* 데이터를 읽어들인다 */

            /* 입력이 마지막에 되면(자) deflate()의 제2 인수는 Z_FINISH 로 한다 */
            if (z.avail_in < INBUFSIZE) flush = Z_FINISH;
        }
        status = deflate(&z, flush); /* 압축한다 */
        if (status == Z_STREAM_END) break; /* 완료 */
        if (status != Z_OK) {   /* 에러 */
            fprintf(stderr, "deflate: %s\n", (z.msg) ?  z.msg : "???");
            exit(1);
        }
        if (z.avail_out == 0) { /* 출력 버퍼가 다하면 */
            /* 정리해 써낸다 */
            if (fwrite(outbuf, 1, OUTBUFSIZE, fout) != OUTBUFSIZE) {
                fprintf(stderr, "Write error\n");
                exit(1);
            }
            z.next_out = outbuf; /* 출력 버퍼 잔량을 바탕으로 되돌린다 */
            z.avail_out = OUTBUFSIZE; /* 출력 포인터를 바탕으로 되돌린다 */
        }
    }

    /* 나머지를 토해낸다 */
    if ((count = OUTBUFSIZE - z.avail_out) != 0) {
        if (fwrite(outbuf, 1, count, fout) != count) {
            fprintf(stderr, "Write error\n");
            exit(1);
        }
    }

    /* 뒤처리 */
    if (deflateEnd(&z) != Z_OK) {
        fprintf(stderr, "deflateEnd: %s\n", (z.msg) ?  z.msg : "???");
        exit(1);
    }

    free(inbuf);
    free(outbuf);
    fclose(fin);
    fclose(fout);
}
BOOL gz_AllFile(LPCTSTR szPath)
{
    char szFileName[MAX_PATH];
    char szDrive[_MAX_DRIVE];
    char szDir[MAX_PATH];
    char szNewPath[MAX_PATH];

    BOOL bResult = TRUE;
    HANDLE hSrc;
    WIN32_FIND_DATA wfd;

    hSrc = FindFirstFile(szPath, &wfd);
    if(hSrc == INVALID_HANDLE_VALUE)
    {
        printf("Searching failed\n");
        exit(1);
    }
    while(bResult)
    {
        _splitpath(szPath, szDrive, szDir, NULL, NULL);

        if(wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
        {
            if(wfd.cFileName[0] != '.')
            {
                wsprintf(szNewPath, "%s%s%s\\*.*", szDrive, szDir, wfd.cFileName);
                gz_AllFile(szNewPath);
            }
        }
        else
        {
            wsprintf(szFileName,"%s%s%s", szDrive, szDir, wfd.cFileName);
            gz_compress(szFileName);
        }
        bResult = FindNextFile(hSrc,&wfd);
    }
    FindClose(hSrc);
    return TRUE;
}
void main( int argc, char *argv[])
{
    BOOL bCompress = FALSE;
    BOOL bAllFile  = FALSE;
    if(argc != 3)
    {
        printf("USE: GetFileInfo [c] [in_filename|*]\n");
        exit(1);
    }
    if(argv[1][0] == 'c' || argv[1][0] == 'C')
    {
        bCompress = TRUE;
    }
    else
    {
        printf("USE: GetFileInfo [c] [in_filename|*]\n");
    }
    if(argv[2][0] == '*')
    {
        bAllFile = TRUE;
    }

    //압축을 합니다.
    if(bCompress)
    {
        if(!bAllFile)
        {
            gz_compress(argv[2]);
        }
        else
        {
            char szSearchMethod[MAX_PATH];
            GetCurrentDirectory(MAX_PATH, szSearchMethod);
            strcat(szSearchMethod,"\\*.*");
            gz_AllFile(szSearchMethod);
        }
    }

}


ZLIB 1

ZLIB 1.1.3

ZLIB 의 소개

zlib 는 zip 나 gzip 에 사용되고 있는 압축 알고리즘을 라이브러리화한 것입니다.
만든이는 zip 나 gzip 같이 Jean-loup Gailly 와 Mark Adler 입니다.

다행스럽게도 프리 라이브러리입니다.

차세대의 화상 포맷 PNG 의 압축도 zlib 으로 만들어지고 있습니다.

Java(JDK 1.1 이후)에는 zlib 에 해당되는 것이 만들어지고 있습니다.

java.util.zip 의 문서를 봐 주세요.

ZLIB 홈페이지

http://www.gzip.org/zlib/

PNG 홈페이지

http://www.libpng.org/pub/png/

ZLIB 설치하기

UNIX / LINUX

구하신 Zlib 이 Unix 용이라면 libz.a 라고 하는 라이브러리가 기본으로

/usr/local/lib 에 들어갑니다.

이 라이브러리를 사용하기 위한 인클루드 파일 zlib.h (와)과 zconf.h 가

/usr/local/include 에 들어갑니다.

zlib-1. 1.3.tar.gz 의 파일을 임의의 디렉토리에 압축을 풀고 ./configure 해

make 합니다.
만약을 위해 make test 로 테스트해, 에러가 나오지 않는 것을 확인하고

make install 합니다.

WINDOWS (VISUAL C++)

구하신 Zlib 을 임의의 디렉토리에 압축을 풀고 zlib.h (와)과 zconf.h 를

프로젝트 폴더에 복사합니다. Static 라이브러리를 사용하신다면 zlibstat.lib

도 해당 프로젝트 폴더로 복사합니다.

VC++ 의 프로젝트에 zlib.h 를 추가하고

Project > Setting > Link > Object/Liblary modules:

란에 zlibstat.lib 을 추가합니다.

ZLIB 사용하기

zlib 를 사용하는 프로그램은 zlib.h 의 헤더파일을 포함시켜야합니다.

#include <zlib.h> 또는 #include “zlib.h” (현재 디렉토리에 zlib.h가 있다면)

그리고, 우선 z_stream 라고 하는 형태의 구조체를 만듭니다.

z_stream z;
표준적인 압축의 방법은, 우선 세개의 변수를
z.zalloc = Z_NULL;
  z.zfree = Z_NULL;
z.opaque = Z_NULL;

(와)과 같이 초기화 하고 나서,

초기화 루틴 deflateInit(&z, Z_DEFAULT_COMPRESSION) (을)를 호출합니다

이 반환값이 Z_OK 나오지 않으면 에러입니다.

zlib 의 에러 메세지는 모두 z.msg (을)를 선두 번지로 하는 영역에 '\0'로 끝나는

캐릭터 라인으로서 기입해집니다.

다음에,

z.next_in = 입력 포인터;
z.avail_in = 입력 데이터의 잔량;
z.next_out = 출력 포인터;
z.avail_out = 출력 버퍼의 잔량;

와)과 같이 설정하고 ,deflate(&z, Z_NO_FLUSH) (을)를 호출합니다.

반환값이 Z_OK 나오지 않으면 에러입니다.

deflate(&z, Z_NO_FLUSH) 는 입력 버퍼의 데이터를 압축해 출력버퍼에 이를 써넣습니다.

포인터 z.next_in 와 z.next_out 를 진행시켜 잔량 z.avail_in 와 z.avail_out 를

줄입니다.

z.avail_in 가 0 이 되면, 다음의 입력 데이터를 준비해, 그 선두 주소를 z.avail_in,

데이터양을 z.avail_in 에 설정합니다.

같이 z.avail_out 가 0이 되면, 파일에 써내고 z.next_out 와 z.avail_out 를

갱신합니다.

마지막 입력이 되면 deflate(&z, Z_FINISH) 을 호출합니다.

이 때는, 반환값이 Z_OK 이면 아직 해 나머지 데이타가 있다고 하는 의미이므로,

출력 버퍼를 파일에 쓰면서 재차 deflate(&z, Z_FINISH) 를 호출합니다.

이것을 반환값이 Z_STREAM_END 가 될 때까지 계속합니다.

마지막에 뒤처리를 위해 deflateEnd(&z) 를 호출해, 사용한 메모리를 해제합니다.

역시 반환값이 Z_OK 나오지 않으면 에러입니다.

압축을 살펴보았지만 전개(복원)도 거의 같습니다만, 함수의 이름이 deflate...

로부터 inflate... 로 바뀝니다.

인수의 주는 방법 등은 압축의 경우보다 약간 간단하게 됩니다.

예제

UNIX / LINUX 예제 (알집으로 압축된 파일입니다)

[ 받기 | 보기 ]

gcc comptest.c -o comptest -lz

lz 옵션을 붙여 합니다. 이것은 zlib 라이브러리 libz.a 를 링크 한다고 하는 의미입니다.

Windows 예제(Visual C++) (알집으로 압축된 파일입니다)

하나의 파일 압축/풀기 예제

[ 받기 | 보기 ]

하위폴더까지 모두 압축하는 예제입니다.

[ 받기 | 보기 ]

컴파일시

LINK : warning LNK4098: defaultlib "LIBC" conflicts with use of other libs; use /NODEFAULTLIB:library

의 경고가 나오면

Project > Setting > Link > Object/Project Option 에

/NODEFAULTLIB:LIBC 를 입력해 주세요

문서 정보

원문 : 마쓰사카 대학 오쿠무라 하루히코 okumura@matsusaka-u.ac.jp

옮김 : 김성철 paranwave@korea.com

VC++ 예제파일 추가 : 김성철 paranwave@korea.com

이 문서를 다른 곳에 게시할 경우 김성철에게 메일을 보내주시고 문서정보를 반드시

포함하여 게시하여 주십시오


List of supported OpenGL extensions (version 1.0.1)

OpenGL Version

GL_VERSION_1_2 41 definitions, no type definitions, 38 functions
Definitions
 | GL_SMOOTH_POINT_SIZE_RANGE | 0xb12
 | GL_SMOOTH_POINT_SIZE_GRANULARITY | 0xb13
 | GL_SMOOTH_LINE_WIDTH_RANGE | 0xb22
 | GL_SMOOTH_LINE_WIDTH_GRANULARITY | 0xb23
 | GL_UNSIGNED_BYTE_3_3_2 | 0x8032
 | GL_UNSIGNED_SHORT_4_4_4_4 | 0x8033
 | GL_UNSIGNED_SHORT_5_5_5_1 | 0x8034
 | GL_UNSIGNED_INT_8_8_8_8 | 0x8035
 | GL_UNSIGNED_INT_10_10_10_2 | 0x8036
 | GL_RESCALE_NORMAL | 0x803a
 | GL_TEXTURE_BINDING_3D | 0x806a
 | GL_PACK_SKIP_IMAGES | 0x806b
 | GL_PACK_IMAGE_HEIGHT | 0x806c
 | GL_UNPACK_SKIP_IMAGES | 0x806d
 | GL_UNPACK_IMAGE_HEIGHT | 0x806e
 | GL_TEXTURE_3D | 0x806f
 | GL_PROXY_TEXTURE_3D | 0x8070
 | GL_TEXTURE_DEPTH | 0x8071
 | GL_TEXTURE_WRAP_R | 0x8072
 | GL_MAX_3D_TEXTURE_SIZE | 0x8073
 | GL_BGR | 0x80e0
 | GL_BGRA | 0x80e1
 | GL_MAX_ELEMENTS_VERTICES | 0x80e8
 | GL_MAX_ELEMENTS_INDICES | 0x80e9
 | GL_CLAMP_TO_EDGE | 0x812f
 | GL_TEXTURE_MIN_LOD | 0x813a
 | GL_TEXTURE_MAX_LOD | 0x813b
 | GL_TEXTURE_BASE_LEVEL | 0x813c
 | GL_TEXTURE_MAX_LEVEL | 0x813d
 | GL_LIGHT_MODEL_COLOR_CONTROL | 0x81f8
 | GL_SINGLE_COLOR | 0x81f9
 | GL_SEPARATE_SPECULAR_COLOR | 0x81fa
 | GL_UNSIGNED_BYTE_2_3_3_REV | 0x8362
 | GL_UNSIGNED_SHORT_5_6_5 | 0x8363
 | GL_UNSIGNED_SHORT_5_6_5_REV | 0x8364
 | GL_UNSIGNED_SHORT_4_4_4_4_REV | 0x8365
 | GL_UNSIGNED_SHORT_1_5_5_5_REV | 0x8366
 | GL_UNSIGNED_INT_8_8_8_8_REV | 0x8367
 | GL_UNSIGNED_INT_2_10_10_10_REV | 0x8368
 | GL_ALIASED_POINT_SIZE_RANGE | 0x846d
 | GL_ALIASED_LINE_WIDTH_RANGE | 0x846e
Functions
 | GLvoid glBlendColor(GLclampf, GLclampf, GLclampf, GLclampf);
 | GLvoid glBlendEquation(GLenum);
 | GLvoid glColorSubTable(GLenum, GLsizei, GLsizei, GLenum, GLenum, GLvoid const *);
 | GLvoid glColorTable(GLenum, GLenum, GLsizei, GLenum, GLenum, GLvoid const *);
 | GLvoid glColorTableParameterfv(GLenum, GLenum, GLfloat const *);
 | GLvoid glColorTableParameteriv(GLenum, GLenum, GLint const *);
 | GLvoid glConvolutionFilter1D(GLenum, GLenum, GLsizei, GLenum, GLenum, GLvoid const *);
 | GLvoid glConvolutionFilter2D(GLenum, GLenum, GLsizei, GLsizei, GLenum, GLenum, GLvoid const *);
 | GLvoid glConvolutionParameterf(GLenum, GLenum, GLfloat);
 | GLvoid glConvolutionParameterfv(GLenum, GLenum, GLfloat const *);
 | GLvoid glConvolutionParameteri(GLenum, GLenum, GLint);
 | GLvoid glConvolutionParameteriv(GLenum, GLenum, GLint const *);
 | GLvoid glCopyColorSubTable(GLenum, GLsizei, GLint, GLint, GLsizei);
 | GLvoid glCopyColorTable(GLenum, GLenum, GLint, GLint, GLsizei);
 | GLvoid glCopyConvolutionFilter1D(GLenum, GLenum, GLint, GLint, GLsizei);
 | GLvoid glCopyConvolutionFilter2D(GLenum, GLenum, GLint, GLint, GLsizei, GLsizei);
 | GLvoid glCopyTexSubImage3D(GLenum, GLint, GLint, GLint, GLint, GLint, GLint, GLsizei, GLsizei);
 | GLvoid glDrawRangeElements(GLenum, GLuint, GLuint, GLsizei, GLenum, GLvoid const *);
 | GLvoid glGetColorTable(GLenum, GLenum, GLenum, GLvoid *);
 | GLvoid glGetColorTableParameterfv(GLenum, GLenum, GLfloat *);
 | GLvoid glGetColorTableParameteriv(GLenum, GLenum, GLint *);
 | GLvoid glGetConvolutionFilter(GLenum, GLenum, GLenum, GLvoid *);
 | GLvoid glGetConvolutionParameterfv(GLenum, GLenum, GLfloat *);
 | GLvoid glGetConvolutionParameteriv(GLenum, GLenum, GLint *);
 | GLvoid glGetHistogram(GLenum, GLboolean, GLenum, GLenum, GLvoid *);
 | GLvoid glGetHistogramParameterfv(GLenum, GLenum, GLfloat *);
 | GLvoid glGetHistogramParameteriv(GLenum, GLenum, GLint *);
 | GLvoid glGetMinmax(GLenum, GLboolean, GLenum, GLenum, GLvoid *);
 | GLvoid glGetMinmaxParameterfv(GLenum, GLenum, GLfloat *);
 | GLvoid glGetMinmaxParameteriv(GLenum, GLenum, GLint *);
 | GLvoid glGetSeparableFilter(GLenum, GLenum, GLenum, GLvoid *, GLvoid *, GLvoid *);
 | GLvoid glHistogram(GLenum, GLsizei, GLenum, GLboolean);
 | GLvoid glMinmax(GLenum, GLenum, GLboolean);
 | GLvoid glResetHistogram(GLenum);
 | GLvoid glResetMinmax(GLenum);
 | GLvoid glSeparableFilter2D(GLenum, GLenum, GLsizei, GLsizei, GLenum, GLenum, GLvoid const *, GLvoid const *);
 | GLvoid glTexImage3D(GLenum, GLint, GLint, GLsizei, GLsizei, GLsizei, GLint, GLenum, GLenum, GLvoid const *);
 | GLvoid glTexSubImage3D(GLenum, GLint, GLint, GLint, GLint, GLsizei, GLsizei, GLsizei, GLenum, GLenum, GLvoid const *);
GL_VERSION_1_3 96 definitions, no type definitions, 46 functions
Definitions
 | GL_MULTISAMPLE | 0x809d
 | GL_SAMPLE_ALPHA_TO_COVERAGE | 0x809e
 | GL_SAMPLE_ALPHA_TO_ONE | 0x809f
 | GL_SAMPLE_COVERAGE | 0x80a0
 | GL_SAMPLE_BUFFERS | 0x80a8
 | GL_SAMPLES | 0x80a9
 | GL_SAMPLE_COVERAGE_VALUE | 0x80aa
 | GL_SAMPLE_COVERAGE_INVERT | 0x80ab
 | GL_CLAMP_TO_BORDER | 0x812d
 | GL_TEXTURE0 | 0x84c0
 | GL_TEXTURE1 | 0x84c1
 | GL_TEXTURE2 | 0x84c2
 | GL_TEXTURE3 | 0x84c3
 | GL_TEXTURE4 | 0x84c4
 | GL_TEXTURE5 | 0x84c5
 | GL_TEXTURE6 | 0x84c6
 | GL_TEXTURE7 | 0x84c7
 | GL_TEXTURE8 | 0x84c8
 | GL_TEXTURE9 | 0x84c9
 | GL_TEXTURE10 | 0x84ca
 | GL_TEXTURE11 | 0x84cb
 | GL_TEXTURE12 | 0x84cc
 | GL_TEXTURE13 | 0x84cd
 | GL_TEXTURE14 | 0x84ce
 | GL_TEXTURE15 | 0x84cf
 | GL_TEXTURE16 | 0x84d0
 | GL_TEXTURE17 | 0x84d1
 | GL_TEXTURE18 | 0x84d2
 | GL_TEXTURE19 | 0x84d3
 | GL_TEXTURE20 | 0x84d4
 | GL_TEXTURE21 | 0x84d5
 | GL_TEXTURE22 | 0x84d6
 | GL_TEXTURE23 | 0x84d7
 | GL_TEXTURE24 | 0x84d8
 | GL_TEXTURE25 | 0x84d9
 | GL_TEXTURE26 | 0x84da
 | GL_TEXTURE27 | 0x84db
 | GL_TEXTURE28 | 0x84dc
 | GL_TEXTURE29 | 0x84dd
 | GL_TEXTURE30 | 0x84de
 | GL_TEXTURE31 | 0x84df
 | GL_ACTIVE_TEXTURE | 0x84e0
 | GL_CLIENT_ACTIVE_TEXTURE | 0x84e1
 | GL_MAX_TEXTURE_UNITS | 0x84e2
 | GL_TRANSPOSE_MODELVIEW_MATRIX | 0x84e3
 | GL_TRANSPOSE_PROJECTION_MATRIX | 0x84e4
 | GL_TRANSPOSE_TEXTURE_MATRIX | 0x84e5
 | GL_TRANSPOSE_COLOR_MATRIX | 0x84e6
 | GL_SUBTRACT | 0x84e7
 | GL_COMPRESSED_ALPHA | 0x84e9
 | GL_COMPRESSED_LUMINANCE | 0x84ea
 | GL_COMPRESSED_LUMINANCE_ALPHA | 0x84eb
 | GL_COMPRESSED_INTENSITY | 0x84ec
 | GL_COMPRESSED_RGB | 0x84ed
 | GL_COMPRESSED_RGBA | 0x84ee
 | GL_TEXTURE_COMPRESSION_HINT | 0x84ef
 | GL_NORMAL_MAP | 0x8511
 | GL_REFLECTION_MAP | 0x8512
 | GL_TEXTURE_CUBE_MAP | 0x8513
 | GL_TEXTURE_BINDING_CUBE_MAP | 0x8514
 | GL_TEXTURE_CUBE_MAP_POSITIVE_X | 0x8515
 | GL_TEXTURE_CUBE_MAP_NEGATIVE_X | 0x8516
 | GL_TEXTURE_CUBE_MAP_POSITIVE_Y | 0x8517
 | GL_TEXTURE_CUBE_MAP_NEGATIVE_Y | 0x8518
 | GL_TEXTURE_CUBE_MAP_POSITIVE_Z | 0x8519
 | GL_TEXTURE_CUBE_MAP_NEGATIVE_Z | 0x851a
 | GL_PROXY_TEXTURE_CUBE_MAP | 0x851b
 | GL_MAX_CUBE_MAP_TEXTURE_SIZE | 0x851c
 | GL_COMBINE | 0x8570
 | GL_COMBINE_RGB | 0x8571
 | GL_COMBINE_ALPHA | 0x8572
 | GL_RGB_SCALE | 0x8573
 | GL_ADD_SIGNED | 0x8574
 | GL_INTERPOLATE | 0x8575
 | GL_CONSTANT | 0x8576
 | GL_PRIMARY_COLOR | 0x8577
 | GL_PREVIOUS | 0x8578
 | GL_SOURCE0_RGB | 0x8580
 | GL_SOURCE1_RGB | 0x8581
 | GL_SOURCE2_RGB | 0x8582
 | GL_SOURCE0_ALPHA | 0x8588
 | GL_SOURCE1_ALPHA | 0x8589
 | GL_SOURCE2_ALPHA | 0x858a
 | GL_OPERAND0_RGB | 0x8590
 | GL_OPERAND1_RGB | 0x8591
 | GL_OPERAND2_RGB | 0x8592
 | GL_OPERAND0_ALPHA | 0x8598
 | GL_OPERAND1_ALPHA | 0x8599
 | GL_OPERAND2_ALPHA | 0x859a
 | GL_TEXTURE_COMPRESSED_IMAGE_SIZE | 0x86a0
 | GL_TEXTURE_COMPRESSED | 0x86a1
 | GL_NUM_COMPRESSED_TEXTURE_FORMATS | 0x86a2
 | GL_COMPRESSED_TEXTURE_FORMATS | 0x86a3
 | GL_DOT3_RGB | 0x86ae
 | GL_DOT3_RGBA | 0x86af
 | GL_MULTISAMPLE_BIT | 0x20000000
Functions
 | GLvoid glActiveTexture(GLenum);
 | GLvoid glClientActiveTexture(GLenum);
 | GLvoid glCompressedTexImage1D(GLenum, GLint, GLenum, GLsizei, GLint, GLsizei, GLvoid const *);
 | GLvoid glCompressedTexImage2D(GLenum, GLint, GLenum, GLsizei, GLsizei, GLint, GLsizei, GLvoid const *);
 | GLvoid glCompressedTexImage3D(GLenum, GLint, GLenum, GLsizei, GLsizei, GLsizei, GLint, GLsizei, GLvoid const *);
 | GLvoid glCompressedTexSubImage1D(GLenum, GLint, GLint, GLsizei, GLenum, GLsizei, GLvoid const *);
 | GLvoid glCompressedTexSubImage2D(GLenum, GLint, GLint, GLint, GLsizei, GLsizei, GLenum, GLsizei, GLvoid const *);
 | GLvoid glCompressedTexSubImage3D(GLenum, GLint, GLint, GLint, GLint, GLsizei, GLsizei, GLsizei, GLenum, GLsizei, GLvoid const *);
 | GLvoid glGetCompressedTexImage(GLenum, GLint, GLvoid *);
 | GLvoid glLoadTransposeMatrixd(GLdouble const *);
 | GLvoid glLoadTransposeMatrixf(GLfloat const *);
 | GLvoid glMultiTexCoord1d(GLenum, GLdouble);
 | GLvoid glMultiTexCoord1dv(GLenum, GLdouble const *);
 | GLvoid glMultiTexCoord1f(GLenum, GLfloat);
 | GLvoid glMultiTexCoord1fv(GLenum, GLfloat const *);
 | GLvoid glMultiTexCoord1i(GLenum, GLint);
 | GLvoid glMultiTexCoord1iv(GLenum, GLint const *);
 | GLvoid glMultiTexCoord1s(GLenum, GLshort);
 | GLvoid glMultiTexCoord1sv(GLenum, GLshort const *);
 | GLvoid glMultiTexCoord2d(GLenum, GLdouble, GLdouble);
 | GLvoid glMultiTexCoord2dv(GLenum, GLdouble const *);
 | GLvoid glMultiTexCoord2f(GLenum, GLfloat, GLfloat);
 | GLvoid glMultiTexCoord2fv(GLenum, GLfloat const *);
 | GLvoid glMultiTexCoord2i(GLenum, GLint, GLint);
 | GLvoid glMultiTexCoord2iv(GLenum, GLint const *);
 | GLvoid glMultiTexCoord2s(GLenum, GLshort, GLshort);
 | GLvoid glMultiTexCoord2sv(GLenum, GLshort const *);
 | GLvoid glMultiTexCoord3d(GLenum, GLdouble, GLdouble, GLdouble);
 | GLvoid glMultiTexCoord3dv(GLenum, GLdouble const *);
 | GLvoid glMultiTexCoord3f(GLenum, GLfloat, GLfloat, GLfloat);
 | GLvoid glMultiTexCoord3fv(GLenum, GLfloat const *);
 | GLvoid glMultiTexCoord3i(GLenum, GLint, GLint, GLint);
 | GLvoid glMultiTexCoord3iv(GLenum, GLint const *);
 | GLvoid glMultiTexCoord3s(GLenum, GLshort, GLshort, GLshort);
 | GLvoid glMultiTexCoord3sv(GLenum, GLshort const *);
 | GLvoid glMultiTexCoord4d(GLenum, GLdouble, GLdouble, GLdouble, GLdouble);
 | GLvoid glMultiTexCoord4dv(GLenum, GLdouble const *);
 | GLvoid glMultiTexCoord4f(GLenum, GLfloat, GLfloat, GLfloat, GLfloat);
 | GLvoid glMultiTexCoord4fv(GLenum, GLfloat const *);
 | GLvoid glMultiTexCoord4i(GLenum, GLint, GLint, GLint, GLint);
 | GLvoid glMultiTexCoord4iv(GLenum, GLint const *);
 | GLvoid glMultiTexCoord4s(GLenum, GLshort, GLshort, GLshort, GLshort);
 | GLvoid glMultiTexCoord4sv(GLenum, GLshort const *);
 | GLvoid glMultTransposeMatrixd(GLdouble const *);
 | GLvoid glMultTransposeMatrixf(GLfloat const *);
 | GLvoid glSampleCoverage(GLclampf, GLboolean);
GL_VERSION_1_4 39 definitions, no type definitions, 45 functions
Definitions
 | GL_BLEND_DST_RGB | 0x80c8
 | GL_BLEND_SRC_RGB | 0x80c9
 | GL_BLEND_DST_ALPHA | 0x80ca
 | GL_BLEND_SRC_ALPHA | 0x80cb
 | GL_POINT_SIZE_MIN | 0x8126
 | GL_POINT_SIZE_MAX | 0x8127
 | GL_POINT_FADE_THRESHOLD_SIZE | 0x8128
 | GL_POINT_DISTANCE_ATTENUATION | 0x8129
 | GL_GENERATE_MIPMAP | 0x8191
 | GL_GENERATE_MIPMAP_HINT | 0x8192
 | GL_DEPTH_COMPONENT16 | 0x81a5
 | GL_DEPTH_COMPONENT24 | 0x81a6
 | GL_DEPTH_COMPONENT32 | 0x81a7
 | GL_MIRRORED_REPEAT | 0x8370
 | GL_FOG_COORDINATE_SOURCE | 0x8450
 | GL_FOG_COORDINATE | 0x8451
 | GL_FRAGMENT_DEPTH | 0x8452
 | GL_CURRENT_FOG_COORDINATE | 0x8453
 | GL_FOG_COORDINATE_ARRAY_TYPE | 0x8454
 | GL_FOG_COORDINATE_ARRAY_STRIDE | 0x8455
 | GL_FOG_COORDINATE_ARRAY_POINTER | 0x8456
 | GL_FOG_COORDINATE_ARRAY | 0x8457
 | GL_COLOR_SUM | 0x8458
 | GL_CURRENT_SECONDARY_COLOR | 0x8459
 | GL_SECONDARY_COLOR_ARRAY_SIZE | 0x845a
 | GL_SECONDARY_COLOR_ARRAY_TYPE | 0x845b
 | GL_SECONDARY_COLOR_ARRAY_STRIDE | 0x845c
 | GL_SECONDARY_COLOR_ARRAY_POINTER | 0x845d
 | GL_SECONDARY_COLOR_ARRAY | 0x845e
 | GL_MAX_TEXTURE_LOD_BIAS | 0x84fd
 | GL_TEXTURE_FILTER_CONTROL | 0x8500
 | GL_TEXTURE_LOD_BIAS | 0x8501
 | GL_INCR_WRAP | 0x8507
 | GL_DECR_WRAP | 0x8508
 | GL_TEXTURE_DEPTH_SIZE | 0x884a
 | GL_DEPTH_TEXTURE_MODE | 0x884b
 | GL_TEXTURE_COMPARE_MODE | 0x884c
 | GL_TEXTURE_COMPARE_FUNC | 0x884d
 | GL_COMPARE_R_TO_TEXTURE | 0x884e
Functions
 | GLvoid glBlendFuncSeparate(GLenum, GLenum, GLenum, GLenum);
 | GLvoid glFogCoordd(GLdouble);
 | GLvoid glFogCoorddv(GLdouble const *);
 | GLvoid glFogCoordf(GLfloat);
 | GLvoid glFogCoordfv(GLfloat const *);
 | GLvoid glFogCoordPointer(GLenum, GLsizei, GLvoid const *);
 | GLvoid glMultiDrawArrays(GLenum, GLint *, GLsizei *, GLsizei);
 | GLvoid glMultiDrawElements(GLenum, GLsizei const *, GLenum, GLvoid const * *, GLsizei);
 | GLvoid glPointParameterf(GLenum, GLfloat);
 | GLvoid glPointParameterfv(GLenum, GLfloat const *);
 | GLvoid glPointParameteri(GLenum, GLint);
 | GLvoid glPointParameteriv(GLenum, GLint const *);
 | GLvoid glSecondaryColor3b(GLbyte, GLbyte, GLbyte);
 | GLvoid glSecondaryColor3bv(GLbyte const *);
 | GLvoid glSecondaryColor3d(GLdouble, GLdouble, GLdouble);
 | GLvoid glSecondaryColor3dv(GLdouble const *);
 | GLvoid glSecondaryColor3f(GLfloat, GLfloat, GLfloat);
 | GLvoid glSecondaryColor3fv(GLfloat const *);
 | GLvoid glSecondaryColor3i(GLint, GLint, GLint);
 | GLvoid glSecondaryColor3iv(GLint const *);
 | GLvoid glSecondaryColor3s(GLshort, GLshort, GLshort);
 | GLvoid glSecondaryColor3sv(GLshort const *);
 | GLvoid glSecondaryColor3ub(GLubyte, GLubyte, GLubyte);
 | GLvoid glSecondaryColor3ubv(GLubyte const *);
 | GLvoid glSecondaryColor3ui(GLuint, GLuint, GLuint);
 | GLvoid glSecondaryColor3uiv(GLuint const *);
 | GLvoid glSecondaryColor3us(GLushort, GLushort, GLushort);
 | GLvoid glSecondaryColor3usv(GLushort const *);
 | GLvoid glSecondaryColorPointer(GLint, GLenum, GLsizei, GLvoid const *);
 | GLvoid glWindowPos2d(GLdouble, GLdouble);
 | GLvoid glWindowPos2dv(GLdouble const *);
 | GLvoid glWindowPos2f(GLfloat, GLfloat);
 | GLvoid glWindowPos2fv(GLfloat const *);
 | GLvoid glWindowPos2i(GLint, GLint);
 | GLvoid glWindowPos2iv(GLint const *);
 | GLvoid glWindowPos2s(GLshort, GLshort);
 | GLvoid glWindowPos2sv(GLshort const *);
 | GLvoid glWindowPos3d(GLdouble, GLdouble, GLdouble);
 | GLvoid glWindowPos3dv(GLdouble const *);
 | GLvoid glWindowPos3f(GLfloat, GLfloat, GLfloat);
 | GLvoid glWindowPos3fv(GLfloat const *);
 | GLvoid glWindowPos3i(GLint, GLint, GLint);
 | GLvoid glWindowPos3iv(GLint const *);
 | GLvoid glWindowPos3s(GLshort, GLshort, GLshort);
 | GLvoid glWindowPos3sv(GLshort const *);

OpenGL Extension

GL_3DFX_multisample 4 definitions, no type definitions, no functions
Definitions
 | GL_MULTISAMPLE_3DFX | 0x86b2
 | GL_SAMPLE_BUFFERS_3DFX | 0x86b3
 | GL_SAMPLES_3DFX | 0x86b4
 | GL_MULTISAMPLE_BIT_3DFX | 0x20000000
GL_3DFX_tbuffer no definitions, no type definitions, 1 function
Functions
GLvoid glTbufferMask3DFX(GLuint);
GL_3DFX_texture_compression_FXT1 2 definitions, no type definitions, no functions
Definitions
 | GL_COMPRESSED_RGB_FXT1_3DFX | 0x86b0
 | GL_COMPRESSED_RGBA_FXT1_3DFX | 0x86b1
GL_APPLE_client_storage 1 definition, no type definitions, no functions
Definitions
GL_UNPACK_CLIENT_STORAGE_APPLE
0x85b2
GL_APPLE_element_array 3 definitions, no type definitions, 5 functions
Definitions
 | GL_ELEMENT_ARRAY_APPLE | 0x8768
 | GL_ELEMENT_ARRAY_TYPE_APPLE | 0x8769
 | GL_ELEMENT_ARRAY_POINTER_APPLE | 0x876a
Functions
 | GLvoid glDrawElementArrayAPPLE(GLenum, GLint, GLsizei);
 | GLvoid glDrawRangeElementArrayAPPLE(GLenum, GLuint, GLuint, GLint, GLsizei);
 | GLvoid glElementPointerAPPLE(GLenum, GLvoid const *);
 | GLvoid glMultiDrawElementArrayAPPLE(GLenum, GLint const *, GLsizei const *, GLsizei);
 | GLvoid glMultiDrawRangeElementArrayAPPLE(GLenum, GLuint, GLuint, GLint const *, GLsizei const *, GLsizei);
GL_APPLE_fence 2 definitions, no type definitions, 8 functions
Definitions
 | GL_DRAW_PIXELS_APPLE | 0x8a0a
 | GL_FENCE_APPLE | 0x8a0b
Functions
 | GLvoid glDeleteFencesAPPLE(GLsizei, GLuint const *);
 | GLvoid glFinishFenceAPPLE(GLuint);
 | GLvoid glFinishObjectAPPLE(GLenum, GLint);
 | GLvoid glGenFencesAPPLE(GLsizei, GLuint *);
 | GLboolean glIsFenceAPPLE(GLuint);
 | GLvoid glSetFenceAPPLE(GLuint);
 | GLboolean glTestFenceAPPLE(GLuint);
 | GLboolean glTestObjectAPPLE(GLenum, GLuint);
GL_APPLE_specular_vector 1 definition, no type definitions, no functions
Definitions
GL_LIGHT_MODEL_SPECULAR_VECTOR_APPLE
0x85b0
GL_APPLE_transform_hint 1 definition, no type definitions, no functions
Definitions
GL_TRANSFORM_HINT_APPLE
0x85b1
GL_APPLE_vertex_array_object 1 definition, no type definitions, 4 functions
Definitions
 | GL_VERTEX_ARRAY_BINDING_APPLE | 0x85b5
Functions
 | GLvoid glBindVertexArrayAPPLE(GLuint);
 | GLvoid glDeleteVertexArraysAPPLE(GLsizei, GLuint const *);
 | GLvoid glGenVertexArraysAPPLE(GLsizei, GLuint const *);
 | GLboolean glIsVertexArrayAPPLE(GLuint);
GL_APPLE_vertex_array_range 6 definitions, no type definitions, 3 functions
Definitions
 | GL_VERTEX_ARRAY_RANGE_APPLE | 0x851d
 | GL_VERTEX_ARRAY_RANGE_LENGTH_APPLE | 0x851e
 | GL_VERTEX_ARRAY_STORAGE_HINT_APPLE | 0x851f
 | GL_VERTEX_ARRAY_RANGE_POINTER_APPLE | 0x8521
 | GL_STORAGE_CACHED_APPLE | 0x85be
 | GL_STORAGE_SHARED_APPLE | 0x85bf
Functions
 | GLvoid glFlushVertexArrayRangeAPPLE(GLsizei, GLvoid *);
 | GLvoid glVertexArrayParameteriAPPLE(GLenum, GLint);
 | GLvoid glVertexArrayRangeAPPLE(GLsizei, GLvoid *);
GL_APPLE_ycbcr_422 3 definitions, no type definitions, no functions
Definitions
 | GL_YCBCR_422_APPLE | 0x85b9
 | GL_UNSIGNED_SHORT_8_8_APPLE | 0x85ba
 | GL_UNSIGNED_SHORT_8_8_REV_APPLE | 0x85bb
GL_ARB_depth_texture 5 definitions, no type definitions, no functions
Definitions
 | GL_DEPTH_COMPONENT16_ARB | 0x81a5
 | GL_DEPTH_COMPONENT24_ARB | 0x81a6
 | GL_DEPTH_COMPONENT32_ARB | 0x81a7
 | GL_TEXTURE_DEPTH_SIZE_ARB | 0x884a
 | GL_DEPTH_TEXTURE_MODE_ARB | 0x884b
GL_ARB_fragment_program 15 definitions, no type definitions, no functions
Definitions
 | GL_FRAGMENT_PROGRAM_ARB | 0x8804
 | GL_PROGRAM_ALU_INSTRUCTIONS_ARB | 0x8805
 | GL_PROGRAM_TEX_INSTRUCTIONS_ARB | 0x8806
 | GL_PROGRAM_TEX_INDIRECTIONS_ARB | 0x8807
 | GL_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB | 0x8808
 | GL_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB | 0x8809
 | GL_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB | 0x880a
 | GL_MAX_PROGRAM_ALU_INSTRUCTIONS_ARB | 0x880b
 | GL_MAX_PROGRAM_TEX_INSTRUCTIONS_ARB | 0x880c
 | GL_MAX_PROGRAM_TEX_INDIRECTIONS_ARB | 0x880d
 | GL_MAX_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB | 0x880e
 | GL_MAX_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB | 0x880f
 | GL_MAX_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB | 0x8810
 | GL_MAX_TEXTURE_COORDS_ARB | 0x8871
 | GL_MAX_TEXTURE_IMAGE_UNITS_ARB | 0x8872
GL_ARB_matrix_palette 10 definitions, no type definitions, 5 functions
Definitions
 | GL_MATRIX_PALETTE_ARB | 0x8840
 | GL_MAX_MATRIX_PALETTE_STACK_DEPTH_ARB | 0x8841
 | GL_MAX_PALETTE_MATRICES_ARB | 0x8842
 | GL_CURRENT_PALETTE_MATRIX_ARB | 0x8843
 | GL_MATRIX_INDEX_ARRAY_ARB | 0x8844
 | GL_CURRENT_MATRIX_INDEX_ARB | 0x8845
 | GL_MATRIX_INDEX_ARRAY_SIZE_ARB | 0x8846
 | GL_MATRIX_INDEX_ARRAY_TYPE_ARB | 0x8847
 | GL_MATRIX_INDEX_ARRAY_STRIDE_ARB | 0x8848
 | GL_MATRIX_INDEX_ARRAY_POINTER_ARB | 0x8849
Functions
 | GLvoid glCurrentPaletteMatrixARB(GLint);
 | GLvoid glMatrixIndexPointerARB(GLint, GLenum, GLsizei, GLvoid const *);
 | GLvoid glMatrixIndexubvARB(GLint, GLubyte const *);
 | GLvoid glMatrixIndexuivARB(GLint, GLuint const *);
 | GLvoid glMatrixIndexusvARB(GLint, GLushort const *);
GL_ARB_multisample 9 definitions, no type definitions, 1 function
Definitions
 | GL_MULTISAMPLE_ARB | 0x809d
 | GL_SAMPLE_ALPHA_TO_COVERAGE_ARB | 0x809e
 | GL_SAMPLE_ALPHA_TO_ONE_ARB | 0x809f
 | GL_SAMPLE_COVERAGE_ARB | 0x80a0
 | GL_SAMPLE_BUFFERS_ARB | 0x80a8
 | GL_SAMPLES_ARB | 0x80a9
 | GL_SAMPLE_COVERAGE_VALUE_ARB | 0x80aa
 | GL_SAMPLE_COVERAGE_INVERT_ARB | 0x80ab
 | GL_MULTISAMPLE_BIT_ARB | 0x20000000
Functions
 | GLvoid glSampleCoverageARB(GLclampf, GLboolean);
GL_ARB_multitexture 35 definitions, no type definitions, 34 functions
Definitions
 | GL_TEXTURE0_ARB | 0x84c0
 | GL_TEXTURE1_ARB | 0x84c1
 | GL_TEXTURE2_ARB | 0x84c2
 | GL_TEXTURE3_ARB | 0x84c3
 | GL_TEXTURE4_ARB | 0x84c4
 | GL_TEXTURE5_ARB | 0x84c5
 | GL_TEXTURE6_ARB | 0x84c6
 | GL_TEXTURE7_ARB | 0x84c7
 | GL_TEXTURE8_ARB | 0x84c8
 | GL_TEXTURE9_ARB | 0x84c9
 | GL_TEXTURE10_ARB | 0x84ca
 | GL_TEXTURE11_ARB | 0x84cb
 | GL_TEXTURE12_ARB | 0x84cc
 | GL_TEXTURE13_ARB | 0x84cd
 | GL_TEXTURE14_ARB | 0x84ce
 | GL_TEXTURE15_ARB | 0x84cf
 | GL_TEXTURE16_ARB | 0x84d0
 | GL_TEXTURE17_ARB | 0x84d1
 | GL_TEXTURE18_ARB | 0x84d2
 | GL_TEXTURE19_ARB | 0x84d3
 | GL_TEXTURE20_ARB | 0x84d4
 | GL_TEXTURE21_ARB | 0x84d5
 | GL_TEXTURE22_ARB | 0x84d6
 | GL_TEXTURE23_ARB | 0x84d7
 | GL_TEXTURE24_ARB | 0x84d8
 | GL_TEXTURE25_ARB | 0x84d9
 | GL_TEXTURE26_ARB | 0x84da
 | GL_TEXTURE27_ARB | 0x84db
 | GL_TEXTURE28_ARB | 0x84dc
 | GL_TEXTURE29_ARB | 0x84dd
 | GL_TEXTURE30_ARB | 0x84de
 | GL_TEXTURE31_ARB | 0x84df
 | GL_ACTIVE_TEXTURE_ARB | 0x84e0
 | GL_CLIENT_ACTIVE_TEXTURE_ARB | 0x84e1
 | GL_MAX_TEXTURE_UNITS_ARB | 0x84e2
Functions
 | GLvoid glActiveTextureARB(GLenum);
 | GLvoid glClientActiveTextureARB(GLenum);
 | GLvoid glMultiTexCoord1dARB(GLenum, GLdouble);
 | GLvoid glMultiTexCoord1dvARB(GLenum, GLdouble const *);
 | GLvoid glMultiTexCoord1fARB(GLenum, GLfloat);
 | GLvoid glMultiTexCoord1fvARB(GLenum, GLfloat const *);
 | GLvoid glMultiTexCoord1iARB(GLenum, GLint);
 | GLvoid glMultiTexCoord1ivARB(GLenum, GLint const *);
 | GLvoid glMultiTexCoord1sARB(GLenum, GLshort);
 | GLvoid glMultiTexCoord1svARB(GLenum, GLshort const *);
 | GLvoid glMultiTexCoord2dARB(GLenum, GLdouble, GLdouble);
 | GLvoid glMultiTexCoord2dvARB(GLenum, GLdouble const *);
 | GLvoid glMultiTexCoord2fARB(GLenum, GLfloat, GLfloat);
 | GLvoid glMultiTexCoord2fvARB(GLenum, GLfloat const *);
 | GLvoid glMultiTexCoord2iARB(GLenum, GLint, GLint);
 | GLvoid glMultiTexCoord2ivARB(GLenum, GLint const *);
 | GLvoid glMultiTexCoord2sARB(GLenum, GLshort, GLshort);
 | GLvoid glMultiTexCoord2svARB(GLenum, GLshort const *);
 | GLvoid glMultiTexCoord3dARB(GLenum, GLdouble, GLdouble, GLdouble);
 | GLvoid glMultiTexCoord3dvARB(GLenum, GLdouble const *);
 | GLvoid glMultiTexCoord3fARB(GLenum, GLfloat, GLfloat, GLfloat);
 | GLvoid glMultiTexCoord3fvARB(GLenum, GLfloat const *);
 | GLvoid glMultiTexCoord3iARB(GLenum, GLint, GLint, GLint);
 | GLvoid glMultiTexCoord3ivARB(GLenum, GLint const *);
 | GLvoid glMultiTexCoord3sARB(GLenum, GLshort, GLshort, GLshort);
 | GLvoid glMultiTexCoord3svARB(GLenum, GLshort const *);
 | GLvoid glMultiTexCoord4dARB(GLenum, GLdouble, GLdouble, GLdouble, GLdouble);
 | GLvoid glMultiTexCoord4dvARB(GLenum, GLdouble const *);
 | GLvoid glMultiTexCoord4fARB(GLenum, GLfloat, GLfloat, GLfloat, GLfloat);
 | GLvoid glMultiTexCoord4fvARB(GLenum, GLfloat const *);
 | GLvoid glMultiTexCoord4iARB(GLenum, GLint, GLint, GLint, GLint);
 | GLvoid glMultiTexCoord4ivARB(GLenum, GLint const *);
 | GLvoid glMultiTexCoord4sARB(GLenum, GLshort, GLshort, GLshort, GLshort);
 | GLvoid glMultiTexCoord4svARB(GLenum, GLshort const *);
GL_ARB_occlusion_query no definitions, no type definitions, 8 functions
Functions
GLvoid glBeginQueryARB(GLenum, GLuint);
GLvoid glDeleteQueriesARB(GLsizei, GLuint const *);
GLvoid glEndQueryARB(GLenum);
GLvoid glGenQueriesARB(GLsizei, GLuint *);
GLvoid glGetQueryivARB(GLenum, GLenum, GLint *);
GLvoid glGetQueryObjectivARB(GLuint, GLenum, GLint *);
GLvoid glGetQueryObjectuivARB(GLuint, GLenum, GLuint *);
GLboolean glIsQueryARB(GLuint);
GL_ARB_point_parameters 4 definitions, no type definitions, 2 functions
Definitions
 | GL_POINT_SIZE_MIN_ARB | 0x8126
 | GL_POINT_SIZE_MAX_ARB | 0x8127
 | GL_POINT_FADE_THRESHOLD_SIZE_ARB | 0x8128
 | GL_POINT_DISTANCE_ATTENUATION_ARB | 0x8129
Functions
 | GLvoid glPointParameterfARB(GLenum, GLfloat);
 | GLvoid glPointParameterfvARB(GLenum, GLfloat const *);
GL_ARB_point_sprite no definitions, no type definitions, no functions
GL_ARB_shader_objects no definitions, 2 type definitions, 39 functions
Type Definitions
 | GLcharARB | char
 | GLhandleARB | void *
Functions
 | GLvoid glAttachObjectARB(GLhandleARB, GLhandleARB);
 | GLvoid glCompileShaderARB(GLhandleARB);
 | GLhandleARB glCreateProgramObjectARB();
 | GLhandleARB glCreateShaderObjectARB(GLenum);
 | GLvoid glDeleteObjectARB(GLhandleARB);
 | GLvoid glDetachObjectARB(GLhandleARB, GLhandleARB);
 | GLvoid glGetActiveUniformARB(GLhandleARB, GLuint, GLsizei, GLsizei *, GLint *, GLenum *, GLcharARB *);
 | GLvoid glGetAttachedObjectsARB(GLhandleARB, GLsizei, GLsizei *, GLhandleARB *);
 | GLhandleARB glGetHandleARB(GLenum);
 | GLvoid glGetInfoLogARB(GLhandleARB, GLsizei, GLsizei *, GLcharARB *);
 | GLvoid glGetObjectParameterfvARB(GLhandleARB, GLenum, GLfloat *);
 | GLvoid glGetObjectParameterivARB(GLhandleARB, GLenum, GLint *);
 | GLvoid glGetShaderSourceARB(GLhandleARB, GLsizei, GLsizei *, GLcharARB *);
 | GLvoid glGetUniformfvARB(GLhandleARB, GLint, GLfloat *);
 | GLvoid glGetUniformivARB(GLhandleARB, GLint, GLint *);
 | GLint glGetUniformLocationARB(GLhandleARB, GLcharARB const);
 | GLvoid glLinkProgramARB(GLhandleARB);
 | GLvoid glShaderSourceARB(GLhandleARB, GLsizei, GLcharARB const, GLint const *);
 | GLvoid glUniform1fARB(GLint, GLfloat);
 | GLvoid glUniform1fvARB(GLint, GLsizei, GLfloat *);
 | GLvoid glUniform1iARB(GLint, GLint);
 | GLvoid glUniform1ivARB(GLint, GLsizei, GLint *);
 | GLvoid glUniform2fARB(GLint, GLfloat, GLfloat);
 | GLvoid glUniform2fvARB(GLint, GLsizei, GLfloat *);
 | GLvoid glUniform2iARB(GLint, GLint, GLint);
 | GLvoid glUniform2ivARB(GLint, GLsizei, GLint *);
 | GLvoid glUniform3fARB(GLint, GLfloat, GLfloat, GLfloat);
 | GLvoid glUniform3fvARB(GLint, GLsizei, GLfloat *);
 | GLvoid glUniform3iARB(GLint, GLint, GLint, GLint);
 | GLvoid glUniform3ivARB(GLint, GLsizei, GLint *);
 | GLvoid glUniform4fARB(GLint, GLfloat, GLfloat, GLfloat, GLfloat);
 | GLvoid glUniform4fvARB(GLint, GLsizei, GLfloat *);
 | GLvoid glUniform4iARB(GLint, GLint, GLint, GLint, GLint);
 | GLvoid glUniform4ivARB(GLint, GLsizei, GLint *);
 | GLvoid glUniformMatrix2fvARB(GLint, GLsizei, GLboolean, GLfloat *);
 | GLvoid glUniformMatrix3fvARB(GLint, GLsizei, GLboolean, GLfloat *);
 | GLvoid glUniformMatrix4fvARB(GLint, GLsizei, GLboolean, GLfloat *);
 | GLvoid glUseProgramObjectARB(GLhandleARB);
 | GLvoid glValidateProgramARB(GLhandleARB);
GL_ARB_shading_language_100 no definitions, no type definitions, no functions
GL_ARB_shadow 3 definitions, no type definitions, no functions
Definitions
 | GL_TEXTURE_COMPARE_MODE_ARB | 0x884c
 | GL_TEXTURE_COMPARE_FUNC_ARB | 0x884d
 | GL_COMPARE_R_TO_TEXTURE_ARB | 0x884e
GL_ARB_shadow_ambient 1 definition, no type definitions, no functions
Definitions
GL_TEXTURE_COMPARE_FAIL_VALUE_ARB
0x80bf
GL_ARB_texture_border_clamp 1 definition, no type definitions, no functions
Definitions
GL_CLAMP_TO_BORDER_ARB
0x812d
GL_ARB_texture_compression 11 definitions, no type definitions, 7 functions
Definitions
 | GL_COMPRESSED_ALPHA_ARB | 0x84e9
 | GL_COMPRESSED_LUMINANCE_ARB | 0x84ea
 | GL_COMPRESSED_LUMINANCE_ALPHA_ARB | 0x84eb
 | GL_COMPRESSED_INTENSITY_ARB | 0x84ec
 | GL_COMPRESSED_RGB_ARB | 0x84ed
 | GL_COMPRESSED_RGBA_ARB | 0x84ee
 | GL_TEXTURE_COMPRESSION_HINT_ARB | 0x84ef
 | GL_TEXTURE_COMPRESSED_IMAGE_SIZE_ARB | 0x86a0
 | GL_TEXTURE_COMPRESSED_ARB | 0x86a1
 | GL_NUM_COMPRESSED_TEXTURE_FORMATS_ARB | 0x86a2
 | GL_COMPRESSED_TEXTURE_FORMATS_ARB | 0x86a3
Functions
 | GLvoid glCompressedTexImage1DARB(GLenum, GLint, GLenum, GLsizei, GLint, GLsizei, GLvoid const *);
 | GLvoid glCompressedTexImage2DARB(GLenum, GLint, GLenum, GLsizei, GLsizei, GLint, GLsizei, GLvoid const *);
 | GLvoid glCompressedTexImage3DARB(GLenum, GLint, GLenum, GLsizei, GLsizei, GLsizei, GLint, GLsizei, GLvoid const *);
 | GLvoid glCompressedTexSubImage1DARB(GLenum, GLint, GLint, GLsizei, GLenum, GLsizei, GLvoid const *);
 | GLvoid glCompressedTexSubImage2DARB(GLenum, GLint, GLint, GLint, GLsizei, GLsizei, GLenum, GLsizei, GLvoid const *);
 | GLvoid glCompressedTexSubImage3DARB(GLenum, GLint, GLint, GLint, GLint, GLsizei, GLsizei, GLsizei, GLenum, GLsizei, GLvoid const *);
 | GLvoid glGetCompressedTexImageARB(GLenum, GLint, GLvoid *);
GL_ARB_texture_cube_map 12 definitions, no type definitions, no functions
Definitions
 | GL_NORMAL_MAP_ARB | 0x8511
 | GL_REFLECTION_MAP_ARB | 0x8512
 | GL_TEXTURE_CUBE_MAP_ARB | 0x8513
 | GL_TEXTURE_BINDING_CUBE_MAP_ARB | 0x8514
 | GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB | 0x8515
 | GL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB | 0x8516
 | GL_TEXTURE_CUBE_MAP_POSITIVE_Y_ARB | 0x8517
 | GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB | 0x8518
 | GL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB | 0x8519
 | GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB | 0x851a
 | GL_PROXY_TEXTURE_CUBE_MAP_ARB | 0x851b
 | GL_MAX_CUBE_MAP_TEXTURE_SIZE_ARB | 0x851c
GL_ARB_texture_env_add no definitions, no type definitions, no functions
GL_ARB_texture_env_combine 22 definitions, no type definitions, no functions
Definitions
 | GL_SUBTRACT_ARB | 0x84e7
 | GL_COMBINE_ARB | 0x8570
 | GL_COMBINE_RGB_ARB | 0x8571
 | GL_COMBINE_ALPHA_ARB | 0x8572
 | GL_RGB_SCALE_ARB | 0x8573
 | GL_ADD_SIGNED_ARB | 0x8574
 | GL_INTERPOLATE_ARB | 0x8575
 | GL_CONSTANT_ARB | 0x8576
 | GL_PRIMARY_COLOR_ARB | 0x8577
 | GL_PREVIOUS_ARB | 0x8578
 | GL_SOURCE0_RGB_ARB | 0x8580
 | GL_SOURCE1_RGB_ARB | 0x8581
 | GL_SOURCE2_RGB_ARB | 0x8582
 | GL_SOURCE0_ALPHA_ARB | 0x8588
 | GL_SOURCE1_ALPHA_ARB | 0x8589
 | GL_SOURCE2_ALPHA_ARB | 0x858a
 | GL_OPERAND0_RGB_ARB | 0x8590
 | GL_OPERAND1_RGB_ARB | 0x8591
 | GL_OPERAND2_RGB_ARB | 0x8592
 | GL_OPERAND0_ALPHA_ARB | 0x8598
 | GL_OPERAND1_ALPHA_ARB | 0x8599
 | GL_OPERAND2_ALPHA_ARB | 0x859a
GL_ARB_texture_env_crossbar no definitions, no type definitions, no functions
GL_ARB_texture_env_dot3 2 definitions, no type definitions, no functions
Definitions
 | GL_DOT3_RGB_ARB | 0x86ae
 | GL_DOT3_RGBA_ARB | 0x86af
GL_ARB_texture_mirror_repeat no definitions, no type definitions, no functions
GL_ARB_texture_non_power_of_two no definitions, no type definitions, no functions
GL_ARB_transpose_matrix 4 definitions, no type definitions, 4 functions
Definitions
 | GL_TRANSPOSE_MODELVIEW_MATRIX_ARB | 0x84e3
 | GL_TRANSPOSE_PROJECTION_MATRIX_ARB | 0x84e4
 | GL_TRANSPOSE_TEXTURE_MATRIX_ARB | 0x84e5
 | GL_TRANSPOSE_COLOR_MATRIX_ARB | 0x84e6
Functions
 | GLvoid glLoadTransposeMatrixdARB(GLdouble const *);
 | GLvoid glLoadTransposeMatrixfARB(GLfloat const *);
 | GLvoid glMultTransposeMatrixdARB(GLdouble const *);
 | GLvoid glMultTransposeMatrixfARB(GLfloat const *);
GL_ARB_vertex_blend 42 definitions, no type definitions, 10 functions
Definitions
 | GL_MODELVIEW0_ARB | 0x1700
 | GL_MODELVIEW1_ARB | 0x850a
 | GL_MAX_VERTEX_UNITS_ARB | 0x86a4
 | GL_ACTIVE_VERTEX_UNITS_ARB | 0x86a5
 | GL_WEIGHT_SUM_UNITY_ARB | 0x86a6
 | GL_VERTEX_BLEND_ARB | 0x86a7
 | GL_CURRENT_WEIGHT_ARB | 0x86a8
 | GL_WEIGHT_ARRAY_TYPE_ARB | 0x86a9
 | GL_WEIGHT_ARRAY_STRIDE_ARB | 0x86aa
 | GL_WEIGHT_ARRAY_SIZE_ARB | 0x86ab
 | GL_WEIGHT_ARRAY_POINTER_ARB | 0x86ac
 | GL_WEIGHT_ARRAY_ARB | 0x86ad
 | GL_MODELVIEW2_ARB | 0x8722
 | GL_MODELVIEW3_ARB | 0x8723
 | GL_MODELVIEW4_ARB | 0x8724
 | GL_MODELVIEW5_ARB | 0x8725
 | GL_MODELVIEW6_ARB | 0x8726
 | GL_MODELVIEW7_ARB | 0x8727
 | GL_MODELVIEW8_ARB | 0x8728
 | GL_MODELVIEW9_ARB | 0x8729
 | GL_MODELVIEW10_ARB | 0x872a
 | GL_MODELVIEW11_ARB | 0x872b
 | GL_MODELVIEW12_ARB | 0x872c
 | GL_MODELVIEW13_ARB | 0x872d
 | GL_MODELVIEW14_ARB | 0x872e
 | GL_MODELVIEW15_ARB | 0x872f
 | GL_MODELVIEW16_ARB | 0x8730
 | GL_MODELVIEW17_ARB | 0x8731
 | GL_MODELVIEW18_ARB | 0x8732
 | GL_MODELVIEW19_ARB | 0x8733
 | GL_MODELVIEW20_ARB | 0x8734
 | GL_MODELVIEW21_ARB | 0x8735
 | GL_MODELVIEW22_ARB | 0x8736
 | GL_MODELVIEW23_ARB | 0x8737
 | GL_MODELVIEW24_ARB | 0x8738
 | GL_MODELVIEW25_ARB | 0x8739
 | GL_MODELVIEW26_ARB | 0x873a
 | GL_MODELVIEW27_ARB | 0x873b
 | GL_MODELVIEW28_ARB | 0x873c
 | GL_MODELVIEW29_ARB | 0x873d
 | GL_MODELVIEW30_ARB | 0x873e
 | GL_MODELVIEW31_ARB | 0x873f
Functions
 | GLvoid glVertexBlendARB(GLint);
 | GLvoid glWeightbvARB(GLint, GLbyte const *);
 | GLvoid glWeightdvARB(GLint, GLdouble const *);
 | GLvoid glWeightfvARB(GLint, GLfloat const *);
 | GLvoid glWeightivARB(GLint, GLint const *);
 | GLvoid glWeightPointerARB(GLint, GLenum, GLsizei, GLvoid const *);
 | GLvoid glWeightsvARB(GLint, GLshort const *);
 | GLvoid glWeightubvARB(GLint, GLubyte const *);
 | GLvoid glWeightuivARB(GLint, GLuint const *);
 | GLvoid glWeightusvARB(GLint, GLushort const *);
GL_ARB_vertex_buffer_object 31 definitions, 2 type definitions, 11 functions
Definitions
 | GL_BUFFER_SIZE_ARB | 0x8764
 | GL_BUFFER_USAGE_ARB | 0x8765
 | GL_ARRAY_BUFFER_ARB | 0x8892
 | GL_ELEMENT_ARRAY_BUFFER_ARB | 0x8893
 | GL_ARRAY_BUFFER_BINDING_ARB | 0x8894
 | GL_ELEMENT_ARRAY_BUFFER_BINDING_ARB | 0x8895
 | GL_VERTEX_ARRAY_BUFFER_BINDING_ARB | 0x8896
 | GL_NORMAL_ARRAY_BUFFER_BINDING_ARB | 0x8897
 | GL_COLOR_ARRAY_BUFFER_BINDING_ARB | 0x8898
 | GL_INDEX_ARRAY_BUFFER_BINDING_ARB | 0x8899
 | GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING_ARB | 0x889a
 | GL_EDGE_FLAG_ARRAY_BUFFER_BINDING_ARB | 0x889b
 | GL_SECONDARY_COLOR_ARRAY_BUFFER_BINDING_ARB | 0x889c
 | GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING_ARB | 0x889d
 | GL_WEIGHT_ARRAY_BUFFER_BINDING_ARB | 0x889e
 | GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING_ARB | 0x889f
 | GL_READ_ONLY_ARB | 0x88b8
 | GL_WRITE_ONLY_ARB | 0x88b9
 | GL_READ_WRITE_ARB | 0x88ba
 | GL_BUFFER_ACCESS_ARB | 0x88bb
 | GL_BUFFER_MAPPED_ARB | 0x88bc
 | GL_BUFFER_MAP_POINTER_ARB | 0x88bd
 | GL_STREAM_DRAW_ARB | 0x88e0
 | GL_STREAM_READ_ARB | 0x88e1
 | GL_STREAM_COPY_ARB | 0x88e2
 | GL_STATIC_DRAW_ARB | 0x88e4
 | GL_STATIC_READ_ARB | 0x88e5
 | GL_STATIC_COPY_ARB | 0x88e6
 | GL_DYNAMIC_DRAW_ARB | 0x88e8
 | GL_DYNAMIC_READ_ARB | 0x88e9
 | GL_DYNAMIC_COPY_ARB | 0x88ea
Type Definitions
 | GLintptrARB | ptrdiff_t
 | GLsizeiptrARB | ptrdiff_t
Functions
 | GLvoid glBindBufferARB(GLenum, GLuint);
 | GLvoid glBufferDataARB(GLenum, GLsizeiptrARB, GLvoid const *, GLenum);
 | GLvoid glBufferSubDataARB(GLenum, GLintptrARB, GLsizeiptrARB, GLvoid const *);
 | GLvoid glDeleteBuffersARB(GLsizei, GLuint const *);
 | GLvoid glGenBuffersARB(GLsizei, GLuint *);
 | GLvoid glGetBufferParameterivARB(GLenum, GLenum, GLint *);
 | GLvoid glGetBufferPointervARB(GLenum, GLenum, GLvoid * *);
 | GLvoid glGetBufferSubDataARB(GLenum, GLintptrARB, GLsizeiptrARB, GLvoid *);
 | GLboolean glIsBufferARB(GLuint);
 | GLvoid * glMapBufferARB(GLenum, GLenum);
 | GLboolean glUnmapBufferARB(GLenum);
GL_ARB_vertex_program 79 definitions, no type definitions, 62 functions
Definitions
 | GL_COLOR_SUM_ARB | 0x8458
 | GL_VERTEX_PROGRAM_ARB | 0x8620
 | GL_VERTEX_ATTRIB_ARRAY_ENABLED_ARB | 0x8622
 | GL_VERTEX_ATTRIB_ARRAY_SIZE_ARB | 0x8623
 | GL_VERTEX_ATTRIB_ARRAY_STRIDE_ARB | 0x8624
 | GL_VERTEX_ATTRIB_ARRAY_TYPE_ARB | 0x8625
 | GL_CURRENT_VERTEX_ATTRIB_ARB | 0x8626
 | GL_PROGRAM_LENGTH_ARB | 0x8627
 | GL_PROGRAM_STRING_ARB | 0x8628
 | GL_MAX_PROGRAM_MATRIX_STACK_DEPTH_ARB | 0x862e
 | GL_MAX_PROGRAM_MATRICES_ARB | 0x862f
 | GL_CURRENT_MATRIX_STACK_DEPTH_ARB | 0x8640
 | GL_CURRENT_MATRIX_ARB | 0x8641
 | GL_VERTEX_PROGRAM_POINT_SIZE_ARB | 0x8642
 | GL_VERTEX_PROGRAM_TWO_SIDE_ARB | 0x8643
 | GL_VERTEX_ATTRIB_ARRAY_POINTER_ARB | 0x8645
 | GL_PROGRAM_ERROR_POSITION_ARB | 0x864b
 | GL_PROGRAM_BINDING_ARB | 0x8677
 | GL_MAX_VERTEX_ATTRIBS_ARB | 0x8869
 | GL_VERTEX_ATTRIB_ARRAY_NORMALIZED_ARB | 0x886a
 | GL_PROGRAM_ERROR_STRING_ARB | 0x8874
 | GL_PROGRAM_FORMAT_ASCII_ARB | 0x8875
 | GL_PROGRAM_FORMAT_ARB | 0x8876
 | GL_PROGRAM_INSTRUCTIONS_ARB | 0x88a0
 | GL_MAX_PROGRAM_INSTRUCTIONS_ARB | 0x88a1
 | GL_PROGRAM_NATIVE_INSTRUCTIONS_ARB | 0x88a2
 | GL_MAX_PROGRAM_NATIVE_INSTRUCTIONS_ARB | 0x88a3
 | GL_PROGRAM_TEMPORARIES_ARB | 0x88a4
 | GL_MAX_PROGRAM_TEMPORARIES_ARB | 0x88a5
 | GL_PROGRAM_NATIVE_TEMPORARIES_ARB | 0x88a6
 | GL_MAX_PROGRAM_NATIVE_TEMPORARIES_ARB | 0x88a7
 | GL_PROGRAM_PARAMETERS_ARB | 0x88a8
 | GL_MAX_PROGRAM_PARAMETERS_ARB | 0x88a9
 | GL_PROGRAM_NATIVE_PARAMETERS_ARB | 0x88aa
 | GL_MAX_PROGRAM_NATIVE_PARAMETERS_ARB | 0x88ab
 | GL_PROGRAM_ATTRIBS_ARB | 0x88ac
 | GL_MAX_PROGRAM_ATTRIBS_ARB | 0x88ad
 | GL_PROGRAM_NATIVE_ATTRIBS_ARB | 0x88ae
 | GL_MAX_PROGRAM_NATIVE_ATTRIBS_ARB | 0x88af
 | GL_PROGRAM_ADDRESS_REGISTERS_ARB | 0x88b0
 | GL_MAX_PROGRAM_ADDRESS_REGISTERS_ARB | 0x88b1
 | GL_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB | 0x88b2
 | GL_MAX_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB | 0x88b3
 | GL_MAX_PROGRAM_LOCAL_PARAMETERS_ARB | 0x88b4
 | GL_MAX_PROGRAM_ENV_PARAMETERS_ARB | 0x88b5
 | GL_PROGRAM_UNDER_NATIVE_LIMITS_ARB | 0x88b6
 | GL_TRANSPOSE_CURRENT_MATRIX_ARB | 0x88b7
 | GL_MATRIX0_ARB | 0x88c0
 | GL_MATRIX1_ARB | 0x88c1
 | GL_MATRIX2_ARB | 0x88c2
 | GL_MATRIX3_ARB | 0x88c3
 | GL_MATRIX4_ARB | 0x88c4
 | GL_MATRIX5_ARB | 0x88c5
 | GL_MATRIX6_ARB | 0x88c6
 | GL_MATRIX7_ARB | 0x88c7
 | GL_MATRIX8_ARB | 0x88c8
 | GL_MATRIX9_ARB | 0x88c9
 | GL_MATRIX10_ARB | 0x88ca
 | GL_MATRIX11_ARB | 0x88cb
 | GL_MATRIX12_ARB | 0x88cc
 | GL_MATRIX13_ARB | 0x88cd
 | GL_MATRIX14_ARB | 0x88ce
 | GL_MATRIX15_ARB | 0x88cf
 | GL_MATRIX16_ARB | 0x88d0
 | GL_MATRIX17_ARB | 0x88d1
 | GL_MATRIX18_ARB | 0x88d2
 | GL_MATRIX19_ARB | 0x88d3
 | GL_MATRIX20_ARB | 0x88d4
 | GL_MATRIX21_ARB | 0x88d5
 | GL_MATRIX22_ARB | 0x88d6
 | GL_MATRIX23_ARB | 0x88d7
 | GL_MATRIX24_ARB | 0x88d8
 | GL_MATRIX25_ARB | 0x88d9
 | GL_MATRIX26_ARB | 0x88da
 | GL_MATRIX27_ARB | 0x88db
 | GL_MATRIX28_ARB | 0x88dc
 | GL_MATRIX29_ARB | 0x88dd
 | GL_MATRIX30_ARB | 0x88de
 | GL_MATRIX31_ARB | 0x88df
Functions
 | GLvoid glBindProgramARB(GLenum, GLuint);
 | GLvoid glDeleteProgramsARB(GLsizei, GLuint const *);
 | GLvoid glDisableVertexAttribArrayARB(GLuint);
 | GLvoid glEnableVertexAttribArrayARB(GLuint);
 | GLvoid glGenProgramsARB(GLsizei, GLuint *);
 | GLvoid glGetProgramEnvParameterdvARB(GLenum, GLuint, GLdouble *);
 | GLvoid glGetProgramEnvParameterfvARB(GLenum, GLuint, GLfloat *);
 | GLvoid glGetProgramivARB(GLenum, GLenum, GLint *);
 | GLvoid glGetProgramLocalParameterdvARB(GLenum, GLuint, GLdouble *);
 | GLvoid glGetProgramLocalParameterfvARB(GLenum, GLuint, GLfloat *);
 | GLvoid glGetProgramStringARB(GLenum, GLenum, GLvoid *);
 | GLvoid glGetVertexAttribdvARB(GLuint, GLenum, GLdouble *);
 | GLvoid glGetVertexAttribfvARB(GLuint, GLenum, GLfloat *);
 | GLvoid glGetVertexAttribivARB(GLuint, GLenum, GLint *);
 | GLvoid glGetVertexAttribPointervARB(GLuint, GLenum, GLvoid * *);
 | GLboolean glIsProgramARB(GLuint);
 | GLvoid glProgramEnvParameter4dARB(GLenum, GLuint, GLdouble, GLdouble, GLdouble, GLdouble);
 | GLvoid glProgramEnvParameter4dvARB(GLenum, GLuint, GLdouble const *);
 | GLvoid glProgramEnvParameter4fARB(GLenum, GLuint, GLfloat, GLfloat, GLfloat, GLfloat);
 | GLvoid glProgramEnvParameter4fvARB(GLenum, GLuint, GLfloat const *);
 | GLvoid glProgramLocalParameter4dARB(GLenum, GLuint, GLdouble, GLdouble, GLdouble, GLdouble);
 | GLvoid glProgramLocalParameter4dvARB(GLenum, GLuint, GLdouble const *);
 | GLvoid glProgramLocalParameter4fARB(GLenum, GLuint, GLfloat, GLfloat, GLfloat, GLfloat);
 | GLvoid glProgramLocalParameter4fvARB(GLenum, GLuint, GLfloat const *);
 | GLvoid glProgramStringARB(GLenum, GLenum, GLsizei, GLvoid const *);
 | GLvoid glVertexAttrib1dARB(GLuint, GLdouble);
 | GLvoid glVertexAttrib1dvARB(GLuint, GLdouble const *);
 | GLvoid glVertexAttrib1fARB(GLuint, GLfloat);
 | GLvoid glVertexAttrib1fvARB(GLuint, GLfloat const *);
 | GLvoid glVertexAttrib1sARB(GLuint, GLshort);
 | GLvoid glVertexAttrib1svARB(GLuint, GLshort const *);
 | GLvoid glVertexAttrib2dARB(GLuint, GLdouble, GLdouble);
 | GLvoid glVertexAttrib2dvARB(GLuint, GLdouble const *);
 | GLvoid glVertexAttrib2fARB(GLuint, GLfloat, GLfloat);
 | GLvoid glVertexAttrib2fvARB(GLuint, GLfloat const *);
 | GLvoid glVertexAttrib2sARB(GLuint, GLshort, GLshort);
 | GLvoid glVertexAttrib2svARB(GLuint, GLshort const *);
 | GLvoid glVertexAttrib3dARB(GLuint, GLdouble, GLdouble, GLdouble);
 | GLvoid glVertexAttrib3dvARB(GLuint, GLdouble const *);
 | GLvoid glVertexAttrib3fARB(GLuint, GLfloat, GLfloat, GLfloat);
 | GLvoid glVertexAttrib3fvARB(GLuint, GLfloat const *);
 | GLvoid glVertexAttrib3sARB(GLuint, GLshort, GLshort, GLshort);
 | GLvoid glVertexAttrib3svARB(GLuint, GLshort const *);
 | GLvoid glVertexAttrib4bvARB(GLuint, GLbyte const *);
 | GLvoid glVertexAttrib4dARB(GLuint, GLdouble, GLdouble, GLdouble, GLdouble);
 | GLvoid glVertexAttrib4dvARB(GLuint, GLdouble const *);
 | GLvoid glVertexAttrib4fARB(GLuint, GLfloat, GLfloat, GLfloat, GLfloat);
 | GLvoid glVertexAttrib4fvARB(GLuint, GLfloat const *);
 | GLvoid glVertexAttrib4ivARB(GLuint, GLint const *);
 | GLvoid glVertexAttrib4NbvARB(GLuint, GLbyte const *);
 | GLvoid glVertexAttrib4NivARB(GLuint, GLint const *);
 | GLvoid glVertexAttrib4NsvARB(GLuint, GLshort const *);
 | GLvoid glVertexAttrib4NubARB(GLuint, GLubyte, GLubyte, GLubyte, GLubyte);
 | GLvoid glVertexAttrib4NubvARB(GLuint, GLubyte const *);
 | GLvoid glVertexAttrib4NuivARB(GLuint, GLuint const *);
 | GLvoid glVertexAttrib4NusvARB(GLuint, GLushort const *);
 | GLvoid glVertexAttrib4sARB(GLuint, GLshort, GLshort, GLshort, GLshort);
 | GLvoid glVertexAttrib4svARB(GLuint, GLshort const *);
 | GLvoid glVertexAttrib4ubvARB(GLuint, GLubyte const *);
 | GLvoid glVertexAttrib4uivARB(GLuint, GLuint const *);
 | GLvoid glVertexAttrib4usvARB(GLuint, GLushort const *);
 | GLvoid glVertexAttribPointerARB(GLuint, GLint, GLenum, GLboolean, GLsizei, GLvoid const *);
GL_ARB_window_pos no definitions, no type definitions, 16 functions
Functions
GLvoid glWindowPos2dARB(GLdouble, GLdouble);
GLvoid glWindowPos2dvARB(GLdouble const *);
GLvoid glWindowPos2fARB(GLfloat, GLfloat);
GLvoid glWindowPos2fvARB(GLfloat const *);
GLvoid glWindowPos2iARB(GLint, GLint);
GLvoid glWindowPos2ivARB(GLint const *);
GLvoid glWindowPos2sARB(GLshort, GLshort);
GLvoid glWindowPos2svARB(GLshort const *);
GLvoid glWindowPos3dARB(GLdouble, GLdouble, GLdouble);
GLvoid glWindowPos3dvARB(GLdouble const *);
GLvoid glWindowPos3fARB(GLfloat, GLfloat, GLfloat);
GLvoid glWindowPos3fvARB(GLfloat const *);
GLvoid glWindowPos3iARB(GLint, GLint, GLint);
GLvoid glWindowPos3ivARB(GLint const *);
GLvoid glWindowPos3sARB(GLshort, GLshort, GLshort);
GLvoid glWindowPos3svARB(GLshort const *);
GL_ATI_draw_buffers 17 definitions, no type definitions, 1 function
Definitions
 | GL_MAX_DRAW_BUFFERS_ATI | 0x8824
 | GL_DRAW_BUFFER0_ATI | 0x8825
 | GL_DRAW_BUFFER1_ATI | 0x8826
 | GL_DRAW_BUFFER2_ATI | 0x8827
 | GL_DRAW_BUFFER3_ATI | 0x8828
 | GL_DRAW_BUFFER4_ATI | 0x8829
 | GL_DRAW_BUFFER5_ATI | 0x882a
 | GL_DRAW_BUFFER6_ATI | 0x882b
 | GL_DRAW_BUFFER7_ATI | 0x882c
 | GL_DRAW_BUFFER8_ATI | 0x882d
 | GL_DRAW_BUFFER9_ATI | 0x882e
 | GL_DRAW_BUFFER10_ATI | 0x882f
 | GL_DRAW_BUFFER11_ATI | 0x8830
 | GL_DRAW_BUFFER12_ATI | 0x8831
 | GL_DRAW_BUFFER13_ATI | 0x8832
 | GL_DRAW_BUFFER14_ATI | 0x8833
 | GL_DRAW_BUFFER15_ATI | 0x8834
Functions
 | GLvoid glDrawBuffersATI(GLsizei, GLenum const *);
GL_ATI_element_array 3 definitions, no type definitions, 3 functions
Definitions
 | GL_ELEMENT_ARRAY_ATI | 0x8768
 | GL_ELEMENT_ARRAY_TYPE_ATI | 0x8769
 | GL_ELEMENT_ARRAY_POINTER_ATI | 0x876a
Functions
 | GLvoid glDrawElementArrayATI(GLenum, GLsizei);
 | GLvoid glDrawRangeElementArrayATI(GLenum, GLuint, GLuint, GLsizei);
 | GLvoid glElementPointerATI(GLenum, GLvoid const *);
GL_ATI_envmap_bumpmap 8 definitions, no type definitions, 4 functions
Definitions
 | GL_BUMP_ROT_MATRIX_ATI | 0x8775
 | GL_BUMP_ROT_MATRIX_SIZE_ATI | 0x8776
 | GL_BUMP_NUM_TEX_UNITS_ATI | 0x8777
 | GL_BUMP_TEX_UNITS_ATI | 0x8778
 | GL_DUDV_ATI | 0x8779
 | GL_DU8DV8_ATI | 0x877a
 | GL_BUMP_ENVMAP_ATI | 0x877b
 | GL_BUMP_TARGET_ATI | 0x877c
Functions
 | GLvoid glGetTexBumpParameterfvATI(GLenum, GLfloat *);
 | GLvoid glGetTexBumpParameterivATI(GLenum, GLint *);
 | GLvoid glTexBumpParameterfvATI(GLenum, GLfloat const *);
 | GLvoid glTexBumpParameterivATI(GLenum, GLint const *);
GL_ATI_fragment_shader 104 definitions, no type definitions, 14 functions
Definitions
 | GL_RED_BIT_ATI | 0x1
 | GL_2X_BIT_ATI | 0x1
 | GL_GREEN_BIT_ATI | 0x2
 | GL_4X_BIT_ATI | 0x2
 | GL_COMP_BIT_ATI | 0x2
 | GL_BLUE_BIT_ATI | 0x4
 | GL_8X_BIT_ATI | 0x4
 | GL_NEGATE_BIT_ATI | 0x4
 | GL_HALF_BIT_ATI | 0x8
 | GL_BIAS_BIT_ATI | 0x8
 | GL_QUARTER_BIT_ATI | 0x10
 | GL_EIGHTH_BIT_ATI | 0x20
 | GL_SATURATE_BIT_ATI | 0x40
 | GL_FRAGMENT_SHADER_ATI | 0x8920
 | GL_REG_0_ATI | 0x8921
 | GL_REG_1_ATI | 0x8922
 | GL_REG_2_ATI | 0x8923
 | GL_REG_3_ATI | 0x8924
 | GL_REG_4_ATI | 0x8925
 | GL_REG_5_ATI | 0x8926
 | GL_REG_6_ATI | 0x8927
 | GL_REG_7_ATI | 0x8928
 | GL_REG_8_ATI | 0x8929
 | GL_REG_9_ATI | 0x892a
 | GL_REG_10_ATI | 0x892b
 | GL_REG_11_ATI | 0x892c
 | GL_REG_12_ATI | 0x892d
 | GL_REG_13_ATI | 0x892e
 | GL_REG_14_ATI | 0x892f
 | GL_REG_15_ATI | 0x8930
 | GL_REG_16_ATI | 0x8931
 | GL_REG_17_ATI | 0x8932
 | GL_REG_18_ATI | 0x8933
 | GL_REG_19_ATI | 0x8934
 | GL_REG_20_ATI | 0x8935
 | GL_REG_21_ATI | 0x8936
 | GL_REG_22_ATI | 0x8937
 | GL_REG_23_ATI | 0x8938
 | GL_REG_24_ATI | 0x8939
 | GL_REG_25_ATI | 0x893a
 | GL_REG_26_ATI | 0x893b
 | GL_REG_27_ATI | 0x893c
 | GL_REG_28_ATI | 0x893d
 | GL_REG_29_ATI | 0x893e
 | GL_REG_30_ATI | 0x893f
 | GL_REG_31_ATI | 0x8940
 | GL_CON_0_ATI | 0x8941
 | GL_CON_1_ATI | 0x8942
 | GL_CON_2_ATI | 0x8943
 | GL_CON_3_ATI | 0x8944
 | GL_CON_4_ATI | 0x8945
 | GL_CON_5_ATI | 0x8946
 | GL_CON_6_ATI | 0x8947
 | GL_CON_7_ATI | 0x8948
 | GL_CON_8_ATI | 0x8949
 | GL_CON_9_ATI | 0x894a
 | GL_CON_10_ATI | 0x894b
 | GL_CON_11_ATI | 0x894c
 | GL_CON_12_ATI | 0x894d
 | GL_CON_13_ATI | 0x894e
 | GL_CON_14_ATI | 0x894f
 | GL_CON_15_ATI | 0x8950
 | GL_CON_16_ATI | 0x8951
 | GL_CON_17_ATI | 0x8952
 | GL_CON_18_ATI | 0x8953
 | GL_CON_19_ATI | 0x8954
 | GL_CON_20_ATI | 0x8955
 | GL_CON_21_ATI | 0x8956
 | GL_CON_22_ATI | 0x8957
 | GL_CON_23_ATI | 0x8958
 | GL_CON_24_ATI | 0x8959
 | GL_CON_25_ATI | 0x895a
 | GL_CON_26_ATI | 0x895b
 | GL_CON_27_ATI | 0x895c
 | GL_CON_28_ATI | 0x895d
 | GL_CON_29_ATI | 0x895e
 | GL_CON_30_ATI | 0x895f
 | GL_CON_31_ATI | 0x8960
 | GL_MOV_ATI | 0x8961
 | GL_ADD_ATI | 0x8963
 | GL_MUL_ATI | 0x8964
 | GL_SUB_ATI | 0x8965
 | GL_DOT3_ATI | 0x8966
 | GL_DOT4_ATI | 0x8967
 | GL_MAD_ATI | 0x8968
 | GL_LERP_ATI | 0x8969
 | GL_CND_ATI | 0x896a
 | GL_CND0_ATI | 0x896b
 | GL_DOT2_ADD_ATI | 0x896c
 | GL_SECONDARY_INTERPOLATOR_ATI | 0x896d
 | GL_NUM_FRAGMENT_REGISTERS_ATI | 0x896e
 | GL_NUM_FRAGMENT_CONSTANTS_ATI | 0x896f
 | GL_NUM_PASSES_ATI | 0x8970
 | GL_NUM_INSTRUCTIONS_PER_PASS_ATI | 0x8971
 | GL_NUM_INSTRUCTIONS_TOTAL_ATI | 0x8972
 | GL_NUM_INPUT_INTERPOLATOR_COMPONENTS_ATI | 0x8973
 | GL_NUM_LOOPBACK_COMPONENTS_ATI | 0x8974
 | GL_COLOR_ALPHA_PAIRING_ATI | 0x8975
 | GL_SWIZZLE_STR_ATI | 0x8976
 | GL_SWIZZLE_STQ_ATI | 0x8977
 | GL_SWIZZLE_STR_DR_ATI | 0x8978
 | GL_SWIZZLE_STQ_DQ_ATI | 0x8979
 | GL_SWIZZLE_STRQ_ATI | 0x897a
 | GL_SWIZZLE_STRQ_DQ_ATI | 0x897b
Functions
 | GLvoid glAlphaFragmentOp1ATI(GLenum, GLuint, GLuint, GLuint, GLuint, GLuint);
 | GLvoid glAlphaFragmentOp2ATI(GLenum, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint);
 | GLvoid glAlphaFragmentOp3ATI(GLenum, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint);
 | GLvoid glBeginFragmentShaderATI();
 | GLvoid glBindFragmentShaderATI(GLuint);
 | GLvoid glColorFragmentOp1ATI(GLenum, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint);
 | GLvoid glColorFragmentOp2ATI(GLenum, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint);
 | GLvoid glColorFragmentOp3ATI(GLenum, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint);
 | GLvoid glDeleteFragmentShaderATI(GLuint);
 | GLvoid glEndFragmentShaderATI();
 | GLuint glGenFragmentShadersATI(GLuint);
 | GLvoid glPassTexCoordATI(GLuint, GLuint, GLenum);
 | GLvoid glSampleMapATI(GLuint, GLuint, GLenum);
 | GLvoid glSetFragmentShaderConstantATI(GLuint, GLfloat const *);
GL_ATI_map_object_buffer no definitions, no type definitions, 2 functions
Functions
GLvoid * glMapObjectBufferATI(GLuint);
GLvoid glUnmapObjectBufferATI(GLuint);
GL_ATI_pn_triangles 9 definitions, no type definitions, 2 functions
Definitions
 | GL_PN_TRIANGLES_ATI | 0x87f0
 | GL_MAX_PN_TRIANGLES_TESSELATION_LEVEL_ATI | 0x87f1
 | GL_PN_TRIANGLES_POINT_MODE_ATI | 0x87f2
 | GL_PN_TRIANGLES_NORMAL_MODE_ATI | 0x87f3
 | GL_PN_TRIANGLES_TESSELATION_LEVEL_ATI | 0x87f4
 | GL_PN_TRIANGLES_POINT_MODE_LINEAR_ATI | 0x87f5
 | GL_PN_TRIANGLES_POINT_MODE_CUBIC_ATI | 0x87f6
 | GL_PN_TRIANGLES_NORMAL_MODE_LINEAR_ATI | 0x87f7
 | GL_PN_TRIANGLES_NORMAL_MODE_QUADRATIC_ATI | 0x87f8
Functions
 | GLvoid glPNTrianglesfATI(GLenum, GLfloat);
 | GLvoid glPNTrianglesiATI(GLenum, GLint);
GL_ATI_separate_stencil 4 definitions, no type definitions, 2 functions
Definitions
 | GL_STENCIL_BACK_FUNC_ATI | 0x8800
 | GL_STENCIL_BACK_FAIL_ATI | 0x8801
 | GL_STENCIL_BACK_PASS_DEPTH_FAIL_ATI | 0x8802
 | GL_STENCIL_BACK_PASS_DEPTH_PASS_ATI | 0x8803
Functions
 | GLvoid glStencilFuncSeparateATI(GLenum, GLenum, GLint, GLuint);
 | GLvoid glStencilOpSeparateATI(GLenum, GLenum, GLenum, GLenum);
GL_ATI_texture_env_combine3 3 definitions, no type definitions, no functions
Definitions
 | GL_MODULATE_ADD_ATI | 0x8744
 | GL_MODULATE_SIGNED_ADD_ATI | 0x8745
 | GL_MODULATE_SUBTRACT_ATI | 0x8746
GL_ATI_texture_float 12 definitions, no type definitions, no functions
Definitions
 | GL_RGBA_FLOAT32_ATI | 0x8814
 | GL_RGB_FLOAT32_ATI | 0x8815
 | GL_ALPHA_FLOAT32_ATI | 0x8816
 | GL_INTENSITY_FLOAT32_ATI | 0x8817
 | GL_LUMINANCE_FLOAT32_ATI | 0x8818
 | GL_LUMINANCE_ALPHA_FLOAT32_ATI | 0x8819
 | GL_RGBA_FLOAT16_ATI | 0x881a
 | GL_RGB_FLOAT16_ATI | 0x881b
 | GL_ALPHA_FLOAT16_ATI | 0x881c
 | GL_INTENSITY_FLOAT16_ATI | 0x881d
 | GL_LUMINANCE_FLOAT16_ATI | 0x881e
 | GL_LUMINANCE_ALPHA_FLOAT16_ATI | 0x881f
GL_ATI_texture_mirror_once 2 definitions, no type definitions, no functions
Definitions
 | GL_MIRROR_CLAMP_ATI | 0x8742
 | GL_MIRROR_CLAMP_TO_EDGE_ATI | 0x8743
GL_ATI_text_fragment_shader 1 definition, no type definitions, no functions
Definitions
GL_TEXT_FRAGMENT_SHADER_ATI
0x8200
GL_ATI_vertex_array_object 8 definitions, no type definitions, 12 functions
Definitions
 | GL_STATIC_ATI | 0x8760
 | GL_DYNAMIC_ATI | 0x8761
 | GL_PRESERVE_ATI | 0x8762
 | GL_DISCARD_ATI | 0x8763
 | GL_OBJECT_BUFFER_SIZE_ATI | 0x8764
 | GL_OBJECT_BUFFER_USAGE_ATI | 0x8765
 | GL_ARRAY_OBJECT_BUFFER_ATI | 0x8766
 | GL_ARRAY_OBJECT_OFFSET_ATI | 0x8767
Functions
 | GLvoid glArrayObjectATI(GLenum, GLint, GLenum, GLsizei, GLuint, GLuint);
 | GLvoid glFreeObjectBufferATI(GLuint);
 | GLvoid glGetArrayObjectfvATI(GLenum, GLenum, GLfloat *);
 | GLvoid glGetArrayObjectivATI(GLenum, GLenum, GLint *);
 | GLvoid glGetObjectBufferfvATI(GLuint, GLenum, GLfloat *);
 | GLvoid glGetObjectBufferivATI(GLuint, GLenum, GLint *);
 | GLvoid glGetVariantArrayObjectfvATI(GLuint, GLenum, GLfloat *);
 | GLvoid glGetVariantArrayObjectivATI(GLuint, GLenum, GLint *);
 | GLboolean glIsObjectBufferATI(GLuint);
 | GLuint glNewObjectBufferATI(GLsizei, GLvoid const *, GLenum);
 | GLvoid glUpdateObjectBufferATI(GLuint, GLuint, GLsizei, GLvoid const *, GLenum);
 | GLvoid glVariantArrayObjectATI(GLuint, GLenum, GLsizei, GLuint, GLuint);
GL_ATI_vertex_attrib_array_object no definitions, no type definitions, 3 functions
Functions
GLvoid glGetVertexAttribArrayObjectfvATI(GLuint, GLenum, GLfloat *);
GLvoid glGetVertexAttribArrayObjectivATI(GLuint, GLenum, GLint *);
GLvoid glVertexAttribArrayObjectATI(GLuint, GLint, GLenum, GLboolean, GLsizei, GLuint, GLuint);
GL_ATI_vertex_streams 10 definitions, no type definitions, 45 functions
Definitions
 | GL_MAX_VERTEX_STREAMS_ATI | 0x876b
 | GL_VERTEX_STREAM0_ATI | 0x876c
 | GL_VERTEX_STREAM1_ATI | 0x876d
 | GL_VERTEX_STREAM2_ATI | 0x876e
 | GL_VERTEX_STREAM3_ATI | 0x876f
 | GL_VERTEX_STREAM4_ATI | 0x8770
 | GL_VERTEX_STREAM5_ATI | 0x8771
 | GL_VERTEX_STREAM6_ATI | 0x8772
 | GL_VERTEX_STREAM7_ATI | 0x8773
 | GL_VERTEX_SOURCE_ATI | 0x8774
Functions
 | GLvoid glClientActiveVertexStreamATI(GLenum);
 | GLvoid glNormalStream3bATI(GLenum, GLbyte, GLbyte, GLbyte);
 | GLvoid glNormalStream3bvATI(GLenum, GLbyte const *);
 | GLvoid glNormalStream3dATI(GLenum, GLdouble, GLdouble, GLdouble);
 | GLvoid glNormalStream3dvATI(GLenum, GLdouble const *);
 | GLvoid glNormalStream3fATI(GLenum, GLfloat, GLfloat, GLfloat);
 | GLvoid glNormalStream3fvATI(GLenum, GLfloat const *);
 | GLvoid glNormalStream3iATI(GLenum, GLint, GLint, GLint);
 | GLvoid glNormalStream3ivATI(GLenum, GLint const *);
 | GLvoid glNormalStream3sATI(GLenum, GLshort, GLshort, GLshort);
 | GLvoid glNormalStream3svATI(GLenum, GLshort const *);
 | GLvoid glVertexBlendEnvfATI(GLenum, GLfloat);
 | GLvoid glVertexBlendEnviATI(GLenum, GLint);
 | GLvoid glVertexStream1dATI(GLenum, GLdouble);
 | GLvoid glVertexStream1dvATI(GLenum, GLdouble const *);
 | GLvoid glVertexStream1fATI(GLenum, GLfloat);
 | GLvoid glVertexStream1fvATI(GLenum, GLfloat const *);
 | GLvoid glVertexStream1iATI(GLenum, GLint);
 | GLvoid glVertexStream1ivATI(GLenum, GLint const *);
 | GLvoid glVertexStream1sATI(GLenum, GLshort);
 | GLvoid glVertexStream1svATI(GLenum, GLshort const *);
 | GLvoid glVertexStream2dATI(GLenum, GLdouble, GLdouble);
 | GLvoid glVertexStream2dvATI(GLenum, GLdouble const *);
 | GLvoid glVertexStream2fATI(GLenum, GLfloat, GLfloat);
 | GLvoid glVertexStream2fvATI(GLenum, GLfloat const *);
 | GLvoid glVertexStream2iATI(GLenum, GLint, GLint);
 | GLvoid glVertexStream2ivATI(GLenum, GLint const *);
 | GLvoid glVertexStream2sATI(GLenum, GLshort, GLshort);
 | GLvoid glVertexStream2svATI(GLenum, GLshort const *);
 | GLvoid glVertexStream3dATI(GLenum, GLdouble, GLdouble, GLdouble);
 | GLvoid glVertexStream3dvATI(GLenum, GLdouble const *);
 | GLvoid glVertexStream3fATI(GLenum, GLfloat, GLfloat, GLfloat);
 | GLvoid glVertexStream3fvATI(GLenum, GLfloat const *);
 | GLvoid glVertexStream3iATI(GLenum, GLint, GLint, GLint);
 | GLvoid glVertexStream3ivATI(GLenum, GLint const *);
 | GLvoid glVertexStream3sATI(GLenum, GLshort, GLshort, GLshort);
 | GLvoid glVertexStream3svATI(GLenum, GLshort const *);
 | GLvoid glVertexStream4dATI(GLenum, GLdouble, GLdouble, GLdouble, GLdouble);
 | GLvoid glVertexStream4dvATI(GLenum, GLdouble const *);
 | GLvoid glVertexStream4fATI(GLenum, GLfloat, GLfloat, GLfloat, GLfloat);
 | GLvoid glVertexStream4fvATI(GLenum, GLfloat const *);
 | GLvoid glVertexStream4iATI(GLenum, GLint, GLint, GLint, GLint);
 | GLvoid glVertexStream4ivATI(GLenum, GLint const *);
 | GLvoid glVertexStream4sATI(GLenum, GLshort, GLshort, GLshort, GLshort);
 | GLvoid glVertexStream4svATI(GLenum, GLshort const *);
GL_EXT_422_pixels 4 definitions, no type definitions, no functions
Definitions
 | GL_422_EXT | 0x80cc
 | GL_422_REV_EXT | 0x80cd
 | GL_422_AVERAGE_EXT | 0x80ce
 | GL_422_REV_AVERAGE_EXT | 0x80cf
GL_EXT_abgr 1 definition, no type definitions, no functions
Definitions
GL_ABGR_EXT
0x8000
GL_EXT_bgra 2 definitions, no type definitions, no functions
Definitions
 | GL_BGR_EXT | 0x80e0
 | GL_BGRA_EXT | 0x80e1
GL_EXT_blend_color 5 definitions, no type definitions, 1 function
Definitions
 | GL_CONSTANT_COLOR_EXT | 0x8001
 | GL_ONE_MINUS_CONSTANT_COLOR_EXT | 0x8002
 | GL_CONSTANT_ALPHA_EXT | 0x8003
 | GL_ONE_MINUS_CONSTANT_ALPHA_EXT | 0x8004
 | GL_BLEND_COLOR_EXT | 0x8005
Functions
 | GLvoid glBlendColorEXT(GLclampf, GLclampf, GLclampf, GLclampf);
GL_EXT_blend_func_separate 4 definitions, no type definitions, 1 function
Definitions
 | GL_BLEND_DST_RGB_EXT | 0x80c8
 | GL_BLEND_SRC_RGB_EXT | 0x80c9
 | GL_BLEND_DST_ALPHA_EXT | 0x80ca
 | GL_BLEND_SRC_ALPHA_EXT | 0x80cb
Functions
 | GLvoid glBlendFuncSeparateEXT(GLenum, GLenum, GLenum, GLenum);
GL_EXT_blend_logic_op no definitions, no type definitions, no functions
GL_EXT_blend_minmax 4 definitions, no type definitions, 1 function
Definitions
 | GL_FUNC_ADD_EXT | 0x8006
 | GL_MIN_EXT | 0x8007
 | GL_MAX_EXT | 0x8008
 | GL_BLEND_EQUATION_EXT | 0x8009
Functions
 | GLvoid glBlendEquationEXT(GLenum);
GL_EXT_blend_subtract 2 definitions, no type definitions, no functions
Definitions
 | GL_FUNC_SUBTRACT_EXT | 0x800a
 | GL_FUNC_REVERSE_SUBTRACT_EXT | 0x800b
GL_EXT_clip_volume_hint 1 definition, no type definitions, no functions
Definitions
GL_CLIP_VOLUME_CLIPPING_HINT_EXT
0x80f0
GL_EXT_cmyka 4 definitions, no type definitions, no functions
Definitions
 | GL_CMYK_EXT | 0x800c
 | GL_CMYKA_EXT | 0x800d
 | GL_PACK_CMYK_HINT_EXT | 0x800e
 | GL_UNPACK_CMYK_HINT_EXT | 0x800f
GL_EXT_color_matrix no definitions, no type definitions, no functions
GL_EXT_color_subtable no definitions, no type definitions, 2 functions
Functions
GLvoid glColorSubTableEXT(GLenum, GLsizei, GLsizei, GLenum, GLenum, GLvoid const *);
GLvoid glCopyColorSubTableEXT(GLenum, GLsizei, GLint, GLint, GLsizei);
GL_EXT_compiled_vertex_array 2 definitions, no type definitions, 2 functions
Definitions
 | GL_ARRAY_ELEMENT_LOCK_FIRST_EXT | 0x81a8
 | GL_ARRAY_ELEMENT_LOCK_COUNT_EXT | 0x81a9
Functions
 | GLvoid glLockArraysEXT(GLint, GLsizei);
 | GLvoid glUnlockArraysEXT();
GL_EXT_convolution 20 definitions, no type definitions, 13 functions
Definitions
 | GL_CONVOLUTION_1D_EXT | 0x8010
 | GL_CONVOLUTION_2D_EXT | 0x8011
 | GL_SEPARABLE_2D_EXT | 0x8012
 | GL_CONVOLUTION_BORDER_MODE_EXT | 0x8013
 | GL_CONVOLUTION_FILTER_SCALE_EXT | 0x8014
 | GL_CONVOLUTION_FILTER_BIAS_EXT | 0x8015
 | GL_REDUCE_EXT | 0x8016
 | GL_CONVOLUTION_FORMAT_EXT | 0x8017
 | GL_CONVOLUTION_WIDTH_EXT | 0x8018
 | GL_CONVOLUTION_HEIGHT_EXT | 0x8019
 | GL_MAX_CONVOLUTION_WIDTH_EXT | 0x801a
 | GL_MAX_CONVOLUTION_HEIGHT_EXT | 0x801b
 | GL_POST_CONVOLUTION_RED_SCALE_EXT | 0x801c
 | GL_POST_CONVOLUTION_GREEN_SCALE_EXT | 0x801d
 | GL_POST_CONVOLUTION_BLUE_SCALE_EXT | 0x801e
 | GL_POST_CONVOLUTION_ALPHA_SCALE_EXT | 0x801f
 | GL_POST_CONVOLUTION_RED_BIAS_EXT | 0x8020
 | GL_POST_CONVOLUTION_GREEN_BIAS_EXT | 0x8021
 | GL_POST_CONVOLUTION_BLUE_BIAS_EXT | 0x8022
 | GL_POST_CONVOLUTION_ALPHA_BIAS_EXT | 0x8023
Functions
 | GLvoid glConvolutionFilter1DEXT(GLenum, GLenum, GLsizei, GLenum, GLenum, GLvoid const *);
 | GLvoid glConvolutionFilter2DEXT(GLenum, GLenum, GLsizei, GLsizei, GLenum, GLenum, GLvoid const *);
 | GLvoid glConvolutionParameterfEXT(GLenum, GLenum, GLfloat);
 | GLvoid glConvolutionParameterfvEXT(GLenum, GLenum, GLfloat const *);
 | GLvoid glConvolutionParameteriEXT(GLenum, GLenum, GLint);
 | GLvoid glConvolutionParameterivEXT(GLenum, GLenum, GLint const *);
 | GLvoid glCopyConvolutionFilter1DEXT(GLenum, GLenum, GLint, GLint, GLsizei);
 | GLvoid glCopyConvolutionFilter2DEXT(GLenum, GLenum, GLint, GLint, GLsizei, GLsizei);
 | GLvoid glGetConvolutionFilterEXT(GLenum, GLenum, GLenum, GLvoid *);
 | GLvoid glGetConvolutionParameterfvEXT(GLenum, GLenum, GLfloat *);
 | GLvoid glGetConvolutionParameterivEXT(GLenum, GLenum, GLint *);
 | GLvoid glGetSeparableFilterEXT(GLenum, GLenum, GLenum, GLvoid *, GLvoid *, GLvoid *);
 | GLvoid glSeparableFilter2DEXT(GLenum, GLenum, GLsizei, GLsizei, GLenum, GLenum, GLvoid const *, GLvoid const *);
GL_EXT_coordinate_frame 14 definitions, no type definitions, 22 functions
Definitions
 | GL_TANGENT_ARRAY_EXT | 0x8439
 | GL_BINORMAL_ARRAY_EXT | 0x843a
 | GL_CURRENT_TANGENT_EXT | 0x843b
 | GL_CURRENT_BINORMAL_EXT | 0x843c
 | GL_TANGENT_ARRAY_TYPE_EXT | 0x843e
 | GL_TANGENT_ARRAY_STRIDE_EXT | 0x843f
 | GL_BINORMAL_ARRAY_TYPE_EXT | 0x8440
 | GL_BINORMAL_ARRAY_STRIDE_EXT | 0x8441
 | GL_TANGENT_ARRAY_POINTER_EXT | 0x8442
 | GL_BINORMAL_ARRAY_POINTER_EXT | 0x8443
 | GL_MAP1_TANGENT_EXT | 0x8444
 | GL_MAP2_TANGENT_EXT | 0x8445
 | GL_MAP1_BINORMAL_EXT | 0x8446
 | GL_MAP2_BINORMAL_EXT | 0x8447
Functions
 | GLvoid glBinormal3bEXT(GLbyte, GLbyte, GLbyte);
 | GLvoid glBinormal3bvEXT(GLbyte const *);
 | GLvoid glBinormal3dEXT(GLdouble, GLdouble, GLdouble);
 | GLvoid glBinormal3dvEXT(GLdouble const *);
 | GLvoid glBinormal3fEXT(GLfloat, GLfloat, GLfloat);
 | GLvoid glBinormal3fvEXT(GLfloat const *);
 | GLvoid glBinormal3iEXT(GLint, GLint, GLint);
 | GLvoid glBinormal3ivEXT(GLint const *);
 | GLvoid glBinormal3sEXT(GLshort, GLshort, GLshort);
 | GLvoid glBinormal3svEXT(GLshort const *);
 | GLvoid glBinormalPointerEXT(GLenum, GLsizei, GLvoid const *);
 | GLvoid glTangent3bEXT(GLbyte, GLbyte, GLbyte);
 | GLvoid glTangent3bvEXT(GLbyte const *);
 | GLvoid glTangent3dEXT(GLdouble, GLdouble, GLdouble);
 | GLvoid glTangent3dvEXT(GLdouble const *);
 | GLvoid glTangent3fEXT(GLfloat, GLfloat, GLfloat);
 | GLvoid glTangent3fvEXT(GLfloat const *);
 | GLvoid glTangent3iEXT(GLint, GLint, GLint);
 | GLvoid glTangent3ivEXT(GLint const *);
 | GLvoid glTangent3sEXT(GLshort, GLshort, GLshort);
 | GLvoid glTangent3svEXT(GLshort const *);
 | GLvoid glTangentPointerEXT(GLenum, GLsizei, GLvoid const *);
GL_EXT_copy_texture no definitions, no type definitions, 5 functions
Functions
GLvoid glCopyTexImage1DEXT(GLenum, GLint, GLenum, GLint, GLint, GLsizei, GLint);
GLvoid glCopyTexImage2DEXT(GLenum, GLint, GLenum, GLint, GLint, GLsizei, GLsizei, GLint);
GLvoid glCopyTexSubImage1DEXT(GLenum, GLint, GLint, GLint, GLint, GLsizei);
GLvoid glCopyTexSubImage2DEXT(GLenum, GLint, GLint, GLint, GLint, GLint, GLsizei, GLsizei);
GLvoid glCopyTexSubImage3DEXT(GLenum, GLint, GLint, GLint, GLint, GLint, GLint, GLsizei, GLsizei);
GL_EXT_cull_vertex 3 definitions, no type definitions, 2 functions
Definitions
 | GL_CULL_VERTEX_EXT | 0x81aa
 | GL_CULL_VERTEX_EYE_POSITION_EXT | 0x81ab
 | GL_CULL_VERTEX_OBJECT_POSITION_EXT | 0x81ac
Functions
 | GLvoid glCullParameterdvEXT(GLenum, GLdouble *);
 | GLvoid glCullParameterfvEXT(GLenum, GLfloat *);
GL_EXT_depth_bounds_test no definitions, no type definitions, no functions
GL_EXT_draw_range_elements 2 definitions, no type definitions, 1 function
Definitions
 | GL_MAX_ELEMENTS_VERTICES_EXT | 0x80e8
 | GL_MAX_ELEMENTS_INDICES_EXT | 0x80e9
Functions
 | GLvoid glDrawRangeElementsEXT(GLenum, GLuint, GLuint, GLsizei, GLenum, GLvoid const *);
GL_EXT_fog_coord 8 definitions, no type definitions, 5 functions
Definitions
 | GL_FOG_COORDINATE_SOURCE_EXT | 0x8450
 | GL_FOG_COORDINATE_EXT | 0x8451
 | GL_FRAGMENT_DEPTH_EXT | 0x8452
 | GL_CURRENT_FOG_COORDINATE_EXT | 0x8453
 | GL_FOG_COORDINATE_ARRAY_TYPE_EXT | 0x8454
 | GL_FOG_COORDINATE_ARRAY_STRIDE_EXT | 0x8455
 | GL_FOG_COORDINATE_ARRAY_POINTER_EXT | 0x8456
 | GL_FOG_COORDINATE_ARRAY_EXT | 0x8457
Functions
 | GLvoid glFogCoorddEXT(GLdouble);
 | GLvoid glFogCoorddvEXT(GLdouble const *);
 | GLvoid glFogCoordfEXT(GLfloat);
 | GLvoid glFogCoordfvEXT(GLfloat const *);
 | GLvoid glFogCoordPointerEXT(GLenum, GLsizei, GLvoid const *);
GL_EXT_fragment_lighting no definitions, no type definitions, no functions
GL_EXT_histogram 14 definitions, no type definitions, 10 functions
Definitions
 | GL_HISTOGRAM_EXT | 0x8024
 | GL_PROXY_HISTOGRAM_EXT | 0x8025
 | GL_HISTOGRAM_WIDTH_EXT | 0x8026
 | GL_HISTOGRAM_FORMAT_EXT | 0x8027
 | GL_HISTOGRAM_RED_SIZE_EXT | 0x8028
 | GL_HISTOGRAM_GREEN_SIZE_EXT | 0x8029
 | GL_HISTOGRAM_BLUE_SIZE_EXT | 0x802a
 | GL_HISTOGRAM_ALPHA_SIZE_EXT | 0x802b
 | GL_HISTOGRAM_LUMINANCE_SIZE_EXT | 0x802c
 | GL_HISTOGRAM_SINK_EXT | 0x802d
 | GL_MINMAX_EXT | 0x802e
 | GL_MINMAX_FORMAT_EXT | 0x802f
 | GL_MINMAX_SINK_EXT | 0x8030
 | GL_TABLE_TOO_LARGE_EXT | 0x8031
Functions
 | GLvoid glGetHistogramEXT(GLenum, GLboolean, GLenum, GLenum, GLvoid *);
 | GLvoid glGetHistogramParameterfvEXT(GLenum, GLenum, GLfloat *);
 | GLvoid glGetHistogramParameterivEXT(GLenum, GLenum, GLint *);
 | GLvoid glGetMinmaxEXT(GLenum, GLboolean, GLenum, GLenum, GLvoid *);
 | GLvoid glGetMinmaxParameterfvEXT(GLenum, GLenum, GLfloat *);
 | GLvoid glGetMinmaxParameterivEXT(GLenum, GLenum, GLint *);
 | GLvoid glHistogramEXT(GLenum, GLsizei, GLenum, GLboolean);
 | GLvoid glMinmaxEXT(GLenum, GLenum, GLboolean);
 | GLvoid glResetHistogramEXT(GLenum);
 | GLvoid glResetMinmaxEXT(GLenum);
GL_EXT_index_array_formats 8 definitions, no type definitions, no functions
Definitions
 | GL_IUI_V2F_EXT | 0x81ad
 | GL_IUI_V3F_EXT | 0x81ae
 | GL_IUI_N3F_V2F_EXT | 0x81af
 | GL_IUI_N3F_V3F_EXT | 0x81b0
 | GL_T2F_IUI_V2F_EXT | 0x81b1
 | GL_T2F_IUI_V3F_EXT | 0x81b2
 | GL_T2F_IUI_N3F_V2F_EXT | 0x81b3
 | GL_T2F_IUI_N3F_V3F_EXT | 0x81b4
GL_EXT_index_func 3 definitions, no type definitions, 1 function
Definitions
 | GL_INDEX_TEST_EXT | 0x81b5
 | GL_INDEX_TEST_FUNC_EXT | 0x81b6
 | GL_INDEX_TEST_REF_EXT | 0x81b7
Functions
 | GLvoid glIndexFuncEXT(GLenum, GLclampf);
GL_EXT_index_material 3 definitions, no type definitions, 1 function
Definitions
 | GL_INDEX_MATERIAL_EXT | 0x81b8
 | GL_INDEX_MATERIAL_PARAMETER_EXT | 0x81b9
 | GL_INDEX_MATERIAL_FACE_EXT | 0x81ba
Functions
 | GLvoid glIndexMaterialEXT(GLenum, GLenum);
GL_EXT_index_texture no definitions, no type definitions, no functions
GL_EXT_light_texture 9 definitions, no type definitions, 3 functions
Definitions
 | GL_FRAGMENT_MATERIAL_EXT | 0x8349
 | GL_FRAGMENT_NORMAL_EXT | 0x834a
 | GL_FRAGMENT_COLOR_EXT | 0x834c
 | GL_ATTENUATION_EXT | 0x834d
 | GL_SHADOW_ATTENUATION_EXT | 0x834e
 | GL_TEXTURE_APPLICATION_MODE_EXT | 0x834f
 | GL_TEXTURE_LIGHT_EXT | 0x8350
 | GL_TEXTURE_MATERIAL_FACE_EXT | 0x8351
 | GL_TEXTURE_MATERIAL_PARAMETER_EXT | 0x8352
Functions
 | GLvoid glApplyTextureEXT(GLenum);
 | GLvoid glTextureLightEXT(GLenum);
 | GLvoid glTextureMaterialEXT(GLenum, GLenum);
GL_EXT_misc_attribute no definitions, no type definitions, no functions
GL_EXT_multisample 17 definitions, no type definitions, 2 functions
Definitions
 | GL_MULTISAMPLE_EXT | 0x809d
 | GL_SAMPLE_ALPHA_TO_MASK_EXT | 0x809e
 | GL_SAMPLE_ALPHA_TO_ONE_EXT | 0x809f
 | GL_SAMPLE_MASK_EXT | 0x80a0
 | GL_1PASS_EXT | 0x80a1
 | GL_2PASS_0_EXT | 0x80a2
 | GL_2PASS_1_EXT | 0x80a3
 | GL_4PASS_0_EXT | 0x80a4
 | GL_4PASS_1_EXT | 0x80a5
 | GL_4PASS_2_EXT | 0x80a6
 | GL_4PASS_3_EXT | 0x80a7
 | GL_SAMPLE_BUFFERS_EXT | 0x80a8
 | GL_SAMPLES_EXT | 0x80a9
 | GL_SAMPLE_MASK_VALUE_EXT | 0x80aa
 | GL_SAMPLE_MASK_INVERT_EXT | 0x80ab
 | GL_SAMPLE_PATTERN_EXT | 0x80ac
 | GL_MULTISAMPLE_BIT_EXT | 0x20000000
Functions
 | GLvoid glSampleMaskEXT(GLclampf, GLboolean);
 | GLvoid glSamplePatternEXT(GLenum);
GL_EXT_multitexture 10 definitions, no type definitions, 36 functions
Definitions
 | GL_SELECTED_TEXTURE_EXT | 0x83c0
 | GL_SELECTED_TEXTURE_COORD_SET_EXT | 0x83c1
 | GL_SELECTED_TEXTURE_TRANSFORM_EXT | 0x83c2
 | GL_MAX_TEXTURES_EXT | 0x83c3
 | GL_MAX_TEXTURE_COORD_SETS_EXT | 0x83c4
 | GL_TEXTURE_ENV_COORD_SET_EXT | 0x83c5
 | GL_TEXTURE0_EXT | 0x83c6
 | GL_TEXTURE1_EXT | 0x83c7
 | GL_TEXTURE2_EXT | 0x83c8
 | GL_TEXTURE3_EXT | 0x83c9
Functions
 | GLvoid glInterleavedTextureCoordSetsEXT(GLint);
 | GLvoid glMultiTexCoord1dEXT(GLenum, GLdouble);
 | GLvoid glMultiTexCoord1dvEXT(GLenum, GLdouble const *);
 | GLvoid glMultiTexCoord1fEXT(GLenum, GLfloat);
 | GLvoid glMultiTexCoord1fvEXT(GLenum, GLfloat const *);
 | GLvoid glMultiTexCoord1iEXT(GLenum, GLint);
 | GLvoid glMultiTexCoord1ivEXT(GLenum, GLint const *);
 | GLvoid glMultiTexCoord1sEXT(GLenum, GLshort);
 | GLvoid glMultiTexCoord1svEXT(GLenum, GLshort const *);
 | GLvoid glMultiTexCoord2dEXT(GLenum, GLdouble, GLdouble);
 | GLvoid glMultiTexCoord2dvEXT(GLenum, GLdouble const *);
 | GLvoid glMultiTexCoord2fEXT(GLenum, GLfloat, GLfloat);
 | GLvoid glMultiTexCoord2fvEXT(GLenum, GLfloat const *);
 | GLvoid glMultiTexCoord2iEXT(GLenum, GLint, GLint);
 | GLvoid glMultiTexCoord2ivEXT(GLenum, GLint const *);
 | GLvoid glMultiTexCoord2sEXT(GLenum, GLshort, GLshort);
 | GLvoid glMultiTexCoord2svEXT(GLenum, GLshort const *);
 | GLvoid glMultiTexCoord3dEXT(GLenum, GLdouble, GLdouble, GLdouble);
 | GLvoid glMultiTexCoord3dvEXT(GLenum, GLdouble const *);
 | GLvoid glMultiTexCoord3fEXT(GLenum, GLfloat, GLfloat, GLfloat);
 | GLvoid glMultiTexCoord3fvEXT(GLenum, GLfloat const *);
 | GLvoid glMultiTexCoord3iEXT(GLenum, GLint, GLint, GLint);
 | GLvoid glMultiTexCoord3ivEXT(GLenum, GLint const *);
 | GLvoid glMultiTexCoord3sEXT(GLenum, GLshort, GLshort, GLshort);
 | GLvoid glMultiTexCoord3svEXT(GLenum, GLshort const *);
 | GLvoid glMultiTexCoord4dEXT(GLenum, GLdouble, GLdouble, GLdouble, GLdouble);
 | GLvoid glMultiTexCoord4dvEXT(GLenum, GLdouble const *);
 | GLvoid glMultiTexCoord4fEXT(GLenum, GLfloat, GLfloat, GLfloat, GLfloat);
 | GLvoid glMultiTexCoord4fvEXT(GLenum, GLfloat const *);
 | GLvoid glMultiTexCoord4iEXT(GLenum, GLint, GLint, GLint, GLint);
 | GLvoid glMultiTexCoord4ivEXT(GLenum, GLint const *);
 | GLvoid glMultiTexCoord4sEXT(GLenum, GLshort, GLshort, GLshort, GLshort);
 | GLvoid glMultiTexCoord4svEXT(GLenum, GLshort const *);
 | GLvoid glSelectTextureCoordSetEXT(GLenum);
 | GLvoid glSelectTextureEXT(GLenum);
 | GLvoid glSelectTextureTransformEXT(GLenum);
GL_EXT_multi_draw_arrays no definitions, no type definitions, 2 functions
Functions
GLvoid glMultiDrawArraysEXT(GLenum, GLint *, GLsizei *, GLsizei);
GLvoid glMultiDrawElementsEXT(GLenum, GLsizei const *, GLenum, GLvoid const * *, GLsizei);
GL_EXT_packed_pixels 5 definitions, no type definitions, no functions
Definitions
 | GL_UNSIGNED_BYTE_3_3_2_EXT | 0x8032
 | GL_UNSIGNED_SHORT_4_4_4_4_EXT | 0x8033
 | GL_UNSIGNED_SHORT_5_5_5_1_EXT | 0x8034
 | GL_UNSIGNED_INT_8_8_8_8_EXT | 0x8035
 | GL_UNSIGNED_INT_10_10_10_2_EXT | 0x8036
GL_EXT_paletted_texture 7 definitions, no type definitions, 4 functions
Definitions
 | GL_COLOR_INDEX1_EXT | 0x80e2
 | GL_COLOR_INDEX2_EXT | 0x80e3
 | GL_COLOR_INDEX4_EXT | 0x80e4
 | GL_COLOR_INDEX8_EXT | 0x80e5
 | GL_COLOR_INDEX12_EXT | 0x80e6
 | GL_COLOR_INDEX16_EXT | 0x80e7
 | GL_TEXTURE_INDEX_SIZE_EXT | 0x80ed
Functions
 | GLvoid glColorTableEXT(GLenum, GLenum, GLsizei, GLenum, GLenum, GLvoid const *);
 | GLvoid glGetColorTableEXT(GLenum, GLenum, GLenum, GLvoid *);
 | GLvoid glGetColorTableParameterfvEXT(GLenum, GLenum, GLfloat *);
 | GLvoid glGetColorTableParameterivEXT(GLenum, GLenum, GLint *);
GL_EXT_pixel_transform 9 definitions, no type definitions, 4 functions
Definitions
 | GL_PIXEL_TRANSFORM_2D_EXT | 0x8330
 | GL_PIXEL_MAG_FILTER_EXT | 0x8331
 | GL_PIXEL_MIN_FILTER_EXT | 0x8332
 | GL_PIXEL_CUBIC_WEIGHT_EXT | 0x8333
 | GL_CUBIC_EXT | 0x8334
 | GL_AVERAGE_EXT | 0x8335
 | GL_PIXEL_TRANSFORM_2D_STACK_DEPTH_EXT | 0x8336
 | GL_MAX_PIXEL_TRANSFORM_2D_STACK_DEPTH_EXT | 0x8337
 | GL_PIXEL_TRANSFORM_2D_MATRIX_EXT | 0x8338
Functions
 | GLvoid glPixelTransformParameterfEXT(GLenum, GLenum, GLfloat);
 | GLvoid glPixelTransformParameterfvEXT(GLenum, GLenum, GLfloat const *);
 | GLvoid glPixelTransformParameteriEXT(GLenum, GLenum, GLint);
 | GLvoid glPixelTransformParameterivEXT(GLenum, GLenum, GLint const *);
GL_EXT_pixel_transform_color_table no definitions, no type definitions, no functions
GL_EXT_point_parameters 4 definitions, no type definitions, 2 functions
Definitions
 | GL_POINT_SIZE_MIN_EXT | 0x8126
 | GL_POINT_SIZE_MAX_EXT | 0x8127
 | GL_POINT_FADE_THRESHOLD_SIZE_EXT | 0x8128
 | GL_DISTANCE_ATTENUATION_EXT | 0x8129
Functions
 | GLvoid glPointParameterfEXT(GLenum, GLfloat);
 | GLvoid glPointParameterfvEXT(GLenum, GLfloat const *);
GL_EXT_polygon_offset 3 definitions, no type definitions, 1 function
Definitions
 | GL_POLYGON_OFFSET_EXT | 0x8037
 | GL_POLYGON_OFFSET_FACTOR_EXT | 0x8038
 | GL_POLYGON_OFFSET_BIAS_EXT | 0x8039
Functions
 | GLvoid glPolygonOffsetEXT(GLfloat, GLfloat);
GL_EXT_rescale_normal 1 definition, no type definitions, no functions
Definitions
GL_RESCALE_NORMAL_EXT
0x803a
GL_EXT_secondary_color 7 definitions, no type definitions, 17 functions
Definitions
 | GL_COLOR_SUM_EXT | 0x8458
 | GL_CURRENT_SECONDARY_COLOR_EXT | 0x8459
 | GL_SECONDARY_COLOR_ARRAY_SIZE_EXT | 0x845a
 | GL_SECONDARY_COLOR_ARRAY_TYPE_EXT | 0x845b
 | GL_SECONDARY_COLOR_ARRAY_STRIDE_EXT | 0x845c
 | GL_SECONDARY_COLOR_ARRAY_POINTER_EXT | 0x845d
 | GL_SECONDARY_COLOR_ARRAY_EXT | 0x845e
Functions
 | GLvoid glSecondaryColor3bEXT(GLbyte, GLbyte, GLbyte);
 | GLvoid glSecondaryColor3bvEXT(GLbyte const *);
 | GLvoid glSecondaryColor3dEXT(GLdouble, GLdouble, GLdouble);
 | GLvoid glSecondaryColor3dvEXT(GLdouble const *);
 | GLvoid glSecondaryColor3fEXT(GLfloat, GLfloat, GLfloat);
 | GLvoid glSecondaryColor3fvEXT(GLfloat const *);
 | GLvoid glSecondaryColor3iEXT(GLint, GLint, GLint);
 | GLvoid glSecondaryColor3ivEXT(GLint const *);
 | GLvoid glSecondaryColor3sEXT(GLshort, GLshort, GLshort);
 | GLvoid glSecondaryColor3svEXT(GLshort const *);
 | GLvoid glSecondaryColor3ubEXT(GLubyte, GLubyte, GLubyte);
 | GLvoid glSecondaryColor3ubvEXT(GLubyte const *);
 | GLvoid glSecondaryColor3uiEXT(GLuint, GLuint, GLuint);
 | GLvoid glSecondaryColor3uivEXT(GLuint const *);
 | GLvoid glSecondaryColor3usEXT(GLushort, GLushort, GLushort);
 | GLvoid glSecondaryColor3usvEXT(GLushort const *);
 | GLvoid glSecondaryColorPointerEXT(GLint, GLenum, GLsizei, GLvoid const *);
GL_EXT_separate_specular_color 3 definitions, no type definitions, no functions
Definitions
 | GL_LIGHT_MODEL_COLOR_CONTROL_EXT | 0x81f8
 | GL_SINGLE_COLOR_EXT | 0x81f9
 | GL_SEPARATE_SPECULAR_COLOR_EXT | 0x81fa
GL_EXT_shadow_funcs no definitions, no type definitions, no functions
GL_EXT_shared_texture_palette 1 definition, no type definitions, no functions
Definitions
GL_SHARED_TEXTURE_PALETTE_EXT
0x81fb
GL_EXT_stencil_two_side 2 definitions, no type definitions, 1 function
Definitions
 | GL_STENCIL_TEST_TWO_SIDE_EXT | 0x8910
 | GL_ACTIVE_STENCIL_FACE_EXT | 0x8911
Functions
 | GLvoid glActiveStencilFaceEXT(GLenum);
GL_EXT_stencil_wrap 2 definitions, no type definitions, no functions
Definitions
 | GL_INCR_WRAP_EXT | 0x8507
 | GL_DECR_WRAP_EXT | 0x8508
GL_EXT_subtexture no definitions, no type definitions, 2 functions
Functions
GLvoid glTexSubImage1DEXT(GLenum, GLint, GLint, GLsizei, GLenum, GLenum, GLvoid const *);
GLvoid glTexSubImage2DEXT(GLenum, GLint, GLint, GLint, GLsizei, GLsizei, GLenum, GLenum, GLvoid const *);
GL_EXT_texture 43 definitions, no type definitions, no functions
Definitions
 | GL_ALPHA4_EXT | 0x803b
 | GL_ALPHA8_EXT | 0x803c
 | GL_ALPHA12_EXT | 0x803d
 | GL_ALPHA16_EXT | 0x803e
 | GL_LUMINANCE4_EXT | 0x803f
 | GL_LUMINANCE8_EXT | 0x8040
 | GL_LUMINANCE12_EXT | 0x8041
 | GL_LUMINANCE16_EXT | 0x8042
 | GL_LUMINANCE4_ALPHA4_EXT | 0x8043
 | GL_LUMINANCE6_ALPHA2_EXT | 0x8044
 | GL_LUMINANCE8_ALPHA8_EXT | 0x8045
 | GL_LUMINANCE12_ALPHA4_EXT | 0x8046
 | GL_LUMINANCE12_ALPHA12_EXT | 0x8047
 | GL_LUMINANCE16_ALPHA16_EXT | 0x8048
 | GL_INTENSITY_EXT | 0x8049
 | GL_INTENSITY4_EXT | 0x804a
 | GL_INTENSITY8_EXT | 0x804b
 | GL_INTENSITY12_EXT | 0x804c
 | GL_INTENSITY16_EXT | 0x804d
 | GL_RGB2_EXT | 0x804e
 | GL_RGB4_EXT | 0x804f
 | GL_RGB5_EXT | 0x8050
 | GL_RGB8_EXT | 0x8051
 | GL_RGB10_EXT | 0x8052
 | GL_RGB12_EXT | 0x8053
 | GL_RGB16_EXT | 0x8054
 | GL_RGBA2_EXT | 0x8055
 | GL_RGBA4_EXT | 0x8056
 | GL_RGB5_A1_EXT | 0x8057
 | GL_RGBA8_EXT | 0x8058
 | GL_RGB10_A2_EXT | 0x8059
 | GL_RGBA12_EXT | 0x805a
 | GL_RGBA16_EXT | 0x805b
 | GL_TEXTURE_RED_SIZE_EXT | 0x805c
 | GL_TEXTURE_GREEN_SIZE_EXT | 0x805d
 | GL_TEXTURE_BLUE_SIZE_EXT | 0x805e
 | GL_TEXTURE_ALPHA_SIZE_EXT | 0x805f
 | GL_TEXTURE_LUMINANCE_SIZE_EXT | 0x8060
 | GL_TEXTURE_INTENSITY_SIZE_EXT | 0x8061
 | GL_REPLACE_EXT | 0x8062
 | GL_PROXY_TEXTURE_1D_EXT | 0x8063
 | GL_PROXY_TEXTURE_2D_EXT | 0x8064
 | GL_TEXTURE_TOO_LARGE_EXT | 0x8065
GL_EXT_texture3D 9 definitions, no type definitions, 2 functions
Definitions
 | GL_PACK_SKIP_IMAGES_EXT | 0x806b
 | GL_PACK_IMAGE_HEIGHT_EXT | 0x806c
 | GL_UNPACK_SKIP_IMAGES_EXT | 0x806d
 | GL_UNPACK_IMAGE_HEIGHT_EXT | 0x806e
 | GL_TEXTURE_3D_EXT | 0x806f
 | GL_PROXY_TEXTURE_3D_EXT | 0x8070
 | GL_TEXTURE_DEPTH_EXT | 0x8071
 | GL_TEXTURE_WRAP_R_EXT | 0x8072
 | GL_MAX_3D_TEXTURE_SIZE_EXT | 0x8073
Functions
 | GLvoid glTexImage3DEXT(GLenum, GLint, GLenum, GLsizei, GLsizei, GLsizei, GLint, GLenum, GLenum, GLvoid const *);
 | GLvoid glTexSubImage3DEXT(GLenum, GLint, GLint, GLint, GLint, GLsizei, GLsizei, GLsizei, GLenum, GLenum, GLvoid const *);
GL_EXT_texture_compression_s3tc 4 definitions, no type definitions, no functions
Definitions
 | GL_COMPRESSED_RGB_S3TC_DXT1_EXT | 0x83f0
 | GL_COMPRESSED_RGBA_S3TC_DXT1_EXT | 0x83f1
 | GL_COMPRESSED_RGBA_S3TC_DXT3_EXT | 0x83f2
 | GL_COMPRESSED_RGBA_S3TC_DXT5_EXT | 0x83f3
GL_EXT_texture_env_add no definitions, no type definitions, no functions
GL_EXT_texture_env_combine 21 definitions, no type definitions, no functions
Definitions
 | GL_COMBINE_EXT | 0x8570
 | GL_COMBINE_RGB_EXT | 0x8571
 | GL_COMBINE_ALPHA_EXT | 0x8572
 | GL_RGB_SCALE_EXT | 0x8573
 | GL_ADD_SIGNED_EXT | 0x8574
 | GL_INTERPOLATE_EXT | 0x8575
 | GL_CONSTANT_EXT | 0x8576
 | GL_PRIMARY_COLOR_EXT | 0x8577
 | GL_PREVIOUS_EXT | 0x8578
 | GL_SOURCE0_RGB_EXT | 0x8580
 | GL_SOURCE1_RGB_EXT | 0x8581
 | GL_SOURCE2_RGB_EXT | 0x8582
 | GL_SOURCE0_ALPHA_EXT | 0x8588
 | GL_SOURCE1_ALPHA_EXT | 0x8589
 | GL_SOURCE2_ALPHA_EXT | 0x858a
 | GL_OPERAND0_RGB_EXT | 0x8590
 | GL_OPERAND1_RGB_EXT | 0x8591
 | GL_OPERAND2_RGB_EXT | 0x8592
 | GL_OPERAND0_ALPHA_EXT | 0x8598
 | GL_OPERAND1_ALPHA_EXT | 0x8599
 | GL_OPERAND2_ALPHA_EXT | 0x859a
GL_EXT_texture_env_dot3 2 definitions, no type definitions, no functions
Definitions
 | GL_DOT3_RGB_EXT | 0x8740
 | GL_DOT3_RGBA_EXT | 0x8741
GL_EXT_texture_filter_anisotropic 2 definitions, no type definitions, no functions
Definitions
 | GL_TEXTURE_MAX_ANISOTROPY_EXT | 0x84fe
 | GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT | 0x84ff
GL_EXT_texture_lod_bias 3 definitions, no type definitions, no functions
Definitions
 | GL_MAX_TEXTURE_LOD_BIAS_EXT | 0x84fd
 | GL_TEXTURE_FILTER_CONTROL_EXT | 0x8500
 | GL_TEXTURE_LOD_BIAS_EXT | 0x8501
GL_EXT_texture_mirror_clamp no definitions, no type definitions, no functions
GL_EXT_texture_object 5 definitions, no type definitions, 6 functions
Definitions
 | GL_TEXTURE_PRIORITY_EXT | 0x8066
 | GL_TEXTURE_RESIDENT_EXT | 0x8067
 | GL_TEXTURE_1D_BINDING_EXT | 0x8068
 | GL_TEXTURE_2D_BINDING_EXT | 0x8069
 | GL_TEXTURE_3D_BINDING_EXT | 0x806a
Functions
 | GLboolean glAreTexturesResidentEXT(GLsizei, GLuint const *, GLboolean *);
 | GLvoid glBindTextureEXT(GLenum, GLuint);
 | GLvoid glDeleteTexturesEXT(GLsizei, GLuint const *);
 | GLvoid glGenTexturesEXT(GLsizei, GLuint *);
 | GLboolean glIsTextureEXT(GLuint);
 | GLvoid glPrioritizeTexturesEXT(GLsizei, GLuint const *, GLclampf const *);
GL_EXT_texture_perturb_normal 2 definitions, no type definitions, 1 function
Definitions
 | GL_PERTURB_EXT | 0x85ae
 | GL_TEXTURE_NORMAL_EXT | 0x85af
Functions
 | GLvoid glTextureNormalEXT(GLenum);
GL_EXT_vertex_array 32 definitions, no type definitions, 9 functions
Definitions
 | GL_VERTEX_ARRAY_EXT | 0x8074
 | GL_NORMAL_ARRAY_EXT | 0x8075
 | GL_COLOR_ARRAY_EXT | 0x8076
 | GL_INDEX_ARRAY_EXT | 0x8077
 | GL_TEXTURE_COORD_ARRAY_EXT | 0x8078
 | GL_EDGE_FLAG_ARRAY_EXT | 0x8079
 | GL_VERTEX_ARRAY_SIZE_EXT | 0x807a
 | GL_VERTEX_ARRAY_TYPE_EXT | 0x807b
 | GL_VERTEX_ARRAY_STRIDE_EXT | 0x807c
 | GL_VERTEX_ARRAY_COUNT_EXT | 0x807d
 | GL_NORMAL_ARRAY_TYPE_EXT | 0x807e
 | GL_NORMAL_ARRAY_STRIDE_EXT | 0x807f
 | GL_NORMAL_ARRAY_COUNT_EXT | 0x8080
 | GL_COLOR_ARRAY_SIZE_EXT | 0x8081
 | GL_COLOR_ARRAY_TYPE_EXT | 0x8082
 | GL_COLOR_ARRAY_STRIDE_EXT | 0x8083
 | GL_COLOR_ARRAY_COUNT_EXT | 0x8084
 | GL_INDEX_ARRAY_TYPE_EXT | 0x8085
 | GL_INDEX_ARRAY_STRIDE_EXT | 0x8086
 | GL_INDEX_ARRAY_COUNT_EXT | 0x8087
 | GL_TEXTURE_COORD_ARRAY_SIZE_EXT | 0x8088
 | GL_TEXTURE_COORD_ARRAY_TYPE_EXT | 0x8089
 | GL_TEXTURE_COORD_ARRAY_STRIDE_EXT | 0x808a
 | GL_TEXTURE_COORD_ARRAY_COUNT_EXT | 0x808b
 | GL_EDGE_FLAG_ARRAY_STRIDE_EXT | 0x808c
 | GL_EDGE_FLAG_ARRAY_COUNT_EXT | 0x808d
 | GL_VERTEX_ARRAY_POINTER_EXT | 0x808e
 | GL_NORMAL_ARRAY_POINTER_EXT | 0x808f
 | GL_COLOR_ARRAY_POINTER_EXT | 0x8090
 | GL_INDEX_ARRAY_POINTER_EXT | 0x8091
 | GL_TEXTURE_COORD_ARRAY_POINTER_EXT | 0x8092
 | GL_EDGE_FLAG_ARRAY_POINTER_EXT | 0x8093
Functions
 | GLvoid glArrayElementEXT(GLint);
 | GLvoid glColorPointerEXT(GLint, GLenum, GLsizei, GLsizei, GLvoid const *);
 | GLvoid glDrawArraysEXT(GLenum, GLint, GLsizei);
 | GLvoid glEdgeFlagPointerEXT(GLsizei, GLsizei, GLboolean const);
 | GLvoid glGetPointervEXT(GLenum, GLvoid * *);
 | GLvoid glIndexPointerEXT(GLenum, GLsizei, GLsizei, GLvoid const *);
 | GLvoid glNormalPointerEXT(GLenum, GLsizei, GLsizei, GLvoid const *);
 | GLvoid glTexCoordPointerEXT(GLint, GLenum, GLsizei, GLsizei, GLvoid const *);
 | GLvoid glVertexPointerEXT(GLint, GLenum, GLsizei, GLsizei, GLvoid const *);
GL_EXT_vertex_shader 110 definitions, no type definitions, 42 functions
Definitions
 | GL_VERTEX_SHADER_EXT | 0x8780
 | GL_VERTEX_SHADER_BINDING_EXT | 0x8781
 | GL_OP_INDEX_EXT | 0x8782
 | GL_OP_NEGATE_EXT | 0x8783
 | GL_OP_DOT3_EXT | 0x8784
 | GL_OP_DOT4_EXT | 0x8785
 | GL_OP_MUL_EXT | 0x8786
 | GL_OP_ADD_EXT | 0x8787
 | GL_OP_MADD_EXT | 0x8788
 | GL_OP_FRAC_EXT | 0x8789
 | GL_OP_MAX_EXT | 0x878a
 | GL_OP_MIN_EXT | 0x878b
 | GL_OP_SET_GE_EXT | 0x878c
 | GL_OP_SET_LT_EXT | 0x878d
 | GL_OP_CLAMP_EXT | 0x878e
 | GL_OP_FLOOR_EXT | 0x878f
 | GL_OP_ROUND_EXT | 0x8790
 | GL_OP_EXP_BASE_2_EXT | 0x8791
 | GL_OP_LOG_BASE_2_EXT | 0x8792
 | GL_OP_POWER_EXT | 0x8793
 | GL_OP_RECIP_EXT | 0x8794
 | GL_OP_RECIP_SQRT_EXT | 0x8795
 | GL_OP_SUB_EXT | 0x8796
 | GL_OP_CROSS_PRODUCT_EXT | 0x8797
 | GL_OP_MULTIPLY_MATRIX_EXT | 0x8798
 | GL_OP_MOV_EXT | 0x8799
 | GL_OUTPUT_VERTEX_EXT | 0x879a
 | GL_OUTPUT_COLOR0_EXT | 0x879b
 | GL_OUTPUT_COLOR1_EXT | 0x879c
 | GL_OUTPUT_TEXTURE_COORD0_EXT | 0x879d
 | GL_OUTPUT_TEXTURE_COORD1_EXT | 0x879e
 | GL_OUTPUT_TEXTURE_COORD2_EXT | 0x879f
 | GL_OUTPUT_TEXTURE_COORD3_EXT | 0x87a0
 | GL_OUTPUT_TEXTURE_COORD4_EXT | 0x87a1
 | GL_OUTPUT_TEXTURE_COORD5_EXT | 0x87a2
 | GL_OUTPUT_TEXTURE_COORD6_EXT | 0x87a3
 | GL_OUTPUT_TEXTURE_COORD7_EXT | 0x87a4
 | GL_OUTPUT_TEXTURE_COORD8_EXT | 0x87a5
 | GL_OUTPUT_TEXTURE_COORD9_EXT | 0x87a6
 | GL_OUTPUT_TEXTURE_COORD10_EXT | 0x87a7
 | GL_OUTPUT_TEXTURE_COORD11_EXT | 0x87a8
 | GL_OUTPUT_TEXTURE_COORD12_EXT | 0x87a9
 | GL_OUTPUT_TEXTURE_COORD13_EXT | 0x87aa
 | GL_OUTPUT_TEXTURE_COORD14_EXT | 0x87ab
 | GL_OUTPUT_TEXTURE_COORD15_EXT | 0x87ac
 | GL_OUTPUT_TEXTURE_COORD16_EXT | 0x87ad
 | GL_OUTPUT_TEXTURE_COORD17_EXT | 0x87ae
 | GL_OUTPUT_TEXTURE_COORD18_EXT | 0x87af
 | GL_OUTPUT_TEXTURE_COORD19_EXT | 0x87b0
 | GL_OUTPUT_TEXTURE_COORD20_EXT | 0x87b1
 | GL_OUTPUT_TEXTURE_COORD21_EXT | 0x87b2
 | GL_OUTPUT_TEXTURE_COORD22_EXT | 0x87b3
 | GL_OUTPUT_TEXTURE_COORD23_EXT | 0x87b4
 | GL_OUTPUT_TEXTURE_COORD24_EXT | 0x87b5
 | GL_OUTPUT_TEXTURE_COORD25_EXT | 0x87b6
 | GL_OUTPUT_TEXTURE_COORD26_EXT | 0x87b7
 | GL_OUTPUT_TEXTURE_COORD27_EXT | 0x87b8
 | GL_OUTPUT_TEXTURE_COORD28_EXT | 0x87b9
 | GL_OUTPUT_TEXTURE_COORD29_EXT | 0x87ba
 | GL_OUTPUT_TEXTURE_COORD30_EXT | 0x87bb
 | GL_OUTPUT_TEXTURE_COORD31_EXT | 0x87bc
 | GL_OUTPUT_FOG_EXT | 0x87bd
 | GL_SCALAR_EXT | 0x87be
 | GL_VECTOR_EXT | 0x87bf
 | GL_MATRIX_EXT | 0x87c0
 | GL_VARIANT_EXT | 0x87c1
 | GL_INVARIANT_EXT | 0x87c2
 | GL_LOCAL_CONSTANT_EXT | 0x87c3
 | GL_LOCAL_EXT | 0x87c4
 | GL_MAX_VERTEX_SHADER_INSTRUCTIONS_EXT | 0x87c5
 | GL_MAX_VERTEX_SHADER_VARIANTS_EXT | 0x87c6
 | GL_MAX_VERTEX_SHADER_INVARIANTS_EXT | 0x87c7
 | GL_MAX_VERTEX_SHADER_LOCAL_CONSTANTS_EXT | 0x87c8
 | GL_MAX_VERTEX_SHADER_LOCALS_EXT | 0x87c9
 | GL_MAX_OPTIMIZED_VERTEX_SHADER_INSTRUCTIONS_EXT | 0x87ca
 | GL_MAX_OPTIMIZED_VERTEX_SHADER_VARIANTS_EXT | 0x87cb
 | GL_MAX_OPTIMIZED_VERTEX_SHADER_LOCAL_CONSTANTS_EXT | 0x87cc
 | GL_MAX_OPTIMIZED_VERTEX_SHADER_INVARIANTS_EXT | 0x87cd
 | GL_MAX_OPTIMIZED_VERTEX_SHADER_LOCALS_EXT | 0x87ce
 | GL_VERTEX_SHADER_INSTRUCTIONS_EXT | 0x87cf
 | GL_VERTEX_SHADER_VARIANTS_EXT | 0x87d0
 | GL_VERTEX_SHADER_INVARIANTS_EXT | 0x87d1
 | GL_VERTEX_SHADER_LOCAL_CONSTANTS_EXT | 0x87d2
 | GL_VERTEX_SHADER_LOCALS_EXT | 0x87d3
 | GL_VERTEX_SHADER_OPTIMIZED_EXT | 0x87d4
 | GL_X_EXT | 0x87d5
 | GL_Y_EXT | 0x87d6
 | GL_Z_EXT | 0x87d7
 | GL_W_EXT | 0x87d8
 | GL_NEGATIVE_X_EXT | 0x87d9
 | GL_NEGATIVE_Y_EXT | 0x87da
 | GL_NEGATIVE_Z_EXT | 0x87db
 | GL_NEGATIVE_W_EXT | 0x87dc
 | GL_ZERO_EXT | 0x87dd
 | GL_ONE_EXT | 0x87de
 | GL_NEGATIVE_ONE_EXT | 0x87df
 | GL_NORMALIZED_RANGE_EXT | 0x87e0
 | GL_FULL_RANGE_EXT | 0x87e1
 | GL_CURRENT_VERTEX_EXT | 0x87e2
 | GL_MVP_MATRIX_EXT | 0x87e3
 | GL_VARIANT_VALUE_EXT | 0x87e4
 | GL_VARIANT_DATATYPE_EXT | 0x87e5
 | GL_VARIANT_ARRAY_STRIDE_EXT | 0x87e6
 | GL_VARIANT_ARRAY_TYPE_EXT | 0x87e7
 | GL_VARIANT_ARRAY_EXT | 0x87e8
 | GL_VARIANT_ARRAY_POINTER_EXT | 0x87e9
 | GL_INVARIANT_VALUE_EXT | 0x87ea
 | GL_INVARIANT_DATATYPE_EXT | 0x87eb
 | GL_LOCAL_CONSTANT_VALUE_EXT | 0x87ec
 | GL_LOCAL_CONSTANT_DATATYPE_EXT | 0x87ed
Functions
 | GLvoid glBeginVertexShaderEXT();
 | GLuint glBindLightParameterEXT(GLenum, GLenum);
 | GLuint glBindMaterialParameterEXT(GLenum, GLenum);
 | GLuint glBindParameterEXT(GLenum);
 | GLuint glBindTexGenParameterEXT(GLenum, GLenum, GLenum);
 | GLuint glBindTextureUnitParameterEXT(GLenum, GLenum);
 | GLvoid glBindVertexShaderEXT(GLuint);
 | GLvoid glDeleteVertexShaderEXT(GLuint);
 | GLvoid glDisableVariantClientStateEXT(GLuint);
 | GLvoid glEnableVariantClientStateEXT(GLuint);
 | GLvoid glEndVertexShaderEXT();
 | GLvoid glExtractComponentEXT(GLuint, GLuint, GLuint);
 | GLuint glGenSymbolsEXT(GLenum, GLenum, GLenum, GLuint);
 | GLuint glGenVertexShadersEXT(GLuint);
 | GLvoid glGetInvariantBooleanvEXT(GLuint, GLenum, GLboolean *);
 | GLvoid glGetInvariantFloatvEXT(GLuint, GLenum, GLfloat *);
 | GLvoid glGetInvariantIntegervEXT(GLuint, GLenum, GLint *);
 | GLvoid glGetLocalConstantBooleanvEXT(GLuint, GLenum, GLboolean *);
 | GLvoid glGetLocalConstantFloatvEXT(GLuint, GLenum, GLfloat *);
 | GLvoid glGetLocalConstantIntegervEXT(GLuint, GLenum, GLint *);
 | GLvoid glGetVariantBooleanvEXT(GLuint, GLenum, GLboolean *);
 | GLvoid glGetVariantFloatvEXT(GLuint, GLenum, GLfloat *);
 | GLvoid glGetVariantIntegervEXT(GLuint, GLenum, GLint *);
 | GLvoid glGetVariantPointervEXT(GLuint, GLenum, GLvoid * *);
 | GLvoid glInsertComponentEXT(GLuint, GLuint, GLuint);
 | GLboolean glIsVariantEnabledEXT(GLuint, GLenum);
 | GLvoid glSetInvariantEXT(GLuint, GLenum, GLvoid const *);
 | GLvoid glSetLocalConstantEXT(GLuint, GLenum, GLvoid const *);
 | GLvoid glShaderOp1EXT(GLenum, GLuint, GLuint);
 | GLvoid glShaderOp2EXT(GLenum, GLuint, GLuint, GLuint);
 | GLvoid glShaderOp3EXT(GLenum, GLuint, GLuint, GLuint, GLuint);
 | GLvoid glSwizzleEXT(GLuint, GLuint, GLenum, GLenum, GLenum, GLenum);
 | GLvoid glVariantbvEXT(GLuint, GLbyte const *);
 | GLvoid glVariantdvEXT(GLuint, GLdouble const *);
 | GLvoid glVariantfvEXT(GLuint, GLfloat const *);
 | GLvoid glVariantivEXT(GLuint, GLint const *);
 | GLvoid glVariantPointerEXT(GLuint, GLenum, GLuint, GLvoid const *);
 | GLvoid glVariantsvEXT(GLuint, GLshort const *);
 | GLvoid glVariantubvEXT(GLuint, GLubyte const *);
 | GLvoid glVariantuivEXT(GLuint, GLuint const *);
 | GLvoid glVariantusvEXT(GLuint, GLushort const *);
 | GLvoid glWriteMaskEXT(GLuint, GLuint, GLenum, GLenum, GLenum, GLenum);
GL_EXT_vertex_weighting 13 definitions, no type definitions, 3 functions
Definitions
 | GL_MODELVIEW0_STACK_DEPTH_EXT | 0xba3
 | GL_MODELVIEW0_MATRIX_EXT | 0xba6
 | GL_MODELVIEW0_EXT | 0x1700
 | GL_MODELVIEW1_STACK_DEPTH_EXT | 0x8502
 | GL_MODELVIEW1_MATRIX_EXT | 0x8506
 | GL_VERTEX_WEIGHTING_EXT | 0x8509
 | GL_MODELVIEW1_EXT | 0x850a
 | GL_CURRENT_VERTEX_WEIGHT_EXT | 0x850b
 | GL_VERTEX_WEIGHT_ARRAY_EXT | 0x850c
 | GL_VERTEX_WEIGHT_ARRAY_SIZE_EXT | 0x850d
 | GL_VERTEX_WEIGHT_ARRAY_TYPE_EXT | 0x850e
 | GL_VERTEX_WEIGHT_ARRAY_STRIDE_EXT | 0x850f
 | GL_VERTEX_WEIGHT_ARRAY_POINTER_EXT | 0x8510
Functions
 | GLvoid glVertexWeightfEXT(GLfloat);
 | GLvoid glVertexWeightfvEXT(GLfloat const *);
 | GLvoid glVertexWeightPointerEXT(GLsizei, GLenum, GLsizei, GLvoid const *);
GL_HP_convolution_border_modes 4 definitions, no type definitions, no functions
Definitions
 | GL_IGNORE_BORDER_HP | 0x8150
 | GL_CONSTANT_BORDER_HP | 0x8151
 | GL_REPLICATE_BORDER_HP | 0x8153
 | GL_CONVOLUTION_BORDER_COLOR_HP | 0x8154
GL_HP_image_transform 15 definitions, no type definitions, 6 functions
Definitions
 | GL_IMAGE_SCALE_X_HP | 0x8155
 | GL_IMAGE_SCALE_Y_HP | 0x8156
 | GL_IMAGE_TRANSLATE_X_HP | 0x8157
 | GL_IMAGE_TRANSLATE_Y_HP | 0x8158
 | GL_IMAGE_ROTATE_ANGLE_HP | 0x8159
 | GL_IMAGE_ROTATE_ORIGIN_X_HP | 0x815a
 | GL_IMAGE_ROTATE_ORIGIN_Y_HP | 0x815b
 | GL_IMAGE_MAG_FILTER_HP | 0x815c
 | GL_IMAGE_MIN_FILTER_HP | 0x815d
 | GL_IMAGE_CUBIC_WEIGHT_HP | 0x815e
 | GL_CUBIC_HP | 0x815f
 | GL_AVERAGE_HP | 0x8160
 | GL_IMAGE_TRANSFORM_2D_HP | 0x8161
 | GL_POST_IMAGE_TRANSFORM_COLOR_TABLE_HP | 0x8162
 | GL_PROXY_POST_IMAGE_TRANSFORM_COLOR_TABLE_HP | 0x8163
Functions
 | GLvoid glGetImageTransformParameterfvHP(GLenum, GLenum, GLfloat *);
 | GLvoid glGetImageTransformParameterivHP(GLenum, GLenum, GLint *);
 | GLvoid glImageTransformParameterfHP(GLenum, GLenum, GLfloat);
 | GLvoid glImageTransformParameterfvHP(GLenum, GLenum, GLfloat const *);
 | GLvoid glImageTransformParameteriHP(GLenum, GLenum, GLint);
 | GLvoid glImageTransformParameterivHP(GLenum, GLenum, GLint const *);
GL_HP_occlusion_test 2 definitions, no type definitions, no functions
Definitions
 | GL_OCCLUSION_TEST_HP | 0x8165
 | GL_OCCLUSION_TEST_RESULT_HP | 0x8166
GL_HP_texture_lighting 3 definitions, no type definitions, no functions
Definitions
 | GL_TEXTURE_LIGHTING_MODE_HP | 0x8167
 | GL_TEXTURE_POST_SPECULAR_HP | 0x8168
 | GL_TEXTURE_PRE_SPECULAR_HP | 0x8169
GL_IBM_cull_vertex 1 definition, no type definitions, no functions
Definitions
GL_CULL_VERTEX_IBM
0x1928a
GL_IBM_multimode_draw_arrays no definitions, no type definitions, 2 functions
Functions
GLvoid glMultiModeDrawArraysIBM(GLenum, GLint const *, GLsizei const *, GLsizei, GLint);
GLvoid glMultiModeDrawElementsIBM(GLenum const *, GLsizei const *, GLenum, GLvoid const * *, GLsizei, GLint);
GL_IBM_rasterpos_clip 1 definition, no type definitions, no functions
Definitions
GL_RASTER_POSITION_UNCLIPPED_IBM
0x19262
GL_IBM_static_data no definitions, no type definitions, no functions
GL_IBM_texture_mirrored_repeat 1 definition, no type definitions, no functions
Definitions
GL_MIRRORED_REPEAT_IBM
0x8370
GL_IBM_vertex_array_lists 16 definitions, no type definitions, 8 functions
Definitions
 | GL_VERTEX_ARRAY_LIST_IBM | 0x1929e
 | GL_NORMAL_ARRAY_LIST_IBM | 0x1929f
 | GL_COLOR_ARRAY_LIST_IBM | 0x192a0
 | GL_INDEX_ARRAY_LIST_IBM | 0x192a1
 | GL_TEXTURE_COORD_ARRAY_LIST_IBM | 0x192a2
 | GL_EDGE_FLAG_ARRAY_LIST_IBM | 0x192a3
 | GL_FOG_COORDINATE_ARRAY_LIST_IBM | 0x192a4
 | GL_SECONDARY_COLOR_ARRAY_LIST_IBM | 0x192a5
 | GL_VERTEX_ARRAY_LIST_STRIDE_IBM | 0x192a8
 | GL_NORMAL_ARRAY_LIST_STRIDE_IBM | 0x192a9
 | GL_COLOR_ARRAY_LIST_STRIDE_IBM | 0x192aa
 | GL_INDEX_ARRAY_LIST_STRIDE_IBM | 0x192ab
 | GL_TEXTURE_COORD_ARRAY_LIST_STRIDE_IBM | 0x192ac
 | GL_EDGE_FLAG_ARRAY_LIST_STRIDE_IBM | 0x192ad
 | GL_FOG_COORDINATE_ARRAY_LIST_STRIDE_IBM | 0x192ae
 | GL_SECONDARY_COLOR_ARRAY_LIST_STRIDE_IBM | 0x192af
Functions
 | GLvoid glColorPointerListIBM(GLint, GLenum, GLint, GLvoid const * *, GLint);
 | GLvoid glEdgeFlagPointerListIBM(GLint, GLboolean const, GLint);
 | GLvoid glFogCoordPointerListIBM(GLenum, GLint, GLvoid const * *, GLint);
 | GLvoid glIndexPointerListIBM(GLenum, GLint, GLvoid const * *, GLint);
 | GLvoid glNormalPointerListIBM(GLenum, GLint, GLvoid const * *, GLint);
 | GLvoid glSecondaryColorPointerListIBM(GLint, GLenum, GLint, GLvoid const * *, GLint);
 | GLvoid glTexCoordPointerListIBM(GLint, GLenum, GLint, GLvoid const * *, GLint);
 | GLvoid glVertexPointerListIBM(GLint, GLenum, GLint, GLvoid const * *, GLint);
GL_INGR_blend_func_separate no definitions, no type definitions, 1 function
Functions
GLvoid glBlendFuncSeparateINGR(GLenum, GLenum, GLenum, GLenum);
GL_INGR_color_clamp 8 definitions, no type definitions, no functions
Definitions
 | GL_RED_MIN_CLAMP_INGR | 0x8560
 | GL_GREEN_MIN_CLAMP_INGR | 0x8561
 | GL_BLUE_MIN_CLAMP_INGR | 0x8562
 | GL_ALPHA_MIN_CLAMP_INGR | 0x8563
 | GL_RED_MAX_CLAMP_INGR | 0x8564
 | GL_GREEN_MAX_CLAMP_INGR | 0x8565
 | GL_BLUE_MAX_CLAMP_INGR | 0x8566
 | GL_ALPHA_MAX_CLAMP_INGR | 0x8567
GL_INGR_interlace_read 1 definition, no type definitions, no functions
Definitions
GL_INTERLACE_READ_INGR
0x8568
GL_INTEL_parallel_arrays 5 definitions, no type definitions, 4 functions
Definitions
 | GL_PARALLEL_ARRAYS_INTEL | 0x83f4
 | GL_VERTEX_ARRAY_PARALLEL_POINTERS_INTEL | 0x83f5
 | GL_NORMAL_ARRAY_PARALLEL_POINTERS_INTEL | 0x83f6
 | GL_COLOR_ARRAY_PARALLEL_POINTERS_INTEL | 0x83f7
 | GL_TEXTURE_COORD_ARRAY_PARALLEL_POINTERS_INTEL | 0x83f8
Functions
 | GLvoid glColorPointervINTEL(GLint, GLenum, GLvoid const * *);
 | GLvoid glNormalPointervINTEL(GLenum, GLvoid const * *);
 | GLvoid glTexCoordPointervINTEL(GLint, GLenum, GLvoid const * *);
 | GLvoid glVertexPointervINTEL(GLint, GLenum, GLvoid const * *);
GL_MESA_resize_buffers no definitions, no type definitions, 1 function
Functions
GLvoid glResizeBuffersMESA();
GL_MESA_window_pos no definitions, no type definitions, 24 functions
Functions
GLvoid glWindowPos2dMESA(GLdouble, GLdouble);
GLvoid glWindowPos2dvMESA(GLdouble const *);
GLvoid glWindowPos2fMESA(GLfloat, GLfloat);
GLvoid glWindowPos2fvMESA(GLfloat const *);
GLvoid glWindowPos2iMESA(GLint, GLint);
GLvoid glWindowPos2ivMESA(GLint const *);
GLvoid glWindowPos2sMESA(GLshort, GLshort);
GLvoid glWindowPos2svMESA(GLshort const *);
GLvoid glWindowPos3dMESA(GLdouble, GLdouble, GLdouble);
GLvoid glWindowPos3dvMESA(GLdouble const *);
GLvoid glWindowPos3fMESA(GLfloat, GLfloat, GLfloat);
GLvoid glWindowPos3fvMESA(GLfloat const *);
GLvoid glWindowPos3iMESA(GLint, GLint, GLint);
GLvoid glWindowPos3ivMESA(GLint const *);
GLvoid glWindowPos3sMESA(GLshort, GLshort, GLshort);
GLvoid glWindowPos3svMESA(GLshort const *);
GLvoid glWindowPos4dMESA(GLdouble, GLdouble, GLdouble, GLdouble);
GLvoid glWindowPos4dvMESA(GLdouble const *);
GLvoid glWindowPos4fMESA(GLfloat, GLfloat, GLfloat, GLfloat);
GLvoid glWindowPos4fvMESA(GLfloat const *);
GLvoid glWindowPos4iMESA(GLint, GLint, GLint, GLint);
GLvoid glWindowPos4ivMESA(GLint const *);
GLvoid glWindowPos4sMESA(GLshort, GLshort, GLshort, GLshort);
GLvoid glWindowPos4svMESA(GLshort const *);
GL_NV_blend_square no definitions, no type definitions, no functions
GL_NV_copy_depth_to_color 2 definitions, no type definitions, no functions
Definitions
 | GL_DEPTH_STENCIL_TO_RGBA_NV | 0x886e
 | GL_DEPTH_STENCIL_TO_BGRA_NV | 0x886f
GL_NV_depth_clamp 1 definition, no type definitions, no functions
Definitions
GL_DEPTH_CLAMP_NV
0x864f
GL_NV_evaluators 24 definitions, no type definitions, 9 functions
Definitions
 | GL_EVAL_2D_NV | 0x86c0
 | GL_EVAL_TRIANGULAR_2D_NV | 0x86c1
 | GL_MAP_TESSELLATION_NV | 0x86c2
 | GL_MAP_ATTRIB_U_ORDER_NV | 0x86c3
 | GL_MAP_ATTRIB_V_ORDER_NV | 0x86c4
 | GL_EVAL_FRACTIONAL_TESSELLATION_NV | 0x86c5
 | GL_EVAL_VERTEX_ATTRIB0_NV | 0x86c6
 | GL_EVAL_VERTEX_ATTRIB1_NV | 0x86c7
 | GL_EVAL_VERTEX_ATTRIB2_NV | 0x86c8
 | GL_EVAL_VERTEX_ATTRIB3_NV | 0x86c9
 | GL_EVAL_VERTEX_ATTRIB4_NV | 0x86ca
 | GL_EVAL_VERTEX_ATTRIB5_NV | 0x86cb
 | GL_EVAL_VERTEX_ATTRIB6_NV | 0x86cc
 | GL_EVAL_VERTEX_ATTRIB7_NV | 0x86cd
 | GL_EVAL_VERTEX_ATTRIB8_NV | 0x86ce
 | GL_EVAL_VERTEX_ATTRIB9_NV | 0x86cf
 | GL_EVAL_VERTEX_ATTRIB10_NV | 0x86d0
 | GL_EVAL_VERTEX_ATTRIB11_NV | 0x86d1
 | GL_EVAL_VERTEX_ATTRIB12_NV | 0x86d2
 | GL_EVAL_VERTEX_ATTRIB13_NV | 0x86d3
 | GL_EVAL_VERTEX_ATTRIB14_NV | 0x86d4
 | GL_EVAL_VERTEX_ATTRIB15_NV | 0x86d5
 | GL_MAX_MAP_TESSELLATION_NV | 0x86d6
 | GL_MAX_RATIONAL_EVAL_ORDER_NV | 0x86d7
Functions
 | GLvoid glEvalMapsNV(GLenum, GLenum);
 | GLvoid glGetMapAttribParameterfvNV(GLenum, GLuint, GLenum, GLfloat *);
 | GLvoid glGetMapAttribParameterivNV(GLenum, GLuint, GLenum, GLint *);
 | GLvoid glGetMapControlPointsNV(GLenum, GLuint, GLenum, GLsizei, GLsizei, GLboolean, GLvoid *);
 | GLvoid glGetMapParameterfvNV(GLenum, GLenum, GLfloat *);
 | GLvoid glGetMapParameterivNV(GLenum, GLenum, GLint *);
 | GLvoid glMapControlPointsNV(GLenum, GLuint, GLenum, GLsizei, GLsizei, GLint, GLint, GLboolean, GLvoid const *);
 | GLvoid glMapParameterfvNV(GLenum, GLenum, GLfloat const *);
 | GLvoid glMapParameterivNV(GLenum, GLenum, GLint const *);
GL_NV_fence 3 definitions, no type definitions, 7 functions
Definitions
 | GL_ALL_COMPLETED_NV | 0x84f2
 | GL_FENCE_STATUS_NV | 0x84f3
 | GL_FENCE_CONDITION_NV | 0x84f4
Functions
 | GLvoid glDeleteFencesNV(GLsizei, GLuint const *);
 | GLvoid glFinishFenceNV(GLuint);
 | GLvoid glGenFencesNV(GLsizei, GLuint *);
 | GLvoid glGetFenceivNV(GLuint, GLenum, GLint *);
 | GLboolean glIsFenceNV(GLuint);
 | GLvoid glSetFenceNV(GLuint, GLenum);
 | GLboolean glTestFenceNV(GLuint);
GL_NV_float_buffer 15 definitions, no type definitions, no functions
Definitions
 | GL_FLOAT_R_NV | 0x8880
 | GL_FLOAT_RG_NV | 0x8881
 | GL_FLOAT_RGB_NV | 0x8882
 | GL_FLOAT_RGBA_NV | 0x8883
 | GL_FLOAT_R16_NV | 0x8884
 | GL_FLOAT_R32_NV | 0x8885
 | GL_FLOAT_RG16_NV | 0x8886
 | GL_FLOAT_RG32_NV | 0x8887
 | GL_FLOAT_RGB16_NV | 0x8888
 | GL_FLOAT_RGB32_NV | 0x8889
 | GL_FLOAT_RGBA16_NV | 0x888a
 | GL_FLOAT_RGBA32_NV | 0x888b
 | GL_TEXTURE_FLOAT_COMPONENTS_NV | 0x888c
 | GL_FLOAT_CLEAR_COLOR_VALUE_NV | 0x888d
 | GL_FLOAT_RGBA_MODE_NV | 0x888e
GL_NV_fog_distance 3 definitions, no type definitions, no functions
Definitions
 | GL_FOG_DISTANCE_MODE_NV | 0x855a
 | GL_EYE_RADIAL_NV | 0x855b
 | GL_EYE_PLANE_ABSOLUTE_NV | 0x855c
GL_NV_fragment_program 6 definitions, no type definitions, 6 functions
Definitions
 | GL_MAX_FRAGMENT_PROGRAM_LOCAL_PARAMETERS_NV | 0x8868
 | GL_FRAGMENT_PROGRAM_NV | 0x8870
 | GL_MAX_TEXTURE_COORDS_NV | 0x8871
 | GL_MAX_TEXTURE_IMAGE_UNITS_NV | 0x8872
 | GL_FRAGMENT_PROGRAM_BINDING_NV | 0x8873
 | GL_PROGRAM_ERROR_STRING_NV | 0x8874
Functions
 | GLvoid glGetProgramNamedParameterdvNV(GLuint, GLsizei, GLubyte const *, GLdouble *);
 | GLvoid glGetProgramNamedParameterfvNV(GLuint, GLsizei, GLubyte const *, GLfloat *);
 | GLvoid glProgramNamedParameter4dNV(GLuint, GLsizei, GLubyte const *, GLdouble, GLdouble, GLdouble, GLdouble);
 | GLvoid glProgramNamedParameter4dvNV(GLuint, GLsizei, GLubyte const *, GLdouble const *);
 | GLvoid glProgramNamedParameter4fNV(GLuint, GLsizei, GLubyte const *, GLfloat, GLfloat, GLfloat, GLfloat);
 | GLvoid glProgramNamedParameter4fvNV(GLuint, GLsizei, GLubyte const *, GLfloat const *);
GL_NV_half_float 1 definition, 1 type definition, 46 functions
Definitions
 | GL_HALF_FLOAT_NV | 0x140b
Type Definitions
 | GLhalfNV | unsigned short
Functions
 | GLvoid glColor3hNV(GLhalfNV, GLhalfNV, GLhalfNV);
 | GLvoid glColor3hvNV(GLhalfNV const *);
 | GLvoid glColor4hNV(GLhalfNV, GLhalfNV, GLhalfNV, GLhalfNV);
 | GLvoid glColor4hvNV(GLhalfNV const *);
 | GLvoid glFogCoordhNV(GLhalfNV);
 | GLvoid glFogCoordhvNV(GLhalfNV const *);
 | GLvoid glMultiTexCoord1hNV(GLenum, GLhalfNV);
 | GLvoid glMultiTexCoord1hvNV(GLenum, GLhalfNV const *);
 | GLvoid glMultiTexCoord2hNV(GLenum, GLhalfNV, GLhalfNV);
 | GLvoid glMultiTexCoord2hvNV(GLenum, GLhalfNV const *);
 | GLvoid glMultiTexCoord3hNV(GLenum, GLhalfNV, GLhalfNV, GLhalfNV);
 | GLvoid glMultiTexCoord3hvNV(GLenum, GLhalfNV const *);
 | GLvoid glMultiTexCoord4hNV(GLenum, GLhalfNV, GLhalfNV, GLhalfNV, GLhalfNV);
 | GLvoid glMultiTexCoord4hvNV(GLenum, GLhalfNV const *);
 | GLvoid glNormal3hNV(GLhalfNV, GLhalfNV, GLhalfNV);
 | GLvoid glNormal3hvNV(GLhalfNV const *);
 | GLvoid glSecondaryColor3hNV(GLhalfNV, GLhalfNV, GLhalfNV);
 | GLvoid glSecondaryColor3hvNV(GLhalfNV const *);
 | GLvoid glTexCoord1hNV(GLhalfNV);
 | GLvoid glTexCoord1hvNV(GLhalfNV const *);
 | GLvoid glTexCoord2hNV(GLhalfNV, GLhalfNV);
 | GLvoid glTexCoord2hvNV(GLhalfNV const *);
 | GLvoid glTexCoord3hNV(GLhalfNV, GLhalfNV, GLhalfNV);
 | GLvoid glTexCoord3hvNV(GLhalfNV const *);
 | GLvoid glTexCoord4hNV(GLhalfNV, GLhalfNV, GLhalfNV, GLhalfNV);
 | GLvoid glTexCoord4hvNV(GLhalfNV const *);
 | GLvoid glVertex2hNV(GLhalfNV, GLhalfNV);
 | GLvoid glVertex2hvNV(GLhalfNV const *);
 | GLvoid glVertex3hNV(GLhalfNV, GLhalfNV, GLhalfNV);
 | GLvoid glVertex3hvNV(GLhalfNV const *);
 | GLvoid glVertex4hNV(GLhalfNV, GLhalfNV, GLhalfNV, GLhalfNV);
 | GLvoid glVertex4hvNV(GLhalfNV const *);
 | GLvoid glVertexAttrib1hNV(GLuint, GLhalfNV);
 | GLvoid glVertexAttrib1hvNV(GLuint, GLhalfNV const *);
 | GLvoid glVertexAttrib2hNV(GLuint, GLhalfNV, GLhalfNV);
 | GLvoid glVertexAttrib2hvNV(GLuint, GLhalfNV const *);
 | GLvoid glVertexAttrib3hNV(GLuint, GLhalfNV, GLhalfNV, GLhalfNV);
 | GLvoid glVertexAttrib3hvNV(GLuint, GLhalfNV const *);
 | GLvoid glVertexAttrib4hNV(GLuint, GLhalfNV, GLhalfNV, GLhalfNV, GLhalfNV);
 | GLvoid glVertexAttrib4hvNV(GLuint, GLhalfNV const *);
 | GLvoid glVertexAttribs1hvNV(GLuint, GLsizei, GLhalfNV const *);
 | GLvoid glVertexAttribs2hvNV(GLuint, GLsizei, GLhalfNV const *);
 | GLvoid glVertexAttribs3hvNV(GLuint, GLsizei, GLhalfNV const *);
 | GLvoid glVertexAttribs4hvNV(GLuint, GLsizei, GLhalfNV const *);
 | GLvoid glVertexWeighthNV(GLhalfNV);
 | GLvoid glVertexWeighthvNV(GLhalfNV const *);
GL_NV_light_max_exponent 2 definitions, no type definitions, no functions
Definitions
 | GL_MAX_SHININESS_NV | 0x8504
 | GL_MAX_SPOT_EXPONENT_NV | 0x8505
GL_NV_multisample_filter_hint 1 definition, no type definitions, no functions
Definitions
GL_MULTISAMPLE_FILTER_HINT_NV
0x8534
GL_NV_occlusion_query 4 definitions, no type definitions, 7 functions
Definitions
 | GL_PIXEL_COUNTER_BITS_NV | 0x8864
 | GL_CURRENT_OCCLUSION_QUERY_ID_NV | 0x8865
 | GL_PIXEL_COUNT_NV | 0x8866
 | GL_PIXEL_COUNT_AVAILABLE_NV | 0x8867
Functions
 | GLvoid glBeginOcclusionQueryNV(GLuint);
 | GLvoid glDeleteOcclusionQueriesNV(GLsizei, GLuint const *);
 | GLvoid glEndOcclusionQueryNV();
 | GLvoid glGenOcclusionQueriesNV(GLsizei, GLuint *);
 | GLvoid glGetOcclusionQueryivNV(GLuint, GLenum, GLint *);
 | GLvoid glGetOcclusionQueryuivNV(GLuint, GLenum, GLuint *);
 | GLboolean glIsOcclusionQueryNV(GLuint);
GL_NV_packed_depth_stencil 2 definitions, no type definitions, no functions
Definitions
 | GL_DEPTH_STENCIL_NV | 0x84f9
 | GL_UNSIGNED_INT_24_8_NV | 0x84fa
GL_NV_pixel_data_range 6 definitions, no type definitions, 2 functions
Definitions
 | GL_WRITE_PIXEL_DATA_RANGE_NV | 0x8878
 | GL_READ_PIXEL_DATA_RANGE_NV | 0x8879
 | GL_WRITE_PIXEL_DATA_RANGE_LENGTH_NV | 0x887a
 | GL_READ_PIXEL_DATA_RANGE_LENGTH_NV | 0x887b
 | GL_WRITE_PIXEL_DATA_RANGE_POINTER_NV | 0x887c
 | GL_READ_PIXEL_DATA_RANGE_POINTER_NV | 0x887d
Functions
 | GLvoid glFlushPixelDataRangeNV(GLenum);
 | GLvoid glPixelDataRangeNV(GLenum, GLsizei, GLvoid *);
GL_NV_point_sprite 3 definitions, no type definitions, 2 functions
Definitions
 | GL_POINT_SPRITE_NV | 0x8861
 | GL_COORD_REPLACE_NV | 0x8862
 | GL_POINT_SPRITE_R_MODE_NV | 0x8863
Functions
 | GLvoid glPointParameteriNV(GLenum, GLint);
 | GLvoid glPointParameterivNV(GLenum, GLint const *);
GL_NV_primitive_restart 2 definitions, no type definitions, 2 functions
Definitions
 | GL_PRIMITIVE_RESTART_NV | 0x8558
 | GL_PRIMITIVE_RESTART_INDEX_NV | 0x8559
Functions
 | GLvoid glPrimitiveRestartIndexNV(GLuint);
 | GLvoid glPrimitiveRestartNV();
GL_NV_register_combiners 51 definitions, no type definitions, 13 functions
Definitions
 | GL_REGISTER_COMBINERS_NV | 0x8522
 | GL_VARIABLE_A_NV | 0x8523
 | GL_VARIABLE_B_NV | 0x8524
 | GL_VARIABLE_C_NV | 0x8525
 | GL_VARIABLE_D_NV | 0x8526
 | GL_VARIABLE_E_NV | 0x8527
 | GL_VARIABLE_F_NV | 0x8528
 | GL_VARIABLE_G_NV | 0x8529
 | GL_CONSTANT_COLOR0_NV | 0x852a
 | GL_CONSTANT_COLOR1_NV | 0x852b
 | GL_PRIMARY_COLOR_NV | 0x852c
 | GL_SECONDARY_COLOR_NV | 0x852d
 | GL_SPARE0_NV | 0x852e
 | GL_SPARE1_NV | 0x852f
 | GL_DISCARD_NV | 0x8530
 | GL_E_TIMES_F_NV | 0x8531
 | GL_SPARE0_PLUS_SECONDARY_COLOR_NV | 0x8532
 | GL_UNSIGNED_IDENTITY_NV | 0x8536
 | GL_UNSIGNED_INVERT_NV | 0x8537
 | GL_EXPAND_NORMAL_NV | 0x8538
 | GL_EXPAND_NEGATE_NV | 0x8539
 | GL_HALF_BIAS_NORMAL_NV | 0x853a
 | GL_HALF_BIAS_NEGATE_NV | 0x853b
 | GL_SIGNED_IDENTITY_NV | 0x853c
 | GL_SIGNED_NEGATE_NV | 0x853d
 | GL_SCALE_BY_TWO_NV | 0x853e
 | GL_SCALE_BY_FOUR_NV | 0x853f
 | GL_SCALE_BY_ONE_HALF_NV | 0x8540
 | GL_BIAS_BY_NEGATIVE_ONE_HALF_NV | 0x8541
 | GL_COMBINER_INPUT_NV | 0x8542
 | GL_COMBINER_MAPPING_NV | 0x8543
 | GL_COMBINER_COMPONENT_USAGE_NV | 0x8544
 | GL_COMBINER_AB_DOT_PRODUCT_NV | 0x8545
 | GL_COMBINER_CD_DOT_PRODUCT_NV | 0x8546
 | GL_COMBINER_MUX_SUM_NV | 0x8547
 | GL_COMBINER_SCALE_NV | 0x8548
 | GL_COMBINER_BIAS_NV | 0x8549
 | GL_COMBINER_AB_OUTPUT_NV | 0x854a
 | GL_COMBINER_CD_OUTPUT_NV | 0x854b
 | GL_COMBINER_SUM_OUTPUT_NV | 0x854c
 | GL_MAX_GENERAL_COMBINERS_NV | 0x854d
 | GL_NUM_GENERAL_COMBINERS_NV | 0x854e
 | GL_COLOR_SUM_CLAMP_NV | 0x854f
 | GL_COMBINER0_NV | 0x8550
 | GL_COMBINER1_NV | 0x8551
 | GL_COMBINER2_NV | 0x8552
 | GL_COMBINER3_NV | 0x8553
 | GL_COMBINER4_NV | 0x8554
 | GL_COMBINER5_NV | 0x8555
 | GL_COMBINER6_NV | 0x8556
 | GL_COMBINER7_NV | 0x8557
Functions
 | GLvoid glCombinerInputNV(GLenum, GLenum, GLenum, GLenum, GLenum, GLenum);
 | GLvoid glCombinerOutputNV(GLenum, GLenum, GLenum, GLenum, GLenum, GLenum, GLenum, GLboolean, GLboolean, GLboolean);
 | GLvoid glCombinerParameterfNV(GLenum, GLfloat);
 | GLvoid glCombinerParameterfvNV(GLenum, GLfloat const *);
 | GLvoid glCombinerParameteriNV(GLenum, GLint);
 | GLvoid glCombinerParameterivNV(GLenum, GLint const *);
 | GLvoid glFinalCombinerInputNV(GLenum, GLenum, GLenum, GLenum);
 | GLvoid glGetCombinerInputParameterfvNV(GLenum, GLenum, GLenum, GLenum, GLfloat *);
 | GLvoid glGetCombinerInputParameterivNV(GLenum, GLenum, GLenum, GLenum, GLint *);
 | GLvoid glGetCombinerOutputParameterfvNV(GLenum, GLenum, GLenum, GLfloat *);
 | GLvoid glGetCombinerOutputParameterivNV(GLenum, GLenum, GLenum, GLint *);
 | GLvoid glGetFinalCombinerInputParameterfvNV(GLenum, GLenum, GLfloat *);
 | GLvoid glGetFinalCombinerInputParameterivNV(GLenum, GLenum, GLint *);
GL_NV_register_combiners2 1 definition, no type definitions, 2 functions
Definitions
 | GL_PER_STAGE_CONSTANTS_NV | 0x8535
Functions
 | GLvoid glCombinerStageParameterfvNV(GLenum, GLenum, GLfloat const *);
 | GLvoid glGetCombinerStageParameterfvNV(GLenum, GLenum, GLfloat *);
GL_NV_texgen_emboss 3 definitions, no type definitions, no functions
Definitions
 | GL_EMBOSS_LIGHT_NV | 0x855d
 | GL_EMBOSS_CONSTANT_NV | 0x855e
 | GL_EMBOSS_MAP_NV | 0x855f
GL_NV_texgen_reflection 2 definitions, no type definitions, no functions
Definitions
 | GL_NORMAL_MAP_NV | 0x8511
 | GL_REFLECTION_MAP_NV | 0x8512
GL_NV_texture_compression_vtc no definitions, no type definitions, no functions
GL_NV_texture_env_combine4 5 definitions, no type definitions, no functions
Definitions
 | GL_COMBINE4_NV | 0x8503
 | GL_SOURCE3_RGB_NV | 0x8583
 | GL_SOURCE3_ALPHA_NV | 0x858b
 | GL_OPERAND3_RGB_NV | 0x8593
 | GL_OPERAND3_ALPHA_NV | 0x859b
GL_NV_texture_expand_normal 1 definition, no type definitions, no functions
Definitions
GL_TEXTURE_UNSIGNED_REMAP_MODE_NV
0x888f
GL_NV_texture_rectangle 4 definitions, no type definitions, no functions
Definitions
 | GL_TEXTURE_RECTANGLE_NV | 0x84f5
 | GL_TEXTURE_BINDING_RECTANGLE_NV | 0x84f6
 | GL_PROXY_TEXTURE_RECTANGLE_NV | 0x84f7
 | GL_MAX_RECTANGLE_TEXTURE_SIZE_NV | 0x84f8
GL_NV_texture_shader 73 definitions, no type definitions, no functions
Definitions
 | GL_OFFSET_TEXTURE_RECTANGLE_NV | 0x864c
 | GL_OFFSET_TEXTURE_RECTANGLE_SCALE_NV | 0x864d
 | GL_DOT_PRODUCT_TEXTURE_RECTANGLE_NV | 0x864e
 | GL_RGBA_UNSIGNED_DOT_PRODUCT_MAPPING_NV | 0x86d9
 | GL_UNSIGNED_INT_S8_S8_8_8_NV | 0x86da
 | GL_UNSIGNED_INT_8_8_S8_S8_REV_NV | 0x86db
 | GL_DSDT_MAG_INTENSITY_NV | 0x86dc
 | GL_SHADER_CONSISTENT_NV | 0x86dd
 | GL_TEXTURE_SHADER_NV | 0x86de
 | GL_SHADER_OPERATION_NV | 0x86df
 | GL_CULL_MODES_NV | 0x86e0
 | GL_OFFSET_TEXTURE_MATRIX_NV | 0x86e1
 | GL_OFFSET_TEXTURE_2D_MATRIX_NV | 0x86e1
 | GL_OFFSET_TEXTURE_SCALE_NV | 0x86e2
 | GL_OFFSET_TEXTURE_2D_SCALE_NV | 0x86e2
 | GL_OFFSET_TEXTURE_BIAS_NV | 0x86e3
 | GL_OFFSET_TEXTURE_2D_BIAS_NV | 0x86e3
 | GL_PREVIOUS_TEXTURE_INPUT_NV | 0x86e4
 | GL_CONST_EYE_NV | 0x86e5
 | GL_PASS_THROUGH_NV | 0x86e6
 | GL_CULL_FRAGMENT_NV | 0x86e7
 | GL_OFFSET_TEXTURE_2D_NV | 0x86e8
 | GL_DEPENDENT_AR_TEXTURE_2D_NV | 0x86e9
 | GL_DEPENDENT_GB_TEXTURE_2D_NV | 0x86ea
 | GL_DOT_PRODUCT_NV | 0x86ec
 | GL_DOT_PRODUCT_DEPTH_REPLACE_NV | 0x86ed
 | GL_DOT_PRODUCT_TEXTURE_2D_NV | 0x86ee
 | GL_DOT_PRODUCT_TEXTURE_CUBE_MAP_NV | 0x86f0
 | GL_DOT_PRODUCT_DIFFUSE_CUBE_MAP_NV | 0x86f1
 | GL_DOT_PRODUCT_REFLECT_CUBE_MAP_NV | 0x86f2
 | GL_DOT_PRODUCT_CONST_EYE_REFLECT_CUBE_MAP_NV | 0x86f3
 | GL_HILO_NV | 0x86f4
 | GL_DSDT_NV | 0x86f5
 | GL_DSDT_MAG_NV | 0x86f6
 | GL_DSDT_MAG_VIB_NV | 0x86f7
 | GL_HILO16_NV | 0x86f8
 | GL_SIGNED_HILO_NV | 0x86f9
 | GL_SIGNED_HILO16_NV | 0x86fa
 | GL_SIGNED_RGBA_NV | 0x86fb
 | GL_SIGNED_RGBA8_NV | 0x86fc
 | GL_SIGNED_RGB_NV | 0x86fe
 | GL_SIGNED_RGB8_NV | 0x86ff
 | GL_SIGNED_LUMINANCE_NV | 0x8701
 | GL_SIGNED_LUMINANCE8_NV | 0x8702
 | GL_SIGNED_LUMINANCE_ALPHA_NV | 0x8703
 | GL_SIGNED_LUMINANCE8_ALPHA8_NV | 0x8704
 | GL_SIGNED_ALPHA_NV | 0x8705
 | GL_SIGNED_ALPHA8_NV | 0x8706
 | GL_SIGNED_INTENSITY_NV | 0x8707
 | GL_SIGNED_INTENSITY8_NV | 0x8708
 | GL_DSDT8_NV | 0x8709
 | GL_DSDT8_MAG8_NV | 0x870a
 | GL_DSDT8_MAG8_INTENSITY8_NV | 0x870b
 | GL_SIGNED_RGB_UNSIGNED_ALPHA_NV | 0x870c
 | GL_SIGNED_RGB8_UNSIGNED_ALPHA8_NV | 0x870d
 | GL_HI_SCALE_NV | 0x870e
 | GL_LO_SCALE_NV | 0x870f
 | GL_DS_SCALE_NV | 0x8710
 | GL_DT_SCALE_NV | 0x8711
 | GL_MAGNITUDE_SCALE_NV | 0x8712
 | GL_VIBRANCE_SCALE_NV | 0x8713
 | GL_HI_BIAS_NV | 0x8714
 | GL_LO_BIAS_NV | 0x8715
 | GL_DS_BIAS_NV | 0x8716
 | GL_DT_BIAS_NV | 0x8717
 | GL_MAGNITUDE_BIAS_NV | 0x8718
 | GL_VIBRANCE_BIAS_NV | 0x8719
 | GL_TEXTURE_BORDER_VALUES_NV | 0x871a
 | GL_TEXTURE_HI_SIZE_NV | 0x871b
 | GL_TEXTURE_LO_SIZE_NV | 0x871c
 | GL_TEXTURE_DS_SIZE_NV | 0x871d
 | GL_TEXTURE_DT_SIZE_NV | 0x871e
 | GL_TEXTURE_MAG_SIZE_NV | 0x871f
GL_NV_texture_shader2 1 definition, no type definitions, no functions
Definitions
GL_DOT_PRODUCT_TEXTURE_3D_NV
0x86ef
GL_NV_texture_shader3 17 definitions, no type definitions, no functions
Definitions
 | GL_OFFSET_PROJECTIVE_TEXTURE_2D_NV | 0x8850
 | GL_OFFSET_PROJECTIVE_TEXTURE_2D_SCALE_NV | 0x8851
 | GL_OFFSET_PROJECTIVE_TEXTURE_RECTANGLE_NV | 0x8852
 | GL_OFFSET_PROJECTIVE_TEXTURE_RECTANGLE_SCALE_NV | 0x8853
 | GL_OFFSET_HILO_TEXTURE_2D_NV | 0x8854
 | GL_OFFSET_HILO_TEXTURE_RECTANGLE_NV | 0x8855
 | GL_OFFSET_HILO_PROJECTIVE_TEXTURE_2D_NV | 0x8856
 | GL_OFFSET_HILO_PROJECTIVE_TEXTURE_RECTANGLE_NV | 0x8857
 | GL_DEPENDENT_HILO_TEXTURE_2D_NV | 0x8858
 | GL_DEPENDENT_RGB_TEXTURE_3D_NV | 0x8859
 | GL_DEPENDENT_RGB_TEXTURE_CUBE_MAP_NV | 0x885a
 | GL_DOT_PRODUCT_PASS_THROUGH_NV | 0x885b
 | GL_DOT_PRODUCT_TEXTURE_1D_NV | 0x885c
 | GL_DOT_PRODUCT_AFFINE_DEPTH_REPLACE_NV | 0x885d
 | GL_HILO8_NV | 0x885e
 | GL_SIGNED_HILO8_NV | 0x885f
 | GL_FORCE_BLUE_TO_ONE_NV | 0x8860
GL_NV_vertex_array_range 5 definitions, no type definitions, 2 functions
Definitions
 | GL_VERTEX_ARRAY_RANGE_NV | 0x851d
 | GL_VERTEX_ARRAY_RANGE_LENGTH_NV | 0x851e
 | GL_VERTEX_ARRAY_RANGE_VALID_NV | 0x851f
 | GL_MAX_VERTEX_ARRAY_RANGE_ELEMENT_NV | 0x8520
 | GL_VERTEX_ARRAY_RANGE_POINTER_NV | 0x8521
Functions
 | GLvoid glFlushVertexArrayRangeNV();
 | GLvoid glVertexArrayRangeNV(GLsizei, GLvoid const *);
GL_NV_vertex_array_range2 1 definition, no type definitions, no functions
Definitions
GL_VERTEX_ARRAY_RANGE_WITHOUT_FLUSH_NV
0x8533
GL_NV_vertex_program 83 definitions, no type definitions, 64 functions
Definitions
 | GL_VERTEX_PROGRAM_NV | 0x8620
 | GL_VERTEX_STATE_PROGRAM_NV | 0x8621
 | GL_ATTRIB_ARRAY_SIZE_NV | 0x8623
 | GL_ATTRIB_ARRAY_STRIDE_NV | 0x8624
 | GL_ATTRIB_ARRAY_TYPE_NV | 0x8625
 | GL_CURRENT_ATTRIB_NV | 0x8626
 | GL_PROGRAM_LENGTH_NV | 0x8627
 | GL_PROGRAM_STRING_NV | 0x8628
 | GL_MODELVIEW_PROJECTION_NV | 0x8629
 | GL_IDENTITY_NV | 0x862a
 | GL_INVERSE_NV | 0x862b
 | GL_TRANSPOSE_NV | 0x862c
 | GL_INVERSE_TRANSPOSE_NV | 0x862d
 | GL_MAX_TRACK_MATRIX_STACK_DEPTH_NV | 0x862e
 | GL_MAX_TRACK_MATRICES_NV | 0x862f
 | GL_MATRIX0_NV | 0x8630
 | GL_MATRIX1_NV | 0x8631
 | GL_MATRIX2_NV | 0x8632
 | GL_MATRIX3_NV | 0x8633
 | GL_MATRIX4_NV | 0x8634
 | GL_MATRIX5_NV | 0x8635
 | GL_MATRIX6_NV | 0x8636
 | GL_MATRIX7_NV | 0x8637
 | GL_CURRENT_MATRIX_STACK_DEPTH_NV | 0x8640
 | GL_CURRENT_MATRIX_NV | 0x8641
 | GL_VERTEX_PROGRAM_POINT_SIZE_NV | 0x8642
 | GL_VERTEX_PROGRAM_TWO_SIDE_NV | 0x8643
 | GL_PROGRAM_PARAMETER_NV | 0x8644
 | GL_ATTRIB_ARRAY_POINTER_NV | 0x8645
 | GL_PROGRAM_TARGET_NV | 0x8646
 | GL_PROGRAM_RESIDENT_NV | 0x8647
 | GL_TRACK_MATRIX_NV | 0x8648
 | GL_TRACK_MATRIX_TRANSFORM_NV | 0x8649
 | GL_VERTEX_PROGRAM_BINDING_NV | 0x864a
 | GL_PROGRAM_ERROR_POSITION_NV | 0x864b
 | GL_VERTEX_ATTRIB_ARRAY0_NV | 0x8650
 | GL_VERTEX_ATTRIB_ARRAY1_NV | 0x8651
 | GL_VERTEX_ATTRIB_ARRAY2_NV | 0x8652
 | GL_VERTEX_ATTRIB_ARRAY3_NV | 0x8653
 | GL_VERTEX_ATTRIB_ARRAY4_NV | 0x8654
 | GL_VERTEX_ATTRIB_ARRAY5_NV | 0x8655
 | GL_VERTEX_ATTRIB_ARRAY6_NV | 0x8656
 | GL_VERTEX_ATTRIB_ARRAY7_NV | 0x8657
 | GL_VERTEX_ATTRIB_ARRAY8_NV | 0x8658
 | GL_VERTEX_ATTRIB_ARRAY9_NV | 0x8659
 | GL_VERTEX_ATTRIB_ARRAY10_NV | 0x865a
 | GL_VERTEX_ATTRIB_ARRAY11_NV | 0x865b
 | GL_VERTEX_ATTRIB_ARRAY12_NV | 0x865c
 | GL_VERTEX_ATTRIB_ARRAY13_NV | 0x865d
 | GL_VERTEX_ATTRIB_ARRAY14_NV | 0x865e
 | GL_VERTEX_ATTRIB_ARRAY15_NV | 0x865f
 | GL_MAP1_VERTEX_ATTRIB0_4_NV | 0x8660
 | GL_MAP1_VERTEX_ATTRIB1_4_NV | 0x8661
 | GL_MAP1_VERTEX_ATTRIB2_4_NV | 0x8662
 | GL_MAP1_VERTEX_ATTRIB3_4_NV | 0x8663
 | GL_MAP1_VERTEX_ATTRIB4_4_NV | 0x8664
 | GL_MAP1_VERTEX_ATTRIB5_4_NV | 0x8665
 | GL_MAP1_VERTEX_ATTRIB6_4_NV | 0x8666
 | GL_MAP1_VERTEX_ATTRIB7_4_NV | 0x8667
 | GL_MAP1_VERTEX_ATTRIB8_4_NV | 0x8668
 | GL_MAP1_VERTEX_ATTRIB9_4_NV | 0x8669
 | GL_MAP1_VERTEX_ATTRIB10_4_NV | 0x866a
 | GL_MAP1_VERTEX_ATTRIB11_4_NV | 0x866b
 | GL_MAP1_VERTEX_ATTRIB12_4_NV | 0x866c
 | GL_MAP1_VERTEX_ATTRIB13_4_NV | 0x866d
 | GL_MAP1_VERTEX_ATTRIB14_4_NV | 0x866e
 | GL_MAP1_VERTEX_ATTRIB15_4_NV | 0x866f
 | GL_MAP2_VERTEX_ATTRIB0_4_NV | 0x8670
 | GL_MAP2_VERTEX_ATTRIB1_4_NV | 0x8671
 | GL_MAP2_VERTEX_ATTRIB2_4_NV | 0x8672
 | GL_MAP2_VERTEX_ATTRIB3_4_NV | 0x8673
 | GL_MAP2_VERTEX_ATTRIB4_4_NV | 0x8674
 | GL_MAP2_VERTEX_ATTRIB5_4_NV | 0x8675
 | GL_MAP2_VERTEX_ATTRIB6_4_NV | 0x8676
 | GL_MAP2_VERTEX_ATTRIB7_4_NV | 0x8677
 | GL_MAP2_VERTEX_ATTRIB8_4_NV | 0x8678
 | GL_MAP2_VERTEX_ATTRIB9_4_NV | 0x8679
 | GL_MAP2_VERTEX_ATTRIB10_4_NV | 0x867a
 | GL_MAP2_VERTEX_ATTRIB11_4_NV | 0x867b
 | GL_MAP2_VERTEX_ATTRIB12_4_NV | 0x867c
 | GL_MAP2_VERTEX_ATTRIB13_4_NV | 0x867d
 | GL_MAP2_VERTEX_ATTRIB14_4_NV | 0x867e
 | GL_MAP2_VERTEX_ATTRIB15_4_NV | 0x867f
Functions
 | GLboolean glAreProgramsResidentNV(GLsizei, GLuint const *, GLboolean *);
 | GLvoid glBindProgramNV(GLenum, GLuint);
 | GLvoid glDeleteProgramsNV(GLsizei, GLuint const *);
 | GLvoid glExecuteProgramNV(GLenum, GLuint, GLfloat const *);
 | GLvoid glGenProgramsNV(GLsizei, GLuint *);
 | GLvoid glGetProgramivNV(GLuint, GLenum, GLint *);
 | GLvoid glGetProgramParameterdvNV(GLenum, GLuint, GLenum, GLdouble *);
 | GLvoid glGetProgramParameterfvNV(GLenum, GLuint, GLenum, GLfloat *);
 | GLvoid glGetProgramStringNV(GLuint, GLenum, GLubyte *);
 | GLvoid glGetTrackMatrixivNV(GLenum, GLuint, GLenum, GLint *);
 | GLvoid glGetVertexAttribdvNV(GLuint, GLenum, GLdouble *);
 | GLvoid glGetVertexAttribfvNV(GLuint, GLenum, GLfloat *);
 | GLvoid glGetVertexAttribivNV(GLuint, GLenum, GLint *);
 | GLvoid glGetVertexAttribPointervNV(GLuint, GLenum, GLvoid * *);
 | GLboolean glIsProgramNV(GLuint);
 | GLvoid glLoadProgramNV(GLenum, GLuint, GLsizei, GLubyte const *);
 | GLvoid glProgramParameter4dNV(GLenum, GLuint, GLdouble, GLdouble, GLdouble, GLdouble);
 | GLvoid glProgramParameter4dvNV(GLenum, GLuint, GLdouble const *);
 | GLvoid glProgramParameter4fNV(GLenum, GLuint, GLfloat, GLfloat, GLfloat, GLfloat);
 | GLvoid glProgramParameter4fvNV(GLenum, GLuint, GLfloat const *);
 | GLvoid glProgramParameters4dvNV(GLenum, GLuint, GLuint, GLdouble const *);
 | GLvoid glProgramParameters4fvNV(GLenum, GLuint, GLuint, GLfloat const *);
 | GLvoid glRequestResidentProgramsNV(GLsizei, GLuint const *);
 | GLvoid glTrackMatrixNV(GLenum, GLuint, GLenum, GLenum);
 | GLvoid glVertexAttrib1dNV(GLuint, GLdouble);
 | GLvoid glVertexAttrib1dvNV(GLuint, GLdouble const *);
 | GLvoid glVertexAttrib1fNV(GLuint, GLfloat);
 | GLvoid glVertexAttrib1fvNV(GLuint, GLfloat const *);
 | GLvoid glVertexAttrib1sNV(GLuint, GLshort);
 | GLvoid glVertexAttrib1svNV(GLuint, GLshort const *);
 | GLvoid glVertexAttrib2dNV(GLuint, GLdouble, GLdouble);
 | GLvoid glVertexAttrib2dvNV(GLuint, GLdouble const *);
 | GLvoid glVertexAttrib2fNV(GLuint, GLfloat, GLfloat);
 | GLvoid glVertexAttrib2fvNV(GLuint, GLfloat const *);
 | GLvoid glVertexAttrib2sNV(GLuint, GLshort, GLshort);
 | GLvoid glVertexAttrib2svNV(GLuint, GLshort const *);
 | GLvoid glVertexAttrib3dNV(GLuint, GLdouble, GLdouble, GLdouble);
 | GLvoid glVertexAttrib3dvNV(GLuint, GLdouble const *);
 | GLvoid glVertexAttrib3fNV(GLuint, GLfloat, GLfloat, GLfloat);
 | GLvoid glVertexAttrib3fvNV(GLuint, GLfloat const *);
 | GLvoid glVertexAttrib3sNV(GLuint, GLshort, GLshort, GLshort);
 | GLvoid glVertexAttrib3svNV(GLuint, GLshort const *);
 | GLvoid glVertexAttrib4dNV(GLuint, GLdouble, GLdouble, GLdouble, GLdouble);
 | GLvoid glVertexAttrib4dvNV(GLuint, GLdouble const *);
 | GLvoid glVertexAttrib4fNV(GLuint, GLfloat, GLfloat, GLfloat, GLfloat);
 | GLvoid glVertexAttrib4fvNV(GLuint, GLfloat const *);
 | GLvoid glVertexAttrib4sNV(GLuint, GLshort, GLshort, GLshort, GLshort);
 | GLvoid glVertexAttrib4svNV(GLuint, GLshort const *);
 | GLvoid glVertexAttrib4ubNV(GLuint, GLubyte, GLubyte, GLubyte, GLubyte);
 | GLvoid glVertexAttrib4ubvNV(GLuint, GLubyte const *);
 | GLvoid glVertexAttribPointerNV(GLuint, GLint, GLenum, GLsizei, GLvoid const *);
 | GLvoid glVertexAttribs1dvNV(GLuint, GLsizei, GLdouble const *);
 | GLvoid glVertexAttribs1fvNV(GLuint, GLsizei, GLfloat const *);
 | GLvoid glVertexAttribs1svNV(GLuint, GLsizei, GLshort const *);
 | GLvoid glVertexAttribs2dvNV(GLuint, GLsizei, GLdouble const *);
 | GLvoid glVertexAttribs2fvNV(GLuint, GLsizei, GLfloat const *);
 | GLvoid glVertexAttribs2svNV(GLuint, GLsizei, GLshort const *);
 | GLvoid glVertexAttribs3dvNV(GLuint, GLsizei, GLdouble const *);
 | GLvoid glVertexAttribs3fvNV(GLuint, GLsizei, GLfloat const *);
 | GLvoid glVertexAttribs3svNV(GLuint, GLsizei, GLshort const *);
 | GLvoid glVertexAttribs4dvNV(GLuint, GLsizei, GLdouble const *);
 | GLvoid glVertexAttribs4fvNV(GLuint, GLsizei, GLfloat const *);
 | GLvoid glVertexAttribs4svNV(GLuint, GLsizei, GLshort const *);
 | GLvoid glVertexAttribs4ubvNV(GLuint, GLsizei, GLubyte const *);
GL_NV_vertex_program1_1 no definitions, no type definitions, no functions
GL_NV_vertex_program2 no definitions, no type definitions, no functions
GL_OML_interlace 2 definitions, no type definitions, no functions
Definitions
 | GL_INTERLACE_OML | 0x8980
 | GL_INTERLACE_READ_OML | 0x8981
GL_OML_resample 6 definitions, no type definitions, no functions
Definitions
 | GL_PACK_RESAMPLE_OML | 0x8984
 | GL_UNPACK_RESAMPLE_OML | 0x8985
 | GL_RESAMPLE_REPLICATE_OML | 0x8986
 | GL_RESAMPLE_ZERO_FILL_OML | 0x8987
 | GL_RESAMPLE_AVERAGE_OML | 0x8988
 | GL_RESAMPLE_DECIMATE_OML | 0x8989
GL_OML_subsample 2 definitions, no type definitions, no functions
Definitions
 | GL_FORMAT_SUBSAMPLE_24_24_OML | 0x8982
 | GL_FORMAT_SUBSAMPLE_244_244_OML | 0x8983
GL_PGI_misc_hints 20 definitions, no type definitions, 1 function
Definitions
 | GL_PREFER_DOUBLEBUFFER_HINT_PGI | 0x1a1f8
 | GL_CONSERVE_MEMORY_HINT_PGI | 0x1a1fd
 | GL_RECLAIM_MEMORY_HINT_PGI | 0x1a1fe
 | GL_NATIVE_GRAPHICS_HANDLE_PGI | 0x1a202
 | GL_NATIVE_GRAPHICS_BEGIN_HINT_PGI | 0x1a203
 | GL_NATIVE_GRAPHICS_END_HINT_PGI | 0x1a204
 | GL_ALWAYS_FAST_HINT_PGI | 0x1a20c
 | GL_ALWAYS_SOFT_HINT_PGI | 0x1a20d
 | GL_ALLOW_DRAW_OBJ_HINT_PGI | 0x1a20e
 | GL_ALLOW_DRAW_WIN_HINT_PGI | 0x1a20f
 | GL_ALLOW_DRAW_FRG_HINT_PGI | 0x1a210
 | GL_ALLOW_DRAW_MEM_HINT_PGI | 0x1a211
 | GL_STRICT_DEPTHFUNC_HINT_PGI | 0x1a216
 | GL_STRICT_LIGHTING_HINT_PGI | 0x1a217
 | GL_STRICT_SCISSOR_HINT_PGI | 0x1a218
 | GL_FULL_STIPPLE_HINT_PGI | 0x1a219
 | GL_CLIP_NEAR_HINT_PGI | 0x1a220
 | GL_CLIP_FAR_HINT_PGI | 0x1a221
 | GL_WIDE_LINE_HINT_PGI | 0x1a222
 | GL_BACK_NORMALS_HINT_PGI | 0x1a223
Functions
 | GLvoid glHintPGI(GLenum, GLint);
GL_PGI_vertex_hints 22 definitions, no type definitions, no functions
Definitions
 | GL_VERTEX23_BIT_PGI | 0x4
 | GL_VERTEX4_BIT_PGI | 0x8
 | GL_COLOR3_BIT_PGI | 0x10000
 | GL_VERTEX_DATA_HINT_PGI | 0x1a22a
 | GL_VERTEX_CONSISTENT_HINT_PGI | 0x1a22b
 | GL_MATERIAL_SIDE_HINT_PGI | 0x1a22c
 | GL_MAX_VERTEX_HINT_PGI | 0x1a22d
 | GL_COLOR4_BIT_PGI | 0x20000
 | GL_EDGEFLAG_BIT_PGI | 0x40000
 | GL_INDEX_BIT_PGI | 0x80000
 | GL_MAT_AMBIENT_BIT_PGI | 0x100000
 | GL_MAT_AMBIENT_AND_DIFFUSE_BIT_PGI | 0x200000
 | GL_MAT_DIFFUSE_BIT_PGI | 0x400000
 | GL_MAT_EMISSION_BIT_PGI | 0x800000
 | GL_MAT_COLOR_INDEXES_BIT_PGI | 0x1000000
 | GL_MAT_SHININESS_BIT_PGI | 0x2000000
 | GL_MAT_SPECULAR_BIT_PGI | 0x4000000
 | GL_NORMAL_BIT_PGI | 0x8000000
 | GL_TEXCOORD1_BIT_PGI | 0x10000000
 | GL_TEXCOORD2_BIT_PGI | 0x20000000
 | GL_TEXCOORD3_BIT_PGI | 0x40000000
 | GL_TEXCOORD4_BIT_PGI | 0x7fffffff
GL_REND_screen_coordinates 2 definitions, no type definitions, no functions
Definitions
 | GL_SCREEN_COORDINATES_REND | 0x8490
 | GL_INVERTED_SCREEN_W_REND | 0x8491
GL_S3_s3tc 4 definitions, no type definitions, no functions
Definitions
 | GL_RGB_S3TC | 0x83a0
 | GL_RGB4_S3TC | 0x83a1
 | GL_RGBA_S3TC | 0x83a2
 | GL_RGBA4_S3TC | 0x83a3
GL_SGIS_detail_texture 8 definitions, no type definitions, 2 functions
Definitions
 | GL_DETAIL_TEXTURE_2D_SGIS | 0x8095
 | GL_DETAIL_TEXTURE_2D_BINDING_SGIS | 0x8096
 | GL_LINEAR_DETAIL_SGIS | 0x8097
 | GL_LINEAR_DETAIL_ALPHA_SGIS | 0x8098
 | GL_LINEAR_DETAIL_COLOR_SGIS | 0x8099
 | GL_DETAIL_TEXTURE_LEVEL_SGIS | 0x809a
 | GL_DETAIL_TEXTURE_MODE_SGIS | 0x809b
 | GL_DETAIL_TEXTURE_FUNC_POINTS_SGIS | 0x809c
Functions
 | GLvoid glDetailTexFuncSGIS(GLenum, GLsizei, GLfloat const *);
 | GLvoid glGetDetailTexFuncSGIS(GLenum, GLfloat *);
GL_SGIS_fog_function 3 definitions, no type definitions, 2 functions
Definitions
 | GL_FOG_FUNC_SGIS | 0x812a
 | GL_FOG_FUNC_POINTS_SGIS | 0x812b
 | GL_MAX_FOG_FUNC_POINTS_SGIS | 0x812c
Functions
 | GLvoid glFogFuncSGIS(GLsizei, GLfloat const *);
 | GLvoid glGetFogFuncSGIS(GLfloat *);
GL_SGIS_generate_mipmap 2 definitions, no type definitions, no functions
Definitions
 | GL_GENERATE_MIPMAP_SGIS | 0x8191
 | GL_GENERATE_MIPMAP_HINT_SGIS | 0x8192
GL_SGIS_multisample 16 definitions, no type definitions, 2 functions
Definitions
 | GL_MULTISAMPLE_SGIS | 0x809d
 | GL_SAMPLE_ALPHA_TO_MASK_SGIS | 0x809e
 | GL_SAMPLE_ALPHA_TO_ONE_SGIS | 0x809f
 | GL_SAMPLE_MASK_SGIS | 0x80a0
 | GL_1PASS_SGIS | 0x80a1
 | GL_2PASS_0_SGIS | 0x80a2
 | GL_2PASS_1_SGIS | 0x80a3
 | GL_4PASS_0_SGIS | 0x80a4
 | GL_4PASS_1_SGIS | 0x80a5
 | GL_4PASS_2_SGIS | 0x80a6
 | GL_4PASS_3_SGIS | 0x80a7
 | GL_SAMPLE_BUFFERS_SGIS | 0x80a8
 | GL_SAMPLES_SGIS | 0x80a9
 | GL_SAMPLE_MASK_VALUE_SGIS | 0x80aa
 | GL_SAMPLE_MASK_INVERT_SGIS | 0x80ab
 | GL_SAMPLE_PATTERN_SGIS | 0x80ac
Functions
 | GLvoid glSampleMaskSGIS(GLclampf, GLboolean);
 | GLvoid glSamplePatternSGIS(GLenum);
GL_SGIS_multitexture 10 definitions, no type definitions, 36 functions
Definitions
 | GL_SELECTED_TEXTURE_SGIS | 0x83c0
 | GL_SELECTED_TEXTURE_COORD_SET_SGIS | 0x83c1
 | GL_SELECTED_TEXTURE_TRANSFORM_SGIS | 0x83c2
 | GL_MAX_TEXTURES_SGIS | 0x83c3
 | GL_MAX_TEXTURE_COORD_SETS_SGIS | 0x83c4
 | GL_TEXTURE_ENV_COORD_SET_SGIS | 0x83c5
 | GL_TEXTURE0_SGIS | 0x83c6
 | GL_TEXTURE1_SGIS | 0x83c7
 | GL_TEXTURE2_SGIS | 0x83c8
 | GL_TEXTURE3_SGIS | 0x83c9
Functions
 | GLvoid glInterleavedTextureCoordSetsSGIS(GLint);
 | GLvoid glMultiTexCoord1dSGIS(GLenum, GLdouble);
 | GLvoid glMultiTexCoord1dvSGIS(GLenum, GLdouble const *);
 | GLvoid glMultiTexCoord1fSGIS(GLenum, GLfloat);
 | GLvoid glMultiTexCoord1fvSGIS(GLenum, GLfloat const *);
 | GLvoid glMultiTexCoord1iSGIS(GLenum, GLint);
 | GLvoid glMultiTexCoord1ivSGIS(GLenum, GLint const *);
 | GLvoid glMultiTexCoord1sSGIS(GLenum, GLshort);
 | GLvoid glMultiTexCoord1svSGIS(GLenum, GLshort const *);
 | GLvoid glMultiTexCoord2dSGIS(GLenum, GLdouble, GLdouble);
 | GLvoid glMultiTexCoord2dvSGIS(GLenum, GLdouble const *);
 | GLvoid glMultiTexCoord2fSGIS(GLenum, GLfloat, GLfloat);
 | GLvoid glMultiTexCoord2fvSGIS(GLenum, GLfloat const *);
 | GLvoid glMultiTexCoord2iSGIS(GLenum, GLint, GLint);
 | GLvoid glMultiTexCoord2ivSGIS(GLenum, GLint const *);
 | GLvoid glMultiTexCoord2sSGIS(GLenum, GLshort, GLshort);
 | GLvoid glMultiTexCoord2svSGIS(GLenum, GLshort const *);
 | GLvoid glMultiTexCoord3dSGIS(GLenum, GLdouble, GLdouble, GLdouble);
 | GLvoid glMultiTexCoord3dvSGIS(GLenum, GLdouble const *);
 | GLvoid glMultiTexCoord3fSGIS(GLenum, GLfloat, GLfloat, GLfloat);
 | GLvoid glMultiTexCoord3fvSGIS(GLenum, GLfloat const *);
 | GLvoid glMultiTexCoord3iSGIS(GLenum, GLint, GLint, GLint);
 | GLvoid glMultiTexCoord3ivSGIS(GLenum, GLint const *);
 | GLvoid glMultiTexCoord3sSGIS(GLenum, GLshort, GLshort, GLshort);
 | GLvoid glMultiTexCoord3svSGIS(GLenum, GLshort const *);
 | GLvoid glMultiTexCoord4dSGIS(GLenum, GLdouble, GLdouble, GLdouble, GLdouble);
 | GLvoid glMultiTexCoord4dvSGIS(GLenum, GLdouble const *);
 | GLvoid glMultiTexCoord4fSGIS(GLenum, GLfloat, GLfloat, GLfloat, GLfloat);
 | GLvoid glMultiTexCoord4fvSGIS(GLenum, GLfloat const *);
 | GLvoid glMultiTexCoord4iSGIS(GLenum, GLint, GLint, GLint, GLint);
 | GLvoid glMultiTexCoord4ivSGIS(GLenum, GLint const *);
 | GLvoid glMultiTexCoord4sSGIS(GLenum, GLshort, GLshort, GLshort, GLshort);
 | GLvoid glMultiTexCoord4svSGIS(GLenum, GLshort const *);
 | GLvoid glSelectTextureCoordSetSGIS(GLenum);
 | GLvoid glSelectTextureSGIS(GLenum);
 | GLvoid glSelectTextureTransformSGIS(GLenum);
GL_SGIS_pixel_texture 4 definitions, no type definitions, 6 functions
Definitions
 | GL_PIXEL_TEXTURE_SGIS | 0x8353
 | GL_PIXEL_FRAGMENT_RGB_SOURCE_SGIS | 0x8354
 | GL_PIXEL_FRAGMENT_ALPHA_SOURCE_SGIS | 0x8355
 | GL_PIXEL_GROUP_COLOR_SGIS | 0x8356
Functions
 | GLvoid glGetPixelTexGenParameterfvSGIS(GLenum, GLfloat *);
 | GLvoid glGetPixelTexGenParameterivSGIS(GLenum, GLint *);
 | GLvoid glPixelTexGenParameterfSGIS(GLenum, GLfloat);
 | GLvoid glPixelTexGenParameterfvSGIS(GLenum, GLfloat const *);
 | GLvoid glPixelTexGenParameteriSGIS(GLenum, GLint);
 | GLvoid glPixelTexGenParameterivSGIS(GLenum, GLint const *);
GL_SGIS_point_line_texgen 8 definitions, no type definitions, no functions
Definitions
 | GL_EYE_DISTANCE_TO_POINT_SGIS | 0x81f0
 | GL_OBJECT_DISTANCE_TO_POINT_SGIS | 0x81f1
 | GL_EYE_DISTANCE_TO_LINE_SGIS | 0x81f2
 | GL_OBJECT_DISTANCE_TO_LINE_SGIS | 0x81f3
 | GL_EYE_POINT_SGIS | 0x81f4
 | GL_OBJECT_POINT_SGIS | 0x81f5
 | GL_EYE_LINE_SGIS | 0x81f6
 | GL_OBJECT_LINE_SGIS | 0x81f7
GL_SGIS_point_parameters 4 definitions, no type definitions, 2 functions
Definitions
 | GL_POINT_SIZE_MIN_SGIS | 0x8126
 | GL_POINT_SIZE_MAX_SGIS | 0x8127
 | GL_POINT_FADE_THRESHOLD_SIZE_SGIS | 0x8128
 | GL_DISTANCE_ATTENUATION_SGIS | 0x8129
Functions
 | GLvoid glPointParameterfSGIS(GLenum, GLfloat);
 | GLvoid glPointParameterfvSGIS(GLenum, GLfloat const *);
GL_SGIS_sharpen_texture 4 definitions, no type definitions, 2 functions
Definitions
 | GL_LINEAR_SHARPEN_SGIS | 0x80ad
 | GL_LINEAR_SHARPEN_ALPHA_SGIS | 0x80ae
 | GL_LINEAR_SHARPEN_COLOR_SGIS | 0x80af
 | GL_SHARPEN_TEXTURE_FUNC_POINTS_SGIS | 0x80b0
Functions
 | GLvoid glGetSharpenTexFuncSGIS(GLenum, GLfloat *);
 | GLvoid glSharpenTexFuncSGIS(GLenum, GLsizei, GLfloat const *);
GL_SGIS_texture4D 10 definitions, no type definitions, 2 functions
Definitions
 | GL_PACK_SKIP_VOLUMES_SGIS | 0x8130
 | GL_PACK_IMAGE_DEPTH_SGIS | 0x8131
 | GL_UNPACK_SKIP_VOLUMES_SGIS | 0x8132
 | GL_UNPACK_IMAGE_DEPTH_SGIS | 0x8133
 | GL_TEXTURE_4D_SGIS | 0x8134
 | GL_PROXY_TEXTURE_4D_SGIS | 0x8135
 | GL_TEXTURE_4DSIZE_SGIS | 0x8136
 | GL_TEXTURE_WRAP_Q_SGIS | 0x8137
 | GL_MAX_4D_TEXTURE_SIZE_SGIS | 0x8138
 | GL_TEXTURE_4D_BINDING_SGIS | 0x814f
Functions
 | GLvoid glTexImage4DSGIS(GLenum, GLint, GLenum, GLsizei, GLsizei, GLsizei, GLsizei, GLint, GLenum, GLenum, GLvoid const *);
 | GLvoid glTexSubImage4DSGIS(GLenum, GLint, GLint, GLint, GLint, GLint, GLsizei, GLsizei, GLsizei, GLsizei, GLenum, GLenum, GLvoid const *);
GL_SGIS_texture_border_clamp no definitions, no type definitions, no functions
GL_SGIS_texture_color_mask 1 definition, no type definitions, 1 function
Definitions
 | GL_TEXTURE_COLOR_WRITEMASK_SGIS | 0x81ef
Functions
 | GLvoid glTextureColorMaskSGIS(GLboolean, GLboolean, GLboolean, GLboolean);
GL_SGIS_texture_edge_clamp 1 definition, no type definitions, no functions
Definitions
GL_CLAMP_TO_EDGE_SGIS
0x812f
GL_SGIS_texture_filter4 2 definitions, no type definitions, 2 functions
Definitions
 | GL_FILTER4_SGIS | 0x8146
 | GL_TEXTURE_FILTER4_SIZE_SGIS | 0x8147
Functions
 | GLvoid glGetTexFilterFuncSGIS(GLenum, GLenum, GLfloat *);
 | GLvoid glTexFilterFuncSGIS(GLenum, GLenum, GLsizei, GLfloat const *);
GL_SGIS_texture_lod 4 definitions, no type definitions, no functions
Definitions
 | GL_TEXTURE_MIN_LOD_SGIS | 0x813a
 | GL_TEXTURE_MAX_LOD_SGIS | 0x813b
 | GL_TEXTURE_BASE_LEVEL_SGIS | 0x813c
 | GL_TEXTURE_MAX_LEVEL_SGIS | 0x813d
GL_SGIX_async 1 definition, no type definitions, 6 functions
Definitions
 | GL_ASYNC_MARKER_SGIX | 0x8329
Functions
 | GLvoid glAsyncMarkerSGIX(GLuint);
 | GLvoid glDeleteAsyncMarkersSGIX(GLuint, GLsizei);
 | GLint glFinishAsyncSGIX(GLuint *);
 | GLuint glGenAsyncMarkersSGIX(GLsizei);
 | GLboolean glIsAsyncMarkerSGIX(GLuint);
 | GLint glPollAsyncSGIX(GLuint *);
GL_SGIX_async_histogram 2 definitions, no type definitions, no functions
Definitions
 | GL_ASYNC_HISTOGRAM_SGIX | 0x832c
 | GL_MAX_ASYNC_HISTOGRAM_SGIX | 0x832d
GL_SGIX_async_pixel 6 definitions, no type definitions, no functions
Definitions
 | GL_ASYNC_TEX_IMAGE_SGIX | 0x835c
 | GL_ASYNC_DRAW_PIXELS_SGIX | 0x835d
 | GL_ASYNC_READ_PIXELS_SGIX | 0x835e
 | GL_MAX_ASYNC_TEX_IMAGE_SGIX | 0x835f
 | GL_MAX_ASYNC_DRAW_PIXELS_SGIX | 0x8360
 | GL_MAX_ASYNC_READ_PIXELS_SGIX | 0x8361
GL_SGIX_blend_alpha_minmax 2 definitions, no type definitions, no functions
Definitions
 | GL_ALPHA_MIN_SGIX | 0x8320
 | GL_ALPHA_MAX_SGIX | 0x8321
GL_SGIX_calligraphic_fragment 1 definition, no type definitions, no functions
Definitions
GL_CALLIGRAPHIC_FRAGMENT_SGIX
0x8183
GL_SGIX_clipmap 12 definitions, no type definitions, no functions
Definitions
 | GL_LINEAR_CLIPMAP_LINEAR_SGIX | 0x8170
 | GL_TEXTURE_CLIPMAP_CENTER_SGIX | 0x8171
 | GL_TEXTURE_CLIPMAP_FRAME_SGIX | 0x8172
 | GL_TEXTURE_CLIPMAP_OFFSET_SGIX | 0x8173
 | GL_TEXTURE_CLIPMAP_VIRTUAL_DEPTH_SGIX | 0x8174
 | GL_TEXTURE_CLIPMAP_LOD_OFFSET_SGIX | 0x8175
 | GL_TEXTURE_CLIPMAP_DEPTH_SGIX | 0x8176
 | GL_MAX_CLIPMAP_DEPTH_SGIX | 0x8177
 | GL_MAX_CLIPMAP_VIRTUAL_DEPTH_SGIX | 0x8178
 | GL_NEAREST_CLIPMAP_NEAREST_SGIX | 0x844d
 | GL_NEAREST_CLIPMAP_LINEAR_SGIX | 0x844e
 | GL_LINEAR_CLIPMAP_NEAREST_SGIX | 0x844f
GL_SGIX_convolution_accuracy 1 definition, no type definitions, no functions
Definitions
GL_CONVOLUTION_HINT_SGIX
0x8316
GL_SGIX_depth_pass_instrument no definitions, no type definitions, no functions
GL_SGIX_depth_texture 3 definitions, no type definitions, no functions
Definitions
 | GL_DEPTH_COMPONENT16_SGIX | 0x81a5
 | GL_DEPTH_COMPONENT24_SGIX | 0x81a6
 | GL_DEPTH_COMPONENT32_SGIX | 0x81a7
GL_SGIX_flush_raster no definitions, no type definitions, 1 function
Functions
GLvoid glFlushRasterSGIX();
GL_SGIX_fog_offset 2 definitions, no type definitions, no functions
Definitions
 | GL_FOG_OFFSET_SGIX | 0x8198
 | GL_FOG_OFFSET_VALUE_SGIX | 0x8199
GL_SGIX_fog_scale 2 definitions, no type definitions, no functions
Definitions
 | GL_FOG_SCALE_SGIX | 0x81fc
 | GL_FOG_SCALE_VALUE_SGIX | 0x81fd
GL_SGIX_fragment_lighting 20 definitions, no type definitions, 18 functions
Definitions
 | GL_FRAGMENT_LIGHTING_SGIX | 0x8400
 | GL_FRAGMENT_COLOR_MATERIAL_SGIX | 0x8401
 | GL_FRAGMENT_COLOR_MATERIAL_FACE_SGIX | 0x8402
 | GL_FRAGMENT_COLOR_MATERIAL_PARAMETER_SGIX | 0x8403
 | GL_MAX_FRAGMENT_LIGHTS_SGIX | 0x8404
 | GL_MAX_ACTIVE_LIGHTS_SGIX | 0x8405
 | GL_CURRENT_RASTER_NORMAL_SGIX | 0x8406
 | GL_LIGHT_ENV_MODE_SGIX | 0x8407
 | GL_FRAGMENT_LIGHT_MODEL_LOCAL_VIEWER_SGIX | 0x8408
 | GL_FRAGMENT_LIGHT_MODEL_TWO_SIDE_SGIX | 0x8409
 | GL_FRAGMENT_LIGHT_MODEL_AMBIENT_SGIX | 0x840a
 | GL_FRAGMENT_LIGHT_MODEL_NORMAL_INTERPOLATION_SGIX | 0x840b
 | GL_FRAGMENT_LIGHT0_SGIX | 0x840c
 | GL_FRAGMENT_LIGHT1_SGIX | 0x840d
 | GL_FRAGMENT_LIGHT2_SGIX | 0x840e
 | GL_FRAGMENT_LIGHT3_SGIX | 0x840f
 | GL_FRAGMENT_LIGHT4_SGIX | 0x8410
 | GL_FRAGMENT_LIGHT5_SGIX | 0x8411
 | GL_FRAGMENT_LIGHT6_SGIX | 0x8412
 | GL_FRAGMENT_LIGHT7_SGIX | 0x8413
Functions
 | GLvoid glFragmentColorMaterialSGIX(GLenum, GLenum);
 | GLvoid glFragmentLightfSGIX(GLenum, GLenum, GLfloat);
 | GLvoid glFragmentLightfvSGIX(GLenum, GLenum, GLfloat const *);
 | GLvoid glFragmentLightiSGIX(GLenum, GLenum, GLint);
 | GLvoid glFragmentLightivSGIX(GLenum, GLenum, GLint const *);
 | GLvoid glFragmentLightModelfSGIX(GLenum, GLfloat);
 | GLvoid glFragmentLightModelfvSGIX(GLenum, GLfloat const *);
 | GLvoid glFragmentLightModeliSGIX(GLenum, GLint);
 | GLvoid glFragmentLightModelivSGIX(GLenum, GLint const *);
 | GLvoid glFragmentMaterialfSGIX(GLenum, GLenum, GLfloat);
 | GLvoid glFragmentMaterialfvSGIX(GLenum, GLenum, GLfloat const *);
 | GLvoid glFragmentMaterialiSGIX(GLenum, GLenum, GLint);
 | GLvoid glFragmentMaterialivSGIX(GLenum, GLenum, GLint const *);
 | GLvoid glGetFragmentLightfvSGIX(GLenum, GLenum, GLfloat *);
 | GLvoid glGetFragmentLightivSGIX(GLenum, GLenum, GLint *);
 | GLvoid glGetFragmentMaterialfvSGIX(GLenum, GLenum, GLfloat *);
 | GLvoid glGetFragmentMaterialivSGIX(GLenum, GLenum, GLint *);
 | GLvoid glLightEnviSGIX(GLenum, GLint);
GL_SGIX_framezoom 3 definitions, no type definitions, 1 function
Definitions
 | GL_FRAMEZOOM_SGIX | 0x818b
 | GL_FRAMEZOOM_FACTOR_SGIX | 0x818c
 | GL_MAX_FRAMEZOOM_FACTOR_SGIX | 0x818d
Functions
 | GLvoid glFrameZoomSGIX(GLint);
GL_SGIX_igloo_interface no definitions, no type definitions, 1 function
Functions
GLvoid glIglooInterfaceSGIX(GLenum, GLvoid const *);
GL_SGIX_instruments 2 definitions, no type definitions, 6 functions
Definitions
 | GL_INSTRUMENT_BUFFER_POINTER_SGIX | 0x8180
 | GL_INSTRUMENT_MEASUREMENTS_SGIX | 0x8181
Functions
 | GLint glGetInstrumentsSGIX();
 | GLvoid glInstrumentsBufferSGIX(GLsizei, GLint *);
 | GLint glPollInstrumentsSGIX(GLint *);
 | GLvoid glReadInstrumentsSGIX(GLint);
 | GLvoid glStartInstrumentsSGIX();
 | GLvoid glStopInstrumentsSGIX(GLint);
GL_SGIX_interlace 1 definition, no type definitions, no functions
Definitions
GL_INTERLACE_SGIX
0x8094
GL_SGIX_ir_instrument1 1 definition, no type definitions, no functions
Definitions
GL_IR_INSTRUMENT1_SGIX
0x817f
GL_SGIX_list_priority 1 definition, no type definitions, 6 functions
Definitions
 | GL_LIST_PRIORITY_SGIX | 0x8182
Functions
 | GLvoid glGetListParameterfvSGIX(GLuint, GLenum, GLfloat *);
 | GLvoid glGetListParameterivSGIX(GLuint, GLenum, GLint *);
 | GLvoid glListParameterfSGIX(GLuint, GLenum, GLfloat);
 | GLvoid glListParameterfvSGIX(GLuint, GLenum, GLfloat const *);
 | GLvoid glListParameteriSGIX(GLuint, GLenum, GLint);
 | GLvoid glListParameterivSGIX(GLuint, GLenum, GLint const *);
GL_SGIX_pixel_texture 2 definitions, no type definitions, 1 function
Definitions
 | GL_PIXEL_TEX_GEN_SGIX | 0x8139
 | GL_PIXEL_TEX_GEN_MODE_SGIX | 0x832b
Functions
 | GLvoid glPixelTexGenSGIX(GLenum);
GL_SGIX_pixel_tiles 8 definitions, no type definitions, no functions
Definitions
 | GL_PIXEL_TILE_BEST_ALIGNMENT_SGIX | 0x813e
 | GL_PIXEL_TILE_CACHE_INCREMENT_SGIX | 0x813f
 | GL_PIXEL_TILE_WIDTH_SGIX | 0x8140
 | GL_PIXEL_TILE_HEIGHT_SGIX | 0x8141
 | GL_PIXEL_TILE_GRID_WIDTH_SGIX | 0x8142
 | GL_PIXEL_TILE_GRID_HEIGHT_SGIX | 0x8143
 | GL_PIXEL_TILE_GRID_DEPTH_SGIX | 0x8144
 | GL_PIXEL_TILE_CACHE_SIZE_SGIX | 0x8145
GL_SGIX_polynomial_ffd 4 definitions, no type definitions, 4 functions
Definitions
 | GL_GEOMETRY_DEFORMATION_SGIX | 0x8194
 | GL_TEXTURE_DEFORMATION_SGIX | 0x8195
 | GL_DEFORMATIONS_MASK_SGIX | 0x8196
 | GL_MAX_DEFORMATION_ORDER_SGIX | 0x8197
Functions
 | GLvoid glDeformationMap3dSGIX(GLenum, GLdouble, GLdouble, GLint, GLint, GLdouble, GLdouble, GLint, GLint, GLdouble, GLdouble, GLint, GLint, GLdouble const *);
 | GLvoid glDeformationMap3fSGIX(GLenum, GLfloat, GLfloat, GLint, GLint, GLfloat, GLfloat, GLint, GLint, GLfloat, GLfloat, GLint, GLint, GLfloat const *);
 | GLvoid glDeformSGIX(GLbitfield);
 | GLvoid glLoadIdentityDeformationMapSGIX(GLbitfield);
GL_SGIX_reference_plane 2 definitions, no type definitions, 1 function
Definitions
 | GL_REFERENCE_PLANE_SGIX | 0x817d
 | GL_REFERENCE_PLANE_EQUATION_SGIX | 0x817e
Functions
 | GLvoid glReferencePlaneSGIX(GLdouble const *);
GL_SGIX_resample 5 definitions, no type definitions, no functions
Definitions
 | GL_PACK_RESAMPLE_SGIX | 0x842c
 | GL_UNPACK_RESAMPLE_SGIX | 0x842d
 | GL_RESAMPLE_REPLICATE_SGIX | 0x842e
 | GL_RESAMPLE_ZERO_FILL_SGIX | 0x842f
 | GL_RESAMPLE_DECIMATE_SGIX | 0x8430
GL_SGIX_scalebias_hint 1 definition, no type definitions, no functions
Definitions
GL_SCALEBIAS_HINT_SGIX
0x8322
GL_SGIX_shadow 4 definitions, no type definitions, no functions
Definitions
 | GL_TEXTURE_COMPARE_SGIX | 0x819a
 | GL_TEXTURE_COMPARE_OPERATOR_SGIX | 0x819b
 | GL_TEXTURE_LEQUAL_R_SGIX | 0x819c
 | GL_TEXTURE_GEQUAL_R_SGIX | 0x819d
GL_SGIX_shadow_ambient 1 definition, no type definitions, no functions
Definitions
GL_SHADOW_AMBIENT_SGIX
0x80bf
GL_SGIX_sprite 7 definitions, no type definitions, 4 functions
Definitions
 | GL_SPRITE_SGIX | 0x8148
 | GL_SPRITE_MODE_SGIX | 0x8149
 | GL_SPRITE_AXIS_SGIX | 0x814a
 | GL_SPRITE_TRANSLATION_SGIX | 0x814b
 | GL_SPRITE_AXIAL_SGIX | 0x814c
 | GL_SPRITE_OBJECT_ALIGNED_SGIX | 0x814d
 | GL_SPRITE_EYE_ALIGNED_SGIX | 0x814e
Functions
 | GLvoid glSpriteParameterfSGIX(GLenum, GLfloat);
 | GLvoid glSpriteParameterfvSGIX(GLenum, GLfloat const *);
 | GLvoid glSpriteParameteriSGIX(GLenum, GLint);
 | GLvoid glSpriteParameterivSGIX(GLenum, GLint const *);
GL_SGIX_subsample 5 definitions, no type definitions, no functions
Definitions
 | GL_PACK_SUBSAMPLE_RATE_SGIX | 0x85a0
 | GL_UNPACK_SUBSAMPLE_RATE_SGIX | 0x85a1
 | GL_PIXEL_SUBSAMPLE_4444_SGIX | 0x85a2
 | GL_PIXEL_SUBSAMPLE_2424_SGIX | 0x85a3
 | GL_PIXEL_SUBSAMPLE_4242_SGIX | 0x85a4
GL_SGIX_tag_sample_buffer no definitions, no type definitions, 1 function
Functions
GLvoid glTagSampleBufferSGIX();
GL_SGIX_texture_add_env 1 definition, no type definitions, no functions
Definitions
GL_TEXTURE_ENV_BIAS_SGIX
0x80be
GL_SGIX_texture_coordinate_clamp 3 definitions, no type definitions, no functions
Definitions
 | GL_TEXTURE_MAX_CLAMP_S_SGIX | 0x8369
 | GL_TEXTURE_MAX_CLAMP_T_SGIX | 0x836a
 | GL_TEXTURE_MAX_CLAMP_R_SGIX | 0x836b
GL_SGIX_texture_lod_bias 3 definitions, no type definitions, no functions
Definitions
 | GL_TEXTURE_LOD_BIAS_S_SGIX | 0x818e
 | GL_TEXTURE_LOD_BIAS_T_SGIX | 0x818f
 | GL_TEXTURE_LOD_BIAS_R_SGIX | 0x8190
GL_SGIX_texture_multi_buffer 1 definition, no type definitions, no functions
Definitions
GL_TEXTURE_MULTI_BUFFER_HINT_SGIX
0x812e
GL_SGIX_texture_scale_bias 4 definitions, no type definitions, no functions
Definitions
 | GL_POST_TEXTURE_FILTER_BIAS_SGIX | 0x8179
 | GL_POST_TEXTURE_FILTER_SCALE_SGIX | 0x817a
 | GL_POST_TEXTURE_FILTER_BIAS_RANGE_SGIX | 0x817b
 | GL_POST_TEXTURE_FILTER_SCALE_RANGE_SGIX | 0x817c
GL_SGIX_texture_select no definitions, no type definitions, no functions
GL_SGIX_vertex_preclip 2 definitions, no type definitions, no functions
Definitions
 | GL_VERTEX_PRECLIP_SGIX | 0x83ee
 | GL_VERTEX_PRECLIP_HINT_SGIX | 0x83ef
GL_SGIX_ycrcb 2 definitions, no type definitions, no functions
Definitions
 | GL_YCRCB_422_SGIX | 0x81bb
 | GL_YCRCB_444_SGIX | 0x81bc
GL_SGIX_ycrcba 2 definitions, no type definitions, no functions
Definitions
 | GL_YCRCB_SGIX | 0x8318
 | GL_YCRCBA_SGIX | 0x8319
GL_SGIX_ycrcb_subsample no definitions, no type definitions, no functions
GL_SGI_color_matrix 11 definitions, no type definitions, no functions
Definitions
 | GL_COLOR_MATRIX_SGI | 0x80b1
 | GL_COLOR_MATRIX_STACK_DEPTH_SGI | 0x80b2
 | GL_MAX_COLOR_MATRIX_STACK_DEPTH_SGI | 0x80b3
 | GL_POST_COLOR_MATRIX_RED_SCALE_SGI | 0x80b4
 | GL_POST_COLOR_MATRIX_GREEN_SCALE_SGI | 0x80b5
 | GL_POST_COLOR_MATRIX_BLUE_SCALE_SGI | 0x80b6
 | GL_POST_COLOR_MATRIX_ALPHA_SCALE_SGI | 0x80b7
 | GL_POST_COLOR_MATRIX_RED_BIAS_SGI | 0x80b8
 | GL_POST_COLOR_MATRIX_GREEN_BIAS_SGI | 0x80b9
 | GL_POST_COLOR_MATRIX_BLUE_BIAS_SGI | 0x80ba
 | GL_POST_COLOR_MATRIX_ALPHA_BIAS_SGI | 0x80bb
GL_SGI_color_table 16 definitions, no type definitions, 7 functions
Definitions
 | GL_COLOR_TABLE_SGI | 0x80d0
 | GL_POST_CONVOLUTION_COLOR_TABLE_SGI | 0x80d1
 | GL_POST_COLOR_MATRIX_COLOR_TABLE_SGI | 0x80d2
 | GL_PROXY_COLOR_TABLE_SGI | 0x80d3
 | GL_PROXY_POST_CONVOLUTION_COLOR_TABLE_SGI | 0x80d4
 | GL_PROXY_POST_COLOR_MATRIX_COLOR_TABLE_SGI | 0x80d5
 | GL_COLOR_TABLE_SCALE_SGI | 0x80d6
 | GL_COLOR_TABLE_BIAS_SGI | 0x80d7
 | GL_COLOR_TABLE_FORMAT_SGI | 0x80d8
 | GL_COLOR_TABLE_WIDTH_SGI | 0x80d9
 | GL_COLOR_TABLE_RED_SIZE_SGI | 0x80da
 | GL_COLOR_TABLE_GREEN_SIZE_SGI | 0x80db
 | GL_COLOR_TABLE_BLUE_SIZE_SGI | 0x80dc
 | GL_COLOR_TABLE_ALPHA_SIZE_SGI | 0x80dd
 | GL_COLOR_TABLE_LUMINANCE_SIZE_SGI | 0x80de
 | GL_COLOR_TABLE_INTENSITY_SIZE_SGI | 0x80df
Functions
 | GLvoid glColorTableParameterfvSGI(GLenum, GLenum, GLfloat const *);
 | GLvoid glColorTableParameterivSGI(GLenum, GLenum, GLint const *);
 | GLvoid glColorTableSGI(GLenum, GLenum, GLsizei, GLenum, GLenum, GLvoid const *);
 | GLvoid glCopyColorTableSGI(GLenum, GLenum, GLint, GLint, GLsizei);
 | GLvoid glGetColorTableParameterfvSGI(GLenum, GLenum, GLfloat *);
 | GLvoid glGetColorTableParameterivSGI(GLenum, GLenum, GLint *);
 | GLvoid glGetColorTableSGI(GLenum, GLenum, GLenum, GLvoid *);
GL_SGI_texture_color_table 2 definitions, no type definitions, no functions
Definitions
 | GL_TEXTURE_COLOR_TABLE_SGI | 0x80bc
 | GL_PROXY_TEXTURE_COLOR_TABLE_SGI | 0x80bd
GL_SUNX_constant_data 2 definitions, no type definitions, 1 function
Definitions
 | GL_UNPACK_CONSTANT_DATA_SUNX | 0x81d5
 | GL_TEXTURE_CONSTANT_DATA_SUNX | 0x81d6
Functions
 | GLvoid glFinishTextureSUNX();
GL_SUN_convolution_border_modes 1 definition, no type definitions, no functions
Definitions
GL_WRAP_BORDER_SUN
0x81d4
GL_SUN_global_alpha 2 definitions, no type definitions, 8 functions
Definitions
 | GL_GLOBAL_ALPHA_SUN | 0x81d9
 | GL_GLOBAL_ALPHA_FACTOR_SUN | 0x81da
Functions
 | GLvoid glGlobalAlphaFactorbSUN(GLbyte);
 | GLvoid glGlobalAlphaFactordSUN(GLdouble);
 | GLvoid glGlobalAlphaFactorfSUN(GLfloat);
 | GLvoid glGlobalAlphaFactoriSUN(GLint);
 | GLvoid glGlobalAlphaFactorsSUN(GLshort);
 | GLvoid glGlobalAlphaFactorubSUN(GLubyte);
 | GLvoid glGlobalAlphaFactoruiSUN(GLuint);
 | GLvoid glGlobalAlphaFactorusSUN(GLushort);
GL_SUN_mesh_array 2 definitions, no type definitions, 1 function
Definitions
 | GL_QUAD_MESH_SUN | 0x8614
 | GL_TRIANGLE_MESH_SUN | 0x8615
Functions
 | GLvoid glDrawMeshArraysSUN(GLenum, GLint, GLsizei, GLsizei);
GL_SUN_slice_accum 1 definition, no type definitions, no functions
Definitions
GL_SLICE_ACCUM_SUN
0x85cc
GL_SUN_triangle_list 17 definitions, no type definitions, 7 functions
Definitions
 | GL_RESTART_SUN | 0x1
 | GL_REPLACE_MIDDLE_SUN | 0x2
 | GL_REPLACE_OLDEST_SUN | 0x3
 | GL_TRIANGLE_LIST_SUN | 0x81d7
 | GL_REPLACEMENT_CODE_SUN | 0x81d8
 | GL_REPLACEMENT_CODE_ARRAY_SUN | 0x85c0
 | GL_REPLACEMENT_CODE_ARRAY_TYPE_SUN | 0x85c1
 | GL_REPLACEMENT_CODE_ARRAY_STRIDE_SUN | 0x85c2
 | GL_REPLACEMENT_CODE_ARRAY_POINTER_SUN | 0x85c3
 | GL_R1UI_V3F_SUN | 0x85c4
 | GL_R1UI_C4UB_V3F_SUN | 0x85c5
 | GL_R1UI_C3F_V3F_SUN | 0x85c6
 | GL_R1UI_N3F_V3F_SUN | 0x85c7
 | GL_R1UI_C4F_N3F_V3F_SUN | 0x85c8
 | GL_R1UI_T2F_V3F_SUN | 0x85c9
 | GL_R1UI_T2F_N3F_V3F_SUN | 0x85ca
 | GL_R1UI_T2F_C4F_N3F_V3F_SUN | 0x85cb
Functions
 | GLvoid glReplacementCodePointerSUN(GLenum, GLsizei, GLvoid const * *);
 | GLvoid glReplacementCodeubSUN(GLubyte);
 | GLvoid glReplacementCodeubvSUN(GLubyte const *);
 | GLvoid glReplacementCodeuiSUN(GLuint);
 | GLvoid glReplacementCodeuivSUN(GLuint const *);
 | GLvoid glReplacementCodeusSUN(GLushort);
 | GLvoid glReplacementCodeusvSUN(GLushort const *);
GL_SUN_vertex no definitions, no type definitions, 40 functions
Functions
GLvoid glColor3fVertex3fSUN(GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat);
GLvoid glColor3fVertex3fvSUN(GLfloat const *, GLfloat const *);
GLvoid glColor4fNormal3fVertex3fSUN(GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat);
GLvoid glColor4fNormal3fVertex3fvSUN(GLfloat const *, GLfloat const *, GLfloat const *);
GLvoid glColor4ubVertex2fSUN(GLubyte, GLubyte, GLubyte, GLubyte, GLfloat, GLfloat);
GLvoid glColor4ubVertex2fvSUN(GLubyte const *, GLfloat const *);
GLvoid glColor4ubVertex3fSUN(GLubyte, GLubyte, GLubyte, GLubyte, GLfloat, GLfloat, GLfloat);
GLvoid glColor4ubVertex3fvSUN(GLubyte const *, GLfloat const *);
GLvoid glNormal3fVertex3fSUN(GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat);
GLvoid glNormal3fVertex3fvSUN(GLfloat const *, GLfloat const *);
GLvoid glReplacementCodeuiColor3fVertex3fSUN(GLuint, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat);
GLvoid glReplacementCodeuiColor3fVertex3fvSUN(GLuint const *, GLfloat const *, GLfloat const *);
GLvoid glReplacementCodeuiColor4fNormal3fVertex3fSUN(GLuint, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat);
GLvoid glReplacementCodeuiColor4fNormal3fVertex3fvSUN(GLuint const *, GLfloat const *, GLfloat const *, GLfloat const *);
GLvoid glReplacementCodeuiColor4ubVertex3fSUN(GLuint, GLubyte, GLubyte, GLubyte, GLubyte, GLfloat, GLfloat, GLfloat);
GLvoid glReplacementCodeuiColor4ubVertex3fvSUN(GLuint const *, GLubyte const *, GLfloat const *);
GLvoid glReplacementCodeuiNormal3fVertex3fSUN(GLuint, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat);
GLvoid glReplacementCodeuiNormal3fVertex3fvSUN(GLuint const *, GLfloat const *, GLfloat const *);
GLvoid glReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fSUN(GLuint, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat);
GLvoid glReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fvSUN(GLuint const *, GLfloat const *, GLfloat const *, GLfloat const *, GLfloat const *);
GLvoid glReplacementCodeuiTexCoord2fNormal3fVertex3fSUN(GLuint, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat);
GLvoid glReplacementCodeuiTexCoord2fNormal3fVertex3fvSUN(GLuint const *, GLfloat const *, GLfloat const *, GLfloat const *);
GLvoid glReplacementCodeuiTexCoord2fVertex3fSUN(GLuint, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat);
GLvoid glReplacementCodeuiTexCoord2fVertex3fvSUN(GLuint const *, GLfloat const *, GLfloat const *);
GLvoid glReplacementCodeuiVertex3fSUN(GLuint, GLfloat, GLfloat, GLfloat);
GLvoid glReplacementCodeuiVertex3fvSUN(GLuint const *, GLfloat const *);
GLvoid glTexCoord2fColor3fVertex3fSUN(GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat);
GLvoid glTexCoord2fColor3fVertex3fvSUN(GLfloat const *, GLfloat const *, GLfloat const *);
GLvoid glTexCoord2fColor4fNormal3fVertex3fSUN(GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat);
GLvoid glTexCoord2fColor4fNormal3fVertex3fvSUN(GLfloat const *, GLfloat const *, GLfloat const *, GLfloat const *);
GLvoid glTexCoord2fColor4ubVertex3fSUN(GLfloat, GLfloat, GLubyte, GLubyte, GLubyte, GLubyte, GLfloat, GLfloat, GLfloat);
GLvoid glTexCoord2fColor4ubVertex3fvSUN(GLfloat const *, GLubyte const *, GLfloat const *);
GLvoid glTexCoord2fNormal3fVertex3fSUN(GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat);
GLvoid glTexCoord2fNormal3fVertex3fvSUN(GLfloat const *, GLfloat const *, GLfloat const *);
GLvoid glTexCoord2fVertex3fSUN(GLfloat, GLfloat, GLfloat, GLfloat, GLfloat);
GLvoid glTexCoord2fVertex3fvSUN(GLfloat const *, GLfloat const *);
GLvoid glTexCoord4fColor4fNormal3fVertex4fSUN(GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat);
GLvoid glTexCoord4fColor4fNormal3fVertex4fvSUN(GLfloat const *, GLfloat const *, GLfloat const *, GLfloat const *);
GLvoid glTexCoord4fVertex4fSUN(GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat);
GLvoid glTexCoord4fVertex4fvSUN(GLfloat const *, GLfloat const *);


OpenGL 튜토리얼<기초편>

OpenGL 튜토리얼 < 기초편 > - #1

OpenGL 윈도우 프레임웍 만들기 1

by 김성철.EDin ( paranwave@korea.com )

OpenGL 을 배워보는 첫 걸음으로 앞으로 사용하게 될 간단한 윈도우 프레임웤을 만들어 보자. 이 프레임웤의 목적은 윈도우 생성 및 OpenGL 의 설정 그리고 메세지 처리 등의 귀찮은 점을 미리 만들어 놓거나 사용하기 쉽게 하는데 있다. 여기에 소개되는 프레임웤은 초기 버전이므로 앞으로 충분히 수정이 가해질 것이고 간단한 OpenGL 엔진을 작성하는데 사용될 수 있도록 수렴해 가는데 목적이 있다. 물론 여러분들이 자신의 입맛에 맞게 뜯어 고치는 일도 가능하다 ;]

우선은 OpenGL 에 대한 설정은 빼두고 윈도우 생성 과정을 만들어 보자. 우리가 만들 프레임웤이 만들어지면 아래와 같이 사용된다.

#include "eglWindow.h"

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam,
  int nCmdShow)
{
eglWindow app;
app.Create(hInstance);
return app.Run();
}

코드를 살펴보면 메세지 프로시져(거의 모든 책에서 WndProc 으로 쓰는 콜백 함수 ;] )가 빠져있음을 볼 수 있다. 이 것이 돌아갈 수 있을까? MFC 가 사용하는 메세지맵 매크로 시스템을 이용하면 가능하다. 즉 모든 메세지들마다 일일이 switch - case 문을 작성하지도 않고 메세지맵 매크로 시스템처럼 OnMessage() 의 형식으로 만들 수 있다. 하지만 우리는 MFC 를 사용하지는 않을 것이다. 직접 그 메세지맵 매크로 시스템을 만들어 볼 것이다. 물론 지금은 처음 부분이므로 그 단계는 잠시 뒤로 미루어 두고 위의 eglWindow 클래스가 어떻게 구성되어 있는지 살펴보자. 물론 첫 단계이므로 WndProc 가 필요하다 :]

다음은 eglWindow 클래스의 코드이다. 초기단계이므로 많은 멤버 함수들이 없다. 당분간은 그럴 것이다. 그 것이 핵심 부분만 이해하는데 더욱 도움이 될 것이다.

class eglWindow
{
private:     static BOOL RegisterWindow(HINSTANCE hInstance);
private:     static LRESULT CALLBACK WndProc(HWND hWnd,
  UINT iMessage, WPARAM wParam, LPARAM lParam);
protected:
HWND mHWnd;
MSG mMsg;
HINSTANCE mHInstance;
public:
eglWindow();
~eglWindow();
BOOL Create(HINSTANCE hInstance, std::string WindowTitle="EDin
  OpenGL Framework", BOOL bFullScreen=FALSE);
virtual int Run(void);
};

eglWindow 의 멤버함수 하나하나씩 살펴보자.
멤버변수로는 인스턴스의 핸들로 HINSTANCE mHInstance 가 있고, 윈도우 핸들인 HWND mHWnd 그리고 메세지를 받아올 때 필요한 MSG mMsg 가 있다. 이 변수들은 모두 protected: 영역 안의 멤버 변수들로서 eglWindow 가 상속에 사용 될 때 자식 클래스에서 이 멤버 변수들을 사용할 수가 있다. 주목해야할 함수로는 두개의 static 멤버함수이다. static 멤버함수의 성격은 어떠한가? 클래스가 생성되기 전에 프로그램 실행시 우선 실행이 되며 이 static 멤버함수의 안에서는 this 포인터를 사용할 수가 없다. 이는 매우 중요한 성격이다. 꼭 알아두어야하며 이렇게 만드는 이유는 메세지맵 매크로 시스템을 만들면서 알게 될 것이다. 그리고 위의 두개의 static 멤버함수는 private 영역의 함수로 인스턴스를 사용해서 외부에서 접근할 수 없다. 그리고 상속에 사용되도 자식클래스에서 위의 두 static 멤버 함수의 코드는 사용할 수가 없다. 왜 그럴까? 그 이유도 뒤에서 알게될 것이다 ;]

eglWindow::eglWindow() : mHWnd(NULL), mHInstance(NULL)
{
}

위의 함수는 eglWindow 의 생성자로 멤버변수들을 초기화 해주고 있다. 다음은 Create 함수를 살펴보자.

BOOL eglWindow::Create(HINSTANCE hInstance, std::string WindowTitle,
  BOOL bFullScreen)
{
if(!eglWindow::RegisterWindow(hInstance))
return FALSE;

mHInstance = hInstance;
mHWnd = CreateWindowEx(
0,
"EdineglWindowClass",
WindowTitle.c_str(),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
(HWND)NULL,
(HMENU)NULL,
hInstance,
NULL);

if(!mHWnd)
{
return FALSE;
}

ShowWindow(mHWnd, SW_SHOW);
UpdateWindow(mHWnd);
return TRUE;
}

위의 코드를 보면 내용은 API 를 잘 아는 사람들은 알겠지만 단순히 윈도우즈를 생성해 주는 내용이다. 하지만 주목할 점은 아직 설명하지 않은 RegisterWindow() 함수를 호출한다는 것이다. static 멤버 함수의 호출형식은 네임스페이스::함수이름 이 된다. 자세한 것은 C++ 문법책을 보도록하자. 그럼 RegisterWindow 함수는 어떻게 구성되어 있을까?

BOOL eglWindow::RegisterWindow(HINSTANCE hInstance)
{
WNDCLASSEX wnd;
wnd.cbSize = sizeof(WNDCLASSEX);
wnd.cbWndExtra = 0;
wnd.cbClsExtra = 0;
wnd.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
wnd.hCursor = LoadCursor(NULL, IDC_ARROW);
wnd.hIcon = LoadIcon(NULL, IDI_WINLOGO);
wnd.hIconSm = LoadIcon(NULL, IDI_WINLOGO);
wnd.hInstance = hInstance;
wnd.lpfnWndProc = eglWindow::WndProc;
wnd.lpszClassName = "EdinCGLWindowClass";
wnd.lpszMenuName = NULL;
wnd.style = CS_HREDRAW | CS_VREDRAW;

if(!::RegisterClassEx(&wnd))
return FALSE;
return TRUE;
}

위 함수의 내용은 우리가 사용할 윈도우클래스를 시스템에 등록하는 것이다. 하지만 왜 이 함수를 static 함수로 만들어야 하는 것일까? 대답은 간단하다. 멤버함수를 호출하려면 어떻게 해야하는가? 잠시만 생각해보자. 멤버함수를 호출하려면 반드시 해당 클래스의 인스턴스를 만들어야만 멤버 함수를 호출할 수 있다. 그리고 명시적으로 호출을 해줘야한다. 즉 우리가 만든 어떤 클래스의 인스턴스가 app 일 때 app.RegisterWindow(..) 를 꼭 해줘야하는 것이다. 하지만 우리에게 필요한 RegisterWindow 함수는 호출되건 말건 상관없는 함수가 아니라 반드시 호출되어야할 함수이다. 그렇다면 프로그래머가 호출에 대해 걱정하지 않아도 자동으로 호출 되게끔 할 수는 없을까? 답은 바로 static 멤버 함수를 사용하는 것이다. 그렇다면 인스턴스를 만들어서 명시적으로 호출하지 않아도 된다. 시스템이 프로그램 실행시 자동으로 호출해 준다. 따라서 위의 static RegisterWindow 멤버 함수는 자동으로 호출되어 우리가 사용할 윈도우클래스를 시스템에 등록해준다. 위에서 굵은 글씨로 적혀 있는 코드는 WndProc 를 한 클래스의 멤버함수로 지정한다. 하지만 WndProc 도 static 함수이다. 그 이유는 RegisterWindow 함수가 자동으로 실행될 때의 상황은 eglWindow 의 인스턴스가 메모리에 만들어진 상태가 아니기 때문에 static 멤버 함수인 RegisterWindow 에서 일반 WndProc 멤버 함수를 호출할 수 없다. 따라서 이를 해결하기 위해서는 WndProc 도 static 멤버 함수로 만들어 프로그램 실행시 메모리에 코드를 올려 놓도록 한다. 그러면 당연히 RegisterWindow 함수에서 WndProc 함수의 포인터(코드가 올려져 있는 메모리 주소)를 얻을 수 있다.

eglWindow 의 static 멤버 함수 WndProc 의 코드는 아래와 같다.

LRESULT CALLBACK eglWindow::WndProc(HWND hWnd, UINT iMessage,
  WPARAM wParam, LPARAM lParam)
{
switch(iMessage)
{
case WM_LBUTTONDOWN:
MessageBox(hWnd, TEXT("EDin
  OpenGL Framework 0.1"), TEXT("HELLO:)"), MB_OK);
break;
case WM_SIZE:
MessageBeep(-1);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return DefWindowProc(hWnd, iMessage, wParam, lParam);
}

Run 의 멤버 함수는 단순히 메세지 펌핑을 하는 코드이다.

int eglWindow::Run(void)
{
while(GetMessage(&mMsg, NULL, 0, 0))
{
TranslateMessage(&mMsg);
DispatchMessage(&mMsg);
}
return mMsg.message;
}

위의 코드를 보았을 때 어떤 윈도우 메세지의 핸들링을 하려할 때 매번 eglWindow::WndProc 의 코드를 고쳐야 한다. 그리고 코드가 길어지면 길어 질 수록 WndProc 의 코드를 읽기(분석)가 힘들어 질 것이다. 이럴 때 MFC 의 메세지 맵 같은 시스템을 사용할 수 있으면 얼마나 편할까? 지금부터 그 메세지맵 매크로 시스템을 만들어 보자. 잠시 눈을 돌려 MFC 의 클래스 코드를 보면 다음과 같은 형태로 메세지 매크로가 쓰이는 것을 볼 수 있다.

class CMyDialog : public CDialog
{
.....
.....
DECLARE_MESSAGE_MAP()
};

BEGIN_MESSAGE_MAP(CMyDialog, CDialog)
ON_MESSAGE(...)
END_MESSAGE_MAP();

위의 DECLARE_MESSAGE_MAP, BEGIN_MESSAGE_MAP, END_MESSAGE_MAP 등은 위에서 우리가 작성했던 코드들을 매크로로 써주는 역할만을 할 뿐이다. DECLARE_MESSAGE_MAP 은 메세지 매크로 시스템에 필요한 함수들과 타입들을 선언하는 역할을 하고 BEGIN_MESSAGE_MAP, ON_MESSAGE, END_MESSAGE_MAP 은 DECLARE_MESSAGE_MAP 가 선언한 함수를 구현하는 역할을 한다. 그럼 각각의 매크로가 어떻게 구성되는지를 살펴보자.

아래의 코드는 DECLARE_MESSAGE_MAP 의 코드이다.

#define DECLARE_MESSAGE_MAP(CLASSNAME) \
typedef void (CLASSNAME::*CLASSNAME##FuncPtr)(WPARAM, LPARAM);\
typedef struct _MESSAGEMAP\
{\
UINT iMsg;\
CLASSNAME##FuncPtr fp;\
}MESSAGEMAP;\
private: static BOOL RegisterWindow(HINSTANCE hInstance);\
private: static LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam);\
private: static MESSAGEMAP MessageMap[];\
public:

코드가 좀 복잡해 보일지도 모르겠다. 매크로함수와 ## 연산자 등은 문법책을 참고하길 바란다. 연결연산자를 제거하고 CLASSNAME 을 eglWindow 의 코드로 바꾸어 살펴보자.

#define DECLARE_MESSAGE_MAP(eglWindow)
typedef void (eglWindow::*eglWindowFuncPtr)(WPARAM,
  LPARAM);
typedef struct _MESSAGEMAP
{
UINT iMsg;
eglWindowFuncPtr fp;
}MESSAGEMAP;
private: static BOOL RegisterWindow(HINSTANCE hInstance);
private: static LRESULT CALLBACK WndProc(HWND hWnd,
  UINT iMessage, WPARAM wParam, LPARAM lParam);
private: static MESSAGEMAP MessageMap[];
public:

함수포인터의 타입 정의와 MESSAGEMAP 구조체를 제외하면 위에서 보았던 클래스의 일부분임을 금새 눈치챘을 것이다. typedef void (eglWindow::*eglWindowFuncPtr)(WPARAM, LPARAM); 로 된 타입 정의문은 eglWindow 의 멤버함수 중 리턴값은 void 이고 인자는 WPARAM 과 LPARAM 을 받는 함수를 가리키는 포인터 타입을 정의한 것이다. 그리고 MESSAGEMAP 구조체는 메세지와 관련된 함수를 연결해 놓는 자료형으로 BEGIN_MESSAGE_MAP 과 ON_MESSAGE 로 초기화 된다. 이 예는 매크로의 CLASSNAME 이 eglWindow 우 일 때의 경우를 보여주는 것이다. 다른 클래스에서 필요하다면 CLASSNAME 에 그 클래스의 이름을 써 주면 위의 자료형이 선언될 것이다. 여기서 RegisterWindow 와 WndProc 멤버 함수들이 private 영역에 포함된 것은 메세지 처리가 필요한 모든 클래스에서는 각각의 RegisterWindow 와 WndProc 들이 필요하기 때문이다. 상속에서도 이는 마찬가지인데 WndProc 만 코드를 공유할 수 없지만 메세지 핸들링 함수는 코드를 공유할 수가 있다. 물론 공유하지 않을 수도 있다. 뒷부분에서 상속에서 사용하는 예제를 보이겠다.

다음은 BEGIN_MESSAGE_MAP 과 ON_MESSAGE , END_MESSAGE_MAP 을 살펴보자.

#define BEGIN_MESSAGE_MAP(CLASSNAME) \
BOOL CLASSNAME::RegisterWindow(HINSTANCE hInstance)\
{\
WNDCLASSEX wnd;\
wnd.cbSize = sizeof(WNDCLASSEX);\
wnd.cbWndExtra = 0;\
wnd.cbClsExtra = 0;\
wnd.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);\
wnd.hCursor = LoadCursor(NULL, IDC_ARROW);\
wnd.hIcon = LoadIcon(NULL, IDI_WINLOGO);\
wnd.hIconSm = LoadIcon(NULL, IDI_WINLOGO);\
wnd.hInstance = hInstance;\
wnd.lpfnWndProc = CLASSNAME::WndProc;\
wnd.lpszClassName = "EdineglWindowClass";\
wnd.lpszMenuName = NULL;\
wnd.style = CS_HREDRAW | CS_VREDRAW;\
if(!::RegisterClassEx(&wnd))\
return FALSE;\
return TRUE;\
}\
LRESULT CALLBACK CLASSNAME::WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)\
{\
int i=0;\
static CLASSNAME ido;
while(MessageMap[i].iMsg != 0)\
{\
if(iMessage == MessageMap[i].iMsg)\
{\
(ido.*(MessageMap[i].fp))(wParam, lParam);\
}\
++i;\
}\
return DefWindowProc(hWnd, iMessage, wParam, lParam);\
}\
CLASSNAME::MESSAGEMAP CLASSNAME::MessageMap[] = {

아래는 코드의 이해를 돕기 위해서 CLASSNAME 을 eglWindow 로 바꾼 것이다.

#define BEGIN_MESSAGE_MAP(eglWindow)
BOOL eglWindow::RegisterWindow(HINSTANCE hInstance)
{
WNDCLASSEX wnd;
wnd.cbSize = sizeof(WNDCLASSEX);
wnd.cbWndExtra = 0;
wnd.cbClsExtra = 0;
wnd.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
wnd.hCursor = LoadCursor(NULL,
  IDC_ARROW);
wnd.hIcon = LoadIcon(NULL, IDI_WINLOGO);
wnd.hIconSm = LoadIcon(NULL,
  IDI_WINLOGO);
wnd.hInstance = hInstance;
wnd.lpfnWndProc = eglWindow::WndProc;
wnd.lpszClassName = "EdineglWindowClass";
wnd.lpszMenuName = NULL;
wnd.style = CS_HREDRAW | CS_VREDRAW;
if(!::RegisterClassEx(&wnd))
return
  FALSE;
return TRUE;
}
LRESULT CALLBACK eglWindow::WndProc(HWND hWnd, UINT
  iMessage, WPARAM wParam, LPARAM lParam)
{
int i=0;
static eglWindow ido;
while(MessageMap[i].iMsg != 0)
{
if(iMessage
  == MessageMap[i].iMsg)
{
(ido.*(MessageMap[i].fp))(wParam,
  lParam);
}
++i;
}
return DefWindowProc(hWnd, iMessage,
  wParam, lParam);
}
eglWindow::MESSAGEMAP eglWindow::MessageMap[] = {

위의 내용은 BEGIN_MESSAGE_MAP 매크로의 내용이다. 그냥 RegisterWindow 와 WndProc 의 함수 정의만 있을 뿐이며 맨 끝줄에 eglWindow::MESSAGEMAP eglWindow::MessageMap[] = { 로 MessageMap[] 구조체 배열의 초기화를 하는 부분이 있다. 초기화 부분은 뒤에서 보면 알겠지만 ON_MESSAGE 가 처리한다. 그런데 코드를 보면 이상한 부분이 있다. 어디일까? 굵게 쓰인 코드가 보일 것이다. 위의 코드는 무엇이 잘 못되었을까? 그것은 static eglWindow 의 인스턴스 ido 와 우리가 프로그램에서 사용하는 인스턴스(this) 는 전혀 다른 객체라는 것이다. 즉 함수의 코드는 공유할 수 있되 멤버 변수는 공유를 할 수 없다. 메세지 처리 등을 해보면 별 문제 없이 작동하지만 변수조작등을 가하면 반드시 벌레가 생길 것이다. 그렇다면 어떻게 해야하는가? (ido.*(MessageMap[i].fp))(wParam, lParam); 에서 ido 를 WndProc 를 호출하는, 즉 this 포인터로 바꾸어 호출해야 한다. (this->*(MessageMap[i].fp))(wParam, lParam); 으로 바꾸어야한다. (이 코드는 함수포인터를 실행하는 코드이다. 함수포인터에 대한 내용은 문법책을 참고하길 바란다) 하지만 WndProc 는 무슨 함수인가? 바로 static 함수이다! static 함수는 위에서 말했듯이 this 포인터를 사용할 수 없다. 그렇다면 두 눈 시퍼렇게 뜨고 벌레가 생기는 걸 보고 있어야만 할까? 당연히 아니다! 해결법이 있다. this 포인터를 사용할 수 있는 방법이 있다. 해결법은 WNDCLASSEX 구조체에 숨어 있다. 바로 여분의 데이타를 저장할 수 있도록 한 cbWndExtra 와 cbClsExtra 를 사용하면 된다. 둘 다 사용하는 것이 아니라 여기서는 cbWndExtra 를 사용하기로 하겠다. 아래는 이를 적용한 코드로 굵게 쓰여진 코드를 주의 깊에 살펴 보자.

 BOOL eglWindow::RegisterWindow(HINSTANCE hInstance)
{
WNDCLASSEX wnd;
wnd.cbSize = sizeof(WNDCLASSEX);
wnd.cbWndExtra = 4;
wnd.cbClsExtra = 0;
wnd.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
wnd.hCursor = LoadCursor(NULL,
  IDC_ARROW);
wnd.hIcon = LoadIcon(NULL, IDI_WINLOGO);
wnd.hIconSm = LoadIcon(NULL,
  IDI_WINLOGO);
wnd.hInstance = hInstance;
wnd.lpfnWndProc = eglWindow::WndProc;
wnd.lpszClassName = "EdineglWindowClass";
wnd.lpszMenuName = NULL;
wnd.style = CS_HREDRAW | CS_VREDRAW;
if(!::RegisterClassEx(&wnd))
return
  FALSE;
return TRUE;
}

BOOL eglWindow::Create(HINSTANCE hInstance, std::string WindowTitle,
  BOOL bFullScreen)
{
if(!eglWindow::RegisterWindow(hInstance))
return FALSE;
mHInstance = hInstance;
mHWnd = CreateWindowEx(
0,
"EdineglWindowClass",
WindowTitle.c_str(),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
(HWND)NULL,
(HMENU)NULL,
hInstance,
(LPVOID)this);
  //메세지맵을 위하여 this 포인터를 넘긴다.
if(!mHWnd)
{
return
  FALSE;
}
ShowWindow(mHWnd, SW_SHOW);
UpdateWindow(mHWnd);
return TRUE;
}

LRESULT CALLBACK eglWindow::WndProc(HWND hWnd, UINT iMessage,
  WPARAM wParam, LPARAM lParam)
{
int i=0;
static eglWindow *ido = (eglWindow *)GetWindowLong(hWnd,
  GWL_USERDATA);
if(ido == NULL && iMessage != WM_CREATE)
{
return DefWindowProc(hWnd, iMessage,
  wParam, lParam);
}
if(iMessage == WM_CREATE)
{
ido = (eglWindow *)(((LPCREATESTRUCT)lParam)->lpCreateParams);
}
while(MessageMap[i].iMsg != 0)
{
if(iMessage == MessageMap[i].iMsg)
{
(ido->*(MessageMap[i].fp))(wParam,
  lParam);
}
++i;
}
return DefWindowProc(hWnd, iMessage, wParam, lParam);
}

위의 코드를 보면 RegisterWindow 함수에서 WNDCLASSEX 의 구조체 멤버 변수 중 cbWndExtra 에 4 의 값을 주는데 이 것은 포인터가 4 바이트이기 때문이다. 그리고 이렇게 할당된 공간에 데이타를 넣는 것은 CreateWindowEx 함수에서 하는데 맨 끝에 (LPVOID)this 를 넘겨 줌으로써 this 포인터를 저장하게 된다. 이렇게 저장된 값은 윈도우 프로시져 WndProc 에서 WM_CREATE 메세지가 전달 될 때 lParam 으로 넘어온다. 정확하게 말하자면 lParam 은 CREATESTRUCT 의 포인터를 가지고 있고 CREATESTRUCT 구조체의 멤버 변수중 lpCreateParams 이 여분의 데이타 즉 위에서 저장한 this 포인터의 값을 가지고 있게 되는 것이다. 메세지가 WM_CREATE 일 때 ido 에 this 포인터의 값을 할당하는 것을 볼 수 있다. 이렇게 함으로써 벌레를 잡을 수 있게 되었다. 그럼 나머지 매크로들을 보자.

#define ON_MESSAGE(CLASSNAME, MESSAGE, HANDLER) {MESSAGE, CLASSNAME::HANDLER},
#define END_MESSAGE_MAP() {0, NULL} };

위는 ON_MESSAGE 와 END_MESSAGE_MAP 의 매크로 내용이다. BEGIN_MESSAGE_MAP 과 ON_MESSAGE, END_MESSAGE_MAP 의 매크로를 합쳐서 쓰면 다음과 같은 MessageMap[] 구조체 배열을 초기화하는 코드가 된다.

BEGIN_MESSAGE_MAP(eglWindow)
ON_MESSAGE(eglWindow, WM_LBUTTONDOWN, OnLButtonDown)
ON_MESSAGE(eglWindow, WM_DESTROY, OnDestroy)
END_MESSAGE_MAP();

위의 내용을 코드로 써보면 아래와 같다.

eglWindow::MESSAGEMAP eglWindow::MessageMap[] = {
{WM_LBUTTONDOWN, eglWindow::OnLButtonDown},
{0, NULL}
};

그럼 벌레를 잡은 모든 매크로를 보도록 하자.

#ifndef _EGL_MSGMACRO_SYSTEM_H_
#define _EGL_MSGMACRO_SYSTEM_H_

//*************************************************************************************************************//
// Message Macro System

#define BEGIN_MESSAGE_MAP(CLASSNAME) \
BOOL CLASSNAME::RegisterWindow(HINSTANCE hInstance)\
{\
WNDCLASSEX wnd;\
wnd.cbSize = sizeof(WNDCLASSEX);\
wnd.cbWndExtra = 4;\
wnd.cbClsExtra = 0;\
wnd.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);\
wnd.hCursor = LoadCursor(NULL, IDC_ARROW);\
wnd.hIcon = LoadIcon(NULL, IDI_WINLOGO);\
wnd.hIconSm = LoadIcon(NULL, IDI_WINLOGO);\
wnd.hInstance = hInstance;\
wnd.lpfnWndProc = CLASSNAME::WndProc;\
wnd.lpszClassName = "EdineglWindowClass";\
wnd.lpszMenuName = NULL;\
wnd.style = CS_HREDRAW | CS_VREDRAW;\
if(!::RegisterClassEx(&wnd))\
return FALSE;\
return TRUE;\
}\
LRESULT CALLBACK CLASSNAME::WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)\
{\
int i=0;\
static CLASSNAME *ido = (CLASSNAME *)GetWindowLong(hWnd, GWL_USERDATA);\
if(ido == NULL && iMessage != WM_CREATE)\
{\
return DefWindowProc(hWnd, iMessage, wParam, lParam);\
}\
if(iMessage == WM_CREATE)\
{\
ido = (CLASSNAME *)(((LPCREATESTRUCT)lParam)->lpCreateParams);\
}\
while(MessageMap[i].iMsg != 0)\
{\
if(iMessage == MessageMap[i].iMsg)\
{\
(ido->*(MessageMap[i].fp))(wParam, lParam);\
}\
++i;\
}\
return DefWindowProc(hWnd, iMessage, wParam, lParam);\
}\
CLASSNAME::MESSAGEMAP CLASSNAME::MessageMap[] = {
#define ON_MESSAGE(CLASSNAME, MESSAGE, HANDLER) {MESSAGE, CLASSNAME::HANDLER},
#define END_MESSAGE_MAP() {0, NULL} };

#define DECLARE_MESSAGE_MAP(CLASSNAME) \
typedef void (CLASSNAME::*CLASSNAME##FuncPtr)(WPARAM, LPARAM);\
typedef struct _MESSAGEMAP\
{\
UINT iMsg;\
CLASSNAME##FuncPtr fp;\
}MESSAGEMAP;\
private: static BOOL RegisterWindow(HINSTANCE hInstance);\
private: static LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam);\
private: static MESSAGEMAP MessageMap[];\
public:

// Message Macro System
//****************************************************************************************************************//

#endif

이를 이용해 맨처음 만들었던 예제를 다시 만들어 보면 아래와 같다.

// 클래스 선언부분
class eglWindow
{
protected:
HWND mHWnd;
MSG mMsg;
HDC mDC;
HINSTANCE mHInstance;
public:
eglWindow();
~eglWindow();
BOOL Create(HINSTANCE hInstance, std::string WindowTitle="EDin
  OpenGL Framework", BOOL bFullScreen=FALSE);
virtual int Run(void);

DECLARE_MESSAGE_MAP(eglWindow);

void OnLButtonDown(WPARAM w, LPARAM l);
void OnDestroy(WPARAM w, LPARAM l);
};

//클래스 구현 부분
BEGIN_MESSAGE_MAP(eglWindow)
ON_MESSAGE(eglWindow, WM_LBUTTONDOWN, OnLButtonDown)
ON_MESSAGE(eglWindow, WM_DESTROY, OnDestroy)
END_MESSAGE_MAP();

void eglWindow::OnLButtonDown(WPARAM w, LPARAM l)
{
MessageBox(this->mHWnd, TEXT("EDin's OpenGL
  Framework 입니다"), TEXT("Hello;)"), MB_OK);
}

void eglWindow::OnDestroy(WPARAM w, LPARAM l)
{
PostQuitMessage(0);
}

eglWindow::eglWindow() : mDC(NULL), mHWnd(NULL), mHInstance(NULL)
{
}

eglWindow::~eglWindow()
{
}

BOOL eglWindow::Create(HINSTANCE hInstance, std::string WindowTitle, BOOL bFullScreen)
{
if(!eglWindow::RegisterWindow(hInstance))
return FALSE;

mHInstance = hInstance;
mHWnd = CreateWindowEx(
0,
"EdineglWindowClass",
WindowTitle.c_str(),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
(HWND)NULL,
(HMENU)NULL,
hInstance,
(LPVOID)this); //메세지맵을 위하여 this
  포인터를 넘긴다.

if(!mHWnd)
{
return FALSE;
}

ShowWindow(mHWnd, SW_SHOW);
UpdateWindow(mHWnd);
return TRUE;
}

int eglWindow::Run(void)
{
while(GetMessage(&mMsg, NULL, 0, 0))
{
TranslateMessage(&mMsg);
DispatchMessage(&mMsg);
}
return mMsg.message;
}

//메인프로그램
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,
  int nShowCmd)
{
eglWindow app;
app.Create(hInstance);
return app.Run();
}

위의 코드를 보면 WndProc 부분이 사라졌음을 볼 수 있다. 그런데 매 클래스 작성마다 Create 함수와 Run 함수등을 만들어야할까? 그렇다면 이런 프레임워크를 만들지도 않았을 것이다. 즉 eglWindow 에 필요한 것은 다 만들어 놓고 재정의 필요할지도 모르는 함수에는 가상함수를 사용하면 그만이다. 위에서 나중에 설명한다고 했던 상속에서 메세지 핸들러에 대한 코드 공유를 살펴 보자.

위에서는 마우스 왼쪽 버튼을 눌렀을 때 메세지 박스가 뜨는데 이 것을 비프음(띵~)이 들리게 할려면 어떻게 할까? 또는 상속함수에서도 그대로 메세지 박스가 보이게 하려면 어떻게 할까? 첫 질문에 대한 답은 바로 가상함수를 사용하는 것이다.

//클래스 선언 부분
class eglWindow
{
protected:
HWND mHWnd;
MSG mMsg;
HDC mDC;
HINSTANCE mHInstance;
public:
eglWindow();
~eglWindow();
BOOL Create(HINSTANCE hInstance, std::string WindowTitle="EDin
  OpenGL Framework", BOOL bFullScreen=FALSE);
virtual int Run(void);

DECLARE_MESSAGE_MAP(eglWindow);

virtual void OnLButtonDown(WPARAM w,
  LPARAM l);
virtual void OnDestroy(WPARAM w, LPARAM
  l);
};

//클래스 구현 부분
BEGIN_MESSAGE_MAP(eglWindow)
ON_MESSAGE(eglWindow, WM_LBUTTONDOWN, OnLButtonDown)
ON_MESSAGE(eglWindow, WM_DESTROY, OnDestroy)
END_MESSAGE_MAP();

void eglWindow::OnLButtonDown(WPARAM w, LPARAM l)
{
MessageBox(this->mHWnd, TEXT("EDin's OpenGL
  Framework 입니다"), TEXT("Hello;)"), MB_OK);
}

void eglWindow::OnDestroy(WPARAM w, LPARAM l)
{
PostQuitMessage(0);
}

//클래스 선언 부분
class eglSubWindow : public eglWindow
{
DECLARE_MESSAGE_MAP(eglSubWindow);
public:
virtual void OnLButtonDown(WPARAM w, LPARAM l);
};

BEGIN_MESSAGE_MAP(eglSubWindow)
ON_MESSAGE(eglSubWindow, WM_LBUTTONDOWN, OnLButtonDown)
ON_MESSAGE(eglSubWindow, WM_DESTROY, OnDestroy)
END_MESSAGE_MAP();

void eglSubWindow::OnLButtonDown(WPARAM w, LPARAM l)
{
MessageBeep(-1);
}

위의 코드를 보면 eglWindow 의 메세지 핸들러의 함수가 virtual 즉 가상함수로 선언되어 있다. 그리고 OnLButtonDown 과 OnDestroy 의 메세지 핸들러를 정의하고 있다. eglWindow 를 상속받는 eglSubWindow 는 OnLButtonDown 만 정의하고 있다. 내용은 마우스 왼쪽 버튼을 눌렀을 때 비프음이 들리는 것이다. 하지만 OnDestroy 의 핸들러는 없는데 메세지맵에는 ON_MESSAGE(eglSubWindow, WM_DESTROY, OnDestroy) 으로 핸들러를 등록하고 있다. 이는 상속에서 eglWindow 의 public 함수인 OnDestroy 가 eglSubWindow 에 상속되기 때문이며(코드공유) 마우스왼쪽 버튼을 눌렀을 때 우리가 원하는 기능을 수행하는 함수를 다시 만들 수 있도록(코드재정의) 가상함수를 사용했다.

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
  LPSTR lpCmdLine, int nShowCmd)
{
eglSubWindow app;
app.Create(hInstance);
return app.Run();
}

위의 코드를 실행하면 이번에는 마우스 왼쪽 버튼을 누르면 메세지 박스가 나타나는 대신에 비프음이 들릴 것이고 프로그램 종료는 OnDestroy 의 코드 공유로 전 단계의 프로그램과 똑같이 작동한다.

이상으로 OpenGL 을 제외하고 윈도우 생성과 메세지 핸들링에 관한 프레임웤을 만들어 보았다. 너무나 미약한 내용이지만 이 글이 많은 사람들에게 도움이 되었으면 좋겠다. 이 글의 다음 내용에서는 OpenGL 관련 책 중 어느서나 다루는 OpenGL 윈도우 생성에 대한 코드가 나올 것이다. 이를 클래스화 해 본다. - 끝 -

SOURCE CODE ARCHIVES
DOWNLOAD : Visual C++ [ EDinFramework4.zip ]
DOWNLOAD : Borland C++ Builder [ EDinFramework4.zip ]

BUILD TEST
Visual C++ 6.0 ServicePack 5
Borland C++ 6.0 Build Number 10.157


OpenGL 튜토리얼<기초편>

OpenGL 튜토리얼 < 기초편 > - #2

OpenGL 윈도우 프레임웍 만들기 2

by 김성철.EDin ( paranwave@korea.com )

전편에서 만들었던 eglWindow 클래스에 조금 변화가 생겼고 멤버 함수도 몇개 추가 되었지만 그리 변한 것은 없다. 아래는 eglWindow 클래스의 모습이며 여기에 추가되는 코드는 모두 OpenGL 을 설정하기 위한 코드이다. 이러한 코드에 대해서 자세하게 설명하지는 않을 것이다. 그 이유는 이 문서 말고도 OpenGL 기초 서적이나 Nehe 튜토리얼 같은 문서를 보면 아주 자세하게 설명되어 있기 때문이다. 그럼 각설하고 eglWindow 의 모습을 보자.

class eglWindow
{
protected:
BOOL mFullscreen;
HWND mHWnd;
MSG mMsg;
HDC mDC;
HGLRC mRC;
HINSTANCE mHInstance;
void DestroyGLWindow(void);
public:
eglWindow();
~eglWindow();
virtual BOOL InitGL(void);
virtual void RenderGLScene(void);
HWND GetHWND(void);
HDC GetHDC(void);

BOOL Create(BOOL bFullScreen=FALSE, const char *title="EDin
  OpenGL Framework", int width=640, int height=480, int bits=16);
int Run(void);

DECLARE_MESSAGE_MAP(eglWindow);
virtual void OnCreate(WPARAM wParam, LPARAM lParam){}
virtual void OnSize(WPARAM wParam, LPARAM lParam);
virtual void OnClose(WPARAM wParam, LPARAM lParam);
virtual void OnKeyDown(WPARAM wParam, LPARAM lParam);
virtual void OnSysCommand(WPARAM wParam, LPARAM lParam);
virtual void OnCommand(WPARAM wParam, LPARAM lParam)
  {}
virtual void OnKeyUp(WPARAM wParam, LPARAM lParam){}
virtual void OnLButtonDown(WPARAM wParam, LPARAM lParam){}
virtual void OnLButtonUp(WPARAM wParam, LPARAM lParam){}
virtual void OnMouseMove(WPARAM wParam, LPARAM lParam){}
virtual void OnRButtonDown(WPARAM wParam, LPARAM lParam){}
virtual void OnRButtonUp(WPARAM wParam, LPARAM lParam){}
virtual void OnChar(WPARAM wParam, LPARAM lParam){}
};

새로 추가된 멤버 변수는 HGLRC 인 GL 렌더링 컨텍스트와 풀스크린을 설정하는 플래그인 BOOL mFullscreen 이고 내부 헬퍼함수로 DestroyGLWindow(void) 함수가 있는데 이는 우리가 사용했던 시스템 자원들을 다시 시스템에 돌려주는 역할, 즉 뒷정리를 한다. virtual BOOL InitGL(void) 는 OpenGL 을 사용하기 위해서 OpenGL 의 상태를 초기화 하는 곳이며 가상함수인 것은 초기화는 때에 따라서 달라질 수 있기 때문에 상속 클래스에서 쉽게 변화를 줄 수 있게 하기 위해서다. virtual void RenderGLScene(void) 함수도 역시 가상이다. 이 함수가 하는 일은 화면에 무엇인가를 그리는 역할을 한다. 그러므로 그리는 일은 매번 다를 수 있으므로 상속클래스에서 쉽게 변경이 가능하도록 하기 위해서 가상함수로 만들었다. BOOL Create(....) 함수는 모습이 많이 바뀌었는데 함수의 인자를 보면 무슨 역할을 하는 인자인지 짐작이 갈 것이다. 이 함수 안에서 OpenGL 을 이용하여 그린 그림을 표시할 수 있는 윈도우를 생성한다. int Run(void) 함수는 전과 같이 단순한 메세지 펌핑코드로 만들어져 있다.

그 다음 눈에 띄는 것은 많은 수의 가상함수로 이루어진 메세지 핸들러이다. virtual void OnCreate(WPARAM wParam, LPARAM lParam){} 와 virtual void OnSize(WPARAM wParam, LPARAM lParam); 를 보면 하나는 선언과 동시에 구현(아무런 역할도 하지 않는 OnCreate 함수)을 함수이고 하나는 선언만 있는데 선언만 있는 함수는 많은 코드가 필요하여 inline 으로 만들지 않은 것이다. 자세한 사항은 소스코드를 살펴본다.

Create 함수는 아래와 같이 코딩되어 있다.

BOOL eglWindow::Create(BOOL fullscreen, const char *title, int width, int height, int bits)
{
if(!eglWindow::RegisterWindow(mHInstance))
return FALSE;

GLuint PixelFormat; // Holds The Results After Searching For A Match
DWORD dwExStyle; // Window Extended Style
DWORD dwStyle; // Window Style
RECT ClientRect;
ClientRect.left = (long)0;
ClientRect.right = (long)width;
ClientRect.top = (long)0;
ClientRect.bottom = (long)height;

mFullscreen = fullscreen;
if(mFullscreen)
{
DEVMODE dm; // Device Mode
memset(&dm, 0, sizeof(dm)); // Makes Sure Memory's Cleared
dm.dmSize = sizeof(dm); // Size Of The Devmode Structure
dm.dmPelsHeight = height; // Selected Screen Height
dm.dmPelsWidth = width; // Selected Screen Width
dm.dmBitsPerPel = bits; // Selected Bits Per Pixel
dm.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;

if(ChangeDisplaySettings(&dm, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL)
{
if(MessageBox(NULL, TEXT("현재의 비디오카드에서 풀스크린을 지원하지 않습니다. 보통화면으로 보시겠습니까?"), TEXT("오류"),
MB_YESNO | MB_ICONEXCLAMATION) == IDYES)
{
mFullscreen = FALSE;
}
else
{
MessageBox(NULL, TEXT("프로그램을 종료합니다"), TEXT("알림"), MB_OK | MB_ICONSTOP);
PostQuitMessage(0);
return FALSE; //exit
}
}
}

if(mFullscreen)
{
dwExStyle = WS_EX_APPWINDOW;
dwStyle = WS_POPUP;
//ShowCursor(false);
}
else
{
dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
dwStyle = WS_OVERLAPPEDWINDOW;
}

AdjustWindowRectEx(&ClientRect, dwStyle, false, dwExStyle); // Adjust Window To true Requested Size

mHWnd = CreateWindowEx(
dwExStyle,
"eglWindow",
title,
dwStyle|WS_CLIPSIBLINGS|WS_CLIPCHILDREN,
0, 0,
ClientRect.right-ClientRect.left,
ClientRect.bottom-ClientRect.top,
(HWND)NULL,
(HMENU)NULL,
mHInstance,
(LPVOID)this); //메세지맵을 위하여 this 포인터를 넘긴다.

if(!mHWnd)
{
MessageBox(NULL, TEXT("윈도우를 생성하는데 실패했습닏"), TEXT("오류"), MB_OK | MB_ICONEXCLAMATION);
return FALSE;
}

static PIXELFORMATDESCRIPTOR pfd = {
sizeof(pfd), // Size Of This Pixel Format Descriptor
1, // Version Number
PFD_DRAW_TO_WINDOW | // Format Must Support Window
PFD_SUPPORT_OPENGL | // Format Must Support OpenGL
PFD_DOUBLEBUFFER, // Must Support Double Buffering
PFD_TYPE_RGBA, // Request An RGBA Format
bits, // Select Our Color Depth
0, 0, 0, 0, 0, 0, // Color Bits Ignored
0, // No Alpha Buffer
0, // Shift Bit Ignored
0, // No Accumulation Buffer
0, 0, 0, 0, // Accumulation Bits Ignored
16, // 16Bit Z-Buffer (Depth Buffer)
0, // No Stencil Buffer
0, // No Auxiliary Buffer
PFD_MAIN_PLANE, // Main Drawing Layer
0, // Reserved
0, 0, 0 // Layer Masks Ignored
};

// Did We Get A Device Context?
if(!(mDC = GetDC(mHWnd)))
{
DestroyGLWindow();
MessageBox(NULL, TEXT("GL Device Context 를 생성할 수 없습니다"), TEXT("오류"), MB_OK | MB_ICONEXCLAMATION);
return FALSE;
}

// Did Windows Find A Matching Pixel Format?
if (!(PixelFormat = ChoosePixelFormat(mDC, &pfd)))
{
DestroyGLWindow();
MessageBox(NULL, TEXT("장치에 맞는 픽셀형식을 찾지 못했습니다"), TEXT("오류"), MB_OK | MB_ICONEXCLAMATION);
return FALSE;
}

// Are We Able To Set The Pixel Format?
if(!SetPixelFormat(mDC, PixelFormat, &pfd))
{
DestroyGLWindow();
MessageBox(NULL, TEXT("픽셀형식을 설정할 수 없습니다"), TEXT("오류"), MB_OK | MB_ICONEXCLAMATION);
return FALSE;
}

// Are We Able To Get A Rendering Context?
if (!(mRC=wglCreateContext(mDC)))
{
DestroyGLWindow();
MessageBox(NULL, TEXT("GL Rendering Context 를 생성할 수 없습니다"), TEXT("오류"), MB_OK | MB_ICONEXCLAMATION);
return FALSE; // Return false
}

// Try To Activate The Rendering Context
if(!wglMakeCurrent(mDC, mRC))
{
DestroyGLWindow();
MessageBox(NULL, TEXT("GL Rendering Context 를 사용할 수 없습니다"), TEXT("오류"), MB_OK | MB_ICONEXCLAMATION);
return FALSE; // Return false
}

ShowWindow(mHWnd, SW_SHOW); // Show The Window
SetForegroundWindow(mHWnd); // Slightly Higher Priority
SetFocus(mHWnd); // Sets Keyboard Focus To The Window

OnSize(0, MAKELPARAM(width, height)); //이함수를 호출하지 않으면 FULLSCREEN 에서 그림이 그려지지 않는다.

// Initialize Our Newly Created GL Window
if (!InitGL())
{
DestroyGLWindow();
MessageBox(NULL, TEXT("초기화에 실패했습니다"), TEXT("오류"), MB_OK | MB_ICONEXCLAMATION);
return FALSE;
}

return TRUE;
}

위의 코드는 NeHe 튜토리얼에서 코드를 가져와 eglWindow 에 알맞게 아주 약간 수정만 한 코드이다. 코드가 조금 길지만 특별한 사항은 없으며 코드를 보면 누구나 다 이해할 수 있는 코드이다.(OpenGL 을 조금 안다고치고..) InitGL 의 함수는 아래와 같이 되어 있다.

BOOL eglWindow::InitGL(void)
{
glShadeModel(GL_SMOOTH); // Enable Smooth Shading
glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // Black Background
glClearDepth(1.0f); // Depth Buffer Setup
glEnable(GL_DEPTH_TEST); // Enables Depth Testing
glDepthFunc(GL_LEQUAL); // The Type Of Depth Testing
  To Do
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); //
  Really Nice Perspective Calculations
return TRUE;
}

다음은 RenderGLScene 의 코드이다.

void eglWindow::RenderGLScene(void)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  // Clear Screen And Depth Buffer
glLoadIdentity(); // Reset The Current Modelview Matrix
}

다음은 Run 함수의 코드이다.

int eglWindow::Run(void)
{
while(1)
{
if(PeekMessage(&mMsg, NULL,
  0, 0, PM_REMOVE))
{
if(mMsg.message
  == WM_QUIT)
{
break;
}
else
{
TranslateMessage(&mMsg);
DispatchMessage(&mMsg);
}
}
else
{
RenderGLScene();
SwapBuffers(mDC);
}
}
return mMsg.message;
}

eglWindow 에는 몇몇개의 메세지에 대해 필요한 코드를 담고 있는데 그 핸들러의 코드는 아래와 같다.

void eglWindow::OnKeyDown(WPARAM wParam, LPARAM lParam)
{
if(wParam == VK_ESCAPE)
{
SendMessage(mHWnd, WM_CLOSE,
  0, 0);
}
}

void eglWindow::OnSize(WPARAM wParam, LPARAM lParam)
{
GLsizei width = LOWORD(lParam);
GLsizei height = HIWORD(lParam);
if (height == 0)
height = 1; // Making Height
  Equal One

glViewport( 0, 0, width, height ); // Reset The Current
  Viewport

glMatrixMode(GL_PROJECTION); // Select The Projection
  Matrix
glLoadIdentity(); // Reset The Projection Matrix

// Calculate The Aspect Ratio Of The Window
gluPerspective(45.0f, GLfloat(width)/GLfloat(height),
  0.1f, 100.0f);

glMatrixMode(GL_MODELVIEW); // Select The Modelview
  Matrix
glLoadIdentity(); // Reset The Modelview Matrix
}

void eglWindow::OnClose(WPARAM wParam, LPARAM lParam)
{
DestroyGLWindow();
PostQuitMessage(0);
}

void eglWindow::OnSysCommand(WPARAM wParam, LPARAM lParam)
{
switch (wParam) // Check System Calls
{
case SC_SCREENSAVE: // Screensaver Trying To Start?
case SC_MONITORPOWER: // Monitor Trying To Enter Powersave?
return ; // Prevent From Happening
}
}

위의 eglWindow 에 정의되지 않은 메세지를 핸들링하려면 반드시 eglWindow 의 메세지맵에 등록을 시켜줘야한다. 주의할 점은 상속함수의 메세지맵에만 등록시키면 안된다는 것이다. 그래서 위에서 가상함수 메세지 핸들러가 많았던 이유는 필요할 것 같은 메세지 핸들러들을 미리 등록해 두었기 때문이다. 이 클래스를 이용해서 프로그램을 만들려면 다음과 같이 팔요한 것만 골라 쓰면 된다.

class eglSubWindow : public eglWindow
{
private:
POINT oldPoint;
GLfloat xrot, yrot;
public:
virtual void RenderGLScene(void);

DECLARE_MESSAGE_MAP(eglSubWindow)

virtual void OnCreate(WPARAM wParam, LPARAM lParam);
virtual void OnLButtonDown(WPARAM wParam, LPARAM lParam);
virtual void OnLButtonUp(WPARAM wParam, LPARAM lParam);
virtual void OnMouseMove(WPARAM wParam, LPARAM lParam);
};

BEGIN_MESSAGE_MAP(eglSubWindow)
ON_MESSAGE(eglSubWindow, WM_SIZE, OnSize)
ON_MESSAGE(eglSubWindow, WM_CLOSE, OnClose)
ON_MESSAGE(eglSubWindow, WM_KEYDOWN, OnKeyDown)
ON_MESSAGE(eglSubWindow, WM_CREATE, OnCreate)
ON_MESSAGE(eglSubWindow, WM_LBUTTONDOWN, OnLButtonDown)
ON_MESSAGE(eglSubWindow, WM_LBUTTONUP, OnLButtonUp)
ON_MESSAGE(eglSubWindow, WM_MOUSEMOVE, OnMouseMove)
END_MESSAGE_MAP();

void eglSubWindow::RenderGLScene(void)
{
eglWindow::RenderGLScene();
glTranslatef(0.0f, 0.0f, -6.0f);
glRotatef(xrot, 1.0f, 0.0f, 0.0f);
glRotatef(yrot, 0.0f, 1.0f, 0.0f);
glutWireCube(1.0);
}
void eglSubWindow::OnCreate(WPARAM wParam, LPARAM lParam)
{
xrot = yrot = 0.0f;
memset(&oldPoint, 0, sizeof(oldPoint));
}
void eglSubWindow::OnLButtonDown(WPARAM wParam, LPARAM lParam)
{
oldPoint.x = LOWORD(lParam);
oldPoint.y = HIWORD(lParam);
SetCapture(GetHWND());
}
void eglSubWindow::OnLButtonUp(WPARAM wParam, LPARAM lParam)
{
oldPoint.x = 0;
oldPoint.y = 0;
ReleaseCapture();
}
void eglSubWindow::OnMouseMove(WPARAM wParam, LPARAM lParam)
{
if(GetCapture()==GetHWND())
{
xrot = LOWORD(lParam) - oldPoint.x/3.6;
yrot = HIWORD(lParam) - oldPoint.y/3.6;
}
}

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,
  int nShowCmd)
{
eglSubWindow app;
app.Create(TRUE);
return app.Run();
}

위의 프로그램은 화면에서 마우스 왼쪽 버튼을 누른채 마우스를 움직이면 큐브가 알맞게 회전하는 코드이다.

이상으로 OpenGL 을 위한 윈도우프레임웤을 만들어 보았다. 이번은 설명이 너무나 성의없는 것 같기도 해 미안할 따름이다. 바랄 것이 있다면 부족한 내용이지만 많은 이들에게 도움이 되었으면 좋겠다. 질문할 사항이 있다면 언제든지 위의 이메일 주소로 메일을 보내거나 홈페이지에 글을 남겨주길 바란다. 다음에 더욱 멋진 내용으로 다시 만나길 바라며... ;] -끝-

알고 있는 문제점

  • 다수의 WndProc 이 선언되지 않는다. 즉 DECLARE_MESSAGE_MAP(eglSubWindow) 를 해도 매크로 함수에 들어가는 CLASSNAME 은 eglSubWindow 의 부모 클래스의 이름인 eglWindow 의 이름이 들어간다. 그래서 eglWindow 에 다수의 메세지 핸들러를 가상함수로 선언해 놓은 것이다.
  • 위의 문제로 eglSubWindow 에서 DECLARE_MESSAGE_MAP, BEGIN_MESSAGE_MAP, END_MESSAGE_MAP 을 선언할 필요가 없다.
  • 위의 문제로 크게 문제될 것은 없으나 eglWindow 에서 핸들링 하지 않는 메세지를 핸들링하려면 반드시 eglWindow 에 핸들링 함수와 메세지를 등록해야하는 불편함이 있다. 원래 바라던 동작은 상속클래스에서 바로 새로운 메세지 핸들러를 추가할 수 있는 것이다. 어떻게 수정해야할까?

변경 사항

  • 리소스인 메뉴를 추가할 수 있도록 Create 함수를 아래와 같이 수정

  • GLUT 모든 객체 그리기

//함수 원형
BOOL Create(BOOL bFullScreen=FALSE, const char *title="EDin OpenGL Framework", int width=640, int height=480, int bits=16, LPTSTR menu=NULL);

//CreateWindow 함수 내
mHWnd = CreateWindowEx(
dwExStyle,
"eglWindow",
title,
dwStyle|WS_CLIPSIBLINGS|WS_CLIPCHILDREN,
0, 0,
ClientRect.right-ClientRect.left,
ClientRect.bottom-ClientRect.top,
(HWND)NULL,
(HMENU)((!fullscreen) ? LoadMenu(mHInstance, menu) : NULL),
mHInstance,
(LPVOID)this);

//사용예
class eglSubWindow : public eglWindow
{
private:
public:
virtual void RenderGLScene(void);
virtual void OnCommand(WPARAM wParam, LPARAM lParam);
};
void eglSubWindow::OnCommand(WPARAM wParam, LPARAM lParam)
{
switch(LOWORD(wParam))
{
case IDM_DRAW_LINE:
MessageBox(mHWnd, "DRAW LINE", "CAPTION", MB_OK);
break;
}
}
void eglSubWindow::RenderGLScene(void)
{
eglWindow::RenderGLScene();
}
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
eglSubWindow app;
app.Create(FALSE, "OpenGL ex03-Menu", 640, 480, 16, MAKEINTRESOURCE(IDR_MAINMENU));
return app.Run();
}

SOURCE CODE ARCHIVES
DOWNLOAD : Visual C++ [ EDinFramework5.zip ] [ ex02.zip ] [ ExLight.zip ]
DOWNLOAD : Borland C++ Builder [ EDinFramework5.zip ]

BUILD TEST
Visual C++ 6.0 ServicePack 5
Borland C++ 6.0 Build Number 10.157


OpenGL 튜토리얼<기초편>

OpenGL 튜토리얼<기초편> - #3

점그리기

by 김성철 ( paranwave@korea.com )

  • GL_POINTS
  • glPointSize()
  • glVertex2f(), glVertex3f()

앞 장에서는 윈도우즈에서 쉽게 OpenGL 을 이용한 어플리케이션을 만들기 위해서 윈도우용 OpenGL 프레임웤을 만들어 보았다. 그 프레임웤을 사용해서 OpenGL 에 대해서 알아보자. 그 첫번재 과정으로 점(Point) 그리기부터 알아볼 것이다. 점(Point)은 정점(Vertex)이라고 부르는 2차원 이상의 좌표계 위치 조합으로 나타낸다. X축과 Y축으로 표현되는 2D 정점은 OpenGL 에 의해서 Z 축도 포함하게 된다. OpenGL 은 모든 정점을 4가지 좌표축의 위치 조합(X축, Y축, Z축, W축)으로 나타낸다. 이 좌표계를 동차좌표계(homogeneous coordinate system) 이라고 하는데 자세한 내용은 OpenGL 전문서적을 참고하자. 만약 W 가 0 이 아니라면 이 정점은 유클리드 3D 좌표계(X/W, Y/W, Z/W) 의 점에 해당된다. OpenGL 명령어에서 W 를 사용할 수 있으나 거의 쓰이지 않는다. 만약 W 가 쓰이지 않는다면 그 값을 1.0 으로 생각하면 된다. 즉, glVertex2f(1.0f, 1.0f) 라 하면 (1.0, 1.0, 0.0, 1.0) 의 정점을 나타내는 것이다. 다음은 정점을 나타내는 OpenGL 의 함수 그룹이다. 함수는 두 그룹으로 나뉘는데 직접 좌표계 위치를 받는 것과 각 좌표계 위치의 배열을 받는 것이 있다. 배열을 받는 것은 함수의 이름 끝에 v 자 붙는다.

void glVertex2d( GLdouble x, GLdouble y )
void glVertex2f( GLfloat x, GLfloat y )
void glVertex2i( GLint x, GLint y )
void glVertex2s( GLshort x, GLshort y )
void glVertex3d( GLdouble x, GLdouble y, GLdouble z )
void glVertex3f( GLfloat x, GLfloat y, GLfloat z )
void glVertex3i( GLint x, GLint y, GLint z )
void glVertex3s( GLshort x, GLshort y, GLshort z )
void glVertex4d( GLdouble x, GLdouble y, GLdouble z, GLdouble w )
void glVertex4f( GLfloat x, GLfloat y, GLfloat z, GLfloat w )
void glVertex4i( GLint x, GLint y, GLint z, GLint w )
void glVertex4s( GLshort x, GLshort y, GLshort z, GLshort w )

void glVertex2dv( const GLdouble *v )
void glVertex2fv( const GLfloat *v )
void glVertex2iv( const GLint *v )
void glVertex2sv( const GLshort *v )
void glVertex3dv( const GLdouble *v )
void glVertex3fv( const GLfloat *v )
void glVertex3iv( const GLint *v )
void glVertex3sv( const GLshort *v )
void glVertex4dv( const GLdouble *v )
void glVertex4fv( const GLfloat *v )
void glVertex4iv( const GLint *v )
void glVertex4sv( const GLshort *v )

OpenGL 에서 그리기는 glBegin() 함수와 glEnd() 함수 사이에서 할 수 있는데 그릴 수 있는 것은 다음과 같은 종류가 있다.

glBegin( GL_POINT);
점을 그린다.
glEnd();

glBegin( GL_LINES );
선을 그린다.
glEnd();

glBegin( GL_POLYGON );
다각형을 그린다.
glEnd();

glBegin( GL_TRIANGLES );
삼각형을 그린다.
glEnd();

glBegin( GL_QUADS );
사각형을 그린다.
glEnd();

glBegin( GL_LINE_STRIP );
겹친 선을 그린다.
glEnd();

glBegin( GL_LINE_LOOP );
겹치지 않고 연결 된 선을 그린다.
glEnd();

glBegin( GL_TRIANGLE_STRIP );
삼각형이 연결된 도형을 그린다.
glEnd();

glBegin( GL_TRIANBLE_FAN );
한점을 중심으로 삼각형이 연결된 도형을 그린다.
glEnd();

glBegin( GL_QUAD_STRIP );
사각형이 이어진 도형을 그린다.
glEnd();

아래의 그림을 보면 이해하는데 도움이 될 것이다.

5 개의 점을 그리는 코드를 쓰면 아래와 같다.

glBegin( GL_POINTS )
glVertex2f( 1.0f, 1.0f ); //v0
glVertex2f( 1.5f, 0.5f ); //v1
glVertex2f( 2.0f, 0.0f ); //v2
glVertex2f( 2.5f, 0.5f ); //v3
glVertex2f( 3.0f, 1.0f ); //v4
glEnd();

점의 크기는 항상 일정한 것이 아니라 변경가능하다. 점의 크기를 변경하는 OpenGL 함수는 아래와 같다. 점의 크기로 설정할 수 있는 최대값은 MS 에서 구현한 OpenGL 에서는 10.0 까지이다. OpenGL 스펙에서는 1.0 만지원된다.

void glPointSize( GLfloat size );

이 내용을 바탕으로 점이 커졌다 작아졌다 하는 프로그램을 만들어보자.

위의 그림은 프로그램을 실행시킨 결과 이고 아래의 코드는 위 프로그램의 코드이다. 앞에서 만든 프레임웤을 바탕으로 만들어진 것임을 기억하자.

#include <windows.h>
#include "lib\egl.h"

struct Point3D
{
GLfloat x, y, z;
};

class eglSubWindow : public eglWindow
{
private:
GLfloat mPointSize; //점의 크기
Point3D mPointList[5]; //점의
  리스트
public:
virtual void RenderGLScene(void);
virtual void OnCreate(WPARAM wParam, LPARAM lParam);
};

void eglSubWindow::OnCreate(WPARAM wParam, LPARAM lParam)
{
//점의 위치를 초기화한다.
mPointList[0].x = 0.0f;
mPointList[0].y = 0.0f;
mPointList[0].z = -10.0f;

mPointList[1].x = 1.0f;
mPointList[1].y = 1.0f;
mPointList[1].z = -10.0f;

mPointList[2].x = 2.0f;
mPointList[2].y = 2.0f;
mPointList[2].z = -10.0f;

mPointList[3].x = -1.0f;
mPointList[3].y = -1.0f;
mPointList[3].z = -10.0f;

mPointList[4].x = -2.0f;
mPointList[4].y = -2.0f;
mPointList[4].z = -10.0f;

//점의 크기를 초기화한다.
mPointSize = 1.0f;
}

void eglSubWindow::RenderGLScene(void)
{
static int i=0;
static GLfloat step=0.1f;

eglWindow::RenderGLScene();

glColor3f(1.0f, 0.0f, 0.0f); //점의
  색을 빨강색으로 설정한다.
glPointSize(mPointSize); //점의
  크기를 설정한다.
glBegin(GL_POINTS); //점을 그린다.
for(i=0; i<sizeof(mPointList)/sizeof(Point3D);
  ++i)
{
glVertex3f(mPointList[i].x,
  mPointList[i].y, mPointList[i].z);
}
glEnd();

mPointSize += step;

if(mPointSize >= 10.0f)
{
step = -step;
}
else if(mPointSize <= 0.0f)
{
step = -step;
}
}

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,
  int nShowCmd)
{
eglSubWindow app;
app.Create(FALSE,"OpenGL glPoint");
return app.Run();
}

다음은 마우스 커서가 위치한 곳에서 마우스 왼쪽 버튼을 누르면 점을 찍는 프로그램을 만들어보자.

아래는 이 프로그램의 소스이며 주석과 굵은 글씨로 적혀있는 코드를 주의해서 보자. 참고로 프레임웤에 몇가지의 파일이 추가되어 내용이 업데이트 되었다.

#define EGL_USE_STL
#include "lib\egl.h"

struct Point3D
{
GLfloat x, y, z;
Point3D(GLfloat ax, GLfloat ay, GLfloat az) { x=ax;
  y=ay; z=az; }
};

typedef vector<Point3D> point_list;
typedef vector<Point3D>::iterator piter;

class eglSubWindow : public eglWindow
{
private:
point_list mPointList;
GLsizei ClientWidth; //윈도우의
  너비
GLsizei ClientHeight; //윈도우의
  높이
GLfloat Range; //클리핑 영역의 범위
public:
virtual void RenderGLScene(void);
virtual void OnCreate(WPARAM wParam, LPARAM lParam);
virtual void OnLButtonDown(WPARAM wParam, LPARAM lParam);
virtual void OnSize(WPARAM wParam, LPARAM lParam);
};
void eglSubWindow::OnCreate(WPARAM wParam, LPARAM lParam)
{
ClientWidth = ClientHeight = 0;
Range = 5.0f; //클리핑
  영역의 범의를 5 로 초기화한다.
}
void eglSubWindow::OnSize(WPARAM wParam, LPARAM lParam)
{
GLsizei width = LOWORD(lParam);
GLsizei height = HIWORD(lParam);

if (height == 0)
height = 1;

ClientWidth = width;
ClientHeight = height;

glViewport( 0, 0, width, height );

glMatrixMode(GL_PROJECTION);
glLoadIdentity();

glOrtho(-Range, Range, -Range, Range, 1.0f,
  100.0f);

glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}

void eglSubWindow::OnLButtonDown(WPARAM wParam, LPARAM lParam)
{
GLfloat xPos = (GLfloat)(LOWORD(lParam));
GLfloat yPos = (GLfloat)(HIWORD(lParam));

GLfloat glX = ((xPos * (2*Range)) / ClientWidth)
  - Range;
GLfloat glY = ((yPos * (2*Range)) / ClientHeight) -
  Range;

mPointList.push_back(Point3D(glX, -glY, 0.0f));
//-glY 인 것은 윈도우즈의
  기본 좌표계에서는 Y 축이 아래로 내려 갈 수록 Y 축의 값이 증가한다. 하지만 OpenGL 좌표축은 그와 반대이므로 - 를 해준다.

TRACE("glVertex3f[ %f , %f , %f ]\n", glX,
  -glY, 0.0f);
}

void eglSubWindow::RenderGLScene(void)
{
eglWindow::RenderGLScene();

glTranslatef(0.0f, 0.0f, -10.0f);
glColor3f(1.0f, 0.0f, 0.0f);
glPointSize(10.0f);
glBegin(GL_POINTS);
for(piter i=mPointList.begin();
  i!=mPointList.end(); ++i)
{
glVertex3f((*i).x,
  (*i).y, (*i).z);
}
glEnd();
}

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,
  int nShowCmd)
{
eglSubWindow app;
app.Create(FALSE,"EDin's OpenGL : 점찍기");
return app.Run();
}

SOURCE CODE ARCHIVES
DOWNLOAD : Visual C++ [ glPoint.zip ] [ glPoint2.zip ]
DOWNLOAD : Borland C++ Builder [ glPoint.zip ] [ glPoint2.zip ]

BUILD TEST
Visual C++ 6.0 ServicePack 5
Borland C++ 6.0 Build Number 10.157


OpenGL 튜토리얼<기초편>

OpenGL 튜토리얼<기초편> - #4

선 그리기

by 김성철 ( paranwave@korea.com )

  • GL_LINES
  • GL_LINE_STRIP
  • GL_LINE_LOOP
  • glLineWidth()
  • glLineStipple()

앞에서는 점을 그려보았다. 선을 그리려면 선의 길이만큼 점을 그려줘야하는데 일일이 그것을 해준다는 것은 생각만해도 질릴 일이다. OpenGL 에서 그 일을 쉽게 해주는 것이 glBegin() 함수에 GL_LINES 나 GL_LINE_STRIP , GL_LINE_LOOP 를 설정하는 것이다. 이렇게 함으로써 선을 쉽게 그릴 수 있다. 선을 그리기 위해서는 두 개의 정점이 필요한데, 하나는 선의 시작 위치점이고 나머지 하나는 선이 끝나는 위치 점이다. OpenGL 에서는 가는 선만 그릴 수 있는 것이 아니라 glLineWidth() 함수로 선의 굵기를 조절할 수 있다 또, 직선만 그릴 수 있는 것이 아니라 glLineStipple() 함수를 이용해 패턴이 있는 선을 그릴 수 있다. 예를 들면 점선과 같이 패턴이 있는 선을 쉽게 그릴 수 있는 것이다.

이 예제는 앞에서의 점찍기와 아주 비슷한 예제다. 단 틀린 점은 마우스 오른쪽 버튼을 누르면 선의 굵기가 점점 굵어 지도록 해 놓은 것 뿐이다.

#define EGL_USE_STL
#include "lib\egl.h"

struct Point3D
{
GLfloat x, y, z;
Point3D() { x = y = z = 0.0f; }
Point3D(GLfloat ax, GLfloat ay, GLfloat az) { x=ax;
  y=ay; z=az; }
};

struct Line
{
Point3D startPoint;
Point3D endPoint;
Line(Point3D aStartPoint, Point3D aEndPoint) { startPoint=aStartPoint;
  endPoint=aEndPoint; }
};

typedef vector<Line> line_list;
typedef vector<Line>::iterator liter;

class eglSubWindow : public eglWindow
{
private:
line_list mLineList;
GLfloat Range;
GLsizei ClientWidth;
GLsizei ClientHeight;
GLfloat LineWidth;
public:
virtual void RenderGLScene(void);
virtual void OnSize(WPARAM wParam, LPARAM lParam);
virtual void OnCreate(WPARAM wParam, LPARAM lParam);
virtual void OnLButtonDown(WPARAM wParam, LPARAM lParam);
virtual void OnLButtonUp(WPARAM wParam, LPARAM lParam);
virtual void OnRButtonUp(WPARAM wParam, LPARAM lParam);
};

void eglSubWindow::OnCreate(WPARAM wParam, LPARAM lParam)
{
ClientWidth = ClientHeight = 0.0f;
Range = 5.0f;
LineWidth = 1.0f;
}

void eglSubWindow::OnSize(WPARAM wParam, LPARAM lParam)
{
GLsizei width = LOWORD(lParam);
GLsizei height = HIWORD(lParam);

if (height == 0)
height = 1;

ClientWidth = width;
ClientHeight = height;

glViewport( 0, 0, width, height );

glMatrixMode(GL_PROJECTION);
glLoadIdentity();

glOrtho(-Range, Range, -Range, Range, 1.0f, 100.0f);

glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}

void eglSubWindow::OnLButtonDown(WPARAM wParam, LPARAM lParam)
{
GLfloat xPos = (GLfloat)(LOWORD(lParam));
GLfloat yPos = (GLfloat)(HIWORD(lParam));

GLfloat glX = ((xPos * (2*Range)) / ClientWidth) - Range;
GLfloat glY = ((yPos * (2*Range)) / ClientHeight) -
  Range;

mLineList.push_back(Line(Point3D(glX, glY, 0.0f), Point3D(glX,
  glY, 0.0f)));
SetCapture(GetHWND());
}

void eglSubWindow::OnLButtonUp(WPARAM wParam, LPARAM lParam)
{
GLfloat xPos = (GLfloat)(LOWORD(lParam));
GLfloat yPos = (GLfloat)(HIWORD(lParam));

GLfloat glX = ((xPos * (2*Range)) / ClientWidth) - Range;
GLfloat glY = ((yPos * (2*Range)) / ClientHeight) -
  Range;

if(!mLineList.empty())
{
mLineList[mLineList.size()-1].endPoint.x
  = glX;
mLineList[mLineList.size()-1].endPoint.y
  = glY;
mLineList[mLineList.size()-1].endPoint.z
  = 0.0f;
}
ReleaseCapture();
}

void eglSubWindow::OnRButtonUp(WPARAM wParam, LPARAM lParam)
{
LineWidth += 1.0f;
if(LineWidth > 10.0f)
{
LineWidth = 10.0f;
}
}

void eglSubWindow::RenderGLScene(void)
{
eglWindow::RenderGLScene();

glTranslatef(0.0f, 0.0f, -10.0f);
glLineWidth(LineWidth);

glBegin(GL_LINES);
for(liter i=mLineList.begin();
  i!=mLineList.end(); ++i)
{
glVertex3f((*i).startPoint.x,
  -(*i).startPoint.y, (*i).startPoint.z);
glVertex3f((*i).endPoint.x,
  -(*i).endPoint.y, (*i).endPoint.z);
}
glEnd();
}

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,
  int nShowCmd)
{
eglSubWindow app;
app.Create(FALSE, "EDin's OpenGL glLine");
return app.Run();
}

GL_LINES 와 GL_LINE_STRIP 그리고 GL_LINE_LOOP 의 차이점을 알기 위해서, 위의 코드 중에서 RenderGLScene() 함수를 다음과 같이 수정하고 실행해 보자. 그러면 차이점을 금방 알게 될 것이다.

void eglSubWindow::RenderGLScene(void)
{
eglWindow::RenderGLScene();

glTranslatef(0.0f, 0.0f, -10.0f);
glLineWidth(LineWidth);

glBegin(GL_LINE_STRIP);
for(liter i=mLineList.begin();
  i!=mLineList.end(); ++i)
{
glVertex3f((*i).startPoint.x,
  -(*i).startPoint.y, (*i).startPoint.z);
glVertex3f((*i).endPoint.x,
  -(*i).endPoint.y, (*i).endPoint.z);
}
glEnd();
}

void eglSubWindow::RenderGLScene(void)
{
eglWindow::RenderGLScene();

glTranslatef(0.0f, 0.0f, -10.0f);
glLineWidth(LineWidth);

glBegin(GL_LINE_LOOP);
for(liter i=mLineList.begin();
  i!=mLineList.end(); ++i)
{
glVertex3f((*i).startPoint.x,
  -(*i).startPoint.y, (*i).startPoint.z);
glVertex3f((*i).endPoint.x,
  -(*i).endPoint.y, (*i).endPoint.z);
}
glEnd();
}

glLineStipple() 을 이용해서 패턴이 있는 선을 그려보자. glLineStipple() 함수의 원형은 다음과 같다.

void glLineStipple ( GLint factor, GLushort pattern )

factor 는 pattern 에 정의한 선의 패턴이 몇 픽셀에 나타나게 할 것인지를 말한다. 예를 들어 0xAAAA 라는 패턴이 있으면 이를 2진수로 표현하면 1010101010101010 이다. 여기서 factor 를 1로 설정하면 앞의 패턴이 1 픽셀 안에서 나타나는 것이고 2이면 2픽셀 안에서 4이면 4픽셀 안에서 패턴이 나타난다. 따라서 factor 의 값이 작을 수록 촘촘한 패턴이 나타나게 된다. 또 한가지 중요한 점은 위의 패턴이 거꾸로 적용된다는 것이다. 즉 1010101010101010 패턴이 적용될 때에는 0101010101010101 이 된다. 우리가 패턴을 사용할 경우에 위의 함수에 패턴을 정의하면 모든 선에 패턴이 적용될 것이다. 하지만 그렇게 되면 패턴이 적용되지 않는 선은 그릴 수가 없다. 그래서 OpenGL 에서는 여러 상태들 중 LineStipple 의 사용 가능 여부의 상태를 설정하는 glEnable( GL_LINE_STIPPLE ) 과 glDisable( GL_LINE_STIPPLE ) 을 제공한다. 여기서 glEnable() 과 glDisable() 의 함수는 OpenGL 의 여러가지 상태들을 ON / OFF 하는 함수다.

위의 예제는 좌표계의 각 축을 그려본 것이다. 점선은 각축의 음의 부분을 나타내고 실선은 양의 부분을 나타낸다. 빨간색은 X 축 녹색은 Y 축 파란색은 Z 축이다.

#include "lib\egl.h"

class eglSubWindow : public eglWindow
{
private:
GLushort pattern;
POINT oldPoint;
GLfloat xrot, yrot;
public:
virtual void RenderGLScene(void);
virtual void OnSize(WPARAM wParam, LPARAM lParam);
virtual void OnCreate(WPARAM wParam, LPARAM lParam);
virtual void OnLButtonDown(WPARAM wParam, LPARAM lParam);
virtual void OnLButtonUp(WPARAM wParam, LPARAM lParam);
virtual void OnMouseMove(WPARAM wParam, LPARAM lParam);
};

void eglSubWindow::OnCreate(WPARAM wParam, LPARAM lParam)
{
pattern = 0xAAAA;
xrot = yrot = 0.0f;
}

void eglSubWindow::OnSize(WPARAM wParam, LPARAM lParam)
{
GLsizei width = LOWORD(lParam);
GLsizei height = HIWORD(lParam);
if (height == 0)
height = 1;

glViewport( 0, 0, width, height );
glMatrixMode(GL_PROJECTION);
glLoadIdentity();

glOrtho(-5, 5, -5, 5, 1.0f, 100.0f);

glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}

void eglSubWindow::OnLButtonDown(WPARAM wParam, LPARAM lParam)
{
oldPoint.x = LOWORD(lParam);
oldPoint.y = HIWORD(lParam);
SetCapture(GetHWND());
}

void eglSubWindow::OnLButtonUp(WPARAM wParam, LPARAM lParam)
{
oldPoint.x = 0;
oldPoint.y = 0;
ReleaseCapture();
}

void eglSubWindow::OnMouseMove(WPARAM wParam, LPARAM lParam)
{
if(GetCapture()==GetHWND())
{
yrot = LOWORD(lParam) - oldPoint.y/3.6;
xrot = HIWORD(lParam) - oldPoint.x/3.6;
}
}

void eglSubWindow::RenderGLScene(void)
{
eglWindow::RenderGLScene();

glTranslatef(0.0f, 0.0f, -10.0f);
glRotatef(xrot, 1.0f, 0.0f, 0.0f);
glRotatef(yrot, 0.0f, 1.0f, 0.0f);

glLineStipple(4, pattern);

glEnable(GL_LINE_STIPPLE);
glBegin(GL_LINES);
glColor3f(1.0f, 0.0f, 0.0f);
glVertex3f(-4.0f, 0.0f, 0.0f);
glVertex3f( 0.0f, 0.0f, 0.0f);
glEnd();
glDisable(GL_LINE_STIPPLE);

glBegin(GL_LINES);
glVertex3f( 0.0f, 0.0f, 0.0f);
glVertex3f( 4.0f, 0.0f, 0.0f);
glEnd();

glEnable(GL_LINE_STIPPLE);
glBegin(GL_LINES);
glColor3f(0.0f, 1.0f, 0.0f);
glVertex3f(0.0f, 4.0f, 0.0f);
glVertex3f(0.0f, 0.0f, 0.0f);
glEnd();
glDisable(GL_LINE_STIPPLE);

glBegin(GL_LINES);
glVertex3f(0.0f, 0.0f, 0.0f);
glVertex3f(0.0f,-4.0f, 0.0f);
glEnd();

glEnable(GL_LINE_STIPPLE);
glBegin(GL_LINES);
glColor3f(0.0f, 0.0f, 1.0f);
glVertex3f(0.0f, 0.0f, -4.0f);
glVertex3f(0.0f, 0.0f, 0.0f);
glEnd();
glDisable(GL_LINE_STIPPLE);

glBegin(GL_LINES);
glVertex3f(0.0f, 0.0f, 0.0f);
glVertex3f(0.0f, 0.0f, 4.0f);
glEnd();
}

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,
  int nShowCmd)
{
eglSubWindow app;
app.Create(FALSE, "EDin's OpenGL glLineStipple");
return app.Run();
}

SOURCE CODE ARCHIVES
DOWNLOAD : Visual C++ [ glLine1.zip ] [ glLine2.zip ]
DOWNLOAD : Borland C++ Builder [ glLine1.zip ] [ glLine2.zip ]

BUILD TEST
Visual C++ 6.0 ServicePack 5
Borland C++ 6.0 Build Number 10.157


OpenGL 튜토리얼<기초편>

OpenGL 튜토리얼<기초편> - #5

다각형 그리기

by 김성철 ( paranwave@korea.com )

  • GL_POLYGON
  • GL_TRIANGLES
  • GL_QUADS

점의 집합이 선을 이루고 선의 집합이 다각형을 이루며 다각형의 집합은 다면체를 이룬다. 오늘은 2D 의 다각형을 그려보자. 다각형은 3개 이상의 정점으로 구성된다. 2개의 정점은 선을 이루고 3개의 정점은 삼각형을 이루며 4개의 정점은 사각형을 이룬다. 이 처럼 정점이 늘어나면 늘어날 수록 다각형이 그려지게 된다. OpenGL 에서는 다각형의 종류를 3 가지로 분류해 놓는다. 삼각형, 사각형, 다각형. 사각형까지만 있고 5각형 6각형 등이 없는 이유는 삼각형을 조합하면 얼마든지 우리가 원하는 다각형을 그릴 수 있기 때문이다. OpenGL 에서 삼각형을 그리려면 GL_TRIANGLES 를 사용해야하고 사각형은 GL_QUADS 그리고 다각형은 GL_POLYGON 을 사용하면 된다.

위의 그림은 삼각형을 그린 것이며 아래는 소스 코드다. 코드가 간단하므로 코드 모음은 올리지 않겠다.

void eglSubWindow::RenderGLScene(void)
{
eglWindow::RenderGLScene();

glTranslatef(0.0f, 0.0f, -3.0f);

glBegin( GL_TRIANGLES );
glVertex3f( -1.0f, -1.0f, 0.0f
  );
glVertex3f(  1.0f, -1.0f,
  0.0f );
glVertex3f(  0.0f,  1.0f,
  0.0f );
glEnd();
}

연결된 삼각형을 그리려 할 때 어떻게 해야할까? 물론 6 개의 정점으로 만들 수도 있겠지만 4 개의 정점만으로도 연결된 삼각형을 만들 수 있다. 그렇게 그리는 것이 GL_TRIANGLE_STRIP 이며 아래는 이를 이용한 예제의 그림이다.

아래는 이 예제의 소스 코드다.

void eglSubWindow::RenderGLScene(void)
{
eglWindow::RenderGLScene();

glTranslatef(0.0f, 0.0f, -3.0f);

glBegin( GL_TRIANGLE_STRIP );
glVertex3f( -1.0f, -1.0f, 0.0f
  );
glVertex3f(  1.0f, -1.0f,
  0.0f );
glVertex3f(  0.0f,  1.0f,
  0.0f );
glVertex3f(  1.0f,  0.5f,
  0.0f ); // 하나의 점을 추가했다.
glEnd();
}

GL_TRIANGLE_STRIP 처럼 연결된 삼각형을 그리지만 하나의 점을 중심으로 그려나가려면 GL_TRIANGLE_FAN 을 사용하면 된다. 아래의 그림은 이를 이용하여 그린 것이다. 위의 그림과 비교해보자.

아래의 이 예제의 소스 코드다.

void eglSubWindow::RenderGLScene(void)
{
eglWindow::RenderGLScene();

glTranslatef(0.0f, 0.0f, -3.0f);

glBegin( GL_TRIANGLE_FAN );
glVertex3f( -1.0f, -1.0f, 0.0f
  );
glVertex3f(  1.0f, -1.0f,
  0.0f );
glVertex3f(  0.0f,  1.0f,
  0.0f );
glVertex3f(  1.0f,  0.5f,
  0.0f ); // 하나의 점을 추가했다.
glEnd();
}

이제 사각형을 그려보자. 사각형을 그리려면 GL_QUADS 를 사용하고 정점을 4 개 설정하면 된다.

아래는 위 예제의 소스코드다.

void eglSubWindow::RenderGLScene(void)
{
eglWindow::RenderGLScene();

glTranslatef(0.0f, 0.0f, -3.0f);

glBegin( GL_QUADS );
glVertex3f( -1.0f, -1.0f, 0.0f
  ); //사각형을 그리기 위해 4개의 정점을 설정한다.
glVertex3f(  1.0f, -1.0f,
  0.0f );
glVertex3f(  1.0f,  1.0f,
  0.0f );
glVertex3f( -1.0f,  1.0f,
  0.0f );
glEnd();
}

4각형 이상의 다각형을 그리려면 GL_POLYGON 을 이용하면 된다. 다각형에는 유효한 다각형과 유효하지 않은 다각형이 있는데 선이 서로 겹쳐져서 그려지는 다각형이나 다각형내에 다각형이 들어있는 다각형은 유효하지 않은 다각형이다. GL_LINE_LOOP 와 GL_POLYGON 이 서로 비슷하게 느껴지지만 차이점이 있다. 그것은 내부가 채워지느냐 그렇지 않느냐이며 GL_LINE_LOOP 는 내부를 채우지 않고 GL_POLYGON 은 내부를 채우게 된다. 아래의 예제 실행 예는 이 차이를 보여준다.

void eglSubWindow::RenderGLScene(void)
{
eglWindow::RenderGLScene();

glTranslatef(0.0f, 0.0f, -6.0f);

glBegin( GL_LINE_LOOP );
for(int i=0; i<360; ++i)
{
glVertex3f(
  1.0f*cos(i*(3.14152/180)), 1.0f*sin(i*(3.14152/180)), 0.0f );
}
glEnd();
}

void eglSubWindow::RenderGLScene(void)
{
eglWindow::RenderGLScene();

glTranslatef(0.0f, 0.0f, -6.0f);

glBegin( GL_POLYGON );
for(int i=0; i<360; ++i)
{
glVertex3f(
  1.0f*cos(i*(3.14152/180)), 1.0f*sin(i*(3.14152/180)), 0.0f );
}
glEnd();
}

GL_POLYGON 으로 그려진 그림이 항상 내부가 채워지는 것은 아니다. 이는 glPolyginMode() 함수로 설정할 수가 있다.

glPolygonMode( GL_FRONT, GL_FILL ); // 앞 면을 채운다.
glPolygonMode( GL_BACK, GL_LINE); // 뒷면은 선으로 이어진 다각형을 그린다.

이 함수의 적용은 직접 실행해 보길 바란다.


OpenGL 튜토리얼<기초편>

OpenGL 튜토리얼<기초편> - #6

색 표현하기

by 김성철 ( paranwave@korea.com )

  • glColor3f()
  • glShadeModel()
  • GL_SMOOTH
  • GL_FLAT

OpenGL 에서의 색(color)은 세가지 색의 혼합으로 이루어진다. 그 세가지 색은 적색(RED), 녹색(GREEN), 청색(BLUE) 이고 흔히 부르는 RGB 이다. 컴퓨터에서 이 세가지 색으로 모든 색을 표현하는 것은 우리 인간의 눈이 이 세가지 색에 가장 민감하기 때문이다. 즉 이 세가지 색의 변화를 가장 잘 느낀다는 것이다. 아래는 OpenGL 에서 색을 나타내는 함수의 목록이다.

void glColor3b( GLbyte red, GLbyte green, GLbyte blue )
void glColor3d( GLdouble red, GLdouble green, GLdouble blue )
void glColor3f( GLfloat red, GLfloat green, GLfloat blue )
void glColor3i( GLint red, GLint green, GLint blue )
void glColor3s( GLshort red, GLshort green, GLshort blue )
void glColor3ub( GLubyte red, GLubyte green, GLubyte blue )
void glColor3ui( GLuint red, GLuint green, GLuint blue )
void glColor3us( GLushort red, GLushort green, GLushort blue )
void glColor4b( GLbyte red, GLbyte green, GLbyte blue, GLbyte alpha )
void glColor4d( GLdouble red, GLdouble green, Ldouble blue, GLdouble alpha )
void glColor4f( GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha )
void glColor4i( GLint red, GLint green, GLint blue, GLint alpha )
void glColor4s( GLshort red, GLshort green, GLshort blue, GLshort alpha )
void glColor4ub( GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha )
void glColor4ui( GLuint red, GLuint green, GLuint blue, GLuint alpha )
void glColor4us( GLushort red, GLushort green, GLushort blue, GLushort alpha )

void glColor3bv( const GLbyte *v )
void glColor3dv( const GLdouble *v )
void glColor3fv( const GLfloat *v )
void glColor3iv( const GLint *v )
void glColor3sv( const GLshort *v )
void glColor3ubv( const GLubyte *v )
void glColor3uiv( const GLuint *v )
void glColor3usv( const GLushort *v )
void glColor4bv( const GLbyte *v )
void glColor4dv( const GLdouble *v )
void glColor4fv( const GLfloat *v )
void glColor4iv( const GLint *v )
void glColor4sv( const GLshort *v )
void glColor4ubv( const GLubyte *v )
void glColor4uiv( const GLuint *v )
void glColor4usv( const GLushort *v )

위의 함수들을 보면 정수형의 컬러값을 인자로 받는 함수와 부동소수형 컬러값을 인자로 받는 함수로 나눌 수 있다. 정수형은 컬러값을 0~255 값 즉 각 색깔마다 색을 256단계로 설정할 수 있다. 따라서 256*256*256 = 16777216 의 색을 표현할 수 있다. 부동소수형은 0.0 ~ 1.0 까지 컬러값을 설정할 수 있다. 0.0 과 1.0 의 사이의 수는 무한대이기 때문에 이론상으로는 무한대의 단계로 색을 표현할 수 있다. 정수형보다는 더욱 세밀하게 색을 표현할 수 있다는 것을 기억하면 될 것이다. 그리고 알파(alpha) 값이 있는데 이 것은 색의 투명도를 나타낸다.

BOOL eglSubWindow::InitGL(void)
{
eglWindow::InitGL();

glShadeModel(GL_SMOOTH);
return TRUE;
}
void eglSubWindow::RenderGLScene(void)
{
eglWindow::RenderGLScene();

glTranslatef(0.0f, 0.0f, -3.0f);
glBegin(GL_TRIANGLES);
glColor3f(1.0f, 0.0f, 0.0f);
  //RED
glVertex3f( 0.0f, 1.0f, 0.0f);
glColor3f(0.0f, 1.0f, 0.0f);
  //GREEN
glVertex3f(-1.0f,-1.0f, 0.0f);
glColor3f(0.0f, 0.0f, 1.0f);
  //BLUE
glVertex3f( 1.0f,-1.0f, 0.0f);
glEnd();
}

윗 코드의 실행 결과를 보면 색이 각 정점에서 시작해서 삼각형의 중심에서는 서로의 색이 고르게 혼합되어 있다. 이 것은 쉐이딩 모델을 설정하는 glShadeModel() 함수에 의해서 결정되는 것이다. 이 함수의 원형은 아래와 같다.

glShadeModel( GLenum mode )

mode 값으로는 GL_SMOOTH 와 GL_FLAT 이 있다. GL_SMOOTH 는 두 정점 간에 설정된 색을 부드럽게 이어주며 GL_FLAT 은 마지막 정점에 설정된 색으로 폴리곤의 내부를 채운다.

BOOL eglSubWindow::InitGL(void)
{
eglWindow::InitGL();

glShadeModel(GL_FLAT);
return TRUE;
}
void eglSubWindow::RenderGLScene(void)
{
eglWindow::RenderGLScene();

glTranslatef(0.0f, 0.0f, -3.0f);
glBegin(GL_TRIANGLES);
glColor3f(1.0f, 0.0f, 0.0f);
  //RED
glVertex3f( 0.0f, 1.0f, 0.0f);
glColor3f(0.0f, 1.0f, 0.0f);
  //GREEN
glVertex3f(-1.0f,-1.0f, 0.0f);
glColor3f(0.0f, 0.0f, 1.0f);
  //BLUE
glVertex3f( 1.0f,-1.0f, 0.0f);
glEnd();
}

쉐이딩 모델을 GL_FLAT 으로 설정하자 폴리곤의 내부가 마지막 정점에 설정된 청색으로 채워졌다.


OpenGL 튜토리얼<기초편>

OpenGL 튜토리얼<기초편> - #7

3차원 폴리곤 그리기

by 김성철 ( paranwave@korea.com )

  • glBegin()
  • glEnd()
  • GL_TRIANGLES
  • GL_QUADS
  • glut Objects

우리는 지금까지 2D 의 평면 폴리곤을 그려왔다. 2D 의 평면에서는 점이 X, Y 축의 좌표로 표현되었다. X 축은 폭, Y 축은 높이다. 3D 에서는 하나의 좌표축을 더 추가해 점이 X, Y, Z 축의 좌표로 표현된다. 여기서 X 축은 폭, Y 축은 높이 Z 축은 깊이를 나타낸다.

위의 그림은 피라미드(각뿔)를 그리는 과정의 일부를 그려본 것이다. 피라미드를 그리기 위해서는 4개의 삼각형면이 필요한데 OpenGL 에서는 glBegin() 과 glEnd() 사이에 삼각형 4개를 그려주면 자연스러운 3D 피라미드가 그려진다. 프레임웤의 내용이 조금 변경이 되었다. 몇몇 클래스의 이름이 바뀌었고 egl 이라느 네임스페이스를 사용한다. 때문에 전체 소스 코드를 적어보았다.

#include <windows.h>
#include "lib\egl.h"

using namespace egl;

class RenderWindow : public Window
{
private:
GLfloat rot;
public:
virtual void RenderGLScene(void);
virtual void OnCreate(WPARAM wParam, LPARAM lParam);
};

void RenderWindow::OnCreate(WPARAM wParam, LPARAM lParam)
{
rot = 0.0f;
}

void RenderWindow::RenderGLScene(void)
{
Window::RenderGLScene();

glTranslatef(0.0f, 0.0f, -5.0f);
glRotatef(rot, 0.0f, 1.0f, 0.0f);

glBegin( GL_TRIANGLES );
glColor3f(1.0f, 0.0f, 0.0f);
glVertex3f( 0.0f, 1.0f, 0.0f);
  // { Front }
glVertex3f(-1.0f,-1.0f, 1.0f);
  // { Front }
glVertex3f( 1.0f,-1.0f, 1.0f);
  // { Front }

glColor3f(0.0f, 1.0f, 0.0f);
glVertex3f( 0.0f, 1.0f, 0.0f);
  // { Right }
glVertex3f( 1.0f,-1.0f, 1.0f);
  // { Right }
glVertex3f( 1.0f,-1.0f,-1.0f);
  // { Right }

glColor3f(0.0f, 0.0f, 1.0f);
glVertex3f( 0.0f, 1.0f, 0.0f);
  // { Back }
glVertex3f( 1.0f,-1.0f,-1.0f);
  // { Back }
glVertex3f(-1.0f,-1.0f,-1.0f);
  // { Back }

glColor3f(1.0f, 0.0f, 1.0f);
glVertex3f( 0.0f, 1.0f, 0.0f);
  // { Left }
glVertex3f(-1.0f,-1.0f,-1.0f);
  // { Left }
glVertex3f(-1.0f,-1.0f, 1.0f);
  // { Left }
glEnd();

rot += 0.01f;
}

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,
  int nShowCmd)
{
RenderWindow app;
app.Create(FALSE,"EDin's OpenGL - 3D Pyramid");
return app.Run();
}

큐브(정육면체)를 그리기 위해서는 사각형면 6 개를 그려야한다. 이 또한 glBegin() 과 glEnd() 사이에 6 개의 사각형을 그려주면 자연스런 큐브가 그려진다.

void RenderWindow::RenderGLScene(void)
{
Window::RenderGLScene();

glTranslatef( 0.0f, 0.0f, -5.0f);

glRotatef(rot, 1.0f, 0.0f, 0.0f);
glRotatef(rot, 0.0f, 1.0f, 0.0f);

glBegin( GL_QUADS );
glColor3f(1.0f, 0.0f, 0.0f);
  glVertex3f(-1.0f, 1.0f, 1.0f); // { Front }
glColor3f(0.0f, 1.0f, 0.0f);
  glVertex3f(-1.0f,-1.0f, 1.0f); // { Front }
glColor3f(0.0f, 0.0f, 1.0f);
  glVertex3f( 1.0f,-1.0f, 1.0f); // { Front }
glColor3f(1.0f, 0.0f, 1.0f);
  glVertex3f( 1.0f, 1.0f, 1.0f); // { Front }

glColor3f(1.0f, 0.0f, 1.0f);
  glVertex3f( 1.0f, 1.0f, 1.0f); // { Right }
glColor3f(0.0f, 0.0f, 1.0f);
  glVertex3f( 1.0f,-1.0f, 1.0f); // { Right }
glColor3f(0.0f, 1.0f, 0.0f);
  glVertex3f( 1.0f,-1.0f,-1.0f); // { Right }
glColor3f(1.0f, 0.0f, 0.0f);
  glVertex3f( 1.0f, 1.0f,-1.0f); // { Right }

glColor3f(1.0f, 0.0f, 0.0f);
  glVertex3f( 1.0f, 1.0f,-1.0f); // { Back }
glColor3f(0.0f, 1.0f, 0.0f);
  glVertex3f( 1.0f,-1.0f,-1.0f); // { Back }
glColor3f(0.0f, 0.0f, 1.0f);
  glVertex3f(-1.0f,-1.0f,-1.0f); // { Back }
glColor3f(1.0f, 0.0f, 1.0f);
  glVertex3f(-1.0f, 1.0f,-1.0f); // { Back }

glColor3f(1.0f, 0.0f, 1.0f);
  glVertex3f(-1.0f, 1.0f,-1.0f); // { Left }
glColor3f(0.0f, 0.0f, 1.0f);
  glVertex3f(-1.0f,-1.0f,-1.0f); // { Left }
glColor3f(0.0f, 1.0f, 0.0f);
  glVertex3f(-1.0f,-1.0f, 1.0f); // { Left }
glColor3f(1.0f, 0.0f, 0.0f);
  glVertex3f(-1.0f, 1.0f, 1.0f); // { Left }

glColor3f(1.0f, 0.0f, 0.0f);
  glVertex3f(-1.0f, 1.0f, 1.0f); // { Top }
glColor3f(0.0f, 1.0f, 0.0f);
  glVertex3f( 1.0f, 1.0f, 1.0f); // { Top }
glColor3f(0.0f, 0.0f, 1.0f);
  glVertex3f( 1.0f, 1.0f,-1.0f); // { Top }
glColor3f(1.0f, 0.0f, 1.0f);
  glVertex3f(-1.0f, 1.0f,-1.0f); // { Top }

glColor3f(1.0f, 0.0f, 1.0f);
  glVertex3f( 1.0f,-1.0f, 1.0f); // { Bottom }
glColor3f(0.0f, 0.0f, 1.0f);
  glVertex3f(-1.0f,-1.0f, 1.0f); // { Bottom }
glColor3f(0.0f, 1.0f, 0.0f);
  glVertex3f(-1.0f,-1.0f,-1.0f); // { Bottom }
glColor3f(1.0f, 0.0f, 0.0f);
  glVertex3f( 1.0f,-1.0f,-1.0f); // { Bottom }
glEnd();

rot += 0.01f;
}

이 밖에도 glut 라이브러리는 우리들에게 복잡한 3D 폴리곤 객체를 쉽게 그릴 수 있는 함수를 제공한다. glut 에서 제공하는 3D 폴리곤 객체는 다음과 같다.

  • 원뿔( Cone )
  • 4면체 ( Tetrahedron )
  • 6면체 ( Cube )
  • 8면체( Octahedron )
  • 12면체 ( Dodecahedron )
  • 20면체 ( Icosahedron )
  • 구 ( Sphere )
  • Torus
  • 주전자 ( Teapot )

이들을 그려주는 프로그램과 소스코드는 이 [ ExLight.zip ] 파일을 참고하길 바란다. 아래의 그림에서 노란색 원은 광원(후에 설명할...)이다.

원뿔 | 4면체 | 6면체
8 면체 | 12 면체 | 20 면체
구 | Torus | 주전자

SOURCE CODE ARCHIVES
DOWNLOAD : Visual C++ [ Pyramid.zip ]
DOWNLOAD : Borland C++ Builder [ Pyramid.zip ]

BUILD TEST
Visual C++ 6.0 ServicePack 5
Borland C++ 6.0 Build Number 10.157


OpenGL 튜토리얼<기초편>

OpenGL 튜토리얼<기초편> - #8

투영 변환

by 김성철 ( paranwave@korea.com )

  • GL_PROJECTION
  • glOrtho()
  • glFrustum()
  • gluPerspective()

OpenGL 의 렌더링 파이프 안에서 변환이 일어나는 것 중에 아핀 변환과 투영 변환이 있다. 아핀 변환(affine transformation)은 정점의 이동, 회전, 스케일 등의 변환를 말하며 이 변환의 특징은 변환 후에도 변환 전의 평행성과 비율을 보존해 준다는 것이다. 자세한 설명은 'OpenGL 을 통한 3차원 그래픽스 프로그래밍 기초편 - 임인성[도서출판그린]' 을 읽어보길 바란다. 투영 변환(projection transformation)은 n > m 이라 할 때 n 차원 공간의 점을 m 차원 공간의 점으로 바꾸어주는 변환을 말한다. 3차원의 한 점 p(x, y, z) 를 2차원의 한 점 p'(x, y) 로 변환하는 것을 예로 들 수 있다. OpenGL 의 렌더링 파이프 라인에서는 아핀 변환 후에 투영 변환이 일어난다. OpenGL 에서 아핀 변환에 쓰이는 함수는 glTranslatef(), glRotatef(), glScalef() 등이 있으며 투영 변환에 쓰이는 함수는 glOrtho() 함수와 glFrustum() 함수가 있고 glFrustum() 함수를 쓰기 쉽게 만들어 놓은 gluPerspective() 함수가 있다. OpenGL 에서의 투영 변환에는 2 가지의 변환이 있으며 하나는 직교 투영이고 다른 하나는 원근 투영이다. 이 두가지 외의 투영을 하려면 커스텀 투영 행렬을 만들어 현재 투영 행렬 스택에 glMultMatrix() 라는 함수를 이용해서 투영 행렬 스택의 최상단에 올려 놓으면 된다.

직교투영을 만드는 함수 glOrtho() 의 원형은 다음과 같다.

void glOrtho( GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near, GLdouble far )

이 함수에서 인자들의 의미는 아래 그림에 자세하게 설명되어 있다.

위의 그림에서 (left, bottom) - (right, top) 에 의해서 정의 되는 면은 클립핑된 영역이며 이 면에 2D 의 그림이 그려지게 된다. 즉 뒤의 육면체에서 우리가 생각하는 3D 의 폴리곤이 그려지고 아핀변환이 일어난 후에 클리핑된 면으로 투영 변환(여기서는 직교 변환, 3D 에서 2D 로 변환)이 일어나게 된다. 이 직교 투영의 변환 행렬은 아래와 같다.

원근투영을 만드는 함수 glFrustum() 의 원형은 다음과 같다.

void glFrustum( GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near, GLdouble far )

이 함수에서 인자들의 의미는 아래 그림에 자세하게 설명되어 있다.

위의 그림에서 (left, bottom) - (right, top) 에 의해서 정의 되는 면은 클립핑된 영역이며 이 면에 2D 의 그림이 그려지게 된다. 즉 육면체(절두체)에서 우리가 생각하는 3D 의 폴리곤이 그려지고 아핀변환이 일어난 후에 클리핑된 면으로 투영 변환(여기서는 원근변환, 3D 에서 2D 로 변환)이 일어나게 된다. 이 원근 투영의 변환 행렬은 아래와 같다.

glFrustum() 함수를 좀 더 쉽게 쓰기 위해서 gluPerspective() 함수가 있는데 이 함수에 의해서 생성되는 원근 투영 행렬은 위의 행렬과는 다르다. 이는 직접 책을 찾아 보는 것이 좋을 것 같다. ;]

위의 두 행렬을 비교해 보면 아핀 변환을 할 수 있는 아핀 공간의 점은 p(x, y, z, w) 에서 w = 1 이다. 직교 투영 행렬은 w = 1 인 반면에 원근 투영 행렬은 w 가 1 이 아니다. 이 때문에 직교 투영은 아핀 변환의 특징인 평행성과 비율이 보존 되지만 원근 투영은 보존되지 않는다. 그렇다면 원근 투영에서 아핀 변환은 어떻게 할 수 있을까? 이에 대한 해답은 위에서 언급한 서적을 읽어보길 바란다.

아래의 그림은 아주 간단하게 만들어 본 직교 투영의 예제 프로그램 그림과 소스 코드이다. 원근감이 느껴지지 않는다.

void RenderWindow::OnSize(WPARAM wParam, LPARAM lParam)
{
GLsizei width = LOWORD(lParam);
GLsizei height = HIWORD(lParam);

if (height == 0)
height = 1;

glViewport( 0, 0, width, height );

glMatrixMode(GL_PROJECTION);
glLoadIdentity();

glOrtho(-3.0f, 3.0f, -3.0f, 3.0, 1.0f, 100.0f);

glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}

BOOL RenderWindow::InitGL(void)
{
Window::InitGL();

glEnable(GL_LIGHTING);

GLfloat lightPos[4] = { 0.5f, 0.5f, 0.5f, 1.0f };
glLightfv(GL_LIGHT0, GL_POSITION, lightPos);
glEnable(GL_LIGHT0);

return TRUE;
}

void RenderWindow::RenderGLScene(void)
{
Window::RenderGLScene();

glPushMatrix();
glTranslatef(0.0f, 0.0f, -2.0f);
glRotatef(33.0f, 1.0f, 0.0f,
  0.0f);
glRotatef(33.0f, 0.0f, 1.0f,
  0.0f);
glColor3f(0.5f, 0.5f, 0.5f);
glutSolidTeapot(0.4f);
glPopMatrix();

glPushMatrix();
glTranslatef(1.0f, 0.0f, -4.0f);
glRotatef(33.0f, 1.0f, 0.0f,
  0.0f);
glRotatef(33.0f, 0.0f, 1.0f,
  0.0f);
glColor3f(0.3f, 0.3f, 0.3f);
glutSolidTeapot(0.4f);
glPopMatrix();

glPushMatrix();
glTranslatef(-1.0f, 0.0f, -6.0f);
glRotatef(33.0f, 1.0f, 0.0f,
  0.0f);
glRotatef(33.0f, 0.0f, 1.0f,
  0.0f);
glColor3f(0.1f, 0.1f, 0.1f);
glutSolidTeapot(0.4f);
glPopMatrix();
}

아래의 그림은 아주 간단하게 만들어 본 원근 투영의 예제 프로그램 그림과 소스 코드이다. 원근감이 느껴진다.

void RenderWindow::OnSize(WPARAM wParam, LPARAM lParam)
{
GLsizei width = LOWORD(lParam);
GLsizei height = HIWORD(lParam);

if (height == 0)
height = 1;

glViewport( 0, 0, width, height );

glMatrixMode(GL_PROJECTION);
glLoadIdentity();

glFrustum(-1.0f, 1.0f, -1.0f, 1.0, 1.0f, 10.0f);

glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}

BOOL RenderWindow::InitGL(void)
{
Window::InitGL();

glEnable(GL_LIGHTING);

GLfloat lightPos[4] = { 0.5f, 0.5f, 0.5f, 1.0f };
glLightfv(GL_LIGHT0, GL_POSITION, lightPos);
glEnable(GL_LIGHT0);

return TRUE;
}

void RenderWindow::RenderGLScene(void)
{
Window::RenderGLScene();

glPushMatrix();
glTranslatef(0.0f, 0.0f, -2.0f);
glRotatef(33.0f, 1.0f, 0.0f,
  0.0f);
glRotatef(33.0f, 0.0f, 1.0f,
  0.0f);
glColor3f(0.5f, 0.5f, 0.5f);
glutSolidTeapot(0.4f);
glPopMatrix();

glPushMatrix();
glTranslatef(1.0f, 0.0f, -4.0f);
glRotatef(33.0f, 1.0f, 0.0f,
  0.0f);
glRotatef(33.0f, 0.0f, 1.0f,
  0.0f);
glColor3f(0.3f, 0.3f, 0.3f);
glutSolidTeapot(0.4f);
glPopMatrix();

glPushMatrix();
glTranslatef(-1.0f, 0.0f, -6.0f);
glRotatef(33.0f, 1.0f, 0.0f,
  0.0f);
glRotatef(33.0f, 0.0f, 1.0f,
  0.0f);
glColor3f(0.1f, 0.1f, 0.1f);
glutSolidTeapot(0.4f);
glPopMatrix();
}


OpenGL 튜토리얼<기초편>

OpenGL 튜토리얼<기초편> - #9

이동, 회전, 크기 변환

by 김성철 ( paranwave@korea.com )

  • GL_MODELVIEW
  • glMatrixMode()
  • glTranslatef()
  • glRotatef()
  • glScalef()

앞 장에서 아핀 공간에서 일어 날 수 있는 변환 중에 이동 변환, 회전 변환, 크기 변환이 있다고 얘기했었다. OpenGL 에서는 이러한 변환을 위해서 변환 행렬을 만들어 주는 몇몇 함수가 있으며 그 함수의 목록은 아래에 나열되어 있다. 이 함수들에 의해서 만들어진 변환행렬은 OpenGL 의 모델뷰 행렬 스택(MODEL-VIEW Matrix Stack)의 최상단에 곱해지게 된다. 따라서 이러한 변환을 수행하려 한다면 현재 행렬 스택 모드를 모델뷰 행렬 스택으로 설정해 주어야 하며 그 후에 변환 함수등을 이용해 정점의 변환을 조작해야 한다.

glMatrixMode( GL_MODELVIEW ); //모델뷰 행렬 스택 선택
...변환 조작...

OpenGL 에서 정점의 이동은 glTranslatef() 함수를 이용한다. glTranslatef() 함수의 원형은 아래와 같다.

void glTranslatef( GLfloat x, GLfloat y, GLfloat z )

이 함수에 의해서 생성되는 변환 행렬은 아래와 같다.

이 행렬이 현재 모델뷰행렬에 곱해져 정점의 이동 변환이 이뤄진다.

glScalef() 함수는 크기를 변환하는데 정점의 크기를 변환하는 것이 아니라 정점간의 거리, 즉 벡터의 길이를 늘리고 줄이는 것이다. 이 스케일 변환은 glScalef() 함수를 이용한다.

void glScalef( GLfloat x, GLfloat y, GLfloat z )

이 함수에 의해서 생성되는 변환 행렬은 아래와 같다.

회전 변환은 glRotatef() 함수를 이용한다. 이 함수의 원형은 아래와 같다.

void glRotatef( GLfloat angle, GLfloat x, GLfloat y, GLfloat z )

이 함수는 지정된 angle 만큼 한 축을 기준으로 회전한다. 즉 glRotatef( 30.0f, 1.0f, 0.0f, 0.0f) 라 하면 x 축을 중심으로 30 도 회전하는 것이다. 각각의 축에 따라 회전 변환 행렬이 다르게 생성된다. 아래는 이 함수에 의해서 생성되는 각각의 회전 변환 행렬이다.

아래의 프로그램은 위의 아핀 변환을 이용하여 만든 것이다.

void RenderWindow::RenderGLScene(void)
{
Window::RenderGLScene();

static GLfloat rot=1.0f;
static GLfloat scale=1.0f,factor=0.01f;
glTranslatef(-2.0f, 0.0f, -10.0f);

//5개의 삼각형 이용해서 별만들기
glRotatef(rot,0.0f, 0.0f, 1.0f); //별
  전체 회전
for(int i=1; i<=5; i++)
{
glPushMatrix();
glRotatef(72*(i-1),
  0.0f, 0.0f, 1.0f); //5각형을 중심으로 삼각형 배치
glTranslatef(0.0f,
  1.35, 0.0f); //삼각형이 겹침을 막기 위해서 약간 이동
glRotatef(i*rot,
  1.0f, 0.0f, 0.0f); //각각회전
glBegin(GL_TRIANGLES);
glVertex3f(-1.0f,
  0.0f, 0.0f);
glVertex3f(
  1.0f, 0.0f, 0.0f);
glVertex3f(
  0.0f, 1.0f, 0.0f);
glEnd();
glPopMatrix();
}
//모델뷰행렬초기화
glLoadIdentity();
glTranslatef(2.0f, 0.0f, -10.0f);
glScalef(scale, scale, scale); //크기변환
glRotatef(rot, 0.0f, 1.0f, 0.0f); //Y축을
  중심으로 회전
glRotatef(45.0f, 0.0f, 0.0f, 1.0f); //Z축을
  중심으로 45도회전, 마름모
glBegin(GL_QUADS);
glColor3f(1.0f, 0.0f, 0.0f);
  glVertex3f(-0.4f, -0.4f, 0.0f);
glColor3f(0.0f, 1.0f, 0.0f);
  glVertex3f( 0.4f, -0.4f, 0.0f);
glColor3f(0.0f, 0.0f, 1.0f);
  glVertex3f( 0.4f,  0.4f, 0.0f);
glColor3f(1.0f, 1.0f, 0.0f);
  glVertex3f(-0.4f,  0.4f, 0.0f);
glEnd();

rot += 0.1f; //회전각도증가
scale += factor; //크기 변환 배율

if(scale >= 5.0f)
{
factor = -factor;
}
else if(scale <= 0.0f)
{
factor = -factor;
}
}

SOURCE CODE ARCHIVES
DOWNLOAD : Visual C++ [ AffineTransformation.zip ]
DOWNLOAD : Borland C++ Builder [ AffineTransformation.zip ]

BUILD TEST
Visual C++ 6.0 ServicePack 5
Borland C++ 6.0 Build Number 10.157


OpenGL 튜토리얼<기초편>

OpenGL 튜토리얼<기초편> - #10

빛, 재질 표현하기

by 김성철 ( paranwave@korea.com )

  • GL_LIGHTING
  • GL_LIGHT
  • GL_AMBIENT
  • GL_DIFFUSE
  • GL_SPECULAR
  • GL_POSITION
  • GL_SPOT_CUTOFF
  • GL_SPOT_DIRECTION
  • glEnable()
  • glLightfv()
  • glNormal
  • glMaterialfv()

3D 그래픽스에서 빛이란 없어서는 안 될 존재이다. 3D 그래픽에서 사실감은 무척 중요한 요소인데 이 사실감을 만들어주는 요소 중의 하나가 빛이기 때문이다. OpenGL 은 이렇게 중요한 빛을 몇몇 함수만을 사용하면 표현할 수 있게 아주 쉽게 만들어져 있다. OpenGL 의 빛은 영화 촬영장의 조명을 생각하면 쉽게 이해할 수 있다. 촬영장에서 조명은 배우들만 집중적으로 비추는 것이 있을 것이고, 촬영 배경을 밝게만 해주는 조명 등이 있을 것이다. 이렇게 촬영장에 각각 다른 성질을 부여하여 여러개의 조명을 설치하듯 OpenGL 에서도 여러 성질의 여러개의 조명을 설정할 수 있다. OpenGL 에서는 총 8개의 조명만 설치할 수 있고 각각의 조명이 가질 수 있는 성질에는 주변광, 발산광, 반사광, 스포트라이트가 있다.

아래는 각각의 조명에 대한 설명이며 'OpenGL 프로그래밍 우상수/사이버출판사' 의 내용을 인용했다.

주변광 ( Ambient Light )

모든 방향에서 나타나는 조명이다. 즉, 전체적인 분위기를 만들어 내며 단일 지점에서 사방으로 균등하게 광원을 방출하는 조명을 뜻한다. 갓을 씌우지 않은 백열등과 태양이 좋은 예이다.

발산광( Diffuse Light )

이 조명은 한 방향으로부터 나온다. 그리고 특정한 방향과 위치를 가지고 있다. 그러므로 오브젝트의 표면을 비스듬이 비추는 것보다 직각으로 비추는 것이 더욱 강렬한 조명이 된다. 그러나 이 조명의 광선이 오브젝트의 표면에 부딪치면 시야가 어디에 있든지 모든 방향으로 똑같이 반사된다. 테이블을 비추는 램프의 빛을 예로 들 수 있다.

반사광( Specular Light )

반사광은 일정한 방향으로부터 나온다. 일반적으로 높은 반사율을 가진 오브젝트의 표면에서 흰색에 가까운 색상으로 밝은 지점을 형성한다. 반사되는 빛이 해당 면을 바라보는 방향과 일치하는 경우 밝게 빛나게 되는데, 이 경우 밝게 빛나는 빛의 색상을 설정한다. 이 조명은 빛을 방출하는 방향이 있을 뿐 아니라, 태양의 흑점 중앙의 암흑부(Umbra) 라는 초점 개념도 있다. 레이저 빔이 가장 좋은 예이다.

스포트라이트( Spot Light )

가장 쉽게 인식할 수 있는 광원이다. 무대 위의 한 인물 혹은 한 곳에 집중된 광선을 말한다.

조명 켜기

OpenGL 에서 조명을 사용하려면 다음과 같이 glEnable() 함수를 이용해서 조명에 관련 된 상태 변수를 ON 해줘야한다.

glEnable( GL_LIGHTING ); //조명을 사용할 것이다.
glEnable( GL_LIGHT0 ); //조명 중 0 번 조명을 사용할 것이다.

조명을 사용하려면 사용할 조명의 성질을 glLightfv() 함수를 통해서 설정해 주어야 한다.

glLightfv( GL_LIGHT0, GL_AMBIENT, AmbientLightValue ); //Ambient 조명의 성질을 설정한다.
glLightfv( GL_LIGHT0, GL_DIFFUSE, DiffuseLightValue ); //Diffuse 조명의 성질을 설정한다.
glLightfv( GL_LIGHT0, GL_SPECULAR, SpecularLightValue ); //Specular 조명의 성질을 설정한다.
glLightfv( GL_LIGHT0, GL_POSTION, PotionLightValue ); //조명의 위치(광원)를 설정한다.

위에서 사용하는 각각의 성질값은 아래처럼 정의할 수 있다.

GLfloat AmbientLightValue[] = { 0.3f, 0.3f, 0.3f, 1.0f };
GLfloat DiffuseLightValue[] = { 0.7f, 0.7f, 0.7f, 1.0f };
GLfloat SpecularLightValue[] = { 1.0f, 1.0f, 1.0f, 1.0f };
GLfloat PositionLightValue[] = { 0.0f, 0.0f, 1.0f, 0.0f };

값을 위처럼 설정할 수도 있지만 조명을 켜기만하면 조명을 사용할 수 있도록 OpenGL 에서는 각각의 성질에 대한 기본 설정값을 가진다. 다른 기본 설정값도 있는데 이 것은 링크 싸이트의 OpenGL Programming Guide 싸이트를 참고하길 바란다.

GL_AMBIENT (0.0, 0.0, 0.0, 1.0) //Ambient 조명의 기본 설정값
GL_DIFFUSE (1.0, 1.0, 1.0, 1.0) //Diffuse 조명의 기본 설정값
GL_SPECULAR (1.0, 1.0, 1.0, 1.0) //Specular 조명의 기본 설정값
GL_POSITION (0.0, 0.0, 1.0, 0.0) //광원 위치의 기본 설정값
GL_SPOT_CUTOFF 180.0 //Spot 조명의 Cutoff 기본 설정값

다음 그림은 조명을 설치하여 폴리곤을 그려본 프로그램이다.

위 프로그램의 코드는 다음과 같다.

#include "lib\egl.h"

using namespace egl;

class RenderWindow : public Window
{
private:
GLfloat ambientLight[4];
GLfloat diffuseLight[4];
GLfloat lightPosition[4];
GLfloat specular[4];
GLfloat yrot;
public:
virtual BOOL InitGL(void);
virtual void RenderGLScene(void);
virtual void OnCreate(WPARAM wParam, LPARAM lParam);
};

void RenderWindow::OnCreate(WPARAM wParam, LPARAM lParam)
{
//조명의 성질 설정
ambientLight[0] = 0.3f;
ambientLight[1] = 0.3f;
ambientLight[2] = 0.3f;
ambientLight[3] = 1.0f;

diffuseLight[0] = 0.7f;
diffuseLight[1] = 0.7f;
diffuseLight[2] = 0.7f;
diffuseLight[3] = 1.0f;

lightPosition[0] = 0.0f;
lightPosition[1] = 0.0f;
lightPosition[2] = 5.0f;
lightPosition[3] = 1.0f;

specular[0] = 1.0f;
specular[1] = 1.0f;
specular[2] = 1.0f;
specular[3] = 1.0f;

yrot = 0.0f;
}

BOOL RenderWindow::InitGL(void)
{
Window::InitGL();

glClearColor(0.5f, 0.5f, 0.5f, 1.0f); //밝은
  회색을 배경색으로 설정
glFrontFace(GL_CCW); //반시계방향으로
  감은 폴리곤이 앞면
glEnable(GL_CULL_FACE); //가려진
  부분은 그려지지 않게 한다.

glEnable(GL_LIGHTING); //조명
  켜기
glLightfv(GL_LIGHT0, GL_AMBIENT, ambientLight); //설정
glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuseLight);
  //설정
glLightfv(GL_LIGHT0, GL_SPECULAR, specular); //설정
glLightfv(GL_LIGHT0, GL_POSITION, lightPosition); //설정
glEnable(GL_LIGHT0); // 0번
  조명 사용

return TRUE;
}

void RenderWindow::RenderGLScene(void)
{
Window::RenderGLScene();

glTranslatef(0.0f, 0.0f, -5.0f);
glRotatef(yrot, 0.0f, 1.0f, 0.0f);
//glColor3f(0.7f, 0.7f, 0.0f);

glBegin( GL_TRIANGLES );
//{
  Front }
glVertex3f( 0.0f, 1.0f, 0.0f);
glVertex3f(-1.0f,-1.0f, 1.0f);
glVertex3f( 1.0f,-1.0f, 1.0f);

//{
  Right }
glVertex3f( 0.0f, 1.0f, 0.0f
  );
glVertex3f( 1.0f,-1.0f, 1.0f
  );
glVertex3f( 1.0f,-1.0f,-1.0f
  );

//{
  Back }
glVertex3f( 0.0f, 1.0f, 0.0f
  );
glVertex3f( 1.0f,-1.0f,-1.0f
  );
glVertex3f(-1.0f,-1.0f,-1.0f
  );

//{
  Left }
glVertex3f( 0.0f, 1.0f, 0.0f
  );
glVertex3f(-1.0f,-1.0f,-1.0f
  );
glVertex3f(-1.0f,-1.0f, 1.0f
  );
glEnd();

yrot += 0.1f;
}

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,
  int nShowCmd)
{
RenderWindow app;
if(!app.Create(FALSE,"EDin's OpenGL - Light"))
return EXIT_FAILURE;
return app.Run();
}

위의 그림을 보면 조명을 설정했는데 폴리곤이 반짝 거리지도 않고 전혀 조명을 설정한 것 같지가 않다. 하지만 조명은 설정되었다. 주변광(Ambient Light)가 어두운 회색으로 설정 되었는데 폴리곤이 그 조명으로 인해서 어두운 회색으로 그려졌다. 그럼 이게 사실적인 모습인가? 아니다. 사실적인 모습을 그리려면 몇가지를 더 설정해 주어야한다. 그 하나는 빛이 반사될 방향을 설정할 수 있는 법선벡터를 정하는 것이고 다른 하나는 재질의 설정이다. 우선 법선 벡터를 설정한다는 것이 무엇인지 알아보자.

법선 벡터 설정

빛의 반사각은 입사각과 같다. 다음 그림을 보자.

빛은 어디로 반사될까? 해답은 법선벡터를 알면 구할 수 있다.

위 그림에서 파란색 벡터가 법선 벡터로 폴리곤의 평면에 수직이다. 빛은 법선 벡터를 중심으로 반사각을 계산하여 반사된다. 그렇다면 위의 소스코드에서 임의의 법선 벡터를 설정해보자.

void RenderWindow::RenderGLScene(void)
{
Window::RenderGLScene();

glTranslatef(0.0f, 0.0f, -5.0f);
glRotatef(yrot, 0.0f, 1.0f, 0.0f);
//glColor3f(0.7f, 0.7f, 0.0f);

glBegin( GL_TRIANGLES );
//{
  Front }
glNormal3f(0.0f, 0.0f,
  -1.0f);
glVertex3f( 0.0f, 1.0f, 0.0f);
glVertex3f(-1.0f,-1.0f, 1.0f);
glVertex3f( 1.0f,-1.0f, 1.0f);

//{
  Right }
glVertex3f( 0.0f, 1.0f, 0.0f
  );
glVertex3f( 1.0f,-1.0f, 1.0f
  );
glVertex3f( 1.0f,-1.0f,-1.0f
  );

//{
  Back }
glVertex3f( 0.0f, 1.0f, 0.0f
  );
glVertex3f( 1.0f,-1.0f,-1.0f
  );
glVertex3f(-1.0f,-1.0f,-1.0f
  );

//{
  Left }
glVertex3f( 0.0f, 1.0f, 0.0f
  );
glVertex3f(-1.0f,-1.0f,-1.0f
  );
glVertex3f(-1.0f,-1.0f, 1.0f
  );
glEnd();

yrot += 0.1f;
}

위의 그림을 보면 사실감이 약간 더 가미되었다. 코드를 보자. 사각뿔의 앞면에만 임의의 법선벡터를 설정했다. 하지만 법선 벡터는 평면에 수직해야하는데 위에서 설정한 법선벡터는 그렇지 못하다. 그렇다면 어떻게 해야하는가? OpenGL 에서 각 정점에 대해서 법선벡터를 구해주는 기능이 있지만 성능 저하로 많이 쓰지 않고 직접 법선 벡터를 구하는 루틴을 사용한다. 아래의 코드에 Vector 라는 객체를 사용하는데 여기서 사용할 벡터에 관련된 코드이다. 수학책의 내용을 그대로 옮겨 놓았으니(따라서 최적화는 없다 ;])분석하는데 어려움은 없을 것이다. 이 코드에서 getNormal() 함수가 법선 벡터를 구해준다.

위의 그림처럼 세 정점을 이용해서 법선 벡터를 구해서 프로그램을 실행시켰을 때의 그림이다.

void RenderWindow::RenderGLScene(void)
{
Window::RenderGLScene();

static Vector nv; //법선벡터

glTranslatef(0.0f, 0.0f, -5.0f);
glRotatef(yrot, 0.0f, 1.0f, 0.0f);

//glColor3f(0.7f, 0.7f, 0.0f);
glBegin( GL_TRIANGLES );
//{
  Front }
nv = getNormal( Vector(0.0f,
  1.0f, 0.0f), Vector(-1.0f,-1.0f, 1.0f), Vector(1.0f, -1.0f, 1.0f) );
glNormal3f(nv.x, nv.y, nv.z);
glVertex3f( 0.0f, 1.0f, 0.0f);
  // Top Of Triangle (Front)
glVertex3f(-1.0f,-1.0f, 1.0f);
  // Left Of Triangle (Front)
glVertex3f( 1.0f,-1.0f, 1.0f);
  // Right Of Triangle (Front)

//{
  Right }
nv = getNormal( Vector(0.0f,
  1.0f, 0.0f), Vector(1.0f,-1.0f, 1.0f), Vector(1.0f,-1.0f,-1.0f) );
glNormal3f(nv.x, nv.y, nv.z);
glVertex3f( 0.0f, 1.0f, 0.0f
  );
glVertex3f( 1.0f,-1.0f, 1.0f
  );
glVertex3f( 1.0f,-1.0f,-1.0f
  );

//{
  Back }
nv = getNormal( Vector(0.0f,
  1.0f, 0.0f), Vector(1.0f,-1.0f,-1.0f), Vector(-1.0f,-1.0f,-1.0f) );
glNormal3f(nv.x, nv.y, nv.z);
glVertex3f( 0.0f, 1.0f, 0.0f
  );
glVertex3f( 1.0f,-1.0f,-1.0f
  );
glVertex3f(-1.0f,-1.0f,-1.0f
  );

//{
  Left }
nv = getNormal( Vector(0.0f,
  1.0f, 0.0f), Vector(-1.0f,-1.0f,-1.0f), Vector(-1.0f,-1.0f,1.0f) );
glNormal3f(nv.x, nv.y, nv.z);
glVertex3f( 0.0f, 1.0f, 0.0f
  );
glVertex3f(-1.0f,-1.0f,-1.0f
  );
glVertex3f(-1.0f,-1.0f, 1.0f
  );
glEnd();

yrot += 0.1f;
}

재질 설정

빛과 마찬가지로 재질도 사실감을 표현하는데 빠질 수 없는 요소다. 아래는 재질에 대한 설명이며 'OpenGL 프로그래밍 우상수/사이버출판사' 의 내용을 인용했다.

재질이란 오브젝트가 빛을 얼마나 반영하는지 혹은 얼마나 반사하는지에 대한 성질을 의미한다. 두 개의 사과중에 잘 닦은 사과는 윤기가 흘러 반짝반짝하지만 닦지 않은 사과는 윤기가 없어 탁하다. 여기서 중요한 점은 두 개의 사과가 색깔이 같더라도 껍질의 성질에 의해서 차이가 분명하다는 것이다. 과학적으로 생각하면 간단하다. 사과를 닦아 놓았다는 것은 껍질에 붙어 있는 이물질의 제거를 의미하고 있는 것이다. 그리고 물질의 이물질의 제거는 껍질에 대한 빛의 반사율을 높였던 것이다. 그렇다면 3D 광경에서도 반짝반짝 빛나는 사과와 그렇지 않은 사과 모두를 나타낼 수 있어야 된다는 것이다. 다행스럽게 OpenGL 은 재실의 설정을 쉽게 할 수 있도록 지원하고 있다.

오브젝트의 표면이 어떻게 빛을 흐트리는가에 따라 OpenGL 은 다섯가지 재질을 준비하고 있다. 이러한 재질은 흩어지는 빛의 색상을 설정하는 RGBA 값에 의해서 표현된다. 예를 들어, 빨간색 사과는 빨간색 조명만이 반영되고, 녹색이나 파란색은 다른 영향을 끼칠 것이다. 만약 흰색이나 빨간색 조명으로 사과를 비춰 주면 당연히 빨간색으로 보일 것이다. 그러나 녹색의 조명으로 빨간 사과를 비추면 사과가 검은색에 가깝게 보이게 된다. 즉, 조명의 변화에 따라서 오브젝트의 색상도 변하게 된다. 그리고 오브젝트가 가진 빛의 반사율과 비춰 주는 각도에 따라서 상이한 변화를 가져 올 수 있다.

주변광(Ambient) 재질과 발산광(Diffuse) 재질

발산광 재질과 주변광 재질은 빛을 반사시키는 성질을 가지고 있으며 관점으로부터 독립적이다. 그렇기 때문에 사람의 시각 변동에 의해서 오브젝트의 광경이 크게 변화되지는 않는다. 우선 오브젝트 표면의 속성이 발산광 재질이라면 빛을 주변으로 발산하게 한다. 그리고 이 경우에 퍼뜨리는 빛은 주로 직사광원(Direct Light Source)이다. 주변광 재질은 오브젝트 표면이 주변광에 의해서 반사되는 속성을 가졌다. 발산광 재질은 주변으로 직사 광선을 반사하지만, 주변광은 간접적으로 광원(Indirect Light)을 반사시킨다는 점이 차이점이다.

반사광(Specular) 재질과 광택(Shininess) 재질

만약 오브젝트 표면의 재질이 반사광 재질과 광택 재질로 되어 있으면 빛을 반사시키는 효과를 가지고 있으며 관점(Viewport)으로부터 비독립적이라는 의미이다. 여기서 발산광 혹은 주변광 재질과 다른 차이점이 발견된다. 즉, 관점이 변경되면 그 오브젝트의 속성이 다르게 보인다는 것이다. 반사광 재질은 표면에서 가장 반사율이 높은 표면에서 나오는 빛의 반사 효과를 나타낸다. 그리고 광택 재질은 빛을 반사하는 속성의 집중도를 의미한다. 예를 들어 세라믹 주전자는 플라스틱 주전자보다 반사광을 적게 반사할 것이다. 반면 금속성 주전자는 세라믹 주전자와 플라스틱 주전자보다 더욱 높은 광택값을 가질 것이다.

발광(Emissive) 재질

오브젝트 스스로가 빛을 나타내는 것을 발광 재질이라고 한다. 바로 광원의 원천이 오브젝트인 것이다. 예를 들어 반딧불을 생각해 보자. 캄캄한 밤에도 자신의 몸을 이용해 빛을 내고 있다. 또한 모닥불과 램프 같은 조명 기구들도 스스로 빛을 발산하고 있는 경우이다. 3D 광경에서 태양과 같이 자신 스스로 발광하는 것을 표현하려면 발광값이 설정되어야 한다. 그리고 3D 광경에 램프를 그리고자 할 때 발광값을 설정하여 렌더링을 하는 동시에 광원을 램프 속에 위치시켜야 한다. 램프에서 발광값은 불이 타오르는 것을 나타내고 광원은 램프 주변의 오브젝트를 비추는 역할을 한다.

재질의 반응 색상 설정하기

각각의 조명에 반응하는 오브젝트를 만들려면 재질의 색상을 설정해 주어야한다.

GLfloat materialAmbient[] = { 0.0f, 0.7f, 0.0f, 1.0f };
GLfloat materialSpecular[] = { 1.0f, 1.0f, 1.0f, 1.0f };

재질의 색상

사과를 표현하는 경우 골격만 그린다고 해결되는 것은 아니다. 앞서 언급했듯이 빨간 색상을 칠해줘야 한다.

glColor3f(1.0f, 0.0f, 0.0f);

재질 정의하기

OpenGL 에서 재질을 정의하기 위해서는 glMaterialfv() 함수를 쓴다. 인자값으로 위에서 설정한 재질의 배열값이 쓰인다.

glMaterialfv( GL_FRONT, GL_AMBIENT_AND_DIFFUSE, materialAmbient );
glMaterialfv( GL_FORNT, GL_SPECULAR, materialSpecular );

glMaterialfv() 함수는 첫째 정의한 재질의 다각형 면을 상수값으로 갖는다. 둘째 재질의 속성을 결정한다. 셋째 재질의 속성을 설정한 배열의 포인터를 갖는다. 첫번째 인자로 GL_FRONT, GL_BACK 그리고 GL_FRONT_AND_BACK 이 올수 있다. 이 것은 오브젝트로 만들어진 다각형의 앞면과 뒷면 그리고 양쪽 모두를 선택한다. 직육면체와 같이 속이 채워진 오브젝트는 속을 볼 필요가 없기 때문에 GL_FRONT 로 설정할 것이고, 어떤 특정한 광경의 경우에 속면과 겉면을 모두 보기 위해서는 GL_FRONT_AND_BACK 을 사용할 것이다. 두번째 인자는 GL_AMBIENT, GK_AMBIENT_AND_DIFFUSE, GL_COLOR_INDEXES, GL_DIFFUSE, GL_EMISSION, GL_SHININESS 및 GL_SPECULAR 중 하나가 쓰인다. 만약 반사광이 반영된 조명을 쓰고 싶다면 먼저 재질의 밝기를 설정해야 한다. 재질의 밝기는 반사율 정도를 반영하는 결정적 요소이다. 그래서 glMaterialf() 함수를 사용해서 재질의 밝기를 꼭 설정하도록 한다.

glMaterialf( GL_FRONT, GL_SHININESS, 60.0f );

이 함수의 인자는 정의된 오브젝트 앞면의 재질을 말하고 있으며, GL_SHININESS 상수값은 재질의 밝음을 설정한다. 이 값의 범위는 0.0 에서 128.0 까지이다.

위의 설명을 적용한 프로그램과 코드를 보자.

#include "lib\egl.h"

using namespace egl;

class RenderWindow : public Window
{
private:
GLfloat ambientLight[4];
GLfloat diffuseLight[4];
GLfloat lightPosition[4];
GLfloat specular[4];
GLfloat specref[4]; //전반사
  반사율
GLfloat yrot;
public:
virtual BOOL InitGL(void);
virtual void RenderGLScene(void);
virtual void OnCreate(WPARAM wParam, LPARAM lParam);
};

void RenderWindow::OnCreate(WPARAM wParam, LPARAM lParam)
{
...
specular[0] = 1.0f;
specular[1] = 1.0f;
specular[2] = 1.0f;
specular[3] = 1.0f;
...
}

BOOL RenderWindow::InitGL(void)
{
...
glEnable(GL_COLOR_MATERIAL); //재질의
  색을 폴리곤의 색으로한다.
glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE); //주변광,
  발산광 재질에 대해서만
glMaterialfv(GL_FRONT, GL_SPECULAR, specref); //전반사
  반사율 설정
glMateriali(GL_FRONT, GL_SHININESS, 128); //재질의
  밝기 설정

return TRUE;
}

void RenderWindow::RenderGLScene(void)
{
Window::RenderGLScene();

static Vector nv; //법선벡터

glTranslatef(0.0f, 0.0f, -5.0f);
glRotatef(yrot, 0.0f, 1.0f, 0.0f);
glColor3f(0.7f, 0.7f, 0.0f); //폴리곤의
  색 설정. 이 색에 따라서 재질의 색이 설정된다.

glBegin( GL_TRIANGLES );
//{
  Front }
nv = getNormal( Vector(0.0f,
  1.0f, 0.0f), Vector(-1.0f,-1.0f, 1.0f), Vector(1.0f, -1.0f, 1.0f) );
glNormal3f(nv.x, nv.y, nv.z);
glVertex3f( 0.0f, 1.0f, 0.0f);
  // Top Of Triangle (Front)
glVertex3f(-1.0f,-1.0f, 1.0f);
  // Left Of Triangle (Front)
glVertex3f( 1.0f,-1.0f, 1.0f);
  // Right Of Triangle (Front)

//{
  Right }
nv = getNormal( Vector(0.0f,
  1.0f, 0.0f), Vector(1.0f,-1.0f, 1.0f), Vector(1.0f,-1.0f,-1.0f) );
glNormal3f(nv.x, nv.y, nv.z);
glVertex3f( 0.0f, 1.0f, 0.0f
  );
glVertex3f( 1.0f,-1.0f, 1.0f
  );
glVertex3f( 1.0f,-1.0f,-1.0f
  );

//{
  Back }
nv = getNormal( Vector(0.0f,
  1.0f, 0.0f), Vector(1.0f,-1.0f,-1.0f), Vector(-1.0f,-1.0f,-1.0f) );
glNormal3f(nv.x, nv.y, nv.z);
glVertex3f( 0.0f, 1.0f, 0.0f
  );
glVertex3f( 1.0f,-1.0f,-1.0f
  );
glVertex3f(-1.0f,-1.0f,-1.0f
  );

//{
  Left }
nv = getNormal( Vector(0.0f,
  1.0f, 0.0f), Vector(-1.0f,-1.0f,-1.0f), Vector(-1.0f,-1.0f,1.0f) );
glNormal3f(nv.x, nv.y, nv.z);
glVertex3f( 0.0f, 1.0f, 0.0f
  );
glVertex3f(-1.0f,-1.0f,-1.0f
  );
glVertex3f(-1.0f,-1.0f, 1.0f
  );
glEnd();

yrot += 0.1f;
}

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,
  int nShowCmd)
{
RenderWindow app;
if(!app.Create(FALSE,"EDin's OpenGL - Light"))
return EXIT_FAILURE;
return app.Run();
}

SOURCE CODE ARCHIVES
DOWNLOAD : Visual C++ [ Light.zip ]
DOWNLOAD : Borland C++ Builder [ Light.zip ]

BUILD TEST
Visual C++ 6.0 ServicePack 5
Borland C++ 6.0 Build Number 10.157


OpenGL 튜토리얼<기초편>

OpenGL 튜토리얼<기초편> - #11

텍스춰매핑

by 김성철 ( paranwave@korea.com )

  • GL_TEXTURE_2D
  • glTexCoord2f()
  • glTexImage2D()
  • glTexParameterf()
  • glGenTexture()
  • glBindTexture()
  • glDeleteTexture()
  • 참고 자료 : OpenGL 을 통한 3차원 그래픽스 프로그래밍 기초편, 임인성 저 / 도서출판 그린

컴퓨터 그래픽스 렌더링에 있어 가장 중요한 목표는 실제의 상황을 카메라로 촬영한 듯한 사실적인 영상을 생성하는 것이다. 다면체 모델을 사용하는 렌더링 시스템에서 물체의 사실성을 높이기 위한 방법 중의 하나는 더 많은 수의 다각형을 사용하여 정교하게 물체를 표현하는 것이다. 하지만 비교적 복잡한 물체를 표현하려고 하면 쉽게 수만-수십만 개의 다각형이 필요하고는 한다. 따라서 많은 시간과 메모리를 사용해야 하고 이는 실시간 렌더링의 그래픽스에서 좋은 방법이 아니다. 반면에 비교적 적은 추가 비용으로 이미지의 사실성을 상당히 높일 수 있는 방법 중의 하나는 다면체 모델을 렌더링 할 때 미리 생성해 놓은 다양한 부류의 이미지 데이터를 적절하게 적용하여 이미지를 생성하는 것이다. 그러한 방법의 대표적인 것이 텍스춰 매핑(texture mapping)인데, 우리가 가장 보편적으로 접하는 텍스춰 매핑 기법은 2차원 텍스춰 매핑이라 할 수 있다. 이 방법에서는 물체 표면의 텍스춰, 즉 색깔, 패턴, 형태 등을 표현해주는 2차원 이미지를 미리 생성해 놓고, 이를 다면체 물체에 덮어 씨운 후 렌더링을 하여, 물체 표면에 마치 그러한 텍스춰가 존재하는 것처럼 보이도록 하는 기법이다. 텍스춰 매핑을 하기 위해서 가장 먼저 생각해야할 일은 텍스춰 이미지와 물체간의 대응 관계를 설정하는 것이다. 이는 텍스춰 이미지의 좌표 설정, 텍스춰 이미지의 특성 등을 정하는 것이다.

OpenGL 에서는 텍스춰 좌표를 표현하기 위하여 3차원 공간의 동차 좌표 ( s, t, r, q ) 를 사용한다. 이 좌표는 기하 물체를 표현하기 위한 3차원 공간의 점 ( x, y, z, w ) 에 대응이 되는데, OpenGL 에서 2차원 공간의 점 ( x, y ) 가 z = 0 이고 w = 1 인 특수한 경우로 취급이 되는 것처럼, 2차원 텍스춰 이미지에 대한 좌표 ( s, t ) 는 마찬가지로 r = 0 , q = 1 인 특수한 경우로 취급이 된다. 물론 r 과 q 좌표는 3 차원 텍스춰 매핑이나 투영 텍스춰와 같은 고급 기법에 유용하게 쓰인다. 텍스춰 좌표의 범위는 텍스춰 이미지의 크기와 상관없이 0.0 에서 1.0 까지이다. 아래의 그림은 텍스춰 좌표계를 보여준다.

아래의 그림은 위의 텍스쳐 이미지와 물체간의 매핑을 보여준다.

텍스춰 매핑의 좌표를 설정하는 함수는 glTexCoord2f() 함수다. 이 함수의 원형은 다음과 같다. 인자는 따로 설명하지 않는다.

void glTexCoord2f( GLfloat s, GLfloat t )

텍스춰 매핑을 하기 위해서는 텍스춰 이미지를 준비해야하고 OpenGL 에 어떤 이미지를 사용할지를 알려줘야한다. 이러한 역할을 하는 함수는 glTexImage2D() 함수이다. 1차원 3차원의 함수도 있지만 여기서는 2차원의 텍스춰 매핑만 살펴볼 것이다. 1차원, 3차원의 텍스쳐 매핑은 다른 OpenGL 서적을 참고하자. 아래는 glTexImage2D() 함수의 원형이다.

void glTexImage2D( GLenum target, GLint level, GLint components, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels )

각각의 인자를 설명하자면, target 에 대하여 상수 GL_TEXTURE_2D 를 넘기면, 이 함수는 마지막 인자 pixels 이 가리키는 메모리 영역에 저장이 되어 있는 2차원 이미지 데이터를 텍스춰 메모리에 올려주게 된다. 여기서 네 번째와 다섯 번째 인자 width 와 height 는 그 이름이 나타내듯이 텍스춰 이미지의 해상도를 설정하는데 사용이 되는데, 이 때 이 두 인자는 항상 2의 멱급수 값, 즉 0보다 같거나 큰 정수 n 에 대하여 2n 형태의 값을 가져야 한다. 여섯 번째 인자 border 는 0 또는 1 값을 가지는데, 만약 이 값이 1 이라면 현재 올리려하는 텍스춰 이미지 둘레로 한 화소의 폭을 가지는 경계가 있음을 의미한다. 따라서 width 가 256 이고 height 가 128, 그리고 border 가 1 값을 가질 경우, ( 256 + 1 ) x ( 128 + 1 ) 의 해상도를 가지는 텍스춰를 사용함을 의미하게 된다. 물론 border 에 0 값이 설정되면 경계가 없음을 의미하는데, 텍스춰의 경계에 나타나는 색깔은 필요에 따라 추후 필터링 과정에서 사용이 된다. 이 함수의 두 개의 인자 format 과 type 는 메인 메모리에 저장되어 있는 텍스춰 이미지 데이터의 저장 형식을 나타낸다. 우선 format 은 각 이미지의 픽셀을 구성하는 요소 값들을 나타낸다. OpenGL 에서는 GL_RGB, GL_BGR, GL_RGBA, GL_BGRA, GL_RED, GL_GREEN, GL_ALPHA, GL_LUMINACE, GL_LUMINACE_ALPHA 등의 값을 가질 수 있다. 어떤 상수 값을 사용하는가에 따라 메인 메모리에 저장된 텍스춰 이미지의 픽셀이 몇 개의 필드로 구성이 되어 있는지, 그리고 각 필드가 어떠한 의미를 내포하는지가 결정된다. 이 때 각 필드의 요소 데이터가 실제로 저장이 되어 있는 타입은 type 인자에 의해 결정이 되는데, 가능한 상수 값으로 GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT, GL_INT, GL_UNSIGNED_INT, GL_FLOAT, GL_BITMAP 등이 있다. 마지막으로 level 인자는 현재 설정하려는 텍스춰 이미지의 상세 레벨을 설정하는데 사용이 된다. 텍스춰 매핑을 통한 레더링을 할 때 뒤에서 밉매핑 기능을 사용하려면 동일한 텍스춰 이미지를 여러개의 해상도로 텍스춰 메모리에 올려준다. 이 때 원래의 텍스춰, 즉 해상도가 가장 높은 이미지의 레벨이 0 이 되고, 해상도가 낯아질수록 레벨 값이 1, 2, 3, ... 과 같이 증가한다. 따라서 밉매핑을 하지 않는다면 이 인자에 0 값을 설정하면 된다.

이 함수를 수행하면 텍스춰 매핑에 필요한 정보 중 텍스춰 이미지의 해상도, 경계 두께, 내부 형식, 그리고 각 요소 필드에 할당된 비트 수 등이 갖추어진다. 이 정보만으로는 텍스춰 매핑을 할 수가 없고 다른 정보가 더 필요하다. 그 것은 현재 프래그먼트의 텍스춰 좌표가 주어졌을 때, 텍스춰 이미지를 액세스하여 텍스춰 색깔을 계산하는 방식에 영향을 미치는 정보가 갖추어져야한다. 이 정보를 설정하는 함수가 glTexParameterf() 함수다. 아래에 이 함수의 원형이 있다.

void glTexParameterf( GLenum target, GLenum pname, GLfloat param )

이 함수의 첫 번째 인자 target 에는 GL_TEXTURE_2D 를 사용하면 되고, 두 번째와 세 번째 인자 pname 과 param 에는 각각 이 표의 인자 이름과 그에 해당하는 사용 가능 인자를 사용하면 되는데, pname 은 설정한 값의 이름이고 이 인자에 올 수 있는 이르들은 GL_TEXTURE_MIN_FILTER, GL_TEXTURE_MAG_FILTER, GL_TEXTURE_WRAP_S, GL_TEXTURE_WRAP_T 등이다. 그리고 param 은 각각의 이름에 대한 실제 값이 온다. 아래의 표는 이 함수로 설정 가능한 텍스춰의 상태이다.

상태 종류 | OpenGL 인자 이름 | 사용가능인자(값)
확대 필터 | GL_TEXTURE_MAG_FILTER | GL_NEAREST, GL_LINEAR
축소 필터 | GL_TEXTURE_MIN_FILTER | GL_NEAREST, GL_LINEAR, GL_NEAREST_MIPMAP_NEAREST, GL_LINEAR_MIPMAP_NEAREST, GL_NEAREST_MIPMAP_LINEAR, GL_LINEAR_MIPMAP_LINEAR
워랩 모드(s) | GL_TEXTURE_WRAP_S | GL_CLAMP, GL_REPEAT, GL_CLAMP_TO_EDGE
워랩 모드(t) | GL_TEXTURE_WRAP_T | GL_CLAMP, GL_REPEAT, GL_CLAMP_TO_EDGE
워랩 모드(r) | GL_TEXTURE_WRAP_R | GL_CLAMP, GL_REPEAT, GL_CLAMP_TO_EDGE
텍스춰 경계 색깔 | GL_TEXTURE_BORDER_COLOR | RGBA 색깔 ( 예 : 0, 0, 0, 0 )
최소 LOD | GL_TEXTURE_MIN_LOD | 부동 소수점 숫자 ( 예 : -1000.0 )
최대 LOD | GL_TEXTURE_MAX_LOD | 부동 소수점 숫자 ( 예 : 1000.0 )
기저 밉맵 레벨 | GL_TEXTURE_BASE_LEVEL | 정수 타입 숫자 ( 예 : 0 )
최대 밉맵 레벨 | GL_TEXTURE_MAX_LEVEL | 정수 타입 숫자 ( 예 : 1000 )
텍스춰 우선 순위 | GL_TEXTURE_PRIORITY | 0 과 1 사이의 부동 소수점 타입 숫자

glTexParameterf(v)() 함수로 설정 가능한 텍스춰 상태

이상의 내용으로 텍스춰 매핑을 한 프로그램과 소스 코드를 보자.

#include "lib\egl.h"

using namespace egl;

class RenderWindow : public Window
{
private:
Dib image; //DIBitmap 을 핸들링할
  Dib 클래스 인스턴스
GLfloat rot; //큐브의 회전각
public:
virtual BOOL InitGL(void);
virtual void RenderGLScene(void);
virtual void OnCreate(WPARAM wParam, LPARAM lParam);
};

void RenderWindow::OnCreate(WPARAM wParam, LPARAM lParam)
{
rot = 0.0f;
}

BOOL RenderWindow::InitGL(void)
{
Window::InitGL();

//텍스춰 이미지로 사용할 이미지를 파일로부터
  읽어 온다.
if(!image.LoadFromFile("edin01.bmp"))
{
MessageBox(NULL, "edin01.bmp
  파일을 불러올 수 없습니다", "오류", MB_OK);
return FALSE;
}

//텍스춰 상태 설정
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
  GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
  GL_LINEAR);

//텍스춰 이미지 설정
glTexImage2D(GL_TEXTURE_2D, 0, 3, image.GetWidth(), image.GetHeight(),
  0, GL_BGR_EXT, GL_UNSIGNED_BYTE, image.GetRawData());

//2D 텍스춰를 사용할 것임을 설정한다.
glEnable(GL_TEXTURE_2D);

glFrontFace(GL_CCW);
glEnable(GL_CULL_FACE);
return TRUE;
}

void RenderWindow::RenderGLScene(void)
{
Window::RenderGLScene();

glTranslatef(0.0f, 0.0f, -5.0f);
glRotatef(rot, 1.0f, 0.0f, 0.0f); //큐브
  X 축 중심 회전
glRotatef(rot, 0.0f, 1.0f, 0.0f); //큐브
  Y 축 중심 회전

//텍스춰 좌표 설정을 잘 보자.
glBegin(GL_QUADS);
//
  Front Face
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f,
  -1.0f, 1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f(
  1.0f, -1.0f, 1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f(
  1.0f, 1.0f, 1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,
  1.0f, 1.0f);
//
  Back Face
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f,
  -1.0f, -1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,
  1.0f, -1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(
  1.0f, 1.0f, -1.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f(
  1.0f, -1.0f, -1.0f);
//
  Top Face
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,
  1.0f, -1.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f,
  1.0f, 1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f(
  1.0f, 1.0f, 1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f(
  1.0f, 1.0f, -1.0f);
//
  Bottom Face
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,
  -1.0f, -1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(
  1.0f, -1.0f, -1.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f(
  1.0f, -1.0f, 1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f,
  -1.0f, 1.0f);
//
  Right face
glTexCoord2f(1.0f, 0.0f); glVertex3f(
  1.0f, -1.0f, -1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f(
  1.0f, 1.0f, -1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(
  1.0f, 1.0f, 1.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f(
  1.0f, -1.0f, 1.0f);
//
  Left Face
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f,
  -1.0f, -1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f,
  -1.0f, 1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,
  1.0f, 1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,
  1.0f, -1.0f);
glEnd();

rot += 0.1f; //회전각 증가
}

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,
  int nShowCmd)
{
RenderWindow app;
if(!app.Create(FALSE, "EDin's OpenGL - Texture2D"))
return EXIT_FAILURE;
return app.Run();
}

대개의 물체들을 사실적으로 표현하기 위해서는 여러장의 텍스춰 이미지를 사용한다. 이를 위해서 필요한 텍스춰를 그리기 위해서 매번 glTexImage2D() 함수를 호출해 주면 부하가 무척 크다. 이에 대한 해결책으로 OpenGL 에서는 텍스춰 객체를 제공해준다. 텍스춰 객체란 텍스춰를 다루기 위한 일련의 정보들을 가지고 있는 것으로 특정 번호로 된 id로 구분이 되며 한번 텍스춰 이미지를 설정하면 텍스춰 그리기가 필요할 때 텍스춰 객체의 id 만 호출해 주면 된다. 따라서 glTexImage2D() 함수를 매번 호출해주는 부담이 해결된다. 텍스춰 객체의 고유한 이름을 생성하기 위해서는 glGenTextures() 함수를 사용한다. 아래는 이 함수의 원형이 있다.

void glGenTexture( GLsizei n, GLuint *textureNames )

이 함수를 수행하면 이름을 할당 받을 수 있다. n 개의 이름이 필요할 경우 이 함수를 수행시키면, OpenGL 시스템이 사용 가능한 객체 이름 n 개를 생성하여 두 번째 인자 textureNames 가 가리키는 배열에 저장을 해 준다. 이름을 할당을 받으면 텍스춰 객체를 생성할 수가 있다. 이러한 과정은 glBienTexture() 함수를 통하여 수행된다. 아래는 이 함수의 함수 원형이다.

void glBindTexture( GLenum target, GLuint textureName );

이 함수의 첫 번째 인자 target 은 현재 2차원 텍스춰 매핑을 고려하고 있으므로 GL_TEXTURE_2D 값을 넘기고, 두 번째 인자 textureName 은 생성을 하려하는 텍스춰 객체에 붙일 이름을 넘기면 된다. 이 함수를 최초로 부를 경우에는 우선 객체에 대한 자료 구조가 생성이 된 후, 이 객체의 텍스춰가 현재 텍스춰로 설정이 된다. 한편 이미 만들어진 객체에 대하여 이 함수를 호출하면 해당 텍스춰가 현재 텍스춰로 지정이 된다. 렌더링 과정에서 텍스춰 객체가 더이상 필요가 없으면 glDeleteTexture() 함수를 사용하여 동적으로 해당 텍스춰를 제거할 수 있다. 아래는 glDeleteTexture() 함수의 원형이다.

void glDeleteTexture( GLsizei n, const GLuint *textureNames );

이 함수는 textureNames 에 연달아 저장되어 있는 n 개의 이름에 대한 텍스춰 객체를 제거한다. OpenGL 에서는 현재 텍스춰로 지정된 객체를 제거하면 자동적으로 0 번 텍스춰 객체가 현재 텍스춰로 지정이 된다. 이를 이용한 예제프로그램과 소스코드를 보자. 아래의 프로그램은 두 개의 텍스춰 이미지를 사용하며 F1 키를 누르면 텍스쳐를 번갈아 가면서 매핑한다.

#include "lib\egl.h"

using namespace egl;

class RenderWindow : public Window
{
private:
GLuint textures[2]; //텍스춰
  객체의 id 배열
Dib image1, image2; //2개의
  텍스춰 이미지를 위한 Dib 객체
GLfloat rot; //큐브 회전 각도
GLuint currentTexId; //현재
  선택된 텍스춰 id
public:
virtual BOOL InitGL(void);
virtual void RenderGLScene(void);
virtual void OnCreate(WPARAM wParam, LPARAM lParam);
virtual void OnKeyDown(WPARAM wParam, LPARAM lParam);
virtual void OnClose(WPARAM wParam, LPARAM lParam);
};

void RenderWindow::OnCreate(WPARAM wParam, LPARAM lParam)
{
rot = 0.0f;
currentTexId = 0;
}

void RenderWindow::OnKeyDown(WPARAM wParam, LPARAM lParam)
{
if(wParam == VK_F1) // F1
  키를 누르면 다른 텍스춰 객체로 교환한다.
{
if(currentTexId == textures[0])
{
currentTexId
  = textures[1];
}
else if(currentTexId == textures[1])
{
currentTexId
  = textures[0];
}
else
{
currentTexId
  = textures[0];
}
}
Window::OnKeyDown(wParam, lParam);
}

void RenderWindow::OnClose(WPARAM wParam, LPARAM lParam)
{
glDeleteTextures(2, textures); //
  2개의 텍스춰 객체를 시스템에 반환한다.(메모리해제)
Window::OnClose(wParam, lParam);
}

BOOL RenderWindow::InitGL(void)
{
Window::InitGL();

if(!image1.LoadFromFile("edin01.bmp"))
  //첫번째 텍스춰 이미지를 파일에서 읽어 온다.
{
MessageBox(NULL, "edin01.bmp
  파일을 불러올 수 없습니다", "오류", MB_OK);
return FALSE;
}
if(!image2.LoadFromFile("eva.bmp")) //두번째
  텍스춰 이미지를 파일에서 읽어 온다.
{
MessageBox(NULL, "eva.bmp
  파일을 불러올 수 없습니다", "오류", MB_OK);
return FALSE;
}

glGenTextures(2, textures); //2개의
  텍스춰 객체를 위하여 고유 id 를 생성한다.

glBindTexture(GL_TEXTURE_2D, textures[0]); //첫번째
  텍스춰 객체를 생성한다.
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
  GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, 3, image1.GetWidth(),
  image1.GetHeight(), 0, GL_BGR_EXT, GL_UNSIGNED_BYTE, image1.GetRawData());

glBindTexture(GL_TEXTURE_2D, textures[1]); //두번째
  텍스춰 객체를 생성한다.
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
  GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, 3, image2.GetWidth(),
  image2.GetHeight(), 0, GL_BGR_EXT, GL_UNSIGNED_BYTE, image2.GetRawData());

glEnable(GL_TEXTURE_2D); //2D
  텍스춰를 사용할 수 있도록 설정한다.

currentTexId = textures[0]; //현재의
  텍스처 id 를 texture[0] 으로 설정한다.

glFrontFace(GL_CCW);
glEnable(GL_CULL_FACE);
return TRUE;
}

void RenderWindow::RenderGLScene(void)
{
Window::RenderGLScene();

glTranslatef(0.0f, 0.0f, -5.0f);
glRotatef(rot, 1.0f, 0.0f, 0.0f);
glRotatef(rot, 0.0f, 1.0f, 0.0f);
glBindTexture(GL_TEXTURE_2D, currentTexId); //설정된
  텍스춰 객체를 큐브에 매핑한다.
glBegin(GL_QUADS);
//
  Front Face
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f,
  -1.0f, 1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f(
  1.0f, -1.0f, 1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f(
  1.0f, 1.0f, 1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,
  1.0f, 1.0f);
//
  Back Face
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f,
  -1.0f, -1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,
  1.0f, -1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(
  1.0f, 1.0f, -1.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f(
  1.0f, -1.0f, -1.0f);
//
  Top Face
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,
  1.0f, -1.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f,
  1.0f, 1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f(
  1.0f, 1.0f, 1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f(
  1.0f, 1.0f, -1.0f);
//
  Bottom Face
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,
  -1.0f, -1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(
  1.0f, -1.0f, -1.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f(
  1.0f, -1.0f, 1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f,
  -1.0f, 1.0f);
//
  Right face
glTexCoord2f(1.0f, 0.0f); glVertex3f(
  1.0f, -1.0f, -1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f(
  1.0f, 1.0f, -1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(
  1.0f, 1.0f, 1.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f(
  1.0f, -1.0f, 1.0f);
//
  Left Face
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f,
  -1.0f, -1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f,
  -1.0f, 1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,
  1.0f, 1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,
  1.0f, -1.0f);
glEnd();

rot += 0.1f;
}

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,
  int nShowCmd)
{
RenderWindow app;
if(!app.Create(FALSE, "EDin's OpenGL - Texture2D-2"))
return EXIT_FAILURE;
return app.Run();
}

SOURCE CODE ARCHIVES
DOWNLOAD : Visual C++ [ Texture2D.zip ] [ Texture2D_2.zip ]
DOWNLOAD : Borland C++ Builder [ Texture2D.zip ] [ Texture2D_2.zip ]

ERROR
BCB 에서는 텍스춰의 이미지가 나오질 않습니다. 왜 그런지 아시는 분은 원인을 얘기해 주시길 부탁드립니다.

BUILD TEST
Visual C++ 6.0 ServicePack 5
Borland C++ 6.0 Build Number 10.157


OpenGL 튜토리얼<기초편>

OpenGL 튜토리얼<기초편> - #12

튜토리얼을 끝맺으면서...

by 김성철 ( paranwave@korea.com )

여러분야의 프로그래밍을 했었지만 그래픽스만큼 재미있는 것은 없는 것 같다. 이제 OpenGL 을 접한지 한달이 되어가고 있다. 처음 OpenGL 을 시작했을 때 아무것도 모르면서 시작했는데 지금은 어느 정보 기본은 익힌 것 같다. 사실 이 튜토리얼은 누군가에게 무엇을 가르쳐준다는 의미보다는 내 자신이 더욱 확실하게 공부한다는 의미에서 시작한 것이었다. 아직 한달도 되지 않았지만 그 목적은 충분히 달성한 듯 싶다. 아직 몇 편의 강좌 목록이 남아 있지만 그 것을 작성하면서 발견한 점은 점점 책 베끼기에 의존하는 나의 모습이었다. 그럴바에야 더 좋게 정리된 책을 보는 편이 낫겠다는 생각이 들었다. 그래서 이쯤에서 OpenGL 에 대한 기초편 튜토리얼을 마치려한다. 하지만 이 것으로 홈페이지의 업데이트가 끝나는 것은 아니다. 앞으로 프랙탈, 수학, 게임 등등의 분야의 유용한 글들을 번역하거나 때로는 부족한 글솜씨로 글을 지어 업데이트할 생각이다. 항상 느끼는 점은 "꾸준함은 정말 대단한 것이다" 라는 것이다. 하루에 아무리 작은 것을 하더라도 꾸준히 한다면 그 성과는 정말 어마어마 한 것 같다. 아직 한달도 되지 않은 내가 이 정도로 할 수 있다면 1 년 후에는... 5년 후에는, 10년 후에는 어떻게 될까? 정말 생각만 해도 흥미진진하다. 끝으로 OpenGL 프로그래밍을 하면서 도움이 되는 책을 소개하겠다.

OpenGL SUPER BIBLE 2E Jr. Michael Sweet , Richard S. Wright / 남기혁 (인포북)

OpenGL API 의 레퍼런스로도 활용가치가 높고 OpenGL 초급 서적으로도 정말 좋다.

관련 링크 : http://www.wowbook.com/generic/book/info/book_detail.asp?isbn=ISBN89-8054-427-8

OpenGL Game Programming Kevin Hawkins , Dave Astle / 류광 (정보문화사)

OpenGL 의 기본서로도 아주 좋으며 OpenGL 을 이용하여 게임을 만들기 위한 내용을 아주 충실하게 담고 있다.

관련 링크 : http://www.wowbook.com/generic/book/info/book_detail.asp?isbn=ISBN89-7627-879-8

OpenGL을 통한 3차원 그래픽스 프로그래밍 기초편 / 임인성 ( 그린 )

OpenGL 의 렌더링 파이프라인을 이해하려면 이 책을 읽어 보자. 뼈가 되고 살이 되는 설명들이 가득하다.

관련 링크 : http://www.wowbook.com/generic/book/info/book_detail.asp?isbn=ISBN89-86497-68-9

OPENGL프로그래밍 / 우상수 ( 사이버출판사 )

OpenGL 에서 아주 기초적인 내용만을 담고 있다. 지금은 절판된 상태지만 학교 도서관 등에서 찾을 수 있다. 처음에 한 번 읽으면 좋은 책.

관련 링크 : http://www.wowbook.com/generic/book/info/book_detail.asp?isbn=ISBN89-7636-088-5

OpenGL Programming Guide (3rd Edition) Dave Shreiner , Jackie Neider , Mason Woo , Tom Davis

'Red Book' 으로도 아주 유명한 책이다. 기초서적으로 아주 좋은 책이다.

관련 링크 : http://www.wowbook.com/generic/book/info/book_detail.asp?isbn=ISBN0-201-60458-2

OpenGL Reference Manual, Second Edition: The Official Reference Document to OpenGL, Version 1.1 / OpenGL Architecture Review Board

'Blue Book' 으로도 유명한 책이다. 레퍼런스 서적으로 아주 좋은 책이다.

관련 링크 : http://www.wowbook.com/generic/book/info/book_detail.asp?isbn=ISBN0-201-46140-4


렌더링 파이프라인의 이해

렌더링 파이프라인의 이해 - #1

기초지식

by Paul Brooke

원문 : http://www.comp.leeds.ac.uk/cuddles/hyperbks/Rendering/introduction.html

폴리곤(다각형) 렌더링은 무엇일까?

다각형(Polygon) 렌더링은 3 차원 물체로부터 2 차원의 이미지를 얻어내는 방법을 말한다. 이 3 차원 물체는 평평한 다각형이나, 여러 다각형으로 이뤄진 복잡한 형태들을 조합해서 만들어진다. 이 이미지를 생성하는 일련의 알고리즘들을 렌더링 파이프라인이라 부르고, 대부분의 3D 컴퓨터 그래픽스 어플리케이션들은 이미지를 생성하기 위해서 이 다각형 렌더링 파이프라인을 사용한다. 이 글에서는 대부분의 시스템에서 사용되는 기본 개념과 동작 방식에 대해서 알아볼 것이다.

Polygon rendering is a method of creating two dimensional images from a group of three dimensional objects. These three dimensional objects are made up of flat polygons, any shape can be approximated using polygons. The set of algorithms which produce the image are collectively known as a rendering pipeline. Most 3D computer graphics applications use a polygon rendering pipeline to generate images. This text covers the basic ideas and methods used in most systems.

폴리곤 렌더링은 3차원 입체 물체로부터 2차원 이미지를 만드는 방법이다. 3차원 입체 물체는 평평한 폴리곤 또는 여러개의 폴리곤을 조합해서 만든 메쉬를 사용해서 만든다. 이미지를 만드는 방법을 통틀어서 렌더링 파이프라인이라고 한다. 이미지를 만들기 위해서 대부분의 3D 컴퓨터 그래픽스 어플리케이션들은 폴리곤 렌더링 파이프라인 기술을 사용한다. 그럼 이제 렌더링 파이프라인의 개념과

물체 표현하기

3D 물체들은 장면을 구성하기 위해서, 빛과 함께 배열된다. 이 시스템은 아래와 같은 장면을 사용한다. :

하나의 점은 x, y, z 좌표를 갖는 3차원 점이다.

하나의 모서리는 2 개의 점들로 구성된 선이다.

하나의 다각형은 3 개 (삼각형) 또는 더 많은 모서리로 구성된다.

하나의 표면은 물체를 표현하는 다각형들의 집합이다. 예를 들면, 원기둥은 3 개의 표면을 갖는다 - 위, 아래 그리고 중간의 몸통. 위와 아래는 평평해서 하나의 다각형으로 표현할 수 있다. 중간 표면은 하나의 다각형으로 표현할 수 없고 여러 다각형들을 함께 모아서 표현한다.

표면들이 모여 하나의 물체가 된다.

빛, 뮬체, 가상 카메라가 함께 어우러져 장면을 만든다. :

하나의 장면이 만들어지면, 렌더링 파이프라인을 다 통과한 것이고, 가상 카메라의 뷰를 통해 출력 된다.

왼손 좌표계 VS 오른손 좌표계

오른손 좌표계와 왼손 좌표계의 차이점은 z 축의 점들이 속으로 향하는 방향이다. 화면의 좌측 아래가 원점인 곳에서, x 축은 가로로, y 축은 세로로 나아갈 때, 오른손 좌표계의 z 축은 화면의 바깥으로 나가게 된다. 왼손 좌표계는 z 축이 화면의 안쪽으로 향하게 된다. x 축의 양의 방향에서 y 축의 양의 방향으로 z축 주위로 손가락을 회전시켜보면, 엄지손가락이 시스템이 사용하는 각각의 좌표계에 따라 다른 방향을 가리키게 된다.

(왼쪽) 오른손 좌표계 | (오른쪽) 왼손 좌표계

물체는 관습적으로 오른손 좌표계를 이용해서 표현한다. 오른손 좌표계는 오랫동안 수학계의 관습이지만, 외손 좌표계는 많은 컴퓨터 이미지 생성 시스템에서 사용하는 일반적인 좌표계다. 이 문서에서는 계층적 대화형 그래픽스 시스템(PHIGS) 표준에 따라, 오른손 좌표계를 사용할 것이다.

동차 좌표계

동차 좌표계는 n 차원의 점을 n+1 차원의 좌표로 나타낸다. 그래서, 3D 점들은 4 차원 좌표를 사용해서 표현할 수 있다. 약간 이상할지도 모르지만, 이 방법을 사용하면 수학적인 풀이가 무척 간단해 진다.실제로, 이 동차 좌표계는 변환을 위한 통일된 시스템을 제공한다. 동자 좌표계에서의 한 점은 V(x, y, z) 는 V(X, Y, Z, w) 로 표현된다.

3차원 카테시안 좌표로 다시 나타내려면 다음과 같이 해주면 된다. :

x = X/w
y = Y/w
z = Z/w
단, w 가 0 이 아닐때

컴퓨터 그래픽스에서 w 은 항상 1 로 설정된다. 이 이유는 후에 설명하겠다.

수학적 배경지식

다각형 렌더링은 수학적인 기술이기에 최소한 벡터와 행렬에 대해서 알고 있어야한다. 이 장에서는 렌더링 파이프의 작동 원리를 이해하는데 필요한 모든 수학적인 방법에 대해서 알아볼 것이다. 단 한번에 여기에 소개된 수학적인 방법들을 깊게 이해하려고 애쓰는 것 보다는 대략적으로 살펴보는 것이 더 좋은 생각이다. 이 장과 관련된 수학적인 방법들을 더 깊이 공부하려면 나중에 소개될 링크들을 참고하길 바란다.

3차원 변환

변환은 3차원 컴퓨터 그래픽스에서 물체의 위치를 변경하고, 모양을 바꾸고, 재배열하고 뿐만 아리라 서로 다른 좌표 간에 물체를 이동시키는 일련의 처리를 말한다. 변환은 크게 두가지로 나뉘는데 하나는 아핀변환(affine transformation) 이고 다른 하나는 구조 변형 변환(structure deforming) 이다.

아핀 변환

아핀 변환은 행렬로 표현될 수 있어, 서로 다른 아핀 변환끼리 하나로 혼합될 수 있다. 아핀 변환은 회전 변환(rotation), 크기 변환(scaling), 위치 변환(translation) 과 쉬어링 변환(shearing, 전단 변환)을 포함한다. 아핀 변환은 물체의 모든 점들을 같은 방법으로 변환시킨다.

위치 변환

(왼쪽) 변환 전 | (오른쪽) 변환 후

크기 변환

(왼쪽) 변환 전 | (오른쪽) 변환 후

X 축 회전 변환

(왼쪽) 변환 전 | (오른쪽) 변환 후

Y 축 회전 변환

(왼쪽) 변환 전 | (오른쪽) 변환 후

Z 축 회전 변환

(왼쪽) 변환 전 | (오른쪽) 변환 후

쉬어링 변환

(왼쪽) 변환 전 | (오른쪽) 변환 후

구조 변형 변환

구조 변형 변환은 비선형이라서, 행렬로 나타낼 수 없다. 구조 변형 변환의 예를 들면. 테이퍼 변환(tapering, 끝이 점점 가늘어지는 변환), 트위스트 변환(twisting, 물체를 꼬는 변환), 벤드 변환(bending, 물체를 굴절시키는 변환) 그리고 핀치 변환(pinching, 물체를 움츠러들게 하는 변환) 이 있다.

테이퍼 변환

(왼쪽) 변환 전 | (오른쪽) 변환 후

트위스트 변환

(왼쪽) 변환 전 | (오른쪽) 변환 후

벤드 변환

(왼쪽) 변환 전 | (오른쪽) 변환 후

핀치 변환

(왼쪽) 변환 전 | (오른쪽) 변환 후

벡터와 행렬

벡터에 대해서

3 차원에서 한 점은 다음과 같이 하나의 벡터로 표현할 수 있다. :

동차 좌표계는 필요한 것보다 더 많은 인자를 가지고 있기 때문에, ‘실세계’ 의 점은 동차 좌표계로 다양하게 표현될 수 있다. ‘실세계’의 점은 동차좌표계로 매핑 되어 계산되기 때문에 여분의 인자 w 는 항상 1 이다. 이 1 값은 x, y, z 좌표 값을 w 인자 값으로 나누면 간단하게 만들수 있다.

그러므로 점(3, 4, 5) 는 다음과 같이 동차좌표계에서 여러 점으로 표현될 수 있다.

행렬에 대해서

컴퓨터 안에서, 한 장면에 물체를 놓고, 배열하여 표현하려면, 물체를 변환하는 것이 필요하다. 많은 변환 방법(예를 들어: 스트레칭, 트위스팅, 벤딩)들이 있지만 회전 변환, 이동 변환, 크기 변환은 반드시 있어야한다. 어떤 변환이든 하나의 행렬로 표현할 수 있다. 행렬은 동차좌표계를 따르는 4행 4열의 행렬이다. 예를 들면, 점을 원래 크기에서 3 배 크기 변환하는 행렬은 다음과 같이 만들 수 있다.

점은 변환 행렬에 곱해져서 변환된다. 예를 들어. 점(3, 4, 5)를 아래의 행렬과 곱하면 :

변환된 점 (9, 12, 15, 1)를 얻을 수 있다.

행 벡터 VS 열 벡터 표현

벡터는 다음과 같이 하나의 행으로 표현할 수 있다.:

또는 다음과 같이 하나의 열로도 표현할 수 있다.:

이 두가지 표현 방법의 차이점은 변환 행렬의 원소 배열을 다르게 만든다. 예를 들어보자.

어떤 한 점 열벡터로 표현해서 (2, 3, 4)를 더해 이동 변환 시켜보자.

변환된 점은 ( 5, 7, 9, 1 ) 이다.

어떤 한 점을 행벡터로 표현해서 (2, 3, 4)를 더해 이동 변환 시켜보자.

변환된 점은 ( 5, 7, 9, 1 ) 이다.

결과는 같지만, 행렬은 원소가 1 인 줄을 중심으로 뒤집어 졌다(전치되었다). 열벡터는 수학자들에 의해서 즐겨 사용되고 행벡터는 일반적으로 컴퓨터 그래픽스의 계통에서 즐겨 사용한다. 이 문서에서는 PHIGS 표준에 따라 열벡터를 사용한다.

벡터 연산

벡터 더하기

V, W 벡터가 다음과 같다면 :

두 벡터 의 덧셈 V+W 은 다음과 같다.

기하학상으로 보면, 이 뎃셈 연산은 벡터 W 의 꼬리를 벡터 V 의 머리에 붙인 것과 같다.

[그림] Second 벡터의 꼬리를 First 벡터의 머리에 붙임으로써 벡터가 더해진다.

벡터의 길이

벡터의 크기 또는 길이는 다음과 같이 정의된다. :

기하학상으로, 벡터의 길이는 벡터의 꼬리에서부터 머리까지의 거리이다.

벡터의 정규화

벡터의 정규화는 벡터의 길이를 1 로 만드는 것이다. 길이 1을 가진 벡터를 단위 벡터라고 한다. 벡터 V 의 정규화는 다음과 같다. :

U 벡터는 V 벡터와 방향은 같고 길이는 1 인 벡터다. 그래서 U 벡터를 방향 벡터라고 말할 수 있다. 다음과 같이 쓸 수 있다는 것을 주목하자. :

이 식은 어떤 벡터든지 그것의 크기와 방향이 곱해져서 만들어진다는 것을 말해준다.

벡터의 외적

두 벡터의 외적은 다음과 같이 정의된다. :

i = (1, 0, 0)
j = (0, 1, 0)
k = (0, 0, 1)

다각형의 법선 벡터

컴퓨터 그래픽스 상의 중요한 과정 처리는 다각형에 법선 벡터를 이용한다. 다각형의 법선 벡터는 동일 직선상이 아닌 곳에 위치한 다각형의 3 개의 점으로 구한다. 3개의 점들을 벡터 V1 그리고 V2 로 정의하고 이들을 외적 해서 다각형의 법선 벡터를 찾는다.

법선 = V1 x V2

[그림] 다각형의 법선 벡터 계산하기.

점의 법선 벡터

점에 대한 법선은 다각형을 잘게 나눠 각 다각형에 대한 법선 벡터들의 평균을 내서 구한다. 이 방법이 완벽한 방법이 아니지만 대부분의 상황에서 잘 동작한다.

[그림] 점에 대한 법선 벡터 계산하기

벡터의 내적

벡터 V 와 W 의 내적은 다음과 같이 정의된다. :

두 벡터의 끼인각

삼각법을 이용해서 구한다. :

theta 는 벡터 V 와 W 의 끼인각이다. :

만약 theta 가 90 도 보다 작다면 V.W > 0 이다.
만약 theta 가 90 도 라면 V.W 은 = 0 이다.
만약 theta 가 90 도 보다 크다면 V.W < 0 이다.

행렬 연산

벡터와 행렬의 곱 연산

다음과 같은 벡터와 행렬의 곱 연산의 결과는

다음과 같다.

x1 = ax + by + cz + dw
y1 = ex + fy + gz + hw
z1 = ix + jy + kz + lw
w1 = mx + ny + oz + pw

두 행렬의 곱 연산

결과 행렬의 각 원소를 만들기 위해서 두 행렬을 곱한다. :

한 원소의 위치가 x, y 라면 첫 행렬의 y 행과 두 번째 행렬의 x 열을 연산해서 만든다.

위치가 2, 3 인 j 원소를 구하는 예를 들어 보자:

다음, n 을 1에서 4까지 변화시키면서 행의 n 원소와 열의 n 원소를 곱한다. :

i1 x b2
j1 x f2
k1 x j2
l1 x n2

다음, j 를 구하기 위해서 모두 더한다. :

j = (i1 x b2) + (j1 x f2) + (k1 x j2) + (l1 x n2)

이러한 연산을 결과 행렬의 모든 원소에 대해서 해줘야 한다.

예를 들어 :

이라면

...

이다.

가장 중요한 변환들

단위 행렬

단위 행렬은 1 을 곱하는 것과 같이 행렬 연산 결과에 아무런 영향도 미치지 않는 행렬을 말한다.

(Sx, Sy, Sz) 배 크기 변환 행렬:

(Tx, Ty, Tz) 이동 변환 행렬:

x 축 중심으로 Rx 도 회전 변환 행렬 :

y 축 중심으로 Ry 도 회전 변환 행렬 :

z 축 중심으로 Rz 도 회전 변환 행렬 :

행렬 조합

크기 변환, 회전 변환, 이동 변환과 같은 변환 행렬의 종류는 행렬 조합으로 하나의 변환 행렬로 만들 수 있다. 한 점에 대해서 여러 변환 행렬을 한번에 한번씩 적용하는 것 대신에. 하나의 조합 변환 행렬을 한번만 적용해서 같은 결과를 얻을 수 있다. 이 것은 효율성이 아주 높은 것이다.

여러 변환이 섞인 조합 변환 행렬은 각각의 변환 행렬들을 서로 곱해서 구할 수 있다. 변환 행렬들은 변환 적용 순서에 따라서 왼쪽에서 오른쪽으로 적는다.

전 곱셈 VS 후 곱셈

전 곱셈은 행 벡터를 사용하고, 후 곱셈은 열 벡터를 사용한다. 차이점은 행렬이 곱해지는 순서다. 전 곱셈에서, 행렬은 왼쪽에서 오른쪽으로 곱해지고, 후 곱셈은 행렬이 오른쪽에서 왼쪽으로 곱해진다. 연산 순서의 차이는 행 벡터 변환 행렬이 열 벡터 변환 행렬의 전치 행렬이라는 것에서 일어난다. 이 문서에서는 열 벡터를 중심으로 설명하기 때문에, 행렬은 후 곱셈을 사용한다.

예를 들어 :

(2, 3, 4) 로 이동하고 x 축 중심으로 20 도 회전한 다음 (3, 3, 3) 배로 크기 변환한다면 :

cos (20) = 0.940, sin(20) = 0.342

그 다음,

그 다음,

후 곱셈을 사용해서 행렬 조합하기 :

단계1 : 크기 변환 행렬에 x 축에 대한 회전 변환 행렬을 곱한다.

단계 2: 단계 1의 결과에 이동 변환 행렬을 곱한다.

이 행렬은 각각의 변환 행렬을 결합한 조합 변환 행렬이다.

The "Rendering Pipeline" package was developed in 1995 by Paul Brooke during his final year project.

번역 : 김성철
email : paranwave@korea.com


렌더링 파이프라인의 이해

렌더링 파이프라인의 이해 - #2

뷰잉 파이프라인의 이해

by Paul Brooke

원문 : http://www.comp.leeds.ac.uk/cuddles/hyperbks/Rendering/introduction.html

렌더링 파이프라인은 무엇인가?

렌더링 파이프라인은 한 장면에서 2D 이미지를 생성하는 일련의 처리 과정이다. 렌더링 파이프라인은 이해하기 쉽고, 구현하기 쉽게 하기 위해서 몇 단계의 처리 과정으로 나뉘어진다. 한 처리 과정에서 다음 처리 과정으로 데이타를 전송하는 것을 파이프라인이라고 한다.이 장은 세가지 부분은으로 나뉘어져 있다. : 첫 부분은 뷰잉 파이프라인(viewing pipeling) 으로 와이어 프레임에서 실사 이미지를 생성하는 시스템에 필요한 기하변환을 수행한다. 두번째 부분은 조명 모델을 다루고 있다. 조명 모델은 물체와 상호 작용하며 장면에 나타나는 빛을 처리하는 방법이다. 세번째 부분은 물체에 음영을 주는 것과 물체들을 스크린에 출력하는 알고리즘에 대해서 설명한다.

순서대로 읽어 가길 바라고, 한번에 한 부분씩 읽어 나가길 바란다.

뷰잉 파이프라인은 무엇인가?

[그림] 표준 뷰잉 파이프라인

뷰잉 파이프라인은 와이어프레임에서 실사에 가까운 이미지를 생성하는 일련의 처리 과정이다. 그리고 특정 관점에서 볼 수 있도록 물체를 변환하는 것과 이 시점에서 보이지 않는 면을 제거하는 처리 과정과도 연관되어 있다. 뷰잉 파이프라인의 입력 데이타는 로컬 좌표계에서 정의된 점으로 이루어진 물체들의 목록과 관점의 위치와 원점에 대한 정보다. 출력되는 데이타는 입력 때와 같은 물체들의 목록이되 물체를 정의하는 좌표점들은 2차원 화면 좌표계로 변환된 것이다.

이 장에서는 표준 뷰잉 파이프라인에 포함된 기술들에 대해서 다룰 것이다.

뷰잉 파이프라인 살펴보기

대개의 렌더링 시스템은 각각의 물체를 그 물체의 좌표계를 사용해서 정의한다. 이러한 방법은 물체를 이루는 점들을 표현하는데 편리할 것이다. 예를 들어서, 구의 중심을 생각해보자. 지역좌표계(물체좌표계)를 사용하여 어떤 물체를 표현하면 물체의 모델링 단계에서 같은 장면안에 있는 다른 물체들과 개별적으로 다룰 수 있게 된다.

[그림] 구의 지역좌표계 축들과 구를 나타내었다.

각각의 물체들의 위치와 원점은 장면을 구성하기 위해서 정의된다. 그러므로 뷰잉 파이프라인의 첫 단계는 각 물체의 지역좌표계로 표현된 물체를 세계 좌표계를 사용해서 표현하는 변환 과정이 된다. 장면이 구성되고 나면, 장면의 그림을 생성하기 위해서 카메라의 위치가 정의된다. 카메라의 정의는 렌더링 처리에서 중요한 과정이다. 카메라의 위치, 원점, 필름의 위치와 크기 등의 여러가지 인자들이 완벽한 카메라 모델을 만드는데 필요하다.

[그림] 물체들의 세계 좌표계상(분홍색 좌표축)에 놓여있다. 녹색 좌표계는 카메라를 나타낸다.

카메라 모델이 정의되면, 장면의 좌표계는 카메라의 좌표계로 변환된다. 이 카메라 좌표계를 뷰 참조 좌표계 (View Reference Coordinate (VRC) System) 이라 한다. 변환은 행렬을 사용해서 수행된다. 변환이 끝난 후에, 물체들은 마치 가상의 카메라에서 보고 있는 듯이 장면에 놓여지게 된다.

[그림] 카메라를 통해 보여지는 물체들.

이 뷰에서, 카메라의 관점에서 벗어난 다각형들은 제거된다. 이 것은 관측 공간을 생성하여 처리하는데 관측 공간이란 물체가 보여지는 공간이고 이 공간을 벗어난 다각형은 절단된다. 물체의 뒷면과 같은 카메라에 보이지 않는 다각형들은 렌더링 파이프라인의 나머지 처리 과정에서 고려되지 않는다. 이렇게 물체의 은면을 제거하는 과정을 컬링(culling) 이라 한다.

우리는 지금 다각형들을 뷰 평면에 투영하고 있다. 이러한 모든 변환 과정이 끝난 후에, 3D 장면의 2D 이미지를 만들기 위해서 각각의 다각형에 대해서 음영을 설정한다. 이 과정까지 마친 다각형들과 함께, 이 단계의 관측공간은 컴퓨터 화면의 뷰포트(viewport) 에 매핑되고 다각형들은 음영된 상태에서 2D 이미지로 생성된다.

원근 VS 평행 투영

컴퓨터 그래픽스에서, 가상 카메라를 사용해서 평행 투영 또는 원근 투영을 사용하여 뷰잉 평면의 창에 그림을 생성할 수 있다.

평행 투영에서, 다각형들은 투영기의 평행선을 따라 뷰잉 평면에 투영된다. 이 경우에 투영 방향(DOP, Direction of projection) 이 우선 정의 되어야 한다.

[그림] 평행 투영

[그림] 긴 입방체가 평행 투영을 사용하여 렌더링 된 그림

평행 투영은 깊이감이 없다. 고정 높이의 선이 카메라에서 멀어져도 같은 높이를 갖는다.

원근 투영에서, 투영기는 투영 중심점 또는 투영 참조점(PRP, Projection reference point)이라는 점을 향해서 투영한다. 원근 투영을 사용해서 장면을 렌더링할 경우, 고정 높이의 선은 카메라에서 멀어질 수록 높이가 점점 작아진다. 그래서 이 기술은 2 차원 이미지에 원근감을 느낄 수 있게 해 준다.

[그림] 원근 투영

[그림] 긴 입방체가 원근 투영을 사용하여 렌더링 된 그림

모델링 변환

모델링 변환은 뷰잉 파이프라인의 첫 단계이다. 이 단계에서, 물체들이 세계 좌표계상에 놓여지게 되지만 각각의 물체들은 자신만의 로컬 좌표계를 가지고 있어서 각각의 물체들이 개별적으로 처리된다. 대부분의 렌더링 시스템에서, 각각의 물체는 세계에 로드되거나 생성된다. 그런 다음 사용자가 물체에 특정 변환을 수행할 수 있다. 예를 들어, (2,3,4) 로 이동하고 x 축에 대해서 시계 방향으로 20 도 회전하는 특정 변환. 이러한 변환들은 물체와 함께 수행되는데, 지역 좌표에서 세계 좌표로의 변환은 물체의 각각의 정점 대해 이러한 변환을 수행하는 간단한 예이다. 이 변환은 지역 좌표의 x, y, z 위치로 표현된 정점 데이타를 새로운 정점 x, y, z 위치로 표현한다.

[그림] 물체들이 세계 좌표계상에 놓여져 있다.

모든 물체들이 처리되고 나면, 하나의 좌표계상에 정의된 전체 장면을 얻을 수 있고 모든 물체들을 일관되게 처리할 수 있게 된다.

뷰잉 변환

뷰잉 변환은 뷰잉 파이프라인의 두번째 단계이다. 이 단계에서 물체들은 세계 좌표계에서 뷰 참조 좌표계(카메라 좌표계) 로 변환된다. 하지만, 이 변환이 어떻게 수행되는지 알아보기 전에, 어떻게 카메라가 컴퓨터 그래픽스에서 표현되는지 알아야한다. 이에 대한 내용을 알아보자.

카메라 모델(표현)

가상 카메라를 표현하기 위해서 아래의 기본 형태를 정의해야한다. :

(i) 뷰잉 평면 : 이 것은 필름의 위치를 정의한다. 물체들을 2D 이미지로 생성하기 위해서 이 평면에 투영하게 된다.
(ii) 뷰 참조 좌표계 : 이 것은 사용자가 뷰잉 창(필름)의 방향과 크기를 정의할 수 있게 한다.

뷰 평면 설정

뷰 평면은 뷰 참조점(VRP)에 의해서 정의되고 이 평면의 법선 벡터를 뷰-평면 법선 벡터(VPN) 이라 한다.

뷰잉 참조 좌표계 설정(VRC)

(u,v,n) 축과 뷰잉 참조 좌표계의 원점이 어떻게 설정되는지 알아보자. 뷰잉 참조 좌표계(VRC)의 원점은 뷰 참조점(VRP)이다. 뷰 평면의 법선 벡터(VPN) 로 n 축을 나타낼 수 있다. v 축은 사용자가 정의한 뷰 상향 벡터(VUP) 에서 찾을 수 있다. VUP의 평행 투영인 VPN 을 뷰 평면에 투영하면 v 축이 정의된다. u 축은 u,v,n 이 오른손 좌표계를 형성하는 과정에서 정의된다.

윈도우(창) 정의

현재, 뷰잉 평면은 무한한 평면이다. 그래서 뷰잉 평면에 유한 크기의 창을 정의해야한다. 개념적으로, 이 창은 카메라의 필름으로 생각할 수 있다. 이 창은 뷰 평면의 2개의 점으로 정의된다. 그 점들은 (Umin, Vmin) 그리고 (Umax, Vmax) 이다. 아래의 그램에서, CW 은 창의 중심을 말한다.

뷰 변환 계산 유도

지금 세계 좌표계(WC) 에서 뷰 참조 좌표계(VRC) 로 물체를 변환하는 행렬을 유도할 것이다. 뷰잉 변환은 마치 가상 카메라에서 보는 것처럼 보이도록 물체들을 배열한다.

이 것은 다음과 같이 쓸 수 있다. :

뷰 행렬(Tview) 는 다음과 같이 유도된다. :

1.VRP 를 원점으로 이동 변환한다.
2.VRC 를 n 축이 z 축이 되도록, u 축이 x 축이 되도록, v 축이 y 축이 되도록 회전 변환한다.

이 행렬은 다각형들의 모든 정점에 대해서 WC 에서 VRC 로 변환하기 위해 적용된다.

관측 공간의 클리핑(절단)

관측 공간 클리핑은 뷰잉 파이프라인의 세번째 단계로 이 단계에서, 관측 공간 밖의 물체들이 절단된다. 이 번장에서는 관측 공간을 정의하는 방법과 절단을 수행하는 방법을 알아 볼 것이다.

관측 공간

관측 공간은 장면의 범위를 장면이 투영되는 뷰평면에 맞게 한정짓는다. 하지만, 관측 공간은 투영의 종류에 따라 다르게 정의된다.

원근 투영

원근 투영에서, PRP 는 투영 또는 투영 참조 점의 중심이다. 이 경우에, 관측 공간은 PRP 꼭지점에서 시작해서 창의 각 코너를 지나가는 모서리로 이뤄진 무한 각뿔이 된다.

평행 투영

투영의 종류가 평행 투영이라면 투영의 방향(DOP) 는 PRP 에서 CW 로 향한다. 이 경우에, 관측 공간은 무한한 평행육면체가 되는데, 이 평행 육면체의 각 면은 PRP 에서 창의 중심으로 향하는 투영 방향과 서로 평행한다.

유한 관측 공간

뷰평면에 투영되어 출력되는 프리미티브들의 수를 제한하기 위해서 관측 공간을 유한하게 만들어야 한다.

[그림] 직교 평행 투영을 위한 유한 관측 공간

잘려진 평면의 앞과 뒤는 뷰 평면과 평행하다. 이들은 VRP 와 VPN 에 대해서 앞거리(F)와 뒤거리(B) 값의 부호에 따라 결정된다.

원근 투영의 경우에도 절단된 평면은 위의 평행 투영과 비슷하게 설정된다.

정규 관측 공간

정규 관측 공간은 평행 투영에서는 2x2x1 각기둥이고 원근 투영에서는 꼭지점 부분을 자른 등각등변 각뿔이다. 임의의 관측 공간 보다 이러한 정규 관측 공간을 절단하는 것이 더 쉽다. 그래서 관측 공간은 종종 이러한 정규 관측 공간 중의 하나로 변환되어 절단 된다. 원근 관측 공간을 정규 평행 관측 공간으로 변환하는 것이 가능하기 때문에 같은 알고리즘을 원근 장면과 평행 장면을 자르기 위해 사용한다.

[그림] 두 가지 정규 관측 공간.

예 : 평행 관측 공간을 정규 평행 관측 공간으로 변환하기 :
관측 공간의 앞의 중심을 원점으로 이동 변환하고 이 것을 2x2x1 로 크기 변환한다. (Umin, Vmin, F) 에서 (Umax, Vmax, B) 까지의 입방체 관측 공간이 만들어진다.

수용과 거절 그리고 선별

관측 공간 밖에 있는 물체들은 관측 공간과 물체간의 경계 박스 검사를 통해 거부될 수 있다. 게다가 뒷면 선별은 뷰어에서 보이지 않는 다각형들을 제거한다. 선별은 뷰에서 안 보이는 곳에 있는 다각형을 보이 않게 처리하고 파이프라인 처리 과정에서 무시 되도록 하는 것이다. 대략, 물체를 구성하는 절반정도의 다각형들은 뒷면이라서 렌더링파이프라인의 처리 속도를 높이기 위해서 제거된다.

만약 Np . N > 0 면 다각형이 보이는 것이고 그렇지 않으면 보이지 않는 것이다.(Np 내적 N)

설명 :
Np 는 다각형의 법선이다.
N 은 다각형에서 뷰포트로 향하는 벡터이다. 시선 벡터

내적에 대해서 가물가물하다면 내적 정의를 다시 살펴 보길 바란다.

[그림] 정6면체 선별하기

클리핑(절단) - 코헬-서덜랜드 알고리즘(2D 버전)

코헬-서덜랜드는 선-절단에 일반적으로 사용되는 방법이다. 알고리즘의 원리를 설명하기 위해서 여기서는 2차원 버전의 알고리즘을 사용한다. 3차원의 경우는 뒤에서 설명할 것이다.

‘선의 각 끝점은 4 비트의 ‘아웃코드’로 할당된다‘ 는 기준을 바탕으로 :

bit 1 - Top 모서리 위의 점 ( Y > Ymax )
bit 2 - Bottom 아래의 점 ( Y < Ymin )
bit 3 - Right 오른쪽의 점 ( X > Xmax )
bit 4 - Left 왼쪽의 점 ( X < Xmin )

양 끝이 0000 인 아웃코드를 갖는다면, 그 선은 수용되는 것으로 절단 영역 안에 완전히 포함된다. 만약 두 아웃코드의 논리 AND 연산의 결과가 0 이 아니라면 선은 거부된다. 이 것은 만약 두 개의 끝 점이 각각 같은 비트 셋이라면, 두 점들이 절단 선분의 같은 쪽에 있는 점들이기 때문이다. 그래서 선은 절단 영역을 교차하지 못한다.

어떤 선이 일부는 수용되고 일부는 거부 된다면, 두 부분으로 나누어 한 부분 또는 두 부분모두가 거부 될 수 있게 만든다. 선들은 절단 영역의 모서리에 교차되는 점에서 나누어지고 절단 영역의 모서리 밖에 있는 부분은 거부 된다.

위의 그림에서 선분 AD 를 살펴보자. 점 A 는 아웃코드가 0000 이고 점 D 는 아웃코드가 1001 이다. 그래서 선 AD 는 모든 부분을 수용하거나 거부할 수가 없다. D 는 절단 영역 밖의 점인데 점 D 의 아웃코드는 선분이 왼쪽-위의 모서리를 자르는 것을 말해준다. 코헬-서덜랜드 알고리즘을 위에서 아래, 왼쪽에서 오른쪽까지의 모서리들에 적용해 보면, 윗모서리가 제일 먼저 적용되어 선분 AD 가 선분 DB 와 선분 BA 로 나누어진다. 선분 BA 는 수용되고(A 와 B 의 아웃코드가 모드 0000 이기 때문에), 선분 DB 는 윗 모서리의 밖에 있기 때문에 거부된다.

클리핑(절단) - 코헬-서덜랜드 알고리즘(3D 버전)

코헬-서덜랜드 알고리즘은 쉽게 3 차원으로 확장된다. 3차원의 아웃코드는 6비트 코드로 구성된다.

평행 관측 공간의 비트를 표현하면 :

bit 1 - 관측 공간의 윗 점 ( Y > 1 )
bit 2 - 관측 공간의 아랫 점 ( Y < -1 )
bit 3 - 관측 공간의 우측 점 ( X > 1 )
bit 4 - 관측 공간의 좌측 점 ( X < -1)
bit 5 - 관측 공간의 뒤쪽 점 ( Z < -1)
bit 6 - 관측 공간의 앞쪽 점 ( Z > 0 )

2 차원의 경우처럼, 아웃코드가 000000 이면 선은 수용되고 아니면 거부된다(코드 1과 코드 2의 논리 AND 연산의 결과가 000000 이 아니라면 거부된다). 다른 경우에는, 선이 나누어져 계산된다. 절단 모서리와의 교차점은 선의 절단 범위를 사용해서 구할 수 있다. P0 가 ( x0, y0, z0 ) 인 점에서 P1 이 ( x1, y1, z1 ) 인 점까지 이은 선의 제한 범위는 다음과 같다.

t 는 0 과 1 사이의 값이며 이 식을 이용해서 P0 과 P1 사이에서 선 위의 모든 점들의 좌표를 얻을 수 있다.

예 : y=1 인 평면과 한 선분의 교차 계산하기, y=1 은 식 (2) 로 바꿀 수 있다. 이를 적용해 보면 (4) 식을 얻을 수 있다. :

식 (4) 에 식 (1) 과 (3) 을 적용하면 식 (5) 와 (6) 을 얻을 수 있다. :

식 (5) 와 (6) 의 모든 값들은 알고 있으므로 이 두 식을 교차점의 x 와 z 좌표를 찾기 위해서 풀 수 있다. 이 식을 이용하면 선을 두 부분으로 나눌 수 있게 된다.

투영

투영은 뷰잉 파이프라인의 4번째 단계이다. 일반적으로, 투영은 n 차원 좌표계의 점을 n 차원 보다 작은 차원의 좌표계 점으로 변환하는 것을 말한다. 투영은 투영의 중심(PRP) 에서 퍼져 나가는 투영선들에 의해서 정의된다. 이 때 이 투영선들은 3D 로 표현된 물체의 정점들을 통과하여 투영 평면에 부딪쳐서 투영을 만들어 낸다.

[그림] (a)원근 투영과 (b)평행 투영

원근 투영을 위해서 투영의 중심을 설정해야하고, 평행 투영을 위해서는 투영의 방향(DOP) 를 설정해야한다.

투영의 계산

여기에선 z 축의 z=d 곳에 z 축과 수직인 투영 평면을 가진 원근 투영과 투영 평면이 z=0 인 곳에 있는 평행 투영인 두 가지 종류의 투영에 대해서 다루었다. 이 두 투영은 4x4 행렬로 표현할 수 있어서 다른 변환들과 혼합할 수 있다.

원근 투영의 경우에, 삼각형 계산과 비슷하다.

이므로

이 식은 다음과 같이 동차좌표점이 있는 4x4 행렬로 나타낼 수 있다.(3차원 공간의 점을 얻기 위해서는 x, y, z를 W 로 나누면 된다)

z=0 에 있는 투영 평면에 투영하는 평행 투영의 경우는 아주 간단하다. 투영의 방향은 z 축과 수직인 투영 평면과 같다. 점 P (x, y, z) 는 다음과 같이 투영된다. :

이 식을 행렬로 표현하면 다음과 같다.

z-좌표는 없다.

뷰포트에 매핑하기

매핑은 뷰잉 파이프라인의 5번째 단계이다. 뷰포트에 매핑하는 것은 투영 변환이 수행된 후에 처리한다. 뷰포트에 매핑하는 것은 정규 뷰 평면에서 디스플레이 장치의 실제 화면 좌표계로 사상(매핑)하는 것을 말한다. 이 매핑 방법은 정규 관측 공간에 따라 다르다. 여기서는 정규 평행 투영의 관측 공간인 정 6 면체를 사용하였다.

매핑 변환은 3 단계로 수행된다. :

1. 관측 공간의 코너 ( -1, -1, -1 ) 과 같은 곳을 원점으로 이동변환한다.
2. 뷰포트의 크기에 맞게 관측 공간을 크기 변환 한다.
3. 3D 뷰프트의 좌측 아래의 코너로 이동 변환한다.

이 매핑 변환에서 z 좌표를 그대로 유지 하는 것에 주목하길 바란다. 이 것은 z 좌표가 은면제거에 쓰이기 때문이다. 그러므로, 3D 뷰포트는 z 좌표의 최대 최소값을 가질 뿐만 아니라 x 와 y 의 최대 최소값도 가지고 있다.

첫 이동 변환은 간단한다 :

T(1,1,1)

두 번째 크기변환은 :

세 번째 이동 변환은 :

T(Xmin, Ymin, Zmin)

물론, 이 들 변환은 하나의 변환으로 조합될 수 있다.

이제, 결과 프리미티브를 화면에 출력할 수 있게 되었다. z 좌표는 z 버퍼링이라는 기술을 사용해 은면제거에 쓰인다.

The "Rendering Pipeline" package was developed in 1995 by Paul Brooke during his final year project.

번역 : 김성철
email : paranwave@korea.com