Siphon memo 3
Siphon を開発するにあたって Blender における Freestyle の処理フローを調査している。Blender のソースコードを引用しつつ、Freestyle の関数がどのように呼び出されているのかざっくり追ってみよう。
1. bpy.ops.render.render()
基本的に Freestyle はレンダリング処理中に動作する。そこで bpy.ops.render.render()
の処理を見てみよう。bpy.ops.render
は一見 Python の submodule のように見える。
>>> import bpy
>>> bpy.ops.render
<module like class 'bpy.ops.render'>
module like class
という見慣れないクラスのようだ。help()
で調べてみる。
>>> help(bpy.ops.render)
Help on BPyOpsSubMod in module bpy.ops object:
class BPyOpsSubMod(builtins.object)
| Utility class to fake submodules.
|
| eg. bpy.ops.object
...
すると fake submodules
という興味深い文字列が出力される。どうやらただのクラスではないようだ。実際のコード片については /release/scripts/modules/bpy/ops.py にある。読んでみると _bpy
という C-extension の中身を Python submodule に見せかけてアクセスできるようにしたものらしい。この C-extension は /source/blender/python/intern/bpy_operator.c に内容が記述されている。
render.render
のような処理はどうやら OperatorType (OT
) と呼ばれているらしく、bpy_operator.c
の L.169 のように、
ot = WM_operatortype_find(opname, true);
という関数で呼び出されるようだ。おそらく opname
には render.render
という文字列が入りそうである。WM_operatortype_find
関数は /blender/windowmanager/intern/wm_operators.c に実態がある。
wmOperatorType *WM_operatortype_find(const char *idname, bool quiet)
{
if (idname[0]) {
wmOperatorType *ot;
/* needed to support python style names without the _OT_ syntax */
char idname_bl[OP_MAX_TYPENAME];
WM_operator_bl_idname(idname_bl, idname);
ot = BLI_ghash_lookup(global_ops_hash, idname_bl);
if (ot) {
return ot;
}
さて、ここで WM_operator_bl_idname
関数は同じファイルに、
/* some.op -> SOME_OT_op */
void WM_operator_bl_idname(char *to, const char *from)
とある。つまり render.render
はおそらく RENDER_OT_render
に変換され WM_operatortype_find
関数内部の BLI_ghash_lookup
で検索が行われるようだ。予想通り /editors/render/render_internal.c に RENDER_OT_render
という関数を見つけた。
/* contextual render, using current scene, view3d? */
void RENDER_OT_render(wmOperatorType *ot)
{
PropertyRNA *prop;
/* identifiers */
ot->name = "Render";
ot->description = "Render active scene";
ot->idname = "RENDER_OT_render";
/* api callbacks */
ot->invoke = screen_render_invoke;
ot->modal = screen_render_modal;
ot->cancel = screen_render_cancel;
ot->exec = screen_render_exec;
さて、上のコード片を読んだ限りでは /* api callbacks */
以下の行が処理の中心になりそうだ。試しに同じファイルに含まれている ot->exec
の screen_render_exec
関数を見てみる。
/* executes blocking render */
static int screen_render_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
SceneRenderLayer *srl = NULL;
Render *re;
Image *ima;
View3D *v3d = CTX_wm_view3d(C);
Main *mainp = CTX_data_main(C);
unsigned int lay_override;
const bool is_animation = RNA_boolean_get(op->ptr, "animation");
const bool is_write_still = RNA_boolean_get(op->ptr, "write_still");
// ...
re = RE_NewRender(scene->id.name);
// ...
BLI_begin_threaded_malloc();
if (is_animation)
RE_BlenderAnim(re, mainp, scene, camera_override, lay_override, scene->r.sfra, scene->r.efra, scene->r.frame_step);
else
RE_BlenderFrame(re, mainp, scene, srl, camera_override, lay_override, scene->r.cfra, is_write_still);
BLI_end_threaded_malloc();
// ...
return OPERATOR_FINISHED;
}
実際のレンダリングは1フレームを描画する場合 RE_BlenderFrame
、アニメーションの場合 RE_BlenderAnim
で行われるみたいだ。RE_BlenderFrame
は /render/intern/source/pipeline.c に実装がある。この pipeline.c
でようやく Freestyle
の文字が出現し始める。
2. /render/intern/source/pipeline.c
pipeline.c
はレンダリング処理が主に記述された 4,000 行ほどのファイルである。ここでは /freestyle/FRS_freestyle.h がインクルードされており pipeline.c
のいくつかの関数で様々な Freestyle の関数が呼び出されている。FRS_freestyle.h
の関数名には FRS_
という prefix が基本的に付けられており、この prefix で Blender のソースコード全体を検索すると Freestyle が外部から呼び出されている場面も見つけることができるので便利である。
pipeline.c
の中で実際に呼び出されているのは以下の7つのようだ。
FRS_init_stroke_renderer(re);
FRS_begin_stroke_rendering(re);
FRS_do_stroke_rendering(re, srl, render);
FRS_end_stroke_rendering(re);
FRS_composite_result(re, srl, freestyle_render);
FRS_is_freestyle_enabled(srl);
FRS_exit();
すべての関数は /freestyle/intern/blender_interface/FRS_freestyle.cpp に実装がある。ちなみに /freestyle/
内で全ソースコードで _OT_
とつく関数を検索したところヒットしなかったため OperatorType 自体は存在しないようだ。
呼び出されている関数の中で stroke
が名前に含まれるものについて実装を少し見てみよう。
void FRS_init_stroke_renderer(Render *re)
{
if (G.debug & G_DEBUG_FREESTYLE) {
cout << endl;
cout << "#===============================================================" << endl;
cout << "# Freestyle" << endl;
cout << "#===============================================================" << endl;
}
init_view(re);
controller->ResetRenderCount();
}
void FRS_begin_stroke_rendering(Render *re)
{
init_camera(re);
}
Render *FRS_do_stroke_rendering(Render *re, SceneRenderLayer *srl, int render)
{
Render *freestyle_render = NULL;
if (!render)
return controller->RenderStrokes(re, false);
RenderMonitor monitor(re);
controller->setRenderMonitor(&monitor);
controller->setViewMapCache((srl->freestyleConfig.flags & FREESTYLE_VIEW_MAP_CACHE) ? true : false);
if (G.debug & G_DEBUG_FREESTYLE) {
cout << endl;
cout << "----------------------------------------------------------" << endl;
cout << "| " << (re->scene->id.name + 2) << "|" << srl->name << endl;
cout << "----------------------------------------------------------" << endl;
}
// prepare Freestyle:
// - load mesh
// - add style modules
// - set parameters
// - compute view map
prepare(re, srl);
if (re->test_break(re->tbh)) {
controller->CloseFile();
if (G.debug & G_DEBUG_FREESTYLE) {
cout << "Break" << endl;
}
}
else {
// render and composite Freestyle result
if (controller->_ViewMap) {
// render strokes
re->i.infostr = IFACE_("Freestyle: Stroke rendering");
re->stats_draw(re->sdh, &re->i);
re->i.infostr = NULL;
g_freestyle.scene = re->scene;
int strokeCount = controller->DrawStrokes();
if (strokeCount > 0) {
freestyle_render = controller->RenderStrokes(re, true);
}
controller->CloseFile();
g_freestyle.scene = NULL;
// composite result
if (freestyle_render) {
FRS_composite_result(re, srl, freestyle_render);
RE_FreeRenderResult(freestyle_render->result);
freestyle_render->result = NULL;
}
}
}
return freestyle_render;
}
void FRS_end_stroke_rendering(Render * /*re*/)
{
// clear canvas
controller->Clear();
}
よく見かける controller
というのは /freestyle/intern/application/Controller.h で定義されている。この controller
には FEdgeXDetector
(FEdge
は feature edge
を指す)や ViewMapBuilder
などの処理機構が定義されている。FRS_freestyle.cpp
の FRS_initialize()
に controller
の初期化処理が記述されている。
void FRS_initialize()
{
if (freestyle_is_initialized)
return;
pathconfig = new Config::Path;
controller = new Controller();
view = new AppView;
controller->setView(view);
controller->Clear();
g_freestyle.scene = NULL;
lineset_copied = false;
BLI_callback_add(&load_post_callback_funcstore, BLI_CB_EVT_LOAD_POST);
freestyle_is_initialized = 1;
}
中心的な処理を行うのは FRS_do_stroke_rendering
のようだ。特に prepare(re, srl)
は
// prepare Freestyle:
// - load mesh
// - add style modules
// - set parameters
// - compute view map
というコメントの通り ViewMap
の計算を行ったり重要な役割を果たしている。
3. Blender アドオンからの利用
さて、ここまで Blender における Freestyle の処理を内部処理までは踏み込まないでざっくりと見てきた。Blender アドオンからどれくらいの粒度で利用可能だろうか。Python スクリプトで記述することを考えると、細かく制御するのはちょっと厳しい気がしている。
これから確認しなければならないことは、いまのところ以下である:
ViewMap cache
の詳細 (現行の Blender では ViewMap cache の機能が搭載されている。どのようなデータがキャッシュされているのか、インスタンスが create されたり free されるタイミングを調べる)Predicates
の処理フロー
たとえば Freestyle の論文にある Calligraphic shader は方向をベクトルの形で指定することができるが、これを現在用意されている Python スタイルモジュールではなく NodeTree の GUI でマウス等を用い方向を指定して、さらにリアルタイムで結果を反映させられないかと考えている。以前 Freestyle SVG Exporter を調べたとき Python のオブジェクトから ViewShape, ViewEdge, ViewMap などにアクセスできた記憶があるので、それらを deepcopy して使うという手もあるのかもしれない。