В прошлой заметке всё было усыпано скриншотами, о том как создать и настроить проект, а далее приводился лишь кусок кода. В этот раз будет по большей части код.
Основной момент, состоит в том, что вместо GLM из плюсового кода мы используем возможности OpenTk для работы с матрицами.
Код:
using System; using System.Drawing; using System.IO; using OpenTK; using OpenTK.Graphics; using OpenTK.Graphics.OpenGL; namespace openTk2 { public class GlShader : IDisposable { int ShaderProgram; int vertex_shader; int fragment_shader; private void ReleaseUnmanagedResources() { GL.UseProgram(0); GL.DeleteShader(vertex_shader); GL.DeleteShader(fragment_shader); GL.DeleteProgram(ShaderProgram); } //Освобождение ресурсов шейдера public void Dispose() { ReleaseUnmanagedResources(); GC.SuppressFinalize(this); } ~GlShader() { ReleaseUnmanagedResources(); } public int loadFiles(string vertex_file_name, string fragment_file_name) { vertex_shader = loadSourcefile(vertex_file_name, ShaderType.VertexShader); fragment_shader = loadSourcefile(fragment_file_name, ShaderType.FragmentShader); linkProgram(); return ShaderProgram; } private int loadSourcefile(string file_name, ShaderType type) { var source = File.ReadAllText(file_name); return compileSource(source, type); } public int load(string vertex_source, string fragment_source) { vertex_shader = compileSource(vertex_source, ShaderType.VertexShader); fragment_shader = compileSource(fragment_source, ShaderType.FragmentShader); linkProgram(); return ShaderProgram; } private int compileSource(string source, ShaderType type) { //! Создаем фрагментный шейдер int shader = GL.CreateShader(type); //! Передаем исходный код GL.ShaderSource(shader, source); //! Компилируем шейдер GL.CompileShader(shader); return shader; } //! Функция печати лога шейдера private static void ShaderLog(string tag, int shader) { var infoLog = ""; GL.GetShaderInfoLog(shader, out infoLog); if (infoLog.Length > 1) Console.WriteLine(tag + " InfoLog: " + infoLog + "\n\n\n"); } private void linkProgram() { if (vertex_shader == 0 || fragment_shader == 0) { ShaderProgram = 0; return; } ShaderProgram = GL.CreateProgram(); GL.AttachShader(ShaderProgram, vertex_shader); GL.AttachShader(ShaderProgram, fragment_shader); GL.LinkProgram(ShaderProgram); string log = GL.GetProgramInfoLog(ShaderProgram); if (string.IsNullOrEmpty(log) == false) Console.WriteLine(log); } public void use() { GL.UseProgram(ShaderProgram); } //--------------------------------------------------- //! Attribute get public int getAttribLocation(string name) { int location = -1; location = GL.GetAttribLocation(ShaderProgram, name); if (location == -1) Console.WriteLine("Could not bind attribute {0}", name); return location; } //! Attribute get public int getUniformLocation(string name) { int location = -1; location = GL.GetUniformLocation(ShaderProgram, name); if (location == -1) Console.WriteLine("Could not bind attribute {0}", name); return location; } public void setUniform(int unifMatrix, Matrix4 matrixProjection) { GL.UniformMatrix4(unifMatrix, false, ref matrixProjection); } } class Game : GameWindow { private GlShader shader = new GlShader(); //! ID атрибута вершин int Attrib_vertex; //! ID атрибута цветов int Attrib_color; //! ID юниформ матрицы проекции int Unif_matrix; //! ID Vertex Buffer Object int VBO_vertex; //! ID Vertex Buffer Object int VBO_color; //! ID VBO for element indices int VBO_element; //! Вершина public struct Vertex { public float x; public float y; public float z; }; //! Количество индексов int Indices_count; //! Матрица проекции Matrix4 Matrix_projection; public Game() : base(640, 480, new GraphicsMode(new ColorFormat(32), 16), "Simple shaders on OpenTK!") { } protected override void OnLoad(EventArgs e) { base.OnLoad(e); GL.ClearColor(Color.Black); //! Инициализация initGL(); initVBO(); initShader(); } protected override void OnUnload(EventArgs e) { base.OnUnload(e); //! Освобождение ресурсов freeVBO(); shader.Dispose(); } //! Функция печати лога шейдера static void ShaderLog(string tag, int shader) { string infoLog; GL.GetShaderInfoLog(shader, out infoLog); if (infoLog.Length > 1) Console.WriteLine(tag + " InfoLog: " + infoLog + "\n\n\n"); } //! Проверка ошибок OpenGL, если есть то вывод в консоль тип ошибки static void CheckOpenGLerror() { ErrorCode errCode = GL.GetError(); if (errCode != ErrorCode.NoError) { Console.WriteLine("OpenGl error! - {0}", errCode); Console.WriteLine(System.Environment.StackTrace); } } void initGL() { GL.ClearColor(0, 0, 0, 0); GL.Enable(EnableCap.DepthTest); } //! Инициализация шейдеров private void initShader() { //! Исходный код шейдеров const string vsSource = @"attribute vec3 coord; attribute vec3 color; varying vec3 var_color; uniform mat4 matrix; void main() { gl_Position = matrix * vec4(coord, 1.0); var_color = color; }"; const string fsSource = @"varying vec3 var_color; void main() { gl_FragColor = vec4(var_color, 1.0); }"; if(shader.load(vsSource, fsSource) == -1) { Console.WriteLine("error load shader"); return; } CheckOpenGLerror(); //! Вытягиваем ID атрибута из собранной программы Attrib_vertex = shader.getAttribLocation("coord"); CheckOpenGLerror(); //! Вытягиваем ID юниформ Attrib_color = shader.getAttribLocation("color"); CheckOpenGLerror(); //! Вытягиваем ID юниформ матрицы проекции Unif_matrix = shader.getUniformLocation("matrix"); CheckOpenGLerror(); } //! Инициализация VBO_vertex void initVBO() { //! Вершины куба Vertex [] vertices = { new Vertex {x = -1.0f,y = -1.0f,z = -1.0f}, new Vertex {x = 1.0f, y =-1.0f, z =-1.0f}, new Vertex {x = 1.0f, y = 1.0f, z =-1.0f}, new Vertex {x = -1.0f,y = 1.0f, z =-1.0f}, new Vertex {x = -1.0f,y = -1.0f,z = 1.0f}, new Vertex {x = 1.0f, y =-1.0f, z = 1.0f}, new Vertex {x = 1.0f, y = 1.0f, z = 1.0f}, new Vertex {x = -1.0f,y = 1.0f,z = 1.0f} }; //! Цвета куба без альфа компонента(RGB) Vertex [] colors = { new Vertex {x = 1.0f,y = 0.5f,z = 1.0f}, new Vertex {x = 1.0f,y = 0.5f,z = 0.5f}, new Vertex {x = 0.5f,y = 0.5f,z = 1.0f}, new Vertex {x = 0.0f,y = 1.0f,z = 1.0f}, new Vertex {x = 1.0f,y = 0.0f,z = 1.0f}, new Vertex {x = 1.0f,y = 1.0f,z = 0.0f}, new Vertex {x = 1.0f,y = 0.0f,z = 1.0f}, new Vertex {x = 0.0f,y = 1.0f,z = 1.0f} }; //! Индексы вершин, общие и для цветов uint [] indices = { 0, 4, 5, 0, 5, 1, 1, 5, 6, 1, 6, 2, 2, 6, 7, 2, 7, 3, 3, 7, 4, 3, 4, 0, 4, 7, 6, 4, 6, 5, 3, 0, 1, 3, 1, 2 }; // Создаем буфер для вершин GL.GenBuffers(1, out VBO_vertex); GL.BindBuffer(BufferTarget.ArrayBuffer, VBO_vertex); GL.BufferData(BufferTarget.ArrayBuffer, vertices.Length * Vector3.SizeInBytes, vertices, BufferUsageHint.StaticDraw); // Создаем буфер для цветов вершин GL.GenBuffers(1, out VBO_color); GL.BindBuffer(BufferTarget.ArrayBuffer, VBO_color); GL.BufferData(BufferTarget.ArrayBuffer, colors.Length * Vector3.SizeInBytes, colors, BufferUsageHint.StaticDraw); // Создаем буфер для индексов вершин GL.GenBuffers(1, out VBO_element); GL.BindBuffer(BufferTarget.ElementArrayBuffer, VBO_element); GL.BufferData(BufferTarget.ElementArrayBuffer, indices.Length * sizeof(int), indices, BufferUsageHint.StaticDraw); Indices_count = indices.Length; CheckOpenGLerror(); } void freeVBO() { GL.BindBuffer(BufferTarget.ArrayBuffer, 0); GL.BindBuffer(BufferTarget.ElementArrayBuffer, 0); GL.DeleteBuffers(1, ref VBO_element); GL.DeleteBuffers(1, ref VBO_element); GL.DeleteBuffers(1, ref VBO_color); } protected override void OnRenderFrame(FrameEventArgs e) { base.OnRenderFrame(e); GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); //! Устанавливаем шейдерную программу текущей shader.use(); //! Передаем матрицу в шейдер shader.setUniform(Unif_matrix, Matrix_projection); //! Подключаем буфер с индексами вершин общий для цветов и их вершин GL.BindBuffer(BufferTarget.ElementArrayBuffer, VBO_element); //! ВЕРШИНЫ //! Включаем массив атрибутов для вершин GL.EnableVertexAttribArray(Attrib_vertex); //! Подключаем VBO GL.BindBuffer(BufferTarget.ArrayBuffer, VBO_vertex); //! Указывая pointer 0 при подключенном буфере, мы указываем что данные в VBO GL.VertexAttribPointer(Attrib_vertex, 3, VertexAttribPointerType.Float, false, 0, 0); //! ЦВЕТА //! Включаем массив атрибутов для цветов GL.EnableVertexAttribArray(Attrib_color); GL.BindBuffer(BufferTarget.ArrayBuffer, VBO_color); GL.VertexAttribPointer(Attrib_color, 3, VertexAttribPointerType.Float, false, 0, 0); //! Передаем данные на видеокарту(рисуем) GL.DrawElements(BeginMode.Triangles, Indices_count, DrawElementsType.UnsignedInt, 0); //! Отключаем массив атрибутов GL.DisableVertexAttribArray(Attrib_vertex); //! Отключаем массив атрибутов GL.DisableVertexAttribArray(Attrib_color); CheckOpenGLerror(); SwapBuffers(); } protected override void OnResize(EventArgs e) { base.OnResize(e); GL.Viewport(ClientRectangle.X, ClientRectangle.Y, ClientRectangle.Width, ClientRectangle.Height); var height = ClientRectangle.Height > 0 ? ClientRectangle.Height : 1; float aspectRatio = (float)ClientRectangle.Width/(float)height; Matrix_projection = Matrix4.CreatePerspectiveFieldOfView(MathHelper.DegreesToRadians(45.0f), aspectRatio, 1.0f, 200.0f); // Перемещаем центр нашей оси координат для того чтобы увидеть куб Matrix_projection = Matrix4.Mult(Matrix4.CreateTranslation(new Vector3(0.0f, 0.0f, -10.0f)), Matrix_projection); // Поворачиваем ось координат(тоесть весь мир), чтобы развернуть отрисованное Matrix_projection = Matrix4.Mult(Matrix4.CreateFromAxisAngle(new Vector3(1.0f, 1.0f, 0.0f), MathHelper.DegreesToRadians(60.0f)), Matrix_projection); } } class Program { static void Main(string[] args) { var g = new Game(); g.Run(30.0f); } } } |
Этим кодом можно заменить код из прошлой заметки, он должен компилироваться также хорошо.
C# вариант класса для шейдеров реализует интерфейс IDisposable для правильного освобождения ресурсов шейдерной программы. Освобождение происходит по событию выгрузки класса Game OnUnload.
Для трансформации и поворота матриц применяются методы из класса Matrix4, которые есть в OpenTk.
Результат работы:
Впрочем он должен быть такой же, как в оригинальном плюсовом коде.