성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Roll A Lisp In C - Reading ; https...
[정성태] Java - How to use the Foreign Funct...
[정성태] 제가 큰 실수를 했군요. ^^; Delegate를 통한 Bein...
[정성태] Working with Rust Libraries from C#...
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
글쓰기
제목
이름
암호
전자우편
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 (4), (5) - Shader</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 (3) - Index Buffer ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11773'>http://www.sysnet.pe.kr/2/0/11773</a> </pre> <br /> 4회 강좌는,<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 4: Introduction to Shaders ; <a target='tab' href='https://www.youtube.com/watch?v=AyNZG_mqGVE'>https://www.youtube.com/watch?v=AyNZG_mqGVE</a> </pre> <br /> Shader에 대한 설명을 할 뿐, 딱히 코드의 변경은 없습니다. Shader를 도입한 코드의 변경은 5회 강좌에서 설명합니다.<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 5: Coloring using Shaders ; <a target='tab' href='https://youtu.be/4w7lNF8dnYw'>https://youtu.be/4w7lNF8dnYw</a> 소스 코드 ; <a target='tab' href='https://www.dropbox.com/sh/qtfhwru70y9sg8b/AAAweVar09wgu9DmmSO8yAf8a?dl=0'>https://www.dropbox.com/sh/qtfhwru70y9sg8b/AAAweVar09wgu9DmmSO8yAf8a?dl=0</a> </pre> <br /> Shader를 도입하기 위해, 우선 (ShaderProgram 클래스를 상속한) StaticShader 인스턴스 생성 및 해제 코드와 Shader를 적용할 Model의 렌더링 시 전/후처리를 합니다.<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'>StaticShader _shader;</span> private void glControl_ContextCreated(object sender, OpenGL.GlControlEventArgs e) { GlControl glControl = (GlControl)sender; _displayManager.createDisplay(glControl); _loader = new Loader(); _renderer = new Renderer(); <span style='color: blue; font-weight: bold'>_shader = new StaticShader();</span> _model = _loader.loadToVAO(_vertices, _indices); } private void glControl_ContextDestroying(object sender, GlControlEventArgs e) { _loader.CleanUp(); <span style='color: blue; font-weight: bold'>_shader.CleanUp();</span> } 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(); <span style='color: blue; font-weight: bold'>_shader.Start();</span> _renderer.Render(_model); <span style='color: blue; font-weight: bold'>_shader.Stop();</span> _displayManager.updateDisplay(); } </pre> <br /> 자, 그럼 StaticShader에는 무슨 일을 하느냐? 하면 GLSL 문법의 vertex shader와 fragment shader 파일을,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > #version 400 core in vec3 _position; out vec3 _colour; void main(void) { gl_Position = vec4(_position, 1.0); _colour = vec3(_position.x + 0.5, 1.0, _position.y + 0.5); } </pre> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > #version 400 core in vec3 _colour; out vec4 _out_Color; void main(void) { _out_Color = vec4(_colour, 1.0); } </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;' > using OpenGL; using System; using System.Collections.Generic; using System.IO; namespace GameApp { public abstract class ShaderProgram { uint _programID; uint _vertexShaderID; uint _fragmentShaderID; public ShaderProgram(<span style='color: blue; font-weight: bold'>string vertexFile, string fragmentFile</span>) { <span style='color: blue; font-weight: bold'>_vertexShaderID = loadShader(vertexFile, ShaderType.VertexShader); _fragmentShaderID = loadShader(fragmentFile, ShaderType.FragmentShader);</span> _programID = Gl.CreateProgram(); Gl.AttachShader(_programID, _vertexShaderID); Gl.AttachShader(_programID, _fragmentShaderID); <span style='color: blue; font-weight: bold'>bindAttributes();</span> Gl.LinkProgram(_programID); Gl.ValidateProgram(_programID); } <span style='color: blue; font-weight: bold'>protected abstract void bindAttributes();</span> public void Start() { Gl.UseProgram(_programID); } public void Stop() { Gl.UseProgram(0); } public void CleanUp() { Stop(); Gl.DetachShader(_programID, _vertexShaderID); Gl.DetachShader(_programID, _fragmentShaderID); Gl.DeleteShader(_vertexShaderID); Gl.DeleteShader(_fragmentShaderID); Gl.DeleteProgram(_programID); } protected void bindAttribute(uint attribute, string variableName) { Gl.BindAttribLocation(_programID, attribute, variableName); } static uint loadShader(string file, ShaderType type) { string[] codeText = ReadShaderCode(file); uint shaderID = Gl.CreateShader(type); Gl.ShaderSource(shaderID, codeText); <span style='color: blue; font-weight: bold'>Gl.CompileShader(shaderID);</span> int compileResult = Gl.FALSE; Gl.GetShader(shaderID, ShaderParameterName.CompileStatus, out compileResult); if (compileResult == Gl.FALSE) { throw new InvalidDataException(OpenGLExtension.GetShaderInfoLog(shaderID)); } return shaderID; } private static string[] ReadShaderCode(string file) { // ...[생략: 파일 텍스트 로드]... } } } </pre> <br /> ShaderProgram 타입에서 bindAttributes 메서드를 abstract로 해놓았으니, 당연히 ShaderProgram 타입을 상속받은 타입을 정의해야 하고 그것이 MainForm.cs에서 사용한 StaticShader입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > namespace GameApp { public class <span style='color: blue; font-weight: bold'>StaticShader : ShaderProgram</span> { 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() { base.bindAttribute(0, "position"); } } } </pre> <br /> 바인딩은 0번 위치에 "position"이라는 이름으로 하고 있습니다. 이것은 vertexShader의 소스 코드를 보면 이해할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > #version 400 core <span style='color: blue; font-weight: bold'>in vec3 _position;</span> out vec3 _colour; void main(void) { gl_Position = vec4(_position, 1.0); _colour = vec3(_position.x + 0.5, 1.0, _position.y + 0.5); } </pre> <br /> 위의 소스 코드에 보면 "_position" 이름이 나오는데 원래 저 변수는 다음과 같이 선언한 것을 줄인 것입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > layout(location = 0) in vec3 _position; </pre> <br /> 다시 말해, 이름은 달라도 되지만 location으로 바인딩한 숫자는 틀리면 안 됩니다. 그렇긴 해도 이름 역시 맞춰주는 것이 일관성을 위해 좋을 것입니다. 만약 이름을 기준으로 location 위치를 동적으로 구하고 싶다면 다음과 같은 식으로 Gl.GetAttribLocation 메서드를 이용할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > protected void bindAttribute(...) { uint id = (uint)Gl.GetAttribLocation(_programID, "_position"); // vertex shader 코드의 변수 중 "_position"에 대한 location 값을 반환 Gl.BindAttribLocation(_programID, id, "_position"); } </pre> <br /> <hr style='width: 50%' /><br /> <br /> 그런데, 사실 Gl.BindAttribLocation 메서드에서는 이름과 ID만을 바인딩할 뿐 값이 없습니다. 실질적인 값은, VBO가 로드된 VAO의 슬롯 번호를 통해서 전달하기 때문입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public RawModel loadToVAO(float [] positions, int[] indices) { uint vaoID = createVAO(); bindIndicesBuffer(indices); storeDataInAttributeList(<span style='color: blue; font-weight: bold'>0, positions</span>); unbindVAO(); return new RawModel(vaoID, positions.Length); } unsafe void storeDataInAttributeList(<span style='color: blue; font-weight: bold'>uint attributeNumber, float [] data</span>) { uint vboID = Gl.GenBuffer(); vbos.Add(vboID); Gl.BindBuffer(BufferTarget.ArrayBuffer, vboID); Gl.BufferData(BufferTarget.ArrayBuffer, (uint)(data.Length * sizeof(float)), data, BufferUsage.StaticDraw); <span style='color: blue; font-weight: bold'>Gl.VertexAttribPointer(attributeNumber, 3, VertexAttribType.Float, false, 0, IntPtr.Zero);</span> Gl.BindBuffer(BufferTarget.ArrayBuffer, 0); } </pre> <br /> Gl.VertexAttribPointer의 호출로 Vertex 위치를 가리키는 VBO 데이터가 attributeNumber (== 0)에 해당하는 슬롯으로 지정되었기 때문에 Shader의 Gl.GetAttribLocation에서 이 값과 연결된 것입니다. 그런데 솔직히 Gl.BindAttribLocation이 왜 필요한지 잘 모르겠습니다. 어차피 Gl.VertexAttribPointer에 의해 0번으로 지정되었는데, 그 값을 shader 처리 클래스에서 Gl.BindAttribLocation을 이용해 슬롯 번호를 다른 것으로 할당하는 것이 크게 의미가 없어 보이기 때문입니다. (혹시, 나중에 POSITION 관련 값들이 다중으로 전달될 때 이를 명시하기 위한 걸로 사용되는 걸까요?)<br /> <br /> 암튼, 실제로 현재 예제에서는 ShaderProgram 타입의 bindAttribute를 주석 처리해도 shader가 잘 동작합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > protected void bindAttribute(uint attribute, string variableName) { // Gl.BindAttribLocation(_programID, attribute, variableName); } </pre> <br /> 다음은 이번 글의 예제가 동작했을 때 보이는 화면입니다.<br /> <br /> <img alt='opengl_tutorial_5_1.png' src='/SysWebRes/bbs/opengl_tutorial_5_1.png' /><br /> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1398&boardid=331301885'>첨부 파일은 이 글의 예제 프로젝트를 포함</a>합니다.)<br /> <br /> <hr style='width: 50%' /><br /> <br /> 문서에 보면 아래의 예제에서,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > #version 400 core <span style='color: blue; font-weight: bold'>/* layout(location = 0) */ in vec3 position;</span> out vec3 colour; void main(void) { gl_Position = vec4(position, 1.0); colour = vec3(position.x + 0.5, 1.0, position.y + 0.5); } </pre> <br /> VertexAttribPointer에 전달한 attributeNumber 슬롯 번호는 위의 shader에 할당한 location의 값과 맞춰주기만 하면 된다고 합니다. 그런데 실제로 테스트해 보면 현재 단계에서는 오직 양쪽 모두 0번으로 설정했을 때만 정상적으로 그려지는 것을 확인할 수 있습니다. 만약 VertexAttribPointer의 값이 크고 shader 측의 location 값이 낮다면 비정상 종료하고, 그 반대의 경우라면 (당연히) 데이터가 안 들어왔을 테니 vertex shader의 출력이 비어 있게 됩니다.<br /> <br /> 아마도, VAO에 더 많은 VBO를 슬롯에 할당한 경우에는 번호를 맞춰주는 식으로 동작을 할 것 같습니다.<br /> <br /> 참고로, VAO에 무작정 많은 VBO 슬롯을 할당할 수 있는 것은 아닙니다. 이것은 버전마다 틀린데 근래의 GPU에서는 대부분 16개를 지원한다고 합니다. 이 슬롯의 최대 개수를 코드로 얻고 싶다면 다음과 같은 메서드를 만들 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > int GetMaxVertexAttribs() { ulong[] values = new ulong[1]; Gl.GetIntegerNV(Gl.MAX_VERTEX_ATTRIBS, values); return (int)values[0]; } </pre> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1797
(왼쪽의 숫자를 입력해야 합니다.)