え?UnityのWebGL書き出しでもリッチな表現を?

f:id:hara-makoto:20201221094641j:plain

できらぁっ!

この記事は Tech KAYAC Advent Calendar 2020 の22日目の記事です。

こんにちは、原です。 普段はクライアントワークチームでunityを使って色々インタラクティブなものを作ってる、2児の父です。

unityroom、いいよね

普段の仕事ではVRとかARとか、未来志向なものに近いことをやっているのですが

2児の父にもなりますと、もっと身近に体験できるジャンルのものづくりにも興味が湧いてきますね。

ということで、身近に体験できると言えばunityroom。

unityroom.com

ご存知の方も多いと思いますが、UnityからWebGL書き出しをしたファイルをアップロードすることで、即自分の作ったゲームを公開できる素晴らしいWebサービスです。 これを使えば、簡単に子供と一緒に自作ゲームで遊べる!

やれんのか!UnityWebGL

でも、unityのWebGL。錆びついた私の記憶の中では、unityのwebgl書き出しって、pcやモバイルのネイティブ書き出しに比べるとかなり表現が貧弱で、私が思い描く理想のゲーム世界を描けないんじゃないだろうか? 私は結構ぱっと見の表現にはこだわるタイプだよ?

そう思って試してみました。

こちらは、クリスマスツリーを育てるサンプルゲームです。

unityroom.com

池から水を汲んでツリーに与えると、育っていきます。ぜひ遊んでみてください。

ゲームの内容はともかくとして、見てくださいこの表現。明るいところにはブルームが効いてほわっとしていて、水のところはさざなみ立つような水面シェーダー。また、生える木が距離によってぼやけたりくっきりしたりしていますが、こちらはデプスオブフィールドが効いています。

f:id:hara-makoto:20201221095312j:plain

こういったポストエフェクトや特殊なシェーダーって、てっきりwebgl書き出しだとできないものだと思っていましたが、ばっちりできています。やるじゃんunity !!

webglでもリッチな表現ができるための機能「URP」

ただ、これをやるためには一つ条件があります。それはレンダリングパイプラインを「URP」にすること。スポットライト的な光にはこちらの有料アセットを使っていますが、これはURPに対応したエフェクトアセットです。 もともと私はamplify shader editorを使っていたんですが(昨年記事参照) こちらを使ってスタンダードなレンダリングでwebgl書き出しを試したところ、このシェーダーで作った箇所が真っピンク、つまりいつもの壊れ方をしてしまい、大変なことになりました。

ユニバーサルなレンダリングの時代がやってくる

ちなみに今回作ったこのミニゲームを試しにiOS向けに書き出ししてみたのですが、なんとこちらも全く同じ見た目が再現できました。(厳密にはちょっとバージョンが違うものの動画ですが)

URPとはユニバーサルレンダリングパイプラインの略称なんですが、まさにユニバーサル!どんなプラットフォームでも同じ見た目を再現できるすごい機能ですね。 こちらのURPは、将来的にはデフォルトのパイプラインに置き換わると言われています。つまり、本デモのように、web、モバイル、pcとプラットフォームを跨いでも見た目のクオリティが統一される時代がやってくるのです。

リッチな表現が技術的に可能でも、準備はそれなりにかかる

今回言いたかったことがもう終わってしまって尺が余ったので、今回デモを作成するにあたって踏んだプロセスについて書いておきます。

1. エモいシーンを思い浮かべる

エモいシーンって色々あると思います。自分の中で今回思い浮かんだのは、「暗闇の中で光が差し込んでいて、そこだけ植物とか生えてる」でした。

2. ざっくり下描きする

1で思い浮かべたシーンをざっくりと下描きします。自分はそんなに絵が描けないので、この段階ではそんなに詰めません。

f:id:hara-makoto:20201220074706j:plain
ざっくりすぎてやばい

3. 3Dに起こす

下描きをもとに3Dを起こします。この段階ではまだセットアップやUV展開はしません。 f:id:hara-makoto:20201220075227j:plain

4. 起こした3Dをスクショして、procreateで色検討する

3Dを一旦全部Unityに取り込んで、なんとなくライト配置だけします。 f:id:hara-makoto:20201220073728j:plain

で、それをスクリーンショットして、procreateに取り込んで色をつけます。 f:id:hara-makoto:20201220074150j:plain

この工程を踏んでおかないと、Unity上の色調整で迷子になってしまいます。

5. 3DをUV展開してテクスチャを塗る・Unityシーンのライティング、ポストエフェクト、色を調整する

