上一个教程向我们展示了如何在屏幕上画一个三角形。但是,我说过,那是一种古老的方式,即使它能够正常运行,但是现在这已经不是“正确”的方式。上篇文章中我们将几何发送到GPU的方式是所谓的“即时模式”,它非常简单,但是已经不再推荐使用。

在本教程中,我们将要实现同样的最终目标,但是我们将以更复杂的方式来做事情,疯了么大哥?

我们选择更麻烦的编写方式,是为了更有效率,更快速和可扩展性。

我们将像以前的教程一样开始,我将引用原文几次,所以如果还没有看过上一篇的话,请抽空看看。

Part 1:设置

要开始,我们需要创建一个新的项目,引用OpenTK和System.Drawing,同上一个教程。将其命名为OpenTKTutorial2。

Part 2:编码

首先,我们需要再次做一些基础工作,就像第一个教程那样。添加一个名为“Game”的新类。使它成为GameWindow的子类(您需要为OpenTK添加一个using指令才能使用该类)。

差不多是这样:

using OpenTK;

namespace OpentkTutorials2
{
class Game : GameWindow
{
}
}

回到Program.cs,添加代码:

namespace OpentkTutorials2
{
class Program
{
static void Main(string[] args)
{
using (var game = new Game())
{
game.Run(30.0);
}
}
}
}

Onload方法和OnRenderFrame方法参照上一个教程做就行了。

protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
//修改窗口标题
Title = "Hello OpenTK!";
//设置背景颜色为,额,不知道什么蓝(需要添加 OpenTK.Graphics.OpenGL and System.Drawing引用)
GL.ClearColor(Color.CornflowerBlue);
}
protected override void OnRenderFrame(FrameEventArgs e)
{
base.OnRenderFrame(e); GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); SwapBuffers();
}

好了,从这里开始,我们可以学点新的东西了!

我们首先需要做的是创建我们的着色器(Shader)。现代OpenGL使用它获知如何绘制给出的值。我们将使用两种着色器:顶点着色器(Vertex Shader)和片段着色器(Fragment Shader)。 顶点着色器告诉显卡正在绘制的形状中的点的信息。片段着色器决定绘制到屏幕时形状的每个像素的颜色。我们将要使用的代码非常简单,但是我们可以使用类似于即时模式的风格操作。OpenGL的着色器以类C语言的脚本语言编写,称为GLSL(DirectX使用稍微不同的语言,称为HLSL)。

译者注:有另外一篇非常好的文章讲GLSL,推荐先阅读以更深入了解GLSL:LearnOpenGL CN,该系列教程也非常推荐阅读。

将一个文本文件添加到您的项目中,名为“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;
}

它只是获得上一个着色器输出的颜色变量(注意它现在是“输入”的“in”),并将输出设置为该颜色。

现在我们有了这些着色器,接下来我们需要指示显卡去使用它们。首先,我们需要告诉OpenTK创建一个新的程序对象(program)。 它将以可用的形式存储的这些着色器。

首先,定义程序的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);

现在,添加了着色器,程序需要链接。像C代码一样,代码首先被编译,然后被链接,完成从人类可读的代码到需要的机器语言的转变。

然后再添加:

GL.LinkProgram(pgmID);
Console.WriteLine(GL.GetProgramInfoLog(pgmID));

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

着色器现在被添加到我们的程序中,但是我们需要告诉程序更多的信息才能正常工作。我们在我们的顶点着色器上有多个输入,所以我们需要告诉它们地址来给出顶点的着色器位置和颜色信息。

将此代码添加到Game类中:

	  int attribute_vcol;
int attribute_vpos;
int uniform_mview;

我们在这里定义三个变量,存储每个变量的位置,以供将来引用。往后我们将需要使用这些值,所以我们应该保持简单。要获取每个变量的地址,我们使用GL.GetAttribLocationGL.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");
}

上面代码将存储我们需要的值,并且还要做一个简单的检查,以确保找到属性。

译者注:也可以不在C#代码中指定,而在shader代码中使用layout (location = x)的方式指定。具体用法可以参见上文中说的

现在我们的着色器和程序已经建立起来了,但是我们还需要给他们一些东西绘制。为此,我们将使用顶点缓冲区对象(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);

这就告诉OpenTK,如果我们发送任何数据,我们将使用该缓冲区。接下来,我们会发送数据:

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

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

           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);

我们还需要发送模型视图矩阵(Model-View Matrix):

             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();
}

如果你运行这些代码,效果是不是很熟悉?


本系列教程翻译自Neo Kabuto's Blog。已经取得作者授权。

本文原文地址http://neokabuto.blogspot.com/2013/03/opentk-tutorial-2-drawing-triangle.html

原文代码可以在github上找到。

