Materials

simvx.core.Material is the backend-agnostic material data SimVX feeds to both Vulkan and the WebGPU runtime. It carries colour, PBR parameters, blending mode, flags (wireframe, double-sided, unlit), and up to five texture maps.

from simvx.core import Material

red = Material(colour=(1, 0, 0, 1))
glass = Material(colour=(0.6, 0.9, 1.0, 0.4), blend="alpha")
brick = Material(albedo_map="textures/brick.png", roughness=0.8)

Constructor

Argument

Type

Default

Notes

colour

RGBA (or RGB) in 0-1

(1, 1, 1, 1)

Multiplied by albedo at shade time

metallic

float 0-1

0.0

PBR metallic factor

roughness

float 0-1

0.5

PBR roughness factor

blend

"opaque" / "alpha" / "additive"

"opaque"

Pipeline derivative selector

wireframe

bool

False

Force VK_POLYGON_MODE_LINE

double_sided

bool

False

Disable backface culling

unlit

bool

False

Skip lighting; use raw albedo

albedo_map

str / bytes / ndarray

None

Diffuse texture

normal_map

str / bytes / ndarray

None

Tangent-space normal map

metallic_roughness_map

str / bytes / ndarray

None

Packed B=metal, G=rough

emissive_map

str / bytes / ndarray

None

RGB emissive

ao_map

str / bytes / ndarray

None

Ambient occlusion

emissive_colour

3- or 4-tuple

None

RGB or RGB+intensity

emissive_strength

float

None

Scalar multiplier; folds into slot 4

Texture sources

Every *_map kwarg accepts three forms:

Filesystem / asset URI (str)

Material(albedo_map="assets/brick.png")
Material(albedo_map="pkg://my_game.assets/brick.png")   # importlib.resources URI

The backend’s TextureManager resolves the path, decodes the image, and caches by (source, filter).

Embedded image bytes (bytes)

with open("brick.png", "rb") as f:
    Material(albedo_map=f.read())

Useful when bundling a single-file game export: the bytes ship in the Python source.

NumPy ndarray (in-memory texture)

import numpy as np

# 256x256 procedural ramp, RGBA uint8
ramp = np.zeros((256, 256, 4), dtype=np.uint8)
ramp[..., 0] = np.linspace(0, 255, 256, dtype=np.uint8)[None, :]
ramp[..., 3] = 255
Material(albedo_map=ramp, unlit=True)

# 2D height-tinted gradient, float32 in [0, 1]
gradient = np.zeros((1, 256, 4), dtype=np.float32)
gradient[..., 0] = np.linspace(0.1, 1.0, 256)     # R rises with height
gradient[..., 3] = 1.0                            # opaque
Material(albedo_map=gradient)

The constructor coerces ndarray sources to uint8 at construction:

  • uint8 → passed through unchanged.

  • float32 / float64 in [0, 1] → scaled by 255 to uint8.

  • float outside [0, 1] → WARNING logged, clipped, then scaled.

  • Other dtypes → TypeError.

This guarantees the GPU sees byte-per-channel data on every backend.

Used by

Shipped ports relying on Material(albedo_map=ndarray):

  • Procedural Planets: bakes an HSL height-and-latitude ramp into a 1D texture so the planet shader can sample without a custom ShaderMaterial (which is Vulkan-only: see the TODO entry).

  • Q1K3: bakes per-room palette ramps for the retro look.

  • HexGL: bakes a track-side speed-strip texture.

Use this pattern when:

  • You need procedural / parameterised shading on the web target (ShaderMaterial has no WGSL emission path yet).

  • You don’t want to ship binary asset files in a code-first port.

  • The texture is small enough that numpy generation is cheaper than disk I/O.

Web export compatibility

The Material data shape is identical across Vulkan and WebGPU. albedo_map=ndarray is fully supported on web: the numpy buffer ships through the resource channel as a KIND_TEXTURE upload. No additional configuration required.

What is not yet web-compatible:

  • ShaderMaterial: Vulkan-only. The WGSL emission path is tracked in TODO.md (## Tier 2 / Architectural).

Inspector visibility

All Material constructor arguments are stored as plain Python attributes on the instance (no Property descriptors yet). The editor inspector renders the colour swatch and PBR sliders via a custom material widget; texture-map fields are read-only and show the source URI / “ndarray ({H}x{W}, dtype)” summary.