Siphon memo 4
前回に引き続き Blender における Freestyle の処理フローを調査している。今回は ViewMap の構築とキャッシュあたりについて調べてみよう。
1. Controller::Controller()
まずは Controller::Controller()
の実装を見てみよう。
namespace Freestyle {
Controller::Controller()
{
// ...
_ViewMap = 0;
_Canvas = 0;
_VisibilityAlgo = ViewMapBuilder::ray_casting_adaptive_traditional;
//_VisibilityAlgo = ViewMapBuilder::ray_casting;
_Canvas = new AppCanvas;
_inter = new PythonInterpreter();
_EnableViewMapCache = false;
_EnableQI = true;
_EnableFaceSmoothness = false;
_ComputeRidges = true;
_ComputeSteerableViewMap = false;
_ComputeSuggestive = true;
_ComputeMaterialBoundaries = true;
_sphereRadius = 1.0;
_creaseAngle = 134.43;
prevSceneHash = -1.0;
init_options();
}
_EnableViewMapCache = false;
となっている。これは同じファイルに
void Controller::setViewMapCache(bool iBool)
{
_EnableViewMapCache = iBool;
}
とあるようにここで指定されるようである。
実際は /freestyle/intern/blender_interface/FRS_freestyle.cpp
の FRS_do_stroke_rendering
関数
Render *FRS_do_stroke_rendering(Render *re, SceneRenderLayer *srl, int render)
{
// ...
controller->setViewMapCache((srl->freestyleConfig.flags & FREESTYLE_VIEW_MAP_CACHE) ? true : false);
で実行されているようだ。
2. View map cache
まずは ViewMap のキャッシュを削除している箇所を見てみよう。
/freestyle/intern/blender_interface/FRS_freestyle.cpp
を見てみる。名前からおそらくキャッシュ削除処理は FRS_free_view_map_cache
関数で行われているのではないかと考える。
void FRS_free_view_map_cache(void)
{
// free cache
controller->DeleteViewMap(true);
#if 0
if (G.debug & G_DEBUG_FREESTYLE) {
printf("View map cache freed\n");
}
#endif
}
controller->DeleteViewMap
を見てみる。/freestyle/intern/application/Controller.cpp
L.419
に実装が存在する。
void Controller::DeleteViewMap(bool freeCache)
{
// ...
if (NULL != _ViewMap) {
if (freeCache || !_EnableViewMapCache) {
delete _ViewMap;
_ViewMap = NULL;
prevSceneHash = -1.0;
}
else {
_ViewMap->Clean();
}
}
}
freeCache || _EnableViewMapCache
から推測して freeCache
はハードコードされたタイミングでのキャッシュ消去を意図しているのだろう。DeleteViewMap
で検索すると、
./freestyle/intern/application/Controller.cpp: DeleteViewMap();
./freestyle/intern/application/Controller.cpp:void Controller::DeleteViewMap(bool freeCache)
./freestyle/intern/application/Controller.cpp: DeleteViewMap(true);
./freestyle/intern/application/Controller.cpp: DeleteViewMap();
./freestyle/intern/application/Controller.h: void DeleteViewMap(bool freeCache = false);
./freestyle/intern/blender_interface/FRS_freestyle.cpp: controller->DeleteViewMap(true);
とある。実際に呼び出されている箇所を見てみると、
呼び出し元関数 | 命令 | |
---|---|---|
Controller::CloseFile() |
DeleteViewMap() |
|
Controller::ComputeViewMap() |
DeleteViewMap(true) |
強制削除 |
Controller::DrawStrokes() |
DeleteViewMap() |
|
FRS_free_view_map_cache(void) |
DeleteViewMap(true) |
強制削除 |
となる。
キャッシュを削除する場合は、delete _ViewMap;
とあるように完全に Controller
の _ViewMap
自体を削除するようだ。SceneHash
というのは ViewMap を生成する際 scene graph から生成されるハッシュのようである。
ではキャッシュを削除しない場合はどうなるかというと _ViewMap->Clean()
が実行されるようだ。この実装は /freestyle/intern/view_map/ViewMap.cpp
L.66
付近にある。
void ViewMap::Clean()
{
vector<FEdge*> tmpEdges;
for (vector<ViewShape*>::iterator vs = _VShapes.begin(), vsend = _VShapes.end(); vs != vsend; vs++) {
vector<FEdge*>& edges = (*vs)->sshape()->getEdgeList();
for (vector<FEdge*>::iterator it = edges.begin(), itend = edges.end(); it != itend; it++) {
if ((*it)->isTemporary()) {
(*it)->setTemporary(false); // avoid being counted multiple times
tmpEdges.push_back(*it);
}
}
}
for (vector<FEdge*>::iterator it = tmpEdges.begin(), itend = tmpEdges.end(); it != itend; it++) {
for (vector<ViewShape*>::iterator vs = _VShapes.begin(), vsend = _VShapes.end(); vs != vsend; vs++) {
(*vs)->sshape()->RemoveEdge(*it);
}
(*it)->vertexA()->RemoveFEdge(*it);
(*it)->vertexB()->RemoveFEdge(*it);
delete (*it);
}
}
これを見る感じだと feature edges
のうち一時的に使用したものについて削除しているようである。
さて、この FRS_free_view_map_cache
関数は /freestyle/intern/blender_interface/FRS_freestyle.cpp
以外に /makesrna/intern/rna_scene.c
という箇所でも使用されている。
L.1604-1609
で
static void rna_Scene_use_view_map_cache_update(Main *UNUSED(bmain), Scene *UNUSED(scene), PointerRNA *UNUSED(ptr))
{
#ifdef WITH_FREESTYLE
FRS_free_view_map_cache();
#endif
}
とある。さて、これは同じファイルの L.4141-4145
に 以下のような実装がある。
prop = RNA_def_property(srna, "use_view_map_cache", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flags", FREESTYLE_VIEW_MAP_CACHE);
RNA_def_property_ui_text(prop, "View Map Cache",
"Keep the computed view map and avoid re-calculating it if mesh geometry is unchanged");
RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, "rna_Scene_use_view_map_cache_update");
"Keep the computed view map..."
の文字列は、
と同じようだ。なのでチェックが入れば _EnableViewMapCache = true
となり、
Controller::CloseFile()
Controller::DrawStrokes()
の場合であれば _ViewMap
は削除されず clean()
だけ実行される仕組みのようだ。しかし RNA
や DNA
が Blender でのどんなデータを意味しているのかまだ良くわかっていない。後で調べてみよう。
3. ViewMapBuilder, Controller::ComputeViewMap()
ViewMap の計算は /freestyle/intern/view_map/ViewMapBuilder.cpp
L.995
に内容が示されている。
ViewMap *ViewMapBuilder::BuildViewMap(WingedEdge& we, visibility_algo iAlgo, real epsilon,
const BBox<Vec3r>& bbox, unsigned int sceneNumFaces)
{
_ViewMap = new ViewMap;
_currentId = 1;
_currentFId = 0;
_currentSVertexId = 0;
// Builds initial view edges
computeInitialViewEdges(we);
// Detects cusps
computeCusps(_ViewMap);
// Compute intersections
ComputeIntersections(_ViewMap, sweep_line, epsilon);
// Compute visibility
ComputeEdgesVisibility(_ViewMap, we, bbox, sceneNumFaces, iAlgo, epsilon);
return _ViewMap;
}
ViewMapBuilder は以下の /freestyle/intern/application/Controller.cpp
の ComputeViewMap()
を見てみると vmBuilder
としてインスタンス化されている。WXEdgeBuilder
や edgeDetector
等により処理された拡張 winged edge オブジェクト _winged_edge
と quantitative invisibility (QI)
などのいくつかの設定を用いて ViewMap を構築しているようだ。winged edge は後の Blender から Freestyle へメッシュを読み込む Controller::LoadMesh
でも触れる。
void Controller::ComputeViewMap()
{
if (!_ListOfModels.size())
return;
DeleteViewMap(true);
// ...
edgeDetector.processShapes(*_winged_edge);
real duration = _Chrono.stop();
if (G.debug & G_DEBUG_FREESTYLE) {
printf("Feature lines : %lf\n", duration);
}
if (_pRenderMonitor->testBreak())
return;
// Builds the view map structure from the flagged WSEdge structure:
//----------------------------------------------------------
ViewMapBuilder vmBuilder;
vmBuilder.setEnableQI(_EnableQI);
vmBuilder.setViewpoint(vp);
vmBuilder.setTransform(mv, proj, viewport, _pView->GetFocalLength(), _pView->GetAspect(), _pView->GetFovyRadian());
vmBuilder.setFrustum(_pView->znear(), _pView->zfar());
vmBuilder.setGrid(&_Grid);
vmBuilder.setRenderMonitor(_pRenderMonitor);
#if 0
// Builds a tesselated form of the silhouette for display purpose:
//---------------------------------------------------------------
ViewMapTesselator3D sTesselator3d;
ViewMapTesselator2D sTesselator2d;
sTesselator2d.setNature(_edgeTesselationNature);
sTesselator3d.setNature(_edgeTesselationNature);
#endif
if (G.debug & G_DEBUG_FREESTYLE) {
cout << "\n=== Building the view map ===" << endl;
}
_Chrono.start();
// Build View Map
_ViewMap = vmBuilder.BuildViewMap(*_winged_edge, _VisibilityAlgo, _EPSILON, _Scene3dBBox, _SceneNumFaces);
_ViewMap->setScene3dBBox(_Scene3dBBox);
if (G.debug & G_DEBUG_FREESTYLE) {
printf("ViewMap edge count : %i\n", _ViewMap->viewedges_size());
}
// ...
duration = _Chrono.stop();
if (G.debug & G_DEBUG_FREESTYLE) {
printf("ViewMap building : %lf\n", duration);
}
// ...
// Draw the steerable density map:
//--------------------------------
if (_ComputeSteerableViewMap) {
ComputeSteerableViewMap();
}
// Reset Style modules modification flags
resetModified(true);
DeleteWingedEdge();
}
SteerableViewMap
はまだよく分からない。内部で GrayImage
というのが使用されているようなので、もしかしたらストロークの密度を逐次監視しつつ描き足していく causal density
と関係しているのかもしれない。後で調べる必要がある。また frustum
とは円錐台のことらしい。
ComputeViewMap()
は主に /freestyle/intern/blender_interface/FRS_freestyle.cpp
の prepare
関数で呼び出されるようだ。/freestyle/intern/application/Controller.cpp
L.1017
付近にも
void Controller::toggleEdgeTesselationNature(Nature::EdgeNature iNature)
{
_edgeTesselationNature ^= (iNature);
ComputeViewMap();
}
と呼び出しているところがある。ただこの toggleEdgeTesselationNature
という関数はソースコードを grep した限りヘッダーファイル以外では他にヒットしないので、これは何かの名残りではないかと考えている。
4. prepare(Render *re, SceneRenderLayer *srl)
前回の記事で見た /freestyle/intern/blender_interface/FRS_freestyle.cpp
にある prepare
関数をもう一度見てみよう。
static void prepare(Render *re, SceneRenderLayer *srl)
{
// load mesh
re->i.infostr = IFACE_("Freestyle: Mesh loading");
re->stats_draw(re->sdh, &re->i);
re->i.infostr = NULL;
if (controller->LoadMesh(re, srl)) // returns if scene cannot be loaded or if empty
return;
if (re->test_break(re->tbh))
return;
// add style modules
FreestyleConfig *config = &srl->freestyleConfig;
if (G.debug & G_DEBUG_FREESTYLE) {
cout << "\n=== Rendering options ===" << endl;
}
int layer_count = 0;
switch (config->mode) {
case FREESTYLE_CONTROL_SCRIPT_MODE:
// ...
break;
case FREESTYLE_CONTROL_EDITOR_MODE:
// ...
break;
}
// set parameters
// ...
if (controller->hitViewMapCache())
return;
// compute view map
re->i.infostr = IFACE_("Freestyle: View map creation");
re->stats_draw(re->sdh, &re->i);
re->i.infostr = NULL;
controller->ComputeViewMap();
}
FREESTYLE_CONTROL_SCRIPT_MODE
と FREESTYLE_CONTROL_EDITOR_MODE
という config->mode
によって処理が分岐しているが、基本的には描画処理で必要な設定をそれぞれ準備しているだけなので省略した。
注目すべきは関数後半の // compute view map
のあたり、
if (controller->hitViewMapCache())
return;
// compute view map
re->i.infostr = IFACE_("Freestyle: View map creation");
re->stats_draw(re->sdh, &re->i);
re->i.infostr = NULL;
controller->ComputeViewMap();
である。つまり hitViewMapCache()
が true
であれば ViewMap を再計算せずキャッシュを使用するということなのだろう。hitViewMapCache()
は、
bool Controller::hitViewMapCache()
{
if (!_EnableViewMapCache) {
return false;
}
if (sceneHashFunc.match()) {
return (NULL != _ViewMap);
}
sceneHashFunc.store();
return false;
}
となっている。つまりシーンが変更されなければハッシュ値は一致するので再計算は回避されるようだ。
5. Controller::LoadMesh(Render *re, SceneRenderLayer *srl)
最後に Blender のメッシュを読み込む Controller::LoadMesh
を見てみよう。
int Controller::LoadMesh(Render *re, SceneRenderLayer *srl)
{
BlenderFileLoader loader(re, srl);
loader.setRenderMonitor(_pRenderMonitor);
// ...
if (_EnableViewMapCache) {
NodeCamera *cam;
if (g_freestyle.proj[3][3] != 0.0)
cam = new NodeOrthographicCamera;
else
cam = new NodePerspectiveCamera;
double proj[16];
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
proj[i * 4 + j] = g_freestyle.proj[i][j];
}
}
cam->setProjectionMatrix(proj);
_RootNode->AddChild(cam);
_RootNode->AddChild(new NodeSceneRenderLayer(*re->scene, *srl));
sceneHashFunc.reset();
//blenderScene->accept(sceneHashFunc);
_RootNode->accept(sceneHashFunc);
if (G.debug & G_DEBUG_FREESTYLE) {
cout << "Scene hash : " << sceneHashFunc.toString() << endl;
}
if (hitViewMapCache()) {
ClearRootNode();
return 0;
}
else {
delete _ViewMap;
_ViewMap = NULL;
}
}
_Chrono.start();
WXEdgeBuilder wx_builder;
wx_builder.setRenderMonitor(_pRenderMonitor);
blenderScene->accept(wx_builder);
_winged_edge = wx_builder.getWingedEdge();
duration = _Chrono.stop();
if (G.debug & G_DEBUG_FREESTYLE) {
printf("WEdge building : %lf\n", duration);
}
// ...
_ListOfModels.push_back("Blender_models");
_Scene3dBBox = _RootNode->bbox();
_bboxDiag = (_RootNode->bbox().getMax() - _RootNode->bbox().getMin()).norm();
if (G.debug & G_DEBUG_FREESTYLE) {
cout << "Triangles nb : " << _SceneNumFaces << " imported, " <<
_winged_edge->getNumFaces() << " retained" << endl;
cout << "Bounding Box : " << _bboxDiag << endl;
}
ClearRootNode();
_SceneNumFaces = _winged_edge->getNumFaces();
if (_SceneNumFaces == 0) {
DeleteWingedEdge();
return 1;
}
return 0;
}
WXEdgeBuilder
から _winged_edge
が生成されている。WXEdge
の X
というのは何かというと /freestyle/intern/winged_edge/WXEdgeBuilder.cpp
に、
/** \file blender/freestyle/intern/winged_edge/WXEdgeBuilder.cpp
* \ingroup freestyle
* \brief Class inherited from WingedEdgeBuilder and designed to build a WX (WingedEdge + extended info
* (silhouette etc...)) structure from a polygonal model
というコメントがある。この extended info
というのは前に見た edgeDetector
により検知した様々な feature edges
の種類、たとえば silhouette
や valleys
などを保持しているのだと予想している。
さて ViewMap の構築やキャッシュの処理について、ざっくりと流れを見てみた。キャッシュされた ViewMap 自体は Controller()
にアクセスするのが難しい以上 Python からアクセスするのは無理そうだ。なので render 後のポスト処理から処理を実装していくのをメインに進めていけば良さそうである。