2011年11月24日

引擎设计跟踪(六)

摘要: terrain atlas貌似不能使用volme texture depth decal.阅读全文

posted @ 2011-11-24 09:38 crazii 阅读(14) 评论(0) 编辑

2011年9月11日

引擎设计跟踪(五)

最近主要的更新:
媒体库(MediaLibrary),
集成FreeImage
地形高度画刷,
地形贴图画刷,
地形法线计算,
天空球,光源 

以上支持无缝编辑.

另外,写了命令行参数解析以及Editor的Splash,纯当作消遣娱乐了,呵呵.

关于媒体库:
类似UE的Contetn Brower,当然比它要简陋的多了.呵呵.MediaLibrary的窗口可以使用两种方式:模态窗口和非模态窗口.
非模态窗口就像UE那中模式,显示的时候不抢占前台,不影响其他操作,而且运行时不需要重复构建窗体,缺点是需要额外的通信,比如拖动项目到目标窗口(UE的模式)
模态窗口就相反,好处是do-modal 之后就可以返回选择的结果,避免了OLE,drag之类的东西,这个没有做过,不知道复杂不.所以我还是选择了模态窗口模式.

为了使媒体库的数据和UI分离,实现了两个类,一个全局的IMediaLibrary singleton,来保存所有文件的分类和路径信息(纯数据模块),和一个MediaLibraryUI(UI模块),来读取纯数据并显示.同时需要一个IPreviewer接口,来生成文件的预览缩略图,
需要IPreview,而不是直接生成的原因是,某些资源(model,paticle等等,目前这些都还没有实现)是需要调用渲染系统来生成缩略图的,而渲染系统的细节对于编辑器,甚至对于整个上层逻辑都是不可见的,所以要添加这样一个接口来解藕.
目前只实现了一个ImagePreviewer来生成贴图和画刷的缩略图.

 

关于FreeImage:
之前用Ogre没有看过FreeImage,这次粗略的看了一下,还是比较灵活的,比如FreeImageIO这个接口(C语言风格的指针集合),之前是用GDI+加载文件,目前已经改成利用这个FreeIamage接口加上引擎的IStream来加载图片,这样以后如果有自己的资源包格式,就方便多了.

同时,为了避免对FreeImage的直接依赖,仍然是先定义了简单的公共接口(IImage和IImageManager),然后用插件的形式将FreeImage加入进来. 

 

关于地形工具:
由于地形的画刷类型(高度,贴图等等)的目标(地形)区域判断和源(画刷)区域判断几乎都是一致的,还有射线检测,绘制贴花等,所以把这功能放在基类里面,这样可以尽量避免冗余代码.
地形高度和法线等的编辑有两种方式:
1,直接编辑地形的硬件缓冲区,就是包含顶点(Vector3)数据的VertexBuffer: modify-->vertexbuffer
2,先编辑地形的资源数据(就是nxn*(short/int/char)的高度图和法线图),再将heitmap转换为渲染需要的顶点数据 :modify-->heightmap-->vertexbuffer
第一种方式的实时效率比较高,直接提交到显卡就可以了,缺点是资源保存的时候需要回转数据为nxn的高度图等,
第二种方式直接修改了资源的数据,这样在数据保存的时候,就避免了数据回转(将顶点数据转换为高度图) ,当然缺点是运行时多了一个中间步骤.就是再将高度图转换位显卡顶点数据
由于第二种方式后面的heightmap->vertexbuffer的转换过程,与地形初始加载时候的过程类似,所以比较简单,又考虑到Undo/Redo可能会节省内存空间(这个功能还没有做,也没做过,只是感觉会的),
同时这种编辑方式跟具体的渲染(顶点流格式)无关所以耦合度更低~ 选择了第二种方式.


