Tricks of Realtime VFX with Houdini詳細解説 vol.1

こんにちは、セガゲームス龍が如くスタジオの伊地知です。

 去年もHoudiniのお話をこのSEGA TECH Blogで掲載しており、これで二回目となります。

さる2018年12月2日にCEDEC+KYUSHU2018が開催されたのですが

その時私の方で講演させていただいたTricks of Realtime VFX with Houdiniというセッションがありまして

(セッションのスライドはこちらからダウンロード出来ます。)

そのスライドの後半に

「実際にすぐにゲームに出して使えるエフェクトのテクニック」=「トリック」

を3つご紹介させて頂きました。

その3つのトリックを今回のSEGA TECH Blogで詳細に解説させて頂きます。

 

  1. 加工して戻す(RestSOP) ー processAndRest01.hip
  2. 独自シミュレーション(SolverSOP)
  3. 最短経路探索(FindShortestPath)

3ついっぺんにやると果てしなく長いので全部で3回に分け、

今回は1つ目である「加工して戻す」というトリックだけを扱うことにします。

シーンファイルもご利用頂けますしHoudiniの体験版(Apprentice版)でも開けますので

実際にご自身のPC(Windows)、Mac、Linuxで確認しながらご覧になるとご理解いただきやすいでしょう。

(HoudiniはいろんなOSに対応しています!) 

 

今回は初心者の方でも安心の詳細解説となっております。

またwrangleの行数も最低限に抑えてあります。 

操作方法や基礎知識に関しては1年前の前回のHoudiniの回でも

techblog.sega.jp

御紹介させおりますのでその辺が分からない方はそちらも合わせてご覧下さい。

 


1.加工して戻す(RestSOP)

では1つ目のトリックの内容から説明させて頂きます。

Houdiniの新規シーンを開いた想定でお話をさせて頂きます。

ダウンロードして頂いた方はシーンを上から順にデータを見ていって下さい。

まず画面一番左下のボタン

f:id:sgtech:20190224233230p:plain

を押してGlobal Animation Optionsを開きます。

FPS30End256にしてSaveAsDefaultボタンを押します。

f:id:sgtech:20190224233226p:plain

ネットワークエディタのオブジェクトレベルでTabキーを押しTabメニューでgeと押すと候補が出ますのでGeometryを選び作ります。

f:id:sgtech:20190224233347p:plain

f:id:sgtech:20190224233344p:plain

f:id:sgtech:20190224233340p:plain

できたgeo1にダブルクリックやiキーなどでジオメトリレベルに潜ります。

たいていの作業はこのジオメトリレベルで行う事になります。

 

TabメニューからSphereを選択してPrimitiveTypeをPolygon

Frequencyを50にします。

f:id:sgtech:20190224233155p:plain

 

次にTabメニューからUVTextureを出してつなげます。

TextureTypeをPolarFix Boundary Seamsにチェックします。

f:id:sgtech:20190224233149p:plain

この状態でビュー上でSpace+5を押すとUVの状態が確認出来ます。

f:id:sgtech:20190224233143p:plain

UVが01の範囲内からはみ出していると都合が悪いので

TabメニューからUV Transformを出してUV Textureにつなげ

ScaleXの値に 1/$XMAX と入力します。

これはUV値のUの最大値で全体を割るという意味で

こうする事によって01の範囲内に収めます。

f:id:sgtech:20190224233137p:plain

ビュー上でSpace+1を押してPerspectiveに戻します。

TabメニューからPointWrangleを出してUV Transformの出力につなげます。

f:id:sgtech:20190224233131p:plain

ポイントアトリビュートの@rest,@Cd,@Alphaを設定します。 

VEXpressionの欄に下記のコードを記述します。

v@rest = @P;
f@Alpha = 0.0;
@Cd = {1,1,1};

@はアトリビュートという意味でジオメトリのクラス(Point,Vertex,Primitive,Detail)自体に持たせる変数です。@の前のvやfは型を指定しておりvならベクター、fならフロートを定義します。ここではRunOverがPointになっているのでPointのクラスのアトリビュートを制御するという事になります。

@restはポジション(@P)を保持しておく為のアトリビュート。

頂点α(@Alpha)は0で初期化、頂点カラー(@Cd)は白で初期化という意味になります。

TabメニューからPointWrangleを出して先程のPointWrangleにつなげます。

f:id:sgtech:20190224233244p:plain

f@Alpha = 1.0;

VEXpressionの欄にはこの様に記述します。

再度Alphaを1にするのには訳がありますがそれはあとで解説します。

TabメニューからTwist( or Bend)を出して先程のPointWrangleにつなげます。

f:id:sgtech:20190224233240p:plain

Limit Deformation to Capture Regionのチェックを外し

Twistの値を360Capture OriginのZを-1Capture Length2にします。

こうする事で球がZ軸に沿って360度ねじれます。

TabメニューからMountaionを出して先程のBendにつなげます。

f:id:sgtech:20190224233233p:plain

MountainノードのHeight1.73Element Size2.18Scale0.06,0.16,0.06、

OffsetX値を-14に設定しY値は20に設定し1フレーム目Altキーを押しながらクリック256フレーム目18と設定しAltキーを押しながらクリック

これでパラメータの欄が緑色になったのが分かります。

これはアニメーションが設定されていますという事を現しています。

ちなみにShiftを押しながらクリックでアニメーションエディタが開きます。

Max Octaves9Lacunarity2.19Roughness0.407とします。

この状態で再生ボタン(画面左下にあります)を押すか、カーソルキーの↑を

押してみて下さい。

f:id:sgtech:20190224233321g:plain

球のトゲトゲが蠢きながら下に流れていくアニメーションが確認出来ます。

確認出来たら停止ボタン(画面左下にあります)を押すか、Ctrl+↑を押して止めて下さい。

TabメニューからTransformを出して先程のMountainにつなげます。

f:id:sgtech:20190224233317p:plain

RotateX値を31Y値を5Z値を5とし少し回転させます

TabメニューからClipを出して先程のTransformにつなげます。

f:id:sgtech:20190224234332p:plain

先程と同じ要領でDistance1フレーム目-1.25256フレーム目1.36とキーを打ちます。

この状態で再生させると下半分が削れた状態が段々と上に上がっていって

トゲトゲの球が消えていくのが分かります。

TabメニューからPointWrangleを出して先程のClipにつなげます。

f:id:sgtech:20190224234328p:plain

@P = @rest;

VEXPressionの欄にこの1行を入力すると歪められていたポイントのポジションが元に戻ります。

この挙動を不思議に思う方もいらっしゃるかもしれませんがこれは

単純に@restに保持していた座標で元の位置に戻っただけなのです。

しかし消されたポイントは戻りません。

歪んだ状態で水平に切るという事は座標を戻すと水平では無く歪んだ状態で削れていくのです。

↑キーで再生させるとこの様に動作しているのが確認出来るでしょう。

f:id:sgtech:20190224234240g:plain

映像の表現ならここまでで良いですがゲームの場合

ゲーム中で表現する為に工夫が必要です。

 CEDEC+KYUSHU2018では私はテクスチャパターンアニメーションでやりましょうと言ってしまいましたが実はもっと良いαカットオフを用いた手法があります。

シェーダ側でαしきい値を用いて透明にするのですが

そのαをどう出すのかがこのトリックの鍵になります。

2つ目のトリックで紹介するSolverというノードをもうここで使います。

毎フレーム値を加工して蓄積させていく事が出来るノードで評価した結果を

キャッシュしておくことが出来ます。

TabメニューからSolverを出して上から4つ目のpointwrangle1から

左から1番目のところにつなげて左から二番目の入力にpointwrangle3をつなげます。

 

f:id:sgtech:20190224234238p:plain

ダブルクリックしてsolver1の中に潜ります。

 TabメニューからAttributeTransferを出してPrev_Frameを第一入力に

Input_2を第二入力につなげPointsにチェックを付けAlphaを指定します。

f:id:sgtech:20190224234234p:plain

AttributeTransferは第一入力に対し第二入力のアトリビュートを近いものから転写していくノードです。MayaであればTransferAttribute、Softimageをお使いだった方ならGATORを御存知だと思いますがその万能版だと思って頂ければ理解が早いかと思います。(Softimageユーザーの方ならMayaのTransferAttributeなんかと比べられるのは屈辱だと思いますがHoudiniのAttributeTransferからすればどちらも五十歩百歩です。)

SolverSOPの中でこの様なつなぎ方をすると前のフレームの結果に対し現在のフレームの第二入力のアトリビュートを転写するという意味を持ちます。

TabメニューからPointWrangleを出して第一入力にPrev_Frame、第二入力にAttributeTransferをつなげます

f:id:sgtech:20190224233439p:plain

VEXpressionの欄には 

@Alpha += @opinput1_Alpha/256;

 と記述します。

このコードと接続の意味は前のフレームの結果のアルファに対し

第二入力のアルファを256で割った数値を加算するという意味になります。

つまりこの2つのノードの意味は毎フレーム現在のアルファ値の1/256を

加算し続けるということになります。

 

uキーで1つ上の階層に戻って256フレーム目まで進めます。

すると球がこの様に見えているはずです。

f:id:sgtech:20190224233435p:plain

毎フレームアルファ値を累積して256フレーム分貯めた結果がこれです。

これをUV座標に基づいてテクスチャに書き出してやります。

GameDevelopmentToolsetのSimpleBakerを使うやり方が最も簡単でしょう。

GameDevelopmentToolsetのインストールの最も簡単な方法は左上の

GameDevelopmentToolsetタブのUpdateToolsetボタンを押すことです。

f:id:sgtech:20190224233432p:plain

ゲーム会社や映像スタジオ、CGスクールなどでプロキシ環境下であれば

この機能がうまくいかない場合があります。

その際は公式のgithubからダウンロードして手動でインストールして下さい。

GameDevelopmentToolsetがインストールされた状態になったら

TabメニューからGameDev Simple Bakerを出して下さい。

f:id:sgtech:20190224233430p:plain

ここで普通に考えればアルファにチェックを入れれば出力出来るはずなんですが

何故か真っ白になってしまうので一旦@Cdに@Alphaを移して

basecolorとして出力する事になります。

 simple bakerの1つ前にPointWrangleを足して

@Cd = @Alpha;

 記述して頂点カラーにアルファを移してからsimple baker でテクスチャを焼きます。

すると

f:id:sgtech:20190224233426p:plain

この様なテクスチャが焼き上がりますのでPhotoshop上でレベル補正かけたり

トーンカーブで補正かけたりして1~254くらいの値の範囲にしておくと

シェーダに食わせた時の見た目が良い様です。

 

モデルの方も同時に出力しておきます。

 UVを設定したuvtransform1から出すと良いのですがそのままだと

25,000頂点もありかなりメモリを食ってしまいます。

uvtransform1の下にPolyReduceを作ってつなげます。

 Percent To Keep1Vertex Attribute Seams0.4にして頂点数を252にまで

落とします。ゲーム内で綺麗に見える最低限の頂点数であれば Percent To Keepを

いろいろ試してみても良いでしょう。

f:id:sgtech:20190224233533p:plain

TabメニューからROP FBX Outputを出してつなげます。

 Output Fileを指定してSave to Diskボタンを押します。

これでモデルファイルも出力出来ました。

 ではUnity上で確認してみましょう。

普通のスタンダードシェーダでは両面に対応していないので

Create -> Shader -> Standard Surface Shader

で作ったものにちょい足ししたシェーダで表示してみましょう。

Shader "Custom/doubleSidedCutOff" {
    Properties {
        _Color ("Color", Color) = (1,1,1,1)
        _Cutoff ( "Cutoff", Range(0, 1) ) = 0.5
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0
        _BumpMap ( "Normal Map" , 2D ) = "bump" {}
        _BumpScale ( "Normal Scale" , Range(0,1) ) = 1.0
    }
    SubShader{
        Tags { 
            "Queue" =    "AlphaTest" 
            "RenderType"="TransparentCutout"
        }
        LOD 200
        Cull Off
        CGPROGRAM
        // Physically based Standard lighting model, and enable shadows on all light types
        #pragma surface surf Standard fullforwardshadows alphatest:_Cutoff

        // Use shader model 3.0 target, to get nicer looking lighting
        #pragma target 3.0

        sampler2D _MainTex;
        sampler2D _BumpMap;

        struct Input {
            float2 uv_MainTex;
        };

        half _Glossiness;
        half _Metallic;
        half _BumpScale;
        fixed4 _Color;

        // Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
        // See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.
        // #pragma instancing_options assumeuniformscaling
        UNITY_INSTANCING_BUFFER_START(Props)
            // put more per-instance properties here
        UNITY_INSTANCING_BUFFER_END(Props)

        void surf (Input IN, inout SurfaceOutputStandard o)
        {
            // Albedo comes from a texture tinted by color
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = c.rgb;
            // Metallic and smoothness come from slider variables
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = c.a;

            fixed4 n = tex2D( _BumpMap, IN.uv_MainTex);
            o.Normal = UnpackScaleNormal(n, _BumpScale);
        }
        ENDCG
    }
    FallBack "Transparent/Cutout/VertexLit"
    
}

このシェーダをアサインしたマテリアルを用意して適当なテクスチャを貼ってあげます。

その際テクスチャのアルファに先程生成したテクスチャを入れ込んでおきます。

するとこの様に表示されます。

youtu.be

 

@restに座標を保持して戻す時に削れたpointが戻らないのは当然としても

生成されたpointがちゃんと良い感じの場所に戻ってくれるのは凄いと思います。

中で一体どういう処理が走ってるんでしょうか?気になります。 

累積アルファのカットオフはテクスチャパターンアニメーションでやるより

遥かに質も向上しメモリも削減出来る賢いやり方なので

覚えておいて損は無いでしょう。

 

さて今回はここまでです、いかがでしたでしょうか?
ゲームに出力する部分は様々な手法を考慮し工夫する必要があります。

現状ビルボードが主流のゲームエフェクトですがこんなトリックを用いる事で思いがけない表現をプレイヤーの方々にお見せし感動体験を演出する事も可能になるのです。

ゲームエンジン側の知識もフル動員すればもっと凄いものが表現出来るでしょう。

勉強する事は山程ありますし考えなければいけない事もいっぱいあります。

ですがやりたい表現が達成出来た時の喜びもまたひとしおです。

次回のHoudiniの記事もお楽しみに。 

 

この記事に興味を持って頂けた方は弊社で私達と一緒に働いてみませんか?

弊社ではHoudiniに興味を持って取り組めるような人を募集しています!
我こそはという方、興味のある方は以下のリンクを是非クリックしてみてください。

 
sega-games.co.jp

 

©SEGA

 

Are you readyyy to Deep Learning!?

 セガゲームス第4事業部第4開発1部TA(テクニカルアーティスト)セクション*1 宮下です。2019年が始まって早1カ月がたとうとしてますが、あなたにとって2018年はどんな年でしたか?(少々時期外れな質問なのですが…)

Readyyy!

 私は2019年2月1日にリリースされたスマホタイトル「Readyyy!」に携わっており、とても忙しい1年でした。このタイトルは、プレイヤーが新人プロデューサー兼寮長として、男子高校生アイドル18人を育成するスマホゲームで、ゲームエンジンUnity*2を使って開発しています。

f:id:sgtech:20190120235301p:plain

Readyyy! キービジュアル

 TAとして、3Dによるチビメンや「Live2D*3」の立ち絵をはじめとする「Readyyy!」の技術的なグラフィック表現の根幹を担っています。特に立ち絵については、ライティングという結構面白いことしているんですよ。

 昼間、夕方、逆光などの環境によるライティングを実現するための仕組みの設計と、それに関連するシェーダーやコンポーネント*4を作りました。そのデータを「Photoshop」や「Live2D」から出力するためのツール整備もしています。

f:id:sgtech:20190120235043j:plain

昼間順光の表現

f:id:sgtech:20190120235101j:plain

夕焼け逆光の表現

 ご興味あれば、ぜひ遊んでみてください!

ready.sega.jp

CEDEC+KYUSHU2018

 実はこのセガテックブログのおかげで、昨年12月に「CEDEC+KYUSHU2018」での講演という、貴重な体験をさせていただきました。おかげというのは、「CEDEC+KYUSHU2018」の関係者の方が、2017年に私の書いた記事、

techblog.sega.jp

をご覧になって、講演を依頼してくださったのです。

 今回は、そのとき講演した5つの自動化効率化のお話*5の中から、ディープラーニングをテーマにしたものにプラスアルファしてお届けします。

 「CEDEC+KYUSHU2018」での講演をお聞きになった方も復習を兼ねてご覧いただければと思います。説明も、より丁寧になっていますので!

