simvx.core.graphics.material

Material: pure data (no GPU dependencies).

Backends (SDL3, Vulkan) extend this to add texture/GPU management.

Module Contents

Classes

Material

Pure material data for rendering. Backend-agnostic.

Data

log

API

simvx.core.graphics.material.log

‘getLogger(…)’

class simvx.core.graphics.material.Material(colour: tuple[float, ...] | numpy.ndarray = (1.0, 1.0, 1.0, 1.0), metallic: float = 0.0, roughness: float = 0.5, blend: Literal[opaque, alpha, additive, cutoff] = 'opaque', alpha_cutoff: float = 0.5, wireframe: bool = False, double_sided: bool = False, unlit: bool = False, albedo_map: str | bytes | None = None, normal_map: str | bytes | None = None, metallic_roughness_map: str | bytes | None = None, emissive_map: str | bytes | None = None, ao_map: str | bytes | None = None, emissive_colour: tuple[float, ...] | None = None, emissive_strength: float | None = None, needs_scene_colour: bool = False, needs_scene_depth: bool = False, uv_offset: tuple[float, float] = (0.0, 0.0), uv_scale: tuple[float, float] = (1.0, 1.0), uv_rotation: float = 0.0, wetness_affected: bool = False, receives_decals: bool = True)[source]

Pure material data for rendering. Backend-agnostic.

Every map kwarg (albedo_map, normal_map, metallic_roughness_map, emissive_map, ao_map) accepts three forms:

  • str: filesystem path or asset URI; backend loads from disk via TextureManager.

  • bytes: raw image bytes (PNG / JPEG / etc.) decoded by the backend’s image loader.

  • numpy.ndarray: an in-memory texture, shape (H, W, C) where C is 1, 3, or 4. Coerced to uint8 at construction time so the GPU always sees byte-per-channel data; float32 arrays in [0, 1] are scaled, out-of-range floats log a WARNING and clip, and unsupported dtypes raise TypeError. Used by Procedural Planets and Q1K3 to bake gradient ramps without shipping PNGs.

Example: mat = Material(colour=(1, 0, 0, 1)) # Red mat = Material(colour=(0, 1, 0), blend=”alpha”) # Translucent green mat = Material(albedo_map=”textures/brick.png”) # On-disk texture mat = Material(albedo_map=numpy_rgba_uint8) # Numpy texture

Initialization

Initialize material with colour and properties.

Args: colour: RGBA (or RGB auto-expanded to 1.0 alpha) in [0-1] metallic: [0-1] metallic factor roughness: [0-1] roughness factor blend: “opaque”, “alpha”, “additive”, or “cutoff” (alpha-tested cutout: fragments with albedo alpha below alpha_cutoff are discarded; renders in the opaque pass, no sorting) alpha_cutoff: [0-1] alpha-test threshold for blend="cutoff" wireframe: Render as wireframe double_sided: Disable backface culling unlit: Disable lighting (flat colour) albedo_map: Path or embedded bytes for albedo/diffuse texture, or a SubViewport / RenderView node whose live offscreen feed to sample. A PlanarReflection3D node is sampled with a mirrored projective UV from the fragment’s clip position (planar reflection) instead of the mesh UV. normal_map: Path or embedded bytes for normal map texture (optional) metallic_roughness_map: Path or embedded bytes for metallic-roughness texture (optional) emissive_map: Path or embedded bytes for emissive texture (optional) ao_map: Path or embedded bytes for ambient occlusion texture (optional) emissive_colour: (R, G, B) or (R, G, B, intensity) packing. If a 4-tuple, the fourth component is the intensity multiplier (legacy/round-trip form). Prefer the 3-tuple form with the separate emissive_strength kwarg. emissive_strength: Scalar multiplier applied to the emissive RGB. Stored as the 4th component of emissive_colour. May be combined with a 3-tuple emissive_colour or used alone (an opaque-white default is supplied when emissive_colour is None). needs_scene_colour: The material samples the copied opaque scene colour (screen-space refraction). Schedules the desktop opaque/transparent pass split so the transparent draw can read the scene behind it. Only meaningful for a transparent (blend="alpha") material. needs_scene_depth: The material samples the copied opaque scene depth. Rides the same pass split as needs_scene_colour. uv_offset: UV-space offset applied to every material texture sample (KHR_texture_transform). Applied after scale/rotation: uv' = rotate(uv * uv_scale, uv_rotation) + uv_offset. uv_scale: UV-space scale factor per axis. (1, 1) is identity. uv_rotation: UV rotation in radians, counter-clockwise about the UV origin. Identity transforms (all defaults) are free: the shader path is gated by a feature bit set only when any of the three deviates from identity. wetness_affected: The surface responds to global weather. Under rain (WorldEnvironment wetness / rain_intensity > 0, delivered via FrameGlobals) the shader darkens the albedo, drops the roughness (wet surfaces are glossier), and overlays an animated ripple normal on near-horizontal faces. Free when dry or unset: the shader block is gated on both this feature bit and wetness > 0, so a dry frame is byte-identical to the dry path. receives_decals: Whether Decal3D projectors composite onto this surface. True by default (every surface receives, as before); set False to exclude a surface (glass, water, skybox proxies). Combined with each decal’s cull_mask, which selects receiving render layers.

__slots__

(‘_uid’, ‘colour’, ‘metallic’, ‘roughness’, ‘blend’, ‘alpha_cutoff’, ‘wireframe’, ‘double_sided’, ‘u…

property content_key: tuple[source]

Hashable key representing all rendering-relevant properties.

Two materials with the same content_key are visually identical and can share a single GPU material slot. Texture fields that hold an unhashable source (e.g. a numpy ndarray passed via TextureManager) are fingerprinted by id() so the key stays hashable without mutating the source.

property colour_bytes: bytes[source]

RGBA as 16 bytes (4x float32) for GPU upload.

property emissive_strength: float[source]

Scalar emissive intensity (the 4th slot of emissive_colour).

Returns 0.0 when no emissive colour is configured. Setting this mutates the intensity component without changing the RGB; a default of opaque white is supplied when no colour is present yet.