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

2024-08-22 14:08

本文主要是介绍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   地形、天空、雾

这篇关于OGRE学习系列四:基础教程2 灯光,相机和阴影的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1096493

相关文章

Java进阶学习之如何开启远程调式

《Java进阶学习之如何开启远程调式》Java开发中的远程调试是一项至关重要的技能,特别是在处理生产环境的问题或者协作开发时,:本文主要介绍Java进阶学习之如何开启远程调式的相关资料,需要的朋友... 目录概述Java远程调试的开启与底层原理开启Java远程调试底层原理JVM参数总结&nbsMbKKXJx

Java深度学习库DJL实现Python的NumPy方式

《Java深度学习库DJL实现Python的NumPy方式》本文介绍了DJL库的背景和基本功能,包括NDArray的创建、数学运算、数据获取和设置等,同时,还展示了如何使用NDArray进行数据预处理... 目录1 NDArray 的背景介绍1.1 架构2 JavaDJL使用2.1 安装DJL2.2 基本操

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

科研绘图系列:R语言扩展物种堆积图(Extended Stacked Barplot)

介绍 R语言的扩展物种堆积图是一种数据可视化工具,它不仅展示了物种的堆积结果,还整合了不同样本分组之间的差异性分析结果。这种图形表示方法能够直观地比较不同物种在各个分组中的显著性差异,为研究者提供了一种有效的数据解读方式。 加载R包 knitr::opts_chunk$set(warning = F, message = F)library(tidyverse)library(phyl

【机器学习】高斯过程的基本概念和应用领域以及在python中的实例

引言 高斯过程(Gaussian Process,简称GP)是一种概率模型,用于描述一组随机变量的联合概率分布,其中任何一个有限维度的子集都具有高斯分布 文章目录 引言一、高斯过程1.1 基本定义1.1.1 随机过程1.1.2 高斯分布 1.2 高斯过程的特性1.2.1 联合高斯性1.2.2 均值函数1.2.3 协方差函数(或核函数) 1.3 核函数1.4 高斯过程回归(Gauss