GPUインスタンスと生成系AIを組み合わせてPlayCanvasで3Dシーンを構築してみました。

はじめに

GMOグローバルサイン・ホールディングスの羽賀(@mxcn3)です。ゲームエンジンPlayCanvasの日本コミュニティ運営や、テクニカルサポートを担当しております。GPUインスタンスと生成系AIを組み合わせて作成したデモプロジェクトの検証結果を紹介いたします。

経緯

この記事を書くきっかけとなったのは、全社的な生成系AIの取り組み強化を目指し、2023年6月21日に始まった「AI 活用 No.1 企業グループへの取組を加速全パートナー向け、NVIDIA 社最新 GPU 搭載サーバー/ノートパソコンの無償貸出を実施」プログラムにより、GPUインスタンスを借りたことです。

ノートパソコンの貸与やサーバーの貸与が受けられますが、サーバー利用の際に貸与されるGPUインスタンスは、「NVIDIA H100 (VRAM 80GB)」または「NVIDIA L4 (VRAM 24GB)」です。本記事での検証内容は、NVIDIA L4を利用したものです。

作成したデモについて

デモの概要

WebGLベースのゲームエンジン、PlayCanvasと生成系 AI を組み合わせて3Dシーンを生成するデモを作成しました。

テキストを入力すると、仮の3Dシーンが配置されます。配置された3Dシーンのオブジェクトをクリックすると、その3Dモデルが生成されます。ボクセル化といった機能も提供しており、生成されたモデルデータは .glb形式のモデルデータ でダウンロード可能です。

ウェブブラウザからアクセスできるようになっていますので、ぜひお試しください。

https://playcanv.as/e/p/xl9NWKYg/

※ デモは2023年 7月31日まで公開予定です。

PlayCanvasと生成系AIを組み合わせたデモの実装について

以下がこのデモで実行している内容です。この内容に沿ってそれぞれの実装と検証について説明します。

  1. プロンプトから3Dシーンの生成
  2. プロンプトから3Dモデルの生成
  3. GPUインスタンス上で Blender を用いた UV 展開とモディファイアの適用
  4. 生成した 3D モデルデータのダウンロード

1. プロンプトから3Dシーンの生成

(動画)プロンプトから3Dシーン生成中

デモにアクセスすると、最初に、3D モデルの生成画面が表示されます。この画面ではプロンプトで入力した内容を元に、3Dシーンを作成ができます。

構成について

PlayCanvasとChatGPTを利用したデモの構成

この部分では、3Dの描画にPlayCanvas、シーンデータの作成にChatGPTのAPIを利用してシーン作成しました。実際にどのようにChatGPTを用いて3Dシーンが作成されるのかを解説します。

PlayCanvas

PlayCanvasは、ウェブ上で3Dコンテンツを作成するためのプラットフォームです。ゲームエンジンのみを使用する場合は、Three.jsやBabylon.jsなどのWebGLライブラリと同様の方法で利用できます。PlayCanvasエディターを使用すると、共同編集機能やコードエディターなどを使用できます。プロジェクトの作成から公開まで、すべてをクラウド上で行うことができる便利なツールです。

ChatGPT API

ChatGPT APIは、OpenAIが提供するAPIの一部で、ChatGPTをプログラムから利用するためのものです。ChatGPT APIを使用することで、開発者は自身のアプリケーションやサービスにChatGPTの機能を組み込むことができます。

ChatGPT API Function calling

2023年 6月13日のFunction calling and other API updatesのアップデートにより、ChatGPTのAPIにFunction callingの機能が追加されました。これにより、ChatGPT APIの出力をテキストではなく、JSONオブジェクト形式で受け取ることができます。

Function callingを使用して3Dシーンの出力をする

Function callingの設定と呼び出しは以下のように行います。ChatGPTのAPIモデルとしてgpt-3.5-turbo-16kを利用します。例えば、今回のデモの実装では、Node.jsを利用してPlayCanvasから「公園」という入力がされた場合には、以下のように呼び出します。

const configuration = new Configuration({ apiKey: `your-api-key` });
const openai = new OpenAIApi(configuration);

const systemMessage = { role: "system", content: `3Dモデルのシーンを作成してください` }; // システムメッセージ
const userMessage = { role: "user", content: `公園` }; // ユーザーから入力されたプロンプト

