SceneManager分析

场景管理主要工作包括以下几点:

1.可移动、不可移动和可渲染物体的创建删除。

2.场景查询。

3.渲染队列。

4.动态阴影。

一. 场景对象创建

场景中的所有对象,包括可移动与不可移动的:Camera、Light、SceneNode、Entity、ManualObject、BillboardChain、RibbonTrail、ParticleSystem、BillboardSet、Animation、AnimationState、StaticGeometry、MovableObject等13种物体的createXXX、getXXX、hasXXX、destroyXXX.都由场景管理器来创建。场景中的任何东西都由场景管理器来管理。任何通过场景管理器得到的东西,都必须由场景管理器来销毁。用户不能delete通过由场景管理器得到的指针。

SkyDome, SkyPlane, SkyBox

主要的相似点是它们与相机保持一个常量的距离。它们可以在场景中其它对象之前或是之后渲染。它们使用普通的ogre material,因此纹理动画与其他纹理没有什么不同。它们可以被场景管理器打开或关闭,与相机的距离也可以设置。

skyplane 是一个平面。用距离和法线定义它与相机的位置关系。可以弯曲,可以分多个段,可对纹理进行多次平铺。

skydome由五个平面组成,底部空。使用改变纹理坐标的方式来达到外观上的曲率变化。有一个值用来调节,值越低,曲率越柔和,值越高,越陡峭。

skybox 像skydome,但他不能“弯曲”材质坐标。它可以使用立方材质。可使用硬件加速功能达到很好渲染效率。

SkyDome, SkyPlane, SkyBox等3种天空形式的setSkyXXX、isSkyXXX、getSkyXXX、

getSkyXXXGenParameters.以及setFog和getFog等参数的操作。

以下是这些操作一览:

1.Create函数

2.Destroy函数

3.Set函数

4.Get函数

 

二. 场景查询

Ogre的场景查询目前分为3种,一种是相交查询,一种是射线查询,一种是区域查询。查询的结果分为两种,一种是可运动对象(movable objects),一种是关卡的一部分(world geometry)。对于不同的场景管理器,同种查询得到的结果是不一样的。

其实查询可以直接做到场景管理器中,不过Ogre另外开辟了一个类,专门负责处理查询,就是SceneQuery。这个类的查询操作,其实就是包装场景管理器的查询接口。

相交查询,主要是查询两两相交的可运动对象,以及可运动对象和关卡子部分。

射线查询也是这种。

区域查询,是设定一个区域,如AABB区域,或者球形区域,或者由多面体组成的区域,查询在该区域中的可运动对象。

所有这些查询都是 maskble的,这表明可以在查询时过滤掉不关心的对象类型。如球查询时,只想看看它包含了多少lights,其他的对象即使包含在球里也不必返回,实际上根本不用计算。以下是UML图

1. WorldFragmentType和WorldFragment

前面说过查询的结果分两种, movable objects和world geometry。而world geometry根据不同的查询方式得到的结果又分几种。WorldFragment就是表示world geometry查询结果的结构。WorldFragmentType是指用那种查询方式得到的结果。

enum WorldFragmentType {

WFT_NONE,

WFT_PLANE_BOUNDED_REGION,    // Region Scene Query结果

WFT_SINGLE_INTERSECTION,     // Ray Scene Query结果

WFT_CUSTOM_GEOMETRY,         // 自定义结果

WFT_RENDER_OPERATION         // ?没发现有用

};

struct WorldFragment {

WorldFragmentType fragmentType;

Vector3 singleIntersection;  // WFT_SINGLE_INTERSECTION

std::list<Plane>* planes;    // WFT_PLANE_BOUNDED_REGION

void* geometry;              // WFT_CUSTOM_GEOMETRY

RenderOperation* renderOp;   // ?没发现有用

};

2. SceneQuery和SceneQueryListener

SceneQuery场景查询的基类,负责保存与之相关的SceneManager、查询掩码和查询类掩码(SceneQuery类型掩码需要自己定义,也自己解释)和场景查询支持的World Fragment Type。

