承接上节,本节主要介绍个人开源项目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);