- 投稿日:2020-01-19T20:01:54+09:00
Unity WebGL なゲームをNode.JS + jsdom + headless-glで動かしたかった
えっ このネタ続くの。。?
前回( Unity WebGLで使われているシェーダを抜き出してARBアセンブリを眺める )はWebGLビルドのUnityゲームをトレースして、使われているシェーダ命令があんまり多くないことを確認した。
ブラウザ上の動作では同期APIの実装に制約がありちょっと手を入れづらいため、Node.jsで動かしたかった。別案としてNW.jsを使うというのもあったが、今回の手法でもWebブラウザ側のDOMを使わないといけないところは一応クリアしている。
結果と手法
結局成功したんだか失敗したんだかよくわからないところまでは来たと思う。
結果
- Node.jsでもUnity WebGLは起動して描画コマンドも発行する
- でも描画がまっくらなので、まだ描画が正常かどうかは確認できていない
JSdomでWebAssemblyを使ったサイトがそのまま動くのは地味にすごい気はする。
手法
Node.jsにはWebAssemblyがあり、Webプラットフォームの実装としては:
- JSdom : https://github.com/jsdom/jsdom -
createElement
とかXMLHttpRequest
のようなDOM APIのNode.js上の実装。- headless-gl : https://github.com/stackgl/headless-gl - Node.js上のWebGL実装
- fake-indexeddb : https://github.com/dumbmatter/fakeIndexedDB - Indexeddb APIのfake実装。Unity WebGLはEmscriptenのIDBFSをセーブデータの保存とかキャッシュに使っているっぽいのでそれ用。
のようなものが既にある。これらはUnity WebGLビルドを動かすには十分に見える。なので、 JSdomで作成したNode.js上の仮想ブラウザ環境の
window
、document
各オブジェクトを都合よくpolyfillし、Unity WebGLが生成したWebページをそこにロードする が基本的な方針となる。結果、実際に描画コマンドの発行は確認でき内容は正しそうだが(
Error
と出ているのは単にError.captureStackTrace
でスタックトレースを拾っているからで、glError
になるようなエラーが無いことは確認している)、出画は真っ暗だった。
描画内容自体はゼロ埋めではない(濃いグレーになっている)し、描画コマンドが出ているのは確認できているので、headless-gl側の問題だと考えている。
Unity WebGL ビルドの構造
(今回は Unity
2019.2.17f1
のWebGL 1.0ビルドを元に書いている。)Unity WebGLビルドは、要するに Emscripten でビルドしたUnityエンジンを単に動作させているだけで、Webプラットフォームに移植されている部分は殆んどない。例外は通常のビルドではFMODを使用しているオーディオエンジンで、WebGLビルドでは自前のオーディオミキシングを.wasm側に持っているようだ。
重要な構成ファイルは4つある。これらのファイルを直接見るには、Unityのビルド設定で圧縮を事前に無効化しておく必要がある。
UnityLoader.js
Build/UnityLoader.js
はJavaScript製の起動ルーチンで、こちらはUnity手製とみられる。通常のビルドではMinifyされるがDevelopment buildを選択することでMinify前のソースを見ることができる。UnityLoaderでは:
- ゲーム本体のIndexeddbへのキャッシュ (HTTPキャッシュを使わないのは安全と圧縮のため?)
- ブラウザ検出とUser Agentベースでの非対応ブラウザの起動抑止
- Emscriptenの出力に
Math.fround
の省略パッチを当てる- プログレスバーの出力
- Web Workerを使用した gzip、brotli のデコード
- アセットのEmscriptenファイルシステム側への投入
- 実際のゲーム本体のロード
といった作業をしている。
対応ブラウザチェックは、
"Please note that your browser is not currently supported for this Unity WebGL content. Press OK if you wish to continue anyway."
のようなメッセージを表示して起動を止めてしまう。ここは回避できなかったので今回の実験ではパッチしてしまった。
NAME.wasm.framework.unityweb
Build/<NAME>.wasm.framework.unityweb
はEmscriptenが出力したJavaScript側のコードで、C++(IL2CPP)部分とDOMのインターフェースは基本的にここに集約されている。... これはEmscripteのランタイムほぼそのままなので特にコメントはなし。ただ、JSdomで要素の
width
、height
を変更する方法がわからなかったので、こちらに手を入れて無理矢理表示状態を作りだしている。NAME.wasm.code.unityweb
Build/<NAME>.wasm.code.unityweb
は、IL2CPPとEmscriptenによって生成されたエンジン本体およびゲームコードで、少くともDevelopment buildでは完全にシンボル入りになるためwasm2wat
コマンド等で逆アセンブルできる。例えば、jsdomで作ったElementは自動的に
width = height = 0
となるが、この状況だと:call $__Z12InputProcessv block ;; label = @3 block ;; label = @4 call $__Z14GetPlayerPausev i32.const 2 i32.eq br_if 0 (;@4;) i32.const 0 call $_emscripten_is_webgl_context_lost br_if 0 (;@4;) call $_JS_SystemInfo_GetCurrentCanvasWidth i32.eqz br_if 0 (;@4;) ;; ★ ゼロだったらレンダリングループを中断 call $_JS_SystemInfo_GetCurrentCanvasHeight i32.eqz br_if 0 (;@4;) ;; ★ ゼロだったらレンダリングループを中断となっていて、何らかの方法でfakeしないといけないことが判る。今回は
NAME.wasm.framework.unityweb
の方をパッチした。NAME.data.unityweb
Build/<NAME>.data.unityweb
はゲームアセットを格納したアーカイブで、UnityLoader.js
でEmscriptenのファイルシステム側に投入される。現時点ではメモリ上に完全なファイルシステムを構築している。つまり、WebGLビルドではアセットのサイズがそのままメモリ消費量になる。
DOM API のfake
JSdomやheadless-glでそれなりの量のAPIを実装できているが、いくつかは手で実装する必要があった。
createElement
JSdomは実は
canvas
要素を実装しており、2d
コンテキストは使うことができる。 ...が、残念ながらWebGLコンテキストはサポートしていないため自前の実装で置き換える必要がある。今回はロードしたスクリプトから呼ばれる
createElement
のみをhookして置き換える方向とした。function proxyCEl(nam){ console.log("PROXY CEL", nam); if(nam == "canvas"){ let cv = d.super_createElement("div"); // ★ 適当に div 要素でお茶を濁す cv.getContext = function(type, attr){ // ★ div要素に getContext メソッドを追加する ...Emscripten側は実際に生成された要素が
div
であっても、getContext
メソッドさえ有れば正常に動作する。Duck typing。
getContext
で得たコンテキストには WebGLDebugTools( https://github.com/KhronosGroup/WebGLDeveloperTools )を入れて呼び出しのトレースを実施している。ただ WebGLDebugTools はextensionを正常に処理できなかったため、extensionについてはhookしないように適当にパッチした。createObjectURL 、 revokeObjectURL
createObjectURL
は、ブラウザ内部のポインタを指すURL(Blob URL)を生成するもので、HTML5 File APIの一部になっていて( https://www.w3.org/TR/2019/WD-FileAPI-20190911/#creating-revoking )、ここで生成したURLがfetch
で使えるようになることが期待される( https://www.w3.org/TR/2019/WD-FileAPI-20190911/#blob-url )。しかし、JSdomにはこの機能が無いため、自前で実装してやる必要がある。
revokeObjectURL
は、Development buildではデバッグの都合で使用されていない。(ブラウザ側のDeveloper toolsで見るのに不便だからと考えられる)今回は
Promise
を生成するクロージャの形で適当なところに保存し、fetch
操作ではそのPromise
そのものを返す(2回目以降はPromise.resolve
で直接値を返す)ことにした。createObjectURL
で生成されるURLの形式は決まっているが今回は適当に付けている。function createObjectURL(blob){ let cache = false; function cb(){ // ★ このクロージャをBlob URLと関連付けて保存する if(cache){ return Promise.resolve(cache); // ★ 2回目以降は cache を直接resolveする }else{ return new Promise((res, rej) => { const the_reader = new w.FileReader(); // ★ 初回はFile APIで読み出す the_reader.onload = (e => { const bv = the_reader.result; cache = Buffer.from(bv); res(cache); }); // FIXME: ??? It seems JSDOM ArrayBuffer cannot move to // Buffer object. Use readAsText instead for now... the_reader.readAsText(blob); }); } } return blob_to_url(cb); }function fetch_blob(uri){ // => promise const r = blobs[uri](); return r; } class MyLoader extends ResourceLoader { // ★ ResourceLoaderはJSdom本来のローダー fetch(url, options){ // (JSdomのfetchを実装している 、 HTML5のfetchではないことに注意) console.log("LOADER",url,options); if(url == "xblob:1"){ // ★ 1番のblobはローカルファイルに差し替え console.log("PATCH!!!"); const buf = fs.readFileSync(path.resolve(__dirname, "patch1.js")); return Promise.resolve(buf); }else if(url.indexOf("xblob:") != -1){ return fetch_blob(url); // ★ Promiseを生成して返す }else{ return super.fetch(url, options); // ★ blob以外では本来のローダーを使う } } }Unityでは、この
createObjectURL
は、JavaScriptで展開される可能性のある.unityweb
を保持するのに使用している。このため、MyLoader
クラスにはデバッグ用にファイルを差し替える機能も付けている。Web Workers
UnityではHTTP経由で取得するファイルを解凍するためにWeb workerを使用している。実際の展開処理はプレイヤー設定で "Uncompressed" を選べばskipでき、そのためのコードは非常に単純なため、今回は真面目にWeb Workerを実装するのではなく単に関数だけ実行できるようにした。
Unityが供給するWeb workerコードは、
this.onmessage
ハンドラでコマンドを受けとり、グローバルのpostMessage
手続きでデータを返す簡単なものなので、const text = buf.toString(); const src = "const obj = function (postMessage) {" + text + "}; obj"; const ex = eval(src); // ★ 元のUnityのWorkerコードをwrapしたもの const home = this; const recvmsg = function(x,y){ if(home.the_handler){ let ev = {data: x}; console.log("RECVMSG", x, y); home.the_handler(ev); }else{ console.log("!! Q RECV MSG", x, y); home.recvq.push([x,y]); } } this.workerobj = {}; this.workerobj._init = ex; // ★ 元のコードが this にアクセスしているため、適当なオブジェクトに付ける this.workerobj._init(recvmsg); console.log("WORKER STANDUP", this.workerobj);のように
function(postMessage){ /* 元のUnityのWorkerコード */}
をeval
してWorkerコードのグローバル変数postMessage
をエミュレートする形にした。元のコードはJavaScript的な
this
にonmessage
手続きを新設するため、wrapしたコードを呼び出す前に適当なオブジェクトに付け、後からthis.workerobj.onmessage(ev);のように使用(Workerへのメッセージングのエミュレート)している。
requestAnimationFrame
requestAnimationFrame
は、通常のブラウザではVSYNC(物理的な画面の更新完了)のたびに呼ばれる。ただし、今回は面倒なのでフリーラン(全力で描画する)とした。つまり、単にNode.jsのprocess.nextTick
にコールバックを登録して即呼び出すだけとしている。function sleep(ms){ return new Promise((res) => setTimeout(res, ms)); } function proxyRAF(cb){ //console.log("RAF", cb); process.nextTick(async function(){ //await sleep(100); // ★ 描画速度の調整用 const now = w.performance.now(); // ★ JSdomの Window.performance を使用して現在時刻を返す console.log("RAF", now); cb(now); update_screenshot(); }); return 99.99; // ★ これは cancel 用だが使われないので適当な値を返す }かんそう
... いきなりUnityをやらないで、Emscriptenで書いたコードで小さく試すべきだったね。。ただ、もう必要なWebAPIは揃えてしまったし面倒なところは結局Unity固有だったので何とも。。
次はheadless-glを自前のWebGL実装に置き換えてみる。
- 投稿日:2020-01-19T19:47:50+09:00
[Unity+Live2D]再生する音に合わせてリップシンクする
はじめに
Live2Dモデルが再生する音に合わせてリップシンクする方法をまとめてみました。
リップシンクの設定をする前に今回のリップシンクする対象のモデルを以下の手順で配置します。
Unity初心者がUnityでLive2Dモデルを動かすため環境構築をした
口元がよく見えた方がいいので、Main CameraのSizeは0.7くらいがいいかもしれません。
起動時に音声を再生しリップシンクする
公式のCubism SDK Tutorial リップシンクの設定の手順を実施し、起動時に再生した音声に対してリップシンクするように設定します。
1.リップシンクを管理するコンポーネントをアタッチ
モデルのインスタンスにCubismMouthControllerをアタッチします。
Cubism SDK Tutorial リップシンクの設定 より、設定項目は以下の指定をする必要があります。
CubismMouthControllerには、設定項目が2つあります。
– Blend Mode:指定のパラメータに現在設定されている値に対して、Mouth Openingの値をどう計算するのかを指定します。
Mutiply:現在設定されている値にMouth Openingの値を掛け合わせます。
Additive:現在設定されている値にMouth Openingの値を足し合わせます。
Override:現在設定されている値をMouth Openingの値で上書きします。
– Mouth Opening:口の開閉の値です。1で開いた状態、0で閉じた状態として扱います。この値が外から操作されると、指定されたパラメータの値も連動します。Blend ModeをOrverrideにします。
2.リップシンクをさせるパラメータを指定
モデルのインスタンスのParamMouthOpenYに
CubismMouthParameterをアタッチします。
これで、CubismMouthControllerでParamMouthOpenYが扱われるようになります。
3.指定されたパラメータの値を操作するコンポーネントを設定
モデルのインスタンスに自動リップシンクするためにリップシンクの入力用コンポーネントとしてCubismAudioMouthInputをアタッチします。
CubismAudioMouthInputはAudio Souceの音量でCubismMouthControllerのMouth Openingの値を操作しているようです。
Cubism SDK Tutorial リップシンクの設定 より、設定項目は以下の指定をする必要があります。
CubismAudioMouthInputには、以下の4つの設定項目があります。
- Audio Input:入力に使用するAudioSourceを設定します。ここで設定したAudioSourceにセットされたAudioClipの音量を使用します。
- Sampling Quality:サンプリングする音量の精度を設定します。以下の設定の並びが下になるほど精度が上がりますが、計算の負荷も上がります。
- High
- Very High
- Maximum
- Gain:サンプリングした音量を、何倍した値で扱うかを設定します。1で等倍です。
- Smoothing:音量から算出する開閉の値をどのくらいスムージングするかを設定します。値を大きくするほど滑らかになりますが、計算の負荷も上がります。今回はGainを10、Smoothingを1にします。
Gainを小さくすると、口を開く大きさが小さくなります。
Smoothingを小さくすると、口の開閉の動きが緩やかになります。
リップシンクの動きを大きく滑らかにして見てみたいので、最大値を入れます。
(Cubism SDK Tutorial リップシンクの設定では、Gainが1になっていますが、1だとほとんど開閉しません)CubismAudioMouthInputのAudio ImputにAudio Sourceを入れるとリップシンクしてくれるようになりますので、まずAudio Sourceを作りましょう。
Hierarchyタブで右クリックして、Audio Sourceを作ります。
Play On Awakeにチェックがついていれば起動時に音声ファイルが再生されます。
今回は起動時にリップシンクするところが見たいので、チェックします。
Audio Clipには音声ファイルを置きます。
音声ファイルはProjectにドラッグアンドドロップで置きます。
Unityで使える拡張子はオーディオファイル - Unityマニュアルに記載があります。
作ったAudio SourceはCubismAudioMouthInputのAudio Imputへ設定してください。
Playボタンを押してリップシンクするか見てみましょう。
音量に応じて口の開閉の大きさが変わると思います。サンプルモデルがリップシンクする pic.twitter.com/V0iejDzxjp
— nori (@nori0__) January 19, 2020キー操作で再生される音声を変える
上記の操作だと決まった音声ファイルしか再生できないので、いろいろなファイルが再生できるようにしてみました。
C#スクリプトでAudio SourceのAudioClipを変更するようにします。ProjectタブにC#スクリプトをAudioControllerという名前で新規追加します。
C#スクリプトをダブルクリックしてエディターで編集します。
using Live2D.Cubism.Framework.MouthMovement; using System.Collections; using System.Collections.Generic; using UnityEngine; public class AudioController : MonoBehaviour { [SerializeField] CubismAudioMouthInput audioMouthInput; public AudioClip AudioClip1; public AudioClip AudioClip2; // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { if (Input.GetKey(KeyCode.Z)) { // Zが押された時、AudioClip1を再生する playAudio(AudioClip1, stopAudio); } else if (Input.GetKey(KeyCode.Y)) { // Yが押された時、AudioClip2を再生する playAudio(AudioClip2, stopAudio); } } public delegate void functionType(); private IEnumerator Checking(functionType callback) { if (audioMouthInput?.AudioInput == null) { Debug.LogError("audioMouthInput.AudioInput no set"); yield return null; } while (true) { // 1フレーム待つ yield return new WaitForFixedUpdate(); if (!audioMouthInput.AudioInput.isPlaying) { callback(); break; } } } private void playAudio(AudioClip audioClip, functionType callback) { if (audioMouthInput?.AudioInput == null) { Debug.LogError("audioMouthInput.AudioInput設定なし"); return; } audioMouthInput.AudioInput.clip = audioClip; // 音声ファイルを変更 audioMouthInput.AudioInput.Play(); StartCoroutine(Checking(callback)); Debug.Log("play sound"); } // Checkingメソッドのcallbackメソッドで実行される private void stopAudio() { Debug.Log("stop sound"); } }MainシーンにGameObjectを作成して
GameObjectにC#スクリプトを追加します。
GameObjectにフィールドを設定します。
Audio Mouth Inputにモデルのインスタンスを設定します。
上記で記載した手順「起動時に音声を再生しリップシンクする」でモデルのインスタンスにCubismAudioMouthInputをアタッチしていたら選べると思います。
Audio Clip1、Audio Clip2に音声ファイルを設定します。
Projectタブに音声ファイルを置いたものをドラッグアンドドロップしてください。
Playボタンを押して、Zを押すとAudio Clip1に設定した音声ファイルが、Yを押すとAudio Clip2に設定した音声ファイルが再生されます。
参考
- 投稿日:2020-01-19T15:55:11+09:00
UnityでUnityちゃん3Dモデルを動かす(2020年版)
概要
Unityちゃん3Dモデルを動かそうとしたらよく解らないバグが出て
すんなりいかなかったのでメモを残します。
Unityバージョン:2018.4.14f1 Personal
Unityちゃん3Dモデルバージョン:1.2.1本文
1.
最新のUnity本体(Unity Hub)をダウンロードする
https://unity3d.com/jp/get-unity/update
Unity Hubバージョン 2019.2.182.
Unityを起動して新規プロジェクト(3D)を作成する3.
何も追加していない状態で再生ボタン(Play)を押す。
→Game画面に切り替わる
※切り替わったらもう一時再生ボタンをおしてScene画面に戻します。
4.
「Unityちゃん3Dモデルデータ」をダウンロードする。
https://unity-chan.com/download/index.php
バージョン 1.2.15.
ダウンロードしたUnityちゃんのデータ(UnityChan_1_2_1.unitypackage)を
Assetsフォルダにドラッグアンドドロップする。
→確認画面が出たら「import」を押す。
6.
importが終わった後に再生ボタン(Play)を押すと、
「All compiler errors have to be fixed before you can enter playmode!」
のエラーが表示される。
7.
Console の画面で赤い警告ボタンを押すと、エラーの詳細が表示される。
「Assets\UnityChan\Scripts\AutoBlink.cs(8,23): error CS0234: The type or namespace name 'Policy' does not exist in the namespace 'System.Security' (are you missing an assembly reference?)」
また、エラーメッセージをダブルクリックすると、エラーとなっているソースコードが開く。
8.
'Policy'がないと言われているので、
using System.Security.Policy;
をコメントアウトして保存します。変更前
// //AutoBlink.cs //オート目パチスクリプト //2014/06/23 N.Kobayashi // using UnityEngine; using System.Collections; using System.Security.Policy; namespace UnityChan {変更後
// //AutoBlink.cs //オート目パチスクリプト //2014/06/23 N.Kobayashi // using UnityEngine; using System.Collections; //using System.Security.Policy; namespace UnityChan {9.
再生ボタン(Play)を押すと、Game画面に切り替わる。10.
Unityちゃんを追加します。
Assets > UnityChan > Prefabs > for Locomotion
の中にある
unitychan.prefab
をScene画面にラッグアンドドロップする。
11.
再生ボタン(Play)を押すと、Game画面に切り替わる。
が、Unityちゃんが落下してどこかに行ってしまう。
12.
床を作ります。
Scene画面で
Create > 3D Object > Planeを選択する。
13.
Unityちゃんが落ちなくなる。
Game画面内にある Interactionにある通り、動かすこともできる。
※方向キーの上で前進するバイオハザード方式
以上です。お疲れ様でした!
参考ページ
【2019】UNITY-CHAN(ユニティちゃん)を最速で動かす具体的な方法
https://miyagame.net/unity-chan-move/
- 投稿日:2020-01-19T15:42:02+09:00
UnityでUnityちゃん3Dモデルを動かす(2020年版)
概要
Unityちゃん3Dモデルを動かそうとしたらよく解らないバグが出て
すんなりいかなかったのでメモを残します。
Unityバージョン:2018.4.14f1 Personal
Unityちゃん3Dモデルバージョン:1.2.1本文
1.
最新のUnity本体(Unity Hub)をダウンロードする
https://unity3d.com/jp/get-unity/update
Unity Hubバージョン 2019.2.182.
Unityを起動して新規プロジェクト(3D)を作成する3.
何も追加していない状態で再生ボタン(Play)を押す。
→Game画面に切り替わる
※切り替わったらもう一時再生ボタンをおしてScene画面に戻します。
4.
「Unityちゃん3Dモデルデータ」をダウンロードする。
https://unity-chan.com/download/index.php
バージョン 1.2.15.
ダウンロードしたUnityちゃんのデータ(UnityChan_1_2_1.unitypackage)を
Assetsフォルダにドラッグアンドドロップする。
→確認画面が出たら「import」を押す。
6.
importが終わった後に再生ボタン(Play)を押すと、
「All compiler errors have to be fixed before you can enter playmode!」
のエラーが表示される。
7.
Console の画面で赤い警告ボタンを押すと、エラーの詳細が表示される。
「Assets\UnityChan\Scripts\AutoBlink.cs(8,23): error CS0234: The type or namespace name 'Policy' does not exist in the namespace 'System.Security' (are you missing an assembly reference?)」
また、エラーメッセージをダブルクリックすると、エラーとなっているソースコードが開く。
8.
'Policy'がないと言われているので、
using System.Security.Policy;
をコメントアウトして保存します。変更前
// //AutoBlink.cs //オート目パチスクリプト //2014/06/23 N.Kobayashi // using UnityEngine; using System.Collections; using System.Security.Policy; namespace UnityChan {変更後
// //AutoBlink.cs //オート目パチスクリプト //2014/06/23 N.Kobayashi // using UnityEngine; using System.Collections; //using System.Security.Policy; namespace UnityChan {9.
再生ボタン(Play)を押すと、Game画面に切り替わる。10.
Unityちゃんを追加します。
Assets > UnityChan > Prefabs > for Locomotion
の中にある
unitychan.prefab
をScene画面にラッグアンドドロップする。
11.
再生ボタン(Play)を押すと、Game画面に切り替わる。
が、Unityちゃんが落下してどこかに行ってしまう。
12.
床を作ります。
Scene画面で
Create > 3D Object > Planeを選択する。
13.
Unityちゃんが落ちなくなる。
Game画面内にある Interactionにある通り、動かすこともできる。
※方向キーの上で前進するバイオハザード方式
以上です。お疲れ様でした!
参考ページ
【2019】UNITY-CHAN(ユニティちゃん)を最速で動かす具体的な方法
https://miyagame.net/unity-chan-move/
- 投稿日:2020-01-19T12:29:03+09:00
Awake, Start, Updateの違い
Awake
- 全てのオブジェクトが初期化された後に読み込まれる。
- 必ずStartの前に読み込まれる。
- scriptが有効か無効かに関わらず読み込まれる。(Unity上のチェックを外しても読み込まれます。)
Start
- Updateメソッドが最初に読みこまれる直前に読み込まれる。
- scriptが無効だと読み込まれない。
Update
- 毎フレーム読み込まれる。
- パソコンの性能によって読み込まれる回数が変わってくるため、Time.deltaTimeを使って性能差を埋める必要がある。
MonoBehaviourとは
- Unityで読み込まれる全てのscriptのベースクラス。
- C#を使うときは、MonoBehaviourから引き出して使う。
- 投稿日:2020-01-19T06:55:34+09:00
Azure の次世代 GPU 搭載 VM NVv4 を試す
概要
Azure では 2019 年の夏ごろに NVv4 という次世代の GPU 搭載仮想マシンが登場しており、現在プレビューが実施されています。
このアナウンス時から申請していたプレビューアクセスが最近になってようやく認められましたので、試してみた結果と感想をまとめました。
NVv4 の紹介
基本的なことは NVv4 シリース (プレビュー) のドキュメント を参照してください。
「NV って名前なのに NVIDIA じゃなくて AMD の GPU なんですか?」といった素朴な疑問については Microsoft Ignite 2019 イベント開催時の AMD のブログ投稿が言及しています。
Wondering what does NVv4 stands for? “N” = GPU Accelerated VM family in Azure “V” = Visualization “4” = Generation 4 – which means the NVv4 is the latest generation of GPU enabled virtual desktops services from Azure.
NVv4 シリーズの価格表を見ると、米国中南部リージョンで開始価格が 11,446円/月〜と、従来世代の GPU 搭載 VM と比べ非常に安くなっています (ただしこれはプレビュー価格であり、GA すると 2 倍くらいになると思います)。
この価格は AMD Radeon Instinct MI25 という仮想化対応 GPU を最大で 8 分割することで実現しているため、性能も値段相応になるものと思われます。なお CPU には AMD EPYC 7V12 (Rome) を使用しています。
NVv4 の想定用途としてはモダンな Web ブラウザやオフィスアプリなどを利用する VDI があげられていますが、Unreal や Unity といったゲームエンジンのツールを多用するようになったゲーム開発分野でも同様に役立ちそうです。クラウドで GPU は使いたいけど月 10 万円以上するモンスタースペック GPU VM は不要という用途は数多くあり、NVv4 の提供する性能・価格ゾーンはこれにマッチしていると思います。
NVv4 プレビューに申し込む
ということでぜひ試してみたい NVv4 ですが、それにはこちらの申請フォームを提出してプレビューアクセスを申し込む必要があります。
なお自分は申請後かなり待たされました。プレビューのアナウンス後に申請フォームを合計で 3 回くらい提出しましたが、申請を受け付けたといったような連絡もなく、まったく状況がわかりませんでしたので、Microsoft の営業の方にもお願いしたところ 2019 年末にようやくプレビューの案内のメールが来て NVv4 が使えるようになりました。
NVv4 プレビューは米国中南部または西ヨーロッパのリージョンで実施されています。実際に利用可能なリージョンと個数 (vCPU クオータ) はプレビュー開始時に案内メールの中で通知されます。自分の場合は米国中南部で 8 コアまででした。
NVv4 VM を使ってみる
NV_v4 VM 自体は Azure Portal や CLI などを使って普通に作ることができます。
GPU を利用するにはドライバのインストールが必要です。いまのところサポートされているのは Windows VM のみです。
Windows
こちらのドキュメントに説明があるとおり、ドライバ Radeon-Pro-Software-for-Enterprise-19.Q4.1-Technical-Preview.exe をダウンロードしてインストールします。インストールが完了すると Radeon Instinct MI25 MxGPU というディスプレイアダプタが現れ、すぐに利用可能になります。
ディスプレイアダプタはデバイスマネージャーより無効化・有効化することができます。 GPU の有無は即時反映され、再起動は必要ありません (自分はこれにちょっと驚きました)。
Standard_NV4as_v4 の Windows VM で DxDiag を実行した結果を Gist に貼っておきました。
Linux
Microsoft からは Linux 用のドライバは提供されていません。次のような場所で見つかるソフトウェアを使うとなにか動くかもしれないですが、まだ試せていません。
Standard_NV4as_v4 の Linux VM で lscpu や lspci してみた結果は次のとおりでした。
CPU は 2 コア 4 スレッドのようです。
NVv4 の感想
自分はもっぱら macOS の Microsoft Remote Desktop 10 アプリで、東京から米国中南部リージョンの Standard_NV4as_v4 の Windows Server 2019 Datacenter 1809 の VM に接続して試しています。いくつかテストした結果や感想を書いておきます。
- traceroute で計測したところ、ホップ数は 17 くらい、遅延は 150〜160ms 程度でした。
- リモートデスクトップの操作感自体は、ネットワーク的な距離や遅延から想像されるほど悪くはない感じでした。 Chrome も VSCode も若干のひっかかりはありますが、普通に使うことができます。
- ゲームエンジンや 3D CG ツール類の作業環境として常用するのはやはりちょっと厳しいですが、ビルドやレンダリング前の確認・調整には十分使えるパフォーマンスがあります。東日本リージョンで動くようになれば常用も可能になるかもしれません。
- Unity: GPU のない VM ではカクカク動作だった Editor のシーンプレビューが、 NVv4 で GPU を有効にするとスルスルと動くようになりました。 Angry Bots 2 から出力した Windows64 Player は 1FPS 動作だったものが 45FPS 程度まで向上しました。
- Unreal: GPU のない VM では
DX11 feature level 10.0 is required to run the engine.
と出て Editor がそもそも起動しませんでしたが、 NVv4 では普通に起動するようになりました。いくつかの Maketplace のサンプルプロジェクトも動作しました。- Blender: GPU のない VM では
A graphics card and driver with support for OpenGL 3.3 or higher is required.
と出て起動しませんでしたが、NVv4 では普通に起動します。 Blender のデモ からダウンロードできるサンプルプロジェクトを開いて編集・レンダリングができました。…動作確認レベルの情報やふんわりとした感想しかなくて申しわけありません。
本来ならスコアが出るベンチマークプログラムでも走らせるべきところですが、リモートデスクトップ環境だと普通のゲーム系のベンチマークアプリを試したところ軒並み悲惨なスコアしか出ず、本来の CPU・GPU の性能が計測できていないように感じました。
オフスクリーンのレンダリング性能を計測しようと Blender Benchmark を走らせてみましたが、
Radeon Instinct MI25 MxGPU (OPENCL)
を使おうとすると、どのシーンを選んでも Loading scece ... から先に進行しませんでした。なにかバグとかスペック不足とかがあるのかもしれませんが、詳しくは調べてはいません。自分が特に関心があるのは Unity のアセットビルドや Unreal のクックの所要時間のような、ゲームプロジェクトの CI/CD の観点での性能なので、今後はなにか手頃なサイズのベンチマーク用プロジェクトを用意して時間を計ってみたいと思っています。
まとめ
Azure の新しい GPU 搭載 VM である NVv4 が試せるようになったので、とりあえず試した結果をまとめてみました。
これまでクラウドの GPU といえば重い機械学習やムービーレンダリング用途の性能お化けばかりでしたが、 NVv4 はそれよりずっと軽い性能と低い価格の領域をカバーしています。 Azure 以外のクラウドベンダには NVv4 に相当するプロダクトはまだなく、ポテンシャルを感じます。
早くプレビューが終わって東日本リージョンでも使えるようになることを願っていますが、こればかりは需要で決まるものなので、日本のどこかの企業が Windows Virtual Desktop などで大口利用してくれないかなあと思っています。またゲーム業界でも、クラウドの GPU を活用した開発や CI/CD をやっていこうという人が増えるとよいですね。