背景
在对Blender源码分析的过程中,开发一个自定义的Modifier是一个比较好的切入点,一方面,一个Modifier相对独立,不会影响太多其他模块;另一方面,Modifier其实对模型相关的很多数据结构都有涉及,也有助于理解Blender的内部机制。
在自定义Modifier开发方面,网上已经有一些文章:
https://blog.exppad.com/article/writing-blender-modifier
https://archive.blender.org/wiki/index.php/Dev:Source/Modifiers/Adding/
苍蓝星:blender——编译与修改188 赞同 · 32 评论文章
不过这些文章都有些陈旧了,经过几个版本,很多字段都有变化,完全没办法直接套用。
本次Modifier简介
所以我就针对原有的Mirror Modifier,代码复制过来后进行一定简化,来探索自定义Modifier的整个流程,命名为 GxMove。
一个典型的Modifier一般需要修改很多文件,以GxMove Modifier为例,
文件夹结构:
blender/source/blender
makesdna
DNA_modifier_types.h :eModifierType_GxMove, GxMoveModifierData 结构的定义
DNA_modifier_defaults.h :_DNA_DEFAULT_GxMoveModifierData 各个属性的默认数值
intern
dna_defaults.c :针对GxMoveModifierData调用两个宏
makesrna
RNA_access.h :RNA_GxMoveModifier 的声明
intern
rna_modifier.c :配置图标;定义RNA的属性和底层数据的关联
blenkernel
BKE_mesh_gxmove.h :功能函数 BKE_mesh_gxmove_apply_gxmove_on_axis的声明
intern
mesh_gxmove.c :功能函数的实现
modifiers
MOD_modifiertypes.h :modifierType_GxMove 声明
intern
MOD_gxmove.c :modifierType_GxMove 类型的具体实现
MOD_util.c :初始化 GxMove,实际初始化 modifierType_GxMove
生成代码(很多代码不是开发者直接写的,是通过代码生成的,有机会细讲):
build_windows_Full_x64_vc16_Release/source/blender
blenkernel
只是 bf_blenkernel.vcxproj.filters 引入 BKE_mesh_gxmove.h, mesh_gxmove.c
makesdna/intern
dna_type_offsets.h: _SDNA_TYPE_GxMoveModifierData 常量定义
dna_verify.c: 一些Assert校验
makesrna/intern
rna_gpencil_modifier_gen.c: 引用了一下 RNA_GxMoveModifier,不知道什么用
rna_modifier_gen.c: rna_GxMoveModifier_move_x 这种属性的展开(重点!!)
rna_object_gen.c: GxMove 的定义
rna_prototypes_gen.h: RNA_GxMoveModifier 声明
modifiers
bf_modifiers.vcxproj.filters 引入 MOD_gxmove.c
架构图:
DNA:规定基础的数据结构
RNA:基于DNA的底层数据进行一层封装,方便参数在UI上展示和操作
Kernel:这里只是模仿Mirror Modifier,把一些底层的操作封装了一下,放到了Kernel模块里面,并不是必须这样的,功能在Modifiers模块里直接写也OK
Modifiers:实际的Modifier实现
代码详细讲解
相关调整代码:
makesdna 模块
1.1 makesdna / DNA_modifier_types.h
typedef enum ModifierType {
...
eModifierType_GxMove = 61, // 数字不能重复
NUM_MODIFIER_TYPES,
} ModifierType;
...
typedef struct GxMoveModifierData {
ModifierData modifier;
/** Deprecated, use flag instead. */
short axis DNA_DEPRECATED;
short flag;
float tolerance;
float uv_offset[2];
float uv_offset_copy[2];
struct Object *mirror_ob;
float move[3]; // 我新加的参数
char _pad[4]; // 8 byte补齐
} GxMoveModifierData;
move参数之前的,都是从Mirror的复制过来的,因为加了一个 float move[3],float 的长度是 4byte,但是Blender要求必须8byte对其,否则都无法编译,所以最后补齐上4byte
1.2 makesdna / DNA_modifier_defaults.h
#define _DNA_DEFAULT_GxMoveModifierData \
{ \
.flag = MOD_MIR_AXIS_X | MOD_MIR_VGROUP, \
.tolerance = 0.001f, \
.uv_offset = {0.0f, 0.0f}, \
.uv_offset_copy = {0.0f, 0.0f}, \
.mirror_ob = NULL, \
.move = {5.0f, 0.0f, 0.0f}, \
}
这里的数值,就是UI中Modifier创建后的初始数值
1.3 makesdna / intern / dna_defaults.c
SDNA_DEFAULT_DECL_STRUCT(GxMoveModifierData);
// 展开为
// static const GxMoveModifierData DNA_DEFAULT_GxMoveModifierData =
// _DNA_DEFAULT_GxMoveModifierData
...
const void *DNA_default_table[SDNA_TYPE_MAX] = {
...
SDNA_DEFAULT_DECL(GxMoveModifierData),
// 展开为
// [_SDNA_TYPE_GxMoveModifierData] = (&(DNA_DEFAULT_GxMoveModifierData))
};
(Blender里面的宏非常、非常多,有可能会跳好几个工程,这也是分析代码中比较麻烦的一点)
**********
2. makesrna模块
2.1 makesrna / RNA_access.h
extern StructRNA RNA_GxMoveModifier;
2.2 makesrna / intern / rna_modifier.c
// 注意,这里的位置影响了在UI中的位置,它后面就是Deform的定义,下一列了
{eModifierType_GxMove,
"GX_MOVE",
ICON_MOD_MIRROR,
"GxMove",
"GxMove along the local X, Y and/or Z axes, over the object origin"},
{0, "", 0, N_("Deform"), ""}, // 这里就是 Deform 那一列了
static void rna_def_modifier_gxmove(BlenderRNA *brna)
{
StructRNA *srna;
PropertyRNA *prop;
srna = RNA_def_struct(brna, "GxMoveModifier", "Modifier");
RNA_def_struct_ui_text(srna, "GxMove Modifier", "GxMove modifier");
RNA_def_struct_sdna(srna, "GxMoveModifierData");
RNA_def_struct_ui_icon(srna, ICON_MOD_MIRROR);
RNA_define_lib_overridable(true);
prop = RNA_def_property(srna, "move_x", PROP_FLOAT, PROP_FACTOR);
RNA_def_property_float_sdna(prop, NULL, "move[0]");
RNA_def_property_range(prop, -10000.0f, 10000.0f);
RNA_def_property_ui_range(prop, -1, 1, 2, 4);
RNA_def_property_ui_text(prop, "X Move", "Move on the X axis");
RNA_def_property_update(prop, 0, "rna_Modifier_update");
prop = RNA_def_property(srna, "move_y", PROP_FLOAT, PROP_FACTOR);
RNA_def_property_float_sdna(prop, NULL, "move[1]");
...
}
void RNA_def_modifier(BlenderRNA *brna) {
...
rna_def_modifier_gxmove(brna);
}
注意上面 move_x --> move[0], move_y --> move[1] 暴露出 move_x, move_y 新属性,供UI来绑定,也就是RNA的精髓。上面的注释里也写了,这里的定义会影响Modifier在UI中展示的位置的,要注意。
*************
3. kernel模块
3.1 blenkernel / BKE_mesh_gxmove.h
struct Mesh *BKE_mesh_gxmove_apply_gxmove_on_axis(
struct GxMoveModifierData *mmd,
const struct ModifierEvalContext *UNUSED(ctx),
struct Object *ob,
const struct Mesh *mesh,
int axis);
3.2 blenkernel / intern / mesh_gxmove.c
Mesh *BKE_mesh_gxmove_apply_gxmove_on_axis(
GxMoveModifierData *mmd,
const ModifierEvalContext *UNUSED(ctx),
Object *ob,
const Mesh *mesh,
int axis)
{
Mesh *result;
float mtx[4][4]; // 用来变换的矩阵
const int maxVerts = mesh->totvert; // 8 (对于默认立方体)
const int maxEdges = mesh->totedge; // 12
const int maxLoops = mesh->totloop; // 24
const int maxPolys = mesh->totpoly; // 6
// 因为是参考Mirror复制了原有Mesh,所以 x2
result = BKE_mesh_new_nomain_from_template(
mesh, maxVerts * 2, maxEdges * 2, 0, maxLoops * 2, maxPolys * 2);
/* Copy custom-data to original geometry. */
CustomData_copy_data(&mesh->vdata, &result->vdata, 0, 0, maxVerts);
CustomData_copy_data(&mesh->edata, &result->edata, 0, 0, maxEdges);
CustomData_copy_data(&mesh->ldata, &result->ldata, 0, 0, maxLoops);
CustomData_copy_data(&mesh->pdata, &result->pdata, 0, 0, maxPolys);
mv_prev = result->mvert;
mv = mv_prev + maxVerts;
// 便利第二个Mesh数据的所有顶点,按照矩阵进行变换
for (i = 0; i < maxVerts; i++, mv++, mv_prev++) {
mul_m4_v3(mtx, mv->co);
}
}
*****************
4. modifiers模块
4.1 modifiers / MOD_modifiertypes.h
extern ModifierTypeInfo modifierType_GxMove;
4.2 modifiers / intern / MOD_gxmove.c
// 主UI设置
static void panel_draw(const bContext *UNUSED(C), Panel *panel)
{
...
// 这里将RNA中的 move_x, move_y这种属性渲染为Slider控件
uiItemR(col, ptr, "move_x", UI_ITEM_R_SLIDER,IFACE_("Move X"),ICON_NONE);
uiItemR(col, ptr, "move_y", UI_ITEM_R_SLIDER,IFACE_("Y"),ICON_NONE);
}
ModifierTypeInfo modifierType_GxMove = {
/* name */ "GxMove",
/* structName */ "GxMoveModifierData",
/* structSize */ sizeof(GxMoveModifierData),
/* srna */ &RNA_GxMoveModifier,
/* type */ eModifierTypeType_Constructive,
/* flags */ eModifierTypeFlag_AcceptsMesh | ...
/* icon */ ICON_MOD_MIRROR,
/* copyData */ BKE_modifier_copydata_generic,
/* deformVerts */ NULL, // Deform类型的Modifier要实现这个
/* deformMatrices */ NULL,
/* deformVertsEM */ NULL, // Deform类型的Modifier要实现这个
/* deformMatricesEM */ NULL,
/* modifyMesh */ modifyMesh, // Generate类型的Modifier要实现这个
/* modifyHair */ NULL,
/* modifyPointCloud */ NULL,
/* modifyVolume */ NULL,
/* initData */ initData,
/* requiredDataMask */ NULL,
/* freeData */ NULL,
/* isDisabled */ NULL,
/* updateDepsgraph */ updateDepsgraph,
/* dependsOnTime */ NULL,
/* dependsOnNormals */ NULL,
/* foreachIDLink */ foreachIDLink,
/* foreachTexLink */ NULL,
/* freeRuntimeData */ NULL,
/* panelRegister */ panelRegister, // 对面板的UI进行设置
/* blendWrite */ NULL,
/* blendRead */ NULL,
};
4.3 modifiers / intern / MOD_util.c
void modifier_type_init(ModifierTypeInfo *types[])
{
...
INIT_TYPE(GxMove);
}
**********
我们来看一看上面提到的一些 Generated Code
build_windows_Full_x64_vc16_Release/source/blender/makesrna/intern/ rna_modifier_gen.c
FloatPropertyRNA rna_GxMoveModifier_move_x;
float GxMoveModifier_move_x_get(PointerRNA *ptr){}
void GxMoveModifier_move_x_set(PointerRNA *ptr, float value){}
FloatPropertyRNA rna_GxMoveModifier_move_x = {...}
StructRNA RNA_GxMoveModifier = {...}
可以看到,我们在源码中对GxMove的RNA相关属性设置,完全展开了,每个都有对应的 getter, setter
最终结果:
对一个物体加入GxMove这个Modifier后,可以通过move属性来移动物体。虽然没什么用,但好歹把整个流程跑通了。
结论
从上面的代码 1.1 到 4.3 ,我们可以看到,我仅仅是实现了一个最简单的功能而已,就需要调整10个文件。当然其中一个 mesh_gxmove.c 是为了模仿Mirror的代码结构,将代码提出来了,如果全部写在 MOD_gxmove.c 应该也是可以的,就算能省一个文件吧。
如此简单的功能就要调整10个文件,完全不符合“对扩展开放、对修改关闭”的理念。关键是其中很多文件还只是简单的声明了一个十分弱智的数据结构。大哥,你不是能生成代码么,这会儿怎么不生成了。
真的佩服维护Blender的这些大神,怎么撑到今天的,头发还在么。
!!!撒花,完!!!