arkleinberg commited on
Commit
76a6df1
·
verified ·
1 Parent(s): 858e665

Create Avatar.html

Browse files
Files changed (1) hide show
  1. Avatar.html +203 -0
Avatar.html ADDED
@@ -0,0 +1,203 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <!-- =============================================
5
+ Lip‑Sync Avatar
6
+ ---------------------------------------------
7
+ This page display a 3‑D
8
+ character in front of a looping background
9
+ texture and drive its mouth‑opening morph
10
+ target in real‑time, synchronised with speech
11
+ synthesis.
12
+ ‑ Technologies used:
13
+ • three.js (WebGL) – core 3‑D engine
14
+ • GLTFLoader – load the avatar
15
+ • Web Speech API – text‑to‑speech
16
+ • Standard JS / CSS
17
+
18
+ ✅ Place the following assets next to this file
19
+ ├─ avatar.glb ← your character exported from
20
+ │ the video, with a morph target
21
+ │ named "viseme_aa" (or rename in
22
+ │ code below).
23
+ └─ bg.jpg ← background image (optional
24
+ video texture shown later)
25
+ ============================================= -->
26
+
27
+ <meta charset="UTF-8" />
28
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
29
+ <title>Lip‑Sync Ark Avatar</title>
30
+
31
+ <!-- Page styling inspired by the glass‑look & gradients
32
+ of the uploaded examples → custom properties keep
33
+ things tidy. -->
34
+ <style>
35
+ :root {
36
+ --accent‑1: #6965db; /* primary violet */
37
+ --accent‑2: #3a86ff; /* secondary blue */
38
+ --bg‑dark : #0f0f1a;
39
+ --text‑lite: #e5e5f7;
40
+ }
41
+
42
+ * { box‑sizing: border‑box; margin: 0; padding: 0; }
43
+ html,body { height: 100%; overflow: hidden; font‑family: 'Segoe UI', Tahoma, sans‑serif; background: var(--bg‑dark); color: var(--text‑lite); }
44
+
45
+ /* Three.js full‑screen canvas */
46
+ #three‑canvas { position: fixed; inset: 0; z‑index: 1; }
47
+
48
+ /* UI overlay */
49
+ #ui { position: fixed; left: 0; right: 0; bottom: 2rem; display: flex; justify‑content: center; gap: 1rem; z‑index: 2; }
50
+ button {
51
+ padding: .8rem 1.6rem; border: none; border‑radius: 40px;
52
+ background: linear‑gradient(100deg,var(--accent‑1),var(--accent‑2));
53
+ color: #fff; font‑size: 1rem; font‑weight: 600; cursor: pointer;
54
+ box‑shadow: 0 4px 15px rgba(0,0,0,.25); transition: transform .2s;
55
+ }
56
+ button:hover { transform: translateY(-3px); }
57
+ </style>
58
+ </head>
59
+ <body>
60
+
61
+ <!-- Three.js draws here -->
62
+ <canvas id="three‑canvas"></canvas>
63
+
64
+ <!-- Simple UI -->
65
+ <div id="ui">
66
+ <button id="speakBtn">Say it 👉 “Hello I’m your personal assistant”</button>
67
+ </div>
68
+
69
+ <!-- Three.js & loader -->
70
+ <script src="https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.min.js"></script>
71
+ <script src="https://cdn.jsdelivr.net/npm/three@0.160.0/examples/js/controls/OrbitControls.js"></script>
72
+ <script src="https://cdn.jsdelivr.net/npm/three@0.160.0/examples/js/loaders/GLTFLoader.js"></script>
73
+
74
+ <script>
75
+ /* ==========================================================
76
+ 1. Basic scene set‑up
77
+ ========================================================== */
78
+ const canvas = document.getElementById('three‑canvas');
79
+ const renderer = new THREE.WebGLRenderer({ canvas, antialias:true, alpha:true });
80
+ renderer.setPixelRatio(Math.min(window.devicePixelRatio,2));
81
+ const scene = new THREE.Scene();
82
+
83
+ // Camera
84
+ const camera = new THREE.PerspectiveCamera(35, window.innerWidth/window.innerHeight, 0.1, 100);
85
+ camera.position.set(0, 1.55, 3.5);
86
+
87
+ // Controls (for debugging – remove on prod)
88
+ const controls = new THREE.OrbitControls(camera, canvas);
89
+ controls.enableDamping = true;
90
+
91
+ // Resize handling
92
+ function onResize(){
93
+ camera.aspect = window.innerWidth / window.innerHeight;
94
+ camera.updateProjectionMatrix();
95
+ renderer.setSize(window.innerWidth, window.innerHeight);
96
+ }
97
+ window.addEventListener('resize', onResize);
98
+ onResize();
99
+
100
+ /* ==========================================================
101
+ 2. Background – simple textured quad (static image)
102
+ Replace with THREE.VideoTexture for a live video bg.
103
+ ========================================================== */
104
+ const bgTex = new THREE.TextureLoader().load('bg.jpg', tex=>{ tex.encoding = THREE.sRGBEncoding; });
105
+ const bgMat = new THREE.MeshBasicMaterial({ map:bgTex });
106
+ const bgGeo = new THREE.PlaneGeometry(16, 9);
107
+ const bg = new THREE.Mesh(bgGeo, bgMat);
108
+ bg.position.z = -5; // push back
109
+ bg.scale.set(2,2,1); // cover
110
+ scene.add(bg);
111
+
112
+ /* ==========================================================
113
+ 3. Avatar loading – expects a morph target named
114
+ "viseme_aa" (common for open‑mouth). Adjust index/name
115
+ below if different.
116
+ ========================================================== */
117
+ let avatar, mouthIndex = null; // will store index of morph
118
+
119
+ const loader = new THREE.GLTFLoader();
120
+ loader.load('avatar.glb', gltf=>{
121
+ avatar = gltf.scene;
122
+ avatar.traverse(obj=>{
123
+ if (obj.isMesh && obj.morphTargetDictionary) {
124
+ // try to find a suitable mouth‑open morph
125
+ const dict = obj.morphTargetDictionary;
126
+ const possible = ['viseme_aa','mouthOpen','jawOpen','vrc.v_morph_aa'];
127
+ for(const key of possible){ if(key in dict){ mouthIndex = dict[key]; break; } }
128
+ if(mouthIndex!==null){ obj.userData.isMouth = true; }
129
+ }
130
+ });
131
+
132
+ // Center & scale heuristic – adjust as needed
133
+ const box = new THREE.Box3().setFromObject(avatar);
134
+ const size = new THREE.Vector3(); box.getSize(size);
135
+ avatar.scale.setScalar(1.6/size.y);
136
+ box.setFromObject(avatar);
137
+ const center = new THREE.Vector3(); box.getCenter(center);
138
+ avatar.position.sub(center); avatar.position.y -= box.min.y; // feet to ground
139
+
140
+ scene.add(avatar);
141
+ });
142
+
143
+ /* ==========================================================
144
+ 4. Lip‑sync logic (very lightweight)
145
+ – Uses SpeechSynthesisUtterance and its onboundary
146
+ event (fires at each word).
147
+ – At each word start we trigger a quick mouth‑open
148
+ impulse, which then eases back to closed inside the
149
+ render loop. For higher fidelity, integrate a full
150
+ phoneme‑to‑viseme mapper (Google TTS marks, deepspeech
151
+ etc.).
152
+ ========================================================== */
153
+ const mouthAnim = {
154
+ strength: 0 // 0 = closed, 1 = fully open
155
+ };
156
+
157
+ function speak(text){
158
+ if(!window.speechSynthesis) return alert('SpeechSynthesis unsupported');
159
+ const utter = new SpeechSynthesisUtterance(text);
160
+ utter.lang = 'en-US';
161
+ utter.rate = 1;
162
+ utter.pitch = 1;
163
+ utter.onboundary = ({ name }) => {
164
+ if(name === 'word') {
165
+ // quick open kick
166
+ mouthAnim.strength = 1;
167
+ }
168
+ };
169
+ window.speechSynthesis.speak(utter);
170
+ }
171
+
172
+ // UI button
173
+ document.getElementById('speakBtn').addEventListener('click', ()=>{
174
+ speak("Hello I'm your personal assistant");
175
+ });
176
+
177
+ /* ==========================================================
178
+ 5. Render loop – drive mouth closing + optional subtle
179
+ idle movement.
180
+ ========================================================== */
181
+ const clock = new THREE.Clock();
182
+ function tick(){
183
+ requestAnimationFrame(tick);
184
+ const dt = clock.getDelta();
185
+
186
+ // Ease mouth strength back to 0
187
+ mouthAnim.strength = THREE.MathUtils.damp(mouthAnim.strength, 0, 5, dt);
188
+
189
+ if(avatar && mouthIndex!==null){
190
+ avatar.traverse(obj=>{
191
+ if(obj.userData.isMouth){
192
+ obj.morphTargetInfluences[mouthIndex] = mouthAnim.strength;
193
+ }
194
+ });
195
+ }
196
+
197
+ controls.update();
198
+ renderer.render(scene, camera);
199
+ }
200
+ tick();
201
+ </script>
202
+ </body>
203
+ </html>