这是关于使用C#中的OpenTK库实现OpenGL4的系列教程的第五部分。在之前的教程中,介绍了如何初始化游戏窗口、编译着色器、链接着色器、传递数据给着色器以及重构代码和添加错误处理。本部分将基于之前的游戏窗口和着色器进行扩展。
首先,需要定义一个结构体来存储顶点的位置和颜色。
public struct Vertex
{
public const int Size = (4 + 4) * 4;
private readonly Vector4 _position;
private readonly Color4 _color;
public Vertex(Vector4 position, Color4 color)
{
_position = position;
_color = color;
}
}
上述结构体与顶点着色器相匹配,即它包含一个Vec4用于位置和一个Vec4用于颜色。Vector4和Color4由OpenTK提供。
_vertexArray = GL.GenVertexArray();
_buffer = GL.GenBuffer();
GL.BindVertexArray(_vertexArray);
GL.BindBuffer(BufferTarget.ArrayBuffer, _buffer);
接下来,需要告诉OpenGL如何使用这个新的缓冲区:
GL.NamedBufferStorage(
_buffer,
Vertex.Size * vertices.Length,
vertices,
BufferStorageFlags.MapWriteBit);
现在,需要提供关于着色器中属性数据位置的信息,即位置和颜色。首先是位置:
GL.VertexArrayAttribBinding(_vertexArray, 0, 0);
GL.EnableVertexArrayAttrib(_vertexArray, 0);
GL.VertexArrayAttribFormat(
_vertexArray,
0,
4,
VertexAttribType.Float,
false,
0);
然后是颜色:
GL.VertexArrayAttribBinding(_vertexArray, 1, 0);
GL.EnableVertexArrayAttrib(_vertexArray, 1);
GL.VertexArrayAttribFormat(
_vertexArray,
1,
4,
VertexAttribType.Float,
false,
16);
最后,使用以下命令将它们链接在一起:
GL.VertexArrayVertexBuffer(_vertexArray, 0, _buffer, IntPtr.Zero, Vertex.Size);
渲染所有内容:
GL.BindVertexArray(_vertexArray);
GL.DrawArrays(PrimitiveType.Triangles, 0, 3);
由于代码较多,决定创建一个名为RenderObject的类。上述初始化代码放入构造函数,渲染代码放入Render方法。
public class RenderObject : IDisposable
{
private bool _initialized;
private readonly int _vertexArray;
private readonly int _buffer;
private readonly int _verticeCount;
public RenderObject(Vertex[] vertices)
{
_verticeCount = vertices.Length;
_initialized = true;
}
public void Render()
{
GL.BindVertexArray(_vertexArray);
GL.DrawArrays(PrimitiveType.Triangles, 0, _verticeCount);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
if (_initialized)
{
GL.DeleteVertexArray(_vertexArray);
GL.DeleteBuffer(_buffer);
_initialized = false;
}
}
}
}
这将有助于在将来向场景中添加更多对象时的工作。
将VertexBuffer的初始化替换为初始化渲染对象列表。示例顶点数组应该在屏幕上显示一个粉红色的三角形,类似于本文标题图片。
private List<RenderObject> _renderObjects = new List<RenderObject>();
protected override void OnLoad(EventArgs e)
{
Vertex[] vertices =
{
new Vertex(new Vector4(-0.25f, 0.25f, 0.5f, 1f), Color4.HotPink),
new Vertex(new Vector4(0.0f, -0.25f, 0.5f, 1f), Color4.HotPink),
new Vertex(new Vector4(0.25f, 0.25f, 0.5f, 1f), Color4.HotPink),
};
_renderObjects.Add(new RenderObject(vertices));
CursorVisible = true;
_program = CreateProgram();
GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Fill);
GL.PatchParameter(PatchParameterInt.PatchVertices, 3);
Closed += OnClosed;
}
OnExit方法更改为释放已初始化的渲染对象。
public override void Exit()
{
Debug.WriteLine("Exit called");
foreach (var obj in _renderObjects)
obj.Dispose();
GL.DeleteProgram(_program);
base.Exit();
}
最后,OnRenderFrame代码循环遍历渲染对象,并调用它们独立的Render方法将它们显示在屏幕上。
protected override void OnRenderFrame(FrameEventArgs e)
{
_time += e.Time;
Title = $"{_title}: (Vsync: {VSync}) FPS: {1f / e.Time:0}";
Color4 backColor;
backColor.A = 1.0f;
backColor.R = 0.1f;
backColor.G = 0.1f;
backColor.B = 0.3f;
GL.ClearColor(backColor);
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
GL.UseProgram(_program);
foreach (var renderObject in _renderObjects)
renderObject.Render();
SwapBuffers();
}
#version 450 core
layout (location = 0) in vec4 position;
layout(location = 1) in vec4 color;
out vec4 vs_color;
void main(void)
{
gl_Position = position;
vs_color = color;
}
#version 450 core
in vec4 vs_color;
out vec4 color;
void main(void)
{
color = vs_color;
}