• 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<CSceneNode*>::iterator i = m_lstChildren.begin();
             i != m_lstChildren.end(); i++ )
        {
          (*i)->Update();
        }
      }
    
      // destroy all the children
      void Destroy()
      {
        for( std::list<CSceneNode*>::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<CSceneNode*> 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节点开始显示一些可以使用场景图执行的更复杂的操作。交换节点的作用就像铁路上的一个交叉点,只允许您选择以下路径之一(可以将它们更改为沿着两条路径,但这将由读者来完成)。让我们看一幅场景图,图中有一个开关节点。

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

  • 相关阅读:
    rsync使用
    文件系统、mkdir、touch、nano、cp笔记
    man/ls/clock/date/echo笔记
    Python之路,Day2
    Python之路,Day1
    自动化部署nginx负载均衡及监控短信报警
    NO.11天作业
    Tiny C Compiler简介-wiki
    stm32中使用cubemx配置freertos的信号量大小
    c99的新功能
  • 原文地址:https://www.cnblogs.com/grass-and-moon/p/13637387.html
Copyright © 2020-2023  润新知