0% found this document useful (0 votes)
3 views12 pages

VRM Code Explanation

Uploaded by

pradeepnaveen930
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
3 views12 pages

VRM Code Explanation

Uploaded by

pradeepnaveen930
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd

VRM AVATAR SYSTEM

Line-by-Line Code Explanation


How a 3D Talking Avatar Works in the Browser

[Link] • @pixiv/three-vrm • WebGL


1. What This File Does
This JavaScript module loads a VRM (Virtual Ready-Made) 3D avatar, renders it inside a browser
<canvas> using WebGL via [Link], and animates it with three states: idle breathing/blinking, bot
speaking with lip sync, and user listening pose. Three global window functions let [Link] control the
avatar.

Component What It Does

Scene The 3D world — an empty container every object lives in

Camera The viewer's eye — defines angle, zoom and clipping range

Renderer GPU-powered painter that draws every frame onto the canvas

VRM Model The 3D avatar with a full skeleton and facial expressions

Animation Loop Runs ~60× per second, updates poses, expressions, redraws

Analogy: A film studio. Scene = the studio room. Camera = film camera on a tripod. Renderer =
projector. VRM = the actor. Animation loop = the film strip running at 60 fps.

2. Imports
import * as THREE from 'three';
Imports the entire [Link] library as the namespace THREE. [Link] wraps raw WebGL (the browser
GPU API) into easy-to-use objects like Scene, Camera, Mesh, Light.
Analogy: WebGL is raw lumber and nails. [Link] is the power tools that let you build without knowing
every nail.

import { GLTFLoader } from 'three/addons/loaders/[Link]';


Imports a file-loader that understands GLTF/GLB/VRM 3D model files — the most popular web 3D
format, used by Blender, Unity, Sketchfab exports, and more.
Analogy: Like importing a PDF reader, but for 3D model files.

import { VRMLoaderPlugin, VRMUtils } from '@pixiv/three-vrm';


VRMLoaderPlugin is a GLTF parser extension that extracts VRM-specific data (humanoid bones, facial
expressions, spring bones). VRMUtils provides helper functions for fixing common VRM quirks.
Analogy: VRM is a dialect of GLTF. VRMLoaderPlugin is the dialect translator. VRMUtils is a spell-
checker for the translated result.

Other 3D Format Options


Format Notes
GLTF / GLB Best for web — used here. Blender, Unity, Sketchfab all export
it

OBJ + MTL Old, no animation support

FBX Common in games — needs FBXLoader from [Link] extras

COLLADA (.dae) XML-based, mostly legacy now

VRM Anime humanoid avatars by pixiv — extends GLTF

Resources
• [Link] docs: [Link]
• three-vrm GitHub: [Link]
• VRM specification: [Link]

3. Canvas & Scene Setup


const canvas = [Link]('vrm-canvas');
Grabs the HTML <canvas> element — the actual pixel grid where [Link] paints. Without targeting a
canvas, the renderer has nowhere to draw.

const shadowEl = [Link]('model-shadow');


Gets a CSS shadow div placed under the avatar. This is a simple design element (a blurred circle), not
a real 3D shadow — shown and hidden alongside the avatar.

const scene = new [Link]();


Creates the 3D world — an empty container. Every object (lights, model, helpers) must be added to the
scene before it can be rendered.
Analogy: Building an empty theatre stage before putting actors or lights on it.

Camera
const camera = new [Link](44, [Link] /
[Link], 0.1, 20);
Creates a perspective camera — the type that mimics the human eye (far objects look smaller than
near ones). The four arguments are:
Argument Meaning

44 Field of view in degrees — like a 44mm camera lens

innerWidth / innerHeight Aspect ratio — stops image being squished or stretched

0.1 Near clipping plane — objects closer than 0.1 m are invisible

20 Far clipping plane — objects beyond 20 m are invisible


Analogy: A real camera. FOV = zoom ring. Aspect = portrait vs landscape. Near/far clipping = minimum
and maximum focus distance. Anything outside that range is cut out.

[Link](0, 1.1, 3.8);


Places the camera at X=0 (centred), Y=1.1 m (chest height), Z=3.8 m (in front of the avatar). In
[Link], the Z axis points toward the viewer.

[Link](0, 0.8, 0);


Points the camera lens at coordinate (0, 0.8, 0) — roughly the avatar's upper chest / lower face. This is
where the camera focuses.
Analogy: [Link] = where you put the tripod. lookAt = which direction you aim the lens.

4. Renderer
const renderer = new [Link]({ canvas, alpha: true, antialias: true });
Creates the WebGL renderer — the GPU-powered engine that composites the entire 3D scene into 2D
pixels on the canvas every frame.
Option Effect

