D3D常用API

D3DAPI大全,全部函数

//Direct3D 9.0 SDK 开发参考Direct3D 9.0 SDK 文档 (中文版)

词汇表

DirectX 8 教程

你也可以把 COM 对象就想象成一套为某个主题而设计的一整套库函数。DX 就提供了一套完整的设计3D游戏的库。

http://baike.baidu.com/view/1169027.htm

使用DirectX的不同组件,你需要链接不同的静态库。例如你要使用DirectDraw组件,你就需要ddraw.lib。

对于DirectDraw,这个头文件是ddraw.h。//com编程

D3D.H

http://wowe1314.blog.163.com/blog/static/2358876200751191213583/

在Direct3D编程中,我们要做的工作基本上可以归纳为:

调用适当的函数获取接口指针;

调用接口的方法(成员函数)来完成所需功能;

用完接口后,调用Release方法进行“释放”,注意释放顺序应该和获取它们的顺序相反。

http://www.lihuasoft.net/article/show.php?id=2928

Microsoft_DirectX_9.0c里的 9个DirectX的DLL

DX9和DX10在渲染流水线上都是有天壤之别的,好在DX高版本开发包运行库中包含了对低版本开发包运行库的实现,所以用DX8开发的程序,DX9运行库也能够很好的支持,在安装有D9运行库的系统上跑DX8开发的程序不需要再安装DX8运行库,但是这个兼容性支持在最近被微软逐渐放弃,有时候DX9的不同更新版本做的程序也不能向下兼容,比如DX9FEB2007SDK,同DX9AUG2006SDK在shader编译规则上也是不同的,2007放弃了VS2.0和PS2.0以下版本shader的支持,同时对于HLSL中#include相对路径引用的默认根目录也是有区别的.openGL的shader扩展不同的厂商有不同的扩展开发包,但是这种情况随着GLSL和openGL2.0的出现有所改观.同时OpenGL是跨平台的而DX不是,这意味着用OpenGL和GNU   C++规则开发的程序可以同时在Linux,unix和安装有GNU环境的Windows上同时运行。从效率上来看,DX由于数据时批量写入显存的,同OpenGL的单条函数写入来讲DX效率上要高一些,不过近来OpenGL也支持了批写入,只是支持批写入的OpenGL放弃了openGL一惯的优势也就是语言架构上的简洁使得函数的数目变得很冗杂。在效果上看DX9同支持GLSL或CG扩展的openGL可以实现相同的显示效果。但是有一点不同是DXUT和D3DX在一些基础绘制上比glu和openGL   ARB   Extend要差一点,比如绘制虚线,DX没有好的函数可以是实现这一功能。但是DX的扩展工具比openGL扩展工具又有多余的优势比如向量计算,GUI控件,mesh优化和曲面展开,PRT预计算等等和性能测试等等上又要强一点。DX10同OpenGL比较就感觉openGL不是同一个数量级上的产品,DX10在渲染流水线和架构上和能够实现的效果上要比DX9和openGL进步的多。要做面向未来的游戏产品尽量还是用DX10吧。

­

LPDIRECT3D9 D3D主接口

LPDIRECT3DDEVICE9 D3D硬件主接口

LPDZRECT3DVERTXBUFFER9 顶点缓冲区接口

LPD3DVIEWPORT9  视口接口

LPD3DDISPLAYMODE D3D设备显示模式接口

LPD3DPRESENT_PARAMETERS 创建结构用来保存设备的显示模式接口

LPD3DXVECTOR3  3D向量接口

LPDIRECT3DTEXTURE9 纹理接口

ID3DXSprite  精灵接口

g.pvb  成员函数

g_pD3D  成员函数

g_pD3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT,&d3ddm) 获取显示模式

g_pd3dDevice 成员函数

g_pd3dDevice->SetRenderState(,BOOL) 是否开启灯光

g_pd3dDevice->SetTransform( D3DTS_WORLD, &(matWorld * matWorld_x));//将上面计算出来的旋转矩阵,设置为世界变换矩阵

g_pd3dDevice->SetStreamSource( 0, g_pVB, 0, sizeof(CUSTOMVERTEX) );写入流

g_pd3dDevice->SetFVF() 设置FVF

g_pd3dDevice->DrawIndexedPrimitive( 画形状, 0, 0, 点个数, 0, 三角形个数 ); 画

timeGetTime 当前系统的时间

DIRECT3DTXTURE 文理接口

BITMAPPEILEHEADER 文件头

BITMAPINFOHEADER 信息头

fread 读一个数据块

biBitcout 每个象素占几个字节

bicompression 是否被压缩

fseek 跳,偏移指针

greatetxture 创建一个空文理

D3Dcaked_RECT 锁定结构体

setTexturestagestata 设置文理操作

CONSTD3DMATRIX*  进行变换的变换矩阵

结构体

D3DPRESENT_PARAMETERS 存储D3D设备信息

D3DXMATRIX  修改矩阵

数组

CUSTOMVERTEX 设置顶点位置颜色信息

­

矩阵函数

D3DXMATRIX * D3DXMatrixIdentity(POut,pM) 单位矩阵

D3DXMATRIX * D3DXMatrixTranspose(上)  矩阵转置

D3DXMATRIX * D3DXMatrixInverse(上中间加个FLOAT) 逆矩阵

D3DXMATRIX * D3DXMatrixTransformation()

D3DXMATRIX* D3DXMatrixTranslation(输出矩阵,X,Y,Z) 平移变换

D3DXMATRIX * D3DXMatrixScaling(上) 缩放变换

FLOAT D3DXPlaneDotCoord(pp,pv) 点和平面之见的关系

D3DXPLANE * D3DXPlaneFromPointNormal(POUT,PPOINT,PNORMAL) 构造子

D3DXPLANE * D3DXPlaneFromPoints(Pout,pv1,pv2,pv3) 通过点来描述平面

D3DXPLANE * D3DPlaneNormalize(POUT,PP) 标准化一个平面

D3DXPLANE * D3DXPlaneTransform(POUT,PP,PM) 平移平面

D3DXM

转换函数

D3DXMATRIX* D3DXMatrixLookAtLH(输出用于视图转换的矩阵,摄象机的位置,摄象机面向的位置,摄象机的正方向)  视图转换的矩阵

D3DXMATRIX* D3DXMatrixOrthoLH(输出用于正交投影的交换矩阵,取景宽,取景高,取景离摄象机的最近距离,取景离摄象机的最远距离) 正交投影变换矩阵

D3DXMATRIX* D3DXMatrixPerspectiveFovLH(输出用于透视投影的交换矩阵,摄象机镜头的夹角Y,平截台体的纵横比,近平截面的距离,远平截面的距离) 透视投影的矩阵

Direct3DCreate9(D3D版本) 创建D3D对象

设备函数

SetTransform(变换的类型,变换的变换矩阵) 设置左手或右手坐标

SetViewport(视口指针) 设置远近距离

GetClientRect(hWnd,*RECT) 获取窗口绘图区域

memcpy(指针,数组,长度) 拷贝

SetStreamSource(0,G.pvb接口指针,0,长度) 数据流

GetAdapterDisplayMode(指定显示卡序列号,存储显示模式的指针) 获取显卡的模式

HRESULT CreateDevice(显卡序列号,D3D设备类型,所属窗口句柄,D3D进行3D运算,存储D3D设备相关信息指针,返回D3D设备借口指针的地址) 创建设备借口

HRESULT CreateVertexBuffer(顶点缓冲区大小(字节),顶点缓冲区属性,灵活顶点格式,顶点缓冲区内存位置,顶点缓冲区指针地址,保留参数通常为0) 创建顶点缓冲

HRESULT CreateIndexBuffer(索引缓冲区大小(字节),顶点缓冲区属性,FMT颜色,顶点缓冲区内存位置,索引缓冲区指针地址,保留参数通常为0)   创建索引缓冲

HRESULT Lock(加锁内存起始地址,加锁内存大小,返回内存指针地址,加锁属性) 加缩内存

HRESULT UnLock() 解锁

HRESULT SetStreamSource(渲染数据流序列号,进行绑定连接的顶点缓冲区指针,进行绑定连接渲染数据流的起始位置,渲染数据流中一个顶点所占的内存大小) 顶点缓冲区和渲染数据流连接

HRESULT SetFVF(灵活顶点格式) 设置顶点格式

HRESULT DrawPrimitive(绘制的图元类型,绘制的开始顶点的索引值,绘制的图元数量)  画到后向缓冲区

HRESULT DrawPrimitiveup() 可以直接画

HRESULT Preesent(复制源的矩形区域指针,复制目的地的矩形区域指针,D3D设备窗口句柄,最小更新区域指针) 屏幕翻转

HRESULT SetIndices(使用的索引缓冲区指针) 设置当前绘制的索引数组

DrawIndexedPrimitive(图元类型,绘制到的索引缓冲区的开始地址,最小的索引数组元素的值,顶点的数目,开始的索引数组元素的值,绘制的数量) 同DrawPrimitive()

­

绘制函数

HRESULT DrawPrimitive(基本图元类型,起始顶点,绘制的图元的数量)  图元绘制

HRESULT Clear(清楚的矩形区域数量,清除的举行区域数组指针,清楚哪个缓冲区,清除后重置的颜色,清除后重置的深度,0-1.0,重置的摸版值) 清空图形绘制区

HRESULT BeginScene() 开始绘制

HRESULT EndScene() 结束绘制

­

纹理函数

CreateTexture()  创建D3D纹理对象

LoadBmpTeture() 装载文理函数

LoadBmpTexture24Bit (LPDIRECT3DDEVICE9 pDevice,LPCSTR  pSrcFile,LPDIRECT3DTEXTURE9* ppTexture) 24位纹理

D3DXCreateTextureFromFile(D3D设备指针,纹理图形文件,存储D3D文理的指针地址) 直接从磁盘获取纹理

D3DXCreateTextureFromFileEx(D3D设备指针,纹理图形文件,指定纹理宽,高,指定渐进纹理序列级数,纹理使用方式一般为0,指定纹理图形格式,纹理存放的内存类型一般位为0,纹理过滤方式,自动生成的纹理序列过滤方式,设置透明色,图形文件信息存放地址可设置0,调色板存储地址,创建的D3D文理的指针地址) 高级获取纹理

HRESULT SetTexture(多级纹理的索引0-7,D3D的纹理接口指针) 设置当前要渲染的纹理

HRESULT SetTextureStageState(多级纹理的索引,纹理渲染状态的类型,纹理渲染状态的值,与类型相对应) 设置纹理的渲染状态

HRESULT SetSamplerState(指定纹理属性0-7,纹理采样属性类型,设置纹理采样属性) 纹理采样

HRESULT CheckDeviceFormat(指定显卡序列号,D3D设备类型,指定显示模式格式,缓冲区属性,需要使用查询的格式的设备类型,需要查询的显示格式) 纹理压缩

HRESULT LockRect(指定加锁的纹理级别,指向D3DLOCKED_RECT结构,要加锁的RECT区域-0代表整个区域,加锁类型-取0或下表的值) 锁定纹理

HRESULT UnlockRect(解锁的纹理级别) 解锁纹理

向量函数

D3DXVECTOR3 * D3DXVer3Length(V) 向量模的计算

D3DXVECTOR3 * D3DXVec3Normalize(返回指针,V) 单位化

D3DXVECTOR3 * D3DXVec3Add(返回的指针,u,v) 向量加法

D3DXVECTOR3 * D3DXVec3Subtract(同上) 减法

D3DXVECTOR3 * D3DXVec3Cross(同上) 向量X乘

D3DXVECTOR3 * D3DXVec3Lerp(同上) 数乘

D3DXVECTOR3 * D3DXVec3Maximize(同上) 取最大值

D3DXVECTOR3 * D3DXVec3Minimize(同上) 取最小值

D3DXVECTOR3 * D3DXVec3Scale(返回指针,PV,FLOAT) 比例

FLOAT D3DXVec3Dot(pv1,pv2) 点乘

参见编程精粹.chm中的COM中模块的导出函数

Private Type D3DVECTOR

    x As Single

    y As Single

    z As Single

End Type

'返回3D向量的规格化向量

Private Declare Function D3DXVec3Normalize Lib "DX8VB.DLL" Alias "VB_D3DXVec3Normalize" (VOut As D3DVECTOR, v As D3DVECTOR) As Long

Private Declare Function D3DXVec3Add Lib "DX8VB.DLL" Alias "VB_D3DXVec3Add" (VOut As D3DVECTOR, v1 As D3DVECTOR, V2 As D3DVECTOR) As Long

Private Declare Function D3DXVec3Subtract Lib "DX8VB.DLL" Alias "VB_D3DXVec3Subtract" (VOut As D3DVECTOR, v1 As D3DVECTOR, V2 As D3DVECTOR) As Long

Private Declare Function D3DXVec3Length Lib "DX8VB.DLL" Alias "VB_D3DXVec3Length" (v As D3DVECTOR) As Single

D3DFVF 自由顶点的格式

D3DFVF_DIFFUSE 包含谩反射的信息

D3DFVF_NORMAL 包含法线信息

D3DFVF_PSIZE 顶点信息指明绘制点的大小

D3DFVF_SPECULAR 包含镜面反射的信息

D3DFVF_XYZ 包含未经转换的顶点坐标

D3DFVF_XYZRHW 包含经过转换的顶点坐标

D3DFVF_XYZB1 through D3DFVF_XYZB5 包含用于骨骼动化的顶点和顶点对骨骼的权重信息

D3DFVF_XYZW 包含经过转换和裁剪的顶点坐标

D3DTRANSFORMSTATETYPE 变换的类型

­

D3DPRIMITIVETYPE 定义基本图元

D3DPT_POINTLIST 一组点的集合

D3DPT_LINELIST 一组线的集合

D3DPT_LINESTRIP 首尾相连的线段的集合

D3DPT_TRIANGLELIST 一组三角形的集合

D3DPT_TRIANGLESTRIP 首尾相连的三角形,有两个顶点集合

D3DPT_TRIANGLEFAN   组成扇形的一组三角形集合

D3DPT_FORCE_DWORD 未定义的

D3DDISPLAYMODE 屏幕显示模式

D3DFMT_UNKNOWN  未知的象素格式

D3DFMT_R8G8B8  24位色,RGB各占8位

D3DFMT_A8R8G8B8  32位色,@RGB各占8位

D3DFMT_X8R8G8B8  32位色,X为保留8位 RGB各占8位

D3DFMT_R5G6B5  16位色,R占5,G占6,B占5位

D3DFMT_X1R5G5B5  16位色,保留1位,RGB各占5位

D3DFMT_A1R5G5B5  16位色,@占1位,RG5各占5位

D3DFMT_A4R4G4B4  16位色,@RGB各占4位

D3DFMT_R3G3B2  8位色,R3,G3,B2位

D3DFMT_A8  只有8位@

D3DFMT_A8R3G3B2  16位色,@8,R3,G3,B2位

D3DFMT_X4R4G4B4  16位色

D3DFMT_A2B10G10R10 32位色,@占2位,RGB各10位

D3DFMT_A8B8G8R8  32位色

D3DFMT_X8B8G8R8  32位色

D3DFMT_G16R16  32位色,只有红和绿

D3DFMT_A2R10G10B10 32位色

D3DFMT_A16B16G16R16 64位色

D3DFMT_A8P8  8位色,8位表示半透明,8位表示颜色

D3DFMT_P8  8位色,用牙色索引值表示

D3DFMT_L8  8位色,只表示亮度

D3DFMT_L16  16位色,只表示亮度

D3DFMT_A8L8  16位色,8位表示半透明,8位表示亮度

D3DFMT_A4L4  8位色,4位表示半透明,4位表示亮度

D3DDEVTYPE_HAL   硬件抽象层,通过显示硬件来完成图形渲染工作

D3DDEVTYPE_NULLREF  

D3DDEVTYPE_REF   参考光栅器,一般用语测试显示卡不支持的D3D功能

D3DDEVTYPE_SW   用语支持第三方的软件

D3DDEVTYPE_FORCE_DWORD  扩展的

D3DCREATE 3D运算的方式

D3DCREATE_ADAPTERGROUP_DEVICE

D3DCREATE_DISABLE_DRIVER_MANAGEMENT

D3DCREATE_DISABLE_DRIVER_MANAGEMENT_EX

D3DCREATE_FPU_PRESERVE   激活双精度浮点运算或浮点运算异常检测,设置该项会降低系统性能

D3DCREATE_HARDWARE_VERTEXPROCESSING 由D3D硬件进行顶点预算

D3DCREATE_MIXED_VERTEXPROCESSING 由混合方式进行顶点运算

D3DCREATE_MULTITHREADED   支持多线程绘制,设置该项会降低系统性能

D3DCREATE_NOWINDOWCHANGES  

D3DCREATE_PUREDEVICE   禁用D3D的GET*()函数,禁止D3D使用虚拟设备模拟顶点运算

D3DCREATE_SCREENSAVER  

D3DCREATE_SOFTWARE_VERTEXPROCESSING 由D3D软件进行顶点运算

D3DSWAPEFFECT 取值列表

D3DSWAPEFFECT_DISCARD    后台缓冲区复制到前台时,清除后台缓冲区内容

D3DSWAPEFFECT_FLIP    后台缓冲区内容复制后,保持不变,有多个后台缓冲区时使用

D3DSWAPEFFECT_COPY    后台缓冲区内容复制后,保持不变,只有1个后台缓冲区时使用

D3DSWAPEFFECT_FORCE_DWORD   强迫该直作为32位存储,通常不用

D3DPRESENT 屏幕反转模式列表

D3DPRESENT_DONOTWAIT  

D3DPRESENT_INTERVAL_DEFAULT 默认的同ONE

D3DPRESENT_INTERVAL_ONE  当屏幕刷新一次时前台后台进行交换

D3DPRESENT_INTERVAL_TWO  当屏幕刷新二次时前台后台进行交换

D3DPRESENT_INTERVAL_THREE 当屏幕刷新三次时前台后台进行交换

D3DPRESENT_INTERVAL_FOUR 当屏幕刷新四次时前台后台进行交换

D3DPRESENT_INTERVAL_IMMEDIATE 图形绘制完成时立即进行交换

D3DPRESENT_LINEAR_CONTENT

D3DUSAGE 缓冲区属性值列表

D3DUSAGE_AUTOGENMIPMAP  

D3DUSAGE_DEPTHSTENCIL

D3DUSAGE_DMAP  

D3DUSAGE_DONOTCLIP 禁用裁剪,表示顶点缓冲区中的顶点不进行裁剪,当设置该属性时,渲染状态D3DRS_CLIPPING必须设为FALSE

D3DUSAGE_DYNAMIC 使用动态内存分配

D3DUSAGE_NPATCHES 使用顶点缓冲区绘制N-patches曲线

D3DUSAGE_POINTS  使用顶点缓冲区绘制点

D3DUSAGE_RENDERTARGET

D3DUSAGE_RTPATCHES 使用顶点缓冲区绘制曲线

D3DUSAGE_SOFTWAREPROCESSING 使用软件进行顶点运算,否则使用硬件计算

D3DUSAGE_WRITEONLY 只写属性,不能进行读操作,设置该属性可以提高系统性能

D3DPOOL  缓冲区资源内存位置列表

