13 KiB (Stored with Git LFS)

기술 사양서 (Technical Specifications)

🏗️ 시스템 아키텍처

전체 시스템 구조

[모션캡쳐 스튜디오] → [Unity 서버] → [WebSocket] → [웹 클라이언트]
       ↓                    ↓                            ↓
   모션 데이터 수집      실시간 스트리밍           3D 렌더링 + 제어

데이터 흐름

  1. 모션캡쳐 → Unity: Mocap 장비에서 Unity로 실시간 데이터 입력
  2. Unity → Web: WebSocket을 통한 실시간 아바타 Transform 전송
  3. Web → Unity: 녹화 명령 및 제어 신호 전송
  4. Web: 로컬 모션 데이터 저장 및 파일 생성

📡 통신 프로토콜

WebSocket 메시지 포맷

1. 모션 데이터 (Unity → Web)

{
  "type": "motion_frame",
  "timestamp": 1640995200000,
  "frameRate": 30,
  "avatar": {
    "bones": [
      {
        "name": "Hips",
        "position": {"x": 0.0, "y": 1.0, "z": 0.0},
        "rotation": {"x": 0.0, "y": 0.0, "z": 0.0, "w": 1.0},
        "scale": {"x": 1.0, "y": 1.0, "z": 1.0}
      },
      {
        "name": "Spine",
        "position": {"x": 0.0, "y": 1.2, "z": 0.0},
        "rotation": {"x": 0.1, "y": 0.0, "z": 0.0, "w": 0.995}
      }
    ]
  }
}

2. 녹화 제어 명령 (Web → Unity)

{
  "type": "recording_command",
  "command": "start_recording",
  "parameters": {
    "filename": "motion_capture_001",
    "format": "fbx",
    "frameRate": 30,
    "duration": 300
  }
}

3. 상태 응답 (Unity → Web)

{
  "type": "recording_status",
  "status": "recording",
  "filename": "motion_capture_001.fbx",
  "duration": 45.2,
  "frameCount": 1356,
  "fileSizeKB": 2048
}

에러 처리

{
  "type": "error",
  "code": "RECORDING_FAILED",
  "message": "녹화 파일 생성에 실패했습니다.",
  "details": {
    "cause": "disk_space_insufficient",
    "timestamp": 1640995200000
  }
}

🎮 Unity 구현 사양

핵심 컴포넌트

1. WebSocketServer.cs

public class WebSocketServer : MonoBehaviour
{
    [SerializeField] private int port = 8080;
    [SerializeField] private float updateRate = 30f;
    
    private WebSocketSharp.Server.WebSocketServer server;
    private MotionDataStreamer streamer;
    
    public void StartServer() { /* 서버 시작 */ }
    public void StopServer() { /* 서버 종료 */ }
    public void BroadcastMotionData(MotionFrame frame) { /* 데이터 전송 */ }
}

2. MotionDataStreamer.cs

public class MotionDataStreamer : MonoBehaviour
{
    [SerializeField] private Transform avatarRoot;
    [SerializeField] private string[] boneNames;
    
    private Dictionary<string, Transform> boneTransforms;
    
    public MotionFrame CaptureCurrentFrame()
    {
        // 현재 아바타 상태를 MotionFrame으로 변환
    }
    
    public void SendMotionData()
    {
        // 실시간으로 모션 데이터 전송
    }
}

3. RemoteMotionRecorder.cs

public class RemoteMotionRecorder : MonoBehaviour
{
    private EasyMotionRecorder motionRecorder;
    private bool isRecording = false;
    
    public void StartRecording(string filename) { /* 녹화 시작 */ }
    public void StopRecording() { /* 녹화 종료 */ }
    public RecordingStatus GetStatus() { /* 상태 조회 */ }
}

데이터 구조

[System.Serializable]
public class MotionFrame
{
    public long timestamp;
    public float frameRate;
    public BoneData[] bones;
}

[System.Serializable]
public class BoneData
{
    public string name;
    public Vector3 position;
    public Quaternion rotation;
    public Vector3 scale;
}

🌐 웹 클라이언트 사양

핵심 모듈

1. WebSocketClient.js

class WebSocketClient {
    constructor(url) {
        this.url = url;
        this.socket = null;
        this.onMotionData = null;
        this.onStatusUpdate = null;
    }
    
