KLabGames Tech Blog

KLabは、多くのスマートフォン向けゲームを開発・提供しています。 スマートフォン向けゲームの開発と運用は、Webソーシャルゲームと比べて格段に複雑で、またコンシューマゲーム開発とも異なったノウハウが求められる領域もあります。 このブログでは、KLabのゲーム開発・運用の中で培われた様々な技術や挑戦とそのノウハウについて、広く紹介していきます。

カテゴリ: Blender

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

Blender標準のレンダラよりもリアルな絵を作る事の出来る「Cycles」というレンダラ。アーティストの方々はクオリティ・リアリティの高い画像や動画などを作る際に利用されている事でしょう。数あるBlenderの機能の中でも注目度が高いのではないでしょうか?

プログラマである私のBlenderの使い道と言えばメッシュを作ったりボーンを仕込んだりしてゲーム開発の際のテスト用モデルを作る事であり、Cyclesのお世話になる事はなかったのですが、コードリーディングをしてみようと思い立ち1から勉強してみることにしました(なんだか目的と手段が逆な気もしますが ^_^; )。

Cyclesはとても奥深い機能であるし、現在も開発が進んでいます。ですのでこのCyclesの記事は1回では終わりませんし、part何までいくか今のところ分かりません。もしかしたら途中で別の機能についての記事を挟んでまた再開するようなこともあるかもしれません。とにかく頑張ってコードリーディングしていこうと思いますのでお付き合いいただければ幸いです。

”Cyclesとは” と書こうとしたんです!

まずはじめに、「Cyclesとは〜〜だ!」と一言で説明されたものを頭に入れておけば後々理解の助けになりそうです。そこでBlender Reference ManualのCyclesのイントロダクション から引用します。

Cycles is a ray tracing renderer focused on interactivity and ease of use, while still supporting many production features.

要するに肝心なところは「Cyclesとはレイトレーシングレンダラだ!」というところですね!
しかしこの「レイトレーシング」、ちょっと調べてみると指しているものが広すぎてもう少し整理しないことには理解の助けにならない事が分かったのです。

ここから先は上記の「レイトレーシング」が何を指すのかもう少しハッキリさせるべく、歴史的なところから調べた事を書いていますが、それぞれのキーワードについて厳密な定義が存在するのかはわかりません。故に私よりも深く勉強されている方からは指摘されるような部分があるかもしれませんが、このように解釈すればCyclesの理解に役立つだろうという部分に主眼を置いて自分なりに整理してみました。

レイトレーシングの歴史

「レイトレーシング」(便宜上、古典的レイトレーシングと呼ぶ事にします。)が考案されたのは1980年代の初頭だったそうです。古典的レイトレーシングは光源から放出される光が物体に直接及ぼす影響しか考慮されていなかったようです。

上記の英文がさす"ray tracing"とは「古典的レイトレーシング」のことなのでしょうか?

実際にCyclesを使ってみるとそうではなさそうだという事が分かります。
Cycles Rendering
Torus Material
Floor Material
床のオブジェクトにトーラスの色が少し映り込んでいるのが分かります。トーラスと床のマテリアル設定はノードの示す通りで光源として設定されている訳ではなく、Diffuseとなっています。つまりCyclesは光源からの直接の影響だけではなく、他の物体が反射した間接的な光も影響するように出来ているようです。このような現象は「間接照明」と呼ばれ、間接照明まで考慮して物体表面の光の挙動をモデル化したものを「グローバルイルミネーション」と呼ぶそうです。

「古典的レイトレーシング」は「間接照明」を考慮していないので「グローバルイルミネーション」には含まれませんが、1984年頃に「古典的レイトレーシング」を発展させる形で「間接照明」まで考慮されたレイトレーシングが考案されたようです。それが「分散レイトレーシング」と呼ばれるアルゴリズムです。「分散レイトレーシング」では1本のレイが物体に入射すると複数のレイが反射されるようです。

しかしこの「分散レイトレーシング」にはレイの数が膨大になる事やその他の欠点があるようで、これらの問題を解消する形で「パストレーシング」というアルゴリズムが1986年頃に考案されました。

その後、1990年代になると視点からのレイも光源からのレイも両方追跡する「双方向パストレーシング」というアルゴリズムが考案されます。

