Three.jsで3Dアバターの表示

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>Three.js glTF アバターデモ (最終サイズ調整版)</title>
    <style>
        body { margin: 0; overflow: hidden; background-color: #f0f0f0; }
        canvas { display: block; }
    </style>
    
    <script src="https://unpkg.com/three@0.137.0/build/three.min.js"></script>
    <script src="https://unpkg.com/three@0.137.0/examples/js/loaders/GLTFLoader.js"></script>
    <script src="https://unpkg.com/three@0.137.0/examples/js/controls/OrbitControls.js"></script>
</head>
<body>
    <script>
        // ==========================================================
        // 1. シーン、カメラ、レンダラーのセットアップ
        // ==========================================================
        const scene = new THREE.Scene();
        // カメラのクリッピング範囲を広げる (near=0.001, far=10000)
        const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.001, 10000); 
        const renderer = new THREE.WebGLRenderer({ antialias: true });
        
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.setPixelRatio(window.devicePixelRatio);
        renderer.outputEncoding = THREE.sRGBEncoding; 
        renderer.toneMapping = THREE.ACESFilmicToneMapping;
        renderer.setClearColor(0xcccccc, 1); 
        document.body.appendChild(renderer.domElement);

        // ==========================================================
        // 2. ライトのセットアップ
        // ==========================================================
        const ambientLight = new THREE.AmbientLight(0xffffff, 2.0); 
        scene.add(ambientLight);

        const directionalLight = new THREE.DirectionalLight(0xffffff, 1.0);
        directionalLight.position.set(5, 10, 5);
        scene.add(directionalLight);

        const pointLight = new THREE.PointLight(0xffffff, 3.0);
        pointLight.position.set(0, 3, 0); 
        scene.add(pointLight);

        // ==========================================================
        // 3. GLTFLoaderとアニメーションミキサーのセットアップ
        // ==========================================================
        const loader = new THREE.GLTFLoader(); 
        let mixer; 
        const clock = new THREE.Clock(); 

        // ==========================================================
        // 4. モデルの読み込み
        // ==========================================================
        const MODEL_PATH = './ellen_joe_oncampus/scene.gltf';

        loader.load(
            MODEL_PATH,
            function (gltf) {
                const model = gltf.scene;
                scene.add(model);

                // 【最終修正点】モデルを 50倍に拡大し、適切なサイズで表示
                model.scale.set(50, 50, 50); 
                
                // モデルの位置を強制的に原点 (0, 0, 0) に設定
                model.position.set(0, 0, 0); 
                
                // アニメーションデータの処理
                if (gltf.animations && gltf.animations.length > 0) {
                    mixer = new THREE.AnimationMixer(model);
                    const action = mixer.clipAction(gltf.animations[0]);
                    action.play();
                } else {
                    console.log('モデルにアニメーションデータが見つかりませんでした。静止画として表示します。');
                }
            },
            function (xhr) {
                console.log('モデル読み込み中: ' + (xhr.loaded / xhr.total * 100).toFixed(2) + '%');
            },
            function (error) {
                console.error('モデルの読み込み中にエラーが発生しました。', error);
            }
        );

        // ==========================================================
        // 5. カメラとコントロールの設定
        // ==========================================================
        // 【最終修正点】50倍スケールに合わせてカメラを遠ざけ、全体が見えるように調整
        camera.position.set(0, 5, 10); 

        const controls = new THREE.OrbitControls(camera, renderer.domElement); 
        // 【最終修正点】注視点をモデルの中心 (Y=5m) に設定
        controls.target.set(0, 5, 0); 
        controls.update();

        // ==========================================================
        // 6. アニメーションループ (毎フレームの更新処理)
        // ==========================================================
        function animate() {
            requestAnimationFrame(animate);

            const delta = clock.getDelta();

            if (mixer) {
                mixer.update(delta);
            }

            controls.update();
            renderer.render(scene, camera);
        }

        animate();

        // 画面サイズ変更時の対応
        window.addEventListener('resize', () => {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        });

    </script>
</body>
</html>

VAE Autoencoder

VAE(Variational Autoencoder)や Autoencoder も、Text-to-Image / Text-to-Video における“補助的な技術”として重要な役割を担っています。
ただし、**役割は GAN とは異なり、画像を「圧縮・展開するための土台」**として使われることが多いです。

