引擎设计跟踪(五)

最近主要的更新:
媒体库(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 )

原文地址:https://www.cnblogs.com/crazii/p/3D_Engine_Note.html