.NET으로 구현하는 OpenGL (1) - OpenGL.Net 라이브러리
제가 ^^ 게임 프로그래머는 아니지만, 좋은 OpenGL 강좌가 있길래 따라 해 봤습니다.
TheThinMatrix/OpenGL-Tutorial-1
; https://github.com/TheThinMatrix/OpenGL-Tutorial-1
OpenGL 3D Game Tutorial 1: The Display
; https://www.youtube.com/watch?v=VS8wlS9hF8E
위의 강좌는 Java로 하고 있지만 1편의 개발 환경 구성을 제외하고는 대부분 닷넷에서도 그대로 따라 할 수 있습니다. (아니, 그럴 것 같습니다. ^^) 닷넷으로 하는 경우, 저 강좌의 1편은 자바 특화 환경이라 굳이 볼 필요가 없습니다. 닷넷은, 닷넷에 맞게 구성해야 하는데 우선 C# 프로젝트에서 NuGet을 이용해 다음의 패키지를 설치합니다.
OpenGL.Net
; https://github.com/luca-piccioni/OpenGL.Net
이 글에서는 Windows Forms 응용 프로그램으로 호스팅할 것이므로, 다음의 3개 정도만 추가하면 됩니다.
Install-Package OpenGL.Net
Install-Package OpenGL.Net.Math
Install-Package OpenGL.Net.WinForms
그런 다음, MainForm에 "Toolbox"에 있는 OpenGL.Net.WinForms의 "GlControl"을 얹어 놓습니다.
해당 GlControl의 속성 창을 이용해 다음의 4가지 이벤트를 구독하고,
- ContextCreated
- ContextDestroying
- ContextUpdate
- Render
"Animation" 속성 값을 "true"로 변경합니다
다시 말해, InitializeComponent (또는 그냥 MainForm의 생성자) 등의 메서드 안에 다음과 같은 코드가 추가되어 있으면 됩니다.
private void InitializeComponent()
{
this.glControl = new OpenGL.GlControl();
this.SuspendLayout();
//
// glControl
//
this.glControl.Animation = true;
this.glControl.AnimationTimer = false;
this.glControl.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
this.glControl.ColorBits = ((uint)(24u));
this.glControl.DepthBits = ((uint)(0u));
this.glControl.Dock = System.Windows.Forms.DockStyle.Fill;
this.glControl.Location = new System.Drawing.Point(0, 0);
this.glControl.MultisampleBits = ((uint)(0u));
this.glControl.Name = "glControl";
this.glControl.Size = new System.Drawing.Size(581, 270);
this.glControl.StencilBits = ((uint)(0u));
this.glControl.TabIndex = 0;
this.glControl.ContextCreated += new System.EventHandler<OpenGL.GlControlEventArgs>(this.glControl_ContextCreated);
this.glControl.ContextDestroying += new System.EventHandler<OpenGL.GlControlEventArgs>(this.glControl_ContextDestroying);
this.glControl.ContextUpdate += new System.EventHandler<OpenGL.GlControlEventArgs>(this.glControl_ContextUpdate);
this.glControl.Render += new System.EventHandler<OpenGL.GlControlEventArgs>(this.glControl_Render);
//
// MainForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(581, 270);
this.Controls.Add(this.glControl);
this.Name = "MainForm";
this.Text = "MainForm";
this.ResumeLayout(false);
}
이하 나머지는, 일단은 기본 프로젝트 구성이 목적이니 그냥 기본에 속한 이벤트 핸들러 코드만 작성하겠습니다.
Gl.DebugProc _debugProc;
private void glControl_ContextCreated(object sender, OpenGL.GlControlEventArgs e)
{
GlControl glControl = (GlControl)sender;
_debugProc = GLDebugMessageCallbackProc;
if (Gl.CurrentExtensions != null && Gl.CurrentExtensions.DebugOutput_ARB)
{
Gl.DebugMessageCallback(_debugProc, null);
Gl.DebugMessageControl(Gl.DebugSource.DontCare, Gl.DebugType.DontCare, Gl.DebugSeverity.DontCare, 0, null, true);
}
if (Gl.CurrentVersion != null && Gl.CurrentVersion.Api == KhronosVersion.ApiGl && glControl.MultisampleBits > 0)
{
Gl.Enable(EnableCap.Multisample);
}
}
private void GLDebugMessageCallbackProc(Gl.DebugSource source, Gl.DebugType type, uint id, Gl.DebugSeverity severity, int length, IntPtr message, IntPtr userParam)
{
string strMessage;
unsafe
{
strMessage = Encoding.ASCII.GetString((byte*)message.ToPointer(), length);
}
Debug.WriteLine($"{source}, {type}, {severity}: {strMessage}");
}
private void glControl_ContextUpdate(object sender, GlControlEventArgs e)
{
}
private void glControl_ContextDestroying(object sender, GlControlEventArgs e)
{
}
private void glControl_Render(object sender, OpenGL.GlControlEventArgs e)
{
Control senderControl = (Control)sender;
Gl.Viewport(0, 0, senderControl.ClientSize.Width, senderControl.ClientSize.Height);
}
위의 변경 사항들은 원래의 OpenGL.NET 예제에 있는 것을 보고 베낀 것입니다. ^^ 좀 더 자세한 예제가 궁금하다면 다음을 참고하시고.
OpenGL.Net/Samples/HelloTriangle/
; https://github.com/luca-piccioni/OpenGL.Net/tree/master/Samples/HelloTriangle
위의 구조에 "
OpenGL 3D Game Tutorial 1: The Display" 강좌의 DisplayManager 타입을,
// DisplayManager.cs
using OpenGL;
using System;
namespace GameApp
{
public class DisplayManager
{
public void createDisplay()
{
}
public void updateDisplay()
{
}
public void closeDisplay()
{
}
}
}
굳이 끼워 맞추자면 각각 ContextCreated, ContextUpdate, Render 이벤트 핸들러에서 다음과 같이 호출하는 식으로 작성할 수 있습니다.
using Khronos;
using OpenGL;
using System;
using System.Diagnostics;
using System.Text;
namespace GameApp
{
public class DisplayManager
{
GlControl _glControl;
Gl.DebugProc _debugProc;
public void createDisplay(GlControl glControl)
{
_debugProc = GLDebugMessageCallbackProc;
_glControl = glControl;
if (Gl.CurrentExtensions != null && Gl.CurrentExtensions.DebugOutput_ARB)
{
Gl.DebugMessageCallback(_debugProc, null);
Gl.DebugMessageControl(Gl.DebugSource.DontCare, Gl.DebugType.DontCare, Gl.DebugSeverity.DontCare, 0, null, true);
}
if (Gl.CurrentVersion != null && Gl.CurrentVersion.Api == KhronosVersion.ApiGl && glControl.MultisampleBits > 0)
{
Gl.Enable(EnableCap.Multisample);
}
}
private void GLDebugMessageCallbackProc(Gl.DebugSource source, Gl.DebugType type, uint id, Gl.DebugSeverity severity, int length, IntPtr message, IntPtr userParam)
{
string strMessage;
unsafe
{
strMessage = Encoding.ASCII.GetString((byte*)message.ToPointer(), length);
}
Debug.WriteLine($"{source}, {type}, {severity}: {strMessage}");
}
public unsafe void updateDisplay()
{
}
public void closeDisplay()
{
}
}
}
GlControl _glControl;
private void glControl_ContextCreated(object sender, OpenGL.GlControlEventArgs e)
{
GlControl glControl = (GlControl)sender;
_displayManager.createDisplay(glControl);
}
private void glControl_ContextDestroying(object sender, GlControlEventArgs e)
{
_displayManager.closeDisplay();
}
private void glControl_Render(object sender, OpenGL.GlControlEventArgs e)
{
// ...[생략]...
_displayManager.updateDisplay();
}
그래도 이렇게만 하고 실행하면 너무 심심하니, Render 메서드에 다음과 같은 정도의 내용만 추가해 보겠습니다. (게임 개발자가 아니라서 ^^ 더 멋있는 예제를 추가할 수가 없군요.)
Random _random = new Random(Environment.TickCount);
private unsafe void glControl_Render(object sender, OpenGL.GlControlEventArgs e)
{
Control senderControl = (Control)sender;
Gl.Viewport(0, 0, senderControl.ClientSize.Width, senderControl.ClientSize.Height);
int w = 200;
int h = 200;
uint* n = stackalloc uint[w * h * 3];
{
Gl.ClearColor(0.0f, 0.0f, 1.0f, 1.0f);
Gl.Clear(ClearBufferMask.ColorBufferBit);
for (int i = 0; i < (w * h * 3); i++)
{
*(n + i) = (uint)_random.Next(255) * 255u * 255u * 255u;
}
IntPtr ptr = new IntPtr(n);
Gl.DrawPixels(w, h, PixelFormat.Bgr, PixelType.UnsignedInt, ptr);
}
_displayManager.updateDisplay();
}
이렇게 하고 실행하면 좌측 하단에 지글(?)거리는 화면을 볼 수 있습니다.
(
첨부 파일은 이 글의 예제 프로젝트를 포함합니다.)
그나저나... DirectX뿐만 아니라, OpenGL이 의외로 닷넷에서도 매끄럽게 잘 연동이 되어 놀랐습니다. ^^
참고로 MainForm 생성 시
1st-chance 예외로 꼭 System.InvalidOperationException이 발생합니다.
System.InvalidOperationException
HResult=0x80131509
Message=unable to load library at libbcm_host.so
Source=OpenGL.Net
StackTrace:
at Khronos.GetProcAddressWindows.GetLibraryHandle(String libraryPath) in C:\OpenGL.Net\Khronos.Net\GetProcAddressOS.cs:line 304
Inner Exception 1:
Win32Exception: The specified module could not be found
무시하고 지나가면 되는데, System.InvalidOperationException 예외가 1st-chance 발생 시 멈추도록 설정되어 있다면 Visual Studio로 디버깅 시 무조건 한번 걸리게 되므로
"Debug" / "Exceptions..." 메뉴를 이용해 "thrown"으로 설정된 것을 해제해야 합니다.
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]