⭐ VAE / Autoencoder は「画像を扱いやすくするための変換装置」
最新の Text-to-Image(Stable Diffusion など)では、

**画像をいきなりピクセルで扱わず、
一度「潜在空間(latent space)」に圧縮してから処理します。**
ここで使われるのが VAE や Autoencoder。

⭐ 1. Stable Diffusion を例にすると、VAE は「圧縮と復元」を担当
Stable Diffusion の大まかな流れ:

1️⃣ VAE Encoder:
画像 → 低次元の潜在表現(latent)

2️⃣ Diffusion (U-Net):
潜在空間でノイズ除去 / 生成処理
(ここが Text-to-Image のメイン)

3️⃣ VAE Decoder:
潜在 → 最終画像(512×512 など高解像度)
つまり VAE は、
Diffusion が扱う“潜在空間”を作るための重要モジュール。

⭐ 2. なぜ Autoencoder が必要なのか?

理由は3つ。

✔ 理由①:計算量を激減させる(高速化)
画像を直接生成すると 512×512×3 = 786,432 ピクセル
これは非常に重い。
潜在空間は 1/8〜1/16のサイズなので
Diffusion の計算が一気に軽くなる。

✔ 理由②:高解像度の構造を少ない次元で表現できる
Autoencoder は


質感

などの情報を「圧縮しても失われにくい形」に変換できる。
GAN や Diffusion だけではこの圧縮が難しいので Autoencoder が必要。

✔ 理由③:潜在空間はノイズ処理と相性が良い
Diffusion の“ノイズ除去プロセス”は、
潜在空間の方がやりやすい。

⭐ 3. Text-to-Video でも Autoencoder が使われる
動画の場合は、
“空間だけでなく時間方向にも圧縮”が必要。

そこで登場するのが:
Video Autoencoder
Temporal VAE
3D VAE(空間+時間)

これらは
動画 → 潜在動画
に変換してから Diffusion で生成します。

Sora など最新モデルでは
専用の Video Autoencoder が重要な基盤技術として使われています。

⭐ 4. まとめ:VAE / Autoencoder は「補助」だけど“めちゃ重要な基盤”
技術 主な役割
Diffusion 画像・動画そのものを生成する“エンジン”
GAN 仕上げの高解像化・質感改善・時間的一貫性補正
Autoencoder / VAE 画像や動画を扱いやすい潜在空間に変換

要するに、

🔹 Diffusion(生成の本体)

🔹 Autoencoder(圧縮/展開の基盤)

🔹 GAN(質感や解像度を補強)

という構成が最新モデルの一般形です。

Three.jsで BlendShape を動かすコード

Three.js では BlendShape = MorphTarget(モーフターゲット) と呼ばれ、
mesh.morphTargetInfluences を操作することで口形状・表情を動かせる

import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';

let scene, camera, renderer;
let model;

function init() {
  // シーン・カメラ・レンダラー
  scene = new THREE.Scene();
  camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
  camera.position.set(0, 1.5, 3);

  renderer = new THREE.WebGLRenderer({ antialias: true });
  renderer.setSize(window.innerWidth, window.innerHeight);
  document.body.appendChild(renderer.domElement);

  // ライト
  const light = new THREE.DirectionalLight(0xffffff, 1);
  light.position.set(1, 1, 1);
  scene.add(light);

  // GLTF 読み込み
  const loader = new GLTFLoader();
  loader.load('model.glb', (gltf) => {
    model = gltf.scene;
    scene.add(model);

    // 読み込み後にブレンドシェイプを確認
    model.traverse((obj) => {
      if (obj.isMesh && obj.morphTargetInfluences) {
        console.log("MorphTargets:", obj.morphTargetDictionary); 
      }
    });
  });

  animate();
}

function animate() {
  requestAnimationFrame(animate);

  // BlendShape(MorphTarget)を動かす例
  if (model) {
    model.traverse((obj) => {
      if (obj.isMesh && obj.morphTargetInfluences) {

        // 例:口「あ」を 0〜1 でアニメーション
        const t = (Math.sin(Date.now() * 0.002) + 1) / 2;

        // "A" という名前のモーフがある場合を想定
        const index = obj.morphTargetDictionary["A"];
        if (index !== undefined) {
          obj.morphTargetInfluences[index] = t;  // 0〜1で動かす
        }
      }
    });
  }

  renderer.render(scene, camera);
}

