출처: http://soen.kr/lecture/library/opengl/opengl-4.htm
1. 기본 타입
2. 함수 형식
glVertex3i(1,2,3); int arv[]={1,2,3}; glVertex3iv(arv);
3차원상의 좌표는 x, y, z 세가지 값으로 표현하는 것이 원칙적이되 분수 표현을 위해 w로 분모를 지정할 수 있다. 또 평면상의 정점인 경우는 z 좌표를 생략하고 x, y만 밝힐 수도 있다. w가 생략되면 1로 간주되며 z를 생략하면 0으로 간주한다.
함수 형식을 일반화하여 표시하면 glVertex[2,3,4][s,i,f,d][v](x,y,z,w)
등으로 나타낼 수 있다.
3. 기초 예제
#include <windows.h> #include <gl/glut.h> void DoDisplay() { glClear(GL_COLOR_BUFFER_BIT); glBegin(GL_TRIANGLES); glVertex2f(0.0, 0.5); glVertex2f(-0.5, -0.5); glVertex2f(0.5, -0.5); glEnd(); glFlush(); } void main() { glutCreateWindow("OpenGL"); glutDisplayFunc(DoDisplay); glutMainLoop(); }
메인의 glutCreateWindow
함수는 OpenGL 출력을 위한 윈도우를 생성한다. 인수로는 타이틀 바에 출력될 제목 문자열을 전달한다. glutDisplayFunc
함수는 윈도우에 그리기를 전담하는 함수를 지정한다. 윈도우가 처음 생성될 때, 크기가 변할 때, 출력 내용을 바꾸었을 때 등에 그리기 함수가 호출된다. glutMainLoop
함수는 메시지 루프를 돌린다. 프로그램이 종료될 때까지 계속 실행된다.
DoDisplay
는 사용자 정의 함수이므로 이름은 물론 마음대로 정할 수 있다. 출력 내용이 바뀔 때마다 화면을 깔끔하게 지우기 위해 glClear
함수를 호출한다. glBegin
은 도형을 그리기 시작한다는 뜻이고 glEnd
는 도형을 다 정의했다는 뜻이다. 이 두 함수 호출문 사이에 도형을 구성하는 정점들이 배치된다.
정점들을 어떻게 연결할 것인가는 glBegin의 인수로 지정하는데 GL_TRIANGLES
로 주었으므로 세 정점들이 연결되어 삼각형이 그려진다.
다 그린 후에 glFlush
로 그리기 명령을 그래픽 카드로 보내 그린다. OpenGL은 속도를 높이기 위해 매 그리기 명령들을 즉시 수행하지 않고 버퍼에 모아 두었다가 한꺼번에 수행한다. glFlush는 버퍼를 비워 명령을 즉시 실행시킨다. 이 호출을 생략하면 정점만 정의될 뿐 출력은 나가지 않는다.
4. 색상 변경
void glClearColor(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha);
개의 인수는 RGBA 각 색상 요소의 강도를 지정한다. 각 요소의 강도는 0 ~ 1 사이의 실수값이며 0이면 해당 요소가 하나도 없는 것이고 1이면 최대 밝기이다. 타입명에 clamp
가 들어간 인수는 모두 0 ~ 1 사이의 범위를 가진다. (1,1,1,1)은 모든 요소가 최대 밝기이므로 흰색이고 (0,0,0,0)은 검정색이다. 이 함수는 이후부터 배경으로 사용할 색상을 지정하기만 할 뿐 실제 배경을 지우지는 않는다. 배경을 실제로 지울 때는 다음 함수를 호출한다.
void glClear(GLbitfield mask);
인수로 어떤 버퍼를 지울 것인가를 지정한다. OR 연산자로 연결하여 두 개 이상의 버퍼를 한꺼번에 지울 수도 있다.
#include <windows.h> #include <gl/glut.h> void DoDisplay() { // change color glClearColor(1.0, 0.24, 0.5, 0); glClear(GL_COLOR_BUFFER_BIT); glBegin(GL_TRIANGLES); glVertex2f(0.0, 0.5); glVertex2f(-0.5, -0.5); glVertex2f(0.5, -0.5); glEnd(); glFlush(); } void main() { glutCreateWindow("OpenGL"); glutDisplayFunc(DoDisplay); glutMainLoop(); }
배경 색상은 한번 지정해 놓으면 다른 값으로 바꾸지 않는 한 계속 유지된다. 그래서 매번 그리기를 할 때마다 지정할 필요없이 프로그램 초기화시에 별도의 함수에서 따로 설정하는 것이 원칙적이다.
#include <windows.h> #include <gl/glut.h> void DoDisplay() { glClear(GL_COLOR_BUFFER_BIT); glBegin(GL_TRIANGLES); glVertex2f(0.0, 0.5); glVertex2f(-0.5, -0.5); glVertex2f(0.5, -0.5); glEnd(); glFlush(); } void DoInit() { // change color glClearColor(1.0, 0.24, 0.5, 0); } void main() { glutCreateWindow("OpenGL"); glutDisplayFunc(DoDisplay); DoInit(); glutMainLoop(); }
도형의 색상은 다음 함수로 지정한다. 정확하게는 도형의 색상이 아닌 정점의 색상을 변경하는데 정점의 색상이 결과적으로 도형의 색상이 된다.
glColor[3,4][b,s,i,f,d,ub,us,ui][v](red, green, blue, alpha)
세가지 색상 요소의 강도를 실수로 지정하는 glColor3f
가 가장 일반적이다. 0 ~ 255까지의 정수로 강도를 지정할 때는 glColor3ub
함수도 종종 사용되는데 윈도우즈나 웹 환경의 색상 포맷과 동일해서 친숙하다.
#include <windows.h> #include <gl/glut.h> void DoDisplay() { // change background color glClearColor(1.0, 0.24, 0.5, 0); glClear(GL_COLOR_BUFFER_BIT); // change color of triangle glColor3f(0.4, 0.72, 0.1); glBegin(GL_TRIANGLES); glVertex2f(0.0, 0.5); glVertex2f(-0.5, -0.5); glVertex2f(0.5, -0.5); glEnd(); glFlush(); } void main() { glutCreateWindow("OpenGL"); glutDisplayFunc(DoDisplay); glutMainLoop(); }
5. 상태 머신
OpenGL은 그리기에 필요한 여러 가지 정보들을 상태 머신(State Machine)에 저장한다. 상태 머신이란 상태를 저장하는 장소이며 그리기에 영향을 미치는 여러 변수값들이 집합이다. 앞 항에서 실습해 본 배경 색상은 GL_COLOR_CLEAR_VALUE
상태 변수에 저장되며 현재 정점의 색상은 GL_CURRENT_COLOR
상태 변수에 저장된다.
모든 그리기 함수들은 인수로 전달받은 것 외의 정보들에 대해서는 상태 변수값을 읽어 사용한다. 상태 변수들은 적당한 디폴트로 초기화되어 있는데 예를 들어 배경색은 검정색이고 정점의 색은 흰색이다. 디폴트를 바꾸고 싶으면 변경 함수로 언제든지 다른 값으로 바꿀 수 있다. 배경색을 바꾸고 싶으면 glClearColor
함수를 호출하고 정점의 색상은 glColor
함수로 바꾼다.
상태 머신은 전역적이며 영속적인 저장소이므로 한번 지정해 놓은 상태 변수는 다른 값으로 바꾸기 전에는 계속 유효하다. 그래서 같은 값이라면 이전 값을 계속 사용할 수 있으며 매번 새로 지정할 필요가 없다.
#include <windows.h> #include <gl/glut.h> void DoDisplay() { glClear(GL_COLOR_BUFFER_BIT); glBegin(GL_POLYGON); //glColor3f(1.0, 1.0, 1.0); // white glVertex2f(0.0, 0.6); glColor3f(1.0, 0.0, 0.0); // red glVertex2f(-0.6, 0.0); //glColor3f(1.0, 0.0, 0.0); glVertex2f(-0.4, -0.6); glColor3f(0.0, 1.0, 0.0); // green glVertex2f(0.4, -0.6); glColor3f(0.0, 0.0, 1.0); // blue glVertex2f(0.6, 0.0); glEnd(); glFlush(); } void main() { glutCreateWindow("OpenGL"); glutDisplayFunc(DoDisplay); glutMainLoop(); }
상태값은 전역적이므로 함수 내부에서 뿐만 아니라 외부에서 바꾼 값에도 영향을 받으며 어디서 바꾸었건 한번 설정한 값은 다른 값으로 덮어 쓰기 전에는 계속 유효하다.
6. 점
3차원 그래픽의 가장 원자적인 요소는 정점(Vertex)이다. 정점은 색상이나 크기에 대한 정보는 없고 오로지 위치만을 가진다는 면에서 점과는 다르다. 정점은 다음 두 함수 블록 사이에서 정의한다.
void glBegin(GLenum mode);
void glEnd(void);
정점은 다음 함수로 정의한다.
<span>glVertex[2,3,4][s,i,f,d][v](x,y,z,w)</span>
블록내의 정점들로 무엇을 어떻게 그릴 것인가는 glBegin
으로 전달되는 모드값에 의해 결정된다.
void DoDisplay() { glClear(GL_COLOR_BUFFER_BIT); glBegin(GL_POINTS); glVertex2f(0.0, 0.5); glVertex2f(-0.5, -0.5); glVertex2f(0.5, -0.5); glEnd(); glFlush(); }
좌표는 위치만 가지는데 비해 점은 화면상에 표시되므로 크기를 변경할 수 있고 색상도 지정할 수 있다. 윈도우 환경에서는 1.0 ~ 63.375까지의 범위를 가지며 0.125 단위로 지정할 수 있다. 범위를 벗어나더라도 가장 가까운 값이 선택되며 에러는 나지 않는다. 점 크기는 다음 함수로 지정한다. 점 크기의 디폴트는 1이다.
void glPointSize(GLfloat size);
size 인수는 점을 감싸는 원의 직경을 지정한다. 안티 알리아싱을 하지 않으면 점은 시각형으로 출력되는데 size는 사각형의 한변 길이에 해당한다. 안티알리아싱을 지정하면 둥그렇게 원 모양으로 표시된다.
void DoDisplay() { glClear(GL_COLOR_BUFFER_BIT); glPointSize(10); // set point size glBegin(GL_POINTS); glVertex2f(0.0, 0.5); glVertex2f(-0.5, -0.5); glVertex2f(0.5, -0.5); glEnd(); glFlush(); }
7. 선
void DoDisplay() { glClear(GL_COLOR_BUFFER_BIT); glBegin(GL_LINE_STRIP); // or GL_LINE_LOOP { glVertex2f(0.0, 0.5); glVertex2f(-0.5, -0.5); glVertex2f(0.5, -0.5); } glEnd(); glFlush(); }
GL_LINE_STRIP
은 정점들을 연결하여 선분을 그린다. 처음과 끝을 연결하지 않으므로 열린 개곡선이 된다. GL_LINE_LOOP
는 선분을 이어서 그리고 시작점과 끝점을 자동으로 연결하여 폐곡선을 만든다.
void DoDisplay() { glClear(GL_COLOR_BUFFER_BIT); glBegin(GL_LINES); { GLfloat x = -0.8; GLfloat y = 0.4; for(int i = 0; i < 6; i++){ glVertex2f(x, y); x += 0.3; y *= -1; // toggle plus and minus } } glEnd(); glFlush(); }
GL_LINES
는 정점들을 두개씩 한 쌍으로 묶어 선을 그린다. 여러 개의 정점을 배치해 놓으면 각각 떨어진 선분이 그려진다. 첫번째, 두번째 정점이 연결되고 세번째, 네번째 정점이 연결되는 식이다. 정점이 6개이므로 두 개씩 짝을 지어 총 3개의 선분이 그려진다.
만약 정점이 홀수개라면 마지막 정점은 대응되는 짝이 없으므로 무시된다.
void DoDisplay() { glClear(GL_COLOR_BUFFER_BIT); GLfloat y; GLfloat w = 1; for(y = 0.8; y > -0.8; y -= 0.2) { glLineWidth(w++); glBegin(GL_LINES); { glVertex2f(-0.8, y); glVertex2f(0.8, y); } glEnd(); } glFlush(); }
실선이 아닌 다른 모양의 선을 그리려면 스티플(Stipple:점묘법) 기능을 켜야 한다. 특정 기능을 사용할 때는 glEnable
함수로 사용할 기능의 이름을 전달한다. 스티플 기능은 다음 호출문에 의해 활성화된다.
glEnable(GL_LINE_STIPPLE);
선의 모양은 다음 함수로 지정한다.
void glLineStipple(GLint factor, GLushort pattern);
pattern
은 이진수로 표현한 선의 모양이다. 하위 비트부터 선의 앞쪽 부분의 점 모양을 지정한다. 대응되는 비트가 1인 자리는 점이 찍히고 0인 부분은 찍히지 않는다. factor
인수는 비트 하나가 점 몇 개에 대응될 것인가를 지정한다. 이 값이 1이면 비트 하나가 점 하나에 대응되며 2이면 비트당 2개의 점이 그려져 좀 더 긴 모양을 만들 수 있다. 다음 예제는 점선, 쇄선 등을 출력한다.
void DoDisplay() { GLushort arPat[]= {0xaaaa,0xaaaa,0xaaaa,0xaaaa,0x33ff,0x33ff,0x33ff,0x57ff,0x57ff }; GLint arFac[] = { 1, 2, 3, 4, 1, 2, 3, 1, 2}; glClear(GL_COLOR_BUFFER_BIT); glEnable(GL_LINE_STIPPLE); // turn on line stripple GLfloat y; GLint idx = 0; for (y = 0.8; y > -0.8; y -= 0.2) { glLineStipple(arFac[idx], arPat[idx]); glBegin(GL_LINES); glVertex2f(-0.8, y); glVertex2f(0.8, y); glEnd(); idx++; } glFlush(); }
0xaaaa
는 이진수로 1010101010101010이므로 점과 공백이 계속 반복되는 점선이 된다. factor가 2면 점과 공백이 두 배로 확장되므로 더 성긴 점선이 그려진다. factor를 3, 4로 더 크게 지정하면 점 사이의 거리가 더욱 멀어진다.
0x33ff
는 일점 쇄선의 패턴을 지정하는데 각 비트가 어떻게 대응되는지를 보자.
하위 비트부터 오른쪽에서 순서대로 점과 대응되므로 비트를 뒤집어야 한다. 왜 하위부터 앞쪽 점에 대응시키는가하면 기계적 연산이 간단하기 때문이다. 점 10개 그리고 2개 건너 뛰고 2개 그리고 다시 2개 건너 뛴다. 긴선, 짧은선이 계속 반복되므로 일점 쇄선이 된다. 끝부분에 여백이 있어야 반복될 때 긴선과 짧은선이 붙지 않는다.
0x57ff
는 이진수로 0101011111111111이 되며 긴선 하나에 짧은선 두 개가 계속 반복되므로 이점 쇄선이다. 어떤 모양이든간에 비트로 선 모양을 만들고 16진수로 바꿔서 패턴으로 사용하면 된다.
8. 삼각형
3차원 그래픽의 기본 요소는 삼각형이다. GL_TRIANGLES
모드는 정점 세개씩을 모아서 삼각형을 그린다.
void DoDisplay() { glClear(GL_COLOR_BUFFER_BIT); glBegin(GL_TRIANGLES); GLfloat x = -0.8; GLfloat y = 0.4; for (int i = 0; i < 6; i++) { glVertex2f(x, y); x += 0.3; y *= -1; } glEnd(); glFlush(); }
GL_TRIANGLE_STRIP
은 삼각형을 계속 이어서 그린다. 첫 세 개의 정점으로 삼각형을 그리고 추가되는 정점을 새로운 꼭지점으로 하는 삼각형을 계속 이어서 그린다.
void DoDisplay() { glClear(GL_COLOR_BUFFER_BIT); glShadeModel(GL_FLAT); glBegin(GL_TRIANGLE_STRIP); GLfloat x = -0.8; GLfloat y = 0.4; for (int i = 0; i < 6; i++) { if (i % 2 == 0) { glColor3f(1.0, 1.0, 0.0); } else { glColor3f(0.0, 1.0, 1.0); } glVertex2f(x, y); x += 0.3; y *= -1; } glEnd(); glFlush(); }
각 삼각형을 분리해 보면 다음과 같다.
n개의 삼각형을 그리는데 n+2
개의 정점만 있으면 된다. 개별적으로 삼각형을 그리는 방법에 비해 이전 정점을 재활용하므로 메모리도 절약되고 속도도 훨씬 빠르다. 새로 그려지는 삼각형은 반시계 방향으로 그려진다.
GL_TRIANGLE_FAN
은 첫 삼각형의 꼭지점 하나를 고정해 두고 새로 추가되는 두 정점을 연결하여 계속 삼각형을 그린다.
void DoDisplay() { glClear(GL_COLOR_BUFFER_BIT); glShadeModel(GL_FLAT); glBegin(GL_TRIANGLE_FAN); glColor3f(1.0, 0.0, 0.0); glVertex2f(0.0, 0.0); // center-middle glVertex2f(0.0, 0.5); // go up to above glVertex2f(-0.35, 0.35); // down to left lower glColor3f(0.0, 1.0, 0.0); glVertex2f(-0.5, 0.0); glColor3f(1.0, 0.0, 0.0); glVertex2f(-0.35, -0.35); glColor3f(0.0, 1.0, 0.0); glVertex2f(0.0, -0.5); glEnd(); glFlush(); }
9. 사각형
사각형은 자주 사용되므로 별도의 그리기 함수가 제공된다.
<span>void glRect[i,s,f,d][v](x1, y1, x2, y2)</span>
완전히 독립된 함수이므로 glBegin
~ glEnd
블록에 포함시키지 않고도 사각형을 그릴 수 있다. 인수로 좌상단 좌표와 우하단 좌표를 주거나 또는 각 좌표값의 배열을 전달한다.
대각선의 두 점을 지정하는 식이므로 각 변이 수직인 직사각형만 그릴 수 있다. 평행사변형이나 사다리꼴처럼 직각이 아닌 사각형은 그릴 수 없다.
void DoDisplay() { glClear(GL_COLOR_BUFFER_BIT); glRectf(-0.8, 0.8, 0.8, -0.8); glFlush(); }
다음은 마름모를 그린다. GL_QUADS
는 4개씩 정점을 연결하여 사각형을 그린다. 제일 위의 꼭지점을 시작으로 하여 반시계 방향으로 마름모를 구성하는 꼭지점을 전달했다. 임의 위치의 정점을 전달할 수 있으므로 직각이 아닌 사각형을 그릴 수 있다.
void DoDisplay() { glClear(GL_COLOR_BUFFER_BIT); glBegin(GL_QUADS); glVertex2f(0.0, 0.5); glVertex2f(-0.5, 0.0); glVertex2f(0.0, -0.5); glVertex2f(0.5, 0.0); glEnd(); glFlush(); }
10. 다각형
GL_POLYGON
은 모든 정점을 하나로 연결하여 다각형을 그린다. 아무 다각형이나 그릴 수 있는 것은 아니고 까다로운 조건이 적용된다. GL_POLYGON
모드로 그리는 다각형음 다음 세 가지 조건을 반드시 만족해야 한다.
① 정점의 선이 교차해서는 안된다.
② 다각형은 볼록해야 한다.
③ 모든 정점은 같은 평면내에 있어야 한다.
리본 모양의 다각형은 선분이 교차되어 두 개의 다각형으로 분할된 것처럼 보인다. L자 모양의 다각형은 오목해서 적법하지 않다. 볼록하다는 것은 내부에서 임의의 선분을 그었을 때 선분이 다각형을 벗어나지 않아야 함을 뜻한다. 그러나 L자 모양은 선분이 바깥을 벗어날 수 있어 오목하다. 십자형의 다각형도 같은 이유로 적법하지 않다.
예를 들어 아래 그림의 오른쪽의 도형을 그리고 싶다고 할 때,
다음 코드로 그리면 결과는 나오지만 의도했던 결과가 나오지 않는다. 위 규칙에 맞지 않기 때문이다.
void DoDisplay() { glClear(GL_COLOR_BUFFER_BIT); glBegin(GL_POLYGON); glVertex2f(0.0, 0.5); glVertex2f(-0.5, 0.0); glVertex2f(0.5, 0.0); glVertex2f(0.0, -0.5); glEnd(); glFlush(); }
의도한 다각형을 그리고 싶다면 규칙에 맞는 두 개의 다각형으로 분할해야 한다. 이 경우는 리본의 아래쪽과 위쪽을 두 개의 삼각형으로 나누고 GL_TRIANGLES
모드로 그리면 된다. 볼록한 두 개의 다각형이 그려지며 둘 다 다각형 조건에 적합하다.
void DoDisplay() { glClear(GL_COLOR_BUFFER_BIT); glBegin(GL_TRIANGLES); glVertex2f(0.0, 0.5); glVertex2f(-0.5, 0.0); glVertex2f(0, 0); glEnd(); glBegin(GL_TRIANGLES); glVertex2f(0, 0); glVertex2f(0.5, 0.0); glVertex2f(0.0, -0.5); glEnd(); glFlush(); }
반드시 모든 정점은 한 면에 속해야 한다. 사각형은 항상 이 조건을 만족시키지 못한다. 반면 삼각형은 어떤 경우라도 한 평면에 속한다는 특징이 있다.
사각형도 조건만 맞추면 다각형이 될 수 있으며 삼각형보다 처리 속도가 빠르지만 반드시 조건을 지켜야 한다. 이런 이유로 3차원 그래픽을 구성하는 기본 단위는 대부분의 경우 삼각형이다. 아무리 복잡한 물체도 삼각형의 조합으로 표현할 수 있다. 사각형은 삼각형 2개를 붙이면 간단하게 정의된다. 참고로 모바일 환경의 OpenGL ES는 사각형을 아예 인정하지 않으며 무조건 삼각형만 가능하다. OpenGL이 다각형에 대해 이런 까다로운 제한을 두는 이유는 그래야 속도가 빠르기 때문이다.
폴리곤 모드(추후 설명)가 GL_FILL
이면 어차피 다 채워지므로 분할된 것인지 원래 하나인지 구분되지도 않는다. 그러나 GL_LINE
인 경우는 분할된 안쪽의 선도 그려진다. 안쪽 선을 숨기려면 매 선분마다 외곽선인지 아니면 다른 도형을 구성하는 내부 선분인지 지정해야 한다. 다음 함수로 지정한다.
void glEdgeFlag(GLboolean flag);
GL_TRUE
이면 이후의 정점으로 이동하면서 그려지는 선은 외곽선으로 인식된다. GL_FALSE
로 지정하면 큰 다각형을 구성하는 내부의 선으로 인식된다.
void DoDisplay() { glClear(GL_COLOR_BUFFER_BIT); glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); // 폴리곤 모드가 GL_LINE인 경우 glEdgeFlag(TRUE); glBegin(GL_TRIANGLES); if (bEdge) glEdgeFlag(TRUE); glVertex2f(0.0, 0.0); glVertex2f(-0.5, 0.5); if (bEdge) glEdgeFlag(FALSE); glVertex2f(-0.5, -0.5); glVertex2f(0.0, 0.0); if (bEdge) glEdgeFlag(TRUE); glVertex2f(0.5, -0.5); glVertex2f(0.5, 0.5); // 아래쪽 삼각형 if (bEdge) glEdgeFlag(FALSE); // 선분 1 glVertex2f(0.0, 0.0); if (bEdge) glEdgeFlag(TRUE); // 선분 2 glVertex2f(-0.5, -0.5); if (bEdge) glEdgeFlag(FALSE); // 선분 3 glVertex2f(0.5, -0.5); glEnd(); glFlush(); }
bEdge
기능을 사용하지 않으면(false
이라면) 디폴트로 모든 선분이 외곽선으로 인식되므로 안쪽 삼각형도 경계선이 그려진다. bEdge
변수를 토글하여 내부의 선임을 알려 주면 내부의 선이 사라져 오각형만 남는다.
0개의 댓글