ディープラーニングで自動的にサムネイルを作れないか!?

 前置きが大変長くなりました。それでは始めましょう!

 なお、ディープラーニングを勉強するのにゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装」という本を使っています!難しいですがとてもいい本です。

アニメ顔検出

 2016年ごろ、とあるデザイナーさんからこのような相談を受けました。

「サムネイルを自動的に作れませんか?」

f:id:sgtech:20190120234116j:plain
f:id:sgtech:20190120234114j:plain
f:id:sgtech:20190120234112j:plain
f:id:sgtech:20190120234110j:plain
f:id:sgtech:20190120234107j:plain

 サムネイルとは縮小した画像のことですが、このように顔など特徴的な箇所をクローズアップしてトリミングするケースもあります。ちなみに彼らは、「Readyyy!」の「SP!CA」というユニットのアイドルたちです。

 そのデザイナーさんが所属しているプロジェクトのプラットフォームはスマホでした。スマホタイトルは運営を長く続けていくため、新規要素をどんどん追加していきます。その要素を一覧で表示させたりするために、サムネイルが必要となるのです。

 また、ご存知とは思いますがゲーム開発ではアニメ顔が使われることが多く、そのプロジェクトでもアニメ顔が採用されていました。

 これを自動的に作成…何か良い方法はないものか…。検索すると…出てきました!「OpenCV」を使ったものです。

ultraist.hatenablog.com

 多くの人がこれを使ってアニメ顔検出しているようですね。勉強も兼ねて「Photoshop」のプラグインという形で実装してみました。一から実装すると大変なので、サンプルのプラグインに間借りする感じで作りました。
 余談ですが、「Photoshop」プラグインのAPIって独特というか、なんとも難しいですよね…。下のgif動画は、プラグインを動作させたときのものです。

f:id:sgtech:20190120234252g:plain

Photoshopプラグイン

 どうですか!?素晴らしい性能ですね!ただ、おしいのが誤検出(下の半透明で赤く塗り潰した部分)です。

f:id:sgtech:20190120234339p:plain

 このOpenCV(lbpcascade_animeface)を使った手法は検出精度の調整ができるのですが、精度を上げすぎると、顔ではない部分も検出してしまい、精度を下げると、顔を検出しづらくなってしまうという現象が発生します。そのちょうどよいバランスを見つければいいのかもしれませんが、その設定で絶対大丈夫と言い切る自信がありません。残念ながら、これでは自動生成するツールとして、信頼性が足りませんよね…。

顔なのか?顔ではないのか?

 しばらくして、1つのアイデアが閃きました。OpenCV(lbpcascade_animeface)が検出した画像の、「顔」「顔ではない」をディープラーニングで学習させて、判断させるようにすればいいのではないか…と。ここではディープラーニングの代表的な手法、畳み込みニューラルネットワーク(Convolutional Neural Network、以下CNNと呼びます)を用います。本来なら切り出した顔などの特徴量から誰なのかを判定できる素晴らしい手法なのですが、今回は贅沢(?)に、「顔」「顔ではない」の2種類の判定のためだけに使います。

 後で知ったのですが、この物体検出後にCNNで分類する手法は「R-CNN」という立派な名前がついていました。

Keras

 ディープラーニングのフレームワークには、簡単で分かりやすいという評判のKeras*6を使いました。KerasはPythonによるフレームワークで、TensorFlowやTheanoなどのディープラーニングライブラリをバックエンドとして選択できます。今回バックエンドには、CPU版TensorFlowを使いました。

 KerasやCNNについては、こちらの記事を参考にしています。

qiita.com

教師データ

 ディープラーニングを始めるにあたって、まず教師データというものを大量に用意します。ここでは「顔」と「顔ではない」画像ファイル群ですね。実は、この教師データを用意するところに1つのハードルがあります。個人で用意する場合「大量に」という点が大変だと思うのですが、セガでは複数のタイトルを開発していますので、他のプロジェクトから画像を提供してもらいました。これに先ほどのOpenCV(lbpcascade_animeface)を使ったアニメ顔検出をさせて、「顔」と「顔ではない」画像群を、比較的簡単に用意できました。

f:id:sgtech:20190120234423j:plain

顔 700枚

f:id:sgtech:20190120234420j:plain

顔ではない 1000枚

 これで準備が整いました。100回学習させてみると、7時間ほどかかりました。

 学習させた成果のことをモデルと呼びます。モデルは基本的に学習回数が多いほど、性能が良くなります。ただし、特定の教師データにだけ過度に対応した状態、過学習(overfitting)になってしまうことがあります。未知のデータに対応できない状態ですね。

 下の図は今回学習させたモデルの性能を表しています。

f:id:sgtech:20190120234336p:plain

 「acc」は正答率、「loss」は性能の悪さを示す指標で、教師データに対してどれだけ一致していないかを表しています。横軸は学習回数で、多いほど性能が良くなる傾向を示しているのがお分りいただけると思います。

 また、実は教師データの全てを学習に使っているわけではなく、教師データの一部は未知のデータに対応できるかどうかのテストに使っており、そのテスト結果が、val_acc」val_loss」です。

 緑のグラフval_loss」は、学習を繰り返すたびに上昇していく傾向にありますが、これは過学習状態であることを示しています。なので100回ではなく、30回ぐらいで止めておいたほうが良いモデルと言えそうですが、今回はこのまま100回学習したモデルで話を進めます。

 では、このモデルを切り出した画像に適用してみましょう。

f:id:sgtech:20190120234332j:plain

学習の成果は!?

 「Readyyy!」のキービジュアルでの判定の成果はこのようになりました。7番だけ99%顔と判断して失敗してしまいましたが、この手法は有効そうです。

デザイナーが使えるようPhotoshopで動作させてみる

 では、この顔検出+顔判定のシステムを、「Photoshop」から使えるようにしてみましょう。システムはPythonで動作しているので、「Falcon*7」というフレームワークを使って、WebAPIとしてアクセスするようにしてみます。手順としては、

  1. Photoshop上でドキュメントを一時的にファイル(JPEG等)保存する。
  2. Photoshop上でそのファイルを読み込んで、WebAPIを使ってアップロードし、応答を待つ。
  3. 画像を受け取ったWebサーバー側で、アニメ顔を検出する。
  4. 引き続きWebサーバー側で、「顔」か「顔ではない」かを判定し、「顔」の矩形の座標を返す。
  5. Photoshop上でその結果を受け取り、選択範囲として反映する。

となります。

 今回は「Photoshop」上での動作には、プラグインではなくスクリプト(JavaScript)を使います。青い文字が「Photoshop」スクリプトでの処理赤い文字がWebサーバーでの処理となります。

 「Photoshop」スクリプトでバイナリデータをアップロードする方法は、ここに良いコードがありますので、参考にしてみてください。

forums.adobe.com

 「Falcon」を使ったWebAPIも簡単に実装できます。今回はこちらを参考しました。

qiita.com

f:id:sgtech:20190120234223g:plain

WebAPI+Photoshopスクリプトによる実装サンプル

 対象の画像内に複数人いる場合は最初に見つかった人の顔の矩形を返します。上の動画では、左から2番めの茶色の髪の比呂君が選択されてますね。

 これで、「Photoshop」スクリプトを使って、画像から自動的にサムネイルを作りだせるようになりました。

  ここまでが、「CEDEC+KYUSHU2018」でディープラーニングについて講演した内容です。

ツールと連携させる(プラスアルファ)

 ここから先が今回追加する内容で、「Readyyy!」で試みた★1と★2フォトの自動生成をご紹介します。人間並みの高い精度でアニメ顔を矩形選択できるのであれば、いろいろ応用できそうですよね。

 ちなみにフォトというのは、「Readyyy!」内で手に入るアイドルの写真のことです。★5フォトは、このような豪華なフォトです。

f:id:sgtech:20190120235131j:plain

★5フォト

 ★1と★2のフォトは、アイドル画像と汎用的な背景画像を組み合わせたシンプルなフォトです。

f:id:sgtech:20190120235139j:plain

★1フォト

 なお、連携させるツールのフローは以下のようなもので、全て「Photoshop」上での操作です。

  1. アイドル画像を読み込み、あとでデザイナーが位置調整しやすいように、スマートオブジェクト化しておく。
  2. アイドル画像の顔の部分を選択範囲として囲む。
  3. 背景画像を読み込む。
  4. その背景画像にあらかじめ指定してある基準位置と、先ほどのアイドル画像の選択範囲の大きさが一致するように調整して、アイドル画像をコピー&ペーストする。
  5. 完成した画像を書き出す。

 2番(赤い文字の部分)にアニメ顔検出判定システムを組み込んで、自動化を試みます。では実行してみますね。動画の左側が「Photoshop」、右側が出力フォルダの様子です。また20倍速で再生しており、本来はもっとゆっくりとした動作となります。 

f:id:sgtech:20190121133210g:plain

ツールへの組み込み

 よし!どんどん生成されている!結構いい感じです!

f:id:sgtech:20190120235152j:plain