canvas Which HTML canvas to paint on

alpha: true Transparent background so CSS / HTML shows behind the


avatar

antialias: true Smooths jagged diagonal edges (costs a small GPU budget)

[Link]([Link]([Link], 2));
Uses the screen's native pixel density for sharp rendering but caps at 2×. Retina displays are 3× but
rendering at 3× triples GPU workload — 2× is the sweet spot.
Analogy: Print resolution. 2× is sharp enough for any screen. 3× is overkill for display use and wastes
GPU.

[Link] = [Link];
Sets colour output to sRGB — the standard colour space monitors expect. Without this, colours appear
washed out and incorrect because the GPU outputs linear light values.

[Link]('resize', () => { ... });


On window resize: updates the camera's aspect ratio and the renderer's pixel dimensions so the 3D
content fills the new size correctly without distortion.
Analogy: A smart TV that automatically readjusts picture ratio when you rotate it.
5. Lighting
[Link] has no default lighting — a scene with no lights is completely black. You manually compose a
lighting rig. This file uses a classic 2-light setup.

[Link](new [Link](0xffffff, 1.6));


Ambient light fills the whole scene equally from every direction with no shadows. 0xffffff = white. 1.6 =
intensity (fairly bright).
Analogy: Overhead office fluorescent lighting — illuminates everything uniformly, creates no shadows.

const key = new [Link](0xffffff, 1.2); [Link](0.5, 2, 2);


The main key light, shining from upper-front-right. Creates highlights that make the face look 3D and
vivid. 'Key' is from professional 3-point lighting terminology.
Analogy: The main spotlight aimed at an actor on a theatre stage. It creates shadows and makes the
face pop.

const fill = new [Link](0xffffff, 0.4); [Link](-1, 0.5,


1);
A weaker fill light from the left softens the harsh shadows the key light creates. Intensity 0.4 = one-third
of the key light.
Analogy: The reflector board a photographer uses during a portrait shoot to bounce light into shadowy
areas.

[Link] Light Types


Type Behaviour

AmbientLight Equal light from all directions — no shadows

DirectionalLight Parallel rays from a direction — like sunlight

PointLight Light from a point in all directions — like a light bulb

SpotLight Cone-shaped beam — like a flashlight or stage spotlight

HemisphereLight Sky colour from above, ground colour from below

6. State Variables
Variable Purpose

vrm = null Holds the loaded VRM model object after loading completes

vrmVisible = false Tracks whether the avatar is currently shown on screen

pendingShow = false vrmShow() was called before model loaded — show it when
ready
animState = 'idle' Current animation: 'idle' | 'bot' (speaking) | 'user' (listening)

Analogy: A stage manager's checklist — which actor is on stage, are the lights on, what scene are we
in.

7. Loading the VRM Model


const loader = new GLTFLoader(); [Link](parser => new
VRMLoaderPlugin(parser));
Creates the file loader and teaches it VRM by registering the plugin. Without registration, the loader
reads the raw 3D mesh but ignores all humanoid bone assignments and expression data.

[Link]('/static/[Link]', (gltf) => {...}, (p) => {...}, (e) => {...});
Asynchronously downloads and parses the VRM file. Three callback functions handle: success (model
loaded), progress (percentage update), and error (file missing/corrupt).

vrm = [Link];
Extracts the VRM object from the loaded GLTF. The VRMLoaderPlugin stores it at [Link]
after parsing is complete.

VRMUtils.rotateVRM0(vrm);
VRM version 0 models face backwards by default (a legacy quirk from early VRM spec). This utility
rotates the model 180° to face forward. Not needed for VRM 1.0 files.
Analogy: Correcting a photo that was scanned facing the wrong direction.

[Link]([Link]);
Removes skeleton bones that have no influence on any mesh vertex. Fewer active bones = fewer
matrix calculations per frame = better performance.

[Link]((child) => { if ([Link]) [Link] =


false; });
Disables frustum culling on all skinned meshes. Frustum culling hides objects outside the camera view
to save GPU, but avatar limbs can legally move off-screen — disabling prevents them popping out.
Analogy: Normally [Link] hides things outside the camera frame. But a waving arm might go off-
screen briefly — we disable this optimisation so body parts don't vanish mid-gesture.

[Link] = false; [Link]([Link]);


Adds the model to the scene but keeps it invisible until vrmShow() is called.

if (pendingShow) { pendingShow = false; _show(); }


If vrmShow() was called before loading finished, show the model now. pendingShow acts as a 'show
me when ready' deferred flag.
8. Helper Functions
const getBone = name => vrm?.humanoid?.getNormalizedBoneNode(name) ?? null;
Returns the Transform node (position/rotation/scale object) for a named skeleton bone. Optional
chaining (?.) safely returns null if VRM is not loaded yet. You then rotate the returned node to move that
body part.
Analogy: Like asking a puppet master for the string that controls the left arm. He hands you the string —
you pull it to move the arm.