D3DPOOL_DEFAULT  默认的,顶点缓冲区尽可能存在与显存中

D3DPOOL_MANAGED  由D3D自动调度顶点缓冲区内存位置(显存和内存)

D3DPOOL_SCRATCH  顶点缓冲区位于计算机的临时内存中,这种类型的顶点缓冲区不能直接进行渲染,只能进行内存枷锁,拷贝等操作

D3DPOOL_SYSTEMMEM 顶点缓冲区位于内存中

D3DLOCK  缓冲区加锁

D3DLOCK_DISCARD  更新整个缓冲区

D3DLOCK_DONOTWAIT

D3DLOCK_NO_DIRTY_UPDATE 在加锁的过程中系统进行其他操作(默认有Dirty标记)

D3DLOCK_NOOVERWRITE 保证不腹稿缓冲区数据,设置该属性可以立即返回内存指针,提高系统性能

D3DLOCK_NOSYSLOCK 在加锁的过程中系统可能执行其他操作

D3DLOCK_READONLY 设置缓冲区位只读属性

D3DXVECTOR3 向量算法

D3DXVECTOR3u(x,y,z);

D3DXVECTOR3v(x,y,z);

float 变量=D3DXVec3Dot(u指针,v指针) 点乘

D3DXMATRIX 矩阵

D3DXMatrixIdentity 单位矩阵

D3DXMatrixInverse 逆矩阵

D3D实现图形变换

D3DXMatrixTranslation 平移矩阵

D3DXMatrixLockAtLH 观察矩阵

D3DXMatrixIdentity  将一个矩阵单位化

D3DXMatrixRotationY 绕Y轴转

D3DXMatrixRotationX 绕X轴转

D3DXMatrixRotationZ 绕Z轴转

D3DXMatrixScaling 缩放变换

D3DXMatrixMuLationAxis 围绕任意一个轴旋转

D3DXMatrixMultiply 组合变换

D3DUSAGE 纹理使用

D3DUSAGE_AUTOGENMIPMAP 自动生成多级渐进纹理序列,该方式在资源处于D3DPOOL_SYSTEMMEM时无效

D3DUSAGE_DEPTHSTENCIL 深度模版缓冲区,只在资源处于D3DPOOL_default时有效

D3DUSAGE_DMAP  该纹理是一个置换纹理

D3DUSAGE_DONOTCLIP

D3DUSAGE_DYNAMIC

D3DUSAGE_NPATCHES

D3DUSAGE_POINTS

D3DUSAGE_RENDERTARGET 该文理是一个渲染目标缓冲区

D3DUSAGE_RTPATCHES

D3DUSAGE_SOFTWAREPROCESSING 应用坐标变换

D3DUSAGE_WRITEONLY

D3DTEXTURESTAGESTATETYPE 渲染状态类型

D3DTSS_COLOROP   1 文理层的颜色混合方式

D3DTSS_COLORARG1  2 颜色混合的第一个参数

D3DTSS_COLORARG2  3 颜色混合的第二个参数

D3DTSS_ALPHAOP   4 指定纹理层的Alpha透明

D3DTSS_ALPHAARG1  5 Alpha混合的第一个参数

D3DTSS_ALPHAARG2  6 Alpha混合的第二个参数

D3DTSS_BUMPENVMAT00  7 绘制凹凸纹理时

D3DTSS_BUMPENVMAT01  8 绘制凹凸纹理时

D3DTSS_BUMPENVMAT10  9 绘制凹凸纹理时

D3DTSS_BUMPENVMAT11  10 绘制凹凸纹理时

D3DTSS_TEXCOORDINDEX  11 该纹理层使用的纹理坐标的索引

D3DTSS_BUMPENVLSCALE  22 绘制凹凸纹理的缩放参数

D3DTSS_BUMPENVLOFFSET   23 绘制凹凸纹理的平移参数

D3DTSS_TEXTURETRANSFORMFLAGS  24 控制纹理坐标的转换标志

D3DTSS_COLORARG0  26 指定混合过程的第三个颜色

D3DTSS_ALPHAARG0  27 Alpha混合的第三个参数

D3DTSS_RESULTARG  28 颜色混合的结果输出寄存器

D3DTSS_CONSTANT  32 颜色混合的常量寄存器

D3DTSS_FORCE_DWORD  0x7fffffff 强制转换为32位,通常不用

D3DSAMPLERSTATETYPE 纹理采样属性

D3DSAMP_ADDRESSU  1 包装纹理

D3DSAMP_ADDRESSV  2 包装纹理

D3DSAMP_ADDRESSW  3 包装纹理

D3DSAMP_BORDERCOLOR  4

D3DSAMP_MAGFILTER  5 处理放大过滤

D3DSAMP_MINFILTER  6 处理缩小过滤

D3DSAMP_MIPFILTER  7 多纹理过滤

D3DSAMP_MIPMAPLODBIAS  8 多级文理级数偏移值,初试直为0

D3DSAMP_MAXMIPLEVEL  9 最大多纹理级别,初试值为0

D3DSAMP_MAXANISOTROPY  10 各向异性,初试值为1

D3DSAMP_SRGBTEXTURE  11

D3DSAMP_ELEMENTINDEX   12

D3DSAMP_DMAPOFFSET  13

D3DSAMP_FORCE_DWORD  0x7fffffff 强制转换32位,通常不用

纹理寻址

D3DTADDRESS_WRAP  1 包装纹理寻址

D3DTADDRESS_MIRROR  2 镜像纹理寻址

D3DTADDRESS_CLAMP  3 夹取纹理寻址

D3DTADDRESS_BORDER  4 边框颜色纹理寻址

D3DTADDRESS_MIRRORONCE  5 一次镜像纹理寻址

D3DTADDRESS_FORCE_DWORD 0x7fffffff强制转换32位,通常不用

世界变换

D3DTS_WORLD  世界变换

posted @ 2009-09-17 07:55 RedLight 阅读(3663) | 评论 (0)编辑 收藏

D3d9的一些更新 (转)

由于Aug 8造成的D3D9恐惧症已经完全消除了,这一章将会给大家介绍将3D引擎转向D3D9的各个方面,包括终于出现的全屏幕模式。从这章以后,我将使用D3D9作为讲解的语言继续D2D教程。

【OP结束,开始正片】

『Why?』

  估计大家首先要问的就是“Why?”为什么要前进到D3D9?理由如下:
1、D3D9修复了D3D8已知的所有Bug,因此运行起来更稳定,速度也要快。
2、D3D9提供了许多便利的新功能,虽然绝大多数是面向3D的,但是也有不少2D适用的,比如IDirect3DDevice9::StretchRect,以及对IDirect3DSurface9的改进等等。D3DX库就更多了,比如D3DXSaveSurfaceToFileInMemory,一开始没发现这个函数有啥用处,现在基本离不开了。
3、HLSL。就像上一话我说的那样,D2D教程以后会有PixelShader的内容。我可不想拿汇编来写Shader,会死人的(祝贺我吧,终于抛弃汇编Shader了……)。虽然说这不是决定性的理由,因为还有Cg什么的,不过我想编写显卡无关的代码,因此我不去研究Cg(反正和HLSL差不多)以及R2VB之类。
4、ID3DXFont,往下看你就知道了。

《D3D的变化》

『界面名称变化』

  一句话:8改成9就行。

『“创建”型方法的一个统一变化』

  许多Create*()方法,比如创建设备、创建纹理、创建顶点缓冲等等,多了一个HANDLE* pSharedHandle参数,无用,NULL之(看来微软原打算弄个共享句柄之类,不过被D3D10巨大的变化浮云了)

『创建D3D设备的变化』

  D3DPRESENT_PARAMS的FullScreen_PresentationInterval变成了PresentationInterval,也就是说即使在窗口模式下也可以做到垂直同步来防止撕裂现象(2D的福音啊)。相应的,D3DSWAPEFFECT_COPY_VSYNC消失了,反正这个效果也不咋的,消失了也好。
  要做到垂直同步需要给PresentationInterval赋值D3DPRESENT_INTERVAL_DEFAULT或D3DPRESENT_INTERVAL_ONE。其中D3DPRESENT_INTERVAL_ONE的效果比D3DPRESENT_INTERVAL_DEFAULT好一点,不过相应的也会占用多一点点系统资源……真的只有一点点而已,实在是无所谓的……
  如果不要垂直同步,想要看看实际祯速的话,D3DPRESENT_INTERVAL_IMMEDIATE。
  注意在窗口模式下,你只能使用这三种Present模式,全屏幕模式下就可以使用别的(但是要首先检测D3DCAPS9以查看显卡是否支持)。不过我感觉对99%的游戏来说,有这三个就足够了。
  另外在窗口模式下,BackBufferFormat也可以设置成D3DFMT_UNKNOWN,D3D会自动获取当前桌面的格式设定成后备缓冲的格式,省去GetDisplayMode。实际上,窗口模式下的后备缓冲已经不需要和桌面格式相同,你可以通过IDirect3D9::CheckDeviceFormatConversion来检查,如果这个设备支持这两种颜色格式之间的转换,就可以给程序的后备缓冲设定上不同的格式。我试过在桌面格式为32Bit(D3DFMT_X8R8G8B8)时将程序的后备缓冲格式设置为D3DFMT_R5G6B5(16Bit),发现了速度提升,也就是说这个设定是有意义的。
  可创建的设备类型多了一种D3DDEVTYPE_NULLREF,在安装了D3D SDK的机子上等同于D3DDEYTYPE_REF,在其他的机子上,这种设备实际上没有创建真正意义的D3D设备,只是允许你创建的纹理、表面等资源,但是Render、Present等操作都会无效(实际上这些资源都创建在了D3DPOOL_SCRATCH池里,不管你设定使用的是什么POOL)。也就是说,仅仅在模拟基本的运行而已。你可以用这个设备来编写一个利用D3DX函数库进行图像格式转换的程序,比如把一大堆不同的格式转换成易于D3D9使用的DDS格式。因为实际上没有创建设备,你甚至可以编写成控制台的,通过GetConsoleWindow的方法获得HWND。Mercury 3用的MIF格式的转换器就是这么做出来的。注意D3DDEVTYPE_NULLREF只能用在IDirect3D::CreateDevice时,其他的方法都不行。

『创建表面的变化』

  创建表面(Surface)的方法变成了IDirect3DDevice9::CreateOffscreenPlainSurface,参数很简单不用多说,需要注意的是可以选择POOL了。

『设定FVF的变化』

  设定FVF时,原来通过IDirect3DDevice8::SetVertexShader,现在有了一个专门用来设定FVF的方法:IDirect3DDevice9::SetFVF。这是个很好的变化,省得把FVF和Shader弄混(题外话:也就是因为这个变化,让Shader在设备Reset后得以保存,不错不错)

『获取后备缓冲』

  D3D9现在允许有多个后备缓冲交换链,不过对于2D来说,基本不需要这种东西,IDirect3DDevice9::GetBackBuffer多出来的第一个参数赋值0即可。如果你有兴趣,可以去研究一下这个玩意,有时候可以用来做分场。

SetStreamSource』

  这个方法的功能被扩展了,对比参数就可以知道,多出来的OffsetInBytes允许你选择一个顶点缓冲的Offset,D3D9将从这个Offset之后开始读取数据。因此你可以把几组用来渲染纹理的正方形顶点存储到一个顶点缓冲里面。

SetSamplerState』

  这个是D3D9的新方法,把原先SetTextureStageState的一些功能独立了出来,和2D关系最密切的就是纹理过滤了。原先的D3DTSS_MINFILTER变成了D3DSAMP_MINFILTER,相应的D3DTSS_MAGFILTER也变成D3DSAMP_MAGFILTER,D3DTSS_MAXANISOTROPY变成D3DSAMP_MAXANISOTROPY。另外还有更多的,比如纹理寻址等。你去看一下D3DSAMPLERSTATETYPE枚举类型的内容就知道它“迁移”了些什么。
  这个变化对于Shader来说很方便。改成Sampler的东西在PixelShader过程也会有效,而没有更改的东西在PixelShader就不会有效了。D3D8时候把这些全都放在了一起,容易造成混乱。

SetRenderTarget』

  D3D9现在允许多重RenderTarget存在,不过我们基本上只用一个,RenderTargetIndex设为0,第二个参数仍然是需要设定的表面。与D3D8相同的是,在设定之前仍然需要先通过GetSurfaceLevel获得表面才行。

『顶点缓冲的锁定』

  注意IDirect3DVertexBuffer9::Lock的第三个参数,从原来的BYTE**变成了void**。也就是这样了……

『其他的一些变化』

1、CopyRects变成了UpdateSurface。和UpdateTexture一样,只能从D3DPOOL_SYSTEMMEM拷贝到D3DPOOL_DEFAULT
2、增加了一个比较有用的IDirect3DDevice9::ColorFill方法,作用是向D3DPOOL_DEFAULT的某个区域填充颜色,和Clear的功能类似,但是在使用目的上要比Clear明确的多,并且由于不牵扯深度缓冲之类,速度要快一些。
3、增加了一个IDirect3DDevice9::StretchRect方法,通过这个方法就可以在D3DPOOL_DEFAULT的表面或纹理之间进行带过滤器的缩放操作,免去利用Render的过程,非常有用。不过这个方法由于使用了硬件处理,限制较多,请大家仔细看SDK文档的Remarks部分。

《D3DX的变化》

  D3DX的变化实际上相当的多,但正如我一开始所说,基本都是面向3D的。需要我们注意的有以下几种:
1、D3DX***FromFile之类的函数支持的图像格式增加了,不过所增加的都是很少见的格式。平时基本上还是用BMP、TGA和PNG就足够。
2、增加了D3DXSave***ToFileInMemory,将会把文件写入内存。这个函数的作用似乎不是很容易想到,但是如果你要写一个集成了转换、打包功能的工具,这个就很有用了,省去了通过临时文件操作造成的各种问题。另外如果你熟悉某种图形文件的格式的话,还可以通过直接访问这个文件获得RAW信息。注意,这类函数写入的是一个ID3DXBuffer,这个东西很简单,只有两个特定的方法,一看便懂,不再多言。
3、增加了一个ID3DXLine,可以方便你在2D上画线,创建ID3DXLine的方法是D3DXCreateLine。这个东西也不复杂,使用方法有点像ID3DXSprite,稍微研究一下就能弄懂,注意每次Draw的是D3DPT_LINESTRIP。用它比直接用顶点缓冲的好处是可以方便的打开反锯齿,效果嘛……基本满意。
4、增加了一个ID3DXRenderToSurface,“理论上来说”方便了利用RenderTarget的过程……不过我感觉反而弄得复杂了。创建的方法是D3DXCreateRenderToSurface,有心情的朋友自己研究看看吧,我就不讲了。

  ID3DXSprite和ID3DXFont在Summer 2004的DX9 SDK(也就是第一版DX9.0c)开始发生了很大变化,下面详述:

『ID3DXSprite』

  你会发现ID3DXSprite::DrawTransform不见了,取而代之的是其功能被整合到ID3DXSprite::SetTransform里面,也就是说为了缩放和旋转,我们不得不和矩阵打交道了。其实也不会太复杂,因为我们只是做一些矩阵运算,学过线性代数的朋友肯定会很熟悉,就算你不怎么熟悉线性代数,也没关系,D3DX函数库提供了现成的矩阵运算函数,你只要用就行了。

D3DXMatrixScaling
D3DXMatrixRotationZ
D3DXMatrixTranslation

  按照顺序调用这三个函数……或许学过3D的马上就想到这点了,的确是没错啦。注意顺序哦:Scaling -> Rotation -> Translation,简称SRT(看过全金属狂潮吗?看过的话这个单词很好记吧^_^),弄错了可是得不到正确结果的。
  你是不是想到把同一个D3DXMATRIX当作参数使用三次?错啦!你要用矩阵乘法。创建三个D3DXMATRIX,比如mat1、mat2、mat3,分别用这三个函数将其创建为缩放矩阵、旋转矩阵和平移矩阵,然后在ID3DXSprite::SetTransform时,这样写:

SetTransform(mat1 * mat2 * mat3);

  有够麻烦的是不?ID3DXSprite方便了做3D的,可害苦了做2D的,所以我已经不直接用这个了(什么叫不直接用?往下看)。

『ID3DXFont』

  大家来欢呼吧!Summer 2004改进的ID3DXFont彻底枪毙掉了上一话那个字体引擎……
  这东西的改进,怎么说呢,应该说是改头换面吧,速度、效果都和以前不是一个数量级。可怜的PixelFont,才存在了一话就要被抛弃了。
  ID3DXFont多出来的几个方法,Preload*()这类的,就是把一些常用的字的字模提前读取到内存里面加快速度,同时还可以使用ID3DXSprite渲染,进一步加快速度。虽然内部仍然有GDI的部分,不过很明显工作方式发生了极大的变化。根据我的估计,这次的ID3DXFont很聪明的利用GDI获得文字的轮廓,然后通过纹理来渲染。这样的速度就快得多了,而且文字质量也得到了很好的控制,基本和直接用GDI的质量相同了。
  由于PreloadCharacters()和PreloadGlyphs()不是那么好理解,一般用PreloadText()就行。建议将所有ASCII字符、标点符号和部分汉字预读进去。这个预读过程略微有点慢,而且根据预读的文字数量和你创建文字的字号,占用的内存也不同。这里给大家一堆文字,你Copy过去就行:

引用

const char strPreloadText[] = " 1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM~!@#$%^&*()-=[]\\;',./_+{}|:\"<>? 、。·ˉˇ¨〃—~‖…‘’“”〔〕〈〉《》「」『』〖〗【】!"#¥%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}我人有的和主产不为这工要在第一上是中国经已发了民同";

  注意第一个字符是空格哦!把空格预读进去可是很重要的^_^
  看上去并不多,因为要考虑到内存占用及速度,我只预读了一些符号和五笔的一键字。这些字符在24号字时候已经占用了快1MB了,比起PixelFont字库占用的要大得多。天知道ID3DXFont到底预读了些什么……
  PreloadText()的第二个参数不要用strlen,sizeof(strPreloadText)即可。
  然后就是利用ID3DXSprite来渲染。注意ID3DXFont::DrawText的第一个参数就是LPD3DXSPRITE,因此如果要利用ID3DXSprite,要将ID3DXFont::DrawText放到ID3DXSprite::Begin和ID3DXSprite::End之间。这就是我刚才说的不直接用ID3DXSprite的意思,ID3DXFont会完成ID3DXSprite的全部调用,你不用担心。
  另外你应该注意到ID3DXSprite::Begin增加了参数,实际上DX文档里面没说,但是示例里面有,如果想让ID3DXSprite发挥作用并且最大幅度的提升效率,参数上设定D3DXSPRITE_ALPHABLEND | D3DXSPRITE_SORT_TEXTURE即可。意思很明白:打开Alpha过滤和纹理筛选。这里DX文档上有个错误一直没改:文档里给出的是D3DXSprite__SORT_TEXTURE,但是你可以试试,绝对报错。
  剩下的就没啥了,ID3DXFont的使用方法上一话已经讲过。要注意的是D3DXCreateFont和D3DXCreateFontIndirect都发生了变化。D3DXCreateFont已经不再牵扯GDI了,D3DXCreateFontIndirect所使用的结构也变成了D3DXFONT_DESC,相对于LOGFONT结构,除去了一些用不着的参数,增加了一个MipLevels,就是MipMap等级啦,不用多说,2D下只用1。其他的上一话都有。实际上由于D3DXCreateFont已经不再关联GDI,D3DXCreateFontIndirect的存在仅仅是由于历史原因(为了兼容像我这种人的使用习惯),大家还是用D3DXCreateFont吧,省事。
  截图就不贴了,没啥意义。你可能觉得直接向后备缓冲上DrawText还不够好看,那么就先画到一张纹理上,然后将纹理错位渲染到后备缓冲并且打开线型过滤,就可以达到和PixelFont相同的效果了。
  速度嘛……我画了整整一屏幕字,在不缓冲文字的情况下(这个“缓冲文字”和ID3DXFont的文字缓冲可不是一回事啊!看过上一话的都应该知道我这里指的是什么),速度仍然在120FPS以上。或许你会觉得速度还是有点慢,但是,如果用D3D8的ID3DXFont画上这么一屏幕,基本就只剩20FPS了。
  使用ID3DXFont替换掉PixelFont的优势就是可以方便的自定义字体字号了,并且也不再受GB2312字库的限制。所以大家都换了吧……都换了吧……把PixelFont忘了吧……