比较繁琐的是顶点的法线更新,由于是使用了一次性命令来更新所有地形,所以为了效率考虑,方法参考了Ogre的思路:使用后台线程更新.由于之前的TaskManager已经可以很方便的添加(后台/前台)任务,所以现在写起来比较简单.需要考虑的问题是无缝模式的法线更新,虽然是逐Tile更新的,但是边缘的发现更新,还是需要邻接Tile的顶点数据的,这里处理就会比较麻烦,每次更新一个tile,都要将相邻的tile顶点数据也传进去.

 

关于命令行解析:
定义个全局的ICmdParser可以添加支持的命令,命令有全名--开头的,也有缩写-开头的一个字幕,如--help,-h等.然后把argc,argv传给ICmdParser,让它解析,之后应用程序就可以通过ICmdParser来读取命令行的配置了.
顺便将两个EXE工程,Editor和Game合并为一个工程,通过命令行来切换模式--mode=Game 或者--mode=Editor.
首先定义个Application类,还有对应的工厂,于是可以实现EditorApplication和GameApplication,--mode的可选项是遍历了Application的工厂来添加的.于是注册了EditorApplication和GameApplication之后,--mode中就出现了两个选项.以后如果添加新的应用类型,--mode的参数就会自动添加新的选项了.流程如下:
--mode options generator:
for each available object in Application factory
  add object name to the option(Game,Editor,etc.)
其实引擎里面很多地方都使用了枚举工厂的可用对象来生成UI等可选的列表,从而方便用户选择,这样就避免了硬编码添加.
另外添加了--config选项来强制打开配置界面,配置完成后退出程序.

Splash的写法就相对简单了,除了在OnCtlColor 里面设置文字信息的透明背景之后,还要在每次更新文字之后,InvalidateRect:
CStaticTextCtrl->GetWindowRect(&rect);
ScreenToClient(&rect);
InvalidateRect(&rect);
上面的代码比较简单,就不解释了.

 SplashWindow是一个非模态的TopMost窗口,显示之后,就可以初始化editor的资源了,同时将资源路径交给SplashWindow显示就可以了.

还有天空球和光源,比较简单了,光源是很简单的方向光,为了渲染地形的法线用的.天空球是手动创建的球形Vertexbuffer+UV.由于之前的设计里面有渲染前的回调函数:
void updateRelativeCamera(const Vector3& cameraPos,const Quaternion& cameraRotation);所以天空球就可以根据摄像机的位置来更新位置了.

还有光照的shader,目前使用了Shader Model3.0的动态循环来实现多光源,代码贴出来:

#define MAX_LIGHT_COUNT 8

int light_count;

float4 light_vector[MAX_LIGHT_COUNT];

float4 light_diffuse[MAX_LIGHT_COUNT];

float4 light_ambient;

float4 light_specular[MAX_LIGHT_COUNT];

float4 eye_position;


float4 calculateLight(float3 worldPos,float3 worldNormal)
{
	float4 light = float4(light_ambient.xyz,1);
	
	float3 eye_dir = normalize(eye_position.xyz - worldPos);
	worldNormal = normalize(worldNormal);
	
	for( int i = 0; i < light_count; ++i )
	{
		//calculate diffuse
		float3 light_dir = normalize(light_vector[i].xyz - light_vector[i].w*worldPos);
		light.xyz += light_diffuse[i] * dot( worldNormal, light_dir );
		
		//calculate specular
		float3 half_vec = normalize(light_dir + eye_dir);
		float specular = pow( dot(half_vec,worldNormal),32);
		light.xyz += light_specular[i] * specular;
	}
	
	//return saturate(light);
	return light;
}

  



下一步的计划是研究模型和动画,以及Max的模型动画导出.不过最近准备忙别的事情了,这个计划等到以后有时间了再搞.
最后还是传统,贴张图纪念下 (512*512)*(3*3 )

posted @ 2011-09-11 20:55 crazii 阅读(83) 评论(0) 编辑

2011年8月20日

引擎设计跟踪(四)

