Any3DAny3D
·Any3D Team

Different Platforms, Different Fates: A Compression Selection Guide

3d-compressiontexture-compressionvertex-compressionoptimizationpipeline

The previous four articles covered the tools of vertex and texture compression. But knowing how to use the tools is one thing; which to use, and in what scenario is another. This article answers that—and aims to let you decide the moment you finish reading.

After this one you should be able to answer: what platform does my project run on, is the first-paint bottleneck download or VRAM, and which scheme should textures and vertices each use.

Set the tone: scenario-driven, not tool-driven

The whole article has one core principle, one the series keeps stressing:

There is no "best" compression scheme—only the "best-matching-the-scenario" scheme.

Three variables that drive the choice:

  1. Platform/device: desktop PC, mobile browser, VR headset, Mini Program—radically different capabilities
  2. Usage: one-shot display (e-commerce product page) or long immersion (VR game)
  3. Primary bottleneck: slow download, insufficient VRAM, or decode stalling the first paint

Think through the bottleneck first, then come back to pick tools. The matrix below freezes that thinking into place.

Core decision matrix: platform × recommended scheme

This is the most important table in the article. By platform, it gives the recommended scheme for texture and vertex, with reasons.

Platform / scenarioTexture schemeVertex schemeMain bottleneckKey reason
Desktop web (PC browser)KTX2 or WebPMeshOpt / quantizationDownload speedVRAM is ample; focus on small files, fast load
Mobile web (phone browser)KTX2 (must)MeshOptVRAMPhone VRAM is tight; textures must be block-compressed
WebXR / VR headsetKTX2 (must)MeshOpt + LODVRAM + frame rateVRAM blowup crashes; dropped frames cause nausea
WeChat Mini ProgramKTX2 / WebPMeshOpt / plain quantizationPackage + compatibilityPackage size is limited; avoid heavy decoders
E-commerce product displayWebP (light) / KTX2 (precise)MeshOptFirst-paint speedDemands instant open; trade size for speed
Large scenes / digital twinsKTX2 (must)MeshOpt + Draco + LODVRAM + draw callsMany textures, big models; compress on every front

A few judgments worth remembering:

  • Whenever VRAM is the bottleneck, KTX2 is mandatory (mobile, VR, large scenes all qualify)
  • When download is the bottleneck and VRAM is ample, WebP is enough (desktop, e-commerce)
  • For package-sensitive environments like Mini Programs, prefer MeshOpt over Draco—smaller decoder, better compatibility
  • Only consider Draco for very large models; for the vast majority of small-to-medium models, MeshOpt is more balanced

Texture formats compared across all dimensions

Knowing "use KTX2" isn't enough. For texture formats specifically, lay out all the dimensions:

FormatFile sizeVRAM usageUpload speedCompatibilityQualityUse case
PNGLargeLarge (after decompress)SlowVery wideLosslessNeeds exact values/alpha, or legacy fallback
JPGTinyLarge (after decompress)SlowVery wideLossyColor maps, network-first
WebPVery smallLarge (after decompress)SlowFairly wideHighDesktop web, chasing download speed
AVIFEven smallerLarge (after decompress)SlowProgressiveHighNew platforms, extreme compression
KTX2 (ETC1S)SmallTinyFastNeeds transcodeMedium (color is fine)Color maps, mobile/VR
KTX2 (UASTC)MediumSmallFastNeeds transcodeHighNormal/data maps

Note the first three rows (PNG/JPG/WebP/AVIF) all show VRAM as "large"—no matter how small on disk, once in VRAM they all decompress back to raw pixels. That's the fundamental limit of traditional formats.

A mixed strategy is common: key color maps use KTX2 to secure VRAM; secondary maps (e.g. a small emissive) use WebP for convenience. You don't have to go all-KTX2; allocate by bottleneck.

Decision flowchart: pick step by step

The matrix is the result; the flowchart shows how to reach it.

Where does your project run?
│
├─ Desktop web (ample VRAM)
│   └─ First paint slow?
│       ├─ Yes → WebP (or AVIF) + MeshOpt  [chase download speed]
│       └─ No, but many models → KTX2 + MeshOpt [leave headroom]
│
├─ Mobile web / VR / large scenes (tight VRAM)
│   └─ Textures must use KTX2 (color ETC1S, data UASTC)
│       └─ Vertices super-dense?
│           ├─ Yes → + Draco [max ratio, can tolerate slow decode]
│           └─ No → + MeshOpt [balanced]
│
└─ Mini Program / restricted runtime
    └─ Package-sensitive?
        ├─ Yes → plain quantization or MeshOpt + WebP [zero/small decoder]
        └─ Okay → MeshOpt + KTX2 [standard combo]

