0

我正在用 C++ 构建一个动画库。该库将包括一个用于建模和渲染场景的系统。系统的要求是

  1. 建模与渲染分离。有关场景状态的信息应与渲染场景的过程分开存储。
  2. 可扩展的建模和渲染。如果库本身定义了一个node类,则该库的用户应该能够定义一个custom_node扩展其功能的新类型node(可能通过继承,但也可能通过其他方式)。然后,用户应该能够指定一个自定义过程来呈现custom_node. 在这样做时,用户应该能够以某种方式利用库中已经存在的渲染过程。用户还应该能够定义用于渲染库节点的新过程。添加:用户应该能够定义整个渲染系统并选择使用哪个渲染场景。例如,假设该库包含一个逼真的渲染系统,但用户想要使用准系统示意图渲染系统来渲染场景。用户应该能够使用动画库在动画循环(渲染帧、更新场景、渲染下一帧等)期间使用的通用渲染接口来实现这样的渲染器。
  3. 库的封装。要将库的功能扩展到自定义node和渲染过程,用户不需要编辑库的底层代码。

一种失败的方法:使用nodes 的树作为场景的模型。node创建新节点类型的子类。由于节点的子节点的类型可能要到运行时才能知道,所以节点的子节点存储在vector<std::shared_ptr<node>>. 还定义一个顶级renderer类和子类renderer以提供特定类型的渲染。

class image;

class node {
    virtual image render(renderer &r) {return r.render(*this);}
    std::vector<std::shared_ptr<node>> children;
    std::weak_ptr<node> parent;
    // ...
}

class renderer {
    image render(node &n) {/*rendering code */}
// ...
}

要渲染场景,请定义渲染器

renderer r{};

并使用您最喜欢的遍历方法遍历节点树。当你遇到每一个std::shared_ptr<node> n,打电话

n->render(r);

这种方法将建模和渲染分开,并且允许可扩展性。要创建一个custom_node,库的用户只需子类node

class custom_node : public node {
    virtual image render(renderer &r) override {return r.render(*this)}
}

在我们尝试提供一种自定义方式来呈现我们的custom_node. 为此,我们尝试子类renderer化和重载该render方法:

class custom_renderer : public renderer {
    image render(custom_node &n) {/*custom rendering code*/}
}

就其本身而言,这是行不通的。考虑:

renderer &r = custom_renderer{};
std::shared_ptr<node> n = std::make_shared<custom_node>{};
n->render(r); // calls renderer::render(node &)

为了根据需要调用 custom_renderer::render(custom_node &n),我们需要在原始渲染器类中添加一个虚拟重载:

class renderer {
    image render(node &n) {/*rendering code */}
    virtual image render(custom_node &n) = 0;
}

不幸的是,这破坏了库的封装,因为我们已经编辑了其中一个库类。

那么,我们如何设计一个满足所有 3 个要求的系统呢?

4

2 回答 2

0

我自己的解决方案,Yakk 提出的类型擦除方法的变体。可以在此处找到有关该问题和此特定方法的更多详细信息。

struct image{};

struct renderable_concept {
  virtual image render() const = 0;
};

template <class WRAPPED, class RENDERER>
struct renderable_model : public renderable_concept {
  WRAPPED *w;
  RENDERER r;
  virtual image render() const final override {
    return r.render(*w);
  }
  renderable_model(WRAPPED *w_, RENDERER r_) : w(w_), r(r_) {}
};

struct node {
  template <class WRAPPED, class RENDERER>
  node(WRAPPED *w_, RENDERER r_) :
    p_renderable(new renderable_model<WRAPPED,RENDERER>(w_,r_)) {}

  template <class RENDERER>
  node(RENDERER r_) : node(this,r_) {}

  image render() {return p_renderable->render();}
  vector<shared_ptr<node>> children;
  unique_ptr<renderable_concept> p_renderable;
};

struct text_node : public node {
  template<class RENDERER>
  text_node(RENDERER r) : node(this,r) {}

  string val;
};

struct shape_node : public node {
  template<class RENDERER>
  shape_node(RENDERER r) : node(this,r) {}
};

struct color_renderer {
  image render(node &) const {/*implementation*/};
  image render(text_node &) const {/*implementation*/};
  image render(shape_node &) const {/*implementation*/};
};

struct grayscale_renderer {
  image render(node &) const {/*implementation*/};
  image render(text_node &) const {/*implementation*/};
  image render(shape_node &) const {/*implementation*/};
};
于 2017-08-09T21:07:27.893 回答
0

类型擦除。该库提供了 render(some_data) 函数。

我们从几种节点开始。Primitives 是 render(primitive) 只是绘制一些东西的节点。

列表节点有子节点,render(list_node) 绘制其内容。

generic_node 存储任何具有渲染(?)重载的东西。它类型擦除渲染(?)操作。调用 render(generic_node) 会在包含的数据上调用该类型擦除操作。

list_node 包含 generic_nodes 的向量。

为了添加新的渲染类型,您只需定义一个新类型,重载 render(new_type),然后将其存储在 generic_node 中。

这是一个原始的实现:

struct render_target {
  // stuff about the thing we are rendering on
};
struct renderable_concept {
  virtual ~renderable_concept() {}
  virtual void render_on( render_target* ) const = 0;
};
template<class T>
void render( render_target*, T const& ) = delete; // by default, nothing renders

struct emplace_tag {};
template<class T>
struct renderable_model : renderable_concept {
  T t;
  template<class...Us>
  renderable_model( emplace_tag, Us&&...us ):
    t{std::forward<Us>(us)...}
  {}
  void render_on( render_target* target ) const final override {
    render( target, t );
  }
};
template<class T>
struct emplace_as {};
struct generic_node {
  friend void render( render_target* target, generic_node const& node ) {
    if (!node.pImpl) return;
    node.pImpl->render_on(target);
  }
  template<class T, class...Us>
  generic_node( emplace_as<T>, Us&&... us):
    pImpl( std::make_shared<renderable_model<T>>(emplace_tag{}, std::forward<Us>(us)...) )
  {}
  generic_node() = default;
  generic_node(generic_node&&)=default;
  generic_node(generic_node const&)=default;
  generic_node& operator=(generic_node&&)=default;
  generic_node& operator=(generic_node const&)=default;
private:
  std::shared_ptr<renderable_concept> pImpl;
};

现在,如何制作列表节点。

struct list_node {
  std::vector<generic_node> nodes;
  friend void render( render_target* target, list_node const& self ) {
    for (auto&& node:self.nodes)
      render(target, node);
  }
  list_node(std::vector<generic_node> ns):nodes(std::move(ns)) {}
  list_node() = default;
  list_node(list_node&&)=default;
  list_node& operator=(list_node&&)=default;
};

template<class T, class...Args>
generic_node make_node( Args&&... args ) {
  return {emplace_as<T>{}, std::forward<Args>(args)...};
}
template<class T>
generic_node make_node( T&& t ) {
  return {emplace_as<std::decay_t<T>>{}, std::forward<T>(t) };
}

渲染时打印 hello world 的节点怎么样?

struct printing_node {
  std::string message;
  friend void render( render_target* target, printing_node const& self ) {
    std::cout << self.message;
  }
};

测试代码:

auto list = make_node( list_node{{
  make_node( printing_node{{"hello"}} ),
  make_node( printing_node{{"world"}} )
}});
render_target target;
render(&target, list);

活生生的例子

通用节点是基于不可变共享指针的值类型,在复制时几乎没有工作。

于 2017-06-30T11:35:55.090 回答