之前看了两天OGRE结果就没看了,现在重新开始,边用边研究源码,写点笔记,多吸取点引擎的东西,有写的不对的麻烦指出: )
概念:场景管理器,场景节点和实体
OGRE最基础的框架 SceneManager, SceneNode, Entity
SceneManager管理渲染到屏幕上的东西,记录所有物体的位置,摄象机位置。
Entity可以理解为场景中渲染的任何一个3D模型,和Instance差不多吧,OGRE中渲染的对象是节点,不能将Entity直接放入场景,必须和SceneNode进行绑定,SceneNode负责维护Entity的一些信息,如位置。
SceneNode类似树节点,包含了已绑定的Entity的相关信息,SceneNode也不会被单独渲染,必须绑定一个Entity
很典型的一个树结构
OGRE坐标和向量基本和D3D一样,只是Z轴相反,右手坐标系
节点的添加
SceneNode::createChildSceneNode()
child,肯定是从父节点创建,这里的父节点可以为场景中的任一节点,且每个场景管理器都有一个root节点
Entity *e = mSceneMgr->createEntity( "Mode","Mode.mesh" );
SceneNode *newNode = mSceneMgr->getRootSceneNode()->createChildSceneNode( "newNode",
Vector3( 50, 0, 0 ) );
newNode->attchObject( e );
上面代码很简单,创建一个实体,"Mode"表示该节点的标识符,且唯一,不然创建实体会失败,创建节点也一样,"newNode"表示唯一标识符,这里注意节点的位置Vector3( 50, 0, 0 ) ,这是个相对值,相对于父节点而言的偏移,最后用节点绑定实体
场景节点可以对实体进行各种操作,必须平移,旋转
旋转中的Yaw是绕Y轴,Pitch是绕X轴,Roll是绕Z轴。Degree()角度转弧度的函数
基本了解了OGRE的基石,这在以后是经常使用的摄像机
教程说摄像机类似于一个SceneNode,我觉的进一步跟像一个实体,可以绑定在场景节点上,同时位置受父节点影响,Ogre同一时间内只能使用一台摄像机,要实现一些多摄像机的功能可以用场景中的节点来模拟摄像机
mCamera = mSceneMgr->createCamera( "camera" );
又见标识符,创建好摄像机后可以用SceneManager::getCamera通过标识符来获取相应的摄像机,剩下的操作和D3D的基本一直,设置位置,方向,近截面,远截面等
void Camera::lookAt(const Vector3& targetPoint)
{
updateView();
this->setDirection(targetPoint - mRealPosition);
}
通过目标点自己计算方向0。0我还是喜欢自己算好方向传给摄像机。。。。
视口
视口的概念已经在DX中很清楚了,OGRE渲染场景的三个结构:Camera, SceneManager, RenderWindow。RenderWindow就是显示物体的窗口,sceneManager建立的摄像机以后,必须要告诉RenderWindow,让它显示哪个摄像机,在窗口的什么部分显示,一般的程序中,我们建立一个摄像机,而这个摄像机占有整个RenderWindow,这样就只有一个视口了。建立视口可以用RenderWindow::addViewport( Camera * )来创建视口,通过Camera::setAspectRatio( Real ) 来设置宽高比
灯光和阴影
OGRE目前支持三种阴影:
调制纹理阴影(SHADOWTYPE_TEXTURE_MODULATIVE)
调制模版阴影(SHADOWTYPE_STENCIL_MODULATIVE)
附加模板阴影 (SHADOWTYPE_STENCIL_ADDITIVE)
纹理阴影
纹理阴影是通过将光源位置作为视点,把投影体渲染到纹理来得到,然后再把该纹理投射到接受阴影的表面上来得到最终结果。纹理阴影的优势是极少的增加了集合体的细节,不用执行对每个三角形的计算。而且渲染文理阴影的大部分工作都放在图形硬件中执行,纹理阴影的应用更加广泛,你可以把他们防入着色器中对整个阴影进行过滤而产生软阴影或者其他效果。但纹理阴影也有弊端,因为毕竟是一张纹理,是有固定的像素值,如果纹理被拉长后像素就变的很明显或者突兀,这需要一些方法来处理。
模板阴影
模板阴影是一种通过硬件的模板缓存来标记屏幕中区域的方法,这个标记最终用来挑选屏幕中的一些区域,进而包含和排除阴影区域,因为模板只能标记"打开"或者"关闭"两种状态,所以模板阴影只能产生"硬"边缘。模板阴影的弊端很多,比如CPU的消耗,因为他的计算都是在CPU中进行,如果过多的使用这项技术,会造成CPU瓶颈。
调制阴影
调制阴影的作用是暗化已经渲染完场景的颜色。场景会正常的渲染所有接受阴影的物体,然后对每一个光源执行调制,把场景变暗,最后渲染那些不接受阴影的物体
使用阴影
OGRE中使用阴影比较简单,SceneManager中提供的setShadowTechnique函数来设置阴影的类型,用setCastShadows的来设置投射阴影
Entity *e = mSceneMgr->createEntity( "Robot", "robot.mesh" ); //创建物体,开启投射引擎
e->setCastShadows( true );
mSceneMgr->getRootSceneNode()->createChildSceneNode()->attachObject( e );
mSceneMgr->setAmbientLight( ColourValue( 0, 0, 0 ) ); //设置黑色环境光
mSceneMgr->setShadowTechnique( SHADOWTYPE_STENCIL_MODULATIVE ); //设置阴影类型
有了物体及阴影后,需要一个接受阴影的物体,我们创建一个plane:
Plane plane( Vector3::UNIT_Y, 0 ); //第一个参数为法向量,第个为沿法向量移动的距离
MeshManager::getSingleton().createPlane( "ground", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, plane, 1500, 1500, 1, 1, ]true, 1, 5, 5, Vector3::UNIT_X ); //创建一个重复贴图5次,大小1500*1500的面
//把平面创建成一个实体,设置材质纹理后绑定
Entity *ent= mSceneMgr->createEntity( "GroundEntity", "ground" );
mSceneMgr->getRootSceneNode()->createChildSceneNode()->attachObject( ent );
ent->setMaterialName( "Examples/Rockwall" );
ent->setCastShadows( false );
灯光
OGRE提供了三种灯光
LT_POINT点光源 LT_SPOTLIGHT聚光 LT_DIRECTTIONAL方向光
光照最重要的是漫反射和镜面反射系数的设置,这决定了能反射多少的diffuse 和specular
建立灯光
Light *light = mSceneMgr->createLight( "light" );
light->setType( Light::LT_POINT);
light->setPosition( Vector3( 0.0f, 200.0f, 80.0f ) );
light->setDiffuseColour( 1.0f, 1.0f, 1.0f );
light->setSpecularColour( 1.0f, 1.0f, 1.0f ); light->setAttenuation( 1500, 0.1, 0, 0 );
//创建了一个点光源,设置位置,漫反射和镜面反射系数
帧监听
帧监听主要去实现一些要求每一帧更新的东西,比如所有的输入操作,在OGRE中,我们可以注册一个类来接受当一帧被渲染到屏幕之前和之后的消息,FrameListener接口定义了两个函数:
bool frameStarted( const FrameEvent& ent );
bool frameEndedst FrameEvent& ent );
OGRE的一个渲染周期大概是这样:
1.Root object调用所有注册的FrameListener的frameStarted方法
2.Root object渲染一帧
3.Root object调用所有注册的FrameListener的frameEndedst方法
frameStarted和frameEndedst的返回值表示程序是否保持渲染,如果返回false,程序将退出,
FrameEvent的两个成员,只有timeSinceLastFram是在帧监听有用的,它记录了frameStarted最近被激活的时间
注册监听类
在createCamera中创建摄像机后,添加下面的代码
mFrameListener = new ********FrameListener( mWindow, mCamara, mSceneMgr );
mRoot->addFrameListener( mFrameListener );
至于为什么要在createCamera中加,原文说的是因为createCamera可以用来移动相机和改变相机的位置,感觉很匪夷所思啊,不知道和添加帧监听有什么关系,我觉的主要是因为要在场景开始update前完成对监听的添加,其实放在createScene也可以,同样mRoot也有专门解除监听的函数removeFrameListener,两个函数都只能用指针做参数,不像之前的可以有标识符,所以得为监听保存一份指针
使用
先创建一个模型以及灯光
Entity *ent = mSceneMgr->createEntity( "Ninja", "ninja.mesh" );
SceneNode *node = mSceneMgr->getRootSceneNode()->createChildSceneNode( "NinjaNode" );
node->attachObject( ent );
Light *light = mSceneMgr->createLight( "light1" );
light->setType( Light::LT_POINT );
light->setPosition( pos );
light->setDiffuseColour( ColourValue::White );
light->setSpecularColour( ColourValue::White );
然后创建两个节点用来绑定摄像机,我们准备通过鼠标左键的电击来切换摄像机所在的节点
node = mSceneMgr->getRootSceneNode()->createChildSceneNode( "CamNode1", Vector3( -400, 200, 400 ) );
node->yaw( Degree( -45 ) );
node->attachObject( mCamera );
node = mSceneMgr->getRootSceneNode()->createChildSceneNode( "CamNode2", Vector3( 0, 200, 400 ) );
然后在FrameListener中进行操作,这里要添加几个变量
bool mMouseDown; //判断左键是否在上一帧被按下,这里主要做的目的是为了防止一次按键效果被逻辑执行几帧,这种情况以前经常遇到,不过之前都是加个时间间隔来判断
Real mToggle; //我们可以执行下一个操作的时间,即当一个按键被按下去后,在mToggle指明的这段时间里不允许有其他动作发生。
Real mMove; //移动常量
Real mRotate; //旋转常量
SceneManager *mSceneMgr; //当前场景管理器
SceneNode *mCamNode; //当前摄像机所附在的场景节点
在我们注册是帧监听类里面,重载frameStated方法,因为每次渲染前都会执行这个函数,然后开始获取输入,开放输入系统(OIS)提供了三个主要的类来获取输入:KeyBoard, Mouse和JoyStick,我们这里使用无缓冲输入,要做的第一件事是或者当前鼠标键盘的状态。在frameStated中添加
mMouse->capture();
mKeyboard->capture();
检查左键是否按下
bool currMouse = mMonse->getMouseState().buttonDown(OIS::MB_Left);
如果鼠标按下,currMouse为true,同时我们要判断上一帧鼠标有没有按下,如果按下了,那着帧我们就不处理
if( currMouse && !mMouseDown )
{
SceneNode *node1 = mSceneMgr->getSceneNode( "CamNode1" );
SceneNode *node2 = mSceneMgr->getSceneNode( "CamNode2" );
if( mCamera->getParentSceneNode() == node1 )
{
node1->detachObject( mCamera );
node2->attachObject( mCamera );
}
else
{
node2->detachObject( mCamera );
node1->attachObject( mCamera );
}
}
mMouseDown = currMouse;
上面的方法有个缺点就是要为每一个操作建立一个变量标志,这很麻烦,取而带之的是使用mToggle,来各种上一次所有按键的时间,只有经过一定时间了才允许出发事件
mToggle -= evt.timeSinceLastFrame; //计算上一帧到现在和我们设定的时间段差多少,如果是负,则证明已经超过时间段了,可以进行事件响应
if( ( mToggle < 0.0f) && mMonse->getMouseState().buttonDown(OIS::MB_Left) )
{
mToggle = 0.5f;
***********************代码同上
}
剩下关于摄像机移动就不写了,其实就是重点就是在不同坐标系TS_PARENT,TS_LOCAL,TS_PARENT里面移动