文档简述: 随着显卡的飞速发展,更快的速度以及越来越多的新功能为硬件所支持,硬件的进步使得图形程序开发人员可以创造出更加绚丽的视觉效果,现在,电影级动画的实时渲染已不再是梦想。我们怎么在OpenGL中利用显卡的新特性呢?答案就是OpenGL扩展。 注:如不作特别说明,本站文章中的显卡均指面向普通用户的非专业显卡。
文档目录: OpenGL扩展 显卡差异 顶点/片断编程 Cg/RenderMonkey/及其他
文档内容:
OpenGL扩展(OpenGL Extensions)
OpenGL和Direct3D比较起来,最大的一个长处就是其扩展机制。硬件厂商开发出一个新功能,可以针对新功能开发OpenGL扩展,软件开发人员通过这个扩展就可以使用新的硬件功能。所以虽然显卡的发展速度比OpenGL版本更新速度快得多,但程序员仍然可以通过OpenGL使用最新的硬件功能。而Direct3D则没有扩展机制,硬件的新功能要等到微软发布新版DirectX后才可能支持。
OpenGL扩展也不是没有缺点,正因为各个硬件厂商都可以开发自己的扩展,所以扩展的数目比较大,而且有点混乱,有些扩展实现的相同的功能,可因为是不同厂商开发的,接口却不一样,所以程序中为了实现这个功能,往往要为不同的显卡写不同的程序。这个问题在OpenGL 2.0出来后可能会得到解决,OpenGL 2.0的一个目标就是统一扩展,减少扩展数目。
扩展名
每个扩展都有一个扩展名,扩展名类似如下形式: GL_ARB_multitexture
第一段GL,用来表示针对OpenGL哪部分开发的扩展,有以下几个值:
第二段ARB,用来表示是谁开发的这个扩展,常见以下几个值:
-
ARB – 经OpenGL Architecture Review Board(OpenGL管理机构)正式核准的扩展,往往由厂商开发的扩展发展而来,如果同时存在厂商开发的扩展和ARB扩展,应该优先使用ARB扩展
-
EXT – 被多个硬件厂商支持的扩展
-
NV – nVIDIA 公司开发的扩展
-
ATI – ATI公司开发的扩展
-
ATIX– ATI公司开发的实验性扩展
-
SGI – Silicon Graphics(SGI)公司开发的扩展
-
SGIX– Silicon Graphics(SGI)公司开发的实验性扩展
第三段multitexture就是真正的扩展名了,如multitexture就是多重纹理扩展。 使用OpenGL扩展 要使用一个OpenGL扩展,首先必须检查显卡是否支持这个扩展,以下代码可以获取一个显卡支持的的OpenGL扩展: const char *str = glGetString( GL_EXTENSIONS ); 函数返回一个字符串指针,这个字符串就是显卡所支持的所有扩展的扩展名,不同的扩展名之间用空格隔开,形如: "GL_ARB_imaging GL_ARB_multitexture GL_ARB_point_parameters ……" OpenGL扩展往往都会新增一些函数,在Windows平台上,这些函数不是通过.lib库连接到程序里的,而要在运行时动态获得函数的指针。我们以GL_ARB_point_parameters扩展为例看看怎么获得函数指针。 首先要定义函数指针类型, typedef void (APIENTRY * PFNGLPOINTPARAMETERFARBPROC)(GLenum pname, GLfloat param); typedef void (APIENTRY * PFNGLPOINTPARAMETERFVARBPROC)(GLenum pname, const GLfloat *params); 这个工作SGI已经为我们做好,它提供了一个头文件 glext.h ,里面有目前绝大多数扩展的常量和函数指针定义,下载下来放到编译器的include/GL文件夹下面,然后在程序里面加上: #include 就可以在程序中使用常量和函数指针类型了。 然后要定义函数指针: PFNGLPOINTPARAMETERFARBPROC glPointParameterfARB; PFNGLPOINTPARAMETERFVARBPROC glPointParameterfvARB; 再检查显卡是否支持GL_ARB_point_parameters扩展,其中isExtensionSupported是自定义的一个函数,就是在glGetString( GL_EXTENSIONS )返回的字符串里查找是否存在指定的扩展名: int hasPointParams = isExtensionSupported("GL_ARB_point_parameters"); 如果支持,就可以用wglGetProcAddress函数获取扩展函数的指针: if (hasPointParams) { glPointParameterfARB = (PFNGLPOINTPARAMETERFARBPROC)\ wglGetProcAddress( "glPointParameterfEXT" ); glPointParameterfvARB = (PFNGLPOINTPARAMETERFVARBPROC) \ wglGetProcAddress( "glPointParameterfvEXT" ); } 最后就可以在程序里使用扩展函数: if (hasPointParams) { static GLfloat quadratic[3] = { 0.25, 0.0, 1/60.0 }; glPointParameterfvARB(GL_DISTANCE_ATTENUATION_ARB, quadratic); glPointParameterfARB(GL_POINT_FADE_THRESHOLD_SIZE_ARB, 1.0); } WGL扩展 glGetString( GL_EXTENSIONS )取得的扩展字符串中并不包括针对Windows平台的WGL扩展,WGL扩展串要通过WGL_ARB_extensions_string扩展来获得,以下代码演示了如何获得WGL扩展串: 定义WGL_ARB_extensions_string扩展新增函数wglGetExtensionsStringARB的函数指针类型,同样这个工作SGI已经为我们做好,只不过不在glext.h中,而在它提供的另外一个头文件 wglext.h 中: typedef const char *(APIENTRY * PFNWGLGETEXTENSIONSSTRINGARBPROC)( HDC hdc); 定义函数指针: PFNWGLGETEXTENSIONSSTRINGARBPROC wglGetExtensionsStringARB; 检查是否支持WGL_ARB_extensions_string扩展,如果不支持,表示这个显卡不支持WGL扩展,如果支持,则得到wglGetExtensionsStringARB函数的指针,并调用它得到WGL扩展串: int hasWGLext = isExtensionSupported("WGL_ARB_extensions_string"); if (hasWGLext) { wglGetExtensionsStringARB = (PFNWGLGETEXTENSIONSSTRINGARBPROC) \ wglGetProcAddress( "wglGetExtensionsStringARB" ); const char *wglExt = wglGetExtensionsStringARB( hdc ); …… } OpenGL版本 一些常用的OpenGL扩展会在新版的OpenGL中加到OpenGL核心中去,成为OpenGL标准的一部分,可以简化程序开发,程序员使用这些功能时不必做繁琐的扩展初始化工作。比如多重纹理功能,在OpenGL1.2.1加入到OpenGL核心中,以前要使用多重纹理,要先检查是否支持GL_ARB_multitexture扩展,然后初始化glActiveTextureARB等函数,很麻烦,而OpenGL1.2后,则可以直接使用glActiveTexture函数。 不过,这种简化只有Mac/Unix/Linux程序员才能享受到,在Windows平台上没有这么简单。微软为了维护Direct3D,对OpenGL的支持很消极,其OpenGL实现仍然是1.1。由于Windows上的OpenGL程序最终都会动态链接到微软的OpenGL32.dll,可OpenGL32.dll只支持OpenGL 1.1,使我们不能直接使用新版OpenGL,仍然要用扩展访问OpenGL1.1以来新增的功能。 OpenGL扩展资料 All About OpenGL Extensions : 讨论OpenGL扩展机制,讲述了如何阅读扩展官方说明书,并举了一些扩展的例子。必读。 OpenGL Extension Registry : 由SGI维护,列出了目前公开的所有扩展及其官方说明书。
OpenGL Hardware Registry : 由Delphi3D.net维护,列出了目前几乎所有3D加速卡的OpenGL硬件信息,包括其支持的扩展。当然,这里面列的扩展不能作为程序的依据,程序中要使用某个扩展,还是要先检查显卡是否支持。因为同样的显卡,如果驱动程序不同,支持的扩展也不相同,往往新的驱动程序会加入新的扩展,丢掉一些废弃的扩展。
OpenGL硬件加速
在Windows平台上,OpenGL驱动可能有三种模式:纯软件、MCD和ICD:
-
纯软件模式:微软提供一个OpenGL的软件实现,所有渲染操作均由CPU完成,速度很慢。如果安装系统时使用Windows自带的显卡驱动程序,那么OpenGL程序就会运行在软件模式下。而且由于微软有自己的Direct3D,所以对OpenGL的支持很消极,它的OpenGL纯软件实现只支持OpenGL1.1,而目前OpenGL的最新版本为1.4
-
MCD(Mini Client Driver):MCD是早期微软在Windows NT上支持OpenGL时,为了简化驱动开发时使用的一个模型。在这个模型中,OpenGL渲染管线的变换、光照部分仍然由软件实现,而光栅化部分则由硬件厂商实现,因此只要硬件支持,MCD可以硬件加速光栅化部分。MCD虽然可以简化驱动开发,但是功能限制太大,现在市面上的3D加速卡均支持硬件变换和光照,MCD却不能利用这一特性,看上去MCD已经没有存在的价值
-
ICD(Installable Client Driver):ICD是一个完整的OpenGL驱动模型,比MCD复杂得多。硬件厂商要实现完整的OpenGL渲染管线,如变换、光照、光栅化等,因此只要硬件支持,ICD可以硬件加速整个OpenGL渲染管线。我们通常说的OpenGL硬件加速就是指的通过ICD模型获得的硬件加速,而现在硬件厂商提供的OpenGL驱动程序也都是依照ICD模型开发的。主要硬件厂商的ICD已经可以支持OpenGL的最新版1.4
Windows怎么实现OpenGL硬件加速呢?OpenGL32.dll是微软的OpenGL 1.1纯软件实现,我们的程序都要动态链接到这个dll。如果安装3D芯片厂商的驱动程序,会将一个不同名字的dll放到Windows系统目录下,比如在Windows 2000下安装nVIDIA GeForce2 MX的驱动程序,会在系统目录下放一个nvoglnt.dll(这就是nVIDIA的OpenGL驱动),并在注册表中登记nvoglnt.dll,让Windows知道硬件加速OpenGL驱动的名字,以后运行OpenGL程序,OpenGL32.dll就会把OpenGL调用直接转到nvoglnt.dll。
Windows平台上,一个OpenGL程序是否使用硬件加速由三个因素决定,这三个因素缺一不可,否则程序都会运行于纯软件模式:
判断一种像素格式是否被显卡硬件所支持,可以用函数DescribePixelFormat取得该像素格式的数据,然后看结构体PIXELFORMATDESCRIPTOR中的dwFlags的值,如果
-
PFD_GENERIC_FORMAT被置1,并且PFD_GENERIC_ACCELERATED被置0,即 (pfd.dwFlags & PFD_GENERIC_FORMAT) && !(pfd.dwFlags & PFD_GENERIC_ACCELERATED) 表明该像素格式不被显卡硬件支持,使用该像素格式的OpenGL程序将使用纯软件模式渲染
-
PFD_GENERIC_FORMAT被置1,并且PFD_GENERIC_ACCELERATED被置1,即 (pfd.dwFlags & PFD_GENERIC_FORMAT) && (pfd.dwFlags & PFD_GENERIC_ACCELERATED) 表明该像素格式被显卡硬件支持,并且程序使用MCD模式渲染
-
PFD_GENERIC_FORMAT被置0,并且PFD_GENERIC_ACCELERATED被置0, !(pfd.dwFlags & PFD_GENERIC_FORMAT) && !(pfd.dwFlags & PFD_GENERIC_ACCELERATED) 表明该像素格式被显卡硬件支持,并且程序使用ICD模式渲染
显卡差异 正如前面所说,不同的显卡厂商可能会为相同的功能开发不同的OpenGL扩展,使得OpenGL扩展的编程复杂化,就算实现相同的功能也可能需要为不同的显卡开发不同的程序。好在现在显卡芯片市场只有nVIDIA和ATI两家当道,所以工作量也不会太大。而OpenGL 2.0推出后,这种情况会大为改观。 游戏的运行环境差异很大,可能运行于只具备基本3D加速能力的老显卡上,也可能运行于最新推出的功能强大的3D加速卡上,而且显卡芯片的厂商也各不相同。对于显卡较差的机器,要保证游戏能运行,而对于较好的显卡,要充分发挥它的功能,创造绚丽的图形效果。这就是说游戏需要多条执行路径,运行时根据显卡配置选择不同的执行路径。怎么决定需要多少种执行路径呢? 设计执行路径要考虑两个主要因素:
现在3D显卡芯片市场有两个主要厂商nVIDIA和ATI,它们各自的OpenGL扩展也最多,必须为它们设计相应的执行路径。对于其他厂商,因为几乎所有厂商都会尽量支持ARB扩展,可以设计执行路径,使用各个厂商都支持的ARB扩展。
显卡芯片都会有个芯片代号,类似于软件的版本号。相同主版本号的芯片属于一个档次,支持的功能往往一样。设计图形程序时,可以根据芯片代号来决定需要设计多少种执行路径。要注意的是必须要设计一条和显卡无关的,只使用OpenGL基本功能的执行路径,使得程序能够在低端显卡上运行。
现在可能使用的nVIDIA和ATI的显卡芯片代号和显卡型号的对应关系如下表:
nVIDIA |
|
ATI |
芯片代号 |
显卡型号 |
|
芯片代号 |
显卡型号 |
NV1 NV2 NV3 NV4 NV5 |
NV1 Riva 128 Riva 128ZX Riva TnT Riva TnT2 |
|
RAGE(?) |
RAGE PRO RAGE 128 RAGE 128 PRO |
NV10 NV11 NV15 NV17 NV18 |
GeForce 256 GeForce2 MX * GeForce2 * GeForce4 MX * GeForce4 MX AGP8X * |
|
R100
RV200 |
RADEON 7000 RADEON RADEON 7200 RADEON 7500 |
NV20
NV25 NV28 |
Geforce3 Ti200 GeForce3 GeForce3 Ti500 GeForce4 Ti * GeForce4 Ti AGP 8X * |
|
RV250 R200
RV280 |
RADEON 9000 RADEON 8500 RADEON 9100 RADEON 9200 AGP 8X |
NV34 NV31 NV30 NV35 |
GeForce FX 5200 GeForce FX 5600 GeForce FX 5800 GeForce FX 5900 |
|
RV300 RV350 R300 R350 |
RADEON 9500 RADEON 9600 RADEON 9700 RADEON 9800 |
·其中标注 * 的是产品系列,型号还会细分 ·通常大家习惯用芯片代号的主版本号来统称一代芯片,例如用NV20统称NV20/NV25/NV28,用R200统称RV250/R200/RV280 ·虽然ATI RADEON 7500的芯片型号是RV200,但实际属于R100档次的产品 我们来看一个执行路径的例子,idSoftware即将推出的Doom3的执行路径。 Doom3一共有6条执行路径:
-
ARB:几乎不使用扩展,没有镜面高光效果,没有顶点编程(vertex program),保证程序能在低端显卡上运行
-
NV10:支持所有功能,每帧需要渲染5个pass(five rendering passes,指一个对象多次送入渲染管道,比如第一次渲染漫射光diffuse,第二次渲染镜面高光specular ……,各次渲染结果通过glBlend混合在一起形成最终效果),没有顶点编程
-
NV20:支持所有功能,2或3个pass
-
NV30:支持所有功能,1个pass
-
R200:支持所有功能,大部分情况下只需1个pass
-
ARB2:支持所有功能,浮点片断编程(fragment program),1个pass
nVIDIA显卡可能执行ARB / NV10 / NV20 / NV30 / ARB2 五条路径,其中NV10 / NV20 / NV30 专门针对不同档次的nVIDIA显卡开发的。 ATI显卡可能执行 ARB / R200 / ARB2 三条路径,其中R200是专门针对ATI开发的。 而其他显卡则根据档次高低执行ARB或ARB2路径。ARB用于低端显卡,ARB2用于高端显卡。
顶点/片断编程 1999年底,nVIDIA推出GeForce 256,并开始使用GPU(Graphics Processing Unit)来称呼显卡芯片(也有厂商叫VPU(Visual Processing Unit),是一回事)。GeForce 256最大卖点是硬件T&L(Transform&Lighting,变换和光照),这意味着变换和光照计算可以在GPU中进行,大大减轻了CPU的压力,显卡开始成为独立于CPU的一个处理器。 硬件T&L后,GPU最激动人心的进步就是引入了可编程能力。我们知道,OpenGL和Direct3D都有固定的渲染管线,定义光源,送入顶点位置、法线、纹理坐标等,就可以给你渲染出一幅图像来,程序员对具体的渲染过程无法控制。而OpenGL扩展和DirectX8给渲染管线引入可编程能力,图形软件开发人员可以编写运行于显卡芯片的汇编程序来控制具体的渲染过程,这给予图形软件开发更大的灵活性,并且由于硬件的支持,获得这些灵活性并不会牺牲性能。GPU的可编程能力对实时图形渲染会产生深远的影响。 OpenGL支持两种可编程模型:
目前顶点编程相关的扩展有:
-
GL_NV_vertex_program:nVIDIA NV10档次显卡用软件模拟,NV20及以上档次显卡上硬件支持
-
GL_NV_vertex_program1_1:nVIDIA NV10档次显卡用软件模拟,NV20及以上档次显卡上硬件支持
-
GL_EXT_vertex_shader:在ATI R200及以上档次显卡上支持
-
GL_ARB_vertex_program:由上面三个扩展发展而来,支持上面三个扩展的显卡安装最新的驱动程序后都会支持此扩展,所以程序中不必支持上面三个扩展,只支持此扩展就可以了。此扩展不支持分支循环
-
GL_NV_vertex_program2:在nVIDIA NV30及以上档次显卡上支持,支持分支循环,估计会成为GL_ARB_vertex_program2扩展的原型
-
实际上ATI R300级别的显卡已经在顶点编程中支持分支循环(硬件支持DirectX9的vs2.0),但并没有开发扩展提供给OpenGL程序员使用,估计是在等支持分支循环的GL_ARB_vertex_program2扩展出台
可见现在使用OpenGL的vertex program,只需支持GL_ARB_vertex_program和GL_NV_vertex_program2两个扩展。
目前片断编程相关的扩展有:
-
GL_NV_register_combiners:在nVIDIA NV10及以上档次显卡上支持,处理逐像素颜色及雾效计算
-
GL_NV_register_combiners2:在nVIDIA NV20及以上档次显卡上支持,对GL_NV_register_combiners作了个简单的扩展,支持两个常量寄存器在每级combiner都可以有不同的值
-
GL_NV_texture_shader / GL_NV_texture_shader2 / GL_NV_texture_shader3:在nVIDIA NV20及以上档次显卡上支持,提供多种功能强大的纹理提取操作
-
GL_ATI_fragment_shader:在ATI R200及以上档次显卡上支持
以上扩展提供的功能不是编写运行于GPU的汇编码,而是通过函数调用的方式实现,不如编写汇编码直观方便。下面几个扩展则可以通过编写汇编码来实现片断编程
-
GL_ATI_text_fragment_shader:实际上就是GL_ATI_fragment_shader的汇编码版本,到现在还没看到在PC上的支持信息,看来只会在苹果机上支持
-
GL_ARB_fragment_program:在nVIDIA NV30 / ATI R300及以上档次显卡上支持
-
GL_NV_fragment_program:在nVIDIA NV30及以上档次显卡上支持,比GL_ARB_fragment_program更强大
与顶点编程相比,片断编程要复杂得多:
-
在NV10系列上,只能使用GL_NV_register_combiners提供的部分片断编程能力
-
在NV20系列上,则可以使用register combiners和texture shader实现片断编程
-
在NV30系列上,可以使用GL_ARB_fragment_program和GL_NV_fragment_program
-
在ATI R200系列上,使用GL_ATI_fragment_shader
-
在ATI R300系列上,使用GL_ARB_fragment_program
看到这里我们不难理解为什么Doom3会将渲染执行路径分成ARB、NV10、NV20、NV30、R200、ARB2几条了:
-
ARB既没有顶点编程也没有片断编程
-
NV10没有用到顶点编程(虽然NV10支持顶点编程,但是是软件模拟,而显卡硬件并不支持),但用到register combiners实现凹凸贴图(Bump Mapping)
-
NV20使用顶点编程,并用register combiners和texture shader实现片断编程
-
NV30使用顶点编程,并用GL_NV_fragment_program实现片断编程
-
R200使用顶点编程,并用GL_ATI_fragment_shader实现片断编程
-
ARB2使用顶点编程,并用GL_ARB_fragment_program实现片断编程
附表:
nVIDIA显卡顶点、片断编程支持情况
芯片代号 |
纹理单元数 |
Register Combiner |
Texture Shader |
Vertex Program |
Fragment Program |
NVX |
2 |
X |
X |
X |
X |
NV10 |
2 |
2级 |
X |
NVvp1.1/ARBvp1.0 |
RC |
NV20 |
4 |
8级 |
支持 |
NVvp1.1/ARBvp1.0 |
RC/TS |
NV30 |
4 |
8级 |
支持 |
NVvp2.0/ARBvp1.0 |
ARBfp1.0/NVfp |
ATI显卡顶点、片断编程支持情况
芯片代号 |
纹理单元数 |
Vertex Program |
Fragment Program |
RAGE |
2 |
X |
X |
R100 |
3 |
X |
X |
R200 |
6 |
EXTvp/ARBvp1.0 |
ATIfp |
R300 |
8 |
EXTvp/ARBvp1.0 |
ARBfp1.0 |
Cg(C for Graphics) 直接使用扩展编写vertex program和fragment program不太方便,要么是函数调用,要么是汇编码,相当于用x86汇编编写PC程序,而现在已经有了面向vertex program和fragment program的高级语言,称为HLSL(高级着色语言,High Level Shading Language)。 Cg是nVIDIA提出的一种高级着色语言,它的语法和C语言类似,可以大大简化vertex program和fragment program的编写。用它写的程序可以:
-
编译成GL_NV_vertex_program / GL_NV_vertex_program1_1 / GL_ARB_vertex_program / GL_NV_vertex_program2 的汇编码
-
编译成GL_ARB_fragment_program / GL_NV_fragment_program的汇编码
-
编译成用于nvParse的RC(Register Combiners)及TS(Texture Shader)脚本
-
直接在程序中调用Cg提供的API,运行Cg程序
-
编译成DirectX的 vertex shader / pixel shader
我们可以看到Cg只是对nVIDIA的产品支持比较好,而其他厂商的产品只有支持GL_ARB_vertex_program/GL_ARB_fragment_program时才能从Cg获得好处,不支持这两个ARB扩展的显卡则不能运行Cg编写的程序,大大降低了Cg的实用性。虽然Cg提供接口,使其他厂商可以对Cg进行扩展,以支持各个厂商自己的OpenGL扩展,不过Cg毕竟是一个企业的产品,别的厂商不会支持,所以如果要写通用的图形程序,Cg并不合适。 况且OpenGL的HLSL——GLslang(OpenGL Shading Language)规范已经被ARB审核通过,估计不久就可以使用GLslang编写vertex program和fragment program,到时Cg的位置应该会相当尴尬,因为OpenGL和DirectX都已经有了自己的HLSL。不过话说回来,Cg可以同时支持OpenGL和DirectX,这也算是它的一个优势。 RenderMonkey RenderMonkey并不是一种语言,而是ATI推出的一个编写调试vertex program和fragment program的集成开发环境,目前只支持DirectX的vertex shader / pixel shader / HLSL,不过ATI正在和3Dlabs合作,不日RenderMonkey也会支持OpenGL vertex program / fragment program / GLslang。另外,RenderMonkey不仅仅是为程序员设计的,美工也可以使用。 nvParse nvParse是nVIDIA公司推出的一个库,可以简化RC(Register Combiners)及TS(Texture Shader)的开发。使用GL_NV_register_combiners和GL_NV_texture_shader扩展实现片断编程全是函数调用的形式,很不方便,而nVIDIA为了简化RC和TS程序开发,建立了一种脚本格式,用简单的脚本代替复杂的GL_NV_register_combiners和GL_NV_texture_shader函数调用,nvParse则提供API用于解释执行RC和TS脚本。 模拟NV30 并非所有程序员都有一块NV30显卡,但nVIDIA最新的雷管驱动程序(version 40.41及以后)支持软件模拟NV30架构,只不过很慢,但对没有NV30显卡的程序员已经是个福音了,只要我们有一块GeForce级的显卡,安装最新的雷管驱动程序,然后下载一个NVEmulate.exe (52 KB),运行它,打开NV30模拟,你的显卡就支持NV30的所有功能了,这样就算没有NV30显卡同样可以针对NV30开发程序。要注意的是,不需要NV30模拟的时候要记得关掉它,毕竟是软件模拟,速度很慢。 后记 写这些只是希望为后来的朋友指一个方向,写得很简略,因为我自己也在学习,hoho,更多的东西还是需要大家自己多看多写 :) 我将来也会慢慢放上一些详细的文章以及源码,希望能对大家有所帮助。关于这篇文章的问题或建议,可以写信给我,我的联系方式。 |