集成GLPbuffer和JavaGraphics2D

该程序在 2005 年又重新用 Java 编写了一次,主要设计工作由 David J. Eck 完成。其 Java 版本被称作 3D-XplorMath-J。它可在作为独 立程序运行,也可以作为 网站 上的一系列 applet 运行。该项目受到国际自然科学基金 (DUE Award #0514781) 一定程度上的支持。

OpenGL 集成中的设计问题

3D-XplorMath-J 最初使用 Java Graphics2D,我们的目标是找到集成 OpenGL 图形的最简单方法。3D-XplorMath-J 使用 接口 Renderer3D 定义的渲染程序来绘图。我们创建了另外一个渲染程序类,使该程序除了能执行现有 Java Graphics2D 渲染以外,还能执行 OpenGL 渲染。这 个新的渲染程序将设置 OpenGL 光照、影像和视点转换,然后使用 OpenGL 命令进行渲染和绘制。

OpenGL 提供了硬件加速 3D 绘图,借助改进的图形和极快的速度,能使用 Graphics2D 实现 3D 绘图。OpenGL 通过 JOGL API 使用 Java 实现。图 1 中的图像给出了改进图形的可视化表示,我们是通过 JOGL 实现的。从上面两幅图可以看出,JOGL 给出的蜗牛壳表示形式比 Java Graphics2D 绘制的更加清晰。但是,这点区别在不是很复杂的对象中并不明显。对于正在旋转的对象,这点区别会变得很明显,即使是卷形垫 ,这从下面两幅图中可以看出。

图 1. 图形比较。左上方:静止的蜗牛壳,使用 JOGL 绘制。右上方:静止的蜗牛壳,使用 Java Graphics2D 绘制。左下方:动态的卷形 垫,使用 JOGL 绘制。右下方:动态的卷形垫,使用 Java Graphics2D 绘制。

JOGL 中有三个类表示 OpenGL “可绘制区域”,即几个可以绘制 3D 图像的地方。它们是 GLCanvas、GLJPanel 和 GLPbuffer。其中, GLCanvas 和 GLJPanel 是两个常用的类,但在大型复杂的项目中,可能需要更改大量代码后,集成才能成功。GLPbuffer 是三个类中最少知道 的一个,但它拥有一些独特的功能,足以引起 Java 程序员的重视。在本例中,使用一个 pbuffer 来渲染对象似乎很简单,因为我们可以使用 与 Java Graphics2D 相同的框架,只要稍微修改为绘制到 pbuffer(在内存中存储屏幕以外的图片)即可。这样的图片然后可以作为一个标准 BufferedImage 被检索到,该 BufferedImage 可以被复制到屏幕。

GLCanvas 和 GLJPanel 是 GUI 组件。GLCanvas 更兼容 AWT,GLJPanel 更兼容 Swing。在 GLCanvas 中绘制会更快一点,但它是一个“重 量级”组件,在 Swing 中使用比较困难。在 Amy Fowler 和 Chris Campbell 合著的一些文章中对此问题进行了讨论。在 3D-XplorMath-J 中 使用这些类需要用 GLCanvas 或 GLJPanel 替换 JPanel。遗憾的是,JPanel 更多用在 3D 绘图中。例如,它用于显示 BufferedImage,这些 事情用 GLCanvas 或 GLJPanel 不是那么容易实现。请注意:至少在 Java 5.0 中是这样的。文章最后,我们将简要讨论 Java 6.0 中的一些 变化。

另一方面,JPanel 可以在不破坏程序 Graphics2D 绘制框架的情况下使用。GLPbuffer 表示内存中一个可用作 OpenGL 命令绘制界面的区 域,非常类似于 Graphics2D 的 BufferedImage。这样,就可以获得用 BufferedImage 形式绘制的图像副本。BufferedImage 然后可以使用标 准的 Graphics2D 技术复制到屏幕。在这篇文章中,我们将说明如何实现这一点,并讨论这样做的性能意义。使用 pbuffer 最主要是考虑到它 仍然是 JOGL 的一个试验方面,因而受各种平台不同程序的支持。绘制到缓冲区而不是直接绘制到 GLJPanel 或 GLCanvas 会变慢也是我们考 虑的一点。在 Linux、Mac 和 Windows 环境中经过测试后可以确定,这三者之间的差异很小,不会影响到 pbuffer 的使用。运行同一动画, 绘制 500 帧,每一帧画 500 个任意大小的球,每一种平台得到的速度如下:

类 整个动画的估计时间 每帧的估计时间 GLCanvas 39285ms=39.285s 78.57ms=.07857s GLJPanel 42325ms=42.325s 84.65ms=.08465s GLPbuffer 50945ms=50.945s 101.89ms=.10189s 类 整个动画的估计时间 每帧的估计时间 GLCanvas 48795ms=48.795s 97.59ms=.09759s GLJPanel 53161ms=53.161s 106.322ms=.106322s GLPbuffer 82221ms=82.221s 164.442ms=.164442s 类 整个动画的估计时间 每帧的估计时间 GLCanvas 50813ms=50.813s 101.626ms=.101626s GLJPanel 67704ms=67.704s 135.408ms=.135.408s GLPbuffer 70703ms = 70.703s 141.406ms=.141406s

这些表显示了使用 GLPbuffer 消耗的时间并不长,在 Mac 平台上表现得比其他平台更显著。因此我们决定使用 pbuffer 来试着集成,利 用它的试验特性来碰碰运气。用于生成这些表的程序代码可从 示例代码 下载。其中还有一个交互式程序,用于以交互方式比较这两种技术。

Pbuffer

从 pbuffer 获得一个 BufferedImage 后,再与现有数学可视化框架集成就变得非常简单了。剩下的只是设置一个 OpenGL 渲染程序来绘制 到 pbuffer,从缓冲区获得缓冲图像,然后将 BufferedImage 复制到屏幕上。下面说明操作方法。

要创建 pbuffer,使用如下代码,在绘制图片(宽和高是窗口的宽和高)之前调用 OpenGL 渲染程序。

if (buf == null || bufw != width || bufh != height){if (buf != null) { // clean the old buffercontext.destroy(); //context is type GLContextbuf.destroy(); // buf is type GLPbuffer}GLDawableFacTory fac = GLDrawableFacTory.getFacTory();GLCapabilities glCap = new GLCapabilities();// Without line below, there is an error on Windows.glCap.setDoubleBuffered(false);//makes a new bufferbuf = fac.createGLPbuffer(glCap, null, width, height, null);//save size for later use in getting imagebufw = width;bufh = height;//required for drawing to the buffercontext = buf.createContext(null);}

context 是一个渲染到任何 OpenGL 可绘制区域所需的抽象。JOGL 事件驱动绘制框架自动处理上下文,但它们需要在该框架外部绘制,即 本例中使用 pbuffer 的情况。手动控制上下文需要从可绘制对象获取上下文,并使它在渲染到时成为当前的。

我们发现还需要一个缓冲区清除检查,如下所示:

if (buf != null) {context.destroy();buf.destroy();}

我们发现,为避免创建太多 pbuffer 而使速度变慢和发生冲突,这一个检查很重要。destroy() 方法释放被上下文和 pbuffer 占用的资源 。这在 JOGL 中显然不会自动完成。

还应该在程序开头做一个检查,以确保能创建一个 GLPbuffer:

if(!GLDrawableFacTory.getFacTory().canCreateGLPbuffer())//Disable the using of OpenGL or at least by way of a Pbuffer

这个检查很重要,因为 pbuffer 不一定在所有计算机上受支持,而它要使用硬件来加快渲染速度。该检查查看计算机上的显卡是否支持 pbuffer,但从我们在不同实验机器上的测试来看,试图创建一个 GLPbuffer 有时会生成一条错误,即使该方法返回的是 true。既然这样,似 乎最好只试着创建 pbuffer 并捕获发生的所有异常。

现在渲染到 pbuffer,我们使用在缓冲区创建的上下文,并使它成为当前的。这确保随后的 OpenGL 命令将绘制到 pbuffer,而不是其他地 方。在此 OpenGL 渲染程序中,我们在渲染程序绘制方法开头调用上下文:

context.makeCurrent();

下一个任务是获取一个绘制使用的 GL 对象。直接使用 pbuffer 即可实现这一点,剩下的遵循常规 JOGL 语法。

GL gl = buf.getGL();

从 pbuffer 获取图形通过使用 JOGL 类 Screenshot 来完成,该类用于获取 OpenGL 应用程序的屏幕快照。不管名称如何,它只返回当前 OpenGL 可绘制区域而不是整个屏幕的图像。通过调用 Screenshot.readToBufferedImage(bufw,bufh),我们获取了当前 GL Drawable(我们的 pbuffer)的屏幕快照作为缓冲图像。另请注意,该 pbuffer 的上下文必须是当前的。现在我们有了缓冲图像,然后便可以使用 Java Graphics2D 来将图像绘制到屏幕了。

context.makeCurrent();BufferedImage img = Screenshot.readToBufferedImage(bufw,bufh);context.release();g.drawImage(img,0,0,null);

因为设计 3D-XplorMath-J 是为了使用 Java Graphics2D,所以最有用的部分是在项目中使用 pbuffer。既然能从 pbuffer 获取缓冲图像 ,那么在渲染程序外部就无需更改任何代码,只是要创建一个 OpenGL 渲染程序来进行 3D 绘制,而不是使用 Graphics2D 渲染程序。

速度考虑和一些解决方案

因为数学对象很复杂,所以渲染这些对象会非常慢,特别是在渲染一个曲面并对它进行实时渲染时。尽可能以多种方式旋转和查看数学对象 很大程度上是 3D-XplorMath 项目的目标,因此我们需要一种方法来提高实时渲染数学对象的效率。部分无效渲染归因于使用 pbuffer,但有 了 OpenGL 功能后,绘图仍然太慢。要缩短渲染时间,我们看一下 OpenGL 显示列表 以及它们的使用方式,并看看使用它们会达到怎样的速度 。

显示列表允许以优化形式存储 OpenGL 命令以便以后执行,这在存储将使用多次的几何形状和状态更改时特别有用,如旋转数学对象时。这 在本例中很有用,因为每次在绘制旋转对象时可以调用同一显示列表。既然这对组织代码很适用,那么它是否能按我们的需要提高速度呢?

显示列表由一个唯一的整数标识,这个整数是使用 gl.glGenList() 命令创建的,其中 gl 是一个 GL 类型的对象。接着我们使用列表标识 符和需要的模式创建一个新的列表。

displayListID = gl.glGenLists(1); // type intgl.glNewList(displayListID, GL.GL_COMPILE);

glNewList 后的 OpenGL 命令被添加到该列表中,当要存储在列表中的命令完整后,我们使用

gl.glEndList(); 来结束列表。

最后,在需要执行显示列表中的高速缓存代码时,使用列表的标识符调用该列表即可。

gl.glCallList(displayListID);

学会如何使用显示列表后,在了解如何将它添加到 3D OpenGL 渲染程序之前,还应该编写一个测试来看它是否真的提高了效率。以之前用 500 帧绘制 500 个球的测试程序为例,我们实现同样的测试,用额外的显示列表来存储绘制球的命令,并调用该列表将每个球绘制到屏幕。结 果我们发现速度大大提高了。

简单的球绘制程序只使用一个 pbuffer 和 0 个显示列表来用 500 个不同的帧绘制 500 个球,在 Linux 环境下大约花了 50s 的时间。使 用显示列表来实现同样的程序却只花了12s,所以我们决定将显示列表集成到项目中来使它更容易。

将显示列表添加到 3D-XplorMath-J 相当简单。其思想是将渲染对象的代码放入显示列表中,在渲染程序需要绘制到屏幕时,通过调用显示 列表来实现。这意味着,当对象仅需要转换时,如在旋转对象时,我们只需将转换应用到列表中,而不是使用对象新的方向来重新渲染它。

使用显示列表能大大提高旋转曲面的速度,但使用显示列表后还是比较慢。我们发现,这是因为我们使用 glBegin(GL_POLYGON) 来绘制单 个多边形。使用 glBegin(GL_QUAD_STRIP) 来绘制曲面后,速度就提高了。GL_QUAD_STRIP 是一个几何原型,是一次由两个顶点定义的多边形 闭边。这点微小的改动使得与绘制单个对变形相比,速度大大提高了。

了解将这两项技术实现到项目所带来的速度提高后,使用 pbuffer 付出的代价相比而言就很小了。现在我们就有了一个能很快执行实时渲 染的程序,甚至对复杂的几何对象也是如此。

快速了解 Java 6 和 OpenGL

在 Java SE 6 中,JOGL 和 Java 2D 可以直接同时使用。Java 6 现在允许 Swing 对象覆盖 OpenGL 渲染。现在,渲染 3D OpenGL 图形可 以直接在 Java 2D 图形上完成,类似地,Java 2D 图形也可以直接在 3D OpenGL 图形上绘制。到目前为止,3D-XplorMath-J 项目会继续使用 Java 5,但将来转向 Java 6 会有更多设计选项来集成 OpenGL 渲染,这会更可靠和可有效。Java 6 使得使用 GLJPanel 或 GLCanvas 相当自 然,这意味着无需再依赖从 pbuffer 获取缓冲图像来获得一点小小的速度提高了。不使用 pbuffer 将避免不能在一些计算机上使用 pbuffer 的麻烦,使 OpenGL 渲染能被更多人利用。Java 6 在图形和 Java 编程语言方面无疑是一大进步,应该通过它来结合使用 OpenGL 和 Java。 但对于仍然需要 Java 5 的程序而言,在转向 Java 6 之前,pbuffer 是使用 OpenGL 和 Java 2D 的选择。

才能做到人在旅途,感悟人生,享受人生。

集成GLPbuffer和JavaGraphics2D

相关文章:

你感兴趣的文章:

标签云: