Blenderコードリーディング その5 〜Subdivision Surface〜

※この記事ではgitのタグ「v2.75」から生成したブランチ上でコードリーディングしています。

こんにちは。前回まではタグ「v2.74」でコードリーディングしていましたが、今回からは「v2.75」を使います。

さて、今回は初心者から上級者までよく使うモディファイアであろうSubdivision Surfaceについて追いかけていこうと思います。

Subdivision Surfaceモディファイアは私がBlenderを学び始めてから最初に触れたモディファイアです。私はアーティストではないので立派な作品が作れる訳ではないのですが、ゲーム開発の際にテスト用のモデルを作るときやスカルプトの元となるメッシュを作るとき等、個人的には最も使用頻度の高いモディファイアです。「最も良く使うモディファイアは?」と聞かれたときにSubdivision Surfaceを挙げる人は多いのではないでしょうか?

Subdivision Surfaceってなに??

What is Subdivision Surface?
です。(ゲーム開発のテスト用として作ったモデルより)

まずはキーワード検索

それではコードリーディングを始めます。

まずはそれっぽいキーワードで検索して読む場所の狙いをつけていきます。

「Subdivision(Any)Surface」で検索すると
 8 results in 4 files これくらいなら1つずつ見ていけますね。こんなenum値が見つかりました。

typedef enum ModifierType {
    eModifierType_None              = 0,
    eModifierType_Subsurf           = 1,
    eModifierType_Lattice           = 2,
    eModifierType_Curve             = 3,
    eModifierType_Build             = 4,
    eModifierType_Mirror            = 5,
    /* 省略 */
    eModifierType_CorrectiveSmooth  = 51,
    NUM_MODIFIER_TYPES
} ModifierType;

Subsurf は1番ですか。やはり基本的なモディファイアとして据えられているようですね。このenum値の定義がある事は覚えておく事にします。

ただ、「Subdivision(Any)Surface」を検索しても頂点の位置を計算しているような関数がヒットしませんでした。そこで少し視点を変えて、Subdivision SurfaceモディファイアのApplyボタンを押したときの挙動から追いかけていく事にします。

