DEV Community

Cover image for Ready Player Me Shut Down — Here's How I Rebuilt Our Avatar System
MUHAMMAD USMAN AWAN
MUHAMMAD USMAN AWAN

Posted on • Edited on

Ready Player Me Shut Down — Here's How I Rebuilt Our Avatar System

From Ready Player Me Shutdown to Building Our Own Avatar Renderer 🚀

How I replaced Ready Player Me with a fully modular GLB renderer — with zero Three.js experience.

On January 31, 2026, Ready Player Me shut down.

We were warned at the end of December 2025.

That gave us roughly 3–4 weeks to replace a system that powered avatars across our entire application.

Two things had to be rebuilt immediately:

  1. Avatar creation
  2. Avatar appearance across the app

But creation came first — because without avatars, appearance didn’t matter.

And the responsibility landed completely on me — a core frontend developer with zero experience in Three.js, React Three Fiber, or Drei.

It felt like a do-or-die penalty in the last minute of a match.

But as developers, learning under pressure is part of the job.

As Master Shifu said:

"If you only do what you can do, then you will never be better than what you are."

So I started.


The Old System (Before Ready Player Me)

Before using Ready Player Me, our avatar system was simple:

  • A single Base64 GLB
  • Fully combined avatar
  • All assets embedded
  • Easy to render
  • Easy to store
  • Easy to preview

But it was not flexible.

Ready Player Me introduced:

  • Modular assets
  • Morph targets
  • Animations
  • Texture customization
  • Live preview

When RPM shut down — we lost everything.


Step 1 — First Three.js Success

The first milestone was simple:

Render a GLB.

Using React Three Fiber, I loaded a dummy Wolf3D model.

And it worked.

Seeing a 3D avatar render inside our app was the first real sign:

This might actually be possible.


The First Real Problem — Skeleton Collisions

Then reality hit.

Ready Player Me assets had their own armature.

Our custom assets had a different armature.

Nothing aligned.

Meshes broke.
Animations failed.
Bones mismatched.

Our graphics team stepped in and produced:

  • Custom body GLB
  • Custom head GLB
  • Matching armature

Once those worked together:

We finally had:

  1. Head
  2. Body
  3. 2–3 test assets

And everything rendered correctly.

First real victory. 🎉


The Real Challenge — 37 Modular Assets

Then came the real problem.

We had:

  • 18 assets for female
  • 19 assets for male

Different categories:

Body Assets

  • Top
  • Bottom
  • Footwear
  • Outfit
  • Skin color

Head Assets

  • Hair
  • Hair color
  • Eyes
  • Beard
  • Beard color
  • Face shapes
  • Eyebrows
  • Nose
  • Lips and many more.

Global Assets

  • Animations
  • Poses

Each category needed different rendering logic.


Apparel Was Easy

Clothing assets were simple:

Just attach GLB meshes to the body.

addBodyPart(getGLTFScene(shirtGLTF), 'shirt');
addBodyPart(getGLTFScene(pantsGLTF), 'pants');
addBodyPart(getGLTFScene(shoesGLTF), 'shoes');
Enter fullscreen mode Exit fullscreen mode

That part worked almost immediately.


Asset Loading Strategy

Assets could come from multiple sources:

  • Redux preview state
  • API data
  • Defaults

Preview needed priority.

const assets = {
  ...defaultAssets,
  ...glbAssets,
  bodyColorTexture: glbAssets?.bodyColorTexture || defaultAssets.bodyColorTexture,
  eyeColorTexture: glbAssets?.eyeColorTexture || defaultAssets.eyeColorTexture,
};

const shirtPath = hasOutfitPreview
  ? defaultAssets.shirt
  : (apparelPreview.top || assets.shirt || defaultAssets.shirt);

const hairPath = hasOutfitPreview && gender === 'female'
  ? defaultAssets.hair
  : (apparelPreview.hair || assets.hair || defaultAssets.hair);
Enter fullscreen mode Exit fullscreen mode

This ensured:

Live preview always wins.


Runtime Texture Baking (Eyebrows & Beard)

Head assets were harder.

Beards and eyebrows were PNG overlays.

Instead of adding extra meshes, I baked them directly onto the face texture.

function createFaceWithEyebrowsTexture(baseTexture, eyebrowsTexture, fallback, options) {

  if (eyebrowColor) {
    // tint only eyebrow pixels
  }

  const baked = new THREE.CanvasTexture(canvas);
  baked.flipY = false;
  baked.colorSpace = THREE.SRGBColorSpace;

  return baked;
}
Enter fullscreen mode Exit fullscreen mode

Result:

  • No extra draw calls
  • Better performance
  • Realistic blending

The Core Innovation — Skeleton Rebinding

The biggest technical challenge was equipping assets.

Every asset needed to use the same skeleton.

Solution:

Skeleton rebinding.

