logo
Unreal Asset Exporter

uasset-to-glb User Guide

Convert cooked Unreal Engine projects into GLB files with PBR materials correctly wired. Works with UE4.20 through UE5.4. macOS arm64 binary ships inside the package — no .NET or Unreal install required to run.

Installation

Terminal
npm install -g @babylonjsmarket/uasset-to-glb

Verify installation:

Terminal
uasset-to-glb --version

The native parser (bin/uasset-export) is a self-contained macOS arm64 binary bundled in the package. It spawns as a subprocess when you run a conversion.


Quick Start

Terminal
# 1. Cook your Unreal project in the editor (see next section)

# 2. Point uasset-to-glb at the cooked output
uasset-to-glb convert \
  ~/Documents/Unreal\ Projects/MyGame/Saved/Cooked/MacNoEditor/MyGame \
  -o ./out

# 3. Open any .glb in your favorite viewer
open ./out/Content/Characters/SM_Hero.glb

Output:

project:  /Users/you/Documents/Unreal Projects/MyGame/Saved/Cooked/MacNoEditor/MyGame
engine:   UE4.27
staging:  /var/folders/.../uasset-staging-1234
output:   ./out
✔ Extracted 828 (14 skipped). Wrote 113/113 GLBs to ./out

Each GLB appears in ./out as its mesh finishes — you don't wait for the whole project.


Prerequisite: Cook Your Project First

This is the step that makes or breaks the tool. Read it carefully.

Unreal Engine stores mesh data two different ways:

  • Uncooked (what's in your project's Content/ folder): geometry lives in editor-only SourceModels. Our parser cannot read this.
  • Cooked (what ships in a packaged build): geometry lives in RenderData. Our parser reads this fine.

If you point the tool at a raw project you'll get no RenderData — asset is uncooked for every mesh, and zero GLBs.

Cook from the Unreal Editor (GUI)

  1. Open YourProject.uproject in UE Editor
  2. File → Cook Content for Mac (or Windows/Linux — any target works, we just need the cooked binary format)
  3. Wait a few minutes — progress shows in the bottom-right corner
  4. Cooked output lands at YourProject/Saved/Cooked/<Platform>/YourProject/Content/

Cook from the command line

Terminal
"/Users/Shared/Epic Games/UE_4.27/Engine/Binaries/Mac/UE4Editor-Cmd" \
  /path/to/YourProject/YourProject.uproject \
  -run=cook -targetplatform=MacNoEditor

Adjust UE_4.27 to your installed engine version.

Verify the cook worked

Terminal
ls /path/to/YourProject/Saved/Cooked/MacNoEditor/YourProject/Content/

You should see the same folder hierarchy as your project's Content/ directory. That's what you pass to uasset-to-glb convert.


Converting Assets

Terminal
uasset-to-glb convert <cooked-project-path> -o <output-dir> [flags]

Core flags

FlagPurposeExample
-o, --output <dir>Required. Where GLB files land.-o ./out
--ue <version>Engine version. Auto-detected from .uproject if present.--ue 4.27
--types <list>Comma-separated asset types to export.--types StaticMesh,SkeletalMesh
--filter <substr>Only export assets whose package path contains this substring.--filter Characters
--compress <fmt>Texture compression inside the GLB.--compress webp
--meshoptApply meshopt geometry compression.--meshopt
--keep-stagingKeep the intermediate glTF + PNGs for debugging.--keep-staging

Supported asset types

  • StaticMesh — environment props, weapons, static geometry
  • SkeletalMesh — characters and rigged meshes with bone hierarchies
  • AnimSequence — bone animations (experimental, some UE4.27 anims fail in CUE4Parse)
  • Texture2D — loose texture export to PNG
  • Material / MaterialInstanceConstant — material parameter extraction

Default (no --types) exports everything that's convertible.


Listing Assets (Dry Run)

Preview what would be exported without running the parser:

Terminal
uasset-to-glb list ~/Documents/Unreal\ Projects/MyGame/Saved/Cooked/MacNoEditor/MyGame

Output:

engine: UE4.27
scanning: /path/to/Content

RetroArcade/ArcadeMachine_01/Mesh/SM_ArcademachineA_Plane.uasset
RetroArcade/ArcadeMachine_01/Mesh/SM_ArcadeMachine_DeathGrip.uasset
RetroArcade/BasketBallArcade/Mesh/SM_BasketballHoop.uasset
...

1003 .uasset files found.

Combine with --filter to narrow:

Terminal
uasset-to-glb list /path/to/cooked --filter Characters

Material Mapping

Unreal materials are arbitrary HLSL node graphs. We don't reconstruct the graph — we map the material instance's texture parameters to glTF's PBR channels using two passes:

Pass 1 — parameter name