Applyボタンにマウスカーソルを当て、Pythonコードを表示します。 Apply
このPythonコードと同じように「modifier_apply(」で検索すればヒントになるかもしれません。

検索結果は
8 results in 5 files
今回もうまく絞り込めたようです。

見つかった関数は

static DerivedMesh *dynamicPaint_Modifier_apply(DynamicPaintModifierData *pmd, Object *ob, DerivedMesh *dm)  // ダイナミックペイントなので関係なさそう
int ED_object_modifier_apply(ReportList *reports, Scene *scene, Object *ob, ModifierData *md, int mode)
void OBJECT_OT_modifier_apply(wmOperatorType *ot) // ウインドウマネージャーのオペレータータイプが関係してるのでモディファイアの中身に関する処理だとは思えない
static void maskmodifier_apply(struct SequenceModifierData *UNUSED(smd), ImBuf *ibuf, ImBuf *mask) //マスクは今回関係ないと思う

こんな感じです。

というわけで

int ED_object_modifier_apply(ReportList *reports, Scene *scene, Object *ob, ModifierData *md, int mode)

を細かく調べていきましょう。

ステップ実行

ED_object_modifier_apply関数の先頭部分にブレイクポイントを張ってApplyボタンを押します。するとちゃんとこの関数を通ることが確認できます。
ここで引数のModifierDataというのが気になるので定義に飛んでみます。すると先ほど登場したenum値「ModifierType」のすぐ近くに定義されていました。

typedef enum ModifierType {
    eModifierType_None              = 0,
    eModifierType_Subsurf           = 1,
    eModifierType_Lattice           = 2,
    eModifierType_Curve             = 3,
    eModifierType_Build             = 4,
    eModifierType_Mirror            = 5,
    /* 省略 */
    NUM_MODIFIER_TYPES
} ModifierType;

typedef enum ModifierMode {
    eModifierMode_Realtime          = (1 << 0),
    eModifierMode_Render            = (1 << 1),
    eModifierMode_Editmode          = (1 << 2),
    eModifierMode_OnCage            = (1 << 3),
    eModifierMode_Expanded          = (1 << 4),
    eModifierMode_Virtual           = (1 << 5),
    eModifierMode_ApplyOnSpline     = (1 << 6),
    eModifierMode_DisableTemporary  = (1 << 31)
} ModifierMode;

typedef struct ModifierData {
    struct ModifierData *next, *prev;

    int type, mode;
    int stackindex, pad;
    char name[64];  /* MAX_NAME */

    /* XXX for timing info set by caller... solve later? (ton) */
    struct Scene *scene;

    char *error;
} ModifierData;

int type, mode;にはenum値「ModifierType」「ModifierMode」がそれぞれ入りそうですね。「ModifierType」の中でSubdivision Surfaceモディファイアを示す「eModifierType_Subsurf」の値は1です。
ModifierData *mdのtypeをデバッカで確認してみると1が代入されているので、このmdはSubdivision Surfaceモディファイアを示すModifierDataであることが確認できます。

さてここからメッシュを形作るための計算をしている場所を目指していきたいと思います。
ここから先は紛らわしい名前の関数はないのであまり迷いなくステップインしていけます。 ED_object_modifier_apply()
 ー>modifier_apply_obdata()
  ー>mesh_create_derived_for_modifier()
   ー>modwrap_applyModifier()
    ー>applyModifier()
     ー>subsurf_make_derived_from_derived()

struct DerivedMesh *subsurf_make_derived_from_derived(
        struct DerivedMesh *dm,
        struct SubsurfModifierData *smd,
        float (*vertCos)[3],
        SubsurfFlags flags)

という関数までたどり着きました。この関数はDerivedMesh型のポインタを引数として受け取り、同じ型のポインタを返す関数です。おそらく引数の方が変形前のメッシュ、戻り値の方が変形後のメッシュなのだと予想できます。関数の内容は、

struct DerivedMesh *subsurf_make_derived_from_derived(
        struct DerivedMesh *dm,
        struct SubsurfModifierData *smd,
        float (*vertCos)[3],
        SubsurfFlags flags)
{
    /* 省略 */
    CCGDerivedMesh *result;

    /* 省略 */

            result = getCCGDerivedMesh(ss, drawInteriorEdges, useSubsurfUv, dm);

    /* 省略 */

    return (DerivedMesh *)result;
}

となっているので、dmとresultsの頂点数を比較してみると確認できるでしょう。頂点数を確認するには、

            printf("before: %d\n",dm->numVertData);
            result = getCCGDerivedMesh(ss, drawInteriorEdges, useSubsurfUv, dm);
            printf("after:  %d\n",result->dm.numVertData);

とすればよく、結果は
 before: 8
 after: 26
となります。

デフォルトのキューブの頂点数は当然8で正解、変形後のメッシュの頂点数はBlenderの画面の表示 num of verts と一致していますね!

CCGSubSurf

というわけで、いろいろ確認しながら

static CCGDerivedMesh *getCCGDerivedMesh(CCGSubSurf *ss,
                                         int drawInteriorEdges,
                                         int useSubsurfUv,
                                         DerivedMesh *dm)

にたどり着きました。この中でSubdivision Surfaceの計算を行っているようです。

この「CCG」ってなんだろう??引数の型「CCGSubSurf」で検索してみましょう。

すると
http://minormatter.com/zr/software/ccgsubsurf
にたどり着きます。 「Catmull-Clark Gridding Subdivision Surface Library」CCGとはこの事だったのですね。「Catmull-Clark法」というアルゴリズムに基づいたSubdivision Surfaceライブラリだそうです。この記事の執筆時点ではv0.01がダウンロードできるようで、.h/.cファイル1つずつのシンプルなものになっています。Blender2.75にはこのライブラリが組み込まれているようですね。

Subdivision Surfaceの今後

2015年中の開発が期待されるBlenderのプロジェクト18 anticipated Blender development projects of 2015の中に「OpenSubdiv」の導入があります。

「OpenSubdiv」はPixarのオープンソースプロジェクトだそうで、GPUでのSubdivision Surfaceの処理が可能になるようですね。CCGにはGPUで計算しているような箇所は見受けられなかったので今後Subdivision Surfaceモディファイアが高速に処理できるようになる事が期待できそうです。コレは楽しみですし、今後様々なモディファイアやその他の処理にGPGPUが活きてくることが予想されますね!


@fmystB

このブログについて

KLabのゲーム開発・運用で培われた技術や挑戦とそのノウハウを発信します。

おすすめ

合わせて読みたい

このブログについて

KLabのゲーム開発・運用で培われた技術や挑戦とそのノウハウを発信します。