今後書く記事の中で詳しく触れようと思いますが、Cyclesのレンダリング部分のコードを追いかけてみると、kernel_path_traceという名前の関数にたどり着きます。この関数の名前が「パストレース」「双方向パストレース」のどちらを指すのかは今のところわかりませんので後々探っていく事となりますが、ここまでで「レイトレーシングという言葉の指すものが広すぎてもう少し詳しく見ないとCyclesを理解する上で助けにならない」といった意味がおわかりいただけたかと思います。

長い説明になってしまったので、ここまでの私の見解を大まかに図に表しておきます。
RaytraceHistory

レイトレーシングとラスタライジングの違い

さて、ゲームのように秒間に何十フレームもレンダリングする必要のある場合、その技術はリアルタイムレンダリングというカテゴリに分類されて、多くはラスタライジングという手法が採用されます。ラスタライジングはプリミティブ単位でレンダリングする手法です。

一方、何秒・何分・モノによっては何時間もかけて1フレームをレンダリングする事を繰り返し、予め画像や動画などを仕上げておく技術をプリレンダリングと呼びます。レイトレーシングがリアルタイムレンダリングで採用される例は現時点では少なく、多くはプリレンダリングにおいて使われます。

レイトレーシングはラスタライジングと違い、ピクセル単位でレンダリングされます。その事をCyclesのコード上で確認してみましょう。

GPUを使う設定をしていないのであれば、Cyclesでレンダリングをする際に
Sources/cycles_device/Source Files/device_cpu.cppの

for(int y = tile.y; y < tile.y + tile.h; y++) {
    for(int x = tile.x; x < tile.x + tile.w; x++) {
        path_trace_kernel(&kg, render_buffer, rng_state,
                                     sample, x, y, tile.offset, tile.stride);
    }
}

という部分を通ります。

どうやらタイル状に区切ってピクセル単位でレンダリングしているような雰囲気がありますね。レンダリング結果はこうなります。
Default

tile.hとtile.wを2で割ってみたらどのようにレンダリングされているのかわかりやすい絵が出来そうですね。レンダリング結果はこうです。
After
ご覧の通りタイル状に区切られてピクセル単位でレンダリングされています。プリミティブ単位でレンダリングされるラスタライジングとは違うのがわかります。

参考書

レイトレーシングの歴史を調べるにあたり、こちらの本を参考にさせていただきました。

CG Magic:レンダリング
倉地 紀子
オーム社
2007-11

とても親切に書かれている本ですが、学術的な内容であるだけに私にとって容易なものではありませんでした。もし何か間違った解釈をしたままこの記事の中に書いてしまっている箇所があれば、その責任は私にあります。

@fmystB

※この記事では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

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

前回は流体シミュレーションの実験のためのプロジェクトを作成しました。
( プロジェクトファイルのダウンロードはこちら
今回はこれをもとに流体シミュレーションのコードリーディングをしていきます。

流体シミュレーションの計算が行われるのはDomainタイプのFluidオブジェクトのBakeボタンを押したときです。このときに何が起きるのか探っていきましょう。

流体計算の関数を探す

まずはBakeボタンを押したときに呼ばれる関数を探したいと思います。

どうやって探すか?UIパーツにマウスカーソルを合わせると、そのUIに関するPythonの命令を表示してくれます。Bakeボタンにマウスカーソルを合わせてみると、
bake_python
bpy.ops.fluid.bake() というPythonの命令が出てきます。

ここで余談になるのですが、Blenderはコマンドラインからバックグラウンドで立ち上げる事も出来ます。Mac Proなどハイスペックなマシンをサーバーとして用意しておき、流体シミュレーションのBakeなど長く時間のかかるものはサーバーで起動したBlender上でPythonコマンドを使って自動で処理させておくような事も可能です。

さて、話を元に戻してbpy.ops.fluid.bake()を起点にC<ー>Pythonのつなぎの部分のコードを追いかけていくと確実にたどり着けそうですが・・・まあ、もっとシンプルに「fluid」や「bake」で検索すればおそらくたどり着けるでしょう。

Xcodeのパターン検索で、「fluid(Any)bake」で検索すると、
30 results in 4 files
案の定、physics_fluid.cといういかにも関係ありそうな名前のファイルが引っかかります。 このphysics_fluid.cのなかで「fluid(Any)bake」は27カ所。その中でも関数の定義だけに絞り込んで関係してそうな名前のものだけピックアップしてみると

static int fluidsimBake(bContext *C, ReportList *reports, Object *fsDomain, short do_job)

static int fluid_bake_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))

