对于3d 图形程序员,看到了这个标题,可能想到了硝烟弥漫的API大战… 首先声明,这篇文章并不参战,当然也不希望任何人利用本文的观点作为API战争的佐证,本文是要客观的阐述OpenGL 足以实现现代游戏的3d图形表现需要,而不是要贬低其它的API,比如Direct3D,当然本文的读者也不仅限于3d 图形程序员,也可以作为项目制作人选择什么API的一个参考。
如题所言,使用OpenGL制做3D游戏,当然,这不是说一定就直接使用OpenGL API来开发游戏;采用基于OpenGL的Graphics Engine来制作游戏,也包含在这个话题的范畴之内;可以理解为使用OpenGL API制作小型3d图形应用,或者采用基于OpenGL的Graphics Engine 来制作大型的图形应用;
下面就几个方面来说明选择OpenGL 的理由
1.跨平台
这个理由充足么?MS Windows 占据了多数PC用户,但是其它平台用户不应该忽视吧?如Linux,mac; 即便从商业角度来考虑,也不应该主动放弃阵地的。或者你会说,你的游戏只运行在MS Windows 平台,但是我要说,还是要用发展的眼光来看问题,现在的断言不一定永远都成立;举一个很现实的例子来说明这个问题,Windows 6(vista,2008,windows 7 )以后的版本都自己封杀了DX10,DX11,这意味着,DX10的游戏要想运行于WinXP,必须采用DX9 另实现,其实DX10,DX11 有很多的地方无法和DX9对应起来,这必然增大开发得复杂性和成本;然而如果采用OpenGL2.0 或者OpenGL 3.0都没有这个问题;它对任何平台都是一视同仁的,没有强行的拒绝任何系统,OpenGL拒绝的只是霸权主义,强jia(n)用户;这里可以看到,即便只做Windows 程序,DirectX都会难以跨自家的平台…
2.天生支持多核心
次时代游戏,一个非常热火的字眼,多线程渲染就是次时代的重要标志之一;OpenGL诞生伊始,就是针对高性能并行图形工作站而设计的,所以它天生支持多核心系统;而Direct3D 生来就是面向PC用户.根本就没有考虑多核心问题;这里说的多核心是CPU多核,而不是GPU多核;
OpenGL 采用TLS( Thread Local Storage,线程局部存储 )技术实现渲染线程的RenderContext,TLS,对于经常做多线程性能优化的程序员来说,应该非常的熟悉,它意味着互斥访问的零开销;它用的是独立的copy来实现共享资源的访问。
#define _declspec(thread) _THREAD_LOCAL_STORAGE
struct SRenderState
struct SRenderContext
{
SRenderState rs;// render state of this render context
}
struct SRenderTSD
{
SRenderContext* rc;//current render context of this thread
SRenderContext* rc_list;//all of render context of this thread
};
_THREAD_LOCAL_STORAGE SRenderTSD * _render_tsd;
从上面伪结构来看每个渲染线程可以有一个独立的_render_tsd 的copy,虽然你只定义了一个,但是每当有一个线程运行的时候,都有一份独立的copy;意味着每个线程的rc都是没有访问冲突的;既然rc没有访问冲突,那么每个rc的state_block也是独立的;
即便在多核心cpu上并发的执行set_state,也没有任何问题,因为set_state通过上面的_render_tsd来访问自己私有的state block;
假如一个并发的多线程如下所示:
Thread1 – RC1 Thread2 – RC2
//操作RC1 //操作RC2
SetState() SetState()
//采用RC1.state绘制 //采用RC2.state绘制
DrawPrimitive() DrawPrimitive()
… …
从上面看到,上面两个执行序列在多核心上任何时刻被打断都不会出现问题,因为它工作在各自独立的state block上。
另外不同线程的OpenGL渲染资源(纹理,显示列表,VB)是可以全局共享的,可以保证最大的利用率.
这里是否可以说明OpenGL 架构的天生优越性呢?Direct3D11以前的版本是都不行的;如果用Device来代表RenderContext,可以保证状态独立性,但无法实现资源共享;同一个device又只能使用一个state,面临着这样的尴尬境地:共享资源,就不能状态独立;状态独立,就不能共享资源;
到了Direct3D11 终于出现了RenderContext接口,才解决了这个问题。遗憾的是,在我写这篇文章之时,DirectX11 还没有正式发布,仅仅还是Preview 版本;在我设计的引擎中试图采用一些hack手段来改造Direct3D 9 来实现Render Context,后来也放弃了,因为DirectX11 已经意识到了这个问题,并且把它实现了,我没必要费这个力气了,用Direct3D11对应就好了。
3.天生支持多流
对于VertexStream,OpenGL是天生支持多流的;glVertexPointer,glNormalPointer,glTexcoordPointer…等等一系列API就是用来设置流的;Direct3D的FVF很令人讨厌;连MS自己都讨厌它,后来演变出了VertexDeclaration接口…那一堆罗里八索的declaration还是令我欲吐不已。
多流有什么好处?实时改写很高效。比如,修改Normal array,不必把Position,Texcoord,一起unmap…
当然对于只读的vertexbuffer,单流性能最好… 单流,就是跨距顶点格式,访问具有良好的局部性,所以提高了性能;不过,OpenGL 天生支持多流,并不是说它不支持单流,
用同样的api仍然很容易很简洁的实现跨距顶点格式。
4.多视图
多视图问题对于CAD开发者应该很感兴趣;我做过一些调研,大家使用OpenGL做多视图,通常使用这样的方案:在主线程创建所有视图的RenderContext,然后再渲染每个视图之前要执行MakeCurrentContext 操作;并且抱怨MakeCurrentContext 操作的开销很大。这里要说明的是,如果这样做,说明你真的还处在没有入门的阶段;使用OpenGL一定要有线程的概念:RenderContext是属于线程的;
让每个视图都有一个独立的线程,每个渲染线程都有独立的RenderContext,并且使用wglShareLists在不同线程间共享资源,也许你会觉得很麻烦,但是这的确是最佳的办法。
通常多视图都是子窗口,如果多个视图让子窗口在不同线程创建,会导致线程必须自己来处理消息队列,因为消息队列也是线程拥有的东西,并且还要调用AttachInput来给线程获得输入。
另一种思路是让渲染管线和视图相分离,就是采用离屏渲染的方法,让每个视图的渲染流程渲染到离屏的FBO中,在把FBO blit到视图窗口的显示区域。这样离屏渲染放在独立的线程中,而所有视图仍然在同一线程中,这样就解决了消息处理的麻烦问题,同时又实现了多线程并发渲染;
还有一个问题,如何把同时Present 所有的视图?熟悉Direct3D的程序员都知道使用SwapChain,但是OpenGL中的SwapBuffers,或者wglSwapBuffer仍然是一个和RC相关的API,就说此线程的SwapBuffer不能呈现彼线程的渲染视图,即便采用了ShareList来共享fbo也不成。它的实现就是不让你跨线程执行Swap。这也是ms实现的一个蹩脚之处。这个问题困扰了我许久。。。后来我意外之中发现了wglSwapMultipleBuffer 这个API,MS 对这个API的支持非常低调,只在wingdi.h中有寥寥数行声明而已,没有任何文档和注释。。。(真的很想骂ms…)
//wingdi.h
#if (WINVER >= 0x0500)
typedef struct _WGLSWAP
{
HDC hdc;
UINT uiFlags;
} WGLSWAP, *PWGLSWAP, FAR *LPWGLSWAP;
#define WGL_SWAPMULTIPLE_MAX 16
WINGDIAPI DWORD WINAPI wglSwapMultipleBuffers(UINT, CONST WGLSWAP *);
#endif // (WINVER >= 0x0500)
网上没有任何解释,不信你google一下这个api,寥寥无几。
我也只知道uiFlags设置非0表示可以翻转呈现,其他的细节只有ms自己知道了。而且只支持WINVER >= 0x0500的系统,就是说win2k/xp以下的系统不支持这个api,比如win95/98/nt
5.可编程管线
GPU编程今年来如火如荼,和多核环境一样,这也是3d图形编程的重大变化之一。Dx9时代的N6800芯片率先完整实现SHADER MODEL 3,标志着3d 图形编程进入次时代第一阶段。
OpenGL 2.0最初由3dlabs执掌帅印,GLSL1.0的推出,3dslabs功不可没,只是不知怎么就悄悄地被knronos给取代了。看起来knronos雷厉风行,但是雷声大雨点小。。。没有什么实质性的进展。
OpenGL 2.0 中的可编程管线支持Pixel shader,Vertex shader,Geometry Shader,相当于Shader Model 4.
如果从固定功能和可编程功能,以及框架结构对高性能渲染的支持程度来看,OpenGL 是远胜于Direct3D11 以前的任何版本;注意截至本文发布之时,Directx11还是未出生得胎儿,OpenGL 4.0似乎也在酝酿之中,但是看起来有点渺茫,这让我不得不怀疑khronos的实际执行能力了,但愿我怀疑错了。
6.扩展的问题
关于OpenGL 扩展,很多人的一致观点是OpenGL ARB 太官僚,效率太低,不过这的确是个事实。但也不得不承认,慎重的加入新的功能,才能保证质量。另一个观点是OpenGL扩展在不同显卡上支持得程度的不一样,比如 N 卡支持较好,A 卡支持不好。
这里首先要说,Direct3D 的功能也不是在任意显卡上都可以硬件实现,也是依赖硬件实际能力的。另外我个人只偏好 N 卡,A卡上OpenGL效果没怎么见过。但是我坚信一点,如果你只用 ARB 扩展,我相信是相同的。非 ARB 扩展本来就是有差异的。
如果说某一显卡对 ARB 扩展支持的不好,只能说明这款显卡是不合格的,而不能归罪于OpenGL。永远都要记住,OpenGL 只是一套规范而已,某某显卡不符合规范,这不能赖OpenGL吧?
另外,很多人只会用OpenGL 标准功能,而新版本OpenGL 的标准扩展却知之甚少。
而且他总把API版本和硬件OpenGL 版本相混淆,他总会说,MS的OpenGL 只支持1 .21,我如何使用OpenGL 2.0?这两个版本号根本就不是一类,没有可比性。
要使用OpenGL 新功能,推荐大家一定要多上 [url]www.opengl.org[/url]这个站点
[url]www.opengl.org/registry[/url] 下面的opengl specfiction pdf和那些 opengl extensions 都要看,ARB 开头的扩展是必须要掌握的;
和DirectX8,9,10一样,OpenGL 1.5,2.0,3.0所包含的不仅仅是API中看到的标准功能,也包括A[i][/i]RB标准扩展。
7.可用资源储备量
可用资源,一个是技术资料,包括文档,代码,商业库等等,这一点Direct3D很丰富,OpenGL也不差,大概大家都是轻信了OpenGL 胜过 Direct3D的谣言了,所以Direct3D的可用资源似乎积累的要多一点。
另一种可用资源就是3d用户。和上面的原因一样,很少有人深入的研究它们之间的细微差异,简单的听别人说Direct3D比OpenGL好,就认准了Direct3D了。让我想起了小马过河的故事。。。
最后我想说的是,无论用什么api,或者更退一步说,无论研究什么,自己都要深入的研究一下,才能做出准确的判断,才是对自己负责的一种态度。