Why your USDZ renders pink in Quick Look
Pink is a placeholder, not a colour. It means the renderer successfully found geometry and then failed to resolve a surface for it — which narrows the cause down to about five things.
Last reviewed July 27, 2026
What pink actually signals
Real-time renderers use a loud, unmissable colour for "no valid material here" precisely so it cannot be mistaken for art direction. In the USD world the two ways to end up there are a prim with no bound material at all, and a prim whose bound material resolves to something the renderer cannot shade. Apple's developer forums carry a documented instance of the second: prims carrying a `displayColor` primvar rendering pink when opened in AR Quick Look.
Cause 1 — no material binding on the mesh
A USD Mesh prim needs a material:binding relationship pointing at a UsdPreviewSurface-backed Material prim. Exporters that write geometry-only stages — mesh converters, some scan pipelines, hand-authored .usda — produce perfectly valid USD with nothing to shade. Open the stage and look for the binding:
def Mesh "chair"
{
rel material:binding = </Root/Materials/oak>
point3f[] points = [...]
}
def Material "oak"
{
token outputs:surface.connect = </Root/Materials/oak/PBR.outputs:surface>
def Shader "PBR"
{
uniform token info:id = "UsdPreviewSurface"
color3f inputs:diffuseColor.connect = </Root/Materials/oak/tex.outputs:rgb>
float inputs:roughness = 0.6
}
}Cause 2 — displayColor instead of a real material
USD lets you attach a displayColor primvar to geometry as a cheap preview tint. Many exporters emit it as a fallback when a mesh has vertex colours and no PBR material. It is a *hint* for interactive viewports, not a shading network — and the forum thread above shows AR Quick Look declining to treat it as one. If your source asset relied on vertex colours with no material, you need to author a UsdPreviewSurface (or bake the vertex colours to a texture) before it will shade correctly on iOS.
Cause 3 — the texture file is missing from the archive
USDZ is a self-contained bundle: every asset the stage references must live inside the archive. When a shader input reads asset inputs:file = @/Users/you/Textures/albedo.png@, that absolute path exists on exactly one machine on earth. Once packaged, the reference dangles.
This is the single most common cause of "it looked fine in my DCC". Fix it by re-exporting with relative paths (@./textures/albedo.png@) and confirming the file is genuinely inside the .usdz — unzip it and list the entries. A dangling texture reference is one of the blockers we treat as fatal for exactly this reason: the model loads, so nothing errors, and the product just looks wrong.
Cause 4 — the texture is there but unreadable
The USDZ spec permits png, jpeg, exr and avif inside a package, but what the packaging spec allows and what Quick Look decodes reliably are not the same set. The encodings that repeatedly cause trouble:
- 16-bit-per-channel PNG — widely reported to render black on iOS. Re-encode at 8 bits per channel.
- Paletted PNG (colour type 3) — index lookup is not handled; convert to truecolour.
- Progressive JPEG (SOF2) — baseline JPEG (SOF0) is the safe choice; re-encode without progressive.
- Very large maps — beyond 4096 px on a side, expect downscaling at best and a memory-driven refusal to open at worst.
Cause 5 — colour space and channel mix-ups
Not pink, but the same family of "the shader resolved to nonsense" bugs, and worth checking while you are in there. UsdPreviewSurface expects base colour in sRGB and data maps — roughness, metalness, occlusion, normal — in linear space. A normal map tagged sRGB produces lighting that is subtly but consistently wrong. Similarly, glTF packs occlusion/roughness/metalness into one texture's R/G/B channels; a converter that wires the whole RGB texture into inputs:roughness instead of its green channel gives you a surface that reads as plastic. These channel and colour-space slips are a converter problem more than an authoring one — see why converters break PBR.
Triage order
- Unzip the
.usdz. List the entries. Is every referenced texture actually present? - Open the stage (
usdcatit if binary). Does the pink mesh carry amaterial:binding? - If it does, does the bound
Materialconnectoutputs:surfaceto aUsdPreviewSurfaceshader? - If it does, check the texture encodings: 8-bit truecolour PNG or baseline JPEG.
- Only then look at colour spaces and channel routing.
Steps 1 through 4 are all answerable from the bytes, which is what makes them worth automating across a catalogue rather than debugging one SKU at a time. If you want the answer for a specific file right now, the fastest route is to check it — and if the model turns out to open but render badly for a different reason, USDZ not working in Quick Look covers the rest of the taxonomy.
Common questions
- Does pink always mean a missing texture?
- No. A missing texture more often renders black or flat white, because a shader still exists. Pink or magenta specifically indicates that no usable surface shader was resolved for the prim — most often a missing material binding, or geometry relying on a displayColor primvar rather than a UsdPreviewSurface.
- Why does the model look right in Blender but pink on iPhone?
- Because your DCC can resolve absolute texture paths on your local disk and Quick Look cannot. A USDZ must contain every asset it references. Re-export with relative paths and verify the textures are inside the archive.
- Can vertex colours work in AR Quick Look?
- Not as a displayColor primvar on its own. Author a UsdPreviewSurface material, or bake the vertex colours into a base-colour texture bound through that material.
Reading about it is slower than measuring it. Drop your .glb, .gltf, or .usdz in and get every blocker on this page checked against the real bytes — packaging, textures, geometry, AR parity — with the fix for each one.