SceneManager* mParentSceneMgr;

uint32 mQueryMask;

uint32 mQueryTypeMask;

std::set<WorldFragmentType> mSupportedWorldFragments;

WorldFragmentType mWorldFragmentType;

在SceneManager中定义了几种类型掩码,如下:

static uint32 WORLD_GEOMETRY_TYPE_MASK;   // for world geometry

static uint32 ENTITY_TYPE_MASK;           // for entities

static uint32 FX_TYPE_MASK;               // for effects like billboardsets / particle systems

static uint32 STATICGEOMETRY_TYPE_MASK;   // for StaticGeometry

static uint32 LIGHT_TYPE_MASK;            // for lights

static uint32 USER_TYPE_MASK_LIMIT;       // max limit

SceneQueryListener是负责执行场景查询的回调抽象类,在3种查询的执行中把

SceneQueryListener的指针传递给SceneQuery的子类。

virtual void execute(SceneQueryListener* listener);

以下是SceneQueryListener的定义

class _OgreExport SceneQueryListener

{

public:

virtual ~SceneQueryListener() { }

virtual bool queryResult(MovableObject* object) = 0;

virtual bool queryResult(SceneQuery::WorldFragment* fragment) = 0;

};

3. RegionSceneQuery RaySceneQuery IntersectionSceneQuery

上面3个查询的功能很简单,主要负责执行查询和保存查询结果。RegionSceneQuery又分别派生AxisAlignedBoxSceneQuery、SphereSceneQuery和PlaneBoundedVolumeListSceneQuery。

4.SceneManager中的SceneQuery

上面提过SceneQuery其实就是包装场景管理器的查询接口。下面代码证明了这个说法:

class DefaultRaySceneQuery : public RaySceneQuery

{

public:

DefaultRaySceneQuery(SceneManager* creator);

~DefaultRaySceneQuery();

void execute(RaySceneQueryListener* listener);

};

class DefaultSphereSceneQuery : public SphereSceneQuery

{

public:

DefaultSphereSceneQuery(SceneManager* creator);

~DefaultSphereSceneQuery();

void execute(SceneQueryListener* listener);

};

class DefaultPlaneBoundedVolumeListSceneQuery : public PlaneBoundedVolumeListSceneQuery

{

public:

DefaultPlaneBoundedVolumeListSceneQuery(SceneManager* creator);

~DefaultPlaneBoundedVolumeListSceneQuery();

void execute(SceneQueryListener* listener);

};

class DefaultAxisAlignedBoxSceneQuery : public AxisAlignedBoxSceneQuery

{

public:

DefaultAxisAlignedBoxSceneQuery(SceneManager* creator);

~DefaultAxisAlignedBoxSceneQuery();

void execute(SceneQueryListener* listener);

};

5. SceneManager中的接口

virtual AxisAlignedBoxSceneQuery*

createAABBQuery(const AxisAlignedBox& box, unsigned long mask = 0xFFFFFFFF);

virtual SphereSceneQuery*

createSphereQuery(const Sphere& sphere, unsigned long mask = 0xFFFFFFFF);

virtual PlaneBoundedVolumeListSceneQuery*

createPlaneBoundedVolumeQuery(const PlaneBoundedVolumeList& volumes, unsigned long mask = 0xFFFFFFFF);

virtual RaySceneQuery*

createRayQuery(const Ray& ray, unsigned long mask = 0xFFFFFFFF);

virtual IntersectionSceneQuery*

createIntersectionQuery(unsigned long mask = 0xFFFFFFFF);

virtual void destroyQuery(SceneQuery* query);

下图是整个SceneQuery家族的UML结构图

三. 渲染队列RenderQueue

1. RenderQueue的组成

RenderQueue由Ogre::RenderQueueGroup组成的,RenderQueue中有一个RenderQueueGroup的Map的数据成员:

typedef std::map< RenderQueueGroupID, RenderQueueGroup * > RenderQueueGroupMap

RenderQueueGroupMap mGroups