const setExpr = (name, v) => vrm?.expressionManager?.setValue(name, v);


Sets a named facial expression to a value between 0.0 (fully off) and 1.0 (fully on). The
expressionManager blends multiple expressions simultaneously.
Analogy: A slider panel on a puppet's face. Pull the 'aa' slider to 0.8 and the mouth opens 80%. Pull the
'blink' slider to 1.0 and the eyes close fully.

VRM Standard Facial Expressions


Expression Name Effect

aa Open mouth — vowel A shape. Used for lip sync

ih / ou / ee / oh Other vowel mouth shapes for detailed lip sync

blink Both eyes closed simultaneously

blinkLeft / blinkRight Individual eye blink

happy Happy expression (eyebrows raised, slight smile)

sad Sad expression

angry Angry expression

surprised Wide eyes, open mouth

relaxed Soft, calm expression

9. setIdlePose — Default Resting Position


Called once after model load. Rotates specific bones into a natural resting position so the avatar does
not stand in a stiff T-pose.

Bone Rotation Applied

leftUpperArm z = +[Link]/3 (60°) — arm raised out to left side

rightUpperArm z = -[Link]/3 (60°) — arm raised out to right side

leftLowerArm z = +0.2 rad — slight forearm bend


rightLowerArm z = -0.2 rad — slight forearm bend

leftHand z = +0.1 — natural wrist angle

rightHand z = -0.1 — natural wrist angle

spine x = +0.05 — very slight forward lean

chest x = -0.03 — subtle counter-rotation for realism

head x = +0.05 — subtle downward head tilt

Analogy: A choreographer positioning a dancer before the show. Arms slightly out at 60 degrees, natural
body lean — not a robot T-pose.
[Link] uses radians. [Link] = 180°. [Link]/3 = 60°. [Link]/2 = 90°. Convert: degrees ×
([Link]/180).

10. Animation Functions


doIdle — Breathing & Blinking
function doIdle(dt, t) { ... }
dt = delta time (seconds since last frame, ~0.016 at 60 fps). t = total elapsed seconds since page load.
Called every frame in all three states.

[Link].z = [Link](t * 1.1) * 0.007;


Sways the spine left-right using a sine wave. t*1.1 = speed (1.1 full oscillations per second). 0.007 =
amplitude (barely noticeable, just like a real person standing still).
Key insight: [Link]() produces a smooth -1 to +1 wave, infinitely. Multiplying by small numbers (0.007)
makes motion subtle. Multiplying t by a number controls the speed.

[Link].y = [Link](t * 0.38) * 0.022;


Very slow neck rotation (0.38 cycles/second) creates a subtle natural micro-sway, like a person thinking
or waiting.

blinkTimer += dt; const c = blinkTimer % 3.5;


Accumulates time, uses modulo (%) to repeat a 3.5-second blink cycle. The avatar blinks once every
3.5 seconds.

if (c < 0.06) setExpr('blink', c / 0.06); else if (c < 0.13)


setExpr('blink', (0.13-c) / 0.07); else setExpr('blink', 0);
The blink animation: 0–0.06 s eyes closing (expression ramps 0→1). 0.06–0.13 s eyes opening (ramps
1→0). Remaining 3.37 s: eyes fully open. Total blink = 0.13 s — same as a real human blink.
Analogy: A timer that resets every 3.5 seconds. For the first 0.13 seconds it blinks, then holds open for
the rest. Just like a real person.
doBot — Speaking Animation
function doBot(dt, t) { doIdle(dt, t); lipPhase += dt * 11;
setExpr('aa', [Link](0, [Link](lipPhase) * 0.5 + 0.3) * 0.85);
[Link].x = 0.05 + [Link](t * 4.8) * 0.03; }
Runs while the bot is speaking. Inherits all idle motion and adds rapid lip movement and a head nod.

lipPhase += dt * 11: Advances lip phase at 11 rad/s — rapid oscillation creating a talking pace
[Link](lipPhase) * 0.5 + 0.3: Sine centred at 0.3 with ±0.5 amplitude — mouth bounces between -
0.2 and 0.8
[Link](0, ...): Clamps to zero — mouth cannot go negative (cannot over-close)
* 0.85: Scales peak to 85% open — slightly under fully open for realism
[Link].x = 0.05 + [Link](t * 4.8) * 0.03: Subtle head bob at 4.8 Hz — natural speaking
motion
Analogy: A marionette whose mouth opens and closes while the operator speaks. 11 rad/s creates a
natural-paced talking rate — not too slow, not cartoon-fast.

