相较于Java3D API来说,SWT以前在3D图形绘制方面一直没有什么好的表现。OpenGL的加入会不会使SWT在3D领域有所作为还尚未可知,不过起码IBM的程序员们给了SWT机会。当大家了解了这个正处于试验阶段的组合之后,我们在SWT上绘制3D图形就不再是噩梦。
OpenGL是一个为创建高性能2D,3D图形而设计的多平台的标准。其硬件和软件的实现存在于多个系统之中,包括Windows,Linux和MacOS。OpenGL可以用于渲染简单的2D图形或复杂的3D游戏图形(OpenGL最主要的应用领域就是游戏)。作为一个正在处于事件阶段的Eclipse插件,我将在下面的小节中介绍如何在SWT窗口组件上用SWT绘制图形。在Eclipse最新的3.2版中,对OpenGL的支持被集成到org.eclipse.swt项目中,所以我们在实现的时候即可以选择以插件方式进行,也可以直接利用已经集成好的组件来进行图形操作。在本节,我们将以插件方式为例对代码进行说明。
4.1 SWT OpenGL插件
SWT实现了OpenGL1.1全部功能。包括三个核心类和一个数据类。核心类为GLContext,GL和GLU。GLContext架起了SWT和OpenGL之间的桥接。一个Context必须用Drawable,通常是用Canvas来创建,OpenGL可以在Drawable上渲染场景。需要注意的是,当context不再被使用的时候就应该将它释放掉。同样,一旦某个context被释放掉之后,就不应该再次试图去渲染它。每次Drawable改变大小的时候,context都需要通过调用其resize方法在通知这一事件。这个方法的调用让context调整自己的view port和视图参数。在下一节中将描述一个处理这一部分任务的类。
当context可用的时候,我们就可以通过定义在GL和GLU的一系列方法调用来绘制场景。一个GL类大概有超过330条命令。在GL和GLU中定义的这些函数和他们的Native实现几乎是一一对应的。下图给出了一个绘制矩形的例子,我们可以看到用C写成的API和SWT OpenGL API是何其相似:
4.2 SWT OpenGL编程基础
在下面的小节中,我将描述一个显示四幅3D图像的应用程序。应用程序采用了GLSense,这是一个用于显示OpenGL场景的工具类。它和SWT的Canvas很像,所区别的是它所展现的内容是用OpenGL命令渲染的,而不是使用GC来绘制。要做到这一点,我们需要将一个GLContext类和一个SWT Canvas相关联,并且无论何时,当前上下文中的内容都应该是由在drawScene中定义的命令来渲染的。

在构造函数中,一个SWT Canvas被创建出来。这就是那个要和一个GLContext相关联的Canvas实例。紧接着,这个Canvas又注册了两个监听器。第一个监听器的作用是确保这个Canvas无论何时被改变大小,其相应的GLContex也会收到通知并适当的改变大小。第二个监听器主要用于确保一旦Canvas被释放之后,其相对应的GLContext的也同时被释放。为了确保渲染区域是一个非零大小的区域,父组件的客户矩形区被取出来用于设置该Canvas的初始大小。这个初始大小可以在稍后用布局管理器或用户Action来修改。

GLScene将Canvas的全部区域用于绘图。无论Canvas何时调整其尺寸,我们都要获取客户区并将新的宽度和高度传递给Contex,而context将根据新的宽度和高度适当的调整视图。
GLScene被分割为两个部分:初始化Context和初始化OpenGL的状态机。对于Context来说,我们只是简单的建立一个新的GLContext并使它成为当前被使用的Context。OpenGL的渲染总是在当前的context上进行绘制,因此如果你有超过一个活动的GLScene,很重要的一点是要在所有绘制动作发生之前将它的Context设置为当前的Context。initGL方法最开始提供清除颜色缓存颜色,随后建立了一个深度缓存(depth buffer).第47行指出了深度值如何进行比较。这一比较函数主要用于拒绝或接受正在引用的像素。GL.GL_LEQUAL选项指定接受那些在视图上更接近或有相同距离的像素。第48行启动了深度测试(depth test),紧接的一行设定阴影模型为GL.GL_SMOOTH,这一设定的效果是如果表面上的两个顶点颜色不同的话,系统将对颜色进行插值。最后,第50行要求渲染引擎在计算颜色和纹理协调插值运算的时候起到关键的作用。
GLScene类的最后两个方法用于处理重绘和场景绘制。当场景何时需要重绘的时候,第一个方法为其他类提供重绘操作的接口。第二个方法主要用于让继承GLScene的子类覆写。其缺省实现只是简单的清除了颜色和深度缓存,通过装在鉴别矩阵(identify matrix)重新恢复调整系统。
4.3 3D Chart
利用上一节的准备,我们已经将主应用程序进行了划分。这个图像显示了4组数据。每一组数据都是由相同的固定点所组成,每个点都是从0.0到10.0之间的一个正值。
示例程序运行在一个非常简单的Eclipse view上,唯一值得注意的是Refresher,这个线程将强迫OpenGL场景被周期性的重绘。通过这种方法,当视图被移动或旋转的时候,component总能进行有效的更新渲染效果。run()方法调用的时间间隔为100毫秒,所以理论上的图像速度能达到每秒10帧。
每个数据集合的点的值是用圆柱体来表示的。通过执行3个GLU调用,我们就能够绘制圆柱体:其中的两个用于渲染圆柱体两头的圆盘部分,另外一个用于渲染圆柱体的四周。例如,要渲染两个单元高的圆柱体,你可以用下面的代码来实现:
第一行申请了绘制圆盘和圆柱所需的二次曲面。然后整个场景被逆时针旋转了90度,以便圆柱体可以被垂直绘制。下一步,底部的圆盘被渲染,然后是圆柱体的四周。在我们能够绘制顶部圆盘的时候,通过场景转换(scene translation),我们可以在Z轴移动两个单元。最后一个圆盘随后被绘制出来,调整系统通过向回移动两个单元来进行恢复。最后,由第一行申请的二次曲面被释放掉。
按照上述方法运行程序是很费时间的。当仅绘制一个圆柱体的时候,效率低下不是一个很严重的问题,但如果要绘制成百个对象的话就会严重影响程序的执行性能。对于这种情况,OpenGL给出了一个解决这个问题的技巧,就是使用显示列表(display list)。
一个显示列表是一组已编译的OpenGL命令。定义命令集合的列表被放在glNewList(int list, int mode) 和 glEndList()方法调用之间。第一个参数必须是一个正整数,可以用来唯一的表示一个被创建的显示列表。你可以让GL用glGenLists(int n)方法为你生成多个列表标识符。第二个参数用于指定列表是否被编译或编译之后立即被执行。大多数情况下你都需要编译这个列表。然后,你可以使用glCallList(int list)方法来显示整个列表。
5 结束语
至此,有关于SWT与OpenGL图形有关的粗略功能就介绍完了,有鉴于3D图形对象和OpenGL的复杂性,一篇这样篇幅的文章肯定不能覆盖其每一个角落,我只能给各位一个动手尝试机会。希望整篇专题没有让你枯燥得睡着,并因此有了一个不错的SWT的基础,我的目的就达到了。