init();

Viseme(ビズィーム)→口形状の対応表

**Viseme(ビズィーム)**とは、
発音される音(phoneme:フォニーム)に対応する “口の形” のセットのことです。
つまり、音素(あ・い・う・え・お、ba、fa など)をアニメで表現するための口形状の対応表です。

🔷 Viseme とは何か?
アニメーションや3Dキャラで**リップシンク(口パク)**を作るとき、
音声のすべての音素ごとに口形状を作るのは大変です。

そこで、多くの音をまとめて
👉 「この音はだいたいこういう口の形」
という分類をしたものが Viseme(視覚的音素) です。

🔷 具体例:Viseme の対応表(例)
英語圏で一般的な Viseme 例:

音(Phoneme) Viseme(口形状)例 説明
A, AA, AH A(あ型) 口を縦に開く
I, EE I(い型) 口を横に引く
U, OO U(う型) 唇を前に突き出す
F, V F 下唇を噛む
B, P, M Closed(閉じる) 唇を閉じる
S, Z, T, D S(す型) 歯を軽く見せる
O O(お型) 丸く開ける

VTuber や VRChat、Unity、Blender のリップシンクでよく使われます。

🔷 なぜ Viseme が必要なのか?
音声には数十〜100以上の音素がありますが、
見た目の口の形は10種類前後にまとめられるためです。

これにより:
✔ BlendShape の数が減る(制作が楽)
✔ 音声 → Viseme の変換が簡単
✔ リアルタイム処理が軽く、VTuber に向いている
✔ アニメーションが自然に見える

🔷 日本語の場合は?
日本語のリップシンクでは **5母音(あ・い・う・え・お)+閉じ口(ん・ま行)**が基本です。
例:

日本語音素 Viseme(口形状)
あ行 A
い行 I
う行 U
え行 E
お行 O
ま・ぱ・ば 口を閉じる(Close)
🔷 まとめ

Viseme = フォニーム(音素)を視覚的な“口形状”にまとめたもの
リップシンクで

「どの音に対してどの口形状を使うか」
を定義したのが Viseme 対応表 です。

textToImageとdeeplearning

text-to-image(テキストから画像生成)では、Deep Learning が 文章の理解 → 画像の生成 までのすべての工程に使われています。代表的な仕組みを分かりやすくまとめます。

🌟 text-to-image における Deep Learning の使われ方
1. テキストを理解する(テキストエンコーダ)

入力された文章を数値ベクトルに変換するために Transformer(例:BERT、CLIP の Text Encoder) が使われます。

文章の「意味」や「スタイル」「関係性」を理解し、生成モデルに渡せる形にします。

2. 画像を生成する(拡散モデル or GAN など)

現在主流は 拡散モデル(Diffusion Models) です。

Stable Diffusion

DALL·E

Midjourney(内部構造は非公開だが拡散系と言われる)

📌 拡散モデルの流れ

ランダムノイズからスタート

「ノイズを少しずつ取り除いて画像に近づける」トレーニングを大量データで学習

テキストの意味を反映しながらノイズを除去して最終画像を生成

この「ノイズ除去」のステップを学習するのに Deep Learning(U-Net + Attention)が使われます。

3. テキストと画像を対応づける(クロスアテンション)

画像を作る時に
「文章のどの単語が、画像のどの部分に対応するか」
を学習する仕組みが必要です。

これに使われているのが Cross-Attention(クロスアテンション)

例:「a red cat sitting on a table」

“red”→猫の色

“cat”→動物の形

“table”→背景
といった対応を画像生成中に参照します。

4. 大規模データでの事前学習(Foundation Models)

text-to-image モデルは、
画像+キャプション(説明文) の巨大データセットで事前に学習されます。

これにより、

物体

スタイル

質感

写真の構図
などを深層学習が「理解」できるようになります。

🧠 全体構造まとめ
[テキスト] → Transformer(テキスト理解)
→ ベクトル
→ Diffusion Model(画像生成:U-Net + Cross-Attention)
→ [画像]

🔍 一言でまとめると

text-to-image は、
「テキスト理解 × ノイズから画像を作る技術 × アテンション」
を組み合わせた Deep Learning の応用です。

[3D] BlendShape

