성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] VT sequences to "CONOUT$" vs. STD_O...
[정성태] NetCoreDbg is a managed code debugg...
[정성태] Evaluating tail call elimination in...
[정성태] What’s new in System.Text.Json in ....
[정성태] What's new in .NET 9: Cryptography ...
[정성태] 아... 제시해 주신 "https://akrzemi1.wordp...
[정성태] 다시 질문을 정리할 필요가 있을 것 같습니다. 제가 본문에...
[이승준] 완전히 잘못 짚었습니다. 댓글 지우고 싶네요. 검색을 해보...
[정성태] 우선 답글 감사합니다. ^^ 그런데, 사실 저 예제는 (g...
[이승준] 수정이 안되어서... byteArray는 BYTE* 타입입니다...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>.NET으로 구현하는 OpenGL (6) - Texturing</h1> <p> 아래의 글에 이어,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > .NET으로 구현하는 OpenGL (4), (5) - Shader ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11774'>http://www.sysnet.pe.kr/2/0/11774</a> </pre> <br /> 6회 강좌는,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > OpenGL 3D Game Tutorial 6: Texturing ; <a target='tab' href='https://youtu.be/SPt-aogu72A'>https://youtu.be/SPt-aogu72A</a> </pre> <br /> 3D 객체에 texture를 입히는 방법에 대해 설명하고 있습니다. OpenGL에서 texture는 이미지의 색상 데이터를 구하는 것에서 시작하는데, 아쉽게도 OpenGL 자체는 이미지 파일들에 대한 로드 방법을 제공하지 않습니다. 즉, 그 부분은 우리가 직접 구현해야 하는 것입니다. 가령, 그나마 가장 간단한 유형인 BMP 파일의 경우 아래의 글에 설명하는 데로,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Tutorial 5 : A Textured Cube ; <a target='tab' href='http://www.opengl-tutorial.org/kr/beginners-tutorials/tutorial-5-a-textured-cube/'>http://www.opengl-tutorial.org/kr/beginners-tutorials/tutorial-5-a-textured-cube/</a> </pre> <br /> 직접 이미지의 데이터를 로드해서 glGenTextures, glBindTexture, glTexImage2D 등의 함수를 이용해 사용할 수 있습니다. 그런데, 사실 저건 C/C++의 상황에서 그런 것이고 닷넷이라면 Bitmap 타입이 알아서 대표적인 이미지들에 대한 데이터 처리를 해주므로,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > OpenGL C# (OpenTK) Load and Draw Image functions not working ; <a target='tab' href='https://stackoverflow.com/questions/11645368/opengl-c-sharp-opentk-load-and-draw-image-functions-not-working'>https://stackoverflow.com/questions/11645368/opengl-c-sharp-opentk-load-and-draw-image-functions-not-working</a> </pre> <br /> 그나마 간단하게 OpenGL 리소스로 바인딩할 수 있습니다. 혹은, 다양한 이미지 파일을 OpenGL 용으로 바인딩해주는 라이브러리를 가져다 쓰는 것도 가능합니다. soil이 그런 용도로 사용할 수 있는 오픈 소스인데요,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > soil 1.16.0 - Simple OpenGL Image Library ; <a target='tab' href='https://www.nuget.org/packages/soil/'>https://www.nuget.org/packages/soil/</a> ; <a target='tab' href='http://www.lonesock.net/soil.html'>http://www.lonesock.net/soil.html</a> </pre> <br /> NuGet에서도 제공하긴 하지만 아쉽게도 해당 라이브러리는 C/C++ 용으로 닷넷에서는 사용할 수 없습니다. 대신... ^^ 제가 저 라이브러리를 .NET 용으로 래핑했으므로,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > SOIL(Simple OpenGL Image Library) - Native DLL 및 .NET DLL 제공 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11768'>http://www.sysnet.pe.kr/2/0/11768</a> ; <a target='tab' href='https://www.nuget.org/packages/SoilDotnet'>https://www.nuget.org/packages/SoilDotnet</a> </pre> <br /> 저걸 가져다 쓰시면 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Install-Package SoilDotnet </pre> <br /> 자, 그럼 지난 5회 강좌의 소스 코드에 texture를 입히는 과정을 진행해 보겠습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 우선, 3D 객체의 정보를 담고 있는 Model에 texture 매핑 정보를 담을 UV 좌표 데이터를 연결해야 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // MainForm.cs <span style='color: blue; font-weight: bold'>float[] _textureCoords = { 0,0, // V0 0,1, // V1 1,1, // V2 1,0, // V3 };</span> private void glControl_ContextCreated(object sender, OpenGL.GlControlEventArgs e) { GlControl glControl = (GlControl)sender; _displayManager.createDisplay(glControl); // ...[생략]... // 3D 객체에 _textureCoords UV 좌표 데이터를 추가 _model = _loader.loadToVAO(_vertices, <span style='color: blue; font-weight: bold'>_textureCoords</span>, _indices); // ...[생략]... } </pre> <br /> loadToVAO에서는 당연히 _textureCoords 데이터를 GPU 메모리에 올리고, VAO의 슬롯 하나에 UV 데이터를 바인딩합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // Loader.cs public RawModel loadToVAO(float [] positions, float [] textures, int[] indices) { uint vaoID = createVAO(); bindIndicesBuffer(indices); <span style='color: blue; font-weight: bold'>storeDataInAttributeList(0, 3, positions);</span> // Position 데이터를 VAO의 0번 슬롯에 할당 <span style='color: blue; font-weight: bold'>storeDataInAttributeList(1, 2, textures);</span> // textures UV 매핑 데이터를 VAO의 1번 슬롯에 할당 unbindVAO(); return new RawModel(vaoID, positions.Length); } unsafe void storeDataInAttributeList(uint attributeNumber, int coordinateSize, float[] data) { uint vboID = Gl.GenBuffer(); _vbos.Add(vboID); Gl.BindBuffer(BufferTarget.ArrayBuffer, vboID); Gl.BufferData(BufferTarget.ArrayBuffer, (uint)(data.Length * sizeof(float)), data, BufferUsage.StaticDraw); Gl.VertexAttribPointer(attributeNumber, coordinateSize, VertexAttribType.Float, false, 0, IntPtr.Zero); Gl.BindBuffer(BufferTarget.ArrayBuffer, 0); } </pre> <br /> texture UV 매핑 데이터를 Model에 넣었으니 이제 해당 texture를 로드해야 합니다. 이를 위해 Texture 처리를 Loader 타입에 제공하고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // Loader.cs List<uint> _textures = new List<uint>(); public void CleanUp() { Gl.DeleteVertexArrays(_vaos.ToArray()); Gl.DeleteBuffers(_vbos.ToArray()); <span style='color: blue; font-weight: bold'>Gl.DeleteTextures(_textures.ToArray());</span> } <span style='color: blue; font-weight: bold'>public uint loadTexture(string fileName) { string filePath = $".\\res\\{fileName}.png"; uint tex2d_id = Soil.NET.WrapSOIL.load_OGL_texture(filePath, Soil.NET.WrapSOIL.SOIL_LOAD.AUTO, Soil.NET.WrapSOIL.SOIL_NEW.ID, Soil.NET.WrapSOIL.SOIL_FLAG.MIPMAPS | Soil.NET.WrapSOIL.SOIL_FLAG.NTSC_SAFE_RGB | Soil.NET.WrapSOIL.SOIL_FLAG.COMPRESS_TO_DXT); _textures.Add(tex2d_id); return tex2d_id; }</span> </pre> <br /> Model에 입힐 Texture 자원의 바인딩 핸들을 보관할 ModelTexture 타입과,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // ModelTexture.cs namespace GameApp { public class ModelTexture { uint _textureID; public uint ID { get { return _textureID; } } public ModelTexture(uint id) { this._textureID = id; } } } </pre> <br /> Texture가 지정된 Model을 나타내는 TextureModel 타입을 만듭니다. (작명이 다소 혼란스럽지만 <a target='tab' href='https://youtu.be/SPt-aogu72A?list=PLRIWtICgwaX0u7Rf9zkZhLoLuZVfUksDP'>원 강좌의 내용을 그대로 따라</a> 했습니다.)<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // TextureModel.cs namespace GameApp.Model { public class TextureModel { RawModel _rawModel; public RawModel RawModel { get { return _rawModel; } } ModelTexture _texture; public ModelTexture Texture { get { return _texture; } } public TextureModel(RawModel model, ModelTexture texture) { this._rawModel = model; this._texture = texture; } } } </pre> <br /> 이제 이것들을 통합해서 초기화하면 데이터는 모두 마련이 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // MainForm.cs ModelTexture _texture; TextureModel _textureModel; private void glControl_ContextCreated(object sender, OpenGL.GlControlEventArgs e) { GlControl glControl = (GlControl)sender; _displayManager.createDisplay(glControl); // SOIL.NET 초기화 <span style='color: blue; font-weight: bold'>bool result = Soil.NET.WrapSOIL.Initialize(); if (result == false) { MessageBox.Show("SOIL: Not initialized: " + Soil.NET.WrapSOIL.GetSoilLastError()); return; }</span> _loader = new Loader(); _renderer = new Renderer(); _model = _loader.loadToVAO(_vertices, _textureCoords, _indices); <span style='color: blue; font-weight: bold'>_texture = new ModelTexture(_loader.loadTexture("image")); _textureModel = new TextureModel(_model, _texture);</span> _shader = new StaticShader(); } </pre> <br /> (잊지 말고 /res 폴더에 256x256 크기의 image.png 파일을 넣어둡니다.)<br /> <br /> <hr style='width: 50%' /><br /> <br /> 나머지는 이제 렌더링과 관련된 변경을 해야 합니다. Texture가 지정된 Model이 되었으므로 이제 Renderer 타입에서는 _textureModel을 렌더링할 수 있어야 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // MainForm.cs private void glControl_Render(object sender, OpenGL.GlControlEventArgs e) { Control senderControl = (Control)sender; Gl.Viewport(0, 0, senderControl.ClientSize.Width, senderControl.ClientSize.Height); _renderer.Prepare(); _shader.Start(); _renderer.Render(<span style='color: blue; font-weight: bold'>_textureModel</span>); _shader.Stop(); _displayManager.updateDisplay(); } </pre> <br /> 따라서 Renderer의 Redner 메서드도 다음과 같이 바뀌게 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public void Render(TextureModel textureModel) { RawModel model = textureModel.RawModel; Gl.BindVertexArray(model.VaoID); Gl.EnableVertexAttribArray(0); <span style='color: blue; font-weight: bold'>Gl.EnableVertexAttribArray(1);</span> // UV 매핑 데이터 Slot 활성 <span style='color: blue; font-weight: bold'>Gl.ActiveTexture(TextureUnit.Texture0); Gl.BindTexture(TextureTarget.Texture2d, textureModel.Texture.ID);</span> Gl.DrawElements(PrimitiveType.Triangles, model.VertexCount, DrawElementsType.UnsignedInt, IntPtr.Zero); Gl.DisableVertexAttribArray(0); <span style='color: blue; font-weight: bold'>Gl.DisableVertexAttribArray(1);</span> // UV 매핑 데이터 Slot 비활성 Gl.BindVertexArray(0); } </pre> <br /> 끝입니다. 이렇게 변경하고 실행하면 다음과 같은 화면을 볼 수 있습니다.<br /> <br /> <img alt='opengl_tutorial_6_1.png' src='/SysWebRes/bbs/opengl_tutorial_6_1.png' /><br /> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1402&boardid=331301885'>첨부 파일은 이 글의 예제 프로젝트를 포함</a>합니다.)<br /> <br /> <hr style='width: 50%' /><br /> <br /> 참고로 이번에도 역시, bindAttribute의 코드는 없애도 실행에는 아무런 지장이 없습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // StaticShader.cs public class StaticShader : ShaderProgram { const string VERTEX_FILE = "./shaders/vertexShader.txt"; const string FRAGMENT_FILE = "./shaders/fragmentShader.txt"; public StaticShader() : base(VERTEX_FILE, FRAGMENT_FILE) { } protected override void bindAttributes() { <span style='color: blue; font-weight: bold'>// base.bindAttribute(0, "_position"); // base.bindAttribute(1, "_textureCoords");</span> } } </pre> </p><br /> <br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1800
(왼쪽의 숫자를 입력해야 합니다.)