WebのみでVRスライドが作れるVCIスライドジェネレータの作り方
この記事は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!
関連記事:
VCIスライドジェネレータはどんなもの?
まずはVCIスライドジェネレータってどんなもの? ということですがこちらはPDFをアップロードするだけでスライド用のVCIに変換するWebサイトです。
Vキャスでプレゼンスライドを作る方法としてホワイトボードを使う方法が一般的です。ただPDFやPPTXではなく画像として1枚1枚アップロードしてそのURLを貼り付ける、という作業が必要です。そもそもアップロード先をどこにする? って問題も。私は概ね毎週Weekly ITニュースなどの配信をしているので、さすがに面倒でSlide4VRを作りましたがVCIになってくれてれば、それが一番楽ですよね? ホワイトボードに貼付ける作業もいらなくなるし。
VCIスライドジェネレータの使い方は以下のような感じです。
- PowerPoint等で作ったPDFをアップロード
- 変換されてダウンロードしたVCIをThe Seed Online (TSO)にアップロード
これだけです。ね、簡単でしょ?
作成したスライドは以下のレーザポインタで操作する事ができます。
脱Unity。VCIの民主化を目指して
さて、VCIスライドジェネレータは 「スライドを手軽に作りたい」 という目的ももちろんあるのですが、それ以上に脱Unityというか VCIの民主化 を目指した個人的な試みの第一歩でもあります。
VCIはスタジオ/ルーム間で持ち運び出来きる自由さだけではなく、ユーザストアのようなコミュニティベースのエコシステムの仕組みもあります。
こうしたVCIはUnityを使うことで比較的簡単に作成することが出来ます。そう、あくまでUnityを使うことで、です。もちろん、これは素晴らしい事だし多くの高度なVCIにはUnityは必須だと思うのですがVCIのポテンシャルはもっとある、と思ってます。
それはコンテンツ系のVCIです。例えば私のスライドもそうですがVCIとしてのギミックはそのままに、中身のコンテンツだけを差し替えれば良いアイテムというのはたくさんあります。例えば「ホロポスター」等は(実際は細かな調整が要るかもだけど)基本的には画像を差し替えれば使えそうですし、こういうVCIで作成したコミックもそうです。
ミュージックプレイヤーなども曲を差し替えるだけで作ることができると思います。現時点ではこうしたコンテンツを差し替えるだけで済むVCIを作成するのにもUnityをインストールし、立ち上げて、変換するという非常に高いハードルが存在しています。
もしコンテンツをアップロードするだけでこれらを作ることが出来ればどうでしょう? VR上での即売会のようなイベントももっとコンテンツホルダーが参加しやすくなると思いますし、様々な電子書籍プラットフォームや音楽配信に追加する 「ついで」 程度の感覚でVR上のコンテンツも増えるかもしれません。作成をUnityに限定しないことでVCIの可能性はもっともっと広がると思うのです。
公式でも出されてるVCIジェネレータ(画像アップローダ)も同じような思想なんだろうな、と。ミ
クランドのフォトコンテストも、このアップローダ無しにはあんなに盛り上がらなかったでしょうし。
これをコミュニティでもやってしまいたい、というのが私の野望です。
すでに変換は対応してるので後はアップロードAPIを公式が開放してくれれば、TSOに自動登録するところまで正式に対応することが出来ます。MIROさんもみゅみゅさんもサービス開発進捗報告会で 「整理して考えます」 と言ってたから待ってますよ!
VCIの基本的な構造
前半はポエムをたくさん書いたので少しは技術的な話を入れていきたいと思います。
以下の記事でも書いていますが、VCIはVRMと同様にglTFというフォーマットをベースにしています。曰く3D界隈のJPEGのような存在らしいです。
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の生成に関するより詳しい説明は下記も御覧ください。
SinatraでのWebアプリケーション化
上記のロジックをWebアプリケーションとして動かしているのが下記となります。
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