UE4.24時代のimposter製作方法

まえがき

おそらくの話ですが、昔の頭良い人は考えました。
「遠景オブジェクトはどうせ見える角度が変わらないんだから、板ポリで表現すれば良いじゃん。」
そんなわけで、以下のように負荷軽減策として使われるようになりました。
f:id:crssnky:20200509220833p:plain

左側のメイン部分のより奥にある、宙に浮く岩

f:id:crssnky:20200509220850p:plain

こんな感じで板ポリで表現されています。

以上、Epic Zen Gardenの紹介でした。
めでたしめでたし。

Imposterとは

日本語に直訳すると「詐欺師」らしいですが、UE4では遠景オブジェクトを表現する手法の一つとして存在しています。

docs.unrealengine.com

「まえがき」では、見える角度が変わらないオブジェクトに対して板ポリで表現するということを紹介しましたが、UE4のImposterは一味違います。カメラの位置に対して、パラパラ漫画のように板ポリに表示する画像を変えることで、ポリゴン数を抑えつつもどのような向きにも対応できるオブジェクトが表現できます。つまり、こう↓

向きが断続的なことと、真上真下は若干怪しい感じでしたが、遠景だったり大量に出ている等の負荷削減のための場所に適用すれば気付かれにくいでしょう。
本家本元であるEpicGamesはFortnite内で、遠景の木などにしようしてるらしいですね。

Imposterの作り方

基本的には先程貼った

3D Imposter スプライトをレンダリングする | Unreal Engine Documentation

や、ヒストリアさんの

[UE4] Imposterを使ってみる(多視点対応のビルボード)|株式会社ヒストリア

を参考にすれば大丈夫です。
しかし、とある一点について注意が必要で、UE4.24では上手くいきません。

DecalMaskが無い問題

どちらの手順でも、CaptureするG Bufferの選択でDecal Maskを選択しています。
これは当たり前の話で、板ポリにテクスチャを貼るので余白をMaskするために出力します。 しかし、出力できません。他のものは出力されますが、DecalMaskは出力されません。

forums.unrealengine.com

なんでだろうと思い、探しましたらありました。
どうやら2017年の2月末のバージョンのではもう、DecalMaskは無いようです。
出力できないからSceneDepthを使ってねとあります。

んじゃ使うか~と思いきや、SceneDepthもありません。 f:id:crssnky:20200509225712p:plain

Scene Depth World Unitsはあるが、目的のものでは無い

答え

おまじないコマンドke * rendertexturesを、
r.BufferVisualizationOverviewTargets BaseColor,SceneDepthHighResShot N(Nは高精細スクショの倍数)にしましょう。

解説

おまじないコマンドke * rendertexturesを簡単に見ていきます。
keというのはKismetEventの略称でBlueprintのイベントを呼び出すコンソールコマンドです。
どのBlueprintに対してかを次に入力するのですが、*とすることで全てのBlueprintに対して送ります。 その次がBlueprintのイベント名でrendertexturesというイベントを呼び出します。
参考
[UE4] コンソールコマンドの使い方&よく使うコマンド一覧|株式会社ヒストリア

rendertexturesイベントを持つのは、GameModeに定められているRenderToTexture_Pawnです。
Event Graphを見ると、rendertexturesイベントは以下の2つを実行しているだけだと分かります。 f:id:crssnky:20200509231842p:plain このGeneratorというのは、Levelに配置して設定を行うRenderToTexture_LevelBPのことです。

BufferCommand

r.BufferVisualizationOverviewTargets BaseColor,SceneDepth側ですね。
RenderToTexture_LevelBPSetBufferCommand関数を見ると、チェックを付けるとr.BufferVisualizationOverviewTargetsの後ろにG Bufferを追加していく様子が分かります。そして、この追加される文字列にはSceneDepthが無いことも分かります。
つまり後ろに撮りたいG Bufferを繋げれば良いので、r.BufferVisualizationOverviewTargets BaseColor,SceneDepthとなります。
Normalも欲しければr.BufferVisualizationOverviewTargets BaseColor,SceneDepth, WorldNormalとなるでしょう。

