SceneGraph(场景图) 简介

SceneGraph(场景图) 简介

场景图介绍

该节内容翻译自gemedev的一篇文章 blog-SceneGraph Introduction。

什么是场景图

场景图是一种将数据排序到层次结构中的方法,在层次结构中父节点影响子节点。你可能会说“这不是树吗?”你说得没错,场景图就是一棵n-tree。也就是说,它可以有任意多的孩子。但是场景图比一棵简单的树要复杂一些。它们表示在处理子对象之前要执行的某些操作。如果现在对这个概念不好理解,不用担心,这一切都会在后面的内容中给出解释。

为什么场景图有用

如果你还没有发现为什么场景图如此酷,那么让我来解释一下场景图的一些细节。假设你需要在你的游戏中模拟太阳系。这个系统里面,在中心有一颗恒星,带有两颗行星。每个行星也有两颗卫星。有两种方式可以实现这个功能。 我们可以为太阳系中的每个物体创建一个复杂的行为函数,但是如果设计师想要改变行星的位置,那么通过改变所有其他围绕它旋转的物体,就有很多工作要做。 另一个选择是创建一个场景图,让我们的生活变得简单。下图显示了如何创建场景图来表示对象:

假设旋转节点保存当前世界矩阵,并将其与旋转相乘。这将影响其后渲染的所有其他对象。所以有了这个场景图,让我们看看这个场景图的逻辑流程。

绘制Star

保存当前的矩阵(star)

执行旋转(star)

绘制Planet 1

保存当前的矩阵(planet1)

执行旋转(planet1)

绘制Moon A

绘制Moon B

恢复保存的矩阵(planet1)

绘制Planet2

保存当前的矩阵(Planet2)

执行旋转(Planet2)

绘制Moon C

绘制Moon D

恢复保存的矩阵(Planet2)

恢复保存的矩阵(star)

这是一个非常简单的场景图的实现,你也应该发现为什么场景图是一个值得拥有的东西。但你可能会对自己说,这很容易做到,只要硬编码就可以了。场景图的优势在于场景图的显示方式可以不通过硬编码的方式实现,虽然对于你能想象到的节点,比如旋转,渲染等是硬编码实现的。基于这些知识,我们可以将上面的场景变得更加复杂,let's do it。让我们在太阳系中增加一些生命,让1号行星稍微摇晃一下。是的,1号行星被一颗大小行星撞击,现在正稍微偏离其轴旋转。不用担心,我们只需要创建一个抖动节点,并在绘制行星1之前设置它。

但是行星1的摆动对我来说还不够真实,让我们继续这样做,让这两颗行星以不同的速度旋转。

现在,这个场景图比最初呈现的要复杂得多,现在让我们来看看程序的逻辑流程。

绘制Star

保存当前的矩阵

应用旋转

保存当前的矩阵

应用抖动

绘制planet1

保存当前的矩阵

应用旋转

绘制Moon A

绘制Moon B

恢复矩阵

恢复矩阵

恢复矩阵

保存当前的矩阵

应用旋转

绘制planet2

保存当前的矩阵

应用旋转

绘制Moon C

绘制Moon D

恢复矩阵

恢复矩阵

真的!现在这只是一个简单的太阳系模型!想象一下,如果我们模仿这个级别的其他部分会发生什么。

简单实现示例

我认为这已经足够对场景图进行高层次的讨论了,让我们来谈谈我们将如何实现它们。为此,我们需要一个基类,以便从所有场景图节点派生。

class CSceneNode

{

public:

// constructor

CSceneNode() { }

// destructor - calls destroy

virtual ~CSceneNode() { Destroy(); }

// release this object from memory

void Release() { delete this; }

// update our scene node

virtual void Update()

{

// loop through the list and update the children

for( std::list::iterator i = m_lstChildren.begin();

i != m_lstChildren.end(); i++ )

{

(*i)->Update();

}

}

// destroy all the children

void Destroy()

{

for( std::list::iterator i = m_lstChildren.begin();

i != m_lstChildren.end(); i++ )

(*i)->Release();

m_lstChildren.clear();

}

// add a child to our custody

void AddChild( CSceneNode* pNode )

{

m_lstChildren.push_back(pNode);

}

protected:

// list of children

std::list m_lstChildren;

}