static int fluid_bake_exec(bContext *C, wmOperator *op)

となります。

fluid_bake_invoke()もfluid_bake_exec()も両方中でfluidsimBake()を呼んでいるので、fluidsimBake()の先頭部分にブレイクポイントを張ってBakeボタンを押してみる事にします。すると狙い通りブレイクポイントに引っかかりました!

fluidsimBake()の中身を追う

fluidsimBake()の中身は232行に渡っています。その中の多くはパラメータをセットする命令になっているようです。最後の方にジョブをスタートさせたりコールバックの設定をしたりするような部分があります。おそらく別スレッドで非同期に動かすためのジョブのパラメータをセットし、最後にジョブをスタートさせる事で流体シミュレーションを実行させているのではないでしょうか?Bakeが終わるのを待っている間他の操作が同時に出来るので、そう考えると自然な気がします。

ジョブを追う

このような予測のもと、ジョブを中心に追いかけていこうと思います。実際の流体計算にたどり着き、それが別スレッドで実行されていれば予測が的中しているものと見てよさそうです。

fluidsimBake()の最後の方で呼ばれているfluidbake_startjob()がジョブをスタートさせる関数で、これに渡されているジョブがfbと言う名前の付いたFluidBakeJob構造体のポインタです。

typedef struct FluidBakeJob {
    /* from wmJob */
    void *owner;
    short *stop, *do_update;
    float *progress;
    int current_frame;
    elbeemSimulationSettings *settings;
} FluidBakeJob;

最後のelbeemSimulationSettings *settings;にいろいろ詰まってそうです。

fluidbake_startjob()を呼ぶ直前で、

fb->settings = fsset;

というように、fssetというポインタが渡されています。

このelbeemSimulationSettings構造体の中身に

elbeemRunSimulationCallback runsimCallback;

という関数ポインタ(別の場所でelbeemRunSimulationCallbackはtypedefされている)が定義されています。コールバックなのでもし流体計算が別スレッドで処理されていればそのスレッドから呼ばれるので、ブレイクポイントで止めれば計算箇所を突き止められるかもしれません。

fsset->runsimCallback = &runSimulationCallback;

のように、physics_fluid.cの中の

static int runSimulationCallback(void *data, int status, int frame)

がセットされています。この関数の先頭にブレイクポイントを仕掛けてみましょう。すると予想通り別スレッドから呼ばれている事がわかりました。ブレイクポイントに引っかかっているので、どの関数からコールバックが呼ばれているのかもわかります。

「elbeem」ってなんだ??

どうもさっきからelbeemSimulationSettingsやelbeemRunSimulationCallbackの「elbeem」という名前が目につきます。そしてブレイクポイントを張って調べたrunSimulationCallback()もbf_intern_elbeemフォルダの中のソースから呼ばれています。

この「elbeem」って何なんでしょう?Googleで調べてみると
http://elbeem.sourceforge.net
にたどり着きます。

「El'Beem Is an Open-Source free surface fluid simulation library.」Blenderは流体シミュレーションのためにこのライブラリを採用していたのですね!格子ボルツマン法に基づいたライブラリだそうです。これだけで参考書や論文が何本もありそうですね(^^; こ、ここを追いかけるのは ま、またの機会に・・・|)彡サッ


@fmystB

ターミネーター

2015年7月10日に映画ターミネーターの最新作「ターミネーター: 新起動/ジェニシス」が公開になりましたね!

この最新作でも「ターミネーター2」で初登場してシュワちゃん達を苦しめたアイツが登場するようです。

そう、液体金属ロボット「T-1000」です!

液体金属なのでどんな形にも自在に変形可能。手を武器に変形させて襲いかかります。小さな隙間から侵入したり、別の人物に成り代わって待ち伏せする事も可能です。銃で撃たれても自己再生するのでとても厄介!

Youtubeで検索すれば元祖の方も含めてT-1000の映像はたくさん出てきますよ。

ターミネーター2が公開されたのはずいぶん前の話ですが、あの時代にもこんなにすごい映像を作る技術が存在したのですね。

私はこのT-1000が大好きで、技術的目標の一つなのです。それもあって今では3D関連のプログラミングを仕事にしています。見る人を「あっ!!」と言わせるようなものを作りたい!