ShotCommand

HighResShot N側です。同じくSetBufferCommand関数にあります。
なんやかんやで数値を決めていますが、つまり自分の欲しい解像度の倍数を入れれば良いです。
参考
Taking Screenshots | Unreal Engine Documentation

おわりに

自動化されている部分がバージョンアップに追いついていないので、手作業でやる方法を示しました。
EngineContentを変えちゃっても良いかな。っていう人は、DecalMaskをチェックしたときにSceneDepthが追加されるようにすれば良いのではないでしょうか。
まぁこれで、ようやくUE4.24でもImposterが書き出せるということで。

え?!UE4.25ってもうリリースされちゃったの?!?!?!?!

(Rust + Rocket + Tera) × プロデュース = im@sparql_details

github.com

タイトル通り、Rust言語とそのCrateであるRocket、テンプレートエンジンのTeraを使ってim@sparqlのdetailsのアレ(特に名前がないゆえの表現)を置き換えました。

アレの例→Kisaragi_Chihaya detail

Rust

Rust言語については特に大丈夫ですかね。
コンパイル時のチェックが厳しいでお馴染みのシステムプログラミング言語ですね。
C++に足を浸けてる人間としては、今後学びたい言語でした。    ja.wikipedia.org

Rocket

rocket.rs

Rustで使えるWebフレームワークです。初学者にも満たないので、Webフレームワークって何ができればWebフレームワークなのか分かりませんが、ルーティングとかフォーム入力とか当たり前の機能はあるみたいです。
なんだこの頭悪い説明は...

Tera

tera.netlify.app

Rocketで使えるテンプレートエンジンの一つです。

Inspired by Jinja2 and Django templates

ですって。

やったこと

最初にも例出したIchikawa_Hinana detailの実装を置き換えました。
今まではNode.jsとexpressだったのですが、Rustを勉強するために差し替えた次第です。
なお、見た目は全く変わってないのでユーザーには特に益はありません。

内容

Rustでも非常に簡単に書けました。126行のうち、半分くらいはJSONのデシリアライズなのでめっちゃ少ないです。

JSONシリアライズ部(展開可)

#[derive(Debug, Deserialize, Serialize)]
struct N {
  r#type: String,
  value: String,
}
#[derive(Debug, Deserialize, Serialize)]
struct O {
  r#type: String,
  #[serde(default)]
  datatype: String,
  #[serde(default, rename = "xml:lang")]
  xml_lang: String,
  value: String,
}
#[derive(Debug, Deserialize, Serialize)]
struct Bindings {
  n: N,
  o: O,
}
#[derive(Debug, Deserialize)]
struct Results {
  bindings: Vec<Bindings>,
}
#[derive(Debug, Deserialize)]
struct Head {
  vars: Vec<String>,
}
#[derive(Debug, Deserialize)]
struct Response {
  head: Head,
  results: Results,
}
#[derive(Serialize)]
struct MessageContent {
  title: String,
  num: usize,
  json: Vec<Bindings>,
}

im@sparqlのJSONの結果を見たことある人は納得の構造体群ですね。
ちなみにクエリは

PREFIX schema: <http://schema.org/>
PREFIX imas: <https://sparql.crssnky.xyz/imasrdf/RDFs/detail/>
SELECT * WHERE {
  { imas:{} ?n ?o;}
}order by (?n)

です。{}には適切な主語が入ります。

処理部(展開可)

