浅谈新一代可程序化移动3D绘图技术

本文作者:admin       点击: 2008-12-10 00:00
前言:

自2003年Khoronos Group推出OpenGL ES 1.0标准,移动3D绘图技术的发展至今已近5年,但移动3D绘图技术真正开始受到一般消费者大众所注目,则是来自于去年发表之iPhone的操作接口。iPhone让消费者发现到,原来他/她每天带在身上的那个小小装置,可以有那么酷炫且好用的操作接口。智能型手机大厂HTC今年推出的HTC Touch Diamond,更是搭载了全3D的操作接口,试图给消费者一种全新且超越iPhone的使用经验。

本文将介绍新一代可程序化移动3D绘图技术,包括OpenGL ES 2.0、ES Shading Language、以及JSR297 (Mobile Graphics API 2.0) 。透过这些技术的支持,未来的移动电话上将会逐渐出现视觉效果超越PS2之移动游戏,或是媲美Apple Mac的操作接口(见图2)。

(1)新一代绘图接口标准 - OpenGL ES 2.0
目前大多数的中高阶手机都已支持OpenGL ES 1.x的绘图接口,应用程序:如游戏、或是用户接口 (User Interface),可以透过此OpenGL ES绘图接口对图形加速硬件进行设定绘图状态(Render state)与下达绘图指令(Draw primitive),进而在屏幕上呈现出以多边形及贴图等效果所合成的3D内容。OpenGL ES规格系由Khronos Group所制定,新一代的绘图接口标准为OpenGL ES 2.0,在2006年的SIGGRAPH会议中正式推出。目前OpenGL ES 2.0的硬件尚未在市场上出现,但包括NVIDIA Tegra与TI OMAP3等高阶应用程序处理器(Application Processor)都已经标榜支持OpenGL ES 2.0标准。根据ARM的预估,在2010年始将会有少量的高阶智慧手机开始内建OpenGL ES 2.0图形加速器。

OpenGL ES 1.x到OpenGL ES 2.0的改版在架构上有非常大的改变。不同于OpenGL ES 1.x是固定功能的架构(fixed-function pipeline),OpenGL ES 2.0改为可程序化硬件架构(Programmable),来提供使用者(如:游戏开发者)大幅扩充硬件绘制功能的空间。我们从Khronos Group所制定的未来蓝图可以知道,未来OpenGL ES将会持续有Fixed-function与programmable两种不同架构的版本,且OpenGL ES 1.x与OpenGL ES 2.0各自会有自己的演进路程。

全新架构的OpenGL ES 2.0

为了支持更精致与拟真的3D绘图质量,针对OpenGL ES 2.0标准Khronos Group一改过去OpenGL ES 1.X版本的固定功能(Fixed-Function Hardware)架构,而是引进桌上型OpenGL 3D应用程序接口所使用的可程序化硬件架构(Programmable Hardware)。但OpenGL ES 2.0与桌上型OpenGL 2最大的不同是,不像OpenGL 2仍有固定管线(Fixed-Function Hardware)架构,OpenGL ES 2.0则是完全移除了固定管线之功能,使得OpenGL ES 2.0在Vertex处理阶段与Pixel处理阶段是一个完全可程序化硬件的架构。

图4为OpenGL ES 1.1版本的绘图管线图,其中橘色部分所表示的处理阶段如:3D空间坐标转换、光照计算(Lighting)、材质环境、颜色、雾化及透明测试等,在OpenGL ES 2.0皆被着色器(Shader)所取代;其中3D空间坐标转换与光照计算将会被顶点着色器(Vertex Shader)取代,而其余部分将由片段着色器(Fragment Shader,又称Pixel Shader)所取代。OpenGL ES 2.0的绘图管线图,如图5所示。

在OpenGL ES 2.0的架构内,可程序化绘图硬件全面支持浮点数运算,在使用浮点数的运算与上个版本相比有很大的进步。受惠于可程序化绘图硬件上有浮点数向量与矩阵计算的硬件,让OpenGL ES 2.0的Vertex Shader与Pixel Shader可以执行复杂且精确的数学运算,让OpenGL ES 2.0可支持的功能或效果有非常高的弹性。

OpenGL ES 2.0 内的Shader Model 

