Administrator
发布于 2024-05-27 / 101 阅读
0

Blender自定义Modifier开发

背景

在对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实现

代码详细讲解

相关调整代码:

  1. 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的这些大神,怎么撑到今天的,头发还在么。

!!!撒花,完!!!