可见RenderQueueGroupMap 的key为RenderQueueGroupID,代表Objects的渲染先后顺序。RenderQueueGroupID是一个枚举量,根据场景内物体的渲染顺序由先及后定义,RenderQueueGroupID的定义如下

enum RenderQueueGroupID

{

RENDER_QUEUE_BACKGROUND = 0,

RENDER_QUEUE_SKIES_EARLY = 5,

RENDER_QUEUE_1 = 10,

RENDER_QUEUE_2 = 20,

RENDER_QUEUE_WORLD_GEOMETRY_1 = 25,

RENDER_QUEUE_3 = 30,

RENDER_QUEUE_4 = 40,

RENDER_QUEUE_MAIN = 50,

RENDER_QUEUE_6 = 60,

RENDER_QUEUE_7 = 70,

RENDER_QUEUE_WORLD_GEOMETRY_2 = 75,

RENDER_QUEUE_8 = 80,

RENDER_QUEUE_9 = 90,

RENDER_QUEUE_SKIES_LATE = 95,

RENDER_QUEUE_OVERLAY = 100,

}

RenderQueue通过成员函数addRenderable 添加物体到渲染队列中,在RenderQueue的 getQueueGroup成员负责RenderQueueGroup的查找创建。RenderQueueGroup的生命周期由RenderQueue来控制。

2. RenderQueueGroup的组成

RenderQueueGroup中有一个RenderPriorityGroup的Map的数据成员:

typedef std::map<ushort, RenderPriorityGroup*, std::less<ushort> > PriorityMap;

PriorityMap mPriorityGroups;

PriorityMap的key为一个ushort,它代表着RenderPriorityGroup渲染的优先级。对同一优先级的Objects,RenderQueueGroup会通过成员函数addRenderable 将它加入相同的RenderPriorityGroup中,RenderPriorityGroup的生命周期是由 RenderQueueGroup管理的。

3. RenderPriorityGroup的组成

RenderPriorityGroup中是存放需要渲染的Objects的最终场所。需要渲染的Objects——Renderable,RenderPriorityGroup组织将其组织为RenderableList,然后把RenderableList组织成SolidRenderablePassMap:

typedef std::vector<Renderable*> RenderableList;

typedef std::map<Pass*, RenderableList*, SolidQueueItemLess> SolidRenderablePassMap;

SolidRenderablePassMap mSolidPasses;

SolidRenderablePassMap mSolidPassesDiffuseSpecular;

SolidRenderablePassMap mSolidPassesDecal;

SolidRenderablePassMap mSolidPassesNoShadow;

综上所述,需渲染的物体分别经过RenderPriorityGroup、RenderQueueGroup分类后,由RenderQueue统一管理。

4. QueuedRenderableCollection

RenderPriorityGroup有5个成员变量mSolidsBasicm、SolidsDiffuseSpecular、mSolidsDecal、mSolidsNoShadowReceive、mTransparents都是QueuedRenderableCollection,

QueuedRenderableCollection是存储Renderable和Pass的最终场所。通过多种排序实现Renderable和Pass的有序化。排序包括小于排序、深度递减排序和基数排序。

typedef std::vector<RenderablePass> RenderablePassList;

typedef std::vector<Renderable*> RenderableList;

typedef std::map<Pass*, RenderableList*, PassGroupLess> PassGroupRenderableMap;

PassGroupRenderableMap mGrouped;

RenderablePassList mSortedDescending;

mGrouped和mSortedDescending中存储的是Renderable和Pass。

QueuedRenderableCollection组织Renderable和Pass有三种,分别是按Pass分组、按与camera的距离升序和按与camera的距离减序。

enum OrganisationMode{

OM_PASS_GROUP = 1,

OM_SORT_DESCENDING = 2,

OM_SORT_ASCENDING = 6

};

5.QueuedRenderableVisitor

QueuedRenderableVisitor是按访问者模式设计的抽象接口。在QueuedRenderableCollection中有一个公用接口和三个内部接口如下:

void acceptVisitor(QueuedRenderableVisitor* visitor, OrganisationMode om) const;

void acceptVisitorGrouped(QueuedRenderableVisitor* visitor) const;