ビジュアルの方向性が決まったので、あとは一気に仕上げの作業を進めます。 今回は島のまわりの水面がそうですが、単純に色を調整するだけではなくてシェーダーの実装も必要になるとここの工程がより大変になりますね。 また、3DモデルのUV展開・テクスチャ作成は手間がかかりつつ省略できない作業で、ひたすら地道に進めていく感じです。

6. 完成

5が全部終わったら完成です。 といってもこの工程はビジュアル面の工程だけ書いていますが、今回のサンプルはわずかながらもゲーム要素を入れています。 今回はビジュアル重視のサンプルなのでゲーム要素はボリュームは少ないですが、上記のビジュアルプロセスと並行してゲーム部分の検討・実装を進める感じだったので結構大変でした。

まとめ

UnityのWebGL書き出しでもリッチな表現は、、できる!

以上です。

Siriとfirebaseを連携させて、アプリを操作してみる

こんにちは。はじめまして。
クライアントワーク事業部でフロントエンドエンジニアをしている
マチダヨウスケと申します。

www.kayac.com

この記事は、Tech KAYAC Advent Calendar 2020 の21日目の記事になります。

本記事の内容について

TouchDesignerの公式ツイッターでこのような動画がリツイートされていました。

この動画では、どうやらHomebridgeというものを使っているようです。
Homebridgeは、HomeKitに対応していないIoT機器でも制御出来るようにするソフトウェアです。

homebridge.io

今回は、このHomebridgeを使わないで、
Siriとfirebase(realtime database) を使って他のアプリ(TouchDesigner)を操作するシステムを作成してみます。

結果

この動画では、hey siriという音声が流れるので、もしかしたら手持ちのiPhoneが反応する可能性があります。
イヤホンをつけて視聴することをおすすめします。また滑舌が非常に悪いのでご注意ください。

vimeo.com

今回作ったファイルはこちらから閲覧できます。 github.com

システムの概要

システムは単純です。
firebaseのrealtime databaseで何らかの値を保持し、
firebase functionsで値を変更、更新するAPIを作成、
Siriに命令して、そのAPIを叩かせます。

他のアプリ側はdatabaseの更新を監視し、
変更に合わせて動かせばいいだけです。

今回はTouchDesignerで勧めていきますが、
realtime databaseが扱える環境(ブラウザとかvvvvとか、たぶんアンリアルエンジンもできる)
ならなんでも自由にSiriから操作する事ができます。
(UnityはDesktop向けのfirebaseがないので、OSCとかNodeとかで連携される形になると思います)
techblog.kayac.com

firebaseの設定

firebaseの初期設定やデプロイ方法は、たくさんの人が記事をあげてるので割愛します。
今回、firebase functionsを使いますが、
firebase functionsは無料プランで使うことはできなくなったので
従量課金プランに変更します。

速度や手軽さを重視したいので、realtime databaseを使用します。

TouchDesignerのPythonを使ってfirebaseと接続するので、
サービスアカウントから秘密鍵の作成をします。
f:id:machida-yosuke:20201219203242p:plain

Siriの設定

Siriとfirebaseと連携させます。
Apple公式からSiriKitというiphoneアプリとsiriを連携できるものがあり、
このkitを使って専用のアプリを作成する、といったことを最初に考えると思いますが、

SiriKitからできるのは
・メッセージの送信
・メモの作成
・お金の送金
などなど

Siriに対してなんでも命令させて、何でもやってもらえるというわけではないようです。
(検証期間はあまりとれなかったので、もしかしたらできるかもしれないです)

なので、今回はショートカットという機能を使って、アクションを作成します。

アクションからfirebaseのdatabaseを更新するAPIをたたき、
尚且、Siriに任意の言葉を喋らせるアクションを作成します。

下記添付の画像では、Siriに対して「データを初期化して」と命令したときに、

・firebase functionsで初期化するAPIを叩く
・「データを初期化しました」とSiriに言わせる
アクションです。

f:id:machida-yosuke:20201219205500p:plain

あとは、アクションを何個か作るだけです。

TouchDesignerの設定

TouchDesignerの操作は、たくさんの人が記事をあげてるので割愛します。

techblog.kayac.com

TouchDesignerでは、pythonのコードを実行することができます。
また、pipでpythonのライブラリをインストールし、インポートして実行することができます。
pythonとfirebaseを連携するライブラリはpyrebaseを使用します。

まずはTouchDesigner側でpipでインストールしたライブラリへのパスを教えてあげます。

import sys
mypath= '/Users/admins/.pyenv/versions/3.7.2/lib/python3.7/site-packages'
if mypath not in sys.path:
    sys.path.append(mypath)
print(sys.path)