现在这已经超出了我们的方式,我们现在可以做一个我们享有的所有类型的节点的清单。这是我认为每个场景图都应该具有的节点列表。当然,如果你觉得合适的话,你可以添加新的类型。

Geometry Node

DOF(下面会有解释)

Rotation(animated)

Scaling(animated)

Translating(animated)

Animated DOF

Switch

对于一个基本的场景图引擎来说,这应该足够了。你总是可以在你的引擎里添加更多的东西,使它成为最好的新东西。

Geometry Node

会有一个没有图形的图形引擎么?这是不可能的。所以,现在介绍一下最重要的节点:

class CGeometryNode: public CSceneNode

{

public:

CGeometryNode() { }

~CGeometryNode() { }

void Update()

{

// Draw our geometry here!

CSceneNode::Update();

}

};

注意,上面的渲染代码上有点敷衍。你应该对于如何处理这个节点,是非常清楚的。先执行几何体的渲染(或将其发送到要渲染的位置),然后更新我们的子对象。

DOF

DOF节点通常称为变换。它们只不过是一个表示偏移、旋转或缩放的矩阵。如果不想将矩阵存储在Geometry Node中,这些选项非常有用。在下一个示例中,我们假设使用OpenGL进行渲染。

class CDOFNode: public CSceneNode

{

public:

CDOFNode() { }

~CDOFNode() { }

void Initialize( float m[4][4] )

{

for( int i = 0; i < 4; i++ )

for( int j = 0; j < 4; j++ )

m_fvMatrix[i][j] = m[i][j];

}

void Update()

{

glPushMatrix();

glLoadMatrix( (float*)m_fvMatrix );

CSceneNode::Update();

glPopMatrix();

}

private:

float m_fvMatrix[4][4];

};

Switch Node

switch节点开始显示一些可以使用场景图执行的更复杂的操作。交换节点的作用就像铁路上的一个交叉点,只允许您选择以下路径之一(可以将它们更改为沿着两条路径,但这将由读者来完成)。让我们看一幅场景图,图中有一个开关节点。

现在对于场景图的这一部分,开关表示赛车游戏中的车门。由于这辆车损坏了,我们想证明它正在损坏。当我们开始比赛时,我们希望赛车不会受到任何损坏,但随着赛车在水平面上的前进,受到的损坏越来越多,我们需要将路径切换到损坏更严重的车门上。我们甚至可以扩展这一范围,使受损更严重的身体部位在产生烟雾效应后附着粒子系统。你的想象力限制了这种可能性。

猜你喜欢

人的衰老并不是匀速,科学家研究发现:这三个年龄段人老得最快
手机弯曲怎么恢复平直
365bet官方

手机弯曲怎么恢复平直

📅 08-08 ❤️ 869
探探登录失败请稍候重试
365bet官方

探探登录失败请稍候重试

📅 06-29 ❤️ 552
boss直聘怎么投简历
365bet官方

boss直聘怎么投简历

📅 01-28 ❤️ 198
苍蝇怕什么颜色的光?用什么颜色的灯光能让苍蝇害怕不再来?
掌上英雄联盟萌币怎么用?
中信精彩365

掌上英雄联盟萌币怎么用?

📅 01-04 ❤️ 995
借呗多久不用会被关?怎么避免蚂蚁借呗被关闭?
骶髂关节炎与平板支撑:你不能忽视的关系!
英雄联盟修改皮肤会封号多久
365bet官方

英雄联盟修改皮肤会封号多久

📅 07-08 ❤️ 627