OpenGL шейдеры: Вторая часть. GLSL Шейдер в OpenTK

В продолжение перевода с С++ на C#/OpenTk, привожу вторую и последнюю часть цикла уроков.
В прошлой заметке всё было усыпано скриншотами, о том как создать и настроить проект, а далее приводился лишь кусок кода. В этот раз будет по большей части код.
Основной момент, состоит в том, что вместо GLM из плюсового кода мы используем возможности OpenTk для работы с матрицами.

Код:
/*http://esate.ru, badcat*/

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.

Результат работы:


Впрочем он должен быть такой же, как в оригинальном плюсовом коде.

Ссылка на оригинал.
0.0754       137        14.05.2018 02:52:58

^