最近主要目标是做地形画刷.但是准备工作太多,只能一步一步来.目前加上了射线查询和地形的三角形集合的相交查询,主要是为了做贴花,这个是画刷显示的时候必须有的东西. 
关于地形的贴花,可以一直multipass,再画一遍,加上depthbias,使用画刷贴图做纹理投影就可以了,比如Ogre的Ogitor就是这么做的,
这样搞对于编辑器没什么问题,足够了.但是对于游戏中使用的贴花来说,最少要一个地形块多渲染一次,感觉太耗了.所以才想用地形三角面查询,只绘制需要的少量三角形就可以了,这对于游戏(而非编辑器)来说更好点.

至于射线查询,由于有了空间管理,所以找到与射线相交的三角形和点不难.而三角形集合相交的查询,要找到bounding volme内部的所有三角形,由于空间管理只定位到一个小地形块(假如类似WOW那样的,一个大约16x16的最小单位)那么这个块的三角形数量还是很多的(实时特效,不只一个贴花啊,可能有N个,比如战斗时),如果贴花在四个块的相交中心位置(一个"田"字的中心",那么可能同时占据了块,如果直接渲染,还是觉得可惜.所以想找到指定volume内部的三角形. 虽然对于现代GPU来说,渲染这点三角形没什么压力,我还是想尽量减小渲染负担.不过自己也不知道值不值..

方法是块内部三角形查询使用四叉数.(这个四叉数跟空间管理无关,空间管理可能是Octree或者Quadtree).
在使用了空间管理,找到对应的块之后,再使用块内部的四叉数结构找到相应的三角形集合,比如一个16x16的地形块,大概分到3-4级的四叉数就可以精确定位到三角形了.

遇到的问题是,地形块有不同的LOD,那么每一级的LOD,都对应有不同的三角形分部.现在的做法是,要建立所有LOD级别所对应的四叉数划分,然后运行时,根据当前的LOD级别,选择对应的四叉数.
这么做导致的问题是,内存使用量急剧上升.原来没什么东西100M左右,添加了这个功能之后内存达到了200M.这个是数据量确实很大.32x32的一个小块,细分到2x2的叶子,那么就有16x16个叶子,一个512的地形,就有256x256个叶子,再加上非叶子节点,还有不同的LOD,还有如果考虑3x3x512的大无缝,又要翻9+倍.....确实很吓人.
目前的解决方法是优化内存使用量.把内部四叉数的数据压缩,比如AABB本来是直接存储,现在改成动态生成,还有四个子节点指针,改成了索引.
 

class BlockQueryData
{

BlockQueryData
* mSubLeaf[4];
AxisAlignedBox mLocalAABB;
};
//40字节

//改成这样:

class BlockQueryData
{
//所有节点在数组中存放,一个节点的四个叶子的内存是连续的.
//这里存储四个连续索引值的第一个值
uint16 mSubLeafIndex;
//下面用于运行时构建AABB
uint16 mX;
uint16 mZ;
uint16 mSize;
fp32 mMinY; //float
fp32 mMaxY;
};
//16字节

  这样,这部分的内存使用量从90多M下降到了30多M,基本可以接受了.

目前的更新主要做了以上.也加上了DInput的支持,由于UI配置的机制是自己遍历工厂的可用对象,然后显示到UI列表,所以添加DInput代码之后,直接可以通过界面来选择,不用改UI的代码,现在真的感觉到爽了.扩展确实很方便的说.呵呵
还有UI界面的修改,比如所有的Toolbar和menu的图标都使用了直接渲染,而不使用MFC的默认方式,因为PNG的半透明,在MFC下转成ICO之后,羽化的边缘可能就没有了,直接空了,可能是残缺的.而且MFC自动转化出的灰色图标(Disable状态的图标)会有残缺,很郁闷,所以就改成直接用GDI+做了,效果还不错.图标尺寸也从16x16改为32x32了.不过这个由于之前的预留,支持多种大小的图标(16,32,48),所以只再创建的时候改一个参数就可以了,以后考虑加上用户运行时选择(像IE那样可以选择工具栏图标大小)不过这个...虽然更友好,但是对于我来说,有点偏离主题了..因为咱主要还是想学习渲染的说...

现在的工具系统的结构已经具备了一个雏形,有了toolbar等等,画刷也显示了.下一步准备加上工具栏按钮的右键点击配置(像UE的编辑器那样),主要是画刷的选择界面,还没有UI.准备写一个MediaLibrary来供用户选择Mask画刷等等(包括以后的texture,可能还有声音什么的,等等),这个界面要写完,才能开始画刷的计算,不过操作逻辑也有了,就差顶点的计算和更新了. 顶点的更新机制也需要考虑一下下...还有UI,想改成4视图的multi-view,同时把多窗口的MDI改成Tabbed风格(像VS的那种)不过这个也得等到后面看看再说吧,没有太多业余时间啊...UI的工作量确实很多很繁琐..暂时不考虑实现了.真的想考研了,呵呵.
..
然后下一步的目标是地形的贴图画刷,基本的逻辑都一样,所以尽量写的可复用,除了计算的对象从顶点/VB改为贴图的blend weight/blend texture.再然后呢....学习模型和动画,包括模型格式的设计.或者先给地形加上法线计算(放在编辑器端的逻辑部分),同时加上灯光对象(渲染系统太简陋了,目前没有Light),给地形打光... 这个是目前的计划.策略就是纵向和横向轮流搞,自上而下和自下而上轮流搞吧,这样才能尽可能及时的找到更多的问题,便于调整架构.不管怎么搞,个人对于图形学还有架构设计,都是有很大学习空间的.


最后来一张图吧... 用fraps显示的FPS,实在没时间写这些了.看到FPS,我又想到了一个问题,debug模式下挺好的,FPS比较稳定,二三百吧(下图).但是release下,稳定在1000,但只要鼠标一直移动,那么FPS就降到了30-90...发现是MFC的OnIdle太消耗的问题(因为注掉这一句就稳定了),网上搜了很多,但是目前没有太好的解决方案..这个mark一下,有空了再看看.

另,程序中使用的ICON都是网上搜到的,有的自己又PS了一下...,只是个人测试用的.还有上次一个哥们儿说到基于深度的贴花,我还没有想过这种方法,有空了也考虑考虑..

posted @ 2011-08-20 02:26 crazii 阅读(142) 评论(0) 编辑

2011年5月23日

引擎设计跟踪(三)

一直忙着写编辑器的UI部分.大至框架终于搭好了.
简单说说思路,UI作为引擎的一个插件,不包含编辑的逻辑.因为要作为插件,又要封装.目前是简单的把菜单和工具栏,以及主窗口做了抽象.

因为目前实现部分是用的MFC,所以MFC的主窗口在DLL中创建,同时有回调接口可以在主循环调用MFCAPP的OnIdle和PreTranslateMsg(这两个函数在DLL模式下不会自动执行),这样保证了MFC框架的正常运行.之前没有加OnIdle的时候,toolbar会出现问题,比如双击一个工具栏之后,如果上面的DockBar空了,仍然不消失,等等还有很多问题.考虑到扩展性,窗口系统使用的MDI.

UI部分抽象的接口有 IMenu 和IMenuManager, IEditorUI(主窗口), IToolbox, 还有一个IIconManager,用来加载图片的.目前所有的图标都是单个动态载入的png格式,包括主窗口的图标.这些接口是编辑框架预定义的回调接口,UI插件只需要实现就可以了.
还有一个IHotkeyManager,是编辑器框架内部封装的,不需要UI来实现(没有用MFC的Accelerator)这是为了使功能尽量脱离(不依赖)UI的实现.

我怀疑是不是有点过度抽象了,因为如果用跨平台的UI库,比如Qt,就可以直接拿来用了.现在是UI的实现细节,根本不可见,只有接口.不过想想,就算使用Qt,如果直接依赖的话,如果要换UI比如WxWidgets,就悲剧了.所以感觉这样可能会灵活一点儿吧.还是依赖于接口,而不依赖于具体的类,这样比较好,如果换UI了,那么编辑器的逻辑代码不需要任何修改.如果是直接依赖,那么换UI模块,编辑逻辑也不会改,但起码要改一下对UI调用的地方吧..

菜单和工具栏都是可以动态创建的,这样编辑器的插件就可以自己加载图片,并添加菜单和工具栏了.

编辑工具的抽象刚有一个雏形,目前只简单写了选择(Select)工具,因为没有实现mesh,所以Rotate和Translate工具先不写,因为不好调试.下一步准备写地形高度画刷(Terrain Deform).

为了实现Select工具,简单写了场景射线查询和AABB渲染.AABB渲染用的hardware Instancing,遇到的问题是,如果不使用index,渲染不正确.DX doc上面说,如果不用index,则需要有多个instance的vertex copy放在vertexbuffer里面,但是我用一个AABB实例测试,用PIX调试发现后面的顶点在渲染时instance数据为空.但是改为index模式就好了,这个仍有疑问.不过DX doc上说没有index的instancing是没有硬件加速的,所以也没有深入debug了.

另外从CodeProject上面下了CSizingToolbar的代码,简单改改直接用了,还有ownerdraw tabcontrol,下载下来简单改了改.懒得自己写了,菜单的OWNERDRAW是自己写的,因为CodeProject上面的菜单都太复杂了,不想用.也简单写了下左边带tab的dockbar.

编辑器框架的接口,主要用IEditorFile,IEditorTool.这两个接口主要用于插件扩展,因为框架主要提供机制,除了内置的菜单(比如文件菜单),还有内置的工具(比如选择工具),没有内置文件格式.都是通过插件来扩展的.

这两个接口,也可被UI模块访问,来处理一些基本的逻辑判断.

贴一段编辑器框架初始化内置菜单的代码:

		IMenu* menu_file = IMenuManager::getSingleton().addRootMenu( BTString("File"),TEXT('F') );
		IMenu* menu_edit = IMenuManager::getSingleton().addRootMenu( BTString("Edit"),TEXT('E') );
		IMenu* menu_tool = IMenuManager::getSingleton().addRootMenu( BTString("Tool"),TEXT('T') );
		IMenu* menu_view = IMenuManager::getSingleton().addRootMenu( BTString("View"),TEXT('V') );

		menu_file->addSubItem(BTString("New"),EdUICmd.getEditorCommand(UIC_NEW),true,TEXT('N'),HOTKEY(KC_N) );
		menu_file->addSubItem(BTString("Open"),EdUICmd.getEditorCommand(UIC_OPEN),true,TEXT('O'),HOTKEY(KC_O) );
		menu_file->addSeparator();
		menu_file->addSubItem(BTString("Save"),EdUICmd.getEditorCommand(UIC_SAVE),false,TEXT('S'),HOTKEY(KC_S) );
		menu_file->addSubItem(BTString("Save All"),EdUICmd.getEditorCommand(UIC_SAVE_ALL),false,TEXT('A'),HOTKEY(KC_S,KC_CTRL,KC_SHIFT) );
		menu_file->addSeparator();
		menu_file->addSubItem(BTString("Exit"),EdUICmd.getEditorCommand(UIC_EXIT),false,TEXT('E'),HOTKEY(KC_X) );

上面是效果图.目前连雏形都不算吧..工作量太大了,一定要坚持学习下去.

posted @ 2011-05-23 23:34 crazii 阅读(158) 评论(0) 编辑

2011年5月16日

DX9的窗口关闭了,设备依然有效

最近在做多窗口渲染.说说我遇到的情况
首先DX9Device是根据一个窗口(比如D3DPRESENT_PARAMETERS里面的hDeviceWindow)创建的,
创建的时候必须制定一个窗口,否则创建失败.

但诡异的是,当我关闭了这个窗口之后,渲染设备竟然依然有效(但为什么创建设备的时候非要一个HWND呢,很奇怪,求达人解惑,这个跟DX的Foreground window和焦点有关么?),
而且我可以继续创建swapchain,继续多窗口渲染.
甚至可以用新窗口,来复用 已经关闭窗口的那个隐式的Swapchian和Backbuffer,就是设备创建的时候带的那个,不过我这时用的是swapchain的present,并且present的时候应该要指定新窗口
另外一点,我的设想是这样的(好像在哪儿看到的tip?我忘了):
在窗口模式下,设备初始化时,创建一个全屏幕大小的backbuffer(虽然有点浪费),但是如果窗口大小变化了,不需要设备重设,只简单更改present的destRect,避免DX9最恶心的重建资源问题.也可以实现简单的伪全屏.
除非真的有全屏/窗口切换,才会重置设备.

posted @ 2011-05-16 01:49 crazii 阅读(88) 评论(0) 编辑

2011年5月15日

地形法线贴图的优化

摘要: 法线可以保存在切空间和世界空间两种方式.保存在切线空间的好处是渲染对象无论怎么旋转,都可以得到正确的法线信息, 通常的法线也都保存在切空间.它的缺点是,在渲染时需要进行空间变换,有额外的开销, 比如将法线从切线空间变换到世界空间, 从而根据世界空间的光照方向,做N dot L, 或者把世界空间的光照方向变换到切线空间,在切空间计算光照.另外一个缺点就是需要保存顶点的切线信息,来完成切空间的计算.世界空间的法线贴图,则可以直接采样出来使用,不需要变换.缺点自然很明显,如果渲染对象有旋转,那么采样出来的法线已经跟原位置不匹配了.对于地形来说,通常都是不需要旋转的,所以,法线贴图使用世界空间的话,可阅读全文

posted @ 2011-05-15 09:57 crazii 阅读(166) 评论(0) 编辑

2010年6月18日

引擎设计跟踪(二)

摘要: 配置参数和配置界面的分离阅读全文

posted @ 2010-06-18 10:21 crazii 阅读(212) 评论(1) 编辑

2010年6月9日

CEGUI 的渲染优化

摘要: 前两天同事跟我说了下CEGUI的字体优化,主要思路是它一次创建了256个字的纹理,太慢太浪费。改成单个字的删格化就好了。另外我考虑到0-255这些ASCII字符的使用频率,就单独把这一页的字符一次性创建出来了。呵呵。今天做好了,今天他又说到了渲染优化方法。原理很单,就是不用每帧都渲染UI,把它单放到一个BackBuffer里面,类似windows的invalidate 和paint,如果UI系统不...阅读全文

posted @ 2010-06-09 11:52 crazii 阅读(340) 评论(0) 编辑

2010年5月27日

[转]3D图形渲染通道负载优化的几种方法

摘要: 一般来说, 定位渲染通道瓶颈的方法就是改变渲染通道每个步骤的工作量, 如果吞吐量也改变了, 那个步骤就是瓶颈. 找到了瓶颈就要想办法消除瓶颈, 可以减少该步骤的工作量, 增加其他步骤的工作量. 一般在光栅化之前的瓶颈称作”transform bound”, 三角形设置处理后的瓶颈称作”fill bound” 定位瓶颈的办法 1. 改变帧缓冲或者渲染目...阅读全文

posted @ 2010-05-27 14:03 crazii 阅读(176) 评论(0) 编辑

[转]D3DCREATE_PUREDEVICE

摘要: What is the purpose of the D3DCREATE_PUREDEVICE flag?D3DCREATE_PUREDEVICE 标记的作用是什么?Use the D3DCREATE_PUREDEVICE flag during device creation to create a pure device. A pure device does not save the cur...阅读全文

posted @ 2010-05-27 12:11 crazii 阅读(202) 评论(0) 编辑

导航

<2012年2月>
2930311234
567891011
12131415161718
19202122232425
26272829123
45678910

公告

昵称:crazii
园龄:1年9个月
粉丝:2
关注:2

搜索

 
 

常用链接

我的标签

随笔分类

随笔档案

文章分类

最新评论

阅读排行榜

评论排行榜

推荐排行榜