上一个教程向我们展示了如何在屏幕上画一个三角形。但是,我说过,那是一种古老的方式,即使它能够正常运行,但是现在这已经不是“正确”的方式。上篇文章中我们将几何发送到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. 【OpenGL4.0】GLSL渲染语言入门与VBO、VAO使用:绘制一个三角形 【转】

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

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

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

  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. centos7安装图形化界面

    yum groups install -y "GNOME Desktop" "Graphical Administration Tools"

  2. 常用PHP函数

    md5_file() 生成md5 $zip = new \ZipArchive(); if($zip->open($savepath.$key) === TRUE){ $zip ->ext ...

  3. 西电2017ACM网络赛

    #include<bits/stdc++.h> using namespace std; typedef long long LL; #define ms(a,x) memset(a,x, ...

  4. MD5加密Demo

    package com.util; import java.security.MessageDigest; public class MD5 { public final static String ...

  5. ASP.NET MVC5 怒跨 Linux 平台

    安装CentOS 安装Mono #安装yum工具包 yum -y install yum-utils #通过rpm添加Mono源 rpm --import "http://keyserver ...

  6. js一些重点知识总结(一)

    1.javaScript与java的区别?(从它们的解释,运行等方面说)   第一,javascript是基于对象的,而java是面向对象,即java是一种真正的面向对象的语言,即使是开发简单的程序, ...

  7. 如何把我的Java程序变成exe文件?

    JAVA是一种“跨平台”的语言,拥有“一次编写,处处运行”的特点,让它成为当今IT行业,必不可少的一门编程语言.每一个软件开发完成之后,应该大家都需要打包程序并发送给客户,常见的方式:java程序打成 ...

  8. 【万能的搜索,用广搜来解决DP问题】ZZNU -2046 : 生化危机 / HDU 1260:Tickets

    2046 : 生化危机 时间限制:1 Sec内存限制:128 MiB提交:19答案正确:8 题目描述 当致命的T病毒从Umbrella Corporation 逃出的时候,地球上大部分的人都死去了. ...

  9. PyCharm 去掉自动保存功能

    PyCharm 4.5.4 环境配置 1.去掉"自动保存功能" pycharm默认是自动保存的,习惯自己按 ctrl + s 的可以进行如下设置: 菜单File -> Set ...

  10. vsftp虚拟主机

    ################################Vsftp服务器实战##########################################3 文件传输协议,基于该协议FT ...