KLab Tech Book Vol. 3

2018-10-08 版

KLab 技術書サークル 発⾏

1

はじめに
このたびは、本書をお⼿に取っていただきありがとうございます。本書は、KLab 株式会社の有志にて作成された KLab Tech Book の第 3 弾です。

KLab 株式会社では、主にスマートフォン向けのゲームを開発していますが、本書ではこれまでどおり、社内のエンジニアの多岐にわたる興味を反映したさまざまな記事を収録しました。業務に関係のあることもあり、完全に趣味の内容もあります。
あまりにもバラバラな内容なので、統⼀感がまったくありませんが、ひとつ共通していえるのは、著者⾃⾝がその内容に興味を持ち、楽しさを感じて書いていることです。
その楽しさが読者のみなさまにも伝わればさいわいです。
於保 俊

お問い合わせ先本書に関するお問い合わせは tech-book@support.klab.com まで。

免責事項
本書に記載された内容は、情報の提供のみを⽬的としています。したがって、本書を⽤いた開発、製作、運⽤は、必ずご⾃⾝の責任と判断によって⾏ってください。これらの情報による開発、製作、運⽤の結果について、著者はいかなる責任も負いません。

2

⽬次

はじめに

2

お問い合わせ先 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

2

免責事項 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

2

第1章

プロシージャルモデリングを⽀える Houdini の機能紹介

71.1

本章で定義するプロシージャルモデリングとは . . . . . . . . . . . . . .

8

1.2

Houdini でプロシージャルモデリング . . . . . . . . . . . . . . . . . . .

8

1.3

HDA を触ってみる：パラメータ変更のみでアセットの数やサイズを変更

13

1.4

HDA を触ってみる：ノードやパラメータを確認 . . . . . . . . . . . . .

15

1.5

おわりに . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

17

1.6

参考⽂献、資料 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

18

2.5 万円で買える 3D プリンタのススメ

19

2.1

はじめに . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

19

2.2

3D プリンタはいいぞ! . . . . . . . . . . . . . . . . . . . . . . . . . . .

19

2.3

今回紹介する 3D プリンタの種類について

21

2.4

3D プリントに必要なものを揃えよう . . . . . . . . . . . . . . . . . . .

22

2.5

プリントしてみよう . . . . . . . . . . . . . . . . . . . . . . . . . . . .

23

2.6

おわりに . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

28

第2章

第3章

. . . . . . . . . . . . . . . .

Airtest を⽤いた Unity アプリの⾃動実機テスト

29

3.1

はじめに . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

29

3.2

導⼊⽅法 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

30

3.3

テストの書き⽅ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

31

3.4

Jenkins との連携 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

33

3.5

おわりに . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

33

Rider + Unity で Roslyn Analyzers を使う

34

4.1

はじめに . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

34

4.2

Roslyn Analyzers について . . . . . . . . . . . . . . . . . . . . . . . .

35

第4章

3

⽬次

4.3

導⼊の前提について . . . . . . . . . . . . . . . . . . . . . . . . . . . .

36

4.4

Unity の⾃動⽣成プロジェクトファイルを編集する . . . . . . . . . . . .

36

4.5

Roslyn Analyzers の制限 . . . . . . . . . . . . . . . . . . . . . . . . .

41

4.6

⾃動⽣成ファイルとしてマークして解析範囲外にする。 . . . . . . . . .

41

4.7

アナライザー側でファイル判定を⾏う処理を追加する。 . . . . . . . . .

43

4.8

まとめ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

47

バーコードリーダーになろう

48

5.1

バーコードの規格について . . . . . . . . . . . . . . . . . . . . . . . . .

48

5.2

バーコードの構造

49

5.3

シンボルキャラクタの読み⽅. . . . . . . . . . . . . . . . . . . . . . .

49

5.4

チェックデジットの検証 . . . . . . . . . . . . . . . . . . . . . . . . . .

51

5.5

おわりに . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

52

Unity Timeline Tips 集

53

6.1

はじめに . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

53

6.2

Tips.1 [PlayableAsset] TimelineAsset にキャストせずにトラック情報

第5章

第6章

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

を取り出す . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

53

6.3

Tips.2 [PlayableBehaviour] PlayableDirector の参照を取得する . . . .

55

6.4

Tips.3 [PlayableBehaviour] 各クリップの時間に関する情報を取得する .

56

6.5

Tips.4 [AssetBundle] TimelineAsset を AssetBundle 化する . . . . . .

58

6.6

Tips.5 [SelectionManager] 選択中のトラック/クリップを取得・設定する

60

6.7

Tips.6 [TimelineWindow] スクロール位置を制御する . . . . . . . . . .

62

6.8

最後に . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

64

第7章

物理ベースレンダラーを Rust 実装して、ちょっと⾼速化した話

65

7.1

パストレーシングと計算時間. . . . . . . . . . . . . . . . . . . . . . .

65

7.2

⾃作レンダラーの紹介 . . . . . . . . . . . . . . . . . . . . . . . . . . .

66

7.3

BVH による衝突判定の⾼速化 . . . . . . . . . . . . . . . . . . . . . . .

68

7.4

式を整理して不要な計算を省く⾼速化 . . . . . . . . . . . . . . . . . . .

69

7.5

Next Event Estimation（NEE）の実装 . . . . . . . . . . . . . . . . .

70

7.6

デノイズの実装 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

72

7.7

Rust 環境の最新化 . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

74

7.8

まとめ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

75

ヘッドレス Chrome でリボ払いを回避している話

77

8.1

はじめに . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

77

8.2

実⾏環境 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

78

8.3

Puppeteer（ぱぺってぃあ）とは . . . . . . . . . . . . . . . . . . . . .

78

第8章

4

8.4

Puppeteer のインストール . . . . . . . . . . . . . . . . . . . . . . . . .

78

8.5

Puppeteer でのコード例 . . . . . . . . . . . . . . . . . . . . . . . . . .

79

8.6

Puppeteer の良いところ . . . . . . . . . . . . . . . . . . . . . . . . . .

80

8.7

おわりに . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

81

著者

82

5

6

第1章

プロシージャルモデリングを⽀える
Houdini の機能紹介
Kinuko MIZUSAWA
KLab でクライアントエンジニアをやっている⼈間です。普段はビルドのマシンやジョブの管理、OS ネイティブ周りの機能を担当しているのですが、最近 3D 関連の業務に関わる機会が出来、Houdini*1 の機能について調べたり実際にソフトを触ったりしています。

▲図 1.1

Houdini の公式サイト上でのビジュアル。プロシージャルなコンテンツ製作ツールとして訴求されています

Houdini とは、SideFX 社が提供している 3DCG ソフトです。モデリング、アニメー*1 https://www.sidefx.com/ja/

7

第 1 章 プロシージャルモデリングを⽀える Houdini の機能紹介

ション、エフェクトの制作から、それらを組み合わせて合成するコンポジットまでと、3DCG 制作の全⾏程をカバーしているソフトになります*2 。
Houdini は元々、プロシージャルにモデル製作ができ、効率的にハイクオリティなアセットを作ることができるツールと期待して学習を始めたのですが、Houdini を知れば知るほど、その強⼒かつ柔軟性のある機能に魅⼒を感じました。
このため、今回は Houdini と、Houdini のどういった機能、特徴がプロシージャルモデ
リングを可能としているかということを紹介したいと思います。
本章が、Houdini を利⽤したアセット制作を検討する際にお役に⽴てば幸いです。

本章で定義するプロシージャルモデリングとは

1.1

プロシージャルモデリングとは、昨今のゲームコンテンツのリッチ化、制作コストの⾼騰を受けて特に注⽬されている技術です。
プロシージャルモデリングでは数式やプログラムによって数値などの⼊⼒を元にモデルを作成する仕組みを作り、その仕組みによってモデルを作成することができます。
⾮プロシージャルなモデリングでは、量産にあたりモデリングを⾏うアーティストの⼿作業が発⽣し、たとえばキャラクターのシルエット、体型の変更を⾏った際にディテールの追加修正作業も同時に発⽣します。
⼀⽅で、プロシージャルなモデリングでは、たとえキャラクターのシルエット、体型の変更を⾏った場合でも、⼀度モデリング作業そのものを⾃動化する仕組みを作っていることから、体型に合わせてディテールの調整も⾏ってくれ、作業を不要とすることができます。
このように、プロシージャルモデリングによって、3D アセットを作る⼈は、ツールのオペレーションのような単純作業を減らし、デザインの検討のようなクリエイティブな作業に時間を割くことができるようになります。また、3D アセットの作成スキルを持っていない⼈でもパラメータの変更やネットワークのみでアセットの⼤きさや形を変えることができるようになります。

1.2 Houdini でプロシージャルモデリング
Houdini でプロシージャルモデリングすることを語る上で、語るべきは⼤きく次の三点です。これらについて触れていきたいと思います。

• ノードベースプログラミング• Houdini Digital Assets• Houdini Engine*2 特にエフェクト、シミュレーションは機能が豊富かつ強⼒で、映画の VFX なら Houdini を使っている現場が増えているようです81.2 Houdini でプロシージャルモデリング

ノードベースプログラミングによる⼿作業の⾃動化
そもそもノードベースプログラミングとはどういうものでしょうか？ これは、最近多くのゲームエンジンやツールで採⽤されている視覚的にプログラムを組めるものになります。

▲図 1.2 Houdini で、ノードを追加したりつないだりするネットワークエディタの画⾯

視覚的にプログラムを組めるものの例としては、Houdini 以外のゲーム開発のツールだと Unreal Engine の Blueprints*3 や、Unity の Shader Graph*4 などがあります。
これらは視覚的にプログラムを組めて、処理の流れが⽬でみてわかりやすいのでアーティストにとっても扱いやすくまた普段コードでプログラムしているプログラマにとっても管理がしやすかったり、ノードを繋いだりする作業⾃体が直感的で楽しいものとなっていることから⼈気です。

Houdini ではこのノードベースプログラミングで記述できることにより、アセット作成を⾃動化することができ、また、視覚的にも処理がわかりやすくなるメリットがあります。また、副次的な作⽤として、たとえば Photoshop や Illustlator にあるレイヤー機能のように、処理のひとつひとつに対してオンオフの切り替えができるようになっていることで⼿戻りが起きた時の⼿作業をなくしたり、減らしたりすることができます。

*3 http://api.unrealengine.com/JPN/Engine/Blueprints/*4 https://unity3d.com/jp/shader-graph9

第 1 章 プロシージャルモデリングを⽀える Houdini の機能紹介

Houdini で扱うことのできるノードとは
Houdini のノードにはその機能ごとにいくつかの種類があります。⼀度どういう種類があるのか、⼤まかに把握しておくと実際に作業で使うべきノードを探すときに助かります
ので、ここではざっくりとしたノードの種類を紹介しつつ、特にモデリングで多⽤することになるノードタイプ、SOP の具体例について順に紹介していきたいと思います。

Houdini のノードの種類
Houdini のノードはすべて、次のいずれかのノードに該当します。
CHOP ノード• 波形の作成、編集を⾏うノードです。アニメーションやオーディオのデータを編集したりします。

COP ノード• 2D 画像の作成、編集、合成を⾏うコンポジットのノードです。レンダリング画像を合成したり、⾊の調整をしたりします。

DOP ノード• ダイナミクス、シミュレーション処理を扱うノードです。煙や炎、⽔や布の揺れ、破壊の挙動を計算したりします。15 以前の Houdini のバージョンでは POP ノー
ドとして扱われていたパーティクルもこの中に含まれます。

OBJ ノード• シーンの最上位階層を表すノードです。ジオメトリ、ライト、カメラなどのタイプがあります。

ROP ノード• 出⼒を⾏うコンポジットのノードです。レンダリングやキャッシュ作成などのデー
タ出⼒をしたりします。

VOP ノード• Houdini 独⾃のプログラミング⾔語「VEX」を⽤いて書かれた処理を表すノードです。VOP ではジオメトリの操作、シェーダー作成などいろいろなことができます。15 以前の Houdini のバージョンでは SHOP ノードとして扱われていたマテ
リアルもこの中に含まれます。

101.2 Houdini でプロシージャルモデリング

SOP ノード• 本章で紹介しているジオメトリの作成、編集を⾏うノードです。

モデリングで特に利⽤する SOP の具体例
ここでは、特にモデリングで多⽤することになる SOP のノードを具体的に紹介することで、Houdini でプロシージャルモデリングすると、どういうネットワークを組むことができるのかイメージできるようにしたいと思います。

SOP のノードには、たとえば次のようなものがあります。
■基本形状の⽣成
ノード名

説明

Curve
Line
Sweep
SphereGrid

曲線を作成するノード
直線を作成するノード
スイープ曲⾯を作成するノード
球体を作成するノード
平⾯を作成するノード■ジオメトリの変更
ノード名

説明Transform
Bend
Merge

ジオメトリのサイズを変更するノード
ジオメトリを曲げるノード
ジオメトリを統合するノード■ジオメトリの追加
ノード名

説明

Copy Stamp
Copy To Point
Scatter

ジオメトリのコピーを作成し配置するノード
ジオメトリをポリゴンのポイントに合わせてコピーして配置するノード
ジオメトリを散乱させて配置するノード

これらの基本となるノードやよく使われるであろう便利なノードは Houdini の標準機
能として⽤意されているので、Houdini ユーザーはそれらを組み合わせることでモデリン
グすることができます。

Houdini の標準機能として⽤意されているノードは、次のサイトにて公開されています。実際に Houdini でモデリングしていく際にはそちらの情報をまず⾒てみるのが良さそうです。
公式サイトリンク
上記のノードは、次の公式ページでも紹介されています。

https://www.sidefx.com/ja/docs/houdini/examples/nodes/index.html

11

第 1 章 プロシージャルモデリングを⽀える Houdini の機能紹介

デジタルアセットによるプログラム化されたモデリング作業の移植容易化
さて、さらに Houdini では、ノードベースプログラミングによって組んだネットワー
ク、ノードの仕組みはそのかたまりごとに Houdini Digital Assets という形で外部ファイ
ル化し、ノードのパラメータを変更可能なものとすることができます。

▲図 1.3 Houdini で、ノードのパラメーターを編集するパラメータエディタの画⾯

デジタルアセットがあることで、アセットを作る作業そのものを部品化し、組み合わせることができるようになります。

Maya や Unity, Unreal Engine との連携を可能にする Houdini Engine
ここまで紹介して来たノードベースプログラミングで作ったネットワークは、デジタルアセット化してネットワークそのものを外部ファイルとして吐き出すことで、GUI 上で利⽤できるプラグインとして Maya のような DCC ツールや Unity, Unreal Engine
といったゲームエンジンでも利⽤できるようにすることができます。
この利⽤により、Houdini の機能を利⽤するために Houdini と DCC ツール、ゲームエ
ンジンといったツールの⾏き来をなくし、DCC ツールやゲームエンジン上で Houdini で作ったネットワークによってリアルタイムに最終的な⾒た⽬を確認しながらアセット制作などの作業を⾏うことができるようになります。

12

1.3 HDA を触ってみる：パラメータ変更のみでアセットの数やサイズを変更

Houdini が連携できる外部ソフトは次のとおりです。
• ゲームエンジン– Unity– UE4• DCC ツール
– Maya– 3DS Max
– Cinema 4D
この、Houdini と外部ツールを連携させるためのツールが Houdini Engine です。現在
はノードロック版の Indie ライセンスであれば、3 ライセンスまで無料で使えるようになっています。また、フローティング版のライセンスも、サブスクリプションで 1 年/90
⽇/60 ⽇/30 ⽇/14 ⽇/7 ⽇といった期間で使えるようになっています。
サブスクリプションの期間

価格

備考

1年
90 ⽇
60 ⽇
30 ⽇
14 ⽇
7⽇

$ 795 USD
$ 525 USD
$ 350 USD
$ 175 USD
$ 150 USD
$ 75 USD

1 ライセンスから購⼊可能
2 ライセンスから購⼊可能
2 ライセンスから購⼊可能
5 ライセンスから購⼊可能
5 ライセンスから購⼊可能
10 ライセンスから購⼊可能

また、Houdini Engine は GUI 上で利⽤できるプラグインとしてだけではなくプログラムから呼び出せる API も提供されています。この API を利⽤して、深夜にアセットをビルドする、統合する、といったことも可能になります。

1.3 HDA を触ってみる：パラメータ変更のみでアセットの数やサイズを変更
それでは、ここで「グリッドのポイントに沿ってきれいに配置された球の群れ」「bubble」と名付けた HDA を触ってみたいと思います。

• シーンへの読み込み
ネットワークエディタ上で、右クリックあるいはタブボタンを押してノードの追加メ
ニューから「Digital Assets」を選択して読み込むアセットを選択します。
選択すると、シーンエディタ上に球の群れが表⽰されます。

13

第 1 章 プロシージャルモデリングを⽀える Houdini の機能紹介▲図 1.4

あらかじめ作成していた「bubbles」を Houdini 上で読み込み、シーン上に配置させた時の画⾯• 球の数を変えてみる
読み込んだアセットのノードを選択し、パラメータのエディタで「Sphere Rows」「Sphere Columns」を 50 から 10 に変更してみます。
そうすると、シーンエディタ上で表⽰される球の数が減っていることが確認できます。

▲図 1.5「Sphere Columns」の設定値を下げ、「bubbles」のパラメータ「Sphere Rows」
シーン上に表⽰される球の数を減らしてみた時の画⾯• 球のサイズを変えてみる
また、続いてパラメータのエディタで「Sphere Radius」というパラメータを 0.05 から

0.2 に変更してみます。

14