void acceptVisitorDescending(QueuedRenderableVisitor* visitor) const;

void acceptVisitorAscending(QueuedRenderableVisitor* visitor) const;

acceptVisitor按OrganisationMode3种方式分别调用内部接口acceptVisitorGrouped、

acceptVisitorDescending和acceptVisitorAscending。

switch(om)

{

case OM_PASS_GROUP:

acceptVisitorGrouped(visitor);

break;

case OM_SORT_DESCENDING:

acceptVisitorDescending(visitor);

break;

case OM_SORT_ASCENDING:

acceptVisitorAscending(visitor);

break;

}

而acceptVisitorGrouped、acceptVisitorDescending和acceptVisitorAscending。内部调用如下

void QueuedRenderableCollection::acceptVisitorGrouped(QueuedRenderableVisitor* visitor) const

{

RenderableList* rendList = ipass->second;

RenderableList::const_iterator irend, irendend;

irendend = rendList->end();

for (irend = rendList->begin(); irend != irendend; ++irend)

{

visitor->visit(*irend);

}

}

通过以上分析可以看到最终的渲染任务是交到QueuedRenderableVisitor手中。

而在SceneManager中有如下定义,具体实现了渲染任务。

class SceneMgrQueuedRenderableVisitor : public QueuedRenderableVisitor

SceneMgrQueuedRenderableVisitor* mActiveQueuedRenderableVisitor;

SceneMgrQueuedRenderableVisitor mDefaultQueuedRenderableVisitor;

6. RenderTarget

RenderTarget用来接收渲染操作的结果,它可以是屏幕上的窗口、离屏面(如texture)等。FPS信息的统计也是由RenderTarget完成的。在RenderTarget每次更新完成后,将会更新统计信息(封装于FrameStats中)。 除了负责统计帧的信息外,RenderTarget还负责创建维护Viewport(视口):

typedef std::map<int, Viewport*, std::less<int> > ViewportList;

ViewportList mViewportList;

Viewport* RenderTarget::addViewport(Camera* cam, int ZOrder, float left, float top ,

float width , float height)

{

ViewportList::iterator it = mViewportList.find(ZOrder);

if (it != mViewportList.end())

{

}

Viewport* vp = new Viewport(cam, this, left, top, width, height, ZOrder);

mViewportList.insert(ViewportList::value_type(ZOrder, vp));

fireViewportAdded(vp);

return vp;

}

由上面的代码可以看出,每个Viewport都对应一个Camera和一个RenderTarget。当创建一个Viewport后,它会自动建立与Camera的联系。可以把Camera看作是图像的来源,而RenderTarget是图像渲染的目的地。一个Viewport只能对应一个Camera和一个RenderTarget,而一个Camera也只能对应一个Viewport,但RenderTarget却可以拥有几个Viewport。

7. 渲染过程

OGRE通过WinMain或main调用go再通过Root调用startRendering进行消息循环,然后调用renderOneFrame,通过RenderSystem的_updateAllRenderTargets方法,更新所有的RenderTarget。RenderTarget通过update方法更新与之关联的Viewport并产生FPS统计信息。而Viewport则调用与之关联的Camera的_renderScene方法进行渲染,Camera此时把“球”踢给SceneManager。进入SceneManager的renderScene成员函数中后,在经过计算后,把需要渲染的场景送给RenderSystem去做真正的渲染,此时我们可以看到熟悉的_breginFrame和_endFrame。 一直下去经过RenderQueue、RenderQueueGroup、RenderPriorityGroup、QueuedRenderableCollection再通过访问者到达QueuedRenderableVisitor的子类SceneMgrQueuedRenderableVisitor,最终又回到SceneManager,由SceneManager再到RenderSystem完成整个渲染过程。过程伪码如下所示:

int WinMain or main(int argc, char **argv)

{

app.go();

}

virtual void go(void)

{

Root->startRendering();

}

void Root::startRendering(void)

{

renderOneFrame();

}

bool Root::renderOneFrame(void)

{

_updateAllRenderTargets();

}

