OpenTK教程2 - 绘制三角形......正确的方法!

上一个教程向我们展示了如何在屏幕上绘制三角形。但是,它附带了免责声明。尽管它有效,但它不再是“正确”的做法。我们将几何体发送到GPU的方式就是所谓的“即时模式”,即使它非常简单,但不是最新的做法。

在本教程中,我们将采用相同的最终目标,但我们将以更复杂的方式处理事务,但同时更高效,更快速,更可扩展。

第1部分:设置

我们需要创建一个新的项目文件,引用OpenTK和System.Drawing,如上一个教程中所述。为方便起见,将其命名为OpenTKTutorial2。

第2部分:编码

首先,我们需要再次从第一个教程中完成一些基础知识。添加一个名为“Game”的新类。使它成为GameWindow的子类(您需要为OpenTK添加using指令才能使用该类)。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using OpenTK;

namespace OpenTKTutorial2
{
    class Game: GameWindow
    {
    }
}

返回Program.cs,并将以下代码添加到Main函数:

using (Game game = new Game())
{
       game.Run(30,30);
}

这是制作窗口并让它为我们弹出的相同代码(设置为每秒更新30次并以30 FPS渲染)。现在我们将再次重写onLoad来更改窗口的颜色和标题:

protected override void OnLoad(EventArgs e)
{
         base.OnLoad(e);
         Title = "Hello OpenTK!";
         GL.ClearColor(Color.CornflowerBlue);
}

像以前一样重写onRenderFrame,它将显示蓝色背景:

protected override void OnRenderFrame(FrameEventArgs e)
{
         base.OnRenderFrame(e);
         GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
         SwapBuffers();
}

现在我们可以了解新的东西!

我们首先需要做的是创建着色器。着色器是现代OpenGL如何知道如何绘制它所给出的值的着色器。我们将使用两种着色器:顶点着色器和片段着色器。顶点着色器告诉图形代码有关正在绘制的形状中的点的信息。片段着色器确定形状在绘制到屏幕时每个像素的颜色。我们将使用的代码非常简单,但它可以让我们绘制类似于立即模式。OpenGL的着色器使用类似C语言的脚本语言GLSL进行编程(但是和DirectX使用的语言略有不同,称为HLSL)。

将文本文件添加到名为“vs.glsl”的项目中。这将存储我们的顶点着色器:

#version 330

in vec3 vPosition;
in  vec3 vColor;
out vec4 color;
uniform mat4 modelview;

void
main()
{
    gl_Position = modelview * vec4(vPosition, 1.0);
    color = vec4( vColor, 1.0);
}

(注意:对于着色器文件,您可能需要告诉IDE将它们复制到输出目录。否则,程序将无法找到它们!)

第一行告诉链接器哪个版本的GLSL是正在使用。

“in”行指的是每个顶点不同的变量。“out”变量被发送到图形管道的下一部分,在那里它们被内插以实现跨片段的平滑过渡。我们正在发送每个顶点的颜色。“vec3”类型是指具有三个值的向量,“vec4”是具有四个值的向量。

这里还有一个“uniform”变量,对于绘制的整个对象都是相同的。这将有我们的变换矩阵,因此我们可以立即改变对象中的顶点。我们还没有使用它,但我们很快就会使用它。

我们的片段着色器非常简单。将以下内容另存为“fs.glsl”:

#version 330

in vec4 color;
out vec4 outputColor;

void
main()
{
    outputColor = color;
}

它只需要从之前的颜色变量(注意它现在是“out”的“out”),并将输出设置为该颜色。

现在我们已经制作了这些着色器,我们需要告诉显卡使用它们。首先,我们需要告诉OpenGL创建一个新的程序对象。这将以可用的形式存储我们的着色器。

首先,在任何函数的范围之外定义程序ID(其地址)的变量。我们不会将程序对象本身存储在代码的末尾。我们只有一个地址可以参考,因为它留在显卡上。

int pgmID;

在Game类中创建一个名为initProgram的新函数。在这个函数中,我们将首先调用GL.CreateProgram()函数,该函数返回一个新程序对象的ID,我们将它存储在pgmID中。

void initProgram()
{
            pgmID = GL.CreateProgram();
}

现在我们将绕道而行,创建一个读取着色器并添加它们的函数。

此函数需要获取文件名和一些信息,并返回已创建的着色器的地址。

它应该看起来像这样:

void loadShader(String filename, ShaderType type, int program, out int address)
{
    address = GL.CreateShader(type);
    using (StreamReader sr = new StreamReader(filename))
    {
        GL.ShaderSource(address, sr.ReadToEnd());
    }
    GL.CompileShader(address);
    GL.AttachShader(program, address);
    Console.WriteLine(GL.GetShaderInfoLog(address));
}