1.4 HDA を触ってみる：ノードやパラメータを確認▲図 1.6「bubbles」のパラメータ「Sphere Radius」の設定値を上げ、シーン上に表⽰される球のサイズを⼤きくしてみた時の画⾯

そうすると、シーンエディタ上で表⽰される球のサイズが⼤きくなっていることが確認できます。

1.4 HDA を触ってみる：ノードやパラメータを確認
次に、ちょっとだけ HDA の設定を⾒てみようと思います。

• ネットワークエディタを開きます• bubble1 という名前のノードがあることがわかります• ダブルクリックすると geo1 という名前のノードがあることがわかります15

第 1 章 プロシージャルモデリングを⽀える Houdini の機能紹介▲図 1.7 Houdini のネットワークエディタにて、ノードの階層を下りていった時の画⾯• さらにダブルクリックすると sphere1, grid1 というノードと、copytopoints1 というノードが繋がっていることがわかります– sphere というノードは球を配置するノードです– grid というノードは平⾯を配置するノードです– copytopoints というノードはコピーするオブジェクトのノードと、コピー先のポイントのノードを⼊⼒として、オブジェクトをポイント上に配置するノードです。

∗ 今回は、コピーオブジェクトを sphere, コピー先のポイントを grid にしています▲図 1.8

さらにノードの階層を下り、「bubbles」の特徴をなしている「球の群れ」を表現しているネットワークを確認している画⾯

16

1.5 おわりに• Edit Digital Assets の画⾯で、Parameter の画⾯を開くと、bubble の Sphere
Radius, Sphere Rows, Sphere Columns, Grid Size といったパラメータが Existing
Parameters リスト上に配置されていることがわかります▲図 1.9HDA の編集画⾯を開き、外部から編集可能なパラメータの中に「Sphere Radius」「Sphere Columns」「Sphere Rows」「Grid Size」という名前が載っていることを確認している画⾯

なお、ここで紹介している HDA は、Houdini 公式サイトにある次のチュートリアル動
画を参考にして作ったものです。

https://www.sidefx.com/ja/tutorials/asset-menu/?collection=27

1.5

おわりに

以上、この章では Houdini を利⽤したプロシージャルモデリングについてお伝えしてきました。
今年は CEDEC でも多くの Houdini のセッションがありましたが、CEDEC のセッションは⾮常に参考になるものでしたので、URL を記載しておきます。興味のある⽅はぜひご覧ください。

17

第 1 章 プロシージャルモデリングを⽀える Houdini の機能紹介

1.6

参考⽂献、資料

書籍• 理論と実践で学ぶ Houdini -SOP&VEX 編-∕ボーンデジタル• Houdini ビジュアルエフェクトの教科書∕エムディエヌコーポレーション