Vertex vs texture compression: where to invest

A common question: budget is limited, optimize which end first? Look at the model's makeup:

Model traitInvestment focusReason
PBR character/product (many maps)Texture compressionTextures are 80%+; low ROI on vertices
CAD/scan model (super-dense vertices, few maps)Vertex compressionVertices are the main bulk
Animated character (skeleton + skinning)Invest in both, but vertex-prioritize beyond DracoAnimation data also takes bulk; Draco is weak on animation
Building/scene (large, medium maps)Texture + LODTextures save VRAM; LOD saves draw calls

A rough return ranking: texture KTX2 > vertex MeshOpt/quantization > geometry simplification (LOD) > vertex Draco. Do the high-return ones first.

Mixed strategy: key maps KTX2, secondary maps WebP

Not every map is worth compressing to KTX2. A typical PBR material has 5-6 maps; compressing all to KTX2 is a lot of engineering and sometimes unnecessary.

A practical split:

  • Must KTX2: albedo (large, color), normal (precision-sensitive), roughness/metallic (affect lighting)
  • Can be WebP/JPG: emissive (usually small), AO (mid-low frequency), detail normal (if any)

The judgment criteria: is the map large, is it high-frequency detail, will it be sampled frequently. All three → KTX2; none → a traditional format is easier.

Automated selection: gltf-transform does it in one shot

gltf-transform's optimize command is the silver bullet for most scenarios—it auto-detects texture types, compresses textures by the recommended strategy, and optionally handles vertices.

# Install
npm install -g @gltf-transform/cli

# Standard optimization: texture KTX2 + vertex MeshOpt + de-duplication
gltf-transform optimize model.glb model-optimized.glb \
  --texture-compress basisu \
  --meshopt

Parameter reference:

ParameterEffectDefault
--texture-compress basisuCompress textures to KTX2 (auto ETC1S/UASTC)Off
--meshoptEnable MeshOpt vertex compressionOff
--simplifyGeometry simplification (reduces vertices, lossy)Off
--weldMerge duplicate verticesOn
--pruneRemove unused nodes/materialsOn

A "strong compression" combo suited for mobile:

gltf-transform optimize model.glb model-mobile.glb \
  --texture-compress basisu \
  --meshopt \
  --simplify --simplify-ratio 0.5 \
  --prune --weld

A "gentle" desktop combo (preserve detail, only compress textures and vertices):

gltf-transform optimize model.glb model-desktop.glb \
  --texture-compress webp \
  --meshopt

Wrap it as a reusable script

Encapsulate the above into a script that outputs a version per platform:

// compress.mjs — output different compressed versions by target
import { optimize } from "@gltf-transform/functions";
import { NodeIO } from "@gltf-transform/core";
import { KHRONOS_EXTENSIONS } from "@gltf-transform/extensions";

const io = new NodeIO().registerExtensions(KHRONOS_EXTENSIONS);

const targets = {
  // Mobile: KTX2 + MeshOpt + simplify
  mobile: { texture: "basisu", meshopt: true, simplify: 0.5 },
  // VR: KTX2 + MeshOpt, no simplify (close-up viewing in VR)
  vr: { texture: "basisu", meshopt: true, simplify: null },
  // Desktop: WebP + MeshOpt, gentle
  desktop: { texture: "webp", meshopt: true, simplify: null },
};

const input = process.argv[2];
const doc = await io.read(input);

for (const [name, cfg] of Object.entries(targets)) {
  const out = doc.clone(); // independent copy per target
  await optimize(out, {
    textureCompression: cfg.texture,
    meshCompression: cfg.meshopt ? "meshopt" : null,
    simplify: cfg.simplify != null ? { ratio: cfg.simplify } : null,
  });
  await io.write(`model-${name}.glb`, out);
  console.log(`✓ model-${name}.glb`);
}
node compress.mjs model.glb
# Outputs model-mobile.glb / model-vr.glb / model-desktop.glb

At runtime, load the matching version by device (distinguish via UA or WebGL capability probing) for the best first-paint experience. The next article's end-to-end walkthrough strings this all together.

One-line cheat sheet

  • VRAM is the bottleneck → KTX2, no choice
  • Download is the bottleneck, VRAM ample → WebP/AVIF also fine
  • Sensitive to decoder size → MeshOpt > plain quantization > Draco
  • Very large models → only then consider Draco
  • Unsure → run gltf-transform optimize in one shot; adjust if wrong

What's next

The selection framework is in place. The final article wraps up: starting from a real model, walk the full flow of Blender export, texture compression, vertex compression, and engine loading, with a complete automation script and a series cheat sheet.

Support Us