OpenTK---空间中三条线段的绘制

这篇博客介绍了如何利用OpenTK库在3D空间中绘制三条线段,模拟形成一个三角形。通过定义顶点坐标和索引,使用GL.DrawElements()函数按照指定顺序绘制线段。文章详细解析了GL.DrawElements()函数的参数含义,并对比了它与GL.DrawArrays()的区别。同时,还展示了着色器类和GLSL着色器代码,用于颜色渲染。
摘要由CSDN通过智能技术生成

之前我们已经提及了如何在空间中绘制单条线段,这里,我们将会说到如何在空间中绘制三条线段,使它们相互组合看起来像是一个三角形。注意,这里实际上我们所画的并不是一个三角形,因为三角形通常要求三条线段所围成的部分被填充。

  1. 创建主程序中的类
using OpenTK.Mathematics;
using OpenTK.Windowing.Desktop;

namespace OpenTK_SelfMadeBasis
{
    class Program
    {
        static void Main(string[] args)
        {
            NativeWindowSettings nativeWindowSettings = new NativeWindowSettings()
            {
                Size = new Vector2i(800, 600),
                Title = "Draw Three Lines",
            };
            using (Window window = new Window(GameWindowSettings.Default, nativeWindowSettings))
            {
                window.Run();
            }
        }
    }
}

截至到这篇为止,我们可以看到,无论我们想要在空间中画什么样的图形,主程序中的类都拥有相同的形式。唯一的差异是我们重新定义了Title参数的值,现在是Draw Three Lines

  1. 构建主程序中调用的GUI窗口界面的类
using OpenTK_SelfMadeBasis.Common;
using OpenTK.Graphics.OpenGL4;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.GraphicsLibraryFramework;
using OpenTK.Windowing.Desktop;

namespace OpenTK_SelfMadeBasis
{
    public class Window : GameWindow
    {
        // 我们想要画三条线段,并使其能够围成一个三角形,那么我们需要三个端点坐标
        private readonly float[] _vertices =
        {
            -0.5f, -0.5f, 0.0f,
             0.5f, -0.5f, 0.0f,
             0.0f,  0.5f, 0.0f,
        };

        // uint类型表示仅仅为单个的一个数,常作为placeholder(位置占用符)用
       	// 三个点,通过第0个点和第2个点绘制第1条线段,通过第0个点和第2个点绘制第2条线段,通过第1个点和第2个点绘制第3条线段
        private readonly uint[] _indices =
        {
            0, 1,
            0, 2,
            1, 2,
        };

        private int _vertexBufferObject;

        private int _vertexArrayObject;

        private Shader _shader;

        private int _elementBufferObject; // EBO,主要用于控制绘制三条线段的顺序

        public Window(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings)
            : base(gameWindowSettings, nativeWindowSettings)
        {
        }

        protected override void OnLoad()
        {
            GL.ClearColor(0.2f, 0.3f, 0.3f, 1.0f);

            _vertexBufferObject = GL.GenBuffer();

            GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBufferObject);

            GL.BufferData(BufferTarget.ArrayBuffer, _vertices.Length * sizeof(float), _vertices, BufferUsageHint.StaticDraw);

            _vertexArrayObject = GL.GenVertexArray();
            GL.BindVertexArray(_vertexArrayObject);

            GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, 3 * sizeof(float), 0);
            GL.EnableVertexAttribArray(0);

            _elementBufferObject = GL.GenBuffer();  // VBO
            GL.BindBuffer(BufferTarget.ElementArrayBuffer, _elementBufferObject);
            GL.BufferData(BufferTarget.ElementArrayBuffer, _indices.Length * sizeof(uint), _indices, BufferUsageHint.StaticDraw);

            _shader = new Shader("Shaders/shader.vert", "Shaders/shader.frag");

            _shader.Use();