    connect() {
        this.socket = new WebSocket(this.url);
        this.setupEventHandlers();
    }
    
    sendCommand(command) {
        this.socket.send(JSON.stringify(command));
    }
    
    setupEventHandlers() {
        this.socket.onmessage = (event) => {
            const data = JSON.parse(event.data);
            this.handleMessage(data);
        };
    }
}

2. MotionViewer.js

class MotionViewer {
    constructor(containerId) {
        this.scene = new THREE.Scene();
        this.camera = new THREE.PerspectiveCamera();
        this.renderer = new THREE.WebGLRenderer();
        this.avatar = null;
        this.bones = new Map();
    }
    
    loadAvatar(modelPath) {
        // 3D 아바타 모델 로드
    }
    
    updateMotion(motionFrame) {
        // 실시간 모션 데이터 적용
        motionFrame.bones.forEach(bone => {
            const boneObject = this.bones.get(bone.name);
            if (boneObject) {
                boneObject.position.set(bone.position.x, bone.position.y, bone.position.z);
                boneObject.quaternion.set(bone.rotation.x, bone.rotation.y, bone.rotation.z, bone.rotation.w);
            }
        });
    }
    
    render() {
        this.renderer.render(this.scene, this.camera);
        requestAnimationFrame(() => this.render());
    }
}

3. MotionRecorder.js

class MotionRecorder {
    constructor() {
        this.isRecording = false;
        this.motionData = [];
        this.startTime = null;
    }
    
    startRecording() {
        this.isRecording = true;
        this.motionData = [];
        this.startTime = Date.now();
    }
    
    addFrame(motionFrame) {
        if (this.isRecording) {
            this.motionData.push({
                ...motionFrame,
                relativeTime: Date.now() - this.startTime
            });
        }
    }
    
    stopRecording() {
        this.isRecording = false;
        return this.motionData;
    }
}

4. MotionExporter.js

class MotionExporter {
    exportToBVH(motionData) {
        // BVH 포맷으로 변환
        const bvhContent = this.generateBVHContent(motionData);
        return this.createDownloadableFile(bvhContent, 'motion.bvh');
    }
    
    exportToFBX(motionData) {
        // FBX 포맷으로 변환 (Three.js FBXExporter 활용)
        const fbxContent = this.generateFBXContent(motionData);
        return this.createDownloadableFile(fbxContent, 'motion.fbx');
    }
    
    exportToJSON(motionData) {
        // JSON 포맷으로 내보내기
        const jsonContent = JSON.stringify(motionData, null, 2);
        return this.createDownloadableFile(jsonContent, 'motion.json');
    }
    
    createDownloadableFile(content, filename) {
        const blob = new Blob([content], { type: 'application/octet-stream' });
        const url = URL.createObjectURL(blob);
        
        const link = document.createElement('a');
        link.href = url;
        link.download = filename;
        link.click();
        
        URL.revokeObjectURL(url);
    }
}

🎨 사용자 인터페이스 사양

HTML 구조

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>원격 모션캡쳐 시스템</title>
    <link rel="stylesheet" href="css/style.css">
</head>
<body>
    <div id="app">
        <!-- 연결 상태 -->
        <div id="connection-status">
            <span id="status-indicator"></span>
            <span id="status-text">연결 대기 중...</span>
        </div>
        
        <!-- 3D 뷰어 -->
        <div id="viewer-container">
            <canvas id="three-canvas"></canvas>
        </div>
        
        <!-- 제어 패널 -->
        <div id="control-panel">
            <div id="recording-controls">
                <button id="record-btn">⏺️ 녹화 시작</button>
                <button id="stop-btn" disabled>⏹️ 정지</button>
                <button id="play-btn" disabled>▶️ 재생</button>
            </div>
            
            <div id="recording-info">
                <span id="timer">00:00:00</span>
                <span id="frame-count">프레임: 0</span>
                <span id="file-size">크기: 0KB</span>
            </div>
            
            <div id="export-controls">
                <button id="export-bvh">BVH 다운로드</button>
                <button id="export-fbx">FBX 다운로드</button>
                <button id="export-json">JSON 다운로드</button>
            </div>
        </div>
        
        <!-- 설정 패널 -->
        <div id="settings-panel">
            <h3>녹화 설정</h3>
            <label>파일명: <input type="text" id="filename" value="motion_001"></label>
            <label>프레임레이트: 
                <select id="framerate">
                    <option value="24">24 FPS</option>
                    <option value="30" selected>30 FPS</option>
                    <option value="60">60 FPS</option>
                </select>
            </label>
        </div>
    </div>
    
    <script src="js/websocketClient.js"></script>
    <script src="js/motionViewer.js"></script>
    <script src="js/motionRecorder.js"></script>
    <script src="js/motionExporter.js"></script>
    <script src="js/main.js"></script>
