🔬

WebのみでVRスライドが作れるVCIスライドジェネレータの作り方

2021/12/20に公開

この記事はVCI Advent Calendar 2021 20日目です。

TL;DR

  • 脱Unity、VCIの民主化
  • VCIの中身はglTFなのでC#やUnityとか知らなくても直でいじれる
  • 実装としてはImageMagicでPDFを画像に変換し、RubyでテンプレートとなるVCIを元に生成
  • 将来的にはTSO連携もちゃんとサポートしたい

はじめに

今年1年ちょこちょこやっていた活動として 「Unityを使わずにVCIを作る」 という事をしていたので、VCIアドベントカレンダーとしては少し変化球ですが、今回はその話をしたいと思います。

VCIはバーチャルキャストでワールド(スタジオ/ルーム)を超えてアイテムを扱うための仕様です。多くのVR SNSのようにワールドやアバター側にアイテムが紐付いてるのではなくアイテムが独立しているのがバーチャルキャスト/VCIの特徴だと思います。これは昨今話題のメタバースにおける 相互運用(Interoperability) への第一歩ですね。

前半はこちらの動画でも少しだけ話している内容で、後半は少し技術的な解説を入れています。「
ポエムはいらん!」という人は記事の前半スキップでOK!
https://www.youtube.com/watch?v=GUHt_Uk-pF4

関連記事:

VCIスライドジェネレータはどんなもの?

まずはVCIスライドジェネレータってどんなもの? ということですがこちらはPDFをアップロードするだけスライド用のVCIに変換するWebサイトです。

https://vci-slide-generator-dnb6froqha-uc.a.run.app/

Vキャスでプレゼンスライドを作る方法としてホワイトボードを使う方法が一般的です。ただPDFやPPTXではなく画像として1枚1枚アップロードしてそのURLを貼り付ける、という作業が必要です。そもそもアップロード先をどこにする? って問題も。私は概ね毎週Weekly ITニュースなどの配信をしているので、さすがに面倒でSlide4VRを作りましたがVCIになってくれてれば、それが一番楽ですよね? ホワイトボードに貼付ける作業もいらなくなるし。

VCIスライドジェネレータの使い方は以下のような感じです。

  • PowerPoint等で作ったPDFをアップロード
  • 変換されてダウンロードしたVCIをThe Seed Online (TSO)にアップロード

これだけです。ね、簡単でしょ?

作成したスライドは以下のレーザポインタで操作する事ができます。
https://seed.online/products/bcaf01edf83f0526682593cd160d5f36816c13c0faca41fc7fdfa1c421e0167d

脱Unity。VCIの民主化を目指して

さて、VCIスライドジェネレータは 「スライドを手軽に作りたい」 という目的ももちろんあるのですが、それ以上に脱Unityというか VCIの民主化 を目指した個人的な試みの第一歩でもあります。
VCIはスタジオ/ルーム間で持ち運び出来きる自由さだけではなく、ユーザストアのようなコミュニティベースのエコシステムの仕組みもあります。
https://seed.online/static/guide-user-store
https://seed.online/store

こうしたVCIはUnityを使うことで比較的簡単に作成することが出来ます。そう、あくまでUnityを使うことで、です。もちろん、これは素晴らしい事だし多くの高度なVCIにはUnityは必須だと思うのですがVCIのポテンシャルはもっとある、と思ってます。

それはコンテンツ系のVCIです。例えば私のスライドもそうですがVCIとしてのギミックはそのままに、中身のコンテンツだけを差し替えれば良いアイテムというのはたくさんあります。例えば「ホロポスター」等は(実際は細かな調整が要るかもだけど)基本的には画像を差し替えれば使えそうですし、こういうVCIで作成したコミックもそうです。

ミュージックプレイヤーなども曲を差し替えるだけで作ることができると思います。現時点ではこうしたコンテンツを差し替えるだけで済むVCIを作成するのにもUnityをインストールし、立ち上げて、変換するという非常に高いハードルが存在しています。

もしコンテンツをアップロードするだけでこれらを作ることが出来ればどうでしょう? VR上での即売会のようなイベントももっとコンテンツホルダーが参加しやすくなると思いますし、様々な電子書籍プラットフォームや音楽配信に追加する 「ついで」 程度の感覚でVR上のコンテンツも増えるかもしれません。作成をUnityに限定しないことでVCIの可能性はもっともっと広がると思うのです。

公式でも出されてるVCIジェネレータ(画像アップローダ)も同じような思想なんだろうな、と。ミ
クランドのフォトコンテストも、このアップローダ無しにはあんなに盛り上がらなかったでしょうし。
https://virtualcast.jp/blog/2021/06/vcquest-vcigenerator/

これをコミュニティでもやってしまいたい、というのが私の野望です。
すでに変換は対応してるので後はアップロードAPIを公式が開放してくれれば、TSOに自動登録するところまで正式に対応することが出来ます。MIROさんもみゅみゅさんもサービス開発進捗報告会「整理して考えます」 と言ってたから待ってますよ!

VCIの基本的な構造

前半はポエムをたくさん書いたので少しは技術的な話を入れていきたいと思います。
以下の記事でも書いていますが、VCIはVRMと同様にglTFというフォーマットをベースにしています。曰く3D界隈のJPEGのような存在らしいです。
https://zenn.dev/koduki/articles/7596fadeaff328

JSONとバイナリをベースにしたフォーマットで以下のようなフォーマットになっています。

glTFには大きく分けて以下の3つの領域があります。

  • ヘッダー部(先頭12バイト)
  • JSONによるメタ情報(Chunk 0, 長さはChunk 0の先頭4バイト(符号なし32bit整数)に記載)
  • バイナリ形式のBuffer部 (Chunk 1, 長さはChunk 0の先頭4バイト(符号なし32bit整数)に記載)