            base.OnLoad();
        }

        protected override void OnRenderFrame(FrameEventArgs e)
        {
            GL.Clear(ClearBufferMask.ColorBufferBit);

            _shader.Use();

            GL.BindVertexArray(_vertexArrayObject);

            GL.DrawElements(PrimitiveType.Lines, _indices.Length, DrawElementsType.UnsignedInt, 0);

            SwapBuffers();

            base.OnRenderFrame(e);
        }

        protected override void OnUpdateFrame(FrameEventArgs e)
        {
            var input = KeyboardState;

            if (input.IsKeyDown(Keys.Escape))
            {
                Close();
            }

            base.OnUpdateFrame(e);
        }

        protected override void OnResize(ResizeEventArgs e)
        {
            GL.Viewport(0, 0, Size.X, Size.Y);
            base.OnResize(e);
        }

        protected override void OnUnload()
        {
            GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
            GL.BindVertexArray(0);
            GL.UseProgram(0);

            GL.DeleteBuffer(_vertexBufferObject);
            GL.DeleteVertexArray(_vertexArrayObject);

            GL.DeleteProgram(_shader.Handle);
            base.OnUnload();
        }
    }
}

细心的小伙伴也许发现了,在这个例子中我们使用的绘制图形命令为GL.DrawElements(PrimitiveType.Lines, _indices.Length, DrawElementsType.UnsignedInt, 0);这里介绍一下GL.DrawElements()函数的用法。

public static void DrawElements(PrimitiveType mode, int count, DrawElementsType type, int indices);

各个参数的意义:
PrimitiveType mode:之前我们已经说到过了,说明要渲染图元的类型。
int count:说明需要被渲染的元素的个数。
DrawElementsType type:说明索引参数的类型,只能为:UnsignedByteUnsignedShort或者UnsignedInt。本例中我们定义的时候为UnsignedInt类型。
int indices:说明索引参数存放的开始位置,将指针指向该处。我们这里的_indices参数中索引参数从第一个就开始了,因此这个值应该设置为0

那么这个函数与我们之前提到的GL.DrawArrays()有什么区别呢?并且两个函数各自的应用场景又是什么呢?

个人理解:如果每个端点只使用一次,并且端点之间是逐个按次序连接的关系,那么可以使用DrawArrays()方法。相反如果端点需要被重复使用,则应使用DrawElements()方法。即,单次有序调用使用DrawArrays()方法,多次无序调用使用DrawElements()方法

  1. 着色器类
using System;
using System.IO;
using System.Text;
using System.Collections.Generic;
using OpenTK.Graphics.OpenGL4;
using OpenTK.Mathematics;

namespace OpenTK_SelfMadeBasis.Common
{
    public class Shader
    {
        public readonly int Handle;

        private readonly Dictionary<string, int> _uniformLocations;

        public Shader(string vertPath, string fragPath)
        {
            var shaderSource = File.ReadAllText(vertPath);

            var vertexShader = GL.CreateShader(ShaderType.VertexShader);

            GL.ShaderSource(vertexShader, shaderSource);

            CompileShader(vertexShader);

            shaderSource = File.ReadAllText(fragPath);
            var fragmentShader = GL.CreateShader(ShaderType.FragmentShader);
            GL.ShaderSource(fragmentShader, shaderSource);
            CompileShader(fragmentShader);

            Handle = GL.CreateProgram();

            GL.AttachShader(Handle, vertexShader);
            GL.AttachShader(Handle, fragmentShader);

            LinkProgram(Handle);

            GL.DetachShader(Handle, vertexShader);
            GL.DetachShader(Handle, fragmentShader);
            GL.DeleteShader(fragmentShader);
            GL.DeleteShader(vertexShader);
            
            GL.GetProgram(Handle, GetProgramParameterName.ActiveUniforms, out var numberOfUniforms);

            _uniformLocations = new Dictionary<string, int>();

            for (var i = 0; i < numberOfUniforms; i++)
            {
                var key = GL.GetActiveUniform(Handle, i, out _, out _);

                var location = GL.GetUniformLocation(Handle, key);

                _uniformLocations.Add(key, location);
            }
        }

        private static void CompileShader(int shader)
        {
            GL.CompileShader(shader);

            GL.GetShader(shader, ShaderParameter.CompileStatus, out var code);
            if (code != (int)All.True)
            {
                var infoLog = GL.GetShaderInfoLog(shader);
                throw new Exception($"Error occurred whilst compiling Shader({shader}).\n\n{infoLog}");
            }
        }

        private static void LinkProgram(int program)
        {
            GL.LinkProgram(program);

            GL.GetProgram(program, GetProgramParameterName.LinkStatus, out var code);
            if (code != (int)All.True)
            {
                throw new Exception($"Error occurred whilst linking Program({program})");
            }
        }