『稳定的DX9 SDK版本』

  我现在用的是April 2006,而且应该会用很长时间。August 2006我是肯定不会去用啦!即使我不再恐惧D3D9,也会对这个SDK避让三分的。其实对于2D,我感觉用到April 2006就足够了,之后的DX9 SDK主要在D3DX的3D函数库部分进行更改……其实也是秋后的蚂蚱蹦达不了几天,D3D10马上就要出来了。要说D3D10啊……你还是看我另外一篇日志好了,总之打死我都不拿它做2D。

  实际上仅仅是2D的话,从D3D8转向D3D9并没有多少变化,主要是稳定嘛!只要你不调用一些D3D9专用的功能,即使拿D3D9来做2D,在绝大多数显卡上还是能够运行的。嗯……GF2等级以上吧,GF2之前的,也太老了,无视好了。

《再上点菜好了:全屏幕模式》

  其实并不是多么复杂的问题,让我拖了这么久……不拖了,这里就教给大家如何做全屏幕模式以及如何处理设备丢失的问题。

『创建全屏幕模式』

  D3DPRESENT_PARAMS里面,Windowed设定为false,并且一定要设定BackBufferWidth和BackBufferHeight,完毕。
  哈哈,就这么简单,或许早就有人尝试过了,但是你试试按下Alt+Tab,再切换回去,保证你什么都看不到。
  之前曾经说过,DX8之前的版本,在全屏幕下工作比在窗口下容易,到DX8之后就则完全颠倒过来。因为在窗口模式下不用担心设备丢失(除非你更改桌面分辨率),全屏幕模式下就会有这个问题了。下面详述:

『设备、资源丢失』

  设备丢失会发生在全屏幕模式下切换回桌面时(不论是通过Alt+Tab还是QQ上有人给你发了张图片-_-bbb),而且如果在调用IDirect3DDevice9::Reset(从现在开始就是D3D9了啊!忘记D3D8吧……)的时候发生错误,设备也会丢失。
  设备丢失会造成资源丢失:所有创建在D3DPOOL_DEFAULT池的资源都会丢失,需要重新创建,其内容当然也会消失,需要重写。
  然而创建在D3DPOOL_SYSTEMMEM和D3DPOOL_SCRATCH池的资源不会受到影响。创建在D3DPOOL_MANAGED池的资源也不会丢失,而且在设备重新可用的时候,D3DPOOL_MANAGED池的资源也可以立即投入使用,内容也不会改变。看这个池名字:托管池就能知道,D3D帮你处理了所有问题。
  因此避免设备丢失后资源丢失的简易方法就是将所有资源创建在D3DPOOL_MANAGED池内。不过这并不是个好方法,这意味着不能用渲染对象——记得吗?RenderTarget只能创建在D3DPOOL_DEFAULT。实际上最好的方法是跟踪所有D3DPOOL_DEFAULT资源,比如利用std::list,将所有D3DPOOL_DEFAULT资源勾住,在设备发生丢失的时候释放掉资源,设备可以继续使用的时候重新创建资源,记得把数据写回去。对于其他的池就不用这么折腾了。

『当设备丢失之后』

  不论通过任何方式发生了设备丢失,所有的操作几乎都会失效,只有Release()可以用——其实D3D会保证有部分操作可以成功,但是也仅仅是“可以”成功而不是“一定”成功,所以你还不如认定丢失的时候全都会失败比较好——以及IDirect3DDevice9::TestCooperativeLevel。因此在设备丢失之后,你应该停止整个游戏循环,而通过反复调用IDirect3DDevice9::TestCooperativeLevel判断设备是否可用。

『IDirect3DDevice9::TestCooperativeLevel』

  这个方法检测当前的设备状态。返回值有四种:D3D_OK一切正常,D3DERR_DEVICELOST设备丢失,D3DERR_DEVICENOTRESET设备可以Reset。另外还有D3D9新增的D3DERR_DRIVERINTERNALERROR,遇到这个你就完蛋了,基本不可能恢复了,终止程序吧。
  按照顺序来讲,如果游戏在正常运行,D3D_OK会返回;如果发生了设备丢失并且在这个时候不能恢复,比如全屏幕模式的时候用户切换到了Windows桌面,就会返回D3DERR_DEVICELOST;如果用户又切换回了游戏,设备可以恢复了(还没恢复呢!只是“可以”恢复而已),就会返回D3DERR_DEVICENOTRESET
  另外,IDirect3DDevice9::Present也会返回类似的值,不过你最好别指望这个,老老实实的用TestCooperativeLevel。因为Present在设备可以恢复的时候还是返回D3DERR_DEVICELOST(外一句:D3D10的时候TestCooperativeLevel就会完全整合到Present里面了,可喜可贺可喜可贺)

『处理设备丢失』

  看下面的伪代码:

switch (IDirect3DDevice9::TestCooperativeLevel()){
  case D3D_OK:
    GameLoop();
    break;
  case D3DERR_DEVICELOST:
    break;
  case D3DERR_DEVICENOTRESET
    OnLostDevice();
    IDirect3DDevice9::Reset();
    OnResetDevice();
    break;
  default:
    QuitGame();
    break;
}

  GameLoop()就是你的游戏运行的过程了。把这个switch写在我们游戏框架的GameMain()部分,具体的位置可以看任何一话附带的源代码。
  好像我一直没有讲IDirect3DDevice9::Reset的参数啊?因为只有一个参数,就是指向D3DPRESENT_PARAMS的指针。把你第一次创建设备时使用的D3DPRESENT_PARAMS结构保存起来,供Reset来用。
  OnLostDevice()就是Release掉所有D3DPOOL_DEFAULT的资源,OnResetDevice()就是Create*()恢复啦!你可能注意到ID3DXFont、ID3DXSprite等等都有同名的方法,就是在这个时候调用的。如果你没有这么做,也就是说还保留着任何D3DPOOL_DEFAULT的资源的话,IDirect3DDevice9::Reset就一定会失败。
  另外在OnResetDevice里面你还要重新进行SetRenderState、SetSamplerState等等,Reset之后这些东西也丢失了。实际上Reset和重新创建一次设备类似,所不同的是重新创建设备的话你需要连D3DPOOL_MANAGED的资源也Release掉。这个话题就不讨论了。
  从代码可以看出来,D3DERR_DEVICELOST时程序什么都没做,只是在傻等。我认为这是一个好习惯,因为实在不能保证在D3DERR_DEVICELOST时除了Release还能干什么,与其这样还不如等设备能用了再说。

  实在懒得管资源的话,全部D3DPOOL_MANAGED好了。至于渲染对象?自己想办法。

『人工制造“设备丢失”』

  “干嘛还要制造设备丢失啊?”如果更改游戏分辨率、色深、切换全屏幕及窗口状态,进行这样的操作也要通过Reset,同样的,Reset之前也要释放掉所有D3DPOOL_DEFAULT资源(其实严格来说,还有更多的资源也要释放,不过在2D下基本不会创建这类资源,你就不用管了)并且调用ID3DXSprite::OnLostDevice之类的方法。这就是人工制造“设备丢失”了。实际上在这个过程设备并没有真正的丢失,只是会有一段时间处于不可用的状态,此时Reset尚未返回,整个D3D设备就好像死了一样。举个例子,你切换桌面分辨率,会有那么一段时间显示器上什么都不显示,然后很快就正常了。和这个现象是同一个原因。Reset成功后记得恢复资源。
  你可能注意到这里的Reset和上面的Reset不是一回事。的确是这样,这里是为了重设状态而不是恢复设备。因此更改分辨率、色深的Reset需要写到switch外面,也就是别和它搅和的意思-_-bb。而且你只需要OnLostDevice -> Reset -> OnResetDevice。记住:正确的调用Reset不会造成设备丢失,这个概念别弄混了。

『切换全屏幕模式时的注意事项』

  注意WindowStyle的变化。切换成全屏幕模式后,只能使用WS_POPUP,不然显示会变得怪怪的,你可以通过SetWindowLongPtr函数更改窗口外观,第二个参数指定GWL_STYLE即可。别忘了WS_VISIBLE啊!不然你什么都看不见。

『更详细的文档』

  我这里只是简单讨论了造成设备丢失的原因及处理方法,更详细的内容你可以参考DX SDK文档的Lost Device文章,人家是权威的。

【以上,正片结束,后面是ED】

  我们前进到了D3D9,赶上了时代。
  我们创建了全屏幕游戏,赶上了时代。
  我却变得一脑子浆糊,被观众抛弃了。
  哈哈,开玩笑啦,不过这一话很乱倒是真的,因为不论是更新到D3D9还是设备丢失,牵扯的东西都太散太杂,结果弄得这一话也是一盘散沙(居然又没有附带代码)。唉,大家就忍了吧,忍不了的话就来PIA我吧。

  关于更新至D3D9更多的内容,你可以参考SDK文档的《Converting to Direct3D 9》。

【以上,ED结束,后面是……】

  第一季完结了……
  回过头来看看,从第一话创建一个Windows窗口,到这一话的设备丢失,话题的层次一直在深入,现在已经深入到了不再是“学习”而是“研究”的范围。我也不再想仅仅是搞“教学”而是想和大家“讨论”。不过第一季主要还是教学吧。能坚持着看D2D教程到现在的,应该基本能够写出完整的2D Demo来了吧。如果有什么问题的话,欢迎提出,我在看到后会立刻回答的……只要你这个问题不太RP的话……
  那么,第二季会是什么样子?
  第二季就不再是教学了,而开始我和大家的讨论过程。第二季的第一话,也就是第09话,我将提供一些高级技巧给大家,并希望有兴趣的朋友和我一起进行这些技巧的研究。另外在第二季里面,我们还要创建一个2D图形引擎。原来打算给大家讲解Medux 2,不过现在感觉这东西实在小儿科,绝对会让大家B4的。那么既然如此,干脆介绍Mercury 3好了,有意见无?
  透漏一点下一话的内容吧:模糊精度和多次纹理渲染,嘿嘿,听上去挺高深的是不?实际上超级简单,就看你能不能想到而已。
  希望你在看完这一话之后,返回去再把前面的内容看看,相信你会得到新的收获。搞不好你还能抓出几个Bug呢!因为我是想到什么写什么,没个章法,Bug是难免的。
 


附加:

Direct3D中的字体与文本显示
图形系统中为了获得当前运行程序的相关信息,往往需要在屏幕上显示文本,Direct3D的功能扩展接口ID3DXFont对此提供了方便的解决方法。

 

 

创建ID3DXFont对象

使用接口ID3DXFont绘制文本,首先需要通过函数D3DXCreateFont()创建ID3DXFont字体对象。ID3DXFont接口封装了Windows字体和Direct3D设备指针,D3DXCreateFont()函数通过Windows字体和Direct3D设备指针创建ID3DXFont对象,该函数的声明如下:

Creates a font object for a device and font.

HRESULT D3DXCreateFont(  LPDIRECT3DDEVICE9 pDevice,  INT Height,  UINT Width,  UINT Weight,  UINT MipLevels,  BOOL Italic,  DWORD CharSet,  DWORD OutputPrecision,  DWORD Quality,  DWORD PitchAndFamily,  LPCTSTR pFacename,  LPD3DXFONT * ppFont);
Parameters
pDevice
[in] Pointer to an IDirect3DDevice9 interface, the device to be associated with the font object.
Height
[in] The height of the characters in logical units.
Width
[in] The width of the characters in logical units.
Weight
[in] Typeface weight. One example is bold.
MipLevels
[in] The number of mipmap levels.
Italic
[in] True for italic font, false otherwise.
CharSet
[in] The character set of the font.
OutputPrecision
[in] Specifies how Windows should attempt to match the desired font sizes and characteristics with actual fonts. Use OUT_TT_ONLY_PRECIS for instance, to ensure that you always get a TrueType font.
Quality
[in] Specifies how Windows should match the desired font with a real font. It applies to raster fonts only and should not affect TrueType fonts.
PitchAndFamily
[in] Pitch and family index.
pFacename
[in] String containing the typeface name. If the compiler settings require Unicode, the data type LPCTSTR resolves to LPCWSTR. Otherwise, the string data type resolves to LPCSTR. See Remarks.
ppFont
[out] Returns a pointer to an ID3DXFont interface, representing the created font object.
Return Values
If the function succeeds, the return value is S_OK. If the function fails, the return value can be one of the following: D3DERR_INVALIDCALL, D3DXERR_INVALIDDATA, E_OUTOFMEMORY.

Remarks
The creation of an ID3DXFont object requires that the device supports 32-bit color.

The compiler setting also determines the function version. If Unicode is defined, the function call resolves to D3DXCreateFontW. Otherwise, the function call resolves to D3DXCreateFontA because ANSI strings are being used.

If you want more information about font parameters, see The Logical Font.

示例代码如下:

D3DXCreateFont(g_device, 50, 20, 20, 0, FALSE, DEFAULT_CHARSET, 0, 0, 0, "Arial", &g_font);

posted @ 2009-09-12 07:40 RedLight 阅读(2367) | 评论 (0)编辑 收藏

OpenGL中的选择和反馈

读完此章之后,你将能够做到:
  建立允许用户选择(select)屏幕区域或拾取(pick)绘制在屏幕上的物体的应用程序
  利用OpenGL的反馈(feedback)模式获取绚染计算结果
  
  有些图形应用程序只绘制两维和三维物体构成的静态图形,另一些允许用户识别屏幕上的物体并移动、修改、删除或用其它方法操纵这些物体。OpenGL正是设计用于支持这些交互式应用程序的。因为绘制在屏幕上的物体通常经过多次旋转、移动和透视变换,所以确定用户选中了三维场景中的哪个物体会很困难。为了帮助你,OpenGL提供了一个选取机制可惟自动告诉你哪个物体被绘制在窗口的提定区域里。你可以用这个机制与一个工具例程(a special utility routine)一道决定哪个物体在用户说明或用光标选取的区域里。
  
  选择(selection)实际上是OpenGL的一个操作模式;反馈(feedback)是这类模式中的别一个。在反馈模式中,你用你的图形硬件和OpenGL完成通常的绚染计算。但与用这个计算结果去在屏幕上绘制图形相反,OpenGL返回(或反馈(feeds back))这些绘制信息给你。如果你想在绘图仪而不是屏幕上绘制图形,举个例子,你就得在反馈模式绘制它们,收集绘制指令,然后将这些指令转换为绘图仪可以理解的命令。
  
  在选择和反馈模式中,绘制信息返回给应用程序而不是象在绚染模式中那样送往帧缓冲。因此,当OpenGL处于选择或反馈模式时,屏幕将被冻结-没有图形出现。这一章将会在各自的节中解释这些模式:
  
  “选择(Selection)” 讨论怎样使用选择模式和相关的例程以使你程序的用户能拾取画在屏幕上的物体。
  
  “反馈(Feedback)” 描述了怎样获取有关什么将被画在屏幕上的信息和这些信息是以什么格式组织的。
  
  ---------------------------------------------------
  Section
  
  通常,当你打算使用OpenGL的选择机制时,你首先把你的场景画进帧缓冲,然后进入选择模式并重新绘制这个场景。然而,一旦你进入了选择模式,帧缓冲的内容将保存不变,直到你退出选择模式。当你退出时,OpenGL返回一个图元(premitives)清单,图元可能被视见体(viewing volume)分割(记住,视见体是由当前模式视见和投影矩阵及你定义的所有裁剪面定义,裁剪面详见"Additional Clipping Planes.")。每个被视见体图元引出一资选择命中(hit)。确切的说,图元清单是作为一个取整数值的名字(integer-valued names)数组和相关的数据-命中记录(hit record)-对应名字栈(name stack)的当前内容。当你在选择模式下发布图元绘制命令时向名字栈中加入名字就可建立起名字栈。这样,当名字清单被返回后,你就可以用它来确定屏幕上的哪个图元可能被用户选中了。
  
  除了这个选择机制之外,OpenGL提供了一个工具例程,以便在某些情况下通过限定在视口(viewport)一个小区域内绘制来简化选择。通常你可以用这个例程决定哪个物体被画在光标附近了,这样你就能识别用户拾取了哪个物体。你也可以通过指定附加的裁剪面来界定一个选择区域;详见"Additional Clipping Planes"。因为拾取是选择的一个特殊情况,所以本章选讲选择,然后讲拾取。
  
  基本步骤
  建立名字矩阵
  命中记录
  一个选择的例子
  拾取
  关于编写使用选择的程序的提示
  
  ------------------------------------------------------------------------
  基本步骤
  
  为使用选择机制,你得作以下几步:
  1、用glSelectBuffer()指定用于返回命中记录的数组。
  2、以GL_SELECT为参数调用glRenderMode()进入选择模式。
  3、用glInitName()和glPushName()初始化名字栈。
  4、定义用于选择的视见体。通常它与你原来用于绘制场景的视见体不同。因此你或许会想用glPushMatrix()和glPopMatrix()来保存和恢复当前的变换矩阵。
  5、交替发布图元绘制命令和名字栈控制命令,这样每个感兴趣的图元都会被指定适当的名字。
  6、退出选择模式并处理返回的选择数据(命中记录)。
  
  后面的段落将描述glSelectBuffer()和glRenderMode()。下一节则讲解名字栈的控制。
  
  void glSelectBuffer(GLsizei size, GLuint *buffer);
  指定用于返回选择数据的数组。参数buffer是指向无符号整数(unsigned integer)数组的指针,数据就存在这个数组中,size参数说明数组中最多能够保存的值的个数。要在进入选择模式之前调用glSelectBuffer()!
  
  GLint glRenderMode(GLenum mode);
  控制应用程序是否进入绚染(rendering)、选择或反馈模式。mode参数可以是GL_RENDER(默认)、GL_SELECT或GL_FEEDBACK之一。应用程序将保持处于给定模式,直到再次以不同的参数调用glRenderMode()。在进入选择模式之前必须调用glSelectBuffer()指定选择数组。类似的,进入反馈模式之前要调用glFeedbackBuffer()指定反馈数组。如果当前模式是GL_SELECT或GL_FEEDBACK之一,那么glRenderMode()的返回值有意义。返回值是当前退出当前模式时,选择命中数或放在反馈数组中的值的个数。(译者注:调用此函数就会退出当前模式);负值意味着选择或反馈数组溢出(overflowed)。你可以用GL_RENDER_MODE调用glGetIntegerv()获取当前模式。
  
  -------------------------------------------------------------------------------
  建立名字矩阵
  
  正如前面提到的,名字栈是返回给你的选择信息的基础。要建立名字栈,首先用glInitNames()初始化它,这将简单地清空栈。然后当你发布相应的绘制命令时向其中加入整数名字。正如你可能想象,栈控制命令允许你压入名字(glPushName()),弹出名字(glPopName()),替换栈顶的名字(glLoadName())。
  /********************************************************************/
  Example 12-1: Creating a Name Stack
  glInitNames();
  glPushName(-1);
  
  glPushMatrix(); /* save the current transformation state */
  
  /*to do: create your desired viewing volume here */
  
  glLoadName(1);
  drawSomeObject();
  glLoadName(2);
  drawAnotherObject();
  glLoadName(3);
  drawYetAnotherObject();
  drawJustOneMoreObject();
  
  glPopMatrix (); /* restore the previous transformation state*/
  /********************************************************************/
  
  在这个例子中,前两个被绘制的物体有自己的名字,第三和第四个共用一个名字。这样,如果第三或第四个物体中的一个或全部引起一个选择命中,只有一个命中记录返回给你。如果处理命中记录时不想区分各个物体的话,可以让多个物体共享一个名字。
  
  void glInitNames(void);
  清空名字栈。
  
  void glPushName(GLuint name);
  将name压入名字栈。压入名字超过栈容量时将生成一个GL_STACK_OVERFLOW错误。名字栈深度因OpenGL实现(implementations)不同而不同,但最少要能容纳64个名字。你可以用参数GL_NAME_STACK_DEPTH调用glGetIntegerv()以获取名字栈深度。
  
  void glPopName(void);
  弹出名字栈栈顶的那一个名字。从空栈中弹出名字引发GL_STACK_UNDERFLOW错误。
  
  void glLoadName(GLuint name);
  用name取代名字栈栈顶的那个名字。如果栈是空的,刚调用过glInitName()后就是这样,glLoadName()生成一个GL_INVALID_OPRATION错。为避免这种情况,如果栈初始时是空的,那么在调用glLoadName()之前至少调用一次glPushName()以在名字栈中放上点东西。
  
  如果不是在选择模式下,对glPushName()、glPopName()、glLoadName()的调用将被忽略。这使得在选择模式和正常的绚染模式下用相同的绘制代码大为简化。
  
  -------------------------------------------------------------------------------
  命中记录
  
  在选择模式下,被视见体裁剪的每个图元引起一个选择命中。当前一个名字栈控制命令被执行或glRenderMode()被调用后,OpenGL将一个命中记录写进选择数组,如果从上一次名字栈操纵或glRenderMode()调用以来有了一个命中记录的话。这个过程中,共用同样名字的物体-例如:由多个图元组成的物体-不生成多个命中记录。当然,命中记录不保证会被写进数组中直到glRenderMode()被调用。
  
  除图元之外,glRasterPos()产生的有效坐标也可以引起选择命中。在多边形的情况下,如果它已经被消隐掉的话不会有命中记录出现。
  
  每个命中记录由四项组成,依次是:
  当命中出现时名字栈中的名字数
  至上次记录的命中以来,被视见体裁剪后的图元的所有顶点的窗口Z坐标的 最大和最小值
  本次命中时名字栈的内容,最底元素最前。
  
  当前你进入选择模式时,OpenGL初始化一个指针指向选择数组的起点。每写入一个命中记录,指针相应更新。如果写入一个命中记录会使数组中值的个数超过glSelectBuffer()的size参数时,OpenGL会写入尽可能多的记录并设置一个溢出标志。当用glRenderMode()退出选择模式时,这条命令返回被写入的记录的个数(包括一条部分记录如果有的话),清除名字栈,复位溢出标识,重置栈指针。如设定溢了出标识则返回值是-1。

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/Crazyjumper/archive/2007/10/18/1830865.aspx