特に重要なのはJOSNによるメタ情報と実データのBuffer部です。JSON部分を取り出したものがこちらです。メタ情報からバッファのどの領域になんのデータが入っているかを確認し、データを抜き出したり置き換えたりすることでVCIを取り扱う事が出来ます。

PDFからVCIへの変換の仕組み

PDFからVCIに変換する手順ですが以下のような順番で行っています。

  • アップロードしたPDFをImageMagickを使って特定サイズの画像に変換
  • 画像に変換後、画像数を数えてページ数と縦横の最大INDEXを取得
  • テンプレートとなるVCIを読み込みベースとなるメタデータ(JSON)やサムネイルを取得する
  • VCIスクリプトを修正(RubyのテンプレートエンジンのERBを利用)
  • 上記を元にVCIのメタデータを変更し、合わせてバイナリであるBufferも置き換える
  • 変換後のVCIを返す

という挙動をしています。特にVCI変換のメインは以下のようなコードになっています。上記の画像に変換後の作業を順番にやっている形ですね。

    def generate
        property, glb_buff_data = load_template(@template_vci_path)
        image, img_idx = load_image(property, @image_path, SLIDE_TEXTURE_NAME)
        thumbnail, thum_idx = load_thumbnail(property, @thum_path)
        src, src_idx = load_script(property, page_size, max_page_index)

        data = mk_data(property, glb_buff_data, image, img_idx, thumbnail, thum_idx, src, src_idx)
        meta = {title:@meta_title, version:@meta_version, author:@meta_author, description:@meta_description}
        json = mk_json(property, image, img_idx, thumbnail, thum_idx, src, src_idx, data, @page_size, meta)

        json, data = align(json,  data)
        store(@output_path, json, data)
    end

全体はこちらのソースコードをご確認ください。
ポイントとしては以下のバイナリを書き込む際に4で割り切れない場合はFFでパディングをしてやることです。これを怠ると見かけ上は上手く生成されているのに壊れたVCIになります。

    def align json, data
        json_padding = padding_size(json.size)
        json = json + (" " * json_padding)
    
        data_padding = padding_size(data.size)
        data = data + (FF * data_padding)
    
        return [json, data]
    end
    def mk_data property, glb_buff_data, image, img_idx, thumbnail, thum_idx, src, src_idx
        data = ""
        property["bufferViews"].each_with_index do |x, i|
            case i
            when img_idx
                data += image + FF * padding_size(image.size)
            when thum_idx
                data += thumbnail + FF * padding_size(thumbnail.size)
            when src_idx
                data += src + FF * padding_size(src.size)
            else
                len = x["byteLength"]
                data += glb_buff_data[x["byteOffset"], len] + FF * padding_size(len)
            end
        end
        data
    end

最後にヘッダ、メタデータ、データのすべてを1つのバイナリに書き込む事で最終的なVCIが完成します。

   def store output_path, json, data
        glb = GLB_H_MAGIC
        glb += GLB_H_VERSION
        glb += [(GLB_H_SIZE * 3) + (GLB_H_SIZE * 2) + json.size + (GLB_H_SIZE * 2) + data.size].pack("L*")

        glb += [json.size].pack("L*")
        glb += GLB_JSON_TYPE
        glb += json
        glb += [data.size].pack("L*")
        glb += GLB_BUFF_TYPE
        glb += data

        open(output_path, 'wb') do |f|
            f.write(glb)
        end
    end

VCIの生成に関するより詳しい説明は下記も御覧ください。
https://zenn.dev/koduki/articles/d4332883491f7a

SinatraでのWebアプリケーション化

上記のロジックをWebアプリケーションとして動かしているのが下記となります。
https://github.com/koduki/vci-slide-generator

post '/generate' do
    p params[:author].strip
    vci_meta = VCIMeta.new(
        title: params[:title].strip,
        version: params[:version].strip,
        author: params[:author].strip,
        description: params[:description].strip
    )

    file = params[:file][:tempfile]

    template = VCITemplate.new
    workspace = Workspace.new

    # upload
    upload workspace.pdf_path, file

    # translate to image
    page_size, max_page_index = Pdf2png.new.transform template, workspace

    # translate to vci
    vci_slide = VCISlide.new template, workspace, vci_meta, page_size, max_page_index
    vci_slide.generate

    send_file workspace.vci_output_path, {filename:"slide.vci", disposition:"attachment"}

end

基本的にはアップロードして、Pdf2png.new.transformでPDFを画像に変換した後、vci_slide.generateでVCIを生成し、そちらをsend_fileでブラウザに返してダウンロードさせています。
Slide4VR等に組み込む時にはPDFの保存等も考えますが現時点のVCI Slideジェネレータは直にダウンロードとなっています。
TSOのアップロードAPIが公開されtれば、ここのsend_fileをTSOへのアップロード処理に差し替える事でシームレスなサムネイル更新や商品登録等も可能です。

まとめ

とりあえずポエムとして 「何故脱Unityをしたいのか?」 という話を書きつつ、今年1年ちょこちょこちょこやっていた 「WebアプリケーションでのVCI生成」 について書きました。

もちろん、テンプレートとなるVCIを作成するのにはUnityが必要ですが、Webアプリケーションを使う場合には一切の知識や準備が不要、というのがポイントです。上手く行けばVCIを一気に増やすことも可能なんじゃないかと。

私自身は複雑なVCIも作れないですしイラストや音楽のようなコンテンツも作れないので、こういった支援する仕組みを作ってVキャス、ひいてはメタバースそのものを盛り上げていけたらな、と思っています。来年はミュージックプレイヤーとか作りたいなー。

それではHappy Hacking!

Discussion