Microsoft MVP성태의 닷넷 이야기
Graphics: 27. .NET으로 구현하는 OpenGL (1) - OpenGL.Net 라이브러리 [링크 복사], [링크+제목 복사],
조회: 27750
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일

(시리즈 글이 8개 있습니다.)
Graphics: 27. .NET으로 구현하는 OpenGL (1) - OpenGL.Net 라이브러리
; https://www.sysnet.pe.kr/2/0/11770

Graphics: 28. .NET으로 구현하는 OpenGL (2) - VAO, VBO
; https://www.sysnet.pe.kr/2/0/11772

Graphics: 29. .NET으로 구현하는 OpenGL (3) - Index Buffer
; https://www.sysnet.pe.kr/2/0/11773

Graphics: 30. .NET으로 구현하는 OpenGL (4), (5) - Shader
; https://www.sysnet.pe.kr/2/0/11774

Graphics: 31. .NET으로 구현하는 OpenGL (6) - Texturing
; https://www.sysnet.pe.kr/2/0/11775

Graphics: 32. .NET으로 구현하는 OpenGL (7), (8) - Matrices and Uniform Variables, Model, View & Projection Matrices
; https://www.sysnet.pe.kr/2/0/11784

Graphics: 33. .NET으로 구현하는 OpenGL (9), (10) - OBJ File Format, Loading 3D Models
; https://www.sysnet.pe.kr/2/0/11787

Graphics: 34. .NET으로 구현하는 OpenGL (11) - Per-Pixel Lighting
; https://www.sysnet.pe.kr/2/0/11792




.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"을 얹어 놓습니다.

opengl_tutorial_1_0.png

해당 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();
}

이렇게 하고 실행하면 좌측 하단에 지글(?)거리는 화면을 볼 수 있습니다.

opengl_tutorial_1_1.png

(첨부 파일은 이 글의 예제 프로젝트를 포함합니다.)

그나저나... 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"으로 설정된 것을 해제해야 합니다.




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]







[최초 등록일: ]
[최종 수정일: 11/21/2020]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 



2021-03-26 09시06분
dotnet/Silk.NET - high-speed .NET multimedia, graphics, and compute; providing bindings to popular low-level APIs such as OpenGL, OpenCL, OpenAL, OpenXR, GLFW, SDL, Vulkan, Assimp, and DirectX.
; https://github.com/dotnet/Silk.NET
정성태
2022-07-22 09시14분
STRIDE - Open-source C# Game Engine
; https://www.stride3d.net/

On .NET Live - Taking .NET game development in Stride
; https://www.youtube.com/watch?v=J6g5y8m26zs&ab_channel=dotNET

Graph3D: A Windows.Forms Render Control in C#
; https://www.codeproject.com/Articles/5293980/Graph3D-A-Windows-Forms-Render-Control-in-Csharp
정성태
2023-06-27 08시43분
ComputeSharp 2.0 - DirextX 12 및 D2D1으로 GPU에서 C#을 쉽게 실행
; https://forum.dotnetdev.kr/t/computesharp-2-0-dirextx-12-d2d1-gpu-c/7498
정성태

... 151  152  [153]  154  155  156  157  158  159  160  161  162  163  164  165  ...
NoWriterDateCnt.TitleFile(s)
1227정성태2/3/201229244.NET Framework: 299. 해당 어셈블리가 Debug 빌드인지, Release 빌드인지 알아내는 방법파일 다운로드1
1226정성태1/28/201270153.NET Framework: 298. 홀 펀칭(Hole Punching)을 이용한 Private IP 간 통신 - C# [15]파일 다운로드3
1225정성태1/24/201225746.NET Framework: 297. 특정 EXE 파일의 실행을 Internet Explorer처럼 "Protected Mode"로 실행하는 방법 [1]파일 다운로드1
1224정성태1/21/201237269개발 환경 구성: 139. 아마존 EC2에 새로 추가된 "1년 무료 Windows 서버 인스턴스"가 있다는데, 직접 만들어 볼까요? ^^ [11]
1223정성태1/20/201227291.NET Framework: 296. 괜찮은 문자열 해시함수? - 두 번째 이야기 [1]파일 다운로드1
1222정성태1/18/201235017.NET Framework: 295. 괜찮은 문자열 해시 함수? [4]파일 다운로드1
1221정성태1/17/201223998오류 유형: 147. System.Runtime.InteropServices.COMException (0x80005000)
1220정성태1/15/201224151.NET Framework: 294. Master web.config 파일을 수정하려면?파일 다운로드1
1219정성태1/15/201226558.NET Framework: 293. Microsoft PowerPoint 슬라이드를 HTML 파일로 ".files" 폴더 없이 저장하는 방법 (C# 코드)파일 다운로드1
1218정성태1/15/201239055.NET Framework: 292. RSACryptoServiceProvider의 공개키와 개인키 구분 [1]파일 다운로드2
1217정성태1/14/201241147.NET Framework: 291. .NET에서 WAV, MP3 파일 재생하는 방법 [1]파일 다운로드1
1216정성태1/14/201229874오류 유형: 146. Microsoft Visual C++ 재배포 패키지 - 설치 로그 남기는 방법 [1]
1215정성태1/9/201227427제니퍼 .NET: 20. 제니퍼 닷넷 적용 사례 (3) - '닷넷'이 문제일까? '닷넷 개발자'가 문제일까? [6]
1214정성태1/3/201224290제니퍼 .NET: 19. 제니퍼 닷넷 설치/제거 방법 - IIS
1213정성태12/31/201124218.NET Framework: 290. WCF - 접속된 클라이언트의 IP 주소 알아내는 방법 - 두 번째 이야기
1212정성태12/31/201124320오류 유형: 145. The trust relationship between this workstation and the primary domain failed.
1211정성태12/31/201129085.NET Framework: 289. WindowsFormsHost를 사용하는 XBAP 응용 프로그램파일 다운로드1
1210정성태12/30/201148090.NET Framework: 288. FFmpeg.exe를 이용한 C# 동영상 인코더 예제 [9]파일 다운로드1
1209정성태12/29/201122730개발 환경 구성: 138. BizTalk 2006 설치 방법
1208정성태12/28/201145706.NET Framework: 287. Excel Sheet를 WinForm에서 사용하는 방법 [8]파일 다운로드2
1207정성태12/26/201124990.NET Framework: 286. x86/x64로 구분된 코드를 포함하는 경우, 다중으로 어셈블리를 만들어야 할까요?파일 다운로드1
1206정성태12/25/201125991.NET Framework: 285. Shader 강좌와 함께 배워보는 XNA Framework (3) - 텍스처 매핑 예제파일 다운로드1
1205정성태12/25/201131698.NET Framework: 284. Thread 개체의 Interrupt와 Abort의 차이점파일 다운로드1
1204정성태12/22/201125198.NET Framework: 283. MEF를 ASP.NET에 성능 손실 없이 적용하려면? [7]
1203정성태12/21/201125567제니퍼 .NET: 18. MEF가 적용된 ASP.NET 웹 사이트를 제니퍼 닷넷으로 모니터링 해본 결과! [6]
1202정성태12/21/201125977오류 유형: 144. The database '...' cannot be opened because it is version 661.
... 151  152  [153]  154  155  156  157  158  159  160  161  162  163  164  165  ...