posted @ 2009-09-01 06:28 RedLight 阅读(515) | 评论 (0)编辑 收藏

OpenGL中的Alpha测试,深度测试,模板测试,裁减测试

大家好。现在因为参加工作的关系,又是长时间没有更新。趁着国庆的空闲,总算是又写出了一课。我感觉入门的知识已经快要介绍完毕,这课之后再有一课,就可以告一段落了。以后我可能会写一些自己在这方面的体会,做一份进阶课程。

现在即将放出的是第十二课的内容。

首先还是以前课程的连接:

第一课,编写第一个OpenGL程序
第二课,绘制几何图形
第三课,绘制几何图形的一些细节问题
第四课,颜色的选择
第五课,三维的空间变换
第六课,动画的制作
第七课,使用光照来表现立体感
第八课,使用显示列表
第九课,使用混合来实现半透明效果
第十课,BMP文件与像素操作
第十一课,纹理的使用入门
第十二课,OpenGL片断测试  ——→  本次课程的内容

片断测试其实就是测试每一个像素,只有通过测试的像素才会被绘制,没有通过测试的像素则不进行绘制。OpenGL提供了多种测试操作,利用这些操作可以实现一些特殊的效果。
我们在前面的课程中,曾经提到了“深度测试”的概念,它在绘制三维场景的时候特别有用。在不使用深度测试的时候,如果我们先绘制一个距离较近的物体,再绘制距离较远的物体,则距离远的物体因为后绘制,会把距离近的物体覆盖掉,这样的效果并不是我们所希望的。
如果使用了深度测试,则情况就会有所不同:每当一个像素被绘制,OpenGL就记录这个像素的“深度”(深度可以理解为:该像素距离观察者的距离。深度值越大,表示距离越远),如果有新的像素即将覆盖原来的像素时,深度测试会检查新的深度是否会比原来的深度值小。如果是,则覆盖像素,绘制成功;如果不是,则不会覆盖原来的像素,绘制被取消。这样一来,即使我们先绘制比较近的物体,再绘制比较远的物体,则远的物体也不会覆盖近的物体了。
实际上,只要存在深度缓冲区,无论是否启用深度测试,OpenGL在像素被绘制时都会尝试将深度数据写入到缓冲区内,除非调用了glDepthMask(GL_FALSE)来禁止写入。这些深度数据除了用于常规的测试外,还可以有一些有趣的用途,比如绘制阴影等等。

除了深度测试,OpenGL还提供了剪裁测试、Alpha测试和模板测试。

1、剪裁测试
剪裁测试用于限制绘制区域。我们可以指定一个矩形的剪裁窗口,当启用剪裁测试后,只有在这个窗口之内的像素才能被绘制,其它像素则会被丢弃。换句话说,无论怎么绘制,剪裁窗口以外的像素将不会被修改。
有的朋友可能玩过《魔兽争霸3》这款游戏。游戏时如果选中一个士兵,则画面下方的一个方框内就会出现该士兵的头像。为了保证该头像无论如何绘制都不会越界而覆盖到外面的像素,就可以使用剪裁测试。

可以通过下面的代码来启用或禁用剪裁测试:
glEnable(GL_SCISSOR_TEST);  // 启用剪裁测试
glDisable(GL_SCISSOR_TEST); // 禁用剪裁测试

可以通过下面的代码来指定一个位置在(x, y),宽度为width,高度为height的剪裁窗口。
glScissor(x, y, width, height);

注意,OpenGL窗口坐标是以左下角为(0, 0),右上角为(width, height)的,这与Windows系统窗口有所不同。

还有一种方法可以保证像素只绘制到某一个特定的矩形区域内,这就是视口变换(在第五课第3节中有介绍)。但视口变换和剪裁测试是不同的。视口变换是将所有内容缩放到合适的大小后,放到一个矩形的区域内;而剪裁测试不会进行缩放,超出矩形范围的像素直接忽略掉。


2、Alpha测试
在前面的课程中,我们知道像素的Alpha值可以用于混合操作。其实Alpha值还有一个用途,这就是Alpha测试。当每个像素即将绘制时,如果启动了Alpha测试,OpenGL会检查像素的Alpha值,只有Alpha值满足条件的像素才会进行绘制(严格的说,满足条件的像素会通过本项测试,进行下一种测试,只有所有测试都通过,才能进行绘制),不满足条件的则不进行绘制。这个“条件”可以是:始终通过(默认情况)、始终不通过、大于设定值则通过、小于设定值则通过、等于设定值则通过、大于等于设定值则通过、小于等于设定值则通过、不等于设定值则通过。
如果我们需要绘制一幅图片,而这幅图片的某些部分又是透明的(想象一下,你先绘制一幅相片,然后绘制一个相框,则相框这幅图片有很多地方都是透明的,这样就可以透过相框看到下面的照片),这时可以使用Alpha测试。将图片中所有需要透明的地方的Alpha值设置为0.0,不需要透明的地方Alpha值设置为1.0,然后设置Alpha测试的通过条件为:“大于0.5则通过”,这样便能达到目的。当然也可以设置需要透明的地方Alpha值为1.0,不需要透明的地方Alpha值设置为0.0,然后设置条件为“小于0.5则通过”。Alpha测试的设置方式往往不只一种,可以根据个人喜好和实际情况需要进行选择。

可以通过下面的代码来启用或禁用Alpha测试:


glEnable(GL_ALPHA_TEST);  // 启用Alpha测试
glDisable(GL_ALPHA_TEST); // 禁用Alpha测试


可以通过下面的代码来设置Alpha测试条件为“大于0.5则通过”:


glAlphaFunc(GL_GREATER, 0.5f);


该函数的第二个参数表示设定值,用于进行比较。第一个参数是比较方式,除了GL_LESS(小于则通过)外,还可以选择:
GL_ALWAYS(始终通过),
GL_NEVER(始终不通过),
GL_LESS(小于则通过),
GL_LEQUAL(小于等于则通过),
GL_EQUAL(等于则通过),
GL_GEQUAL(大于等于则通过),
GL_NOTEQUAL(不等于则通过)。
现在我们来看一个实际例子。一幅照片图片,一幅相框图片,如何将它们组合在一起呢?为了简单起见,我们使用前面两课一直使用的24位BMP文件来作为图片格式。(因为发布到网络上,为了节约容量,我所发布的是JPG格式。大家下载后可以用Windows XP自带的画图工具打开,并另存为24位BMP格式)

注:第一幅图片是著名网络游戏《魔兽世界》的一幅桌面背景,用在这里希望没有涉及版权问题。如果有什么不妥,请及时指出,我会立即更换。

在24位的BMP文件格式中,BGR三种颜色各占8位,没有保存Alpha值,因此无法直接使用Alpha测试。注意到相框那幅图片中,所有需要透明的位置都是白色,所以我们在程序中设置所有白色(或很接近白色)的像素Alpha值为0.0,设置其它像素Alpha值为1.0,然后设置Alpha测试的条件为“大于0.5则通过”即可。这种使用某种特殊颜色来代表透明颜色的技术,有时又被成为Color Key技术。
利用前面第11课的一段代码,将图片读取为纹理,然后利用下面这个函数来设置“当前纹理”中每一个像素的Alpha值。


/* 将当前纹理BGR格式转换为BGRA格式
 * 纹理中像素的RGB值如果与指定rgb相差不超过absolute,则将Alpha设置为0.0,否则设置为1.0
 */
void texture_colorkey(GLubyte r, GLubyte g, GLubyte b, GLubyte absolute)
{
    GLint width, height;
    GLubyte* pixels = 0;

    // 获得纹理的大小信息
    glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width);
    glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height);

    // 分配空间并获得纹理像素
    pixels = (GLubyte*)malloc(width*height*4);
    if( pixels == 0 )
        return;
    glGetTexImage(GL_TEXTURE_2D, 0, GL_BGRA_EXT, GL_UNSIGNED_BYTE, pixels);

    // 修改像素中的Alpha值
    // 其中pixels[i*4], pixels[i*4+1], pixels[i*4+2], pixels[i*4+3]
    //   分别表示第i个像素的蓝、绿、红、Alpha四种分量,0表示最小,255表示最大
    {
        GLint i;
        GLint count = width * height;
        for(i=0; i<count; ++i)
        {
            if( abs(pixels[i*4] - b) <= absolute
             && abs(pixels[i*4+1] - g) <= absolute
             && abs(pixels[i*4+2] - r) <= absolute )
                pixels[i*4+3] = 0;
            else
                pixels[i*4+3] = 255;
        }
    }

    // 将修改后的像素重新设置到纹理中,释放内存
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0,
        GL_BGRA_EXT, GL_UNSIGNED_BYTE, pixels);
    free(pixels);
}

有了纹理后,我们开启纹理,指定合适的纹理坐标并绘制一个矩形,这样就可以在屏幕上将图片绘制出来。我们先绘制相片的纹理,再绘制相框的纹理。程序代码如下:


void display(void)
{
    static int initialized   = 0;
    static GLuint texWindow  = 0;
    static GLuint texPicture = 0;

    // 执行初始化操作,包括:读取相片,读取相框,将相框由BGR颜色转换为BGRA,启用二维纹理
    if( !initialized )
    {
        texPicture = load_texture("pic.bmp");
        texWindow  = load_texture("window.bmp");
        glBindTexture(GL_TEXTURE_2D, texWindow);
        texture_colorkey(255, 255, 255, 10);

        glEnable(GL_TEXTURE_2D);

        initialized = 1;
    }

    // 清除屏幕
    glClear(GL_COLOR_BUFFER_BIT);

    // 绘制相片,此时不需要进行Alpha测试,所有的像素都进行绘制
    glBindTexture(GL_TEXTURE_2D, texPicture);
    glDisable(GL_ALPHA_TEST);
    glBegin(GL_QUADS);
        glTexCoord2f(0, 0);     glVertex2f(-1.0f, -1.0f);
        glTexCoord2f(0, 1);     glVertex2f(-1.0f,  1.0f);
        glTexCoord2f(1, 1);     glVertex2f( 1.0f,  1.0f);
        glTexCoord2f(1, 0);     glVertex2f( 1.0f, -1.0f);
    glEnd();

    // 绘制相框,此时进行Alpha测试,只绘制不透明部分的像素
    glBindTexture(GL_TEXTURE_2D, texWindow);
    glEnable(GL_ALPHA_TEST);
    glAlphaFunc(GL_GREATER, 0.5f);
    glBegin(GL_QUADS);
        glTexCoord2f(0, 0);     glVertex2f(-1.0f, -1.0f);
        glTexCoord2f(0, 1);     glVertex2f(-1.0f,  1.0f);
        glTexCoord2f(1, 1);     glVertex2f( 1.0f,  1.0f);
        glTexCoord2f(1, 0);     glVertex2f( 1.0f, -1.0f);
    glEnd();

    // 交换缓冲
    glutSwapBuffers();
}

其中:load_texture函数是从第11课中照搬过来的(该函数还使用了一个power_of_two函数,一个BMP_Header_Length常数,同样照搬),无需进行修改。main函数跟其它课程的基本相同,不再重复。
程序运行后,会发现相框与相片的衔接有些不自然,这是因为相框某些边缘部分虽然肉眼看上去是白色,但其实RGB值与纯白色相差并不少,因此程序计算其Alpha值时认为其不需要透明。解决办法是仔细处理相框中的每个像素,在需要透明的地方涂上纯白色,这也许是一件很需要耐心的工作。


    大家可能会想:前面我们学习过混合操作,混合可以实现半透明,自然也可以通过设定实现全透明。也就是说,Alpha测试可以实现的效果几乎都可以通过OpenGL混合功能来实现。那么为什么还需要一个Alpha测试呢?答案就是,这与性能相关。Alpha测试只要简单的比较大小就可以得到最终结果,而混合操作一般需要进行乘法运算,性能有所下降。另外,OpenGL测试的顺序是:剪裁测试、Alpha测试、模板测试、深度测试。如果某项测试不通过,则不会进行下一步,而只有所有测试都通过的情况下才会执行混合操作。因此,在使用Alpha测试的情况下,透明的像素就不需要经过模板测试和深度测试了;而如果使用混合操作,即使透明的像素也需要进行模板测试和深度测试,性能会有所下降。还有一点:对于那些“透明”的像素来说,如果使用Alpha测试,则“透明”的像素不会通过测试,因此像素的深度值不会被修改;而使用混合操作时,虽然像素的颜色没有被修改,但它的深度值则有可能被修改掉了。
因此,如果所有的像素都是“透明”或“不透明”,没有“半透明”时,应该尽量采用Alpha测试而不是采用混合操作。当需要绘制半透明像素时,才采用混合操作。

   3、模板测试
模板测试是所有OpenGL测试中比较复杂的一种。

首先,模板测试需要一个模板缓冲区,这个缓冲区是在初始化OpenGL时指定的。如果使用GLUT工具包,可以在调用glutInitDisplayMode函数时在参数中加上GLUT_STENCIL,例如:


glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_STENCIL);

在Windows操作系统中,即使没有明确要求使用模板缓冲区,有时候也会分配模板缓冲区。但为了保证程序的通用性,最好还是明确指定使用模板缓冲区。如果确实没有分配模板缓冲区,则所有进行模板测试的像素全部都会通过测试。

通过glEnable/glDisable可以启用或禁用模板测试。


glEnable(GL_STENCIL_TEST);  // 启用模板测试
glDisable(GL_STENCIL_TEST); // 禁用模板测试


OpenGL在模板缓冲区中为每个像素保存了一个“模板值”,当像素需要进行模板测试时,将设定的模板参考值与该像素的“模板值”进行比较,符合条件的通过测试,不符合条件的则被丢弃,不进行绘制。
条件的设置与Alpha测试中的条件设置相似。但注意Alpha测试中是用浮点数来进行比较,而模板测试则是用整数来进行比较。比较也有八种情况:始终通过、始终不通过、大于则通过、小于则通过、大于等于则通过、小于等于则通过、等于则通过、不等于则通过。


glStencilFunc(GL_LESS, 3, mask);

这段代码设置模板测试的条件为:“小于3则通过”。glStencilFunc的前两个参数意义与glAlphaFunc的两个参数类似,第三个参数的意义为:如果进行比较,则只比较mask中二进制为1的位。例如,某个像素模板值为5(二进制101),而mask的二进制值为00000011,因为只比较最后两位,5的最后两位为01,其实是小于3的,因此会通过测试。

如何设置像素的“模板值”呢?glClear函数可以将所有像素的模板值复位。代码如下:


glClear(GL_STENCIL_BUFFER_BIT);

可以同时复位颜色值和模板值:


glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

正如可以使用glClearColor函数来指定清空屏幕后的颜色那样,也可以使用glClearStencil函数来指定复位后的“模板值”。

每个像素的“模板值”会根据模板测试的结果和深度测试的结果而进行改变。


glStencilOp(fail, zfail, zpass);