CEDEC 2014
• Houdini Engine と Houdini Indie に よ る プ ロ シ ー ジ ャ ル コ ン テ ン ツ 作 成(http://cedil.cesa.or.jp/cedil_sessions/view/1153)

CEDEC 2017
• Houdini16 に HDA を２つ追加したらビルが量産できた (プロシージャルモデリン
グ事始め)(http://cedil.cesa.or.jp/cedil_sessions/view/1710)

CEDEC 2018
• Maya と Houdini を 連 携 さ せ て 効 率 的 な 環 境 を 構 築 し て み よ う ！
(http://cedil.cesa.or.jp/cedil_sessions/view/1840)

18

第2章

2.5 万円で買える 3D プリンタのススメ
黒井 春⼈/ @halt

2.1

はじめに

この章ではプログラミングの話ではなく、3D プリンタについて紹介します。

KLab 内での私の職種はサーバーサイドエンジニアで PHP がメインなんですが、エンジニアの中途採⽤や新卒採⽤に関わる書類選考、⾯接、技術教育などの採⽤系を担当しています。*1
会社の業務で 3D プリンタを触っているわけではなくて、個⼈的な趣味で電⼦⼯作をやっていて、その延⻑で 3D プリンタを買ってみたらものすごく楽しかったので 3D プリ
ンタ布教のためにこの章を書きました。

3 年前くらいに 15 万円の 3D プリンタを購⼊して楽しんでいたのですが、さすがに 15万円は気軽に払える値段ではないため、周りに布教するにはいたっていませんでした。しかしここ数年で 3D プリンタはどんどん価格を下げ、とうとう 2.5 万円でそこそこの性能のものが購⼊できるようになったため、満を持して布教活動させていただくことにしたわけです。

2.2 3D プリンタはいいぞ!
3D プリンタの魅⼒はなんといっても「画⾯の中にあるものを現実世界に取り出すことができる」につきます。画⾯の中にいる美少⼥キャラをフィギュアとしてプリントするに*1 マニアックでカオスなこの本を楽しめる⼈は KLab 適性⾼いと思うので是⾮⾯接に来てください（切実）。

19第 2 章 2.5 万円で買える 3D プリンタのススメはまだまだ精度的に厳しいですが、シンプルな 3D モデル*2 であればそれほど違和感なく出⼒ができます。

▲図 2.1 CG の世界で有名なあのティーポットも現実世界に呼び出せます

また、お店では単独で売ってない部品（なくしてしまったテレビリモコンのフタとか）や、⾃分で⼯作したものをおさめるための専⽤のケースなども簡単に作ることができます。

▲図 2.2

専⽤ケースも⾃分で設計して⾃分でプリントできます。

*2 Utah Teapot https://www.thingiverse.com/thing:2067607

20

2.3 今回紹介する 3D プリンタの種類について

2.3

今回紹介する 3D プリンタの種類について

⼀⾔で 3D プリンタといってもさまざまな種類の 3D プリンタがあります。形や機構の違いも⼤きいですが、造形⽅式によっていくつかの種類に分類できます。個⼈向けにおいて⼀番メジャーなのが熱熔解積層⽅式（FDM）です。熱を使って細い樹脂を溶かし、1 層ずつ積層していく⽅式です。個⼈向けの 3D プリンタは FDM ⼀択でしたが最近になって6 万円から 15 万円くらいで購⼊できる格安の光造形プリンタが登場し、光造形式についても⼿が届く値段になっています。光造形は、紫外線で硬化する樹脂に光をあてて積層していきます。他にも粉末焼結⽅式という粉にレーザーをあてて硬化させる仕組みがあるのですがこれは最低 80 万-200 万円程度なのでまだ個⼈で扱うには難しい状況です。
今回は⼀番低価格でメジャーな FDM ⽅式を前提とします。

光造形のプリンタは"まだ"オススメしない個⼈で趣味として使う光造形プリンタには FDM と⽐較していくつかの課題があるように感じるので個⼈的にはまだオススメできません。細かい話でいうと、液体を扱うのでミスってこぼすと⼤惨事とか、プリントごとに清掃するのが⾯倒とかありますが私がオススメできない⼤きな理由としては次の 2 点の問題があると考えています。

• 印刷に必要なレジンの価格の⾼さ（FDM 形式のプリンタの約 5 倍）
• レジンの硬化に使う LCD 寿命の短さ（使⽤期間にもよるが数ヶ⽉で交換する消耗品扱い）この 2 点の問題はようするに「コスパが悪い」という話なのでカネで解決できる問題でもあります。光造形プリンタは FDM よりも遥かに⼩さなものや細かいものをプリントできるので、フィギュアの原型師の⽅がプロトタイプ作成に利⽤しているという話も聞きます。造形物をもっときれいにプリントしたいんだという
⼈は挑戦してみてはいかがでしょうか。

21

第 2 章 2.5 万円で買える 3D プリンタのススメ

2.4 3D プリントに必要なものを揃えよう
3D プリント⽣活をはじめるにあたって購⼊しなければならないものは実はたった 2 つしかありません。3D プリンタ本体とフィラメント*3 です。フィラメントとは 2D プリン
タでいうインクのことで、プリントする物体の素材になるものです。厳密には他にもいろ
いろなモノが必要になるのですが、たいていの場合、必要な機材は⼯具や SD カード含めて 3D プリンタに同梱されているので、あとはフィラメントを⼀緒に買えばプリントまで
もっていけます。*4
しかも 3D プリンタとフィラメントは両⽅とも Amazon に売っているので数回クリッ
クすると届きます。簡単ですね。

早速 3D プリンタを購⼊しよう
3D プリンタは安いものから⾼いものまで本当にたくさんあるのでどれを買えばいいのか最初はわからないと思います。いろいろ調べて何が違うのかを知ることは⼤切ですが、最初の 1 台であれば重要視するのは気軽に試せる安さと、不具合なく動かせる安⼼感かと思います。
そんな中で私がこれから 3D プリントはじめてみようかなと思っている初⼼者に勧めたいのは Creality3D Ender-3 です。組み⽴てキットが本家である Creality3D から2018 年 9 ⽉ 27 ⽇時点での販売価格で 23300 円で購⼊できます。*5
Ender-3 はオープンソースのハードウェアなので、製造に必要な情報やファームウェ
アがすべて Web に公開されており、簡単にコピー品を作ることができるため、検索すると怪しい中華業者を筆頭にたくさんの販売業者が出てきます。国内の Amazon を使ってCreality 3D から購⼊するのが現時点で⼀番安⼼ですが、aliexpress などの中国のショッ
ピングサイトを使うと、国内の Amazon で買うよりもかなり安く購⼊できます。ただし、⽇本語サポートや何かあったときの返品⼿続きの難易度は⼤幅にあがるので、そのリスクに⾒合う値段の差かどうかは⼗分に検討したほうがよいでしょう。

フィラメントの購⼊
フィラメントとは、先ほども説明したように 2D プリンタでいうインクのようなもの
で、細⻑いプラスチックをリールにグルグル巻きにしたような形状で売っています。フィ
ラメントはさまざまな種類の素材で作られており、その素材によってフィラメントの特性*3 実はたいていの 3D プリンタ本体にはテスト⽤のフィラメントが少量同梱されているので、フィラメント

を買わなくても最初のテストプリントまではできるようになっています。
*4 当然ですが、プリントデータを⽤意したり、プリント設定を⾏うために PC が必要です。
*5 Creality3D Ender-3 販売ページ：https://www.amazon.co.jp/dp/B07H2QN1H4/

22

2.5 プリントしてみよう

が変わります。最近はかなり種類が増えていますが*6 、⼀番メジャーなフィラメント素材
は PLA と ABS になります。
プリントするときは、フィラメントを 190-220 度程度の⾼温で溶かしつつ押し出すエク
ストルーダーと、プリント中の物体が収縮したりしないように物体の下にある板（ベッド）を 0-60 度前後に加熱するのですが、この加熱の条件がフィラメントによって変わってきます。PLA はベッドをそれほどあたためる必要がなく印刷開始までの時間が短いというメリットがあります。ABS は PLA と⽐較して強度は⾼いものの、エクストルーダーの温度が⾼めでベッドの⼗分な加熱が必要、プリント中に素材からでるニオイが少し強いという特徴をもっています。

3D プリンタによって扱うフィラメントの幅が異なり、Ender-3 の場合、フィラメント
の直径が 1.75mm のものを使うので購⼊するときは 1.75mm のものを購⼊してください。
私は加熱時の負担を考えて PLA を中⼼に使っています。ちなみにだいたい 1kg で2000 円です。*7 先ほど紹介したプリンタ本体の価格と合計すると約 2.5 万円で必要なものがすべて購⼊できることになります。

2.5

プリントしてみよう

3D プリンタとフィラメントが届いて、説明書を⾒ながら組み⽴てたらいよいよプリントです。3D プリンタ本体に同梱された SD カードにはテストプリント⽤のデータが⼊っているので、それを使えばプリント⾃体はできるのですが、⾃分がプリントしたいと思う
ようなものを⽤意してプリントするまでにはいくつかの⼿順があるので、その⼿順を紹介します。

Thingiverse からプリントしたい 3D モデルをダウンロードする
3D プリンタがどんなに素晴らしいものであっても、プリントしたいと思うものがなけ
れば無意味です。毎年お正⽉の年賀状のときだけ稼働する 2D プリンタと同じです。
⾃分でプリントしたいものをデザインできる能⼒や作るモチベーションがある⼈はよいのですが、何かを作る前にまずは便利なものやかっこいいものをプリントしてみたいですよね。
そんな時は Thingiverse というサイトが使えます。このサイトは⼀⾔でいえば 3D プ
リント版 Github のようなもので、みんなが⾃分で作った 3D データを公開しており、そこからデータをダウンロードしてプリントしたり、データを修正して再公開する Remix
を⾏ったりできます。Thingiverse の Popular ページ*8 にアクセスすると⼈気の作品がで*6 プリントすると⽊⽬のような質感になる wood フィラメントや、スマホケースに使われている弾性のある

TPU フィラメント、透明フィラメントなどもあります。
3D プ リ ン タ ー ⽤ ⾼ 強 度 PLA 樹 脂 材 料 フ ィ ラ メ ン ト
https://www.amazon.co.jp/dp/B07539P1JL/*8 Thingiverse Popular ページ：https://www.thingiverse.com/explore/popular/*7 HICTOP

23

第 2 章 2.5 万円で買える 3D プリンタのススメてくるので、これを⾒ながら⾃分がプリントしてみたいものを探して⾒るとよいでしょう。*9
私がオススメするプリントデータはエンジニアなら誰でも１つは持っている Raspberry

Pi のケースです。ラズパイは本体が安いので、ケースを買おうとすると値段が相対的に⾼く⾒えてしまい購⼊していない⼈も多いのではないでしょうか。3D プリンタなら電気代を加味しても原価 100 円以下でプリント可能です。
プリント⼿順を理解するために今回はこのケースをプリントする場合の⼿順を紹介します。

Thingiverse にはさまざまな種類のラズパイ⽤ケースがありますが、今回は Compact
Raspberry pi B+ case derivative*10 をプリントしてみましょう。URL にアクセスした
あと、「Thing Files」というリンクをクリックすると、3D モデルが記録されている STL
ファイルのリストが表⽰されるのでここから STL ファイルをダウンロードします。

Cura のインストール
Thingiverse で 3D プリントするデータを⼿に⼊れることができたわけですが、このデータには 3D モデルに関するデータしかないためこれだけではプリントできません。

3D データをどの向きで、どれくらいの温度で、どれくらいの密度でプリントするかというプリントに関する情報が必要で、3D モデルをもとにその情報を作成し、gcode と呼ばれる最終的なプリント情報に変換するのが Cura というアプリケーションです。*11

Cura は Ultimaker という 3D プリンタを作っている会社が公開しています。*12 Curaの Web ページから Download for Free と書かれたボタンを押し、Personal Projects を選択、適当に欄を埋めて Download を押しましょう。

Cura の設定
インストールが完了したら早速 Cura を起動して、メニューバーにある「ファイル」の「開く」から Thingiverse でダウンロードしたケースの STL データを読み込みます。
この画⾯では、読み込んだ 3D モデルを、プリンタのどの位置でプリントするのかなどを変更できます。プリンタのベッドと 3D モデルの接地⾯が多い⽅がプリントが安定する
ので、プリントしやすい位置に移動と回転させると次の画像のようになります。

*9 もちろん作品を利⽤する際はライセンスを確認しておきましょう。作品ページに表⽰されているので簡単に確認することができます。
*10 Compact Raspberry pi B+ case derivative https://www.thingiverse.com/thing:400624*11 3D データやプリンタ情報をもとに gcode を出⼒するアプリケーションをスライサーといい、Cura 以外にも Slic3r や Simplify3D などがあります。
*12 Cura Web ページ：https://ultimaker.com/en/products/ultimaker-cura-software24

2.5 プリントしてみよう▲図 2.3ケースは表⾯と裏⾯の 2 つで構成されているので 2 回にわけてプリントします

プリント位置が定まったら、プリント設定の確認です。フィラメントの加熱温度やベッドの温度、印刷速度は素材やプリントデータの複雑さ、プリンタの種類によって最適値が異なります。*13 ⼀度安定してプリントできる設定が⾒つかればそれ以降はあまり変更しな
くてもよくなるのですが、はじめてプリントする場合、どの値が最適解なのかわかりづら
いので、数をこなして少しづつ調整していくのがよいでしょう。
設定値が決まったら MicroSD カードに gcode を書き込みます。右下の保存ボタンから書き込みを⾏い、3D プリンタに MicroSD カードを刺せば印刷準備完了です。

プリント前ベッドレベルチェック
さぁ印刷！ と⾔いたいところですが、やっておかないといけないことがまだあります。

3D プリンタのベッドのレベリングです。簡単にいうと、フィラメントを出⼒するノズルの先端からまでのベッドの距離を紙⼀枚通る距離で⽔平にする⼿続きなんですが、これは印刷品質に直結するので特に時間がかかる⼤きなものを印刷する時は失敗するリスクを減らすために⼊念にチェックしておくことをオススメします。Ender-3 の場合、ベッド底⾯に 4 箇所あるハンドルを回すとスプリングの圧⼒が変わってベッドの傾きが変わるようになっているので、この４箇所のハンドルを少しづつ回しながら紙をノズルとベッドの間に差し込んでベッドのどの位置でも同じ圧⼒になるようにしていきます。

*13 同じ種類のプリンタでもタイミングベルトの貼り⽅や、XYZ 軸の状態によって変わってくるので真にベストな設定は各⼈が⾒つける必要があります25

第 2 章 2.5 万円で買える 3D プリンタのススメ▲図 2.4スプリングの圧⼒を微妙に変えながらベッドを⽔平にしていきます。慣れるとすぐできます。

プリント開始プリントボタンを押したら我々にできることはほとんどなくて成功を祈って⾒守るか、
失敗を確認して中⽌ボタンを押すかの 2 択だけです。プリント開始ボタンを押すと同時に完成予定時間まで外出するのもよいですが、プリントの成功可否は最初の⼀層⽬で決まる
ことが多いので、⼀層⽬が正しくでているかは⽬視で確認しておくとよいです。

26

2.5 プリントしてみよう▲図 2.5

きれいにできることを祈りながら 1 層 1 層積まれていくのを⾒守りましょう

プリント終了フィラメントの素材や、造形物の形状、ベッドの素材によって状況は変わってくるのですが、PLA はプリントが終わってすぐの状態だと剥がれづらいので、簡単にとれそうにない場合はいったん冷えるまで待つと剥がしやすくなるでしょう。今回のプリントデータには印刷しやすくするためのサポートがついているのでそれを剥がせば印刷完了です。

▲図 2.6

出⼒できたラズパイケース

27

第 2 章 2.5 万円で買える 3D プリンタのススメ

2.6

おわりに

3D プリンタがあれば、デジタルの世界にあるものをリアルに持ってくることができます。もちろん 2.5 万円で買えるプリンタではまだまだ限界を感じることも多いですが、ケースや⼩さな部品などは実⽤的なレベルで印刷できます。数年後にはきっと今のものよりさらに使いやすいプリンタが開発されて、より⼤きなものや複雑なものを誰もが簡単にプリントできる時代がやってきそうですが、そうなる前にプリンタを購⼊して、他の⼈よりも早く 3D プリント体験をしてみるのはいかがでしょうか。

28

第3章

Airtest を⽤いた Unity アプリの⾃動実機テストDaisuke TAKAI

3.1

はじめに

アプリのテストを⾃動で⾏いたいと思ったことはありませんか？
そこで、この章では Unity で開発されたスマートフォン向けアプリの実機テストについて書きたいと思います。Airtest というフレームワークを利⽤しますが、Unity 製ス
マートフォン向けアプリの実機テストができるのは Android のみとなっています。（執筆時点）

Airtest とはAirtest*1 とは、ゲームやアプリのための UI ⾃動テストフレームワークです。クロス
プラットフォームの⾃動テストフレームワークで Windows や Android、iOS でアプリの⾃動テストができます。Unity などで開発したアプリもテストできるフレームワーク

Poco*2 が提供されています。今回はこの Poco も⼀緒に使っていきます。

Airtest の仕組み
Airtest は Android の場合は ADB で接続しています。タップなどの操作は ADB を介して⾏われます。また、ADB とは別に RPC で内部メソッドを呼び出しており、Unity
シーンの Hierarchy 情報などをダンプして Airtest IDE に送っています。この情報を使い、テストを簡単に記述できるような仕組みがあります。
*1 https://github.com/AirtestProject/Airtest*2 https://github.com/AirtestProject/Poco29

第 3 章 Airtest を⽤いた Unity アプリの⾃動実機テスト

さらに OpenCV も利⽤することができ、画像が正しく表⽰されているかの判定などに⽤いることが可能です。

3.2

導⼊⽅法

導⼊⽅法について説明します。Unity プロジェクトへの導⼊⽅法は、公式ドキュメン
ト*3 にもありますので、あわせてお読みください。Airtest を使うためには PocoManager
を GameObject にアタッチして Unity シーン上に配置しておく必要があります。
まず、Poco*4 を clone します。clone したプロジェクト内の Unity3D フォルダを Unity
プロジェクトにコピーします。NGUI を使っているのであれば Unity3D/ngui のみを残し、uGUI を使っているのであれば Unity3D/ugui を残してください。PocoManager.cs
は mainCamera などにアタッチするとよいと書いてありますが、結局は全シーンでテストを⾏うため、常にシーン上に配置されるように永続化するためのクラスを作成しました。
▼リスト 3.1

PocoManager 永続化のためのクラス

1: using UnityEngine;2:
3: /// <summary>
4: /// Poco を永続化するための GameObject
5: /// </summary>
6: public class PocoObject : MonoBehaviour
7: {8:
static PocoObject instance;
9:10:
public static PocoObject Instance
11:
{
12:
get
13:
{
14:
if (instance == null)
15:
{
16:
instance = new GameObject(typeof(PocoObject).Name)
17:.AddComponent<PocoObject>();18:
instance.gameObject.AddComponent<PocoManager>();19:
DontDestroyOnLoad(instance.gameObject);
20:
}
21:
22:
return instance;
23:
}
24:
}
25:
26:
public void Initialize()
27:
{
28:
}
29: }

アプリの最初の⽅でこのクラスの Initialize() を呼び出せば、シーン上に永続化された形で配置されます。
ハマるポイントとして Transform の親の下に RectTransform の⼦供がいるシーン構造*3 https://poco.readthedocs.io/en/latest/source/doc/integration.html*4 https://github.com/AirtestProject/Poco-SDK30

3.3 テストの書き⽅

だと Airtest 実⾏時にエラーになります。開発の進んだ Unity プロジェクトに Airtest を導⼊する場合は気をつけてください。

3.3

テストの書き⽅テストは Python で書くことができます。図 3.1 はテストを書くための IDE*5 です。これを利⽤すれば簡単にテストを作成することができます。

▲図 3.1 Airtest IDE の画⾯

Android を接続し、実際にアプリを起動しながらテストを書くことで Unity の Hierarchy 構造を取得し、対象の GameObject に対しての操作をテストとして書けます。
Hierarchy の構造は図 3.2 のように表⽰されます。

*5 http://airtest.netease.com/index.html

31

第 3 章 Airtest を⽤いた Unity アプリの⾃動実機テスト▲図 3.2 Airtest IDE 上の Hierarchy 情報表⽰の例

さらに Hierarchy の構造だけでなく、実機画⾯も IDE 上にプレビューされます。
「タップする」などのアクションを図 3.1 の左下にあるような⼀覧から選び、実機プレビュー上
で「ボタン」の GameObject を選択すれば「ボタンをタップする」というテストコード
を⾃動⽣成することもできます。
テストコードの⼀例を載せておきます。if ⽂などのコード以外は前述したように⾃動的に⽣成することができます。タップ操作などは⾃動⽣成し、条件に合わせて if ⽂や for ⽂を追加する⽅法が楽だと思います。
▼リスト 3.2

テストスクリプトの⼀例# タップするだけ
poco(’Button’).click()# 親⼦関係がある場合
poco(’Window’).child(’Button’).click()# 表⽰されているかチェック
if (poco(’Button’).exists()):
poco(’Button’).click()32

3.4 Jenkins との連携

3.4 Jenkins との連携
やはりテストの実⾏も⾃動化したいので、Jenkins から叩けるようにしました。上流のプロジェクトでアプリをビルドし、そのバイナリを Airtest を⾛らせるジョブに成果物としてコピーしています。テストスクリプトは専⽤のリポジトリを作り、ジョブ実⾏時に clone して最新の状態にしています。今回はゲームのチュートリアルのテストを想定して、テストのファイル名を tutorial.air にしています。ADB を使うことで、インストールから実⾏まで Jenkins 上から操作できます。
▼リスト 3.3

Jenkins で⾛らせるスクリプト

1: #!/bin/bash
2:
3: # ADB からインストール
4: adb install ${WORKSPACE}/*.apk
5:
6: # コピーした成果物、前回のログなどを消している
7: git clean -fd
8:
9: # テストを実⾏するアプリのアクティビティ名を指定して、アプリを起動
10: adb shell am start -n "${ANDROID_BUNDLE_IDENTIFIER}/アクティビティ名"11:
12: # airtest の実⾏
13: airtest run "${WORKSPACE}/tests/tutorial.air" --device Android:/// --log
14: RET=$?
15: # レポートの⽣成
16: airtest report "${WORKSPACE}/tests/tutorial.air"
17:
18: # アプリ終了
19: adb shell am force-stop "${ANDROID_BUNDLE_IDENTIFIER}"
20: ＃ アプリのアンインストール
21: adb uninstall ${ANDROID_BUNDLE_IDENTIFIER}
22:
23: exit $RET

3.5

おわりに

Airtest というフレームワークを使い、Android 実機上で⾃動テストをすることができました。テストシナリオを⽐較的簡単に作成できる Airtest IDE を紹介しました。さらに、ADB を利⽤して Jenkins からテストを実⾏することもできました。

iOS もネイティブアプリはテスト実⾏できるようになっているようです。今後、Unity
製アプリの iOS 実機テストもできるようになるとよいなと思っています。

33

第4章

Rider + Unity で Roslyn
Analyzers を使う
Yoshihiro KASHIMA

4.1

はじめに

3 年遅れ （CTP から計算すると 4 年遅れ）で Roslyn Analyzers をやっています。
筆者の開発する環境では、開発者全員が Windows ではなく、Mac が 8 割で Windows
が 2 割という環境です。Mac については、Visual Studio for Mac を使っている⼈がほとんどおらず、JetBrains Rider のユーザーの⽅が多い状態です。そのため Unity で Roslyn

Analyzers を使おう、と思っても IDE 環境の違いからコーディング規約、実装スタイル
が⼈によって異なるという形にしかできませんでした。
しかし 2018 年になり、JetBrains Rider の Roslyn Analyzers 対応*1 、Visual Studio

Code への実装*2 が進んでいるなど、Visual Studio 以外の環境でも Roslyn Analyzers を使える環境が整いつつあります。さらに、JetBrains Rider では Visual Studio の Roslyn

Analyzers に関する設定ファイルを読み込むことも可能になっています。これらの変更によって、OS や IDE の差異の影響を受けない、⼀元的な規則に基づいて Roslyn Analyzers
を導⼊することができるようになっています。
本稿では Visual Studio と Rider のように複数の IDE の共存を図りつつ、 Unity 環境
で Roslyn Analyzers を導⼊する⽅法について紹介していきます。

*1 https://blog.jetbrains.com/dotnet/2018/03/22/roslyn-analyzer-support-rider-2018-1-eap/*2 https://github.com/OmniSharp/omnisharp-roslyn/pull/1076

34

4.2 Roslyn Analyzers について

4.2 Roslyn Analyzers についてRoslyn Analyzers は、.NET コンパイラプラットフォームである Roslyn のコード解析
部分を API として提供しているものです。主に構⽂⽊を解析する Analyzer と、エラーに対して修正処理を⾏う CodeFix Provider の 2 つが機能として提供されています。

2 つを組み合わせることで、Microsoft が規定していない独⾃のルールセットを定義し、さらに違反したコードの修正機能を提供することができます。
たとえば、コーディングルールとして string.Empty、もしくは""の強制といったことができます。
さらに、CodeFix Provider の活⽤メインで利⽤するケースもあります。たとえば、

DataContract 属性のついたクラスについて、すべてのメンバーに DataMember 属性をつけてほしいという場合があるかと思います。そのような時に、DataContract 属性があったら DataMember 属性をつけるというアナライザーと CodeFix を作成しておき、次のようなコードを書きます。
▼リスト 4.1

DataMember 付与前のコード[DataContract]public class TestData{
public string Name { get; set; }public int Age { get; set; }}DataMember を付与していないため、TestData の宣⾔部分でアナライザー違反が出るように作っておきます。この違反に対する CodeFix を⾏うことで、リスト 4.2 のコードに変換できます。
▼リスト 4.2

DataMember 付与後のコード[DataContract]public class TestData{[DataMember]public string Name { get; set; }[DataMember]public int Age { get; set; }}若⼲わかりづらい例になってしまいましたが、クラスに属性を付与して CodeFix を実⾏して⽬的のコードを得る、という使い⽅も可能となっています。そのため Roslyn

Analyzers は独⾃のコード解析と CodeFix、またそれらを⽤いた開発効率の向上を図るための機能となっています。

35

第 4 章 Rider + Unity で Roslyn Analyzers を使う

導⼊の前提について

4.3

どんな Mac でも OK！ というわけではないので、いくつか前提条件を書いておきます。

• Mac OSX
– Mono 5.8.0.0 以上 （可能であれば 5.14 以降を推奨）• Unity• Rider 2018.2 以上
Rider 以外の下限については正確には把握できていません。Mono についてはもう少し低いバージョンでも可能かもしれません。
（とはいえ Mono は 5.6 がスキップされていて、5.4.1.6 では動作しなかったことから 5.8.0.0 がいったんベースになりそうです。）Unity
は 2018 年 9 ⽉現在でサポートされているバージョンなら可能です。5.6.x 以降であれば問題なく動きます。

Rider については、2018.1 で Roslyn Analyzers に対応しているのですが、他 PC 環境との設定の共有が⾮常に難しく、Windows との互換性は皆無なため、2018.2 以上を推奨します。個⼈環境であれば 2018.1 でも実⾏可能です。

Windows については Visual Studio 2015 が動く環境であれば問題ありません。

4.4 Unity の⾃動⽣成プロジェクトファイルを編集する
Roslyn Analyzers を任意のプロジェクトで実⾏するには、そのプロジェクトの csproj
ファイルに Analyzer の DLL を追加する必要があります。Unity プロジェクトでは、Assembly-CSharp.csproj を修正することになるのですが、若⼲問題となるのが.csproj
ファイルが⾃動で⽣成されるという点です。
⾃動⽣成されるということは、⼀回⼿で書き換えても再度⽣成すると変更した内容が失われるということを⽰しています。
この点について、Visual Studio では、Visual Studio Tools for Unity（以降 VSTU）にある Project File Generation*3 というイベントを提供しています。ProjectFilesGener

ator.ProjectFileGenerationイベントに紐づけたメソッドについて、Unity が.csproj
ファイルを⽣成するときに呼び出すようにする、という仕組みです。
しかしこの仕組みは全員が Visual Studio を使っている環境でないといけません。今
回のように Rider を⽤いるユーザーがいるケースでは VSTU だけの解決法は利⽤できま
せん。

IDE 間の差異をなくすため、Rider 固有の解決⽅法ではなく、Unity プロジェクトを扱える IDE であればどれを使っても解決できる⽅法を利⽤していきます。
*3 https://docs.microsoft.com/en-us/visualstudio/cross-platform/

customize-project-files-created-by-vstu?view=vs-201736

4.4 Unity の⾃動⽣成プロジェクトファイルを編集する

Unity では、アセットのインポートパイプラインに⼲渉する⼿段として AssetPostpro
cessorを提供しています。この中に、csproj ファイルの⽣成タイミングで任意のメソッ
ドを呼び出す処理が実装されています。
今回使う CallOnGeneratedCSProjectFiles*4 を書き出してみると次のとおりです。
▼リスト 4.3

Unity 側から csproj ⽣成時にユーザーコードを呼び出すメソッド

static internal void CallOnGeneratedCSProjectFiles(){object[] args = {};string methodName = "OnGeneratedCSProjectFiles";foreach (var method in AllPostProcessorMethodsNamed(methodName)){method.Invoke(null, args);}}

AllPostProcessorMethodsNamedは、AssetPostprocessorを継承するすべてのクラスから、引数で指定した名前で定義されたメソッドの⼀覧を取得しています。取得したメソッド⼀覧を foreach ですべて呼び出すというだけの簡単な仕組みです。ただしドキュメ
ント化されていないため*5 、実装するにあたり MethodInfo.Invokeの引数を頼りに実装する必要があります。

OnGeneratedCSProjectFiles を最低限実装した結果が次のとおりです。
▼リスト 4.4

csproj ⽣成イベントをハンドルするサンプル

public class AnalyzersPostprocessor : AssetPostprocessor{
public static void OnGeneratedCSProjectFiles(){Debug.Log("AnalyzersPostprocessor Called!")}}この AnalyzersPostprocessor をエディタ拡張スクリプトのフォルダに追加します。

Asset 以下にある任意の Editor フォルダに追加すればよいです。
ここまでの内容で csproj に⼲渉する準備が整ったので、実際に Roslyn Analyzers を追加していきます。

Roslyn Analyzers を追加する
まず、前提として Roslyn Analyzers の DLL ファイルを⼿元に⽤意しておく必要があります。DLL についてはアナライザーの GitHub リポジトリから、または.nuget フォル*4 https://github.com/Unity-Technologies/UnityCsReference/blob/2018.2/Editor/Mono/

AssetPostprocessor.cs#L96-L105*5 Visual Studio の仕様変更で⽣成した csproj が読み込めないときに、ユーザーが緊急回避できるようにするための実装なので基本は不要

37

第 4 章 Rider + Unity で Roslyn Analyzers を使う

ダを探索することで⼊⼿できます。NuGet から.nupkg を⼊⼿しても直接解凍できないので注意してください。
追加したいアナライザーの DLL ファイルですが、分かりやすい位置にまとめて⼊れておきます。今回は csproj と同じ階層に"Analyzers"というフォルダを作り、その中に⼊れておくことにします。ディレクトリ構造について⼀部抜粋すると次のような形です。

*├─ Analyzers├─ Assets├─ ProjectSettings│└─ Assembly-CSharp.csproj

ファイルの準備ができたら実際に csproj ファイルを編集していきます。今後のコードについては先ほどの AnalyzersPostprocessorに記述します。

csproj については中⾝は XML のため、編集しやすいように System.Xml.Linq.XDoc
umentに読み込んで編集します。
▼リスト 4.5

AssetPostprocessor で csproj ファイルの⼀覧を取得するように変更したもの

public class AnalyzersPostprocessor : AssetPostprocessor{
public static void OnGeneratedCSProjectFiles(){var currentDir = Directory.GetCurrentDirectory();
var projectFiles = Directory.GetFiles(currentDir, "*.csproj");
foreach (var file in projectFiles){
var document = XDocument.Load(file);// ToDo : 編集するメソッドを呼び出す// AddAnalyzerReference(//
document.Root, document.Root.Name.NamespaceName);}}}

続いて、Roslyn Analyzers を csproj に追加するメソッドを作成します。ItemGroup の要素に Analyzer という要素を追加することで追加することができます。Analyzer の要
素は Include 属性で DLL のパスを指定する必要があります。
▼リスト 4.6

Roslyn Analyzer のライブラリを追加するメソッド

private static readonly string AnalyzerDirectoryPath = "Analyzers";
private static void AddAnalyzerReference(XElement projectRoot, XNamespace xmlns){
var itemGroup = new XElement(xmlns + "ItemGroup");var currentDir = Directory.GetCurrentDirectory();
var analyzerPath = Path.Combine(currentDir, AnalyzerDirectoryPath);var files = Directory.GetFiles(
analyzerPath, "*.dll", SearchOption.AllDirectories);
foreach (var analyzer in files)

384.4 Unity の⾃動⽣成プロジェクトファイルを編集する{
var analyzerElement = new XElement(xmlns + "Analyzer");analyzerElement.Add(new XAttribute("Include", analyzer));
itemGroup.Add(analyzerElement);}
projectRoot.Add(itemGroup);}

先ほど作成した Analyzer フォルダの⼦孫フォルダ内に存在するすべての.dll ファイルをアナライザーとして追加しています。フォルダ構成に応じてコードは適宜変更してください。
ここまで記述できたら Unity 側の Asset メニューにある"Open C# Project"を実⾏します。csproj ファイルを再⽣成することで Analyzer が追加されるようになるはずです。
追加できたらアナライザーの設定ファイルも読み込むようにしていきます。

規則セットと AdditionalFiles の読み込み
Roslyn Analyzers でも、通常の C#⾔語規則同様に、.ruleset ファイルで適⽤するルールを設定することができます。.ruleset の内容については詳しくは触れませんが、Visual

Studio であれば追加のルールセットの作成で作成ができるため、まず Visual Studio で作成を⾏い、その後調整を⾏っていく形が⼿軽に作成できるかと思います。中⾝については XML のため、⼀度作れば編集は容易です。
追加にあたっては、PropertyGroup に CodeAnalysisRuleSet として追加する必要があります。追加する PropertyGroup はプロジェクト全体の設定ではなく、ソリューション
構成、プラットフォームごとの PropertyGroup に追加する必要があります。ソリューショ
ン構成の PropertyGroup すべてに追加する必要があるので若⼲⼿間ですが、Condition
属性があるかどうかで判定が可能なため追加処理は難しくありません。なお csproj の完
全な構成については把握していないため、次のコードについては Visual Studio の出⼒に基づいて追加するようにしています。そのため不要な属性も追加している可能性があります。
▼リスト 4.7

ルールセットファイルを追加する処理

private static void AddRuleSetFiles(XElement rootElement, XNamespace xmlns){
var itemGroup = new XElement(xmlns + "ItemGroup");var currentDir = Directory.GetCurrentDirectory();
var analyzerPath = Path.Combine(currentDir, AnalyzerDirectoryPath);var files = Directory.GetFiles(
analyzerPath, "*.ruleset", SearchOption.AllDirectories);
var attrVal = "’$(Configuration)|$(Platform)’";var propGroups = rootElement.Elements().Where(e =>e.Attribute("Condition") != null &&e.Attribute("Condition").Value.ToString().Contains(attrVal)).ToArray();if (files.Length == 0){39

第 4 章 Rider + Unity で Roslyn Analyzers を使う

return;}foreach (var ruleset in files){
var relativePath = ruleset.Replace(currentDir, "").Substring(1);var rulesetReference = new XElement(xmlns + "None");rulesetReference.Add(new XAttribute("Include", relativePath));var ruleElement = new XElement(xmlns + "CodeAnalysisRuleSet");ruleElement.SetValue(relativePath);
foreach (var group in propGroups){
group.Add(ruleElement);}
itemGroup.Add(rulesetReference);}
projectContentElement.Add(itemGroup);}

ビルドアクションの None で ruleset ファイルを追加した上で、CodeAnalysisRuleSetも追加しています。このとき、CodeAnalysisRuleSet については正確な相対パスを⼊⼒する必要があります。絶対パスの⼊⼒では追加できないため注意が必要です。
続いて Roslyn Analyzers の各ライブラリが利⽤する設定ファイルを読み込みます。ビ
ルドアクションのひとつである"Additional Files"を Roslyn Analyzers は受け取ることができるので、Additional Files で設定を記述することがスタンダードになっています。

csproj 上での Additional Files の扱いについては Roslyn Analyzers とほとんど⼀緒です。追加する⼿法についてもほぼ同じにすることができます。設定ファイルについては
フォーマットが決まっていませんが、いったん StyleCop.Analyzers に則って json ファイルであると仮定してコードを書くと次のとおりとなります。
▼リスト 4.8

オプションとなる AdditionalFiles を追加する処理

private static void AddAnalyzerOptions(XElement projectRoot, XNamespace xmlns){
var itemGroup = new XElement(xmlns + "ItemGroup");var currentDir = Directory.GetCurrentDirectory();
var analyzerPath = Path.Combine(currentDir, AnalyzerDirectoryPath);var files = Directory.GetFiles(
analyzerPath, "*.json", SearchOption.AllDirectories);
foreach (var additionalFile in files){
var optionElement = new XElement(xmlns + "AdditionalFiles");
optionElement.Add(new XAttribute("Include", additionalFile));itemGroup.Add(optionElement);}
projectRoot.Add(itemGroup);}

これでほぼすべてのアナライザーで必要なファイルを読み込むようになります。

40

4.5 Roslyn Analyzers の制限

4.5 Roslyn Analyzers の制限
ここまでの話で Roslyn Analyzers を Unity Project に追加することができました。めでたしめでたしというところなのですが、Unity ではアセットストアからパッケージを落としてきたくなります。ちょっとユニティちゃんモデルを使おうと思って⼊れることはよくある話かと思います。
しかし、このようにしてアセットストアからパッケージを⼊れると⾃分のプロジェクトに別の⼈のコードが⼊ってしまいます。ユニティちゃんモデルでも、Scripts 以下にモデ
ルをキー⼊⼒で操作するコントローラや、カメラを制御する C#ファイルが含まれています。これらは DLL ではなく.cs ファイルです。当然 csproj の中の 1 ファイルとして認識され、ビルド時には⼀緒にビルドされます。
何が問題かというと、この別の⼈の書いたコードが Roslyn Analyzers の解析対象とされてしまうことです。別の⼈の書いた C#なのですから、当然⾃分たちのコーディング
規約など知るはずもありません。Analyzer は⼤量に他の⼈のコードの規約違反を指摘するでしょう。もし CI テストとして Roslyn Analyzers を導⼊したい、と考えても他⼈のファイルがエラーとして⼤量に記録されてしまっては導⼊することができません。

Roslyn Analyzers では、コードがあるということはユーザーが管理すべきソースコー
ドとして認識します。管理してるコードについては規約に沿っているはずであるという⽅
針と、C#では NuGet を⽤いて DLL を追加する⽅法が主流となってきた時期であったため、特定のファイルを制限しようという動きがそもそもなかったためです。
上記については Roslyn の Issue*6 でも話題となっており、改善しようという流れ⾃体はあるのですが、具体的な仕様までは決まってはいません。公式なサポートを待つのもひ
とつの⼿なのですが、すぐに解決する話とも思えません。
⾃分のコードは Roslyn Analyzers で解析していきたいけど、アセットストアから持ってきたコードは解析対象外としたい！ ということで、Roslyn Analyzers の解析範囲を何とか制限する⽅法について書いていきます。

4.6

⾃動⽣成ファイルとしてマークして解析範囲外にする。

最初期の Roslyn は、本当にすべてのファイルを解析対象としていました。ですがこれだと Visual Studio が⽣成しているファイルも解析対象になってしまいます。⽣成されるファイルはビルドタイミングで再⽣成をするため直したところで再度違反するようになってしまいます。
そのため、Roslyn Analyzers では、⼀定の規則に基づいて⾃動⽣成されたファイルを認識しており、⾃動⽣成として認識したものは Roslyn 側で解析しないような仕組みが導⼊*6 https://github.com/dotnet/roslyn/issues/3705

41

第 4 章 Rider + Unity で Roslyn Analyzers を使うされています。この解析の実装は Roslyn.Utilities.GeneratedCodeUtilities*7 にまとまっています。
上記のファイルに記述されている条件を書き出すと、次のとおりとなります。

• ファイル名の条件– ファイル名が次の⽂字列から始まる（⼤⽂字⼩⽂字問わず）∗ TemporaryGeneratedFile_– 拡張⼦を含まないファイルの末尾が次のいずれかである。
∗ .designer∗ .generated∗ .g∗ .g.i• ファイル内容の条件– SyntaxTree に含まれる最初のコメントの SyntaxTrivia が次のどちらかの⽂⾔を含んでいる∗ <autogenerated∗ <auto-generated
これらの条件のいずれかに当てはまるコードについては⾃動⽣成したファイルとして認識され、Roslyn 側で除外されます。
とはいえ、ファイル名を変えるのはどこに依存しているか分かったものではないです。
特に Unity ではファイル名を変更することで Unity 側の参照が切れる、といった可能性も考えられます。そのように考えた場合、ファイル名の変更を⾏うよりもファイル内容を変更する⽅がリスクを低減できます。
⼀個⼀個ファイルの先頭にコメントをつけるのは現実的ではないので C#で簡単なロジックを記述して対処します。（ここは AnalyzersPostprocessor とは関係ないです。）▼リスト 4.9

auto-generated をファイルに付与する処理

private string[] targetDir = { "" };private void WriteAutoGenerated(string path, bool isRemove){
var autoGenTag = "// <auto-generated />";
var culture = StringComparison.CurrentCultureIgnoreCase;var files = Directory.GetFiles(path, "*.cs", SearchOption.AllDirectories).Where(x => targetDir.Any(y => x.Contains(y)));foreach (var file in files){
var isExists = false;using (var sr = new StreamReader(file)){
var firstLine = sr.ReadLine();isExists = firstLine.Contains("<auto-generate", culture)|| firstLine.Contains("<autogenerate", culture);*7 https://github.com/dotnet/roslyn/blob/master/src/Compilers/Core/Portable/

InternalUtilities/GeneratedCodeUtilities.cs

42

4.7 アナライザー側でファイル判定を⾏う処理を追加する。

}
if (isExists && isRemove){
var lines = File.ReadAllLines(file);
File.WriteAllLines(file, lines.Skip(1).ToArray());}
else if (!isExists && !isRemove){
var lines = File.ReadAllText(file);lines = string.Concat(autoGenTag, Environment.NewLine, lines);
File.WriteAllText(file, lines);}}}.NET Core で作れば OS 間の差異も無視できるので問題ありません。対象ファイルの判定については適宜書き直す必要がありますが、これで auto-generated の付与、および削除をコマンド 1 回で⾏えます。1 個ずつ⾜した場合は負債になりかねませんが、このレベルであれば低いメンテナンスコストを維持できると考えます。

4.7

アナライザー側でファイル判定を⾏う処理を追加する。

auto-generated をコードの最初に記述することで解析対象にならないことについて紹介しましたが、どうしても auto-generated を付与するのを避けたいという場合があると思います。そのようなケースではこの節で紹介するアナライザーそのものを書き換え、ファイル判定するロジックを追加する⼿法を取らざるを得ません。
処理を追加する場合、コードを書き換える必要があるためアナライザーがオープンソー
スである必要があります。またメンテナンスコストについても auto-generated を付与す
るよりはかかるため、アナライザーを複数導⼊したいという場合は注意が必要です。
では実際にファイルを判定する処理について記述していきます。ポイントとなるのは次の 3 点です。

• DiagnosticAnalyzerへの介⼊• 解析中のファイルパスの取得• パスの判定処理
上から⾒ていくと、DiagnosticAnalyserへの介⼊については選択肢がほぼないた
め、DiagnosticAnalyzer.Initializeを上書きすることで対処します。というのも、DiagnosticAnalyser でオーバーライドできるのは SupportedDiagnosticsと Initiali
zeしかありません。SupportedDiagnostics はアナライザーの ID、解析の内容などの情報が⼊っているだけなので、実際の解析処理を⾏う Initialize に⼿を加えます。
修正の内容としては、引数の AnalysisContextと同⼀のメンバーをもつクラス A を定義し、元の Initialize の引数に A を渡すことで介⼊の余地を作ります。例として、修正を⾏う前の DiagnosticAnalyser のサンプルを次に⽰します。

43

第 4 章 Rider + Unity で Roslyn Analyzers を使う▼リスト 4.10

DiagnosticAnalyzer の実装サンプル[DiagnosticAnalyzer(LanguageNames.CSharp)]
internal class SampleAnalyzer : DiagnosticAnalyzer{
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics=> ImmutableArray.Create(descriptor);
public override void Initialize(AnalysisContext context){
context.ConfigureGeneratedCodeAnalysis(
GeneratedCodeAnalysisFlags.None);context.EnableConcurrentExecution();context.RegisterSyntaxNodeAction(
XmlElementAction, SyntaxKind.XmlElement);}}

上記のコードをクラス A で置き換えた結果が次のコードとなります。
▼リスト 4.11

ファイルパス判定処理を追加できるようにした DiagnosticAnalyzer[DiagnosticAnalyzer(LanguageNames.CSharp)]
internal class SampleAnalyzer : DiagnosticAnalyzer{
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics=> ImmutableArray.Create(descriptor);
public override void Initialize(AnalysisContext context){
this.Initialize(new CapsuledAnalysisContext(context));}
private void Initialize(CapsuledAnalysisContext context){
context.ConfigureGeneratedCodeAnalysis(
GeneratedCodeAnalysisFlags.None);context.EnableConcurrentExecution();context.RegisterSyntaxNodeAction(
XmlElementAction, SyntaxKind.XmlElement);}}

CapsuledAnalysisContext がクラス A の定義となります。この⽅法で実装することで、既存の DiagnosticAnalyzerへの変更が、複数⾏の置換で対応できます。
▼リスト 4.12

置き換え処理の置き換え元

public override void Initialize(AnalysisContext context)▼リスト 4.13

置き換え処理の置き換え先

public override void Initialize(AnalysisContext context){
this.Initialize(new CapsuledAnalysisContext(context));}
private void Initialize(CapsuledAnalysisContext context)44

4.7 アナライザー側でファイル判定を⾏う処理を追加する。

プロジェクト内に含まれるリスト 4.12 をリスト 4.13 で置換することで作業が終わる
ので、再度置換の必要が出ても少しのコストで対応ができます。ただし⼆重置換については注意が必要なため、2 回⽬以降は置換する範囲を絞る必要があります。

CapsuledAnalysisContext の実装については、AnalysisContext と同じメソッドを持っていれば問題ありません。⽅法としては、AnalysisContext と同じメソッド・引数をもつ新しいクラスを作成するか、AnalysisContext を継承したクラスで可能な限りオーバーライドして実装を⾏う⽅法の 2 つが存在します。基本どちらでも問題はないですが、AnalysisContext に定義されているメソッドのすべてがオーバーライド可能ではないた
め、必要なメソッドが abstract・virtual で定義されていない場合は継承の⽅法は取れません。その場合は同名の定義を持ったラッパークラスを作成する必要があります。
パスの取得については別途対応することとして、ひとまず実装を⾏います。CapsuledA

nalysisContextの⼀部を次に⽰します。
▼リスト 4.14

AnalysisContext のラッパークラスの実装

internal class CapsuledAnalysisContext{
public CapsuledAnalysisContext(AnalysisContext c) => this.Context = c;private AnalysisContext Context { get; }public void ConfigureGeneratedCodeAnalysis(
GeneratedCodeAnalysisFlags analysisMode){
this.Context.ConfigureGeneratedCodeAnalysis(analysisMode);}
public void RegisterSyntaxTreeAction(
Action<SyntaxTreeAnalysisContext> action){
this.Context.RegisterSyntaxTreeAction(_ =>{if (AnalyzerPathDetector.IsExcludePath(_.Tree)){return;}action.Invoke(_);});}
public void RegisterCompilationStartAction(
Action<CompilationStartAnalysisContext> action){
this.Context.RegisterCompilationStartAction(_ =>{var interrupted = new CapsuledCompilationStartAnalysisContext(_);
action.Invoke(interrupted);});}}

基本的にはあるメソッドが呼ばれたら AnalysisContext の同じ名前のメソッドを呼び出し、引数に Action が渡されていたらファイルパス判定を先に⾏う処理を追加していま45

第 4 章 Rider + Unity で Roslyn Analyzers を使う

す。このパターンでほとんど実装できますが、RegisterCompilationStartAction については引数を AnalysisContext と同じように扱うメソッドとなるので、別途 Capsuled

CompilationStartAnalysisContextを定義して渡してあげる必要があります。Capsul
edCompilationStartAnalysisContext の実装⽅針は CapsuledAnalysisContext と同じです。
メソッドのオーバーライドが終わったらファイルパスから解析対象を判定する処理を書いていきます。ファイルパスの判定にあたり、HogeHogeAnalysisContextから現在の
コードのファイルパスを取得する必要があります。
取得については、SyntaxTree・ISymbolのどちらかが取れればパスの取得が可能です。
▼リスト 4.15

ファイルパスの判定処理

internal class AnalyzerPathDetector{// Exclude にマッチしているが解析対象としたいファイルのパスの⼀部を指定
private static string[] analyzerIncludePath = { "" };// 解析対象にしないパスの⼀部を指定する
private static string[] analyzerExcludePath = { "" };internal static bool IsExcludePath(SyntaxTree tree){
return IsExcludePath(tree.FilePath);}internal static bool IsExcludePath(ISymbol symbol){
if (symbol.Locations.Length == 0){
return false;}
var path = symbol.Locations.First().SourceTree.FilePath;
return IsExcludePath(path);}
private static bool IsExcludePath(string path, StyleCopSettings settings){
if (analyzerIncludePath.Any(pattern => IsPathMatch(path, pattern))){
return false;}
return analyzerExcludePath.Any(patten => IsPathMatch(path, pattern))}
private static bool IsPathMatch(string path, string searchText){
var localText = System.IO.Path.Combine(sratchText.Split(’\\’, ’/’));
return path.Contains(localText);}}

設定関連については、配列ベタ書きではなく別途ファイルから読み込むようにしてもよ
いかと思います。基本的には解析対象外にするファイルパスの⼀部を指定していく形です。ここにはディレクトリ名などを追加していきます。ただパスの⼀部だと誤検知する
パターンが存在するため、除外されていても解析対象とする設定についても⽤意しています。

IsPathMatchでは、OS ごとのパス区切り⽂字の差異を吸収するため、⼀度パスを分解
46

4.8 まとめして再度結合することで現在の OS にあったパスを取得し直しています。OS のパス区
切り⽂字は.NET Standard では取得できないため、Path.Combine を使って設定しています。
ここまでの実装をうまく組み合わせることで、解析対象外のファイルに存在するルール
違反については表⽰されないようになります。

4.8

まとめ

かなり駆け⾜ですが、ここまで複数の IDE 環境の Unity プロジェクトで Roslyn

Analyzers を導⼊する⼿法について書いてきました。全員が Visual Studio じゃなくて導⼊できなかった！ という⽅がもしいたら試していただけると幸いです。IDE 関係なしに
これまであまり触ってこなかった、という⽅もこれを機に触ってみるのはいかがでしょうか。
今後についてですが、⽂中で⼀瞬触れましたが Roslyn Analyzers の CI テストへの転⽤について考えています。IDE と CI で⽤いる規則が異なるというのは好ましくない状況
ではあるので、.NET Core を⽤いてテストを⾏うだけの仕組みを作れないか検討中です。
こちらについてはまた別の機会に記述出来ればよいなと考えています。⻑くなりましたがここまで読んでいただき本当にありがとうございました。読んでいただいた⽅の何らかの助けになっていれば幸いです。

47

第5章

バーコードリーダーになろう

Daisuke MAKIUCHI / @makki_d
近年、バーコードや QR コード*1 でさまざまな情報を受け渡しする場⾯が増えてきました。専⽤の機械がなくてもスマートフォンで簡単に読み取ることができ、皆さんもよく利⽤しているのではないでしょうか。
そんな便利なスマートフォンも電池切れや家に忘れてしまいがち。そんなとき、機械に頼らずとも⾁眼で読み取ることができればとても便利ですよね。
ただ、QR コードのような⼆次元コードは複雑で、慣れていないと読むのがかなり⼤変です。そこでまずは⽐較的単純なバーコードから読めるようになりましょう。

5.1

バーコードの規格についてひと⼝にバーコードと⾔っても世の中にはたくさんの規格があります。この章では、おそらくもっともよく⾒かけるであろう、EAN（European Article Number）規格のバー
コードを扱います。

EAN の E は European の略ですが、ISO/ECI 15420 として標準化もされている国際規格で、⼀般的な商品を表すバーコードとして世界中で使われています*2 。⽇本国内でもJIS-X-0507 として標準化されていて*3 、特に国コードが⽇本（49 または 45）のもは JAN（Japanese Article Number）と呼ぶこともあります。

EAN には 13 桁のものと 8 桁のものがありますが、今回は 13 桁の EAN-13 を読めるようになりましょう。

*1 QR コードは株式会社デンソーウェーブの登録商標です*2 International Article Number とも呼ばれます*3 JIS 規格は「⽇本⼯業標準調査会」のウェブサイトで閲覧できます。

http://www.jisc.go.jp/app/jis/general/GnrJISSearch.html

48

5.2 バーコードの構造

5.2

バーコードの構造▲図 5.1 EAN-13 バーコード

題材として図 5.1 にバーコードを⽤意しました*4 。まずはこの構造を詳しく⾒ていきます。機械で読み取れなかった場合に備えて、バーコードの下に内容がそのまま書かれていることが多いですが、書かれていなかったり破損している場合のためにバーコード本体を読めなくてはなりません。今回は練習として、答え合わせに利⽤しながら読み進めてみてください。

EAN-13 の共通の固定パターンとして、両側に標準ガードパターン、そして中央に中央
ガードパターンがあります。両端と中央の⻑く書かれている 2 本線がそれぞれのガードパ
ターンです。正確には、標準ガードパターンは「黒・⽩・黒」中央ガードパターンは「⽩・黒・⽩・黒・⽩」のパターンになっています。このガードパターンによって、バー 1 本あたりの幅が分かるようになっています。
中央ガードパターンを挟んで、左半分と右半分にそれぞれ 6 個のシンボルキャラクタが描かれています。１個のシンボルキャラクタはバー 7 本分の幅で、そのまま数値 1 桁を表しています。
ところで EAN-13 は 13 桁なので、これでは 1 桁⾜りません。実は先頭の 1 桁は、左側

6 個のシンボルキャラクタの中に隠されているのです。
それではさっそく、シンボルキャラクタから数値を読み取ってみましょう。

5.3

シンボルキャラクタの読み⽅

表 5.1 がシンボルキャラクタと数値の対応表です。まずはこの表を覚えなくてはなりま
せん。数値 1 つあたり 3 種類のパターンがあって⼤変に⾒えるかもしれませんが、よく⾒るとセット A を⽩黒反転するとセット C に、さらにそれを逆順にするとセット B になります。
*4 出典：https://en.wikipedia.org/wiki/International_Article_Number49

第 5 章 バーコードリーダーになろう▼表 5.1

数字セット

数値

セット A

セット B

セット C

0

3:2:1:1

1:1:2:3

3:2:1:1

1

2:2:2:1

1:2:2:2

2:2:2:12

2:1:2:2

2:2:1:2

2:1:2:2

3

1:4:1:1

1:1:4:1

1:4:1:14

1:1:3:2

2:3:1:1

1:1:3:2

5

1:2:3:1

1:3:2:1

1:2:3:1

6

1:1:1:4

4:1:1:1

1:1:1:4

7

1:3:1:2

2:1:3:1

1:3:1:2

8

1:2:1:3

3:1:2:1

1:2:1:3

9

3:1:1:2

2:1:1:3

3:1:1:2

左半分の 6 個はセット A またはセット B、右半分の 6 個はセット C が使われます。
ガードパターンからだけでは逆さになっているかわからないのですが、逆さにして読んでしまうとこの表に無いパターンが途中で必ず出てくるので判断できます。
さっそく図 5.1 を読んでみましょう。左のガードパターンの次から、⽩ 3 黒 1 ⽩ 1 黒

2 と並んでいるので、セット A の 9 になります。その次は⽩ 1 黒 1 ⽩ 2 黒 3 なのでセッ
ト B の 0 ですね。その調⼦で進めていくと、左側は「A9-B0-B1-A2-A3-B4」になりました。中央ガードパターンを超えて右側に⼊ります。こからはセット C しか出てこないのでもっと簡単です。「1-2-3-4-5-7」になっています。読めましたか？
先頭の 1 桁は左側 6 個のシンボルキャラクタに隠されているのでした。まず左側の 6
個で、セット A・セット B のどちらが使われていたか書き出してみましょう。「A-B-B-

A-A-B」ですね。このパターンを表 5.2 と照らし合わせると「5」になっています。これが先頭の 1 桁です。
こうして無事、「5-901234-123457」の 13 桁を読み取ることができました。

50

5.4 チェックデジットの検証▼表 5.2

先頭桁の導出

数値

左半分の数字セット

0
1
2
3
4
5
6
7
8
9

AAAAAAAABABBAABBABAABBBAABAABBABBAABABBBAAABABABABABBAABBABA

チェックデジットの検証

5.4

EAN-13 では、13 桁のうち最後の 1 桁がチェックデジットになっていて、読み取りミスを検知できるようになっています。
計算⽅法は次のようになっています:1. 先頭の桁はそのまま、2 桁⽬は 3 倍、3 桁⽬はそのまま、4 桁⽬は 3 倍、と交互に係数を掛けながら 12 桁を合計する

2. 1 で合計した値を 10 で割った余り（mod10）を計算する
3. 余りが 0 ならチェックデジットは 0、それ以外なら 10 から余りを引いた値がチェッ
クデジット
この値と最後の 1 桁の値が⼀致していなければ、どこかで読み取りミスがあったことがわかります。
それでは「5901234123457」のチェックデジットを検証してみましょう。まず係数を掛けながら 12 桁を合計します。

5 + 9 × 3 + 0 + 1 × 3 + 2 + 3 × 3 + 4 + 1 × 3 + 2 + 3 × 3 + 4 + 5 × 3 = 83

83mod10 = 3
10 − 3 = 7
チェックデジットは 7 で、最後の 1 桁と⼀致しました。読み取りミスはなかったようです。

51

第 5 章 バーコードリーダーになろう

5.5

おわりに

EAN-13 バーコードから 13 桁の数値を読み取ることができました。⾝の回りのバー
コードも読み取ってみてください。最後に、その数値がどんな意味を持っているかは皆さんへの宿題とします。
これで今⽇からあなたもバーコードリーダー！

バーコードの逆向き判定
表 5.1 の特定のセットに着⽬すると、同じセットの中では逆向きにしたときに重複しないようなパターンが巧妙に選ばれています。たとえば、セット A の 0「3:2:1:1」を逆にした「1:1:2:3」はセット A には含まれていません。
セット B とセット C はお互い逆向きにのパターンになっているため、逆向きに読んだ場合も対応するパターンがテーブルから必ず⾒つかってしまいます。⼀⽅でセット A を逆向きにしたセットはテーブルに存在せず、それが⾒つかったらバーコード⾃体が逆向きだと判断できます。表 5.2 を⾒ると A が含まれないパ
ターンはひとつも無いため、バーコードが逆向きだった場合、必ず A の逆向きのパターンが⾒つかります。
ちなみに、EAN-13 の元になった規格、UPC-A は元々 12 桁で、セット A と
セット C しか使われていませんでした。EAN-13 の策定過程で桁数を増やすとき、セット C を逆にしたセット B が新たに加えられましたが、逆向きかの判定ができなくならないように、注意深く導出テーブルが決められているわけです。
もし⼊れられる情報をさらに増やそうとして、セット A を逆順にしたセットを追加したり、先頭桁の導出テーブルに A が出てこないパターンを加えてしまうと、バーコードが逆向きかどうかの判断できなくなってしまいます。

52

第6章

Unity Timeline Tips 集

Junichi KIKUCHI @jukey17

6.1

はじめに

Unity2017 から正式版がスタートした Timeline 機能は、登場から 1 年あまり経過して⽇本語の解説記事も少しずつ増えてきました。しかし、まだ機能の説明やサンプルコー
ドの紹介などにとどまる内容が多く、そこからさらに踏み込んだ内容の記事はあまり出揃っていません。そこで、この章では私の所属しているプロジェクトが実際にいざ Unity

Timeline を導⼊してみようと検討した際に Unity エンジニア的な観点で開拓していった
内容をいくつか紹介します。主に Unity 公式が⽤意しているサンプルコードプロジェクトの DefaultPlayables を触ったことがある⼈向けの内容になっています。Unity Timelineの導⼊を考えている⽅の参考に少しでもなれば幸いです。
※この章で紹介している機能やコードは Unity2018.3.0b1 で動作確認をしています。
それ以外のバージョンでは動作しないケースがあるかもしれませんのでご了承ください。

6.2 Tips.1 [PlayableAsset] TimelineAsset にキャスト
せずにトラック情報を取り出す
Timeline の機能を使おうとするとまずややこしいのが、Timeline の機能と Playablesの機能です。TimelineAssetの再⽣に使う PlayableDirectorは Timeline ではなくPlayables の機能になるため PlayableDirectorから直接 TimelineAssetや TrackAss
etの情報にアクセスすることができません。
⼀応、Timeline の機能として使う際は PlayableDirector.playableAssetに TimelineAssetを設定しているはずなので、PlayableDirector.playableAssetを Timelin53

第 6 章 Unity Timeline Tips 集

eAssetにダウンキャストすることで Timeline の情報を取り出すことができますが、それをしなくても PlayableAssetのプロパティからトラックの情報にアクセスする⽅法があります。

PlayableAsset.outputsにトラックの情報が格納されているのです▼リスト 6.1

PlayableAsset.outputs と TimelineAsset.GetOutputTracks はほぼ同等

var timelineAsset = playableDirector.playableAsset as TimelineAsset;
foreach (var track in timelineAsset.GetOutputTracks()){
Debug.Log(track);}
foreach (var binding in playableDirector.playableAsset.outputs){// TimelineAsset.GetOutputTracks で列挙した TrackAsset と同じものになる
UnityEngine.Debug.Log(binding.sourceObject);// 上記の TrackAsset の名前
UnityEngine.Debug.Log(binding.streamName);// 上記の TrackAsset に Bind されるオブジェクトの型情報// ※ TrackBindingTypeAttribute で指定されている型
UnityEngine.Debug.Log(binding.outputTargetType);}

TrackAssetから TrackBindingTypeAttributeを直接取得してくるコードはないの
で、PlayableBindingから取り出せる情報のほうが単純に TrackAssetを⾒るよりも詳しかったりします。

Unity の機能を紹介してくれるブログで有名なテラシュールブログさんの Timeline の紹介記事でも簡単に紹介されていますが、これは Track に対して動的にオブジェクトをBind する際に便利です。
▼リスト 6.2

Timeline の名前空間を使わずに動的 Bind

public class DynamicBinder : MonoBehaviour{[SerializeField] PlayableDirector director;// 指定の名前のトラックに任意のオブジェクトを Bind する
public void Bind(string trackName, Object target){
var binding = director.playableAsset.output.FirstOrDefault(o => o.steamName == trackName);if (binding != null){
director.SetGenericBinding(binding.sourceObject, target);}}}

余計な変換を噛まさずに処理できるものは処理していきたいので、こういったものはどんどん活⽤していきましょう。

54

6.3 Tips.2 [PlayableBehaviour] PlayableDirector の参照を取得する

6.3 Tips.2 [PlayableBehaviour] PlayableDirector の参照を取得する
Timeline の機能で独⾃に拡張したトラックに対応した処理（振る舞い）を作るためには、PlayableBehaviourを継承した Mixer や Behaviour といった独⾃クラスも合わせて作らなければなりません。ここは各クリップの情報を受け取ってアレコレ処理をする部
分になるので、シーン上にある情報にもアクセスできるようにしたいです。
対象の機能をシングルトンパターンで実装して Mixer/Behaviour から直接アクセスしに⾏くのもアリですが、PlayableDirectorを通して Playable の機能を使って再⽣制御されているはずなので、Playable の機能を使って上⼿くシーン上の参照が取れるようにしたいです。

PlayableBehaviourには⾃⾝ (ScriptPlayable) の⼊出⼒を制御してくれる Playab
leGraphを取得するためのメソッドが⽤意されています。Timeline の機能の場合この Pl
ayableGraphから取得できる Resolver が実は PlayableDirectorとなるので、そこから参照を取ってくるようにしてみます。
具体的には次のコードで取得が可能です。
PlayableBehaviour.OnPlayableCreate のタイミングで PlayableDirector の参照を

▼リスト 6.3 保持する// サンプル⽤独⾃トラック
public class SampleTrack : TrackAsset{public override Playable CreateTrackMixer(
PlayableGraph graph, GameObject go, int inputCount){// Track 側で独⾃の Mixer ⽣成をしておく
return ScriptPlayable<SampleMixer>.Create(graph, inputCount);}}// SampleTrack の Mixer
public class SampleMixer : PlayableBehaviour{
public override void OnPlayableCreate(Playable playable){// Resolver が PlayableDirector になっている
var director = playable.GetGraph().GetResolver() as PlayableDirector;}}

このようにして⽣成時に⾃⾝の親⽟となる PlayableDirectorの参照を保持しておくことで、ProcessFrameなどの何度も呼び出されるメソッドの中で再⽣中の全体の情報にアクセスするといったことが容易にできるようになります。
たとえば PlayableDirectorをアタッチしている GameObjectに独⾃のコンポーネン
トをセットでアタッチしておく、というルールを作っておけば PlayableDirectorからさらにその独⾃管理コンポーネントを取得して Mixer に参照を持たせるという芸当も可55

第 6 章 Unity Timeline Tips 集

能になります。
▼リスト 6.4

⽣成時に独⾃コンポーネントの参照を保持する// SampleTrack の Mixer
public class SampleMixer : PlayableBehaviour{
TexturePool texturePool;
public override void OnPlayableCreate(Playable playable){
var director = playable.GetGraph().GetResolver() as PlayableDirector;// テクスチャ管理機構を保持しておく
texturePool = director.gameObject.GetComponent<TexturePool>();}public override void ProcessFrame(
Playable playable, FrameData info, object playerData){var sample = playerData as SampleComponent;
var inputCount = playable.GetInputCount();for(var i = 0; i i < inputCount; i++){var weight = playable.GetInputWeight(i);if (Mathf.Approximately(weight, 0)){continue;}// クリップ (Behaviour) に指定されているテクスチャを Bind 対象にセット
var input = (ScriptPlayable<SampleBehaviour>)playable.GetInput(i);var behaviour = input.GetBehaviour();var texture = texturePool.FindTexture(behaviour.TextureName);sample.SetTexture(texture);
break;}}}

上記の例では、クリップの情報を元に独⾃のテクスチャ管理機構からテクスチャをもらってきてそれを Track に Bind されているコンポーネントに対して設定する。といった処理を書いてみました。テクスチャが個別に AssetBundle 化されていたりすると、クリップに直接テクスチャを埋め込むわけにもいかなくなるのでこのようにして外部の管理機構からテクスチャをもらってきて設定するといった流れを取ることになります。

Timeline のトラックやクリップ、ミキサーなどはランタイム動作の際は Timeline の機能の中に埋まってしまっていて Timeline の機能外へのアクセスが中々に⾯倒なことになっているので、このようにしてアクセスできる⼝を作っておくと拡張がしやすくなります。

6.4 Tips.3 [PlayableBehaviour] 各クリップの時間に関する情報を取得する
Mixer を独⾃に拡張していると Mixer の ProcessFrameメソッド内で各クリップの⻑さや、そのクリップ内でどこまで再⽣されたかの再⽣時間などが欲しくなるケースが出てきます。各クリップの⻑さは TimelineClip.durationで取ってこれますが、Timeline566.4 Tips.3 [PlayableBehaviour] 各クリップの時間に関する情報を取得する

Clipは Timeline の機能であるのに対して PlayableBehaviourは Playables の機能であるため ProcessFrameなどの⽤意されているメソッド内では直接 TimelineClipの参照は取ってくることができません。

DefaultPlayables のサンプルコードでも、クリップごとの Behaviour を定義してそこに独⾃パラメータのプロパティを⽣やしておき Mixer 内でその Behaviour の参照を掘り起こして処理しているだけなので、TimelineClip の情報は勿論取ってきていません。
▼リスト 6.5

DefaultPlayables の例[Serializable]
public class SampleBehaviour : PlayableBehaviour{// クリップごとにもつ独⾃パラメータ
public int integerField = 1;}
public class SampleClip : PlayableAsset, ITimelineClipAsset{// DefaultPlayables では Editor 拡張で Behaviour を Inspector から編集できるようにしている// 確かに Playable の機能を使って Mixer 内から直接取り出せるのでそのほうが便利だが…
// そうすると Timeline 機能としてのクリップの情報が取ってこれない（悲）
public SampleBehaviour template = new SampleBehaviour();public override Playable CreatePlayable(PlayableGraph graph, GameObject owner){
return ScriptPlayable<SampleBehaviour>.Create(graph, template);}}public class SampleMixer : PlayableBehaviour{
public override void ProcessFrame(
Playable playable, FrameData info, object playerData){
var inputCount = playable.GetInputCount();
var value = 0.0f;
for(var i = 0; i i < inputCount; i++){// Input から取り出した Behaviour のパラメータを使ってクリップ毎の情報を処理している
var input = (ScriptPlayable<SampleBehaviour>)playable.GetInput(i);var weight = playable.GetInputWeight(i);var behaviour = input.GetBehaviour();value += (behaviour.integerField * weight);}
var sample = playerData as SampleComponent;
sample.SetValue(value);}}

ここで上記サンプルコードの playable.GetInput()の処理をしていることに注⽬しま
しょう。playable.GetInput()して取得した ScriptPlayable<T>がクリップ単位のもの (TimelineClipと 1 対 1 の関係) であることがわかります。
実は ScriptPlayable<T>.GetDuration()メソッドを使うことで TimelineClip.du

rationと同等の内容を取得することができるのです。さらに ScriptPlayable<T>.Get
Time()メソッドではそのクリップ内でどこまで再⽣されたかの再⽣時間も取ることができます。これはとても便利です。

57

第 6 章 Unity Timeline Tips 集▼リスト 6.6

クリップ単位の Playable から時間の情報を取り出す

public class SampleMixer : PlayableBehaviour{
public override void ProcessFrame(Playable playable, FrameData info, object playerData){
var inputCount = playable.GetInputCount();for(var i = 0; i i < inputCount; i++){var input = (ScriptPlayable<SampleBehaviour>)playable.GetInput(i);// クリップの中でどこまで再⽣しているのかの再⽣時間
var currentTime = input.GetTime();// クリップ⾃体の⻑さ
var duration = input.GetDuration();// クリップ内でどこまで再⽣が進んだのかの割合（進捗率）を求めることも可能
var progress = currentTime / duration;}}}

上記のような形で、クリップの⻑さやそのクリップ内の現在の再⽣時間を取得することでそのクリップの中でどれだけ再⽣が進んだかの割合値も求めることができます。前述のTips で親⽟の PlayableDirectorの参照も取ってこれるので、組み合わせで⾊々と応⽤ができるようになるはずです。是⾮とも活⽤していきましょう。

6.5 Tips.4 [AssetBundle] TimelineAsset を
AssetBundle 化する
オンラインアップデートが当たり前な昨今のゲームでは、Unity で開発する上で扱うアセットの AssetBundle 化は必須と⾔っても過⾔ではありません。勿論、Timeline の機能として扱う TimelineAssetも例外ではありません。
とは⾔っても TimelineAssetをそのまま AssetBundle 化するだけであれば何も難しいことはありません。その他のアセットの AssetBundle 化と内容は変わりません。
▼リスト 6.7

TimelineAsset の AssetBundle 化

public void Build(){// 指定のディレクトリにある TimelineAsset をすべて Build 対象にする
var targetPath = "Assets/Resources/TimelineAssets";
var builds = AssetDatabase.FindAssets("t:TimelineAsset", new[] { targetPath }).Select(AssetDatabase.GUIDToAssetPath).Select(CreateAssetBundleBuild).ToArray();// Options や BuildTarget はよしなに書き換えてください
var options = BuildAssetBundleOptions.None;var platforms = new[] { BuildTarget.iOS, BuildTarget.Android };var outputPath = "Assets/StreamingAssets/AssetBundles/TimelineAssets";
foreach (var platform in platforms){
BuildPipeline.BuildAssetBundles(outputPath, builds, options, platform);}}
AssetBundleBuild CreateAssetBundleBuild(string path)58

6.5 Tips.4 [AssetBundle] TimelineAsset を AssetBundle 化する{// サンプルなので最低限必要になるものだけ埋めてます// 必要に応じて Variant や AddressableName を使ってください
return new AssetBundleBuild{
assetBundleName = Path.GetFileName(path),
assetNames = new []{ path },assetBundleVariant = "",addressableNames = new string[0],};}ただ単に AssetBundle 化するだけであれば上記のように⼀般的な AssetBundle のビル
ド処理を作れば終わりになります。
しかし、この⽅法だと中のクリップで指定する AnimationClipや AudioClipなどが増えてくると、この AssetBundle に内包されていきドンドン容量が増えていきます。また、AssetBundle の仕様上の問題で内部的に使われているアセットに重複があってもそれぞれの AssetBundle にそのアセットを含めてしまうため無駄ができてしまいます。
関係するアセットを TimelineAssetと⼀緒に明⽰的に全部 AssetBundle 化することでDependecies に含まれている依存関係をよい感じにしてくれるのですが、ある程度の規模の開発になってくると、各種単体のアセット制作と Timeline の演出制作とで制作パイプラインも変わってくるので単純にまとめてビルドというのも難しくなります。※ディレク
トリ構成が違ったりアセットの制作タイミングがずれたりするため
ここは最終納品物として TimelineAssetを指定したらそれに使われているアセットもまとめてビルドしてくれるような仕組みにしたいです。使われるアセットは AnimationT

rackや AudioTrackから各種クリップごとの PlayableAssetを引っ張り出すことができ
れば合わせて⾃動的に AssetBundle 化できそうです。
▼リスト 6.8

TimelineAsset に内包されているアセットを取り出す// TimelineAsset の中⾝に使っているアセットのパスをすべて取得
string[] GetIncludeAssetPaths(TimelineAsset timelineAsset){
var outputPaths = new List<string>();foreach (var track in timelineAsset.GetRootTracks()){
GetIncludeAssetPathsRecursive(track, outputPaths);}
return outputPaths.ToArray();}// 再帰的にトラックの中⾝のアセットを探す
void GetIncludeAssetPathsRecursive(TrackAsset track, List<string> outputPaths){
foreach (var clip in track.GetClips()){// デフォルトの機能では AnimationClip/AudioClip/Prefab の 3 種類
UnityEngine.Object asset = null;
if (track is AnimationTrack){
var playableAsset = clip.asset as AnimationPlayableAsset;
asset = playableAsset.clip;}
else if (track is AudioTrack)59

第 6 章 Unity Timeline Tips 集{
var playableAsset = clip.asset as AudioPlayableAsset;
asset = playableAsset.clip;}
else if (track is ControlTrack){
var playableAsset = clip.asset as ControlPlayableAsset;
asset = playableAsset.prefabGameObject;}// 独⾃トラックでアセットを扱っている場合はここに if ⽂を⾜していく
else if (track is SampleTrack){
var playableAsset = clips.asset as SamplePlayableAsset;
asset = playableAsset.SampleData;}
if (asset != null){var path = AssetDatabase.GetAssetPath(asset);
outputPaths.Add(path);}}// ⼦トラックへ再帰的に処理していく
foreach (var childTrack in track.GetChildTracks()){
GetIncludeAssetPathsRecursive(childTrack, outputPaths);}}

このようにして TimelineAssetの中⾝を調べることで内包しているアセットのパスを取得することができます。あとはこの処理をビルド対象となるすべての TimelineAssetに通して重複を消し、TimelineAssetと⼀緒にビルドすれば別々に AssetBundle 化することができます。
▼リスト 6.9

依存アセットも含めた AssetBundle 化

var targetPath = "Assets/Resources/TimelineAssets";
var timelinePaths = AssetDatabase.FindAssets("t:TimelineAsset", new[] { targetPath }).Select(AssetDatabase.GUIDToAssetPath).ToArray();// 重複を除いた上で内包するアセットのパスも取得してビルド対象に含める
var includeAssetPaths = timelinePaths.Select(AssetDatabase.LoadAssetAtPath<TimelineAsset>).SelectMany(GetIncludeAssetPaths).Distinct().ToArray();
var builds = includeAssetPaths.Concat(timelinePaths).Select(CreateAssetBundleBuild).ToArray();

6.6 Tips.5 [SelectionManager] 選択中のトラック/クリップを取得・設定する
Unity Timeline の API は内部的には使⽤されているものの外部に公開されていないものも多く、特に TimelineWindowに関するものはまだほとんどと⾔っていいほど internalで定義されているのが実情です。
60

6.6 Tips.5 [SelectionManager] 選択中のトラック/クリップを取得・設定する

実は、この項⽬のタイトルにもなっている選択中のトラックやクリップの情報も内部的には API が存在します。この Tips ではこれらの internalな API を C#の Reflectionを使ってアクセスできるようにしてしまおうという内容です。

Timeline に関する選択中のオブジェクトを管理する機構として SelectionManagerというクラスが namespace UnityEditor.Timeline内に定義されています。

UnityEditor.Selectionクラスからも情報は取れますが Timeline 機能以外の選択オ
ブジェクトを考慮したり、Timeline 機能内での連携も考慮しなければならないため Sele

ctionManager経由で操作しましょう。
まずはじめに、SelectionManagerの型情報を取得します※ Reflection の機能を使っているので using System.Reflection;を忘れずに。
▼リスト 6.10

SelectionManager の型情報を取得する

var assembly = Assembly.Load("UnityEditor.Timeline");var type = assembly.GetType("UnityEditor.Timeline.SelectionManager");

型情報が取れたらそこからさらにメソッド情報を取得していきます。
▼リスト 6.11

SelectionManager のメソッド情報を取得する// 選択中の TimelineClip/TrackAsset をすべて取得する
var selectedClipsMethod = type.GetMethod("SelectedClips",
BindingFlags.Public | BindingFlags.Static);
var selectedTracksMethod = type.GetMethod("SelectedTracks",
BindingFlags.Public | BindingFlags.Static);// 任意の TimelineClip/TrackAsset を選択状態する
var addClipMethod = type.GetMethod("Add",
BindingFlags.Public | BindingFlags.Static, null,
new [] { typeof(TimelineClip) }, null);var addTrackMethod = type.GetMethod("Add",
BindingFlags.Public | BindingFlags.Static, null,
new [] { typeof(TrackAsset) }, null);// 任意の選択されている TimelineClip/TrackAsset を選択解除状態にする
var removeClipMethod = type.GetMethod("Remove",
BindingFlags.Public | BindingFlags.Static, null,
new [] { typeof(TimelineClip) }, null);var removeTrackMethod = type.GetMethod("Remove",
BindingFlags.Public | BindingFlags.Static, null,
new [] { typeof(TrackAsset) }, null);

⽤途が分かりやすいものをいくつかピックアップしてみました。Timeline の編集で扱うデータは TimelineClipと TrackAssetなので、それらに関するメソッドが並んでいます。Add/Remove メソッドに関しては引数違いの同名メソッドになっているので、引数の型を明⽰的に指定してあげています。
※ Reflection についてはここでは詳しく解説しません。各⾃調べてください。
最後に上記の掘り起こしたメソッドを使った独⾃の拡張も⼀緒に紹介します。
▼リスト 6.12

SelectionManager クラスを活⽤した拡張

61

第 6 章 Unity Timeline Tips 集// 選択されている TimelineClip の⻑さを⼀律で設定する
public void SetDurationToSelectedClips(float duration){
var selectedClips = (IEnumerable<TimelineClip>) selectedClipsMethod.Invoke(null, null);
foreach (var clip in selectedClips){
clip.duration = duration;}}// TTrack の型の TrackAsset だけを選択状態にする
public void FilteringSelectedTracks<TTrack>()
where TTrack : TrackAsset{var selectedTracks = (IEnumerable<TrackAsset>) selectedTracksMethod.Invoke(null, null);
foreach (var remove in selectedTracks.Where(track => !(track is TTrack)){
removeTrackMethod.Invoke(null, new object[] { remove });}}このようにして内部の機能を掘り起こすことで、通常では難しい/出来ないような拡張もできるようになります。次にもうひとつ、内部 API を掘り起こした拡張を紹介しましょう。

6.7 Tips.6 [TimelineWindow] スクロール位置を制御する
Tips.5 では internalな静的クラス・メソッドを取得してくるだけの単純な内容でしたが、この Tips ではもう少し踏み込んで複数の internalなクラスを組み合わせる内容です。

Timeline の編集をする際に⼀番操作すると⾔っても過⾔ではない TimelineWindowのスクロール。既存の動作だけでは痒いところに⼿が届かないと感じる⽅も多いと思うので、ここはエンジニアの⼒でサポートしてあげたいところです。

namespace UnityEditor.Timeline内に WindowStateという internalなクラスがあり、TimelineWindowの状態の管理はこのクラスの中で⾏われています。このクラスからスクロールの情報を引っ張り出して制御していきます。
次に再⽣バーの位置までスクロールさせるサンプルコードを載せます。⼀気に載せるので少し⻑いです。
▼リスト 6.13

ScrollToCurrentTime

using System.Reflection;using UnityEditor.Timeline;// TimelineWindow のスクロール位置を再⽣バーの位置まで移動させるメソッド
void ScrollToCurrentTime(){// UnityEditor.Timeline.dll のアセンブリ情報を取得する
var assembly = Assembly.Load("UnityEditor.Timeline");// TimelineWindow と WindowState の型情報を取得する

62

6.7 Tips.6 [TimelineWindow] スクロール位置を制御する

var timelineWindowType = assembly.GetType("UnityEditor.Timeline.TimelineWindow");
var windowStateType = assembly.GetType("UnityEditor.Timeline.WindowState");// TimelineWindow のシングルトンインスタンスの取得を試みる// TimelineWindow が開かれていたら取得できる
var timelineWindowInstanceProp = timelineWindowType.GetProperty("instance", BindingFlags.Public | BindingFlags.Static);
var timelineWindowInstance = timelineWindowInstanceProp.GetValue(null, null);
if (timelineWindowInstance == null){
Debug.LogWarning("TimelineWindow が開かれていません");
return;}// TimelineWindow のインスタンスから WindowState インスタンスを取得する
var stateProp = timelineWindowType.GetProperty("state", BindingFlags.Public | BindingFlags.Instance);
var windowState = stateProp.GetValue(timelineWindowInstance, null);// TimelineWindow の横軸の中央値を取得する
var timeAreaRectProp = windowStateType.GetProperty("timeAreaRect", BindingFlags.Public | BindingFlags.Instance);
var timeAreaRect = (Rect) timeAreaRectProp.GetValue(windowState, null);var timeAreaCenterX = timeAreaRect.width * 0.5f;// 現在の再⽣時間から再⽣バーの X 座標を算出する
var timeToScreenSpacePixelMethod = windowStateType.GetMethod("TimeToScreenSpacePixel", BindingFlags.Public | BindingFlags.Instance,
null, new[] { typeof(double) }, null);var currentTime = TimelineEditor.masterDirector.time;
var cursorPosX = (float) timeToScreenSpacePixelMethod.Invoke(windowState, new object[] { currentTime });// TimelineWindow のスクロール位置を再⽣バーが中央に来るように補正する
var timeAreaTranslationProp = windowStateType.GetProperty("timeAreaTranslation", BindingFlags.Public | BindingFlags.Instance);
var timeAreaTranslation = (Vector2) timeAreaTranslationProp.GetValue(windowState, null);timeAreaTranslation.x -= cursorPosX;
timeAreaTranslation.x += timeAreaCenterX;// 補正したスクロール位置を TimelineWindow に設定する ※ Scale は変更しないのでそのまま値を渡し直す

var timeAreaScaleProp = windowStateType.GetProperty("timeAreaScale", BindingFlags.Public | BindingFlags.Instance);
var timeAreaScale = (Vector2) timeAreaScaleProp.GetValue(windowState, null);var setTimeAreaTransformMethod = windowStateType.GetMethod("SetTimeAreaTransform", BindingFlags.Public | BindingFlags.Instance, null,new[] { typeof(Vector2), typeof(Vector2) }, null);setTimeAreaTransformMethod.Invoke(windowState,
new object[] { timeAreaTranslation, timeAreaScale });// TimelineWindow の再描画をする
TimelineEditor.Refresh(RefreshReason.WindowNeedsRedraw);}

上記のコードを実⾏すると、再⽣バーがちょうど TimelimeWindowの真ん中に来るよ
うにスクロールされます。
ある程度⻑い TimelineAssetを扱っていると、再⽣バーとスクロール位置が⼤きくずれて⾯倒なことになるケースが多発します。上記のようにボタンひとつで再⽣バーの位置
までスクロールできるようになるだけでかなり便利になると思います。
またこれを応⽤すれば、縦のスクロールを操ったり、再⽣中にスクロールを追従させるというようなこともできるようになります。

63

第 6 章 Unity Timeline Tips 集

6.8

最後に

いかがでしたでしょうか。今回紹介したものは必要にかられて Timeline の機能を⼿探りで開拓していったものの⼀部で、まだ開拓できていない内容も沢⼭あります。私がTimeline の機能を触り始めた Unity2017 から Timeline の機能は少しずつですが確実に進化していっています。もしかしたらここで紹介しているコードも次のバージョンからはLegacy になっていたり、そもそも呼び出せなくなってしまったりしているかもしれません。※特に Reflection で掘り起こしているところは容赦なくリファクタリングされていくので要注意です。
ただ、このようにしてできたばかりの機能を開拓していくのはエンジニアとしては結構
やりごたえがあって楽しいです。皆さんも、是⾮とも⼀緒に Unity の新機能を開拓して情報を出していきましょう！ ありがとうございました。

64

第7章

物理ベースレンダラーを Rust 実装
して、ちょっと⾼速化した話
Sho HOSODA / @gam0022
本章では、筆者⾃作の物理ベースレンダラを題材にして、パストレーシングの⾼速化の*1 の第⼀章『物理ベー
⼿法を紹介します。また技術書典 3 の『KLab Tech Book Vol. 1』

スレンダラーを Rust 実装して、表紙絵をレンダリングした話』の続編です。

7.1

パストレーシングと計算時間

パストレーシングを簡単にいうと、現実世界の光の振る舞いを簡略化してシミュレートすることで、写実的なレンダリングができる 3D の描画⼿法です。まず 3DCG の描画⽅
法はレイトレーシング法とラスタライズ法の⼤きく 2 つに分類できます。レイトレーシング法（以下、レイトレ）ではカメラからレイを⾶ばし、レイがシーン中の物体に衝突したらシーンを描画し、衝突しなかったら背景を描画します。そして物体表⾯がどのくらい照らされているかを計算し、⾊付けをしていきます。この⾊付け処理をシェーディングと呼びます。パストレーシングは、このシェーディングの処理として、光の伝達・挙動を記述したレンダリング⽅程式を⽤います。レンダリング⽅程式を正確に計算することで⼤域照明を考慮した写実的な描画ができますが、実際のシーンのレンダリングにおいて解析的に正確な計算をすることは⾮現実的です。そこで、モンテカルロ積分（乱数を使ったシミュ
レーションを繰り返して数値的に積分を計算する⼿法）を⽤いてレンダリング⽅程式を解きます。以上をまとめると、パストレーシングはレイトレに分類される⼿法で、モンテカ
ルロ積分を⽤いてレンダリング⽅程式を計算することでシェーディングを⾏います。
モンテカルロ積分ではサンプリング数（シミュレーションの回数）が計算時間と⽐例します。図 7.1 は、サンプリング数による描画結果と計算時間の違いを⽰したものです。⼗
*1 http://klabgames.tech.blog.jp.klab.com/techbook/KLab-Tech-Book-Vol-1-1.2-ebook.

pdf

65

第 7 章 物理ベースレンダラーを Rust 実装して、ちょっと⾼速化した話

分な計算時間があればノイズの無いきれいな描画結果（右）となりますが、計算時間が限られた状況では、サンプリング数が不⾜してノイズだらけの描画結果（左）になってしまいます。

▲図 7.1

パストレーシングの描画結果。（左）は 1 サブピクセルあたり 10 サンプリング、Intel Core i7-6700K による計算時間は 20.7 秒。（右）は 10000 サンプリング、5
時間 45 分 18.6 秒

⾼品質なシーンを限られた時間内で描画しなければならないという場⾯は往々にしてあるかと思います。品質を保ちつつ、計算時間を短縮するにはどうすればよいでしょうか。
以降ではパストレーシングの⾼速化の⼿法として、次に挙げる 5 つの⼿法を紹介していきます。

• BVH による衝突判定の⾼速化• 式を整理して不要な計算を省くことによる⾼速化• Next Event Estimation（NEE）によるサンプリングの効率化• デノイズによるノイズの除去• コンパイラの最適化を利⽤

7.2

⾃作レンダラーの紹介

⾼速化の話を始める前に、本章で題材とする筆者⾃作の Hanamaru レンダラー*2 を簡単に紹介します。Hanamaru レンダラーはパストレーシングによる物理ベースレンダ
ラーであり、図 7.2 はその描画結果です。Rust で実装されていて、次のとおりレンダラー
としての基本機能が揃っています。

• パストレーシング（BSDF による重点的サンプリングあり）*2 https://github.com/gam0022/hanamaru-renderer667.2 ⾃作レンダラーの紹介• シーン上のオブジェクトとして Sphere, Polygon Mesh, AABB に対応• 並列処理による⾼速化• 対応マテリアル– 完全拡散反射– 完全鏡⾯反射– ⾦属⾯（GGX の法線分布モデル）– ガラス⾯（GGX の法線分布モデル）• Image Based Lighting（IBL）• テクスチャによる albedo / roughness / emission の指定• 薄レンズモデルによる被写界深度（レンズのピンぼけ）▲図 7.2

⾃作の Hanamru レンダラーによる描画結果

Hanamaru レンダラーの作成過程や各機能について KLab Tech Book Vol. 1 と筆者のブログで詳しく紹介しています。興味がありましたら、そちらもぜひご覧いただければ嬉しいです。
レイトレ合宿 5 に参加して、Rust でパストレーシングを実装しました！

https://gam0022.net/blog/2017/10/02/rtcamp5/
レイトレ合宿 6 参加報告 前編（準備編）

https://gam0022.net/blog/2018/09/18/rtcamp6-part1/67

第 7 章 物理ベースレンダラーを Rust 実装して、ちょっと⾼速化した話

7.3 BVH による衝突判定の⾼速化
衝突判定はレイトレの実⾏時間のほとんどを占めるため、これを⾼速化するのは⾮常に効果的です。衝突判定の⾼速化としては BVH（Bounding Volume Hierarchy）を実装しました。シーン内のすべてのオブジェクトを総当りで衝突判定せずに、空間分割することで衝突判定の回数を減らします。BVH は前回の記事でも紹介しましたが、とても効果が⾼い⾼速化⼿法であるため、改めて紹介します。

BVH による⾼速化の効果はシーンに依存し、シーンが⼤規模になるほど⾼速化の効果は⼤きくなります。シーンに含まれるポリゴンの数を n としたとき、BVH なしの総当たりで衝突判定する場合、計算量のオーダーは O(n) となります。⼀⽅、BVH を⽤いる場
合は O(log n) になります。つまり、シーンに含まれるポリゴンの数が多い⼤規模なシーンになるほど BVH は効果的です。

BVH はオブジェクトのバウンディングボックスを⼊れ⼦にした⼆分⽊の構造です。
BVH の例を図 7.3 に⽰します。A が根ノードかつ内部ノードで、B と C は葉ノードです。
BVH を構築する⼿法はさまざまですが、今回はバウンディングボックスの最⻑辺を軸に選んで要素数で 2 分割する実装にしました。まずは全オブジェクトを内包するバウン
ディングボックスをつくり、それを根ノードとします。次に、バウンディングボックスの⼀番⻑い辺を選んで、その辺を軸として内包するオブジェクトの数が均等になるように⼆等分します。⼆等分されたオブジェクトそれぞれでノードを作り、さらに分割をする⼿順
を再帰的に繰り返し、ノードが持つオブジェクト数が 4 以下になったら、そのノードを葉ノードとして分割を終了します。

▲図 7.3 BVH の例。オブジェクトのバウンディングボックスを⼊れ⼦にした⼆分⽊の構造

BVH の構築ができたら、いよいよ衝突判定です。BVH との衝突判定はよくある⼆分⽊に対する⾛査と同じです。まずは根ノードのバウンディングボックスと衝突判定を⾏い、もし交差していればその⼦ノードと衝突判定を⾏い、交差していなければ⾛査を打ち切る、という⼿順を再帰的に繰り返すと、いずれは交差するオブジェクトを持つ葉ノード

687.4 式を整理して不要な計算を省く⾼速化に到達します。あとは葉ノードが持つオブジェクトに対して総当りで衝突判定を⾏うだけです。単純な BVH 構築の⽅法でしたが、総当りと⽐較すると⼗分に⾼速化できました。
シーンを構成するオブジェクトとして、ポリゴンメッシュ、球体、⽴⽅体の 3 種類に対応しました。シーンを構成するオブジェクトに対する BVH だけでなく、ポリゴンメッ
シュを構成するポリゴンに対しても BVH を構築しました。

7.4

式を整理して不要な計算を省く⾼速化

式を整理して不要な計算を省くことによる⾼速化を紹介します。「7.1 パストレーシン
グと計算時間」の繰り返しとなりますが、パストレーシングはレンダリング⽅程式をモン
テカルロ積分によって解く⼿法でしたね。レンダリング⽅程式は、光の伝達・挙動を記述した⽅程式であり、次の式で定義されます。

∫
L0 (x, ω0 ) = Le +fs (x, ωi , ω0 )Li cos θdωΩ

L0 は、ある点 x から ω0 ⽅向に放出される放射輝度（=レイのもつ光のエネルギー）を表します。Le は、 x が光源の場合に ω0 ⽅向に放出される放射輝度となります。

fs (x, ωi , ω0 ) は x において ωi と ωo に対する BSDF（Bidirectional Scattering Distribution Function）です。BSDF は、簡単にいうと「ある⽅向から来た光が別の⽅向にどれくらい反射（透過）するか」を表しています。

∫Ω

fs (x, ωi , ω0 )Li cos θdω は半球上の積分となっており、x に⼊射するあらゆる⽅向からの放射輝度を集めるという意味です。つまり、ある点 x の放射輝度が、 x ⾃⾝が放つ光と周囲から集めた光のエネルギーの総和であることを⽰しています。
レンダリング⽅程式の積分の被積分関数は再帰的で⾼次元かつ⾮連続的であるため、解析的に解くことは困難です。そこで、パストレーシングではモンテカルロ積分によって数値的にレンダリング⽅程式を解きます。レンダリング⽅程式をモンテカルロ積分をつかって書き直すと次の式となります。

1 ∑ fs (x, ωi , ω0 )Li cos θN i=1
p(ωi )N

L̂0 (x, ω0 ) = Le +L̂0 は L0 の推定値を表します。N はモンテカルロ積分のサンプリング数であり、p(ωi )は ωi ⽅向を選ぶ確率密度関数です。
以上を踏まえると、モンテカルロ積分において、ある 1 サンプリングの重み wi は次の式となります。

wi =fs (x, ωi , ω0 ) cos θp(ωi )

愚直に wi を計算する場合、BSDF である fs (x, ωi , ω0 ) 、確率密度関数である p(ωi ) 、

cos θ という 3 つの項の計算が必要になります。
69

第 7 章 物理ベースレンダラーを Rust 実装して、ちょっと⾼速化した話

しかし、特定のマテリアルの場合にはこのような計算を省略できます。たとえば完全拡
散⾯において cos 項に応じた重点的サンプリングをした場合を考えてみましょう。
まず fs (x, ωi , ω0 ) は次の式になります。 ρ は物体の反射率です。

fs (x, ωi , ω0 ) =ρπ

そして cos 項に応じた重点的サンプリングする場合、 p(ωi ) は次の式になります。

p(ωi ) =cos θπ

以上から wi を整理すると、次の式のように単なる定数である ρ に簡略化できます。

ρcos θ
fs (x, ωi , ω0 ) cos θ
wi =
=ρ
= π
p(ωi )cos θπ
完全拡散⾯だけでなく、完全鏡⾯反射でも同様に wi を整理することで ρ に簡略化できます。GGX といったもう少し複雑な BSDF の場合は定数にはなりませんが、愚直に 3項を計算するよりも計算を削減できます。Hanamaru レンダラーの実装ではマテリアルのインターフェースとしてサンプリング⽅向と wi をセットで返す関数を定義しました。
このように式を整理することで不要な計算を省く⾼速化を紹介しました。

7.5 Next Event Estimation（NEE）の実装
NEE（Next Event Estimation）と呼ばれるパストレーシングのサンプリングを効率化する⼿法を実装しました。
光源が⼩さいシーンでは、BSDF による重点的サンプリングだけではなかなか光源に
ヒットしません。そのため、短い計算時間ではノイズだらけの結果になってしまいます。
そこで、光源の表⾯上の点を明⽰的にサンプリングして光転送経路を⽣成することで、効率的なサンプリングを⾏う⼿法が NEE です。

70

7.5 Next Event Estimation（NEE）の実装▲図 7.4 Next Event Estimation の効果

図 7.4 は同じサンプリング数で NEE 実装前と NEE 実装後を⽐較した結果です。左のNEE 実装前では、ほとんどのレイは光源にヒットしないために全体的に暗く、偶然にも光源にヒットした箇所が極端に明るいためノイズが⽬⽴ちます。右の NEE 実装後では、直接光源をサンプリングしているため、どの箇所も明るくノイズも⽬⽴ちません。上は⻑
時間をかけて計算したリファレンスの結果です。NEE 実装後の結果とリファレンスの結
果はほとんど⼀致しており、正しい結果を保ちつつ⾼速化できたことが分かります。

NEE の理論と実装についてはいくつかの資料*3 *4 *5 を参考にさせていただきました。
Sphere の光源を NEE に対応させるために必要な「球⾯上に⼀様分布した点を選ぶ処理」
は渡辺さんの資料*6 の「2.4 単位球⾯に⼀様分布する点」を参考にさせていただきました。
極座標ではなく (z, ϕ) で球⾯を表現するとシンプルに計算できます。

0 ≤ z ≤ 1,

0 ≤ ϕ ≤ 2π

*3 パ ス ト レ ー シ ン グ- Computer Graphics - memoRANDOM https://rayspace.xyz/CG/
contents/path_tracing/*4 パストレーシング / Path Tracing - Speaker Deck fhttps://speakerdeck.com/shocker_0x15/
path-tracing*5 レイトレ再⼊⾨ - ☆ PROJECTASURA ☆ http://project-asura.com/blog/archives/4046*6 ⼀様乱数を使う http://apollon.issp.u-tokyo.ac.jp/~watanabe/pdf/prob.pdf

71

第 7 章 物理ベースレンダラーを Rust 実装して、ちょっと⾼速化した話

x=

y=

√

1 − z 2 cosϕ√

1 − z 2 sinϕ

z=z

7.6

デノイズの実装

⼗分な時間がかけられずにサンプリング数が不⾜したパストレーシングの結果には⾼周
波なノイズが発⽣します。デノイズはレンダリング結果に 2D 的なフィルタリング処理を⾏うことでノイズを軽減する処理です。
今回はデノイズの中でも、もっとも単純そうな Bilateral Filter を実装しました。

▲図 7.5デノイズの効果

図 7.5 はデノイズなしとデノイズありを⽐較した結果です。デノイズを⾏うことでノイズを除去できたことが⾒て取れます。

72

7.6 デノイズの実装

Bilateral Filter を簡単に解説します。平滑化フィルター（ぼかしフィルター）として有
名な Gaussian Blur では「空間的な重み」に基づいて周囲のピクセルを混ぜ合わせて平滑化を⾏います。Gaussian Blur では全体的にぼやけてしまうので、エッジ部分を保持す
るように重みを変化させたものが Bilateral Filter です。Bilateral Filter では「空間的な重み」と「ピクセル値の差による重み」を掛け合わせた重みを⽤います。

▲図 7.6「空間的な重み」と「ピクセル値の差による重み」を組み合わせる

図 7.6 は"A Gentle Introduction to Bilateral Filtering and its Applications" SIG-

GRAPH 2007 の"Fixing the Gaussian Blur": the Bilateral Filter*7 の 7 ページ⽬から引⽤しました。

Gσs (∥∥p − q∥∥) は距離をパラメータとしたガウス関数なので「空間的な重み」となります。

Gσr (|Ip − Iq |) は画素値の差をパラメータとしたガウス関数なので「ピクセル値の差による重み」です。
この 2 つの重みを組み合わせることでエッジ部分を保持しながら平滑化ができます。1
つ補⾜すると、上の式の σr を無限⼤にするとガウス関数の性質上、「ピクセル値の差によ*7 http://people.csail.mit.edu/sparis/bf_course/slides/03_definition_bf.pdf

73

第 7 章 物理ベースレンダラーを Rust 実装して、ちょっと⾼速化した話る重み」が⼀様な分布になります。つまり σr を無限⼤にすると Gaussian Blur になります。このような知識を念頭に置いておくと、パラメータ調整に役に⽴つでしょう。
今回は簡単なデノイズを実装しましたが、今後の展望としては BCD*8 などもっと品質の⾼いデノイズに挑戦したいです。

7.7 Rust 環境の最新化
実⾏速度を重視した⾔語を採⽤することやコンパイラの最適化を利⽤することも⾼速化では重要です。幸いにも Rust は実⾏速度を重視しているため、多くのケースで C++と遜⾊ないパフォーマンスを発揮できます。Rust は新しい⾔語だけあって取り巻く環境の変化も激しく、古いバージョンの Rust ではパフォーマンスに関する改善が取り込まれていないことがあります。そのため、コンパイラの最適化の恩恵を最⼤限に受けるためには最新版に追従する必要があります。この節では Rust 環境を最新化する⽅法を紹介します。

Rust コンパイラのバージョンアップ
Rust のコンパイラのバージョンを 0.20.0 から 1.28.0 にアップデートしたところ、コードをまったく書き換えずに約 3 倍の⾼速化ができました。メジャーバージョンを上げると劇的にパフォーマンスが変わることがあるのですね。1.27*9 で導⼊された SIMD の最適化の効果が⼤きいと予想していますが、今回はバージョンの上がり幅が⼤きいので、複数の理由で⾼速化したと思われます。

Rust を最新の安定バージョンに上げる⼿順は⾮常に簡単で rustup updateを実⾏するだけです。

# 最新の安定バージョンに上げる$ rustup update# バージョンの確認$ rustc -V

依存ライブラリのバージョンアップ
今回は速度に変化ありませんでしたが、依存ライブラリもバージョンアップしました。
ここでは Rayon という並列化のライブラリのバージョンを上げる例を紹介します。
まず Cargo.tomlを編集します。

*8 BCD- Bayesian Collaborative Denoiser for Monte-Carlo Rendering https://github.com/
superboubek/bcd*9 Announcing Rust 1.27 https://blog.rust-lang.org/2018/06/21/Rust-1.27.html

74

7.8 まとめ-rayon = "0.8.2"+rayon = "1.0"

そして次のコマンドを叩くと、Cargo.tomlで指定された中で最新のバージョンに依存
ライブラリがアップデートされます。

$ cargo update

依存ライブラリの最新バージョンを調べる際に cargo-outdated*10 というツールが役に⽴ちました。cargo-outdatedは次のコマンドからインストールできます。

$ cargo install cargo-outdated

cargo outdatedを実⾏すると、依存ライブラリのバージョン情報を⼀括で調査できます。

Project
現在のバージョンCompat
現在の Cargo.tomlのままで cargo updateからインストール可能なバージョン

Latest最新バージョン

$ cargo outdated
Name---fuchsia-zircon->bitflags
fuchsia-zircon->fuchsia-zircon-sys
rand
rand->fuchsia-zircon
rand->libc
rand->rand
rand->winapi
winapi->winapi-i686-pc-windows-gnu
winapi->winapi-x86_64-pc-windows-gnu

7.8

Project------1.0.4
0.3.3
0.3.220.3.3
0.2.43
0.4.3
0.3.5
0.4.0
0.4.0

Compat------------------------

Latest-----Removed
Removed
0.5.5
RemovedRemovedRemovedRemovedRemovedRemovedKind---Normal
NormalNormalNormalNormalNormalNormalNormalNormalPlatform-------------cfg(target_os = "fuchsia")cfg(unix)--cfg(windows)
i686-pc-windows-gnu
x86_64-pc-windows-gnu

まとめ

本章では、パストレーシングの⾼速化についてアプローチの異なる 5 つの⼿法を紹介しました。

*10 https://github.com/kbknapp/cargo-outdated75

第 7 章 物理ベースレンダラーを Rust 実装して、ちょっと⾼速化した話

1 つ⽬にレイと物体との衝突判定の回数を減らす⽅法を取り上げました。BVH というデータ構造を⽤いて空間分割することで、シーン内の全オブジェクトに対して総当りで衝突判定を⾏うよりも、効率的に衝突判定ができます。

2 つ⽬に実⾏時の不要な計算を洗い出して、全体の処理を減らす⽅法について述べました。これはパストレーシングに限らず、さまざまな⾼速化課題に応⽤が利く⽅法です。

3 つ⽬はサンプリングのアルゴリズムの改良です。BSDF に応じた重点的サンプリングと光源⽅向へのサンプリングを組み合わせることによって、少ないサンプリング数でも効率的にパストレーシングの計算を⾏い、ノイズを軽減できます。

4 つ⽬はレンダリング結果に 2D 的なフィルタリング処理を⾏うことでノイズを軽減させるデノイズです。今回はデノイズの⼿法として Bilateral Filter を実装しました。

5 つ⽬にコンパイラの最適化を利⽤する⽅法を紹介しました。今回は極端な例かもしれませんが、Rust を最新バージョンにすることで約 3 倍の⼤幅な⾼速化ができました。
最後に、本章では取り上げませんでしたが、コンピューターの性質を深く理解することも⾮常に重要です。たとえば、並列化をする際にメモリのキャッシュヒット率を意識した戦略に変えることでも速度の向上が期待できます。
パストレーシングをはじめとしたレイトレはとてもシンプルなアルゴリズムですが、⾼速化を突き詰めていくとさまざまな⼯夫の⽅法が考えられて⾯⽩いですし、得られる並列化などの知⾒は CG 以外の分野でも応⽤できます。
みなさんもレイトレや、その⾼速化に挑戦してみませんか？

76

第8章

ヘッドレス Chrome でリボ払いを回避している話
Yoshio HANAWA / @hnw

8.1

はじめに

この章では私の仕事とは⼀切関係のない、趣味のプログラミングとクレジットカードの話をします。
私は DC カード Jizile というクレジットカードを利⽤しています。このカードはポイ
ント還元率が 1.5% と⽐較的⾼いのが魅⼒ですが、⼀⽅でリボ払い*1 専⽤カードなので使い⽅を間違えると事故につながる可能性もあるようなカードです。
このカードではリボ返済額の上限が 5 万円になっており、⽉の利⽤額が 5 万円を超えるとリボ払いの⾦利が発⽣します。ただし、5 万円以上使った⽉でも Web サイトから⽀払い⽅法を変更すれば全額⼀括返済に変更することができます。逆にいうと、⽉に 1 回

Web サイトにアクセスしないとリボ払いの⾦利⼿数料が発⽣してしまいます。
私は忘れっぽい性格なので、⼿動で Web サイトにアクセスする運⽤を続けていたら
いずれ⼤⾦を⽀払うことになるのは明らかでした*2 。そこで、この作業をヘッドレス

Chrome で⾃動化して Raspberry Pi 上で定期実⾏するような仕組みを作りました。最近の Chrome にはヘッドレスモードが実装されており、ウインドウシステムのない環境でもフル機能の Web ブラウザを動かすことができるようになっているのです。
本稿ではこの仕組みの作り⽅と、作業を通じて得た気づきを紹介します。

*1 クレジットカードで⽀払った額のうち設定額を超えた分が⾃動的に借⾦になるような仕組み。仕組みを知らずに借⾦を増やしてしまうと⾮常に危険。
*2 実際、本稿で紹介する仕組みを作るまでは怖くて滅多に使わないカードでした。

77

第 8 章 ヘッドレス Chrome でリボ払いを回避している話

8.2

実⾏環境

今回の仕組みは⾃宅で運⽤しています。実⾏環境は次のとおりです。

• Raspberry Pi 2 Model B– Raspbian 9.4– Node.js v8.11.1– Puppetter 1.7.0 （後述）
– Chromium*3 70– crondcron により⽉に 1 回ヘッドレス Chrome が起動しているというわけです。
⾃宅で運⽤する理由は、クラウド上にクレジットカード会社の ID・パスワードを置きたくなかったからです。昨今⾃宅サーバを持っている⼈は減っている印象ですが、こういう⽤途のためにも Raspberry Pi を⾃宅サーバにしておくのは悪くない選択肢だと感じています。

8.3 Puppeteer（ぱぺってぃあ）とは
すでに紹介したとおり、最近の Chrome は単体でヘッドレス動作が可能ですが、⾃動操作をするには操作⽤のライブラリも必要になります。
こうしたライブラリはいくつか存在するようですが、個⼈的に⼀番有望だと感じているのが Node.js の API である Puppeteer*4 です。すでにユーザーも多いですし、かなりア
クティブにメンテナンスされています。Chrome DevTools チームがメンテナンスしているので⻑期サポートが期待できるのも良いところだといえるでしょう。

8.4 Puppeteer のインストール
Puppeteer のインストールは簡単です。作業⽤のディレクトリに移動してから次のよ
うに打つだけです*5 。

$ npm install puppeteer

これでカレントディレクトリの node_modules以下に必要なファイルがインストールさ*3 Chrome のオープンソース版プロジェクト。Puppeteer から⾒ると⼤きな違いはないので、本稿では両者をほとんど区別せず使っています。
*4 https://github.com/GoogleChrome/puppeteer*5 インストールには Node.js と npm が必要です。事前に apt などでインストールしておいてください。

78

8.5 Puppeteer でのコード例

れます。動作に必要な Chromium も合わせてインストールされるので、すぐに試せるのは⾮常に良いと感じます。

Rapsberry Pi でのインストール
Raspberry Pi 上で動かす場合は npm install⼀発で終了とはいきません。というのも、npm installでインストールされる Chromium が x86-64 バイナリなので、ARM
アーキテクチャの Raspberry Pi では動作しないのです。
そこで⾃前で Chrome/Chromium を⽤意する必要があるのですが、⾃前でインストー
ルする場合は dev 版がオススメです。というのも、Puppeteer の機能追加やバグ修正の際に Chrome 本体を修正していることがあるので、古い Chrome では動かないことがあるのです。
実際、私は最初 Chrome 68（当時の最新リリース版）と Puppeteer 1.7.0 の組み合わせ
で使っていたのですが、Puppeteer の⼀部機能が動作しないトラブルが発⽣し、最終的にChromium 70（当時の最新 dev 版）を使ってようやく全機能を動かすことができました。
dev 版の⼊⼿⽅法ですが、Chromium チームが dev 版 Chromium の ARM バイナリを
apt リポジトリ形式で提供しているので、これを利⽤しましょう。

deb http://ppa.launchpad.net/chromium-team/dev/ubuntu xenial maindeb-src http://ppa.launchpad.net/chromium-team/dev/ubuntu xenial main

このファイルを /etc/apt/sources.list.d/chromium-dev.list として作成すればapt 経由で ARM バイナリをインストールできます。
また、⾃前でインストールした Chromium を Puppeteer から使うには、次のように puppeteer.launch()のオプションでフルパスを渡してやる必要があります。

const browser = await puppeteer.launch({executablePath: ’/usr/bin/chromium-browser’});

8.5 Puppeteer でのコード例
すでに紹介したとおり、Puppeteer は Node.js のライブラリです。次に Puppeteer のコード例を紹介します。

const puppeteer = require(’puppeteer’);(async () => {const browser = await puppeteer.launch();const page = await browser.newPage();await page.goto(’https://www.example.com/’);79

第 8 章 ヘッドレス Chrome でリボ払いを回避している話

await page.waitForSelector(’input[name="userid"]’, {visible: true}).then(el => el.type(’USER_ID’));await page.type(’input[name="password"]’, ’PASSWORD’);await page.click(’input[type="image"]’);})();

このように page.type()と page.click()を使ってページ遷移していき、ページ遷移のタイミングで page.waitForSelector()で次ページの要素の出現を待つ、というところだけ把握すれば⼀定の処理はすぐに書き始められるはずです。また、細かい部分については Puppeteer 公式のマニュアル*6 が⾮常に参考になります。

Puppeteer ではほぼすべての API が⾮同期（async）関数として提供されているため、あちこちに awaitが登場するようなコードになります。⼈によっては「⾃分の知ってる

JavaScript じゃない…」と思うかもしれません。
async・awaitというのは ES2017 で導⼊された⾮同期処理の構⽂です。私も今回が初⾒だったので慣れるまで⼤変でしたが、慣れてくると「全部 await⼊れれば逐次処理っぽく書けるでしょ*7 」くらいの雑な境地に達することができます。

8.6 Puppeteer の良いところ
私は過去に PhantomJS*8 を使ったりスクリプト⾔語のスクレイピングライブラリを使ったりしてきましたが、それらと⽐べても Puppeteer は便利に思えます。Puppeteerの良い点を紹介します。

ブラウザの全機能を使えるのが最⼤の強み
銀⾏やクレジットカード会社などの⾦融系サイトだと普通の<a>リンクで良さそうな部
分まで JavaScript で実装されていたりします。HTML の解釈しかできないスクレイピン
グライブラリでこうしたサイトを扱うのは⾯倒なのですが、本物のブラウザを利⽤している Puppeteer なら問題なく遷移できます。
他にも CSS、Canvas、SVG なども本物のブラウザと同じように動作します。
普通のブラウザと同じことが同じようにできるので、ツールを使い分けたり特別な⼯夫をしたりする必要がないのが最⼤のメリットだといえるでしょう。

メモリ 1GB 程度のマシンでも動く
普通のサイトを 1 タブで閲覧するだけであれば、メモリ 300MB もあれば問題なく動きます。メモリ 1GB の Raspberry Pi でも余裕を持って動くのは嬉しいところですね。
実際にはメモリより CPU の⽅がボトルネックになりやすい印象があります。広告が⼭*6 https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md*7 とはいえハマったときは真⾯⽬に調べることになり、結局 Promise の復習をする⽻⽬になります。
*8 ヘッドレスブラウザの代名詞的存在でしたが、現在はメンテナンスされていません。

80

8.7 おわりにほど設置されていたり、JavaScript+Canvas でゴリゴリ描画するようなサイトでは CPU
負荷が厳しくなりがちですので、これが問題になるようであれば良いマシンを使った⽅がよいでしょう。

実際のブラウザの様⼦を⾒ながら開発できる
Chrome をヘッドレス動作させるのが Puppeteer のデフォルトの挙動ですが、次のよ
うにオプション指定すればウインドウありで起動することもできます。
const browser = await puppeteer.launch({headless: false});

これにより、実際のページ上で Chrome のデベロッパーツールを使いながらセレクタを特定できるので、開発中は⾮常に便利です。また、期待通りに動かなかった場合のデバッ
グ時にも活躍します。

ブラウザのスクリーンショットが撮れる
Puppeteer スクリプトは運⽤に⼊ってからが本番だといえます。というのも、対象のWeb サイト側がいつまでも同じ挙動である保証がないので、ある⽇突然動かなくなることが⼗分考えられるからです。
その意味で、ヘッドレス動作中のログは⾮常に重要です。思いつくログは全部残しておくとトラブル時に役⽴ちます。また、下⼿なログより断然役⽴つのがスクリーンショットです。以前とサイトデザインが変わった、なんてことがすぐにわかります。
ちなみに、私は Puppeteer で例外を catch したら例外の詳細とその時点のスクリーン
ショットを Slack に転送しています。スクリーンショット込みで時系列順に並んでいるとログとして⾮常に⾒やすいので、Slack への転送するのはオススメです。

8.7

おわりに

Web サイトからしかできない処理というのは案外多いものです。
私は今回作った仕組み以外に、銀⾏振込の⾃動化にも Puppeteer を使っています。
いわゆる⾦融系のサービスは⾃動化のメリットが⼤きい割に API 公開されないので、

Puppeteer の活躍範囲は広いように思います。
もちろん、⾦融系以外の Web サイトでも便利に使えるツールだと思います。私の隣の席の同僚はコマンドラインからお弁当の注⽂をするのに Puppeteer を利⽤しており、な
かなか気に⼊っているようです。
本稿でご紹介したとおり、試すまでのハードルは⾮常に低いツールなので、読者の皆さんも⽇々の⽣活の改善に使ってみてはいかがでしょうか。

81

著者
第 1 章 Kinuko MIZUSAWA
某アイドルリズムゲームのイベントやら何やらと原稿のスケジュールが重なり正直死ぬ思いでしたが何とか原稿を形に出来て良かったです。今年は 3D 関係を頑張っていきたいと思います。
第 2 章 黒井春⼈
平成最後の技術書典。ちゃんと原稿が書けてよかったです。
第 3 章 Daisuke TAKAI
技術書典 5 当⽇は⼤阪でとあるライブに参加してます。
第 4 章 Yoshihiro KASHIMA

Microsoft Excel が好きです。
第 5 章 Daisuke MAKIUCHI / @makki_d
眼鏡っ娘が好きです。
第6章

Junichi KIKUCHI / @jukey17

初参加でバタバタしましたがガッツリと書けたと思います！ スプラトゥーン 2 楽しいです！
第 7 章 Sho HOSODA / @gam0022
今年はレイトレ⾶躍の年でしたね！ Microsoft が DirectX Raytracing を発表し、NVIDIA も GeForce RTX を発売して、リアルタイムレンダリングの世界にも⽻ばたい
たレイトレの今後に⽬が離せません。
第 8 章 Yoshio HANAWA / @hnw
クレジットカードが好きです。30 枚あるので引っ越し時の⼿続きが怖いです。

82

表紙

あつち

幼⼥描くの楽しかったです。
⽊こり担当 ひらた
褐⾊はいいぞ装丁 ⼭浦
つづりには気を付けよう

PDF 版表紙 きくた
眼鏡⼥⼦イイ…！
編集 於保 俊 / @ohomagic
今回は編集と取りまとめ役での参加です。

83KLab Tech Book Vol. 3
2018 年 10 ⽉ 8 ⽇ 技術書典 5 版（1.0）
著 者 KLab 技術書サークル
編

集

於保 俊

発⾏所

KLab 技術書サークル

印刷所

⽇光企画

(C) 2018 KLab 技術書サークル

84