Materials, Textures, And Color Management
This tutorial connects the runtime material API with exporter-side material and color-management workflows. Use it when a scene loads correctly but its surfaces or final image do not match what you authored.
Runtime Material Editing
Untold Engine uses PBR materials. Each renderable entity can have one or more meshes and submeshes, and each submesh owns a material.
Set base color:
Set roughness and metallic:
updateMaterialRoughness(entityId: entity, roughness: 0.35)
updateMaterialMetallic(entityId: entity, metallic: 1.0)
Set opacity:
Setting opacity below 1.0 automatically switches the material to blend mode.
For scene-wide visibility modes, prefer scene channels instead of opacity.
Target A Specific Submesh
Material APIs accept optional meshIndex and submeshIndex values:
Use this when a model has multiple material slots and only one surface needs to change.
Runtime Textures
Set a texture:
updateMaterialTexture(
entityId: entity,
textureType: .baseColor,
path: URL(fileURLWithPath: "/path/to/GameData/Textures/brick_basecolor.png")
)
Remove a texture:
Adjust UV tiling:
updateMaterialSTScale(entityId: entity, stScale: 4.0)
updateTextureSampler(entityId: entity, textureType: .baseColor, wrapMode: .repeat)
Material changes automatically notify static batching when needed.
Complex Blender Materials
The exporter only reads a fixed set of material inputs (base color,
roughness, metallic, normal, emissive). Inputs driven by procedural nodes
with no image behind them are baked by the export itself (see Procedural
materials). If a Blender
material uses other node graphs the runtime cannot evaluate directly —
image textures blended by Mix, adjusted by Math, or other complex graph
behavior — bake it to flat textures with a third-party tool before export
so the imported result matches Blender. The Blender addon's Untold
Materials panel (Scan Materials) tells you which materials diverge and
why; see Using The Blender
Plugin.
Color Grading
Blender's View Transform, Look, Exposure, and Gamma are scene-wide display
settings, not part of a normal mesh import. The engine approximates them
with a native tonemap operator (setPostFX(.tonemapOperator(_)), ACES
Filmic by default, AgX also available) and composes an optional
externally-authored .cube creative grade on top:
untoldengine export \
--input GameData/Models/office/office.usdz \
--output GameData/Models/office/office.untold \
--color-grade-lut GameData/LUTs/warm_grade.cube
Then load scene-authored data:
loadSceneAuthored(filename: "office", withExtension: "untold") { success in
// Scene-authored lights/cameras and the .cube grade are registered.
}
For tiled scenes:
Toggle the grade for comparison:
See Using Color Management for the full
picture, including the standalone setColorGradeLUT API.
Texture Optimization
After export, use the texture baker when you need optimized runtime textures:
untoldengine texbake --dir GameData/Models/robot/Textures
untoldengine texbake --patch-refs GameData/Models/robot/robot.untold
Use this as part of production optimization, not as a first debugging step. First confirm the unoptimized asset looks correct.
Practical Debug Order
When a material does not look right:
- Confirm the
.untoldasset loads successfully. - Check base color, roughness, metallic, normal, and opacity.
- If Blender node graphs are involved, read the material fidelity report at the end of the export log: it says what the export baked and what still differs. Bake those with a third-party tool before re-exporting.
- If the whole image tone differs from Blender, try switching the tonemap
operator (
.aces/.agx) or applying a--color-grade-lut. - If runtime memory or package size is high, apply texture baking/optimization.