BlendShape(ブレンドシェイプ)とは、3Dキャラクターの表情や口の動きなどの“変形”を作るための仕組みです。
アニメーション制作やゲーム、VTuberの顔モデルなどで非常によく使われます。

🔷 BlendShape の基本的な考え方
1つの3Dモデルに対して、
笑顔
怒り顔
口を「あ」形に開ける
目を閉じる
など、**形が変化した複数のモデル(ターゲット形状)**を用意します。

そして、アニメーション中に
「元の形」+「ターゲット形状の混合量(0〜1)」
でモデルの形が変わる仕組みです。
例:
“Smile” ブレンドシェイプを 0 → 1 にすると、だんだん笑顔になる
“Mouth Open” を 0.5 にすると、口が半分開く

🔷 なぜBlensShapeが使われるのか?
自然な表情の作りやすさ
骨(ボーン)だけでは難しい細かな変形が可能
リアルタイムで軽い処理(ゲーム・VTuber向き)
複数の表情を混ぜられる(笑いながら目を閉じるなども簡単)

🔷 具体例:口のBlendShape
例えば「口」に関係するBlendShapeには:
A(あ)
I(い)
U(う)
E(え)
O(お)

口角上げ
口すぼめ
ニコッと笑う

などがあり、MMD・VTuber・ゲーム顔アニメで使われています。

🔷 まとめ
BlendShape=“複数の形を混ぜて表情や口の形を変える仕組み”
ボーンのアニメと違い、メッシュ自体が変形するので、細かい顔表現に最適です。

[Android] androidの簡単なテスト(Unit, UIテスト)

Unit Test(Kotlin ロジックのテスト)サンプル

■ 例:足し算関数のテスト

(src/main/java/…/Calculator.kt)

class Calculator {
    fun add(a: Int, b: Int): Int = a + b
}
import org.junit.Assert.assertEquals
import org.junit.Test

class CalculatorTest {

    @Test
    fun `add should return correct sum`() {
        val calculator = Calculator()
        val result = calculator.add(2, 3)

        assertEquals(5, result)
    }
}
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable

@Composable
fun Greeting(name: String) {
    Text(text = "Hello, $name!")
}
import androidx.compose.ui.test.*
import androidx.compose.ui.test.junit4.createComposeRule
import org.junit.Rule
import org.junit.Test

class GreetingTest {

    @get:Rule
    val composeTestRule = createComposeRule()

    @Test
    fun greeting_displaysCorrectText() {
        composeTestRule.setContent {
            Greeting("Android")
        }

        composeTestRule
            .onNodeWithText("Hello, Android!")
            .assertIsDisplayed()
    }
}

@Composable
fun CounterScreen() {
    var count = remember { mutableStateOf(0) }

    Column {
        Text("Count: ${count.value}")
        Button(onClick = { count.value++ }) {
            Text("Add")
        }
    }
}
@Test
fun counter_incrementsWhenButtonClicked() {
    composeTestRule.setContent {
        CounterScreen()
    }

    // 初期表示チェック
    composeTestRule
        .onNodeWithText("Count: 0")
        .assertIsDisplayed()

    // ボタンクリック
    composeTestRule
        .onNodeWithText("Add")
        .performClick()

    // 更新後表示チェック
    composeTestRule
        .onNodeWithText("Count: 1")
        .assertIsDisplayed()
}

3Dアバターの構成要素

3Dアバター制作の基本要素である
メッシュ・テクスチャ・リグ・BlendShape を、
初心者向けだけど専門的に正しくわかりやすくまとめます。

🎨 ① メッシュ(Mesh)= 形・立体の表面のこと
メッシュ=3Dの形そのものです。
点(Vertex)
線(Edge)
面(Face)
これがたくさん集まって、
人間の顔・体・服の立体モデルが作られます。

例:顔の輪郭、鼻、目、唇、髪のボリュームなど。

👀 ポイント
VRMでもBlenderでも「見えている形」は全部メッシュ。

🎨 ② テクスチャ(Texture)= 色・模様の画像のこと
メッシュは“形”だけなので、まだ色がありません。
そこで使うのがテクスチャ。
モデルに貼り付けられた「画像(2D)」のことです。

肌の色
目の模様
服の柄
影の塗り(アニメ調)
シワや質感
すべて テクスチャ画像(PNG/JPEG) で表現されます。

📌 よくある例
VRMの「テクスチャ」というフォルダ内に
face.png
body.png
hair.png
などが入っています。