Unreal parameter nameglTF channel
BaseColor, Diffuse, Albedo, ColorMapbaseColorTexture
Normal, NormalMapnormalTexture
ORM, PackedMRA, MRA, MRAO, ARMocclusionTexture + metallicRoughnessTexture (packed, same image — glTF convention)
RAM (Unreal's R=Rough/G=AO/B=Metal)Swizzled to glTF ORM (R=AO/G=Rough/B=Metal) at export time
Roughness, Metallic, AO (separate)Combined into a synthesized ORM PNG
Emissive, Emission, EmissiveColoremissiveTexture

Pass 2 — filename suffix fallback

For parameters with generic names like Texture01, we fall back to the texture asset's filename suffix:

_BC/_D/_Diffuse → color, _N/_Normal → normal, _ORM/_MRA → packed, _R/_M/_AO → separate, _E/_Emissive → emissive.

Scalars

Roughness and Metallic scalar parameters become roughnessFactor and metallicFactor in the glTF material.

What won't work perfectly

Hair, eye, cloth, and subsurface shaders use custom graph logic we don't reproduce. Expect ~80% of standard opaque surfaces to land correctly. The rest need manual fix-up — check _report.json (see next section).


Output Structure

out/
├── Content/
│   ├── Characters/
│   │   ├── SK_Hero.glb
│   │   └── SK_Enemy.glb
│   ├── Environment/
│   │   └── SM_Crate.glb
│   └── ...
└── _report.json

One GLB per mesh, mirroring the project's package path. Each GLB is a single self-contained file — all textures are embedded.

_report.json

JSON
{
  "written": 113,
  "totalMeshEntries": 113,
  "materialWarnings": [
    "MI_Hero: unmapped parameter \"CustomParamA\" → T_Hero_Mask",
    "MI_Eyes: duplicate baseColor (param \"Diffuse\" → T_Eyes_Diff, keeping first)"
  ]
}

Scan the materialWarnings for assets that need manual wiring after export.


Examples

Convert only characters, compress textures to webp

Terminal
uasset-to-glb convert ./cooked -o ./out \
  --types SkeletalMesh \
  --filter Characters \
  --compress webp

Get everything packed as small as possible

Terminal
uasset-to-glb convert ./cooked -o ./out \
  --compress webp \
  --meshopt

Debug a single problematic asset

Terminal
uasset-to-glb convert ./cooked -o ./out \
  --filter SM_BrokenMesh \
  --keep-staging

The path to the temp staging directory prints at the end. Open the raw glTF + PNGs there to see what the extractor produced before post-processing.

CI/CD: convert fresh cooks on every build

YAML
# .github/workflows/convert-assets.yml
- name: Install uasset-to-glb
  run: npm install -g @babylonjsmarket/uasset-to-glb

- name: Convert cooked assets
  run: |
    uasset-to-glb convert \
      ./UnrealProject/Saved/Cooked/LinuxNoEditor/UnrealProject \
      -o ./public/models

Troubleshooting

no RenderData — asset is uncooked

Your project hasn't been cooked yet. See the Prerequisite: Cook Your Project First section above. Uncooked .uasset files store geometry in editor-only data blocks we can't read.

Meshes render "shiny black" in my viewer

This means the base color texture is being multiplied by all-zero per-vertex colors. Our post-processor strips those out automatically — if you're still seeing it, you have an older local build. Reinstall:

Terminal
npm uninstall -g @babylonjsmarket/uasset-to-glb
npm install -g @babylonjsmarket/uasset-to-glb

Object reference not set to an instance of an object on AnimSequences

Known CUE4Parse limitation with some UE4.27 animation packages. Static + skeletal meshes still export correctly. The warning is non-fatal.

Missing textures / unmapped material parameters

Check out/_report.json for materialWarnings. Each entry tells you which parameter on which material wasn't recognized. Often the fix is: rename the texture file with a standard suffix (_BC, _N, _ORM), then re-export.

Encrypted .pak files or IoStore builds

Not supported in this version. The tool reads loose cooked .uasset files, not .pak bundles. If you only have paks, unpack them first with UnrealPak or FModel.


How It Works

cooked UE project (Content/**/*.uasset)

        ▼  spawn subprocess
┌──────────────────────────────┐
│  bin/uasset-export (C#)      │   CUE4Parse-based native binary
│  - scans Content/ dir        │   self-contained macOS arm64
│  - exports meshes → glTF     │
│  - dumps referenced PNGs     │
│  - emits per-asset manifest  │
└──────────────┬───────────────┘
               │ glTF + PNGs + manifest (streamed)

┌──────────────────────────────┐
│  TS post-process             │   @gltf-transform
│  - material mapping (PBR)    │   swizzle RAM → ORM
│  - pack textures into GLB    │   strip zero vertex colors
│  - single .glb per asset     │   meshopt / webp if requested
└──────────────────────────────┘

The C# host wraps CUE4Parse (the same library that powers FModel). The TypeScript CLI spawns it, reads READY: <json> lines from stderr as each mesh finishes, and post-processes each one immediately so GLBs land in ./out incrementally rather than all at the end.


Source

Open source under MIT. Contributions welcome.

Was this page helpful?

We read every note — tell us what's working and what isn't.

↑↓ NavigateEnter SelectEsc CloseCtrl+K Open Search