该函数指定了三种情况下“模板值”该如何变化。第一个参数表示模板测试未通过时该如何变化;第二个参数表示模板测试通过,但深度测试未通过时该如何变化;第三个参数表示模板测试和深度测试均通过时该如何变化。如果没有起用模板测试,则认为模板测试总是通过;如果没有启用深度测试,则认为深度测试总是通过)
变化可以是:
GL_KEEP(不改变,这也是默认值),
GL_ZERO(回零),
GL_REPLACE(使用测试条件中的设定值来代替当前模板值),
GL_INCR(增加1,但如果已经是最大值,则保持不变),
GL_INCR_WRAP(增加1,但如果已经是最大值,则从零重新开始),
GL_DECR(减少1,但如果已经是零,则保持不变),
GL_DECR_WRAP(减少1,但如果已经是零,则重新设置为最大值),
GL_INVERT(按位取反)。

在新版本的OpenGL中,允许为多边形的正面和背面使用不同的模板测试条件和模板值改变方式,于是就有了glStencilFuncSeparate函数和glStencilOpSeparate函数。这两个函数分别与glStencilFunc和glStencilOp类似,只在最前面多了一个参数face,用于指定当前设置的是哪个面。可以选择GL_FRONT, GL_BACK, GL_FRONT_AND_BACK。

注意:模板缓冲区与深度缓冲区有一点不同。无论是否启用深度测试,当有像素被绘制时,总会重新设置该像素的深度值(除非设置glDepthMask(GL_FALSE);)。而模板测试如果不启用,则像素的模板值会保持不变,只有启用模板测试时才有可能修改像素的模板值。(这一结论是我自己的实验得出的,暂时没发现什么资料上是这样写。如果有不正确的地方,欢迎指正)
另外,模板测试虽然是从OpenGL 1.0就开始提供的功能,但是对于个人计算机而言,硬件实现模板测试的似乎并不多,很多计算机系统直接使用CPU运算来完成模板测试。因此在一些老的显卡,或者是多数集成显卡上,大量而频繁的使用模板测试可能造成程序运行效率低下。即使是当前配置比较高端的个人计算机,也尽量不要使用glStencilFuncSeparate和glStencilOpSeparate函数。

从前面所讲可以知道,使用剪裁测试可以把绘制区域限制在一个矩形的区域内。但如果需要把绘制区域限制在一个不规则的区域内,则需要使用模板测试。
例如:绘制一个湖泊,以及周围的树木,然后绘制树木在湖泊中的倒影。为了保证倒影被正确的限制在湖泊表面,可以使用模板测试。具体的步骤如下:
(1) 关闭模板测试,绘制地面和树木。
(2) 开启模板测试,使用glClear设置所有像素的模板值为0。
(3) 设置glStencilFunc(GL_ALWAYS, 1, 1); glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);绘制湖泊水面。这样一来,湖泊水面的像素的“模板值”为1,而其它地方像素的“模板值”为0。
(4) 设置glStencilFunc(GL_EQUAL, 1, 1); glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);绘制倒影。这样一来,只有“模板值”为1的像素才会被绘制,因此只有“水面”的像素才有可能被倒影的像素替换,而其它像素则保持不变。


   我们仍然来看一个实际的例子。这是一个比较简单的场景:空间中有一个球体,一个平面镜。我们站在某个特殊的观察点,可以看到球体在平面镜中的镜像,并且镜像处于平面镜的边缘,有一部分因为平面镜大小的限制,而无法显示出来。整个场景的效果如下图:


绘制这个场景的思路跟前面提到的湖面倒影是接近的。
假设平面镜所在的平面正好是X轴和Y轴所确定的平面,则球体和它在平面镜中的镜像是关于这个平面对称的。我们用一个draw_sphere函数来绘制球体,先调用该函数以绘制球体本身,然后调用glScalef(1.0f, 1.0f, -1.0f); 再调用draw_sphere函数,就可以绘制球体的镜像。
另外需要注意的地方就是:因为是绘制三维的场景,我们开启了深度测试。但是站在观察者的位置,球体的镜像其实是在平面镜的“背后”,也就是说,如果按照常规的方式绘制,平面镜会把镜像覆盖掉,这不是我们想要的效果。解决办法就是:设置深度缓冲区为只读,绘制平面镜,然后设置深度缓冲区为可写的状态,绘制平面镜“背后”的镜像。
有的朋友可能会问:如果在绘制镜像的时候关闭深度测试,那镜像不就不会被平面镜遮挡了吗?为什么还要开启深度测试,又需要把深度缓冲区设置为只读呢?实际情况是:虽然关闭深度测试确实可以让镜像不被平面镜遮挡,但是镜像本身会出现若干问题。我们看到的镜像是一个球体,但实际上这个球体是由很多的多边形所组成的,这些多边形有的代表了我们所能看到的“正面”,有的则代表了我们不能看到的“背面”。如果关闭深度测试,而有的“背面”多边形又比“正面”多边形先绘制,就会造成球体的背面反而把正面挡住了,这不是我们想要的效果。为了确保正面可以挡住背面,应该开启深度测试。
绘制部分的代码如下:


void draw_sphere()
{
    // 设置光源
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    {
        GLfloat
            pos[]     = {5.0f, 5.0f, 0.0f, 1.0f},
            ambient[] = {0.0f, 0.0f, 1.0f, 1.0f};
        glLightfv(GL_LIGHT0, GL_POSITION, pos);
        glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
    }

    // 绘制一个球体
    glColor3f(1, 0, 0);
    glPushMatrix();
    glTranslatef(0, 0, 2);
    glutSolidSphere(0.5, 20, 20);
    glPopMatrix();
}

void display(void)
{
    // 清除屏幕
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // 设置观察点
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(60, 1, 5, 25);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    gluLookAt(5, 0, 6.5, 0, 0, 0, 0, 1, 0);

    glEnable(GL_DEPTH_TEST);

    // 绘制球体
    glDisable(GL_STENCIL_TEST);
    draw_sphere();

    // 绘制一个平面镜。在绘制的同时注意设置模板缓冲。
    // 另外,为了保证平面镜之后的镜像能够正确绘制,在绘制平面镜时需要将深度缓冲区设置为只读的。
    // 在绘制时暂时关闭光照效果
    glClearStencil(0);
    glClear(GL_STENCIL_BUFFER_BIT);
    glStencilFunc(GL_ALWAYS, 1, 0xFF);
    glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
    glEnable(GL_STENCIL_TEST);

    glDisable(GL_LIGHTING);
    glColor3f(0.5f, 0.5f, 0.5f);
    glDepthMask(GL_FALSE);
    glRectf(-1.5f, -1.5f, 1.5f, 1.5f);
    glDepthMask(GL_TRUE);

    // 绘制一个与先前球体关于平面镜对称的球体,注意光源的位置也要发生对称改变
    // 因为平面镜是在X轴和Y轴所确定的平面,所以只要Z坐标取反即可实现对称
    // 为了保证球体的绘制范围被限制在平面镜内部,使用模板测试
    glStencilFunc(GL_EQUAL, 1, 0xFF);
    glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
    glScalef(1.0f, 1.0f, -1.0f);
    draw_sphere();

    // 交换缓冲
    glutSwapBuffers();

    // 截图
    grab();
}


其中display函数的末尾调用了一个grab函数,它保存当前的图象到一个BMP文件。这个函数本来是在第十课和第十一课中都有所使用的。但是我发现它有一个bug,现在进行了修改:在函数最开头的部分加上一句:glReadBuffer(GL_FRONT);即可。注意这个函数最好是在绘制完毕后(如果是使用双缓冲,则应该在交换缓冲后)立即调用。


  大家可能会有这样的感觉:模板测试的设置是如此复杂,它可以实现的功能应该很多,肯定不止这样一个“限制像素的绘制范围”。事实上也是如此,不过现在我们暂时只讲这些。

其实,如果不需要绘制半透明效果,有时候可以用混合功能来代替模板测试。就绘制镜像这个例子来说,可以采用下面的步骤:
(1) 清除屏幕,在glClearColor中设置合适的值确保清除屏幕后像素的Alpha值为0.0
(2) 关闭混合功能,绘制球体本身,设置合适的颜色(或者光照与材质)以确保所有被绘制的像素的Alpha值为0.0
(3) 绘制平面镜,设置合适的颜色(或者光照与材质)以确保所有被绘制的像素的Alpha值为1.0
(4) 启用混合功能,用GL_DST_ALPHA作为源因子,GL_ONE_MINUS_DST_ALPHA作为目标因子,这样就实现了只有原来Alpha为1.0的像素才能被修改,而原来Alpha为0.0的像素则保持不变。这时再绘制镜像物体,注意确保所有被绘制的像素的Alpha值为1.0。
在有的OpenGL实现中,模板测试是软件实现的,而混合功能是硬件实现的,这时候可以考虑这样的代替方法以提高运行效率。但是并非所有的模板测试都可以用混合功能来代替,并且这样的代替显得不自然,复杂而且容易出错。
另外始终注意:使用混合来模拟时,即使某个像素原来的Alpha值为0.0,以致于在绘制后其颜色不会有任何变化,但是这个像素的深度值有可能会被修改,而如果是使用模板测试,没有通过测试的像素其深度值不会发生任何变化。而且,模板测试和混合功能中,像素模板值的修改方式是不一样的。
  

  4、深度测试
在本课的开头,已经简单的叙述了深度测试。这里是完整的内容。

深度测试需要深度缓冲区,跟模板测试需要模板缓冲区是类似的。如果使用GLUT工具包,可以在调用glutInitDisplayMode函数时在参数中加上GLUT_DEPTH,这样来明确指定要求使用深度缓冲区。
深度测试和模板测试的实现原理很类似,都是在一个缓冲区保存像素的某个值,当需要进行测试时,将保存的值与另一个值进行比较,以确定是否通过测试。两者的区别在于:模板测试是设定一个值,在测试时用这个设定值与像素的“模板值”进行比较,而深度测试是根据顶点的空间坐标计算出深度,用这个深度与像素的“深度值”进行比较。也就是说,模板测试需要指定一个值作为比较参考,而深度测试中,这个比较用的参考值是OpenGL根据空间坐标自动计算的。

通过glEnable/glDisable函数可以启用或禁用深度测试。
glEnable(GL_DEPTH_TEST);  // 启用深度测试
glDisable(GL_DEPTH_TEST); // 禁用深度测试

至于通过测试的条件,同样有八种,与Alpha测试中的条件设置相同。条件设置是通过glDepthFunc函数完成的,默认值是GL_LESS。
glDepthFunc(GL_LESS);

与模板测试相比,深度测试的应用要频繁得多。几乎所有的三维场景绘制都使用了深度测试。正因为这样,几乎所有的OpenGL实现都对深度测试提供了硬件支持,所以虽然两者的实现原理类似,但深度测试很可能会比模板测试快得多。当然了,两种测试在应用上很少有交集,一般不会出现使用一种测试去代替另一种测试的情况。


   小结:
本次课程介绍了OpenGL所提供的四种测试,分别是剪裁测试、Alpha测试、模板测试、深度测试。OpenGL会对每个即将绘制的像素进行以上四种测试,每个像素只有通过一项测试后才会进入下一项测试,而只有通过所有测试的像素才会被绘制,没有通过测试的像素会被丢弃掉,不进行绘制。每种测试都可以单独的开启或者关闭,如果某项测试被关闭,则认为所有像素都可以顺利通过该项测试。
剪裁测试是指:只有位于指定矩形内部的像素才能通过测试。
Alpha测试是指:只有Alpha值与设定值相比较,满足特定关系条件的像素才能通过测试。
模板测试是指:只有像素模板值与设定值相比较,满足特定关系条件的像素才能通过测试。
深度测试是指:只有像素深度值与新的深度值比较,满足特定关系条件的像素才能通过测试。
上面所说的特定关系条件可以是大于、小于、等于、大于等于、小于等于、不等于、始终通过、始终不通过这八种。
模板测试需要模板缓冲区,深度测试需要深度缓冲区。这些缓冲区都是在初始化OpenGL时指定的。如果使用GLUT工具包,则可以在glutInitDisplayMode函数中指定。无论是否开启深度测试,OpenGL在像素被绘制时都会尝试修改像素的深度值;而只有开启模板测试时,OpenGL才会尝试修改像素的模板值,模板测试被关闭时,OpenGL在像素被绘制时也不会修改像素的模板值。
利用这些测试操作可以控制像素被绘制或不被绘制,从而实现一些特殊效果。利用混合功能可以实现半透明,通过设置也可以实现完全透明,因而可以模拟像素颜色的绘制或不绘制。但注意,这里仅仅是颜色的模拟。OpenGL可以为像素保存颜色、深度值和模板值,利用混合实现透明时,像素颜色不发生变化,但深度值则会可能变化,模板值受glStencilFunc函数中第三个参数影响;利用测试操作实现透明时,像素颜色不发生变化,深度值也不发生变化,模板值受glStencilFunc函数中前两个参数影响。
此外,修正了第十课、第十一课中的一个函数中的bug。在grab函数中,应该在最开头加上一句glReadBuffer(GL_FRONT);以保证读取到的内容正好就是显示的内容。

 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/Crazyjumper/archive/2007/12/26/1968567.aspx

posted @ 2009-09-01 06:27 RedLight 阅读(617) | 评论 (0)编辑 收藏

优化3D图形渲染通道负载(转)

一般来说, 定位渲染通道瓶颈的方法就是改变渲染通道每个步骤的工作量, 如果吞吐量也改变了, 那个步骤就是瓶颈.。找到了瓶颈就要想办法消除瓶颈, 可以减少该步骤的工作量, 增加其他步骤的工作量。

   一般在光栅化之前的瓶颈称作”transform bound”, 三角形设置处理后的瓶颈称作”fill bound”定位瓶颈的办法:
  • 1.改变帧缓冲或者渲染目标(Render Target)的颜色深度(16 到 32 位), 如果帧速改变了, 那么瓶颈应该在帧缓冲(RenderTarget)的填充率上。
  • 2.否则试试改变贴图大小和贴图过滤设置, 如果帧速变了,那么瓶颈应该是在贴图这里。
  • 3.否则改变分辨率.如果帧速改变了, 那么改变一下pixel shader的指令数量, 如果帧速变了, 那么瓶颈应该就是pixel shader. 否则瓶颈就在光栅化过程中。
  • 4.否则, 改变顶点格式的大小, 如果帧速改变了, 那么瓶颈应该在显卡带宽上。
  • 5.如果以上都不是, 那么瓶颈就在CPU这一边。
  • 优化方法36条:
  • 1.尽量减少无用的顶点数据, 比如贴图坐标, 如果有Object使用2组有的使用1组, 那么不 要将他们放在一个vertex buffer中, 这样可以减少传输的数据量。
  • 2.使用多个streamsource, 比如SkinMesh渲染, 可以把顶点坐标和法线这些每一帧都要修改的数据放在一个动态VB中, 其它不需要修改的(如贴图坐标)放到一个静态VB中, 这样就减少了数据传输量。
  • 3.尽量使用16位的索引缓冲,避免32位的. 一方面浪费带宽, 一方面也不是所有的显卡都支持32位的索引缓冲。
  • 4.可以考虑使用vertex shader来计算静态VB中的数据.比如SkinMesh的顶点可以放到vectex shader中计算, 这样就可以避免每一帧都从AGP内存中向显存传送数据. 这样也可以使用静态VB了。
  • 5.坚决避免使用Draw**UP一族的函数来绘制多边形。
  • 6.在设计程序之前好好规划一下显卡内存的使用, 确保framebuffer, 贴图, 静态VB能够正好放入显卡的本地内存中。
  • 7.尽量使顶点格式大小是32字节的倍数.可以考虑使用压缩过的顶点格式然后用vertex shader去解. 或者留下冗余的部分, 使顶点大小刚好使32字节的倍数。
  • 8.顶点在顶点缓冲中的顺序尽量符合绘制的顺序, 考虑使用strips来代替list。
  • 9.如果可能尽量多的使用static vertex buffer代替dynamic vertex buffer。
  • 10.动态VB使用DISCARD参数来lock更新, 使用NOOVERWRITE来添加.尽量不要使用不带参数的lock调用(0)。
  • 11.尽量减少lock的次数, 有些东西并不一定非要每一帧都更新VB, 比如人物动画一般每秒钟更新30次VB基本上就够了。
  • 12.如果是因为需要绘制的顶点数据太多了可以考虑使用LOD, 但是现在的显卡的绘制能力都很强劲, 所以需要权衡一下LOD是否能够带来相应的好处, 如果过分的强化LOD很可能将瓶颈转移到CPU这边。
  • 13.避免过多的顶点计算,比如过多的光源, 过于复杂的光照计算(复杂的光照模型), 纹理自动生成的开启也会增加顶点的计算量. 如果贴图坐标变换矩阵不是单位矩阵, 也会造成顶点计算量的增加, 所以如果纹理变换已经结束, 记得要将纹理变换矩阵设为单位矩阵同时调整贴图坐标。
  • 14.避免Vertex shader指令数量太多或者分支过多, 尽量减少vertex shader的长度和复杂程度. 尽量使用swizzling代替mov。
  • 15.如果图象质量方面的计算(pixel shader)范围很大, 并且很复杂, 可以考虑试试全屏反走样。说不定更快。
  • 16.尽量按照front – back的顺序来绘制。
  • 17.在shader中判断Z值可以避免绘制不可见的象素, 但是nvidia建议简单的shader不要这么做.(Don't do this in a simple shader)。
  • 18.如果可能, 尽量使用vertex shader来代替pixel shader.将计算从逐象素变成逐顶点。
  • 19.尽量降低贴图的大小.过大的贴图可能造成贴图cache过载, 从而导致贴图cache命中降低.过大的贴图会导致显存过载, 这时候贴图是从系统内存中取的。
  • 20.只要可能就用16位色的贴图, 如环境贴图或者shadow map.它们用32位色的贴图实在是浪费。
  • 21.考虑使用DXT 贴图压缩。
  • 22.如果可能,使用简单的贴图过滤或者mip map, 除非必要否则尽量不要使用三线过滤和各项异性过滤. light map 和 环境贴图基本上都不需要使用它们。
  • 23.只有真正需要修改的贴图才使用Dynamic, 并且使用DISCRAD和WRITEONLY来lock。
  • 24.太多的帧缓冲读写可以考虑关闭Z-Writes如有些多pass的渲染中的后续pass或者粒子系统等半透明几何物体(如果可以)。
  • 25.可能的话尽量使用alpha test代替alpha blending。
  • 26.如果不需要stencil buffer就尽量使用16位的Z buffer。
  • 27.减小RenderTarget 贴图的大小, 如shadow map 环境贴图. 可能根本不需要那么大效果就很好。
  • 28.Stencil 和 Z buffer 尽量一起clear. 他们本来就是一块缓冲。
  • 29.尽量减少渲染状态的切换, 尽量一次画尽可能多的多边形。(根据显卡性能决定最多画多少, 不过一般再多也不会多到哪里去。 除非你根本不需要贴图和渲染状态的切换)。
  • 30.尽量使用shader来代替Fixed Pipeline。
  • 31.尽量使用shader来实现来取代Multipass渲染效果。
  • 32.尽量优先先建立重要的资源, 如Render target, shaders, 贴图, VB, IB等等.以免显存过载的时候它们被创建到系统内存中。
  • 33.坚决不要在渲染循环中调用创建资源。
  • 34.按照shader和贴图分组后再渲染.先按照shaders分组再按贴图。
  • 35.Color Stencil Z buffer尽量在一次Clear调用中清除。
  • 36.一个Vertex buffer 的大小在2M-4M之间最好。

posted @ 2009-09-01 01:32 RedLight 阅读(572) | 评论 (0)编辑 收藏

OpenGL Performance Optimization(转)

SIGGRAPH '97

Course 24: OpenGL and Window System Integration

OpenGL Performance Optimization



Contents



1. Hardware vs. Software

OpenGL may be implemented by any combination of hardware and software. At the high-end, hardware may implement virtually all of OpenGL while at the low-end, OpenGL may be implemented entirely in software. In between are combination software/hardware implementations. More money buys more hardware and better performance.

Intro-level workstation hardware and the recent PC 3-D hardware typically implement point, line, and polygon rasterization in hardware but implement floating point transformations, lighting, and clipping in software. This is a good strategy since the bottleneck in 3-D rendering is usually rasterization and modern CPU's have sufficient floating point performance to handle the transformation stage.

OpenGL developers must remember that their application may be used on a wide variety of OpenGL implementations. Therefore one should consider using all possible optimizations, even those which have little return on the development system, since other systems may benefit greatly.

From this point of view it may seem wise to develop your application on a low-end system. There is a pitfall however; some operations which are cheep in software may be expensive in hardware. The moral is: test your application on a variety of systems to be sure the performance is dependable.



2. Application Organization

At first glance it may seem that the performance of interactive OpenGL applications is dominated by the performance of OpenGL itself. This may be true in some circumstances but be aware that the organization of the application is also significant.

2.1 High Level Organization

Multiprocessing

Some graphical applications have a substantial computational component other than 3-D rendering. Virtual reality applications must compute object interactions and collisions. Scientific visualization programs must compute analysis functions and graphical representations of data.

One should consider multiprocessing in these situations. By assigning rendering and computation to different threads they may be executed in parallel on multiprocessor computers.

For many applications, supporting multiprocessing is just a matter of partitioning the render and compute operations into separate threads which share common data structures and coordinate with synchronization primitives.

SGI's Performer is an example of a high level toolkit designed for this purpose.

Image quality vs. performance

In general, one wants high-speed animation and high-quality images in an OpenGL application. If you can't have both at once a reasonable compromise may be to render at low complexity during animation and high complexity for static images.

Complexity may refer to the geometric or rendering attributes of a database. Here are a few examples.

  • During interactive rotation (i.e. mouse button held down) render a reduced-polygon model. When drawing a static image draw the full polygon model.
  • During animation, disable dithering, smooth shading, and/or texturing. Enable them for the static image.
  • If texturing is required, use GL_NEAREST sampling and glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST ).
  • During animation, disable antialiasing. Enable antialiasing for the static image.
  • Use coarser NURBS/evaluator tesselation during animation. Use glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ) to inspect tesselation granularity and reduce if possible.