void Root::_updateAllRenderTargets(void)

{

RenderSystem->_updateAllRenderTargets();

}

void RenderSystem::_updateAllRenderTargets(void)

{

RenderTarget->update();

}

void RenderTarget::update(void)

{

Viewport->update();

}

void Viewport::update(void)

{

Camera->_renderScene(this, mShowOverlays);

}

void Camera::_renderScene(Viewport *vp, bool includeOverlays)

{

SceneManager->_renderScene(this, vp, includeOverlays);

}

void SceneManager::_renderScene(Camera* camera, Viewport* vp, bool includeOverlays)

{

RenderSystem->_beginFrame();

_renderVisibleObjects();

RenderSystem->_endFrame();

}

void SceneManager::_renderVisibleObjects(void)

{

//如果有阴影

renderVisibleObjectsCustomSequence();

//否则

renderVisibleObjectsDefaultSequence();

}

void SceneManager::renderVisibleObjectsDefaultSequence(void)

{

fireRenderQueueStarted(qId,mIlluminationStage)

_renderQueueGroupObjects(pGroup, QueuedRenderableCollection::OM_PASS_GROUP);

fireRenderQueueEnded(qId, mIlluminationStage)

}

void SceneManager::_renderQueueGroupObjects(RenderQueueGroup* pGroup, OrganisationMode om)

{

renderBasicQueueGroupObjects(pGroup, om);

}

void SceneManager::renderBasicQueueGroupObjects(RenderQueueGroup* pGroup, OrganisationMode om)

{

renderObjects(pPriorityGrp->getSolidsBasic(), om, true);

}

void SceneManager::renderObjects(const QueuedRenderableCollection& objs, …)

{

objs.acceptVisitor(mActiveQueuedRenderableVisitor, om);

}

void QueuedRenderableCollection::acceptVisitor(QueuedRenderableVisitor* visitor…)

{

switch(om){

case OM_PASS_GROUP:

acceptVisitorGrouped(visitor);

}

}

void QueuedRenderableCollection::acceptVisitorGrouped(QueuedRenderableVisitor* visitor)

{

QueuedRenderableVisitor->visit(Renderable);

}

void SceneManager::SceneMgrQueuedRenderableVisitor::visit(const Renderable* r)

{

SceneManager->renderSingleObject(r, mUsedPass, autoLights, manualLightList);

}

void SceneManager::renderSingleObject(const Renderable* rend, const Pass* pass…)

{

RenderSystem->_render(RenderOperation);

}

最终进入的RenderSystem的子类D3D9RenderSystem or GLRenderSystem。

void D3D9RenderSystem::_render(const RenderOperation& op)

{

mpD3DDevice->DrawIndexedPrimitive or mpD3DDevice->DrawPrimitive

}

void GLRenderSystem::_render(const RenderOperation& op)

{

glDrawElements or glDrawArrays

}

 

8. RenderQueueListener

class _OgreExport RenderQueueListener

{

public:

virtual ~RenderQueueListener() {}

virtual void renderQueueStarted(uint8 queueGroupId, const String& invocation, bool& skip) = 0;

virtual void renderQueueEnded(uint8 queueGroupId, const String& invocation, bool& repeat) = 0;

};

RenderQueueListener的作用就是在SceneManager开始渲染和结束渲染时改变render state和别的操作,比如检查本次RenderQueue是否skip,如果skip就直接break本次render。

在上面渲染过程中有如下:

void SceneManager::renderVisibleObjectsDefaultSequence(void)

{

fireRenderQueueStarted(qId,mIlluminationStage)

_renderQueueGroupObjects(pGroup, QueuedRenderableCollection::OM_PASS_GROUP);

fireRenderQueueEnded(qId, mIlluminationStage)

}

fireRenderQueueStarted和fireRenderQueueEnded中遍列RenderQueueListener。

定义如下:

typedef std::vector<RenderQueueListener*> RenderQueueListenerList;

RenderQueueListenerList mRenderQueueListeners;

bool SceneManager::fireRenderQueueStarted(uint8 id, const String& invocation)