#[get("/<subject>")]
fn get_data(subject: String) -> Template {
  const FRAGMENT: &AsciiSet = &CONTROLS;
  let encoded_subject = utf8_percent_encode(&subject, FRAGMENT).to_string();
  let quety = format!("PREFIX schema: <http://schema.org/>PREFIX imas: <https://sparql.crssnky.xyz/imasrdf/RDFs/detail/>SELECT * WHERE {{ imas:{} ?n ?o;}}order by (?n)", encoded_subject);
  let encoded_query = form_urlencoded::Serializer::new(String::new())
    .append_pair("output", "json")
    .append_pair("force-accept", "text/plain")
    .append_pair("query", &quety)
    .finish();
  let base_url = format!(
    "https://sparql.crssnky.xyz/spql/imas/query?{}",
    encoded_query
  );
  let res = ureq::get(&base_url).call();

  if res.ok() {
    let json_str = res.into_string().unwrap();
    let res_json: Response = serde_json::from_str(&json_str).unwrap();
    let mut json = res_json.results.bindings;
    let json_num = json.len();
    if json_num > 0 {
      for data in &mut json {
        match &*(data.n.value) {
          "http://schema.org/memberOf" => {
            data.o.value = percent_decode(data.o.value.as_bytes())
              .decode_utf8()
              .unwrap()
              .to_string();
          }
          "http://schema.org/owns" => {
            data.o.value = percent_decode(data.o.value.as_bytes())
              .decode_utf8()
              .unwrap()
              .to_string();
          }
          _ => (),
        }
      }
      let content = MessageContent {
        title: subject,
        num: json_num,
        json: json,
      };
      return Template::render("detail", &content);
    }
  }
  let content = MessageContent {
    title: subject,
    num: 0,
    json: vec![],
  };
  Template::render("error", &content)
}

キモなのは#[get("/<subject>")]fn get_data(subject: String)でしょうか。いわゆるルーティングの文字列を引数として取得しています。これをすることで、URLhttps://sparql.crssnky.xyz/imasrdf/RDFs/detail/の後ろに主語が続くと適切に処理されます。
引数として取得した文字列は、そのままクエリの主語の部分に連結されます。そしてクエリを送って結果をデシリアライズします。
im@sparql特有の処理として、for文の部分があります。一部の主語は日本語をそのままURLエンコードしたものをつっこんでいるので、表示には適しません。なので、テンプレートエンジンに送る前にデコードしてあげています。
それ以外はそのままのJSONをテンプレートに渡して描画しています。

こんな感じで、簡単にWebフレームワークを使ったRustを書けます。みなさんも安全性に守られたコーディングしませんか。

DECLARE_DYNAMIC_MULTICAST_DELEGATEとC++

ちょっとしたお話

みんな大好き動的マルチキャストデリゲート

マルチキャスト デリゲート | Unreal Engine ドキュメント

動的デリゲート | Unreal Engine ドキュメント

個人的な主観では、C++でグイッと処理したあとにBPに結果を渡すときによく使うんですけど、C++とBP両方にイベントを発行したい時もあります。
BPであればBindノードを使うことでイベントを登録できます。
C++であれば、動的デリゲートの説明にあるようにAddDynamicを使って関数を登録します。

でも、VisualStudioのIntellisenseにはAddAddUniqueくらいしか出てきません。

なんでやろなぁ

動的デリゲートの説明をよく見たらヘルパーマクロって書いてありました。
そうです、マクロなんです。
Delegate.h#define AddDynamic(....となってました。

というわけで備忘録

週刊IM@Study Vol.4 と アイマスハッカソン2019の続き

こちらはアイドルマスター Advent Calendar 2019の10日目の記事です。


12/5のアイマスハッカソン2019にご参加いただいたみなさんありがとうございました。
そして、関東・関西のスタッフさん、お疲れ様でした!

10時から行われていた本イベントですが、18時からの成果発表・プロデューサーLT大会の様子はこちらでご確認いただけます。


アイマスハッカソン2019 in 東京&関西 成果発表・プロデューサーLT

そこでは週刊IM@Studyの寄稿内容の概要について発表しました。僕が発表してる部分はこちら↓

https://youtu.be/SMEKvULOnvc?t=4055

発表している対象についてはこちらを見てもらったほうが早いかもしれません。

UE4アイマス楽曲をさらに盛り上げてけ☆」と題しまして、UE4で楽曲のビジュアライズ的なことをしています。
こんな感じでUE4の簡単な説明をし、操作方法を学びつつ詳しく編集を説明しています。

f:id:crssnky:20191209232153p:plain

