OGRE学习系列四:基础教程2 灯光,相机和阴影

注:本文翻译自官网Basic Tutorial 2,由于本人英语水平有限,翻译内容难免出现错误,敬请理解

//========================================================================================

教程简介:

本教程将扩展灯光在场景中的使用,使用他们生成阴影。同时,本教程还将包括Ogre的相机的基本用法。

本节教程使用的全部源代码可以从这里下载

//========================================================================================

一、预备知识

本教程假设你已经知道如何构建一个Ogre项目并能成功编译。如果需要帮助,请阅读Setting Up An Application。本教程也是基础教程系列的一部分,本教程假设你已经掌握了前面教程中的内容。


本节教程目录列表:

1、预备知识

2、建立场景(Setting Up the Scene)

3、Ogre的Camera类

4、创建一个相机

5、视口

6、创建一个视口

7、构建场景(Building the Scene)

8、在Ogre中使用阴影

9、灯光

10、创建一个灯光

11、创建更多灯光

12、阴影类型

13、总结

14、全部源文件

//===================================================================================================

二、建立场景(Setting Up the Scene)

这次,我们给TutorialApplication类添加更多的方法。添加如下代码到头文件中的protected选项下。

TutorialApplication.h

virtual void createCamera();

virtual void createViewports();

这两个方法已经在BaseApplication类内定义为虚函数,在本节教程中,我们提供了重载。这也是我们如何逐步替代BaseApplication中的一些隐藏的函数。

记得在cpp文件中添加定义。

TutorialApplication.cpp

voidTutorialApplication::createCamera()

{

}

void TutorialApplication::createViewports()

{

}


三、Ogre的Camera类

相机Camera类是我们用来观察我们的场景的对象。Camera 是一种特殊的对象,它与SceneNode的使用方式相似。它有setPosition和yaw方法。你也可以将它与SceneNode连接。例如:你也许想要临时的将你的相机与一个SceneNode连接,使它能够沿着一条空中的路径创建一个空中动画。就像SceneNode一样,相机的位置也与其父节点位置相关。相机并不是一个场景节点(它其实是继承于Frustum类),但是对于移动和旋转,你可以将它视为SceneNode。


四、创建一个相机

我们现在重载一个BaseApplication 的createCamera方法,第一步将要求SceneManager去创建一个新的相机,添加下列代码到createCamera中:

mCamera = mSceneMgr->createCamera("PlayerCam");

你可以使用SceneManager的getCamera方法获得相机名称

下一步:我们将设置相机的位置,并使用lookAt方法设置它的朝向

mCamera->setPosition(Ogre::Vector3(0,300,500));

mCamera->lookAt(Ogre::Vector3(0,0,0));

这个lookAt函数特别有用,作用如其名,它旋转相机,使得观察的朝向与给定的向量方向相同,它使相机“看向”这个点。

最后我们要做的就是设置最近剪裁距离为5个单位,这是一个相机不再渲染任何Mesh的距离,如果你距离一个Mesh特别近,你会切开Mesh并允许你看到Mesh的内部。备选方案是用一个微小的,高度放大的小块的Mesh纹理填充整个屏幕,这取决于你想在你的屏幕上显示什么。针对本示例,我们设置如下:

mCamera->setNearClipDistance(5);

你也可以为相机设置远方裁剪距离。这回砍掉范围内的Meshs。尽管如此,当你使用stencil shadows(本节教程将会用到)的时候,你也不应该设置远方裁剪距离。

最后我们将要做的是创建一个新的sdkCameraMan。这是一个相机控制器,由OgreBites提供。

mCameraMan=new OgreBites::SdkCameraMan(mCamera);

由于我们已经申请了一些动态内存,我们必须经常确认它是否适当的清除干净。在我们的这种情况下,mCameraMan变量由BaseApplication类的析构函数管理。因为我们简单的重新创建了Camera代码。如果你看一下BaseApplication::~BaseApplication,你会看到这一行:

if(mCameraMan) delete mCameraMan;

这句代码将mCameraMan完成析构,释放内存。


五、视口

当处理场景中的多个相机的时候,视口的概念变得十分有用,我们现在开始接触它,因为它会帮助你理解更多关于Ogre是如何决定在渲染时使用哪一个相机的。Ogre使得多个场景管理器同时运行成为现实。这也允许你将屏幕分块,并使用分散的相机渲染不同的视角下的场景。这将允许一些有创意的事情发生,比如分割窗口和使用迷你地图。这些内容会在后续的教程中讲到。

有三种结构对理解Ogre渲染场景的方式十分重要:相机、场景管理器和渲染窗口。我们还没提及到渲染窗口。它基本代表了我们渲染的整个窗口。场景管理器将创建相机去观察场景,然后我们告诉渲染窗口在哪里展示每一个相机视角。我们告诉渲染窗口渲染窗口的哪部分区域的方式就是给它一个Viewport(视口)。在许多情况下,我们将简单的创建一个相机和一个代表整个屏幕的视口,在BaseApplication中我们也是这样做了的。


六、创建视口

让我们为我们的场景创建一个视口,为了做到这一点,我们使用了RenderWindow的addViewport的方法,添加如下代码到TutorialApplication::createViewports:

Ogre::Viewport* vp=mWindow->addViewport(mCamera);

mWindow是另一个在BaseApplication里为我们定义的变量,让我们设置视口的背景颜色

vp->setBackgroundColour(Ogre::ColourValue(0,0,0));

我们将它设置成黑色,因为我们接下来将添加有颜色的光照,我们不想背景颜色影响我们看到的灯光。

我们将要做的最后一件事是设置相机的高宽比,如果你使用了一些东西而不是标准的全屏视口,如果没有设置这个会导致场景扭曲。为了展示,我们将在这里设置它尽管我们使用了默认的高宽比。

mCamera->setAspectRatio(Ogre::Real(vp->getActualWidth()) / Ogre::Real(vp->getActualHeight()));

我们已经从视口中获取了宽度和高度,然后去设置高宽比。正如我们提到的,设置的默认值使用了全屏的维度。

编译和运行你的程序,你英爱仍然仅仅可以看到一个黑色的屏幕,仅仅是确认一下它的运行。


七、构建场景(Building the Scene)

在我们正式开始学习光照和阴影之前,我们先添加一些元素到我们的场景中。让我们先放置一个ninja在正中间。添加下列的createScene代码在我们设置ambient light之后:

Ogre::Entity* ninjaEntity = mSceneMgr->createEntity("ninja.mesh");

ninjaEntity->setCastShadows(true);

mSceneMgr->getRootSceneNode()->createChildSceneNode()->attachObject(ninjaEntity);

这段代码应该熟悉,除了我们要求Mesh投射阴影。注意到我们已经创建了一个子场景节点,并且将ninjaEntity一次性连接到上面。

我们还应该创建一些东西让ninja站在上面,我们可以使用MeshManager 根据草稿创建Mesh。我们将使用它生辰纹理平面当作地面来使用。

我们要做的第一件事就是创建一个抽象的平面,这个不是Mesh,只是个蓝图。

Ogre::Plane plane(Ogre::Vector3::UNIT_Y,0);

我们通过提供一个向量创建了一个平面,这个向量是平面的法向量。向量的起点在原点,所以我们创建了一个垂直于y轴的平面并且距离远点的距离为0.如下图:

也有其他的平面构造函数的重载,需要我们传递第二个向量取代距离原点的距离。这允许我们构建三维空间里的任意平面。

现在,我们要求MeshManager根据我们的平面蓝图去为我们创建一个Mesh。这个MeshManager在我们初始化我们的程序时就已经追踪了我们加载的资源。紧接着,它可以为我们创建一个新的Mesh。