在OpenGL ES 2.0的可程序化Shader模块内主要可以分为两大块,分别为顶点着色器(Vertex Shader Stage)与片段着色器(Fragment Shader Stage)。对于顶点着色器来说,它处理的对象就是顶点(vertex)。顶点内部的信息我们称为该顶点的属性(Attributes),它通常是被用来描述那些变动相当频繁的信息,像是顶点位置等。在OpenGL ES 2.0标准中一个顶点最多可以包含8个属性字段,每一个属性字段可以最多拥有4 component-vector的大小。在顶点着色器内,各顶点间其属性信息是无法共享的,且顶点彼此之间并无法得到互相的顶点属性。而在属性的输入方面要注意的是,每一次的绘制呼叫(也就是说,绘制一个三角片串行(Triangle list)或是一个三角片带(Triangle strip)),所有的顶点都必须拥有相同数量与型态的顶点属性。

要正确地启动顶点着色器,必须透过API把顶点信息(Attributes)与顶点Uniforms参数这两类输入信息传递给顶点着色器。其中,顶点信息是一定要输入进去。而顶点的Uniforms参数则是一个从外部传进来给顶点着色器内部运算用的全局变量,通常是用来描述一些对于顶点来说变动较少的信息,如:转换矩阵、光源位置及颜色等相关信息,相对于顶点属性参数来说,这些Uniforms信息不会因为顶点的不同而有所不同,每一个Uniforms参数对于全部要被处理的Primitives都是不变的,且所有Uniforms参数都是只读。
 处理顶点后(以一个Primitive为单位,如一个三角片),顶点着色器会将已处理好的各种数据输出至Rasterizer,而Rasterizer将会对Primitive作描绘(rasterization)的处理。这时一个三角形将根据其在屏幕坐标的位置与范围,被细切成多个的像素。相对于顶点着色器处理完的各种数据(Varying),会传递到Rasterizer产生之像素,且其内容将会根据三角形的顶点与像素之位置来作内插,计算出经过描绘后来顶点着色器传给每一个像素的Varying参数资料。Varying参数将跟随像素信息传递给片段着色器去做下一阶段的处理。这里需要特别说明的是,即便顶点着色器可以针对各顶点去作控制运算来达到我们想要的效果,但仅仅限于控制当下这一个顶点,在着色器内无法允许去存取在处理中顶点邻近的其他顶点信息。

片段着色器的运作与顶点着色器非常相像,每一次着色器是处理一个像素(Pixel)。相同地,片段着色器的输入端是像素Uniform参数与顶点着色器所传递过来的Varying参数。如同顶点着色器一般,Uniforms是外部传进来的参数,在片段着色器这边,可以使用Uniform参数将材质(Textures)由外部传递进来作贴图与颜色合成等的动作。Varying参数如同顶点着色器的顶点属性,它所代表的是由顶点传递过来细切后的信息。经由片段着色器计算完的pixel颜色值,会再进行Raster Operation,包括Depth test、Alpha Blending、与Multi-sampling Anti-aliasing等处理,最后之pixel结果将会被使用来更新Frame buffer或是材质(Textures)的内存。要控制更新Frame buffer还是材质是取决于给予的OpenGL ES指令与状态来决定的。

OpenGL ES 着色语言 - ESSL

在编写控制顶点着色器(Vertex Shader)与片段着色器(Fragment Shader)之程序代码(即shader code)所使用的程序语言方面,Khronos Group定义了OpenGL ES着色语言(OpenGL ES Shading Language, ESSL)。ESSL是根据OpenGL 2.0的OpenGL着色语言(GLSL, OpenGL Shading Language)当作参考依据,针对嵌入式装置的需求,以简易使用与实作目标而设计出来的。在使用的语法及流程上皆与桌上型OpenGL着色语言相同。ESSL语法类似C语言的着色语言,对于C/C++/Java的程序开发者而言,它很容易学习且使用。不同的是在着色语言内并没有指针的存在,这意会着shader程序开发人员无法直接地控制内存。由于着色器内的缓存器数量有限(参考前述之shader model),shader程序的复杂度与缓存器的使用必须非常谨慎。过度复杂的着色器程序将会有非常差的执行效率,甚至是无法正确的执行。另一个ESSL与GLSL之不同点是,它新追加了三个精度修饰词:

highp、mediump、lowp,目的是为了增加嵌入式系统硬件使用弹性与效率,让不同的使用状况可以应用不同的精确度来作运算。以浮点数为例,highp的使用范围是(-262, 262),大小范围则是(2-62, 262),mediump的使用范围是(-214, 214),大小范围则是(2-14, 214),lowp的使用范围是(-2, 2),大小范围则是(2-8, 2)。