doUser — Listening Animation


function doUser(dt, t) { doIdle(dt, t); setExpr('aa', 0);
[Link].x = 0.01 + [Link](t * 0.85) * 0.005; }
Called when the user is speaking (bot is listening). Mouth fully closed (aa=0). Spine has a slightly more
forward lean at a slower breathing rate — classic attentive-listening posture.
Analogy: A receptionist leaning slightly forward, mouth closed, attentively listening to a patient describe
their symptoms.

11. The Animation Loop


(function animate() { requestAnimationFrame(animate); ...
[Link](scene, camera); })();
An Immediately Invoked Function Expression (IIFE) that recurses via requestAnimationFrame. Runs
approximately 60 times per second, matching the monitor refresh rate.
Analogy: A film projector — advance one frame, project it, advance the next, project it. 60 times a
second. Each call to animate() is one frame.

const dt = [Link]((now - prevTime) / 1000, 0.05);


Delta time = seconds since last frame (~0.0167 at 60 fps). Capped at 0.05 s (20 fps equivalent) to
prevent huge animation jumps if the tab was backgrounded or the page froze.
Why delta time? Without it, animations run twice as fast on 120 Hz monitors vs 60 Hz. Multiplying
movements by dt makes them frame-rate independent — same speed on all screens.

if (animState === 'bot') doBot(dt, t); else if (animState === 'user') doUser(dt,
t); else doIdle(dt, t);
Routes to the correct animation function based on the current state. State is changed externally by
[Link]().
[Link]?.update(); [Link](dt);
[Link]() applies all queued expression value changes to the 3D mesh vertices.
[Link](dt) runs VRM physics — spring bones (hair, loose clothing) and other VRM internal
systems.
Analogy: Setting expression values is like queuing puppet string movements on a notepad. .update() is
when the puppet master actually pulls the strings.

[Link](scene, camera);
The final step every frame — instructs the GPU to rasterise the entire 3D scene from the camera's
point of view and paint the result onto the canvas pixels.

12. Public API — Global Functions


Three functions are attached to window so [Link] (in a different module) can control the avatar without
importing this file.

[Link]()
[Link] = () => { if (!vrm) { pendingShow = true; return; } _show();
};
Makes the avatar visible. If VRM has not finished loading yet, sets pendingShow=true so it auto-shows
when loading completes. Call when the user starts a call.

[Link]()
[Link] = () => { pendingShow = false; if (!vrm) return;
vrmVisible = false; [Link] = false;
shadowEl?.[Link]('hidden'); };
Hides the avatar and its CSS shadow. Clears any pending show. Call when the call ends.

[Link](state)
[Link] = (state) => { animState = state; if (state !== 'bot')
setExpr('aa', 0); };
Changes the avatar animation state. When leaving 'bot' state, immediately zeroes the mouth
expression so it does not freeze mid-open.
State When to Use

'idle' Call has just started — bot not speaking or listening yet

'bot' Bot is generating and playing its audio response

'user' User is speaking — bot is actively listening

Integration tip: In [Link], call vrmSetState('bot') inside setBotSpeaking(), vrmSetState('user') inside


setUserSpeaking(), and vrmSetState('idle') inside setActive().
13. Ways to Improve This
Real Audio-Driven Lip Sync
Currently lip sync is a synthetic sine wave. For real audio-driven lip sync, use the Web Audio API
AnalyserNode to read actual audio amplitude and drive the 'aa' expression:
[Link](dataArray); const amp =
[Link]((a,b)=>a+b) / [Link] / 255; setExpr('aa', [Link](1,
amp * 1.5));
This makes lip movement react to actual voice volume in real time.

Emotion Expressions
Add happy/concerned/surprised expressions based on conversation tone. Subscribe to the transcript
SSE stream, detect emotion keywords ('great', 'sorry', 'urgent') and call setExpr('happy', 0.5) etc.

Smooth State Transitions


Instead of instant expression cuts when switching states, lerp (linearly interpolate) between values for
smooth transitions:
currentAA = [Link](currentAA, targetAA, dt * 8); setExpr('aa',
currentAA);

Clinic Background
Add a reception desk and clinic background using a PlaneGeometry with a photo texture, or a 360°
HDRI environment map with [Link].

Resources
Resource Link / Description

[Link] Docs [Link] — complete API reference

[Link] Examples [Link] — 500+ interactive live demos

three-vrm [Link] — VRM library with


examples

VRM Models [Link] — free VRM avatar downloads

VRoid Studio Free Windows/Mac app to create custom VRM avatars

Mixamo [Link] — free character animations (GLTF


export)
[Link] Journey [Link] — the best [Link] learning course

You might also like