Ogre::MeshManager::getSingleton().createPlane(

"ground";

Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,

plane,

1500,1500,20,20,

true,

1,5,5,

Ogre::Vector3::UNIT_Z);

这是一个复杂的函数,目前我们并不准备完整的理解它,如果你想了解更多,你可以通过阅读MeshManager类。基本上,我们创建了一个称为“ground”的新类,它的尺寸为1500*1500。

现在我们将使用这个Mesh创建一个新的实体。

Ogre::Entity* groundEntity = mSceneMgr->createEntity("ground");

mSceneMgr->getRootSceneNode()->createChildSceneNode()->attachObject(groundEntity);

注意到我们给createEntity函数传递的作为实体名称的参数,这就是我们刚刚创建的Mesh的名称,我们通常看到的Mesh的名称后缀通常是“.mesh”。

我们想要告诉我们的SceneManager不要通过我们的地面实体投射阴影。这会是一种浪费。不要迷惑,这意味着地面不会投射阴影。并不意味着我们不能向地面投射阴影。

groundEntity->setCastShadows(false);

最后我们需要给我们的地面一种材料,现在,这将是最易于使用的材料,来自于Ogre包含的示例脚本。在你的SDK里你应该拥有这些资源或者是你下载的Ogre源文件路径下。

groundEntity->setMaterialName("Examples/Rockwall");

确认你为这些材料添加的纹理以及这些例子,材料脚本在你下载的路径里。在我们的示例中,纹理被称作“rockwall.tga”,你可以根据名字找到它。

八、在Ogre中使用阴影

在Ogre中使用阴影十分简单。SceneManager类中有一个setShadowTechnique的方法我们可以使用。然后无论何时我们创建了一个实体,我们可以调用setCastShadows去选择哪一个实体将会投射阴影。

让我们关闭这个ambient 灯光,我们可以看到光源的全部影像。找到createScene中调用的setAmbientLight,做出以下改变:

mSceneMgr->setAmbientLight(Orge::ColourValue(0,0,0));

mSceneMgr->setShadowTechnique(Ogre::SHADOWTYPE_STENCIL_ADDITIVE);

现在这个场景管理器将使用additive stencil shadows。我们添加一些灯光来看一下。


九、灯光

Ogre提供了三种类型的灯光

Ogre::Light::LT_POINT - 这种灯光从一点向所有方向相同的传播。(点光源)

Ogre::Light::LT_SPOTLIGHT - 这种光工作模式像是手电筒,它产生一个实心圆柱形光路,且重心最亮,向周围逐渐减弱

Ogre::Light::LT_DIRECTIONAL - 这种光模拟一个很远的地方的巨大的发光体,像是白天,灯光以相同的角度照射在整个场景中的任何角落(平行光)

Light类有一个非常广泛的属性,其中两个最重要的是镜面反射与漫反射颜色,每一种材质的脚本定义了镜面反射和漫反射各反射多少光。这些属性将会在后续的教程中提到。


十、创建灯光

让我们在场景中增加一个灯光。我们调用SceneManager的createLight方法来实现这个创建过程,在createScene函数里,当我们完成创建地面实体之后添加如下代码:

Ogre::Light* spotLight = mSceneMgr->createLight("SpotLight");

我们将设置漫反射与镜面反射的颜色为纯蓝色。

spotLight->setDiffuseColour(0,0,1.0);

spotLight->setSpecularColour(0,0,1.0);

下一步,我们将设置聚光灯的光线类型。

spotLight->setType(Ogre::Light::LT_SPOTLIGHT);

聚光灯需要位置和方向。记得它就像是一个手电筒。我们将聚光灯防止在ninja的右侧肩膀上方,以斜向下45度角的方位照射他。

spotLight->setDirection(-1,-1,0);

spotLight->setPosition(Ogre::Vector3(200,200,0));


最后我们设置聚光灯范围,这些是决定光线从中心向外周衰减的距离的边缘的角度。

