The engine uses a PBR (Physically Based Rendering) material model. Each entity's mesh can contain one or more submeshes, and each submesh holds its own Material. You can read and write individual material properties at runtime using the functions below.
All material functions accept optional meshIndex and submeshIndex parameters (both default to 0) so you can target a specific submesh when an entity contains more than one.
Note: Every update function automatically refreshes static batching for the affected entity, so you do not need to do this manually.
The base color is stored as a simd_float4 (RGBA). The .w component doubles as the opacity channel.
let color = getMaterialBaseColor(entityId: entity)
// color.x = red, color.y = green, color.z = blue, color.w = alphaupdateMaterialColor(entityId: entity, color: .red)This converts the SwiftUI Color to RGBA internally. If the alpha is below 1.0, the material automatically switches to .blend alpha mode.
Controls how rough or smooth a surface appears. A value of 0.0 is perfectly smooth (mirror-like reflections) and 1.0 is fully rough (diffuse).
let roughness = getMaterialRoughness(entityId: entity)updateMaterialRoughness(entityId: entity, roughness: 0.3)When a roughness texture is present, the scalar value acts as a modulator (multiplied with the texture sample in the shader). If you remove the texture, the scalar value is used directly.
Controls how metallic a surface appears. 0.0 is fully dielectric (plastic, wood, etc.) and 1.0 is fully metallic.
let metallic = getMaterialMetallic(entityId: entity)updateMaterialMetallic(entityId: entity, metallic: 1.0)Like roughness, the scalar value modulates the metallic texture when one is present.
Controls self-illumination. The value is a simd_float3 (RGB) representing the emitted light color and intensity. A value of .zero means no emission.
let emissive = getMaterialEmmissive(entityId: entity)updateMaterialEmmisive(entityId: entity, emmissive: simd_float3(1.0, 0.5, 0.0))To apply the same emissive value to an entity and all of its scenegraph descendants:
updateMaterialEmmisive(entityId: rootEntity, emmissive: simd_float3(1.0, 0.5, 0.0), recursive: true)Spelling note: The API currently uses
getMaterialEmmissive/updateMaterialEmmisive(with double-m). Use these exact names when calling the functions.
Determines how the renderer handles transparency for this material.
.opaque— Fully opaque. Alpha channel is ignored..mask— Binary transparency. Pixels with alpha below the cutoff are discarded; the rest are fully opaque. Useful for foliage, fences, etc..blend— Smooth alpha blending. Pixels are composited based on their alpha value.
let mode = getMaterialAlphaMode(entityId: entity) // returns MaterialAlphaModeupdateMaterialAlphaMode(entityId: entity, mode: .blend)Used only when the alpha mode is .mask. Pixels with alpha below this threshold are discarded. The value is clamped to 0.0 ... 1.0. Default is 0.5.
let cutoff = getMaterialAlphaCutoff(entityId: entity)updateMaterialAlphaCutoff(entityId: entity, cutoff: 0.3)A convenience layer over the base color's alpha channel (.w). The value is clamped to 0.0 ... 1.0. Setting opacity below 1.0 automatically switches the alpha mode to .blend.
let opacity = getMaterialOpacity(entityId: entity)updateMaterialOpacity(entityId: entity, opacity: 0.5)By default this applies to every submesh on the entity. To target a single submesh instead:
updateMaterialOpacity(entityId: entity, opacity: 0.5, applyToAllSubmeshes: false)To apply opacity to an entity and all of its scenegraph descendants:
updateMaterialOpacity(entityId: rootEntity, opacity: 0.5, recursive: true)Or specify exact indices:
updateMaterialOpacity(entityId: entity, opacity: 0.5, meshIndex: 0, submeshIndex: 1)Each material slot (.baseColor, .roughness, .metallic, .normal — the TextureType enum) can carry an image texture in addition to its scalar/color value. When a texture is present it modulates or replaces the scalar value in the shader, as noted above for roughness and metallic.
updateMaterialTexture(
entityId: entity,
textureType: .baseColor,
path: URL(fileURLWithPath: "/path/to/GameData/Textures/brick_basecolor.png")
)The filename, extension, and containing folder are extracted from path and resolved the same way other engine assets are (relative to the game's resource bundle).
removeMaterialTexture(entityId: entity, textureType: .roughness)Clears the texture and reverts the slot to its scalar value (roughness resets to 1.0, metallic resets to 0.0). The original embedded texture, if any, is retained internally so it can be restored later.
let url = getMaterialTextureURL(entityId: entity, type: .baseColor) // URL? — nil if no texture is set
let mdlTexture = getMaterialMDLTexture(entityId: entity, type: .baseColor) // MDLTexture? — used for embedded USDZ texturesstScale controls texture-coordinate tiling/repetition across the surface:
let scale = getMaterialSTScale(entityId: entity)
updateMaterialSTScale(entityId: entity, stScale: 4.0)WrapMode is .clampToEdge or .repeat. Only .baseColor currently reports a wrap mode via the getter; updateTextureSampler applies the wrap mode to any texture slot's sampler:
let wrapMode = getTextureWrapMode(entityId: entity, textureType: .baseColor) // WrapMode?
updateTextureSampler(entityId: entity, textureType: .baseColor, wrapMode: .repeat)getMaterialBaseColor(entityId:meshIndex:submeshIndex:)→simd_float4updateMaterialColor(entityId:color:meshIndex:submeshIndex:)— sets base color from SwiftUIColorgetMaterialRoughness(entityId:meshIndex:submeshIndex:)→FloatupdateMaterialRoughness(entityId:roughness:meshIndex:submeshIndex:)getMaterialMetallic(entityId:meshIndex:submeshIndex:)→FloatupdateMaterialMetallic(entityId:metallic:meshIndex:submeshIndex:)getMaterialEmmissive(entityId:meshIndex:submeshIndex:)→simd_float3updateMaterialEmmisive(entityId:emmissive:recursive:meshIndex:submeshIndex:)getMaterialAlphaMode(entityId:meshIndex:submeshIndex:)→MaterialAlphaModeupdateMaterialAlphaMode(entityId:mode:meshIndex:submeshIndex:)getMaterialAlphaCutoff(entityId:meshIndex:submeshIndex:)→FloatupdateMaterialAlphaCutoff(entityId:cutoff:meshIndex:submeshIndex:)getMaterialOpacity(entityId:meshIndex:submeshIndex:)→FloatupdateMaterialOpacity(entityId:opacity:applyToAllSubmeshes:recursive:)updateMaterialOpacity(entityId:opacity:meshIndex:submeshIndex:)updateMaterialTexture(entityId:textureType:path:meshIndex:submeshIndex:)removeMaterialTexture(entityId:textureType:meshIndex:submeshIndex:)getMaterialTextureURL(entityId:type:meshIndex:submeshIndex:)→URL?getMaterialMDLTexture(entityId:type:meshIndex:submeshIndex:)→MDLTexture?getMaterialSTScale(entityId:meshIndex:submeshIndex:)→FloatupdateMaterialSTScale(entityId:stScale:meshIndex:submeshIndex:)getTextureWrapMode(entityId:textureType:meshIndex:submeshIndex:)→WrapMode?updateTextureSampler(entityId:textureType:wrapMode:meshIndex:submeshIndex:)