🦴 ③ リグ(Rig)= 操作するための“骨”の仕組み
メッシュを動かすために内部に入れるのが 骨(ボーン / Armature)。
首を曲げる
口を動かす
手を振る
体を回す

こういった動きは リグ(骨)をメッシュに紐づけることで実現します。

📌 VRM では「Humanoid(人型)の規格」が決まっているため、
UnityやThree.jsが自動で動かしやすいようになっています。

😀 ④ BlendShape(ブレンドシェイプ)= 形の変形(表情・口の動き)
メッシュを「別の形に変形させる仕組み」です。
例えば:
BlendShape名 変形の意味
A 口を「あ」の形に開く
I 「い」の形
O 「お」の形
Smile 口角を上げる
Blink まばたき
Angry 怒り表情

複数のBlendShapeを“混ぜる(Blend)”ことで表情が作れる → これが名前の由来。

📌 リップシンクでは
Viseme(発音記号) → BlendShape(口の形)
へ変換して口の動きを作ります。

⭐ 4つの違いを1行でまとめる
名称 一言で言うと
メッシュ 立体の形
テクスチャ その形に貼る色・模様(画像)
リグ(骨) 動かすための骨組み
BlendShape 表情や口の形の変形

全部セットで、
「しゃべって動く3Dアバター」 が成立します。

[Android/iOS] ストア申請の流れ

以下に Android(Google Play)と iOS(App Store) のストア申請の流れをわかりやすくまとめ ました。
「開発者登録 → 審査 → 公開」まで、現場で実際に行う手順ベースです。

📱 Android(Google Play)ストア申請の流れ
✅ 1. Google Play デベロッパー登録
Google アカウントで登録
登録料:25ドル(買い切り)

✅ 2. Google Play Console にアプリを作成
アプリ名入力
パッケージ名(com.example.app)が必要
アプリ種別(アプリ / ゲーム)
無料 / 有料の選択

✅ 3. アプリの情報入力
以下を必ず入力する必要があります:
ストア掲載情報
アプリ名
短い説明
長い説明
アイコン
スクリーンショット
プロモーション画像
Appカテゴリ(ジャンル・コンテンツレーティング)
プライバシーポリシーURL
アプリのアクセス権に関する申請(位置情報・カメラ等)

✅ 4. App Bundle(AAB)をアップロード
Android Studio → Build > Generate Signed App Bundle
keystore で署名
AAB を Play Console にアップロード

✅ 5. 審査リクエスト(リリース)
プロダクションリリースへ申請
審査期間:数時間~3日程度
新人アカウントほど長くなる傾向あり

✅ 6. 公開
公開後、数時間でストアに反映される
ホットフィックスは数時間で更新可能

🍎 iOS(App Store)ストア申請の流れ
✅ 1. Apple Developer Program 登録
料金:年間 99ドル
個人 / 企業で登録形態が異なる
iPhone実機テストには Developer Program が必要

✅ 2. App Store Connect でアプリ作成
アプリ名
バンドルID(Xcode側設定と一致)
SKU の設定
カテゴリ選択

✅ 3. アプリの情報入力(詳細)
App Store用の説明情報
タイトル
サブタイトル
プロモーションテキスト
スクリーンショット(6.7インチ / 5.5インチ 必須)
アイコン(1024×1024)
年齢レーティング
プライバシーポリシーURL
データ収集とトラッキングの申告(AppTrackingTransparency)

✅ 4. Xcode でビルドしてアップロード
Xcode → Archive → Distribute App
Transporter(Apple純正)経由でもOK
Upload 後、App Store Connect の「TestFlight」に表示される

✅ 5. App Review に提出
審査に必要な入力:
デモアカウント(ログイン必要アプリの場合)
審査用メモ
有料アイテムの申請(In-App Purchaseがある場合)
審査期間:1~3日程度(早ければ半日)

✅ 6. リリース(公開)
審査OK → 公開日を即時または手動で選択
公開後、数時間で App Store へ反映

🔍 Android と iOS の比較(重要ポイント)
項目 Android iOS
開発者登録 25ドル(買い切り) 99ドル/年
審査 数時間〜3日 半日〜3日
ビルド形式 AAB IPA(Xcodeからアップロード)
審査の厳しさ やや緩い かなり厳格
更新反映 早い やや遅い
🎯 まとめ(全