私以外にも多くのファンを惹き付けたT-1000ですが、この映像を作るためにシェーディングや流体シミュレーションなど色んな技術が使われている事でしょう。今は映画スタッフのような専門の人でなくても普通の人が無料でこのような技術に触れる事が出来るんですよ!そう、Blenderの出番です。

特に今回はT-1000の動きに関係の深そうな流体シミュレーションの技術に触れてみましょう。と、いうわけでBlenderの流体シミュレーション周りのコードを探っていきたいと思います。part1では下準備として、簡単な流体シミュレーションのプロジェクトをBlenderで作っていきます。

とってもお手軽な流体シミュレーションのやり方

( 作り方は気にせず完成したプロジェクトだけ欲しい場合は下にファイルへのリンクがあります。 )

1、オブジェクトの中にオブジェクトを作る都合上、ひとまずWireflame表示に切り替えます。
wireflame

2、デフォルトで配置されているキューブを適当に大きくします。
default
big

3、上記のキューブの内部に新しくキューブを追加して、大きいキューブ内部の上の方に移動します
small

4、さらに障害物となるキューブを追加し、大きさを調整して移動します。
obstacle_perspective
obstacle_frustum

5、ここから物理オブジェクトとしての設定をしていきます。まず小さいキューブを選択してプロパティエディタのPhysicsタブからFluidボタンを押します。
fluid
Typeは「Fluid」を指定します。
type_fluid

6、次に障害物用のオブジェクトを選択して同じようにFluidボタンを押します。Typeは「Obstacle」を指定します。
type_obstacle

7、最後に大きいキューブを選択してFluidボタンを押し、Typeは「Domain」を指定します。
type_domain

8、DomainタイプにはBakeボタンがあるので押します。
bake_button
これで物理計算が走り、進捗は画面上に表示されるプログレスバーで確認できます。時間のかかる処理なので途中でキャンセルしたい場合はxボタンを押しましょう。
baking

9、物理計算が終了した後、アニメーションを再生するとドメイン内で障害物に邪魔されながら水の落ちる様子が確認できます。
playing

今回のプロジェクトファイルのダウンロードはこちら(右クリックでリンク先を名前を付けて保存してください)
ちなみに、上記bakeボタンを押して物理計算が終了すると、このfluidsym.blendと同じフォルダにcache_fluidというフォルダが出来て中に計算結果のキャッシュデータがたまるのですが、このデータはファイルサイズが大きいためここには置かないので上記ファイルダウンロード後にbakeボタンを押してご自身で作成してください。これが無いとアニメーションを再生させても水が落ちません。

プリレンダリングという技術

Bakeには少々時間がかかると思います。MacBook Pro Mid 2014, 2.6GHz intel Core i5で実験しましたが数分待たされました。このようにレンダリングに必要なデータを事前計算しておく事はプリレンダリングという技術に分類されます。ゲームのムービーシーンや映画で使われる技術で、ターミネーターシリーズでも様々なシーンで使われている事でしょう。ゲームのプレイ中にユーザーの操作を動的に受け付けながらレンダリングするリアルタイムレンダリングとはまた違った技術です。Blenderには多くのプリレンダリングの技術が含まれているのでプリレンダリングを研究したい場合は他に無いくらい最適な材料になるかと思います。

今回作ったプロジェクトを使って、part2ではブレイクポイントを張りながらコードを追いかけていきます。それではまた次回。
I'll be back.


@fmystB

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

頂点が頻繁に更新されるのはEditモードだよね

前回の記事の続きです。

前回はObjectモード時のレンダリングはGPUで行われている事を突き止めましたが、今回は頂点データが頻繁に更新されてメインメモリ<ー>GPUメモリ間のデータ転送コストが毎回発生するであろうEditモードのレンダリングについて調べてみます。Editモードのレンダリングに使われているのはCPUでしょうか?GPUでしょうか?

OB_MODE_EDITで探るもレンダリング命令まで行き着かず

Objectモードのときはdrawobject.cの中でObjectモードを示すenum値:OB_MODE_OBJECTの付近を探っていたら運良くレンダリング命令の部分を引き当てる事に成功しましたが、今回はEditモードを示すOB_MODE_EDITの付近を探ってみるも、レンダリング命令に行き着きませんでした。

しかしdrawobject.cの中に目的の部分がある可能性は捨てず、別の方法で探す事にしました。いろいろ検索条件を変えて絞り込んでいきましょう。

