The export for the last few versions uses USD Shade NodeGraphs (https://openusd.org/dev/api/class_usd_shade_node_graph.html), so to comply with UsdShade connection rules. I dont think this is well supported in the iOS viewers - which unless I am mistaken, support the "arkit" spec. "arkit" only supports a subset of USD features. When we export to USDZ, we do not comply with this subset of the USD spec.
Unreal similarly lacks support for USD NodeGraphs, and I had written a little chaser plugin for someone else to bypass the usage of NodeGraphs (see bellow).
After running this script, you will have the option to enable this chasers in the exporter UI like so :

What this chaser (a chaser is just some code that runs after the export to post-process the resulting usd stage) does is bypass the nodegraphs by connecting the materials directly onto the texture nodes. This violates some usd shade connection rules - but these are very rarely enforced and it might work for you (disclaimer : have only tried this for unreal, only suspect the issue is the same).
As I said though, the ARKIT USD spec is a small subset of what is supported in USD and so many other things may not work.
import maxUsd
from pxr import Usd
from pxr import UsdShade
from pymxs import runtime as mxs
import os, sys, glob
class matFixChaser(maxUsd.ExportChaser):
def __init__(self, factoryContext, *args, **kwargs):
super(matFixChaser, self).__init__(factoryContext, *args, **kwargs)
self.stage = factoryContext.GetStage()
def isId(self, id , shader):
info_id_attr = shader.GetIdAttr()
if info_id_attr:
id_value = info_id_attr.Get()
return id_value == id
return False
def PostExport(self):
try:
print("Running Unreal UsdPreviewSurface fix chaser...")
all_shaders = [x for x in self.stage.Traverse() if UsdShade.Shader(x)]
for prim in all_shaders:
shader = UsdShade.Shader(prim)
if self.isId('UsdPreviewSurface', shader):
# Iterate over the shader's inputs
for input in shader.GetInputs():
# Check if the input is connected
if input.HasConnectedSource():
# Get the connected path
source = input.GetConnectedSource()
source_prim = source[0].GetPrim()
# The node graph connection...
connection_path = source_prim.GetPath()
# print(connection_path)
# Instead connect to the USD texture 2d that lives inside the node graph.
new_connection = connection_path.AppendChild(source_prim.GetName())
# print(new_connection)
texture = UsdShade.Shader(self.stage.GetPrimAtPath(new_connection))
input.ConnectToSource(texture.ConnectableAPI(), 'rgb')
continue
if self.isId('UsdPrimvarReader_float2', shader):
varname_input = shader.GetInput("varname")
varname_value_attributes = varname_input.GetValueProducingAttributes()
primvar_name = varname_value_attributes[0].Get()
# Punch in value directly, not from nodegraph...
varname_input.Set(primvar_name)
except Exception as e:
print('Chaser ERROR : %s' % str(e))
print(traceback.format_exc())
return True
maxUsd.ExportChaser.Register(matFixChaser, "unrealMatFixer", "Unreal UsdPreviewSurface fix chaser", "Connects straight to textures, bypassing nodegraphs.")
def unrealMatFixContext():
extraArgs = {}
extraArgs['chaser'] = ['unrealMatFixer']
extraArgs['chaserNames'] = ['unrealMatFixer']
return extraArgs
registeredContexts = maxUsd.JobContextRegistry.ListJobContexts()
if 'unrealMatFixContext' not in registeredContexts:
maxUsd.JobContextRegistry.RegisterExportJobContext("unrealMatFixContext", "Fix USD Preview Surface for Unreal", "Fixes USD preview surface materials for usage in Unreal, avoiding NodeGraphs.", unrealMatFixContext)
print("Registered unreal material fixer")