转:Ogre的SceneManager分析
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分析的更多相关文章
- 转:Ogre源码分析之Root类、Facade模式
Ogre源码分析(一)Root类,Facade模式 Ogre中的Root对象是一个Ogre应用程序的主入口点.因为它是整个Ogre引擎的外观(Façade)类.通过Root对象来开启和停止Ogre是最 ...
- [Ogre][地形]OgreTerrain分析以及使用
Ogre 1.7.2中的地形教程 ○读者可以对照着Ogre1.7.2中的terrain.h源码进行阅读加深理解,蓝色部分均为源码 ○去除了一些具体场景比如添加mesh,设置setAmbientLigh ...
- 转:Ogre的MaterialSystem分析
1. Mesh .SubMesh.SubEntity和Entity 所有的Mesh对象是由SubMesh构成的,每个SubMesh代表了Mesh对象的一部分,该部分只能使用一种Meterial.如果一 ...
- Axiom3D写游戏:第一个窗口
Axiom主要的代码大致翻看了下,就想到了自己来模拟一下游戏开发. 这章主要包括创建窗口及3D渲染的一些基本元素,并添加一个第三人称的骨骼动画作主角,加上前文中修改过后的地形组件,能用鼠标和键盘进行漫 ...
- OGRE启动过程详解(OGRE HelloWorld程序原理解析)
本文介绍 OGRE 3D 1.9 程序的启动过程,即从程序启动到3D图形呈现,背后有哪些OGRE相关的代码被执行.会涉及的OGRE类包括: Root RenderSystem RenderWindow ...
- Ogre内部渲染流程分析系列
come from:http://blog.csdn.net/weiqubo/article/details/6956005 要理解OGRE引擎,就要理解其中占很重要位置的 Renderable接口, ...
- 【转载】Ogre:Beginner Tutorial 1: SceneNode, Entity,和SceneManager 结构
原文:Beginner Tutorial 1: SceneNode, Entity,和SceneManager 结构 先决条件 这个教程假设你有C++编程的基础并且可以配置并编译OGRE应用程序 ...
- 实现Ogre的脚本分离 - 天龙八部的源码分析(一)
目的 在研究天龙八部游戏的源码之时, 发现 Ogre 材质的模板部分被单独放在一个 material 文件之内, 继承模板的其他材质则位于另外的文件, 当我使用Ogre 官方源码, 加载脚本时其不会查 ...
- ogre sample分析(一)
ogre自带了一些例子,逐个过一遍并自己动手做一些调整 1 Sample_BezierPatch:这个例子直接用数值来构造顶点缓存并创建entity,这种方法一般只能创建简单对象,本人以为复杂对象顶点 ...
随机推荐
- SCRIPT5009: “Sys”未定义 部署.net ajax 解决方案
今天在部署asp.net ajax 的时候发现部署服务器的时候,ajax刷新不正确,开始以为是System.Web.Extensions没有引用到本地,baidu一圈发现(最近google上不了郁闷~ ...
- 如何确定HyperThreading是否在Linux上已开启?
命令如下: dmidecode -t processor | grep HTT 参考资料 ============= https://unix.stackexchange.com/questions/ ...
- CSS- 横向和纵向时间轴
时间轴在展示公司发展信息,服务流程中用的比较多,常见的注册登录有的是通过引导,一步一步的来完成,上面会通过时间轴告诉用户当前在哪一步,公司在关于我们或者发展流程的时候也特别喜欢用时间轴来展示,简单的实 ...
- java.sql.SQLException: ORA-00932: 数据类型不一致: 应为 -, 但却获得 CLOB
总是报:ORA-00932: 数据类型不一致: 应为 -, 但却获得 CLOB 是由于这个a.progressAndPlan字段clob字段. 第一种解决方法: a.progressAndPlan 改 ...
- 【Spark】SparkStreaming-Kafka-集成-终极参考资料
SparkStreaming-Kafka-集成-终极参考资料 Spark Streaming和Kafka整合开发指南(二) – 过往记忆 Streamingkafka零丢失 | 等英博客 spark- ...
- sort命令的使用,结合uniq, awk等
这条命令的意思:lastb | awk -F " " '{print $3}' | sort | uniq -c | sort -k1,1nr > output.log 过滤 ...
- 基于ARM的模拟器
ARM的ARMulator: ARMulator 是一个在 ARM 公司推出的集成开发环境 ADS (ARM Developer Suite)中提供的指令集模拟器.它与运行在通用计算机(通常是x86体 ...
- [Git] Change the commit message of my last commit
Did you make a typo in your last commit message? No problem, we can use the git --amend command to c ...
- 详解Nginx + Tomcat 反向代理 如何在高效的在一台服务器部署多个站点
转自:http://www.jb51.net/article/100111.htm 首先我们需要安装好Nginx.jdk.Tomcat,安装方法已经在 上一篇 说过了,本篇不再赘述. 下来看一下我们的 ...
- C++ 纯虚方法
1.纯虚方法解决什么样的问题,为什么要设计出纯虚方法? 考虑下面的需求,基类声明了一个方法,这个方法只针对具体的子类才有意义,比如Animal的Eat()方法,调用Animal的Eat方法是没有意义的 ...