</body>
</html>

CSS 스타일 가이드

/* 주요 색상 팔레트 */
:root {
    --primary-color: #2c3e50;
    --secondary-color: #3498db;
    --success-color: #27ae60;
    --warning-color: #f39c12;
    --danger-color: #e74c3c;
    --background-color: #ecf0f1;
}

/* 레이아웃 */
#app {
    display: grid;
    grid-template-areas: 
        "status status"
        "viewer controls"
        "viewer settings";
    grid-template-columns: 2fr 1fr;
    height: 100vh;
    gap: 10px;
    padding: 10px;
}

#viewer-container {
    grid-area: viewer;
    background: #2c3e50;
    border-radius: 8px;
    position: relative;
}

/* 반응형 디자인 */
@media (max-width: 768px) {
    #app {
        grid-template-areas: 
            "status"
            "viewer"
            "controls"
            "settings";
        grid-template-columns: 1fr;
    }
}

성능 최적화

네트워크 최적화

  • 데이터 압축: gzip 압축 적용
  • 전송 주기: 30fps 기본, 네트워크 상태에 따라 조절
  • 델타 압축: 이전 프레임과의 차이만 전송
  • 버퍼링: 클라이언트 사이드 적응형 버퍼

렌더링 최적화

  • LOD 시스템: 거리에 따른 모델 디테일 조절
  • 프러스텀 컬링: 카메라 시야 밖 객체 제외
  • 인스턴싱: 반복 객체 최적화
  • 텍스처 압축: 적절한 해상도 및 포맷 사용

메모리 최적화

  • 오브젝트 풀링: 반복 생성/삭제 객체 재사용
  • 가비지 컬렉션: 불필요한 객체 생성 최소화
  • IndexedDB: 대용량 모션 데이터 효율적 저장

🔒 보안 고려사항

데이터 보안

  • HTTPS/WSS: 암호화된 통신 프로토콜 사용
  • 토큰 인증: JWT 기반 사용자 인증
  • 데이터 무결성: 체크섬을 통한 데이터 검증

네트워크 보안

  • CORS 설정: 허용된 도메인에서만 접근
  • 레이트 리미팅: 과도한 요청 방지
  • 방화벽 설정: 필요한 포트만 개방

📊 모니터링 및 로깅

성능 메트릭

  • 지연시간: WebSocket 왕복 시간 측정
  • 프레임 드롭: 누락된 프레임 수 추적
  • 대역폭 사용량: 실시간 네트워크 사용량
  • CPU/메모리 사용률: 시스템 리소스 모니터링

로그 시스템

class Logger {
    static logMotionData(frameData) {
        console.log(`[MOTION] Frame ${frameData.frameNumber} received`);
    }
    
    static logError(error, context) {
        console.error(`[ERROR] ${context}:`, error);
    }
    
    static logPerformance(metric, value) {
        console.log(`[PERF] ${metric}: ${value}ms`);
    }
}

🧪 테스트 전략

Unity 단위 테스트

  • WebSocket 서버 연결/해제 테스트
  • 모션 데이터 직렬화/역직렬화 테스트
  • 녹화 기능 통합 테스트

웹 클라이언트 테스트

  • WebSocket 통신 테스트
  • 3D 렌더링 성능 테스트
  • 파일 내보내기 기능 테스트

통합 테스트

  • 전체 시스템 End-to-End 테스트
  • 장시간 연결 안정성 테스트
  • 다양한 네트워크 환경 테스트

이 기술 사양서는 개발 과정에서 지속적으로 업데이트됩니다.