OpenGL 렌더링 결과를 보여주기 위해 필요한 윈도우(UI)와 OpenGL 컨텍스트 생성을 MFC로 처리하는 방법을 설명합니다.
64비트 윈도우(x64)에서 OpenGL 3.x 이상 API를 사용하여 프로그래밍을 하는 경우를 대상으로 하고 있습니다.
Visual Studio 2017 Community를 사용해서 진행했지만 다른 버전에서도 거의 동일하게 동작할 듯합니다.
이제 다이얼로그기반 MFC 프로젝트 생성부터 예제 코드 실행까지 단계별로 설명하겠습니다.
MFC 응용 프로그램 생성
GLEW 라이브러리 추가
UI 구성
OpenGL 관련 코드 추가
테스트
관련 포스팅
참고한 사이트
최초작성 2017.5.26
마지막 수정 2018.1.29
MFC 응용 프로그램 생성
Visual Studio 2017을 실행시키고 메뉴에서 파일 > 새로 만들기 > 프로젝트를 선택합니다.
MFC 응용 프로그램을 선택하고 프로젝트 이름을 지정해 줍니다.
대화 상자 기반을 선택하고 마침을 클릭합니다.
메뉴 아래에 보이는 솔루션 구성을 Debug, 솔루션 플랫폼을 x64로 변경합니다.
프로젝트에 추가하게 되는 GLEW 라이브러리가 Release용이지만 MFC 디버깅을 위해 Debug로 설정합니다.
이제부터 Visual Studio 2017에서 OpenGL 개발을 하기위해 필요한 라이브러리를 프로젝트에 추가하는 방법을 설명합니다.
프로젝트 이름이 OpenGL with MFC이고 프로젝트 위치가 C:\Users\사용자이름\Documents\Visual Studio 2017\Projects\OpenGL with MFC인 경우로 가정하고 설명합니다.
사용하시는 환경에 맞추어 적절히 변경하시면 됩니다.
GLEW 라이브러리 추가
OpenGL 3.x 이상에서는 호출 가능한 OpenGL API 함수들이 런타임에 결정됩니다.
GLEW를 사용하여 런타임에 OpenGL API 함수를 호출합니다.
아래 링크에서 GLEW를 다운로드 받을 수 있습니다.
http://glew.sourceforge.net/
Windows 32-bit and 64-bit를 클릭하여 미리 라이브러리를 컴파일해둔 바이너리를 다운로드 받습니다.
압축을 풀은 후, include 폴더를 복사하여
프로젝트 폴더의 경로 OpenGL with MFC\OpenGL with MFC에 붙여넣기해줍니다.
기존에 생성된 include 폴더에 추가로 복사되어 집니다.
최근에 Visual Studio 2017 Community 버전을 설치했거나 업데이트를 했다면 다음처럼 소스 폴더의 위치가 변경된 것을 볼 수 있습니다.
C:\Users\사용자 이름\source\repos 아래에 프로젝트 폴더가 위치하게 됩니다.
압축을 풀은 폴더의 경로 glew-2.1.0\lib\Release\x64에 있는 glew32.lib, glew32s.lib를 복사하여
프로젝트 폴더의 경로 OpenGL-Example\OpenGL-Example\에 lib 폴더를 생성하고 붙여넣기 해줍니다.
GLEW 라이브러리를 사용하여 컴파일한 실행 파일과 같이 glew32.dll 파일을 배포해야 합니다.
아직 실행 파일이 저장되는 폴더가 생성되어 있지 않기 때문에 dll 파일을 복사해줄 위치가 아직 없습니다.
아직 코드를 추가하지 않았지만 Visual Studio의 메뉴에서 빌드 > 솔루션 빌드를 선택하여 컴파일을 해주면 실행파일이 저장될 위치를 위한 폴더들이 생성됩니다.
압축을 풀은 폴더의 경로 glew-2.1.0\bin\Release\x64에 있는 glew32.dll 파일을 복사하여
프로젝트 폴더의 경로 OpenGL with MFC\x64\Debug에 붙여넣기 해줍니다.
UI 구성
OpenGL의 렌더링 결과가 보여질 영역이 필요합니다.
메뉴에서 보기 > 리소스 뷰를 선택합니다. 리소스 뷰의 다이얼로그를 선택하고
오른쪽 끝에 위치한 도구 상자를 클릭 후 Picture Control을 드래그하여 다이얼로그에 추가합니다.
Picture Control을 배치한 결과입니다.
버튼은 이후 코드 수정시 사용하게 될 거 같아서 남겨두었습니다.
다이얼로그 위에 있는 Picture Control을 선택한 상태에서 마우스 우클릭하여 보이는 메뉴에서 속성을 클릭합니다.
ID를 IDC_PICTURE로 수정하고 엔터를 눌러 변경합니다.
다시 다이얼로그 위에 있는 Picture Control을 선택하고 마우스 오른쪽 버튼을 클릭하여 메뉴에서 변수 추가를 선택합니다.
멤버 변수 추가 마법사 창의 변수 이름 항목에 m_picture를 입력하고 마침을 클릭합니다.
OpenGL 관련 코드 추가
Ctrl + Shift + X를 눌러서 클래스 마법사를 실행합니다.
클래스 이름을 COpenGLwithMFCDlg로 변경합니다.
메시지 탭을 선택하고 메시지 리스트에서 WM_DESTROY를 선택 후 처리기 추가를 클릭합니다.
같은 방식으로 WM_TIMER도 선택해서 추가하고 확인을 클릭합니다.
OpenGL with MFCDlg.h 파일에 GLEW 헤더파일과 라이브러리 파일를 지정해줍니다.
// OpenGL with MFCDlg.h : 헤더 파일 //
#pragma once #include "afxwin.h"
//상대경로로 헤더파일을 지정합니다. #include "./include/GL/glew.h" #include "./include/GL/wglew.h"
//사용할 라이브러리를 지정해줍니다. #pragma comment(lib, "OpenGL32.lib") #pragma comment(lib, "./lib/glew32.lib") |
OpenGL with MFCDlg.h 파일 끝에 필요한 멤버함수 및 멤버 변수를 추가해줍니다.
public: CStatic m_picture;
public: afx_msg void OnDestroy(); afx_msg void OnTimer(UINT_PTR nIDEvent);
protected: virtual BOOL GetOldStyleRenderingContext(void); virtual BOOL SetupPixelFormat(void);
private: //OpenGL Setup BOOL GetRenderingContext(); //Rendering Context and Device Context Pointers HGLRC m_hRC; CDC* m_pDC;
GLuint vao; void defineVAO(GLuint &vao, GLuint &shaderProgram); GLuint create_program(); }; |
OpenGL with MFCDlg.cpp 파일의 OnInitDialog 함수에 붉은색 줄을 추가합니다.
// TODO: 여기에 추가 초기화 작업을 추가합니다. //OpenGL context 생성 if (!GetRenderingContext()) { AfxMessageBox(CString("OpenGL 초기화중 에러가 발생하여 프로그램을 실행할 수 없습니다.")); return -1; }
GLuint shaderProgram; defineVAO(vao, shaderProgram);
glUseProgram(shaderProgram); glBindVertexArray(vao);
SetTimer(1000, 30, NULL);
return TRUE; // 포커스를 컨트롤에 설정하지 않으면 TRUE를 반환합니다. } |
OnDestroy 함수에 다음 붉은색 줄을 추가합니다.
void COpenGLwithMFCDlg::OnDestroy() { CDialogEx::OnDestroy();
// TODO: 여기에 메시지 처리기 코드를 추가합니다. glDeleteVertexArrays(1, &vao);
if (FALSE == ::wglDeleteContext(m_hRC)) { AfxMessageBox(CString("wglDeleteContext failed")); } } |
OnTimer 함수에 붉은색 코드를 추가합니다.
void COpenGLwithMFCDlg::OnTimer(UINT_PTR nIDEvent) { // TODO: 여기에 메시지 처리기 코드를 추가 및/또는 기본값을 호출합니다.
CDialogEx::OnTimer(nIDEvent);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT);
glDrawArrays(GL_TRIANGLES, 0, 3);
//화면 업데이트 SwapBuffers(m_pDC->GetSafeHdc()); } |
OpenGL with MFCDlg.cpp 파일에 GetRenderingContext 함수를 추가합니다.
BOOL COpenGLwithMFCDlg::GetRenderingContext() { //픽처 컨트롤에만 그리도록 DC 생성 //참고 https://goo.gl/CK36zE CWnd* pImage = GetDlgItem(IDC_PICTURE); CRect rc; pImage->GetWindowRect(rc); m_pDC = pImage->GetDC();
if (NULL == m_pDC) { AfxMessageBox(CString("Unable to get a DC")); return FALSE; }
if (!GetOldStyleRenderingContext()) { return TRUE; }
//Get access to modern OpenGL functionality from this old style context. glewExperimental = GL_TRUE; if (GLEW_OK != glewInit()) { AfxMessageBox(CString("GLEW could not be initialized!")); return FALSE; }
//Get a new style pixel format if (!SetupPixelFormat()) { return FALSE; }
//참고 http://gamedev.stackexchange.com/a/30443 GLint attribs[] = { //OpenGL 3.3 사용 WGL_CONTEXT_MAJOR_VERSION_ARB, 3, WGL_CONTEXT_MINOR_VERSION_ARB, 3, // Uncomment this for forward compatibility mode //WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, // Uncomment this for Compatibility profile //WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB, // We are using Core profile here WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB, 0 };
HGLRC CompHRC = wglCreateContextAttribsARB(m_pDC->GetSafeHdc(), 0, attribs); if (CompHRC && wglMakeCurrent(m_pDC->GetSafeHdc(), CompHRC)) m_hRC = CompHRC;
return TRUE; } |
OpenGL with MFCDlg.cpp 파일에 defineVAO 함수를 추가합니다.
void COpenGLwithMFCDlg::defineVAO(GLuint &vao, GLuint &shaderProgram) { glGenVertexArrays(1, &vao); glBindVertexArray(vao);
float position[] = { 0.0f, 0.5f, 0.0f, //vertex 1 0.5f, -0.5f, 0.0f, //vertex 2 -0.5f, -0.5f, 0.0f //vertex 3 };
float color[] = { 1.0f, 0.0f, 0.0f, //vertex 1 : RED (1,0,0) 0.0f, 1.0f, 0.0f, //vertex 2 : GREEN (0,1,0) 0.0f, 0.0f, 1.0f //vertex 3 : BLUE (0,0,1) };
GLuint position_vbo, color_vbo;
glGenBuffers(1, &position_vbo); glBindBuffer(GL_ARRAY_BUFFER, position_vbo); glBufferData(GL_ARRAY_BUFFER, sizeof(position), position, GL_STATIC_DRAW);
glGenBuffers(1, &color_vbo); glBindBuffer(GL_ARRAY_BUFFER, color_vbo); glBufferData(GL_ARRAY_BUFFER, sizeof(color), color, GL_STATIC_DRAW);
shaderProgram = create_program();
GLint position_attribute = glGetAttribLocation(shaderProgram, "position"); glBindBuffer(GL_ARRAY_BUFFER, position_vbo); glVertexAttribPointer(position_attribute, 3, GL_FLOAT, GL_FALSE, 0, 0); glEnableVertexAttribArray(position_attribute);
GLint color_attribute = glGetAttribLocation(shaderProgram, "color"); glBindBuffer(GL_ARRAY_BUFFER, color_vbo); glVertexAttribPointer(color_attribute, 3, GL_FLOAT, GL_FALSE, 0, 0); glEnableVertexAttribArray(color_attribute);
glBindVertexArray(0); } |
OpenGL with MFCDlg.cpp 파일에 create_program 함수를 추가합니다.
GLuint COpenGLwithMFCDlg::create_program() {
const GLchar* vertexShaderSource = "#version 330 core\n" "in vec3 position;" "in vec3 color;" "out vec3 color_from_vshader;" "void main()" "{" "gl_Position = vec4(position, 1.0);" "color_from_vshader = color;" "}";
const GLchar* fragmentShaderSource = "#version 330 core\n" "in vec3 color_from_vshader;" "out vec4 out_color;" "void main()" "{" "out_color = vec4(color_from_vshader, 1.0);" "}";
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER); glShaderSource(vertexShader, 1, &vertexShaderSource, NULL); glCompileShader(vertexShader);
GLint success; GLchar infoLog[512];
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success); if (!success) { glGetShaderInfoLog(vertexShader, 512, NULL, infoLog); TRACE(CString("ERROR: vertex shader 컴파일 실패 ") + CString(infoLog) + CString("\n")); }
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL); glCompileShader(fragmentShader);
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success); if (!success) { glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog); TRACE(CString("ERROR: fragment shader 컴파일 실패 ") + CString(infoLog) + CString("\n")); }
GLuint shaderProgram = glCreateProgram(); glAttachShader(shaderProgram, vertexShader); glAttachShader(shaderProgram, fragmentShader);
glDeleteShader(vertexShader); glDeleteShader(fragmentShader);
glLinkProgram(shaderProgram);
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success); if (!success) { glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
TRACE(CString("ERROR: shader program 연결 실패 ") + CString(infoLog) + CString("\n")); }
return shaderProgram; } |
OpenGL with MFCDlg.cpp 파일에 GetOldStyleRenderingContext 함수를 추가합니다.
BOOL COpenGLwithMFCDlg::GetOldStyleRenderingContext() { //A generic pixel format descriptor. This will be replaced with a more //specific and modern one later, so don't worry about it too much. static PIXELFORMATDESCRIPTOR pfd = { sizeof(PIXELFORMATDESCRIPTOR), 1, PFD_DRAW_TO_WINDOW | // support window PFD_SUPPORT_OPENGL | // support OpenGL PFD_DOUBLEBUFFER, // double buffered PFD_TYPE_RGBA, // RGBA type 32, // 32-bit 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, // accum bits ignored 24, // 24-bit z-buffer 0, // no stencil buffer 0, // no auxiliary buffer PFD_MAIN_PLANE, // main layer 0, // reserved 0, 0, 0 // layer masks ignored };
// Get the id number for the best match supported by the hardware device context // to what is described in pfd int pixelFormat = ChoosePixelFormat(m_pDC->GetSafeHdc(), &pfd);
//If there's no match, report an error if (0 == pixelFormat) { AfxMessageBox(CString("ChoosePixelFormat failed")); return FALSE; }
//If there is an acceptable match, set it as the current if (FALSE == SetPixelFormat(m_pDC->GetSafeHdc(), pixelFormat, &pfd)) { AfxMessageBox(CString("SetPixelFormat failed")); return FALSE; }
//Create a context with this pixel format if (0 == (m_hRC = wglCreateContext(m_pDC->GetSafeHdc()))) { AfxMessageBox(CString("wglCreateContext failed")); return FALSE; }
//Make it current. if (FALSE == wglMakeCurrent(m_pDC->GetSafeHdc(), m_hRC)) { AfxMessageBox(CString("wglMakeCurrent failed")); return FALSE; } return TRUE; } |
OpenGL with MFCDlg.cpp 파일에 SetupPixelFormat 함수를 추가합니다.
BOOL COpenGLwithMFCDlg::SetupPixelFormat() { //This is a modern pixel format attribute list. //It has an extensible structure. Just add in more argument pairs //befroe the null to request more features. const int attribList[] = { WGL_DRAW_TO_WINDOW_ARB, GL_TRUE, WGL_SUPPORT_OPENGL_ARB, GL_TRUE, WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB, WGL_DOUBLE_BUFFER_ARB, GL_TRUE, WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB, WGL_COLOR_BITS_ARB, 32, WGL_DEPTH_BITS_ARB, 24, WGL_STENCIL_BITS_ARB, 8, 0, 0 //End };
unsigned int numFormats; int pixelFormat; PIXELFORMATDESCRIPTOR pfd;
//Select a pixel format number wglChoosePixelFormatARB(m_pDC->GetSafeHdc(), attribList, NULL, 1, &pixelFormat, &numFormats);
//Optional: Get the pixel format's description. We must provide a //description to SetPixelFormat(), but its contents mean little. //According to MSDN: // The system's metafile component uses this structure to record the logical // pixel format specification. The structure has no other effect upon the // behavior of the SetPixelFormat function. //DescribePixelFormat(m_pDC->GetSafeHdc(), pixelFormat, sizeof(PIXELFORMATDESCRIPTOR), &pfd);
//Set it as the current if (FALSE == SetPixelFormat(m_pDC->GetSafeHdc(), pixelFormat, &pfd)) { AfxMessageBox(CString("SelectPixelFormat failed")); return FALSE; }
return TRUE; } |
테스트
F5를 눌러서 실행시켜 봅니다.
관련 포스팅
OpenGL과 MFC 연동 예제( GLEW 사용, Dialog 기반, OpenGL 2.x 코드)
http://webnautes.tistory.com/1108
참고한 사이트
http://www.cs.uregina.ca/Links/class-info/315/WWW/Lab1/MFC/