        public void Use()
        {
            GL.UseProgram(Handle);
        }

        public int GetAttribLocation(string attribName)
        {
            return GL.GetAttribLocation(Handle, attribName);
        }

        public void SetInt(string name, int data)
        {
            GL.UseProgram(Handle);
            GL.Uniform1(_uniformLocations[name], data);
        }

        public void SetFloat(string name, float data)
        {
            GL.UseProgram(Handle);
            GL.Uniform1(_uniformLocations[name], data);
        }

        public void SetMatrix4(string name, Matrix4 data)
        {
            GL.UseProgram(Handle);
            GL.UniformMatrix4(_uniformLocations[name], true, ref data);
        }

        public void SetVector3(string name, Vector3 data)
        {
            GL.UseProgram(Handle);
            GL.Uniform3(_uniformLocations[name], data);
        }
    }
}

仔细对比不难发现,我们的着色器类也与前面提及的着色器类别无二致。

  1. 端点着色器
#version 330 core

layout(location = 0) in vec3 aPosition;

void main(void)
{
    gl_Position = vec4(aPosition, 1.0);
}
  1. 片段着色器
#version 330

out vec4 outputColor;

void main()
{
    outputColor = vec4(1.0, 1.0, 0.0, 1.0);
}

仔细对比不难发现,我们GLSL写的端点和片段着色器与之前绘制单条直线时所使用的着色器写法一致。

运行程序可以得到下面的结果:

在这里插入图片描述

码字不易,如果大家觉得有用,请高抬贵手给一个赞让我上推荐让更多的人看到吧~

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
要使用 OpenTK 绘制矩形,你可以使用 OpenGL 的基本绘图功能。下面是一个使用 OpenTK 绘制矩形的示例代码: ```csharp using OpenTK; using OpenTK.Graphics.OpenGL; public class RectangleRenderer : GameWindow { public RectangleRenderer() : base(800, 600) { VSync = VSyncMode.On; } protected override void OnLoad(EventArgs e) { base.OnLoad(e); GL.ClearColor(Color.CornflowerBlue); } protected override void OnResize(EventArgs e) { base.OnResize(e); GL.Viewport(0, 0, Width, Height); GL.MatrixMode(MatrixMode.Projection); GL.LoadIdentity(); GL.Ortho(0, Width, 0, Height, -1, 1); } protected override void OnRenderFrame(FrameEventArgs e) { base.OnRenderFrame(e); GL.Clear(ClearBufferMask.ColorBufferBit); GL.MatrixMode(MatrixMode.Modelview); GL.LoadIdentity(); GL.Begin(PrimitiveType.Quads); GL.Color3(Color.Red); GL.Vertex2(100, 100); // Top-left GL.Vertex2(200, 100); // Top-right GL.Vertex2(200, 200); // Bottom-right GL.Vertex2(100, 200); // Bottom-left GL.End(); SwapBuffers(); } protected override void OnUpdateFrame(FrameEventArgs e) { base.OnUpdateFrame(e); if (Keyboard[OpenTK.Input.Key.Escape]) { Exit(); } } public static void Main() { using (RectangleRenderer rectangleRenderer = new RectangleRenderer()) { rectangleRenderer.Run(60); } } } ``` 在上述代码,我们创建了一个名为 `RectangleRenderer` 的类,继承自 `GameWindow`。在 `OnLoad` 方法,我们设置了清除颜色。在 `OnResize` 方法,我们设置了视图矩阵,使用正交投影来适应窗口大小。在 `OnRenderFrame` 方法,我们使用 `GL.Begin` 和 `GL.End` 来绘制矩形的四个顶点。我们使用 `GL.Vertex2` 来指定每个顶点的坐标。在 `OnUpdateFrame` 方法,我们检查按下的键盘按键,如果按下了 Escape 键,则退出应用程序。 在 `Main` 方法,我们创建了一个 `RectangleRenderer` 的实例,并调用 `Run` 方法来运行应用程序。 请确保你的项目引用了 OpenTK 库,并且在项目属性的 "生成" 选项卡设置了 "平台目标" 为 x86 或 x64,以匹配你的系统架构。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

勤奋的大熊猫

你的鼓励将是我写作最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值