entry-header-eye-catch.html
entry-title-container.html

entry-header-author-info.html
Article by

ブラウザ上で3Dキャラクターと会話できる「ChatVRM」をオープンソースで公開しました

こんにちは、VRoid部のkeshigomuです。

普段は主にVRoid Hubのフロントエンドエンジニアとして、3Dキャラクターを表示するビューワーの開発に携わっています。また@pixiv/three-vrmという、Web上で3Dモデルを使ったコンテンツを開発するためのOSSライブラリの運用も行っています。

今回、ブラウザで簡単に3Dキャラクターと会話できる技術デモ「ChatVRM」とそのコードをオープンソースで公開しました。

「ChatVRM」は、テキスト・口頭で話しかけた言葉にキャラクターがフルボイスで回答してくれる「キャラクターと会話できる」デモです。WEBブラウザ上で動作でき、3Dキャラクターのインポート・切り替え、キャラクターに併せて声を調整することもできます。

(2023/07/10追記) 読み上げ音声の生成に使用していたKoeiro APIの提供終了に伴い、以前のデモとコードは動作しなくなりました。 正式リリースされたKoemotionのKoeiromap 1.0 APIに対応したデモとコードはChatVRMのリポジトリをご確認ください。

デモページとサンプルコードについて

このデモは、GitHubPagesからお試しいただけます。
▼デモはこちら
https://chatvrm.glitch.me
 ※実際に動かすには、ChatGPTのAPI keyが必要です

また、GitHub上でオープンソースでコードを公開しています。 ブラウザで3Dキャラクターとコミュニケーションするサービスや機能を実装する際の参考に自由にお使いいただけます。
▼サンプルコードはこちら
https://github.com/pixiv/ChatVRM

「ChatVRM」の技術概要

「ChatVRM」における、3Dキャラクターとのテキスト・音声での会話部分の実装は、以下に紹介する4つの技術によって実現されています。

①3Dキャラクターの表示
@pixiv/three-vrm
https://github.com/pixiv/three-vrm

②ユーザーの音声の認識
Web Speech API(SpeechRecognition) https://developer.mozilla.org/ja/docs/Web/API/SpeechRecognition

③返答文の生成
ChatGPT API
https://platform.openai.com/docs/api-reference/chat

④読み上げ音声の生成
Koeiro API
http://koeiromap.rinna.jp/

①3Dキャラクターの表示

ブラウザ上での3Dキャラクターの表示には@pixiv/three-vrmを使用しています。
@pixiv/three-vrm はthree.js を使ってブラウザ上でVRMを読み込んで表示するためのライブラリであり、ピクシブがオープンソースとして公開しています。
自社内では、VRoidHubの開発に利用しており、個人企業問わずWEBアプリケーションで使われています。 three.jsと@pixiv/three-vrmを使ってVRMを読み込むコードは以下になります。

import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import { VRMLoaderPlugin } from '@pixiv/three-vrm';

const scene = new THREE.Scene();
const loader = new GLTFLoader();
loader.register((parser) => {
  return new VRMLoaderPlugin(parser);
});

loader.load(
  '/sample.vrm',
  (gltf) => {
    const vrm = gltf.userData.vrm;
    scene.add(vrm.scene);
  }
);

詳しくはhttps://github.com/pixiv/three-vrmを確認してください。

今回のデモでは3Dキャラクターには以下のように簡単なリップシンクや、
src/features/vrmViewer/model.ts