这将创建一个新着色器(使用ShaderType枚举中的值),为其加载代码,编译它,并将其添加到我们的程序中。它还会将它发现的任何错误打印到控制台,这对于在着色器中出错时非常好(如果使用弃用的代码,它也会对你大喊大叫)。

现在我们有了这个,让我们添加我们的着色器。首先,我们在类里还需要两个变量:

int vsID;
int fsID;

它们将存储我们的两个着色器的地址。现在,我们将要调用刚创建的函数从文件加载着色器。

将此代码添加到initProgram:

loadShader("vs.glsl", ShaderType.VertexShader, pgmID, out vsID);
loadShader("fs.glsl", ShaderType.FragmentShader, pgmID, out fsID);

这将链接它,并告诉我们是否有任何错误。

着色器现已添加到我们的程序中,但我们需要在程序正确使用之前为我们的程序提供更多信息。我们的顶点着色器上有多个输入,因此我们需要获取它们的地址以给出顶点的着色器位置和颜色信息。

将此代码添加到Game类:

int attribute_vcol;
int attribute_vpos;
int uniform_mview;

我们在这里定义了三个变量,用于存储每个变量的地址,以供将来参考。稍后我们会再次需要这些值,所以我们应该把它们放在手边。(作为旁注,我使用名称“attribute”,因为在旧版本的GLSL中,不是“in”变量,而是“attribute”。)为了获取每个变量的地址,我们使用GL.GetAttribLocation和GL.GetUniformLocation函数。每个都在着色器中获取程序的ID和变量的名称。

在initProgram结束时,添加:

attribute_vpos = GL.GetAttribLocation(pgmID, "vPosition");
attribute_vcol = GL.GetAttribLocation(pgmID, "vColor");
uniform_mview = GL.GetUniformLocation(pgmID, "modelview");

if (attribute_vpos == -1 || attribute_vcol == -1 || uniform_mview == -1)
{
    Console.WriteLine("Error binding attributes");
}

此代码将获取我们需要的值,并进行简单检查以确保找到属性。

现在我们的着色器和程序已经建立,但我们需要给它们一些东西来绘制。为此,我们将使用顶点缓冲对象(VBO)。当您使用VBO时,首先需要让显卡创建一个,然后绑定到它并发送参数信息。然后,当调用DrawArrays函数时,缓冲区中的信息将被发送到着色器并绘制到屏幕上。

与着色器变量一样,我们需要存储地址以供将来使用:

int vbo_position;
int vbo_color;
int vbo_mview;

创建缓冲区非常简单。在initProgram中,添加:

GL.GenBuffers(1, out vbo_position);
GL.GenBuffers(1, out vbo_color);
GL.GenBuffers(1, out vbo_mview);

这将生成3个单独的缓冲区并将其地址存储在变量中。对于像这样的多个缓冲区,可以选择生成多个缓冲区并将它们存储在一个数组中,但为了简单起见,我们将它们保存在单独的int中。

这些缓冲区接下来需要一些数据。位置和颜色都是Vector3变量,模型视图将是Matrix4。我们需要将它们存储在一个数组中,以便更有效地将数据发送到缓冲区。

在Game类中再添加三个变量:

Vector3[] vertdata;
Vector3[] coldata;
Matrix4[] mviewdata;

对于此示例,我们将在onLoad中设置这些值,同时调用initProgram():

protected override void OnLoad(EventArgs e)
{
    base.OnLoad(e);
    initProgram();
    vertdata = new Vector3[] { new Vector3(-0.8f, -0.8f, 0f),
            new Vector3( 0.8f, -0.8f, 0f),
            new Vector3( 0f,  0.8f, 0f)};

    coldata = new Vector3[] { new Vector3(1f, 0f, 0f),
            new Vector3( 0f, 0f, 1f),
            new Vector3( 0f,  1f, 0f)};

    mviewdata = new Matrix4[]{
            Matrix4.Identity
        };

    Title = "Hello OpenTK!";
    GL.ClearColor(Color.CornflowerBlue);
    GL.PointSize(5f);
}

存储数据后,我们现在可以将其发送到缓冲区。我们需要为OnUpdateFrame函数添加另一个重写,这次。我们需要做的第一件事是绑定到缓冲区:

GL.BindBuffer(BufferTarget.ArrayBuffer, vbo_position);

这告诉OpenGL如果我们向它发送任何数据,我们将使用该缓冲区。接下来,我们将实际发送数据:

GL.BufferData<Vector3>(BufferTarget.ArrayBuffer, (IntPtr)(vertdata.Length * Vector3.SizeInBytes), vertdata, BufferUsageHint.StaticDraw);

此代码告诉它我们将vertdata的内容(长度为(vertdata.Length * Vector3.SizeInBytes))发送到缓冲区。最后,我们需要告诉它使用这个缓冲区(最后一个绑定)用于vPosition变量,这将需要3个浮点数:

