翻译自https://learnopengl.com/Getting-started/OpenGL
目录
核心配置与直接模式(Core-profile vs Immediate mode)
OpenGL
在开始我们的旅程之前,我们应该首先定义OpenGL实际上是什么。OpenGL主要被认为是一种API(应用程序编程接口),它为我们提供了一组可以用来操作图形和图像的函数。然而,OpenGL本身并不是一个API,而仅仅是一个由Khronos小组开发和维护的规范。
OpenGL规范确切地指定了每个函数的结果/输出应该是什么,以及它应该如何执行。然后,由实现该规范的开发人员提出该函数应该如何操作的解决方案。由于OpenGL规范没有提供实现细节,所以允许实际开发的OpenGL版本有不同的实现,只要它们的结果符合规范(因此对用户来说是一样的)。
开发实际OpenGL库的人通常是显卡制造商。您购买的每个显卡都支持特定版本的OpenGL,这些版本是专门为该卡(系列)开发的OpenGL版本。当使用苹果系统时,OpenGL库是由苹果自己维护的,在Linux下存在着图形供应商版本和爱好者对这些库的适应的组合。这也意味着,每当OpenGL出现它不应该出现的奇怪行为时,这很可能是显卡制造商(或开发/维护库的人)的问题。
因为大多数实现是由显卡制造商构建的。每当在实现中有一个bug,这通常是解决更新您的视频卡驱动程序;这些驱动程序包括你的卡支持的最新版本的OpenGL。这就是为什么它总是建议偶尔更新你的图形驱动程序的原因之一。
Khronos公开托管所有OpenGL版本的所有规范文档。感兴趣的读者可以在这里找到版本3.3的OpenGL规范(我们将使用它),如果你想深入了解OpenGL的细节(注意它们主要是如何描述结果而不是实现的),这是一本很好的读物。规范还为查找其功能的确切工作原理提供了很好的参考。
核心配置与直接模式(Core-profile vs Immediate mode)
在过去,使用OpenGL意味着以直接模式(通常称为固定函数管道)进行开发,这是一种易于使用的绘图方法。OpenGL的大部分功能隐藏在库中,开发人员对OpenGL如何计算没有太多的控制权。开发人员最终渴望更大的灵活性,随着时间的推移,规范也变得更加灵活;开发人员对他们的图形有了更多的控制。即时模式确实很容易使用和理解,但它的效率也非常低。出于这个原因,该规范从3.2版本开始就开始弃用即时模式功能,并开始鼓励开发人员使用OpenGL的核心-概要模式进行开发,该模式是OpenGL规范的一部分,它删除了所有过时的功能。
当使用OpenGL的Core-profile模式时,OpenGL强迫我们使用现代实践。当我们尝试使用一个OpenGL的不推荐的函数时,OpenGL会出现错误并停止绘制。学习现代方法的优点是它非常灵活和有效。然而,它也更难学。直接模式从OpenGL执行的实际操作中抽象出了相当多的内容,虽然很容易学习,但很难掌握OpenGL实际是如何操作的。现代方法要求开发人员真正理解OpenGL和图形编程,虽然有点困难,但它允许更灵活、更高效,最重要的是:更好地理解图形编程。
这也是为什么这本书是针对Core-profile OpenGL 3.3版本的原因。虽然比较困难,但很值得一试。
今天,可以从更高版本的OpenGL中选择(在写4.6的时候),你可能会问:既然OpenGL 4.6已经发布了,为什么我还想学习OpenGL 3.3 ?这个问题的答案相对简单。从3.3开始的所有未来版本都在不改变OpenGL核心机制的前提下为OpenGL添加了额外有用的特性;更新的版本只是引入了一些更有效或更有用的方法来完成相同的任务。结果是,所有的概念和技术在现代OpenGL版本中都是一样的,所以学习OpenGL 3.3是完全有效的。只要你准备好了或者更有经验,你就可以轻松地使用最近的OpenGL版本中的特定功能。
当使用最新版本的OpenGL的功能时,只有最现代的显卡才能运行你的应用程序。这就是为什么大多数开发人员通常以较低版本的OpenGL为目标,而选择性地启用更高版本的功能。
在一些章节中,你会发现更多的现代特征被这样记录下来。
扩展(Extensions)
OpenGL的一个很好的特性是它对扩展的支持。每当图形公司提出一项新的技术或新的大型渲染优化时,通常会在驱动程序中实现的扩展中发现。如果应用程序运行的硬件支持这样的扩展,开发人员可以使用扩展提供的功能来实现更高级或更高效的图形。这样一来,图形开发人员就可以继续使用这些新的渲染技术,而不必等待OpenGL在未来的版本中包含这些功能,只需检查图形卡是否支持扩展即可。通常,当一个扩展很流行或者非常有用时,它最终会成为未来OpenGL版本的一部分。
在使用这些扩展(或者使用OpenGL扩展库)之前,开发者必须查询这些扩展是否可用。这允许开发人员做事情更好或更有效,基于是否扩展可用:
if(GL_ARB_extension_name)
{
// Do cool new and modern stuff supported by hardware
}
else
{
// Extension not supported: do it the old way
}
在OpenGL 3.3版本中,我们很少需要对大多数技术进行扩展,但只要有必要,就会提供适当的说明。
状态机(State machine)
OpenGL本身是一个大型状态机:定义OpenGL当前应该如何操作的变量集合。OpenGL的状态通常被称为OpenGL上下文。在使用OpenGL时,我们经常通过设置一些选项、操作一些缓冲区并使用当前上下文呈现来改变它的状态。
例如,每当我们告诉OpenGL我们现在想要绘制线条而不是三角形时,我们通过改变上下文变量来改变OpenGL的状态来设置OpenGL应该如何绘制。一旦我们通过改变上下文告诉OpenGL它应该画线,下一个绘制命令将画线而不是三角形。
在OpenGL中工作时,我们会遇到几个改变上下文的state-changing函数,以及几个基于OpenGL当前状态执行一些操作的state-using函数。只要你记住OpenGL基本上是一个大型状态机,它的大部分功能就会更有意义。
对象(Objects)
OpenGL库是用C编写的,允许在其他语言中派生,但在其核心中仍然是C库。由于许多C的语言结构不能很好地转换为其他高级语言,因此OpenGL在开发时考虑了几种抽象。其中一个抽象是OpenGL中的对象。
OpenGL中的对象是表示OpenGL状态子集的选项集合。例如,我们可以有一个对象来表示绘图窗口的设置;然后我们可以设置它的大小,它支持多少种颜色等等。我们可以把一个对象想象成类c的结构:
struct object_name {
float option1;
int option2;
char[] name;
};
每当我们想使用对象,它通常看起来像这样(OpenGL的上下文可视化为一个大的结构):
// The State of OpenGL
struct OpenGL_Context {
...
object_name* object_Window_Target;
...
};
// create object
unsigned int objectId = 0;
glGenObject(1, &objectId);
// bind/assign object to context
glBindObject(GL_WINDOW_TARGET, objectId);
// set options of object currently bound to GL_WINDOW_TARGET
glSetObjectOption(GL_WINDOW_TARGET, GL_OPTION_WINDOW_WIDTH, 800);
glSetObjectOption(GL_WINDOW_TARGET, GL_OPTION_WINDOW_HEIGHT, 600);
// set context target back to default
glBindObject(GL_WINDOW_TARGET, 0);
这一小段代码是在使用OpenGL时经常看到的工作流。我们首先创建一个对象,并将对它的引用存储为id(实际对象的数据存储在后台)。然后,我们将对象(使用其id)绑定到上下文的目标位置(示例窗口对象目标的位置定义为GL_WINDOW_TARGET)。接下来我们设置窗口选项,最后通过将窗口目标的当前对象id设置为0来解除对象绑定。我们设置的选项存储在objectId引用的对象中,一旦将对象绑定回GL_WINDOW_TARGET,就会恢复。
到目前为止提供的代码示例只是OpenGL操作的近似值;在整本书中,你会遇到很多实际的例子。
使用这些对象的好处是,我们可以在应用程序中定义多个对象,设置它们的选项,每当我们启动使用OpenGL状态的操作时,我们将对象与首选设置绑定。例如,有一些对象充当了3D模型数据(房子或角色)的容器对象,每当我们想绘制其中一个对象时,我们就绑定包含我们想要绘制的模型数据的对象(我们首先为这些对象创建并设置选项)。拥有多个对象允许我们指定许多模型,并且无论何时我们想绘制一个特定的模型,我们只需在绘制之前绑定相应的对象,而无需再次设置它们的所有选项。
让我们开始
现在,您了解了一些关于作为规范和库的OpenGL的知识,OpenGL的基本原理以及OpenGL使用的一些自定义技巧。如果你没有完全理解,也不要担心;在全书中,我们将逐一讲解每一步,您将看到足够的示例来真正理解OpenGL。