在Windows下,找到OpenGL函数很简单,做一个extern的函数声明即可。例如C#语言的glClear(..)的声明如下:\[DllImport("opengl32.dll", SetLastError = true)\] // C# Attribute
static extern void glClear(uint mask);但如果是扩展的OpenGL函数(例如uint glCreateShader(uint shaderType);),则要使用动态加载的方式,从系统文件opengl32.dll中找到函数指针。 接着,创建Render Context。
没有这个Context,任何对OpenGL函数的调用都是无效的。创建Context的步骤很繁琐,其中任何一点出错,就可能产生各种奇怪的问题,而且难以查找。 然后准备模型。
为了便于学习,可以只用一个三角形当模型,但在学习光照等环节时,没有恰当的模型很难体会到算法的精髓。 最后,要先领会很多孤立的函数和矩阵变换。
把模型数据正确地传送到显卡,并渲染出来,中间涉及几十个函数、各种神似的参数和复杂的绑定关系。其中任何一点出错,都很难调试,让人心态崩溃。矩阵也是OpenGL里很难理解的一个工具。为了实现各种方位变换、光照效果,矩阵变换也是绕不开的问题。
综上所述,晦涩繁琐、易错难改是初学OpenGL的最大困难。为了解决这些问题,本书以CSharpGL为基础,讲解如何开始使用OpenGL。其好处之一就是,上述准备工作都可以省了。
1.3.2开源库
开源库(CSharpGL)是笔者设计编写维护的,它包含下述部分:
① CSharpGL:这是一个OpenGL库,封装了OpenGL的功能,编译出来的是一个库文件CSharpGL.dll;
② CSharpGL.Windows:封装了在Windows下使用OpenGL的准备工作。编译出来的是一个库文件CSharpGL.Windows.dll;
③ Infrastructures\\:有一些辅助代码。既有必要出现,又不属于上述部分;
④ 其他:使用CSharpGL的示例集合。
有了CSharpGL,读者只需做如下准备工作:
下载安装Visual Studio 2013;
在本书网络资料中找到CSharpGL.zip,用Visual Studio 2013打开CSharpGL.sln解决方案;
完毕。读者可以直接运行各个示例项目,看看CSharpGL都能做些什么。
1.3.3渲染流程
OpenGL应用程序开发者只需告知OpenGL想做些什么(渲染三维场景、执行并行计算等),而不需知道OpenGL如何去完成这些任务。OpenGL会在一系列复杂的处理之后完成这些任务,这一系列复杂的处理过程就是OpenGL的渲染流程(Pipeline)。这类似《植物大战僵尸》这样的塔防游戏,玩家只需放置需要的植物(坚果墙、向日葵、豌豆炮、南瓜、磁力菇等),它们就会自动发挥自己的作用,玩家不必关心豌豆是如何神奇地修炼成精并发出炮弹的。
这里来初次认识一下简化的Pipeline。OpenGL处理模型数据主要分3步,如图11所示。
图11简化的OpenGL Pipeline1. 准备数据
三维世界里的物体,可以由组成物体的点以及点之间的连线(直线)来描述。例如图12就用点和连线描述了一个立方体和一个球体。
图12用点和连线描述的立方体和球体一个点最重要的数据就是其位置和颜色,该点就是OpenGL中的顶点(Vertex)。用这种方式描述的物体称为一个模型(Model)。
OpenGL处理的模型,是由一个个顶点组成的。对于每个顶点,都可能需要指定它的位置、颜色、法线和纹理坐标等属性(Attribute)。顶点的位置属性一般必须指定,而其他属性则未必。但是从程序的角度看,位置属性和其他属性是并列关系。
为了便于解释,这里以一个立方体Cube模型为例。立方体具有很特殊的性质,以后也将用它学习观察OpenGL的各种功能用法。CSharpGL中用图13所示的注释来记录立方体的顶点顺序和坐标轴。这样的方式既直观,又不像图片那样占用大量的空间。
图13用字符画描述Cube根据图13,首先指定立方体8个顶点的位置属性:private const float halfLength = 0.5f;
private static readonly vec3\[\] positions = new vec3\[\]
{
new vec3(+halfLength, +halfLength, +halfLength),// 0
new vec3(+halfLength, +halfLength, -halfLength),// 1
new vec3(+halfLength, -halfLength, +halfLength),// 2
new vec3(+halfLength, -halfLength, -halfLength),// 3
new vec3(-halfLength, +halfLength, +halfLength),// 4
new vec3(-halfLength, +halfLength, -halfLength),// 5
new vec3(-halfLength, -halfLength, +halfLength),// 6
new vec3(-halfLength, -halfLength, -halfLength),// 7
};这里只指定位置属性,其他属性暂不涉及。
有了位置,下面要指定这些顶点的连接关系。立方体有6个面,用12个三角形来描述这6个面,每个三角形需要指定3个顶点。用一个uint\[\]类型的数组indexes指定12个三角形,代码如下:private static readonly uint\[\] indexes = new uint\[\]
{
0, 2, 1,1, 2, 3, // +X faces.
0, 1, 5,0, 5, 4, // +Y faces.
0, 4, 2,2, 4, 6, // +Z faces.
7, 6, 4,7, 4, 5, // -X faces.
7, 5, 3,3, 5, 1, // -Z faces.
7, 3, 2,7, 2, 6, // -Y faces.
};上述代码中,每3个数值描述一个三角形,每个数值指定此三角形的顶点在positions里的索引。例如,indexes\[0\]、 indexes\[1\]、 indexes\[2\]指定了第一个三角形,其顶点位置分别为positions\[ indexes\[0\] \]、 positions\[ indexes\[1\] \]和positions\[ indexes\[2\] \]。
2. 顶点着色器
着色器(Shader)是一段负责给图形上色(或为上色做准备)的程序,其形式类似于C语言。OpenGL开始渲染后,首先会对每个顶点都执行一个简短的程序,这个程序就叫Vertex Shader。
Vertex Shader有2个用处:
指定顶点在Clip Space里的位置(即设置内置变量vec4 gl_Position;的值)。Clip Space的相关内容将在矩阵章节具体介绍。现在只需知道,为立方体指定的顶点位置是在三维空间里的,而这最终要画到二维的窗口上,所以必然要进行坐标变换。坐标变换就是矩阵的功能。
向后续的Fragment Shader传递数据。为了实现各种光照效果,向Fragment Shader传递顶点的位置、颜色、纹理坐标等属性。
3. 线性插值
立方体只有8个顶点,然而OpenGL却要画出成片的三角形,这是通过OpenGL内部的插值功能实现的。在Vertex Shader完成后,OpenGL会根据indexes生成所有的三角形,然后根据三角形的三个顶点的位置、颜色等属性,通过插值的方式,为三角形覆盖到的各个像素生成其位置、颜色等属性值。上文说明了Vertex Shader可以向Fragment Shader传递数据。这些数据,也将作为顶点的属性,一并进行插值。
具体的插值过程完全由OpenGL自动完成,OpenGL应用程序不需也无法干预。
4. Fragment Shader
经过插值处理,画布上的一个像素所在的位置,每被三角形覆盖一次,就会产生一个富含顶点属性的片段(Fragment)。Fragment Shader会对每个Fragment执行一次,其输出结果就是它所在像素的候选颜色值。
在Fragment Shader中,一个像素的颜色是由四维向量vec4(r, g, b, a);描述的。其中r、g、b、a的范围都是\[0, 1\]。如果Shader输出的值超出此范围,OpenGL会自动将其裁切到范围内。
1.3.4小结
这是首次认识Pipeline,因此省略了很多环节。真正完整的Pipeline要复杂得多,本书将逐步展开予以介绍。
Pipeline是OpenGL的核心,是一个高度灵活的算法(可以自定义Shader、控制开关)。充分利用Pipeline,就可以实现光照、阴影、拾取、透明等各种效果和功能。
1.4Hello OpenGL
本节一步步地介绍如何完成第一个OpenGL程序,画出一个立方体。读者可在本书网络资料上的c01d00_Cube项目中找到此示例的完整代码。建议读者先打开此项目,以逐步执行的方式调试运行几次,对项目代码建立一些感性的了解,再详细阅读下面的章节。请注意随时记得Pipeline。OpenGL所做的一切都是围绕Pipeline进行的。
Pipeline需要三类信息:模型、Shader和状态开关。最后调用OpenGL的渲染命令即可。状态开关用于控制OpenGL的各种状态,例如控制多边形渲染模式的glPolygonMode(..),控制线宽度的glLineWidth(..)等。
1.4.1新建项目
首先,在本书网络资料中找到CSharpGL.zip进行解压缩,用Visual Studio打开CSharpGL.sln解决方案。将解决方案中的Demos文件夹和OpenGLviaCSharp文件夹下的所有项目都删除,以便从零开始创建新项目。然后,新建一个项目,如图14所示。
图14新建项目c01d00_Cube新建一个“Windows窗体应用程序”,项目命名为“c01d00_Cube”。然后,为该项目引用必要的DLL库项目,如图15所示。
图15添加引用只需引用CSharpGL.dll和CSharpGL.Windows.dll。CSharpGL.dll封装了OpenGL函数,类似于在C++中引用了gl.h头文件。CSharpGL.Windows.dll封装了在Windows下使用OpenGL的初始化工作,类似于在C++中引用了glut.h头文件。