今回はUE4のエディタ機能のみを使用しているため、C++を1字も書きません。その代わりにBlueprintを引いてもらいます。世の中の評価としても、エンジニア以外にも簡単に使えるというものということで普及しており、フローチャートとだいたい同じと考えても良いと思います。

f:id:crssnky:20191209232929p:plain

もちろん、C++に慣れた方には少し煩雑かもしれませんし、C++と比べて関数呼び出しが死ぬほど遅いといったデメリットもあります。しかし、小さなプロジェクトであったり、大きなプロジェクトでも用法を正しく使えば開発イテレーションを大幅に上げることのできる素晴らしいツールです。

そんなUE4のハンズオンや他3本の記事、ミニコラム3本が詰まった100ページに及ぶ週刊IM@Study Vol.4は、第2回技術書同人誌博覧会で頒布されるので、ぜひお手にとってみてください。Vol.3の時にあった電子決済に引き続き対応している他、電子版も頒布されるので、100ページの本は...という方もぜひどうぞ!

f:id:crssnky:20191208214932p:plain

gishohaku.dev


さてお話は変わり。
Youtubeコメでもありましたが、今回はスライドソフトにUE4を使用しています。
UE4上でスライドを表示することで、シームレスにデモへ移行することができました。
スライド自体は前日に作っており、当日はそのシステムを作ってたので今回はそれについてもお話しようかと思います。

Step 0 スライドを作る

なんの変哲もないです。僕はPowerPointで作りました。

アイマスハッカソン2019in関西 - Speaker Deck

作り終えたら、各ページを画像出力してください。パワポにはそういう機能あるし、Webサービス使わなくて良き。

f:id:crssnky:20191210001222p:plain

Step 1 UE4に取り込む

テクスチャとして取り込みます。なんとなくそんな気分なので、Mipmapは作りません。 f:id:crssnky:20191210230824p:plain

Step 2 Materialを作る

テクスチャパラメータのノードを一つ持つMaterialを一つ作ります。
ハッキリ見えてほしいので、Unlitです。 f:id:crssnky:20191210231117p:plain

Step 3 スライドBPを作る

ConstructionScriptで全部仕上げます。
InstanceEditableなInt型変数からどのTextureObjectを使うかを分岐し、Step2で作ったMaterialからMaterialInstanceDynamics(MID)を作ってそこにTextureObjectを投入します。

f:id:crssnky:20191210231502p:plain

f:id:crssnky:20191210231534p:plain

ConstructionScriptで済ますことで、Levelに配置した後でもぐりぐり~って変えられます。

Step 4 スライドLevelを作る

逆に言えばConstructionScriptで作っちゃったので、Levelが開始してしまったら変更ができません。せっかくなので、LevelStreamingを試してみたいし、良い感じにしましょう。
まず、テキトーにスライドBPが一つだけあるレベルを量産します。各BPは順番に四角の各辺をなぞるように配置します。

  • Slide1はx,y,z=1000,0,0・Pitch,Yaw,Roll=0, 0, 0
  • Slide2はx,y,z=0,1000,0・Pitch,Yaw,Roll=0, 90, 0
  • Slide3はx,y,z=-1000,0,0・Pitch,Yaw,Roll=0, 180, 0
  • Slide4はx,y,z=0,-1000,0・Pitch,Yaw,Roll=0, 270, 0

みたいな感じ

f:id:crssnky:20191210233303p:plain

スライドの数だけ...スライドの数だけ....多い....

f:id:crssnky:20191210233133p:plain

Step 5 LevelStreamingを整える

"HackathonPersistant"的な名前のパーシスタントレベルを作り、Levelsウィンドウで下にぶら下げます。
今回は、スライド以外にもデモ用のレベル(Test)もぶら下げています。

f:id:crssnky:20191210234320p:plain

Step 6 LevelBPを書く

次へキー・戻るキーを設定し、それのイベントに紐づけて次のスライドを持つレベルをその場で読み込むBPを記述します。Timelineノードを使えば、良い感じにカメラを回すことも出来ます。

f:id:crssnky:20191210234605p:plain