const createCompletionRequest = {
  model: "gpt-3.5-turbo-16k",
  messages: [systemMessage, userMessage],
  function_call: "auto",
  functions: [
    {
      name: "generateSceneObjects",
      description: "3Dのシーンのオブジェクト一覧を作成",
      parameters: {
        type: "object",
        properties: {
          SceneObjects: {
            type: "array",
            items: {
              name: {
                // 3Dオブジェクトの名前
                type: "string",
              },
              position: {
                // 3Dオブジェクトの座標
                type: "array",
                items: {
                  type: "number",
                },
              },
              rotation: {
                // 3Dオブジェクトの回転
                type: "array",
                items: {
                  type: "number",
                },
              },
              scale: {
                // 3Dオブジェクトのスケール
                type: "array",
                items: {
                  type: "number",
                },
              },
            },
          },
        },
        required: ["SceneObjects"],
      },
    },
  ],
};
const completion = await openai.createChatCompletion(createCompletionRequest);

期待するレスポンスをfunctions内に定義し、ChatGPT のcreateChatCompletionを呼び出します。Function calling がChatGPTのAPI側で生成ができる場合には、requiredに指定されたSceneObjects含むレスポンスが返ってきます。

const completion = await openai.createChatCompletion(createCompletionRequest);
const message = completion.data.choices[0].message; 

// Function calling が利用されたかの判定
if (message.function_call) {
    const response = JSON.parse(message?.function_call.arguments);
    const sceneObjects = response.SceneObjects;

    // 結果を出力
    console.log(sceneObjects);
    // [
    //     {
    //         name: "オブジェクト名", 
    //         position: [0, 0, 0], 
    //         rotation: [0, 0, 0], 
    //         scale: [1, 1, 1]
    //     }
    // ]
}

Function callingが使用されなかった場合にも message.function_call の値を元に判定ができます。今回のデモも同様に、Function callingが呼び出された場合にPlayCanvas側にレスポンスを返しています。

上記のコードでオブジェクト名、座標、回転、スケールの情報を含んだJSONオブジェクトを取得

Function callingを利用せずに行う場合、受け取ったテキストデータをパースする必要がありますが、出力が安定しないことがあります。しかし、この機能を利用することで開発者が望むデータをある程度作成できます。

以上の手順により、複数のオブジェクトを含んだ3Dシーンを構築することができました。

2. プロンプトから3Dモデルの生成

(動画)プロンプトから3Dモデル生成中

先程生成された3Dシーンのモデルから、3Dモデルの生成を行います。3Dシーン上では自由にカメラの移動ができるため、好きなオブジェクトをクリックすることで3Dモデルを生成できます。

構成について

プロンプトから3Dモデルの生成のデモの構成