{

RenderQueueListener->renderQueueStarted(id, invocation, skip);

}

bool SceneManager::fireRenderQueueEnded(uint8 id, const String& invocation)

{

RenderQueueListener->renderQueueEnded(id, invocation, repeat);

}

 

9. SpecialCaseRenderQueue

SenceManager中的RenderQueue包含一种特殊情况的渲染队列(Special Case Render Queue)

enum SpecialCaseRenderQueueMode{

SCRQM_INCLUDE,     // 只渲染Special Case

SCRQM_EXCLUDE      // 不渲染Special Case

};

通过setSpecialCaseRenderQueueMode可以设置。以下是跟Special Case Render Queue有关的变量和操作:

typedef std::set<uint8> SpecialCaseRenderQueueList;

SpecialCaseRenderQueueList mSpecialCaseQueueList;

SpecialCaseRenderQueueMode mSpecialCaseQueueMode;

virtual void addSpecialCaseRenderQueue(uint8 qid);

virtual void removeSpecialCaseRenderQueue(uint8 qid);

virtual void clearSpecialCaseRenderQueues(void);

virtual void setSpecialCaseRenderQueueMode(SpecialCaseRenderQueueMode mode);

virtual SpecialCaseRenderQueueMode getSpecialCaseRenderQueueMode(void);

这里面需要说明一下的是qid,qid是RenderQueueGroupID,RenderQueueGroupID的定义在上面已经提过。

总之,通过上面分析可以看出render queue是Renderable的集合。其实场景树和渲染队列都是对Renderable进行分类,只是分类的标准不同,场景树主要是从空间结构对Renderable进行分类,而渲染队列则是对Renderable从material以及blend上进行分类。

四. 动态阴影Shadow

Ogre支持两种主流的动态阴影技术,模板(stencil)阴影与纹理(texture)阴影,每一种都有两个变体:modulative与additive。这四种技术完全兼容fixed-function pipeline,因此不需要可编程GPU支持。然而,可利用GPU程序进行加速。在场景中只能使用一种阴影Technique,应该在场景渲染这前进行阴影Technique相关设置(最好是在创建场景管理器之间)。通过调用SceneManager::setShadowTechnique来设置Technique,参数指定Technique的具体类型。阴影Technique缺省情况下被关闭。对于物体,投射与接收阴影可以在材质中控制,也可以控制物体自己对自己投射。由于模板阴影算法的本质特征,透明半透明的物体要么全部投射实心阴影要么根本不投影影,不可能得到半透明的阴影。而使用纹理阴影技术则可以。灯不能用来投射阴影。

1. ShadowRenderable

    ShadowRenderable是存储shadow volume材质和渲染数据的类。

2. ShadowCaster

    ShadowCaster是计算和产生shadow volume的类。

3. ShadowVolumeExtrudeProgram

ShadowVolumeExtrudeProgram是存储asm shader代码的类。

4. ShadowListener

ShadowListener可以在渲染阴影过程中定义一些自己的操作。这个类只在shadow texture时有用。

Shadow这块可以精简,ogre有点复杂。