Level of detail management and culling

Objects which are distant from the viewer may be rendered with a reduced complexity model. This strategy reduces the demands on all stages of the graphics pipeline. Toolkits such as Inventor and Performer support this feature automatically.

Objects which are entirely outside of the field of view may be culled. This type of high level cull testing can be done efficiently with bounding boxes or spheres and have a major impact on performance. Again, toolkits such as Inventor and Performer have this feature.

2.2 Low Level Organization

The objects which are rendered with OpenGL have to be stored in some sort of data structure. Some data structures are more efficient than others with respect to how quickly they can be rendered.

Basically, one wants data structures which can be traversed quickly and passed to the graphics library in an efficient manner. For example, suppose we need to render a triangle strip. The data structure which stores the list of vertices may be implemented with a linked list or an array. Clearly the array can be traversed more quickly than a linked list. The way in which a vertex is stored in the data structure is also significant. High performance hardware can process vertexes specified by a pointer more quickly than those specified by three separate parameters.

An Example

Suppose we're writing an application which involves drawing a road map. One of the components of the database is a list of cities specified with a latitude, longitude and name. The data structure describing a city may be:
	struct city {
float latitute, longitude;	/* city location */
char *name;			/* city's name */
int large_flag;  		/* 0 = small, 1 = large */
};
A list of cities may be stored as an array of city structs.

Our first attempt at rendering this information may be:

	void draw_cities( int n, struct city citylist[] )
{
int i;
for (i=0; i < n; i++) {
if (citylist[i].large_flag) {
glPointSize( 4.0 );
}
else {
glPointSize( 2.0 );
}
glBegin( GL_POINTS );
glVertex2f( citylist[i].longitude, citylist[i].latitude );
glEnd();
glRasterPos2f( citylist[i].longitude, citylist[i].latitude );
glCallLists( strlen(citylist[i].name),
GL_BYTE,
citylist[i].name );
}
}
This is a poor implementation for a number of reasons:
  • glPointSize is called for every loop iteration.
  • only one point is drawn between glBegin and glEnd
  • the vertices aren't being specified in the most efficient manner
Here's a better implementation:
	void draw_cities( int n, struct city citylist[] )
{
int i;
/* draw small dots first */
glPointSize( 2.0 );
glBegin( GL_POINTS );
for (i=0; i < n ;i++) {
if (citylist[i].large_flag==0) {
glVertex2f( citylist[i].longitude, citylist[i].latitude );
}
}
glEnd();
/* draw large dots second */
glPointSize( 4.0 );
glBegin( GL_POINTS );
for (i=0; i < n ;i++) {
if (citylist[i].large_flag==1) {
glVertex2f( citylist[i].longitude, citylist[i].latitude );
}
}
glEnd();
/* draw city labels third */
for (i=0; i < n ;i++) {
glRasterPos2f( citylist[i].longitude, citylist[i].latitude );
glCallLists( strlen(citylist[i].name),
GL_BYTE,
citylist[i].name );
}
}
In this implementation we're only calling glPointSize twice and we're maximizing the number of vertices specified between glBegin and glEnd.

We can still do better, however. If we redesign the data structures used to represent the city information we can improve the efficiency of drawing the city points. For example:

	struct city_list {
int num_cities;		/* how many cities in the list */
float *position;	/* pointer to lat/lon coordinates */
char **name;		/* pointer to city names */
float size;		/* size of city points */
};
Now cities of different sizes are stored in separate lists. Position are stored sequentially in a dynamically allocated array. By reorganizing the data structures we've eliminated the need for a conditional inside the glBegin/glEnd loops. Also, we can render a list of cities using the GL_EXT_vertex_array extension if available, or at least use a more efficient version of glVertex and glRasterPos.
	/* indicates if server can do GL_EXT_vertex_array: */
GLboolean varray_available;
void draw_cities( struct city_list *list )
{
int i;
GLboolean use_begin_end;
/* draw the points */
glPointSize( list->size );
#ifdef GL_EXT_vertex_array
if (varray_available) {
glVertexPointerEXT( 2, GL_FLOAT, 0, list->num_cities, list->position );
glDrawArraysEXT( GL_POINTS, 0, list->num_cities );
use_begin_end = GL_FALSE;
}
else
#else
{
use_begin_end = GL_TRUE;
}
#endif
if (use_begin_end) {
glBegin(GL_POINTS);
for (i=0; i < list->num_cities; i++) {
glVertex2fv( &position[i*2] );
}
glEnd();
}
/* draw city labels */
for (i=0; i < list->num_cities ;i++) {
glRasterPos2fv( list->position[i*2] );
glCallLists( strlen(list->name[i]),
GL_BYTE, list->name[i] );
}
}
As this example shows, it's better to know something about efficient rendering techniques before designing the data structures. In many cases one has to find a compromize between data structures optimized for rendering and those optimized for clarity and convenience.

In the following sections the techniques for maximizing performance, as seen above, are explained.



3. OpenGL Optimization

There are many possibilities to improving OpenGL performance. The impact of any single optimization can vary a great deal depending on the OpenGL implementation. Interestingly, items which have a large impact on software renderers may have no effect on hardware renderers, and vice versa! For example, smooth shading can be expensive in software but free in hardware While glGet* can be cheap in software but expensive in hardware.

After each of the following techniques look for a bracketed list of symbols which relates the significance of the optimization to your OpenGL system:

  • H - beneficial for high-end hardware
  • L - beneficial for low-end hardware
  • S - beneficial for software implementations
  • all - probably beneficial for all implementations

3.1 Traversal

Traversal is the sending of data to the graphics system. Specifically, we want to minimize the time taken to specify primitives to OpenGL.
Use connected primitives
Connected primitives such as GL_LINES, GL_LINE_LOOP, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN, and GL_QUAD_STRIP require fewer vertices to describe an object than individual line, triangle, or polygon primitives. This reduces data transfer and transformation workload. [all]
Use the vertex array extension
On some architectures function calls are somewhat expensive so replacing many glVertex/glColor/glNormal calls with the vertex array mechanism may be very beneficial. [all]
Store vertex data in consecutive memory locations
When maximum performance is needed on high-end systems it's good to store vertex data in contiguous memory to maximize through put of data from host memory to graphics subsystem. [H,L]
Use the vector versions of glVertex, glColor, glNormal and glTexCoord
The glVertex, glColor, etc. functions which take a pointer to their arguments such as glVertex3fv(v) may be much faster than those which take individual arguments such as glVertex3f(x,y,z) on systems with DMA-driven graphics hardware. [H,L]
Reduce quantity of primitives
Be careful not to render primitives which are over-tesselated. Experiment with the GLU primitives, for example, to determine the best compromise of image quality vs. tesselation level. Textured objects in particular may still be rendered effectively with low geometric complexity. [all]
Display lists
Use display lists to encapsulate frequently drawn objects. Display list data may be stored in the graphics subsystem rather than host memory thereby eliminating host-to-graphics data movement. Display lists are also very beneficial when rendering remotely. [all]
Don't specify unneeded per-vertex information
If lighting is disabled don't call glNormal. If texturing is disabled don't call glTexCoord, etc.
Minimize code between glBegin/glEnd
For maximum performance on high-end systems it's extremely important to send vertex data to the graphics system as fast as possible. Avoid extraneous code between glBegin/glEnd.

Example:

	glBegin( GL_TRIANGLE_STRIP );
for (i=0; i < n; i++) {
if (lighting) {
glNormal3fv( norm[i] );
}
glVertex3fv( vert[i] );
}
glEnd();

This is a very bad construct. The following is much better:

	if (lighting) {
glBegin( GL_TRIANGLE_STRIP );
for (i=0; i < n ;i++) {
glNormal3fv( norm[i] );
glVertex3fv( vert[i] );
}
glEnd();
}
else {
glBegin( GL_TRIANGLE_STRIP );
for (i=0; i < n ;i++) {
glVertex3fv( vert[i] );
}
glEnd();
}
Also consider manually unrolling important rendering loops to maximize the function call rate.

3.2 Transformation

Transformation includes the transformation of vertices from glVertex to window coordinates, clipping and lighting.

Lighting
  • Avoid using positional lights, i.e. light positions should be of the form (x,y,z,0) [L,S]
  • Avoid using spotlights. [all]
  • Avoid using two-sided lighting. [all]
  • Avoid using negative material and light color coefficients [S]
  • Avoid using the local viewer lighting model. [L,S]
  • Avoid frequent changes to the GL_SHININESS material parameter. [L,S]
  • Some OpenGL implementations are optimized for the case of a single light source.
  • Consider pre-lighting complex objects before rendering, ala radiosity. You can get the effect of lighting by specifying vertex colors instead of vertex normals. [S]
Two sided lighting
If you want both the front and back of polygons shaded the same try using two light sources instead of two-sided lighting. Position the two light sources on opposite sides of your object. That way, a polygon will always be lit correctly whether it's back or front facing. [L,S]
Disable normal vector normalization when not needed
glEnable/Disable(GL_NORMALIZE) controls whether normal vectors are scaled to unit length before lighting. If you do not use glScale you may be able to disable normalization without ill effects. Normalization is disabled by default. [L,S]
Use connected primitives
Connected primitives such as GL_LINES, GL_LINE_LOOP, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN, and GL_QUAD_STRIP decrease traversal and transformation load.
glRect usage
If you have to draw many rectangles consider using glBegin(GL_QUADS) ... glEnd() instead. [all]

3.3 Rasterization

Rasterization is the process of generating the pixels which represent points, lines, polygons, bitmaps and the writing of those pixels to the frame buffer. Rasterization is often the bottleneck in software implementations of OpenGL.
Disable smooth shading when not needed
Smooth shading is enabled by default. Flat shading doesn't require interpolation of the four color components and is usually faster than smooth shading in software implementations. Hardware may perform flat and smooth-shaded rendering at the same rate though there's at least one case in which smooth shading is faster than flat shading (E&S Freedom). [S]
Disable depth testing when not needed
Background objects, for example, can be drawn without depth testing if they're drawn first. Foreground objects can be drawn without depth testing if they're drawn last. [L,S]
Disable dithering when not needed
This is easy to forget when developing on a high-end machine. Disabling dithering can make a big difference in software implementations of OpenGL on lower-end machines with 8 or 12-bit color buffers. Dithering is enabled by default. [S]
Use back-face culling whenever possible.
If you're drawing closed polyhedra or other objects for which back facing polygons aren't visible there's probably no point in drawing those polygons. [all]
The GL_SGI_cull_vertex extension
SGI's Cosmo GL supports a new culling extension which looks at vertex normals to try to improve the speed of culling.
Avoid extra fragment operations
Stenciling, blending, stippling, alpha testing and logic ops can all take extra time during rasterization. Be sure to disable the operations which aren't needed. [all]
Reduce the window size or screen resolution
A simple way to reduce rasterization time is to reduce the number of pixels drawn. If a smaller window or reduced display resolution are acceptable it's an easy way to improve rasterization speed. [L,S]

3.4 Texturing

Texture mapping is usually an expensive operation in both hardware and software. Only high-end graphics hardware can offer free to low-cost texturing. In any case there are several ways to maximize texture mapping performance.
Use efficient image formats
The GL_UNSIGNED_BYTE component format is typically the fastest for specifying texture images. Experiment with the internal texture formats offered by the GL_EXT_texture extension. Some formats are faster than others on some systems (16-bit texels on the Reality Engine, for example). [all]
Encapsulate texture maps in texture objects or display lists
This is especially important if you use several texture maps. By putting textures into display lists or texture objects the graphics system can manage their storage and minimize data movement between the client and graphics subsystem. [all]
Use smaller texture maps
Smaller images can be moved from host to texture memory faster than large images. More small texture can be stored simultaneously in texture memory, reducing texture memory swapping. [all]
Use simpler sampling functions
Experiment with the minification and magnification texture filters to determine which performs best while giving acceptable results. Generally, GL_NEAREST is fastest and GL_LINEAR is second fastest. [all]
Use the same sampling function for minification and magnification
If both the minification and magnification filters are GL_NEAREST or GL_LINEAR then there's no reason OpenGL has to compute the lambda value which determines whether to use minification or magnification sampling for each fragment. Avoiding the lambda calculation can be a good performace improvement.
Use a simpler texture environment function
Some texture environment modes may be faster than others. For example, the GL_DECAL or GL_REPLACE_EXT functions for 3 component textures is a simple assignment of texel samples to fragments while GL_MODULATE is a linear interpolation between texel samples and incoming fragments. [S,L]
Combine small textures
If you are using several small textures consider tiling them together as a larger texture and modify your texture coordinates to address the subtexture you want. This technique can eliminate texture bindings.
Use glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST)
This hint can improve the speed of texturing when perspective- correct texture coordinate interpolation isn't needed, such as when using a glOrtho() projection.
Animated textures
If you want to use an animated texture, perhaps live video textures, don't use glTexImage2D to repeatedly change the texture. Use glTexSubImage2D or glTexCopyTexSubImage2D. These functions are standard in OpenGL 1.1 and available as extensions to 1.0.

3.5 Clearing

Clearing the color, depth, stencil and accumulation buffers can be time consuming, especially when it has to be done in software. There are a few tricks which can help.
Use glClear carefully [all]
Clear all relevant color buffers with one glClear.

Wrong:

  glClear( GL_COLOR_BUFFER_BIT );
if (stenciling) {
glClear( GL_STENCIL_BUFFER_BIT );
}
Right:

  if (stenciling) {
glClear( GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT );
}
else {
glClear( GL_COLOR_BUFFER_BIT );
}
Disable dithering
Disable dithering before clearing the color buffer. Visually, the difference between dithered and undithered clears is usually negligable.
Use scissoring to clear a smaller area
If you don't need to clear the whole buffer use glScissor() to restrict clearing to a smaller area. [L].
Don't clear the color buffer at all
If the scene you're drawing opaquely covers the entire window there is no reason to clear the color buffer.
Eliminate depth buffer clearing
If the scene you're drawing covers the entire window there is a trick which let's you omit the depth buffer clear. The idea is to only use half the depth buffer range for each frame and alternate between using GL_LESS and GL_GREATER as the depth test function.

Example:

   int EvenFlag;
/* Call this once during initialization and whenever the window
* is resized.
*/
void init_depth_buffer( void )
{
glClearDepth( 1.0 );
glClear( GL_DEPTH_BUFFER_BIT );
glDepthRange( 0.0, 0.5 );
glDepthFunc( GL_LESS );
EvenFlag = 1;
}
/* Your drawing function */
void display_func( void )
{
if (EvenFlag) {
glDepthFunc( GL_LESS );
glDepthRange( 0.0, 0.5 );
}
else {
glDepthFunc( GL_GREATER );
glDepthRange( 1.0, 0.5 );
}
EvenFlag = !EvenFlag;
/* draw your scene */
}
Avoid glClearDepth( d ) where d!=1.0
Some software implementations may have optimized paths for clearing the depth buffer to 1.0. [S]

3.6 Miscellaneous

Avoid "round-trip" calls
Calls such as glGetFloatv, glGetIntegerv, glIsEnabled, glGetError, glGetString require a slow, round trip transaction between the application and renderer. Especially avoid them in your main rendering code.

Note that software implementations of OpenGL may actually perform these operations faster than hardware systems. If you're developing on a low-end system be aware of this fact. [H,L]

Avoid glPushAttrib
If only a few pieces of state need to be saved and restored it's often faster to maintain the information in the client program. glPushAttrib( GL_ALL_ATTRIB_BITS ) in particular can be very expensive on hardware systems. This call may be faster in software implementations than in hardware. [H,L]
Check for GL errors during development
During development call glGetError inside your rendering/event loop to catch errors. GL errors raised during rendering can slow down rendering speed. Remove the glGetError call for production code since it's a "round trip" command and can cause delays. [all]
Use glColorMaterial instead of glMaterial
If you need to change a material property on a per vertex basis, glColorMaterial may be faster than glMaterial. [all]
glDrawPixels
  • glDrawPixels often performs best with GL_UNSIGNED_BYTE color components [all]
  • Disable all unnecessary raster operations before calling glDrawPixels. [all]
  • Use the GL_EXT_abgr extension to specify color components in alpha, blue, green, red order on systems which were designed for IRIS GL. [H,L].
