🦒
Unity×gRPCでVR描画データを送信する
はじめに
本記事は2025年3月1日(土)に開催した、VRプロフェッショナルアカデミー主催の 『第16回VRフェス ~VRが創るミライ★ARが救うセカイ』virtual reality festival vol.16 にて、VRエキスパートコース16期生として出展させていただいた作品 『ペンと鍵の部屋VR』 の補助資料になります。
本プロジェクトでは、VR空間内で描画したものを解析し、アイテムとして生成する仕組み を実装するために gRPC通信 を活用しました。
概要
Unity(MetaQuest3)とgRPCを活用して、VR内の描画データをサーバーへ送信する仕組みについて記述します。
-
gRPCとは?
- Googleが開発した高性能なRPC(Remote Procedure Call)フレームワーク。
- ProtoBufを使用して通信データを最適化。
- リアルタイム通信に適している。
gRPCのプロトコル定義
以下は、VRの描画データを送信するために作成した ProtoBuf 定義です。
syntax = "proto3";
option csharp_namespace = "VRAcademyAudition";
package vracademyaudition;
// ヘルスチェック
message HealthCheckRequest {}
message HealthCheckResponse {
enum ServingStatus {
UNKNOWN = 0; // 不明な状態
SERVING = 1; // サービス中
NOT_SERVING = 2; // サービス停止中
}
ServingStatus status = 1; // サービスの状態
string message = 2; // エラーメッセージなど
}
// 3Dベクトル情報
message Vector3Proto {
float x = 1;
float y = 2;
float z = 3;
}
// 色情報
message Color {
float r = 1;
float g = 2;
float b = 3;
float a = 4; // アルファ値(透明度)
}
// 線データ
message Line {
repeated Vector3Proto positions = 1; // LineRenderer.GetPositions()
float width = 2; // 線の太さ
Color color = 3; // 線の色
}
// クライアント情報の定義
message ClientInfo {
enum ClientType {
UNKNOWN = 0;
DEVELOPMENT = 1; // 開発環境
PRODUCTION = 2; // 本番環境
}
ClientType type = 1;
string device_id = 2; // デバイス固有ID
string device_name = 3; // デバイス名
string system_info = 4; // システム情報
string app_version = 5; // アプリケーションバージョン
}
// 描画データの定義
message DrawingData {
string drawing_id = 1; // 一意識別子
string scene_id = 2; // シーンID
int64 draw_timestamp = 3; // 描画タイムスタンプ(UNIX時間)
repeated Line draw_lines = 4; // 複数のLineRendererデータ
Vector3Proto center = 5; // 描画全体の中心点
bool use_ai = 6; // AIによる処理を行うかどうか
string client_id = 7; // クライアントID
ClientInfo client_info = 8; // クライアント情報
map<string, string> metadata = 9; // メタデータ
}
// アップロード結果を表すメッセージ
message UploadResponse {
bool success = 1; // アップロード成功フラグ
string message = 2; // メッセージ(エラー時はエラー内容)
string upload_id = 3; // アップロードされたデータの識別子
}
// AI判定結果を表すメッセージ(クライアント)
message ShapeRecognitionClient {
bool success = 1; // 処理成功フラグ
string drawing_id= 2; // 描画データID
string prefab_name = 3; // 生成するプレハブ名
string error_message = 4; // エラーメッセージ
}
// AI判定結果を表すメッセージ(サーバー)
message ShapeRecognitionServer {
string result_id = 1; // 結果ID
string drawing_id = 2; // 描画データID
string scene_id = 3; // シーンID
string shape_id = 4; // 形状ID
string prefab_name = 5; // プレハブ名
bool success = 6; // 処理成功フラグ
int32 score = 7; // 信頼度スコア
string reasoning = 8; // AI判定の理由
int32 process_time_ms = 9; // 処理時間(ミリ秒)
string model_name = 10; // 利用したモデル名
string api_response = 11; // APIのレスポンス
string error_message = 12; // エラーメッセージ
string client_id = 13; // クライアントID
}
// サービス定義
service DrawingService {
// ヘルスチェック用
rpc CheckHealth (HealthCheckRequest) returns (HealthCheckResponse);
// データのアップロード用
rpc UploadDrawing (DrawingData) returns (UploadResponse);
// 描画データを処理してAI判定結果を返す
rpc ProcessDrawing (DrawingData) returns (ShapeRecognitionClient);
}
UnityでのgRPCクライアント実装
Grpc.Net.Clientと Cysharp.Threading.Tasks(UniTask) を使用してgRPC通信を非同期処理として実装しました。
1. gRPCクライアントの初期化
using UnityEngine;
using System;
using System.Collections.Generic;
using System.Threading;
using Cysharp.Net.Http;
using Cysharp.Threading.Tasks;
using Grpc.Net.Client;
using VRAcademyAudition;
public class GrpcClient : MonoBehaviour
{
[SerializeField] private string serverAddress = "http://localhost:50051";
private GrpcChannel channel;
private DrawingService.DrawingServiceClient client;
private async void Start()
{
await InitializeClient();
}
private async UniTask InitializeClient()
{
channel = GrpcChannel.ForAddress(serverAddress);
client = new DrawingService.DrawingServiceClient(channel);
Debug.Log("gRPC Client Initialized");
}
}
2. サーバーへの描画データ送信
public async UniTask<string> SendDrawingDataAsync(LineRenderer[] lineRenderers, Vector3 center)
{
List<Line> worldLines = new List<Line>();
foreach (var lr in lineRenderers)
{
worldLines.Add(ConvertToProtoLine(lr));
}
var drawingData = new DrawingData
{
DrawingId = Guid.NewGuid().ToString(),
SceneId = "TestScene",
DrawTimestamp = DateTimeOffset.Now.ToUnixTimeMilliseconds(),
Center = new Vector3Proto { X = center.x, Y = center.y, Z = center.z },
UseAi = true,
ClientId = SystemInfo.deviceUniqueIdentifier,
DrawLines = { worldLines }
};
var uploadResponse = await client.UploadDrawingAsync(drawingData);
return uploadResponse.Success ? uploadResponse.UploadId : null;
}
3. gRPCのヘルスチェック
public async UniTask<bool> CheckServerHealth()
{
try
{
var request = new HealthCheckRequest();
var response = await client.CheckHealthAsync(request);
return response.Status == HealthCheckResponse.Types.ServingStatus.Serving;
}
catch (Exception ex)
{
Debug.LogError($"Health check failed: {ex.Message}");
return false;
}
}
4. サンプルデータ
MetaQuest3から送信するデータの例を示します。
{
"drawing_id": "1234567890abcdef",
"scene_id": "TestScene",
"draw_timestamp": 1715678901234,
"draw_lines": [
{
"positions": [
{ "x": 0.0, "y": 1.0, "z": 2.0 },
{ "x": 1.0, "y": 2.0, "z": 3.0 }
],
"width": 0.05,
"color": { "r": 1.0, "g": 0.0, "b": 0.0, "a": 1.0 }
}
],
"center": { "x": 0.5, "y": 1.5, "z": 2.5 },
"use_ai": true,
"client_id": "device-123456"
}
まとめ
- gRPCを活用してVR描画データをサーバーへ効率的に送信
- UniTaskを活用した非同期処理でスムーズな通信を実現
- ProtoBufを定義し、軽量かつ高速な通信を可能に
Discussion