以下のような構成でウェブAPIとしてリクエストを受け取り、GPUインスタンス上で3Dモデルを生成して、PlayCanvasに対して“.glb形式の3Dモデルのデータ`を返却しています。

サーバーのスペック

今回、サーバーのスペックは以下のとおりです。L4のGPUインスタンスを活用し、サーバー上で3Dモデルを生成しています。

  • Ubuntu 22.04.2 LTS
  • メモリ 128GB
  • VRAM 24GB (NVIDIA L4)
  • ディスク 1TB
Shap-Eについて

Shap-Eは、OpenAIが公開しているテキストや画像から3Dモデルを生成するためのツールです。このツールはMITライセンスで公開されており、公式のGitHubには利用方法が掲載されており、ローカル環境でも実行可能です。

GPUインスタンスでのShap-E の実行

FastAPI を使ってShap-Eを利用して3Dモデルの生成と返却を行っています。
HuggingFaceにて公開されている、「hysts/Shap-E」を参考に作成いたしました。

Shap-Eには、プロンプト(prompt)、シード(seed)、ガイダンススケール(guidance_scale)、ステップ数(num_steps)の入力があります。これらの情報を受け取り、モデルデータの生成を行い、.glb形式のモデルデータを返却しています。

以上がShap-Eの概要とGPUインスタンスでの実行に関する説明です。

from fastapi import FastAPI, UploadFile, File, HTTPException, Query
from fastapi.responses import JSONResponse
from pathlib import Path
from starlette.responses import FileResponse
from model import Model # Shap-Eのインポート

app = FastAPI()
model = Model()
@app.get("/model/generate/")
async def generate_model(
    prompt: str = Query("A chair that looks like an avocado"),
    seed: int = Query(0),
    guidance_scale: float = Query(15),
    num_steps: int = Query(64)
):
    try:
        glb_path = model.run_text(prompt, seed, guidance_scale, num_steps)
        path = Path(glb_path)
        if path.is_file():
            return FileResponse(path, media_type='model/gltf-binary', filename="model.glb")
    except Exception as e:
        raise HTTPException(status_code=500, detail=e)

データ形式の説明

今回生成されることになるモデルデータは、.glb形式です。.glb形式とは、ウェブ上で3Dコンテンツを効率的に表示するための規格であり、WebGL系のライブラリ、PlayCanvas等で広くサポートされています。

.glb形式モデルデータのブラウザ上での描画

PlayCanvasでは、.glb形式のモデルデータをブラウザ上で描画するためにloadFromUrlAndFilenameメソッドが使用されます。コードは以下の通りです。

const url = `/model/generate/?prompt=プロンプト`;
pc.app.assets.loadFromUrlAndFilename(url, `model.glb`, "container", (err, asset) => {
  const object = asset.resource.instantiateRenderEntity(); // APIレスポンスから3Dモデルデータを読み込みます
  this.entity.addChild(object); // シーンに3Dモデルを追加します
  // ... すでに存在するモデルデータの削除処理
});

PlayCanvasでモデルデータを描画する

この実装により、Shape-Eを利用して、GPUインスタンス上で生成された3DモデルをPlayCanvas上で表示することが可能となりました。

3.GPUインスタンス上でBlenderを用いたUV展開とモディファイアの適用

(動画)生成したモデルに対して、リメッシュとスマートUV展開を実行

モデルデータのダウンロード機能の実装が完了したので、次に、GPUインスタンス上でBlenderを用いることを試みました。Shap-Eにより生成されたモデルは、色の決定に頂点カラーを使用しています。また、モデルデータの整形も含めて、Blenderを用いてUV展開とモディファイアの適用を行いました。

構成について

Blenderを利用したエンドポイントの構成

こちらの実装ではShap-Eで生成されたモデルに対して3Dモデルの編集の処理として、Blenderを利用してUV展開とモディファイアの適用を行っています。

Blender

Blender(ブレンダー)は、オープンソースの3Dコンピュータグラフィックスソフトウェアです。3Dモデリング、アニメーション、レンダリング、ビデオ編集など、多岐にわたる機能を提供しています。

GPUインスタンスでBlenderの実行

今回の検証をして初めて知ったのですが、GPU インスタンス上で Blender を実行することができます。Blender は実行ファイルに--backgroundの引数を渡すことでヘッドレスモードで起動ができます。

blender --background

バックグラウンドで起動した場合でも、 .blend ファイルというBlenderのプロジェクトファイルを与えて起動することができます。 .blend ファイルを渡すには -b オプションをつけます。

blender --background -b xxxxxx.blend

以下のコードで、GPUインスタンスで生成されたモデルに対してリメッシュとスマートUV展開が行っています。

import bpy
import sys

# Blender の引数を取得
argv = sys.argv
argv = argv[argv.index("--") + 1:]  # '--' 以降がスクリプトの引数
input_path, output_path = argv[0], argv[1]

# 入力となる 3D モデルを読み込みます。
bpy.ops.import_scene.gltf(filepath=input_path)

# インポートされた全てのメッシュに対し、リメッシュとスマートUV展開を適用します。
for obj in bpy.context.scene.objects:
    if obj.type == 'MESH':
        # アクティブなオブジェクトを設定します。
        bpy.context.view_layer.objects.active = obj
        obj.select_set(True)

        # Remesh モディファイアを追加します。
        modifier = obj.modifiers.new(name="Remesh", type='REMESH')

        modifier.mode = 'BLOCKS'
        modifier.octree_depth = 5  # Remesh の解像度を設定します。値が大きいほど詳細になりますが、その分、計算時間も増えます。

        # モディファイアを適用します。
        bpy.context.view_layer.objects.active = obj
        bpy.ops.object.modifier_apply(modifier="Remesh")

        # UV 展開のために Edit モードに切り替え、全ての頂点を選択します。
        bpy.ops.object.mode_set(mode='EDIT')  # Edit モードに切り替えます。
        bpy.ops.mesh.select_all(action='SELECT')  # 全ての頂点を選択します。

        bpy.ops.uv.smart_project() # スマートUV展開を実行します。

        bpy.ops.object.mode_set(mode='OBJECT')

# 結果を保存します。
bpy.ops.export_scene.gltf(filepath=output_path)

この実装により、UVマップが生成され、生成された3Dモデルにテクスチャを適用可能です。

4. Shap-E で生成されるモデルデータのクオリティについて

Shape-E では、プロンプトの他にモデルのクオリティを指定するパラメータを設定できます。このパラメーターは、生成されるモデルの質を調整するもので、数値が大きいほどモデルのクオリティが向上します。デフォルトのステップ数は64で、その値を変更してそれぞれのシーンを生成した際の、生成時間と生成モデルデータのクオリティを検証しました。

※ 生成時間は3Dモデル毎の生成にかかる時間の目安となります。

各パラメーターによる生成時間とクオリティの変化

1.

ステップ数:1 生成時間: 1秒未満
2.

ステップ数: 8 生成時間 約3 秒
3.

ステップ数: 16 生成時間 5 秒程度
4.

ステップ数: 32 生成時間 10 秒程度
5.

ステップ数: 64 生成時間 15 秒程度
6.

ステップ数: 128 生成時間 30 秒程度
7.

ステップ数: 200 生成時間 50 秒程度

生成時間と生成されるモデルデータの品質を考慮して、今回のデモプロジェクトではステップ数を64に固定して生成を行いました。

5.生成した 3D モデルデータのダウンロード

(動画)生成された3Dシーンを3Dモデリングソフトで利用する

デモプロジェクトでは、右上のダウンロードボタンを押すと、シーンのデータを.glb形式の3Dモデルデータとしてダウンロードできます。

生成された3Dモデルは.glb形式のモデルデータであるため、Blenderなどの3Dモデリングソフトで編集可能です。

シーンのエクスポートについては、PlayCanvasを用いて実装しています。

詳細はこちらの記事「PlayCanvas エディターのシーンを動的に GLB / USDZ としてエクスポートをする」をご覧ください。

3Dモデルをゲームエンジン(PlayCanvas)で利用する方法

生成された3Dシーンをゲームエンジンで実行中

今回は最後に、作成したモデルデータを利用して、3Dウォークスルーシーンを作成してみましょう。
生成された.glb形式のモデルデータはPlayCanvas等のゲームエンジンで読み込むことができます。

PlayCanvasにモデルデータをインポートすることにより、以下のようなシーンが作成できます。

ゲームエンジンに読み込まれたモデルデータ

PlayCanvasはキャラクターコントロールのためのFirst Person Controllerアセットをアセットストアから利用可能です。このアセットをシーンに追加することで、WASDキーによる移動が可能なプロジェクトを作成できます。

PlayCanvasのアセットストア

次に、地面に対してコライダーを設定します。PlayCanvasではエディタ上から直接、コライダーの設定ができます。

PlayCanvasで当たり判定の設定設定

以上の手順により、GPUインスタンスを使用して作成した3Dモデルをゲームエンジン(PlayCanvas)上で操作できるようになりました。

生成したモデルデータを使って作成されたウォークスルー

こちらもURLからアクセスができますのでぜひお試しください。

デモ URL: https://playcanv.as/e/p/s5YwrvK0/

あとがき。

今回は、GPUインスタンスを活用し、生成系AIを用いた3Dシーンの生成について検証しました。GPUインスタンスの使用により、3Dモデルを生成した後にBlenderなどの3Dモデリングソフトを動かし、UV展開やモディファイアの適用などを行うことが可能であることを確認しました。

まだ改善の余地はいくつか存在しますが、モックデータの作成などについて今後利用可能になる可能性があると思われます。

質問や不明点がありましたら、Twitterの@mxcn3までお問い合わせください。