转:Ogre的SceneManager分析的更多相关文章

  1. 转:Ogre源码分析之Root类、Facade模式

    Ogre源码分析(一)Root类,Facade模式 Ogre中的Root对象是一个Ogre应用程序的主入口点.因为它是整个Ogre引擎的外观(Façade)类.通过Root对象来开启和停止Ogre是最 ...

  2. [Ogre][地形]OgreTerrain分析以及使用

    Ogre 1.7.2中的地形教程 ○读者可以对照着Ogre1.7.2中的terrain.h源码进行阅读加深理解,蓝色部分均为源码 ○去除了一些具体场景比如添加mesh,设置setAmbientLigh ...

  3. 转:Ogre的MaterialSystem分析

    1. Mesh .SubMesh.SubEntity和Entity 所有的Mesh对象是由SubMesh构成的,每个SubMesh代表了Mesh对象的一部分,该部分只能使用一种Meterial.如果一 ...

  4. Axiom3D写游戏:第一个窗口

    Axiom主要的代码大致翻看了下,就想到了自己来模拟一下游戏开发. 这章主要包括创建窗口及3D渲染的一些基本元素,并添加一个第三人称的骨骼动画作主角,加上前文中修改过后的地形组件,能用鼠标和键盘进行漫 ...

  5. OGRE启动过程详解(OGRE HelloWorld程序原理解析)

    本文介绍 OGRE 3D 1.9 程序的启动过程,即从程序启动到3D图形呈现,背后有哪些OGRE相关的代码被执行.会涉及的OGRE类包括: Root RenderSystem RenderWindow ...

  6. Ogre内部渲染流程分析系列

    come from:http://blog.csdn.net/weiqubo/article/details/6956005 要理解OGRE引擎,就要理解其中占很重要位置的 Renderable接口, ...

  7. 【转载】Ogre:Beginner Tutorial 1: SceneNode, Entity,和SceneManager 结构

    原文:Beginner Tutorial 1: SceneNode, Entity,和SceneManager 结构   先决条件 这个教程假设你有C++编程的基础并且可以配置并编译OGRE应用程序 ...

  8. 实现Ogre的脚本分离 - 天龙八部的源码分析(一)

    目的 在研究天龙八部游戏的源码之时, 发现 Ogre 材质的模板部分被单独放在一个 material 文件之内, 继承模板的其他材质则位于另外的文件, 当我使用Ogre 官方源码, 加载脚本时其不会查 ...

  9. ogre sample分析(一)

    ogre自带了一些例子,逐个过一遍并自己动手做一些调整 1 Sample_BezierPatch:这个例子直接用数值来构造顶点缓存并创建entity,这种方法一般只能创建简单对象,本人以为复杂对象顶点 ...

随机推荐

  1. 不得不知的ES6十大特性

    ES6(ECMAScript2015)的出现,无疑给前端开发人员带来了新的惊喜,它包含了一些很棒的新特性,可以更加方便的实现很多复杂的操作,提高开发人员的效率. 本文主要针对ES6做一个简要介绍. 主 ...

  2. GridViewColumn.CellTemplate

    <GridViewColumn Header="Sig" Width="210"> <GridViewColumn.CellTemplate& ...

  3. Spark:java api实现word count统计

    方案一:使用reduceByKey 数据word.txt 张三 李四 王五 李四 王五 李四 王五 李四 王五 王五 李四 李四 李四 李四 李四 代码: import org.apache.spar ...

  4. 关于 stl的内存分配的深浅拷贝

    http://blog.csdn.net/u012501459/article/details/44132147

  5. junit与spring-data-redis 版本对应成功的

    spring-data-redis  版本:1.7.2.RELEASE junit 版本:4.12

  6. mysql设置允许外网访问

    1.设置mysql服务允许外网访问 修改mysql的配置文件,有的是my.ini,有的是my.cnf[linux],找到bind-address变量,这个值默认是127.0.0.1,设置为0.0.0. ...

  7. [RxJS] Build your own RxJS

    JavaScript has multiple APIs that use callback functions that all do nearly the same thing with slig ...

  8. OpenGL ES 3.0之Fragment buffer objects(FBO)详解(二)

    我们可以使用帧缓冲对象来实现离屏渲染.帧缓冲对象支持下列操作 1.只使用OpenGL ES 函数创建帧缓冲区对象. 2.使用EGL context创建多个FBO. 3.创建离屏颜色.深度.模板渲染缓冲 ...

  9. 解决CentOS下无法发送邮件的问题

    最近有个项目从虚拟主机迁到云主机上,换了个环境,原来用于找回密码的发送邮件功能竟然用不了了!反应极慢,导致tenginx报504网关超时错误. 5.5 or 7.0, sockets 初次百度,看了别 ...

  10. Docker 容器入门

    1.1 容器简介 1.1.1 什么是 Linux 容器 Linux容器是与系统其他部分隔离开的一系列进程,从另一个镜像运行,并由该镜像提供支持进程所需的全部文件.容器提供的镜像包含了应用的所有依赖项, ...