つぎにfirebaseとの接続、databaseの値を取得するクラスを作成します

"""
Extension classes enhance TouchDesigner components with python. An
extension is accessed via ext.ExtensionClassName from any operator
within the extended component. If the extension is promoted via its
Promote Extension parameter, all its attributes with capitalized names
can be accessed externally, e.g. op('yourComp').PromotedFunction().

Help: search "Extensions" in wiki
"""

from TDStoreTools import StorageManager
TDF = op.TDModules.mod.TDFunctions

import pyrebase

class firebase:
    """
  firebase description
  """
    def __init__(self, ownerComp):
        # The component to which this extension is attached
        
        self.ownerComp = ownerComp
        
        self.InitData = {}
        self.UpdateData = {}
        self.Firebase = {}
        self.DB = {}

        # properties
        TDF.createProperty(self, 'MyProperty', value=0, dependable=True,
                           readOnly=False)

        # attributes:
        self.a = 0 # attribute
        self.B = 1 # promoted attribute

        # stored items (persistent across saves and re-initialization):
        storedItems = [
            # Only 'name' is required...
            {'name': 'StoredProperty', 'default': None, 'readOnly': False,
                                    'property': True, 'dependable': True},
        ]
        # Uncomment the line below to store StoredProperty. To clear stored
        #  items, use the Storage section of the Component Editor
        
        # self.stored = StorageManager(self, ownerComp, storedItems)

    def myFunction(self, v):
        debug(v)

    def PromotedFunction(self, v):
        debug(v)
    
    def FirebaseSetup(self):
        config = {
            "apiKey": "AIzaSyDB9lRYBhq9K_IvRAkCnDqFQvSU3dlFLyM",
            "authDomain": "siri-to-app.firebaseapp.com",
            "databaseURL": "https://siri-to-app-default-rtdb.firebaseio.com",
            "storageBucket": "siri-to-app.appspot.com",
            "serviceAccount": "/Users/admins/siri-to-touchdesigner/siri-to-app-firebase-adminsdk-5hxtt-83294d32cf.json"
        }
        self.Firebase = pyrebase.initialize_app(config)
        self.DB = self.Firebase.database()
    
    def GetInitData(self):
        self.InitData = self.DB.child("status").get()

    def InitUpdateData(self):
        self.UpdateData = {}
        
    def StartStream(self):
        def StartDBStream(message):
            print(message["event"])
            print(message["path"])
            print(message["data"])
            self.UpdateData = message["data"]
        self.DB.child("status").stream(StartDBStream)

self.DB.child("status").stream(StartDBStream)
databeseの更新を監視し、更新があった場合、変数に入れます。

databaseの監視は別スレッドで行われ、
別スレッドで行われる処理の中で、TouchDesignerのオペレーターにアクセスすることはできないので、
更新内容を変数にいれ、
timerオペレーターを使って、2秒に1回の間隔で
メンバ変数に値が入ってるか監視します。(2秒に1回の間隔でonDoneを実行)
ここの間隔は使用するPCのスペックに応じて変更します。

def onDone(timerOp, segment, interrupt):
    print('onDone')
    # print(firebase.InitData, 'firebase.InitData')
    # print(firebase.UpdateData, 'firebase.UpdateData')

    if firebase.UpdateData == {}:
        print('none')
        return

    for k in firebase.UpdateData.keys():
        # print(k, firebase.UpdateData[k],'keys')

        for r in range(table.numRows):

            tableName = table[r,0]
            # print(tableName)
            
            if tableName == k:
                table[r,1] = firebase.UpdateData[k]
                print('!!BREAK!!')
                break
    firebase.InitUpdateData()
    return

firebaseクラスのメンバ変数の参照とメソッドを叩くコードを書きます。

firebase = op('firebase')

firebase.FirebaseSetup()
firebase.GetInitData()

for status in firebase.InitData.each():
    key = status.key()
    val = status.val()
    op('table_data').appendRow([key, val])

firebase.InitUpdateData()
firebase.StartStream()

あとは、値の変更にたいして、どのような演出をいれるかを考えて実装するだけです。
f:id:machida-yosuke:20201219212334p:plain

あとがき

Siriとfirebaseを使うことで遠隔でアプリを操作することができました。
これを実装すれば、
「Hey Siri デバックモードに変更して」
といえば、両手がふさがってても、または遠隔でも、
アプリをデバックモードにすることができたりします。
※実用性はほぼありません

最後まで読んでいただき、ありがとうございました!!

今回作ったファイルはこちらから閲覧できます。

github.com

その他リンク

maoudamashii.jokersounds.com

konchi.kayac.jp

音楽著作権:魔王魂