f:id:crssnky:20191210234705p:plain

Step 7 試す

LevelStreamingによって、必要なスライドのみが違和感なく読み込まれてることが確認できます。とってもメモリに優しいですね!!


こんな感じでアイマスハッカソン2019ではスライドを流していました。これを使い回せば、UE4で作ったものを簡単に見せられますね!

お詫び

WebRemoteControlの発表を書きたかったのですが、本日公開されたUE4.24でも難しいみたいです...

UE4とRTTI ~MSVC(Windows) V.S. clang(Mac, Linux)~

RTTIとMSVC, clangの環境の違いには気をつけろの情報共有である。

背景

im@sparqlをUE4で触りたいなと思ったら、JSONが使えるようにしたかった。
そこで、ちょうどよいものを見つけたので導入した。

usagi.hatenablog.jp

こいつ(cereal)は最高だ、JSONを構造体のように扱える。
いや、実際に構造体を定義して、そこにJSONを流し込んでるから当たり前だ。

これに関しては、技術書典で頒布した週刊IM@Studyの2019/04号に記載している。
当然リポジトリもある。

github.com

問題

昨年末のpaypay祭りの時に思い切って、ノートPCをWin機からMac機に切り替えた。
当然開発環境は変わり、Xcodeなのでclangとなる。
これが問題。
おうちのデスクトップPCはWin機なため、VS2019のMSVCで作業している。
こいつで動作確認をしているのだが、clangではどうもビルドが通らない。

具体的には、RTTI関連でエラーが出る。
MSVCはRTTIはデフォルトでONなので気にすることは無かったが、cerealではtypeinfoを用いる。
clangではデフォルトでRTTIはOFFなので、エラー文で叱られてしまった。
コンパイラに従いBuild.csでbUseRTTI=true;を指定してビルドすると見知らぬリンカエラーに遭遇した。
似た人↓
https://answers.unrealengine.com/questions/745636/undefined-symbols-for-architecture-arm64-1.html

理由

理由↓
https://answers.unrealengine.com/questions/871773/view.html

意訳すると

  • LinuxではRTTIと非RTTIを混ぜちゃダメ
  • モジュールごとにRTTIの有無は分けられるから、必要な部分に切り出して
  • その時に、Engineのクラスを混ぜちゃダメ
  • それを"ブリッジ"するラッパーを書くのが良い
  • "OpenEXRWrapper"プラグインを参考にしてね

質問者も「Windowsでは動くから、変に気を取られたよ」(意訳)と、自分と同じ心境を語ってる。

現在

モジュール分割をしている。
でも上手くいかない。
C++なんもわからん
何がわからないのか分からない

RTTIとMSVC, clangの環境の違いには気をつけろの情報共有でした。

IM@Study合同本@C96 4日目(月曜日)南地区"リ"01a

f:id:crssnky:20190810021237p:plain
imasbook03
なんとぉ!
週刊IM@Studyの3冊目が今夏のコミケで頒布されます。
場所はC96 4日目(月曜日)南地区"リ"01aです。
https://webcatalog-free.circle.ms/Circle/14514591

表紙絵はお馴染みになってきた文月きょうさんにお描きいただきました。
前回分はこのブログで宣伝していませんが、未来ちゃんが持っています!再帰!!(再帰ではない)

表紙構成は、Uske_Sさんです。
本業はPかつ副業で印刷系ITエンジニアらしく、レイアウトが華やかになりました!
強力な協力者です。

