自从 2 月份研究了投射矩阵(projection matrix)之后,学习 OpenGL 的事情就放下了好一阵子。直到最近工作上有了实际的需求才重新拾起来。
附带说一下,《OpenGL SuperBible》这本书真的不错,虽然已经多少年对名字含有『Bible』的书敬而远之了。不过纠结了很久是买第四版还是第五版,因为第五版把 OpenGL 3.0 里标为 deprecated 的 fixed-pipeline 部分删除了。但是短期内不好说 OS X 对 OpenGL 3.0 能支持到什么程度。而且实际中也要尽量支持 Windows ,那是个对高版本 OpenGL 更严酷的环境。
每次看 OpenGL 都觉得它的编程风格透着股邪气,这当然有我本人经历的缘故,但也主要是因为 OpenGL APIs 对参数和返回值的使用相对一般的编程风格来说比较少,而对当前状态(current context)的依赖相对较多。比如说纹理贴图操作(texture),一般的风格是把作为纹理的位图载入内存,然后返回一个纹理对象的引用或者 handle ;然后调用贴图的函数,把纹理对象和被贴图的对象作为这个函数的参数(或者贴图函数就是被贴图对象的一个方法)。OpenGL 里,一般先用一个函数把位图调入内存创建一个『当前』纹理对象。但是没有任何引用或者 handle 表示『当前』纹理对象,后续的贴图操作仅仅依靠『当前』这个状态本身来操作。再比如向一个多边形添加一个顶点(vertex),没有任何引用表示这个多边形,你能依靠的只是『当前』多边形。当然用过的人对此也不会太奇怪,因为 OpenGL 标准开宗明义 OpenGL 是一个『状态机』模型。
我初次接触 3D 开发是 7 年前的 Java 3D,使用的是和 OpenGL 完全不同的概念模型,虽然用的是 Java 语言,但是更接近声明式的风格。这也是我觉得 OpenGL 邪门的一个原因。
要说这个概念也不是那么难懂,但是为什么会透出一种邪气呢?我猜大概是从高级语言诞生以来,程序员已经习惯了用参数和返回值进行跨函数的信息传递。而高度依赖上下文属于上一代语言,也就是汇编语言的编程风格。
在高级语言中,借助符号化的变量和逻辑化的语法,可以毫不费力的在一行代码里访问五六个甚至十来个变量。所以借助于这些直接访问变量之外的隐式上下文在高级语言里成为了不必要的事情,尤其因为隐式上下文是加大程序局部复杂度的主要原因。而汇编语言则恰恰相反,需要通过好几条甚至几十条指令才能完成高级语言一行的功能,而每条指令只能操作一两个操作数。所以隐式上下文是不可缺少的。
因为采用状态机模型和汇编的编程风格,所以基于 OpenGL 编写的代码颇有些奇异的风格。比如,今天的代码很少在语法结构之外采用缩进,也就是说,除非是语法级别的结构,比如类的定义、分支、循环、函数定义、或者命名空间等等,一般的语句不会采用缩进。而在基于 OpenGL 的代码里,基于上下文变化的缩进是普遍的习惯。OpenGL 虽然提供 C 函数作为 APIs ,但是本质上和形式上都是显卡的汇编语言。在对性能要求极高的 3D 领域,虽然形式上通过 C 语言提供了跨平台能力,本质上却重演了早期计算机开发注重性能的汇编语言阶段。
OpenGL 的汇编风格还我想到一个在网上讨论中遇到过两三次的观点:声明式(declarative)的 DSL(domain specific language)具有广泛的未来,特别是声明式语言给底层平台的全局优化留下了巨大的空间。OpenGL 的目的是描述一个场景(scene),从问题本身来说是一个非常适合声明式 DSL 的任务,从机制来说也需要最大化利用硬件优化。但 OpenGL 并非声明式而是命令式(imperative),而且并非高级语言的过程式结构化风格而是偏重于汇编风格,我想这也是现实向理想低头的一个例子,毕竟只有程序员才能从各个层次考虑优化。
2020/05/11 10:51 下午 |
[…] 在上篇的结尾提到了声明式 DSL 和 OpenGL 的汇编风格的差别和联系。其实我当时还联想到 model-view-controller 模式,这次另起一篇。 […]