OpenGL
原文章出自OpenGL - OpenTK,这里仅作翻译记录。
注意:这里不会编写任何代码。如果你已经知道OpenGL是什么以及它是如何工作的,你可以直接跳到下一个教程并立即开始编程。
在开始我们的教程之前,我们应该首先定义OpenGL实际上是什么。OpenGL主要被认为是一个API(应用程序编程接口),它为我们提供了一整套函数,我们可以使用这些函数来操作图形和图像。然而,OpenGL本身并不是一个API,而只是一个规范,由Khronos团队开发和维护。
OpenGL 规范精确地规定了每个函数的结果/输出应该是什么以及它们应该如何执行。然后由实现该规范的开发人员提出如何操作此函数的解决方案。由于 OpenGL 规范没有提供实现细节,实际开发的 OpenGL 版本可以有不同的实现,只要它们的结果符合规范(因此对用户来说是一样的)。
开发实际OpenGL库的人通常是显卡制造商。你购买的每张显卡都支持特定版本的OpenGL,这些版本是专门为该显卡(系列)开发的OpenGL版本。在使用Apple系统时,OpenGL库由Apple自己维护,在Linux下存在图形供应商版本和爱好者对这些库的改编组合。这也意味着,当OpenGL出现不应有的奇怪行为时,这很可能是显卡制造商(或开发/维护库的人)的过错。由于大多数实现是由显卡制造商构建的。每当实现中出现错误时,通常通过更新显卡驱动程序来解决;这些驱动程序包括你的显卡支持的最新版本的OpenGL。这就是为什么建议偶尔更新图形驱动程序的原因之一。
Khronos 公开了所有 OpenGL 版本的所有规范文档。感兴趣的读者可以在这里找到我们将要使用的 OpenGL 3.3 版本的规范,如果你想要深入了解 OpenGL 的细节,这是一个很好的读物(请注意,它们主要描述的是结果而不是实现)。规范还提供了一个很好的参考,用于查找其函数的确切工作原理。
核心配置 VS 即时模式
在过去,使用OpenGL意味着以即时模式(通常称为固定功能管道)开发,这是一种易于使用的绘图方法。OpenGL的大多数功能都隐藏在库中,开发人员对OpenGL如何进行计算几乎没有太多自由。随着时间的推移,开发人员开始渴望更多的灵活性,规范也变得越来越灵活;开发人员获得了更多对其图形的控制权。即时模式非常容易使用和理解,但它也非常低效。因此,从3.2版本开始,规范开始弃用即时模式功能,并开始鼓励开发人员在OpenGL的核心配置文件模式下开发,这是OpenGL规范的一个分支,移除了所有旧的已弃用的功能。
当使用OpenGL的核心配置文件时,OpenGL迫使我们使用现代方法(modern practices)。每当尝试使用OpenGL的已弃用函数时,OpenGL会引发错误并停止绘制。学习现代方法的优点是它非常灵活和高效,但不幸的是也更难学习。即时模式抽象了OpenGL执行的实际操作的很多内容,虽然容易学习,但很难理解OpenGL实际如何工作。现代方法要求开发人员真正理解OpenGL和图形编程,虽然有点困难,但它允许更大的灵活性、更高的效率,最重要的是对图形编程有更深入的理解。
这也是我们教程针对核心配置OpenGL 3.3版本的原因。尽管它更难,但付出的努力是值得的。
截至今天,已经发布了更高版本的OpenGL(撰写本文时为4.5),你可能会问:既然有OpenGL 4.5,为什么还要学习OpenGL 3.3?这个问题的答案相对简单。从3.3开始,所有未来的OpenGL版本基本上都是在OpenGL的基础上添加了额外有用的功能,而没有改变OpenGL的核心机制;新版本只是引入了稍微更高效或更有用的方式来完成相同的任务。结果是,所有概念和技术在现代OpenGL版本中保持不变,因此学习OpenGL 3.3是完全有效的。当你准备好了和/或更有经验时,可以轻松地使用更新的OpenGL版本中的特定功能。
当使用最新版本的OpenGL功能时,只有最现代的显卡才能运行您的应用程序。这通常就是为什么大多数开发者通常会针对较低版本的OpenGL,并可选地启用更高版本的功能。
在一些教程中,有时会发现更现代的功能,这些功能会被特别注明。
扩展
OpenGL的一个重要特性是它对扩展的支持。每当一家图形公司开发出一种新的技术或渲染优化时,通常会在驱动程序中实现一个扩展。如果应用程序运行的硬件支持此类扩展,开发者可以使用扩展提供的功能来实现更高级或更高效的图形。这样一来,图形开发者仍然可以使用这些新的渲染技术,而无需等待OpenGL在其未来的版本中包含该功能,只需检查显卡是否支持该扩展即可。通常,当一个扩展流行或非常有用时,最终会成为未来OpenGL版本的一部分。
开发者然后需要查询这些扩展是否可用(或使用OpenGL扩展库)。这允许开发者根据扩展是否可用,以更好的方式或更高效地完成工作:
if (GL_ARB_extension_name)
{
// 做由硬件支持的新颖、酷炫和现代的事情
}
else
{
// 扩展不支持:请按旧方式执行
}
使用OpenGL 3.3版本时,我们很少需要为大多数技术使用扩展,但在必要时会提供适当的指导。
状态机
OpenGL 本身是一个大型状态机:一组定义 OpenGL 当前操作方式的变量。OpenGL 的状态通常被称为 OpenGL 上下文。在使用 OpenGL 时,我们经常通过设置一些选项、操作一些缓冲区来改变其状态,然后使用当前上下文进行渲染。
每当我们告诉OpenGL我们现在想画线而不是三角形时,我们会通过改变一些设置OpenGL如何绘制的上下文变量来改变OpenGL的状态。一旦我们通过告诉OpenGL应该画线来改变了状态,接下来的绘制命令将会画线而不是三角形。
在使用OpenGL时,我们会遇到许多改变状态的函数,这些函数会改变上下文,还有一些基于当前OpenGL状态执行某些操作的状态使用函数。只要记住OpenGL基本上是一个大型的状态机,它的大多数功能就会更容易理解。
对象
OpenGL库是用C语言编写的,并允许在其他语言中进行许多派生,但在其核心它仍然是一个C库。由于C语言的许多语言结构并不能很好地转换为其他高级语言,因此OpenGL是在几个抽象的基础上开发的。其中一个抽象是OpenGL中的对象。
OpenGL对象不像你在面向对象语言中找到的对象。你不能直接修改它们,因为它们存储在显卡上。相反,你有一个整数,表示对象在显卡上的位置,这个整数被称为句柄。你需要将这个句柄传递给任何需要对象的函数。它的工作原理是这样的:
//创建一个变量来存储句柄
int ObjectHandle;
//创建对象
GL.GenObjects(1, &ObjectHandle);
//然后将其传递给一个使用它的函数
GL.SomeFunctionThatUsesThisObject(ObjectHandle);
这段小代码是你在使用OpenGL时经常看到的工作流程。我们首先创建一个对象,并将其引用存储为id(实际的对象数据是在后台存储的)。然后我们将对象绑定到上下文的目标位置(示例窗口对象目标的位置定义为GL_WINDOW_TARGET)。接下来我们设置窗口选项,最后通过将窗口目标的当前对象id设置为0来解除对象的绑定。我们设置的选项存储在由objectId引用的对象中,并且一旦我们将对象重新绑定回GL_WINDOW_TARGET,这些选项就会被恢复。
使用这些对象的好处在于,我们可以在应用程序中定义多个对象,设置它们的选项,并且每当启动使用OpenGL状态的操作时,我们可以绑定具有我们首选设置的对象。例如,有些对象充当3D模型数据(如房子或角色)的容器对象,每当我们想要绘制其中一个时,我们会绑定包含要绘制的模型数据的对象(我们首先创建并设置了这些对象的选项)。拥有多个对象使我们能够指定许多模型,每当我们想要绘制特定模型时,只需在绘制前绑定相应的对象,而无需再次设置所有选项。让我们开始吧。
你现在对OpenGL作为规范和库有了一些了解,了解了OpenGL大致是如何在幕后工作的,以及OpenGL使用的一些自定义技巧。如果你没有完全理解也不要担心;在整个教程中,我们将逐步讲解每个步骤,并且你会看到足够的例子来真正掌握OpenGL。如果你准备好进入下一步,继续下一个教程,我们将创建第一个窗口。