Objectモードのときと同じようにdrawFacesSolidが答えか?

ObjectモードのときはdrawFacesSolidという関数ポインタがドローコールにつながっていたので、Editモードのときも同じようにViewport ShadingがSolidの場合は関数ポインタdrawFacesSolidがドローコールにつながっているかもしれません。

drawobject.cの中で関数ポインタdrawFacesSolidがコールされている場所は8カ所。試しに8カ所全てコメントアウトしてみるも、Editモードの時のモデルの面が消える事はありませんでした。

drawFaces〜〜〜かな??

ついでに「drawFaces」でdrawobject.c内を検索してみましょう。

ヒットは11件で8件が先ほどのdrawFacesSolid。1件はenum値なのでこれは関係なし。残る2件はdrawFacesGLSL(GLSLはOpenGLのシェーダ言語)という関数ポインタで、EditモードはデフォルトでSolidシェーディングなのでGLSLが使われる事もなさそうだから違うだろうなと思いつつ2件ともコメントアウトしてみるもやはり該当しませんでした。

試行錯誤

「draw」ならどうか?ヒットは1311件。これは調べきれません。

Objectモードのときと同じように構造体内の関数ポインタになっていると予想できるので、「->draw」で検索してみると135件。まだ手探りで該当箇所を探るのはキツい件数ですね。

Xcodeはパターン検索が出来るので「->draw〜〜〜(」という形で検索して絞り込む。すると55件。もう少し絞りたい!この55件の〜〜〜の中には「Verts」(頂点)や「Edges」(辺)という文字列が含まれています。でも狙いは「Faces」(面)なので「->draw〜〜〜Faces」で検索。すると12件まで絞れました。これくらいの数であれば1つ1つ手で調べていけますね!この12件の中に答えがあるか!?

容疑者:drawMappedFaces

12件のうち1件は関数でも関数ポインタでもないので除外。そして10件は関数ポインタdrawMappedFaces、1件は関数ポインタdrawMappedFacesGLSLです。Objectモードのときもdraw〜〜〜という関数ポインタがレンダリング命令につながっていたので、この11件の中のどれかがEditモードの面のレンダリングにつながっているかもしれません。

どの関数がdrawMappedFacesまたはdrawMappedFacesGLSLを呼び出しているかを列挙してみると、

  • draw_dm_faces_sel() 1件
  • draw_em_fancy() 3件
  • draw_mesh_fancy() 1件
  • bbs_mesh_solid_EM() 2件
  • bbs_mesh_solid_verts() 1件
  • bbs_mesh_solid_faces() 2件
  • draw_object_mesh_instance() 1件

となっていました。

Objectモードのときはdraw_mesh_fancy()がdrawFacesSolid()を呼び出していてレンダリング命令につながっていましたね。試しにdraw_mesh_fancy()の中のdrawMappedFaces()をコメントアウトして実行してみましたが、Editモードの面のレンダリングには関係ないようです。

次はdraw_mesh_fancy()と同じように「fancy」の付くdraw_em_fancy()辺りが怪しいでしょうか?3件ともコメントアウトしてみます。
before
↓↓↓
after
Editモードの面が消えましたね!!結果、3つの関数ポインタdrawMappedFacesのうち最後が今回探している起動時のデフォルト状態からEditモードに切り替えた直後の面のレンダリング部分につながっているようです。

関数ポインタが指す関数本体は?

さて、狙いの関数ポインタdrawMappedFacesも見つかった事ですし、ステップ実行してこのdrawMappedFacesが指し示している関数本体を探してみましょう。するとdrawMappedFacesはeditderivedmesh.c内のemDM_drawMappedFaces()を指し示している事がわかります。

emDM_drawMappedFaces()の中ではやはりOpenGLのレンダリング命令が使われており、GPUレンダリングが行われているようです。CPUか?GPUか?答えが出ましたね!

おまけ

ObjectモードのときはglDrawArraysで頂点データの格納された配列の中身を一気にレンダリングしていました。

Editモードのときはこれと違い、for文の中で三角形を1つずつレンダリングしているようです。きっと頻繁な頂点の追加&削除の負荷に耐えるためでしょう。

試しにfor文のカウントの上限値から1を引いてみたら三角形が1つ欠けたりするんでしょうか?
before
三角形が1つ消えましたね^^


@fmystB

↑このページのトップヘ