Windows MFC (Microsoft Foundation Classes)를 사용하여 OpenGL을 초기화하고 간단한 2D 그래픽을 렌더링 하는 뷰 클래스를 사용하여 간단한 태양계 구축하기.
이번에는 OpenGL을 사용하여 태양계 모델링을 구현과 MFC(Microsoft Foundation Classes)를 사용하여 Windows 환경에서 OpenGL을 초기화하고 3D 모델을 렌더링 하여 보자.
COpenGLView 클래스
- COpenGLView 클래스 생성자 (COpenGLView::COpenGLView())
클래스 생성자에서는 행성과 달의 회전 등 각도 초기화를 수행한다! - COpenGLView::PreCreateWindow 함수
윈도우를 생성하기 전에 윈도 클래스 및 스타일을 수정하며, OpenGL 관련 플래그를 추가하고 화면 스크린 리더링 중 화면 청소 영역을 설정합니다.
cs.style |= (WS_CLIPCHILDREN | WS_CLIPSIBLINGS | CS_OWNDC);
cs.style |= (WS_CLIPCHILDREN | WS_CLIPSIBLINGS | CS_OWNDC); 이 코드는 윈도를 생성할 때 사용되는 CREATESTRUCT 구조체의 style 멤버에 특정 스타일 플래그를 추가하는 것을 의미하며, OpenGL을 사용하는 경우에 주로 설정되며 다음과 같은 역할을 한다.
- WS_CLIPCHILDREN: 이 스타일은 윈도우의 자식 윈도들이 상위 윈도의 클라이언트 영역에서 그려질 때, 다른 자식 윈도들의 영역을 덮지 않도록 합니다. 이것은 자식 윈도들 간의 그리기 충돌을 방지하고 그림이 깨지는 것을 방지하는 데 도움이 된다.
- WS_CLIPSIBLINGS: 이 스타일은 동일한 레벨에 있는 (동등한 부모 윈도우를 가진) 다른 윈도들이 현재 윈도의 클라이언트 영역을 덮지 않도록 하며, 이것은 형제 윈도 간의 그리기 충돌을 방지한다.
- CS_OWNDC: 이 스타일은 윈도우가 자체 디바이스 콘텍스트 (DC)를 소유하고 사용한다는 것을 나타냅니다. OpenGL을 사용할 때, OpenGL 디바이스 콘텍스트를 소유하는 것이 중요하기에 CS_OWNDC 플래그를 설정하면 현재 윈도에 대한 OpenGL 콘텍스트를 만들고 설정하는 데 도움이 된다.
- OpenGLView::OnDraw 함수
OpenGL을 사용하여 실제 그래픽을 그리는 함수입니다. 현재는 비어 있으며, 여기에 원하는 그래픽 렌더링 코드를 추가한다. - COpenGLView::OnRButtonUp 함수
마우스 오른쪽 버튼 업 이벤트를 처리하는 함수로, 컨텍스트 메뉴를 표시하기 위한 로직 등을 넣는다. - OpenGL 초기화 및 설정
COpenGLView::OnCreate 함수- OpenGL 컨텍스트 생성
- 픽셀 형식 설정
- 렌더링 컨텍스트를 현재로 만들기
- SetupRC() 함수를 호출하여 초기화 설정 수행
- 타이머 설정 (행성과 달의 회전을 위한 타이머)
- 윈도가 처음 생성될 때 호출되는 함수로, OpenGL 초기화를 수행한다!
int nPixelFormat;
m_hDC = ::GetDC(m_hWnd);
static PIXELFORMATDESCRIPTOR pfd = {
sizeof(PIXELFORMATDESCRIPTOR),
1,
PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,
PFD_TYPE_RGBA,
24,
0, 0, 0, 0, 0, 0,
0, 0,
0, 0, 0, 0, 0,
32,
0,
0,
PFD_MAIN_PLANE,
0,
0, 0, 0
};
nPixelFormat = ChoosePixelFormat(m_hDC, &pfd);
VERIFY(SetPixelFormat(m_hDC, nPixelFormat, &pfd));
m_hRC = wglCreateContext(m_hDC);
VERIFY(wglMakeCurrent(m_hDC, m_hRC));
SetupRC();
SetTimer(1, 50, NULL); // 행성 회전 타이머
SetTimer(2, 17, NULL); // 달 회전 타이머
- COpenGLView::OnDestroy 함수
윈도우가 파괴될 때 호출되는 함수로, OpenGL 리소스를 정리하고 콘텍스트를 해제한다. - COpenGLView::OnSize 함수
윈도우 크기 조정 이벤트를 처리하고 OpenGL 뷰포트와 투영 행렬을 설정한다.
그래픽 렌더링
- COpenGLView::RenderScene 함수
이 함수는 실제 그래픽을 렌더링 하는 함수입니다. 여기에서는 행성과 달의 3D 모델링 및 회전 등을 구현한다.
OpenGL 초기화 설정
- COpenGLView::SetupRC 함수
OpenGL 초기화 및 렌더링 설정을 수행하는 함수이며, OpenGL의 초기 설정, 렌더링 콘텍스트 설정, 그리기 옵션 등을 설정할 때 사용한다. - COpenGLView::OnTimer 함수
타이머 이벤트를 처리하는 함수로, 행성과 달의 회전을 업데이트하고 화면을 다시 그립니다. - COpenGLView::OnEraseBkgnd 함수
배경을 지우는 함수로, 이 예제에서는 배경을 지우지 않고 0을 반환하여 배경을 지우지 않습니다.
밑에 따로 추가적인 설명이 있습니다!
이렇게 글을 적으면서도 확실하게 머리속에 들어오지 않는다는 사실....
코드 구현
OnCreate() 함수 코드
int COpenGLView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
// CView 클래스의 OnCreate 함수를 호출
if (CView::OnCreate(lpCreateStruct) == -1)
return -1;
int nPixelFormat; // 픽셀 포맷 번호를 저장 변수
m_hDC = ::GetDC(m_hWnd); // 현재 윈도우의 디바이스 컨텍스트를 가져와 m_hDC 변수에 저장
static PIXELFORMATDESCRIPTOR pfd = {
sizeof(PIXELFORMATDESCRIPTOR), // 픽셀 포맷 디스크립터의 크기 설정
1, // 버전 번호 설정
PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, // 윈도우에 그리기 및 OpenGL 지원 설정
PFD_TYPE_RGBA, // RGBA 색상 모드 사용
24, // 색상 비트 수 설정
0, 0, 0, 0, 0, 0, // 사용하지 않는 비트 초기화
0, 0, // 사용하지 않는 값 초기화
0, 0, 0, 0, 0, // 사용하지 않는 값 초기화
32, // 깊이 버퍼 비트 수 설정
0, // 스텐실 버퍼를 사용하지 않도록 설정
0, // 오버레이를 사용하지 않도록 설정
PFD_MAIN_PLANE, // 주 플레인 설정
0, // 레이어를 지원하지 않도록 설정
0, 0, 0 // 사용하지 않는 값 초기화
};
nPixelFormat = ChoosePixelFormat(m_hDC, &pfd); // 적절한 픽셀 포맷 선택
VERIFY(SetPixelFormat(m_hDC, nPixelFormat, &pfd)); // 선택한 픽셀 포맷 설정
m_hRC = wglCreateContext(m_hDC); // OpenGL 컨텍스트 생성하고 m_hRC 변수에 저장
VERIFY(wglMakeCurrent(m_hDC, m_hRC)); // 현재 스레드에서 OpenGL 컨텍스트 사용 설정
SetupRC(); // OpenGL 렌더링 컨텍스트 설정 함수 호출
SetTimer(1, 50, NULL); // 타이머 1 설정, 50ms마다 타이머 메시지 생성
SetTimer(2, 17, NULL); // 타이머 2 설정, 17ms마다 타이머 메시지 생성
return 0; // 함수 종료, 성공을 나타내는 값 반환
}
OnDestory() 함수 코드
void COpenGLView::OnDestroy()
{
VERIFY(wglMakeCurrent(NULL, NULL)); // 현재 OpenGL 컨텍스트 해제
wglDeleteContext(m_hRC); // OpenGL 컨텍스트 삭제
::ReleaseDC(m_hWnd, m_hDC); // 디바이스 컨텍스트 해제
gluDeleteQuadric(m_objQuad); // Quadric 객체 삭제
KillTimer(1); // 타이머 1 해제
KillTimer(2); // 타이머 2 해제
CView::OnDestroy(); // CView 클래스의 OnDestroy 함수 호출
// TODO: 여기에 메시지 처리기 코드를 추가합니다.
}
OnSize() 함수 코드
void COpenGLView::OnSize(UINT nType, int cx, int cy)
{
// 부모 클래스의 OnSize 함수를 호출한 후
CView::OnSize(nType, cx, cy);
// 화면 가로 세로 비율을 설정
GLfloat fAspect;
// 화면의 높이가 0인 경우를 방지
if (cy == 0)
cy = 1;
// OpenGL의 viewport를 윈도우 크기에 맞게 설정
glViewport(0, 0, cx, cy);
// 가로 세로 비율을 계산
fAspect = (GLfloat)cx / (GLfloat)cy;
// 투영 행렬 모드로 전환하고 초기화
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
// 원근 투영 행렬을 설정
gluPerspective(45.0f, fAspect, 1.0f, 400.0f);
// 모델 뷰 행렬 모드로 전환하고 초기화
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
OnDraw() 함수 코드
void COpenGLView::OnDraw(CDC* /*pDC*/)
{
// 현재 문서를 가져온다.
COpenGLDoc* pDoc = GetDocument();
// 문서의 유효성을 검사하며
ASSERT_VALID(pDoc);
if (!pDoc)
return; // 문서가 유효하지 않으면 함수를 종료
// RenderScene 함수를 호출하여 3D 장면을 그린다
RenderScene();
// 그린 내용을 화면에 표시하기 위해 버퍼를 교체
SwapBuffers(m_hDC);
// TODO: 여기에 원시 데이터에 대한 그리기 코드를 추가합니다.
}
RenderScene() 함수 코드
void COpenGLView::RenderScene()
{
// 색상 버퍼와 깊이 버퍼를 지우고, 화면을 초기화
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// 양면 그리기와 깊이 테스트 활성화
glEnable(GL_CULL_FACE);
glEnable(GL_DEPTH_TEST);
gluQuadricNormals(m_objQuad, GLU_FLAT);
// 모델뷰 행렬 모드로 전환
glMatrixMode(GL_MODELVIEW);
// 원점에서 일정 거리 떨어진 곳에 빨간 구를 그림
glPushMatrix();
glTranslatef(0.0, 0.0, -300.0); // (x축, y축, z축);로 위치 설정
glColor3ub(255, 0, 0); // 빨간색으로 설정
gluSphere(m_objQuad, 15, 32, 32); // 구를 그림
glPopMatrix();
// 파란 구를 지구처럼 보이게 하기 위해 회전하고 그림
glPushMatrix();
glTranslatef(105.0, 0.0, 0.0); // (x축, y축, z축);로 위치 설정
glRotatef(m_fEarth, 0.0, 0.1, 0.0); // (각도, x축, y축, z축);로 회전 설정
glColor3ub(0, 0, 255); // 파란색으로 설정
gluSphere(m_objQuad, 15, 32, 32); // 구를 그림
glPopMatrix();
// 회색 구를 달처럼 보이게 하기 위해 회전하고 그림
glPushMatrix();
glTranslatef(30.0, 0.0, 0.0); // (x축, y축, z축);로 위치 설정
glRotatef(m_fMoon, 0.0, 1.0, 0.0); // (각도, x축, y축, z축);로 회전 설정
glColor3ub(200, 200, 200); // 회색으로 설정
gluSphere(m_objQuad, 6, 32, 32); // 구를 그림
glPopMatrix();
}
SetupRC() 함수 코드
void COpenGLView::SetupRC()
{
// 배경색을 검은색(R=0, G=0, B=0)으로 설정하고 투명도(alpha)는 1.0으로 설정
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
// OpenGL 배경색을 설정
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
// 그림자 모드를 설정
glShadeModel(GL_FLAT);
// 다각형의 정면을 설정
glFrontFace(GL_CCW);
// Quadric 객체를 생성하여 다양한 기하학적 도형을 그릴 수 있음
m_objQuad = gluNewQuadric();
// 타이머를 설정하여 200밀리초(0.2초)마다 Timer1 메시지를 생성
SetTimer(1, 200, NULL);
}
Ontimer() 함수 코드
void COpenGLView::OnTimer(UINT_PTR nIDEvent)
{
// 타이머 이벤트 처리
// nIDEvent는 타이머의 ID를 나타냅니다.
if (nIDEvent == 1) {
// 타이머 ID가 1인 경우 지구(Earth)의 회전 각도를 6.0도 증가
m_fEarth += 6.0f;
// 회전 각도가 360도를 넘으면 0도로 초기화
if (m_fEarth > 360.0f)
m_fEarth = 6.0f;
// 화면을 다시 그리도록 강제 업데이트
InvalidateRect(NULL);
}
else if (nIDEvent == 2) {
// 타이머 ID가 2인 경우 달(Moon)의 회전 각도를 6.0도 증가
m_fMoon += 6.0f;
// 회전 각도가 360도를 넘으면 360도를 빼서 0도로 만든다
if (m_fMoon > 360.0f)
m_fMoon -= 360.0f;
// 화면을 다시 그리도록 강제 업데이트
InvalidateRect(NULL);
}
// 부모 클래스의 OnTimer 함수를 호출
CView::OnTimer(nIDEvent);
}
이렇게 각 함수들에 코드들을 입력한 후 실행을하면... 화면이 깜빡인다.. 신경이 쓰인다.
왜 이러한 현상이 발생하는지 구글링을 하여보니 Windows에서 WM_PAINT 메시지가 발생하는 상황인 것이다...
- 윈도가 처음 생성될 때, 윈도의 위치가 변경될 때, 윈도의 크기가 변경될 때 (최소화 및 최대화 포함),
윈도가 다른 윈도에 가려져 있던 부분이 나타날 때, 윈도가 스크롤될 때, UpdateWindow나 RedrawWindow 함수가 호출될 때, InvalidateRect나 InvalidateRgn 함수가 호출되어 다시 그려져야 할 영역이 발생하고, Message Queue에 처리해야 할 다른 Windows Message가 없을 때
WM_PAINT 메시지가 발생하기 '전'에 WM_ERASEBKGND 메시지가 보통 함께 전송됩니다.
WM_ERASEBKGND는 "배경을 지워라"를 의미하며, 기본적으로 Windows의 기본 메시지 프로시저(DefWindowProc)는 WM_ERASEBKGND를 받으면 WNDCLASS의 hbrBackground로 지정된 색상으로 배경을 지웁니다.
이로 인해 화면이 깜박이는 현상이 발생하며, 이를 방지하기 위해 WM_ERASEBKGND 메시지를 처리하여 배경을 지우지 않도록 할 수 있습니다. 일반적으로 WM_ERASEBKGND 메시지 핸들러인 OnEraseBkgnd에서 0을 반환하면 됩니다.
또한, 화면 깜빡임 문제가 자식 윈도 또는 컨트롤에 의해 발생하는 경우, 부모 윈도에 WS_CLIPCHILDREN 스타일을 적용하여 자식 컨트롤에 의해 가려지는 영역을 그리기에서 제외시킬 수 있습니다.
출처 : 데브피아 ( 박재민(MAXIST) )
따라서 클래스 마법사를 이용해 밑에 코드를 추가하면 현상이 해결이 된다.
BOOL COpenGLView::OnEraseBkgnd(CDC* pDC)
{
// TODO: 여기에 메시지 처리기 코드를 추가 및/또는 기본값을 호출합니다.
return 0; //CView::OnEraseBkgnd(pDC);
}
이렇게 태양과 지구 그리고 달까지 구현해 보았다.
'C, C++ > OpenGL' 카테고리의 다른 글
OpenGl을 이용한 간단한 태양계 구축 프로젝트(3) (0) | 2023.11.24 |
---|---|
C/C++ OpenGl을 이용한 간단한 태양계 구축 프로젝트(2) (1) | 2023.11.18 |