Avoid using viewports which are larger than the window
Software implementations may have to do additional clipping in this situation. [S]
Alpha planes
Don't allocate alpha planes in the color buffer if you don't need them. Specifically, they are not needed for transparency effects. Systems without hardware alpha planes may have to resort to a slow software implementation. [L,S]
Accumulation, stencil, overlay planes
Do not allocate accumulation, stencil or overlay planes if they are not needed. [all]
Be aware of the depth buffer's depth
Your OpenGL may support several different sizes of depth buffers- 16 and 24-bit for example. Shallower depth buffers may be faster than deep buffers both for software and hardware implementations. However, the precision of of a 16-bit depth buffer may not be sufficient for some applications. [L,S]
Transparency may be implemented with stippling instead of blending
If you need simple transparent objects consider using polygon stippling instead of alpha blending. The later is typically faster and may actually look better in some situations. [L,S]
Group state changes together
Try to mimimize the number of GL state changes in your code. When GL state is changed, internal state may have to be recomputed, introducing delays. [all]
Avoid using glPolygonMode
If you need to draw many polygon outlines or vertex points use glBegin with GL_POINTS, GL_LINES, GL_LINE_LOOP or GL_LINE_STRIP instead as it can be much faster. [all]

3.7 Window System Integration

Minimize calls to the make current call
The glXMakeCurrent call, for example, can be expensive on hardware systems because the context switch may involve moving a large amount of data in and out of the hardware.
Visual / pixel format performance
Some X visuals or pixel formats may be faster than others. On PCs for example, 24-bit color buffers may be slower to read/write than 12 or 8-bit buffers. There is often a tradeoff between performance and quality of frame buffer configurations. 12-bit color may not look as nice as 24-bit color. A 16-bit depth buffer won't have the precision of a 24-bit depth buffer.

The GLX_EXT_visual_rating extension can help you select visuals based on performance or quality. GLX 1.2's visual caveat attribute can tell you if a visual has a performance penalty associated with it.

It may be worthwhile to experiment with different visuals to determine if there's any advantage of one over another.

Avoid mixing OpenGL rendering with native rendering
OpenGL allows both itself and the native window system to render into the same window. For this to be done correctly synchronization is needed. The GLX glXWaitX and glXWaitGL functions serve this purpose.

Synchronization hurts performance. Therefore, if you need to render with both OpenGL and native window system calls try to group the rendering calls to minimize synchronization.

For example, if you're drawing a 3-D scene with OpenGL and displaying text with X, draw all the 3-D elements first, call glXWaitGL to synchronize, then call all the X drawing functions.

Don't redraw more than necessary
Be sure that you're not redrawing your scene unnecissarily. For example, expose/repaint events may come in batches describing separate regions of the window which must be redrawn. Since one usually redraws the whole window image with OpenGL you only need to respond to one expose/repaint event. In the case of X, look at the count field of the XExposeEvent structure. Only redraw when it is zero.

Also, when responding to mouse motion events you should skip extra motion events in the input queue. Otherwise, if you try to process every motion event and redraw your scene there will be a noticable delay between mouse input and screen updates.

It can be a good idea to put a print statement in your redraw and event loop function so you know exactly what messages are causing your scene to be redrawn, and when.

SwapBuffer calls and graphics pipe blocking
On systems with 3-D graphics hardware the SwapBuffers call is synchronized to the monitor's vertical retrace. Input to the OpenGL command queue may be blocked until the buffer swap has completed. Therefore, don't put more OpenGL calls immediately after SwapBuffers. Instead, put application computation instructions which can overlap with the buffer swap delay.

3.8 Mesa-specific

Mesa is a free library which implements most of the OpenGL API in a compatible manner. Since it is a software library, performance depends a great deal on the host computer. There are several Mesa-specific features to be aware of which can effect performance.

Double buffering
The X driver supports two back color buffer implementations: Pixmaps and XImages. The MESA_BACK_BUFFER environment variable controls which is used. Which of the two that's faster depends on the nature of your rendering. Experiment.
X Visuals
As described above, some X visuals can be rendered into more quickly than others. The MESA_RGB_VISUAL environment variable can be used to determine the quickest visual by experimentation.
Depth buffers
Mesa may use a 16 or 32-bit depth buffer as specified in the src/config.h configuration file. 16-bit depth buffers are faster but may not offer the precision needed for all applications.
Flat-shaded primitives
If one is drawing a number of flat-shaded primitives all of the same color the glColor command should be put before the glBegin call.

Don't do this:

	glBegin(...);
glColor(...);
glVertex(...);
...
glEnd();

Do this:

	glColor(...);
glBegin(...);
glVertex(...);
...
glEnd();
glColor*() commands
The glColor[34]ub[v] are the fastest versions of the glColor command.
Avoid double precision valued functions
Mesa does all internal floating point computations in single precision floating point. API functions which take double precision floating point values must convert them to single precision. This can be expensive in the case of glVertex, glNormal, etc.


4. Evaluation and Tuning

To maximize the performance of an OpenGL applications one must be able to evaluate an application to learn what is limiting its speed. Because of the hardware involved it's not sufficient to use ordinary profiling tools. Several different aspects of the graphics system must be evaluated.

Performance evaluation is a large subject and only the basics are covered here. For more information see "OpenGL on Silicon Graphics Systems".

4.1 Pipeline tuning

The graphics system can be divided into three subsystems for the purpose of performance evaluation:
  • CPU subsystem - application code which drives the graphics subsystem
  • Geometry subsystem - transformation of vertices, lighting, and clipping
  • Rasterization subsystem - drawing filled polygons, line segments and per-pixel processing
At any given time, one of these stages will be the bottleneck. The bottleneck must be reduced to improve performance. The strategy is to isolate each subsystem in turn and evaluate changes in performance. For example, by decreasing the workload of the CPU subsystem one can determine if the CPU or graphics system is limiting performance.

4.1.1 CPU subsystem

To isosulate the CPU subsystem one must reduce the graphics workload while presevering the application's execution characteristics. A simple way to do this is to replace glVertex() and glNormal calls with glColor calls. If performance does not improve then the CPU stage is the bottleneck.

4.1.2 Geometry subsystem

To isoslate the geometry subsystem one wants to reduce the number of primitives processed, or reduce the transformation work per primitive while producing the same number of pixels during rasterization. This can be done by replacing many small polygons with fewer large ones or by simply disabling lighting or clipping. If performance increases then your application is bound by geometry/transformation speed.

4.1.3 Rasterization subsystem

A simple way to reduce the rasterization workload is to make your window smaller. Other ways to reduce rasterization work is to disable per-pixel processing such as texturing, blending, or depth testing. If performance increases, your program is fill limited.

After bottlenecks have been identified the techniques outlined in section 3 can be applied. The process of identifying and reducing bottlenecks should be repeated until no further improvements can be made or your minimum performance threshold has been met.

4.2 Double buffering

For smooth animation one must maintain a high, constant frame rate. Double buffering has an important effect on this. Suppose your application needs to render at 60Hz but is only getting 30Hz. It's a mistake to think that you must reduce rendering time by 50% to achive 60Hz. The reason is the swap-buffers operation is synchronized to occur during the display's vertical retrace period (at 60Hz for example). It may be that your application is taking only a tiny bit too long to meet the 1/60 second rendering time limit for 60Hz.

Measure the performance of rendering in single buffer mode to determine how far you really are from your target frame rate.

4.3 Test on several implementations

The performance of OpenGL implementations varies a lot. One should measure performance and test OpenGL applications on several different systems to be sure there are no unexpected problems.


posted @ 2009-08-25 06:05 RedLight 阅读(887) | 评论 (0)编辑 收藏

游戏Entity设计不完全整理(转)

在游戏引擎中,Entity通常被翻译成实体,也常用诸如GameObjectActorSimulationObjectUnitCharacter等名字。相比于对图像声音引擎的热情,Entity层多年来一直备受冷遇,但最近几年随着大型游戏的发展,Entity层设计的重要性已经达到和图像声音的同等水平,而且已经出现了多种通用型Entity架构。当然,这里伴随着争议和分歧。