OpenTK教程-2绘制一个三角形(正确的方式)的更多相关文章

  1. OpenTK教程-2绘制一个三角形(正确的方法)

    上一个教程向我们展示了如何在屏幕上画一个三角形.但是,我说过,那是一种古老的方式,即使它能够正常运行,但是现在这已经不是"正确"的方式.上篇文章中我们将几何发送到GPU的方式是所谓 ...

  2. OpenTK教程-1绘制一个三角形

    OpenTK的官方文档是真心的少,他们把怎么去安装OpenTK说的很清楚,但是也就仅限于此,这有一篇learn opentk in 15的教程(链接已经失效,译者注),但是并不完美.你可以在15分钟内 ...

  3. WebGL简易教程(三):绘制一个三角形(缓冲区对象)

    目录 1. 概述 2. 示例:绘制三角形 1) HelloTriangle.html 2) HelloTriangle.js 3) 缓冲区对象 (1) 创建缓冲区对象(gl.createBuffer( ...

  4. 【OpenGL4.0】GLSL渲染语言入门与VBO、VAO使用:绘制一个三角形 【转】

    http://blog.csdn.net/xiajun07061225/article/details/7628146 以前都是用Cg的,现在改用GLSL,又要重新学,不过两种语言很多都是相通的. 下 ...

  5. Unity3D学习笔记1——绘制一个三角形

    目录 1. 绪论 2. 概述 3. 详论 3.1. 准备 3.2. 实现 3.3. 解析 3.3.1. 场景树对象 3.3.2. 绘制方法 4. 结果 1. 绪论 最近想学习一下Unity3d,无奈发 ...

  6. [Modern OpenGL系列(三)]用OpenGL绘制一个三角形

    本文已同步发表在CSDN:http://blog.csdn.net/wenxin2011/article/details/51347008 在上一篇文章中已经介绍了OpenGL窗口的创建.本文接着说如 ...

  7. Opentk教程系列-1绘制一个三角形

    本系列教程翻译自Neo Kabuto's Blog.已经取得作者授权. 本文原文地址http://neokabuto.blogspot.com/2013/02/opentk-tutorial-1-op ...

  8. Android快乐贪吃蛇游戏实战项目开发教程-03虚拟方向键(二)绘制一个三角形

    该系列教程概述与目录:http://www.cnblogs.com/chengyujia/p/5787111.html 一.绘制三角形 在上一篇文章中,我们已经新建了虚拟方向键的自定义控件Direct ...

  9. Stage3D学习笔记(二):使用GPU绘制一个三角形

    我们需要使用到Adobe自家提供的AGALMiniAssembler代码类,可以在网下进行下载: 关于AGAL的入门知识可以参考下面的文章: AGAL介绍系列文章(第一部分)AGAL介绍系列文章(第二 ...

随机推荐

  1. 模拟开户接口,使用python脚本实现批量用户开通

    1.目的 通过模拟接口方法,实现批量用户开通 2.分析 A.接口含body和head部分,其中body中的某些变量为必填字段,包含用户的信息. B.用户信息清单可以整理成ott_after_check ...

  2. Android ListView在增加HeaderView之后使用getLocationInWindow和getLocationOnScreen获得值不正确的解决方法

    近日遇到一个很恶心的问题,把解决方法放到空间里来分享给大家: 问题发生的条件: 1)ListView 控件中使用addHeaderView,为其添加了一个header view.(基本常识:heade ...

  3. SVN与Git比较的优缺点差异

    目录: SVN与Git比较(一)集中式vs分布式 SVN与Git比较(二)版本库与工作区 SVN与Git比较(三)全局版本号和全球版本号 SVN与Git比较(四)部分检出 SVN与Git比较(五)更新 ...

  4. VM虚拟机打不开,没有反应,解决方法。

    最近的项目开发,需要用到虚拟机,但是打开虚拟机VM8却发现,以前创建的虚拟机都用不了,点击左侧[我的计算机]中的虚拟机列表,没有任何反应,也没有任何错误提示,服务中所有的虚拟机服务都开启了,网上百度没 ...

  5. Source Insight4 破解安装

    首先确保你在官网下载了原版4.0并安装好了. 1,下载如下的sourceinsight4.exe文件,替换安装文件夹下的sourceinsight4.exe. 链接:http://pan.baidu. ...

  6. JS中=>,>>>是什么意思

    最近经常看到 JS中=>,符号,于是查了一下别人的博客 =>是es6语法中的arrow function 举例:(x) => x + 6 相当于   function(x){ ret ...

  7. 捕获海康威视IPCamera图像,转成OpenCV能够处理的图像(二)

    海康威视IPCamera图像捕获 捕获海康威视IPCamera图像.转成OpenCV能够处理的IplImage图像(一) 捕获海康威视IPCamera图像.转成OpenCV能够处理的IplImage图 ...

  8. Usaco 2019 Jan Platinum

    Usaco 2019 Jan Platinum 要不是昨天老师给我们考了这套题,我都不知道usaco还有铂金这么一级. 插播一则新闻:杨神坚持认为铂金比黄金简单,原因竟是:铜 汞 银 铂 金(金属活动 ...

  9. IDEA多线程调试设置

    转至:http://blog.csdn.net/kevindai007/article/details/71412324 使用idea调试多线程的时候发现多线程无法调试,后来经过搜索发现,idea的断 ...

  10. ssh linux免密登录。。。。生产共钥到另一台主机

    一.第一种方式: 1.ssh-keygen -t rsa -t : 加密方式 默认为rsa 可以省略不写 加密方式选 rsa|dsa 2.将 .pub 文件复制到目标机器的 .ssh 目录, 并 ca ...