中身

  1. im@sparql応用 〜HTMLでさっと表示編〜
    著者:croMisa
    概要:im@sparqlを利用する、簡単なAjaxのWebページを作るハンズオンです。
  2. スコアを用いたライブの予習セットリスト自動生成の試み
    THE IDOLM@STER CINDERELLA GIRLS 7thLIVE編〜
    著者:遠山 賢寿
    概要:創刊号で紹介されたデレマスのセットリスト自動生成、これを7thライブに対して動かし、セットリストを予想していきます。
  3. pLaTeXを用いた小説系同人誌作成入門
    著者:MH35
    概要:小説などの文章がメインとなる同人誌において、pLaTeXで作成する時の方法や入稿の注意点が紹介されます。
  4. 小鳥さんと一緒に100%Kotlinのサイト製作〜Backend編〜
    著者:にしこりさぶろ〜
    概要:Kotolin製Webアプリフレームワーク"Ktor"を、Kotlin初心者の小鳥さんと学ぶ前半とルーティングやJSONシリアライズ、GETクエリの取得を解説する後半で紹介していきます。
  5. 青羽美咲が業務効率化を目指してAlexaスキルを作る話
    著者:きりだるま
    概要:美咲ちゃんと一緒にAlexaのスキルを学び、Pの日頃のプロデュース業務(ミリシタ)を効率化するまでを描くハンズオン的ストーリです。
  6. プレイリスト自動生成でアイマスをより楽しむ
    著者:Wakuwaku
    概要:アイマス楽曲が増えてきても、いつも聴く楽曲は固定化してしまう悩みを解決するべく、NAS内に日替わりでプレイリストを自動生成する方法を紹介します。

特定のRDFの変更に合わせて、それを基にクエリを実行させて別のRDFを作るCircleCI

「特定のRDFの変更に合わせて、それを基にクエリを実行させて別のRDFを作るCircleCI」

タイトルの通り
実際のPRはこちら
まぁ、訳あって(後述)マージせずに閉じましたが。

背景

im@sparqlでは自動生成系のRDFがある。

この2つは、Clothes.rdfUnit.rdfの逆方向の述語のトリプルが定義されている。
つまり、

  • Unit.rdf
    エターナルハーモニー --メンバー--> 千早
  • Unit_memberOf.rdf
    千早 --所属--> エターナルハーモニー

みたいな感じ。

なので、Clothes.rdfUnit.rdfが編集されたらそちらも編集する必要がある。
とはいえ編集するのは面倒なので、Node.jsを用意して適度に回している。

github.com

(一応公開してるが完全に自分専用って感じのゴミコード.....)

問題点

XML構文解析はど素人だし興味もないので、Node.jsでやっているのはキャラごとにまとめたSPARQLクエリをGETで叩いて、XMLっぽく排出している。
そう、SPARQLを叩いている
ということは、一旦変更をSPARQL鯖に反映しなくてはならない。
つまり作業としては,

  1. Unit.rdf || Clothes.rdfを編集してPullReq出す
  2. GitHubでマージする
  3. SPARQL鯖の更新
  4. 自動生成スクリプトを走らせる
  5. PullReqを出す
  6. GitHubでマージする
  7. SPARQL鯖の更新

というわけ。慣れればそんなに苦でもない
とはいえ傍から見ればアホ丸出しなので、苦手意識のあるCircleCI克服も兼ねて自動化しました。

結果

CircleCIでやっていることの流れ

  1. DockerImagestain/jenaでCircleCI上でSPARQLを動かせる環境を作り、自動生成で使っているのと同じクエリを撃つ
  2. 結果をJSONファイルに保存する
  3. DockerImageをcircleci/nodeに切り替える
  4. 結果を保存したJSONファイルを読み込み、自動生成で使っているのと同じ処理でXMLを作る
  5. node.jsのconsole.logはUTF-16っぽいので、エンコードを変換する
  6. GitHubbotアカウントでPullReqに対して追加コミットをする

botでの追加コミットはこちらを参考にした

www.ncaq.net

実際の動作

github.com

試行錯誤の末の結果なので、ゴミみたいなコミットは無視して欲しい

おわりに

まぁ結局、botでやる方式だとPullReqする人全員がbotのアクセスを許可しないといけなかったり、CIの発火のタイミング調整がブランチかタグしかなくてコミット時に統一しないといけなかったりと他の編集者の負担を増やしそうだったのでマージせず、別の方法をすることにした。 某所での助言より、自動生成物かつ、逆方向の述語という性質上GitHubで変更を記録するほどでも無いので、Jenkinsでどうにかしようとしている。
それはまたの機会に。