直接模式(The C Approach

这是最早、最简单、也是任何人都能直接想到的模式。这种方式下一个Entity就是一个简单的struct:

struct Mob
{
int level, hp, mp, attack, …;
};

这种情况下往往需要对不同类型的Entity定义不同的struct,比如PlayerMobItemDoodad等。Entity的数据库可以直接使用现成的数据库系统或者从数据文件读取,比如csv文件。一般会选择Excel编辑数据库,然后导出csvExcel的强大表格和统计计算功能是调节游戏数据平衡的得力工具。以致这种最古老而简单的Entity模式以强大的生命力一直活到现在。

那么为什么Developers会要去探索其他方式呢?最大的问题是这种方式下各种Entity完全不同质。比如PlayerMobDoodad都有位置,都要做碰撞检测,但他们却是不同的类型,不能使用同一份代码;PlayerMob都有hpmp,也有基本相同的处理逻辑……当然,这个可以用hack的方法解决:只要各个struct前若干个成员类型和顺序完全相同,就可以将指针cast成统一的一个Entity类型来处理。不过,任何人都能想到更好的办法,用类!

继承模式(Inheritage

这也是EntityGameObject等这些名字的由来,他们就是基类。于是我们可以得到一颗继承关系树,例如:

Entity

       Character

              Player

              Mob

       Missile

              Laser

              GuidedMissile

       Item

      

Entity对象的逻辑大多也是直接包含在类里的。但也有人采用了数据对象和逻辑对象的分离,其好处是对相同的数据可以替换不同的逻辑。不过,个人比较怀疑这种分离是否值得,因为类的派生本身就是可以拥有不同的逻辑。

另外,Entity间的交互除了用标准的直接访问方式外,经常使用消息模式。消息模式和Windows的消息模式类似,Entity间通过消息交互。虽说窗口编程画了多年时间才摆脱了当年肥大的switch的消息处理模式,在Entity层使用消息模式还是有意义的,因为消息很容易广播且可以直接在网络上发送。执着于OO的人会将消息写成Command对象,但原理仍然是一样的。

同时,联网游戏出现,指针不能在不同的机器上标识对象,我们必须用稳定的ID来标识对象,于是我们有了EntityManager来分配ID和管理对象集合,或者叫GameObjectManagerObjectManager等。这在一段时期内几乎成了完美的方案。

随着游戏内容的丰富性的迅速膨胀,传统的由程序员来实现游戏逻辑功能的模式也越来越力不从心。脚本语言的集成将大部分创意性工作从程序员的担子上拿了下来,交还给游戏设计人员。为了给脚本提供足够的控制权,Entity结构上必须有充分的灵活性。

数据驱动(Data-Driven

现在有句很流行的话,“唯一不变的是变化。(The only constant is change.)”数据驱动使得变化对引擎的影响最小化。数据驱动不能算是一种独立的Entity模式,不如说它是一种设计思想,其目的就是将内容制作和游戏引擎的制作分离开来。与上面所说的填充Entity属性数据库的不同之处在于,它还要能通过数据来设计游戏逻辑。

游戏设计人员需要的一项最基本功能就是自定义人物属性,所以与其在类里写死属性成员,不如使用属性表(Attributes/Properties)。通常使用一个哈希表(Hashtable),或者std::map,或者Dictionary,或者就是个数组,随个人喜好,其实就是要一个key-value的映射表。然后为脚本和编辑器提供对属性表的操作。

动态的逻辑主要就靠脚本了,必须为脚本提供足够的事件和方法。个人推荐用Lua脚本,因为它是在游戏领域内用得最多的通用脚本语言,其引擎很小、速度很快、易于集成,尽管语法过于松散。不要迷信宣传去用庞大、极慢、难于集成的Python。为脚本提供事件,其实也就是调用脚本里的函数,这里如果使用了前面所述的消息模式,那么就只要调用一个脚本方法,传递不同的消息参数就行了。当然也有人觉得这样很丑陋而更愿意为不同的事件注册不同的函数。

当有了数据驱动后,Entity的继承树就基本失去意义了,因为一个Entity是什么已经不是程序里决定的了,而是通过数据和脚本设计出来的。但数据和脚本又不是全部,一个Entity的核心内容和需要高效处理的部分,如碰撞检测,还是要程序来完成。于是我们需要重新设计Entity类,困难的局面也就由此开始。

一个直观的想法是一个统一且唯一的Entity类,它包含了所有的基本功能,如显示、碰撞检测、运动、背包等,然后由数据决定哪些组件被启用。比如一个玩家角色可能会启用绝大部分组件,而一颗树只启用显示和碰撞检测组件。但也伴随着缺点:一、这个类太大了;二、对于树木等一些简单的Entity也要浪费其他组件的私有数据所占的内存。那么一个简单的折中是部分使用继承、部分使用数据定制。例如Entity只提供最基本的组件,再派生出CharactorEntity提供足够人物角色使用的组件。

组件模式(Component-Based Entity

提到组件,那么很自然的就过渡到组件模式,就是把显示、运动、攻击、背包、队伍、声音等基本功能都做成独立的组件,由数据来决定向Entity里添加哪些组件。由此可以得到另外一个扩展,就是既然可以有引擎内置的组件,那就也可以有脚本制作的组件,实现脚本模块的复用。这种模式在GDC2002正式提出,到现在主流的引擎都有这种设计。

这种模式在理论上很完美,但实践上还是有不少疑问。最常见的问题就是组件间的依赖关系。理想情况下,各个组件是完全独立的,但实践中必然有所依赖。比如运动速度、攻击强度等和角色的基本属性有关,运动组件需要角色的包围盒来测试是否碰撞,AI组件需要分析角色当前状态和发出运动、攻击命令,角色动作状态变化时改变显示组件属性,攻击组件需要访问队伍信息组件以禁止攻击队友等等。处理这种依赖关系主要要解决两个问题:

<!--[if !supportLists]-->一、              <!--[endif]-->谁依赖谁。比如是敏捷属性改变而去修改移动速度,还是运动组件读取敏捷属性来计算移动速度。如果要游戏设计人员自由定义基本属性的话,就要选择前者,因为基本属性组件会是脚本组件。

<!--[if !supportLists]-->二、              <!--[endif]-->如何取得另一组件的指针/引用。常见的方法是给每个组件类型一个唯一ID,然后用该IDEntity上查询注册了的组件,如果找到返回其指针/引用,否则返回null。当然,每次访问都做这个查询会很浪费CPU,如果Entity的组件不会在运行时动态添加删除的话(除非在编辑器里,否则很少有人会这么做),可以在Entity初始化后让每个组件缓存它所要用的其他组件指针。那么当所依赖的组件不存在怎么办,一般情况下都是无声地忽略。

Entity由很多组件组成后,交互的消息需要发给每一个组件。这里又一次体现出消息机制的优势,你不需要在Entity里为每一个事件函数写一个loop来调用组件的相应事件函数。但这里也出现了一个问题,消息到达各个组件的顺序。很多时候这个顺序不会有什么影响,但个别时候不同的顺序会导致完全不同的逻辑发展方向。

此外,Entity的序列化存储也变得比较复杂,经典的Excel导出csv的模式难以奏效,因为这里需要结构化的存储,所以需要结构化的数据文件如XML来存储,或者完全用脚本来包含所有数据和构造Entity

据个人经验,使用数据驱动的继承模式时很是向往组件模式,感觉上它一个非常自然的扩展方向,但顾忌其引入的额外的复杂性,尤其是需要游戏设计人员具有一定的编程能力,所以一直不敢全盘接过使用。但退一步,在引擎里仍然使用组件思想,但Entity的组件构成在编译时固定,可以达到某种妥协,这和采用继承与数据驱动的折中类似。

混入模式(Mix-in

这是又一种常见的折中模式,即使用C++的多重继承,将各个组件类混入一个Entity类。如:

class Mob: public GameObject, public Renderable, public Movable, public Attackable
{

}

这种方法因其简单而且非常符合多重继承设计思想而被很多引擎采用。当然缺点也是只能在支持多重继承的语言里使用,而且当组件很丰富时,dynamic_cast就变成一个代价高昂的操作。

功能性与复杂性(Functionality vs Complexity

编程领域最古老的原则之一就是要“简单快速”。但随着问题的复杂化,程序也随之变得越来越复杂。好的方法应该能有效的降低或隐藏复杂性。但是,没有不带副作用的药(No silver bullet.),在取得更强大的功能的同时总会带来额外的复杂性。我们需要做出权衡,在必要时牺牲一些功能,也就是要估算性价比。

一般游戏内容制作人员不会或不太会编程,编程人员也不善于游戏的内容创造和数值平衡,过于复杂的系统会导致需要两面兼顾的人才,会大大增加做出一款游戏的难度。

posted @ 2009-08-18 03:11 RedLight 阅读(1050) | 评论 (0)编辑 收藏

游戏对象的实现

狭义的游戏对象是指游戏世界中所能看到及可交互的对象,如玩家、怪物、物品等,我们这里也主要讨论这类对象在服务器上的组织及实现。
  

  在大部分的MMOG中,游戏对象的类型都大同小异,主要有物品、生物、玩家等。比如在wow中,通过服务器发下来的GUID我们可以了解到,游戏中有9大类对象,包括物品(Item)、背包(Container)、生物(Unit)、玩家(Player)、游戏对象(GameObject)、动态对象(DynamicObject)、尸体(Corpse)等。

  在mangos的实现中,对象使用类继承的方式,由Object基类定义游戏对象的公有接口及属性,包括GUID的生成及管理、构造及更新UpdateData数据的虚接口、设置及获取对象属性集的方法等。然后分出了两类派生对象,一是Item,另一是WorldObject。Item即物品对象,WorldObject顾名思义,为世界对象,即可添加到游戏世界场景中的对象,该对象类型定义了纯虚接口,也就是不可被实例化,主要是在Object对象的基础上又添加了坐标设置或获取的相关接口。

  Item类型又派兵出了一类Bag对象,这是一种特殊的物品对象,其本身具有物品的所有属性及方法,但又可作为新的容器类型,并具有自己特有的属性和方法,所以实现上采用了派生。mangos在实现时对Bag的类型定义做了点小技巧,Item的类型为2,Bag的类型为6,这样在通过位的方式来表示类型时,Bag类型也就同时属于Item类型了。虽然只是很小的一个技巧,但在很多地方却带来了极大的便利。

  从WorldObject派生出的类型就有好几种了,Unit、GameObject、DynamicObject和Corpse。Unit为所有生物类型的基类,同WorldObject一样,也不可被实例化。它定义了生物类型的公有属性,如种族、职业、性别、生命、魔法等,另外还提供了相关的一些操作接口。游戏中实际的生物对象类型为Creature,从Unit派生,另外还有一类派生对象Player为玩家对象。Player与Creature在实现上最大的区别是玩家的操作由客户端发来的消息驱动,而Creature的控制是由自己定义的AI对象来驱动,另外Player内部还包括了很多的逻辑系统实现。

  另外还有两类特殊的Creature,Pet和Totem,其对象类型仍然还是生物类,只是实现上与会有些特殊的东西需要处理,所以在mangos中将其作为独立的派生类,只是实现上的一点处理。另外在GameObject中也实现有派生对象,最终的继承关系图比较简单,就不麻烦地去画图了。

  从我所了解的早期游戏实现来看,大部分的游戏对象结构都是采用的类似这种方式。可能与早期对面向对象的理解有关,当面向对象的概念刚出来时,大家认为继承就是面向对象的全部,所以处处皆对象,处处皆继承。

  类实现的是一种封装,虽然从云风那里出来的弃C++而转投C的声音可能会影响一部分人,但是,使用什么语言本身就是个人喜好及团队整体情况决定的。我们所要的也是最终的实现结果,至于中间的步骤,完全看个人。还是用云风的话说,这只是一种信仰问题,我依然采用我所熟悉的C++,下面的描述也是如此。

  随着面向对象技术的深入,以及泛型等概念的相继提出,软件程序结构方面的趋势也有了很大改变。C++大师们常说的话中有一句是这样说的,尽是采用组合而不是继承。游戏对象的实现也有类似的转变,趋向于以组合的方式来实现游戏对象类型,也就是实现一个通用的entity类型,然后以脚本定义的方式组合出不同的实际游戏对象类型。

  描述的有些抽象,具体实现下一篇来仔细探讨下。


在游戏编程精粹四有三篇文章讲到了实体以及实体管理的实现方案,其中一篇文章说到了实体管理系统的四大要素:定义实体怎样沟通的实体消息,实现一实体类代码和数据的实体代码,维护已经注册在案的实体类列表,和用来创建、管理、发送消息的实体管理器。

  关于实体消息的内容之前讨论事件机制的时候做过一点说明,其实这也就是按接口调用和按消息驱动的区别,现在mangos的做法是完全的接口调用,所以引擎内部就没有任何的实体消息。实体代码实现和实体管理器是我们重点要讨论的内容。

  另有一篇文章也提到了使用类继续的方式实现游戏对象的两大问题,一是它要求系统中的所有对象都必须从一个起点衍生而成,也就是说所有对象类在编译的时候已经确定,这可能是一个不受欢迎的限制,如果开发者决定添加新的对象类,则必须要对基类有所了解,方能支持新类。另一个问题在于所有的对象类都必须实现同样的一些底层函数。

  对于第二个问题,可以通过接口继承的方式来避免基类的方法太多。在mangos的实现中就采用了类似的方法,从Object虚基类派生的Unit和WorldObject仍然还是不可实例化的类,这两种对象定义了不同的属性和方法,分来实现不同类型的对象。在游戏内部可以根据对象的实际类型来Object指针向下转型为Unit或WorldObject,以调用需要的接口。方法虽然不够OO,也还能解决问题。但是第一个问题是始终无法避免的。

  所以我们便有了通用实体这么一个概念,其主要方法是将原来基类的接口进行分类,分到一个个不同的子类中,然后以对象组合的方式来生成我们所需要的实际游戏对象类型。这个组合的过程可以通过脚本定义的方式,这样便可以在运行时生成为同的对象类型,也就解决了上面提到的第一个问题。

  通用实体的实现方法在目前的游戏引擎及开源代码中也可以看到影子。一个是BigWorld,从提供的资料来看,其引擎只提供了一个entity游戏对象,然后由游戏内容实现者通过xml和python脚本来自由定义不同类型的entity类型,每种类型可有不同的property和不同的方法。这样原来由基类定义的接口完全转移到脚本定义,具有非常强的灵活性。

  另外还有一个是CEL中的entity实现。按照CEL的描述,entity可以是游戏中的任意对象,包括玩家可交互的对象,如钥匙、武器等,也可以包括不能直接交互的对象,如游戏世界,甚至任务链中的一部分等。entity本身并没有任何特性,具体的功能实现需要靠附加property来完成。简单来说,property才定义了entity可以做什么,至于该怎么做,那又是依靠behavior来定义。所以,最终在CEL中一个游戏对象其实是由entity组合了多个property及多个behavior而生成的。

  但是CEL中的property与BigWorld中的property意义不大一样,在CEL中可定义的property其实是引擎内部要先提供的,比如其示例中所举的pcobject.mesh、pcmove.linear、pctools.inventory等,而BigWorld中的property是完全的自由定制。从这个角度来讲,其实可以把CEL中的property看作是游戏的逻辑系统,也就是我们上面所描述的,接口分类后所定义的子类。

  由引擎内部提供可选择的property与BigWorld所采用的完全自由定制property其实本质上是相同的。在用BigWorld实现的游戏中,也不可能为每种游戏对象类型都完全从头定义property,基于代码利用的原则,也会先定义一些小类,然后在entity类型定义时以自定义property的方式来包含这些小类。当然,我没有使用过BigWorld,上面的描述也只是基于游戏实现的大原则所做出来的。

  描述的依然有些抽象,我们可以用wow及mangos代码来说明一下。mangos中为object定义了一个属性集合,根据对象类型的不同,这个属性集的大小及保存数据也会有差异,另外游戏对象内部含有处理不同游戏逻辑的系统,如RestSystem、FloodFilterSystem、VariousSystem等等,在player.h中以接口组的方式进行定义。

  如果要将这种结构改为我们描述的通用entity系统,可以让object只提供property注册和删除的接口,这里的property定义与CEL中的相同,放在mangos中也就是上面说的RestSystem、FloodFilterSystem、VariousSystem这些。然后也通过xml文件的方式定义我们所需要的游戏对象类型,如player,creature,item等,不同的对象类型可以选择加载不同的property,加载的原则是需要哪些功能就加载哪些property。

  对象的定义按上面的方法完成后,对象的实现也需要做一些修改。以前客户端的消息是直接交由player来处理,AI也是直接调用creature的接口来完成一些功能,现在通用的entity内部已经没有任何可用的方法,所有的实现都转到了property中,所以需要由各个property实现自己来注册感兴趣的事件及消息,entity实现一个消息的转发,转给对此感兴趣的property来处理。其余的实现就没有什么不同了。

  当然,我们再做一点扩展,让property不光由引擎来提供,用脚本本身也能定义property,并且可以通过xml来注册这些property,这样便实现了与BigWorld一样的完全自由特性。这其实也就是将很多用C++实现的功能转移到了python中,这种做法可作为参考,但不一定对所有人合适,至少在我看来,这样的实现也基本只能由程序员来做,所以让程序员选择自己最擅长的语言可能会更易于开发和调试。

有关游戏对象实现的描述,前面两篇文章中说的不甚清楚,主要是一直都要引用网上能够找到的资料来进行描述,以避免与公司引起不必要的麻烦。所以语言有些拼凑的感觉,举的例子也很不恰当,今天正好看到了游戏编程精粹五和六上的两篇文章,内容都差不多,<<基于组件的对象管理>>和<<基于组件的游戏对象系统>>,说的也是我上两篇文章想要描述的内容,所以再补一篇,引用其中的部分文字进行明确的说明。

 

  传统的游戏对象管理系统采用继承的方式来实现,例如,所有的子类都从CObject派生。大多数情况下,直接派生的也是抽象类,其中带一些功能而另一些子类则不带这些功能,比如可控制/不可控制,可动画/不可动画等。mangos的实现中基本就是这种情况,从Object直接派生的Unit和WorldObject都是不可直接实例化的类。

  传统方法的问题在于无法应对需求的变化,如要求武器也有动画效果时就无法处理了。如果硬要是这样做,那随着需求的啬,很多的方法会被放到基类中,最终的结果是继承树变得越来越头重脚轻,这样的类会丧失它的内聚性,因为它们试图为所有对象完成所有的事。

  就是说到最后,基类会有一个很长的接口列表,而很多的游戏对象类型根本不需要实现其中的一些甚至大部分接口,但是按照这种结构却又必须去实现。以至于于实现一个非常庞大的对象,而且想要修改一点功能会导致系统的大调整。

  我们希望的系统是可以将现有的功能组合到新的对象中,并且在将新的功能添加到现有的对象中时不需要重构大量的代码和调整继承树的结构。

  实现的方法就是从组件来创建一个对象。组件是一个包含所有相关数据成员和方法的类,它完成某个特定的任务。把几个组件组合在一起就可以创建一个新的对象。如把Entity组件、Render组件和Collectable组件组合在一起生成了一个Spoon对象。Entity组件让我们可以把对象放到游戏世界中,Render组件让我们可以为对象指定一个模型进行渲染,而Collectable组件让我们可以拾取这个对象。

  关于组件的实现,所有的组件都从一个基础组件接口派生,可称其为IComponent。每个组件也有自己的接口定义,并且这个接口也需要从IComponent派生,类似于这样:IComponent -- ICmpRender -- CCmpRender

  这里的每个组件也就是我在上一篇中所说的由引擎提供的属性,或者说在BigWorld中自己实现然后定义的属性,或者使用mangos中的定义,就是一个个的System,虽然mangos并没有将其完全做成组件,但是通过其代码注释可以看到,接口也是按功能组进行了分类,如果要拆分成组件也是比较方便的。

  组件之间的通信有两种方法,一是用组件ID查询到组件接口指针,然后调用接口方法;二是使用消息的方式,向对象中所有组件发消息。在初始化的时候,每一个组件类型都会告诉对象管理器应该接收什么样的消息。

  查询接口的方法也就是直接的方法调用,只不过接口不是全部在基类中,所以必须先查询到指定的组件然后才能调用其接口。消息的使用前面已经说过多次,其实现方案也有过说明。

  最后是关于游戏对象功能的扩展和游戏对象的定义。需要扩展功能也就是需要实现一个新的组件,或者修改现在组件。在大多数情况下,扩展都不会引起结构的很大调整,受影响的最多只是使用到该组件的部分代码。

  游戏对象定义可采用完全数据驱动的方式,使用xml或者脚本语言来定义对象类型,以及每个类型需要加载的组件。对象类型注册到对象管理器后,由管理器提供创建指定类型的对象的方法。数据驱动的方式能够让策划自由定义游戏对象类型,并且随时可自由创建新的对象类型。


posted @ 2009-07-29 14:04 RedLight 阅读(2428) | 评论 (1)编辑 收藏

虚拟货币可能引发现实风险 金融部门紧急应对

 

adsense
中国高速发展的互联网市场让无数的游戏厂商看到这是一个即将到来的金蛋。

  据中国互联网络信息中心(CNNIC)最近的一次《中国互联网络发展状况统计报告》显示,中国网民人数达到了1.23亿人,与去年同期相比增长了19.4%,调查结果表明,人们对互联网的使用越来越频繁,本次调查网民平均每周上网16.5小时,达到了新的历史高度,这一数据已经超过了许多互联网发达国家和地区的网民平均上网时长。

  总体而言,中国互联网正处于一个新的高速增长时期,与GDP增长率相近的印度相比,中国的互联网普及率高了一倍。但在这些喜人成绩背后却无法掩饰中国互联网经济尤其是虚拟货币带来的不合理问题。

  网络催生虚拟货币

  不知不觉之中,伴随着中国互联网成长起来的还有大量的网络游戏厂商等。随着互联网的发展壮大,在给广大网民提供大量免费服务的同时,根据公司盈利需要和用户多样化需求,各个网站纷纷推出了收费服务项目。这也推动了虚拟货币的产生。

  不少门户网站、网络游戏运营商为了提供更好的服务,很早就开始提供虚拟货币以供使用。据不完全统计,目前市面流通的网络虚拟货币(简称网币)不下10种,如Q币、百度币、酷币、魔兽币、天堂币、盛大(游戏区)点券等。以Q币为例,使用者超过两亿人。业内人士估计,国内互联网已具备每年几十亿元的虚拟货币市场规模,并以15%~20%的速度成长。

  在市场经济中,需求会刺激创新,创新反过来能拉动需求。虚拟货币的日渐火爆正是在用户的需求和企业的创新中完成的。目前中国网络市场的虚拟货币种类之多,不胜枚举,其中腾讯公司依托庞大的QQ用户,随着即时通信市场的成熟,也适时推出了Q币。

  不可否认的是,虚拟货币让我们感受到购买网络服务的便利性,这是网络经济发展的必然产物。但目前网络虚拟货币已经悄然变身,形成庞大的交易市场。

  美国著名经济学家林顿·拉鲁什也曾经预言:从2050年开始,网络的虚拟货币将在某种程度上得到官方承认,成为可以流动的通行货币。如此规模的市场和光明前景,使不少公司已经跃跃欲试,意图占领这块市场。现在看来,百度、PAYPAL(贝宝)和腾讯处于领先地位。

  虚拟的货币体系

  可以说,如今的网络货币已经成为网络上最受人们关注的一种虚拟经济现象。

  不少虚拟货币都有一整套完整的虚拟货币销售体系。就拿Q币来说,已经有了一个非常大的货币销售体系,可以进行声讯电话充值、可以通过人民币直接到代理商处购买、也可以用手机话费进行购买。

  毋庸置疑,在网游世界中,腾讯公司的Q币已经承担了许多超出其规定功用的职能,比如,在虚拟交易中充当一般等价物,成为虚拟财富与现实财富之间的桥梁。

  Q币之所以成为虚拟世界的硬通货,这当然首先应归功于Q币的“硬”,与诸多游戏币相比:它历时更长,使用人数众多,使用范围也比较广,不易贬值……以至于在此之上,网民们又赋予了它新的功用,成为虚拟世界的“美元”,被用来交易不同虚拟世界以及虚拟世界和现实世界之间的虚拟财富。反过来,Q币这种网络一般等价物的角色又强化了其早已拥有的那些优势。

  Q币之所以有如此“强大”的功能,庞大的用户群则是其天然的基础。据不完全统计,2005年中国即时通讯软件的最高同时在线人数已经突破千万。在用户最常用的即时通讯软件中,腾讯公司的QQ用户占据了绝对的市场份额。截止到目前为止,QQ用户已经突破4亿用户。

  Q币能够成为虚拟世界的硬通货,对于腾讯公司来说,不但是一种荣誉,更意味着经济利益的滋生。业内人士分

  析,虚拟交易商在相当程度上促进了游?分行槟饩玫姆⒄寡贰?

  根据腾讯公司2006年第二季度财报的数据,其互联网增值服务收入为4.62亿元,占总收入近66%,易观国际分析认为,腾讯互联网业务很大程度上依赖于虚拟货币Q币来运行。

  淘宝网9月的统计数据显示,前不久超女投票由短信改为Q币投票后,专门从事腾讯产品销售的卖家已经突破3000家,商品数量已经超过30万,QQ专区每天的交易额都超过55万元。

  事实上,欧美国家2005年虚拟交易成交超过42亿美元,在网络游戏的“圣地”———韩国,虚拟交易的交易额在几年前就已经超过了网络游戏运营商的收入。

  虚拟货币的社会体系

  尽管网络虚拟货币大大激发了网络使用者的积极性,但也表现出了一定的社会危害性。

  这类网络货币的危险性也即在于虚拟性,理论上它可以被无限生成而不像货币需要黄金来支撑。当网上存在的货币达到一定程度时,必然引起网上虚拟的通货膨胀。

  国家工商部门有关人员表示,虚拟货币交易是网络时代派生的经营行为,至今还没有明确的法律条文规范,工商注册范围也没有关于虚拟物品交易的项目。但当虚拟财产交易逐渐人员雇佣化、场所固定化、交易盈利明确化,具有经营性质后,就可能涉及市场秩序、税收问题。

  更有甚者,不少网络游戏厂商都选择了网络赌博这样灰色的产品作支柱。

  网游公司在大量发行虚拟货币并从中牟利时,很容易造成玩家花钱买来的虚拟财产贬值,利益受到损害的现象。这是因为在互联网上,虚拟货币如腾讯Q币或其他虚拟货币,它们的发行和金属货币储值没有任何关系,虚拟货币发行商的发币行为也不受任何部门的监管。

  甚至,游戏运营商也没有能力来控制,网络中存在大量“伪钞制造者”,以主营棋牌类网游的边锋为例,其网币对应的购买力曾在一年内缩水近40%。不少人士指出:这样的通货膨胀只会让网民受损,也会让网民丧失对互联网的信心。

  据悉,正常情况下,现今使用最为广泛的Q币只能实现人民币→Q币→游戏币→增值服务的单向流通,即在正常渠道中用人民币兑换成Q币之后,不可以再将Q币兑换成人民币。

  包括腾讯在内的提供网络虚拟货币的游戏运营商等都表示,提供产品和服

  务不允许将其虚拟货币转换为人民币。但是在腾讯旗下的拍拍网首页就有非常明显的“腾讯QQ专区”标志,腾讯客服小姐明确告知,设定类目、制作专题、推荐商品都属于网站官方权限,只有拍拍网官方才能进行相关操作。

  尽管目前国家对虚拟货币也没有明确的监管办法,但是在有关的基本法律法规里明确了其他形式的代金券等,不能与人民币进行反向兑换,这就等于明确了不允许Q币这样的“虚拟货币”兑换成人民币。但是现实中将Q币兑换成人民币并不鲜见,还出现了专门销售这种“虚拟货币”的网站,甚至都出现了专门的网上“倒爷”,这些“倒爷”们的活动表现为低价收购各种虚拟货币、虚拟产品,然后再高价卖出,在一定程度上实现了虚拟货币与人民币之间的双向流通,对外经贸大学信息学院院长陈进表示:“虚拟货币但凡跟人民币发生联系,就会跟现实中的银行一样,可能面对挤兑等现实风险。”

  业内人士认为,如果要控制上述情况的发展,最根本的就是要出台措施,切断Q币等“虚拟货币”兑人民币的途径,比如主管部门可以对那些专门从事虚拟货币兑换的网站进行监管。

  可喜的是,目前中国人民银行办公厅主任、新闻发言人李超表示,央行已开始关注虚拟货币的发展,目前正在认真研究。

  网络交易的发展,需要虚拟货币,虚拟货币的发展是网络环境下不可阻挡的趋势。但如何让虚拟货币扮演好在网络交易中的角色,无时不在考验着网络服务商和中国金融部门的智慧。

posted @ 2009-06-05 02:19 RedLight 阅读(310) | 评论 (0)编辑 收藏

学3D虚拟现实技术有感

最近想研究一下虚拟现实(VR)方面的编辑器,  因为我做过游戏地图编辑器, 所以比较好奇, 都是调用3D渲染和写3D Max插件, 但毕竟两种编辑器所偏向领域不同, 不同专业有些东西是可以互补的。

初次入门,我选择了上海一家听说比较著名的公司《中视典》,听说他们公司的产品很经典,已是统治位置了,所以我就下载了个共享版的VRP编辑器来试用一下,同时下载了官方的视频教程及使用帮助文档,还有下载了一个我以后梦想中的《二层别墅.exe》来Look 1 Look,呵呵!





感觉VR地图编辑器想得还是很到位的,还有视频教程几百M, 相当精彩,讲得很细,包含在使用3D Max插件出现的问题也讲了,还有3D模型优化方面的知识,受益非浅啊,特别是美术那一块的东西,其中比较吸引我的是摄相机实时反射贴图,烘培,还有玻璃材质等。我终于知道室内场景的一些美术知识了,不限于无边无际在游戏野外打怪那么姑燥无味!

posted @ 2009-05-29 15:43 RedLight 阅读(596) | 评论 (1)编辑 收藏

仅列出标题
共9页: 1 2 3 4 5 6 7 8 9 
<2024年11月>
272829303112
3456789
10111213141516
17181920212223
24252627282930
1234567

导航

统计

公告


Name: Galen
QQ: 88104725

常用链接

留言簿(3)

随笔分类

随笔档案

相册

My Friend

搜索

最新评论

阅读排行榜

评论排行榜