3.3 抽象渲染API(一)
这章描述抽象基类Renderer,它担当着作为绘图对象的资源管理器的作用。接口相当多,因此,我会一次列出接口的一部分和描述每部分的作用,而不是把整个类结构的代码块都展示出来。
3.3.1 构造和析构
基类Renderer是抽象的,因为它仅有的构造函数是保护的,并且它指定了相当一些纯虚函数,这些纯虚函数必须由继承类实现。相关的构建,销毁以及传递给构造函数的信息的存取函数成员,已经在下面列出来。
Renderer( FormatType, DepthType, StencilType, BufferingType, MultiSamplingType, iWidth, iHeight );
virtual ~Renderer();
FormatType GetFormatType() const;
DepthType GetDepthType() const;
StencilType GetStencilType() const;
BufferingType GetBufferingType() const;
MultiSamplingType GetMultiSamplingType() const;
int GetWidth() const;
int GetHeight() const;
enum
{
OPENGL,
DIRECTX,
SOFTWARE,
MAX_RENDERER_TYPES
};
virtual int GetType() const = 0;
构造函数接受的输入描述了应用程序需要什么样的帧缓存成分。典型的格式是32位RGBA或24位RGB。深度类型为深度缓存指定位数,当前要么是没有,16,24或32。如果你计划为图形系统创建继承类支持一种不同的位数(例如,SGI 02机器有一种15位格式),那么你必须添加新常量到DepthType枚举。模板类型为模板缓存指定位数,当前是没有或者8。缓冲类型要么是单或者双。对于实时应用程序,你需要双缓冲。多重采样类型指的是光栅化期间每像素产生多次采样的能力。你可选的采样次数是没有,2或者4。缓存的宽和高也同样在构造的时候指定。
提供了宽,高和帧缓存参数的成员存取器。同时列在一起的是函数GetType,一种简单的运行时类型信息(RTTI)形式。引擎当前支持的仅有的继承类是OpenGLRenderer(枚举OPENGL),Dx9Renderer(枚举DIRECTX)和SoftRenderer(枚举SOFTWARE)。OpenGL和software渲染器运行在微软Windows之外的平台。为了提供少量平台相关的特性,每个平台都从这些类里继承新的类。例如,微软Windows OpenGL渲染器是类WglRenderer,它继承自OpenGLRenderer。抽象渲染API不需要知道这层继承,除了渲染器是基于OpenGL;这样,除了列在这里的,就没有其他的枚举。
3.3.2 摄像机管理
画任何东西,为了定义要渲染的空间范围以及定义视模型(透视或正交),渲染器需要一个摄像机。摄像机相关的接口有
void SetCamera( Camera* );
Camera* GetCamera() const;
virtual void OnFrameChange();
virtual void OnFrustumChange();
virtual void OnViewportChange() = 0;
SetCamera函数告诉渲染器使用指定的摄像机绘图。这个调用在渲染器和摄像机之间建立了双路通讯——渲染器拥有一个摄像机的指针,反过来摄像机也拥有一个渲染器的指针。当摄像机移动了,重定位了,或者它的平截头体参数改变了,通过函数OnFrameChange,OnFrustumChange和OnViewportChange,渲染器会被自动通知到。在Wild Magic 3,渲染器继承类得去实现所有这3种情况,调用图形API,更新与摄像机坐标系统,摄像机视模型和视口相关联的齐次矩阵。2.8章作出的论断允许我按照自己的方法设置矩阵,而不是按照图形API通过它方便的函数进行设置的那种方式,因此,矩阵的存储和很多矩阵操作被移出继承类而放置在基类。世界矩阵,视矩阵和投影矩阵在渲染器里有显式的保存。继承类有少量工作去做,也就是,告诉图形API它们的是什么矩阵。
3.3.3 全局状态管理
渲染器维护着一份当前起作用的的全局状态列表,这些状态有alpha混合,剔除(怎样剔除三角形),雾,材质,多边形偏移(怎样处理深度混叠问题),模板,线框和深度缓冲。这些存取器有
virtual void SetAlphaState( AlphaState* );
virtual void SetCullState( CullState* );
virtual void SetFogState( FogState* );
virtual void SetMaterialState( MaterialState* );
virtual void SetPolygonOffsetState( PolygonOffsetState* );
virtual void SetStencilState( StencilState* );
virtual void SetWireframeState( WireframeState* );
virtual void SetZBufferState( ZBufferState* );
AlphaState* GetAlphaState();
CullState* GetCullState();
FogState* GetFogState();
MaterialState* GetMaterialState();
PolygonOffsetState* GetPolygonOffsetState();
StencilState* GetStencilState();
WireframeState* GetWireframeState();
ZBufferState* GEtZBufferState();
void SetReverseCullFace( bReverseCullFace );
bool GetReverseCullFace() const;
void SetGlobalState( GlobalStatePtr[] );
void RestoreGlobalState( GlobalStatePtr[] );
void SetLight( int, Light* );
Light* GetLight( int );
通过存取当前起作用状态的指针数组,全局状态的Set/Get函数都是直截了当的。凭借着镜像图像的三角形顶点顺序与正常图像顺序相反,SetReverseCullFace和GetReverseCullFace函数是被用在涉及到镜像的特别效果上。
SetGlobalState和GetGlobalState函数在绘图系统里是作为内部使用的。绘图中,它们的使用是基于每个对象的。特别地,如果一个对象它自己有全局状态附加其上,那么在绘图期间,这些函数会覆盖掉当前起作用的全局状态,但,绘图后,之前的全局状态会被恢复。
SetLight和GetLight函数是一个简单的Light对象指针数组的存取器。同样,这些函数也作为内部使用的,当绘制对象时,用在需要有光照支持的特殊效果上。
3.3.4 缓存清除
在绘制一个对象集之前,组成帧缓存的各种缓存可能需要清除。相关的接口列在这里。
virtual void SetBackgroundColor( const ColorRGBA& );
const ColorRGBA& GetBackgroundColor() const;
virtual void ClearBackBuffer() = 0;
virtual void ClearZBuffer() = 0;
virtual void ClearStencilBuffer() = 0;
virtual void ClearBuffers() = 0;
virtual void DisplayBackBuffer() = 0;
virtual void ClearBackBuffer( iXPos, iYPos, iWidth, iHeight ) = 0;
virtual void ClearZBuffer( iXPos, iYPos, iWidth, iHeight ) = 0;
virtual void ClearStencilBuffer( iXPos, iYPos, iWidth, iHeight ) = 0;
virtual void ClearBackBuffers( iXPos, iYPos, iWidth, iHeight ) = 0;
典型地,所有缓存都被清除。双缓存渲染意味着渲染场景到一个后缓存(一个颜色缓存),随后交换到前缓存(显示在屏幕的颜色缓存)。这个交换通过DisplayBackBuffer的调用而发生。ClearBackBuffer函数清除后缓存,设置它的值为当前背景颜色。你可以用SetBackgroundColor来设置背景颜色。深度缓存通过ClearZBuffer函数来清除,而模板缓存通过ClearStencilBuffer函数。所有这三个缓存的清除可以通过ClearBuffers函数。第一块里面的清除函数是用于清除整个缓存,但第二块里面的清除函数是用于情况缓存的一个子矩形。
典型的操作顺序是
clear all buffers;
render the scene;
display back buffer;
然而,这不是规定的。举个例子,如果你有一个填充整个窗口的天空穹,那么场景物体是在天空穹渲染之后才渲染,你并没有必要去清除后缓存。你也可能有一层预生成的静态几何体,它产生颜色缓存和深度缓存。意图是,这些缓存从磁盘里加载,并且每帧通过图形API的调用写到硬件。然后,另外的(动态的)物体才被渲染。这种情况下,你不需要清除后缓存或者深度缓存。
清除缓存和交换后缓存到前缓存是平台和API特定的操作,因此,虚函数是纯的,并且继承类必须实现它们。
3.3.5 物体绘制
物体的绘制通过下面的接口函数提供:
virtual bool BeginScene();
virtual void EndScene();
void DrawScene( VisibleSet& );
void Draw( Geometry* );
virtual void DrawElements() = 0;
void ApplyEffect( ShaderEffect*, rbPrimaryEffect );
BeginScene和EndScene函数界限了包含有绘制调用的代码块。一些图形API可能需要绘制前设置以及绘制或清理。实例程序里面的主绘制例程是DrawScene。输入是潜在可见物体集。这个集合是由场景图剔除系统产生的;细节查看4.5章。Draw函数由DrawScene为每个场景里面的几何图元间接调用,但Draw是一个公开的函数,它能用作绘制屏幕空间多边形;例如,绘制在已渲染场景上面的GUI元素。ApplyEffect函数仅仅是由Draw调用。正在绘制的物体可以拥有多重特殊效果附加在其上面。ApplyEffect函数为每个效果而被调用。当光照起作用的时候它也被调用。DrawElements函数由每个渲染器继承类实现。当几何图元的所有资源都加载和启用完毕后,它就会被调用。
Draw和ApplyEffect函数在3.4章里会有更细节的讨论。它们隐藏了许多绘图子系统,包括加载和启用资源,构造顶点和索引缓存,使着色程序有效,设置着色常量和创建纹理采样器。
3.3.6 文本和2D绘制
在渲染的场景上面显示文本是很普通的。渲染器API有一些基本的函数来支持这个功能。
virtual int LoadFont( acFace, iSize, bBold, bItalic ) = 0;
virtual void UnloadFont( iFontId ) = 0;
virtual bool SelectFont( iFontId ) = 0;
virtual void Draw( iX, iY, rkColor, acText ) = 0;
virtual void Draw( aucBuffer ) = 0;
提供了最少量的字体处理。WglRenderer和Dx9Renderer类使用微软Windows API调用来选择字体。AglRenderer类(Macintosh)拥有唯一一种默认字体;还没有加入选择其他字体的支持。GlxRenderer类(Linux/Unix)使用位图字体。与视窗系统的对接不是这本书的重点。
最后一个函数Draw允许你写一个已经创建的颜色缓存到后缓存。我仅仅使用它来支持2D图形程序。渲染器写2D图元到一个内存缓存,它然后被传递给Draw和交换到前缓存。如果你想看细节的话,实例里有一些2D程序。2D系统仅有最少量支持来绘制图元(点,线段,圆,矩形,文本)。
3.3.7 杂项
下面列出的函数支持各种有趣的操作。
virtual void SetColorMask( bAllowRed, bAllowGreen, bAllowBlue, bAllowAlpha );
virtual void GetColorMask( &bAllowRed, &bAllowGreen, &bAllowBlue, &bAllowAlpha );
virtual void SetDepthRange( fZMin, fZMax ) = 0;
virtual void EnableUserClipPlane( int, const Plane3f& ) = 0;
virtual void DisableUserClipPlane( int ) = 0;
virtual void SetPostWorldTransformation( const Matrix4f& );
virtual void RestorePostWorldTransformation();
virtual void SetWorldTransformation();
virtual void RestoreWorldTransformation();
void SetProjector( Camera* );
Camera* GetProjector();
virtual const char* GetExtension() const = 0;
virtual char GetCommentCharacter() const = 0;
SetColorMask和GetColorMask函数允许你指定哪个颜色通道可以被像素着色器更新;细节可以查看3.1.11。
SetDepthRange函数允许你改变默认的深度范围[0,1]来完成一些不同的事情。这在某些特效里面很有用;例如,PlanarReflections实例设置深度范围为[1,1],因此,当渲染包含有镜子的平面时,深度缓存的值被设置为最大深度。这允许其它物体,当它们在镜子上面(或前面)绘制时,能被正确地(在深度)绘制。
六个平截头体面用作裁剪,但用户可以指定额外的裁剪平面。EnableUserClipPlane和DisableUserClipPlane函数允许你指定裁剪平面。输入的平面一定要在模型坐标。它会被内部转换到摄像机坐标来支持裁剪空间裁剪。PlanarReflections实例同样也操作用户自定义裁剪平面。
模型到裁剪空间转换是一个应用到模型空间点的组合,
XmodelHworldHviewHproj
在进一步在视空间或裁剪空间处理之前,一些特效需要转换世界空间的点。这可以通过在组合里插入额外的转换来完成,
XmodelHworldHpostworldHviewHproj
效果会在它需要的时候提供Hworld。SetPostWorldTransformation和RestorePostWorldTransformation函数是钩子,它们允许你指定如此一个转换。PlanarReflections实例使用这个机制来将一个反射矩阵插入到组合里。PlanarShadows实例使用这个机制来将一个投影矩阵插入到组合里。这个矩阵是相对于产生阴影的光源的投影。
SetWorldTransformation和RestoreWorldTransformation函数是由Draw内部使用。它们设置保存在Renderer里面的世界矩阵。继承类也要将新的世界矩阵通知图形API。SetProjector和GetProjector函数用在特效里,这些特效需要通知渲染器一个投射源,例如投射纹理,投射阴影和阴影贴图。
GetExtension和GetCommentCharacter函数是由继承类实现,用于为从磁盘加载和分析着色程序提供信息。所有着色程序拥有一个.wmsp(Wild Magic着色程序)扩展名,但每种图形API拥有它自己版本的程序。OpenGL版本拥有复合扩展名.ogl.wmsp。这个渲染器的GetExtension函数返回ogl。Direct3D渲染器函数返回dx9,软件渲染器返回sft。着色程序被分析以获得相关信息,这些信息有输入变量,输出变量和着色常量,包括哪些寄存器已经被分配给它们。这些信息出现在程序文件的注释里,每个注释行以一个特定的字符开始。这个字符在调用GetCommentCharacter时返回。OpenGL渲染器返回字符#。Direct3D和软件渲染器返回字符/。