spotLight->setSpotlightRange(Ogre::Degree(35),Ogre::Degree(50));

编译运行程序,你可以看到有蓝色阴影的ninja角色;



十一、创建更多灯光

下一步我们在场景中添加一个定向光源,这种类型的光本质上是模拟日光或者月光。这个光源以相同的角度均衡的投射到场景中。正如前面所说,我们以创建光源和设置它的类型开始。

Ogre::Light* directionalLight = mSceneMgr->createLight("DirectionalLight");

directionalLight->setType(Ogre::Light::LT_DIRECTIONAL);

现在,我们将镜面反射和漫反射光设置为暗红。

directionalLight->setDiffuseColour(Ogre::ColourValue(.4,0,0));

directionalLight->setSpecularColour(Ogre::ColourValue(.4,0,0));

最后,我们需要设置光线的角度,一个定向光源并不存在位置,因为它的模型是一个无限远的点光源。

directionalLight->setDirection(Ogre::Vector3(0,-1,1));


这个Light类也定义了一个setAttenuation 函数,它允许你控制灯光在远处如何消散。当你看完这节教程,尝试在你的场景中使用这个方法看它如何影响你的灯光。

编译运行你的程序,你的ninja应该在身后有一个阴影,此时,场景应该填充了绿光。


为完成这个设置,我们将添加一个点光源到我们的场景中。

Ogre::Light* pointLight = mSceneMgr->createLight("PointLight");

pointLight->setType(Ogre::Light::LT_POINT);

我们将镜面反射光和漫反射光设置为暗黑色。

pointLight->setDiffuseColour(.3,.3,.3);

pointLight->setSpecularColour(.3,.3,.3);

点光源没有方向,只有一个位置。我们将把我们的最后一个光源防止在ninja的后上方。

pointLight->setPosition(Ogre::Vector3(0,150,250));

编译运行程序,你可以看到ninja正面出现一个长阴影,考虑一下为什么颜色最后呈现成这样。例如:为什么ninja后面的阴影看起来完全没有红色?



十二、阴影类型

Ogre目前支持三种阴影类型

1):Ogre::SHADOWTYPE_TEXTURE_MODULATIVE - 这种需要大量的计算,渲染成黑色或者白色也会用到很多的阴影casters。然后被用于场景中。

2):Ogre::SHADOWTYPE_STENCIL_MODULATIVE - 当所有不透明物体被渲染完成后,这种技术在渲染阴影时调整所有的体积阴影,这样并没有让阴影渲染更精确,反而降低了其准确性。

3):Ogre::SHADOWTYPE_STENCIL_ADDITIVE - 这种渲染方法将每个灯光以独立加入的形式进行渲染。这种方式的实现在图像卡上也比较困难,因为每一次添加都需要增加以此渲染过程。

尝试体验一下不同的渲染类型,还有一些阴影相关的方法在SceneManager类中,你也可以关注一下。

Ogre不支持柔和阴影作为引擎的一部分,你可以写你自己的程序去实现柔和阴影以及一些其他的东西。The Ogre Manual 有大量的关于阴影的描述。


十三、总结

本节教程介绍了场景中灯光和阴影的用法。第一步,我们介绍了如何使用MeshManager 根据草图生成Mesh,然后我们选择Ogre的阴影类型,最后,我们开始添加每个光源(Light)类型的实例到我们的场景中。我们创建了一个聚光灯,一个定向光源,和一个点光源。你也可以通过写你自己的顶点和框架结构来扩展Ogre的光源和阴影系统。参考Ogre Manual获得更多细节。

我们已经提到了许多不同的设置来说明Ogre如何渲染灯光和阴影。


十四、本节教程全部源文件

本节教程的全部源文件可以从这里下载。


//====================================================================================================================

下一节:OGRE学习系列五:基础教程3   地形、天空、雾

展开阅读全文

没有更多推荐了,返回首页