下表是个简单的Vertex Shader 范例,内容是对顶点进行矩阵运算,其中顶点值是透过attribute变量由主程序透过OpenGL ES API取得,而uniform变量也是来自主程序的给定。计算后的结果则设定至gl_position此内建变量。gl_Position为OpenGL ES内部Built-in variables,要让片段着色器正确的知道绘制的屏幕坐标,gl_Position是正确执行着色器所必须要给定的参数,如此经过转换后的位置将会透过Rasterizer将每个顶点的屏幕坐标细切成为每个像素的屏幕坐标。 

(2)JSR297 - 支持可程序化3D之Java 3D Engine

现在的手机不论是Feature phone或是高阶智能型手机,绝大多数都有提供下载与执行Java应用程序(Midlet)的功能。至于手机能执行那些功能的Midlet,则取决于该手机有搭载哪些Profile及Package。在2003年的11月,负责制订Java Profile规格的JCP组织公布了一个可于移动平台上执行3D功能的Package - Mobile 3D Graphics API。Mobile 3D Graphics API简称M3G,其JSR( Java Specification Request )代号为184,是由Nokia所主导制订的,其余参与制定的专家群( Expert Group )成员包括:SonyEricsson、Motorola、Siemens、Sun、ARM、Intel、Texas Instruments等等。在2005年的6月JCP组织公布了M3G的更新版本- M3G 1.1,此次更新主要包括:扩增Loader支持所有PNG的格式、多了一些查询的API以及移除一些不必要的exceptions。考虑到硬件技术的不断更新与演进,移动绘图技术也随着PC产业的脚步迈进可程序化的时代,JCP委员会近期发布了支持OpenGL ES 2.0的新一代M3G规格草案 - JSR 297。JSR 297同样是由Nokia公司起草,于2006年5月提交到JCP委员会。JSR 297是基于原JSR 184架构的进一步扩充,它被设计为可以直接实作在OpenGL ES 2.0和1.1的图形加速硬件上,充分利用手持装置的3D加速硬件。

JSR184概述

JSR 184(M3G 1.0、1.1) 共有30个Class,依功能性可以分为:绘图模块( Rendering Module)、材质与光源模块( Material and Lighting Module)、动画模块( Animation Module )、场景模块( Scene Module)、2D影像模块( 2D Image Module ) 、加载模块( Loader Module )等六大模块。各模块简述如下:

● “绘图模块”:以Graphics3D此Java Class为主,共有Retain Mode与Immediate Mode两种不同模式。Retain Mode是把整个虚拟场景(World)一次绘制;而Immediate Mode则是针对虚拟场景中的某个单位(Node)或是更低阶的Vertex Buffer数据来进行绘制的动作。此绘图模块为M3G的核心引擎,由它来趋动呈现整个输出结果。

● “场景模块”:包括 World、Group、Camera、Light、Mesh、MorphingMesh、SkinnedMesh、Sprite3D等8个Java Class。M3G利用这些对象来构成整个虚拟场景之树状阶层结构中的各式节点。其中,World,用来建构场景的根节点( Root Node ),群体( Group )用来建构 Scene Graph 中具有子节点( Children ) 的节点,而Camera、Group、Light、Mesh、MorphingMesh、SkinnedMesh、Sprite3D则为叶节点( Leaf Node ),此种节点下不能再有任何的子节点。

● “材质与光源模块”:包括Apearance、CompositingMode、Fog、Material、PolygonMode和Texture2D等6个Java Class。每个Mesh和Sprite3D对象,都有一份记录它对应的绘图属性(rendering attributes),称之为Appearance。如果任一个Mesh或Sprite3D对象没有这份数据,则我们将无法在画面上看到它,也无法对它做选取(picking)的动作。一个Appearance对象包含了至多一个“CompositingMode”对象、至多一个“Fog”物件、至多一个“Material”对象、至多一个“PolygonMode”对象以及0个到数个“Texture2D”对象,其关系如图9所示。

● “2D影像模块”:包括 Image2D、Background 以及 Sprite3D等3个Java Class。虽然是M3G是以3D为主的函示库,不过M3G仍提供许多对2D处理的函示。一般2D影像的缩放、对影像内容实时修改等动作,Image2D 对象都有相对应的API可以使用。再配合上Appearance class的相关函示,M3G在 2D 影像的变化上可产生多种不同的效果。

● “加载模块”:以Loader此Java Class为主,其目的是将已制作好的M3G档案载入进来。有了这个模块,M3G之midlet开发者可以直接将别人制作好的3D场景加载进来。目前主要的3D制作软件如:3DS Max或Maya,都可以透过内建的Exporter将档案输出成JSR184之”.m3g”檔。

