Loading

享元模式学习笔记

引用

微信读书——游戏编程模式

前言

“使用共享以高效地支持大量的细粒度对象。”
迷雾升起,一片雄伟、古老而茂盛的森林在眼前展现。数不尽的远古铁杉迎面扑来,宛如一座绿色的大教堂。漫天树叶像是褪色的巨大玻璃穹顶,将阳光滤碎成细密的水雾。透过高大树干的间隙,你能感到这庞大的森林往远方渐逝。这是每一个游戏开发者都梦寐以求的超现实游戏场景,这样的游戏场景通常会使用一个模式来实现,它有个很低调的名字:享元模式。

森林之树

数以千计的树木,每一棵树木又包含着成千上万的多边形。即便你有足够的内存来存储这片森林,为了在屏幕上面渲染出森林,所有的数据也必须按照一定的方式进行组织并沿着总线从CPU传送到GPU里去。
每一棵树都有一些与之关联的数据:

  1. 一个多边形网格:它定义了树干、树枝和树叶的几何描述。
  2. 树皮和树叶的纹理。·树在森林中的位置以及朝向。
  3. 调节参数:如大小、颜色等,以使每棵树看起来都不一样。

如果你想用代码表述上面的特征,那么将得到类似如下的结构:

class Tree
{
    private:
        Mesh mesh_;
        Texture bark_;
        Texture leaves_;
        Vector position_;
        double height_;
        double thickness_;
        Color barkTint_;
        Color leafTint_;
};

这里的数据量很大,尤其是网格和纹理。想要将包含整片森林的对象数据在一帧内传给GPU几乎是不可能的。好在,有一个老办法可以解决这个问题。

如果你让美工为整片森林的每棵树单独制作独立的模型,那要么你是疯子,要么你就是个亿万富豪。

我们可以将对象分割成两个独立的类。首先,我们将所有树木通用的数据放到一个单独的类中:

class TreeModel
{
    private:
        Mesh mesh_;
        Texture bark_;
        Texture leaves_;
};

整个游戏只需要一份这样的数据,因为没有理由为相同的网格和纹理分配成千上万份内存。然后,游戏世界中每一颗树的实例都有一个指向共享的TreeModel的引用。Tree类中的其他数据成员用来形成树木之间的差异:

class Tree
{
    private:
        TreeModel* model_;
        Vector position_;
        double height_;
        double thickness_;
        Color barkTint_;
        Color leafTint_;
};

为了最大程度地减少发送到GPU上的数据量,我们希望只发送一次共享数据——TreeModel。然后我们再单独地将每棵树实例的特有数据——位置、颜色和缩放比推送到GPU。最后,我们告诉GPU,“使用那个共享的模型来渲染每个实例”。

享元模式

享元(Flyweight),顾名思义,一般来说当你有太多对象并考虑对其进行轻量化时它便能派上用场。
在实例绘制时,在总线上往GPU传输每棵树的数据所花费的时间,与这些数据所占用的内存都是问题所在,但解决这两个问题的基本思想是一致的。
享元模式通过将对象数据切分成两种类型来解决问题。

  1. 第一种类型数据是那些不属于单一实例对象并且能够被所有对象共享的数据。GoF将其称为内部状态(the intrinsic state),但我更喜欢将它认为是“上下文无关”的状态。在本例中,这指的便是树木的几何形状和纹理数据等。
  2. 其他数据便是外部状态(the extrinsic state),对于每一个实例它们都是唯一的。在本例中,指的是每棵树的位置、缩放比例和颜色。就像上面的示例代码一样,这个模式通过在每一个对象实例之间共享内部状态数据来节省内存。

总结

享元模式的精髓在于“享”,把一个对象的公共资源抽象成一个实例,并分享出去,让所有对象都可以公用,从而减少计算量,但也正是如此享元对象一般总是不可变。

posted @ 2022-08-28 18:12  数学天才琪露诺  阅读(29)  评论(0编辑  收藏  举报