R2grap项目介绍(3):path渲染篇

介绍R2grap的动画渲染设计

承接上节,本节主要介绍个人开源项目R2grap中计算最终渲染Path的流程与数据结构,以及调用相应的图形api实现。

前言

我们在codec篇已实现AE动画信息的建模,将不同的shape图层信息存储在LayerInfo的数组内,以及将各个layer与group之间的绑定关系存储在AniInfoManger中,但是对于图形api来说,最终需要构建的每个图元是基于每个Path而言的,因此我们还需要做一层中间处理,将layerInfo中的渲染信息拆解开来(主要为绑定后的transform信息),以path为单位传递给渲染管线。

拆解RenderContent

首先我们需要对LayerInfo中的数据进行解绑定,即对绑定的子layer的path与transform信息进行更新,我们将拆解后的数据定义为三类:VerticesData(顶点信息),ColorData(颜色与纹理信息),TransformData(动画信息);

RenderContent::RenderContent(LayersInfo* layer_info){
  layer_contents_path_ = SRenderDataFactory::GetIns().CreateVerticesData(layer_info);
  layer_contents_color_ = SRenderDataFactory::GetIns().CreateColorData(layer_info);
  layer_contents_trans_ = SRenderDataFactory::GetIns().CreateTransformData(layer_info); //shape transform->generate layer's transform curve

  layer_data_.index = layer_info->GetLayerInd();
  layer_data_.start_pos = layer_info->GetLayerInpos();
  layer_data_.end_pos = layer_info->GetLayerOutpos();
  layer_transform_ = layer_info->GetShapeTransform();
  shape_groups_ = layer_info->GetShapeGroup();

	bool no_group_keyframe = true;
  for(auto& group : shape_groups_){
    unsigned int group_index = std::find(shape_groups_.begin(), shape_groups_.end(), group) - shape_groups_.begin();
    std::vector<unsigned int> indexs = { group_index };
    GroupData group_data;
    RecusCalcRenderData(group, indexs, group_data, no_group_keyframe);//迭代解绑group
    layer_data_.group_data.push_back(group_data);
  }
	layer_data_.groups_no_keyframe = no_group_keyframe;
}

从上面可以看到拆解layerInfo的过程中也需要对Group信息进行解绑,由于Group存在多层嵌套的情况,所以我们这里使用迭代的形式去完成group_data的解析;

void RenderContent::RecusCalcRenderData(const std::shared_ptr<ShapeGroup> group, std::vector<unsigned int> indexs, GroupData& group_data, bool& no_keyframe) {
  if (group->HasChildGroups()) {
    no_keyframe &= group->GetTransform()->IsNoKeyframe();

    auto child_groups = group->GetChildGroups();
    for (auto i = 0; i < child_groups.size(); i++) {
      auto cur_inds = indexs;
      cur_inds.push_back(i);
      GroupData child_group_data;
      RecusCalcRenderData(child_groups[i], cur_inds, child_group_data, no_keyframe);
      group_data.child_groups.push_back(child_group_data);
    }
  }
  else {
    no_keyframe &= group->GetTransform()->IsNoKeyframe();

    auto color_infos = layer_contents_color_->GetColor(indexs);
    LoadColorContent(color_infos, group_data);

    auto path_num = group->GetContents()->GetPathsNum();
    for (auto path_ind = 0; path_ind < path_num; path_ind++) {
      BezierVertData vert_data;
      layer_contents_path_->GetBezierVertData( indexs, path_ind, vert_data);
      LoadPathContent(vert_data, group_data);
    }
  }
}

从上面可以看到,我们并没有对每个子group的做最终的解绑定操作,这是因为每个group也同样受其所属的layer影响,因此对于group的transform我们在处理完全部的layer再回过头处理。

定义static函数以更新全部RenderContent的Transform信息,函数声明如下:

static void UpdateTransRenderData(const std::vector<std::shared_ptr<RenderContent>>& contents, std::vector<RePathObj>& objs);

可以看到在此函数实现中不仅将RenderContent中的TransformRenderData进行了更新,同时将其以Path为单位进行了拆解,以用于后续的渲染流程中;

其中,RePathObj的定义如下所示,每一组信息都是以单次所绘制的图元为单位的:

struct RePathObj{
		float in_pos;//开始时间
		float out_pos;//结束时间

		PathData* path = nullptr;//贝塞尔曲线
		FillData* fill = nullptr;//颜色填充
		StrokeData* stroke = nullptr;//先挑颜色

		bool keep_trans = true;//是否沿用之前的transform
		std::vector<glm::mat4> trans;//以帧为单位,存储Transform矩阵

		RePathObj(float in, float out, FillData* fill, StrokeData* stroke):
			in_pos(in), out_pos(out), fill(fill),stroke(stroke){}
};

由于同一父group下子group的transform矩阵可能相同,为了节省RePathObj所占据的内存大小,定义了一个bool型参数用以标记是否沿用之前的Transform矩阵;

至此我们已成功封装出了用于后续渲染的PathObj数组,这部分的整体流程图如下所示: 解析流程图

Path渲染

我们已经实现了R2grap的动画信息拆解与转译工作,下一步就是调用相应的图形API完成,在R2grap中支持OpenGl、DirectX、Metal三种图形API,根据所使用的平台不同,调用相应的图形api,三种图形api的流程实现依次封装在 /src/opengl/r2grapgl.cpp/src/directx/r2grapdx.cpp/src/metal/r2grapmt.cpp中。

下面主要讲解OpenGl的渲染流程: 首先根据PathObj数组建立VBO、VAO与EBO数组;

  auto paths_count = static_cast<int>(objs_.size());
  VBOs = new unsigned int[paths_count];
  VAOs = new unsigned int[paths_count];
  EBOs = new unsigned int[paths_count];
  glGenBuffers(paths_count, VBOs);
  glGenBuffers(paths_count, EBOs);
  glGenVertexArrays(paths_count, VAOs);

进而完成VBO与VAO的绑定,并向VBO中装填定点信息:

glBindVertexArray(VAOs[ind]);
glBindBuffer(GL_ARRAY_BUFFER, VBOs[ind]);
glBufferData(GL_ARRAY_BUFFER, sizeof(float) * vert_array.size(), out_vert, GL_STATIC_DRAW);

在创建EBO时流程类似,但需要判断当前图元是否为闭合图元,我们之前已将此信息存储在PathObj中的PathData中。

为了让所绘制的图元实现“动”的效果,我们在glsl中定义了如下uniform变量,并依次与顶点坐标做如下运算:

uniform mat4 transform;//变换坐标
uniform mat4 model;//模型坐标
uniform mat4 view;//视角坐标
uniform mat4 projection;//投影坐标

//...
vec4 vert = projection * view * model * transform * vec4(aPos, 1.0); 

See also

comments powered by Disqus