● “动画模块”:包括AnimationController、AnimationTrack、KeyframeSequence等Java Class。每个具动画效果的对象(Animatable Object)将链接有1到数个的“AnimationTrack”来负责动画对象中某个被改变的项特性(Property)如:位置、颜色等等。而每个“AnimationTrack”都有其相对的“KeyframeSequence”和“AnimationController”,其中,KeyframeSequence负责储存对象特性的“动画数据”,而由AnimationController负责控制动画如何变化。
图10为两个JSR184的范例游戏图标。目前的中高阶款手机大部分都有支持JSR184,而JSR184更被纳入JSR248/249(Mobile Service Architecture)中,成为电信业者日后采购手机之必要规格之一。

JSR297与JSR184之差异

JSR 297设计的目标主要是希望支持现阶段所有的装置,甚至是未来的产品,而设计的一个重要的准则为可以向下兼容于现行的JSR 184。为了要能确保在日后JSR 184可渐渐地被JSR 297取代,且使用JSR184开发游戏的厂商能够快速的移植原有的程序代码以及游戏资源到JSR 297,因此JSR 297被设计为可让之前存在的JSR 184应用程序的程序代码可以正确编译无误,而原来的.m3g档案也可正确的载入。

基于制造成本、产品定位等因素,移动电话的硬件能力差异非常的大,从单独的ARM 7/9等级的CPU,到ARM 11等级CPU搭配3D加速器等。为满足移动装置产业特殊的硬件规格差异性,JSR297的设计分为两个部分:一个是必须实作的“Core Block”、另一个是选择性实作的“Advanced Block”。“Advanced Block”包含了可程序化的Shader以及一些必须要有绘图硬件才能实现的功能。若一个JSR297的实作只有支持“Core Block”被称为“Core Implementation”,而能支持所有功能的实作被称为“ Advanced Implementation”,这两个block与M3G 1.1的关系如图11所示:

“Core Block”相对于JSR 184能提供较佳的绘图质量、效能以及较少的内存使用量;而“Advanced Block”主要是针对有可程序化的绘图硬件所设计,能够提供更好且不受限于过往OpenGL ES 1.x固定功能(Fixed-Function)的绘图质量。简单的说,未来我们可以预期高阶手机会支持“Advanced Block”以及“Core Block”的部分,而低阶手机将仅仅能支持“Core Block”。

“Core Block”新增了一些特征让它相对于JSR 184能提供较优的绘图质量、效能以及较少的内存使用量,分述如下:

● DynamicImage2D:一个二维的图像,其内容会自动的从它关联的数据源做更新的动作,可用来做”Video Texture Mapping” 。”Video Texture Mapping”是目前最好的材质贴图效果,其可将一段连续的图像(可能是实时运算所得或是来自一个video档案)以材质的方式做处理然后贴到3D物体的表面上,如图12:

● Multi-channel keyframe tracks:对于一些动画的角色而言通常会有一些AnimationTrack来驱动角色的动画。为了有效表示这样的动画,M3G 2.0支持单一的数据序列可以有多组的keyframe数据(multiple channels),此多组keyframe数据可以共享相同的时间值,当这些多组的keyframe数据要驱动相同的动画属性时仅仅需要一个AnimationTrack对象即可,如此可降低内存使用量。

● Point Sprite:Point Sprite可以藉由绘制一个3D的点将一张2D贴图绘制在屏幕的任何一个地方,利用Point Sprite可以实作粒子效果,大量的粒子在屏幕上移动可以产生许多视觉的粒子效果,诸如:烟、火以及水花,如图13所示:

在还没有Point Sprite之前要制作粒子效果通常是利用绘制大量有贴图的四边形(quads),但必须要对每个粒子做旋转以确保每个粒子都有面对视点(view-point),此项操作十分耗费效能。透过Point Sprite可以允许利用单一个3D的点来绘制贴图的四边形,如此本来每个粒子需要传送4个点到硬件,现在使用Point Sprite只需要一个点。这样降低内存使用量,也可不需要对每个粒子做旋转以确保每个粒子都有面对视点(view-point)。
● TextureCombiner:“Core Block”中新增的TextureCombiner功能对Texture Blending有更多的弹性。例如:新的”DOT3” Combiner Function可用来实作Bump Mapping,如图14所示:

一般来说 Bump Mapping是需要在每个像数点来计算光的颜色,而一般OpenGL ES内部的固定绘制扁平电缆光的颜色是在顶点做处理,如此必须自行撰写Fragment Shader来达到Bump Mapping的效果,而TextureCombiner可以让程序设计师不需要撰写Shader。意即使用固定绘制扁平电缆,即可有Bump Mapping的效果。”DOT3” Combiner Function亦可用来实作Toon Shading ,如图15所示:

Toon Shading可利用一张Normal Map Texture来实作。首先透光源位置和眼睛位置分别计算出光的方向以及视线的方向,再利用Normal Map和光的方向经过 DOT3计算出Lighting Diffuse的颜色,并利用Normal Map和视线的方向经过 DOT3计算出的Scale值来决定轮廓的走向及宽度,如此可实现将一个3D对象利用近似手绘的卡通图案来呈现。

而“Advanced Block”主要是针对有可程序化的绘图硬件所设计,可让程序员自行撰写ESSL shader程序,其主要功能介绍如下:
● AppearanceBase:一个抽象的基础类别,底下直接继承的类别为固定式绘图扁平电缆的”Appearance”类别以及新的”ShaderAppearance”类别,若一个对象的Appearance为固定式绘图扁平电缆 “Appearance”的话那就会使用固定式绘图扁平电缆来绘制此对象,如果是新的”ShaderAppearance”的话就会使用程序员自行撰写的shader来绘制此对象,如此可增加弹性。

ImageCube与TextureCube:一个Cube Map Image会有6张影像,它能够附加在一个TextureCube上来构成Cube Map的6个面。Cube Map在实时shader的应用十分广泛,透过视线和法线计算出的反射光线来查询Cube Map可以创造一个真实的反射镜面体,利用视线和法线计算出的折射光线来查询Cube Map可以创造一个透明体,如图16:

● RenderPass:RenderPass可加在场景中任何一个节点上,使得系统在绘制整个场景之前,可以先对节点执行一些附加的绘制。此新增的功能可以用在程序性的贴图产生、以及整个场景的后处理等等,当要绘制一个炫丽的对象时,除了本身的贴图以外,如果还必须呈现光的反射以及该物体反射周遭环境的情况时,就必须对此对象作多重的绘制。图17为一个例子:
在此范例中会对该对象作3次的绘制:首先是本身的贴图部分(Beauty Pass),再来是该对象要呈现对周遭光反射的部分(Highlight Pass),最后是该对象要呈现对周遭环境反射的部分(Reflection Pass)。

● VertexShader:此类别用来管理利用ESSL所撰写的”Vertex Shader”程序。Vertex Shader可让程序设计师控制每个顶点的数据,诸如顶点位置、顶点颜色以及顶点法向量等等。程序设计师想实作Vertex Lighting以及将顶点在不同的空间中做转换等运算都可以在Vertex Shader中进行。

● FragmentShader:此类别用来管理利用ESSL撰写的”Fragment Shader”程序。Fragment Shader可让程序员控制每个fragment如何绘制在屏幕上,诸如Texture Blending、Alpha Testing、Fog、pixel discard以及Bump Mapping和Cube Mapping等等。
● ShaderProgram:此类别用来管理一个OpenGL ES 2.0的Shader Program,包含已经编译过并完成连结(Link)的Vertex Shader以及Fragment Shader。

● ShaderAppearance:用来定义一个Mesh的外观,其内含一个ShaderProgram以及一组ShaderUniforms。ShaderProgram内部存有编译和连结好的Vertex Shader以及Fragment Shader,而ShaderUniforms主要是存放shader会使用到的一些参数,其意义与前面所讨论的ESSL之uniform一样。当Mesh的外观设定为ShaderAppearance即表示在绘制此Mesh时是利用内部使用者所撰写的Vertex Shader以及Fragment Shader。此外在ShaderAppearance中Texture算是一种特殊型态的Uniforms,shader内部会根据设定的Texture sampler的值取得相对应的Texture Unit来做存取,其他还包括Polygon Mode、LineMode以及CompositingMode用来描述Mesh的几何属性以及记录每个点(pixel)的属性。 

结语

OpenGL ES 2.0标准规格的制定,正式宣告了移动装置将走上游戏机所能支持之视觉质量的时代,日后手机上将会出现视觉效果媲美XBox的游戏。而JSR297的制定则进一步表示,在Java这一个共同执行平台的加持下,游戏厂商发展移动游戏的门坎与风险将会降低,而消费者在移动装置上享受高质量之移动游戏的日子将指日可待。