GL.VertexAttribPointer(attribute_vpos, 3, VertexAttribPointerType.Float, false, 0, 0);

所以,两个变量的共同点是:

GL.BindBuffer(BufferTarget.ArrayBuffer, vbo_position);
GL.BufferData<Vector3>(BufferTarget.ArrayBuffer, (IntPtr)(vertdata.Length * Vector3.SizeInBytes), vertdata, BufferUsageHint.StaticDraw);
GL.VertexAttribPointer(attribute_vpos, 3, VertexAttribPointerType.Float, false, 0, 0);

GL.BindBuffer(BufferTarget.ArrayBuffer, vbo_color);
GL.BufferData<Vector3>(BufferTarget.ArrayBuffer, (IntPtr)(coldata.Length * Vector3.SizeInBytes), coldata, BufferUsageHint.StaticDraw);
GL.VertexAttribPointer(attribute_vcol, 3, VertexAttribPointerType.Float, true, 0, 0);

我们还需要发送模型视图矩阵,它使用不同的函数:

GL.UniformMatrix4(uniform_mview, false, ref mviewdata[0]);

最后,我们要清除缓冲区绑定并将其设置为使用我们着色器的程序:

GL.UseProgram(pgmID);
GL.BindBuffer(BufferTarget.ArrayBuffer, 0);

我们差不多完成了!现在我们将数据,着色器和数据发送到图形卡,但我们仍然需要绘制它。在我们的OnRenderFrame函数中,首先我们需要告诉它使用我们想要的变量:

GL.EnableVertexAttribArray(attribute_vpos);
GL.EnableVertexAttribArray(attribute_vcol);

然后我们告诉它如何绘制它们:

GL.DrawArrays(PrimitiveType.Triangles, 0, 3);

然后我们做一些事情来保持清洁:

GL.DisableVertexAttribArray(attribute_vpos);
GL.DisableVertexAttribArray(attribute_vcol);

GL.Flush();

所以,总的来说,这个函数是:

protected override void OnRenderFrame(FrameEventArgs e)
{
    base.OnRenderFrame(e);
    GL.Viewport(0, 0, Width, Height);
    GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
    GL.Enable(EnableCap.DepthTest);


    GL.EnableVertexAttribArray(attribute_vpos);
    GL.EnableVertexAttribArray(attribute_vcol);


    GL.DrawArrays(BeginMode.Triangles, 0, 3);


    GL.DisableVertexAttribArray(attribute_vpos);
    GL.DisableVertexAttribArray(attribute_vcol);


    GL.Flush();
    SwapBuffers();
}

现在,如果你运行它,你会发现在完成所有这些工作后,我们(或多或少)回到了以前的状态。

原文链接

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当然,我可以为您提供一些关于C#和OpenTK教程OpenTK是一个用于在C#中编写跨平台图形应用程序的库。它基于OpenGL,并提供了许多有用的功能和工具。 以下是一些学习C# OpenTK的步骤: 1. 安装OpenTK:首先,您需要在您的计算机上安装OpenTK库。您可以通过NuGet包管理器或从OpenTK官方网站下载并手动安装。 2. 创建新项目:使用Visual Studio或您喜欢的C#集成开发环境(IDE)创建一个新项目。确保选择C#语言。 3. 添加OpenTK引用:在您的项目中,右键单击“引用”文件夹,然后选择“添加引用”。浏览并选择您之前安装的OpenTK库。 4. 创建窗口:在您的代码中,创建一个新的OpenTK窗口对象。您可以设置窗口的标题、大小和其他属性。然后,为窗口注册事件处理程序,例如当窗口关闭时进行清理。 5. 初始化OpenGL:在窗口的Load事件处理程序中,初始化OpenGL上下文,并设置一些OpenGL的选项和状态。这将为您提供一个可以进行图形绘制的环境。 6. 渲染循环:在窗口的RenderFrame事件处理程序中,编写您的渲染代码。这是一个循环,在每一帧中执行一次。您可以在此处绘制图形、加载纹理、更新模型等。 7. 用户输入:处理用户的输入,例如鼠标和键盘事件。通过OpenTK提供的事件处理程序,您可以轻松捕获用户的操作。 8. 销毁:在窗口的Closed事件处理程序中,进行一些清理操作,例如释放资源、关闭文件等。 这只是一个简单的教程概述,帮助您入门C# OpenTK开发。当然,要深入研究和掌握OpenTK,还需要学习更多的OpenGL知识和技术。 您可以参考OpenTK官方文档、教程和示例代码来进一步了解和学习C# OpenTK的用法和技巧。祝您学习愉快!如果您有其他问题,请随时提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值