顔の大きさが違う…

 ただ極端な例なのですが、このあたりを比較してみるとアイドルの顔の大きさがそろっていません。このあたりの微調整は、デザイナーさんにがんばってもらいました…。さらなる自動化を目指すには、工夫が必要そうです。

 今回は検出にOpenCV(lbpcascade_animeface)を使いましたが、検出自体をDeepLearningで行う手法(Faster R-CNNもあるので、次はこちらも試したいと思っています。

まとめ

  • アニメ顔検出は、OpenCV(lbpcascade_animeface)が使える
  • 誤検出には、ディープラーニング(CNN)で対応
  • 検出されたアニメ顔のトリミングに課題あり

最後に…

 ここまで読んでくださり、ありがとうございました。課題も見つかり今回は道半ばという結果になってしましたが、いかがだったでしょうか?今後も継続的に改良を積み重ねて、またここでお伝えできればなぁと思っています。

 TAセクションではこのようにデザイナーの制作に役立つ環境を提供できるよう力を注いでおります。Unityの登場でTAの活躍できるフィールドは格段に広がり、シェーダーやポストエフェクトなど面白い表現を生み出せないかと日々格闘中です。

 そんな中でお仕事したい方は、ぜひ下記の弊社グループ採用サイトをご確認ください。いっしょに働いてみませんか?

sega-games.co.jp

 そして、「Readyyy!」、よろしくお願いします!

 

*1:1月に組織改編がありまして部署名が変わりました。

*2:https://unity3d.com/jp

*3:ここではLive2D Cubismのこと。2Dイラストに擬似3D的な滑らかなアニメーションを追加することのできるソフト。Spineという海外の競合ソフトがある。また、最近はVTuberにも良く使われている。https://www.live2d.com/ja/products/cubism3

*4:Unityにおいてオブジェクトの振る舞いを記述するためのスクリプトのこと。

*5:https://cedil.cesa.or.jp/cedil_sessions/view/1972

*6:https://keras.io/ja/

*7:https://falconframework.org/

龍が如くにおけるキャラクター制作ワークフロー

初めまして。
セガゲームス 第1CSスタジオの有賀千陽です。
キャラクターデザインの業務に10年以上携わったのち、現在は新設されたデザインサポートチームでデザイナーの作業を支援するツールを制作しています。

 

「龍が如くスタジオ」で制作されているタイトルにはたくさんの実在の人物が登場します。
今回のSEGA TECH Blogでは「龍が如くスタジオ」キャラクター班流のリアルなキャラクターを作成するフローを皆さまにご紹介させていただきます。

【目次】

 

まずは「龍が如く」シリーズのキャラクターの特徴から説明しましょう。

f:id:sgtech:20190313135651j:plain

  • プラットフォームはPS4などハイエンド機である
  • 開発期間が短い※おおよそ一年
  • 芸能人とタイアップし、ご本人がゲーム中でキャラクターを演じている
  • 沢山のユニークなキャラクターが登場する

などが挙げられます。

 

そのリアルなキャラクターたちを短い期間にどれくらいのクオリティで、どれくらいの顔の数を作らないといけなかったのか?

クオリティはもちろん写真のようなクオリティです。

f:id:sgtech:20190313135644j:plain

タイアップキャストになると誰が見てもそっくりに作らなくてはなりません。

 

次に数の話ですが…

f:id:sgtech:20181224005946j:plain

1プロジェクトで使用したキャラ(男性のみ)

『龍が如く6 命の詩。』の男性NPC*1の顔だけで上記の画像ぐらいです。

 

これに加えて女性NPC・重要キャラクター・キャバ嬢のタイアップキャラなどを含めると、

  • 男性の顔:約200種 
  • 女性の顔:約70種 

あいかわらずたくさん作ってますね…。まとめると、

短時間でリアルなキャラを沢山作らないといけない!

リアルなキャラを大量に作成するに当たり活躍してくれたのが、これから説明するフォトグラメトリーを活用した制作フローでした。

f:id:sgtech:20181224005310p:plain

 

まずは、フォトグラメトリーの説明と、フォトグラメトリーによる3Dの自動生成、Maya等のDCCツールに持っていくまでのフローの説明をします。


フォトグラメトリーとは?
------------------------------------------------------------------------------------
3次元の物体を複数の観測点から撮影して得た2次元画像から、
視差情報を解析して寸法・形状を求める写真測量のこと。

小難しくてよくわかりませんね…

要は撮った写真から自動で3Dデータを生成してくれる技法のことです。
この文言だけで、かなり簡単に作れるように感じられるのではないでしょうか?

 

フォトグラメトリーを本格的に導入したのは『龍が如く6 命の詩。』の開発中でした。

ではそれ以前はどうしていたのか?


「龍が如くスタジオ」で行っていた3Dスキャンの歴史を簡単に説明します。

「龍が如く」シリーズにおける3Dスキャンの歴史

  1. リアリティの追及
  2. 制作スピードの向上

この2つの要求を満たすために、「龍が如く」シリーズでは2007年PS3対応が始まった『龍が如く 見参!』の制作から3Dスキャンによるワークフローが導入されました。

PS2世代までは写真を見てモデリングを行っていました。いわゆる目コピーです。つまり制作クオリティはデザイナーのスキル頼りだったのです。

PS3世代になってゲーム機のスペック向上に伴い、リアリティのあるゲームモデルの必要性が高まり、さらに『龍が如く 見参!』から実在の俳優を登場させることにもなり、リアリティとクオリティの追及がさらに重要視されるようなりました。

光学式スキャナー

2007年の『龍が如く 見参!』以降『龍が如く3』『龍が如く4 伝説を継ぐもの』『龍が如く OF THE END』まで社外のスタジオで役者さんを撮影し、メッシュデータもそのスタジオで生成されたものを使用していました。
3Dスキャンを行うようになりキャラクターのモデリング時間は40%削減でき、クオリティも上がりました。

f:id:sgtech:20181224005306p:plain

外部スタジオを利用していたころのスキャンメッシュ

そのスタジオでは当時としては最新式だった光学式(ヘリウムネオンレーザー)の3Dスキャナーを使用していましたが、スキャンメッシュの精度はごらんの通り、なんとなく誰だか認識できる程度で細かいディテールは撮れていません。

 投影式の3Dスキャナー

以下の画像は『龍が如く OF THE END』の途中から導入が始まった社内撮影スタジオでスキャンされた3Dモデルです。

f:id:sgtech:20181224005302p:plain

投影式3Dスキャナを利用してキャプチャされた3Dモデル

 

この頃から社内に撮影専用スタジオを設け、テクスチャはカメラ1台(手持ち)での撮影からスタートし、後にカメラ5台(+三脚・ストロボ)でシャッターを同期させて撮影する方法に移行。より精度の高いテクスチャを撮影できるようになりました。

3DモデルのキャプチャにはWhite-Light (走査型白色)方式*2の3Dスキャナーを導入しました。プロジェクターを用い撮影対象に画像を投影して専用ソフトウェアで解析して3Dメッシュを生成するという方法です。

社内に専用スタジオを設け新しい3Dキャプチャのシステムを導入したことにより、メッシュの精度が以前より向上し時間面でも費用面でも社外スタジオの利用時に比べて大幅なコストカットを達成しました。

 ただ、暗室で5秒程度息を止めて動かないでいる必要があるため、まばたきや体が動いてしまうだけでメッシュの精度が低下するなど正確な3Dモデルのキャプチャは非常に難しく、撮影されている人の負担も大きかったのです。

さらに、社外スタジオのスキャンデータも社内スタジオのスキャンデータも、どちらもテクスチャの生成ができなかった為、3Dメッシュを生成した後撮影した画像をメッシュにベイクしてテクスチャを自分たちで作成する必要がありました。

f:id:sgtech:20181224005259p:plain


その頃のテクスチャ作成方法は、DCCツール内部に画角が同一となるカメラを作成し、位置を合わせてカメラプロジェクションUVを作成しテクスチャベイクしていたのですが、この作業だけで数時間かかっていました。

f:id:sgtech:20181224005255p:plain

また、社外スタジオのスキャンデータも社内スタジオのスキャンデータもそのままではノーマルマップベイクのソースにできるほど精細にキャプチャできるわけではなく、Zbrushでのスカルプトによるディテールの追加作業は必須でした。

 

PhotoScanの導入

そんななか、フォトグラメトリーの技術を使い3Dメッシュを生成するPhotoScanというソフトウェアがプロジェクトに導入されました。

 3Dデータ生成用の写真撮影の様子

f:id:sgtech:20181224005401p:plain

上の写真は2015年から2018年現在まで使用している社内フォトスタジオです。

事業所移転前の羽田にあった旧スタジオの様子なので手作り感あふれる仕上がりですが、ここで沢山の芸能人の方々をスキャンしてきた見た目にそぐわずできるヤツなんです・・・が、なんと!現在大崎の新オフィス内に「龍が如くスタジオ」キャラクター班こだわりの新スタジオを構築中なのです!
今年度中に完成予定ですのでお楽しみに。

話は戻って、カメラのスペックは以下のようになっています。

  • 台数 : 30台
  • 機種 : CanonX7i
  • レンズ : 標準ズームレンズ
  • 焦点距離 : 55mm

基本設計や機材の調達は社外の専門家に委託し、その後の運用、撮影のオペレーション、メンテナンスなどはキャラクター班が行っています。

 画像の現像

X-Rite社のColorCheckerPassportと一緒に撮影した画像からカメラプロファイルを作成し全ての現像に使用します。

f:id:sgtech:20181224005357p:plain

 

秘訣は 「龍が如くスタジオ」独自の「ドラゴンエンジン」

「龍が如く」シリーズは常に部内開発のゲームエンジン上で制作されてきました。そうすることでレスポンスが良く柔軟なゲーム開発が可能になり、デザイナーの時には無茶な要求にも短時間で実装することができるのです。
『龍が如く6 命の詩。』の開発時にアップデートされた内製ゲームエンジン「ドラゴンエンジン」は、それまでのゲームエンジンよりシームレスでリアルな表現が可能になりました。

部内でゲームエンジンを開発できるような高い技術力を持ったプログラマがたくさんいるのも、「龍が如くスタジオ」がクオリティの高いゲームを素早く作れる要素の一つとなっています。

キャラクター制作ワークフロー

前置きがだいぶ長くなってしまいましたが、本題のキャラクターワークフローの解説に移ります。
今回は「龍が如く」シリーズ関連のプロモーションでおなじみ島野さんの顔制作に沿ってお伝えします。

3Dメッシュ生成

3Dメッシュの生成にはAGISoft社のPhotoScanというソフトウェアを使用しています。

f:id:sgtech:20181224005951j:plain

PhotoScanの操作画面

このソフトウェアの導入により3Dメッシュの生成と同時にテクスチャも生成できるようになったことから、テクスチャをMaya上でベイクしていた数時間の作業が削減されテクスチャ作成時間が大幅に短縮できました。


この一連のフローにより、デザイナーのスキルに依存していたクオリティの向上と制作時間が短縮し安定して高水準なクオリティがアウトプットできるようになったのです。

メッシュ生成 フロー

では実際に3Dメッシュを生成する過程を紹介しましょう。

  1. 始めに現像した撮影画像をツールに読み込みカメラ位置を計測
  2. ポイントクラウド(点群データ)を生成
  3. 3Dメッシュを生成
  4. 最後にテクスチャベイクで終了

PhotoScnanは各画像のカメラの位置や画角を画像解析により取得し、ソフトウェア上で撮影環境を再現しますが、ソースにしている写真の点数が少ないと各画像の位置関係が予測できずカメラの位置情報が取得できなくなります。

現在弊社スタジオは30台のカメラで構成されており、横の耳から後ろの首回りまで360度カバーできるようになりました。

f:id:sgtech:20181224005825j:plain

高密度クラウド

上記の画像はポイントクラウド(点群)です。

この状態では点群と認識できないくらい高密度なので寄ってみます。

f:id:sgtech:20181224005822j:plain
更に寄ってみます

f:id:sgtech:20181224005818j:plain

 小さい点が見えるのがわかりますか…?

 

このポイントクラウドを元にメッシュとテクスチャを生成します。

f:id:sgtech:20181224005948j:plain

PhotoScanで生成したポリゴンメッシュ



上の画像は生成したメッシュデータです。1000万ポリゴンで生成しています。

 

f:id:sgtech:20181224005815j:plain

テクスチャ表示とUV

上の画像はテクスチャです。
PhotoScanが生成したポリゴンメッシュとUVに写真がベイクされます。

これらを任意の形式でエクスポートしてR3DS社のWrapXというツールでPhotoScanからエクスポートした3Dメッシュにベースメッシュをシュリンクラップします。
この工程の詳細は後ほど解説しますね。

 

顔モデル作成フロー

PhotoScanやWrapXで得たソースを元に実際にどのようにゲームのデータに落としこむのでしょうか。

まずはスキャンメッシュとゲームメッシュを合わせるという作業になります。

f:id:sgtech:20181224005353p:plain

これはPhotoScanで生成したメッシュです。

f:id:sgtech:20181224005350p:plain

これがWrapXで生成したメッシュです。

f:id:sgtech:20181224005346p:plain

実際に合わせてみるとこうなります。

わかりにくいですが目の周りのエッジフローが少し歪んでいますね。

ゲームメッシュのエッジのラインがきちんとスキャンメッシュの特徴に沿うように修正していきます。

目の周りはまつ毛や二重や粘膜の部分など細かい造形が多くPhotoScanでは再現しきれません。口の周りも唇の内側など写真には写らない部分はメッシュが生成されないので、その二か所はどうしても手作業で修正する必要があります。

f:id:sgtech:20181224005536p:plain

f:id:sgtech:20181224005533p:plain

f:id:sgtech:20181224005529p:plain

これらの作業は非常に重要で、メッシュの法線情報を正しく作ることによりクオリティを上げるという目的もありますが、「龍が如く」シリーズではフェイシャルモーションが複雑に動くためエッジフローが実際の筋肉の流れに沿っていないと後々イベントムービーシーンで思わぬ破たんを生みだす原因となってしまいます。

この作業に関してはWrapXの導入でかなり効率化されました。

テクスチャ作成

次はテクスチャ作成なのですが、その前に簡単に描画周りを説明しておきます。

「龍が如く」ではいわゆる主流ではない、f0値を扱ったPBRを採用しています。

この理由は、「龍が如く」というプロジェクトがゴリゴリのフォトリアル指向ではないことと、ゲームに妖怪や怪物*3など現実にいないキャラクターが登場する可能性を加味し、ある程度振り切った表現ができる今の形に収まりました。

 

テクスチャについて

次は「ドラゴンエンジン」で使われているテクスチャについてです。

f:id:sgtech:20181224005954j:plain

顔一つの表情に沢山の種類のテクスチャが使われています

上記の画像にあるような8枚*4のテクスチャをキャラクター班で作成して使用しています。
DiffuseMap、NormalMap、ShinnessMap、AmbientOccrusionMapは、おそらく大体のプロジェクトでお馴染みだと思いますので説明は割愛します。

アンビエントオクリュージョンマップ

アンビエントオクリュージョンマップをわざわざ1枚のマップで持っているのは、キャラ単体では画面を占める割合は低くスクリーンスペースのアンビエントオクリュージョンが充分に乗らないので個別でベイクしキャラごとに用意しているからです。

トランスミッタンスマップ

肌透過用のトランスミッタンスマップは、肌の薄い部分を表現するために使っています。逆光を当てられた時の耳や、指などに効果的です。

F(0°)マップ

黒い画像ですが、F(0°)マップと呼んでいます。
F(0°)とは屈折率の比から求まる、0度のフレネル反射率という意味らしいです。
屈折率0度、法線方向から見たときの、鏡面反射率、スペキュラーの総量です。
mayaにあったmental_rayのマテリアルでは、IOR(屈折率)と書かれていることからリフレクションの強さにも影響します。値が高いほど正面から見ても環境が映りこみます。

キャラクター班としては素材ごとに担当プログラマーに用意してもらった値で作業するという認識です。

テクスチャ作成フロー

次にテクスチャの作成フローです。

龍のテクスチャワークフローは、タンジェントノーマル、AO、シャイニネスの順番に一連の流れで制作しています。
ディフューズは状況によって前後いたしますが、スキャンメッシュと生成された画像をソースに、ゲームメッシュでベイクすればほぼ終了です。

タンジェントノーマルマップ

まずは16bit高解像度テクスチャを利用してディスプレイスメントマップを作成します。

そのディスプレイスメントマップをZbrush上でスキャンメッシュにアサインしてゲームメッシュにベイクするのが目的です。

PhotoScanにより自動で生成されたテクスチャがコレです。

f:id:sgtech:20181224005905j:plain

PhotoScanで生成したテクスチャ

ご覧のとおりバラバラです。

 

ほくろやシミなどの余計な凹凸情報になりえるものは先にフラットにしておきます。

f:id:sgtech:20181224010044j:plain

 

前処理が済んだらPhotoshop上でハイパス等を使いディスプレイスメントマップを作成します。


次にZbrush上での作業にうつります。

f:id:sgtech:20181224005812j:plain

右側がZbrush上で、スキャンメッシュにディスプレイスメントマップを適用した状態です。

f:id:sgtech:20181224005526p:plain

ディスプレイスをかける前よりかなり細かい凹凸が表現できているのがわかりますでしょうか。

先ほど調整したゲームメッシュをインポートし、レイヤー分けしてからスキャンメッシュにディスプレイスメントを適応します。その凹凸情報を、projectALLを使って転写します。

スキャンメッシュにはエラーも多いので、この作業の前に事前にモーフターゲットを登録しておきprojectALLで投影したくない部分をマスクしてするなどして都合の悪い部分をフィルタリングしています。

その後、キャラクターによってはさらにスカルプトしてディティールアップを図ります。

後はSubdivisionの一番上と一番下をエクスポートし、XnormalやSubstance等のテクスチャベイカーを使ってタンジェントノーマルとAOを生成します。
人によってはディフューズもこの段階でスキャンメッシュからベイクします。

生成したテクスチャが…

f:id:sgtech:20181224005902j:plain

これですね。ちゃんとバラバラだったUVが繋がっています。

スキャンメッシュからベイクによって得たデータとなります。

ディフューズマップはベイクしたままというわけにはいかないのでさらに手を入れて調整します。

テクスチャの撮影時に陰影を消すようにはしているものの、どうしても取りきれない陰影が入ってしまいますので手作業で余計な陰影を除去します。

f:id:sgtech:20181224010047j:plain

少しわかりづらいかもしれませんが、除去前と除去後で陰影の差が少し緩和されています。
消しすぎると単調に見えるのでさじ加減に多少経験が必要な作業です。


シャイニネスマップ

シャイニネスマップは、ノーマルマップをキャビティマップに変換するアクションを使い作成したマップをオーバレイで5枚重ね共通のトーンカーブを適用した後、光って欲しいところ、欲しくないところを既定の値で塗り完成です。

すべてのキャラクターはUVが共通なので、実際はキャビティレイヤーの差し替えと個人で違う位置になる唇周りと眉毛周りの塗りの調整のみ行っています。

Substanceでも同様に上の一連の作業が組んであります。

トランスミッタンスマップ

つづいて肌透過用のトランスミッタンスマップの作成も紹介しましょう。

f:id:sgtech:20181224005522p:plain

顔モデルにスムースをかけた後に、白黒反転をしてアンビエントオクリュージョンをベイクしたものです。
このフロー自体が正しいものなのかは正直わかりませんが、コストと効率を鑑みて、最も効果的なフローでした。

処理軽減のため全てのキャラが同じテクスチャを使っています。

以上ですべてのテクスチャ作成が完了です。

 

仕上げにテクスチャの中間データはDDSを採用しているのでDDSで書き出します。

その際のインターフェイスが煩雑で多くの設定ができてしまうので、デザイナーが迷わないようにツールを用意しました。

リサイズのアルゴリズムとDDS保存形式を任意で選ぶことができます。

f:id:sgtech:20181224005737p:plain

f:id:sgtech:20181224005943j:plain

元々はガンマ補正、レベル補正の設定や、ラップモード設定、ミップマップ設定など細かく分かれていたのですが、それらの仕様が確定されてからは、混乱を避けるため全て非表示にして触れないようにしてあります。

細かいことですが、これによりかなりミスも少なくなりかなり効率的に作業できるようになりました。

その後いろいろ微調整*5して、完成したモデルですが…

f:id:sgtech:20181224005912j:plain

f:id:sgtech:20181224005909j:plain

全身

どうでしょう、島野さんに見えますか?

「龍が如くスタジオ」流のフローでこのクオリティの顔モデルが今では1日もあれば誰でも制作できるようになりました!

 

架空のキャラクター

ついでに架空キャラクターのフローについてもすこし触れてみましょう。

基本的に弊社の社員に協力してもらい、リアルなキャラクターを作成する場合のフローでほぼそのまま再現します。

その後、キャラクターの設定に合わせて修正します。

f:id:sgtech:20181224010040j:plain

このキャラクターは眉毛の角度や目つきの修正ぐらいしか行っていません。

設定によっては元の風貌からは別人になるケースもあり、特に重要度の高いキャラになるとコンセプト画で用意した顔に近づけるために原形をとどめなくなります。

その場合は、ほとんど肌のディフューズ以外は、使用しないのでデザイナーの腕の見せ所でしょうか。

 

次は顔モデリング以外の話を簡単にご紹介します。

 

顔のしわ

各キャラクターには顔の表情が変わったときに表出する皺を表現するための皺マップが用意されています。

下の画像はとあるキャラの顔の皺マップです。

f:id:sgtech:20181224005734p:plain

用意するマップはノーマルマップのみです。

皺用のディフューズやシャイニネス等は用意しておらず、頂点カラーの塗分けによってノーマルマップの表出を制御しています。

f:id:sgtech:20181224010212j:plain

皺表出用の頂点カラー

イベントムービー班が設定した表情のパターンに合わせて表出するようになっており、

デザイナーの作業負担軽減の為全キャラクター設定はほとんど同じです。


キャラ作成をサポートするツール群

ここまでモデルを作成するフローを説明してきましたが、ここからはそのフローの作業を軽減するツールについてお話したいと思います。

まずウェイトのセットアップツールです。DCCツールをSoftimage*6からMayaに切り替えた経緯上、Softimageと同じ感覚でMayaでのウェイト編集を出来るようにする必要がありました。

そこでまずはデザインサポートチームでウェイトのエディタを作成することになりました。

顔のポリゴンメッシュはWrapXによって生成されたものを使っていれば全て同じUV・同じ頂点番号なので、ウェイトセットアップは頂点番号かUVのポジションでウェイトをアサインします。

これで殆どの場合うまくいくのですが、目の形によって目を閉じた時に破綻したり眉毛の位置によっては眉毛が付いてこない、あるいは瞬きしたときに眉毛もついてくる等のバグが発生します。

Maya用のウェイトエディターを使って修正していきます。

f:id:sgtech:20181224005731p:plain

ウェイトのスムースや、ジョイントのロック、ウェイトを保持したままのエッジ切り直し、スキンモデルのデュプリケート、スキンモデルのコンバインなど沢山の便利なツールが用意されています。
このエディタが用意されてからはコンポーネントエディターの数倍速くウェイトの修正ができるようになりました。

 
WrapX

上記でチラホラ名前の挙がっていたツール、WrapXを紹介します。

WrapXとは大抵はDCCツール上で行うシュリンクラップを、高精度で行うスタンドアロンのアプリケーションです。
今は多機能な新バージョンもリリースしている古いツールですが、スクリプトで操作できるのが決め手でこのツールを長らく使用しています。

「龍が如く」プロジェクトでは、PhotoScanで取得した高精細なポリゴンモデルを、リトポロジも兼ねて一定のトポロジのメッシュに落とし込む目的で使用しています。

Pythonでスクリプトを書かないとカスタマイズができないのでデザイナーからしてみれば使いにくいツールですが、このツールを導入しあえてデザイナーが触れないようにしてリトポロジやポリゴンの削減をWrapXで吸収してしまえば、こだわりがちなデザイナーの工数も増えづらいのではないかという狙いもあります。

f:id:sgtech:20181224005858j:plain

WrapXでシュリンクラップを行います

左がベースメッシュで約5000頂点あります。
ベースメッシュとは、「龍が如く」プロジェクトにおいて素体として最初に作られるモデルで、ほぼすべてのキャラクターモデルはこのベースモデルを基準に作られています。
右がスキャンメッシュです。

まず、既存のPhotoScanで生成した顔のメッシュやテクスチャ、トポロジを共通にするためのベースモデルを読み込み、最初は三点、両目頭と人中にポイントを指定して大まかに頭部の位置とスケールを合わせます。

次に、詳細にシュリンクラップを行うため、位置合わせのためのポイントをPhotoScanのメッシュとベースモデル両方に指定していきます。

計算を除外するポリゴンも指定する事ができるので、ソースのメッシュが大きく欠けている場合も、ベースのモデルを大きく変形させる事なくシュリンクラップを行う事ができます。

設定が完了したら計算を行います。

その過程をGifにしてみました。

f:id:sgtech:20181224010051g:plain

シュリンクラップの様子(約40倍速)

最後にスキャンメッシュをシュリンクラップしたベースメッシュと、それに位置とスケールを合わせたスキャンメッシュをエクスポートして完了です。 

f:id:sgtech:20181224010206j:plain

WrapXをMayaから操作するツール

最初はWrapXのみでシュリンクラップの作業を行っていましたが、現在では手軽にシュリンクラップや編集が行えるようにMayaからコントロールできるツールを用意してあります。

こちらのツールでは、WrapXの鬼門であったフローの途中で指定していたポイントの編集を行う機能も追加しました。

顔のハイメッシュは、このWrapXのフローを採用することでまだ技術的に未熟なデザイナーでも比較的ハイスピード&ハイクオリティに作成できるようになり、トポロジを共通化することで面倒なウェイト関連の作業もストレスなく行う事ができるようになりました。

 LODの作成

ゲーム制作の辛いところはここからLODを作成しないといけないところですが、LOD作成もツール側である程度自動化してあります。 

現在既成のLOD作成ツールは「龍が如く」プロジェクトでは使用せず、クオリティ重視で手作業によりLODを作成しています。

ですが顔のLODは先ほどのトポロジの共通化によりボタン一つでクオリティを維持したまま生成できるツールが使えるのでLOD制作の時間はほとんどかかっていません。

そのほかのツール

「龍が如く」プロジェクトではほかにもリアルなキャラモデルを短時間で作り上げる為に、いろいろなフローを取り入れています。

しかし、「リアルにする為のフローを取り入れる=工数の増大につながる」ことが多いため、フローの採用とその際に増える工数の圧縮の為のツールの制作はセットで行うようにしています。

たとえばMarvelousDesignerで作成したデータのリトポロジ用のツールなどがあります。

 

f:id:sgtech:20181224005728p:plain

f:id:sgtech:20181224005725p:plain

MarverousDesignerは衣服などの3Dデータの作成や、シミュレーションを行えるアプリケーションです。
リアリティのある衣服の3Dモデルを作成するには最適なアプリケーションでしたがリトポロジが大きな課題でした。
この問題もデザインサポートチームでリトポロジーツールを作成し、さらにゲームエンジンにすぐに組み込めるようウェイトやマテリアルの設定、各種テクスチャの焼きつけなども簡易的に行えるようにしました。

他には

  • 今まで作成した大量の顔データをブレンドして新しい顔を作るツール
  • 写真を撮影するだけでPhotoScanにより全自動でスキャンメッシュが生成されるツール
  • そのPhotoScanで生成したスキャンメッシュを短時間でゲーム用のデータに落とし込むツール

など沢山あります。

Mayaはスクリプトを覚えてツールを作れるようになるとあちこち自動化・高速化ができてとても楽しいツールです。
私はデザイナーとしてセガに入社しましたが、今ではPythonでMayaのツールを作るのにすっかりハマっています。
このブログを読んでいるデザイナーなのにツールを作って自動化するのが大好きな方!クオリティの高い作品作りに懸命に取り組む弊社デザイナーたちの作業効率化に向けた環境づくりに、共に取り組んでいただけませんか?
もちろん、そうではない方も「龍が如くスタジオ」であんな芸能人やこんな有名人の方々と一緒にお仕事してみませんか?
「龍が如くスタジオ」は一緒にゲームを作ってくれる仲間を募集中です!

sega-games.co.jp

それでは皆さま、よいお年をお迎えください!


©SEGA

 

*1:「龍が如く」プロジェクトの方言で簡易的なフェイシャルアニメーションが適用されるスペックのキャラクター

*2:プロジェクターから発する光のパターン画像をデジタルカメラで撮影するという技術

*3:河童や小野ミチオなど

*4:シェーダーによって前後します

*5:企業秘密★

*6:つい最近まで「龍が如く」シリーズのキャラクター班はSoftimageで開発していました

20年オヤジのUnreal Engine 4 TIPS

皆さん、はじめまして!
セガ・インタラクティブ技術統括室の林田です。

1997年入社、20年越えのおっさんプログラマです。入社以来、おおむねアーケードゲームに携わってます。

いくつか私の代表作を紹介しておきましょう。Crazy Taxiシリーズ、頭文字D Arcade Stageシリーズ、WCCFシリーズなどなど。古いタイトルもありますので、あまりご存知ないかもしれませんね。ご興味があればWebやYoutubeなどで検索してみてください。その他、変わり種としては、初代ダーツライブのCPU戦、Answer×Answer Pocket (Objective-Cネイティブ!)、あとは古い時代の部内ライブラリなども担当したことがあります。

主な担当分野は、キャラクタアニメーション周り、あとは「火消し」と呼ばれる厄介な事件を解決する人、最近ではアンリアルエンジン4全般を担当しています。

今回の記事は、セガテックブログ初(!)の「アンリアルエンジン4(以下UE4)」についてです。初のUE4記事ゆえ力が入ってしまい、とても長くなってしまいました。お時間のある時にゆっくり読んでいただければと思います。

この記事は、UE4の(ちょっとマニアックな)TIPSを中心に書かせていただいています。なお、ちょっと紙面を余分に頂いて、我々がUE4タイトルをリリースする前にやっていた実験などの紹介などを、前半に書かせていただいています。「前置きはいいから、本題を!」という方は、途中を飛ばして「マクロは悪?」のあたりから読んでくださいませ。

もくじ

セガ製UE4タイトルは、お近くのゲームセンターで好評稼働中です。

見かけた際には、ぜひプレイしてみてくださいね!

セガUE4タイトル第1弾「SEGA World Drivers Championship」


『SEGA World Drivers Championship 2019』アドバタイズムービー

開発チームより

UnrealEngineの機能を存分に使って、SUPER GTの実車両や実在コース(実在コースは期間限定)を再現しています。
ぜひ、体感してみてください。

セガUE4タイトル第2弾「HOUSE OF THE DEAD ~SCARLET DAWN~」


HOUSE OF THE DEAD ~SCARLET DAWN~ JAEPO 2018 MOVIE

開発チームより

13年ぶりにアーケードゲームへ帰ってきました。
ゲームのグラフィックやプログラムは全てUE4エンジンで完結しています。
またアーケードゲームらしく、筐体の音や振動が非常に凝った演出となっていますので、お近くのゲームセンターで見かけたら、ぜひプレイしてみてください。

言い出しっぺが語るセガ×UE4

何を隠そう、私がセガのアーケードゲームにUE4を持ち込もうと騒ぎ始めた張本人、いわゆる「言い出しっぺ」です。そしてご多分に漏れず「言い出しっぺの法則」が発動しまして、ここ数年は社内UE4担当大臣として活動しています。

なんで自社ライブラリを持っている会社がゲームエンジンを?等々、会社の政治的・経済的・人事的な事情は脇に置いておいて(とは言え、おおむね多くの大手ゲームメーカーさんと同じ事情だと思います)、ここは「テック(技術)」ブログですから、UE4を使ってモノを作る話をしましょう。

タイトルを作る前は、こんな実験をしてました。

UE4を甘噛みしてみる実験(UE4.3のころ)

f:id:sgtech:20181125225944p:plain f:id:sgtech:20181125225942p:plain

四輪駆動のおもちゃが壁のあるコースを走るアレです。あのおもちゃと同様、前進しかできず、コリジョンで(壁に沿って)曲がるように作られています。原理的には非常に簡単ですので、興味のある方はぜひ真似をしてみてください。

作り方ヒント

  • f:id:yoshimasa_hayashida:20181019113423p:plain こういうポリゴンを使って、スプラインメッシュでコースを作っています。コリジョンは見た目よりもかなり高い壁を作りましょう。でないとクルマが簡単に吹き飛んでコースアウトします。
  • 車はアクセルボタンに応じた前進しかしません。あとは運を天に任せて、うまく曲がってくれるのを祈る感じです。場合によっては吹き飛んでしまいますが、それはそれで楽しいかと思います。何ならブレーキ操作をできるようにして、コーナーではうまく減速するゲームにしても面白いでしょう。
  • コースの各所にトリガーボリュームなどを置いて、「時間内に通過するとパワーアップ」などのギミックを入れても面白いでしょう。

 格好いい映像を作ってみる実験(UE4.5のころ)

上記の「四輪駆動のおもちゃ」はプログラマ主導でやっており、アーティストにはコース作成だけお願いしていましたが、今度の実験はアーティスト主導で行いました。この実験ではSubstance Designer/Painterも試しています。 

f:id:sgtech:20181125230654p:plain f:id:sgtech:20181125225931p:plain

さすが本職の人が作ると違いますね。HOUSE OF THE DEADを意識するというテーマを悪用して(?)、作ったメンバーの趣味が存分に盛り込まれています(笑)。

UE4の特徴である「物理ベースレンダリング」を活かそうと、光と影を意識した映像の実験となります。従来のセガのアーケードゲームとは多少趣が異なり、明暗のコントラストを強めに出しています。いわゆる「洋ゲー」に寄せた、といったところでしょうか。

一通りゲームを作ってみる実験(UE4.9のころ)

ここまでの実験である程度UE4が分かってきましたので、今度は一連のゲームの流れを作る実験をしました。何らかのタイトルプロジェクトを発足させる前に、踏める地雷はあらかじめ踏んでおこう、がテーマです。

パーティクルエフェクト担当のアーティストや、ユーザーインターフェース担当のアーティストも参加して、チェインクロニクルをアーケード向けにアレンジしたらどうなるか?を試しに作ってみたものです。

f:id:sgtech:20181125225927p:plain f:id:sgtech:20181125230016p:plain

本家チェインクロニクルの倍(10人!)のプレイヤーキャラクタ、倍どころじゃない数の敵、パーティクルエフェクトが派手に飛び交い、カメラもグルングルン回る。これを80型の大型タッチパネルモニタで両手を振り回して遊ぶ。アーケードゲーム化するんだったら、派手に!大味に!半ばパニックになっちゃうくらい忙しい!そんな風にしちゃいました。これは作るのも楽しかったですね。

この実験成果の報告会では、100名を超える社員が会議室に集まってしまい、大半は立ち見、エアコンが追いつかず会議室はどんどん暑くなるという事態に。おかげさまで社内へのUE4の認知度は高まり、UE4採用プロジェクトとしてSWDCとHODを発足させることが出来ました。

ご注意いただきたいのですが、本家チェインクロニクルがUE4に移植される予定も、アーケード版をつくる予定も全くありません。あくまで実験の題材として使用しただけですので、「次のチェインクロニクルはUE4だ!」なんて、間違った情報をうっかり拡散してしまわないようにお願いしますね!

さて、今回の記事は…

20年越えのオッサンプログラマが、その経験をBlueprintに活かすとどうなるか、そういう話をできればいいなと思っています。

UE4の紹介記事などもWebを検索すればたくさん見つかるようになりました。申し訳ありませんが、この記事には初心者向けの説明はあまりありません。それよりはもうちょっと深い部分、私視点でのUE4の「コーディング(code-ing)」ならぬ「ノーディング(node-ing)」事例などを紹介したいと思っています。

初心者の皆さんには、有名な書籍をご紹介。

初心者お断り!なんてバッサリ斬り捨ててしまうのも心が痛いので、書籍を数点ご紹介しますね。本を見つつ、Webで検索しつつ、おそらく最初は苦労されるかとは思いますが、それを乗り越えて楽しくなってきたら、ぜひこの記事に戻ってきてくださいませ!

では、書いてある通りにやれば、何か動くものが出来上がる、おそらくはUE4ユーザーの大半がお世話になっている良書から。

Unreal Engine 4で極めるゲーム開発:サンプルデータと動画で学ぶUE4ゲーム制作プロジェクト

Unreal Engine 4で極めるゲーム開発:サンプルデータと動画で学ぶUE4ゲーム制作プロジェクト

 

 同じく、「書いてある通りにやれば何かできる」もう一冊。

作れる!学べる!Unreal Engine 4 ゲーム開発入門

作れる!学べる!Unreal Engine 4 ゲーム開発入門

 

 こちらは残念ながら、おそらくUnityで使われてただろうキャラクタモデルが流用されており、Unlit(ライティングなし)前提ですので、UE4の美しいレンダリングには触れられていません。それでもキャラクタをアニメーションさせて動かす等々、おそらく多くの皆さんがやりたいと思っているだろう要素は、ちゃんと揃っていますのでご安心を。

もっとお手軽に試してみたい方には、書籍ではありませんが、こちらの動画はいかがでしょうか。


Unreal Engine 4 勉強会 『40分でBlock崩しを作る』(1)

これは「Unreal Engine 4で極める…」の著者、湊さんによる勉強会での公演の動画です。私も一番最初にこの動画をマネをすることで、UE4へのとっかかりを得ました。動画では画面の細かい文字が見えませんが、Blueprintのスクショを貼ってくれた方がいらっしゃいますので、こちら(↓)のページと合わせて見ると良いでしょう。

www.jonki.net

なお、この動画は慣れた方がサクサク組んでいらっしゃいますが、初心者にはテンポが速すぎて追いつけません(当時の私もそうでした)。一時停止や巻き戻しを繰り返して追いかける感じになりますので、決して、40分では完成しません。悪しからずご了承ください。2~3日かけてゆっくりやるくらいの心づもりで。

UE4はアップデートの周期がとても短く(この記事の執筆時点でUE4.20.3。記事のチェックをしている間にUE4.21が出てしまいました)、気を抜くとあっという間に情報が古くなってしまいます。上記3つの書籍・動画は古いバージョンのものですので、お手持ちのUE4バージョンとは違い、もしかしたらメニューの位置やコマンドの名前などが変わってしまっているかもしれません。新旧のバージョン間の機能の違いを、Web検索などを駆使してなんとか見つけ出すのもUE4の勉強の一環だと思って、頑張ってみてください。

Web上では、UE4は難しい…という声をチラホラ見かけます。

確かにそうかもしれません。私も最初の2か月くらいは、UE4が嫌いでした。
それを乗り越えて、ざっくりでもUE4が分かってきたころから、逆にサクサクものが作れていく感覚を覚え始めます。今では「テキストエディタを開いてプログラミング言語を入力する」こと自体に抵抗を覚えるくらいのUE4信者になってしまいました。特にBlueprintとMaterialは手放せません。C++/C#の時代は終わったなんて言っちゃっています。

皆さん、最初は我慢です!(これ、プロからの助言としてはどうかと思いますが…)

UE4に限らず、初めて使うツールって、体に馴染むまでは難しい顔してやるもんでしょ? ましてや、UE4はAAAタイトルすら作れちゃうくらいの豪華ツールです。大量の機能や設定の中から、今自分が欲しい機能を見出せるようになるには時間をかけて慣れるしかありません。

今回つかう題材は、UE4ぷちコン応募作品です。

株式会社ヒストリアさん主催の「UE4ぷちコン」はご存知でしょうか。毎回テーマが出され、それに沿ったゲームや映像作品をUE4で作って応募する、ちょっとしたゲームコンテストです。「UE4の勉強の成果」を発表する、勉強会の延長線上にあるもので、誰でも参加できます。皆さんも参加してみてはいかがでしょうか。

一方で、意外と本気勢も多く(私もその一人ですね)、「ガチコン」と言われてしまうこともあるようです。ヒストリアの佐々木社長はこの言葉を嫌がっているようですが、幸か不幸か、初心者作品からプロ作品まで非常に幅広いレベル帯の作品が集まるコンテストになっており、自分の現在のレベルと、次に目指すべきレベルが見えやすい、とても良いイベントです。

それはともかく、今回の記事は、私が第9回ぷちコンに応募したゲームを題材にお話しようと思っています。

第9回ぷちコン応募作品「Gravity One」


【UE4ぷちコン#9応募作品】GRAVITY ONE

テーマは「ループ」。なので、丸い地球上(マップがループしている)でのシューティングゲームを作ってみました。ありがたいことに、「カナマイ賞」を受賞することができ、アンリアルフェスWEST2018でプレイアブル展示していただきました。

社内でも、毎日昼休みにスコアアタックをしてくれるドハマリプレイヤーが出現。最終的にノーミスクリアを達成! 記念にご本人の強い要望に応えて、このゲームのexeを進呈しました。

第10回ぷちコン応募作品「MAGITYPE」

つい先日行われました、第10回ぷちコンにも応募させていただいています。


【UE4ぷちコン#10応募作品】MAGITYPE

テーマは「ぷち」。ぷちコンの「ぷち」です。
安易に思いつく「プチプチ」(梱包材のアレです)は断固避ける!と意気込んだものの、制作期間の6割くらいの間、ずっと迷走してしまい、結局は未完成のままの応募になってしまいました。テーマ「ぷち」の回収方法として私が選んだのは、過去10回の「ぷち」コンのテーマを全部使う!明らかに盛りすぎですね。間に合うわけもありません。

審査結果発表会では軽くご紹介いただきましたが(動画2:15:00くらいから)、もちろん落選です。プロとして恥ずかしい限りです。

そして、いまだに鋭意作成中…(どうなることやら)

この「MAGITYPE」からも、ご紹介したい事例がいくつもあるのですが、長くなってしまいそうなので、また次の機会に。例えば「MAGITYPE」が完成した後などに記事にできればと思います。

(困ったことに、2018年内にすら完成させられる自信はありませんが…)

プロなのに応募してずるい!

いやいや! 応募要項には「日本在住のUE4ユーザーなら誰でもOK!」と書いてあります。

…いえ。確かにズルいとは思っています。ですので、自分なりにハンデを設けて作っています。

  • 「絵も音もプログラムも、全素材を自分一人で作る!」

面白そうでしょ? やること多くて死ねますけど。

21年もゲームプログラマをやっていますと、アーティストやサウンドデザイナーとのやり取りなどで、それなりにツールの使い方や用語などは覚えるものです。なので、箱を作ってテクスチャを貼れる程度には使い方を知っています。まあ、その程度の技量で全素材づくりに挑むのですから、十分ハンデかと。

おかげさまで、2回のぷちコン参加で、SDキャラクタ作成・テクスチャ作成・アニメーション作成なども自分でできるようになりました(クオリティはともかく)。ほら、ちゃんと勉強になっているから、ズルくない!

参考までに、プログラムは組めるけど絵素材や音素材がなくて困っている皆さんや、インディーズを目指している皆さんに、私が使っているツールをご紹介しておきますね。(ちょっと世間の流行りからはズレていますけど…)

MODO indie(月々1500円ほど)

3Dモデリングツールの中では新しく、古い時代のしがらみなどが少ない分かりやすいツールです。MODO JAPAN GROUPのページにはチュートリアル動画やTIPS動画がたくさんあり、学習のしやすさもオススメしたい理由の一つです。

store.steampowered.com

CorelDRAW Graphics Suite(買い切り6万円ほど)

私は学生のころから論文などの図の作成に使っていました。未だにアップデートを繰り返して使い続けています。いわゆるフォトショップやイラストレーターに相当するツールがセットになっています。特に CorelDRAW(イラストレーター相当)は、私や多くのアーティストでない方々のように、フリーハンドで思い通りに線が描けない人にはありがたいツールです。筆で描くのではなく、方眼紙に定規やコンパスで図を描くイメージですね。私はベジエで描いてから、ビットマップに変換する流れでテクスチャを作成しています。

www.coreldraw.com

Logic Pro(買い切り2.5万円ほど)

いわゆるDTMソフト、音楽編集ソフトです。私の使っているのは古いバージョンの方、Logic Pro 9です。当時は5.5万円ほどしていたのが、今では1.7万円ですね!驚愕です。なお、私が音楽の世界に足を踏み入れたのは、ボカロ(なぜかミクさんではなくルカさん)の影響です。ありがちですね。

www.apple.com

C-Deck(540円×3種)

トランプ大のカードにコード進行が書かれた作曲ツールです。Aメロ、Bメロ、サビカードをランダムに選ぶだけでコード進行が完成です!私はこれがないと作曲できません。古いツールのなので、今ではAmazonなどでも欠品してますね…。再販してほしいものです。

blog.shimamura.co.jp

マクロは悪? いえ、善です。(Blueprintに限る)

前置きが長くなってしまいました。
さて、ここからは具体的なUE4プログラミングやTIPSの話を書いていきましょう。

以下、Blueprintのスクリーンショットが多く登場します。紙面の都合上、どうしても画像が小さくなってしまいますが、クリックすると大きく表示されますので、その機能をご活用ください。

また、ブラウザを複数立ち上げて、片方では文章、片方では画像の拡大を表示する、などの小技を使えば、より見やすくなるでしょう。

定数はどうしてますか?

最初に固定値を決めたっきり、以後は絶対変更したくない変数、つまり定数のことですが、皆さんはBlueprintではどのようにしていますか? 残念ながら、Blueprintには定数という概念がありません。

多くの方が、例えば変数名を大文字とアンダースコア(_)の組み合わせにしてみたり、

f:id:sgtech:20181125230013p:plain

接頭辞や接尾辞を付けるなど、命名規則を工夫して対処しているかと思います。

f:id:sgtech:20181125230011p:plain

C++ならconst修飾子がありましたが、

const int MaxPositionHistory = 256;

Blueprintではそうはいきません。

ここでマクロの出番です。
特にC言語の時代を知っている人は、マクロは危険という認識から、UE4でも避けてしまっているのではないでしょうか。

#define MAX_POSITION_HISTORY (256)

こんな風に書いちゃった日には、確実に先輩にドヤされます。なぜなら、C/C++言語のマクロとは、その数値が整数型なのか実数型なのか、ましてやそれが数値であるかどうかすら、コンパイラは全く気にせず、単なる文字列の置き換えで対応してしまうからです。

ところが、ご存知の通り、UE4のマクロはきっちり型を意識してくれます。ですので、

f:id:sgtech:20181125230008p:plain

こういうマクロを作ると、これは間違いなくInteger型の256となってくれます。
そうすると、

f:id:sgtech:20181125230005p:plain

ミソは、詳細(Details)パネルの「Compact Node Title」の欄にノード名を書くことです。

f:id:sgtech:20181125230053p:plain

これを書くことで、「いかにも定数です」みたいな見た目になってくれます。

f:id:sgtech:20181125230050p:plain

ゲーム用定数マクロライブラリを用意する。

上記の例はアクタの中で作っていましたが、ゲーム全体で使う定数はどうしましょうか。答えは簡単、マクロライブラリを作れば解決します。私はUE4で何か作る際には必ず「Game Constants Macro Library」といった名前のマクロライブラリを作り、ゲーム全体で使う種々の定数を入れています。

これで、このマクロライブラリを編集するだけで、ゲーム中の様々な状態を変更することができるようになりますね。例えば、バランス調整用の定数マクロライブラリなどがあっても良いかもしれません。(ゲームエンジンの時代に定数でバランス調整って、時代遅れな気もしますが…)

なお、定数を変更したらコンパイルしなくてはなりません。ここはご注意ください。

ノードグラフをより見やすく、便利にするための定数マクロ

それでは、ゲーム専用に限らず、Blueprintそのものの見通しをよりよくするために、こういうマクロを作ってみてはいかがでしょうか?

f:id:sgtech:20181125230047p:plain

作り方は先ほどの Max Position History と同様です。Boolean型のFalseを返すマクロを作り、Compact Node Title に「False」と書くだけ。

f:id:sgtech:20181125230630p:plain

同様の方法でTrue マクロも用意しておきましょう。

これらは、作成者の意図を明示するだけでなく、ノードグラフをサクサク組むための助けにもなってくれます。「Compact Node Title」に書かれた文字列は、右クリックで出てくる検索ウィンドウに引っかかるようになってくれますので、例えば「True」や「False」と入力するだけで、今作った Trueマクロや Falseマクロがサクっと引っぱり出せるようになります。

f:id:sgtech:20181125230043p:plain

これらもマクロライブラリを作って、便利そうなものはどんどん入れておきましょう。参考までに、私のマクロライブラリに入っている便利定数をご紹介します。UE4/Blueprintライフが向上すること請け合いです。

f:id:sgtech:20181125230701p:plain

よく使う整数として「Integer 0」や「Integer 1」。このほかにも「Integer -1」などを用意してます。これを使うと、Make Literal Int を検索して、(あの小さい枠に)0を入力して、の手間がまるっと解消されます。

f:id:sgtech:20181125230037p:plain

同じく「Float 0, 1, -1」。ちなみに上記は軸との内積の正負で、回転角度のプラスマイナスを決めるような、比較的よくありそうなノードグラフですね。

f:id:sgtech:20181125230144p:plain

あの巨大な「Make Transform」ノードが非常にコンパクトになる「Transform Identity」ノード。ちなみにIdentityとは「単位行列」の意味。高校数学でしたっけ?内容は、Location=(0,0,0), Rotation=(0,0,0), Scale=(1,1,1) です。これも右クリックのノード検索メニューで「Identity」と入力するだけで出てきます。

下側のSpawn Emitter at Locationに繋がっているのは、意外と面倒な(0, 0, 0)とか(1, 1, 1)を一発で書くためのものです。

マクロ内ローカル変数を活用する。

 下のGIFアニメはぷちコン応募作品「Gravity One」に登場するザコ敵です。「プレドニゾローン」という、とても覚えられない名前がついてます。さて、このプレドニゾローン、キノコ状の「フタ(Flap)」が一定時間ごとに上がったり下がったりします。これをBlueprintで書いてみるとどうなるでしょうか。

f:id:sgtech:20181125230128g:plain

普通のコーディング例、もとい「ノーディング」例

Event BeginPlayとEvent Tick

f:id:sgtech:20181125230124p:plain

  • Event BeginPlay: 使っているメンバ変数に初期値をセットしています。
  • Event Tick: 後述のTickEvent UpdateFlap(カスタムイベント)を呼び出しています。ザコ敵とは言え、弾を撃つなど、もっとたくさんの処理がぶら下がるはずですが、上の図はあくまでサンプルだととらえてください。

TickEvent UpdateFlap

f:id:sgtech:20181125230120p:plain

  • Event Tickから呼び出され、「フタ(Flap)」の回転や上下動を行うカスタムイベントです。
  • カスタムイベントTickEvent Open/Close Flap を呼び出しています。このカスタムイベントは、フタの高さ(Float FlapHeight)を決めています。
  • FlapYaw変数の値を算出しています。現在のYaw角度+DeltaSeconds × RotationSpeedで、次の角度を求めています。
  • Set Relative Location And Rotation で、フタモデルSM_Flap (Static Mesh Component)の相対位置と相対角度をセットしています。

TickEvent Open/Close Flap

f:id:sgtech:20181125230116p:plain

  • TickEvent UpdateFlapから呼び出され、フタ(Flap)の上下動を行います。
  • Sequence - Then 0側:
    ・経過時間(Float ElapsedSeconds)を算出しています。
    ・経過時間÷開け閉めの秒数(Open/Close Duration)で開け閉めの割合(Alpha)を求ています。
    ・フタの高さを求めるマクロ(Make Flap Height Macro)(後述)に、開け閉めの割合(Alpha)と現在の開け閉め状態(Boolean CloseFlag)を渡しています。
    ・算出された新しい高さを、Float変数 FlapHeightに格納します。
  • Sequence - Then 1側: 経過時間が、開け閉めの秒数(Open/Close Duration)+待ち秒数(Wait Duration)を越えたら、経過時間をゼロクリアし、開け閉め状態のフラグ(CloseFlag)を反転します。
  • Open/Close Duration、Wait Duration、Elapsed Seconds の使い方がちょっとだけトリッキーなので注意してください。
    ・Elapsed Secondsの変化する範囲は 0~(Open/CloseDuration+WaitDuration)ですが、Make Flap Height Macro に渡す Alphaの値は WaitDurationを考慮に入れていないため、1を超えてしまいます。
    ・Make Flap Height Macro 内で Alphaの値を0~1にクランプしています。これで、Wait Duration秒数だけ開いたまま/閉じたまましばらく待つ、という状態を作ってます。

Make Flap Height Macro

f:id:sgtech:20181125230226p:plain

  • 入力された開け閉め率(Float Alpha)を0~1の範囲にクランプしています。
  • 入力された開け閉め状態のフラグ(Boolean Close)に応じて、OpenHeightからCloseHeightまでのをEaseするのか、CloseHeightからOpenHeightまでをEaseするのかを切り替えています。
  • Easeで求められた高さをNewHeightとして出力します。

多少トリッキーな書き方をしているのは認めますが、考え方は至って普通です。

  • 経過時間を計算する。
  • その経過時間から、フタの高さを求める。
  • 一定時間経過したら、開け/閉めを切り替える。

さて、ここで話題に挙げたいのは、この「フタの開け閉め」で使われている4つのメンバ変数です。

f:id:sgtech:20181125230223p:plain

  • Boolean CloseFlag: このフラグがTrueの時は閉め、Falseの時は開け、の動作を行う。状態切り替えのためのフラグ。
  • Float ElapsedSeconds: フタを開け始めてから/あるいは閉め始めてからの経過時間(秒)。
  • Float FlapHeight: フタの高さ(Z値)
  • Float FlapYaw: フタの回転角度(Yaw値)

 

さて、この、至って普通のノーディング、20年オヤジは何が気に食わないのでしょうか。

これらの変数は、単にフタを開け閉めするだけのための用意されました。しかし、フタの開け閉めなんて、このActorにとっては影響度/重要度はかなり低いです。私としては、重要度の低い変数(や関数)は、できる限り重要度の高いものとは分けて置いておきたいのです。本音を言うと、メンバ変数にすらしたくない。さもないと、クラスに機能が増えていくにつれ、メンバ変数の欄はどんどん肥大化し、重要度の高い変数と低い変数が同レベルでゴチャっと並んでしまうのです。

実はこの問題、マクロ内ローカル変数を使うことで解消できます。

マクロ内ローカル変数で重要度の低いメンバ変数を減らした例

(Event BeginPlayやEvent Tickは、おおむね変更はなく、想像がつくと思いますので割愛します)

TickEvent UpdateFlap (Modified)

f:id:sgtech:20181125230220p:plain

まずは簡単にできるところから。

フタの回転角度として使っていたメンバ変数FlapYawをなくしてしまいました。その代わりにUpdate Yaw Macroというマクロを作りました。これがミソです。

Update Yaw Macroの中身はこうです。

f:id:sgtech:20181125230217p:plain

Local Floatノードが「マクロ内ローカル変数」です。

  • 普通に「+」ノードなどに挿して、Floatとして使えます。
  • いわゆるSETノードに相当するののが「Assign」ノードです。
  • 上の図では「マクロ内ローカル変数に、YawAdderを足して、マクロ内ローカル変数にAssign(セット)する」「マクロ内ローカル変数をNewYawとして出力する」が行われています。

マクロ内ローカル変数の制限などについて。

  • 変数名がつけられませんので、複数のマクロ内ローカル変数を使うときは混乱しがちです。
  • 使う場合は、Local Floatから必ずラインを伸ばす必要があります。上の図のように、ひたすらLocal Floatノードから、相手のノードに直接つなげる必要があります。したがって、マクロ内ローカル変数をたくさん使うと、非常にたくさんのラインが飛び交うスパゲッティコードになりがちです。

このマクロ内ローカル変数、「Event Graph内に限り、常に値は保持される」という特徴があります。例えば、入力イベントに1つカウントアップするマクロを繋げると、ボタンを押すたびに1, 2, 3... と一つずつ増えてくれます。このアクタが破棄されるまで、この値がクリアされることはありません。(もちろん、自分でクリアした場合は除きます)

f:id:sgtech:20181125230212p:plain

ボタンを押すたび1増やす例

f:id:sgtech:20181125230325p:plain

Count Upマクロの実装

ただし、イベントグラフ内ではなく、関数内でマクロ内ローカル変数を使った場合、関数呼び出しのたびに値がクリアされますので注意してください。例えば、関数内で DoOnce マクロを使って、思ったように動かなかった経験のある方はたくさんいらっしゃると思います。これもマクロ内ローカル変数のこの性質が原因です。(どちらかというと、イベントグラフが特殊で、関数が普通の挙動をしているのですが…それはさておき)

興味のある方は、UE4標準の種々のマクロの実装を見てみてください。ダブルクリックするだけで見られます。皆さんが良く使うであろう ForLoop や ForEachLoop もマクロ内ローカル変数を使って実装されていますよ。

TickEvent Open/Close Flap (Modified)

さて、次は TickEvent Open/Close Flap関数がどう変わったか見てみましょう。

f:id:sgtech:20181125230322p:plain

こちらは比較的トリッキーです。戸惑われた方もいらっしゃるかもしれません。マクロが2か所あるのは良いとして、恐るべきことに(?)1つのグラフにカスタムイベントが3つ繋がっています。

それでは個々に見ていきましょう。まずは、Elapsed Seconds Macroから。経過時間を格納していたメンバ変数Elapsed Secondsをこのマクロで置き換えています。

f:id:sgtech:20181125230318p:plain

ここまでに紹介してきたマクロとほぼ同じような形ですが、InputsノードにあるResetピンで経過時間をゼロクリアできるようにしてあります。カスタムイベントTickEvent Open/Close Flap (Modified)のグラフをもう一度見てみてください。このResetピンには、カスタムイベント Reset ElapsedSeconds が繋がっており、さらにそのReset ElapsedSecondsイベントは、Sequence - Then 1の、時間経過後のOpen/Close切り替え時に呼び出されています。ここで、ElapsedTimeにゼロをセットするのと同等の処理を行っているわけです。

次に Make Flap Height Macro (Modified) マクロについて。これは「普通版」の同名マクロを変更して、メンバ変数CloseFlagを置き換えました。

f:id:sgtech:20181125230314p:plain

変更前のMake Flap Height Macroと見比べていただくと分かると思いますが、

  • CloseFlagの代わりにマクロ内ローカル変数を用意。
  • Toggle Open/Close ピンを新設して、CloseFlagの役割をするBoolean型のマクロ内ローカル変数のTrue/Falseを切り替え。
  • Toggle Open/Closeピンには、カスタムイベントToggle Open/Closeを繋げ、時間経過でのOpen/Close切り替えのタイミングでこのカスタムイベントを呼び出し。

という仕組みになっています。

いかがでしょうか。これでActorクラスにとって重要度の低い3つの変数 CloseFlag、ElapsedSeconds、FlapYawがメンバ変数から消えて、まさに必要な部分だけに登場する適材適所なグラフになりました。
(その代償として、初心者お断りなトリッキーなグラフになっていますが…)

次はさらにマニアックな技です。ご注意を!

さて、重要度の低い4つの変数のうち3つを「適材適所」な位置に移すことが出来ました。こうなると、最後の1つFlapHeightもメンバ変数から外してしまいたくなります。

あらかじめ言っておきます。ここから先はマニアックな使い方になります(ここまでも十分マニアックかもしれませんが)。ですので、他人がノードグラフを見ると、頭の上にクエスチョンマークを浮かべてしまうかもしれません。あるいは先輩に「やりすぎだ!可読性を上げろ!」と怒られてしまうかもしれません。

f:id:sgtech:20181125230310p:plain

  • TickEvent UpdateFlap (Maniac)
    ・TickEvent UpdateFlapのマニアック版です。
    ・ここまでに登場した同名イベントと同様、算出された高さと回転角度をフタ(Flap)モデルにセットしています。
  • TickEvent Open/Close Flap (Maniac)
    ・TickEvent Open/Close Flapのマニアック版です。
    ・ここまでに登場した同名イベントと同様、経過時間を計算し、そこからフタ(Flap)の高さを算出しています。
  • Flap LotationマクロとFlap Rotationマクロ
    ・Set Relative Location And Rotation 関数のNew Location引数とNew Rotation引数をバラすのが面倒になったので、マクロを作って見た目をシンプルにしています。
    ・Flap Location マクロ
     f:id:sgtech:20181125230429p:plain
    ・Flap Rotationマクロ
     f:id:sgtech:20181125230426p:plain

さて、このマニアック版でのミソは、カスタムイベントSet Flap Height (Maniac)と、Set Flap Height Macroです。

Set Flap Height Macro

f:id:sgtech:20181125230422p:plain

  • Inputsノードに指定されたマクロの引数In Flap Heightを、Float型のマクロ内ローカル変数に格納しています。
  • Outputsノードから、その格納された値をOut Flap Heightとして出力しています。

一見、全く何の意味もないプログラムのようにも思えますが、このマクロは「Event Graph内に限り、マクロ内ローカル変数の値は保持され続ける」特性を利用して、変数のGETとSETの両方の機能を持たせたものです。

  • SET機能: カスタムイベントSet Flap Height (Maniac)が呼び出されると、その引数の値をマクロ内ローカル変数にSETする。
    ・つまり、カスタムイベント、TickEvent Open/Close Flap (Maniac)でフタ(Flap)の高さが決まった際に、FlapHeight変数にその高さをSETする代わりにカスタムイベントSet Flap Height (Maniac)を呼び出します。
    ・SET(Assign)された値は、再びSET(Assign)されるまで、変更されることはありません。
  • GET機能: Out Flap Height を通じて、マクロ内ローカル変数に格納された値をGETする。
    ・マクロ内ローカル変数に格納された値は、Event Graph内であれば、いつでもGETすることができます。

このマクロを用いて、TickEvent UpdateFlap (Maniac)や TickEvent Open/Close Flap (Maniac)イベントは、以下のメカニズムで動いています:

  • TickEvent UpdateFlap (Maniac)で、Set Relative Location And Rotationが呼び出される際、Set Flap Height Macroの出力Out Flap Height経由で、マクロ内ローカル変数に格納された値(現時点のフタ(Flap)の高さ)をGETしています。
  • TickEvent Open/Close Flap (Maniac)で、Make Flap Height Macro (Maniac)で算出されたフタ(Flap)の高さを、Set Flap Height (Maniac)イベントを使って、マクロ内ローカル変数にSETしています。

さすがに無理矢理やった感は否めませんが、Actorのメンバ変数からはフタ(Flap)の上下動に関する4つの変数をメンバ変数欄から消すことができ、Event Graph内の必要なところでのみGETあるいはSETされる「適材適所」な形に整理することができました。

マクロ内ローカル変数を活用する際の注意点

  • デバッグが難しい: ウォッチができないので、Print Stringなどのログ出力を使う必要があります。
  • スパゲッティになりがち: マクロ内変数が増えるたびに、おびただしい数のラインが増えてしまい、可読性が非常に下がります。

簡潔に言うと、気を付けないとバグの温床になりやすい、ということです。そういう意味では、UE4やプログラミングにある程度慣れた人以外は、多用しない方が良いかもしれません。まずは、小さなサンプルを作って試し、正しく動くことを確認出来たら本使用に移すなど、守備的な方法で運用するように注意してください。特にチームで作業している場合は、チーム全員でマクロ内ローカル変数をどう使うかの意思統一してから使うべきでしょう。

弾幕で分かるBlueprintの限界

さて、次はBlueprintの最適化とプロファイリングの話をしましょう。

ぷちコン応募作品「Gravity One」は弾幕シューティングを目指しました。その意気込みの割には密度が薄かったのは反省点ですが… やはり弾が多いと処理落ちしてしまうのです。

Blueprintは重いと知りつつも、Blueprint信者としてC++に逃げることは許されません。結局、弾一個一個をアクタで作ってしまいました。結果、本当に重くなってしまい、ボス戦などでは60FPSをキープできていません。というか、40FPSくらいです。ホント、プロとしてお恥ずかしい限りです。

f:id:sgtech:20181125230408p:plain

この弾幕の最適化、今までずっと放置していたのですが、今回のブログのネタとしても面白そうだと思ったのでやってみました。Blueprint信者としての信念を曲げてのC++化もやっています。ここではそのあたりのお話をさせていただこうと思います。

当初から重くなるのは覚悟してました。

もともと弾幕を出すつもりでしたので、作り始めた当初から重くなるのは想定していました。ですので、弾のActorはできる限りシンプルに組んだつもりです。

まずは、Tickの仕事を最小限にするように気を付けました。

f:id:sgtech:20181125230359p:plain

  • Sequence - Then 0: 移動計算は地球表面に沿って直進するだけ(Ballisitic Object.Update)。その結果をSet Actor Location And Rotationに渡している。
  • Sequence - Then 1: Dynamic Material Instance を使って、時間に応じて色を変化させる。
  • Sequence - Then 2: 一定時間で自滅させる。

コンポーネントの構成も非常にシンプルです。

f:id:sgtech:20181125230512p:plain

  • Sphere Collision: ルートコンポーネントとして球コリジョンを使っています。
  • Bullet: その子コンポーネントとして、Material Billboard Component を使っています。つまりメッシュですらありません。

コリジョンプロファイルも限定的にしました。

f:id:sgtech:20181125230509p:plain

  • プレイヤーオブジェクトに対してのみ「Overlap」
  • その他のオブジェクトに対してはすべて「Ignore」
  • 「Block」は一切使っていません。

セッションフロントエンドでCPU負荷をチェックしてみる。

セッションフロントエンド、使ったことのない方は、これを機にぜひ使ってみてください。使い方は、UE4公式にあります。ちゃんとわかりやすく書かれているのでご安心を。

では、ボス戦をDevelopmentパッケージで調べてみた結果を見てみましょう。

f:id:sgtech:20181125230505p:plain

画像をクリックすると拡大して見られますが、それでも見づらいと思いますので要点をまとめます。

  • 敵の弾Actor (BP_EnemyBullet01)がダントツで重い。14.404 ms。案の定、弾幕がボトルネックになっていた。

そのうち顕著なのは、

  • Set Actor Location And Rotation 7.167 ms/コール数 814.3
  • BP_BallisticObject.Update 2.582 ms/コール数 814.3

となっています。

ちなみに60FPSですと、1フレームは16.667 msです。
つまり、弾幕だけでCPUリソースの14.404 / 16.667(9割弱!)使っていることになります。

最初からシンプルになるように組んだことが功を奏しています。なぜなら、ボトルネックのトップとしてUE4の標準関数(Set Actor Location And Rotation)が挙がっており、自作の関数たちは次点以下になっているからです。自作の関数BP_BallisticObject.Updateが次点ですので、ここはもちろん考察しておくべきですね。

まずは、自作関数を見てみましょうか。

では、ボトルネック次点となった自作関数BP_BallisiticObject.Updateから。

f:id:sgtech:20181125230459p:plain

正直なところ、この関数もとてもシンプルで、これを最適化しろと言われると困ってしまいます。やっていることは、1フレーム前の「弾を地球中心から見たベクトル」を、今回の移動量分だけ地球の中心で回転しているだけです。

この関数については、最適化方針が見出せなかったので見送ります。これ以上軽くするには「とんち」(試行錯誤の労力、あるいは天才的な閃き)が必要そうです。後述しますが、結局は、こんなシンプルな関数でも大量に呼び出されるとボトルネックとなってしまうということですね。なんせ、ボス戦では700~1000個の弾が動いているわけですから。

では、Set Actor Location And Rotation をどうにかしましょうか。

念のため言っておきますが、UE4標準関数のSet Actor Location and RotationのC++コードにテコを入れるという話ではありませんよ。ここで問題視したいのは、コール回数の多さです。ざっくり調べてみたところ700~1000回の呼び出しがありました。ですので、今回の最適化の方針は

  • Set Actor Location And Rotation を呼ばなくていい時は呼ばない

ということにしてみます。

それはそうと、Set Actor Location And Rotation をはじめ、アクタのトランスフォームをセットする関数(以下、総称して Set Actor Transform系と呼びます)が重いということを知らない人が意外と多いようです。

f:id:sgtech:20181125230455p:plain

せっかくセッションフロントエンドを開いていますので、Set Actor Location And Rotation のやっていることをざっくり確認してみましょう。

f:id:sgtech:20181125230559p:plain

見づらいでしょうから、要約します。なお、エンジンのC++コードを開いて詳しく調べたわけではなく、関数名から想像した話でしかありませんので、ご注意を。

GeomSweepMultiple 2.480 ms/コール数 814.3

関数名から、コリジョンのSweep形状を作っていると想像できます。Set Actor Transform系の「Sweep」パラメータが関連するのでしょう。

f:id:sgtech:20181125230556p:plain

ということは、Sweepしない方が軽いのでしょうが、さすがにSweep処理なしでシューティングゲームを作るのは至難の業ですね。

UpdateComponentToWorld 2.037 ms/コール数 814.3

関数名から、そのアクタに付随するコンポーネントのトランスフォームをワールド系に変換していると想像できます。つまりは、コンポーネントが多ければ多いほど重くなるのでしょう。

Set Actor Location And Rotationのその他の処理

  • Component PostUpdateNavData: NavMesh系の処理だと想像できます。NavMeshが不要なら切りましょう。
  • UpdateOverlaps Time, MoveComponent FastOverlap: どちらもコリジョンのOverlap系の処理だと想像できます。

これらをまとめると、下記のようになるでしょうか。

  • Sweepが不要なら、積極的にOFFにする。
  • コンポーネントの数を必要最小限にする。
  • NavMeshが不要なら、積極的にOFFにする。

また、Set Actor Transform系が重いということから、UE4の基本として:

  • そもそも、Set Actor Transform系の呼び出しは、Tick1回の流れで1回だけにとどめるべき。

についても留意するようにしましょう。

カリング処理を作ってみる。

f:id:sgtech:20181125230551p:plain

上の画像は、地球を非表示にした状態でのボス戦です。見ての通り、実は地球の裏側でも弾幕が動いていたのです。どうしようもなく無駄ですね。このような見えないところにあるオブジェクトの処理を省くことを、カリング処理といいます。

カリング処理については、Gravity Oneを実装していた当初から、気にしつつも放置してきた部分です。と言いますか、プロとしてカリング処理をしていないこと自体お恥ずかしい。それくらい基本中の基本です。では、当時の私はなぜカリング処理を実装しなかったのか。もちろん1番の理由は、やることが多すぎてそれどころじゃなかったからですが、実際のところ他にも躊躇する理由があります。

  • 重いと言われているBlueprintでカリング処理を組んだ場合、逆にそのカリング処理が重くてボトルネックになってしまう可能性がある。

Blueprintでは関数コールは重い処理の一つです(関数名で検索してから呼び出す仕組みとの話です)。ノード1個1個が関数に相当しますから、ノードの数が増えると比例して重くなる、ということです。したがって、カリング処理もできる限りシンプルに組む必要がありますが、カリング処理の多くは算術演算が比較的多くなってしまいがちです。ご存知の通りBlueprintでは「+」や「-」すら一つのノードになっています。これも、もちろん関数呼び出しです。つまり算術演算は関数コールが増えやすいものの一つです(なお、Math Expressionを使って軽減することはできます)。この算術演算周りの負荷が高くなってしまった場合、せっかく作った最適化処理(カリング処理)は逆効果となってしまいます。ここを懸念していました。

Blueprintでの最適化処理は、(今の私のUE4技能レベルですと)「とりあえず作ってみて、効果を確認する」の試行錯誤を繰り返すことが必要だと思います。労力がかかる上に、うまく行くかどうかが不明。困ったものです。そういう意味では、Blueprintでの最適化に悩まずに、とっととC++へ移行した方が、費用対効果は高いかもしれません。

今、このブログの執筆時点では、時間に追われているわけではありませんし、面白そうだし!ということで、やってみました。

内積1発で済ますカリング処理。

上記に書いた通り、Blueprintで組んだカリング処理が重くなっては意味がありません。ある意味、C++で組まれたUE4標準関数Set Actor Location And Rotationに勝てるような、できる限りシンプルなカリング処理を選ぶ必要があります。そこで今回は「内積1発でざっくり判定」する方法を選びました。

もともと、プレイヤー機や敵の弾をはじめ、すべてのアクタのUpベクトルが地球表面の法線ベクトル(球の中心から球面を垂直に突き抜けるベクトル)に一致するように作ってあります。このUpベクトル同士の内積を取れば、プレイヤー機と敵の弾の「球面に対する」位置関係が分かるはずです。内積の値が1に近ければ、球面上でプレイヤー機に近い位置にいて、-1に近ければ、球面上では反対側にいるはずです。

結果、弾のアクタのTickはこうなりました。

f:id:sgtech:20181125230547p:plain



  • クラスメンバ変数Location (OptTest)とRotation (OptTest)を用意。修正前ではGet Actor Locationから取得し、Set Actor Location And Rotation でセットしていたところを、いったんこの変数に保存している。これは、Set Actor Location And Rotation を呼ばなくても、Tickのたびに更新される位置と回転を保持しておくため。
  • 修正前ではSet Actor Location And Rotationを呼び出していた場所で、カスタムイベント「TickEvent Cull If Outsight」を呼び出している。このカスタムイベントについては後述する。
  • その他の個所については、変更なし。

カスタムイベント TickEvent Cull If Outsight

f:id:sgtech:20181125230625p:plain

  • TickEvent Cull If Outsight: プレイヤー機と敵の弾のUpベクトル同士の内積の大小を判定しているところ。Math Expressionの 0.7071は cos(45度)の値。つまりプレイヤー機のUpベクトルとの角度差が45度以内のものを可視と判定している。
  • SubEvent Cull If Outsight > Sequence - Then 0: 可視圏内/可視圏外へ切り替わった時の処理。SweepのOFFと、コリジョンのON/OFF、アクタの表示/非表示を切り替えている。可視圏内に入った直後はSweepをOFFにしておかないと、過去の可視圏内だった位置から可視圏内に戻った現在位置まで、長いSweepが作られてしまい、意図せぬコリジョンが発生してしまう。
  • SubEvent Cull If Outsight > Sequence - Then 1: 可視であれば Set Actor Location And Rotation を呼び出した後、SweepフラグをTrueにしている。

さて、「内積1発で済ます」と言った割には大きなノードグラフになってしまいました。これがプログラミングの怖いところですね。果たしてこの修正でパフォーマンスは改善したのでしょうか。

セッションフロントエンド再び。

f:id:sgtech:20181125230544p:plain

  • 敵の弾Actorの負荷は 2.991 ms改善(14.404 msから11.413 msへ)
  • Set Actor Location And Rotation のコール数が半減(814.3から429.0へ)。今回作ったカリング処理で、半数の弾の Set Actor Location And Rotation が呼ばれなくなったため。結果、負荷が半減(7.167 msから3.566 msへ)。
  • BP_BallisticObject.Update は現状維持(変更を加えていないため)

おおよそ3 msの軽減という、良い結果が得られました。Blueprintでも負荷の低いカリング処理が組めたようです。3 msというと、単純計算で弾があと234.5個出せます。これはかなり良い結果と言えます。成果が得られずに色々な手法を試行錯誤する覚悟をしていましたが、一回で結果が出てホッとしています。

現実のゲーム開発では0.5 msも稼げれば相当頑張ったと言えます。各プログラマがおのおの自分の担当分野で頑張って少しずつ稼ぎ、合計で3 ms稼ぐ、といったところです。そういう意味では、今回の3 msという成果は、逆に怒られる領域かもしれませんね。「こんな基本的なところを今までなぜ放置してた!」と。(はい。申し訳ありません…)

C++化も試してみる。

さて、せっかくですからC++化も試してみましょう。C++化と言っても「Nativize」の方です。ところで「Nativize」とは日本人には非常に読みにくい綴りです。Web辞書で調べてみたところ「ネイティバイズ」と発音するようです。このNativize機能はUE4による「自動C++化」です。非常にありがたい機能が実装されたものです。

プロジェクトセッティングを見てみると、Nativizeには3つのモードがあります。Project Settings >Project > PackagingにあるBlueprint > Blueprint Nativization Method の項目があります。

f:id:sgtech:20181125230639p:plain

  • Disabled: Nativizeなし。デフォルト。
  • Inclusive: プロジェクトのBlueprintをまるっと一式Nativizeする。
  • Exclusive: 指定されたBlueprintのみNativizeする。(ただし関連するBlueprintも一緒にNativizeされる)

残念ながら、Nativize機能はうまく行く場合とうまく行かない場合があるようです。本シューティングゲームGravity Oneも、Inclusive(まるっと全部)のNativizeはエラーが出てしまい、うまく行きませんでした。今後のアップデートに期待です。

一方、今回の「敵の弾」に対してのみNativizeするように指定した、Exclusiveモードではうまく行きました。今回はこちらについてお話します。

ExclusiveなNativizeのしかた

まずは、NativizeしたいBlueprintを開き、上のボタンバーから「Class Settings」をクリックします。そうすると詳細パネルにクラス設定一覧が出ますので、その中の「Packaging > Nativize」にチェックマークを入れます。

f:id:sgtech:20181125230636p:plain

これをNativizeしたいすべてのBlueprintに対して行います。なお、チェックマークを入れたBlueprintだけでなく、それと参照関係にある他の(チェックマークを入れていない)BlueprintもNativizeの対象となるようです。それがいわゆるReference Viewerでみられる参照関係と同じなのか、他に法則があるのか、などは調べていないので分かりません。悪しからずご了承を。

次に、プロジェクトセッティングを開き、Project > Packagingから、Blueprints > Blueprint Nativization Methodを「Exclusive」にします。(意外とこの設定を忘れがち)

f:id:sgtech:20181125230634p:plain

あとは、パッケージングするだけです。ちゃんとNativizedされたかどうかを確認するには、Output Logで「NativizedAssets.cpp」を検索すると良いでしょう。また、NativizedAssets.cppの付近には、NativizeされたBlueprint名に妙な数字と.cppがつけられて並んでいますので、何がC++化されたかを知ることもできます。

今回は、カリング処理なし/ありの両方についてNativizeしてみました。それらのプロファイリング結果を見てみましょう。

Nativizeされた弾幕はどれくらい軽いか?

結果を以下に並べてみます。()内の数字は、最適化前の状態のNativizeなし/カリングなしの状態に対して、何 ms高速化できたかを表しています。

  • Nativeなし/カリングなし 14.404 ms(0.000 ms)
  • Nativeなし/カリングあり 11.413 ms(2.991 ms高速化!)
  • Nativeあり/カリングなし 7.618 ms(6.786 ms高速化!)
  • Nativeあり/カリングあり 7.266 ms(7.138 ms高速化!)

見ての通り、小躍りしたくなるような結果を得られました。これを見ると手動でC++を組むのがばかばかしくなってきますね! 問題はNativizeが失敗することが多々あることですが、今後のUE4アップデートに期待することにしましょう。

弾幕で分かるBlueprintの限界

ここまでの話をまとめます。文字ばっかりになりますが、ご容赦くださいね。

Blueprintをできるだけ軽く組むために。

Blueprintは非常にサクサク組めて、イテレーションサイクル(試行錯誤の繰り返し)も速いので、UE4でのゲーム開発では積極的に取り入れるべきものだと思います。一方で、Blueprintは所詮スクリプトに過ぎないという一面もありますので、CPUパフォーマンスに問題が出やすいのも事実です。大事なことは、Blueprint/Nativize/手動C++の境界をどこに置くかで、これを見極めることができると、サクサク作れて高パフォーマンス、という夢のような環境を手に入れることも不可能ではないでしょう。

その見極めの基準の一例として、Blueprintで注意するべき項目を並べてみようと思います。これはあくまで私個人の見解で、セガのUE4エンジニアの総意ではありませんので、その辺はご注意ください。

  • アクタの数やTickの数を必要最低限に絞る: この数は純粋にCPU負荷に影響します。今回の弾幕のようにアクタ数が多いと、明確に分かりやすく処理落ちし始めます。まずは、アクタの数を必要最低限に絞ることです。また、Set Actor Tick Enabledなどを使って、不要なTickは切ってしまいましょう。
  • Set Actor Transform系はTick1回につき1回まで: 前述したとおり、一見トランスフォームをセットするだけのように見えて、その中ではコリジョンのSweep処理や、コンポーネントの座標変換など多くの処理が行われています。位置や回転などのトランスフォーム値は変数で計算しておき、確定した段階でSet Actor Transform系を呼び出すようにしましょう。また、不要なSweepは行わないこと、コンポーネントの数を絞ることも大事です。
  • ループの数を減らす: 今回の記事では取り上げていませんが、Blueprintのループ処理も重い処理の一つです。試しに、256回回るForLoopを2重にして(つまり256x256=65536回ループ)実行してみてください。劇的に重いことが分かるかと思います。これは、パッケージ化すると改善はされますが、劇的に軽くなるわけではありません。また、ループはエディタでの実行とパッケージでの実行との速度差が大きく、開発中にCPU負荷を見積もるのが難しいので、ループを多用している場合は、パッケージでの動作チェックを頻繁にすべきでしょう。
  • できる限りノードの数を減らす: Blueprintの関数呼び出し、イベント呼び出しは、比較的重い処理のようです。できる限りシンプルなノードグラフを組んで、関数呼び出しそのものを減らす工夫をしましょう。また、UE4標準で用意されている関数は、たいていは内部がC++化されています。似た処理を複数のBlueprintノードで自作するよりは、UE4標準関数を積極的に使いましょう。また、UE4アップデートのたびに便利関数がこっそり増えていたりします。UE4リリースノートや右クリックの検索ウィンドウなどを活用して、新しいノードや機能をマメにチェックしましょう。

何を基準にNativize/手動C++化に踏み切るか。

個人的には、上記4項目がそこそこ出来ているならば、最適化処理をBlueprintで組んで試行錯誤するより、Nativizeを試すべきだと思っています。そういう意味では、上記4項目がBlueprintの限界点だと言えます。

今回例に挙げたカリング処理は幸いにも成果を得られましたが、一方で、Blueprintで最適化を模索するのは、Blueprintそのものの重さが災いして、うまく行かない可能性も高いと思っています。その試行錯誤にかけた手間暇に対して、得られる効果が見合ったものかどうか、いわゆるコストパフォーマンスは、私のUE4技能レベルではまだまだ謎の領域にあります。

ですので、Blueprintが一定のレベルまで組めているなら、最適化の模索より先にNativizeを試すほうが建設的かと思います。前述したとおり、得られる効果は絶大です。Nativizeでエラーが出てしまうようなら、そこで初めて、Blueprintでの最適化を模索するか、手動でC++を組むかを悩むべきでしょう。

ここで、Blueprintでの最適化と、手動でのC++化が、同レベルで天秤に掛かっていることを不思議に思う方もいらっしゃるでしょう。一見、手動C++化で決まりだと思えます。しかし、私が手動によるC++化を避けている理由は、関連するゲームプログラム周りの設計を再構成する必要がでてしまいやすい点にあります。例えば、ゲームバランスに直結するようなBlueprintを安易にC++化してしまうと、以後ゲームバランスを変更するたびに Visual Studio でのビルドが必要になり、作業スピードが落ちてしまいます。また、アーティストやレベルデザイナーが頻繁に触っているBlueprintをC++化するわけにもいきません。そのためには、基底クラスをC++で作って、そのBlueprintの親にする等、何を表(Blueprint)に出して、何を裏(C++)に隠すかを設計しなおさなくてはならないのです。一方のNativizeはパッケージングにのみ影響する機能ですから、その辺の心配はいりません。

手動C++化については、もっと気楽な方法もあります。Blueprintでゲーム全体を組むと決めてしまい、C++はそのためのツールを提供するためのものと割り切ってしまうことです。例えば、基本機能を充実させた、そのゲーム専用の基底クラスをC++で作っておいて、Blueprint側はその基底クラスを派生して使うようにする、他には便利関数や便利クラスなどの小さいが高速なモジュールをC++で用意しておくなどです。

まあ、これらはBlueprint信者の妄信かもしれません。参考にしていただきつつも、皆さんは皆さん流のBlueprint/C++使い分け術を見出していただければと思います。

おわりに

 夢中になって書いてしまった結果、非常に長くなってしまいました。ここまで読んでくださった皆さん、本当にありがとうございます! 楽しんでいただけましたでしょうか?

とは言え、これでも書こうと思っていたトピックの4分の1だったりします。現時点でもあと6つくらいトピックがありますし、今後もUE4を使う限りどんどん増えていくでしょう。また、社内の他のUE4エンジニアたちも、あれこれたくさんトピックを持っているようです。今回紹介できなかったトピックも、また機会を作って紹介できればと思っています。

ですので、今回の記事をお楽しみいただけた方、ぜひご反響をくださいませ! 今後もUE4のトピックをどんどん増やしていきたいと思います。

インディーズのススメ

ゲームエンジンの時代が到来して久しいですね。それに伴い、そのゲームエンジンで活かしてもらおうと、3Dモデリング環境や、画像・映像制作環境など、ゲームの素材を作る環境もどんどん安価に・身近になってきています。冒頭でもいくつかツールを紹介しましたが、今やゲーム制作環境の大部分が一般に降りてきています。何ならフリーのツールだけでも全部作れてしまいます。やる気さえあれば、誰でもゲームを作れる時代が到来しました。

今回題材に使いましたぷちコン応募作品「Gravity One」も、未完ながら「MAGITYPE」も、概ね全部の素材を私一人で作っています(もちろん、ものすごく大変でしたけど)。一昔前なら、チームを組まなくては作れない規模のゲームも、いまや個人や少人数グループの頑張りで作れてしまいます。

明らかに時代は変わってきています。ゲームエンジンが台頭する前は「特定の分野一つに強い職人」を集め、チームを組んで、リーダーが全体を統括しながら作る時代でした。それゆえに、企業が強い分野でもありました。一方、昨今では、高度な/専門的な知識はあらかじめゲームエンジンに内包されており、ゲーム開発者は「その使い方と効能」を理解してさえいれば良くなりました。その結果、ゲーム開発者は、特定の分野一つを専門にして深く理解/応用する労力から解放され、複数の分野に渡って広く知る余裕が出来てきました。

今後は、「複数の分野を知っていて、それらを上手く組み合わせてコンテンツを作れる人」同士がチームを組んで、その相乗効果でより面白いものを作る時代になるのでしょう。ゲーム開発者はより「面白さ」に専念できるようになりました。

皆さんも、小規模で良いので、「一つのパッケージを自分一人で作り上げる」を試してみてはいかがでしょうか。他人の評価を得ることは何にもまして勉強になりますし、その成果を何らかの形で発表すると、なお良いでしょう。ゲーム制作の全部の流れを何となくでも知っておく(体に通しておく)と、非常に視野が広がり、例えば、プログラマがアーティストの作業を知ると、以後、お互いの作業領域に重なりができることで、お互いにカバーしあえる体制が出来上がります。これは非常に良い信頼関係となります。

ゲームエンジンの台頭によって、ゲーム開発者の価値が変わりつつあります。今はまだ「UE4を上手く使える」「Unityを上手く使える」という技能面だけで、それなりのステータスを得られますが、将来は、ゲームエンジンを上手に使えることよりも、「どんなテイストのゲームが作れるか」「どんな触り心地のゲームが作れるか」といった「クリエイティブ力」が、今まで以上に、ゲーム開発者の価値となる時代が来るでしょう。例えば、いわゆる「絵師さん」。そのテイストや画風が人々に受け入れられ、その絵師さんの価値となるように、あなたの作ったゲームの「テイスト」や「触り心地」が人々に受け入れられ、あなたの価値となる、そういう時代になるのでしょう。

セガ・インタラクティブではUE4エンジニアを募集しています。

長いうえに説教臭い話でしたね。すみません。私も年を取ったもんです。

それはさておき。

我ら「セガ・インタラクティブ」は、アーケードゲームをはじめ、スマホ、テーマパーク、ショッピングモール、はたまたeスポーツなどなど、様々なマーケットにエンタメを投入する、セガの中でも一風変わったオモシロカンパニーであります。弊社では、UE4エンジニアがまだまだ足りません。まさに、この長い記事を最後まで読んでくださったイケてるUE4遣い(とその候補)の皆さん! 弊社にご興味を持っていただけると嬉しいです。

採用情報などはこちらに記載されています。是非ご確認ください。

採用情報|株式会社セガ・インタラクティブ - 【SEGA Interactive Co., Ltd.】

 

また、UE4インターンも「画策」しています。「開催」や「予定」と書かず「画策」と書いたのは、まだいつ開催できるかが不明だからですが、弊社のUE4エンジニアと、イケてるUE4遣い(とその候補)の皆さんとで、一緒にゲーム開発を体験できれば!と思っています。開催された折にはぜひご参加くださいませ。

弊社では、もちろんUnityタイトルも出しています。スマホに限らず、ゲームセンターにもUnityタイトルを出していますので、イケてるUnity遣い(とその候補)の皆さんも是非、セガ・インタラクティブへ! そして、おそらく最も万能な、イケてるC++遣い(とその候補)の皆さんも是非、セガ・インタラクティブへ!

私たちと一緒にオモシロ体験を作っていきませんか?

 

©SEGA

新卒からのTAが内製ツールに動画撮影機能をつけてみた話

初めまして。

セガゲームス開発サポート部テクニカルアーティストの清水と申します。
新卒からテクニカルアーティスト(以後TA)業務に積極的に関わらせてもらい、今年で社会人歴TA歴共に3年目になります。

今回は、弊社で使われてる内製ツールHNDViewerにコマンドライン実行を組み込み、アセットのモーションGIF画像を自動生成できるようにした時の対応について紹介させていただきます。

概要

独自の内製ビューアに動画撮影機能を実装し、「モーションプレビュー一覧を半自動的に作成する環境を構築する」
という目的のもと、以下のようなコマンドをJenkinsで自動定期実行することにより、内製ビューアがGif画像を自動で生成できるようにしてみました。

HNDViewer.exe -cmd -exec=capturegif -out=D:\foruda\output -open=D:\directory\Narita_Ann_mojimoji.hn2v -width=600 -height=450

f:id:sgtech:20181023232429g:plain
モーションのGif画像 (C)SEGA

今回紹介する事例で取り組んだ内容は、3つの内容から成り立っています。
・内製3DCGモデルビューアHNDViewerにモデルのモーションに関するGIF画像を生成する機能を追加した。
・HNDViewerにコマンドライン実行機能を追加して、モデルのモーションに関するGIF画像を生成する機能をコマンドライン実行できるようにした。
・実装したコマンドラインをJenkinsから実行することで、モデルのモーションに関するGIF画像の生成を自動化した。

まず、今回紹介するアプリケーションについて、触れておきましょう。

f:id:sgtech:20181023232512p:plain
概要

HNDViewerとは?

弊社には、3D CGツールのデータをエクスポートして格納するための中間構造を扱うHNDというシステムがあります。
HNDViewerとは、このHNDを格納した「HNDファイル」のビューアツールです。

格納されている3DCGデータの表示・再生はもちろん、各ポリゴンやノードのパラメータを一覧することもできます。

f:id:sgtech:20181023232418p:plain
HNDViewer

Jenkinsとは?

Jenkinsとは、ソフトウェアの実行等の一連の作業を自動化できる自動実行ツールです。

jenkins.io

高い汎用性を持った自動実行ツールで、事前に設定した条件を満たすたびに指定したコマンドを自動で実行してくれます。

自動実行に指定できるのは基本的にbat処理などのコマンドライン実行可能な処理になります。

ここからは少し実装の話に入っていきます。

GIF画像の生成

今回はImageMagickを使用してgif画像を作成する方法を採用しました。

www.imagemagick.org

ImageMagickは、幅広い画像ファイルフォーマットに対応している、画像を操作したり表示したりするためのソフトウェアです。 コマンドラインツールを含んでおり、バッチ処理での画像の変換・編集などに必要な機能が揃っています。

ImageMagickを使用して指定した連番画像からGif画像を生成することができます。

HNDViewerには下記の3つの機能はすでに搭載されています。
・3DCGモデルを表示する
・モデルのモーションを再生する
・モデルを表示している画面のスクリーンショットを作成する
従ってGif画像を作成するために追加する必要のある機能は下記の2つです。
・モデルのモーションの連番画像を作成する機能
・ImageMagickに連番画像を受け渡してGIF画像を作成してもらう機能

まず、読み込んだモデルを1フレームずつ動かして表示している画面をキャプチャし、画像として一時フォルダへ保存します。 これをモーションの最終フレームまで行い、モデルのモーションの連番画像を作成します。

f:id:sgtech:20181023232423p:plain
連番画像の作成

モデルのモーションの連番画像の生成が完了したら、ImageMagickを利用してGIF画像を生成します。

キャプチャした連番画像をImageMagickのコマンドラインツールであるconvert.exeの引数に渡してGIF画像を作成します。

 convert.exe -delay 3.3 -loop 0 キャプチャした画像_*.png キャプチャした画像.gif  

これでGIF画像の生成は完了です。

GIF画像の生成処理が終了したことを確認したら、一時フォルダに保存してあるGIF画像の生成に使用した連番画像はすべて削除します。

これでモデルのモーションに関するGIF画像を作成する機能ができました。

コマンドライン実行の追加

モデルのモーションに関するGIF画像を作成する機能を実装したHNDViewerは、GUIツールでコマンドラインから処理を実行する機能は持たないため、次にHNDViewerにコマンドライン実行機能の追加をしていきます。

コマンドライン実行時の引数の入力方法について

入力する引数については以下のようなルールを採用しました。
・引数はキーとなる文字列とパラメータを「=」で接続したものを一つの引数として入力する。
・キーとなる文字列の頭には「/」もしくは「-」を付ける。
このような引数をプログラム側でパースしてパラメータを読み込みます。

つまり、以下の2種類のうち、いずれかの引数を入力するルールとします。

application.exe -cmd -exec=コマンド名 -out=出力ディレクトリ -open=出力ファイル -width=画像の横幅 -height=画像の縦幅  

または

application.exe /cmd /exec=コマンド名 /out=出力ディレクトリ /open=出力ファイル /width=画像の横幅 /height=画像の縦幅  

そして、次のようにプログラム側で引数を=でパースし、辞書にします。

Dictionary<string, string> ParseArgs(string[] args)  
{  
    Dictionary<string, string> ParsedArgsDictionary = new Dictionary<string, string>();  
    char[] splitters = { '=' };  
    foreach (string line in args)  
    {  
        string s = line;  
        if (s.StartsWith("/") || s.StartsWith("-"))  
        {  
            s = s.Remove(0, 1);  
        }  
        args = s.Split(splitters);  
        if (args.Length > 1)  
        {  
            ParsedArgsDictionary.Add(args[0], args[1]);  
        }  
        else  
        {  
            ParsedArgsDictionary.Add(args[0], "");  
        }  
    }  
    return ParsedArgsDictionary;  
}  

この引数の入力方法は以下のような利点があります。

・コマンドを使用する際にどのパラメーターにどの値を入力しようとしているのかユーザーに分かりやすい

例:通常のスペース区切りの場合

HNDViewer.exe capturegif D:\foruda\models D:\directory\model 640 320

capturegifはコマンド名であることは推測できます。

また、2つあるパスは入力先と出力先を表していると推測できます。

が、どちらが入力でどちらが出力かは読み取ることはできません。

残り2つの数値については何を表すのかは推測することすら困難です。

しかし、このルールを採用した場合

HNDViewer.exe -cmd -exec=capturegif -out=D:\foruda\models -open=D:\directory\model -width=640 -height=320

-exec=capturegifによって実行したい処理がcapturegifであることがわかる。

-outと-openで指定されていることによって入力と出力が一目でわかる。

-widthと-heightによってそれぞれの数値が幅と高さを表していることがわかる。

・アプリケーション側からのユーザーへ対しての通知内容を分かりやすくできる。

コマンドラインに渡す引数の順番を間違えるなどといったミスを事前に防ぐことができる。 各パラメータとパラメータが示すキーが対になっているため、アプリケーション側からどのパラメータに問題があったのかをユーザーに通知できる。

・アプリケーションに関連付けられたファイルを開く処理を明確に分けることができる。

第1引数に「-cmd」のようなコマンドライン実行であることを表すキー文字列が存在することによって、プログラム側は今回の実行がコマンドライン実行であるか否かを明確に判定することができます。

既存ツールへのコマンドライン実行の追加

コマンドライン実行時の処理の流れ

f:id:sgtech:20181023232408p:plain
コマンドライン実行の流れ

コマンドラインの確認

まず、アプリケーションのmain関数内でツール本来の処理に入る前に、 実行時の引数を確認する処理を追加します。

本来の処理に入る前に引数の内容を確認して、第一引数が「-cmd」または「/cmd」であった場合 コマンドラインモードで起動できるようにします。

この「コマンドラインモードで起動するか否か」の判定は、引数の有無で判断してしまうと、関連付けられたファイルを開くことができなくなってしまいますので、必ず引数の内容を確かめるようにします。
これは、アプリケーションが関連付けられたファイルから起動される際に、そのファイルのフルパスが引数1に入るためです。
アプリケーションに関連付けられたファイルがダブルクリックなどで開かれる場合、一般的に起動するアプリケーションには関連付けられたファイルのパスが第1引数に渡されます。
このパスの情報をもとにアプリケーションはファイルを開いた状態で起動することができます。
そのため、コマンドライン実行か否かの判定は引数の有無だけで判断しないようにします。

また、第一引数が「-?」または「/?」であった場合も
コマンドラインモードに遷移するようにして最終的にユーザー対してヘルプが表示されるようにします。

 main(){  
        var args = Environment.GetCommandLineArgs();  
        bool consoleMode=false;  
        //第1引数を確認
        if(args[1].Equals("-cmd") || args[1].Equals("/cmd")){  
            //-cmdまたは/cmdの場合はコマンドライン実行用の処理に遷移するようにする。
            consoleMode=true;
        }
        if(args[1].Equals("-?") || args[1].Equals("/?")){  
            //-?または/?の場合もコマンドライン実行用の処理に遷移するようにする。
            //その後ヘルプを表示するようにする。
            consoleMode=true;
        }
        if(consoleMode){
            //コマンドライン実行用の処理へ遷移 
            Application.Run(new MainForm(consoleMode));  
        } 
        else{
            //通常起動用の処理へ遷移 
            Application.Run(new MainForm()); 
        }
    }

実行コマンドの確認

コマンドライン実行であることが確定した後に、実行しようとしているコマンドがアプリケーションに登録されたコマンドであるかを確認します。 コマンド名が指定されていない場合やアプリケーションに登録されていないコマンド名の場合は、ヘルプを表示して終了させます。

 //CommandListは実行できるコマンドのリスト

    //引数    
    Dictionary<string, string> argsDictionary = ParseArgs(args);
    //引数に実行するコマンド名が含まれているか否か
    if (argsDictionary.ContainsKey("exec")) {
    //実行しようとするコマンドがあるか否か
        if (CommandList.Contains(argsDictionary["exec"])) {
            //コマンドが存在するならば実行する
            ExecuteCommand(argsDictionary);
        }
        else {
            //コマンドが無い場合はヘルプを表示する。
            ShowHelp();
        }
    } else {
        //execが無いということはコマンドが指定されていないということなのでヘルプを表示する。
        ShowHelp();
    }

引数の確認

実行するコマンドが確定した後に実行に必要な引数が全てそろっているか、引数に入力されている値は有効かを確認します。 引数が足りていない場合や引数の内容に問題がある場合は、その旨エラーを出力し、最後にヘルプを表示して終了させます。

以下のコードは私が実装した「capturegif」コマンドの引数確認の例です。

 public Bool CheckCaptureGifImageArgs(Dictionary<string, string> argsDictionary)
    {
        //入力ファイルを確認する。
        if (!argsDictionary.ContainsKey("open")) {
            //引数にopneが存在しない場合は、openが無いことをユーザーに表示してヘルプを表示
            Console.WriteLine("引数[open]がありません。");
            ShowCaptureGifImageHelp();
            return false;
        }
        //入力ファイルが存在するかを確認する。
        string modelpath = argsDictionary.ContainsKey("open");
        if(!File.Exists(modelpath)){
            //opneで指定されたファイルが存在しない場合は、その旨をユーザーに表示してヘルプを表示
            Console.WriteLine("指定されたファイルmodelpathが存在しません。");
            ShowCaptureGifImageHelp();
            return false;
        }

        //出力フォルダを確認する。
        if (!argsDictionary.ContainsKey("out")) {
            //引数にoutが存在しない場合は、outが無いことをユーザーに表示してヘルプを表示
            Console.WriteLine("引数[out]がありません");
            ShowCaptureGifImageHelp();
            return false;
        }
        //出力フォルダが存在するかを確認する。
        string outputDirectory = argsDictionary.ContainsKey("out");
        if(!Directory.Exists(outputDirectory)){
            //outで指定されたフォルダが存在しない場合は、その旨をユーザーに表示してヘルプを表示
            Console.WriteLine("指定されたフォルダoutputDirectoryが存在しません。");
            ShowCaptureGifImageHelp();
            return false;
        }

        //生成するgif画像の大きさを確認する。
        if (argsDictionary.ContainsKey("width") && argsDictionary.ContainsKey("height")) {
            int w = 0;
            int h = 0;
            //widthとheightが数値に変換できるかを確認する。
            if (int.TryParse(argsDictionary["width"], out w) && int.TryParse(argsDictionary["height"], out h)) {
                SetGifImageSize(w, h);
            }
            else{
                //数値に変換できない場合は、その旨をユーザーに表示してヘルプを表示
                Console.WriteLine("widthとheightが数値に変換できませんでした。");
                ShowCaptureGifImageHelp();
                return false;               
            }
        }
        else{
            //引数にwidthとheightが存在しない場合は、その旨をユーザーに表示してヘルプを表示
            Console.WriteLine("引数[width]、[height]がありません。");
            ShowCaptureGifImageHelp();
            return false;
        }

        return true;
    }

処理の実行・アプリケーションの終了

ここまでの手順で、実行するコマンドが決まり、引数に問題ないことが確認できたので、処理を実行します。 処理が終了したら、アプリケーションを終了します。

これでGUIツールだったHNDViewerにコマンドライン実行機能を追加することができました。

既存の機能に対応するコマンド名と処理に必要な引数を決めておき、
設定したコマンド名が実行されたら、対応する既存の機能を実行するように処理を実装することで、コマンドラインからツールの任意の機能を実行できるようになります。

一例として、モデルのモーションに関するGIF画像の生成機能について、コマンド名と引数は以下のように設定しました。

コマンド名 : capturegif
引数

Key 内容
out 出力ディレクトリ
open モデルファイルのパス
width 画像の横幅
height 画像の高さ

複数の作業をバッチ処理にまとめることもできるようになりました。

jenkinsを用いた処理の自動実行

  ここまでで、コマンドライン実行でモデルのモーションに関するGIF画像を作成することができました。
この処理をjenkinsから実行してもらうことで、自動でモデルのモーションに関するGIF画像を作成できるようにします。

jenkinsを立ち上げて新規のジョブを作成し、
Windowsバッチコマンドの実行に、実装したモデルのモーションに関するGIF画像を作成するコマンドを設定します。
自動実行するタイミングは好きなタイミングを指定します。

f:id:sgtech:20181023232413p:plain
jenkinsのジョブが実行するコマンドを設定する。

jenkinsの導入についてはCEDEC2017で講演された以下のセッションの資料がわかりやすくてオススメです。

cedil.cesa.or.jp

これで自動でモデルのモーションに関するGIF画像を作成することができるようになりました。

最後に

いかがでしたでしょうか。

改めて振り返ってみると実装した各処理の内容自体は、特別優れた技術が使われているわけでもなく、普通な実装だなぁと自分でも感じる部分があります。
しかし、その普通な実装の積み重ねで、「アセットのアニメーションGIF画像の自動生成」という目的を達成することができました。

この処理の自動化を実現したことによって、 手作業を必要とせずに、常に最新のモデルのモーションに関するGIF画像を生成できるようになりました。
さらに生成されたGIF画像を用いて、手軽にモデルのモーションを確認できるようになり、作業の効率化に貢献できました。
社内での反響も上々です。

テクニカルアーティストとして実際に業務を経験するまでは、 作業の効率化とは最先端の高度な技術によって実現される「未だかつてない便利」なものだというイメージを持っていたのですが、 実際の業務経験を通して、作業の効率化というのは、案外小さな普通の技術でできている「ちょっとした便利」の組み合わせで成り立っているんだなぁと感じました。 これからもこうした作業の効率化にどんどん取り組んでいき、クリエイターがより快適にゲーム制作に注力できる環境の実現を目指して、頑張っていきたいと思います。

快適なゲーム制作環境の実現や快適な環境でのゲーム制作に興味がある方は、ぜひセガゲームスの採用情報にアクセス!

自己紹介(補足) 

新卒からTAという比較的珍しいキャリアパスを広めるべく過去2回CEDECで登壇などもいたしました。

若手テクニカルアーティストの業務効率改善への貢献、育成について話すラウンドテーブル http://cedec.cesa.or.jp/2017/session/VA/s58da6294d3416/

若手テクニカルアーティストの育成とその役割について話すラウンドテーブル https://2018.cedec.cesa.or.jp/session/detail/s5aafcb42e41b7

参加してくださった方はありがとうございます。
上記セッションについて興味を持たれた方には以下の記事がオススメです。

新卒からテクニカルアーティスト(TA)を育成するために必要なこと>>TAの素養ってなんだろう?
https://entry.cgworld.jp/column/post/201710-cedec-ta.html

TAを増やす改善策は「余裕をもつこと」~若手テクニカルアーティストが大いに語ったラウンドテーブル~CEDEC 2018レポート(3)
https://cgworld.jp/feature/201809-cedec-03tart.html

記事に取り上げてくださった

株式会社ボーンデジタル 尾形美幸様

株式会社ボーンデジタル 小村仁美様

にはこの場を借りて感謝申し上げます。

Powered by はてなブログ