const addBodyPart = (gltfScene: THREE.Group, partName: string) => {

  if (child instanceof THREE.SkinnedMesh) {

    const newBones = skinnedChild.skeleton.bones
      .map(bone => bonesByName[bone.name])
      .filter(Boolean);

    const newSkeleton = new THREE.Skeleton(
      newBones,
      skinnedChild.skeleton.boneInverses.map(m => m.clone())
    );

    skinnedClone.skeleton = newSkeleton;
    skinnedClone.bind(newSkeleton, skinnedChild.bindMatrix.clone());

    skinnedClone.pose();
    skinnedClone.visible = true;

    combined.add(skinnedClone);
  }
};
Enter fullscreen mode Exit fullscreen mode

This allowed:

  • Any asset to attach dynamically
  • Animations to work
  • No broken meshes
  • No T-pose issues

Static Attachments (Head Parts)

Some assets didn't need skeleton binding.

They could attach directly to bones.

if (['skin','eyes','teeth','beard','hair'].includes(resolvedPartName) && headBone) {
  headBone.add(clonedMesh);
}
Enter fullscreen mode Exit fullscreen mode

This worked perfectly for:

  • Hair
  • Eyes
  • Beard
  • Accessories

Costume Mode vs Modular Mode

Two rendering paths were required.

Costume Mode

Single full-body GLB:

if (isCostumeMode) {
  const costumeScene = SkeletonUtils.clone(getGLTFScene(outfitGLTF));
  return costumeScene;
}
Enter fullscreen mode Exit fullscreen mode

Modular Mode

Dynamic assembly:

addBodyPart(getGLTFScene(bodyGLTF), 'skin');
addBodyPart(getGLTFScene(headGLTF), 'head');
addBodyPart(getGLTFScene(shirtGLTF), 'shirt');
addBodyPart(getGLTFScene(pantsGLTF), 'pants');
addBodyPart(getGLTFScene(shoesGLTF), 'shoes');
Enter fullscreen mode Exit fullscreen mode

Animations and Morph Targets

Animations had to survive scene rebuilds.

<AnimatedCombinedScene key={combinedScene.uuid} combinedScene={combinedScene} />
Enter fullscreen mode Exit fullscreen mode

Morph targets enabled facial customization:

Object.entries(apparelPreview.morphTargets || {}).forEach(([name, value]) => {
  const idx = child.morphTargetDictionary?.[name];
  if (idx !== undefined)
    child.morphTargetInfluences[idx] = value;
});
Enter fullscreen mode Exit fullscreen mode

Automatic Eye Blink

Even blinking was implemented.

Wolf3D style.

useFrame((_, delta) => {
  // eyeBlinkLeft / eyeBlinkRight logic
});
Enter fullscreen mode Exit fullscreen mode

Small detail.

Huge realism boost.


The Final Renderer Architecture

Key ideas:

Modular Equipping

  • Every asset is a GLB
  • Loaded via useGLTF
  • Preview has priority

Skeleton System

  • One template skeleton
  • Bone-name matching
  • Rebinding

Runtime Baking

  • Eyebrows
  • Beard
  • Face details

Dual Render Paths

  • Costume mode
  • Modular mode

Stable Rendering

  • No T-pose
  • No disappearing meshes
skinnedClone.frustumCulled = false;
skinnedClone.pose();
Enter fullscreen mode Exit fullscreen mode

Live Updates

Redux drives everything.

key={combinedScene.uuid}
Enter fullscreen mode Exit fullscreen mode

Forces clean rebuild.


Avatar Appearance Was Easy

Once creation worked:

Appearance across the app was trivial.

The renderer already supported props.

So avatar panels and previews worked instantly.

Sometimes the hardest part unlocks everything else.


Performance Tradeoffs

The old system:

  • Single GLB
  • Fast
  • Simple

The new system:

  • Modular GLBs
  • Slightly slower
  • Much more flexible

Creation flow became far smoother, even if asset loading takes slightly longer.

Optimization is ongoing.


What I Learned

1 — Research Is Development

Learning is part of the job.

Not optional.

Essential.

2 — AI Helps Real Engineers

AI helped me:

  • Learn Three.js
  • Solve skeleton issues
  • Debug rendering
  • Speed up development

AI didn't replace engineering.

It accelerated learning.


Building My Own GLB Renderer

After finishing the system, I got inspired to go further.

I started building a custom GLB renderer.

Features:

  • Load any GLB
  • Test assets on provided bodies
  • Male + female models
  • Asset testing

Currently supports:

  • Tops
  • Bottoms
  • Footwear
  • Hair

Live Demo: glb-viewer

GitHub: glb-viewer


Final Thoughts

Ready Player Me shutting down could have been a disaster.

Instead, it became one of the biggest growth experiences of my career.

In less than a month:

  • Learned Three.js
  • Built a modular avatar system
  • Implemented skeleton rebinding
  • Created runtime texture baking
  • Supported animations and morph targets

And most importantly:

We didn't depend on a third-party avatar system anymore.

We built our own.

Top comments (2)

Collapse
 
webdeveloperhyper profile image
Web Developer Hyper

Wow! This is a really cool 3D project. Thank you for sharing. I’ve also been trying 3D recently, and I hope to reach your level soon. 😄

Collapse
 
usman_awan profile image
MUHAMMAD USMAN AWAN

Thank you @webdeveloperhyper 🙌.