这是一个游戏引擎开发者 irrlicht 的学习笔记
花了一天,通过对irrlicht进行粗略的学习。
知道了基本的使用,如何加载资源,创建节点,如何加载地图,GUI,碰撞检测,内置效果,可编程渲染管线,逐像素光照,地形渲染,渲染到纹理,分屏,光照管理,xml文件读写,自定义Mesh,遮挡查询,字体使用与创建等。
但是也发现了一些问题,irrlicht的没有高级的编程思想,仅提供了一些基本的渲染引擎功能,不易扩展。无法支持汉字。从支持的平台以及特性来看,属于远古产物,技术已属于淘汰队列。
从性能上看,只有遮挡查询优化。
做个小demo是可以的。
可以放弃了
下载 irrlicht Irrlicht Engine - A free open source 3D engine (sourceforge.io)
编译
(1) 打开 irrlicht/examples/BuildAllExamples_vc12.sln
(2) 运行samples
相关文档 Irrlicht 3D Engine: Irrlicht Engine 1.8 API documentation (sourceforge.io)
使用vscode 打开 irrlicht 文件夹
分析文件结构,分为bin, doc, examples, include, lib, media, source, tools, changes.txt, readme.txt
阅读 readme.txt
(1). 文件结构概述
(2)其他…
打开 include 文件夹
发现一些以C,E,I ,S 开头的.h文件,随便打开几个,发现
打开 source 文件夹
打开 tools 文件夹
回到 include 文件夹
打开一个 I 开头的文件,发现继承于 IReferenceCounted,打开IReferceneCounted.h,从名称以及内容得出是引用计数类。基本得出内存管理机制是引用计数。
把该文件夹下所有的文件名看一遍,
大致包含
indexbuffer, vertex buffer, mesh, scene, file, scene, light, particle, animate, texture, material, vertex2d, vertex3d, map, rect, quaternion, position2d, plane3d, matrix4,line, skybox等
从这些文件可以得出,这些都是游戏引擎的渲染部分相关的代码。
初步得出, irrlicht是一个渲染引擎。
打开 source 文件夹
大致浏览里面的文件,发现基本上都是 include 中的实现。
一些新的文件,以D3D8, D3D9, OpenGL, SoftDriver, xxxLoader, DeviceWin32,DeviceWinCE, DeviceLinux
得出 它 支持 支持D3D8, D3D9,OpenGL, SoftDriver
支持MacOS, Windows, Linux, STL, FB(framebuffer)
从支持的api D3D8,D3D9, SoftDriver可以看出,这是一个远古产物。
运行Samples
(1) 窗口标题:Hello World! - Irrlicht Engine Demo
(2) 有一行文字 : Hello World! This is the Irrlicht Software renderer!
(3) 有一个动态模型
IrrlichtDevice *device =
createDevice(video::EDT_SOFTWARE, // 软件渲染
dimension2d<u32>(640, 480), // 窗口大小 640x480
16, // 16位像素,非全屏,忽略
false, // 非全屏
false, // 无模板缓存
false, // 无垂直同步
0 // 无事件接收器
);
device->setWindowCaption(L"Hello World! - Irrlicht Engine Demo");
// 设置窗口标题
IVideoDriver* driver = device->getVideoDriver();
// 获取视频驱动,上面指定的 SOFTWARE
ISceneManager* smgr = device->getSceneManager();
// 获取场景管理器
IGUIEnvironment* guienv = device->getGUIEnvironment();
// 获取GUI环境
从这一些获取可以得出一些信息,
VideoDriver, SceneManager, GUIEnvionment 属于内部对象,创建设备时会被创建,有可能会创建其他内部对象。
// 添加一个静态文本框
guienv->addStaticText(L"Hello World! This is the Irrlicht Software renderer!",
rect<s32>(10,10,260,22), true);
// 使用场景管理器,加载Mesh
IAnimatedMesh* mesh = smgr->getMesh("../../media/sydney.md2");
// 使用场景管理器添加一个动画Mesh
IAnimatedMeshSceneNode* node = smgr->addAnimatedMeshSceneNode( mesh );
if (node)
{
node->setMaterialFlag(EMF_LIGHTING, false);// 给动画添加材质,不需要光照。
node->setMD2Animation(scene::EMAT_STAND);// 设置站立动画
ITexture* texture = driver->getTexture("../../media/sydney.bmp");// 加载纹理
node->setMaterialTexture( 0, texture);// 设置材质纹理
}
// 增加一个相机
smgr->addCameraSceneNode(0, vector3df(0,30,-40), vector3df(0,5,0));
while(device->run())
{
driver->beginScene(true, true, SColor(255,100,101,140));// 场景开始
smgr->drawAll(); // 渲染场景
guienv->drawAll(); // 渲染GUI
driver->endScene(); // 场景结束
}
device->drop(); // 释放device
从HelloWorld可以得出,基本的代码顺序,
VideoDriver->beginScene(bool backBuffer, bool zBuffer, SColor color)
从参数可以猜测,这是清空缓存,
所以这个函数实际功能可能是 beginDrawScene,
但是在beginScene和endScene中间有guienv->drawAll,
从场景管理器与GUI环境分开猜测,UI应该不属于场景,
那么beginScene实际应该是 beginDraw.
endScene应该是 endDraw
从运行的loop的内容发现,都是绘制的功能,
videoDriver->beginScene
sceneManager->drawAll
guienv->drawAll
videoDriver->endScene
猜测,场景/UI/动画的更新操作应该在 device->run()里。
通过调试发现,run()里面并没有涉及update的操作。
那么,猜测场景/UI的更新应该在 drawAll内部.
调试发现,sceneManager::drawAll函数代码较多,放弃深入。
从 quake3里加载地图
通过看代码发现新的东西
device->getFileSystem()->addFileArchive("")
通过注释,发现这个是添加文件缓存功能,下次读取相同的文件时,可以从缓存中读取,加快读取速度。
smgr->addOctreeSceneNode
可以得出,场景使用八叉树管理
smgr->addCameraSceneNodeFPS()
FPS统计功能
device->getCursorControl()->setVisible(false);
隐藏光标
device->isWindowActive()
窗口是否活动的
device->yield()
窗口非活动状态,等待, Pause状态
自定义场景节点
从 class CSampleSceneNode 可以得知,自定义节点,有一个aab碰撞盒,有顶点,有材质。
// 注册节点渲染
virtual void OnRegisterSceneNode()
{
if (IsVisible)
SceneManager->registerNodeForRendering(this);
ISceneNode::OnRegisterSceneNode();
}
如果需要渲染节点,需要把在sceneManager中注册节点
isVisible的时候注册?
没有发现 unregisterNodeForRendering
猜测它是每一帧都去注册
通过调试发现,确实如此
// 如何渲染节点
virtual void render()
{
u16 indices[] = { 0,2,3, 2,1,3, 1,0,3, 2,0,1 };
video::IVideoDriver* driver = SceneManager->getVideoDriver();
driver->setMaterial(Material);
driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
driver->drawVertexPrimitiveList(&Vertices[0], 4, &indices[0], 4, video::EVT_STANDARD, scene::EPT_TRIANGLES, video::EIT_16BIT);
}
// 获取aab碰撞盒
virtual const core::aabbox3d<f32>& getBoundingBox() const
{
return Box;
}
// 获取材质数量
virtual u32 getMaterialCount() const
{
return 1;
}
// 获取材质
virtual video::SMaterial& getMaterial(u32 i)
{
return Material;
}
// 创建一个旋转动画
scene::ISceneNodeAnimator* anim =
smgr->createRotationAnimator(core::vector3df(0.8f, 0, 0.8f));
if(anim)
{
// 自定义节点添加旋转动画
myNode->addAnimator(anim);
// ...
}
移动
创建一些模型/动画,通过按键控制
这是一个极度坑爹的场景,没有判断pause,导致应用pause状态还在运行,光光标无法移出窗口以外的区域
创建了一个 class MyEventReceiver,能响应一些内部事件,例如,按键事件。
具体消息,查看 IEventReceiver.h
创建动画节点
scene::IAnimatedMeshSceneNode* anms =
smgr->addAnimatedMeshSceneNode(smgr->getMesh("../../media/ninja.b3d")); // 加载模型,创建动画节点
anms->setFrameLoop(0, 13); // 设置循环帧
anms->setAnimationSpeed(15); // 设置动画速度
anms->setScale(core::vector3df(2.f,2.f,2.f)); // 设置缩放
anms->setRotation(core::vector3df(0,-90,0)); // 设置旋转
创建静态Text,颜色为白色
gui::IGUIStaticText* diagnostics = device->getGUIEnvironment()->addStaticText(
L"", core::rect<s32>(10, 10, 400, 20));
diagnostics->setOverrideColor(video::SColor(255, 255, 255, 0));
fix bug
while(device->run())
{
// 判断窗口是否活动的
if (!device->isWindowActive())
{
device->yield();
continue;
}
// ...
}
演示一些基本的UI
IGUISkin* skin = env->getSkin();
IGUIFont* font = env->getFont("../../media/fonthaettenschweiler.bmp");
if (font)
skin->setFont(font);
skin->setFont(env->getBuiltInFont(), EGDF_TOOLTIP);
打开 fonthaettenschweiler.bmp 会发现,这是一个ascii码字体图片,
全局搜索truetype, 发现没有,不支持truetype字体。
那么对汉字的支持有点尴尬了。
创建一个scrollbar,并设置ID,当scrollbar变化时,会发送 event,通过 EventReceiver 得到事件
IGUIScrollBar* scrollbar = env->addScrollBar(true,
rect<s32>(150, 45, 350, 60), 0, GUI_ID_TRANSPARENCY_SCROLL_BAR);
整体设置UI透明度
setSkinTransparency( scrollbar->getPos(), env->getSkin());
// 获取ui皮肤所有的颜色设置,修改alpha
void setSkinTransparency(s32 alpha, irr::gui::IGUISkin * skin)
{
for (s32 i=0; i<irr::gui::EGDC_COUNT ; ++i)
{
video::SColor col = skin->getColor((EGUI_DEFAULT_COLOR)i);
col.setAlpha(alpha);
skin->setColor((EGUI_DEFAULT_COLOR)i, col);
}
}
通过 setSkinTransparency 可知,ui被分成了不同的部分,每一个部分可以设置不同的颜色。
2D功能
把图片中与指定位置像素值相同的像素变成透明的
video::ITexture* images = driver->getTexture("../../media/2ddemo.png");
driver->makeColorKeyTexture(images, core::position2d<s32>(0,0));
(鸡肋的功能)
设置材质属性
driver->getMaterial2D().TextureLayer[0].BilinearFilter=true;
driver->getMaterial2D().AntiAliasing=video::EAAM_FULL_BASIC;
抗锯齿是材质属性?
绘制2D图片
driver->draw2DImage(images, core::position2d<s32>(50,50),
core::rect<s32>(0,0,342,224), 0,
video::SColor(255,255,255,255), true);
绘制文字
font2->draw(L"Also mixing with 3d graphics is possible.",
core::rect<s32>(130,20,300,60),
video::SColor(255,time % 255,time % 255,255));
使用2D材质绘制图片
driver->enableMaterial2D();
driver->draw2DImage(images, core::rect<s32>(10,10,108,48),
core::rect<s32>(354,87,442,118));
driver->enableMaterial2D(false);
绘制2D矩形
driver->draw2DRectangle(...)
碰撞
创建可被选中的节点
q3node = smgr->addOctreeSceneNode(q3levelmesh->getMesh(0), 0, IDFlag_IsPickable);
创建八叉树三角形选择器
auto selector = smgr->createOctreeTriangleSelector(q3node->getMesh(), q3node, 128);
q3node->setTriangleSelector(selector);
创建三角形选择器时,指定了q3node,q3node还需要设置选择器?
创建的时候,指定的node做什么用?
待探究!
创建场景动画碰撞与响应器,并设置给相机
scene::ISceneNodeAnimator* anim = smgr->createCollisionResponseAnimator(
selector, camera, core::vector3df(30,50,30),
core::vector3df(0,-10,0), core::vector3df(0,30,0));
selector->drop(); // As soon as we're done with the selector, drop it.
camera->addAnimator(anim);
anim->drop(); // And likewise, drop the animator when we're done referring to it.
创建billboard
scene::IBillboardSceneNode * bill = smgr->addBillboardSceneNode();
// ...
bill->setID(ID_IsNotPickable);
设置了一个特殊用途的ID?
enum
{
ID_IsNotPickable = 0,
IDFlag_IsPickable = 1 << 0,
IDFlag_IsHighlightable = 1 << 1
};
获取碰撞检测器
scene::ISceneCollisionManager* collMan = smgr->getSceneCollisionManager();
获取交互射线
core::line3d<f32> ray;
ray.start = camera->getPosition();
ray.end = ray.start + (camera->getTarget() - ray.start).normalize() * 1000.0f;
相机正前方到正前方1000的一条射线
射线交互
scene::ISceneNode * selectedSceneNode =
collMan->getSceneNodeAndCollisionPointFromRay(
ray,
intersection, // This will be the position of the collision
hitTriangle, // This will be the triangle hit in the collision
IDFlag_IsPickable, // This ensures that only nodes that we have
// set up to be pickable are considered
0); // Check the entire scene (this is actually the implicit default)
第四个参数 idBitMask = IDFlag_IsPickable,它是一个按位运算的值,也就是有相同位的是同一个layer。
也就是说可以通过sceneNode的ID来区分不同的交互层。
内置的效果
smgr->getMeshManipulator()->makePlanarTextureMapping(mesh->getMesh(0), 0.004f);
添加地形
mesh = smgr->addHillPlaneMesh( "myHill",
core::dimension2d<f32>(20,20),
core::dimension2d<u32>(40,40), 0, 0,
core::dimension2d<f32>(0,0),
core::dimension2d<f32>(10,10));
```
水面效果
node = smgr->addWaterSurfaceSceneNode(mesh->getMesh(0), 3.0f, 300.0f, 30.0f);
粒子效果
// 创建粒子效果节点
scene::IParticleSystemSceneNode* ps =
smgr->addParticleSystemSceneNode(false);
// 创建方形发射器
scene::IParticleEmitter* em = ps->createBoxEmitter(
core::aabbox3d<f32>(-7,0,-7,7,1,7), // emitter size
core::vector3df(0.0f,0.06f,0.0f), // initial direction
80,100, // emit rate
video::SColor(0,255,255,255), // darkest color
video::SColor(0,255,255,255), // brightest color
800,2000,0, // min and max age, angle
core::dimension2df(10.f,10.f), // min size
core::dimension2df(20.f,20.f)); // max size
// 设置发射器
ps->setEmitter(em); // this grabs the emitter
// 创建淡出粒子效果影响器
scene::IParticleAffector* paf = ps->createFadeOutParticleAffector();
// 设置影响器
ps->addAffector(paf); // same goes for the affector
体积光节点
scene::IVolumeLightSceneNode * n = smgr->addVolumeLightSceneNode(0, -1,
32, // Subdivisions on U axis
32, // Subdivisions on V axis
video::SColor(0, 255, 255, 255), // foot color
video::SColor(0, 0, 0, 0)); // tail color
纹理动画
scene::ISceneNodeAnimator* glow = smgr->createTextureAnimator(textures, 150);
增加阴影节点
anode->addShadowVolumeSceneNode();
smgr->setShadowColor(video::SColor(150,0,0,0)); // 设置阴影颜色
设置COLLADA mesh加载单例
smgr->getParameters()->setAttribute(scene::COLLADA_CREATE_SCENE_INSTANCES, true);
设置纹理创建标志
driver->setTextureCreationFlag(video::ETCF_ALWAYS_32_BIT, true);
读取xml
Device->getFileSystem()->createXMLReader( L"config.xml");
创建菜单
gui::IGUIContextMenu* menu = env->addMenu();
增加菜单项
menu->addItem(L"File", -1, true, true);
submenu->addItem(L"Open Model File & Texture...", GUI_ID_OPEN_MODEL); // 指定CommandID
获取子菜单
submenu = menu->getSubMenu(0);
增加分割符
submenu->addSeparator();
创建toolbar
gui::IGUIToolBar* bar = env->addToolBar();
添加按钮
bar->addButton(GUI_ID_BUTTON_OPEN_MODEL, 0, L"Open a model",image, 0, false, true); // 指定ID
导入场景
Device->getSceneManager()->loadScene(filename);
获取场景中的指定类型的节点
Device->getSceneManager()->getSceneNodesFromType(scene::ESNT_ANIMATED_MESH, outNodes);
显示MessageBox
Device->getGUIEnvironment()->addMessageBox(
Caption.c_str(), L"The model could not be loaded. " \
L"Maybe it is not a supported file format.");
设置Debug数据是否显示
Model->setDebugDataVisible(scene::EDS_OFF);
通过ID获取GUI
gui::IGUIContextMenu* menu = (gui::IGUIContextMenu*)Device->getGUIEnvironment()->getRootGUIElement()->getElementFromId(GUI_ID_TOGGLE_DEBUG_INFO, true);
设置项是否被选中
menu->setItemChecked(item, false);
增加天空盒
SkyBox = smgr->addSkyBoxSceneNode(
driver->getTexture("irrlicht2_up.jpg"),
driver->getTexture("irrlicht2_dn.jpg"),
driver->getTexture("irrlicht2_lf.jpg"),
driver->getTexture("irrlicht2_rt.jpg"),
driver->getTexture("irrlicht2_ft.jpg"),
driver->getTexture("irrlicht2_bk.jpg"));
增加一个maya类型的相机
Camera[0] = smgr->addCameraSceneNodeMaya();
设置对其方式
img->setAlignment(EGUIA_UPPERLEFT, EGUIA_UPPERLEFT,
EGUIA_LOWERRIGHT, EGUIA_LOWERRIGHT);
使用shader,可编程渲染管线
查询特性
driver->queryFeature(video::EVDF_PIXEL_SHADER_1_1);
driver->queryFeature(video::EVDF_VERTEX_SHADER_1_1);
获取GPU程序服务器
video::IGPUProgrammingServices* gpu = driver->getGPUProgrammingServices();
加载shader材质
newMaterialType1 = gpu->addHighLevelShaderMaterialFromFiles(
vsFileName, "vertexMain", video::EVST_VS_1_1,
psFileName, "pixelMain", video::EPST_PS_1_1,
mc, video::EMT_SOLID, 0, shadingLanguage);
newMaterialType2 = gpu->addHighLevelShaderMaterialFromFiles(
vsFileName, "vertexMain", video::EVST_VS_1_1,
psFileName, "pixelMain", video::EPST_PS_1_1,
mc, video::EMT_TRANSPARENT_ADD_COLOR, 0 , shadingLanguage);
逐像素光照
设置雾化
driver->setFog(video::SColor(0,138,125,81), video::EFT_FOG_LINEAR, 250, 1000, .003f, true, false);
设置法线贴图纹理
driver->makeNormalMapTexture(normalMap, 9.0f);
创建带切线Mesh
scene::IMesh* tangentMesh = smgr->getMeshManipulator()->
createMeshWithTangents(roomMesh->getMesh(0));
设置顶点透明度
manipulator->setVertexColorAlpha(tangentSphereMesh, 200);
设置transform
core::matrix4 m;
m.setScale ( core::vector3df(50,50,50) );// 放大矩阵
manipulator->transform( tangentSphereMesh, m );
设置事件接收器
MyEventReceiver receiver(room, earth, env, driver);
device->setEventReceiver(&receiver);
地形渲染
创建地形
scene::ITerrainSceneNode* terrain = smgr->addTerrainSceneNode(
"../../media/terrain-heightmap.bmp",
0, // parent node
-1, // node id
core::vector3df(0.f, 0.f, 0.f), // position
core::vector3df(0.f, 0.f, 0.f), // rotation
core::vector3df(40.f, 4.4f, 40.f), // scale
video::SColor ( 255, 255, 255, 255 ), // vertexColor
5, // maxLOD
scene::ETPS_17, // patchSize
4 // smoothFactor
);
创建/设置地形三角形选择器
scene::ITriangleSelector* selector
= smgr->createTerrainTriangleSelector(terrain, 0);
terrain->setTriangleSelector(selector);
创建动态mesh,并获取地形LOD 0的mesh
scene::CDynamicMeshBuffer* buffer = new scene::CDynamicMeshBuffer(video::EVT_2TCOORDS, video::EIT_16BIT);
terrain->getMeshBufferForLOD(*buffer, 0);
video::S3DVertex2TCoords* data = (video::S3DVertex2TCoords*)buffer->getVertexBuffer().getData();
// Work on data or get the IndexBuffer with a similar call.
buffer->drop(); // When done drop the buffer again.
增加穹顶天空盒
scene::ISceneNode* skydome=smgr->addSkyDomeSceneNode(driver->getTexture("../../media/skydome.jpg"),16,8,0.95f,2.0f);
渲染到纹理
创建渲染目标纹理
rt = driver->addRenderTargetTexture(core::dimension2d<u32>(256,256), "RTT1");
设置渲染目标纹理
driver->setRenderTarget(rt, true, true, video::SColor(0,0,0,255));
driver->setRenderTarget(0, true, true, 0);// 设置默认RTT
设置活动的相机
smgr->setActiveCamera(fpsCamera);
Win32多窗口程序
这个就没啥好看的了,属于windows编程
通过创建的HWND窗口句柄,指定windowID来指定渲染到哪个窗口。
smgr->loadScene("../../media/example.irr");
导入Quake 3 map
windows mobile
分屏
if (SplitScreen)
{
//Activate camera1
smgr->setActiveCamera(camera[0]);
//Set viewpoint to the first quarter (left top)
driver->setViewPort(rect<s32>(0,0,ResX/2,ResY/2));
//Draw scene
smgr->drawAll();
//Activate camera2
smgr->setActiveCamera(camera[1]);
//Set viewpoint to the second quarter (right top)
driver->setViewPort(rect<s32>(ResX/2,0,ResX,ResY/2));
//Draw scene
smgr->drawAll();
//Activate camera3
smgr->setActiveCamera(camera[2]);
//Set viewpoint to the third quarter (left bottom)
driver->setViewPort(rect<s32>(0,ResY/2,ResX/2,ResY));
//Draw scene
smgr->drawAll();
//Set viewport the last quarter (right bottom)
driver->setViewPort(rect<s32>(ResX/2,ResY/2,ResX,ResY));
}
//Activate camera4
smgr->setActiveCamera(camera[3]);
//Draw scene
smgr->drawAll();
创建4个相机,渲染时,分别启用每个相机,并指定视口,然后渲染所有场景
鼠标和摇杆
通过 IEventReceiver 的 OnEvent(const SEvent& event) 获取
光照管理
smgr->setLightManager(0);
重载 IEventReceiver::OnPreRender(core::arrayscene::ISceneNode* & lightList)
可以获取所有的光照信息,可以在每个渲染阶段修改
展示如何导入不同的Quake 3 地图
从菜单中在运行时加载BSP存档
从菜单中加载地图。显示屏幕截图
截图
Device->getVideoDriver()->createScreenShot();
从菜单中设置运行时的视频驱动程序
在运行时调整GammaLevel
Device->setGammaRamp
为着色器创建场景节点
加载EntityList并创建实体场景节点
创造有武器和碰撞反应的玩家
演奏音乐
需要 irrKlang : https://www.ambiera.com/irrklang/
材质设置并查看结果。仅使用默认的非着色器材质。
创建自定义Mesh
光标控制
Device->getCursorControl()->changeIcon(...)
Device->getCursorControl()->setActiveIcon( ECURSOR_ICON);
XML文件读写
使用遮挡查询功能加快渲染速度
driver->addOcclusionQuery(node, mesh); // 增加遮挡查询
driver->runAllOcclusionQueries(false); // 运行遮挡查询
driver->updateAllOcclusionQueries(); // 更新状态
nodeVisible=driver->getOcclusionQueryResult(node)>0; // 获取结果
仅支持ascii
gui编辑器