public update(delta: number): void {
    if (this._lipSync) {
      const { volume } = this._lipSync.update();
      this.emoteController?.lipSync("aa", volume);
    }

...

定期的な瞬きをしてもらっています。
デモの該当箇所はsrc/features/emoteController/autoBlink.tsです。

public update(delta: number) {
  if (this._remainingTime > 0) {
    this._remainingTime -= delta;
    return;
  }

  if (this._isOpen && this._isAutoBlink) {
    this.close();
    return;
  }

  this.open();
}

private close() {
  this._isOpen = false;
  this._remainingTime = BLINK_CLOSE_MAX;
  this._expressionManager.setValue("blink", 1);
}

private open() {
  this._isOpen = true;
  this._remainingTime = BLINK_OPEN_MAX;
  this._expressionManager.setValue("blink", 0);
}

②ユーザーの音声の認識

ユーザーの音声の認識にはWeb Speech APIのSpeechRecognitionを使用しています。
マイクボタンを押してユーザーにマイクの使用を許可してもらった後、
resultイベントでテキストとして受け取りつつ随時表示し、発言の終了時に返答文の生成へ移行しています。
デモでの該当箇所はsrc/components/messageInputContainer.tsx です。

const SpeechRecognition =
      window.webkitSpeechRecognition || window.SpeechRecognition;

const recognition = new SpeechRecognition();
recognition.lang = "ja-JP";
recognition.interimResults = true; // 認識の途中結果を返す
recognition.continuous = false; // 発言の終了時に認識を終了する

recognition.addEventListener("result", handleRecognitionResult);
recognition.addEventListener("end", handleRecognitionEnd);
// 音声認識の結果を処理する
const handleRecognitionResult = useCallback(
  (event: SpeechRecognitionEvent) => {
    const text = event.results[0][0].transcript;
    setUserMessage(text);

    // 発言の終了時
    if (event.results[0].isFinal) {
      setUserMessage(text);
      // 返答文の生成を開始
      onChatProcessStart(text);
    }
  },
  [onChatProcessStart]
)

③返答文の生成

返答文の生成にはChatGPT APIを使用しています。デモではGPT-3を使用するように設定しています。
始めの応答を出来るだけ早くするためにStreamで随時受け取り一文ごとに読み上げ音声の生成を行っています。

https://github.com/openai/openai-cookbook/blob/main/examples/How_to_stream_completions.ipynb

デモでは以下のような文章をsystemロールで送っています。
この返答に含まれる[happy]などの文字からキャラクターの表情か声色を変更しています。

...

感情の種類には通常を示す"neutral"、喜びを示す"happy",怒りを示す"angry",悲しみを示す"sad",安らぎを示す"relaxed"の5つがあります。

会話文の書式は以下の通りです。
[{neutral|happy|angry|sad|relaxed}]{会話文}

あなたの発言の例は以下通りです。
[neutral]こんにちは。[happy]元気だった?

...

④読み上げ音声の生成

読み上げ音声の生成(Text-to-Speech)にはKoeiro APIを使用しています。
Koeiromap と Koeiro APIでは2つのパラメータを指定することで、様々な声色の音声を生成することができます。
モデルに合わせた声色をユーザーが自由に調整できるので、今回のデモに非常に適しています。

Koeiromap と Koeiro APIの使い方や利用規約は
http://koeiromap.rinna.jp/
をご確認ください。

また、Koeiromap と Koeiro APIでは喜怒哀楽などの感情ごとに音声を生成することが出来るので、
生成された返答文に含まれる[angry]等の感情タグからstyleを設定し、音声での喜怒哀楽を表現しています。

デモでの該当箇所はsrc/features/koeiromap/koeiromap.tsです。

import { TalkStyle } from "../messages/messages";

export async function synthesizeVoice(
  message: string,
  speaker_x: number,
  speaker_y: number,
  style: TalkStyle
) {
  const param = {
    method: "POST",
    body: JSON.stringify({
      text: message,
      speaker_x: speaker_x,
      speaker_y: speaker_y,
      style: style,
    }),
    headers: {
      "Content-type": "application/json; charset=UTF-8",
    },
  };

  const koeiroRes = await fetch(
    "https://api.rinna.co.jp/models/cttse/koeiro",
    param
  );

  const data = (await koeiroRes.json()) as any;

  return { audio: data.audio };
}

基本的なところは以上です。
ChatGPT APIの返答文からKoeiro APIの声色とVRMの表情を変更することで、より生き生きとした印象を与えることができました。 特定のキャラクターに特化した表現などもあると思います。その場合は設定メニューからsystemロールの文章を変更したり、リポジトリからフォークしてぜひ機能追加などをしてみてください。

最後に

「ChatVRM」はオープンソースで展開しておりますので、3Dキャラクターとのコミュニケーションを可能にするサービスや機能の参考に積極的に活用してください。
ピクシブ社は、「未来にあって当たり前のものを作り続ける」ことを目指して、日々プロダクトを開発しております。

おまけ

今回実装した待機モーションは、mocopiで収録したBVHデータを、現在仕様策定中のVRMアニメーション形式へ変換し使用しています。

興味がある方はぜひ以下の「ChatVRM」のコードや、VRMの現状の仕様を確認してみてください。
github.com

デモの該当箇所はsrc/lib/VRMAnimation/VRMAnimation.tsです。

keshigomu
2022年新卒入社。VRoid Hubのフロントエンドや3Dビューアの開発を行っています。3Dアバターで遊ぶことや映画鑑賞が好きです。